From 1e8e83fa6a4a489626897dc6c39356fdf5326309 Mon Sep 17 00:00:00 2001 From: clint shryock Date: Wed, 30 Nov 2016 15:38:47 -0600 Subject: [PATCH] provider/aws: Encrypt aws_iam_access_key.secret with pgp optionally encrypt the iam access key secret with a pgp key --- .../aws/resource_aws_iam_access_key.go | 46 ++++- .../aws/resource_aws_iam_access_key_test.go | 166 +++++++++++++++++- .../aws/r/iam_access_key.html.markdown | 29 ++- 3 files changed, 218 insertions(+), 23 deletions(-) diff --git a/builtin/providers/aws/resource_aws_iam_access_key.go b/builtin/providers/aws/resource_aws_iam_access_key.go index ef74fe88b..3aaf64ee9 100644 --- a/builtin/providers/aws/resource_aws_iam_access_key.go +++ b/builtin/providers/aws/resource_aws_iam_access_key.go @@ -10,6 +10,7 @@ import ( "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/iam" + "github.com/hashicorp/terraform/helper/encryption" "github.com/hashicorp/terraform/helper/schema" ) @@ -26,19 +27,31 @@ func resourceAwsIamAccessKey() *schema.Resource { ForceNew: true, }, "status": &schema.Schema{ - Type: schema.TypeString, - // this could be settable, but goamz does not support the - // UpdateAccessKey API yet. - Computed: true, - }, - "secret": &schema.Schema{ Type: schema.TypeString, Computed: true, }, + "secret": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + Deprecated: "Please use a PGP key to encrypt", + }, "ses_smtp_password": &schema.Schema{ Type: schema.TypeString, Computed: true, }, + "pgp_key": { + Type: schema.TypeString, + ForceNew: true, + Optional: true, + }, + "key_fingerprint": { + Type: schema.TypeString, + Computed: true, + }, + "encrypted_secret": { + Type: schema.TypeString, + Computed: true, + }, }, } } @@ -59,8 +72,25 @@ func resourceAwsIamAccessKeyCreate(d *schema.ResourceData, meta interface{}) err ) } - if err := d.Set("secret", createResp.AccessKey.SecretAccessKey); err != nil { - return err + d.SetId(*createResp.AccessKey.AccessKeyId) + + if createResp.AccessKey == nil || createResp.AccessKey.SecretAccessKey == nil { + return fmt.Errorf("[ERR] CreateAccessKey response did not contain a Secret Access Key as expected") + } + + if v, ok := d.GetOk("pgp_key"); ok { + pgpKey := v.(string) + encryptionKey, err := encryption.RetrieveGPGKey(pgpKey) + if err != nil { + return err + } + fingerprint, encrypted, err := encryption.EncryptValue(encryptionKey, *createResp.AccessKey.SecretAccessKey, "IAM Access Key Secret") + if err != nil { + return err + } + + d.Set("key_fingerprint", fingerprint) + d.Set("encrypted_secret", encrypted) } d.Set("ses_smtp_password", diff --git a/builtin/providers/aws/resource_aws_iam_access_key_test.go b/builtin/providers/aws/resource_aws_iam_access_key_test.go index 3a1df33ae..9c8ff2bfa 100644 --- a/builtin/providers/aws/resource_aws_iam_access_key_test.go +++ b/builtin/providers/aws/resource_aws_iam_access_key_test.go @@ -1,18 +1,23 @@ package aws import ( + "errors" "fmt" + "strings" "testing" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/iam" + "github.com/hashicorp/terraform/helper/acctest" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/vault/helper/pgpkeys" ) func TestAccAWSAccessKey_basic(t *testing.T) { var conf iam.AccessKeyMetadata + rName := fmt.Sprintf("test-user-%d", acctest.RandInt()) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -20,7 +25,7 @@ func TestAccAWSAccessKey_basic(t *testing.T) { CheckDestroy: testAccCheckAWSAccessKeyDestroy, Steps: []resource.TestStep{ resource.TestStep{ - Config: testAccAWSAccessKeyConfig, + Config: testAccAWSAccessKeyConfig(rName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSAccessKeyExists("aws_iam_access_key.a_key", &conf), testAccCheckAWSAccessKeyAttributes(&conf), @@ -30,6 +35,33 @@ func TestAccAWSAccessKey_basic(t *testing.T) { }) } +func TestAccAWSAccessKey_encrypted(t *testing.T) { + var conf iam.AccessKeyMetadata + rName := fmt.Sprintf("test-user-%d", acctest.RandInt()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAccessKeyDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSAccessKeyConfig_encrypted(rName, testPubAccessKey1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAccessKeyExists("aws_iam_access_key.a_key", &conf), + testAccCheckAWSAccessKeyAttributes(&conf), + testDecryptSecretKeyAndTest("aws_iam_access_key.a_key", testPrivKey1), + resource.TestCheckResourceAttr( + "aws_iam_access_key.a_key", "secret", ""), + resource.TestCheckResourceAttrSet( + "aws_iam_access_key.a_key", "encrypted_secret"), + resource.TestCheckResourceAttrSet( + "aws_iam_access_key.a_key", "key_fingerprint"), + ), + }, + }, + }) +} + func testAccCheckAWSAccessKeyDestroy(s *terraform.State) error { iamconn := testAccProvider.Meta().(*AWSClient).iamconn @@ -74,16 +106,17 @@ func testAccCheckAWSAccessKeyExists(n string, res *iam.AccessKeyMetadata) resour } iamconn := testAccProvider.Meta().(*AWSClient).iamconn + name := rs.Primary.Attributes["user"] resp, err := iamconn.ListAccessKeys(&iam.ListAccessKeysInput{ - UserName: aws.String("testuser"), + UserName: aws.String(name), }) if err != nil { return err } if len(resp.AccessKeyMetadata) != 1 || - *resp.AccessKeyMetadata[0].UserName != "testuser" { + *resp.AccessKeyMetadata[0].UserName != name { return fmt.Errorf("User not found not found") } @@ -95,7 +128,7 @@ func testAccCheckAWSAccessKeyExists(n string, res *iam.AccessKeyMetadata) resour func testAccCheckAWSAccessKeyAttributes(accessKeyMetadata *iam.AccessKeyMetadata) resource.TestCheckFunc { return func(s *terraform.State) error { - if *accessKeyMetadata.UserName != "testuser" { + if !strings.Contains(*accessKeyMetadata.UserName, "test-user") { return fmt.Errorf("Bad username: %s", *accessKeyMetadata.UserName) } @@ -107,15 +140,55 @@ func testAccCheckAWSAccessKeyAttributes(accessKeyMetadata *iam.AccessKeyMetadata } } -const testAccAWSAccessKeyConfig = ` +func testDecryptSecretKeyAndTest(nAccessKey, key string) resource.TestCheckFunc { + return func(s *terraform.State) error { + keyResource, ok := s.RootModule().Resources[nAccessKey] + if !ok { + return fmt.Errorf("Not found: %s", nAccessKey) + } + + password, ok := keyResource.Primary.Attributes["encrypted_secret"] + if !ok { + return errors.New("No password in state") + } + + // We can't verify that the decrypted password is correct, because we don't + // have it. We can verify that decrypting it does not error + _, err := pgpkeys.DecryptBytes(password, key) + if err != nil { + return fmt.Errorf("Error decrypting password: %s", err) + } + + return nil + } +} + +func testAccAWSAccessKeyConfig(rName string) string { + return fmt.Sprintf(` resource "aws_iam_user" "a_user" { - name = "testuser" + name = "%s" } resource "aws_iam_access_key" "a_key" { - user = "${aws_iam_user.a_user.name}" + user = "${aws_iam_user.a_user.name}" +} +`, rName) +} + +func testAccAWSAccessKeyConfig_encrypted(rName, key string) string { + return fmt.Sprintf(` +resource "aws_iam_user" "a_user" { + name = "%s" +} + +resource "aws_iam_access_key" "a_key" { + user = "${aws_iam_user.a_user.name}" + pgp_key = < **NOTE:** The encrypted secret may be decrypted using the command line, + for example: `terraform output secret | base64 --decode | keybase pgp decrypt`. * `ses_smtp_password` - The secret access key converted into an SES SMTP password by applying [AWS's documented conversion algorithm](https://docs.aws.amazon.com/ses/latest/DeveloperGuide/smtp-credentials.html#smtp-credentials-convert).