diff --git a/keyserver/src/user/checks.js b/keyserver/src/user/checks.js index 06fb70195..ddf6861f5 100644 --- a/keyserver/src/user/checks.js +++ b/keyserver/src/user/checks.js @@ -1,36 +1,39 @@ // @flow import { getCommConfig } from 'lib/utils/comm-config.js'; + +// changes here should be reflected for keyserver_user_credentials in +// services/terraform/self-host/variables.tf export type UserCredentials = { +username: string, +password: string, +usingIdentityCredentials?: boolean, +forceLogin?: boolean, }; async function ensureUserCredentials() { const userCredentials = await getCommConfig({ folder: 'secrets', name: 'user_credentials', }); if (!userCredentials) { console.warn( 'User credentials for the keyserver owner must be specified. They can be ' + 'specified either by setting the ' + '`COMM_JSONCONFIG_secrets_user_credentials` environmental variable, or by ' + 'setting a file at keyserver/secrets/user_credentials.json. The contents ' + 'should be a JSON blob that looks like this:\n' + '{\n' + ' "username": ,\n' + ' "password": \n' + '}\n', ); // Since we don't want to apply the migration until there are credentials; // throw the error and force keyserver to be configured next restart throw new Error('missing_keyserver_owner_credentials'); } } export { ensureUserCredentials }; diff --git a/services/terraform/self-host/aws_db.tf b/services/terraform/self-host/aws_db.tf index 7f2c68978..96ecb3df2 100644 --- a/services/terraform/self-host/aws_db.tf +++ b/services/terraform/self-host/aws_db.tf @@ -1,92 +1,99 @@ # MariaDB Security Group resource "aws_security_group" "keyserver_mariadb_security_group" { name = "keyserver-mariadb-sg" description = "Allow inbound traffic on port 3307 and all outbound traffic" vpc_id = local.vpc_id # Inbound rules + ingress { + from_port = 3307 + to_port = 3307 + protocol = "tcp" + security_groups = [aws_security_group.keyserver_service.id] + } + ingress { from_port = 3307 to_port = 3307 protocol = "tcp" cidr_blocks = ["${var.allowed_ip}/32"] } # Outbound rules egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } } # MariaDB RDS Instance resource "aws_db_instance" "mariadb" { allocated_storage = 100 max_allocated_storage = 3000 storage_type = "gp3" db_name = "mariadb" identifier = "mariadb-instance" engine = "mariadb" engine_version = "10.11" instance_class = "db.m6g.large" db_subnet_group_name = aws_db_subnet_group.public_db_subnet_group.name vpc_security_group_ids = [aws_security_group.keyserver_mariadb_security_group.id] username = var.mariadb_username password = var.mariadb_password parameter_group_name = aws_db_parameter_group.mariadb_parameter_group.name storage_encrypted = true publicly_accessible = true port = 3307 skip_final_snapshot = true } # MariaDB Parameter Group resource "aws_db_parameter_group" "mariadb_parameter_group" { name = "mariadb-parameter-group" family = "mariadb10.11" parameter { apply_method = "pending-reboot" name = "performance_schema" value = "1" } parameter { apply_method = "immediate" name = "max_allowed_packet" # 256 MiB: (1024 * 1024 * 256) value = "268435456" } parameter { apply_method = "immediate" name = "local_infile" value = "0" } parameter { apply_method = "immediate" name = "sql_mode" value = "STRICT_ALL_TABLES" } parameter { apply_method = "pending-reboot" name = "innodb_buffer_pool_size" value = "{DBInstanceClassMemory*3/4}" } parameter { apply_method = "pending-reboot" name = "innodb_ft_min_token_size" value = "1" } parameter { apply_method = "immediate" name = "innodb_ft_enable_stopword" value = "0" } } diff --git a/services/terraform/self-host/aws_ecs.tf b/services/terraform/self-host/aws_ecs.tf new file mode 100644 index 000000000..f9376d01c --- /dev/null +++ b/services/terraform/self-host/aws_ecs.tf @@ -0,0 +1,23 @@ +resource "aws_ecs_cluster" "keyserver_cluster" { + name = "keyserver-cluster" + + configuration { + execute_command_configuration { + logging = "DEFAULT" + } + } +} + +# Namespace for services to be able to communicate with each other +# by their hostnames. Similar to docker compose network. +resource "aws_service_discovery_http_namespace" "keyserver_cluster" { + name = "keyserver-cluster-http-namespace" + tags = { + "AmazonECSManaged" = "true" + } +} + +resource "aws_ecs_cluster_capacity_providers" "keyserver_cluster" { + cluster_name = aws_ecs_cluster.keyserver_cluster.name + capacity_providers = ["FARGATE"] +} diff --git a/services/terraform/self-host/aws_iam.tf b/services/terraform/self-host/aws_iam.tf new file mode 100644 index 000000000..5ab58425a --- /dev/null +++ b/services/terraform/self-host/aws_iam.tf @@ -0,0 +1,87 @@ +resource "aws_iam_role" "ecs_task_role" { + name = "ecs-iam_role" + description = "Allows to SSH into ECS containers" + assume_role_policy = data.aws_iam_policy_document.assume_role_ecs_ec2.json + + managed_policy_arns = [ + aws_iam_policy.allow_ecs_exec.arn, + ] +} + +data "aws_iam_policy_document" "assume_role_ecs_ec2" { + statement { + effect = "Allow" + actions = [ + "sts:AssumeRole", + ] + principals { + type = "Service" + identifiers = [ + "ec2.amazonaws.com", + "ecs-tasks.amazonaws.com" + ] + } + } +} + +resource "aws_iam_policy" "allow_ecs_exec" { + name = "allow-ecs-exec" + description = "Adds SSM permissions to enable ECS Exec" + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Action = [ + "ssmmessages:CreateControlChannel", + "ssmmessages:CreateDataChannel", + "ssmmessages:OpenControlChannel", + "ssmmessages:OpenDataChannel" + ] + Resource = "*" + } + ] + }) +} + +resource "aws_iam_role" "fargate_execution_role" { + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Principal = { + Service = "ecs-tasks.amazonaws.com" + } + Action = "sts:AssumeRole" + } + ] + }) +} + +resource "aws_iam_role_policy_attachment" "fargate_execution_role" { + role = aws_iam_role.fargate_execution_role.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" +} + +resource "aws_iam_role" "ecs_task_execution" { + name = "ecsTaskExecutionRole" + assume_role_policy = jsonencode({ + Version = "2008-10-17" + Statement = [ + { + Sid = "" + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "ecs-tasks.amazonaws.com" + } + } + ] + }) + + managed_policy_arns = [ + "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy", + "arn:aws:iam::aws:policy/CloudWatchLogsFullAccess", + ] +} diff --git a/services/terraform/self-host/keyserver_primary.tf b/services/terraform/self-host/keyserver_primary.tf new file mode 100644 index 000000000..cf8e4ca8e --- /dev/null +++ b/services/terraform/self-host/keyserver_primary.tf @@ -0,0 +1,155 @@ +locals { + keyserver_service_image_tag = "0.1" + keyserver_service_server_image = "commapp/keyserver:${local.keyserver_service_image_tag}" + keyserver_service_container_name = "keyserver-primary" +} + +resource "aws_cloudwatch_log_group" "ecs_log_group" { + name = "/ecs/keyserver-primary-task-def" + retention_in_days = 7 +} + +output "mariadb_address" { + value = aws_db_instance.mariadb.address +} + +resource "aws_ecs_task_definition" "keyserver_service" { + network_mode = "awsvpc" + family = "keyserver-primary-task-def" + requires_compatibilities = ["FARGATE"] + task_role_arn = aws_iam_role.ecs_task_role.arn + execution_role_arn = aws_iam_role.ecs_task_execution.arn + cpu = "1024" + memory = "3072" + + ephemeral_storage { + size_in_gib = 40 + } + + container_definitions = jsonencode([ + { + name = local.keyserver_service_container_name + image = local.keyserver_service_server_image + essential = true + portMappings = [ + { + name = "keyserver-port" + containerPort = 3000 + protocol = "tcp" + }, + { + name = "http-port" + containerPort = 80 + protocol = "tcp" + appProtocol = "http" + }, + ] + environment = [ + { + name = "COMM_DATABASE_HOST" + value = "${aws_db_instance.mariadb.address}" + }, + { + name = "COMM_DATABASE_DATABASE" + value = "comm" + }, + { + name = "COMM_DATABASE_PORT" + value = "3307" + }, + { + name = "COMM_DATABASE_USER" + value = "${var.mariadb_username}" + }, + { + name = "COMM_DATABASE_PASSWORD" + value = "${var.mariadb_password}" + }, + { + name = "COMM_JSONCONFIG_secrets_user_credentials" + value = jsonencode(var.keyserver_user_credentials) + }, + { + name = "COMM_JSONCONFIG_facts_webapp_cors" + value = jsonencode({ + "domain" : "https://web.comm.app" + }) + }, + { + name = "COMM_JSONCONFIG_secrets_identity_service_config", + value = jsonencode({ + "identitySocketAddr" : "${var.identity_socket_address}" + }) + }, + ] + logConfiguration = { + "logDriver" = "awslogs" + "options" = { + "awslogs-create-group" = "true" + "awslogs-group" = aws_cloudwatch_log_group.ecs_log_group.name + "awslogs-stream-prefix" = "ecs" + "awslogs-region" = "${var.region}" + } + } + linuxParameters = { + initProcessEnabled = true + } + } + ]) + + runtime_platform { + cpu_architecture = "ARM64" + operating_system_family = "LINUX" + } + + skip_destroy = false +} + +resource "aws_ecs_service" "keyserver_primary_service" { + name = "keyserver-primary-service" + cluster = aws_ecs_cluster.keyserver_cluster.id + task_definition = aws_ecs_task_definition.keyserver_service.arn + launch_type = "FARGATE" + enable_execute_command = true + enable_ecs_managed_tags = true + force_new_deployment = true + desired_count = 1 + + network_configuration { + subnets = local.vpc_subnets + security_groups = [aws_security_group.keyserver_service.id] + assign_public_ip = true + } + + deployment_circuit_breaker { + enable = true + rollback = true + } +} + +resource "aws_security_group" "keyserver_service" { + name = "keyserver-service-ecs-sg" + vpc_id = local.vpc_id + + # Allow all inbound traffic. This is temporary until load balancer is configured + ingress { + from_port = 0 + to_port = 65535 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + # Allow all outbound traffic + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + lifecycle { + create_before_destroy = true + } +} + + diff --git a/services/terraform/self-host/variables.tf b/services/terraform/self-host/variables.tf index 3e52f231e..aef201744 100644 --- a/services/terraform/self-host/variables.tf +++ b/services/terraform/self-host/variables.tf @@ -1,40 +1,54 @@ +variable "keyserver_user_credentials" { + description = "Credentials for user authentication" + type = object({ + username = string + password = string + usingIdentityCredentials = optional(bool) + force = optional(bool) + }) +} + variable "mariadb_username" { description = "MariaDB username" type = string sensitive = true } variable "mariadb_password" { description = "MariaDB password" type = string sensitive = true } variable "region" { description = "The AWS region to deploy your keyserver in" type = string default = "us-west-1" } variable "allowed_ip" { description = "IP address" type = string } variable "user_created_vpc" { description = "Use non-default vpc and subnets" - type = bool - default = false } variable "availability_zone_1" { description = "First availability zone for vpc subnet if user created vpc" type = string default = "us-west-1b" } variable "availability_zone_2" { description = "Second availability zone for vpc subnet if user created vpc" type = string default = "us-west-1c" } + +variable "identity_socket_address" { + description = "The socket address to access the identity service" + type = string + default = "https://identity.commtechnologies.org:50054" +}