diff --git a/auth/builtin.go b/auth/builtin.go index 459dac71f..fb4fca5c7 100644 --- a/auth/builtin.go +++ b/auth/builtin.go @@ -15,7 +15,10 @@ var Validators []Validator // Validator provides token validation. type Validator interface { // Valid determines if the token is valid. - Valid(token *jwt.Token, db *gorm.DB) (valid bool) + // When valid, return nil. + // When not valid, return NotValid error. + // On failure, return the (cause) error. + Valid(token *jwt.Token, db *gorm.DB) (err error) } // @@ -71,9 +74,15 @@ type Builtin struct { // // Authenticate the token func (r *Builtin) Authenticate(request *Request) (jwToken *jwt.Token, err error) { - token := request.Token + token := strings.Replace(request.Token, "Bearer", "", 1) + token = strings.Fields(token)[0] + defer func() { + if err != nil { + Log.Info(err.Error()) + } + }() jwToken, err = jwt.Parse( - request.Token, + token, func(jwToken *jwt.Token) (secret interface{}, err error) { _, cast := jwToken.Method.(*jwt.SigningMethodHMAC) if !cast { @@ -93,32 +102,52 @@ func (r *Builtin) Authenticate(request *Request) (jwToken *jwt.Token, err error) } claims, cast := jwToken.Claims.(jwt.MapClaims) if !cast { - err = liberr.Wrap(&NotAuthenticated{Token: token}) + err = liberr.Wrap( + &NotValid{ + Reason: "Claims not specified.", + Token: token, + }) return } v, found := claims["user"] if !found { - err = liberr.Wrap(&NotAuthenticated{Token: token}) + err = liberr.Wrap( + &NotValid{ + Reason: "User not specified.", + Token: token, + }) return } _, cast = v.(string) if !cast { - err = liberr.Wrap(&NotAuthenticated{Token: token}) + err = liberr.Wrap( + &NotValid{ + Reason: "User not string.", + Token: token, + }) return } v, found = claims["scope"] if !found { - err = liberr.Wrap(&NotAuthenticated{Token: token}) + err = liberr.Wrap( + &NotValid{ + Reason: "Scope not specified.", + Token: token, + }) return } _, cast = v.(string) if !cast { - err = liberr.Wrap(&NotAuthenticated{Token: token}) + err = liberr.Wrap( + &NotValid{ + Reason: "Scope not string.", + Token: token, + }) return } for _, v := range Validators { - if !v.Valid(jwToken, request.DB) { - err = liberr.Wrap(&NotValid{Token: token}) + err = v.Valid(jwToken, request.DB) + if err != nil { return } } diff --git a/auth/provider.go b/auth/provider.go index 0b63aea8d..3b83d6c1b 100644 --- a/auth/provider.go +++ b/auth/provider.go @@ -1,6 +1,7 @@ package auth import ( + "errors" "fmt" "github.com/golang-jwt/jwt/v4" "github.com/jortel/go-utils/logr" @@ -51,26 +52,29 @@ type NotAuthenticated struct { } func (e *NotAuthenticated) Error() (s string) { - return fmt.Sprintf("Token %s not-valid.", e.Token) + return fmt.Sprintf("Token [%s] not-authenticated.", e.Token) } func (e *NotAuthenticated) Is(err error) (matched bool) { - _, matched = err.(*NotAuthenticated) + notAuth := &NotAuthenticated{} + matched = errors.As(err, ¬Auth) return } // // NotValid is returned when a token is not valid. type NotValid struct { - Token string + Reason string + Token string } func (e *NotValid) Error() (s string) { - return fmt.Sprintf("Token %s not-valid.", e.Token) + return fmt.Sprintf("Token [%s] not-valid: %s", e.Token, e.Reason) } func (e *NotValid) Is(err error) (matched bool) { - _, matched = err.(*NotValid) + notValid := &NotValid{} + matched = errors.As(err, ¬Valid) return } diff --git a/hack/jwt.sh b/hack/jwt.sh new file mode 100755 index 000000000..98e10e477 --- /dev/null +++ b/hack/jwt.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# +# Usage: jwt.sh +# +# scope - (string) space-separated scopes. (default: *:*). +# +key=$1 +scope="${2:-*:*}" +header='{"typ":"JWT","alg":"HS512"}' +payload="{\"user\":\"operator\",\"scope\":\"${scope}\"}" +headerStr=$(echo -n ${header} \ + | base64 -w 0 \ + | sed s/\+/-/g \ + | sed 's/\//_/g' \ + | sed -E s/=+$//) +payloadStr=$(echo -n ${payload} \ + | base64 -w 0 \ + | sed s/\+/-/g \ + | sed 's/\//_/g' \ + | sed -E s/=+$//) +signStr=$(echo -n "${headerStr}.${payloadStr}" \ + | openssl dgst -sha512 -hmac ${key} -binary \ + | base64 -w 0 \ + | sed s/\+/-/g \ + | sed 's/\//_/g' \ + | sed -E s/=+$//) +token="${headerStr}.${payloadStr}.${signStr}" +echo "${token}" diff --git a/task/auth.go b/task/auth.go index 585d680d4..1b2254e4d 100644 --- a/task/auth.go +++ b/task/auth.go @@ -2,7 +2,9 @@ package task import ( "context" + "fmt" "github.com/golang-jwt/jwt/v4" + "github.com/konveyor/tackle2-hub/auth" "github.com/konveyor/tackle2-hub/model" "gorm.io/gorm" core "k8s.io/api/core/v1" @@ -22,26 +24,34 @@ type Validator struct { // - The token references a task. // - The task is valid and running. // - The task pod valid and pending|running. -func (r *Validator) Valid(token *jwt.Token, db *gorm.DB) (valid bool) { - var err error +func (r *Validator) Valid(token *jwt.Token, db *gorm.DB) (err error) { claims := token.Claims.(jwt.MapClaims) v, found := claims["task"] id, cast := v.(float64) if !found || !cast { - Log.Info("Task not referenced by token.") return } task := &model.Task{} err = db.First(task, id).Error if err != nil { - Log.Info("Task referenced by token: not found.") + err = &auth.NotValid{ + Token: token.Raw, + Reason: fmt.Sprintf( + "Task (%d) referenced by token: not found.", + uint64(id)), + } return } switch task.State { case Pending, Running: default: - Log.Info("Task referenced by token: not running.") + err = &auth.NotValid{ + Token: token.Raw, + Reason: fmt.Sprintf( + "Task (%d) referenced by token: not running.", + uint64(id)), + } return } pod := &core.Pod{} @@ -53,24 +63,26 @@ func (r *Validator) Valid(token *jwt.Token, db *gorm.DB) (valid bool) { }, pod) if err != nil { - Log.Info( - "Pod referenced by token: not found.", - "name", - task.Pod) + err = &auth.NotValid{ + Token: token.Raw, + Reason: fmt.Sprintf( + "Pod (%s) referenced by token: not found.", + pod.Name), + } return } switch pod.Status.Phase { case core.PodPending, core.PodRunning: default: - Log.Info( - "Pod referenced by token: not running.", - "name", - task.Pod, - "phase", - pod.Status.Phase) + err = &auth.NotValid{ + Token: token.Raw, + Reason: fmt.Sprintf( + "Pod (%s) referenced by token: not pending|running. Phase detected: %s", + task.Pod, + pod.Status.Phase), + } return } - valid = true return }