diff --git a/README.md b/README.md index fe8ecb4..34de479 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,93 @@ # Sysdig Orchestrator Agent for ECS Fargate This Terraform module deploys a Sysdig orchestrator agent for Fargate into a specified VPC. + +## Example + +The module can be created using the IDs of your VPC and two subnets capable of accessing the internet. + +``` +module "sysdig_orchestrator_agent" { + source = "../sysdig-orchestrator-agent" + + name = "test-fargate-orchestrator" + + vpc_id = var.my_vpc_id + subnet = [var.my_subnet_a, var.my_subnet_b_id] + access_key = var.my_sysdig_access_key + assign_public_ip = true # if using Internet Gateway +} +``` + +The module outputs can be plugged into the Fargate workload agent data source in the [Sysdig Terraform provider](https://github.com/sysdiglabs/terraform-provider-sysdig): +``` +data "sysdig_fargate_workload_agent" "instrumented" { + ... + + orchestrator_host = module.sysdig_orchestrator_agent.orchestrator_host + orchestrator_port = module.sysdig_orchestrator_agent.orchestrator_port +} +``` + +The resulting Terraform plan will have the Sysdig Orchestrator ECS service and a load balancer, as well as instrumented container JSON to use in your ECS Fargate task. + + +## Requirements + +No requirements. + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | 3.61.0 | +| [template](#provider\_template) | 2.2.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_cloudwatch_log_group.orchestrator_agent](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | +| [aws_ecs_cluster.orchestrator_agent](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_cluster) | resource | +| [aws_ecs_service.orchestrator_agent](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_service) | resource | +| [aws_ecs_task_definition.orchestrator_agent](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_task_definition) | resource | +| [aws_iam_role.orchestrator_agent_execution_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role.orchestrator_agent_task_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_lb.orchestrator_agent](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb) | resource | +| [aws_lb_listener.orchestrator_agent](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_listener) | resource | +| [aws_lb_target_group.orchestrator_agent](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_target_group) | resource | +| [aws_security_group.orchestrator_agent](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | +| [aws_security_group_rule.orchestrator_agent_egress_rule](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_security_group_rule.orchestrator_agent_ingress_rule](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_region.current_region](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | +| [template_file.orchestrator_agent_container_definitions](https://registry.terraform.io/providers/hashicorp/template/latest/docs/data-sources/file) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [access\_key](#input\_access\_key) | Sysdig access key | `string` | n/a | yes | +| [agent\_image](#input\_agent\_image) | Orchestrator agent image | `string` | `"quay.io/sysdig/orchestrator-agent:latest"` | no | +| [agent\_tags](#input\_agent\_tags) | Comma separated list of tags for this agent | `string` | `""` | no | +| [assign\_public\_ip](#input\_assign\_public\_ip) | Provisions a public IP for the service. Required when using an Internet Gateway for egress. | `bool` | `false` | no | +| [check\_collector\_certificate](#input\_check\_collector\_certificate) | Whether to check the collector certificate when connecting. Mainly for development. | `string` | `"true"` | no | +| [collector\_host](#input\_collector\_host) | Sysdig collector host | `string` | `"collector.sysdigcloud.com"` | no | +| [collector\_port](#input\_collector\_port) | Sysdig collector port | `string` | `"6443"` | no | +| [default\_tags](#input\_default\_tags) | Default tags for all Sysdig Fargate Orchestrator resources | `map(string)` |
{
"Application": "sysdig",
"Module": "fargate-orchestrator-agent"
}
| no | +| [name](#input\_name) | Identifier for module resources | `string` | `"sysdig-fargate-orchestrator"` | no | +| [orchestrator\_port](#input\_orchestrator\_port) | Port for the workload agent to connect | `number` | `6667` | no | +| [subnets](#input\_subnets) | A list of subnets that can access the internet and are reachable by instrumented services. The subnets must be in at least 2 different AZs. | `list(string)` | n/a | yes | +| [tags](#input\_tags) | Extra tags for all Sysdig Fargate Orchestrator resources | `map(string)` | `{}` | no | +| [vpc\_id](#input\_vpc\_id) | ID of the VPC where the orchestrator should be installed | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [orchestrator\_host](#output\_orchestrator\_host) | The DNS name of the orchestrator's load balancer | +| [orchestrator\_port](#output\_orchestrator\_port) | The configured port on the orchestrator | + diff --git a/container-definitions/orchestrator-agent.json b/container-definitions/orchestrator-agent.json new file mode 100644 index 0000000..19b7b4e --- /dev/null +++ b/container-definitions/orchestrator-agent.json @@ -0,0 +1,47 @@ +[ + { + "name": "OrchestratorAgent", + "image": "${agent_image}", + "portMappings": [ + { + "hostPort": ${orchestrator_port}, + "protocol": "tcp", + "containerPort": ${orchestrator_port} + } + ], + "environment": [ + { + "name": "ACCESS_KEY", + "value": "${access_key}" + }, + { + "name": "CHECK_CERTIFICATE", + "value": "${check_certificate}" + }, + { + "name": "COLLECTOR", + "value": "${collector_host}" + }, + { + "name": "COLLECTOR_PORT", + "value": "${collector_port}" + }, + { + "name": "TAGS", + "value": "${agent_tags}" + }, + { + "name": "ADDITIONAL_CONF", + "value": "agentino_port: ${orchestrator_port}" + } + ], + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-group": "${awslogs_group}", + "awslogs-region": "${awslogs_region}", + "awslogs-stream-prefix": "ecs" + } + } + } +] diff --git a/example/README.md b/example/README.md new file mode 100644 index 0000000..edb9574 --- /dev/null +++ b/example/README.md @@ -0,0 +1,21 @@ +# Example + +This example uses the Sysdig Fargate Orchestrator module along with the Sysdig Terraform provider to deploy an instrumented ECS Task that is performing a suspicious activity. + +## Usage + +Initialize the state using `terraform init` + +Run `terraform apply`. Enter your values for the required variables. + +``` +terraform apply \ + -var='name=' \ + -var='sysdig_access_key=' \ + -var='vpc_id=' \ + -var='subnets=["", "", ...] +``` + +After a few minutes, the orchestrator and workload should be up. You can see the workload logs with `aws logs tail --follow` and the orchestrator logs using `aws logs tail -orchestrator-logs --follow`, using the value you gave Terraform for `name`. + +Clean up by running `terraform destroy`. diff --git a/example/main.tf b/example/main.tf new file mode 100644 index 0000000..9201309 --- /dev/null +++ b/example/main.tf @@ -0,0 +1,42 @@ +module "sysdig_orchestrator_agent" { + source = "../" + + name = "${var.name}-orchestrator" + + vpc_id = var.vpc_id + subnets = var.subnets + collector_host = "collector-staging2.sysdigcloud.com" + collector_port = "6443" + access_key = var.sysdig_access_key + assign_public_ip = true +} + +data "sysdig_fargate_workload_agent" "instrumented" { + container_definitions = jsonencode([ + { + "image": "quay.io/rehman0288/busyboxplus:latest", + "name": "busybox", + "EntryPoint": [ + "watch", + "-n60", + "cat", + "/etc/shadow" + ], + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-group": var.name, + "awslogs-region": "us-east-1", + "awslogs-stream-prefix": "ecs" + } + } + } + ]) + + sysdig_access_key = var.sysdig_access_key + + workload_agent_image = var.sysdig_workload_agent_image + + orchestrator_host = module.sysdig_orchestrator_agent.orchestrator_host + orchestrator_port = module.sysdig_orchestrator_agent.orchestrator_port +} diff --git a/example/provider.tf b/example/provider.tf new file mode 100644 index 0000000..b379679 --- /dev/null +++ b/example/provider.tf @@ -0,0 +1,17 @@ +terraform { + required_providers { + sysdig = { + source = "sysdiglabs/sysdig" + version = ">= 0.4.0" + } + } +} + +provider "aws" { + region = "us-east-1" +} + +provider "sysdig" { + sysdig_secure_api_token = var.sysdig_access_key +} + diff --git a/example/service.tf b/example/service.tf new file mode 100644 index 0000000..cecc395 --- /dev/null +++ b/example/service.tf @@ -0,0 +1,92 @@ +resource "aws_ecs_cluster" "example_cluster" { + name = var.name +} + +resource "aws_cloudwatch_log_group" "example_logs" { + name = var.name +} + +resource "aws_iam_role" "example_execution_role" { + assume_role_policy = data.aws_iam_policy_document.assume_role_policy.json + + managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"] +} + +resource "aws_iam_role" "example_task_role" { + assume_role_policy = data.aws_iam_policy_document.assume_role_policy.json + + inline_policy { + name = "root" + policy = data.aws_iam_policy_document.task_policy.json + } +} + +resource "aws_security_group" "example_security_group" { + description = "Allow workload to reach internet" + vpc_id = var.vpc_id +} + +resource "aws_security_group_rule" "example_egress_rule" { + type = "egress" + protocol = "all" + from_port = 0 + to_port = 0 + cidr_blocks = [ "0.0.0.0/0" ] + security_group_id = aws_security_group.example_security_group.id +} + +resource "aws_ecs_task_definition" "example_task_definition" { + family = var.name + task_role_arn = aws_iam_role.example_task_role.arn + execution_role_arn = aws_iam_role.example_execution_role.arn + + cpu = "256" + memory = "1024" + network_mode = "awsvpc" + requires_compatibilities = ["FARGATE"] + + container_definitions = data.sysdig_fargate_workload_agent.instrumented.output_container_definitions +} + +resource "aws_ecs_service" "example_service" { + name = var.name + + cluster = aws_ecs_cluster.example_cluster.id + task_definition = aws_ecs_task_definition.example_task_definition.arn + desired_count = 1 + launch_type = "FARGATE" + platform_version = "1.4.0" + + network_configuration { + subnets = var.subnets + security_groups = [ aws_security_group.example_security_group.id ] + assign_public_ip = true + } +} + +data "aws_iam_policy_document" "assume_role_policy" { + statement { + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["ecs-tasks.amazonaws.com"] + } + } +} + +data "aws_iam_policy_document" "task_policy" { + statement { + actions = [ + "ecr:GetAuthorizationToken", + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage", + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ] + + resources = ["*"] + } +} diff --git a/example/variables.tf b/example/variables.tf new file mode 100644 index 0000000..51b0fd5 --- /dev/null +++ b/example/variables.tf @@ -0,0 +1,43 @@ +# +# Required variables +# +variable "name" { + description = "Identifier for module resources" + type = string +} + +variable "vpc_id" { + description = "ID of the VPC where the orchestrator should be installed" + type = string +} + +variable "subnets" { + description = "A list of subnets that can access the internet and are reachable by instrumented services. The subnets must be in at least 2 different AZs." + type = list(string) +} + +variable "sysdig_access_key" { + description = "Sysdig access key" + type = string +} + +# +# Optional variables +# +variable "sysdig_workload_agent_image" { + description = "Workload agent image" + type = string + default = "quay.io/sysdig/workload-agent:latest" +} + +variable "collector_host" { + description = "Sysdig collector host" + type = string + default = "collector.sysdigcloud.com" +} + +variable "collector_port" { + description = "Sysdig collector port" + type = string + default = "6443" +} diff --git a/loadbalancer.tf b/loadbalancer.tf new file mode 100644 index 0000000..1076831 --- /dev/null +++ b/loadbalancer.tf @@ -0,0 +1,31 @@ +resource "aws_lb" "orchestrator_agent" { + internal = true + load_balancer_type = "network" + ip_address_type = "ipv4" + subnets = var.subnets + + tags = merge(var.tags, var.default_tags) +} + +resource "aws_lb_target_group" "orchestrator_agent" { + port = var.orchestrator_port + protocol = "TCP" + target_type = "ip" + deregistration_delay = 60 + vpc_id = var.vpc_id + + tags = merge(var.tags, var.default_tags) +} + +resource "aws_lb_listener" "orchestrator_agent" { + load_balancer_arn = aws_lb.orchestrator_agent.arn + port = var.orchestrator_port + protocol = "TCP" + + default_action { + type = "forward" + target_group_arn = aws_lb_target_group.orchestrator_agent.arn + } + + tags = merge(var.tags, var.default_tags) +} diff --git a/main.tf b/main.tf new file mode 100644 index 0000000..2c52d68 --- /dev/null +++ b/main.tf @@ -0,0 +1,29 @@ +resource "aws_ecs_cluster" "orchestrator_agent" { + name = "${var.name}-cluster" + + tags = merge(var.tags, var.default_tags) +} + +resource "aws_cloudwatch_log_group" "orchestrator_agent" { + name = "${var.name}-logs" + + tags = merge(var.tags, var.default_tags) +} + +data "template_file" "orchestrator_agent_container_definitions" { + template = file("${path.module}/container-definitions/orchestrator-agent.json") + + vars = { + agent_image = var.agent_image + access_key = var.access_key + collector_host = var.collector_host + collector_port = var.collector_port + agent_tags = var.agent_tags + check_certificate = var.check_collector_certificate + orchestrator_port = var.orchestrator_port + awslogs_region = data.aws_region.current_region.name + awslogs_group = "${var.name}-logs" + } +} + +data "aws_region" "current_region" {} diff --git a/outputs.tf b/outputs.tf new file mode 100644 index 0000000..82fea94 --- /dev/null +++ b/outputs.tf @@ -0,0 +1,9 @@ +output "orchestrator_host" { + description = "The DNS name of the orchestrator's load balancer" + value = aws_lb.orchestrator_agent.dns_name +} + +output "orchestrator_port" { + description = "The configured port on the orchestrator" + value = var.orchestrator_port +} diff --git a/roles.tf b/roles.tf new file mode 100644 index 0000000..170fa93 --- /dev/null +++ b/roles.tf @@ -0,0 +1,45 @@ +resource "aws_iam_role" "orchestrator_agent_execution_role" { + assume_role_policy = data.aws_iam_policy_document.assume_role_policy.json + + managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"] + + tags = merge(var.tags, var.default_tags) +} + +resource "aws_iam_role" "orchestrator_agent_task_role" { + assume_role_policy = data.aws_iam_policy_document.assume_role_policy.json + + inline_policy { + name = "root" + policy = data.aws_iam_policy_document.task_policy.json + } + + tags = merge(var.tags, var.default_tags) +} + +data "aws_iam_policy_document" "assume_role_policy" { + statement { + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["ecs-tasks.amazonaws.com"] + } + } +} + +data "aws_iam_policy_document" "task_policy" { + statement { + actions = [ + "ecr:GetAuthorizationToken", + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage", + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ] + + resources = ["*"] + } +} diff --git a/security-group.tf b/security-group.tf new file mode 100644 index 0000000..72ebf6c --- /dev/null +++ b/security-group.tf @@ -0,0 +1,24 @@ +resource "aws_security_group" "orchestrator_agent" { + description = "Allow agentino to connect" + vpc_id = var.vpc_id + + tags = merge(var.tags, var.default_tags) +} + +resource "aws_security_group_rule" "orchestrator_agent_ingress_rule" { + type = "ingress" + protocol = "tcp" + from_port = var.orchestrator_port + to_port = var.orchestrator_port + cidr_blocks = [ "0.0.0.0/0" ] + security_group_id = aws_security_group.orchestrator_agent.id +} + +resource "aws_security_group_rule" "orchestrator_agent_egress_rule" { + type = "egress" + protocol = "all" + from_port = 0 + to_port = 0 + cidr_blocks = [ "0.0.0.0/0" ] + security_group_id = aws_security_group.orchestrator_agent.id +} diff --git a/service.tf b/service.tf new file mode 100644 index 0000000..98bddd6 --- /dev/null +++ b/service.tf @@ -0,0 +1,25 @@ +resource "aws_ecs_service" "orchestrator_agent" { + name = "OrchestratorAgent" + cluster = aws_ecs_cluster.orchestrator_agent.id + task_definition = aws_ecs_task_definition.orchestrator_agent.arn + desired_count = 1 + launch_type = "FARGATE" + platform_version = "1.4.0" + depends_on = [ aws_lb_listener.orchestrator_agent ] + + load_balancer { + target_group_arn = aws_lb_target_group.orchestrator_agent.arn + container_name = "OrchestratorAgent" + container_port = var.orchestrator_port + } + + network_configuration { + subnets = var.subnets + + security_groups = [ aws_security_group.orchestrator_agent.id ] + + assign_public_ip = var.assign_public_ip + } + + tags = merge(var.tags, var.default_tags) +} diff --git a/task.tf b/task.tf new file mode 100644 index 0000000..fa3868b --- /dev/null +++ b/task.tf @@ -0,0 +1,12 @@ +resource "aws_ecs_task_definition" "orchestrator_agent" { + family = "${var.name}-orchestrator-agent" + task_role_arn = "${aws_iam_role.orchestrator_agent_task_role.arn}" + execution_role_arn = "${aws_iam_role.orchestrator_agent_execution_role.arn}" + network_mode = "awsvpc" + requires_compatibilities = ["FARGATE"] + cpu = "2048" + memory = "8192" + container_definitions = data.template_file.orchestrator_agent_container_definitions.rendered + + tags = merge(var.tags, var.default_tags) +} diff --git a/variables.tf b/variables.tf new file mode 100644 index 0000000..003b1ff --- /dev/null +++ b/variables.tf @@ -0,0 +1,83 @@ +# +# Required variables +# +variable "name" { + description = "Identifier for module resources" + type = string + default = "sysdig-fargate-orchestrator" +} + +variable "vpc_id" { + description = "ID of the VPC where the orchestrator should be installed" + type = string +} + +variable "access_key" { + description = "Sysdig access key" + type = string +} + +variable "subnets" { + description = "A list of subnets that can access the internet and are reachable by instrumented services. The subnets must be in at least 2 different AZs." + type = list(string) +} + +# +# Optional variables +# +variable "orchestrator_port" { + description = "Port for the workload agent to connect" + type = number + default = 6667 +} + +variable "agent_image" { + description = "Orchestrator agent image" + type = string + default = "quay.io/sysdig/orchestrator-agent:latest" +} + +variable "collector_host" { + description = "Sysdig collector host" + type = string + default = "collector.sysdigcloud.com" +} + +variable "collector_port" { + description = "Sysdig collector port" + type = string + default = "6443" +} + +variable "agent_tags" { + description = "Comma separated list of tags for this agent" + type = string + default = "" +} + +variable "check_collector_certificate" { + description = "Whether to check the collector certificate when connecting. Mainly for development." + type = string + default = "true" +} + +variable "assign_public_ip" { + description = "Provisions a public IP for the service. Required when using an Internet Gateway for egress." + type = bool + default = false +} + +variable "tags" { + description = "Extra tags for all Sysdig Fargate Orchestrator resources" + type = map(string) + default = {} +} + +variable "default_tags" { + description = "Default tags for all Sysdig Fargate Orchestrator resources" + type = map(string) + default = { + Application = "sysdig" + Module = "fargate-orchestrator-agent" + } +}