mirror of https://github.com/docker/cli.git
Merge pull request #924 from vdemeester/trust-suite-tests
Add some content trust tests
This commit is contained in:
commit
c0ffb9491c
|
@ -2,6 +2,7 @@ package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
@ -10,6 +11,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/cli/internal/test"
|
"github.com/docker/cli/internal/test"
|
||||||
|
"github.com/docker/cli/internal/test/notary"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/network"
|
"github.com/docker/docker/api/types/network"
|
||||||
|
@ -119,6 +121,51 @@ func TestCreateContainerPullsImageIfMissing(t *testing.T) {
|
||||||
assert.Check(t, is.Contains(stderr, "Unable to find image 'does-not-exist-locally:latest' locally"))
|
assert.Check(t, is.Contains(stderr, "Unable to find image 'does-not-exist-locally:latest' locally"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewCreateCommandWithContentTrustErrors(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
expectedError string
|
||||||
|
notaryFunc test.NotaryClientFuncType
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "offline-notary-server",
|
||||||
|
notaryFunc: notary.GetOfflineNotaryRepository,
|
||||||
|
expectedError: "client is offline",
|
||||||
|
args: []string{"image:tag"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "uninitialized-notary-server",
|
||||||
|
notaryFunc: notary.GetUninitializedNotaryRepository,
|
||||||
|
expectedError: "remote trust data does not exist",
|
||||||
|
args: []string{"image:tag"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty-notary-server",
|
||||||
|
notaryFunc: notary.GetEmptyTargetsNotaryRepository,
|
||||||
|
expectedError: "No valid trust data for tag",
|
||||||
|
args: []string{"image:tag"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
cli := test.NewFakeCli(&fakeClient{
|
||||||
|
createContainerFunc: func(config *container.Config,
|
||||||
|
hostConfig *container.HostConfig,
|
||||||
|
networkingConfig *network.NetworkingConfig,
|
||||||
|
containerName string,
|
||||||
|
) (container.ContainerCreateCreatedBody, error) {
|
||||||
|
return container.ContainerCreateCreatedBody{}, fmt.Errorf("shouldn't try to pull image")
|
||||||
|
},
|
||||||
|
}, test.EnableContentTrust)
|
||||||
|
cli.SetNotaryClient(tc.notaryFunc)
|
||||||
|
cmd := NewCreateCommand(cli)
|
||||||
|
cmd.SetOutput(ioutil.Discard)
|
||||||
|
cmd.SetArgs(tc.args)
|
||||||
|
err := cmd.Execute()
|
||||||
|
assert.ErrorContains(t, err, tc.expectedError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type fakeNotFound struct{}
|
type fakeNotFound struct{}
|
||||||
|
|
||||||
func (f fakeNotFound) NotFound() bool { return true }
|
func (f fakeNotFound) NotFound() bool { return true }
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
package container
|
package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/cli/internal/test"
|
"github.com/docker/cli/internal/test"
|
||||||
|
"github.com/docker/cli/internal/test/notary"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/network"
|
"github.com/docker/docker/api/types/network"
|
||||||
"github.com/gotestyourself/gotestyourself/assert"
|
"github.com/gotestyourself/gotestyourself/assert"
|
||||||
|
is "github.com/gotestyourself/gotestyourself/assert/cmp"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRunLabel(t *testing.T) {
|
func TestRunLabel(t *testing.T) {
|
||||||
|
@ -23,3 +26,48 @@ func TestRunLabel(t *testing.T) {
|
||||||
cmd.SetArgs([]string{"--label", "foo", "busybox"})
|
cmd.SetArgs([]string{"--label", "foo", "busybox"})
|
||||||
assert.NilError(t, cmd.Execute())
|
assert.NilError(t, cmd.Execute())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRunCommandWithContentTrustErrors(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
expectedError string
|
||||||
|
notaryFunc test.NotaryClientFuncType
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "offline-notary-server",
|
||||||
|
notaryFunc: notary.GetOfflineNotaryRepository,
|
||||||
|
expectedError: "client is offline",
|
||||||
|
args: []string{"image:tag"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "uninitialized-notary-server",
|
||||||
|
notaryFunc: notary.GetUninitializedNotaryRepository,
|
||||||
|
expectedError: "remote trust data does not exist",
|
||||||
|
args: []string{"image:tag"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty-notary-server",
|
||||||
|
notaryFunc: notary.GetEmptyTargetsNotaryRepository,
|
||||||
|
expectedError: "No valid trust data for tag",
|
||||||
|
args: []string{"image:tag"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
cli := test.NewFakeCli(&fakeClient{
|
||||||
|
createContainerFunc: func(config *container.Config,
|
||||||
|
hostConfig *container.HostConfig,
|
||||||
|
networkingConfig *network.NetworkingConfig,
|
||||||
|
containerName string,
|
||||||
|
) (container.ContainerCreateCreatedBody, error) {
|
||||||
|
return container.ContainerCreateCreatedBody{}, fmt.Errorf("shouldn't try to pull image")
|
||||||
|
},
|
||||||
|
}, test.EnableContentTrust)
|
||||||
|
cli.SetNotaryClient(tc.notaryFunc)
|
||||||
|
cmd := NewRunCommand(cli)
|
||||||
|
cmd.SetArgs(tc.args)
|
||||||
|
err := cmd.Execute()
|
||||||
|
assert.Assert(t, err != nil)
|
||||||
|
assert.Assert(t, is.Contains(cli.ErrBuffer().String(), tc.expectedError))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -41,6 +41,7 @@ func TestRunBuildResetsUidAndGidInContext(t *testing.T) {
|
||||||
|
|
||||||
options := newBuildOptions()
|
options := newBuildOptions()
|
||||||
options.context = dir.Path()
|
options.context = dir.Path()
|
||||||
|
options.untrusted = true
|
||||||
|
|
||||||
err := runBuild(cli, options)
|
err := runBuild(cli, options)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
|
|
@ -57,6 +57,7 @@ func TestRunBuildDockerfileFromStdinWithCompress(t *testing.T) {
|
||||||
options.compress = true
|
options.compress = true
|
||||||
options.dockerfileName = "-"
|
options.dockerfileName = "-"
|
||||||
options.context = dir
|
options.context = dir
|
||||||
|
options.untrusted = true
|
||||||
|
|
||||||
err = runBuild(cli, options)
|
err = runBuild(cli, options)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
@ -191,6 +192,7 @@ RUN echo hello world
|
||||||
cli := test.NewFakeCli(&fakeClient{imageBuildFunc: fakeImageBuild})
|
cli := test.NewFakeCli(&fakeClient{imageBuildFunc: fakeImageBuild})
|
||||||
options := newBuildOptions()
|
options := newBuildOptions()
|
||||||
options.context = tmpDir.Join("context-link")
|
options.context = tmpDir.Join("context-link")
|
||||||
|
options.untrusted = true
|
||||||
assert.NilError(t, runBuild(cli, options))
|
assert.NilError(t, runBuild(cli, options))
|
||||||
|
|
||||||
assert.DeepEqual(t, files, []string{"Dockerfile"})
|
assert.DeepEqual(t, files, []string{"Dockerfile"})
|
||||||
|
|
|
@ -59,7 +59,7 @@ func runPull(cli command.Cli, opts pullOptions) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, AuthResolver(cli), distributionRef.String())
|
imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, nil, AuthResolver(cli), distributionRef.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,7 +93,7 @@ func TestNewPullCommandWithContentTrustErrors(t *testing.T) {
|
||||||
args: []string{"image:tag"},
|
args: []string{"image:tag"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "empty-notary-server",
|
name: "uninitialized-notary-server",
|
||||||
notaryFunc: notary.GetUninitializedNotaryRepository,
|
notaryFunc: notary.GetUninitializedNotaryRepository,
|
||||||
expectedError: "remote trust data does not exist",
|
expectedError: "remote trust data does not exist",
|
||||||
args: []string{"image:tag"},
|
args: []string{"image:tag"},
|
||||||
|
|
|
@ -198,7 +198,7 @@ func trustedPull(ctx context.Context, cli command.Cli, imgRefAndAuth trust.Image
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
updatedImgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, AuthResolver(cli), trustedRef.String())
|
updatedImgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, nil, AuthResolver(cli), trustedRef.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -293,35 +293,24 @@ func imagePullPrivileged(ctx context.Context, cli command.Cli, imgRefAndAuth tru
|
||||||
|
|
||||||
// TrustedReference returns the canonical trusted reference for an image reference
|
// TrustedReference returns the canonical trusted reference for an image reference
|
||||||
func TrustedReference(ctx context.Context, cli command.Cli, ref reference.NamedTagged, rs registry.Service) (reference.Canonical, error) {
|
func TrustedReference(ctx context.Context, cli command.Cli, ref reference.NamedTagged, rs registry.Service) (reference.Canonical, error) {
|
||||||
var (
|
imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, rs, AuthResolver(cli), ref.String())
|
||||||
repoInfo *registry.RepositoryInfo
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
if rs != nil {
|
|
||||||
repoInfo, err = rs.ResolveRepository(ref)
|
|
||||||
} else {
|
|
||||||
repoInfo, err = registry.ParseRepositoryInfo(ref)
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve the Auth config relevant for this server
|
notaryRepo, err := cli.NotaryClient(imgRefAndAuth, []string{"pull"})
|
||||||
authConfig := command.ResolveAuthConfig(ctx, cli, repoInfo.Index)
|
|
||||||
|
|
||||||
notaryRepo, err := trust.GetNotaryRepository(cli.In(), cli.Out(), command.UserAgent(), repoInfo, &authConfig, "pull")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "error establishing connection to trust repository")
|
return nil, errors.Wrap(err, "error establishing connection to trust repository")
|
||||||
}
|
}
|
||||||
|
|
||||||
t, err := notaryRepo.GetTargetByName(ref.Tag(), trust.ReleasesRole, data.CanonicalTargetsRole)
|
t, err := notaryRepo.GetTargetByName(ref.Tag(), trust.ReleasesRole, data.CanonicalTargetsRole)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, trust.NotaryError(repoInfo.Name.Name(), err)
|
return nil, trust.NotaryError(imgRefAndAuth.RepoInfo().Name.Name(), err)
|
||||||
}
|
}
|
||||||
// Only list tags in the top level targets role or the releases delegation role - ignore
|
// Only list tags in the top level targets role or the releases delegation role - ignore
|
||||||
// all other delegation roles
|
// all other delegation roles
|
||||||
if t.Role != trust.ReleasesRole && t.Role != data.CanonicalTargetsRole {
|
if t.Role != trust.ReleasesRole && t.Role != data.CanonicalTargetsRole {
|
||||||
return nil, trust.NotaryError(repoInfo.Name.Name(), client.ErrNoSuchTarget(ref.Tag()))
|
return nil, trust.NotaryError(imgRefAndAuth.RepoInfo().Name.Name(), client.ErrNoSuchTarget(ref.Tag()))
|
||||||
}
|
}
|
||||||
r, err := convertTarget(t.Target)
|
r, err := convertTarget(t.Target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -66,7 +66,7 @@ type trustKey struct {
|
||||||
// This information is to be pretty printed or serialized into a machine-readable format.
|
// This information is to be pretty printed or serialized into a machine-readable format.
|
||||||
func lookupTrustInfo(cli command.Cli, remote string) (trustTagRowList, []client.RoleWithSignatures, []data.Role, error) {
|
func lookupTrustInfo(cli command.Cli, remote string) (trustTagRowList, []client.RoleWithSignatures, []data.Role, error) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, image.AuthResolver(cli), remote)
|
imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, nil, image.AuthResolver(cli), remote)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return trustTagRowList{}, []client.RoleWithSignatures{}, []data.Role{}, err
|
return trustTagRowList{}, []client.RoleWithSignatures{}, []data.Role{}, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ func newRevokeCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
|
|
||||||
func revokeTrust(cli command.Cli, remote string, options revokeOptions) error {
|
func revokeTrust(cli command.Cli, remote string, options revokeOptions) error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, image.AuthResolver(cli), remote)
|
imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, nil, image.AuthResolver(cli), remote)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ func newSignCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
func runSignImage(cli command.Cli, options signOptions) error {
|
func runSignImage(cli command.Cli, options signOptions) error {
|
||||||
imageName := options.imageName
|
imageName := options.imageName
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, image.AuthResolver(cli), imageName)
|
imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, nil, image.AuthResolver(cli), imageName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,7 +82,7 @@ func addSigner(cli command.Cli, options signerAddOptions) error {
|
||||||
|
|
||||||
func addSignerToRepo(cli command.Cli, signerName string, repoName string, signerPubKeys []data.PublicKey) error {
|
func addSignerToRepo(cli command.Cli, signerName string, repoName string, signerPubKeys []data.PublicKey) error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, image.AuthResolver(cli), repoName)
|
imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, nil, image.AuthResolver(cli), repoName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,7 +80,7 @@ func isLastSignerForReleases(roleWithSig data.Role, allRoles []client.RoleWithSi
|
||||||
|
|
||||||
func removeSingleSigner(cli command.Cli, repoName, signerName string, forceYes bool) error {
|
func removeSingleSigner(cli command.Cli, repoName, signerName string, forceYes bool) error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, image.AuthResolver(cli), repoName)
|
imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, nil, image.AuthResolver(cli), repoName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -300,14 +300,23 @@ type ImageRefAndAuth struct {
|
||||||
|
|
||||||
// GetImageReferencesAndAuth retrieves the necessary reference and auth information for an image name
|
// GetImageReferencesAndAuth retrieves the necessary reference and auth information for an image name
|
||||||
// as an ImageRefAndAuth struct
|
// as an ImageRefAndAuth struct
|
||||||
func GetImageReferencesAndAuth(ctx context.Context, authResolver func(ctx context.Context, index *registrytypes.IndexInfo) types.AuthConfig, imgName string) (ImageRefAndAuth, error) {
|
func GetImageReferencesAndAuth(ctx context.Context, rs registry.Service,
|
||||||
|
authResolver func(ctx context.Context, index *registrytypes.IndexInfo) types.AuthConfig,
|
||||||
|
imgName string,
|
||||||
|
) (ImageRefAndAuth, error) {
|
||||||
ref, err := reference.ParseNormalizedNamed(imgName)
|
ref, err := reference.ParseNormalizedNamed(imgName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ImageRefAndAuth{}, err
|
return ImageRefAndAuth{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve the Repository name from fqn to RepositoryInfo
|
// Resolve the Repository name from fqn to RepositoryInfo
|
||||||
repoInfo, err := registry.ParseRepositoryInfo(ref)
|
var repoInfo *registry.RepositoryInfo
|
||||||
|
if rs != nil {
|
||||||
|
repoInfo, err = rs.ResolveRepository(ref)
|
||||||
|
} else {
|
||||||
|
repoInfo, err = registry.ParseRepositoryInfo(ref)
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ImageRefAndAuth{}, err
|
return ImageRefAndAuth{}, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
FROM golang:1.9.4-alpine3.6
|
FROM golang:1.9.4-alpine3.6
|
||||||
|
|
||||||
RUN apk add -U git make bash coreutils ca-certificates
|
RUN apk add -U git make bash coreutils ca-certificates curl
|
||||||
|
|
||||||
ARG VNDR_SHA=a6e196d8b4b0cbbdc29aebdb20c59ac6926bb384
|
ARG VNDR_SHA=a6e196d8b4b0cbbdc29aebdb20c59ac6926bb384
|
||||||
RUN go get -d github.com/LK4D4/vndr && \
|
RUN go get -d github.com/LK4D4/vndr && \
|
||||||
|
@ -24,6 +24,12 @@ RUN go get -d github.com/dnephin/filewatcher && \
|
||||||
go build -v -o /usr/bin/filewatcher . && \
|
go build -v -o /usr/bin/filewatcher . && \
|
||||||
rm -rf /go/src/* /go/pkg/* /go/bin/*
|
rm -rf /go/src/* /go/pkg/* /go/bin/*
|
||||||
|
|
||||||
|
# FIXME(vdemeester) only used for e2e, could be in e2e special image in the future
|
||||||
|
ARG NOTARY_VERSION=v0.6.0
|
||||||
|
RUN export URL=https://github.com/theupdateframework/notary/releases/download; \
|
||||||
|
curl -Ls $URL/${NOTARY_VERSION}/notary-Linux-amd64 -o /usr/local/bin/notary && \
|
||||||
|
chmod +x /usr/local/bin/notary
|
||||||
|
|
||||||
ENV CGO_ENABLED=0 \
|
ENV CGO_ENABLED=0 \
|
||||||
PATH=$PATH:/go/src/github.com/docker/cli/build \
|
PATH=$PATH:/go/src/github.com/docker/cli/build \
|
||||||
DISABLE_WARN_OUTSIDE_CONTAINER=1
|
DISABLE_WARN_OUTSIDE_CONTAINER=1
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
package container
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/cli/e2e/internal/fixtures"
|
||||||
|
"github.com/gotestyourself/gotestyourself/icmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateWithContentTrust(t *testing.T) {
|
||||||
|
dir := fixtures.SetupConfigFile(t)
|
||||||
|
defer dir.Remove()
|
||||||
|
image := fixtures.CreateMaskedTrustedRemoteImage(t, registryPrefix, "trust-create", "latest")
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
icmd.RunCommand("docker", "image", "rm", image).Assert(t, icmd.Success)
|
||||||
|
}()
|
||||||
|
|
||||||
|
result := icmd.RunCmd(
|
||||||
|
icmd.Command("docker", "create", image),
|
||||||
|
fixtures.WithConfig(dir.Path()),
|
||||||
|
fixtures.WithTrust,
|
||||||
|
fixtures.WithNotary,
|
||||||
|
)
|
||||||
|
result.Assert(t, icmd.Expected{
|
||||||
|
Err: fmt.Sprintf("Tagging %s@sha", image[:len(image)-7]),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME(vdemeester) TestTrustedCreateFromBadTrustServer needs to be backport too (see https://github.com/moby/moby/pull/36515/files#diff-4b1e56bb77ac16f2ccf956fc24cf0a82L331)
|
|
@ -12,6 +12,8 @@ import (
|
||||||
"github.com/gotestyourself/gotestyourself/icmd"
|
"github.com/gotestyourself/gotestyourself/icmd"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const registryPrefix = "registry:5000"
|
||||||
|
|
||||||
func TestRunAttachedFromRemoteImageAndRemove(t *testing.T) {
|
func TestRunAttachedFromRemoteImageAndRemove(t *testing.T) {
|
||||||
image := createRemoteImage(t)
|
image := createRemoteImage(t)
|
||||||
|
|
||||||
|
@ -23,6 +25,26 @@ func TestRunAttachedFromRemoteImageAndRemove(t *testing.T) {
|
||||||
golden.Assert(t, result.Stderr(), "run-attached-from-remote-and-remove.golden")
|
golden.Assert(t, result.Stderr(), "run-attached-from-remote-and-remove.golden")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRunWithContentTrust(t *testing.T) {
|
||||||
|
dir := fixtures.SetupConfigFile(t)
|
||||||
|
defer dir.Remove()
|
||||||
|
image := fixtures.CreateMaskedTrustedRemoteImage(t, registryPrefix, "trust-run", "latest")
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
icmd.RunCommand("docker", "image", "rm", image).Assert(t, icmd.Success)
|
||||||
|
}()
|
||||||
|
|
||||||
|
result := icmd.RunCmd(
|
||||||
|
icmd.Command("docker", "run", image),
|
||||||
|
fixtures.WithConfig(dir.Path()),
|
||||||
|
fixtures.WithTrust,
|
||||||
|
fixtures.WithNotary,
|
||||||
|
)
|
||||||
|
result.Assert(t, icmd.Expected{
|
||||||
|
Err: fmt.Sprintf("Tagging %s@sha", image[:len(image)-7]),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: create this with registry API instead of engine API
|
// TODO: create this with registry API instead of engine API
|
||||||
func createRemoteImage(t *testing.T) string {
|
func createRemoteImage(t *testing.T) string {
|
||||||
image := "registry:5000/alpine:test-run-pulls"
|
image := "registry:5000/alpine:test-run-pulls"
|
||||||
|
|
|
@ -2,13 +2,12 @@ package image
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/cli/e2e/internal/fixtures"
|
"github.com/docker/cli/e2e/internal/fixtures"
|
||||||
|
"github.com/docker/cli/internal/test/output"
|
||||||
"github.com/gotestyourself/gotestyourself/fs"
|
"github.com/gotestyourself/gotestyourself/fs"
|
||||||
"github.com/gotestyourself/gotestyourself/icmd"
|
"github.com/gotestyourself/gotestyourself/icmd"
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBuildFromContextDirectoryWithTag(t *testing.T) {
|
func TestBuildFromContextDirectoryWithTag(t *testing.T) {
|
||||||
|
@ -28,16 +27,72 @@ func TestBuildFromContextDirectoryWithTag(t *testing.T) {
|
||||||
withWorkingDir(dir))
|
withWorkingDir(dir))
|
||||||
|
|
||||||
result.Assert(t, icmd.Expected{Err: icmd.None})
|
result.Assert(t, icmd.Expected{Err: icmd.None})
|
||||||
assertBuildOutput(t, result.Stdout(), map[int]lineCompare{
|
output.Assert(t, result.Stdout(), map[int]func(string) error{
|
||||||
0: prefix("Sending build context to Docker daemon"),
|
0: output.Prefix("Sending build context to Docker daemon"),
|
||||||
1: equals("Step 1/4 : FROM\tregistry:5000/alpine:3.6"),
|
1: output.Equals("Step 1/4 : FROM\tregistry:5000/alpine:3.6"),
|
||||||
3: equals("Step 2/4 : COPY\trun /usr/bin/run"),
|
3: output.Equals("Step 2/4 : COPY\trun /usr/bin/run"),
|
||||||
5: equals("Step 3/4 : RUN\t\trun"),
|
5: output.Equals("Step 3/4 : RUN\t\trun"),
|
||||||
7: equals("running"),
|
7: output.Equals("running"),
|
||||||
8: prefix("Removing intermediate container "),
|
8: output.Prefix("Removing intermediate container "),
|
||||||
10: equals("Step 4/4 : COPY\tdata /data"),
|
10: output.Equals("Step 4/4 : COPY\tdata /data"),
|
||||||
12: prefix("Successfully built "),
|
12: output.Prefix("Successfully built "),
|
||||||
13: equals("Successfully tagged myimage:latest"),
|
13: output.Equals("Successfully tagged myimage:latest"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrustedBuild(t *testing.T) {
|
||||||
|
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) {
|
||||||
|
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",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,38 +101,3 @@ func withWorkingDir(dir *fs.Dir) func(*icmd.Cmd) {
|
||||||
cmd.Dir = dir.Path()
|
cmd.Dir = dir.Path()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertBuildOutput(t *testing.T, actual string, expectedLines map[int]lineCompare) {
|
|
||||||
for i, line := range strings.Split(actual, "\n") {
|
|
||||||
cmp, ok := expectedLines[i]
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := cmp(line); err != nil {
|
|
||||||
t.Errorf("line %d: %s", i, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if t.Failed() {
|
|
||||||
t.Log(actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type lineCompare func(string) error
|
|
||||||
|
|
||||||
func prefix(expected string) func(string) error {
|
|
||||||
return func(actual string) error {
|
|
||||||
if strings.HasPrefix(actual, expected) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return errors.Errorf("expected %s to start with %s", actual, expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func equals(expected string) func(string) error {
|
|
||||||
return func(actual string) error {
|
|
||||||
if expected == actual {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return errors.Errorf("got %s, expected %s", actual, expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package image
|
package image
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/cli/e2e/internal/fixtures"
|
"github.com/docker/cli/e2e/internal/fixtures"
|
||||||
|
@ -12,37 +11,55 @@ import (
|
||||||
const registryPrefix = "registry:5000"
|
const registryPrefix = "registry:5000"
|
||||||
|
|
||||||
func TestPullWithContentTrust(t *testing.T) {
|
func TestPullWithContentTrust(t *testing.T) {
|
||||||
image := createMaskedTrustedRemoteImage(t, "trust", "latest")
|
dir := fixtures.SetupConfigFile(t)
|
||||||
|
defer dir.Remove()
|
||||||
|
image := fixtures.CreateMaskedTrustedRemoteImage(t, registryPrefix, "trust-pull", "latest")
|
||||||
|
defer func() {
|
||||||
|
icmd.RunCommand("docker", "image", "rm", image).Assert(t, icmd.Success)
|
||||||
|
}()
|
||||||
|
|
||||||
result := icmd.RunCmd(icmd.Command("docker", "pull", image), fixtures.WithTrust, fixtures.WithNotary)
|
result := icmd.RunCmd(icmd.Command("docker", "pull", image),
|
||||||
|
fixtures.WithConfig(dir.Path()),
|
||||||
|
fixtures.WithTrust,
|
||||||
|
fixtures.WithNotary,
|
||||||
|
)
|
||||||
result.Assert(t, icmd.Success)
|
result.Assert(t, icmd.Success)
|
||||||
golden.Assert(t, result.Stderr(), "pull-with-content-trust-err.golden")
|
golden.Assert(t, result.Stderr(), "pull-with-content-trust-err.golden")
|
||||||
golden.Assert(t, result.Stdout(), "pull-with-content-trust.golden")
|
golden.Assert(t, result.Stdout(), "pull-with-content-trust.golden")
|
||||||
}
|
}
|
||||||
|
|
||||||
// createMaskedTrustedRemoteImage creates a remote image that is signed with
|
func TestPullWithContentTrustUsesCacheWhenNotaryUnavailable(t *testing.T) {
|
||||||
// content trust, then pushes a different untrusted image at the same tag.
|
dir := fixtures.SetupConfigFile(t)
|
||||||
func createMaskedTrustedRemoteImage(t *testing.T, repo, tag string) string {
|
defer dir.Remove()
|
||||||
image := createTrustedRemoteImage(t, repo, tag)
|
image := fixtures.CreateMaskedTrustedRemoteImage(t, registryPrefix, "trust-pull-unreachable", "latest")
|
||||||
createNamedUnsignedImageFromBusyBox(t, image)
|
defer func() {
|
||||||
return image
|
icmd.RunCommand("docker", "image", "rm", image).Assert(t, icmd.Success)
|
||||||
}
|
}()
|
||||||
|
result := icmd.RunCmd(icmd.Command("docker", "pull", image),
|
||||||
|
fixtures.WithConfig(dir.Path()),
|
||||||
|
fixtures.WithTrust,
|
||||||
|
fixtures.WithNotaryServer("https://invalidnotaryserver"),
|
||||||
|
)
|
||||||
|
result.Assert(t, icmd.Expected{
|
||||||
|
ExitCode: 1,
|
||||||
|
Err: "error contacting notary server",
|
||||||
|
})
|
||||||
|
|
||||||
func createTrustedRemoteImage(t *testing.T, repo, tag string) string {
|
// Do valid trusted pull to warm cache
|
||||||
image := fmt.Sprintf("%s/%s:%s", registryPrefix, repo, tag)
|
result = icmd.RunCmd(icmd.Command("docker", "pull", image),
|
||||||
icmd.RunCommand("docker", "pull", fixtures.AlpineImage).Assert(t, icmd.Success)
|
fixtures.WithConfig(dir.Path()),
|
||||||
icmd.RunCommand("docker", "tag", fixtures.AlpineImage, image).Assert(t, icmd.Success)
|
fixtures.WithTrust,
|
||||||
result := icmd.RunCmd(
|
fixtures.WithNotary,
|
||||||
icmd.Command("docker", "push", image),
|
)
|
||||||
fixtures.WithPassphrase("root_password", "repo_password"), fixtures.WithTrust, fixtures.WithNotary)
|
result.Assert(t, icmd.Success)
|
||||||
|
result = icmd.RunCommand("docker", "rmi", image)
|
||||||
result.Assert(t, icmd.Success)
|
result.Assert(t, icmd.Success)
|
||||||
icmd.RunCommand("docker", "rmi", image).Assert(t, icmd.Success)
|
|
||||||
return image
|
|
||||||
}
|
|
||||||
|
|
||||||
func createNamedUnsignedImageFromBusyBox(t *testing.T, image string) {
|
// Try pull again with invalid notary server, should use cache
|
||||||
icmd.RunCommand("docker", "pull", fixtures.BusyboxImage).Assert(t, icmd.Success)
|
result = icmd.RunCmd(icmd.Command("docker", "pull", image),
|
||||||
icmd.RunCommand("docker", "tag", fixtures.BusyboxImage, image).Assert(t, icmd.Success)
|
fixtures.WithConfig(dir.Path()),
|
||||||
icmd.RunCommand("docker", "push", image).Assert(t, icmd.Success)
|
fixtures.WithTrust,
|
||||||
icmd.RunCommand("docker", "rmi", image).Assert(t, icmd.Success)
|
fixtures.WithNotaryServer("https://invalidnotaryserver"),
|
||||||
|
)
|
||||||
|
result.Assert(t, icmd.Success)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,378 @@
|
||||||
|
package image
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/cli/e2e/internal/fixtures"
|
||||||
|
"github.com/docker/cli/internal/test/output"
|
||||||
|
"github.com/gotestyourself/gotestyourself/assert"
|
||||||
|
"github.com/gotestyourself/gotestyourself/fs"
|
||||||
|
"github.com/gotestyourself/gotestyourself/golden"
|
||||||
|
"github.com/gotestyourself/gotestyourself/icmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
notary = "/usr/local/bin/notary"
|
||||||
|
|
||||||
|
pubkey1 = "./testdata/notary/delgkey1.crt"
|
||||||
|
privkey1 = "./testdata/notary/delgkey1.key"
|
||||||
|
pubkey2 = "./testdata/notary/delgkey2.crt"
|
||||||
|
privkey2 = "./testdata/notary/delgkey2.key"
|
||||||
|
pubkey3 = "./testdata/notary/delgkey3.crt"
|
||||||
|
privkey3 = "./testdata/notary/delgkey3.key"
|
||||||
|
pubkey4 = "./testdata/notary/delgkey4.crt"
|
||||||
|
privkey4 = "./testdata/notary/delgkey4.key"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPushWithContentTrust(t *testing.T) {
|
||||||
|
dir := fixtures.SetupConfigFile(t)
|
||||||
|
defer dir.Remove()
|
||||||
|
image := createImage(t, registryPrefix, "trust-push", "latest")
|
||||||
|
|
||||||
|
result := icmd.RunCmd(icmd.Command("docker", "push", image),
|
||||||
|
fixtures.WithConfig(dir.Path()),
|
||||||
|
fixtures.WithTrust,
|
||||||
|
fixtures.WithNotary,
|
||||||
|
fixtures.WithPassphrase("foo", "bar"),
|
||||||
|
)
|
||||||
|
result.Assert(t, icmd.Success)
|
||||||
|
golden.Assert(t, result.Stderr(), "push-with-content-trust-err.golden")
|
||||||
|
output.Assert(t, result.Stdout(), map[int]func(string) error{
|
||||||
|
0: output.Equals("The push refers to repository [registry:5000/trust-push]"),
|
||||||
|
1: output.Equals("5bef08742407: Preparing"),
|
||||||
|
3: output.Equals("latest: digest: sha256:641b95ddb2ea9dc2af1a0113b6b348ebc20872ba615204fbe12148e98fd6f23d size: 528"),
|
||||||
|
4: output.Equals("Signing and pushing trust metadata"),
|
||||||
|
5: output.Equals(`Finished initializing "registry:5000/trust-push"`),
|
||||||
|
6: output.Equals("Successfully signed registry:5000/trust-push:latest"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPushWithContentTrustUnreachableServer(t *testing.T) {
|
||||||
|
dir := fixtures.SetupConfigFile(t)
|
||||||
|
defer dir.Remove()
|
||||||
|
image := createImage(t, registryPrefix, "trust-push-unreachable", "latest")
|
||||||
|
|
||||||
|
result := icmd.RunCmd(icmd.Command("docker", "push", image),
|
||||||
|
fixtures.WithConfig(dir.Path()),
|
||||||
|
fixtures.WithTrust,
|
||||||
|
fixtures.WithNotaryServer("https://invalidnotaryserver"),
|
||||||
|
)
|
||||||
|
result.Assert(t, icmd.Expected{
|
||||||
|
ExitCode: 1,
|
||||||
|
Err: "error contacting notary server",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPushWithContentTrustExistingTag(t *testing.T) {
|
||||||
|
dir := fixtures.SetupConfigFile(t)
|
||||||
|
defer dir.Remove()
|
||||||
|
image := createImage(t, registryPrefix, "trust-push-existing", "latest")
|
||||||
|
|
||||||
|
result := icmd.RunCmd(icmd.Command("docker", "push", image))
|
||||||
|
result.Assert(t, icmd.Success)
|
||||||
|
|
||||||
|
result = icmd.RunCmd(icmd.Command("docker", "push", image),
|
||||||
|
fixtures.WithConfig(dir.Path()),
|
||||||
|
fixtures.WithTrust,
|
||||||
|
fixtures.WithNotary,
|
||||||
|
fixtures.WithPassphrase("foo", "bar"),
|
||||||
|
)
|
||||||
|
result.Assert(t, icmd.Expected{
|
||||||
|
Out: "Signing and pushing trust metadata",
|
||||||
|
})
|
||||||
|
|
||||||
|
// Re-push
|
||||||
|
result = icmd.RunCmd(icmd.Command("docker", "push", image),
|
||||||
|
fixtures.WithConfig(dir.Path()),
|
||||||
|
fixtures.WithTrust,
|
||||||
|
fixtures.WithNotary,
|
||||||
|
fixtures.WithPassphrase("foo", "bar"),
|
||||||
|
)
|
||||||
|
result.Assert(t, icmd.Expected{
|
||||||
|
Out: "Signing and pushing trust metadata",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPushWithContentTrustReleasesDelegationOnly(t *testing.T) {
|
||||||
|
role := "targets/releases"
|
||||||
|
|
||||||
|
dir := fixtures.SetupConfigFile(t)
|
||||||
|
defer dir.Remove()
|
||||||
|
copyPrivateKey(t, dir.Join("trust", "private"), privkey1)
|
||||||
|
notaryDir := setupNotaryConfig(t, dir)
|
||||||
|
defer notaryDir.Remove()
|
||||||
|
homeDir := fs.NewDir(t, "push_test_home")
|
||||||
|
defer notaryDir.Remove()
|
||||||
|
|
||||||
|
baseRef := fmt.Sprintf("%s/%s", registryPrefix, "trust-push-releases-delegation")
|
||||||
|
targetRef := fmt.Sprintf("%s:%s", baseRef, "latest")
|
||||||
|
|
||||||
|
// Init repository
|
||||||
|
notaryInit(t, notaryDir, homeDir, baseRef)
|
||||||
|
// Add delegation key (public key)
|
||||||
|
notaryAddDelegation(t, notaryDir, homeDir, baseRef, role, pubkey1)
|
||||||
|
// Publish it
|
||||||
|
notaryPublish(t, notaryDir, homeDir, baseRef)
|
||||||
|
// Import private key
|
||||||
|
notaryImportPrivateKey(t, notaryDir, homeDir, baseRef, role, privkey1)
|
||||||
|
|
||||||
|
// Tag & push with content trust
|
||||||
|
icmd.RunCommand("docker", "pull", fixtures.AlpineImage).Assert(t, icmd.Success)
|
||||||
|
icmd.RunCommand("docker", "tag", fixtures.AlpineImage, targetRef).Assert(t, icmd.Success)
|
||||||
|
result := icmd.RunCmd(icmd.Command("docker", "push", targetRef),
|
||||||
|
fixtures.WithConfig(dir.Path()),
|
||||||
|
fixtures.WithTrust,
|
||||||
|
fixtures.WithNotary,
|
||||||
|
fixtures.WithPassphrase("foo", "foo"),
|
||||||
|
)
|
||||||
|
result.Assert(t, icmd.Expected{
|
||||||
|
Out: "Signing and pushing trust metadata",
|
||||||
|
})
|
||||||
|
|
||||||
|
targetsInRole := notaryListTargetsInRole(t, notaryDir, homeDir, baseRef, role)
|
||||||
|
assert.Assert(t, targetsInRole["latest"] == role, "%v", targetsInRole)
|
||||||
|
targetsInRole = notaryListTargetsInRole(t, notaryDir, homeDir, baseRef, "targets")
|
||||||
|
assert.Assert(t, targetsInRole["latest"] != "targets", "%v", targetsInRole)
|
||||||
|
|
||||||
|
result = icmd.RunCmd(icmd.Command("docker", "pull", targetRef),
|
||||||
|
fixtures.WithConfig(dir.Path()),
|
||||||
|
fixtures.WithTrust,
|
||||||
|
fixtures.WithNotary,
|
||||||
|
)
|
||||||
|
result.Assert(t, icmd.Success)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPushWithContentTrustSignsAllFirstLevelRolesWeHaveKeysFor(t *testing.T) {
|
||||||
|
dir := fixtures.SetupConfigFile(t)
|
||||||
|
defer dir.Remove()
|
||||||
|
copyPrivateKey(t, dir.Join("trust", "private"), privkey1)
|
||||||
|
copyPrivateKey(t, dir.Join("trust", "private"), privkey2)
|
||||||
|
copyPrivateKey(t, dir.Join("trust", "private"), privkey3)
|
||||||
|
notaryDir := setupNotaryConfig(t, dir)
|
||||||
|
defer notaryDir.Remove()
|
||||||
|
homeDir := fs.NewDir(t, "push_test_home")
|
||||||
|
defer notaryDir.Remove()
|
||||||
|
|
||||||
|
baseRef := fmt.Sprintf("%s/%s", registryPrefix, "trust-push-releases-first-roles")
|
||||||
|
targetRef := fmt.Sprintf("%s:%s", baseRef, "latest")
|
||||||
|
|
||||||
|
// Init repository
|
||||||
|
notaryInit(t, notaryDir, homeDir, baseRef)
|
||||||
|
// Add delegation key (public key)
|
||||||
|
notaryAddDelegation(t, notaryDir, homeDir, baseRef, "targets/role1", pubkey1)
|
||||||
|
notaryAddDelegation(t, notaryDir, homeDir, baseRef, "targets/role2", pubkey2)
|
||||||
|
notaryAddDelegation(t, notaryDir, homeDir, baseRef, "targets/role3", pubkey3)
|
||||||
|
notaryAddDelegation(t, notaryDir, homeDir, baseRef, "targets/role1/subrole", pubkey3)
|
||||||
|
// Import private key
|
||||||
|
notaryImportPrivateKey(t, notaryDir, homeDir, baseRef, "targets/role1", privkey1)
|
||||||
|
notaryImportPrivateKey(t, notaryDir, homeDir, baseRef, "targets/role2", privkey2)
|
||||||
|
notaryImportPrivateKey(t, notaryDir, homeDir, baseRef, "targets/role1/subrole", privkey3)
|
||||||
|
// Publish it
|
||||||
|
notaryPublish(t, notaryDir, homeDir, baseRef)
|
||||||
|
|
||||||
|
// Tag & push with content trust
|
||||||
|
icmd.RunCommand("docker", "pull", fixtures.AlpineImage).Assert(t, icmd.Success)
|
||||||
|
icmd.RunCommand("docker", "tag", fixtures.AlpineImage, targetRef).Assert(t, icmd.Success)
|
||||||
|
result := icmd.RunCmd(icmd.Command("docker", "push", targetRef),
|
||||||
|
fixtures.WithConfig(dir.Path()),
|
||||||
|
fixtures.WithTrust,
|
||||||
|
fixtures.WithNotary,
|
||||||
|
fixtures.WithPassphrase("foo", "foo"),
|
||||||
|
)
|
||||||
|
result.Assert(t, icmd.Expected{
|
||||||
|
Out: "Signing and pushing trust metadata",
|
||||||
|
})
|
||||||
|
|
||||||
|
// check to make sure that the target has been added to targets/role1 and targets/role2, and
|
||||||
|
// not targets (because there are delegations) or targets/role3 (due to missing key) or
|
||||||
|
// targets/role1/subrole (due to it being a second level delegation)
|
||||||
|
targetsInRole := notaryListTargetsInRole(t, notaryDir, homeDir, baseRef, "targets/role1")
|
||||||
|
assert.Assert(t, targetsInRole["latest"] == "targets/role1", "%v", targetsInRole)
|
||||||
|
targetsInRole = notaryListTargetsInRole(t, notaryDir, homeDir, baseRef, "targets/role2")
|
||||||
|
assert.Assert(t, targetsInRole["latest"] == "targets/role2", "%v", targetsInRole)
|
||||||
|
targetsInRole = notaryListTargetsInRole(t, notaryDir, homeDir, baseRef, "targets")
|
||||||
|
assert.Assert(t, targetsInRole["latest"] != "targets", "%v", targetsInRole)
|
||||||
|
|
||||||
|
assert.NilError(t, os.RemoveAll(filepath.Join(dir.Join("trust"))))
|
||||||
|
// Try to pull, should fail because non of these are the release role
|
||||||
|
// FIXME(vdemeester) should be unit test
|
||||||
|
result = icmd.RunCmd(icmd.Command("docker", "pull", targetRef),
|
||||||
|
fixtures.WithConfig(dir.Path()),
|
||||||
|
fixtures.WithTrust,
|
||||||
|
fixtures.WithNotary,
|
||||||
|
)
|
||||||
|
result.Assert(t, icmd.Expected{
|
||||||
|
ExitCode: 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPushWithContentTrustSignsForRolesWithKeysAndValidPaths(t *testing.T) {
|
||||||
|
dir := fixtures.SetupConfigFile(t)
|
||||||
|
defer dir.Remove()
|
||||||
|
copyPrivateKey(t, dir.Join("trust", "private"), privkey1)
|
||||||
|
copyPrivateKey(t, dir.Join("trust", "private"), privkey2)
|
||||||
|
copyPrivateKey(t, dir.Join("trust", "private"), privkey3)
|
||||||
|
copyPrivateKey(t, dir.Join("trust", "private"), privkey4)
|
||||||
|
notaryDir := setupNotaryConfig(t, dir)
|
||||||
|
defer notaryDir.Remove()
|
||||||
|
homeDir := fs.NewDir(t, "push_test_home")
|
||||||
|
defer notaryDir.Remove()
|
||||||
|
|
||||||
|
baseRef := fmt.Sprintf("%s/%s", registryPrefix, "trust-push-releases-keys-valid-paths")
|
||||||
|
targetRef := fmt.Sprintf("%s:%s", baseRef, "latest")
|
||||||
|
|
||||||
|
// Init repository
|
||||||
|
notaryInit(t, notaryDir, homeDir, baseRef)
|
||||||
|
// Add delegation key (public key)
|
||||||
|
notaryAddDelegation(t, notaryDir, homeDir, baseRef, "targets/role1", pubkey1, "l", "z")
|
||||||
|
notaryAddDelegation(t, notaryDir, homeDir, baseRef, "targets/role2", pubkey2, "x", "y")
|
||||||
|
notaryAddDelegation(t, notaryDir, homeDir, baseRef, "targets/role3", pubkey3, "latest")
|
||||||
|
notaryAddDelegation(t, notaryDir, homeDir, baseRef, "targets/role4", pubkey4, "latest")
|
||||||
|
// Import private keys (except 3rd key)
|
||||||
|
notaryImportPrivateKey(t, notaryDir, homeDir, baseRef, "targets/role1", privkey1)
|
||||||
|
notaryImportPrivateKey(t, notaryDir, homeDir, baseRef, "targets/role2", privkey2)
|
||||||
|
notaryImportPrivateKey(t, notaryDir, homeDir, baseRef, "targets/role4", privkey4)
|
||||||
|
// Publish it
|
||||||
|
notaryPublish(t, notaryDir, homeDir, baseRef)
|
||||||
|
|
||||||
|
// Tag & push with content trust
|
||||||
|
icmd.RunCommand("docker", "pull", fixtures.AlpineImage).Assert(t, icmd.Success)
|
||||||
|
icmd.RunCommand("docker", "tag", fixtures.AlpineImage, targetRef).Assert(t, icmd.Success)
|
||||||
|
result := icmd.RunCmd(icmd.Command("docker", "push", targetRef),
|
||||||
|
fixtures.WithConfig(dir.Path()),
|
||||||
|
fixtures.WithTrust,
|
||||||
|
fixtures.WithNotary,
|
||||||
|
fixtures.WithPassphrase("foo", "foo"),
|
||||||
|
)
|
||||||
|
result.Assert(t, icmd.Expected{
|
||||||
|
Out: "Signing and pushing trust metadata",
|
||||||
|
})
|
||||||
|
|
||||||
|
// check to make sure that the target has been added to targets/role1 and targets/role4, and
|
||||||
|
// not targets (because there are delegations) or targets/role2 (due to path restrictions) or
|
||||||
|
// targets/role3 (due to missing key)
|
||||||
|
targetsInRole := notaryListTargetsInRole(t, notaryDir, homeDir, baseRef, "targets/role1")
|
||||||
|
assert.Assert(t, targetsInRole["latest"] == "targets/role1", "%v", targetsInRole)
|
||||||
|
targetsInRole = notaryListTargetsInRole(t, notaryDir, homeDir, baseRef, "targets/role4")
|
||||||
|
assert.Assert(t, targetsInRole["latest"] == "targets/role4", "%v", targetsInRole)
|
||||||
|
targetsInRole = notaryListTargetsInRole(t, notaryDir, homeDir, baseRef, "targets")
|
||||||
|
assert.Assert(t, targetsInRole["latest"] != "targets", "%v", targetsInRole)
|
||||||
|
|
||||||
|
assert.NilError(t, os.RemoveAll(filepath.Join(dir.Join("trust"))))
|
||||||
|
// Try to pull, should fail because non of these are the release role
|
||||||
|
// FIXME(vdemeester) should be unit test
|
||||||
|
result = icmd.RunCmd(icmd.Command("docker", "pull", targetRef),
|
||||||
|
fixtures.WithConfig(dir.Path()),
|
||||||
|
fixtures.WithTrust,
|
||||||
|
fixtures.WithNotary,
|
||||||
|
)
|
||||||
|
result.Assert(t, icmd.Expected{
|
||||||
|
ExitCode: 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func createImage(t *testing.T, registryPrefix, repo, tag string) string {
|
||||||
|
image := fmt.Sprintf("%s/%s:%s", registryPrefix, repo, tag)
|
||||||
|
icmd.RunCommand("docker", "pull", fixtures.AlpineImage).Assert(t, icmd.Success)
|
||||||
|
icmd.RunCommand("docker", "tag", fixtures.AlpineImage, image).Assert(t, icmd.Success)
|
||||||
|
return image
|
||||||
|
}
|
||||||
|
|
||||||
|
func withNotaryPassphrase(pwd string) func(*icmd.Cmd) {
|
||||||
|
return func(c *icmd.Cmd) {
|
||||||
|
c.Env = append(c.Env, []string{
|
||||||
|
fmt.Sprintf("NOTARY_ROOT_PASSPHRASE=%s", pwd),
|
||||||
|
fmt.Sprintf("NOTARY_TARGETS_PASSPHRASE=%s", pwd),
|
||||||
|
fmt.Sprintf("NOTARY_SNAPSHOT_PASSPHRASE=%s", pwd),
|
||||||
|
fmt.Sprintf("NOTARY_DELEGATION_PASSPHRASE=%s", pwd),
|
||||||
|
}...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func notaryImportPrivateKey(t *testing.T, notaryDir, homeDir *fs.Dir, baseRef, role, privkey string) {
|
||||||
|
icmd.RunCmd(
|
||||||
|
icmd.Command(notary, "-c", notaryDir.Join("client-config.json"), "key", "import", privkey, "-g", baseRef, "-r", role),
|
||||||
|
withNotaryPassphrase("foo"),
|
||||||
|
fixtures.WithHome(homeDir.Path()),
|
||||||
|
).Assert(t, icmd.Success)
|
||||||
|
}
|
||||||
|
|
||||||
|
func notaryPublish(t *testing.T, notaryDir, homeDir *fs.Dir, baseRef string) {
|
||||||
|
icmd.RunCmd(
|
||||||
|
icmd.Command(notary, "-c", notaryDir.Join("client-config.json"), "publish", baseRef),
|
||||||
|
withNotaryPassphrase("foo"),
|
||||||
|
fixtures.WithHome(homeDir.Path()),
|
||||||
|
).Assert(t, icmd.Success)
|
||||||
|
}
|
||||||
|
|
||||||
|
func notaryAddDelegation(t *testing.T, notaryDir, homeDir *fs.Dir, baseRef, role, pubkey string, paths ...string) {
|
||||||
|
pathsArg := "--all-paths"
|
||||||
|
if len(paths) > 0 {
|
||||||
|
pathsArg = "--paths=" + strings.Join(paths, ",")
|
||||||
|
}
|
||||||
|
icmd.RunCmd(
|
||||||
|
icmd.Command(notary, "-c", notaryDir.Join("client-config.json"), "delegation", "add", baseRef, role, pubkey, pathsArg),
|
||||||
|
withNotaryPassphrase("foo"),
|
||||||
|
fixtures.WithHome(homeDir.Path()),
|
||||||
|
).Assert(t, icmd.Success)
|
||||||
|
}
|
||||||
|
|
||||||
|
func notaryInit(t *testing.T, notaryDir, homeDir *fs.Dir, baseRef string) {
|
||||||
|
icmd.RunCmd(
|
||||||
|
icmd.Command(notary, "-c", notaryDir.Join("client-config.json"), "init", baseRef),
|
||||||
|
withNotaryPassphrase("foo"),
|
||||||
|
fixtures.WithHome(homeDir.Path()),
|
||||||
|
).Assert(t, icmd.Success)
|
||||||
|
}
|
||||||
|
|
||||||
|
func notaryListTargetsInRole(t *testing.T, notaryDir, homeDir *fs.Dir, baseRef, role string) map[string]string {
|
||||||
|
result := icmd.RunCmd(
|
||||||
|
icmd.Command(notary, "-c", notaryDir.Join("client-config.json"), "list", baseRef, "-r", role),
|
||||||
|
fixtures.WithHome(homeDir.Path()),
|
||||||
|
)
|
||||||
|
out := result.Combined()
|
||||||
|
|
||||||
|
// should look something like:
|
||||||
|
// NAME DIGEST SIZE (BYTES) ROLE
|
||||||
|
// ------------------------------------------------------------------------------------------------------
|
||||||
|
// latest 24a36bbc059b1345b7e8be0df20f1b23caa3602e85d42fff7ecd9d0bd255de56 1377 targets
|
||||||
|
|
||||||
|
targets := make(map[string]string)
|
||||||
|
|
||||||
|
// no target
|
||||||
|
lines := strings.Split(strings.TrimSpace(out), "\n")
|
||||||
|
if len(lines) == 1 && strings.Contains(out, "No targets present in this repository.") {
|
||||||
|
return targets
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise, there is at least one target
|
||||||
|
assert.Assert(t, len(lines) >= 3, "output is %s", out)
|
||||||
|
|
||||||
|
for _, line := range lines[2:] {
|
||||||
|
tokens := strings.Fields(line)
|
||||||
|
assert.Assert(t, len(tokens) == 4)
|
||||||
|
targets[tokens[0]] = tokens[3]
|
||||||
|
}
|
||||||
|
|
||||||
|
return targets
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupNotaryConfig(t *testing.T, dockerConfigDir fs.Dir) *fs.Dir {
|
||||||
|
return fs.NewDir(t, "notary_test", fs.WithMode(0700),
|
||||||
|
fs.WithFile("client-config.json", fmt.Sprintf(`
|
||||||
|
{
|
||||||
|
"trust_dir": "%s",
|
||||||
|
"remote_server": {
|
||||||
|
"url": "%s"
|
||||||
|
}
|
||||||
|
}`, dockerConfigDir.Join("trust"), fixtures.NotaryURL)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyPrivateKey(t *testing.T, dir, source string) {
|
||||||
|
icmd.RunCommand("/bin/cp", source, dir).Assert(t, icmd.Success)
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDhTCCAm2gAwIBAgIJAP2EcMN2UXPcMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNV
|
||||||
|
BAYTAlVTMQswCQYDVQQIEwJDQTEVMBMGA1UEBxMMU2FuRnJhbmNpc2NvMQ8wDQYD
|
||||||
|
VQQKEwZEb2NrZXIxEzARBgNVBAMTCmRlbGVnYXRpb24wHhcNMTYwOTI4MTc0ODQ4
|
||||||
|
WhcNMjYwNjI4MTc0ODQ4WjBXMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFTAT
|
||||||
|
BgNVBAcTDFNhbkZyYW5jaXNjbzEPMA0GA1UEChMGRG9ja2VyMRMwEQYDVQQDEwpk
|
||||||
|
ZWxlZ2F0aW9uMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvgewhaYs
|
||||||
|
Ke5s2AM7xxKrT4A6n7hW17qSnBjonCcPcwTFmYqIOdxWjYITgJuHrTwB4ZhBqWS7
|
||||||
|
tTsUUu6hWLMeB7Uo5/GEQAAZspKkT9G/rNKF9lbWK9PPhGGkeR01c/Q932m92Hsn
|
||||||
|
fCQ0Pp/OzD3nVTh0v9HKk+PObNMOCcqG87eYs4ylPRxs0RrE/rP+bEGssKQSbeCZ
|
||||||
|
wazDnO+kiatVgKQZ2CK23iFdRE1z2rzqVDeaFWdvBqrRdWnkOZClhlLgEQ5nK2yV
|
||||||
|
B6tSqOiI3MmHyHzIkGOQJp2/s7Pe0ckEkzsjTsJW8oKHlBBl6pRxHIKzNN4VFbeB
|
||||||
|
vvYvrogrDrC/owIDAQABo1QwUjAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIF
|
||||||
|
oDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUFoHfukRa6qGk1ncON64Z
|
||||||
|
ASKlZdkwDQYJKoZIhvcNAQELBQADggEBAEq9Adpd03CPmpbRtTAJGAkjjLFr60sV
|
||||||
|
2r+/l/m9R31ZCN9ymM9nxToQ8zfMdeAh/nnPcErziil2gDVqXueCNDkRj09tmDIE
|
||||||
|
Q1Oc92uyNZNgcECow77cKZCTZSTku+qsJrYaykH5vSnia8ltcKj8inJedIcpBR+p
|
||||||
|
608HEQvF0Eg5eaLPJwH48BCb0Gqdri1dJgrNnqptz7MDr8M+u7tHVulbAd3YxLlq
|
||||||
|
JH1W2bkVUx6esbn/MUE5HL5iTuOYREEINvBSmLdmmFkampmCnCB/bDEyJeL9bAkt
|
||||||
|
ZPIi0UNSnqFKLSP1Vf8AGLXt6iO7+1OGvtsDXEEYdXVOMsSXZtUuT7A=
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,27 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpAIBAAKCAQEAvgewhaYsKe5s2AM7xxKrT4A6n7hW17qSnBjonCcPcwTFmYqI
|
||||||
|
OdxWjYITgJuHrTwB4ZhBqWS7tTsUUu6hWLMeB7Uo5/GEQAAZspKkT9G/rNKF9lbW
|
||||||
|
K9PPhGGkeR01c/Q932m92HsnfCQ0Pp/OzD3nVTh0v9HKk+PObNMOCcqG87eYs4yl
|
||||||
|
PRxs0RrE/rP+bEGssKQSbeCZwazDnO+kiatVgKQZ2CK23iFdRE1z2rzqVDeaFWdv
|
||||||
|
BqrRdWnkOZClhlLgEQ5nK2yVB6tSqOiI3MmHyHzIkGOQJp2/s7Pe0ckEkzsjTsJW
|
||||||
|
8oKHlBBl6pRxHIKzNN4VFbeBvvYvrogrDrC/owIDAQABAoIBAB/o8KZwsgfUhqh7
|
||||||
|
WoViSCwQb0e0z7hoFwhpUl4uXPTGf1v6HEgDDPG0PwwgkdbwNaypQZVtWevj4NTQ
|
||||||
|
R326jjdjH1xbfQa2PZpz722L3jDqJR6plEtFxRoIv3KrCffPsrgabIu2mnnJJpDB
|
||||||
|
ixtW5cq0sT4ov2i4H0i85CWWwbSY/G/MHsvCuK9PhoCj9uToVqrf1KrAESE5q4fh
|
||||||
|
mPSYUL99KVnj7SZkUz+79rc8sLLPVks3szZACMlm1n05ZTj/d6Nd2ZZUO45DllIj
|
||||||
|
1XJghfWmnChrB/P/KYXgQ3Y9BofIAw1ra2y3wOZeqRFNsbmojcGldfdtN/iQzhEj
|
||||||
|
uk4ThokCgYEA9FTmv36N8qSPWuqX/KzkixDQ8WrDGohcB54kK98Wx4ijXx3i38SY
|
||||||
|
tFjO8YUS9GVo1+UgmRjZbzVX7xeum6+TdBBwOjNOxEQ4tzwiQBWDdGpli8BccdJ2
|
||||||
|
OOIVxSslWhiUWfpYloXVetrR88iHbT882g795pbonDaJdXSLnij4UW8CgYEAxxrr
|
||||||
|
QFpsmOEZvI/yPSOGdG7A1RIsCeH+cEOf4cKghs7+aCtAHlIweztNOrqirl3oKI1r
|
||||||
|
I0zQl46WsaW8S/y99v9lmmnZbWwqLa4vIu0NWs0zaZdzKZw3xljMhgp4Ge69hHa2
|
||||||
|
utCtAxcX+7q/yLlHoTiYwKdxX54iLkheCB8csw0CgYEAleEG820kkjXUIodJ2JwO
|
||||||
|
Tihwo8dEC6CeI6YktizRgnEVFqH0rCOjMO5Rc+KX8AfNOrK5PnD54LguSuKSH7qi
|
||||||
|
j04OKgWTSd43lF90+y63RtCFnibQDpp2HwrBJAQFk7EEP/XMJfnPLN/SbuMSADgM
|
||||||
|
kg8kPTFRW5Iw3DYz9z9WpE0CgYAkn6/8Q2XMbUOFqti9JEa8Lg8sYk5VdwuNbPMA
|
||||||
|
3QMYKQUk9ieyLB4c3Nik3+XCuyVUKEc31A5egmz3umu7cn8i6vGuiJ/k/8t2YZ7s
|
||||||
|
Bry5Ihu95Yzab5DW3Eiqs0xKQN79ebS9AluAwQO5Wy2h52rknfuDHIm/M+BHsSoS
|
||||||
|
xl5KFQKBgQCokCsYuX1z2GojHw369/R2aX3ovCGuHqy4k7fWxUrpHTHvth2+qNPr
|
||||||
|
84qLJ9rLWoZE5sUiZ5YdwCgW877EdfkT+v4aaBX79ixso5VdqgJ/PdnoNntah/Vq
|
||||||
|
njQiW1skn6/P5V/eyimN2n0VsyBr/zMDEtYTRP/Tb1zi/njFLQkZEA==
|
||||||
|
-----END RSA PRIVATE KEY-----
|
|
@ -0,0 +1,21 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDhTCCAm2gAwIBAgIJAIq8naKlYAQfMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNV
|
||||||
|
BAYTAlVTMQswCQYDVQQIEwJDQTEVMBMGA1UEBxMMU2FuRnJhbmNpc2NvMQ8wDQYD
|
||||||
|
VQQKEwZEb2NrZXIxEzARBgNVBAMTCmRlbGVnYXRpb24wHhcNMTYwOTI4MTc0ODQ4
|
||||||
|
WhcNMjYwNjI4MTc0ODQ4WjBXMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFTAT
|
||||||
|
BgNVBAcTDFNhbkZyYW5jaXNjbzEPMA0GA1UEChMGRG9ja2VyMRMwEQYDVQQDEwpk
|
||||||
|
ZWxlZ2F0aW9uMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyY2EWYTW
|
||||||
|
5VHipw08t675upmD6a+akiuZ1z+XpuOxZCgjZ0aHfoOe8wGKg3Ohz7UCBdD5Mob/
|
||||||
|
L/qvRlsCaqPHGZKIyyX1HDO4mpuQQFBhYxt+ZAO3AaawEUOw2rwwMDEjLnDDTSZM
|
||||||
|
z8jxCMvsJjBDqgb8g3z+AmjducQ/OH6llldgHIBY8ioRbROCL2PGgqywWq2fThav
|
||||||
|
c70YMxtKviBGDNCouYeQ8JMK/PuLwPNDXNQAagFHVARXiUv/ILHk7ImYnSGJUcuk
|
||||||
|
JTUGN2MBnpY0eakg7i+4za8sjjqOdn+2I6aVzlGJDSiRP72nkg/cE4BqMl9FrMwK
|
||||||
|
9iS8xa9yMDLUvwIDAQABo1QwUjAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIF
|
||||||
|
oDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUvQzzFmh3Sv3HcdExY3wx
|
||||||
|
/1u6JLAwDQYJKoZIhvcNAQELBQADggEBAJcmDme2Xj/HPUPwaN/EyCmjhY73EiHO
|
||||||
|
x6Pm16tscg5JGn5A+u3CZ1DmxUYl8Hp6MaW/sWzdtL0oKJg76pynadCWh5EacFR8
|
||||||
|
u+2GV/IcN9mSX6JQzvrqbjSqo5/FehqBD+W5h3euwwApWA3STAadYeyEfmdOA3SQ
|
||||||
|
W1vzrA1y7i8qgTqeJ7UX1sEAXlIhBK2zPYaMB+en+ZOiPyNxJYj6IDdGdD2paC9L
|
||||||
|
6H9wKC+GAUTSdCWp89HP7ETSXEGr94AXkrwU+qNsiN+OyK8ke0EMngEPh5IQoplw
|
||||||
|
/7zEZCth3oKxvR1/4S5LmTVaHI2ZlbU4q9bnY72G4tw8YQr2gcBGo4w=
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,27 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEogIBAAKCAQEAyY2EWYTW5VHipw08t675upmD6a+akiuZ1z+XpuOxZCgjZ0aH
|
||||||
|
foOe8wGKg3Ohz7UCBdD5Mob/L/qvRlsCaqPHGZKIyyX1HDO4mpuQQFBhYxt+ZAO3
|
||||||
|
AaawEUOw2rwwMDEjLnDDTSZMz8jxCMvsJjBDqgb8g3z+AmjducQ/OH6llldgHIBY
|
||||||
|
8ioRbROCL2PGgqywWq2fThavc70YMxtKviBGDNCouYeQ8JMK/PuLwPNDXNQAagFH
|
||||||
|
VARXiUv/ILHk7ImYnSGJUcukJTUGN2MBnpY0eakg7i+4za8sjjqOdn+2I6aVzlGJ
|
||||||
|
DSiRP72nkg/cE4BqMl9FrMwK9iS8xa9yMDLUvwIDAQABAoIBAHmffvzx7ydESWwa
|
||||||
|
zcfdu26BkptiTvjjfJrqEd4wSewxWGPKqJqMXE8xX99A2KTZClZuKuH1mmnecQQY
|
||||||
|
iRXGrK9ewFMuHYGeKEiLlPlqR8ohXhyGLVm+t0JDwaXMp5t9G0i73O5iLTm5fNGd
|
||||||
|
FGxa9YnVW20Q8MqNczbVGH1D1zInhxzzOyFzBd4bBBJ8PdrUdyLpd7+RxY2ghnbT
|
||||||
|
p9ZANR2vk5zmDLJgZx72n/u+miJWuhY6p0v3Vq4z/HHgdhf+K6vpDdzTcYlA0rO4
|
||||||
|
c/c+RKED3ZadGUD5QoLsmEN0e3FVSMPN1kt4ZRTqWfH8f2X4mLz33aBryTjktP6+
|
||||||
|
1rX6ThECgYEA74wc1Tq23B5R0/GaMm1AK3Ko2zzTD8wK7NSCElh2dls02B+GzrEB
|
||||||
|
aE3A2GMQSuzb+EA0zkipwANBaqs3ZemH5G1pu4hstQsXCMd4jAJn0TmTXlplXBCf
|
||||||
|
PSc8ZUU6XcJENRr9Q7O9/TGlgahX+z0ndxYx/CMCsSu7XsMg4IZsbAcCgYEA12Vb
|
||||||
|
wKOVG15GGp7pMshr+2rQfVimARUP4gf3JnQmenktI4PfdnMW3a4L3DEHfLhIerwT
|
||||||
|
6lRp/NpxSADmuT4h1UO1l2lc+gmTVPw0Vbl6VwHpgS5Kfu4ZyM6n3S66f/dE4nu7
|
||||||
|
hQF9yZz7vn5Agghak4p6a1wC1gdMzR1tvxFzk4kCgYByBMTskWfcWeok8Yitm+bB
|
||||||
|
R3Ar+kWT7VD97SCETusD5uG+RTNLSmEbHnc+B9kHcLo67YS0800pAeOvPBPARGnU
|
||||||
|
RmffRU5I1iB+o0MzkSmNItSMQoagTaEd4IEUyuC/I+qHRHNsOC+kRm86ycAm67LP
|
||||||
|
MhdUpe1wGxqyPjp15EXTHQKBgDKzFu+3EWfJvvKRKQ7dAh3BvKVkcl6a2Iw5l8Ej
|
||||||
|
YdM+JpPPfI/i8yTmzL/dgoem0Nii4IUtrWzo9fUe0TAVId2S/HFRSaNJEbbVTnRH
|
||||||
|
HjbQqmfPv5U08jjD+9siHp/0UfCFc1QRT8xe+RqTmReCY9+KntoaZEiAm2FEZgqt
|
||||||
|
TukRAoGAf7QqbTP5/UH1KSkX89F5qy/6GS3pw6TLj9Ufm/l/NO8Um8gag6YhEKWR
|
||||||
|
7HpkpCqjfWj8Av8ESR9cqddPGrbdqXFm9z7dCjlAd5T3Q3h/h+v+JzLQWbsI6WOb
|
||||||
|
SsOSWNyE006ZZdIiFwO6GfxpLI24sVtYKgyob6Q71oxSqfnrnT0=
|
||||||
|
-----END RSA PRIVATE KEY-----
|
|
@ -0,0 +1,21 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDhTCCAm2gAwIBAgIJAKHt/jxiWqMtMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNV
|
||||||
|
BAYTAlVTMQswCQYDVQQIEwJDQTEVMBMGA1UEBxMMU2FuRnJhbmNpc2NvMQ8wDQYD
|
||||||
|
VQQKEwZEb2NrZXIxEzARBgNVBAMTCmRlbGVnYXRpb24wHhcNMTYwOTI4MTc0ODQ5
|
||||||
|
WhcNMjYwNjI4MTc0ODQ5WjBXMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFTAT
|
||||||
|
BgNVBAcTDFNhbkZyYW5jaXNjbzEPMA0GA1UEChMGRG9ja2VyMRMwEQYDVQQDEwpk
|
||||||
|
ZWxlZ2F0aW9uMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqfbJk2Dk
|
||||||
|
C9FJVjV2+Q2CQrJphG3vFc1Qlu9jgVA5RhGmF9jJzetsclsV/95nBhinIGcSmPQA
|
||||||
|
l318G7Bz/cG/6O2n5+hj+S1+YOvQweReZj3d4kCeS86SOyLNTpMD9gsF0S8nR1RN
|
||||||
|
h0jD4t1vxAVeGD1o61U8/k0O5eDoeOfOSWZagKk5PhyrMZgNip4IrG46umCkFlrw
|
||||||
|
zMMcgQdwTQXywPqkr/LmYpqT1WpMlzHYTQEY8rKorIJQbPtHVYdr4UxYnNmk6fbU
|
||||||
|
biEP1DQlwjBWcFTsDLqXKP/K+e3O0/e/hMB0y7Tj9fZ7Viw0t5IKXZPsxMhwknUT
|
||||||
|
9vmPzIJO6NiniwIDAQABo1QwUjAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIF
|
||||||
|
oDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUdTXRP1EzxQ+UDZSoheVo
|
||||||
|
Mobud1cwDQYJKoZIhvcNAQELBQADggEBADV9asTWWdbmpkeRuKyi0xGho39ONK88
|
||||||
|
xxkFlco766BVgemo/rGQj3oPuw6M6SzHFoJ6JUPjmLiAQDIGEU/2/b6LcOuLjP+4
|
||||||
|
YejCcDTY3lSW/HMNoAmzr2foo/LngNGfe/qhVFUqV7GjFT9+XzFFBfIZ1cQiL2ed
|
||||||
|
kc8rgQxFPwWXFCSwaENWeFnMDugkd+7xanoAHq8GsJpg5fTruDTmJkUqC2RNiMLn
|
||||||
|
WM7QaqW7+lmUnMnc1IBoz0hFhgoiadWM/1RQxx51zTVw6Au1koIm4ZXu5a+/WyC8
|
||||||
|
K1+HyUbc0AVaDaRBpRSOR9aHRwLGh6WQ4aUZQNyJroc999qfYrDEEV8=
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,27 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpQIBAAKCAQEAqfbJk2DkC9FJVjV2+Q2CQrJphG3vFc1Qlu9jgVA5RhGmF9jJ
|
||||||
|
zetsclsV/95nBhinIGcSmPQAl318G7Bz/cG/6O2n5+hj+S1+YOvQweReZj3d4kCe
|
||||||
|
S86SOyLNTpMD9gsF0S8nR1RNh0jD4t1vxAVeGD1o61U8/k0O5eDoeOfOSWZagKk5
|
||||||
|
PhyrMZgNip4IrG46umCkFlrwzMMcgQdwTQXywPqkr/LmYpqT1WpMlzHYTQEY8rKo
|
||||||
|
rIJQbPtHVYdr4UxYnNmk6fbUbiEP1DQlwjBWcFTsDLqXKP/K+e3O0/e/hMB0y7Tj
|
||||||
|
9fZ7Viw0t5IKXZPsxMhwknUT9vmPzIJO6NiniwIDAQABAoIBAQCAr/ed3A2umO7T
|
||||||
|
FDYZik3nXBiiiW4t7r+nGGgZ3/kNgY1lnuHlROxehXLZwbX1mrLnyML/BjhwezV9
|
||||||
|
7ZNVPd6laVPpNj6DyxtWHRZ5yARlm1Al39E7CpQTrF0QsiWcpGnqIa62xjDRTpnq
|
||||||
|
askV/Q5qggyvqmE9FnFCQpEiAjlhvp7F0kVHVJm9s3MK3zSyR0UTZ3cpYus2Jr2z
|
||||||
|
OotHgAMHq5Hgb3dvxOeE2xRMeYAVDujbkNzXm2SddAtiRdLhWDh7JIr3zXhp0HyN
|
||||||
|
4rLOyhlgz00oIGeDt/C0q3fRmghr3iZOG+7m2sUx0FD1Ru1dI9v2A+jYmIVNW6+x
|
||||||
|
YJk5PzxJAoGBANDj7AGdcHSci/LDBPoTTUiz3uucAd27/IJma/iy8mdbVfOAb0Fy
|
||||||
|
PRSPvoozlpZyOxg2J4eH/o4QxQR4lVKtnLKZLNHK2tg3LarwyBX1LiI3vVlB+DT1
|
||||||
|
AmV8i5bJAckDhqFeEH5qdWZFi03oZsSXWEqX5iMYCrdK5lTZggcrFZeHAoGBANBL
|
||||||
|
fkk3knAdcVfTYpmHx18GBi2AsCWTd20KD49YBdbVy0Y2Jaa1EJAmGWpTUKdYx40R
|
||||||
|
H5CuGgcAviXQz3bugdTU1I3tAclBtpJNU7JkhuE+Epz0CM/6WERJrE0YxcGQA5ui
|
||||||
|
6fOguFyiXD1/85jrDBOKy74aoS7lYz9r/a6eqmjdAoGBAJpm/nmrIAZx+Ff2ouUe
|
||||||
|
A1Ar9Ch/Zjm5zEmu3zwzOU4AiyWz14iuoktifNq2iyalRNz+mnVpplToPFizsNwu
|
||||||
|
C9dPtXtU0DJlhtIFrD/evLz6KnGhe4/ZUm4lgyBvb2xfuNHqL5Lhqelwmil6EQxb
|
||||||
|
Oh3Y7XkfOjyFln89TwlxZUJdAoGAJRMa4kta7EvBTeGZLjyltvsqhFTghX+vBSCC
|
||||||
|
ToBbYbbiHJgssXSPAylU4sD7nR3HPwuqM6VZip+OOMrm8oNXZpuPTce+xqTEq1vK
|
||||||
|
JvmPrG3RAFDLdMFZjqYSXhKnuGE60yv3Ol8EEbDwfB3XLQPBPYU56Jdy0xcPSE2f
|
||||||
|
dMJXEJ0CgYEAisZw0nXw6lFeYecu642EGuU0wv1O9i21p7eho9QwOcsoTl4Q9l+M
|
||||||
|
M8iBv+qTHO+D19l4JbkGvy2H2diKoYduUFACcuiFYs8fjrT+4Z6DyOQAQGAf6Ylw
|
||||||
|
BFbU15k6KbA9v4mZDfd1tY9x62L/XO55ZxYG+J+q0e26tEThgD8cEog=
|
||||||
|
-----END RSA PRIVATE KEY-----
|
|
@ -0,0 +1,21 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDhTCCAm2gAwIBAgIJANae++ZkUEWMMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNV
|
||||||
|
BAYTAlVTMQswCQYDVQQIEwJDQTEVMBMGA1UEBxMMU2FuRnJhbmNpc2NvMQ8wDQYD
|
||||||
|
VQQKEwZEb2NrZXIxEzARBgNVBAMTCmRlbGVnYXRpb24wHhcNMTYwOTI4MTc0ODQ5
|
||||||
|
WhcNMjYwNjI4MTc0ODQ5WjBXMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFTAT
|
||||||
|
BgNVBAcTDFNhbkZyYW5jaXNjbzEPMA0GA1UEChMGRG9ja2VyMRMwEQYDVQQDEwpk
|
||||||
|
ZWxlZ2F0aW9uMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqULAjgba
|
||||||
|
Y2I10WfqdmYnPfEqEe6iMDbzcgECb2xKafXcI4ltkQj1iO4zBTs0Ft9EzXFc5ZBh
|
||||||
|
pTjZrL6vrIa0y/CH2BiIHBJ0wRHx/40HXp4DSj3HZpVOlEMI3npRfBGNIBllUaRN
|
||||||
|
PWG7zL7DcKMIepBfPXyjBsxzH3yNiISq0W5hSiy+ImhSo3aipJUHHcp9Z9NgvpNC
|
||||||
|
3QvnxsGKRnECmDRDlxkq+FQu9Iqs/HWFYWgyfcsw+YTrWZq3qVnnqUouHO//c9PG
|
||||||
|
Ry3sZSDU97MwvkjvWys1e01Xvd3AbHx08YAsxih58i/OBKe81eD9NuZDP2KrjTxI
|
||||||
|
5xkXKhj6DV2NnQIDAQABo1QwUjAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIF
|
||||||
|
oDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUDt95hiqbQvi0KcvZGAUu
|
||||||
|
VisnztQwDQYJKoZIhvcNAQELBQADggEBAGi7qHai7MWbfeu6SlXhzIP3AIMa8TMi
|
||||||
|
lp/+mvPUFPswIVqYJ71MAN8uA7CTH3z50a2vYupGeOEtZqVJeRf+xgOEpwycncxp
|
||||||
|
Qz6wc6TWPVIoT5q1Hqxw1RD2MyKL+Y+QBDYwFxFkthpDMlX48I9frcqoJUWFxBF2
|
||||||
|
lnRr/cE7BbPE3sMbXV3wGPlH7+eUf+CgzXJo2HB6THzagyEgNrDiz/0rCQa1ipFd
|
||||||
|
mNU3D/U6BFGmJNxhvSOtXX9escg8yjr05YwwzokHS2K4jE0ZuJPBd50C/Rvo3Mf4
|
||||||
|
0h7/2Q95e7d42zPe9WYPu2F8KTWsf4r+6ddhKrKhYzXIcTAfHIOiO+U=
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,27 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpAIBAAKCAQEAqULAjgbaY2I10WfqdmYnPfEqEe6iMDbzcgECb2xKafXcI4lt
|
||||||
|
kQj1iO4zBTs0Ft9EzXFc5ZBhpTjZrL6vrIa0y/CH2BiIHBJ0wRHx/40HXp4DSj3H
|
||||||
|
ZpVOlEMI3npRfBGNIBllUaRNPWG7zL7DcKMIepBfPXyjBsxzH3yNiISq0W5hSiy+
|
||||||
|
ImhSo3aipJUHHcp9Z9NgvpNC3QvnxsGKRnECmDRDlxkq+FQu9Iqs/HWFYWgyfcsw
|
||||||
|
+YTrWZq3qVnnqUouHO//c9PGRy3sZSDU97MwvkjvWys1e01Xvd3AbHx08YAsxih5
|
||||||
|
8i/OBKe81eD9NuZDP2KrjTxI5xkXKhj6DV2NnQIDAQABAoIBAGK0ZKnuYSiXux60
|
||||||
|
5MvK4pOCsa/nY3mOcgVHhW4IzpRgJdIrcFOlz9ncXrBsSAIWjX7o3u2Ydvjs4DOW
|
||||||
|
t8d6frB3QiDInYcRVDjLCD6otWV97Bk9Ua0G4N4hAWkMF7ysV4oihS1JDSoAdo39
|
||||||
|
qOdki6s9yeyHZGKwk2oHLlowU5TxQMBA8DHmxqBII1HTm+8xRz45bcEqRXydYSUn
|
||||||
|
P1JuSU9jFqdylxU+Nrq6ehslMQ3y7qNWQyiLGxu6EmR+vgrzSU0s3iAOqCHthaOS
|
||||||
|
VBBXPL3DNEYUS+0QGnGrACuJhanOMBfdiO6Orelx6ZzWZm38PNGv0yBt0WCM+8/A
|
||||||
|
TtQNGkECgYEA1LqR6AH9XikUQ0+rM4526BgVuYqtjw21h4Lj9alaA+YTQntBBJOv
|
||||||
|
iAcUpnJiV4T8jzAMLeqpK8R/rbxRnK5S9jOV2gr+puk4L6tH46cgahBUESDigDp8
|
||||||
|
6vK8ur6ubBcXNPh3AT6rsPj+Ph2EU3raqiYdouvCdga/OCYZb+jr6UkCgYEAy7Cr
|
||||||
|
l8WssI/8/ORcQ4MFJFNyfz/Y2beNXyLd1PX0H+wRSiGcKzeUuTHNtzFFpMbrK/nx
|
||||||
|
ZOPCT2ROdHsBHzp1L+WquCb0fyMVSiYiXBU+VCFDbUU5tBr3ycTc7VwuFPENOiha
|
||||||
|
IdlWgew/aW110FQHIaqe9g+htRe+mXe++faZtbUCgYB/MSJmNzJX53XvHSZ/CBJ+
|
||||||
|
iVAMBSfq3caJRLCqRNzGcf1YBbwFUYxlZ95n+wJj0+byckcF+UW3HqE8rtmZNf3y
|
||||||
|
qTtTCLnj8JQgpGeybU4LPMIXD7N9+fqQvBwuCC7gABpnGJyHCQK9KNNTLnDdPRqb
|
||||||
|
G3ki3ZYC3dvdZaJV8E2FyQKBgQCMa5Mf4kqWvezueo+QizZ0QILibqWUEhIH0AWV
|
||||||
|
1qkhiKCytlDvCjYhJdBnxjP40Jk3i+t6XfmKud/MNTAk0ywOhQoYQeKz8v+uSnPN
|
||||||
|
f2ekn/nXzq1lGGJSWsDjcXTjQvqXaVIZm7cjgjaE+80IfaUc9H75qvUT3vaq3f5u
|
||||||
|
XC7DMQKBgQDMAzCCpWlEPbZoFMl6F49+7jG0/TiqM/WRUSQnNtufPMbrR9Je4QM1
|
||||||
|
L1UCANCPaHFOncKYer15NfIV1ctt5MZKImevDsUaQO8CUlO+dzd5H8KvHw9E29gA
|
||||||
|
B22v8k3jIjsYeRL+UJ/sBnWHgxdAe/NEM+TdlP2oP9D1gTifutPqAg==
|
||||||
|
-----END RSA PRIVATE KEY-----
|
|
@ -0,0 +1,18 @@
|
||||||
|
for selfsigned in delgkey1 delgkey2 delgkey3 delgkey4; do
|
||||||
|
subj='/C=US/ST=CA/L=SanFrancisco/O=Docker/CN=delegation'
|
||||||
|
|
||||||
|
openssl genrsa -out "${selfsigned}.key" 2048
|
||||||
|
openssl req -new -key "${selfsigned}.key" -out "${selfsigned}.csr" -sha256 -subj "${subj}"
|
||||||
|
cat > "${selfsigned}.cnf" <<EOL
|
||||||
|
[selfsigned]
|
||||||
|
basicConstraints = critical,CA:FALSE
|
||||||
|
keyUsage = critical, digitalSignature, keyEncipherment
|
||||||
|
extendedKeyUsage=codeSigning
|
||||||
|
subjectKeyIdentifier=hash
|
||||||
|
EOL
|
||||||
|
|
||||||
|
openssl x509 -req -days 3560 -in "${selfsigned}.csr" -signkey "${selfsigned}.key" -sha256 \
|
||||||
|
-out "${selfsigned}.crt" -extfile "${selfsigned}.cnf" -extensions selfsigned
|
||||||
|
|
||||||
|
rm "${selfsigned}.cnf" "${selfsigned}.csr"
|
||||||
|
done
|
|
@ -0,0 +1,19 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDCTCCAfOgAwIBAgIQTOoFF+ypXwgdXnXHuCTvYDALBgkqhkiG9w0BAQswJjER
|
||||||
|
MA8GA1UEChMIUXVpY2tUTFMxETAPBgNVBAMTCFF1aWNrVExTMB4XDTE1MDcxNzE5
|
||||||
|
NDg1M1oXDTE4MDcwMTE5NDg1M1owJzERMA8GA1UEChMIUXVpY2tUTFMxEjAQBgNV
|
||||||
|
BAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMDO
|
||||||
|
qvTBAi0ApXLfe90ApJkdkRGwF838Qzt1UFSxomu5fHRV6l3FjX5XCVHiFQ4w3ROh
|
||||||
|
dMOu9NahfGLJv9VvWU2MV3YoY9Y7lIXpKwnK1v064wuls4nPh13BUWKQKofcY/e2
|
||||||
|
qaSPd6/qmSRc/kJUvOI9jZMSX6ZRPu9K4PCqm2CivlbLq9UYuo1AbRGfuqHRvTxg
|
||||||
|
mQG7WQCzGSvSjuSg5qX3TEh0HckTczJG9ODULNRWNE7ld0W4sfv4VF8R7Uc/G7LO
|
||||||
|
8QwLCZ9TIl3gYMPCrhUL3Q6z9Jnn1SQS4mhDnPi6ugRYO1X8k3jjdxV9C2sXwUvN
|
||||||
|
OZI1rLEWl9TJNA7ZXtMCAwEAAaM2MDQwDgYDVR0PAQH/BAQDAgCgMAwGA1UdEwEB
|
||||||
|
/wQCMAAwFAYDVR0RBA0wC4IJbG9jYWxob3N0MAsGCSqGSIb3DQEBCwOCAQEAH6iq
|
||||||
|
kM2+UMukGDLEQKHHiauioWJlHDlLXv76bJiNfjSz94B/2XOQMb9PT04//tnGUyPK
|
||||||
|
K8Dx7RoxSodU6T5VRiz/A36mLOvt2t3bcL/1nHf9sAOHcexGtnCbQbW91V7RKfIL
|
||||||
|
sjiLNFDkQ9VfVNY+ynQptZoyH1sy07+dplfkIiPzRs5WuVAnEGsX3r6BrhgUITzi
|
||||||
|
g1B4kpmGZIohP4m6ZEBY5xuo/NQ0+GhjAENQMU38GpuoMyFS0i0dGcbx8weqnI/B
|
||||||
|
Er/qa0+GE/rBnWY8TiRow8dzpneSFQnUZpJ4EwD9IoOIDHo7k2Nbz2P50HMiCXZf
|
||||||
|
4RqzctVssRlrRVnO5w==
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,27 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEogIBAAKCAQEAwM6q9MECLQClct973QCkmR2REbAXzfxDO3VQVLGia7l8dFXq
|
||||||
|
XcWNflcJUeIVDjDdE6F0w6701qF8Ysm/1W9ZTYxXdihj1juUhekrCcrW/TrjC6Wz
|
||||||
|
ic+HXcFRYpAqh9xj97appI93r+qZJFz+QlS84j2NkxJfplE+70rg8KqbYKK+Vsur
|
||||||
|
1Ri6jUBtEZ+6odG9PGCZAbtZALMZK9KO5KDmpfdMSHQdyRNzMkb04NQs1FY0TuV3
|
||||||
|
Rbix+/hUXxHtRz8bss7xDAsJn1MiXeBgw8KuFQvdDrP0mefVJBLiaEOc+Lq6BFg7
|
||||||
|
VfyTeON3FX0LaxfBS805kjWssRaX1Mk0Dtle0wIDAQABAoIBAHbuhNHZROhRn70O
|
||||||
|
Ui9vOBki/dt1ThnH5AkHQngb4t6kWjrAzILvW2p1cdBKr0ZDqftz+rzCbVD/5+Rg
|
||||||
|
Iq8bsnB9g23lWEBMHD/GJsAxmRA3hNooamk11IBmwTcVSsbnkdq5mEdkICYphjHC
|
||||||
|
Ey0DbEf6RBxWlx3WvAWLoNmTw6iFaOCH8IyLavPpe7kLbZc219oNUw2qjCnCXCZE
|
||||||
|
/NuViADHJBPN8r7g1gmyclJmTumdUK6oHgXEMMPe43vhReGcgcReK9QZjnTcIXPM
|
||||||
|
4oJOraw+BtoZXVvvIPnC+5ntoLFOzjIzM0kaveReZbdgffqF4zy2vRfCHhWssanc
|
||||||
|
7a0xR4ECgYEA3Xuvcqy5Xw+v/jVCO0VZj++Z7apA78dY4tWsPx5/0DUTTziTlXkC
|
||||||
|
ADduEbwX6HgZ/iLvA9j4C3Z4mO8qByby/6UoBU8NEe+PQt6fT7S+dKSP4uy5ZxVM
|
||||||
|
i5opkEyrJsMbve9Jrlj4bk5CICsydrZ+SBFHnpNGjbduGQick5LORWECgYEA3trt
|
||||||
|
gepteDGiUYmnnBgjbYtcD11RvpKC8Z/QwGnzN5vk4eBu8r7DkMcLN+SiHjAovlJo
|
||||||
|
r5j3EbF8sla1zBf/yySdQZFqUGcwtw7MaAKCLdhQl5WsViNMIx6p2OJapu0dzbv2
|
||||||
|
KTXrnoRCafcH92k0dUX1ahE9eyc8KX6VhbWwXLMCgYATGCCuEDoC+gVAMzM8jOQF
|
||||||
|
xrBMjwr+IP+GvskUv/pg5tJ9V/FRR5dmkWDJ4p9lCUWkZTqZ6FCqHFKVTLkg2LjG
|
||||||
|
VWS34HLOAwskxrCRXJG22KEW/TWWr31j46yFpjZzJwrzOvftMfpo+BI3V8IH/f+x
|
||||||
|
EtxLzYKdoRy6x8VH67YgwQKBgHor2vjV45142FuK83AHa6SqOZXSuvWWrGJ6Ep7p
|
||||||
|
doSN2jRaLXi2S9AaznOdy6JxFGUCGJHrcccpXgsGrjNtFLXxJKTFa1sYtwQkALsk
|
||||||
|
ZOltJQF09D1krGC0driHntrUMvqOiKye+sS0DRS6cIuaCUAhUiELwoC5SaoV0zKy
|
||||||
|
IDUxAoGAOK8Xq+3/sqe79vTpw25RXl+nkAmOAeKjqf3Kh6jbnBhr81rmefyKXB9a
|
||||||
|
uj0b980tzUnliwA5cCOsyxfN2vASvMnJxFE721QZI04arlcPFHcFqCtmNnUYTcLp
|
||||||
|
0hgn/yLZptcoxpy+eTBu3eNsxz1Bu/Tx/198+2Wr3MbtGpLNIcA=
|
||||||
|
-----END RSA PRIVATE KEY-----
|
|
@ -1 +1 @@
|
||||||
Tagging registry:5000/trust@sha256:641b95ddb2ea9dc2af1a0113b6b348ebc20872ba615204fbe12148e98fd6f23d as registry:5000/trust:latest
|
Tagging registry:5000/trust-pull@sha256:641b95ddb2ea9dc2af1a0113b6b348ebc20872ba615204fbe12148e98fd6f23d as registry:5000/trust-pull:latest
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
Pull (1 of 1): registry:5000/trust:latest@sha256:641b95ddb2ea9dc2af1a0113b6b348ebc20872ba615204fbe12148e98fd6f23d
|
Pull (1 of 1): registry:5000/trust-pull:latest@sha256:641b95ddb2ea9dc2af1a0113b6b348ebc20872ba615204fbe12148e98fd6f23d
|
||||||
sha256:641b95ddb2ea9dc2af1a0113b6b348ebc20872ba615204fbe12148e98fd6f23d: Pulling from trust
|
sha256:641b95ddb2ea9dc2af1a0113b6b348ebc20872ba615204fbe12148e98fd6f23d: Pulling from trust-pull
|
||||||
Digest: sha256:641b95ddb2ea9dc2af1a0113b6b348ebc20872ba615204fbe12148e98fd6f23d
|
Digest: sha256:641b95ddb2ea9dc2af1a0113b6b348ebc20872ba615204fbe12148e98fd6f23d
|
||||||
Status: Downloaded newer image for registry:5000/trust@sha256:641b95ddb2ea9dc2af1a0113b6b348ebc20872ba615204fbe12148e98fd6f23d
|
Status: Downloaded newer image for registry:5000/trust-pull@sha256:641b95ddb2ea9dc2af1a0113b6b348ebc20872ba615204fbe12148e98fd6f23d
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package fixtures
|
package fixtures
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -35,7 +36,7 @@ func SetupConfigFile(t *testing.T) fs.Dir {
|
||||||
},
|
},
|
||||||
"experimental": "enabled"
|
"experimental": "enabled"
|
||||||
}
|
}
|
||||||
`))
|
`), fs.WithDir("trust", fs.WithDir("private")))
|
||||||
return *dir
|
return *dir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,3 +76,47 @@ func WithNotary(cmd *icmd.Cmd) {
|
||||||
)
|
)
|
||||||
cmd.Env = append(cmd.Env, env...)
|
cmd.Env = append(cmd.Env, env...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//WithHome sets the HOME environment variable
|
||||||
|
func WithHome(path string) func(*icmd.Cmd) {
|
||||||
|
return func(cmd *icmd.Cmd) {
|
||||||
|
cmd.Env = append(cmd.Env, "HOME="+path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//WithNotaryServer sets the location of the notary server
|
||||||
|
func WithNotaryServer(notaryURL string) func(*icmd.Cmd) {
|
||||||
|
return func(cmd *icmd.Cmd) {
|
||||||
|
env := append(os.Environ(),
|
||||||
|
"DOCKER_CONTENT_TRUST_SERVER="+notaryURL,
|
||||||
|
)
|
||||||
|
cmd.Env = append(cmd.Env, env...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateMaskedTrustedRemoteImage creates a remote image that is signed with
|
||||||
|
// content trust, then pushes a different untrusted image at the same tag.
|
||||||
|
func CreateMaskedTrustedRemoteImage(t *testing.T, registryPrefix, repo, tag string) string {
|
||||||
|
image := createTrustedRemoteImage(t, registryPrefix, repo, tag)
|
||||||
|
createNamedUnsignedImageFromBusyBox(t, image)
|
||||||
|
return image
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTrustedRemoteImage(t *testing.T, registryPrefix, repo, tag string) string {
|
||||||
|
image := fmt.Sprintf("%s/%s:%s", registryPrefix, repo, tag)
|
||||||
|
icmd.RunCommand("docker", "image", "pull", AlpineImage).Assert(t, icmd.Success)
|
||||||
|
icmd.RunCommand("docker", "image", "tag", AlpineImage, image).Assert(t, icmd.Success)
|
||||||
|
result := icmd.RunCmd(
|
||||||
|
icmd.Command("docker", "image", "push", image),
|
||||||
|
WithPassphrase("root_password", "repo_password"), WithTrust, WithNotary)
|
||||||
|
result.Assert(t, icmd.Success)
|
||||||
|
icmd.RunCommand("docker", "image", "rm", image).Assert(t, icmd.Success)
|
||||||
|
return image
|
||||||
|
}
|
||||||
|
|
||||||
|
func createNamedUnsignedImageFromBusyBox(t *testing.T, image string) {
|
||||||
|
icmd.RunCommand("docker", "image", "pull", BusyboxImage).Assert(t, icmd.Success)
|
||||||
|
icmd.RunCommand("docker", "image", "tag", BusyboxImage, image).Assert(t, icmd.Success)
|
||||||
|
icmd.RunCommand("docker", "image", "push", image).Assert(t, icmd.Success)
|
||||||
|
icmd.RunCommand("docker", "image", "rm", image).Assert(t, icmd.Success)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
p, err := filepath.Abs(filepath.Join("run", "docker", "plugins"))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(p, 0755); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
l, err := net.Listen("unix", filepath.Join(p, "basic.sock"))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
server := http.Server{
|
||||||
|
Addr: l.Addr().String(),
|
||||||
|
Handler: http.NewServeMux(),
|
||||||
|
}
|
||||||
|
mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1.1+json")
|
||||||
|
fmt.Println(w, `{"Implements": ["dummy"]}`)
|
||||||
|
})
|
||||||
|
server.Serve(l)
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/cli/internal/test/environment"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
if err := environment.Setup(); err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
os.Exit(3)
|
||||||
|
}
|
||||||
|
os.Exit(m.Run())
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/cli/e2e/internal/fixtures"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/gotestyourself/gotestyourself/assert"
|
||||||
|
"github.com/gotestyourself/gotestyourself/fs"
|
||||||
|
"github.com/gotestyourself/gotestyourself/icmd"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const registryPrefix = "registry:5000"
|
||||||
|
|
||||||
|
func TestInstallWithContentTrust(t *testing.T) {
|
||||||
|
pluginName := fmt.Sprintf("%s/plugin-content-trust", registryPrefix)
|
||||||
|
|
||||||
|
dir := fixtures.SetupConfigFile(t)
|
||||||
|
defer dir.Remove()
|
||||||
|
|
||||||
|
pluginDir := preparePluginDir(t)
|
||||||
|
defer pluginDir.Remove()
|
||||||
|
|
||||||
|
icmd.RunCommand("docker", "plugin", "create", pluginName, pluginDir.Path()).Assert(t, icmd.Success)
|
||||||
|
result := icmd.RunCmd(icmd.Command("docker", "plugin", "push", pluginName),
|
||||||
|
fixtures.WithConfig(dir.Path()),
|
||||||
|
fixtures.WithTrust,
|
||||||
|
fixtures.WithNotary,
|
||||||
|
fixtures.WithPassphrase("foo", "bar"),
|
||||||
|
)
|
||||||
|
result.Assert(t, icmd.Expected{
|
||||||
|
Out: "Signing and pushing trust metadata",
|
||||||
|
})
|
||||||
|
|
||||||
|
icmd.RunCommand("docker", "plugin", "rm", "-f", pluginName).Assert(t, icmd.Success)
|
||||||
|
|
||||||
|
result = icmd.RunCmd(icmd.Command("docker", "plugin", "install", "--grant-all-permissions", pluginName),
|
||||||
|
fixtures.WithConfig(dir.Path()),
|
||||||
|
fixtures.WithTrust,
|
||||||
|
fixtures.WithNotary,
|
||||||
|
)
|
||||||
|
result.Assert(t, icmd.Expected{
|
||||||
|
Out: fmt.Sprintf("Status: Downloaded newer image for %s@sha", pluginName),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInstallWithContentTrustUntrusted(t *testing.T) {
|
||||||
|
dir := fixtures.SetupConfigFile(t)
|
||||||
|
defer dir.Remove()
|
||||||
|
|
||||||
|
result := icmd.RunCmd(icmd.Command("docker", "plugin", "install", "--grant-all-permissions", "tiborvass/sample-volume-plugin:latest"),
|
||||||
|
fixtures.WithConfig(dir.Path()),
|
||||||
|
fixtures.WithTrust,
|
||||||
|
fixtures.WithNotary,
|
||||||
|
)
|
||||||
|
result.Assert(t, icmd.Expected{
|
||||||
|
ExitCode: 1,
|
||||||
|
Err: "Error: remote trust data does not exist",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func preparePluginDir(t *testing.T) *fs.Dir {
|
||||||
|
p := &types.PluginConfig{
|
||||||
|
Interface: types.PluginConfigInterface{
|
||||||
|
Socket: "basic.sock",
|
||||||
|
Types: []types.PluginInterfaceType{{Capability: "docker.dummy/1.0"}},
|
||||||
|
},
|
||||||
|
Entrypoint: []string{"/basic"},
|
||||||
|
}
|
||||||
|
configJSON, err := json.Marshal(p)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
|
||||||
|
binPath, err := ensureBasicPluginBin()
|
||||||
|
assert.NilError(t, err)
|
||||||
|
|
||||||
|
dir := fs.NewDir(t, "plugin_test",
|
||||||
|
fs.WithFile("config.json", string(configJSON), fs.WithMode(0644)),
|
||||||
|
fs.WithDir("rootfs", fs.WithMode(0755)),
|
||||||
|
)
|
||||||
|
icmd.RunCommand("/bin/cp", binPath, dir.Join("rootfs", p.Entrypoint[0])).Assert(t, icmd.Success)
|
||||||
|
return dir
|
||||||
|
}
|
||||||
|
|
||||||
|
func ensureBasicPluginBin() (string, error) {
|
||||||
|
name := "docker-basic-plugin"
|
||||||
|
p, err := exec.LookPath(name)
|
||||||
|
if err == nil {
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
goBin, err := exec.LookPath("/usr/local/go/bin/go")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
installPath := filepath.Join(os.Getenv("GOPATH"), "bin", name)
|
||||||
|
cmd := exec.Command(goBin, "build", "-o", installPath, "./basic")
|
||||||
|
cmd.Env = append(cmd.Env, "CGO_ENABLED=0")
|
||||||
|
if out, err := cmd.CombinedOutput(); err != nil {
|
||||||
|
return "", errors.Wrapf(err, "error building basic plugin bin: %s", string(out))
|
||||||
|
}
|
||||||
|
return installPath, nil
|
||||||
|
}
|
|
@ -1,63 +1,64 @@
|
||||||
-----BEGIN CERTIFICATE-----
|
-----BEGIN CERTIFICATE-----
|
||||||
MIIFBDCCAuygAwIBAgIJAPlHYZzp1daGMA0GCSqGSIb3DQEBCwUAMGwxCzAJBgNV
|
MIIFKjCCAxKgAwIBAgIJAMyOpNbHzq7KMA0GCSqGSIb3DQEBCwUAMGwxCzAJBgNV
|
||||||
BAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEPMA0G
|
BAYTAlVTMQswCQYDVQQIDAJDQTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEPMA0G
|
||||||
A1UEChMGRG9ja2VyMScwJQYDVQQDEx5Ob3RhcnkgSW50ZXJtZWRpYXRlIFRlc3Rp
|
A1UECgwGRG9ja2VyMScwJQYDVQQDDB5Ob3RhcnkgSW50ZXJtZWRpYXRlIFRlc3Rp
|
||||||
bmcgQ0EwHhcNMTcwMjE3MDA0MzE3WhcNMTkwMzA5MDA0MzE3WjBbMQswCQYDVQQG
|
bmcgQ0EwHhcNMTgwMzA3MDk0MjAxWhcNMjAwMzI2MDk0MjAxWjBbMQswCQYDVQQG
|
||||||
EwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xDzANBgNV
|
EwJVUzELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDzANBgNV
|
||||||
BAoTBkRvY2tlcjEWMBQGA1UEAxMNbm90YXJ5LXNlcnZlcjCCASIwDQYJKoZIhvcN
|
BAoMBkRvY2tlcjEWMBQGA1UEAwwNbm90YXJ5LXNlcnZlcjCCASIwDQYJKoZIhvcN
|
||||||
AQEBBQADggEPADCCAQoCggEBAKjbeflOtVrOv0IOeJGKfi5LHH3Di0O2nlZu8AIT
|
AQEBBQADggEPADCCAQoCggEBAKjbeflOtVrOv0IOeJGKfi5LHH3Di0O2nlZu8AIT
|
||||||
SJbDZPSXoYc+cprpoEWYncbFFC3C94z5xBW5vcAqMhLs50ml5ADl86umcLl2C/mX
|
SJbDZPSXoYc+cprpoEWYncbFFC3C94z5xBW5vcAqMhLs50ml5ADl86umcLl2C/mX
|
||||||
8NuZnlIevMCb0mBiavDtSPV3J5DqOk+trgKEXs9g4hyh5Onh5Y5InPO1lDJ+2cEt
|
8NuZnlIevMCb0mBiavDtSPV3J5DqOk+trgKEXs9g4hyh5Onh5Y5InPO1lDJ+2cEt
|
||||||
VGBMhhddfWRVlV9ZUWxPYVCTt6L0bD9SeyXJVB0dnFhr3xICayhDlhlvcjXVOTUs
|
VGBMhhddfWRVlV9ZUWxPYVCTt6L0bD9SeyXJVB0dnFhr3xICayhDlhlvcjXVOTUs
|
||||||
ewJLo/L2nq0ve93Jb2smKio27ZGE79bCGqJK213/FNqfAlGUPkhYTfYJTcgjhS1p
|
ewJLo/L2nq0ve93Jb2smKio27ZGE79bCGqJK213/FNqfAlGUPkhYTfYJTcgjhS1p
|
||||||
lmtgN6KZF6RVXvOrCBMEDM2yZq1mEPWjoT0tn0MkWErDANcCAwEAAaOBuTCBtjAf
|
lmtgN6KZF6RVXvOrCBMEDM2yZq1mEPWjoT0tn0MkWErDANcCAwEAAaOB3zCB3DAf
|
||||||
BgNVHSMEGDAWgBS6l0MbzVfv/9OdgJ2V2t/f3oOJ3TAMBgNVHRMBAf8EAjAAMB0G
|
BgNVHSMEGDAWgBSIZiRbIhk/26DjfXrjiqS2UiRgEjAMBgNVHRMBAf8EAjAAMB0G
|
||||||
A1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAOBgNVHQ8BAf8EBAMCBaAwNwYD
|
A1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAOBgNVHQ8BAf8EBAMCBaAwXQYD
|
||||||
VR0RBDAwLoINbm90YXJ5LXNlcnZlcoIMbm90YXJ5c2VydmVygglsb2NhbGhvc3SH
|
VR0RBFYwVIINbm90YXJ5LXNlcnZlcoIMbm90YXJ5c2VydmVyghJldmlsLW5vdGFy
|
||||||
BH8AAAEwHQYDVR0OBBYEFBQcColyhey0o0RTLiiGAtaRhUIuMA0GCSqGSIb3DQEB
|
eS1zZXJ2ZXKCEGV2aWxub3RhcnlzZXJ2ZXKCCWxvY2FsaG9zdIcEfwAAATAdBgNV
|
||||||
CwUAA4ICAQB04WZaMeF90mQDqiRVhBUkp8HvfEqchP6QLwprZmgbaRi75JksK59x
|
HQ4EFgQUFBwKiXKF7LSjRFMuKIYC1pGFQi4wDQYJKoZIhvcNAQELBQADggIBACE6
|
||||||
ynaqgQj61hvN2RzpA1V/YXagmD6dk+GqhgiR+O++k+wb26446qQTSP6jkYRQGUT6
|
Y+XRchaN7B8+z+4qXxNTTPT5WZYWU4IMddPLWPcSj9U+XFBz19nObrZLAf6Z3Te7
|
||||||
s2Qp0fhgV9eHHZ/27Cl4rEpjYtxd6yVN/DNQapj/h3qejuZ1UDIZhvswfEgiL57f
|
6BlcVfEdIRMNCspcjX6RZjeC0iJAaDKo+SdqoajKpEwph9R4OP2I4mwcpf/oMG7p
|
||||||
0W0huPNS6LnSOwoKKgSlA38OGs993BwMJkc+1ikzEcpVcn4l+kjeefnDmguBrxFv
|
IsNyzeA7427ignwQ+XfwSElJ5BeTUSPgkYujrTZDxRfCA01t0X0tpzv/3o4dfoZ0
|
||||||
5il7yQ45BxGwR/SLobpehV+XodjNUd8mpdoF9QWr8kibaDPNndhdJLHuzyYatnRe
|
QKA+2djJagrjU82kTneWRktn6+G+zQuZdupcwUEPvlH3YdEgH22W0m0mavnJMYI/
|
||||||
hDqFA5DqZ+uaSwPyixilDoAXFs81P6UTkGh3EjP7rMbZNYnIHYFYIKpYVu23vbh+
|
J/J4rmanvknLnjCmChEdpwCORL+G84Hu9hdy0WSbvQzQ9puGyukWJO/71Z0fhis2
|
||||||
eriCw61YvEcIxqfvtIAVfbxwnXExQWGIDXgkJlfskHh/c4hQ1CWHCgqmO8Hvix1u
|
DuLIt2esRJoKyrnaCDA+EFJ0JkzozZfS2BcRe3UL9awnwa/iKlUcs/u4bhKu9gwL
|
||||||
OMfhB5LygX+4QANoKMkUUlKv2MC5HXQ7Bg6rCfPioju2nzGIbbUK043UnfJ2yXIh
|
F2dXtYPJzyr3PUdPGT4FixTpVZG37BvUSy2IH97qxgK9vn1FGTLDqQlSwCLXvrzr
|
||||||
5g0bKGGdWMr5Qw0at8A2NvR6WvXm6+9gu94rNDGoIPn6umTmFjJCbGhjcyyjxg+k
|
g2Zz7+JpF2SK3yR1G/IYmAFa37vmbBjW7dZp2JepTMelpblxiTthH1jPN7Lf3uvd
|
||||||
DO0uhoilX2OkvQHeaBwiy1WM2ETMQBKvkUfq6EUoLsWQTT2NOZiwwEMywwJCb853
|
tDe2ZLxTNOVpymDupUqOEXanvcTAkRD3ye3/yhWaXQ44lxVJqG+FNdjQCKcARHWH
|
||||||
LuQjsvxfOFuqEgXEWjrEnhjwDCJFEDqaJAgajmBZ9xU+yUco44U9zQ==
|
H1Otd12QzOsHytUSe7n4zsT06Seyt9huq4g6h0EcuVZaJbi43u5mJtn9yZiZOLF+
|
||||||
|
dJsYWaDeSXDnyaFl1tHwcF1tqvguI41E63QqrM0N
|
||||||
-----END CERTIFICATE-----
|
-----END CERTIFICATE-----
|
||||||
-----BEGIN CERTIFICATE-----
|
-----BEGIN CERTIFICATE-----
|
||||||
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
|
||||||
ALNmRewqFkXD
|
EIL7P2RDzU63
|
||||||
-----END CERTIFICATE-----
|
-----END CERTIFICATE-----
|
||||||
|
|
|
@ -1,32 +1,32 @@
|
||||||
-----BEGIN CERTIFICATE-----
|
-----BEGIN CERTIFICATE-----
|
||||||
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
|
||||||
rba7O3YGSuScQakjt9mw2Q5ISwImkRHF3qE=
|
Kq+PFV31ZYMnY6ZeGY8LdIJ/L7/2xndikJE=
|
||||||
-----END CERTIFICATE-----
|
-----END CERTIFICATE-----
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
package output
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Assert checks wether the output contains the specified lines
|
||||||
|
func Assert(t *testing.T, actual string, expectedLines map[int]func(string) error) {
|
||||||
|
for i, line := range strings.Split(actual, "\n") {
|
||||||
|
cmp, ok := expectedLines[i]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := cmp(line); err != nil {
|
||||||
|
t.Errorf("line %d: %s", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if t.Failed() {
|
||||||
|
t.Log(actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefix returns whether if the line has the specified string as prefix
|
||||||
|
func Prefix(expected string) func(string) error {
|
||||||
|
return func(actual string) error {
|
||||||
|
if strings.HasPrefix(actual, expected) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.Errorf("expected %s to start with %s", actual, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals returns wether the line is the same as the specified string
|
||||||
|
func Equals(expected string) func(string) error {
|
||||||
|
return func(actual string) error {
|
||||||
|
if expected == actual {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.Errorf("got %s, expected %s", actual, expected)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue