├── terraform ├── vault-admin │ ├── provider.tf │ ├── backend.tf │ ├── output.tf │ ├── variables.tf │ ├── jwt_gitlab.tf │ └── README.md └── project │ ├── backend.tf │ ├── provider.tf │ ├── output.tf │ ├── vault.tf │ ├── variables.tf │ ├── userdata.sh │ ├── main.tf │ └── README.md ├── README.md ├── .gitignore └── .gitlab-ci.yml /terraform/vault-admin/provider.tf: -------------------------------------------------------------------------------- 1 | provider "vault" { 2 | } 3 | -------------------------------------------------------------------------------- /terraform/vault-admin/backend.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | vault = { 4 | source = "hashicorp/vault" 5 | version = "~>2.17.0" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /terraform/project/backend.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | vault = { 4 | source = "hashicorp/vault" 5 | version = "~>2.14.0" 6 | } 7 | aws = { 8 | source = "hashicorp/aws" 9 | version = "~>3.23" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /terraform/project/provider.tf: -------------------------------------------------------------------------------- 1 | provider "vault" { 2 | address = var.vault_addr 3 | } 4 | 5 | data "vault_aws_access_credentials" "creds" { 6 | backend = var.vault_backend 7 | role = var.vault_role 8 | type = "sts" 9 | } 10 | 11 | provider "aws" { 12 | region = var.region 13 | access_key = data.vault_aws_access_credentials.creds.access_key 14 | secret_key = data.vault_aws_access_credentials.creds.secret_key 15 | token = data.vault_aws_access_credentials.creds.security_token 16 | } 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vault demonstration with Gitlab-CI and AWS 2 | 3 | This repository is dedicated to the talk: **Secure your Terraform deploy in Gitlab-CI with Vault** 4 | 5 | **Disclaimer:** The repository is here for demonstration purpose. meaning: No best pratice and a lot of review. 6 | 7 | For the demonstration we need a [Gitlab repository using the CI](https://docs.gitlab.com/ce/ci/) and an [operational Vault](https://learn.hashicorp.com/collections/vault/day-one-consul) in a [AWS account](https://aws.amazon.com/fr/getting-started/). 8 | 9 | First, configure your Vault server. Check the [README](./terraform/vault-admin/README.md). 10 | 11 | Then, use Gitlab-CI to execute your Terraform for your project. Check the [README](./terraform/project/README.md) to know how to implement it. 12 | 13 | ## Contact 14 | 15 | You see something wrong ? You want extra information or more ? 16 | 17 | Contact me: <3exr269ch@mozmail.com> 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | 4 | # .tfstate files 5 | *.tfstate 6 | *.tfstate.* 7 | *.lock.hcl 8 | # Crash log files 9 | crash.log 10 | 11 | # Exclude all .tfvars files, which are likely to contain sentitive data, such as 12 | # password, private keys, and other secrets. These should not be part of version 13 | # control as they are data points which are potentially sensitive and subject 14 | # to change depending on the environment. 15 | # 16 | *.tfvars 17 | 18 | # Ignore override files as they are usually used to override resources locally and so 19 | # are not checked in 20 | override.tf 21 | override.tf.json 22 | *_override.tf 23 | *_override.tf.json 24 | 25 | # Include override files you do wish to add to version control using negated pattern 26 | # 27 | # !example_override.tf 28 | 29 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 30 | # example: *tfplan* 31 | 32 | # Ignore CLI configuration files 33 | .terraformrc 34 | terraform.rc 35 | -------------------------------------------------------------------------------- /terraform/vault-admin/output.tf: -------------------------------------------------------------------------------- 1 | output "pipeline_auth_path" { 2 | description = "The path of the Vault JWT auth backend for pipeline" 3 | value = vault_jwt_auth_backend.gitlab.path 4 | } 5 | 6 | output "pipeline_auth_role" { 7 | description = "The role name of the Vault JWT auth backend for pipeline" 8 | value = vault_jwt_auth_backend_role.pipeline.role_name 9 | } 10 | 11 | output "pipeline_path_secret" { 12 | description = "The path of the AWS secret engine for pipeline" 13 | value = vault_aws_secret_backend.aws.path 14 | } 15 | 16 | output "pipeline_role_secret" { 17 | description = "The role name of the AWS secret engine for pipeline" 18 | value = vault_aws_secret_backend_role.pipeline.name 19 | } 20 | 21 | output "project_path_secret" { 22 | description = "The path of the Database secret engine for project" 23 | value = vault_mount.db.path 24 | } 25 | 26 | output "project_policy_name" { 27 | description = "The policy project name who give acces for project secrets" 28 | value = vault_policy.project.name 29 | } 30 | -------------------------------------------------------------------------------- /terraform/project/output.tf: -------------------------------------------------------------------------------- 1 | output "web_instance_public_ip" { 2 | description = "The AWS EC2 instnce public ipv4" 3 | value = aws_instance.web.public_ip 4 | } 5 | 6 | output "web_endpoint" { 7 | description = "The endpoint to your website. Copy/paste the endpoint into a web browser to test it." 8 | value = "http://${aws_instance.web.public_ip}" 9 | } 10 | 11 | output "db_endpoint" { 12 | description = "The endpoint to the RDS database" 13 | value = aws_db_instance.web.endpoint 14 | } 15 | 16 | output "db_engine" { 17 | description = "The database engine used by your RDS database" 18 | value = aws_db_instance.web.engine 19 | } 20 | 21 | output "db_name" { 22 | description = "The database name created by your RDS database" 23 | value = aws_db_instance.web.name 24 | } 25 | 26 | output "db_user" { 27 | description = "The admin username of your database" 28 | value = aws_db_instance.web.username 29 | } 30 | 31 | output "vault_path_db_rotate" { 32 | description = "The Vault database secret path to rotate the root user password" 33 | value = "${local.db_backend}/rotate-root/mysql" 34 | } 35 | -------------------------------------------------------------------------------- /terraform/vault-admin/variables.tf: -------------------------------------------------------------------------------- 1 | variable "region" { 2 | description = "AWS regions" 3 | default = "eu-west-1" 4 | } 5 | 6 | variable "gitlab_domain" { 7 | description = "The domain name of your gitlab (e.g: gitlab.com)" 8 | } 9 | 10 | variable "gitlab_project_id" { 11 | description = "The pipeline ID to authorize to auth with Vault" 12 | } 13 | 14 | variable "vault_aws_assume_role" { 15 | description = "The AWS arn role for Vault to assume for AWS auth backend" 16 | } 17 | 18 | variable "application_aws_assume_role" { 19 | description = "The AWS arn role for Vault to assume for AWS Secret engine. The AWS credentials are pass to the application." 20 | } 21 | 22 | ##### OPTIONS ##### 23 | variable "project_name" { 24 | description = "Project name (ex: web)" 25 | default = "web" 26 | } 27 | 28 | variable "gitlab_project_branch" { 29 | description = "The pipeline project branch to authorize to auth with Vault" 30 | default = "master" 31 | } 32 | 33 | variable "aws_secret_default_ttl" { 34 | description = "The default lease ttl for AWS secret engine (default: 10min)" 35 | default = 600 36 | } 37 | 38 | variable "aws_secret_max_ttl" { 39 | description = "The max lease ttl for AWS secret engine (default: 15min)" 40 | default = 900 41 | } 42 | 43 | variable "jwt_token_max_ttl" { 44 | description = "The token max ttl for JWT auth backend (default: 15min)" 45 | default = 900 46 | } 47 | 48 | variable "jwt_auth_tune_default_ttl" { 49 | description = "The tune default lease ttl for JWT auth backend (default: 10min)" 50 | default = "10m" 51 | } 52 | variable "jwt_auth_tune_max_ttl" { 53 | description = "The tune max lease ttl for JWT auth backend (default: 15min)" 54 | default = "15m" 55 | } 56 | -------------------------------------------------------------------------------- /terraform/project/vault.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | db_backend = "${var.project_name}-db" 3 | aws_backend = "${var.project_name}-aws" 4 | } 5 | 6 | // Put secret into Vault 7 | resource "vault_database_secret_backend_connection" "mysql" { 8 | backend = local.db_backend 9 | name = "mysql" 10 | allowed_roles = [var.project_name] 11 | 12 | mysql { 13 | connection_url = "${aws_db_instance.web.username}:${random_password.password.result}@tcp(${aws_db_instance.web.endpoint})/" 14 | } 15 | } 16 | 17 | // Create a role for readonly user in database 18 | resource "vault_database_secret_backend_role" "role" { 19 | backend = local.db_backend 20 | name = var.project_name 21 | db_name = vault_database_secret_backend_connection.mysql.name 22 | creation_statements = ["CREATE USER '{{name}}'@'%' IDENTIFIED BY '{{password}}';GRANT SELECT ON *.* TO '{{name}}'@'%';"] 23 | default_ttl = var.db_secret_ttl 24 | } 25 | 26 | // Rotate root credentials 27 | /* 28 | data "vault_generic_endpoint" "db_rotate" { 29 | path = "${local.db_backend}/rotate-root/${var.project_name}" 30 | } 31 | */ 32 | 33 | // Allow the application to auth with AWS method 34 | resource "vault_aws_auth_backend_role" "web" { 35 | backend = local.aws_backend 36 | role = var.project_name 37 | auth_type = "ec2" 38 | bound_ami_ids = [data.aws_ami.amazon-linux-2.id] 39 | bound_account_ids = [data.aws_caller_identity.current.account_id] 40 | bound_vpc_ids = [data.aws_vpc.default.id] 41 | bound_subnet_ids = [aws_instance.web.subnet_id] 42 | bound_regions = [var.region] 43 | token_ttl = var.project_token_ttl 44 | token_max_ttl = var.project_token_max_ttl 45 | token_policies = ["default", var.project_name] 46 | } 47 | -------------------------------------------------------------------------------- /terraform/project/variables.tf: -------------------------------------------------------------------------------- 1 | # MANDATORY 2 | variable "vault_backend" { 3 | description = "Vault PATH backend to be authenticate." 4 | } 5 | 6 | variable "vault_role" { 7 | description = "Vault role name to use to be authenticate." 8 | } 9 | 10 | variable "vault_addr" { 11 | description = "The vault address (endpoint)." 12 | } 13 | 14 | ##### OPTIONS ##### 15 | variable "region" { 16 | description = "AWS regions" 17 | default = "eu-west-1" 18 | } 19 | 20 | variable "aws_instance_type" { 21 | description = "The AWS instance EC2 type (default: t3.micro)" 22 | default = "t3.micro" 23 | } 24 | 25 | variable "aws_db_instance_class" { 26 | description = "The RDS instance class (default: db.t3.micro)" 27 | default = "db.t3.micro" 28 | } 29 | 30 | variable "project_name" { 31 | description = "Project name (default: web)" 32 | default = "web" 33 | } 34 | 35 | variable "project_token_ttl" { 36 | description = "The Vault token default ttl (default: 1min)" 37 | default = 60 38 | } 39 | 40 | variable "project_token_max_ttl" { 41 | description = "The Vault token max ttl (default: 2min)" 42 | default = 120 43 | } 44 | 45 | variable "db_admin_username" { 46 | description = "The admin username of the database (default: admin)" 47 | default = "admin" 48 | } 49 | 50 | variable "secret_id_num_uses" { 51 | description = "The number uses for secret ID (default: 0)" 52 | default = 0 53 | } 54 | 55 | variable "secret_id_ttl" { 56 | description = "The secret ID TTL (default: 10min)" 57 | default = 600 58 | } 59 | 60 | variable "token_num_uses" { 61 | description = "The number uses for token (default: 0)" 62 | default = 0 63 | } 64 | 65 | variable "token_ttl" { 66 | description = "The token TTL (default: 1min)" 67 | default = 60 68 | } 69 | 70 | variable "token_max_ttl" { 71 | description = "The token max TTL (default: 10min)" 72 | default = 600 73 | } 74 | 75 | variable "db_secret_ttl" { 76 | description = "The secret database TTL (default: 1min)" 77 | default = 60 78 | } 79 | 80 | variable "vault_agent_version" { 81 | description = "The Vault Agent version used (default: 1.6.2)" 82 | default = "1.6.2" 83 | } 84 | 85 | variable "vault_agent_parameters" { 86 | description = "The parameters to pass as environment variables to your Vault Agent (ex: VAULT_NAMESPACE='test')" 87 | default = "" 88 | } 89 | -------------------------------------------------------------------------------- /terraform/project/userdata.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | yum update -y 3 | amazon-linux-extras install -y lamp-mariadb10.2-php7.2 php7.2 4 | yum install -y httpd curl unzip 5 | 6 | # Install Vault agent 7 | cd /home/ec2-user/ 8 | curl https://releases.hashicorp.com/vault/${vault_version}/vault_${vault_version}_linux_386.zip --output vault.zip 9 | unzip vault.zip 10 | rm -f vault.zip 11 | 12 | cat < vault-agent.hcl 13 | auto_auth { 14 | method { 15 | mount_path = "auth/${vault_auth_path}" 16 | type = "aws" 17 | config = { 18 | type = "ec2" 19 | role = "web" 20 | } 21 | } 22 | 23 | sink { 24 | type = "file" 25 | config = { 26 | path = "/home/ec2-user/.vault-token" 27 | } 28 | } 29 | } 30 | 31 | template { 32 | source = "/var/www/secrets.tpl" 33 | destination = "/var/www/secrets.json" 34 | } 35 | 36 | vault { 37 | address = "${vault_addr}" 38 | } 39 | EOT 40 | 41 | cat < /var/www/secrets.tpl 42 | { 43 | {{ with secret "web-db/creds/web" }} 44 | "username":"{{ .Data.username }}", 45 | "password":"{{ .Data.password }}", 46 | "db_host":"${db_host}", 47 | "db_name":"${db_name}" 48 | {{ end }} 49 | } 50 | EOT 51 | 52 | ${vault_agent_parameters} ./vault agent -config=vault-agent.hcl & 53 | 54 | cat << 'EOT' > /var/www/html/index.php 55 | 56 | 57 | Hello HashiTalk 2021! 58 | 59 | 60 | 61 | 62 |

Hello HashiTalk 2021!

63 | Attempting MySQL connection from php...
64 | {'username'}; 68 | $pass = json_decode($secrets_json)->{'password'}; 69 | $host = json_decode($secrets_json)->{'db_host'}; 70 | $dbname = json_decode($secrets_json)->{'db_name'}; 71 | } 72 | else{ 73 | echo "Secrets not found."; 74 | exit; 75 | } 76 | 77 | echo "Using database user: ".$user."
"; 78 | 79 | // Create connection 80 | $conn = new mysqli($host, $user, $pass, $dbname); 81 | 82 | // Check connection 83 | if ($conn->connect_error) { 84 | die("Connection failed: " . $conn->connect_error); 85 | } 86 | else { 87 | echo "Connected successfully.
"; 88 | } 89 | ?> 90 | 91 | 92 | EOT 93 | 94 | systemctl start httpd 95 | systemctl enable httpd 96 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: bash 2 | 3 | stages: 4 | - test 5 | - plan 6 | - deploy 7 | 8 | variables: 9 | TERRAFORM_VERSION: 0.14.6 10 | VAULT_VERSION: 1.6.2 11 | TF_VAR_vault_role: web-pipeline 12 | TF_VAR_vault_backend: web-aws 13 | 14 | before_script: 15 | - apk --update add curl unzip bash 16 | - cd /usr/local/bin/ 17 | - curl https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_386.zip --output terraform.zip 18 | - unzip terraform.zip 19 | - curl https://releases.hashicorp.com/vault/${VAULT_VERSION}/vault_${VAULT_VERSION}_linux_386.zip --output vault.zip 20 | - unzip vault.zip 21 | - cd - 22 | - terraform version 23 | - vault version 24 | 25 | vault_auth: 26 | stage: test 27 | script: 28 | - export VAULT_TOKEN="$(vault write -field=token auth/jwt/login role=$TF_VAR_vault_role jwt=$CI_JOB_JWT)" 29 | - vault token lookup 30 | 31 | plan: 32 | stage: plan 33 | artifacts: 34 | paths: 35 | - terraform/project/ 36 | expire_in: 1 day 37 | script: 38 | # Check job's ref name 39 | - echo $CI_COMMIT_REF_NAME 40 | # and is this ref protected 41 | - echo $CI_COMMIT_REF_PROTECTED 42 | # Authenticate and get token. Token expiry time and other properties can be configured 43 | # when configuring JWT Auth - https://www.vaultproject.io/api/auth/jwt#parameters-1 44 | - export VAULT_TOKEN="$(vault write -field=token auth/jwt/login role=$TF_VAR_vault_role jwt=$CI_JOB_JWT)" 45 | # Now use the VAULT_TOKEN to provide child token and execute Terraform in AWS env 46 | - cd terraform/project 47 | - export TF_VAR_vault_addr=$VAULT_ADDR 48 | - export TF_VAR_vault_agent_version=$VAULT_VERSION 49 | - terraform init 50 | - terraform plan 51 | 52 | apply: 53 | stage: deploy 54 | when: manual 55 | script: 56 | - export VAULT_TOKEN="$(vault write -field=token auth/jwt/login role=$TF_VAR_vault_role jwt=$CI_JOB_JWT)" 57 | # Now use the VAULT_TOKEN to provide child token and execute Terraform in AWS env 58 | - cd terraform/project 59 | - export TF_VAR_vault_addr=$VAULT_ADDR 60 | - export TF_VAR_vault_agent_version=$VAULT_VERSION 61 | - terraform init 62 | - terraform apply -auto-approve 63 | # - vault write -force $(terraform output -raw vault_path_db_rotate) 64 | 65 | destroy: 66 | stage: deploy 67 | when: manual 68 | script: 69 | - export VAULT_TOKEN="$(vault write -field=token auth/jwt/login role=$TF_VAR_vault_role jwt=$CI_JOB_JWT)" 70 | # Now use the VAULT_TOKEN to provide child token and execute Terraform in AWS env 71 | - cd terraform/project 72 | - export TF_VAR_vault_addr=$VAULT_ADDR 73 | - export TF_VAR_vault_agent_version=$VAULT_VERSION 74 | - terraform init 75 | - terraform destroy -auto-approve 76 | -------------------------------------------------------------------------------- /terraform/project/main.tf: -------------------------------------------------------------------------------- 1 | data "aws_caller_identity" "current" {} 2 | 3 | data "aws_vpc" "default" { 4 | default = true 5 | } 6 | 7 | data "aws_ami" "amazon-linux-2" { 8 | most_recent = true 9 | owners = ["amazon"] 10 | 11 | filter { 12 | name = "name" 13 | values = ["amzn2-ami-hvm*"] 14 | } 15 | } 16 | 17 | resource "aws_security_group" "web" { 18 | name = "${var.project_name}-web" 19 | description = "Allow HTTP inbound traffic" 20 | vpc_id = data.aws_vpc.default.id 21 | 22 | ingress { 23 | description = "Authorize HTTP interaction with web server" 24 | from_port = 80 25 | to_port = 80 26 | protocol = "tcp" 27 | cidr_blocks = ["0.0.0.0/0"] 28 | } 29 | 30 | egress { 31 | from_port = 0 32 | to_port = 0 33 | protocol = "-1" 34 | cidr_blocks = ["0.0.0.0/0"] 35 | } 36 | 37 | tags = { 38 | Name = var.project_name 39 | } 40 | } 41 | 42 | data "template_file" "userdata" { 43 | template = file("${path.module}/userdata.sh") 44 | vars = { 45 | vault_addr = var.vault_addr 46 | db_host = aws_db_instance.web.endpoint 47 | db_name = aws_db_instance.web.name 48 | vault_auth_path = local.aws_backend 49 | vault_version = var.vault_agent_version 50 | vault_agent_parameters = var.vault_agent_parameters 51 | } 52 | } 53 | 54 | resource "aws_instance" "web" { 55 | ami = data.aws_ami.amazon-linux-2.id 56 | instance_type = var.aws_instance_type 57 | vpc_security_group_ids = [aws_security_group.web.id] 58 | user_data = data.template_file.userdata.rendered 59 | monitoring = false 60 | 61 | tags = { 62 | Name = var.project_name 63 | } 64 | volume_tags = { 65 | Name = var.project_name 66 | } 67 | } 68 | 69 | resource "aws_security_group" "db" { 70 | name = "${var.project_name}-db" 71 | description = "Allow mysql inbound traffic" 72 | vpc_id = data.aws_vpc.default.id 73 | 74 | ingress { 75 | description = "Authorize HTTP interaction with webserv" 76 | from_port = 3306 77 | to_port = 3306 78 | protocol = "tcp" 79 | cidr_blocks = ["0.0.0.0/0"] 80 | } 81 | 82 | egress { 83 | from_port = 0 84 | to_port = 0 85 | protocol = "-1" 86 | cidr_blocks = ["0.0.0.0/0"] 87 | } 88 | 89 | tags = { 90 | Name = var.project_name 91 | } 92 | } 93 | 94 | resource "random_password" "password" { 95 | length = 16 96 | special = true 97 | override_special = "!#$%^&*()-_=+[]{}<>:?" 98 | } 99 | 100 | resource "aws_db_instance" "web" { 101 | allocated_storage = 20 102 | storage_type = "gp2" 103 | engine = "mysql" 104 | engine_version = "8.0.21" 105 | instance_class = var.aws_db_instance_class 106 | name = var.project_name 107 | username = var.db_admin_username 108 | password = random_password.password.result 109 | parameter_group_name = "default.mysql8.0" 110 | vpc_security_group_ids = [aws_security_group.db.id] 111 | publicly_accessible = true 112 | 113 | tags = { 114 | Name = var.project_name 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /terraform/vault-admin/jwt_gitlab.tf: -------------------------------------------------------------------------------- 1 | // INIT THE VAULT 2 | resource "vault_jwt_auth_backend" "gitlab" { 3 | description = "JWT auth backend for Gitlab-CI pipeline" 4 | path = "jwt" 5 | jwks_url = "https://${var.gitlab_domain}/-/jwks" 6 | bound_issuer = var.gitlab_domain 7 | default_role = "default" 8 | 9 | tune { 10 | default_lease_ttl = var.jwt_auth_tune_default_ttl 11 | max_lease_ttl = var.jwt_auth_tune_max_ttl 12 | token_type = "default-service" 13 | } 14 | } 15 | 16 | resource "vault_auth_backend" "aws" { 17 | description = "Auth backend to auth project in AWS env" 18 | type = "aws" 19 | path = "${var.project_name}-aws" 20 | } 21 | 22 | resource "vault_aws_auth_backend_sts_role" "role" { 23 | backend = vault_auth_backend.aws.path 24 | account_id = split(":", var.vault_aws_assume_role)[4] 25 | sts_role = var.vault_aws_assume_role 26 | } 27 | 28 | resource "vault_aws_secret_backend" "aws" { 29 | description = "AWS secret engine for Gitlab-CI pipeline" 30 | path = "${var.project_name}-aws" 31 | region = var.region 32 | 33 | default_lease_ttl_seconds = var.aws_secret_default_ttl 34 | max_lease_ttl_seconds = var.aws_secret_max_ttl 35 | } 36 | 37 | // PIPELINE 38 | data "vault_policy_document" "pipeline_aws_read" { 39 | rule { 40 | required_parameters = [] 41 | path = "${vault_aws_secret_backend.aws.path}/sts/${vault_aws_secret_backend_role.pipeline.name}" 42 | capabilities = ["read"] 43 | description = "Allow to read AWS secrets" 44 | } 45 | rule { 46 | required_parameters = [] 47 | path = "${vault_mount.db.path}/*" 48 | capabilities = ["read", "update", "create", "delete", "list"] 49 | description = "Allow to manage db secrets" 50 | } 51 | rule { 52 | required_parameters = [] 53 | path = "auth/${vault_auth_backend.aws.path}/*" 54 | capabilities = ["read", "update", "create", "delete", "list"] 55 | description = "Allow to manage authentification into the Vault with AWS auth method" 56 | } 57 | rule { 58 | required_parameters = [] 59 | path = "auth/token/create" 60 | capabilities = ["update"] 61 | description = "Allow pipeline to create a Vault child token when using Terraform" 62 | } 63 | } 64 | 65 | resource "vault_policy" "pipeline" { 66 | name = "${var.project_name}-pipeline" 67 | policy = data.vault_policy_document.pipeline_aws_read.hcl 68 | } 69 | 70 | resource "vault_jwt_auth_backend_role" "pipeline" { 71 | backend = vault_jwt_auth_backend.gitlab.path 72 | role_type = "jwt" 73 | 74 | role_name = "${var.project_name}-pipeline" 75 | token_policies = ["default", vault_policy.pipeline.name] 76 | 77 | bound_claims = { 78 | project_id = var.gitlab_project_id 79 | ref = var.gitlab_project_branch 80 | ref_type = "branch" 81 | } 82 | user_claim = "user_email" 83 | token_explicit_max_ttl = var.jwt_token_max_ttl 84 | } 85 | 86 | resource "vault_aws_secret_backend_role" "pipeline" { 87 | backend = vault_aws_secret_backend.aws.path 88 | name = "${var.project_name}-pipeline" 89 | credential_type = "assumed_role" 90 | 91 | role_arns = [var.application_aws_assume_role] 92 | 93 | policy_document = < This folder will only use the Vault provider and do not create AWS resources 7 | 8 | ## Prerequisite 9 | 10 | ### Before to start 11 | 12 | You should prepare your Vault and AWS environment. 13 | At this stage, we considere you use a Vault who is already operational. 14 | 15 | #### On the Vault side 16 | 17 | You should have configure: 18 | - 1 Vault user who is already created and authenticate into Vault server 19 | - The Vault user should have admin right 20 | 21 | #### On the AWS side 22 | 23 | You should have configure: 24 | - The Vault server should have a IAM role (instance profile if EC2 is used) 25 | - A Vault IAM role to the target AWS account where the project will be deployed. This role should be assumable by Vault server and give enough right to Vault to check identity (e.g: EC2) for the AWS auth backend 26 | - A pipeline IAM role to the target AWS account where the projet will be deployed. This role should be assumable by Vault server and give right for your pipeline (e.g: EC2:* or RDS:*). This role will be use by the AWS Secret engine. 27 | 28 | ##### Vault IAM role (instance profile) 29 | 30 | ``` 31 | { 32 | "Version": "2012-10-17", 33 | "Statement": { 34 | "Effect": "Allow", 35 | "Action": "sts:AssumeRole", 36 | "Resource": "*" 37 | } 38 | } 39 | ``` 40 | 41 | ##### Vault assumable role for AWS auth backend 42 | 43 | ``` 44 | { 45 | "Version": "2012-10-17", 46 | "Statement": [ 47 | { 48 | "Effect": "Allow", 49 | "Action": [ 50 | "ec2:DescribeInstances" 51 | ], 52 | "Resource": "*" 53 | } 54 | ] 55 | } 56 | ``` 57 | 58 | Modify the trust policy with the Vault source AWS account ID as a trust. 59 | 60 | ##### Vault assumable role for pipeline (AWS secret engine) 61 | 62 | This will use only AWS IAM managed policy (you can use least privilege with your own policy if need): 63 | - `AmazonRDSFullAccess` 64 | - `AmazonEC2FullAccess` 65 | - `IAMReadOnlyAccess` 66 | 67 | Modify the trust policy with the Vault source AWS account ID as a trust. 68 | 69 | ### Environment variables 70 | 71 | What you need to setup: 72 | - `VAULT_ADDR` environment variable with the Vault address 73 | - `VAULT_NAMESPACE` environment variable if you use namespace 74 | - `VAULT_TOKEN` environment variable with temporary Vault user 75 | admin variable. 76 | 77 | 78 | ### Provide terraform.tfvars file 79 | 80 | You should provide some required value for your Terraform. Keep it mind, the `.gitignore` 81 | will not push your `terraform.tfvars` file if you use it to setup your variables. 82 | 83 | Refer to the section `Input` below to check which variables to setup. 84 | 85 | ## What this Terraform do ? 86 | 87 | It will create JWT auth backend and AWS secret engine for pipeline, AWS auth backend and policy for project. 88 | 89 | ## Requirements 90 | 91 | | Name | Version | 92 | |------|---------| 93 | | vault | ~>2.17.0 | 94 | 95 | ## Providers 96 | 97 | | Name | Version | 98 | |------|---------| 99 | | vault | ~>2.17.0 | 100 | 101 | ## Modules 102 | 103 | No Modules. 104 | 105 | ## Resources 106 | 107 | | Name | 108 | |------| 109 | | [vault_auth_backend](https://registry.terraform.io/providers/hashicorp/vault/~>2.17.0/docs/resources/auth_backend) | 110 | | [vault_aws_auth_backend_sts_role](https://registry.terraform.io/providers/hashicorp/vault/~>2.17.0/docs/resources/aws_auth_backend_sts_role) | 111 | | [vault_aws_secret_backend](https://registry.terraform.io/providers/hashicorp/vault/~>2.17.0/docs/resources/aws_secret_backend) | 112 | | [vault_aws_secret_backend_role](https://registry.terraform.io/providers/hashicorp/vault/~>2.17.0/docs/resources/aws_secret_backend_role) | 113 | | [vault_jwt_auth_backend](https://registry.terraform.io/providers/hashicorp/vault/~>2.17.0/docs/resources/jwt_auth_backend) | 114 | | [vault_jwt_auth_backend_role](https://registry.terraform.io/providers/hashicorp/vault/~>2.17.0/docs/resources/jwt_auth_backend_role) | 115 | | [vault_mount](https://registry.terraform.io/providers/hashicorp/vault/~>2.17.0/docs/resources/mount) | 116 | | [vault_policy](https://registry.terraform.io/providers/hashicorp/vault/~>2.17.0/docs/resources/policy) | 117 | | [vault_policy_document](https://registry.terraform.io/providers/hashicorp/vault/~>2.17.0/docs/data-sources/policy_document) | 118 | 119 | ## Inputs 120 | 121 | | Name | Description | Type | Default | Required | 122 | |------|-------------|------|---------|:--------:| 123 | | application\_aws\_assume\_role | The AWS arn role for Vault to assume for AWS Secret engine. The AWS credentials are pass to the application. | `any` | n/a | yes | 124 | | aws\_secret\_default\_ttl | The default lease ttl for AWS secret engine (default: 10min) | `number` | `600` | no | 125 | | aws\_secret\_max\_ttl | The max lease ttl for AWS secret engine (default: 15min) | `number` | `900` | no | 126 | | gitlab\_domain | The domain name of your gitlab (e.g: gitlab.com) | `any` | n/a | yes | 127 | | gitlab\_project\_branch | The pipeline project branch to authorize to auth with Vault | `string` | `"master"` | no | 128 | | gitlab\_project\_id | The pipeline ID to authorize to auth with Vault | `any` | n/a | yes | 129 | | jwt\_auth\_tune\_default\_ttl | The tune default lease ttl for JWT auth backend (default: 10min) | `string` | `"10m"` | no | 130 | | jwt\_auth\_tune\_max\_ttl | The tune max lease ttl for JWT auth backend (default: 15min) | `string` | `"15m"` | no | 131 | | jwt\_token\_max\_ttl | The token max ttl for JWT auth backend (default: 15min) | `number` | `900` | no | 132 | | project\_name | Project name (ex: web) | `string` | `"web"` | no | 133 | | region | AWS regions | `string` | `"eu-west-1"` | no | 134 | | vault\_aws\_assume\_role | The AWS arn role for Vault to assume for AWS auth backend | `any` | n/a | yes | 135 | 136 | ## Outputs 137 | 138 | | Name | Description | 139 | |------|-------------| 140 | | pipeline\_auth\_path | The path of the Vault JWT auth backend for pipeline | 141 | | pipeline\_auth\_role | The role name of the Vault JWT auth backend for pipeline | 142 | | pipeline\_path\_secret | The path of the AWS secret engine for pipeline | 143 | | pipeline\_role\_secret | The role name of the AWS secret engine for pipeline | 144 | | project\_path\_secret | The path of the Database secret engine for project | 145 | | project\_policy\_name | The policy project name who give acces for project secrets | 146 | -------------------------------------------------------------------------------- /terraform/project/README.md: -------------------------------------------------------------------------------- 1 | # Presentation 2 | 3 | This Terraform folder is dedicated to Gitlab-CI (your pipeline). 4 | You should use Terraform only in Gitlab-ci for this project. 5 | 6 | It will deploy a website using Vault to get dynamic secrets to authenticate into a database. 7 | 8 | ## Prerequisite 9 | 10 | ### Before to start 11 | 12 | Refer to the [README](../vault-admin/README.md) to setup your Vault first. 13 | 14 | Then check the [gitlab-ci file](../../.gitlab-ci.yml). The **gitlab-ci** file will handle your CI and also execute your Terraform. 15 | 16 | ### About your Gitlab-CI file 17 | 18 | The **gitlab-ci.yml** will execute your CI inside Gitlab-CI and also deploy your project (using Terraform). 19 | 20 | The [gitlab-ci file](../../.gitlab-ci.yml) have 3 stages: 21 | - `test`: This stage have 1 job. This job is to check if your pipeline can be authenticate into your Vault through the JWT auth backend. 22 | - `plan`: This stage have 1 job. This job is to authenticate into your Vault through the JWT auth backend and do a `terraform plan`. It will also test if you can access to the AWS secret engine and generate AWS secrets. 23 | - `deploy`: The stage have 2 jobs. The job `apply` will use `terraform apply` based and the previous `terraform plan`. The second job `destroy` will use `terraform destroy` and will destroy your project infrastructure. Both need to be manually execute through Gitlab-CI. 24 | 25 | You have also a `before_script` where your Gitlab-ci will install **Vault** and **Terraform** binaries. 26 | 27 | ### Environment variables 28 | 29 | In your Gitlab project, in **Settings** and **CI/CD**, add a variable: 30 | - `VAULT_ADDR`: The value should be the address of your Vault server. 31 | 32 | ## What this Terraform do ? 33 | 34 | For the AWS side, it will create a RDS database with a mysql as a database engine and an EC2 instance with your website. 35 | 36 | For the Vault side : 37 | - The RDS secrets are stored into the `Database secret engine` in Vault. 38 | - The EC2 have a Vault role created in the `AWS auth backend` based on the EC2 metadata (e.g: account ID, subnet ID, etc). 39 | 40 | Your website will able to authenticate into Vault with `AWS auth backend` and then use Vault Database secrets to authenticate into the RDS database through the `Database secret engine`. 41 | 42 | ### Vault agent 43 | 44 | > WIP 45 | 46 | ## Requirements 47 | 48 | | Name | Version | 49 | |------|---------| 50 | | aws | ~>3.23 | 51 | | vault | ~>2.14.0 | 52 | 53 | ## Providers 54 | 55 | | Name | Version | 56 | |------|---------| 57 | | aws | ~>3.23 | 58 | | random | n/a | 59 | | template | n/a | 60 | | vault | ~>2.14.0 | 61 | 62 | ## Modules 63 | 64 | No Modules. 65 | 66 | ## Resources 67 | 68 | | Name | 69 | |------| 70 | | [aws_ami](https://registry.terraform.io/providers/hashicorp/aws/~>3.23/docs/data-sources/ami) | 71 | | [aws_caller_identity](https://registry.terraform.io/providers/hashicorp/aws/~>3.23/docs/data-sources/caller_identity) | 72 | | [aws_db_instance](https://registry.terraform.io/providers/hashicorp/aws/~>3.23/docs/resources/db_instance) | 73 | | [aws_instance](https://registry.terraform.io/providers/hashicorp/aws/~>3.23/docs/resources/instance) | 74 | | [aws_security_group](https://registry.terraform.io/providers/hashicorp/aws/~>3.23/docs/resources/security_group) | 75 | | [aws_vpc](https://registry.terraform.io/providers/hashicorp/aws/~>3.23/docs/data-sources/vpc) | 76 | | [random_password](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | 77 | | [template_file](https://registry.terraform.io/providers/hashicorp/template/latest/docs/data-sources/file) | 78 | | [vault_aws_access_credentials](https://registry.terraform.io/providers/hashicorp/vault/~>2.14.0/docs/data-sources/aws_access_credentials) | 79 | | [vault_aws_auth_backend_role](https://registry.terraform.io/providers/hashicorp/vault/~>2.14.0/docs/resources/aws_auth_backend_role) | 80 | | [vault_database_secret_backend_connection](https://registry.terraform.io/providers/hashicorp/vault/~>2.14.0/docs/resources/database_secret_backend_connection) | 81 | | [vault_database_secret_backend_role](https://registry.terraform.io/providers/hashicorp/vault/~>2.14.0/docs/resources/database_secret_backend_role) | 82 | 83 | ## Inputs 84 | 85 | | Name | Description | Type | Default | Required | 86 | |------|-------------|------|---------|:--------:| 87 | | aws\_db\_instance\_class | The RDS instance class (default: db.t3.micro) | `string` | `"db.t3.micro"` | no | 88 | | aws\_instance\_type | The AWS instance EC2 type (default: t3.micro) | `string` | `"t3.micro"` | no | 89 | | db\_admin\_username | The admin username of the database (default: admin) | `string` | `"admin"` | no | 90 | | db\_secret\_ttl | The secret database TTL (default: 1min) | `number` | `60` | no | 91 | | project\_name | Project name (default: web) | `string` | `"web"` | no | 92 | | project\_token\_max\_ttl | The Vault token max ttl (default: 2min) | `number` | `120` | no | 93 | | project\_token\_ttl | The Vault token default ttl (default: 1min) | `number` | `60` | no | 94 | | region | AWS regions | `string` | `"eu-west-1"` | no | 95 | | secret\_id\_num\_uses | The number uses for secret ID (default: 0) | `number` | `0` | no | 96 | | secret\_id\_ttl | The secret ID TTL (default: 10min) | `number` | `600` | no | 97 | | token\_max\_ttl | The token max TTL (default: 10min) | `number` | `600` | no | 98 | | token\_num\_uses | The number uses for token (default: 0) | `number` | `0` | no | 99 | | token\_ttl | The token TTL (default: 1min) | `number` | `60` | no | 100 | | vault\_addr | The vault address (endpoint). | `any` | n/a | yes | 101 | | vault\_agent\_parameters | The parameters to pass as environment variables to your Vault Agent (ex: VAULT\_NAMESPACE='test') | `string` | `""` | no | 102 | | vault\_agent\_version | The Vault Agent version used (default: 1.6.2) | `string` | `"1.6.2"` | no | 103 | | vault\_backend | Vault PATH backend to be authenticate. | `any` | n/a | yes | 104 | | vault\_role | Vault role name to use to be authenticate. | `any` | n/a | yes | 105 | 106 | ## Outputs 107 | 108 | | Name | Description | 109 | |------|-------------| 110 | | db\_endpoint | The endpoint to the RDS database | 111 | | db\_engine | The database engine used by your RDS database | 112 | | db\_name | The database name created by your RDS database | 113 | | db\_user | The admin username of your database | 114 | | vault\_path\_db\_rotate | The Vault database secret path to rotate the root user password | 115 | | web\_endpoint | The endpoint to your website. Copy/paste the endpoint into a web browser to test it. | 116 | | web\_instance\_public\_ip | The AWS EC2 instnce public ipv4 | 117 | --------------------------------------------------------------------------------