Skip to content

Commit fba02c0

Browse files
committed
[#327] Update bastion module to use ssm for login, add iam role module, remove ssh ingress rule and add HTTPS engress rule for SSM
1 parent 8a52dce commit fba02c0

File tree

14 files changed

+209
-25
lines changed

14 files changed

+209
-25
lines changed

scripts/generateAdvancedAWS.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const {
77
applyAwsRegion,
88
applyAwsSecurityGroup,
99
applyAwsVpc,
10+
applyAwsIamRole,
1011
} = require('../dist/generators/addons/aws/modules/index.js');
1112
const { applyAdvancedTemplate } = require('../dist/generators/addons/aws/advanced.js');
1213

@@ -19,5 +20,6 @@ const { applyAdvancedTemplate } = require('../dist/generators/addons/aws/advance
1920
await applyAwsVpc(options);
2021
await applyAwsSecurityGroup(options);
2122
await applyAwsIamUserAndGroup(options);
23+
await applyAwsIamRole(options);
2224
await applyAdvancedTemplate(options);
2325
})();

src/generators/addons/aws/dependencies.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
applyAwsSecurityGroup,
1414
applyAwsSsm,
1515
applyAwsVpc,
16+
applyAwsIamRole,
1617
} from '@/generators/addons/aws/modules';
1718
import { containsContent, isExisting } from '@/helpers/file';
1819

@@ -85,6 +86,12 @@ const AWS_MODULES: Record<AwsModuleName | string, AwsModule> = {
8586
mainContent: 'module "ssm"',
8687
applyModuleFunction: (options: AwsOptions) => applyAwsSsm(options),
8788
},
89+
iamRole: {
90+
name: 'iamRole',
91+
path: 'modules/iam_role',
92+
mainContent: 'module "iam_role"',
93+
applyModuleFunction: (options: AwsOptions) => applyAwsIamRole(options),
94+
},
8895
};
8996

9097
const isAwsModuleAdded = (

src/generators/addons/aws/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
applyAwsRegion,
1111
applyAwsSecurityGroup,
1212
applyAwsVpc,
13+
applyAwsIamRole,
1314
} from './modules';
1415

1516
const awsChoices = [
@@ -69,6 +70,7 @@ const generateAwsTemplate = async (
6970
await applyAwsVpc(awsOptions);
7071
await applyAwsSecurityGroup(awsOptions);
7172
await applyAwsIamUserAndGroup(awsOptions);
73+
await applyAwsIamRole(awsOptions);
7274
await applyAdvancedTemplate(awsOptions);
7375

7476
break;

src/generators/addons/aws/modules/bastion.ts

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
INFRA_CORE_LOCALS_PATH,
1010
INFRA_CORE_MAIN_PATH,
1111
INFRA_CORE_VARIABLES_PATH,
12+
INFRA_CORE_DATA_PATH,
1213
} from '@/generators/terraform/constants';
1314
import { appendToFile, copy } from '@/helpers/file';
1415

@@ -18,19 +19,23 @@ import {
1819
AWS_TEMPLATE_PATH,
1920
} from '../constants';
2021

22+
const bastionDataContent = dedent`
23+
### Begin Bastion Host ###
24+
data "aws_iam_policy" "ssm_managed_instance_core" {
25+
name = "AmazonSSMManagedInstanceCore"
26+
}
27+
### End Bastion Host ###`;
28+
2129
const bastionLocalContent = dedent`
2230
### Begin Bastion Host ###
2331
locals {
24-
enable_bastion = true
32+
enable_bastion = true
33+
bastion_ssm_role_name = "\${local.env_namespace}-SSMInstanceRole"
34+
bastion_ssm_policy_arns = [data.aws_iam_policy.ssm_managed_instance_core.arn]
2535
}
2636
### End Bastion Host ###`;
2737

2838
const bastionVariablesContent = dedent`
29-
variable "bastion_image_id" {
30-
description = "The AMI image ID for the bastion instance"
31-
default = "ami-0801a1e12f4a9ccc0"
32-
}
33-
3439
variable "bastion_instance_type" {
3540
description = "The bastion instance type"
3641
default = "t3.nano"
@@ -52,16 +57,26 @@ const bastionVariablesContent = dedent`
5257
}`;
5358

5459
const bastionModuleContent = dedent`
60+
module "bastion_ssm_role" {
61+
count = local.enable_bastion ? 1 : 0
62+
source = "../modules/iam_role"
63+
64+
role_name = local.bastion_ssm_role_name
65+
assume_role_services = ["ec2.amazonaws.com"]
66+
policy_arns = local.bastion_ssm_policy_arns
67+
create_instance_profile = true
68+
}
69+
5570
module "bastion" {
5671
count = local.enable_bastion ? 1 : 0
5772
source = "../modules/bastion"
5873
5974
subnet_ids = module.vpc.public_subnet_ids
6075
instance_security_group_ids = module.security_group.bastion_security_group_ids
6176
62-
env_namespace = local.env_namespace
63-
image_id = var.bastion_image_id
64-
instance_type = var.bastion_instance_type
77+
env_namespace = local.env_namespace
78+
instance_type = var.bastion_instance_type
79+
iam_instance_profile = module.bastion_ssm_role[0].instance_profile_name
6580
6681
min_instance_count = var.bastion_min_instance_count
6782
max_instance_count = var.bastion_max_instance_count
@@ -79,16 +94,6 @@ const bastionSGMainContent = dedent`
7994
}
8095
}
8196
82-
resource "aws_security_group_rule" "bastion_ingress_ssh_nimble" {
83-
type = "ingress"
84-
security_group_id = aws_security_group.bastion.id
85-
from_port = 22
86-
to_port = 22
87-
protocol = "tcp"
88-
cidr_blocks = ["\${var.nimble_office_ip}/32"]
89-
description = "Nimble office"
90-
}
91-
9297
resource "aws_security_group_rule" "bastion_egress_rds" {
9398
type = "egress"
9499
security_group_id = aws_security_group.bastion.id
@@ -97,6 +102,16 @@ const bastionSGMainContent = dedent`
97102
protocol = "tcp"
98103
source_security_group_id = aws_security_group.rds.id
99104
description = "From RDS to bastion"
105+
}
106+
107+
resource "aws_security_group_rule" "bastion_egress_ssm" {
108+
type = "egress"
109+
security_group_id = aws_security_group.bastion.id
110+
from_port = 443
111+
to_port = 443
112+
protocol = "tcp"
113+
cidr_blocks = ["0.0.0.0/0"]
114+
description = "Allow outbound HTTPS traffic for SSM"
100115
}`;
101116

102117
const bastionSGOutputsContent = dedent`
@@ -121,6 +136,7 @@ const applyAwsBastion = async (options: AwsOptions) => {
121136
bastionLocalContent,
122137
options.projectName
123138
);
139+
appendToFile(INFRA_CORE_DATA_PATH, bastionDataContent, options.projectName);
124140
appendToFile(
125141
INFRA_CORE_VARIABLES_PATH,
126142
bastionVariablesContent,
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { AwsOptions } from '@/generators/addons/aws';
2+
import { applyTerraformCore } from '@/generators/terraform';
3+
import { remove } from '@/helpers/file';
4+
5+
import applyAwsIamRole from './iamRole';
6+
import applyTerraformAwsProvider from './provider';
7+
8+
describe('IAM Role add-on', () => {
9+
describe('given valid AWS options', () => {
10+
const projectDir = 'iam-addon-test';
11+
12+
beforeAll(() => {
13+
const awsOptions: AwsOptions = {
14+
projectName: projectDir,
15+
provider: 'aws',
16+
infrastructureType: 'advanced',
17+
};
18+
19+
applyTerraformCore(awsOptions);
20+
applyTerraformAwsProvider(awsOptions);
21+
applyAwsIamRole(awsOptions);
22+
});
23+
24+
afterAll(() => {
25+
jest.clearAllMocks();
26+
remove('/', projectDir);
27+
});
28+
29+
it('creates expected files', () => {
30+
const expectedFiles = [
31+
'shared/main.tf',
32+
'shared/providers.tf',
33+
'shared/outputs.tf',
34+
'shared/variables.tf',
35+
36+
'modules/iam_role/data.tf',
37+
'modules/iam_role/main.tf',
38+
'modules/iam_role/outputs.tf',
39+
];
40+
41+
expect(projectDir).toHaveFiles(expectedFiles);
42+
});
43+
});
44+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { AwsOptions } from '@/generators/addons/aws';
2+
import { AWS_TEMPLATE_PATH } from '@/generators/addons/aws/constants';
3+
import { isAwsModuleAdded } from '@/generators/addons/aws/dependencies';
4+
import { copy } from '@/helpers/file';
5+
6+
const applyAwsIamRole = async (options: AwsOptions) => {
7+
if (isAwsModuleAdded('iamRole', options.projectName)) {
8+
return;
9+
}
10+
11+
copy(
12+
`${AWS_TEMPLATE_PATH}/modules/iam_role`,
13+
'modules/iam_role',
14+
options.projectName
15+
);
16+
};
17+
18+
export default applyAwsIamRole;

src/generators/addons/aws/modules/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import applyAwsAlb from './alb';
22
import applyAwsBastion from './bastion';
33
import applyAwsCloudwatch from './cloudwatch';
4+
import applyAwsIamRole from './core/iamRole';
45
import applyAwsIamUserAndGroup from './core/iamUserAndGroup';
56
import applyTerraformAwsProvider from './core/provider';
67
import applyAwsRegion from './core/region';
@@ -14,6 +15,7 @@ import applyAwsSsm from './ssm';
1415

1516
export {
1617
applyAwsAlb,
18+
applyAwsIamRole,
1719
applyAwsBastion,
1820
applyTerraformAwsProvider,
1921
applyAwsCloudwatch,
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
data "aws_ami" "amazon_linux_2023" {
2+
most_recent = true
3+
owners = ["amazon"]
4+
5+
filter {
6+
name = "name"
7+
values = ["al2023-ami-2023.*-x86_64"]
8+
}
9+
10+
filter {
11+
name = "architecture"
12+
values = ["x86_64"]
13+
}
14+
15+
filter {
16+
name = "virtualization-type"
17+
values = ["hvm"]
18+
}
19+
}

templates/addons/aws/modules/bastion/main.tf

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
# trivy:ignore:AVD-AWS-0009
22
resource "aws_launch_template" "bastion_instance" {
33
name_prefix = "${local.name_prefix}-"
4-
image_id = var.image_id
4+
image_id = data.aws_ami.amazon_linux_2023.id
55
instance_type = var.instance_type
66
key_name = var.key_name
77

8+
iam_instance_profile {
9+
name = var.iam_instance_profile
10+
}
11+
812
metadata_options {
913
http_tokens = local.metadata_options.http_tokens
1014
}

templates/addons/aws/modules/bastion/variables.tf

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,6 @@ variable "instance_security_group_ids" {
1313
type = list(string)
1414
}
1515

16-
variable "image_id" {
17-
description = "The AMI image ID"
18-
default = "ami-0801a1e12f4a9ccc0"
19-
}
20-
2116
variable "instance_type" {
2217
description = "The instance type"
2318
default = "t3.nano"
@@ -53,5 +48,11 @@ variable "device_name" {
5348
variable "key_name" {
5449
description = "The name of the key pair to use for the instance"
5550
type = string
51+
default = "" // Set empty to disable key pair
52+
}
53+
54+
variable "iam_instance_profile" {
55+
description = "The name of the IAM instance profile for the instance"
56+
type = string
5657
default = ""
5758
}

0 commit comments

Comments
 (0)