From 0db8272df89018b3eb664071c59280e13cf59ca2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Huss?= Date: Sun, 15 Oct 2023 17:29:02 +0200 Subject: [PATCH] fix --- apps/dbgate/application.tf | 39 +++++ apps/dbgate/configs.tf | 62 ++++++-- apps/dbgate/datas.tf | 7 + apps/dbgate/deploy.tf | 10 ++ apps/dbgate/forward.tf | 161 --------------------- apps/dbgate/index.yaml | 288 +++++++++++++++++-------------------- apps/dbgate/ingress.tf | 3 +- apps/dbgate/oauth2.tf | 65 +++++++++ 8 files changed, 308 insertions(+), 327 deletions(-) create mode 100644 apps/dbgate/application.tf delete mode 100644 apps/dbgate/forward.tf create mode 100644 apps/dbgate/oauth2.tf diff --git a/apps/dbgate/application.tf b/apps/dbgate/application.tf new file mode 100644 index 0000000..1d837dd --- /dev/null +++ b/apps/dbgate/application.tf @@ -0,0 +1,39 @@ +locals { + app-name = var.component == var.instance ? var.instance : format("%s-%s", var.component, var.instance) + main-group = format("app-%s", local.app-name) +} +data "authentik_group" "akadmin" { + name = "authentik Admins" +} +resource "authentik_group" "groups" { + name = local.main-group + attributes = jsonencode({"${local.app-name}" = true}) +} + +resource "authentik_application" "prj_app" { + name = "${var.instance}" + slug = "${var.component}-${var.instance}" + group = var.app-group + protocol_provider = authentik_provider_oauth2.oauth2.id + meta_launch_url = format("https://%s.%s", var.sub-domain, var.domain-name) + meta_icon = format("https://%s.%s/%s", var.sub-domain, var.domain-name, "apps/theming/favicon") +} + +resource "authentik_policy_expression" "policy" { + name = local.main-group + expression = <<-EOF + attr = request.user.group_attributes() + return attr['${local.app-name}'] if '${local.app-name}' in attr else False + EOF +} + +resource "authentik_policy_binding" "prj_access_users" { + target = authentik_application.prj_app.uuid + policy = authentik_policy_expression.policy.id + order = 0 +} +resource "authentik_policy_binding" "prj_access_vynil" { + target = authentik_application.prj_app.uuid + group = data.authentik_group.akadmin.id + order = 1 +} diff --git a/apps/dbgate/configs.tf b/apps/dbgate/configs.tf index 3db43b5..96fb028 100644 --- a/apps/dbgate/configs.tf +++ b/apps/dbgate/configs.tf @@ -1,31 +1,54 @@ locals { pg_vars = merge([for pg in var.pg: { - join("_",["LABEL_pg", pg.namespace, pg.name, pg.dbname]) = join("|",["pg", pg.namespace, pg.name, pg.dbname]) + join("_",["LABEL_pg", pg.namespace, pg.name, pg.dbname]) = join(" | ",["pg", pg.namespace, pg.name, pg.dbname]) join("_",["ENGINE_pg", pg.namespace, pg.name, pg.dbname]) = "postgres@dbgate-plugin-postgres" - join("_",["SERVER_pg", pg.namespace, pg.name, pg.dbname]) = join(".",[pg.name, pg.namespace, "svc"]) + join("_",["SERVER_pg", pg.namespace, pg.name, pg.dbname]) = join(".",["${pg.name}-rw", pg.namespace, "svc"]) join("_",["PORT_pg", pg.namespace, pg.name, pg.dbname]) = "5432" + join("_",["DATABASE_pg", pg.namespace, pg.name, pg.dbname]) = pg.dbname join("_",["USER_pg", pg.namespace, pg.name, pg.dbname]) = pg.username }]...) pg_secrets = merge([for pg in var.pg: { - join("_",["PASSWORD_pg", pg.namespace, pg.name, pg.dbname]) = join("|",["pg", pg.namespace, pg.name, pg.dbname]) + join("_",["PASSWORD_pg", pg.namespace, pg.name, pg.dbname]) = data.kubernetes_secret_v1.pgs[index].data[var.pgs[index].secret.key] }]...) pg_conns = [for pg in var.pg: join("_",["pg", pg.namespace, pg.name, pg.dbname])] maria_vars = merge([for m in var.maria: { - join("_",["LABEL_maria", m.namespace, m.name]) = join("|",["maria", m.namespace, m.name]) + join("_",["LABEL_maria", m.namespace, m.name]) = join(" | ",["maria", m.namespace, m.name]) join("_",["ENGINE_maria", m.namespace, m.name]) = "mysql@dbgate-plugin-mysql" - join("_",["SERVER_maria", m.namespace, m.name]) = join(".",[m.name, m.namespace, "svc"]) + join("_",["SERVER_maria", m.namespace, m.name]) = join(".",["${m.name}-svc", m.namespace, "svc"]) join("_",["PORT_maria", m.namespace, m.name]) = "3306" + join("_",["DATABASE_maria", m.namespace, m.name]) = m.dbname join("_",["USER_maria", m.namespace, m.name]) = m.username }]...) - maria_secrets = merge([for m in var.maria: { - join("_",["PASSWORD_maria", m.namespace, m.name]) = join("|",["maria", m.namespace, m.name, m.dbname]) + maria_secrets = merge([for index, m in var.maria: { + join("_",["PASSWORD_maria", m.namespace, m.name]) = "unimplemented" }]...) maria_conns = [for m in var.maria: join("_",["maria", m.namespace, m.name])] - connections = join(",",concat(local.pg_conns, local.maria_conns)) - connection_vars = merge(local.pg_vars, local.maria_vars) - connection_secrets = merge(local.pg_secrets,local.maria_secrets) + mongo_vars = merge([for m in var.mongo: { + join("_",["LABEL_mongo", m.namespace, m.name]) = join(" | ",["mongo", m.namespace, m.name]) + join("_",["ENGINE_mongo", m.namespace, m.name]) = "mongo@dbgate-plugin-mongo" + join("_",["SERVER_mongo", m.namespace, m.name]) = join(".",["${m.name}-svc", m.namespace, "svc"]) + join("_",["PORT_mongo", m.namespace, m.name]) = "3306" + join("_",["DATABASE_mongo", m.namespace, m.name]) = m.dbname + join("_",["USER_mongo", m.namespace, m.name]) = m.username + }]...) + mongo_secrets = merge([for index, m in var.mongo: { + join("_",["PASSWORD_mongo", m.namespace, m.name]) = join(" | ",["mongo", m.namespace, m.name, m.dbname]) + }]...) + mongo_conns = [for m in var.mongo: join("_",["mongo", m.namespace, m.name])] + oauth_config = { + "OAUTH_AUTH" = "https://${data.kubernetes_ingress_v1.authentik.spec[0].rule[0].host}/application/o/authorize/" + "OAUTH_TOKEN" = "https://${data.kubernetes_ingress_v1.authentik.spec[0].rule[0].host}/application/o/token/" + "OAUTH_LOGOUT" = "https://${data.kubernetes_ingress_v1.authentik.spec[0].rule[0].host}/application/o/${var.component}-${var.instance}/end-session/" + "OAUTH_LOGIN_FIELD" = "nickname" + "OAUTH_SCOPE" = "email" + } + + + connections = join(",",concat(local.pg_conns, local.maria_conns, local.mongo_conns)) + connection_vars = merge(local.pg_vars, local.maria_vars, local.mongo_vars) + connection_secrets = merge(local.pg_secrets,local.mongo_secrets) } resource "kubectl_manifest" "dbgate-config" { @@ -36,7 +59,7 @@ resource "kubectl_manifest" "dbgate-config" { name: "${var.component}-${var.instance}" namespace: "${var.namespace}" labels: ${jsonencode(local.common-labels)} - data: ${jsonencode(local.connection_vars)} + data: ${jsonencode(merge(local.oauth_config, local.connection_vars))} EOF } @@ -47,3 +70,20 @@ resource "kubernetes_secret_v1" "dbgate-config-secret" { } data = local.connection_secrets } + + +data "kubernetes_secret_v1" "pgs" { + count = length(var.pg) + metadata { + name = "${var.pgs[count.index].secret.name}" + namespace = "${var.pgs[count.index].namespace}" + } +} + +data "kubernetes_secret_v1" "mongos" { + count = length(var.mongo) + metadata { + name = "${var.pgs[count.index].secret.name}" + namespace = "${var.pgs[count.index].namespace}" + } +} diff --git a/apps/dbgate/datas.tf b/apps/dbgate/datas.tf index ac5f6fe..f084bc2 100644 --- a/apps/dbgate/datas.tf +++ b/apps/dbgate/datas.tf @@ -17,6 +17,13 @@ data "kubernetes_secret_v1" "authentik" { } } +data "kubernetes_ingress_v1" "authentik" { + metadata { + name = "authentik" + namespace = "${var.domain}-auth" + } +} + data "kustomization_overlay" "data" { resources = [] } diff --git a/apps/dbgate/deploy.tf b/apps/dbgate/deploy.tf index 96ec681..74a600f 100644 --- a/apps/dbgate/deploy.tf +++ b/apps/dbgate/deploy.tf @@ -32,6 +32,16 @@ resource "kubectl_manifest" "deploy" { env: - name: CONNECTIONS value: ${local.connections} + - name: OAUTH_CLIENT_ID + valueFrom: + secretKeyRef: + name: "${var.component}-${var.instance}-id" + key: client-id + - name: OAUTH_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: "${var.component}-${var.instance}-secret" + key: client-secret image: "${var.images.dbgate.registry}/${var.images.dbgate.repository}:${var.images.dbgate.tag}" imagePullPolicy: "${var.images.dbgate.pullPolicy}" ports: diff --git a/apps/dbgate/forward.tf b/apps/dbgate/forward.tf deleted file mode 100644 index b57fc7b..0000000 --- a/apps/dbgate/forward.tf +++ /dev/null @@ -1,161 +0,0 @@ -locals { - authentik-token = data.kubernetes_secret_v1.authentik.data["AUTHENTIK_BOOTSTRAP_TOKEN"] - request_headers = { - "Content-Type" = "application/json" - Authorization = "Bearer ${local.authentik-token}" - } - 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 - app-name = var.component == var.instance ? var.instance : format("%s-%s", var.component, var.instance) - app-icon = "logo192.png" - main-group = format("app-%s", local.app-name) - sub-groups = [] - external-url = format("https://%s", local.dns-names[0]) - access-token-validity = "hours=10" // ;minutes=10 - rules-icons = [ for v in local.dns-names : { - "host" = "${v}" - "http" = { - "paths" = [{ - "backend" = { - "service" = local.service - } - "path" = "/${local.app-icon}" - "pathType" = "Prefix" - }] - } - }] -} - -resource "kubectl_manifest" "prj_ingress_icon" { - force_conflicts = true - yaml_body = <<-EOF - apiVersion: "networking.k8s.io/v1" - kind: "Ingress" - metadata: - name: "${var.instance}-icons" - namespace: "${var.namespace}" - labels: ${jsonencode(local.common-labels)} - spec: - ingressClassName: "${var.ingress-class}" - rules: ${jsonencode(local.rules-icons)} - tls: - - hosts: ${jsonencode(local.dns-names)} - secretName: "${var.instance}-cert" - EOF -} - -data "authentik_flow" "default-authorization-flow" { - depends_on = [authentik_group.prj_users] - slug = "default-provider-authorization-implicit-consent" -} - -resource "authentik_provider_proxy" "prj_forward" { - name = local.app-name - external_host = local.external-url - authorization_flow = data.authentik_flow.default-authorization-flow.id - mode = "forward_single" - access_token_validity = local.access-token-validity -} - - -resource "authentik_application" "prj_application" { - name = local.app-name - slug = "${var.component}-${var.instance}" - group = var.app-group - protocol_provider = authentik_provider_proxy.prj_forward.id - meta_launch_url = local.external-url - meta_icon = format("%s/%s", local.external-url, local.app-icon) -} - -resource "authentik_group" "prj_users" { - name = local.main-group - attributes = jsonencode({"${local.app-name}" = true}) -} - -resource "authentik_group" "subgroup" { - count = length(local.sub-groups) - name = format("%s-%s", local.app-name, local.sub-groups[count.index]) - parent = authentik_group.prj_users.id -} - -resource "authentik_policy_expression" "policy" { - name = local.main-group - expression = <<-EOF - attr = request.user.group_attributes() - return attr['${local.app-name}'] if '${local.app-name}' in attr else False - EOF -} - -data "authentik_group" "vynil-admin" { - depends_on = [authentik_group.prj_users] # fake dependency so it is not evaluated at plan stage - name = "vynil-forward-admins" -} - -resource "authentik_policy_binding" "prj_access_users" { - target = authentik_application.prj_application.uuid - policy = authentik_policy_expression.policy.id - order = 0 -} -resource "authentik_policy_binding" "prj_access_vynil" { - target = authentik_application.prj_application.uuid - group = data.authentik_group.vynil-admin.id - order = 1 -} - -data "http" "get_forward_outpost" { - depends_on = [authentik_provider_proxy.prj_forward] - url = "http://authentik.${var.domain}-auth.svc/api/v3/outposts/instances/?name__iexact=forward" - method = "GET" - request_headers = local.request_headers - lifecycle { - postcondition { - condition = contains([200], self.status_code) - error_message = "Status code invalid" - } - } -} - -provider "restapi" { - uri = "http://authentik.${var.domain}-auth.svc/api/v3/" - headers = local.request_headers - create_method = "PATCH" - update_method = "PATCH" - destroy_method = "PATCH" - write_returns_object = true - id_attribute = "name" -} - -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.prj_forward.id) ? local.forward-outpost-providers : concat(local.forward-outpost-providers, [authentik_provider_proxy.prj_forward.id]) - }) -} - -resource "kubectl_manifest" "prj_middleware" { - yaml_body = <<-EOF - apiVersion: traefik.containo.us/v1alpha1 - kind: Middleware - metadata: - name: "forward-${local.app-name}" - namespace: "${var.namespace}" - labels: ${jsonencode(local.common-labels)} - spec: - forwardAuth: - address: http://ak-outpost-forward.${var.domain}-auth.svc:9000/outpost.goauthentik.io/auth/traefik - trustForwardHeader: true - authResponseHeaders: - - X-authentik-username - # - X-authentik-groups - # - X-authentik-email - # - 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 -} diff --git a/apps/dbgate/index.yaml b/apps/dbgate/index.yaml index 287868d..34364f3 100644 --- a/apps/dbgate/index.yaml +++ b/apps/dbgate/index.yaml @@ -6,165 +6,11 @@ metadata: name: dbgate description: null options: - pg: - default: [] - examples: - - [] - items: - properties: - dbname: - default: '' - type: string - name: - default: '' - type: string - namespace: - default: '' - type: string - secret: - properties: - key: - default: '' - type: string - name: - default: '' - type: string - type: object - username: - default: '' - type: string - type: object - type: array - ingress-class: - default: traefik - examples: - - traefik - type: string - mongo: - default: [] - examples: - - [] - items: - properties: - dbname: - default: '' - type: string - name: - default: '' - type: string - namespace: - default: '' - type: string - secret: - properties: - key: - default: '' - type: string - name: - default: '' - type: string - type: object - username: - default: '' - type: string - type: object - type: array sub-domain: default: dbgate examples: - dbgate type: string - domain-name: - default: your_company.com - examples: - - your_company.com - type: string - domain: - default: your-company - examples: - - your-company - type: string - issuer: - default: letsencrypt-prod - examples: - - letsencrypt-prod - type: string - maria: - default: [] - examples: - - [] - items: - properties: - name: - default: '' - type: string - namespace: - default: '' - type: string - secret: - properties: - key: - default: '' - type: string - name: - default: '' - type: string - type: object - username: - default: '' - type: string - type: object - type: array - storage: - default: - accessMode: ReadWriteOnce - size: 1Gi - type: Filesystem - examples: - - accessMode: ReadWriteOnce - size: 1Gi - type: Filesystem - properties: - accessMode: - default: ReadWriteOnce - enum: - - ReadWriteOnce - - ReadOnlyMany - - ReadWriteMany - type: string - size: - default: 1Gi - type: string - type: - default: Filesystem - enum: - - Filesystem - - Block - type: string - type: object - redis: - default: [] - examples: - - [] - items: - properties: - name: - default: '' - type: string - namespace: - default: '' - type: string - secret: - properties: - key: - default: '' - type: string - name: - default: '' - type: string - type: object - type: object - type: array images: default: dbgate: @@ -204,11 +50,145 @@ options: type: string type: object type: object + domain-name: + default: your_company.com + examples: + - your_company.com + type: string + storage: + default: + accessMode: ReadWriteOnce + size: 1Gi + type: Filesystem + examples: + - accessMode: ReadWriteOnce + size: 1Gi + type: Filesystem + properties: + accessMode: + default: ReadWriteOnce + enum: + - ReadWriteOnce + - ReadOnlyMany + - ReadWriteMany + type: string + size: + default: 1Gi + type: string + type: + default: Filesystem + enum: + - Filesystem + - Block + type: string + type: object + maria: + default: [] + examples: + - [] + items: + properties: + dbname: + default: '' + type: string + name: + default: '' + type: string + namespace: + default: '' + type: string + secret: + properties: + key: + default: '' + type: string + name: + default: '' + type: string + type: object + username: + default: '' + type: string + type: object + type: array + ingress-class: + default: traefik + examples: + - traefik + type: string + domain: + default: your-company + examples: + - your-company + type: string + mongo: + default: [] + examples: + - [] + items: + properties: + dbname: + default: '' + type: string + name: + default: '' + type: string + namespace: + default: '' + type: string + secret: + properties: + key: + default: '' + type: string + name: + default: '' + type: string + type: object + username: + default: '' + type: string + type: object + type: array app-group: default: dev examples: - dev type: string + issuer: + default: letsencrypt-prod + examples: + - letsencrypt-prod + type: string + pg: + default: [] + examples: + - [] + items: + properties: + dbname: + default: '' + type: string + name: + default: '' + type: string + namespace: + default: '' + type: string + secret: + properties: + key: + default: '' + type: string + name: + default: '' + type: string + type: object + username: + default: '' + type: string + type: object + type: array dependencies: - dist: null category: share diff --git a/apps/dbgate/ingress.tf b/apps/dbgate/ingress.tf index 19ecf4f..9aea95d 100644 --- a/apps/dbgate/ingress.tf +++ b/apps/dbgate/ingress.tf @@ -1,6 +1,7 @@ locals { - dns-names = ["${var.sub-domain}.${var.domain-name}"] + dns-name = "${var.sub-domain}.${var.domain-name}" + dns-names = [local.dns-name] middlewares = ["${var.instance}-https", "forward-${local.app-name}"] service = { "name" = "${var.component}-${var.instance}" diff --git a/apps/dbgate/oauth2.tf b/apps/dbgate/oauth2.tf new file mode 100644 index 0000000..5173d82 --- /dev/null +++ b/apps/dbgate/oauth2.tf @@ -0,0 +1,65 @@ +resource "kubectl_manifest" "oauth2-secret" { + ignore_fields = ["metadata.annotations"] + yaml_body = <<-EOF + apiVersion: "secretgenerator.mittwald.de/v1alpha1" + kind: "StringSecret" + metadata: + name: "${var.component}-${var.instance}-id" + namespace: "${var.namespace}" + labels: ${jsonencode(local.common-labels)} + spec: + forceRegenerate: false + fields: + - fieldName: "client-id" + length: "32" + EOF +} +data "kubernetes_secret_v1" "oauth2-client-id" { + depends_on = [kubectl_manifest.oauth2-secret] + metadata { + name = kubectl_manifest.oauth2-secret.name + namespace = var.namespace + } +} + +data "authentik_certificate_key_pair" "ca" { + name = "authentik Self-signed Certificate" +} + +data "authentik_scope_mapping" "oauth2" { + managed_list = [ + "goauthentik.io/providers/oauth2/scope-email", + "goauthentik.io/providers/oauth2/scope-openid", + "goauthentik.io/providers/oauth2/scope-profile" + ] +} +data "authentik_flow" "default-authorization-flow" { + slug = "default-provider-authorization-implicit-consent" +} +data "authentik_flow" "default-authentication-flow" { + slug = "default-authentication-flow" +} + +resource "authentik_provider_oauth2" "oauth2" { + name = "${var.component}-${var.instance}" + client_id = "${data.kubernetes_secret_v1.oauth2-client-id.data["client-id"]}" + authentication_flow = data.authentik_flow.default-authentication-flow.id + authorization_flow = data.authentik_flow.default-authorization-flow.id + client_type = "confidential" + sub_mode = "user_username" + signing_key = data.authentik_certificate_key_pair.ca.id + property_mappings = data.authentik_scope_mapping.oauth2.ids + redirect_uris = [ + "https://${local.dns-name}/" + ] +} + +resource "kubernetes_secret_v1" "oauth2-client-secret" { + metadata { + name = "${var.component}-${var.instance}-secret" + namespace = var.namespace + } + data = { + client-secret = authentik_provider_oauth2.oauth2.client_secret + } +}