diff --git a/handlers/sower.go b/handlers/sower.go index 4a5c422..dfdc1bb 100644 --- a/handlers/sower.go +++ b/handlers/sower.go @@ -26,6 +26,11 @@ type InputRequest struct { Format string `json:"access_format"` } +type Principal struct { + Raw string // original, e.g., "auth0|67ec..." + LabelSafe string // sanitized for k8s label value +} + func dispatch(w http.ResponseWriter, r *http.Request) { log.Debug("Dispatch") if r.Method != "POST" { @@ -68,12 +73,13 @@ func dispatch(w http.ResponseWriter, r *http.Request) { accessTokenVal = *accessToken } - email, err := getEmailFromToken(accessTokenVal) + principal, err := getPrincipalFromToken(accessTokenVal) if err != nil { panic(err) } - result, err := createK8sJob(currentAction, string(out), accessFormat, accessTokenVal, userName, email) + // pass both raw and safe; createK8sJob will still defensively sanitize at label write + result, err := createK8sJob(currentAction, string(out), accessFormat, accessTokenVal, userName, principal.LabelSafe) if err != nil { http.Error(w, err.Error(), 500) return @@ -88,28 +94,35 @@ func dispatch(w http.ResponseWriter, r *http.Request) { } func status(w http.ResponseWriter, r *http.Request) { - email := "" + UID := r.URL.Query().Get("UID") + if UID != "" { + username := "" + if bt := getBearerToken(r); bt != nil && *bt != "" { + if p, err := getPrincipalFromToken(*bt); err == nil { + username = p.LabelSafe + } + } + + result, errUID := getJobStatusByID(UID, sanitizeLabelValue(username)) + if errUID != nil { + http.Error(w, errUID.Error(), 500) + return + } + + out, err := json.Marshal(result) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + + fmt.Fprint(w, string(out)) + } else { + http.Error(w, "Missing UID argument", 400) + return + } +} - UID := r.URL.Query().Get("UID") - if UID != "" { - result, errUID := getJobStatusByID(UID, email) - if errUID != nil { - http.Error(w, errUID.Error(), 500) - return - } - out, err := json.Marshal(result) - if err != nil { - http.Error(w, err.Error(), 500) - return - } - - fmt.Fprint(w, string(out)) - } else { - http.Error(w, "Missing UID argument", 300) - return - } -} func output(w http.ResponseWriter, r *http.Request) { accessToken := getBearerToken(r) @@ -119,14 +132,14 @@ func output(w http.ResponseWriter, r *http.Request) { accessTokenVal = *accessToken } - email, err := getEmailFromToken(accessTokenVal) + principal, err := getPrincipalFromToken(accessTokenVal) if err != nil { panic(err.Error()) } UID := r.URL.Query().Get("UID") if UID != "" { - result, errUID := getJobLogs(UID, email) + result, errUID := getJobLogs(UID, principal.LabelSafe) if errUID != nil { http.Error(w, errUID.Error(), 500) return @@ -173,12 +186,12 @@ func list(w http.ResponseWriter, r *http.Request) { accessTokenVal = *accessToken } - email, err := getEmailFromToken(accessTokenVal) + principal, err := getPrincipalFromToken(accessTokenVal) if err != nil { panic(err.Error()) } - result := listJobs(getJobClient(), email) + result := listJobs(getJobClient(), principal.LabelSafe) out, err := json.Marshal(result) if err != nil { @@ -201,20 +214,20 @@ func getBearerToken(r *http.Request) *string { return nil } -func getEmailFromToken(accessTokenVal string) (string, error) { +func getPrincipalFromToken(accessTokenVal string) (Principal, error) { jwksURL := "http://fence-service/.well-known/jwks" // create the JWKS from the resource at the given URL jwks, err := keyfunc.Get(jwksURL, keyfunc.Options{}) if err != nil { log.Debugf("Failed to create JWKS from resource at the given URL.\nError: %s", err.Error()) - return "", err + return Principal{}, err } token, err := jwt.Parse(accessTokenVal, jwks.Keyfunc) if err != nil { log.Debugf("Failed to parse the JWT.\nError: %s", err.Error()) - return "", err + return Principal{}, err } // Verify if sub field exists, to identify if it is a user token or a client token @@ -227,13 +240,15 @@ func getEmailFromToken(accessTokenVal string) (string, error) { // User token context := claims["context"].(map[string]interface{}) user := context["user"].(map[string]interface{}) - username := user["name"].(string) - username = strings.ReplaceAll(username, "@", "_") - return username, nil + // This field was previously called "email" or "username"; it’s really a principal ID. + raw := user["name"].(string) // often email-ish or provider-prefixed ID + raw = strings.ReplaceAll(raw, "@", "_") + return Principal{Raw: raw, LabelSafe: sanitizeLabelValue(raw)}, nil } else if claims["azp"] != nil { // Client token - return claims["azp"].(string), nil + raw := claims["azp"].(string) + return Principal{Raw: raw, LabelSafe: sanitizeLabelValue(raw)}, nil } } - return "", nil + return Principal{}, nil } diff --git a/handlers/util.go b/handlers/util.go index e55c17f..56308c0 100644 --- a/handlers/util.go +++ b/handlers/util.go @@ -1,5 +1,11 @@ package handlers +import ( + "crypto/sha256" + "encoding/hex" + "regexp" +) + func filter(scs []SowerConfig, test func(SowerConfig) bool) (ret []SowerConfig) { for _, s := range scs { if test(s) { @@ -8,3 +14,31 @@ func filter(scs []SowerConfig, test func(SowerConfig) bool) (ret []SowerConfig) } return } + +var ( + // valid: alphanum, '-', '_', '.', must start/end alnum, <= 63 chars + labelAllowed = regexp.MustCompile(`[^A-Za-z0-9_.-]+`) + trimEnds = regexp.MustCompile(`^[^A-Za-z0-9]+|[^A-Za-z0-9]+$`) + collapseDash = regexp.MustCompile(`[-._]{2,}`) +) + +func sanitizeLabelValue(raw string) string { + if raw == "" { + return "id-" + shortHash("empty") + } + s := labelAllowed.ReplaceAllString(raw, "-") + s = collapseDash.ReplaceAllString(s, "-") + s = trimEnds.ReplaceAllString(s, "") + if len(s) > 63 { + s = s[:63] + } + if s == "" { + return "id-" + shortHash(raw) + } + return s +} + +func shortHash(s string) string { + h := sha256.Sum256([]byte(s)) + return hex.EncodeToString(h[:])[:16] +} \ No newline at end of file