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
- 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
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.
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.
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.
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?
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.
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
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
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
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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#694Maybe 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.
@@ -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.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.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 --syncvaluesshould only copy the secret value from rauthy to app2, not the other way around.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-hookfor 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 --syncvaluesto updated the dummy secret values that were generated randomly by abra in the first place?Did you tried to use the
secret-hooksfor 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.
@@ -558,0 +595,4 @@except RuntimeError as e:logging.warning(f"Could not read secret for comparison ({app1_domain}/{app2_domain}): {e}")continueif app2_value is None or app1_value != app2_value: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.
@@ -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 "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 likecouldn't compare secret values, because {app1} is not runningwould be good.@@ -579,0 +652,4 @@if server:breakif not server:raise RuntimeError(f"Could not determine server for {domain}")Instead of this for loop, you could use
get_server_apps()@@ -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:for a more intuitive function signature it would be good to pass a boolean
was_deployedinstead of the complete list of containers.@@ -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.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.
38440692eeto888341c1dcThis looks perfect and can be merged :)