Add error logging to aid in SSO debugging
This commit is contained in:
parent
6fa4ba03da
commit
f0bbe5bd1e
23
logging.go
23
logging.go
|
@ -1,10 +1,33 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
type rateLimitedLogger struct {
|
||||
logger *log.Logger
|
||||
limiter *rate.Limiter
|
||||
}
|
||||
|
||||
func newRateLimitedLogger(out io.Writer, prefix string, flag int) *rateLimitedLogger {
|
||||
return &rateLimitedLogger{
|
||||
logger: log.New(out, prefix, flag),
|
||||
limiter: rate.NewLimiter(rate.Every(250*time.Millisecond), 30),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *rateLimitedLogger) Printf(format string, v ...interface{}) {
|
||||
if !l.limiter.Allow() {
|
||||
return
|
||||
}
|
||||
l.logger.Printf(format, v...)
|
||||
}
|
||||
|
||||
type loggableResponseWriter struct {
|
||||
StatusCode int
|
||||
|
||||
|
|
115
main.go
115
main.go
|
@ -6,6 +6,7 @@ import (
|
|||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
|
@ -24,6 +25,8 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
logger = newRateLimitedLogger(os.Stderr, "", 0)
|
||||
|
||||
config *Config
|
||||
|
||||
nonceCache = lru.New(20)
|
||||
|
@ -112,7 +115,7 @@ func checkAuthorizationHeader(handler http.Handler, r *http.Request, w http.Resp
|
|||
handler.ServeHTTP(w, r)
|
||||
return true
|
||||
} else {
|
||||
log.Printf("Rejected basic auth creds. Authorization header was %s", auth_header)
|
||||
logger.Printf("rejected basic auth creds: authorization header: %s", auth_header)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -133,6 +136,17 @@ func checkWhitelist(handler http.Handler, r *http.Request, w http.ResponseWriter
|
|||
}
|
||||
|
||||
func redirectIfNoCookie(handler http.Handler, r *http.Request, w http.ResponseWriter) {
|
||||
writeHttpError := func(code int) {
|
||||
http.Error(w, http.StatusText(code), code)
|
||||
}
|
||||
writeClientError := func() {
|
||||
writeHttpError(http.StatusBadRequest)
|
||||
}
|
||||
fail := func(format string, v ...interface{}) {
|
||||
logger.Printf(format, v...)
|
||||
writeClientError()
|
||||
}
|
||||
|
||||
cookie, err := r.Cookie("__discourse_proxy")
|
||||
var username, groups string
|
||||
|
||||
|
@ -155,52 +169,77 @@ func redirectIfNoCookie(handler http.Handler, r *http.Request, w http.ResponseWr
|
|||
url := config.SSOURLString + "/session/sso_provider?" + sso_payload(config.SSOSecret, config.ProxyURLString, r.URL.String())
|
||||
http.Redirect(w, r, url, 302)
|
||||
} else {
|
||||
decoded, _ := base64.StdEncoding.DecodeString(sso)
|
||||
decodedString := string(decoded)
|
||||
parsedQuery, _ := url.ParseQuery(decodedString)
|
||||
|
||||
username := parsedQuery["username"]
|
||||
groups := parsedQuery["groups"]
|
||||
admin := parsedQuery["admin"]
|
||||
nonce := parsedQuery["nonce"]
|
||||
|
||||
groupsArray := strings.Split(groups[0], ",")
|
||||
|
||||
if len(nonce) > 0 && len(admin) > 0 && len(username) > 0 && (admin[0] == "true" || config.AllowAll) {
|
||||
returnUrl, err := getReturnUrl(config.SSOSecret, sso, sig, nonce[0])
|
||||
|
||||
if err != nil {
|
||||
fmt.Fprintf(w, "Invalid request")
|
||||
return
|
||||
}
|
||||
|
||||
// we have a valid auth
|
||||
expiration := time.Now().Add(365 * 24 * time.Hour)
|
||||
|
||||
cookieData := strings.Join([]string{username[0], strings.Join(groupsArray, "|")}, ",")
|
||||
cookie := http.Cookie{Name: "__discourse_proxy", Value: signCookie(cookieData, config.CookieSecret), Expires: expiration, HttpOnly: true}
|
||||
http.SetCookie(w, &cookie)
|
||||
|
||||
// works around weird safari stuff
|
||||
fmt.Fprintf(w, "<html><head></head><body><script>window.location = '%v'</script></body>", returnUrl)
|
||||
decoded, err := base64.StdEncoding.DecodeString(sso)
|
||||
if err != nil {
|
||||
fail("invalid sso query parameter: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
parsedQuery, err := url.ParseQuery(string(decoded))
|
||||
if err != nil {
|
||||
fail("invalid sso query parameter: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
username = parsedQuery["username"]
|
||||
groups = parsedQuery["groups"]
|
||||
admin = parsedQuery["admin"]
|
||||
nonce = parsedQuery["nonce"]
|
||||
groupsArray = strings.Split(groups[0], ",")
|
||||
)
|
||||
|
||||
if len(nonce) == 0 {
|
||||
fail("incomplete payload from sso provider: missing nonce")
|
||||
return
|
||||
}
|
||||
if len(username) == 0 {
|
||||
fail("incomplete payload from sso provider: missing username")
|
||||
return
|
||||
}
|
||||
if len(admin) == 0 {
|
||||
fail("incomplete payload from sso provider: missing admin")
|
||||
return
|
||||
}
|
||||
if !(config.AllowAll || admin[0] == "true") {
|
||||
writeHttpError(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
returnUrl, err := getReturnUrl(config.SSOSecret, sso, sig, nonce[0])
|
||||
if err != nil {
|
||||
fail("failed to build return URL: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
// we have a valid auth
|
||||
expiration := time.Now().Add(365 * 24 * time.Hour)
|
||||
|
||||
cookieData := strings.Join([]string{username[0], strings.Join(groupsArray, "|")}, ",")
|
||||
cookie := http.Cookie{Name: "__discourse_proxy", Value: signCookie(cookieData, config.CookieSecret), Expires: expiration, HttpOnly: true}
|
||||
http.SetCookie(w, &cookie)
|
||||
|
||||
// works around weird safari stuff
|
||||
fmt.Fprintf(w, "<html><head></head><body><script>window.location = '%v'</script></body>", returnUrl)
|
||||
}
|
||||
}
|
||||
|
||||
func getReturnUrl(secret string, payload string, sig string, nonce string) (returnUrl string, err error) {
|
||||
nonceMutex.Lock()
|
||||
value, gotNonce := nonceCache.Get(nonce)
|
||||
value, ok := nonceCache.Get(nonce)
|
||||
nonceMutex.Unlock()
|
||||
if !gotNonce {
|
||||
err = fmt.Errorf("Nonce %s not found", nonce)
|
||||
if !ok {
|
||||
err = fmt.Errorf("nonce not found: %s", nonce)
|
||||
return
|
||||
}
|
||||
|
||||
returnUrl = value.(string)
|
||||
nonceMutex.Lock()
|
||||
nonceCache.Remove(nonce)
|
||||
nonceMutex.Unlock()
|
||||
if ComputeHmac256(payload, secret) != sig {
|
||||
err = fmt.Errorf("Signature is invalid")
|
||||
|
||||
if computeHMAC(payload, secret) != sig {
|
||||
err = errors.New("signature is invalid")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -213,7 +252,7 @@ func sameHost(handler http.Handler) http.Handler {
|
|||
}
|
||||
|
||||
func signCookie(data, secret string) string {
|
||||
return data + "," + ComputeHmac256(data, secret)
|
||||
return data + "," + computeHMAC(data, secret)
|
||||
}
|
||||
|
||||
func parseCookie(data, secret string) (username string, groups string, err error) {
|
||||
|
@ -230,7 +269,7 @@ func parseCookie(data, secret string) (username string, groups string, err error
|
|||
|
||||
signature := split[len(split)-1]
|
||||
parsed := strings.Join(split[:len(split)-1], ",")
|
||||
expected := ComputeHmac256(parsed, secret)
|
||||
expected := computeHMAC(parsed, secret)
|
||||
|
||||
if expected != signature {
|
||||
parsed = ""
|
||||
|
@ -248,7 +287,7 @@ func sso_payload(secret string, return_sso_url string, returnUrl string) string
|
|||
result := "return_sso_url=" + return_sso_url + returnUrl + "&nonce=" + addNonce(returnUrl)
|
||||
payload := base64.StdEncoding.EncodeToString([]byte(result))
|
||||
|
||||
return "sso=" + payload + "&sig=" + ComputeHmac256(payload, secret)
|
||||
return "sso=" + payload + "&sig=" + computeHMAC(payload, secret)
|
||||
}
|
||||
|
||||
func addNonce(returnUrl string) string {
|
||||
|
@ -259,7 +298,7 @@ func addNonce(returnUrl string) string {
|
|||
return guid
|
||||
}
|
||||
|
||||
func ComputeHmac256(message string, secret string) string {
|
||||
func computeHMAC(message string, secret string) string {
|
||||
key := []byte(secret)
|
||||
h := hmac.New(sha256.New, key)
|
||||
h.Write([]byte(message))
|
||||
|
|
Loading…
Reference in New Issue