diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..5a40521 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,32 @@ +name: Build + +on: + push: + branches: + - "*" + tags-ignore: + - "*" + pull_request: + types: + - opened + - reopened + +jobs: + build: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + os: [ ubuntu-latest ] + tf-version: [ 0.14.11, 0.15.5, 1.0.8 ] + steps: + - name: Install terraform v${{ matrix.tf-version }} + run: | + curl -LO https://releases.hashicorp.com/terraform/${{ matrix.tf-version }}/terraform_${{ matrix.tf-version }}_linux_amd64.zip + unzip terraform_${{ matrix.tf-version }}_linux_amd64.zip + sudo mv terraform /usr/local/bin + rm * + - name: Checkout code + uses: actions/checkout@v2 + - name: Validate examples terraform v${{ matrix.tf-version }} + run: make examples diff --git a/.github/workflows/commitlint.yaml b/.github/workflows/commitlint.yaml new file mode 100644 index 0000000..67e6df3 --- /dev/null +++ b/.github/workflows/commitlint.yaml @@ -0,0 +1,11 @@ +name: Lint Commit Messages +on: [pull_request] + +jobs: + commitlint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - uses: wagoid/commitlint-github-action@v4 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3fa8c86 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.terraform diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6918e82 --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ +#!/usr/bin/make -f + +SHELL = /bin/bash +EXAMPLES = $(shell find ./examples/* -maxdepth 1 -type d -not -path '*/\.*') +.PHONY: examples +examples: $(addprefix example/,$(EXAMPLES)) + +.PHONY: example/% +example/%: + @echo "Processing example: $(notdir $*)" + @terraform -chdir=$* init + @terraform -chdir=$* validate + @terraform -chdir=$* plan + diff --git a/README.md b/README.md new file mode 100644 index 0000000..68dad7b --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +# terraform-aws-k8s-addons-external-secrets + +A terraform module which provides +the [IRSA external permissions](https://kops.sigs.k8s.io/cluster_spec/#service-account-issuer-discovery-and-aws-iam-roles-for-service-accounts-irsa) +and the [custom addon](https://kops.sigs.k8s.io/addons/#custom-addons) +for [external-dns](https://github.com/kubernetes-sigs/external-dns) to be used together +with [opzkit/k8s/aws](https://registry.terraform.io/modules/opzkit/k8s/aws/latest). + diff --git a/examples/basic/k8s.tf b/examples/basic/k8s.tf new file mode 100644 index 0000000..f642277 --- /dev/null +++ b/examples/basic/k8s.tf @@ -0,0 +1,69 @@ +locals { + zone = "example.com" + name = "k8s.${local.zone}" + region = "eu-west-1" + account_id = "012345678901" +} + +resource "aws_iam_role" "kubernetes_admin" { + assume_role_policy = jsonencode({ + Statement = [ + { + Action = "sts:AssumeRole" + Condition = {} + Effect = "Allow" + Principal = { + AWS = "arn:aws:iam::${local.account_id}:root" + } + }, + ] + Version = "2012-10-17" + }) + description = "Kubernetes administrator role (for AWS IAM Authenticator for Kubernetes)." +} + +module "external_dns" { + source = "opzkit/k8s-addons-external-dns/aws" + version = "0.10.0" + account_id = local.account_id + name = local.name + region = local.region +} + +module "state_store" { + source = "opzkit/kops-state-store/aws" + version = "0.0.2" + state_store_name = "some-kops-storage-s3-bucket" +} + +module "k8s-network" { + source = "opzkit/k8s-network/aws" + version = "0.0.5" + name = local.name + region = local.region + public_subnet_zones = ["a", "b", "c"] + vpc_cidr = "172.20.0.0/16" +} + +module "k8s" { + depends_on = [module.state_store] + source = "opzkit/k8s/aws" + version = "0.2.0" + name = local.name + region = local.region + dns_zone = local.zone + kubernetes_version = "1.21.5" + master_count = 3 + vpc_id = module.k8s-network.vpc_id + public_subnet_ids = module.k8s-network.public_subnets + iam_role_name = aws_iam_role.kubernetes_admin.arn + bucket_state_store = module.state_store.bucket + admin_ssh_key = "../dummy_ssh_private" + aws_oidc_provider = true + service_account_external_permissions = [ + module.external_dns.permissions + ] + extra_addons = [ + module.external_dns.addon + ] +} diff --git a/examples/basic/provider.tf b/examples/basic/provider.tf new file mode 100644 index 0000000..9d08f1c --- /dev/null +++ b/examples/basic/provider.tf @@ -0,0 +1,27 @@ +provider "kops" { + state_store = "s3://state-store" +} + +provider "aws" { + skip_requesting_account_id = true + skip_credentials_validation = true + skip_metadata_api_check = true + s3_force_path_style = true + region = "eu-west-1" + access_key = "mock_access_key" + secret_key = "mock_secret_key" +} + +terraform { + required_providers { + kops = { + source = "eddycharly/kops" + version = "1.21.0" + } + + aws = { + source = "hashicorp/aws" + version = "~> 3.0" + } + } +} diff --git a/examples/dummy_ssh_private b/examples/dummy_ssh_private new file mode 100644 index 0000000..a684ce2 --- /dev/null +++ b/examples/dummy_ssh_private @@ -0,0 +1 @@ +This should be your private ssh key. diff --git a/external-dns.yaml b/external-dns.yaml new file mode 100644 index 0000000..7443333 --- /dev/null +++ b/external-dns.yaml @@ -0,0 +1,85 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: external-dns + namespace: kube-system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: external-dns +rules: +- apiGroups: [ "" ] + resources: [ "services","endpoints","pods" ] + verbs: [ "get","watch","list" ] +- apiGroups: [ "extensions","networking.k8s.io" ] + resources: [ "ingresses" ] + verbs: [ "get","watch","list" ] +- apiGroups: [ "" ] + resources: [ "nodes" ] + verbs: [ "list","watch" ] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: external-dns-viewer +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: external-dns +subjects: +- kind: ServiceAccount + name: external-dns + namespace: kube-system +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: external-dns + namespace: kube-system +spec: + strategy: + type: Recreate + selector: + matchLabels: + app: external-dns + template: + metadata: + labels: + app: external-dns + spec: + serviceAccountName: external-dns + containers: + - name: external-dns + image: k8s.gcr.io/external-dns/external-dns:v0.10.0 + args: + - --source=service + - --source=ingress + - --provider=aws + - --aws-zone-type=public + - --registry=txt + - --txt-owner-id=${name} + env: + - name: AWS_DEFAULT_REGION + value: "${region}" + - name: AWS_REGION + value: "${region}" + - name: AWS_ROLE_ARN + value: "arn:aws:iam::${account_id}:role/external-dns.kube-system.sa.${name}" + - name: AWS_WEB_IDENTITY_TOKEN_FILE + value: "/var/run/secrets/amazonaws.com/serviceaccount/token" + - name: AWS_STS_REGIONAL_ENDPOINTS + value: "regional" + volumeMounts: + - mountPath: "/var/run/secrets/amazonaws.com/serviceaccount/" + name: aws-token + volumes: + - name: aws-token + projected: + sources: + - serviceAccountToken: + audience: "amazonaws.com" + expirationSeconds: 86400 + path: token + securityContext: + fsGroup: 65534 diff --git a/locals.tf b/locals.tf new file mode 100644 index 0000000..51062f9 --- /dev/null +++ b/locals.tf @@ -0,0 +1,7 @@ +locals { + yaml = templatefile("${path.module}/external-dns.yaml", { + name = var.name + region = var.region + account_id = var.account_id + }) +} diff --git a/outputs.tf b/outputs.tf new file mode 100644 index 0000000..4716b7f --- /dev/null +++ b/outputs.tf @@ -0,0 +1,40 @@ +output "permissions" { + value = { + name = "external-dns" + namespace = "kube-system" + aws = { + inline_policy = <