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  }