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

Native SSL Support #233

Merged
merged 2 commits into from
Jan 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -214,6 +218,8 @@ func NewServer(config Config) (*Server, error) {
EventsController: eventsController,
IndexTemplate: indexTemplate,
LockDetailTemplate: lockTemplate,
SSLKeyFile: config.SSLKeyFile,
SSLCertFile: config.SSLCertFile,
}, nil
}

Expand Down Expand Up @@ -249,7 +255,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