Skip to content

Commit

Permalink
feat: add BasicAuth Support to Atlantis ServeHTTP (#1777)
Browse files Browse the repository at this point in the history
* Add BasicAuth Support to Atlantis ServeHTTP

* Added Security notes

Co-authored-by: xmurias <[email protected]>
  • Loading branch information
fblgit and xmurias authored Oct 21, 2021
1 parent d01796b commit cbf35ca
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 8 deletions.
24 changes: 24 additions & 0 deletions cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ const (
TFEHostnameFlag = "tfe-hostname"
TFETokenFlag = "tfe-token"
WriteGitCredsFlag = "write-git-creds"
WebBasicAuthFlag = "web-basic-auth"
WebUsernameFlag = "web-username"
WebPasswordFlag = "web-password"

// NOTE: Must manually set these as defaults in the setDefaults function.
DefaultADBasicUser = ""
Expand All @@ -120,6 +123,9 @@ const (
DefaultTFDownloadURL = "https://releases.hashicorp.com"
DefaultTFEHostname = "app.terraform.io"
DefaultVCSStatusName = "atlantis"
DefaultWebBasicAuth = false
DefaultWebUsername = "atlantis"
DefaultWebPassword = "atlantis"
)

var stringFlags = map[string]stringFlag{
Expand Down Expand Up @@ -287,6 +293,14 @@ var stringFlags = map[string]stringFlag{
description: "Name used to identify Atlantis for pull request statuses.",
defaultValue: DefaultVCSStatusName,
},
WebUsernameFlag: {
description: "Username used for Web Basic Authentication on Atlantis HTTP Middleware",
defaultValue: DefaultWebUsername,
},
WebPasswordFlag: {
description: "Password used for Web Basic Authentication on Atlantis HTTP Middleware",
defaultValue: DefaultWebPassword,
},
}

var boolFlags = map[string]boolFlag{
Expand Down Expand Up @@ -385,6 +399,10 @@ var boolFlags = map[string]boolFlag{
description: "Skips cloning the PR repo if there are no projects were changed in the PR.",
defaultValue: false,
},
WebBasicAuthFlag: {
description: "Switches on or off the Basic Authentication on the HTTP Middleware interface",
defaultValue: DefaultWebBasicAuth,
},
}
var intFlags = map[string]intFlag{
ParallelPoolSize: {
Expand Down Expand Up @@ -634,6 +652,12 @@ func (s *ServerCmd) setDefaults(c *server.UserConfig) {
if c.TFEHostname == "" {
c.TFEHostname = DefaultTFEHostname
}
if c.WebUsername == "" {
c.WebUsername = DefaultWebUsername
}
if c.WebPassword == "" {
c.WebPassword = DefaultWebPassword
}
}

func (s *ServerCmd) validate(userConfig server.UserConfig) error {
Expand Down
11 changes: 10 additions & 1 deletion runatlantis.io/docs/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ To prevent this, allowlist [Bitbucket's IP addresses](https://confluence.atlassi

## Mitigations
### Don't Use On Public Repos
Because anyone can comment on public pull requests, even with all the security mitigations available, it's still dangerous to run Atlantis on public repos until Atlantis gets an authentication system.
Because anyone can comment on public pull requests, even with all the security mitigations available, it's still dangerous to run Atlantis on public repos without proper configuration of the security settings.

### Don't Use `--allow-fork-prs`
If you're running on a public repo (which isn't recommended, see above) you shouldn't set `--allow-fork-prs` (defaults to false)
Expand Down Expand Up @@ -79,3 +79,12 @@ Azure DevOps supports sending a basic authentication header in all webhook event
If you're using webhook secrets but your traffic is over HTTP then the webhook secrets
could be stolen. Enable SSL/HTTPS using the `--ssl-cert-file` and `--ssl-key-file`
flags.

### Enable Authentication on Atlantis Web Server
It is very reccomended to enable authentication in the web service. Enable BasicAuth using the `--web-basic-auth=true` and setup a username and a password using `--web-username=yourUsername` and `--web-password=yourPassword` flags.

You can also pass these as environment variables `ATLANTIS_WEB_BASIC_AUTH=true` `ATLANTIS_WEB_USERNAME=yourUsername` and `ATLANTIS_WEB_PASSWORD=yourPassword`.

::tip Tip
We do encourage the usage of complex passwords in order to prevent basic bruteforcing attacks.
:::
41 changes: 36 additions & 5 deletions server/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,49 @@ import (
)

// NewRequestLogger creates a RequestLogger.
func NewRequestLogger(logger logging.SimpleLogging) *RequestLogger {
return &RequestLogger{logger}
func NewRequestLogger(s *Server) *RequestLogger {
return &RequestLogger{
s.Logger,
s.WebAuthentication,
s.WebUsername,
s.WebPassword,
}
}

// RequestLogger logs requests and their response codes.
// RequestLogger logs requests and their response codes
// as well as handle the basicauth on the requests
type RequestLogger struct {
logger logging.SimpleLogging
logger logging.SimpleLogging
WebAuthentication bool
WebUsername string
WebPassword string
}

// ServeHTTP implements the middleware function. It logs all requests at DEBUG level.
func (l *RequestLogger) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
l.logger.Debug("%s %s – from %s", r.Method, r.URL.RequestURI(), r.RemoteAddr)
next(rw, r)
allowed := false
if r.URL.Path == "/events" || !l.WebAuthentication {
allowed = true
} else {
user, pass, ok := r.BasicAuth()
if ok {
r.SetBasicAuth(user, pass)
l.logger.Debug("user: %s / pass: %s >> url: %s", user, pass, r.URL.RequestURI())
if user == l.WebUsername && pass == l.WebPassword {
l.logger.Debug("[VALID] user: %s / pass: %s >> url: %s", user, pass, r.URL.RequestURI())
allowed = true
} else {
allowed = false
l.logger.Info("[INVALID] user: %s / pass: %s >> url: %s", user, pass, r.URL.RequestURI())
}
}
}
if !allowed {
rw.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`)
http.Error(rw, "Unauthorized", http.StatusUnauthorized)
} else {
next(rw, r)
}
l.logger.Debug("%s %s – respond HTTP %d", r.Method, r.URL.RequestURI(), rw.(negroni.ResponseWriter).Status())
}
9 changes: 7 additions & 2 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ type Server struct {
SSLCertFile string
SSLKeyFile string
Drainer *events.Drainer
WebAuthentication bool
WebUsername string
WebPassword string
}

// Config holds config for server that isn't passed in by the user.
Expand Down Expand Up @@ -655,7 +658,6 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
GithubOrg: userConfig.GithubOrg,
GithubStatusName: userConfig.VCSStatusName,
}

return &Server{
AtlantisVersion: config.AtlantisVersion,
AtlantisURL: parsedURL,
Expand All @@ -675,6 +677,9 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
SSLKeyFile: userConfig.SSLKeyFile,
SSLCertFile: userConfig.SSLCertFile,
Drainer: drainer,
WebAuthentication: userConfig.WebBasicAuth,
WebUsername: userConfig.WebUsername,
WebPassword: userConfig.WebPassword,
}, nil
}

Expand All @@ -699,7 +704,7 @@ func (s *Server) Start() error {
PrintStack: false,
StackAll: false,
StackSize: 1024 * 8,
}, NewRequestLogger(s.Logger))
}, NewRequestLogger(s))
n.UseHandler(s.Router)

defer s.Logger.Flush()
Expand Down
3 changes: 3 additions & 0 deletions server/user_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ type UserConfig struct {
VCSStatusName string `mapstructure:"vcs-status-name"`
DefaultTFVersion string `mapstructure:"default-tf-version"`
Webhooks []WebhookConfig `mapstructure:"webhooks"`
WebBasicAuth bool `mapstructure:"web-basic-auth"`
WebUsername string `mapstructure:"web-username"`
WebPassword string `mapstructure:"web-password"`
WriteGitCreds bool `mapstructure:"write-git-creds"`
}

Expand Down

0 comments on commit cbf35ca

Please sign in to comment.