From 2ea73522154cd417b55a14ba37c1f2c3610bb489 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Huss?= Date: Sat, 14 Oct 2023 20:41:17 +0200 Subject: [PATCH] fix --- apps/okd/datas.tf | 22 ++++ apps/okd/deploy.tf | 58 ++++++++++ apps/okd/forward.tf | 161 ++++++++++++++++++++++++++ apps/okd/index.yaml | 107 ++++++++++++++++++ apps/okd/ingress.tf | 76 +++++++++++++ apps/okd/rbac.tf | 68 +++++++++++ apps/okd/svc.tf | 18 +++ meta/domain-infra/apps.tf | 26 ++++- meta/domain-infra/index.yaml | 211 ++++++++++++++++++----------------- 9 files changed, 643 insertions(+), 104 deletions(-) create mode 100644 apps/okd/datas.tf create mode 100644 apps/okd/deploy.tf create mode 100644 apps/okd/forward.tf create mode 100644 apps/okd/index.yaml create mode 100644 apps/okd/ingress.tf create mode 100644 apps/okd/rbac.tf create mode 100644 apps/okd/svc.tf diff --git a/apps/okd/datas.tf b/apps/okd/datas.tf new file mode 100644 index 0000000..ac5f6fe --- /dev/null +++ b/apps/okd/datas.tf @@ -0,0 +1,22 @@ +locals { + common-labels = { + "vynil.solidite.fr/owner-name" = var.instance + "vynil.solidite.fr/owner-namespace" = var.namespace + "vynil.solidite.fr/owner-category" = var.category + "vynil.solidite.fr/owner-component" = var.component + "app.kubernetes.io/managed-by" = "vynil" + "app.kubernetes.io/name" = var.component + "app.kubernetes.io/instance" = var.instance + } +} + +data "kubernetes_secret_v1" "authentik" { + metadata { + name = "authentik" + namespace = "${var.domain}-auth" + } +} + +data "kustomization_overlay" "data" { + resources = [] +} diff --git a/apps/okd/deploy.tf b/apps/okd/deploy.tf new file mode 100644 index 0000000..962686a --- /dev/null +++ b/apps/okd/deploy.tf @@ -0,0 +1,58 @@ +resource "kubectl_manifest" "deploy" { + yaml_body = <<-EOF + apiVersion: apps/v1 + kind: Deployment + metadata: + name: "${var.component}-${var.instance}" + namespace: "${var.namespace}" + labels: ${jsonencode(local.common-labels)} + spec: + replicas: 1 + selector: + matchLabels: ${jsonencode(local.common-labels)} + template: + metadata: + labels: ${jsonencode(local.common-labels)} + spec: + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + containers: + - name: okd + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + env: + - name: BRIDGE_USER_AUTH + value: disabled + image: "${var.images.okd.registry}/${var.images.okd.repository}:${var.images.okd.tag}" + imagePullPolicy: "${var.images.okd.pullPolicy}" + ports: + - containerPort: 9000 + name: http + protocol: TCP + livenessProbe: + failureThreshold: 3 + httpGet: + path: / + port: http + scheme: HTTP + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + readinessProbe: + failureThreshold: 3 + httpGet: + path: / + port: http + scheme: HTTP + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + restartPolicy: Always + serviceAccount: okd + serviceAccountName: okd + EOF +} diff --git a/apps/okd/forward.tf b/apps/okd/forward.tf new file mode 100644 index 0000000..7d1c0e7 --- /dev/null +++ b/apps/okd/forward.tf @@ -0,0 +1,161 @@ +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 = "_static/src/browser/media/favicon-dark-support.svg" + 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/okd/index.yaml b/apps/okd/index.yaml new file mode 100644 index 0000000..e337ef1 --- /dev/null +++ b/apps/okd/index.yaml @@ -0,0 +1,107 @@ +--- +apiVersion: vinyl.solidite.fr/v1beta1 +kind: Component +category: apps +metadata: + name: okd + description: null +options: + domain-name: + default: your_company.com + examples: + - your_company.com + type: string + issuer: + default: letsencrypt-prod + examples: + - letsencrypt-prod + type: string + namespaces: + default: [] + items: + type: string + type: array + images: + default: + okd: + pullPolicy: IfNotPresent + registry: quay.io + repository: openshift/origin-console + tag: 4.10.0 + examples: + - okd: + pullPolicy: IfNotPresent + registry: quay.io + repository: openshift/origin-console + tag: 4.10.0 + properties: + dbgate: + default: null + properties: + pullPolicy: + enum: + - Always + - Never + - IfNotPresent + okd: + default: + pullPolicy: IfNotPresent + registry: quay.io + repository: openshift/origin-console + tag: 4.10.0 + properties: + pullPolicy: + default: IfNotPresent + type: string + registry: + default: quay.io + type: string + repository: + default: openshift/origin-console + type: string + tag: + default: 4.10.0 + type: string + type: object + type: object + cluster-admin: + default: true + examples: + - true + type: boolean + sub-domain: + default: okd + examples: + - okd + type: string + app-group: + default: infra + examples: + - infra + type: string + ingress-class: + default: traefik + examples: + - traefik + type: string + domain: + default: your-company + examples: + - your-company + type: string +dependencies: +- dist: null + category: share + component: authentik-forward +- dist: null + category: core + component: secret-generator +providers: + kubernetes: true + authentik: true + kubectl: true + postgresql: null + restapi: true + http: true + gitea: null +tfaddtype: null diff --git a/apps/okd/ingress.tf b/apps/okd/ingress.tf new file mode 100644 index 0000000..19ecf4f --- /dev/null +++ b/apps/okd/ingress.tf @@ -0,0 +1,76 @@ + +locals { + dns-names = ["${var.sub-domain}.${var.domain-name}"] + middlewares = ["${var.instance}-https", "forward-${local.app-name}"] + service = { + "name" = "${var.component}-${var.instance}" + "port" = { + "number" = 80 + } + } + rules = [ for v in local.dns-names : { + "host" = "${v}" + "http" = { + "paths" = [{ + "backend" = { + "service" = local.service + } + "path" = "/" + "pathType" = "Prefix" + }] + } + }] +} + +resource "kubectl_manifest" "prj_certificate" { + yaml_body = <<-EOF + apiVersion: "cert-manager.io/v1" + kind: "Certificate" + metadata: + name: "${var.instance}" + namespace: "${var.namespace}" + labels: ${jsonencode(local.common-labels)} + spec: + secretName: "${var.instance}-cert" + dnsNames: ${jsonencode(local.dns-names)} + issuerRef: + name: "${var.issuer}" + kind: "ClusterIssuer" + group: "cert-manager.io" + EOF +} + +resource "kubectl_manifest" "prj_https_redirect" { + yaml_body = <<-EOF + apiVersion: "traefik.containo.us/v1alpha1" + kind: "Middleware" + metadata: + name: "${var.instance}-https" + namespace: "${var.namespace}" + labels: ${jsonencode(local.common-labels)} + spec: + redirectScheme: + scheme: "https" + permanent: true + EOF +} + +resource "kubectl_manifest" "prj_ingress" { + force_conflicts = true + yaml_body = <<-EOF + apiVersion: "networking.k8s.io/v1" + kind: "Ingress" + metadata: + name: "${var.instance}" + namespace: "${var.namespace}" + labels: ${jsonencode(local.common-labels)} + annotations: + "traefik.ingress.kubernetes.io/router.middlewares": "${join(",", [for m in local.middlewares : format("%s-%s@kubernetescrd", var.namespace, m)])}" + spec: + ingressClassName: "${var.ingress-class}" + rules: ${jsonencode(local.rules)} + tls: + - hosts: ${jsonencode(local.dns-names)} + secretName: "${var.instance}-cert" + EOF +} diff --git a/apps/okd/rbac.tf b/apps/okd/rbac.tf new file mode 100644 index 0000000..313d988 --- /dev/null +++ b/apps/okd/rbac.tf @@ -0,0 +1,68 @@ +locals { + sorted-namespaces = reverse(distinct(sort(var.namespaces))) +} +resource "kubectl_manifest" "okd_sa" { + yaml_body = <<-EOF + apiVersion: v1 + kind: ServiceAccount + metadata: + name: "${var.component}-${var.instance}" + namespace: "${var.namespace}" + labels: ${jsonencode(local.common-labels)} + EOF +} + +resource "kubectl_manifest" "okd_crb" { + count = var.cluster-admin ? 1 : 0 + yaml_body = <<-EOF + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRoleBinding + metadata: + name: "${var.namespace}-${var.component}-${var.instance}" + labels: ${jsonencode(local.common-labels)} + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin + subjects: + - kind: ServiceAccount + name: "${var.component}-${var.instance}" + namespace: "${var.namespace}" + EOF +} + +resource "kubectl_manifest" "okd_roles" { + count = length(local.sorted-namespaces) + yaml_body = <<-EOF + apiVersion: rbac.authorization.k8s.io/v1 + kind: Role + metadata: + name: "${var.namespace}-${var.component}-${var.instance}" + namespace: "${local.sorted-namespaces[count.index]}" + labels: ${jsonencode(local.common-labels)} + rules: + - apiGroups: ["*"] + resources: ["*"] + verbs: ["*"] + EOF +} + +resource "kubectl_manifest" "okd_role_bindings" { + count = length(local.sorted-namespaces) + yaml_body = <<-EOF + apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding + metadata: + name: "${var.namespace}-${var.component}-${var.instance}" + namespace: "${local.sorted-namespaces[count.index]}" + labels: ${jsonencode(local.common-labels)} + subjects: + - kind: ServiceAccount + name: "${var.component}-${var.instance}" + namespace: "${var.namespace}" + roleRef: + kind: Role + name: "${var.namespace}-${var.component}-${var.instance}" + apiGroup: rbac.authorization.k8s.io + EOF +} diff --git a/apps/okd/svc.tf b/apps/okd/svc.tf new file mode 100644 index 0000000..c73fa48 --- /dev/null +++ b/apps/okd/svc.tf @@ -0,0 +1,18 @@ +resource "kubectl_manifest" "service" { + yaml_body = <<-EOF + apiVersion: v1 + kind: Service + metadata: + name: "${var.component}-${var.instance}" + namespace: "${var.namespace}" + labels: ${jsonencode(local.common-labels)} + spec: + type: ClusterIP + ports: + - name: http + port: 80 + protocol: TCP + targetPort: http + selector: ${jsonencode(local.common-labels)} + EOF +} diff --git a/meta/domain-infra/apps.tf b/meta/domain-infra/apps.tf index efca1b5..5b2ffa8 100644 --- a/meta/domain-infra/apps.tf +++ b/meta/domain-infra/apps.tf @@ -8,7 +8,7 @@ locals { } global = { "domain" = var.namespace - "domain-name" = var.domain-name + "domain-name" = "admin.${var.domain-name}" "issuer" = var.issuer "ingress-class" = var.ingress-class "backups" = var.backups @@ -17,10 +17,11 @@ locals { traefik = { for k, v in var.traefik : k => v if k!="enable" } dns = { for k, v in var.dns : k => v if k!="enable" } api = { for k, v in var.api : k => v if k!="enable" } + okd = { for k, v in var.okd : k => v if k!="enable" } } resource "kubernetes_namespace_v1" "infra-ns" { - count = ( var.dns.enable )? 1 : 0 + count = ( var.dns.enable || var.okd.enable )? 1 : 0 metadata { annotations = local.annotations labels = merge(local.common-labels, local.annotations) @@ -48,7 +49,6 @@ resource "kubectl_manifest" "dns" { resource "kubectl_manifest" "traefik" { count = var.traefik.enable ? 1 : 0 - depends_on = [kubernetes_namespace_v1.infra-ns] yaml_body = <<-EOF apiVersion: "vynil.solidite.fr/v1" kind: "Install" @@ -63,9 +63,9 @@ resource "kubectl_manifest" "traefik" { options: ${jsonencode(merge(local.global, local.traefik))} EOF } + resource "kubectl_manifest" "k8s_api" { count = var.traefik.enable ? 1 : 0 - depends_on = [kubernetes_namespace_v1.infra-ns] yaml_body = <<-EOF apiVersion: "vynil.solidite.fr/v1" kind: "Install" @@ -80,3 +80,21 @@ resource "kubectl_manifest" "k8s_api" { options: ${jsonencode(merge(local.global, local.api))} EOF } + +resource "kubectl_manifest" "okd" { + count = var.okd.enable ? 1 : 0 + depends_on = [kubernetes_namespace_v1.infra-ns] + yaml_body = <<-EOF + apiVersion: "vynil.solidite.fr/v1" + kind: "Install" + metadata: + name: "okd" + namespace: "${kubernetes_namespace_v1.infra-ns[0].metadata[0].name}" + labels: ${jsonencode(local.common-labels)} + spec: + distrib: "${var.distributions.domain}" + category: "apps" + component: "okd" + options: ${jsonencode(merge(local.global, local.okd))} + EOF +} diff --git a/meta/domain-infra/index.yaml b/meta/domain-infra/index.yaml index 795824d..208920a 100644 --- a/meta/domain-infra/index.yaml +++ b/meta/domain-infra/index.yaml @@ -6,6 +6,116 @@ metadata: name: domain-infra description: null options: + ingress-class: + default: traefik + examples: + - traefik + type: string + app-group: + default: infra + examples: + - infra + type: string + okd: + default: + enable: false + examples: + - enable: false + properties: + enable: + default: false + type: boolean + type: object + domain-name: + default: your_company.com + examples: + - your_company.com + type: string + traefik: + default: + enable: false + namespace: traefik + examples: + - enable: false + namespace: traefik + properties: + enable: + default: false + type: boolean + namespace: + default: traefik + type: string + type: object + api: + default: + enable: false + examples: + - enable: false + properties: + enable: + default: false + type: boolean + type: object + storage-classes: + default: + BlockReadWriteMany: '' + BlockReadWriteOnce: '' + FilesystemReadWriteMany: '' + FilesystemReadWriteOnce: '' + examples: + - BlockReadWriteMany: '' + BlockReadWriteOnce: '' + FilesystemReadWriteMany: '' + FilesystemReadWriteOnce: '' + properties: + BlockReadWriteMany: + default: '' + type: string + BlockReadWriteOnce: + default: '' + type: string + FilesystemReadWriteMany: + default: '' + type: string + FilesystemReadWriteOnce: + default: '' + type: string + type: object + domain: + default: your-company + examples: + - your-company + type: string + issuer: + default: letsencrypt-prod + examples: + - letsencrypt-prod + type: string + dns: + default: + enable: false + examples: + - enable: false + properties: + enable: + default: false + type: boolean + type: object + distributions: + default: + core: core + domain: domain + examples: + - core: core + domain: domain + properties: + core: + default: core + type: string + domain: + default: domain + type: string + type: object backups: default: enable: false @@ -36,106 +146,6 @@ options: default: backup-settings type: string type: object - app-group: - default: infra - examples: - - infra - type: string - traefik: - default: - enable: false - namespace: traefik - examples: - - enable: false - namespace: traefik - properties: - enable: - default: false - type: boolean - namespace: - default: traefik - type: string - type: object - dns: - default: - enable: false - examples: - - enable: false - properties: - enable: - default: false - type: boolean - type: object - api: - default: - enable: false - examples: - - enable: false - properties: - enable: - default: false - type: boolean - type: object - distributions: - default: - core: core - domain: domain - examples: - - core: core - domain: domain - properties: - core: - default: core - type: string - domain: - default: domain - type: string - type: object - domain-name: - default: your_company.com - examples: - - your_company.com - type: string - issuer: - default: letsencrypt-prod - examples: - - letsencrypt-prod - type: string - domain: - default: your-company - examples: - - your-company - type: string - ingress-class: - default: traefik - examples: - - traefik - type: string - storage-classes: - default: - BlockReadWriteMany: '' - BlockReadWriteOnce: '' - FilesystemReadWriteMany: '' - FilesystemReadWriteOnce: '' - examples: - - BlockReadWriteMany: '' - BlockReadWriteOnce: '' - FilesystemReadWriteMany: '' - FilesystemReadWriteOnce: '' - properties: - BlockReadWriteMany: - default: '' - type: string - BlockReadWriteOnce: - default: '' - type: string - FilesystemReadWriteMany: - default: '' - type: string - FilesystemReadWriteOnce: - default: '' - type: string - type: object dependencies: [] providers: kubernetes: true @@ -144,4 +154,5 @@ providers: postgresql: null restapi: null http: null + gitea: null tfaddtype: null