From 5dbc3bdea22e9c3ec02536b11a290d2fad4fc08a Mon Sep 17 00:00:00 2001 From: Xavier Mortelette Date: Tue, 8 Oct 2024 16:41:52 +0200 Subject: [PATCH] Add ak-gatekeeper --- ak-gatekeeper/ak_provider.tf | 12 ++++++ ak-gatekeeper/common.tf | 15 +++++++ ak-gatekeeper/forward.tf | 51 ++++++++++++++++++++++++ ak-gatekeeper/middleware.tf | 26 ++++++++++++ ak-gatekeeper/outpost.tf | 74 +++++++++++++++++++++++++++++++++++ ak-gatekeeper/outpost_read.sh | 21 ++++++++++ ak-gatekeeper/outputs.tf | 6 +++ ak-gatekeeper/providers.tf | 25 ++++++++++++ ak-gatekeeper/variables.tf | 32 +++++++++++++++ 9 files changed, 262 insertions(+) create mode 100644 ak-gatekeeper/ak_provider.tf create mode 100644 ak-gatekeeper/common.tf create mode 100644 ak-gatekeeper/forward.tf create mode 100644 ak-gatekeeper/middleware.tf create mode 100644 ak-gatekeeper/outpost.tf create mode 100644 ak-gatekeeper/outpost_read.sh create mode 100644 ak-gatekeeper/outputs.tf create mode 100644 ak-gatekeeper/providers.tf create mode 100644 ak-gatekeeper/variables.tf diff --git a/ak-gatekeeper/ak_provider.tf b/ak-gatekeeper/ak_provider.tf new file mode 100644 index 0000000..a32f86a --- /dev/null +++ b/ak-gatekeeper/ak_provider.tf @@ -0,0 +1,12 @@ +data "authentik_flow" "proxy_authorization_flow" { + depends_on = [data.kubernetes_secret_v1.authentik] + slug = "default-provider-authorization-implicit-consent" +} + +resource "authentik_provider_proxy" "app_proxy_provider" { + name = "${local.app_slug}-provider" + external_host = local.external_url + authorization_flow = data.authentik_flow.proxy_authorization_flow.id + mode = "forward_single" + access_token_validity = var.access_token_validity +} diff --git a/ak-gatekeeper/common.tf b/ak-gatekeeper/common.tf new file mode 100644 index 0000000..ddb87f7 --- /dev/null +++ b/ak-gatekeeper/common.tf @@ -0,0 +1,15 @@ +data "kubernetes_secret_v1" "authentik" { + metadata { + name = "authentik" + namespace = var.namespace + } +} +locals { + app_slug = "${var.instance}${var.component == "" ? "" : "-"}${var.component}" + ak_gatekeeper_labels = merge(var.labels, { + "app.kubernetes.io/component" = "ak-gatekeeper" + }) + authentik_url = "http://authentik.${var.domain}-auth.svc" + authentik_token = try(data.kubernetes_secret_v1.authentik.data["AUTHENTIK_BOOTSTRAP_TOKEN"], "no-token") + external_url = format("https://%s", var.dns_name) +} diff --git a/ak-gatekeeper/forward.tf b/ak-gatekeeper/forward.tf new file mode 100644 index 0000000..9130e9d --- /dev/null +++ b/ak-gatekeeper/forward.tf @@ -0,0 +1,51 @@ +# locals { +# app_slug = "${var.instance}${var.component == "" ? "" : "-"}${var.component}" +# forward_labels = merge(var.labels, { +# "app.kubernetes.io/component" = "ak-gatekeeper" +# }) +# external_url = format("https://%s", var.dns_name) +# forward_outpost_providers = jsondecode(data.http.get_forward_outpost.response_body).results[0].providers +# forward_outpost_pk = jsondecode(data.http.get_forward_outpost.response_body).results[0].pk +# } + +# data "authentik_flow" "default_authorization_flow" { +# slug = "default-provider-authorization-implicit-consent" +# } + +# resource "authentik_provider_proxy" "forward" { +# name = local.app_slug +# external_host = local.external_url +# authorization_flow = data.authentik_flow.default_authorization_flow.id +# mode = "forward_single" +# access_token_validity = var.access_token_validity +# } + +# data "http" "get_forward_outpost" { +# depends_on = [authentik_provider_proxy.forward] +# url = "http://authentik.${var.domain}-auth.svc/api/v3/outposts/instances/?name__iexact=${var.domain}-proxy-outpost" +# method = "GET" +# request_headers = var.request_headers +# lifecycle { +# postcondition { +# condition = contains([200], self.status_code) +# error_message = "Status code invalid" +# } +# } +# } + +# resource "restapi_object" "forward_outpost_binding" { +# path = "/outposts/instances/${local.forward_outpost_pk}/" +# data = jsonencode({ +# name = "forward" +# providers = contains(local.forward_outpost_providers, authentik_provider_proxy.forward.id) ? local.forward_outpost_providers : concat(local.forward_outpost_providers, [authentik_provider_proxy.forward.id]) +# }) +# } + + + +# data "kubernetes_ingress_v1" "authentik" { +# metadata { +# name = "authentik" +# namespace = "${var.domain}-auth" +# } +# } diff --git a/ak-gatekeeper/middleware.tf b/ak-gatekeeper/middleware.tf new file mode 100644 index 0000000..4df753f --- /dev/null +++ b/ak-gatekeeper/middleware.tf @@ -0,0 +1,26 @@ +resource "kubectl_manifest" "middleware" { + yaml_body = <<-EOF + apiVersion: traefik.io/v1alpha1 + kind: Middleware + metadata: + name: "${local.app_slug}-gatekeeper" + namespace: "${var.namespace}" + labels: ${jsonencode(local.ak_gatekeeper_labels)} + spec: + forwardAuth: + address: http://authentik.${var.domain}-auth.svc:9000/outpost.goauthentik.io/auth/traefik + trustForwardHeader: true + authResponseHeaders: + - X-authentik-username + - X-authentik-email + - X-authentik-groups + - X-authentik-name + - X-authentik-uid + - X-authentik-jwt + - X-authentik-meta-jwks + - X-authentik-meta-outpost + - X-authentik-meta-provider + - X-authentik-meta-app + - X-authentik-meta-version + EOF +} \ No newline at end of file diff --git a/ak-gatekeeper/outpost.tf b/ak-gatekeeper/outpost.tf new file mode 100644 index 0000000..bd0fe15 --- /dev/null +++ b/ak-gatekeeper/outpost.tf @@ -0,0 +1,74 @@ +locals { + request_headers = { + "Content-Type" = "application/json" + Authorization = "Bearer ${local.authentik_token}" + } + outposts = jsondecode(data.http.get_proxy_outpost.response_body).results + outpost_providers = local.outposts[0].providers + outpost_pk = local.outposts[0].pk +} + + +data "http" "get_proxy_outpost" { + depends_on = [data.kubernetes_secret_v1.authentik] + url = "http://authentik.${var.domain}-auth.svc/api/v3/outposts/instances/?name__iexact=${var.domain}-proxy-outpost" + method = "GET" + request_headers = var.request_headers + lifecycle { + postcondition { + condition = contains([200], self.status_code) + error_message = "Status code invalid" + } + } +} + + +# resource "restapi_object" "proxy_outpost_binding" { +# path = "/outposts/instances/${local.outpost_pk}/" +# data = jsonencode({ +# name = "${var.domain}-proxy-outpost" +# providers = contains(local.outpost_providers, authentik_provider_proxy.app_proxy_provider.id) ? local.outpost_providers : concat(local.outpost_providers, [authentik_provider_proxy.app_proxy_provider.id]) +# }) +# } + +# data "http" "get_local_sck" { +# depends_on = [data.kubernetes_secret_v1.authentik] +# url = "http://authentik-authentik.${var.namespace}.svc/api/v3/outposts/service_connections/kubernetes/?local=true" +# method = "GET" +# request_headers = local.request_headers +# lifecycle { +# postcondition { +# condition = contains([200], self.status_code) +# error_message = "Status code invalid" +# } +# } +# } + +# data "kubernetes_ingress_v1" "authentik" { +# metadata { +# name = "authentik" +# namespace = var.namespace +# } +# } + +# resource "authentik_outpost" "proxy_outpost" { +# depends_on = [data.http.get_local_sck, data.kubernetes_ingress_v1.authentik] +# name = "${var.domain}-proxy-outpost" +# type = "proxy" +# service_connection = local.local_sck[0].pk +# config = jsonencode({ +# "log_level" : "info", +# "authentik_host" : "http://authentik.${var.namespace}.svc", +# "docker_map_ports" : true, +# "kubernetes_replicas" : 1, +# "kubernetes_namespace" : var.namespace, +# "authentik_host_browser" : "https://${data.kubernetes_ingress_v1.authentik.spec[0].rule[0].host}", +# "object_naming_template" : "ak-%(name)s", +# "authentik_host_insecure" : false, +# "kubernetes_service_type" : "ClusterIP", +# "kubernetes_image_pull_secrets" : [], +# "kubernetes_disabled_components" : [], +# "kubernetes_ingress_annotations" : {}, +# }) +# protocol_providers = [authentik_provider_proxy.domain_proxy_provider.id] +# } diff --git a/ak-gatekeeper/outpost_read.sh b/ak-gatekeeper/outpost_read.sh new file mode 100644 index 0000000..1f4f644 --- /dev/null +++ b/ak-gatekeeper/outpost_read.sh @@ -0,0 +1,21 @@ +#!/bin/bash +set -e + +CURL_OPTIONS="-sL" +if [ ! -z ${INSECURE_CURL+x} ]; then + CURL_OPTIONS="${CURL_OPTIONS} -k" +fi +OUTPUT_FILE=$(mktemp) +HTTP_CODE=$(curl $CURL_OPTIONS \ + --output $OUTPUT_FILE \ + --write-out "%{http_code}" \ + -H "Accept: application/json" \ + -H "Authorization: Bearer ${AK_TOKEN}" \ + "${AK_BASEURL}/api/v3/outposts/instances/${AK_OUTPOST_ID}/") +if [[ ${HTTP_CODE} -lt 200 || ${HTTP_CODE} -gt 299 ]] ; then + >&2 cat $OUTPUT_FILE + rm $OUTPUT_FILE + exit 2 +fi +cat | jq -r ".results" +rm $OUTPUT_FILE diff --git a/ak-gatekeeper/outputs.tf b/ak-gatekeeper/outputs.tf new file mode 100644 index 0000000..0139dae --- /dev/null +++ b/ak-gatekeeper/outputs.tf @@ -0,0 +1,6 @@ +output "provider_id" { + value = authentik_provider_proxy.app_proxy_provider.id +} +output "middleware" { + value = kubectl_manifest.middleware.name +} diff --git a/ak-gatekeeper/providers.tf b/ak-gatekeeper/providers.tf new file mode 100644 index 0000000..f3da28e --- /dev/null +++ b/ak-gatekeeper/providers.tf @@ -0,0 +1,25 @@ +terraform { + required_version = ">= 1.0" + required_providers { + kubernetes = { + source = "hashicorp/kubernetes" + version = "~> 2.20.0" + } + kubectl = { + source = "gavinbunney/kubectl" + version = "~> 1.14.0" + } + authentik = { + source = "goauthentik/authentik" + version = "~> 2023.5.0" + } + http = { + source = "hashicorp/http" + version = "~> 3.3.0" + } + restapi = { + source = "Mastercard/restapi" + version = "~> 1.18.0" + } + } +} diff --git a/ak-gatekeeper/variables.tf b/ak-gatekeeper/variables.tf new file mode 100644 index 0000000..d06cbaf --- /dev/null +++ b/ak-gatekeeper/variables.tf @@ -0,0 +1,32 @@ +variable "component" { + type = string +} + +variable "instance" { + type = string +} + +variable "domain" { + type = string +} + +variable "namespace" { + type = string +} + +variable "labels" { + type = map(string) +} + +variable "dns_name" { + type = string +} + +variable "access_token_validity" { + type = string + default = "hours=10" // ;minutes=10 +} + +variable "request_headers" { + type = map(string) +}