[QT-318] Add Vault CI bootstrap scenarios (#17907)

This commit is contained in:
Mike Baum 2022-11-30 12:44:02 -05:00 committed by GitHub
parent 547cb27b8a
commit b03da5157e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 393 additions and 0 deletions

67
.github/workflows/enos-bootstrap-ci.yml vendored Normal file
View File

@ -0,0 +1,67 @@
name: enos-ci-bootstrap
on:
pull_request:
branches:
- main
push:
branches:
- main
paths:
- enos/ci/**
- .github/workflows/enos-bootstrap-ci.yml
jobs:
bootstrap-ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Terraform
uses: hashicorp/setup-terraform@v2
- name: Prepare for Terraform execution
id: prepare_for_terraform
env:
IS_ENT: ${{ startsWith(github.event.repository.name, 'vault-enterprise' ) }}
run: |
if ${IS_ENT} == true; then
echo "aws_role=arn:aws:iam::505811019928:role/github_actions-vault-enterprise_ci" >> $GITHUB_OUTPUT
echo "aws role set to 'arn:aws:iam::505811019928:role/github_actions-vault-enterprise_ci'"
echo "product_line=vault-enterprise" >> $GITHUB_OUTPUT
echo "product line set to 'vault-enterprise'"
else
echo "aws_role=arn:aws:iam::040730498200:role/github_actions-vault_ci" >> $GITHUB_OUTPUT
echo "aws role set to 'arn:aws:iam::040730498200:role/github_actions-vault_ci'"
echo "product_line=vault" >> $GITHUB_OUTPUT
echo "product line set to 'vault'"
fi
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID_CI }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_CI }}
aws-region: us-east-1
role-to-assume: ${{ steps.prepare_for_terraform.outputs.aws_role }}
role-skip-session-tagging: true
role-duration-seconds: 3600
- name: Init Terraform
id: tf_init
run: |
export TF_WORKSPACE="${{ steps.prepare_for_terraform.outputs.product_line }}-ci-enos-bootstrap"
export TF_VAR_aws_ssh_public_key="${{ secrets.ENOS_CI_SSH_KEY }}"
export TF_TOKEN_app_terraform_io="${{ secrets.TF_API_TOKEN }}"
terraform -chdir=enos/ci/bootstrap init
- name: Plan Terraform
id: tf_plan
run: |
export TF_WORKSPACE="${{ steps.prepare_for_terraform.outputs.product_line }}-ci-enos-bootstrap"
export TF_VAR_aws_ssh_public_key="${{ secrets.ENOS_CI_SSH_KEY }}"
export TF_TOKEN_app_terraform_io="${{ secrets.TF_API_TOKEN }}"
terraform -chdir=enos/ci/bootstrap plan
- name: Apply Terraform
if: ${{ github.ref == 'refs/heads/main' }}
id: tf_apply
run: |
export TF_WORKSPACE="${{ steps.prepare_for_terraform.outputs.product_line }}-ci-enos-bootstrap"
export TF_VAR_aws_ssh_public_key="${{ secrets.ENOS_CI_SSH_KEY }}"
export TF_TOKEN_app_terraform_io="${{ secrets.TF_API_TOKEN }}"
terraform -chdir=enos/ci/bootstrap apply -auto-approve

5
.gitignore vendored
View File

@ -61,6 +61,11 @@ Vagrantfile
# Enos # Enos
enos/.enos enos/.enos
enos/support enos/support
# Enos local Terraform files
enos/.terraform/*
enos/.terraform.lock.hcl
enos/*.tfstate
enos/*.tfstate.*
.DS_Store .DS_Store
.idea .idea

View File

@ -140,3 +140,68 @@ downloads the artifact built by the `build.yml` workflow, unzips it, and sets th
This variant is for running the Enos scenario locally. It builds the Vault bundle This variant is for running the Enos scenario locally. It builds the Vault bundle
from the current branch, placing the bundle at the `vault_bundle_path` and the from the current branch, placing the bundle at the `vault_bundle_path` and the
unzipped Vault binary at the `vault_local_binary_path`. unzipped Vault binary at the `vault_local_binary_path`.
# CI Bootstrap
In order to execute any of the scenarios in this repository, it is first necessary to bootstrap the
CI AWS account with the required permissions and supporting AWS resources. There are two Terraform
modules which are used for this purpose, [service-user-iam](./ci/service-user-iam) for the account
permissions and [bootstrap](./ci/bootstrap) for the supporting resources.
**Supported Regions** - enos scenarios are supported in the following regions:
`"us-east-1", "us-east-2", "us-west-1", "us-west-2"`
## Bootstrap Process
These steps should be followed to bootstrap this repo for enos scenario execution:
### Set up CI service user IAM role
The service user that is used when executing enos scenarios from any GitHub Action workflow must have
a properly configured IAM role granting the access required to create resources in AWS. The
[service-user-iam](./ci/service-user-iam) module contains the IAM Policy and Role for that grants
this access. This module should be updated whenever a new AWS resource type is required for a scenario.
Since this is persistent and cannot be created and destroyed each time a scenario is run, the Terraform
state will be managed by Terraform Cloud. Here are the steps to configure the GitHub Actions service user:
#### Pre-requisites
- Access to the `hashicorp-qti` organization in Terraform Cloud.
- Full access to the CI AWS account is required.
**Notes:**
- For help with access to Terraform Cloud and the CI Account, contact the QT team on Slack (#team-quality)
for an invite. After receiving an invite to Terraform Cloud, a personal access token can be created
by clicking `User Settings` --> `Tokens` --> `Create an API token`.
- Access to the AWS account can be done via Doormat, at: https://doormat.hashicorp.services/.
- For the vault repo the account is: `vault_ci` and for the vault-enterprise repo, the account is:
`vault-enterprise_ci`.
- Access can be requested by clicking: `Cloud Access` --> `AWS` --> `Request Account Access`.
1. **Create the Terraform Cloud Workspace** - The name of the workspace to be created depends on the
repository for which it is being created, but the pattern is: `<repository>-ci-service-user-iam`,
e.g. `vault-ci-service-user-iam`. It is important that the execution mode for the workspace be set
to `local`. For help on setting up the workspace, contact the QT team on Slack (#team-quality)
2. **Execute the Terraform module**
```shell
> cd ./enos/ci/service-user-iam
> export TF_WORKSPACE=<repo name>-ci-service-user-iam
> export TF_TOKEN_app_terraform_io=<Terraform Cloud Token>
> export TF_VAR_repository=<repository name>
> terraform init
> terraform plan
> terraform apply -auto-approve
```
### Bootstrap the CI resources
Bootstrapping of the resources in the CI account is accomplished via the GitHub Actions workflow:
[enos-bootstrap-ci](../.github/workflows/enos-bootstrap-ci.yml). Before this workflow can be run a
workspace must be created as follows:
1. **Create the Terraform Cloud Workspace** - The name workspace to be created depends on the repository
for which it is being created, but the pattern is: `<repository>-ci-bootstrap`, e.g.
`vault-ci-bootstrap`. It is important that the execution mode for the workspace be set to
`local`. For help on setting up the workspace, contact the QT team on Slack (#team-quality).
Once the workspace has been created, changes to the bootstrap module will automatically be applied via
the GitHub PR workflow. Each time a PR is created for changes to files within that module the module
will be planned via the workflow described above. If the plan is ok and the PR is merged, the module
will automatically be applied via the same workflow.

66
enos/ci/bootstrap/main.tf Normal file
View File

@ -0,0 +1,66 @@
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
}
}
cloud {
hostname = "app.terraform.io"
organization = "hashicorp-qti"
// workspace must be exported in the environment as: TF_WORKSPACE=<vault|vault-enterprise>-ci-enos-boostrap
}
}
provider "aws" {
region = "us-east-1"
alias = "us_east_1"
}
provider "aws" {
region = "us-east-2"
alias = "us_east_2"
}
provider "aws" {
region = "us-west-1"
alias = "us_west_1"
}
provider "aws" {
region = "us-west-2"
alias = "us_west_2"
}
locals {
key_name = "enos-ci-ssh-key"
}
resource "aws_key_pair" "enos_ci_key_us_east_1" {
key_name = local.key_name
public_key = var.aws_ssh_public_key
provider = aws.us_east_1
}
resource "aws_key_pair" "enos_ci_key_us_east_2" {
key_name = local.key_name
public_key = var.aws_ssh_public_key
provider = aws.us_east_2
}
resource "aws_key_pair" "enos_ci_key_us_west_1" {
key_name = local.key_name
public_key = var.aws_ssh_public_key
provider = aws.us_west_1
}
resource "aws_key_pair" "enos_ci_key_us_west_2" {
key_name = local.key_name
public_key = var.aws_ssh_public_key
provider = aws.us_west_2
}

View File

@ -0,0 +1,20 @@
output "keys" {
value = {
"us-east-1" = {
name = aws_key_pair.enos_ci_key_us_east_1.key_name
arn = aws_key_pair.enos_ci_key_us_east_1.arn
}
"us-east-2" = {
name = aws_key_pair.enos_ci_key_us_east_2.key_name
arn = aws_key_pair.enos_ci_key_us_east_2.arn
}
"us-west-1" = {
name = aws_key_pair.enos_ci_key_us_west_1.key_name
arn = aws_key_pair.enos_ci_key_us_west_1.arn
}
"us-west-2" = {
name = aws_key_pair.enos_ci_key_us_west_2.key_name
arn = aws_key_pair.enos_ci_key_us_west_2.arn
}
}
}

View File

@ -0,0 +1,4 @@
variable "aws_ssh_public_key" {
description = "The public key to use for the ssh key"
type = string
}

View File

@ -0,0 +1,145 @@
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
}
}
cloud {
hostname = "app.terraform.io"
organization = "hashicorp-qti"
// workspace must be exported in the environment as: TF_WORKSPACE=<vault|vault-enterprise>-ci-enos-service-user-iam
}
}
provider "aws" {
region = "us-east-1"
}
locals {
enterprise_repositories = ["vault-enterprise"]
is_ent = contains(local.enterprise_repositories, var.repository)
ci_account_prefix = local.is_ent ? "vault-enterprise" : "vault"
service_user = "github_actions-${local.ci_account_prefix}_ci"
aws_account_id = local.is_ent ? "505811019928" : "040730498200"
}
resource "aws_iam_role" "role" {
name = local.service_user
assume_role_policy = data.aws_iam_policy_document.assume_role_policy_document.json
}
data "aws_iam_policy_document" "assume_role_policy_document" {
statement {
effect = "Allow"
actions = ["sts:AssumeRole"]
principals {
type = "AWS"
identifiers = ["arn:aws:iam::${local.aws_account_id}:user/${local.service_user}"]
}
}
}
resource "aws_iam_role_policy" "role_policy" {
role = aws_iam_role.role.name
name = "${local.service_user}_policy"
policy = data.aws_iam_policy_document.iam_policy_document.json
}
data "aws_iam_policy_document" "iam_policy_document" {
statement {
effect = "Allow"
actions = [
"iam:ListRoles",
"iam:CreateRole",
"iam:GetRole",
"iam:DeleteRole",
"iam:ListInstanceProfiles",
"iam:ListInstanceProfilesForRole",
"iam:CreateInstanceProfile",
"iam:GetInstanceProfile",
"iam:DeleteInstanceProfile",
"iam:ListPolicies",
"iam:CreatePolicy",
"iam:DeletePolicy",
"iam:ListRoles",
"iam:CreateRole",
"iam:AddRoleToInstanceProfile",
"iam:PassRole",
"iam:RemoveRoleFromInstanceProfile",
"iam:DeleteRole",
"iam:ListRolePolicies",
"iam:ListAttachedRolePolicies",
"iam:AttachRolePolicy",
"iam:GetRolePolicy",
"iam:PutRolePolicy",
"iam:DetachRolePolicy",
"iam:DeleteRolePolicy",
"ec2:DescribeAccountAttributes",
"ec2:DescribeInstanceTypes",
"ec2:DescribeInstanceCreditSpecifications",
"ec2:DescribeImages",
"ec2:DescribeTags",
"ec2:DescribeVpcClassicLink",
"ec2:DescribeVpcClassicLinkDnsSupport",
"ec2:DescribeNetworkInterfaces",
"ec2:DescribeAvailabilityZones",
"ec2:DescribeSecurityGroups",
"ec2:CreateSecurityGroup",
"ec2:AuthorizeSecurityGroupIngress",
"ec2:AuthorizeSecurityGroupEgress",
"ec2:DeleteSecurityGroup",
"ec2:RevokeSecurityGroupIngress",
"ec2:RevokeSecurityGroupEgress",
"ec2:DescribeInstances",
"ec2:DescribeInstanceAttribute",
"ec2:CreateTags",
"ec2:RunInstances",
"ec2:ModifyInstanceAttribute",
"ec2:TerminateInstances",
"ec2:ResetInstanceAttribute",
"ec2:DeleteTags",
"ec2:DescribeVolumes",
"ec2:CreateVolume",
"ec2:DeleteVolume",
"ec2:DescribeVpcs",
"ec2:DescribeVpcAttribute",
"ec2:CreateVPC",
"ec2:ModifyVPCAttribute",
"ec2:DeleteVPC",
"ec2:DescribeSubnets",
"ec2:CreateSubnet",
"ec2:ModifySubnetAttribute",
"ec2:DeleteSubnet",
"ec2:DescribeInternetGateways",
"ec2:CreateInternetGateway",
"ec2:AttachInternetGateway",
"ec2:DetachInternetGateway",
"ec2:DeleteInternetGateway",
"ec2:DescribeRouteTables",
"ec2:CreateRoute",
"ec2:CreateRouteTable",
"ec2:AssociateRouteTable",
"ec2:DisassociateRouteTable",
"ec2:DeleteRouteTable",
"ec2:CreateKeyPair",
"ec2:ImportKeyPair",
"ec2:DeleteKeyPair",
"ec2:DescribeKeyPairs",
"kms:ListKeys",
"kms:ListResourceTags",
"kms:GetKeyPolicy",
"kms:GetKeyRotationStatus",
"kms:DescribeKey",
"kms:CreateKey",
"kms:Encrypt",
"kms:Decrypt",
"kms:ScheduleKeyDeletion",
"kms:ListAliases",
"kms:CreateAlias",
"kms:DeleteAlias",
]
resources = ["*"]
}
}

View File

@ -0,0 +1,13 @@
output "ci_role" {
value = {
name = aws_iam_role.role.name
arn = aws_iam_role.role.arn
}
}
output "ci_role_policy" {
value = {
name = aws_iam_role_policy.role_policy.name
policy = aws_iam_role_policy.role_policy.policy
}
}

View File

@ -0,0 +1,8 @@
variable "repository" {
description = "The GitHub repository, either vault or vault-enterprise"
type = string
validation {
condition = contains(["vault", "vault-enterprise"], var.repository)
error_message = "Invalid repository, only vault or vault-enterprise are supported"
}
}