Skip to content

Commit

Permalink
Merge pull request #4 from terraform-modules/feature-access-keys
Browse files Browse the repository at this point in the history
add creation of access keys
  • Loading branch information
badra001 committed Jun 23, 2022
2 parents 831e208 + f8fb914 commit c695d4e
Show file tree
Hide file tree
Showing 13 changed files with 257 additions and 39 deletions.
32 changes: 22 additions & 10 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,47 @@
# Versions

* v1.0.0
## Version 1.x

* 1.0.0
- tag initial release for tf 0.11

* v2.0.0
## Version 2.x

* 2.0.0
- for tf-0.12 support

* v2.0.1
* 2.0.1
- add create_access_key variable, but do not implement
- format
- change saml_role to list

* v2.0.2
* 2.0.2
- do not include groups if groups empty
- add ```service_account``` variable (true|false)
- if service account, use contact vs user

* v2.1
* 2.1
- add pre-commit to generate docs

* v2.2
* 2.2
- add enable_sending_mail to enable email address in SES (this generates a request to the user)

* v2.3
* 2.3
- change default for enable_sending_mail to false

* v2.4.0 -- 20210405
* 2.4.0 -- 20210405
- use `ldap-get-attribute` to get email address

* v2.5.0 -- 20210512
* 2.5.0 -- 20210512
- add `attached_policies` to enable adding policies for an IAM account (generally, just services)

* v2.5.1 -- 20210526
* 2.5.1 -- 20210526
- change computing of output of user_password

* 2.6.0 -- 2022-05-20
- update bin/show-user-info.sh to accept TFCOMMAND for an alternate terraform binary, or to pull from the git root file

* 2.7.0 -- 2022-06-17
- add inline_policies
- add path
- enable code for creating access keys
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ module "admin_user_bond0007" {
# roles = [ ]
# saml_role = [ "role-name" ]
# create_access_keys = true
# inline_policies = [ { name = "my-policy", policy = data.aws_iam_policy_document.my-policy.json } ]
}
```

Expand All @@ -47,6 +48,9 @@ No requirements.
| Name | Version |
|------|---------|
| <a name="provider_aws"></a> [aws](#provider\_aws) | n/a |
| <a name="provider_local"></a> [local](#provider\_local) | n/a |
| <a name="provider_null"></a> [null](#provider\_null) | n/a |
| <a name="provider_time"></a> [time](#provider\_time) | n/a |

## Modules

Expand All @@ -58,11 +62,17 @@ No requirements.

| Name | Type |
|------|------|
| [aws_iam_access_key.iam_access_key_v1](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_access_key) | resource |
| [aws_iam_user.user](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user) | resource |
| [aws_iam_user_group_membership.user_groups](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user_group_membership) | resource |
| [aws_iam_user_login_profile.user](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user_login_profile) | resource |
| [aws_iam_user_policy.user_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user_policy) | resource |
| [aws_iam_user_policy_attachment.user_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user_policy_attachment) | resource |
| [aws_ses_email_identity.user_send_mail](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ses_email_identity) | resource |
| [local_file.access_key_file](https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/file) | resource |
| [local_file.rotate_keys_tfvars](https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/file) | resource |
| [null_resource.rotate_keys_tfvars](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource |
| [time_sleep.wait_iam_access_key_v1](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) | resource |
| [aws_arn.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/arn) | data source |
| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source |
Expand All @@ -72,13 +82,18 @@ No requirements.
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_attached_policies"></a> [attached\_policies](#input\_attached\_policies) | List of IAM Policy ARNs to attach to this user (only for service accounts) | `list(string)` | `[]` | no |
| <a name="input_create_access_keys"></a> [create\_access\_keys](#input\_create\_access\_keys) | Set to 1 or true to create access keys (not implemented) | `bool` | `false` | no |
| <a name="input_component_tags"></a> [component\_tags](#input\_component\_tags) | Additional tags for Components (role, policy) | `map(map(string))` | <pre>{<br> "policy": {},<br> "user": {}<br>}</pre> | no |
| <a name="input_create_access_keys"></a> [create\_access\_keys](#input\_create\_access\_keys) | Flag to create access keys | `bool` | `false` | no |
| <a name="input_email_address"></a> [email\_address](#input\_email\_address) | Email address for the user | `string` | n/a | yes |
| <a name="input_enable_sending_mail"></a> [enable\_sending\_mail](#input\_enable\_sending\_mail) | Enable using email address for the user from SES | `bool` | `false` | no |
| <a name="input_generate_password"></a> [generate\_password](#input\_generate\_password) | Flag to generate\_password upon creation (requires pgp\_key set) | `bool` | `false` | no |
| <a name="input_groups"></a> [groups](#input\_groups) | Groups to which the user belongs | `list(string)` | `[]` | no |
| <a name="input_iam_username"></a> [iam\_username](#input\_iam\_username) | AWS username for user (default a-username) | `string` | `""` | no |
| <a name="input_inline_policies"></a> [inline\_policies](#input\_inline\_policies) | List of IAM Policy Document objects to include in this role. Format is {name=name,policy=policy-json} | `list(object({ name = string, policy = string }))` | `[]` | no |
| <a name="input_path"></a> [path](#input\_path) | IAM path | `string` | `"/"` | no |
| <a name="input_pgp_key"></a> [pgp\_key](#input\_pgp\_key) | PGP key used to encrypt access key | `string` | `""` | no |
| <a name="input_pgp_key_file"></a> [pgp\_key\_file](#input\_pgp\_key\_file) | PGP key filename for encrypting the access key (supercedes pgp\_key) | `string` | `""` | no |
| <a name="input_profile"></a> [profile](#input\_profile) | AWS Profile Name, used generating key rotation file | `string` | `null` | no |
| <a name="input_reference"></a> [reference](#input\_reference) | Remedy ticket reference number for the user | `string` | `""` | no |
| <a name="input_roles"></a> [roles](#input\_roles) | Roles to which the user belongs | `list(string)` | `[]` | no |
| <a name="input_saml_role"></a> [saml\_role](#input\_saml\_role) | SAML role for user | `list(string)` | `[]` | no |
Expand All @@ -90,6 +105,8 @@ No requirements.

| Name | Description |
|------|-------------|
| <a name="output_aws_access_key_id"></a> [aws\_access\_key\_id](#output\_aws\_access\_key\_id) | AWS Access Key (current) |
| <a name="output_aws_secret_access_key"></a> [aws\_secret\_access\_key](#output\_aws\_secret\_access\_key) | AWS Secret Access Key [encrypted] (current) |
| <a name="output_user_arn"></a> [user\_arn](#output\_user\_arn) | User ARN |
| <a name="output_user_name"></a> [user\_name](#output\_user\_name) | User name |
| <a name="output_user_password"></a> [user\_password](#output\_user\_password) | User Password |
75 changes: 75 additions & 0 deletions access_keys.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
resource "aws_iam_access_key" "iam_access_key_v1" {
count = var.create_access_keys ? 1 : 0
user = aws_iam_user.user.name
pgp_key = local.pgp_key

depends_on = [time_sleep.wait_iam_access_key_v1]
}

# this gives a prior key time to be removed. When doing just one key like
# here, this may not be necessary
resource "time_sleep" "wait_iam_access_key_v1" {
count = var.create_access_keys ? 1 : 0

create_duration = "60s"
}

locals {
rotate_keys_tfvars = templatefile("${path.module}/templates/rotate-keys.tpl",
{
username = aws_iam_user.user.name
region = local.account_environment == "ew" ? "us-east-1" : "us-gov-east-1"
profile = var.profile == null ? "" : var.profile
pgp_key_file = fileexists(var.pgp_key_file) ? var.pgp_key_file : ""
})
access_key_file = templatefile("${path.module}/templates/access_key.tpl",
{
username = aws_iam_user.user.name
profile = var.profile == null ? "" : var.profile
account_id = local.account_id
create_date = var.create_access_keys ? aws_iam_access_key.iam_access_key_v1[0].create_date : ""
access_key_id = var.create_access_keys ? aws_iam_access_key.iam_access_key_v1[0].id : ""
encrypted_secret = var.create_access_keys ? aws_iam_access_key.iam_access_key_v1[0].encrypted_secret : ""
})
}

resource "null_resource" "rotate_keys_tfvars" {
count = var.create_access_keys ? 1 : 0
triggers = {
username = aws_iam_user.user.name
access_keys_directory = format("access_keys/%v", aws_iam_user.user.name)
}

provisioner "local-exec" {
when = create
command = "test -d ${path.root}/${self.triggers.access_keys_directory} || mkdir -p ${path.root}/${self.triggers.access_keys_directory}"
}

provisioner "local-exec" {
when = create
working_dir = self.triggers.access_keys_directory
command = "echo access_key.yml > .gitignore"
}

# provisioner "local-exec" {
# when = create
# working_dir = self.triggers.access_keys_directory
# command = "ln -sf ../../${var.ppg_key_file} ."
# }
}

resource "local_file" "rotate_keys_tfvars" {
count = var.create_access_keys ? 1 : 0
content = local.rotate_keys_tfvars
filename = var.create_access_keys ? format("%v/%v/variables.auto.tfvars", path.root, null_resource.rotate_keys_tfvars[0].triggers.access_keys_directory) : "empty.txt"

depends_on = [null_resource.rotate_keys_tfvars]
}

resource "local_file" "access_key_file" {
count = var.create_access_keys ? 1 : 0
content = local.access_key_file
filename = var.create_access_keys ? format("%v/%v/access_key.yml", path.root, null_resource.rotate_keys_tfvars[0].triggers.access_keys_directory) : "empty.txt"

depends_on = [null_resource.rotate_keys_tfvars]
}
7 changes: 7 additions & 0 deletions base_tags.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
locals {
base_tags = {
"boc:tf_module_version" = local._module_version
"boc:tf_module_name" = local._module_name
"boc:created_by" = "terraform"
}
}
49 changes: 48 additions & 1 deletion bin/show-user-info.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
#!/bin/bash

get_git_root()
{
TOP=$(git rev-parse --show-toplevel 2>/dev/null)
if [ -z "$TOP" ]
then
TOP=$HOME
fi
}

VERSION="1.1.0"
if [ ! -x /usr/bin/jq ]
then
echo "* no 'jq' program installed, cannot execute"
Expand All @@ -19,7 +29,44 @@ fi
VARS="user_name user_arn user_password"
declare -A values

jsondata=$(terraform output -json $MODULE)
# path or name of terraform binary
# get from top of git repo or $HOME/.tf-control
get_git_root
if [ -z "$TFCONTROL" ]
then
if [ -r $TOP/.tf-control ]
then
TFCONTROL=$TOP/.tf-control
elif [ -r $HOME/.tf-control ]
then
TFCONTROL=$HOME/.tf-control
fi
fi
if [ ! -z "$TFCONTROL" ]
then
source $TFCONTROL
fi

if [ -z $TFCOMMAND ]
then
TFCOMMAND="terraform"
fi

# look for config file
if [ -z "$TF_CLI_CONFIG_FILE" ]
then
if [ -r $TOP/.tf-control.tfrc ]
then
export TF_CLI_CONFIG_FILE=$TOP/.tf-control.tfrc
elif [ -r $HOME/.tf-control.tfrc ]
then
export TF_CLI_CONFIG_FILE=$HOME/.tf-control.tfrc
else
unset TF_CLI_CONFIG_FILE
fi
fi

jsondata=$($TERRAFORM output -json $MODULE)
echo "# $(date +%Y%m%d)"
for k in $VARS
do
Expand Down
16 changes: 0 additions & 16 deletions data.tf
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,3 @@ data "aws_arn" "current" {
}

data "aws_region" "current" {}

# output "caller_account_id" {
# value = data.aws_caller_identity.current.account_id
# }
#
# output "account_caller_arn" {
# value = data.aws_caller_identity.current.arn
# }
#
# output "account_caller_arn_partition" {
# value = data.aws_arn.current.partition
# }
#
# output "account_region_name" {
# value = data.aws_region.current.name
# }
6 changes: 6 additions & 0 deletions locals.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
locals {
# account_id = var.account_id != "" ? var.account_id : data.aws_caller_identity.current.account_id
account_id = data.aws_caller_identity.current.account_id
account_environment = data.aws_arn.current.partition == "aws-us-gov" ? "gov" : "ew"
region = data.aws_region.current.name
}
25 changes: 16 additions & 9 deletions main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
* # roles = [ ]
* # saml_role = [ "role-name" ]
* # create_access_keys = true
* # inline_policies = [ { name = "my-policy", policy = data.aws_iam_policy_document.my-policy.json } ]
* }
* ```
*/
Expand All @@ -45,8 +46,9 @@ locals {
username = lower(var.username)
iam_username = var.iam_username != "" ? var.iam_username : "a-${local.username}"
email_address = var.email_address != "" ? lower(var.email_address) : module.user_email.search_result["attribute_value"][0]
pgp_key_exists = var.pgp_key != "" ? 1 : 0
generate_password = var.generate_password ? local.pgp_key_exists : 0
pgp_key_exists = var.pgp_key != "" || fileexists(var.pgp_key_file)
pgp_key = fileexists(var.pgp_key_file) ? file(var.pgp_key_file) : var.pgp_key
generate_password = var.generate_password && local.pgp_key_exists
user_password = local.generate_password == 1 ? element(concat(aws_iam_user_login_profile.user[*].encrypted_password, tolist([""])), 0) : ""
ap1 = var.service_account ? var.attached_policies : []
ap2 = [for arn in local.ap1 : {
Expand All @@ -70,15 +72,12 @@ locals {
}

tags = merge(
local.base_tags,
var.tags,
local.tags_username,
local.tags_email[local.email_address != "" ? "exists" : "not_exists"],
local.tags_service,
local.tags_reference[var.reference != "" ? "exists" : "not_exists"],
{
"boc:tf_module_version" = local._module_version
"boc:created_by" = "terraform"
},
)
}

Expand All @@ -87,17 +86,19 @@ locals {
#---
resource "aws_iam_user" "user" {
name = local.iam_username
tags = local.tags
path = var.path
force_destroy = true

tags = local.tags
}

#---
# login profile (if genrate password)
#---
resource "aws_iam_user_login_profile" "user" {
count = local.generate_password
count = local.generate_password ? 1 : 0
user = aws_iam_user.user.name
pgp_key = var.pgp_key
pgp_key = local.generate_password ? local.pgp_key : null
password_reset_required = true

lifecycle {
Expand Down Expand Up @@ -139,3 +140,9 @@ resource "aws_iam_user_policy_attachment" "user_policy" {
policy_arn = each.value
}


resource "aws_iam_user_policy" "user_policy" {
for_each = { for p in var.inline_policies : p.name => p.policy }
user = aws_iam_user.user.name
policy = each.value
}
20 changes: 20 additions & 0 deletions outputs.access_keys.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
## output "aws_access_key_id_prev" {
## description = "AWS Access Key (previous)"
## value = ""
## }
##
## output "aws_secret_access_key_prev" {
## description = "AWS Secret Access Key [encrypted] (previous)"
## value = ""
## }

output "aws_access_key_id" {
description = "AWS Access Key (current)"
value = var.create_access_keys ? aws_iam_access_key.iam_access_key_v1[0].id : ""
}

output "aws_secret_access_key" {
description = "AWS Secret Access Key [encrypted] (current)"
value = var.create_access_keys ? aws_iam_access_key.iam_access_key_v1[0].encrypted_secret : ""
}

7 changes: 7 additions & 0 deletions templates/access_key.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
account: ${account_id}
profile: ${profile}
create_date: ${create_date}
username: ${username}
access_key_id: ${access_key_id}
access_secret_key_encrypted: ${encrypted_secret}
access_secret_key_encrypted:
Loading

0 comments on commit c695d4e

Please sign in to comment.