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 API profiles have 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 profile 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 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 API profiles), this is inferred from the context automatically. However, in the case of unauthenticated execution of API profiles, 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
  • context -- in API profiles

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

Output

When using API profiles, 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.

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 API profiles

  • 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 API profiles

  • 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: <required>) - 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 context_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: <required>) - 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 context_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 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, 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 API profiles; it is not available for declarative self-initialization.

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

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

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