Skip to content

add creation of access keys #4

Merged
merged 11 commits into from
Jun 23, 2022
Merged
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
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