diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index aa1dc6566..53b750be3 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -179,6 +179,7 @@ func Provider() terraform.ResourceProvider { "aws_iam_saml_provider": resourceAwsIamSamlProvider(), "aws_iam_server_certificate": resourceAwsIAMServerCertificate(), "aws_iam_user_policy": resourceAwsIamUserPolicy(), + "aws_iam_user_ssh_key": resourceAwsIamUserSshKey(), "aws_iam_user": resourceAwsIamUser(), "aws_instance": resourceAwsInstance(), "aws_internet_gateway": resourceAwsInternetGateway(), diff --git a/builtin/providers/aws/resource_aws_iam_user_ssh_key.go b/builtin/providers/aws/resource_aws_iam_user_ssh_key.go new file mode 100644 index 000000000..646eea6aa --- /dev/null +++ b/builtin/providers/aws/resource_aws_iam_user_ssh_key.go @@ -0,0 +1,153 @@ +package aws + +import ( + "fmt" + "log" + + "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/schema" +) + +func resourceAwsIamUserSshKey() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsIamUserSshKeyCreate, + Read: resourceAwsIamUserSshKeyRead, + Update: resourceAwsIamUserSshKeyUpdate, + Delete: resourceAwsIamUserSshKeyDelete, + + Schema: map[string]*schema.Schema{ + "ssh_public_key_id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "fingerprint": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "username": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "public_key": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "encoding": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ValidateFunc: validateIamUserSSHKeyEncoding, + }, + + "status": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + } +} + +func resourceAwsIamUserSshKeyCreate(d *schema.ResourceData, meta interface{}) error { + iamconn := meta.(*AWSClient).iamconn + username := d.Get("username").(string) + publicKey := d.Get("public_key").(string) + + request := &iam.UploadSSHPublicKeyInput{ + UserName: aws.String(username), + SSHPublicKeyBody: aws.String(publicKey), + } + + log.Println("[DEBUG] Create IAM User SSH Key Request:", request) + createResp, err := iamconn.UploadSSHPublicKey(request) + if err != nil { + return fmt.Errorf("Error creating IAM User SSH Key %s: %s", username, err) + } + + d.Set("ssh_public_key_id", createResp.SSHPublicKey.SSHPublicKeyId) + d.SetId(*createResp.SSHPublicKey.SSHPublicKeyId) + + return resourceAwsIamUserSshKeyRead(d, meta) +} + +func resourceAwsIamUserSshKeyRead(d *schema.ResourceData, meta interface{}) error { + iamconn := meta.(*AWSClient).iamconn + username := d.Get("username").(string) + request := &iam.GetSSHPublicKeyInput{ + UserName: aws.String(username), + SSHPublicKeyId: aws.String(d.Id()), + Encoding: aws.String(d.Get("encoding").(string)), + } + + getResp, err := iamconn.GetSSHPublicKey(request) + if err != nil { + if iamerr, ok := err.(awserr.Error); ok && iamerr.Code() == "NoSuchEntity" { // XXX test me + log.Printf("[WARN] No IAM user ssh key (%s) found", d.Id()) + d.SetId("") + return nil + } + return fmt.Errorf("Error reading IAM User SSH Key %s: %s", d.Id(), err) + } + + d.Set("fingerprint", getResp.SSHPublicKey.Fingerprint) + d.Set("status", getResp.SSHPublicKey.Status) + + return nil +} + +func resourceAwsIamUserSshKeyUpdate(d *schema.ResourceData, meta interface{}) error { + if d.HasChange("status") { + iamconn := meta.(*AWSClient).iamconn + + request := &iam.UpdateSSHPublicKeyInput{ + UserName: aws.String(d.Get("username").(string)), + SSHPublicKeyId: aws.String(d.Id()), + Status: aws.String(d.Get("status").(string)), + } + + log.Println("[DEBUG] Update IAM User SSH Key request:", request) + _, err := iamconn.UpdateSSHPublicKey(request) + if err != nil { + if iamerr, ok := err.(awserr.Error); ok && iamerr.Code() == "NoSuchEntity" { + log.Printf("[WARN] No IAM user ssh key by ID (%s) found", d.Id()) + d.SetId("") + return nil + } + return fmt.Errorf("Error updating IAM User SSH Key %s: %s", d.Id(), err) + } + return resourceAwsIamUserRead(d, meta) + } + return nil +} + +func resourceAwsIamUserSshKeyDelete(d *schema.ResourceData, meta interface{}) error { + iamconn := meta.(*AWSClient).iamconn + + request := &iam.DeleteSSHPublicKeyInput{ + UserName: aws.String(d.Get("username").(string)), + SSHPublicKeyId: aws.String(d.Id()), + } + + log.Println("[DEBUG] Delete IAM User SSH Key request:", request) + if _, err := iamconn.DeleteSSHPublicKey(request); err != nil { + return fmt.Errorf("Error deleting IAM User SSH Key %s: %s", d.Id(), err) + } + return nil +} + +func validateIamUserSSHKeyEncoding(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + encodingTypes := map[string]bool{ + "PEM": true, + "SSH": true, + } + + if !encodingTypes[value] { + errors = append(errors, fmt.Errorf("IAM User SSH Key Encoding can only be PEM or SSH")) + } + return +} diff --git a/builtin/providers/aws/resource_aws_iam_user_ssh_key_test.go b/builtin/providers/aws/resource_aws_iam_user_ssh_key_test.go new file mode 100644 index 000000000..d26638f03 --- /dev/null +++ b/builtin/providers/aws/resource_aws_iam_user_ssh_key_test.go @@ -0,0 +1,143 @@ +package aws + +import ( + "fmt" + "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" +) + +func TestAccAWSUserSSHKey_basic(t *testing.T) { + var conf iam.GetSSHPublicKeyOutput + + ri := acctest.RandInt() + config := fmt.Sprintf(testAccAWSSSHKeyConfig_sshEncoding, ri) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSUserSSHKeyDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: config, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSUserSSHKeyExists("aws_iam_user_ssh_key.user", &conf), + ), + }, + }, + }) +} + +func TestAccAWSUserSSHKey_pemEncoding(t *testing.T) { + var conf iam.GetSSHPublicKeyOutput + + ri := acctest.RandInt() + config := fmt.Sprintf(testAccAWSSSHKeyConfig_pemEncoding, ri) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSUserSSHKeyDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: config, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSUserSSHKeyExists("aws_iam_user_ssh_key.user", &conf), + ), + }, + }, + }) +} + +func testAccCheckAWSUserSSHKeyDestroy(s *terraform.State) error { + iamconn := testAccProvider.Meta().(*AWSClient).iamconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_iam_user_ssh_key" { + continue + } + + username := rs.Primary.Attributes["username"] + encoding := rs.Primary.Attributes["encoding"] + _, err := iamconn.GetSSHPublicKey(&iam.GetSSHPublicKeyInput{ + SSHPublicKeyId: aws.String(rs.Primary.ID), + UserName: aws.String(username), + Encoding: aws.String(encoding), + }) + if err == nil { + return fmt.Errorf("still exist.") + } + + // Verify the error is what we want + ec2err, ok := err.(awserr.Error) + if !ok { + return err + } + if ec2err.Code() != "NoSuchEntity" { + return err + } + } + + return nil +} + +func testAccCheckAWSUserSSHKeyExists(n string, res *iam.GetSSHPublicKeyOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No SSHPublicKeyID is set") + } + + iamconn := testAccProvider.Meta().(*AWSClient).iamconn + + username := rs.Primary.Attributes["username"] + encoding := rs.Primary.Attributes["encoding"] + resp, err := iamconn.GetSSHPublicKey(&iam.GetSSHPublicKeyInput{ + SSHPublicKeyId: aws.String(rs.Primary.ID), + UserName: aws.String(username), + Encoding: aws.String(encoding), + }) + if err != nil { + return err + } + + *res = *resp + + return nil + } +} + +const testAccAWSSSHKeyConfig_sshEncoding = ` +resource "aws_iam_user" "user" { + name = "test-user-%d" + path = "/" +} + +resource "aws_iam_user_ssh_key" "user" { + username = "${aws_iam_user.user.name}" + encoding = "SSH" + public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD3F6tyPEFEzV0LX3X8BsXdMsQz1x2cEikKDEY0aIj41qgxMCP/iteneqXSIFZBp5vizPvaoIR3Um9xK7PGoW8giupGn+EPuxIA4cDM4vzOqOkiMPhz5XK0whEjkVzTo4+S0puvDZuwIsdiW9mxhJc7tgBNL0cYlWSYVkz4G/fslNfRPW5mYAM49f4fhtxPb5ok4Q2Lg9dPKVHO/Bgeu5woMc7RY0p1ej6D4CKFE6lymSDJpW0YHX/wqE9+cfEauh7xZcG0q9t2ta6F6fmX0agvpFyZo8aFbXeUBr7osSCJNgvavWbM/06niWrOvYX2xwWdhXmXSrbX8ZbabVohBK41 phodgson@thoughtworks.com" +} +` + +const testAccAWSSSHKeyConfig_pemEncoding = ` +resource "aws_iam_user" "user" { + name = "test-user-%d" + path = "/" +} + +resource "aws_iam_user_ssh_key" "user" { + username = "${aws_iam_user.user.name}" + encoding = "PEM" + public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD3F6tyPEFEzV0LX3X8BsXdMsQz1x2cEikKDEY0aIj41qgxMCP/iteneqXSIFZBp5vizPvaoIR3Um9xK7PGoW8giupGn+EPuxIA4cDM4vzOqOkiMPhz5XK0whEjkVzTo4+S0puvDZuwIsdiW9mxhJc7tgBNL0cYlWSYVkz4G/fslNfRPW5mYAM49f4fhtxPb5ok4Q2Lg9dPKVHO/Bgeu5woMc7RY0p1ej6D4CKFE6lymSDJpW0YHX/wqE9+cfEauh7xZcG0q9t2ta6F6fmX0agvpFyZo8aFbXeUBr7osSCJNgvavWbM/06niWrOvYX2xwWdhXmXSrbX8ZbabVohBK41 phodgson@thoughtworks.com" +} +`