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 }