From a5d9f1cb280006c3cbc4693e682687c559cf5f8a Mon Sep 17 00:00:00 2001 From: Daniel J Walsh Date: Mon, 1 Nov 2021 16:52:48 -0400 Subject: [PATCH] Add --unsetenv option to buildah commit and build This option will allow users to remove environment variables from the final image. Fixes: https://github.com/containers/buildah/issues/3512 Signed-off-by: Daniel J Walsh --- cmd/buildah/build.go | 1 + cmd/buildah/commit.go | 33 ++++++++++++++++++++++++++------- commit.go | 2 ++ define/build.go | 2 ++ docs/buildah-build.1.md | 4 ++++ docs/buildah-commit.1.md | 4 ++++ image.go | 24 ++++++++++++++++++++++-- imagebuildah/executor.go | 2 ++ imagebuildah/stage_executor.go | 1 + pkg/cli/common.go | 3 +++ tests/bud.bats | 11 +++++++++++ tests/commit.bats | 13 +++++++++++++ 12 files changed, 91 insertions(+), 9 deletions(-) diff --git a/cmd/buildah/build.go b/cmd/buildah/build.go index bc5898d541..c8752b205c 100644 --- a/cmd/buildah/build.go +++ b/cmd/buildah/build.go @@ -381,6 +381,7 @@ func buildCmd(c *cobra.Command, inputArgs []string, iopts buildOptions) error { Excludes: excludes, Timestamp: timestamp, Platforms: platforms, + UnsetEnvs: iopts.UnsetEnvs, } if iopts.Quiet { options.ReportWriter = ioutil.Discard diff --git a/cmd/buildah/commit.go b/cmd/buildah/commit.go index e2909fddd9..c8b7e40194 100644 --- a/cmd/buildah/commit.go +++ b/cmd/buildah/commit.go @@ -11,6 +11,7 @@ import ( "github.com/containers/buildah/pkg/parse" "github.com/containers/buildah/util" "github.com/containers/common/pkg/auth" + "github.com/containers/common/pkg/completion" "github.com/containers/image/v5/pkg/shortnames" storageTransport "github.com/containers/image/v5/storage" "github.com/containers/image/v5/transports/alltransports" @@ -40,6 +41,7 @@ type commitInputOptions struct { tlsVerify bool encryptionKeys []string encryptLayers []int + unsetenvs []string } func init() { @@ -59,30 +61,45 @@ func init() { buildah commit containerID docker://localhost:5000/imageId`, } commitCommand.SetUsageTemplate(UsageTemplate()) - flags := commitCommand.Flags() + commitListFlagSet(commitCommand, &opts) + rootCmd.AddCommand(commitCommand) + +} + +func commitListFlagSet(cmd *cobra.Command, opts *commitInputOptions) { + flags := cmd.Flags() flags.SetInterspersed(false) flags.StringVar(&opts.authfile, "authfile", auth.GetDefaultAuthFile(), "path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") + _ = cmd.RegisterFlagCompletionFunc("authfile", completion.AutocompleteDefault) flags.StringVar(&opts.blobCache, "blob-cache", "", "assume image blobs in the specified directory will be available for pushing") - flags.StringSliceVar(&opts.encryptionKeys, "encryption-key", nil, "key with the encryption protocol to use needed to encrypt the image (e.g. jwe:/path/to/key.pem)") - flags.IntSliceVar(&opts.encryptLayers, "encrypt-layer", nil, "layers to encrypt, 0-indexed layer indices with support for negative indexing (e.g. 0 is the first layer, -1 is the last layer). If not defined, will encrypt all layers if encryption-key flag is specified") - if err := flags.MarkHidden("blob-cache"); err != nil { panic(fmt.Sprintf("error marking blob-cache as hidden: %v", err)) } + flags.StringSliceVar(&opts.encryptionKeys, "encryption-key", nil, "key with the encryption protocol to use needed to encrypt the image (e.g. jwe:/path/to/key.pem)") + _ = cmd.RegisterFlagCompletionFunc("encryption-key", completion.AutocompleteDefault) + flags.IntSliceVar(&opts.encryptLayers, "encrypt-layer", nil, "layers to encrypt, 0-indexed layer indices with support for negative indexing (e.g. 0 is the first layer, -1 is the last layer). If not defined, will encrypt all layers if encryption-key flag is specified") + _ = cmd.RegisterFlagCompletionFunc("encryption-key", completion.AutocompleteNone) flags.StringVar(&opts.certDir, "cert-dir", "", "use certificates at the specified path to access the registry") + _ = cmd.RegisterFlagCompletionFunc("cirt-dir", completion.AutocompleteDefault) flags.StringVar(&opts.creds, "creds", "", "use `[username[:password]]` for accessing the registry") + _ = cmd.RegisterFlagCompletionFunc("creds", completion.AutocompleteNone) flags.BoolVarP(&opts.disableCompression, "disable-compression", "D", true, "don't compress layers") flags.StringVarP(&opts.format, "format", "f", defaultFormat(), "`format` of the image manifest and metadata") + _ = cmd.RegisterFlagCompletionFunc("format", completion.AutocompleteNone) flags.StringVar(&opts.manifest, "manifest", "", "adds created image to the specified manifest list. Creates manifest list if it does not exist") + _ = cmd.RegisterFlagCompletionFunc("manifest", completion.AutocompleteNone) flags.StringVar(&opts.iidfile, "iidfile", "", "write the image ID to the file") + _ = cmd.RegisterFlagCompletionFunc("iidfile", completion.AutocompleteDefault) flags.BoolVar(&opts.omitTimestamp, "omit-timestamp", false, "set created timestamp to epoch 0 to allow for deterministic builds") flags.Int64Var(&opts.timestamp, "timestamp", 0, "set created timestamp to epoch seconds to allow for deterministic builds, defaults to current time") + _ = cmd.RegisterFlagCompletionFunc("timestamp", completion.AutocompleteNone) flags.BoolVarP(&opts.quiet, "quiet", "q", false, "don't output progress information when writing images") flags.StringVar(&opts.referenceTime, "reference-time", "", "set the timestamp on the image to match the named `file`") + _ = cmd.RegisterFlagCompletionFunc("reference-time", completion.AutocompleteNone) flags.StringVar(&opts.signBy, "sign-by", "", "sign the image using a GPG key with the specified `FINGERPRINT`") - + _ = cmd.RegisterFlagCompletionFunc("sign-by", completion.AutocompleteNone) if err := flags.MarkHidden("omit-timestamp"); err != nil { panic(fmt.Sprintf("error marking omit-timestamp as hidden: %v", err)) } @@ -92,6 +109,7 @@ func init() { flags.BoolVar(&opts.rm, "rm", false, "remove the container and its content after committing it to an image. Default leaves the container and its content in place.") flags.StringVar(&opts.signaturePolicy, "signature-policy", "", "`pathname` of signature policy file (not usually used)") + _ = cmd.RegisterFlagCompletionFunc("signature-policy", completion.AutocompleteDefault) if err := flags.MarkHidden("signature-policy"); err != nil { panic(fmt.Sprintf("error marking signature-policy as hidden: %v", err)) @@ -100,8 +118,8 @@ func init() { flags.BoolVar(&opts.squash, "squash", false, "produce an image with only one layer") flags.BoolVar(&opts.tlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when accessing the registry. TLS verification cannot be used when talking to an insecure registry.") - rootCmd.AddCommand(commitCommand) - + flags.StringSliceVar(&opts.unsetenvs, "unsetenv", nil, "unset env from final image") + _ = cmd.RegisterFlagCompletionFunc("unsetenv", completion.AutocompleteNone) } func commitCmd(c *cobra.Command, args []string, iopts commitInputOptions) error { @@ -190,6 +208,7 @@ func commitCmd(c *cobra.Command, args []string, iopts commitInputOptions) error SignBy: iopts.signBy, OciEncryptConfig: encConfig, OciEncryptLayers: encLayers, + UnsetEnvs: iopts.unsetenvs, } exclusiveFlags := 0 if c.Flag("reference-time").Changed { diff --git a/commit.go b/commit.go index bbf1727fbd..25c3007161 100644 --- a/commit.go +++ b/commit.go @@ -101,6 +101,8 @@ type CommitOptions struct { // integers in the slice represent 0-indexed layer indices, with support for negative // indexing. i.e. 0 is the first layer, -1 is the last (top-most) layer. OciEncryptLayers *[]int + // UnsetEnvs is a list of environments to not add to final image. + UnsetEnvs []string } var ( diff --git a/define/build.go b/define/build.go index f5db8a9aaa..c15d163bf8 100644 --- a/define/build.go +++ b/define/build.go @@ -240,4 +240,6 @@ type BuildOptions struct { // to match the set of platforms for which all of the build's base // images are available. If this field is set, Platforms is ignored. AllPlatforms bool + // UnsetEnvs is a list of environments to not add to final image. + UnsetEnvs []string } diff --git a/docs/buildah-build.1.md b/docs/buildah-build.1.md index 738db5fc7d..5883fed532 100644 --- a/docs/buildah-build.1.md +++ b/docs/buildah-build.1.md @@ -586,6 +586,10 @@ include: "sigpending": maximum number of pending signals (ulimit -i) "stack": maximum stack size (ulimit -s) +**--unsetenv** *env* + +Unset environment variables from the final image. + **--userns** *how* Sets the configuration for user namespaces when handling `RUN` instructions. diff --git a/docs/buildah-commit.1.md b/docs/buildah-commit.1.md index 065cf4fd83..52cc19b521 100644 --- a/docs/buildah-commit.1.md +++ b/docs/buildah-commit.1.md @@ -101,6 +101,10 @@ When --timestamp is set, the created timestamp is always set to the time specifi Require HTTPS and verification of certificates when talking to container registries (defaults to true). TLS verification cannot be used when talking to an insecure registry. +**--unsetenv** *env* + +Unset environment variables from the final image. + ## EXAMPLE This example saves an image based on the container. diff --git a/image.go b/image.go index ef05f37d26..df94b0cd0b 100644 --- a/image.go +++ b/image.go @@ -752,11 +752,31 @@ func (b *Builder) makeImageRef(options CommitOptions) (types.ImageReference, err if manifestType == "" { manifestType = define.OCIv1ImageManifest } - oconfig, err := json.Marshal(&b.OCIv1) + oci1 := b.OCIv1 + docker := b.Docker + for _, u := range options.UnsetEnvs { + var env []string + for _, e := range oci1.Config.Env { + if strings.HasPrefix(e, u+"=") { + continue + } + env = append(env, e) + } + oci1.Config.Env = env + env = []string{} + for _, e := range docker.Config.Env { + if strings.HasPrefix(e, u+"=") { + continue + } + env = append(env, e) + } + docker.Config.Env = env + } + oconfig, err := json.Marshal(&oci1) if err != nil { return nil, errors.Wrapf(err, "error encoding OCI-format image configuration %#v", b.OCIv1) } - dconfig, err := json.Marshal(&b.Docker) + dconfig, err := json.Marshal(&docker) if err != nil { return nil, errors.Wrapf(err, "error encoding docker-format image configuration %#v", b.Docker) } diff --git a/imagebuildah/executor.go b/imagebuildah/executor.go index 0930b4d5eb..229e4a4b7f 100644 --- a/imagebuildah/executor.go +++ b/imagebuildah/executor.go @@ -126,6 +126,7 @@ type Executor struct { secrets map[string]define.Secret sshsources map[string]*sshagent.Source logPrefix string + unsetEnvs []string } type imageTypeAndHistoryAndDiffIDs struct { @@ -270,6 +271,7 @@ func newExecutor(logger *logrus.Logger, logPrefix string, store storage.Store, o secrets: secrets, sshsources: sshsources, logPrefix: logPrefix, + unsetEnvs: options.UnsetEnvs, } if exec.err == nil { exec.err = os.Stderr diff --git a/imagebuildah/stage_executor.go b/imagebuildah/stage_executor.go index 99f829db92..414b01f96b 100644 --- a/imagebuildah/stage_executor.go +++ b/imagebuildah/stage_executor.go @@ -1403,6 +1403,7 @@ func (s *StageExecutor) commit(ctx context.Context, createdBy string, emptyLayer RetryDelay: s.executor.retryPullPushDelay, HistoryTimestamp: s.executor.timestamp, Manifest: s.executor.manifest, + UnsetEnvs: s.executor.unsetEnvs, } imgID, _, manifestDigest, err := s.builder.Commit(ctx, imageRef, options) if err != nil { diff --git a/pkg/cli/common.go b/pkg/cli/common.go index f6f8304019..ac7391c539 100644 --- a/pkg/cli/common.go +++ b/pkg/cli/common.go @@ -87,6 +87,7 @@ type BudResults struct { Jobs int LogRusage bool RusageLogFile string + UnsetEnvs []string } // FromAndBugResults represents the results for common flags @@ -231,6 +232,7 @@ func GetBudFlags(flags *BudResults) pflag.FlagSet { fs.Int64Var(&flags.Timestamp, "timestamp", 0, "set created timestamp to the specified epoch seconds to allow for deterministic builds, defaults to current time") fs.BoolVar(&flags.TLSVerify, "tls-verify", true, "require HTTPS and verify certificates when accessing the registry") fs.String("variant", "", "override the `variant` of the specified image") + fs.StringSliceVar(&flags.UnsetEnvs, "unsetenv", nil, "Unset environment variable from final image") return fs } @@ -263,6 +265,7 @@ func GetBudFlagsCompletions() commonComp.FlagCompletions { flagCompletion["target"] = commonComp.AutocompleteNone flagCompletion["timestamp"] = commonComp.AutocompleteNone flagCompletion["variant"] = commonComp.AutocompleteNone + flagCompletion["unsetenv"] = commonComp.AutocompleteNone return flagCompletion } diff --git a/tests/bud.bats b/tests/bud.bats index 49374d64b5..aa3f928924 100644 --- a/tests/bud.bats +++ b/tests/bud.bats @@ -454,6 +454,17 @@ symlink(subdir)" expect_output "${target}-working-container" } +@test "build --unsetenv PATH" { + _prefetch alpine + target=scratch-image + run_buildah build --unsetenv PATH --signature-policy ${TESTSDIR}/policy.json -t oci-${target} ${TESTSDIR}/bud/from-scratch + run_buildah inspect --type=image --format '{{.OCIv1.Config.Env}}' oci-${target} + expect_output "[]" "No Path should be defined" + run_buildah build --unsetenv PATH --signature-policy ${TESTSDIR}/policy.json --format docker -t docker-${target} ${TESTSDIR}/bud/from-scratch + run_buildah inspect --type=image --format '{{.OCIv1.Config.Env}}' docker-${target} + expect_output "[]" "No Path should be defined" +} + @test "bud-from-scratch-untagged" { run_buildah build --iidfile ${TESTDIR}/output.iid --signature-policy ${TESTSDIR}/policy.json ${TESTSDIR}/bud/from-scratch iid=$(cat ${TESTDIR}/output.iid) diff --git a/tests/commit.bats b/tests/commit.bats index aedd0c3a10..68caeab63d 100644 --- a/tests/commit.bats +++ b/tests/commit.bats @@ -39,6 +39,19 @@ load helpers expect_output --from="$mediatype" "application/vnd.docker.image.rootfs.diff.tar.gzip" } +@test "commit --unsetenv PATH" { + _prefetch alpine + run_buildah from --quiet --pull=false --signature-policy ${TESTSDIR}/policy.json alpine + cid=$output + run_buildah commit --unsetenv PATH --signature-policy ${TESTSDIR}/policy.json $cid alpine-image-oci + run_buildah commit --unsetenv PATH --format docker --disable-compression=false --signature-policy ${TESTSDIR}/policy.json $cid alpine-image-docker + + run_buildah inspect --type=image --format '{{.OCIv1.Config.Env}}' alpine-image-oci + expect_output "[]" "No Path should be defined" + run_buildah inspect --type=image --format '{{.Docker.Config.Env}}' alpine-image-docker + expect_output "[]" "No Path should be defined" +} + @test "commit quiet test" { _prefetch alpine run_buildah from --quiet --pull=false --signature-policy ${TESTSDIR}/policy.json alpine