github.com/opentofu/opentofu@v1.7.1/internal/encryption/keyprovider/compliancetest/compliance.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package compliancetest 7 8 import ( 9 "bytes" 10 "encoding/json" 11 "errors" 12 "reflect" 13 "testing" 14 15 "github.com/hashicorp/hcl/v2/gohcl" 16 "github.com/opentofu/opentofu/internal/encryption/compliancetest" 17 "github.com/opentofu/opentofu/internal/encryption/config" 18 "github.com/opentofu/opentofu/internal/encryption/keyprovider" 19 ) 20 21 func ComplianceTest[TDescriptor keyprovider.Descriptor, TConfig keyprovider.Config, TMeta keyprovider.KeyMeta, TKeyProvider keyprovider.KeyProvider]( 22 t *testing.T, 23 config TestConfiguration[TDescriptor, TConfig, TMeta, TKeyProvider], 24 ) { 25 var cfg TConfig 26 cfgType := reflect.TypeOf(cfg) 27 if cfgType.Kind() != reflect.Ptr || cfgType.Elem().Kind() != reflect.Struct { 28 compliancetest.Fail(t, "You declared the config type to be %T, but it should be a pointer to a struct. Please fix your call to ComplianceTest().", cfg) 29 } 30 31 var meta TMeta 32 metaType := reflect.TypeOf(cfg) 33 if metaType.Kind() != reflect.Interface { 34 if metaType.Kind() != reflect.Ptr || metaType.Elem().Kind() != reflect.Struct { 35 compliancetest.Log(t, "You declared a metadata type as %T, but it should be a pointer to a struct. Please fix your call to ComplianceTest().", meta) 36 } 37 } else { 38 compliancetest.Log(t, "Metadata type declared as interface{}, assuming the key provider does not need metadata. (This will be validated later.)") 39 } 40 41 t.Run("ID", func(t *testing.T) { 42 complianceTestID(t, config) 43 }) 44 45 t.Run("ConfigStruct", func(t *testing.T) { 46 compliancetest.ConfigStruct[TConfig](t, config.Descriptor.ConfigStruct()) 47 48 t.Run("hcl-parsing", func(t *testing.T) { 49 if config.HCLParseTestCases == nil { 50 compliancetest.Fail(t, "Please provide a map in HCLParseTestCases.") 51 } 52 for name, tc := range config.HCLParseTestCases { 53 tc := tc 54 t.Run(name, func(t *testing.T) { 55 complianceTestHCLParsingTestCase(t, tc, config) 56 }) 57 } 58 }) 59 60 t.Run("config", func(t *testing.T) { 61 if config.ConfigStructTestCases == nil { 62 compliancetest.Fail(t, "Please provide a map in ConfigStructTestCases.") 63 } 64 for name, tc := range config.ConfigStructTestCases { 65 tc := tc 66 t.Run(name, func(t *testing.T) { 67 complianceTestConfigCase[TConfig, TKeyProvider, TMeta](t, tc) 68 }) 69 } 70 }) 71 }) 72 73 t.Run("metadata", func(t *testing.T) { 74 if config.MetadataStructTestCases == nil { 75 compliancetest.Fail(t, "Please provide a map in MetadataStructTestCases.") 76 } 77 for name, tc := range config.MetadataStructTestCases { 78 tc := tc 79 t.Run(name, func(t *testing.T) { 80 complianceTestMetadataTestCase[TConfig, TKeyProvider, TMeta](t, tc) 81 }) 82 } 83 }) 84 85 t.Run("provide", func(t *testing.T) { 86 complianceTestProvide[TDescriptor, TConfig, TKeyProvider, TMeta](t, config) 87 }) 88 89 t.Run("test-completeness", func(t *testing.T) { 90 t.Run("HCL", func(t *testing.T) { 91 hasNotValidHCL := false 92 hasValidHCLNotValidBuild := false 93 hasValidHCLAndBuild := false 94 for _, tc := range config.HCLParseTestCases { 95 if !tc.ValidHCL { 96 hasNotValidHCL = true 97 } else { 98 if tc.ValidBuild { 99 hasValidHCLAndBuild = true 100 } else { 101 hasValidHCLNotValidBuild = true 102 } 103 } 104 } 105 if !hasNotValidHCL { 106 compliancetest.Fail(t, "Please define at least one test with an invalid HCL.") 107 } 108 if !hasValidHCLNotValidBuild { 109 compliancetest.Fail(t, "Please define at least one test with a valid HCL that will fail on Build() for validation.") 110 } 111 if !hasValidHCLAndBuild { 112 compliancetest.Fail(t, "Please define at least one test with a valid HCL that will succeed on Build() for validation.") 113 } 114 }) 115 t.Run("metadata", func(t *testing.T) { 116 hasNotPresent := false 117 hasNotValid := false 118 hasValid := false 119 for _, tc := range config.MetadataStructTestCases { 120 if !tc.IsPresent { 121 hasNotPresent = true 122 } else { 123 if tc.IsValid { 124 hasValid = true 125 } else { 126 hasNotValid = true 127 } 128 } 129 } 130 if !hasNotPresent { 131 compliancetest.Fail(t, "Please provide at least one metadata test that represents non-present metadata.") 132 } 133 if !hasNotValid { 134 compliancetest.Log(t, "Warning: Please provide at least one metadata test that represents an invalid metadata that is present.") 135 } 136 if !hasValid { 137 compliancetest.Log(t, "Warning: Please provide at least one metadata test that represents a valid metadata.") 138 } 139 }) 140 }) 141 } 142 143 func complianceTestProvide[TDescriptor keyprovider.Descriptor, TConfig keyprovider.Config, TKeyProvider keyprovider.KeyProvider, TMeta keyprovider.KeyMeta]( 144 t *testing.T, 145 cfg TestConfiguration[TDescriptor, TConfig, TMeta, TKeyProvider], 146 ) { 147 if reflect.ValueOf(cfg.ProvideTestCase.ValidConfig).IsNil() { 148 compliancetest.Fail(t, "Please provide a ValidConfig in ProvideTestCase.") 149 } 150 keyProviderConfig := cfg.ProvideTestCase.ValidConfig 151 t.Run("nil-metadata", func(t *testing.T) { 152 keyProvider, inMeta := complianceTestBuildConfigAndValidate[TKeyProvider, TMeta](t, keyProviderConfig, true) 153 154 if reflect.ValueOf(inMeta).IsNil() { 155 compliancetest.Skip(t, "The key provider does not have metadata (no metadata returned from Build()).") 156 return 157 } 158 _, _, err := keyProvider.Provide(nil) 159 if err == nil { 160 compliancetest.Fail(t, "Provide() did not return no error when provided with nil metadata.") 161 } else { 162 compliancetest.Log(t, "Provide() correctly returned an error when provided with nil metadata (%v).", err) 163 } 164 var typedError *keyprovider.ErrInvalidMetadata 165 if !errors.As(err, &typedError) { 166 compliancetest.Fail(t, "Provide() returned an error of the type %T instead of %T. Please use the correct typed errors.", err, typedError) 167 } else { 168 compliancetest.Log(t, "Provide() correctly returned a %T when provided with nil metadata.", typedError) 169 } 170 }) 171 t.Run("incorrect-metadata-type", func(t *testing.T) { 172 keyProvider, inMeta := complianceTestBuildConfigAndValidate[TKeyProvider, TMeta](t, keyProviderConfig, true) 173 if reflect.ValueOf(inMeta).IsNil() { 174 compliancetest.Skip(t, "The key provider does not have metadata (no metadata returned from Build()).") 175 return 176 } 177 _, _, err := keyProvider.Provide(&struct{}{}) 178 if err == nil { 179 compliancetest.Fail(t, "Provide() did not return no error when provided with an incorrect metadata type.") 180 } else { 181 compliancetest.Log(t, "Provide() correctly returned an error when provided with an metadata type (%v).", err) 182 } 183 var typedError *keyprovider.ErrInvalidMetadata 184 if !errors.As(err, &typedError) { 185 compliancetest.Fail(t, "Provide() returned an error of the type %T instead of %T. Please use the correct typed errors.", err, typedError) 186 } else { 187 compliancetest.Log(t, "Provide() correctly returned a %T when provided with an incorrect metadata type.", typedError) 188 } 189 }) 190 t.Run("round-trip", func(t *testing.T) { 191 complianceTestRoundTrip(t, keyProviderConfig, cfg) 192 }) 193 } 194 195 func complianceTestRoundTrip[TDescriptor keyprovider.Descriptor, TConfig keyprovider.Config, TKeyProvider keyprovider.KeyProvider, TMeta keyprovider.KeyMeta]( 196 t *testing.T, 197 keyProviderConfig TConfig, 198 cfg TestConfiguration[TDescriptor, TConfig, TMeta, TKeyProvider], 199 ) { 200 keyProvider, inMeta := complianceTestBuildConfigAndValidate[TKeyProvider, TMeta](t, keyProviderConfig, true) 201 output, outMeta, err := keyProvider.Provide(inMeta) 202 if err != nil { 203 compliancetest.Fail(t, "Provide() failed (%v).", err) 204 } else { 205 compliancetest.Log(t, "Provide() succeeded.") 206 } 207 if cfg.ProvideTestCase.ValidateMetadata != nil { 208 if err := cfg.ProvideTestCase.ValidateMetadata(outMeta.(TMeta)); err != nil { 209 compliancetest.Fail(t, "The metadata after the second Provide() call failed the test (%v).", err) 210 } 211 } 212 213 // Create a second key provider to avoid internal state. 214 keyProvider2, inMeta2 := complianceTestBuildConfigAndValidate[TKeyProvider, TMeta](t, keyProviderConfig, true) 215 216 marshalledMeta, err := json.Marshal(outMeta) 217 if err != nil { 218 compliancetest.Fail(t, "JSON-marshalling output meta failed (%v).", err) 219 } else { 220 compliancetest.Log(t, "JSON-marshalling output meta succeeded: %s", marshalledMeta) 221 } 222 223 if err := json.Unmarshal(marshalledMeta, &inMeta2); err != nil { 224 compliancetest.Fail(t, "JSON-unmarshalling meta failed (%v).", err) 225 } else { 226 compliancetest.Log(t, "JSON-unmarshalling meta succeeded.") 227 } 228 229 output2, outMeta2, err := keyProvider2.Provide(inMeta2) 230 if err != nil { 231 compliancetest.Fail(t, "Provide() on the subsequent run failed (%v).", err) 232 } else { 233 compliancetest.Log(t, "Provide() on the subsequent run succeeded.") 234 } 235 236 if cfg.ProvideTestCase.ExpectedOutput != nil { 237 if !bytes.Equal(cfg.ProvideTestCase.ExpectedOutput.EncryptionKey, output.EncryptionKey) { 238 compliancetest.Fail(t, "Incorrect encryption key received after the first Provide() call. Please set a break point to the line of this error message to debug this error.") 239 } 240 if !bytes.Equal(cfg.ProvideTestCase.ExpectedOutput.DecryptionKey, output2.DecryptionKey) { 241 compliancetest.Fail(t, "Incorrect decryption key received after the second Provide() call. Please set a break point to the line of this error message to debug this error.") 242 } 243 if !bytes.Equal(cfg.ProvideTestCase.ExpectedOutput.EncryptionKey, output2.EncryptionKey) { 244 compliancetest.Fail(t, "Incorrect encryption key received after the second Provide() call. Please set a break point to the line of this error message to debug this error.") 245 } 246 } 247 if cfg.ProvideTestCase.ValidateMetadata != nil { 248 if err := cfg.ProvideTestCase.ValidateMetadata(outMeta2.(TMeta)); err != nil { 249 compliancetest.Fail(t, "The metadata after the second Provide() call failed the test (%v).", err) 250 } 251 } 252 if cfg.ProvideTestCase.ValidateKeys == nil { 253 if !bytes.Equal(output2.DecryptionKey, output.EncryptionKey) { 254 compliancetest.Fail( 255 t, 256 "The encryption key from the first call to Provide() does not match the decryption key provided by the second Provide() call. If you intend the two keys to be different, please provide an ProvideTestCase.ValidateKeys function. If this is not intended, please set a break point to the line of this error message.", 257 ) 258 } else { 259 compliancetest.Log( 260 t, 261 "The encryption and decryption keys match.", 262 ) 263 } 264 } else { 265 if err := cfg.ProvideTestCase.ValidateKeys(output2.DecryptionKey, output.EncryptionKey); err != nil { 266 compliancetest.Fail( 267 t, 268 "The encryption key from the first call to Provide() does not match the decryption key provided by the second Provide() call (%v),", 269 err, 270 ) 271 } else { 272 compliancetest.Log( 273 t, 274 "The encryption and decryption keys match.", 275 ) 276 } 277 } 278 } 279 280 func complianceTestID[TDescriptor keyprovider.Descriptor, TConfig keyprovider.Config, TMeta keyprovider.KeyMeta, TKeyProvider keyprovider.KeyProvider]( 281 t *testing.T, 282 config TestConfiguration[TDescriptor, TConfig, TMeta, TKeyProvider], 283 ) { 284 id := config.Descriptor.ID() 285 if id == "" { 286 compliancetest.Fail(t, "ID is empty.") 287 } else { 288 compliancetest.Log(t, "ID is not empty.") 289 } 290 if err := id.Validate(); err != nil { 291 compliancetest.Fail(t, "ID failed validation: %s", id) 292 } else { 293 compliancetest.Log(t, "ID passed validation.") 294 } 295 } 296 297 func complianceTestHCLParsingTestCase[TDescriptor keyprovider.Descriptor, TConfig keyprovider.Config, TMeta keyprovider.KeyMeta, TKeyProvider keyprovider.KeyProvider]( 298 t *testing.T, 299 tc HCLParseTestCase[TConfig, TKeyProvider], 300 cfg TestConfiguration[TDescriptor, TConfig, TMeta, TKeyProvider], 301 ) { 302 parseError := false 303 parsedConfig, diags := config.LoadConfigFromString("config.hcl", tc.HCL) 304 if tc.ValidHCL { 305 if diags.HasErrors() { 306 compliancetest.Fail(t, "Unexpected HCL error (%v).", diags) 307 } else { 308 compliancetest.Log(t, "HCL successfully parsed.") 309 } 310 } else { 311 if diags.HasErrors() { 312 parseError = true 313 } 314 } 315 316 configStruct := cfg.Descriptor.ConfigStruct() 317 diags = gohcl.DecodeBody( 318 parsedConfig.KeyProviderConfigs[0].Body, 319 nil, 320 configStruct, 321 ) 322 var keyProvider TKeyProvider 323 if tc.ValidHCL { 324 if diags.HasErrors() { 325 compliancetest.Fail(t, "Failed to parse empty HCL block into config struct (%v).", diags) 326 } else { 327 compliancetest.Log(t, "HCL successfully loaded into config struct.") 328 } 329 330 keyProvider, _ = complianceTestBuildConfigAndValidate[TKeyProvider, TMeta](t, configStruct, tc.ValidBuild) 331 } else { 332 if !parseError && !diags.HasErrors() { 333 compliancetest.Fail(t, "Expected error during HCL parsing, but no error was returned.") 334 } else { 335 compliancetest.Log(t, "HCL loading errored correctly (%v).", diags) 336 } 337 } 338 339 if tc.Validate != nil { 340 if err := tc.Validate(configStruct.(TConfig), keyProvider); err != nil { 341 compliancetest.Fail(t, "Error during validation and configuration (%v).", err) 342 } else { 343 compliancetest.Log(t, "Successfully validated parsed HCL config and applied modifications.") 344 } 345 } else { 346 compliancetest.Log(t, "No ValidateAndConfigure provided, skipping HCL parse validation.") 347 } 348 } 349 350 func complianceTestConfigCase[TConfig keyprovider.Config, TKeyProvider keyprovider.KeyProvider, TMeta keyprovider.KeyMeta]( 351 t *testing.T, 352 tc ConfigStructTestCase[TConfig, TKeyProvider], 353 ) { 354 keyProvider, _ := complianceTestBuildConfigAndValidate[TKeyProvider, TMeta](t, tc.Config, tc.ValidBuild) 355 if tc.Validate != nil { 356 if err := tc.Validate(keyProvider); err != nil { 357 compliancetest.Fail(t, "Error during validation and configuration (%v).", err) 358 } else { 359 compliancetest.Log(t, "Successfully validated parsed HCL config and applied modifications.") 360 } 361 } else { 362 compliancetest.Log(t, "No ValidateAndConfigure provided, skipping HCL parse validation.") 363 } 364 } 365 366 func complianceTestBuildConfigAndValidate[TKeyProvider keyprovider.KeyProvider, TMeta keyprovider.KeyMeta]( 367 t *testing.T, 368 configStruct keyprovider.Config, 369 validBuild bool, 370 ) (TKeyProvider, TMeta) { 371 if configStruct == nil { 372 compliancetest.Fail(t, "Nil struct passed!") 373 } 374 375 var typedKeyProvider TKeyProvider 376 var typedMeta TMeta 377 var ok bool 378 kp, meta, err := configStruct.Build() 379 if validBuild { 380 if err != nil { 381 compliancetest.Fail(t, "Build() returned an unexpected error: %v.", err) 382 } else { 383 compliancetest.Log(t, "Build() did not return an error.") 384 } 385 typedKeyProvider, ok = kp.(TKeyProvider) 386 if !ok { 387 compliancetest.Fail(t, "Build() returned an invalid key provider type of %T, expected %T", kp, typedKeyProvider) 388 } else { 389 compliancetest.Log(t, "Build() returned the correct key provider type of %T.", typedKeyProvider) 390 } 391 392 metaType := reflect.TypeOf(typedMeta) 393 if meta == nil { 394 if metaType.Kind() != reflect.Interface { 395 compliancetest.Fail(t, "Build() did not return a metadata, but you declared a metadata type. Please make sure that you always return the same metadata type.") 396 } else { 397 compliancetest.Log(t, "Build() did not return a metadata and the declared metadata type is interface{}.") 398 } 399 } else { 400 if metaType.Kind() == reflect.Interface { 401 compliancetest.Fail(t, "Build() returned metadata, but you declared an interface type as the metadata type. Please always declare a pointer to a struct as a metadata type.") 402 } else { 403 compliancetest.Log(t, "Build() returned metadata and the declared metadata type is not an interface.") 404 } 405 typedMeta, ok = meta.(TMeta) 406 if !ok { 407 compliancetest.Fail(t, "Build() returned an invalid metadata type of %T, expected %T", meta, typedMeta) 408 } else { 409 compliancetest.Log(t, "Build() returned the correct metadata type of %T.", meta) 410 } 411 } 412 } else { 413 if err == nil { 414 compliancetest.Fail(t, "Build() did not return an error.") 415 } else { 416 compliancetest.Log(t, "Build() correctly returned an error: %v", err) 417 } 418 419 var typedError *keyprovider.ErrInvalidConfiguration 420 if !errors.As(err, &typedError) { 421 compliancetest.Fail( 422 t, 423 "Build() did not return the correct error type, got %T but expected %T", 424 err, 425 typedError, 426 ) 427 } else { 428 compliancetest.Log(t, "Build() returned the correct error type of %T", typedError) 429 } 430 } 431 return typedKeyProvider, typedMeta 432 } 433 434 func complianceTestMetadataTestCase[TConfig keyprovider.Config, TKeyProvider keyprovider.KeyProvider, TMeta keyprovider.KeyMeta]( 435 t *testing.T, 436 tc MetadataStructTestCase[TConfig, TMeta], 437 ) { 438 keyProvider, _ := complianceTestBuildConfigAndValidate[TKeyProvider, TMeta](t, tc.ValidConfig, true) 439 440 output, _, err := keyProvider.Provide(tc.Meta) 441 if tc.IsPresent { 442 // This test case means that the input metadata should be considered present, so it's either an error or a 443 // decryption key. 444 if tc.IsValid { 445 if err != nil { 446 var typedError *keyprovider.ErrKeyProviderFailure 447 if !errors.As(err, &typedError) { 448 compliancetest.Fail( 449 t, 450 "The Provide() function returned an unexpected error, which was also of the incorrect type of %T instead of %T: %v", 451 err, 452 typedError, 453 err, 454 ) 455 } 456 compliancetest.Fail(t, "The Provide() function returned an unexpected error: %v", err) 457 } 458 } else { 459 if err == nil { 460 compliancetest.Fail(t, "The Provide() function did not return an error as expected.") 461 } else { 462 compliancetest.Log(t, "The Provide() function returned an expected error: %v", err) 463 } 464 465 var typedError *keyprovider.ErrInvalidMetadata 466 if !errors.As(err, &typedError) { 467 compliancetest.Fail( 468 t, 469 "The Provide() function returned the error type of %T instead of %T. Please use the correct typed errors.", 470 err, 471 typedError, 472 ) 473 } 474 } 475 } else { 476 if err != nil { 477 var typedError *keyprovider.ErrKeyProviderFailure 478 if !errors.As(err, &typedError) { 479 compliancetest.Fail( 480 t, 481 "The Provide() function returned an unexpected error, which was also of the incorrect type of %T instead of %T: %v", 482 err, 483 typedError, 484 err, 485 ) 486 } 487 compliancetest.Fail(t, "The Provide() function returned an unexpected error: %v", err) 488 } 489 if len(output.DecryptionKey) != 0 { 490 compliancetest.Fail( 491 t, 492 "The Provide() function a decryption key despite not receiving input meta. This is incorrect, please don't return a decryption key unless you receive the input metadata.", 493 ) 494 } else { 495 compliancetest.Log( 496 t, 497 "The Provide() function correctly did not return a decryption key without input metadata.", 498 ) 499 } 500 } 501 }