Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement SMTP egress proxy #1156

Merged
merged 9 commits into from
May 26, 2023
Merged

Implement SMTP egress proxy #1156

merged 9 commits into from
May 26, 2023

Conversation

mogul
Copy link
Contributor

@mogul mogul commented May 24, 2023

Relates to #1071.

@mogul mogul temporarily deployed to management May 24, 2023 03:59 — with GitHub Actions Inactive
@github-actions
Copy link
Contributor

github-actions bot commented May 24, 2023

Terraform plan for dev

Plan: 11 to add, 0 to change, 7 to destroy.
Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create
  - destroy

Terraform will perform the following actions:

  # module.dev.module.egress-proxy.cloudfoundry_app.egress_app will be destroyed
  # (because cloudfoundry_app.egress_app is not in configuration)
  - resource "cloudfoundry_app" "egress_app" {
      - buildpack                       = "binary_buildpack" -> null
      - command                         = "./caddy run --config Caddyfile" -> null
      - disk_quota                      = 1024 -> null
      - enable_ssh                      = true -> null
      - environment                     = (sensitive value) -> null
      - health_check_invocation_timeout = 0 -> null
      - health_check_timeout            = 0 -> null
      - health_check_type               = "port" -> null
      - id                              = "6a01a949-a251-42f7-8828-c15fff27fc7f" -> null
      - id_bg                           = "6a01a949-a251-42f7-8828-c15fff27fc7f" -> null
      - instances                       = 1 -> null
      - memory                          = 64 -> null
      - name                            = "egress" -> null
      - path                            = "../shared/modules/egress-proxy/proxy.zip" -> null
      - ports                           = [
          - 8080,
        ] -> null
      - source_code_hash                = "9fcf4a7f6abfc9a220de2b8bb97591ab490a271ac0933b984f606f645319e1a4" -> null
      - space                           = "2631379e-2805-4f71-b4c7-1062fe7c99f4" -> null
      - stack                           = "cflinuxfs4" -> null
      - stopped                         = false -> null
      - strategy                        = "rolling" -> null
      - timeout                         = 60 -> null

      - routes {
          - port  = 8080 -> null
          - route = "9fdb3852-706a-4794-98f8-c0db7d9b6eab" -> null
        }
    }

  # module.dev.module.egress-proxy.cloudfoundry_network_policy.client_routing["fac-av-dev"] will be destroyed
  # (because cloudfoundry_network_policy.client_routing is not in configuration)
  - resource "cloudfoundry_network_policy" "client_routing" {
      - id = "a0041a19-ff78-5bea-d389-06ad6cfca200" -> null

      - policy {
          - destination_app = "6a01a949-a251-42f7-8828-c15fff27fc7f" -> null
          - port            = "61443" -> null
          - protocol        = "tcp" -> null
          - source_app      = "779bbc51-f78a-4186-90eb-5acb68d7d746" -> null
        }
    }

  # module.dev.module.egress-proxy.cloudfoundry_network_policy.client_routing["gsa-fac"] will be destroyed
  # (because cloudfoundry_network_policy.client_routing is not in configuration)
  - resource "cloudfoundry_network_policy" "client_routing" {
      - id = "282f67af-0a77-e406-1848-beb3ddec5460" -> null

      - policy {
          - destination_app = "6a01a949-a251-42f7-8828-c15fff27fc7f" -> null
          - port            = "61443" -> null
          - protocol        = "tcp" -> null
          - source_app      = "d7fcdc7a-fd97-4e31-89ae-b823883b5992" -> null
        }
    }

  # module.dev.module.egress-proxy.cloudfoundry_route.egress_route will be destroyed
  # (because cloudfoundry_route.egress_route is not in configuration)
  - resource "cloudfoundry_route" "egress_route" {
      - domain   = "26df58ef-0c0d-4997-b68b-8defb7b3998b" -> null
      - endpoint = "gsa-tts-oros-fac-dev-egress-egress.apps.internal" -> null
      - hostname = "gsa-tts-oros-fac-dev-egress-egress" -> null
      - id       = "9fdb3852-706a-4794-98f8-c0db7d9b6eab" -> null
      - space    = "2631379e-2805-4f71-b4c7-1062fe7c99f4" -> null
    }

  # module.dev.module.egress-proxy.cloudfoundry_user_provided_service.credentials will be destroyed
  # (because cloudfoundry_user_provided_service.credentials is not in configuration)
  - resource "cloudfoundry_user_provided_service" "credentials" {
      - credentials = (sensitive value) -> null
      - id          = "9330c239-5b48-4b25-ac76-fa846ad48095" -> null
      - name        = "egress-creds" -> null
      - space       = "06525ba3-19c2-451b-96e9-ea4a9134e8b9" -> null
      - tags        = [] -> null
    }

  # module.dev.module.egress-proxy.random_password.password will be destroyed
  # (because random_password.password is not in configuration)
  - resource "random_password" "password" {
      - bcrypt_hash = (sensitive value) -> null
      - id          = "none" -> null
      - length      = 16 -> null
      - lower       = true -> null
      - min_lower   = 0 -> null
      - min_numeric = 0 -> null
      - min_special = 0 -> null
      - min_upper   = 0 -> null
      - number      = true -> null
      - numeric     = true -> null
      - result      = (sensitive value) -> null
      - special     = false -> null
      - upper       = true -> null
    }

  # module.dev.module.egress-proxy.random_uuid.username will be destroyed
  # (because random_uuid.username is not in configuration)
  - resource "random_uuid" "username" {
      - id     = "3d5b1410-f8ce-0a3f-49f4-e530fe58595d" -> null
      - result = "3d5b1410-f8ce-0a3f-49f4-e530fe58595d" -> null
    }

  # module.dev.module.https-proxy.cloudfoundry_app.egress_app will be created
  + resource "cloudfoundry_app" "egress_app" {
      + buildpack                       = "binary_buildpack"
      + command                         = "./caddy run --config Caddyfile"
      + disk_quota                      = (known after apply)
      + enable_ssh                      = (known after apply)
      + environment                     = (sensitive value)
      + health_check_http_endpoint      = (known after apply)
      + health_check_invocation_timeout = (known after apply)
      + health_check_timeout            = (known after apply)
      + health_check_type               = "port"
      + id                              = (known after apply)
      + id_bg                           = (known after apply)
      + instances                       = 1
      + memory                          = 64
      + name                            = "https-proxy"
      + path                            = "../shared/modules/https-proxy/proxy.zip"
      + ports                           = (known after apply)
      + source_code_hash                = "9fcf4a7f6abfc9a220de2b8bb97591ab490a271ac0933b984f606f645319e1a4"
      + space                           = "2631379e-2805-4f71-b4c7-1062fe7c99f4"
      + stack                           = (known after apply)
      + stopped                         = false
      + strategy                        = "rolling"
      + timeout                         = 60

      + routes {
          + port  = (known after apply)
          + route = (known after apply)
        }
    }

  # module.dev.module.https-proxy.cloudfoundry_network_policy.client_routing["fac-av-dev"] will be created
  + resource "cloudfoundry_network_policy" "client_routing" {
      + id = (known after apply)

      + policy {
          + destination_app = (known after apply)
          + port            = "61443"
          + protocol        = "tcp"
          + source_app      = "779bbc51-f78a-4186-90eb-5acb68d7d746"
        }
    }

  # module.dev.module.https-proxy.cloudfoundry_network_policy.client_routing["gsa-fac"] will be created
  + resource "cloudfoundry_network_policy" "client_routing" {
      + id = (known after apply)

      + policy {
          + destination_app = (known after apply)
          + port            = "61443"
          + protocol        = "tcp"
          + source_app      = "d7fcdc7a-fd97-4e31-89ae-b823883b5992"
        }
    }

  # module.dev.module.https-proxy.cloudfoundry_route.egress_route will be created
  + resource "cloudfoundry_route" "egress_route" {
      + domain   = "26df58ef-0c0d-4997-b68b-8defb7b3998b"
      + endpoint = (known after apply)
      + hostname = "gsa-tts-oros-fac-dev-egress-https-proxy"
      + id       = (known after apply)
      + port     = (known after apply)
      + space    = "2631379e-2805-4f71-b4c7-1062fe7c99f4"
    }

  # module.dev.module.https-proxy.cloudfoundry_user_provided_service.credentials will be created
  + resource "cloudfoundry_user_provided_service" "credentials" {
      + credentials = (sensitive value)
      + id          = (known after apply)
      + name        = "https-proxy-creds"
      + space       = "06525ba3-19c2-451b-96e9-ea4a9134e8b9"
    }

  # module.dev.module.https-proxy.random_password.password will be created
  + resource "random_password" "password" {
      + bcrypt_hash = (sensitive value)
      + id          = (known after apply)
      + length      = 16
      + lower       = true
      + min_lower   = 0
      + min_numeric = 0
      + min_special = 0
      + min_upper   = 0
      + number      = true
      + numeric     = true
      + result      = (sensitive value)
      + special     = false
      + upper       = true
    }

  # module.dev.module.https-proxy.random_uuid.username will be created
  + resource "random_uuid" "username" {
      + id     = (known after apply)
      + result = (known after apply)
    }

  # module.dev.module.smtp-proxy.cloudfoundry_app.egress_app will be created
  + resource "cloudfoundry_app" "egress_app" {
      + buildpack                       = "nginx_buildpack"
      + disk_quota                      = (known after apply)
      + enable_ssh                      = (known after apply)
      + environment                     = (sensitive value)
      + health_check_http_endpoint      = (known after apply)
      + health_check_invocation_timeout = (known after apply)
      + health_check_timeout            = (known after apply)
      + health_check_type               = "port"
      + id                              = (known after apply)
      + id_bg                           = (known after apply)
      + instances                       = 1
      + memory                          = 64
      + name                            = "smtp-proxy"
      + path                            = "../shared/modules/stream-proxy/proxy.zip"
      + ports                           = (known after apply)
      + source_code_hash                = "7e600e415790ba2f140fe0f6471fb45b46bdb7fa37af1efb1c25bf48443c87e5"
      + space                           = "2631379e-2805-4f71-b4c7-1062fe7c99f4"
      + stack                           = (known after apply)
      + stopped                         = false
      + strategy                        = "rolling"
      + timeout                         = 60

      + routes {
          + port  = (known after apply)
          + route = (known after apply)
        }
    }

  # module.dev.module.smtp-proxy.cloudfoundry_network_policy.client_routing["gsa-fac"] will be created
  + resource "cloudfoundry_network_policy" "client_routing" {
      + id = (known after apply)

      + policy {
          + destination_app = (known after apply)
          + port            = "8080"
          + protocol        = "tcp"
          + source_app      = "d7fcdc7a-fd97-4e31-89ae-b823883b5992"
        }
    }

  # module.dev.module.smtp-proxy.cloudfoundry_route.egress_route will be created
  + resource "cloudfoundry_route" "egress_route" {
      + domain   = "26df58ef-0c0d-4997-b68b-8defb7b3998b"
      + endpoint = (known after apply)
      + hostname = "gsa-tts-oros-fac-dev-egress-smtp-proxy"
      + id       = (known after apply)
      + port     = (known after apply)
      + space    = "2631379e-2805-4f71-b4c7-1062fe7c99f4"
    }

  # module.dev.module.smtp-proxy.cloudfoundry_user_provided_service.credentials will be created
  + resource "cloudfoundry_user_provided_service" "credentials" {
      + credentials = (sensitive value)
      + id          = (known after apply)
      + name        = "smtp-proxy-creds"
      + space       = "06525ba3-19c2-451b-96e9-ea4a9134e8b9"
    }

Plan: 11 to add, 0 to change, 7 to destroy.

❌ Plan not applied in Deploy to the dev and management cloud.gov environments #111 (Plan has changed)

@github-actions
Copy link
Contributor

github-actions bot commented May 24, 2023

Terraform plan for management

No changes. Your infrastructure matches the configuration.
No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration
and found no differences, so no changes are needed.

✅ Plan applied in Deploy to the dev and management cloud.gov environments #111

@mogul mogul temporarily deployed to management May 24, 2023 04:04 — with GitHub Actions Inactive
@mogul mogul temporarily deployed to management May 24, 2023 04:10 — with GitHub Actions Inactive
@mogul mogul temporarily deployed to management May 24, 2023 04:33 — with GitHub Actions Inactive
@mogul mogul temporarily deployed to dev May 24, 2023 04:33 — with GitHub Actions Inactive
@JeanMarie-PM
Copy link
Contributor

It would be good to add some test scripts to show that we can send emails via the GSA SMTP service and that we cannot do so via some other SMTP service provider.

@jadudm
Copy link
Contributor

jadudm commented May 24, 2023

Does "it would be good to..." mean:

  1. I think someone/the ticket originator should do this as part of this ticket/PR,
  2. I think we should ticket it separately, OR
  3. I think this is a good idea, and I'll do it?

@JeanMarie-PM
Copy link
Contributor

"It would be good" means this is a recommendation to the PR owner. They can choose to implement as part of the PR, say this does not make sense and why, create a ticket for later implementation, etc.

@mogul
Copy link
Contributor Author

mogul commented May 24, 2023

It would be good to add some test scripts to show that we can send emails via the GSA SMTP service and that we cannot do so via some other SMTP service provider.

Unlike the https-proxy, the stream-proxy only ever passes bytes between the client and the specific preconfigured upstream host+port (the "stream")... There's no logic to filter requests based on a requested destination, because the client can't even specify a destination!

So it wouldn't make sense to add a test specifically for the GSA SMTP service.

@mogul
Copy link
Contributor Author

mogul commented May 24, 2023

Let me know if y'all want a walkthrough of this before approving/merging!

@mogul
Copy link
Contributor Author

mogul commented May 24, 2023

(I'd like to get it merged before Thursday's review, though.)

@asteel-gsa
Copy link
Contributor

Let me know if y'all want a walkthrough of this before approving/merging!

Happy to do a walkthrough if you have time in the next hour, and can approve.

@JeanMarie-PM
Copy link
Contributor

LGTM - Will leave the approval to Alex after the walkthrough.

@JeanMarie-PM
Copy link
Contributor

It would be good to add some test scripts to show that we can send emails via the GSA SMTP service and that we cannot do so via some other SMTP service provider.

Unlike the https-proxy, the stream-proxy only ever passes bytes between the client and the specific preconfigured upstream host+port (the "stream")... There's no logic to filter requests based on a requested destination, because the client can't even specify a destination!

So it wouldn't make sense to add a test specifically for the GSA SMTP service.

Good for now. But I think we should create a test to send an email as a follow yo ticket.. This will give us the confidence that the integration is working. We should not be having paths with no test coverage.

@mogul
Copy link
Contributor Author

mogul commented May 24, 2023

Note from walkthrough with Alex: I'm going to add a Mermaid diagram showing how the test works, since it's not obvious that the client app is acting both as a client and as the proxy's upstream. (Essentially, the client app is a mock.)

@mogul
Copy link
Contributor Author

mogul commented May 24, 2023

So it wouldn't make sense to add a test specifically for the GSA SMTP service.

Good for now. But I think we should create a test to send an email as a follow yo ticket.. This will give us the confidence that the integration is working. We should not be having paths with no test coverage.

If you want to be able to prove that the application sends mails, and that they will be delivered to the right location when they're submitted to the GSA G Suite SMTP Relay, that's pretty out of scope for what the proxy actually does... The sole function of the proxy is to enable controlling/funneling egress traffic.

What you're describing is more of an integration test. I was originally going to do that with mailhog as the mock, but once I started working on it I realized that I was testing a lot more than what the module actually does. I do think there's a call for mailhog to be integrated into the docker-compose stack with tests there for the application sending mails as expected, but that's a whole COULD issue.

@mogul
Copy link
Contributor Author

mogul commented May 24, 2023

(I invite you to contemplate what kind of controlled test setup would be necessary to send a mail through the G Suite SMTP Relay and verify that it was delivered, even without the proxy in the loop. 😜)

@mogul mogul temporarily deployed to dev May 26, 2023 05:38 — with GitHub Actions Inactive
@mogul mogul temporarily deployed to management May 26, 2023 05:38 — with GitHub Actions Inactive
@mogul mogul temporarily deployed to dev May 26, 2023 05:41 — with GitHub Actions Inactive
@mogul mogul temporarily deployed to management May 26, 2023 05:41 — with GitHub Actions Inactive
@mogul mogul temporarily deployed to dev May 26, 2023 05:42 — with GitHub Actions Inactive
@mogul mogul temporarily deployed to management May 26, 2023 05:42 — with GitHub Actions Inactive
@mogul
Copy link
Contributor Author

mogul commented May 26, 2023

I'm going to add a Mermaid diagram showing how the test works, since it's not obvious that the client app is acting both as a client and as the proxy's upstream. (Essentially, the client app is a mock.)

I added that diagram. I also reordered the commits to make them easier to follow in sequence.

@mogul mogul temporarily deployed to management May 26, 2023 05:48 — with GitHub Actions Inactive
@mogul mogul temporarily deployed to dev May 26, 2023 05:48 — with GitHub Actions Inactive
@mogul mogul temporarily deployed to dev May 26, 2023 05:48 — with GitHub Actions Inactive
@mogul mogul temporarily deployed to management May 26, 2023 05:48 — with GitHub Actions Inactive
@mogul mogul temporarily deployed to dev May 26, 2023 05:53 — with GitHub Actions Inactive
@mogul mogul temporarily deployed to management May 26, 2023 05:53 — with GitHub Actions Inactive
@JeanMarie-PM JeanMarie-PM merged commit 68d5501 into main May 26, 2023
@JeanMarie-PM JeanMarie-PM deleted the add-smtp-proxy branch May 26, 2023 16:45
@mogul mogul temporarily deployed to dev May 26, 2023 18:22 — with GitHub Actions Inactive
@mogul mogul temporarily deployed to management May 26, 2023 18:22 — with GitHub Actions Inactive
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants