Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(auth): server side changes for new worker auth flow #790

Merged
merged 25 commits into from
Mar 23, 2023
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b339c4c
initial work
ecrupper Feb 28, 2023
746b399
Merge branch 'main' into worker-auth/complete
ecrupper Mar 9, 2023
8bf613c
adding symmetric token handling
ecrupper Mar 14, 2023
c1f1463
incorporating constants
ecrupper Mar 15, 2023
1da6bdd
convert admin register token endpoint to be POST
ecrupper Mar 15, 2023
4d7b735
pulling in types update
ecrupper Mar 21, 2023
e925301
Merge branch 'main' into worker-auth/complete
ecrupper Mar 21, 2023
e620fa3
add server mocks and json tags to check in resp
ecrupper Mar 21, 2023
febb17d
handle symmetric token in worker endpoints
ecrupper Mar 21, 2023
4a12e25
appease linter overlord
ecrupper Mar 21, 2023
25118c9
return properly when met with server worker token
ecrupper Mar 22, 2023
ad7db54
add proper return to update worker too
ecrupper Mar 22, 2023
467b47f
add back actually updating the worker
ecrupper Mar 22, 2023
f6dea85
add comments to create and update worker api func
ecrupper Mar 22, 2023
85c0efd
add new endpoint for refresh, remove plat admin access to worker auth…
ecrupper Mar 23, 2023
75d4c1f
mocks mocks mocks
ecrupper Mar 23, 2023
fa8ba2c
Merge branch 'main' into worker-auth/complete
ecrupper Mar 23, 2023
bbdcfc5
fix mock, fix status returns, move check in update to refresh
ecrupper Mar 23, 2023
2f49e8e
Merge branch 'worker-auth/complete' of github.com:go-vela/server into…
ecrupper Mar 23, 2023
7bc1b5c
update mock validate http method
ecrupper Mar 23, 2023
0014ccd
use authorization not token in mock
ecrupper Mar 23, 2023
077c267
retrieve plat admin for logging in register token + swagger updates
ecrupper Mar 23, 2023
66e3642
change return type of validate token
ecrupper Mar 23, 2023
b140651
getting mocked by mock
ecrupper Mar 23, 2023
05b7cd2
update validate token mock comment
ecrupper Mar 23, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions api/admin/worker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// 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"
ecrupper marked this conversation as resolved.
Show resolved Hide resolved
"github.com/go-vela/server/util"
"github.com/go-vela/types/constants"
"github.com/go-vela/types/library"

"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)

// swagger:operation POST /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")
ecrupper marked this conversation as resolved.
Show resolved Hide resolved

host := util.PathParameter(c, "worker")

tm := c.MustGet("token-manager").(*token.Manager)
rmto := &token.MintTokenOpts{
Hostname: host,
TokenType: constants.WorkerRegisterTokenType,
ecrupper marked this conversation as resolved.
Show resolved Hide resolved
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})
}
20 changes: 19 additions & 1 deletion api/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -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: constants.WorkerAuthTokenType,
ecrupper marked this conversation as resolved.
Show resolved Hide resolved
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)
Expand Down
37 changes: 37 additions & 0 deletions api/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -65,3 +67,38 @@ 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
ecrupper marked this conversation as resolved.
Show resolved Hide resolved
// '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) {
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)
}
93 changes: 83 additions & 10 deletions api/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -85,7 +89,42 @@ func CreateWorker(c *gin.Context) {
return
}

c.JSON(http.StatusCreated, fmt.Sprintf("worker %s created", input.GetHostname()))
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)
}

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)

wmto := &token.MintTokenOpts{
TokenType: constants.WorkerAuthTokenType,
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
Expand Down Expand Up @@ -231,6 +270,13 @@ func UpdateWorker(c *gin.Context) {
// capture middleware values
u := user.Retrieve(c)
w := worker.Retrieve(c)
cl := claims.Retrieve(c)
ecrupper marked this conversation as resolved.
Show resolved Hide resolved

// 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
//
Expand Down Expand Up @@ -272,20 +318,47 @@ func UpdateWorker(c *gin.Context) {
w.SetLastCheckedIn(input.GetLastCheckedIn())
}

// send API call to update the worker
err = database.FromContext(c).UpdateWorker(w)
ecrupper marked this conversation as resolved.
Show resolved Hide resolved
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())

util.HandleError(c, http.StatusInternalServerError, retErr)
switch cl.TokenType {
ecrupper marked this conversation as resolved.
Show resolved Hide resolved
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})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the swagger doc comment's 200 response needs to be updated to show the two different response types: Worker, and WorkerCheckIn.

Copy link
Member

@cognifloyd cognifloyd Mar 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, sdk-go is going to need to know about both types that can be returned. Or, maybe the worker needs a different server end point it can use to check in and get the new token.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The more I think about this, the more I think it should just be a separate endpoint altogether

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The latest commit decoupled UpdateWorker and RefreshWorkerAuth, which I think is much cleaner. Plus we don't have to create a temporary struct as a return type. I also took this change to its natural conclusion and eliminated platform admin access to refresh auth, as that can only cause problems in practice.

Thank you for the feedback!

}

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)

// send API call to capture the updated worker
w, _ = database.FromContext(c).GetWorkerForHostname(w.GetHostname())
wmto := &token.MintTokenOpts{
TokenType: constants.WorkerAuthTokenType,
ecrupper marked this conversation as resolved.
Show resolved Hide resolved
TokenDuration: tm.WorkerAuthTokenDuration,
Hostname: cl.Subject,
}

c.JSON(http.StatusOK, w)
tkn := new(library.Token)

wt, err := tm.MintToken(wmto)
if err != nil {
retErr := fmt.Errorf("unable to generate auth token for worker %s: %w", w.GetHostname(), err)

util.HandleError(c, http.StatusInternalServerError, retErr)

return
}

tkn.SetToken(wt)

c.JSON(http.StatusOK, WorkerCheckIn{Worker: w, Token: tkn})
}
}

// swagger:operation DELETE /api/v1/workers/{worker} workers DeleteWorker
Expand Down
12 changes: 12 additions & 0 deletions cmd/vela-server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
Expand Down
12 changes: 7 additions & 5 deletions cmd/vela-server/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 0 additions & 4 deletions cmd/vela-server/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
6 changes: 6 additions & 0 deletions internal/token/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
7 changes: 7 additions & 0 deletions internal/token/mint.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ func (tm *Manager) MintToken(mto *MintTokenOpts) (string, error) {
claims.Repo = mto.Repo
claims.Subject = mto.Hostname

case constants.WorkerAuthTokenType, constants.WorkerRegisterTokenType:
ecrupper marked this conversation as resolved.
Show resolved Hide resolved
ecrupper marked this conversation as resolved.
Show resolved Hide resolved
ecrupper marked this conversation as resolved.
Show resolved Hide resolved
ecrupper marked this conversation as resolved.
Show resolved Hide resolved
ecrupper marked this conversation as resolved.
Show resolved Hide resolved
ecrupper marked this conversation as resolved.
Show resolved Hide resolved
ecrupper marked this conversation as resolved.
Show resolved Hide resolved
ecrupper marked this conversation as resolved.
Show resolved Hide resolved
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")
}
Expand Down
14 changes: 14 additions & 0 deletions mock/server/authentication.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
ecrupper marked this conversation as resolved.
Show resolved Hide resolved
func validateToken(c *gin.Context) {
err := "error"

token := c.Request.Header.Get("Token")
ecrupper marked this conversation as resolved.
Show resolved Hide resolved
if len(token) == 0 {
c.AbortWithStatusJSON(http.StatusUnauthorized, types.Error{Message: &err})
}

c.JSON(http.StatusOK, "vela-server")
}
Loading