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