Skip to content

Add secrets adapter for AWS SSM Parameter Store#1791

Open
davafons wants to merge 1 commit intobasecamp:mainfrom
davafons:main
Open

Add secrets adapter for AWS SSM Parameter Store#1791
davafons wants to merge 1 commit intobasecamp:mainfrom
davafons:main

Conversation

@davafons
Copy link
Copy Markdown

@davafons davafons commented Mar 2, 2026

This PR adds AWS SSM Parameter Store as a secrets adapter. I was struggling to find a service that:

  • Allows me to store encrypted secrets
  • Provides user/scope management to share/manage secrets with other members
  • Is free, or at least cheap

Normal password manager SaaS require a per-seat payment for team access, which I don't want to pay for a small project. And AWS Secrets Manager is expensive at $0.40 per stored secret.
On the other hand, Parameter Store has a generous 10k free tier and can be scoped with IAM roles and SSO login. I really think it's the perfect tool for small/medium sized projects that don't need automated rotation and all the bells and whistles that Secrets Manager provides.

1Password AWS Secrets Manager AWS SSM Parameter Store
Cost Paid plan required $0.40/secret/month + $0.05 per 10K API calls Free for Standard parameters (up to 10K); $0.05 per 10K API calls for Advanced
Secret size Up to 1 MB per item Up to 64 KB per secret Up to 4 KB (Standard) or 8 KB (Advanced)
Rotation Manual Automatic rotation possible with Lambda Manual
Versioning Versioning history Versioning history Versioning history
Encryption End-to-end encryption AWS KMS AWS KMS
Access control 1Password vaults and groups IAM policies IAM policies

For the implementation, I followed #1141 as a reference to keep the interface and functionality close to the existing Secrets Manger adapter.

Also created a docs PR here


I haven't discussed anything previous to open this PR. I really though that this adapter was different enough from other existing ones that it deserved a place here. Happy to open an Issue first if a proper discussion is needed before adding it.

Copilot AI review requested due to automatic review settings March 2, 2026 14:22
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new Kamal secrets adapter that fetches encrypted secrets from AWS SSM Parameter Store via the AWS CLI, mirroring the existing AWS Secrets Manager adapter’s CLI-driven approach.

Changes:

  • Introduce aws_ssm_parameter_store secrets adapter implementation that reads parameters via aws ssm get-parameters --with-decryption.
  • Add test coverage for fetch behavior, missing-parameter handling, missing AWS CLI, and optional --account/--profile behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
lib/kamal/secrets/adapters/aws_ssm_parameter_store.rb New adapter implementation using AWS CLI to fetch and decrypt SSM parameters.
test/secrets/aws_ssm_parameter_store_adapter_test.rb New test suite validating adapter fetch and error scenarios.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +20 to +34
args = [ "aws", "ssm", "get-parameters", "--names" ] + names.map(&:shellescape)
args += [ "--with-decryption" ]
args += [ "--profile", account.shellescape ] if account
args += [ "--output", "json" ]
cmd = args.join(" ")

`#{cmd}`.tap do |response|
raise RuntimeError, "Could not read from AWS SSM Parameter Store" unless $?.success?

response = JSON.parse(response)

return response["Parameters"] unless response["InvalidParameters"].present?

raise RuntimeError, response["InvalidParameters"].map { |name| "#{name}: not found" }.join(" ")
end
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AWS SSM get-parameters has a hard limit on how many parameter names can be requested per call (10 per request). As written, passing more than the limit will cause the AWS CLI call to fail even though the inputs are otherwise valid. Consider chunking names (e.g., in slices of 10), calling get-parameters per chunk, and concatenating the returned Parameters (while still surfacing any InvalidParameters).

Suggested change
args = [ "aws", "ssm", "get-parameters", "--names" ] + names.map(&:shellescape)
args += [ "--with-decryption" ]
args += [ "--profile", account.shellescape ] if account
args += [ "--output", "json" ]
cmd = args.join(" ")
`#{cmd}`.tap do |response|
raise RuntimeError, "Could not read from AWS SSM Parameter Store" unless $?.success?
response = JSON.parse(response)
return response["Parameters"] unless response["InvalidParameters"].present?
raise RuntimeError, response["InvalidParameters"].map { |name| "#{name}: not found" }.join(" ")
end
all_parameters = []
invalid_parameters = []
names.each_slice(10) do |chunk|
args = [ "aws", "ssm", "get-parameters", "--names" ] + chunk.map(&:shellescape)
args += [ "--with-decryption" ]
args += [ "--profile", account.shellescape ] if account
args += [ "--output", "json" ]
cmd = args.join(" ")
response = `#{cmd}`
raise RuntimeError, "Could not read from AWS SSM Parameter Store" unless $?.success?
parsed = JSON.parse(response)
all_parameters.concat(parsed["Parameters"] || [])
invalid_parameters.concat(parsed["InvalidParameters"] || [])
end
unless invalid_parameters.empty?
raise RuntimeError, invalid_parameters.map { |name| "#{name}: not found" }.join(" ")
end
all_parameters

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

@davafons davafons Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented in a different way in the PR. The current Secrets Manager adapter also has a batch limit of 20 which is not enforced, so it would fail when passing >20 secret names.

SSM Parameter Store has a limit of 10 which I feel is easier to reach by mistake if not enforced, so that's why I decided it would be good to add the batch fetch even if it wouldn't match the Secrets Manager current behavior (we might want to also add batch fetch for Secrets Manager in another PR)

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@davafons davafons marked this pull request as ready for review March 2, 2026 14:51
Copilot AI review requested due to automatic review settings March 2, 2026 14:51
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


return response["Parameters"] unless response["InvalidParameters"].present?

raise RuntimeError, response["InvalidParameters"].map { |name| "#{name}: SSM Parameter Store can't find the specified secret." }.join(" ")
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sadly, get-parameters doesn't return a proper Error field, just an InvalidParameters: ["secret1", "secret2"]. So we can't only provide a generic error message here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants