Skip to content

Configuration AWS secrets

View Markdown

Status: Stable Package: dev.vertique.config.awssecrets Artifact: vertique-config-aws-secrets Depends on: config API reference: javadoc.io

Provides an AWS Secrets Manager property source for the config.propertySources placeholder chain. At construction time the module fetches every declared secret from Secrets Manager, builds an immutable in-memory map, and serves all subsequent lookup() calls from that map with no further I/O. The module integrates with the ConfigPropertySourceFactory SPI and is discovered automatically via ServiceLoader — no Dagger module is required.

This module is not a Vert.x config store (config.stores). It is a property source consulted only during placeholder resolution (${...} pass 3). It never performs I/O after bootstrap.


Install vertique-config-aws-secrets when secrets stored in AWS Secrets Manager must be injected into the application config tree at startup via ${key} placeholder references. Pair it with vertique-config (already a transitive dependency) and vertique-launcher for full bootstrap integration.

Use the self-reference idiom (see Core Concepts) to pull secrets into the tree without writing any value in a config file.


All declared secrets are fetched once, at source construction time during bootstrap pass 2 (source creation). The resulting map is immutable. Any fetch failure — secret not found, access denied, binary secret, or network error — aborts startup immediately with a ConfigPropertySourceException naming the source and secret ID but never the secret value. Placeholder defaults do not override this: if a source is declared and fails to load, bootstrap fails before pass 3 (placeholder resolution) runs. The SDK client is closed immediately after the eager fetch; no further SDK calls occur.

Each entry in the secrets array is processed in one of two mutually exclusive modes. Exactly one of prefix or key must be present per entry — both or neither is a schema error.

Prefix mode ("prefix": "db.") — the secret’s SecretString must be a JSON object. Its keys are flattened under the declared prefix using SecretDataFlattener from the config module. Nested JSON objects are flattened with dot-separators: a key {"nested": {"host": "db-host"}} stored under prefix "db." produces db.nested.host. If the SecretString is not a valid JSON object, startup is aborted with a ConfigPropertySourceException that names the secret ID but never its content.

Key mode ("key": "api.token") — the secret’s SecretString is stored as-is under exactly the declared key, regardless of whether it looks like JSON. The content is never inspected or parsed.

When two secrets produce the same resolved key, the later entry in the declaration order wins.

Binary secrets (SecretBinary set, SecretString null), missing secrets, access-denied responses, and all network or SDK errors all abort startup. None of these conditions degrades to empty-string or missing-key — partial configuration is a startup failure, not a silent default.

Credentials are resolved via the AWS SDK default credential provider chain: environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY), Java system properties (aws.accessKeyId, aws.secretAccessKey), ~/.aws/credentials, EC2/ECS instance metadata, and IAM roles. Credentials are never configured inline in the source declaration and never appear in error messages.

The recommended pattern for pulling an AWS secret into the config tree without writing any value in a config file:

{
"db": {
"password": "${db.password}"
},
"config": {
"propertySources": [
{
"name": "aws-app",
"type": "aws-secrets",
"region": "eu-west-1",
"secrets": [
{ "secretId": "prod/db-creds", "prefix": "db." }
]
}
]
}
}

When the resolution engine encounters ${db.password} and the db.password key is already on the stack, the recursive tree probe is skipped. Resolution falls through to the AWS Secrets source, which supplies the value under the prefixed key db.password.


The full per-source configuration block under config.propertySources[*]:

{
"name": "aws-app",
"type": "aws-secrets",
"region": "eu-west-1",
"endpointOverride": "http://localhost:4566",
"connectTimeoutMs": 5000,
"readTimeoutMs": 5000,
"secrets": [
{ "secretId": "prod/db-creds", "prefix": "db." },
{ "secretId": "prod/api-token", "key": "api.token" }
]
}
Field Required Default Description
type Yes — Must be "aws-secrets"
name No aws-secrets[{index}] Source instance name for diagnostics
region No SDK default region chain AWS region string (e.g. "eu-west-1"). When absent, the SDK resolves the region via AWS_DEFAULT_REGION, aws.region, instance metadata, etc. Passed to the SDK unvalidated (see below).
endpointOverride No none Override the Secrets Manager endpoint URL. Use for LocalStack (http://localhost:4566) or other test endpoints. Passed to the SDK unvalidated (see below).
connectTimeoutMs No 5000 Connection timeout in milliseconds, applied to UrlConnectionHttpClient
readTimeoutMs No 5000 Socket/read timeout in milliseconds, applied to UrlConnectionHttpClient
secrets Yes — Non-empty array of secret entry objects

Each element of the secrets array:

Field Required Description
secretId Yes AWS Secrets Manager secret ID or full ARN. Non-blank.
prefix Exactly one of prefix/key Prefix prepended to all keys produced by flattening the JSON blob SecretString. Empty string "" is allowed — keys appear without a prefix.
key Exactly one of prefix/key Single property key under which the plain SecretString is exposed as-is.

Validation errors (thrown during bootstrap pass 2):

  • Missing or empty secrets array
  • A secrets element that is not a JSON object (null, string, number, boolean, or array). The ConfigPropertySourceException names the source and the element’s index and the JSON type it found, never the element’s value.
  • Missing or blank secretId
  • Both prefix and key present on the same entry
  • Neither prefix nor key present on an entry

region and endpointOverride Are Not Validated

Section titled “region and endpointOverride Are Not Validated”

region and endpointOverride are handed to the AWS SDK as given: a non-null region becomes Region.of(...) and a non-null endpointOverride becomes URI.create(...). This module does not check either value. An unknown region string or a malformed endpoint URI surfaces as the SDK’s or JDK’s own exception (for example IllegalArgumentException) from create(), not as a ConfigPropertySourceException; bootstrap still fails, so the fail-closed outcome holds. Every other validation error listed above is a ConfigPropertySourceException. This passthrough is deliberate and stable: the SDK’s messages are more precise than anything this module could synthesize.


public class AwsSecretsPropertySourceFactory implements ConfigPropertySourceFactory

The factory discovered by ServiceLoader. Registered in META-INF/services/dev.vertique.config.source.ConfigPropertySourceFactory with type key "aws-secrets".

Method Description
type() Returns "aws-secrets"
create(String name, JsonObject sourceConfig) Parses and validates the source config, builds connection settings, constructs an SdkSecretsGateway, and returns an AwsSecretsPropertySource that eagerly fetches all declared secrets. Throws ConfigPropertySourceException on any validation or fetch failure.
  • create() is fail-closed: any declared secret that returns SecretBinary (not SecretString), is missing, or cannot be reached throws ConfigPropertySourceException and aborts bootstrap. This holds even when the AWS SDK exception carries no error details; the message then falls back to the SDK exception’s own message.
  • Schema validation happens before any SDK call. A malformed secrets entry fails before the first network request is made.
  • create() is called from the single-threaded bootstrap path; no thread-safety is required.

class AwsSecretsPropertySource implements ConfigPropertySource (package-private)

Holds the immutable in-memory map built at construction time. The SDK client is closed by this class immediately after all secrets are fetched; no further SDK I/O occurs during the source’s lifetime.

Method Description
name() Returns the source instance name
lookup(String key) Map lookup — no I/O. Returns Optional.empty() for keys absent from the map.
close() No-op. The SDK client was already closed during construction.

Logs the number of loaded keys and secrets at INFO level on construction. Never logs key values or secret content.


The module is registered as a ConfigPropertySourceFactory extension via ServiceLoader. No Dagger module is needed.

META-INF/services/dev.vertique.config.source.ConfigPropertySourceFactory contains:

dev.vertique.config.awssecrets.AwsSecretsPropertySourceFactory

To use the source, add vertique-config-aws-secrets to the application’s pom.xml and declare an aws-secrets entry under config.propertySources:

{
"config": {
"propertySources": [
{
"name": "aws-app",
"type": "aws-secrets",
"region": "eu-west-1",
"secrets": [
{ "secretId": "prod/db-creds", "prefix": "db." }
]
}
]
}
}

The source config subtree is resolved in pass 1 (tree-only), so region, endpointOverride, and any other fields may themselves be ${...} references to environment variables or tree keys — as long as they resolve without needing a property source.


NFR-CONF-002 applies throughout this module:

  • Secret values never appear in log output at any level, in exception messages, or in toString() of any internal type.
  • Exception messages contain only: source instance name, secret IDs (structural identifiers, safe to echo), field names, structural descriptions, and the AWS SDK’s own error message text. GetSecretValue error bodies describe operational failures (access-denied, not-found, etc.) and do not carry secret values; the module passes those messages through as-is.
  • Binary secrets surface a descriptive error naming the secret ID and the reason (binary secret; only SecretString secrets are supported) with no content.
  • Credentials are resolved via the AWS SDK default credential provider chain. They are never configured inline in the source declaration, stored in memory beyond the SDK client’s internal lifecycle, or included in error messages.
  • The endpointOverride field (a URL, not a secret) is safe to include in error and log messages. It is treated as a structural identifier.

  • dev.vertique:vertique-config-core — ConfigPropertySource, ConfigPropertySourceFactory, ConfigPropertySourceException, SecretDataFlattener SPIs
  • software.amazon.awssdk:secretsmanager — AWS SDK v2 Secrets Manager client (version managed via aws.sdk.version BOM property in the parent POM)
  • software.amazon.awssdk:url-connection-client — lightweight JDK-based HTTP client for the SDK; avoids an Apache HttpClient runtime dependency
  • io.vertx:vertx-core — JsonObject, JsonArray (config parsing)

The two AWS SDK artifacts are governed by the software.amazon.awssdk:bom BOM, imported at version ${aws.sdk.version} in the parent <dependencyManagement>. Application POMs do not need to declare SDK versions directly.

No Dagger runtime dependency. No Vert.x web dependency.