Skip to main content
Preloop’s default credential backends keep secret values on the host: the private file store, or the operating-system keychain. A hosted, multi-tenant engine must not be able to produce a secret value from a copy of its database, its state directory, or a backup of either. The ssm backend puts every value in AWS Systems Manager Parameter Store as a SecureString, encrypted by KMS, and leaves the database with references only. The backend is compiled only when the aws-ssm feature is enabled, so the shipped CLI carries no AWS SDK:

Configuration

Select it like any other backend, through the environment or the config file:
Each credential reference becomes one parameter named <prefix>/<reference>. References are validated, not rewritten: a reference that is not a legal SSM name segment (a namespaced namespace:reference, for example) is rejected before any request, so the engine can never read or write a different parameter than the one it asked for.

IAM policy

Least privilege for one prefix in one account, with values encrypted by a dedicated customer-managed key:
  • ssm:GetParameter covers both reads and the availability probe, which reads a never-written leaf, .availability-probe, under the prefix; a ParameterNotFound answer is what “reachable” means. ssm:GetParameters and ssm:GetParametersByPath are not used by this backend.
  • ssm:PutParameter with Overwrite=true creates and rotates values; ssm:DeleteParameter removes them.
  • The key needs kms:Decrypt for reads and kms:GenerateDataKey for writes. With the account-default alias/aws/ssm key, kms:Decrypt and kms:GenerateDataKey on * are usually granted by the AWS-managed policy; a dedicated key keeps the blast radius per deployment.
  • No ssm:DescribeParameters, no ssm:AddTagsToResource, and no wildcard resource: the prefix scopes every call.
A shared account can host several deployments under separate prefixes (/preloop/prod, /preloop/staging); grant each role only its own parameter<prefix>/*.

Limits and throughput

Retries and failures

ThrottlingException and TooManyUpdates are retried with bounded exponential backoff: four attempts total, starting at 100 ms and doubling to a 1.6 s ceiling. The SDK’s own retry layer is disabled for this backend so there is exactly one policy and a bounded number of requests per call. Every other failure is an error, never a missing secret:
  • An access denial surfaces as AccessDeniedException with the parameter name, so a broken policy fails a job loudly instead of looking like a deleted credential.
  • A missing parameter is the only condition reported as “no value”; delete treats ParameterNotFound as success, because the requested end state already holds.
  • Network and DNS failures surface with their cause chain.
Values never appear in logs or error messages. Parameter names are identifiers and do appear; they are derived from credential references such as github-pat or engine-token-<digest>, not from secret material.

Operational notes

  • The engine keeps the file store out of the write path when ssm is selected: a failed write fails the command. It never falls back to a local file, which would put a tenant’s password on the engine host.
  • Startup and preloop doctor exercise the same backend (the sealing key and the engine’s own GitHub credentials are credential-store references), so a missing region, a denied role, or an unreachable endpoint fails there, naming the probe, before the first job runs.
  • Rotating the KMS key is an SSM/KMS operation; the engine reads whatever the parameter’s current key is, so no engine restart is needed after a re-encryption.
  • Deleting the hierarchy (aws ssm delete-parameters --names ...) makes every stored credential absent; jobs that need one fail closed, and a job that already resolved its values keeps them for its remaining lifetime.