tom_core_secrets 0.1.0
tom_core_secrets: ^0.1.0 copied to clipboard
Cloud secret stores as secrets providers for Tom servers — configuration values written as secret:<provider>:<path>#<field> references, resolved at start and refreshed on rotation. One library per pro [...]
tom_core_secrets #
Published on pub.dev as tom_core_secrets 0.1.0.
Attribution. The Tom Framework is developed by Peter Nicolai Alexis Kyaw. These packages are BSD-3-Clause licensed and published on pub.dev.
Cloud secret stores as secrets providers for Tom servers — configuration values
written as secret:<provider>:<path>#<field> references, resolved at boot and
refreshed on rotation. One library per provider:
| Library | Provider | Registered as |
|---|---|---|
package:tom_core_secrets/aws.dart |
AWS Secrets Manager — TomAwsSecretsProvider |
aws |
package:tom_core_secrets/azure.dart |
Azure Key Vault — TomAzureKeyVaultSecretsProvider |
azure |
package:tom_core_secrets/vault.dart |
HashiCorp Vault — TomVaultSecretsProvider |
vault |
package:tom_core_secrets/tom_core_secrets.dart |
all three | — |
Each implements tom_core_server's TomSecretsProvider. The device keychain a
client application keeps its settings key in is a different thing — it is
tom_core_storage's TomSecretStore.
Installation #
Add it from pub.dev:
dependencies:
tom_core_server: ^1.3.0
tom_core_secrets: ^0.1.0
Dart SDK ^3.10.0. A server imports the library of the provider it registers.
AWS Secrets Manager (aws.dart) #
Overview #
tom_core_server lets any configuration value be written as a reference
to a secret store instead of the value itself:
databasePassword: "secret:aws:prod/billing#password"
The server resolves every reference once at boot, before it builds its
configuration object, and puts the secret where the reference was. Which store
a reference reaches is decided by the provider registered under the name in
its middle part. This package is that provider for AWS Secrets Manager:
TomAwsSecretsProvider, registered as aws.
A reference names the secret by name or ARN. Without a field the whole
SecretString is the value; #<field> reads one member of a secret that
holds a JSON object — the shape the Secrets Manager console writes for
key/value secrets. The current version (AWSCURRENT) is read, so a rotation
by Secrets Manager reaches the server at the next refresh.
The provider calls GetSecretValue over HTTP and signs it with the
framework's own Signature Version 4 signer — the one tom_core_server's S3
blob store uses — so no AWS SDK is involved. Credentials come from a
credentials source called for every request: static keys for a developer
or a CI job, or a function returning a role's temporary credentials on an
instance or task role.
Features #
| Type | What it does |
|---|---|
TomAwsSecretsProvider |
The TomSecretsProvider over Secrets Manager: fetch calls GetSecretValue for a secret id, answers its SecretString, or its SecretBinary when that is UTF-8 text, with the VersionId. fromConfiguration builds one from secrets.aws, falling back to the standard AWS_* variables. call is the signed client for any Secrets Manager action. |
TomAwsCredentialsSource |
Future<TomAwsCredentials> Function() — where each request's credentials come from. |
Quick start #
A server registers the provider before it loads its secrets, and loads them before it builds its configuration:
import 'dart:io';
import 'package:tom_core_secrets/aws.dart';
import 'package:tom_core_server/tom_core_server.dart';
Future<void> main(List<String> args) async {
TomSecrets.register(
TomAwsSecretsProvider.fromConfiguration(
environment: Platform.environment, // AWS_REGION, AWS_ACCESS_KEY_ID, ...
),
);
await TomSecrets.load(TomBaseServerConfiguration.collectSources(args));
print(TomSecrets.current('secret:aws:prod/billing#password'));
// the member `password` of the JSON secret `prod/billing`
}
The configuration class built after TomSecrets.load receives the secret, not
the reference. On a role, construct the provider with a credentials source
that returns the role's current temporary credentials instead:
final provider = TomAwsSecretsProvider(
region: 'eu-west-1',
credentials: roleCredentials, // a TomAwsCredentialsSource
);
Example #
| Example | Demonstrates |
|---|---|
example/aws_secrets_example.dart |
A signed GetSecretValue, a member of a JSON secret loaded through TomSecrets, and a rotation reaching a watcher on refresh — against an in-process stand-in for Secrets Manager, so it runs anywhere |
dart run example/aws_secrets_example.dart
test/aws/example_test.dart runs it and requires a clean exit.
Configuration #
TomAwsSecretsProvider.fromConfiguration reads the secrets.aws key of the
configuration resources; each setting falls back to the standard AWS variables
when an environment map is passed:
| Setting | Variable | Meaning |
|---|---|---|
region |
AWS_REGION, then AWS_DEFAULT_REGION |
The region the secrets live in. Required. |
accessKeyId |
AWS_ACCESS_KEY_ID |
Required for the configured form. |
secretAccessKey |
AWS_SECRET_ACCESS_KEY |
Required for the configured form. |
sessionToken |
AWS_SESSION_TOKEN |
Optional, for temporary credentials. |
endpoint |
AWS_ENDPOINT_URL_SECRETS_MANAGER |
Optional: a VPC endpoint or an emulator instead of https://secretsmanager.<region>.amazonaws.com/. |
Outgoing calls are resilient by default. Without an httpClient of its own,
TomAwsSecretsProvider sends through the kernel's TomResilientClient, under the policy of
the Secrets Manager endpoint that tom_core_server's httpClient configuration key sets — a
30-second timeout per attempt by default, and a circuit breaker and a bulkhead
per destination. Every Secrets Manager action is a POST, so none is retried by the client; the timeout, the circuit breaker and the bulkhead apply. Name a destination under httpClient.destinations
to give it its own policy; TomSecrets.load runs before the server applies the
key, so a boot that wants it at load applies it first — see
tom_core_server/doc/configuration.md and doc/aws_secrets.md.
Limits #
fromConfigurationtakes static keys only. Credentials that rotate — an instance profile, a task role, an assumed role — are supplied by constructing the provider with aTomAwsCredentialsSource; the package does not call the instance metadata service or STS itself.- A binary secret must be text.
SecretBinaryis decoded as UTF-8; a secret that is not text cannot be a configuration value and is refused,secrets.aws.binary_not_text. - Only the current version is read. A reference cannot pin a
VersionIdor a staging label.
Further documentation #
| Document | Covers |
|---|---|
| doc/index.md | The catalogue of this package's documentation |
| doc/aws_secrets.md | The reference syntax, configuration, credentials and roles, signing, refresh, the error keys and the emulator suite |
| doc/api/api_summary_aws.md | Every public type and member |
tom_core_server/doc/configuration.md |
Secret references in general: syntax, precedence, boot order, rotation, writing a provider |
The Vault and Azure providers are vault.dart
and azure.dart.
Azure Key Vault (azure.dart) #
Overview #
tom_core_server lets any configuration value be written as a reference
to a secret store instead of the value itself:
smtpPassword: "secret:azure:smtp-password"
The server resolves every reference once at boot, before it builds its
configuration object, and puts the secret where the reference was. Which store
a reference reaches is decided by the provider registered under the name in
its middle part. This package is that provider for Azure Key Vault:
TomAzureKeyVaultSecretsProvider, registered as azure.
A reference names the secret, and optionally a version: db-password reads
the current version, db-password/<version> a fixed one. A secret whose value
is a JSON object can be read a field at a time, db#password.
Every request carries a Microsoft Entra ID bearer token from a
credential: a service principal's client secret, the managed identity
of the VM, container or App Service the server runs on, or a token obtained
elsewhere. A token is reused until five minutes before it expires, and a 401
from Key Vault drops it and retries once with a fresh one. The provider talks
to Key Vault's REST API (version 7.4) over HTTP with package:http; there is
no Azure SDK in between.
Features #
| Type | What it does |
|---|---|
TomAzureKeyVaultSecretsProvider |
The TomSecretsProvider over Key Vault: fetch reads secrets/<name>[/<version>], answers the value with the version taken from the secret's id, and retries once with a fresh token after a 401. fromConfiguration builds one from secrets.azure, falling back to the variables Azure's SDKs read. |
TomAzureCredential |
Where the bearer token comes from — token and invalidate. |
TomAzureClientSecretCredential |
A service principal: tenant id, client id and client secret exchanged at Entra ID's token endpoint. |
TomAzureManagedIdentityCredential |
The host's managed identity: the App Service / Container Apps identity endpoint when IDENTITY_ENDPOINT and IDENTITY_HEADER are set, the instance metadata endpoint otherwise; system-assigned, or user-assigned by client id. |
TomAzureStaticTokenCredential |
A token obtained elsewhere — az account get-access-token, or a test. |
tomAzureKeyVaultScope · tomAzureKeyVaultResource |
The scope and the resource a Key Vault token is requested for. |
Quick start #
A server registers the provider before it loads its secrets, and loads them before it builds its configuration:
import 'dart:io';
import 'package:tom_core_secrets/azure.dart';
import 'package:tom_core_server/tom_core_server.dart';
Future<void> main(List<String> args) async {
TomSecrets.register(
TomAzureKeyVaultSecretsProvider.fromConfiguration(
environment: Platform.environment, // AZURE_KEYVAULT_URL, AZURE_TENANT_ID, ...
),
);
await TomSecrets.load(TomBaseServerConfiguration.collectSources(args));
print(TomSecrets.current('secret:azure:smtp-password'));
// the current version of the secret `smtp-password`
}
The configuration class built after TomSecrets.load receives the secret, not
the reference. On Azure compute, the host's identity needs no secret at all:
final provider = TomAzureKeyVaultSecretsProvider(
vaultUri: Uri.parse('https://my-vault.vault.azure.net/'),
credential: TomAzureManagedIdentityCredential(
environment: Platform.environment,
),
);
Example #
| Example | Demonstrates |
|---|---|
example/azure_secrets_example.dart |
A service principal's token, a field of a JSON secret loaded through TomSecrets, and a new version reaching a watcher on refresh while the cached token is reused — against in-process stand-ins for Entra ID and Key Vault, so it runs anywhere |
dart run example/azure_secrets_example.dart
test/azure/example_test.dart runs it and requires a clean exit.
Configuration #
TomAzureKeyVaultSecretsProvider.fromConfiguration reads the secrets.azure
key of the configuration resources; each setting but managedIdentity falls
back to an environment variable when an environment map is passed:
| Setting | Variable | Meaning |
|---|---|---|
vaultUri |
AZURE_KEYVAULT_URL |
The vault, https://<name>.vault.azure.net/. Required. |
managedIdentity |
— | true uses the host's managed identity. |
tenantId · clientId · clientSecret |
AZURE_TENANT_ID · AZURE_CLIENT_ID · AZURE_CLIENT_SECRET |
A service principal. With managedIdentity, clientId selects a user-assigned identity. |
Outgoing calls are resilient by default. Without an httpClient of its own,
TomAzureKeyVaultSecretsProvider sends through the kernel's TomResilientClient, under the policy of
the vault, Entra ID and the managed identity endpoint that tom_core_server's httpClient configuration key sets — a
30-second timeout per attempt by default, and a circuit breaker and a bulkhead
per destination. The token requests use the same client, each destination with its own circuit; GETs are retried after a connection failure, a timeout or a 429/502/503/504, the service principal's token POST is sent once. Name a destination under httpClient.destinations
to give it its own policy; TomSecrets.load runs before the server applies the
key, so a boot that wants it at load applies it first — see
tom_core_server/doc/configuration.md and doc/azure_secrets.md.
Limits #
- The provider's own settings are literal values.
secrets.azureholds the credentials that open the store, so a reference inside it is not resolved. - Three credentials are built in. A certificate credential, workload
identity federation or the Azure CLI's cache are not; each is an
implementation of
TomAzureCredential. - A pinned version does not move.
name/<version>reads that version on every refresh, so only an unpinned reference follows a rotation.
Further documentation #
| Document | Covers |
|---|---|
| doc/index.md | The catalogue of this package's documentation |
| doc/azure_secrets.md | The reference syntax, configuration, the three credentials and token caching, refresh, the error keys and the emulator suite |
| doc/api/api_summary_azure.md | Every public type and member |
tom_core_server/doc/configuration.md |
Secret references in general: syntax, precedence, boot order, rotation, writing a provider |
The Vault and AWS providers are vault.dart
and aws.dart.
HashiCorp Vault (vault.dart) #
Overview #
tom_core_server lets any configuration value be written as a reference
to a secret store instead of the value itself:
jwtSharedSecretKey: "secret:vault:secret/data/billing#jwt"
The server resolves every reference once at boot, before it builds its
configuration object, and puts the secret where the reference was. Which store
a reference reaches is decided by the provider registered under the name in
its middle part. This package is that provider for HashiCorp Vault:
TomVaultSecretsProvider, registered as vault.
A reference names the API path after /v1/, exactly as Vault's own CLI and
documentation do, and #<field> picks one value of the entry. Both KV versions
are read — version 2 nests the entry under data a second time and carries a
version number — and so is any engine whose response holds its values under
data. The provider talks to Vault over HTTP with package:http; there is no
Vault SDK in between.
Two ways to authenticate are built in: a fixed token (a developer's
VAULT_TOKEN, an agent sidecar, a CI job) and AppRole (a role id and a
secret id exchanged for a token that is renewed by logging in again).
Features #
| Type | What it does |
|---|---|
TomVaultSecretsProvider |
The TomSecretsProvider over Vault: fetch reads a path through GET /v1/<path>, understands KV version 1 and 2, sends the namespace header, and retries once with a fresh token after a 403. fromConfiguration builds one from secrets.vault, falling back to Vault's own environment variables. |
TomVaultAuth |
How the provider obtains its token — token and invalidate. |
TomVaultTokenAuth |
A fixed token. |
TomVaultAppRoleAuth |
AppRole: logs in at auth/<mount>/login, keeps the token for nine tenths of its lease, and logs in again when the lease runs out or Vault refuses the token. |
Quick start #
A server registers the provider before it loads its secrets, and loads them before it builds its configuration:
import 'dart:io';
import 'package:tom_core_secrets/vault.dart';
import 'package:tom_core_server/tom_core_server.dart';
Future<void> main(List<String> args) async {
TomSecrets.register(
TomVaultSecretsProvider.fromConfiguration(
environment: Platform.environment, // VAULT_ADDR, VAULT_TOKEN, ...
),
);
await TomSecrets.load(TomBaseServerConfiguration.collectSources(args));
print(TomSecrets.current('secret:vault:secret/data/billing#jwt'));
// the field `jwt` of the KV v2 entry `billing`
}
The configuration class built after TomSecrets.load receives the secret, not
the reference: TomBaseServerConfiguration.getDefaultValues substitutes every
loaded reference in place. TomSecrets.startRefresh keeps the values current
when Vault rotates them; see doc/vault_secrets.md.
Without a configuration, construct the provider directly:
final provider = TomVaultSecretsProvider(
address: Uri.parse('https://vault.example.com:8200'),
auth: TomVaultAppRoleAuth(roleId: roleId, secretId: secretId),
namespace: 'team-a',
);
Example #
| Example | Demonstrates |
|---|---|
example/vault_secrets_example.dart |
Building the provider from configuration, loading two fields of one KV v2 entry, and a rotation reaching a watcher on refresh — against an in-process stand-in for Vault, so it runs anywhere |
dart run example/vault_secrets_example.dart
test/vault/example_test.dart runs it and requires a clean exit.
Configuration #
TomVaultSecretsProvider.fromConfiguration reads the secrets.vault key of
the configuration resources; each setting falls back to an environment
variable when an environment map is passed:
| Setting | Variable | Meaning |
|---|---|---|
address |
VAULT_ADDR |
Where Vault answers. Required. |
token |
VAULT_TOKEN |
A fixed token. |
roleId · secretId |
VAULT_ROLE_ID · VAULT_SECRET_ID |
AppRole; preferred over a token when both are given. |
appRoleMount |
VAULT_APPROLE_MOUNT |
Where the AppRole method is mounted; approle by default. |
namespace |
VAULT_NAMESPACE |
The Vault Enterprise namespace, sent as X-Vault-Namespace. |
Outgoing calls are resilient by default. Without an httpClient of its own,
TomVaultSecretsProvider sends through the kernel's TomResilientClient, under the policy of
Vault's address that tom_core_server's httpClient configuration key sets — a
30-second timeout per attempt by default, and a circuit breaker and a bulkhead
per destination. Reads are retried after a connection failure, a timeout or a 429/502/503/504; the AppRole login POST is sent once. Name a destination under httpClient.destinations
to give it its own policy; TomSecrets.load runs before the server applies the
key, so a boot that wants it at load applies it first — see
tom_core_server/doc/configuration.md and doc/vault_secrets.md.
Limits #
- The provider's own settings are literal values.
secrets.vaultholds the credentials that open the store, so it is read as written and a reference inside it is not resolved. - A dynamic secret's lease is not renewed. An engine that issues credentials with a lease — the database engine, say — is read like any other; the provider re-reads the path on refresh, and renewing or revoking the lease stays with Vault.
- Only token and AppRole are built in. Another auth method is an
implementation of
TomVaultAuth, which logs in through the provider's publiccall.
Further documentation #
| Document | Covers |
|---|---|
| doc/index.md | The catalogue of this package's documentation |
| doc/vault_secrets.md | The reference syntax, configuration, the two auth methods, namespaces, refresh, the error keys and the emulator suite |
| doc/api/api_summary_vault.md | Every public type and member |
tom_core_server/doc/configuration.md |
Secret references in general: syntax, precedence, boot order, rotation, writing a provider |
The AWS and Azure providers are aws.dart
and azure.dart.
Dependencies #
| Dependency | Kind | For |
|---|---|---|
tom_core_server |
runtime | TomSecretsProvider, TomSecretPayload, TomSecretsException, TomServerConfigResourceProvider, the SigV4 signer TomAwsV4Signer with TomAwsCredentials (aws.dart), and the kernel's TomResilientClient it re-exports — the default client |
tom_core_kernel |
runtime | the framework base the server builds on |
http |
runtime | the Secrets Manager, Key Vault and Vault HTTP APIs |
crypto |
runtime | SHA-256 and HMAC for SigV4 (aws.dart) |
test, lints |
dev | the suites |
Status #
Version 0.1.0. dart test runs the unit suites, which drive each provider
against MockClient stand-ins, and the three examples. The tests tagged
emulator run against each provider's emulator and skip themselves when it does
not answer; tool/run_aws_emulator_tests.sh, tool/run_azure_emulator_tests.sh
and tool/run_vault_emulator_tests.sh start one in Docker, run that provider's
emulator tests, and fail when every one of them skipped.