Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
2 changes: 1 addition & 1 deletion .tool-versions
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
nodejs 18.12.1
terraform 1.13.3
trivy 0.47.0
trivy 0.69.2
2 changes: 2 additions & 0 deletions scripts/generateAdvancedAWS.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const {
applyAwsRegion,
applyAwsSecurityGroup,
applyAwsVpc,
applyAwsIamRole,
} = require('../dist/generators/addons/aws/modules/index.js');
const { applyAdvancedTemplate } = require('../dist/generators/addons/aws/advanced.js');

Expand All @@ -19,5 +20,6 @@ const { applyAdvancedTemplate } = require('../dist/generators/addons/aws/advance
await applyAwsVpc(options);
await applyAwsSecurityGroup(options);
await applyAwsIamUserAndGroup(options);
await applyAwsIamRole(options);
await applyAdvancedTemplate(options);
})();
7 changes: 7 additions & 0 deletions src/generators/addons/aws/dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
applyAwsSecurityGroup,
applyAwsSsm,
applyAwsVpc,
applyAwsIamRole,
} from '@/generators/addons/aws/modules';
import { containsContent, isExisting } from '@/helpers/file';

Expand Down Expand Up @@ -85,6 +86,12 @@ const AWS_MODULES: Record<AwsModuleName | string, AwsModule> = {
mainContent: 'module "ssm"',
applyModuleFunction: (options: AwsOptions) => applyAwsSsm(options),
},
iamRole: {
name: 'iamRole',
path: 'modules/iam_role',
mainContent: 'module "iam_role"',
applyModuleFunction: (options: AwsOptions) => applyAwsIamRole(options),
},
};

const isAwsModuleAdded = (
Expand Down
2 changes: 2 additions & 0 deletions src/generators/addons/aws/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
applyAwsRegion,
applyAwsSecurityGroup,
applyAwsVpc,
applyAwsIamRole,
} from './modules';

const awsChoices = [
Expand Down Expand Up @@ -69,6 +70,7 @@ const generateAwsTemplate = async (
await applyAwsVpc(awsOptions);
await applyAwsSecurityGroup(awsOptions);
await applyAwsIamUserAndGroup(awsOptions);
await applyAwsIamRole(awsOptions);
await applyAdvancedTemplate(awsOptions);

break;
Expand Down
55 changes: 36 additions & 19 deletions src/generators/addons/aws/modules/bastion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
INFRA_CORE_LOCALS_PATH,
INFRA_CORE_MAIN_PATH,
INFRA_CORE_VARIABLES_PATH,
INFRA_CORE_DATA_PATH,
} from '@/generators/terraform/constants';
import { appendToFile, copy } from '@/helpers/file';

Expand All @@ -18,19 +19,23 @@ import {
AWS_TEMPLATE_PATH,
} from '../constants';

const bastionDataContent = dedent`
### Begin Bastion Host ###
data "aws_iam_policy" "ssm_managed_instance_core" {
name = "AmazonSSMManagedInstanceCore"
}
### End Bastion Host ###`;

const bastionLocalContent = dedent`
### Begin Bastion Host ###
locals {
enable_bastion = true
enable_bastion = true
bastion_ssm_role_name = "\${local.env_namespace}-SSMInstanceRole"
bastion_ssm_policy_arns = [data.aws_iam_policy.ssm_managed_instance_core.arn]
}
### End Bastion Host ###`;

const bastionVariablesContent = dedent`
variable "bastion_image_id" {
description = "The AMI image ID for the bastion instance"
default = "ami-0801a1e12f4a9ccc0"
}

variable "bastion_instance_type" {
description = "The bastion instance type"
default = "t3.nano"
Expand All @@ -52,16 +57,26 @@ const bastionVariablesContent = dedent`
}`;

const bastionModuleContent = dedent`
module "bastion_ssm_role" {
count = local.enable_bastion ? 1 : 0
source = "../modules/iam_role"

role_name = local.bastion_ssm_role_name
assume_role_services = ["ec2.amazonaws.com"]
policy_arns = local.bastion_ssm_policy_arns
create_instance_profile = true
}

module "bastion" {
count = local.enable_bastion ? 1 : 0
source = "../modules/bastion"

subnet_ids = module.vpc.public_subnet_ids
instance_security_group_ids = module.security_group.bastion_security_group_ids

env_namespace = local.env_namespace
image_id = var.bastion_image_id
instance_type = var.bastion_instance_type
env_namespace = local.env_namespace
instance_type = var.bastion_instance_type
iam_instance_profile = module.bastion_ssm_role[0].instance_profile_name

min_instance_count = var.bastion_min_instance_count
max_instance_count = var.bastion_max_instance_count
Expand All @@ -79,16 +94,6 @@ const bastionSGMainContent = dedent`
}
}

resource "aws_security_group_rule" "bastion_ingress_ssh_nimble" {
type = "ingress"
security_group_id = aws_security_group.bastion.id
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["\${var.nimble_office_ip}/32"]
description = "Nimble office"
}

resource "aws_security_group_rule" "bastion_egress_rds" {
type = "egress"
security_group_id = aws_security_group.bastion.id
Expand All @@ -97,6 +102,17 @@ const bastionSGMainContent = dedent`
protocol = "tcp"
source_security_group_id = aws_security_group.rds.id
description = "From RDS to bastion"
}

# trivy:ignore:AVD-AWS-0104
resource "aws_security_group_rule" "bastion_egress_ssm" {
type = "egress"
security_group_id = aws_security_group.bastion.id
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
description = "Allow outbound HTTPS traffic for SSM"
}`;

const bastionSGOutputsContent = dedent`
Expand All @@ -121,6 +137,7 @@ const applyAwsBastion = async (options: AwsOptions) => {
bastionLocalContent,
options.projectName
);
appendToFile(INFRA_CORE_DATA_PATH, bastionDataContent, options.projectName);
appendToFile(
INFRA_CORE_VARIABLES_PATH,
bastionVariablesContent,
Expand Down
45 changes: 45 additions & 0 deletions src/generators/addons/aws/modules/core/iamRole.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { AwsOptions } from '@/generators/addons/aws';
import { applyTerraformCore } from '@/generators/terraform';
import { remove } from '@/helpers/file';

import applyAwsIamRole from './iamRole';
import applyTerraformAwsProvider from './provider';

describe('IAM Role add-on', () => {
describe('given valid AWS options', () => {
const projectDir = 'iam-addon-test';

beforeAll(async () => {
const awsOptions: AwsOptions = {
projectName: projectDir,
provider: 'aws',
infrastructureType: 'advanced',
};

await applyTerraformCore(awsOptions);
await applyTerraformAwsProvider(awsOptions);
await applyAwsIamRole(awsOptions);
});

afterAll(() => {
jest.clearAllMocks();
remove('/', projectDir);
});

it('creates expected files', () => {
const expectedFiles = [
'shared/main.tf',
'shared/providers.tf',
'shared/outputs.tf',
'shared/variables.tf',

'modules/iam_role/data.tf',
'modules/iam_role/variables.tf',
'modules/iam_role/main.tf',
'modules/iam_role/outputs.tf',
];

expect(projectDir).toHaveFiles(expectedFiles);
});
});
});
18 changes: 18 additions & 0 deletions src/generators/addons/aws/modules/core/iamRole.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { AwsOptions } from '@/generators/addons/aws';
import { AWS_TEMPLATE_PATH } from '@/generators/addons/aws/constants';
import { isAwsModuleAdded } from '@/generators/addons/aws/dependencies';
import { copy } from '@/helpers/file';

const applyAwsIamRole = async (options: AwsOptions) => {
if (isAwsModuleAdded('iamRole', options.projectName)) {
return;
}

copy(
`${AWS_TEMPLATE_PATH}/modules/iam_role`,
'modules/iam_role',
options.projectName
);
};

export default applyAwsIamRole;
2 changes: 2 additions & 0 deletions src/generators/addons/aws/modules/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import applyAwsAlb from './alb';
import applyAwsBastion from './bastion';
import applyAwsCloudwatch from './cloudwatch';
import applyAwsIamRole from './core/iamRole';
import applyAwsIamUserAndGroup from './core/iamUserAndGroup';
import applyTerraformAwsProvider from './core/provider';
import applyAwsRegion from './core/region';
Expand All @@ -14,6 +15,7 @@ import applyAwsSsm from './ssm';

export {
applyAwsAlb,
applyAwsIamRole,
applyAwsBastion,
applyTerraformAwsProvider,
applyAwsCloudwatch,
Expand Down
19 changes: 19 additions & 0 deletions templates/addons/aws/modules/bastion/data.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
data "aws_ami" "amazon_linux_2023" {
most_recent = true
owners = ["amazon"]

filter {
name = "name"
values = ["al2023-ami-2023.*-x86_64"]
}

filter {
name = "architecture"
values = ["x86_64"]
}

filter {
name = "virtualization-type"
values = ["hvm"]
}
}
6 changes: 5 additions & 1 deletion templates/addons/aws/modules/bastion/main.tf
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
# trivy:ignore:AVD-AWS-0009
resource "aws_launch_template" "bastion_instance" {
name_prefix = "${local.name_prefix}-"
image_id = var.image_id
image_id = data.aws_ami.amazon_linux_2023.id
instance_type = var.instance_type
key_name = var.key_name
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we're replacing SSH with SSM, can we remove this? 🙏

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm removed in 90b3e99


iam_instance_profile {
name = var.iam_instance_profile
}

metadata_options {
http_tokens = local.metadata_options.http_tokens
}
Expand Down
13 changes: 7 additions & 6 deletions templates/addons/aws/modules/bastion/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ variable "env_namespace" {
}

variable "subnet_ids" {
description = "The public setnet IsD for the instance"
description = "The public subnet IDs for the instance"
type = list(string)
}

Expand All @@ -13,11 +13,6 @@ variable "instance_security_group_ids" {
type = list(string)
}

variable "image_id" {
description = "The AMI image ID"
default = "ami-0801a1e12f4a9ccc0"
}

variable "instance_type" {
description = "The instance type"
default = "t3.nano"
Expand Down Expand Up @@ -53,5 +48,11 @@ variable "device_name" {
variable "key_name" {
description = "The name of the key pair to use for the instance"
type = string
default = "" // Set empty to disable key pair
}

variable "iam_instance_profile" {
description = "The name of the IAM instance profile for the instance"
type = string
default = ""
}
12 changes: 12 additions & 0 deletions templates/addons/aws/modules/iam_role/data.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
data "aws_iam_policy_document" "assume_role" {
statement {
effect = "Allow"

principals {
type = "Service"
identifiers = var.assume_role_services
}

actions = ["sts:AssumeRole"]
}
}
26 changes: 26 additions & 0 deletions templates/addons/aws/modules/iam_role/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
resource "aws_iam_role" "role" {
name = var.role_name
assume_role_policy = data.aws_iam_policy_document.assume_role.json

tags = {
Name = var.role_name
}
}

resource "aws_iam_role_policy_attachment" "this" {
for_each = toset(var.policy_arns)

role = aws_iam_role.role.name
policy_arn = each.value
}

resource "aws_iam_instance_profile" "instance_profile" {
count = var.create_instance_profile ? 1 : 0

name = var.role_name
role = aws_iam_role.role.name

tags = {
Name = var.role_name
}
}
9 changes: 9 additions & 0 deletions templates/addons/aws/modules/iam_role/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
output "role_arn" {
description = "The ARN of the IAM role"
value = aws_iam_role.role.arn
}

output "instance_profile_name" {
description = "The name of the IAM instance profile"
value = var.create_instance_profile ? aws_iam_instance_profile.instance_profile[0].name : null
}
22 changes: 22 additions & 0 deletions templates/addons/aws/modules/iam_role/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
variable "role_name" {
description = "The name of the IAM role"
type = string
}

variable "assume_role_services" {
description = "List of AWS services that can assume this role"
type = list(string)
default = ["ec2.amazonaws.com"]
}

variable "policy_arns" {
description = "List of IAM policy ARNs to attach to the role"
type = list(string)
default = []
}

variable "create_instance_profile" {
description = "Whether to create an IAM instance profile"
type = bool
default = false
}
Loading