diff --git a/CHANGELOG.md b/CHANGELOG.md index ea77a3e..5096a79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,10 +5,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [X.Y.Z] - 2022-MM-DD +## [0.1.0] 2024-04-15 ### Added -- -- -- +- Added initial (non-working) terraform configuration in support of deploying Unity UI to Unity Marketplace [#9](https://github.com/unity-sds/unity-sds-portal/issues/9) + +#### Known Issues +- The initial terraform configuration requires additional customization to the HTTPD Proxy and SSM parameter configurations before the system can be deployed. \ No newline at end of file diff --git a/terraform-unity-ui/cognito.tf b/terraform-unity-ui/cognito.tf new file mode 100644 index 0000000..94198fc --- /dev/null +++ b/terraform-unity-ui/cognito.tf @@ -0,0 +1,34 @@ +# ============================================================================= +# This terraform configuration for cognito intends to create a app integration +# so that user authentication can be processed by our cognito user pool +# ============================================================================= + +resource "aws_cognito_user_pool_client" "userpool_client" { + name = "${var.deployment_name}-unity-ui-client" + user_pool_id = data.aws_ssm_parameter.cognito_user_pool.id + access_token_validity = 60 + allowed_oauth_flows = ["code"] + allowed_oauth_flows_user_pool_client = true + allowed_oauth_scopes = ["email", "openid", "profile"] + auth_session_validity = 3 + callback_urls = "${data.aws_cloudfront_distribution.cloudfront_distribution.domain_name}/dashboard" + enable_token_revocation = true + explicit_auth_flows = ["ALLOW_REFRESH_TOKEN_AUTH", "ALLOW_USER_SRP_AUTH"] + generate_secret = false + id_token_validity = 60 + logout_urls = "" // todo: determine if logout urls are needed + prevent_user_existence_errors = true + refresh_token_validity = 30 + supported_identity_providers = ["COGNITO"] + + token_validity_units { + // Valid values are: seconds | minutes | hours | days + access_token = "minutes" + id_token = "minutes" + refresh_token = "days" + } +} + +output "unity_ui_cognito_client_id" { + value = aws_cognito_user_pool_client.userpool_client.id +} \ No newline at end of file diff --git a/terraform-unity-ui/ecs.tf b/terraform-unity-ui/ecs.tf new file mode 100644 index 0000000..454ff12 --- /dev/null +++ b/terraform-unity-ui/ecs.tf @@ -0,0 +1,179 @@ +resource "aws_ecs_cluster" "ui_application_cluster" { + name = "${var.deployment_name}-ui-application-cluster" + tags = { + ServiceArea = "uiux" + } +} + +resource "aws_iam_role" "ecs_execution_role" { + name = "${var.deployment_name}ecs_execution_role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Action = "sts:AssumeRole", + Effect = "Allow", + Principal = { + Service = "ecs-tasks.amazonaws.com" + } + }, + ] + }) + + permissions_boundary = data.aws_iam_policy.mcp_operator_policy.arn +} + +resource "aws_iam_role_policy_attachment" "ecs_execution_role_policy" { + role = aws_iam_role.ecs_execution_role.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" +} + +resource "aws_ecs_task_definition" "ui_application_task_definition" { + family = "ui_application" + network_mode = "awsvpc" + requires_compatibilities = ["FARGATE"] + execution_role_arn = aws_iam_role.ecs_execution_role.arn + memory = "512" + cpu = "256" + volume { + name = "ui-application-config" + + efs_volume_configuration { + file_system_id = aws_efs_file_system.ui_application_config_efs.id + root_directory = "/" + transit_encryption = "ENABLED" + transit_encryption_port = 2049 + } + } + + container_definitions = jsonencode([{ + name = "ui-application" + image = "ghcr.io/unity-sds/unity-ui-infra:latest" + environment = [ + { + name = "VPC_ID", + value = data.aws_ssm_parameter.vpc_id.value + }, + { + name = "ENV_UNITY_UI_AUTH_OAUTH_CLIENT_ID" + valueFrom = unity_ui_cognito_client_id + }, + { + name = "ENV_UNITY_UI_AUTH_OAUTH_REDIRECT_URI" + value = "${data.aws_cloudfront_distribution.cloudfront_distribution.domain_name}/dashboard" + }, + { + name = "ENV_UNITY_UI_AUTH_OAUTH_LOGOUT_ENDPOINT" + value = "" # todo insert cognito domain + }, + { + name = "ENV_UNITY_UI_AUTH_OAUTH_PROVIDER_URL" + value = "" # todo insert cognito domain + }, + { + name = "ENV_UNITY_UI_AUTH_APP_ADMIN_GROUP_NAME" + value = "Unity_Admin" + }, + { + name = "ENV_UNITY_UI_AUTH_APP_APP_VIEWER_GROUP_NAME" + value = "Unity_Viewer" + }, + { + name = "ENV_UNITY_UI_STAC_BROWSER_URL" + value = "" # todo insert stac browser url + }, + { + name = "ENV_UNITY_UI_SPS_WPST_ENDPOINT" + value = "${data.aws_cloudfront_distribution.cloudfront_distribution.domain_name}/ades-wpst" + }, + { + name = "ENV_UNITY_UI_HEALTH_DASHBOARD_ENDPOINT" + value = "" # todo insert health dashboard url + }, + { + name = "ENV_UNITY_UI_AIRFLOW" + value = "" # todo insert airflow url (need to ) + }, + { + name = "ENV_UNITY_UI_ADMIN_EMAIL" + value = "anil.natha@jpl.nasa.gov" + } + ] + portMappings = [ + { + containerPort = 8888 + hostPort = 8888 + } + ] + mountPoints = [ + { + containerPath = "/etc/apache2/sites-enabled/" + sourceVolume = "ui-application-config" + } + ] + }]) + tags = { + ServiceArea = "uiux" + } +} + +resource "aws_security_group" "ecs_sg" { + name = "${var.deployment_name}-ecs_service_sg" + description = "Security group for ECS service" + vpc_id = data.aws_ssm_parameter.vpc_id.value + + // Inbound rules + // Example: Allow HTTP and HTTPS + ingress { + from_port = 8888 + to_port = 8888 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + // Outbound rules + // Example: Allow all outbound traffic + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + ServiceArea = "uiux" + } +} + +# Update the ECS Service to use the Load Balancer +resource "aws_ecs_service" "ui_application_service" { + name = "ui-application-service" + cluster = aws_ecs_cluster.ui_application_cluster.id + task_definition = aws_ecs_task_definition.ui_application_task_definition.arn + launch_type = "FARGATE" + desired_count = 1 + + load_balancer { + target_group_arn = aws_lb_target_group.ui_application_tg.arn + container_name = "ui-application" + container_port = 8888 + } + + network_configuration { + subnets = local.subnet_ids + security_groups = [aws_security_group.ecs_sg.id] + #needed so it can pull images + assign_public_ip = true + } + tags = { + ServiceArea = "uiux" + } + depends_on = [ + aws_lb_listener.ui_application_listener, + ] +} + +output "aws_alb_domain" { + value = aws_ecs_service.ui_application_service.load_balancer.dns_name +} \ No newline at end of file diff --git a/terraform-unity-ui/efs.tf b/terraform-unity-ui/efs.tf new file mode 100644 index 0000000..493cff2 --- /dev/null +++ b/terraform-unity-ui/efs.tf @@ -0,0 +1,60 @@ +resource "aws_efs_file_system" "ui_application_config_efs" { + creation_token = "${var.deployment_name}-ui-application-config" + tags = { + Service = "U-UIUX" + } +} +resource "aws_security_group" "efs_sg" { + name = "${var.deployment_name}-efs-security-group" + description = "Security group for EFS" + vpc_id = data.aws_ssm_parameter.vpc_id.value + + # Ingress rule to allow NFS + ingress { + from_port = 2049 + to_port = 2049 + protocol = "tcp" + security_groups = [aws_security_group.ecs_sg.id] + } + + # Egress rule - allowing all outbound traffic + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Service = "U-UIUX" + } +} +resource "aws_efs_mount_target" "efs_mount_target" { + for_each = toset(local.subnet_ids) + file_system_id = aws_efs_file_system.ui_application_config_efs.id + subnet_id = each.value + security_groups = [aws_security_group.efs_sg.id] +} + +resource "aws_efs_access_point" "ui_application_config_ap" { + file_system_id = aws_efs_file_system.ui_application_config_efs.id + + posix_user { + gid = 1000 + uid = 1000 + } + + root_directory { + path = "/efs" + creation_info { + owner_gid = 1000 + owner_uid = 1000 + permissions = "0755" + } + } + + tags = { + Name = "${var.deployment_name}-ui-application-config-ap" + Service = "U-UIUX" + } +} diff --git a/terraform-unity-ui/main.tf b/terraform-unity-ui/main.tf new file mode 100644 index 0000000..dfbf648 --- /dev/null +++ b/terraform-unity-ui/main.tf @@ -0,0 +1,60 @@ +# Terraform driver to instantiate the Unity UI via Unity Marketplace + +data "aws_ssm_parameter" "vpc_id" { + name = "/unity/account/network/vpc_id" +} + +data "aws_ssm_parameter" "subnet_list" { + name = "/unity/account/network/subnet_list" +} + +data "aws_ssm_parameter" "proxylambda" { + name = "/unity/cs/management/proxy/${var.installprefix}-httpd-lambda-name" +} + +data "aws_iam_policy" "mcp_operator_policy" { + name = "mcp-tenantOperator-AMI-APIG" +} + +# This SSM parameter references the predefined cognito user pool +data "aws_ssm_parameter" "cognito_user_pool" { + name = "/unity/cs/security/shared-services-cognito-user-pool/user-pool-id" +} + +# todo: Get this param added to SSM +data "aws_ssm_parameter" "cloudfront_distribution_id" { + name = "/unity/cs/networking/shared-services-cloudfront/cloudfront-distribution-id" +} + +data "aws_cloudfront_distribution" "cloudfront_distribution" { + id = data.aws_ssm_parameter.cloudfront_distribution_id +} + +data "aws_iam_policy" "mcp_operator_policy" { + name = "mcp-tenantOperator-AMI-APIG" +} + +data "aws_cognito_user_pool_client" "unity_ui_client" { + client_id = unity_ui_cognito_client_id + user_pool_id = data.aws_ssm_parameter.cognito_user_pool.id +} + +#todo add airflow url configuation + +locals { + subnet_map = jsondecode(data.aws_ssm_parameter.subnet_list.value) + subnet_ids = nonsensitive(local.subnet_map["private"]) + public_subnet_ids = nonsensitive(local.subnet_map["public"]) +} + +##################### + +resource "aws_lambda_invocation" "demoinvocation2" { + function_name = data.aws_ssm_parameter.proxylambda.value + + input = jsonencode({ + filename = "proxy-lambda-${var.installprefix}", + template = var.template + }) + +} diff --git a/terraform-unity-ui/networking.tf b/terraform-unity-ui/networking.tf new file mode 100644 index 0000000..9bc36b5 --- /dev/null +++ b/terraform-unity-ui/networking.tf @@ -0,0 +1,53 @@ +# Create an Application Load Balancer (ALB) +resource "aws_lb" "ui_application_alb" { + name = "${var.deployment_name}-ui-application-alb" + internal = false + load_balancer_type = "application" + security_groups = [aws_security_group.ecs_sg.id] + subnets = local.public_subnet_ids + enable_deletion_protection = false + tags = { + Service = "U-UIUX" + } +} + +# Create a Target Group for UI Application +resource "aws_lb_target_group" "ui_application_tg" { + name = "${var.deployment_name}-ui-application-tg" + port = 8888 + protocol = "HTTP" + vpc_id = data.aws_ssm_parameter.vpc_id.value + target_type = "ip" + + health_check { + healthy_threshold = 2 + unhealthy_threshold = 2 + timeout = 5 + path = "/" + protocol = "HTTP" + matcher = "200" + interval = 30 + } + tags = { + Service = "U-UIUX" + } +} + +# Create a Listener for the ALB that forwards requests to the httpd Target Group +resource "aws_lb_listener" "ui_application_listener" { + load_balancer_arn = aws_lb.ui_application_alb.arn + port = 8888 + protocol = "HTTP" + + default_action { + type = "forward" + target_group_arn = aws_lb_target_group.ui_application_tg.arn + } + tags = { + Service = "U-UIUX" + } +} + +output "ui_application_alb_url" { + value = aws_lb.ui_application_alb.dns_name +} \ No newline at end of file diff --git a/terraform-unity-ui/variables.new.tf b/terraform-unity-ui/variables.new.tf new file mode 100644 index 0000000..eca933e --- /dev/null +++ b/terraform-unity-ui/variables.new.tf @@ -0,0 +1,89 @@ +variable "add_routes_to_api_gateway" { + description = "If true, adds routes to api gateway configured in account" + type = bool + default = false +} + +variable "capability" { + description = "The capability being provided to Unity." + type = string +} + +variable "capability_version" { + description = "The version of the capability being provided." + type = string +} + +variable "component" { + description = "The primary type of application/runtime that will be run on this resource." + type = string +} + +variable "created_by" { + description = "This should contain the same value as the 'service_area' variable (tag)." +} + +variable "deployment_name" { + description = "Unique name of this deployment in the account." + type = string +} + +variable "installprefix" { + description = "The management console install prefix" + type = string + default = "UnknownPrefix" +} + +variable "environment" { + description = "This should contain the same value as the 'venue' variable (tag)." +} + +variable "name" { + description = "The name of the AWS resource. All characters need to be in lowercase." + type = string +} + +variable "project" { + description = "The name of the project/mission (e.g. europa) deploying the application." + type = string +} + +variable "region" { + description = "The AWS region being used to deploy the application." + type = string + default = "us-west-2" +} + +variable "release" { + description = "The UI release version" + type = string +} + +variable "service_area" { + description = "The service area that owns the application/service being deployed." + type = string + default = "uiux" +} + +variable "stack" { + description = "This should contain the same value as the 'component' variable (tag)." + type = string +} + +variable "tags" { + description = "Tags to track key pieces of metadata associated with the deployed Unity UI application." + type = map(string) +} + +variable "template" { + default = <