diff --git a/infrastructure/README.md b/infrastructure/README.md index a1b2ac69..722f47e1 100644 --- a/infrastructure/README.md +++ b/infrastructure/README.md @@ -94,3 +94,23 @@ The CLI can be downloaded at https://aws.amazon.com/cli/ Specifically for the ECS Exec command, the session manager is required: https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html To authenticate with AWS, the AWS CLI can use named profiles (as used in this README): https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html + +# EC2 change + +## Updating the certificate + +To renew the certificate, the same command needs to be run: +```bash +sudo su +letsencrypt certonly --standalone -d api.littil.org -m --agree-tos --no-eff-email +``` + +To use port 80 for verification, stop the nginx webserver (way faster than shutting down and starting the back-end itself) for a moment: +```bash +systemctl stop nginx +``` + +Restart again using: +```bash +systemctl start nginx +``` diff --git a/infrastructure/bin/infrastructure.ts b/infrastructure/bin/infrastructure.ts index a4d9a8bc..b1e0a7d0 100644 --- a/infrastructure/bin/infrastructure.ts +++ b/infrastructure/bin/infrastructure.ts @@ -2,6 +2,7 @@ import { App, Fn, StackProps } from 'aws-cdk-lib'; import 'source-map-support/register'; import { ApiEc2Stack, ApiEc2StackProps } from '../lib/api-ec2-stack'; +import { ApiElasticIpStack } from '../lib/api-elastic-ip-stack'; import { ApiStack, ApiStackProps } from '../lib/api-stack'; import { CertificateStack, CertificateStackProps } from '../lib/certificate-stack'; import { DatabaseStack, DatabaseStackProps } from '../lib/database-stack'; @@ -107,12 +108,26 @@ if (!littilEnvironment) { }; new DatabaseStack(app, 'ApiDatabaseStack', databaseStackProps); + /* Separate stack for an Elastic IP so that the Elastic IP stack can be created first, then DNS updated, then the + EC2 stack can be created. If the EC2 stack is created immediately, certificate generation will fail. It must then + be retried manually */ + const elasticIpStack = new ApiElasticIpStack(app, 'ApiElasticIpStack', { + env, + }); + const apiEc2StackProps: ApiEc2StackProps = { littil: littilEnvironmentSettings, apiVpc: vpcStack.vpc, env, + ecrRepository: { + awsAccount: sharedAccountId, + name: ecrApiRepositoryName, + }, + elasticIp: elasticIpStack.elasticIp, database: { + host: Fn.importValue(crossStackReferenceExportNames.databaseHost), port: Fn.importValue(crossStackReferenceExportNames.databasePort), + name: Fn.importValue(crossStackReferenceExportNames.databaseName), securityGroup: { id: Fn.importValue(crossStackReferenceExportNames.databaseSecurityGroup), }, diff --git a/infrastructure/lib/api-ec2-stack.ts b/infrastructure/lib/api-ec2-stack.ts index 4fa0b2ce..ad2d0c36 100644 --- a/infrastructure/lib/api-ec2-stack.ts +++ b/infrastructure/lib/api-ec2-stack.ts @@ -3,17 +3,21 @@ import { AmazonLinuxCpuType, AmazonLinuxGeneration, AmazonLinuxImage, - CfnEIP, CfnEIPAssociation, CfnKeyPair, + CfnEIP, + CfnEIPAssociation, Instance, InstanceClass, InstanceSize, - InstanceType, Peer, Port, + InstanceType, + KeyPair, + Peer, + Port, SecurityGroup, - SubnetType, UserData, + SubnetType, + UserData, Vpc } from 'aws-cdk-lib/aws-ec2'; -import { CfnAccessKey, Effect, Policy, PolicyStatement, Role, User } from 'aws-cdk-lib/aws-iam'; -import { LogGroup } from 'aws-cdk-lib/aws-logs'; +import { Effect, Policy, PolicyStatement } from 'aws-cdk-lib/aws-iam'; import { Construct } from 'constructs'; import { LittilEnvironmentSettings } from './littil-environment-settings'; import { LoggingStack } from './logging-stack'; @@ -24,8 +28,15 @@ export interface ApiEc2StackProps extends StackProps { littil: LittilEnvironmentSettings; apiVpc: Vpc; + elasticIp: CfnEIP; + ecrRepository: { + awsAccount: string; + name: string; + }; database: { + host: string; port: string; + name: string; securityGroup: { id: string; }; @@ -43,61 +54,94 @@ export class ApiEc2Stack extends Stack { }); ec2SecurityGroup.addIngressRule(Peer.anyIpv4(), Port.tcp(443), 'HTTPS over IPv4'); ec2SecurityGroup.addIngressRule(Peer.anyIpv6(), Port.tcp(443), 'HTTPS over IPv6'); + ec2SecurityGroup.addIngressRule(Peer.anyIpv4(), Port.tcp(80), 'HTTP over IPv4'); + ec2SecurityGroup.addIngressRule(Peer.anyIpv6(), Port.tcp(80), 'HTTP over IPv6'); + ec2SecurityGroup.addIngressRule(Peer.anyIpv4(), Port.tcp(22), 'SSH access'); + ec2SecurityGroup.addIngressRule(Peer.anyIpv6(), Port.tcp(22), 'SSH access'); - const elasticIp = new CfnEIP(this, 'ApiIP', { - tags: [ - { - key: 'Name', - value: 'API EC2', - } - ] - }); - const keypair = new CfnKeyPair(this, 'ApiEc2Keypair', { - keyName: 'EC2 Keypair', - publicKeyMaterial: '---- BEGIN SSH2 PUBLIC KEY ----\n' + - 'Comment: "rsa-key-20230917"\n' + - 'AAAAB3NzaC1yc2EAAAADAQABAAABAQCbNh2l09RvvYyDtIXjtqnJG/nFYtV44Gwx\n' + - 'TjYteFvwyK3wSFlgA0qFIjoUxrh5KLGsVYzoz7JmcD3thg7YdbcCy3agZ8EIK8ds\n' + - '8ly37dGn5D1u7re5AU+7Y+LPsw31lxjusZCFPJEElKexryuhIP043EAe/pWXDfM6\n' + - '6urIhgXGKRhxu3prw43xX8STTGGUaropESaEnudAxMlgHu/nNI8DauQhf5LZWboT\n' + - 'YjnB2X8lpDY9Vsab6e0OINUcXgHvH9A9r/twBPt1Hx8MXWjmDTEiU5+vuDOcws+g\n' + - '4/TsVUxuJ/MqhBkJj/ou2cVkCcBuzbreQdd9zlc5J5CJSfReg4dl\n' + - '---- END SSH2 PUBLIC KEY ----\n', + const keypair = new KeyPair(this, 'ApiEc2Keypair', { + keyPairName: 'EC2 Keypair', + publicKeyMaterial: 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIN5OhXLM34h3omM+5AoaYgUktcBMUBrC5awrPoItmf3S prod@littil.org', }); + const apiDomain = 'api.' + (props.littil.environment === 'production' ? '' : props.littil.environment + '.') + 'littil.org'; + const littilServerConf = fs.readFileSync('lib/nginx/serverconfiguration') .toString('utf-8') - .replaceAll('%ENVIRONMENT%', props.littil.environment); + .replaceAll('%API_DOMAIN%', apiDomain); + + /* Logging. */ + const logGroupName = 'BackendApiEc2Logs'; + const apiEc2LoggingStack = new LoggingStack(this, 'ApiEc2LoggingStack', { + littil: props.littil, + logGroupName, + }); + + const dockerTag = '1.3.1'; + const dockerImage = props.ecrRepository.awsAccount + '.dkr.ecr.eu-west-1.amazonaws.com/' + props.ecrRepository.name + ':' + dockerTag; + + const littilOidcSecretName = 'littil/backend/' + props.littil.environment + '/oidc'; + const littilSmtpSecretName = 'littil/backend/' + props.littil.environment + '/smtp'; + // TODO: Find by tags + const littilBackendDatabaseSecretName = 'ApiDatabaseStackLittilApiDa-ia57olJcscCP'; const userData = UserData.forLinux(); userData.addCommands( 'sudo su', - 'yum update', + 'yum update -y', 'yum install docker -y', 'systemctl start docker.service', - 'docker run -p 8081:80 -P -d nginxdemos/hello', - 'aws ecr get-login-password --region ' + this.region + ' | docker login --username AWS --password-stdin ' + this.account + '.dkr.ecr.' + this.region + '.amazonaws.com', - 'docker pull ' + this.account + '.dkr.ecr.eu-west-1.amazonaws.com/littil-backend:latest', + + 'aws ecr get-login-password --region ' + this.region + ' | docker login --username AWS --password-stdin ' + props.ecrRepository.awsAccount + '.dkr.ecr.' + this.region + '.amazonaws.com', + 'docker pull ' + dockerImage, + + 'echo DATASOURCE_HOST="' + props.database.host + '" >> littil.env', + 'echo DATASOURCE_PORT=' + props.database.port + ' >> littil.env', + 'echo DATASOURCE_DATABASE=' + props.database.name + ' >> littil.env', + 'echo QUARKUS_HTTP_CORS_ORIGINS="' + props.littil.httpCorsOrigin + '" >> littil.env', + 'echo QUARKUS_LOG_CLOUDWATCH_ACCESS_KEY_ID="' + apiEc2LoggingStack.loggingAccessKey.ref + '" >> littil.env', + 'echo QUARKUS_LOG_CLOUDWATCH_ACCESS_KEY_SECRET="' + apiEc2LoggingStack.loggingAccessKey.attrSecretAccessKey + '" >> littil.env', + 'echo QUARKUS_LOG_CLOUDWATCH_LOG_GROUP="' + apiEc2LoggingStack.cloudwatchLogGroup.logGroupName + '" >> littil.env', + 'echo QUARKUS_LOG_CLOUDWATCH_REGION=' + this.region + ' >> littil.env', + 'echo QUARKUS_LOG_CLOUDWATCH_LOG_STREAM_NAME=' + logGroupName + ' >> littil.env', + 'echo QUARKUS_MAILER_FROM=no-reply@mail.littil.org >> littil.env', + + 'yum install jq -y', + // TODO: Pass these secret values to the docker process in a more secure way than storing them in a file + 'echo DATASOURCE_USERNAME=$(aws secretsmanager get-secret-value --region ' + this.region + ' --secret-id ' + littilBackendDatabaseSecretName + ' | jq --raw-output \'.SecretString\' | jq -r .username) >> littil.env', + 'echo DATASOURCE_PASSWORD=$(aws secretsmanager get-secret-value --region ' + this.region + ' --secret-id ' + littilBackendDatabaseSecretName + ' | jq --raw-output \'.SecretString\' | jq -r .password) >> littil.env', + 'echo OIDC_CLIENT_ID=$(aws secretsmanager get-secret-value --region ' + this.region + ' --secret-id ' + littilOidcSecretName + ' | jq --raw-output \'.SecretString\' | jq -r .oidcClientId) >> littil.env', + 'echo OIDC_CLIENT_SECRET=$(aws secretsmanager get-secret-value --region ' + this.region + ' --secret-id ' + littilOidcSecretName + ' | jq --raw-output \'.SecretString\' | jq -r .oidcClientSecret) >> littil.env', + 'echo OIDC_TENANT=$(aws secretsmanager get-secret-value --region ' + this.region + ' --secret-id ' + littilOidcSecretName + ' | jq --raw-output \'.SecretString\' | jq -r .oidcTenant) >> littil.env', + 'echo M2M_CLIENT_ID=$(aws secretsmanager get-secret-value --region ' + this.region + ' --secret-id ' + littilOidcSecretName + ' | jq --raw-output \'.SecretString\' | jq -r .m2mClientId) >> littil.env', + 'echo M2M_CLIENT_SECRET=$(aws secretsmanager get-secret-value --region ' + this.region + ' --secret-id ' + littilOidcSecretName + ' | jq --raw-output \'.SecretString\' | jq -r .m2mClientSecret) >> littil.env', + 'echo SMTP_HOST=$(aws secretsmanager get-secret-value --region ' + this.region + ' --secret-id ' + littilSmtpSecretName + ' | jq --raw-output \'.SecretString\' | jq -r .smtpHost) >> littil.env', + 'echo SMTP_USERNAME=$(aws secretsmanager get-secret-value --region ' + this.region + ' --secret-id ' + littilSmtpSecretName + ' | jq --raw-output \'.SecretString\' | jq -r .smtpUsername) >> littil.env', + 'echo SMTP_PASSWORD=$(aws secretsmanager get-secret-value --region ' + this.region + ' --secret-id ' + littilSmtpSecretName + ' | jq --raw-output \'.SecretString\' | jq -r .smtpPassword) >> littil.env', + + 'docker run -p 8080:8080 --env-file littil.env -d ' + dockerImage, + // TODO: Test whether we can immediately delete the file + // 'rm littil.env', /* Install Nginx. */ 'amazon-linux-extras install nginx1', 'systemctl stop nginx', /* Nginx configuration. */ - 'echo test > /etc/nginx/conf.d/test', - 'echo ' + Buffer.from(littilServerConf).toString('base64') + ' > /etc/nginx/conf.d/test2', - 'echo ' + Buffer.from(littilServerConf).toString('base64') + ' | base64 -d > /etc/nginx/conf.d/api.' + props.littil.environment + '.littil.org.conf', + 'echo ' + Buffer.from(littilServerConf).toString('base64') + ' | base64 -d > /etc/nginx/conf.d/' + apiDomain + '.conf', /* Certbot. */ 'amazon-linux-extras install epel', - 'yum update', + 'yum update -y', 'yum install certbot -y', - 'letsencrypt certonly --standalone -d api.' + props.littil.environment + '.littil.org -m lennert.gijsen@littil.org --agree-tos --no-eff-email', + // Will fail when there's no DNS A record pointing to the above created Elastic IP. Should be interactively waiting or perhaps put the Elastic IP in a separate stack that needs to be created first. + 'letsencrypt certonly --standalone -d ' + apiDomain + ' -m lennert.gijsen@littil.org --agree-tos --no-eff-email', 'systemctl enable docker', 'systemctl enable nginx', + 'systemctl start nginx', ); const pullPolicyStatement = new PolicyStatement({ @@ -116,41 +160,47 @@ export class ApiEc2Stack extends Stack { '*', ] }); - const pullPolicy = new Policy(this, 'PullPolicy'); - pullPolicy.addStatements( + const readSecretsPolicyStatment = new PolicyStatement({ + effect: Effect.ALLOW, + actions: [ + 'secretsmanager:GetSecretValue', + ], + resources: [ + '*', + ] + }); + const littilApiPolicy = new Policy(this, 'PullPolicy'); + littilApiPolicy.addStatements( pullPolicyStatement, + readSecretsPolicyStatment, ); /**/ const ec2Instance = new Instance(this, 'ApiInstance', { vpc: props.apiVpc, - instanceType: InstanceType.of(InstanceClass.T4G, InstanceSize.NANO), + instanceType: InstanceType.of(InstanceClass.T3A, InstanceSize.MICRO), machineImage: new AmazonLinuxImage({ generation: AmazonLinuxGeneration.AMAZON_LINUX_2, - cpuType: AmazonLinuxCpuType.ARM_64, + cpuType: AmazonLinuxCpuType.X86_64, }), vpcSubnets: {subnetType: SubnetType.PUBLIC}, securityGroup: ec2SecurityGroup, - keyName: keypair.keyName, + keyPair: keypair, userData, }); - pullPolicy.attachToRole(ec2Instance.role); + littilApiPolicy.attachToRole(ec2Instance.role); new CfnEIPAssociation(this, 'Ec2EipAssociation', { - eip: elasticIp.ref, + eip: props.elasticIp.ref, instanceId: ec2Instance.instanceId, }); /**/ - /* Logging. */ - const apiEc2LoggingStack = new LoggingStack(this, 'ApiEc2LoggingStack', { - littil: props.littil, - logGroupName: 'BackendApiEc2Logs', - }); - /* Database access. */ const databaseSecurityGroup = SecurityGroup.fromSecurityGroupId(this, 'DatabaseSecurityGroup', props.database.securityGroup.id); - databaseSecurityGroup.connections.allowFrom(ec2SecurityGroup, Port.tcp(parseInt(props.database.port))); + // TODO: Uncomment, test and remove the allTcp rule + // databaseSecurityGroup.connections.allowFrom(ec2SecurityGroup, Port.tcp(parseInt(props.database.port))); + databaseSecurityGroup.connections.allowFrom(ec2SecurityGroup, Port.allTcp()); } } diff --git a/infrastructure/lib/api-elastic-ip-stack.ts b/infrastructure/lib/api-elastic-ip-stack.ts new file mode 100644 index 00000000..89aa5a6e --- /dev/null +++ b/infrastructure/lib/api-elastic-ip-stack.ts @@ -0,0 +1,22 @@ +import { Stack, StackProps } from 'aws-cdk-lib'; +import { CfnEIP } from 'aws-cdk-lib/aws-ec2'; +import { Construct } from 'constructs'; + +export class ApiElasticIpStack extends Stack { + public readonly elasticIp: CfnEIP; + + constructor(scope: Construct, + id: string, + props: StackProps) { + super(scope, id, props); + + this.elasticIp = new CfnEIP(this, 'ApiIP', { + tags: [ + { + key: 'Name', + value: 'API EC2', + } + ] + }); + } +} diff --git a/infrastructure/lib/database-stack.ts b/infrastructure/lib/database-stack.ts index c8a0735e..764f3ccd 100644 --- a/infrastructure/lib/database-stack.ts +++ b/infrastructure/lib/database-stack.ts @@ -1,13 +1,13 @@ import { CfnOutput, Stack, StackProps } from 'aws-cdk-lib'; -import { InstanceClass, InstanceSize, InstanceType, Vpc } from 'aws-cdk-lib/aws-ec2'; +import { InstanceClass, InstanceSize, InstanceType, SubnetType, Vpc } from 'aws-cdk-lib/aws-ec2'; import { - Credentials, - DatabaseInstance, DatabaseInstanceEngine, + DatabaseInstanceFromSnapshot, MariaDbEngineVersion, - ParameterGroup + ParameterGroup, + SnapshotCredentials } from 'aws-cdk-lib/aws-rds'; -import { DatabaseInstanceProps } from 'aws-cdk-lib/aws-rds/lib/instance'; +import { DatabaseInstanceFromSnapshotProps } from 'aws-cdk-lib/aws-rds/lib/instance'; import { Construct } from 'constructs'; import { LittilEnvironmentSettings } from './littil-environment-settings'; @@ -31,7 +31,7 @@ export class DatabaseStack extends Stack { const databaseName = 'LittilDatabase'; const rdsEngine = DatabaseInstanceEngine.mariaDb({ - version: MariaDbEngineVersion.VER_10_6_8, + version: MariaDbEngineVersion.VER_10_6_17, }); const rdsParameterGroup = new ParameterGroup(this, 'littil-rds-parametergroup', { @@ -41,27 +41,27 @@ export class DatabaseStack extends Stack { } }); - const credentials = Credentials.fromGeneratedSecret( - 'littil_' + props.littil.environment.substring(0, 7), - { - secretName: 'littil/backend/' + props.littil.environment + '/database', - } - ); + const dbUserName = 'littil_' + props.littil.environment.substring(0, 7); + // TODO: As soon as supported, use 'secretName' property. Will make looking this secret up easier. + const snapshotCredentials = SnapshotCredentials.fromGeneratedSecret(dbUserName); - const databaseProperties: DatabaseInstanceProps = { - databaseName, - credentials, + const databaseProperties: DatabaseInstanceFromSnapshotProps = { + credentials: snapshotCredentials, publiclyAccessible: false, vpc: props.apiVpc, + vpcSubnets: { + subnetType: SubnetType.PRIVATE_ISOLATED, + }, engine: rdsEngine, parameterGroup: rdsParameterGroup, instanceType: InstanceType.of( InstanceClass.T4G, InstanceSize.MICRO, ), + snapshotIdentifier: 'apidatabasestack-snapshot-littilapidatabase74782804-gy2ioxtzgpgy', }; - const database = new DatabaseInstance(this, 'LittilApiDatabase', databaseProperties); + const database = new DatabaseInstanceFromSnapshot(this, 'LittilApiDatabase', databaseProperties); new CfnOutput(this, 'databaseHost', { value: database.instanceEndpoint.hostname, diff --git a/infrastructure/lib/nginx/serverconfiguration b/infrastructure/lib/nginx/serverconfiguration index dacd8357..f02b2749 100644 --- a/infrastructure/lib/nginx/serverconfiguration +++ b/infrastructure/lib/nginx/serverconfiguration @@ -2,11 +2,11 @@ server { listen 443 ssl; server_name api.littil.org; - ssl_certificate /etc/letsencrypt/live/api.%ENVIRONMENT%.littil.org/cert.pem; - ssl_certificate_key /etc/letsencrypt/live/api.%ENVIRONMENT%.littil.org/privkey.pem; + ssl_certificate /etc/letsencrypt/live/%API_DOMAIN%/cert.pem; + ssl_certificate_key /etc/letsencrypt/live/%API_DOMAIN%/privkey.pem; location / { - proxy_pass http://localhost:8081; + proxy_pass http://localhost:8080; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; diff --git a/infrastructure/lib/vpc-stack.ts b/infrastructure/lib/vpc-stack.ts index 7cf51801..68bbad9a 100644 --- a/infrastructure/lib/vpc-stack.ts +++ b/infrastructure/lib/vpc-stack.ts @@ -1,5 +1,5 @@ import { Stack, StackProps } from 'aws-cdk-lib'; -import { Vpc } from 'aws-cdk-lib/aws-ec2'; +import { ISubnet, Subnet, SubnetType, Vpc } from 'aws-cdk-lib/aws-ec2'; import { Construct } from 'constructs'; export class VpcStack extends Stack { @@ -13,7 +13,17 @@ export class VpcStack extends Stack { this.vpc = new Vpc(this, 'LittilBackendVpc', { maxAzs: 2, - natGateways: 1, + natGateways: 0, + subnetConfiguration: [ + { + name: 'LITTIL-Public-Subnet', + subnetType: SubnetType.PUBLIC, + }, + { + name: 'LITTIL-Private-Subnet', + subnetType: SubnetType.PRIVATE_ISOLATED, + } + ] }); } } diff --git a/infrastructure/package-lock.json b/infrastructure/package-lock.json index 21271cfb..7a795cd4 100644 --- a/infrastructure/package-lock.json +++ b/infrastructure/package-lock.json @@ -8,7 +8,7 @@ "name": "infrastructure", "version": "0.1.0", "dependencies": { - "aws-cdk-lib": "2.40.0", + "aws-cdk-lib": "2.148.0", "constructs": "^10.0.0", "source-map-support": "^0.5.21" }, @@ -18,7 +18,7 @@ "devDependencies": { "@types/jest": "29.5.5", "@types/node": "20.6.2", - "aws-cdk": "2.96.2", + "aws-cdk": "2.148.0", "jest": "29.7.0", "ts-jest": "29.1.1", "ts-node": "^10.9.1", @@ -38,6 +38,21 @@ "node": ">=6.0.0" } }, + "node_modules/@aws-cdk/asset-awscli-v1": { + "version": "2.2.202", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.202.tgz", + "integrity": "sha512-JqlF0D4+EVugnG5dAsNZMqhu3HW7ehOXm5SDMxMbXNDMdsF0pxtQKNHRl52z1U9igsHmaFpUgSGjbhAJ+0JONg==" + }, + "node_modules/@aws-cdk/asset-kubectl-v20": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-kubectl-v20/-/asset-kubectl-v20-2.1.2.tgz", + "integrity": "sha512-3M2tELJOxQv0apCIiuKQ4pAbncz9GuLwnKFqxifWfe77wuMxyTRPmxssYHs42ePqzap1LT6GDcPygGs+hHstLg==" + }, + "node_modules/@aws-cdk/asset-node-proxy-agent-v6": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.0.3.tgz", + "integrity": "sha512-twhuEG+JPOYCYPx/xy5uH2+VUsIEhPTzDY0F1KuB+ocjWWB/KEDiOVL19nHvbPCB6fhWnkykXEMJ4HHcKvjtvg==" + }, "node_modules/@babel/code-frame": { "version": "7.22.13", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", @@ -1278,9 +1293,9 @@ } }, "node_modules/aws-cdk": { - "version": "2.96.2", - "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.96.2.tgz", - "integrity": "sha512-13ERpPV99OFAD75PLOtl0rRMXTWn6bCrmUPwYKkLwIMkj2xWCBiwo2Y9Qg+UzEszm5NMHA1N4ichSvuZ0mt2IQ==", + "version": "2.148.0", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.148.0.tgz", + "integrity": "sha512-nuCWY8I0xkIz7B2LjIL4h/xLHWTFhINL8i2fdA0BPq8t0byhLaNw5wggvztDjWH5xq5I1DM3laiv4/q5S21Xrg==", "dev": true, "bin": { "cdk": "bin/cdk" @@ -1293,9 +1308,9 @@ } }, "node_modules/aws-cdk-lib": { - "version": "2.40.0", - "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.40.0.tgz", - "integrity": "sha512-AHDPU4I+WP3x+8W2TcSNPDhiA1wmvYkhaz5VjsQ9bqrnu2tJhcQaYkJCUu49MOVfUDpWYp9DnZIL0Yirlp5X6w==", + "version": "2.148.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.148.0.tgz", + "integrity": "sha512-Pa0pyIHlhnsqtMkPJS3tnptYhoOSNDOgoFurNB4Qfa0vnAkjYQ+JKQkR1tNNr8+UtO9jUfXRklQgjEqlFlrgBA==", "bundleDependencies": [ "@balena/dockerignore", "case", @@ -1305,17 +1320,24 @@ "minimatch", "punycode", "semver", - "yaml" + "table", + "yaml", + "mime-types" ], "dependencies": { + "@aws-cdk/asset-awscli-v1": "^2.2.202", + "@aws-cdk/asset-kubectl-v20": "^2.1.2", + "@aws-cdk/asset-node-proxy-agent-v6": "^2.0.3", "@balena/dockerignore": "^1.0.2", "case": "1.6.3", - "fs-extra": "^9.1.0", - "ignore": "^5.2.0", + "fs-extra": "^11.2.0", + "ignore": "^5.3.1", "jsonschema": "^1.4.1", + "mime-types": "^2.1.35", "minimatch": "^3.1.2", - "punycode": "^2.1.1", - "semver": "^7.3.7", + "punycode": "^2.3.1", + "semver": "^7.6.2", + "table": "^6.8.2", "yaml": "1.10.2" }, "engines": { @@ -1330,12 +1352,49 @@ "inBundle": true, "license": "Apache-2.0" }, - "node_modules/aws-cdk-lib/node_modules/at-least-node": { - "version": "1.0.0", + "node_modules/aws-cdk-lib/node_modules/ajv": { + "version": "8.16.0", "inBundle": true, - "license": "ISC", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.4.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/aws-cdk-lib/node_modules/ansi-regex": { + "version": "5.0.1", + "inBundle": true, + "license": "MIT", "engines": { - "node": ">= 4.0.0" + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/ansi-styles": { + "version": "4.3.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aws-cdk-lib/node_modules/astral-regex": { + "version": "2.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" } }, "node_modules/aws-cdk-lib/node_modules/balanced-match": { @@ -1360,38 +1419,76 @@ "node": ">= 0.8.0" } }, + "node_modules/aws-cdk-lib/node_modules/color-convert": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/color-name": { + "version": "1.1.4", + "inBundle": true, + "license": "MIT" + }, "node_modules/aws-cdk-lib/node_modules/concat-map": { "version": "0.0.1", "inBundle": true, "license": "MIT" }, + "node_modules/aws-cdk-lib/node_modules/emoji-regex": { + "version": "8.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fast-deep-equal": { + "version": "3.1.3", + "inBundle": true, + "license": "MIT" + }, "node_modules/aws-cdk-lib/node_modules/fs-extra": { - "version": "9.1.0", + "version": "11.2.0", "inBundle": true, "license": "MIT", "dependencies": { - "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" }, "engines": { - "node": ">=10" + "node": ">=14.14" } }, "node_modules/aws-cdk-lib/node_modules/graceful-fs": { - "version": "4.2.10", + "version": "4.2.11", "inBundle": true, "license": "ISC" }, "node_modules/aws-cdk-lib/node_modules/ignore": { - "version": "5.2.0", + "version": "5.3.1", "inBundle": true, "license": "MIT", "engines": { "node": ">= 4" } }, + "node_modules/aws-cdk-lib/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/json-schema-traverse": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT" + }, "node_modules/aws-cdk-lib/node_modules/jsonfile": { "version": "6.1.0", "inBundle": true, @@ -1411,15 +1508,28 @@ "node": "*" } }, - "node_modules/aws-cdk-lib/node_modules/lru-cache": { - "version": "6.0.0", + "node_modules/aws-cdk-lib/node_modules/lodash.truncate": { + "version": "4.4.2", "inBundle": true, - "license": "ISC", + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/mime-db": { + "version": "1.52.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/mime-types": { + "version": "2.1.35", + "inBundle": true, + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" + "mime-db": "1.52.0" }, "engines": { - "node": ">=10" + "node": ">= 0.6" } }, "node_modules/aws-cdk-lib/node_modules/minimatch": { @@ -1434,20 +1544,25 @@ } }, "node_modules/aws-cdk-lib/node_modules/punycode": { - "version": "2.1.1", + "version": "2.3.1", "inBundle": true, "license": "MIT", "engines": { "node": ">=6" } }, + "node_modules/aws-cdk-lib/node_modules/require-from-string": { + "version": "2.0.2", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/aws-cdk-lib/node_modules/semver": { - "version": "7.3.7", + "version": "7.6.2", "inBundle": true, "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -1455,18 +1570,76 @@ "node": ">=10" } }, + "node_modules/aws-cdk-lib/node_modules/slice-ansi": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/aws-cdk-lib/node_modules/string-width": { + "version": "4.2.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/strip-ansi": { + "version": "6.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/table": { + "version": "6.8.2", + "inBundle": true, + "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/aws-cdk-lib/node_modules/universalify": { - "version": "2.0.0", + "version": "2.0.1", "inBundle": true, "license": "MIT", "engines": { "node": ">= 10.0.0" } }, - "node_modules/aws-cdk-lib/node_modules/yallist": { - "version": "4.0.0", + "node_modules/aws-cdk-lib/node_modules/uri-js": { + "version": "4.4.1", "inBundle": true, - "license": "ISC" + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } }, "node_modules/aws-cdk-lib/node_modules/yaml": { "version": "1.10.2", @@ -3555,7 +3728,6 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, "bin": { "semver": "bin/semver.js" } @@ -4124,6 +4296,21 @@ "@jridgewell/trace-mapping": "^0.3.9" } }, + "@aws-cdk/asset-awscli-v1": { + "version": "2.2.202", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.202.tgz", + "integrity": "sha512-JqlF0D4+EVugnG5dAsNZMqhu3HW7ehOXm5SDMxMbXNDMdsF0pxtQKNHRl52z1U9igsHmaFpUgSGjbhAJ+0JONg==" + }, + "@aws-cdk/asset-kubectl-v20": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-kubectl-v20/-/asset-kubectl-v20-2.1.2.tgz", + "integrity": "sha512-3M2tELJOxQv0apCIiuKQ4pAbncz9GuLwnKFqxifWfe77wuMxyTRPmxssYHs42ePqzap1LT6GDcPygGs+hHstLg==" + }, + "@aws-cdk/asset-node-proxy-agent-v6": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.0.3.tgz", + "integrity": "sha512-twhuEG+JPOYCYPx/xy5uH2+VUsIEhPTzDY0F1KuB+ocjWWB/KEDiOVL19nHvbPCB6fhWnkykXEMJ4HHcKvjtvg==" + }, "@babel/code-frame": { "version": "7.22.13", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", @@ -5115,27 +5302,32 @@ } }, "aws-cdk": { - "version": "2.96.2", - "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.96.2.tgz", - "integrity": "sha512-13ERpPV99OFAD75PLOtl0rRMXTWn6bCrmUPwYKkLwIMkj2xWCBiwo2Y9Qg+UzEszm5NMHA1N4ichSvuZ0mt2IQ==", + "version": "2.148.0", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.148.0.tgz", + "integrity": "sha512-nuCWY8I0xkIz7B2LjIL4h/xLHWTFhINL8i2fdA0BPq8t0byhLaNw5wggvztDjWH5xq5I1DM3laiv4/q5S21Xrg==", "dev": true, "requires": { "fsevents": "2.3.2" } }, "aws-cdk-lib": { - "version": "2.40.0", - "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.40.0.tgz", - "integrity": "sha512-AHDPU4I+WP3x+8W2TcSNPDhiA1wmvYkhaz5VjsQ9bqrnu2tJhcQaYkJCUu49MOVfUDpWYp9DnZIL0Yirlp5X6w==", + "version": "2.148.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.148.0.tgz", + "integrity": "sha512-Pa0pyIHlhnsqtMkPJS3tnptYhoOSNDOgoFurNB4Qfa0vnAkjYQ+JKQkR1tNNr8+UtO9jUfXRklQgjEqlFlrgBA==", "requires": { + "@aws-cdk/asset-awscli-v1": "^2.2.202", + "@aws-cdk/asset-kubectl-v20": "^2.1.2", + "@aws-cdk/asset-node-proxy-agent-v6": "^2.0.3", "@balena/dockerignore": "^1.0.2", "case": "1.6.3", - "fs-extra": "^9.1.0", - "ignore": "^5.2.0", + "fs-extra": "^11.2.0", + "ignore": "^5.3.1", "jsonschema": "^1.4.1", + "mime-types": "^2.1.35", "minimatch": "^3.1.2", - "punycode": "^2.1.1", - "semver": "^7.3.7", + "punycode": "^2.3.1", + "semver": "^7.6.2", + "table": "^6.8.2", "yaml": "1.10.2" }, "dependencies": { @@ -5143,8 +5335,29 @@ "version": "1.0.2", "bundled": true }, - "at-least-node": { - "version": "1.0.0", + "ajv": { + "version": "8.16.0", + "bundled": true, + "requires": { + "fast-deep-equal": "^3.1.3", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.4.1" + } + }, + "ansi-regex": { + "version": "5.0.1", + "bundled": true + }, + "ansi-styles": { + "version": "4.3.0", + "bundled": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "astral-regex": { + "version": "2.0.0", "bundled": true }, "balanced-match": { @@ -5163,26 +5376,52 @@ "version": "1.6.3", "bundled": true }, + "color-convert": { + "version": "2.0.1", + "bundled": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "bundled": true + }, "concat-map": { "version": "0.0.1", "bundled": true }, + "emoji-regex": { + "version": "8.0.0", + "bundled": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "bundled": true + }, "fs-extra": { - "version": "9.1.0", + "version": "11.2.0", "bundled": true, "requires": { - "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "graceful-fs": { - "version": "4.2.10", + "version": "4.2.11", "bundled": true }, "ignore": { - "version": "5.2.0", + "version": "5.3.1", + "bundled": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "bundled": true + }, + "json-schema-traverse": { + "version": "1.0.0", "bundled": true }, "jsonfile": { @@ -5197,11 +5436,19 @@ "version": "1.4.1", "bundled": true }, - "lru-cache": { - "version": "6.0.0", + "lodash.truncate": { + "version": "4.4.2", + "bundled": true + }, + "mime-db": { + "version": "1.52.0", + "bundled": true + }, + "mime-types": { + "version": "2.1.35", "bundled": true, "requires": { - "yallist": "^4.0.0" + "mime-db": "1.52.0" } }, "minimatch": { @@ -5212,23 +5459,63 @@ } }, "punycode": { - "version": "2.1.1", + "version": "2.3.1", + "bundled": true + }, + "require-from-string": { + "version": "2.0.2", "bundled": true }, "semver": { - "version": "7.3.7", + "version": "7.6.2", + "bundled": true + }, + "slice-ansi": { + "version": "4.0.0", "bundled": true, "requires": { - "lru-cache": "^6.0.0" + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + }, + "string-width": { + "version": "4.2.3", + "bundled": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "bundled": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "table": { + "version": "6.8.2", + "bundled": true, + "requires": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" } }, "universalify": { - "version": "2.0.0", + "version": "2.0.1", "bundled": true }, - "yallist": { - "version": "4.0.0", - "bundled": true + "uri-js": { + "version": "4.4.1", + "bundled": true, + "requires": { + "punycode": "^2.1.0" + } }, "yaml": { "version": "1.10.2", @@ -6777,8 +7064,7 @@ "semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" }, "shebang-command": { "version": "2.0.0", diff --git a/infrastructure/package.json b/infrastructure/package.json index 2beada7a..151e39e3 100644 --- a/infrastructure/package.json +++ b/infrastructure/package.json @@ -26,7 +26,8 @@ "cdk:deploy:production": "cdk deploy ApiCertificatesStack ApiVpcStack ApiDatabaseStack ApiStack --context accountName=production --context environment=production --require-approval=never", "cdk:deploy:production:maintenance": "cdk deploy ApiVpcStack MaintenanceServiceStack --context accountName=production --context environment=production --require-approval=never", - "cdk:deploy:ec2": "cdk deploy ApiCertificatesStack ApiVpcStack ApiEc2Stack --require-approval=never", + "cdk:diff:ec2": "cdk diff ApiCertificatesStack ApiVpcStack ApiDatabaseStack ApiEc2Stack --context accountName=production --context environment=production", + "cdk:deploy:ec2": "cdk deploy ApiCertificatesStack ApiVpcStack ApiDatabaseStack ApiElasticIpStack ApiEc2Stack --context accountName=production --context environment=production --require-approval=never", "cdk:deploy:maintenance": "cdk deploy MaintenanceServiceStack --require-approval=never", "cdk:destroy:maintenance": "cdk destroy MaintenanceServiceStack --require-approval=never" }, @@ -35,12 +36,12 @@ "@types/node": "20.6.2", "jest": "29.7.0", "ts-jest": "29.1.1", - "aws-cdk": "2.96.2", + "aws-cdk": "2.148.0", "ts-node": "^10.9.1", "typescript": "5.2.2" }, "dependencies": { - "aws-cdk-lib": "2.40.0", + "aws-cdk-lib": "2.148.0", "constructs": "^10.0.0", "source-map-support": "^0.5.21" }