From 0e2f33d73e4cfcea15166d38d620064dde7c928a Mon Sep 17 00:00:00 2001 From: Anton Antonov Date: Mon, 9 Aug 2021 20:36:34 +0300 Subject: [PATCH] Add support for AWS KMS asymmetric keypair backend --- .gitignore | 1 + .go-version | 2 +- .golangci.yml | 21 +- CHANGELOG.md | 17 +- cmd/decrypt.go | 132 +- cmd/encrypt.go | 68 +- .../external_interfaces.go | 50 +- cmd/rekey.go | 40 +- cmd/root.go | 43 +- cmd/rotate.go | 38 +- cmd/terraform.go | 14 +- cmd/terraform/vault.go | 39 +- cmd/terraform/vault/ini.go | 110 - cmd/terraform/vault/ini_integration_test.go | 208 -- cmd/terraform/vault/migrate.go | 141 -- cmd/terraform/vault/new_resource.go | 29 +- .../vault/new_resource_integration_test.go | 29 +- cmd/terraform/vault/new_resource_test.go | 13 +- cmd/terraform/vault/rekey.go | 31 +- cmd/terraform/vault/rotate.go | 31 +- cmd/terraform/vault_test.go | 43 +- cmd/terraform_test.go | 41 +- go.mod | 5 +- go.sum | 29 +- main_e2e_test.go | 92 +- pkg/aws/service.go | 56 + pkg/ini/section.go | 24 - pkg/ini/section_value.go | 24 - pkg/ini/service.go | 72 - pkg/rsa/builtin.go | 1 + pkg/rsa/service.go | 7 +- pkg/rsa/service_test.go | 64 +- pkg/rsa/test/testing.go | 14 + pkg/terraform/service.go | 2 +- pkg/terraform/service_test.go | 2 +- .../external_interfaces.go | 32 +- pkg/terraform_encryption_migration/service.go | 309 +-- .../service_integration_test.go | 1903 ++--------------- .../test/testing.go | 48 +- .../legacy_encrypted_content_service.go | 72 - .../legacy_encrypted_content_service_test.go | 387 ---- ...ypted_content_service.go => v1_service.go} | 10 +- pkg/vaulted/header/header.go | 8 +- pkg/vaulted/header/header_service.go | 4 +- pkg/vaulted/header/header_service_test.go | 20 +- pkg/vaulted/header/header_test.go | 8 +- .../passphrase/decryption_aws_kms_service.go | 44 + .../decryption_rsa_pkcs1v15_service.go | 50 + .../passphrase/enc_rsa_oaep_service.go | 44 + .../passphrase/enc_rsa_pkcs1v15_service.go | 47 + .../encrypted_passphrase_service.go | 100 - .../encrypted_passphrase_service_test.go | 352 --- pkg/vaulted/passphrase/external_interfaces.go | 7 +- .../passphrase/service.go} | 23 +- pkg/vaulted/payload/decryption_service.go | 49 + .../payload/encrypted_payload_service.go | 203 -- .../payload/encrypted_payload_service_test.go | 1214 ----------- pkg/vaulted/payload/encryption_service.go | 51 + pkg/vaulted/payload/external_interfaces.go | 43 +- pkg/vaulted/payload/payload_test.go | 12 +- pkg/vaulted/payload/serde_service.go | 227 ++ 61 files changed, 1255 insertions(+), 5545 deletions(-) delete mode 100644 cmd/terraform/vault/ini.go delete mode 100644 cmd/terraform/vault/ini_integration_test.go delete mode 100644 cmd/terraform/vault/migrate.go create mode 100644 pkg/aws/service.go delete mode 100644 pkg/ini/section.go delete mode 100644 pkg/ini/section_value.go delete mode 100644 pkg/ini/service.go delete mode 100644 pkg/vaulted/content/legacy_encrypted_content_service.go delete mode 100644 pkg/vaulted/content/legacy_encrypted_content_service_test.go rename pkg/vaulted/content/{v1_encrypted_content_service.go => v1_service.go} (85%) create mode 100644 pkg/vaulted/passphrase/decryption_aws_kms_service.go create mode 100644 pkg/vaulted/passphrase/decryption_rsa_pkcs1v15_service.go create mode 100644 pkg/vaulted/passphrase/enc_rsa_oaep_service.go create mode 100644 pkg/vaulted/passphrase/enc_rsa_pkcs1v15_service.go delete mode 100644 pkg/vaulted/passphrase/encrypted_passphrase_service.go delete mode 100644 pkg/vaulted/passphrase/encrypted_passphrase_service_test.go rename pkg/{ini/content.go => vaulted/passphrase/service.go} (60%) create mode 100644 pkg/vaulted/payload/decryption_service.go delete mode 100644 pkg/vaulted/payload/encrypted_payload_service.go delete mode 100644 pkg/vaulted/payload/encrypted_payload_service_test.go create mode 100644 pkg/vaulted/payload/encryption_service.go create mode 100644 pkg/vaulted/payload/serde_service.go diff --git a/.gitignore b/.gitignore index 4d89fb2..8677f2d 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ public.pem /vaulted /vaulted.exe /vaulted.exe~ +aws-public.pem diff --git a/.go-version b/.go-version index 15b989e..a232073 100644 --- a/.go-version +++ b/.go-version @@ -1 +1 @@ -1.16.0 +1.16.4 diff --git a/.golangci.yml b/.golangci.yml index 1a76b21..ca83784 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -92,18 +92,21 @@ linters: - nlreturn # NOTE: False-positives - nestif + # NOTE: Doesn't play nice with `stacktrace` pkg + - wrapcheck + # NOTE: More opinionated than useful + - revive + # NOTE: Very bad practice in terms of readability and code consistency. + # Questionable benefit of saving 1 line of code. + - ifshort issues: exclude-rules: - - text: "don't use an underscore in package name" - linters: - - revive - text: "weak cryptographic primitive" linters: - gosec - # NOTE: Ignore duplicate false positives - - path: pkg/vaulted/content/legacy_encrypted_content_service.go + - text: "appendAssign: append result not assigned to the same slice" linters: - - dupl + - gocritic - path: pkg/vaulted/content/v1_encrypted_content_service.go linters: - dupl @@ -113,12 +116,6 @@ issues: - path: cmd/terraform/vault/rekey.go linters: - dupl - - path: cmd/terraform/vault/ini.go - linters: - - dupl - - path: cmd/legacy/vault/ini.go - linters: - - dupl - path: _test\.go linters: - gocyclo diff --git a/CHANGELOG.md b/CHANGELOG.md index 51c6c86..fd7ebcb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,18 +55,23 @@ Change line format: ### Changed -* Replaced HCLv1 parser with HCLv2 one ; Ref: https://github.com/sumup-oss/vaulted/pull/16 +## v0.3.0 -### Removed +### Added -* Commands from `legacy` sub-command are now removed. We're not using them internally. The migration from legacy to v1 secret format command(s) are there to help you transition ; Ref: https://github.com/sumup-oss/vaulted/pull/15 -* HCLv1 parsing and support for Terraform earlier than 0.12 ; Ref: https://github.com/sumup-oss/vaulted/pull/16 - -## v0.3.0 +* AWS KMS asymmetric keypair encryption & decryption support ; Ref: https://github.com/sumup-oss/vaulted/pull/33 ### Changed * Commands from `terraform` sub-command are now part of `terraform vault`. This is to accommodate for future `terraform X` command where X might be another provider ; Ref: https://github.com/sumup-oss/vaulted/pull/5 +* Replaced HCLv1 parser with HCLv2 one ; Ref: https://github.com/sumup-oss/vaulted/pull/16 +* Untangled the API implemented by other users of vaulted like terraform providers. Result is now it's easier to implement different strategies, like in the future - GCP KMS support. ; Ref: https://github.com/sumup-oss/vaulted/pull/33 + +### Removed + +* Commands from `legacy` sub-command are now removed. We're not using them internally. The migration from legacy to v1 secret format command(s) are there to help you transition ; Ref: https://github.com/sumup-oss/vaulted/pull/15 +* HCLv1 parsing and support for Terraform earlier than 0.12 ; Ref: https://github.com/sumup-oss/vaulted/pull/16 +* `ini` commands. Ref: https://github.com/sumup-oss/vaulted/pull/33 ## v0.2.1 diff --git a/cmd/decrypt.go b/cmd/decrypt.go index e7599ee..5e7c207 100644 --- a/cmd/decrypt.go +++ b/cmd/decrypt.go @@ -15,47 +15,85 @@ package cmd import ( + "context" + stdRsa "crypto/rsa" "fmt" "github.com/palantir/stacktrace" "github.com/spf13/cobra" "github.com/sumup-oss/go-pkgs/os" + "github.com/sumup-oss/vaulted/cmd/external_interfaces" "github.com/sumup-oss/vaulted/internal/cli" - "github.com/sumup-oss/vaulted/pkg/aes" - "github.com/sumup-oss/vaulted/pkg/base64" - "github.com/sumup-oss/vaulted/pkg/pkcs7" - "github.com/sumup-oss/vaulted/pkg/rsa" + "github.com/sumup-oss/vaulted/pkg/aws" "github.com/sumup-oss/vaulted/pkg/vaulted/content" - "github.com/sumup-oss/vaulted/pkg/vaulted/header" "github.com/sumup-oss/vaulted/pkg/vaulted/passphrase" "github.com/sumup-oss/vaulted/pkg/vaulted/payload" ) +// nolint:lll +const decryptExample = ` + # Decryption using local RSA asymmetric keypair. This requires the "--in" to have been encrypted using local RSA public key. + > vaulted decrypt --private-key-path ./my-pubkey.pem --in ./mysecret-enc.base64 --out ./mysecret.txt + + # Decryption using AWS KMS asymmetric keypair. This requires the "--in" to have been encrypted using local AWS KMS asymmetric public key. + # Make sure to set the correct AWS_REGION and AWS_PROFILE where the AWS KMS key is present. + > AWS_REGION=eu-west-1 AWS_PROFILE=secretprofile vaulted decrypt --aws-kms-id=alias/yourkey --in ./mysecret-enc.base64 --out ./mysecret.txt +` + func NewDecryptCommand( osExecutor os.OsExecutor, + rsaSvc external_interfaces.RsaService, + b64Svc external_interfaces.Base64Service, + aesSvc external_interfaces.AesService, ) *cobra.Command { cmdInstance := &cobra.Command{ Use: "decrypt --private-key-path ./private.pem --in ./mysecret-enc.base64 --out ./mysecret.txt", Short: "Decrypt a file/value", Long: "Decrypt a file/value using AES-256GCM symmetric encryption. " + "Passphrase is encrypted with RSA asymmetric keypair.", + Example: decryptExample, RunE: func(cmdInstance *cobra.Command, args []string) error { privateKeyPath := cmdInstance.Flag("private-key-path").Value.String() + awsKmsKeyID := cmdInstance.Flag("aws-kms-key-id").Value.String() + awsRegion := cmdInstance.Flag("aws-region").Value.String() - rsaSvc := rsa.NewRsaService(osExecutor) - // NOTE: Read early to avoid needless decryption - privKey, err := rsaSvc.ReadPrivateKeyFromPath(privateKeyPath) - if err != nil { - return stacktrace.Propagate( - err, - "failed to read specified private key", + var privKey *stdRsa.PrivateKey + var err error + + var decryptionService *payload.DecryptionService + contentDecrypter := content.NewV1Service(b64Svc, aesSvc) + + if privateKeyPath != "" { + privKey, err = rsaSvc.ReadPrivateKeyFromPath(privateKeyPath) + if err != nil { + return stacktrace.Propagate( + err, + "failed to read specified private key", + ) + } + + decryptionService = payload.NewDecryptionService( + passphrase.NewDecryptionRsaPKCS1v15Service(privKey, rsaSvc), + contentDecrypter, ) - } + } else if awsKmsKeyID != "" { + var awsSvc *aws.Service + awsSvc, err = aws.NewService(context.Background(), awsRegion) + if err != nil { + return stacktrace.Propagate(err, "failed to create aws service") + } - inFilePathArg := cmdInstance.Flag("in").Value.String() + decryptionService = payload.NewDecryptionService( + passphrase.NewDecryptionAwsKmsService(awsSvc, awsKmsKeyID), + contentDecrypter, + ) + } var serializedEncryptedPayload []byte + + // NOTE: Read early to avoid needless decryption + inFilePathArg := cmdInstance.Flag("in").Value.String() if inFilePathArg == "" { serializedEncryptedPayload, err = cli.ReadFromStdin( osExecutor, @@ -77,27 +115,9 @@ func NewDecryptCommand( } } - base64Svc := base64.NewBase64Service() - pkcs7Svc := pkcs7.NewPkcs7Service() - aesSvc := aes.NewAesService(pkcs7Svc) + payloadSerdeSvc := payload.NewSerdeService(b64Svc) - encryptedPassphraseSvc := passphrase.NewEncryptedPassphraseService( - base64Svc, - rsaSvc, - ) - - encryptedContentSvc := content.NewV1EncryptedContentService( - base64Svc, - aesSvc, - ) - - encryptedPayloadSvc := payload.NewEncryptedPayloadService( - header.NewHeaderService(), - encryptedPassphraseSvc, - encryptedContentSvc, - ) - - encryptedPayload, err := encryptedPayloadSvc.Deserialize(serializedEncryptedPayload) + encryptedPayload, err := payloadSerdeSvc.Deserialize(serializedEncryptedPayload) if err != nil { return stacktrace.Propagate( err, @@ -105,7 +125,7 @@ func NewDecryptCommand( ) } - payload, err := encryptedPayloadSvc.Decrypt(privKey, encryptedPayload) + payloadInstance, err := decryptionService.Decrypt(encryptedPayload) if err != nil { return stacktrace.Propagate( err, @@ -115,25 +135,23 @@ func NewDecryptCommand( outFilePath := cmdInstance.Flag("out").Value.String() if outFilePath == "" { - fmt.Fprintln(osExecutor.Stdout(), "Decrypted payload below:") + _, _ = fmt.Fprintln(osExecutor.Stdout(), "Decrypted payload below:") // NOTE: Explicitly print as string representation - fmt.Fprintln(osExecutor.Stdout(), string(payload.Content.Plaintext)) - } else { - err := osExecutor.WriteFile( - outFilePath, - payload.Content.Plaintext, - 0644, - ) - if err != nil { - return stacktrace.Propagate( - err, - "failed to write decrypted payload", - ) - } + _, _ = fmt.Fprintln(osExecutor.Stdout(), string(payloadInstance.Content.Plaintext)) + + return nil } - return nil + err = osExecutor.WriteFile( + outFilePath, + payloadInstance.Content.Plaintext, + 0644, + ) + return stacktrace.Propagate( + err, + "failed to write decrypted payload", + ) }, } @@ -142,8 +160,18 @@ func NewDecryptCommand( "", "Path to RSA private key used to decrypt encrypted payload.", ) - //nolint:errcheck - cmdInstance.MarkPersistentFlagRequired("private-key-path") + + cmdInstance.PersistentFlags().String( + "aws-kms-key-id", + "", + "AWS Asymmetric Customer Managed Key ID", + ) + + cmdInstance.PersistentFlags().String( + "aws-region", + "", + "AWS Region to use for KMS. Can also be provided by `AWS_REGION` environment variable.", + ) cmdInstance.PersistentFlags().String( "in", diff --git a/cmd/encrypt.go b/cmd/encrypt.go index bf04aee..77e0411 100644 --- a/cmd/encrypt.go +++ b/cmd/encrypt.go @@ -25,24 +25,38 @@ import ( "github.com/sumup-oss/vaulted/internal/cli" "github.com/sumup-oss/vaulted/pkg/vaulted/content" "github.com/sumup-oss/vaulted/pkg/vaulted/header" + "github.com/sumup-oss/vaulted/pkg/vaulted/passphrase" "github.com/sumup-oss/vaulted/pkg/vaulted/payload" ) +var knownTypes = []string{ + "local-rsa", + "aws-kms-asym", +} + +const encryptExample = ` + # Encryption using local RSA asymmetric keypair + > vaulted encrypt --public-key-path ./my-pubkey.pem --in ./mysecret.txt --out ./mysecret-enc.base64 + # Encryption using AWS KMS asymmetric keypair. Prerequisite: public key is already locally downloaded + > vaulted encrypt --type=aws-kms-asym --public-key-path ./my-pubkey.pem --in ./mysecret.txt --out ./mysecret-enc.base64 +` + func NewEncryptCommand( osExecutor os.OsExecutor, rsaSvc external_interfaces.RsaService, - encryptedPassphraseSvc external_interfaces.EncryptedPassphraseService, - encryptedPayloadSvc external_interfaces.EncryptedPayloadService, + b64Svc external_interfaces.Base64Service, + aesSvc external_interfaces.AesService, ) *cobra.Command { cmdInstance := &cobra.Command{ Use: "encrypt --public-key-path ./my-pubkey.pem --in ./mysecret.txt --out ./mysecret-enc.base64", Short: "Encrypt a file/value", Long: "Encrypt a file/value using AES256-GCM symmetric encryption. " + - "Passfile runtime random generated and encrypted with RSA asymmetric keypair.", + "Passphrase is runtime randomly generated and encrypted with RSA asymmetric keypair.", + Example: encryptExample, RunE: func(cmdInstance *cobra.Command, args []string) error { publicKeyPath := cmdInstance.Flag("public-key-path").Value.String() - // NOTE: Read early to avoid needless encryption + // NOTE: Read early to make sure the RSA key is valid pubKey, err := rsaSvc.ReadPublicKeyFromPath(publicKeyPath) if err != nil { return stacktrace.Propagate( @@ -75,9 +89,11 @@ func NewEncryptCommand( } } - content := content.NewContent(inFileContent) + contentInstance := content.NewContent(inFileContent) - passphrase, err := encryptedPassphraseSvc.GeneratePassphrase(32) + passphraseSvc := passphrase.NewService() + + generatedPassphrase, err := passphraseSvc.GeneratePassphrase(32) if err != nil { return stacktrace.Propagate( err, @@ -85,13 +101,29 @@ func NewEncryptCommand( ) } - payload := payload.NewPayload( + payloadInstance := payload.NewPayload( header.NewHeader(), - passphrase, - content, + generatedPassphrase, + contentInstance, ) - encryptedPayload, err := encryptedPayloadSvc.Encrypt(pubKey, payload) + var encryptionService *payload.EncryptionService + contentEncrypter := content.NewV1Service(b64Svc, aesSvc) + + encryptionType := cmdInstance.Flag("type").Value.String() + switch encryptionType { + case "local-rsa": + passphraseEncrypter := passphrase.NewEncryptionRsaPKCS1v15Service(rsaSvc, pubKey) + encryptionService = payload.NewEncryptionService(passphraseEncrypter, contentEncrypter) + case "aws-kms-asym": + passphraseEncrypter := passphrase.NewEncRsaOaepService(rsaSvc, pubKey) + encryptionService = payload.NewEncryptionService(passphraseEncrypter, contentEncrypter) + default: + return stacktrace.NewError("unsupported `type` specified. Supported values: %#v", knownTypes) + } + + serdeSvc := payload.NewSerdeService(b64Svc) + encryptedPayload, err := encryptionService.Encrypt(payloadInstance) if err != nil { return stacktrace.Propagate( err, @@ -99,7 +131,7 @@ func NewEncryptCommand( ) } - serializedEncryptedPayload, err := encryptedPayloadSvc.Serialize(encryptedPayload) + serializedEncryptedPayload, err := serdeSvc.Serialize(encryptedPayload) if err != nil { return stacktrace.Propagate( err, @@ -109,10 +141,10 @@ func NewEncryptCommand( outFilePath := cmdInstance.Flag("out").Value.String() if outFilePath == "" { - fmt.Fprintln(osExecutor.Stdout(), "Encrypted payload below:") + _, _ = fmt.Fprintln(osExecutor.Stdout(), "Encrypted payload below:") // NOTE: Explicitly print as string representation - fmt.Fprintln(osExecutor.Stdout(), string(serializedEncryptedPayload)) + _, _ = fmt.Fprintln(osExecutor.Stdout(), string(serializedEncryptedPayload)) } else { err := osExecutor.WriteFile( outFilePath, @@ -131,13 +163,19 @@ func NewEncryptCommand( }, } + cmdInstance.PersistentFlags().String( + "type", + "local-rsa", + fmt.Sprintf("Encryption type that must match the `decrypt` cmd's `type`. Valid value is one of %#v", knownTypes), + ) + cmdInstance.PersistentFlags().String( "public-key-path", "", "Path to RSA public key used to encrypt runtime random generated passphrase.", ) - //nolint:errcheck - cmdInstance.MarkPersistentFlagRequired("public-key-path") + + _ = cmdInstance.MarkPersistentFlagRequired("public-key-path") cmdInstance.PersistentFlags().String( "in", diff --git a/cmd/external_interfaces/external_interfaces.go b/cmd/external_interfaces/external_interfaces.go index 1df6100..8e760b0 100644 --- a/cmd/external_interfaces/external_interfaces.go +++ b/cmd/external_interfaces/external_interfaces.go @@ -16,14 +16,10 @@ package external_interfaces import ( stdRsa "crypto/rsa" + "hash" "io" - goIni "github.com/go-ini/ini" - "github.com/hashicorp/hcl/v2/hclwrite" - "github.com/sumup-oss/vaulted/pkg/hcl" - "github.com/sumup-oss/vaulted/pkg/ini" - "github.com/sumup-oss/vaulted/pkg/terraform_encryption_migration" "github.com/sumup-oss/vaulted/pkg/vaulted/content" "github.com/sumup-oss/vaulted/pkg/vaulted/passphrase" "github.com/sumup-oss/vaulted/pkg/vaulted/payload" @@ -42,16 +38,21 @@ type EncryptedPassphraseService interface { GeneratePassphrase(length int) (*passphrase.Passphrase, error) Serialize(encryptedPassphrase *passphrase.EncryptedPassphrase) ([]byte, error) Deserialize(encoded []byte) (*passphrase.EncryptedPassphrase, error) - Encrypt(publicKey *stdRsa.PublicKey, passphrase *passphrase.Passphrase) (*passphrase.EncryptedPassphrase, error) + Encrypt( + useOAEP bool, + publicKey *stdRsa.PublicKey, + passphrase *passphrase.Passphrase, + ) (*passphrase.EncryptedPassphrase, error) Decrypt( + kmsKeyID string, privateKey *stdRsa.PrivateKey, encryptedPassphrase *passphrase.EncryptedPassphrase, ) (*passphrase.Passphrase, error) } type EncryptedPayloadService interface { - Encrypt(publicKey *stdRsa.PublicKey, payload *payload.Payload) (*payload.EncryptedPayload, error) - Decrypt(privateKey *stdRsa.PrivateKey, encryptedPayload *payload.EncryptedPayload) (*payload.Payload, error) + Encrypt(useOAEP bool, publicKey *stdRsa.PublicKey, payload *payload.Payload) (*payload.EncryptedPayload, error) + Decrypt(kmsKeyID string, privateKey *stdRsa.PrivateKey, encryptedPayload *payload.EncryptedPayload) (*payload.Payload, error) Serialize(encryptedPayload *payload.EncryptedPayload) ([]byte, error) Deserialize(encodedContent []byte) (*payload.EncryptedPayload, error) } @@ -69,38 +70,12 @@ type EncryptedContentService interface { ) (*content.Content, error) } -type TerraformEncryptionMigrationService interface { - ConvertIniContentToV1ResourceHCL( - passphraseLength int, - iniContent *ini.Content, - pubKey *stdRsa.PublicKey, - encryptedPassphraseSvc terraform_encryption_migration.EncryptedPassphraseService, - encryptedPayloadSvc terraform_encryption_migration.EncryptedPayloadService, - ) (*hclwrite.File, error) - MigrateEncryptedTerraformResourceHcl( - hclParser hcl.Parser, - hclBytes []byte, - privKey *stdRsa.PrivateKey, - pubKey *stdRsa.PublicKey, - legacyEncryptedContentSvc terraform_encryption_migration.EncryptedContentService, - encryptedPassphraseSvc terraform_encryption_migration.EncryptedPassphraseService, - encryptedPayloadSvc terraform_encryption_migration.EncryptedPayloadService, - ) (*hclwrite.File, error) - RotateOrRekeyEncryptedTerraformResourceHcl( - hclParser hcl.Parser, - hclBytes []byte, - privKey *stdRsa.PrivateKey, - pubKey *stdRsa.PublicKey, - encryptedPassphraseSvc terraform_encryption_migration.EncryptedPassphraseService, - encryptedPayloadSvc terraform_encryption_migration.EncryptedPayloadService, - ) (*hclwrite.File, error) -} - type RsaService interface { ReadPublicKeyFromPath(publicKeyPath string) (*stdRsa.PublicKey, error) ReadPrivateKeyFromPath(privateKeyPath string) (*stdRsa.PrivateKey, error) DecryptPKCS1v15(rand io.Reader, priv *stdRsa.PrivateKey, ciphertext []byte) ([]byte, error) EncryptPKCS1v15(rand io.Reader, pub *stdRsa.PublicKey, msg []byte) ([]byte, error) + EncryptOAEP(hash hash.Hash, random io.Reader, pub *stdRsa.PublicKey, msg []byte, label []byte) ([]byte, error) } type AesService interface { @@ -109,8 +84,3 @@ type AesService interface { EncryptGCM(key []byte, plaintext []byte) ([]byte, error) DecryptGCM(key []byte, ciphertext []byte) ([]byte, error) } - -type IniService interface { - ReadIniAtPath(path string) (*goIni.File, error) - ParseIniFileContents(file *goIni.File) *ini.Content -} diff --git a/cmd/rekey.go b/cmd/rekey.go index 6ca4b5b..d791bc6 100644 --- a/cmd/rekey.go +++ b/cmd/rekey.go @@ -23,17 +23,17 @@ import ( "github.com/sumup-oss/vaulted/cmd/external_interfaces" "github.com/sumup-oss/vaulted/internal/cli" - "github.com/sumup-oss/vaulted/pkg/rsa" "github.com/sumup-oss/vaulted/pkg/vaulted/content" "github.com/sumup-oss/vaulted/pkg/vaulted/header" + "github.com/sumup-oss/vaulted/pkg/vaulted/passphrase" "github.com/sumup-oss/vaulted/pkg/vaulted/payload" ) func NewRekeyCommand( osExecutor os.OsExecutor, - rsaSvc *rsa.Service, - encryptedPassphraseSvc external_interfaces.EncryptedPassphraseService, - encryptedPayloadSvc external_interfaces.EncryptedPayloadService, + rsaSvc external_interfaces.RsaService, + b64Svc external_interfaces.Base64Service, + aesSvc external_interfaces.AesService, ) *cobra.Command { cmdInstance := &cobra.Command{ Use: "rekey --old-private-key-path ./old-my-privatekey.pem " + @@ -45,7 +45,7 @@ func NewRekeyCommand( "AES256-GCM symmetric encryption. " + "Public key must NOT originate from same private key, otherwise you probably want" + "to use `rotate` instead. " + - "Passfile runtime random generated and encrypted with RSA asymmetric keypair.", + "Passphrase is runtime randomly generated and encrypted with RSA asymmetric keypair.", RunE: func(cmdInstance *cobra.Command, args []string) error { oldPrivateKeyPath := cmdInstance.Flag("old-private-key-path").Value.String() // NOTE: Read early to avoid needless decryption @@ -101,7 +101,8 @@ func NewRekeyCommand( } } - oldEncryptedPayload, err := encryptedPayloadSvc.Deserialize(inFileContent) + payloadSerdeSvc := payload.NewSerdeService(b64Svc) + oldEncryptedPayload, err := payloadSerdeSvc.Deserialize(inFileContent) if err != nil { return stacktrace.Propagate( err, @@ -109,7 +110,10 @@ func NewRekeyCommand( ) } - oldPayload, err := encryptedPayloadSvc.Decrypt(oldPrivKey, oldEncryptedPayload) + contentV1Svc := content.NewV1Service(b64Svc, aesSvc) + + oldPassphraseDecrypter := passphrase.NewDecryptionRsaPKCS1v15Service(oldPrivKey, rsaSvc) + oldPayload, err := payload.NewDecryptionService(oldPassphraseDecrypter, contentV1Svc).Decrypt(oldEncryptedPayload) if err != nil { return stacktrace.Propagate( err, @@ -118,7 +122,8 @@ func NewRekeyCommand( ) } - passphrase, err := encryptedPassphraseSvc.GeneratePassphrase(32) + passphraseSvc := passphrase.NewService() + passphraseInstance, err := passphraseSvc.GeneratePassphrase(32) if err != nil { return stacktrace.Propagate( err, @@ -126,13 +131,16 @@ func NewRekeyCommand( ) } - payload := payload.NewPayload( + payloadInstance := payload.NewPayload( header.NewHeader(), - passphrase, + passphraseInstance, content.NewContent(oldPayload.Content.Plaintext), ) - newEncryptedPayload, err := encryptedPayloadSvc.Encrypt(newPubKey, payload) + passphraseEncrypter := passphrase.NewEncryptionRsaPKCS1v15Service(rsaSvc, newPubKey) + payloadEncrypter := payload.NewEncryptionService(passphraseEncrypter, contentV1Svc) + + newEncryptedPayload, err := payloadEncrypter.Encrypt(payloadInstance) if err != nil { return stacktrace.Propagate( err, @@ -140,7 +148,7 @@ func NewRekeyCommand( ) } - serializedEncryptedPayload, err := encryptedPayloadSvc.Serialize(newEncryptedPayload) + serializedEncryptedPayload, err := payloadSerdeSvc.Serialize(newEncryptedPayload) if err != nil { return stacktrace.Propagate( err, @@ -177,16 +185,16 @@ func NewRekeyCommand( "", "Path to RSA private key used to decrypt specified `in` path content.", ) - //nolint:errcheck - cmdInstance.MarkPersistentFlagRequired("old-private-key-path") + + _ = cmdInstance.MarkPersistentFlagRequired("old-private-key-path") cmdInstance.PersistentFlags().String( "new-public-key-path", "", "Path to RSA public key used to encrypt runtime random generated passphrase.", ) - //nolint:errcheck - cmdInstance.MarkPersistentFlagRequired("new-public-key-path") + + _ = cmdInstance.MarkPersistentFlagRequired("new-public-key-path") cmdInstance.PersistentFlags().String( "in", diff --git a/cmd/root.go b/cmd/root.go index 882bbd5..a8e66ef 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -21,14 +21,7 @@ import ( "github.com/sumup-oss/vaulted/pkg/aes" "github.com/sumup-oss/vaulted/pkg/base64" "github.com/sumup-oss/vaulted/pkg/hcl" - "github.com/sumup-oss/vaulted/pkg/ini" "github.com/sumup-oss/vaulted/pkg/rsa" - "github.com/sumup-oss/vaulted/pkg/terraform" - "github.com/sumup-oss/vaulted/pkg/terraform_encryption_migration" - "github.com/sumup-oss/vaulted/pkg/vaulted/content" - "github.com/sumup-oss/vaulted/pkg/vaulted/header" - "github.com/sumup-oss/vaulted/pkg/vaulted/passphrase" - "github.com/sumup-oss/vaulted/pkg/vaulted/payload" ) func NewRootCmd( @@ -52,39 +45,13 @@ func NewRootCmd( }, } - terraformSvc := terraform.NewTerraformService() - terraformEncryptionMigrationSvc := terraform_encryption_migration.NewTerraformEncryptionMigrationService( - terraformSvc, - ) - headerSvc := header.NewHeaderService() - encPassphraseSvc := passphrase.NewEncryptedPassphraseService(base64Svc, rsaSvc) - - legacyEncContentSvc := content.NewLegacyEncryptedContentService(base64Svc, aesSvc) - v1EncContentSvc := content.NewV1EncryptedContentService(base64Svc, aesSvc) - - iniSvc := ini.NewIniService() - encPayloadSvc := payload.NewEncryptedPayloadService( - headerSvc, - encPassphraseSvc, - v1EncContentSvc, - ) - cmdInstance.AddCommand( NewVersionCmd(osExecutor), - NewEncryptCommand(osExecutor, rsaSvc, encPassphraseSvc, encPayloadSvc), - NewDecryptCommand(osExecutor), - NewRotateCommand(osExecutor, rsaSvc, encPassphraseSvc, encPayloadSvc), - NewRekeyCommand(osExecutor, rsaSvc, encPassphraseSvc, encPayloadSvc), - NewTerraformCmd( - osExecutor, - rsaSvc, - iniSvc, - encPassphraseSvc, - legacyEncContentSvc, - encPayloadSvc, - hclSvc, - terraformEncryptionMigrationSvc, - ), + NewEncryptCommand(osExecutor, rsaSvc, base64Svc, aesSvc), + NewDecryptCommand(osExecutor, rsaSvc, base64Svc, aesSvc), + NewRotateCommand(osExecutor, rsaSvc, base64Svc, aesSvc), + NewRekeyCommand(osExecutor, rsaSvc, base64Svc, aesSvc), + NewTerraformCmd(osExecutor, rsaSvc, hclSvc, base64Svc, aesSvc), ) return cmdInstance diff --git a/cmd/rotate.go b/cmd/rotate.go index 05ccef8..fe3c27e 100644 --- a/cmd/rotate.go +++ b/cmd/rotate.go @@ -24,13 +24,16 @@ import ( "github.com/sumup-oss/vaulted/cmd/external_interfaces" "github.com/sumup-oss/vaulted/internal/cli" "github.com/sumup-oss/vaulted/pkg/rsa" + "github.com/sumup-oss/vaulted/pkg/vaulted/content" + "github.com/sumup-oss/vaulted/pkg/vaulted/passphrase" + "github.com/sumup-oss/vaulted/pkg/vaulted/payload" ) func NewRotateCommand( osExecutor os.OsExecutor, rsaSvc *rsa.Service, - encryptedPassphraseSvc external_interfaces.EncryptedPassphraseService, - encryptedPayloadSvc external_interfaces.EncryptedPayloadService, + b64Svc external_interfaces.Base64Service, + aesSvc external_interfaces.AesService, ) *cobra.Command { cmdInstance := &cobra.Command{ Use: "rotate " + @@ -42,7 +45,7 @@ func NewRotateCommand( Long: "Rotate (decrypt and encrypt) a file/value using AES256-GCM symmetric encryption. " + "Public key must originate from same private key, otherwise you probably want" + "to use `rekey` instead. " + - "Passfile runtime random generated and encrypted with RSA asymmetric keypair.", + "Passphrase is runtime randomly generated and encrypted with RSA asymmetric keypair.", RunE: func(cmdInstance *cobra.Command, args []string) error { publicKeyPath := cmdInstance.Flag("public-key-path").Value.String() @@ -98,7 +101,9 @@ func NewRotateCommand( } } - encryptedPayload, err := encryptedPayloadSvc.Deserialize(inFileContent) + payloadSerdeSvc := payload.NewSerdeService(b64Svc) + + encryptedPayload, err := payloadSerdeSvc.Deserialize(inFileContent) if err != nil { return stacktrace.Propagate( err, @@ -106,7 +111,11 @@ func NewRotateCommand( ) } - payload, err := encryptedPayloadSvc.Decrypt(privKey, encryptedPayload) + contentV1Svc := content.NewV1Service(b64Svc, aesSvc) + oldPassphraseDecrypter := passphrase.NewDecryptionRsaPKCS1v15Service(privKey, rsaSvc) + oldPayloadDecrypter := payload.NewDecryptionService(oldPassphraseDecrypter, contentV1Svc) + + payloadInstance, err := oldPayloadDecrypter.Decrypt(encryptedPayload) if err != nil { return stacktrace.Propagate( err, @@ -114,7 +123,7 @@ func NewRotateCommand( ) } - passphrase, err := encryptedPassphraseSvc.GeneratePassphrase(32) + passphraseInstance, err := passphrase.NewService().GeneratePassphrase(32) if err != nil { return stacktrace.Propagate( err, @@ -124,9 +133,12 @@ func NewRotateCommand( // NOTE: Change passphrase with new one, // and encrypt the payload anew. - payload.Passphrase = passphrase + payloadInstance.Passphrase = passphraseInstance + + passphraseEncrypter := passphrase.NewEncryptionRsaPKCS1v15Service(rsaSvc, pubKey) + payloadEncrypter := payload.NewEncryptionService(passphraseEncrypter, contentV1Svc) - newEncryptedPayload, err := encryptedPayloadSvc.Encrypt(pubKey, payload) + newEncryptedPayload, err := payloadEncrypter.Encrypt(payloadInstance) if err != nil { return stacktrace.Propagate( err, @@ -134,7 +146,7 @@ func NewRotateCommand( ) } - serializedEncryptedPayload, err := encryptedPayloadSvc.Serialize(newEncryptedPayload) + serializedEncryptedPayload, err := payloadSerdeSvc.Serialize(newEncryptedPayload) if err != nil { return stacktrace.Propagate( err, @@ -171,16 +183,16 @@ func NewRotateCommand( "", "Path to RSA public key used to encrypt runtime random generated passphrase.", ) - //nolint:errcheck - cmdInstance.MarkPersistentFlagRequired("public-key-path") + + _ = cmdInstance.MarkPersistentFlagRequired("public-key-path") cmdInstance.PersistentFlags().String( "private-key-path", "", "Path to RSA private key used to decrypt specified `in` path content.", ) - //nolint:errcheck - cmdInstance.MarkPersistentFlagRequired("private-key-path") + + _ = cmdInstance.MarkPersistentFlagRequired("private-key-path") cmdInstance.PersistentFlags().String( "in", diff --git a/cmd/terraform.go b/cmd/terraform.go index 9da9bea..ea2c6e0 100644 --- a/cmd/terraform.go +++ b/cmd/terraform.go @@ -25,12 +25,9 @@ import ( func NewTerraformCmd( osExecutor os.OsExecutor, rsaSvc external_interfaces.RsaService, - iniSvc external_interfaces.IniService, - encryptedPassphraseSvc external_interfaces.EncryptedPassphraseService, - legacyEncryptedContentSvc external_interfaces.EncryptedContentService, - v1EncryptedPayloadSvc external_interfaces.EncryptedPayloadService, hclSvc external_interfaces.HclService, - tfEncryptionMigrationSvc external_interfaces.TerraformEncryptionMigrationService, + b64Svc external_interfaces.Base64Service, + aesSvc external_interfaces.AesService, ) *cobra.Command { cmdInstance := &cobra.Command{ Use: "terraform", @@ -45,12 +42,9 @@ func NewTerraformCmd( terraformCmd.NewVaultCmd( osExecutor, rsaSvc, - iniSvc, - encryptedPassphraseSvc, - legacyEncryptedContentSvc, - v1EncryptedPayloadSvc, hclSvc, - tfEncryptionMigrationSvc, + b64Svc, + aesSvc, ), ) diff --git a/cmd/terraform/vault.go b/cmd/terraform/vault.go index afb2b06..0899c50 100644 --- a/cmd/terraform/vault.go +++ b/cmd/terraform/vault.go @@ -25,12 +25,9 @@ import ( func NewVaultCmd( osExecutor os.OsExecutor, rsaSvc external_interfaces.RsaService, - iniSvc external_interfaces.IniService, - encryptedPassphraseSvc external_interfaces.EncryptedPassphraseService, - legacyEncryptedContentSvc external_interfaces.EncryptedContentService, - v1EncryptedPayloadSvc external_interfaces.EncryptedPayloadService, hclSvc external_interfaces.HclService, - tfEncryptionMigrationSvc external_interfaces.TerraformEncryptionMigrationService, + b64Svc external_interfaces.Base64Service, + aesSvc external_interfaces.AesService, ) *cobra.Command { cmdInstance := &cobra.Command{ Use: "vault", @@ -45,42 +42,22 @@ func NewVaultCmd( vault.NewNewResourceCommand( osExecutor, rsaSvc, - encryptedPassphraseSvc, - v1EncryptedPayloadSvc, - ), - vault.NewMigrateCommand( - osExecutor, - rsaSvc, - encryptedPassphraseSvc, - legacyEncryptedContentSvc, - v1EncryptedPayloadSvc, - hclSvc, - tfEncryptionMigrationSvc, + b64Svc, + aesSvc, ), vault.NewRotateCommand( osExecutor, rsaSvc, - encryptedPassphraseSvc, - v1EncryptedPayloadSvc, hclSvc, - tfEncryptionMigrationSvc, + b64Svc, + aesSvc, ), vault.NewRekeyCommand( osExecutor, rsaSvc, - encryptedPassphraseSvc, - v1EncryptedPayloadSvc, - hclSvc, - tfEncryptionMigrationSvc, - ), - vault.NewIniCommand( - osExecutor, - rsaSvc, - iniSvc, - encryptedPassphraseSvc, - v1EncryptedPayloadSvc, hclSvc, - tfEncryptionMigrationSvc, + b64Svc, + aesSvc, ), ) diff --git a/cmd/terraform/vault/ini.go b/cmd/terraform/vault/ini.go deleted file mode 100644 index 1b59fba..0000000 --- a/cmd/terraform/vault/ini.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2018 SumUp Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package vault - -import ( - "github.com/palantir/stacktrace" - "github.com/spf13/cobra" - "github.com/sumup-oss/go-pkgs/os" - - "github.com/sumup-oss/vaulted/cmd/external_interfaces" -) - -func NewIniCommand( - osExecutor os.OsExecutor, - rsaSvc external_interfaces.RsaService, - iniSvc external_interfaces.IniService, - encryptedPassphraseSvc external_interfaces.EncryptedPassphraseService, - encryptedPayloadSvc external_interfaces.EncryptedPayloadService, - hclSvc external_interfaces.HclService, - terraformEncryptionMigrationSvc external_interfaces.TerraformEncryptionMigrationService, -) *cobra.Command { - cmdInstance := &cobra.Command{ - Use: "ini --public-key-path ./my-key.pem --in ./secrets.ini --out ./secrets.tf", - Short: "Convert an INI file to Terraform file", - Long: "Convert an INI file to Terraform file with vaulted_vault_secret resources, " + - "encrypted with AES256-GCM symmetric encryption. " + - "Passfile is random generated during runtime and encrypted with RSA asymmetric keypair.", - RunE: func(cmdInstance *cobra.Command, _ []string) error { - publicKeyPath := cmdInstance.Flag("public-key-path").Value.String() - pubKey, err := rsaSvc.ReadPublicKeyFromPath(publicKeyPath) - if err != nil { - return stacktrace.Propagate( - err, - "failed to read specified public key", - ) - } - - inPath := cmdInstance.Flag("in").Value.String() - - iniFile, err := iniSvc.ReadIniAtPath(inPath) - if err != nil { - return stacktrace.Propagate( - err, - "failed to read specified INI file", - ) - } - - iniContent := iniSvc.ParseIniFileContents(iniFile) - - hclFile, err := terraformEncryptionMigrationSvc.ConvertIniContentToV1ResourceHCL( - 32, - iniContent, - pubKey, - encryptedPassphraseSvc, - encryptedPayloadSvc, - ) - if err != nil { - return stacktrace.Propagate( - err, - "failed to transform INI content to HCL", - ) - } - - outPath := cmdInstance.Flag("out").Value.String() - err = osExecutor.WriteFile(outPath, hclFile.Bytes(), 0755) - return stacktrace.Propagate( - err, - "failed to write HCL to file", - ) - }, - } - - cmdInstance.PersistentFlags().String( - "public-key-path", - "", - "Path to RSA public key used to encrypt runtime random generated passphrase.", - ) - //nolint:errcheck - cmdInstance.MarkPersistentFlagRequired("public-key-path") - - cmdInstance.PersistentFlags().String( - "in", - "", - "Path to the input INI file", - ) - //nolint:errcheck - cmdInstance.MarkPersistentFlagRequired("in") - - cmdInstance.PersistentFlags().String( - "out", - "", - "Path to the output terraform file", - ) - //nolint:errcheck - cmdInstance.MarkPersistentFlagRequired("out") - - return cmdInstance -} diff --git a/cmd/terraform/vault/ini_integration_test.go b/cmd/terraform/vault/ini_integration_test.go deleted file mode 100644 index 918b456..0000000 --- a/cmd/terraform/vault/ini_integration_test.go +++ /dev/null @@ -1,208 +0,0 @@ -// Copyright 2018 SumUp Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package vault - -import ( - "bytes" - "crypto/rand" - stdRsa "crypto/rsa" - "fmt" - "path/filepath" - "testing" - - "github.com/sumup-oss/vaulted/pkg/ini" - "github.com/sumup-oss/vaulted/pkg/terraform_encryption_migration" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/sumup-oss/go-pkgs/os" - "github.com/sumup-oss/go-pkgs/os/ostest" - "github.com/sumup-oss/go-pkgs/testutils" - "github.com/sumup-oss/vaulted/pkg/aes" - "github.com/sumup-oss/vaulted/pkg/base64" - "github.com/sumup-oss/vaulted/pkg/hcl" - "github.com/sumup-oss/vaulted/pkg/pkcs7" - "github.com/sumup-oss/vaulted/pkg/rsa" - "github.com/sumup-oss/vaulted/pkg/terraform" - vaultedTestUtils "github.com/sumup-oss/vaulted/pkg/testutils" - "github.com/sumup-oss/vaulted/pkg/vaulted/content" - "github.com/sumup-oss/vaulted/pkg/vaulted/header" - "github.com/sumup-oss/vaulted/pkg/vaulted/passphrase" - "github.com/sumup-oss/vaulted/pkg/vaulted/payload" -) - -func TestIniCmd_Execute(t *testing.T) { - t.Run( - "with no arguments, it returns error", - func(t *testing.T) { - t.Parallel() - - outputBuff := &bytes.Buffer{} - - osExecutor := ostest.NewFakeOsExecutor(t) - - b64Svc := base64.NewBase64Service() - rsaSvc := rsa.NewRsaService(osExecutor) - aesSvc := aes.NewAesService(pkcs7.NewPkcs7Service()) - encPassphraseSvc := passphrase.NewEncryptedPassphraseService(b64Svc, rsaSvc) - encContentSvc := content.NewV1EncryptedContentService(b64Svc, aesSvc) - encPayloadSvc := payload.NewEncryptedPayloadService( - header.NewHeaderService(), - encPassphraseSvc, - encContentSvc, - ) - hclSvc := hcl.NewHclService() - tfSvc := terraform.NewTerraformService() - tfEncMigrationSvc := terraform_encryption_migration.NewTerraformEncryptionMigrationService( - tfSvc, - ) - iniSvc := ini.NewIniService() - - cmdInstance := NewIniCommand( - osExecutor, - rsaSvc, - iniSvc, - encPassphraseSvc, - encPayloadSvc, - hclSvc, - tfEncMigrationSvc, - ) - - _, err := testutils.RunCommandInSameProcess( - cmdInstance, - []string{}, - outputBuff, - ) - - assert.Equal( - t, - `required flag(s) "in", "out", "public-key-path" not set`, - err.Error(), - ) - }, - ) - - t.Run( - "with 'public-key-path', 'in' and 'out' flags specified "+ - "it writes migrated terraform resources at `out`", - func(t *testing.T) { - tmpDir := testutils.TestCwd(t, "vaulted") - - realOsExecutor := &os.RealOsExecutor{} - - iniContent := []byte(`[sectionExample] -myKey=example - -[sectionExampleAgain] -myOtherKey=exampleother -`) - inPathFlag := filepath.Join(tmpDir, "in.ini") - - err := realOsExecutor.WriteFile(inPathFlag, iniContent, 0644) - require.Nil(t, err) - - privKey, err := stdRsa.GenerateKey(rand.Reader, 2048) - require.Nil(t, err) - - pubkeyPath := testutils.GenerateAndWritePublicKey(t, tmpDir, "key.pub", privKey) - - rsaSvc := rsa.NewRsaService(realOsExecutor) - b64Svc := base64.NewBase64Service() - - encPassphraseSvc := passphrase.NewEncryptedPassphraseService(b64Svc, rsaSvc) - - aesSvc := aes.NewAesService(pkcs7.NewPkcs7Service()) - encContentSvc := content.NewV1EncryptedContentService(b64Svc, aesSvc) - encPayloadSvc := payload.NewEncryptedPayloadService( - header.NewHeaderService(), - encPassphraseSvc, - encContentSvc, - ) - hclSvc := hcl.NewHclService() - tfSvc := terraform.NewTerraformService() - iniSvc := ini.NewIniService() - tfEncMigrationSvc := terraform_encryption_migration.NewTerraformEncryptionMigrationService( - tfSvc, - ) - - outPathFlag := filepath.Join(tmpDir, "out.tf") - cmdInstance := NewIniCommand( - realOsExecutor, - rsaSvc, - iniSvc, - encPassphraseSvc, - encPayloadSvc, - hclSvc, - tfEncMigrationSvc, - ) - - cmdArgs := []string{ - fmt.Sprintf("--public-key-path=%s", pubkeyPath), - fmt.Sprintf("--in=%s", inPathFlag), - fmt.Sprintf("--out=%s", outPathFlag), - } - - var outputBuff bytes.Buffer - _, err = testutils.RunCommandInSameProcess( - cmdInstance, - cmdArgs, - &outputBuff, - ) - require.Nil(t, err) - assert.Equal(t, "", outputBuff.String()) - - outContent, err := realOsExecutor.ReadFile(outPathFlag) - require.Nil(t, err) - - regexMatches := vaultedTestUtils.NewTerraformRegex.FindAllStringSubmatch( - string(outContent), - -1, - ) - require.Equal(t, 2, len(regexMatches)) - - assert.Equal( - t, - "vaulted_vault_secret_sectionExampleAgain_myOtherKey", - regexMatches[0][1], - ) - assert.Equal( - t, - "secret/sectionExampleAgain/myOtherKey", - regexMatches[0][2], - ) - assert.NotEqual( - t, - "", - regexMatches[0][3], - ) - - assert.Equal( - t, - "vaulted_vault_secret_sectionExample_myKey", - regexMatches[1][1], - ) - assert.Equal( - t, - "secret/sectionExample/myKey", - regexMatches[1][2], - ) - assert.NotEqual( - t, - "", - regexMatches[1][3], - ) - }, - ) -} diff --git a/cmd/terraform/vault/migrate.go b/cmd/terraform/vault/migrate.go deleted file mode 100644 index a973055..0000000 --- a/cmd/terraform/vault/migrate.go +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright 2018 SumUp Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package vault - -import ( - "github.com/palantir/stacktrace" - "github.com/spf13/cobra" - "github.com/sumup-oss/go-pkgs/os" - - "github.com/sumup-oss/vaulted/cmd/external_interfaces" - "github.com/sumup-oss/vaulted/internal/cli" -) - -func NewMigrateCommand( - osExecutor os.OsExecutor, - rsaSvc external_interfaces.RsaService, - encryptedPassphraseSvc external_interfaces.EncryptedPassphraseService, - legacyEncryptedContentSvc external_interfaces.EncryptedContentService, - v1EncryptedPayloadSvc external_interfaces.EncryptedPayloadService, - hclSvc external_interfaces.HclService, - tfEncryptionMigrationSvc external_interfaces.TerraformEncryptionMigrationService, -) *cobra.Command { - cmdInstance := &cobra.Command{ - Use: "migrate --public-key-path ./my-pubkey.pem " + - "--private-key-path ./my-privkey.pem " + - "--in ./mysecret.txt " + - "--out ./mysecret.tf ", - Short: "Reads terraform resources file and migrates them to new encryption format", - Long: "Reads terraform resources file and migrates them to new encryption format", - RunE: func(cmdInstance *cobra.Command, args []string) error { - publicKeyPath := cmdInstance.Flag("public-key-path").Value.String() - - // NOTE: Read early to avoid needless encryption - pubKey, err := rsaSvc.ReadPublicKeyFromPath(publicKeyPath) - if err != nil { - return stacktrace.Propagate( - err, - "failed to read specified public key", - ) - } - - privateKeyPath := cmdInstance.Flag("private-key-path").Value.String() - - // NOTE: Read early to avoid needless encryption - privKey, err := rsaSvc.ReadPrivateKeyFromPath(privateKeyPath) - if err != nil { - return stacktrace.Propagate( - err, - "failed to read specified private key", - ) - } - - var inFileContent []byte - - inFilePath := cmdInstance.Flag("in").Value.String() - if inFilePath == "" { - inFileContent, err = cli.ReadFromStdin( - osExecutor, - "Enter terraform content to migrate: ", - ) - if err != nil { - return stacktrace.Propagate( - err, - "failed to read user input from stdin", - ) - } - } else { - inFileContent, err = osExecutor.ReadFile(inFilePath) - if err != nil { - return stacktrace.Propagate( - err, - "failed to read specified in file path", - ) - } - } - - hclFile, err := tfEncryptionMigrationSvc.MigrateEncryptedTerraformResourceHcl( - hclSvc, - inFileContent, - privKey, - pubKey, - legacyEncryptedContentSvc, - encryptedPassphraseSvc, - v1EncryptedPayloadSvc, - ) - if err != nil { - return stacktrace.Propagate( - err, - "failed to migrate read terraform resources", - ) - } - - return cli.WriteHCLout( - osExecutor, - cmdInstance.Flag("out").Value.String(), - hclFile, - ) - }, - } - - cmdInstance.PersistentFlags().String( - "public-key-path", - "", - "Path to RSA public key used to encrypt runtime random generated passphrase.", - ) - //nolint:errcheck - cmdInstance.MarkPersistentFlagRequired("public-key-path") - - cmdInstance.PersistentFlags().String( - "private-key-path", - "", - "Path to RSA private key used to decrypt runtime random generated passphrase.", - ) - //nolint:errcheck - cmdInstance.MarkPersistentFlagRequired("private-key-path") - - cmdInstance.PersistentFlags().String( - "in", - "", - "Path to the input file.", - ) - cmdInstance.PersistentFlags().String( - "out", - "", - "Path to the output file, that's going to be created if not exists, otherwise appended to.", - ) - - return cmdInstance -} diff --git a/cmd/terraform/vault/new_resource.go b/cmd/terraform/vault/new_resource.go index e2ec235..53bdbfc 100644 --- a/cmd/terraform/vault/new_resource.go +++ b/cmd/terraform/vault/new_resource.go @@ -28,14 +28,15 @@ import ( "github.com/sumup-oss/vaulted/pkg/vaulted" "github.com/sumup-oss/vaulted/pkg/vaulted/content" "github.com/sumup-oss/vaulted/pkg/vaulted/header" + "github.com/sumup-oss/vaulted/pkg/vaulted/passphrase" "github.com/sumup-oss/vaulted/pkg/vaulted/payload" ) func NewNewResourceCommand( osExecutor os.OsExecutor, rsaSvc external_interfaces.RsaService, - encryptedPassphraseSvc external_interfaces.EncryptedPassphraseService, - encryptedPayloadSvc external_interfaces.EncryptedPayloadService, + b64Svc external_interfaces.Base64Service, + aesSvc external_interfaces.AesService, ) *cobra.Command { cmdInstance := &cobra.Command{ Use: "new-resource --public-key-path ./my-pubkey.pem " + @@ -81,9 +82,10 @@ func NewNewResourceCommand( } } - content := content.NewContent(inFileContent) + contentInstance := content.NewContent(inFileContent) + passphraseSvc := passphrase.NewService() - passphrase, err := encryptedPassphraseSvc.GeneratePassphrase(32) + generatedPassphrase, err := passphraseSvc.GeneratePassphrase(32) if err != nil { return stacktrace.Propagate( err, @@ -91,13 +93,17 @@ func NewNewResourceCommand( ) } - payload := payload.NewPayload( + payloadInstance := payload.NewPayload( header.NewHeader(), - passphrase, - content, + generatedPassphrase, + contentInstance, ) - encryptedPayload, err := encryptedPayloadSvc.Encrypt(pubKey, payload) + contentEncrypter := content.NewV1Service(b64Svc, aesSvc) + passphraseEncrypter := passphrase.NewEncryptionRsaPKCS1v15Service(rsaSvc, pubKey) + encryptionService := payload.NewEncryptionService(passphraseEncrypter, contentEncrypter) + + encryptedPayload, err := encryptionService.Encrypt(payloadInstance) if err != nil { return stacktrace.Propagate( err, @@ -105,7 +111,9 @@ func NewNewResourceCommand( ) } - serializedEncryptedPayload, err := encryptedPayloadSvc.Serialize(encryptedPayload) + payloadSerdeSvc := payload.NewSerdeService(b64Svc) + + serializedEncryptedPayload, err := payloadSerdeSvc.Serialize(encryptedPayload) if err != nil { return stacktrace.Propagate( err, @@ -134,11 +142,12 @@ func NewNewResourceCommand( hclFile := hclwrite.NewEmptyFile() hclFile.Body().AppendBlock(block) - return cli.WriteHCLout( + err = cli.WriteHCLout( osExecutor, outFilePath, hclFile, ) + return stacktrace.Propagate(err, "failed to write HCL to %s", outFilePath) }, } diff --git a/cmd/terraform/vault/new_resource_integration_test.go b/cmd/terraform/vault/new_resource_integration_test.go index 37a1e2d..cad55f0 100644 --- a/cmd/terraform/vault/new_resource_integration_test.go +++ b/cmd/terraform/vault/new_resource_integration_test.go @@ -37,10 +37,6 @@ import ( "github.com/sumup-oss/vaulted/pkg/base64" "github.com/sumup-oss/vaulted/pkg/pkcs7" "github.com/sumup-oss/vaulted/pkg/rsa" - "github.com/sumup-oss/vaulted/pkg/vaulted/content" - "github.com/sumup-oss/vaulted/pkg/vaulted/header" - "github.com/sumup-oss/vaulted/pkg/vaulted/passphrase" - "github.com/sumup-oss/vaulted/pkg/vaulted/payload" ) func TestNewResourceCmd_Execute(t *testing.T) { @@ -56,19 +52,12 @@ func TestNewResourceCmd_Execute(t *testing.T) { b64Svc := base64.NewBase64Service() rsaSvc := rsa.NewRsaService(osExecutor) aesSvc := aes.NewAesService(pkcs7.NewPkcs7Service()) - encPassphraseSvc := passphrase.NewEncryptedPassphraseService(b64Svc, rsaSvc) - encContentSvc := content.NewV1EncryptedContentService(b64Svc, aesSvc) - encPayloadSvc := payload.NewEncryptedPayloadService( - header.NewHeaderService(), - encPassphraseSvc, - encContentSvc, - ) cmdInstance := NewNewResourceCommand( osExecutor, rsaSvc, - encPassphraseSvc, - encPayloadSvc, + b64Svc, + aesSvc, ) _, err := gopkgsTestUtils.RunCommandInSameProcess( @@ -122,22 +111,12 @@ func TestNewResourceCmd_Execute(t *testing.T) { rsaSvc := rsa.NewRsaService(realOsExecutor) b64Svc := base64.NewBase64Service() - encPassphraseSvc := passphrase.NewEncryptedPassphraseService(b64Svc, rsaSvc) - - aesSvc := aes.NewAesService(pkcs7.NewPkcs7Service()) - encContentSvc := content.NewV1EncryptedContentService(b64Svc, aesSvc) - encPayloadSvc := payload.NewEncryptedPayloadService( - header.NewHeaderService(), - encPassphraseSvc, - encContentSvc, - ) - outPathFlag := filepath.Join(tmpDir, "out.tf") cmdInstance := NewNewResourceCommand( realOsExecutor, rsaSvc, - encPassphraseSvc, - encPayloadSvc, + b64Svc, + aes.NewAesService(pkcs7.NewPkcs7Service()), ) pathArg := "secret/exampleapp/example" diff --git a/cmd/terraform/vault/new_resource_test.go b/cmd/terraform/vault/new_resource_test.go index 81d3b56..38b0df0 100644 --- a/cmd/terraform/vault/new_resource_test.go +++ b/cmd/terraform/vault/new_resource_test.go @@ -23,10 +23,6 @@ import ( "github.com/sumup-oss/vaulted/pkg/base64" "github.com/sumup-oss/vaulted/pkg/pkcs7" "github.com/sumup-oss/vaulted/pkg/rsa" - "github.com/sumup-oss/vaulted/pkg/vaulted/content" - "github.com/sumup-oss/vaulted/pkg/vaulted/header" - "github.com/sumup-oss/vaulted/pkg/vaulted/passphrase" - "github.com/sumup-oss/vaulted/pkg/vaulted/payload" ) func TestNewNewResourceCmd(t *testing.T) { @@ -36,15 +32,8 @@ func TestNewNewResourceCmd(t *testing.T) { b64Svc := base64.NewBase64Service() rsaSvc := rsa.NewRsaService(osExecutor) aesSvc := aes.NewAesService(pkcs7.NewPkcs7Service()) - encPassphraseSvc := passphrase.NewEncryptedPassphraseService(b64Svc, rsaSvc) - encContentSvc := content.NewV1EncryptedContentService(b64Svc, aesSvc) - encPayloadSvc := payload.NewEncryptedPayloadService( - header.NewHeaderService(), - encPassphraseSvc, - encContentSvc, - ) - actual := NewNewResourceCommand(osExecutor, rsaSvc, encPassphraseSvc, encPayloadSvc) + actual := NewNewResourceCommand(osExecutor, rsaSvc, b64Svc, aesSvc) assert.Equal( t, diff --git a/cmd/terraform/vault/rekey.go b/cmd/terraform/vault/rekey.go index 57b76cc..2046c2c 100644 --- a/cmd/terraform/vault/rekey.go +++ b/cmd/terraform/vault/rekey.go @@ -21,15 +21,19 @@ import ( "github.com/sumup-oss/vaulted/cmd/external_interfaces" "github.com/sumup-oss/vaulted/internal/cli" + "github.com/sumup-oss/vaulted/pkg/terraform" + "github.com/sumup-oss/vaulted/pkg/terraform_encryption_migration" + "github.com/sumup-oss/vaulted/pkg/vaulted/content" + "github.com/sumup-oss/vaulted/pkg/vaulted/passphrase" + "github.com/sumup-oss/vaulted/pkg/vaulted/payload" ) func NewRekeyCommand( osExecutor os.OsExecutor, rsaSvc external_interfaces.RsaService, - encryptedPassphraseSvc external_interfaces.EncryptedPassphraseService, - v1EncryptedPayloadSvc external_interfaces.EncryptedPayloadService, hclSvc external_interfaces.HclService, - tfEncryptionMigrationSvc external_interfaces.TerraformEncryptionMigrationService, + b64Svc external_interfaces.Base64Service, + aesSvc external_interfaces.AesService, ) *cobra.Command { cmdInstance := &cobra.Command{ Use: "rekey --old-private-key-path ./old-my-privatekey.pem " + @@ -41,7 +45,7 @@ func NewRekeyCommand( "AES256-GCM symmetric encryption. " + "Public key must NOT originate from same private key, otherwise you probably want" + "to use `rotate` instead. " + - "Passfile runtime random generated and encrypted with RSA asymmetric keypair.", + "Passphrase is runtime randomly generated and encrypted with RSA asymmetric keypair.", RunE: func(cmdInstance *cobra.Command, args []string) error { oldPrivateKeyPath := cmdInstance.Flag("old-private-key-path").Value.String() // NOTE: Read early to avoid needless decryption @@ -97,13 +101,24 @@ func NewRekeyCommand( } } + tfSvc := terraform.NewService() + payloadSerdeSvc := payload.NewSerdeService(b64Svc) + contentV1Svc := content.NewV1Service(b64Svc, aesSvc) + oldPassphraseDecrypter := passphrase.NewDecryptionRsaPKCS1v15Service(oldPrivKey, rsaSvc) + oldPayloadDecrypter := payload.NewDecryptionService(oldPassphraseDecrypter, contentV1Svc) + + passphraseSvc := passphrase.NewService() + passphraseEncrypter := passphrase.NewEncryptionRsaPKCS1v15Service(rsaSvc, newPubKey) + payloadEncrypter := payload.NewEncryptionService(passphraseEncrypter, contentV1Svc) + + tfEncryptionMigrationSvc := terraform_encryption_migration.NewTerraformEncryptionMigrationService(tfSvc) hclFile, err := tfEncryptionMigrationSvc.RotateOrRekeyEncryptedTerraformResourceHcl( hclSvc, inFileContent, - oldPrivKey, - newPubKey, - encryptedPassphraseSvc, - v1EncryptedPayloadSvc, + passphraseSvc, + payloadSerdeSvc, + oldPayloadDecrypter, + payloadEncrypter, ) if err != nil { return stacktrace.Propagate( diff --git a/cmd/terraform/vault/rotate.go b/cmd/terraform/vault/rotate.go index c6881b3..c7af6e8 100644 --- a/cmd/terraform/vault/rotate.go +++ b/cmd/terraform/vault/rotate.go @@ -21,15 +21,19 @@ import ( "github.com/sumup-oss/vaulted/cmd/external_interfaces" "github.com/sumup-oss/vaulted/internal/cli" + "github.com/sumup-oss/vaulted/pkg/terraform" + "github.com/sumup-oss/vaulted/pkg/terraform_encryption_migration" + "github.com/sumup-oss/vaulted/pkg/vaulted/content" + "github.com/sumup-oss/vaulted/pkg/vaulted/passphrase" + "github.com/sumup-oss/vaulted/pkg/vaulted/payload" ) func NewRotateCommand( osExecutor os.OsExecutor, rsaSvc external_interfaces.RsaService, - encryptedPassphraseSvc external_interfaces.EncryptedPassphraseService, - v1EncryptedPayloadSvc external_interfaces.EncryptedPayloadService, hclSvc external_interfaces.HclService, - tfEncryptionMigrationSvc external_interfaces.TerraformEncryptionMigrationService, + b64Svc external_interfaces.Base64Service, + aesSvc external_interfaces.AesService, ) *cobra.Command { cmdInstance := &cobra.Command{ Use: "rotate " + @@ -41,7 +45,7 @@ func NewRotateCommand( Long: "Rotate (decrypt and encrypt) existing terraform resources using AES256-GCM encryption. " + "Public key must originate from same private key, otherwise you probably want" + "to use `rekey` instead. " + - "Passfile runtime random generated and encrypted with RSA asymmetric keypair.", + "Passphrase is runtime randomly generated and encrypted with RSA asymmetric keypair.", RunE: func(cmdInstance *cobra.Command, args []string) error { publicKeyPath := cmdInstance.Flag("public-key-path").Value.String() @@ -97,13 +101,24 @@ func NewRotateCommand( } } + tfSvc := terraform.NewService() + payloadSerdeSvc := payload.NewSerdeService(b64Svc) + contentV1Svc := content.NewV1Service(b64Svc, aesSvc) + oldPassphraseDecrypter := passphrase.NewDecryptionRsaPKCS1v15Service(privKey, rsaSvc) + oldPayloadDecrypter := payload.NewDecryptionService(oldPassphraseDecrypter, contentV1Svc) + + passphraseSvc := passphrase.NewService() + passphraseEncrypter := passphrase.NewEncryptionRsaPKCS1v15Service(rsaSvc, pubKey) + payloadEncrypter := payload.NewEncryptionService(passphraseEncrypter, contentV1Svc) + + tfEncryptionMigrationSvc := terraform_encryption_migration.NewTerraformEncryptionMigrationService(tfSvc) hclFile, err := tfEncryptionMigrationSvc.RotateOrRekeyEncryptedTerraformResourceHcl( hclSvc, inFileContent, - privKey, - pubKey, - encryptedPassphraseSvc, - v1EncryptedPayloadSvc, + passphraseSvc, + payloadSerdeSvc, + oldPayloadDecrypter, + payloadEncrypter, ) if err != nil { return stacktrace.Propagate( diff --git a/cmd/terraform/vault_test.go b/cmd/terraform/vault_test.go index 2c166e3..5dd12a4 100644 --- a/cmd/terraform/vault_test.go +++ b/cmd/terraform/vault_test.go @@ -24,15 +24,8 @@ import ( "github.com/sumup-oss/vaulted/pkg/aes" "github.com/sumup-oss/vaulted/pkg/base64" "github.com/sumup-oss/vaulted/pkg/hcl" - "github.com/sumup-oss/vaulted/pkg/ini" "github.com/sumup-oss/vaulted/pkg/pkcs7" "github.com/sumup-oss/vaulted/pkg/rsa" - "github.com/sumup-oss/vaulted/pkg/terraform" - "github.com/sumup-oss/vaulted/pkg/terraform_encryption_migration" - "github.com/sumup-oss/vaulted/pkg/vaulted/content" - "github.com/sumup-oss/vaulted/pkg/vaulted/header" - "github.com/sumup-oss/vaulted/pkg/vaulted/passphrase" - "github.com/sumup-oss/vaulted/pkg/vaulted/payload" ) func TestNewVaultCmd(t *testing.T) { @@ -42,27 +35,14 @@ func TestNewVaultCmd(t *testing.T) { b64Svc := base64.NewBase64Service() rsaSvc := rsa.NewRsaService(osExecutor) aesSvc := aes.NewAesService(pkcs7.NewPkcs7Service()) - encPassphraseSvc := passphrase.NewEncryptedPassphraseService(b64Svc, rsaSvc) - legacyEncContentSvc := content.NewLegacyEncryptedContentService(b64Svc, aesSvc) - encContentSvc := content.NewV1EncryptedContentService(b64Svc, aesSvc) - encPayloadSvc := payload.NewEncryptedPayloadService( - header.NewHeaderService(), - encPassphraseSvc, - encContentSvc, - ) hclSvc := hcl.NewHclService() - tfSvc := terraform.NewTerraformService() - tfEncMigrationSvc := terraform_encryption_migration.NewTerraformEncryptionMigrationService(tfSvc) actual := NewVaultCmd( osExecutor, rsaSvc, - ini.NewIniService(), - encPassphraseSvc, - legacyEncContentSvc, - encPayloadSvc, hclSvc, - tfEncMigrationSvc, + b64Svc, + aesSvc, ) assert.Equal(t, "vault", actual.Use) @@ -79,27 +59,14 @@ func TestVaultCmd_Execute(t *testing.T) { b64Svc := base64.NewBase64Service() rsaSvc := rsa.NewRsaService(osExecutor) aesSvc := aes.NewAesService(pkcs7.NewPkcs7Service()) - encPassphraseSvc := passphrase.NewEncryptedPassphraseService(b64Svc, rsaSvc) - encContentSvc := content.NewV1EncryptedContentService(b64Svc, aesSvc) - encPayloadSvc := payload.NewEncryptedPayloadService( - header.NewHeaderService(), - encPassphraseSvc, - encContentSvc, - ) hclSvc := hcl.NewHclService() - tfSvc := terraform.NewTerraformService() - legacyEncContentSvc := content.NewLegacyEncryptedContentService(b64Svc, aesSvc) - tfEncMigrationSvc := terraform_encryption_migration.NewTerraformEncryptionMigrationService(tfSvc) cmdInstance := NewVaultCmd( osExecutor, rsaSvc, - ini.NewIniService(), - encPassphraseSvc, - legacyEncContentSvc, - encPayloadSvc, hclSvc, - tfEncMigrationSvc, + b64Svc, + aesSvc, ) _, err := gopkgsTestUtils.RunCommandInSameProcess( @@ -118,8 +85,6 @@ Usage: Available Commands: help Help about any command - ini Convert an INI file to Terraform file - migrate Reads terraform resources file and migrates them to new encryption format new-resource Create new terraform vaulted vault secret resource rekey Rekey (decrypt and encrypt using different keypair) existing terraform resources rotate Rotate (decrypt and encrypt) existing terraform resources diff --git a/cmd/terraform_test.go b/cmd/terraform_test.go index c5c2e67..da0cbb1 100644 --- a/cmd/terraform_test.go +++ b/cmd/terraform_test.go @@ -24,15 +24,8 @@ import ( "github.com/sumup-oss/vaulted/pkg/aes" "github.com/sumup-oss/vaulted/pkg/base64" "github.com/sumup-oss/vaulted/pkg/hcl" - "github.com/sumup-oss/vaulted/pkg/ini" "github.com/sumup-oss/vaulted/pkg/pkcs7" "github.com/sumup-oss/vaulted/pkg/rsa" - "github.com/sumup-oss/vaulted/pkg/terraform" - "github.com/sumup-oss/vaulted/pkg/terraform_encryption_migration" - "github.com/sumup-oss/vaulted/pkg/vaulted/content" - "github.com/sumup-oss/vaulted/pkg/vaulted/header" - "github.com/sumup-oss/vaulted/pkg/vaulted/passphrase" - "github.com/sumup-oss/vaulted/pkg/vaulted/payload" ) func TestNewTerraformCmd(t *testing.T) { @@ -42,27 +35,14 @@ func TestNewTerraformCmd(t *testing.T) { b64Svc := base64.NewBase64Service() rsaSvc := rsa.NewRsaService(osExecutor) aesSvc := aes.NewAesService(pkcs7.NewPkcs7Service()) - encPassphraseSvc := passphrase.NewEncryptedPassphraseService(b64Svc, rsaSvc) - legacyEncContentSvc := content.NewLegacyEncryptedContentService(b64Svc, aesSvc) - encContentSvc := content.NewV1EncryptedContentService(b64Svc, aesSvc) - encPayloadSvc := payload.NewEncryptedPayloadService( - header.NewHeaderService(), - encPassphraseSvc, - encContentSvc, - ) hclSvc := hcl.NewHclService() - tfSvc := terraform.NewTerraformService() - tfEncMigrationSvc := terraform_encryption_migration.NewTerraformEncryptionMigrationService(tfSvc) actual := NewTerraformCmd( osExecutor, rsaSvc, - ini.NewIniService(), - encPassphraseSvc, - legacyEncContentSvc, - encPayloadSvc, hclSvc, - tfEncMigrationSvc, + b64Svc, + aesSvc, ) assert.Equal(t, "terraform", actual.Use) @@ -79,27 +59,14 @@ func TestTerraformCmd_Execute(t *testing.T) { b64Svc := base64.NewBase64Service() rsaSvc := rsa.NewRsaService(osExecutor) aesSvc := aes.NewAesService(pkcs7.NewPkcs7Service()) - encPassphraseSvc := passphrase.NewEncryptedPassphraseService(b64Svc, rsaSvc) - encContentSvc := content.NewV1EncryptedContentService(b64Svc, aesSvc) - encPayloadSvc := payload.NewEncryptedPayloadService( - header.NewHeaderService(), - encPassphraseSvc, - encContentSvc, - ) hclSvc := hcl.NewHclService() - tfSvc := terraform.NewTerraformService() - legacyEncContentSvc := content.NewLegacyEncryptedContentService(b64Svc, aesSvc) - tfEncMigrationSvc := terraform_encryption_migration.NewTerraformEncryptionMigrationService(tfSvc) cmdInstance := NewTerraformCmd( osExecutor, rsaSvc, - ini.NewIniService(), - encPassphraseSvc, - legacyEncContentSvc, - encPayloadSvc, hclSvc, - tfEncMigrationSvc, + b64Svc, + aesSvc, ) _, err := gopkgsTestUtils.RunCommandInSameProcess( diff --git a/go.mod b/go.mod index f3f72e4..b2798e6 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,9 @@ module github.com/sumup-oss/vaulted require ( + github.com/aws/aws-sdk-go-v2 v1.8.0 // indirect + github.com/aws/aws-sdk-go-v2/config v1.6.0 + github.com/aws/aws-sdk-go-v2/service/kms v1.4.2 github.com/go-ini/ini v1.62.0 github.com/hashicorp/hcl v1.0.0 github.com/hashicorp/hcl/v2 v2.10.1 @@ -14,4 +17,4 @@ require ( gopkg.in/ini.v1 v1.42.0 // indirect ) -go 1.14 +go 1.16 \ No newline at end of file diff --git a/go.sum b/go.sum index 0e9ad91..41e9dd8 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,26 @@ github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkE github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aws/aws-sdk-go-v2 v1.8.0 h1:HcN6yDnHV9S7D69E7To0aUppJhiJNEzQSNcUxc7r3qo= +github.com/aws/aws-sdk-go-v2 v1.8.0/go.mod h1:xEFuWz+3TYdlPRuo+CqATbeDWIWyaT5uAPwPaWtgse0= +github.com/aws/aws-sdk-go-v2/config v1.6.0 h1:rtoCnNObhVm7me+v9sA2aY+NtHNZjjWWC3ifXVci+wE= +github.com/aws/aws-sdk-go-v2/config v1.6.0/go.mod h1:TNtBVmka80lRPk5+S9ZqVfFszOQAGJJ9KbT3EM3CHNU= +github.com/aws/aws-sdk-go-v2/credentials v1.3.2 h1:Uud/fZzm0lqqhE8kvXYJFAJ3PGnagKoUcvHq1hXfBZw= +github.com/aws/aws-sdk-go-v2/credentials v1.3.2/go.mod h1:PACKuTJdt6AlXvEq8rFI4eDmoqDFC5DpVKQbWysaDgM= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.4.0 h1:SGqDJun6tydgsSIFxv9+EYBJVqVUwg2QMJp6PbNq8C8= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.4.0/go.mod h1:Mj/U8OpDbcVcoctrYwA2bak8k/HFPdcLzI/vaiXMwuM= +github.com/aws/aws-sdk-go-v2/internal/ini v1.2.0 h1:xu45foJnwMwBqSkIMKyJP9kbyHi5hdhZ/WiJ7D2sHZ0= +github.com/aws/aws-sdk-go-v2/internal/ini v1.2.0/go.mod h1:Q5jATQc+f1MfZp3PDMhn6ry18hGvE0i8yvbXoKbnZaE= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.2.2 h1:Xv1rGYgsRRn0xw9JFNnfpBMZam54PrWpC4rJOJ9koA8= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.2.2/go.mod h1:NXmNI41bdEsJMrD0v9rUvbGCB5GwdBEpKvUvIY3vTFg= +github.com/aws/aws-sdk-go-v2/service/kms v1.4.2 h1:1YEn/JSLNyyyWcu0m3ALzxwutTuzdnxHmwhq7M4IFyI= +github.com/aws/aws-sdk-go-v2/service/kms v1.4.2/go.mod h1:tI5X+JLvKFhhclV1aIUMSNcPyanDkuAkQ0gZaimo7nY= +github.com/aws/aws-sdk-go-v2/service/sso v1.3.2 h1:b+U3WrF9ON3f32FH19geqmiod4uKcMv/q+wosQjjyyM= +github.com/aws/aws-sdk-go-v2/service/sso v1.3.2/go.mod h1:J21I6kF+d/6XHVk7kp/cx9YVD2TMD2TbLwtRGVcinXo= +github.com/aws/aws-sdk-go-v2/service/sts v1.6.1 h1:1Pls85C5CFjhE3aH+h85/hyAk89kQNlAWlEQtIkaFyc= +github.com/aws/aws-sdk-go-v2/service/sts v1.6.1/go.mod h1:hLZ/AnkIKHLuPGjEiyghNEdvJ2PP0MgOxcmv9EBJ4xs= +github.com/aws/smithy-go v1.7.0 h1:+cLHMRrDZvQ4wk+KuQ9yH6eEg6KZEJ9RI2IkDqnygCg= +github.com/aws/smithy-go v1.7.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= @@ -56,8 +76,10 @@ github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaW github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= @@ -93,6 +115,8 @@ github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKe github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE= @@ -254,6 +278,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= @@ -276,6 +302,7 @@ gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76 gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/main_e2e_test.go b/main_e2e_test.go index c0b8221..deece3b 100644 --- a/main_e2e_test.go +++ b/main_e2e_test.go @@ -332,15 +332,12 @@ func TestMvpWorkflow(t *testing.T) { assert.Equal(t, inFileContent, rekeyDecryptOutContent) } -// NOTE: Test the terraform workflow that is feasible when you're converting -// "ini"-file based secrets to encrypted terraform resources. +// NOTE: Test the terraform workflow that is feasible when you're producing terraform resources for `terraform-provider-vaulted`. // The flow is: -// 1. terraform vault ini -// 2. terraform vault migrate // TODO: When `terraform view` is added this will verify that the content is still decryptable and viewable. -// 4. terraform vault new-resource -// 5. terraform vault rotate -// 6. terraform vault rekey +// 1. terraform vault new-resource +// 2. terraform vault rotate +// 3. terraform vault rekey // TODO: Investigate why commands that are run do not output to neither stderr nor stdout. // Tests intentionally expect blank stdout/stderr, even though it's wrong, // to pass and later be able to correct to expected output. @@ -360,79 +357,18 @@ func TestV1TerraformWorkflow(t *testing.T) { privKeyPath, privKey := testutils.GenerateAndWritePrivateKey(t, tmpDir, "priv.key") pubKeyPath := testutils.GenerateAndWritePublicKey(t, tmpDir, "pub.key", privKey) - // NOTE: Start of `1. vault ini` - iniContent := []byte(`[sectionExample] -myKey=example - -[sectionExampleAgain] -myOtherKey=exampleother -`) - osExecutor := &os.RealOsExecutor{} - - inputIniFilePath := path.Join(tmpDir, "input.ini") - err := osExecutor.WriteFile(inputIniFilePath, iniContent, 0644) - require.Nil(t, err) - - iniTfFilePath := path.Join(tmpDir, "ini.tf") - stdout, stderr, err := build.Run( - "terraform", - "vault", - "ini", - "--public-key-path", - pubKeyPath, - "--in", - inputIniFilePath, - "--out", - iniTfFilePath, - ) - require.Nil(t, err) - assert.Equal(t, "", stdout) - assert.Equal(t, "", stderr) - - iniTfFileContent, err := osExecutor.ReadFile(iniTfFilePath) - require.Nil(t, err) - - // NOTE: Make sure we actually wrote valid terraform resources - regexMatches := vaultedTestUtils.NewTerraformRegex.FindAllStringSubmatch(string(iniTfFileContent), -1) - assert.Equal(t, 2, len(regexMatches)) - - // NOTE: Start of `2. terraform vault migrate` - migratedTfFilePath := path.Join(tmpDir, "migrated.tf") - stdout, stderr, err = build.Run( - "terraform", - "vault", - "migrate", - "--private-key-path", - privKeyPath, - "--public-key-path", - pubKeyPath, - "--in", - iniTfFilePath, - "--out", - migratedTfFilePath, - ) - require.Nil(t, err) - assert.Equal(t, "", stdout) - assert.Equal(t, "", stderr) - - migratedTfFileContent, err := osExecutor.ReadFile(migratedTfFilePath) - require.Nil(t, err) - - // NOTE: Make sure we actually wrote valid terraform resources - regexMatches = vaultedTestUtils.NewTerraformRegex.FindAllStringSubmatch(string(migratedTfFileContent), -1) - assert.Equal(t, 2, len(regexMatches)) - // NOTE: Start of `4. terraform new-resource` inFilePath := path.Join(tmpDir, "in.raw") + newResourceOutFilePath := path.Join(tmpDir, "out.tf") inFileContent := []byte("mynewsecret") newResourcePathArg := "secret/new-resource/example" newResourceResourceName := "myresource" - err = osExecutor.WriteFile(inFilePath, inFileContent, 0644) + err := osExecutor.WriteFile(inFilePath, inFileContent, 0644) require.Nil(t, err) // NOTE: Append to the same output file as migrated one - stdout, stderr, err = build.Run( + stdout, stderr, err := build.Run( "terraform", "vault", "new-resource", @@ -445,18 +381,18 @@ myOtherKey=exampleother "--in", inFilePath, "--out", - migratedTfFilePath, + newResourceOutFilePath, ) require.Nil(t, err) assert.Equal(t, "", stdout) assert.Equal(t, "", stderr) - migratedTfFileContent, err = osExecutor.ReadFile(migratedTfFilePath) + tfFileContent, err := osExecutor.ReadFile(newResourceOutFilePath) require.Nil(t, err) // NOTE: Make sure we actually wrote valid terraform resources - regexMatches = vaultedTestUtils.NewTerraformRegex.FindAllStringSubmatch(string(migratedTfFileContent), -1) - assert.Equal(t, 3, len(regexMatches)) + regexMatches := vaultedTestUtils.NewTerraformRegex.FindAllStringSubmatch(string(tfFileContent), -1) + assert.Equal(t, 1, len(regexMatches)) rotatedTfFilePath := path.Join(tmpDir, "rotated.tf") @@ -470,7 +406,7 @@ myOtherKey=exampleother "--private-key-path", privKeyPath, "--in", - migratedTfFilePath, + newResourceOutFilePath, "--out", rotatedTfFilePath, ) @@ -483,7 +419,7 @@ myOtherKey=exampleother // NOTE: Make sure we actually wrote valid terraform resources regexMatches = vaultedTestUtils.NewTerraformRegex.FindAllStringSubmatch(string(rotatedTfFileContent), -1) - assert.Equal(t, 3, len(regexMatches)) + assert.Equal(t, 1, len(regexMatches)) rekeyedTfFilePath := path.Join(tmpDir, "rekeyed.tf") @@ -513,5 +449,5 @@ myOtherKey=exampleother // NOTE: Make sure we actually wrote valid terraform resources regexMatches = vaultedTestUtils.NewTerraformRegex.FindAllStringSubmatch(string(rekeyedTfFileContent), -1) - assert.Equal(t, 3, len(regexMatches)) + assert.Equal(t, 1, len(regexMatches)) } diff --git a/pkg/aws/service.go b/pkg/aws/service.go new file mode 100644 index 0000000..4461dd0 --- /dev/null +++ b/pkg/aws/service.go @@ -0,0 +1,56 @@ +// Copyright 2018 SumUp Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package aws + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/aws" + awsconfig "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/kms" + "github.com/palantir/stacktrace" +) + +type Service struct { + cfg *aws.Config +} + +func NewService(ctx context.Context, region string) (*Service, error) { + cfg, err := awsconfig.LoadDefaultConfig(ctx) + if err != nil { + return nil, stacktrace.Propagate(err, "failed to load default AWS config") + } + + if region != "" { + cfg.Region = region + } + + return &Service{cfg: &cfg}, nil +} + +func (s *Service) Decrypt(ctx context.Context, kmsKeyID string, ciphertext []byte) ([]byte, error) { + kmsSvc := kms.NewFromConfig(*s.cfg) + + decryptOutput, err := kmsSvc.Decrypt(ctx, &kms.DecryptInput{ + CiphertextBlob: ciphertext, + EncryptionAlgorithm: "RSAES_OAEP_SHA_256", + KeyId: aws.String(kmsKeyID), + }) + if err != nil { + return nil, stacktrace.Propagate(err, "failed to decrypt using AWS KMS") + } + + return decryptOutput.Plaintext, nil +} diff --git a/pkg/ini/section.go b/pkg/ini/section.go deleted file mode 100644 index 4c3b345..0000000 --- a/pkg/ini/section.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2018 SumUp Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package ini - -type Section struct { - Name string - Values []*SectionValue -} - -func NewIniSection(name string) *Section { - return &Section{name, []*SectionValue{}} -} diff --git a/pkg/ini/section_value.go b/pkg/ini/section_value.go deleted file mode 100644 index caec335..0000000 --- a/pkg/ini/section_value.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2018 SumUp Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package ini - -type SectionValue struct { - KeyName string - Value interface{} -} - -func NewIniSectionValue(key string, value interface{}) *SectionValue { - return &SectionValue{key, value} -} diff --git a/pkg/ini/service.go b/pkg/ini/service.go deleted file mode 100644 index 375b760..0000000 --- a/pkg/ini/service.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2018 SumUp Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package ini - -import ( - "github.com/go-ini/ini" - "github.com/palantir/stacktrace" -) - -const ( - // NOTE: Default section name used by the INI parser. - defaultSectionName = "DEFAULT" -) - -type Service struct{} - -func NewIniService() *Service { - return &Service{} -} - -func (s *Service) ReadIniAtPath(path string) (*ini.File, error) { - cfg, err := ini.LoadSources( - ini.LoadOptions{ - AllowPythonMultilineValues: true, - SpaceBeforeInlineComment: true, - }, - path, - ) - if err != nil { - return nil, stacktrace.Propagate(err, "failed to read ini file") - } - - return cfg, nil -} - -func (s *Service) ParseIniFileContents(file *ini.File) *Content { - iniContent := NewIniContent() - - for _, section := range file.Sections() { - if section.Name() == defaultSectionName { - continue - } - - iniSection := NewIniSection(section.Name()) - - for _, sectionKey := range section.Keys() { - iniSection.Values = append( - iniSection.Values, - NewIniSectionValue( - sectionKey.Name(), - sectionKey.Value(), - ), - ) - } - - iniContent.AddSection(iniSection) - } - - return iniContent -} diff --git a/pkg/rsa/builtin.go b/pkg/rsa/builtin.go index 0e8839e..92e9055 100644 --- a/pkg/rsa/builtin.go +++ b/pkg/rsa/builtin.go @@ -19,6 +19,7 @@ import ( ) var ( + rsaEncryptOAEP = rsa.EncryptOAEP rsaEncryptPKCS1v15 = rsa.EncryptPKCS1v15 rsaDecryptPKCS1v15 = rsa.DecryptPKCS1v15 ) diff --git a/pkg/rsa/service.go b/pkg/rsa/service.go index 9ed75cb..ecf8783 100644 --- a/pkg/rsa/service.go +++ b/pkg/rsa/service.go @@ -20,6 +20,7 @@ import ( "encoding/pem" "errors" "fmt" + "hash" "io" "github.com/palantir/stacktrace" @@ -75,7 +76,7 @@ func (s *Service) ReadPublicKeyFromBytes(publicKeyContent []byte) (*rsa.PublicKe if err != nil { return nil, stacktrace.Propagate( err, - "unable to parse PKCS1 public key", + "unable to parse PKIX public key", ) } @@ -116,6 +117,10 @@ func (s *Service) ReadPrivateKeyFromBytes(privateKeyContent []byte) (*rsa.Privat return key, nil } +func (s *Service) EncryptOAEP(hash hash.Hash, random io.Reader, pub *rsa.PublicKey, msg []byte, label []byte) ([]byte, error) { + return rsaEncryptOAEP(hash, random, pub, msg, label) +} + func (s *Service) EncryptPKCS1v15(rand io.Reader, pub *rsa.PublicKey, msg []byte) ([]byte, error) { return rsaEncryptPKCS1v15(rand, pub, msg) } diff --git a/pkg/rsa/service_test.go b/pkg/rsa/service_test.go index 73eb846..4b3f62f 100644 --- a/pkg/rsa/service_test.go +++ b/pkg/rsa/service_test.go @@ -17,7 +17,9 @@ package rsa import ( "bytes" "crypto/rsa" + "crypto/sha256" "errors" + "hash" "io" "testing" @@ -144,7 +146,7 @@ s/oC2WeIW+KwL2as5+Sw/KcoN3PqYxOLhMVaPiQAk2g= actualReturn, actualErr := svc.ReadPublicKeyFromPath(publicKeyPathArg) require.Nil(t, actualReturn) - assert.Contains(t, actualErr.Error(), "unable to parse PKCS1 public key") + assert.Contains(t, actualErr.Error(), "unable to parse PKIX public key") }, ) @@ -467,3 +469,63 @@ func TestService_DecryptPKCS1v15(t *testing.T) { }, ) } + +func TestService_EncryptOAEP(t *testing.T) { + t.Run( + "it uses builtin `rsaEncryptOAEP`", + func(t *testing.T) { + called := false + var calledHash hash.Hash + var calledRand io.Reader + var calledPub *rsa.PublicKey + var calledMsg []byte + var calledLabel []byte + + calledReturnBytes := []byte{1, 2, 3} + var calledReturnErr error + + realRsaEncryptOAEP := rsaEncryptOAEP + defer func() { + rsaEncryptOAEP = realRsaEncryptOAEP + }() + + rsaEncryptOAEP = func( + hash hash.Hash, + rand io.Reader, + pub *rsa.PublicKey, + msg, + label []byte, + ) (bytes []byte, e error) { + called = true + calledHash = hash + calledRand = rand + calledPub = pub + calledMsg = msg + calledLabel = label + + return calledReturnBytes, calledReturnErr + } + + hashArg := sha256.New() + randArg := bytes.NewReader( + bytes.NewBufferString("1234").Bytes(), + ) + pubArg := &rsa.PublicKey{} + msgArg := bytes.NewBufferString("mymsg") + labelArg := []byte("exampleLabel") + + svc := NewRsaService(&os.RealOsExecutor{}) + + actualBytes, err := svc.EncryptOAEP(hashArg, randArg, pubArg, msgArg.Bytes(), labelArg) + require.NoError(t, err) + + assert.True(t, called) + assert.Equal(t, calledHash, hashArg) + assert.Equal(t, calledRand, randArg) + assert.Equal(t, calledPub, pubArg) + assert.Equal(t, calledMsg, msgArg.Bytes()) + assert.Equal(t, calledLabel, labelArg) + assert.Equal(t, calledReturnBytes, actualBytes) + }, + ) +} diff --git a/pkg/rsa/test/testing.go b/pkg/rsa/test/testing.go index 3295f68..4818eb6 100644 --- a/pkg/rsa/test/testing.go +++ b/pkg/rsa/test/testing.go @@ -16,6 +16,7 @@ package test import ( stdRsa "crypto/rsa" + "hash" "io" "github.com/stretchr/testify/mock" @@ -74,3 +75,16 @@ func (m *MockRsaService) DecryptPKCS1v15(rand io.Reader, priv *stdRsa.PrivateKey return returnValue.([]byte), args.Error(1) } + +func (m *MockRsaService) EncryptOAEP(hash hash.Hash, rand io.Reader, pub *stdRsa.PublicKey, msg []byte, label []byte) ([]byte, error) { + args := m.Called(hash, rand, pub, msg) + + returnValue := args.Get(0) + + // NOTE: Workaround lack of get bytes without type-assertion method. + if returnValue == nil { + return nil, args.Error(1) + } + + return returnValue.([]byte), args.Error(1) +} diff --git a/pkg/terraform/service.go b/pkg/terraform/service.go index 31b555e..157bcb8 100644 --- a/pkg/terraform/service.go +++ b/pkg/terraform/service.go @@ -23,7 +23,7 @@ import ( type Service struct{} -func NewTerraformService() *Service { +func NewService() *Service { return &Service{} } diff --git a/pkg/terraform/service_test.go b/pkg/terraform/service_test.go index 9c9ae58..7ac62d1 100644 --- a/pkg/terraform/service_test.go +++ b/pkg/terraform/service_test.go @@ -26,7 +26,7 @@ func TestNewTerraformService(t *testing.T) { func(t *testing.T) { t.Parallel() - actual := NewTerraformService() + actual := NewService() assert.IsType(t, actual, &Service{}) }, diff --git a/pkg/terraform_encryption_migration/external_interfaces.go b/pkg/terraform_encryption_migration/external_interfaces.go index 34bd05a..99647af 100644 --- a/pkg/terraform_encryption_migration/external_interfaces.go +++ b/pkg/terraform_encryption_migration/external_interfaces.go @@ -15,13 +15,9 @@ package terraform_encryption_migration import ( - stdRsa "crypto/rsa" - "github.com/hashicorp/hcl/v2/hclwrite" "github.com/sumup-oss/vaulted/pkg/hcl" - "github.com/sumup-oss/vaulted/pkg/vaulted/content" - "github.com/sumup-oss/vaulted/pkg/vaulted/passphrase" "github.com/sumup-oss/vaulted/pkg/vaulted/payload" ) @@ -33,30 +29,10 @@ type terraformService interface { ) (*hclwrite.File, error) } -type EncryptedContentService interface { - Encrypt(passphrase *passphrase.Passphrase, content *content.Content) (*content.EncryptedContent, error) - Decrypt(passphrase *passphrase.Passphrase, encryptedContent *content.EncryptedContent) (*content.Content, error) - Serialize(encryptedContent *content.EncryptedContent) ([]byte, error) - Deserialize(encoded []byte) (*content.EncryptedContent, error) -} - -type EncryptedPassphraseService interface { - Serialize(encryptedPassphrase *passphrase.EncryptedPassphrase) ([]byte, error) - Encrypt( - publicKey *stdRsa.PublicKey, - passphrase *passphrase.Passphrase, - ) (*passphrase.EncryptedPassphrase, error) - Deserialize(encoded []byte) (*passphrase.EncryptedPassphrase, error) - Decrypt( - privateKey *stdRsa.PrivateKey, - encryptedPassphrase *passphrase.EncryptedPassphrase, - ) (*passphrase.Passphrase, error) - GeneratePassphrase(length int) (*passphrase.Passphrase, error) +type PayloadDecrypter interface { + Decrypt(encryptedPayload *payload.EncryptedPayload) (*payload.Payload, error) } -type EncryptedPayloadService interface { - Encrypt(publicKey *stdRsa.PublicKey, payload *payload.Payload) (*payload.EncryptedPayload, error) - Decrypt(privateKey *stdRsa.PrivateKey, encryptedPayload *payload.EncryptedPayload) (*payload.Payload, error) - Serialize(encryptedPayload *payload.EncryptedPayload) ([]byte, error) - Deserialize(encodedContent []byte) (*payload.EncryptedPayload, error) +type PayloadEncrypter interface { + Encrypt(payload *payload.Payload) (*payload.EncryptedPayload, error) } diff --git a/pkg/terraform_encryption_migration/service.go b/pkg/terraform_encryption_migration/service.go index 88c1615..08efb5f 100644 --- a/pkg/terraform_encryption_migration/service.go +++ b/pkg/terraform_encryption_migration/service.go @@ -15,27 +15,17 @@ package terraform_encryption_migration import ( - stdRsa "crypto/rsa" - "encoding/json" - "fmt" - "sort" - "strings" - "github.com/hashicorp/hcl/v2/hclsyntax" "github.com/hashicorp/hcl/v2/hclwrite" "github.com/palantir/stacktrace" - "github.com/zclconf/go-cty/cty" "github.com/sumup-oss/vaulted/pkg/hcl" - "github.com/sumup-oss/vaulted/pkg/ini" - "github.com/sumup-oss/vaulted/pkg/vaulted/content" - "github.com/sumup-oss/vaulted/pkg/vaulted/header" + "github.com/sumup-oss/vaulted/pkg/vaulted/passphrase" "github.com/sumup-oss/vaulted/pkg/vaulted/payload" ) const ( - resourceType = "resource" - vaultEncryptedSecretResourceType = "vault_encrypted_secret" + resourceType = "resource" // nolint:gosec vaultedVaultSecretResourceType = "vaulted_vault_secret" ) @@ -50,32 +40,6 @@ func NewTerraformEncryptionMigrationService(terraformSvc terraformService) *Serv } } -// MigrateEncryptedTerraformResourceHcl parses and migrates a HCL terraform file -// with `vault_encrypted_secret` terraform resources encrypted that were using `legacy encrypt` cmd. -// It decrypts, encrypts and replaces existing terraform `vaulted`. -// It does not lose/modify resources that are not `vault_encrypted_secret`. -func (s *Service) MigrateEncryptedTerraformResourceHcl( - hclParser hcl.Parser, - hclBytes []byte, - privKey *stdRsa.PrivateKey, - pubKey *stdRsa.PublicKey, - legacyEncryptedContentSvc EncryptedContentService, - encryptedPassphraseSvc EncryptedPassphraseService, - encryptedPayloadSvc EncryptedPayloadService, -) (*hclwrite.File, error) { - return s.terraformSvc.ModifyInPlaceHclAst( - hclParser, - hclBytes, - s.migrateEncryptedTerraformResourceHclObjectItemVisitor( - privKey, - pubKey, - legacyEncryptedContentSvc, - encryptedPassphraseSvc, - encryptedPayloadSvc, - ), - ) -} - // RotateOrRekeyEncryptedTerraformResourceHcl parses and rotates a HCL terraform file // with `vault_encrypted_secret` terraform resources encrypted that were using `encrypt` cmd. // It decrypts, encrypts and replaces existing terraform `vaulted`. @@ -83,28 +47,28 @@ func (s *Service) MigrateEncryptedTerraformResourceHcl( func (s *Service) RotateOrRekeyEncryptedTerraformResourceHcl( hclParser hcl.Parser, hclBytes []byte, - privKey *stdRsa.PrivateKey, - pubKey *stdRsa.PublicKey, - encryptedPassphraseSvc EncryptedPassphraseService, - encryptedPayloadSvc EncryptedPayloadService, + passphraseSvc *passphrase.Service, + payloadSerdeSvc *payload.SerdeService, + oldPayloadDecrypter PayloadDecrypter, + newPayloadEncrypter PayloadEncrypter, ) (*hclwrite.File, error) { return s.terraformSvc.ModifyInPlaceHclAst( hclParser, hclBytes, s.rotateOrRekeyEncryptedTerraformResourceHclObjectItemVisitor( - privKey, - pubKey, - encryptedPassphraseSvc, - encryptedPayloadSvc, + passphraseSvc, + payloadSerdeSvc, + oldPayloadDecrypter, + newPayloadEncrypter, ), ) } func (s *Service) rotateOrRekeyEncryptedTerraformResourceHclObjectItemVisitor( - privKey *stdRsa.PrivateKey, - pubKey *stdRsa.PublicKey, - encryptedPassphraseSvc EncryptedPassphraseService, - encryptedPayloadSvc EncryptedPayloadService, + passphraseSvc *passphrase.Service, + payloadSerdeSvc *payload.SerdeService, + oldPayloadDecrypter PayloadDecrypter, + newPayloadEncrypter PayloadEncrypter, ) func(block *hclwrite.Block) error { return func(block *hclwrite.Block) error { if block.Type() != resourceType { @@ -142,7 +106,7 @@ func (s *Service) rotateOrRekeyEncryptedTerraformResourceHclObjectItemVisitor( ) } - oldEncryptedPayload, err := encryptedPayloadSvc.Deserialize(previousEncPayload) + oldEncryptedPayload, err := payloadSerdeSvc.Deserialize(previousEncPayload) if err != nil { return stacktrace.NewError( "failed to deserialize `payload_json` attr's value for `%s.%s`", @@ -151,7 +115,7 @@ func (s *Service) rotateOrRekeyEncryptedTerraformResourceHclObjectItemVisitor( ) } - oldPayload, err := encryptedPayloadSvc.Decrypt(privKey, oldEncryptedPayload) + oldPayload, err := oldPayloadDecrypter.Decrypt(oldEncryptedPayload) if err != nil { return stacktrace.NewError( "failed to decrypt `payload_json` attr's value for `%s.%s`", @@ -160,7 +124,7 @@ func (s *Service) rotateOrRekeyEncryptedTerraformResourceHclObjectItemVisitor( ) } - newPassphrase, err := encryptedPassphraseSvc.GeneratePassphrase(32) + newPassphrase, err := passphraseSvc.GeneratePassphrase(32) if err != nil { return stacktrace.NewError( "failed to generate new passphrase for `%s.%s`", @@ -173,7 +137,7 @@ func (s *Service) rotateOrRekeyEncryptedTerraformResourceHclObjectItemVisitor( // and encrypt the payload anew. oldPayload.Passphrase = newPassphrase - newEncryptedPayload, err := encryptedPayloadSvc.Encrypt(pubKey, oldPayload) + newEncryptedPayload, err := newPayloadEncrypter.Encrypt(oldPayload) if err != nil { return stacktrace.NewError( "failed to encrypt new encrypted payload for `%s.%s`", @@ -182,7 +146,7 @@ func (s *Service) rotateOrRekeyEncryptedTerraformResourceHclObjectItemVisitor( ) } - serializedNewEncPayload, err := encryptedPayloadSvc.Serialize(newEncryptedPayload) + serializedNewEncPayload, err := payloadSerdeSvc.Serialize(newEncryptedPayload) if err != nil { return stacktrace.NewError( "failed to serialize new encrypted payload for `%s.%s`", @@ -200,241 +164,6 @@ func (s *Service) rotateOrRekeyEncryptedTerraformResourceHclObjectItemVisitor( } } -func (s *Service) migrateEncryptedTerraformResourceHclObjectItemVisitor( - privKey *stdRsa.PrivateKey, - pubKey *stdRsa.PublicKey, - legacyEncryptedContentSvc EncryptedContentService, - encryptedPassphraseSvc EncryptedPassphraseService, - encryptedPayloadSvc EncryptedPayloadService, -) func(block *hclwrite.Block) error { - return func(block *hclwrite.Block) error { - if block.Type() != resourceType { - return nil - } - - // NOTE: We're only interested in - // "resource_type" "resource_name"` - labels := block.Labels() - if len(labels) != 2 { - return nil - } - - if labels[0] != vaultEncryptedSecretResourceType { - return nil - } - - oldEncryptedDataJSON, _, _, err := s.readHclQuoteLitAttr(block, "encrypted_data_json") - if err != nil { - return stacktrace.NewError( - "failed to read `encrypted_data_json` attr value for `%s.%s`", - vaultEncryptedSecretResourceType, - labels[1], - ) - } - - if len(oldEncryptedDataJSON) == 0 { - return stacktrace.NewError( - "empty `encrypted_data_json` attr value for `%s.%s`", - vaultEncryptedSecretResourceType, - labels[1], - ) - } - - oldEncryptedPassphrase, _, _, err := s.readHclQuoteLitAttr(block, "encrypted_passphrase") - if err != nil { - return stacktrace.NewError( - "failed to read `encrypted_passphrase` attr value for `%s.%s`", - vaultEncryptedSecretResourceType, - labels[1], - ) - } - - if len(oldEncryptedPassphrase) == 0 { - return stacktrace.NewError( - "empty `encrypted_passphrase` attr value for `%s.%s`", - vaultEncryptedSecretResourceType, - labels[1], - ) - } - - legacyEncryptedPassphrase, err := encryptedPassphraseSvc.Deserialize(oldEncryptedPassphrase) - if err != nil { - return stacktrace.NewError( - "failed to deserialize `encrypted_passphrase` attr value for `%s.%s`", - vaultEncryptedSecretResourceType, - labels[1], - ) - } - - legacyPassphrase, err := encryptedPassphraseSvc.Decrypt(privKey, legacyEncryptedPassphrase) - if err != nil { - return stacktrace.NewError( - "failed to decrypt `encrypted_passphrase` attr value for `%s.%s`", - vaultEncryptedSecretResourceType, - labels[1], - ) - } - - legacyEncryptedContent, err := legacyEncryptedContentSvc.Deserialize(oldEncryptedDataJSON) - if err != nil { - return stacktrace.NewError( - "failed to deserialize `encrypted_data_json` attr value for `%s.%s`", - vaultEncryptedSecretResourceType, - labels[1], - ) - } - - content, err := legacyEncryptedContentSvc.Decrypt(legacyPassphrase, legacyEncryptedContent) - if err != nil { - return stacktrace.NewError( - "failed to decrypt `encrypted_data_json` attr value for `%s.%s`", - vaultEncryptedSecretResourceType, - labels[1], - ) - } - - newPassphrase, err := encryptedPassphraseSvc.GeneratePassphrase(32) - if err != nil { - return stacktrace.NewError( - "failed to generate new encrypted passphrase for `%s.%s`", - vaultEncryptedSecretResourceType, - labels[1], - ) - } - - payload := payload.NewPayload( - header.NewHeader(), - newPassphrase, - content, - ) - - encryptedPayload, err := encryptedPayloadSvc.Encrypt(pubKey, payload) - if err != nil { - return stacktrace.NewError( - "failed to encrypt new encrypted payload for `%s.%s`", - vaultEncryptedSecretResourceType, - labels[1], - ) - } - - serializedEncryptedPayload, err := encryptedPayloadSvc.Serialize(encryptedPayload) - if err != nil { - return stacktrace.NewError( - "failed to serialize new encrypted payload for `%s.%s`", - vaultEncryptedSecretResourceType, - labels[1], - ) - } - - blockBody := block.Body() - labels[0] = "vaulted_vault_secret" - block.SetLabels(labels) - - // NOTE: Adapt to resource `vaulted_vault_secret` format. - blockBody.RemoveAttribute("encrypted_data_json") - blockBody.RemoveAttribute("encrypted_passphrase") - blockBody.SetAttributeValue("encrypted_payload", cty.StringVal(string(serializedEncryptedPayload))) - - return nil - } -} - -func (s *Service) ConvertIniContentToV1ResourceHCL( - passphraseLength int, - iniContent *ini.Content, - pubKey *stdRsa.PublicKey, - encryptedPassphraseSvc EncryptedPassphraseService, - encryptedPayloadSvc EncryptedPayloadService, -) (*hclwrite.File, error) { - hclFile := hclwrite.NewEmptyFile() - - blocksByResourceName := make(map[string]*hclwrite.Block) - sortedResourceNames := make([]string, 0) - - for name, section := range iniContent.SectionsByName { - for _, sectionValue := range section.Values { - iniPath := fmt.Sprintf("%s/%s", name, sectionValue.KeyName) - resourceName := strings.ReplaceAll(iniPath, "/", "_") - - block := hclwrite.NewBlock( - "resource", - []string{ - "vaulted_vault_secret", - fmt.Sprintf( - "vaulted_vault_secret_%s", - resourceName, - ), - }, - ) - valueMap := map[string]interface{}{ - "value": sectionValue.Value, - } - - dataJSON, err := json.Marshal(valueMap) - if err != nil { - return nil, stacktrace.Propagate( - err, - "failed to marshal in JSON value for section: %s, key: %s", - name, - sectionValue.KeyName, - ) - } - - passphrase, err := encryptedPassphraseSvc.GeneratePassphrase(passphraseLength) - if err != nil { - return nil, stacktrace.Propagate( - err, - "failed to generate random passphrase", - ) - } - - payloadInstance := payload.NewPayload( - header.NewHeader(), - passphrase, - content.NewContent(dataJSON), - ) - - encPayload, err := encryptedPayloadSvc.Encrypt(pubKey, payloadInstance) - if err != nil { - return nil, stacktrace.Propagate( - err, - "failed to encrypt content from section: %s, key: %s", - name, - sectionValue.KeyName, - ) - } - - serializedEncPayload, err := encryptedPayloadSvc.Serialize(encPayload) - if err != nil { - return nil, stacktrace.Propagate( - err, - "failed to serialize encrypted payload", - ) - } - - path := fmt.Sprintf("secret/%s", iniPath) - - blockBody := block.Body() - blockBody.SetAttributeValue("path", cty.StringVal(path)) - blockBody.SetAttributeValue("payload_json", cty.StringVal(string(serializedEncPayload))) - - blocksByResourceName[resourceName] = block - - sortedResourceNames = append(sortedResourceNames, resourceName) - } - } - - // NOTE: Always add the resources alphabetically for consistency. - sort.Strings(sortedResourceNames) - - for _, resourceName := range sortedResourceNames { - block := blocksByResourceName[resourceName] - hclFile.Body().AppendBlock(block) - } - - return hclFile, nil -} - func (s *Service) readHclQuoteLitAttr(block *hclwrite.Block, name string) ([]byte, int, int, error) { blockBody := block.Body() if blockBody == nil { diff --git a/pkg/terraform_encryption_migration/service_integration_test.go b/pkg/terraform_encryption_migration/service_integration_test.go index 301f7e7..24922f6 100644 --- a/pkg/terraform_encryption_migration/service_integration_test.go +++ b/pkg/terraform_encryption_migration/service_integration_test.go @@ -17,19 +17,16 @@ package terraform_encryption_migration import ( "crypto/rand" stdRsa "crypto/rsa" - "errors" "fmt" "regexp" "testing" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/sumup-oss/go-pkgs/os/ostest" "github.com/sumup-oss/vaulted/pkg/aes" "github.com/sumup-oss/vaulted/pkg/base64" "github.com/sumup-oss/vaulted/pkg/hcl" - "github.com/sumup-oss/vaulted/pkg/ini" "github.com/sumup-oss/vaulted/pkg/pkcs7" "github.com/sumup-oss/vaulted/pkg/rsa" "github.com/sumup-oss/vaulted/pkg/terraform" @@ -37,1549 +34,9 @@ import ( "github.com/sumup-oss/vaulted/pkg/vaulted/content" "github.com/sumup-oss/vaulted/pkg/vaulted/header" "github.com/sumup-oss/vaulted/pkg/vaulted/passphrase" - testPassphrase "github.com/sumup-oss/vaulted/pkg/vaulted/passphrase/test" "github.com/sumup-oss/vaulted/pkg/vaulted/payload" - testPayload "github.com/sumup-oss/vaulted/pkg/vaulted/payload/test" ) -func TestService_ConvertIniContentToV1ResourceHCL(t *testing.T) { - t.Run( - "when `iniContent` has no sections, it returns empty HCL", - func(t *testing.T) { - t.Parallel() - - privKey, actualErr := stdRsa.GenerateKey(rand.Reader, 2048) - require.Nil(t, actualErr) - - tfSvc := terraform.NewTerraformService() - tfEncMigrationSvc := NewTerraformEncryptionMigrationService(tfSvc) - - iniContent := ini.NewIniContent() - - encryptedPassphraseSvc := &testPassphrase.MockEncryptedPassphraseService{} - encryptedPayloadSvc := &testPayload.MockEncryptedPayloadService{} - - actualReturn, actualErr := tfEncMigrationSvc.ConvertIniContentToV1ResourceHCL( - 32, - iniContent, - &privKey.PublicKey, - encryptedPassphraseSvc, - encryptedPayloadSvc, - ) - - require.Nil(t, actualErr) - - assert.Equal(t, "", string(actualReturn.Bytes())) - }, - ) - - t.Run( - "when `iniContent` has 1 section, but no values in the section, "+ - "it returns empty HCL", - func(t *testing.T) { - t.Parallel() - - privKey, actualErr := stdRsa.GenerateKey(rand.Reader, 2048) - require.Nil(t, actualErr) - - tfSvc := terraform.NewTerraformService() - tfEncMigrationSvc := NewTerraformEncryptionMigrationService(tfSvc) - - iniSection := ini.NewIniSection("section_a") - iniSection.Values = []*ini.SectionValue{} - - iniContent := ini.NewIniContent() - iniContent.AddSection(iniSection) - - assert.Equal(t, 1, len(iniContent.SectionsByName)) - - encryptedPassphraseSvc := &testPassphrase.MockEncryptedPassphraseService{} - encryptedPayloadSvc := &testPayload.MockEncryptedPayloadService{} - - actualReturn, actualErr := tfEncMigrationSvc.ConvertIniContentToV1ResourceHCL( - 32, - iniContent, - &privKey.PublicKey, - encryptedPassphraseSvc, - encryptedPayloadSvc, - ) - - require.Nil(t, actualErr) - - assert.Equal(t, "", string(actualReturn.Bytes())) - }, - ) - - t.Run( - "when `iniContent` has at least 1 section, but json marshalling of section value fails, "+ - "it returns an error", - func(t *testing.T) { - t.Parallel() - - privKey, actualErr := stdRsa.GenerateKey(rand.Reader, 2048) - require.Nil(t, actualErr) - - tfSvc := terraform.NewTerraformService() - tfEncMigrationSvc := NewTerraformEncryptionMigrationService(tfSvc) - - iniSection := ini.NewIniSection("section_a") - // NOTE: Section value is not JSON serializable. - // It's expected to be the cause of the JSON marshal error. - sectionValue := ini.NewIniSectionValue("key_a", make(chan interface{})) - - iniSection.Values = []*ini.SectionValue{sectionValue} - - iniContent := ini.NewIniContent() - iniContent.AddSection(iniSection) - - assert.Equal(t, 1, len(iniContent.SectionsByName)) - - encryptedPassphraseSvc := &testPassphrase.MockEncryptedPassphraseService{} - encryptedPayloadSvc := &testPayload.MockEncryptedPayloadService{} - - actualReturn, actualErr := tfEncMigrationSvc.ConvertIniContentToV1ResourceHCL( - 32, - iniContent, - &privKey.PublicKey, - encryptedPassphraseSvc, - encryptedPayloadSvc, - ) - - require.Nil(t, actualReturn) - - assert.Contains( - t, - actualErr.Error(), - fmt.Sprintf( - "failed to marshal in JSON value for section: %s, key: %s", - iniSection.Name, - sectionValue.KeyName, - ), - ) - }, - ) - - t.Run( - "when `iniContent` has at least 1 section, but generating of random passphrase fails, "+ - "it returns an error", - func(t *testing.T) { - t.Parallel() - - privKey, actualErr := stdRsa.GenerateKey(rand.Reader, 2048) - require.Nil(t, actualErr) - - tfSvc := terraform.NewTerraformService() - tfEncMigrationSvc := NewTerraformEncryptionMigrationService(tfSvc) - - iniSection := ini.NewIniSection("section_a") - sectionValue := ini.NewIniSectionValue("key_a", "value_a") - - iniSection.Values = []*ini.SectionValue{sectionValue} - - iniContent := ini.NewIniContent() - iniContent.AddSection(iniSection) - - assert.Equal(t, 1, len(iniContent.SectionsByName)) - - fakeError := errors.New("fakeGeneratePassphraseError") - encryptedPassphraseSvc := &testPassphrase.MockEncryptedPassphraseService{} - encryptedPassphraseSvc.On( - "GeneratePassphrase", - 32, - ).Return(nil, fakeError) - - encryptedPayloadSvc := &testPayload.MockEncryptedPayloadService{} - - actualReturn, actualErr := tfEncMigrationSvc.ConvertIniContentToV1ResourceHCL( - 32, - iniContent, - &privKey.PublicKey, - encryptedPassphraseSvc, - encryptedPayloadSvc, - ) - - require.Nil(t, actualReturn) - - assert.Contains( - t, - actualErr.Error(), - "failed to generate random passphrase", - ) - }, - ) - - t.Run( - "when `iniContent` has at least 1 section, but encrypting the section value's content fails, "+ - "it returns an error", - func(t *testing.T) { - t.Parallel() - - privKey, actualErr := stdRsa.GenerateKey(rand.Reader, 2048) - require.Nil(t, actualErr) - - tfSvc := terraform.NewTerraformService() - tfEncMigrationSvc := NewTerraformEncryptionMigrationService(tfSvc) - - iniSection := ini.NewIniSection("section_a") - sectionValue := ini.NewIniSectionValue("key_a", "value_a") - - iniSection.Values = []*ini.SectionValue{sectionValue} - - iniContent := ini.NewIniContent() - iniContent.AddSection(iniSection) - - assert.Equal(t, 1, len(iniContent.SectionsByName)) - - b64Svc := base64.NewBase64Service() - osExecutor := ostest.NewFakeOsExecutor(t) - - rsaSvc := rsa.NewRsaService(osExecutor) - - encryptedPassphraseSvc := passphrase.NewEncryptedPassphraseService( - b64Svc, - rsaSvc, - ) - - fakeError := errors.New("fakeEncryptError") - encryptedPayloadSvc := &testPayload.MockEncryptedPayloadService{} - encryptedPayloadSvc.On( - "Encrypt", - mock.AnythingOfType("*rsa.PublicKey"), - mock.AnythingOfType("*payload.Payload"), - ).Return(nil, fakeError) - - actualReturn, actualErr := tfEncMigrationSvc.ConvertIniContentToV1ResourceHCL( - 32, - iniContent, - &privKey.PublicKey, - encryptedPassphraseSvc, - encryptedPayloadSvc, - ) - - require.Nil(t, actualReturn) - - assert.Contains( - t, - actualErr.Error(), - fmt.Sprintf( - "failed to encrypt content from section: %s, key: %s", - iniSection.Name, - sectionValue.KeyName, - ), - ) - }, - ) - - t.Run( - "when `iniContent` has at least 1 section and generating random passphrase, encryption of "+ - "passphrase and content succeeds, "+ - "it returns HCL with at least 1 terraform resource added", - func(t *testing.T) { - t.Parallel() - - privKey, actualErr := stdRsa.GenerateKey(rand.Reader, 2048) - require.Nil(t, actualErr) - - tfSvc := terraform.NewTerraformService() - tfEncMigrationSvc := NewTerraformEncryptionMigrationService(tfSvc) - - iniSection := ini.NewIniSection("section_a") - sectionValue := ini.NewIniSectionValue("key_a", "value_a") - - iniSection.Values = []*ini.SectionValue{sectionValue} - - iniContent := ini.NewIniContent() - iniContent.AddSection(iniSection) - - assert.Equal(t, 1, len(iniContent.SectionsByName)) - - b64Svc := base64.NewBase64Service() - osExecutor := ostest.NewFakeOsExecutor(t) - - rsaSvc := rsa.NewRsaService(osExecutor) - - encryptedPassphraseSvc := passphrase.NewEncryptedPassphraseService( - b64Svc, - rsaSvc, - ) - - aesSvc := aes.NewAesService(pkcs7.NewPkcs7Service()) - encryptedContentSvc := content.NewV1EncryptedContentService(b64Svc, aesSvc) - encryptedPayloadSvc := payload.NewEncryptedPayloadService( - header.NewHeaderService(), - encryptedPassphraseSvc, - encryptedContentSvc, - ) - - actualReturn, actualErr := tfEncMigrationSvc.ConvertIniContentToV1ResourceHCL( - 32, - iniContent, - &privKey.PublicKey, - encryptedPassphraseSvc, - encryptedPayloadSvc, - ) - - require.Nil(t, actualErr) - require.NotNil(t, actualReturn) - - actualReturnString := string(actualReturn.Bytes()) - - resourceKey := fmt.Sprintf( - "vaulted_vault_secret_%s_%s", - iniSection.Name, - sectionValue.KeyName, - ) - path := fmt.Sprintf( - "secret/%s/%s", - iniSection.Name, - sectionValue.KeyName, - ) - - expectedRegex := fmt.Sprintf(`resource "vaulted_vault_secret" "%s" { - path = "%s" - payload_json = "\$VED;1.0::(.+)::(.+)" -}\n`, resourceKey, path) - assert.Regexp(t, regexp.MustCompile(expectedRegex), actualReturnString) - }, - ) -} - -func TestService_MigrateEncryptedTerraformResourceHcl(t *testing.T) { - t.Run( - "when failing to parse 'hclBytes', it returns an error", - func(t *testing.T) { - privKey, err := stdRsa.GenerateKey(rand.Reader, 2048) - require.Nil(t, err) - - tfSvc := terraform.NewTerraformService() - tfEncMigrationSvc := NewTerraformEncryptionMigrationService(tfSvc) - hclParserArg := hcl.NewHclService() - hclBytesArg := []byte("{ invalid }") - - b64Svc := base64.NewBase64Service() - aesSvc := aes.NewAesService(pkcs7.NewPkcs7Service()) - - legacyEncryptedContentSvcArg := content.NewLegacyEncryptedContentService( - b64Svc, - aesSvc, - ) - - osExecutor := ostest.NewFakeOsExecutor(t) - - encryptedPassphraseSvcArg := passphrase.NewEncryptedPassphraseService( - b64Svc, - rsa.NewRsaService(osExecutor), - ) - encryptedPayloadSvcArg := payload.NewEncryptedPayloadService( - header.NewHeaderService(), - encryptedPassphraseSvcArg, - content.NewLegacyEncryptedContentService(b64Svc, aesSvc), - ) - - actualReturn, actualErr := tfEncMigrationSvc.MigrateEncryptedTerraformResourceHcl( - hclParserArg, - hclBytesArg, - privKey, - &privKey.PublicKey, - legacyEncryptedContentSvcArg, - encryptedPassphraseSvcArg, - encryptedPayloadSvcArg, - ) - require.Nil(t, actualReturn) - - assert.Contains(t, actualErr.Error(), "failed to parse HCL") - }, - ) - - t.Run( - "when succeeding to parse 'hclBytes', "+ - "but HCL terraform does not have any `vault_encrypted_secret` resources, "+ - "it returns AST without any modification", - func(t *testing.T) { - // NOTE: Memory references are *intentionally* asserted for equality. - // It's very well known which objects are supposed to be changed. - - privKey, err := stdRsa.GenerateKey(rand.Reader, 2048) - require.Nil(t, err) - - hclBytesArg := []byte(` - resource "vault_generic_secret" "mysecret" { - path = "secret/example" - data_json = "{ 'foo': 'bar' }" - } - `) - hclParserArg := hcl.NewHclService() - - b64Svc := base64.NewBase64Service() - aesSvc := aes.NewAesService(pkcs7.NewPkcs7Service()) - - legacyEncryptedContentSvcArg := content.NewLegacyEncryptedContentService( - b64Svc, - aesSvc, - ) - - osExecutor := ostest.NewFakeOsExecutor(t) - - encryptedPassphraseSvcArg := passphrase.NewEncryptedPassphraseService( - b64Svc, - rsa.NewRsaService(osExecutor), - ) - encryptedPayloadSvcArg := payload.NewEncryptedPayloadService( - header.NewHeaderService(), - encryptedPassphraseSvcArg, - content.NewLegacyEncryptedContentService(b64Svc, aesSvc), - ) - - tfSvc := terraform.NewTerraformService() - tfEncMigrationSvc := NewTerraformEncryptionMigrationService(tfSvc) - - expectedReturn, err := hclParserArg.Parse(hclBytesArg) - require.Nil(t, err) - - actualReturn, actualErr := tfEncMigrationSvc.MigrateEncryptedTerraformResourceHcl( - hclParserArg, - hclBytesArg, - privKey, - &privKey.PublicKey, - legacyEncryptedContentSvcArg, - encryptedPassphraseSvcArg, - encryptedPayloadSvcArg, - ) - require.Nil(t, actualErr) - - assert.Equal(t, expectedReturn.Bytes(), actualReturn.Bytes()) - }, - ) - - t.Run( - "when succeeding to parse 'hclBytes' which has `vault_encrypted_secret`, "+ - "but `vault_encrypted_secret` is not a HCL object, "+ - "it returns error", - func(t *testing.T) { - privKey, err := stdRsa.GenerateKey(rand.Reader, 2048) - require.Nil(t, err) - - hclBytesArg := []byte(`resource "vault_encrypted_secret" "mysecret" = "123"`) - hclParserArg := hcl.NewHclService() - - b64Svc := base64.NewBase64Service() - aesSvc := aes.NewAesService(pkcs7.NewPkcs7Service()) - - legacyEncryptedContentSvcArg := content.NewLegacyEncryptedContentService( - b64Svc, - aesSvc, - ) - - osExecutor := ostest.NewFakeOsExecutor(t) - - encryptedPassphraseSvcArg := passphrase.NewEncryptedPassphraseService( - b64Svc, - rsa.NewRsaService(osExecutor), - ) - encryptedPayloadSvcArg := payload.NewEncryptedPayloadService( - header.NewHeaderService(), - encryptedPassphraseSvcArg, - content.NewLegacyEncryptedContentService(b64Svc, aesSvc), - ) - - tfSvc := terraform.NewTerraformService() - tfEncMigrationSvc := NewTerraformEncryptionMigrationService(tfSvc) - - actualReturn, actualErr := tfEncMigrationSvc.MigrateEncryptedTerraformResourceHcl( - hclParserArg, - hclBytesArg, - privKey, - &privKey.PublicKey, - legacyEncryptedContentSvcArg, - encryptedPassphraseSvcArg, - encryptedPayloadSvcArg, - ) - require.Nil(t, actualReturn) - - assert.Contains( - t, - actualErr.Error(), - "failed to parse HCL", - ) - }, - ) - - t.Run( - "when succeeding to parse 'hclBytes' which has `vault_encrypted_secret`, "+ - "but `vault_encrypted_secret` has less than 3 content keys, "+ - "it returns error", - func(t *testing.T) { - // NOTE: Memory references are *intentionally* asserted for equality. - // It's very well known which objects are supposed to be changed. - - privKey, err := stdRsa.GenerateKey(rand.Reader, 2048) - require.Nil(t, err) - - hclBytesArg := []byte(` - resource "vault_encrypted_secret" "mysecret" { - path = "secret/example" - encrypted_passphrase = "" - }`) - hclParserArg := hcl.NewHclService() - - b64Svc := base64.NewBase64Service() - aesSvc := aes.NewAesService(pkcs7.NewPkcs7Service()) - - legacyEncryptedContentSvcArg := content.NewLegacyEncryptedContentService( - b64Svc, - aesSvc, - ) - - osExecutor := ostest.NewFakeOsExecutor(t) - - encryptedPassphraseSvcArg := passphrase.NewEncryptedPassphraseService( - b64Svc, - rsa.NewRsaService(osExecutor), - ) - encryptedPayloadSvcArg := payload.NewEncryptedPayloadService( - header.NewHeaderService(), - encryptedPassphraseSvcArg, - content.NewLegacyEncryptedContentService(b64Svc, aesSvc), - ) - - tfSvc := terraform.NewTerraformService() - tfEncMigrationSvc := NewTerraformEncryptionMigrationService(tfSvc) - - actualReturn, actualErr := tfEncMigrationSvc.MigrateEncryptedTerraformResourceHcl( - hclParserArg, - hclBytesArg, - privKey, - &privKey.PublicKey, - legacyEncryptedContentSvcArg, - encryptedPassphraseSvcArg, - encryptedPayloadSvcArg, - ) - require.Nil(t, actualReturn) - - assert.Contains( - t, - actualErr.Error(), - "failed to read `encrypted_data_json` attr value for `vault_encrypted_secret.mysecret`", - ) - }, - ) - - t.Run( - "when succeeding to parse 'hclBytes' which has `vault_encrypted_secret`, "+ - "but `vault_encrypted_secret` has non-string key, "+ - "it returns error", - func(t *testing.T) { - // NOTE: Memory references are *intentionally* asserted for equality. - // It's very well known which objects are supposed to be changed. - - privKey, err := stdRsa.GenerateKey(rand.Reader, 2048) - require.Nil(t, err) - - hclBytesArg := []byte(` - resource "vault_encrypted_secret" "mysecret" { - 1 = "1234" - encrypted_passphrase = "a1b2c3d4" - encrypted_data_json = "a1b2c3d4" - }`) - hclParserArg := hcl.NewHclService() - - b64Svc := base64.NewBase64Service() - aesSvc := aes.NewAesService(pkcs7.NewPkcs7Service()) - - legacyEncryptedContentSvcArg := content.NewLegacyEncryptedContentService( - b64Svc, - aesSvc, - ) - - osExecutor := ostest.NewFakeOsExecutor(t) - - encryptedPassphraseSvcArg := passphrase.NewEncryptedPassphraseService( - b64Svc, - rsa.NewRsaService(osExecutor), - ) - encryptedPayloadSvcArg := payload.NewEncryptedPayloadService( - header.NewHeaderService(), - encryptedPassphraseSvcArg, - content.NewLegacyEncryptedContentService(b64Svc, aesSvc), - ) - - tfSvc := terraform.NewTerraformService() - tfEncMigrationSvc := NewTerraformEncryptionMigrationService(tfSvc) - - actualReturn, actualErr := tfEncMigrationSvc.MigrateEncryptedTerraformResourceHcl( - hclParserArg, - hclBytesArg, - privKey, - &privKey.PublicKey, - legacyEncryptedContentSvcArg, - encryptedPassphraseSvcArg, - encryptedPayloadSvcArg, - ) - require.Nil(t, actualReturn) - - assert.Contains( - t, - actualErr.Error(), - "failed to parse HCL", - ) - }, - ) - - t.Run( - "when succeeding to parse 'hclBytes' which has `vault_encrypted_secret`, "+ - "but `vault_encrypted_secret` has non-string 'encrypted_passphrase' value, "+ - "it returns error", - func(t *testing.T) { - // NOTE: Memory references are *intentionally* asserted for equality. - // It's very well known which objects are supposed to be changed. - - privKey, err := stdRsa.GenerateKey(rand.Reader, 2048) - require.Nil(t, err) - - hclBytesArg := []byte(` - resource "vault_encrypted_secret" "mysecret" { - path = "secret/example" - encrypted_passphrase = 1234 - encrypted_data_json = "a1b2c3d4" - }`) - hclParserArg := hcl.NewHclService() - - b64Svc := base64.NewBase64Service() - aesSvc := aes.NewAesService(pkcs7.NewPkcs7Service()) - - legacyEncryptedContentSvcArg := content.NewLegacyEncryptedContentService( - b64Svc, - aesSvc, - ) - - osExecutor := ostest.NewFakeOsExecutor(t) - - encryptedPassphraseSvcArg := passphrase.NewEncryptedPassphraseService( - b64Svc, - rsa.NewRsaService(osExecutor), - ) - encryptedPayloadSvcArg := payload.NewEncryptedPayloadService( - header.NewHeaderService(), - encryptedPassphraseSvcArg, - content.NewLegacyEncryptedContentService(b64Svc, aesSvc), - ) - - tfSvc := terraform.NewTerraformService() - tfEncMigrationSvc := NewTerraformEncryptionMigrationService(tfSvc) - - actualReturn, actualErr := tfEncMigrationSvc.MigrateEncryptedTerraformResourceHcl( - hclParserArg, - hclBytesArg, - privKey, - &privKey.PublicKey, - legacyEncryptedContentSvcArg, - encryptedPassphraseSvcArg, - encryptedPayloadSvcArg, - ) - require.Nil(t, actualReturn) - - assert.Contains( - t, - actualErr.Error(), - "failed to read `encrypted_passphrase` attr value for "+ - "`vault_encrypted_secret.mysecret`", - ) - }, - ) - - t.Run( - "when succeeding to parse 'hclBytes' which has `vault_encrypted_secret`, "+ - "but `vault_encrypted_secret` has non-string 'encrypted_data_json' value, "+ - "it returns error", - func(t *testing.T) { - // NOTE: Memory references are *intentionally* asserted for equality. - // It's very well known which objects are supposed to be changed. - - privKey, err := stdRsa.GenerateKey(rand.Reader, 2048) - require.Nil(t, err) - - hclBytesArg := []byte(` - resource "vault_encrypted_secret" "mysecret" { - path = "secret/example" - encrypted_passphrase = "a1b2c3d4" - encrypted_data_json = 1234 - }`) - hclParserArg := hcl.NewHclService() - - b64Svc := base64.NewBase64Service() - aesSvc := aes.NewAesService(pkcs7.NewPkcs7Service()) - - legacyEncryptedContentSvcArg := content.NewLegacyEncryptedContentService( - b64Svc, - aesSvc, - ) - - osExecutor := ostest.NewFakeOsExecutor(t) - - encryptedPassphraseSvcArg := passphrase.NewEncryptedPassphraseService( - b64Svc, - rsa.NewRsaService(osExecutor), - ) - encryptedPayloadSvcArg := payload.NewEncryptedPayloadService( - header.NewHeaderService(), - encryptedPassphraseSvcArg, - content.NewLegacyEncryptedContentService(b64Svc, aesSvc), - ) - - tfSvc := terraform.NewTerraformService() - tfEncMigrationSvc := NewTerraformEncryptionMigrationService(tfSvc) - - actualReturn, actualErr := tfEncMigrationSvc.MigrateEncryptedTerraformResourceHcl( - hclParserArg, - hclBytesArg, - privKey, - &privKey.PublicKey, - legacyEncryptedContentSvcArg, - encryptedPassphraseSvcArg, - encryptedPayloadSvcArg, - ) - require.Nil(t, actualReturn) - - assert.Contains( - t, - actualErr.Error(), - "failed to read `encrypted_data_json` attr value for "+ - "`vault_encrypted_secret.mysecret`", - ) - }, - ) - - t.Run( - "when succeeding to parse 'hclBytes' which has `vault_encrypted_secret`, "+ - "but `vault_encrypted_secret` has empty 'encrypted_data_json' string value, "+ - "it returns error", - func(t *testing.T) { - // NOTE: Memory references are *intentionally* asserted for equality. - // It's very well known which objects are supposed to be changed. - - privKey, err := stdRsa.GenerateKey(rand.Reader, 2048) - require.Nil(t, err) - - hclBytesArg := []byte(` - resource "vault_encrypted_secret" "mysecret" { - path = "secret/example" - encrypted_passphrase = "a1b2c3d4" - encrypted_data_json = "" - }`) - hclParserArg := hcl.NewHclService() - - b64Svc := base64.NewBase64Service() - aesSvc := aes.NewAesService(pkcs7.NewPkcs7Service()) - - legacyEncryptedContentSvcArg := content.NewLegacyEncryptedContentService( - b64Svc, - aesSvc, - ) - - osExecutor := ostest.NewFakeOsExecutor(t) - - encryptedPassphraseSvcArg := passphrase.NewEncryptedPassphraseService( - b64Svc, - rsa.NewRsaService(osExecutor), - ) - encryptedPayloadSvcArg := payload.NewEncryptedPayloadService( - header.NewHeaderService(), - encryptedPassphraseSvcArg, - content.NewLegacyEncryptedContentService(b64Svc, aesSvc), - ) - - tfSvc := terraform.NewTerraformService() - tfEncMigrationSvc := NewTerraformEncryptionMigrationService(tfSvc) - - actualReturn, actualErr := tfEncMigrationSvc.MigrateEncryptedTerraformResourceHcl( - hclParserArg, - hclBytesArg, - privKey, - &privKey.PublicKey, - legacyEncryptedContentSvcArg, - encryptedPassphraseSvcArg, - encryptedPayloadSvcArg, - ) - require.Nil(t, actualReturn) - - assert.Contains( - t, - actualErr.Error(), - "empty `encrypted_data_json` attr value for "+ - "`vault_encrypted_secret.mysecret`", - ) - }, - ) - - t.Run( - "when succeeding to parse 'hclBytes' which has `vault_encrypted_secret`, "+ - "but `vault_encrypted_secret` has empty 'encrypted_passphrase' string value, "+ - "it returns error", - func(t *testing.T) { - // NOTE: Memory references are *intentionally* asserted for equality. - // It's very well known which objects are supposed to be changed. - - privKey, err := stdRsa.GenerateKey(rand.Reader, 2048) - require.Nil(t, err) - - hclBytesArg := []byte(` - resource "vault_encrypted_secret" "mysecret" { - path = "secret/example" - encrypted_passphrase = "" - encrypted_data_json = "a1b2c3d4" - }`) - hclParserArg := hcl.NewHclService() - - b64Svc := base64.NewBase64Service() - aesSvc := aes.NewAesService(pkcs7.NewPkcs7Service()) - - legacyEncryptedContentSvcArg := content.NewLegacyEncryptedContentService( - b64Svc, - aesSvc, - ) - - osExecutor := ostest.NewFakeOsExecutor(t) - - encryptedPassphraseSvcArg := passphrase.NewEncryptedPassphraseService( - b64Svc, - rsa.NewRsaService(osExecutor), - ) - encryptedPayloadSvcArg := payload.NewEncryptedPayloadService( - header.NewHeaderService(), - encryptedPassphraseSvcArg, - content.NewLegacyEncryptedContentService(b64Svc, aesSvc), - ) - - tfSvc := terraform.NewTerraformService() - tfEncMigrationSvc := NewTerraformEncryptionMigrationService(tfSvc) - - actualReturn, actualErr := tfEncMigrationSvc.MigrateEncryptedTerraformResourceHcl( - hclParserArg, - hclBytesArg, - privKey, - &privKey.PublicKey, - legacyEncryptedContentSvcArg, - encryptedPassphraseSvcArg, - encryptedPayloadSvcArg, - ) - require.Nil(t, actualReturn) - - assert.Contains( - t, - actualErr.Error(), - "empty `encrypted_passphrase` attr value for "+ - "`vault_encrypted_secret.mysecret`", - ) - }, - ) - - t.Run( - "when succeeding to parse 'hclBytes' which has `vault_encrypted_secret`, "+ - "but `vault_encrypted_secret` has non-base64 deserializable "+ - "'encrypted_passphrase' string value, "+ - "it returns error", - func(t *testing.T) { - privKey, err := stdRsa.GenerateKey(rand.Reader, 2048) - require.Nil(t, err) - - hclBytesArg := []byte(` - resource "vault_encrypted_secret" "mysecret" { - path = "secret/example" - encrypted_passphrase = "WvLTlMrX9NpYDQ\n\n\nlEIFlnDB==" - encrypted_data_json = "a1b2c3d4" - }`) - hclParserArg := hcl.NewHclService() - - b64Svc := base64.NewBase64Service() - aesSvc := aes.NewAesService(pkcs7.NewPkcs7Service()) - - legacyEncryptedContentSvcArg := content.NewLegacyEncryptedContentService( - b64Svc, - aesSvc, - ) - - osExecutor := ostest.NewFakeOsExecutor(t) - - encryptedPassphraseSvcArg := passphrase.NewEncryptedPassphraseService( - b64Svc, - rsa.NewRsaService(osExecutor), - ) - - encryptedPayloadSvcArg := payload.NewEncryptedPayloadService( - header.NewHeaderService(), - encryptedPassphraseSvcArg, - content.NewLegacyEncryptedContentService(b64Svc, aesSvc), - ) - - tfSvc := terraform.NewTerraformService() - tfEncMigrationSvc := NewTerraformEncryptionMigrationService(tfSvc) - - actualReturn, actualErr := tfEncMigrationSvc.MigrateEncryptedTerraformResourceHcl( - hclParserArg, - hclBytesArg, - privKey, - &privKey.PublicKey, - legacyEncryptedContentSvcArg, - encryptedPassphraseSvcArg, - encryptedPayloadSvcArg, - ) - require.Nil(t, actualReturn) - - assert.Contains( - t, - actualErr.Error(), - "failed to deserialize `encrypted_passphrase` attr value for "+ - "`vault_encrypted_secret.mysecret`", - ) - }, - ) - - t.Run( - "when succeeding to parse 'hclBytes' which has `vault_encrypted_secret`, "+ - "but `vault_encrypted_secret` has non-decryptable "+ - "'encrypted_passphrase' string value, "+ - "it returns error", - func(t *testing.T) { - // NOTE: Memory references are *intentionally* asserted for equality. - // It's very well known which objects are supposed to be changed. - - privKey, err := stdRsa.GenerateKey(rand.Reader, 2048) - require.Nil(t, err) - - hclBytesArg := []byte(` - resource "vault_encrypted_secret" "mysecret" { - path = "secret/example" - encrypted_passphrase = "dGVzdAo=" - encrypted_data_json = "dGVzdAo=" - }`) - hclParserArg := hcl.NewHclService() - - b64Svc := base64.NewBase64Service() - aesSvc := aes.NewAesService(pkcs7.NewPkcs7Service()) - - legacyEncryptedContentSvcArg := content.NewLegacyEncryptedContentService( - b64Svc, - aesSvc, - ) - - osExecutor := ostest.NewFakeOsExecutor(t) - - encryptedPassphraseSvcArg := passphrase.NewEncryptedPassphraseService( - b64Svc, - rsa.NewRsaService(osExecutor), - ) - - encryptedPayloadSvcArg := payload.NewEncryptedPayloadService( - header.NewHeaderService(), - encryptedPassphraseSvcArg, - content.NewLegacyEncryptedContentService(b64Svc, aesSvc), - ) - - tfSvc := terraform.NewTerraformService() - tfEncMigrationSvc := NewTerraformEncryptionMigrationService(tfSvc) - - actualReturn, actualErr := tfEncMigrationSvc.MigrateEncryptedTerraformResourceHcl( - hclParserArg, - hclBytesArg, - privKey, - &privKey.PublicKey, - legacyEncryptedContentSvcArg, - encryptedPassphraseSvcArg, - encryptedPayloadSvcArg, - ) - require.Nil(t, actualReturn) - - assert.Contains( - t, - actualErr.Error(), - "failed to decrypt `encrypted_passphrase` attr value for "+ - "`vault_encrypted_secret.mysecret`", - ) - }, - ) - - t.Run( - "when succeeding to parse 'hclBytes' which has `vault_encrypted_secret`, "+ - "but `vault_encrypted_secret` has non-deserializable "+ - "'encrypted_data_json' string value, "+ - "it returns error", - func(t *testing.T) { - // NOTE: Memory references are *intentionally* asserted for equality. - // It's very well known which objects are supposed to be changed. - - privKey, err := stdRsa.GenerateKey(rand.Reader, 2048) - require.Nil(t, err) - - osExecutor := ostest.NewFakeOsExecutor(t) - b64Svc := base64.NewBase64Service() - rsaSvc := rsa.NewRsaService(osExecutor) - encryptedPassphraseSvcArg := passphrase.NewEncryptedPassphraseService(b64Svc, rsaSvc) - - passphrase, err := encryptedPassphraseSvcArg.GeneratePassphrase(16) - require.Nil(t, err) - - encPassphrase, err := encryptedPassphraseSvcArg.Encrypt(&privKey.PublicKey, passphrase) - require.Nil(t, err) - - serializedEncPassphrase, err := encryptedPassphraseSvcArg.Serialize(encPassphrase) - require.Nil(t, err) - - hclBytesArg := []byte( - fmt.Sprintf( - `resource "vault_encrypted_secret" "mysecret" { - path = "secret/example" - encrypted_passphrase = "%s" - encrypted_data_json = "dGV\nz\ndAo=" - }`, - serializedEncPassphrase, - ), - ) - hclParserArg := hcl.NewHclService() - - aesSvc := aes.NewAesService(pkcs7.NewPkcs7Service()) - - legacyEncryptedContentSvcArg := content.NewLegacyEncryptedContentService( - b64Svc, - aesSvc, - ) - - encryptedPayloadSvcArg := payload.NewEncryptedPayloadService( - header.NewHeaderService(), - encryptedPassphraseSvcArg, - content.NewLegacyEncryptedContentService(b64Svc, aesSvc), - ) - - tfSvc := terraform.NewTerraformService() - tfEncMigrationSvc := NewTerraformEncryptionMigrationService(tfSvc) - - actualReturn, actualErr := tfEncMigrationSvc.MigrateEncryptedTerraformResourceHcl( - hclParserArg, - hclBytesArg, - privKey, - &privKey.PublicKey, - legacyEncryptedContentSvcArg, - encryptedPassphraseSvcArg, - encryptedPayloadSvcArg, - ) - require.Nil(t, actualReturn) - - assert.Contains( - t, - actualErr.Error(), - "failed to deserialize `encrypted_data_json` attr value for "+ - "`vault_encrypted_secret.mysecret`", - ) - }, - ) - - t.Run( - "when succeeding to parse 'hclBytes' which has `vault_encrypted_secret`, "+ - "but `vault_encrypted_secret` has non-decryptable "+ - "'encrypted_data_json' string value, "+ - "it returns error", - func(t *testing.T) { - // NOTE: Memory references are *intentionally* asserted for equality. - // It's very well known which objects are supposed to be changed. - - privKey, err := stdRsa.GenerateKey(rand.Reader, 2048) - require.Nil(t, err) - - osExecutor := ostest.NewFakeOsExecutor(t) - b64Svc := base64.NewBase64Service() - rsaSvc := rsa.NewRsaService(osExecutor) - encryptedPassphraseSvcArg := passphrase.NewEncryptedPassphraseService(b64Svc, rsaSvc) - - passphrase, err := encryptedPassphraseSvcArg.GeneratePassphrase(16) - require.Nil(t, err) - - encPassphrase, err := encryptedPassphraseSvcArg.Encrypt(&privKey.PublicKey, passphrase) - require.Nil(t, err) - - serializedEncPassphrase, err := encryptedPassphraseSvcArg.Serialize(encPassphrase) - require.Nil(t, err) - - hclBytesArg := []byte( - fmt.Sprintf( - `resource "vault_encrypted_secret" "mysecret" { - path = "secret/example" - encrypted_passphrase = "%s" - encrypted_data_json = "dGVzdAo=" - }`, - serializedEncPassphrase, - ), - ) - hclParserArg := hcl.NewHclService() - - aesSvc := aes.NewAesService(pkcs7.NewPkcs7Service()) - - legacyEncryptedContentSvcArg := content.NewLegacyEncryptedContentService( - b64Svc, - aesSvc, - ) - - encryptedPayloadSvcArg := payload.NewEncryptedPayloadService( - header.NewHeaderService(), - encryptedPassphraseSvcArg, - content.NewLegacyEncryptedContentService(b64Svc, aesSvc), - ) - - tfSvc := terraform.NewTerraformService() - tfEncMigrationSvc := NewTerraformEncryptionMigrationService(tfSvc) - - actualReturn, actualErr := tfEncMigrationSvc.MigrateEncryptedTerraformResourceHcl( - hclParserArg, - hclBytesArg, - privKey, - &privKey.PublicKey, - legacyEncryptedContentSvcArg, - encryptedPassphraseSvcArg, - encryptedPayloadSvcArg, - ) - require.Nil(t, actualReturn) - - assert.Contains( - t, - actualErr.Error(), - "failed to decrypt `encrypted_data_json` attr value for "+ - "`vault_encrypted_secret.mysecret`", - ) - }, - ) - - t.Run( - "when succeeding to parse 'hclBytes' which has `vault_encrypted_secret`, "+ - "but fails to generate passphrase for new `vault_encrypted_secret` resource"+ - "it returns error", - func(t *testing.T) { - privKey, err := stdRsa.GenerateKey(rand.Reader, 2048) - require.Nil(t, err) - - b64Svc := base64.NewBase64Service() - - aesSvc := aes.NewAesService(pkcs7.NewPkcs7Service()) - osExecutor := ostest.NewFakeOsExecutor(t) - rsaSvc := rsa.NewRsaService(osExecutor) - realEncryptedPassphraseSvc := passphrase.NewEncryptedPassphraseService( - b64Svc, - rsaSvc, - ) - passphrase, err := realEncryptedPassphraseSvc.GeneratePassphrase(16) - require.Nil(t, err) - - encPassphrase, err := realEncryptedPassphraseSvc.Encrypt(&privKey.PublicKey, passphrase) - require.Nil(t, err) - - serializedEncPassphrase, err := realEncryptedPassphraseSvc.Serialize(encPassphrase) - require.Nil(t, err) - - legacyEncContentSvc := content.NewLegacyEncryptedContentService(b64Svc, aesSvc) - - encContent, err := legacyEncContentSvc.Encrypt( - passphrase, - content.NewContent( - []byte("content_a"), - ), - ) - require.Nil(t, err) - - serializedEncContent, err := legacyEncContentSvc.Serialize(encContent) - require.Nil(t, err) - - hclBytesArg := []byte( - fmt.Sprintf( - `resource "vault_encrypted_secret" "mysecret" { - path = "secret/example" - encrypted_passphrase = "%s" - encrypted_data_json = "%s" - }`, - serializedEncPassphrase, - serializedEncContent, - ), - ) - hclParserArg := hcl.NewHclService() - - legacyEncryptedContentSvcArg := content.NewLegacyEncryptedContentService( - b64Svc, - aesSvc, - ) - - encryptedPayloadSvcArg := payload.NewEncryptedPayloadService( - header.NewHeaderService(), - realEncryptedPassphraseSvc, - content.NewLegacyEncryptedContentService(b64Svc, aesSvc), - ) - - tfSvc := terraform.NewTerraformService() - tfEncMigrationSvc := NewTerraformEncryptionMigrationService(tfSvc) - - fakeError := errors.New("fakeGeneratePassphraseError") - encryptedPassphraseSvcArg := &testPassphrase.MockEncryptedPassphraseService{} - - encryptedPassphraseSvcArg.On( - "Deserialize", - mock.AnythingOfType("[]uint8"), - ).Return(encPassphrase, nil) - - encryptedPassphraseSvcArg.On( - "Decrypt", - privKey, - encPassphrase, - ).Return(passphrase, nil) - - encryptedPassphraseSvcArg.On( - "GeneratePassphrase", - 32, - ).Return( - nil, - fakeError, - ) - - actualReturn, actualErr := tfEncMigrationSvc.MigrateEncryptedTerraformResourceHcl( - hclParserArg, - hclBytesArg, - privKey, - &privKey.PublicKey, - legacyEncryptedContentSvcArg, - encryptedPassphraseSvcArg, - encryptedPayloadSvcArg, - ) - require.Nil(t, actualReturn) - - assert.Contains( - t, - actualErr.Error(), - "failed to generate new encrypted passphrase for "+ - "`vault_encrypted_secret.mysecret`", - ) - }, - ) - - t.Run( - "when succeeding to parse 'hclBytes' which has `vault_encrypted_secret`, "+ - "but fails to encrypt payload for new terraform resource, "+ - "it returns error", - func(t *testing.T) { - privKey, err := stdRsa.GenerateKey(rand.Reader, 2048) - require.Nil(t, err) - - b64Svc := base64.NewBase64Service() - - aesSvc := aes.NewAesService(pkcs7.NewPkcs7Service()) - osExecutor := ostest.NewFakeOsExecutor(t) - rsaSvc := rsa.NewRsaService(osExecutor) - realEncryptedPassphraseSvc := passphrase.NewEncryptedPassphraseService( - b64Svc, - rsaSvc, - ) - passphraseArg, err := realEncryptedPassphraseSvc.GeneratePassphrase(16) - require.Nil(t, err) - - encPassphrase, err := realEncryptedPassphraseSvc.Encrypt(&privKey.PublicKey, passphraseArg) - require.Nil(t, err) - - serializedEncPassphrase, err := realEncryptedPassphraseSvc.Serialize(encPassphrase) - require.Nil(t, err) - - legacyEncContentSvc := content.NewLegacyEncryptedContentService(b64Svc, aesSvc) - - encContent, err := legacyEncContentSvc.Encrypt( - passphraseArg, - content.NewContent( - []byte("content_a"), - ), - ) - require.Nil(t, err) - - serializedEncContent, err := legacyEncContentSvc.Serialize(encContent) - require.Nil(t, err) - - hclBytesArg := []byte( - fmt.Sprintf( - `resource "vault_encrypted_secret" "mysecret" { - path = "secret/example" - encrypted_passphrase = "%s" - encrypted_data_json = "%s" - }`, - serializedEncPassphrase, - serializedEncContent, - ), - ) - hclParserArg := hcl.NewHclService() - - legacyEncryptedContentSvcArg := content.NewLegacyEncryptedContentService( - b64Svc, - aesSvc, - ) - - tfSvc := terraform.NewTerraformService() - tfEncMigrationSvc := NewTerraformEncryptionMigrationService(tfSvc) - - encryptedPassphraseSvcArg := passphrase.NewEncryptedPassphraseService( - b64Svc, - rsaSvc, - ) - encryptedPayloadSvcArg := &testPayload.MockEncryptedPayloadService{} - encryptedPayloadSvcArg.Test(t) - - fakeError := errors.New("fakeEncrypt") - encryptedPayloadSvcArg.On( - "Encrypt", - &privKey.PublicKey, - mock.AnythingOfType("*payload.Payload"), - ).Return(nil, fakeError) - - actualReturn, actualErr := tfEncMigrationSvc.MigrateEncryptedTerraformResourceHcl( - hclParserArg, - hclBytesArg, - privKey, - &privKey.PublicKey, - legacyEncryptedContentSvcArg, - encryptedPassphraseSvcArg, - encryptedPayloadSvcArg, - ) - require.Nil(t, actualReturn) - - assert.Contains( - t, - actualErr.Error(), - "failed to encrypt new encrypted payload for "+ - "`vault_encrypted_secret.mysecret`", - ) - }, - ) - - t.Run( - "when succeeding to parse 'hclBytes' which has `vault_encrypted_secret`, "+ - "but fails to serialize encrypted payload for new terraform resource, "+ - "it returns error", - func(t *testing.T) { - privKey, err := stdRsa.GenerateKey(rand.Reader, 2048) - require.Nil(t, err) - - b64Svc := base64.NewBase64Service() - - aesSvc := aes.NewAesService(pkcs7.NewPkcs7Service()) - osExecutor := ostest.NewFakeOsExecutor(t) - rsaSvc := rsa.NewRsaService(osExecutor) - realEncryptedPassphraseSvc := passphrase.NewEncryptedPassphraseService( - b64Svc, - rsaSvc, - ) - passphraseArg, err := realEncryptedPassphraseSvc.GeneratePassphrase(16) - require.Nil(t, err) - - encPassphrase, err := realEncryptedPassphraseSvc.Encrypt(&privKey.PublicKey, passphraseArg) - require.Nil(t, err) - - serializedEncPassphrase, err := realEncryptedPassphraseSvc.Serialize(encPassphrase) - require.Nil(t, err) - - legacyEncContentSvc := content.NewLegacyEncryptedContentService(b64Svc, aesSvc) - - encContent, err := legacyEncContentSvc.Encrypt( - passphraseArg, - content.NewContent( - []byte("content_a"), - ), - ) - require.Nil(t, err) - - serializedEncContent, err := legacyEncContentSvc.Serialize(encContent) - require.Nil(t, err) - - hclBytesArg := []byte( - fmt.Sprintf( - `resource "vault_encrypted_secret" "mysecret" { - path = "secret/example" - encrypted_passphrase = "%s" - encrypted_data_json = "%s" - }`, - serializedEncPassphrase, - serializedEncContent, - ), - ) - hclParserArg := hcl.NewHclService() - - legacyEncryptedContentSvcArg := content.NewLegacyEncryptedContentService( - b64Svc, - aesSvc, - ) - - tfSvc := terraform.NewTerraformService() - tfEncMigratioNSvc := NewTerraformEncryptionMigrationService(tfSvc) - - encryptedPassphraseSvcArg := passphrase.NewEncryptedPassphraseService( - b64Svc, - rsaSvc, - ) - encryptedPayloadSvcArg := &testPayload.MockEncryptedPayloadService{} - encryptedPayloadSvcArg.Test(t) - - encPassphrase, err = realEncryptedPassphraseSvc.Encrypt(&privKey.PublicKey, passphraseArg) - require.Nil(t, err) - - encContent, err = legacyEncContentSvc.Encrypt( - passphraseArg, - content.NewContent( - []byte("content_a"), - ), - ) - require.Nil(t, err) - - encPayload := payload.NewEncryptedPayload( - header.NewHeader(), - encPassphrase, - encContent, - ) - - encryptedPayloadSvcArg.On( - "Encrypt", - &privKey.PublicKey, - mock.AnythingOfType("*payload.Payload"), - ).Return(encPayload, nil) - - fakeError := errors.New("fakeSerialize") - encryptedPayloadSvcArg.On( - "Serialize", - encPayload, - ).Return(nil, fakeError) - - actualReturn, actualErr := tfEncMigratioNSvc.MigrateEncryptedTerraformResourceHcl( - hclParserArg, - hclBytesArg, - privKey, - &privKey.PublicKey, - legacyEncryptedContentSvcArg, - encryptedPassphraseSvcArg, - encryptedPayloadSvcArg, - ) - require.Nil(t, actualReturn) - - assert.Contains( - t, - actualErr.Error(), - "failed to serialize new encrypted payload for "+ - "`vault_encrypted_secret.mysecret`", - ) - }, - ) - - t.Run( - "when succeeding to parse 'hclBytes' which has `vault_encrypted_secret`, "+ - " and succeeds to encrypt and serialize payload "+ - "it returns AST with modified resources", - func(t *testing.T) { - t.Parallel() - - tfSvc := terraform.NewTerraformService() - privKey, err := stdRsa.GenerateKey(rand.Reader, 2048) - require.Nil(t, err) - - osExecutor := ostest.NewFakeOsExecutor(t) - b64Svc := base64.NewBase64Service() - rsaSvc := rsa.NewRsaService(osExecutor) - aesSvc := aes.NewAesService(pkcs7.NewPkcs7Service()) - - encPassphraseSvc := passphrase.NewEncryptedPassphraseService(b64Svc, rsaSvc) - legacyEncContentSvc := content.NewLegacyEncryptedContentService(b64Svc, aesSvc) - - // NOTE: 16 key length since we're using AES CBC for legacy encryption strategy - passphraseA, err := encPassphraseSvc.GeneratePassphrase(16) - require.Nil(t, err) - - encPassphraseA, err := encPassphraseSvc.Encrypt(&privKey.PublicKey, passphraseA) - require.Nil(t, err) - - serializedEncPassphraseA, err := encPassphraseSvc.Serialize(encPassphraseA) - require.Nil(t, err) - - contentA := content.NewContent([]byte("content_a")) - encContentA, err := legacyEncContentSvc.Encrypt(passphraseA, contentA) - require.Nil(t, err) - - serializedEncContentA, err := legacyEncContentSvc.Serialize(encContentA) - require.Nil(t, err) - - // NOTE: 16 key length since we're using AES CBC for legacy encryption strategy - passphraseB, err := encPassphraseSvc.GeneratePassphrase(16) - require.Nil(t, err) - - encPassphraseB, err := encPassphraseSvc.Encrypt(&privKey.PublicKey, passphraseB) - require.Nil(t, err) - - serializedEncPassphraseB, err := encPassphraseSvc.Serialize(encPassphraseB) - require.Nil(t, err) - - contentB := content.NewContent([]byte("content_b")) - encContentB, err := legacyEncContentSvc.Encrypt(passphraseB, contentB) - require.Nil(t, err) - - serializedEncContentB, err := legacyEncContentSvc.Serialize(encContentB) - require.Nil(t, err) - - hclSvcArg := hcl.NewHclService() - hclBytesArg := fmt.Sprintf( - `resource "vault_encrypted_secret" "my_secret_a" { - path = "secret/my_app/a" - encrypted_passphrase = "%s" - encrypted_data_json = "%s" -} -resource "vault_encrypted_secret" "my_secret_b" { - path = "secret/my_app/b" - encrypted_passphrase = "%s" - encrypted_data_json = "%s" -}`, - serializedEncPassphraseA, - serializedEncContentA, - serializedEncPassphraseB, - serializedEncContentB, - ) - - encContentSvc := content.NewV1EncryptedContentService(b64Svc, aesSvc) - - encPayloadSvc := payload.NewEncryptedPayloadService( - header.NewHeaderService(), - encPassphraseSvc, - encContentSvc, - ) - - tfEncMigrationSvc := NewTerraformEncryptionMigrationService(tfSvc) - actualReturn, actualErr := tfEncMigrationSvc.MigrateEncryptedTerraformResourceHcl( - hclSvcArg, - []byte(hclBytesArg), - privKey, - &privKey.PublicKey, - legacyEncContentSvc, - encPassphraseSvc, - encPayloadSvc, - ) - - osExecutor.AssertExpectations(t) - - require.Nil(t, actualErr) - - expectedRegex := regexp.MustCompile(`resource "vaulted_vault_secret" "my_secret_a" { - path(?:\s+)= "secret/my_app/a" - encrypted_payload(?:\s+)= "(.+)" -} -resource "vaulted_vault_secret" "my_secret_b" { - path(?:\s+)= "secret/my_app/b" - encrypted_payload(?:\s+)= "(.+)" -}`) - - actualReturnString := string(actualReturn.Bytes()) - regexMatches := expectedRegex.FindAllStringSubmatch(actualReturnString, -1) - require.Equal(t, 1, len(regexMatches)) - require.Equal(t, 3, len(regexMatches[0])) - - match := testutils.VaultedPayloadRegex.FindAllStringSubmatch(regexMatches[0][1], -1) - assert.Equal(t, 1, len(match)) - match = testutils.VaultedPayloadRegex.FindAllStringSubmatch(regexMatches[0][2], -1) - assert.Equal(t, 1, len(match)) - }, - ) -} - func TestService_RotateOrRekeyEncryptedTerraformResourceHcl(t *testing.T) { t.Run( "when failing to parse 'hclBytes', it returns an error", @@ -1587,7 +44,7 @@ func TestService_RotateOrRekeyEncryptedTerraformResourceHcl(t *testing.T) { privKey, err := stdRsa.GenerateKey(rand.Reader, 2048) require.Nil(t, err) - tfSvc := terraform.NewTerraformService() + tfSvc := terraform.NewService() tfEncMigrationSvc := NewTerraformEncryptionMigrationService(tfSvc) hclParserArg := hcl.NewHclService() hclBytesArg := []byte("{ invalid }") @@ -1597,23 +54,27 @@ func TestService_RotateOrRekeyEncryptedTerraformResourceHcl(t *testing.T) { osExecutor := ostest.NewFakeOsExecutor(t) - encryptedPassphraseSvcArg := passphrase.NewEncryptedPassphraseService( - b64Svc, - rsa.NewRsaService(osExecutor), - ) - encryptedPayloadSvcArg := payload.NewEncryptedPayloadService( - header.NewHeaderService(), - encryptedPassphraseSvcArg, - content.NewLegacyEncryptedContentService(b64Svc, aesSvc), + passphraseSvc := passphrase.NewService() + payloadSerdeSvc := payload.NewSerdeService(b64Svc) + + rsaSvc := rsa.NewRsaService(osExecutor) + contentSvc := content.NewV1Service(b64Svc, aesSvc) + passphraseDecrypter := passphrase.NewDecryptionRsaPKCS1v15Service( + privKey, + rsaSvc, ) + oldPayloadDecryptor := payload.NewDecryptionService(passphraseDecrypter, contentSvc) + + passphraseEncrypter := passphrase.NewEncryptionRsaPKCS1v15Service(rsaSvc, &privKey.PublicKey) + newPayloadEncryptor := payload.NewEncryptionService(passphraseEncrypter, contentSvc) actualReturn, actualErr := tfEncMigrationSvc.RotateOrRekeyEncryptedTerraformResourceHcl( hclParserArg, hclBytesArg, - privKey, - &privKey.PublicKey, - encryptedPassphraseSvcArg, - encryptedPayloadSvcArg, + passphraseSvc, + payloadSerdeSvc, + oldPayloadDecryptor, + newPayloadEncryptor, ) require.Nil(t, actualReturn) @@ -1649,29 +110,32 @@ resource "vault_generic_secret" "mysecret" { osExecutor := ostest.NewFakeOsExecutor(t) - encryptedPassphraseSvcArg := passphrase.NewEncryptedPassphraseService( - b64Svc, - rsa.NewRsaService(osExecutor), - ) - encryptedPayloadSvcArg := payload.NewEncryptedPayloadService( - header.NewHeaderService(), - encryptedPassphraseSvcArg, - content.NewLegacyEncryptedContentService(b64Svc, aesSvc), + passphraseSvc := passphrase.NewService() + payloadSerdeSvc := payload.NewSerdeService(b64Svc) + + rsaSvc := rsa.NewRsaService(osExecutor) + contentSvc := content.NewV1Service(b64Svc, aesSvc) + passphraseDecrypter := passphrase.NewDecryptionRsaPKCS1v15Service( + privKey, + rsaSvc, ) + oldPayloadDecryptor := payload.NewDecryptionService(passphraseDecrypter, contentSvc) - tfSvc := terraform.NewTerraformService() - tfEncMigrationSvc := NewTerraformEncryptionMigrationService(tfSvc) + passphraseEncrypter := passphrase.NewEncryptionRsaPKCS1v15Service(rsaSvc, &privKey.PublicKey) + newPayloadEncryptor := payload.NewEncryptionService(passphraseEncrypter, contentSvc) expectedReturn, err := hclParserArg.Parse(hclBytesArg) require.Nil(t, err) + tfSvc := terraform.NewService() + tfEncMigrationSvc := NewTerraformEncryptionMigrationService(tfSvc) actualReturn, actualErr := tfEncMigrationSvc.RotateOrRekeyEncryptedTerraformResourceHcl( hclParserArg, hclBytesArg, - privKey, - &privKey.PublicKey, - encryptedPassphraseSvcArg, - encryptedPayloadSvcArg, + passphraseSvc, + payloadSerdeSvc, + oldPayloadDecryptor, + newPayloadEncryptor, ) require.Nil(t, actualErr) @@ -1695,26 +159,30 @@ resource "vault_generic_secret" "mysecret" { osExecutor := ostest.NewFakeOsExecutor(t) - encryptedPassphraseSvcArg := passphrase.NewEncryptedPassphraseService( - b64Svc, - rsa.NewRsaService(osExecutor), - ) - encryptedPayloadSvcArg := payload.NewEncryptedPayloadService( - header.NewHeaderService(), - encryptedPassphraseSvcArg, - content.NewLegacyEncryptedContentService(b64Svc, aesSvc), + passphraseSvc := passphrase.NewService() + payloadSerdeSvc := payload.NewSerdeService(b64Svc) + + rsaSvc := rsa.NewRsaService(osExecutor) + contentSvc := content.NewV1Service(b64Svc, aesSvc) + passphraseDecrypter := passphrase.NewDecryptionRsaPKCS1v15Service( + privKey, + rsaSvc, ) + oldPayloadDecryptor := payload.NewDecryptionService(passphraseDecrypter, contentSvc) - tfSvc := terraform.NewTerraformService() + passphraseEncrypter := passphrase.NewEncryptionRsaPKCS1v15Service(rsaSvc, &privKey.PublicKey) + newPayloadEncryptor := payload.NewEncryptionService(passphraseEncrypter, contentSvc) + + tfSvc := terraform.NewService() tfEncMigrationSvc := NewTerraformEncryptionMigrationService(tfSvc) actualReturn, actualErr := tfEncMigrationSvc.RotateOrRekeyEncryptedTerraformResourceHcl( hclParserArg, hclBytesArg, - privKey, - &privKey.PublicKey, - encryptedPassphraseSvcArg, - encryptedPayloadSvcArg, + passphraseSvc, + payloadSerdeSvc, + oldPayloadDecryptor, + newPayloadEncryptor, ) require.Nil(t, actualReturn) @@ -1748,26 +216,29 @@ resource "vault_generic_secret" "mysecret" { osExecutor := ostest.NewFakeOsExecutor(t) - encryptedPassphraseSvcArg := passphrase.NewEncryptedPassphraseService( - b64Svc, - rsa.NewRsaService(osExecutor), - ) - encryptedPayloadSvcArg := payload.NewEncryptedPayloadService( - header.NewHeaderService(), - encryptedPassphraseSvcArg, - content.NewLegacyEncryptedContentService(b64Svc, aesSvc), + passphraseSvc := passphrase.NewService() + payloadSerdeSvc := payload.NewSerdeService(b64Svc) + + rsaSvc := rsa.NewRsaService(osExecutor) + contentSvc := content.NewV1Service(b64Svc, aesSvc) + passphraseDecrypter := passphrase.NewDecryptionRsaPKCS1v15Service( + privKey, + rsaSvc, ) + oldPayloadDecryptor := payload.NewDecryptionService(passphraseDecrypter, contentSvc) - tfSvc := terraform.NewTerraformService() - tfEncMigrationSvc := NewTerraformEncryptionMigrationService(tfSvc) + passphraseEncrypter := passphrase.NewEncryptionRsaPKCS1v15Service(rsaSvc, &privKey.PublicKey) + newPayloadEncryptor := payload.NewEncryptionService(passphraseEncrypter, contentSvc) + tfSvc := terraform.NewService() + tfEncMigrationSvc := NewTerraformEncryptionMigrationService(tfSvc) actualReturn, actualErr := tfEncMigrationSvc.RotateOrRekeyEncryptedTerraformResourceHcl( hclParserArg, hclBytesArg, - privKey, - &privKey.PublicKey, - encryptedPassphraseSvcArg, - encryptedPayloadSvcArg, + passphraseSvc, + payloadSerdeSvc, + oldPayloadDecryptor, + newPayloadEncryptor, ) require.Nil(t, actualReturn) @@ -1802,26 +273,29 @@ resource "vault_generic_secret" "mysecret" { osExecutor := ostest.NewFakeOsExecutor(t) - encryptedPassphraseSvcArg := passphrase.NewEncryptedPassphraseService( - b64Svc, - rsa.NewRsaService(osExecutor), - ) - encryptedPayloadSvcArg := payload.NewEncryptedPayloadService( - header.NewHeaderService(), - encryptedPassphraseSvcArg, - content.NewLegacyEncryptedContentService(b64Svc, aesSvc), + passphraseSvc := passphrase.NewService() + payloadSerdeSvc := payload.NewSerdeService(b64Svc) + + rsaSvc := rsa.NewRsaService(osExecutor) + contentSvc := content.NewV1Service(b64Svc, aesSvc) + passphraseDecrypter := passphrase.NewDecryptionRsaPKCS1v15Service( + privKey, + rsaSvc, ) + oldPayloadDecryptor := payload.NewDecryptionService(passphraseDecrypter, contentSvc) - tfSvc := terraform.NewTerraformService() - tfEncMigrationSvc := NewTerraformEncryptionMigrationService(tfSvc) + passphraseEncrypter := passphrase.NewEncryptionRsaPKCS1v15Service(rsaSvc, &privKey.PublicKey) + newPayloadEncryptor := payload.NewEncryptionService(passphraseEncrypter, contentSvc) + tfSvc := terraform.NewService() + tfEncMigrationSvc := NewTerraformEncryptionMigrationService(tfSvc) actualReturn, actualErr := tfEncMigrationSvc.RotateOrRekeyEncryptedTerraformResourceHcl( hclParserArg, hclBytesArg, - privKey, - &privKey.PublicKey, - encryptedPassphraseSvcArg, - encryptedPayloadSvcArg, + passphraseSvc, + payloadSerdeSvc, + oldPayloadDecryptor, + newPayloadEncryptor, ) require.Nil(t, actualReturn) @@ -1856,26 +330,29 @@ resource "vault_generic_secret" "mysecret" { osExecutor := ostest.NewFakeOsExecutor(t) - encryptedPassphraseSvcArg := passphrase.NewEncryptedPassphraseService( - b64Svc, - rsa.NewRsaService(osExecutor), - ) - encryptedPayloadSvcArg := payload.NewEncryptedPayloadService( - header.NewHeaderService(), - encryptedPassphraseSvcArg, - content.NewLegacyEncryptedContentService(b64Svc, aesSvc), + passphraseSvc := passphrase.NewService() + payloadSerdeSvc := payload.NewSerdeService(b64Svc) + + rsaSvc := rsa.NewRsaService(osExecutor) + contentSvc := content.NewV1Service(b64Svc, aesSvc) + passphraseDecrypter := passphrase.NewDecryptionRsaPKCS1v15Service( + privKey, + rsaSvc, ) + oldPayloadDecryptor := payload.NewDecryptionService(passphraseDecrypter, contentSvc) - tfSvc := terraform.NewTerraformService() - tfEncMigrationSvc := NewTerraformEncryptionMigrationService(tfSvc) + passphraseEncrypter := passphrase.NewEncryptionRsaPKCS1v15Service(rsaSvc, &privKey.PublicKey) + newPayloadEncryptor := payload.NewEncryptionService(passphraseEncrypter, contentSvc) + tfSvc := terraform.NewService() + tfEncMigrationSvc := NewTerraformEncryptionMigrationService(tfSvc) actualReturn, actualErr := tfEncMigrationSvc.RotateOrRekeyEncryptedTerraformResourceHcl( hclParserArg, hclBytesArg, - privKey, - &privKey.PublicKey, - encryptedPassphraseSvcArg, - encryptedPayloadSvcArg, + passphraseSvc, + payloadSerdeSvc, + oldPayloadDecryptor, + newPayloadEncryptor, ) require.Nil(t, actualReturn) @@ -1910,27 +387,29 @@ resource "vault_generic_secret" "mysecret" { osExecutor := ostest.NewFakeOsExecutor(t) - encryptedPassphraseSvcArg := passphrase.NewEncryptedPassphraseService( - b64Svc, - rsa.NewRsaService(osExecutor), - ) + passphraseSvc := passphrase.NewService() + payloadSerdeSvc := payload.NewSerdeService(b64Svc) - encryptedPayloadSvcArg := payload.NewEncryptedPayloadService( - header.NewHeaderService(), - encryptedPassphraseSvcArg, - content.NewLegacyEncryptedContentService(b64Svc, aesSvc), + rsaSvc := rsa.NewRsaService(osExecutor) + contentSvc := content.NewV1Service(b64Svc, aesSvc) + passphraseDecrypter := passphrase.NewDecryptionRsaPKCS1v15Service( + privKey, + rsaSvc, ) + oldPayloadDecryptor := payload.NewDecryptionService(passphraseDecrypter, contentSvc) - tfSvc := terraform.NewTerraformService() - tfEncMigrationSvc := NewTerraformEncryptionMigrationService(tfSvc) + passphraseEncrypter := passphrase.NewEncryptionRsaPKCS1v15Service(rsaSvc, &privKey.PublicKey) + newPayloadEncryptor := payload.NewEncryptionService(passphraseEncrypter, contentSvc) + tfSvc := terraform.NewService() + tfEncMigrationSvc := NewTerraformEncryptionMigrationService(tfSvc) actualReturn, actualErr := tfEncMigrationSvc.RotateOrRekeyEncryptedTerraformResourceHcl( hclParserArg, hclBytesArg, - privKey, - &privKey.PublicKey, - encryptedPassphraseSvcArg, - encryptedPayloadSvcArg, + passphraseSvc, + payloadSerdeSvc, + oldPayloadDecryptor, + newPayloadEncryptor, ) require.Nil(t, actualReturn) @@ -1966,114 +445,36 @@ resource "vault_generic_secret" "mysecret" { osExecutor := ostest.NewFakeOsExecutor(t) - encryptedPassphraseSvcArg := passphrase.NewEncryptedPassphraseService( - b64Svc, - rsa.NewRsaService(osExecutor), - ) - - encryptedPayloadSvcArg := payload.NewEncryptedPayloadService( - header.NewHeaderService(), - encryptedPassphraseSvcArg, - content.NewLegacyEncryptedContentService(b64Svc, aesSvc), - ) - - tfSvc := terraform.NewTerraformService() - tfEncMigrationSvc := NewTerraformEncryptionMigrationService(tfSvc) - - actualReturn, actualErr := tfEncMigrationSvc.RotateOrRekeyEncryptedTerraformResourceHcl( - hclParserArg, - hclBytesArg, - privKey, - &privKey.PublicKey, - encryptedPassphraseSvcArg, - encryptedPayloadSvcArg, - ) - require.Nil(t, actualReturn) - - assert.Contains( - t, - actualErr.Error(), - "failed to decrypt `payload_json` attr's value for `vaulted_vault_secret.mysecret`", - ) - }, - ) - - t.Run( - "when succeeding to parse 'hclBytes' which has `vaulted_vault_secret`, "+ - "but fails to generate passphrase for new `vaulted_vault_secret` resource"+ - "it returns error", - func(t *testing.T) { - privKey, err := stdRsa.GenerateKey(rand.Reader, 2048) - require.Nil(t, err) - - b64Svc := base64.NewBase64Service() + passphraseSvc := passphrase.NewService() + payloadSerdeSvc := payload.NewSerdeService(b64Svc) - aesSvc := aes.NewAesService(pkcs7.NewPkcs7Service()) - osExecutor := ostest.NewFakeOsExecutor(t) rsaSvc := rsa.NewRsaService(osExecutor) - realEncryptedPassphraseSvc := passphrase.NewEncryptedPassphraseService( - b64Svc, + contentSvc := content.NewV1Service(b64Svc, aesSvc) + passphraseDecrypter := passphrase.NewDecryptionRsaPKCS1v15Service( + privKey, rsaSvc, ) - realEncContentSvc := content.NewV1EncryptedContentService(b64Svc, aesSvc) - encPayloadSvcArg := payload.NewEncryptedPayloadService( - header.NewHeaderService(), - realEncryptedPassphraseSvc, - realEncContentSvc, - ) + oldPayloadDecryptor := payload.NewDecryptionService(passphraseDecrypter, contentSvc) - passphrase, err := realEncryptedPassphraseSvc.GeneratePassphrase(32) - require.Nil(t, err) - - contentArg := content.NewContent([]byte("mysecret!")) - payload := payload.NewPayload( - header.NewHeader(), - passphrase, - contentArg, - ) - - encPayload, err := encPayloadSvcArg.Encrypt(&privKey.PublicKey, payload) - serializedEncPayload, err := encPayloadSvcArg.Serialize(encPayload) - require.Nil(t, err) - - hclBytesArg := []byte( - fmt.Sprintf( - `resource "vaulted_vault_secret" "mysecret" { - path = "secret/example" - payload_json = "%s" - }`, - serializedEncPayload, - ), - ) - hclParserArg := hcl.NewHclService() + passphraseEncrypter := passphrase.NewEncryptionRsaPKCS1v15Service(rsaSvc, &privKey.PublicKey) + newPayloadEncryptor := payload.NewEncryptionService(passphraseEncrypter, contentSvc) - tfSvc := terraform.NewTerraformService() + tfSvc := terraform.NewService() tfEncMigrationSvc := NewTerraformEncryptionMigrationService(tfSvc) - - fakeError := errors.New("fakeGeneratePassphraseError") - encryptedPassphraseSvcArg := &testPassphrase.MockEncryptedPassphraseService{} - encryptedPassphraseSvcArg.On( - "GeneratePassphrase", - 32, - ).Return( - nil, - fakeError, - ) - actualReturn, actualErr := tfEncMigrationSvc.RotateOrRekeyEncryptedTerraformResourceHcl( hclParserArg, hclBytesArg, - privKey, - &privKey.PublicKey, - encryptedPassphraseSvcArg, - encPayloadSvcArg, + passphraseSvc, + payloadSerdeSvc, + oldPayloadDecryptor, + newPayloadEncryptor, ) require.Nil(t, actualReturn) assert.Contains( t, actualErr.Error(), - "failed to generate new passphrase for `vaulted_vault_secret.mysecret`", + "failed to decrypt `payload_json` attr's value for `vaulted_vault_secret.mysecret`", ) }, ) @@ -2085,7 +486,7 @@ resource "vault_generic_secret" "mysecret" { func(t *testing.T) { t.Parallel() - tfSvc := terraform.NewTerraformService() + tfSvc := terraform.NewService() privKey, err := stdRsa.GenerateKey(rand.Reader, 2048) require.Nil(t, err) @@ -2094,29 +495,35 @@ resource "vault_generic_secret" "mysecret" { rsaSvc := rsa.NewRsaService(osExecutor) aesSvc := aes.NewAesService(pkcs7.NewPkcs7Service()) - encPassphraseSvc := passphrase.NewEncryptedPassphraseService(b64Svc, rsaSvc) - encContentSvc := content.NewV1EncryptedContentService(b64Svc, aesSvc) - encPayloadSvc := payload.NewEncryptedPayloadService( - header.NewHeaderService(), - encPassphraseSvc, - encContentSvc, - ) + contentSvc := content.NewV1Service(b64Svc, aesSvc) + passphraseSvc := passphrase.NewService() - passphrase, err := encPassphraseSvc.GeneratePassphrase(32) + generatedPassphrase, err := passphraseSvc.GeneratePassphrase(32) require.Nil(t, err) - payload := payload.NewPayload( + payloadInstance := payload.NewPayload( header.NewHeader(), - passphrase, + generatedPassphrase, content.NewContent( []byte("mysecret!"), ), ) - encPayload, err := encPayloadSvc.Encrypt(&privKey.PublicKey, payload) + passphraseDecrypter := passphrase.NewDecryptionRsaPKCS1v15Service( + privKey, + rsaSvc, + ) + oldPayloadDecryptor := payload.NewDecryptionService(passphraseDecrypter, contentSvc) + + passphraseEncrypter := passphrase.NewEncryptionRsaPKCS1v15Service(rsaSvc, &privKey.PublicKey) + newPayloadEncryptor := payload.NewEncryptionService(passphraseEncrypter, contentSvc) + + payloadSerdeSvc := payload.NewSerdeService(b64Svc) + + encPayload, err := newPayloadEncryptor.Encrypt(payloadInstance) require.Nil(t, err) - serializedPayloadJSON, err := encPayloadSvc.Serialize(encPayload) + serializedPayloadJSON, err := payloadSerdeSvc.Serialize(encPayload) hclSvcArg := hcl.NewHclService() hclBytesArg := fmt.Sprintf( @@ -2131,10 +538,10 @@ resource "vault_generic_secret" "mysecret" { actualReturn, actualErr := tfEncMigrationSvc.RotateOrRekeyEncryptedTerraformResourceHcl( hclSvcArg, []byte(hclBytesArg), - privKey, - &privKey.PublicKey, - encPassphraseSvc, - encPayloadSvc, + passphraseSvc, + payloadSerdeSvc, + oldPayloadDecryptor, + newPayloadEncryptor, ) osExecutor.AssertExpectations(t) diff --git a/pkg/terraform_encryption_migration/test/testing.go b/pkg/terraform_encryption_migration/test/testing.go index 77d010c..8923c4e 100644 --- a/pkg/terraform_encryption_migration/test/testing.go +++ b/pkg/terraform_encryption_migration/test/testing.go @@ -15,62 +15,34 @@ package test import ( - "crypto/rsa" - "github.com/hashicorp/hcl/hcl/ast" "github.com/stretchr/testify/mock" "github.com/sumup-oss/vaulted/pkg/hcl" "github.com/sumup-oss/vaulted/pkg/terraform_encryption_migration" + "github.com/sumup-oss/vaulted/pkg/vaulted/passphrase" + "github.com/sumup-oss/vaulted/pkg/vaulted/payload" ) type MockTerraformEncryptionMigrationService struct { mock.Mock } -func (m *MockTerraformEncryptionMigrationService) MigrateEncryptedTerraformResourceHcl( - hclParser hcl.Parser, - hclBytes []byte, - privKey *rsa.PrivateKey, - pubKey *rsa.PublicKey, - legacyEncryptedContentSvc terraform_encryption_migration.EncryptedContentService, - encryptedPassphraseSvc terraform_encryption_migration.EncryptedPassphraseService, - encryptedPayloadSvc terraform_encryption_migration.EncryptedPayloadService, -) (*ast.File, error) { - args := m.Called( - hclParser, - hclBytes, - privKey, - pubKey, - legacyEncryptedContentSvc, - encryptedPassphraseSvc, - encryptedPayloadSvc, - ) - returnValue := args.Get(0) - err := args.Error(1) - - if returnValue == nil { - return nil, err - } - - return returnValue.(*ast.File), nil -} - func (m *MockTerraformEncryptionMigrationService) RotateOrRekeyEncryptedTerraformResourceHcl( hclParser hcl.Parser, hclBytes []byte, - privKey *rsa.PrivateKey, - pubKey *rsa.PublicKey, - encryptedPassphraseSvc terraform_encryption_migration.EncryptedPassphraseService, - encryptedPayloadSvc terraform_encryption_migration.EncryptedPayloadService, + passphraseSvc *passphrase.Service, + payloadSerdeSvc *payload.SerdeService, + oldPayloadDecrypter terraform_encryption_migration.PayloadDecrypter, + newPayloadEncrypter terraform_encryption_migration.PayloadEncrypter, ) (*ast.File, error) { args := m.Called( hclParser, hclBytes, - privKey, - pubKey, - encryptedPassphraseSvc, - encryptedPayloadSvc, + passphraseSvc, + payloadSerdeSvc, + oldPayloadDecrypter, + newPayloadEncrypter, ) returnValue := args.Get(0) err := args.Error(1) diff --git a/pkg/vaulted/content/legacy_encrypted_content_service.go b/pkg/vaulted/content/legacy_encrypted_content_service.go deleted file mode 100644 index 57b216f..0000000 --- a/pkg/vaulted/content/legacy_encrypted_content_service.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2018 SumUp Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package content - -import ( - "github.com/palantir/stacktrace" - - "github.com/sumup-oss/vaulted/pkg/vaulted/passphrase" -) - -type LegacyEncryptedContentService struct { - *baseEncryptedContentService - aesService aesService -} - -func NewLegacyEncryptedContentService( - base64Service base64Service, - aesService aesService, -) *LegacyEncryptedContentService { - return &LegacyEncryptedContentService{ - baseEncryptedContentService: &baseEncryptedContentService{ - base64Service: base64Service, - }, - aesService: aesService, - } -} - -func (s *LegacyEncryptedContentService) Encrypt( - passphrase *passphrase.Passphrase, - content *Content, -) (*EncryptedContent, error) { - ciphertext, err := s.aesService.EncryptCBC( - passphrase.Content, - content.Plaintext, - ) - if err != nil { - return nil, stacktrace.Propagate(err, "encryption of content failed") - } - - encryptedContent := NewEncryptedContent(ciphertext) - - return encryptedContent, nil -} - -func (s *LegacyEncryptedContentService) Decrypt( - passphrase *passphrase.Passphrase, - encryptedContent *EncryptedContent, -) (*Content, error) { - plaintext, err := s.aesService.DecryptCBC( - passphrase.Content, - encryptedContent.Ciphertext, - ) - if err != nil { - return nil, stacktrace.Propagate(err, "decryption of encrypted content failed") - } - - content := NewContent(plaintext) - - return content, nil -} diff --git a/pkg/vaulted/content/legacy_encrypted_content_service_test.go b/pkg/vaulted/content/legacy_encrypted_content_service_test.go deleted file mode 100644 index 2941f5d..0000000 --- a/pkg/vaulted/content/legacy_encrypted_content_service_test.go +++ /dev/null @@ -1,387 +0,0 @@ -// Copyright 2018 SumUp Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package content - -import ( - "errors" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/sumup-oss/go-pkgs/os/ostest" - "github.com/sumup-oss/vaulted/pkg/aes" - testAes "github.com/sumup-oss/vaulted/pkg/aes/test" - "github.com/sumup-oss/vaulted/pkg/base64" - "github.com/sumup-oss/vaulted/pkg/base64/test" - "github.com/sumup-oss/vaulted/pkg/pkcs7" - "github.com/sumup-oss/vaulted/pkg/rsa" - "github.com/sumup-oss/vaulted/pkg/vaulted/passphrase" -) - -func TestNewLegacyEncryptedContentService(t *testing.T) { - t.Run( - "it creates a new LegacyEncryptedContentService with 'base64Service' and 'aesService' arguments", - func(t *testing.T) { - t.Parallel() - - base64Svc := base64.NewBase64Service() - aesSvc := aes.NewAesService( - pkcs7.NewPkcs7Service(), - ) - - actual := NewLegacyEncryptedContentService( - base64Svc, - aesSvc, - ) - - assert.Equal(t, base64Svc, actual.base64Service) - assert.Equal(t, aesSvc, actual.aesService) - }, - ) -} - -func TestEncryptedContentService_Serialize(t *testing.T) { - t.Run( - "when base64 encoding of 'encryptedContent' fails, it returns an error", - func(t *testing.T) { - t.Parallel() - - mockBase64Svc := &test.MockBase64Service{} - - encContent := NewEncryptedContent( - []byte("1a2b3c4d"), - ) - fakeErr := errors.New("serializeErr") - - mockBase64Svc.On( - "Serialize", - encContent.Ciphertext, - ).Return(nil, fakeErr) - - svc := NewLegacyEncryptedContentService( - mockBase64Svc, - aes.NewAesService( - pkcs7.NewPkcs7Service(), - ), - ) - - actualReturn, actualErr := svc.Serialize(encContent) - - require.Nil(t, actualReturn) - assert.Equal(t, fakeErr, actualErr) - - mockBase64Svc.AssertExpectations(t) - }, - ) - - t.Run( - "when base64 encoding of 'encryptedContent' succeeds, it returns it base64 encoded", - func(t *testing.T) { - t.Parallel() - - b64Service := base64.NewBase64Service() - svc := NewLegacyEncryptedContentService( - base64.NewBase64Service(), - aes.NewAesService( - pkcs7.NewPkcs7Service(), - ), - ) - - encryptedContent := NewEncryptedContent( - []byte( - "1a2b3c4d"), - ) - - expectedReturn, err := b64Service.Serialize(encryptedContent.Ciphertext) - require.Nil(t, err) - - actualReturn, actualErr := svc.Serialize(encryptedContent) - require.Nil(t, actualErr) - - assert.Equal(t, expectedReturn, actualReturn) - }, - ) -} - -func TestEncryptedContentService_Deserialize(t *testing.T) { - t.Run( - "when base64 decoding of 'encoded' fails, it returns an error", - func(t *testing.T) { - t.Parallel() - - mockBase64Svc := &test.MockBase64Service{} - - encodedArg := []byte("1a2b3c4d") - fakeErr := errors.New("serializeErr") - - mockBase64Svc.On( - "Deserialize", - encodedArg, - ).Return(nil, fakeErr) - - svc := NewLegacyEncryptedContentService( - mockBase64Svc, - aes.NewAesService( - pkcs7.NewPkcs7Service(), - ), - ) - - actualReturn, actualErr := svc.Deserialize(encodedArg) - - require.Nil(t, actualReturn) - assert.Contains( - t, - actualErr.Error(), - "failed to deserialize base64 encoded encrypted content", - ) - - mockBase64Svc.AssertExpectations(t) - }, - ) - - t.Run( - "when base64 decoding of 'encoded' succeeds, it returns it encrypted content", - func(t *testing.T) { - t.Parallel() - - b64Service := base64.NewBase64Service() - svc := NewLegacyEncryptedContentService( - b64Service, - aes.NewAesService( - pkcs7.NewPkcs7Service(), - ), - ) - - ciphertext := []byte("1a2b3c4d") - encryptedPassphrase := NewEncryptedContent(ciphertext) - - encoded, err := b64Service.Serialize(encryptedPassphrase.Ciphertext) - require.Nil(t, err) - - actualReturn, actualErr := svc.Deserialize(encoded) - require.Nil(t, actualErr) - - assert.Equal(t, ciphertext, actualReturn.Ciphertext) - }, - ) -} - -func TestEncryptedContentService_Encrypt(t *testing.T) { - t.Run( - "when encryption of 'content' fails, it returns error", - func(t *testing.T) { - t.Parallel() - - b64Svc := base64.NewBase64Service() - osExecutor := ostest.NewFakeOsExecutor(t) - - encPassphraseSvc := passphrase.NewEncryptedPassphraseService( - b64Svc, - rsa.NewRsaService(osExecutor), - ) - passphraseArg, err := encPassphraseSvc.GeneratePassphrase(16) - require.Nil(t, err) - - contentArg := NewContent( - []byte("hello"), - ) - - fakeErr := errors.New("fakeEncryptError") - - mockAesSvc := &testAes.MockAesService{} - mockAesSvc.Test(t) - - mockAesSvc.On( - "EncryptCBC", - passphraseArg.Content, - []byte( - contentArg.Plaintext, - ), - ).Return( - nil, - fakeErr, - ) - - encryptedContentSvc := NewLegacyEncryptedContentService( - b64Svc, - mockAesSvc, - ) - - actualReturn, actualErr := encryptedContentSvc.Encrypt( - passphraseArg, - contentArg, - ) - - require.Nil(t, actualReturn) - - assert.Contains(t, actualErr.Error(), fakeErr.Error()) - - mockAesSvc.AssertExpectations(t) - }, - ) - - t.Run( - "when encryption of 'content' succeeds, it returns encrypted content", - func(t *testing.T) { - t.Parallel() - - b64Svc := base64.NewBase64Service() - osExecutor := ostest.NewFakeOsExecutor(t) - - encPassphraseSvc := passphrase.NewEncryptedPassphraseService( - b64Svc, - rsa.NewRsaService(osExecutor), - ) - passphraseArg, err := encPassphraseSvc.GeneratePassphrase(16) - require.Nil(t, err) - - contentArg := NewContent( - []byte("hello"), - ) - - aesSvc := aes.NewAesService( - pkcs7.NewPkcs7Service(), - ) - - encryptedContentSvc := NewLegacyEncryptedContentService( - b64Svc, - aesSvc, - ) - - actualReturn, actualErr := encryptedContentSvc.Encrypt( - passphraseArg, - contentArg, - ) - - require.Nil(t, actualErr) - - assert.NotContains( - t, - string( - actualReturn.Ciphertext, - ), - string( - contentArg.Plaintext, - ), - ) - - assert.IsType( - t, - actualReturn, - &EncryptedContent{}, - ) - }, - ) -} - -func TestEncryptedContentService_Decrypt(t *testing.T) { - t.Run( - "when decryption of 'encryptedContent' fails, it returns error", - func(t *testing.T) { - t.Parallel() - - b64Svc := base64.NewBase64Service() - osExecutor := ostest.NewFakeOsExecutor(t) - - encPassphraseSvc := passphrase.NewEncryptedPassphraseService( - b64Svc, - rsa.NewRsaService(osExecutor), - ) - passphraseArg, err := encPassphraseSvc.GeneratePassphrase(16) - require.Nil(t, err) - - encryptedContentArg := NewEncryptedContent( - []byte("1a2b3c4"), - ) - - fakeErr := errors.New("fakeDecryptError") - - mockAesSvc := &testAes.MockAesService{} - mockAesSvc.Test(t) - - mockAesSvc.On( - "DecryptCBC", - passphraseArg.Content, - []byte( - encryptedContentArg.Ciphertext, - ), - ).Return( - nil, - fakeErr, - ) - - encryptedContentSvc := NewLegacyEncryptedContentService( - b64Svc, - mockAesSvc, - ) - - actualReturn, actualErr := encryptedContentSvc.Decrypt( - passphraseArg, - encryptedContentArg, - ) - - require.Nil(t, actualReturn) - - assert.Contains(t, actualErr.Error(), fakeErr.Error()) - - mockAesSvc.AssertExpectations(t) - }, - ) - - t.Run( - "when decryption of 'encryptedContent' succeeds, it returns decrypted content", - func(t *testing.T) { - t.Parallel() - - aesSvc := aes.NewAesService( - pkcs7.NewPkcs7Service(), - ) - - b64Svc := base64.NewBase64Service() - osExecutor := ostest.NewFakeOsExecutor(t) - - encPassphraseSvc := passphrase.NewEncryptedPassphraseService( - b64Svc, - rsa.NewRsaService(osExecutor), - ) - passphraseArg, err := encPassphraseSvc.GeneratePassphrase(16) - require.Nil(t, err) - - contentArg := NewContent( - []byte("hello"), - ) - - encryptedContentSvc := NewLegacyEncryptedContentService( - b64Svc, - aesSvc, - ) - - encryptedContentArg, err := encryptedContentSvc.Encrypt( - passphraseArg, - contentArg, - ) - require.Nil(t, err) - - actualReturn, actualErr := encryptedContentSvc.Decrypt( - passphraseArg, - encryptedContentArg, - ) - - require.Nil(t, actualErr) - - assert.Equal(t, contentArg.Plaintext, actualReturn.Plaintext) - }, - ) -} diff --git a/pkg/vaulted/content/v1_encrypted_content_service.go b/pkg/vaulted/content/v1_service.go similarity index 85% rename from pkg/vaulted/content/v1_encrypted_content_service.go rename to pkg/vaulted/content/v1_service.go index 39c8910..d2e31b8 100644 --- a/pkg/vaulted/content/v1_encrypted_content_service.go +++ b/pkg/vaulted/content/v1_service.go @@ -20,13 +20,13 @@ import ( "github.com/sumup-oss/vaulted/pkg/vaulted/passphrase" ) -type V1EncryptedContentService struct { +type V1Service struct { *baseEncryptedContentService aesService aesService } -func NewV1EncryptedContentService(base64Service base64Service, aesService aesService) *V1EncryptedContentService { - return &V1EncryptedContentService{ +func NewV1Service(base64Service base64Service, aesService aesService) *V1Service { + return &V1Service{ baseEncryptedContentService: &baseEncryptedContentService{ base64Service: base64Service, }, @@ -34,7 +34,7 @@ func NewV1EncryptedContentService(base64Service base64Service, aesService aesSer } } -func (s *V1EncryptedContentService) Encrypt( +func (s *V1Service) Encrypt( passphrase *passphrase.Passphrase, content *Content, ) (*EncryptedContent, error) { @@ -51,7 +51,7 @@ func (s *V1EncryptedContentService) Encrypt( return encryptedContent, nil } -func (s *V1EncryptedContentService) Decrypt( +func (s *V1Service) Decrypt( passphrase *passphrase.Passphrase, encryptedContent *EncryptedContent, ) (*Content, error) { diff --git a/pkg/vaulted/header/header.go b/pkg/vaulted/header/header.go index a6372d5..a20d10d 100644 --- a/pkg/vaulted/header/header.go +++ b/pkg/vaulted/header/header.go @@ -15,8 +15,8 @@ package header const ( - defaultName = "$VED" - defaultVersion = "1.0" + DefaultName = "$VED" + DefaultVersion = "1.0" ) type Header struct { @@ -26,7 +26,7 @@ type Header struct { func NewHeader() *Header { return &Header{ - Name: defaultName, - Version: defaultVersion, + Name: DefaultName, + Version: DefaultVersion, } } diff --git a/pkg/vaulted/header/header_service.go b/pkg/vaulted/header/header_service.go index 6989e81..524a3d8 100644 --- a/pkg/vaulted/header/header_service.go +++ b/pkg/vaulted/header/header_service.go @@ -27,8 +27,8 @@ const ( ) var ( - headerAllowedNames = []string{defaultName} - headerAllowedVersions = []string{defaultVersion} + headerAllowedNames = []string{DefaultName} + headerAllowedVersions = []string{DefaultVersion} errHeadersPartsMismatch = errors.New("did not find exactly 2 header parts") errHeaderNameInvalid = fmt.Errorf( diff --git a/pkg/vaulted/header/header_service_test.go b/pkg/vaulted/header/header_service_test.go index 61612de..75201bd 100644 --- a/pkg/vaulted/header/header_service_test.go +++ b/pkg/vaulted/header/header_service_test.go @@ -27,12 +27,12 @@ func TestHeaderServiceConstants(t *testing.T) { assert.Equal(t, headerPartSeparator, ";") - expectedAllowedNames := []string{defaultName} + expectedAllowedNames := []string{DefaultName} assert.Equal(t, len(expectedAllowedNames), len(headerAllowedNames)) assert.Equal(t, expectedAllowedNames[0], headerAllowedNames[0]) - expectedAllowedVersions := []string{defaultVersion} + expectedAllowedVersions := []string{DefaultVersion} assert.Equal(t, len(expectedAllowedVersions), len(headerAllowedVersions)) assert.Equal(t, expectedAllowedVersions[0], headerAllowedVersions[0]) @@ -76,7 +76,7 @@ func TestHeaderService_Serialize(t *testing.T) { headerArg := &Header{ Name: "", - Version: defaultVersion, + Version: DefaultVersion, } headerSvc := NewHeaderService() @@ -95,7 +95,7 @@ func TestHeaderService_Serialize(t *testing.T) { t.Parallel() headerArg := &Header{ - Name: defaultName, + Name: DefaultName, Version: "", } @@ -159,7 +159,7 @@ func TestHeaderService_Deserialize(t *testing.T) { func(t *testing.T) { t.Parallel() - contentArg := defaultName + contentArg := DefaultName headerService := NewHeaderService() @@ -176,7 +176,7 @@ func TestHeaderService_Deserialize(t *testing.T) { func(t *testing.T) { t.Parallel() - contentArg := fmt.Sprintf("%s%s%s", "BAD", headerPartSeparator, defaultVersion) + contentArg := fmt.Sprintf("%s%s%s", "BAD", headerPartSeparator, DefaultVersion) headerService := &HeaderService{} @@ -193,7 +193,7 @@ func TestHeaderService_Deserialize(t *testing.T) { func(t *testing.T) { t.Parallel() - contentArg := fmt.Sprintf("%s%s%s", defaultName, headerPartSeparator, "BAD") + contentArg := fmt.Sprintf("%s%s%s", DefaultName, headerPartSeparator, "BAD") headerService := &HeaderService{} @@ -210,7 +210,7 @@ func TestHeaderService_Deserialize(t *testing.T) { func(t *testing.T) { t.Parallel() - contentArg := fmt.Sprintf("%s%s%s", defaultName, headerPartSeparator, defaultVersion) + contentArg := fmt.Sprintf("%s%s%s", DefaultName, headerPartSeparator, DefaultVersion) headerService := &HeaderService{} @@ -219,8 +219,8 @@ func TestHeaderService_Deserialize(t *testing.T) { require.NotNil(t, header) require.Nil(t, err) - assert.Equal(t, header.Name, defaultName) - assert.Equal(t, header.Version, defaultVersion) + assert.Equal(t, header.Name, DefaultName) + assert.Equal(t, header.Version, DefaultVersion) }, ) } diff --git a/pkg/vaulted/header/header_test.go b/pkg/vaulted/header/header_test.go index 3525c44..a009d6a 100644 --- a/pkg/vaulted/header/header_test.go +++ b/pkg/vaulted/header/header_test.go @@ -22,8 +22,8 @@ import ( func TestHeaderConstants(t *testing.T) { t.Parallel() - assert.Equal(t, "$VED", defaultName) - assert.Equal(t, "1.0", defaultVersion) + assert.Equal(t, "$VED", DefaultName) + assert.Equal(t, "1.0", DefaultVersion) } func TestNewHeader(t *testing.T) { @@ -34,8 +34,8 @@ func TestNewHeader(t *testing.T) { header := NewHeader() - assert.Equal(t, header.Name, defaultName) - assert.Equal(t, header.Version, defaultVersion) + assert.Equal(t, header.Name, DefaultName) + assert.Equal(t, header.Version, DefaultVersion) }, ) } diff --git a/pkg/vaulted/passphrase/decryption_aws_kms_service.go b/pkg/vaulted/passphrase/decryption_aws_kms_service.go new file mode 100644 index 0000000..da73a49 --- /dev/null +++ b/pkg/vaulted/passphrase/decryption_aws_kms_service.go @@ -0,0 +1,44 @@ +// Copyright 2018 SumUp Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package passphrase + +import ( + "context" + + "github.com/palantir/stacktrace" + + "github.com/sumup-oss/vaulted/pkg/aws" +) + +type DecryptionAwsKmsService struct { + awsSvc *aws.Service + kmsKeyID string +} + +func NewDecryptionAwsKmsService(awsSvc *aws.Service, kmsKeyID string) *DecryptionAwsKmsService { + return &DecryptionAwsKmsService{ + awsSvc: awsSvc, + kmsKeyID: kmsKeyID, + } +} + +func (s *DecryptionAwsKmsService) Decrypt(encryptedPassphrase *EncryptedPassphrase) (*Passphrase, error) { + plaintext, err := s.awsSvc.Decrypt(context.Background(), s.kmsKeyID, encryptedPassphrase.Ciphertext) + if err != nil { + return nil, stacktrace.Propagate(err, "failed to decrypt passphrase") + } + + return newPassphrase(plaintext), nil +} diff --git a/pkg/vaulted/passphrase/decryption_rsa_pkcs1v15_service.go b/pkg/vaulted/passphrase/decryption_rsa_pkcs1v15_service.go new file mode 100644 index 0000000..f0c0bd6 --- /dev/null +++ b/pkg/vaulted/passphrase/decryption_rsa_pkcs1v15_service.go @@ -0,0 +1,50 @@ +// Copyright 2018 SumUp Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package passphrase + +import ( + "crypto/rand" + stdRsa "crypto/rsa" + + "github.com/palantir/stacktrace" +) + +type DecryptionRsaPKCS1v15Service struct { + privateKey *stdRsa.PrivateKey + rsaService rsaService +} + +func NewDecryptionRsaPKCS1v15Service( + privateKey *stdRsa.PrivateKey, + rsaService rsaService, +) *DecryptionRsaPKCS1v15Service { + return &DecryptionRsaPKCS1v15Service{ + privateKey: privateKey, + rsaService: rsaService, + } +} + +func (s *DecryptionRsaPKCS1v15Service) Decrypt(encryptedPassphrase *EncryptedPassphrase) (*Passphrase, error) { + plaintext, err := s.rsaService.DecryptPKCS1v15( + rand.Reader, + s.privateKey, + encryptedPassphrase.Ciphertext, + ) + if err != nil { + return nil, stacktrace.Propagate(err, "failed to decrypt pasphrase") + } + + return newPassphrase(plaintext), nil +} diff --git a/pkg/vaulted/passphrase/enc_rsa_oaep_service.go b/pkg/vaulted/passphrase/enc_rsa_oaep_service.go new file mode 100644 index 0000000..eea7e61 --- /dev/null +++ b/pkg/vaulted/passphrase/enc_rsa_oaep_service.go @@ -0,0 +1,44 @@ +// Copyright 2018 SumUp Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package passphrase + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + + "github.com/palantir/stacktrace" +) + +type EncRsaOaepService struct { + rsaSvc rsaService + publicKey *rsa.PublicKey +} + +func NewEncRsaOaepService(rsaSvc rsaService, publicKey *rsa.PublicKey) *EncRsaOaepService { + return &EncRsaOaepService{ + rsaSvc: rsaSvc, + publicKey: publicKey, + } +} + +func (s *EncRsaOaepService) Encrypt(passphrase *Passphrase) (*EncryptedPassphrase, error) { + ciphertext, err := s.rsaSvc.EncryptOAEP(sha256.New(), rand.Reader, s.publicKey, passphrase.Content, nil) + if err != nil { + return nil, stacktrace.Propagate(err, "failed to encrypt passphrase using PKCS#1v15") + } + + return NewEncryptedPassphrase(ciphertext), nil +} diff --git a/pkg/vaulted/passphrase/enc_rsa_pkcs1v15_service.go b/pkg/vaulted/passphrase/enc_rsa_pkcs1v15_service.go new file mode 100644 index 0000000..3439afd --- /dev/null +++ b/pkg/vaulted/passphrase/enc_rsa_pkcs1v15_service.go @@ -0,0 +1,47 @@ +// Copyright 2018 SumUp Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package passphrase + +import ( + "crypto/rand" + "crypto/rsa" + + "github.com/palantir/stacktrace" +) + +type EncRsaPKCS1v15Service struct { + rsaSvc rsaService + publicKey *rsa.PublicKey +} + +func NewEncryptionRsaPKCS1v15Service(rsaSvc rsaService, publicKey *rsa.PublicKey) *EncRsaPKCS1v15Service { + return &EncRsaPKCS1v15Service{ + rsaSvc: rsaSvc, + publicKey: publicKey, + } +} + +func (s *EncRsaPKCS1v15Service) Encrypt(passphrase *Passphrase) (*EncryptedPassphrase, error) { + ciphertext, err := s.rsaSvc.EncryptPKCS1v15( + rand.Reader, + s.publicKey, + passphrase.Content, + ) + if err != nil { + return nil, stacktrace.Propagate(err, "failed to encrypt passphrase using PKCS#1v15") + } + + return NewEncryptedPassphrase(ciphertext), nil +} diff --git a/pkg/vaulted/passphrase/encrypted_passphrase_service.go b/pkg/vaulted/passphrase/encrypted_passphrase_service.go deleted file mode 100644 index 79b23ad..0000000 --- a/pkg/vaulted/passphrase/encrypted_passphrase_service.go +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2018 SumUp Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package passphrase - -import ( - "crypto/rand" - stdRsa "crypto/rsa" - - "github.com/palantir/stacktrace" -) - -type EncryptedPassphraseService struct { - base64Service base64Service - rsaService rsaService -} - -func NewEncryptedPassphraseService( - base64Service base64Service, - rsaService rsaService, -) *EncryptedPassphraseService { - return &EncryptedPassphraseService{ - base64Service: base64Service, - rsaService: rsaService, - } -} - -func (s *EncryptedPassphraseService) Serialize(encryptedPassphrase *EncryptedPassphrase) ([]byte, error) { - return s.base64Service.Serialize(encryptedPassphrase.Ciphertext) -} - -func (s *EncryptedPassphraseService) Deserialize(encoded []byte) (*EncryptedPassphrase, error) { - decoded, err := s.base64Service.Deserialize(encoded) - if err != nil { - return nil, stacktrace.Propagate( - err, - "failed to deserialize base64 encoded encrypted passphrase", - ) - } - - encryptedPassphrase := NewEncryptedPassphrase(decoded) - - return encryptedPassphrase, nil -} - -func (s *EncryptedPassphraseService) Encrypt( - publicKey *stdRsa.PublicKey, - passphrase *Passphrase, -) (*EncryptedPassphrase, error) { - ciphertext, err := s.rsaService.EncryptPKCS1v15( - rand.Reader, - publicKey, - passphrase.Content, - ) - if err != nil { - return nil, stacktrace.Propagate(err, "failed to encrypt passphrase") - } - - return NewEncryptedPassphrase( - ciphertext, - ), nil -} - -func (s *EncryptedPassphraseService) Decrypt( - privateKey *stdRsa.PrivateKey, - encryptedPassphrase *EncryptedPassphrase, -) (*Passphrase, error) { - plaintext, err := s.rsaService.DecryptPKCS1v15( - rand.Reader, - privateKey, - encryptedPassphrase.Ciphertext, - ) - if err != nil { - return nil, stacktrace.Propagate(err, "failed to decrypt pasphrase") - } - - return newPassphrase(plaintext), nil -} - -func (s *EncryptedPassphraseService) GeneratePassphrase(length int) (*Passphrase, error) { - b := make([]byte, length) - - _, err := randRead(b) - if err != nil { - return nil, stacktrace.Propagate(err, "failed to generate random sequence") - } - - return newPassphrase(b), nil -} diff --git a/pkg/vaulted/passphrase/encrypted_passphrase_service_test.go b/pkg/vaulted/passphrase/encrypted_passphrase_service_test.go deleted file mode 100644 index c3f0f5d..0000000 --- a/pkg/vaulted/passphrase/encrypted_passphrase_service_test.go +++ /dev/null @@ -1,352 +0,0 @@ -// Copyright 2018 SumUp Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package passphrase - -import ( - "crypto/rand" - stdRsa "crypto/rsa" - "errors" - "testing" - - "github.com/palantir/stacktrace" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/sumup-oss/go-pkgs/os" - "github.com/sumup-oss/go-pkgs/os/ostest" - "github.com/sumup-oss/vaulted/pkg/base64" - testB64 "github.com/sumup-oss/vaulted/pkg/base64/test" - "github.com/sumup-oss/vaulted/pkg/rsa" - testRsa "github.com/sumup-oss/vaulted/pkg/rsa/test" -) - -func TestNewEncryptedPassphraseService(t *testing.T) { - t.Run( - "it creates a new encrypted passphrase with 'base64Service' and 'rsaService' arguments", - func(t *testing.T) { - t.Parallel() - - base64Service := base64.NewBase64Service() - rsaService := rsa.NewRsaService(&os.RealOsExecutor{}) - - actual := NewEncryptedPassphraseService(base64Service, rsaService) - - assert.Equal(t, actual.base64Service, base64Service) - assert.Equal(t, actual.rsaService, rsaService) - }, - ) -} - -func TestEncryptedPassphraseService_Serialize(t *testing.T) { - t.Run( - "when base64 encoding of 'encryptedPassphrase' fails, it returns an error", - func(t *testing.T) { - t.Parallel() - - mockBase64Svc := &testB64.MockBase64Service{} - - encPassphrase := NewEncryptedPassphrase( - []byte("1a2b3c4d"), - ) - fakeErr := errors.New("serializeErr") - - mockBase64Svc.On( - "Serialize", - encPassphrase.Ciphertext, - ).Return(nil, fakeErr) - - svc := NewEncryptedPassphraseService( - mockBase64Svc, - rsa.NewRsaService(&os.RealOsExecutor{}), - ) - - actualReturn, actualErr := svc.Serialize(encPassphrase) - - require.Nil(t, actualReturn) - assert.Equal(t, fakeErr, actualErr) - - mockBase64Svc.AssertExpectations(t) - }, - ) - - t.Run( - "when base64 encoding of 'encryptedPassphrase' succeeds, it returns it base64 encoded", - func(t *testing.T) { - t.Parallel() - - b64Service := base64.NewBase64Service() - svc := NewEncryptedPassphraseService( - b64Service, - rsa.NewRsaService(&os.RealOsExecutor{}), - ) - - encryptedPassphrase := NewEncryptedPassphrase( - []byte( - "1a2b3c4d"), - ) - - expectedReturn, err := b64Service.Serialize(encryptedPassphrase.Ciphertext) - require.Nil(t, err) - - actualReturn, actualErr := svc.Serialize(encryptedPassphrase) - require.Nil(t, actualErr) - - assert.Equal(t, expectedReturn, actualReturn) - }, - ) -} - -func TestEncryptedPassphraseService_Deserialize(t *testing.T) { - t.Run( - "when base64 decoding of 'encoded' fails, it returns an error", - func(t *testing.T) { - t.Parallel() - - mockBase64Svc := &testB64.MockBase64Service{} - - encodedArg := []byte("1a2b3c4d") - fakeErr := errors.New("serializeErr") - - mockBase64Svc.On( - "Deserialize", - encodedArg, - ).Return(nil, fakeErr) - - svc := NewEncryptedPassphraseService( - mockBase64Svc, - rsa.NewRsaService(&os.RealOsExecutor{}), - ) - - actualReturn, actualErr := svc.Deserialize(encodedArg) - - require.Nil(t, actualReturn) - assert.Contains( - t, - actualErr.Error(), - "failed to deserialize base64 encoded encrypted passphrase", - ) - - mockBase64Svc.AssertExpectations(t) - }, - ) - - t.Run( - "when base64 decoding of 'encoded' succeeds, it returns it encrypted passphrase", - func(t *testing.T) { - t.Parallel() - - b64Service := base64.NewBase64Service() - svc := NewEncryptedPassphraseService( - b64Service, - rsa.NewRsaService(&os.RealOsExecutor{}), - ) - - ciphertext := []byte("1a2b3c4d") - encryptedPassphrase := NewEncryptedPassphrase(ciphertext) - - encoded, err := b64Service.Serialize(encryptedPassphrase.Ciphertext) - require.Nil(t, err) - - actualReturn, actualErr := svc.Deserialize(encoded) - require.Nil(t, actualErr) - - assert.Equal(t, ciphertext, actualReturn.Ciphertext) - }, - ) -} - -func TestEncryptedPassphraseService_Encrypt(t *testing.T) { - t.Run( - "when 'passphrase' content pkcs#1 v1.5 encryption fails, it returns error", - func(t *testing.T) { - t.Parallel() - - mockRsaSvc := &testRsa.MockRsaService{} - mockRsaSvc.Test(t) - - fakeError := errors.New("fakencrypterror") - - svc := NewEncryptedPassphraseService( - base64.NewBase64Service(), - mockRsaSvc, - ) - - pubkeyArg := &stdRsa.PublicKey{} - passphraseArg := newPassphrase([]byte("1234")) - - mockRsaSvc.On( - "EncryptPKCS1v15", - rand.Reader, - pubkeyArg, - []byte(passphraseArg.Content), - ).Return(nil, fakeError) - - actualReturn, actualErr := svc.Encrypt(pubkeyArg, passphraseArg) - - require.Nil(t, actualReturn) - assert.Equal(t, fakeError, stacktrace.RootCause(actualErr)) - - mockRsaSvc.AssertExpectations(t) - }, - ) - - t.Run( - "when 'passphrase' content pkcs#1 v1.5 encryption succeeds, it returns encrypted passphrase", - func(t *testing.T) { - t.Parallel() - - mockRsaSvc := &testRsa.MockRsaService{} - mockRsaSvc.Test(t) - - fakeEncryptedPassphrase := NewEncryptedPassphrase([]byte("1a2b3c4d")) - - pubkeyArg := &stdRsa.PublicKey{} - passphraseArg := newPassphrase([]byte("1234")) - - mockRsaSvc.On( - "EncryptPKCS1v15", - rand.Reader, - pubkeyArg, - []byte(passphraseArg.Content), - ).Return( - []byte(fakeEncryptedPassphrase.Ciphertext), - nil, - ) - - svc := NewEncryptedPassphraseService( - base64.NewBase64Service(), - mockRsaSvc, - ) - - actualReturn, actualErr := svc.Encrypt(pubkeyArg, passphraseArg) - - require.Nil(t, actualErr) - assert.Equal(t, fakeEncryptedPassphrase, actualReturn) - - mockRsaSvc.AssertExpectations(t) - }, - ) -} - -func TestEncryptedPassphraseService_Decrypt(t *testing.T) { - t.Run( - "when 'ciphertext' content pkcs#1 v1.5 decryption fails, it returns error", - func(t *testing.T) { - t.Parallel() - - mockRsaSvc := &testRsa.MockRsaService{} - mockRsaSvc.Test(t) - - fakeError := errors.New("fakencrypterror") - - svc := NewEncryptedPassphraseService( - base64.NewBase64Service(), - mockRsaSvc, - ) - - privkeyArg := &stdRsa.PrivateKey{} - encryptedPassphraseArg := NewEncryptedPassphrase([]byte("1a2b3c4d")) - - mockRsaSvc.On( - "DecryptPKCS1v15", - rand.Reader, - privkeyArg, - []byte(encryptedPassphraseArg.Ciphertext), - ).Return(nil, fakeError) - - actualReturn, actualErr := svc.Decrypt(privkeyArg, encryptedPassphraseArg) - - require.Nil(t, actualReturn) - assert.Equal(t, fakeError, stacktrace.RootCause(actualErr)) - - mockRsaSvc.AssertExpectations(t) - }, - ) - - t.Run( - "when 'passphrase' content pkcs#1 v1.5 decryption succeeds, it returns passphrase", - func(t *testing.T) { - t.Parallel() - - privKey, err := stdRsa.GenerateKey(rand.Reader, 2048) - require.Nil(t, err) - - svc := NewEncryptedPassphraseService( - base64.NewBase64Service(), - rsa.NewRsaService(&os.RealOsExecutor{}), - ) - - passphrase, err := svc.GeneratePassphrase(16) - require.Nil(t, err) - - encryptedPasshraseArg, err := svc.Encrypt(&privKey.PublicKey, passphrase) - require.Nil(t, err) - - actualReturn, actualErr := svc.Decrypt(privKey, encryptedPasshraseArg) - - require.Nil(t, actualErr) - assert.Equal(t, passphrase, actualReturn) - }, - ) -} - -func TestEncryptedPassphraseService_GeneratePassphrase(t *testing.T) { - t.Run( - "when generating a random buffer fails, it returns an error", - func(t *testing.T) { - lengthArg := 12 - - calledRandReadErr := errors.New("fakeerror") - - realRandRead := randRead - - defer func() { - randRead = realRandRead - }() - - randRead = func(b []byte) (n int, err error) { - return 0, calledRandReadErr - } - - osExecutor := ostest.NewFakeOsExecutor(t) - svc := NewEncryptedPassphraseService( - base64.NewBase64Service(), - rsa.NewRsaService(osExecutor), - ) - - actualReturn, actualErr := svc.GeneratePassphrase(lengthArg) - require.Nil(t, actualReturn) - assert.Equal(t, calledRandReadErr, stacktrace.RootCause(actualErr)) - }, - ) - - t.Run( - "when generating a random buffer succeeds, it generates a passphrase up to 'length'", - func(t *testing.T) { - lengthArg := 12 - - osExecutor := ostest.NewFakeOsExecutor(t) - svc := NewEncryptedPassphraseService( - base64.NewBase64Service(), - rsa.NewRsaService(osExecutor), - ) - - actual, err := svc.GeneratePassphrase(lengthArg) - - require.Nil(t, err) - assert.IsType(t, actual, &Passphrase{}) - assert.Equal(t, lengthArg, len(actual.Content)) - }, - ) -} diff --git a/pkg/vaulted/passphrase/external_interfaces.go b/pkg/vaulted/passphrase/external_interfaces.go index 2f66e65..df06d17 100644 --- a/pkg/vaulted/passphrase/external_interfaces.go +++ b/pkg/vaulted/passphrase/external_interfaces.go @@ -16,15 +16,12 @@ package passphrase import ( stdRsa "crypto/rsa" + "hash" "io" ) -type base64Service interface { - Serialize(raw []byte) ([]byte, error) - Deserialize(encoded []byte) ([]byte, error) -} - type rsaService interface { + EncryptOAEP(hash hash.Hash, random io.Reader, pub *stdRsa.PublicKey, msg []byte, label []byte) ([]byte, error) EncryptPKCS1v15(rand io.Reader, pub *stdRsa.PublicKey, msg []byte) ([]byte, error) DecryptPKCS1v15(rand io.Reader, priv *stdRsa.PrivateKey, ciphertext []byte) ([]byte, error) } diff --git a/pkg/ini/content.go b/pkg/vaulted/passphrase/service.go similarity index 60% rename from pkg/ini/content.go rename to pkg/vaulted/passphrase/service.go index 7150e64..502be4a 100644 --- a/pkg/ini/content.go +++ b/pkg/vaulted/passphrase/service.go @@ -12,16 +12,23 @@ // See the License for the specific language governing permissions and // limitations under the License. -package ini +package passphrase -type Content struct { - SectionsByName map[string]*Section -} +import "github.com/palantir/stacktrace" + +type Service struct{} -func NewIniContent() *Content { - return &Content{map[string]*Section{}} +func NewService() *Service { + return &Service{} } -func (content *Content) AddSection(section *Section) { - content.SectionsByName[section.Name] = section +func (s *Service) GeneratePassphrase(length int) (*Passphrase, error) { + b := make([]byte, length) + + _, err := randRead(b) + if err != nil { + return nil, stacktrace.Propagate(err, "failed to generate random sequence") + } + + return newPassphrase(b), nil } diff --git a/pkg/vaulted/payload/decryption_service.go b/pkg/vaulted/payload/decryption_service.go new file mode 100644 index 0000000..7a522a4 --- /dev/null +++ b/pkg/vaulted/payload/decryption_service.go @@ -0,0 +1,49 @@ +// Copyright 2018 SumUp Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package payload + +import "github.com/palantir/stacktrace" + +type DecryptionService struct { + passphraseDecrypter passphraseDecrypter + contentDecrypter contentDecrypter +} + +func NewDecryptionService(passphraseDecrypter passphraseDecrypter, contentDecrypter contentDecrypter) *DecryptionService { + return &DecryptionService{ + passphraseDecrypter: passphraseDecrypter, + contentDecrypter: contentDecrypter, + } +} + +func (s *DecryptionService) Decrypt(encryptedPayload *EncryptedPayload) (*Payload, error) { + passphraseInstance, err := s.passphraseDecrypter.Decrypt(encryptedPayload.EncryptedPassphrase) + if err != nil { + return nil, stacktrace.Propagate(err, "failed to decrypt encrypted passphrase") + } + + contentInstance, err := s.contentDecrypter.Decrypt(passphraseInstance, encryptedPayload.EncryptedContent) + if err != nil { + return nil, stacktrace.Propagate(err, "failed to decrypt content with decrypted passphrase") + } + + payloadInstance := NewPayload( + encryptedPayload.Header, + passphraseInstance, + contentInstance, + ) + + return payloadInstance, nil +} diff --git a/pkg/vaulted/payload/encrypted_payload_service.go b/pkg/vaulted/payload/encrypted_payload_service.go deleted file mode 100644 index 471a0c9..0000000 --- a/pkg/vaulted/payload/encrypted_payload_service.go +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright 2018 SumUp Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package payload - -import ( - "crypto/rsa" - "errors" - "strings" - - "github.com/palantir/stacktrace" -) - -const ( - EncryptionPayloadSeparator = "::" -) - -var ( - errInvalidPayloadParts = errors.New( - "invalid encryption payload. it must be in format of " + - "`
;;;;`", - ) - errEmptyHeader = errors.New("invalid header. empty") - errEmptyEncryptedPassphrase = errors.New("invalid encrypted passphrase. empty") - errEmptyEncryptedContent = errors.New("invalid encrypted payload. empty") -) - -type EncryptedPayloadService struct { - headerService headerService - encryptedPassphraseService encryptedPassphraseService - encryptedContentService encryptedContentService -} - -func NewEncryptedPayloadService( - headerService headerService, - encryptedPassphraseService encryptedPassphraseService, - encryptedContentService encryptedContentService, -) *EncryptedPayloadService { - return &EncryptedPayloadService{ - headerService: headerService, - encryptedPassphraseService: encryptedPassphraseService, - encryptedContentService: encryptedContentService, - } -} - -func (s *EncryptedPayloadService) Serialize(encryptedPayload *EncryptedPayload) ([]byte, error) { - serializedHeader, err := s.headerService.Serialize(encryptedPayload.Header) - if err != nil { - return nil, stacktrace.Propagate(err, "failed to serialize encrypted payload's header") - } - - var payloadParts []string - payloadParts = append( - payloadParts, - string(serializedHeader), - ) - - serializedEncryptedPassphrase, err := s.encryptedPassphraseService.Serialize( - encryptedPayload.EncryptedPassphrase, - ) - if err != nil { - return nil, stacktrace.Propagate( - err, - "failed to serialize encrypted payload's encrypted passphrase", - ) - } - - payloadParts = append( - payloadParts, - string(serializedEncryptedPassphrase), - ) - - serializedEncryptedContent, err := s.encryptedContentService.Serialize( - encryptedPayload.EncryptedContent, - ) - if err != nil { - return nil, stacktrace.Propagate( - err, - "failed to serialize encrypted payload's encrypted content", - ) - } - - payloadParts = append( - payloadParts, - string(serializedEncryptedContent), - ) - - serialized := strings.Join(payloadParts, EncryptionPayloadSeparator) - - return []byte(serialized), nil -} - -func (s *EncryptedPayloadService) Deserialize(encodedContent []byte) (*EncryptedPayload, error) { - payloadParts := strings.Split( - string(encodedContent), - EncryptionPayloadSeparator, - ) - - if len(payloadParts) != 3 { - return nil, errInvalidPayloadParts - } - - if len(payloadParts[0]) < 1 { - return nil, errEmptyHeader - } - - parsedHeader, err := s.headerService.Deserialize(payloadParts[0]) - if err != nil { - return nil, stacktrace.Propagate(err, "failed to parse header") - } - - if len(payloadParts[1]) < 1 { - return nil, errEmptyEncryptedPassphrase - } - - encryptedPassphrase, err := s.encryptedPassphraseService.Deserialize( - []byte( - payloadParts[1], - ), - ) - if err != nil { - return nil, stacktrace.Propagate( - err, - "failed to deserialize base64 encoded encrypted passphrase", - ) - } - - if len(payloadParts[2]) < 1 { - return nil, errEmptyEncryptedContent - } - - encryptedContent, err := s.encryptedContentService.Deserialize( - []byte( - payloadParts[2], - ), - ) - if err != nil { - return nil, stacktrace.Propagate( - err, - "failed to deserialize base64 encoded encrypted content", - ) - } - - encryptedPayload := NewEncryptedPayload(parsedHeader, encryptedPassphrase, encryptedContent) - - return encryptedPayload, nil -} - -func (s *EncryptedPayloadService) Encrypt(publicKey *rsa.PublicKey, payload *Payload) (*EncryptedPayload, error) { - encryptedPassphrase, err := s.encryptedPassphraseService.Encrypt(publicKey, payload.Passphrase) - if err != nil { - return nil, stacktrace.Propagate(err, "failed to encrypt encrypted passphrase") - } - - encryptedContent, err := s.encryptedContentService.Encrypt(payload.Passphrase, payload.Content) - if err != nil { - return nil, stacktrace.Propagate(err, "failed to encrypt encrypted content") - } - - encryptedPayload := NewEncryptedPayload( - payload.Header, - encryptedPassphrase, - encryptedContent, - ) - - return encryptedPayload, nil -} - -func (s *EncryptedPayloadService) Decrypt( - privateKey *rsa.PrivateKey, - encryptedPayload *EncryptedPayload, -) (*Payload, error) { - decryptedPassphrase, err := s.encryptedPassphraseService.Decrypt( - privateKey, - encryptedPayload.EncryptedPassphrase, - ) - if err != nil { - return nil, stacktrace.Propagate(err, "failed to decrypt encrypted passphrase") - } - - decryptedContent, err := s.encryptedContentService.Decrypt( - decryptedPassphrase, - encryptedPayload.EncryptedContent, - ) - if err != nil { - return nil, stacktrace.Propagate(err, "failed to decrypt encrypted content") - } - - payload := NewPayload(encryptedPayload.Header, decryptedPassphrase, decryptedContent) - - return payload, nil -} diff --git a/pkg/vaulted/payload/encrypted_payload_service_test.go b/pkg/vaulted/payload/encrypted_payload_service_test.go deleted file mode 100644 index c6023a9..0000000 --- a/pkg/vaulted/payload/encrypted_payload_service_test.go +++ /dev/null @@ -1,1214 +0,0 @@ -// Copyright 2018 SumUp Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package payload - -import ( - "crypto/rand" - stdRsa "crypto/rsa" - "errors" - "fmt" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/sumup-oss/go-pkgs/os" - "github.com/sumup-oss/go-pkgs/os/ostest" - "github.com/sumup-oss/vaulted/pkg/aes" - "github.com/sumup-oss/vaulted/pkg/base64" - "github.com/sumup-oss/vaulted/pkg/pkcs7" - "github.com/sumup-oss/vaulted/pkg/rsa" - "github.com/sumup-oss/vaulted/pkg/vaulted/content" - testContent "github.com/sumup-oss/vaulted/pkg/vaulted/content/test" - "github.com/sumup-oss/vaulted/pkg/vaulted/header" - testHeader "github.com/sumup-oss/vaulted/pkg/vaulted/header/test" - "github.com/sumup-oss/vaulted/pkg/vaulted/passphrase" - testPassphrase "github.com/sumup-oss/vaulted/pkg/vaulted/passphrase/test" -) - -func TestNewEncryptionPayloadService(t *testing.T) { - t.Run( - "it creates a new encrypted content service with specified 'HeaderService', 'EncryptedPassphraseService' and 'EncryptedContentService'", - func(t *testing.T) { - t.Parallel() - - headerServiceArg := header.NewHeaderService() - encryptedPassphraseServiceArg := passphrase.NewEncryptedPassphraseService( - base64.NewBase64Service(), - rsa.NewRsaService(&os.RealOsExecutor{}), - ) - encryptedContentServiceArg := content.NewLegacyEncryptedContentService( - base64.NewBase64Service(), - aes.NewAesService( - pkcs7.NewPkcs7Service(), - ), - ) - - actual := NewEncryptedPayloadService( - headerServiceArg, - encryptedPassphraseServiceArg, - encryptedContentServiceArg, - ) - - assert.Equal(t, headerServiceArg, actual.headerService) - assert.Equal(t, encryptedPassphraseServiceArg, actual.encryptedPassphraseService) - assert.Equal(t, encryptedContentServiceArg, actual.encryptedContentService) - }, - ) -} - -func TestEncryptedPayloadService_Constants(t *testing.T) { - t.Parallel() - - assert.Equal(t, "::", EncryptionPayloadSeparator) -} - -func TestEncryptedPayloadService_Serialize(t *testing.T) { - t.Run( - "when serializing the 'header', 'encryptedPassphrase' and "+ - "'encryptedContent' from 'encryptedPayload', "+ - "but serializing 'header' fails, "+ - "it returns an error", - func(t *testing.T) { - t.Parallel() - - mockHeaderSvc := &testHeader.MockHeaderService{} - mockHeaderSvc.Test(t) - - encryptedPassphrase := passphrase.NewEncryptedPassphrase( - []byte("1a2b3c4d"), - ) - encryptedContent := content.NewEncryptedContent( - []byte("1a2b3c4d"), - ) - - header := header.NewHeader() - encryptedPayloadArg := NewEncryptedPayload( - header, - encryptedPassphrase, - encryptedContent, - ) - - fakeError := errors.New("headerSvcError") - mockHeaderSvc.On("Serialize", header).Return(nil, fakeError) - - b64Service := base64.NewBase64Service() - - svc := NewEncryptedPayloadService( - mockHeaderSvc, - passphrase.NewEncryptedPassphraseService( - b64Service, - rsa.NewRsaService(&os.RealOsExecutor{}), - ), - content.NewLegacyEncryptedContentService( - b64Service, - aes.NewAesService( - pkcs7.NewPkcs7Service(), - ), - ), - ) - - actualReturn, actualErr := svc.Serialize(encryptedPayloadArg) - require.Nil(t, actualReturn) - - assert.Contains( - t, - actualErr.Error(), - "failed to serialize encrypted payload's header", - ) - - mockHeaderSvc.AssertExpectations(t) - }, - ) - - t.Run( - "when serializing the 'header', 'encryptedPassphrase' and "+ - "'encryptedContent' from 'encryptedPayload', "+ - "but serializing 'encryptedPassphrase' fails, "+ - "it returns an error", - func(t *testing.T) { - t.Parallel() - - mockEncryptedPassphraseSvc := &testPassphrase.MockEncryptedPassphraseService{} - mockEncryptedPassphraseSvc.Test(t) - - encryptedPassphrase := passphrase.NewEncryptedPassphrase( - []byte("1a2b3c4d"), - ) - encryptedContent := content.NewEncryptedContent( - []byte("1a2b3c4d"), - ) - - headerArg := header.NewHeader() - encryptedPayloadArg := NewEncryptedPayload( - headerArg, - encryptedPassphrase, - encryptedContent, - ) - - fakeError := errors.New("encryptedPassphraseSvcError") - mockEncryptedPassphraseSvc.On( - "Serialize", - encryptedPassphrase, - ).Return(nil, fakeError) - - b64Service := base64.NewBase64Service() - - svc := NewEncryptedPayloadService( - header.NewHeaderService(), - mockEncryptedPassphraseSvc, - content.NewLegacyEncryptedContentService( - b64Service, - aes.NewAesService( - pkcs7.NewPkcs7Service(), - ), - ), - ) - - actualReturn, actualErr := svc.Serialize(encryptedPayloadArg) - require.Nil(t, actualReturn) - - assert.Contains( - t, - actualErr.Error(), - "failed to serialize encrypted payload's encrypted passphrase", - ) - - mockEncryptedPassphraseSvc.AssertExpectations(t) - }, - ) - - t.Run( - "when serializing the 'header', 'encryptedPassphrase' and "+ - "'encryptedContent' from 'encryptedPayload', "+ - "but serializing 'encryptedContent' fails, "+ - "it returns an error", - func(t *testing.T) { - t.Parallel() - - mockEncryptedContentSvc := &testContent.MockEncryptedContentService{} - mockEncryptedContentSvc.Test(t) - - encryptedContent := content.NewEncryptedContent( - []byte("1a2b3c4d"), - ) - - fakeError := errors.New("encryptedContentSvcError") - mockEncryptedContentSvc.On( - "Serialize", - encryptedContent, - ).Return(nil, fakeError) - - encryptedPassphrase := passphrase.NewEncryptedPassphrase( - []byte("1a2b3c4d"), - ) - encryptedPayloadArg := NewEncryptedPayload( - header.NewHeader(), - encryptedPassphrase, - encryptedContent, - ) - - b64Service := base64.NewBase64Service() - - svc := NewEncryptedPayloadService( - header.NewHeaderService(), - passphrase.NewEncryptedPassphraseService( - b64Service, - rsa.NewRsaService(&os.RealOsExecutor{}), - ), - mockEncryptedContentSvc, - ) - - actualReturn, actualErr := svc.Serialize(encryptedPayloadArg) - require.Nil(t, actualReturn) - - assert.Contains( - t, - actualErr.Error(), - "failed to serialize encrypted payload's encrypted content", - ) - - mockEncryptedContentSvc.AssertExpectations(t) - }, - ) - - t.Run( - "when serializing the 'header', 'encryptedPassphrase' and "+ - "'encryptedContent' from 'encryptedPayload', "+ - "and everything succeeds, it returns serialized content", - func(t *testing.T) { - t.Parallel() - - headerArg := header.NewHeader() - encryptedPassphrase := passphrase.NewEncryptedPassphrase( - []byte("1a2b3c4d"), - ) - encryptedContent := content.NewEncryptedContent( - []byte("1a2b3c4d"), - ) - - encryptedPayloadArg := NewEncryptedPayload( - headerArg, - encryptedPassphrase, - encryptedContent, - ) - - b64Service := base64.NewBase64Service() - - headerSvc := header.NewHeaderService() - encPassphraseSvc := passphrase.NewEncryptedPassphraseService( - b64Service, - rsa.NewRsaService(&os.RealOsExecutor{}), - ) - - encContentSvc := content.NewLegacyEncryptedContentService( - b64Service, - aes.NewAesService( - pkcs7.NewPkcs7Service(), - ), - ) - - svc := NewEncryptedPayloadService( - headerSvc, - encPassphraseSvc, - encContentSvc, - ) - - encodedHeader, err := headerSvc.Serialize(headerArg) - require.Nil(t, err) - - encodedEncryptedPassphrase, err := encPassphraseSvc.Serialize(encryptedPassphrase) - require.Nil(t, err) - - encodedEncryptedContent, err := encContentSvc.Serialize(encryptedContent) - require.Nil(t, err) - - serializedReturn := fmt.Sprintf( - "%s%s%s%s%s", - encodedHeader, - EncryptionPayloadSeparator, - encodedEncryptedPassphrase, - EncryptionPayloadSeparator, - encodedEncryptedContent, - ) - - expectedReturn := []byte(serializedReturn) - - actualReturn, actualErr := svc.Serialize(encryptedPayloadArg) - require.Nil(t, actualErr) - - assert.Equal(t, expectedReturn, actualReturn) - }, - ) -} - -func TestEncryptedPayloadService_Deserialize(t *testing.T) { - t.Run( - "when 'encodedContent' is blank, it returns an error", - func(t *testing.T) { - t.Parallel() - - b64ServiceArg := base64.NewBase64Service() - - headerServiceArg := header.NewHeaderService() - encryptedPassphraseServiceArg := passphrase.NewEncryptedPassphraseService( - b64ServiceArg, - rsa.NewRsaService(&os.RealOsExecutor{}), - ) - encryptedContentServiceArg := content.NewLegacyEncryptedContentService( - b64ServiceArg, - aes.NewAesService( - pkcs7.NewPkcs7Service(), - ), - ) - - encryptedPayloadSvc := NewEncryptedPayloadService( - headerServiceArg, - encryptedPassphraseServiceArg, - encryptedContentServiceArg, - ) - - encodedContentArg := []byte("") - - actualReturn, actualErr := encryptedPayloadSvc.Deserialize(encodedContentArg) - require.Nil(t, actualReturn) - - assert.Equal(t, errInvalidPayloadParts, actualErr) - }, - ) - - t.Run( - "when 'encodedContent' contains only a header, it returns an error", - func(t *testing.T) { - t.Parallel() - - b64ServiceArg := base64.NewBase64Service() - - headerServiceArg := header.NewHeaderService() - encryptedPassphraseServiceArg := passphrase.NewEncryptedPassphraseService( - b64ServiceArg, - rsa.NewRsaService(&os.RealOsExecutor{}), - ) - encryptedContentServiceArg := content.NewLegacyEncryptedContentService( - b64ServiceArg, - aes.NewAesService( - pkcs7.NewPkcs7Service(), - ), - ) - - encryptedPayloadSvc := NewEncryptedPayloadService( - headerServiceArg, - encryptedPassphraseServiceArg, - encryptedContentServiceArg, - ) - - h := header.NewHeader() - encodedContentArg := []byte( - fmt.Sprintf("%s;%s", h.Name, h.Version), - ) - - actualReturn, actualErr := encryptedPayloadSvc.Deserialize(encodedContentArg) - require.Nil(t, actualReturn) - - assert.Equal(t, errInvalidPayloadParts, actualErr) - }, - ) - - t.Run( - "when 'encodedContent' contains only a header and encrypted passphrase, it returns an error", - func(t *testing.T) { - t.Parallel() - - b64ServiceArg := base64.NewBase64Service() - - headerServiceArg := header.NewHeaderService() - encryptedPassphraseService := passphrase.NewEncryptedPassphraseService( - b64ServiceArg, - rsa.NewRsaService(&os.RealOsExecutor{}), - ) - encryptedContentService := content.NewLegacyEncryptedContentService( - b64ServiceArg, - aes.NewAesService( - pkcs7.NewPkcs7Service(), - ), - ) - - encryptedPayloadSvc := NewEncryptedPayloadService( - headerServiceArg, - encryptedPassphraseService, - encryptedContentService, - ) - - serializedHeader, err := headerServiceArg.Serialize( - header.NewHeader(), - ) - require.Nil(t, err) - - encryptedPassphrase := passphrase.NewEncryptedPassphrase( - []byte("1a2b3c4d"), - ) - - serializedEncPassphrase, err := encryptedPassphraseService.Serialize( - encryptedPassphrase, - ) - require.Nil(t, err) - - encodedContent := fmt.Sprintf( - "%s%s%s", - serializedHeader, - EncryptionPayloadSeparator, - serializedEncPassphrase, - ) - encodedContentArg := []byte(encodedContent) - - actualReturn, actualErr := encryptedPayloadSvc.Deserialize(encodedContentArg) - require.Nil(t, actualReturn) - - assert.Equal(t, errInvalidPayloadParts, actualErr) - }, - ) - - t.Run( - "when 'encodedContent' contains a header, encrypted passphrase, encrypted content, but header is empty, it returns an error", - func(t *testing.T) { - t.Parallel() - - b64ServiceArg := base64.NewBase64Service() - - headerServiceArg := header.NewHeaderService() - encryptedPassphraseService := passphrase.NewEncryptedPassphraseService( - b64ServiceArg, - rsa.NewRsaService(&os.RealOsExecutor{}), - ) - encryptedContentService := content.NewLegacyEncryptedContentService( - b64ServiceArg, - aes.NewAesService( - pkcs7.NewPkcs7Service(), - ), - ) - - encryptedPayloadSvc := NewEncryptedPayloadService( - headerServiceArg, - encryptedPassphraseService, - encryptedContentService, - ) - - encryptedPassphrase := passphrase.NewEncryptedPassphrase( - []byte("1a2b3c4d"), - ) - - serializedEncPassphrase, err := encryptedPassphraseService.Serialize( - encryptedPassphrase, - ) - require.Nil(t, err) - - serializedEncContent, err := encryptedContentService.Serialize( - content.NewEncryptedContent( - []byte("1a2b3c"), - ), - ) - require.Nil(t, err) - - encodedContent := fmt.Sprintf( - "%s%s%s%s%s", - // NOTE: Empty header, - "", - EncryptionPayloadSeparator, - serializedEncPassphrase, - EncryptionPayloadSeparator, - serializedEncContent, - ) - encodedContentArg := []byte(encodedContent) - - actualReturn, actualErr := encryptedPayloadSvc.Deserialize(encodedContentArg) - require.Nil(t, actualReturn) - - assert.Equal(t, errEmptyHeader, actualErr) - }, - ) - - t.Run( - "when 'encodedContent' contains a header, encrypted passphrase, encrypted content, but header does not contain all header parts, it returns an error", - func(t *testing.T) { - t.Parallel() - - b64ServiceArg := base64.NewBase64Service() - - headerServiceArg := header.NewHeaderService() - encryptedPassphraseService := passphrase.NewEncryptedPassphraseService( - b64ServiceArg, - rsa.NewRsaService(&os.RealOsExecutor{}), - ) - encryptedContentService := content.NewLegacyEncryptedContentService( - b64ServiceArg, - aes.NewAesService( - pkcs7.NewPkcs7Service(), - ), - ) - - encryptedPayloadSvc := NewEncryptedPayloadService( - headerServiceArg, - encryptedPassphraseService, - encryptedContentService, - ) - - encryptedPassphrase := passphrase.NewEncryptedPassphrase( - []byte("1a2b3c4d"), - ) - - serializedEncPassphrase, err := encryptedPassphraseService.Serialize( - encryptedPassphrase, - ) - require.Nil(t, err) - - serializedEncContent, err := encryptedContentService.Serialize( - content.NewEncryptedContent( - []byte("1a2b3c"), - ), - ) - require.Nil(t, err) - - partiallySerializedHeader := header.NewHeader().Name - - encodedContent := fmt.Sprintf( - "%s%s%s%s%s", - partiallySerializedHeader, - EncryptionPayloadSeparator, - serializedEncPassphrase, - EncryptionPayloadSeparator, - serializedEncContent, - ) - encodedContentArg := []byte(encodedContent) - - actualReturn, actualErr := encryptedPayloadSvc.Deserialize(encodedContentArg) - require.Nil(t, actualReturn) - - assert.Contains(t, actualErr.Error(), "failed to parse header") - }, - ) - - t.Run( - "when 'encodedContent' contains a header, encrypted passphrase, encrypted content, but encrypted passphrase is blank, it returns an error", - func(t *testing.T) { - t.Parallel() - - b64ServiceArg := base64.NewBase64Service() - - headerServiceArg := header.NewHeaderService() - encryptedPassphraseService := passphrase.NewEncryptedPassphraseService( - b64ServiceArg, - rsa.NewRsaService(&os.RealOsExecutor{}), - ) - encryptedContentService := content.NewLegacyEncryptedContentService( - b64ServiceArg, - aes.NewAesService( - pkcs7.NewPkcs7Service(), - ), - ) - - encryptedPayloadSvc := NewEncryptedPayloadService( - headerServiceArg, - encryptedPassphraseService, - encryptedContentService, - ) - - serializedEncContent, err := encryptedContentService.Serialize( - content.NewEncryptedContent( - []byte("1a2b3c"), - ), - ) - require.Nil(t, err) - - serializedHeader, err := header.NewHeaderService().Serialize( - header.NewHeader(), - ) - require.Nil(t, err) - - encodedContent := fmt.Sprintf( - "%s%s%s%s%s", - serializedHeader, - EncryptionPayloadSeparator, - // NOTE: Empty encrypted passphrase - "", - EncryptionPayloadSeparator, - serializedEncContent, - ) - encodedContentArg := []byte(encodedContent) - - actualReturn, actualErr := encryptedPayloadSvc.Deserialize(encodedContentArg) - require.Nil(t, actualReturn) - - assert.Equal(t, actualErr, errEmptyEncryptedPassphrase) - }, - ) - - t.Run( - "when 'encodedContent' contains a header, encrypted passphrase, encrypted content, but encrypted passphrase fails base64 decoding, it returns an error", - func(t *testing.T) { - t.Parallel() - - b64ServiceArg := base64.NewBase64Service() - - headerServiceArg := header.NewHeaderService() - encryptedContentService := content.NewLegacyEncryptedContentService( - b64ServiceArg, - aes.NewAesService( - pkcs7.NewPkcs7Service(), - ), - ) - - serializedEncContent, err := encryptedContentService.Serialize( - content.NewEncryptedContent( - []byte("1a2b3c"), - ), - ) - require.Nil(t, err) - - serializedHeader, err := header.NewHeaderService().Serialize( - header.NewHeader(), - ) - require.Nil(t, err) - - encPassphrase := passphrase.NewEncryptedPassphrase( - []byte("1a2b3c4d"), - ) - - badSerializedEncryptedPassphrase := fmt.Sprintf("%s", encPassphrase.Ciphertext) - - encodedContent := fmt.Sprintf( - "%s%s%s%s%s", - serializedHeader, - EncryptionPayloadSeparator, - badSerializedEncryptedPassphrase, - EncryptionPayloadSeparator, - serializedEncContent, - ) - encodedContentArg := []byte(encodedContent) - - mockEncPassphraseSvc := &testPassphrase.MockEncryptedPassphraseService{} - - fakeError := errors.New("") - - mockEncPassphraseSvc.On( - "Deserialize", - []byte(badSerializedEncryptedPassphrase), - ).Return(nil, fakeError) - - encryptedPayloadSvc := NewEncryptedPayloadService( - headerServiceArg, - mockEncPassphraseSvc, - encryptedContentService, - ) - actualReturn, actualErr := encryptedPayloadSvc.Deserialize(encodedContentArg) - require.Nil(t, actualReturn) - - assert.Contains( - t, - actualErr.Error(), - "failed to deserialize base64 encoded encrypted passphrase", - ) - - mockEncPassphraseSvc.AssertExpectations(t) - }, - ) - - t.Run( - "when 'encodedContent' contains a header, encrypted passphrase, encrypted content, but encrypted content is blank, it returns an error", - func(t *testing.T) { - t.Parallel() - - b64ServiceArg := base64.NewBase64Service() - - headerServiceArg := header.NewHeaderService() - encryptedPassphraseService := passphrase.NewEncryptedPassphraseService( - b64ServiceArg, - rsa.NewRsaService(&os.RealOsExecutor{}), - ) - encryptedContentService := content.NewLegacyEncryptedContentService( - b64ServiceArg, - aes.NewAesService( - pkcs7.NewPkcs7Service(), - ), - ) - - encryptedPayloadSvc := NewEncryptedPayloadService( - headerServiceArg, - encryptedPassphraseService, - encryptedContentService, - ) - - serializedEncPassphrase, err := encryptedPassphraseService.Serialize( - passphrase.NewEncryptedPassphrase( - []byte("1a2b3c"), - ), - ) - require.Nil(t, err) - - serializedHeader, err := header.NewHeaderService().Serialize( - header.NewHeader(), - ) - require.Nil(t, err) - - encodedContent := fmt.Sprintf( - "%s%s%s%s%s", - serializedHeader, - EncryptionPayloadSeparator, - serializedEncPassphrase, - EncryptionPayloadSeparator, - // NOTE: Empty encrypted content - "", - ) - encodedContentArg := []byte(encodedContent) - - actualReturn, actualErr := encryptedPayloadSvc.Deserialize(encodedContentArg) - require.Nil(t, actualReturn) - - assert.Equal(t, actualErr, errEmptyEncryptedContent) - }, - ) - - t.Run( - "when 'encodedContent' contains a header, encrypted passphrase, encrypted content, but encrypted content base64 decoding fails, it returns an error", - func(t *testing.T) { - t.Parallel() - - b64ServiceArg := base64.NewBase64Service() - - headerServiceArg := header.NewHeaderService() - encryptedPassphraseService := passphrase.NewEncryptedPassphraseService( - b64ServiceArg, - rsa.NewRsaService(&os.RealOsExecutor{}), - ) - - serializedEncPassphrase, err := encryptedPassphraseService.Serialize( - passphrase.NewEncryptedPassphrase( - []byte("1a2b3c"), - ), - ) - require.Nil(t, err) - - serializedHeader, err := header.NewHeaderService().Serialize( - header.NewHeader(), - ) - require.Nil(t, err) - - badSerializedEncryptedContent := "a1234" - - encodedContent := fmt.Sprintf( - "%s%s%s%s%s", - serializedHeader, - EncryptionPayloadSeparator, - serializedEncPassphrase, - EncryptionPayloadSeparator, - // NOTE: Empty encrypted content - badSerializedEncryptedContent, - ) - encodedContentArg := []byte(encodedContent) - - fakeError := errors.New("deserializeEncryptedContentError") - mockEncContentSvc := &testContent.MockEncryptedContentService{} - mockEncContentSvc.On( - "Deserialize", - []byte(badSerializedEncryptedContent), - ).Return(nil, fakeError) - - encryptedPayloadSvc := NewEncryptedPayloadService( - headerServiceArg, - encryptedPassphraseService, - mockEncContentSvc, - ) - - actualReturn, actualErr := encryptedPayloadSvc.Deserialize(encodedContentArg) - require.Nil(t, actualReturn) - - assert.Contains( - t, - actualErr.Error(), - "failed to deserialize base64 encoded encrypted content", - ) - - mockEncContentSvc.AssertExpectations(t) - }, - ) - - t.Run( - "when 'encodedContent' contains a header, encrypted passphrase, encrypted content, it returns encrypted payload", - func(t *testing.T) { - t.Parallel() - - base64Svc := base64.NewBase64Service() - svc := NewEncryptedPayloadService( - header.NewHeaderService(), - passphrase.NewEncryptedPassphraseService( - base64Svc, - rsa.NewRsaService(&os.RealOsExecutor{}), - ), - content.NewLegacyEncryptedContentService( - base64Svc, - aes.NewAesService( - pkcs7.NewPkcs7Service(), - ), - ), - ) - - encryptedPayload := NewEncryptedPayload( - header.NewHeader(), - passphrase.NewEncryptedPassphrase( - []byte("1a2b3c4d"), - ), - content.NewEncryptedContent( - []byte("1a2b3c4d"), - ), - ) - - encodedArg, err := svc.Serialize(encryptedPayload) - require.Nil(t, err) - - actualReturn, actualErr := svc.Deserialize(encodedArg) - - require.Nil(t, actualErr) - - assert.Equal( - t, - encryptedPayload.Header.Name, - actualReturn.Header.Name, - ) - - assert.Equal( - t, - encryptedPayload.Header.Version, - actualReturn.Header.Version, - ) - - assert.Equal( - t, - encryptedPayload.Header.Version, - actualReturn.Header.Version, - ) - - assert.Equal( - t, - encryptedPayload.EncryptedPassphrase.Ciphertext, - actualReturn.EncryptedPassphrase.Ciphertext, - ) - - assert.Equal( - t, - encryptedPayload.EncryptedContent.Ciphertext, - actualReturn.EncryptedContent.Ciphertext, - ) - }, - ) -} - -func TestEncryptedPayloadService_Encrypt(t *testing.T) { - t.Run( - "when encryption of 'payload's 'encryptedPassphrase' fails, it returns an error", - func(t *testing.T) { - t.Parallel() - - privKey, err := stdRsa.GenerateKey(rand.Reader, 2048) - require.Nil(t, err) - - b64Service := base64.NewBase64Service() - encPassphraseSvc := passphrase.NewEncryptedPassphraseService( - b64Service, - rsa.NewRsaService( - ostest.NewFakeOsExecutor(t), - ), - ) - - fakeError := errors.New("encryptPassphraseError") - passphrase, err := encPassphraseSvc.GeneratePassphrase(16) - require.Nil(t, err) - - mockEncPassphraseSvc := &testPassphrase.MockEncryptedPassphraseService{} - mockEncPassphraseSvc.Test(t) - mockEncPassphraseSvc.On( - "Encrypt", - &privKey.PublicKey, - passphrase, - ).Return(nil, fakeError) - - pkcs7Service := pkcs7.NewPkcs7Service() - svc := NewEncryptedPayloadService( - header.NewHeaderService(), - mockEncPassphraseSvc, - content.NewLegacyEncryptedContentService( - b64Service, - aes.NewAesService(pkcs7Service), - ), - ) - - payloadArg := NewPayload( - header.NewHeader(), - passphrase, - content.NewContent( - []byte("samplecontent"), - ), - ) - - actualReturn, actualErr := svc.Encrypt(&privKey.PublicKey, payloadArg) - require.Nil(t, actualReturn) - - assert.Contains(t, actualErr.Error(), "failed to encrypt encrypted passphrase") - - mockEncPassphraseSvc.AssertExpectations(t) - }, - ) - - t.Run( - "when encryption of 'payload's 'encryptedContent' fails, it returns an error", - func(t *testing.T) { - t.Parallel() - - privKey, err := stdRsa.GenerateKey(rand.Reader, 2048) - require.Nil(t, err) - - b64Service := base64.NewBase64Service() - encPassphraseSvc := passphrase.NewEncryptedPassphraseService( - b64Service, - rsa.NewRsaService(&os.RealOsExecutor{}), - ) - passphrase, err := encPassphraseSvc.GeneratePassphrase(16) - require.Nil(t, err) - - content := content.NewContent( - []byte("samplecontent"), - ) - fakeError := errors.New("encryptContentError") - - mockEncContentSvc := &testContent.MockEncryptedContentService{} - mockEncContentSvc.Test(t) - mockEncContentSvc.On( - "Encrypt", - passphrase, - content, - ).Return(nil, fakeError) - - svc := NewEncryptedPayloadService( - header.NewHeaderService(), - encPassphraseSvc, - mockEncContentSvc, - ) - - payloadArg := NewPayload( - header.NewHeader(), - passphrase, - content, - ) - - actualReturn, actualErr := svc.Encrypt(&privKey.PublicKey, payloadArg) - require.Nil(t, actualReturn) - - assert.Contains(t, actualErr.Error(), "failed to encrypt encrypted content") - - mockEncContentSvc.AssertExpectations(t) - }, - ) - - t.Run( - "when 'payload' is encrypted, it returns encrypted payload", - func(t *testing.T) { - t.Parallel() - - b64Svc := base64.NewBase64Service() - encPassphraseSvc := passphrase.NewEncryptedPassphraseService( - b64Svc, - rsa.NewRsaService(&os.RealOsExecutor{}), - ) - passphrase, err := encPassphraseSvc.GeneratePassphrase(16) - require.Nil(t, err) - - contentArg := content.NewContent( - []byte("mycontent"), - ) - - h := header.NewHeader() - payload := NewPayload(h, passphrase, contentArg) - - privKey, err := stdRsa.GenerateKey(rand.Reader, 2048) - require.Nil(t, err) - - encContentSvc := content.NewLegacyEncryptedContentService( - base64.NewBase64Service(), - aes.NewAesService( - pkcs7.NewPkcs7Service(), - ), - ) - - encPayloadSvc := NewEncryptedPayloadService( - header.NewHeaderService(), - encPassphraseSvc, - encContentSvc, - ) - - actualReturn, actualErr := encPayloadSvc.Encrypt(&privKey.PublicKey, payload) - require.Nil(t, actualErr) - - assert.Equal(t, h.Name, actualReturn.Header.Name) - assert.Equal(t, h.Version, actualReturn.Header.Version) - assert.NotContains( - t, - actualReturn.EncryptedPassphrase.Ciphertext, - passphrase.Content, - ) - - assert.NotContains( - t, - actualReturn.EncryptedContent.Ciphertext, - contentArg.Plaintext, - ) - }, - ) -} - -func TestEncryptedPayloadService_Decrypt(t *testing.T) { - t.Run( - "when 'encryptedPayload's 'encryptedPassphrase' decryption fails, "+ - "it returns an error", - func(t *testing.T) { - t.Parallel() - - privKey, err := stdRsa.GenerateKey(rand.Reader, 2048) - require.Nil(t, err) - - b64Svc := base64.NewBase64Service() - encryptedPassphraseSvc := passphrase.NewEncryptedPassphraseService( - b64Svc, - rsa.NewRsaService(&os.RealOsExecutor{}), - ) - - wrongPrivKey, err := stdRsa.GenerateKey(rand.Reader, 2048) - require.Nil(t, err) - - passphrase := &passphrase.Passphrase{ - Content: []byte("mysecretpassphrase"), - } - - badEncryptedPassphrase, err := encryptedPassphraseSvc.Encrypt( - &wrongPrivKey.PublicKey, - passphrase, - ) - require.Nil(t, err) - - encryptedPayloadArg := NewEncryptedPayload( - header.NewHeader(), - badEncryptedPassphrase, - content.NewEncryptedContent( - []byte("a1b2c3"), - ), - ) - - svc := NewEncryptedPayloadService( - header.NewHeaderService(), - encryptedPassphraseSvc, - content.NewLegacyEncryptedContentService( - b64Svc, - aes.NewAesService( - pkcs7.NewPkcs7Service(), - ), - ), - ) - - actualReturn, actualErr := svc.Decrypt(privKey, encryptedPayloadArg) - require.Nil(t, actualReturn) - - assert.Contains(t, actualErr.Error(), "failed to decrypt encrypted passphrase") - }, - ) - - t.Run( - "when 'encryptedPayload's 'encryptedContent' decryption fails, "+ - "it returns an error", - func(t *testing.T) { - t.Parallel() - - privKey, err := stdRsa.GenerateKey(rand.Reader, 2048) - require.Nil(t, err) - - b64Svc := base64.NewBase64Service() - encryptedPassphraseSvc := passphrase.NewEncryptedPassphraseService( - b64Svc, - rsa.NewRsaService(&os.RealOsExecutor{}), - ) - - correctPassphrase, err := encryptedPassphraseSvc.GeneratePassphrase(16) - require.Nil(t, err) - - encryptedPassphrase, err := encryptedPassphraseSvc.Encrypt( - &privKey.PublicKey, - correctPassphrase, - ) - require.Nil(t, err) - - encryptedContentSvc := content.NewLegacyEncryptedContentService( - b64Svc, - aes.NewAesService( - pkcs7.NewPkcs7Service(), - ), - ) - - // NOTE: Use a bad (different) passphrase - // that is not the encrypted passphrase. - badPassphrase, err := encryptedPassphraseSvc.GeneratePassphrase(16) - require.Nil(t, err) - - content := content.NewContent([]byte("mycontent")) - badEncryptedContent, err := encryptedContentSvc.Encrypt( - badPassphrase, - content, - ) - require.Nil(t, err) - - encryptedPayloadArg := NewEncryptedPayload( - header.NewHeader(), - encryptedPassphrase, - badEncryptedContent, - ) - - svc := NewEncryptedPayloadService( - header.NewHeaderService(), - encryptedPassphraseSvc, - encryptedContentSvc, - ) - - actualReturn, actualErr := svc.Decrypt(privKey, encryptedPayloadArg) - require.Nil(t, actualReturn) - - assert.Contains( - t, - actualErr.Error(), - "failed to decrypt encrypted content", - ) - }, - ) - - t.Run( - "when 'encryptedPayload' is successfully decrypted, it returns payload", - func(t *testing.T) { - t.Parallel() - - privKey, err := stdRsa.GenerateKey(rand.Reader, 2048) - require.Nil(t, err) - - b64Svc := base64.NewBase64Service() - - encPassphraseSvc := passphrase.NewEncryptedPassphraseService( - b64Svc, - rsa.NewRsaService(&os.RealOsExecutor{}), - ) - - encContentSvc := content.NewLegacyEncryptedContentService( - b64Svc, - aes.NewAesService( - pkcs7.NewPkcs7Service(), - ), - ) - - h := header.NewHeader() - passphrase, err := encPassphraseSvc.GeneratePassphrase(16) - require.Nil(t, err) - - encryptedPassphrase, err := encPassphraseSvc.Encrypt(&privKey.PublicKey, passphrase) - require.Nil(t, err) - - content := content.NewContent( - []byte("mycontent!"), - ) - encryptedContent, err := encContentSvc.Encrypt(passphrase, content) - require.Nil(t, err) - - encryptedPayloadArg := NewEncryptedPayload( - h, - encryptedPassphrase, - encryptedContent, - ) - - svc := NewEncryptedPayloadService( - header.NewHeaderService(), - encPassphraseSvc, - encContentSvc, - ) - - actualReturn, actualErr := svc.Decrypt(privKey, encryptedPayloadArg) - require.Nil(t, actualErr) - - assert.Equal(t, h.Name, actualReturn.Header.Name) - assert.Equal(t, h.Version, actualReturn.Header.Version) - assert.Equal(t, passphrase.Content, actualReturn.Passphrase.Content) - assert.Equal(t, content.Plaintext, actualReturn.Content.Plaintext) - }, - ) -} diff --git a/pkg/vaulted/payload/encryption_service.go b/pkg/vaulted/payload/encryption_service.go new file mode 100644 index 0000000..5d252fd --- /dev/null +++ b/pkg/vaulted/payload/encryption_service.go @@ -0,0 +1,51 @@ +// Copyright 2018 SumUp Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package payload + +import ( + "github.com/palantir/stacktrace" +) + +type EncryptionService struct { + passphraseEncrypter passphraseEncrypter + contentEncrypter contentEncrypter +} + +func NewEncryptionService(passphraseEncrypter passphraseEncrypter, contentEncrypter contentEncrypter) *EncryptionService { + return &EncryptionService{ + passphraseEncrypter: passphraseEncrypter, + contentEncrypter: contentEncrypter, + } +} + +func (s *EncryptionService) Encrypt(payload *Payload) (*EncryptedPayload, error) { + encryptedContent, err := s.contentEncrypter.Encrypt(payload.Passphrase, payload.Content) + if err != nil { + return nil, stacktrace.Propagate(err, "failed to encrypt content using generated passphrase") + } + + encryptedPassphrase, err := s.passphraseEncrypter.Encrypt(payload.Passphrase) + if err != nil { + return nil, stacktrace.Propagate(err, "failed to encrypt passphrase") + } + + encryptedPayload := NewEncryptedPayload( + payload.Header, + encryptedPassphrase, + encryptedContent, + ) + + return encryptedPayload, nil +} diff --git a/pkg/vaulted/payload/external_interfaces.go b/pkg/vaulted/payload/external_interfaces.go index 006e470..7fd506f 100644 --- a/pkg/vaulted/payload/external_interfaces.go +++ b/pkg/vaulted/payload/external_interfaces.go @@ -15,40 +15,27 @@ package payload import ( - stdRsa "crypto/rsa" - "github.com/sumup-oss/vaulted/pkg/vaulted/content" - "github.com/sumup-oss/vaulted/pkg/vaulted/header" "github.com/sumup-oss/vaulted/pkg/vaulted/passphrase" ) -type headerService interface { - Serialize(header *header.Header) ([]byte, error) - Deserialize(content string) (*header.Header, error) +type contentEncrypter interface { + Encrypt(passphrase *passphrase.Passphrase, content *content.Content) (*content.EncryptedContent, error) +} + +type contentDecrypter interface { + Decrypt(passphrase *passphrase.Passphrase, encryptedContent *content.EncryptedContent) (*content.Content, error) +} + +type passphraseEncrypter interface { + Encrypt(passphrase *passphrase.Passphrase) (*passphrase.EncryptedPassphrase, error) } -type encryptedPassphraseService interface { - Serialize(encryptedPassphrase *passphrase.EncryptedPassphrase) ([]byte, error) - Deserialize(encoded []byte) (*passphrase.EncryptedPassphrase, error) - Encrypt( - publicKey *stdRsa.PublicKey, - passphrase *passphrase.Passphrase, - ) (*passphrase.EncryptedPassphrase, error) - Decrypt( - privateKey *stdRsa.PrivateKey, - encryptedPassphrase *passphrase.EncryptedPassphrase, - ) (*passphrase.Passphrase, error) +type passphraseDecrypter interface { + Decrypt(encryptedPassphrase *passphrase.EncryptedPassphrase) (*passphrase.Passphrase, error) } -type encryptedContentService interface { - Serialize(encryptedContent *content.EncryptedContent) ([]byte, error) - Deserialize(encoded []byte) (*content.EncryptedContent, error) - Encrypt( - passphrase *passphrase.Passphrase, - content *content.Content, - ) (*content.EncryptedContent, error) - Decrypt( - passphrase *passphrase.Passphrase, - encryptedContent *content.EncryptedContent, - ) (*content.Content, error) +type base64Serde interface { + Serialize(raw []byte) ([]byte, error) + Deserialize(encoded []byte) ([]byte, error) } diff --git a/pkg/vaulted/payload/payload_test.go b/pkg/vaulted/payload/payload_test.go index 108fe80..5b47945 100644 --- a/pkg/vaulted/payload/payload_test.go +++ b/pkg/vaulted/payload/payload_test.go @@ -19,9 +19,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/sumup-oss/go-pkgs/os/ostest" - "github.com/sumup-oss/vaulted/pkg/base64" - "github.com/sumup-oss/vaulted/pkg/rsa" "github.com/sumup-oss/vaulted/pkg/vaulted/content" "github.com/sumup-oss/vaulted/pkg/vaulted/header" "github.com/sumup-oss/vaulted/pkg/vaulted/passphrase" @@ -33,14 +30,7 @@ func TestNewPayload(t *testing.T) { func(t *testing.T) { headerArg := header.NewHeader() - encPassphraseSvc := passphrase.NewEncryptedPassphraseService( - base64.NewBase64Service(), - rsa.NewRsaService( - ostest.NewFakeOsExecutor(t), - ), - ) - - passphraseArg, err := encPassphraseSvc.GeneratePassphrase(16) + passphraseArg, err := passphrase.NewService().GeneratePassphrase(16) contentArg := content.NewContent([]byte("12345678")) actual := NewPayload(headerArg, passphraseArg, contentArg) diff --git a/pkg/vaulted/payload/serde_service.go b/pkg/vaulted/payload/serde_service.go new file mode 100644 index 0000000..9eb61ce --- /dev/null +++ b/pkg/vaulted/payload/serde_service.go @@ -0,0 +1,227 @@ +// Copyright 2018 SumUp Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package payload + +import ( + "errors" + "fmt" + "strings" + + "github.com/palantir/stacktrace" + + "github.com/sumup-oss/vaulted/pkg/vaulted" + "github.com/sumup-oss/vaulted/pkg/vaulted/content" + "github.com/sumup-oss/vaulted/pkg/vaulted/header" + "github.com/sumup-oss/vaulted/pkg/vaulted/passphrase" +) + +const ( + HeaderPartSeparator = ";" + EncryptionPayloadSeparator = "::" +) + +var ( + HeaderAllowedNames = []string{header.DefaultName} + headerAllowedVersions = []string{header.DefaultVersion} + + ErrInvalidPayloadParts = errors.New( + "invalid encryption payload. it must be in format of " + + "`
;;;;`", + ) + ErrEmptyHeader = errors.New("invalid header. empty") + ErrEmptyEncryptedPassphrase = errors.New("invalid encrypted passphrase. empty") + ErrEmptyEncryptedContent = errors.New("invalid encrypted payload. empty") + ErrHeadersPartsMismatch = errors.New("did not find exactly 2 header parts") + ErrHeaderNameInvalid = fmt.Errorf( + "did not find name equal to any of allowed header names: %#v", + HeaderAllowedNames, + ) + ErrHeaderVersionInvalid = fmt.Errorf( + "did not find version equal to any of allowed header versions: %#v", + headerAllowedVersions, + ) + ErrSerializeBlankHeaderName = errors.New( + "failed to serialize blank header name", + ) + ErrSerializeBlankHeaderVersion = errors.New( + "failed to serialize blank header version", + ) +) + +type SerdeService struct { + b64Serde base64Serde +} + +func NewSerdeService(b64Serde base64Serde) *SerdeService { + return &SerdeService{ + b64Serde: b64Serde, + } +} + +func (s *SerdeService) serializeHeader(header *header.Header) ([]byte, error) { + if len(header.Name) < 1 { + return nil, ErrSerializeBlankHeaderName + } + + if len(header.Version) < 1 { + return nil, ErrSerializeBlankHeaderVersion + } + + headerParts := []string{ + header.Name, + header.Version, + } + + serialized := strings.Join(headerParts, HeaderPartSeparator) + + return []byte(serialized), nil +} + +func (s *SerdeService) deserializeHeader(serialized string) (*header.Header, error) { + headerParts := strings.Split(serialized, HeaderPartSeparator) + + if len(headerParts) != 2 { + return nil, ErrHeadersPartsMismatch + } + + if !vaulted.Contains(HeaderAllowedNames, headerParts[0]) { + return nil, ErrHeaderNameInvalid + } + + if !vaulted.Contains(headerAllowedVersions, headerParts[1]) { + return nil, ErrHeaderVersionInvalid + } + + header := &header.Header{ + Name: headerParts[0], + Version: headerParts[1], + } + + return header, nil +} + +func (s *SerdeService) deserializeEncryptedPassphrase(serialized string) (*passphrase.EncryptedPassphrase, error) { + deserialized, err := s.b64Serde.Deserialize( + []byte(serialized), + ) + if err != nil { + return nil, stacktrace.Propagate( + err, + "failed to deserialize base64 encoded encrypted passphrase", + ) + } + + return passphrase.NewEncryptedPassphrase(deserialized), nil +} + +func (s *SerdeService) deserializeEncryptedContent(serialized string) (*content.EncryptedContent, error) { + deserialized, err := s.b64Serde.Deserialize( + []byte(serialized), + ) + if err != nil { + return nil, stacktrace.Propagate( + err, + "failed to deserialize base64 encoded encrypted content", + ) + } + + return content.NewEncryptedContent(deserialized), nil +} + +func (s *SerdeService) Serialize(encryptedPayload *EncryptedPayload) ([]byte, error) { + serializedHeader, err := s.serializeHeader(encryptedPayload.Header) + if err != nil { + return nil, stacktrace.Propagate(err, "failed to serialize encrypted payload's header") + } + + payloadParts := []string{ + string(serializedHeader), + } + + serializedEncryptedPassphrase, err := s.b64Serde.Serialize(encryptedPayload.EncryptedPassphrase.Ciphertext) + if err != nil { + return nil, stacktrace.Propagate( + err, + "failed to serialize encrypted payload's encrypted passphrase", + ) + } + + payloadParts = append( + payloadParts, + string(serializedEncryptedPassphrase), + ) + + serializedEncryptedContent, err := s.b64Serde.Serialize(encryptedPayload.EncryptedContent.Ciphertext) + if err != nil { + return nil, stacktrace.Propagate( + err, + "failed to serialize encrypted payload's encrypted content", + ) + } + + payloadParts = append( + payloadParts, + string(serializedEncryptedContent), + ) + + serialized := strings.Join(payloadParts, EncryptionPayloadSeparator) + + return []byte(serialized), nil +} + +func (s *SerdeService) Deserialize(encodedContent []byte) (*EncryptedPayload, error) { + payloadParts := strings.Split( + string(encodedContent), + EncryptionPayloadSeparator, + ) + + if len(payloadParts) != 3 { + return nil, ErrInvalidPayloadParts + } + + if len(payloadParts[0]) < 1 { + return nil, ErrEmptyHeader + } + + parsedHeader, err := s.deserializeHeader(payloadParts[0]) + if err != nil { + return nil, stacktrace.Propagate(err, "failed to deserialize header") + } + + if len(payloadParts[1]) < 1 { + return nil, ErrEmptyEncryptedPassphrase + } + + encryptedPassphrase, err := s.deserializeEncryptedPassphrase(payloadParts[1]) + if err != nil { + return nil, stacktrace.Propagate(err, "failed to deserialize encrypted passphrase") + } + + if len(payloadParts[2]) < 1 { + return nil, ErrEmptyEncryptedContent + } + + encryptedContent, err := s.deserializeEncryptedContent(payloadParts[2]) + if err != nil { + return nil, stacktrace.Propagate( + err, + "failed to deserialize encrypted content", + ) + } + + encryptedPayload := NewEncryptedPayload(parsedHeader, encryptedPassphrase, encryptedContent) + + return encryptedPayload, nil +}