Merge pull request #3393 from ycao56/basic-auth

Add http Basic Auth
This commit is contained in:
Bryan Boreham
2018-11-07 14:32:03 +00:00
committed by GitHub
7 changed files with 274 additions and 1 deletions

View File

@@ -31,6 +31,7 @@ func init() {
// ProbeConfig contains all the info needed for a probe to do HTTP requests
type ProbeConfig struct {
BasicAuth bool
Token string
ProbeVersion string
ProbeID string
@@ -38,7 +39,11 @@ type ProbeConfig struct {
}
func (pc ProbeConfig) authorizeHeaders(headers http.Header) {
headers.Set("Authorization", fmt.Sprintf("Scope-Probe token=%s", pc.Token))
if pc.BasicAuth {
headers.Set("Authorization", fmt.Sprintf("Basic %s", pc.Token))
} else {
headers.Set("Authorization", fmt.Sprintf("Scope-Probe token=%s", pc.Token))
}
headers.Set(xfer.ScopeProbeIDHeader, pc.ProbeID)
headers.Set(xfer.ScopeProbeVersionHeader, pc.ProbeVersion)
}

View File

@@ -12,6 +12,7 @@ import (
"strings"
"time"
"github.com/goji/httpauth"
"github.com/gorilla/mux"
"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
@@ -300,6 +301,13 @@ func appMain(flags appFlags) {
}.Wrap(handler)
}
if flags.basicAuth {
log.Infof("Basic authentication enabled")
handler = httpauth.SimpleBasicAuth(flags.username, flags.password)(handler)
} else {
log.Infof("Basic authentication disabled")
}
server := &graceful.Server{
// we want to manage the stop condition ourselves below
NoSignalHandling: true,

View File

@@ -94,6 +94,9 @@ type flags struct {
type probeFlags struct {
printOnStdout bool
basicAuth bool
username string
password string
token string
httpListen string
publishInterval time.Duration
@@ -149,6 +152,10 @@ type appFlags struct {
logHTTP bool
logHTTPHeaders bool
basicAuth bool
username string
password string
weaveEnabled bool
weaveAddr string
weaveHostname string
@@ -282,6 +289,9 @@ func setupFlags(flags *flags) {
// Probe flags
flag.BoolVar(&flags.probe.printOnStdout, "probe.publish.stdout", false, "Print reports on stdout instead of sending to app, for debugging")
flag.BoolVar(&flags.probe.basicAuth, "probe.basicAuth", false, "Use basic authentication to authenticate with app")
flag.StringVar(&flags.probe.username, "probe.basicAuth.username", "admin", "Username for basic authentication")
flag.StringVar(&flags.probe.password, "probe.basicAuth.password", "admin", "Password for basic authentication")
flag.StringVar(&flags.probe.token, serviceTokenFlag, "", "Token to authenticate with cloud.weave.works")
flag.StringVar(&flags.probe.token, probeTokenFlag, "", "Token to authenticate with cloud.weave.works")
flag.StringVar(&flags.probe.httpListen, "probe.http.listen", "", "listen address for HTTP profiling and instrumentation server")
@@ -353,6 +363,10 @@ func setupFlags(flags *flags) {
flag.BoolVar(&flags.app.logHTTP, "app.log.http", false, "Log individual HTTP requests")
flag.BoolVar(&flags.app.logHTTPHeaders, "app.log.httpHeaders", false, "Log HTTP headers. Needs app.log.http to be enabled.")
flag.BoolVar(&flags.app.basicAuth, "app.basicAuth", false, "Enable basic authentication for app")
flag.StringVar(&flags.app.username, "app.basicAuth.username", "admin", "Username for basic authentication")
flag.StringVar(&flags.app.password, "app.basicAuth.password", "admin", "Password for basic authentication")
flag.StringVar(&flags.app.weaveAddr, "app.weave.addr", app.DefaultWeaveURL, "Address on which to contact WeaveDNS")
flag.StringVar(&flags.app.weaveHostname, "app.weave.hostname", "", "Hostname to advertise in WeaveDNS")
flag.StringVar(&flags.app.containerName, "app.container.name", app.DefaultContainerName, "Name of this container (to lookup container ID)")
@@ -449,6 +463,25 @@ func main() {
flags.probe.kubernetesNodeName = os.Getenv("KUBERNETES_NODENAME")
}
if strings.ToLower(os.Getenv("ENABLE_BASIC_AUTH")) == "true" {
flags.probe.basicAuth = true
flags.app.basicAuth = true
} else if strings.ToLower(os.Getenv("ENABLE_BASIC_AUTH")) == "false" {
flags.probe.basicAuth = false
flags.app.basicAuth = false
}
username := os.Getenv("BASIC_AUTH_USERNAME")
if username != "" {
flags.probe.username = username
flags.app.username = username
}
password := os.Getenv("BASIC_AUTH_PASSWORD")
if password != "" {
flags.probe.password = password
flags.app.password = password
}
if flags.dryRun {
return
}

View File

@@ -1,6 +1,8 @@
package main
import (
"encoding/base64"
"fmt"
"math/rand"
"net"
"net/http"
@@ -97,6 +99,12 @@ func probeMain(flags probeFlags, targets []appclient.Target) {
setLogLevel(flags.logLevel)
setLogFormatter(flags.logPrefix)
if flags.basicAuth {
log.Infof("Basic authentication enabled")
} else {
log.Infof("Basic authentication disabled")
}
traceCloser := tracing.NewFromEnv("scope-probe")
defer traceCloser.Close()
@@ -143,7 +151,13 @@ func probeMain(flags probeFlags, targets []appclient.Target) {
token = url.User.Username()
url.User = nil // erase credentials, as we use a special header
}
if flags.basicAuth {
token = base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", flags.username, flags.password)))
}
probeConfig := appclient.ProbeConfig{
BasicAuth: flags.basicAuth,
Token: token,
ProbeVersion: version,
ProbeID: probeID,

20
vendor/github.com/goji/httpauth/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,20 @@
Copyright (c) 2014 Carl Jackson (carl@avtok.com), Matt Silverlock (matt@eatsleeprepeat.net)
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

185
vendor/github.com/goji/httpauth/basic_auth.go generated vendored Normal file
View File

@@ -0,0 +1,185 @@
package httpauth
import (
"bytes"
"crypto/sha256"
"crypto/subtle"
"encoding/base64"
"fmt"
"net/http"
"strings"
)
type basicAuth struct {
h http.Handler
opts AuthOptions
}
// AuthOptions stores the configuration for HTTP Basic Authentication.
//
// A http.Handler may also be passed to UnauthorizedHandler to override the
// default error handler if you wish to serve a custom template/response.
type AuthOptions struct {
Realm string
User string
Password string
AuthFunc func(string, string, *http.Request) bool
UnauthorizedHandler http.Handler
}
// Satisfies the http.Handler interface for basicAuth.
func (b basicAuth) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Check if we have a user-provided error handler, else set a default
if b.opts.UnauthorizedHandler == nil {
b.opts.UnauthorizedHandler = http.HandlerFunc(defaultUnauthorizedHandler)
}
// Check that the provided details match
if b.authenticate(r) == false {
b.requestAuth(w, r)
return
}
// Call the next handler on success.
b.h.ServeHTTP(w, r)
}
// authenticate retrieves and then validates the user:password combination provided in
// the request header. Returns 'false' if the user has not successfully authenticated.
func (b *basicAuth) authenticate(r *http.Request) bool {
const basicScheme string = "Basic "
if r == nil {
return false
}
// In simple mode, prevent authentication with empty credentials if User is
// not set. Allow empty passwords to support non-password use-cases.
if b.opts.AuthFunc == nil && b.opts.User == "" {
return false
}
// Confirm the request is sending Basic Authentication credentials.
auth := r.Header.Get("Authorization")
if !strings.HasPrefix(auth, basicScheme) {
return false
}
// Get the plain-text username and password from the request.
// The first six characters are skipped - e.g. "Basic ".
str, err := base64.StdEncoding.DecodeString(auth[len(basicScheme):])
if err != nil {
return false
}
// Split on the first ":" character only, with any subsequent colons assumed to be part
// of the password. Note that the RFC2617 standard does not place any limitations on
// allowable characters in the password.
creds := bytes.SplitN(str, []byte(":"), 2)
if len(creds) != 2 {
return false
}
givenUser := string(creds[0])
givenPass := string(creds[1])
// Default to Simple mode if no AuthFunc is defined.
if b.opts.AuthFunc == nil {
b.opts.AuthFunc = b.simpleBasicAuthFunc
}
return b.opts.AuthFunc(givenUser, givenPass, r)
}
// simpleBasicAuthFunc authenticates the supplied username and password against
// the User and Password set in the Options struct.
func (b *basicAuth) simpleBasicAuthFunc(user, pass string, r *http.Request) bool {
// Equalize lengths of supplied and required credentials
// by hashing them
givenUser := sha256.Sum256([]byte(user))
givenPass := sha256.Sum256([]byte(pass))
requiredUser := sha256.Sum256([]byte(b.opts.User))
requiredPass := sha256.Sum256([]byte(b.opts.Password))
// Compare the supplied credentials to those set in our options
if subtle.ConstantTimeCompare(givenUser[:], requiredUser[:]) == 1 &&
subtle.ConstantTimeCompare(givenPass[:], requiredPass[:]) == 1 {
return true
}
return false
}
// Require authentication, and serve our error handler otherwise.
func (b *basicAuth) requestAuth(w http.ResponseWriter, r *http.Request) {
w.Header().Set("WWW-Authenticate", fmt.Sprintf(`Basic realm=%q`, b.opts.Realm))
b.opts.UnauthorizedHandler.ServeHTTP(w, r)
}
// defaultUnauthorizedHandler provides a default HTTP 401 Unauthorized response.
func defaultUnauthorizedHandler(w http.ResponseWriter, r *http.Request) {
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
}
// BasicAuth provides HTTP middleware for protecting URIs with HTTP Basic Authentication
// as per RFC 2617. The server authenticates a user:password combination provided in the
// "Authorization" HTTP header.
//
// Example:
//
// package main
//
// import(
// "net/http"
// "github.com/zenazn/goji"
// "github.com/goji/httpauth"
// )
//
// func main() {
// basicOpts := httpauth.AuthOptions{
// Realm: "Restricted",
// User: "Dave",
// Password: "ClearText",
// }
//
// goji.Use(httpauth.BasicAuth(basicOpts), SomeOtherMiddleware)
// goji.Get("/thing", myHandler)
// }
//
// Note: HTTP Basic Authentication credentials are sent in plain text, and therefore it does
// not make for a wholly secure authentication mechanism. You should serve your content over
// HTTPS to mitigate this, noting that "Basic Authentication" is meant to be just that: basic!
func BasicAuth(o AuthOptions) func(http.Handler) http.Handler {
fn := func(h http.Handler) http.Handler {
return basicAuth{h, o}
}
return fn
}
// SimpleBasicAuth is a convenience wrapper around BasicAuth. It takes a user and password, and
// returns a pre-configured BasicAuth handler using the "Restricted" realm and a default 401 handler.
//
// Example:
//
// package main
//
// import(
// "net/http"
// "github.com/zenazn/goji/web/httpauth"
// )
//
// func main() {
//
// goji.Use(httpauth.SimpleBasicAuth("dave", "somepassword"), SomeOtherMiddleware)
// goji.Get("/thing", myHandler)
// }
//
func SimpleBasicAuth(user, password string) func(http.Handler) http.Handler {
opts := AuthOptions{
Realm: "Restricted",
User: user,
Password: password,
}
return BasicAuth(opts)
}

8
vendor/manifest vendored
View File

@@ -871,6 +871,14 @@
"path": "types",
"notests": true
},
{
"importpath": "github.com/goji/httpauth",
"repository": "https://github.com/goji/httpauth",
"vcs": "git",
"revision": "2da839ab0f4df05a6db5eb277995589dadbd4fb9",
"branch": "master",
"notests": true
},
{
"importpath": "github.com/golang/glog",
"repository": "https://github.com/golang/glog",