Skip to content

Commit

Permalink
feat!: Vela OIDC provider (#1120)
Browse files Browse the repository at this point in the history
* init commit

* some renaming and comment fixing

* tests and renaming things here and there, plus pull in types

* update subject

* integration test

* pull in types and add event to subject

* address lint review feedback

* fix tests

* more integration test fixes

* bytes buffer for exponent

* correct issuer and add commands claim

* sender to actor

* use lestrrat jwx lib for jwks

* fixes

* more fixes

* use wrapper for swagger jwk set

* address feedback

* enhance: add build_id and actor_id to claims

* enhance: complete adding build_id and actor_id to claims

* enhance: complete adding build_id and actor_id to claims

* fix: apply context to GenerateRSA

* fix: add err check to ParseBool

* enhance: audience validation

* enhance: better audience validation

* enhance: add query parameter input validation

* tweak: order of operations, move sanitize lower

* enhance: add scm user id to build obj

* enhance: add GetUserID to scm interface

* fix: apply missing scm id using scm client lookups

* chore: verbose comment on fallback user fetch

* chore: comment typo

* enhance: use repo owner token in schedule processing

* enhance: use repo owner token in restart build

* enhance: change claims actor_id to actor_scm_id

---------

Co-authored-by: davidvader <[email protected]>
  • Loading branch information
ecrupper and plyr4 authored Jun 5, 2024
1 parent 4f81558 commit a0b14ae
Show file tree
Hide file tree
Showing 62 changed files with 2,919 additions and 143 deletions.
53 changes: 53 additions & 0 deletions api/admin/rotate_keys.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: Apache-2.0

package admin

import (
"fmt"
"net/http"

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

"github.com/go-vela/server/database"
"github.com/go-vela/server/util"
)

// swagger:operation POST /api/v1/admin/rotate_oidc_keys admin AdminRotateOIDCKeys
//
// Rotate RSA Keys
//
// ---
// produces:
// - application/json
// parameters:
// security:
// - ApiKeyAuth: []
// responses:
// '200':
// description: Successfully rotated OIDC provider keys
// schema:
// type: string
// '500':
// description: Error unable to rotate OIDC provider keys
// schema:
// "$ref": "#/definitions/Error"

// RotateOIDCKeys represents the API handler to
// rotate RSA keys in OIDC provider service.
func RotateOIDCKeys(c *gin.Context) {
logrus.Info("Admin: rotating keys for OIDC provider")

// capture middleware values
ctx := c.Request.Context()

err := database.FromContext(c).RotateKeys(ctx)
if err != nil {
retErr := fmt.Errorf("unable to rotate keys: %w", err)
util.HandleError(c, http.StatusInternalServerError, retErr)

return
}

c.JSON(http.StatusOK, "keys rotated successfully")
}
152 changes: 152 additions & 0 deletions api/build/id_request_token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// SPDX-License-Identifier: Apache-2.0

package build

import (
"errors"
"fmt"
"net/http"
"strconv"
"time"

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

"github.com/go-vela/server/constants"
"github.com/go-vela/server/internal/token"
"github.com/go-vela/server/router/middleware/build"
"github.com/go-vela/server/router/middleware/claims"
"github.com/go-vela/server/util"
"github.com/go-vela/types/library"
)

// swagger:operation GET /api/v1/repos/{org}/{repo}/builds/{build}/id_request_token builds GetIDRequestToken
//
// Get a Vela OIDC request token associated with a build
//
// ---
// produces:
// - application/json
// parameters:
// - in: path
// name: repo
// description: Name of the repo
// required: true
// type: string
// - in: path
// name: org
// description: Name of the org
// required: true
// type: string
// - in: path
// name: build
// description: Build number
// required: true
// type: integer
// - in: query
// name: image
// description: Add image to token claims
// type: string
// - in: query
// name: request
// description: Add request input to token claims
// type: string
// - in: query
// name: commands
// description: Add commands input to token claims
// type: boolean
// security:
// - ApiKeyAuth: []
// responses:
// '200':
// description: Successfully retrieved ID Request token
// schema:
// "$ref": "#/definitions/Token"
// '400':
// description: Bad request
// schema:
// "$ref": "#/definitions/Error"
// '401':
// description: Unauthorized request
// schema:
// "$ref": "#/definitions/Error"
// '404':
// description: Unable to find build
// schema:
// "$ref": "#/definitions/Error"
// '500':
// description: Unable to generate ID request token
// schema:
// "$ref": "#/definitions/Error"

// GetIDRequestToken represents the API handler to generate and return an ID request token.
func GetIDRequestToken(c *gin.Context) {
// capture middleware values
b := build.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{
"build": b.GetNumber(),
"org": b.GetRepo().GetOrg(),
"repo": b.GetRepo().GetName(),
"user": cl.Subject,
}).Infof("generating ID request token for build %s/%d", b.GetRepo().GetFullName(), b.GetNumber())

image := c.Query("image")
if len(image) == 0 {
retErr := errors.New("no step 'image' provided in query parameters")

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

return
}

request := c.Query("request")
if len(request) == 0 {
retErr := errors.New("no 'request' provided in query parameters")

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

return
}

commands, err := strconv.ParseBool(c.Query("commands"))
if err != nil {
retErr := fmt.Errorf("unable to parse 'commands' query parameter as boolean %s: %w", c.Query("commands"), err)

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

return
}

// retrieve token manager from context
tm := c.MustGet("token-manager").(*token.Manager)

exp := (time.Duration(b.GetRepo().GetTimeout()) * time.Minute) + tm.BuildTokenBufferDuration

// set mint token options
idmto := &token.MintTokenOpts{
Build: b,
Repo: b.GetRepo().GetFullName(),
TokenType: constants.IDRequestTokenType,
TokenDuration: exp,
Image: util.Sanitize(image),
Request: util.Sanitize(request),
Commands: commands,
}

// mint token
idrt, err := tm.MintToken(idmto)
if err != nil {
retErr := fmt.Errorf("unable to generate ID request token: %w", err)

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

return
}

c.JSON(http.StatusOK, library.Token{Token: &idrt})
}
138 changes: 138 additions & 0 deletions api/build/id_token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// SPDX-License-Identifier: Apache-2.0

package build

import (
"fmt"
"net/http"

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

"github.com/go-vela/server/constants"
"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/router/middleware/claims"
"github.com/go-vela/server/util"
"github.com/go-vela/types/library"
)

// swagger:operation GET /api/v1/repos/{org}/{repo}/builds/{build}/id_token builds GetIDToken
//
// Get a Vela OIDC token associated with a build
//
// ---
// produces:
// - application/json
// parameters:
// - in: path
// name: repo
// description: Name of the repo
// required: true
// type: string
// - in: path
// name: org
// description: Name of the org
// required: true
// type: string
// - in: path
// name: build
// description: Build number
// required: true
// type: integer
// - in: query
// name: audience
// description: Add audience to token claims
// type: array
// items:
// type: string
// collectionFormat: multi
// security:
// - ApiKeyAuth: []
// responses:
// '200':
// description: Successfully retrieved ID token
// schema:
// "$ref": "#/definitions/Token"
// '400':
// description: Bad request
// schema:
// "$ref": "#/definitions/Error"
// '401':
// description: Unauthorized request
// schema:
// "$ref": "#/definitions/Error"
// '404':
// description: Unable to find build
// schema:
// "$ref": "#/definitions/Error"
// '500':
// description: Unable to generate id token
// schema:
// "$ref": "#/definitions/Error"

// GetIDToken represents the API handler to generate a id token.
func GetIDToken(c *gin.Context) {
// capture middleware values
b := build.Retrieve(c)
cl := claims.Retrieve(c)
ctx := c.Request.Context()

// update engine logger with API metadata
//
// https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
logrus.WithFields(logrus.Fields{
"build": b.GetNumber(),
"org": b.GetRepo().GetOrg(),
"repo": b.GetRepo().GetName(),
"subject": cl.Subject,
}).Infof("generating ID token for build %s/%d", b.GetRepo().GetFullName(), b.GetNumber())

// retrieve token manager from context
tm := c.MustGet("token-manager").(*token.Manager)

// set mint token options
idmto := &token.MintTokenOpts{
Build: b,
Repo: b.GetRepo().GetFullName(),
TokenType: constants.IDTokenType,
TokenDuration: tm.IDTokenDuration,
Image: cl.Image,
Request: cl.Request,
Commands: cl.Commands,
}

// if audience is provided, include that in claims
audience := []string{}

if len(c.QueryArray("audience")) > 0 {
for _, a := range c.QueryArray("audience") {
if len(a) > 0 {
audience = append(audience, util.Sanitize(a))
}
}
}

if len(audience) == 0 {
retErr := fmt.Errorf("unable to generate ID token: %s", "no audience provided")

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

return
}

idmto.Audience = audience

// mint token
idt, err := tm.MintIDToken(ctx, idmto, database.FromContext(c))
if err != nil {
retErr := fmt.Errorf("unable to generate ID token: %w", err)

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

return
}

c.JSON(http.StatusOK, library.Token{Token: &idt})
}
2 changes: 1 addition & 1 deletion api/build/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func GetBuildToken(c *gin.Context) {
// set mint token options
bmto := &token.MintTokenOpts{
Hostname: cl.Subject,
BuildID: b.GetID(),
Build: b,
Repo: r.GetFullName(),
TokenType: constants.WorkerBuildTokenType,
TokenDuration: exp,
Expand Down
Loading

0 comments on commit a0b14ae

Please sign in to comment.