terraform/builtin/providers/aws/resource_aws_iam_server_cer...

231 lines
6.2 KiB
Go

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,
Schema: map[string]*schema.Schema{
"certificate_body": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
StateFunc: normalizeCert,
},
"certificate_chain": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
StateFunc: normalizeCert,
},
"path": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "/",
ForceNew: true,
},
"private_key": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
StateFunc: normalizeCert,
},
"name": &schema.Schema{
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": &schema.Schema{
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": &schema.Schema{
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)
}
// 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(3*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 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]
}