Skip to content

Commit

Permalink
🚩 (backend): Allows omitting the backend user and role (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
bendoerr authored Dec 12, 2023
1 parent 278f95a commit bcc2222
Show file tree
Hide file tree
Showing 9 changed files with 192 additions and 37 deletions.
16 changes: 12 additions & 4 deletions aws-iam-apply.tf
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,21 @@ data "aws_iam_policy_document" "apply_assume_role" {
}

dynamic "statement" {
for_each = range(length(coalesce(var.apply_role.extra_principals, [])))
for_each = range(length(coalesce(var.apply_role.extra_assume_statements, [])))
content {
sid = replace("${module.label_apply.id}-${statement.key + 1}", "-", "")
actions = ["sts:AssumeRole"]
actions = var.apply_role.extra_assume_statements[statement.key].actions
principals {
type = var.apply_role.extra_principals[statement.key].type
identifiers = var.apply_role.extra_principals[statement.key].identifiers
type = var.apply_role.extra_assume_statements[statement.key].principals.type
identifiers = var.apply_role.extra_assume_statements[statement.key].principals.identifiers
}
dynamic "condition" {
for_each = range(length(coalesce(var.apply_role.extra_assume_statements[statement.key].conditions, [])))
content {
test = var.apply_role.extra_assume_statements[statement.key].conditions[condition.key].test
variable = var.apply_role.extra_assume_statements[statement.key].conditions[condition.key].variable
values = var.apply_role.extra_assume_statements[statement.key].conditions[condition.key].values
}
}
}
}
Expand Down
27 changes: 21 additions & 6 deletions aws-iam-backend.tf
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ resource "aws_iam_access_key" "backend" {
}

data "aws_iam_user" "backend" {
count = var.backend_user.create ? 0 : 1
count = var.backend_user.create ? 0 : (var.backend_user.name != null ? 1 : 0)
user_name = var.backend_user.name
}

Expand Down Expand Up @@ -134,13 +134,21 @@ data "aws_iam_policy_document" "backend_assume_role" {
}

dynamic "statement" {
for_each = range(length(coalesce(var.backend_role.extra_principals, [])))
for_each = range(length(coalesce(var.backend_role.extra_assume_statements, [])))
content {
sid = replace("${module.label_backend[0].id}-${statement.key + 1}", "-", "")
actions = ["sts:AssumeRole"]
actions = var.backend_role.extra_assume_statements[statement.key].actions
principals {
type = var.backend_role.extra_principals[statement.key].type
identifiers = var.backend_role.extra_principals[statement.key].identifiers
type = var.backend_role.extra_assume_statements[statement.key].principals.type
identifiers = var.backend_role.extra_assume_statements[statement.key].principals.identifiers
}
dynamic "condition" {
for_each = range(length(coalesce(var.backend_role.extra_assume_statements[statement.key].conditions, [])))
content {
test = var.backend_role.extra_assume_statements[statement.key].conditions[condition.key].test
variable = var.backend_role.extra_assume_statements[statement.key].conditions[condition.key].variable
values = var.backend_role.extra_assume_statements[statement.key].conditions[condition.key].values
}
}
}
}
Expand All @@ -153,17 +161,24 @@ resource "aws_iam_role" "backend" {
assume_role_policy = data.aws_iam_policy_document.backend_assume_role[0].json
}

locals {
attach_dyndb = var.backend_role.create && (var.backend_role.dynamodb_policy.create || var.backend_role.dynamodb_policy.policy_arn != null)
attach_s3 = var.backend_role.create && (var.backend_role.s3_policy.create || var.backend_role.s3_policy.policy_arn != null)
}

data "aws_iam_role" "backend" {
count = var.backend_role.create ? 0 : 1
count = var.backend_role.create ? 0 : (local.attach_dyndb || local.attach_s3 ? 1 : 0)
name = var.backend_role.arn
}

resource "aws_iam_role_policy_attachment" "backend_dynamodb" {
count = local.attach_dyndb ? 1 : 0
role = var.backend_role.create ? aws_iam_role.backend[0].id : data.aws_iam_role.backend[0].id
policy_arn = var.backend_role.dynamodb_policy.create ? aws_iam_policy.backend_dynamodb_rw[0].arn : var.backend_role.dynamodb_policy.policy_arn
}

resource "aws_iam_role_policy_attachment" "backend_s3" {
count = local.attach_s3 ? 1 : 0
role = var.backend_role.create ? aws_iam_role.backend[0].id : data.aws_iam_role.backend[0].id
policy_arn = var.backend_role.s3_policy.create ? aws_iam_policy.backend_s3_rw[0].arn : var.backend_role.s3_policy.policy_arn
}
27 changes: 27 additions & 0 deletions examples/only_apply/ctx.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
terraform {
required_version = ">= 0.13"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}

provider "aws" {
region = "us-east-1"
}

variable "namespace" {
type = string
}

module "context" {
source = "bendoerr-terraform-modules/context/null"
version = "0.4.1"
namespace = var.namespace
environment = "example"
role = "tfuser"
region = "us-east-1"
project = "simple"
}
5 changes: 5 additions & 0 deletions examples/only_apply/infracost-usage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# You can use this file to define resource usage estimates for Infracost to use when calculating
# the cost of usage-based resource, such as AWS S3 or Lambda.
# `infracost breakdown --usage-file infracost-usage.yml [other flags]`
# See https://infracost.io/usage-file/ for docs
version: 0.1
28 changes: 28 additions & 0 deletions examples/only_apply/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module "tfuser" {
source = "../.."
context = module.context.shared

apply_user = {
create = true,
pgp_key = "keybase:bendoerr"
}

apply_role = {
create = true
budgets = true
dynamodb = true
ec2_account = true
ec2_networking = true
ec2_tags = true
ecs = true
efs = true
iam = true
kms = true
lambda = true
logs = true
route53 = true
s3 = true
sns = true
ssm_params = true
}
}
32 changes: 16 additions & 16 deletions outputs.tf
Original file line number Diff line number Diff line change
@@ -1,63 +1,63 @@
output "backend_user_arn" {
value = var.backend_user.create ? aws_iam_user.backend[0].arn : ""
value = try(aws_iam_user.backend[0].arn, data.aws_iam_user.backend[0].arn, null)
}

output "backend_user_name" {
value = var.backend_user.create ? aws_iam_user.backend[0].name : ""
value = try(aws_iam_user.backend[0].id, data.aws_iam_user.backend[0].user_name, null)
}

output "backend_user_unique_id" {
value = var.backend_user.create ? aws_iam_user.backend[0].unique_id : ""
value = try(aws_iam_user.backend[0].unique_id, data.aws_iam_user.backend[0].id, null)
}

output "backend_user_access_key_id" {
value = var.backend_user.create ? aws_iam_access_key.backend[0].id : ""
value = var.backend_user.create ? aws_iam_access_key.backend[0].id : null
}

output "backend_user_access_key_encrypted_secret" {
value = var.backend_user.create ? aws_iam_access_key.backend[0].encrypted_secret : ""
value = var.backend_user.create ? aws_iam_access_key.backend[0].encrypted_secret : null
}

output "backend_role_arn" {
value = var.backend_role.create ? aws_iam_role.backend[0].arn : data.aws_iam_role.backend[0].arn
value = try(aws_iam_role.backend[0].arn, data.aws_iam_role.backend[0].arn, null)
}

output "backend_role_name" {
value = var.backend_role.create ? aws_iam_role.backend[0].name : data.aws_iam_role.backend[0].name
value = try(aws_iam_role.backend[0].name, data.aws_iam_role.backend[0].name, null)
}

output "backend_dynamodb_rw_policy_arn" {
value = var.backend_role.dynamodb_policy.create ? aws_iam_policy.backend_s3_rw[0].arn : var.backend_role.s3_policy.policy_arn
value = try(aws_iam_policy.backend_dynamodb_rw[0].arn, var.backend_role.dynamodb_policy.policy_arn)
}

output "backend_s3_rw_policy_arn" {
value = var.backend_role.s3_policy.create ? aws_iam_policy.backend_s3_rw[0].arn : var.backend_role.s3_policy.policy_arn
value = try(aws_iam_policy.backend_s3_rw[0].arn, var.backend_role.s3_policy.policy_arn)
}

output "apply_user_arn" {
value = var.apply_user.create ? aws_iam_user.apply[0].arn : ""
value = try(aws_iam_user.apply[0].arn, data.aws_iam_user.apply[0].arn, null)
}

output "apply_user_name" {
value = var.apply_user.create ? aws_iam_user.apply[0].name : ""
value = try(aws_iam_user.apply[0].name, data.aws_iam_user.apply[0].user_name, null)
}

output "apply_user_unique_id" {
value = var.apply_user.create ? aws_iam_user.apply[0].unique_id : ""
value = try(aws_iam_user.apply[0].unique_id, data.aws_iam_user.apply[0].id, null)
}

output "apply_user_access_key_id" {
value = var.apply_user.create ? aws_iam_access_key.apply[0].id : ""
value = var.apply_user.create ? aws_iam_access_key.apply[0].id : null
}

output "apply_user_access_key_encrypted_secret" {
value = var.apply_user.create ? aws_iam_access_key.apply[0].encrypted_secret : ""
value = var.apply_user.create ? aws_iam_access_key.apply[0].encrypted_secret : null
}

output "apply_role_arn" {
value = var.apply_role.create ? aws_iam_role.apply[0].arn : data.aws_iam_role.apply[0].arn
value = try(aws_iam_role.apply[0].arn, data.aws_iam_role.apply[0].arn, null)
}

output "apply_role_name" {
value = var.apply_role.create ? aws_iam_role.apply[0].name : data.aws_iam_role.apply[0].name
value = try(aws_iam_role.apply[0].name, data.aws_iam_role.apply[0].name, null)
}
47 changes: 47 additions & 0 deletions test/examples_only_apply_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package test_test

import (
"context"
"strings"
"testing"

"github.com/gruntwork-io/terratest/modules/random"

"github.com/aws/aws-sdk-go-v2/config"
"github.com/gruntwork-io/terratest/modules/terraform"
test_structure "github.com/gruntwork-io/terratest/modules/test-structure"
)

func TestOnlyApply(t *testing.T) {
rootFolder := "../"
terraformFolderRelativeToRoot := "examples/only_apply"

tempTestFolder := test_structure.CopyTerraformFolderToTemp(t, rootFolder, terraformFolderRelativeToRoot)

rndns := strings.ToLower(random.UniqueId())

terraformOptions := &terraform.Options{
// The path to where our Terraform code is located
TerraformDir: tempTestFolder,
Upgrade: true,
Vars: map[string]interface{}{
"namespace": rndns,
},
}

// At the end of the test, run `terraform destroy` to clean up any resources that were created
defer terraform.Destroy(t, terraformOptions)

// This will run `terraform init` and `terraform apply` and fail the test if there are any errors
terraform.InitAndApply(t, terraformOptions)

// AWS Session
_, err := config.LoadDefaultConfig(
context.TODO(),
config.WithRegion("us-east-1"),
)

if err != nil {
t.Fatal(err)
}
}
2 changes: 2 additions & 0 deletions trivy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ scan:
- ./.infracost
- ./examples/simple/.terraform
- ./examples/simple/.infracost
- ./examples/only_apply/.terraform
- ./examples/only_apply/.infracost
45 changes: 34 additions & 11 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ variable "backend_user" {
force_destroy = optional(bool) # opt
pgp_key = optional(string) # req if create is true or invalid
})
default = {
create = false
}
nullable = false

# TODO Validation

Expand All @@ -33,26 +37,37 @@ variable "backend_role" {
create = bool
arn = optional(string) # opt, if create is false

extra_principals = optional(list(object({
type = string
identifiers = list(string)
extra_assume_statements = optional(list(object({
actions = list(string)
principals = object({
type = string
identifiers = list(string)
})
conditions = optional(list(object({
test = string
variable = string
values = list(string)
})))
})))

dynamodb_policy = object({
dynamodb_policy = optional(object({
create = bool
policy_arn = optional(string) # req, if create is false or invalid
table_arn = optional(string) # req, if create is true or invalid
kms_key = optional(string) # opt, if create is true or invalid
})
}), { create = false })

s3_policy = object({
s3_policy = optional(object({
create = bool
policy_arn = optional(string) # req, if create is false or invalid
bucket_arn = optional(string) # req, if create is true or invalid
kms_key = optional(string) # opt, if create is true or invalid
})

}), { create = false })
})
default = {
create = false
}
nullable = false

# TODO Validation
}
Expand All @@ -71,9 +86,17 @@ variable "apply_role" {
create = bool
arn = optional(string) # req, if create is false

extra_principals = optional(list(object({
type = string
identifiers = list(string)
extra_assume_statements = optional(list(object({
actions = list(string)
principals = object({
type = string
identifiers = list(string)
})
conditions = optional(list(object({
test = string
variable = string
values = list(string)
})))
})))

budgets = optional(bool, false)
Expand Down

0 comments on commit bcc2222

Please sign in to comment.