Skip to main content
Version: Development

profiles

The Profiles engine processes declarative API requests with dynamic data source evaluation. The system implements a request/response pattern, supporting chained operations, allowing multiple API calls from a single entry point. This can be parsed from HCL or JSON as desired.

Data sources are dependent on the profile call site: self-initialization has one set of allowed sources whereas the workflows API has a different combination. Data sources can be nested; for instance, a template source can be used to combine an environment variable and the contents of a file.

info

All requests are subject to the same restrictions as if they were executed normally: audit logging, authentication, and API variable restrictions (such as unauthenticated rotation and audit device creation) still apply.

Profiles end to end workflow

  1. Profile Creation

A privileged operator designs the profile; this involves creating a configuration which describes what API endpoints to call. If this profile is executed from the API workflow system, it also involves defining input and output parameters.

This configuration either ends up in one or more files (if using declarative self-initialization) or in a profile under the sys/profiles/manage/ endpoint.

  1. Input Validation

During execution, before any OpenBao operation is attempted, the engine:

  • Ensures every declared parameter is present and of the correct type, e.g., string, int, bool.
  • Confirms that each referenced source (environment variable, file path, earlier request, earlier response) is available.
  • Rejects a run immediately if required inputs are missing, preventing half-configured states.

Certain engines, like template or CEL cannot fully validate the execution environment and thus may err at runtime.

  1. Synchronous Execution

Requests are executed exactly in the order they are listed. Within a single profile run, there is no parallelism, guaranteeing that any request or response references always point to data that already exists. Additionally, requests are fully synchronous and are not executed in the background.

If a request fails unexpectedly, the engine stops immediately and returns an error to the caller; nothing after that point is attempted.

History of all requests and responses are recorded, though, are not exposed to the caller by default.

  1. Output Generation

Depending on context, the profile system may output information back to the caller. In the case of self-initialization, this information is logged at the Trace level; this will contain privileged information and should not be exposed to arbitrary users.

Request format

The core of a profile is the request, which contains the following fields:

  • name (string: <required>) - the human-readable name of this request. This is specified via a key to the HCL request block.
  • operation (string: <required>) - the operation type to invoke. This aligns with ACL operations.
  • path (string: <required>) - which API endpoint to invoke.
  • token (string: <optional>) - the OpenBao token to use to authenticate with; in some cases (like self-initialization or authenticated execution of the workflows API), this is inferred from the context automatically. However, in the case of unauthenticated execution of the workflows API, this would need to be manually handled as none exists in the execution environment. Unauthenticated APIs (such as login requests) can continue to be called without this parameter or with this parameter explicitly set to the empty string.
  • data (map[string]any: <optional>) - any parameters for this request.
  • headers (map[string][]string: <optional>) - any headers for this request.
  • allow_failure (bool: false) - whether to allow profile execution to continue despite failure of this request.
  • when (bool: true) - when false, skips execution of the request.

Other than name which must be static, all of the remaining fields can use dynamic data sources as described below.

In HCL this translates to:

request "create-admin-policy" {
operation = "create"
path = "/sys/policies/acl/admin"
token = "my-root-token"
data = {
policy = "my-acl-policy"
}
}

Logical groupings

Multiple requests can be grouped into a logical organization structure in certain profile execution environments:

  • initialize -- in declarative self-initialization
  • flow -- in the workflows API

When doing so, the name of this outer grouping block is used for the request and response dynamic data source and must be specified.

Input

When using the workflows API, input passed by the caller can be specified in an input block. This has the following named child blocks:

  • field ([]FieldSchema: <optional>) - a field definition to be parsed from the API request's data.

FieldSchema

The FieldSchema definition takes the following values:

  • type (string: <required>) - the type of this field. This can be specified as a key parameter on the block definition or as an explicit parameter.
  • name (string: <required>) - a name to reference this field by; this is used in the CEL and templating engines below. This can be specified as a key parameter on the block definition or as an explicit parameter.
  • default (any: <optional>) - the default field value; if the field is not required this field is required to have a value.
  • description (string: <optional>) - a description of this field.
  • required (bool: false) - whether this field is required.
  • deprecated (bool: false) - whether this field is deprecated.
  • query (bool: false) - whether this field is part of the request's query strings.
  • allowed_values ([]any: <optional>) - an allow-list of possible values for this field.

Known field types are:

  • string;
  • int;
  • int64;
  • bool;
  • map, for a generic map with string keys;
  • duration_second, for unsigned duration;
  • signed_duration_second;
  • slice;
  • comma_slice_string, for a field which is either a slice of strings or a single string which is potentially comma-separated.
  • lower_case_string;
  • name_string, a URI-safe name;
  • kv_pairs;
  • comma_int_slice, for a field which is either a slice of ints or a single string which is a potentially comma-separated list of ints.
  • header, for parsing headers from intermediary systems which strip headers and send them via request data;
  • float; and
  • time.

For example:

input {
field {
type = "string"
name = "namespace"
description = "name of the namespace to create"
required = true
}

field "namespace" {
type = "string"
description = "name of the namespace to create"
required = true
}

field "string" {
name = "namespace"
description = "name of the namespace to create"
required = true
}
}

Output

When using the workflows API, output returned to the caller must be specified in an output block. This takes the following values:

  • data - (map[string]any: <optional>) - any output information to be returned to the caller.
  • headers - (map[string][]string: <optional>) - any response headers to be included.

Note that both of these fields can use dynamic data sources to reference earlier responses.

For example:

output {
data = {
password = {
eval_source = "response"
eval_type = "string"
flow_name = "generate-password"
response_name = "generate"
field_selector = ["data", "password"]
}
}
}

Value types and dynamic data sources

Values may be of the following types:

  • A literal, such as a string or map. For example the following are valid literals:

    path = "sys/audit/stdout"

    or

    data = {
    type = "file"
    }
  • A data source evaluation. These take the form:

    {
    eval_source = "<source name>"
    eval_type = "<resulting type>"
    ... additional source-specific parameters ...
    }

    where

    • eval_source (string: <required>) - the name of the given source; valid values are defined below.
    • eval_type (string: <required>) - the output type after evaluation; recognized Go types are string, int, float64, bool, []string, map (alias for map[string]interface{}), and any (alias for interface{}). If the output of the source evaluation is not convertible to the desired type, a runtime error will occur.

Note that fields within a source evaluation statement may not be themselves be source evaluations.

env source

info

The env source is only available for declarative self-initialization; it is not available for the workflows API.

  • env_var (string: <required>) - the name of an environment variable to return the value of.
  • require_present (bool: false) - when asserted, require that the named environment variable must be present and err otherwise.

For example, to reference an initial admin password from an environment variable:

request "userpass-add-admin" {
operation = "update"
path = "auth/userpass/users/admin"
data = {
"password" = {
eval_type = "string"
eval_source = "env"
env_var = "INITIAL_ADMIN_PASSWORD"
require_present = true
}
"token_policies" = ["superuser"]
}
}

file source

info

The file source is only available for declarative self-initialization; it is not available for the workflows API.

  • path (string: <required>) - the path to the file to read. This file is only ever read once and is closed after use; thus a single piece of data could be provided on /dev/stdin. Errs if the file is not readable.

For example, to load a X.509 root CA into a PKI engine:

request "provision-root-ca" {
operation = "update"
path = "pki/issuers/import/cert"
data = {
"pem_bundle" = {
eval_type = "string"
eval_source = "file"
path = "/data/root-ca.pem"
}
}
}

request source

  • <outer-block>_name (string: <initialize_name|flow_name>) - name of the outer block to reference. <outer-block> is the type of the outer block; for self-init, this is initialize_name; for profiles, this is flow_name.
  • request_name (string: <required>) - name of the request block inside the initialize block to reference. Must have already been executed.
  • field_selector (string or []string: <required>) - field within the request to reference; for nesting, specify multiple values as a list. The request is marshalled directly; for example, to reference the path, use field_selector = "path"; to reference a specific field of input data, use field_selector = ["data", "my_top_level_field"].

For example, to reference a previous request's input for a subsequent call:

initialize "namespace-identity" {
request "namespace-userpass-add-admin" {
operation = "update"
path = "ns1/auth/userpass/users/admin"
data = {
"password" = {
eval_type = "string"
eval_source = "request"
initialize_name = "identity"
request_name = "userpass-add-admin"
field_selector = ["data", "password"]
}
"token_policies" = ["superuser"]
}
}
}

response source

  • <outer-block>_name (string: <initialize_name|flow_name>) - name of the outer block block to reference. <outer-block> is the type of the outer block; for self-init, this is initialize_name; for profiles, this is flow_name.
  • request_name (string: <required>) - name of the request block inside the initialize block, to reference the corresponding response of. Must have already been executed.
  • field_selector (string or []string: <required>) - field within the response to reference; for nesting, specify multiple values as a list. The response is marshalled directly; for example, to reference a specific field of output data, use field_selector = ["data", "my_top_level_field"].

For example, to reference a login token in a subsequent call:

initialize "example" {
request "use-auth" {
operation = "update"
path = "sys/namespaces/ns1"
token = {
eval_type = "string"
eval_source = "response"
initialize_name = "authenticate"
response_name = "admin-userpass"
field_selector = ["auth", "client_token"]
}
data = {}
}
}

cel source

  • expression (string: <required>) - the CEL expression to evaluate; errs if not present.
  • variables ([]map["name": string, "expression": string]: <optional>): a list of variables to evaluate and inject into the program's context; each entry in the list is a map containing two keys:
    • name (string), the name of the variable,
    • expression (string), the CEL expression to evaluate for this variable's value.

Additionally, the following reserved values are injected into the CEL context when allowed as sources:

  • requests, if the request source is allowed
  • responses, if the response source is allowed
  • input, if the input source is allowed

For example, to sum the value of two responses:

request "sum" {
operation = "update"
path = "secret/data/my-secret"
data = {
"secret_number" = {
eval_type = "int"
eval_source = "cel"
expression = "requests.first.read.my_field + requests.second.read.my_other_field"
}
}
}

template source

  • template (string: <required>) - the template expression to evaluate; errs if not present.
  • data (map[string]interface{}: <optional>): additional context for the templating engine.

Additionally, the following reserved values are injected into the text/template context when allowed as sources:

  • requests, if the request source is allowed
  • responses, if the response source is allowed
  • input, if the input source is allowed

For example, inject the hash of a password:

request "password" {
operation = "update"
path = "secret/data/my-secret"
data = {
pem_bundle = {
eval_type = "string"
eval_source = "template"
template = "{{ .responses.database.read-password.password | sha256 }}"
}
}
}

input source

info

The input source is only available for the workflows API; it is not available for declarative self-initialization.

Use of the input source requires a corresponding input block definition.

  • field_name (string: <required>) - the field of the input request parameter to read.

For example, if the parameter password was specified in a workflow:

input {
field "string" "password" {
description = "Password to be stored."
required = true
}
}

flow "provision" {
request "password" {
operation = "update"
path = "secret/data/my-secret"
data = {
"pem_bundle" = {
eval_type = "string"
eval_source = "input"
field_name = "password"
}
}
}
}

Example Workflow

The following example Workflow allows generating a password using a pre-existing password policy and saving it directly into a KVv2 engine. Additionally it returns the password to the caller as an output.

input {
field "policy" {
type = "string"
required = true
}

field "mount" {
type = "string"
required = true
}

field "secret" {
type = "string"
required = true
}
}

flow "generate-password" {
request "generate" {
operation = "read"
path = {
eval_source = "template"
eval_type = "string"
template = "sys/policies/password/{{ .input.policy }}/generate"
}
}

request "store" {
operation = "update"
path = {
eval_source = "template"
eval_type = "string"
template = "{{ .input.mount }}/data/{{ .input.secret }}"
}
data = {
data = {
"password" = {
eval_source = "response"
eval_type = "string"
flow_name = "generate-password"
response_name = "generate"
field_selector = ["data", "password"]
}
}
}
}
}

output {
data = {
password = {
eval_source = "response"
eval_type = "string"
flow_name = "generate-password"
response_name = "generate"
field_selector = ["data", "password"]
}
}
}