github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/swarmkit/manager/controlapi/secret_test.go (about) 1 package controlapi 2 3 import ( 4 "context" 5 "fmt" 6 "strings" 7 "testing" 8 9 "github.com/docker/swarmkit/api" 10 "github.com/docker/swarmkit/manager/state/store" 11 "github.com/docker/swarmkit/testutils" 12 "github.com/stretchr/testify/assert" 13 "google.golang.org/grpc/codes" 14 ) 15 16 func createSecretSpec(name string, data []byte, labels map[string]string) *api.SecretSpec { 17 return &api.SecretSpec{ 18 Annotations: api.Annotations{Name: name, Labels: labels}, 19 Data: data, 20 } 21 } 22 23 func TestValidateSecretSpec(t *testing.T) { 24 type BadServiceSpec struct { 25 spec *api.ServiceSpec 26 c codes.Code 27 } 28 29 for _, badName := range []string{ 30 "", 31 ".", 32 "-", 33 "_", 34 ".name", 35 "name.", 36 "-name", 37 "name-", 38 "_name", 39 "name_", 40 "/a", 41 "a/", 42 "a/b", 43 "..", 44 "../a", 45 "a/..", 46 "withexclamation!", 47 "with space", 48 "with\nnewline", 49 "with@splat", 50 "with:colon", 51 "with;semicolon", 52 "snowman☃", 53 strings.Repeat("a", 65), 54 } { 55 err := validateSecretSpec(createSecretSpec(badName, []byte("valid secret"), nil)) 56 assert.Error(t, err) 57 assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err), testutils.ErrorDesc(err)) 58 } 59 60 for _, badSpec := range []*api.SecretSpec{ 61 nil, 62 createSecretSpec("validName", nil, nil), 63 } { 64 err := validateSecretSpec(badSpec) 65 assert.Error(t, err) 66 assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err), testutils.ErrorDesc(err)) 67 } 68 69 for _, goodName := range []string{ 70 "0", 71 "a", 72 "A", 73 "name-with--dashes", 74 "name.with..dots", 75 "name_with__underscores", 76 "name.with-all_special", 77 "02624name035with1699numbers015125", 78 strings.Repeat("a", 64), 79 } { 80 err := validateSecretSpec(createSecretSpec(goodName, []byte("valid secret"), nil)) 81 assert.NoError(t, err) 82 } 83 84 for _, good := range []*api.SecretSpec{ 85 createSecretSpec("validName", []byte("☃\n\t\r\x00 dg09236l;kajdgaj5%#9836[Q@!$]"), nil), 86 createSecretSpec("validName", []byte("valid secret"), nil), 87 createSecretSpec("createName", make([]byte, 1), nil), // 1 byte 88 } { 89 err := validateSecretSpec(good) 90 assert.NoError(t, err) 91 } 92 93 // Ensure secret driver has a name 94 spec := createSecretSpec("secret-driver", make([]byte, 1), nil) 95 spec.Driver = &api.Driver{} 96 err := validateSecretSpec(spec) 97 assert.Error(t, err) 98 assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err), testutils.ErrorDesc(err)) 99 spec.Driver.Name = "secret-driver" 100 err = validateSecretSpec(spec) 101 assert.NoError(t, err) 102 } 103 104 func TestCreateSecret(t *testing.T) { 105 ts := newTestServer(t) 106 defer ts.Stop() 107 108 // ---- creating a secret with an invalid spec fails, thus checking that CreateSecret validates the spec ---- 109 _, err := ts.Client.CreateSecret(context.Background(), &api.CreateSecretRequest{Spec: createSecretSpec("", nil, nil)}) 110 assert.Error(t, err) 111 assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err), testutils.ErrorDesc(err)) 112 113 // ---- creating a secret with a valid spec succeeds, and returns a secret that reflects the secret in the store 114 // exactly, but without the private data ---- 115 data := []byte("secret") 116 creationSpec := createSecretSpec("name", data, nil) 117 validSpecRequest := api.CreateSecretRequest{Spec: creationSpec} 118 119 resp, err := ts.Client.CreateSecret(context.Background(), &validSpecRequest) 120 assert.NoError(t, err) 121 assert.NotNil(t, resp) 122 assert.NotNil(t, resp.Secret) 123 124 // the data should be empty/omitted 125 assert.Equal(t, *createSecretSpec("name", nil, nil), resp.Secret.Spec) 126 127 // for sanity, check that the stored secret still has the secret data 128 var storedSecret *api.Secret 129 ts.Store.View(func(tx store.ReadTx) { 130 storedSecret = store.GetSecret(tx, resp.Secret.ID) 131 }) 132 assert.NotNil(t, storedSecret) 133 assert.Equal(t, data, storedSecret.Spec.Data) 134 135 // ---- creating a secret with the same name, even if it's the exact same spec, fails due to a name conflict ---- 136 _, err = ts.Client.CreateSecret(context.Background(), &validSpecRequest) 137 assert.Error(t, err) 138 assert.Equal(t, codes.AlreadyExists, testutils.ErrorCode(err), testutils.ErrorDesc(err)) 139 } 140 141 func TestGetSecret(t *testing.T) { 142 ts := newTestServer(t) 143 defer ts.Stop() 144 145 // ---- getting a secret without providing an ID results in an InvalidArgument ---- 146 _, err := ts.Client.GetSecret(context.Background(), &api.GetSecretRequest{}) 147 assert.Error(t, err) 148 assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err), testutils.ErrorDesc(err)) 149 150 // ---- getting a non-existent secret fails with NotFound ---- 151 _, err = ts.Client.GetSecret(context.Background(), &api.GetSecretRequest{SecretID: "12345"}) 152 assert.Error(t, err) 153 assert.Equal(t, codes.NotFound, testutils.ErrorCode(err), testutils.ErrorDesc(err)) 154 155 // ---- getting an existing secret returns the secret with all the private data cleaned ---- 156 secret := secretFromSecretSpec(createSecretSpec("name", []byte("data"), nil)) 157 err = ts.Store.Update(func(tx store.Tx) error { 158 return store.CreateSecret(tx, secret) 159 }) 160 assert.NoError(t, err) 161 162 resp, err := ts.Client.GetSecret(context.Background(), &api.GetSecretRequest{SecretID: secret.ID}) 163 assert.NoError(t, err) 164 assert.NotNil(t, resp) 165 assert.NotNil(t, resp.Secret) 166 167 // the data should be empty/omitted 168 assert.NotEqual(t, secret, resp.Secret) 169 secret.Spec.Data = nil 170 assert.Equal(t, secret, resp.Secret) 171 } 172 173 func TestUpdateSecret(t *testing.T) { 174 ts := newTestServer(t) 175 defer ts.Stop() 176 177 // Add a secret to the store to update 178 secret := secretFromSecretSpec(createSecretSpec("name", []byte("data"), map[string]string{"mod2": "0", "mod4": "0"})) 179 err := ts.Store.Update(func(tx store.Tx) error { 180 return store.CreateSecret(tx, secret) 181 }) 182 assert.NoError(t, err) 183 184 // updating a secret without providing an ID results in an InvalidArgument 185 _, err = ts.Client.UpdateSecret(context.Background(), &api.UpdateSecretRequest{}) 186 assert.Error(t, err) 187 assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err), testutils.ErrorDesc(err)) 188 189 // getting a non-existent secret fails with NotFound 190 _, err = ts.Client.UpdateSecret(context.Background(), &api.UpdateSecretRequest{SecretID: "1234adsaa", SecretVersion: &api.Version{Index: 1}}) 191 assert.Error(t, err) 192 assert.Equal(t, codes.NotFound, testutils.ErrorCode(err), testutils.ErrorDesc(err)) 193 194 // updating an existing secret's data returns an error 195 secret.Spec.Data = []byte{1} 196 resp, err := ts.Client.UpdateSecret(context.Background(), &api.UpdateSecretRequest{ 197 SecretID: secret.ID, 198 Spec: &secret.Spec, 199 SecretVersion: &secret.Meta.Version, 200 }) 201 assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err), testutils.ErrorDesc(err)) 202 203 // updating an existing secret's Name returns an error 204 secret.Spec.Data = nil 205 secret.Spec.Annotations.Name = "AnotherName" 206 resp, err = ts.Client.UpdateSecret(context.Background(), &api.UpdateSecretRequest{ 207 SecretID: secret.ID, 208 Spec: &secret.Spec, 209 SecretVersion: &secret.Meta.Version, 210 }) 211 assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err), testutils.ErrorDesc(err)) 212 213 // updating the secret with the original spec succeeds 214 secret.Spec.Data = []byte("data") 215 secret.Spec.Annotations.Name = "name" 216 assert.NotNil(t, secret.Spec.Data) 217 resp, err = ts.Client.UpdateSecret(context.Background(), &api.UpdateSecretRequest{ 218 SecretID: secret.ID, 219 Spec: &secret.Spec, 220 SecretVersion: &secret.Meta.Version, 221 }) 222 assert.NoError(t, err) 223 assert.NotNil(t, resp) 224 assert.NotNil(t, resp.Secret) 225 226 // updating an existing secret's labels returns the secret with all the private data cleaned 227 newLabels := map[string]string{"mod2": "0", "mod4": "0", "mod6": "0"} 228 secret.Spec.Annotations.Labels = newLabels 229 secret.Spec.Data = nil 230 resp, err = ts.Client.UpdateSecret(context.Background(), &api.UpdateSecretRequest{ 231 SecretID: secret.ID, 232 Spec: &secret.Spec, 233 SecretVersion: &resp.Secret.Meta.Version, 234 }) 235 assert.NoError(t, err) 236 assert.NotNil(t, resp) 237 assert.NotNil(t, resp.Secret) 238 assert.Nil(t, resp.Secret.Spec.Data) 239 assert.Equal(t, resp.Secret.Spec.Annotations.Labels, newLabels) 240 241 // updating a secret with nil data and correct name succeeds again 242 secret.Spec.Data = nil 243 secret.Spec.Annotations.Name = "name" 244 resp, err = ts.Client.UpdateSecret(context.Background(), &api.UpdateSecretRequest{ 245 SecretID: secret.ID, 246 Spec: &secret.Spec, 247 SecretVersion: &resp.Secret.Meta.Version, 248 }) 249 assert.NoError(t, err) 250 assert.NotNil(t, resp) 251 assert.NotNil(t, resp.Secret) 252 assert.Nil(t, resp.Secret.Spec.Data) 253 assert.Equal(t, resp.Secret.Spec.Annotations.Labels, newLabels) 254 } 255 256 func TestRemoveUnusedSecret(t *testing.T) { 257 ts := newTestServer(t) 258 defer ts.Stop() 259 260 // removing a secret without providing an ID results in an InvalidArgument 261 _, err := ts.Client.RemoveSecret(context.Background(), &api.RemoveSecretRequest{}) 262 assert.Error(t, err) 263 assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err), testutils.ErrorDesc(err)) 264 265 // removing a secret that exists succeeds 266 secret := secretFromSecretSpec(createSecretSpec("name", []byte("data"), nil)) 267 err = ts.Store.Update(func(tx store.Tx) error { 268 return store.CreateSecret(tx, secret) 269 }) 270 assert.NoError(t, err) 271 272 resp, err := ts.Client.RemoveSecret(context.Background(), &api.RemoveSecretRequest{SecretID: secret.ID}) 273 assert.NoError(t, err) 274 assert.Equal(t, api.RemoveSecretResponse{}, *resp) 275 276 // ---- it was really removed because attempting to remove it again fails with a NotFound ---- 277 _, err = ts.Client.RemoveSecret(context.Background(), &api.RemoveSecretRequest{SecretID: secret.ID}) 278 assert.Error(t, err) 279 assert.Equal(t, codes.NotFound, testutils.ErrorCode(err), testutils.ErrorDesc(err)) 280 281 } 282 283 func TestRemoveUsedSecret(t *testing.T) { 284 ts := newTestServer(t) 285 defer ts.Stop() 286 287 // Create two secrets 288 data := []byte("secret") 289 creationSpec := createSecretSpec("secretID1", data, nil) 290 resp, err := ts.Client.CreateSecret(context.Background(), &api.CreateSecretRequest{Spec: creationSpec}) 291 assert.NoError(t, err) 292 creationSpec2 := createSecretSpec("secretID2", data, nil) 293 resp2, err := ts.Client.CreateSecret(context.Background(), &api.CreateSecretRequest{Spec: creationSpec2}) 294 assert.NoError(t, err) 295 296 // Create a service that uses a secret 297 service := createSpec("service1", "image", 1) 298 secretRefs := []*api.SecretReference{ 299 { 300 SecretName: resp.Secret.Spec.Annotations.Name, 301 SecretID: resp.Secret.ID, 302 Target: &api.SecretReference_File{ 303 File: &api.FileTarget{ 304 Name: "target.txt", 305 }, 306 }, 307 }, 308 } 309 service.Task.GetContainer().Secrets = secretRefs 310 _, err = ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: service}) 311 assert.NoError(t, err) 312 313 service2 := createSpec("service2", "image", 1) 314 service2.Task.GetContainer().Secrets = secretRefs 315 _, err = ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: service2}) 316 assert.NoError(t, err) 317 318 // removing a secret that exists but is in use fails 319 _, err = ts.Client.RemoveSecret(context.Background(), &api.RemoveSecretRequest{SecretID: resp.Secret.ID}) 320 assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err), testutils.ErrorDesc(err)) 321 assert.Regexp(t, "service[1-2], service[1-2]", testutils.ErrorDesc(err)) 322 323 // removing a secret that exists but is not in use succeeds 324 _, err = ts.Client.RemoveSecret(context.Background(), &api.RemoveSecretRequest{SecretID: resp2.Secret.ID}) 325 assert.NoError(t, err) 326 327 // it was really removed because attempting to remove it again fails with a NotFound 328 _, err = ts.Client.RemoveSecret(context.Background(), &api.RemoveSecretRequest{SecretID: resp2.Secret.ID}) 329 assert.Error(t, err) 330 assert.Equal(t, codes.NotFound, testutils.ErrorCode(err), testutils.ErrorDesc(err)) 331 } 332 333 func TestListSecrets(t *testing.T) { 334 s := newTestServer(t) 335 336 listSecrets := func(req *api.ListSecretsRequest) map[string]*api.Secret { 337 resp, err := s.Client.ListSecrets(context.Background(), req) 338 assert.NoError(t, err) 339 assert.NotNil(t, resp) 340 341 byName := make(map[string]*api.Secret) 342 for _, secret := range resp.Secrets { 343 byName[secret.Spec.Annotations.Name] = secret 344 } 345 return byName 346 } 347 348 // ---- Listing secrets when there are no secrets returns an empty list but no error ---- 349 result := listSecrets(&api.ListSecretsRequest{}) 350 assert.Len(t, result, 0) 351 352 // ---- Create a bunch of secrets in the store so we can test filtering ---- 353 allListableNames := []string{"aaa", "aab", "abc", "bbb", "bac", "bbc", "ccc", "cac", "cbc", "ddd"} 354 secretNamesToID := make(map[string]string) 355 for i, secretName := range allListableNames { 356 secret := secretFromSecretSpec(createSecretSpec(secretName, []byte("secret"), map[string]string{ 357 "mod2": fmt.Sprintf("%d", i%2), 358 "mod4": fmt.Sprintf("%d", i%4), 359 })) 360 err := s.Store.Update(func(tx store.Tx) error { 361 return store.CreateSecret(tx, secret) 362 }) 363 assert.NoError(t, err) 364 secretNamesToID[secretName] = secret.ID 365 } 366 // also add an internal secret to show that it's never returned 367 internalSecret := secretFromSecretSpec(createSecretSpec("internal", []byte("secret"), map[string]string{ 368 "mod2": "1", 369 "mod4": "1", 370 })) 371 internalSecret.Internal = true 372 err := s.Store.Update(func(tx store.Tx) error { 373 return store.CreateSecret(tx, internalSecret) 374 }) 375 assert.NoError(t, err) 376 secretNamesToID["internal"] = internalSecret.ID 377 378 // ---- build up our list of expectations for what secrets get filtered ---- 379 380 type listTestCase struct { 381 desc string 382 expected []string 383 filter *api.ListSecretsRequest_Filters 384 } 385 386 listSecretTestCases := []listTestCase{ 387 { 388 desc: "no filter: all the available secrets are returned", 389 expected: allListableNames, 390 filter: nil, 391 }, 392 { 393 desc: "searching for something that doesn't match returns an empty list", 394 expected: nil, 395 filter: &api.ListSecretsRequest_Filters{Names: []string{"aa", "internal"}}, 396 }, 397 { 398 desc: "multiple name filters are or-ed together", 399 expected: []string{"aaa", "bbb", "ccc"}, 400 filter: &api.ListSecretsRequest_Filters{Names: []string{"aaa", "bbb", "ccc", "internal"}}, 401 }, 402 { 403 desc: "multiple name prefix filters are or-ed together", 404 expected: []string{"aaa", "aab", "bbb", "bbc"}, 405 filter: &api.ListSecretsRequest_Filters{NamePrefixes: []string{"aa", "bb", "int"}}, 406 }, 407 { 408 desc: "multiple ID prefix filters are or-ed together", 409 expected: []string{"aaa", "bbb"}, 410 filter: &api.ListSecretsRequest_Filters{IDPrefixes: []string{ 411 secretNamesToID["aaa"], secretNamesToID["bbb"], secretNamesToID["internal"]}, 412 }, 413 }, 414 { 415 desc: "name prefix, name, and ID prefix filters are or-ed together", 416 expected: []string{"aaa", "aab", "bbb", "bbc", "ccc", "ddd"}, 417 filter: &api.ListSecretsRequest_Filters{ 418 Names: []string{"aaa", "ccc", "internal"}, 419 NamePrefixes: []string{"aa", "bb", "int"}, 420 IDPrefixes: []string{secretNamesToID["aaa"], secretNamesToID["ddd"], secretNamesToID["internal"]}, 421 }, 422 }, 423 { 424 desc: "all labels in the label map must be matched", 425 expected: []string{allListableNames[0], allListableNames[4], allListableNames[8]}, 426 filter: &api.ListSecretsRequest_Filters{ 427 Labels: map[string]string{ 428 "mod2": "0", 429 "mod4": "0", 430 }, 431 }, 432 }, 433 { 434 desc: "name prefix, name, and ID prefix filters are or-ed together, but the results must match all labels in the label map", 435 // + indicates that these would be selected with the name/id/prefix filtering, and 0/1 at the end indicate the mod2 value: 436 // +"aaa"0, +"aab"1, "abc"0, +"bbb"1, "bac"0, +"bbc"1, +"ccc"0, "cac"1, "cbc"0, +"ddd"1 437 expected: []string{"aaa", "ccc"}, 438 filter: &api.ListSecretsRequest_Filters{ 439 Names: []string{"aaa", "ccc", "internal"}, 440 NamePrefixes: []string{"aa", "bb", "int"}, 441 IDPrefixes: []string{secretNamesToID["aaa"], secretNamesToID["ddd"], secretNamesToID["internal"]}, 442 Labels: map[string]string{ 443 "mod2": "0", 444 }, 445 }, 446 }, 447 } 448 449 // ---- run the filter tests ---- 450 451 for _, expectation := range listSecretTestCases { 452 result := listSecrets(&api.ListSecretsRequest{Filters: expectation.filter}) 453 assert.Len(t, result, len(expectation.expected), expectation.desc) 454 for _, name := range expectation.expected { 455 assert.Contains(t, result, name, expectation.desc) 456 assert.NotNil(t, result[name], expectation.desc) 457 assert.Equal(t, secretNamesToID[name], result[name].ID, expectation.desc) 458 } 459 } 460 }