Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

πŸ’» Bootstrap AWS Accounts for Terraform #2

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions bootstrap/configs/pipeline-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"schema_version": "tags/20220517.1",
"ecr_region": "us-east-2",
"docker_context_directory": "Lambda",
"docker_file": "config.Dockerfile",
"services": {
"lsports": {
"service_name": "lsports_consume",
"ecr_tag": 20220517.1
},
"firebase": {
"service_name": "populate_firebase",
"ecr_tag": 20220517.1
},
"create_market": {
"service_name": "create_market",
"ecr_tag": 20220517.1
}
},
"lambda_configs_path": "Lambda/configs",
"config_schema_path": "config-schemas",
"terraform_directory": "Terraform2",
"environments": {
"prod": {
"workspaces": [
"solid",
"flexible"
]
},
"dev": {
"workspaces": [
"default"
]
},
"sandbox": {
"workspaces": [
"default"
]
}
}
}
17 changes: 17 additions & 0 deletions bootstrap/configs/s3-policy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": "arn:aws:s3:::BUCKET/RESOURCE/terraform/KEY",
"Principal": {
"AWS": [
"ARN"
]
}
}
]
}
17 changes: 17 additions & 0 deletions bootstrap/configs/user-assume-role.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::ACCOUNT:role/ROLE"
},
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": "arn:aws:s3:::BUCKET/RESOURCE/terraform/KEY"
}
]
}
117 changes: 117 additions & 0 deletions bootstrap/multi-account/bootstrap.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#!/bin/bash

set -eu

# need to pass in environments & aws account
USAGE="USAGE:
${0} <main-account-id> <AWS_ID:AWS_KEY> <environment:aws_account_id> ..."

if [[ $# < 3 ]]; then
echo "${USAGE}" >&2
exit 1
fi

TERRAFORM_S3_BUCKET="ubet-tfstate-s3"
REGION="us-east-2"
MAIN_ACCOUNT_ID=$1

# get credentials
AWS_CREDENTIALS=(${2//:/ })
AWS_ID="${AWS_CREDENTIALS[0]}"
AWS_KEY="${AWS_CREDENTIALS[1]}"


# ----------------------------------------------------------
# Create S3 Bucket
# N.B: No need state locking! (dynamodb)
# Each environment would be applied via ci and sandbox would have each "user" make use of a local state
#
# Hardcoding region & bucketname, since we would not want to have it otherwise
# states would be created in terraform but we handle state file policy access here
if [[ $(aws s3api list-buckets --query 'Buckets[].Name' | grep $TERRAFORM_S3_BUCKET) != *$TERRAFORM_S3_BUCKET* ]]; then
aws s3api create-bucket \
--bucket $TERRAFORM_S3_BUCKET \
--region $REGION \
--create-bucket-configuration LocationConstraint=$REGION

# set bucket resource permissions for arn
ARN="arn:aws:iam::${MAIN_ACCOUNT_ID}:user/terraform-user"
RESOURCES=""
for i in "${@:3}"
do
env_acc=(${i//:/ })
RESOURCES="\"arn:aws:s3:::${TERRAFORM_S3_BUCKET}/${env_acc[0]}/terraform/.tfstate\", ${RESOURCES}"
done
RESOURCES=${RESOURCES::-2}

sed \
-e "s/S3_BUCKET/${TERRAFORM_S3_BUCKET}/g" \
-e "s|RESOURCES|${RESOURCES}|g" \
-e "s|ARN|${ARN}|g" \
"$(dirname "$0")/../configs/s3-policy.json" > s3-policy.json
aws s3api put-bucket-policy --bucket $TERRAFORM_S3_BUCKET --policy file://s3-policy.json
aws s3api put-bucket-versioning --bucket $TERRAFORM_S3_BUCKET --versioning-configuration Status=Enabled
rm s3-policy.json

fi


# ----------------------------------------------------------
# create roles for each environment if they do not exist [assume_role]
# create terraform user to assume the role
# production & testnet
# create user if !exist
if [[ $(aws iam list-users --query 'Users[*].UserName' | grep "terraform-user") != *"terraform-user"* ]]; then
aws iam create-user --user-name "terraform-user"
echo "created the terraform-user!"
fi
sleep 5 # Let user creation complete to avoid role not finding user

# create policy
if [[ $(aws iam list-policies --query 'Policies[*].PolicyName' | grep "terraform-user-policy") != *"terraform-user-policy"* ]]; then
ROLE_ARNS=""
for i in "${@:3}"
do
env_acc=(${i//:/ })
ROLE_ARNS="\"arn:aws:iam::${env_acc[1]}:role/${env_acc[0]}-terraform-role\", ${ROLE_ARNS}"
done
ROLE_ARNS=${ROLE_ARNS::-2} # remove last , & space
sed -e "s|ROLE_ARNS|${ROLE_ARNS}|g" -e "s|ACCOUNT|${env_acc[0]}|g" "$(dirname "$0")/../configs/user-assume-role-policy.json" > user-assume-role-policy.json
aws iam create-policy --policy-name "terraform-user-policy" --policy-document file://user-assume-role-policy.json
echo "created terraform-user-policy!"
aws iam attach-user-policy --user-name "terraform-user" --policy-arn "arn:aws:iam::$MAIN_ACCOUNT_ID:policy/terraform-user-policy"
echo "attached terraform-user-policy to terraform-user!"
aws iam list-attached-user-policies --user-name "terraform-user"
rm user-assume-role-policy.json

fi

sleep 5 # Let policy creation complete

for i in "${@:3}"
do
env_acc=(${i//:/ })
if [[ $(aws iam list-roles --query 'Roles[*].RoleName' | grep "${env_acc[0]}-terraform-role") != *"${env_acc[0]}-terraform-role"* ]]; then

# role needs to be created in account the user would be deploying infrastructure to
# assume role & aws configure | credentials set
eval $(aws sts assume-role --role-arn arn:aws:iam::${env_acc[1]}:role/setup-role --role-session-name terraform-${env_acc[0]}-role-setup-session | jq -r '.Credentials | "export AWS_ACCESS_KEY_ID=\(.AccessKeyId)\nexport AWS_SECRET_ACCESS_KEY=\(.SecretAccessKey)\nexport AWS_SESSION_TOKEN=\(.SessionToken)\n"')

sed -e "s/USER/terraform-user/g" -e "s|ACCOUNT|$MAIN_ACCOUNT_ID|g" "$(dirname "$0")/../configs/role-trust-policy.json" > ${env_acc[0]}-role-trust-policy.json
aws iam create-role --role-name "${env_acc[0]}-terraform-role" --assume-role-policy-document file://${env_acc[0]}-role-trust-policy.json
echo "created ${env_acc[0]}-terraform-role!"
aws iam list-attached-role-policies --role-name "${env_acc[0]}-terraform-role"
rm ${env_acc[0]}-role-trust-policy.json

# drain assume role credentials
unset AWS_ACCESS_KEY_ID
unset AWS_SECRET_ACCESS_KEY
unset AWS_SESSION_TOKEN
# fill in git user credentials | aws configure
aws configure set aws_access_key_id $AWS_ID
aws configure set aws_secret_access_key $AWS_KEY
fi
done

# One state file per environment with multiple workspaces
# roles get access to state file for their respective environment
69 changes: 69 additions & 0 deletions bootstrap/single-account/bootstrap.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/bin/bash

set -eu

USAGE="USAGE:
${0} <environnment-name>"

if [[ $# -ne 1 ]]; then
echo "${USAGE}" >&2
exit 1
fi

function cleanup (){
export_sate
rm -f state.tf
pushd ${ENV_BOOTSTRAP_DIR} > /dev/null
rm -f .tfstate.backup
rm -rf terraform.log
}

# import
# - lockfile tfvars
function import_state(){
pushd ${ENV_BOOTSTRAP_DIR} > /dev/null
echo $(pwd)
echo "here"
touch -f terraform.log
[ -f ./.terraform.lock.hcl ] && mv ./.terraform.lock.hcl $BOOTSTRAP_MODULE_DIR
[ -f ./variables.tfvars ] && mv ./variables.tfvars $BOOTSTRAP_MODULE_DIR
popd
}

# export
# - tfvars lockfile
function export_sate(){
[ -f "./.terraform.lock.hcl" ] && mv "./.terraform.lock.hcl" $ENV_BOOTSTRAP_DIR
[ -f "./variables.tfvars" ] && mv "./variables.tfvars" $ENV_BOOTSTRAP_DIR
}

# safe exit
trap cleanup EXIT

## Set vars
BOOTSTRAP_MODULE_DIR=$( cd $( dirname "${BASH_SOURCE[0]}" ); pwd -P )
pushd ${BOOTSTRAP_MODULE_DIR} > /dev/null
# Get absolute path to environment bootstrap directory
ENV_BOOTSTRAP_DIR=$(cd "../../environments/${1}/bootstrap"; pwd -P)
echo ${BOOTSTRAP_MODULE_DIR}
echo ${ENV_BOOTSTRAP_DIR}
ENVIRONMENT=$(basename $(cd ${ENV_BOOTSTRAP_DIR}; pwd -P))
echo ${ENVIRONMENT}

# import resources
import_state

# set terraform env vars
export TF_DATA_DIR=$ENV_BOOTSTRAP_DIR/.terraform
export TF_LOG_PATH=$ENV_BOOTSTRAP_DIR/terraform.log
export TF_LOG="trace"
# update statefile reference
sed -e "s|ENVIRONMENT|$ENVIRONMENT|g" "${BOOTSTRAP_MODULE_DIR}/state.tf.template" > state.tf

# terraform
terraform init
terraform validate
terraform apply -var-file="variables.tfvars" # --auto-approve

# export resources
export_sate
62 changes: 62 additions & 0 deletions bootstrap/single-account/iam.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
setup the infrastructure provisioning user
*/
resource "aws_iam_user" "terraform_user" {
name = local.terraform_user
tags = local.common_tags

}

resource "aws_iam_user_policy_attachment" "terraform_user_policy_attachment" {
user = aws_iam_user.terraform_user.name
policy_arn = aws_iam_policy.terraform_user_policy.arn
}

resource "aws_iam_policy" "terraform_user_policy" {
name = "${local.terraform_user}-policy"
description = "Set permissions for creating different pieces of infrastructure"

policy = data.aws_iam_policy_document.terraform_user_policy_document.json

}

data "aws_iam_policy_document" "terraform_user_policy_document" {
override_policy_documents = [
data.aws_iam_policy_document.s3_policy_document.json,
data.aws_iam_policy_document.permissions_policy_document.json
# add policy documents here
]

}

# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document
data "aws_iam_policy_document" "s3_policy_document" {
statement {
actions = [
"s3:ListAllMyBuckets"
]
resources = ["arn:aws:s3:::*"]
effect = "Allow"
}
statement {
actions = [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
]
resources = [
"${aws_s3_bucket.terraform_bucket[0].arn}/${var.environment}/terraform/.tfstate*" # state file
]
effect = "Allow"
}

}

data "aws_iam_policy_document" "permissions_policy_document" {
statement {
actions = var.terraform_user_permissions
resources = ["*"]
effect = "Allow"
}

}
16 changes: 16 additions & 0 deletions bootstrap/single-account/locals.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

locals {
region = var.region
account_environment = "${var.company}-${var.environment}"
terraform_user = "${var.environment}-${var.terraform_user}"
state_bucket = "${var.company}-${var.bucket_purpose}-${var.environment}"
logging_bucket = "${var.company}-${var.bucket_purpose}-${var.log_name}-${var.environment}"
}

locals {
# Common tags to be assigned to all resources
common_tags = {
Environment = local.account_environment
Project = "${var.company}-Infrastructure"
}
}
9 changes: 9 additions & 0 deletions bootstrap/single-account/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
output "state_bucket" {
description = "The state_bucket name"
value = local.state_bucket
}

output "logging_bucket" {
description = "The logging_bucket name"
value = local.logging_bucket
}
9 changes: 9 additions & 0 deletions bootstrap/single-account/providers.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

provider "aws" {
region = local.region
default_tags {
tags = {
owner = "Sports Inference - Ubet"
}
}
}
Loading