Skip to content

Commit

Permalink
Native SSL Support (#233)
Browse files Browse the repository at this point in the history
Add two new flags ssl-key-file and ssl-cert-file to enable Atlantis to serve over SSL natively instead of going through an NGINX proxy.

This is a squashed commit containing the primary work by @natemueller
  • Loading branch information
lkysow authored Jan 23, 2018
1 parent dd766b8 commit c54dd58
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 77 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,9 @@ However, if you were to lose the data, all you would need to do is run `atlantis

**Q: How to add SSL to Atlantis server?**

A: Atlantis currently only supports HTTP. In order to add SSL you will need to front Atlantis server with NGINX or HAProxy. Follow the document [here](./docs/nginx-ssl-proxy.md) to use configure NGINX with SSL as a reverse proxy.
A: First, you'll need to get a public/private key pair to serve over SSL.
These need to be in a directory accessible by Atlantis. Then start `atlantis server` with the `--ssl-cert-file` and `--ssl-key-file` flags.
See `atlantis server --help` for more information.


## Contributing
Expand Down
28 changes: 19 additions & 9 deletions cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ const (
LogLevelFlag = "log-level"
PortFlag = "port"
RequireApprovalFlag = "require-approval"
SSLCertFileFlag = "ssl-cert-file"
SSLKeyFileFlag = "ssl-key-file"
)

var stringFlags = []stringFlag{
Expand Down Expand Up @@ -94,6 +96,14 @@ var stringFlags = []stringFlag{
description: "Log level. Either debug, info, warn, or error.",
value: "info",
},
{
name: SSLCertFileFlag,
description: "File containing x509 Certificate used for serving HTTPS. If the cert is signed by a CA, the file should be the concatenation of the server's certificate, any intermediates, and the CA's certificate.",
},
{
name: SSLKeyFileFlag,
description: fmt.Sprintf("File containing x509 private key matching --%s.", SSLCertFileFlag),
},
}
var boolFlags = []boolFlag{
{
Expand Down Expand Up @@ -248,21 +258,21 @@ func (s *ServerCmd) validate(config server.Config) error {
if logLevel != "debug" && logLevel != "info" && logLevel != "warn" && logLevel != "error" {
return errors.New("invalid log level: not one of debug, info, warn, error")
}
vcsErr := fmt.Errorf("--%s/--%s or --%s/--%s must be set", GHUserFlag, GHTokenFlag, GitlabUserFlag, GitlabTokenFlag)

if (config.SSLKeyFile == "") != (config.SSLCertFile == "") {
return fmt.Errorf("--%s and --%s are both required for ssl", SSLKeyFileFlag, SSLCertFileFlag)
}

// The following combinations are valid.
// 1. github user and token
// 2. gitlab user and token
// 1. github user and token set
// 2. gitlab user and token set
// 3. all 4 set
// We validate using contradiction (I think).
if config.GithubUser != "" && config.GithubToken == "" || config.GithubToken != "" && config.GithubUser == "" {
return vcsErr
}
if config.GitlabUser != "" && config.GitlabToken == "" || config.GitlabToken != "" && config.GitlabUser == "" {
vcsErr := fmt.Errorf("--%s/--%s or --%s/--%s must be set", GHUserFlag, GHTokenFlag, GitlabUserFlag, GitlabTokenFlag)
if ((config.GithubUser == "") != (config.GithubToken == "")) || ((config.GitlabUser == "") != (config.GitlabToken == "")) {
return vcsErr
}
// At this point, we know that there can't be a single user/token without
// its pair, but we haven't checked if any user/token is set at all.
// its partner, but we haven't checked if any user/token is set at all.
if config.GithubUser == "" && config.GitlabUser == "" {
return vcsErr
}
Expand Down
52 changes: 52 additions & 0 deletions cmd/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,58 @@ func TestExecute_ValidateLogLevel(t *testing.T) {
Equals(t, "invalid log level: not one of debug, info, warn, error", err.Error())
}

func TestExecute_ValidateSSLConfig(t *testing.T) {
expErr := "--ssl-key-file and --ssl-cert-file are both required for ssl"
cases := []struct {
description string
flags map[string]interface{}
expectError bool
}{
{
"neither option set",
make(map[string]interface{}),
false,
},
{
"just ssl-key-file set",
map[string]interface{}{
cmd.SSLKeyFileFlag: "file",
},
true,
},
{
"just ssl-cert-file set",
map[string]interface{}{
cmd.SSLCertFileFlag: "flag",
},
true,
},
{
"both flags set",
map[string]interface{}{
cmd.SSLCertFileFlag: "cert",
cmd.SSLKeyFileFlag: "key",
},
false,
},
}
for _, testCase := range cases {
t.Log("Should validate ssl config when " + testCase.description)
// Add in required flags.
testCase.flags[cmd.GHUserFlag] = "user"
testCase.flags[cmd.GHTokenFlag] = "token"

c := setup(testCase.flags)
err := c.Execute()
if testCase.expectError {
Assert(t, err != nil, "should be an error")
Equals(t, expErr, err.Error())
} else {
Ok(t, err)
}
}
}

func TestExecute_ValidateVCSConfig(t *testing.T) {
expErr := "--gh-user/--gh-token or --gitlab-user/--gitlab-token must be set"
cases := []struct {
Expand Down
66 changes: 0 additions & 66 deletions docs/nginx-ssl-proxy.md

This file was deleted.

16 changes: 15 additions & 1 deletion server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ type Server struct {
EventsController *EventsController
IndexTemplate TemplateWriter
LockDetailTemplate TemplateWriter
SSLCertFile string
SSLKeyFile string
}

// Config configures Server.
Expand All @@ -67,6 +69,8 @@ type Config struct {
// allowing terraform apply's to be run.
RequireApproval bool `mapstructure:"require-approval"`
SlackToken string `mapstructure:"slack-token"`
SSLCertFile string `mapstructure:"ssl-cert-file"`
SSLKeyFile string `mapstructure:"ssl-key-file"`
Webhooks []WebhookConfig `mapstructure:"webhooks"`
}

Expand Down Expand Up @@ -229,6 +233,8 @@ func NewServer(config Config) (*Server, error) {
EventsController: eventsController,
IndexTemplate: indexTemplate,
LockDetailTemplate: lockTemplate,
SSLKeyFile: config.SSLKeyFile,
SSLCertFile: config.SSLCertFile,
}, nil
}

Expand Down Expand Up @@ -264,7 +270,15 @@ func (s *Server) Start() error {
server := &http.Server{Addr: fmt.Sprintf(":%d", s.Port), Handler: n}
go func() {
s.Logger.Warn("Atlantis started - listening on port %v", s.Port)
if err := server.ListenAndServe(); err != nil {

var err error
if s.SSLCertFile != "" && s.SSLKeyFile != "" {
err = server.ListenAndServeTLS(s.SSLCertFile, s.SSLKeyFile)
} else {
err = server.ListenAndServe()
}

if err != nil {
// When shutdown safely, there will be no error.
s.Logger.Err(err.Error())
}
Expand Down

0 comments on commit c54dd58

Please sign in to comment.