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 }