From 7357d1b9e093dd2664bfc4cb751dcb30a785559f Mon Sep 17 00:00:00 2001 From: Jeff Ortel Date: Thu, 19 Oct 2023 12:10:39 -0700 Subject: [PATCH 01/12] :ghost: add hack to generate jwt tokens for builtin auth. Signed-off-by: Jeff Ortel --- hack/jwt.sh | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100755 hack/jwt.sh diff --git a/hack/jwt.sh b/hack/jwt.sh new file mode 100755 index 000000000..328c20afe --- /dev/null +++ b/hack/jwt.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# +# Usage: hubtoken.sh +# +key=$1 +hexKey=$(echo -n "key" \ + | xxd -p \ + | tr -d '\n') +header='{"alg":"HS512","typ":"JWT"}' +payload='{"scope":"*:*","user":"operator"}' +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 -mac HMAC -macopt hexkey:${hexKey} -binary \ + | base64 -w 0 \ + | sed s/\+/-/g \ + | sed 's/\//_/g' \ + | sed -E s/=+$//) +token="${headerStr}.${payloadStr}.${signStr}" +echo "${token}" From fbe0e2bd12579604d687716044073cd3cdd6376a Mon Sep 17 00:00:00 2001 From: Jeff Ortel Date: Thu, 19 Oct 2023 12:11:46 -0700 Subject: [PATCH 02/12] comment Signed-off-by: Jeff Ortel --- hack/jwt.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/jwt.sh b/hack/jwt.sh index 328c20afe..c531befe9 100755 --- a/hack/jwt.sh +++ b/hack/jwt.sh @@ -1,6 +1,6 @@ #!/bin/bash # -# Usage: hubtoken.sh +# Usage: jwt.sh # key=$1 hexKey=$(echo -n "key" \ From 87ecf828375c5e0a08e378bea34f47f7f18a5a4c Mon Sep 17 00:00:00 2001 From: Jeff Ortel Date: Thu, 19 Oct 2023 14:04:02 -0700 Subject: [PATCH 03/12] checkpoint Signed-off-by: Jeff Ortel --- hack/jwt.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hack/jwt.sh b/hack/jwt.sh index c531befe9..19c0131bc 100755 --- a/hack/jwt.sh +++ b/hack/jwt.sh @@ -3,11 +3,11 @@ # Usage: jwt.sh # key=$1 -hexKey=$(echo -n "key" \ +hexKey=$(echo -n "${key}" \ | xxd -p \ | tr -d '\n') header='{"alg":"HS512","typ":"JWT"}' -payload='{"scope":"*:*","user":"operator"}' +payload='{"scope":"applicaions:get","user":"operator"}' headerStr=$(echo -n ${header} \ | base64 -w 0 \ | sed s/\+/-/g \ From b25d5568a6570f5ba7e2e575b71abd760a1e1eea Mon Sep 17 00:00:00 2001 From: Jeff Ortel Date: Thu, 19 Oct 2023 14:13:29 -0700 Subject: [PATCH 04/12] checkpoint Signed-off-by: Jeff Ortel --- hack/jwt.sh | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/hack/jwt.sh b/hack/jwt.sh index 19c0131bc..0b01bd637 100755 --- a/hack/jwt.sh +++ b/hack/jwt.sh @@ -3,9 +3,6 @@ # Usage: jwt.sh # key=$1 -hexKey=$(echo -n "${key}" \ - | xxd -p \ - | tr -d '\n') header='{"alg":"HS512","typ":"JWT"}' payload='{"scope":"applicaions:get","user":"operator"}' headerStr=$(echo -n ${header} \ @@ -19,7 +16,7 @@ payloadStr=$(echo -n ${payload} \ | sed 's/\//_/g' \ | sed -E s/=+$//) signStr=$(echo -n "${headerStr}.${payloadStr}" \ - | openssl dgst -sha512 -mac HMAC -macopt hexkey:${hexKey} -binary \ + | openssl dgst -sha512 -mac HMAC -macopt key:${key} -binary \ | base64 -w 0 \ | sed s/\+/-/g \ | sed 's/\//_/g' \ From 28551c3a5effa78b0275b9ae25f97d0dfaa7f663 Mon Sep 17 00:00:00 2001 From: Jeff Ortel Date: Thu, 19 Oct 2023 14:16:24 -0700 Subject: [PATCH 05/12] checkpoint Signed-off-by: Jeff Ortel --- hack/jwt.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/jwt.sh b/hack/jwt.sh index 0b01bd637..234ea8c9a 100755 --- a/hack/jwt.sh +++ b/hack/jwt.sh @@ -16,7 +16,7 @@ payloadStr=$(echo -n ${payload} \ | sed 's/\//_/g' \ | sed -E s/=+$//) signStr=$(echo -n "${headerStr}.${payloadStr}" \ - | openssl dgst -sha512 -mac HMAC -macopt key:${key} -binary \ + | openssl dgst -sha512 -hmac ${key} -binary \ | base64 -w 0 \ | sed s/\+/-/g \ | sed 's/\//_/g' \ From c8efa797922b06115cb116977246ccc05cecd754 Mon Sep 17 00:00:00 2001 From: Jeff Ortel Date: Thu, 19 Oct 2023 14:46:44 -0700 Subject: [PATCH 06/12] checkpoint Signed-off-by: Jeff Ortel --- hack/jwt.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/jwt.sh b/hack/jwt.sh index 234ea8c9a..33b9a1f62 100755 --- a/hack/jwt.sh +++ b/hack/jwt.sh @@ -4,7 +4,7 @@ # key=$1 header='{"alg":"HS512","typ":"JWT"}' -payload='{"scope":"applicaions:get","user":"operator"}' +payload='{"scope":"*:*","user":"operator"}' headerStr=$(echo -n ${header} \ | base64 -w 0 \ | sed s/\+/-/g \ From bbc53a535d11c05ce0834eb92075abd2d26a3be6 Mon Sep 17 00:00:00 2001 From: Jeff Ortel Date: Thu, 19 Oct 2023 15:22:50 -0700 Subject: [PATCH 07/12] checkpoint Signed-off-by: Jeff Ortel --- auth/builtin.go | 3 ++- task/auth.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/auth/builtin.go b/auth/builtin.go index 459dac71f..619c05e5a 100644 --- a/auth/builtin.go +++ b/auth/builtin.go @@ -71,7 +71,8 @@ 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] jwToken, err = jwt.Parse( request.Token, func(jwToken *jwt.Token) (secret interface{}, err error) { diff --git a/task/auth.go b/task/auth.go index 585d680d4..255ccf522 100644 --- a/task/auth.go +++ b/task/auth.go @@ -28,7 +28,7 @@ func (r *Validator) Valid(token *jwt.Token, db *gorm.DB) (valid bool) { v, found := claims["task"] id, cast := v.(float64) if !found || !cast { - Log.Info("Task not referenced by token.") + valid = true return } task := &model.Task{} From 84adf8276bb967a71e46cb7296ce72323169d695 Mon Sep 17 00:00:00 2001 From: Jeff Ortel Date: Mon, 23 Oct 2023 13:16:28 -0700 Subject: [PATCH 08/12] checkpoint Signed-off-by: Jeff Ortel --- auth/builtin.go | 43 ++++++++++++++++++++++++++++++++++--------- auth/provider.go | 14 +++++++++----- hack/jwt.sh | 9 ++++++--- task/auth.go | 45 +++++++++++++++++++++++++++++---------------- 4 files changed, 78 insertions(+), 33 deletions(-) diff --git a/auth/builtin.go b/auth/builtin.go index 619c05e5a..71b370208 100644 --- a/auth/builtin.go +++ b/auth/builtin.go @@ -15,7 +15,7 @@ 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) + Valid(token *jwt.Token, db *gorm.DB) (err error) } // @@ -73,8 +73,13 @@ type Builtin struct { func (r *Builtin) Authenticate(request *Request) (jwToken *jwt.Token, err error) { 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 { @@ -94,32 +99,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 index 33b9a1f62..98e10e477 100755 --- a/hack/jwt.sh +++ b/hack/jwt.sh @@ -1,10 +1,13 @@ #!/bin/bash # -# Usage: jwt.sh +# Usage: jwt.sh +# +# scope - (string) space-separated scopes. (default: *:*). # key=$1 -header='{"alg":"HS512","typ":"JWT"}' -payload='{"scope":"*:*","user":"operator"}' +scope="${2:-*:*}" +header='{"typ":"JWT","alg":"HS512"}' +payload="{\"user\":\"operator\",\"scope\":\"${scope}\"}" headerStr=$(echo -n ${header} \ | base64 -w 0 \ | sed s/\+/-/g \ diff --git a/task/auth.go b/task/auth.go index 255ccf522..b9401dba9 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,35 @@ 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 { - valid = true + // Not a task 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 +64,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 running. Phase: %s", + task.Pod, + pod.Status.Phase), + } return } - valid = true return } From 994f88f081fc7c97d0910873f679b61ec7111e9f Mon Sep 17 00:00:00 2001 From: Jeff Ortel Date: Mon, 23 Oct 2023 13:25:04 -0700 Subject: [PATCH 09/12] checkpoint Signed-off-by: Jeff Ortel --- auth/builtin.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/auth/builtin.go b/auth/builtin.go index 71b370208..e8a61bdad 100644 --- a/auth/builtin.go +++ b/auth/builtin.go @@ -110,7 +110,7 @@ func (r *Builtin) Authenticate(request *Request) (jwToken *jwt.Token, err error) if !found { err = liberr.Wrap( &NotValid{ - Reason: "user not specified.", + Reason: "User not specified.", Token: token, }) return @@ -119,7 +119,7 @@ func (r *Builtin) Authenticate(request *Request) (jwToken *jwt.Token, err error) if !cast { err = liberr.Wrap( &NotValid{ - Reason: "user not string.", + Reason: "User not string.", Token: token, }) return @@ -128,7 +128,7 @@ func (r *Builtin) Authenticate(request *Request) (jwToken *jwt.Token, err error) if !found { err = liberr.Wrap( &NotValid{ - Reason: "scope not specified.", + Reason: "Scope not specified.", Token: token, }) return @@ -137,7 +137,7 @@ func (r *Builtin) Authenticate(request *Request) (jwToken *jwt.Token, err error) if !cast { err = liberr.Wrap( &NotValid{ - Reason: "scope not string.", + Reason: "Scope not string.", Token: token, }) return From e717685baff6e926e9c6e272de8db065fe1a6176 Mon Sep 17 00:00:00 2001 From: Jeff Ortel Date: Mon, 23 Oct 2023 13:29:24 -0700 Subject: [PATCH 10/12] checkpoint Signed-off-by: Jeff Ortel --- auth/builtin.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/auth/builtin.go b/auth/builtin.go index e8a61bdad..fb4fca5c7 100644 --- a/auth/builtin.go +++ b/auth/builtin.go @@ -15,6 +15,9 @@ var Validators []Validator // Validator provides token validation. type Validator interface { // Valid determines if the token is valid. + // 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) } From 5b386fe5e8ca8ef91e50e3166009f1f2676076dc Mon Sep 17 00:00:00 2001 From: Jeff Ortel Date: Mon, 23 Oct 2023 13:34:51 -0700 Subject: [PATCH 11/12] checkpoint Signed-off-by: Jeff Ortel --- task/auth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/task/auth.go b/task/auth.go index b9401dba9..768cbcb43 100644 --- a/task/auth.go +++ b/task/auth.go @@ -79,7 +79,7 @@ func (r *Validator) Valid(token *jwt.Token, db *gorm.DB) (err error) { err = &auth.NotValid{ Token: token.Raw, Reason: fmt.Sprintf( - "Pod (%s) referenced by token: not running. Phase: %s", + "Pod (%s) referenced by token: not pending|running. Phase detected: %s", task.Pod, pod.Status.Phase), } From bb77b4bb1c89c805f441f199ab22f78cccc785d4 Mon Sep 17 00:00:00 2001 From: Jeff Ortel Date: Mon, 23 Oct 2023 13:35:27 -0700 Subject: [PATCH 12/12] checkpoint Signed-off-by: Jeff Ortel --- task/auth.go | 1 - 1 file changed, 1 deletion(-) diff --git a/task/auth.go b/task/auth.go index 768cbcb43..1b2254e4d 100644 --- a/task/auth.go +++ b/task/auth.go @@ -29,7 +29,6 @@ func (r *Validator) Valid(token *jwt.Token, db *gorm.DB) (err error) { v, found := claims["task"] id, cast := v.(float64) if !found || !cast { - // Not a task token. return } task := &model.Task{}