diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index dab42ba87c..c16e8cf9d3 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -223,6 +223,7 @@ func Provider() terraform.ResourceProvider { "aws_load_balancer_policy": resourceAwsLoadBalancerPolicy(), "aws_load_balancer_backend_server_policy": resourceAwsLoadBalancerBackendServerPolicies(), "aws_load_balancer_listener_policy": resourceAwsLoadBalancerListenerPolicies(), + "aws_lb_ssl_negotiation_policy": resourceAwsLBSSLNegotiationPolicy(), "aws_main_route_table_association": resourceAwsMainRouteTableAssociation(), "aws_nat_gateway": resourceAwsNatGateway(), "aws_network_acl": resourceAwsNetworkAcl(), diff --git a/builtin/providers/aws/resource_aws_lb_ssl_negotiation_policy.go b/builtin/providers/aws/resource_aws_lb_ssl_negotiation_policy.go new file mode 100644 index 0000000000..5d0ae78506 --- /dev/null +++ b/builtin/providers/aws/resource_aws_lb_ssl_negotiation_policy.go @@ -0,0 +1,185 @@ +package aws + +import ( + "bytes" + "fmt" + "log" + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/elb" + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsLBSSLNegotiationPolicy() *schema.Resource { + return &schema.Resource{ + // There is no concept of "updating" an LB policy in + // the AWS API. + Create: resourceAwsLBSSLNegotiationPolicyCreate, + Read: resourceAwsLBSSLNegotiationPolicyRead, + Delete: resourceAwsLBSSLNegotiationPolicyDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "load_balancer": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "lb_port": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + + "attribute": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "value": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + }, + }, + Set: func(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + buf.WriteString(fmt.Sprintf("%s-", m["name"].(string))) + return hashcode.String(buf.String()) + }, + }, + }, + } +} + +func resourceAwsLBSSLNegotiationPolicyCreate(d *schema.ResourceData, meta interface{}) error { + elbconn := meta.(*AWSClient).elbconn + + // Provision the SSLNegotiationPolicy + lbspOpts := &elb.CreateLoadBalancerPolicyInput{ + LoadBalancerName: aws.String(d.Get("load_balancer").(string)), + PolicyName: aws.String(d.Get("name").(string)), + PolicyTypeName: aws.String("SSLNegotiationPolicyType"), + } + + // Check for Policy Attributes + if v, ok := d.GetOk("attribute"); ok { + var err error + // Expand the "attribute" set to aws-sdk-go compat []*elb.PolicyAttribute + lbspOpts.PolicyAttributes, err = expandPolicyAttributes(v.(*schema.Set).List()) + if err != nil { + return err + } + } + + log.Printf("[DEBUG] Load Balancer Policy opts: %#v", lbspOpts) + if _, err := elbconn.CreateLoadBalancerPolicy(lbspOpts); err != nil { + return fmt.Errorf("Error creating Load Balancer Policy: %s", err) + } + + setLoadBalancerOpts := &elb.SetLoadBalancerPoliciesOfListenerInput{ + LoadBalancerName: aws.String(d.Get("load_balancer").(string)), + LoadBalancerPort: aws.Int64(int64(d.Get("lb_port").(int))), + PolicyNames: []*string{aws.String(d.Get("name").(string))}, + } + + log.Printf("[DEBUG] SSL Negotiation create configuration: %#v", setLoadBalancerOpts) + if _, err := elbconn.SetLoadBalancerPoliciesOfListener(setLoadBalancerOpts); err != nil { + return fmt.Errorf("Error setting SSLNegotiationPolicy: %s", err) + } + + d.SetId(fmt.Sprintf("%s:%d:%s", + *lbspOpts.LoadBalancerName, + *setLoadBalancerOpts.LoadBalancerPort, + *lbspOpts.PolicyName)) + return nil +} + +func resourceAwsLBSSLNegotiationPolicyRead(d *schema.ResourceData, meta interface{}) error { + elbconn := meta.(*AWSClient).elbconn + + lbName, lbPort, policyName := resourceAwsLBSSLNegotiationPolicyParseId(d.Id()) + + request := &elb.DescribeLoadBalancerPoliciesInput{ + LoadBalancerName: aws.String(lbName), + PolicyNames: []*string{aws.String(policyName)}, + } + + getResp, err := elbconn.DescribeLoadBalancerPolicies(request) + if err != nil { + if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "PolicyNotFound" { + // The policy is gone. + d.SetId("") + return nil + } + return fmt.Errorf("Error retrieving policy: %s", err) + } + + if len(getResp.PolicyDescriptions) != 1 { + return fmt.Errorf("Unable to find policy %#v", getResp.PolicyDescriptions) + } + + // We can get away with this because there's only one policy returned + policyDesc := getResp.PolicyDescriptions[0] + attributes := flattenPolicyAttributes(policyDesc.PolicyAttributeDescriptions) + d.Set("attributes", attributes) + + d.Set("name", policyName) + d.Set("load_balancer", lbName) + d.Set("lb_port", lbPort) + + return nil +} + +func resourceAwsLBSSLNegotiationPolicyDelete(d *schema.ResourceData, meta interface{}) error { + elbconn := meta.(*AWSClient).elbconn + + lbName, _, policyName := resourceAwsLBSSLNegotiationPolicyParseId(d.Id()) + + // Perversely, if we Set an empty list of PolicyNames, we detach the + // policies attached to a listener, which is required to delete the + // policy itself. + setLoadBalancerOpts := &elb.SetLoadBalancerPoliciesOfListenerInput{ + LoadBalancerName: aws.String(d.Get("load_balancer").(string)), + LoadBalancerPort: aws.Int64(int64(d.Get("lb_port").(int))), + PolicyNames: []*string{}, + } + + if _, err := elbconn.SetLoadBalancerPoliciesOfListener(setLoadBalancerOpts); err != nil { + return fmt.Errorf("Error removing SSLNegotiationPolicy: %s", err) + } + + request := &elb.DeleteLoadBalancerPolicyInput{ + LoadBalancerName: aws.String(lbName), + PolicyName: aws.String(policyName), + } + + if _, err := elbconn.DeleteLoadBalancerPolicy(request); err != nil { + return fmt.Errorf("Error deleting SSL negotiation policy %s: %s", d.Id(), err) + } + return nil +} + +// resourceAwsLBSSLNegotiationPolicyParseId takes an ID and parses it into +// it's constituent parts. You need three axes (LB name, policy name, and LB +// port) to create or identify an SSL negotiation policy in AWS's API. +func resourceAwsLBSSLNegotiationPolicyParseId(id string) (string, string, string) { + parts := strings.SplitN(id, ":", 3) + return parts[0], parts[1], parts[2] +} diff --git a/builtin/providers/aws/resource_aws_lb_ssl_negotiation_policy_test.go b/builtin/providers/aws/resource_aws_lb_ssl_negotiation_policy_test.go new file mode 100644 index 0000000000..8df23afe07 --- /dev/null +++ b/builtin/providers/aws/resource_aws_lb_ssl_negotiation_policy_test.go @@ -0,0 +1,263 @@ +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/elb" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSLBSSLNegotiationPolicy_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLBSSLNegotiationPolicyDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccSslNegotiationPolicyConfig( + fmt.Sprintf("tf-acctest-%s", acctest.RandString(10))), + Check: resource.ComposeTestCheckFunc( + testAccCheckLBSSLNegotiationPolicy( + "aws_elb.lb", + "aws_lb_ssl_negotiation_policy.foo", + ), + resource.TestCheckResourceAttr( + "aws_lb_ssl_negotiation_policy.foo", "attribute.#", "7"), + ), + }, + }, + }) +} + +func testAccCheckLBSSLNegotiationPolicyDestroy(s *terraform.State) error { + elbconn := testAccProvider.Meta().(*AWSClient).elbconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_elb" && rs.Type != "aws_lb_ssl_negotiation_policy" { + continue + } + + // Check that the ELB is destroyed + if rs.Type == "aws_elb" { + describe, err := elbconn.DescribeLoadBalancers(&elb.DescribeLoadBalancersInput{ + LoadBalancerNames: []*string{aws.String(rs.Primary.ID)}, + }) + + if err == nil { + if len(describe.LoadBalancerDescriptions) != 0 && + *describe.LoadBalancerDescriptions[0].LoadBalancerName == rs.Primary.ID { + return fmt.Errorf("ELB still exists") + } + } + + // Verify the error + providerErr, ok := err.(awserr.Error) + if !ok { + return err + } + + if providerErr.Code() != "LoadBalancerNotFound" { + return fmt.Errorf("Unexpected error: %s", err) + } + } else { + // Check that the SSL Negotiation Policy is destroyed + elbName, _, policyName := resourceAwsLBSSLNegotiationPolicyParseId(rs.Primary.ID) + _, err := elbconn.DescribeLoadBalancerPolicies(&elb.DescribeLoadBalancerPoliciesInput{ + LoadBalancerName: aws.String(elbName), + PolicyNames: []*string{aws.String(policyName)}, + }) + + if err == nil { + return fmt.Errorf("ELB SSL Negotiation Policy still exists") + } + } + } + + return nil +} + +func testAccCheckLBSSLNegotiationPolicy(elbResource string, policyResource string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[elbResource] + if !ok { + return fmt.Errorf("Not found: %s", elbResource) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + policy, ok := s.RootModule().Resources[policyResource] + if !ok { + return fmt.Errorf("Not found: %s", policyResource) + } + + elbconn := testAccProvider.Meta().(*AWSClient).elbconn + + elbName, _, policyName := resourceAwsLBSSLNegotiationPolicyParseId(policy.Primary.ID) + resp, err := elbconn.DescribeLoadBalancerPolicies(&elb.DescribeLoadBalancerPoliciesInput{ + LoadBalancerName: aws.String(elbName), + PolicyNames: []*string{aws.String(policyName)}, + }) + + if err != nil { + fmt.Printf("[ERROR] Problem describing load balancer policy '%s': %s", policyName, err) + return err + } + + if len(resp.PolicyDescriptions) != 1 { + return fmt.Errorf("Unable to find policy %#v", resp.PolicyDescriptions) + } + + attrmap := policyAttributesToMap(&resp.PolicyDescriptions[0].PolicyAttributeDescriptions) + if attrmap["Protocol-TLSv1"] != "false" { + return fmt.Errorf("Policy attribute 'Protocol-TLSv1' was of value %s instead of false!", attrmap["Protocol-TLSv1"]) + } + if attrmap["Protocol-TLSv1.1"] != "false" { + return fmt.Errorf("Policy attribute 'Protocol-TLSv1.1' was of value %s instead of false!", attrmap["Protocol-TLSv1.1"]) + } + if attrmap["Protocol-TLSv1.2"] != "true" { + return fmt.Errorf("Policy attribute 'Protocol-TLSv1.2' was of value %s instead of true!", attrmap["Protocol-TLSv1.2"]) + } + if attrmap["Server-Defined-Cipher-Order"] != "true" { + return fmt.Errorf("Policy attribute 'Server-Defined-Cipher-Order' was of value %s instead of true!", attrmap["Server-Defined-Cipher-Order"]) + } + if attrmap["ECDHE-RSA-AES128-GCM-SHA256"] != "true" { + return fmt.Errorf("Policy attribute 'ECDHE-RSA-AES128-GCM-SHA256' was of value %s instead of true!", attrmap["ECDHE-RSA-AES128-GCM-SHA256"]) + } + if attrmap["AES128-GCM-SHA256"] != "true" { + return fmt.Errorf("Policy attribute 'AES128-GCM-SHA256' was of value %s instead of true!", attrmap["AES128-GCM-SHA256"]) + } + if attrmap["EDH-RSA-DES-CBC3-SHA"] != "false" { + return fmt.Errorf("Policy attribute 'EDH-RSA-DES-CBC3-SHA' was of value %s instead of false!", attrmap["EDH-RSA-DES-CBC3-SHA"]) + } + + return nil + } +} + +func policyAttributesToMap(attributes *[]*elb.PolicyAttributeDescription) map[string]string { + attrmap := make(map[string]string) + + for _, attrdef := range *attributes { + attrmap[*attrdef.AttributeName] = *attrdef.AttributeValue + } + + return attrmap +} + +// Sets the SSL Negotiation policy with attributes. +// The IAM Server Cert config is lifted from +// builtin/providers/aws/resource_aws_iam_server_certificate_test.go +func testAccSslNegotiationPolicyConfig(certName string) string { + return fmt.Sprintf(` +resource "aws_iam_server_certificate" "test_cert" { + name = "%s" + certificate_body = <aws_load_balancer_policy + > + aws_lb_ssl_negotiation_policy + + > aws_placement_group