diff --git a/controllers/root_test.go b/controllers/root_test.go index 5d40a4ad..cf433bf9 100644 --- a/controllers/root_test.go +++ b/controllers/root_test.go @@ -4,7 +4,6 @@ import ( "strings" "testing" - "github.com/cloudfoundry-community/go-cfenv" "github.com/govau/cf-common/env" "github.com/18F/cg-dashboard/controllers" @@ -13,10 +12,8 @@ import ( func TestPing(t *testing.T) { response, request := NewTestRequest("GET", "/ping", nil) - app, _ := cfenv.Current() router, _, err := controllers.InitApp( env.NewVarSet(env.WithMapLookup(GetMockCompleteEnvVars())), - app, ) if err != nil { t.Fatal(err) diff --git a/controllers/routes.go b/controllers/routes.go index a4fcd6f6..c1a89bd3 100644 --- a/controllers/routes.go +++ b/controllers/routes.go @@ -1,7 +1,6 @@ package controllers import ( - "github.com/cloudfoundry-community/go-cfenv" "github.com/gocraft/web" "github.com/govau/cf-common/env" @@ -71,10 +70,10 @@ func InitRouter(settings *helpers.Settings, templates *helpers.Templates, mailer } // InitApp takes in envars and sets up the router and settings that will be used for the unstarted server. -func InitApp(envVars *env.VarSet, app *cfenv.App) (*web.Router, *helpers.Settings, error) { +func InitApp(envVars *env.VarSet) (*web.Router, *helpers.Settings, error) { // Initialize the settings. settings := helpers.Settings{} - if err := settings.InitSettings(envVars, app); err != nil { + if err := settings.InitSettings(envVars); err != nil { return nil, nil, err } mailer, err := mailer.InitSMTPMailer(settings) diff --git a/controllers/routes_test.go b/controllers/routes_test.go index 1b235c58..e219690a 100644 --- a/controllers/routes_test.go +++ b/controllers/routes_test.go @@ -3,7 +3,6 @@ package controllers_test import ( "testing" - "github.com/cloudfoundry-community/go-cfenv" "github.com/govau/cf-common/env" "github.com/18F/cg-dashboard/controllers" @@ -39,10 +38,8 @@ var initAppTests = []initAppTest{ func TestInitApp(t *testing.T) { for _, test := range initAppTests { - app, _ := cfenv.Current() router, settings, err := controllers.InitApp( env.NewVarSet(env.WithMapLookup(test.envVars)), - app, ) if (router == nil) != test.returnRouterNil { t.Errorf("Test %s did not return correct router value. Expected %t, Actual %t", test.testName, test.returnRouterNil, (router == nil)) diff --git a/helpers/env_vars.go b/helpers/env_vars.go index 0189215c..5ff717f3 100644 --- a/helpers/env_vars.go +++ b/helpers/env_vars.go @@ -45,6 +45,8 @@ var ( SMTPPassEnvVar = "SMTP_PASS" // SMTPFromEnvVar is SMTP from address for UAA invites SMTPFromEnvVar = "SMTP_FROM" + // SMTPCertEnvVar is cert for TLS connection + SMTPCertEnvVar = "SMTP_CERT" // TICSecretEnvVar is the shared secret with CF API proxy for forwarding client IPs TICSecretEnvVar = "TIC_SECRET" // CSRFKeyEnvVar is used for CSRF token. Must be 32 bytes, hex-encoded, e.g. openssl rand -hex 32 diff --git a/helpers/settings.go b/helpers/settings.go index d173de9b..14af7679 100644 --- a/helpers/settings.go +++ b/helpers/settings.go @@ -8,7 +8,6 @@ import ( "fmt" "net/http" - "github.com/cloudfoundry-community/go-cfenv" "github.com/gorilla/sessions" "github.com/govau/cf-common/env" "golang.org/x/net/context" @@ -61,6 +60,8 @@ type Settings struct { SMTPPass string // SMTP from address for UAA invites SMTPFrom string + // SMTPCert is x509 TLS cert + SMTPCert string // Shared secret with CF API proxy TICSecret string // CSRFKey used for gorilla CSRF validation @@ -84,7 +85,7 @@ func (s *Settings) CreateContext() context.Context { // InitSettings attempts to populate all the fields of the Settings struct. It will return an error if it fails, // otherwise it returns nil for success. -func (s *Settings) InitSettings(envVars *env.VarSet, app *cfenv.App) (retErr error) { +func (s *Settings) InitSettings(envVars *env.VarSet) (retErr error) { defer func() { // While .MustString() is convenient in readability below, we'd prefer // to convert this to an error for upstream callers. @@ -174,6 +175,7 @@ func (s *Settings) InitSettings(envVars *env.VarSet, app *cfenv.App) (retErr err s.SMTPPass = envVars.String(SMTPPassEnvVar, "") s.SMTPPort = envVars.String(SMTPPortEnvVar, "") s.SMTPUser = envVars.String(SMTPUserEnvVar, "") + s.SMTPCert = envVars.String(SMTPCertEnvVar, "") s.TICSecret = envVars.String(TICSecretEnvVar, "") return nil } diff --git a/helpers/settings_test.go b/helpers/settings_test.go index 1354dda5..b141f6ab 100644 --- a/helpers/settings_test.go +++ b/helpers/settings_test.go @@ -3,7 +3,6 @@ package helpers_test import ( "testing" - "github.com/cloudfoundry-community/go-cfenv" "github.com/govau/cf-common/env" "github.com/18F/cg-dashboard/helpers" @@ -31,8 +30,30 @@ var initSettingsTests = []initSettingsTest{ helpers.CSRFKeyEnvVar: "00112233445566778899aabbccddeeff", helpers.SMTPFromEnvVar: "blah@blah.com", helpers.SMTPHostEnvVar: "localhost", - helpers.SecureCookiesEnvVar: "1", - helpers.TICSecretEnvVar: "tic", + helpers.SMTPCertEnvVar: `-----BEGIN CERTIFICATE----- + MIIDrjCCApYCCQDdihKIIO0hnTANBgkqhkiG9w0BAQUFADCBmDELMAkGA1UEBhMC + VVMxCzAJBgNVBAgTAkRDMRMwEQYDVQQHEwpXYXNoaW5ndG9uMQwwCgYDVQQKEwNH + U0ExEDAOBgNVBAsTB1RUUy0xOEYxGjAYBgNVBAMTEXNtdHAuZnIuY2xvdWQuZ292 + MSswKQYJKoZIhvcNAQkBFhxjbG91ZC1nb3Ytb3BlcmF0aW9uc0Bnc2EuZ292MB4X + DTE4MDQxMTE5MTQ0NFoXDTE5MDQxMTE5MTQ0NFowgZgxCzAJBgNVBAYTAlVTMQsw + CQYDVQQIEwJEQzETMBEGA1UEBxMKV2FzaGluZ3RvbjEMMAoGA1UEChMDR1NBMRAw + DgYDVQQLEwdUVFMtMThGMRowGAYDVQQDExFzbXRwLmZyLmNsb3VkLmdvdjErMCkG + CSqGSIb3DQEJARYcY2xvdWQtZ292LW9wZXJhdGlvbnNAZ3NhLmdvdjCCASIwDQYJ + KoZIhvcNAQEBBQADggEPADCCAQoCggEBAKTzjVa6lJyMUmyZeT31emnH3smX3/zC + 67r61EnArvjB9f3e/L2u6f7650OY/ywsfBz8hXrI5XpdhCbyqrfI+XpkDKFoABsu + y8anGbiDEwh8PLS4UckRZXDxG/8HBptgpkzNBoClr7XNGQa+Mk4j69949sx4uK/1 + KiMXzKnaFxuuH7t7aNgzLr8eGzwqCtJqGuzEhsl6gPVDvvUlgtSItLiu5FoQLyau + Fkcmkoca0eyCEFW9R/zN39B0cpipMPsyDzyUJ+gP7jU7c2Z+gzNNbiN3qaNIU6pf + olBDW70Rz6/ww8qItkazKw8OaSJV6fM6mgwlc+4IN72qug9XS4kopNECAwEAATAN + BgkqhkiG9w0BAQUFAAOCAQEAKqPNWkXt4CJFlfsX+iBNynJTGiArsJR6FCwaNv6f + +a31WWSCrxkvIepreUNqyGuhs0d7cT1MZMtkM9051nNFbxLqIIi7b3LZWNzwQf9G + jRDrM8aUwh8idRUaJRWQQ+KgkOsY0iyttfLI0OLZzj39NldtDFKJrDpA4QuAKI9/ + KpMer71/GuBE6mgdKOMAgyD7D0eoPxeks0sxzs6MqC+AcMeBrpbQWCKLHaOMhgZk + bGfWcTPRDkXJmVtA44UuNANU+Wc9aHD5ODMFU+LG3TOEK0GCcgxCrksXfwTYrNN2 + 81D90R6hWY2GDdOx+T2KPAWuAZT6IgABaI9rU4/OGvisRA== + -----END CERTIFICATE-----`, + helpers.SecureCookiesEnvVar: "1", + helpers.TICSecretEnvVar: "tic", }, wantNilError: true, }, @@ -51,9 +72,31 @@ var initSettingsTests = []initSettingsTest{ helpers.CSRFKeyEnvVar: "00112233445566778899aabbccddeeff", helpers.SMTPFromEnvVar: "blah@blah.com", helpers.SMTPHostEnvVar: "localhost", - helpers.SecureCookiesEnvVar: "0", - helpers.LocalCFEnvVar: "1", - helpers.TICSecretEnvVar: "tic", + helpers.SMTPCertEnvVar: `-----BEGIN CERTIFICATE----- + MIIDrjCCApYCCQDdihKIIO0hnTANBgkqhkiG9w0BAQUFADCBmDELMAkGA1UEBhMC + VVMxCzAJBgNVBAgTAkRDMRMwEQYDVQQHEwpXYXNoaW5ndG9uMQwwCgYDVQQKEwNH + U0ExEDAOBgNVBAsTB1RUUy0xOEYxGjAYBgNVBAMTEXNtdHAuZnIuY2xvdWQuZ292 + MSswKQYJKoZIhvcNAQkBFhxjbG91ZC1nb3Ytb3BlcmF0aW9uc0Bnc2EuZ292MB4X + DTE4MDQxMTE5MTQ0NFoXDTE5MDQxMTE5MTQ0NFowgZgxCzAJBgNVBAYTAlVTMQsw + CQYDVQQIEwJEQzETMBEGA1UEBxMKV2FzaGluZ3RvbjEMMAoGA1UEChMDR1NBMRAw + DgYDVQQLEwdUVFMtMThGMRowGAYDVQQDExFzbXRwLmZyLmNsb3VkLmdvdjErMCkG + CSqGSIb3DQEJARYcY2xvdWQtZ292LW9wZXJhdGlvbnNAZ3NhLmdvdjCCASIwDQYJ + KoZIhvcNAQEBBQADggEPADCCAQoCggEBAKTzjVa6lJyMUmyZeT31emnH3smX3/zC + 67r61EnArvjB9f3e/L2u6f7650OY/ywsfBz8hXrI5XpdhCbyqrfI+XpkDKFoABsu + y8anGbiDEwh8PLS4UckRZXDxG/8HBptgpkzNBoClr7XNGQa+Mk4j69949sx4uK/1 + KiMXzKnaFxuuH7t7aNgzLr8eGzwqCtJqGuzEhsl6gPVDvvUlgtSItLiu5FoQLyau + Fkcmkoca0eyCEFW9R/zN39B0cpipMPsyDzyUJ+gP7jU7c2Z+gzNNbiN3qaNIU6pf + olBDW70Rz6/ww8qItkazKw8OaSJV6fM6mgwlc+4IN72qug9XS4kopNECAwEAATAN + BgkqhkiG9w0BAQUFAAOCAQEAKqPNWkXt4CJFlfsX+iBNynJTGiArsJR6FCwaNv6f + +a31WWSCrxkvIepreUNqyGuhs0d7cT1MZMtkM9051nNFbxLqIIi7b3LZWNzwQf9G + jRDrM8aUwh8idRUaJRWQQ+KgkOsY0iyttfLI0OLZzj39NldtDFKJrDpA4QuAKI9/ + KpMer71/GuBE6mgdKOMAgyD7D0eoPxeks0sxzs6MqC+AcMeBrpbQWCKLHaOMhgZk + bGfWcTPRDkXJmVtA44UuNANU+Wc9aHD5ODMFU+LG3TOEK0GCcgxCrksXfwTYrNN2 + 81D90R6hWY2GDdOx+T2KPAWuAZT6IgABaI9rU4/OGvisRA== + -----END CERTIFICATE-----`, + helpers.SecureCookiesEnvVar: "0", + helpers.LocalCFEnvVar: "1", + helpers.TICSecretEnvVar: "tic", }, wantNilError: true, }, @@ -216,16 +259,51 @@ var initSettingsTests = []initSettingsTest{ }, wantNilError: false, }, + { + testName: "Invalid SMTP Certificate", + envVars: map[string]string{ + helpers.ClientIDEnvVar: "ID", + helpers.ClientSecretEnvVar: "Secret", + helpers.HostnameEnvVar: "hostname", + helpers.LoginURLEnvVar: "loginurl", + helpers.UAAURLEnvVar: "uaaurl", + helpers.APIURLEnvVar: "apiurl", + helpers.SessionAuthenticationEnvVar: "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff", + helpers.CSRFKeyEnvVar: "00112233445566778899aabbccddeeff", + helpers.SMTPFromEnvVar: "blah@blah.com", + helpers.SMTPCertEnvVar: "12345", + }, + wantNilError: false, + }, + { + testName: "Basic Valid Local CF Settings missing optional SMTP cert", + envVars: map[string]string{ + helpers.ClientIDEnvVar: "ID", + helpers.ClientSecretEnvVar: "Secret", + helpers.HostnameEnvVar: "hostname", + helpers.LoginURLEnvVar: "loginurl", + helpers.UAAURLEnvVar: "uaaurl", + helpers.APIURLEnvVar: "apiurl", + helpers.LogURLEnvVar: "logurl", + helpers.SessionEncryptionEnvVar: "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff", + helpers.SessionAuthenticationEnvVar: "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff", + helpers.CSRFKeyEnvVar: "00112233445566778899aabbccddeeff", + helpers.SMTPFromEnvVar: "blah@blah.com", + helpers.SMTPHostEnvVar: "localhost", + helpers.SecureCookiesEnvVar: "0", + helpers.LocalCFEnvVar: "1", + helpers.TICSecretEnvVar: "tic", + }, + wantNilError: true, + }, } func TestInitSettings(t *testing.T) { - app, _ := cfenv.Current() for _, tt := range initSettingsTests { t.Run(tt.testName, func(t *testing.T) { s := helpers.Settings{} err := s.InitSettings( env.NewVarSet(env.WithMapLookup(tt.envVars)), - app, ) if (err == nil) != tt.wantNilError { t.Errorf("return value: got %t, want %t", (err == nil), tt.wantNilError) diff --git a/helpers/testhelpers/testhelpers.go b/helpers/testhelpers/testhelpers.go index d0b036c8..3a57daf3 100644 --- a/helpers/testhelpers/testhelpers.go +++ b/helpers/testhelpers/testhelpers.go @@ -14,7 +14,6 @@ import ( "testing" "time" - "github.com/cloudfoundry-community/go-cfenv" "github.com/gocraft/web" "github.com/gorilla/sessions" "github.com/govau/cf-common/env" @@ -114,8 +113,7 @@ func EchoResponseHandler(rw http.ResponseWriter, response *http.Response) { func CreateRouterWithMockSession(sessionData map[string]interface{}, envVars map[string]string) (*web.Router, *MockSessionStore) { // Initialize settings. settings := helpers.Settings{} - app, _ := cfenv.Current() - settings.InitSettings(env.NewVarSet(env.WithMapLookup(envVars)), app) + settings.InitSettings(env.NewVarSet(env.WithMapLookup(envVars))) // Initialize a new session store. store := MockSessionStore{} @@ -362,10 +360,8 @@ func PrepareExternalServerCall(t *testing.T, c *controllers.SecureContext, testS // Assign settings to context mockSettings := &helpers.Settings{} - app, _ := cfenv.Current() mockSettings.InitSettings( env.NewVarSet(env.WithMapLookup(test.EnvVars)), - app, ) c.Settings = mockSettings diff --git a/mailer/mailer.go b/mailer/mailer.go index e7445aeb..66872346 100644 --- a/mailer/mailer.go +++ b/mailer/mailer.go @@ -1,6 +1,8 @@ package mailer import ( + "crypto/tls" + "crypto/x509" "net/smtp" "github.com/18F/cg-dashboard/helpers" @@ -14,21 +16,35 @@ type Mailer interface { // InitSMTPMailer creates a new SMTP Mailer func InitSMTPMailer(settings helpers.Settings) (Mailer, error) { + var tlsConfig *tls.Config + if settings.SMTPCert != "" { + pool := x509.NewCertPool() + pool.AppendCertsFromPEM([]byte(settings.SMTPCert)) + tlsConfig = &tls.Config{ + ServerName: settings.SMTPHost, + RootCAs: pool, + } + + } return &smtpMailer{ - smtpHost: settings.SMTPHost, - smtpPort: settings.SMTPPort, - smtpUser: settings.SMTPUser, - smtpPass: settings.SMTPPass, - smtpFrom: settings.SMTPFrom, + smtpHost: settings.SMTPHost, + smtpPort: settings.SMTPPort, + smtpUser: settings.SMTPUser, + smtpPass: settings.SMTPPass, + smtpFrom: settings.SMTPFrom, + smtpCert: settings.SMTPCert, + tlsConfig: tlsConfig, }, nil } type smtpMailer struct { - smtpHost string - smtpPort string - smtpUser string - smtpPass string - smtpFrom string + smtpHost string + smtpPort string + smtpUser string + smtpPass string + smtpFrom string + smtpCert string + tlsConfig *tls.Config } func (s *smtpMailer) SendEmail(emailAddress, subject string, body []byte) error { @@ -37,5 +53,12 @@ func (s *smtpMailer) SendEmail(emailAddress, subject string, body []byte) error e.To = []string{" <" + emailAddress + ">"} e.HTML = body e.Subject = subject + + addr := s.smtpHost + ":" + s.smtpPass + auth := smtp.PlainAuth("", s.smtpUser, s.smtpPass, s.smtpHost) + + if s.tlsConfig != nil { + return e.SendWithTLS(addr, auth, s.tlsConfig) + } return e.Send(s.smtpHost+":"+s.smtpPort, smtp.PlainAuth("", s.smtpUser, s.smtpPass, s.smtpHost)) } diff --git a/server.go b/server.go index 79bdff63..75672623 100644 --- a/server.go +++ b/server.go @@ -64,7 +64,7 @@ func startApp(port string, app *cfenv.App) { envVars = makeDefaultEnvVarSet(app) } - router, settings, err := controllers.InitApp(envVars, app) + router, settings, err := controllers.InitApp(envVars) if err != nil { fmt.Println(err.Error()) // Terminate the program with a non-zero value number.