diff --git a/.github/actions/deploy-proxy/action.yml b/.github/actions/deploy-proxy/action.yml index 978b5d3..6154ffa 100644 --- a/.github/actions/deploy-proxy/action.yml +++ b/.github/actions/deploy-proxy/action.yml @@ -26,30 +26,9 @@ runs: run: | cf api api.fr.cloud.gov cf auth - - name: Set restricted space egress - shell: bash - run: ./bin/ops/set_space_egress.sh -t -s ${{ inputs.cf_space }} - - name: Set public space egress - shell: bash - run: ./bin/ops/set_space_egress.sh -p -s ${{ inputs.cf_space }}-egress - - name: Create temp directory - shell: bash - id: create-temp-dir - run: echo "path=$(mktemp -d -t egress-XXXXXXXXXX --tmpdir=$RUNNER_TEMP)" >> $GITHUB_OUTPUT - - name: Clone cg-egress-proxy - shell: bash - run: git clone ${{ inputs.proxy_repo }} ${{ steps.create-temp-dir.outputs.path }} - - name: Switch to deploy ref - shell: bash - working-directory: ${{ steps.create-temp-dir.outputs.path }} - run: git checkout ${{ inputs.proxy_version }} - - name: Copy config files - shell: bash - run: cp ./config/deployment/egress_proxy/${{ inputs.app }}.*.acl ${{ steps.create-temp-dir.outputs.path }} - name: Target space shell: bash run: cf target -o gsa-tts-devtools-prototyping -s ${{ inputs.cf_space }} - name: Deploy proxy shell: bash - working-directory: ${{ steps.create-temp-dir.outputs.path }} - run: ./bin/cf-deployproxy -a ${{ inputs.app }} -p egress-proxy -e egress_proxy + run: ./bin/ops/deploy_egress_proxy.rb -a ${{ inputs.app }} -s ${{ inputs.cf_space }} -r ${{ inputs.proxy_repo }} -v ${{ inputs.proxy_version }} diff --git a/.github/workflows/deploy-production.yml b/.github/workflows/deploy-production.yml index 1dff626..c1e4b96 100644 --- a/.github/workflows/deploy-production.yml +++ b/.github/workflows/deploy-production.yml @@ -40,3 +40,12 @@ jobs: cf_org: gsa-tts-devtools-prototyping cf_space: rahearn cf_command: push --vars-file config/deployment/production.yml --var rails_master_key="${{ secrets.RAILS_MASTER_KEY }}" --strategy rolling + + - name: Deploy egress proxy + uses: ./.github/actions/deploy-proxy + env: + CF_USERNAME: ${{ secrets.CF_USERNAME }} + CF_PASSWORD: ${{ secrets.CF_PASSWORD }} + with: + cf_space: rahearn + app: continuous_monitoring-production diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml index 428b48b..c7086a0 100644 --- a/.github/workflows/deploy-staging.yml +++ b/.github/workflows/deploy-staging.yml @@ -41,7 +41,7 @@ jobs: cf_space: rahearn cf_command: push --vars-file config/deployment/staging.yml --var rails_master_key="${{ secrets.RAILS_MASTER_KEY }}" --strategy rolling - - name: Deploy proxy + - name: Deploy egress proxy uses: ./.github/actions/deploy-proxy env: CF_USERNAME: ${{ secrets.CF_USERNAME }} diff --git a/README.md b/README.md index f4a7083..8c6a9d7 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,18 @@ Otherwise, they are set as a `((variable))` within `manifest.yml` and the variab Configuration that changes from staging to production, but is public, should be added to `config/deployment/staging.yml` and `config/deployment/production.yml` +### Public Egress Proxy + +Traffic to be delivered to the public internet or s3 must be proxied through the [cg-egress-proxy](https://github.com/GSA-TTS/cg-egress-proxy) app. + +To deploy the proxy manually: + +1. Ensure terraform state is up to date. +1. Update the acl files in `config/deployment/egress_proxy` +1. Deploy the proxy to staging: `bin/ops/deploy_egress_proxy.rb -s rahearn -a continuous_monitoring-staging` +1. Deploy the proxy to production: `bin/ops/deploy_egress_proxy.rb -s rahearn -a continuous_monitoring-production` + +See the [ruby troubleshooting doc](https://github.com/GSA-TTS/cg-egress-proxy/blob/main/docs/ruby.md) first if you have any problems making outbound connections through the proxy. ## Documentation ### Auditree Control Validation diff --git a/bin/ops/deploy_egress_proxy.rb b/bin/ops/deploy_egress_proxy.rb new file mode 100755 index 0000000..e55d2b6 --- /dev/null +++ b/bin/ops/deploy_egress_proxy.rb @@ -0,0 +1,54 @@ +#!/usr/bin/env ruby + +require "tmpdir" +require "optparse" + +options = {} +parser = OptionParser.new do |opt| + opt.on("-s", "--space SPACE", "The space apps are running in") { |o| options[:space] = o unless o == "" } + opt.on("-a", "--apps APPLICATION", "Comma-separated list of cloud.gov apps to be proxied") { |o| options[:apps] = o unless o == "" } + opt.on("-r", "--repo PROXY_REPOSITORY", "Address of egress proxy git repo. Default: https://github.com/GSA-TTS/cg-egress-proxy.git") { |o| options[:repo] = o unless o == "" } + opt.on("-v", "--version PROXY_VERSION", "Git ref (sha, tag, branch) to deploy from repo. Default: main") { |o| options[:version] = o unless o == "" } +end +parser.parse! + +if options[:space].nil? + warn "--space is a required argument" + puts parser + exit 1 +end +if options[:apps].nil? + warn "--apps is a required argument" + puts parser + exit 1 +end +proxy_repo = options[:repo].nil? ? "https://github.com/GSA-TTS/cg-egress-proxy.git" : options[:repo] +proxy_version = options[:version].nil? ? "main" : options[:version] + +def run(command) + system(command) or exit $?.exitstatus +end + +directory = File.dirname(__FILE__) + +run "#{File.join(directory, "set_space_egress.sh")} -s #{options[:space]} -t" +run "#{File.join(directory, "set_space_egress.sh")} -s #{options[:space]}-egress -p" + +Dir.mktmpdir do |dir| + run "git clone #{proxy_repo} #{dir}" + run "cd #{dir}; git checkout #{proxy_version}" + config_dir = File.join(directory, "../../config/deployment/egress_proxy") + options[:apps].split(",").each do |app| + begin + FileUtils.cp File.join(config_dir, "#{app}.allow.acl"), dir + rescue + warn "config/deployment/egress_proxy/#{app}.allow.acl did not exist. Please create it if you need to customize the app's allow rules" + end + begin + FileUtils.cp File.join(config_dir, "#{app}.deny.acl"), dir + rescue + warn "config/deployment/egress_proxy/#{app}.deny.acl did not exist. Please create it if you need to customize the app's deny rules" + end + end + run "cd #{dir}; bin/cf-deployproxy -a #{options[:apps]} -p ep -e egress_proxy" +end diff --git a/config/deployment/egress_proxy/continuous_monitoring-production.allow.acl b/config/deployment/egress_proxy/continuous_monitoring-production.allow.acl new file mode 100644 index 0000000..e69de29 diff --git a/config/deployment/egress_proxy/continuous_monitoring-production.deny.acl b/config/deployment/egress_proxy/continuous_monitoring-production.deny.acl new file mode 100644 index 0000000..e69de29 diff --git a/config/deployment/egress_proxy/continuous_monitoring-staging.allow.acl b/config/deployment/egress_proxy/continuous_monitoring-staging.allow.acl index 0b07794..0f21fdc 100644 --- a/config/deployment/egress_proxy/continuous_monitoring-staging.allow.acl +++ b/config/deployment/egress_proxy/continuous_monitoring-staging.allow.acl @@ -1,2 +1 @@ raw.githubusercontent.com -*.apps.internal diff --git a/doc/compliance/apps/application.boundary.md b/doc/compliance/apps/application.boundary.md index 7e1994a..7ce8816 100644 --- a/doc/compliance/apps/application.boundary.md +++ b/doc/compliance/apps/application.boundary.md @@ -29,6 +29,11 @@ Boundary(aws, "AWS GovCloud") { Boundary(atob, "ATO boundary") { Boundary(space, "Restricted-egress cloud.gov space") { System_Boundary(inventory, "Application") { + Boundary(restricted_space, "Restricted egress space") { + } + Boundary(egress_space, "Public egress space") { + Container(proxy, "<&layers> Egress Proxy", "Caddy, cg-egress-proxy", "Proxy with allow-list of external connections") + } Container(app, "<&layers> Continuous Monitoring", "Ruby 3.3.4, Rails 7.1.3.4", "TKTK Application Description") ContainerDb(app_db, "Application DB", "AWS RDS (PostgreSQL)", "Primary data storage") Container(worker, "<&layers> Sidekiq workers", "Ruby 3.3.4, Sidekiq", "Perform background work and data processing") @@ -64,6 +69,7 @@ Rel(egress_proxy, external, "Request file content", "https (443)") Rel(developer, githuball, "Publish code", "git ssh (22)") Rel(githuball, cg_api, "Deploy App", "Auth: SpaceDeployer Service Account, https (443)") +Rel(app, proxy, "Proxy outbound connections", "https (443)") @enduml ``` diff --git a/doc/compliance/oscal/component-definitions/cg-egress-proxy/component-definition.json b/doc/compliance/oscal/component-definitions/cg-egress-proxy/component-definition.json new file mode 100644 index 0000000..b0f4f73 --- /dev/null +++ b/doc/compliance/oscal/component-definitions/cg-egress-proxy/component-definition.json @@ -0,0 +1,68 @@ +{ + "component-definition": { + "uuid": "d2f3e1b7-363a-4c8a-afb9-7cee1e825bdc", + "metadata": { + "title": "cg-egress-proxy Egress Proxy Component Definition.", + "last-modified": "2024-10-03T13:28:05.931086+00:00", + "version": "0.0.1", + "oscal-version": "1.1.2" + }, + "components": [ + { + "uuid": "1acb8ab7-4191-46c6-b79f-659a2f195b5a", + "type": "software", + "title": "cg-egress-proxy", + "description": "The cg-egress-proxy caddy server with forward_proxy configured", + "props": [ + { + "name": "Rule_Id", + "value": "prod-space-restricted", + "remarks": "rule_prod_space_restricted" + }, + { + "name": "Rule_Description", + "value": "The production space where the system app is running must not have the public-networks-egress ASG applied to it", + "remarks": "rule_prod_space_restricted" + } + ], + "control-implementations": [ + { + "uuid": "eba1125b-5fd7-46c3-8edc-bf22d67d98cf", + "source": "https://raw.githubusercontent.com/usnistgov/oscal-content/refs/tags/v1.3.0/nist.gov/SP800-53/rev5/json/NIST_SP-800-53_rev5_catalog.json", + "description": "Controls implemented via use of the cg-egress-proxy outbound connection proxy", + "implemented-requirements": [ + { + "uuid": "09de7f16-6339-4daa-b09a-333c5e33185c", + "control-id": "sc-7", + "description": "", + "props": [ + { + "name": "implementation-status", + "value": "partial" + } + ], + "statements": [ + { + "statement-id": "sc-7_smt.c", + "uuid": "b56aa629-2452-4052-a5c0-7d245a8122a2", + "description": "eg-egress-proxy provides a control point for allowing network traffic to specific hostnames or IP addresses. Outbound connections are compared to the following list in order:\n\n1. A `deny_file` list of hostnames and/or IP addresses to deny connections to.\n1. An `allow_file` list of hostnames and/or IP addresses to allow connections to.\n1. A `deny all` rule to deny all connections that did not match one of the first two rules.\n\nThe connection is allowed or denied based on the first matching rule.", + "props": [ + { + "name": "Rule_Id", + "value": "prod-space-restricted" + }, + { + "name": "implementation-status", + "value": "implemented" + } + ] + } + ] + } + ] + } + ] + } + ] + } +} diff --git a/doc/compliance/oscal/trestle-config.yaml b/doc/compliance/oscal/trestle-config.yaml index 497a65e..7a53103 100644 --- a/doc/compliance/oscal/trestle-config.yaml +++ b/doc/compliance/oscal/trestle-config.yaml @@ -6,3 +6,4 @@ components: - cloud_gov - devtools_cloud_gov - github_actions + - cg-egress-proxy diff --git a/terraform/README.md b/terraform/README.md index 2e95825..50196f8 100644 --- a/terraform/README.md +++ b/terraform/README.md @@ -64,7 +64,9 @@ These steps are run once per project. A [SpaceDeployer](https://cloud.gov/docs/services/cloud-gov-service-account/) account is required to run terraform or deploy the application from the CI/CD pipeline. Create a new account by running: -`../bin/ops/create_service_account.sh -s -u ` +`../bin/ops/create_service_account.sh -s -u -m` + +Passing the `-m` flag to `create_service_account.sh` is required for the account that will run terraform. ## Set up a new environment manually @@ -80,7 +82,7 @@ The below steps rely on you first configuring access to the Terraform state in s # something that communicates the purpose of the deployer # for example: circleci-deployer for the credentials CircleCI uses to # deploy the application or -terraform for credentials to run terraform manually - ../../bin/ops/create_service_account.sh -s -u > secrets.auto.tfvars + ../../bin/ops/create_service_account.sh -s -u -m > secrets.auto.tfvars ``` The script will output the `username` (as `cf_user`) and `password` (as `cf_password`) for your ``. Read more in the [cloud.gov service account documentation](https://cloud.gov/docs/services/cloud-gov-service-account/). diff --git a/terraform/production/main.tf b/terraform/production/main.tf index beba652..abdfc62 100644 --- a/terraform/production/main.tf +++ b/terraform/production/main.tf @@ -42,3 +42,14 @@ module "redis" { # domain_name = "TKTK-production-domain-name" # host_name = "TKTK-production-hostname (optional)" # } + +module "egress_space" { + source = "github.com/gsa-tts/terraform-cloudgov//cg_space?ref=v1.0.0" + + cf_org_name = local.cf_org_name + cf_space_name = "${local.cf_space_name}-egress" + # deployers should include any user or service account ID that will deploy the egress proxy + deployers = [ + var.cf_user + ] +} diff --git a/terraform/staging/main.tf b/terraform/staging/main.tf index b9902e1..b4b0f5b 100644 --- a/terraform/staging/main.tf +++ b/terraform/staging/main.tf @@ -23,4 +23,14 @@ module "redis" { redis_plan_name = "redis-dev" } +module "egress_space" { + source = "github.com/gsa-tts/terraform-cloudgov//cg_space?ref=v1.0.0" + cf_org_name = local.cf_org_name + cf_space_name = "${local.cf_space_name}-egress" + # deployers should include any user or service account ID that will deploy the egress proxy + deployers = [ + "ryan.ahearn@gsa.gov", + var.cf_user + ] +}