github.com/zntrio/harp/v2@v2.0.9/pkg/vault/kv/v2_service_test.go (about)

     1  // Licensed to Elasticsearch B.V. under one or more contributor
     2  // license agreements. See the NOTICE file distributed with
     3  // this work for additional information regarding copyright
     4  // ownership. Elasticsearch B.V. licenses this file to you under
     5  // the Apache License, Version 2.0 (the "License"); you may
     6  // not use this file except in compliance with the License.
     7  // You may obtain a copy of the License at
     8  //
     9  //     http://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  // Unless required by applicable law or agreed to in writing,
    12  // software distributed under the License is distributed on an
    13  // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    14  // KIND, either express or implied.  See the License for the
    15  // specific language governing permissions and limitations
    16  // under the License.
    17  
    18  package kv
    19  
    20  import (
    21  	"context"
    22  	"fmt"
    23  	"reflect"
    24  	"testing"
    25  
    26  	"github.com/dchest/uniuri"
    27  	"github.com/golang/mock/gomock"
    28  	vaultApi "github.com/hashicorp/vault/api"
    29  
    30  	"github.com/zntrio/harp/v2/pkg/vault/logical"
    31  )
    32  
    33  func Test_KVV2_List(t *testing.T) {
    34  	type args struct {
    35  		ctx  context.Context
    36  		path string
    37  	}
    38  	tests := []struct {
    39  		name    string
    40  		prepare func(*logical.MockLogical)
    41  		args    args
    42  		want    []string
    43  		wantErr bool
    44  	}{
    45  		{
    46  			name: "blank",
    47  			args: args{
    48  				ctx:  context.Background(),
    49  				path: "",
    50  			},
    51  			wantErr: true,
    52  		},
    53  		{
    54  			name: "query error",
    55  			args: args{
    56  				ctx:  context.Background(),
    57  				path: "secrets/application/foo",
    58  			},
    59  			prepare: func(logical *logical.MockLogical) {
    60  				logical.EXPECT().List("secrets/metadata/application/foo").Return(&vaultApi.Secret{}, fmt.Errorf("foo"))
    61  			},
    62  			wantErr: true,
    63  		},
    64  		{
    65  			name: "nil secret",
    66  			args: args{
    67  				ctx:  context.Background(),
    68  				path: "secrets/application/foo",
    69  			},
    70  			prepare: func(logical *logical.MockLogical) {
    71  				logical.EXPECT().List("secrets/metadata/application/foo").Return(nil, nil)
    72  			},
    73  			wantErr: false,
    74  		},
    75  		{
    76  			name: "nil secret data",
    77  			args: args{
    78  				ctx:  context.Background(),
    79  				path: "secrets/application/foo",
    80  			},
    81  			prepare: func(logical *logical.MockLogical) {
    82  				logical.EXPECT().List("secrets/metadata/application/foo").Return(&vaultApi.Secret{
    83  					Data: nil,
    84  				}, nil)
    85  			},
    86  			wantErr: true,
    87  		},
    88  		{
    89  			name: "missing keys data",
    90  			args: args{
    91  				ctx:  context.Background(),
    92  				path: "secrets/application/foo",
    93  			},
    94  			prepare: func(logical *logical.MockLogical) {
    95  				logical.EXPECT().List("secrets/metadata/application/foo").Return(&vaultApi.Secret{
    96  					Data: SecretData{},
    97  				}, nil)
    98  			},
    99  			wantErr: true,
   100  		},
   101  		{
   102  			name: "invalid keys type",
   103  			args: args{
   104  				ctx:  context.Background(),
   105  				path: "secrets/application/foo",
   106  			},
   107  			prepare: func(logical *logical.MockLogical) {
   108  				logical.EXPECT().List("secrets/metadata/application/foo").Return(&vaultApi.Secret{
   109  					Data: SecretData{
   110  						"keys": 1,
   111  					},
   112  				}, nil)
   113  			},
   114  			wantErr: true,
   115  		},
   116  		{
   117  			name: "unclean",
   118  			args: args{
   119  				ctx:  context.Background(),
   120  				path: "    /secrets/application/foo/   ",
   121  			},
   122  			prepare: func(logical *logical.MockLogical) {
   123  				logical.EXPECT().List("secrets/metadata/application/foo").Return(&vaultApi.Secret{
   124  					Data: SecretData{
   125  						"keys": []interface{}{},
   126  					},
   127  				}, nil)
   128  			},
   129  			wantErr: false,
   130  			want:    []string{},
   131  		},
   132  		{
   133  			name: "valid",
   134  			args: args{
   135  				ctx:  context.Background(),
   136  				path: "secrets/application/foo",
   137  			},
   138  			prepare: func(logical *logical.MockLogical) {
   139  				logical.EXPECT().List("secrets/metadata/application/foo").Return(&vaultApi.Secret{
   140  					Data: SecretData{
   141  						"keys": []interface{}{"secrets/application/foo/secret-1", "secrets/application/foo/secret-2"},
   142  					},
   143  				}, nil)
   144  			},
   145  			wantErr: false,
   146  			want: []string{
   147  				"secrets/application/foo/secret-1",
   148  				"secrets/application/foo/secret-2",
   149  			},
   150  		},
   151  	}
   152  	for _, tt := range tests {
   153  		t.Run(tt.name, func(t *testing.T) {
   154  			ctrl := gomock.NewController(t)
   155  			defer ctrl.Finish()
   156  
   157  			// Arm mocks
   158  			logicalMock := logical.NewMockLogical(ctrl)
   159  
   160  			// Prepare mocks
   161  			if tt.prepare != nil {
   162  				tt.prepare(logicalMock)
   163  			}
   164  
   165  			// Service
   166  			underTest := V2(logicalMock, "secrets/", true)
   167  			got, err := underTest.List(tt.args.ctx, tt.args.path)
   168  			if (err != nil) != tt.wantErr {
   169  				t.Errorf("vaultClient.List() error = %v, wantErr %v", err, tt.wantErr)
   170  				return
   171  			}
   172  			if !tt.wantErr && !reflect.DeepEqual(got, tt.want) {
   173  				t.Errorf("vaultClient.List() = %v, want %v", got, tt.want)
   174  			}
   175  		})
   176  	}
   177  }
   178  
   179  func Test_KVV2_Read(t *testing.T) {
   180  	type args struct {
   181  		ctx  context.Context
   182  		path string
   183  	}
   184  	tests := []struct {
   185  		name     string
   186  		prepare  func(*logical.MockLogical)
   187  		args     args
   188  		wantData SecretData
   189  		wantMeta SecretMetadata
   190  		wantErr  bool
   191  	}{
   192  		{
   193  			name: "nil",
   194  			args: args{
   195  				ctx:  context.Background(),
   196  				path: "",
   197  			},
   198  			wantErr: true,
   199  		},
   200  		{
   201  			name: "query error",
   202  			args: args{
   203  				ctx:  context.Background(),
   204  				path: "application/foo",
   205  			},
   206  			prepare: func(logical *logical.MockLogical) {
   207  				logical.EXPECT().Read("secrets/data/application/foo").Return(&vaultApi.Secret{}, fmt.Errorf("foo"))
   208  			},
   209  			wantErr: true,
   210  		},
   211  		{
   212  			name: "nil secret",
   213  			args: args{
   214  				ctx:  context.Background(),
   215  				path: "application/foo",
   216  			},
   217  			prepare: func(logical *logical.MockLogical) {
   218  				logical.EXPECT().Read("secrets/data/application/foo").Return(nil, nil)
   219  			},
   220  			wantErr: true,
   221  		},
   222  		{
   223  			name: "nil secret data",
   224  			args: args{
   225  				ctx:  context.Background(),
   226  				path: "application/foo",
   227  			},
   228  			prepare: func(logical *logical.MockLogical) {
   229  				logical.EXPECT().Read("secrets/data/application/foo").Return(&vaultApi.Secret{
   230  					Data: nil,
   231  				}, nil)
   232  			},
   233  			wantErr: true,
   234  		},
   235  		{
   236  			name: "no secret KVv2 data",
   237  			args: args{
   238  				ctx:  context.Background(),
   239  				path: "application/foo",
   240  			},
   241  			prepare: func(logical *logical.MockLogical) {
   242  				logical.EXPECT().Read("secrets/data/application/foo").Return(&vaultApi.Secret{
   243  					Data: map[string]interface{}{},
   244  				}, nil)
   245  			},
   246  			wantErr: true,
   247  		},
   248  		{
   249  			name: "nil secret KVv2 data",
   250  			args: args{
   251  				ctx:  context.Background(),
   252  				path: "application/foo",
   253  			},
   254  			prepare: func(logical *logical.MockLogical) {
   255  				logical.EXPECT().Read("secrets/data/application/foo").Return(&vaultApi.Secret{
   256  					Data: map[string]interface{}{
   257  						"data": nil,
   258  					},
   259  				}, nil)
   260  			},
   261  			wantErr: true,
   262  		},
   263  		{
   264  			name: "valid",
   265  			args: args{
   266  				ctx:  context.Background(),
   267  				path: "application/foo",
   268  			},
   269  			prepare: func(logical *logical.MockLogical) {
   270  				logical.EXPECT().Read("secrets/data/application/foo").Return(&vaultApi.Secret{
   271  					Data: map[string]interface{}{
   272  						"data": map[string]interface{}{
   273  							"key": "value",
   274  						},
   275  						"metadata": map[string]interface{}{},
   276  					},
   277  				}, nil)
   278  			},
   279  			wantErr: false,
   280  			wantData: map[string]interface{}{
   281  				"key": "value",
   282  			},
   283  			wantMeta: map[string]interface{}{},
   284  		},
   285  	}
   286  	for _, tt := range tests {
   287  		t.Run(tt.name, func(t *testing.T) {
   288  			ctrl := gomock.NewController(t)
   289  			defer ctrl.Finish()
   290  
   291  			// Arm mocks
   292  			logicalMock := logical.NewMockLogical(ctrl)
   293  
   294  			// Prepare mocks
   295  			if tt.prepare != nil {
   296  				tt.prepare(logicalMock)
   297  			}
   298  
   299  			// Service
   300  			underTest := V2(logicalMock, "secrets/", false)
   301  			gotData, gotMeta, err := underTest.Read(tt.args.ctx, tt.args.path)
   302  			if (err != nil) != tt.wantErr {
   303  				t.Errorf("vaultClient.Read() error = %v, wantErr %v", err, tt.wantErr)
   304  				return
   305  			}
   306  			if !tt.wantErr && !reflect.DeepEqual(gotData, tt.wantData) {
   307  				t.Errorf("vaultClient.Read() = %v, want %v", gotData, tt.wantData)
   308  			}
   309  			if !tt.wantErr && !reflect.DeepEqual(gotMeta, tt.wantMeta) {
   310  				t.Errorf("vaultClient.Read() = %v, want %v", gotMeta, tt.wantMeta)
   311  			}
   312  		})
   313  	}
   314  }
   315  
   316  func Test_KVV2_WriteData(t *testing.T) {
   317  	type args struct {
   318  		ctx  context.Context
   319  		path string
   320  		data map[string]interface{}
   321  	}
   322  	tests := []struct {
   323  		name    string
   324  		prepare func(*logical.MockLogical)
   325  		args    args
   326  		wantErr bool
   327  	}{
   328  		{
   329  			name: "blank",
   330  			args: args{
   331  				ctx:  context.Background(),
   332  				path: "",
   333  			},
   334  			wantErr: true,
   335  		},
   336  		{
   337  			name: "query error",
   338  			args: args{
   339  				ctx:  context.Background(),
   340  				path: "application/foo",
   341  			},
   342  			prepare: func(logical *logical.MockLogical) {
   343  				logical.EXPECT().Write("secrets/data/application/foo", gomock.Any()).Return(&vaultApi.Secret{}, fmt.Errorf("foo"))
   344  			},
   345  			wantErr: true,
   346  		},
   347  		{
   348  			name: "valid",
   349  			args: args{
   350  				ctx:  context.Background(),
   351  				path: "application/foo",
   352  			},
   353  			prepare: func(logical *logical.MockLogical) {
   354  				logical.EXPECT().Write("secrets/data/application/foo", gomock.Any()).Return(&vaultApi.Secret{
   355  					Data: SecretData{
   356  						"key": "value",
   357  					},
   358  				}, nil)
   359  			},
   360  			wantErr: false,
   361  		},
   362  	}
   363  	for _, tt := range tests {
   364  		t.Run(tt.name, func(t *testing.T) {
   365  			ctrl := gomock.NewController(t)
   366  			defer ctrl.Finish()
   367  
   368  			// Arm mocks
   369  			logicalMock := logical.NewMockLogical(ctrl)
   370  
   371  			// Prepare mocks
   372  			if tt.prepare != nil {
   373  				tt.prepare(logicalMock)
   374  			}
   375  
   376  			// Service
   377  			underTest := V2(logicalMock, "secrets/", false)
   378  			err := underTest.Write(tt.args.ctx, tt.args.path, tt.args.data)
   379  			if (err != nil) != tt.wantErr {
   380  				t.Errorf("vaultClient.Write() error = %v, wantErr %v", err, tt.wantErr)
   381  				return
   382  			}
   383  		})
   384  	}
   385  }
   386  
   387  func Test_KVV2_WriteWithMeta(t *testing.T) {
   388  	// Fixtures
   389  	tooManyKeysMeta := map[string]interface{}{}
   390  	for i := 0; i <= CustomMetadataKeyLimit; i++ {
   391  		tooManyKeysMeta[fmt.Sprintf("key-%d", i)] = ""
   392  	}
   393  
   394  	type args struct {
   395  		ctx  context.Context
   396  		path string
   397  		data map[string]interface{}
   398  		meta map[string]interface{}
   399  	}
   400  	tests := []struct {
   401  		name    string
   402  		prepare func(*logical.MockLogical)
   403  		args    args
   404  		wantErr bool
   405  	}{
   406  		{
   407  			name: "blank",
   408  			args: args{
   409  				ctx:  context.Background(),
   410  				path: "",
   411  			},
   412  			wantErr: true,
   413  		},
   414  		{
   415  			name: "metadata too many keys",
   416  			args: args{
   417  				ctx:  context.Background(),
   418  				path: "application/foo",
   419  				meta: tooManyKeysMeta,
   420  			},
   421  			wantErr: true,
   422  		},
   423  		{
   424  			name: "metadata key too large",
   425  			args: args{
   426  				ctx:  context.Background(),
   427  				path: "application/foo",
   428  				meta: map[string]interface{}{
   429  					uniuri.NewLen(CustomMetadataKeySizeLimit + 1): "",
   430  				},
   431  			},
   432  			wantErr: true,
   433  		},
   434  		{
   435  			name: "metadata value not a string",
   436  			args: args{
   437  				ctx:  context.Background(),
   438  				path: "application/foo",
   439  				meta: map[string]interface{}{
   440  					"test": make(chan struct{}),
   441  				},
   442  			},
   443  			wantErr: true,
   444  		},
   445  		{
   446  			name: "metadata value too large",
   447  			args: args{
   448  				ctx:  context.Background(),
   449  				path: "application/foo",
   450  				meta: map[string]interface{}{
   451  					"test": uniuri.NewLen(CustomMetadataValueSizeLimit + 1),
   452  				},
   453  			},
   454  			wantErr: true,
   455  		},
   456  		{
   457  			name: "data write error",
   458  			args: args{
   459  				ctx:  context.Background(),
   460  				path: "application/foo",
   461  				meta: map[string]interface{}{
   462  					"environment": "test",
   463  				},
   464  			},
   465  			prepare: func(logical *logical.MockLogical) {
   466  				logical.EXPECT().Write("secrets/data/application/foo", gomock.Any()).Return(&vaultApi.Secret{}, fmt.Errorf("foo"))
   467  			},
   468  			wantErr: true,
   469  		},
   470  		{
   471  			name: "metadata write error",
   472  			args: args{
   473  				ctx:  context.Background(),
   474  				path: "application/foo",
   475  				meta: map[string]interface{}{
   476  					"environment": "test",
   477  				},
   478  			},
   479  			prepare: func(logical *logical.MockLogical) {
   480  				dataWrite := logical.EXPECT().Write("secrets/data/application/foo", gomock.Any()).Return(&vaultApi.Secret{
   481  					Data: SecretData{
   482  						"key": "value",
   483  					},
   484  				}, nil)
   485  				logical.EXPECT().Write("secrets/metadata/application/foo", gomock.Any()).Return(&vaultApi.Secret{}, fmt.Errorf("foo")).After(dataWrite)
   486  			},
   487  			wantErr: true,
   488  		},
   489  		{
   490  			name: "valid",
   491  			args: args{
   492  				ctx:  context.Background(),
   493  				path: "application/foo",
   494  				meta: map[string]interface{}{
   495  					"environment": "test",
   496  				},
   497  			},
   498  			prepare: func(logical *logical.MockLogical) {
   499  				dataWrite := logical.EXPECT().Write("secrets/data/application/foo", gomock.Any()).Return(&vaultApi.Secret{
   500  					Data: SecretData{
   501  						"key": "value",
   502  					},
   503  				}, nil)
   504  				logical.EXPECT().Write("secrets/metadata/application/foo", gomock.Any()).Return(&vaultApi.Secret{
   505  					Data: SecretData{
   506  						"key": "value",
   507  					},
   508  				}, nil).After(dataWrite)
   509  			},
   510  			wantErr: false,
   511  		},
   512  	}
   513  	for _, tt := range tests {
   514  		t.Run(tt.name, func(t *testing.T) {
   515  			ctrl := gomock.NewController(t)
   516  			defer ctrl.Finish()
   517  
   518  			// Arm mocks
   519  			logicalMock := logical.NewMockLogical(ctrl)
   520  
   521  			// Prepare mocks
   522  			if tt.prepare != nil {
   523  				tt.prepare(logicalMock)
   524  			}
   525  
   526  			// Service
   527  			underTest := V2(logicalMock, "secrets/", true)
   528  			err := underTest.WriteWithMeta(tt.args.ctx, tt.args.path, tt.args.data, tt.args.meta)
   529  			if (err != nil) != tt.wantErr {
   530  				t.Errorf("vaultClient.WriteWithMeta() error = %v, wantErr %v", err, tt.wantErr)
   531  				return
   532  			}
   533  		})
   534  	}
   535  }