diff --git a/frontend/dockerfile/dockerfile2llb/convert.go b/frontend/dockerfile/dockerfile2llb/convert.go index 8a3fddc191b1..4b26a4938a80 100644 --- a/frontend/dockerfile/dockerfile2llb/convert.go +++ b/frontend/dockerfile/dockerfile2llb/convert.go @@ -11,6 +11,7 @@ import ( "os" "path" "path/filepath" + "regexp" "runtime" "sort" "strconv" @@ -54,6 +55,11 @@ const ( sbomScanStage = "BUILDKIT_SBOM_SCAN_STAGE" ) +var ( + secretsRegexpOnce sync.Once + secretsRegexp *regexp.Regexp +) + var nonEnvArgs = map[string]struct{}{ sbomScanContext: {}, sbomScanStage: {}, @@ -1122,6 +1128,7 @@ func dispatchEnv(d *dispatchState, c *instructions.EnvCommand, lint *linter.Lint msg := linter.RuleLegacyKeyValueFormat.Format(c.Name()) lint.Run(&linter.RuleLegacyKeyValueFormat, c.Location(), msg) } + validateNoSecretKey("ENV", e.Key, c.Location(), lint) commitMessage.WriteString(" " + e.String()) d.state = d.state.AddEnv(e.Key, e.Value) d.image.Config.Env = addEnv(d.image.Config.Env, e.Key, e.Value) @@ -1700,6 +1707,7 @@ func dispatchShell(d *dispatchState, c *instructions.ShellCommand) error { func dispatchArg(d *dispatchState, c *instructions.ArgCommand, opt *dispatchOpt) error { commitStrs := make([]string, 0, len(c.Args)) for _, arg := range c.Args { + validateNoSecretKey("ARG", arg.Key, c.Location(), opt.lint) _, hasValue := opt.buildArgValues[arg.Key] hasDefault := arg.Value != nil @@ -2344,6 +2352,37 @@ func validateBaseImagePlatform(name string, expected, actual ocispecs.Platform, } } +func getSecretsRegex() *regexp.Regexp { + // Check for either full value or first/last word. + // Examples: api_key, DATABASE_PASSWORD, GITHUB_TOKEN, secret_MESSAGE, AUTH + // Case insensitive. + secretsRegexpOnce.Do(func() { + secretTokens := []string{ + "apikey", + "auth", + "credential", + "credentials", + "key", + "password", + "pword", + "passwd", + "secret", + "token", + } + pattern := `(?i)(?:_|^)(?:` + strings.Join(secretTokens, "|") + `)(?:_|$)` + secretsRegexp = regexp.MustCompile(pattern) + }) + return secretsRegexp +} + +func validateNoSecretKey(instruction, key string, location []parser.Range, lint *linter.Linter) { + pattern := getSecretsRegex() + if pattern.MatchString(key) { + msg := linter.RuleSecretsUsedInArgOrEnv.Format(instruction, key) + lint.Run(&linter.RuleSecretsUsedInArgOrEnv, location, msg) + } +} + type emptyEnvs struct{} func (emptyEnvs) Get(string) (string, bool) { diff --git a/frontend/dockerfile/dockerfile_lint_test.go b/frontend/dockerfile/dockerfile_lint_test.go index 736a468b03bd..6814b53542be 100644 --- a/frontend/dockerfile/dockerfile_lint_test.go +++ b/frontend/dockerfile/dockerfile_lint_test.go @@ -41,8 +41,90 @@ var lintTests = integration.TestFuncs( testBaseImagePlatformMismatch, testAllTargetUnmarshal, testRedundantTargetPlatform, + testSecretsUsedInArgOrEnv, ) +func testSecretsUsedInArgOrEnv(t *testing.T, sb integration.Sandbox) { + dockerfile := []byte(` +FROM scratch +ARG SECRET_PASSPHRASE +ENV SUPER_Secret=foo +ENV password=bar secret=baz +ARG super_duper_secret_token=foo auth=bar +ENV apikey=bar sunflower=foo +ENV git_key= +`) + checkLinterWarnings(t, sb, &lintTestParams{ + Dockerfile: dockerfile, + Warnings: []expectedLintWarning{ + { + RuleName: "SecretsUsedInArgOrEnv", + Description: "Sensitive data should not be used in the ARG or ENV commands", + Detail: `Do not use ARG or ENV instructions for sensitive data (ARG "SECRET_PASSPHRASE")`, + URL: "https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/", + Level: 1, + Line: 3, + }, + { + RuleName: "SecretsUsedInArgOrEnv", + Description: "Sensitive data should not be used in the ARG or ENV commands", + Detail: `Do not use ARG or ENV instructions for sensitive data (ENV "SUPER_Secret")`, + URL: "https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/", + Level: 1, + Line: 4, + }, + { + RuleName: "SecretsUsedInArgOrEnv", + Description: "Sensitive data should not be used in the ARG or ENV commands", + Detail: `Do not use ARG or ENV instructions for sensitive data (ENV "password")`, + URL: "https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/", + Level: 1, + Line: 5, + }, + { + RuleName: "SecretsUsedInArgOrEnv", + Description: "Sensitive data should not be used in the ARG or ENV commands", + Detail: `Do not use ARG or ENV instructions for sensitive data (ENV "secret")`, + URL: "https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/", + Level: 1, + Line: 5, + }, + { + RuleName: "SecretsUsedInArgOrEnv", + Description: "Sensitive data should not be used in the ARG or ENV commands", + Detail: `Do not use ARG or ENV instructions for sensitive data (ARG "super_duper_secret_token")`, + URL: "https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/", + Level: 1, + Line: 6, + }, + { + RuleName: "SecretsUsedInArgOrEnv", + Description: "Sensitive data should not be used in the ARG or ENV commands", + Detail: `Do not use ARG or ENV instructions for sensitive data (ARG "auth")`, + URL: "https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/", + Level: 1, + Line: 6, + }, + { + RuleName: "SecretsUsedInArgOrEnv", + Description: "Sensitive data should not be used in the ARG or ENV commands", + Detail: `Do not use ARG or ENV instructions for sensitive data (ENV "apikey")`, + URL: "https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/", + Level: 1, + Line: 7, + }, + { + RuleName: "SecretsUsedInArgOrEnv", + Description: "Sensitive data should not be used in the ARG or ENV commands", + Detail: `Do not use ARG or ENV instructions for sensitive data (ENV "git_key")`, + URL: "https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/", + Level: 1, + Line: 8, + }, + }, + }) +} + func testAllTargetUnmarshal(t *testing.T, sb integration.Sandbox) { dockerfile := []byte(` FROM scratch AS first @@ -858,7 +940,7 @@ HEALTHCHECK CMD ["/myotherapp"] func testLegacyKeyValueFormat(t *testing.T, sb integration.Sandbox) { dockerfile := []byte(` FROM scratch -ENV key value +ENV testkey value LABEL key value `) checkLinterWarnings(t, sb, &lintTestParams{ @@ -885,7 +967,7 @@ LABEL key value dockerfile = []byte(` FROM scratch -ENV key=value +ENV testkey=value LABEL key=value `) checkLinterWarnings(t, sb, &lintTestParams{Dockerfile: dockerfile}) @@ -896,7 +978,7 @@ LABEL key=value FROM scratch AS a FROM a AS b -ENV key value +ENV testkey value LABEL key value FROM a AS c diff --git a/frontend/dockerfile/docs/rules/_index.md b/frontend/dockerfile/docs/rules/_index.md index 7e7bb9a8d239..3158072d5558 100644 --- a/frontend/dockerfile/docs/rules/_index.md +++ b/frontend/dockerfile/docs/rules/_index.md @@ -84,5 +84,9 @@ $ docker build --check .