feat: add --syncvalues flag and improve secret sharing #5

Merged
moritz merged 1 commits from eCommons/alakazam:secret-sync into main 2026-05-18 17:25:33 +00:00
Contributor
  • Add --syncvalues flag to secrets command to update secret values from source to target apps (with redeploy of target app). Without --syncvalues, log a warning when values differ.
  • Fix bidirectionality: when source is missing its secret but target has one, warn instead of silently copying back and potentially overwriting the source value
  • Add get_secret_from_host fallback for distroless containers that reads the secret from the Docker host filesystem over SSH
  • Run secret hooks for all apps before any secret exchange to ensure source secrets are available before syncing to target app
- Add --syncvalues flag to secrets command to update secret values from source to target apps (with redeploy of target app). Without --syncvalues, log a warning when values differ. - Fix bidirectionality: when source is missing its secret but target has one, warn instead of silently copying back and potentially overwriting the source value - Add get_secret_from_host fallback for distroless containers that reads the secret from the Docker host filesystem over SSH - Run secret hooks for all apps before any secret exchange to ensure source secrets are available before syncing to target app
dannygroenewegen added 2 commits 2026-04-14 14:15:29 +00:00
- Resolve ABRA_DIR using env var or abra.yml instead of hardcoding ~/.abra
- Add ABRA_DIR to exclude_paths when inside root
- Add --syncvalues flag to secrets command to update secret values from source to target apps (with redeploy of target app). Without --syncvalues, log a warning when values differ.
- Fix bidirectionality: when source is missing its secret but target has one, warn instead of silently copying back and potentially overwriting the source value
- Add get_secret_from_host fallback for distroless containers that reads the secret from the Docker host filesystem over SSH
- Run secret hooks for all apps before any secret exchange to ensure source secrets are available before syncing to target app
simon requested review from simon 2026-05-07 11:29:14 +00:00
simon requested review from moritz 2026-05-07 11:29:14 +00:00
moritz requested changes 2026-05-11 15:08:39 +00:00
Dismissed
moritz left a comment
Owner

WHOOYAA! this PR contains some features I was really missing! Thank you a lot. Especially the get_secret_from_host() function I like! Hopefully it can be replaced someday by abra directly: toolshed/abra#694
Maybe this function can be extended to read the secrets, even if there are no running containers: toolshed/organising#480 (comment) but this could be somewhat for another issue.

WHOOYAA! this PR contains some features I was really missing! Thank you a lot. Especially the `get_secret_from_host()` function I like! Hopefully it can be replaced someday by abra directly: https://git.coopcloud.tech/toolshed/abra/issues/694 Maybe this function can be extended to read the secrets, even if there are no running containers: https://git.coopcloud.tech/toolshed/organising/issues/480#issuecomment-27767 but this could be somewhat for another issue.
alakazam.py Outdated
@ -552,2 +574,2 @@
insert_secret(app1_domain, app1_secret, secret)
logging.info(f"Shared secret {app2_secret} from {app2_domain} to {app1_domain} as {app1_secret}")
# Target has secret but source doesn't, warn instead of copying back.
# Copying target to source risks overwriting the correct source value.
Owner

I'm not sure how you argue to remove the bidirectional secret sharing? The order which app is source or target should be quite irrelevant.
The condition app2_secret_is_stored and not app1_secret_is_stored: ensures, that no secret will be overwritten. Because the secret of app1 doesn't exists.

I'm not sure how you argue to remove the bidirectional secret sharing? The order which app is source or target should be quite irrelevant. The condition `app2_secret_is_stored and not app1_secret_is_stored:` ensures, that no secret will be overwritten. Because the secret of app1 doesn't exists.
Author
Contributor

You're right, good catch. The whole logic is a bit hard to grasp because app1 and app2 refer to different apps depending on which call to share_secrets is being made from exchange_secrets. I think the edge case I wanted to solve here has already been solved by splitting the secret insertion and secret exchange into two phases in the secrets function, but I probably made that split after these changes.

For context: I have a case where it is important which app is the source: Rauthy doesn't support creating an OIDC client with a known secret. So Rauthy gets deployed first with a dummy value for the client secret. Then I use the initial-hooks to create a client with the Rauthy API and update the docker secret with the value that the API returns. Afterwards, alakazam group secrets --syncvalues should only copy the secret value from rauthy to app2, not the other way around.

You're right, good catch. The whole logic is a bit hard to grasp because app1 and app2 refer to different apps depending on which call to share_secrets is being made from exchange_secrets. I think the edge case I wanted to solve here has already been solved by splitting the secret insertion and secret exchange into two phases in the secrets function, but I probably made that split after these changes. For context: I have a case where it is important which app is the source: Rauthy doesn't support creating an OIDC client with a known secret. So Rauthy gets deployed first with a dummy value for the client secret. Then I use the initial-hooks to create a client with the Rauthy API and update the docker secret with the value that the API returns. Afterwards, `alakazam group secrets --syncvalues` should only copy the secret value from rauthy to app2, not the other way around.
Owner

I'm not sure if I exactly understand your rauthy flow but it sounds like the authentik saml workflow, for example with kimai.
First auhtentik needs to be deployed, to create the SAML certificate. Then the secret-hook for kimai runs this abra.sh function https://git.coopcloud.tech/coop-cloud/kimai/src/branch/main/abra.sh#L9 which calls the abra.sh function of authentik: https://git.coopcloud.tech/coop-cloud/authentik/src/branch/main/abra.sh#L362 to print the certificate and insert it as kimai secret.

So in the end you use the Rauthy docker secrets only as "cache" to save the return value of the API and then share these values using alakazam group secrets --syncvalues to updated the dummy secret values that were generated randomly by abra in the first place?
Did you tried to use the secret-hooks for that?

I'm not sure if I exactly understand your rauthy flow but it sounds like the authentik saml workflow, for example with kimai. First auhtentik needs to be deployed, to create the SAML certificate. Then the `secret-hook` for kimai runs this abra.sh function https://git.coopcloud.tech/coop-cloud/kimai/src/branch/main/abra.sh#L9 which calls the abra.sh function of authentik: https://git.coopcloud.tech/coop-cloud/authentik/src/branch/main/abra.sh#L362 to print the certificate and insert it as kimai secret. So in the end you use the Rauthy docker secrets only as "cache" to save the return value of the API and then share these values using `alakazam group secrets --syncvalues` to updated the dummy secret values that were generated randomly by abra in the first place? Did you tried to use the `secret-hooks` for that?
Author
Contributor

So in the end you use the Rauthy docker secrets only as "cache" to save the return value of the API and then share these values using alakazam group secrets --syncvalues to updated the dummy secret values that were generated randomly by abra in the first place?
Did you tried to use the secret-hooks for that?

Yes, exactly like this. Indeed seems similar to the saml workflow with kimai, that's another way to approach the problem. I did go over the authentik combine.yml, but missed that this one is different. I think ideally, I would like an SSO recipe to only have some commands to add a generic new client, and any app only have a generic connect-sso command. Alakazam should then contain the integration logic and configs, since alakazam handles that. Feels a bit cumbersome that for almost every integration app1 needs a compose.app2.yml file+secret and app2 needs a compose.app1.yml file+secret.

> So in the end you use the Rauthy docker secrets only as "cache" to save the return value of the API and then share these values using `alakazam group secrets --syncvalues` to updated the dummy secret values that were generated randomly by abra in the first place? > Did you tried to use the `secret-hooks` for that? Yes, exactly like this. Indeed seems similar to the saml workflow with kimai, that's another way to approach the problem. I did go over the authentik combine.yml, but missed that this one is different. I think ideally, I would like an SSO recipe to only have some commands to add a generic new client, and any app only have a generic connect-sso command. Alakazam should then contain the integration logic and configs, since alakazam handles that. Feels a bit cumbersome that for almost every integration app1 needs a compose.app2.yml file+secret and app2 needs a compose.app1.yml file+secret.
alakazam.py Outdated
@ -558,0 +595,4 @@
except RuntimeError as e:
logging.warning(f"Could not read secret for comparison ({app1_domain}/{app2_domain}): {e}")
continue
if app2_value is None or app1_value != app2_value:
Owner

Maybe it should be stated somewhere, that the secret of app2 will be overwritten, not only if it differs from the secret of app1, but also if the container is not running. The result will be the same, but for clarity.

Maybe it should be stated somewhere, that the secret of app2 will be overwritten, not only if it differs from the secret of app1, but also if the container is not running. The result will be the same, but for clarity.
dannygroenewegen marked this conversation as resolved
alakazam.py Outdated
@ -558,0 +603,4 @@
update_secret(app2_domain, app2_secret, app1_value, app2_container)
else:
logging.warning(
f"Secret mismatch: {app1_domain}/{app1_secret} differs from "
Owner

Here you should adapt the warning if the container is not running, because if it is not, the warning Secret mismatch: {app1_domain}/{app1_secret} differs from... would be incorrect. Instead a message like couldn't compare secret values, because {app1} is not running would be good.

Here you should adapt the warning if the container is not running, because if it is not, the warning `Secret mismatch: {app1_domain}/{app1_secret} differs from...` would be incorrect. Instead a message like `couldn't compare secret values, because {app1} is not running` would be good.
dannygroenewegen marked this conversation as resolved
alakazam.py Outdated
@ -579,0 +652,4 @@
if server:
break
if not server:
raise RuntimeError(f"Could not determine server for {domain}")
Owner

Instead of this for loop, you could use get_server_apps()

Instead of this for loop, you could use `get_server_apps()`
dannygroenewegen marked this conversation as resolved
alakazam.py Outdated
@ -665,6 +775,23 @@ def insert_secret(domain: str, secret_name: str, secret: str) -> None:
abra("app", "secret", "insert", domain, secret_name, "v1", secret)
def update_secret(domain: str, secret_name: str, secret: str, containers: List) -> None:
Owner

for a more intuitive function signature it would be good to pass a boolean was_deployed instead of the complete list of containers.

for a more intuitive function signature it would be good to pass a boolean `was_deployed` instead of the complete list of containers.
dannygroenewegen marked this conversation as resolved
alakazam.py Outdated
@ -892,3 +1026,3 @@
insert_secrets_from_conf(domain, app_config)
run_secret_hooks(domain, app_config)
exchange_secrets(app, instance_config, instance_apps)
# Pass 2: exchange secrets between apps, then generate any remaining missing secrets.
Owner

I really like the idea of splitting the secret insertion and secret exchange into two phases. I've never run into the case that I wanted to explicitly insert a secret first and the exchange it, therefore I never thought about this case.

I really like the idea of splitting the secret insertion and secret exchange into two phases. I've never run into the case that I wanted to explicitly insert a secret first and the exchange it, therefore I never thought about this case.
dannygroenewegen marked this conversation as resolved
dannygroenewegen force-pushed secret-sync from 38440692ee to 888341c1dc 2026-05-15 13:52:21 +00:00 Compare
dannygroenewegen requested review from moritz 2026-05-15 13:56:46 +00:00
moritz approved these changes 2026-05-18 17:25:05 +00:00
moritz left a comment
Owner

This looks perfect and can be merged :)

This looks perfect and can be merged :)
moritz merged commit d38140f623 into main 2026-05-18 17:25:33 +00:00
dannygroenewegen deleted branch secret-sync 2026-05-19 18:56:54 +00:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: toolshed/alakazam#5
No description provided.