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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"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 {
|
type loggableResponseWriter struct {
|
||||||
StatusCode int
|
StatusCode int
|
||||||
|
|
||||||
|
|
115
main.go
115
main.go
|
@ -6,6 +6,7 @@ import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
|
@ -24,6 +25,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
logger = newRateLimitedLogger(os.Stderr, "", 0)
|
||||||
|
|
||||||
config *Config
|
config *Config
|
||||||
|
|
||||||
nonceCache = lru.New(20)
|
nonceCache = lru.New(20)
|
||||||
|
@ -112,7 +115,7 @@ func checkAuthorizationHeader(handler http.Handler, r *http.Request, w http.Resp
|
||||||
handler.ServeHTTP(w, r)
|
handler.ServeHTTP(w, r)
|
||||||
return true
|
return true
|
||||||
} else {
|
} 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) {
|
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")
|
cookie, err := r.Cookie("__discourse_proxy")
|
||||||
var username, groups string
|
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())
|
url := config.SSOURLString + "/session/sso_provider?" + sso_payload(config.SSOSecret, config.ProxyURLString, r.URL.String())
|
||||||
http.Redirect(w, r, url, 302)
|
http.Redirect(w, r, url, 302)
|
||||||
} else {
|
} else {
|
||||||
decoded, _ := base64.StdEncoding.DecodeString(sso)
|
decoded, err := base64.StdEncoding.DecodeString(sso)
|
||||||
decodedString := string(decoded)
|
if err != nil {
|
||||||
parsedQuery, _ := url.ParseQuery(decodedString)
|
fail("invalid sso query parameter: %s", err)
|
||||||
|
return
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
func getReturnUrl(secret string, payload string, sig string, nonce string) (returnUrl string, err error) {
|
||||||
nonceMutex.Lock()
|
nonceMutex.Lock()
|
||||||
value, gotNonce := nonceCache.Get(nonce)
|
value, ok := nonceCache.Get(nonce)
|
||||||
nonceMutex.Unlock()
|
nonceMutex.Unlock()
|
||||||
if !gotNonce {
|
if !ok {
|
||||||
err = fmt.Errorf("Nonce %s not found", nonce)
|
err = fmt.Errorf("nonce not found: %s", nonce)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
returnUrl = value.(string)
|
returnUrl = value.(string)
|
||||||
nonceMutex.Lock()
|
nonceMutex.Lock()
|
||||||
nonceCache.Remove(nonce)
|
nonceCache.Remove(nonce)
|
||||||
nonceMutex.Unlock()
|
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
|
return
|
||||||
}
|
}
|
||||||
|
@ -213,7 +252,7 @@ func sameHost(handler http.Handler) http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
func signCookie(data, secret string) string {
|
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) {
|
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]
|
signature := split[len(split)-1]
|
||||||
parsed := strings.Join(split[:len(split)-1], ",")
|
parsed := strings.Join(split[:len(split)-1], ",")
|
||||||
expected := ComputeHmac256(parsed, secret)
|
expected := computeHMAC(parsed, secret)
|
||||||
|
|
||||||
if expected != signature {
|
if expected != signature {
|
||||||
parsed = ""
|
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)
|
result := "return_sso_url=" + return_sso_url + returnUrl + "&nonce=" + addNonce(returnUrl)
|
||||||
payload := base64.StdEncoding.EncodeToString([]byte(result))
|
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 {
|
func addNonce(returnUrl string) string {
|
||||||
|
@ -259,7 +298,7 @@ func addNonce(returnUrl string) string {
|
||||||
return guid
|
return guid
|
||||||
}
|
}
|
||||||
|
|
||||||
func ComputeHmac256(message string, secret string) string {
|
func computeHMAC(message string, secret string) string {
|
||||||
key := []byte(secret)
|
key := []byte(secret)
|
||||||
h := hmac.New(sha256.New, key)
|
h := hmac.New(sha256.New, key)
|
||||||
h.Write([]byte(message))
|
h.Write([]byte(message))
|
||||||
|
|
Loading…
Reference in New Issue