Secrets management

API Mesh for Adobe Developer App Builder allows you to manage secrets for a mesh. You can use these secrets in your mesh configuration file to securely manage sensitive information. When creating or updating a mesh, you can provide a separate file that defines your secrets. API Mesh encrypts secrets using AES-256 encryption.

For security purposes, you cannot receive the secrets once you define them when creating or updating a mesh. For example, running an aio api-mesh get command returns your mesh with the values defined within the mesh configuration file, such as {context.secrets.SECRET} and does not return the actual secret's value.

Secrets limits

API Mesh supports the following limits for secrets management:

Create a secrets file

Create a YAML file, such as secrets.yaml, to define your secrets. The file name must end with the yaml or yml file extension. Each line in the files defines a different secret.

The following example contains a Bash variable as a value for the TOKEN secret. API Mesh supports strings and numbers with or without single or double quotes and Bash variables with or without brackets, such as $TOKEN or $[TOKEN].

data-variant=info
data-slots=text
Bash variables are not supported in Windows.
# String values
TOKEN: $TOKEN
USERNAME: user-name
adminname: 'admin-name'
AEM_USERNAME: "user-name"

# Bash variables
API_KEY: ${COMMERCE_API_KEY}
API_KEY2: $COMMERCE_API_KEY

# Boolean values
ENABLE_FEATURE: true
DEBUG_MODE: false

# Numeric values
MAX_RETRIES: 3
TIMEOUT: 5000

# Object values
DATABASE_CONFIG:
  host: "db.example.com"
  port: 1234
  ssl: true

# Array values
ALLOWED_ORIGINS:
  - "https://example.com"
  - "https://app.example.com"
data-variant=info
data-slots=text, text
When referencing object properties, use dot notation ({context.secrets.OBJECT.key}). Arrays are passed as their full JSON representation.
You must escape a literal $ character in the secrets file. If the $ is not part of a Bash variable, escape it with a backslash (\): SECRET: \$SECRET.

Add secrets to your mesh configuration file

Once you have created your secrets.yaml file, you can reference the secrets in your mesh configuration file. You can use secrets in the following locations:

When using secrets with operational headers, use the template literals syntax, for example, {context.secrets.<SECRET_NAME>}.

When using secrets in JavaScript files using hooks or resolvers, use the secret in context, for example, const secretValue = context.secrets.<SECRET_NAME>.

The following file provides an example using operational headers with different data types:

{
  "meshConfig": {
    "sources": [
      {
        "name": "ExampleSource",
        "handler": {
          "JsonSchema": {
            "baseUrl": "https://example.com",
            "operationHeaders": {
              "secret": "{context.secrets.TOKEN}",
              "api-key": "{context.secrets.API_KEY}",
              "enable-feature": "{context.secrets.ENABLE_FEATURE}",
              "max-retries": "{context.secrets.MAX_RETRIES}",
              "db-host": "{context.secrets.DATABASE_CONFIG.host}",
              "allowed-origins": "{context.secrets.ALLOWED_ORIGINS}"
            }
          }
        }
      }
    ]
  }
}

For more complex use cases, see Examples.

Create or update your mesh secrets

When you create or update a mesh that you want to include secrets in, add the --secrets flag followed by the path to your secrets file. If you do not provide the secrets file when updating a mesh that has secrets, the secrets` values are overwritten by their literal references.

data-slots=heading, code
data-repeat=2
data-languages=bash, bash

Create

aio api-mesh create mesh.json --secrets secrets.yaml

Update

aio api-mesh update mesh.json --secrets secrets.yaml

Your mesh now contains the secrets defined in your secrets.yaml file.

Examples

The following examples demonstrate different use cases in which using secrets management is beneficial.

Header reflection

The following example mesh configuration uses a header reflection service to demonstrate how you can pass your secrets as headers. This can be useful to test and debug your configuration.

data-variant=warning
data-slots=text
Do not use sensitive data with this example, since it is designed to display your secrets as headers.

The getHeadersSchema.json tab contains the JSON file referenced in the example mesh's operations object. This file provides the required response schema. Copy the file into the same folder as your mesh.json file before creating or updating your mesh.

data-slots=heading, code
data-repeat=3
data-languages=json, yaml, json

mesh.json

{
  "meshConfig": {
    "sources": [
      {
        "name": "headersData",
        "handler": {
          "JsonSchema": {
            "baseUrl": "<header-reflection-service>",
            "operations": [
              {
                "type": "Query",
                "field": "data",
                "path": "/",
                "method": "GET",
                "responseSchema": "./getHeadersSchema.json"
              }
            ],
            "operationHeaders": {
              "secretHeader": "{context.secrets.API_KEY}",
              "secretAEMHeader": "{context.secrets.TOKEN}"
            }
          }
        }
      }
    ],
  }
}

secrets.yaml

TOKEN: "my-token"
AEMUSERNAME: "user-name"
adminname: "admin-name"

API_KEY: $COMMERCE_API_KEY

getHeadersSchema.json

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "required": [
    "headerKeys",
    "headerValues",
    "headers"
  ],
  "properties": {
    "headerKeys": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "headerValues": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "headers": {
      "type": "object"
    }
  }
}

Use the following GraphQL query to retrieve the headers. This query will vary depending on the header reflection service you are using.

data-slots=heading, code
data-repeat=2
data-languages=json, json

GraphQL

{
  data {
    headers
  }
}

Response

{
  "data": {
    "data": {
      "headers": {
        "accept-encoding": "gzip, br",
        "cf-connecting-ip": "0.0.0.0",
        "cf-ipcountry": "US",
        "cf-ray": "abc123abc123",
        "cf-visitor": "{\"scheme\":\"https\"}",
        "connection": "keep-alive",
        "host": "header-reflection-service",
        "secretaemeader": "abcabcdefdefxyzxyz",
        "secretheader": "\\/root",
        "x-forwarded-proto": "https",
        "x-real-ip": "0.0.0.0"
      }
    }
  },
  "extensions": {}
}

Authorization

The following example provides a simple authorization test. This mesh only returns a valid response, if the TOKEN in the secrets.yaml file is also passed as an authorization header in the request. If the token does not match, the mesh will return an unauthorized error.

The hooks.js tab contains the JavaScript file referenced in the example mesh's plugins object. This file provides the required composer. Copy the file into the same folder as your mesh.json file before creating or updating your mesh.

data-slots=heading, code
data-repeat=3
data-languages=json, yaml, javascript

mesh.json

{
  "meshConfig": {
    "sources": [
      {
        "name": "Commerce",
        "handler": {
          "graphql": {
            "endpoint": "https://venia.magento.com/graphql"
          }
        }
      }
    ],
    "plugins": [
      {
        "hooks": {
          "beforeAll": {
            "composer": "./hooks.js#isAuth",
            "blocking": true
          }
        }
      }
    ],
  }
}

secrets.yaml

TOKEN: "abcabcdefdefxyzxyz"

hooks.js

module.exports = {
    isAuth: ({
        context
    }) => {
        const {
            headers,
            secrets
        } = context;

        if (headers.authorization != secrets.TOKEN) {
            return {
                status: "ERROR",
                message: "Unauthorized",
            };
        } else {
            return {
                status: "SUCCESS",
                message: "Authorized",
            };
        }
    },
};

After adding the token from the secrets.yaml file to your authorization header, run the following query:

data-slots=heading, code
data-repeat=3
data-languages=json, json, json

Query

{
  storeConfig {
    base_url
  }
}

Authorized response

{
  "data": {
    "storeConfig": {
      "base_url": "https://www.example.com"
    }
  },
  "extensions": {}
}

Unauthorized response

{
  "data": null,
  "errors": [
    "Unauthorized"
  ]
}