mirror of
https://git.vectorsigma.ru/public/atlantis.git
synced 2026-08-05 05:58:44 +00:00
Native SSL Support (#233)
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
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -31,6 +31,8 @@ const (
|
||||
LogLevelFlag = "log-level"
|
||||
PortFlag = "port"
|
||||
RequireApprovalFlag = "require-approval"
|
||||
SSLCertFileFlag = "ssl-cert-file"
|
||||
SSLKeyFileFlag = "ssl-key-file"
|
||||
)
|
||||
|
||||
var stringFlags = []stringFlag{
|
||||
@@ -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{
|
||||
{
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
# NGINX SSL Proxy
|
||||
This document shows how to configure Nginx with SSL as a reverse proxy for Atlantis server.
|
||||
|
||||
* Install NGINX
|
||||
|
||||
```bash
|
||||
sudo apt-get update
|
||||
sudo apt-get install nginx
|
||||
```
|
||||
|
||||
* Install a SSL Certificate
|
||||
This certificate can be purchased or generated. Here is a example of generating a self signed SSL certificate.
|
||||
|
||||
```bash
|
||||
cd /etc/nginx
|
||||
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/nginx/cert.key -out /etc/nginx/cert.crt
|
||||
```
|
||||
You will be prompted to enter some information about the certificate. Fill those as you like.
|
||||
|
||||
* Edit NGINX Config
|
||||
|
||||
```bash
|
||||
sudo vim /etc/nginx/sites-enabled/default
|
||||
server {
|
||||
listen 80;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
|
||||
listen 443;
|
||||
server_name atlantis.domain.com;
|
||||
|
||||
ssl_certificate /etc/nginx/cert.crt;
|
||||
ssl_certificate_key /etc/nginx/cert.key;
|
||||
|
||||
ssl on;
|
||||
ssl_session_cache builtin:1000 shared:SSL:10m;
|
||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
||||
ssl_ciphers HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4;
|
||||
ssl_prefer_server_ciphers on;
|
||||
|
||||
access_log /var/log/nginx/atlantis.access.log;
|
||||
|
||||
location / {
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# Fixes the “It appears that your reverse proxy set up is broken" error.
|
||||
proxy_pass http://localhost:4141;
|
||||
proxy_read_timeout 90;
|
||||
|
||||
proxy_redirect http://localhost:4141 https://atlantis.domain.com;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* Restart NGINX
|
||||
|
||||
```bash
|
||||
sudo service nginx restart
|
||||
|
||||
```
|
||||
@@ -45,6 +45,8 @@ type Server struct {
|
||||
EventsController *EventsController
|
||||
IndexTemplate TemplateWriter
|
||||
LockDetailTemplate TemplateWriter
|
||||
SSLCertFile string
|
||||
SSLKeyFile string
|
||||
}
|
||||
|
||||
// Config configures Server.
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -229,6 +233,8 @@ func NewServer(config Config) (*Server, error) {
|
||||
EventsController: eventsController,
|
||||
IndexTemplate: indexTemplate,
|
||||
LockDetailTemplate: lockTemplate,
|
||||
SSLKeyFile: config.SSLKeyFile,
|
||||
SSLCertFile: config.SSLCertFile,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user