package aws import ( "crypto/sha1" "encoding/hex" "fmt" "log" "strings" "time" "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/resource" "github.com/hashicorp/terraform/helper/schema" ) func resourceAwsIAMServerCertificate() *schema.Resource { return &schema.Resource{ Create: resourceAwsIAMServerCertificateCreate, Read: resourceAwsIAMServerCertificateRead, Delete: resourceAwsIAMServerCertificateDelete, Importer: &schema.ResourceImporter{ State: resourceAwsIAMServerCertificateImport, }, Schema: map[string]*schema.Schema{ "certificate_body": { Type: schema.TypeString, Required: true, ForceNew: true, StateFunc: normalizeCert, }, "certificate_chain": { Type: schema.TypeString, Optional: true, ForceNew: true, StateFunc: normalizeCert, }, "path": { Type: schema.TypeString, Optional: true, Default: "/", ForceNew: true, }, "private_key": { Type: schema.TypeString, Required: true, ForceNew: true, StateFunc: normalizeCert, Sensitive: true, }, "name": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, ConflictsWith: []string{"name_prefix"}, ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { value := v.(string) if len(value) > 128 { errors = append(errors, fmt.Errorf( "%q cannot be longer than 128 characters", k)) } return }, }, "name_prefix": { Type: schema.TypeString, Optional: true, ForceNew: true, ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { value := v.(string) if len(value) > 30 { errors = append(errors, fmt.Errorf( "%q cannot be longer than 30 characters, name is limited to 128", k)) } return }, }, "arn": { Type: schema.TypeString, Optional: true, Computed: true, }, }, } } func resourceAwsIAMServerCertificateCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).iamconn var sslCertName string if v, ok := d.GetOk("name"); ok { sslCertName = v.(string) } else if v, ok := d.GetOk("name_prefix"); ok { sslCertName = resource.PrefixedUniqueId(v.(string)) } else { sslCertName = resource.UniqueId() } createOpts := &iam.UploadServerCertificateInput{ CertificateBody: aws.String(d.Get("certificate_body").(string)), PrivateKey: aws.String(d.Get("private_key").(string)), ServerCertificateName: aws.String(sslCertName), } if v, ok := d.GetOk("certificate_chain"); ok { createOpts.CertificateChain = aws.String(v.(string)) } if v, ok := d.GetOk("path"); ok { createOpts.Path = aws.String(v.(string)) } log.Printf("[DEBUG] Creating IAM Server Certificate with opts: %s", createOpts) resp, err := conn.UploadServerCertificate(createOpts) if err != nil { if awsErr, ok := err.(awserr.Error); ok { return fmt.Errorf("[WARN] Error uploading server certificate, error: %s: %s", awsErr.Code(), awsErr.Message()) } return fmt.Errorf("[WARN] Error uploading server certificate, error: %s", err) } d.SetId(*resp.ServerCertificateMetadata.ServerCertificateId) d.Set("name", sslCertName) return resourceAwsIAMServerCertificateRead(d, meta) } func resourceAwsIAMServerCertificateRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).iamconn resp, err := conn.GetServerCertificate(&iam.GetServerCertificateInput{ ServerCertificateName: aws.String(d.Get("name").(string)), }) if err != nil { if awsErr, ok := err.(awserr.Error); ok { if awsErr.Code() == "NoSuchEntity" { log.Printf("[WARN] IAM Server Cert (%s) not found, removing from state", d.Id()) d.SetId("") return nil } return fmt.Errorf("[WARN] Error reading IAM Server Certificate: %s: %s", awsErr.Code(), awsErr.Message()) } return fmt.Errorf("[WARN] Error reading IAM Server Certificate: %s", err) } d.SetId(*resp.ServerCertificate.ServerCertificateMetadata.ServerCertificateId) // these values should always be present, and have a default if not set in // configuration, and so safe to reference with nil checks d.Set("certificate_body", normalizeCert(resp.ServerCertificate.CertificateBody)) c := normalizeCert(resp.ServerCertificate.CertificateChain) if c != "" { d.Set("certificate_chain", c) } d.Set("path", resp.ServerCertificate.ServerCertificateMetadata.Path) d.Set("arn", resp.ServerCertificate.ServerCertificateMetadata.Arn) return nil } func resourceAwsIAMServerCertificateDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).iamconn log.Printf("[INFO] Deleting IAM Server Certificate: %s", d.Id()) err := resource.Retry(5*time.Minute, func() *resource.RetryError { _, err := conn.DeleteServerCertificate(&iam.DeleteServerCertificateInput{ ServerCertificateName: aws.String(d.Get("name").(string)), }) if err != nil { if awsErr, ok := err.(awserr.Error); ok { if awsErr.Code() == "DeleteConflict" && strings.Contains(awsErr.Message(), "currently in use by arn") { log.Printf("[WARN] Conflict deleting server certificate: %s, retrying", awsErr.Message()) return resource.RetryableError(err) } if awsErr.Code() == "NoSuchEntity" { log.Printf("[WARN] IAM Server Certificate (%s) not found, removing from state", d.Id()) d.SetId("") return nil } } return resource.NonRetryableError(err) } return nil }) if err != nil { return err } d.SetId("") return nil } func resourceAwsIAMServerCertificateImport( d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { d.Set("name", d.Id()) // private_key can't be fetched from any API call return []*schema.ResourceData{d}, nil } func normalizeCert(cert interface{}) string { if cert == nil || cert == (*string)(nil) { return "" } var rawCert string switch cert.(type) { case string: rawCert = cert.(string) case *string: rawCert = *cert.(*string) default: return "" } cleanVal := sha1.Sum(stripCR([]byte(strings.TrimSpace(rawCert)))) return hex.EncodeToString(cleanVal[:]) } // strip CRs from raw literals. Lifted from go/scanner/scanner.go // See https://github.com/golang/go/blob/release-branch.go1.6/src/go/scanner/scanner.go#L479 func stripCR(b []byte) []byte { c := make([]byte, len(b)) i := 0 for _, ch := range b { if ch != '\r' { c[i] = ch i++ } } return c[:i] }