diff --git a/.github/workflows/release-helm-chart.yml b/.github/workflows/release-helm-chart.yml new file mode 100644 index 0000000000..4cb25c900b --- /dev/null +++ b/.github/workflows/release-helm-chart.yml @@ -0,0 +1,43 @@ +name: Publish Helm Chart + +on: + release: + types: + - published + +jobs: + publish: + runs-on: ubuntu-latest + permissions: + packages: write + contents: read + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Extract release version + run: echo "VERSION=${{ github.event.release.tag_name }}" >> $GITHUB_ENV + + - name: Set up Helm + uses: azure/setup-helm@v3 + with: + version: latest + + - name: Log in to GitHub Container Registry + run: echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login ghcr.io --username ${{ github.actor }} --password-stdin + + - name: Install YQ + uses: dcarbone/install-yq-action@v1.3.1 + + - name: Update Chart.yaml with new version + run: | + yq -i ".version = \"${VERSION#v}\"" helm-chart/Chart.yaml + yq -i ".appVersion = \"${VERSION}\"" helm-chart/Chart.yaml + + - name: Package Helm chart + run: | + helm package ./helm-chart + + - name: Push Helm chart to GitHub Container Registry + run: | + helm push formbricks-${VERSION#v}.tgz oci://ghcr.io/formbricks/helm-charts diff --git a/.github/workflows/terrafrom-plan-and-apply.yml b/.github/workflows/terrafrom-plan-and-apply.yml new file mode 100644 index 0000000000..33c2f9e748 --- /dev/null +++ b/.github/workflows/terrafrom-plan-and-apply.yml @@ -0,0 +1,92 @@ +name: 'Terraform' + +on: + workflow_dispatch: + pull_request: + push: + branches: + - main + paths: + - 'infra/terraform/**' + +permissions: + id-token: write + contents: write + +jobs: + terraform: + runs-on: ubuntu-latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ secrets.AWS_ASSUME_ROLE_ARN }} + aws-region: "eu-central-1" + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + + - name: Terraform Format + id: fmt + run: terraform fmt -check -recursive + continue-on-error: true + working-directory: infra/terraform + +# - name: Post Format +# if: always() && github.ref != 'refs/heads/main' && (steps.fmt.outcome == 'success' || steps.fmt.outcome == 'failure') +# uses: robburger/terraform-pr-commenter@v1 +# with: +# commenter_type: fmt +# commenter_input: ${{ format('{0}{1}', steps.fmt.outputs.stdout, steps.fmt.outputs.stderr) }} +# commenter_exitcode: ${{ steps.fmt.outputs.exitcode }} + + - name: Terraform Init + id: init + run: terraform init + working-directory: infra/terraform + +# - name: Post Init +# if: always() && github.ref != 'refs/heads/main' && (steps.init.outcome == 'success' || steps.init.outcome == 'failure') +# uses: robburger/terraform-pr-commenter@v1 +# with: +# commenter_type: init +# commenter_input: ${{ format('{0}{1}', steps.init.outputs.stdout, steps.init.outputs.stderr) }} +# commenter_exitcode: ${{ steps.init.outputs.exitcode }} + + - name: Terraform Validate + id: validate + run: terraform validate + working-directory: infra/terraform + +# - name: Post Validate +# if: always() && github.ref != 'refs/heads/main' && (steps.validate.outcome == 'success' || steps.validate.outcome == 'failure') +# uses: robburger/terraform-pr-commenter@v1 +# with: +# commenter_type: validate +# commenter_input: ${{ format('{0}{1}', steps.validate.outputs.stdout, steps.validate.outputs.stderr) }} +# commenter_exitcode: ${{ steps.validate.outputs.exitcode }} + + - name: Terraform Plan + id: plan + run: terraform plan -out .planfile + working-directory: infra/terraform + + - name: Post PR comment + uses: borchero/terraform-plan-comment@v2 + if: always() && github.ref != 'refs/heads/main' && (steps.validate.outcome == 'success' || steps.validate.outcome == 'failure') + with: + token: ${{ github.token }} + planfile: .planfile + working-directory: "infra/terraform" + skip-comment: true + + - name: Terraform Apply + id: apply + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + run: terraform apply .planfile + diff --git a/helm-chart/Chart.yaml b/helm-chart/Chart.yaml index ced5069a8e..5176281925 100644 --- a/helm-chart/Chart.yaml +++ b/helm-chart/Chart.yaml @@ -5,8 +5,7 @@ description: A Helm chart for Formbricks with PostgreSQL, Redis type: application # Helm chart Version -version: 3.4.0 -appVersion: v3.4.0 +version: 0.0.0-dev keywords: - formbricks diff --git a/helm-chart/README.md b/helm-chart/README.md index 8138cf9206..f76a491d66 100644 --- a/helm-chart/README.md +++ b/helm-chart/README.md @@ -1,6 +1,6 @@ # formbricks -![Version: 3.3.1](https://img.shields.io/badge/Version-3.3.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v3.3.1](https://img.shields.io/badge/AppVersion-v3.3.1-informational?style=flat-square) +![Version: 0.0.0-dev](https://img.shields.io/badge/Version-0.0.0--dev-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) A Helm chart for Formbricks with PostgreSQL, Redis diff --git a/helm-chart/templates/deployment.yaml b/helm-chart/templates/deployment.yaml index e048576bad..378233d183 100644 --- a/helm-chart/templates/deployment.yaml +++ b/helm-chart/templates/deployment.yaml @@ -94,8 +94,12 @@ spec: protocol: {{ $config.protocol | default "TCP" | quote }} {{- end }} {{- end }} - {{- if .Values.deployment.envFrom }} + {{- if or .Values.deployment.envFrom (and .Values.externalSecret.enabled (index .Values.externalSecret.files "app-secrets")) }} envFrom: + {{- if or .Values.secret.enabled (and .Values.externalSecret.enabled (index .Values.externalSecret.files "app-secrets")) }} + - secretRef: + name: {{ template "formbricks.name" . }}-app-secrets + {{- end }} {{- range $value := .Values.deployment.envFrom }} {{- if (eq .type "configmap") }} - configMapRef: @@ -122,42 +126,8 @@ spec: env: {{- if and (.Values.enterprise.enabled) (ne .Values.enterprise.licenseKey "") }} - name: ENTERPRISE_LICENSE_KEY - valueFrom: - secretKeyRef: - name: {{ template "formbricks.name" . }}-app-secrets - key: ENTERPRISE_LICENSE_KEY - {{- else if and (.Values.enterprise.enabled) (eq .Values.enterprise.licenseKey "") }} - - name: ENTERPRISE_LICENSE_KEY - valueFrom: - secretKeyRef: - name: {{ template "formbricks.name" . }}-app-secrets - key: ENTERPRISE_LICENSE_KEY + value: {{ .Values.enterprise.licenseKey | quote }} {{- end }} - - name: REDIS_URL - valueFrom: - secretKeyRef: - name: {{ template "formbricks.name" . }}-app-secrets - key: REDIS_URL - - name: DATABASE_URL - valueFrom: - secretKeyRef: - name: {{ template "formbricks.name" . }}-app-secrets - key: DATABASE_URL - - name: CRON_SECRET - valueFrom: - secretKeyRef: - name: {{ template "formbricks.name" . }}-app-secrets - key: CRON_SECRET - - name: ENCRYPTION_KEY - valueFrom: - secretKeyRef: - name: {{ template "formbricks.name" . }}-app-secrets - key: ENCRYPTION_KEY - - name: NEXTAUTH_SECRET - valueFrom: - secretKeyRef: - name: {{ template "formbricks.name" . }}-app-secrets - key: NEXTAUTH_SECRET {{- range $key, $value := .Values.deployment.env }} - name: {{ include "formbricks.tplvalues.render" ( dict "value" $key "context" $ ) }} {{ include "formbricks.tplvalues.render" ( dict "value" $value "context" $ ) | indent 10 }} diff --git a/helm-chart/templates/hpa.yaml b/helm-chart/templates/hpa.yaml index 5adb814124..d2567401c0 100644 --- a/helm-chart/templates/hpa.yaml +++ b/helm-chart/templates/hpa.yaml @@ -1,10 +1,6 @@ {{- if .Values.autoscaling.enabled }} --- -{{- if .Capabilities.APIVersions.Has "autoscaling/v2/HorizontalPodAutoscaler" }} apiVersion: autoscaling/v2 -{{- else }} -apiVersion: autoscaling/v2beta2 -{{- end }} kind: HorizontalPodAutoscaler metadata: name: {{ template "formbricks.name" . }} diff --git a/helm-chart/values.yaml b/helm-chart/values.yaml index 379cad50f4..684b3ec130 100644 --- a/helm-chart/values.yaml +++ b/helm-chart/values.yaml @@ -54,9 +54,9 @@ deployment: # Environment variables from ConfigMaps or Secrets envFrom: - # app-secrets: - # type: secret - # nameSuffix: app-secrets + # app-secrets: + # type: secret + # nameSuffix: app-secrets # Environment variables passed to the app container env: diff --git a/infra/terraform/data.tf b/infra/terraform/data.tf index fcf818d66e..cecb31932e 100644 --- a/infra/terraform/data.tf +++ b/infra/terraform/data.tf @@ -10,3 +10,11 @@ data "aws_eks_cluster_auth" "eks" { data "aws_ecrpublic_authorization_token" "token" { provider = aws.virginia } + +data "aws_iam_roles" "administrator" { + name_regex = "AWSReservedSSO_AdministratorAccess" +} + +data "aws_iam_roles" "github" { + name_regex = "formbricks-prod-github" +} diff --git a/infra/terraform/iam.tf b/infra/terraform/iam.tf index f0af377861..27ac846e14 100644 --- a/infra/terraform/iam.tf +++ b/infra/terraform/iam.tf @@ -23,7 +23,7 @@ module "iam_github_oidc_role" { "repo:formbricks/*:*", ] policies = { - Administrator = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess" + Administrator = "arn:aws:iam::aws:policy/AdministratorAccess" } tags = local.tags diff --git a/infra/terraform/main.tf b/infra/terraform/main.tf index c1327e5376..1f020e7f5c 100644 --- a/infra/terraform/main.tf +++ b/infra/terraform/main.tf @@ -249,7 +249,7 @@ module "eks" { cluster_name = "${local.name}-eks" cluster_version = "1.32" - enable_cluster_creator_admin_permissions = true + enable_cluster_creator_admin_permissions = false cluster_endpoint_public_access = true cluster_addons = { @@ -271,6 +271,41 @@ module "eks" { } } + kms_key_administrators = [ + tolist(data.aws_iam_roles.github.arns)[0], + tolist(data.aws_iam_roles.administrator.arns)[0] + ] + + kms_key_users = [ + tolist(data.aws_iam_roles.github.arns)[0], + tolist(data.aws_iam_roles.administrator.arns)[0] + ] + + access_entries = { + administrator = { + principal_arn = tolist(data.aws_iam_roles.administrator.arns)[0] + policy_associations = { + Admin = { + policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy" + access_scope = { + type = "cluster" + } + } + } + } + github = { + principal_arn = tolist(data.aws_iam_roles.github.arns)[0] + policy_associations = { + Admin = { + policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy" + access_scope = { + type = "cluster" + } + } + } + } + } + vpc_id = module.vpc.vpc_id subnet_ids = module.vpc.private_subnets control_plane_subnet_ids = module.vpc.intra_subnets @@ -573,95 +608,69 @@ resource "helm_release" "formbricks" { values = [ <<-EOT - postgresql: - enabled: false - redis: - enabled: false - ingress: + postgresql: + enabled: false + redis: + enabled: false + ingress: + enabled: true + ingressClassName: alb + hosts: + - host: "app.${local.domain}" + paths: + - path: / + pathType: "Prefix" + serviceName: "formbricks" + annotations: + alb.ingress.kubernetes.io/scheme: internet-facing + alb.ingress.kubernetes.io/target-type: ip + alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]' + alb.ingress.kubernetes.io/ssl-redirect: "443" + alb.ingress.kubernetes.io/certificate-arn: ${module.acm.acm_certificate_arn} + alb.ingress.kubernetes.io/healthcheck-path: "/health" + alb.ingress.kubernetes.io/group.name: formbricks + alb.ingress.kubernetes.io/ssl-policy: "ELBSecurityPolicy-TLS13-1-2-2021-06" + secret: + enabled: false + rbac: + enabled: true + serviceAccount: enabled: true - ingressClassName: alb - hosts: - - host: "app.${local.domain}" - paths: - - path: / - pathType: "Prefix" - serviceName: "formbricks" + name: formbricks annotations: - alb.ingress.kubernetes.io/scheme: internet-facing - alb.ingress.kubernetes.io/target-type: ip - alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]' - alb.ingress.kubernetes.io/ssl-redirect: "443" - alb.ingress.kubernetes.io/certificate-arn: ${module.acm.acm_certificate_arn} - alb.ingress.kubernetes.io/healthcheck-path: "/health" - alb.ingress.kubernetes.io/group.name: formbricks - alb.ingress.kubernetes.io/ssl-policy: "ELBSecurityPolicy-TLS13-1-2-2021-06" - secret: - enabled: false - rbac: - enabled: true - serviceAccount: - enabled: true - name: formbricks - annotations: - eks.amazonaws.com/role-arn: ${module.formkey-aws-access.iam_role_arn} - serviceMonitor: - enabled: true - deployment: - image: - repository: "ghcr.io/formbricks/formbricks-experimental" - tag: "open-telemetry-for-prometheus" - pullPolicy: Always - env: - S3_BUCKET_NAME: - value: ${module.s3-bucket.s3_bucket_id} - RATE_LIMITING_DISABLED: - value: "1" - envFrom: - app-parameters: - type: secret - nameSuffix: {RELEASE.name}-app-parameters - annotations: - deployed_at: ${timestamp()} - externalSecret: - enabled: true # Enable/disable ExternalSecrets - secretStore: - name: aws-secrets-manager - kind: ClusterSecretStore - refreshInterval: "1h" - files: - app-parameters: - dataFrom: - key: "/prod/formbricks/env" - secretStore: - name: aws-parameter-store - kind: ClusterSecretStore - app-secrets: - data: - DATABASE_URL: - remoteRef: - key: "prod/formbricks/secrets" - property: DATABASE_URL - REDIS_URL: - remoteRef: - key: "prod/formbricks/secrets" - property: REDIS_URL - CRON_SECRET: - remoteRef: - key: "prod/formbricks/secrets" - property: CRON_SECRET - ENCRYPTION_KEY: - remoteRef: - key: "prod/formbricks/secrets" - property: ENCRYPTION_KEY - NEXTAUTH_SECRET: - remoteRef: - key: "prod/formbricks/secrets" - property: NEXTAUTH_SECRET - ENTERPRISE_LICENSE_KEY: - remoteRef: - key: "prod/formbricks/enterprise" - property: ENTERPRISE_LICENSE_KEY - EOT + eks.amazonaws.com/role-arn: ${module.formkey-aws-access.iam_role_arn} + serviceMonitor: + enabled: true + deployment: + image: + repository: "ghcr.io/formbricks/formbricks-experimental" + tag: "open-telemetry-for-prometheus" + pullPolicy: Always + env: + S3_BUCKET_NAME: + value: ${module.s3-bucket.s3_bucket_id} + RATE_LIMITING_DISABLED: + value: "1" + envFrom: + app-env: + type: secret + nameSuffix: app-env + annotations: + deployed_at: ${timestamp()} + externalSecret: + enabled: true # Enable/disable ExternalSecrets + secretStore: + name: aws-secrets-manager + kind: ClusterSecretStore + refreshInterval: "1h" + files: + app-env: + dataFrom: + key: "prod/formbricks/environment" + app-secrets: + dataFrom: + key: "prod/formbricks/secrets" + EOT ] } diff --git a/infra/terraform/secrets.tf b/infra/terraform/secrets.tf index ec98262af1..3a633bfc55 100644 --- a/infra/terraform/secrets.tf +++ b/infra/terraform/secrets.tf @@ -1,19 +1,3 @@ -# Generate random secrets for formbricks -resource "random_password" "nextauth_secret" { - length = 32 - special = false -} - -resource "random_password" "encryption_key" { - length = 32 - special = false -} - -resource "random_password" "cron_secret" { - length = 32 - special = false -} - # Create the first AWS Secrets Manager secret for environment variables resource "aws_secretsmanager_secret" "formbricks_app_secrets" { name = "prod/formbricks/secrets" @@ -24,10 +8,7 @@ resource "aws_secretsmanager_secret" "formbricks_app_secrets" { resource "aws_secretsmanager_secret_version" "formbricks_app_secrets" { secret_id = aws_secretsmanager_secret.formbricks_app_secrets.id secret_string = jsonencode({ - NEXTAUTH_SECRET = random_password.nextauth_secret.result - ENCRYPTION_KEY = random_password.encryption_key.result - CRON_SECRET = random_password.cron_secret.result - DATABASE_URL = "postgres://formbricks:${random_password.postgres.result}@${module.rds-aurora.cluster_endpoint}/formbricks" - REDIS_URL = "rediss://:${random_password.valkey.result}@${module.elasticache.replication_group_primary_endpoint_address}:6379" + DATABASE_URL = "postgres://formbricks:${random_password.postgres.result}@${module.rds-aurora.cluster_endpoint}/formbricks" + REDIS_URL = "rediss://:${random_password.valkey.result}@${module.elasticache.replication_group_primary_endpoint_address}:6379" }) }