├── .gitignore ├── LICENSE ├── README.md ├── assume_role.sh └── main.tf /.gitignore: -------------------------------------------------------------------------------- 1 | .terraform 2 | *.tfstate* 3 | terraform.tfstate 4 | terraform.tfvars 5 | 6 | .idea/ 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Ollie Petch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AWS CLI Resource Terraform Module 2 | ================================= 3 | 4 | This module is used to create a AWS resource in terraform by calling out to the CLI, with support for cross account resource creation (see example). The module encapsulates the act of assuming a role, when required to create the resource in the specified account. 5 | 6 | Use Cases 7 | -------- 8 | * Situations where it's not possible to create resources in the desired configuration due to cross account permissions. (see example) 9 | * When terraform does not yet have support for a resource and resorting to CLI is a necessary compromise 10 | 11 | Prerequisites 12 | ------------- 13 | * Must be using sts assume role as a means to authenticate (where a role is supplied). 14 | * The credentials terraform is using to run must resolve to a identity with permissions allowing assuming of the roles the module is configured to use (where a role is supplied). 15 | * `cksum`, `grep`, `awk` must be available (as they are on most systems) 16 | 17 | Example Usage 18 | ------------- 19 | This module was created initially to combat the issue with the aws provider for terraform where it is not possible to associate a route53 zone to a VPC in a different account [Issue 617 (aws-provider)](https://github.com/terraform-providers/terraform-provider-aws/issues/617) and [Issue 12465](https://github.com/hashicorp/terraform/issues/12465) for more details. 20 | 21 | Use of the CLI circumvents the bug mentioned above and the inclusion of the sts assume role support alleviates some of the pain caused by [Issue 3 (null provider)](https://github.com/terraform-providers/terraform-provider-null/issues/3) 22 | 23 | ```hcl 24 | locals { 25 | cli_flags = "--hosted-zone-id SOME_HOSTEDZONEID --vpc VPCRegion=eu-west-1,VPCId=vpc-abc123xyz" 26 | } 27 | 28 | module "create_vpc_association_authorization" { 29 | source = "github.com/opetch/terraform-aws-cli-resource" 30 | 31 | account_id = "123456789" # Account with the private hosted zone 32 | role = "TF_Role" 33 | cmd = "aws route53 create-vpc-association-authorization ${local.cli_flags}" 34 | destroy_cmd = "aws route53 delete-vpc-association-authorization ${local.cli_flags}" 35 | } 36 | 37 | module "associate_vpc_with_zone" { 38 | source = "github.com/opetch/terraform-aws-cli-resource" 39 | 40 | # Uses the default provider account id if no account id is passed in 41 | role = "TF_Role" 42 | cmd = "aws route53 associate-vpc-with-hosted-zone ${local.cli_flags}" 43 | destroy_cmd = "aws route53 disassociate-vpc-from-hosted-zone ${local.cli_flags}" 44 | 45 | # Require that the above resource is created first 46 | dependency_ids = ["${module.create_vpc_association_authorization.id}"] 47 | } 48 | ``` 49 | 50 | Terraform version 51 | ----------------- 52 | Terraform version 0.11.3 has been used when creating the module, however many previous versions should work also but have not been tested. 53 | 54 | TODO 55 | ---- 56 | - [ ] Add variable region, current implementation relies on aws config having a default set to the value required. 57 | -------------------------------------------------------------------------------- /assume_role.sh: -------------------------------------------------------------------------------- 1 | if [ "$#" -ne 2 ] 2 | then 3 | echo "Usage: source assume_role.sh [account_id] [role]" 4 | exit 1 5 | fi 6 | 7 | ACCOUNT="$1" 8 | ROLE="$2" 9 | 10 | role_session_name=`cat /proc/sys/kernel/random/uuid 2>/dev/null || date | cksum | cut -d " " -f 1` 11 | aws_creds=$(aws sts assume-role --role-arn arn:aws:iam::${ACCOUNT}:role/$ROLE --role-session-name $role_session_name --duration-seconds 3600 --output json) 12 | 13 | if [ "$?" -ne 0 ] 14 | then 15 | exit 1 16 | fi 17 | 18 | export AWS_ACCESS_KEY_ID=$(echo "${aws_creds}" | grep AccessKeyId | awk -F'"' '{print $4}' ) 19 | export AWS_SECRET_ACCESS_KEY=$(echo "${aws_creds}" | grep SecretAccessKey | awk -F'"' '{print $4}' ) 20 | export AWS_SESSION_TOKEN=$(echo "${aws_creds}" | grep SessionToken | awk -F'"' '{print $4}' ) 21 | export AWS_SECURITY_TOKEN=$(echo "${aws_creds}" | grep SessionToken | awk -F'"' '{print $4}' ) 22 | echo "session '$role_session_name' valid for 60 minutes" 23 | -------------------------------------------------------------------------------- /main.tf: -------------------------------------------------------------------------------- 1 | variable "cmd" { 2 | description = "The command used to create the resource." 3 | } 4 | 5 | variable "destroy_cmd" { 6 | description = "The command used to destroy the resource." 7 | } 8 | 9 | variable "account_id" { 10 | description = "The account that holds the role to assume in. Will use providers account by default" 11 | default = "0" 12 | } 13 | 14 | variable "role" { 15 | description = "The role to assume in order to run the cli command." 16 | default = "0" 17 | } 18 | 19 | variable "dependency_ids" { 20 | description = "IDs or ARNs of any resources that are a dependency of the resource created by this module." 21 | type = "list" 22 | default = [] 23 | } 24 | 25 | data "aws_caller_identity" "id" {} 26 | 27 | locals { 28 | account_id = "${var.account_id == 0 ? data.aws_caller_identity.id.account_id : var.account_id}" 29 | assume_role_cmd = "source ${path.module}/assume_role.sh ${local.account_id} ${var.role}" 30 | } 31 | 32 | resource "null_resource" "cli_resource" { 33 | provisioner "local-exec" { 34 | when = "create" 35 | command = "/bin/bash -c '${var.role == 0 ? "" : "${local.assume_role_cmd} && "}${var.cmd}'" 36 | } 37 | 38 | provisioner "local-exec" { 39 | when = "destroy" 40 | command = "/bin/bash -c '${var.role == 0 ? "" : "${local.assume_role_cmd} && "}${var.destroy_cmd}'" 41 | } 42 | 43 | # By depending on the null_resource, the cli resource effectively depends on the existance 44 | # of the resources identified by the ids provided via the dependency_ids list variable. 45 | depends_on = ["null_resource.dependencies"] 46 | } 47 | 48 | resource "null_resource" "dependencies" { 49 | triggers = { 50 | dependencies = "${join(",", var.dependency_ids)}" 51 | } 52 | } 53 | 54 | output "id" { 55 | description = "The ID of the null_resource used to provison the resource via cli. Useful for creating dependencies between cli resources" 56 | value = "${null_resource.cli_resource.id}" 57 | } 58 | --------------------------------------------------------------------------------