"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EC2withDatabase = void 0;
const aws_cdk_lib_1 = require("aws-cdk-lib");
const aws_cdk_lib_2 = require("aws-cdk-lib");
const ssm = require("aws-cdk-lib/aws-ssm");
const aws_kms_1 = require("aws-cdk-lib/aws-kms");
const aws_iam_1 = require("aws-cdk-lib/aws-iam");
const aws_route53_1 = require("aws-cdk-lib/aws-route53");
class EC2withDatabase extends aws_cdk_lib_1.Stack {
    constructor(scope, id, props) {
        var _a, _b, _c, _d;
        super(scope, id, props);
        /*
        *  Security Group setup and usage are optional parameters
        *  SGDBAccessRules = RDS database Access group rules (can be multiple)
        *  SGIngressRules = Ingress rules for additional access (can be multiple)
        */
        props = {
            ...props,
            backup: (_a = props.backup) !== null && _a !== void 0 ? _a : 'False',
            backupAccountId: (_b = props.backupAccountId) !== null && _b !== void 0 ? _b : '140023380087',
            SGDBAccessRules: (_c = props.SGDBAccessRules) !== null && _c !== void 0 ? _c : [],
            SGIngressRules: (_d = props.SGIngressRules) !== null && _d !== void 0 ? _d : [],
        };
        // Determine the VPC from the availability zone and subnet passed in. These are expected to align within the VPC
        const vpc = aws_cdk_lib_1.aws_ec2.Vpc.fromVpcAttributes(this, 'Vpc', {
            vpcId: props.vpcId,
            availabilityZones: props.availabilityZones,
            publicSubnetIds: props.publicSubnetIds,
        });
        // Set the build to use the passed in AMI
        const linux_build = aws_cdk_lib_1.aws_ec2.MachineImage.genericLinux({
            'us-east-1': props.amiId,
        });
        // Need to look up the basic role name and then get the ARN value
        const basic_role = ssm.StringParameter.valueFromLookup(this, '/esu/ec2/basic_role_name');
        const roleName = `arn:aws:iam::${this.account}:${basic_role}`;
        const ec2role = aws_cdk_lib_1.aws_iam.Role.fromRoleArn(this, 'Role', roleName);
        /*
         * Create a security group for this specific server and then add the SGIngressRules passed in
        */
        const ec2SecurityGroup = new aws_cdk_lib_1.aws_ec2.SecurityGroup(this, props.instanceName, {
            description: 'Security Group for $(props.instanceName)',
            allowAllOutbound: true,
            vpc: vpc,
        });
        if (props.SGIngressRules) {
            props.SGIngressRules.forEach((rule) => {
                ec2SecurityGroup.addIngressRule(aws_cdk_lib_1.aws_ec2.Peer.ipv4(rule.ipv4), aws_cdk_lib_1.aws_ec2.Port.tcp(rule.port), rule.description);
            });
        }
        // Create a key to allow volume and instance to be backed up
        const backupKey = new aws_kms_1.Key(this, 'BackupKey', {
            enableKeyRotation: true,
            alias: props.instanceName,
        });
        const backupAccountId = props.backupAccountId; // Our backup account ID
        const currentAccountId = this.account; // Current account ID
        const policyStatement = new aws_iam_1.PolicyStatement({
            actions: [
                'kms:CreateGrant',
                'kms:ListGrants',
                'kms:RevokeGrant',
                'kms:Encrypt',
                'kms:Decrypt',
                'kms:ReEncrypt*',
                'kms:GenerateDataKey',
                'kms:GenerateDataKeyPair',
                'kms:DescribeKey',
                'kms:GenerateDataKeyWithoutPlaintext',
                'kms:GenerateDataKeyPairWithoutPlaintext',
            ],
            principals: [
                new aws_iam_1.AccountPrincipal(backupAccountId),
                new aws_iam_1.AccountPrincipal(currentAccountId),
            ],
            resources: ['*'],
        });
        backupKey.addToResourcePolicy(policyStatement);
        // Allow attachment of persistent resources for cross-account backup
        backupKey.addToResourcePolicy(new aws_iam_1.PolicyStatement({
            sid: 'Allow attachment of persistent resources',
            principals: [
                new aws_iam_1.AccountPrincipal(`${backupAccountId}`),
                new aws_iam_1.AccountPrincipal(`${currentAccountId}`),
            ],
            actions: [
                'kms:CreateGrant',
                'kms:ListGrants',
                'kms:RevokeGrant',
            ],
            effect: aws_iam_1.Effect.ALLOW,
            resources: ['*'],
            conditions: { 'Bool': { 'kms:GrantIsForAWSResource': 'true' } },
        }));
        /*
         * Add connections from the new security group to the RDS which will be used by this server using
         * SGDBAccessRules passed in
        */
        if (props.SGDBAccessRules) {
            props.SGDBAccessRules.forEach((rule) => {
                const db_connect_SG = aws_cdk_lib_1.aws_ec2.SecurityGroup.fromSecurityGroupId(this, `SGAccess${rule.database}`, rule.database);
                ec2SecurityGroup.connections.allowFrom(db_connect_SG, aws_cdk_lib_1.aws_ec2.Port.tcp(rule.port), rule.description);
                ec2SecurityGroup.connections.allowTo(db_connect_SG, aws_cdk_lib_1.aws_ec2.Port.tcp(rule.port), rule.description);
            });
        }
        // With all of the information gathered, as well as values passed in, create the server
        const ec2_server = new aws_cdk_lib_1.aws_ec2.Instance(this, 'ec2-server', {
            blockDevices: [{
                    deviceName: '/dev/xvda',
                    volume: {
                        ebsDevice: {
                            volumeSize: props.volumeSize,
                            encrypted: true,
                            kmsKey: backupKey,
                        },
                    },
                }],
            vpc: vpc,
            role: ec2role,
            securityGroup: ec2SecurityGroup,
            machineImage: linux_build,
            propagateTagsToVolumeOnCreation: true,
            keyPair: aws_cdk_lib_1.aws_ec2.KeyPair.fromKeyPairName(this, 'KeyPair', props.keyName),
            instanceType: aws_cdk_lib_1.aws_ec2.InstanceType.of(props.instanceClass, props.instanceSize),
            instanceName: props.instanceName,
        });
        // Add an Elastic IP Address to the server
        const IPAddr = new aws_cdk_lib_1.aws_ec2.CfnEIP(this, 'Ip', {
            instanceId: ec2_server.instanceId,
        });
        // Tag the server and the EIP with the instance name and FQDN
        const FQDN = `${props.instanceName}.${props.domainName}`;
        const schedule = '*'; // Initially the schedule is always up
        const service = props.instanceName; // Initially the service is the instance name
        // Then tag the EIP and the server
        aws_cdk_lib_1.Tags.of(IPAddr).add('Name', props.instanceName);
        aws_cdk_lib_1.Tags.of(ec2_server).add('FQDN', FQDN);
        aws_cdk_lib_1.Tags.of(ec2_server).add('Schedule', schedule);
        aws_cdk_lib_1.Tags.of(ec2_server).add('Service', service);
        if (props.backup) {
            aws_cdk_lib_1.Tags.of(ec2_server).add('Backup', props.backup);
        }
        // Create DNS for libnd.nd.edu connection
        const privateZoneId = ssm.StringParameter.fromStringParameterAttributes(this, 'PrivateZoneId', {
            parameterName: `/all/dns/${props.keyName}/private/zoneId`,
        }).stringValue;
        new aws_route53_1.ARecord(this, 'PrivateARec', {
            target: aws_route53_1.RecordTarget.fromIpAddresses(ec2_server.instancePrivateIp),
            recordName: props.instanceName,
            zone: aws_route53_1.HostedZone.fromHostedZoneAttributes(this, 'PrivateHostedZone', {
                hostedZoneId: privateZoneId,
                zoneName: `${props.keyName}.nd.edu`,
            }),
            ttl: aws_cdk_lib_2.Duration.minutes(5),
        });
        // Create Public A records for library.nd.edu zone
        for (const direction of ['public']) {
            const hostedZoneId = ssm.StringParameter.fromStringParameterAttributes(this, `${direction}HostedZoneId`, {
                parameterName: `/all/dns/${props.domainName}/${direction}/zoneId`,
            }).stringValue;
            new aws_route53_1.ARecord(this, `${direction}ServiceARec`, {
                target: aws_route53_1.RecordTarget.fromIpAddresses(IPAddr.ref),
                recordName: props.instanceName,
                zone: aws_route53_1.HostedZone.fromHostedZoneAttributes(this, `${direction}ImportedHostedZone`, {
                    hostedZoneId: hostedZoneId,
                    zoneName: props.domainName,
                }),
                ttl: aws_cdk_lib_2.Duration.minutes(5),
            });
            if (props.CnameList.length != 0) {
                props.CnameList.forEach(cname => {
                    new aws_route53_1.ARecord(this, `${direction}${cname}`, {
                        target: aws_route53_1.RecordTarget.fromIpAddresses(IPAddr.ref),
                        recordName: cname,
                        zone: aws_route53_1.HostedZone.fromHostedZoneAttributes(this, `${direction}${cname}HostedZone`, {
                            hostedZoneId: hostedZoneId,
                            zoneName: props.domainName,
                        }),
                        ttl: aws_cdk_lib_2.Duration.minutes(5),
                    });
                });
            }
        }
        // Create Private A records for library.nd.edu zone - uses libnd.nd.edu values
        for (const direction of ['private']) {
            const hostedZoneId = ssm.StringParameter.fromStringParameterAttributes(this, `${direction}HostedZoneId`, {
                parameterName: `/all/dns/${props.domainName}/${direction}/zoneId`,
            }).stringValue;
            new aws_route53_1.ARecord(this, `${direction}ServiceARec`, {
                target: aws_route53_1.RecordTarget.fromIpAddresses(ec2_server.instancePrivateIp),
                recordName: props.instanceName,
                zone: aws_route53_1.HostedZone.fromHostedZoneAttributes(this, `${direction}ImportedHostedZone`, {
                    hostedZoneId: hostedZoneId,
                    zoneName: props.domainName,
                }),
                ttl: aws_cdk_lib_2.Duration.minutes(5),
            });
            if (props.CnameList.length != 0) {
                props.CnameList.forEach(cname => {
                    new aws_route53_1.ARecord(this, `${direction}${cname}`, {
                        target: aws_route53_1.RecordTarget.fromIpAddresses(ec2_server.instancePrivateIp),
                        recordName: cname,
                        zone: aws_route53_1.HostedZone.fromHostedZoneAttributes(this, `${direction}${cname}HostedZone`, {
                            hostedZoneId: hostedZoneId,
                            zoneName: props.domainName,
                        }),
                        ttl: aws_cdk_lib_2.Duration.minutes(5),
                    });
                });
            }
        }
    }
}
exports.EC2withDatabase = EC2withDatabase;
