diff --git a/docs/resources/tls_mutual_authentication.md b/docs/resources/tls_mutual_authentication.md index 41c63e0f3..220d98c0e 100644 --- a/docs/resources/tls_mutual_authentication.md +++ b/docs/resources/tls_mutual_authentication.md @@ -14,9 +14,9 @@ The main Mutual Authentication object represents the certificate bundle and othe Mutual TLS can be added to existing TLS activations to allow for client-to-server authentication. In order to use mutual TLS, you must already have active server-side TLS using either custom certificates or an enabled Fastly-managed subscription. -The example below demonstrates how to use Mutual Authentication along with a TLS Subscription. Refer to the `fastly_tls_subscription` resource documentation for a deeper explanation of that code. +The examples below demonstrate how to use Mutual Authentication along with a TLS Subscription. Refer to the `fastly_tls_subscription` resource documentation for a deeper explanation of that code. -## Example Usage +## Example: Single Activation The following example sets up a TLS Subscription for `www.example.com` and then adds Mutual Authentication. @@ -74,7 +74,7 @@ resource "dnsimple_zone_record" "www_acme_challenge" { ttl = "60" type = "CNAME" value = one([for obj in fastly_tls_subscription.www.managed_dns_challenges : obj.record_value if obj.record_name == "_acme-challenge.www.${var.zone}"]) - zone_name = local.zone + zone_name = var.zone } resource "fastly_tls_subscription_validation" "www" { @@ -92,7 +92,7 @@ resource "dnsimple_zone_record" "www" { ttl = "60" type = "CNAME" value = one([for record in data.fastly_tls_configuration.default.dns_records : record.record_value if record.record_type == "CNAME"]) - zone_name = local.zone + zone_name = var.zone } data "fastly_tls_activation" "www" { @@ -101,9 +101,115 @@ data "fastly_tls_activation" "www" { } resource "fastly_tls_mutual_authentication" "www" { - activation_id = data.fastly_tls_activation.www.id - cert_bundle = "-----BEGIN CERTIFICATE-----\n\n-----END CERTIFICATE-----" - enforced = true + activation_ids = [data.fastly_tls_activation.www.id] + cert_bundle = "-----BEGIN CERTIFICATE-----\n\n-----END CERTIFICATE-----" + enforced = true +} +``` + +## Example: Multiple Activations + +The following example sets up a TLS Subscription for `foo.example.com` and `bar.example.com` and then adds Mutual Authentication to each TLS Activation. + +```terraform +terraform { + required_providers { + dnsimple = { + source = "dnsimple/dnsimple" + version = "1.5.0" + } + fastly = { + source = "fastly/fastly" + version = "5.7.2" + } + } +} + +variable "dnsimple_token" { + type = string +} + +variable "dnsimple_account" { + type = string +} + +provider "dnsimple" { + account = var.dnsimple_account + token = var.dnsimple_token +} + +variable "zone" { + type = string + default = "example.com" +} + +resource "fastly_service_vcl" "example" { + name = "example" + domain { + name = "foo.${var.zone}" + } + domain { + name = "bar.${var.zone}" + } + backend { + address = "httpbin.org" + name = "httpbin" + } + force_destroy = true +} + +resource "fastly_tls_subscription" "example" { + domains = [for domain in fastly_service_vcl.example.domain : domain.name] + certificate_authority = "lets-encrypt" +} + +resource "dnsimple_zone_record" "example_acme_challenge" { + for_each = { + for domain in fastly_tls_subscription.example.domains : domain => one([ + for obj in fastly_tls_subscription.example.managed_dns_challenges : obj if obj.record_name == "_acme-challenge.${domain}" + ]) + } + name = each.value.record_name + ttl = "60" + type = each.value.record_type + value = each.value.record_value + zone_name = var.zone +} + +resource "fastly_tls_subscription_validation" "example" { + subscription_id = fastly_tls_subscription.example.id + depends_on = [dnsimple_zone_record.example_acme_challenge] +} + +data "fastly_tls_configuration" "default" { + default = true + depends_on = [fastly_tls_subscription_validation.example] +} + +resource "dnsimple_zone_record" "foo" { + name = "foo" + ttl = "60" + type = "CNAME" + value = one([for record in data.fastly_tls_configuration.default.dns_records : record.record_value if record.record_type == "CNAME"]) + zone_name = var.zone +} + +resource "dnsimple_zone_record" "bar" { + name = "bar" + ttl = "60" + type = "CNAME" + value = one([for record in data.fastly_tls_configuration.default.dns_records : record.record_value if record.record_type == "CNAME"]) + zone_name = var.zone +} + +data "fastly_tls_activation_ids" "example" { + certificate_id = fastly_tls_subscription.example.certificate_id +} + +resource "fastly_tls_mutual_authentication" "fastly_dev" { + activation_ids = data.fastly_tls_activation_ids.example.ids + cert_bundle = "-----BEGIN CERTIFICATE-----\n\n-----END CERTIFICATE-----" + enforced = true } ``` @@ -116,7 +222,7 @@ resource "fastly_tls_mutual_authentication" "www" { ### Optional -- `activation_id` (String) The ID of your TLS Activation object +- `activation_ids` (Set of String) List of TLS Activation IDs - `enforced` (Boolean) Determines whether Mutual TLS will fail closed (enforced) or fail open. A true value will require a successful Mutual TLS handshake for the connection to continue and will fail closed if unsuccessful. A false value will fail open and allow the connection to proceed (if this attribute is not set we default to `false`). - `include` (String) A comma-separated list used by the Terraform provider during a state refresh to return more data related to your mutual authentication from the Fastly API (permitted values: `tls_activations`). - `name` (String) A custom name for your mutual authentication. If name is not supplied we will auto-generate one. diff --git a/examples/resources/tls_mutual_authentication_basic_usage.tf b/examples/resources/tls_mutual_authentication_basic_usage.tf index e94fe09a3..5cd874dcb 100644 --- a/examples/resources/tls_mutual_authentication_basic_usage.tf +++ b/examples/resources/tls_mutual_authentication_basic_usage.tf @@ -51,7 +51,7 @@ resource "dnsimple_zone_record" "www_acme_challenge" { ttl = "60" type = "CNAME" value = one([for obj in fastly_tls_subscription.www.managed_dns_challenges : obj.record_value if obj.record_name == "_acme-challenge.www.${var.zone}"]) - zone_name = local.zone + zone_name = var.zone } resource "fastly_tls_subscription_validation" "www" { @@ -69,7 +69,7 @@ resource "dnsimple_zone_record" "www" { ttl = "60" type = "CNAME" value = one([for record in data.fastly_tls_configuration.default.dns_records : record.record_value if record.record_type == "CNAME"]) - zone_name = local.zone + zone_name = var.zone } data "fastly_tls_activation" "www" { @@ -78,7 +78,7 @@ data "fastly_tls_activation" "www" { } resource "fastly_tls_mutual_authentication" "www" { - activation_id = data.fastly_tls_activation.www.id - cert_bundle = "-----BEGIN CERTIFICATE-----\n\n-----END CERTIFICATE-----" - enforced = true + activation_ids = [data.fastly_tls_activation.www.id] + cert_bundle = "-----BEGIN CERTIFICATE-----\n\n-----END CERTIFICATE-----" + enforced = true } diff --git a/examples/resources/tls_mutual_authentication_multiple_activations.tf b/examples/resources/tls_mutual_authentication_multiple_activations.tf new file mode 100644 index 000000000..1cd921925 --- /dev/null +++ b/examples/resources/tls_mutual_authentication_multiple_activations.tf @@ -0,0 +1,99 @@ +terraform { + required_providers { + dnsimple = { + source = "dnsimple/dnsimple" + version = "1.5.0" + } + fastly = { + source = "fastly/fastly" + version = "5.7.2" + } + } +} + +variable "dnsimple_token" { + type = string +} + +variable "dnsimple_account" { + type = string +} + +provider "dnsimple" { + account = var.dnsimple_account + token = var.dnsimple_token +} + +variable "zone" { + type = string + default = "example.com" +} + +resource "fastly_service_vcl" "example" { + name = "example" + domain { + name = "foo.${var.zone}" + } + domain { + name = "bar.${var.zone}" + } + backend { + address = "httpbin.org" + name = "httpbin" + } + force_destroy = true +} + +resource "fastly_tls_subscription" "example" { + domains = [for domain in fastly_service_vcl.example.domain : domain.name] + certificate_authority = "lets-encrypt" +} + +resource "dnsimple_zone_record" "example_acme_challenge" { + for_each = { + for domain in fastly_tls_subscription.example.domains : domain => one([ + for obj in fastly_tls_subscription.example.managed_dns_challenges : obj if obj.record_name == "_acme-challenge.${domain}" + ]) + } + name = each.value.record_name + ttl = "60" + type = each.value.record_type + value = each.value.record_value + zone_name = var.zone +} + +resource "fastly_tls_subscription_validation" "example" { + subscription_id = fastly_tls_subscription.example.id + depends_on = [dnsimple_zone_record.example_acme_challenge] +} + +data "fastly_tls_configuration" "default" { + default = true + depends_on = [fastly_tls_subscription_validation.example] +} + +resource "dnsimple_zone_record" "foo" { + name = "foo" + ttl = "60" + type = "CNAME" + value = one([for record in data.fastly_tls_configuration.default.dns_records : record.record_value if record.record_type == "CNAME"]) + zone_name = var.zone +} + +resource "dnsimple_zone_record" "bar" { + name = "bar" + ttl = "60" + type = "CNAME" + value = one([for record in data.fastly_tls_configuration.default.dns_records : record.record_value if record.record_type == "CNAME"]) + zone_name = var.zone +} + +data "fastly_tls_activation_ids" "example" { + certificate_id = fastly_tls_subscription.example.certificate_id +} + +resource "fastly_tls_mutual_authentication" "example" { + activation_ids = data.fastly_tls_activation_ids.example.ids + cert_bundle = "-----BEGIN CERTIFICATE-----\n\n-----END CERTIFICATE-----" + enforced = true +} diff --git a/fastly/resource_fastly_tls_mutual_authentication.go b/fastly/resource_fastly_tls_mutual_authentication.go index 36278bb3b..d9bc85905 100644 --- a/fastly/resource_fastly_tls_mutual_authentication.go +++ b/fastly/resource_fastly_tls_mutual_authentication.go @@ -22,10 +22,13 @@ func resourceFastlyTLSMutualAuthentication() *schema.Resource { StateContext: schema.ImportStatePassthroughContext, }, Schema: map[string]*schema.Schema{ - "activation_id": { - Type: schema.TypeString, - Description: "The ID of your TLS Activation object", + "activation_ids": { + Type: schema.TypeSet, + Description: "List of TLS Activation IDs", Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, }, "cert_bundle": { Type: schema.TypeString, @@ -101,15 +104,19 @@ func resourceFastlyTLSMutualAuthenticationCreate(ctx context.Context, d *schema. d.SetId(mTLS.ID) - if v, ok := d.GetOk("activation_id"); ok { - inputUpdate := &gofastly.UpdateTLSActivationInput{ - ID: v.(string), - MutualAuthentication: &gofastly.TLSMutualAuthentication{ID: mTLS.ID}, - } - log.Printf("[DEBUG] CREATE: Update TLS Activation input: %#v", inputUpdate) - _, err = conn.UpdateTLSActivation(inputUpdate) - if err != nil { - return diag.FromErr(err) + if v, ok := d.GetOk("activation_ids"); ok { + activationIDs := v.(*schema.Set).List() + for _, id := range activationIDs { + inputUpdate := &gofastly.UpdateTLSActivationInput{ + ID: id.(string), + MutualAuthentication: &gofastly.TLSMutualAuthentication{ID: mTLS.ID}, + } + log.Printf("[DEBUG] CREATE: Update TLS Activation input: %#v", inputUpdate) + _, err = conn.UpdateTLSActivation(inputUpdate) + if err != nil { + return diag.FromErr(err) + } + } } @@ -202,15 +209,35 @@ func resourceFastlyTLSMutualAuthenticationUpdate(_ context.Context, d *schema.Re return diag.FromErr(err) } - if d.HasChange("activation_id") { - inputUpdate := &gofastly.UpdateTLSActivationInput{ - ID: d.Get("activation_id").(string), - MutualAuthentication: &gofastly.TLSMutualAuthentication{ID: d.Id()}, + if v, ok := d.GetOk("activation_ids"); ok { + activationIDs := v.(*schema.Set).List() + for _, id := range activationIDs { + inputUpdate := &gofastly.UpdateTLSActivationInput{ + ID: id.(string), + MutualAuthentication: &gofastly.TLSMutualAuthentication{ID: d.Id()}, + } + log.Printf("[DEBUG] CREATE: Update TLS Activation input: %#v", inputUpdate) + _, err = conn.UpdateTLSActivation(inputUpdate) + if err != nil { + return diag.FromErr(err) + } + } - log.Printf("[DEBUG] UPDATE: TLS Activation input: %#v", inputUpdate) - _, err = conn.UpdateTLSActivation(inputUpdate) - if err != nil { - return diag.FromErr(err) + } + + if d.HasChange("activation_ids") { + activationIDs := d.Get("activation_ids").(*schema.Set).List() + for _, id := range activationIDs { + inputUpdate := &gofastly.UpdateTLSActivationInput{ + ID: id.(string), + MutualAuthentication: &gofastly.TLSMutualAuthentication{ID: d.Id()}, + } + log.Printf("[DEBUG] UPDATE: Update TLS Activation input: %#v", inputUpdate) + _, err = conn.UpdateTLSActivation(inputUpdate) + if err != nil { + return diag.FromErr(err) + } + } } @@ -222,10 +249,11 @@ func resourceFastlyTLSMutualAuthenticationDelete(_ context.Context, d *schema.Re // IMPORTANT: You can't delete mTLS with active domains. // You must first disable the active domains. - // To do that, you can set "" for the mTLS ID. - if d.Get("activation_id").(string) != "" { + // To do that, you can set "" for the mTLS ID on each TLS Activation. + activationIDs := d.Get("activation_ids").(*schema.Set).List() + for _, id := range activationIDs { input := &gofastly.UpdateTLSActivationInput{ - ID: d.Get("activation_id").(string), + ID: id.(string), MutualAuthentication: &gofastly.TLSMutualAuthentication{ID: ""}, } log.Printf("[DEBUG] DELETE: TLS Activation input: %#v", input) diff --git a/fastly/resource_fastly_tls_mutual_authentication_test.go b/fastly/resource_fastly_tls_mutual_authentication_test.go index 4415ad889..581549b23 100644 --- a/fastly/resource_fastly_tls_mutual_authentication_test.go +++ b/fastly/resource_fastly_tls_mutual_authentication_test.go @@ -103,7 +103,7 @@ resource "fastly_tls_activation" "test" { } resource "fastly_tls_mutual_authentication" "test" { - activation_id = fastly_tls_activation.test.id + activation_ids = [fastly_tls_activation.test.id] cert_bundle = <