Add error logging to aid in SSO debugging

This commit is contained in:
Saj Goonatilleke 2019-07-23 23:42:36 +10:00
parent 6fa4ba03da
commit f0bbe5bd1e
2 changed files with 100 additions and 38 deletions

View File

@ -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
View File

@ -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))