From da97405e31c48e470c5f30633f78cc6e052924c8 Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Tue, 12 May 2020 13:35:13 +0200 Subject: [PATCH 1/5] add support for service directory zones to dns module --- modules/dns/README.md | 9 ++++++--- modules/dns/main.tf | 27 +++++++++++++++++---------- modules/dns/variables.tf | 13 ++++++++----- 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/modules/dns/README.md b/modules/dns/README.md index 6993e5695..207572c95 100644 --- a/modules/dns/README.md +++ b/modules/dns/README.md @@ -1,6 +1,8 @@ # Google Cloud DNS Module -This module allows simple management of Google Cloud DNS zones and records. It supports creating public, private, forwarding, and peering zones. For DNSSEC configuration, refer to the [`dns_managed_zone` documentation](https://www.terraform.io/docs/providers/google/r/dns_managed_zone.html#dnssec_config). +This module allows simple management of Google Cloud DNS zones and records. It supports creating public, private, forwarding, peering and service directory based zones. + +For DNSSEC configuration, refer to the [`dns_managed_zone` documentation](https://www.terraform.io/docs/providers/google/r/dns_managed_zone.html#dnssec_config). ## Example @@ -32,9 +34,10 @@ module "private-dns" { | *description* | Domain description. | string | | Terraform managed. | | *dnssec_config* | DNSSEC configuration: kind, non_existence, state. | any | | {} | | *forwarders* | List of target name servers, only valid for 'forwarding' zone types. | list(string) | | [] | -| *peer_network* | Peering network self link, only valid for 'peering' zone types. | string | | | +| *peer_network* | Peering network self link, only valid for 'peering' zone types. | string | | null | | *recordsets* | List of DNS record objects to manage. | list(object({...})) | | [] | -| *type* | Type of zone to create, valid values are 'public', 'private', 'forwarding', 'peering'. | string | | private | +| *service_directory_namespace* | Service directory namespace id (URL), only valid for 'service-directory' zone types. | string | | null | +| *type* | Type of zone to create, valid values are 'public', 'private', 'forwarding', 'peering', 'service-directory'. | string | | private | ## Outputs diff --git a/modules/dns/main.tf b/modules/dns/main.tf index 6e098b8d8..abb0beb2d 100644 --- a/modules/dns/main.tf +++ b/modules/dns/main.tf @@ -38,14 +38,11 @@ resource "google_dns_managed_zone" "non-public" { dynamic forwarding_config { for_each = ( - var.type == "forwarding" && var.forwarders != null - ? { config = var.forwarders } - : {} + var.type == "forwarding" && var.forwarders != null ? [""] : [] ) - iterator = config content { dynamic "target_name_servers" { - for_each = config.value + for_each = var.forwarders iterator = address content { ipv4_address = address.value @@ -56,14 +53,11 @@ resource "google_dns_managed_zone" "non-public" { dynamic peering_config { for_each = ( - var.type == "peering" && var.peer_network != null - ? { config = var.peer_network } - : {} + var.type == "peering" && var.peer_network != null ? [""] : [] ) - iterator = config content { target_network { - network_url = config.value + network_url = var.peer_network } } } @@ -78,6 +72,19 @@ resource "google_dns_managed_zone" "non-public" { } } + dynamic service_directory_config { + for_each = ( + var.type == "service-directory" && var.service_directory_namespace != null + ? [""] + : [] + ) + content { + namespace { + namespace_url = var.service_directory_namespace + } + } + } + } resource "google_dns_managed_zone" "public" { diff --git a/modules/dns/variables.tf b/modules/dns/variables.tf index 0991038c0..f38fb36a2 100644 --- a/modules/dns/variables.tf +++ b/modules/dns/variables.tf @@ -30,9 +30,6 @@ variable "description" { default = "Terraform managed." } -# TODO(ludoo): add link to DNSSEC documentation in README -# https://www.terraform.io/docs/providers/google/r/dns_managed_zone.html#dnssec_config - variable "default_key_specs_key" { description = "DNSSEC default key signing specifications: algorithm, key_length, key_type, kind." type = any @@ -71,7 +68,7 @@ variable "name" { variable "peer_network" { description = "Peering network self link, only valid for 'peering' zone types." type = string - default = "" + default = null } variable "project_id" { @@ -90,8 +87,14 @@ variable "recordsets" { default = [] } +variable "service_directory_namespace" { + description = "Service directory namespace id (URL), only valid for 'service-directory' zone types." + type = string + default = null +} + variable "type" { - description = "Type of zone to create, valid values are 'public', 'private', 'forwarding', 'peering'." + description = "Type of zone to create, valid values are 'public', 'private', 'forwarding', 'peering', 'service-directory'." type = string default = "private" } From 88b124ddc83066ddb71b255e291b92d765198e2e Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Tue, 12 May 2020 18:52:03 +0200 Subject: [PATCH 2/5] first service-directory module implementation --- modules/service-directory/README.md | 99 ++++++++++++++++++++++++++ modules/service-directory/main.tf | 81 +++++++++++++++++++++ modules/service-directory/outputs.tf | 66 +++++++++++++++++ modules/service-directory/variables.tf | 80 +++++++++++++++++++++ modules/service-directory/versions.tf | 19 +++++ 5 files changed, 345 insertions(+) create mode 100644 modules/service-directory/README.md create mode 100644 modules/service-directory/main.tf create mode 100644 modules/service-directory/outputs.tf create mode 100644 modules/service-directory/variables.tf create mode 100644 modules/service-directory/versions.tf diff --git a/modules/service-directory/README.md b/modules/service-directory/README.md new file mode 100644 index 000000000..ed94ef9a6 --- /dev/null +++ b/modules/service-directory/README.md @@ -0,0 +1,99 @@ +# Google Cloud Service Directory Module + +This module allows managing a single [Service Directory](https://cloud.google.com/service-directory) namespace, including multiple services, endpoints and IAM bindings at the namespace and service levels. + +It can be used in conjunction with the [DNS](../dns) module to create service-directory based DNS zones, offloading IAM control of `A` and `SRV` records at the namespace or service level to Service Directory. The last examples shows how to wire the two modules together. + + +## Examples + +### Namespace with IAM + +```hcl +module "service-directory" { + source = "./modules/service-directory" + project_id = "my-project + location = "europe-west1" + name = "sd-1" + iam_members = { + "roles/servicedirectory.editor" = [ + "serviceAccount:namespace-editor@example.com" + ] + } + iam_roles = [ + "roles/servicedirectory.editor" + ] +``` + +### Services with IAM and endpoints + +```hcl +module "service-directory" { + source = "./modules/service-directory" + project_id = "my-project + location = "europe-west1" + name = "sd-1" + services = { + one = { + endpoints = ["first", "second"] + metadata = null + } + } + service_iam_members = { + one = { + "roles/servicedirectory.editor" = [ + "serviceAccount:service-editor.example.com" + ] + } + } + service_iam_roles = { + one = ["roles/servicedirectory.editor"] + } + endpoint_config = { + "one/first" = { address = "127.0.0.1", port = 80, metadata = {} } + "one/second" = { address = "127.0.0.2", port = 80, metadata = {} } + } +} +``` + +### DNS based zone + +TODO + +```hcl +module "service-directory" { + source = "./modules/service-directory" + project_id = "my-project + location = "europe-west1" + name = "sd-1" +} +``` + + +## Variables + +| name | description | type | required | default | +|---|---|:---: |:---:|:---:| +| location | Namespace location. | string | ✓ | | +| name | Namespace name. | string | ✓ | | +| project_id | Project used for resources. | string | ✓ | | +| *endpoint_config* | Map of endpoint attributes, keys are in service/endpoint format. | map(object({...})) | | {} | +| *iam_members* | IAM members for each namespace role. | map(list(string)) | | {} | +| *iam_roles* | IAM roles for the namespace. | list(string) | | [] | +| *labels* | Labels. | map(string) | | {} | +| *service_iam_members* | IAM members for each service and role. | map(map(list(string))) | | {} | +| *service_iam_roles* | IAM roles for each service. | map(list(string)) | | {} | +| *services* | Service configuration, using service names as keys. | map(object({...})) | | {} | + +## Outputs + +| name | description | sensitive | +|---|---|:---:| +| endpoints | Endpoint resources. | | +| id | Namespace id (short name). | | +| name | Namespace name (long name). | | +| namespace | Namespace resource. | | +| service_id | Service ids (short names). | | +| service_names | Service ids (long names). | | +| services | Service resources. | | + diff --git a/modules/service-directory/main.tf b/modules/service-directory/main.tf new file mode 100644 index 000000000..8348de44a --- /dev/null +++ b/modules/service-directory/main.tf @@ -0,0 +1,81 @@ +/** + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +locals { + endpoint_list = flatten([ + for name, attrs in var.services : [ + for endpoint in attrs.endpoints : { service : name, endpoint : endpoint } + ] + ]) + endpoints = { + for ep in local.endpoint_list : "${ep.service}/${ep.endpoint}" => ep + } + iam_pairs = var.service_iam_roles == null ? [] : flatten([ + for name, roles in var.service_iam_roles : + [for role in roles : { name = name, role = role }] + ]) + iam_keypairs = { + for pair in local.iam_pairs : + "${pair.name}-${pair.role}" => pair + } + iam_members = ( + var.service_iam_members == null ? {} : var.service_iam_members + ) +} + +resource "google_service_directory_namespace" "default" { + provider = google-beta + project = var.project_id + namespace_id = var.name + location = var.location + labels = var.labels +} + +resource "google_service_directory_namespace_iam_binding" "default" { + provider = google-beta + for_each = toset(var.iam_roles) + name = google_service_directory_namespace.default.name + role = each.value + members = lookup(var.iam_members, each.value, []) +} + +resource "google_service_directory_service" "default" { + provider = google-beta + for_each = var.services + namespace = google_service_directory_namespace.default.id + service_id = each.key + metadata = each.value.metadata +} + +resource "google_service_directory_service_iam_binding" "default" { + provider = google-beta + for_each = local.iam_keypairs + name = google_service_directory_service.default[each.value.name].name + role = each.value.role + members = lookup( + lookup(local.iam_members, each.value.name, {}), each.value.role, [] + ) +} + +resource "google_service_directory_endpoint" "default" { + provider = google-beta + for_each = local.endpoints + endpoint_id = each.value.endpoint + service = google_service_directory_service.default[each.value.service].id + metadata = try(var.endpoint_config[each.key].metadata, null) + address = try(var.endpoint_config[each.key].address, null) + port = try(var.endpoint_config[each.key].port, null) +} diff --git a/modules/service-directory/outputs.tf b/modules/service-directory/outputs.tf new file mode 100644 index 000000000..0fab3996b --- /dev/null +++ b/modules/service-directory/outputs.tf @@ -0,0 +1,66 @@ +/** + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +output "endpoints" { + description = "Endpoint resources." + value = google_service_directory_endpoint.default +} + +output "id" { + description = "Namespace id (short name)." + value = google_service_directory_namespace.default.id +} + +output "name" { + description = "Namespace name (long name)." + value = google_service_directory_namespace.default.name +} + +output "namespace" { + description = "Namespace resource." + value = google_service_directory_namespace.default + depends_on = [ + google_service_directory_namespace_iam_binding.default + ] +} + +output "services" { + description = "Service resources." + value = google_service_directory_service.default + depends_on = [ + google_service_directory_service_iam_binding.default + ] +} + +output "service_id" { + description = "Service ids (short names)." + value = { + for k, v in google_service_directory_service.default : k => v.id + } + depends_on = [ + google_service_directory_service_iam_binding.default + ] +} + +output "service_names" { + description = "Service ids (long names)." + value = { + for k, v in google_service_directory_service.default : k => v.name + } + depends_on = [ + google_service_directory_service_iam_binding.default + ] +} diff --git a/modules/service-directory/variables.tf b/modules/service-directory/variables.tf new file mode 100644 index 000000000..8b921d56c --- /dev/null +++ b/modules/service-directory/variables.tf @@ -0,0 +1,80 @@ +/** + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +# we need a separate variable as address will be dynamic in most cases +variable "endpoint_config" { + description = "Map of endpoint attributes, keys are in service/endpoint format." + type = map(object({ + address = string + port = number + metadata = map(string) + })) + default = {} +} + +variable "iam_members" { + description = "IAM members for each namespace role." + type = map(list(string)) + default = {} +} + +variable "iam_roles" { + description = "IAM roles for the namespace." + type = list(string) + default = [] +} + +variable "labels" { + description = "Labels." + type = map(string) + default = {} +} + +variable "location" { + description = "Namespace location." + type = string +} + +variable "name" { + description = "Namespace name." + type = string +} + +variable "project_id" { + description = "Project used for resources." + type = string +} + +variable "service_iam_members" { + description = "IAM members for each service and role." + type = map(map(list(string))) + default = {} +} + +variable "service_iam_roles" { + description = "IAM roles for each service." + type = map(list(string)) + default = {} +} + +variable "services" { + description = "Service configuration, using service names as keys." + type = map(object({ + endpoints = list(string) + metadata = map(string) + })) + default = {} +} diff --git a/modules/service-directory/versions.tf b/modules/service-directory/versions.tf new file mode 100644 index 000000000..ce6918e09 --- /dev/null +++ b/modules/service-directory/versions.tf @@ -0,0 +1,19 @@ +/** + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +terraform { + required_version = ">= 0.12.6" +} From e85173e4402fb5bf24624f5d919b3686699e4133 Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Tue, 12 May 2020 20:26:34 +0200 Subject: [PATCH 3/5] add DNS example to service directory --- modules/service-directory/README.md | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/modules/service-directory/README.md b/modules/service-directory/README.md index ed94ef9a6..8f58c0b45 100644 --- a/modules/service-directory/README.md +++ b/modules/service-directory/README.md @@ -12,7 +12,7 @@ It can be used in conjunction with the [DNS](../dns) module to create service-di ```hcl module "service-directory" { source = "./modules/service-directory" - project_id = "my-project + project_id = "my-project" location = "europe-west1" name = "sd-1" iam_members = { @@ -23,6 +23,7 @@ module "service-directory" { iam_roles = [ "roles/servicedirectory.editor" ] +} ``` ### Services with IAM and endpoints @@ -30,7 +31,7 @@ module "service-directory" { ```hcl module "service-directory" { source = "./modules/service-directory" - project_id = "my-project + project_id = "my-project" location = "europe-west1" name = "sd-1" services = { @@ -58,15 +59,32 @@ module "service-directory" { ### DNS based zone -TODO +Wiring a service directory namespace to a private DNS zone allows querying the namespace, and delegating control of DNS records at the namespace or service level. This effectively allows fine grained ACL control of Cloud DNS zones. ```hcl module "service-directory" { source = "./modules/service-directory" - project_id = "my-project + project_id = "my-project" location = "europe-west1" - name = "sd-1" + name = "apps" + services = { + app1 = { endpoints = ["one"], metadata = null } + } + endpoint_config = { + "app1/one" = { address = "127.0.0.1", port = 80, metadata = {} } + } } + +module "dns-sd" { + source = "./modules/dns" + project_id = "my-project" + type = "service-directory" + name = "apps" + domain = "apps.example.org." + client_networks = [local.vpc_self_link] + service_directory_namespace = module.service-directory.id +} + ``` From 835915c4e34b37642e120f1e24530075342cd5a9 Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Wed, 13 May 2020 07:54:03 +0200 Subject: [PATCH 4/5] Update README.md --- modules/service-directory/README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/modules/service-directory/README.md b/modules/service-directory/README.md index 8f58c0b45..861e67d6b 100644 --- a/modules/service-directory/README.md +++ b/modules/service-directory/README.md @@ -67,6 +67,14 @@ module "service-directory" { project_id = "my-project" location = "europe-west1" name = "apps" + iam_members = { + "roles/servicedirectory.editor" = [ + "serviceAccount:namespace-editor@example.com" + ] + } + iam_roles = [ + "roles/servicedirectory.editor" + ] services = { app1 = { endpoints = ["one"], metadata = null } } From cd3730393fe5966773e9b31761dc833d825ed89e Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Wed, 13 May 2020 07:54:46 +0200 Subject: [PATCH 5/5] Update README.md --- modules/service-directory/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/service-directory/README.md b/modules/service-directory/README.md index 861e67d6b..c57bd9f8e 100644 --- a/modules/service-directory/README.md +++ b/modules/service-directory/README.md @@ -2,7 +2,7 @@ This module allows managing a single [Service Directory](https://cloud.google.com/service-directory) namespace, including multiple services, endpoints and IAM bindings at the namespace and service levels. -It can be used in conjunction with the [DNS](../dns) module to create service-directory based DNS zones, offloading IAM control of `A` and `SRV` records at the namespace or service level to Service Directory. The last examples shows how to wire the two modules together. +It can be used in conjunction with the [DNS](../dns) module to create [service-directory based DNS zones](https://cloud.google.com/service-directory/docs/configuring-service-directory-zone, offloading IAM control of `A` and `SRV` records at the namespace or service level to Service Directory. The last examples shows how to wire the two modules together. ## Examples