diff --git a/.circleci/config.yml b/.circleci/config.yml index ccd2797ad..7b0acc43e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -18,43 +18,35 @@ jobs: - run: cp config/test.circleci.exs config/test.local.exs - restore_cache: keys: - - v4-yarn-cache-{{ .Branch }}-{{ checksum "assets/yarn.lock" }} - - v4-yarn-cache-{{ .Branch }} - - v4-yarn-cache + - v5-yarn-cache-{{ .Branch }}-{{ checksum "assets/yarn.lock" }} + - v5-yarn-cache-{{ .Branch }} + - v5-yarn-cache - restore_cache: keys: - - v4-mix-cache-{{ .Branch }}-{{ checksum "mix.lock" }} - - v4-mix-cache-{{ .Branch }} - - v4-mix-cache + - v5-mix-cache-{{ .Branch }}-{{ checksum "mix.lock" }} + - v5-mix-cache-{{ .Branch }} - restore_cache: keys: - - v4-build-cache-{{ .Branch }} - - v4-build-cache + - v5-build-cache-{{ .Branch }} - run: mix do deps.get, compile - run: cd assets && yarn && cd .. - save_cache: - key: v4-yarn-cache-{{ .Branch }}-{{ checksum "assets/yarn.lock" }} + key: v5-yarn-cache-{{ .Branch }}-{{ checksum "assets/yarn.lock" }} paths: "assets/node_modules" - save_cache: - key: v4-yarn-cache-{{ .Branch }} + key: v5-yarn-cache-{{ .Branch }} paths: "assets/node_modules" - save_cache: - key: v4-yarn-cache-{{ .Branch }} + key: v5-yarn-cache-{{ .Branch }} paths: "assets/node_modules" - save_cache: - key: v4-mix-cache-{{ .Branch }}-{{ checksum "mix.lock" }} + key: v5-mix-cache-{{ .Branch }}-{{ checksum "mix.lock" }} paths: "deps" - save_cache: - key: v4-mix-cache-{{ .Branch }} + key: v5-mix-cache-{{ .Branch }} paths: "deps" - save_cache: - key: v4-mix-cache - paths: "deps" - - save_cache: - key: v4-build-cache-{{ .Branch }} - paths: "_build" - - save_cache: - key: v4-build-cache + key: v5-build-cache-{{ .Branch }} paths: "_build" lint: @@ -69,11 +61,11 @@ jobs: - run: mix local.rebar --force - run: cp config/test.circleci.exs config/test.local.exs - restore_cache: - key: v4-yarn-cache-{{ .Branch }}-{{ checksum "assets/yarn.lock" }} + key: v5-yarn-cache-{{ .Branch }}-{{ checksum "assets/yarn.lock" }} - restore_cache: - key: v4-mix-cache-{{ .Branch }}-{{ checksum "mix.lock" }} + key: v5-mix-cache-{{ .Branch }}-{{ checksum "mix.lock" }} - restore_cache: - key: v4-build-cache-{{ .Branch }} + key: v5-build-cache-{{ .Branch }} - run: mix deps.get - run: mix compile --force - run: mix deps.audit --ignore-package-names sweet_xml @@ -106,11 +98,11 @@ jobs: - run: mix clean - run: cp config/test.circleci.exs config/test.local.exs - restore_cache: - key: v4-yarn-cache-{{ .Branch }}-{{ checksum "assets/yarn.lock" }} + key: v5-yarn-cache-{{ .Branch }}-{{ checksum "assets/yarn.lock" }} - restore_cache: - key: v4-mix-cache-{{ .Branch }}-{{ checksum "mix.lock" }} + key: v5-mix-cache-{{ .Branch }}-{{ checksum "mix.lock" }} - restore_cache: - key: v4-build-cache-{{ .Branch }} + key: v5-build-cache-{{ .Branch }} - run: name: Wait for DB command: dockerize -wait tcp://localhost:5432 -timeout 1m @@ -135,11 +127,11 @@ jobs: - run: mix local.rebar --force - run: cp config/test.circleci.exs config/test.local.exs - restore_cache: - key: v4-yarn-cache-{{ .Branch }}-{{ checksum "assets/yarn.lock" }} + key: v5-yarn-cache-{{ .Branch }}-{{ checksum "assets/yarn.lock" }} - restore_cache: - key: v4-mix-cache-{{ .Branch }}-{{ checksum "mix.lock" }} + key: v5-mix-cache-{{ .Branch }}-{{ checksum "mix.lock" }} - restore_cache: - key: v4-build-cache-{{ .Branch }} + key: v5-build-cache-{{ .Branch }} - run: mix deps.get - run: cd assets && yarn run build && cd .. - run: mix phx.digest diff --git a/config/prod.exs b/config/prod.exs index 9d7a33789..49051354e 100644 --- a/config/prod.exs +++ b/config/prod.exs @@ -58,9 +58,10 @@ config :challenge_gov, ChallengeGov.GovDelivery, config :challenge_gov, ChallengeGov.Mailer, from: System.get_env("MAILER_FROM_ADDRESS"), adapter: Bamboo.SMTPAdapter, - server: "smtp-relay.gmail.com", + server: System.get_env("SMTP_SERVER"), hostname: System.get_env("HOST"), - port: 25, + port: String.to_integer(System.get_env("SMTP_PORT") || "25"), + tls_verify: :verify_none, tls: :never, ssl: false, retries: 1 diff --git a/docs/cloud_gov.md b/docs/cloud_gov.md index 48e591ae7..b2ae11b1a 100644 --- a/docs/cloud_gov.md +++ b/docs/cloud_gov.md @@ -44,6 +44,10 @@ Learn more generally about [cloud.gov](https://cloud.gov/), and specifically und The app will run database migrations and seeds when booting. Background tasks are processed with Quantum and do not require any additional running processes +## Proxy + +This app expects the configuration of an egress proxy. The configuration is managed locally with a sidecar as well as through the deployment of an egress proxy application configured in the platform codebase https://github.com/GSA/Challenge_platform + ## SSH Disable SSH for any sensitive environment when not in use. This can be done through the cloud.gov web interface. diff --git a/docs/configuration_variables.md b/docs/configuration_variables.md index 43f3ccf02..6c714fc65 100644 --- a/docs/configuration_variables.md +++ b/docs/configuration_variables.md @@ -11,7 +11,7 @@ | AWS_ACCESS_KEY_ID | Access key for S3, provided as a Cloud.gov Service | | | AWS_SECRET_ACCESS_KEY | Secret Access key for S3, provided as a Cloud.gov Service | | | BUCKET_NAME | Bucket Name for S3, provided as a Cloud.gov Service | | -| CHALLENGE_OWNER_ASSUMED_TLDS | By default, only .gov email addresses are assumed to want the Challenge Manager role, this allows other government TLD values to also be considered as wanting the Challenge Manager role | .mil | +| CHALLENGE_OWNER_ASSUMED_TLDS | By default, only .gov email addresses are assumed to want the Challenge Manager role, this allows other government TLD values to also be considered as wanting the Challenge Manager role | .mil | | DATABASE_URL | The standard formatted postgres URL composed from the components of username, password, hostname, and database name, as provided by the Cloud.gov RDS service | postgres://username:password@hostname:port/database | | GOV_DELIVERY_ACCOUNT_CODE | The GovDelivery account code | | | GOV_DELIVERY_API_PASSWORD | The password of the API account with admin permission for the API | | @@ -22,6 +22,7 @@ | GOV_DELIVERY_TOPIC_SUBSCRIBE_URL | The URL that the topic code gets added to for each challenge to be subscribed to | | | GOV_DELIVERY_URL | The base API url for the GovDelivery API | | | HOST | The external DNS name of the running portal | portal.challenge.gov | +| LOCAL_PROXY_HOST | The local sidecar proxy that will relay to the egress proxy | 127.0.0.1 | | LOGIN_CLIENT_ID | The login client ID for the application in Login.gov | urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:challenge_gov | | LOGIN_PRIVATE_KEY_PASSWORD | The password to decrypt the private key used in the Login.gov OpenID connect flow | password | | LOGIN_PUBLIC_KEY_PATH | The path within the code repository to the provide key for the application to use with Login.gov | environment_key.pem | @@ -30,7 +31,10 @@ | LOG_RETENTION_IN_DAYS | Number of days of security audit logs to keep in the database | 90 | | MAILER_FROM_ADDRESS | The from/reply-to address for transactional email sent by the system | support@challenge.gov | | MIX_ENV | The environment mode for the running application, should always be prod | prod | +| PROXYROUTE | The fully qualified URL for the upstream egress proxy with protocol, authentication, domain, and port. Used by the caddy sidecar | | | RECAPTCHA_SECRET_KEY | Google reCaptcha 3 secret key | | | RECAPTCHA_SITE_KEY | Google reCaptcha 3 public site key | | | SECRET_KEY_BASE | Random seed used to encrypt secrets (cookies) for session management between the application and the browser | | | SESSION_TIMEOUT_IN_MINUTES | Amount of idle time before the system logs a user out | 15 | +| SMTP_SERVER | The DNS or IP address of the SMTP server to connect to. May be a TCP relay proxy | | +| SMTP_PORT | The port of the SMTP server to connect to. May be a TCP relay proxy | | diff --git a/lib/challenge_gov/application.ex b/lib/challenge_gov/application.ex index 3b7bc1660..df8264366 100644 --- a/lib/challenge_gov/application.ex +++ b/lib/challenge_gov/application.ex @@ -11,7 +11,7 @@ defmodule ChallengeGov.Application do # Start the PubSub system {Phoenix.PubSub, name: ChallengeGov.PubSub}, ChallengeGov.Repo, - {Finch, name: ChallengeGov.HTTPClient}, + finch_config(System.get_env("LOCAL_PROXY_HOST")), Web.Endpoint, ChallengeGov.Scheduler, ChallengeGov.Telemetry, @@ -34,4 +34,21 @@ defmodule ChallengeGov.Application do defp oban_config do Application.get_env(:challenge_gov, Oban) end + + defp finch_config(nil), do: {Finch, name: ChallengeGov.HTTPClient} + defp finch_config(""), do: {Finch, name: ChallengeGov.HTTPClient} + + defp finch_config(proxy_host) do + {Finch, + name: ChallengeGov.HTTPClient, + pools: %{ + :default => [ + size: 5, + conn_opts: [ + protocols: [:http1], + proxy: {:http, proxy_host, 8080, []} + ] + ] + }} + end end diff --git a/manifest.yml b/manifest.yml index 60ef3864b..6f0366fcb 100644 --- a/manifest.yml +++ b/manifest.yml @@ -29,6 +29,11 @@ applications: <<: *defaults instances: 1 disk_quota: 6GB + sidecars: + - name: local-proxy + process_types: [ 'web' ] + command: ./proxy/sidecar_start.sh + memory: 64M env: MIX_ENV: prod STACK: heroku-22 diff --git a/proxy/.profile b/proxy/.profile new file mode 100644 index 000000000..82ff51ebc --- /dev/null +++ b/proxy/.profile @@ -0,0 +1,48 @@ +#!/bin/sh + +# Despite the temptation to use #!/bin/bash, we want to keep this file as as +# POSIX sh-compatible as possible. This is to facilitate testing the .profile +# under Alpine, which doesn't have /bin/bash, but does have ash (which is itself +# a flavor of busybox). +ENABLE_ASH_BASH_COMPAT=1 + +set -e + +# Ensure there's only one entry per line, and leave no whitespace +PROXY_DENY=$( echo -n "$PROXY_DENY" | sed 's/^\S/ &/' | sed 's/\ /\n/g' | sed '/^\s*$/d' ) +PROXY_ALLOW=$( echo -n "$PROXY_ALLOW" | sed 's/^\S/ &/' | sed 's/\ /\n/g' | sed '/^\s*$/d' ) + +# Append to the appropriate files +echo -n "$PROXY_DENY" > deny.acl +echo -n "$PROXY_ALLOW" > allow.acl + +# Newline Terminate Non-Empty File If Not Already aka ntnefina +# https://stackoverflow.com/a/10082466/17138235 +# +# It's unclear if this works properly under Alpine because it uses ANSI-C +# quoting; that needs more testiing. However, if caddy complains about a blank +# in the file, you know why! +ntnefina() { + if [ -s "$1" ] && [ "$(tail -c1 "$1"; echo x)" != $'\nx' ]; then + echo "" >> "$1" + fi +} + +ntnefina deny.acl +ntnefina allow.acl + +# Make it easy to run curl tests on ourselves +https_proxy="https://$PROXY_USERNAME:$PROXY_PASSWORD@$(echo "$VCAP_APPLICATION" | jq .application_uris[0] | sed 's/"//g'):61443" +export https_proxy + +# Make open ports configurable via the PROXY_PORTS environment variable. +# For example "80 443 22 61443". Default to 443 only. +if [ -z "${PROXY_PORTS}" ]; then + PROXY_PORTS="443" +fi +export PROXY_PORTS + +echo +echo +echo "The proxy connection URL is:" +echo " $https_proxy" diff --git a/proxy/Caddyfile.local.tmpl b/proxy/Caddyfile.local.tmpl new file mode 100644 index 000000000..5b294bce2 --- /dev/null +++ b/proxy/Caddyfile.local.tmpl @@ -0,0 +1,25 @@ +{ + debug + log { + format console + level INFO + } + auto_https off +} + +:8888 { + route { + forward_proxy { + acl { + allow all + } + ports 80 443 61443 + upstream $PROXYROUTE + } + } + log { + format json + level INFO + output stdout + } +} diff --git a/proxy/caddy b/proxy/caddy new file mode 100755 index 000000000..8d7461e42 Binary files /dev/null and b/proxy/caddy differ diff --git a/proxy/envsubst b/proxy/envsubst new file mode 100755 index 000000000..4a97c89eb Binary files /dev/null and b/proxy/envsubst differ diff --git a/proxy/sidecar_start.sh b/proxy/sidecar_start.sh new file mode 100755 index 000000000..300a4d2e6 --- /dev/null +++ b/proxy/sidecar_start.sh @@ -0,0 +1,5 @@ +echo "Updating Caddy config" +./proxy/envsubst < ./proxy/Caddyfile.local.tmpl > ./proxy/Caddyfile.local + +echo "Starting Caddy" +exec ./proxy/caddy run --config ./proxy/Caddyfile.local