diff --git a/cli/command/image/build.go b/cli/command/image/build.go index d51078496d..99e99fce0f 100644 --- a/cli/command/image/build.go +++ b/cli/command/image/build.go @@ -1,8 +1,6 @@ package image import ( - "archive/tar" - "bufio" "bytes" "context" "encoding/json" @@ -20,9 +18,7 @@ import ( "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/image/build" "github.com/docker/cli/cli/streams" - "github.com/docker/cli/cli/trust" "github.com/docker/cli/internal/jsonstream" - "github.com/docker/cli/internal/lazyregexp" "github.com/docker/cli/opts" buildtypes "github.com/docker/docker/api/types/build" "github.com/docker/docker/api/types/container" @@ -65,7 +61,6 @@ type buildOptions struct { target string imageIDFile string platform string - untrusted bool } // dockerfileFromStdin returns true when the user specified that the Dockerfile @@ -144,7 +139,8 @@ func NewBuildCommand(dockerCli command.Cli) *cobra.Command { flags.SetAnnotation("target", annotation.ExternalURL, []string{"https://docs.docker.com/reference/cli/docker/buildx/build/#target"}) flags.StringVar(&options.imageIDFile, "iidfile", "", "Write the image ID to the file") - command.AddTrustVerificationFlags(flags, &options.untrusted, dockerCli.ContentTrustEnabled()) + flags.Bool("disable-content-trust", dockerCli.ContentTrustEnabled(), "Skip image verification (deprecated)") + _ = flags.MarkHidden("disable-content-trust") flags.StringVar(&options.platform, "platform", os.Getenv("DOCKER_DEFAULT_PLATFORM"), "Set platform if server is multi-platform capable") flags.SetAnnotation("platform", "version", []string{"1.38"}) @@ -286,26 +282,6 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions) ctx, cancel := context.WithCancel(ctx) defer cancel() - var resolvedTags []*resolvedTag - if !options.untrusted { - translator := func(ctx context.Context, ref reference.NamedTagged) (reference.Canonical, error) { - return TrustedReference(ctx, dockerCli, ref) - } - // if there is a tar wrapper, the dockerfile needs to be replaced inside it - if buildCtx != nil { - // Wrap the tar archive to replace the Dockerfile entry with the rewritten - // Dockerfile which uses trusted pulls. - buildCtx = replaceDockerfileForContentTrust(ctx, buildCtx, relDockerfile, translator, &resolvedTags) - } else if dockerfileCtx != nil { - // if there was not archive context still do the possible replacements in Dockerfile - newDockerfile, _, err := rewriteDockerfileFromForContentTrust(ctx, dockerfileCtx, translator) - if err != nil { - return err - } - dockerfileCtx = io.NopCloser(bytes.NewBuffer(newDockerfile)) - } - } - if options.compress { buildCtx, err = build.Compress(buildCtx) if err != nil { @@ -402,21 +378,10 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions) return err } } - if !options.untrusted { - // Since the build was successful, now we must tag any of the resolved - // images from the above Dockerfile rewrite. - for _, resolved := range resolvedTags { - if err := trust.TagTrusted(ctx, dockerCli.Client(), dockerCli.Err(), resolved.digestRef, resolved.tagRef); err != nil { - return err - } - } - } return nil } -type translatorFunc func(context.Context, reference.NamedTagged) (reference.Canonical, error) - // validateTag checks if the given image name can be resolved. func validateTag(rawRepo string) (string, error) { _, err := reference.ParseNormalizedNamed(rawRepo) @@ -427,118 +392,6 @@ func validateTag(rawRepo string) (string, error) { return rawRepo, nil } -var dockerfileFromLinePattern = lazyregexp.New(`(?i)^[\s]*FROM[ \f\r\t\v]+(?P[^ \f\r\t\v\n#]+)`) - -// resolvedTag records the repository, tag, and resolved digest reference -// from a Dockerfile rewrite. -type resolvedTag struct { - digestRef reference.Canonical - tagRef reference.NamedTagged -} - -// noBaseImageSpecifier is the symbol used by the FROM -// command to specify that no base image is to be used. -const noBaseImageSpecifier = "scratch" - -// rewriteDockerfileFromForContentTrust rewrites the given Dockerfile by resolving images in -// "FROM " instructions to a digest reference. `translator` is a -// function that takes a repository name and tag reference and returns a -// trusted digest reference. -// This should be called *only* when content trust is enabled -func rewriteDockerfileFromForContentTrust(ctx context.Context, dockerfile io.Reader, translator translatorFunc) (newDockerfile []byte, resolvedTags []*resolvedTag, err error) { - scanner := bufio.NewScanner(dockerfile) - buf := bytes.NewBuffer(nil) - - // Scan the lines of the Dockerfile, looking for a "FROM" line. - for scanner.Scan() { - line := scanner.Text() - - matches := dockerfileFromLinePattern.FindStringSubmatch(line) - if matches != nil && matches[1] != noBaseImageSpecifier { - // Replace the line with a resolved "FROM repo@digest" - var ref reference.Named - ref, err = reference.ParseNormalizedNamed(matches[1]) - if err != nil { - return nil, nil, err - } - ref = reference.TagNameOnly(ref) - if ref, ok := ref.(reference.NamedTagged); ok { - trustedRef, err := translator(ctx, ref) - if err != nil { - return nil, nil, err - } - - line = dockerfileFromLinePattern.ReplaceAllLiteralString(line, "FROM "+reference.FamiliarString(trustedRef)) - resolvedTags = append(resolvedTags, &resolvedTag{ - digestRef: trustedRef, - tagRef: ref, - }) - } - } - - _, err := fmt.Fprintln(buf, line) - if err != nil { - return nil, nil, err - } - } - - return buf.Bytes(), resolvedTags, scanner.Err() -} - -// replaceDockerfileForContentTrust wraps the given input tar archive stream and -// uses the translator to replace the Dockerfile which uses a trusted reference. -// Returns a new tar archive stream with the replaced Dockerfile. -func replaceDockerfileForContentTrust(ctx context.Context, inputTarStream io.ReadCloser, dockerfileName string, translator translatorFunc, resolvedTags *[]*resolvedTag) io.ReadCloser { - pipeReader, pipeWriter := io.Pipe() - go func() { - tarReader := tar.NewReader(inputTarStream) - tarWriter := tar.NewWriter(pipeWriter) - - defer inputTarStream.Close() - - for { - hdr, err := tarReader.Next() - if err == io.EOF { - // Signals end of archive. - _ = tarWriter.Close() - _ = pipeWriter.Close() - return - } - if err != nil { - _ = pipeWriter.CloseWithError(err) - return - } - - content := io.Reader(tarReader) - if hdr.Name == dockerfileName { - // This entry is the Dockerfile. Since the tar archive was - // generated from a directory on the local filesystem, the - // Dockerfile will only appear once in the archive. - var newDockerfile []byte - newDockerfile, *resolvedTags, err = rewriteDockerfileFromForContentTrust(ctx, content, translator) - if err != nil { - _ = pipeWriter.CloseWithError(err) - return - } - hdr.Size = int64(len(newDockerfile)) - content = bytes.NewBuffer(newDockerfile) - } - - if err := tarWriter.WriteHeader(hdr); err != nil { - _ = pipeWriter.CloseWithError(err) - return - } - - if _, err := io.Copy(tarWriter, content); err != nil { - _ = pipeWriter.CloseWithError(err) - return - } - } - }() - - return pipeReader -} - func imageBuildOptions(dockerCli command.Cli, options buildOptions) buildtypes.ImageBuildOptions { configFile := dockerCli.ConfigFile() return buildtypes.ImageBuildOptions{ diff --git a/cli/command/image/build_test.go b/cli/command/image/build_test.go index 22105e47c0..9e12ec03ee 100644 --- a/cli/command/image/build_test.go +++ b/cli/command/image/build_test.go @@ -47,7 +47,6 @@ func TestRunBuildDockerfileFromStdinWithCompress(t *testing.T) { options.compress = true options.dockerfileName = "-" options.context = dir.Path() - options.untrusted = true assert.NilError(t, runBuild(context.TODO(), cli, options)) expected := []string{fakeBuild.options.Dockerfile, ".dockerignore", "foo"} @@ -74,7 +73,6 @@ func TestRunBuildResetsUidAndGidInContext(t *testing.T) { options := newBuildOptions() options.context = dir.Path() - options.untrusted = true assert.NilError(t, runBuild(context.TODO(), cli, options)) headers := fakeBuild.headers(t) @@ -109,7 +107,6 @@ COPY data /data options := newBuildOptions() options.context = dir.Path() options.dockerfileName = df.Path() - options.untrusted = true assert.NilError(t, runBuild(context.TODO(), cli, options)) expected := []string{fakeBuild.options.Dockerfile, ".dockerignore", "data"} @@ -170,7 +167,6 @@ RUN echo hello world cli := test.NewFakeCli(&fakeClient{imageBuildFunc: fakeBuild.build}) options := newBuildOptions() options.context = tmpDir.Join("context-link") - options.untrusted = true assert.NilError(t, runBuild(context.TODO(), cli, options)) assert.DeepEqual(t, fakeBuild.filenames(t), []string{"Dockerfile"}) diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index 595dec6ce1..388fd2165b 100644 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -2803,7 +2803,6 @@ _docker_image_build() { " local boolean_options=" - --disable-content-trust=false --force-rm --help --no-cache diff --git a/contrib/completion/fish/docker.fish b/contrib/completion/fish/docker.fish index 10d72f28b5..6cb4df7856 100644 --- a/contrib/completion/fish/docker.fish +++ b/contrib/completion/fish/docker.fish @@ -139,7 +139,6 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from build' -l cpu-quota -d complete -c docker -A -f -n '__fish_seen_subcommand_from build' -s c -l cpu-shares -d 'CPU shares (relative weight)' complete -c docker -A -f -n '__fish_seen_subcommand_from build' -l cpuset-cpus -d 'CPUs in which to allow execution (0-3, 0,1)' complete -c docker -A -f -n '__fish_seen_subcommand_from build' -l cpuset-mems -d 'MEMs in which to allow execution (0-3, 0,1)' -complete -c docker -A -f -n '__fish_seen_subcommand_from build' -l disable-content-trust -d 'Skip image verification' complete -c docker -A -f -n '__fish_seen_subcommand_from build' -s f -l file -d "Name of the Dockerfile (Default is ‘PATH/Dockerfile’)" complete -c docker -A -f -n '__fish_seen_subcommand_from build' -l force-rm -d 'Always remove intermediate containers' complete -c docker -A -f -n '__fish_seen_subcommand_from build' -l help -d 'Print usage' diff --git a/contrib/completion/zsh/_docker b/contrib/completion/zsh/_docker index 88350aabfb..05ff5d500c 100644 --- a/contrib/completion/zsh/_docker +++ b/contrib/completion/zsh/_docker @@ -1005,7 +1005,6 @@ __docker_image_subcommand() { "($help)--cpu-rt-runtime=[Limit the CPU real-time runtime]:CPU real-time runtime in microseconds: " \ "($help)--cpuset-cpus=[CPUs in which to allow execution]:CPUs: " \ "($help)--cpuset-mems=[MEMs in which to allow execution]:MEMs: " \ - "($help)--disable-content-trust[Skip image verification]" \ "($help -f --file)"{-f=,--file=}"[Name of the Dockerfile]:Dockerfile:_files" \ "($help)--force-rm[Always remove intermediate containers]" \ "($help)--isolation=[Container isolation technology]:isolation:(default hyperv process)" \ diff --git a/docs/reference/commandline/build.md b/docs/reference/commandline/build.md index dca5ee76ab..491b0477db 100644 --- a/docs/reference/commandline/build.md +++ b/docs/reference/commandline/build.md @@ -21,7 +21,6 @@ Build an image from a Dockerfile | `-c`, `--cpu-shares` | `int64` | `0` | CPU shares (relative weight) | | `--cpuset-cpus` | `string` | | CPUs in which to allow execution (0-3, 0,1) | | `--cpuset-mems` | `string` | | MEMs in which to allow execution (0-3, 0,1) | -| `--disable-content-trust` | `bool` | `true` | Skip image verification | | [`-f`](https://docs.docker.com/reference/cli/docker/buildx/build/#file), [`--file`](https://docs.docker.com/reference/cli/docker/buildx/build/#file) | `string` | | Name of the Dockerfile (Default is `PATH/Dockerfile`) | | `--force-rm` | `bool` | | Always remove intermediate containers | | `--iidfile` | `string` | | Write the image ID to the file | diff --git a/docs/reference/commandline/builder_build.md b/docs/reference/commandline/builder_build.md index ad9c095321..71589da94b 100644 --- a/docs/reference/commandline/builder_build.md +++ b/docs/reference/commandline/builder_build.md @@ -21,7 +21,6 @@ Build an image from a Dockerfile | `-c`, `--cpu-shares` | `int64` | `0` | CPU shares (relative weight) | | `--cpuset-cpus` | `string` | | CPUs in which to allow execution (0-3, 0,1) | | `--cpuset-mems` | `string` | | MEMs in which to allow execution (0-3, 0,1) | -| `--disable-content-trust` | `bool` | `true` | Skip image verification | | [`-f`](https://docs.docker.com/reference/cli/docker/buildx/build/#file), [`--file`](https://docs.docker.com/reference/cli/docker/buildx/build/#file) | `string` | | Name of the Dockerfile (Default is `PATH/Dockerfile`) | | `--force-rm` | `bool` | | Always remove intermediate containers | | `--iidfile` | `string` | | Write the image ID to the file | diff --git a/docs/reference/commandline/image_build.md b/docs/reference/commandline/image_build.md index bd9ae8ee3d..62575c4e08 100644 --- a/docs/reference/commandline/image_build.md +++ b/docs/reference/commandline/image_build.md @@ -21,7 +21,6 @@ Build an image from a Dockerfile | `-c`, `--cpu-shares` | `int64` | `0` | CPU shares (relative weight) | | `--cpuset-cpus` | `string` | | CPUs in which to allow execution (0-3, 0,1) | | `--cpuset-mems` | `string` | | MEMs in which to allow execution (0-3, 0,1) | -| `--disable-content-trust` | `bool` | `true` | Skip image verification | | [`-f`](https://docs.docker.com/reference/cli/docker/buildx/build/#file), [`--file`](https://docs.docker.com/reference/cli/docker/buildx/build/#file) | `string` | | Name of the Dockerfile (Default is `PATH/Dockerfile`) | | `--force-rm` | `bool` | | Always remove intermediate containers | | `--iidfile` | `string` | | Write the image ID to the file | diff --git a/e2e/image/build_test.go b/e2e/image/build_test.go index fadd139224..08f4a5a7e9 100644 --- a/e2e/image/build_test.go +++ b/e2e/image/build_test.go @@ -14,7 +14,6 @@ import ( is "gotest.tools/v3/assert/cmp" "gotest.tools/v3/fs" "gotest.tools/v3/icmd" - "gotest.tools/v3/skip" ) func TestBuildFromContextDirectoryWithTag(t *testing.T) { @@ -62,68 +61,6 @@ func TestBuildFromContextDirectoryWithTag(t *testing.T) { }) } -func TestTrustedBuild(t *testing.T) { - skip.If(t, environment.RemoteDaemon()) - t.Setenv("DOCKER_BUILDKIT", "0") - - dir := fixtures.SetupConfigFile(t) - defer dir.Remove() - image1 := fixtures.CreateMaskedTrustedRemoteImage(t, registryPrefix, "trust-build1", "latest") - image2 := fixtures.CreateMaskedTrustedRemoteImage(t, registryPrefix, "trust-build2", "latest") - - buildDir := fs.NewDir(t, "test-trusted-build-context-dir", - fs.WithFile("Dockerfile", fmt.Sprintf(` - FROM %s as build-base - RUN echo ok > /foo - FROM %s - COPY --from=build-base foo bar - `, image1, image2))) - defer buildDir.Remove() - - result := icmd.RunCmd( - icmd.Command("docker", "build", "-t", "myimage", "."), - withWorkingDir(buildDir), - fixtures.WithConfig(dir.Path()), - fixtures.WithTrust, - fixtures.WithNotary, - ) - - result.Assert(t, icmd.Expected{ - Out: fmt.Sprintf("FROM %s@sha", image1[:len(image1)-7]), - Err: fmt.Sprintf("Tagging %s@sha", image1[:len(image1)-7]), - }) - result.Assert(t, icmd.Expected{ - Out: fmt.Sprintf("FROM %s@sha", image2[:len(image2)-7]), - }) -} - -func TestTrustedBuildUntrustedImage(t *testing.T) { - skip.If(t, environment.RemoteDaemon()) - t.Setenv("DOCKER_BUILDKIT", "0") - - dir := fixtures.SetupConfigFile(t) - defer dir.Remove() - buildDir := fs.NewDir(t, "test-trusted-build-context-dir", - fs.WithFile("Dockerfile", fmt.Sprintf(` - FROM %s - RUN [] - `, fixtures.AlpineImage))) - defer buildDir.Remove() - - result := icmd.RunCmd( - icmd.Command("docker", "build", "-t", "myimage", "."), - withWorkingDir(buildDir), - fixtures.WithConfig(dir.Path()), - fixtures.WithTrust, - fixtures.WithNotary, - ) - - result.Assert(t, icmd.Expected{ - ExitCode: 1, - Err: "does not have trust data for", - }) -} - func TestBuildIidFileSquash(t *testing.T) { environment.SkipIfNotExperimentalDaemon(t) t.Setenv("DOCKER_BUILDKIT", "0")