Skip to content

Upload custom SSL certificates for accessories - #1820

Closed
pigoz wants to merge 2 commits into
basecamp:mainfrom
pigoz:accessories_custom_ssl_certificates
Closed

pigoz wants to merge 2 commits into
basecamp:mainfrom
pigoz:accessories_custom_ssl_certificates

Conversation

@pigoz

@pigoz pigoz commented Apr 8, 2026

Copy link
Copy Markdown

This is my first contribution, please be extra careful with the review :) Sadly I didn't manage to run the integration tests on OSX. Original commit follows:


When an accessory is configured with custom SSL certificates via certificate_pem and private_key_pem in its proxy config, kamal fails with "unable to load certificate" during kamal-proxy deploy.

The root cause is that Kamal::Cli::App::SslCertificates, which handles uploading PEM files into the kamal-proxy container, is only called during app boot (cli/app.rb), never during accessory boot (cli/accessory.rb). The kamal-proxy deploy command references cert paths that don't exist because nobody uploaded the files.

I hit this while configuring a proxied imgproxy accessory on a separate subdomain with a Cloudflare Origin Certificate.

The fix has two parts:

  1. Pass role_name: "accessories/#{name}" when initializing the proxy configuration for accessories. Without this, role_name is nil and cert paths collapse to tls/cert.pem, which would collide between multiple accessories or with an app role that also has no name. The accessories/ prefix keeps them in a separate namespace from app roles (e.g. tls/web/ vs tls/accessories/monitoring/).

  2. Add Kamal::Cli::Accessory::SslCertificates, mirroring the existing Kamal::Cli::App::SslCertificates. It creates the target directory and uploads cert/key PEM files before the container is registered with kamal-proxy. This is called in the accessory boot path, between the env upload and docker run.

Fixes #1769

When an accessory is configured with custom SSL certificates via
certificate_pem and private_key_pem in its proxy config, kamal
fails with "unable to load certificate" during kamal-proxy deploy.

The root cause is that Kamal::Cli::App::SslCertificates, which
handles uploading PEM files into the kamal-proxy container, is only
called during app boot (cli/app.rb), never during accessory boot
(cli/accessory.rb). The kamal-proxy deploy command references cert
paths that don't exist because nobody uploaded the files.

I hit this while configuring a proxied imgproxy accessory on a
separate subdomain with a Cloudflare Origin Certificate.

The fix has two parts:

1. Pass role_name: "accessories/#{name}" when initializing the
   proxy configuration for accessories. Without this, role_name is
   nil and cert paths collapse to tls/cert.pem, which would collide
   between multiple accessories or with an app role that also has no
   name. The accessories/ prefix keeps them in a separate namespace
   from app roles (e.g. tls/web/ vs tls/accessories/monitoring/).

2. Add Kamal::Cli::Accessory::SslCertificates, mirroring the
   existing Kamal::Cli::App::SslCertificates. It creates the target
   directory and uploads cert/key PEM files before the container is
   registered with kamal-proxy. This is called in the accessory boot
   path, between the env upload and docker run.

Fixes basecamp#1769
Copilot AI review requested due to automatic review settings April 8, 2026 13:09

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes accessory deployments that use custom SSL PEM secrets by ensuring accessory proxy TLS paths are properly scoped and by uploading the accessory certificate/key into the kamal-proxy apps-config before kamal-proxy deploy runs.

Changes:

  • Scope accessory proxy role_name to accessories/<name> so generated TLS paths don’t collide with app roles or other accessories.
  • Add an accessory SSL certificate uploader invoked during kamal accessory boot to create the TLS directory and upload cert/key PEMs.
  • Add test fixture + tests covering path scoping and the boot flow for custom SSL accessories.

Tip

If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.

Reviewed changes

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

Show a summary per file
File Description
test/fixtures/deploy_with_accessory_custom_ssl.yml Adds a minimal config fixture for an accessory with custom SSL PEM secrets.
test/configuration/accessory_test.rb Verifies accessory TLS paths include accessories/<name>/... scoping.
test/cli/accessory_test.rb Exercises kamal accessory boot output to confirm cert directory creation/upload + proxy args.
lib/kamal/configuration/accessory.rb Passes role_name: "accessories/#{name}" into proxy config for correct TLS path generation.
lib/kamal/commands/accessory/proxy.rb Adds command helper to create the accessory TLS directory under proxy apps-config.
lib/kamal/commands/accessory.rb Delegates name so CLI can reference accessory name in messages.
lib/kamal/cli/accessory/ssl_certificates.rb New uploader that writes accessory cert/key PEMs before container registration with proxy.
lib/kamal/cli/accessory.rb Calls the new accessory SSL uploader during boot.

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

Comment thread lib/kamal/cli/accessory/ssl_certificates.rb
Comment on lines 133 to 136
proxy_config: accessory_config["proxy"],
role_name: "accessories/#{name}",
context: "accessories/#{name}/proxy",
secrets: config.secrets

Copilot AI Apr 8, 2026

Copy link

Choose a reason for hiding this comment

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

role_name is built from the accessory name and then used as part of filesystem paths under the proxy TLS directory. Accessory names come directly from YAML keys and don’t appear to be validated for path separators or .. (contrast with alias name validation in lib/kamal/configuration/validator/alias.rb). Consider validating/normalizing accessory names before using them in paths to avoid unexpected directory layouts or path traversal when creating/uploading TLS files.

Copilot uses AI. Check for mistakes.

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.

The accessory name already ends up in filesystem paths in a bunch of
other places — not just the TLS dir this PR adds. The env file path at
configuration/accessory.rb:73 interpolates it directly, and service_name
(configuration/accessory.rb:27) becomes the docker container name and
is joined into host mount paths at configuration/accessory.rb:217. So
this PR isn't expanding the surface, just using the same convention.

Validating names feels like a separate change to me. A strict regex like
the alias one would also break anyone whose accessory names have
uppercase letters or dots. And since names come from the operator's own
deploy.yml, anyone who can write path separators in there can already do
whatever they want on the servers. Docker also rejects / in container
names, so most weird values blow up early anyway.

Raise an explicit error naming the missing secret and accessory
instead of silently skipping the upload, which previously surfaced
as a confusing "unable to load certificate" failure from kamal-proxy.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@pigoz

pigoz commented Sep 3, 2026

Copy link
Copy Markdown
Author

Superseded by #1951: a rewrite from scratch on top of current main, reduced to the minimum that mirrors the app's certificate upload, with the certificate layout explained and CI run on my fork. Closing this one to keep the discussion in one place.

@pigoz pigoz closed this Sep 3, 2026
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.

kamal accessory boot fails with "unable to load certificate" due to failed certificate mount or invalid path generation

2 participants