-
Notifications
You must be signed in to change notification settings - Fork 622
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP: Secret Providers POC #8730
base: main
Are you sure you want to change the base?
Conversation
@aluzzardi Noice 😎
On a first quick glance this looks good so far! I really really don't like that proxying part either, but it's necessary to coerce buildkit into working with our dagger session that consists of multiple clients. I'd prefer it was just
Fortunately, I think that jail breaking is already protected against in the code you have so far. Worth an integ test to confirm nonetheless, once you're at that point. There's more details in the PR that added support for this, but basically each client in a session gets its own isolated secret store. The CLI is one client and every function call is its own client, so the secrets they can access are isolated from each other even though they are in the same session. The end result is that if a module called That being said, I would probably still call this function in (longer term thoughts) In follow-ups, it would be cool if If we have that, I think we could+should deprecate |
return i, fmt.Errorf("failed to get secret store: %w", err) | ||
} | ||
|
||
accessor, err := core.GetClientResourceAccessor(ctx, parent, args.Name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
General question for @sipsma - do we still need this "accessor" property? Now we've got the secret separation via the SecretsStore
is this still something that we need?
Or is it still required to ensure we still get actually unique values per module?
// secrets | ||
SecretProvider{}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I might be missing something - but this isn't defined anywhere? I'm guessing this is where the actual env
/op
logic should be?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Forgot to git add
:)
Bit of an open question (and not really to-be-tackled in this PR), but how do we want to handle all of these? Are we gonna maintain all of these in-tree, or would we want to consider a plugin mechanism? However we do it, I think we should be prepared for a lot of providers, and make it easy for users/contributors to add new ones - some ones that I would love:
Maybe we should keep the
How would this work if there are multiple potential services for a single provider, e.g.:
Would each provider have a similar scheme? Or would it be up to each provider to determine how to address a service, and then a secret within that service?
Cool - I like this 🎉 Don't wanna derail too far, but just curious about this!
Maybe 🤔 There's a use-case where I think it's still useful, even if we have all of this. I can take a secret like a password, but I need to "manipulate" the password into a config file (or a header, or an encoded env var) - see a couple of examples in our ci: dagger/.dagger/sdk_typescript.go Lines 202 to 211 in 006c2ff
Lines 133 to 143 in 006c2ff
These do feel like a bit of a special case - they're "derived" secrets - I wonder if we could somehow support these in the API as the well? Maybe |
+1000
IMO, a plugin mechanism would be ideal but I don't know what kind of complexity that adds. Without it, we'd be able to maintain support for a good number of providers in-tree, especially with community support, but there will always be a huge list of requests for more providers |
146a000
to
a2c6e61
Compare
👋 chiming in here. The original idea in my head was if these plugins could be also modules so they could both serve as secret providers as well as stand-alone modules that could be also used for other types of operations. Still haven't through about all the details, just sharing some random ideas that came across my head while thinking about this 🤝 |
@aluzzardi since the current API was designed with this extra feature in mind (#4426) couldn't you simply use the human-readable secret name as the url, and no longer need |
Personally I like the idea of leaving the CLI in charge of the mapping. It's not just a matter of sandboxing (I saw @sipsma 's reassuring comment on that dimension) but also of portability. I don't think we want modules hardcoding I like the idea of using "virtual environments" as the interface for CLI-controlled mapping, wdyt @sipsma @aluzzardi @kpenfound ?
|
How would we present a 1-password vault in this style? I'm not too sure. Maybe something like this: func (*Module) Function(
// +defaultKey="API_TOKEN"
password *dagger.Secret,
) error { Then: $ dagger config map-secret API_TOKEN op://...
$ dagger call function Or maybe even an interactive session to do all the secrets at once? It feels like we still need some manual config and setup on the part of a user, because how would we map |
I think that's interesting but not specific to secrets. Arguments do change based on the environment, but IMO it affects all types, nothing "special" about secrets. For instance: The current solution to avoid passing a bunch of arguments has been through default values. I think it might make sense exploring a different interface for passing arguments that can be environment specific. Something like, in order of preference: 1) In-code default value 2) Environment specific file 3) flag |
The most important question :) My thinking is that to use this "virtual env" feature in your vault or password manager, you would have to adapt the layout of your vault. Specifically, you would have to create a single item containing all the key-values for your env, using custom fields. I believe this is a known pattern, although perhaps not ubiquitous. I think, if it passes the gut check of @kpenfound, we should consider "blessing" that pattern as the correct one. It would make the problem of mapping go away completely, by moving it entirely in the password manager. In that way it's much simpler: Dagger doesn't need to implement any custom configuration for mapping: you simply use the tool you already have, to organize your environment as you see fit. Then you just pass that item to dagger. For example: $ dagger --env=op://personal/acme/shykes-dev-env call deploy --token TOKEN Under the hood, the CLI would read the secret value from
Yes sometimes manual configuration will still be needed. In those cases, you would do as in this POC: pass $ dagger --env=op://personal/acme/shykes-dev-env call deploy --token TOKEN --other-token op://shared/foo/some-other-place |
My 2 cents, penciled in opinion:
Based on the 2 assumptions above, IMO we should:
|
Totally. Not sure how exactly, but I hope we can get rid of |
I think this is too constraining as it requires re-tooling the secrets around dagger. Users would lose many of the benefits provided by their secret store (individual secret permissions, ownership, auditing, expiry, rotation, ...). Providers offer more than simple k/v storage (SSH key generation, Cloud tokens based on IAM, etc). Doing this would be the equivalent of saying we don't support external secrets, instead dagger has its own secret mechanism. Similar to SOPS & co where the secrets are handled off platform, and the secret store is only there to store the key encrypting key. Which is a valid model, but it's not a 1:1 alternative: either we support fetching secrets from providers, or we have our own secret mechanism and rely on an external vault to store the sensitive bits (whether it's a key encrypting key or an envrc-like file) |
a2c6e61
to
3428a43
Compare
What I was suggesting was to have both as layered features:
My issue is, if we only support the first layer (explicit per-argument mapping), how do we resolve the convenience problem? One of the most requested features is "contextual auth". How would you suggest we solve that problem, without having eg. |
Replied in an earlier comment #8730 (comment) IMHO it's not tied to secrets specifically. Using the previous example, you'd probably have to do something like:
Assuming You could stash the bucket in the secrets as well, but then it becomes this shadow system to map arbitrary k/v to environments, and everything becomes a secret. For instance, In summary:
|
This is where we start getting into the differences of "password managers" and "secret managers" IMO. In this way, they probably have different usage patterns with a tool like Dagger and we probably want both to feel natural. What I mean is, I could see a call with a "password manager" pointing to each individual value and looking something like:
Those paths will point to specific secret values and the caller better have access to that secret. On the other hand, with a "secret manager" like Vault or Infisical for example, you may have all of the credentials you need in a single map. And depending on who's asking for it, you might get handed different sets of credentials from the server. In that case, you might want a call to pull a whole map and look more like:
That seems fine? I feel like adoption maturity will drive people to structure everything in a "secret manager" fashion. The part where it gets really sticky is when you're mixing password and secret managers in different environments. Like if you're using 1password locally and vault in Jenkins. In that situation I could see people using 1password entries more like secret maps to emulate what's happening in vault, etc. |
Yeah I agree it applies to other environment-specific configurations, which goes beyond secrets. I feel like "virtual environments" could be extended beyond secrets, to work with scalar configurations also. By the way that is how Github Actions "environment" feature works as well: they have secrets and "regular" variables. And of course, the original Unix environment also is used in that way. So the revised example might be:
It works better in the shell:
In summary, is it fair to say the following @aluzzardi ?
|
This PR is stale because it has been open 14 days with no activity. Remove stale label or comment or this will be closed in 7 days. |
Just to keep this moving - I think it feels like we want to support both patterns?
I think just doing item 1 for now feels like a good start, and definitely better than the current status quo, as long as we make sure to follow-up with item 2 (I don't feel like we need to hit both in the same PR - if we can, wonderful).
Could we maybe do that change separately? I think there's some tricky use cases like I mentioned at the end of #8730 (comment) that need some special consideration, and feel kind of separate to the design of secret providers long-term. We need better |
3428a43
to
aec13cd
Compare
I took the liberty of rebasing |
Here's a standalone script to try out this feature :) #!/usr/bin/env dagger shell -q -m github.com/aluzzardi/dagger@secret-providers-poc
# op is only available as amd64 package on alpine
platform=linux/amd64
.core container --platform=$platform |
from alpine |
with-exec sh,-c,"echo https://downloads.1password.com/linux/alpinelinux/stable/ >> /etc/apk/repositories" |
with-file \
/etc/apk/keys/support@1password.com-61ddfc31.rsa.pub \
$(.http https://downloads.1password.com/linux/keys/alpinelinux/support@1password.com-61ddfc31.rsa.pub) |
with-exec apk,update |
with-exec apk,add,1password-cli |
with-file /bin/dagger $(cli | binary --platform=$platform) |
with-service-binding dagger-engine $(engine | service dev) |
with-env-variable _EXPERIMENTAL_DAGGER_RUNNER_HOST $(engine | service dev | endpoint --scheme=tcp) |
terminal --cmd=sh,-c,'op account add; eval $(op signin); dagger shell' Instructions:
|
Quick note for a tricky case we might need to handle (had to deal with it in Concourse): Vault supports generating multiple fields when you request a credential, so you can have ephemeral/rapidly-rotating credentials:
If I were to try to pass the access key and secret key as separate flags, I might try something like this:
For this to work we would need to be careful not to fetch the same credential twice - if we do, I'll end up with a access key and secret key that don't work together. Is that handled already? |
This PR is stale because it has been open 14 days with no activity. Remove stale label or comment or this will be closed in 7 days. |
aec13cd
to
d514b45
Compare
How can the double fetch be avoided? Does that imply that we actually know the secret is Or you're saying that for a given secret ( edit For the former, I think the only way would be by doing that in the provider directly: e.g. the vault provider would parse When edit edit Is the 1Password is different in that the field is the last part of the path. Looks like we'll need to handle that one at the provider level. @kpenfound thoughts? |
- Change how secrets are managed so they are requested just in time by the Engine from the CLI whenever they're needed, rather than stored in plaintext from the get go. - New (still ugly) `MapSecret` API - Similar to `AddSecret` but instead of providing the plaintext value we map the URI of an external secret (e.g. `vault://...`) to a dagger.Secret - Need re-thinking - 1) we may only want the CLI to map a secret (e.g. avoid a module "jail breaking" by mapping more external secrets) - 2) `MapSecret` was shoved into the current API, some bits don't make sense - CLI implements 2 quick & dirty POC secret providers - `env`: like before - `op`: 1Password Signed-off-by: Andrea Luzzardi <al@dagger.io>
d514b45
to
40d8f5a
Compare
I'm getting this error from all SDK tests (missing buildkit session id) ... any pointers @sipsma @helderco ?
|
@aluzzardi Just looking at the code here, my best guess is that the error is coming from this line, and that field might be missing because it was newly added but isn't getting set in the That's probably relevant since AddSecret is called when you grant another client access to it (here). So just need to make sure that field gets transferred over when the secret is added to another store. |
@sipsma 🤦 Sorry I thought there was some dark magic involved for getting the session ID automatically, this PR is quite old and missed the fact I forgot to plumb that argument through. Surprisingly enough -- when I opened this PR, it did work (see screenshot of the original comment). Not sure why? I thought the CI errors were related to some SDK magic. |
Oh yeah -- I'm plumbing the session ID when calling Not sure why the SDK tests are triggering the edit: Right, that's what you mentioned
|
Signed-off-by: Andrea Luzzardi <al@dagger.io>
the Engine from the CLI whenever they're needed, rather than stored in plaintext from the get go.
MapSecret
APIAddSecret
but instead of providing the plaintext valuewe map the URI of an external secret (e.g.
vault://...
) to a dagger.SecretMapSecret
was shoved into the current API, some bits don't make senseenv
: like beforeop
: 1PasswordExample: