Skip to content

Commit 7ee5ec2

Browse files
authored
Merge pull request #2 from unity-sds/403-ssm-httpd-config
Moving HTTPD configuration to SSM parameters
2 parents a516b27 + a1770a7 commit 7ee5ec2

12 files changed

Lines changed: 229 additions & 325 deletions

File tree

Dockerfile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
FROM ubuntu/apache2
22
LABEL authors="barber"
33

4-
RUN apt update && apt install -y libapache2-mod-auth-openidc ca-certificates && a2enmod auth_openidc proxy proxy_http proxy_wstunnel rewrite headers && \
4+
RUN apt update && apt install -y libapache2-mod-auth-openidc ca-certificates python3-boto3 && a2enmod auth_openidc proxy proxy_http proxy_html proxy_wstunnel substitute rewrite headers && \
55
sed -i 's/Listen 80/Listen 8080/' /etc/apache2/ports.conf
66

7-
COPY write_site.sh /usr/local/bin/
7+
COPY write_site.py /usr/local/bin/
88

9-
CMD ["/bin/bash", "-c", "/usr/local/bin/write_site.sh && apache2-foreground"]
9+
CMD ["/bin/bash", "-c", "python3 /usr/local/bin/write_site.py && apache2-foreground"]

README.md

Lines changed: 68 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,61 +6,99 @@ To enable/disable modules, update and change the HTTPD installation then there i
66

77
The webservers default port is also 8080 to let it traverse the MCP NACL.
88

9-
When deployed this terraform code creates an ECS cluster, with an EFS backend that then allows us to store apache configs
10-
in a filesystem that wont vanish when it restarts. As such the EFS filesystem also needs a way to create new files, this is
11-
done via a lambda function that writes valid apache config files to the EFS mount.
9+
When deployed this terraform code creates an ECS cluster, with a baseline set of SSM parameters that other services can then extend with their own Apache HTTPD configurations. The configurations are pulled down and collated by the container on restart, so reloading of the configuration after changes is handled by triggering a lambda function.
1210

13-
A sample trigger:
11+
A sample configuration snippet and trigger:
1412
```
15-
variable "template" {
16-
default = <<EOT
17-
RewriteEngine on
18-
ProxyPass /sample http://test-demo-alb-616613476.us-west-2.elb.amazonaws.com:8888/sample/hello.jsp
19-
ProxyPassReverse /sample http://test-demo-alb-616613476.us-west-2.elb.amazonaws.com:8888/sample/hello.jsp
13+
resource "aws_ssm_parameter" "managementproxy_config" {
14+
depends_on = [aws_ssm_parameter.managementproxy_closevirtualhost]
15+
name = "/unity/${var.project}/${var.venue}/cs/management/proxy/configurations/010-management"
16+
type = "String"
17+
value = <<-EOT
18+
19+
RewriteEngine on
20+
RewriteCond %%{HTTP:Upgrade} websocket [NC]
21+
RewriteCond %%{HTTP:Connection} upgrade [NC]
22+
RewriteRule /management/(.*) ws://${var.mgmt_dns}/$1 [P,L]
23+
<Location "/management/">
24+
ProxyPass http://${var.mgmt_dns}/
25+
ProxyPassReverse http://${var.mgmt_dns}/
26+
ProxyPreserveHost On
27+
FallbackResource /management/index.html
28+
</Location>
2029
2130
EOT
2231
}
2332
2433
resource "aws_lambda_invocation" "demoinvocation2" {
25-
function_name = "ZwUycV-unity-proxy-httpdproxymanagement"
26-
27-
input = jsonencode({
28-
filename = "example_filename1",
29-
template = var.template
30-
})
31-
34+
function_name = "${var.project}-${var.venue}-httpdproxymanagement"
3235
}
3336
```
34-
The config files are written as flat configs. They are then used inside a main apache2 config like this:
3537

38+
39+
The configuration is collated from SSM parameters residing under `/unity/${var.project}/${var.venue}/cs/management/proxy/configurations/`, and assembled like so:
3640
```
3741
<VirtualHost *:8080>
38-
Include /etc/apache2/sites-enabled/mgmt.conf
39-
### ADD MORE HOSTS BELOW THIS LINE
42+
43+
RewriteEngine on
44+
RewriteCond %{HTTP:Upgrade} websocket [NC]
45+
RewriteCond %{HTTP:Connection} upgrade [NC]
46+
RewriteRule /management/(.*) ws://internal-unity-mc-alb-hzs9j-1269535099.us-west-2.elb.amazonaws.com:8080/$1 [P,L]
47+
<Location "/management/">
48+
ProxyPass http://internal-unity-mc-alb-hzs9j-1269535099.us-west-2.elb.amazonaws.com:8080/
49+
ProxyPassReverse http://internal-unity-mc-alb-hzs9j-1269535099.us-west-2.elb.amazonaws.com:8080/
50+
ProxyPreserveHost On
51+
FallbackResource /management/index.html
52+
</Location>
4053
4154
</VirtualHost>
4255
```
4356

44-
They will be added as additional config files below the comment line. The httpd task is then restarted to allow the
45-
config to then take effect.
57+
Live checking of the "current" configuration may be accomplished with `write_site.py` in a local environment:
58+
```
59+
% DEBUG=yes UNITY_PROJECT=btlunsfo UNITY_VENUE=dev11 python write_site.py
60+
<VirtualHost *:8080>
4661
47-
There is currently no way to remove files or fix a broken config other than mounting the EFS mount into an EC2 server and making changes.
48-
To do this you will need to edit the security group to allow access to the EC2 box and then install the EFS utils.
62+
RewriteEngine on
63+
RewriteCond %{HTTP:Upgrade} websocket [NC]
64+
RewriteCond %{HTTP:Connection} upgrade [NC]
65+
RewriteRule /management/(.*) ws://internal-unity-mc-alb-hzs9j-1269535099.us-west-2.elb.amazonaws.com:8080/$1 [P,L]
66+
<Location "/management/">
67+
ProxyPass http://internal-unity-mc-alb-hzs9j-1269535099.us-west-2.elb.amazonaws.com:8080/
68+
ProxyPassReverse http://internal-unity-mc-alb-hzs9j-1269535099.us-west-2.elb.amazonaws.com:8080/
69+
ProxyPreserveHost On
70+
FallbackResource /management/index.html
71+
</Location>
4972
50-
## Manually adding a file/template
73+
</VirtualHost>
5174
52-
One can execute the httpdmanager lambda function directly with the following json syntax:
75+
```
5376

77+
This repository configures only one virtualhost (both open and close directives), but others may be added. This can be accomplished by simply adding more SSM parameters:
5478
```
55-
{
56-
"filename": "example-extension",
57-
"template": "SSLProxyEngine On\nProxyPreserveHost On\n\nProxyPass \/hub https:\/\/jupyter.us-west-2.elb.amazonaws.com:443\/hub\/\nProxyPassReverse \/hub https:\/\/jupyter.us-west-2.elb.amazonaws.com:443\/hub\/"
79+
resource "aws_ssm_parameter" "managementproxy_openvirtualhost" {
80+
name = "/unity/${var.project}/${var.venue}/cs/management/proxy/configurations/001-openvhost8080"
81+
type = "String"
82+
value = <<-EOT
83+
<VirtualHost *:8080>
84+
EOT
5885
}
5986
87+
resource "aws_ssm_parameter" "managementproxy_closevirtualhost" {
88+
depends_on = [aws_ssm_parameter.managementproxy_openvirtualhost]
89+
name = "/unity/${var.project}/${var.venue}/cs/management/proxy/configurations/100-closevhost8080"
90+
type = "String"
91+
value = <<-EOT
92+
</VirtualHost>
93+
EOT
94+
}
6095
```
96+
NOTE the names of each of these SSM parameters:
97+
- 001-openvhost8080
98+
- 010-management
99+
- 100-closevhost8080
61100

62-
The template must be json encoded. I've used https://nddapp.com/json-encoder.html successfully.
63-
101+
For additional virtualhosts, please pick an ordinal number range that is *greater* than 100 (e.g. 101-openTestHost, 120-closeTestHost).
64102

65103
## How do I know what to add in the 'template' file above?
66104
We are not perfect human beings. In order to iterate quickly on the above templat contents, we have created a development proxy environment that can be tested mostly locally. Check out the `develop` directory for instructions.

lambda/lambda.py

Lines changed: 11 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,34 @@
1-
import boto3
21
import os
32

4-
def insert_new_host_line(file_path, new_line):
5-
# Marker to find the position where the new line will be inserted
6-
marker = "### ADD MORE HOSTS BELOW THIS LINE"
7-
8-
# Read the original file content
9-
with open(file_path, 'r') as file:
10-
lines = file.readlines()
11-
12-
# Find the marker and insert the new line after it
13-
for i, line in enumerate(lines):
14-
if marker in line:
15-
# Insert new line after the marker line
16-
lines.insert(i + 1, new_line + "\n")
17-
break # Exit the loop once the marker is found and the line is inserted
18-
19-
# Write the modified content back to the file
20-
with open(file_path, 'w') as file:
21-
file.writelines(lines)
3+
import boto3
224

235

246
def lambda_handler(event, context):
257

26-
filename = event.get('filename')
27-
text_blob = event.get('template')
28-
29-
efs_mount_path = '/mnt/efs'
30-
file_path = os.path.join(efs_mount_path, filename+".conf")
31-
with open(file_path, 'w') as file:
32-
file.write(text_blob)
33-
34-
35-
# Update main file
36-
file_path = "/mnt/efs/main.conf"
37-
new_line = " Include /etc/apache2/sites-enabled/"+filename+".conf"
38-
insert_new_host_line(file_path, new_line)
39-
408
# Restart an ECS task
41-
ecs_client = boto3.client('ecs')
42-
service_name = os.environ.get('SERVICE_NAME')
43-
cluster_name = os.environ.get('CLUSTER_NAME')
9+
ecs_client = boto3.client("ecs")
10+
service_name = os.environ.get("SERVICE_NAME")
11+
cluster_name = os.environ.get("CLUSTER_NAME")
4412

4513
# List the tasks for a given cluster and service
4614
tasks_response = ecs_client.list_tasks(
4715
cluster=cluster_name,
4816
serviceName=service_name, # Use this if tasks are part of a service
49-
desiredStatus='RUNNING' # Optional: Adjust this based on the task status you're interested in
17+
desiredStatus="RUNNING", # Optional: Adjust this based on the task status you're interested in
5018
)
5119

52-
task_arns = tasks_response.get('taskArns')
20+
task_arns = tasks_response.get("taskArns")
5321
if task_arns:
5422
# Assuming you want to restart the first task in the list
5523
task_id = task_arns[0]
5624

5725
# Stop the task (it should be restarted automatically if it's part of a service)
5826
ecs_client.stop_task(cluster=cluster_name, task=task_id)
5927

60-
return_message = 'File written and ECS task restarted'
28+
return_message = "ECS task restarted"
6129
else:
62-
return_message = 'No running tasks found for the specified service in the cluster'
30+
return_message = (
31+
"No running tasks found for the specified service in the cluster"
32+
)
6333

64-
return {
65-
'statusCode': 200,
66-
'body': return_message
67-
}
34+
return {"statusCode": 200, "body": return_message}

terraform-unity/ecs.tf

Lines changed: 18 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
resource "aws_ecs_cluster" "httpd_cluster" {
2-
name = "${var.deployment_name}-httpd-cluster"
2+
name = "${var.project}-${var.venue}-httpd-cluster"
33
tags = {
44
Service = "U-CS"
55
}
@@ -9,34 +9,8 @@ data "aws_iam_policy" "mcp_operator_policy" {
99
name = "mcp-tenantOperator-AMI-APIG"
1010
}
1111

12-
resource "aws_iam_policy" "efs_access" {
13-
name = "${var.deployment_name}-EFSAccessPolicy"
14-
description = "Policy for ECS tasks to access EFS"
15-
16-
policy = jsonencode({
17-
Version = "2012-10-17",
18-
Statement = [
19-
{
20-
Effect = "Allow",
21-
Action = [
22-
"elasticfilesystem:ClientMount",
23-
"elasticfilesystem:ClientWrite",
24-
"elasticfilesystem:ClientRootAccess",
25-
"elasticfilesystem:DescribeMountTargets",
26-
],
27-
Resource = "*",
28-
},
29-
],
30-
})
31-
}
32-
33-
resource "aws_iam_role_policy_attachment" "efs_access_attachment" {
34-
role = aws_iam_role.ecs_task_role.name
35-
policy_arn = aws_iam_policy.efs_access.arn
36-
}
37-
3812
resource "aws_iam_role" "ecs_task_role" {
39-
name = "${var.deployment_name}-ecs_task_role"
13+
name = "${var.project}-${var.venue}-ecs_task_role"
4014

4115
assume_role_policy = jsonencode({
4216
Version = "2012-10-17",
@@ -54,8 +28,13 @@ resource "aws_iam_role" "ecs_task_role" {
5428

5529
}
5630

31+
resource "aws_iam_role_policy_attachment" "ecs_ssm_role_policy" {
32+
role = aws_iam_role.ecs_task_role.name
33+
policy_arn = "arn:aws:iam::aws:policy/AmazonSSMReadOnlyAccess"
34+
}
35+
5736
resource "aws_iam_role" "ecs_execution_role" {
58-
name = "${var.deployment_name}ecs_execution_role"
37+
name = "${var.project}-${var.venue}ecs_execution_role"
5938

6039
assume_role_policy = jsonencode({
6140
Version = "2012-10-17",
@@ -78,9 +57,8 @@ resource "aws_iam_role_policy_attachment" "ecs_execution_role_policy" {
7857
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
7958
}
8059

81-
8260
resource "aws_cloudwatch_log_group" "proxyloggroup" {
83-
name = "/ecs/${var.deployment_name}-managementproxy"
61+
name = "/ecs/${var.project}-${var.venue}-managementproxy"
8462
}
8563

8664
resource "aws_ecs_task_definition" "httpd" {
@@ -91,28 +69,19 @@ resource "aws_ecs_task_definition" "httpd" {
9169
execution_role_arn = aws_iam_role.ecs_execution_role.arn
9270
memory = "512"
9371
cpu = "256"
94-
volume {
95-
name = "httpd-config"
96-
97-
efs_volume_configuration {
98-
file_system_id = aws_efs_file_system.httpd_config_efs.id
99-
transit_encryption = "ENABLED"
100-
transit_encryption_port = 2049
101-
authorization_config {
102-
access_point_id = aws_efs_access_point.httpd_config_ap.id
103-
iam = "ENABLED"
104-
}
105-
}
106-
}
10772

10873

10974
container_definitions = jsonencode([{
11075
name = "httpd"
111-
image = "ghcr.io/unity-sds/unity-proxy/httpd-proxy:0.13.0"
76+
image = "ghcr.io/unity-sds/unity-proxy/httpd-proxy:${var.httpd_proxy_version}"
11277
environment = [
11378
{
114-
name = "ELB_DNS_NAME",
115-
value = var.mgmt_dns
79+
name = "UNITY_PROJECT",
80+
value = var.project
81+
},
82+
{
83+
name = "UNITY_VENUE",
84+
value = var.venue
11685
}
11786
]
11887
logConfiguration = {
@@ -129,20 +98,14 @@ resource "aws_ecs_task_definition" "httpd" {
12998
hostPort = 8080
13099
}
131100
]
132-
mountPoints = [
133-
{
134-
containerPath = "/etc/apache2/sites-enabled/"
135-
sourceVolume = "httpd-config"
136-
}
137-
]
138101
}])
139102
tags = {
140103
Service = "U-CS"
141104
}
142105
}
143106

144107
resource "aws_security_group" "ecs_sg" {
145-
name = "${var.deployment_name}-ecs_service_sg"
108+
name = "${var.project}-${var.venue}-ecs_service_sg"
146109
description = "Security group for ECS service"
147110
vpc_id = data.aws_ssm_parameter.vpc_id.value
148111

@@ -194,5 +157,6 @@ resource "aws_ecs_service" "httpd_service" {
194157
}
195158
depends_on = [
196159
aws_lb_listener.httpd_listener,
160+
aws_ssm_parameter.managementproxy_config
197161
]
198162
}

0 commit comments

Comments
 (0)