-
Notifications
You must be signed in to change notification settings - Fork 129
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
feat(file) implement env var substitution for state files #286
Conversation
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 this can be a security vulnerability (used to exfiltrate environment variables if the deck file is controlled by an actor interested in host's environment variables).
Let's discuss how to mitigate this risk. My first guess is that restricting available env vars e.g. to those sharing a predefined prefix (e.g. DECK_ENV_
) might solve it.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
There are two ways to achieve this:
Which one do you prefer? |
I find the former less "magical" and less confusing. The latter approach seems acceptable too, if it gets documented well enough. |
I'm also in favor of the first option, where users must include the |
@mflendrich @rainest Perfect. I get an odd sense of happiness we all agree AND use the exact same argument, however small the issue maybe. |
file/readfile.go
Outdated
"env": func(key string) (string, error) { | ||
if !strings.HasPrefix(key, envVarPrefix) { | ||
return "", fmt.Errorf("environment variables in the state file must "+ | ||
"be prefixed with 'DECK_', found: '%s'", key) | ||
} | ||
return os.Getenv(key), nil | ||
}, |
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.
For readability and maintainability, please extract the lambda function into a named function, for example func GetPrefixedEnvVar
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.
Done.
file/readfile.go
Outdated
@@ -132,3 +140,26 @@ func configFilesInDir(dir string) ([]string, error) { | |||
} | |||
return res, nil | |||
} | |||
|
|||
func processEnvs(content 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.
This function is in fact a gotemplate rendering mapper. It happens to have env
as its defined function, but may include other ones in the future.
I recommend that we redefine the responsibility of this function from "fill in env vars" to "render the deck file template" and rename it accordingly.
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.
Done.
file/readfile.go
Outdated
if err != nil { | ||
return nil, fmt.Errorf("parsing file: %w", err) | ||
} | ||
bytes = []byte(processedContent) |
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.
Please don't reuse the same []byte
for input and output of template rendering. From a strongly typed perspective, these two values represent different data types (unrendered template versus rendered document)
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 get the point somewhat but not enough.
Why does it really matter? Or maybe when?
I do agree that this way of thinking is correct, I just don't completely grasp it so please help me improve my understanding here.
And since I agree, I have already updated the patch,
file/readfile.go
Outdated
@@ -2,6 +2,9 @@ package file | |||
|
|||
import ( | |||
"bufio" | |||
"bytes" | |||
"fmt" | |||
"html/template" |
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.
You probably meant text/template
. html/template
will perform additional escaping which I think is undesirable (will break valid YAML) here.
Please add a test ensuring that the template renderer doesn't escape yaml characters like & or <.
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.
Done
@mflendrich @rainest Friendly ping to review this when you get a chance. |
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.
Using <
in tags to check that we're not garbling YAML seems a bit off since it's not actually a valid tag, but ultimately that doesn't cause issues in unit tests.
I found one UX gotcha during practical testing: unset variables in the template will (probably, depending on the field type) result in an invalid state, and the resulting error doesn't make the issue cause clear. For example, attempting to apply file/testdata/file.yaml
without setting DECK_SVC2_HOST
results in:
Error: reading file: validating file content: 1 errors occurred:
services.0.host: Invalid type. Expected: string, given: null
With the attached change:
Error: reading file: parsing file: template: state:3:12: executing "state" at <env "DECK_SVC2_HOST">: error calling env: environment variable 'DECK_SVC2_HOST' present in state file but not set
While the complete error unwrap looks a bit silly, it's at least clearer than the type error that you'll hit later.
Originally attempted to use PR suggestions, and then realized that works horribly when you need to include tabs, so git apply /tmp/lookup.diff.txt
: lookup.diff.txt
Fair. What if we error out early whenever we encounter an unset environment variable? |
Introducing support for envirment variable substitution in state files. This is primarily intended for injecting sensitive data at runtime. Fix #191
@rainest I updated the code to include your patch and also added a test case for it. PTAL. ping @mflendrich for another review. |
Co-authored-by: Michał Flendrich <michal@flendrich.pro>
Introducing support for environment variable substitution in state files. This is primarily intended for injecting sensitive data at runtime. Fix #191 Co-authored-by: Michał Flendrich <michal@flendrich.pro>
Introducing support for environment variable substitution in state files. This is primarily intended for injecting sensitive data at runtime. Fix #191 Co-authored-by: Michał Flendrich <michal@flendrich.pro>
Introducing support for envirment variable substitution in state files.
This is primarily intended for injecting sensitive data at runtime.
Fix #191