diff --git a/examples/k8s-namespace-with-service-account/main.tf b/examples/k8s-namespace-with-service-account/main.tf index e6df3a0..79f2fb0 100644 --- a/examples/k8s-namespace-with-service-account/main.tf +++ b/examples/k8s-namespace-with-service-account/main.tf @@ -9,6 +9,7 @@ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ provider "kubernetes" { + version = "~> 1.5" config_context = "${var.kubectl_config_context_name}" config_path = "${var.kubectl_config_path}" } @@ -23,9 +24,7 @@ module "namespace" { # source = "git::git@github.com:gruntwork-io/terraform-kubernetes-helm.git//modules/k8s-namespace?ref=v0.0.1" source = "../../modules/k8s-namespace" - kubectl_config_context_name = "${var.kubectl_config_context_name}" - kubectl_config_path = "${var.kubectl_config_path}" - name = "${var.name}" + name = "${var.name}" } # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -38,11 +37,10 @@ module "service_account_access_all" { # source = "git::git@github.com:gruntwork-io/terraform-kubernetes-helm.git//modules/k8s-service-account?ref=v0.0.1" source = "../../modules/k8s-service-account" - kubectl_config_context_name = "${var.kubectl_config_context_name}" - kubectl_config_path = "${var.kubectl_config_path}" - name = "${var.name}-admin" - namespace = "${module.namespace.name}" - rbac_roles = ["${module.namespace.rbac_access_all_role}"] + name = "${var.name}-admin" + namespace = "${module.namespace.name}" + num_rbac_roles = 1 + rbac_roles = ["${module.namespace.rbac_access_all_role}"] # How to tag the service account with a label labels = { @@ -56,11 +54,10 @@ module "service_account_access_read_only" { # source = "git::git@github.com:gruntwork-io/terraform-kubernetes-helm.git//modules/k8s-service-account?ref=v0.0.1" source = "../../modules/k8s-service-account" - kubectl_config_context_name = "${var.kubectl_config_context_name}" - kubectl_config_path = "${var.kubectl_config_path}" - name = "${var.name}-read-only" - namespace = "${module.namespace.name}" - rbac_roles = ["${module.namespace.rbac_access_read_only_role}"] + name = "${var.name}-read-only" + namespace = "${module.namespace.name}" + num_rbac_roles = 1 + rbac_roles = ["${module.namespace.rbac_access_read_only_role}"] # How to tag the service account with a label labels = { diff --git a/modules/k8s-namespace/dependencies.tf b/modules/k8s-namespace/dependencies.tf deleted file mode 100644 index 8859943..0000000 --- a/modules/k8s-namespace/dependencies.tf +++ /dev/null @@ -1,26 +0,0 @@ -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# DATA SOURCES -# These resources must already exist. -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# KUBERNETES RESOURCE TEMPLATES FOR RBAC ROLES -# Render resource configs for RBAC roles. -# NOTE: These should be replaced with resources from the Terraform Kubernetes provider when they become available. -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -data "template_file" "rbac_role_access_all" { - template = "${file("${path.module}/templates/rbac_role_access_all.json")}" - - vars { - namespace = "${var.name}" - } -} - -data "template_file" "rbac_role_access_read_only" { - template = "${file("${path.module}/templates/rbac_role_access_read_only.json")}" - - vars { - namespace = "${var.name}" - } -} diff --git a/modules/k8s-namespace/main.tf b/modules/k8s-namespace/main.tf index 0df9475..4a2634d 100644 --- a/modules/k8s-namespace/main.tf +++ b/modules/k8s-namespace/main.tf @@ -29,36 +29,32 @@ resource "kubernetes_namespace" "namespace" { # This defines two default RBAC roles scoped to the namespace: # - namespace-access-all : Admin level permissions on all resources in the namespace. # - namespace-access-read-only: Read only permissions on all resources in the namespace. -# NOTE: replace below with resources from the Terraform Kubernetes provider when they become available. -# - Open PR: https://github.com/terraform-providers/terraform-provider-kubernetes/pull/235 # --------------------------------------------------------------------------------------------------------------------- -locals { - kubectl_config_options = "${var.kubectl_config_context_name != "" ? "--context ${var.kubectl_config_context_name}" : ""} ${var.kubectl_config_path != "" ? "--kubeconfig ${var.kubectl_config_path}" : ""}" -} - -resource "null_resource" "rbac_role_access_all" { - provisioner "local-exec" { - command = "echo '${data.template_file.rbac_role_access_all.rendered}' | kubectl auth reconcile ${local.kubectl_config_options} -f -" +resource "kubernetes_role" "rbac_role_access_all" { + metadata { + name = "${var.name}-access-all" + namespace = "${var.name}" + labels = "${var.labels}" + annotations = "${var.annotations}" } - provisioner "local-exec" { - command = "echo '${data.template_file.rbac_role_access_all.rendered}' | kubectl delete ${local.kubectl_config_options} -f -" - when = "destroy" + rule { + api_groups = ["*"] + resources = ["*"] + verbs = ["*"] } - - depends_on = ["kubernetes_namespace.namespace"] } -resource "null_resource" "rbac_role_access_read_only" { - provisioner "local-exec" { - command = "echo '${data.template_file.rbac_role_access_read_only.rendered}' | kubectl auth reconcile ${local.kubectl_config_options} -f -" +resource "kubernetes_role" "rbac_role_access_read_only" { + metadata { + name = "${var.name}-access-read-only" + namespace = "${var.name}" } - provisioner "local-exec" { - command = "echo '${data.template_file.rbac_role_access_read_only.rendered}' | kubectl delete ${local.kubectl_config_options} -f -" - when = "destroy" + rule { + api_groups = ["*"] + resources = ["*"] + verbs = ["get", "list", "watch"] } - - depends_on = ["kubernetes_namespace.namespace"] } diff --git a/modules/k8s-namespace/outputs.tf b/modules/k8s-namespace/outputs.tf index 7933631..e46a0fa 100644 --- a/modules/k8s-namespace/outputs.tf +++ b/modules/k8s-namespace/outputs.tf @@ -5,12 +5,10 @@ output "name" { output "rbac_access_all_role" { description = "The name of the RBAC role that grants admin level permissions on the namespace." - value = "${var.name}-access-all" - depends_on = ["null_resource.rbac_role_access_all"] + value = "${kubernetes_role.rbac_role_access_all.metadata.0.name}" } output "rbac_access_read_only_role" { description = "The name of the RBAC role that grants read only permissions on the namespace." - value = "${var.name}-access-read-only" - depends_on = ["null_resource.rbac_role_access_read_only"] + value = "${kubernetes_role.rbac_role_access_read_only.metadata.0.name}" } diff --git a/modules/k8s-namespace/templates/rbac_role_access_all.json b/modules/k8s-namespace/templates/rbac_role_access_all.json deleted file mode 100644 index 7fdf9f1..0000000 --- a/modules/k8s-namespace/templates/rbac_role_access_all.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "kind": "Role", - "apiVersion": "rbac.authorization.k8s.io/v1", - "metadata": { - "namespace": "${namespace}", - "name": "${namespace}-access-all" - }, - "rules": [ - { - "apiGroups": ["*"], - "resources": ["*"], - "verbs": ["*"] - } - ] -} diff --git a/modules/k8s-namespace/templates/rbac_role_access_read_only.json b/modules/k8s-namespace/templates/rbac_role_access_read_only.json deleted file mode 100644 index ff64638..0000000 --- a/modules/k8s-namespace/templates/rbac_role_access_read_only.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "kind": "Role", - "apiVersion": "rbac.authorization.k8s.io/v1", - "metadata": { - "namespace": "${namespace}", - "name": "${namespace}-access-read-only" - }, - "rules": [ - { - "apiGroups": ["*"], - "resources": ["*"], - "verbs": ["get", "list", "watch"] - } - ] -} diff --git a/modules/k8s-namespace/variables.tf b/modules/k8s-namespace/variables.tf index 5ba82f0..a30fbfa 100644 --- a/modules/k8s-namespace/variables.tf +++ b/modules/k8s-namespace/variables.tf @@ -23,13 +23,3 @@ variable "annotations" { type = "map" default = {} } - -variable "kubectl_config_context_name" { - description = "The config context to use when authenticating to the Kubernetes cluster. If empty, defaults to the current context specified in the kubeconfig file." - default = "" -} - -variable "kubectl_config_path" { - description = "The path to the config file to use for kubectl. If empty, defaults to $HOME/.kube/config" - default = "" -} diff --git a/modules/k8s-service-account/dependencies.tf b/modules/k8s-service-account/dependencies.tf deleted file mode 100644 index 576638b..0000000 --- a/modules/k8s-service-account/dependencies.tf +++ /dev/null @@ -1,31 +0,0 @@ -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# DATA SOURCES -# These resources must already exist. -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# KUBERNETES RESOURCE TEMPLATES FOR RBAC ROLE BINDINGS -# Render resource configs for RBAC role bindings. -# NOTE: These should be replaced with resources from the Terraform Kubernetes provider when they become available. -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -data "template_file" "rbac_role_binding" { - count = "${length(var.rbac_roles)}" - template = "${file("${path.module}/templates/rbac_role_binding.json")}" - - vars { - namespace = "${var.namespace}" - service_account_name = "${var.name}" - encoded_labels = "${jsonencode(var.labels)}" - encoded_annotations = "${jsonencode(var.annotations)}" - role_name = "${element(var.rbac_roles, count.index)}" - } -} - -data "template_file" "rbac_role_binding_list" { - template = "${file("${path.module}/templates/rbac_role_binding_list.json")}" - - vars { - role_binding_jsons_rendered = "${join(",",data.template_file.rbac_role_binding.*.rendered)}" - } -} diff --git a/modules/k8s-service-account/main.tf b/modules/k8s-service-account/main.tf index c25a4c7..fc8c2b5 100644 --- a/modules/k8s-service-account/main.tf +++ b/modules/k8s-service-account/main.tf @@ -31,21 +31,28 @@ resource "kubernetes_service_account" "service_account" { # --------------------------------------------------------------------------------------------------------------------- # BIND THE PROVIDED ROLES TO THE SERVICE ACCOUNT -# NOTE: replace below with resources from the Terraform Kubernetes provider when they become available. -# - Open PR: https://github.com/terraform-providers/terraform-provider-kubernetes/pull/235 # --------------------------------------------------------------------------------------------------------------------- -locals { - kubectl_config_options = "${var.kubectl_config_context_name != "" ? "--context ${var.kubectl_config_context_name}" : ""} ${var.kubectl_config_path != "" ? "--kubeconfig ${var.kubectl_config_path}" : ""}" -} +resource "kubernetes_role_binding" "service_account_role_binding" { + count = "${var.num_rbac_roles}" + + metadata { + name = "${var.name}-${element(var.rbac_roles, count.index)}-role-binding" + namespace = "${var.namespace}" + labels = "${var.labels}" + annotations = "${var.annotations}" + } -resource "null_resource" "rbac_role_binding" { - provisioner "local-exec" { - command = "echo '${data.template_file.rbac_role_binding_list.rendered}' | kubectl auth reconcile ${local.kubectl_config_options} -f -" + role_ref { + api_group = "rbac.authorization.k8s.io" + kind = "Role" + name = "${element(var.rbac_roles, count.index)}" } - provisioner "local-exec" { - command = "echo '${data.template_file.rbac_role_binding_list.rendered}' | kubectl delete ${local.kubectl_config_options} -f -" - when = "destroy" + subject { + api_group = "" + kind = "ServiceAccount" + name = "${kubernetes_service_account.service_account.metadata.0.name}" + namespace = "${var.namespace}" } } diff --git a/modules/k8s-service-account/templates/rbac_role_binding.json b/modules/k8s-service-account/templates/rbac_role_binding.json deleted file mode 100644 index 4c18524..0000000 --- a/modules/k8s-service-account/templates/rbac_role_binding.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "apiVersion": "rbac.authorization.k8s.io/v1", - "kind": "RoleBinding", - "metadata": { - "name": "${service_account_name}-role-binding", - "namespace": "${namespace}", - "labels": ${encoded_labels}, - "annotations": ${encoded_annotations} - }, - "subjects": [ - { - "kind": "ServiceAccount", - "name": "${service_account_name}", - "namespace": "${namespace}" - } - ], - "roleRef": { - "apiGroup": "rbac.authorization.k8s.io", - "kind": "Role", - "name": "${role_name}" - } -} diff --git a/modules/k8s-service-account/templates/rbac_role_binding_list.json b/modules/k8s-service-account/templates/rbac_role_binding_list.json deleted file mode 100644 index 3be246c..0000000 --- a/modules/k8s-service-account/templates/rbac_role_binding_list.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "apiVersion": "v1", - "kind": "List", - "items": [${role_binding_jsons_rendered}] -} diff --git a/modules/k8s-service-account/variables.tf b/modules/k8s-service-account/variables.tf index 6c69272..0e7d71c 100644 --- a/modules/k8s-service-account/variables.tf +++ b/modules/k8s-service-account/variables.tf @@ -16,6 +16,13 @@ variable "namespace" { # These variables have defaults, but may be overridden by the operator. # --------------------------------------------------------------------------------------------------------------------- +# Workaround terraform limitation where resource count can not include interpolated lists. +# See: https://github.com/hashicorp/terraform/issues/17421 +variable "num_rbac_roles" { + description = "Number of RBAC roles to bind. This should match the number of items in the list passed to rbac_roles." + default = 0 +} + variable "rbac_roles" { description = "List of names of the RBAC roles that should be bound to the service account. If this list is non-empty, you must also pass in num_rbac_roles specifying the number of roles." type = "list" @@ -50,13 +57,3 @@ variable "secrets_for_pods" { type = "list" default = [] } - -variable "kubectl_config_context_name" { - description = "The config context to use when authenticating to the Kubernetes cluster. If empty, defaults to the current context specified in the kubeconfig file." - default = "" -} - -variable "kubectl_config_path" { - description = "The path to the config file to use for kubectl. If empty, defaults to $HOME/.kube/config" - default = "" -} diff --git a/test/k8s_namespace_with_service_account_test.go b/test/k8s_namespace_with_service_account_test.go index 1f2ec7d..e05d230 100644 --- a/test/k8s_namespace_with_service_account_test.go +++ b/test/k8s_namespace_with_service_account_test.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" "path/filepath" - "strings" "testing" "text/template" "time" @@ -57,23 +56,7 @@ func TestK8SNamespaceWithServiceAccount(t *testing.T) { defer test_structure.RunTestStage(t, "cleanup", func() { k8sNamespaceTerratestOptions := test_structure.LoadTerraformOptions(t, workingDir) - - // We extract out the outputs before destroying so that we can validate these resources are destroyed. This is - // to test that the null_resource provisioners ran on destroy to destroy those resources. - rbacAccessAllRole := terraform.Output(t, k8sNamespaceTerratestOptions, "rbac_access_all_role") - rbacAccessAllRoleDeleteString := fmt.Sprintf("role.rbac.authorization.k8s.io \"%s\" deleted", rbacAccessAllRole) - rbacAccessReadOnlyRole := terraform.Output(t, k8sNamespaceTerratestOptions, "rbac_access_read_only_role") - rbacAccessReadOnlyRoleDeleteString := fmt.Sprintf("role.rbac.authorization.k8s.io \"%s\" deleted", rbacAccessReadOnlyRole) - accessAllServiceAccount := terraform.Output(t, k8sNamespaceTerratestOptions, "service_account_access_all") - accessAllServiceAccountDeleteString := fmt.Sprintf("rolebinding.rbac.authorization.k8s.io \"%s-role-binding\" deleted", accessAllServiceAccount) - accessROServiceAccount := terraform.Output(t, k8sNamespaceTerratestOptions, "service_account_access_read_only") - accessROServiceAccountDeleteString := fmt.Sprintf("rolebinding.rbac.authorization.k8s.io \"%s-role-binding\" deleted", accessROServiceAccount) - - out := terraform.Destroy(t, k8sNamespaceTerratestOptions) - assert.True(t, strings.Contains(out, rbacAccessAllRoleDeleteString)) - assert.True(t, strings.Contains(out, rbacAccessReadOnlyRoleDeleteString)) - assert.True(t, strings.Contains(out, accessAllServiceAccountDeleteString)) - assert.True(t, strings.Contains(out, accessROServiceAccountDeleteString)) + terraform.Destroy(t, k8sNamespaceTerratestOptions) }) test_structure.RunTestStage(t, "terraform_apply", func() {