From d0fa4f7d8dadec7a9aa70a1186586649417d46ef Mon Sep 17 00:00:00 2001 From: Easton Crupper <65553218+ecrupper@users.noreply.github.com> Date: Thu, 5 Sep 2024 14:28:59 -0400 Subject: [PATCH 1/2] enhance(mock): add current user responses (#1177) --- mock/server/server.go | 2 ++ mock/server/user.go | 24 +++++++++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/mock/server/server.go b/mock/server/server.go index 8a2532a86..62cdc47fc 100644 --- a/mock/server/server.go +++ b/mock/server/server.go @@ -125,6 +125,8 @@ func FakeHandler() http.Handler { e.POST("/api/v1/users", addUser) e.PUT("/api/v1/users/:user", updateUser) e.DELETE("/api/v1/users/:user", removeUser) + e.GET("/api/v1/user", currentUser) + e.PUT("/api/v1/user", currentUser) // mock endpoints for worker calls e.GET("/api/v1/workers", getWorkers) diff --git a/mock/server/user.go b/mock/server/user.go index 54fbedd64..9df1ef087 100644 --- a/mock/server/user.go +++ b/mock/server/user.go @@ -1,6 +1,5 @@ // SPDX-License-Identifier: Apache-2.0 -//nolint:dupl // ignore duplicate with user code package server import ( @@ -12,6 +11,7 @@ import ( "github.com/gin-gonic/gin" api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/router/middleware/auth" "github.com/go-vela/types" ) @@ -82,6 +82,28 @@ func getUser(c *gin.Context) { c.JSON(http.StatusOK, body) } +// currentUser returns mock JSON for a http GET and http PUT. +// +// Pass "invalid" to auth header to test receiving 401 response. +func currentUser(c *gin.Context) { + tkn, _ := auth.RetrieveAccessToken(c.Request) + + if strings.Contains(tkn, "invalid") { + msg := "unauthorized" + + c.AbortWithStatusJSON(http.StatusUnauthorized, types.Error{Message: &msg}) + + return + } + + data := []byte(UserResp) + + var body api.User + _ = json.Unmarshal(data, &body) + + c.JSON(http.StatusOK, body) +} + // addUser returns mock JSON for a http POST. func addUser(c *gin.Context) { data := []byte(UserResp) From b998ec11d9ad140a0748e311d7939c6adea02b8e Mon Sep 17 00:00:00 2001 From: Jordan Sussman Date: Thu, 5 Sep 2024 16:32:49 -0500 Subject: [PATCH 2/2] enhance(oidc): add more claims (#1172) --- api/oi_config.go | 3 ++ api/types/oidc.go | 15 +++++---- internal/token/mint.go | 32 ++++++++++++++++-- internal/token/mint_test.go | 61 +++++++++++++++++++++++++++++++++++ mock/server/authentication.go | 3 ++ 5 files changed, 105 insertions(+), 9 deletions(-) create mode 100644 internal/token/mint_test.go diff --git a/api/oi_config.go b/api/oi_config.go index ec2371c99..9d58a3a8b 100644 --- a/api/oi_config.go +++ b/api/oi_config.go @@ -46,6 +46,7 @@ func GetOpenIDConfig(c *gin.Context) { "iat", "iss", "aud", + "branch", "build_number", "build_id", "repo", @@ -54,6 +55,8 @@ func GetOpenIDConfig(c *gin.Context) { "actor_scm_id", "commands", "image", + "image_name", + "image_tag", "request", "event", "sha", diff --git a/api/types/oidc.go b/api/types/oidc.go index e472c3a42..b4562a9bd 100644 --- a/api/types/oidc.go +++ b/api/types/oidc.go @@ -22,18 +22,21 @@ type OpenIDConfig struct { // OpenIDClaims struct is an extension of the JWT standard claims. It // includes information relevant to OIDC services. type OpenIDClaims struct { - BuildNumber int `json:"build_number,omitempty"` - BuildID int64 `json:"build_id,omitempty"` Actor string `json:"actor,omitempty"` ActorSCMID string `json:"actor_scm_id,omitempty"` - Repo string `json:"repo,omitempty"` - TokenType string `json:"token_type,omitempty"` - Image string `json:"image,omitempty"` - Request string `json:"request,omitempty"` + Branch string `json:"branch,omitempty"` + BuildID int64 `json:"build_id,omitempty"` + BuildNumber int `json:"build_number,omitempty"` Commands bool `json:"commands,omitempty"` Event string `json:"event,omitempty"` + Image string `json:"image,omitempty"` + ImageName string `json:"image_name,omitempty"` + ImageTag string `json:"image_tag,omitempty"` Ref string `json:"ref,omitempty"` + Repo string `json:"repo,omitempty"` + Request string `json:"request,omitempty"` SHA string `json:"sha,omitempty"` + TokenType string `json:"token_type,omitempty"` jwt.RegisteredClaims } diff --git a/internal/token/mint.go b/internal/token/mint.go index c86757b07..4ab6ed4f7 100644 --- a/internal/token/mint.go +++ b/internal/token/mint.go @@ -6,6 +6,7 @@ import ( "context" "errors" "fmt" + "strings" "time" "github.com/golang-jwt/jwt/v5" @@ -120,7 +121,7 @@ func (tm *Manager) MintToken(mto *MintTokenOpts) (string, error) { tk := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - //sign token with configured private signing key + // sign token with configured private signing key token, err := tk.SignedString([]byte(tm.PrivateKeyHMAC)) if err != nil { return "", fmt.Errorf("unable to sign token: %w", err) @@ -134,6 +135,8 @@ func (tm *Manager) MintIDToken(ctx context.Context, mto *MintTokenOpts, db datab // initialize claims struct var claims = new(api.OpenIDClaims) + var err error + // validate provided claims if len(mto.Repo) == 0 { return "", errors.New("missing repo for ID token") @@ -154,6 +157,7 @@ func (tm *Manager) MintIDToken(ctx context.Context, mto *MintTokenOpts, db datab // set claims based on input claims.Actor = mto.Build.GetSender() claims.ActorSCMID = mto.Build.GetSenderSCMID() + claims.Branch = mto.Build.GetBranch() claims.BuildNumber = mto.Build.GetNumber() claims.BuildID = mto.Build.GetID() claims.Repo = mto.Repo @@ -164,6 +168,12 @@ func (tm *Manager) MintIDToken(ctx context.Context, mto *MintTokenOpts, db datab claims.Audience = mto.Audience claims.TokenType = mto.TokenType claims.Image = mto.Image + + claims.ImageName, claims.ImageTag, err = imageParse(mto.Image) + if err != nil { + return "", err + } + claims.Request = mto.Request claims.Commands = mto.Commands @@ -175,7 +185,7 @@ func (tm *Manager) MintIDToken(ctx context.Context, mto *MintTokenOpts, db datab tk := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) // verify key is active in the database before signing - _, err := db.GetActiveJWK(ctx, tm.RSAKeySet.KID) + _, err = db.GetActiveJWK(ctx, tm.RSAKeySet.KID) if err != nil { if !errors.Is(err, gorm.ErrRecordNotFound) { return "", fmt.Errorf("unable to get active public key: %w", err) @@ -191,7 +201,7 @@ func (tm *Manager) MintIDToken(ctx context.Context, mto *MintTokenOpts, db datab // set KID header tk.Header["kid"] = tm.RSAKeySet.KID - //sign token with configured private signing key + // sign token with configured private signing key token, err := tk.SignedString(tm.RSAKeySet.PrivateKey) if err != nil { return "", fmt.Errorf("unable to sign token: %w", err) @@ -201,3 +211,19 @@ func (tm *Manager) MintIDToken(ctx context.Context, mto *MintTokenOpts, db datab return token, nil } + +// imageParse parses the given image string and returns the image name and tag. +// If no tag is provided in the image string, "latest" is used as the tag. +// If the image string is invalid, an error is returned. +func imageParse(image string) (string, string, error) { + parts := strings.Split(image, ":") + + switch len(parts) { + case 1: + return image, "latest", nil + case 2: + return parts[0], parts[1], nil + default: + return "", "", fmt.Errorf("invalid image format: %s", image) + } +} diff --git a/internal/token/mint_test.go b/internal/token/mint_test.go new file mode 100644 index 000000000..7ec01bc34 --- /dev/null +++ b/internal/token/mint_test.go @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: Apache-2.0 + +package token + +import "testing" + +func Test_imageParse(t *testing.T) { + type args struct { + image string + } + tests := []struct { + name string + args args + wantName string + wantTag string + wantErr bool + }{ + { + name: "image with tag", + args: args{ + image: "alpine:1.20", + }, + wantName: "alpine", + wantTag: "1.20", + wantErr: false, + }, + { + name: "image without latest tag", + args: args{ + image: "alpine:latest", + }, + wantName: "alpine", + wantTag: "latest", + wantErr: false, + }, + { + name: "image without tag", + args: args{ + image: "alpine", + }, + wantName: "alpine", + wantTag: "latest", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, got1, err := imageParse(tt.args.image) + if (err != nil) != tt.wantErr { + t.Errorf("imageParse() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.wantName { + t.Errorf("imageParse() got = %v, wantName %v", got, tt.wantName) + } + if got1 != tt.wantTag { + t.Errorf("imageParse() got1 = %v, wantName %v", got1, tt.wantTag) + } + }) + } +} diff --git a/mock/server/authentication.go b/mock/server/authentication.go index b1903eeb9..1d8ed2147 100644 --- a/mock/server/authentication.go +++ b/mock/server/authentication.go @@ -32,6 +32,7 @@ const ( "iat", "iss", "aud", + "branch", "build_number", "build_id", "repo", @@ -40,6 +41,8 @@ const ( "actor_scm_id", "commands", "image", + "image_name", + "image_tag", "request" ], "id_token_signing_alg_values_supported": [