-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #53 from GSA-TTS/egress-proxy
Create an egress proxy module
- Loading branch information
Showing
12 changed files
with
361 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,7 @@ | ||
output "space_id" { | ||
value = cloudfoundry_space.space.id | ||
} | ||
|
||
output "space_name" { | ||
value = cloudfoundry_space.space.name | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
%{ for app, dests in list ~} | ||
%{ for dest in dests ~} | ||
${ split(":", dest)[0] } | ||
%{ endfor ~} | ||
%{ endfor ~} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
locals { | ||
|
||
# Make a clean list of the client apps for iteration purposes | ||
clients = toset(keys(merge(var.allowlist, var.denylist))) | ||
|
||
# Generate Caddy-compatible allow and deny ACLs, one target per line. | ||
# | ||
# For now, there's just one consolidated allowlist and denylist, no matter | ||
# what apps they were specified for. Future improvments could improve this, | ||
# but it would mean also changing the proxy to be both more complex (in terms | ||
# of how the Caddyfile is constructed) and more discriminating (in terms of | ||
# recognizing client apps based on GUIDs supplied by Envoy in request headers, | ||
# as well as the destination ports). However, adding these improvements won't | ||
# require modifying the module's interface, since we're already collecting | ||
# that refined information. | ||
allowacl = templatefile("${path.module}/acl.tftpl", { list = var.allowlist }) | ||
denyacl = templatefile("${path.module}/acl.tftpl", { list = var.denylist }) | ||
} | ||
|
||
### | ||
### Set up the authenticated egress application in the target space on apps.internal | ||
### | ||
|
||
data "cloudfoundry_domain" "internal" { | ||
name = "apps.internal" | ||
} | ||
|
||
resource "cloudfoundry_route" "egress_route" { | ||
space = data.cloudfoundry_space.egress_space.id | ||
domain = data.cloudfoundry_domain.internal.id | ||
hostname = substr("${var.cf_org_name}-${replace(var.cf_space_name, ".", "-")}-${var.name}", -63, -1) | ||
# Yields something like: orgname-spacename-name.apps.internal, limited to the last 63 characters | ||
} | ||
|
||
resource "random_uuid" "username" {} | ||
resource "random_password" "password" { | ||
length = 16 | ||
special = false | ||
} | ||
|
||
data "cloudfoundry_space" "egress_space" { | ||
org_name = var.cf_org_name | ||
name = var.cf_space_name | ||
} | ||
|
||
# This zips up just the depoyable files from the specified gitref in the | ||
# cg-egress-proxy repository | ||
data "external" "proxyzip" { | ||
program = ["/bin/sh", "prepare-proxy.sh"] | ||
working_dir = path.module | ||
query = { | ||
gitref = var.gitref | ||
} | ||
} | ||
|
||
resource "cloudfoundry_app" "egress_app" { | ||
name = var.name | ||
space = data.cloudfoundry_space.egress_space.id | ||
path = "${path.module}/${data.external.proxyzip.result.path}" | ||
source_code_hash = filesha256("${path.module}/${data.external.proxyzip.result.path}") | ||
buildpack = "binary_buildpack" | ||
command = "./caddy run --config Caddyfile" | ||
memory = var.egress_memory | ||
instances = var.instances | ||
strategy = "rolling" | ||
|
||
routes { | ||
route = cloudfoundry_route.egress_route.id | ||
} | ||
environment = { | ||
PROXY_PORTS : join(" ", var.allowports) | ||
PROXY_ALLOW : local.allowacl | ||
PROXY_DENY : local.denyacl | ||
PROXY_USERNAME : random_uuid.username.result | ||
PROXY_PASSWORD : random_password.password.result | ||
} | ||
} | ||
|
||
### | ||
### Set up network policies so that the clients can reach the proxy | ||
### | ||
|
||
data "cloudfoundry_space" "client_space" { | ||
org_name = var.cf_org_name | ||
name = var.client_space | ||
} | ||
|
||
data "cloudfoundry_app" "clients" { | ||
for_each = local.clients | ||
name_or_id = each.key | ||
space = data.cloudfoundry_space.client_space.id | ||
} | ||
|
||
resource "cloudfoundry_network_policy" "client_routing" { | ||
for_each = local.clients | ||
policy { | ||
source_app = data.cloudfoundry_app.clients[each.key].id | ||
destination_app = cloudfoundry_app.egress_app.id | ||
port = "61443" | ||
} | ||
} | ||
|
||
### | ||
### Create a credential service for bound clients to use when make requests of the proxy | ||
### | ||
locals { | ||
https_proxy = "https://${random_uuid.username.result}:${random_password.password.result}@${cloudfoundry_route.egress_route.endpoint}:61443" | ||
domain = cloudfoundry_route.egress_route.endpoint | ||
username = random_uuid.username.result | ||
password = random_password.password.result | ||
protocol = "https" | ||
port = 61443 | ||
app_id = cloudfoundry_app.egress_app.id | ||
} | ||
|
||
resource "cloudfoundry_user_provided_service" "credentials" { | ||
name = "${var.name}-creds" | ||
space = data.cloudfoundry_space.client_space.id | ||
credentials = { | ||
"uri" = local.https_proxy | ||
"domain" = local.domain | ||
"username" = local.username | ||
"password" = local.password | ||
"protocol" = local.protocol | ||
"port" = local.port | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
output "https_proxy" { | ||
value = local.https_proxy | ||
sensitive = true | ||
} | ||
|
||
output "domain" { | ||
value = local.domain | ||
} | ||
|
||
output "username" { | ||
value = local.username | ||
} | ||
|
||
output "password" { | ||
value = local.password | ||
sensitive = true | ||
} | ||
|
||
output "protocol" { | ||
value = local.protocol | ||
} | ||
|
||
output "app_id" { | ||
value = local.app_id | ||
} | ||
|
||
output "port" { | ||
value = local.port | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
#!/bin/sh | ||
|
||
# Exit if any step fails | ||
set -e | ||
|
||
eval "$(jq -r '@sh "GITREF=\(.gitref)"')" | ||
|
||
popdir=$(pwd) | ||
|
||
# Portable construct so this will work everywhere | ||
# https://unix.stackexchange.com/a/84980 | ||
tmpdir=$(mktemp -d 2>/dev/null || mktemp -d -t 'mytmpdir') | ||
cd "$tmpdir" | ||
|
||
# Grab a copy of the zip file for the specified ref | ||
curl -s -L https://github.com/GSA-TTS/cg-egress-proxy/archive/${GITREF}.zip --output local.zip | ||
|
||
# Zip up just the proxy/ subdirectory for pushing | ||
unzip -q -u local.zip \*/proxy/\* | ||
zip -q -j -r ${popdir}/proxy.zip cg-egress-proxy-*/proxy | ||
|
||
# Tell Terraform where to find it | ||
cat << EOF | ||
{ "path": "proxy.zip" } | ||
EOF |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
terraform { | ||
required_version = "~> 1.0" | ||
required_providers { | ||
cloudfoundry = { | ||
source = "cloudfoundry-community/cloudfoundry" | ||
version = ">=0.53.1" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
mock_provider "cloudfoundry" {} | ||
|
||
variables { | ||
cf_org_name = "gsa-tts-devtools-prototyping" | ||
cf_space_name = "terraform-cloudgov-ci-tests-egress" | ||
client_space = "terraform-cloudgov-ci-tests" | ||
name = "terraform-egress-app" | ||
allowlist = { "continuous_monitoring-staging" = ["raw.githubusercontent.com:443"] } | ||
} | ||
|
||
run "test_proxy_creation" { | ||
assert { | ||
condition = output.https_proxy == "https://${output.username}:${output.password}@${output.domain}:61443" | ||
error_message = "HTTPS_PROXY output must match the correct form, got ${nonsensitive(output.https_proxy)}" | ||
} | ||
|
||
assert { | ||
condition = output.domain == cloudfoundry_route.egress_route.endpoint | ||
error_message = "Output domain must match the route endpoint" | ||
} | ||
|
||
assert { | ||
condition = output.username == random_uuid.username.result | ||
error_message = "Output username must come from the random_uuid resource" | ||
} | ||
|
||
assert { | ||
condition = output.password == random_password.password.result | ||
error_message = "Output password must come from the random_password resource" | ||
} | ||
|
||
assert { | ||
condition = output.protocol == "https" | ||
error_message = "protocol only supports https" | ||
} | ||
|
||
assert { | ||
condition = output.app_id == cloudfoundry_app.egress_app.id | ||
error_message = "Output app_id is the egress_app's ID" | ||
} | ||
|
||
assert { | ||
condition = output.port == 61443 | ||
error_message = "port only supports 61443 internal https listener" | ||
} | ||
} |
Oops, something went wrong.