diff --git a/backend/resources/dynamodb.yml b/backend/resources/dynamodb.yml index 05e7b534..08fa560e 100644 --- a/backend/resources/dynamodb.yml +++ b/backend/resources/dynamodb.yml @@ -62,10 +62,10 @@ Resources: LocalSecondaryIndexes: - IndexName: QuestionLSI KeySchema: - - AttributeName: hashKey - KeyType: HASH - - AttributeName: question - KeyType: RANGE + - AttributeName: hashKey + KeyType: HASH + - AttributeName: question + KeyType: RANGE Projection: ProjectionType: ALL @@ -140,3 +140,38 @@ Resources: KeyType: RANGE Projection: ProjectionType: ALL + + EntitiesParameter: + Type: AWS::SSM::Parameter + Properties: + Name: /${self:custom.stage}-sparcs-events-entities + Type: String + Value: ${self:custom.entities} + + RegistrationsParameter: + Type: AWS::SSM::Parameter + Properties: + Name: /${self:custom.stage}-sparcs-events-registrations + Type: String + Value: ${self:custom.registrations} + + PreRegistrationsParameter: + Type: AWS::SSM::Parameter + Properties: + Name: /${self:custom.stage}-sparcs-events-preregistrations + Type: String + Value: ${self:custom.preregistrations} + + EvaluationsParameter: + Type: AWS::SSM::Parameter + Properties: + Name: /${self:custom.stage}-sparcs-events-evaluations + Type: String + Value: ${self:custom.evaluations} + + EventsParameter: + Type: AWS::SSM::Parameter + Properties: + Name: /${self:custom.stage}-sparcs-events-events + Type: String + Value: ${self:custom.events} diff --git a/backend/resources/frontend_role.yml b/backend/resources/frontend_role.yml new file mode 100644 index 00000000..66ed4e7a --- /dev/null +++ b/backend/resources/frontend_role.yml @@ -0,0 +1,43 @@ +Resources: + FrontendSSMReadOnlyRole: + Type: AWS::IAM::Role + Properties: + RoleName: frontend-ssm-role-${sls:stage} + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Action: + - sts:AssumeRole + Policies: + - PolicyName: SSMReadOnlyAccess + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: + - ssm:GetParameter + - ssm:GetParameters + - ssm:GetParametersByPath + Resource: + - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/techtix/auth-api-url-${sls:stage}" + - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/techtix/events-api-url-${sls:stage}" + - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/techtix/payment-api-url-${sls:stage}" + + - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/techtix/cognito-user-pool-id-${sls:stage}" + - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/techtix/cognito-user-pool-client-id-${sls:stage}" + - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/techtix/cognito-domain-url-${sls:stage}" + - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/techtix/cognito-signin-redirect-${sls:stage}" + - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/techtix/cognito-signin-redirect-local" + + - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${sls:stage}-sparcs-events*" + + - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/sparcs-events-email-queue-url-${sls:stage}" + - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/sparcs-events-certificate-queue-url-${sls:stage}" + +Outputs: + FrontendRoleArn: + Value: !GetAtt FrontendSSMReadOnlyRole.Arn diff --git a/backend/resources/s3.yml b/backend/resources/s3.yml index bd605442..b9202783 100644 --- a/backend/resources/s3.yml +++ b/backend/resources/s3.yml @@ -7,13 +7,23 @@ Resources: Status: Enabled CorsConfiguration: CorsRules: + - AllowedHeaders: ["*"] - AllowedHeaders: ["*"] AllowedMethods: [GET, PUT, HEAD] AllowedOrigins: ["*"] + AllowedOrigins: ["*"] Id: ${self:custom.bucket}-name MaxAge: "3600" + MaxAge: "3600" PublicAccessBlockConfiguration: BlockPublicAcls: false IgnorePublicAcls: false BlockPublicPolicy: false RestrictPublicBuckets: false + + BucketNameParameter: + Type: AWS::SSM::Parameter + Properties: + Name: /${self:custom.stage}-sparcs-events-file-bucket + Type: String + Value: ${self:custom.bucket} diff --git a/backend/scripts/generate-env.py b/backend/scripts/generate-env.py new file mode 100644 index 00000000..bd19e500 --- /dev/null +++ b/backend/scripts/generate-env.py @@ -0,0 +1,150 @@ +import argparse +import json +import os +from enum import Enum + +import boto3 +from botocore.exceptions import ClientError + + +class Environments(str, Enum): + DEV = 'dev' + PROD = 'prod' + LOCAL = 'local' + TEST = 'test' + + +class ConfigAssembler: + """ + Assembles the TechTix (Backend) config file + """ + + def __init__(self, aws_region='ap-southeast-1', environment=Environments.DEV.value): + self.__input_environment = environment + self.__project_name = 'sparcs-events' + + # Determine the deployment stage, defaulting to 'dev' for None or 'local' environments + if not self.__input_environment or self.__input_environment == Environments.LOCAL.value: + self.__stage = Environments.DEV.value + else: + self.__stage = self.__input_environment + + self.__region = 'ap-southeast-1' if aws_region is None else aws_region + self.__ssm_client = boto3.client('ssm', region_name=self.__region) + self.__secrets_client = boto3.client('secretsmanager', region_name=self.__region) + self.__base_dir = os.getcwd() + + def __get_parameter(self, key, decrypt=False) -> str: + """ + Retrieves parameter values from SSM + + :param key: key of parameter value to be retrieved + :param decrypt: flag if value is decrypted + :return: parameter value string + """ + kwargs = {'Name': key, 'WithDecryption': decrypt} + value = '' + try: + resp = self.__ssm_client.get_parameter(**kwargs) + except ClientError as e: + print(f'Error: {e.response["Error"]["Code"]} - {key}') + else: + value = resp['Parameter']['Value'] + return value + + def __get_secret(self, secret_arn) -> str: + """ + Retrieves secret value from AWS Secrets Manager + + :param secret_arn: ARN of the secret to retrieve + :return: secret value string + """ + try: + resp = self.__secrets_client.get_secret_value(SecretId=secret_arn) + return resp['SecretString'] + except ClientError as e: + print(f'Error retrieving secret: {e.response["Error"]["Code"]} - {secret_arn}') + return '' + + @staticmethod + def escape_env_value(value: str) -> str: + return value.replace('$', '$$') + + @staticmethod + def write_config(file_handle, key, value) -> None: + """ + Writes specified config key-value in the config file + + :param file_handle: File pointer + :param key: key of config + :param value: value of config + :return: None + """ + entry = f'{key}={ConfigAssembler.escape_env_value(str(value))}\n' + file_handle.write(entry) + + def construct_config_file(self) -> None: + """ + Constructs the config file for Helix + + :return: None + """ + + region = 'ap-southeast-1' + stage = self.__stage + entities_table = self.__get_parameter(f"/{stage}-sparcs-events-entities") + registrations_table = self.__get_parameter(f"/{stage}-sparcs-events-registrations") + preregistrations_table = self.__get_parameter(f"/{stage}-sparcs-events-preregistrations") + evaluations_table = self.__get_parameter(f"/{stage}-sparcs-events-evaluations") + events_table = self.__get_parameter(f"/{stage}-sparcs-events") + email_queue = self.__get_parameter(f"/sparcs-events-email-queue-url-{stage}") + certificate_queue = self.__get_parameter(f"/sparcs-events-certificate-queue-url-{stage}") + s3_bucket = self.__get_parameter(f"/{stage}-sparcs-events-file-bucket") + + userpool_id = f"techtix/cognito-user-pool-id-{stage}" + userpool_client_id = f"techtix/cognito-user-pool-client-id-{stage}" + + if self.__input_environment == Environments.LOCAL.value or stage == Environments.LOCAL.value: + frontend_url = 'http://localhost:3000' + else: + frontend_url = self.__get_parameter(f"techtix/frontend-url-{stage}") + + # Determine if this is a local environment + is_local = ( + self.__input_environment == Environments.LOCAL.value + or self.__input_environment == Environments.TEST.value + ) + + config_file = f'{self.__base_dir}/.env' + + with open(config_file, 'w', encoding='utf-8') as file_handle: + self.write_config(file_handle, 'REGION', region) + self.write_config(file_handle, 'FRONTEND_URL', frontend_url) + self.write_config(file_handle, 'ENTITIES_TABLE', entities_table) + self.write_config(file_handle, 'REGISTRATIONS_TABLE', registrations_table) + self.write_config(file_handle, 'PREREGISTRATIONS_TABLE', preregistrations_table) + self.write_config(file_handle, 'EVALUATIONS_TABLE', evaluations_table) + self.write_config(file_handle, 'EVENTS_TABLE', events_table) + self.write_config(file_handle, 'EMAIL_QUEUE', email_queue) + self.write_config(file_handle, 'CERTIFICATE_QUEUE', certificate_queue) + self.write_config(file_handle, 'S3_BUCKET', s3_bucket) + self.write_config(file_handle, 'USERPOOL_ID', userpool_id) + self.write_config(file_handle, 'USERPOOL_CLIENT_ID', userpool_client_id) + self.write_config(file_handle, 'STAGE', stage) + + print(f'Configuration file created successfully at: {config_file}') + + +if __name__ == '__main__': + print(Environments) + parser = argparse.ArgumentParser(description='TechTix (Backend) Configuration Assembler') + parser.add_argument('-r', '--region', help='AWS Region (default: ap-southeast-1)') + parser.add_argument('-s', '--stage', help='Environment Name (default: dev)') + args = parser.parse_args() + + print('Arguments:', args) + region = args.region + input_stage = args.stage + + config_assembler = ConfigAssembler(region, input_stage) + config_assembler.construct_config_file() \ No newline at end of file diff --git a/backend/serverless.yaml b/backend/serverless.yaml index 7ec526fb..88ce06d7 100644 --- a/backend/serverless.yaml +++ b/backend/serverless.yaml @@ -74,6 +74,7 @@ resources: - ${file(resources/api_gateway.yml)} - ${file(resources/s3.yml)} - ${file(resources/sqs.yml)} + - ${file(resources/frontend_role.yml)} - Resources: ApiGatewayCloudWatchRole: Type: AWS::IAM::Role