From b339c4cdd00397d186f272f01004f7010464a316 Mon Sep 17 00:00:00 2001 From: ecrupper Date: Tue, 28 Feb 2023 10:38:40 -0600 Subject: [PATCH 01/21] initial work --- api/admin/worker.go | 68 +++++++++++++++ api/build.go | 20 ++++- api/token.go | 18 ++++ api/worker.go | 68 ++++++++++++--- cmd/vela-server/main.go | 12 +++ cmd/vela-server/token.go | 12 +-- internal/token/manager.go | 6 ++ internal/token/mint.go | 7 ++ router/admin.go | 3 + router/build.go | 2 +- router/middleware/claims/claims.go | 17 +--- router/middleware/claims/claims_test.go | 67 ++++++++------- router/middleware/executors/executors.go | 23 +++++- router/middleware/perm/perm.go | 56 ++++++++++--- router/middleware/perm/perm_test.go | 101 +++++++++++++++++------ router/router.go | 3 + router/worker.go | 4 +- 17 files changed, 379 insertions(+), 108 deletions(-) create mode 100644 api/admin/worker.go diff --git a/api/admin/worker.go b/api/admin/worker.go new file mode 100644 index 000000000..7c2f2b301 --- /dev/null +++ b/api/admin/worker.go @@ -0,0 +1,68 @@ +// Copyright (c) 2023 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package admin + +import ( + "fmt" + "net/http" + + "github.com/go-vela/server/internal/token" + "github.com/go-vela/server/util" + "github.com/go-vela/types/library" + + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" +) + +// swagger:operation GET /api/v1/admin/workers/{worker}/register-token admin RegisterToken +// +// Get a worker registration token +// +// --- +// produces: +// - application/json +// parameters: +// - in: path +// name: worker +// description: Hostname of the worker +// required: true +// type: string +// security: +// - ApiKeyAuth: [] +// responses: +// '200': +// description: Successfully generated registration token +// schema: +// "$ref": "#/definitions/Token" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" + +// RegisterToken represents the API handler to +// generate a registration token for onboarding a worker. +func RegisterToken(c *gin.Context) { + logrus.Info("Admin: generating registration token") + + host := util.PathParameter(c, "worker") + + tm := c.MustGet("token-manager").(*token.Manager) + rmto := &token.MintTokenOpts{ + Hostname: host, + TokenType: "WorkerRegister", + TokenDuration: tm.WorkerRegisterTokenDuration, + } + + rt, err := tm.MintToken(rmto) + if err != nil { + retErr := fmt.Errorf("unable to generate registration token: %w", err) + + util.HandleError(c, http.StatusInternalServerError, retErr) + + return + } + + c.JSON(http.StatusOK, library.Token{Token: &rt}) +} diff --git a/api/build.go b/api/build.go index 5be0c08d6..ba87c66e7 100644 --- a/api/build.go +++ b/api/build.go @@ -1764,8 +1764,26 @@ func CancelBuild(c *gin.Context) { return } + tm := c.MustGet("token-manager").(*token.Manager) + + // set mint token options + mto := &token.MintTokenOpts{ + Hostname: "vela-server", + TokenType: "WorkerAuth", + TokenDuration: time.Minute * 1, + } + + // mint token + tkn, err := tm.MintToken(mto) + if err != nil { + retErr := fmt.Errorf("unable to generate auth token: %w", err) + util.HandleError(c, http.StatusInternalServerError, retErr) + + return + } + // add the token to authenticate to the worker - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", c.MustGet("secret").(string))) + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tkn)) // perform the request to the worker resp, err := client.Do(req) diff --git a/api/token.go b/api/token.go index 87a49288a..95be33e92 100644 --- a/api/token.go +++ b/api/token.go @@ -7,9 +7,11 @@ package api import ( "fmt" "net/http" + "strings" "github.com/go-vela/server/internal/token" "github.com/go-vela/server/router/middleware/auth" + "github.com/go-vela/server/router/middleware/claims" "github.com/go-vela/server/util" "github.com/go-vela/types/library" @@ -65,3 +67,19 @@ func RefreshAccessToken(c *gin.Context) { c.JSON(http.StatusOK, library.Token{Token: &newAccessToken}) } + +// ValidateServerToken will return the claims of a valid server token +// if it is provided in the auth header. +func ValidateServerToken(c *gin.Context) { + cl := claims.Retrieve(c) + + if !strings.EqualFold(cl.Subject, "vela-server") { + retErr := fmt.Errorf("token is not a valid server token") + + util.HandleError(c, http.StatusUnauthorized, retErr) + + return + } + + c.JSON(http.StatusOK, cl) +} diff --git a/api/worker.go b/api/worker.go index 4951b182a..a243ecf9b 100644 --- a/api/worker.go +++ b/api/worker.go @@ -8,7 +8,10 @@ import ( "fmt" "net/http" + "github.com/go-vela/server/internal/token" + "github.com/go-vela/server/router/middleware/claims" "github.com/go-vela/server/router/middleware/user" + "github.com/go-vela/types/constants" "github.com/go-vela/server/database" "github.com/go-vela/server/router/middleware/worker" @@ -55,6 +58,7 @@ import ( func CreateWorker(c *gin.Context) { // capture middleware values u := user.Retrieve(c) + cl := claims.Retrieve(c) // capture body from API request input := new(library.Worker) @@ -85,7 +89,28 @@ func CreateWorker(c *gin.Context) { return } - c.JSON(http.StatusCreated, fmt.Sprintf("worker %s created", input.GetHostname())) + tm := c.MustGet("token-manager").(*token.Manager) + + wmto := &token.MintTokenOpts{ + TokenType: "WorkerAuth", + TokenDuration: tm.WorkerAuthTokenDuration, + Hostname: cl.Subject, + } + + tkn := new(library.Token) + + wt, err := tm.MintToken(wmto) + if err != nil { + retErr := fmt.Errorf("unable to generate auth token for worker %s: %w", input.GetHostname(), err) + + util.HandleError(c, http.StatusInternalServerError, retErr) + + return + } + + tkn.SetToken(wt) + + c.JSON(http.StatusCreated, tkn) } // swagger:operation GET /api/v1/workers workers GetWorkers @@ -231,6 +256,7 @@ func UpdateWorker(c *gin.Context) { // capture middleware values u := user.Retrieve(c) w := worker.Retrieve(c) + cl := claims.Retrieve(c) // update engine logger with API metadata // @@ -272,20 +298,40 @@ func UpdateWorker(c *gin.Context) { w.SetLastCheckedIn(input.GetLastCheckedIn()) } - // send API call to update the worker - err = database.FromContext(c).UpdateWorker(w) - if err != nil { - retErr := fmt.Errorf("unable to update worker %s: %w", w.GetHostname(), err) + // send API call to capture the updated worker + w, _ = database.FromContext(c).GetWorkerForHostname(w.GetHostname()) + switch cl.TokenType { + case constants.UserAccessTokenType: + c.JSON(http.StatusOK, w) + default: + tm := c.MustGet("token-manager").(*token.Manager) - util.HandleError(c, http.StatusInternalServerError, retErr) + wmto := &token.MintTokenOpts{ + TokenType: "WorkerAuth", + TokenDuration: tm.WorkerAuthTokenDuration, + Hostname: cl.Subject, + } - return - } + tkn := new(library.Token) - // send API call to capture the updated worker - w, _ = database.FromContext(c).GetWorkerForHostname(w.GetHostname()) + wt, err := tm.MintToken(wmto) + if err != nil { + retErr := fmt.Errorf("unable to generate auth token for worker %s: %w", w.GetHostname(), err) - c.JSON(http.StatusOK, w) + util.HandleError(c, http.StatusInternalServerError, retErr) + + return + } + + tkn.SetToken(wt) + + type WorkerCheckIn struct { + Worker *library.Worker + Token *library.Token + } + + c.JSON(http.StatusOK, WorkerCheckIn{Worker: w, Token: tkn}) + } } // swagger:operation DELETE /api/v1/workers/{worker} workers DeleteWorker diff --git a/cmd/vela-server/main.go b/cmd/vela-server/main.go index fd1ebfedf..0dff41473 100644 --- a/cmd/vela-server/main.go +++ b/cmd/vela-server/main.go @@ -147,6 +147,18 @@ func main() { Usage: "sets the duration of the buffer for build token expiration based on repo build timeout", Value: 5 * time.Minute, }, + &cli.DurationFlag{ + EnvVars: []string{"VELA_WORKER_AUTH_TOKEN_DURATION", "WORKER_AUTH_TOKEN_DURATION"}, + Name: "worker-auth-token-duration", + Usage: "sets the duration of the worker auth token", + Value: 20 * time.Minute, + }, + &cli.DurationFlag{ + EnvVars: []string{"VELA_WORKER_REGISTER_TOKEN_DURATION", "WORKER_REGISTER_TOKEN_DURATION"}, + Name: "worker-register-token-duration", + Usage: "sets the duration of the worker register token", + Value: 1 * time.Minute, + }, // Compiler Flags &cli.BoolFlag{ EnvVars: []string{"VELA_COMPILER_GITHUB", "COMPILER_GITHUB"}, diff --git a/cmd/vela-server/token.go b/cmd/vela-server/token.go index eae471373..2f406a16b 100644 --- a/cmd/vela-server/token.go +++ b/cmd/vela-server/token.go @@ -19,11 +19,13 @@ func setupTokenManager(c *cli.Context) *token.Manager { logrus.Debug("Creating token manager from CLI configuration") tm := &token.Manager{ - PrivateKey: c.String("vela-server-private-key"), - SignMethod: jwt.SigningMethodHS256, - UserAccessTokenDuration: c.Duration("user-access-token-duration"), - UserRefreshTokenDuration: c.Duration("user-refresh-token-duration"), - BuildTokenBufferDuration: c.Duration("build-token-buffer-duration"), + PrivateKey: c.String("vela-server-private-key"), + SignMethod: jwt.SigningMethodHS256, + UserAccessTokenDuration: c.Duration("user-access-token-duration"), + UserRefreshTokenDuration: c.Duration("user-refresh-token-duration"), + BuildTokenBufferDuration: c.Duration("build-token-buffer-duration"), + WorkerAuthTokenDuration: c.Duration("worker-auth-token-duration"), + WorkerRegisterTokenDuration: c.Duration("worker-register-token-duration"), } return tm diff --git a/internal/token/manager.go b/internal/token/manager.go index 121e4fa0f..71d82fb49 100644 --- a/internal/token/manager.go +++ b/internal/token/manager.go @@ -25,4 +25,10 @@ type Manager struct { // BuildTokenBufferDuration specifies the additional token duration of build tokens beyond repo timeout BuildTokenBufferDuration time.Duration + + // WorkerAuthTokenDuration specifies the token duration for worker auth (check in) + WorkerAuthTokenDuration time.Duration + + // WorkerRegisterTokenDuration specifies the token duration for worker register + WorkerRegisterTokenDuration time.Duration } diff --git a/internal/token/mint.go b/internal/token/mint.go index e90c0c34f..0adf0278f 100644 --- a/internal/token/mint.go +++ b/internal/token/mint.go @@ -69,6 +69,13 @@ func (tm *Manager) MintToken(mto *MintTokenOpts) (string, error) { claims.Repo = mto.Repo claims.Subject = mto.Hostname + case "WorkerAuth", "WorkerRegister": + if len(mto.Hostname) == 0 { + return "", fmt.Errorf("missing host name for %s token", mto.TokenType) + } + + claims.Subject = mto.Hostname + default: return "", errors.New("invalid token type") } diff --git a/router/admin.go b/router/admin.go index f3dbb680f..a92d25aae 100644 --- a/router/admin.go +++ b/router/admin.go @@ -53,5 +53,8 @@ func AdminHandlers(base *gin.RouterGroup) { // Admin user endpoint _admin.PUT("/user", admin.UpdateUser) + + // Admin worker endpoint + _admin.GET("/workers/:worker/register-token", admin.RegisterToken) } // end of admin endpoints } diff --git a/router/build.go b/router/build.go index 3abf130d9..c2e34e188 100644 --- a/router/build.go +++ b/router/build.go @@ -58,7 +58,7 @@ func BuildHandlers(base *gin.RouterGroup) { build.DELETE("", perm.MustPlatformAdmin(), api.DeleteBuild) build.DELETE("/cancel", executors.Establish(), perm.MustWrite(), api.CancelBuild) build.GET("/logs", perm.MustRead(), api.GetBuildLogs) - build.GET("/token", perm.MustWorker(), api.GetBuildToken) + build.GET("/token", perm.MustWorkerAuthToken(), api.GetBuildToken) // Service endpoints // * Log endpoints diff --git a/router/middleware/claims/claims.go b/router/middleware/claims/claims.go index bc0726c7a..f50a950b4 100644 --- a/router/middleware/claims/claims.go +++ b/router/middleware/claims/claims.go @@ -6,12 +6,10 @@ package claims import ( "net/http" - "strings" "github.com/go-vela/server/internal/token" "github.com/go-vela/server/router/middleware/auth" "github.com/go-vela/server/util" - "github.com/go-vela/types/constants" "github.com/gin-gonic/gin" ) @@ -24,8 +22,6 @@ func Retrieve(c *gin.Context) *token.Claims { // Establish sets the claims in the given context. func Establish() gin.HandlerFunc { return func(c *gin.Context) { - claims := new(token.Claims) - tm := c.MustGet("token-manager").(*token.Manager) // get the access token from the request at, err := auth.RetrieveAccessToken(c.Request) @@ -34,19 +30,8 @@ func Establish() gin.HandlerFunc { return } - // special handling for workers - secret := c.MustGet("secret").(string) - if strings.EqualFold(at, secret) { - claims.Subject = "vela-worker" - claims.TokenType = constants.ServerWorkerTokenType - ToContext(c, claims) - c.Next() - - return - } - // parse and validate the token and return the associated the user - claims, err = tm.ParseToken(at) + claims, err := tm.ParseToken(at) if err != nil { util.HandleError(c, http.StatusUnauthorized, err) return diff --git a/router/middleware/claims/claims_test.go b/router/middleware/claims/claims_test.go index 239c97296..14e306dcc 100644 --- a/router/middleware/claims/claims_test.go +++ b/router/middleware/claims/claims_test.go @@ -9,7 +9,6 @@ import ( "net/http" "net/http/httptest" "reflect" - "strings" "testing" "time" @@ -65,10 +64,12 @@ func TestClaims_Establish(t *testing.T) { user.SetFavorites([]string{}) tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, - UserAccessTokenDuration: time.Minute * 5, - UserRefreshTokenDuration: time.Minute * 30, + PrivateKey: "123abc", + SignMethod: jwt.SigningMethodHS256, + UserAccessTokenDuration: time.Minute * 5, + UserRefreshTokenDuration: time.Minute * 30, + WorkerAuthTokenDuration: time.Minute * 20, + WorkerRegisterTokenDuration: time.Minute * 1, } now := time.Now() @@ -123,29 +124,43 @@ func TestClaims_Establish(t *testing.T) { Endpoint: "repos/:org/:repo/builds/:build", }, { - TokenType: constants.ServerWorkerTokenType, + TokenType: "WorkerAuth", WantClaims: &token.Claims{ - TokenType: constants.ServerWorkerTokenType, + TokenType: "WorkerAuth", RegisteredClaims: jwt.RegisteredClaims{ - Subject: "vela-worker", + Subject: "host", + IssuedAt: jwt.NewNumericDate(now), + ExpiresAt: jwt.NewNumericDate(now.Add(tm.WorkerAuthTokenDuration)), }, }, - CtxRequest: "/repos/foo/bar/builds/1", - Endpoint: "repos/:org/:repo/builds/:build", + Mto: &token.MintTokenOpts{ + Hostname: "host", + TokenDuration: tm.WorkerAuthTokenDuration, + TokenType: "WorkerAuth", + }, + CtxRequest: "/workers/host", + Endpoint: "/workers/:hostname", + }, + { + TokenType: "WorkerRegister", + WantClaims: &token.Claims{ + TokenType: "WorkerRegister", + RegisteredClaims: jwt.RegisteredClaims{ + Subject: "host", + IssuedAt: jwt.NewNumericDate(now), + ExpiresAt: jwt.NewNumericDate(now.Add(tm.WorkerRegisterTokenDuration)), + }, + }, + Mto: &token.MintTokenOpts{ + Hostname: "host", + TokenDuration: tm.WorkerRegisterTokenDuration, + TokenType: "WorkerRegister", + }, + CtxRequest: "/workers/host/register", + Endpoint: "workers/:hostname/register", }, } - // setup database - db, _ := sqlite.NewTest() - - defer func() { - db.Sqlite.Exec("delete from users;") - _sql, _ := db.Sqlite.DB() - _sql.Close() - }() - - _ = db.CreateUser(user) - got := new(token.Claims) gin.SetMode(gin.TestMode) @@ -156,13 +171,7 @@ func TestClaims_Establish(t *testing.T) { context, engine := gin.CreateTestContext(resp) context.Request, _ = http.NewRequest(http.MethodPut, tt.CtxRequest, nil) - var tkn string - - if strings.EqualFold(tt.TokenType, constants.ServerWorkerTokenType) { - tkn = "very-secret" - } else { - tkn, _ = tm.MintToken(tt.Mto) - } + tkn, _ := tm.MintToken(tt.Mto) context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tkn)) @@ -171,8 +180,6 @@ func TestClaims_Establish(t *testing.T) { // setup vela mock server engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) - engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) - engine.Use(func(c *gin.Context) { c.Set("secret", "very-secret") }) engine.Use(Establish()) engine.PUT(tt.Endpoint, func(c *gin.Context) { got = Retrieve(c) diff --git a/router/middleware/executors/executors.go b/router/middleware/executors/executors.go index 96155eacb..317475eae 100644 --- a/router/middleware/executors/executors.go +++ b/router/middleware/executors/executors.go @@ -14,6 +14,7 @@ import ( "github.com/gin-gonic/gin" "github.com/go-vela/server/database" + "github.com/go-vela/server/internal/token" "github.com/go-vela/server/router/middleware/build" "github.com/go-vela/server/util" "github.com/go-vela/types/library" @@ -51,8 +52,26 @@ func Establish() gin.HandlerFunc { return } - // add the token to authenticate to the worker as a header - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", c.MustGet("secret").(string))) + tm := c.MustGet("token-manager").(*token.Manager) + + // set mint token options + mto := &token.MintTokenOpts{ + Hostname: "vela-server", + TokenType: "WorkerAuth", + TokenDuration: time.Minute * 1, + } + + // mint token + tkn, err := tm.MintToken(mto) + if err != nil { + retErr := fmt.Errorf("unable to generate auth token: %w", err) + util.HandleError(c, http.StatusInternalServerError, retErr) + + return + } + + // add the token to authenticate to the worker + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tkn)) // make the request to the worker and check the response resp, err := client.Do(req) diff --git a/router/middleware/perm/perm.go b/router/middleware/perm/perm.go index 560753ad8..87f0aa989 100644 --- a/router/middleware/perm/perm.go +++ b/router/middleware/perm/perm.go @@ -55,34 +55,64 @@ func MustPlatformAdmin() gin.HandlerFunc { } } -// MustWorker ensures the request is coming from an agent. -func MustWorker() gin.HandlerFunc { +// MustWorkerRegisterToken ensures the token is a registration token retrieved by a platform admin. +// +//nolint:dupl // ignore duplicate with worker auth +func MustWorkerRegisterToken() gin.HandlerFunc { return func(c *gin.Context) { cl := claims.Retrieve(c) - // global permissions bypass - if cl.IsAdmin { - logrus.WithFields(logrus.Fields{ - "user": cl.Subject, - }).Debugf("user %s has platform admin permissions", cl.Subject) + // update engine logger with API metadata + // + // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields + logrus.WithFields(logrus.Fields{ + "user": cl.Subject, + }).Debugf("verifying user %s has a registration token for worker", cl.Subject) + + switch cl.TokenType { + case "WorkerRegister": + return + + default: + retErr := fmt.Errorf("invalid token type: must provide a worker registration token") + util.HandleError(c, http.StatusUnauthorized, retErr) return } + } +} + +// MustWorkerAuthToken ensures the token is a worker auth token. +// +//nolint:dupl // ignore duplicate with worker registration +func MustWorkerAuthToken() gin.HandlerFunc { + return func(c *gin.Context) { + cl := claims.Retrieve(c) + u := user.Retrieve(c) // update engine logger with API metadata // // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields logrus.WithFields(logrus.Fields{ - "subject": cl.Subject, - }).Debugf("verifying user %s is a worker", cl.Subject) + "worker": cl.Subject, + }).Debugf("verifying worker %s has a valid auth token", cl.Subject) - // validate claims as worker - switch { - case (strings.EqualFold(cl.Subject, "vela-worker") && strings.EqualFold(cl.TokenType, constants.ServerWorkerTokenType)): + switch cl.TokenType { + case "WorkerAuth", "WorkerRegister": return + case constants.UserAccessTokenType: + if u.GetAdmin() { + logrus.WithFields(logrus.Fields{ + "user": cl.Subject, + }).Debugf("user %s has platform admin permissions", cl.Subject) + return + } + retErr := fmt.Errorf("user %s does not have platform admin permissions", cl.Subject) + util.HandleError(c, http.StatusUnauthorized, retErr) + return default: - retErr := fmt.Errorf("user %s is not a worker", cl.Subject) + retErr := fmt.Errorf("invalid token type: must provide a worker auth token") util.HandleError(c, http.StatusUnauthorized, retErr) return diff --git a/router/middleware/perm/perm_test.go b/router/middleware/perm/perm_test.go index a47dcef40..025119a1b 100644 --- a/router/middleware/perm/perm_test.go +++ b/router/middleware/perm/perm_test.go @@ -188,17 +188,25 @@ func TestPerm_MustPlatformAdmin_NotAdmin(t *testing.T) { } } -func TestPerm_MustWorker(t *testing.T) { +func TestPerm_MustWorkerRegisterToken(t *testing.T) { // setup types - secret := "superSecret" - tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, - UserAccessTokenDuration: time.Minute * 5, - UserRefreshTokenDuration: time.Minute * 30, + PrivateKey: "123abc", + SignMethod: jwt.SigningMethodHS256, + UserAccessTokenDuration: time.Minute * 5, + UserRefreshTokenDuration: time.Minute * 30, + WorkerRegisterTokenDuration: time.Minute * 1, + WorkerAuthTokenDuration: time.Minute * 15, + } + + mto := &token.MintTokenOpts{ + Hostname: "worker", + TokenDuration: tm.WorkerRegisterTokenDuration, + TokenType: "WorkerRegister", } + tok, _ := tm.MintToken(mto) + // setup context gin.SetMode(gin.TestMode) @@ -206,14 +214,13 @@ func TestPerm_MustWorker(t *testing.T) { context, engine := gin.CreateTestContext(resp) context.Request, _ = http.NewRequest(http.MethodGet, "/test/foo/bar", nil) - context.Request.Header.Add("Authorization", fmt.Sprint(secret)) + context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tok)) // setup vela mock server - engine.Use(func(c *gin.Context) { c.Set("secret", secret) }) engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) engine.Use(claims.Establish()) engine.Use(user.Establish()) - engine.Use(MustWorker()) + engine.Use(MustWorkerRegisterToken()) engine.GET("/test/:org/:repo", func(c *gin.Context) { c.Status(http.StatusOK) }) @@ -225,14 +232,11 @@ func TestPerm_MustWorker(t *testing.T) { engine.ServeHTTP(context.Writer, context.Request) if resp.Code != http.StatusOK { - t.Errorf("MustWorker returned %v, want %v", resp.Code, http.StatusOK) + t.Errorf("MustWorkerRegisterToken returned %v, want %v", resp.Code, http.StatusOK) } } -func TestPerm_MustWorker_PlatAdmin(t *testing.T) { - // setup types - secret := "superSecret" - +func TestPerm_MustWorkerRegisterToken_PlatAdmin(t *testing.T) { tm := &token.Manager{ PrivateKey: "123abc", SignMethod: jwt.SigningMethodHS256, @@ -276,12 +280,11 @@ func TestPerm_MustWorker_PlatAdmin(t *testing.T) { context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tok)) // setup vela mock server - engine.Use(func(c *gin.Context) { c.Set("secret", secret) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) engine.Use(claims.Establish()) engine.Use(user.Establish()) - engine.Use(MustWorker()) + engine.Use(MustWorkerRegisterToken()) engine.GET("/test/:org/:repo", func(c *gin.Context) { c.Status(http.StatusOK) }) @@ -292,15 +295,60 @@ func TestPerm_MustWorker_PlatAdmin(t *testing.T) { // run test engine.ServeHTTP(context.Writer, context.Request) - if resp.Code != http.StatusOK { - t.Errorf("MustWorker returned %v, want %v", resp.Code, http.StatusOK) + if resp.Code != http.StatusUnauthorized { + t.Errorf("MustWorkerRegisterToken returned %v, want %v", resp.Code, http.StatusUnauthorized) } } -func TestPerm_MustWorker_UserNamedVelaWorker(t *testing.T) { +func TestPerm_MustWorkerAuthToken(t *testing.T) { // setup types - secret := "superSecret" + tm := &token.Manager{ + PrivateKey: "123abc", + SignMethod: jwt.SigningMethodHS256, + UserAccessTokenDuration: time.Minute * 5, + UserRefreshTokenDuration: time.Minute * 30, + WorkerRegisterTokenDuration: time.Minute * 1, + WorkerAuthTokenDuration: time.Minute * 15, + } + + mto := &token.MintTokenOpts{ + Hostname: "worker", + TokenDuration: tm.WorkerAuthTokenDuration, + TokenType: "WorkerAuth", + } + + tok, _ := tm.MintToken(mto) + + // setup context + gin.SetMode(gin.TestMode) + resp := httptest.NewRecorder() + context, engine := gin.CreateTestContext(resp) + + context.Request, _ = http.NewRequest(http.MethodGet, "/test/foo/bar", nil) + context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tok)) + + // setup vela mock server + engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) + engine.Use(claims.Establish()) + engine.Use(user.Establish()) + engine.Use(MustWorkerAuthToken()) + engine.GET("/test/:org/:repo", func(c *gin.Context) { + c.Status(http.StatusOK) + }) + + s1 := httptest.NewServer(engine) + defer s1.Close() + + // run test + engine.ServeHTTP(context.Writer, context.Request) + + if resp.Code != http.StatusOK { + t.Errorf("MustWorkerAuthToken returned %v, want %v", resp.Code, http.StatusOK) + } +} + +func TestPerm_MustWorkerAuth_PlatAdmin(t *testing.T) { tm := &token.Manager{ PrivateKey: "123abc", SignMethod: jwt.SigningMethodHS256, @@ -313,7 +361,7 @@ func TestPerm_MustWorker_UserNamedVelaWorker(t *testing.T) { u.SetName("vela-worker") u.SetToken("bar") u.SetHash("baz") - u.SetAdmin(false) + u.SetAdmin(true) mto := &token.MintTokenOpts{ User: u, @@ -344,12 +392,11 @@ func TestPerm_MustWorker_UserNamedVelaWorker(t *testing.T) { context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tok)) // setup vela mock server - engine.Use(func(c *gin.Context) { c.Set("secret", secret) }) - engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) + engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) engine.Use(claims.Establish()) engine.Use(user.Establish()) - engine.Use(MustWorker()) + engine.Use(MustWorkerAuthToken()) engine.GET("/test/:org/:repo", func(c *gin.Context) { c.Status(http.StatusOK) }) @@ -360,8 +407,8 @@ func TestPerm_MustWorker_UserNamedVelaWorker(t *testing.T) { // run test engine.ServeHTTP(context.Writer, context.Request) - if resp.Code != http.StatusUnauthorized { - t.Errorf("MustWorker returned %v, want %v", resp.Code, http.StatusUnauthorized) + if resp.Code != http.StatusOK { + t.Errorf("MustWorkerAuthToken returned %v, want %v", resp.Code, http.StatusOK) } } diff --git a/router/router.go b/router/router.go index 3327e4a7b..f9d1f7b24 100644 --- a/router/router.go +++ b/router/router.go @@ -77,6 +77,9 @@ func Load(options ...gin.HandlerFunc) *gin.Engine { // Metric endpoint r.GET("/metrics", api.CustomMetrics, gin.WrapH(api.BaseMetrics())) + // Validate Server Token endpoint + r.GET("/validate-token", claims.Establish(), api.ValidateServerToken) + // Version endpoint r.GET("/version", api.Version) diff --git a/router/worker.go b/router/worker.go index 7853c2a82..b642cb5bc 100644 --- a/router/worker.go +++ b/router/worker.go @@ -24,14 +24,14 @@ func WorkerHandlers(base *gin.RouterGroup) { // Workers endpoints workers := base.Group("/workers") { - workers.POST("", perm.MustWorker(), middleware.Payload(), api.CreateWorker) + workers.POST("", perm.MustWorkerRegisterToken(), middleware.Payload(), api.CreateWorker) workers.GET("", api.GetWorkers) // Worker endpoints w := workers.Group("/:worker") { w.GET("", worker.Establish(), api.GetWorker) - w.PUT("", perm.MustWorker(), worker.Establish(), api.UpdateWorker) + w.PUT("", perm.MustWorkerAuthToken(), worker.Establish(), api.UpdateWorker) w.DELETE("", perm.MustPlatformAdmin(), worker.Establish(), api.DeleteWorker) } // end of worker endpoints } // end of workers endpoints From 8bf613c7ad6f7ff7ab4a0fba97aa5eaa13b8650a Mon Sep 17 00:00:00 2001 From: ecrupper Date: Tue, 14 Mar 2023 12:44:17 -0500 Subject: [PATCH 02/21] adding symmetric token handling --- cmd/vela-server/validate.go | 4 ---- router/middleware/claims/claims.go | 18 +++++++++++++++++- router/middleware/claims/claims_test.go | 21 ++++++++++++++++++++- router/middleware/perm/perm.go | 23 +++++++++++++++++++---- 4 files changed, 56 insertions(+), 10 deletions(-) diff --git a/cmd/vela-server/validate.go b/cmd/vela-server/validate.go index 6b82e6b4e..bce8b12fb 100644 --- a/cmd/vela-server/validate.go +++ b/cmd/vela-server/validate.go @@ -52,10 +52,6 @@ func validateCore(c *cli.Context) error { return fmt.Errorf("clone-image (VELA_CLONE_IMAGE) flag is not properly configured") } - if len(c.String("vela-secret")) == 0 { - return fmt.Errorf("vela-secret (VELA_SECRET) flag is not properly configured") - } - if len(c.String("vela-server-private-key")) == 0 { return fmt.Errorf("vela-server-private-key (VELA_SERVER_PRIVATE_KEY) flag is not properly configured") } diff --git a/router/middleware/claims/claims.go b/router/middleware/claims/claims.go index f50a950b4..5ba2784f6 100644 --- a/router/middleware/claims/claims.go +++ b/router/middleware/claims/claims.go @@ -6,10 +6,12 @@ package claims import ( "net/http" + "strings" "github.com/go-vela/server/internal/token" "github.com/go-vela/server/router/middleware/auth" "github.com/go-vela/server/util" + "github.com/go-vela/types/constants" "github.com/gin-gonic/gin" ) @@ -30,8 +32,22 @@ func Establish() gin.HandlerFunc { return } + claims := new(token.Claims) + + // special handling for workers if symmetric token is provided + if secret, ok := c.Value("secret").(string); ok { + if strings.EqualFold(at, secret) { + claims.Subject = "vela-worker" + claims.TokenType = constants.ServerWorkerTokenType + ToContext(c, claims) + c.Next() + + return + } + } + // parse and validate the token and return the associated the user - claims, err := tm.ParseToken(at) + claims, err = tm.ParseToken(at) if err != nil { util.HandleError(c, http.StatusUnauthorized, err) return diff --git a/router/middleware/claims/claims_test.go b/router/middleware/claims/claims_test.go index 14e306dcc..6cbe121d4 100644 --- a/router/middleware/claims/claims_test.go +++ b/router/middleware/claims/claims_test.go @@ -9,6 +9,7 @@ import ( "net/http" "net/http/httptest" "reflect" + "strings" "testing" "time" @@ -159,6 +160,17 @@ func TestClaims_Establish(t *testing.T) { CtxRequest: "/workers/host/register", Endpoint: "workers/:hostname/register", }, + { + TokenType: constants.ServerWorkerTokenType, + WantClaims: &token.Claims{ + TokenType: constants.ServerWorkerTokenType, + RegisteredClaims: jwt.RegisteredClaims{ + Subject: "vela-worker", + }, + }, + CtxRequest: "/repos/foo/bar/builds/1", + Endpoint: "repos/:org/:repo/builds/:build", + }, } got := new(token.Claims) @@ -171,7 +183,14 @@ func TestClaims_Establish(t *testing.T) { context, engine := gin.CreateTestContext(resp) context.Request, _ = http.NewRequest(http.MethodPut, tt.CtxRequest, nil) - tkn, _ := tm.MintToken(tt.Mto) + var tkn string + + if strings.EqualFold(tt.TokenType, constants.ServerWorkerTokenType) { + tkn = "very-secret" + engine.Use(func(c *gin.Context) { c.Set("secret", "very-secret") }) + } else { + tkn, _ = tm.MintToken(tt.Mto) + } context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tkn)) diff --git a/router/middleware/perm/perm.go b/router/middleware/perm/perm.go index 2d16c0f05..73d5d0d7f 100644 --- a/router/middleware/perm/perm.go +++ b/router/middleware/perm/perm.go @@ -56,8 +56,6 @@ func MustPlatformAdmin() gin.HandlerFunc { } // MustWorkerRegisterToken ensures the token is a registration token retrieved by a platform admin. -// -//nolint:dupl // ignore duplicate with worker auth func MustWorkerRegisterToken() gin.HandlerFunc { return func(c *gin.Context) { cl := claims.Retrieve(c) @@ -72,7 +70,15 @@ func MustWorkerRegisterToken() gin.HandlerFunc { switch cl.TokenType { case "WorkerRegister": return + case constants.ServerWorkerTokenType: + if strings.EqualFold(cl.Subject, "vela-worker") { + return + } + + retErr := fmt.Errorf("server-worker token provided but does not match configuration") + util.HandleError(c, http.StatusBadRequest, retErr) + return default: retErr := fmt.Errorf("invalid token type: must provide a worker registration token") util.HandleError(c, http.StatusUnauthorized, retErr) @@ -83,8 +89,6 @@ func MustWorkerRegisterToken() gin.HandlerFunc { } // MustWorkerAuthToken ensures the token is a worker auth token. -// -//nolint:dupl // ignore duplicate with worker registration func MustWorkerAuthToken() gin.HandlerFunc { return func(c *gin.Context) { cl := claims.Retrieve(c) @@ -108,8 +112,19 @@ func MustWorkerAuthToken() gin.HandlerFunc { return } + retErr := fmt.Errorf("user %s does not have platform admin permissions", cl.Subject) util.HandleError(c, http.StatusUnauthorized, retErr) + + return + case constants.ServerWorkerTokenType: + if strings.EqualFold(cl.Subject, "vela-worker") { + return + } + + retErr := fmt.Errorf("server-worker token provided but does not match configuration") + util.HandleError(c, http.StatusBadRequest, retErr) + return default: retErr := fmt.Errorf("invalid token type: must provide a worker auth token") From c1f14639c2ebe584531612d9824d9970b4941e8a Mon Sep 17 00:00:00 2001 From: ecrupper Date: Wed, 15 Mar 2023 13:13:30 -0500 Subject: [PATCH 03/21] incorporating constants --- api/admin/worker.go | 2 +- api/build.go | 2 +- api/token.go | 19 +++++++++++++++++++ api/worker.go | 4 ++-- internal/token/mint.go | 2 +- router/middleware/claims/claims_test.go | 12 ++++++------ router/middleware/executors/executors.go | 2 +- router/middleware/perm/perm.go | 4 ++-- router/middleware/perm/perm_test.go | 4 ++-- 9 files changed, 35 insertions(+), 16 deletions(-) diff --git a/api/admin/worker.go b/api/admin/worker.go index 7c2f2b301..c4e101969 100644 --- a/api/admin/worker.go +++ b/api/admin/worker.go @@ -51,7 +51,7 @@ func RegisterToken(c *gin.Context) { tm := c.MustGet("token-manager").(*token.Manager) rmto := &token.MintTokenOpts{ Hostname: host, - TokenType: "WorkerRegister", + TokenType: constants.WorkerRegisterTokenType, TokenDuration: tm.WorkerRegisterTokenDuration, } diff --git a/api/build.go b/api/build.go index ba87c66e7..cb27dab14 100644 --- a/api/build.go +++ b/api/build.go @@ -1769,7 +1769,7 @@ func CancelBuild(c *gin.Context) { // set mint token options mto := &token.MintTokenOpts{ Hostname: "vela-server", - TokenType: "WorkerAuth", + TokenType: constants.WorkerAuthTokenType, TokenDuration: time.Minute * 1, } diff --git a/api/token.go b/api/token.go index 95be33e92..9d4a4f8c6 100644 --- a/api/token.go +++ b/api/token.go @@ -68,6 +68,25 @@ func RefreshAccessToken(c *gin.Context) { c.JSON(http.StatusOK, library.Token{Token: &newAccessToken}) } +// swagger:operation GET /validate-token authenticate ValidateServerToken +// +// Validate a server token +// +// --- +// produces: +// - application/json +// security: +// - CookieAuth: [] +// responses: +// '200': +// description: Successfully validated a token +// schema: +// type: string +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" + // ValidateServerToken will return the claims of a valid server token // if it is provided in the auth header. func ValidateServerToken(c *gin.Context) { diff --git a/api/worker.go b/api/worker.go index a243ecf9b..9a9f2f843 100644 --- a/api/worker.go +++ b/api/worker.go @@ -92,7 +92,7 @@ func CreateWorker(c *gin.Context) { tm := c.MustGet("token-manager").(*token.Manager) wmto := &token.MintTokenOpts{ - TokenType: "WorkerAuth", + TokenType: constants.WorkerAuthTokenType, TokenDuration: tm.WorkerAuthTokenDuration, Hostname: cl.Subject, } @@ -307,7 +307,7 @@ func UpdateWorker(c *gin.Context) { tm := c.MustGet("token-manager").(*token.Manager) wmto := &token.MintTokenOpts{ - TokenType: "WorkerAuth", + TokenType: constants.WorkerAuthTokenType, TokenDuration: tm.WorkerAuthTokenDuration, Hostname: cl.Subject, } diff --git a/internal/token/mint.go b/internal/token/mint.go index 0adf0278f..6f1a23941 100644 --- a/internal/token/mint.go +++ b/internal/token/mint.go @@ -69,7 +69,7 @@ func (tm *Manager) MintToken(mto *MintTokenOpts) (string, error) { claims.Repo = mto.Repo claims.Subject = mto.Hostname - case "WorkerAuth", "WorkerRegister": + case constants.WorkerAuthTokenType, constants.WorkerRegisterTokenType: if len(mto.Hostname) == 0 { return "", fmt.Errorf("missing host name for %s token", mto.TokenType) } diff --git a/router/middleware/claims/claims_test.go b/router/middleware/claims/claims_test.go index 6cbe121d4..914c5882e 100644 --- a/router/middleware/claims/claims_test.go +++ b/router/middleware/claims/claims_test.go @@ -125,9 +125,9 @@ func TestClaims_Establish(t *testing.T) { Endpoint: "repos/:org/:repo/builds/:build", }, { - TokenType: "WorkerAuth", + TokenType: constants.WorkerAuthTokenType, WantClaims: &token.Claims{ - TokenType: "WorkerAuth", + TokenType: constants.WorkerAuthTokenType, RegisteredClaims: jwt.RegisteredClaims{ Subject: "host", IssuedAt: jwt.NewNumericDate(now), @@ -137,15 +137,15 @@ func TestClaims_Establish(t *testing.T) { Mto: &token.MintTokenOpts{ Hostname: "host", TokenDuration: tm.WorkerAuthTokenDuration, - TokenType: "WorkerAuth", + TokenType: constants.WorkerAuthTokenType, }, CtxRequest: "/workers/host", Endpoint: "/workers/:hostname", }, { - TokenType: "WorkerRegister", + TokenType: constants.WorkerRegisterTokenType, WantClaims: &token.Claims{ - TokenType: "WorkerRegister", + TokenType: constants.WorkerRegisterTokenType, RegisteredClaims: jwt.RegisteredClaims{ Subject: "host", IssuedAt: jwt.NewNumericDate(now), @@ -155,7 +155,7 @@ func TestClaims_Establish(t *testing.T) { Mto: &token.MintTokenOpts{ Hostname: "host", TokenDuration: tm.WorkerRegisterTokenDuration, - TokenType: "WorkerRegister", + TokenType: constants.WorkerRegisterTokenType, }, CtxRequest: "/workers/host/register", Endpoint: "workers/:hostname/register", diff --git a/router/middleware/executors/executors.go b/router/middleware/executors/executors.go index 317475eae..7005bf658 100644 --- a/router/middleware/executors/executors.go +++ b/router/middleware/executors/executors.go @@ -57,7 +57,7 @@ func Establish() gin.HandlerFunc { // set mint token options mto := &token.MintTokenOpts{ Hostname: "vela-server", - TokenType: "WorkerAuth", + TokenType: constants.WorkerAuthTokenType, TokenDuration: time.Minute * 1, } diff --git a/router/middleware/perm/perm.go b/router/middleware/perm/perm.go index 73d5d0d7f..043639da8 100644 --- a/router/middleware/perm/perm.go +++ b/router/middleware/perm/perm.go @@ -68,7 +68,7 @@ func MustWorkerRegisterToken() gin.HandlerFunc { }).Debugf("verifying user %s has a registration token for worker", cl.Subject) switch cl.TokenType { - case "WorkerRegister": + case constants.WorkerRegisterTokenType: return case constants.ServerWorkerTokenType: if strings.EqualFold(cl.Subject, "vela-worker") { @@ -102,7 +102,7 @@ func MustWorkerAuthToken() gin.HandlerFunc { }).Debugf("verifying worker %s has a valid auth token", cl.Subject) switch cl.TokenType { - case "WorkerAuth", "WorkerRegister": + case constants.WorkerAuthTokenType, constants.WorkerRegisterTokenType: return case constants.UserAccessTokenType: if u.GetAdmin() { diff --git a/router/middleware/perm/perm_test.go b/router/middleware/perm/perm_test.go index ffc189c3e..5992e6c36 100644 --- a/router/middleware/perm/perm_test.go +++ b/router/middleware/perm/perm_test.go @@ -202,7 +202,7 @@ func TestPerm_MustWorkerRegisterToken(t *testing.T) { mto := &token.MintTokenOpts{ Hostname: "worker", TokenDuration: tm.WorkerRegisterTokenDuration, - TokenType: "WorkerRegister", + TokenType: constants.WorkerRegisterTokenType, } tok, _ := tm.MintToken(mto) @@ -314,7 +314,7 @@ func TestPerm_MustWorkerAuthToken(t *testing.T) { mto := &token.MintTokenOpts{ Hostname: "worker", TokenDuration: tm.WorkerAuthTokenDuration, - TokenType: "WorkerAuth", + TokenType: constants.WorkerAuthTokenType, } tok, _ := tm.MintToken(mto) From 1da6bdd532548498011c458bde78e535d7fc83d4 Mon Sep 17 00:00:00 2001 From: ecrupper Date: Wed, 15 Mar 2023 16:06:31 -0500 Subject: [PATCH 04/21] convert admin register token endpoint to be POST --- api/admin/worker.go | 2 +- router/admin.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/admin/worker.go b/api/admin/worker.go index c4e101969..6a0ebdf9d 100644 --- a/api/admin/worker.go +++ b/api/admin/worker.go @@ -16,7 +16,7 @@ import ( "github.com/sirupsen/logrus" ) -// swagger:operation GET /api/v1/admin/workers/{worker}/register-token admin RegisterToken +// swagger:operation POST /api/v1/admin/workers/{worker}/register-token admin RegisterToken // // Get a worker registration token // diff --git a/router/admin.go b/router/admin.go index a92d25aae..f7fddf95f 100644 --- a/router/admin.go +++ b/router/admin.go @@ -55,6 +55,6 @@ func AdminHandlers(base *gin.RouterGroup) { _admin.PUT("/user", admin.UpdateUser) // Admin worker endpoint - _admin.GET("/workers/:worker/register-token", admin.RegisterToken) + _admin.POST("/workers/:worker/register-token", admin.RegisterToken) } // end of admin endpoints } From 4d7b73557b9ae5926b5dda4d60014f3b96ada8bf Mon Sep 17 00:00:00 2001 From: ecrupper Date: Mon, 20 Mar 2023 20:57:05 -0500 Subject: [PATCH 05/21] pulling in types update --- api/admin/worker.go | 1 + go.mod | 2 +- go.sum | 4 ++-- router/middleware/executors/executors.go | 1 + 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/api/admin/worker.go b/api/admin/worker.go index 6a0ebdf9d..09d4b46b6 100644 --- a/api/admin/worker.go +++ b/api/admin/worker.go @@ -10,6 +10,7 @@ import ( "github.com/go-vela/server/internal/token" "github.com/go-vela/server/util" + "github.com/go-vela/types/constants" "github.com/go-vela/types/library" "github.com/gin-gonic/gin" diff --git a/go.mod b/go.mod index 372f560f1..01e869534 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/drone/envsubst v1.0.3 github.com/gin-gonic/gin v1.9.0 github.com/go-playground/assert/v2 v2.2.0 - github.com/go-vela/types v0.18.1 + github.com/go-vela/types v0.18.2-0.20230321015315-6c723879639c github.com/golang-jwt/jwt/v4 v4.5.0 github.com/google/go-cmp v0.5.9 github.com/google/go-github/v50 v50.1.0 diff --git a/go.sum b/go.sum index 9e3ba8725..9b8e2d8a7 100644 --- a/go.sum +++ b/go.sum @@ -148,8 +148,8 @@ github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyh github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= -github.com/go-vela/types v0.18.1 h1:V/luHLnCEaJhD1m9PZCZicIasg8Op6MCK+utkz+gQiU= -github.com/go-vela/types v0.18.1/go.mod h1:6MzMhLaXKSZ9wiJveieqnBd2+4ZMS7yv7+POGSITyS8= +github.com/go-vela/types v0.18.2-0.20230321015315-6c723879639c h1:lnCL1knUGvgZQG4YBHSs/CZnxNBfqFUBlGhyq9LO9uk= +github.com/go-vela/types v0.18.2-0.20230321015315-6c723879639c/go.mod h1:6MzMhLaXKSZ9wiJveieqnBd2+4ZMS7yv7+POGSITyS8= github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= diff --git a/router/middleware/executors/executors.go b/router/middleware/executors/executors.go index 7005bf658..145d134fb 100644 --- a/router/middleware/executors/executors.go +++ b/router/middleware/executors/executors.go @@ -17,6 +17,7 @@ import ( "github.com/go-vela/server/internal/token" "github.com/go-vela/server/router/middleware/build" "github.com/go-vela/server/util" + "github.com/go-vela/types/constants" "github.com/go-vela/types/library" ) From e620fa3ccb2ab50f203bdb268b04678d4ee9f8f8 Mon Sep 17 00:00:00 2001 From: ecrupper Date: Tue, 21 Mar 2023 10:56:51 -0500 Subject: [PATCH 06/21] add server mocks and json tags to check in resp --- api/worker.go | 4 +-- mock/server/authentication.go | 14 ++++++++ mock/server/server.go | 2 ++ mock/server/worker.go | 65 ++++++++++++++++++++++++++++++++--- 4 files changed, 79 insertions(+), 6 deletions(-) diff --git a/api/worker.go b/api/worker.go index 9a9f2f843..c317cc186 100644 --- a/api/worker.go +++ b/api/worker.go @@ -326,8 +326,8 @@ func UpdateWorker(c *gin.Context) { tkn.SetToken(wt) type WorkerCheckIn struct { - Worker *library.Worker - Token *library.Token + Worker *library.Worker `json:"worker,omitempty"` + Token *library.Token `json:"token,omitempty"` } c.JSON(http.StatusOK, WorkerCheckIn{Worker: w, Token: tkn}) diff --git a/mock/server/authentication.go b/mock/server/authentication.go index e75217565..c029e36bc 100644 --- a/mock/server/authentication.go +++ b/mock/server/authentication.go @@ -73,3 +73,17 @@ func getAuthenticateFromToken(c *gin.Context) { c.JSON(http.StatusOK, body) } + +// validateToken returns mock response for a http GET. +// +// Don't pass "Token" in header to receive an error message. Pass "0" in "Token" header to receive invalid token resp. +func validateToken(c *gin.Context) { + err := "error" + + token := c.Request.Header.Get("Token") + if len(token) == 0 { + c.AbortWithStatusJSON(http.StatusUnauthorized, types.Error{Message: &err}) + } + + c.JSON(http.StatusOK, "vela-server") +} diff --git a/mock/server/server.go b/mock/server/server.go index 1b1ebd7c7..c7f7d9571 100644 --- a/mock/server/server.go +++ b/mock/server/server.go @@ -37,6 +37,7 @@ func FakeHandler() http.Handler { e.PUT("/api/v1/admin/step", updateStep) e.GET("/api/v1/admin/users", getUsers) e.PUT("/api/v1/admin/user", updateUser) + e.POST("/api/v1/admin/workers/{worker}/register-token", registerToken) // mock endpoints for build calls e.GET("/api/v1/repos/:org/:repo/builds/:build", getBuild) @@ -134,6 +135,7 @@ func FakeHandler() http.Handler { e.GET("/token-refresh", getTokenRefresh) e.GET("/authenticate", getAuthenticate) e.POST("/authenticate/token", getAuthenticateFromToken) + e.POST("/validate-token", validateToken) return e } diff --git a/mock/server/worker.go b/mock/server/worker.go index ed79507bb..5fa8e5b10 100644 --- a/mock/server/worker.go +++ b/mock/server/worker.go @@ -31,6 +31,26 @@ const ( "last_checked_in": 1602612590 }` + // UpdateWorkerResp represents a JSON return for a single updated worker. + UpdateWorkerResp = ` + { + "worker": { + "id": 1, + "hostname": "worker_1", + "address": "http://vela:8080", + "routes": [ + "large", + "docker", + "large:docker" + ], + "active": true, + "last_checked_in": 1602612590 + }, + "token": { + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ3b3JrZXIiLCJpYXQiOjE1MTYyMzkwMjIsInRva2VuX3R5cGUiOiJXb3JrZXJBdXRoIn0.qeULIimCJlrwsE0JykNpzBmMaHUbvfk0vkyAz2eEo38" + } + }` + // WorkersResp represents a JSON return for one to many workers. WorkersResp = `[ { @@ -58,6 +78,16 @@ const ( "last_checked_in": 1602612590 } ]` + + // AddWorkerResp represents a JSON return for adding a worker. + AddWorkerResp = `{ + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ3b3JrZXIiLCJpYXQiOjE1MTYyMzkwMjIsInRva2VuX3R5cGUiOiJXb3JrZXJBdXRoIn0.qeULIimCJlrwsE0JykNpzBmMaHUbvfk0vkyAz2eEo38" + }` + + // RegisterTokenResp represents a JSON return for an admin requesting a registration token. + RegisterTokenResp = `{ + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ3b3JrZXIiLCJpYXQiOjE1MTYyMzkwMjIsInRva2VuX3R5cGUiOiJXb3JrZXJSZWdpc3RlciJ9.gEzKaZB-sDd_gFCVF5uGo2mcf3iy9CrXDTLPZ6PTsTc" + }` ) // getWorkers returns mock JSON for a http GET. @@ -92,9 +122,9 @@ func getWorker(c *gin.Context) { // addWorker returns mock JSON for a http POST. func addWorker(c *gin.Context) { - data := []byte(WorkerResp) + data := []byte(AddWorkerResp) - var body library.Worker + var body library.Token _ = json.Unmarshal(data, &body) c.JSON(http.StatusCreated, body) @@ -114,9 +144,14 @@ func updateWorker(c *gin.Context) { return } - data := []byte(WorkerResp) + data := []byte(UpdateWorkerResp) - var body library.Worker + type WorkerCheckIn struct { + Worker *library.Worker `json:"worker,omitempty"` + Token *library.Token `json:"token,omitempty"` + } + + var body WorkerCheckIn _ = json.Unmarshal(data, &body) c.JSON(http.StatusOK, body) @@ -138,3 +173,25 @@ func removeWorker(c *gin.Context) { c.JSON(http.StatusOK, fmt.Sprintf("Worker %s removed", w)) } + +// registerToken has a param :worker returns mock JSON for a http POST. +// +// Pass "0" to :worker to test receiving a http 401 response. +func registerToken(c *gin.Context) { + w := c.Param("worker") + + if strings.EqualFold(w, "0") { + msg := fmt.Sprintf("user %s is not a platform admin", w) + + c.AbortWithStatusJSON(http.StatusUnauthorized, types.Error{Message: &msg}) + + return + } + + data := []byte(RegisterTokenResp) + + var body library.Token + _ = json.Unmarshal(data, &body) + + c.JSON(http.StatusCreated, body) +} From febb17d8cb91ab17d1ca0693d5ecacd5c12efaaf Mon Sep 17 00:00:00 2001 From: ecrupper Date: Tue, 21 Mar 2023 11:08:41 -0500 Subject: [PATCH 07/21] handle symmetric token in worker endpoints --- api/worker.go | 67 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 47 insertions(+), 20 deletions(-) diff --git a/api/worker.go b/api/worker.go index c317cc186..0ad7123d0 100644 --- a/api/worker.go +++ b/api/worker.go @@ -89,28 +89,42 @@ func CreateWorker(c *gin.Context) { return } - tm := c.MustGet("token-manager").(*token.Manager) + switch cl.TokenType { + case constants.ServerWorkerTokenType: + if secret, ok := c.Value("secret").(string); ok { + tkn := new(library.Token) + tkn.SetToken(secret) + c.JSON(http.StatusOK, tkn) + } - wmto := &token.MintTokenOpts{ - TokenType: constants.WorkerAuthTokenType, - TokenDuration: tm.WorkerAuthTokenDuration, - Hostname: cl.Subject, - } + retErr := fmt.Errorf("symmetric token provided but not configured in server") + util.HandleError(c, http.StatusBadRequest, retErr) - tkn := new(library.Token) + return + default: + tm := c.MustGet("token-manager").(*token.Manager) - wt, err := tm.MintToken(wmto) - if err != nil { - retErr := fmt.Errorf("unable to generate auth token for worker %s: %w", input.GetHostname(), err) + wmto := &token.MintTokenOpts{ + TokenType: constants.WorkerAuthTokenType, + TokenDuration: tm.WorkerAuthTokenDuration, + Hostname: cl.Subject, + } - util.HandleError(c, http.StatusInternalServerError, retErr) + tkn := new(library.Token) - return - } + wt, err := tm.MintToken(wmto) + if err != nil { + retErr := fmt.Errorf("unable to generate auth token for worker %s: %w", input.GetHostname(), err) + + util.HandleError(c, http.StatusInternalServerError, retErr) - tkn.SetToken(wt) + return + } - c.JSON(http.StatusCreated, tkn) + tkn.SetToken(wt) + + c.JSON(http.StatusCreated, tkn) + } } // swagger:operation GET /api/v1/workers workers GetWorkers @@ -258,6 +272,12 @@ func UpdateWorker(c *gin.Context) { w := worker.Retrieve(c) cl := claims.Retrieve(c) + // establish check in type + type WorkerCheckIn struct { + Worker *library.Worker `json:"worker,omitempty"` + Token *library.Token `json:"token,omitempty"` + } + // update engine logger with API metadata // // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields @@ -300,9 +320,21 @@ func UpdateWorker(c *gin.Context) { // send API call to capture the updated worker w, _ = database.FromContext(c).GetWorkerForHostname(w.GetHostname()) + switch cl.TokenType { case constants.UserAccessTokenType: c.JSON(http.StatusOK, w) + case constants.ServerWorkerTokenType: + if secret, ok := c.Value("secret").(string); ok { + tkn := new(library.Token) + tkn.SetToken(secret) + c.JSON(http.StatusOK, WorkerCheckIn{Worker: w, Token: tkn}) + } + + retErr := fmt.Errorf("symmetric token provided but not configured in server") + util.HandleError(c, http.StatusBadRequest, retErr) + + return default: tm := c.MustGet("token-manager").(*token.Manager) @@ -325,11 +357,6 @@ func UpdateWorker(c *gin.Context) { tkn.SetToken(wt) - type WorkerCheckIn struct { - Worker *library.Worker `json:"worker,omitempty"` - Token *library.Token `json:"token,omitempty"` - } - c.JSON(http.StatusOK, WorkerCheckIn{Worker: w, Token: tkn}) } } From 4a12e25eebe33150b9a1af26ade48c509df917b2 Mon Sep 17 00:00:00 2001 From: ecrupper Date: Tue, 21 Mar 2023 13:23:37 -0500 Subject: [PATCH 08/21] appease linter overlord --- mock/server/worker.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mock/server/worker.go b/mock/server/worker.go index 5fa8e5b10..831da58a9 100644 --- a/mock/server/worker.go +++ b/mock/server/worker.go @@ -85,6 +85,8 @@ const ( }` // RegisterTokenResp represents a JSON return for an admin requesting a registration token. + // + //nolint:gosec // not actual credentials RegisterTokenResp = `{ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ3b3JrZXIiLCJpYXQiOjE1MTYyMzkwMjIsInRva2VuX3R5cGUiOiJXb3JrZXJSZWdpc3RlciJ9.gEzKaZB-sDd_gFCVF5uGo2mcf3iy9CrXDTLPZ6PTsTc" }` From 25118c92b0b25a2f0cf13dc2d52fe47a0140d022 Mon Sep 17 00:00:00 2001 From: ecrupper Date: Wed, 22 Mar 2023 13:40:52 -0500 Subject: [PATCH 09/21] return properly when met with server worker token --- api/worker.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/worker.go b/api/worker.go index 0ad7123d0..ab2ee4066 100644 --- a/api/worker.go +++ b/api/worker.go @@ -95,6 +95,8 @@ func CreateWorker(c *gin.Context) { tkn := new(library.Token) tkn.SetToken(secret) c.JSON(http.StatusOK, tkn) + + return } retErr := fmt.Errorf("symmetric token provided but not configured in server") From ad7db54d579cea77da191c4adce6d0ffce3f6df0 Mon Sep 17 00:00:00 2001 From: ecrupper Date: Wed, 22 Mar 2023 13:43:45 -0500 Subject: [PATCH 10/21] add proper return to update worker too --- api/worker.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/worker.go b/api/worker.go index ab2ee4066..0d9e395be 100644 --- a/api/worker.go +++ b/api/worker.go @@ -331,6 +331,8 @@ func UpdateWorker(c *gin.Context) { tkn := new(library.Token) tkn.SetToken(secret) c.JSON(http.StatusOK, WorkerCheckIn{Worker: w, Token: tkn}) + + return } retErr := fmt.Errorf("symmetric token provided but not configured in server") From 467b47f3f550659cb9dca9c43ba42338d2369d69 Mon Sep 17 00:00:00 2001 From: ecrupper Date: Wed, 22 Mar 2023 14:32:40 -0500 Subject: [PATCH 11/21] add back actually updating the worker --- api/worker.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/api/worker.go b/api/worker.go index 0d9e395be..5511b7cb7 100644 --- a/api/worker.go +++ b/api/worker.go @@ -267,7 +267,7 @@ func GetWorker(c *gin.Context) { // "$ref": "#/definitions/Error" // UpdateWorker represents the API handler to -// create a worker in the configured backend. +// update a worker in the configured backend. func UpdateWorker(c *gin.Context) { // capture middleware values u := user.Retrieve(c) @@ -320,6 +320,16 @@ func UpdateWorker(c *gin.Context) { w.SetLastCheckedIn(input.GetLastCheckedIn()) } + // send API call to update the worker + err = database.FromContext(c).UpdateWorker(w) + if err != nil { + retErr := fmt.Errorf("unable to update worker %s: %w", w.GetHostname(), err) + + util.HandleError(c, http.StatusInternalServerError, retErr) + + return + } + // send API call to capture the updated worker w, _ = database.FromContext(c).GetWorkerForHostname(w.GetHostname()) From f6dea85c15531841d99723de18f3d9b0e040d75d Mon Sep 17 00:00:00 2001 From: ecrupper Date: Wed, 22 Mar 2023 14:36:32 -0500 Subject: [PATCH 12/21] add comments to create and update worker api func --- api/worker.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/api/worker.go b/api/worker.go index 5511b7cb7..59a00272d 100644 --- a/api/worker.go +++ b/api/worker.go @@ -90,6 +90,7 @@ func CreateWorker(c *gin.Context) { } switch cl.TokenType { + // if symmetric token configured, send back symmetric token case constants.ServerWorkerTokenType: if secret, ok := c.Value("secret").(string); ok { tkn := new(library.Token) @@ -103,6 +104,7 @@ func CreateWorker(c *gin.Context) { util.HandleError(c, http.StatusBadRequest, retErr) return + // if worker register token, send back auth token default: tm := c.MustGet("token-manager").(*token.Manager) @@ -333,9 +335,12 @@ func UpdateWorker(c *gin.Context) { // send API call to capture the updated worker w, _ = database.FromContext(c).GetWorkerForHostname(w.GetHostname()) + // prepare the token to send back switch cl.TokenType { + // if update performed by platform admin user, do not send back token case constants.UserAccessTokenType: c.JSON(http.StatusOK, w) + // if symmetric token configured, send back symmetric token case constants.ServerWorkerTokenType: if secret, ok := c.Value("secret").(string); ok { tkn := new(library.Token) @@ -349,6 +354,7 @@ func UpdateWorker(c *gin.Context) { util.HandleError(c, http.StatusBadRequest, retErr) return + // if worker auth / register token, send back auth token default: tm := c.MustGet("token-manager").(*token.Manager) From 85c0efdc42e9ac8df15f6075e17c8bc8cc28dff7 Mon Sep 17 00:00:00 2001 From: ecrupper Date: Thu, 23 Mar 2023 09:37:08 -0500 Subject: [PATCH 13/21] add new endpoint for refresh, remove plat admin access to worker auth perm --- api/worker.go | 71 ++++++++++++++++++++++------- router/middleware/perm/perm.go | 14 ------ router/middleware/perm/perm_test.go | 44 +++++------------- router/worker.go | 4 +- 4 files changed, 69 insertions(+), 64 deletions(-) diff --git a/api/worker.go b/api/worker.go index 59a00272d..41f780189 100644 --- a/api/worker.go +++ b/api/worker.go @@ -41,9 +41,9 @@ import ( // - ApiKeyAuth: [] // responses: // '201': -// description: Successfully created the worker +// description: Successfully created the worker and retrieved auth token // schema: -// type: string +// "$ref": "#definitions/Token" // '400': // description: Unable to create the worker // schema: @@ -274,13 +274,6 @@ func UpdateWorker(c *gin.Context) { // capture middleware values u := user.Retrieve(c) w := worker.Retrieve(c) - cl := claims.Retrieve(c) - - // establish check in type - type WorkerCheckIn struct { - Worker *library.Worker `json:"worker,omitempty"` - Token *library.Token `json:"token,omitempty"` - } // update engine logger with API metadata // @@ -335,17 +328,63 @@ func UpdateWorker(c *gin.Context) { // send API call to capture the updated worker w, _ = database.FromContext(c).GetWorkerForHostname(w.GetHostname()) - // prepare the token to send back + c.JSON(http.StatusOK, w) +} + +// swagger:operation POST /api/v1/workers/{worker}/refresh workers RefreshWorkerAuth +// +// Refresh authorization token for worker +// +// --- +// produces: +// - application/json +// parameters: +// - in: path +// name: worker +// description: Name of the worker +// required: true +// type: string +// security: +// - ApiKeyAuth: [] +// responses: +// '200': +// description: Successfully refreshed auth +// schema: +// "$ref": "#/definitions/Token" +// '400': +// description: Unable to refresh worker auth +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Unable to refresh worker auth +// schema: +// "$ref": "#/definitions/Error" +// '500': +// description: Unable to refresh worker auth +// schema: +// "$ref": "#/definitions/Error" + +// RefreshWorkerAuth represents the API handler to +// refresh the auth token for a worker. +func RefreshWorkerAuth(c *gin.Context) { + // capture middleware values + w := worker.Retrieve(c) + cl := claims.Retrieve(c) + + // update engine logger with API metadata + // + // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields + logrus.WithFields(logrus.Fields{ + "worker": w.GetHostname(), + }).Infof("refreshing worker %s authentication", w.GetHostname()) + switch cl.TokenType { - // if update performed by platform admin user, do not send back token - case constants.UserAccessTokenType: - c.JSON(http.StatusOK, w) // if symmetric token configured, send back symmetric token case constants.ServerWorkerTokenType: if secret, ok := c.Value("secret").(string); ok { tkn := new(library.Token) tkn.SetToken(secret) - c.JSON(http.StatusOK, WorkerCheckIn{Worker: w, Token: tkn}) + c.JSON(http.StatusOK, tkn) return } @@ -355,7 +394,7 @@ func UpdateWorker(c *gin.Context) { return // if worker auth / register token, send back auth token - default: + case constants.WorkerAuthTokenType, constants.WorkerRegisterTokenType: tm := c.MustGet("token-manager").(*token.Manager) wmto := &token.MintTokenOpts{ @@ -377,7 +416,7 @@ func UpdateWorker(c *gin.Context) { tkn.SetToken(wt) - c.JSON(http.StatusOK, WorkerCheckIn{Worker: w, Token: tkn}) + c.JSON(http.StatusCreated, tkn) } } diff --git a/router/middleware/perm/perm.go b/router/middleware/perm/perm.go index 32eef91d4..812c9a8fd 100644 --- a/router/middleware/perm/perm.go +++ b/router/middleware/perm/perm.go @@ -92,7 +92,6 @@ func MustWorkerRegisterToken() gin.HandlerFunc { func MustWorkerAuthToken() gin.HandlerFunc { return func(c *gin.Context) { cl := claims.Retrieve(c) - u := user.Retrieve(c) // update engine logger with API metadata // @@ -103,19 +102,6 @@ func MustWorkerAuthToken() gin.HandlerFunc { switch cl.TokenType { case constants.WorkerAuthTokenType, constants.WorkerRegisterTokenType: - return - case constants.UserAccessTokenType: - if u.GetAdmin() { - logrus.WithFields(logrus.Fields{ - "user": cl.Subject, - }).Debugf("user %s has platform admin permissions", cl.Subject) - - return - } - - retErr := fmt.Errorf("user %s does not have platform admin permissions", cl.Subject) - util.HandleError(c, http.StatusUnauthorized, retErr) - return case constants.ServerWorkerTokenType: if strings.EqualFold(cl.Subject, "vela-worker") { diff --git a/router/middleware/perm/perm_test.go b/router/middleware/perm/perm_test.go index 842a84b8a..61ebf596e 100644 --- a/router/middleware/perm/perm_test.go +++ b/router/middleware/perm/perm_test.go @@ -348,51 +348,29 @@ func TestPerm_MustWorkerAuthToken(t *testing.T) { } } -func TestPerm_MustWorkerAuth_PlatAdmin(t *testing.T) { +func TestPerm_MustWorkerAuth_ServerWorkerToken(t *testing.T) { + // setup types + secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, - UserAccessTokenDuration: time.Minute * 5, - UserRefreshTokenDuration: time.Minute * 30, - } - - u := new(library.User) - u.SetID(1) - u.SetName("vela-worker") - u.SetToken("bar") - u.SetHash("baz") - u.SetAdmin(true) - - mto := &token.MintTokenOpts{ - User: u, - TokenDuration: tm.UserAccessTokenDuration, - TokenType: constants.UserAccessTokenType, + PrivateKey: "123abc", + SignMethod: jwt.SigningMethodHS256, + UserAccessTokenDuration: time.Minute * 5, + UserRefreshTokenDuration: time.Minute * 30, + WorkerRegisterTokenDuration: time.Minute * 1, + WorkerAuthTokenDuration: time.Minute * 15, } - tok, _ := tm.MintToken(mto) - // setup context gin.SetMode(gin.TestMode) resp := httptest.NewRecorder() context, engine := gin.CreateTestContext(resp) - // setup database - db, _ := sqlite.NewTest() - - defer func() { - db.Sqlite.Exec("delete from users;") - _sql, _ := db.Sqlite.DB() - _sql.Close() - }() - - _ = db.CreateUser(u) - context.Request, _ = http.NewRequest(http.MethodGet, "/test/foo/bar", nil) - context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tok)) + context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", secret)) // setup vela mock server - engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) + engine.Use(func(c *gin.Context) { c.Set("secret", secret) }) engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) engine.Use(claims.Establish()) engine.Use(user.Establish()) diff --git a/router/worker.go b/router/worker.go index b642cb5bc..100a24975 100644 --- a/router/worker.go +++ b/router/worker.go @@ -19,6 +19,7 @@ import ( // GET /api/v1/workers // GET /api/v1/workers/:worker // PUT /api/v1/workers/:worker +// POST /api/v1/workers/:worker/refresh // DELETE /api/v1/workers/:worker . func WorkerHandlers(base *gin.RouterGroup) { // Workers endpoints @@ -31,7 +32,8 @@ func WorkerHandlers(base *gin.RouterGroup) { w := workers.Group("/:worker") { w.GET("", worker.Establish(), api.GetWorker) - w.PUT("", perm.MustWorkerAuthToken(), worker.Establish(), api.UpdateWorker) + w.PUT("", perm.MustPlatformAdmin(), worker.Establish(), api.UpdateWorker) + w.POST("/refresh", perm.MustWorkerAuthToken(), worker.Establish(), api.RefreshWorkerAuth) w.DELETE("", perm.MustPlatformAdmin(), worker.Establish(), api.DeleteWorker) } // end of worker endpoints } // end of workers endpoints From 75d4c1fcab4e16a7fcbba5a6b48d09c16aefd4c4 Mon Sep 17 00:00:00 2001 From: ecrupper Date: Thu, 23 Mar 2023 09:44:49 -0500 Subject: [PATCH 14/21] mocks mocks mocks --- mock/server/server.go | 1 + mock/server/worker.go | 33 ++++++++++++++++++++++++++------- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/mock/server/server.go b/mock/server/server.go index c7f7d9571..5d0b9844f 100644 --- a/mock/server/server.go +++ b/mock/server/server.go @@ -129,6 +129,7 @@ func FakeHandler() http.Handler { e.GET("/api/v1/workers/:worker", getWorker) e.POST("/api/v1/workers", addWorker) e.PUT("/api/v1/workers/:worker", updateWorker) + e.POST("/api/v1/workers/:worker/refresh", refreshWorkerAuth) e.DELETE("/api/v1/workers/:worker", removeWorker) // mock endpoints for authentication calls diff --git a/mock/server/worker.go b/mock/server/worker.go index 831da58a9..5c09b5c73 100644 --- a/mock/server/worker.go +++ b/mock/server/worker.go @@ -45,9 +45,6 @@ const ( ], "active": true, "last_checked_in": 1602612590 - }, - "token": { - "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ3b3JrZXIiLCJpYXQiOjE1MTYyMzkwMjIsInRva2VuX3R5cGUiOiJXb3JrZXJBdXRoIn0.qeULIimCJlrwsE0JykNpzBmMaHUbvfk0vkyAz2eEo38" } }` @@ -84,6 +81,11 @@ const ( "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ3b3JrZXIiLCJpYXQiOjE1MTYyMzkwMjIsInRva2VuX3R5cGUiOiJXb3JrZXJBdXRoIn0.qeULIimCJlrwsE0JykNpzBmMaHUbvfk0vkyAz2eEo38" }` + // RefreshWorkerAuthResp represents a JSON return for refreshing a worker's authentication. + RefreshWorkerAuthResp = `{ + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ3b3JrZXIiLCJpYXQiOjE1MTYyMzkwMjIsInRva2VuX3R5cGUiOiJXb3JrZXJBdXRoIn0.qeULIimCJlrwsE0JykNpzBmMaHUbvfk0vkyAz2eEo38" + }` + // RegisterTokenResp represents a JSON return for an admin requesting a registration token. // //nolint:gosec // not actual credentials @@ -148,12 +150,29 @@ func updateWorker(c *gin.Context) { data := []byte(UpdateWorkerResp) - type WorkerCheckIn struct { - Worker *library.Worker `json:"worker,omitempty"` - Token *library.Token `json:"token,omitempty"` + var body library.Worker + _ = json.Unmarshal(data, &body) + + c.JSON(http.StatusOK, body) +} + +// refreshWorkerAuth has a param :worker returns mock JSON for a http PUT. +// +// Pass "0" to :worker to test receiving a http 404 response. +func refreshWorkerAuth(c *gin.Context) { + w := c.Param("worker") + + if strings.EqualFold(w, "0") { + msg := fmt.Sprintf("Worker %s does not exist", w) + + c.AbortWithStatusJSON(http.StatusNotFound, types.Error{Message: &msg}) + + return } - var body WorkerCheckIn + data := []byte(RefreshWorkerAuthResp) + + var body library.Token _ = json.Unmarshal(data, &body) c.JSON(http.StatusOK, body) From bbdcfc5b57b3c2cd16a04cfbdf27b37aa58e425a Mon Sep 17 00:00:00 2001 From: ecrupper Date: Thu, 23 Mar 2023 10:41:11 -0500 Subject: [PATCH 15/21] fix mock, fix status returns, move check in update to refresh --- api/worker.go | 23 ++++++++++++++++------- mock/server/server.go | 2 +- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/api/worker.go b/api/worker.go index 41f780189..4ef613e78 100644 --- a/api/worker.go +++ b/api/worker.go @@ -7,6 +7,7 @@ package api import ( "fmt" "net/http" + "time" "github.com/go-vela/server/internal/token" "github.com/go-vela/server/router/middleware/claims" @@ -95,7 +96,7 @@ func CreateWorker(c *gin.Context) { if secret, ok := c.Value("secret").(string); ok { tkn := new(library.Token) tkn.SetToken(secret) - c.JSON(http.StatusOK, tkn) + c.JSON(http.StatusCreated, tkn) return } @@ -310,11 +311,6 @@ func UpdateWorker(c *gin.Context) { w.SetActive(input.GetActive()) } - if input.GetLastCheckedIn() > 0 { - // update LastCheckedIn if set - w.SetLastCheckedIn(input.GetLastCheckedIn()) - } - // send API call to update the worker err = database.FromContext(c).UpdateWorker(w) if err != nil { @@ -371,6 +367,19 @@ func RefreshWorkerAuth(c *gin.Context) { w := worker.Retrieve(c) cl := claims.Retrieve(c) + // set last checked in time + w.SetLastCheckedIn(time.Now().Unix()) + + // send API call to update the worker + err := database.FromContext(c).UpdateWorker(w) + if err != nil { + retErr := fmt.Errorf("unable to update worker %s: %w", w.GetHostname(), err) + + util.HandleError(c, http.StatusInternalServerError, retErr) + + return + } + // update engine logger with API metadata // // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields @@ -416,7 +425,7 @@ func RefreshWorkerAuth(c *gin.Context) { tkn.SetToken(wt) - c.JSON(http.StatusCreated, tkn) + c.JSON(http.StatusOK, tkn) } } diff --git a/mock/server/server.go b/mock/server/server.go index 5d0b9844f..c0811e6fb 100644 --- a/mock/server/server.go +++ b/mock/server/server.go @@ -37,7 +37,7 @@ func FakeHandler() http.Handler { e.PUT("/api/v1/admin/step", updateStep) e.GET("/api/v1/admin/users", getUsers) e.PUT("/api/v1/admin/user", updateUser) - e.POST("/api/v1/admin/workers/{worker}/register-token", registerToken) + e.POST("/api/v1/admin/workers/:worker/register-token", registerToken) // mock endpoints for build calls e.GET("/api/v1/repos/:org/:repo/builds/:build", getBuild) From 7bc1b5c64833a41794ba7f7b4e279e1823777ef4 Mon Sep 17 00:00:00 2001 From: ecrupper Date: Thu, 23 Mar 2023 10:48:30 -0500 Subject: [PATCH 16/21] update mock validate http method --- mock/server/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mock/server/server.go b/mock/server/server.go index c0811e6fb..e13368711 100644 --- a/mock/server/server.go +++ b/mock/server/server.go @@ -136,7 +136,7 @@ func FakeHandler() http.Handler { e.GET("/token-refresh", getTokenRefresh) e.GET("/authenticate", getAuthenticate) e.POST("/authenticate/token", getAuthenticateFromToken) - e.POST("/validate-token", validateToken) + e.GET("/validate-token", validateToken) return e } From 0014ccd79ced486ec1a360deaa89b88aad9e40c0 Mon Sep 17 00:00:00 2001 From: ecrupper Date: Thu, 23 Mar 2023 10:53:11 -0500 Subject: [PATCH 17/21] use authorization not token in mock --- mock/server/authentication.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mock/server/authentication.go b/mock/server/authentication.go index c029e36bc..0e53fc083 100644 --- a/mock/server/authentication.go +++ b/mock/server/authentication.go @@ -80,7 +80,7 @@ func getAuthenticateFromToken(c *gin.Context) { func validateToken(c *gin.Context) { err := "error" - token := c.Request.Header.Get("Token") + token := c.Request.Header.Get("Authorization") if len(token) == 0 { c.AbortWithStatusJSON(http.StatusUnauthorized, types.Error{Message: &err}) } From 077c267ff365840f3908b1c8319ef836686354e1 Mon Sep 17 00:00:00 2001 From: ecrupper Date: Thu, 23 Mar 2023 12:59:24 -0500 Subject: [PATCH 18/21] retrieve plat admin for logging in register token + swagger updates --- api/admin/worker.go | 6 +++++- api/token.go | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/api/admin/worker.go b/api/admin/worker.go index 09d4b46b6..8c0f9978a 100644 --- a/api/admin/worker.go +++ b/api/admin/worker.go @@ -9,6 +9,7 @@ import ( "net/http" "github.com/go-vela/server/internal/token" + "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" "github.com/go-vela/types/constants" "github.com/go-vela/types/library" @@ -45,7 +46,10 @@ import ( // RegisterToken represents the API handler to // generate a registration token for onboarding a worker. func RegisterToken(c *gin.Context) { - logrus.Info("Admin: generating registration token") + // retrieve user from context + u := user.Retrieve(c) + + logrus.Infof("Platform admin %s: generating registration token", u.GetName()) host := util.PathParameter(c, "worker") diff --git a/api/token.go b/api/token.go index 9d4a4f8c6..9e962931f 100644 --- a/api/token.go +++ b/api/token.go @@ -81,7 +81,7 @@ func RefreshAccessToken(c *gin.Context) { // '200': // description: Successfully validated a token // schema: -// type: string +// "$ref": "#/definitions/Claims" // '401': // description: Unauthorized // schema: From 66e3642df194c3362c5014612452c848659dd664 Mon Sep 17 00:00:00 2001 From: ecrupper Date: Thu, 23 Mar 2023 13:25:31 -0500 Subject: [PATCH 19/21] change return type of validate token --- api/token.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/token.go b/api/token.go index 9e962931f..6c76439ec 100644 --- a/api/token.go +++ b/api/token.go @@ -81,7 +81,7 @@ func RefreshAccessToken(c *gin.Context) { // '200': // description: Successfully validated a token // schema: -// "$ref": "#/definitions/Claims" +// type: string // '401': // description: Unauthorized // schema: @@ -100,5 +100,5 @@ func ValidateServerToken(c *gin.Context) { return } - c.JSON(http.StatusOK, cl) + c.JSON(http.StatusOK, "valid server token") } From b140651067da30782242efb1a1bee3f5682ac7f2 Mon Sep 17 00:00:00 2001 From: ecrupper Date: Thu, 23 Mar 2023 14:16:24 -0500 Subject: [PATCH 20/21] getting mocked by mock --- mock/server/worker.go | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/mock/server/worker.go b/mock/server/worker.go index 5c09b5c73..51aedfa80 100644 --- a/mock/server/worker.go +++ b/mock/server/worker.go @@ -31,23 +31,6 @@ const ( "last_checked_in": 1602612590 }` - // UpdateWorkerResp represents a JSON return for a single updated worker. - UpdateWorkerResp = ` - { - "worker": { - "id": 1, - "hostname": "worker_1", - "address": "http://vela:8080", - "routes": [ - "large", - "docker", - "large:docker" - ], - "active": true, - "last_checked_in": 1602612590 - } - }` - // WorkersResp represents a JSON return for one to many workers. WorkersResp = `[ { @@ -148,7 +131,7 @@ func updateWorker(c *gin.Context) { return } - data := []byte(UpdateWorkerResp) + data := []byte(WorkerResp) var body library.Worker _ = json.Unmarshal(data, &body) From 05b7cd2e3fca5fdec9153baea6db47c11c920567 Mon Sep 17 00:00:00 2001 From: ecrupper Date: Thu, 23 Mar 2023 15:35:01 -0500 Subject: [PATCH 21/21] update validate token mock comment --- mock/server/authentication.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mock/server/authentication.go b/mock/server/authentication.go index 0e53fc083..ea7bb76a1 100644 --- a/mock/server/authentication.go +++ b/mock/server/authentication.go @@ -76,7 +76,7 @@ func getAuthenticateFromToken(c *gin.Context) { // validateToken returns mock response for a http GET. // -// Don't pass "Token" in header to receive an error message. Pass "0" in "Token" header to receive invalid token resp. +// Don't pass "Authorization" in header to receive an unauthorized error message. func validateToken(c *gin.Context) { err := "error"