github.1git.de/docker/cli@v26.1.3+incompatible/cli/command/trust/sign_test.go (about)

     1  package trust
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"io"
     7  	"runtime"
     8  	"testing"
     9  
    10  	"github.com/docker/cli/cli/config"
    11  	"github.com/docker/cli/cli/trust"
    12  	"github.com/docker/cli/internal/test"
    13  	notaryfake "github.com/docker/cli/internal/test/notary"
    14  	"github.com/theupdateframework/notary"
    15  	"github.com/theupdateframework/notary/client"
    16  	"github.com/theupdateframework/notary/client/changelist"
    17  	"github.com/theupdateframework/notary/passphrase"
    18  	"github.com/theupdateframework/notary/trustpinning"
    19  	"github.com/theupdateframework/notary/tuf/data"
    20  	"gotest.tools/v3/assert"
    21  	is "gotest.tools/v3/assert/cmp"
    22  	"gotest.tools/v3/skip"
    23  )
    24  
    25  const passwd = "password"
    26  
    27  func TestTrustSignCommandErrors(t *testing.T) {
    28  	testCases := []struct {
    29  		name          string
    30  		args          []string
    31  		expectedError string
    32  	}{
    33  		{
    34  			name:          "not-enough-args",
    35  			expectedError: "requires exactly 1 argument",
    36  		},
    37  		{
    38  			name:          "too-many-args",
    39  			args:          []string{"image", "tag"},
    40  			expectedError: "requires exactly 1 argument",
    41  		},
    42  		{
    43  			name:          "sha-reference",
    44  			args:          []string{"870d292919d01a0af7e7f056271dc78792c05f55f49b9b9012b6d89725bd9abd"},
    45  			expectedError: "invalid repository name",
    46  		},
    47  		{
    48  			name:          "invalid-img-reference",
    49  			args:          []string{"ALPINE:latest"},
    50  			expectedError: "invalid reference format",
    51  		},
    52  		{
    53  			name:          "no-tag",
    54  			args:          []string{"reg/img"},
    55  			expectedError: "no tag specified for reg/img",
    56  		},
    57  		{
    58  			name:          "digest-reference",
    59  			args:          []string{"ubuntu@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2"},
    60  			expectedError: "cannot use a digest reference for IMAGE:TAG",
    61  		},
    62  	}
    63  	// change to a tmpdir
    64  	config.SetDir(t.TempDir())
    65  	for _, tc := range testCases {
    66  		cmd := newSignCommand(
    67  			test.NewFakeCli(&fakeClient{}))
    68  		cmd.SetArgs(tc.args)
    69  		cmd.SetOut(io.Discard)
    70  		assert.ErrorContains(t, cmd.Execute(), tc.expectedError)
    71  	}
    72  }
    73  
    74  func TestTrustSignCommandOfflineErrors(t *testing.T) {
    75  	cli := test.NewFakeCli(&fakeClient{})
    76  	cli.SetNotaryClient(notaryfake.GetOfflineNotaryRepository)
    77  	cmd := newSignCommand(cli)
    78  	cmd.SetArgs([]string{"reg-name.io/image:tag"})
    79  	cmd.SetOut(io.Discard)
    80  	assert.ErrorContains(t, cmd.Execute(), "client is offline")
    81  }
    82  
    83  func TestGetOrGenerateNotaryKey(t *testing.T) {
    84  	notaryRepo, err := client.NewFileCachedRepository(t.TempDir(), "gun", "https://localhost", nil, passphrase.ConstantRetriever(passwd), trustpinning.TrustPinConfig{})
    85  	assert.NilError(t, err)
    86  
    87  	// repo is empty, try making a root key
    88  	rootKeyA, err := getOrGenerateNotaryKey(notaryRepo, data.CanonicalRootRole)
    89  	assert.NilError(t, err)
    90  	assert.Check(t, rootKeyA != nil)
    91  
    92  	// we should only have one newly generated key
    93  	allKeys := notaryRepo.GetCryptoService().ListAllKeys()
    94  	assert.Check(t, is.Len(allKeys, 1))
    95  	assert.Check(t, notaryRepo.GetCryptoService().GetKey(rootKeyA.ID()) != nil)
    96  
    97  	// this time we should get back the same key if we ask for another root key
    98  	rootKeyB, err := getOrGenerateNotaryKey(notaryRepo, data.CanonicalRootRole)
    99  	assert.NilError(t, err)
   100  	assert.Check(t, rootKeyB != nil)
   101  
   102  	// we should only have one newly generated key
   103  	allKeys = notaryRepo.GetCryptoService().ListAllKeys()
   104  	assert.Check(t, is.Len(allKeys, 1))
   105  	assert.Check(t, notaryRepo.GetCryptoService().GetKey(rootKeyB.ID()) != nil)
   106  
   107  	// The key we retrieved should be identical to the one we generated
   108  	assert.Check(t, is.DeepEqual(rootKeyA.Public(), rootKeyB.Public()))
   109  
   110  	// Now also try with a delegation key
   111  	releasesKey, err := getOrGenerateNotaryKey(notaryRepo, trust.ReleasesRole)
   112  	assert.NilError(t, err)
   113  	assert.Check(t, releasesKey != nil)
   114  
   115  	// we should now have two keys
   116  	allKeys = notaryRepo.GetCryptoService().ListAllKeys()
   117  	assert.Check(t, is.Len(allKeys, 2))
   118  	assert.Check(t, notaryRepo.GetCryptoService().GetKey(releasesKey.ID()) != nil)
   119  	// The key we retrieved should be identical to the one we generated
   120  	assert.Check(t, releasesKey != rootKeyA)
   121  	assert.Check(t, releasesKey != rootKeyB)
   122  }
   123  
   124  func TestAddStageSigners(t *testing.T) {
   125  	skip.If(t, runtime.GOOS == "windows", "FIXME: not supported currently")
   126  
   127  	notaryRepo, err := client.NewFileCachedRepository(t.TempDir(), "gun", "https://localhost", nil, passphrase.ConstantRetriever(passwd), trustpinning.TrustPinConfig{})
   128  	assert.NilError(t, err)
   129  
   130  	// stage targets/user
   131  	userRole := data.RoleName("targets/user")
   132  	userKey := data.NewPublicKey("algoA", []byte("a"))
   133  	err = addStagedSigner(notaryRepo, userRole, []data.PublicKey{userKey})
   134  	assert.NilError(t, err)
   135  	// check the changelist for four total changes: two on targets/releases and two on targets/user
   136  	cl, err := notaryRepo.GetChangelist()
   137  	assert.NilError(t, err)
   138  	changeList := cl.List()
   139  	assert.Check(t, is.Len(changeList, 4))
   140  	// ordering is deterministic:
   141  
   142  	// first change is for targets/user key creation
   143  	newSignerKeyChange := changeList[0]
   144  	expectedJSON, err := json.Marshal(&changelist.TUFDelegation{
   145  		NewThreshold: notary.MinThreshold,
   146  		AddKeys:      data.KeyList([]data.PublicKey{userKey}),
   147  	})
   148  	assert.NilError(t, err)
   149  	expectedChange := changelist.NewTUFChange(
   150  		changelist.ActionCreate,
   151  		userRole,
   152  		changelist.TypeTargetsDelegation,
   153  		"", // no path for delegations
   154  		expectedJSON,
   155  	)
   156  	assert.Check(t, is.DeepEqual(expectedChange, newSignerKeyChange))
   157  
   158  	// second change is for targets/user getting all paths
   159  	newSignerPathsChange := changeList[1]
   160  	expectedJSON, err = json.Marshal(&changelist.TUFDelegation{
   161  		AddPaths: []string{""},
   162  	})
   163  	assert.NilError(t, err)
   164  	expectedChange = changelist.NewTUFChange(
   165  		changelist.ActionCreate,
   166  		userRole,
   167  		changelist.TypeTargetsDelegation,
   168  		"", // no path for delegations
   169  		expectedJSON,
   170  	)
   171  	assert.Check(t, is.DeepEqual(expectedChange, newSignerPathsChange))
   172  
   173  	releasesRole := data.RoleName("targets/releases")
   174  
   175  	// third change is for targets/releases key creation
   176  	releasesKeyChange := changeList[2]
   177  	expectedJSON, err = json.Marshal(&changelist.TUFDelegation{
   178  		NewThreshold: notary.MinThreshold,
   179  		AddKeys:      data.KeyList([]data.PublicKey{userKey}),
   180  	})
   181  	assert.NilError(t, err)
   182  	expectedChange = changelist.NewTUFChange(
   183  		changelist.ActionCreate,
   184  		releasesRole,
   185  		changelist.TypeTargetsDelegation,
   186  		"", // no path for delegations
   187  		expectedJSON,
   188  	)
   189  	assert.Check(t, is.DeepEqual(expectedChange, releasesKeyChange))
   190  
   191  	// fourth change is for targets/releases getting all paths
   192  	releasesPathsChange := changeList[3]
   193  	expectedJSON, err = json.Marshal(&changelist.TUFDelegation{
   194  		AddPaths: []string{""},
   195  	})
   196  	assert.NilError(t, err)
   197  	expectedChange = changelist.NewTUFChange(
   198  		changelist.ActionCreate,
   199  		releasesRole,
   200  		changelist.TypeTargetsDelegation,
   201  		"", // no path for delegations
   202  		expectedJSON,
   203  	)
   204  	assert.Check(t, is.DeepEqual(expectedChange, releasesPathsChange))
   205  }
   206  
   207  func TestGetSignedManifestHashAndSize(t *testing.T) {
   208  	notaryRepo, err := client.NewFileCachedRepository(t.TempDir(), "gun", "https://localhost", nil, passphrase.ConstantRetriever(passwd), trustpinning.TrustPinConfig{})
   209  	assert.NilError(t, err)
   210  	_, _, err = getSignedManifestHashAndSize(notaryRepo, "test")
   211  	assert.Error(t, err, "client is offline")
   212  }
   213  
   214  func TestGetReleasedTargetHashAndSize(t *testing.T) {
   215  	oneReleasedTgt := []client.TargetSignedStruct{}
   216  	// make and append 3 non-released signatures on the "unreleased" target
   217  	unreleasedTgt := client.Target{Name: "unreleased", Hashes: data.Hashes{notary.SHA256: []byte("hash")}}
   218  	for _, unreleasedRole := range []string{"targets/a", "targets/b", "targets/c"} {
   219  		oneReleasedTgt = append(oneReleasedTgt, client.TargetSignedStruct{Role: mockDelegationRoleWithName(unreleasedRole), Target: unreleasedTgt})
   220  	}
   221  	_, _, err := getReleasedTargetHashAndSize(oneReleasedTgt, "unreleased")
   222  	assert.Error(t, err, "No valid trust data for unreleased")
   223  	releasedTgt := client.Target{Name: "released", Hashes: data.Hashes{notary.SHA256: []byte("released-hash")}}
   224  	oneReleasedTgt = append(oneReleasedTgt, client.TargetSignedStruct{Role: mockDelegationRoleWithName("targets/releases"), Target: releasedTgt})
   225  	hash, _, _ := getReleasedTargetHashAndSize(oneReleasedTgt, "unreleased")
   226  	assert.Check(t, is.DeepEqual(data.Hashes{notary.SHA256: []byte("released-hash")}, hash))
   227  }
   228  
   229  func TestCreateTarget(t *testing.T) {
   230  	notaryRepo, err := client.NewFileCachedRepository(t.TempDir(), "gun", "https://localhost", nil, passphrase.ConstantRetriever(passwd), trustpinning.TrustPinConfig{})
   231  	assert.NilError(t, err)
   232  	_, err = createTarget(notaryRepo, "")
   233  	assert.Error(t, err, "no tag specified")
   234  	_, err = createTarget(notaryRepo, "1")
   235  	assert.Error(t, err, "client is offline")
   236  }
   237  
   238  func TestGetExistingSignatureInfoForReleasedTag(t *testing.T) {
   239  	notaryRepo, err := client.NewFileCachedRepository(t.TempDir(), "gun", "https://localhost", nil, passphrase.ConstantRetriever(passwd), trustpinning.TrustPinConfig{})
   240  	assert.NilError(t, err)
   241  	_, err = getExistingSignatureInfoForReleasedTag(notaryRepo, "test")
   242  	assert.Error(t, err, "client is offline")
   243  }
   244  
   245  func TestPrettyPrintExistingSignatureInfo(t *testing.T) {
   246  	buf := bytes.NewBuffer(nil)
   247  	signers := []string{"Bob", "Alice", "Carol"}
   248  	existingSig := trustTagRow{trustTagKey{"tagName", "abc123"}, signers}
   249  	prettyPrintExistingSignatureInfo(buf, existingSig)
   250  
   251  	assert.Check(t, is.Contains(buf.String(), "Existing signatures for tag tagName digest abc123 from:\nAlice, Bob, Carol"))
   252  }
   253  
   254  func TestSignCommandChangeListIsCleanedOnError(t *testing.T) {
   255  	tmpDir := t.TempDir()
   256  
   257  	config.SetDir(tmpDir)
   258  	cli := test.NewFakeCli(&fakeClient{})
   259  	cli.SetNotaryClient(notaryfake.GetLoadedNotaryRepository)
   260  	cmd := newSignCommand(cli)
   261  	cmd.SetArgs([]string{"ubuntu:latest"})
   262  	cmd.SetOut(io.Discard)
   263  
   264  	err := cmd.Execute()
   265  	assert.Assert(t, err != nil)
   266  
   267  	notaryRepo, err := client.NewFileCachedRepository(tmpDir, "docker.io/library/ubuntu", "https://localhost", nil, passphrase.ConstantRetriever(passwd), trustpinning.TrustPinConfig{})
   268  	assert.NilError(t, err)
   269  	cl, err := notaryRepo.GetChangelist()
   270  	assert.NilError(t, err)
   271  	assert.Check(t, is.Equal(len(cl.List()), 0))
   272  }
   273  
   274  func TestSignCommandLocalFlag(t *testing.T) {
   275  	cli := test.NewFakeCli(&fakeClient{})
   276  	cli.SetNotaryClient(notaryfake.GetEmptyTargetsNotaryRepository)
   277  	cmd := newSignCommand(cli)
   278  	cmd.SetArgs([]string{"--local", "reg-name.io/image:red"})
   279  	cmd.SetOut(io.Discard)
   280  	assert.ErrorContains(t, cmd.Execute(), "error contacting notary server: dial tcp: lookup reg-name.io")
   281  }