-
Notifications
You must be signed in to change notification settings - Fork 654
Support templated secrets and configs #2133
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
Conversation
template/context.go
Outdated
| t *api.Task | ||
| } | ||
|
|
||
| func (sg secretPayloadGetter) GetBySource(source string) (string, error) { |
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.
In the theme of brevity, could this just be ByTarget?
template/context.go
Outdated
| // invoke those methods. | ||
| type PayloadContext struct { | ||
| Context | ||
| Secrets secretPayloadGetter |
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.
Why not add this to the base context? It will keep the scope of what is available in a template a lot clearer.
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.
Presumably, this particular context is only available for contexts after env variables have been evaluated?
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 decided to avoid adding these to the base Context type because I wanted to avoid exposing secrets during template substitution on labels, environment variables, etc. This might be a desirable feature to have in its own right, but I was worried there might be more opportunities for injection here, so didn't want to bundle it into this PR.
@diogomonica: Any thoughts on where templates should be able to use secrets or not?
That said, I think when I change this to use the func map as you suggested, this structure will no longer be necessary. There will just be Context with the func map injected (in places where we allow secret/config substitution), and Context without the func map (where we disallow secret/config substitution). This is probably a cleaner result.
|
@aaronlehmann This looks like it has the correct layout, but I am slightly worried about exposing methods on the injected objects. Would it be safer for us to inject these into the func map? For example, we'd have the following, syntactically, as a result: Such a change would allow us to have a "no method" policy on context objects. I'm not sure I understand the distinction behind source and target (other than being the "origin of the secret"). I know we do this in the command line to allow one to select the secret but does it make sense here when the target is constant? |
I definitely agree. I missed the func map in my cursory read of the template docs. I'll update the PR to work like this instead. Thanks for the suggestion.
The target is considered more "portable" - rotating a secret would involve creating a new one with a different name, and updating the service to mount that new secret under the same target (filename). However, I felt it might be weird to only be able to reference secrets by target, and not by secret name - so I included both options here. If people think one or the other makes sense as a canonical method of reference, I'm fine with just choosing one. |
Let's make the default the syntactically nice option, then have an alternate for the other. It sounds like target is the "right" option, but I can see the need for referencing source. |
f54d9b1 to
80b3585
Compare
Codecov Report
@@ Coverage Diff @@
## master #2133 +/- ##
==========================================
+ Coverage 60.06% 60.28% +0.21%
==========================================
Files 119 120 +1
Lines 19847 19972 +125
==========================================
+ Hits 11921 12040 +119
+ Misses 6568 6556 -12
- Partials 1358 1376 +18 |
80b3585 to
7a88763
Compare
|
I've updated the PR to inject functions into the template instead of relying on methods. This also allowed switching to more usable syntax. The injected functions are: I kept the |
|
@aaronlehmann since by default in |
|
I guess the question is which way should be the default. Specifying a secret by its target is more "portable", since the source may change as the secret data is rotated. In the original version of the PR I called the functions
Another thing I can think of is making the function take two arguments: or borrowing the k/v syntax we use in the CLI to specify a source or target: I'm not sure what's best here. |
template/context.go
Outdated
| "Secret": ctx.secretGetterByTarget, | ||
| "ConfigBySource": ctx.configGetterBySource, | ||
| "Config": ctx.configGetterByTarget, | ||
| "Env": ctx.envGetter, |
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 think the convention for template functions is to be lowercase.
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 made them uppercase for consistency with the existing template variables such as .Service.Name, but I'm happy to change them to lowercase if that's better.
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.
The "best practice" seems to be that variables are uppercase and functions are lowercase. This was followed with join.
Here is the builtin function set: https://golang.org/pkg/text/template/#hdr-Functions.
I think we follow this convention in docker, as well: https://github.com/moby/moby/blob/master/pkg/templates/templates.go#L11.
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.
No problem, I'll make them lowercase.
| func newTemplate(s string) (*template.Template, error) { | ||
| return template.New("expansion").Option("missingkey=error").Funcs(funcMap).Parse(s) | ||
| func newTemplate(s string, extraFuncs template.FuncMap) (*template.Template, error) { | ||
| tmpl := template.New("expansion").Option("missingkey=error").Funcs(funcMap) |
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.
Does this merge the func maps?
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.
Does this merge the func maps?
Yeah.
|
What about this? This will allow us to parameterize secret access in templates in the future? |
SGTM. I'll wait for Diogo and Andrea to chime in before making any changes. |
|
@aaronlehmann I like it |
|
Pushed some updates:
|
|
@aaronlehmann agreed on dropping it by source. |
Seconded. We can always add the parameter later. |
|
I've removed the |
|
LGTM |
1 similar comment
|
LGTM |
|
ping @aluzzardi |
|
Overall looks good. My 2 cents:
|
The only reason I can think of is that the actual config files are not backed by tmpfs, but secrets are. @diogomonica any thoughts?
In this PR, only top-level templates are evaluated, to avoid recursion. There is a unit test related to this. I suppose we could support multiple levels of templating, and have some kind of cycle detector, but only applying one template seemed like the simplest thing to do for now.
Sounds okay to me, any opinions for/against @diogomonica @stevvooe? I feel like in the future we might add other properties that are not mutually exclusive with templating, which is why I made the field specific to templating, but then again we might add properties that are mutually exclusive with templating...
There was some discussion above that resulted in the functions becoming lowercase: #2133 (comment) |
I think it is clear that secrets are evaluated in line. This must be a dag, in that a template can consume a secret, but not vice versa.
For consistency with secret, we may be past this point. We may have wanted "external types" at some point, but we just have to deal with sibling fields for that case. I actually think a
I hunted this down. In docker, we use lowercase for functions and that seems to be the best practice across the Go community. |
|
I'm tempted to just keep the protos as they are in this PR. It feels a bit simpler to have a single field related to templating than a bool and a template type field. And if we leave the template type field for the future, existing implementations wouldn't be aware of it, and they would try to evaluate everything as a Go template. But I really don't feel strongly. If there is a preference for switching to a bool, I'm fine with doing that. If there's a preference for renaming the field to |
|
@aaronlehmann I would prefer the |
|
I've updated the protobufs to use the PTAL |
|
LGTM |
|
Rebased |
Add the ability for secrets and configs to expand templates, which can
reference the following fields:
- The same set of fields available to ContainerSpec templates
({{.Service.Name}}, {{.Node.ID}}, etc)
- Environment variables: {{Env "envvar"}}
- Other secrets: {{Secret "sometarget"}}
- Other configs: {{Config "sometarget"}}
Secrets and configs can be accessed either by source (secret name) or
target (file name).
Templating of secrets/configs is optional, and is disabled by default
because it's very possible for Go template syntax to conflict with the
syntax of the payload.
The only change that will be necessary in the executor is wrapping the
DependencyGetter in NewTemplatedDependencyGetter before handing it off
to the execution backend.
Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
This allows a templating error to fail a task. Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
Rename Secret, Config, and Env to lowercase variants. Replace SecretBySource/ConfigBySource with optional bysource=true arguments to "secret" and "config". Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
|
Rebased |
|
LGTM! |
|
LGTM /cc @dhiltgen |
wsong
left a comment
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'm not really qualified to make specific comments on the code, but it LGTM overall. Design seems sensible.
Add the ability for secrets and configs to expand templates, which can
reference the following fields:
(
{{.Service.Name}},{{.Node.ID}}, etc){{Env "envvar"}}{{Secret "sometarget"}}{{Config "sometarget"}}Secrets and configs can be accessed either by source (secret name) or
target (file name).
Templating of secrets/configs is optional, and is disabled by default
because it's very possible for Go template syntax to conflict with the
syntax of the payload.
The only change that will be necessary in the executor is wrapping the
DependencyGetterinNewTemplatedDependencyGetterbefore handing it offto the execution backend.
cc @aluzzardi @stevvooe @diogomonica @cyli