From 3f5ae766e9c2a0f03ec36ce30365802967411c1a Mon Sep 17 00:00:00 2001 From: Vadim Tsarfin <139264463+vtsarfin-cg@users.noreply.github.com> Date: Fri, 1 Dec 2023 22:34:08 +0200 Subject: [PATCH] feat: Backstage and oauth2 initial add (#29) * feat: Backstage and oauth2 initial add * feat: backstage configuration templating * fix: backstage auth section * create cookie secret for backstage oauth2 * feat: tf upgrade + set tf version via templating --------- Co-authored-by: VADIM TSARFIN Co-authored-by: Alex Ulyanov Co-authored-by: Serg Shalavin --- .../core-services/170-backstage.yaml | 28 +++ .../core-services/55-oauth2-proxy.yaml | 28 +++ .../components/backstage/application.yaml | 163 ++++++++++++++++++ .../oauth2-proxy/externalsecrets.yaml | 27 +++ .../oauth2-proxy/kustomization.yaml | 11 ++ .../oauth2-proxy/oauth2-proxy-ing.yaml | 24 +++ .../components/oauth2-proxy/oauth2-proxy.yaml | 64 +++++++ .../modules/secrets_vault/oidc-clients.tf | 18 +- .../modules/secrets_vault/secrets.tf | 17 ++ platform/tpl_README.md | 4 +- tools/cli/commands/setup.py | 2 + 11 files changed, 383 insertions(+), 3 deletions(-) create mode 100644 platform/gitops-pipelines/delivery/clusters/cc-cluster/core-services/170-backstage.yaml create mode 100644 platform/gitops-pipelines/delivery/clusters/cc-cluster/core-services/55-oauth2-proxy.yaml create mode 100644 platform/gitops-pipelines/delivery/clusters/cc-cluster/core-services/components/backstage/application.yaml create mode 100644 platform/gitops-pipelines/delivery/clusters/cc-cluster/core-services/components/oauth2-proxy/externalsecrets.yaml create mode 100644 platform/gitops-pipelines/delivery/clusters/cc-cluster/core-services/components/oauth2-proxy/kustomization.yaml create mode 100644 platform/gitops-pipelines/delivery/clusters/cc-cluster/core-services/components/oauth2-proxy/oauth2-proxy-ing.yaml create mode 100644 platform/gitops-pipelines/delivery/clusters/cc-cluster/core-services/components/oauth2-proxy/oauth2-proxy.yaml diff --git a/platform/gitops-pipelines/delivery/clusters/cc-cluster/core-services/170-backstage.yaml b/platform/gitops-pipelines/delivery/clusters/cc-cluster/core-services/170-backstage.yaml new file mode 100644 index 00000000..37843761 --- /dev/null +++ b/platform/gitops-pipelines/delivery/clusters/cc-cluster/core-services/170-backstage.yaml @@ -0,0 +1,28 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: backstage-components + namespace: argocd + annotations: + argocd.argoproj.io/sync-wave: '170' +spec: + project: core + source: + repoURL: + path: gitops-pipelines/delivery/clusters/cc-cluster/core-services/components/backstage + targetRevision: HEAD + destination: + server: https://kubernetes.default.svc + namespace: backstage + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true + retry: + limit: 10 + backoff: + duration: 30s + maxDuration: 15m0s + factor: 2 diff --git a/platform/gitops-pipelines/delivery/clusters/cc-cluster/core-services/55-oauth2-proxy.yaml b/platform/gitops-pipelines/delivery/clusters/cc-cluster/core-services/55-oauth2-proxy.yaml new file mode 100644 index 00000000..c7b0f7a0 --- /dev/null +++ b/platform/gitops-pipelines/delivery/clusters/cc-cluster/core-services/55-oauth2-proxy.yaml @@ -0,0 +1,28 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: oauth2-proxy + namespace: argocd + annotations: + argocd.argoproj.io/sync-wave: '55' +spec: + project: core + source: + repoURL: + path: gitops-pipelines/delivery/clusters/cc-cluster/core-services/components/oauth2-proxy + targetRevision: HEAD + destination: + server: https://kubernetes.default.svc + namespace: oauth2-proxy + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true + retry: + limit: 10 + backoff: + duration: 30s + maxDuration: 15m0s + factor: 2 diff --git a/platform/gitops-pipelines/delivery/clusters/cc-cluster/core-services/components/backstage/application.yaml b/platform/gitops-pipelines/delivery/clusters/cc-cluster/core-services/components/backstage/application.yaml new file mode 100644 index 00000000..13c280d9 --- /dev/null +++ b/platform/gitops-pipelines/delivery/clusters/cc-cluster/core-services/components/backstage/application.yaml @@ -0,0 +1,163 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: backstage + namespace: argocd + annotations: + argocd.argoproj.io/sync-wave: "170" +spec: + project: core + source: + repoURL: https://backstage.github.io/charts + chart: backstage + targetRevision: 1.6.0 + helm: + values: |- + global: + imageRegistry: "" + imagePullSecrets: [] + kubeVersion: "" + nameOverride: "" + fullnameOverride: "" + clusterDomain: cluster.local + commonLabels: {} + commonAnnotations: {} + extraDeploy: [] + diagnosticMode: + enabled: false + command: + - sleep + args: + - infinity + ingress: + enabled: true + className: "nginx" + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-prod" + nginx.ingress.kubernetes.io/enable-cors: "true" + nginx.ingress.kubernetes.io/cors-allow-origin: "https://" + nginx.ingress.kubernetes.io/auth-url: "https://$host/oauth2/auth" + nginx.ingress.kubernetes.io/auth-signin: "https://$host/oauth2/start?rd=$escaped_request_uri" + host: "" + tls: + enabled: true + secretName: "backstage-tls" + backstage: + replicas: 1 + revisionHistoryLimit: 10 + image: + registry: ghcr.io + repository: backstage/backstage + tag: latest + pullPolicy: Always + pullSecrets: [] + debug: false + containerPorts: + backend: 7007 + command: ["node", "packages/backend"] + args: [] + extraAppConfig: [] + extraContainers: [] + extraEnvVars: [] + extraEnvVarsSecrets: [] + extraVolumeMounts: [] + extraVolumes: [] + initContainers: [] + installDir: /app + resources: {} + readinessProbe: {} + livenessProbe: {} + startupProbe: {} + podSecurityContext: {} + containerSecurityContext: {} + appConfig: + app: + baseUrl: https:// + organization: + name: + backend: + baseUrl: https:// + listen: + port: 7007 + cors: + origin: https:// + methods: [GET, HEAD, PATCH, POST, PUT, DELETE] + credentials: true + database: + client: pg + connection: + host: ${BACKSTAGE_POSTGRESQL_SERVICE_HOST} + port: ${BACKSTAGE_POSTGRESQL_SERVICE_PORT} + user: ${POSTGRES_USER} + password: ${POSTGRES_PASSWORD} + techdocs: + builder: 'local' + publisher: + type: 'local' + generator: + runIn: local + nodeSelector: {} + tolerations: [] + podAnnotations: {} + podLabels: {} + annotations: {} + service: + type: ClusterIP + ports: + backend: 7007 + name: http-backend + targetPort: backend + nodePorts: + backend: "" + sessionAffinity: None + # + clusterIP: "" + # + loadBalancerIP: "" + # + loadBalancerSourceRanges: [] + # + externalTrafficPolicy: Cluster + annotations: {} + extraPorts: [] + networkPolicy: + enabled: false + ingressRules: + namespaceSelector: {} + podSelector: {} + customRules: [] + egressRules: + denyConnectionsToExternal: false + customRules: [] + postgresql: + enabled: true + persistence: + enabled: false + auth: + username: bn_backstage + architecture: standalone + storage: + resourcePolicy: "" + serviceAccount: + create: true + name: "" + labels: {} + annotations: {} + automountServiceAccountToken: true + metrics: + serviceMonitor: + enabled: false + annotations: {} + labels: {} + interval: null + path: /metrics + + destination: + server: https://kubernetes.default.svc + namespace: backstage + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true diff --git a/platform/gitops-pipelines/delivery/clusters/cc-cluster/core-services/components/oauth2-proxy/externalsecrets.yaml b/platform/gitops-pipelines/delivery/clusters/cc-cluster/core-services/components/oauth2-proxy/externalsecrets.yaml new file mode 100644 index 00000000..8a812c58 --- /dev/null +++ b/platform/gitops-pipelines/delivery/clusters/cc-cluster/core-services/components/oauth2-proxy/externalsecrets.yaml @@ -0,0 +1,27 @@ +apiVersion: "external-secrets.io/v1beta1" +kind: ExternalSecret +metadata: + name: oauth2-proxy-secrets + namespace: oauth2-proxy +spec: + target: + name: oauth2-proxy-secrets + secretStoreRef: + kind: ClusterSecretStore + name: vault-kv-secret + refreshInterval: 10s + data: + - remoteRef: + key: oidc/oauth2_backstage + property: client_id + secretKey: client-id + - remoteRef: + key: oidc/oauth2_backstage + property: client_secret + secretKey: client-secret + - remoteRef: + key: oauth2/cookie + property: backstage_cookie_secret + secretKey: cookie-secret + + diff --git a/platform/gitops-pipelines/delivery/clusters/cc-cluster/core-services/components/oauth2-proxy/kustomization.yaml b/platform/gitops-pipelines/delivery/clusters/cc-cluster/core-services/components/oauth2-proxy/kustomization.yaml new file mode 100644 index 00000000..af0937ae --- /dev/null +++ b/platform/gitops-pipelines/delivery/clusters/cc-cluster/core-services/components/oauth2-proxy/kustomization.yaml @@ -0,0 +1,11 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: oauth2-proxy + +resources: +- oauth2-proxy.yaml +- oauth2-proxy-ing.yaml +- externalsecrets.yaml + +generatorOptions: + disableNameSuffixHash: true diff --git a/platform/gitops-pipelines/delivery/clusters/cc-cluster/core-services/components/oauth2-proxy/oauth2-proxy-ing.yaml b/platform/gitops-pipelines/delivery/clusters/cc-cluster/core-services/components/oauth2-proxy/oauth2-proxy-ing.yaml new file mode 100644 index 00000000..7e5a07f5 --- /dev/null +++ b/platform/gitops-pipelines/delivery/clusters/cc-cluster/core-services/components/oauth2-proxy/oauth2-proxy-ing.yaml @@ -0,0 +1,24 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod + name: oauth2-proxy + namespace: oauth2-proxy +spec: + ingressClassName: nginx + rules: + - host: + http: + paths: + - path: /oauth2 + pathType: Prefix + backend: + service: + name: oauth2-proxy + port: + number: 4180 + tls: + - hosts: + - + secretName: backstage-tls diff --git a/platform/gitops-pipelines/delivery/clusters/cc-cluster/core-services/components/oauth2-proxy/oauth2-proxy.yaml b/platform/gitops-pipelines/delivery/clusters/cc-cluster/core-services/components/oauth2-proxy/oauth2-proxy.yaml new file mode 100644 index 00000000..5bacc190 --- /dev/null +++ b/platform/gitops-pipelines/delivery/clusters/cc-cluster/core-services/components/oauth2-proxy/oauth2-proxy.yaml @@ -0,0 +1,64 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + k8s-app: oauth2-proxy + name: oauth2-proxy + namespace: oauth2-proxy +spec: + replicas: 1 + selector: + matchLabels: + k8s-app: oauth2-proxy + template: + metadata: + labels: + k8s-app: oauth2-proxy + spec: + containers: + - args: + - --provider=oidc + - --email-domain=* + - --upstream="https://" + - --http-address=0.0.0.0:4180 + - --oidc-issuer-url=https:// + env: + - name: OAUTH2_PROXY_CLIENT_ID + valueFrom: + secretKeyRef: + name: oauth2-proxy-secrets + key: client-id + - name: OAUTH2_PROXY_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: oauth2-proxy-secrets + key: client-secret + - name: OAUTH2_PROXY_COOKIE_SECRET + valueFrom: + secretKeyRef: + name: oauth2-proxy-secrets + key: cookie-secret + image: quay.io/oauth2-proxy/oauth2-proxy:latest + imagePullPolicy: Always + name: oauth2-proxy + ports: + - containerPort: 4180 + protocol: TCP + +--- + +apiVersion: v1 +kind: Service +metadata: + labels: + k8s-app: oauth2-proxy + name: oauth2-proxy + namespace: oauth2-proxy +spec: + ports: + - name: http + port: 4180 + protocol: TCP + targetPort: 4180 + selector: + k8s-app: oauth2-proxy diff --git a/platform/terraform/modules/secrets_vault/oidc-clients.tf b/platform/terraform/modules/secrets_vault/oidc-clients.tf index 7122b514..601ec1ab 100644 --- a/platform/terraform/modules/secrets_vault/oidc-clients.tf +++ b/platform/terraform/modules/secrets_vault/oidc-clients.tf @@ -76,4 +76,20 @@ module "sonarqube" { "https:///oauth2/callback/oidc", ] secret_mount_path = "secret" -} \ No newline at end of file +} + +module "oauth2_backstage" { + source = "./oidc-client" + + depends_on = [ + vault_identity_oidc_provider.cgdevx + ] + + app_name = "oauth2_backstage" + identity_group_ids = [vault_identity_group.admins.id, vault_identity_group.developers.id] + oidc_provider_key_name = vault_identity_oidc_key.key.name + redirect_uris = [ + "https:///oauth2/callback", + ] + secret_mount_path = "secret" +} diff --git a/platform/terraform/modules/secrets_vault/secrets.tf b/platform/terraform/modules/secrets_vault/secrets.tf index 11f1b486..e2da7645 100644 --- a/platform/terraform/modules/secrets_vault/secrets.tf +++ b/platform/terraform/modules/secrets_vault/secrets.tf @@ -202,3 +202,20 @@ resource "vault_generic_secret" "sonarqube_admin_secret" { depends_on = [vault_mount.secret] } + +resource "random_password" "oauth2_backstage_cookie_password" { + length = 32 + override_special = "-_" +} + +resource "vault_generic_secret" "oauth2_cookie_secret" { + path = "secret/oauth2/cookie" + + data_json = jsonencode( + { + backstage_cookie_secret = random_password.oauth2_backstage_cookie_password.result, + } + ) + + depends_on = [vault_mount.secret] +} diff --git a/platform/tpl_README.md b/platform/tpl_README.md index 31faf5de..8db080af 100644 --- a/platform/tpl_README.md +++ b/platform/tpl_README.md @@ -18,8 +18,8 @@ The CG DevX services: | Atlantis | atlantis | Terraform Workflow Automation | https:// | | Harbor | harbor | Image & Helm Chart Registry | https:// | | Grafana | monitoring | Observability | https:// | -| SonarQube | sonarqube | Code Quality | https:// | - +| SonarQube | sonarqube | Code Quality | https:// | +| Backstage | backstage | Portal | https:// | --- ## GitOps registry diff --git a/tools/cli/commands/setup.py b/tools/cli/commands/setup.py index f7d2135f..eb57f7a5 100644 --- a/tools/cli/commands/setup.py +++ b/tools/cli/commands/setup.py @@ -855,6 +855,7 @@ def prepare_parameters(p): p.parameters[""] = f'harbor.{cluster_fqdn}' p.parameters[""] = f'grafana.{cluster_fqdn}' p.parameters[""] = f'sonarqube.{cluster_fqdn}' + p.parameters[""] = f'backstage.{cluster_fqdn}' # OIDC config sec_man_ing = f'{p.parameters[""]}' @@ -864,6 +865,7 @@ def prepare_parameters(p): p.parameters[""] = f'{sec_man_ing}/v1/identity/oidc/provider/cgdevx/userinfo' p.parameters[""] = f'{p.parameters[""]}/auth/callback' p.parameters[""] = f'{p.parameters[""]}/oauth2/callback' + p.parameters[""] = f'{p.parameters[""]}/oauth2/callback' p.parameters[""] = f'{p.parameters[""]}' p.parameters[ ""] = f'https://{p.parameters[""]}/events'