Skip to content

Commit 4f9e4ea

Browse files
committed
add aws deployment
1 parent b13b4ff commit 4f9e4ea

5 files changed

Lines changed: 335 additions & 0 deletions

File tree

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Example: AWS Account Integration with Jit
2+
# This example shows how to integrate a single AWS account with Jit
3+
4+
terraform {
5+
required_version = ">= 1.5"
6+
7+
required_providers {
8+
aws = {
9+
source = "hashicorp/aws"
10+
version = ">= 5.0"
11+
}
12+
}
13+
}
14+
15+
# Configure the AWS Provider
16+
provider "aws" {
17+
region = "us-east-1"
18+
}
19+
20+
# Single Account Integration Module
21+
module "jit_aws_account_integration" {
22+
source = "../"
23+
24+
# Jit API Configuration
25+
jit_client_id = var.jit_client_id # Set via environment variable or terraform.tfvars
26+
jit_secret = var.jit_secret # Set via environment variable or terraform.tfvars
27+
jit_region = "us" # Use "eu" for European API endpoint
28+
29+
# Integration Configuration
30+
integration_type = "account"
31+
aws_regions_to_monitor = ["us-east-1", "us-west-2"]
32+
33+
# Stack Configuration
34+
stack_name = "JitAccountIntegration"
35+
account_name = "Production Account" # Optional: Display name in Jit platform
36+
resource_name_prefix = "JitProd" # Optional: Prefix for CloudFormation resources
37+
38+
# CloudFormation Configuration
39+
capabilities = ["CAPABILITY_NAMED_IAM"]
40+
}
41+
42+
# Variables that should be defined in your root module or terraform.tfvars
43+
variable "jit_client_id" {
44+
description = "Jit API Client ID"
45+
type = string
46+
sensitive = true
47+
}
48+
49+
variable "jit_secret" {
50+
description = "Jit API Secret"
51+
type = string
52+
sensitive = true
53+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
locals {
2+
# JIT API Configuration
3+
jit_api_endpoint = var.jit_region == "us" ? "https://api.jit.io" : "https://api.eu.jit.io"
4+
5+
# CloudFormation template URLs based on integration type
6+
cloudformation_template_url = var.integration_type == "org" ? "https://jit-aws-prod.s3.amazonaws.com/jit_aws_org_integration_stack.json" : "https://jit-aws-prod.s3.amazonaws.com/jit_aws_integration_stack.json"
7+
8+
# Resource name prefix with integration-specific defaults
9+
resource_name_prefix = var.resource_name_prefix != null ? var.resource_name_prefix : (var.integration_type == "org" ? "JitOrg" : "Jit")
10+
11+
# Base extra parameters for state token request
12+
base_extra_params = {
13+
regions_to_monitor = var.aws_regions_to_monitor
14+
integration_type = var.integration_type
15+
}
16+
17+
# Additional parameters for organization integration
18+
org_extra_params = var.integration_type == "org" ? {
19+
organizationRootId = var.organization_root_id
20+
shouldIncludeRootAccount = var.should_include_root_account
21+
} : {}
22+
23+
# State token request body with correct structure
24+
state_token_request_body = {
25+
vendor = "aws"
26+
token_ttl = 1440
27+
extra = merge(
28+
local.base_extra_params,
29+
local.org_extra_params
30+
)
31+
}
32+
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# Authentication with JIT API to get access token
2+
data "http" "jit_auth" {
3+
url = "${local.jit_api_endpoint}/authentication/login"
4+
method = "POST"
5+
6+
request_headers = {
7+
"Accept" = "application/json"
8+
"Content-Type" = "application/json"
9+
}
10+
11+
request_body = jsonencode({
12+
clientId = var.jit_client_id
13+
secret = var.jit_secret
14+
})
15+
16+
lifecycle {
17+
postcondition {
18+
condition = self.status_code == 200
19+
error_message = "JIT authentication failed with status ${self.status_code}"
20+
}
21+
}
22+
}
23+
24+
# Create state token using shell script resource
25+
resource "shell_script" "jit_state_token" {
26+
triggers = {
27+
client_id = var.jit_client_id
28+
integration_type = var.integration_type
29+
regions = join(",", var.aws_regions_to_monitor)
30+
org_root_id = var.organization_root_id
31+
}
32+
33+
environment = {
34+
JIT_API_ENDPOINT = local.jit_api_endpoint
35+
STATE_TOKEN_BODY = jsonencode(local.state_token_request_body)
36+
}
37+
38+
sensitive_environment = {
39+
JIT_AUTH_RESPONSE = data.http.jit_auth.response_body
40+
}
41+
42+
lifecycle_commands {
43+
create = <<-EOT
44+
ACCESS_TOKEN=$(echo "$JIT_AUTH_RESPONSE" | jq -r '.accessToken')
45+
TOKEN=$(curl -s -X POST "$JIT_API_ENDPOINT/oauth/state-token" \
46+
-H "Authorization: Bearer $ACCESS_TOKEN" \
47+
-H "Accept: application/json" \
48+
-H "Content-Type: application/json" \
49+
-d "$STATE_TOKEN_BODY" \
50+
| jq -r '.token')
51+
echo "{\"token\": \"$TOKEN\"}"
52+
EOT
53+
54+
delete = "echo 'State token cleanup - no action needed'"
55+
}
56+
57+
lifecycle {
58+
ignore_changes = [environment, sensitive_environment]
59+
}
60+
61+
interpreter = ["/bin/bash", "-c"]
62+
63+
depends_on = [data.http.jit_auth]
64+
}
65+
66+
# CloudFormation Stack for single account integration
67+
resource "aws_cloudformation_stack" "jit_integration_account" {
68+
count = var.integration_type == "account" ? 1 : 0
69+
70+
name = var.stack_name
71+
template_url = local.cloudformation_template_url
72+
capabilities = var.capabilities
73+
74+
parameters = {
75+
"ExternalId" = shell_script.jit_state_token.output["token"]
76+
"ResourceNamePrefix" = local.resource_name_prefix
77+
"AccountName" = var.account_name
78+
"ShouldIncludeRootAccount" = tostring(var.should_include_root_account)
79+
}
80+
81+
lifecycle {
82+
prevent_destroy = true
83+
}
84+
85+
depends_on = [
86+
data.http.jit_auth,
87+
shell_script.jit_state_token
88+
]
89+
}
90+
91+
# CloudFormation StackSet for organization integration
92+
resource "aws_cloudformation_stack_set" "jit_integration_org" {
93+
count = var.integration_type == "org" ? 1 : 0
94+
95+
name = var.stack_name
96+
template_url = local.cloudformation_template_url
97+
capabilities = var.capabilities
98+
99+
parameters = {
100+
"ExternalId" = shell_script.jit_state_token.output["token"]
101+
"ResourceNamePrefix" = local.resource_name_prefix
102+
"OrganizationRootId" = var.organization_root_id
103+
"ShouldIncludeRootAccount" = tostring(var.should_include_root_account)
104+
}
105+
106+
# Auto deployment configuration for organization
107+
auto_deployment {
108+
enabled = true
109+
retain_stacks_on_account_removal = false
110+
}
111+
112+
# Permission model for organization deployment
113+
permission_model = "SERVICE_MANAGED"
114+
115+
lifecycle {
116+
prevent_destroy = true
117+
}
118+
119+
depends_on = [
120+
data.http.jit_auth,
121+
shell_script.jit_state_token
122+
]
123+
}
124+
125+
# StackSet instances for organization integration (deploys to all accounts in org)
126+
resource "aws_cloudformation_stack_set_instance" "jit_integration_org_instance" {
127+
count = var.integration_type == "org" ? 1 : 0
128+
129+
stack_set_name = aws_cloudformation_stack_set.jit_integration_org[0].name
130+
deployment_targets {
131+
organizational_unit_ids = [var.organization_root_id]
132+
}
133+
134+
operation_preferences {
135+
region_concurrency_type = "PARALLEL"
136+
max_concurrent_percentage = 100
137+
failure_tolerance_percentage = 10
138+
}
139+
140+
depends_on = [aws_cloudformation_stack_set.jit_integration_org]
141+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
variable "jit_client_id" {
2+
description = "Client ID for Jit API authentication"
3+
type = string
4+
sensitive = true
5+
}
6+
7+
variable "jit_secret" {
8+
description = "Secret for Jit API authentication"
9+
type = string
10+
sensitive = true
11+
}
12+
13+
variable "jit_region" {
14+
description = "Jit API region - determines which endpoint to use"
15+
type = string
16+
default = "us"
17+
validation {
18+
condition = contains(["us", "eu"], var.jit_region)
19+
error_message = "The jit_region must be either 'us' or 'eu'."
20+
}
21+
}
22+
23+
variable "integration_type" {
24+
description = "Type of AWS integration: 'account' for single account, 'org' for organization"
25+
type = string
26+
validation {
27+
condition = contains(["account", "org"], var.integration_type)
28+
error_message = "The integration_type must be either 'account' or 'org'."
29+
}
30+
}
31+
32+
variable "aws_regions_to_monitor" {
33+
description = "List of AWS regions to monitor"
34+
type = list(string)
35+
default = ["us-east-1"]
36+
}
37+
38+
variable "stack_name" {
39+
description = "Name for the CloudFormation stack or stackset"
40+
type = string
41+
default = "JitIntegrationStack"
42+
}
43+
44+
variable "account_name" {
45+
description = "Optional account name alias to be used in Jit platform. If not provided, the account ID will be displayed."
46+
type = string
47+
default = ""
48+
}
49+
50+
variable "resource_name_prefix" {
51+
description = "Prefix to use for the resources created by the CloudFormation template"
52+
type = string
53+
default = null
54+
validation {
55+
condition = var.resource_name_prefix == null || (
56+
length(var.resource_name_prefix) >= 1 &&
57+
length(var.resource_name_prefix) <= 40 &&
58+
can(regex("^[a-zA-Z0-9-_]*$", var.resource_name_prefix))
59+
)
60+
error_message = "The resource_name_prefix must be 1-40 characters and contain only alphanumeric characters, hyphens, and underscores."
61+
}
62+
}
63+
64+
variable "organization_root_id" {
65+
description = "AWS Organization Root ID (required for org integration type). Must start with 'r-'"
66+
type = string
67+
default = ""
68+
validation {
69+
condition = var.organization_root_id == "" || can(regex("^r-[a-z0-9]{4,32}$", var.organization_root_id))
70+
error_message = "The organization_root_id must be a valid organization root ID starting with 'r-' followed by 4-32 alphanumeric characters."
71+
}
72+
}
73+
74+
variable "should_include_root_account" {
75+
description = "Whether to include the root account in organization integration"
76+
type = bool
77+
default = false
78+
}
79+
80+
variable "capabilities" {
81+
description = "CloudFormation capabilities required for stack creation"
82+
type = list(string)
83+
default = ["CAPABILITY_NAMED_IAM"]
84+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
terraform {
2+
required_version = ">= 1.5"
3+
4+
required_providers {
5+
aws = {
6+
source = "hashicorp/aws"
7+
version = ">= 5.0"
8+
}
9+
10+
http = {
11+
source = "hashicorp/http"
12+
version = ">= 3.0"
13+
}
14+
15+
local = {
16+
source = "hashicorp/local"
17+
version = ">= 2.0"
18+
}
19+
20+
shell = {
21+
source = "scottwinkler/shell"
22+
version = ">= 1.7.0"
23+
}
24+
}
25+
}

0 commit comments

Comments
 (0)