Skip to content

Commit

Permalink
Production deployment (#646)
Browse files Browse the repository at this point in the history
**Only merge using a merge commit!**
  • Loading branch information
github-actions[bot] authored May 9, 2024
2 parents 17078eb + b610999 commit c6fde15
Show file tree
Hide file tree
Showing 15 changed files with 191 additions and 53 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build-email-function.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
workflow_call:

jobs:
plan:
build-email-function:
runs-on: ubuntu-22.04

steps:
Expand Down
77 changes: 77 additions & 0 deletions .github/workflows/terraform-apply.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
name: terraform apply

on:
workflow_dispatch:
inputs:
environment:
description: "Target environment"
required: true
default: "test"
type: choice
options:
- test

jobs:
plan:
uses: ./.github/workflows/terraform-plan.yml
with:
environment: ${{ inputs.environment }}
workflow_call: true
secrets: inherit

apply:
runs-on: ubuntu-22.04

# Related
# - https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs
# - https://docs.github.com/en/rest/authentication/permissions-required-for-github-apps
permissions:
id-token: write # Required for aws-actions/configure-aws-credentials
contents: read # Required for aws-actions/configure-aws-credentials

needs: [plan]

environment: "terraform-${{ inputs.environment }}"

steps:
- uses: actions/[email protected]

- name: Read .terraform-version
id: terraform_version
run: echo "value=$(cat .terraform-version)" >> $GITHUB_OUTPUT
working-directory: terraform

- uses: hashicorp/[email protected]
with:
terraform_version: "${{ steps.terraform_version.outputs.value }}"

- name: Configure AWS Credentials
uses: aws-actions/[email protected]
with:
role-to-assume: ${{ vars.IAM_ROLE_ARN }}
aws-region: eu-central-1

- name: Retrieve email-function artifact
uses: actions/[email protected]
with:
name: email-function-dist
path: bun-packages/packages/email-function/dist

- name: Retrieve tfplan artifact
uses: actions/[email protected]
with:
name: tfplan
path: terraform/

- name: terraform init
run: terraform init -backend-config=${{ inputs.environment }}.s3.tfbackend
working-directory: terraform

- name: terraform apply
run: terraform apply tfplan
working-directory: terraform
env:
TF_VAR_cloudflare_api_token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
TF_VAR_api_subdomain: ${{ vars.API_SUBDOMAIN }}
TF_VAR_email_function_parameters: ${{ secrets.EMAIL_FUNCTION_PARAMETERS }}
GITHUB_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
42 changes: 37 additions & 5 deletions .github/workflows/terraform-plan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,27 @@ on:
- terraform/**
- bun-packages/**
- cloudformation/**
workflow_dispatch:
inputs:
environment:
description: "Target environment"
required: true
default: "test"
type: choice
options:
- test
workflow_call:
inputs:
environment:
required: true
default: "test"
type: string

# `github.event_name` is `workflow_dispatch` even for `workflow_call`.
# https://github.com/actions/runner/discussions/1884
workflow_call:
required: false
type: boolean

jobs:
build_email_function:
Expand All @@ -29,6 +50,8 @@ jobs:

needs: [build_email_function]

environment: terraform-${{ inputs.environment || 'test' }}

steps:
- uses: actions/[email protected]

Expand All @@ -44,7 +67,7 @@ jobs:
- name: Configure AWS Credentials
uses: aws-actions/[email protected]
with:
role-to-assume: arn:aws:iam::220746603587:role/github-actions
role-to-assume: ${{ vars.IAM_ROLE_ARN }}
aws-region: eu-central-1

- name: Retrieve email-function artifact
Expand All @@ -54,17 +77,18 @@ jobs:
path: bun-packages/packages/email-function/dist

- name: terraform init
run: terraform init -backend-config=test.s3.tfbackend
run: terraform init -backend-config=${{ inputs.environment || 'test' }}.s3.tfbackend
working-directory: terraform

- name: terraform plan
run: terraform plan -no-color
run: terraform plan -no-color -out=tfplan
working-directory: terraform
id: plan
continue-on-error: true
env:
TF_VAR_cloudflare_api_token: ${{ secrets.TF_VAR_CLOUDFLARE_API_TOKEN }}
TF_VAR_api_subdomain: sinister-api-test
TF_VAR_cloudflare_api_token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
TF_VAR_api_subdomain: ${{ vars.API_SUBDOMAIN }}
TF_VAR_email_function_parameters: ${{ secrets.EMAIL_FUNCTION_PARAMETERS }}
GITHUB_HEAD_SHA: ${{ github.event.pull_request.head.sha }}

- uses: actions/[email protected]
Expand Down Expand Up @@ -116,3 +140,11 @@ jobs:
- name: Break on terraform plan failure
if: steps.plan.outcome == 'failure'
run: exit 1

- name: Store artifact
uses: actions/[email protected]
if: inputs.workflow_call
with:
name: tfplan
path: terraform/tfplan
retention-days: 1
Binary file modified bun-packages/bun.lockb
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const emailConfirmation = async (
}

await mg.messages.create("mailgun.simonknittel.de", {
from: "Sinister Incorporated <noreply@mailgun.simonknittel.de>",
from: "Sinister Incorporated <no-reply@mailgun.simonknittel.de>",
to: htmlMessages.map((message) => message.to),
subject: subject,
html: htmlEmail,
Expand All @@ -53,7 +53,7 @@ export const emailConfirmation = async (
// }

// await mg.messages.create("mailgun.simonknittel.de", {
// from: "Sinister Incorporated <noreply@mailgun.simonknittel.de>",
// from: "Sinister Incorporated <no-reply@mailgun.simonknittel.de>",
// to: textMessages.map((message) => message.to),
// subject: subject,
// text: textEmail,
Expand All @@ -62,7 +62,7 @@ export const emailConfirmation = async (
};

const subject =
"E-Mail-Adresse und Datenschutzerklärung bestätigen | Sinister Incorporated";
"E-Mail-Adresse und Datenschutzerklärung bestätigen | S.A.M. - Sinister Incorporated";

const renderHtmlEmail = (templateProps: EmailConfirmationProps) => {
return render(Email(templateProps));
Expand All @@ -71,7 +71,7 @@ const renderHtmlEmail = (templateProps: EmailConfirmationProps) => {
const renderTextEmail = (templateProps: EmailConfirmationProps) => {
const { baseUrl, host, token } = templateProps;

return `E-Mail-Adresse und Datenschutzerklärung bestätigen - Sinister Inc
return `E-Mail-Adresse und Datenschutzerklärung bestätigen - S.A.M.
Deine E-Mail-Adresse und die Datenschutzerklärung müssen bestätigt werden bevor du ${host} nutzen kannst.
Expand Down
2 changes: 1 addition & 1 deletion bun-packages/packages/emails/emails/EmailConfirmation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export default function Email({
<Body className="bg-neutral-800 text-neutral-50 font-sans px-6 pt-2 pb-6">
<Container className="mx-auto w-[480px]">
<Text className="text-center text-5xl text-sinister-red-500 font-extrabold uppercase">
Sinister Inc
S.A.M.
</Text>
<Text className="font-bold">
Deine E-Mail-Adresse und die <Link href={`${baseUrl}/api/confirm-email?token=${token}`} className="text-sinister-red-500">Datenschutzerklärung</Link> müssen bestätigt werden bevor du{" "}
Expand Down
2 changes: 2 additions & 0 deletions cloudformation/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
test-parameters.json
prod-parameters.json
28 changes: 21 additions & 7 deletions cloudformation/setup.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@ AWSTemplateFormatVersion: 2010-09-09

Description: Initial setup for Terraform and GitHub Actions

Parameters:
TerraformBackendBucketName:
Type: String
Description: The name of the S3 bucket to use as the Terraform backend

Resources:
TerraformBackendBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: terraform-backend-aowb47tawo48wvt4
BucketName: !Ref TerraformBackendBucketName
OwnershipControls:
Rules:
- ObjectOwnership: BucketOwnerEnforced
Expand Down Expand Up @@ -37,9 +42,13 @@ Resources:
ClientIdList:
- sts.amazonaws.com
ThumbprintList:
# The following thumbprints aren't required as AWS writes: "AWS secures communication with this OIDC identity provider (IdP) using our library of trusted CAs rather than using a certificate thumbprint to verify the server certificate of your IdP. Your legacy thumbprint(s) will remain in your configuration but will no longer be needed for validation."
# The following thumbprints aren't required as AWS writes: "AWS secures communication with this
# OIDC identity provider (IdP) using our library of trusted CAs rather than using a certificate
# thumbprint to verify the server certificate of your IdP. Your legacy thumbprint(s) will remain
# in your configuration but will no longer be needed for validation."
#
# Source for the thumbprints: https://github.blog/changelog/2023-06-27-github-actions-update-on-oidc-integration-with-aws/
# Source for the thumbprints:
# https://github.blog/changelog/2023-06-27-github-actions-update-on-oidc-integration-with-aws/
- 6938fd4d98bab03faadb97b34396831e3780aea1
- 1c58a3a8518e8759bf075b76b750d4f2df264fcd

Expand All @@ -58,12 +67,10 @@ Resources:
StringEquals:
token.actions.githubusercontent.com:aud: sts.amazonaws.com
StringLike:
# https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#customizing-the-token-claims
token.actions.githubusercontent.com:sub:
- repo:simonknittel/sinister-incorporated:job_workflow_ref:simonknittel/sinister-incorporated/.github/workflows/terraform-plan.yml@refs/pull/*
- repo:simonknittel/sinister-incorporated:job_workflow_ref:simonknittel/sinister-incorporated/.github/workflows/terraform-apply.yml@refs/heads/main
- repo:simonknittel/sinister-incorporated:*
Policies:
- PolicyName: terraform
- PolicyName: terraform-plan
PolicyDocument:
Version: 2012-10-17
Statement:
Expand Down Expand Up @@ -143,6 +150,13 @@ Resources:
- ssm:GetParameter
- sts:GetCallerIdentity
Resource: "*"
- PolicyName: terraform-apply
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action: "*" # TODO: Don't do this
Resource: "*"

Outputs:
TerraformBackendBucketName:
Expand Down
33 changes: 15 additions & 18 deletions docs/setup-test-and-production.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,21 @@

## 3. Set up GitHub

1. `gh auth login`
2. `gh api --method PUT -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /repos/simonknittel/sinister-incorporated/actions/oidc/customization/sub -F use_default=false -f "include_claim_keys[]=repo" -f "include_claim_keys[]=job_workflow_ref"`
1. `gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /repos/simonknittel/sinister-incorporated/actions/oidc/customization/sub`
3. Enable "Allow GitHub Actions to create and approve pull requests" in Settings/Actions/General/Workflow permissions

### Related

- https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#customizing-the-token-claims
- https://docs.github.com/en/rest/actions/oidc?apiVersion=2022-11-28#set-the-customization-template-for-an-oidc-subject-claim-for-a-repository
1. Create environments
1. `terraform-test`
2. `terraform-prod`
2. Create environment secrets
- `CLOUDFLARE_API_TOKEN`
- `EMAIL_FUNCTION_PARAMETERS`
- `[{"name":"...","value":"..."}]`
3. Create environment variables
- `API_SUBDOMAIN`
- `IAM_ROLE`
4. Enable "Allow GitHub Actions to create and approve pull requests" in Settings/Actions/General/Workflow permissions

## 4. Set up AWS

1. Create two AWS accounts
1. Create AWS accounts

1. `sinister-incorporated-test`
2. `sinister-incorporated-prod`
Expand Down Expand Up @@ -51,16 +53,11 @@

3. Create and deploy setup stack with AWS CloudFormation

1. Create and populate `test-parameters.json` and `prod-parameters.json`
1. `AWS_PROFILE=sinister-incorporated-test aws sso login`
2. `AWS_PROFILE=sinister-incorporated-test aws --region eu-central-1 cloudformation deploy --template-file ./cloudformation/setup.yaml --stack-name setup --capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM --tags ManagedBy=CloudFormation Repository=simonknittel/sinister-incorporated CloudFormationStack=setup`

4. Create parameters in AWS System Manager (make sure to replace `foobar` with the actual values)

1. `AWS_PROFILE=sinister-incorporated-test aws sso login`
2. `AWS_PROFILE=sinister-incorporated-test aws --region eu-central-1 ssm put-parameter --name /email-function/mailgun-api-key --value foobar --type SecureString --overwrite`
3. `AWS_PROFILE=sinister-incorporated-test aws --region eu-central-1 ssm put-parameter --name /email-function/api-key --value foobar --type SecureString --overwrite`
2. `AWS_PROFILE=sinister-incorporated-test aws --region eu-central-1 cloudformation deploy --template-file setup.yaml --stack-name setup --capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM --tags ManagedBy=CloudFormation Repository=simonknittel/sinister-incorporated --parameter-override file://test-parameters.json`

5. Manually set up AWS User Notifications through the console
4. Manually set up AWS User Notifications through the console

1. Notification hubs: eu-central-1
2. Create notification configuration for CloudWatch
Expand Down
5 changes: 1 addition & 4 deletions terraform/email-function.tf
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,5 @@ module "email_function" {
event_bus = aws_cloudwatch_event_bus.api_gateway
event_bus_detail_type = "EmailRequested"
dynamodb = aws_dynamodb_table.api_gateway_processed_requests

parameter_store = [
"/mailgun-api-key"
]
parameters = var.email_function_parameters
}
10 changes: 6 additions & 4 deletions terraform/modules/api-gateway-lambda/iam.tf
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ resource "aws_iam_role" "main" {
[
data.aws_kms_alias.ssm.target_key_arn,
],
tolist(data.aws_ssm_parameter.custom[*].arn),
tolist(aws_ssm_parameter.custom[*].arn),
)
},
]
Expand All @@ -51,7 +51,9 @@ data "aws_kms_alias" "ssm" {
name = "alias/aws/ssm"
}

data "aws_ssm_parameter" "custom" {
count = length(var.parameter_store)
name = "/${var.function_name}${var.parameter_store[count.index]}"
resource "aws_ssm_parameter" "custom" {
count = length(var.parameters)
type = "SecureString"
name = "/${var.function_name}/${var.parameters[count.index].name}"
value = var.parameters[count.index].value
}
7 changes: 5 additions & 2 deletions terraform/modules/api-gateway-lambda/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ variable "method" {
type = string
}

variable "parameter_store" {
type = list(string)
variable "parameters" {
type = list(object({
name = string
value = string
}))
default = []
}

Expand Down
10 changes: 6 additions & 4 deletions terraform/modules/eventbridge-sqs-lambda/iam.tf
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ resource "aws_iam_role" "main" {
[
data.aws_kms_alias.ssm.target_key_arn,
],
tolist(data.aws_ssm_parameter.custom[*].arn),
tolist(aws_ssm_parameter.custom[*].arn),
)
},
{
Expand Down Expand Up @@ -73,7 +73,9 @@ data "aws_kms_alias" "ssm" {
name = "alias/aws/ssm"
}

data "aws_ssm_parameter" "custom" {
count = length(var.parameter_store)
name = "/${var.function_name}${var.parameter_store[count.index]}"
resource "aws_ssm_parameter" "custom" {
count = length(var.parameters)
type = "SecureString"
name = "/${var.function_name}/${var.parameters[count.index].name}"
value = var.parameters[count.index].value
}
Loading

0 comments on commit c6fde15

Please sign in to comment.