Skip to content

Commit

Permalink
Merge pull request #372 from lorengordon/improve-pattern
Browse files Browse the repository at this point in the history
  • Loading branch information
lorengordon authored Oct 24, 2022
2 parents 7a4aed5 + c5b4fec commit e4ddfe8
Show file tree
Hide file tree
Showing 8 changed files with 93 additions and 68 deletions.
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 1.0.3
current_version = 1.1.0
commit = True
message = Bumps version to {new_version}
tag = False
Expand Down
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/).

### 1.1.0

**Commit Delta**: [Change from 1.0.3 release](https://github.com/plus3it/terraform-aws-org-new-account-trust-policy/compare/1.0.3...1.1.0)

**Released**: 2022.10.24

**Summary**:

* Improves event pattern to eliminate loop/wait logic in lambda function.
* Separates the CreateAccountResult and InviteAccountToOrganization patterns into two event rules.

### 1.0.3

**Commit Delta**: [Change from 1.0.2 release](https://github.com/plus3it/terraform-aws-org-new-account-trust-policy/compare/1.0.2...1.0.3)
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ make mockstack/clean
| <a name="input_role_name"></a> [role\_name](#input\_role\_name) | Name of the IAM role to create in the target account (case sensitive) | `string` | n/a | yes |
| <a name="input_role_permission_policy"></a> [role\_permission\_policy](#input\_role\_permission\_policy) | AWS-managed permission policy name to attach to the role (case sensitive) | `string` | n/a | yes |
| <a name="input_trust_policy_json"></a> [trust\_policy\_json](#input\_trust\_policy\_json) | JSON-formatted string containing the role trust policy | `string` | n/a | yes |
| <a name="input_event_types"></a> [event\_types](#input\_event\_types) | Event types that will trigger this lambda | `set(string)` | <pre>[<br> "CreateAccountResult",<br> "InviteAccountToOrganization"<br>]</pre> | no |
| <a name="input_lambda"></a> [lambda](#input\_lambda) | Map of any additional arguments for the upstream lambda module. See <https://github.com/terraform-aws-modules/terraform-aws-lambda> | `any` | `{}` | no |
| <a name="input_log_level"></a> [log\_level](#input\_log\_level) | Log level of the lambda output, one of: debug, info, warning, error, critical | `string` | `"info"` | no |
| <a name="input_tags"></a> [tags](#input\_tags) | Tags that are passed to resources | `map(string)` | `{}` | no |
Expand Down
38 changes: 3 additions & 35 deletions lambda/src/new_account_iam_role.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import json
import os
import sys
import time

from aws_lambda_powertools import Logger
from aws_assume_role_lib import assume_role, generate_lambda_session_name
Expand Down Expand Up @@ -35,32 +34,9 @@ class IamRoleInvalidArgumentsError(Exception):
# Classes and functions specific to the Lambda event handler itself.


class AccountCreationFailedError(Exception):
"""Account creation failed."""


def get_new_account_id(event):
"""Return account id for new account events."""
create_account_status_id = (
event["detail"]
.get("responseElements", {})
.get("createAccountStatus", {})["id"] # fmt: no
)
LOG.info({"create_account_status_id": create_account_status_id})

org_client = boto3.client("organizations")
while True:
account_status = org_client.describe_create_account_status(
CreateAccountRequestId=create_account_status_id
)
state = account_status["CreateAccountStatus"]["State"].upper()
if state == "SUCCEEDED":
return account_status["CreateAccountStatus"]["AccountId"]
if state == "FAILED":
LOG.error({"create_account_status_failure": account_status})
raise AccountCreationFailedError
LOG.info({"create_account_status_state": state})
time.sleep(5)
return event["detail"]["serviceEventDetails"]["createAccountStatus"]["accountId"]


def get_invite_account_id(event):
Expand All @@ -72,15 +48,10 @@ def get_account_id(event):
"""Return account id for supported events."""
event_name = event["detail"]["eventName"]
get_account_id_strategy = {
"CreateAccount": get_new_account_id,
"CreateGovCloudAccount": get_new_account_id,
"CreateAccountResult": get_new_account_id,
"InviteAccountToOrganization": get_invite_account_id,
}
try:
account_id = get_account_id_strategy[event_name](event)
except (botocore.exceptions.ClientError, AccountCreationFailedError) as err:
raise AccountCreationFailedError(err) from err
return account_id
return get_account_id_strategy[event_name](event)


def get_partition():
Expand Down Expand Up @@ -263,9 +234,6 @@ def lambda_handler(event, context): # pylint: disable=unused-argument
try:
account_id = get_account_id(event)
partition = get_partition()
except AccountCreationFailedError as account_err:
LOG.error({"failure": account_err})
raise
except Exception:
LOG.exception("Unexpected, unknown exception in account logic")
raise
Expand Down
21 changes: 13 additions & 8 deletions lambda/tests/test_new_account_iam_role.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,24 +97,29 @@ def org_client(aws_credentials):
def mock_event(org_client):
"""Create an event used as an argument to the Lambda handler."""
org_client.create_organization(FeatureSet="ALL")
account_id = org_client.create_account(
AccountName=MOCK_ORG_NAME, Email=MOCK_ORG_EMAIL
)["CreateAccountStatus"]["Id"]
car_id = org_client.create_account(AccountName=MOCK_ORG_NAME, Email=MOCK_ORG_EMAIL)[
"CreateAccountStatus"
]["Id"]
create_account_status = org_client.describe_create_account_status(
CreateAccountRequestId=car_id
)
return {
"version": "0",
"id": str(uuid.uuid4()),
"detail-type": "AWS API Call via CloudTrail",
"detail-type": "AWS Service Event via CloudTrail",
"source": "aws.organizations",
"account": "222222222222",
"account": ACCOUNT_ID,
"time": datetime.now().isoformat(),
"region": AWS_REGION,
"resources": [],
"detail": {
"eventName": "CreateAccount",
"eventName": "CreateAccountResult",
"eventSource": "organizations.amazonaws.com",
"responseElements": {
"serviceEventDetails": {
"createAccountStatus": {
"id": account_id,
"accountId": create_account_status["CreateAccountStatus"][
"AccountId"
]
}
},
},
Expand Down
63 changes: 43 additions & 20 deletions main.tf
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
locals {
name = "new_account_iam_role_${random_string.id.result}"
name = "new-account-iam-role-${random_string.id.result}"
}

data "aws_partition" "current" {}
Expand Down Expand Up @@ -29,9 +29,9 @@ data "aws_iam_policy_document" "lambda" {
module "lambda" {
source = "git::https://github.com/terraform-aws-modules/terraform-aws-lambda.git?ref=v4.2.0"

function_name = local.name
function_name = "${local.name}-${var.role_name}"

description = "Create new IAM Account Role"
description = "Create new account IAM Role - ${var.role_name}"
handler = "new_account_iam_role.lambda_handler"
runtime = "python3.8"
timeout = 300
Expand Down Expand Up @@ -69,34 +69,57 @@ resource "random_string" "id" {
special = false
}

locals {
event_types = {
CreateAccountResult = jsonencode(
{
"source" : ["aws.organizations"],
"detail-type" : ["AWS Service Event via CloudTrail"]
"detail" : {
"eventSource" : ["organizations.amazonaws.com"],
"eventName" : ["CreateAccountResult"]
"serviceEventDetails" : {
"createAccountStatus" : {
"state" : ["SUCCEEDED"]
}
}
}
}
)
InviteAccountToOrganization = jsonencode(
{
"source" : ["aws.organizations"],
"detail-type" : ["AWS API Call via CloudTrail"]
"detail" : {
"eventSource" : ["organizations.amazonaws.com"],
"eventName" : ["InviteAccountToOrganization"]
}
}
)
}
}

resource "aws_cloudwatch_event_rule" "this" {
name = local.name
for_each = var.event_types

name = "${local.name}-${each.value}"
description = "Managed by Terraform"
event_pattern = <<-PATTERN
{
"source": ["aws.organizations"],
"detail-type": ["AWS API Call via CloudTrail"],
"detail": {
"eventSource": ["organizations.amazonaws.com"],
"eventName": [
"InviteAccountToOrganization",
"CreateAccount",
"CreateGovCloudAccount"
]
}
}
PATTERN
event_pattern = local.event_types[each.value]
tags = var.tags
}

resource "aws_cloudwatch_event_target" "this" {
rule = aws_cloudwatch_event_rule.this.name
for_each = aws_cloudwatch_event_rule.this

rule = each.value.name
arn = module.lambda.lambda_function_arn
}

resource "aws_lambda_permission" "events" {
for_each = aws_cloudwatch_event_rule.this

action = "lambda:InvokeFunction"
function_name = module.lambda.lambda_function_name
principal = "events.amazonaws.com"
source_arn = aws_cloudwatch_event_rule.this.arn
source_arn = each.value.arn
}
11 changes: 7 additions & 4 deletions tests/test_terraform_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,19 +137,22 @@ def test_outputs(tf_output):
"lambda",
]

prefix = "new_account_iam_role"
prefix = "new-account-iam-role"

lambda_module = tf_output["lambda"]
assert lambda_module["lambda_function_name"].startswith(prefix)

event_rule_output = tf_output["aws_cloudwatch_event_rule"]
assert event_rule_output["name"].startswith(prefix)
for _, event_rule in event_rule_output.items():
assert event_rule["name"].startswith(prefix)

event_target_output = tf_output["aws_cloudwatch_event_target"]
assert event_target_output["rule"].startswith(prefix)
for _, event_target in event_target_output.items():
assert event_target["rule"].startswith(prefix)

permission_events_output = tf_output["aws_lambda_permission_events"]
assert permission_events_output["function_name"].startswith(prefix)
for _, lambda_permission in permission_events_output.items():
assert lambda_permission["function_name"].startswith(prefix)


def test_lambda_dry_run(tf_output, localstack_session):
Expand Down
14 changes: 14 additions & 0 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,20 @@ variable "trust_policy_json" {
type = string
}

variable "event_types" {
description = "Event types that will trigger this lambda"
type = set(string)
default = [
"CreateAccountResult",
"InviteAccountToOrganization",
]

validation {
condition = alltrue([for event in var.event_types : contains(["CreateAccountResult", "InviteAccountToOrganization"], event)])
error_message = "Supported event_types include only: CreateAccountResult, InviteAccountToOrganization"
}
}

variable "lambda" {
description = "Map of any additional arguments for the upstream lambda module. See <https://github.com/terraform-aws-modules/terraform-aws-lambda>"
type = any
Expand Down

0 comments on commit e4ddfe8

Please sign in to comment.