From e9109982cc37f447e677dcd74b86749da5e2b8b6 Mon Sep 17 00:00:00 2001 From: ecrupper Date: Mon, 29 Apr 2024 12:19:53 -0500 Subject: [PATCH 01/34] init commit --- api/admin/rotate_keys.go | 53 +++ api/build/id_request_token.go | 109 ++++++ api/build/id_token.go | 111 ++++++ api/jwks.go | 41 +++ api/oi_config.go | 54 +++ api/types/oidc.go | 28 ++ cmd/vela-server/main.go | 6 + cmd/vela-server/server.go | 7 +- cmd/vela-server/token.go | 14 +- compiler/native/environment.go | 1 + constants/secret.go | 33 ++ constants/status.go | 33 ++ constants/table.go | 2 + constants/token.go | 33 ++ constants/visibility.go | 16 + database/database.go | 2 + database/interface.go | 4 + database/keyset/create.go | 33 ++ database/keyset/get.go | 32 ++ database/keyset/interface.go | 31 ++ database/keyset/keyset.go | 77 +++++ database/keyset/list.go | 39 +++ database/keyset/opts.go | 62 ++++ database/keyset/rotate.go | 40 +++ database/keyset/table.go | 50 +++ database/resource.go | 9 + database/types/jwk.go | 102 ++++++ go.mod | 2 + go.sum | 4 +- internal/token/compose_test.go | 5 +- internal/token/generate_rsa.go | 50 +++ internal/token/manager.go | 22 +- internal/token/mint.go | 92 ++++- internal/token/parse.go | 2 +- internal/token/parse_test.go | 22 +- internal/token/refresh_test.go | 7 +- router/admin.go | 3 + router/build.go | 2 + router/middleware/claims/claims_test.go | 9 +- router/middleware/perm/perm.go | 43 ++- router/middleware/perm/perm_test.go | 363 +++++++++++++++++--- router/middleware/pipeline/pipeline_test.go | 4 +- router/middleware/token_manager_test.go | 2 +- router/middleware/user/user_test.go | 16 +- router/router.go | 4 + 45 files changed, 1553 insertions(+), 121 deletions(-) create mode 100644 api/admin/rotate_keys.go create mode 100644 api/build/id_request_token.go create mode 100644 api/build/id_token.go create mode 100644 api/jwks.go create mode 100644 api/oi_config.go create mode 100644 api/types/oidc.go create mode 100644 constants/secret.go create mode 100644 constants/status.go create mode 100644 constants/token.go create mode 100644 constants/visibility.go create mode 100644 database/keyset/create.go create mode 100644 database/keyset/get.go create mode 100644 database/keyset/interface.go create mode 100644 database/keyset/keyset.go create mode 100644 database/keyset/list.go create mode 100644 database/keyset/opts.go create mode 100644 database/keyset/rotate.go create mode 100644 database/keyset/table.go create mode 100644 database/types/jwk.go create mode 100644 internal/token/generate_rsa.go diff --git a/api/admin/rotate_keys.go b/api/admin/rotate_keys.go new file mode 100644 index 000000000..7be3a3e16 --- /dev/null +++ b/api/admin/rotate_keys.go @@ -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 admin AdminRotateOIDCKeys +// +// Rotate RSA Keys +// +// --- +// produces: +// - application/json +// parameters: +// security: +// - ApiKeyAuth: [] +// responses: +// '200': +// description: Successfully updated the repo in the database +// schema: +// "$ref": "#/definitions/Repo" +// '501': +// description: Unable to update the repo in the database +// 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") +} diff --git a/api/build/id_request_token.go b/api/build/id_request_token.go new file mode 100644 index 000000000..73c65a9fd --- /dev/null +++ b/api/build/id_request_token.go @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: Apache-2.0 + +package build + +import ( + "fmt" + "net/http" + "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/router/middleware/org" + "github.com/go-vela/server/router/middleware/repo" + "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 +// security: +// - ApiKeyAuth: [] +// responses: +// '200': +// description: Successfully retrieved ID token +// schema: +// "$ref": "#/definitions/Token" +// '400': +// description: Bad request +// schema: +// "$ref": "#/definitions/Error" +// '409': +// description: Conflict (requested id token for build not in running state) +// schema: +// "$ref": "#/definitions/Error" +// '500': +// description: Unable to generate id 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) + o := org.Retrieve(c) + r := repo.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": o, + "repo": r.GetName(), + "user": cl.Subject, + }).Infof("generating ID request token for build %s/%d", r.GetFullName(), b.GetNumber()) + + // retrieve token manager from context + tm := c.MustGet("token-manager").(*token.Manager) + + exp := (time.Duration(r.GetTimeout()) * time.Minute) + tm.BuildTokenBufferDuration + + // set mint token options + idmto := &token.MintTokenOpts{ + BuildID: b.GetID(), + BuildNumber: b.GetNumber(), + Repo: r.GetFullName(), + TokenType: constants.IDRequestTokenType, + Commit: b.GetCommit(), + TokenDuration: exp, + } + + // mint token + bt, 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: &bt}) +} diff --git a/api/build/id_token.go b/api/build/id_token.go new file mode 100644 index 000000000..763b49d00 --- /dev/null +++ b/api/build/id_token.go @@ -0,0 +1,111 @@ +// 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/router/middleware/org" + "github.com/go-vela/server/router/middleware/repo" + "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 +// security: +// - ApiKeyAuth: [] +// responses: +// '200': +// description: Successfully retrieved ID token +// schema: +// "$ref": "#/definitions/Token" +// '400': +// description: Bad request +// schema: +// "$ref": "#/definitions/Error" +// '409': +// description: Conflict (requested id token for build not in running state) +// 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) + o := org.Retrieve(c) + r := repo.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": o, + "repo": r.GetName(), + "user": cl.Subject, + }).Infof("generating ID token for build %s/%d", r.GetFullName(), b.GetNumber()) + + // retrieve token manager from context + tm := c.MustGet("token-manager").(*token.Manager) + + // set mint token options + idmto := &token.MintTokenOpts{ + BuildNumber: b.GetNumber(), + Repo: r.GetFullName(), + TokenType: constants.IDTokenType, + Commit: b.GetCommit(), + TokenDuration: tm.IDTokenDuration, + } + + // if audience is provided, include that in claims + if len(c.QueryArray("audience")) > 0 { + idmto.Audience = c.QueryArray("audience") + } + + // mint token + bt, err := tm.MintIDToken(idmto, database.FromContext(c)) + if err != nil { + retErr := fmt.Errorf("unable to generate build token: %w", err) + util.HandleError(c, http.StatusInternalServerError, retErr) + + return + } + + c.JSON(http.StatusOK, library.Token{Token: &bt}) +} diff --git a/api/jwks.go b/api/jwks.go new file mode 100644 index 000000000..471514f2a --- /dev/null +++ b/api/jwks.go @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: Apache-2.0 + +package api + +import ( + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database" + "github.com/go-vela/server/util" +) + +// swagger:operation GET /_services/token/.well-known/jwks token GetJWKS +// +// Get the JWKS for the Vela OIDC service +// +// --- +// produces: +// - application/json +// parameters: +// security: +// - ApiKeyAuth: [] +// responses: +// '200': +// description: Successfully retrieved the Vela JWKS +// schema: +// "$ref": "#/definitions/PublicKey" + +// GetJWKS represents the API handler for requests to public keys in the Vela OpenID service. +func GetJWKS(c *gin.Context) { + // retrieve token manager from context + keys, err := database.FromContext(c).ListKeySets(c) + if err != nil { + retErr := fmt.Errorf("unable to get key sets: %w", err) + util.HandleError(c, http.StatusInternalServerError, retErr) + } + + c.JSON(http.StatusOK, types.JWKS{Keys: keys}) +} diff --git a/api/oi_config.go b/api/oi_config.go new file mode 100644 index 000000000..721f71863 --- /dev/null +++ b/api/oi_config.go @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: Apache-2.0 + +package api + +import ( + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt/v5" + + "github.com/go-vela/server/api/types" + "github.com/go-vela/server/internal" +) + +// swagger:operation GET /_services/token/.well-known/openid-configuration token GetOpenIDConfig +// +// Get the OpenID configuration for the Vela OIDC service +// +// --- +// produces: +// - application/json +// parameters: +// security: +// - ApiKeyAuth: [] +// responses: +// '200': +// description: Successfully retrieved the Vela OpenID Configuration +// schema: +// "$ref": "#/definitions/PublicKey" + +// GetOpenIDConfig represents the API handler for requests for configurations in the Vela OpenID service. +func GetOpenIDConfig(c *gin.Context) { + m := c.MustGet("metadata").(*internal.Metadata) + + config := types.OpenIDConfig{ + Issuer: m.Vela.Address, + JWKSAddress: fmt.Sprintf("%s/%s", m.Vela.Address, "_services/token/.well-known/jwks"), + SupportedClaims: []string{ + "sub", + "exp", + "iat", + "iss", + "build_number", + "repo", + "token_type", + }, + Algorithms: []string{ + jwt.SigningMethodRS256.Name, + }, + } + + c.JSON(http.StatusOK, config) +} diff --git a/api/types/oidc.go b/api/types/oidc.go new file mode 100644 index 000000000..cf48d7a69 --- /dev/null +++ b/api/types/oidc.go @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +type OpenIDConfig struct { + Issuer string `json:"issuer"` + JWKSAddress string `json:"jwks_uri"` + SupportedClaims []string `json:"supported_claims"` + Algorithms []string `json:"id_token_signing_alg_values_supported"` +} + +// JWKS is a slice of JWKs +type JWKS struct { + Keys []JWK `json:"keys"` +} + +// JWK represents a JSON Web Key parsed with fields as the correct Go types. +type JWK struct { + Algorithm string `json:"alg"` + Use string `json:"use"` + X5t string `json:"x5t"` + Kid string `json:"kid"` + Kty string `json:"kty"` + X5c []string `json:"x5c"` + + N string `json:"n"` // modulus + E string `json:"e"` // public exponent +} diff --git a/cmd/vela-server/main.go b/cmd/vela-server/main.go index 5ce4c7bd7..069f8e6fc 100644 --- a/cmd/vela-server/main.go +++ b/cmd/vela-server/main.go @@ -175,6 +175,12 @@ func main() { Usage: "sets the duration of the worker register token", Value: 1 * time.Minute, }, + &cli.DurationFlag{ + EnvVars: []string{"VELA_ID_TOKEN_DURATION", "ID_TOKEN_DURATION"}, + Name: "id-token-duration", + Usage: "sets the duration of the id token", + Value: 5 * time.Minute, + }, // Compiler Flags &cli.BoolFlag{ EnvVars: []string{"VELA_COMPILER_GITHUB", "COMPILER_GITHUB"}, diff --git a/cmd/vela-server/server.go b/cmd/vela-server/server.go index 7f25a43df..8e372016e 100644 --- a/cmd/vela-server/server.go +++ b/cmd/vela-server/server.go @@ -97,12 +97,17 @@ func server(c *cli.Context) error { return err } + tm, err := setupTokenManager(c, database) + if err != nil { + return err + } + router := router.Load( middleware.Compiler(compiler), middleware.Database(database), middleware.Logger(logrus.StandardLogger(), time.RFC3339), middleware.Metadata(metadata), - middleware.TokenManager(setupTokenManager(c)), + middleware.TokenManager(tm), middleware.Queue(queue), middleware.RequestVersion, middleware.Secret(c.String("vela-secret")), diff --git a/cmd/vela-server/token.go b/cmd/vela-server/token.go index 04a756fca..ead52788e 100644 --- a/cmd/vela-server/token.go +++ b/cmd/vela-server/token.go @@ -3,26 +3,30 @@ package main import ( - "github.com/golang-jwt/jwt/v5" "github.com/sirupsen/logrus" "github.com/urfave/cli/v2" + "github.com/go-vela/server/database" "github.com/go-vela/server/internal/token" ) // helper function to setup the tokenmanager from the CLI arguments. -func setupTokenManager(c *cli.Context) *token.Manager { +func setupTokenManager(c *cli.Context, db database.Interface) (*token.Manager, error) { logrus.Debug("Creating token manager from CLI configuration") tm := &token.Manager{ - PrivateKey: c.String("vela-server-private-key"), - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: c.String("vela-server-private-key"), 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"), + IDTokenDuration: c.Duration("id-token-duration"), + Issuer: c.String("server-addr"), } - return tm + // generate a new RSA key pair + tm.GenerateRSA(db) + + return tm, nil } diff --git a/compiler/native/environment.go b/compiler/native/environment.go index 1d0666e43..a2542fb3f 100644 --- a/compiler/native/environment.go +++ b/compiler/native/environment.go @@ -315,6 +315,7 @@ func environment(b *api.Build, m *internal.Metadata, r *api.Repo, u *api.User) m env["VELA_NETRC_MACHINE"] = m.Source.Host env["VELA_QUEUE"] = m.Queue.Driver env["VELA_SOURCE"] = m.Source.Driver + env["VELA_ID_TOKEN_REQUEST_URL"] = fmt.Sprintf("%s/api/v1/repos/%s/builds/%d/id_token", m.Vela.Address, r.GetFullName(), b.GetNumber()) channel = m.Queue.Channel workspace = fmt.Sprintf("%s/%s/%s/%s", workspace, m.Source.Host, r.GetOrg(), r.GetName()) } diff --git a/constants/secret.go b/constants/secret.go new file mode 100644 index 000000000..bb794f025 --- /dev/null +++ b/constants/secret.go @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 + +package constants + +// Secret types. +const ( + // SecretPullBuild defines the pull policy type for a secret. + SecretPullBuild = "build_start" + + // SecretPullStep defines the pull policy type for a secret. + SecretPullStep = "step_start" + + // SecretOrg defines the secret type for a secret scoped to a specific org. + SecretOrg = "org" + + // SecretRepo defines the secret type for a secret scoped to a specific repo. + SecretRepo = "repo" + + // SecretShared defines the secret type for a secret shared across the installation. + SecretShared = "shared" + + // SecretMask defines the secret mask to be used in place of secret values returned to users. + SecretMask = "[secure]" + + // SecretLogMask defines the secret mask to be used when distributing logs that contain secrets. + SecretLogMask = "***" + + // SecretRestrictedCharacters defines the set of characters that a secret name cannot contain. + // This matches the following characters: + // Equal Sign = + // Null Character \x00 + SecretRestrictedCharacters = "=\x00" +) diff --git a/constants/status.go b/constants/status.go new file mode 100644 index 000000000..489f4a7df --- /dev/null +++ b/constants/status.go @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 + +package constants + +// Build and step statuses. +const ( + // StatusError defines the status type for build and step error statuses. + StatusError = "error" + + // StatusFailure defines the status type for build and step failure statuses. + StatusFailure = "failure" + + // StatusKilled defines the status type for build and step killed statuses. + StatusKilled = "killed" + + // StatusCanceled defines the status type for build and step canceled statuses. + StatusCanceled = "canceled" + + // StatusPending defines the status type for build and step pending statuses. + StatusPending = "pending" + + // StatusPendingApproval defines the status type for a build waiting to be approved to run. + StatusPendingApproval = "pending approval" + + // StatusRunning defines the status type for build and step running statuses. + StatusRunning = "running" + + // StatusSuccess defines the status type for build and step success statuses. + StatusSuccess = "success" + + // StatusSkipped defines the status type for build and step skipped statuses. + StatusSkipped = "skipped" +) diff --git a/constants/table.go b/constants/table.go index 6d9ff76e8..50b1a0780 100644 --- a/constants/table.go +++ b/constants/table.go @@ -5,4 +5,6 @@ package constants const ( // TableDashboard defines the table type for the database dashboards table. TableDashboard = "dashboards" + // TableKeySet defines the table type for the database keysets table. + TableKeySet = "keysets" ) diff --git a/constants/token.go b/constants/token.go new file mode 100644 index 000000000..793b1f7c7 --- /dev/null +++ b/constants/token.go @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 + +package constants + +// Constants for tokens. +const ( + // RefreshTokenName is the name associated with the refresh token. + RefreshTokenName = "vela_refresh_token" + + // UserAccessTokenType is the name associated with the user access token type. + UserAccessTokenType = "UserAccess" + + // UserRefreshTokenType is the name associated with the user refresh token type. + UserRefreshTokenType = "UserRefresh" + + // WorkerAuthTokenType is the name associated with the worker authentication token type. + WorkerAuthTokenType = "WorkerAuth" + + // WorkerRegisterTokenType is the name associated with the worker registration token type. + WorkerRegisterTokenType = "WorkerRegister" + + // WorkerBuildTokenType is the name associated with the worker build token type. + WorkerBuildTokenType = "WorkerBuild" + + // ServerWorkerTokenType is the name associated with the server-worker symmetric token. + ServerWorkerTokenType = "ServerWorker" + + // IDRequestTokenType is the name associated with the id request token type. + IDRequestTokenType = "IDRequest" + + // IDTokenType is the name associated with the id token type. + IDTokenType = "ID" +) diff --git a/constants/visibility.go b/constants/visibility.go new file mode 100644 index 000000000..af2f35b04 --- /dev/null +++ b/constants/visibility.go @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 + +package constants + +// Repo visibility types. +const ( + // VisibilityPublic defines the visibility type for allowing any + // users in Vela to access their repo regardless of the access + // defined in the source control system. + VisibilityPublic = "public" + + // VisibilityPrivate defines the visibility type for only allowing + // users in Vela with pre-defined access in the source control + // system to access their repo. + VisibilityPrivate = "private" +) diff --git a/database/database.go b/database/database.go index 50d8ae956..9b508b09c 100644 --- a/database/database.go +++ b/database/database.go @@ -17,6 +17,7 @@ import ( "github.com/go-vela/server/database/deployment" "github.com/go-vela/server/database/executable" "github.com/go-vela/server/database/hook" + "github.com/go-vela/server/database/keyset" "github.com/go-vela/server/database/log" "github.com/go-vela/server/database/pipeline" "github.com/go-vela/server/database/repo" @@ -66,6 +67,7 @@ type ( executable.BuildExecutableInterface deployment.DeploymentInterface hook.HookInterface + keyset.KeySetInterface log.LogInterface pipeline.PipelineInterface repo.RepoInterface diff --git a/database/interface.go b/database/interface.go index 16a0fdf27..182ca00f7 100644 --- a/database/interface.go +++ b/database/interface.go @@ -8,6 +8,7 @@ import ( "github.com/go-vela/server/database/deployment" "github.com/go-vela/server/database/executable" "github.com/go-vela/server/database/hook" + "github.com/go-vela/server/database/keyset" "github.com/go-vela/server/database/log" "github.com/go-vela/server/database/pipeline" "github.com/go-vela/server/database/repo" @@ -49,6 +50,9 @@ type Interface interface { // HookInterface defines the interface for hooks stored in the database. hook.HookInterface + // KeySetInterface defines the interface for key sets stored in the database. + keyset.KeySetInterface + // LogInterface defines the interface for logs stored in the database. log.LogInterface diff --git a/database/keyset/create.go b/database/keyset/create.go new file mode 100644 index 000000000..0b9018462 --- /dev/null +++ b/database/keyset/create.go @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 + +package keyset + +import ( + "context" + "database/sql" + + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/constants" + "github.com/go-vela/server/database/types" +) + +// CreateKeySet creates a new dashboard in the database. +func (e *engine) CreateKeySet(ctx context.Context, j api.JWK) error { + e.logger.WithFields(logrus.Fields{ + "jwk": j.Kid, + }).Tracef("creating key %s in the database", j.Kid) + + key := types.JWKFromAPI(j) + key.Active = sql.NullBool{Bool: true, Valid: true} + + err := key.Validate() + if err != nil { + return err + } + + // send query to the database + return e.client.Table(constants.TableKeySet).Create(key).Error + +} diff --git a/database/keyset/get.go b/database/keyset/get.go new file mode 100644 index 000000000..6605e75a3 --- /dev/null +++ b/database/keyset/get.go @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 + +package keyset + +import ( + "context" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/constants" + "github.com/go-vela/server/database/types" +) + +// GetKeySet gets a key by UUID from the database if active. +func (e *engine) GetActiveKeySet(ctx context.Context, id string) (api.JWK, error) { + e.logger.Tracef("getting key %s from the database", id) + + // variable to store query results + j := new(types.JWK) + + // send query to the database and store result in variable + err := e.client. + Table(constants.TableKeySet). + Where("id = ?", id). + Where("active = ?", true). + Take(j). + Error + if err != nil { + return j.ToAPI(), err + } + + return j.ToAPI(), nil +} diff --git a/database/keyset/interface.go b/database/keyset/interface.go new file mode 100644 index 000000000..b297c8460 --- /dev/null +++ b/database/keyset/interface.go @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 + +package keyset + +import ( + "context" + + api "github.com/go-vela/server/api/types" +) + +// KeySetInterface represents the Vela interface for key set +// functions with the supported Database backends. +type KeySetInterface interface { + // KeySet Data Definition Language Functions + // + // https://en.wikipedia.org/wiki/Data_definition_language + CreateKeySetTable(context.Context, string) error + + // KeySet Data Manipulation Language Functions + // + // https://en.wikipedia.org/wiki/Data_manipulation_language + + // CreateKeySet defines a function that creates a key set. + CreateKeySet(context.Context, api.JWK) error + // DeleteKeySet defines a function that gets and deletes a key set. + RotateKeys(context.Context) error + // ListKeySets defines a function that lists all key sets configured. + ListKeySets(context.Context) ([]api.JWK, error) + // GetKeySet defines a function that gets a key set by the provided key ID. + GetActiveKeySet(context.Context, string) (api.JWK, error) +} diff --git a/database/keyset/keyset.go b/database/keyset/keyset.go new file mode 100644 index 000000000..96cb6734b --- /dev/null +++ b/database/keyset/keyset.go @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: Apache-2.0 + +package keyset + +import ( + "context" + "fmt" + + "github.com/sirupsen/logrus" + "gorm.io/gorm" + + "github.com/go-vela/server/constants" +) + +type ( + // config represents the settings required to create the engine that implements the KeySetService interface. + config struct { + // specifies to skip creating tables and indexes for the KeySet engine + SkipCreation bool + // specifies the driver for proper popping query + Driver string + } + + // engine represents the key set functionality that implements the KeySetService interface. + engine struct { + // engine configuration settings used in key set functions + config *config + + ctx context.Context + + // gorm.io/gorm database client used in key set functions + // + // https://pkg.go.dev/gorm.io/gorm#DB + client *gorm.DB + + // sirupsen/logrus logger used in key set functions + // + // https://pkg.go.dev/github.com/sirupsen/logrus#Entry + logger *logrus.Entry + } +) + +// New creates and returns a Vela service for integrating with key sets in the database. +// +//nolint:revive // ignore returning unexported engine +func New(opts ...EngineOpt) (*engine, error) { + // create new KeySet engine + e := new(engine) + + // create new fields + e.client = new(gorm.DB) + e.config = new(config) + e.logger = new(logrus.Entry) + + // apply all provided configuration options + for _, opt := range opts { + err := opt(e) + if err != nil { + return nil, err + } + } + + // check if we should skip creating key set database objects + if e.config.SkipCreation { + e.logger.Warning("skipping creation of key sets table and indexes in the database") + + return e, nil + } + + // create the key sets table + err := e.CreateKeySetTable(e.ctx, e.client.Config.Dialector.Name()) + if err != nil { + return nil, fmt.Errorf("unable to create %s table: %w", constants.TableKeySet, err) + } + + return e, nil +} diff --git a/database/keyset/list.go b/database/keyset/list.go new file mode 100644 index 000000000..204bdf8a7 --- /dev/null +++ b/database/keyset/list.go @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: Apache-2.0 + +package keyset + +import ( + "context" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/constants" + "github.com/go-vela/server/database/types" +) + +// ListKeySets gets a list of all schedules from the database. +func (e *engine) ListKeySets(ctx context.Context) ([]api.JWK, error) { + e.logger.Trace("listing all keysets from the database") + + k := new([]types.JWK) + keys := []api.JWK{} + + // send query to the database and store result in variable + err := e.client. + Table(constants.TableKeySet). + Find(&k). + Error + if err != nil { + return nil, err + } + + // iterate through all query results + for _, key := range *k { + // https://golang.org/doc/faq#closures_and_goroutines + tmp := key + + // convert query result to API type + keys = append(keys, tmp.ToAPI()) + } + + return keys, nil +} diff --git a/database/keyset/opts.go b/database/keyset/opts.go new file mode 100644 index 000000000..8a94e72dd --- /dev/null +++ b/database/keyset/opts.go @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: Apache-2.0 + +package keyset + +import ( + "context" + + "github.com/sirupsen/logrus" + "gorm.io/gorm" +) + +// EngineOpt represents a configuration option to initialize the database engine for key sets. +type EngineOpt func(*engine) error + +// WithClient sets the gorm.io/gorm client in the database engine for key sets. +func WithClient(client *gorm.DB) EngineOpt { + return func(e *engine) error { + // set the gorm.io/gorm client in the key set engine + e.client = client + + return nil + } +} + +// WithDriver sets the driver type in the database engine for key sets. +func WithDriver(driver string) EngineOpt { + return func(e *engine) error { + // set the driver type in the key set engine + e.config.Driver = driver + + return nil + } +} + +// WithLogger sets the github.com/sirupsen/logrus logger in the database engine for key sets. +func WithLogger(logger *logrus.Entry) EngineOpt { + return func(e *engine) error { + // set the github.com/sirupsen/logrus logger in the key set engine + e.logger = logger + + return nil + } +} + +// WithSkipCreation sets the skip creation logic in the database engine for key sets. +func WithSkipCreation(skipCreation bool) EngineOpt { + return func(e *engine) error { + // set to skip creating tables and indexes in the key set engine + e.config.SkipCreation = skipCreation + + return nil + } +} + +// WithContext sets the context in the database engine for key sets. +func WithContext(ctx context.Context) EngineOpt { + return func(e *engine) error { + e.ctx = ctx + + return nil + } +} diff --git a/database/keyset/rotate.go b/database/keyset/rotate.go new file mode 100644 index 000000000..07244485e --- /dev/null +++ b/database/keyset/rotate.go @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: Apache-2.0 + +package keyset + +import ( + "context" + "database/sql" + + "github.com/go-vela/server/constants" + "github.com/go-vela/server/database/types" +) + +// RotateKeys removes all inactive keys and sets active keys to inactive. +func (e *engine) RotateKeys(ctx context.Context) error { + e.logger.Trace("rotating keysets in the database") + + k := types.JWK{} + + // remove inactive keys + err := e.client. + Table(constants.TableKeySet). + Where("active = ?", false). + Delete(&k). + Error + if err != nil { + return err + } + + // set active keys to inactive + err = e.client. + Table(constants.TableKeySet). + Where("active = ?", true). + Update("active", sql.NullBool{Bool: false, Valid: true}). + Error + if err != nil { + return err + } + + return nil +} diff --git a/database/keyset/table.go b/database/keyset/table.go new file mode 100644 index 000000000..3c63432fe --- /dev/null +++ b/database/keyset/table.go @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: Apache-2.0 + +package keyset + +import ( + "context" + + "github.com/go-vela/types/constants" +) + +const ( + // CreatePostgresTable represents a query to create the Postgres keysets table. + CreatePostgresTable = ` +CREATE TABLE +IF NOT EXISTS +keysets ( + id UUID PRIMARY KEY, + active BOOLEAN, + key JSON DEFAULT NULL +); +` + + // CreateSqliteTable represents a query to create the Sqlite keysets table. + CreateSqliteTable = ` +CREATE TABLE +IF NOT EXISTS +keysets ( + id TEXT PRIMARY KEY, + active BOOLEAN, + key TEXT +); +` +) + +// CreateKeySetTable creates the build executables table in the database. +func (e *engine) CreateKeySetTable(ctx context.Context, driver string) error { + e.logger.Tracef("creating keysets table in the database") + + // handle the driver provided to create the table + switch driver { + case constants.DriverPostgres: + // create the keysets table for Postgres + return e.client.Exec(CreatePostgresTable).Error + case constants.DriverSqlite: + fallthrough + default: + // create the keysets table for Sqlite + return e.client.Exec(CreateSqliteTable).Error + } +} diff --git a/database/resource.go b/database/resource.go index cbdad190c..540cd2846 100644 --- a/database/resource.go +++ b/database/resource.go @@ -10,6 +10,7 @@ import ( "github.com/go-vela/server/database/deployment" "github.com/go-vela/server/database/executable" "github.com/go-vela/server/database/hook" + "github.com/go-vela/server/database/keyset" "github.com/go-vela/server/database/log" "github.com/go-vela/server/database/pipeline" "github.com/go-vela/server/database/repo" @@ -82,6 +83,14 @@ func (e *engine) NewResources(ctx context.Context) error { return err } + // create the database agnostic engine for keysets + e.KeySetInterface, err = keyset.New( + keyset.WithContext(e.ctx), + keyset.WithClient(e.client), + keyset.WithLogger(e.logger), + keyset.WithSkipCreation(e.config.SkipCreation), + ) + // create the database agnostic engine for logs e.LogInterface, err = log.New( log.WithContext(e.ctx), diff --git a/database/types/jwk.go b/database/types/jwk.go new file mode 100644 index 000000000..6fc383792 --- /dev/null +++ b/database/types/jwk.go @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "database/sql" + "database/sql/driver" + "encoding/json" + "errors" + "fmt" + + "github.com/google/uuid" + + api "github.com/go-vela/server/api/types" +) + +var ( + // ErrEmptyKID defines the error type when a + // JWK type has an empty ID field provided. + ErrEmptyKID = errors.New("empty key identifier provided") +) + +type ( + // JWK is the database representation of a jwk. + JWK struct { + ID uuid.UUID `gorm:"type:uuid"` + Active sql.NullBool `sql:"active"` + Key KeyJSON `sql:"key"` + } + + KeyJSON api.JWK +) + +// Value - Implementation of valuer for database/sql for KeyJSON. +func (k KeyJSON) Value() (driver.Value, error) { + valueString, err := json.Marshal(k) + return string(valueString), err +} + +// Scan - Implement the database/sql scanner interface for KeyJSON. +func (k *KeyJSON) Scan(value interface{}) error { + switch v := value.(type) { + case []byte: + return json.Unmarshal(v, &k) + case string: + return json.Unmarshal([]byte(v), &k) + default: + return fmt.Errorf("wrong type for key: %T", v) + } +} + +// Nullify ensures the valid flag for +// the sql.Null types are properly set. +// +// When a field within the JWK type is the zero +// value for the field, the valid flag is set to +// false causing it to be NULL in the database. +func (j *JWK) Nullify() *JWK { + if j == nil { + return nil + } + + return j +} + +// ToAPI converts the JWK type +// to an API JWK type. +func (j *JWK) ToAPI() api.JWK { + return api.JWK(j.Key) +} + +// Validate verifies the necessary fields for +// the JWK type are populated correctly. +func (j *JWK) Validate() error { + // verify the Name field is populated + if len(j.ID.String()) == 0 { + return ErrEmptyKID + } + + return nil +} + +// JWKFromAPI converts the API JWK type +// to a database JWK type. +func JWKFromAPI(j api.JWK) *JWK { + var ( + id uuid.UUID + err error + ) + + id, err = uuid.Parse(j.Kid) + if err != nil { + return nil + } + + dashboard := &JWK{ + ID: id, + Key: KeyJSON(j), + } + + return dashboard.Nullify() +} diff --git a/go.mod b/go.mod index bc25fc72b..176743e3e 100644 --- a/go.mod +++ b/go.mod @@ -45,6 +45,8 @@ require ( k8s.io/apimachinery v0.29.2 ) +require github.com/stretchr/testify v1.9.0 // indirect + require ( github.com/Masterminds/goutils v1.1.1 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect diff --git a/go.sum b/go.sum index 1f35c936b..8003aff62 100644 --- a/go.sum +++ b/go.sum @@ -257,8 +257,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= diff --git a/internal/token/compose_test.go b/internal/token/compose_test.go index 0cbce444c..c6efd58ce 100644 --- a/internal/token/compose_test.go +++ b/internal/token/compose_test.go @@ -24,8 +24,7 @@ func TestToken_Compose(t *testing.T) { u.SetToken("bar") tm := &Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -47,7 +46,7 @@ func TestToken_Compose(t *testing.T) { tkn := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - want, err := tkn.SignedString([]byte(tm.PrivateKey)) + want, err := tkn.SignedString([]byte(tm.PrivateKeyHMAC)) if err != nil { t.Errorf("Unable to create test token: %v", err) } diff --git a/internal/token/generate_rsa.go b/internal/token/generate_rsa.go new file mode 100644 index 000000000..f1277e3fd --- /dev/null +++ b/internal/token/generate_rsa.go @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: Apache-2.0 + +package token + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "encoding/base64" + "strconv" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database" + "github.com/golang-jwt/jwt/v5" + "github.com/google/uuid" +) + +// MintToken mints a Vela JWT Token given a set of options. +func (tm *Manager) GenerateRSA(db database.Interface) error { + privateRSAKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return err + } + + kid, err := uuid.NewRandom() + if err != nil { + return err + } + + key := api.JWK{ + Algorithm: jwt.SigningMethodRS256.Name, + Kid: kid.String(), + N: base64.RawURLEncoding.EncodeToString(privateRSAKey.PublicKey.N.Bytes()), + E: base64.RawURLEncoding.EncodeToString([]byte(strconv.Itoa(privateRSAKey.PublicKey.E))), + } + + err = db.CreateKeySet(context.TODO(), key) + if err != nil { + return err + } + + keySet := RSAKeySet{ + PrivateKey: privateRSAKey, + KID: kid.String(), + } + + tm.RSAKeySet = keySet + + return nil +} diff --git a/internal/token/manager.go b/internal/token/manager.go index c02484b87..8094bd131 100644 --- a/internal/token/manager.go +++ b/internal/token/manager.go @@ -3,17 +3,21 @@ package token import ( + "crypto/rsa" "time" - - "github.com/golang-jwt/jwt/v5" ) +type RSAKeySet struct { + PrivateKey *rsa.PrivateKey + KID string +} + type Manager struct { - // PrivateKey key used to sign tokens - PrivateKey string + // PrivateKeyHMAC is the private key used to sign and validate closed-system tokens + PrivateKeyHMAC string - // SignMethod method to sign tokens - SignMethod jwt.SigningMethod + // RSAKeySet is the private key used to sign and validate open-system tokens (OIDC) + RSAKeySet RSAKeySet // UserAccessTokenDuration specifies the token duration to use for users UserAccessTokenDuration time.Duration @@ -29,4 +33,10 @@ type Manager struct { // WorkerRegisterTokenDuration specifies the token duration for worker register WorkerRegisterTokenDuration time.Duration + + // IDTokenDuration specifies the token duration for ID tokens + IDTokenDuration time.Duration + + // Issuer specifies the issuer of the token + Issuer string } diff --git a/internal/token/mint.go b/internal/token/mint.go index f4a4338a7..d4984b9e3 100644 --- a/internal/token/mint.go +++ b/internal/token/mint.go @@ -3,24 +3,28 @@ package token import ( + "context" "errors" "fmt" "time" "github.com/golang-jwt/jwt/v5" + "gorm.io/gorm" api "github.com/go-vela/server/api/types" - "github.com/go-vela/types/constants" + "github.com/go-vela/server/constants" + "github.com/go-vela/server/database" ) // Claims struct is an extension of the JWT standard claims. It // includes information about the user. type Claims struct { - BuildID int64 `json:"build_id"` - IsActive bool `json:"is_active"` - IsAdmin bool `json:"is_admin"` - Repo string `json:"repo"` - TokenType string `json:"token_type"` + BuildID int64 `json:"build_id,omitempty"` + BuildNumber int `json:"build_number,omitempty"` + IsActive bool `json:"is_active,omitempty"` + IsAdmin bool `json:"is_admin,omitempty"` + Repo string `json:"repo,omitempty"` + TokenType string `json:"token_type,omitempty"` jwt.RegisteredClaims } @@ -28,11 +32,14 @@ type Claims struct { // the token. type MintTokenOpts struct { BuildID int64 + BuildNumber int Hostname string Repo string TokenDuration time.Duration TokenType string User *api.User + Audience []string + Commit string } // MintToken mints a Vela JWT Token given a set of options. @@ -75,6 +82,24 @@ func (tm *Manager) MintToken(mto *MintTokenOpts) (string, error) { claims.Subject = mto.Hostname + case constants.IDRequestTokenType: + if len(mto.Repo) == 0 { + return "", errors.New("missing repo for ID request token") + } + + if len(mto.Commit) == 0 { + return "", errors.New("missing commit for ID request token") + } + + if mto.BuildID == 0 { + return "", errors.New("missing build id for ID request token") + } + + claims.Repo = mto.Repo + claims.Subject = fmt.Sprintf("%s/%s", mto.Repo, mto.Commit) + claims.BuildID = mto.BuildID + claims.BuildNumber = mto.BuildNumber + default: return "", errors.New("invalid token type") } @@ -83,10 +108,61 @@ func (tm *Manager) MintToken(mto *MintTokenOpts) (string, error) { claims.ExpiresAt = jwt.NewNumericDate(time.Now().Add(mto.TokenDuration)) claims.TokenType = mto.TokenType - tk := jwt.NewWithClaims(tm.SignMethod, claims) + tk := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + + //sign token with configured private signing key + token, err := tk.SignedString([]byte(tm.PrivateKeyHMAC)) + if err != nil { + return "", fmt.Errorf("unable to sign token: %w", err) + } + + return token, nil +} + +func (tm *Manager) MintIDToken(mto *MintTokenOpts, db database.Interface) (string, error) { + // initialize claims struct + var claims = new(Claims) + + if len(mto.Repo) == 0 { + return "", errors.New("missing repo for ID token") + } + + if len(mto.Commit) == 0 { + return "", errors.New("missing commit for ID token") + } + + if mto.BuildNumber == 0 { + return "", errors.New("missing build id for ID token") + } + + claims.BuildNumber = mto.BuildNumber + claims.Repo = mto.Repo + claims.Subject = fmt.Sprintf("%s/%s", mto.Repo, mto.Commit) + claims.Audience = mto.Audience + + claims.IssuedAt = jwt.NewNumericDate(time.Now()) + claims.ExpiresAt = jwt.NewNumericDate(time.Now().Add(mto.TokenDuration)) + claims.TokenType = mto.TokenType + claims.Issuer = tm.Issuer + + tk := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) + + _, err := db.GetActiveKeySet(context.TODO(), tm.RSAKeySet.KID) + if err != nil { + if !errors.Is(err, gorm.ErrRecordNotFound) { + return "", fmt.Errorf("unable to get active key set: %w", err) + } + + err = tm.GenerateRSA(db) + if err != nil { + return "", fmt.Errorf("unable to generate RSA key set: %w", err) + } + } + + tk.Header["kid"] = tm.RSAKeySet.KID //sign token with configured private signing key - token, err := tk.SignedString([]byte(tm.PrivateKey)) + token, err := tk.SignedString(tm.RSAKeySet.PrivateKey) if err != nil { return "", fmt.Errorf("unable to sign token: %w", err) } diff --git a/internal/token/parse.go b/internal/token/parse.go index c42d90686..d5db3041e 100644 --- a/internal/token/parse.go +++ b/internal/token/parse.go @@ -42,7 +42,7 @@ func (tm *Manager) ParseToken(token string) (*Claims, error) { return nil, errors.New("token has no expiration") } - return []byte(tm.PrivateKey), err + return []byte(tm.PrivateKeyHMAC), err }) if err != nil { diff --git a/internal/token/parse_test.go b/internal/token/parse_test.go index a3efd14ea..2f1c94bcd 100644 --- a/internal/token/parse_test.go +++ b/internal/token/parse_test.go @@ -22,8 +22,7 @@ func TestTokenManager_ParseToken(t *testing.T) { u.SetToken("bar") tm := &Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -122,8 +121,7 @@ func TestTokenManager_ParseToken_Error_NoParse(t *testing.T) { u.SetToken("bar") tm := &Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -147,8 +145,7 @@ func TestTokenManager_ParseToken_Expired(t *testing.T) { u.SetToken("bar") tm := &Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -179,8 +176,7 @@ func TestTokenManager_ParseToken_NoSubject(t *testing.T) { u.SetToken("bar") tm := &Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -195,7 +191,7 @@ func TestTokenManager_ParseToken_NoSubject(t *testing.T) { } tkn := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - token, err := tkn.SignedString([]byte(tm.PrivateKey)) + token, err := tkn.SignedString([]byte(tm.PrivateKeyHMAC)) if err != nil { t.Errorf("Unable to create test token: %v", err) } @@ -219,8 +215,7 @@ func TestTokenManager_ParseToken_Error_InvalidSignature(t *testing.T) { u.SetToken("bar") tm := &Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -236,7 +231,7 @@ func TestTokenManager_ParseToken_Error_InvalidSignature(t *testing.T) { } tkn := jwt.NewWithClaims(jwt.SigningMethodHS512, claims) - token, err := tkn.SignedString([]byte(tm.PrivateKey)) + token, err := tkn.SignedString([]byte(tm.PrivateKeyHMAC)) if err != nil { t.Errorf("Unable to create test token: %v", err) } @@ -260,8 +255,7 @@ func TestToken_Parse_AccessToken_NoExpiration(t *testing.T) { u.SetToken("bar") tm := &Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } diff --git a/internal/token/refresh_test.go b/internal/token/refresh_test.go index 139bb6634..49c809eb6 100644 --- a/internal/token/refresh_test.go +++ b/internal/token/refresh_test.go @@ -10,7 +10,6 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/golang-jwt/jwt/v5" api "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" @@ -25,8 +24,7 @@ func TestTokenManager_Refresh(t *testing.T) { u.SetToken("bar") tm := &Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -85,8 +83,7 @@ func TestTokenManager_Refresh_Expired(t *testing.T) { u.SetToken("bar") tm := &Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } diff --git a/router/admin.go b/router/admin.go index c279445eb..5084c21c2 100644 --- a/router/admin.go +++ b/router/admin.go @@ -46,6 +46,9 @@ func AdminHandlers(base *gin.RouterGroup) { // Admin repo endpoint _admin.PUT("/repo", admin.UpdateRepo) + // Admin rotate keys endpoint + _admin.POST("/rotate_keys", admin.RotateOIDCKeys) + // Admin secret endpoint _admin.PUT("/secret", admin.UpdateSecret) diff --git a/router/build.go b/router/build.go index 44091b1f0..924576763 100644 --- a/router/build.go +++ b/router/build.go @@ -63,6 +63,8 @@ func BuildHandlers(base *gin.RouterGroup) { b.DELETE("/cancel", executors.Establish(), perm.MustWrite(), build.CancelBuild) b.GET("/logs", perm.MustRead(), log.ListLogsForBuild) b.GET("/token", perm.MustWorkerAuthToken(), build.GetBuildToken) + b.GET("/id_token", perm.MustIDRequestToken(), build.GetIDToken) + b.GET("/id_request_token", perm.MustBuildAccess(), build.GetIDRequestToken) b.GET("/graph", perm.MustRead(), build.GetBuildGraph) b.GET("/executable", perm.MustBuildAccess(), build.GetBuildExecutable) diff --git a/router/middleware/claims/claims_test.go b/router/middleware/claims/claims_test.go index 4ef838a3d..f8e0982fb 100644 --- a/router/middleware/claims/claims_test.go +++ b/router/middleware/claims/claims_test.go @@ -61,8 +61,7 @@ func TestClaims_Establish(t *testing.T) { user.SetFavorites([]string{}) tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, WorkerAuthTokenDuration: time.Minute * 20, @@ -223,8 +222,7 @@ func TestClaims_Establish(t *testing.T) { func TestClaims_Establish_NoToken(t *testing.T) { // setup types tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -249,8 +247,7 @@ func TestClaims_Establish_NoToken(t *testing.T) { func TestClaims_Establish_BadToken(t *testing.T) { // setup types tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } diff --git a/router/middleware/perm/perm.go b/router/middleware/perm/perm.go index f32d34dea..70788b241 100644 --- a/router/middleware/perm/perm.go +++ b/router/middleware/perm/perm.go @@ -10,6 +10,7 @@ import ( "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" + "github.com/go-vela/server/constants" "github.com/go-vela/server/router/middleware/build" "github.com/go-vela/server/router/middleware/claims" "github.com/go-vela/server/router/middleware/org" @@ -17,7 +18,6 @@ import ( "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/scm" "github.com/go-vela/server/util" - "github.com/go-vela/types/constants" ) // MustPlatformAdmin ensures the user has admin access to the platform. @@ -173,6 +173,47 @@ func MustBuildAccess() gin.HandlerFunc { } } +// MustIDRequestToken ensures the token is a valid ID request token for the appropriate build. +func MustIDRequestToken() gin.HandlerFunc { + return func(c *gin.Context) { + cl := claims.Retrieve(c) + b := build.Retrieve(c) + + // update engine logger with API metadata + // + // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields + logrus.WithFields(logrus.Fields{ + "repo": cl.Subject, + }).Debugf("verifying worker %s has a valid build token", cl.Subject) + + if !strings.EqualFold(cl.TokenType, constants.IDRequestTokenType) { + retErr := fmt.Errorf("invalid token: must provide a worker request ID token") + util.HandleError(c, http.StatusUnauthorized, retErr) + + return + } + + // if build is not in a running state, then an ID token should not be needed + if !strings.EqualFold(b.GetStatus(), constants.StatusRunning) { + retErr := fmt.Errorf("invalid request: build %d is not in a running state", b.GetID()) + util.HandleError(c, http.StatusBadRequest, retErr) + + return + } + + if b.GetID() != cl.BuildID { + logrus.WithFields(logrus.Fields{ + "user": cl.Subject, + "repo": cl.Repo, + "build": cl.BuildID, + }).Warnf("request ID token for build %d attempted to be used for %s build %d by %s", cl.BuildID, b.GetStatus(), b.GetID(), cl.Subject) + + retErr := fmt.Errorf("invalid token") + util.HandleError(c, http.StatusUnauthorized, retErr) + } + } +} + // MustSecretAdmin ensures the user has admin access to the org, repo or team. // //nolint:funlen // ignore function length diff --git a/router/middleware/perm/perm_test.go b/router/middleware/perm/perm_test.go index 8ca690b50..ba97f9229 100644 --- a/router/middleware/perm/perm_test.go +++ b/router/middleware/perm/perm_test.go @@ -11,9 +11,9 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/golang-jwt/jwt/v5" api "github.com/go-vela/server/api/types" + "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" @@ -23,7 +23,6 @@ import ( "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/scm" "github.com/go-vela/server/scm/github" - "github.com/go-vela/types/constants" ) func TestPerm_MustPlatformAdmin(t *testing.T) { @@ -31,8 +30,7 @@ func TestPerm_MustPlatformAdmin(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -112,8 +110,7 @@ func TestPerm_MustPlatformAdmin_NotAdmin(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -191,8 +188,7 @@ func TestPerm_MustPlatformAdmin_NotAdmin(t *testing.T) { func TestPerm_MustWorkerRegisterToken(t *testing.T) { // setup types tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, WorkerRegisterTokenDuration: time.Minute * 1, @@ -238,8 +234,7 @@ func TestPerm_MustWorkerRegisterToken(t *testing.T) { func TestPerm_MustWorkerRegisterToken_PlatAdmin(t *testing.T) { tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -304,8 +299,7 @@ func TestPerm_MustWorkerRegisterToken_PlatAdmin(t *testing.T) { func TestPerm_MustWorkerAuthToken(t *testing.T) { // setup types tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, WorkerRegisterTokenDuration: time.Minute * 1, @@ -353,8 +347,7 @@ func TestPerm_MustWorkerAuth_ServerWorkerToken(t *testing.T) { // setup types secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, WorkerRegisterTokenDuration: time.Minute * 1, @@ -413,8 +406,7 @@ func TestPerm_MustBuildAccess(t *testing.T) { b.SetNumber(1) tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -508,8 +500,7 @@ func TestPerm_MustBuildAccess_PlatAdmin(t *testing.T) { u.SetAdmin(true) tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -597,8 +588,7 @@ func TestPerm_MustBuildToken_WrongBuild(t *testing.T) { b.SetNumber(1) tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -664,6 +654,288 @@ func TestPerm_MustBuildToken_WrongBuild(t *testing.T) { } } +func TestPerm_MustIDRequestToken(t *testing.T) { + // setup types + secret := "superSecret" + + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) + r.SetID(1) + r.SetOwner(owner) + r.SetHash("baz") + r.SetOrg("foo") + r.SetName("bar") + r.SetFullName("foo/bar") + r.SetVisibility("public") + + b := new(api.Build) + b.SetID(1) + b.SetRepo(r) + b.SetNumber(1) + b.SetStatus(constants.StatusRunning) + b.SetCommit("456def") + + tm := &token.Manager{ + PrivateKeyHMAC: "123abc", + UserAccessTokenDuration: time.Minute * 5, + UserRefreshTokenDuration: time.Minute * 30, + } + + mto := &token.MintTokenOpts{ + Hostname: "foo/bar/456def", + BuildID: b.GetID(), + Commit: b.GetCommit(), + Repo: r.GetFullName(), + TokenDuration: time.Minute * 30, + TokenType: constants.IDRequestTokenType, + } + + tok, err := tm.MintToken(mto) + if err != nil { + t.Errorf("unable to mint token: %v", err) + } + + // setup context + gin.SetMode(gin.TestMode) + + resp := httptest.NewRecorder() + context, engine := gin.CreateTestContext(resp) + + // setup database + db, err := database.NewTest() + if err != nil { + t.Errorf("unable to create test database engine: %v", err) + } + + ctx := _context.TODO() + + defer func() { + _ = db.DeleteBuild(ctx, b) + _ = db.DeleteRepo(_context.TODO(), r) + db.Close() + }() + + _, _ = db.CreateRepo(_context.TODO(), r) + _, _ = db.CreateBuild(ctx, b) + + context.Request, _ = http.NewRequest(http.MethodGet, "/test/foo/bar/builds/1", nil) + 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(claims.Establish()) + engine.Use(user.Establish()) + engine.Use(org.Establish()) + engine.Use(repo.Establish()) + engine.Use(build.Establish()) + engine.Use(MustIDRequestToken()) + engine.GET("/test/:org/:repo/builds/:build", 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("MustIDRequestToken returned %v, want %v: %v", resp.Code, http.StatusOK, resp.Body.String()) + } +} + +func TestPerm_MustIDRequestToken_NotRunning(t *testing.T) { + // setup types + secret := "superSecret" + + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) + r.SetID(1) + r.SetOwner(owner) + r.SetHash("baz") + r.SetOrg("foo") + r.SetName("bar") + r.SetFullName("foo/bar") + r.SetVisibility("public") + + b := new(api.Build) + b.SetID(1) + b.SetRepo(r) + b.SetNumber(1) + b.SetStatus(constants.StatusSuccess) + b.SetCommit("456def") + + u := new(api.User) + u.SetID(1) + u.SetName("admin") + u.SetToken("bar") + u.SetAdmin(true) + + tm := &token.Manager{ + PrivateKeyHMAC: "123abc", + UserAccessTokenDuration: time.Minute * 5, + UserRefreshTokenDuration: time.Minute * 30, + } + + mto := &token.MintTokenOpts{ + Hostname: "foo/bar/456def", + BuildID: b.GetID(), + Commit: b.GetCommit(), + Repo: "foo/bar", + TokenDuration: time.Minute * 30, + TokenType: constants.IDRequestTokenType, + } + + tok, _ := tm.MintToken(mto) + + // setup context + gin.SetMode(gin.TestMode) + + resp := httptest.NewRecorder() + context, engine := gin.CreateTestContext(resp) + + ctx := _context.TODO() + + // setup database + db, err := database.NewTest() + if err != nil { + t.Errorf("unable to create test database engine: %v", err) + } + + defer func() { + _ = db.DeleteBuild(ctx, b) + _ = db.DeleteRepo(_context.TODO(), r) + _ = db.DeleteUser(_context.TODO(), u) + db.Close() + }() + + _, _ = db.CreateRepo(_context.TODO(), r) + _, _ = db.CreateBuild(ctx, b) + _, _ = db.CreateUser(_context.TODO(), u) + + context.Request, _ = http.NewRequest(http.MethodGet, "/test/foo/bar/builds/1", nil) + 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(claims.Establish()) + engine.Use(user.Establish()) + engine.Use(org.Establish()) + engine.Use(repo.Establish()) + engine.Use(build.Establish()) + engine.Use(MustIDRequestToken()) + engine.GET("/test/:org/:repo/builds/:build", 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.StatusBadRequest { + t.Errorf("MustIDRequestToken returned %v, want %v", resp.Code, http.StatusOK) + } +} + +func TestPerm_MustIDRequestToken_WrongBuild(t *testing.T) { + // setup types + secret := "superSecret" + + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) + r.SetID(1) + r.SetOwner(owner) + r.SetHash("baz") + r.SetOrg("foo") + r.SetName("bar") + r.SetFullName("foo/bar") + r.SetVisibility("public") + + b := new(api.Build) + b.SetID(1) + b.SetRepo(r) + b.SetNumber(1) + + tm := &token.Manager{ + PrivateKeyHMAC: "123abc", + UserAccessTokenDuration: time.Minute * 5, + UserRefreshTokenDuration: time.Minute * 30, + } + + mto := &token.MintTokenOpts{ + Hostname: "foo/bar/456def", + BuildID: 2, + Commit: b.GetCommit(), + Repo: "foo/bar", + TokenDuration: time.Minute * 30, + TokenType: constants.IDRequestTokenType, + } + + tok, _ := tm.MintToken(mto) + + // setup context + gin.SetMode(gin.TestMode) + + resp := httptest.NewRecorder() + context, engine := gin.CreateTestContext(resp) + + ctx := _context.TODO() + + // setup database + db, err := database.NewTest() + if err != nil { + t.Errorf("unable to create test database engine: %v", err) + } + + defer func() { + _ = db.DeleteBuild(ctx, b) + _ = db.DeleteRepo(_context.TODO(), r) + db.Close() + }() + + _, _ = db.CreateRepo(_context.TODO(), r) + _, _ = db.CreateBuild(ctx, b) + + context.Request, _ = http.NewRequest(http.MethodGet, "/test/foo/bar/builds/1", nil) + 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(claims.Establish()) + engine.Use(user.Establish()) + engine.Use(org.Establish()) + engine.Use(repo.Establish()) + engine.Use(build.Establish()) + engine.Use(MustIDRequestToken()) + engine.GET("/test/:org/:repo/builds/:build", 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.StatusUnauthorized { + t.Errorf("MustBuildAccess returned %v, want %v", resp.Code, http.StatusOK) + } +} + func TestPerm_MustSecretAdmin_BuildToken_Repo(t *testing.T) { // setup types secret := "superSecret" @@ -686,8 +958,7 @@ func TestPerm_MustSecretAdmin_BuildToken_Repo(t *testing.T) { b.SetNumber(1) tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -772,8 +1043,7 @@ func TestPerm_MustSecretAdmin_BuildToken_Org(t *testing.T) { b.SetNumber(1) tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -858,8 +1128,7 @@ func TestPerm_MustSecretAdmin_BuildToken_Shared(t *testing.T) { b.SetNumber(1) tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -927,8 +1196,7 @@ func TestPerm_MustAdmin(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -1027,8 +1295,7 @@ func TestPerm_MustAdmin_PlatAdmin(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -1127,8 +1394,7 @@ func TestPerm_MustAdmin_NotAdmin(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -1227,8 +1493,7 @@ func TestPerm_MustWrite(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -1327,8 +1592,7 @@ func TestPerm_MustWrite_PlatAdmin(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -1427,8 +1691,7 @@ func TestPerm_MustWrite_RepoAdmin(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -1527,8 +1790,7 @@ func TestPerm_MustWrite_NotWrite(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -1627,8 +1889,7 @@ func TestPerm_MustRead(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -1727,8 +1988,7 @@ func TestPerm_MustRead_PlatAdmin(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -1827,8 +2087,7 @@ func TestPerm_MustRead_WorkerBuildToken(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -1916,8 +2175,7 @@ func TestPerm_MustRead_RepoAdmin(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -2016,8 +2274,7 @@ func TestPerm_MustRead_RepoWrite(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -2116,8 +2373,7 @@ func TestPerm_MustRead_RepoPublic(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -2216,8 +2472,7 @@ func TestPerm_MustRead_NotRead(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } diff --git a/router/middleware/pipeline/pipeline_test.go b/router/middleware/pipeline/pipeline_test.go index 489025859..a20536be1 100644 --- a/router/middleware/pipeline/pipeline_test.go +++ b/router/middleware/pipeline/pipeline_test.go @@ -13,7 +13,6 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/golang-jwt/jwt/v5" "github.com/urfave/cli/v2" api "github.com/go-vela/server/api/types" @@ -227,8 +226,7 @@ func TestPipeline_Establish_NoPipeline(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } diff --git a/router/middleware/token_manager_test.go b/router/middleware/token_manager_test.go index 8f8bb4fa2..6daee4e88 100644 --- a/router/middleware/token_manager_test.go +++ b/router/middleware/token_manager_test.go @@ -21,7 +21,7 @@ func TestMiddleware_TokenManager(t *testing.T) { var got *token.Manager want := new(token.Manager) - want.PrivateKey = "123abc" + want.PrivateKeyHMAC = "123abc" // setup context gin.SetMode(gin.TestMode) diff --git a/router/middleware/user/user_test.go b/router/middleware/user/user_test.go index a6809c334..8e15f80f7 100644 --- a/router/middleware/user/user_test.go +++ b/router/middleware/user/user_test.go @@ -12,7 +12,6 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/golang-jwt/jwt/v5" api "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" @@ -47,8 +46,7 @@ func TestUser_Establish(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -144,8 +142,7 @@ func TestUser_Establish_NoToken(t *testing.T) { // setup types secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -183,8 +180,7 @@ func TestUser_Establish_DiffTokenType(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -232,8 +228,7 @@ func TestUser_Establish_NoAuthorizeUser(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -274,8 +269,7 @@ func TestUser_Establish_NoAuthorizeUser(t *testing.T) { func TestUser_Establish_NoUser(t *testing.T) { // setup types tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } diff --git a/router/router.go b/router/router.go index 49d8f6012..d271e44f2 100644 --- a/router/router.go +++ b/router/router.go @@ -89,6 +89,10 @@ func Load(options ...gin.HandlerFunc) *gin.Engine { // Webhook endpoint r.POST("/webhook", webhook.PostWebhook) + // JWKS endpoints + r.GET("_services/token/.well-known/openid-configuration", api.GetOpenIDConfig) + r.GET("_services/token/.well-known/jwks", api.GetJWKS) + // Authentication endpoints authenticate := r.Group("/authenticate") { From 9a351077d1531169a27782a63ded6cd7a4990d8e Mon Sep 17 00:00:00 2001 From: ecrupper Date: Mon, 29 Apr 2024 14:14:44 -0500 Subject: [PATCH 02/34] some renaming and comment fixing --- api/admin/rotate_keys.go | 10 +++---- api/build/id_request_token.go | 8 ++---- api/build/id_token.go | 4 --- api/jwks.go | 9 ++++--- api/oi_config.go | 2 +- api/types/oidc.go | 3 ++- database/database.go | 4 +-- database/interface.go | 6 ++--- database/{keyset => jwk}/create.go | 7 +++-- database/{keyset => jwk}/get.go | 6 ++--- database/jwk/interface.go | 33 +++++++++++++++++++++++ database/{keyset/keyset.go => jwk/jwk.go} | 6 ++--- database/{keyset => jwk}/list.go | 6 ++--- database/{keyset => jwk}/opts.go | 2 +- database/{keyset => jwk}/rotate.go | 4 +-- database/{keyset => jwk}/table.go | 20 +++++++------- database/keyset/interface.go | 31 --------------------- database/resource.go | 14 +++++----- internal/token/generate_rsa.go | 16 ++++++++--- internal/token/mint.go | 11 ++++++-- router/admin.go | 2 +- router/middleware/perm/perm.go | 7 ++--- 22 files changed, 111 insertions(+), 100 deletions(-) rename database/{keyset => jwk}/create.go (80%) rename database/{keyset => jwk}/get.go (77%) create mode 100644 database/jwk/interface.go rename database/{keyset/keyset.go => jwk/jwk.go} (93%) rename database/{keyset => jwk}/list.go (82%) rename database/{keyset => jwk}/opts.go (99%) rename database/{keyset => jwk}/rotate.go (90%) rename database/{keyset => jwk}/table.go (61%) delete mode 100644 database/keyset/interface.go diff --git a/api/admin/rotate_keys.go b/api/admin/rotate_keys.go index 7be3a3e16..04e2d3795 100644 --- a/api/admin/rotate_keys.go +++ b/api/admin/rotate_keys.go @@ -13,7 +13,7 @@ import ( "github.com/go-vela/server/util" ) -// swagger:operation POST /api/v1/admin/rotate_oidc admin AdminRotateOIDCKeys +// swagger:operation POST /api/v1/admin/rotate_oidc_keys admin AdminRotateOIDCKeys // // Rotate RSA Keys // @@ -25,11 +25,11 @@ import ( // - ApiKeyAuth: [] // responses: // '200': -// description: Successfully updated the repo in the database +// description: Successfully rotated OIDC provider keys // schema: -// "$ref": "#/definitions/Repo" -// '501': -// description: Unable to update the repo in the database +// type: string +// '500': +// description: Error unable to rotate OIDC provider keys // schema: // "$ref": "#/definitions/Error" diff --git a/api/build/id_request_token.go b/api/build/id_request_token.go index 73c65a9fd..5b2327032 100644 --- a/api/build/id_request_token.go +++ b/api/build/id_request_token.go @@ -47,19 +47,15 @@ import ( // - ApiKeyAuth: [] // responses: // '200': -// description: Successfully retrieved ID token +// description: Successfully retrieved ID Request token // schema: // "$ref": "#/definitions/Token" // '400': // description: Bad request // schema: // "$ref": "#/definitions/Error" -// '409': -// description: Conflict (requested id token for build not in running state) -// schema: -// "$ref": "#/definitions/Error" // '500': -// description: Unable to generate id token +// description: Unable to generate ID request token // schema: // "$ref": "#/definitions/Error" diff --git a/api/build/id_token.go b/api/build/id_token.go index 763b49d00..10a227132 100644 --- a/api/build/id_token.go +++ b/api/build/id_token.go @@ -54,10 +54,6 @@ import ( // description: Bad request // schema: // "$ref": "#/definitions/Error" -// '409': -// description: Conflict (requested id token for build not in running state) -// schema: -// "$ref": "#/definitions/Error" // '500': // description: Unable to generate id token // schema: diff --git a/api/jwks.go b/api/jwks.go index 471514f2a..ce05779c3 100644 --- a/api/jwks.go +++ b/api/jwks.go @@ -7,6 +7,7 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" "github.com/go-vela/server/util" @@ -26,14 +27,14 @@ import ( // '200': // description: Successfully retrieved the Vela JWKS // schema: -// "$ref": "#/definitions/PublicKey" +// "$ref": "#/definitions/JWKS" // GetJWKS represents the API handler for requests to public keys in the Vela OpenID service. func GetJWKS(c *gin.Context) { - // retrieve token manager from context - keys, err := database.FromContext(c).ListKeySets(c) + // retrieve JWKs from the database + keys, err := database.FromContext(c).ListJWKs(c) if err != nil { - retErr := fmt.Errorf("unable to get key sets: %w", err) + retErr := fmt.Errorf("unable to get key set: %w", err) util.HandleError(c, http.StatusInternalServerError, retErr) } diff --git a/api/oi_config.go b/api/oi_config.go index 721f71863..2df130421 100644 --- a/api/oi_config.go +++ b/api/oi_config.go @@ -27,7 +27,7 @@ import ( // '200': // description: Successfully retrieved the Vela OpenID Configuration // schema: -// "$ref": "#/definitions/PublicKey" +// "$ref": "#/definitions/OpenIDConfig" // GetOpenIDConfig represents the API handler for requests for configurations in the Vela OpenID service. func GetOpenIDConfig(c *gin.Context) { diff --git a/api/types/oidc.go b/api/types/oidc.go index cf48d7a69..405d5c187 100644 --- a/api/types/oidc.go +++ b/api/types/oidc.go @@ -2,6 +2,7 @@ package types +// OpenIDConfig is a struct that represents the OpenID Connect configuration. type OpenIDConfig struct { Issuer string `json:"issuer"` JWKSAddress string `json:"jwks_uri"` @@ -9,7 +10,7 @@ type OpenIDConfig struct { Algorithms []string `json:"id_token_signing_alg_values_supported"` } -// JWKS is a slice of JWKs +// JWKS is a slice of JWKs. type JWKS struct { Keys []JWK `json:"keys"` } diff --git a/database/database.go b/database/database.go index 9b508b09c..a1c882e20 100644 --- a/database/database.go +++ b/database/database.go @@ -17,7 +17,7 @@ import ( "github.com/go-vela/server/database/deployment" "github.com/go-vela/server/database/executable" "github.com/go-vela/server/database/hook" - "github.com/go-vela/server/database/keyset" + "github.com/go-vela/server/database/jwk" "github.com/go-vela/server/database/log" "github.com/go-vela/server/database/pipeline" "github.com/go-vela/server/database/repo" @@ -67,7 +67,7 @@ type ( executable.BuildExecutableInterface deployment.DeploymentInterface hook.HookInterface - keyset.KeySetInterface + jwk.JWKInterface log.LogInterface pipeline.PipelineInterface repo.RepoInterface diff --git a/database/interface.go b/database/interface.go index 182ca00f7..b5a61d415 100644 --- a/database/interface.go +++ b/database/interface.go @@ -8,7 +8,7 @@ import ( "github.com/go-vela/server/database/deployment" "github.com/go-vela/server/database/executable" "github.com/go-vela/server/database/hook" - "github.com/go-vela/server/database/keyset" + "github.com/go-vela/server/database/jwk" "github.com/go-vela/server/database/log" "github.com/go-vela/server/database/pipeline" "github.com/go-vela/server/database/repo" @@ -50,8 +50,8 @@ type Interface interface { // HookInterface defines the interface for hooks stored in the database. hook.HookInterface - // KeySetInterface defines the interface for key sets stored in the database. - keyset.KeySetInterface + // JWKInterface defines the interface for JWKs stored in the database. + jwk.JWKInterface // LogInterface defines the interface for logs stored in the database. log.LogInterface diff --git a/database/keyset/create.go b/database/jwk/create.go similarity index 80% rename from database/keyset/create.go rename to database/jwk/create.go index 0b9018462..c728973f7 100644 --- a/database/keyset/create.go +++ b/database/jwk/create.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 -package keyset +package jwk import ( "context" @@ -13,8 +13,8 @@ import ( "github.com/go-vela/server/database/types" ) -// CreateKeySet creates a new dashboard in the database. -func (e *engine) CreateKeySet(ctx context.Context, j api.JWK) error { +// CreateJWK creates a new JWK in the database. +func (e *engine) CreateJWK(_ context.Context, j api.JWK) error { e.logger.WithFields(logrus.Fields{ "jwk": j.Kid, }).Tracef("creating key %s in the database", j.Kid) @@ -29,5 +29,4 @@ func (e *engine) CreateKeySet(ctx context.Context, j api.JWK) error { // send query to the database return e.client.Table(constants.TableKeySet).Create(key).Error - } diff --git a/database/keyset/get.go b/database/jwk/get.go similarity index 77% rename from database/keyset/get.go rename to database/jwk/get.go index 6605e75a3..ed0d22004 100644 --- a/database/keyset/get.go +++ b/database/jwk/get.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 -package keyset +package jwk import ( "context" @@ -10,8 +10,8 @@ import ( "github.com/go-vela/server/database/types" ) -// GetKeySet gets a key by UUID from the database if active. -func (e *engine) GetActiveKeySet(ctx context.Context, id string) (api.JWK, error) { +// GetActiveJWK gets a JWK by UUID (kid) from the database if active. +func (e *engine) GetActiveJWK(_ context.Context, id string) (api.JWK, error) { e.logger.Tracef("getting key %s from the database", id) // variable to store query results diff --git a/database/jwk/interface.go b/database/jwk/interface.go new file mode 100644 index 000000000..38ef437d6 --- /dev/null +++ b/database/jwk/interface.go @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 + +package jwk + +import ( + "context" + + api "github.com/go-vela/server/api/types" +) + +// JWKInterface represents the Vela interface for JWK +// functions with the supported Database backends. +// +//nolint:revive // ignore name stutter +type JWKInterface interface { + // JWK Data Definition Language Functions + // + // https://en.wikipedia.org/wiki/Data_definition_language + CreateJWKTable(context.Context, string) error + + // JWK Data Manipulation Language Functions + // + // https://en.wikipedia.org/wiki/Data_manipulation_language + + // CreateJWK defines a function that creates a JWK. + CreateJWK(context.Context, api.JWK) error + // RotateKeys defines a function that rotates JWKs. + RotateKeys(context.Context) error + // ListJWKs defines a function that lists all JWKs configured. + ListJWKs(context.Context) ([]api.JWK, error) + // GetJWK defines a function that gets a JWK by the provided key ID. + GetActiveJWK(context.Context, string) (api.JWK, error) +} diff --git a/database/keyset/keyset.go b/database/jwk/jwk.go similarity index 93% rename from database/keyset/keyset.go rename to database/jwk/jwk.go index 96cb6734b..404150256 100644 --- a/database/keyset/keyset.go +++ b/database/jwk/jwk.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 -package keyset +package jwk import ( "context" @@ -67,8 +67,8 @@ func New(opts ...EngineOpt) (*engine, error) { return e, nil } - // create the key sets table - err := e.CreateKeySetTable(e.ctx, e.client.Config.Dialector.Name()) + // create the JWK table + err := e.CreateJWKTable(e.ctx, e.client.Config.Dialector.Name()) if err != nil { return nil, fmt.Errorf("unable to create %s table: %w", constants.TableKeySet, err) } diff --git a/database/keyset/list.go b/database/jwk/list.go similarity index 82% rename from database/keyset/list.go rename to database/jwk/list.go index 204bdf8a7..a810a7d3c 100644 --- a/database/keyset/list.go +++ b/database/jwk/list.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 -package keyset +package jwk import ( "context" @@ -10,8 +10,8 @@ import ( "github.com/go-vela/server/database/types" ) -// ListKeySets gets a list of all schedules from the database. -func (e *engine) ListKeySets(ctx context.Context) ([]api.JWK, error) { +// ListJWKs gets a list of all configured JWKs from the database. +func (e *engine) ListJWKs(_ context.Context) ([]api.JWK, error) { e.logger.Trace("listing all keysets from the database") k := new([]types.JWK) diff --git a/database/keyset/opts.go b/database/jwk/opts.go similarity index 99% rename from database/keyset/opts.go rename to database/jwk/opts.go index 8a94e72dd..c2accdcb9 100644 --- a/database/keyset/opts.go +++ b/database/jwk/opts.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 -package keyset +package jwk import ( "context" diff --git a/database/keyset/rotate.go b/database/jwk/rotate.go similarity index 90% rename from database/keyset/rotate.go rename to database/jwk/rotate.go index 07244485e..50e6d8580 100644 --- a/database/keyset/rotate.go +++ b/database/jwk/rotate.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 -package keyset +package jwk import ( "context" @@ -11,7 +11,7 @@ import ( ) // RotateKeys removes all inactive keys and sets active keys to inactive. -func (e *engine) RotateKeys(ctx context.Context) error { +func (e *engine) RotateKeys(_ context.Context) error { e.logger.Trace("rotating keysets in the database") k := types.JWK{} diff --git a/database/keyset/table.go b/database/jwk/table.go similarity index 61% rename from database/keyset/table.go rename to database/jwk/table.go index 3c63432fe..6e2108de9 100644 --- a/database/keyset/table.go +++ b/database/jwk/table.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 -package keyset +package jwk import ( "context" @@ -9,22 +9,22 @@ import ( ) const ( - // CreatePostgresTable represents a query to create the Postgres keysets table. + // CreatePostgresTable represents a query to create the Postgres jwks table. CreatePostgresTable = ` CREATE TABLE IF NOT EXISTS -keysets ( +jwks ( id UUID PRIMARY KEY, active BOOLEAN, key JSON DEFAULT NULL ); ` - // CreateSqliteTable represents a query to create the Sqlite keysets table. + // CreateSqliteTable represents a query to create the Sqlite jwks table. CreateSqliteTable = ` CREATE TABLE IF NOT EXISTS -keysets ( +jwks ( id TEXT PRIMARY KEY, active BOOLEAN, key TEXT @@ -32,19 +32,19 @@ keysets ( ` ) -// CreateKeySetTable creates the build executables table in the database. -func (e *engine) CreateKeySetTable(ctx context.Context, driver string) error { - e.logger.Tracef("creating keysets table in the database") +// CreateJWKTable creates the jwks table in the database. +func (e *engine) CreateJWKTable(ctx context.Context, driver string) error { + e.logger.Tracef("creating jwks table in the database") // handle the driver provided to create the table switch driver { case constants.DriverPostgres: - // create the keysets table for Postgres + // create the jwks table for Postgres return e.client.Exec(CreatePostgresTable).Error case constants.DriverSqlite: fallthrough default: - // create the keysets table for Sqlite + // create the jwks table for Sqlite return e.client.Exec(CreateSqliteTable).Error } } diff --git a/database/keyset/interface.go b/database/keyset/interface.go deleted file mode 100644 index b297c8460..000000000 --- a/database/keyset/interface.go +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package keyset - -import ( - "context" - - api "github.com/go-vela/server/api/types" -) - -// KeySetInterface represents the Vela interface for key set -// functions with the supported Database backends. -type KeySetInterface interface { - // KeySet Data Definition Language Functions - // - // https://en.wikipedia.org/wiki/Data_definition_language - CreateKeySetTable(context.Context, string) error - - // KeySet Data Manipulation Language Functions - // - // https://en.wikipedia.org/wiki/Data_manipulation_language - - // CreateKeySet defines a function that creates a key set. - CreateKeySet(context.Context, api.JWK) error - // DeleteKeySet defines a function that gets and deletes a key set. - RotateKeys(context.Context) error - // ListKeySets defines a function that lists all key sets configured. - ListKeySets(context.Context) ([]api.JWK, error) - // GetKeySet defines a function that gets a key set by the provided key ID. - GetActiveKeySet(context.Context, string) (api.JWK, error) -} diff --git a/database/resource.go b/database/resource.go index 540cd2846..fb8af0e9c 100644 --- a/database/resource.go +++ b/database/resource.go @@ -10,7 +10,7 @@ import ( "github.com/go-vela/server/database/deployment" "github.com/go-vela/server/database/executable" "github.com/go-vela/server/database/hook" - "github.com/go-vela/server/database/keyset" + "github.com/go-vela/server/database/jwk" "github.com/go-vela/server/database/log" "github.com/go-vela/server/database/pipeline" "github.com/go-vela/server/database/repo" @@ -83,12 +83,12 @@ func (e *engine) NewResources(ctx context.Context) error { return err } - // create the database agnostic engine for keysets - e.KeySetInterface, err = keyset.New( - keyset.WithContext(e.ctx), - keyset.WithClient(e.client), - keyset.WithLogger(e.logger), - keyset.WithSkipCreation(e.config.SkipCreation), + // create the database agnostic engine for JWKs + e.JWKInterface, err = jwk.New( + jwk.WithContext(e.ctx), + jwk.WithClient(e.client), + jwk.WithLogger(e.logger), + jwk.WithSkipCreation(e.config.SkipCreation), ) // create the database agnostic engine for logs diff --git a/internal/token/generate_rsa.go b/internal/token/generate_rsa.go index f1277e3fd..2b4481ee9 100644 --- a/internal/token/generate_rsa.go +++ b/internal/token/generate_rsa.go @@ -9,36 +9,44 @@ import ( "encoding/base64" "strconv" - api "github.com/go-vela/server/api/types" - "github.com/go-vela/server/database" "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database" ) -// MintToken mints a Vela JWT Token given a set of options. +// GenerateRSA creates an RSA key pair and sets it in the token manager and saves the JWK in the database. func (tm *Manager) GenerateRSA(db database.Interface) error { + // generate key pair privateRSAKey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { return err } + // assign KID to key pair kid, err := uuid.NewRandom() if err != nil { return err } + // abstract the JWK from the public key information key := api.JWK{ Algorithm: jwt.SigningMethodRS256.Name, Kid: kid.String(), + Use: "sig", + Kty: "RSA", N: base64.RawURLEncoding.EncodeToString(privateRSAKey.PublicKey.N.Bytes()), E: base64.RawURLEncoding.EncodeToString([]byte(strconv.Itoa(privateRSAKey.PublicKey.E))), } - err = db.CreateKeySet(context.TODO(), key) + // create the JWK in the database + err = db.CreateJWK(context.TODO(), key) if err != nil { return err } + // create the RSA key set for token manager keySet := RSAKeySet{ PrivateKey: privateRSAKey, KID: kid.String(), diff --git a/internal/token/mint.go b/internal/token/mint.go index d4984b9e3..31c68b381 100644 --- a/internal/token/mint.go +++ b/internal/token/mint.go @@ -119,10 +119,12 @@ func (tm *Manager) MintToken(mto *MintTokenOpts) (string, error) { return token, nil } +// MintIDToken mints a Vela JWT ID Token for a build. func (tm *Manager) MintIDToken(mto *MintTokenOpts, db database.Interface) (string, error) { // initialize claims struct var claims = new(Claims) + // validate provided claims if len(mto.Repo) == 0 { return "", errors.New("missing repo for ID token") } @@ -135,30 +137,35 @@ func (tm *Manager) MintIDToken(mto *MintTokenOpts, db database.Interface) (strin return "", errors.New("missing build id for ID token") } + // set claims based on input claims.BuildNumber = mto.BuildNumber claims.Repo = mto.Repo claims.Subject = fmt.Sprintf("%s/%s", mto.Repo, mto.Commit) claims.Audience = mto.Audience + claims.TokenType = mto.TokenType + // set standard claims claims.IssuedAt = jwt.NewNumericDate(time.Now()) claims.ExpiresAt = jwt.NewNumericDate(time.Now().Add(mto.TokenDuration)) - claims.TokenType = mto.TokenType claims.Issuer = tm.Issuer tk := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) - _, err := db.GetActiveKeySet(context.TODO(), tm.RSAKeySet.KID) + // verify key is active in the database before signing + _, err := db.GetActiveJWK(context.TODO(), tm.RSAKeySet.KID) if err != nil { if !errors.Is(err, gorm.ErrRecordNotFound) { return "", fmt.Errorf("unable to get active key set: %w", err) } + // generate a new RSA key set if previous key is inactive (rotated) err = tm.GenerateRSA(db) if err != nil { return "", fmt.Errorf("unable to generate RSA key set: %w", err) } } + // set KID header tk.Header["kid"] = tm.RSAKeySet.KID //sign token with configured private signing key diff --git a/router/admin.go b/router/admin.go index 5084c21c2..8882be952 100644 --- a/router/admin.go +++ b/router/admin.go @@ -47,7 +47,7 @@ func AdminHandlers(base *gin.RouterGroup) { _admin.PUT("/repo", admin.UpdateRepo) // Admin rotate keys endpoint - _admin.POST("/rotate_keys", admin.RotateOIDCKeys) + _admin.POST("/rotate_oidc_keys", admin.RotateOIDCKeys) // Admin secret endpoint _admin.PUT("/secret", admin.UpdateSecret) diff --git a/router/middleware/perm/perm.go b/router/middleware/perm/perm.go index 70788b241..e0f11979d 100644 --- a/router/middleware/perm/perm.go +++ b/router/middleware/perm/perm.go @@ -186,8 +186,9 @@ func MustIDRequestToken() gin.HandlerFunc { "repo": cl.Subject, }).Debugf("verifying worker %s has a valid build token", cl.Subject) + // verify expected type if !strings.EqualFold(cl.TokenType, constants.IDRequestTokenType) { - retErr := fmt.Errorf("invalid token: must provide a worker request ID token") + retErr := fmt.Errorf("invalid token: must provide a valid request ID token") util.HandleError(c, http.StatusUnauthorized, retErr) return @@ -195,12 +196,12 @@ func MustIDRequestToken() gin.HandlerFunc { // if build is not in a running state, then an ID token should not be needed if !strings.EqualFold(b.GetStatus(), constants.StatusRunning) { - retErr := fmt.Errorf("invalid request: build %d is not in a running state", b.GetID()) - util.HandleError(c, http.StatusBadRequest, retErr) + util.HandleError(c, http.StatusBadRequest, fmt.Errorf("invalid request")) return } + // verify expected build id if b.GetID() != cl.BuildID { logrus.WithFields(logrus.Fields{ "user": cl.Subject, From a089ee24c25aabb5fd723d568b47068784b5da3b Mon Sep 17 00:00:00 2001 From: ecrupper Date: Tue, 30 Apr 2024 15:18:43 -0500 Subject: [PATCH 03/34] tests and renaming things here and there, plus pull in types --- api/build/id_request_token.go | 16 +- api/build/id_token.go | 4 +- api/build/token.go | 2 +- api/jwks.go | 2 + compiler/native/environment_test.go | 16 +- constants/table.go | 4 +- database/jwk/create.go | 2 +- database/jwk/create_test.go | 68 ++++++++ database/jwk/get.go | 2 +- database/jwk/get_test.go | 88 ++++++++++ database/jwk/jwk.go | 2 +- database/jwk/jwk_test.go | 163 +++++++++++++++++++ database/jwk/list.go | 2 +- database/jwk/list_test.go | 102 ++++++++++++ database/jwk/opts.go | 10 -- database/jwk/opts_test.go | 208 ++++++++++++++++++++++++ database/jwk/rotate.go | 4 +- database/jwk/rotate_test.go | 128 +++++++++++++++ database/jwk/table_test.go | 58 +++++++ database/testutils/api_resources.go | 11 ++ go.mod | 2 + go.sum | 2 - internal/token/generate_rsa.go | 2 +- internal/token/mint.go | 28 +++- internal/token/parse_test.go | 7 +- router/middleware/claims/claims_test.go | 7 +- router/middleware/perm/perm_test.go | 24 ++- 27 files changed, 911 insertions(+), 53 deletions(-) create mode 100644 database/jwk/create_test.go create mode 100644 database/jwk/get_test.go create mode 100644 database/jwk/jwk_test.go create mode 100644 database/jwk/list_test.go create mode 100644 database/jwk/opts_test.go create mode 100644 database/jwk/rotate_test.go create mode 100644 database/jwk/table_test.go diff --git a/api/build/id_request_token.go b/api/build/id_request_token.go index 5b2327032..338d4704f 100644 --- a/api/build/id_request_token.go +++ b/api/build/id_request_token.go @@ -43,6 +43,14 @@ import ( // 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 // security: // - ApiKeyAuth: [] // responses: @@ -77,6 +85,9 @@ func GetIDRequestToken(c *gin.Context) { "user": cl.Subject, }).Infof("generating ID request token for build %s/%d", r.GetFullName(), b.GetNumber()) + image := c.Query("image") + request := c.Query("request") + // retrieve token manager from context tm := c.MustGet("token-manager").(*token.Manager) @@ -84,12 +95,13 @@ func GetIDRequestToken(c *gin.Context) { // set mint token options idmto := &token.MintTokenOpts{ - BuildID: b.GetID(), - BuildNumber: b.GetNumber(), + Build: b, Repo: r.GetFullName(), TokenType: constants.IDRequestTokenType, Commit: b.GetCommit(), TokenDuration: exp, + Image: image, + Request: request, } // mint token diff --git a/api/build/id_token.go b/api/build/id_token.go index 10a227132..b0146651a 100644 --- a/api/build/id_token.go +++ b/api/build/id_token.go @@ -82,11 +82,13 @@ func GetIDToken(c *gin.Context) { // set mint token options idmto := &token.MintTokenOpts{ - BuildNumber: b.GetNumber(), + Build: b, Repo: r.GetFullName(), TokenType: constants.IDTokenType, Commit: b.GetCommit(), TokenDuration: tm.IDTokenDuration, + Image: cl.Image, + Request: cl.Request, } // if audience is provided, include that in claims diff --git a/api/build/token.go b/api/build/token.go index 4c6de18ec..fd0664684 100644 --- a/api/build/token.go +++ b/api/build/token.go @@ -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, diff --git a/api/jwks.go b/api/jwks.go index ce05779c3..13323fd27 100644 --- a/api/jwks.go +++ b/api/jwks.go @@ -36,6 +36,8 @@ func GetJWKS(c *gin.Context) { if err != nil { retErr := fmt.Errorf("unable to get key set: %w", err) util.HandleError(c, http.StatusInternalServerError, retErr) + + return } c.JSON(http.StatusOK, types.JWKS{Keys: keys}) diff --git a/compiler/native/environment_test.go b/compiler/native/environment_test.go index 1e090dad1..ab50476a1 100644 --- a/compiler/native/environment_test.go +++ b/compiler/native/environment_test.go @@ -589,7 +589,7 @@ func TestNative_environment(t *testing.T) { m: &internal.Metadata{Database: &internal.Database{Driver: str, Host: str}, Queue: &internal.Queue{Channel: str, Driver: str, Host: str}, Source: &internal.Source{Driver: str, Host: str}, Vela: &internal.Vela{Address: str, WebAddress: str}}, r: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, u: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, - want: map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "push", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "foo", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "push", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "foo", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_BRANCH": "foo", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_OWNER": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"}, + want: map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "push", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "foo", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "push", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "foo", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_BRANCH": "foo", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_OWNER": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_ID_TOKEN_REQUEST_URL": "foo/api/v1/repos/foo/builds/1/id_token"}, }, // tag { @@ -598,7 +598,7 @@ func TestNative_environment(t *testing.T) { m: &internal.Metadata{Database: &internal.Database{Driver: str, Host: str}, Queue: &internal.Queue{Channel: str, Driver: str, Host: str}, Source: &internal.Source{Driver: str, Host: str}, Vela: &internal.Vela{Address: str, WebAddress: str}}, r: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, u: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, - want: map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "tag", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "refs/tags/1", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TAG": "1", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "tag", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "refs/tags/1", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TAG": "1", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_BRANCH": "foo", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_OWNER": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"}, + want: map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "tag", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "refs/tags/1", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TAG": "1", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "tag", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "refs/tags/1", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TAG": "1", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_BRANCH": "foo", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_OWNER": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_ID_TOKEN_REQUEST_URL": "foo/api/v1/repos/foo/builds/1/id_token"}, }, // pull_request { @@ -607,7 +607,7 @@ func TestNative_environment(t *testing.T) { m: &internal.Metadata{Database: &internal.Database{Driver: str, Host: str}, Queue: &internal.Queue{Channel: str, Driver: str, Host: str}, Source: &internal.Source{Driver: str, Host: str}, Vela: &internal.Vela{Address: str, WebAddress: str}}, r: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, u: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, - want: map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "pull_request", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_PULL_REQUEST_NUMBER": "1", "BUILD_REF": "refs/pull/1/head", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "pull_request", "VELA_BUILD_EVENT_ACTION": "opened", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_PULL_REQUEST": "1", "VELA_BUILD_REF": "refs/pull/1/head", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_PULL_REQUEST": "1", "VELA_PULL_REQUEST_SOURCE": "", "VELA_PULL_REQUEST_TARGET": "foo", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_BRANCH": "foo", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_OWNER": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"}, + want: map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "pull_request", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_PULL_REQUEST_NUMBER": "1", "BUILD_REF": "refs/pull/1/head", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "pull_request", "VELA_BUILD_EVENT_ACTION": "opened", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_PULL_REQUEST": "1", "VELA_BUILD_REF": "refs/pull/1/head", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_PULL_REQUEST": "1", "VELA_PULL_REQUEST_SOURCE": "", "VELA_PULL_REQUEST_TARGET": "foo", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_BRANCH": "foo", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_OWNER": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_ID_TOKEN_REQUEST_URL": "foo/api/v1/repos/foo/builds/1/id_token"}, }, // deployment { @@ -616,7 +616,7 @@ func TestNative_environment(t *testing.T) { m: &internal.Metadata{Database: &internal.Database{Driver: str, Host: str}, Queue: &internal.Queue{Channel: str, Driver: str, Host: str}, Source: &internal.Source{Driver: str, Host: str}, Vela: &internal.Vela{Address: str, WebAddress: str}}, r: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, u: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, - want: map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "deployment", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "refs/pull/1/head", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TARGET": "production", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "deployment", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "refs/pull/1/head", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TARGET": "production", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DEPLOYMENT": "production", "VELA_DEPLOYMENT_NUMBER": "0", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_BRANCH": "foo", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_OWNER": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"}, + want: map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "deployment", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "refs/pull/1/head", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TARGET": "production", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "deployment", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "refs/pull/1/head", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TARGET": "production", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DEPLOYMENT": "production", "VELA_DEPLOYMENT_NUMBER": "0", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_BRANCH": "foo", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_OWNER": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_ID_TOKEN_REQUEST_URL": "foo/api/v1/repos/foo/builds/1/id_token"}, }, } @@ -705,27 +705,27 @@ func Test_client_EnvironmentBuild(t *testing.T) { metadata: &internal.Metadata{Database: &internal.Database{Driver: str, Host: str}, Queue: &internal.Queue{Channel: str, Driver: str, Host: str}, Source: &internal.Source{Driver: str, Host: str}, Vela: &internal.Vela{Address: str, WebAddress: str}}, repo: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, user: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, - }, map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "push", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "foo", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "push", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "foo", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_OWNER": "foo", "VELA_REPO_BRANCH": "foo", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"}}, + }, map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "push", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "foo", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "push", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "foo", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_OWNER": "foo", "VELA_REPO_BRANCH": "foo", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_ID_TOKEN_REQUEST_URL": "foo/api/v1/repos/foo/builds/1/id_token"}}, {"tag", fields{ build: &api.Build{ID: &num64, Repo: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, Number: &num, Parent: &num, Event: &tag, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &str, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, Author: &str, Branch: &str, Ref: &tagref, BaseRef: &str}, metadata: &internal.Metadata{Database: &internal.Database{Driver: str, Host: str}, Queue: &internal.Queue{Channel: str, Driver: str, Host: str}, Source: &internal.Source{Driver: str, Host: str}, Vela: &internal.Vela{Address: str, WebAddress: str}}, repo: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, user: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, - }, map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "tag", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "refs/tags/1", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TAG": "1", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "tag", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "refs/tags/1", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TAG": "1", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_OWNER": "foo", "VELA_REPO_BRANCH": "foo", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"}, + }, map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "tag", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "refs/tags/1", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TAG": "1", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "tag", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "refs/tags/1", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TAG": "1", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_OWNER": "foo", "VELA_REPO_BRANCH": "foo", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_ID_TOKEN_REQUEST_URL": "foo/api/v1/repos/foo/builds/1/id_token"}, }, {"pull_request", fields{ build: &api.Build{ID: &num64, Repo: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, Number: &num, Parent: &num, Event: &pull, EventAction: &pullact, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &str, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, Author: &str, Branch: &str, Ref: &pullref, BaseRef: &str}, metadata: &internal.Metadata{Database: &internal.Database{Driver: str, Host: str}, Queue: &internal.Queue{Channel: str, Driver: str, Host: str}, Source: &internal.Source{Driver: str, Host: str}, Vela: &internal.Vela{Address: str, WebAddress: str}}, repo: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, user: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, - }, map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "pull_request", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_PULL_REQUEST_NUMBER": "1", "BUILD_REF": "refs/pull/1/head", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "pull_request", "VELA_BUILD_EVENT_ACTION": "opened", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_PULL_REQUEST": "1", "VELA_BUILD_REF": "refs/pull/1/head", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_PULL_REQUEST": "1", "VELA_PULL_REQUEST_SOURCE": "", "VELA_PULL_REQUEST_TARGET": "foo", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_OWNER": "foo", "VELA_REPO_BRANCH": "foo", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"}, + }, map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "pull_request", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_PULL_REQUEST_NUMBER": "1", "BUILD_REF": "refs/pull/1/head", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "pull_request", "VELA_BUILD_EVENT_ACTION": "opened", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_PULL_REQUEST": "1", "VELA_BUILD_REF": "refs/pull/1/head", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_PULL_REQUEST": "1", "VELA_PULL_REQUEST_SOURCE": "", "VELA_PULL_REQUEST_TARGET": "foo", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_OWNER": "foo", "VELA_REPO_BRANCH": "foo", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_ID_TOKEN_REQUEST_URL": "foo/api/v1/repos/foo/builds/1/id_token"}, }, {"deployment", fields{ build: &api.Build{ID: &num64, Repo: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, Number: &num, Parent: &num, Event: &deploy, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &target, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, Author: &str, Branch: &str, Ref: &pullref, BaseRef: &str}, metadata: &internal.Metadata{Database: &internal.Database{Driver: str, Host: str}, Queue: &internal.Queue{Channel: str, Driver: str, Host: str}, Source: &internal.Source{Driver: str, Host: str}, Vela: &internal.Vela{Address: str, WebAddress: str}}, repo: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, user: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, - }, map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "deployment", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "refs/pull/1/head", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TARGET": "production", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "deployment", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "refs/pull/1/head", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TARGET": "production", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DEPLOYMENT": "production", "VELA_DEPLOYMENT_NUMBER": "0", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_OWNER": "foo", "VELA_REPO_BRANCH": "foo", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"}, + }, map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "deployment", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "refs/pull/1/head", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TARGET": "production", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "deployment", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "refs/pull/1/head", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TARGET": "production", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DEPLOYMENT": "production", "VELA_DEPLOYMENT_NUMBER": "0", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_OWNER": "foo", "VELA_REPO_BRANCH": "foo", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_ID_TOKEN_REQUEST_URL": "foo/api/v1/repos/foo/builds/1/id_token"}, }, } for _, tt := range tests { diff --git a/constants/table.go b/constants/table.go index 50b1a0780..4ad4fb03c 100644 --- a/constants/table.go +++ b/constants/table.go @@ -5,6 +5,6 @@ package constants const ( // TableDashboard defines the table type for the database dashboards table. TableDashboard = "dashboards" - // TableKeySet defines the table type for the database keysets table. - TableKeySet = "keysets" + // TableJWK defines the table type for the database jwks table. + TableJWK = "jwks" ) diff --git a/database/jwk/create.go b/database/jwk/create.go index c728973f7..bbef786fd 100644 --- a/database/jwk/create.go +++ b/database/jwk/create.go @@ -28,5 +28,5 @@ func (e *engine) CreateJWK(_ context.Context, j api.JWK) error { } // send query to the database - return e.client.Table(constants.TableKeySet).Create(key).Error + return e.client.Table(constants.TableJWK).Create(key).Error } diff --git a/database/jwk/create_test.go b/database/jwk/create_test.go new file mode 100644 index 000000000..e5cc20121 --- /dev/null +++ b/database/jwk/create_test.go @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: Apache-2.0 + +package jwk + +import ( + "context" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" +) + +func TestJWK_Engine_CreateJWK(t *testing.T) { + // setup types + _jwk := testutils.APIJWK() + _jwk.Kid = "c8da1302-07d6-11ea-882f-4893bca275b8" + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // ensure the mock expects the query + _mock.ExpectExec(`INSERT INTO "jwks" +("id","active","key") +VALUES ($1,$2,$3)`). + WithArgs("c8da1302-07d6-11ea-882f-4893bca275b8", true, `{"alg":"","use":"","x5t":"","kid":"c8da1302-07d6-11ea-882f-4893bca275b8","kty":"","x5c":null,"n":"","e":""}`). + WillReturnResult(sqlmock.NewResult(1, 1)) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + // setup tests + tests := []struct { + failure bool + name string + database *engine + }{ + { + failure: false, + name: "postgres", + database: _postgres, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := test.database.CreateJWK(context.TODO(), _jwk) + + if test.failure { + if err == nil { + t.Errorf("CreateDashboard for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("CreateDashboard for %s returned err: %v", test.name, err) + } + }) + } +} diff --git a/database/jwk/get.go b/database/jwk/get.go index ed0d22004..d98ad910b 100644 --- a/database/jwk/get.go +++ b/database/jwk/get.go @@ -19,7 +19,7 @@ func (e *engine) GetActiveJWK(_ context.Context, id string) (api.JWK, error) { // send query to the database and store result in variable err := e.client. - Table(constants.TableKeySet). + Table(constants.TableJWK). Where("id = ?", id). Where("active = ?", true). Take(j). diff --git a/database/jwk/get_test.go b/database/jwk/get_test.go new file mode 100644 index 000000000..9e65991bc --- /dev/null +++ b/database/jwk/get_test.go @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: Apache-2.0 + +package jwk + +import ( + "context" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/google/go-cmp/cmp" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" +) + +func TestJWK_Engine_GetJWK(t *testing.T) { + // setup types + _jwk := testutils.APIJWK() + _jwk.Kid = "c8da1302-07d6-11ea-882f-4893bca275b8" + _jwk.Algorithm = "RS256" + _jwk.Kty = "rsa" + _jwk.Use = "sig" + _jwk.N = "123456" + _jwk.E = "123" + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // create expected result in mock + _rows := sqlmock.NewRows( + []string{"id", "active", "key"}, + ).AddRow("c8da1302-07d6-11ea-882f-4893bca275b8", true, []byte(`{"alg":"RS256","use":"sig","x5t":"","kid":"c8da1302-07d6-11ea-882f-4893bca275b8","kty":"rsa","x5c":null,"n":"123456","e":"123"}`)) + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT * FROM "jwks" WHERE id = $1 AND active = $2 LIMIT $3`).WithArgs("c8da1302-07d6-11ea-882f-4893bca275b8", true, 1).WillReturnRows(_rows) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + err := _sqlite.CreateJWK(context.TODO(), _jwk) + if err != nil { + t.Errorf("unable to create test repo for sqlite: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + want api.JWK + }{ + { + failure: false, + name: "postgres", + database: _postgres, + want: _jwk, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + want: _jwk, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := test.database.GetActiveJWK(context.TODO(), "c8da1302-07d6-11ea-882f-4893bca275b8") + + if test.failure { + if err == nil { + t.Errorf("GetActiveJWK for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("GetActiveJWK for %s returned err: %v", test.name, err) + } + + if diff := cmp.Diff(got, test.want); diff != "" { + t.Errorf("GetActiveJWK mismatch (-want +got):\n%s", diff) + } + }) + } +} diff --git a/database/jwk/jwk.go b/database/jwk/jwk.go index 404150256..661396f66 100644 --- a/database/jwk/jwk.go +++ b/database/jwk/jwk.go @@ -70,7 +70,7 @@ func New(opts ...EngineOpt) (*engine, error) { // create the JWK table err := e.CreateJWKTable(e.ctx, e.client.Config.Dialector.Name()) if err != nil { - return nil, fmt.Errorf("unable to create %s table: %w", constants.TableKeySet, err) + return nil, fmt.Errorf("unable to create %s table: %w", constants.TableJWK, err) } return e, nil diff --git a/database/jwk/jwk_test.go b/database/jwk/jwk_test.go new file mode 100644 index 000000000..95ff9a463 --- /dev/null +++ b/database/jwk/jwk_test.go @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: Apache-2.0 + +package jwk + +import ( + "reflect" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/sirupsen/logrus" + "gorm.io/driver/postgres" + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +func TestJWK_New(t *testing.T) { + // setup types + logger := logrus.NewEntry(logrus.StandardLogger()) + + _sql, _mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) + if err != nil { + t.Errorf("unable to create new SQL mock: %v", err) + } + defer _sql.Close() + + _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) + + _config := &gorm.Config{SkipDefaultTransaction: true} + + _postgres, err := gorm.Open(postgres.New(postgres.Config{Conn: _sql}), _config) + if err != nil { + t.Errorf("unable to create new postgres database: %v", err) + } + + _sqlite, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), _config) + if err != nil { + t.Errorf("unable to create new sqlite database: %v", err) + } + + defer func() { _sql, _ := _sqlite.DB(); _sql.Close() }() + + // setup tests + tests := []struct { + failure bool + name string + client *gorm.DB + key string + logger *logrus.Entry + skipCreation bool + want *engine + }{ + { + failure: false, + name: "postgres", + client: _postgres, + key: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW", + logger: logger, + skipCreation: false, + want: &engine{ + client: _postgres, + config: &config{SkipCreation: false}, + logger: logger, + }, + }, + { + failure: false, + name: "sqlite3", + client: _sqlite, + key: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW", + logger: logger, + skipCreation: false, + want: &engine{ + client: _sqlite, + config: &config{SkipCreation: false}, + logger: logger, + }, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := New( + WithClient(test.client), + WithLogger(test.logger), + WithSkipCreation(test.skipCreation), + ) + + if test.failure { + if err == nil { + t.Errorf("New for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("New for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("New for %s is %v, want %v", test.name, got, test.want) + } + }) + } +} + +// testPostgres is a helper function to create a Postgres engine for testing. +func testPostgres(t *testing.T) (*engine, sqlmock.Sqlmock) { + // create the new mock sql database + // + // https://pkg.go.dev/github.com/DATA-DOG/go-sqlmock#New + _sql, _mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) + if err != nil { + t.Errorf("unable to create new SQL mock: %v", err) + } + + _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) + + // create the new mock Postgres database client + // + // https://pkg.go.dev/gorm.io/gorm#Open + _postgres, err := gorm.Open( + postgres.New(postgres.Config{Conn: _sql}), + &gorm.Config{SkipDefaultTransaction: true}, + ) + if err != nil { + t.Errorf("unable to create new postgres database: %v", err) + } + + _engine, err := New( + WithClient(_postgres), + WithLogger(logrus.NewEntry(logrus.StandardLogger())), + WithSkipCreation(false), + ) + if err != nil { + t.Errorf("unable to create new postgres dashboard engine: %v", err) + } + + return _engine, _mock +} + +// testSqlite is a helper function to create a Sqlite engine for testing. +func testSqlite(t *testing.T) *engine { + _sqlite, err := gorm.Open( + sqlite.Open("file::memory:?cache=shared"), + &gorm.Config{SkipDefaultTransaction: true}, + ) + if err != nil { + t.Errorf("unable to create new sqlite database: %v", err) + } + + _engine, err := New( + WithClient(_sqlite), + WithLogger(logrus.NewEntry(logrus.StandardLogger())), + WithSkipCreation(false), + ) + if err != nil { + t.Errorf("unable to create new sqlite dashboard engine: %v", err) + } + + return _engine +} diff --git a/database/jwk/list.go b/database/jwk/list.go index a810a7d3c..4380d9dda 100644 --- a/database/jwk/list.go +++ b/database/jwk/list.go @@ -19,7 +19,7 @@ func (e *engine) ListJWKs(_ context.Context) ([]api.JWK, error) { // send query to the database and store result in variable err := e.client. - Table(constants.TableKeySet). + Table(constants.TableJWK). Find(&k). Error if err != nil { diff --git a/database/jwk/list_test.go b/database/jwk/list_test.go new file mode 100644 index 000000000..8060b8330 --- /dev/null +++ b/database/jwk/list_test.go @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: Apache-2.0 + +package jwk + +import ( + "context" + "reflect" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" +) + +func TestJWK_Engine_ListJWKs(t *testing.T) { + // setup types + _jwkOne := testutils.APIJWK() + _jwkOne.Kid = "c8da1302-07d6-11ea-882f-4893bca275b8" + _jwkOne.Algorithm = "RS256" + _jwkOne.Kty = "rsa" + _jwkOne.Use = "sig" + _jwkOne.N = "123456" + _jwkOne.E = "123" + + _jwkTwo := testutils.APIJWK() + _jwkTwo.Kid = "c8da1302-07d6-11ea-882f-4893bca275b9" + _jwkTwo.Algorithm = "RS256" + _jwkTwo.Kty = "rsa" + _jwkTwo.Use = "sig" + _jwkTwo.N = "123789" + _jwkTwo.E = "456" + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // create expected result in mock + _rows := sqlmock.NewRows( + []string{"id", "active", "key"}). + AddRow("c8da1302-07d6-11ea-882f-4893bca275b8", true, []byte(`{"alg":"RS256","use":"sig","x5t":"","kid":"c8da1302-07d6-11ea-882f-4893bca275b8","kty":"rsa","x5c":null,"n":"123456","e":"123"}`)). + AddRow("c8da1302-07d6-11ea-882f-4893bca275b8", true, []byte(`{"alg":"RS256","use":"sig","x5t":"","kid":"c8da1302-07d6-11ea-882f-4893bca275b9","kty":"rsa","x5c":null,"n":"123789","e":"456"}`)) + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT * FROM "jwks"`).WillReturnRows(_rows) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + err := _sqlite.CreateJWK(context.TODO(), _jwkOne) + if err != nil { + t.Errorf("unable to create test jwk for sqlite: %v", err) + } + + err = _sqlite.CreateJWK(context.TODO(), _jwkTwo) + if err != nil { + t.Errorf("unable to create test jwk for sqlite: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + want []api.JWK + }{ + { + failure: false, + name: "postgres", + database: _postgres, + want: []api.JWK{_jwkOne, _jwkTwo}, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + want: []api.JWK{_jwkOne, _jwkTwo}, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := test.database.ListJWKs(context.TODO()) + + if test.failure { + if err == nil { + t.Errorf("ListJWKs for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("ListJWKs for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("ListJWKs for %s is %v, want %v", test.name, got, test.want) + } + }) + } +} diff --git a/database/jwk/opts.go b/database/jwk/opts.go index c2accdcb9..b26329135 100644 --- a/database/jwk/opts.go +++ b/database/jwk/opts.go @@ -22,16 +22,6 @@ func WithClient(client *gorm.DB) EngineOpt { } } -// WithDriver sets the driver type in the database engine for key sets. -func WithDriver(driver string) EngineOpt { - return func(e *engine) error { - // set the driver type in the key set engine - e.config.Driver = driver - - return nil - } -} - // WithLogger sets the github.com/sirupsen/logrus logger in the database engine for key sets. func WithLogger(logger *logrus.Entry) EngineOpt { return func(e *engine) error { diff --git a/database/jwk/opts_test.go b/database/jwk/opts_test.go new file mode 100644 index 000000000..8a7192111 --- /dev/null +++ b/database/jwk/opts_test.go @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: Apache-2.0 + +package jwk + +import ( + "context" + "reflect" + "testing" + + "github.com/sirupsen/logrus" + "gorm.io/gorm" +) + +func TestHook_EngineOpt_WithClient(t *testing.T) { + // setup types + e := &engine{client: new(gorm.DB)} + + // setup tests + tests := []struct { + failure bool + name string + client *gorm.DB + want *gorm.DB + }{ + { + failure: false, + name: "client set to new database", + client: new(gorm.DB), + want: new(gorm.DB), + }, + { + failure: false, + name: "client set to nil", + client: nil, + want: nil, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := WithClient(test.client)(e) + + if test.failure { + if err == nil { + t.Errorf("WithClient for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("WithClient returned err: %v", err) + } + + if !reflect.DeepEqual(e.client, test.want) { + t.Errorf("WithClient is %v, want %v", e.client, test.want) + } + }) + } +} + +func TestHook_EngineOpt_WithLogger(t *testing.T) { + // setup types + e := &engine{logger: new(logrus.Entry)} + + // setup tests + tests := []struct { + failure bool + name string + logger *logrus.Entry + want *logrus.Entry + }{ + { + failure: false, + name: "logger set to new entry", + logger: new(logrus.Entry), + want: new(logrus.Entry), + }, + { + failure: false, + name: "logger set to nil", + logger: nil, + want: nil, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := WithLogger(test.logger)(e) + + if test.failure { + if err == nil { + t.Errorf("WithLogger for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("WithLogger returned err: %v", err) + } + + if !reflect.DeepEqual(e.logger, test.want) { + t.Errorf("WithLogger is %v, want %v", e.logger, test.want) + } + }) + } +} + +func TestHook_EngineOpt_WithSkipCreation(t *testing.T) { + // setup types + e := &engine{config: new(config)} + + // setup tests + tests := []struct { + failure bool + name string + skipCreation bool + want bool + }{ + { + failure: false, + name: "skip creation set to true", + skipCreation: true, + want: true, + }, + { + failure: false, + name: "skip creation set to false", + skipCreation: false, + want: false, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := WithSkipCreation(test.skipCreation)(e) + + if test.failure { + if err == nil { + t.Errorf("WithSkipCreation for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("WithSkipCreation returned err: %v", err) + } + + if !reflect.DeepEqual(e.config.SkipCreation, test.want) { + t.Errorf("WithSkipCreation is %v, want %v", e.config.SkipCreation, test.want) + } + }) + } +} + +func TestHook_EngineOpt_WithContext(t *testing.T) { + // setup types + e := &engine{config: new(config)} + + // setup tests + tests := []struct { + failure bool + name string + ctx context.Context + want context.Context + }{ + { + failure: false, + name: "context set to TODO", + ctx: context.TODO(), + want: context.TODO(), + }, + { + failure: false, + name: "context set to nil", + ctx: nil, + want: nil, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := WithContext(test.ctx)(e) + + if test.failure { + if err == nil { + t.Errorf("WithContext for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("WithContext returned err: %v", err) + } + + if !reflect.DeepEqual(e.ctx, test.want) { + t.Errorf("WithContext is %v, want %v", e.ctx, test.want) + } + }) + } +} diff --git a/database/jwk/rotate.go b/database/jwk/rotate.go index 50e6d8580..0b2520cd7 100644 --- a/database/jwk/rotate.go +++ b/database/jwk/rotate.go @@ -18,7 +18,7 @@ func (e *engine) RotateKeys(_ context.Context) error { // remove inactive keys err := e.client. - Table(constants.TableKeySet). + Table(constants.TableJWK). Where("active = ?", false). Delete(&k). Error @@ -28,7 +28,7 @@ func (e *engine) RotateKeys(_ context.Context) error { // set active keys to inactive err = e.client. - Table(constants.TableKeySet). + Table(constants.TableJWK). Where("active = ?", true). Update("active", sql.NullBool{Bool: false, Valid: true}). Error diff --git a/database/jwk/rotate_test.go b/database/jwk/rotate_test.go new file mode 100644 index 000000000..4551a24b6 --- /dev/null +++ b/database/jwk/rotate_test.go @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: Apache-2.0 + +package jwk + +import ( + "context" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" +) + +func TestJWK_Engine_RotateKeys(t *testing.T) { + // setup types + _jwkOne := testutils.APIJWK() + _jwkOne.Kid = "c8da1302-07d6-11ea-882f-4893bca275b8" + _jwkOne.Algorithm = "RS256" + _jwkOne.Kty = "rsa" + _jwkOne.Use = "sig" + _jwkOne.N = "123456" + _jwkOne.E = "123" + + _jwkTwo := testutils.APIJWK() + _jwkTwo.Kid = "c8da1302-07d6-11ea-882f-4893bca275b9" + _jwkTwo.Algorithm = "RS256" + _jwkTwo.Kty = "rsa" + _jwkTwo.Use = "sig" + _jwkTwo.N = "123789" + _jwkTwo.E = "456" + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // create expected result in mock + _rows := sqlmock.NewRows( + []string{"id", "active", "key"}, + ).AddRow("c8da1302-07d6-11ea-882f-4893bca275b8", true, []byte(`{"alg":"RS256","use":"sig","x5t":"","kid":"c8da1302-07d6-11ea-882f-4893bca275b8","kty":"rsa","x5c":null,"n":"123456","e":"123"}`)) + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT * FROM "jwks" WHERE id = $1 AND active = $2 LIMIT $3`).WithArgs("c8da1302-07d6-11ea-882f-4893bca275b8", true, 1).WillReturnRows(_rows) + + // create expected result in mock + _rows = sqlmock.NewRows( + []string{"id", "active", "key"}, + ).AddRow("c8da1302-07d6-11ea-882f-4893bca275b9", true, []byte(`{"alg":"RS256","use":"sig","x5t":"","kid":"c8da1302-07d6-11ea-882f-4893bca275b9","kty":"rsa","x5c":null,"n":"123789","e":"456"}`)) + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT * FROM "jwks" WHERE id = $1 AND active = $2 LIMIT $3`).WithArgs("c8da1302-07d6-11ea-882f-4893bca275b9", true, 1).WillReturnRows(_rows) + + _mock.ExpectExec(`DELETE FROM "jwks" WHERE active = $1`). + WithArgs(false). + WillReturnResult(sqlmock.NewResult(1, 1)) + + _mock.ExpectExec(`UPDATE "jwks" SET "active"=$1 WHERE active = $2`). + WithArgs(false, true). + WillReturnResult(sqlmock.NewResult(1, 1)) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + err := _sqlite.CreateJWK(context.TODO(), _jwkOne) + if err != nil { + t.Errorf("unable to create test jwk for sqlite: %v", err) + } + + err = _sqlite.CreateJWK(context.TODO(), _jwkTwo) + if err != nil { + t.Errorf("unable to create test jwk for sqlite: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + }{ + { + failure: false, + name: "postgres", + database: _postgres, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + _, err := test.database.GetActiveJWK(context.TODO(), _jwkOne.Kid) + if err != nil { + t.Errorf("GetActiveJWK for %s returned err: %v", test.name, err) + } + + _, err = test.database.GetActiveJWK(context.TODO(), _jwkTwo.Kid) + if err != nil { + t.Errorf("GetActiveJWK for %s returned err: %v", test.name, err) + } + + err = test.database.RotateKeys(context.TODO()) + + if test.failure { + if err == nil { + t.Errorf("RotateKeys for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("RotateKeys for %s returned err: %v", test.name, err) + } + + _, err = test.database.GetActiveJWK(context.TODO(), _jwkOne.Kid) + if err == nil { + t.Errorf("GetActiveJWK for %s should have returned err", test.name) + } + + _, err = test.database.GetActiveJWK(context.TODO(), _jwkTwo.Kid) + if err == nil { + t.Errorf("GetActiveJWK for %s should have returned err", test.name) + } + }) + } +} diff --git a/database/jwk/table_test.go b/database/jwk/table_test.go new file mode 100644 index 000000000..ec8626677 --- /dev/null +++ b/database/jwk/table_test.go @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: Apache-2.0 + +package jwk + +import ( + "context" + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestJWK_Engine_CreateJWKTable(t *testing.T) { + // setup types + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + // setup tests + tests := []struct { + failure bool + name string + database *engine + }{ + { + failure: false, + name: "postgres", + database: _postgres, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := test.database.CreateJWKTable(context.TODO(), test.name) + + if test.failure { + if err == nil { + t.Errorf("CreateJWKTable for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("CreateJWKTable for %s returned err: %v", test.name, err) + } + }) + } +} diff --git a/database/testutils/api_resources.go b/database/testutils/api_resources.go index 78aec3aa6..1a8a4716d 100644 --- a/database/testutils/api_resources.go +++ b/database/testutils/api_resources.go @@ -251,3 +251,14 @@ func APIDashboardRepo() *api.DashboardRepo { Events: new([]string), } } + +func APIJWK() api.JWK { + return api.JWK{ + Kid: "", + Kty: "", + Algorithm: "", + Use: "", + N: "", + E: "", + } +} diff --git a/go.mod b/go.mod index 176743e3e..ec95fd984 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,8 @@ module github.com/go-vela/server go 1.21.9 +replace github.com/go-vela/types => ../types + require ( github.com/Bose/minisentinel v0.0.0-20200130220412-917c5a9223bb github.com/DATA-DOG/go-sqlmock v1.5.2 diff --git a/go.sum b/go.sum index 8003aff62..c126d83e8 100644 --- a/go.sum +++ b/go.sum @@ -85,8 +85,6 @@ github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= -github.com/go-vela/types v0.23.4-0.20240405205548-f24f795ac0b7 h1:3mN7ej69dMH3Vis3G/tPLzLL0Rfp8nR5qd0gpj5ejRM= -github.com/go-vela/types v0.23.4-0.20240405205548-f24f795ac0b7/go.mod h1:mEF9dLkk00rUXf/t39n2WvXZgJbxnPEEWy+DHqIlRUo= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= diff --git a/internal/token/generate_rsa.go b/internal/token/generate_rsa.go index 2b4481ee9..5d48ee7f0 100644 --- a/internal/token/generate_rsa.go +++ b/internal/token/generate_rsa.go @@ -25,7 +25,7 @@ func (tm *Manager) GenerateRSA(db database.Interface) error { } // assign KID to key pair - kid, err := uuid.NewRandom() + kid, err := uuid.NewV7() if err != nil { return err } diff --git a/internal/token/mint.go b/internal/token/mint.go index 31c68b381..534ac2b4f 100644 --- a/internal/token/mint.go +++ b/internal/token/mint.go @@ -21,18 +21,20 @@ import ( type Claims struct { BuildID int64 `json:"build_id,omitempty"` BuildNumber int `json:"build_number,omitempty"` + BuildSender string `json:"build_sender,omitempty"` IsActive bool `json:"is_active,omitempty"` IsAdmin bool `json:"is_admin,omitempty"` Repo string `json:"repo,omitempty"` TokenType string `json:"token_type,omitempty"` + Image string `json:"image,omitempty"` + Request string `json:"request,omitempty"` jwt.RegisteredClaims } // MintTokenOpts is a type to inform the token minter how to construct // the token. type MintTokenOpts struct { - BuildID int64 - BuildNumber int + Build *api.Build Hostname string Repo string TokenDuration time.Duration @@ -40,6 +42,8 @@ type MintTokenOpts struct { User *api.User Audience []string Commit string + Image string + Request string } // MintToken mints a Vela JWT Token given a set of options. @@ -59,7 +63,7 @@ func (tm *Manager) MintToken(mto *MintTokenOpts) (string, error) { claims.Subject = mto.User.GetName() case constants.WorkerBuildTokenType: - if mto.BuildID == 0 { + if mto.Build.GetID() == 0 { return "", errors.New("missing build id for build token") } @@ -71,7 +75,7 @@ func (tm *Manager) MintToken(mto *MintTokenOpts) (string, error) { return "", errors.New("missing host name for build token") } - claims.BuildID = mto.BuildID + claims.BuildID = mto.Build.GetID() claims.Repo = mto.Repo claims.Subject = mto.Hostname @@ -91,14 +95,17 @@ func (tm *Manager) MintToken(mto *MintTokenOpts) (string, error) { return "", errors.New("missing commit for ID request token") } - if mto.BuildID == 0 { + if mto.Build.GetID() == 0 { return "", errors.New("missing build id for ID request token") } claims.Repo = mto.Repo claims.Subject = fmt.Sprintf("%s/%s", mto.Repo, mto.Commit) - claims.BuildID = mto.BuildID - claims.BuildNumber = mto.BuildNumber + claims.BuildID = mto.Build.GetID() + claims.BuildNumber = mto.Build.GetNumber() + claims.BuildSender = mto.Build.GetSender() + claims.Image = mto.Image + claims.Request = mto.Request default: return "", errors.New("invalid token type") @@ -133,16 +140,19 @@ func (tm *Manager) MintIDToken(mto *MintTokenOpts, db database.Interface) (strin return "", errors.New("missing commit for ID token") } - if mto.BuildNumber == 0 { + if mto.Build.GetNumber() == 0 { return "", errors.New("missing build id for ID token") } // set claims based on input - claims.BuildNumber = mto.BuildNumber + claims.BuildNumber = mto.Build.GetNumber() + claims.BuildSender = mto.Build.GetSender() claims.Repo = mto.Repo claims.Subject = fmt.Sprintf("%s/%s", mto.Repo, mto.Commit) claims.Audience = mto.Audience claims.TokenType = mto.TokenType + claims.Image = mto.Image + claims.Request = mto.Request // set standard claims claims.IssuedAt = jwt.NewNumericDate(time.Now()) diff --git a/internal/token/parse_test.go b/internal/token/parse_test.go index 2f1c94bcd..600efe35e 100644 --- a/internal/token/parse_test.go +++ b/internal/token/parse_test.go @@ -21,6 +21,11 @@ func TestTokenManager_ParseToken(t *testing.T) { u.SetName("foo") u.SetToken("bar") + b := new(api.Build) + b.SetID(1) + b.SetNumber(1) + b.SetSender("octocat") + tm := &Manager{ PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, @@ -73,7 +78,7 @@ func TestTokenManager_ParseToken(t *testing.T) { { TokenType: constants.WorkerBuildTokenType, Mto: &MintTokenOpts{ - BuildID: 1, + Build: b, Repo: "foo/bar", Hostname: "worker", TokenType: constants.WorkerBuildTokenType, diff --git a/router/middleware/claims/claims_test.go b/router/middleware/claims/claims_test.go index f8e0982fb..fa61c773f 100644 --- a/router/middleware/claims/claims_test.go +++ b/router/middleware/claims/claims_test.go @@ -60,6 +60,11 @@ func TestClaims_Establish(t *testing.T) { user.SetAdmin(false) user.SetFavorites([]string{}) + build := new(api.Build) + build.SetID(1) + build.SetNumber(1) + build.SetSender("octocat") + tm := &token.Manager{ PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, @@ -111,7 +116,7 @@ func TestClaims_Establish(t *testing.T) { }, Mto: &token.MintTokenOpts{ Hostname: "host", - BuildID: 1, + Build: build, Repo: "foo/bar", TokenDuration: time.Minute * 35, TokenType: constants.WorkerBuildTokenType, diff --git a/router/middleware/perm/perm_test.go b/router/middleware/perm/perm_test.go index ba97f9229..454bc61dc 100644 --- a/router/middleware/perm/perm_test.go +++ b/router/middleware/perm/perm_test.go @@ -413,7 +413,7 @@ func TestPerm_MustBuildAccess(t *testing.T) { mto := &token.MintTokenOpts{ Hostname: "worker", - BuildID: 1, + Build: b, Repo: "foo/bar", TokenDuration: time.Minute * 30, TokenType: constants.WorkerBuildTokenType, @@ -587,6 +587,9 @@ func TestPerm_MustBuildToken_WrongBuild(t *testing.T) { b.SetRepo(r) b.SetNumber(1) + wB := new(api.Build) + wB.SetID(2) + tm := &token.Manager{ PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, @@ -595,7 +598,7 @@ func TestPerm_MustBuildToken_WrongBuild(t *testing.T) { mto := &token.MintTokenOpts{ Hostname: "worker", - BuildID: 2, + Build: wB, Repo: "foo/bar", TokenDuration: time.Minute * 30, TokenType: constants.WorkerBuildTokenType, @@ -685,7 +688,7 @@ func TestPerm_MustIDRequestToken(t *testing.T) { mto := &token.MintTokenOpts{ Hostname: "foo/bar/456def", - BuildID: b.GetID(), + Build: b, Commit: b.GetCommit(), Repo: r.GetFullName(), TokenDuration: time.Minute * 30, @@ -785,7 +788,7 @@ func TestPerm_MustIDRequestToken_NotRunning(t *testing.T) { mto := &token.MintTokenOpts{ Hostname: "foo/bar/456def", - BuildID: b.GetID(), + Build: b, Commit: b.GetCommit(), Repo: "foo/bar", TokenDuration: time.Minute * 30, @@ -868,6 +871,9 @@ func TestPerm_MustIDRequestToken_WrongBuild(t *testing.T) { b.SetRepo(r) b.SetNumber(1) + wB := new(api.Build) + wB.SetID(2) + tm := &token.Manager{ PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, @@ -876,7 +882,7 @@ func TestPerm_MustIDRequestToken_WrongBuild(t *testing.T) { mto := &token.MintTokenOpts{ Hostname: "foo/bar/456def", - BuildID: 2, + Build: wB, Commit: b.GetCommit(), Repo: "foo/bar", TokenDuration: time.Minute * 30, @@ -965,7 +971,7 @@ func TestPerm_MustSecretAdmin_BuildToken_Repo(t *testing.T) { mto := &token.MintTokenOpts{ Hostname: "worker", - BuildID: 1, + Build: b, Repo: "foo/bar", TokenDuration: time.Minute * 30, TokenType: constants.WorkerBuildTokenType, @@ -1050,7 +1056,7 @@ func TestPerm_MustSecretAdmin_BuildToken_Org(t *testing.T) { mto := &token.MintTokenOpts{ Hostname: "worker", - BuildID: 1, + Build: b, Repo: "foo/bar", TokenDuration: time.Minute * 30, TokenType: constants.WorkerBuildTokenType, @@ -1135,7 +1141,7 @@ func TestPerm_MustSecretAdmin_BuildToken_Shared(t *testing.T) { mto := &token.MintTokenOpts{ Hostname: "worker", - BuildID: 1, + Build: b, Repo: "foo/bar", TokenDuration: time.Minute * 30, TokenType: constants.WorkerBuildTokenType, @@ -2113,7 +2119,7 @@ func TestPerm_MustRead_WorkerBuildToken(t *testing.T) { Hostname: "worker", TokenDuration: time.Minute * 35, TokenType: constants.WorkerBuildTokenType, - BuildID: 1, + Build: b, Repo: "foo/bar", } From e99d1c3fedb863ac1ab7d1f8980eada6d41f4f87 Mon Sep 17 00:00:00 2001 From: ecrupper Date: Thu, 2 May 2024 10:11:40 -0500 Subject: [PATCH 04/34] update subject --- api/build/id_request_token.go | 1 - api/build/id_token.go | 1 - api/oi_config.go | 3 +++ internal/token/mint.go | 13 ++++++------- router/middleware/perm/perm_test.go | 3 --- 5 files changed, 9 insertions(+), 12 deletions(-) diff --git a/api/build/id_request_token.go b/api/build/id_request_token.go index 338d4704f..f0c6fcd9b 100644 --- a/api/build/id_request_token.go +++ b/api/build/id_request_token.go @@ -98,7 +98,6 @@ func GetIDRequestToken(c *gin.Context) { Build: b, Repo: r.GetFullName(), TokenType: constants.IDRequestTokenType, - Commit: b.GetCommit(), TokenDuration: exp, Image: image, Request: request, diff --git a/api/build/id_token.go b/api/build/id_token.go index b0146651a..90ff119d8 100644 --- a/api/build/id_token.go +++ b/api/build/id_token.go @@ -85,7 +85,6 @@ func GetIDToken(c *gin.Context) { Build: b, Repo: r.GetFullName(), TokenType: constants.IDTokenType, - Commit: b.GetCommit(), TokenDuration: tm.IDTokenDuration, Image: cl.Image, Request: cl.Request, diff --git a/api/oi_config.go b/api/oi_config.go index 2df130421..32e68bd2f 100644 --- a/api/oi_config.go +++ b/api/oi_config.go @@ -44,6 +44,9 @@ func GetOpenIDConfig(c *gin.Context) { "build_number", "repo", "token_type", + "build_sender", + "image", + "request", }, Algorithms: []string{ jwt.SigningMethodRS256.Name, diff --git a/internal/token/mint.go b/internal/token/mint.go index 534ac2b4f..538b490d3 100644 --- a/internal/token/mint.go +++ b/internal/token/mint.go @@ -41,7 +41,6 @@ type MintTokenOpts struct { TokenType string User *api.User Audience []string - Commit string Image string Request string } @@ -91,8 +90,8 @@ func (tm *Manager) MintToken(mto *MintTokenOpts) (string, error) { return "", errors.New("missing repo for ID request token") } - if len(mto.Commit) == 0 { - return "", errors.New("missing commit for ID request token") + if mto.Build == nil { + return "", errors.New("missing build for ID request token") } if mto.Build.GetID() == 0 { @@ -100,7 +99,7 @@ func (tm *Manager) MintToken(mto *MintTokenOpts) (string, error) { } claims.Repo = mto.Repo - claims.Subject = fmt.Sprintf("%s/%s", mto.Repo, mto.Commit) + claims.Subject = fmt.Sprintf("repo:%s:ref:%s", mto.Repo, mto.Build.GetRef()) claims.BuildID = mto.Build.GetID() claims.BuildNumber = mto.Build.GetNumber() claims.BuildSender = mto.Build.GetSender() @@ -136,8 +135,8 @@ func (tm *Manager) MintIDToken(mto *MintTokenOpts, db database.Interface) (strin return "", errors.New("missing repo for ID token") } - if len(mto.Commit) == 0 { - return "", errors.New("missing commit for ID token") + if mto.Build == nil { + return "", errors.New("missing build for ID token") } if mto.Build.GetNumber() == 0 { @@ -148,7 +147,7 @@ func (tm *Manager) MintIDToken(mto *MintTokenOpts, db database.Interface) (strin claims.BuildNumber = mto.Build.GetNumber() claims.BuildSender = mto.Build.GetSender() claims.Repo = mto.Repo - claims.Subject = fmt.Sprintf("%s/%s", mto.Repo, mto.Commit) + claims.Subject = fmt.Sprintf("repo:%s:ref:%s", mto.Repo, mto.Build.GetRef()) claims.Audience = mto.Audience claims.TokenType = mto.TokenType claims.Image = mto.Image diff --git a/router/middleware/perm/perm_test.go b/router/middleware/perm/perm_test.go index 454bc61dc..5f921dfc8 100644 --- a/router/middleware/perm/perm_test.go +++ b/router/middleware/perm/perm_test.go @@ -689,7 +689,6 @@ func TestPerm_MustIDRequestToken(t *testing.T) { mto := &token.MintTokenOpts{ Hostname: "foo/bar/456def", Build: b, - Commit: b.GetCommit(), Repo: r.GetFullName(), TokenDuration: time.Minute * 30, TokenType: constants.IDRequestTokenType, @@ -789,7 +788,6 @@ func TestPerm_MustIDRequestToken_NotRunning(t *testing.T) { mto := &token.MintTokenOpts{ Hostname: "foo/bar/456def", Build: b, - Commit: b.GetCommit(), Repo: "foo/bar", TokenDuration: time.Minute * 30, TokenType: constants.IDRequestTokenType, @@ -883,7 +881,6 @@ func TestPerm_MustIDRequestToken_WrongBuild(t *testing.T) { mto := &token.MintTokenOpts{ Hostname: "foo/bar/456def", Build: wB, - Commit: b.GetCommit(), Repo: "foo/bar", TokenDuration: time.Minute * 30, TokenType: constants.IDRequestTokenType, From a2ef469a68bf0c0e9ab8b7bb5490fe155f63d069 Mon Sep 17 00:00:00 2001 From: ecrupper Date: Fri, 3 May 2024 09:46:15 -0500 Subject: [PATCH 05/34] integration test --- database/integration_test.go | 93 ++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/database/integration_test.go b/database/integration_test.go index 36e2cb5a6..731e9d5d3 100644 --- a/database/integration_test.go +++ b/database/integration_test.go @@ -40,6 +40,7 @@ type Resources struct { Deployments []*library.Deployment Executables []*library.BuildExecutable Hooks []*library.Hook + JWKs []*api.JWK Logs []*library.Log Pipelines []*library.Pipeline Repos []*api.Repo @@ -132,6 +133,8 @@ func TestDatabase_Integration(t *testing.T) { t.Run("test_hooks", func(t *testing.T) { testHooks(t, db, resources) }) + t.Run("test_jwks", func(t *testing.T) { testJWKs(t, db, resources) }) + t.Run("test_logs", func(t *testing.T) { testLogs(t, db, resources) }) t.Run("test_pipelines", func(t *testing.T) { testPipelines(t, db, resources) }) @@ -849,6 +852,77 @@ func testHooks(t *testing.T, db Interface, resources *Resources) { } } +func testJWKs(t *testing.T, db Interface, resources *Resources) { + // create a variable to track the number of methods called for logs + methods := make(map[string]bool) + // capture the element type of the log interface + element := reflect.TypeOf(new(log.LogInterface)).Elem() + // iterate through all methods found in the log interface + for i := 0; i < element.NumMethod(); i++ { + // skip tracking the methods to create indexes and tables for logs + // since those are already called when the database engine starts + if strings.Contains(element.Method(i).Name, "Table") { + continue + } + + // add the method name to the list of functions + methods[element.Method(i).Name] = false + } + + for _, jwk := range resources.JWKs { + err := db.CreateJWK(context.TODO(), *jwk) + if err != nil { + t.Errorf("unable to create jwk %s: %v", jwk.Kid, err) + } + } + methods["CreateJWK"] = true + + list, err := db.ListJWKs(context.TODO()) + if err != nil { + t.Errorf("unable to list jwks: %v", err) + } + + if diff := cmp.Diff(resources.JWKs, list); diff != "" { + t.Errorf("ListJWKs() mismatch (-want +got):\n%s", diff) + } + + methods["ListJWKs"] = true + + for _, jwk := range resources.JWKs { + got, err := db.GetActiveJWK(context.TODO(), jwk.Kid) + if err != nil { + t.Errorf("unable to get jwk %s: %v", jwk.Kid, err) + } + + if !cmp.Equal(jwk, got) { + t.Errorf("GetJWK() is %v, want %v", got, jwk) + } + } + + methods["GetActiveJWK"] = true + + err = db.RotateKeys(context.TODO()) + if err != nil { + t.Errorf("unable to rotate keys: %v", err) + } + + for _, jwk := range resources.JWKs { + _, err := db.GetActiveJWK(context.TODO(), jwk.Kid) + if err == nil { + t.Errorf("GetActiveJWK() should return err after rotation") + } + } + + methods["RotateKeys"] = true + + // ensure we called all the methods we expected to + for method, called := range methods { + if !called { + t.Errorf("method %s was not called for logs", method) + } + } +} + func testLogs(t *testing.T, db Interface, resources *Resources) { // create a variable to track the number of methods called for logs methods := make(map[string]bool) @@ -2396,6 +2470,24 @@ func newResources() *Resources { hookThree.SetLink("https://github.com/github/octocat/settings/hooks/1") hookThree.SetWebhookID(78910) + jwkOne := &api.JWK{ + Algorithm: "RS256", + Kid: "c8da1302-07d6-11ea-882f-4893bca275b8", + Kty: "rsa", + Use: "sig", + N: "123456", + E: "123", + } + + jwkTwo := &api.JWK{ + Algorithm: "RS256", + Kid: "c8da1302-07d6-11ea-882f-4893bca275b9", + Kty: "rsa", + Use: "sig", + N: "789101", + E: "456", + } + logServiceOne := new(library.Log) logServiceOne.SetID(1) logServiceOne.SetBuildID(1) @@ -2651,6 +2743,7 @@ func newResources() *Resources { Deployments: []*library.Deployment{deploymentOne, deploymentTwo}, Executables: []*library.BuildExecutable{executableOne, executableTwo}, Hooks: []*library.Hook{hookOne, hookTwo, hookThree}, + JWKs: []*api.JWK{jwkOne, jwkTwo}, Logs: []*library.Log{logServiceOne, logServiceTwo, logStepOne, logStepTwo}, Pipelines: []*library.Pipeline{pipelineOne, pipelineTwo}, Repos: []*api.Repo{repoOne, repoTwo}, From 9b451c6fe38ce90a74751a4b296631ff88881f0d Mon Sep 17 00:00:00 2001 From: ecrupper Date: Thu, 16 May 2024 11:26:45 -0500 Subject: [PATCH 06/34] pull in types and add event to subject --- go.mod | 4 +--- go.sum | 2 ++ internal/token/mint.go | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index a67cba095..0fcdd3f0b 100644 --- a/go.mod +++ b/go.mod @@ -2,8 +2,6 @@ module github.com/go-vela/server go 1.21.9 -replace github.com/go-vela/types => ../types - require ( github.com/Bose/minisentinel v0.0.0-20200130220412-917c5a9223bb github.com/DATA-DOG/go-sqlmock v1.5.2 @@ -18,7 +16,7 @@ require ( github.com/ghodss/yaml v1.0.0 github.com/gin-gonic/gin v1.9.1 github.com/go-playground/assert/v2 v2.2.0 - github.com/go-vela/types v0.23.4-0.20240417135026-fb4a95c30338 + github.com/go-vela/types v0.23.4-0.20240516161114-57d6b8f77b10 github.com/golang-jwt/jwt/v5 v5.2.1 github.com/google/go-cmp v0.6.0 github.com/google/go-github/v61 v61.0.0 diff --git a/go.sum b/go.sum index 902389a07..7182c2db0 100644 --- a/go.sum +++ b/go.sum @@ -87,6 +87,8 @@ github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/go-vela/types v0.23.4-0.20240516161114-57d6b8f77b10 h1:VQxIqxpJKIOzRnMi4z/d+EOo7jc5PXCnlUvZZl5ajzA= +github.com/go-vela/types v0.23.4-0.20240516161114-57d6b8f77b10/go.mod h1:vISsYDdjz9RPEK6qZ+MxtrdZEjTVU4K30NomB3826u8= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= diff --git a/internal/token/mint.go b/internal/token/mint.go index 538b490d3..d3a89b4ac 100644 --- a/internal/token/mint.go +++ b/internal/token/mint.go @@ -99,7 +99,7 @@ func (tm *Manager) MintToken(mto *MintTokenOpts) (string, error) { } claims.Repo = mto.Repo - claims.Subject = fmt.Sprintf("repo:%s:ref:%s", mto.Repo, mto.Build.GetRef()) + claims.Subject = fmt.Sprintf("repo:%s:ref:%s:event:%s", mto.Repo, mto.Build.GetRef(), mto.Build.GetEvent()) claims.BuildID = mto.Build.GetID() claims.BuildNumber = mto.Build.GetNumber() claims.BuildSender = mto.Build.GetSender() @@ -147,7 +147,7 @@ func (tm *Manager) MintIDToken(mto *MintTokenOpts, db database.Interface) (strin claims.BuildNumber = mto.Build.GetNumber() claims.BuildSender = mto.Build.GetSender() claims.Repo = mto.Repo - claims.Subject = fmt.Sprintf("repo:%s:ref:%s", mto.Repo, mto.Build.GetRef()) + claims.Subject = fmt.Sprintf("repo:%s:ref:%s:event:%s", mto.Repo, mto.Build.GetRef(), mto.Build.GetEvent()) claims.Audience = mto.Audience claims.TokenType = mto.TokenType claims.Image = mto.Image From 9be3a5a6f3987761a0705244af7113f87ec432cd Mon Sep 17 00:00:00 2001 From: ecrupper Date: Thu, 16 May 2024 11:30:51 -0500 Subject: [PATCH 07/34] address lint review feedback --- cmd/vela-server/token.go | 5 ++++- database/resource.go | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/cmd/vela-server/token.go b/cmd/vela-server/token.go index ead52788e..bf98025f4 100644 --- a/cmd/vela-server/token.go +++ b/cmd/vela-server/token.go @@ -26,7 +26,10 @@ func setupTokenManager(c *cli.Context, db database.Interface) (*token.Manager, e } // generate a new RSA key pair - tm.GenerateRSA(db) + err := tm.GenerateRSA(db) + if err != nil { + return nil, err + } return tm, nil } diff --git a/database/resource.go b/database/resource.go index ee79993f9..c11341c64 100644 --- a/database/resource.go +++ b/database/resource.go @@ -104,6 +104,9 @@ func (e *engine) NewResources(ctx context.Context) error { jwk.WithLogger(e.logger), jwk.WithSkipCreation(e.config.SkipCreation), ) + if err != nil { + return err + } // create the database agnostic engine for logs e.LogInterface, err = log.New( From 43cae896dfda966c4de13b9eda78430bba60b9ee Mon Sep 17 00:00:00 2001 From: ecrupper Date: Thu, 16 May 2024 11:55:11 -0500 Subject: [PATCH 08/34] fix tests --- api/types/oidc.go | 4 ++++ database/integration_test.go | 10 +++++----- database/resource_test.go | 3 +++ router/middleware/perm/perm_test.go | 4 ++-- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/api/types/oidc.go b/api/types/oidc.go index 405d5c187..00174281c 100644 --- a/api/types/oidc.go +++ b/api/types/oidc.go @@ -3,6 +3,8 @@ package types // OpenIDConfig is a struct that represents the OpenID Connect configuration. +// +// swagger:model OpenIDConfig type OpenIDConfig struct { Issuer string `json:"issuer"` JWKSAddress string `json:"jwks_uri"` @@ -11,6 +13,8 @@ type OpenIDConfig struct { } // JWKS is a slice of JWKs. +// +// swagger:model JWKS type JWKS struct { Keys []JWK `json:"keys"` } diff --git a/database/integration_test.go b/database/integration_test.go index 7e579a738..0f6e6d930 100644 --- a/database/integration_test.go +++ b/database/integration_test.go @@ -42,7 +42,7 @@ type Resources struct { Deployments []*library.Deployment Executables []*library.BuildExecutable Hooks []*library.Hook - JWKs []*api.JWK + JWKs []api.JWK Logs []*library.Log Pipelines []*library.Pipeline Repos []*api.Repo @@ -875,7 +875,7 @@ func testJWKs(t *testing.T, db Interface, resources *Resources) { } for _, jwk := range resources.JWKs { - err := db.CreateJWK(context.TODO(), *jwk) + err := db.CreateJWK(context.TODO(), jwk) if err != nil { t.Errorf("unable to create jwk %s: %v", jwk.Kid, err) } @@ -2555,7 +2555,7 @@ func newResources() *Resources { hookThree.SetLink("https://github.com/github/octocat/settings/hooks/1") hookThree.SetWebhookID(78910) - jwkOne := &api.JWK{ + jwkOne := api.JWK{ Algorithm: "RS256", Kid: "c8da1302-07d6-11ea-882f-4893bca275b8", Kty: "rsa", @@ -2564,7 +2564,7 @@ func newResources() *Resources { E: "123", } - jwkTwo := &api.JWK{ + jwkTwo := api.JWK{ Algorithm: "RS256", Kid: "c8da1302-07d6-11ea-882f-4893bca275b9", Kty: "rsa", @@ -2830,7 +2830,7 @@ func newResources() *Resources { Deployments: []*library.Deployment{deploymentOne, deploymentTwo}, Executables: []*library.BuildExecutable{executableOne, executableTwo}, Hooks: []*library.Hook{hookOne, hookTwo, hookThree}, - JWKs: []*api.JWK{jwkOne, jwkTwo}, + JWKs: []api.JWK{jwkOne, jwkTwo}, Logs: []*library.Log{logServiceOne, logServiceTwo, logStepOne, logStepTwo}, Pipelines: []*library.Pipeline{pipelineOne, pipelineTwo}, Repos: []*api.Repo{repoOne, repoTwo}, diff --git a/database/resource_test.go b/database/resource_test.go index a1371108b..cdc5dbaa3 100644 --- a/database/resource_test.go +++ b/database/resource_test.go @@ -13,6 +13,7 @@ import ( "github.com/go-vela/server/database/deployment" "github.com/go-vela/server/database/executable" "github.com/go-vela/server/database/hook" + "github.com/go-vela/server/database/jwk" "github.com/go-vela/server/database/log" "github.com/go-vela/server/database/pipeline" "github.com/go-vela/server/database/repo" @@ -47,6 +48,8 @@ func TestDatabase_Engine_NewResources(t *testing.T) { // ensure the mock expects the hook queries _mock.ExpectExec(hook.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(hook.CreateRepoIDIndex).WillReturnResult(sqlmock.NewResult(1, 1)) + // ensure the mock expects the jwk queries + _mock.ExpectExec(jwk.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) // ensure the mock expects the log queries _mock.ExpectExec(log.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(log.CreateBuildIDIndex).WillReturnResult(sqlmock.NewResult(1, 1)) diff --git a/router/middleware/perm/perm_test.go b/router/middleware/perm/perm_test.go index 5f921dfc8..772aecf14 100644 --- a/router/middleware/perm/perm_test.go +++ b/router/middleware/perm/perm_test.go @@ -934,8 +934,8 @@ func TestPerm_MustIDRequestToken_WrongBuild(t *testing.T) { // run test engine.ServeHTTP(context.Writer, context.Request) - if resp.Code != http.StatusUnauthorized { - t.Errorf("MustBuildAccess returned %v, want %v", resp.Code, http.StatusOK) + if resp.Code != http.StatusBadRequest { + t.Errorf("MustBuildAccess returned %v, want %v", resp.Code, http.StatusBadRequest) } } From 7c605ab086313ef5eba48d954e45bdd9fe24c5e9 Mon Sep 17 00:00:00 2001 From: ecrupper Date: Thu, 16 May 2024 12:16:08 -0500 Subject: [PATCH 09/34] more integration test fixes --- database/integration_test.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/database/integration_test.go b/database/integration_test.go index 0f6e6d930..fae1981b8 100644 --- a/database/integration_test.go +++ b/database/integration_test.go @@ -19,6 +19,7 @@ import ( "github.com/go-vela/server/database/deployment" "github.com/go-vela/server/database/executable" "github.com/go-vela/server/database/hook" + "github.com/go-vela/server/database/jwk" "github.com/go-vela/server/database/log" "github.com/go-vela/server/database/pipeline" "github.com/go-vela/server/database/repo" @@ -858,13 +859,13 @@ func testHooks(t *testing.T, db Interface, resources *Resources) { } func testJWKs(t *testing.T, db Interface, resources *Resources) { - // create a variable to track the number of methods called for logs + // create a variable to track the number of methods called for jwks methods := make(map[string]bool) - // capture the element type of the log interface - element := reflect.TypeOf(new(log.LogInterface)).Elem() - // iterate through all methods found in the log interface + // capture the element type of the jwk interface + element := reflect.TypeOf(new(jwk.JWKInterface)).Elem() + // iterate through all methods found in the jwk interface for i := 0; i < element.NumMethod(); i++ { - // skip tracking the methods to create indexes and tables for logs + // skip tracking the methods to create indexes and tables for jwks // since those are already called when the database engine starts if strings.Contains(element.Method(i).Name, "Table") { continue @@ -923,7 +924,7 @@ func testJWKs(t *testing.T, db Interface, resources *Resources) { // ensure we called all the methods we expected to for method, called := range methods { if !called { - t.Errorf("method %s was not called for logs", method) + t.Errorf("method %s was not called for jwks", method) } } } From a595dafe5ca7a393ba484aa5692166cb487438d4 Mon Sep 17 00:00:00 2001 From: ecrupper Date: Fri, 17 May 2024 09:26:18 -0500 Subject: [PATCH 10/34] bytes buffer for exponent --- internal/token/generate_rsa.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/internal/token/generate_rsa.go b/internal/token/generate_rsa.go index 5d48ee7f0..80e1b3ed1 100644 --- a/internal/token/generate_rsa.go +++ b/internal/token/generate_rsa.go @@ -3,11 +3,12 @@ package token import ( + "bytes" "context" "crypto/rand" "crypto/rsa" "encoding/base64" - "strconv" + "encoding/binary" "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" @@ -30,6 +31,14 @@ func (tm *Manager) GenerateRSA(db database.Interface) error { return err } + // convert exponent to binary data to encode in base64 + e := new(bytes.Buffer) + + err = binary.Write(e, binary.BigEndian, int64(privateRSAKey.PublicKey.E)) + if err != nil { + return err + } + // abstract the JWK from the public key information key := api.JWK{ Algorithm: jwt.SigningMethodRS256.Name, @@ -37,7 +46,7 @@ func (tm *Manager) GenerateRSA(db database.Interface) error { Use: "sig", Kty: "RSA", N: base64.RawURLEncoding.EncodeToString(privateRSAKey.PublicKey.N.Bytes()), - E: base64.RawURLEncoding.EncodeToString([]byte(strconv.Itoa(privateRSAKey.PublicKey.E))), + E: base64.RawURLEncoding.EncodeToString(e.Bytes()), } // create the JWK in the database From 5582b2698eed8cd9c2e7dc3487fb2a5488be962f Mon Sep 17 00:00:00 2001 From: ecrupper Date: Mon, 20 May 2024 08:51:31 -0500 Subject: [PATCH 11/34] correct issuer and add commands claim --- api/build/id_request_token.go | 3 +++ api/build/id_token.go | 1 + api/oi_config.go | 2 +- cmd/vela-server/token.go | 4 +++- internal/token/mint.go | 4 ++++ router/router.go | 4 ++-- 6 files changed, 14 insertions(+), 4 deletions(-) diff --git a/api/build/id_request_token.go b/api/build/id_request_token.go index f0c6fcd9b..7d8586e89 100644 --- a/api/build/id_request_token.go +++ b/api/build/id_request_token.go @@ -5,6 +5,7 @@ package build import ( "fmt" "net/http" + "strconv" "time" "github.com/gin-gonic/gin" @@ -87,6 +88,7 @@ func GetIDRequestToken(c *gin.Context) { image := c.Query("image") request := c.Query("request") + commands, _ := strconv.ParseBool(c.Query("commands")) // retrieve token manager from context tm := c.MustGet("token-manager").(*token.Manager) @@ -101,6 +103,7 @@ func GetIDRequestToken(c *gin.Context) { TokenDuration: exp, Image: image, Request: request, + Commands: commands, } // mint token diff --git a/api/build/id_token.go b/api/build/id_token.go index 90ff119d8..8a6a412b7 100644 --- a/api/build/id_token.go +++ b/api/build/id_token.go @@ -88,6 +88,7 @@ func GetIDToken(c *gin.Context) { TokenDuration: tm.IDTokenDuration, Image: cl.Image, Request: cl.Request, + Commands: cl.Commands, } // if audience is provided, include that in claims diff --git a/api/oi_config.go b/api/oi_config.go index 32e68bd2f..2390b750a 100644 --- a/api/oi_config.go +++ b/api/oi_config.go @@ -34,7 +34,7 @@ func GetOpenIDConfig(c *gin.Context) { m := c.MustGet("metadata").(*internal.Metadata) config := types.OpenIDConfig{ - Issuer: m.Vela.Address, + Issuer: fmt.Sprintf("%s/_services/token", m.Vela.Address), JWKSAddress: fmt.Sprintf("%s/%s", m.Vela.Address, "_services/token/.well-known/jwks"), SupportedClaims: []string{ "sub", diff --git a/cmd/vela-server/token.go b/cmd/vela-server/token.go index bf98025f4..9853c6c2c 100644 --- a/cmd/vela-server/token.go +++ b/cmd/vela-server/token.go @@ -3,6 +3,8 @@ package main import ( + "fmt" + "github.com/sirupsen/logrus" "github.com/urfave/cli/v2" @@ -22,7 +24,7 @@ func setupTokenManager(c *cli.Context, db database.Interface) (*token.Manager, e WorkerAuthTokenDuration: c.Duration("worker-auth-token-duration"), WorkerRegisterTokenDuration: c.Duration("worker-register-token-duration"), IDTokenDuration: c.Duration("id-token-duration"), - Issuer: c.String("server-addr"), + Issuer: fmt.Sprintf("%s/_services/token", c.String("server-addr")), } // generate a new RSA key pair diff --git a/internal/token/mint.go b/internal/token/mint.go index d3a89b4ac..8b4f12a90 100644 --- a/internal/token/mint.go +++ b/internal/token/mint.go @@ -28,6 +28,7 @@ type Claims struct { TokenType string `json:"token_type,omitempty"` Image string `json:"image,omitempty"` Request string `json:"request,omitempty"` + Commands bool `json:"commands,omitempty"` jwt.RegisteredClaims } @@ -43,6 +44,7 @@ type MintTokenOpts struct { Audience []string Image string Request string + Commands bool } // MintToken mints a Vela JWT Token given a set of options. @@ -105,6 +107,7 @@ func (tm *Manager) MintToken(mto *MintTokenOpts) (string, error) { claims.BuildSender = mto.Build.GetSender() claims.Image = mto.Image claims.Request = mto.Request + claims.Commands = mto.Commands default: return "", errors.New("invalid token type") @@ -152,6 +155,7 @@ func (tm *Manager) MintIDToken(mto *MintTokenOpts, db database.Interface) (strin claims.TokenType = mto.TokenType claims.Image = mto.Image claims.Request = mto.Request + claims.Commands = mto.Commands // set standard claims claims.IssuedAt = jwt.NewNumericDate(time.Now()) diff --git a/router/router.go b/router/router.go index d271e44f2..2e7aebbb2 100644 --- a/router/router.go +++ b/router/router.go @@ -90,8 +90,8 @@ func Load(options ...gin.HandlerFunc) *gin.Engine { r.POST("/webhook", webhook.PostWebhook) // JWKS endpoints - r.GET("_services/token/.well-known/openid-configuration", api.GetOpenIDConfig) - r.GET("_services/token/.well-known/jwks", api.GetJWKS) + r.GET("/_services/token/.well-known/openid-configuration", api.GetOpenIDConfig) + r.GET("/_services/token/.well-known/jwks", api.GetJWKS) // Authentication endpoints authenticate := r.Group("/authenticate") From a7d8b9b7baa922e8e06061fd934f640bf8a52d80 Mon Sep 17 00:00:00 2001 From: ecrupper Date: Tue, 21 May 2024 08:42:14 -0500 Subject: [PATCH 12/34] sender to actor --- api/oi_config.go | 4 +++- internal/token/mint.go | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/api/oi_config.go b/api/oi_config.go index 2390b750a..d17174338 100644 --- a/api/oi_config.go +++ b/api/oi_config.go @@ -41,10 +41,12 @@ func GetOpenIDConfig(c *gin.Context) { "exp", "iat", "iss", + "aud", "build_number", "repo", "token_type", - "build_sender", + "actor", + "commands", "image", "request", }, diff --git a/internal/token/mint.go b/internal/token/mint.go index 8b4f12a90..b223905e8 100644 --- a/internal/token/mint.go +++ b/internal/token/mint.go @@ -21,7 +21,7 @@ import ( type Claims struct { BuildID int64 `json:"build_id,omitempty"` BuildNumber int `json:"build_number,omitempty"` - BuildSender string `json:"build_sender,omitempty"` + Actor string `json:"actor,omitempty"` IsActive bool `json:"is_active,omitempty"` IsAdmin bool `json:"is_admin,omitempty"` Repo string `json:"repo,omitempty"` @@ -104,7 +104,7 @@ func (tm *Manager) MintToken(mto *MintTokenOpts) (string, error) { claims.Subject = fmt.Sprintf("repo:%s:ref:%s:event:%s", mto.Repo, mto.Build.GetRef(), mto.Build.GetEvent()) claims.BuildID = mto.Build.GetID() claims.BuildNumber = mto.Build.GetNumber() - claims.BuildSender = mto.Build.GetSender() + claims.Actor = mto.Build.GetSender() claims.Image = mto.Image claims.Request = mto.Request claims.Commands = mto.Commands @@ -148,7 +148,7 @@ func (tm *Manager) MintIDToken(mto *MintTokenOpts, db database.Interface) (strin // set claims based on input claims.BuildNumber = mto.Build.GetNumber() - claims.BuildSender = mto.Build.GetSender() + claims.Actor = mto.Build.GetSender() claims.Repo = mto.Repo claims.Subject = fmt.Sprintf("repo:%s:ref:%s:event:%s", mto.Repo, mto.Build.GetRef(), mto.Build.GetEvent()) claims.Audience = mto.Audience From 75770cd4872ab75369457da489410d63bb43411c Mon Sep 17 00:00:00 2001 From: ecrupper Date: Tue, 21 May 2024 15:28:10 -0500 Subject: [PATCH 13/34] use lestrrat jwx lib for jwks --- api/jwks.go | 3 +- api/types/oidc.go | 31 ++++---- database/integration_test.go | 62 ++++++++-------- database/jwk/create.go | 13 ++-- database/jwk/create_test.go | 10 ++- database/jwk/get.go | 4 +- database/jwk/get_test.go | 27 ++++--- database/jwk/interface.go | 8 +-- database/jwk/jwk_test.go | 25 +++++++ database/jwk/list.go | 10 +-- database/jwk/list_test.go | 45 ++++++------ database/jwk/rotate_test.go | 45 ++++++------ database/testutils/api_resources.go | 30 +++++--- database/types/jwk.go | 63 +++++------------ database/types/jwk_test.go | 106 ++++++++++++++++++++++++++++ go.mod | 11 ++- go.sum | 21 ++++++ internal/token/generate_rsa.go | 24 ++----- internal/token/mint.go | 2 +- mock/server/authentication.go | 57 +++++++++++++++ mock/server/build.go | 54 ++++++++++++++ mock/server/server.go | 6 ++ 22 files changed, 453 insertions(+), 204 deletions(-) create mode 100644 database/types/jwk_test.go diff --git a/api/jwks.go b/api/jwks.go index 13323fd27..3b002883c 100644 --- a/api/jwks.go +++ b/api/jwks.go @@ -8,7 +8,6 @@ import ( "github.com/gin-gonic/gin" - "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" "github.com/go-vela/server/util" ) @@ -40,5 +39,5 @@ func GetJWKS(c *gin.Context) { return } - c.JSON(http.StatusOK, types.JWKS{Keys: keys}) + c.JSON(http.StatusOK, keys) } diff --git a/api/types/oidc.go b/api/types/oidc.go index 00174281c..cd0168563 100644 --- a/api/types/oidc.go +++ b/api/types/oidc.go @@ -2,6 +2,8 @@ package types +import "github.com/golang-jwt/jwt/v5" + // OpenIDConfig is a struct that represents the OpenID Connect configuration. // // swagger:model OpenIDConfig @@ -12,22 +14,15 @@ type OpenIDConfig struct { Algorithms []string `json:"id_token_signing_alg_values_supported"` } -// JWKS is a slice of JWKs. -// -// swagger:model JWKS -type JWKS struct { - Keys []JWK `json:"keys"` -} - -// JWK represents a JSON Web Key parsed with fields as the correct Go types. -type JWK struct { - Algorithm string `json:"alg"` - Use string `json:"use"` - X5t string `json:"x5t"` - Kid string `json:"kid"` - Kty string `json:"kty"` - X5c []string `json:"x5c"` - - N string `json:"n"` // modulus - E string `json:"e"` // public exponent +// OpenIDClaims struct is an extension of the JWT standard claims. It +// includes information relevant to OIDC services. +type OpenIDClaims struct { + BuildNumber int `json:"build_number,omitempty"` + Actor string `json:"actor,omitempty"` + Repo string `json:"repo,omitempty"` + TokenType string `json:"token_type,omitempty"` + Image string `json:"image,omitempty"` + Request string `json:"request,omitempty"` + Commands bool `json:"commands,omitempty"` + jwt.RegisteredClaims } diff --git a/database/integration_test.go b/database/integration_test.go index ca53ea287..f478fdd8e 100644 --- a/database/integration_test.go +++ b/database/integration_test.go @@ -12,6 +12,7 @@ import ( "github.com/adhocore/gronx" "github.com/google/go-cmp/cmp" + "github.com/lestrrat-go/jwx/jwk" api "github.com/go-vela/server/api/types" "github.com/go-vela/server/api/types/settings" @@ -20,7 +21,7 @@ import ( "github.com/go-vela/server/database/deployment" "github.com/go-vela/server/database/executable" "github.com/go-vela/server/database/hook" - "github.com/go-vela/server/database/jwk" + dbJWK "github.com/go-vela/server/database/jwk" "github.com/go-vela/server/database/log" "github.com/go-vela/server/database/pipeline" "github.com/go-vela/server/database/repo" @@ -44,7 +45,7 @@ type Resources struct { Deployments []*library.Deployment Executables []*library.BuildExecutable Hooks []*library.Hook - JWKs []api.JWK + JWKs jwk.Set Logs []*library.Log Pipelines []*library.Pipeline Repos []*api.Repo @@ -863,7 +864,7 @@ func testJWKs(t *testing.T, db Interface, resources *Resources) { // create a variable to track the number of methods called for jwks methods := make(map[string]bool) // capture the element type of the jwk interface - element := reflect.TypeOf(new(jwk.JWKInterface)).Elem() + element := reflect.TypeOf(new(dbJWK.JWKInterface)).Elem() // iterate through all methods found in the jwk interface for i := 0; i < element.NumMethod(); i++ { // skip tracking the methods to create indexes and tables for jwks @@ -876,10 +877,14 @@ func testJWKs(t *testing.T, db Interface, resources *Resources) { methods[element.Method(i).Name] = false } - for _, jwk := range resources.JWKs { - err := db.CreateJWK(context.TODO(), jwk) + for i := 0; i < resources.JWKs.Len(); i++ { + jk, _ := resources.JWKs.Get(i) + + jkPub, _ := jk.(jwk.RSAPublicKey) + + err := db.CreateJWK(context.TODO(), jkPub) if err != nil { - t.Errorf("unable to create jwk %s: %v", jwk.Kid, err) + t.Errorf("unable to create jwk %s: %v", jkPub.KeyID(), err) } } methods["CreateJWK"] = true @@ -895,14 +900,18 @@ func testJWKs(t *testing.T, db Interface, resources *Resources) { methods["ListJWKs"] = true - for _, jwk := range resources.JWKs { - got, err := db.GetActiveJWK(context.TODO(), jwk.Kid) + for i := 0; i < resources.JWKs.Len(); i++ { + jk, _ := resources.JWKs.Get(i) + + jkPub, _ := jk.(jwk.RSAPublicKey) + + got, err := db.GetActiveJWK(context.TODO(), jkPub.KeyID()) if err != nil { - t.Errorf("unable to get jwk %s: %v", jwk.Kid, err) + t.Errorf("unable to get jwk %s: %v", jkPub.KeyID(), err) } - if !cmp.Equal(jwk, got) { - t.Errorf("GetJWK() is %v, want %v", got, jwk) + if !cmp.Equal(jkPub, got) { + t.Errorf("GetJWK() is %v, want %v", got, jkPub) } } @@ -913,8 +922,12 @@ func testJWKs(t *testing.T, db Interface, resources *Resources) { t.Errorf("unable to rotate keys: %v", err) } - for _, jwk := range resources.JWKs { - _, err := db.GetActiveJWK(context.TODO(), jwk.Kid) + for i := 0; i < resources.JWKs.Len(); i++ { + jk, _ := resources.JWKs.Get(i) + + jkPub, _ := jk.(jwk.RSAPublicKey) + + _, err := db.GetActiveJWK(context.TODO(), jkPub.KeyID()) if err == nil { t.Errorf("GetActiveJWK() should return err after rotation") } @@ -2557,23 +2570,12 @@ func newResources() *Resources { hookThree.SetLink("https://github.com/github/octocat/settings/hooks/1") hookThree.SetWebhookID(78910) - jwkOne := api.JWK{ - Algorithm: "RS256", - Kid: "c8da1302-07d6-11ea-882f-4893bca275b8", - Kty: "rsa", - Use: "sig", - N: "123456", - E: "123", - } + jwkOne := testutils.JWK() + jwkTwo := testutils.JWK() - jwkTwo := api.JWK{ - Algorithm: "RS256", - Kid: "c8da1302-07d6-11ea-882f-4893bca275b9", - Kty: "rsa", - Use: "sig", - N: "789101", - E: "456", - } + jwkSet := jwk.NewSet() + jwkSet.Add(jwkOne) + jwkSet.Add(jwkTwo) logServiceOne := new(library.Log) logServiceOne.SetID(1) @@ -2840,7 +2842,7 @@ func newResources() *Resources { Deployments: []*library.Deployment{deploymentOne, deploymentTwo}, Executables: []*library.BuildExecutable{executableOne, executableTwo}, Hooks: []*library.Hook{hookOne, hookTwo, hookThree}, - JWKs: []api.JWK{jwkOne, jwkTwo}, + JWKs: jwkSet, Logs: []*library.Log{logServiceOne, logServiceTwo, logStepOne, logStepTwo}, Pipelines: []*library.Pipeline{pipelineOne, pipelineTwo}, Repos: []*api.Repo{repoOne, repoTwo}, diff --git a/database/jwk/create.go b/database/jwk/create.go index bbef786fd..3f5896e41 100644 --- a/database/jwk/create.go +++ b/database/jwk/create.go @@ -6,27 +6,22 @@ import ( "context" "database/sql" + "github.com/lestrrat-go/jwx/jwk" "github.com/sirupsen/logrus" - api "github.com/go-vela/server/api/types" "github.com/go-vela/server/constants" "github.com/go-vela/server/database/types" ) // CreateJWK creates a new JWK in the database. -func (e *engine) CreateJWK(_ context.Context, j api.JWK) error { +func (e *engine) CreateJWK(_ context.Context, j jwk.RSAPublicKey) error { e.logger.WithFields(logrus.Fields{ - "jwk": j.Kid, - }).Tracef("creating key %s in the database", j.Kid) + "jwk": j.KeyID(), + }).Tracef("creating key %s in the database", j.KeyID()) key := types.JWKFromAPI(j) key.Active = sql.NullBool{Bool: true, Valid: true} - err := key.Validate() - if err != nil { - return err - } - // send query to the database return e.client.Table(constants.TableJWK).Create(key).Error } diff --git a/database/jwk/create_test.go b/database/jwk/create_test.go index e5cc20121..2275a4c4e 100644 --- a/database/jwk/create_test.go +++ b/database/jwk/create_test.go @@ -4,6 +4,7 @@ package jwk import ( "context" + "encoding/json" "testing" "github.com/DATA-DOG/go-sqlmock" @@ -13,8 +14,11 @@ import ( func TestJWK_Engine_CreateJWK(t *testing.T) { // setup types - _jwk := testutils.APIJWK() - _jwk.Kid = "c8da1302-07d6-11ea-882f-4893bca275b8" + _jwk := testutils.JWK() + _jwkBytes, err := json.Marshal(_jwk) + if err != nil { + t.Errorf("unable to marshal JWK: %v", err) + } _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() @@ -23,7 +27,7 @@ func TestJWK_Engine_CreateJWK(t *testing.T) { _mock.ExpectExec(`INSERT INTO "jwks" ("id","active","key") VALUES ($1,$2,$3)`). - WithArgs("c8da1302-07d6-11ea-882f-4893bca275b8", true, `{"alg":"","use":"","x5t":"","kid":"c8da1302-07d6-11ea-882f-4893bca275b8","kty":"","x5c":null,"n":"","e":""}`). + WithArgs(_jwk.KeyID(), true, _jwkBytes). WillReturnResult(sqlmock.NewResult(1, 1)) _sqlite := testSqlite(t) diff --git a/database/jwk/get.go b/database/jwk/get.go index d98ad910b..49494a222 100644 --- a/database/jwk/get.go +++ b/database/jwk/get.go @@ -5,13 +5,13 @@ package jwk import ( "context" - api "github.com/go-vela/server/api/types" "github.com/go-vela/server/constants" "github.com/go-vela/server/database/types" + "github.com/lestrrat-go/jwx/jwk" ) // GetActiveJWK gets a JWK by UUID (kid) from the database if active. -func (e *engine) GetActiveJWK(_ context.Context, id string) (api.JWK, error) { +func (e *engine) GetActiveJWK(_ context.Context, id string) (jwk.RSAPublicKey, error) { e.logger.Tracef("getting key %s from the database", id) // variable to store query results diff --git a/database/jwk/get_test.go b/database/jwk/get_test.go index 9e65991bc..ab7568573 100644 --- a/database/jwk/get_test.go +++ b/database/jwk/get_test.go @@ -4,24 +4,23 @@ package jwk import ( "context" + "encoding/json" "testing" "github.com/DATA-DOG/go-sqlmock" "github.com/google/go-cmp/cmp" + "github.com/lestrrat-go/jwx/jwk" - api "github.com/go-vela/server/api/types" "github.com/go-vela/server/database/testutils" ) func TestJWK_Engine_GetJWK(t *testing.T) { // setup types - _jwk := testutils.APIJWK() - _jwk.Kid = "c8da1302-07d6-11ea-882f-4893bca275b8" - _jwk.Algorithm = "RS256" - _jwk.Kty = "rsa" - _jwk.Use = "sig" - _jwk.N = "123456" - _jwk.E = "123" + _jwk := testutils.JWK() + _jwkBytes, err := json.Marshal(_jwk) + if err != nil { + t.Errorf("unable to marshal JWK: %v", err) + } _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() @@ -29,15 +28,15 @@ func TestJWK_Engine_GetJWK(t *testing.T) { // create expected result in mock _rows := sqlmock.NewRows( []string{"id", "active", "key"}, - ).AddRow("c8da1302-07d6-11ea-882f-4893bca275b8", true, []byte(`{"alg":"RS256","use":"sig","x5t":"","kid":"c8da1302-07d6-11ea-882f-4893bca275b8","kty":"rsa","x5c":null,"n":"123456","e":"123"}`)) + ).AddRow(_jwk.KeyID(), true, _jwkBytes) // ensure the mock expects the query - _mock.ExpectQuery(`SELECT * FROM "jwks" WHERE id = $1 AND active = $2 LIMIT $3`).WithArgs("c8da1302-07d6-11ea-882f-4893bca275b8", true, 1).WillReturnRows(_rows) + _mock.ExpectQuery(`SELECT * FROM "jwks" WHERE id = $1 AND active = $2 LIMIT $3`).WithArgs(_jwk.KeyID(), true, 1).WillReturnRows(_rows) _sqlite := testSqlite(t) defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() - err := _sqlite.CreateJWK(context.TODO(), _jwk) + err = _sqlite.CreateJWK(context.TODO(), _jwk) if err != nil { t.Errorf("unable to create test repo for sqlite: %v", err) } @@ -47,7 +46,7 @@ func TestJWK_Engine_GetJWK(t *testing.T) { failure bool name string database *engine - want api.JWK + want jwk.RSAPublicKey }{ { failure: false, @@ -66,7 +65,7 @@ func TestJWK_Engine_GetJWK(t *testing.T) { // run tests for _, test := range tests { t.Run(test.name, func(t *testing.T) { - got, err := test.database.GetActiveJWK(context.TODO(), "c8da1302-07d6-11ea-882f-4893bca275b8") + got, err := test.database.GetActiveJWK(context.TODO(), _jwk.KeyID()) if test.failure { if err == nil { @@ -80,7 +79,7 @@ func TestJWK_Engine_GetJWK(t *testing.T) { t.Errorf("GetActiveJWK for %s returned err: %v", test.name, err) } - if diff := cmp.Diff(got, test.want); diff != "" { + if diff := cmp.Diff(test.want, got, jwkOpts); diff != "" { t.Errorf("GetActiveJWK mismatch (-want +got):\n%s", diff) } }) diff --git a/database/jwk/interface.go b/database/jwk/interface.go index 38ef437d6..a83b1b300 100644 --- a/database/jwk/interface.go +++ b/database/jwk/interface.go @@ -5,7 +5,7 @@ package jwk import ( "context" - api "github.com/go-vela/server/api/types" + "github.com/lestrrat-go/jwx/jwk" ) // JWKInterface represents the Vela interface for JWK @@ -23,11 +23,11 @@ type JWKInterface interface { // https://en.wikipedia.org/wiki/Data_manipulation_language // CreateJWK defines a function that creates a JWK. - CreateJWK(context.Context, api.JWK) error + CreateJWK(context.Context, jwk.RSAPublicKey) error // RotateKeys defines a function that rotates JWKs. RotateKeys(context.Context) error // ListJWKs defines a function that lists all JWKs configured. - ListJWKs(context.Context) ([]api.JWK, error) + ListJWKs(context.Context) (jwk.Set, error) // GetJWK defines a function that gets a JWK by the provided key ID. - GetActiveJWK(context.Context, string) (api.JWK, error) + GetActiveJWK(context.Context, string) (jwk.RSAPublicKey, error) } diff --git a/database/jwk/jwk_test.go b/database/jwk/jwk_test.go index 95ff9a463..d0a958218 100644 --- a/database/jwk/jwk_test.go +++ b/database/jwk/jwk_test.go @@ -7,6 +7,8 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + "github.com/google/go-cmp/cmp" + "github.com/lestrrat-go/jwx/jwk" "github.com/sirupsen/logrus" "gorm.io/driver/postgres" "gorm.io/driver/sqlite" @@ -161,3 +163,26 @@ func testSqlite(t *testing.T) *engine { return _engine } + +var jwkOpts = cmp.Options{ + cmp.FilterValues(func(x, y interface{}) bool { + _, xOk := x.(jwk.RSAPublicKey) + _, yOk := y.(jwk.RSAPublicKey) + return xOk && yOk + }, cmp.Comparer(func(x, y interface{}) bool { + xJWK := x.(jwk.RSAPublicKey) + yJWK := y.(jwk.RSAPublicKey) + + var rawXKey, rawYKey interface{} + + if err := xJWK.Raw(&rawXKey); err != nil { + return false + } + + if err := yJWK.Raw(&rawYKey); err != nil { + return false + } + + return reflect.DeepEqual(rawXKey, rawYKey) && xJWK.KeyID() == yJWK.KeyID() + })), +} diff --git a/database/jwk/list.go b/database/jwk/list.go index 4380d9dda..890d04376 100644 --- a/database/jwk/list.go +++ b/database/jwk/list.go @@ -5,17 +5,17 @@ package jwk import ( "context" - api "github.com/go-vela/server/api/types" "github.com/go-vela/server/constants" "github.com/go-vela/server/database/types" + "github.com/lestrrat-go/jwx/jwk" ) // ListJWKs gets a list of all configured JWKs from the database. -func (e *engine) ListJWKs(_ context.Context) ([]api.JWK, error) { +func (e *engine) ListJWKs(_ context.Context) (jwk.Set, error) { e.logger.Trace("listing all keysets from the database") k := new([]types.JWK) - keys := []api.JWK{} + keySet := jwk.NewSet() // send query to the database and store result in variable err := e.client. @@ -32,8 +32,8 @@ func (e *engine) ListJWKs(_ context.Context) ([]api.JWK, error) { tmp := key // convert query result to API type - keys = append(keys, tmp.ToAPI()) + keySet.Add(tmp.ToAPI()) } - return keys, nil + return keySet, nil } diff --git a/database/jwk/list_test.go b/database/jwk/list_test.go index 8060b8330..f4d54dcc2 100644 --- a/database/jwk/list_test.go +++ b/database/jwk/list_test.go @@ -4,32 +4,29 @@ package jwk import ( "context" + "encoding/json" "reflect" "testing" "github.com/DATA-DOG/go-sqlmock" + "github.com/lestrrat-go/jwx/jwk" - api "github.com/go-vela/server/api/types" "github.com/go-vela/server/database/testutils" ) func TestJWK_Engine_ListJWKs(t *testing.T) { // setup types - _jwkOne := testutils.APIJWK() - _jwkOne.Kid = "c8da1302-07d6-11ea-882f-4893bca275b8" - _jwkOne.Algorithm = "RS256" - _jwkOne.Kty = "rsa" - _jwkOne.Use = "sig" - _jwkOne.N = "123456" - _jwkOne.E = "123" - - _jwkTwo := testutils.APIJWK() - _jwkTwo.Kid = "c8da1302-07d6-11ea-882f-4893bca275b9" - _jwkTwo.Algorithm = "RS256" - _jwkTwo.Kty = "rsa" - _jwkTwo.Use = "sig" - _jwkTwo.N = "123789" - _jwkTwo.E = "456" + _jwkOne := testutils.JWK() + _jwkOneBytes, err := json.Marshal(_jwkOne) + if err != nil { + t.Errorf("unable to marshal JWK: %v", err) + } + + _jwkTwo := testutils.JWK() + _jwkTwoBytes, err := json.Marshal(_jwkTwo) + if err != nil { + t.Errorf("unable to marshal JWK: %v", err) + } _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() @@ -37,8 +34,8 @@ func TestJWK_Engine_ListJWKs(t *testing.T) { // create expected result in mock _rows := sqlmock.NewRows( []string{"id", "active", "key"}). - AddRow("c8da1302-07d6-11ea-882f-4893bca275b8", true, []byte(`{"alg":"RS256","use":"sig","x5t":"","kid":"c8da1302-07d6-11ea-882f-4893bca275b8","kty":"rsa","x5c":null,"n":"123456","e":"123"}`)). - AddRow("c8da1302-07d6-11ea-882f-4893bca275b8", true, []byte(`{"alg":"RS256","use":"sig","x5t":"","kid":"c8da1302-07d6-11ea-882f-4893bca275b9","kty":"rsa","x5c":null,"n":"123789","e":"456"}`)) + AddRow(_jwkOne.KeyID(), true, _jwkOneBytes). + AddRow(_jwkTwo.KeyID(), true, _jwkTwoBytes) // ensure the mock expects the query _mock.ExpectQuery(`SELECT * FROM "jwks"`).WillReturnRows(_rows) @@ -46,7 +43,7 @@ func TestJWK_Engine_ListJWKs(t *testing.T) { _sqlite := testSqlite(t) defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() - err := _sqlite.CreateJWK(context.TODO(), _jwkOne) + err = _sqlite.CreateJWK(context.TODO(), _jwkOne) if err != nil { t.Errorf("unable to create test jwk for sqlite: %v", err) } @@ -56,24 +53,28 @@ func TestJWK_Engine_ListJWKs(t *testing.T) { t.Errorf("unable to create test jwk for sqlite: %v", err) } + wantSet := jwk.NewSet() + wantSet.Add(_jwkOne) + wantSet.Add(_jwkTwo) + // setup tests tests := []struct { failure bool name string database *engine - want []api.JWK + want jwk.Set }{ { failure: false, name: "postgres", database: _postgres, - want: []api.JWK{_jwkOne, _jwkTwo}, + want: wantSet, }, { failure: false, name: "sqlite3", database: _sqlite, - want: []api.JWK{_jwkOne, _jwkTwo}, + want: wantSet, }, } diff --git a/database/jwk/rotate_test.go b/database/jwk/rotate_test.go index 4551a24b6..cd4acbb72 100644 --- a/database/jwk/rotate_test.go +++ b/database/jwk/rotate_test.go @@ -4,6 +4,7 @@ package jwk import ( "context" + "encoding/json" "testing" "github.com/DATA-DOG/go-sqlmock" @@ -13,21 +14,17 @@ import ( func TestJWK_Engine_RotateKeys(t *testing.T) { // setup types - _jwkOne := testutils.APIJWK() - _jwkOne.Kid = "c8da1302-07d6-11ea-882f-4893bca275b8" - _jwkOne.Algorithm = "RS256" - _jwkOne.Kty = "rsa" - _jwkOne.Use = "sig" - _jwkOne.N = "123456" - _jwkOne.E = "123" - - _jwkTwo := testutils.APIJWK() - _jwkTwo.Kid = "c8da1302-07d6-11ea-882f-4893bca275b9" - _jwkTwo.Algorithm = "RS256" - _jwkTwo.Kty = "rsa" - _jwkTwo.Use = "sig" - _jwkTwo.N = "123789" - _jwkTwo.E = "456" + _jwkOne := testutils.JWK() + _jwkOneBytes, err := json.Marshal(_jwkOne) + if err != nil { + t.Errorf("unable to marshal JWK: %v", err) + } + + _jwkTwo := testutils.JWK() + _jwkTwoBytes, err := json.Marshal(_jwkTwo) + if err != nil { + t.Errorf("unable to marshal JWK: %v", err) + } _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() @@ -35,18 +32,18 @@ func TestJWK_Engine_RotateKeys(t *testing.T) { // create expected result in mock _rows := sqlmock.NewRows( []string{"id", "active", "key"}, - ).AddRow("c8da1302-07d6-11ea-882f-4893bca275b8", true, []byte(`{"alg":"RS256","use":"sig","x5t":"","kid":"c8da1302-07d6-11ea-882f-4893bca275b8","kty":"rsa","x5c":null,"n":"123456","e":"123"}`)) + ).AddRow(_jwkOne.KeyID(), true, _jwkOneBytes) // ensure the mock expects the query - _mock.ExpectQuery(`SELECT * FROM "jwks" WHERE id = $1 AND active = $2 LIMIT $3`).WithArgs("c8da1302-07d6-11ea-882f-4893bca275b8", true, 1).WillReturnRows(_rows) + _mock.ExpectQuery(`SELECT * FROM "jwks" WHERE id = $1 AND active = $2 LIMIT $3`).WithArgs(_jwkOne.KeyID(), true, 1).WillReturnRows(_rows) // create expected result in mock _rows = sqlmock.NewRows( []string{"id", "active", "key"}, - ).AddRow("c8da1302-07d6-11ea-882f-4893bca275b9", true, []byte(`{"alg":"RS256","use":"sig","x5t":"","kid":"c8da1302-07d6-11ea-882f-4893bca275b9","kty":"rsa","x5c":null,"n":"123789","e":"456"}`)) + ).AddRow(_jwkTwo.KeyID(), true, _jwkTwoBytes) // ensure the mock expects the query - _mock.ExpectQuery(`SELECT * FROM "jwks" WHERE id = $1 AND active = $2 LIMIT $3`).WithArgs("c8da1302-07d6-11ea-882f-4893bca275b9", true, 1).WillReturnRows(_rows) + _mock.ExpectQuery(`SELECT * FROM "jwks" WHERE id = $1 AND active = $2 LIMIT $3`).WithArgs(_jwkTwo.KeyID(), true, 1).WillReturnRows(_rows) _mock.ExpectExec(`DELETE FROM "jwks" WHERE active = $1`). WithArgs(false). @@ -59,7 +56,7 @@ func TestJWK_Engine_RotateKeys(t *testing.T) { _sqlite := testSqlite(t) defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() - err := _sqlite.CreateJWK(context.TODO(), _jwkOne) + err = _sqlite.CreateJWK(context.TODO(), _jwkOne) if err != nil { t.Errorf("unable to create test jwk for sqlite: %v", err) } @@ -90,12 +87,12 @@ func TestJWK_Engine_RotateKeys(t *testing.T) { // run tests for _, test := range tests { t.Run(test.name, func(t *testing.T) { - _, err := test.database.GetActiveJWK(context.TODO(), _jwkOne.Kid) + _, err := test.database.GetActiveJWK(context.TODO(), _jwkOne.KeyID()) if err != nil { t.Errorf("GetActiveJWK for %s returned err: %v", test.name, err) } - _, err = test.database.GetActiveJWK(context.TODO(), _jwkTwo.Kid) + _, err = test.database.GetActiveJWK(context.TODO(), _jwkTwo.KeyID()) if err != nil { t.Errorf("GetActiveJWK for %s returned err: %v", test.name, err) } @@ -114,12 +111,12 @@ func TestJWK_Engine_RotateKeys(t *testing.T) { t.Errorf("RotateKeys for %s returned err: %v", test.name, err) } - _, err = test.database.GetActiveJWK(context.TODO(), _jwkOne.Kid) + _, err = test.database.GetActiveJWK(context.TODO(), _jwkOne.KeyID()) if err == nil { t.Errorf("GetActiveJWK for %s should have returned err", test.name) } - _, err = test.database.GetActiveJWK(context.TODO(), _jwkTwo.Kid) + _, err = test.database.GetActiveJWK(context.TODO(), _jwkTwo.KeyID()) if err == nil { t.Errorf("GetActiveJWK for %s should have returned err", test.name) } diff --git a/database/testutils/api_resources.go b/database/testutils/api_resources.go index 01ecfe0d1..2db83998b 100644 --- a/database/testutils/api_resources.go +++ b/database/testutils/api_resources.go @@ -3,10 +3,15 @@ package testutils import ( + "crypto/rand" + "crypto/rsa" + api "github.com/go-vela/server/api/types" "github.com/go-vela/server/api/types/actions" "github.com/go-vela/types/library" "github.com/go-vela/types/raw" + "github.com/google/uuid" + "github.com/lestrrat-go/jwx/jwk" ) // API TEST RESOURCES @@ -269,13 +274,22 @@ func APIDashboardRepo() *api.DashboardRepo { } } -func APIJWK() api.JWK { - return api.JWK{ - Kid: "", - Kty: "", - Algorithm: "", - Use: "", - N: "", - E: "", +func JWK() jwk.RSAPublicKey { + privateRSAKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil + } + + // assign KID to key pair + kid, err := uuid.NewV7() + if err != nil { + return nil } + + j := jwk.NewRSAPublicKey() + _ = j.FromRaw(&privateRSAKey.PublicKey) + + _ = j.Set(jwk.KeyIDKey, kid.String()) + + return j } diff --git a/database/types/jwk.go b/database/types/jwk.go index 6fc383792..486512da1 100644 --- a/database/types/jwk.go +++ b/database/types/jwk.go @@ -4,20 +4,17 @@ package types import ( "database/sql" - "database/sql/driver" "encoding/json" "errors" - "fmt" "github.com/google/uuid" - - api "github.com/go-vela/server/api/types" + "github.com/lestrrat-go/jwx/jwk" ) var ( - // ErrEmptyKID defines the error type when a - // JWK type has an empty ID field provided. - ErrEmptyKID = errors.New("empty key identifier provided") + // ErrInvalidKID defines the error type when a + // JWK type has an invalid ID field provided. + ErrInvalidKID = errors.New("invalid key identifier provided") ) type ( @@ -25,30 +22,10 @@ type ( JWK struct { ID uuid.UUID `gorm:"type:uuid"` Active sql.NullBool `sql:"active"` - Key KeyJSON `sql:"key"` + Key []byte `sql:"key"` } - - KeyJSON api.JWK ) -// Value - Implementation of valuer for database/sql for KeyJSON. -func (k KeyJSON) Value() (driver.Value, error) { - valueString, err := json.Marshal(k) - return string(valueString), err -} - -// Scan - Implement the database/sql scanner interface for KeyJSON. -func (k *KeyJSON) Scan(value interface{}) error { - switch v := value.(type) { - case []byte: - return json.Unmarshal(v, &k) - case string: - return json.Unmarshal([]byte(v), &k) - default: - return fmt.Errorf("wrong type for key: %T", v) - } -} - // Nullify ensures the valid flag for // the sql.Null types are properly set. // @@ -65,38 +42,36 @@ func (j *JWK) Nullify() *JWK { // ToAPI converts the JWK type // to an API JWK type. -func (j *JWK) ToAPI() api.JWK { - return api.JWK(j.Key) -} +func (j *JWK) ToAPI() jwk.RSAPublicKey { + parsedKey, _ := jwk.ParseKey(j.Key) -// Validate verifies the necessary fields for -// the JWK type are populated correctly. -func (j *JWK) Validate() error { - // verify the Name field is populated - if len(j.ID.String()) == 0 { - return ErrEmptyKID + switch jwk := parsedKey.(type) { + case jwk.RSAPublicKey: + return jwk + default: + return nil } - - return nil } // JWKFromAPI converts the API JWK type // to a database JWK type. -func JWKFromAPI(j api.JWK) *JWK { +func JWKFromAPI(j jwk.RSAPublicKey) *JWK { var ( id uuid.UUID err error ) - id, err = uuid.Parse(j.Kid) + id, err = uuid.Parse(j.KeyID()) if err != nil { return nil } - dashboard := &JWK{ + bytesKey, _ := json.Marshal(j) + + jwk := &JWK{ ID: id, - Key: KeyJSON(j), + Key: bytesKey, } - return dashboard.Nullify() + return jwk.Nullify() } diff --git a/database/types/jwk_test.go b/database/types/jwk_test.go new file mode 100644 index 000000000..01fbf6ddc --- /dev/null +++ b/database/types/jwk_test.go @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "database/sql" + "encoding/json" + "reflect" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/uuid" + + "github.com/go-vela/server/database/testutils" +) + +func TestTypes_JWK_Nullify(t *testing.T) { + // setup tests + var j *JWK + + tests := []struct { + JWK *JWK + want *JWK + }{ + { + JWK: testJWK(), + want: testJWK(), + }, + { + JWK: j, + want: nil, + }, + } + + // run tests + for _, test := range tests { + got := test.JWK.Nullify() + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("Nullify is %v, want %v", got, test.want) + } + } +} + +func TestTypes_JWK_ToAPI(t *testing.T) { + // setup types + want := testutils.JWK() + + wantBytes, err := json.Marshal(want) + if err != nil { + t.Errorf("unable to marshal JWK: %v", err) + } + + uuid, _ := uuid.Parse(want.KeyID()) + h := &JWK{ + ID: uuid, + Active: sql.NullBool{Bool: true, Valid: true}, + Key: wantBytes, + } + + // run test + got := h.ToAPI() + + if !reflect.DeepEqual(got, want) { + t.Errorf("ToAPI is %v, want %v", got, want) + } +} + +func TestTypes_JWKFromAPI(t *testing.T) { + j := testutils.JWK() + + jBytes, err := json.Marshal(j) + if err != nil { + t.Errorf("unable to marshal JWK: %v", err) + } + + uuid, err := uuid.Parse(j.KeyID()) + if err != nil { + t.Errorf("unable to parse JWK key id: %v", err) + } + + // setup types + want := &JWK{ + ID: uuid, + Active: sql.NullBool{Bool: false, Valid: false}, + Key: jBytes, + } + + // run test + got := JWKFromAPI(j) + + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("JWKFromAPI() mismatch (-want +got):\n%s", diff) + } +} + +// testJWK is a test helper function to create a JWK +// type with all fields set to a fake value. +func testJWK() *JWK { + uuid, _ := uuid.Parse("c8da1302-07d6-11ea-882f-4893bca275b8") + + return &JWK{ + ID: uuid, + Active: sql.NullBool{Bool: true, Valid: true}, + } +} diff --git a/go.mod b/go.mod index 0fcdd3f0b..fd440fca1 100644 --- a/go.mod +++ b/go.mod @@ -46,7 +46,15 @@ require ( k8s.io/apimachinery v0.29.2 ) -require github.com/stretchr/testify v1.9.0 // indirect +require ( + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect + github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect + github.com/lestrrat-go/blackmagic v1.0.2 // indirect + github.com/lestrrat-go/httpcc v1.0.1 // indirect + github.com/lestrrat-go/iter v1.0.2 // indirect + github.com/lestrrat-go/option v1.0.1 // indirect + github.com/stretchr/testify v1.9.0 // indirect +) require ( github.com/Masterminds/goutils v1.1.1 // indirect @@ -94,6 +102,7 @@ require ( github.com/klauspost/cpuid/v2 v2.2.4 // indirect github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.2.4 // indirect + github.com/lestrrat-go/jwx v1.2.29 github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-sqlite3 v1.14.17 // indirect github.com/mitchellh/copystructure v1.0.0 // indirect diff --git a/go.sum b/go.sum index 7182c2db0..0f39a0a81 100644 --- a/go.sum +++ b/go.sum @@ -56,6 +56,9 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= @@ -184,6 +187,19 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A= +github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= +github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= +github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= +github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= +github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= +github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= +github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= +github.com/lestrrat-go/jwx v1.2.29 h1:QT0utmUJ4/12rmsVQrJ3u55bycPkKqGYuGT4tyRhxSQ= +github.com/lestrrat-go/jwx v1.2.29/go.mod h1:hU8k2l6WF0ncx20uQdOmik/Gjg6E3/wIRtXSNFeZuB8= +github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= +github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= @@ -252,15 +268,18 @@ github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= @@ -306,6 +325,7 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= @@ -341,6 +361,7 @@ golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= diff --git a/internal/token/generate_rsa.go b/internal/token/generate_rsa.go index 80e1b3ed1..a72410460 100644 --- a/internal/token/generate_rsa.go +++ b/internal/token/generate_rsa.go @@ -3,17 +3,13 @@ package token import ( - "bytes" "context" "crypto/rand" "crypto/rsa" - "encoding/base64" - "encoding/binary" - "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" + "github.com/lestrrat-go/jwx/jwk" - api "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" ) @@ -31,26 +27,20 @@ func (tm *Manager) GenerateRSA(db database.Interface) error { return err } - // convert exponent to binary data to encode in base64 - e := new(bytes.Buffer) + j := jwk.NewRSAPublicKey() - err = binary.Write(e, binary.BigEndian, int64(privateRSAKey.PublicKey.E)) + err = j.FromRaw(&privateRSAKey.PublicKey) if err != nil { return err } - // abstract the JWK from the public key information - key := api.JWK{ - Algorithm: jwt.SigningMethodRS256.Name, - Kid: kid.String(), - Use: "sig", - Kty: "RSA", - N: base64.RawURLEncoding.EncodeToString(privateRSAKey.PublicKey.N.Bytes()), - E: base64.RawURLEncoding.EncodeToString(e.Bytes()), + err = j.Set(jwk.KeyIDKey, kid.String()) + if err != nil { + return err } // create the JWK in the database - err = db.CreateJWK(context.TODO(), key) + err = db.CreateJWK(context.TODO(), j) if err != nil { return err } diff --git a/internal/token/mint.go b/internal/token/mint.go index b223905e8..358a9ba07 100644 --- a/internal/token/mint.go +++ b/internal/token/mint.go @@ -131,7 +131,7 @@ func (tm *Manager) MintToken(mto *MintTokenOpts) (string, error) { // MintIDToken mints a Vela JWT ID Token for a build. func (tm *Manager) MintIDToken(mto *MintTokenOpts, db database.Interface) (string, error) { // initialize claims struct - var claims = new(Claims) + var claims = new(api.OpenIDClaims) // validate provided claims if len(mto.Repo) == 0 { diff --git a/mock/server/authentication.go b/mock/server/authentication.go index 094915768..f14ea54d2 100644 --- a/mock/server/authentication.go +++ b/mock/server/authentication.go @@ -7,7 +7,9 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/lestrrat-go/jwx/jwk" + api "github.com/go-vela/server/api/types" "github.com/go-vela/types" "github.com/go-vela/types/constants" "github.com/go-vela/types/library" @@ -19,6 +21,41 @@ const ( TokenRefreshResp = `{ "token": "header.payload.signature" }` + + // OpenIDConfigResp represents a JSON return for an OpenID configuration. + OpenIDConfigResp = `{ + "issuer": "https://vela.com/_services/token", + "jwks_uri": "https://vela.com/_services/token/.well-known/jwks", + "supported_claims": [ + "sub", + "exp", + "iat", + "iss", + "aud", + "build_number", + "repo", + "token_type", + "actor", + "commands", + "image", + "request" + ], + "id_token_signing_alg_values_supported": [ + "RS256" + ] +}` + + // JWKSResp represents a JSON return for the JWKS. + JWKSResp = `{ + "keys": [ + { + "e": "AQAB", + "kid": "f7ec4ab7-c9a2-440e-bfb3-83b6599479ea", + "kty": "RSA", + "n": "weh9G_J6yZEugOFo6MQ057t_ExafteA_zVRS3CEPWiOgBLLRymh-KS6aCW-kHVuyBsnWNrCcc5cRJ6ISFnQMtkJtbpV_72qbw0zhFLiYomZDh5nb5dqCoiWIVNG8_a_My9jhXAIghs8MLbG-_Tj9jZb3K3n3Ies-Cg1E5SWO3YX8I1_X7ZlgqhEbktoy2RvR_crQA_fi1jRW5Q6PldIJmu4FIeXN_ny_sgg6ZQtTImFderUy1aUxUnpjilU-yv13eJejYQnJ7rExJVsDqq3B_CnYD2ioJC6b7aoEPvCpZ_1VgTTnQt6nedmr2Hih3GHgDNsM-BFr63aG3qZ5v9bVRw" + } + ] +` ) // getTokenRefresh returns mock JSON for a http GET. @@ -100,3 +137,23 @@ func validateOAuthToken(c *gin.Context) { c.JSON(http.StatusOK, "oauth token was created by vela") } + +// openIDConfig returns a mock response for a http GET. +func openIDConfig(c *gin.Context) { + data := []byte(OpenIDConfigResp) + + var body api.OpenIDConfig + _ = json.Unmarshal(data, &body) + + c.JSON(http.StatusOK, body) +} + +// getJWKS returns a mock response for a http GET. +func getJWKS(c *gin.Context) { + data := []byte(JWKSResp) + + var body jwk.RSAPublicKey + _ = json.Unmarshal(data, &body) + + c.JSON(http.StatusOK, body) +} diff --git a/mock/server/build.go b/mock/server/build.go index 37466b910..99721873c 100644 --- a/mock/server/build.go +++ b/mock/server/build.go @@ -202,6 +202,20 @@ const ( "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJidWlsZF9pZCI6MSwicmVwbyI6ImZvby9iYXIiLCJzdWIiOiJPY3RvY2F0IiwiaWF0IjoxNTE2MjM5MDIyfQ.hD7gXpaf9acnLBdOBa4GOEa5KZxdzd0ZvK6fGwaN4bc" }` + // IDTokenResp represents a JSON return for requesting an ID token. + // + //nolint:gosec // not actual credentials + IDTokenResp = `{ + "token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImY3ZWM0YWI3LWM5YTItNDQwZS1iZmIzLTgzYjY1OTk0NzllYSJ9.eyJidWlsZF9udW1iZXIiOjEsImFjdG9yIjoiT2N0b2NhdCIsInJlcG8iOiJPY3RvY2F0L3Rlc3QiLCJ0b2tlbl90eXBlIjoiSUQiLCJpbWFnZSI6ImFscGluZTpsYXRlc3QiLCJjb21tYW5kcyI6dHJ1ZSwicmVxdWVzdCI6IndyaXRlIiwiaXNzIjoiaHR0cHM6Ly92ZWxhLmNvbS9fc2VydmljZXMvdG9rZW4iLCJzdWIiOiJyZXBvOk9jdG9jYXQvdGVzdDpyZWY6cmVmcy9oZWFkcy9tYWluOmV2ZW50OnB1c2giLCJleHAiOjE3MTQ0OTU4MjMsImlhdCI6MTcxNDQ5NTUyMywiYXVkIjpbInRlc3QiLCJleGFtcGxlIl19.nCniV3r0TjJT0JGRtcubhhJnffo_uBUcYvFRlqSOHEOjRQo5cSwNfiePVicfWQYNbLYeJ_7HmxT2C27jCa2MjaYNXFXoVVAP9Cl-LIMqkpiIG85gHlsJaJILT_a8a1F6an0gK1st-iPB6h7casMXR479zdWnVX30WEE7Ed34T-ee6MOxYZ6-VDsVvxVLYe8dYMxvJkBWDI31djqzYfdWWWyYTPqfLCyRaojZeiCzKMt7m5GDRiOLXooYorv3ybizFoupr4md3Kk9MlArgMn7PYNGnmZuzJ01JrIUk6FTVty638yUmEIgiW7sdLzZG9HJ3E3hS6WJleXj--E95wmyyw" + }` + + // IDTokenRequestTokenResp represents a JSON return for requesting an IDTokenRequestToken. + // + //nolint:gosec // not actual credentials + IDTokenRequestTokenResp = `{ + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImY3ZWM0YWI3LWM5YTItNDQwZS1iZmIzLTgzYjY1OTk0NzllYSJ9.eyJidWlsZF9pZCI6MSwiYnVpbGRfbnVtYmVyIjoxLCJhY3RvciI6Ik9jdG9jYXQiLCJyZXBvIjoiT2N0b2NhdC90ZXN0IiwidG9rZW5fdHlwZSI6IklEUmVxdWVzdCIsImltYWdlIjoiYWxwaW5lOmxhdGVzdCIsImNvbW1hbmRzIjp0cnVlLCJyZXF1ZXN0Ijoid3JpdGUiLCJpc3MiOiJodHRwczovL3ZlbGEuY29tL19zZXJ2aWNlcy90b2tlbiIsInN1YiI6InJlcG86T2N0b2NhdC90ZXN0OnJlZjpyZWZzL2hlYWRzL21haW46ZXZlbnQ6cHVzaCIsImV4cCI6MTcxNDQ5NTgyMywiaWF0IjoxNzE0NDk1NTIzfQ.l7ulJ7g5iTWGrR_IOBC2borJj2yixRAMZpsZEeaMvUw" + }` + // BuildExecutableResp represents a JSON return for requesting a build executable. BuildExecutableResp = `{ "id": 1 @@ -419,6 +433,46 @@ func buildToken(c *gin.Context) { c.JSON(http.StatusOK, body) } +// idToken has a param :build returns mock JSON for a http GET. +// +// Pass "0" to :build to test receiving a http 400 response. +func idToken(c *gin.Context) { + b := c.Param("build") + + if strings.EqualFold(b, "0") { + c.AbortWithStatusJSON(http.StatusBadRequest, "") + + return + } + + data := []byte(IDTokenResp) + + var body library.Token + _ = json.Unmarshal(data, &body) + + c.JSON(http.StatusOK, body) +} + +// idTokenRequestToken has a param :build returns mock JSON for a http GET. +// +// Pass "0" to :build to test receiving a http 400 response. +func idTokenRequestToken(c *gin.Context) { + b := c.Param("build") + + if strings.EqualFold(b, "0") { + c.AbortWithStatusJSON(http.StatusBadRequest, "") + + return + } + + data := []byte(IDTokenRequestTokenResp) + + var body library.Token + _ = json.Unmarshal(data, &body) + + c.JSON(http.StatusOK, body) +} + // buildExecutable has a param :build returns mock JSON for a http GET. // // Pass "0" to :build to test receiving a http 500 response. diff --git a/mock/server/server.go b/mock/server/server.go index 9527cbc75..4b249cec3 100644 --- a/mock/server/server.go +++ b/mock/server/server.go @@ -45,6 +45,8 @@ func FakeHandler() http.Handler { e.DELETE("/api/v1/repos/:org/:repo/builds/:build", removeBuild) e.GET("/api/v1/repos/:org/:repo/builds/:build/token", buildToken) e.GET("/api/v1/repos/:org/:repo/builds/:build/executable", buildExecutable) + e.GET("/api/v1/repos/:org/:repo/builds/:build/id_token", idToken) + e.GET("/api/v1/repos/:org/:repo/builds/:build/id_request_token", idTokenRequestToken) // mock endpoints for dashboard calls e.GET("/api/v1/dashboards/:dashboard", getDashboard) @@ -146,6 +148,10 @@ func FakeHandler() http.Handler { e.GET("/validate-token", validateToken) e.GET("/validate-oauth", validateOAuthToken) + // mock endpoints for oidc calls + e.GET("/_services/token/.well-known/openid-configuration", openIDConfig) + e.GET("/_services/token/.well-known/jwks", getJWKS) + // mock endpoint for queue credentials e.GET("/api/v1/queue/info", getQueueCreds) From e974df98428074e8bc1371a9140c240931d5c37d Mon Sep 17 00:00:00 2001 From: ecrupper Date: Wed, 22 May 2024 10:09:13 -0500 Subject: [PATCH 14/34] fixes --- database/integration_test.go | 2 +- database/jwk/get.go | 3 ++- database/jwk/get_test.go | 2 +- database/jwk/jwk_test.go | 25 ------------------------- database/jwk/list.go | 3 ++- database/testutils/api_resources.go | 5 +++-- database/testutils/mock_args.go | 27 +++++++++++++++++++++++++++ 7 files changed, 36 insertions(+), 31 deletions(-) diff --git a/database/integration_test.go b/database/integration_test.go index f478fdd8e..7284d1177 100644 --- a/database/integration_test.go +++ b/database/integration_test.go @@ -910,7 +910,7 @@ func testJWKs(t *testing.T, db Interface, resources *Resources) { t.Errorf("unable to get jwk %s: %v", jkPub.KeyID(), err) } - if !cmp.Equal(jkPub, got) { + if !cmp.Equal(jkPub, got, testutils.JwkOpts) { t.Errorf("GetJWK() is %v, want %v", got, jkPub) } } diff --git a/database/jwk/get.go b/database/jwk/get.go index 49494a222..187753df8 100644 --- a/database/jwk/get.go +++ b/database/jwk/get.go @@ -5,9 +5,10 @@ package jwk import ( "context" + "github.com/lestrrat-go/jwx/jwk" + "github.com/go-vela/server/constants" "github.com/go-vela/server/database/types" - "github.com/lestrrat-go/jwx/jwk" ) // GetActiveJWK gets a JWK by UUID (kid) from the database if active. diff --git a/database/jwk/get_test.go b/database/jwk/get_test.go index ab7568573..819cc1ee9 100644 --- a/database/jwk/get_test.go +++ b/database/jwk/get_test.go @@ -79,7 +79,7 @@ func TestJWK_Engine_GetJWK(t *testing.T) { t.Errorf("GetActiveJWK for %s returned err: %v", test.name, err) } - if diff := cmp.Diff(test.want, got, jwkOpts); diff != "" { + if diff := cmp.Diff(test.want, got, testutils.JwkOpts); diff != "" { t.Errorf("GetActiveJWK mismatch (-want +got):\n%s", diff) } }) diff --git a/database/jwk/jwk_test.go b/database/jwk/jwk_test.go index d0a958218..95ff9a463 100644 --- a/database/jwk/jwk_test.go +++ b/database/jwk/jwk_test.go @@ -7,8 +7,6 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" - "github.com/google/go-cmp/cmp" - "github.com/lestrrat-go/jwx/jwk" "github.com/sirupsen/logrus" "gorm.io/driver/postgres" "gorm.io/driver/sqlite" @@ -163,26 +161,3 @@ func testSqlite(t *testing.T) *engine { return _engine } - -var jwkOpts = cmp.Options{ - cmp.FilterValues(func(x, y interface{}) bool { - _, xOk := x.(jwk.RSAPublicKey) - _, yOk := y.(jwk.RSAPublicKey) - return xOk && yOk - }, cmp.Comparer(func(x, y interface{}) bool { - xJWK := x.(jwk.RSAPublicKey) - yJWK := y.(jwk.RSAPublicKey) - - var rawXKey, rawYKey interface{} - - if err := xJWK.Raw(&rawXKey); err != nil { - return false - } - - if err := yJWK.Raw(&rawYKey); err != nil { - return false - } - - return reflect.DeepEqual(rawXKey, rawYKey) && xJWK.KeyID() == yJWK.KeyID() - })), -} diff --git a/database/jwk/list.go b/database/jwk/list.go index 890d04376..fde45e660 100644 --- a/database/jwk/list.go +++ b/database/jwk/list.go @@ -5,9 +5,10 @@ package jwk import ( "context" + "github.com/lestrrat-go/jwx/jwk" + "github.com/go-vela/server/constants" "github.com/go-vela/server/database/types" - "github.com/lestrrat-go/jwx/jwk" ) // ListJWKs gets a list of all configured JWKs from the database. diff --git a/database/testutils/api_resources.go b/database/testutils/api_resources.go index 2db83998b..9e97491fc 100644 --- a/database/testutils/api_resources.go +++ b/database/testutils/api_resources.go @@ -6,12 +6,13 @@ import ( "crypto/rand" "crypto/rsa" + "github.com/google/uuid" + "github.com/lestrrat-go/jwx/jwk" + api "github.com/go-vela/server/api/types" "github.com/go-vela/server/api/types/actions" "github.com/go-vela/types/library" "github.com/go-vela/types/raw" - "github.com/google/uuid" - "github.com/lestrrat-go/jwx/jwk" ) // API TEST RESOURCES diff --git a/database/testutils/mock_args.go b/database/testutils/mock_args.go index c2d8c562f..8cff1b813 100644 --- a/database/testutils/mock_args.go +++ b/database/testutils/mock_args.go @@ -4,7 +4,11 @@ package testutils import ( "database/sql/driver" + "reflect" "time" + + "github.com/google/go-cmp/cmp" + "github.com/lestrrat-go/jwx/jwk" ) // This will be used with the github.com/DATA-DOG/go-sqlmock library to compare values @@ -33,3 +37,26 @@ func (t NowTimestamp) Match(v driver.Value) bool { return now-ts < 10 } + +var JwkOpts = cmp.Options{ + cmp.FilterValues(func(x, y interface{}) bool { + _, xOk := x.(jwk.RSAPublicKey) + _, yOk := y.(jwk.RSAPublicKey) + return xOk && yOk + }, cmp.Comparer(func(x, y interface{}) bool { + xJWK := x.(jwk.RSAPublicKey) + yJWK := y.(jwk.RSAPublicKey) + + var rawXKey, rawYKey interface{} + + if err := xJWK.Raw(&rawXKey); err != nil { + return false + } + + if err := yJWK.Raw(&rawYKey); err != nil { + return false + } + + return reflect.DeepEqual(rawXKey, rawYKey) && xJWK.KeyID() == yJWK.KeyID() + })), +} From 34d115ffa95f88f95953bcafa4ff17c32f026151 Mon Sep 17 00:00:00 2001 From: ecrupper Date: Wed, 22 May 2024 10:16:15 -0500 Subject: [PATCH 15/34] more fixes --- database/integration_test.go | 6 +++--- database/jwk/get_test.go | 2 +- database/testutils/mock_args.go | 2 +- go.mod | 1 - 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/database/integration_test.go b/database/integration_test.go index 7284d1177..713eee0fc 100644 --- a/database/integration_test.go +++ b/database/integration_test.go @@ -894,8 +894,8 @@ func testJWKs(t *testing.T, db Interface, resources *Resources) { t.Errorf("unable to list jwks: %v", err) } - if diff := cmp.Diff(resources.JWKs, list); diff != "" { - t.Errorf("ListJWKs() mismatch (-want +got):\n%s", diff) + if !reflect.DeepEqual(resources.JWKs, list) { + t.Errorf("ListJWKs() mismatch, want %v, got %v", resources.JWKs, list) } methods["ListJWKs"] = true @@ -910,7 +910,7 @@ func testJWKs(t *testing.T, db Interface, resources *Resources) { t.Errorf("unable to get jwk %s: %v", jkPub.KeyID(), err) } - if !cmp.Equal(jkPub, got, testutils.JwkOpts) { + if !cmp.Equal(jkPub, got, testutils.JwkKeyOpts) { t.Errorf("GetJWK() is %v, want %v", got, jkPub) } } diff --git a/database/jwk/get_test.go b/database/jwk/get_test.go index 819cc1ee9..2cb131bae 100644 --- a/database/jwk/get_test.go +++ b/database/jwk/get_test.go @@ -79,7 +79,7 @@ func TestJWK_Engine_GetJWK(t *testing.T) { t.Errorf("GetActiveJWK for %s returned err: %v", test.name, err) } - if diff := cmp.Diff(test.want, got, testutils.JwkOpts); diff != "" { + if diff := cmp.Diff(test.want, got, testutils.JwkKeyOpts); diff != "" { t.Errorf("GetActiveJWK mismatch (-want +got):\n%s", diff) } }) diff --git a/database/testutils/mock_args.go b/database/testutils/mock_args.go index 8cff1b813..e6d8a9324 100644 --- a/database/testutils/mock_args.go +++ b/database/testutils/mock_args.go @@ -38,7 +38,7 @@ func (t NowTimestamp) Match(v driver.Value) bool { return now-ts < 10 } -var JwkOpts = cmp.Options{ +var JwkKeyOpts = cmp.Options{ cmp.FilterValues(func(x, y interface{}) bool { _, xOk := x.(jwk.RSAPublicKey) _, yOk := y.(jwk.RSAPublicKey) diff --git a/go.mod b/go.mod index fd440fca1..f9837b3eb 100644 --- a/go.mod +++ b/go.mod @@ -53,7 +53,6 @@ require ( github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/iter v1.0.2 // indirect github.com/lestrrat-go/option v1.0.1 // indirect - github.com/stretchr/testify v1.9.0 // indirect ) require ( From e5b9af2e42cf17be94b702f082eed78417e3c4e2 Mon Sep 17 00:00:00 2001 From: ecrupper Date: Wed, 22 May 2024 10:58:58 -0500 Subject: [PATCH 16/34] use wrapper for swagger jwk set --- api/jwks.go | 2 +- api/types/oidc.go | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/api/jwks.go b/api/jwks.go index 3b002883c..0b134f3d1 100644 --- a/api/jwks.go +++ b/api/jwks.go @@ -26,7 +26,7 @@ import ( // '200': // description: Successfully retrieved the Vela JWKS // schema: -// "$ref": "#/definitions/JWKS" +// "$ref": "#/definitions/JWKSet" // GetJWKS represents the API handler for requests to public keys in the Vela OpenID service. func GetJWKS(c *gin.Context) { diff --git a/api/types/oidc.go b/api/types/oidc.go index cd0168563..8fc64c052 100644 --- a/api/types/oidc.go +++ b/api/types/oidc.go @@ -2,7 +2,10 @@ package types -import "github.com/golang-jwt/jwt/v5" +import ( + "github.com/golang-jwt/jwt/v5" + "github.com/lestrrat-go/jwx/jwk" +) // OpenIDConfig is a struct that represents the OpenID Connect configuration. // @@ -26,3 +29,10 @@ type OpenIDClaims struct { Commands bool `json:"commands,omitempty"` jwt.RegisteredClaims } + +// JWKSet is a wrapper of lestrrat-go/jwx/jwk.Set for API Swagger gen. +// +// swagger:model JWKSet +type JWKSet struct { + jwk.Set +} From 9d6aeb12288f015a5b7bcb14ec4443844fff8b07 Mon Sep 17 00:00:00 2001 From: ecrupper Date: Thu, 23 May 2024 15:09:14 -0500 Subject: [PATCH 17/34] address feedback --- api/admin/rotate_keys.go | 2 +- api/build/id_request_token.go | 30 +++++++++++++++++++----------- api/build/id_token.go | 31 +++++++++++++++++++++---------- api/jwks.go | 4 ++++ api/oi_config.go | 3 +++ api/types/oidc.go | 3 +++ cmd/vela-server/main.go | 4 ++-- constants/table.go | 1 + database/jwk/jwk.go | 8 ++++---- database/jwk/list.go | 2 +- database/jwk/rotate.go | 2 +- internal/token/mint.go | 9 ++++++--- 12 files changed, 66 insertions(+), 33 deletions(-) diff --git a/api/admin/rotate_keys.go b/api/admin/rotate_keys.go index 04e2d3795..56681a60c 100644 --- a/api/admin/rotate_keys.go +++ b/api/admin/rotate_keys.go @@ -27,7 +27,7 @@ import ( // '200': // description: Successfully rotated OIDC provider keys // schema: -// type: string +// type: string // '500': // description: Error unable to rotate OIDC provider keys // schema: diff --git a/api/build/id_request_token.go b/api/build/id_request_token.go index 7d8586e89..e3843030f 100644 --- a/api/build/id_request_token.go +++ b/api/build/id_request_token.go @@ -15,8 +15,6 @@ import ( "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/router/middleware/org" - "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/util" "github.com/go-vela/types/library" ) @@ -52,6 +50,10 @@ import ( // 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: @@ -63,6 +65,14 @@ import ( // 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: @@ -72,8 +82,6 @@ import ( func GetIDRequestToken(c *gin.Context) { // capture middleware values b := build.Retrieve(c) - o := org.Retrieve(c) - r := repo.Retrieve(c) cl := claims.Retrieve(c) // update engine logger with API metadata @@ -81,10 +89,10 @@ func GetIDRequestToken(c *gin.Context) { // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields logrus.WithFields(logrus.Fields{ "build": b.GetNumber(), - "org": o, - "repo": r.GetName(), + "org": b.GetRepo().GetOrg(), + "repo": b.GetRepo().GetName(), "user": cl.Subject, - }).Infof("generating ID request token for build %s/%d", r.GetFullName(), b.GetNumber()) + }).Infof("generating ID request token for build %s/%d", b.GetRepo().GetFullName(), b.GetNumber()) image := c.Query("image") request := c.Query("request") @@ -93,12 +101,12 @@ func GetIDRequestToken(c *gin.Context) { // retrieve token manager from context tm := c.MustGet("token-manager").(*token.Manager) - exp := (time.Duration(r.GetTimeout()) * time.Minute) + tm.BuildTokenBufferDuration + exp := (time.Duration(b.GetRepo().GetTimeout()) * time.Minute) + tm.BuildTokenBufferDuration // set mint token options idmto := &token.MintTokenOpts{ Build: b, - Repo: r.GetFullName(), + Repo: b.GetRepo().GetFullName(), TokenType: constants.IDRequestTokenType, TokenDuration: exp, Image: image, @@ -107,7 +115,7 @@ func GetIDRequestToken(c *gin.Context) { } // mint token - bt, err := tm.MintToken(idmto) + 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) @@ -115,5 +123,5 @@ func GetIDRequestToken(c *gin.Context) { return } - c.JSON(http.StatusOK, library.Token{Token: &bt}) + c.JSON(http.StatusOK, library.Token{Token: &idrt}) } diff --git a/api/build/id_token.go b/api/build/id_token.go index 8a6a412b7..253caef3a 100644 --- a/api/build/id_token.go +++ b/api/build/id_token.go @@ -14,8 +14,6 @@ import ( "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/router/middleware/org" - "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/util" "github.com/go-vela/types/library" ) @@ -43,6 +41,13 @@ import ( // 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: @@ -54,6 +59,14 @@ import ( // 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: @@ -63,8 +76,6 @@ import ( func GetIDToken(c *gin.Context) { // capture middleware values b := build.Retrieve(c) - o := org.Retrieve(c) - r := repo.Retrieve(c) cl := claims.Retrieve(c) // update engine logger with API metadata @@ -72,10 +83,10 @@ func GetIDToken(c *gin.Context) { // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields logrus.WithFields(logrus.Fields{ "build": b.GetNumber(), - "org": o, - "repo": r.GetName(), + "org": b.GetRepo().GetOrg(), + "repo": b.GetRepo().GetName(), "user": cl.Subject, - }).Infof("generating ID token for build %s/%d", r.GetFullName(), b.GetNumber()) + }).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) @@ -83,7 +94,7 @@ func GetIDToken(c *gin.Context) { // set mint token options idmto := &token.MintTokenOpts{ Build: b, - Repo: r.GetFullName(), + Repo: b.GetRepo().GetFullName(), TokenType: constants.IDTokenType, TokenDuration: tm.IDTokenDuration, Image: cl.Image, @@ -97,7 +108,7 @@ func GetIDToken(c *gin.Context) { } // mint token - bt, err := tm.MintIDToken(idmto, database.FromContext(c)) + idt, err := tm.MintIDToken(idmto, database.FromContext(c)) if err != nil { retErr := fmt.Errorf("unable to generate build token: %w", err) util.HandleError(c, http.StatusInternalServerError, retErr) @@ -105,5 +116,5 @@ func GetIDToken(c *gin.Context) { return } - c.JSON(http.StatusOK, library.Token{Token: &bt}) + c.JSON(http.StatusOK, library.Token{Token: &idt}) } diff --git a/api/jwks.go b/api/jwks.go index 0b134f3d1..3d124f77d 100644 --- a/api/jwks.go +++ b/api/jwks.go @@ -27,6 +27,10 @@ import ( // description: Successfully retrieved the Vela JWKS // schema: // "$ref": "#/definitions/JWKSet" +// '500': +// description: Unable to get the Vela JWKS +// schema: +// "$ref": "#/definitions/Error" // GetJWKS represents the API handler for requests to public keys in the Vela OpenID service. func GetJWKS(c *gin.Context) { diff --git a/api/oi_config.go b/api/oi_config.go index d17174338..72a9686f7 100644 --- a/api/oi_config.go +++ b/api/oi_config.go @@ -49,6 +49,9 @@ func GetOpenIDConfig(c *gin.Context) { "commands", "image", "request", + "event", + "sha", + "ref", }, Algorithms: []string{ jwt.SigningMethodRS256.Name, diff --git a/api/types/oidc.go b/api/types/oidc.go index 8fc64c052..63e8c0573 100644 --- a/api/types/oidc.go +++ b/api/types/oidc.go @@ -27,6 +27,9 @@ type OpenIDClaims struct { Image string `json:"image,omitempty"` Request string `json:"request,omitempty"` Commands bool `json:"commands,omitempty"` + Event string `json:"event,omitempty"` + Ref string `json:"ref,omitempty"` + SHA string `json:"sha,omitempty"` jwt.RegisteredClaims } diff --git a/cmd/vela-server/main.go b/cmd/vela-server/main.go index 8b52d8272..7dd589f07 100644 --- a/cmd/vela-server/main.go +++ b/cmd/vela-server/main.go @@ -182,9 +182,9 @@ func main() { Value: 1 * time.Minute, }, &cli.DurationFlag{ - EnvVars: []string{"VELA_ID_TOKEN_DURATION", "ID_TOKEN_DURATION"}, + EnvVars: []string{"VELA_OPEN_ID_TOKEN_DURATION", "OPEN_ID_TOKEN_DURATION"}, Name: "id-token-duration", - Usage: "sets the duration of the id token", + Usage: "sets the duration of an OpenID token requested during a build (should be short)", Value: 5 * time.Minute, }, // Compiler Flags diff --git a/constants/table.go b/constants/table.go index 4ad4fb03c..736335482 100644 --- a/constants/table.go +++ b/constants/table.go @@ -5,6 +5,7 @@ package constants const ( // TableDashboard defines the table type for the database dashboards table. TableDashboard = "dashboards" + // TableJWK defines the table type for the database jwks table. TableJWK = "jwks" ) diff --git a/database/jwk/jwk.go b/database/jwk/jwk.go index 661396f66..6b3acc33f 100644 --- a/database/jwk/jwk.go +++ b/database/jwk/jwk.go @@ -13,15 +13,15 @@ import ( ) type ( - // config represents the settings required to create the engine that implements the KeySetService interface. + // config represents the settings required to create the engine that implements the JWKService interface. config struct { - // specifies to skip creating tables and indexes for the KeySet engine + // specifies to skip creating tables and indexes for the JWK engine SkipCreation bool // specifies the driver for proper popping query Driver string } - // engine represents the key set functionality that implements the KeySetService interface. + // engine represents the key set functionality that implements the JWKService interface. engine struct { // engine configuration settings used in key set functions config *config @@ -44,7 +44,7 @@ type ( // //nolint:revive // ignore returning unexported engine func New(opts ...EngineOpt) (*engine, error) { - // create new KeySet engine + // create new JWK engine e := new(engine) // create new fields diff --git a/database/jwk/list.go b/database/jwk/list.go index fde45e660..7c83b48b1 100644 --- a/database/jwk/list.go +++ b/database/jwk/list.go @@ -13,7 +13,7 @@ import ( // ListJWKs gets a list of all configured JWKs from the database. func (e *engine) ListJWKs(_ context.Context) (jwk.Set, error) { - e.logger.Trace("listing all keysets from the database") + e.logger.Trace("listing all jwks from the database") k := new([]types.JWK) keySet := jwk.NewSet() diff --git a/database/jwk/rotate.go b/database/jwk/rotate.go index 0b2520cd7..82ff1c422 100644 --- a/database/jwk/rotate.go +++ b/database/jwk/rotate.go @@ -12,7 +12,7 @@ import ( // RotateKeys removes all inactive keys and sets active keys to inactive. func (e *engine) RotateKeys(_ context.Context) error { - e.logger.Trace("rotating keysets in the database") + e.logger.Trace("rotating jwks in the database") k := types.JWK{} diff --git a/internal/token/mint.go b/internal/token/mint.go index 358a9ba07..3768629e7 100644 --- a/internal/token/mint.go +++ b/internal/token/mint.go @@ -150,6 +150,9 @@ func (tm *Manager) MintIDToken(mto *MintTokenOpts, db database.Interface) (strin claims.BuildNumber = mto.Build.GetNumber() claims.Actor = mto.Build.GetSender() claims.Repo = mto.Repo + claims.Event = fmt.Sprintf("%s:%s", mto.Build.GetEvent(), mto.Build.GetEventAction()) + claims.SHA = mto.Build.GetCommit() + claims.Ref = mto.Build.GetRef() claims.Subject = fmt.Sprintf("repo:%s:ref:%s:event:%s", mto.Repo, mto.Build.GetRef(), mto.Build.GetEvent()) claims.Audience = mto.Audience claims.TokenType = mto.TokenType @@ -168,13 +171,13 @@ func (tm *Manager) MintIDToken(mto *MintTokenOpts, db database.Interface) (strin _, err := db.GetActiveJWK(context.TODO(), tm.RSAKeySet.KID) if err != nil { if !errors.Is(err, gorm.ErrRecordNotFound) { - return "", fmt.Errorf("unable to get active key set: %w", err) + return "", fmt.Errorf("unable to get active public key: %w", err) } - // generate a new RSA key set if previous key is inactive (rotated) + // generate a new RSA key pair if previous key is inactive (rotated) err = tm.GenerateRSA(db) if err != nil { - return "", fmt.Errorf("unable to generate RSA key set: %w", err) + return "", fmt.Errorf("unable to generate RSA key pair: %w", err) } } From ce82c32433125b8c5c1edc25ef44f12934e1f37d Mon Sep 17 00:00:00 2001 From: davidvader Date: Wed, 29 May 2024 11:21:55 -0500 Subject: [PATCH 18/34] enhance: add build_id and actor_id to claims --- api/build/id_token.go | 11 ++++++----- api/types/oidc.go | 2 ++ internal/token/mint.go | 18 ++++++++++++++++-- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/api/build/id_token.go b/api/build/id_token.go index 253caef3a..a54040df1 100644 --- a/api/build/id_token.go +++ b/api/build/id_token.go @@ -77,15 +77,16 @@ 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(), - "user": cl.Subject, + "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 @@ -108,7 +109,7 @@ func GetIDToken(c *gin.Context) { } // mint token - idt, err := tm.MintIDToken(idmto, database.FromContext(c)) + idt, err := tm.MintIDToken(ctx, idmto, database.FromContext(c)) if err != nil { retErr := fmt.Errorf("unable to generate build token: %w", err) util.HandleError(c, http.StatusInternalServerError, retErr) diff --git a/api/types/oidc.go b/api/types/oidc.go index 63e8c0573..fa5f45cbb 100644 --- a/api/types/oidc.go +++ b/api/types/oidc.go @@ -21,7 +21,9 @@ type OpenIDConfig struct { // includes information relevant to OIDC services. type OpenIDClaims struct { BuildNumber int `json:"build_number,omitempty"` + BuildID int64 `json:"build_id,omitempty"` Actor string `json:"actor,omitempty"` + ActorID string `json:"actor_id,omitempty"` Repo string `json:"repo,omitempty"` TokenType string `json:"token_type,omitempty"` Image string `json:"image,omitempty"` diff --git a/internal/token/mint.go b/internal/token/mint.go index 3768629e7..d7dfb676c 100644 --- a/internal/token/mint.go +++ b/internal/token/mint.go @@ -6,6 +6,7 @@ import ( "context" "errors" "fmt" + "strconv" "time" "github.com/golang-jwt/jwt/v5" @@ -129,7 +130,7 @@ func (tm *Manager) MintToken(mto *MintTokenOpts) (string, error) { } // MintIDToken mints a Vela JWT ID Token for a build. -func (tm *Manager) MintIDToken(mto *MintTokenOpts, db database.Interface) (string, error) { +func (tm *Manager) MintIDToken(ctx context.Context, mto *MintTokenOpts, db database.Interface) (string, error) { // initialize claims struct var claims = new(api.OpenIDClaims) @@ -146,9 +147,22 @@ func (tm *Manager) MintIDToken(mto *MintTokenOpts, db database.Interface) (strin return "", errors.New("missing build id for ID token") } + if len(mto.Build.GetSender()) == 0 { + return "", errors.New("missing build sender for ID token") + } + // set claims based on input claims.BuildNumber = mto.Build.GetNumber() + claims.BuildID = mto.Build.GetID() claims.Actor = mto.Build.GetSender() + + // retrieve the user id for the actor + u, err := db.GetUserForName(ctx, mto.Build.GetSender()) + if err != nil { + return "", errors.New("unable to retrieve build sender user ID for ID token") + } + + claims.ActorID = strconv.Itoa(int(u.GetID())) claims.Repo = mto.Repo claims.Event = fmt.Sprintf("%s:%s", mto.Build.GetEvent(), mto.Build.GetEventAction()) claims.SHA = mto.Build.GetCommit() @@ -168,7 +182,7 @@ func (tm *Manager) MintIDToken(mto *MintTokenOpts, db database.Interface) (strin tk := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) // verify key is active in the database before signing - _, err := db.GetActiveJWK(context.TODO(), tm.RSAKeySet.KID) + _, err = db.GetActiveJWK(context.TODO(), tm.RSAKeySet.KID) if err != nil { if !errors.Is(err, gorm.ErrRecordNotFound) { return "", fmt.Errorf("unable to get active public key: %w", err) From a82dd614e28027e7abdd202518929ea9759abdbb Mon Sep 17 00:00:00 2001 From: davidvader Date: Thu, 30 May 2024 08:51:13 -0500 Subject: [PATCH 19/34] enhance: complete adding build_id and actor_id to claims --- api/oi_config.go | 3 ++- internal/token/mint.go | 9 ++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/api/oi_config.go b/api/oi_config.go index 72a9686f7..f174a4b32 100644 --- a/api/oi_config.go +++ b/api/oi_config.go @@ -32,7 +32,6 @@ import ( // GetOpenIDConfig represents the API handler for requests for configurations in the Vela OpenID service. func GetOpenIDConfig(c *gin.Context) { m := c.MustGet("metadata").(*internal.Metadata) - config := types.OpenIDConfig{ Issuer: fmt.Sprintf("%s/_services/token", m.Vela.Address), JWKSAddress: fmt.Sprintf("%s/%s", m.Vela.Address, "_services/token/.well-known/jwks"), @@ -43,9 +42,11 @@ func GetOpenIDConfig(c *gin.Context) { "iss", "aud", "build_number", + "build_id", "repo", "token_type", "actor", + "actor_id", "commands", "image", "request", diff --git a/internal/token/mint.go b/internal/token/mint.go index d7dfb676c..ef2cbe506 100644 --- a/internal/token/mint.go +++ b/internal/token/mint.go @@ -151,18 +151,17 @@ func (tm *Manager) MintIDToken(ctx context.Context, mto *MintTokenOpts, db datab return "", errors.New("missing build sender for ID token") } - // set claims based on input - claims.BuildNumber = mto.Build.GetNumber() - claims.BuildID = mto.Build.GetID() - claims.Actor = mto.Build.GetSender() - // retrieve the user id for the actor u, err := db.GetUserForName(ctx, mto.Build.GetSender()) if err != nil { return "", errors.New("unable to retrieve build sender user ID for ID token") } + // set claims based on input + claims.Actor = mto.Build.GetSender() claims.ActorID = strconv.Itoa(int(u.GetID())) + claims.BuildNumber = mto.Build.GetNumber() + claims.BuildID = mto.Build.GetID() claims.Repo = mto.Repo claims.Event = fmt.Sprintf("%s:%s", mto.Build.GetEvent(), mto.Build.GetEventAction()) claims.SHA = mto.Build.GetCommit() From 5e889415e7ec179dfa818c91be1f12903dd021ad Mon Sep 17 00:00:00 2001 From: davidvader Date: Thu, 30 May 2024 08:51:31 -0500 Subject: [PATCH 20/34] enhance: complete adding build_id and actor_id to claims --- mock/server/authentication.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mock/server/authentication.go b/mock/server/authentication.go index f14ea54d2..dd2405a24 100644 --- a/mock/server/authentication.go +++ b/mock/server/authentication.go @@ -33,9 +33,11 @@ const ( "iss", "aud", "build_number", + "build_id", "repo", "token_type", "actor", + "actor_id", "commands", "image", "request" From 0699f606d8f42e397cef2267c8ed98b0587708c5 Mon Sep 17 00:00:00 2001 From: davidvader Date: Thu, 30 May 2024 08:58:00 -0500 Subject: [PATCH 21/34] fix: apply context to GenerateRSA --- cmd/vela-server/token.go | 2 +- internal/token/generate_rsa.go | 2 +- internal/token/mint.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/vela-server/token.go b/cmd/vela-server/token.go index 9853c6c2c..525ce1518 100644 --- a/cmd/vela-server/token.go +++ b/cmd/vela-server/token.go @@ -28,7 +28,7 @@ func setupTokenManager(c *cli.Context, db database.Interface) (*token.Manager, e } // generate a new RSA key pair - err := tm.GenerateRSA(db) + err := tm.GenerateRSA(c.Context, db) if err != nil { return nil, err } diff --git a/internal/token/generate_rsa.go b/internal/token/generate_rsa.go index a72410460..350ab8ea9 100644 --- a/internal/token/generate_rsa.go +++ b/internal/token/generate_rsa.go @@ -14,7 +14,7 @@ import ( ) // GenerateRSA creates an RSA key pair and sets it in the token manager and saves the JWK in the database. -func (tm *Manager) GenerateRSA(db database.Interface) error { +func (tm *Manager) GenerateRSA(ctx context.Context, db database.Interface) error { // generate key pair privateRSAKey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { diff --git a/internal/token/mint.go b/internal/token/mint.go index ef2cbe506..ee98724c7 100644 --- a/internal/token/mint.go +++ b/internal/token/mint.go @@ -181,14 +181,14 @@ func (tm *Manager) MintIDToken(ctx context.Context, mto *MintTokenOpts, db datab tk := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) // verify key is active in the database before signing - _, err = db.GetActiveJWK(context.TODO(), tm.RSAKeySet.KID) + _, err = db.GetActiveJWK(ctx, tm.RSAKeySet.KID) if err != nil { if !errors.Is(err, gorm.ErrRecordNotFound) { return "", fmt.Errorf("unable to get active public key: %w", err) } // generate a new RSA key pair if previous key is inactive (rotated) - err = tm.GenerateRSA(db) + err = tm.GenerateRSA(ctx, db) if err != nil { return "", fmt.Errorf("unable to generate RSA key pair: %w", err) } From 169b10346842552c2f1fe6a511f61bf3453a893a Mon Sep 17 00:00:00 2001 From: davidvader Date: Thu, 30 May 2024 09:00:48 -0500 Subject: [PATCH 22/34] fix: add err check to ParseBool --- api/build/id_request_token.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/api/build/id_request_token.go b/api/build/id_request_token.go index e3843030f..c5dc7225e 100644 --- a/api/build/id_request_token.go +++ b/api/build/id_request_token.go @@ -96,7 +96,14 @@ func GetIDRequestToken(c *gin.Context) { image := c.Query("image") request := c.Query("request") - commands, _ := strconv.ParseBool(c.Query("commands")) + commands, err := strconv.ParseBool(c.Query("commands")) + if err != nil { + retErr := fmt.Errorf("unable to parse 'commands' query %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) From 3f46a1bde28d6a6de766780cf6624b7a878dfb8a Mon Sep 17 00:00:00 2001 From: davidvader Date: Thu, 30 May 2024 09:09:54 -0500 Subject: [PATCH 23/34] enhance: audience validation --- api/build/id_token.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/api/build/id_token.go b/api/build/id_token.go index a54040df1..2fbe00088 100644 --- a/api/build/id_token.go +++ b/api/build/id_token.go @@ -105,7 +105,13 @@ func GetIDToken(c *gin.Context) { // if audience is provided, include that in claims if len(c.QueryArray("audience")) > 0 { - idmto.Audience = c.QueryArray("audience") + audience := []string{} + for _, a := range c.QueryArray("audience") { + if len(a) > 0 { + audience = append(audience, util.Sanitize(a)) + } + } + idmto.Audience = audience } // mint token From 8eed52632113090a6497764cb0dfb7d8b561185b Mon Sep 17 00:00:00 2001 From: davidvader Date: Thu, 30 May 2024 09:12:11 -0500 Subject: [PATCH 24/34] enhance: better audience validation --- api/build/id_token.go | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/api/build/id_token.go b/api/build/id_token.go index 2fbe00088..a2b7fdab7 100644 --- a/api/build/id_token.go +++ b/api/build/id_token.go @@ -104,20 +104,31 @@ func GetIDToken(c *gin.Context) { } // if audience is provided, include that in claims + audience := []string{} + if len(c.QueryArray("audience")) > 0 { - audience := []string{} for _, a := range c.QueryArray("audience") { if len(a) > 0 { audience = append(audience, util.Sanitize(a)) } } - idmto.Audience = audience } + 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 build token: %w", err) + retErr := fmt.Errorf("unable to generate ID token: %w", err) + util.HandleError(c, http.StatusInternalServerError, retErr) return From ae353bf35648a35206409f45db4beaa18c4608e3 Mon Sep 17 00:00:00 2001 From: davidvader Date: Thu, 30 May 2024 09:20:16 -0500 Subject: [PATCH 25/34] enhance: add query parameter input validation --- api/build/id_request_token.go | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/api/build/id_request_token.go b/api/build/id_request_token.go index c5dc7225e..26b43fee4 100644 --- a/api/build/id_request_token.go +++ b/api/build/id_request_token.go @@ -3,6 +3,7 @@ package build import ( + "errors" "fmt" "net/http" "strconv" @@ -94,11 +95,27 @@ func GetIDRequestToken(c *gin.Context) { "user": cl.Subject, }).Infof("generating ID request token for build %s/%d", b.GetRepo().GetFullName(), b.GetNumber()) - image := c.Query("image") - request := c.Query("request") + image := util.Sanitize(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 := util.Sanitize(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 %s: %w", c.Query("commands"), err) + retErr := fmt.Errorf("unable to parse 'commands' query parameter as boolean %s: %w", c.Query("commands"), err) util.HandleError(c, http.StatusBadRequest, retErr) @@ -125,6 +142,7 @@ func GetIDRequestToken(c *gin.Context) { 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 From 36983c65c8e402a0c0102119b1031385e243a0fd Mon Sep 17 00:00:00 2001 From: davidvader Date: Thu, 30 May 2024 09:25:19 -0500 Subject: [PATCH 26/34] tweak: order of operations, move sanitize lower --- api/build/id_request_token.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/build/id_request_token.go b/api/build/id_request_token.go index 26b43fee4..f5e3c9f93 100644 --- a/api/build/id_request_token.go +++ b/api/build/id_request_token.go @@ -95,7 +95,7 @@ func GetIDRequestToken(c *gin.Context) { "user": cl.Subject, }).Infof("generating ID request token for build %s/%d", b.GetRepo().GetFullName(), b.GetNumber()) - image := util.Sanitize(c.Query("image")) + image := c.Query("image") if len(image) == 0 { retErr := errors.New("no step 'image' provided in query parameters") @@ -104,7 +104,7 @@ func GetIDRequestToken(c *gin.Context) { return } - request := util.Sanitize(c.Query("request")) + request := c.Query("request") if len(request) == 0 { retErr := errors.New("no 'request' provided in query parameters") @@ -133,8 +133,8 @@ func GetIDRequestToken(c *gin.Context) { Repo: b.GetRepo().GetFullName(), TokenType: constants.IDRequestTokenType, TokenDuration: exp, - Image: image, - Request: request, + Image: util.Sanitize(image), + Request: util.Sanitize(request), Commands: commands, } From 32861c68d478c52c5c428fec7ff545a343781105 Mon Sep 17 00:00:00 2001 From: davidvader Date: Mon, 3 Jun 2024 15:17:31 -0500 Subject: [PATCH 27/34] enhance: add scm user id to build obj --- api/build/restart.go | 8 +- api/types/build.go | 84 ++-- api/types/build_test.go | 214 ++++----- cmd/vela-server/schedule.go | 4 + compiler/native/environment_test.go | 625 +++++++++++++------------- database/build/create_test.go | 6 +- database/build/list_dashboard_test.go | 6 +- database/build/table.go | 2 + database/build/update_test.go | 6 +- database/integration_test.go | 2 + database/testutils/api_resources.go | 1 + database/types/build.go | 9 + database/types/build_test.go | 3 + mock/server/build.go | 1 + router/middleware/build/build_test.go | 1 + scm/github/webhook.go | 15 +- scm/github/webhook_test.go | 19 +- 17 files changed, 552 insertions(+), 454 deletions(-) diff --git a/api/build/restart.go b/api/build/restart.go index 2569a4915..5d42a9737 100644 --- a/api/build/restart.go +++ b/api/build/restart.go @@ -112,8 +112,14 @@ func RestartBuild(c *gin.Context) { return } - // set sender to the user who initiated the restart and parent to the previous build + // set sender to the user who initiated the restart and b.SetSender(cl.Subject) + // todo: sender_scm_id: + // vela username is the claims subject + // - (a) auth with repo token and convert username to scm id + // - (b) attach scm id to claims + + // parent to the previous build b.SetParent(b.GetNumber()) logger.Debugf("Generating queue items for build %s", entry) diff --git a/api/types/build.go b/api/types/build.go index 231e8b80b..e44c7e21f 100644 --- a/api/types/build.go +++ b/api/types/build.go @@ -38,6 +38,7 @@ type Build struct { Message *string `json:"message,omitempty"` Commit *string `json:"commit,omitempty"` Sender *string `json:"sender,omitempty"` + SenderSCMID *string `json:"sender_scm_id,omitempty"` Author *string `json:"author,omitempty"` Email *string `json:"email,omitempty"` Link *string `json:"link,omitempty"` @@ -86,33 +87,34 @@ func (b *Build) Duration() string { // provided from the fields of the Build type. func (b *Build) Environment(workspace, channel string) map[string]string { envs := map[string]string{ - "VELA_BUILD_APPROVED_AT": ToString(b.GetApprovedAt()), - "VELA_BUILD_APPROVED_BY": ToString(b.GetApprovedBy()), - "VELA_BUILD_AUTHOR": ToString(b.GetAuthor()), - "VELA_BUILD_AUTHOR_EMAIL": ToString(b.GetEmail()), - "VELA_BUILD_BASE_REF": ToString(b.GetBaseRef()), - "VELA_BUILD_BRANCH": ToString(b.GetBranch()), - "VELA_BUILD_CHANNEL": ToString(channel), - "VELA_BUILD_CLONE": ToString(b.GetClone()), - "VELA_BUILD_COMMIT": ToString(b.GetCommit()), - "VELA_BUILD_CREATED": ToString(b.GetCreated()), - "VELA_BUILD_DISTRIBUTION": ToString(b.GetDistribution()), - "VELA_BUILD_ENQUEUED": ToString(b.GetEnqueued()), - "VELA_BUILD_EVENT": ToString(b.GetEvent()), - "VELA_BUILD_EVENT_ACTION": ToString(b.GetEventAction()), - "VELA_BUILD_HOST": ToString(b.GetHost()), - "VELA_BUILD_LINK": ToString(b.GetLink()), - "VELA_BUILD_MESSAGE": ToString(b.GetMessage()), - "VELA_BUILD_NUMBER": ToString(b.GetNumber()), - "VELA_BUILD_PARENT": ToString(b.GetParent()), - "VELA_BUILD_REF": ToString(b.GetRef()), - "VELA_BUILD_RUNTIME": ToString(b.GetRuntime()), - "VELA_BUILD_SENDER": ToString(b.GetSender()), - "VELA_BUILD_STARTED": ToString(b.GetStarted()), - "VELA_BUILD_SOURCE": ToString(b.GetSource()), - "VELA_BUILD_STATUS": ToString(b.GetStatus()), - "VELA_BUILD_TITLE": ToString(b.GetTitle()), - "VELA_BUILD_WORKSPACE": ToString(workspace), + "VELA_BUILD_APPROVED_AT": ToString(b.GetApprovedAt()), + "VELA_BUILD_APPROVED_BY": ToString(b.GetApprovedBy()), + "VELA_BUILD_AUTHOR": ToString(b.GetAuthor()), + "VELA_BUILD_AUTHOR_EMAIL": ToString(b.GetEmail()), + "VELA_BUILD_BASE_REF": ToString(b.GetBaseRef()), + "VELA_BUILD_BRANCH": ToString(b.GetBranch()), + "VELA_BUILD_CHANNEL": ToString(channel), + "VELA_BUILD_CLONE": ToString(b.GetClone()), + "VELA_BUILD_COMMIT": ToString(b.GetCommit()), + "VELA_BUILD_CREATED": ToString(b.GetCreated()), + "VELA_BUILD_DISTRIBUTION": ToString(b.GetDistribution()), + "VELA_BUILD_ENQUEUED": ToString(b.GetEnqueued()), + "VELA_BUILD_EVENT": ToString(b.GetEvent()), + "VELA_BUILD_EVENT_ACTION": ToString(b.GetEventAction()), + "VELA_BUILD_HOST": ToString(b.GetHost()), + "VELA_BUILD_LINK": ToString(b.GetLink()), + "VELA_BUILD_MESSAGE": ToString(b.GetMessage()), + "VELA_BUILD_NUMBER": ToString(b.GetNumber()), + "VELA_BUILD_PARENT": ToString(b.GetParent()), + "VELA_BUILD_REF": ToString(b.GetRef()), + "VELA_BUILD_RUNTIME": ToString(b.GetRuntime()), + "VELA_BUILD_SENDER": ToString(b.GetSender()), + "VELA_BUILD_SENDER_SCM_ID": ToString(b.GetSenderSCMID()), + "VELA_BUILD_STARTED": ToString(b.GetStarted()), + "VELA_BUILD_SOURCE": ToString(b.GetSource()), + "VELA_BUILD_STATUS": ToString(b.GetStatus()), + "VELA_BUILD_TITLE": ToString(b.GetTitle()), + "VELA_BUILD_WORKSPACE": ToString(workspace), // deprecated environment variables "BUILD_AUTHOR": ToString(b.GetAuthor()), @@ -502,6 +504,19 @@ func (b *Build) GetSender() string { return *b.Sender } +// GetSenderSCMID returns the SenderSCMID field. +// +// When the provided Build type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *Build) GetSenderSCMID() string { + // return zero value if Build type or SenderSCMID field is nil + if b == nil || b.SenderSCMID == nil { + return "" + } + + return *b.SenderSCMID +} + // GetAuthor returns the Author field. // // When the provided Build type is nil, or the field within @@ -944,6 +959,19 @@ func (b *Build) SetSender(v string) { b.Sender = &v } +// SetSenderSCMID sets the SenderSCMID field. +// +// When the provided Build type is nil, it +// will set nothing and immediately return. +func (b *Build) SetSenderSCMID(v string) { + // return if Build type is nil + if b == nil { + return + } + + b.SenderSCMID = &v +} + // SetAuthor sets the Author field. // // When the provided Build type is nil, it @@ -1135,6 +1163,7 @@ func (b *Build) String() string { Repo: %s, Runtime: %s, Sender: %s, + SenderSCMID: %s, Source: %s, Started: %d, Status: %s, @@ -1170,6 +1199,7 @@ func (b *Build) String() string { b.GetRepo().GetFullName(), b.GetRuntime(), b.GetSender(), + b.GetSenderSCMID(), b.GetSource(), b.GetStarted(), b.GetStatus(), diff --git a/api/types/build_test.go b/api/types/build_test.go index d70853bda..dd6d70483 100644 --- a/api/types/build_test.go +++ b/api/types/build_test.go @@ -92,55 +92,56 @@ func TestTypes_Build_Environment(t *testing.T) { { build: testBuild(), want: map[string]string{ - "VELA_BUILD_APPROVED_AT": "1563474076", - "VELA_BUILD_APPROVED_BY": "OctoCat", - "VELA_BUILD_AUTHOR": "OctoKitty", - "VELA_BUILD_AUTHOR_EMAIL": "OctoKitty@github.com", - "VELA_BUILD_BASE_REF": "", - "VELA_BUILD_BRANCH": "main", - "VELA_BUILD_CHANNEL": "TODO", - "VELA_BUILD_CLONE": "https://github.com/github/octocat.git", - "VELA_BUILD_COMMIT": "48afb5bdc41ad69bf22588491333f7cf71135163", - "VELA_BUILD_CREATED": "1563474076", - "VELA_BUILD_DISTRIBUTION": "linux", - "VELA_BUILD_ENQUEUED": "1563474077", - "VELA_BUILD_EVENT": "push", - "VELA_BUILD_EVENT_ACTION": "", - "VELA_BUILD_HOST": "example.company.com", - "VELA_BUILD_LINK": "https://example.company.com/github/octocat/1", - "VELA_BUILD_MESSAGE": "First commit...", - "VELA_BUILD_NUMBER": "1", - "VELA_BUILD_PARENT": "1", - "VELA_BUILD_REF": "refs/heads/main", - "VELA_BUILD_RUNTIME": "docker", - "VELA_BUILD_SENDER": "OctoKitty", - "VELA_BUILD_STARTED": "1563474078", - "VELA_BUILD_SOURCE": "https://github.com/github/octocat/48afb5bdc41ad69bf22588491333f7cf71135163", - "VELA_BUILD_STATUS": "running", - "VELA_BUILD_TITLE": "push received from https://github.com/github/octocat", - "VELA_BUILD_WORKSPACE": "TODO", - "BUILD_AUTHOR": "OctoKitty", - "BUILD_AUTHOR_EMAIL": "OctoKitty@github.com", - "BUILD_BASE_REF": "", - "BUILD_BRANCH": "main", - "BUILD_CHANNEL": "TODO", - "BUILD_CLONE": "https://github.com/github/octocat.git", - "BUILD_COMMIT": "48afb5bdc41ad69bf22588491333f7cf71135163", - "BUILD_CREATED": "1563474076", - "BUILD_ENQUEUED": "1563474077", - "BUILD_EVENT": "push", - "BUILD_HOST": "example.company.com", - "BUILD_LINK": "https://example.company.com/github/octocat/1", - "BUILD_MESSAGE": "First commit...", - "BUILD_NUMBER": "1", - "BUILD_PARENT": "1", - "BUILD_REF": "refs/heads/main", - "BUILD_SENDER": "OctoKitty", - "BUILD_STARTED": "1563474078", - "BUILD_SOURCE": "https://github.com/github/octocat/48afb5bdc41ad69bf22588491333f7cf71135163", - "BUILD_STATUS": "running", - "BUILD_TITLE": "push received from https://github.com/github/octocat", - "BUILD_WORKSPACE": "TODO", + "VELA_BUILD_APPROVED_AT": "1563474076", + "VELA_BUILD_APPROVED_BY": "OctoCat", + "VELA_BUILD_AUTHOR": "OctoKitty", + "VELA_BUILD_AUTHOR_EMAIL": "OctoKitty@github.com", + "VELA_BUILD_BASE_REF": "", + "VELA_BUILD_BRANCH": "main", + "VELA_BUILD_CHANNEL": "TODO", + "VELA_BUILD_CLONE": "https://github.com/github/octocat.git", + "VELA_BUILD_COMMIT": "48afb5bdc41ad69bf22588491333f7cf71135163", + "VELA_BUILD_CREATED": "1563474076", + "VELA_BUILD_DISTRIBUTION": "linux", + "VELA_BUILD_ENQUEUED": "1563474077", + "VELA_BUILD_EVENT": "push", + "VELA_BUILD_EVENT_ACTION": "", + "VELA_BUILD_HOST": "example.company.com", + "VELA_BUILD_LINK": "https://example.company.com/github/octocat/1", + "VELA_BUILD_MESSAGE": "First commit...", + "VELA_BUILD_NUMBER": "1", + "VELA_BUILD_PARENT": "1", + "VELA_BUILD_REF": "refs/heads/main", + "VELA_BUILD_RUNTIME": "docker", + "VELA_BUILD_SENDER": "OctoKitty", + "VELA_BUILD_SENDER_SCM_ID": "123", + "VELA_BUILD_STARTED": "1563474078", + "VELA_BUILD_SOURCE": "https://github.com/github/octocat/48afb5bdc41ad69bf22588491333f7cf71135163", + "VELA_BUILD_STATUS": "running", + "VELA_BUILD_TITLE": "push received from https://github.com/github/octocat", + "VELA_BUILD_WORKSPACE": "TODO", + "BUILD_AUTHOR": "OctoKitty", + "BUILD_AUTHOR_EMAIL": "OctoKitty@github.com", + "BUILD_BASE_REF": "", + "BUILD_BRANCH": "main", + "BUILD_CHANNEL": "TODO", + "BUILD_CLONE": "https://github.com/github/octocat.git", + "BUILD_COMMIT": "48afb5bdc41ad69bf22588491333f7cf71135163", + "BUILD_CREATED": "1563474076", + "BUILD_ENQUEUED": "1563474077", + "BUILD_EVENT": "push", + "BUILD_HOST": "example.company.com", + "BUILD_LINK": "https://example.company.com/github/octocat/1", + "BUILD_MESSAGE": "First commit...", + "BUILD_NUMBER": "1", + "BUILD_PARENT": "1", + "BUILD_REF": "refs/heads/main", + "BUILD_SENDER": "OctoKitty", + "BUILD_STARTED": "1563474078", + "BUILD_SOURCE": "https://github.com/github/octocat/48afb5bdc41ad69bf22588491333f7cf71135163", + "BUILD_STATUS": "running", + "BUILD_TITLE": "push received from https://github.com/github/octocat", + "BUILD_WORKSPACE": "TODO", }, }, { @@ -169,6 +170,7 @@ func TestTypes_Build_Environment(t *testing.T) { "VELA_BUILD_REF": "refs/pulls/1/head", "VELA_BUILD_RUNTIME": "docker", "VELA_BUILD_SENDER": "OctoKitty", + "VELA_BUILD_SENDER_SCM_ID": "123", "VELA_BUILD_STARTED": "1563474078", "VELA_BUILD_SOURCE": "https://github.com/github/octocat/48afb5bdc41ad69bf22588491333f7cf71135163", "VELA_BUILD_STATUS": "running", @@ -227,6 +229,7 @@ func TestTypes_Build_Environment(t *testing.T) { "VELA_BUILD_REF": "refs/heads/main", "VELA_BUILD_RUNTIME": "docker", "VELA_BUILD_SENDER": "OctoKitty", + "VELA_BUILD_SENDER_SCM_ID": "123", "VELA_BUILD_STARTED": "1563474078", "VELA_BUILD_SOURCE": "https://github.com/github/octocat/48afb5bdc41ad69bf22588491333f7cf71135163", "VELA_BUILD_STATUS": "running", @@ -287,6 +290,7 @@ func TestTypes_Build_Environment(t *testing.T) { "VELA_BUILD_REF": "refs/tags/v0.1.0", "VELA_BUILD_RUNTIME": "docker", "VELA_BUILD_SENDER": "OctoKitty", + "VELA_BUILD_SENDER_SCM_ID": "123", "VELA_BUILD_STARTED": "1563474078", "VELA_BUILD_SOURCE": "https://github.com/github/octocat/48afb5bdc41ad69bf22588491333f7cf71135163", "VELA_BUILD_STATUS": "running", @@ -350,6 +354,7 @@ func TestTypes_Build_Environment(t *testing.T) { "VELA_BUILD_REF": "refs/pulls/1/head", "VELA_BUILD_RUNTIME": "docker", "VELA_BUILD_SENDER": "OctoKitty", + "VELA_BUILD_SENDER_SCM_ID": "123", "VELA_BUILD_STARTED": "1563474078", "VELA_BUILD_SOURCE": "https://github.com/github/octocat/48afb5bdc41ad69bf22588491333f7cf71135163", "VELA_BUILD_STATUS": "running", @@ -386,57 +391,58 @@ func TestTypes_Build_Environment(t *testing.T) { { build: _tag, want: map[string]string{ - "VELA_BUILD_APPROVED_AT": "1563474076", - "VELA_BUILD_APPROVED_BY": "OctoCat", - "VELA_BUILD_AUTHOR": "OctoKitty", - "VELA_BUILD_AUTHOR_EMAIL": "OctoKitty@github.com", - "VELA_BUILD_BASE_REF": "", - "VELA_BUILD_BRANCH": "main", - "VELA_BUILD_CHANNEL": "TODO", - "VELA_BUILD_CLONE": "https://github.com/github/octocat.git", - "VELA_BUILD_COMMIT": "48afb5bdc41ad69bf22588491333f7cf71135163", - "VELA_BUILD_CREATED": "1563474076", - "VELA_BUILD_DISTRIBUTION": "linux", - "VELA_BUILD_ENQUEUED": "1563474077", - "VELA_BUILD_EVENT": "tag", - "VELA_BUILD_EVENT_ACTION": "", - "VELA_BUILD_HOST": "example.company.com", - "VELA_BUILD_LINK": "https://example.company.com/github/octocat/1", - "VELA_BUILD_MESSAGE": "First commit...", - "VELA_BUILD_NUMBER": "1", - "VELA_BUILD_PARENT": "1", - "VELA_BUILD_REF": "refs/tags/v0.1.0", - "VELA_BUILD_RUNTIME": "docker", - "VELA_BUILD_SENDER": "OctoKitty", - "VELA_BUILD_STARTED": "1563474078", - "VELA_BUILD_SOURCE": "https://github.com/github/octocat/48afb5bdc41ad69bf22588491333f7cf71135163", - "VELA_BUILD_STATUS": "running", - "VELA_BUILD_TAG": "v0.1.0", - "VELA_BUILD_TITLE": "push received from https://github.com/github/octocat", - "VELA_BUILD_WORKSPACE": "TODO", - "BUILD_AUTHOR": "OctoKitty", - "BUILD_AUTHOR_EMAIL": "OctoKitty@github.com", - "BUILD_BASE_REF": "", - "BUILD_BRANCH": "main", - "BUILD_CHANNEL": "TODO", - "BUILD_CLONE": "https://github.com/github/octocat.git", - "BUILD_COMMIT": "48afb5bdc41ad69bf22588491333f7cf71135163", - "BUILD_CREATED": "1563474076", - "BUILD_ENQUEUED": "1563474077", - "BUILD_EVENT": "tag", - "BUILD_HOST": "example.company.com", - "BUILD_LINK": "https://example.company.com/github/octocat/1", - "BUILD_MESSAGE": "First commit...", - "BUILD_NUMBER": "1", - "BUILD_PARENT": "1", - "BUILD_REF": "refs/tags/v0.1.0", - "BUILD_SENDER": "OctoKitty", - "BUILD_STARTED": "1563474078", - "BUILD_SOURCE": "https://github.com/github/octocat/48afb5bdc41ad69bf22588491333f7cf71135163", - "BUILD_STATUS": "running", - "BUILD_TAG": "v0.1.0", - "BUILD_TITLE": "push received from https://github.com/github/octocat", - "BUILD_WORKSPACE": "TODO", + "VELA_BUILD_APPROVED_AT": "1563474076", + "VELA_BUILD_APPROVED_BY": "OctoCat", + "VELA_BUILD_AUTHOR": "OctoKitty", + "VELA_BUILD_AUTHOR_EMAIL": "OctoKitty@github.com", + "VELA_BUILD_BASE_REF": "", + "VELA_BUILD_BRANCH": "main", + "VELA_BUILD_CHANNEL": "TODO", + "VELA_BUILD_CLONE": "https://github.com/github/octocat.git", + "VELA_BUILD_COMMIT": "48afb5bdc41ad69bf22588491333f7cf71135163", + "VELA_BUILD_CREATED": "1563474076", + "VELA_BUILD_DISTRIBUTION": "linux", + "VELA_BUILD_ENQUEUED": "1563474077", + "VELA_BUILD_EVENT": "tag", + "VELA_BUILD_EVENT_ACTION": "", + "VELA_BUILD_HOST": "example.company.com", + "VELA_BUILD_LINK": "https://example.company.com/github/octocat/1", + "VELA_BUILD_MESSAGE": "First commit...", + "VELA_BUILD_NUMBER": "1", + "VELA_BUILD_PARENT": "1", + "VELA_BUILD_REF": "refs/tags/v0.1.0", + "VELA_BUILD_RUNTIME": "docker", + "VELA_BUILD_SENDER": "OctoKitty", + "VELA_BUILD_SENDER_SCM_ID": "123", + "VELA_BUILD_STARTED": "1563474078", + "VELA_BUILD_SOURCE": "https://github.com/github/octocat/48afb5bdc41ad69bf22588491333f7cf71135163", + "VELA_BUILD_STATUS": "running", + "VELA_BUILD_TAG": "v0.1.0", + "VELA_BUILD_TITLE": "push received from https://github.com/github/octocat", + "VELA_BUILD_WORKSPACE": "TODO", + "BUILD_AUTHOR": "OctoKitty", + "BUILD_AUTHOR_EMAIL": "OctoKitty@github.com", + "BUILD_BASE_REF": "", + "BUILD_BRANCH": "main", + "BUILD_CHANNEL": "TODO", + "BUILD_CLONE": "https://github.com/github/octocat.git", + "BUILD_COMMIT": "48afb5bdc41ad69bf22588491333f7cf71135163", + "BUILD_CREATED": "1563474076", + "BUILD_ENQUEUED": "1563474077", + "BUILD_EVENT": "tag", + "BUILD_HOST": "example.company.com", + "BUILD_LINK": "https://example.company.com/github/octocat/1", + "BUILD_MESSAGE": "First commit...", + "BUILD_NUMBER": "1", + "BUILD_PARENT": "1", + "BUILD_REF": "refs/tags/v0.1.0", + "BUILD_SENDER": "OctoKitty", + "BUILD_STARTED": "1563474078", + "BUILD_SOURCE": "https://github.com/github/octocat/48afb5bdc41ad69bf22588491333f7cf71135163", + "BUILD_STATUS": "running", + "BUILD_TAG": "v0.1.0", + "BUILD_TITLE": "push received from https://github.com/github/octocat", + "BUILD_WORKSPACE": "TODO", }, }, } @@ -650,6 +656,7 @@ func TestTypes_Build_Setters(t *testing.T) { test.build.SetMessage(test.want.GetMessage()) test.build.SetCommit(test.want.GetCommit()) test.build.SetSender(test.want.GetSender()) + test.build.SetSenderSCMID(test.want.GetSenderSCMID()) test.build.SetAuthor(test.want.GetAuthor()) test.build.SetEmail(test.want.GetEmail()) test.build.SetLink(test.want.GetLink()) @@ -751,6 +758,10 @@ func TestTypes_Build_Setters(t *testing.T) { t.Errorf("SetSender is %v, want %v", test.build.GetSender(), test.want.GetSender()) } + if test.build.GetSenderSCMID() != test.want.GetSenderSCMID() { + t.Errorf("SetSenderSCMID is %v, want %v", test.build.GetSenderSCMID(), test.want.GetSenderSCMID()) + } + if test.build.GetAuthor() != test.want.GetAuthor() { t.Errorf("SetAuthor is %v, want %v", test.build.GetAuthor(), test.want.GetAuthor()) } @@ -836,6 +847,7 @@ func TestTypes_Build_String(t *testing.T) { Repo: %s, Runtime: %s, Sender: %s, + SenderSCMID: %s, Source: %s, Started: %d, Status: %s, @@ -871,6 +883,7 @@ func TestTypes_Build_String(t *testing.T) { b.GetRepo().GetFullName(), b.GetRuntime(), b.GetSender(), + b.GetSenderSCMID(), b.GetSource(), b.GetStarted(), b.GetStatus(), @@ -911,6 +924,7 @@ func testBuild() *Build { b.SetMessage("First commit...") b.SetCommit("48afb5bdc41ad69bf22588491333f7cf71135163") b.SetSender("OctoKitty") + b.SetSenderSCMID("123") b.SetAuthor("OctoKitty") b.SetEmail("OctoKitty@github.com") b.SetLink("https://example.company.com/github/octocat/1") diff --git a/cmd/vela-server/schedule.go b/cmd/vela-server/schedule.go index f2832935e..1eb715bd4 100644 --- a/cmd/vela-server/schedule.go +++ b/cmd/vela-server/schedule.go @@ -179,6 +179,10 @@ func processSchedule(ctx context.Context, s *api.Schedule, settings *settings.Pl b.SetRef(fmt.Sprintf("refs/heads/%s", b.GetBranch())) b.SetRepo(r) b.SetSender(s.GetUpdatedBy()) + // todo: sender_scm_id: + // - (a) auth with repo token and convert username to scm id + // - (b) attach scm user id to schedule updated_by_id (lol) + b.SetSource(fmt.Sprintf("%s/tree/%s", url, b.GetBranch())) b.SetStatus(constants.StatusPending) b.SetTitle(fmt.Sprintf("%s received from %s", constants.EventSchedule, url)) diff --git a/compiler/native/environment_test.go b/compiler/native/environment_test.go index 9c55ed41d..6d920021a 100644 --- a/compiler/native/environment_test.go +++ b/compiler/native/environment_test.go @@ -104,104 +104,105 @@ func TestNative_EnvironmentSteps(t *testing.T) { Name: str, Pull: "always", Environment: raw.StringSliceMap{ - "BUILD_AUTHOR": "", - "BUILD_AUTHOR_EMAIL": "", - "BUILD_BASE_REF": "", - "BUILD_BRANCH": "", - "BUILD_CHANNEL": "TODO", - "BUILD_CLONE": "", - "BUILD_COMMIT": "", - "BUILD_CREATED": "0", - "BUILD_ENQUEUED": "0", - "BUILD_EVENT": "", - "BUILD_HOST": "", - "BUILD_LINK": "", - "BUILD_MESSAGE": "", - "BUILD_NUMBER": "0", - "BUILD_PARENT": "0", - "BUILD_REF": "", - "BUILD_SENDER": "", - "BUILD_SOURCE": "", - "BUILD_STARTED": "0", - "BUILD_STATUS": "", - "BUILD_TITLE": "", - "BUILD_WORKSPACE": "/vela/src", - "CI": "true", - "REPOSITORY_ACTIVE": "false", - "REPOSITORY_ALLOW_EVENTS": "", - "REPOSITORY_BRANCH": "", - "REPOSITORY_CLONE": "", - "REPOSITORY_FULL_NAME": "", - "REPOSITORY_LINK": "", - "REPOSITORY_NAME": "", - "REPOSITORY_ORG": "", - "REPOSITORY_PRIVATE": "false", - "REPOSITORY_TIMEOUT": "0", - "REPOSITORY_TRUSTED": "false", - "REPOSITORY_VISIBILITY": "", - "VELA": "true", - "VELA_ADDR": "TODO", - "VELA_BUILD_APPROVED_AT": "0", - "VELA_BUILD_APPROVED_BY": "", - "VELA_BUILD_AUTHOR": "", - "VELA_BUILD_AUTHOR_EMAIL": "", - "VELA_BUILD_BASE_REF": "", - "VELA_BUILD_BRANCH": "", - "VELA_BUILD_CHANNEL": "TODO", - "VELA_BUILD_CLONE": "", - "VELA_BUILD_COMMIT": "", - "VELA_BUILD_CREATED": "0", - "VELA_BUILD_DISTRIBUTION": "", - "VELA_BUILD_ENQUEUED": "0", - "VELA_BUILD_EVENT": "", - "VELA_BUILD_EVENT_ACTION": "", - "VELA_BUILD_HOST": "", - "VELA_BUILD_LINK": "", - "VELA_BUILD_MESSAGE": "", - "VELA_BUILD_NUMBER": "0", - "VELA_BUILD_PARENT": "0", - "VELA_BUILD_REF": "", - "VELA_BUILD_RUNTIME": "", - "VELA_BUILD_SENDER": "", - "VELA_BUILD_SOURCE": "", - "VELA_BUILD_STARTED": "0", - "VELA_BUILD_STATUS": "", - "VELA_BUILD_TITLE": "", - "VELA_BUILD_WORKSPACE": "/vela/src", - "VELA_CHANNEL": "TODO", - "VELA_DATABASE": "TODO", - "VELA_DISTRIBUTION": "TODO", - "VELA_HOST": "TODO", - "VELA_NETRC_MACHINE": "TODO", - "VELA_NETRC_PASSWORD": "", - "VELA_NETRC_USERNAME": "x-oauth-basic", - "VELA_QUEUE": "TODO", - "VELA_REPO_ACTIVE": "false", - "VELA_REPO_ALLOW_EVENTS": "", - "VELA_REPO_APPROVE_BUILD": "", - "VELA_REPO_BRANCH": "", - "VELA_REPO_TOPICS": "", - "VELA_REPO_BUILD_LIMIT": "0", - "VELA_REPO_CLONE": "", - "VELA_REPO_FULL_NAME": "", - "VELA_REPO_LINK": "", - "VELA_REPO_NAME": "", - "VELA_REPO_ORG": "", - "VELA_REPO_OWNER": "", - "VELA_REPO_PIPELINE_TYPE": "", - "VELA_REPO_PRIVATE": "false", - "VELA_REPO_TIMEOUT": "0", - "VELA_REPO_TRUSTED": "false", - "VELA_REPO_VISIBILITY": "", - "VELA_RUNTIME": "TODO", - "VELA_SOURCE": "TODO", - "VELA_USER_ACTIVE": "false", - "VELA_USER_ADMIN": "false", - "VELA_USER_FAVORITES": "[]", - "VELA_USER_NAME": "", - "VELA_VERSION": "TODO", - "VELA_WORKSPACE": "/vela/src", - "HELLO": "Hello, Stage Message", + "BUILD_AUTHOR": "", + "BUILD_AUTHOR_EMAIL": "", + "BUILD_BASE_REF": "", + "BUILD_BRANCH": "", + "BUILD_CHANNEL": "TODO", + "BUILD_CLONE": "", + "BUILD_COMMIT": "", + "BUILD_CREATED": "0", + "BUILD_ENQUEUED": "0", + "BUILD_EVENT": "", + "BUILD_HOST": "", + "BUILD_LINK": "", + "BUILD_MESSAGE": "", + "BUILD_NUMBER": "0", + "BUILD_PARENT": "0", + "BUILD_REF": "", + "BUILD_SENDER": "", + "BUILD_SOURCE": "", + "BUILD_STARTED": "0", + "BUILD_STATUS": "", + "BUILD_TITLE": "", + "BUILD_WORKSPACE": "/vela/src", + "CI": "true", + "REPOSITORY_ACTIVE": "false", + "REPOSITORY_ALLOW_EVENTS": "", + "REPOSITORY_BRANCH": "", + "REPOSITORY_CLONE": "", + "REPOSITORY_FULL_NAME": "", + "REPOSITORY_LINK": "", + "REPOSITORY_NAME": "", + "REPOSITORY_ORG": "", + "REPOSITORY_PRIVATE": "false", + "REPOSITORY_TIMEOUT": "0", + "REPOSITORY_TRUSTED": "false", + "REPOSITORY_VISIBILITY": "", + "VELA": "true", + "VELA_ADDR": "TODO", + "VELA_BUILD_APPROVED_AT": "0", + "VELA_BUILD_APPROVED_BY": "", + "VELA_BUILD_AUTHOR": "", + "VELA_BUILD_AUTHOR_EMAIL": "", + "VELA_BUILD_BASE_REF": "", + "VELA_BUILD_BRANCH": "", + "VELA_BUILD_CHANNEL": "TODO", + "VELA_BUILD_CLONE": "", + "VELA_BUILD_COMMIT": "", + "VELA_BUILD_CREATED": "0", + "VELA_BUILD_DISTRIBUTION": "", + "VELA_BUILD_ENQUEUED": "0", + "VELA_BUILD_EVENT": "", + "VELA_BUILD_EVENT_ACTION": "", + "VELA_BUILD_HOST": "", + "VELA_BUILD_LINK": "", + "VELA_BUILD_MESSAGE": "", + "VELA_BUILD_NUMBER": "0", + "VELA_BUILD_PARENT": "0", + "VELA_BUILD_REF": "", + "VELA_BUILD_RUNTIME": "", + "VELA_BUILD_SENDER": "", + "VELA_BUILD_SENDER_SCM_ID": "", + "VELA_BUILD_SOURCE": "", + "VELA_BUILD_STARTED": "0", + "VELA_BUILD_STATUS": "", + "VELA_BUILD_TITLE": "", + "VELA_BUILD_WORKSPACE": "/vela/src", + "VELA_CHANNEL": "TODO", + "VELA_DATABASE": "TODO", + "VELA_DISTRIBUTION": "TODO", + "VELA_HOST": "TODO", + "VELA_NETRC_MACHINE": "TODO", + "VELA_NETRC_PASSWORD": "", + "VELA_NETRC_USERNAME": "x-oauth-basic", + "VELA_QUEUE": "TODO", + "VELA_REPO_ACTIVE": "false", + "VELA_REPO_ALLOW_EVENTS": "", + "VELA_REPO_APPROVE_BUILD": "", + "VELA_REPO_BRANCH": "", + "VELA_REPO_TOPICS": "", + "VELA_REPO_BUILD_LIMIT": "0", + "VELA_REPO_CLONE": "", + "VELA_REPO_FULL_NAME": "", + "VELA_REPO_LINK": "", + "VELA_REPO_NAME": "", + "VELA_REPO_ORG": "", + "VELA_REPO_OWNER": "", + "VELA_REPO_PIPELINE_TYPE": "", + "VELA_REPO_PRIVATE": "false", + "VELA_REPO_TIMEOUT": "0", + "VELA_REPO_TRUSTED": "false", + "VELA_REPO_VISIBILITY": "", + "VELA_RUNTIME": "TODO", + "VELA_SOURCE": "TODO", + "VELA_USER_ACTIVE": "false", + "VELA_USER_ADMIN": "false", + "VELA_USER_FAVORITES": "[]", + "VELA_USER_NAME": "", + "VELA_VERSION": "TODO", + "VELA_WORKSPACE": "/vela/src", + "HELLO": "Hello, Stage Message", }, }, } @@ -280,104 +281,105 @@ func TestNative_EnvironmentServices(t *testing.T) { Name: str, Pull: "always", Environment: raw.StringSliceMap{ - "BUILD_AUTHOR": "", - "BUILD_AUTHOR_EMAIL": "", - "BUILD_BASE_REF": "", - "BUILD_BRANCH": "", - "BUILD_CHANNEL": "TODO", - "BUILD_CLONE": "", - "BUILD_COMMIT": "", - "BUILD_CREATED": "0", - "BUILD_ENQUEUED": "0", - "BUILD_EVENT": "", - "BUILD_HOST": "", - "BUILD_LINK": "", - "BUILD_MESSAGE": "", - "BUILD_NUMBER": "0", - "BUILD_PARENT": "0", - "BUILD_REF": "", - "BUILD_SENDER": "", - "BUILD_SOURCE": "", - "BUILD_STARTED": "0", - "BUILD_STATUS": "", - "BUILD_TITLE": "", - "BUILD_WORKSPACE": "/vela/src", - "CI": "true", - "REPOSITORY_ACTIVE": "false", - "REPOSITORY_ALLOW_EVENTS": "", - "REPOSITORY_BRANCH": "", - "REPOSITORY_CLONE": "", - "REPOSITORY_FULL_NAME": "", - "REPOSITORY_LINK": "", - "REPOSITORY_NAME": "", - "REPOSITORY_ORG": "", - "REPOSITORY_PRIVATE": "false", - "REPOSITORY_TIMEOUT": "0", - "REPOSITORY_TRUSTED": "false", - "REPOSITORY_VISIBILITY": "", - "VELA": "true", - "VELA_ADDR": "TODO", - "VELA_BUILD_APPROVED_AT": "0", - "VELA_BUILD_APPROVED_BY": "", - "VELA_BUILD_AUTHOR": "", - "VELA_BUILD_AUTHOR_EMAIL": "", - "VELA_BUILD_BASE_REF": "", - "VELA_BUILD_BRANCH": "", - "VELA_BUILD_CHANNEL": "TODO", - "VELA_BUILD_CLONE": "", - "VELA_BUILD_COMMIT": "", - "VELA_BUILD_CREATED": "0", - "VELA_BUILD_DISTRIBUTION": "", - "VELA_BUILD_ENQUEUED": "0", - "VELA_BUILD_EVENT": "", - "VELA_BUILD_EVENT_ACTION": "", - "VELA_BUILD_HOST": "", - "VELA_BUILD_LINK": "", - "VELA_BUILD_MESSAGE": "", - "VELA_BUILD_NUMBER": "0", - "VELA_BUILD_PARENT": "0", - "VELA_BUILD_REF": "", - "VELA_BUILD_RUNTIME": "", - "VELA_BUILD_SENDER": "", - "VELA_BUILD_SOURCE": "", - "VELA_BUILD_STARTED": "0", - "VELA_BUILD_STATUS": "", - "VELA_BUILD_TITLE": "", - "VELA_BUILD_WORKSPACE": "/vela/src", - "VELA_CHANNEL": "TODO", - "VELA_DATABASE": "TODO", - "VELA_DISTRIBUTION": "TODO", - "VELA_HOST": "TODO", - "VELA_NETRC_MACHINE": "TODO", - "VELA_NETRC_PASSWORD": "", - "VELA_NETRC_USERNAME": "x-oauth-basic", - "VELA_QUEUE": "TODO", - "VELA_REPO_ACTIVE": "false", - "VELA_REPO_ALLOW_EVENTS": "", - "VELA_REPO_APPROVE_BUILD": "", - "VELA_REPO_BRANCH": "", - "VELA_REPO_TOPICS": "", - "VELA_REPO_BUILD_LIMIT": "0", - "VELA_REPO_CLONE": "", - "VELA_REPO_FULL_NAME": "", - "VELA_REPO_LINK": "", - "VELA_REPO_NAME": "", - "VELA_REPO_ORG": "", - "VELA_REPO_OWNER": "", - "VELA_REPO_PIPELINE_TYPE": "", - "VELA_REPO_PRIVATE": "false", - "VELA_REPO_TIMEOUT": "0", - "VELA_REPO_TRUSTED": "false", - "VELA_REPO_VISIBILITY": "", - "VELA_RUNTIME": "TODO", - "VELA_SOURCE": "TODO", - "VELA_USER_ACTIVE": "false", - "VELA_USER_ADMIN": "false", - "VELA_USER_FAVORITES": "[]", - "VELA_USER_NAME": "", - "VELA_VERSION": "TODO", - "VELA_WORKSPACE": "/vela/src", - "HELLO": "Hello, Global Message", + "BUILD_AUTHOR": "", + "BUILD_AUTHOR_EMAIL": "", + "BUILD_BASE_REF": "", + "BUILD_BRANCH": "", + "BUILD_CHANNEL": "TODO", + "BUILD_CLONE": "", + "BUILD_COMMIT": "", + "BUILD_CREATED": "0", + "BUILD_ENQUEUED": "0", + "BUILD_EVENT": "", + "BUILD_HOST": "", + "BUILD_LINK": "", + "BUILD_MESSAGE": "", + "BUILD_NUMBER": "0", + "BUILD_PARENT": "0", + "BUILD_REF": "", + "BUILD_SENDER": "", + "BUILD_SOURCE": "", + "BUILD_STARTED": "0", + "BUILD_STATUS": "", + "BUILD_TITLE": "", + "BUILD_WORKSPACE": "/vela/src", + "CI": "true", + "REPOSITORY_ACTIVE": "false", + "REPOSITORY_ALLOW_EVENTS": "", + "REPOSITORY_BRANCH": "", + "REPOSITORY_CLONE": "", + "REPOSITORY_FULL_NAME": "", + "REPOSITORY_LINK": "", + "REPOSITORY_NAME": "", + "REPOSITORY_ORG": "", + "REPOSITORY_PRIVATE": "false", + "REPOSITORY_TIMEOUT": "0", + "REPOSITORY_TRUSTED": "false", + "REPOSITORY_VISIBILITY": "", + "VELA": "true", + "VELA_ADDR": "TODO", + "VELA_BUILD_APPROVED_AT": "0", + "VELA_BUILD_APPROVED_BY": "", + "VELA_BUILD_AUTHOR": "", + "VELA_BUILD_AUTHOR_EMAIL": "", + "VELA_BUILD_BASE_REF": "", + "VELA_BUILD_BRANCH": "", + "VELA_BUILD_CHANNEL": "TODO", + "VELA_BUILD_CLONE": "", + "VELA_BUILD_COMMIT": "", + "VELA_BUILD_CREATED": "0", + "VELA_BUILD_DISTRIBUTION": "", + "VELA_BUILD_ENQUEUED": "0", + "VELA_BUILD_EVENT": "", + "VELA_BUILD_EVENT_ACTION": "", + "VELA_BUILD_HOST": "", + "VELA_BUILD_LINK": "", + "VELA_BUILD_MESSAGE": "", + "VELA_BUILD_NUMBER": "0", + "VELA_BUILD_PARENT": "0", + "VELA_BUILD_REF": "", + "VELA_BUILD_RUNTIME": "", + "VELA_BUILD_SENDER": "", + "VELA_BUILD_SENDER_SCM_ID": "", + "VELA_BUILD_SOURCE": "", + "VELA_BUILD_STARTED": "0", + "VELA_BUILD_STATUS": "", + "VELA_BUILD_TITLE": "", + "VELA_BUILD_WORKSPACE": "/vela/src", + "VELA_CHANNEL": "TODO", + "VELA_DATABASE": "TODO", + "VELA_DISTRIBUTION": "TODO", + "VELA_HOST": "TODO", + "VELA_NETRC_MACHINE": "TODO", + "VELA_NETRC_PASSWORD": "", + "VELA_NETRC_USERNAME": "x-oauth-basic", + "VELA_QUEUE": "TODO", + "VELA_REPO_ACTIVE": "false", + "VELA_REPO_ALLOW_EVENTS": "", + "VELA_REPO_APPROVE_BUILD": "", + "VELA_REPO_BRANCH": "", + "VELA_REPO_TOPICS": "", + "VELA_REPO_BUILD_LIMIT": "0", + "VELA_REPO_CLONE": "", + "VELA_REPO_FULL_NAME": "", + "VELA_REPO_LINK": "", + "VELA_REPO_NAME": "", + "VELA_REPO_ORG": "", + "VELA_REPO_OWNER": "", + "VELA_REPO_PIPELINE_TYPE": "", + "VELA_REPO_PRIVATE": "false", + "VELA_REPO_TIMEOUT": "0", + "VELA_REPO_TRUSTED": "false", + "VELA_REPO_VISIBILITY": "", + "VELA_RUNTIME": "TODO", + "VELA_SOURCE": "TODO", + "VELA_USER_ACTIVE": "false", + "VELA_USER_ADMIN": "false", + "VELA_USER_FAVORITES": "[]", + "VELA_USER_NAME": "", + "VELA_VERSION": "TODO", + "VELA_WORKSPACE": "/vela/src", + "HELLO": "Hello, Global Message", }, }, } @@ -437,105 +439,106 @@ func TestNative_EnvironmentSecrets(t *testing.T) { "foo": "bar", }, Environment: raw.StringSliceMap{ - "BUILD_AUTHOR": "", - "BUILD_AUTHOR_EMAIL": "", - "BUILD_BASE_REF": "", - "BUILD_BRANCH": "", - "BUILD_CHANNEL": "TODO", - "BUILD_CLONE": "", - "BUILD_COMMIT": "", - "BUILD_CREATED": "0", - "BUILD_ENQUEUED": "0", - "BUILD_EVENT": "", - "BUILD_HOST": "", - "BUILD_LINK": "", - "BUILD_MESSAGE": "", - "BUILD_NUMBER": "0", - "BUILD_PARENT": "0", - "BUILD_REF": "", - "BUILD_SENDER": "", - "BUILD_SOURCE": "", - "BUILD_STARTED": "0", - "BUILD_STATUS": "", - "BUILD_TITLE": "", - "BUILD_WORKSPACE": "/vela/src", - "CI": "true", - "PARAMETER_FOO": "bar", - "REPOSITORY_ACTIVE": "false", - "REPOSITORY_ALLOW_EVENTS": "", - "REPOSITORY_BRANCH": "", - "REPOSITORY_CLONE": "", - "REPOSITORY_FULL_NAME": "", - "REPOSITORY_LINK": "", - "REPOSITORY_NAME": "", - "REPOSITORY_ORG": "", - "REPOSITORY_PRIVATE": "false", - "REPOSITORY_TIMEOUT": "0", - "REPOSITORY_TRUSTED": "false", - "REPOSITORY_VISIBILITY": "", - "VELA": "true", - "VELA_ADDR": "TODO", - "VELA_BUILD_APPROVED_AT": "0", - "VELA_BUILD_APPROVED_BY": "", - "VELA_BUILD_AUTHOR": "", - "VELA_BUILD_AUTHOR_EMAIL": "", - "VELA_BUILD_BASE_REF": "", - "VELA_BUILD_BRANCH": "", - "VELA_BUILD_CHANNEL": "TODO", - "VELA_BUILD_CLONE": "", - "VELA_BUILD_COMMIT": "", - "VELA_BUILD_CREATED": "0", - "VELA_BUILD_DISTRIBUTION": "", - "VELA_BUILD_ENQUEUED": "0", - "VELA_BUILD_EVENT": "", - "VELA_BUILD_EVENT_ACTION": "", - "VELA_BUILD_HOST": "", - "VELA_BUILD_LINK": "", - "VELA_BUILD_MESSAGE": "", - "VELA_BUILD_NUMBER": "0", - "VELA_BUILD_PARENT": "0", - "VELA_BUILD_REF": "", - "VELA_BUILD_RUNTIME": "", - "VELA_BUILD_SENDER": "", - "VELA_BUILD_SOURCE": "", - "VELA_BUILD_STARTED": "0", - "VELA_BUILD_STATUS": "", - "VELA_BUILD_TITLE": "", - "VELA_BUILD_WORKSPACE": "/vela/src", - "VELA_CHANNEL": "TODO", - "VELA_DATABASE": "TODO", - "VELA_DISTRIBUTION": "TODO", - "VELA_HOST": "TODO", - "VELA_NETRC_MACHINE": "TODO", - "VELA_NETRC_PASSWORD": "", - "VELA_NETRC_USERNAME": "x-oauth-basic", - "VELA_QUEUE": "TODO", - "VELA_REPO_ACTIVE": "false", - "VELA_REPO_ALLOW_EVENTS": "", - "VELA_REPO_APPROVE_BUILD": "", - "VELA_REPO_BRANCH": "", - "VELA_REPO_TOPICS": "", - "VELA_REPO_BUILD_LIMIT": "0", - "VELA_REPO_CLONE": "", - "VELA_REPO_FULL_NAME": "", - "VELA_REPO_LINK": "", - "VELA_REPO_NAME": "", - "VELA_REPO_ORG": "", - "VELA_REPO_OWNER": "", - "VELA_REPO_PIPELINE_TYPE": "", - "VELA_REPO_PRIVATE": "false", - "VELA_REPO_TIMEOUT": "0", - "VELA_REPO_TRUSTED": "false", - "VELA_REPO_VISIBILITY": "", - "VELA_RUNTIME": "TODO", - "VELA_SOURCE": "TODO", - "VELA_USER_ACTIVE": "false", - "VELA_USER_ADMIN": "false", - "VELA_USER_FAVORITES": "[]", - "VELA_USER_NAME": "", - "VELA_VERSION": "TODO", - "VELA_WORKSPACE": "/vela/src", - "HELLO": "Hello, Global Message", + "BUILD_AUTHOR": "", + "BUILD_AUTHOR_EMAIL": "", + "BUILD_BASE_REF": "", + "BUILD_BRANCH": "", + "BUILD_CHANNEL": "TODO", + "BUILD_CLONE": "", + "BUILD_COMMIT": "", + "BUILD_CREATED": "0", + "BUILD_ENQUEUED": "0", + "BUILD_EVENT": "", + "BUILD_HOST": "", + "BUILD_LINK": "", + "BUILD_MESSAGE": "", + "BUILD_NUMBER": "0", + "BUILD_PARENT": "0", + "BUILD_REF": "", + "BUILD_SENDER": "", + "BUILD_SOURCE": "", + "BUILD_STARTED": "0", + "BUILD_STATUS": "", + "BUILD_TITLE": "", + "BUILD_WORKSPACE": "/vela/src", + "CI": "true", + "PARAMETER_FOO": "bar", + "REPOSITORY_ACTIVE": "false", + "REPOSITORY_ALLOW_EVENTS": "", + "REPOSITORY_BRANCH": "", + "REPOSITORY_CLONE": "", + "REPOSITORY_FULL_NAME": "", + "REPOSITORY_LINK": "", + "REPOSITORY_NAME": "", + "REPOSITORY_ORG": "", + "REPOSITORY_PRIVATE": "false", + "REPOSITORY_TIMEOUT": "0", + "REPOSITORY_TRUSTED": "false", + "REPOSITORY_VISIBILITY": "", + "VELA": "true", + "VELA_ADDR": "TODO", + "VELA_BUILD_APPROVED_AT": "0", + "VELA_BUILD_APPROVED_BY": "", + "VELA_BUILD_AUTHOR": "", + "VELA_BUILD_AUTHOR_EMAIL": "", + "VELA_BUILD_BASE_REF": "", + "VELA_BUILD_BRANCH": "", + "VELA_BUILD_CHANNEL": "TODO", + "VELA_BUILD_CLONE": "", + "VELA_BUILD_COMMIT": "", + "VELA_BUILD_CREATED": "0", + "VELA_BUILD_DISTRIBUTION": "", + "VELA_BUILD_ENQUEUED": "0", + "VELA_BUILD_EVENT": "", + "VELA_BUILD_EVENT_ACTION": "", + "VELA_BUILD_HOST": "", + "VELA_BUILD_LINK": "", + "VELA_BUILD_MESSAGE": "", + "VELA_BUILD_NUMBER": "0", + "VELA_BUILD_PARENT": "0", + "VELA_BUILD_REF": "", + "VELA_BUILD_RUNTIME": "", + "VELA_BUILD_SENDER": "", + "VELA_BUILD_SENDER_SCM_ID": "", + "VELA_BUILD_SOURCE": "", + "VELA_BUILD_STARTED": "0", + "VELA_BUILD_STATUS": "", + "VELA_BUILD_TITLE": "", + "VELA_BUILD_WORKSPACE": "/vela/src", + "VELA_CHANNEL": "TODO", + "VELA_DATABASE": "TODO", + "VELA_DISTRIBUTION": "TODO", + "VELA_HOST": "TODO", + "VELA_NETRC_MACHINE": "TODO", + "VELA_NETRC_PASSWORD": "", + "VELA_NETRC_USERNAME": "x-oauth-basic", + "VELA_QUEUE": "TODO", + "VELA_REPO_ACTIVE": "false", + "VELA_REPO_ALLOW_EVENTS": "", + "VELA_REPO_APPROVE_BUILD": "", + "VELA_REPO_BRANCH": "", + "VELA_REPO_TOPICS": "", + "VELA_REPO_BUILD_LIMIT": "0", + "VELA_REPO_CLONE": "", + "VELA_REPO_FULL_NAME": "", + "VELA_REPO_LINK": "", + "VELA_REPO_NAME": "", + "VELA_REPO_ORG": "", + "VELA_REPO_OWNER": "", + "VELA_REPO_PIPELINE_TYPE": "", + "VELA_REPO_PRIVATE": "false", + "VELA_REPO_TIMEOUT": "0", + "VELA_REPO_TRUSTED": "false", + "VELA_REPO_VISIBILITY": "", + "VELA_RUNTIME": "TODO", + "VELA_SOURCE": "TODO", + "VELA_USER_ACTIVE": "false", + "VELA_USER_ADMIN": "false", + "VELA_USER_FAVORITES": "[]", + "VELA_USER_NAME": "", + "VELA_VERSION": "TODO", + "VELA_WORKSPACE": "/vela/src", + "HELLO": "Hello, Global Message", }, }, }, @@ -589,38 +592,38 @@ func TestNative_environment(t *testing.T) { // push { w: workspace, - b: &api.Build{ID: &num64, Repo: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, Number: &num, Parent: &num, Event: &push, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &str, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, Author: &str, Branch: &str, Ref: &str, BaseRef: &str}, + b: &api.Build{ID: &num64, Repo: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, Number: &num, Parent: &num, Event: &push, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &str, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, SenderSCMID: &str, Author: &str, Branch: &str, Ref: &str, BaseRef: &str}, m: &internal.Metadata{Database: &internal.Database{Driver: str, Host: str}, Queue: &internal.Queue{Channel: str, Driver: str, Host: str}, Source: &internal.Source{Driver: str, Host: str}, Vela: &internal.Vela{Address: str, WebAddress: str}}, r: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, u: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, - want: map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "push", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "foo", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "push", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "foo", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_BRANCH": "foo", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_OWNER": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"}, + want: map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "push", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "foo", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "push", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "foo", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SENDER_SCM_ID": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_BRANCH": "foo", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_OWNER": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"}, }, // tag { w: workspace, - b: &api.Build{ID: &num64, Repo: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, Number: &num, Parent: &num, Event: &tag, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &str, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, Author: &str, Branch: &str, Ref: &tagref, BaseRef: &str}, + b: &api.Build{ID: &num64, Repo: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, Number: &num, Parent: &num, Event: &tag, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &str, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, SenderSCMID: &str, Author: &str, Branch: &str, Ref: &tagref, BaseRef: &str}, m: &internal.Metadata{Database: &internal.Database{Driver: str, Host: str}, Queue: &internal.Queue{Channel: str, Driver: str, Host: str}, Source: &internal.Source{Driver: str, Host: str}, Vela: &internal.Vela{Address: str, WebAddress: str}}, r: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, u: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, - want: map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "tag", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "refs/tags/1", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TAG": "1", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "tag", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "refs/tags/1", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TAG": "1", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_BRANCH": "foo", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_OWNER": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"}, + want: map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "tag", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "refs/tags/1", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TAG": "1", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "tag", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "refs/tags/1", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SENDER_SCM_ID": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TAG": "1", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_BRANCH": "foo", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_OWNER": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"}, }, // pull_request { w: workspace, - b: &api.Build{ID: &num64, Repo: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, Number: &num, Parent: &num, Event: &pull, EventAction: &pullact, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &str, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, Author: &str, Branch: &str, Ref: &pullref, BaseRef: &str}, + b: &api.Build{ID: &num64, Repo: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, Number: &num, Parent: &num, Event: &pull, EventAction: &pullact, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &str, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, SenderSCMID: &str, Author: &str, Branch: &str, Ref: &pullref, BaseRef: &str}, m: &internal.Metadata{Database: &internal.Database{Driver: str, Host: str}, Queue: &internal.Queue{Channel: str, Driver: str, Host: str}, Source: &internal.Source{Driver: str, Host: str}, Vela: &internal.Vela{Address: str, WebAddress: str}}, r: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, u: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, - want: map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "pull_request", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_PULL_REQUEST_NUMBER": "1", "BUILD_REF": "refs/pull/1/head", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "pull_request", "VELA_BUILD_EVENT_ACTION": "opened", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_PULL_REQUEST": "1", "VELA_BUILD_REF": "refs/pull/1/head", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_PULL_REQUEST": "1", "VELA_PULL_REQUEST_SOURCE": "", "VELA_PULL_REQUEST_TARGET": "foo", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_BRANCH": "foo", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_OWNER": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"}, + want: map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "pull_request", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_PULL_REQUEST_NUMBER": "1", "BUILD_REF": "refs/pull/1/head", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "pull_request", "VELA_BUILD_EVENT_ACTION": "opened", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_PULL_REQUEST": "1", "VELA_BUILD_REF": "refs/pull/1/head", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SENDER_SCM_ID": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_PULL_REQUEST": "1", "VELA_PULL_REQUEST_SOURCE": "", "VELA_PULL_REQUEST_TARGET": "foo", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_BRANCH": "foo", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_OWNER": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"}, }, // deployment { w: workspace, - b: &api.Build{ID: &num64, Repo: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, Number: &num, Parent: &num, Event: &deploy, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &target, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, Author: &str, Branch: &str, Ref: &pullref, BaseRef: &str}, + b: &api.Build{ID: &num64, Repo: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, Number: &num, Parent: &num, Event: &deploy, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &target, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, SenderSCMID: &str, Author: &str, Branch: &str, Ref: &pullref, BaseRef: &str}, m: &internal.Metadata{Database: &internal.Database{Driver: str, Host: str}, Queue: &internal.Queue{Channel: str, Driver: str, Host: str}, Source: &internal.Source{Driver: str, Host: str}, Vela: &internal.Vela{Address: str, WebAddress: str}}, r: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, u: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, - want: map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "deployment", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "refs/pull/1/head", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TARGET": "production", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "deployment", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "refs/pull/1/head", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TARGET": "production", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DEPLOYMENT": "production", "VELA_DEPLOYMENT_NUMBER": "0", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_BRANCH": "foo", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_OWNER": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"}, + want: map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "deployment", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "refs/pull/1/head", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TARGET": "production", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "deployment", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "refs/pull/1/head", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SENDER_SCM_ID": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TARGET": "production", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DEPLOYMENT": "production", "VELA_DEPLOYMENT_NUMBER": "0", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_BRANCH": "foo", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_OWNER": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"}, }, } @@ -705,31 +708,31 @@ func Test_client_EnvironmentBuild(t *testing.T) { want map[string]string }{ {"push", fields{ - build: &api.Build{ID: &num64, Repo: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, Number: &num, Parent: &num, Event: &push, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &str, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, Author: &str, Branch: &str, Ref: &str, BaseRef: &str}, + build: &api.Build{ID: &num64, Repo: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, Number: &num, Parent: &num, Event: &push, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &str, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, SenderSCMID: &str, Author: &str, Branch: &str, Ref: &str, BaseRef: &str}, metadata: &internal.Metadata{Database: &internal.Database{Driver: str, Host: str}, Queue: &internal.Queue{Channel: str, Driver: str, Host: str}, Source: &internal.Source{Driver: str, Host: str}, Vela: &internal.Vela{Address: str, WebAddress: str}}, repo: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, user: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, - }, map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "push", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "foo", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "push", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "foo", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_OWNER": "foo", "VELA_REPO_BRANCH": "foo", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"}}, + }, map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "push", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "foo", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "push", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "foo", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SENDER_SCM_ID": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_OWNER": "foo", "VELA_REPO_BRANCH": "foo", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"}}, {"tag", fields{ - build: &api.Build{ID: &num64, Repo: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, Number: &num, Parent: &num, Event: &tag, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &str, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, Author: &str, Branch: &str, Ref: &tagref, BaseRef: &str}, + build: &api.Build{ID: &num64, Repo: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, Number: &num, Parent: &num, Event: &tag, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &str, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, SenderSCMID: &str, Author: &str, Branch: &str, Ref: &tagref, BaseRef: &str}, metadata: &internal.Metadata{Database: &internal.Database{Driver: str, Host: str}, Queue: &internal.Queue{Channel: str, Driver: str, Host: str}, Source: &internal.Source{Driver: str, Host: str}, Vela: &internal.Vela{Address: str, WebAddress: str}}, repo: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, user: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, - }, map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "tag", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "refs/tags/1", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TAG": "1", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "tag", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "refs/tags/1", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TAG": "1", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_OWNER": "foo", "VELA_REPO_BRANCH": "foo", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"}, + }, map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "tag", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "refs/tags/1", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TAG": "1", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "tag", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "refs/tags/1", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SENDER_SCM_ID": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TAG": "1", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_OWNER": "foo", "VELA_REPO_BRANCH": "foo", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"}, }, {"pull_request", fields{ - build: &api.Build{ID: &num64, Repo: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, Number: &num, Parent: &num, Event: &pull, EventAction: &pullact, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &str, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, Author: &str, Branch: &str, Ref: &pullref, BaseRef: &str}, + build: &api.Build{ID: &num64, Repo: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, Number: &num, Parent: &num, Event: &pull, EventAction: &pullact, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &str, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, SenderSCMID: &str, Author: &str, Branch: &str, Ref: &pullref, BaseRef: &str}, metadata: &internal.Metadata{Database: &internal.Database{Driver: str, Host: str}, Queue: &internal.Queue{Channel: str, Driver: str, Host: str}, Source: &internal.Source{Driver: str, Host: str}, Vela: &internal.Vela{Address: str, WebAddress: str}}, repo: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, user: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, - }, map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "pull_request", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_PULL_REQUEST_NUMBER": "1", "BUILD_REF": "refs/pull/1/head", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "pull_request", "VELA_BUILD_EVENT_ACTION": "opened", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_PULL_REQUEST": "1", "VELA_BUILD_REF": "refs/pull/1/head", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_PULL_REQUEST": "1", "VELA_PULL_REQUEST_SOURCE": "", "VELA_PULL_REQUEST_TARGET": "foo", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_OWNER": "foo", "VELA_REPO_BRANCH": "foo", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"}, + }, map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "pull_request", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_PULL_REQUEST_NUMBER": "1", "BUILD_REF": "refs/pull/1/head", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "pull_request", "VELA_BUILD_EVENT_ACTION": "opened", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_PULL_REQUEST": "1", "VELA_BUILD_REF": "refs/pull/1/head", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SENDER_SCM_ID": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_PULL_REQUEST": "1", "VELA_PULL_REQUEST_SOURCE": "", "VELA_PULL_REQUEST_TARGET": "foo", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_OWNER": "foo", "VELA_REPO_BRANCH": "foo", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"}, }, {"deployment", fields{ - build: &api.Build{ID: &num64, Repo: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, Number: &num, Parent: &num, Event: &deploy, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &target, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, Author: &str, Branch: &str, Ref: &pullref, BaseRef: &str}, + build: &api.Build{ID: &num64, Repo: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, Number: &num, Parent: &num, Event: &deploy, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &target, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, SenderSCMID: &str, Author: &str, Branch: &str, Ref: &pullref, BaseRef: &str}, metadata: &internal.Metadata{Database: &internal.Database{Driver: str, Host: str}, Queue: &internal.Queue{Channel: str, Driver: str, Host: str}, Source: &internal.Source{Driver: str, Host: str}, Vela: &internal.Vela{Address: str, WebAddress: str}}, repo: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, user: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, - }, map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "deployment", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "refs/pull/1/head", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TARGET": "production", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "deployment", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "refs/pull/1/head", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TARGET": "production", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DEPLOYMENT": "production", "VELA_DEPLOYMENT_NUMBER": "0", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_OWNER": "foo", "VELA_REPO_BRANCH": "foo", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"}, + }, map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "deployment", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "refs/pull/1/head", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TARGET": "production", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "deployment", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "refs/pull/1/head", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SENDER_SCM_ID": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TARGET": "production", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DEPLOYMENT": "production", "VELA_DEPLOYMENT_NUMBER": "0", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_OWNER": "foo", "VELA_REPO_BRANCH": "foo", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"}, }, } for _, tt := range tests { diff --git a/database/build/create_test.go b/database/build/create_test.go index 83b6f5a2c..9f9d72399 100644 --- a/database/build/create_test.go +++ b/database/build/create_test.go @@ -42,9 +42,9 @@ func TestBuild_Engine_CreateBuild(t *testing.T) { // ensure the mock expects the query _mock.ExpectQuery(`INSERT INTO "builds" -("repo_id","pipeline_id","number","parent","event","event_action","status","error","enqueued","created","started","finished","deploy","deploy_number","deploy_payload","clone","source","title","message","commit","sender","author","email","link","branch","ref","base_ref","head_ref","host","runtime","distribution","approved_at","approved_by","id") -VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31,$32,$33,$34) RETURNING "id"`). - WithArgs(1, nil, 1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, AnyArgument{}, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 1). +("repo_id","pipeline_id","number","parent","event","event_action","status","error","enqueued","created","started","finished","deploy","deploy_number","deploy_payload","clone","source","title","message","commit","sender","sender_scm_id","author","email","link","branch","ref","base_ref","head_ref","host","runtime","distribution","approved_at","approved_by","id") +VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31,$32,$33,$34,$35) RETURNING "id"`). + WithArgs(1, nil, 1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, AnyArgument{}, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 1). WillReturnRows(_rows) _sqlite := testSqlite(t) diff --git a/database/build/list_dashboard_test.go b/database/build/list_dashboard_test.go index 81a042296..803c19db2 100644 --- a/database/build/list_dashboard_test.go +++ b/database/build/list_dashboard_test.go @@ -50,9 +50,9 @@ func TestBuild_Engine_ListBuildsForDashboardRepo(t *testing.T) { // create expected query result in mock _rows := sqlmock.NewRows( - []string{"id", "repo_id", "pipeline_id", "number", "parent", "event", "event_action", "status", "error", "enqueued", "created", "started", "finished", "deploy", "deploy_payload", "clone", "source", "title", "message", "commit", "sender", "author", "email", "link", "branch", "ref", "base_ref", "head_ref", "host", "runtime", "distribution", "approved_at", "approved_by", "timestamp"}). - AddRow(2, 1, nil, 2, 0, "pull_request", "", "", "", 0, 2, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "main", "", "", "", "", "", "", 0, "", 0). - AddRow(1, 1, nil, 1, 0, "push", "", "", "", 0, 1, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "main", "", "", "", "", "", "", 0, "", 0) + []string{"id", "repo_id", "pipeline_id", "number", "parent", "event", "event_action", "status", "error", "enqueued", "created", "started", "finished", "deploy", "deploy_payload", "clone", "source", "title", "message", "commit", "sender", "sender_scm_id", "author", "email", "link", "branch", "ref", "base_ref", "head_ref", "host", "runtime", "distribution", "approved_at", "approved_by", "timestamp"}). + AddRow(2, 1, nil, 2, 0, "pull_request", "", "", "", 0, 2, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "", "main", "", "", "", "", "", "", 0, "", 0). + AddRow(1, 1, nil, 1, 0, "push", "", "", "", 0, 1, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "", "main", "", "", "", "", "", "", 0, "", 0) // ensure the mock expects the query _mock.ExpectQuery(`SELECT * FROM "builds" WHERE repo_id = $1 AND branch IN ($2) AND event IN ($3,$4) ORDER BY number DESC LIMIT $5`).WithArgs(1, "main", "push", "pull_request", 5).WillReturnRows(_rows) diff --git a/database/build/table.go b/database/build/table.go index fde722a17..8d1ca461e 100644 --- a/database/build/table.go +++ b/database/build/table.go @@ -36,6 +36,7 @@ builds ( message VARCHAR(2000), commit VARCHAR(500), sender VARCHAR(250), + sender_scm_id VARCHAR(250), author VARCHAR(250), email VARCHAR(500), link VARCHAR(1000), @@ -80,6 +81,7 @@ builds ( message TEXT, 'commit' TEXT, sender TEXT, + sender_scm_id TEXT, author TEXT, email TEXT, link TEXT, diff --git a/database/build/update_test.go b/database/build/update_test.go index 149779780..4c0c1137e 100644 --- a/database/build/update_test.go +++ b/database/build/update_test.go @@ -41,9 +41,9 @@ func TestBuild_Engine_UpdateBuild(t *testing.T) { // ensure the mock expects the query _mock.ExpectExec(`UPDATE "builds" -SET "repo_id"=$1,"pipeline_id"=$2,"number"=$3,"parent"=$4,"event"=$5,"event_action"=$6,"status"=$7,"error"=$8,"enqueued"=$9,"created"=$10,"started"=$11,"finished"=$12,"deploy"=$13,"deploy_number"=$14,"deploy_payload"=$15,"clone"=$16,"source"=$17,"title"=$18,"message"=$19,"commit"=$20,"sender"=$21,"author"=$22,"email"=$23,"link"=$24,"branch"=$25,"ref"=$26,"base_ref"=$27,"head_ref"=$28,"host"=$29,"runtime"=$30,"distribution"=$31,"approved_at"=$32,"approved_by"=$33 -WHERE "id" = $34`). - WithArgs(1, nil, 1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, AnyArgument{}, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 1). +SET "repo_id"=$1,"pipeline_id"=$2,"number"=$3,"parent"=$4,"event"=$5,"event_action"=$6,"status"=$7,"error"=$8,"enqueued"=$9,"created"=$10,"started"=$11,"finished"=$12,"deploy"=$13,"deploy_number"=$14,"deploy_payload"=$15,"clone"=$16,"source"=$17,"title"=$18,"message"=$19,"commit"=$20,"sender"=$21,"sender_scm_id"=$22,"author"=$23,"email"=$24,"link"=$25,"branch"=$26,"ref"=$27,"base_ref"=$28,"head_ref"=$29,"host"=$30,"runtime"=$31,"distribution"=$32,"approved_at"=$33,"approved_by"=$34 +WHERE "id" = $35`). + WithArgs(1, nil, 1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, AnyArgument{}, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 1). WillReturnResult(sqlmock.NewResult(1, 1)) _sqlite := testSqlite(t) diff --git a/database/integration_test.go b/database/integration_test.go index e1bdcffd3..8996ca9fe 100644 --- a/database/integration_test.go +++ b/database/integration_test.go @@ -2311,6 +2311,7 @@ func newResources() *Resources { buildOne.SetMessage("First commit...") buildOne.SetCommit("48afb5bdc41ad69bf22588491333f7cf71135163") buildOne.SetSender("OctoKitty") + buildOne.SetSenderSCMID("123") buildOne.SetAuthor("OctoKitty") buildOne.SetEmail("OctoKitty@github.com") buildOne.SetLink("https://example.company.com/github/octocat/1") @@ -2347,6 +2348,7 @@ func newResources() *Resources { buildTwo.SetMessage("Second commit...") buildTwo.SetCommit("48afb5bdc41ad69bf22588491333f7cf71135164") buildTwo.SetSender("OctoKitty") + buildTwo.SetSenderSCMID("123") buildTwo.SetAuthor("OctoKitty") buildTwo.SetEmail("OctoKitty@github.com") buildTwo.SetLink("https://example.company.com/github/octocat/2") diff --git a/database/testutils/api_resources.go b/database/testutils/api_resources.go index 06b34eba8..a418ee363 100644 --- a/database/testutils/api_resources.go +++ b/database/testutils/api_resources.go @@ -36,6 +36,7 @@ func APIBuild() *api.Build { Message: new(string), Commit: new(string), Sender: new(string), + SenderSCMID: new(string), Author: new(string), Email: new(string), Link: new(string), diff --git a/database/types/build.go b/database/types/build.go index 4023836e6..66157607e 100644 --- a/database/types/build.go +++ b/database/types/build.go @@ -54,6 +54,7 @@ type Build struct { Message sql.NullString `sql:"message"` Commit sql.NullString `sql:"commit"` Sender sql.NullString `sql:"sender"` + SenderSCMID sql.NullString `sql:"sender_scm_id"` Author sql.NullString `sql:"author"` Email sql.NullString `sql:"email"` Link sql.NullString `sql:"link"` @@ -209,6 +210,11 @@ func (b *Build) Nullify() *Build { b.Sender.Valid = false } + // check if the SenderSCMID field should be false + if len(b.SenderSCMID.String) == 0 { + b.SenderSCMID.Valid = false + } + // check if the Author field should be false if len(b.Author.String) == 0 { b.Author.Valid = false @@ -299,6 +305,7 @@ func (b *Build) ToAPI() *api.Build { build.SetMessage(b.Message.String) build.SetCommit(b.Commit.String) build.SetSender(b.Sender.String) + build.SetSenderSCMID(b.SenderSCMID.String) build.SetAuthor(b.Author.String) build.SetEmail(b.Email.String) build.SetLink(b.Link.String) @@ -342,6 +349,7 @@ func (b *Build) Validate() error { b.Message = sql.NullString{String: util.Sanitize(b.Message.String), Valid: b.Message.Valid} b.Commit = sql.NullString{String: util.Sanitize(b.Commit.String), Valid: b.Commit.Valid} b.Sender = sql.NullString{String: util.Sanitize(b.Sender.String), Valid: b.Sender.Valid} + b.SenderSCMID = sql.NullString{String: util.Sanitize(b.SenderSCMID.String), Valid: b.SenderSCMID.Valid} b.Author = sql.NullString{String: util.Sanitize(b.Author.String), Valid: b.Author.Valid} b.Email = sql.NullString{String: util.Sanitize(b.Email.String), Valid: b.Email.Valid} b.Link = sql.NullString{String: util.Sanitize(b.Link.String), Valid: b.Link.Valid} @@ -383,6 +391,7 @@ func BuildFromAPI(b *api.Build) *Build { Message: sql.NullString{String: b.GetMessage(), Valid: true}, Commit: sql.NullString{String: b.GetCommit(), Valid: true}, Sender: sql.NullString{String: b.GetSender(), Valid: true}, + SenderSCMID: sql.NullString{String: b.GetSenderSCMID(), Valid: true}, Author: sql.NullString{String: b.GetAuthor(), Valid: true}, Email: sql.NullString{String: b.GetEmail(), Valid: true}, Link: sql.NullString{String: b.GetLink(), Valid: true}, diff --git a/database/types/build_test.go b/database/types/build_test.go index 04a88d923..3eb12f142 100644 --- a/database/types/build_test.go +++ b/database/types/build_test.go @@ -133,6 +133,7 @@ func TestTypes_Build_ToAPI(t *testing.T) { want.SetMessage("First commit...") want.SetCommit("48afb5bdc41ad69bf22588491333f7cf71135163") want.SetSender("OctoKitty") + want.SetSenderSCMID("123") want.SetAuthor("OctoKitty") want.SetEmail("OctoKitty@github.com") want.SetLink("https://example.company.com/github/octocat/1") @@ -228,6 +229,7 @@ func TestTypes_Build_BuildFromAPI(t *testing.T) { b.SetMessage("First commit...") b.SetCommit("48afb5bdc41ad69bf22588491333f7cf71135163") b.SetSender("OctoKitty") + b.SetSenderSCMID("123") b.SetAuthor("OctoKitty") b.SetEmail("OctoKitty@github.com") b.SetLink("https://example.company.com/github/octocat/1") @@ -291,6 +293,7 @@ func testBuild() *Build { Message: sql.NullString{String: "First commit...", Valid: true}, Commit: sql.NullString{String: "48afb5bdc41ad69bf22588491333f7cf71135163", Valid: true}, Sender: sql.NullString{String: "OctoKitty", Valid: true}, + SenderSCMID: sql.NullString{String: "123", Valid: true}, Author: sql.NullString{String: "OctoKitty", Valid: true}, Email: sql.NullString{String: "OctoKitty@github.com", Valid: true}, Link: sql.NullString{String: "https://example.company.com/github/octocat/1", Valid: true}, diff --git a/mock/server/build.go b/mock/server/build.go index 37466b910..36e9234d2 100644 --- a/mock/server/build.go +++ b/mock/server/build.go @@ -85,6 +85,7 @@ const ( "message": "First commit...", "commit": "48afb5bdc41ad69bf22588491333f7cf71135163", "sender": "OctoKitty", + "sender_scm_id": "0", "author": "OctoKitty", "email": "octokitty@github.com", "link": "https://vela.example.company.com/github/octocat/1", diff --git a/router/middleware/build/build_test.go b/router/middleware/build/build_test.go index d6d600b30..e95036c54 100644 --- a/router/middleware/build/build_test.go +++ b/router/middleware/build/build_test.go @@ -75,6 +75,7 @@ func TestBuild_Establish(t *testing.T) { want.SetMessage("") want.SetCommit("") want.SetSender("") + want.SetSenderSCMID("") want.SetAuthor("") want.SetEmail("") want.SetLink("") diff --git a/scm/github/webhook.go b/scm/github/webhook.go index 0d0f1fb50..241f7e6d2 100644 --- a/scm/github/webhook.go +++ b/scm/github/webhook.go @@ -156,6 +156,7 @@ func (c *client) processPushEvent(h *library.Hook, payload *github.PushEvent) (* b.SetMessage(payload.GetHeadCommit().GetMessage()) b.SetCommit(payload.GetHeadCommit().GetID()) b.SetSender(payload.GetSender().GetLogin()) + b.SetSenderSCMID(fmt.Sprint(payload.GetSender().GetID())) b.SetAuthor(payload.GetHeadCommit().GetAuthor().GetLogin()) b.SetEmail(payload.GetHeadCommit().GetAuthor().GetEmail()) b.SetBranch(strings.TrimPrefix(payload.GetRef(), "refs/heads/")) @@ -177,6 +178,11 @@ func (c *client) processPushEvent(h *library.Hook, payload *github.PushEvent) (* // ensure the build sender is set if len(b.GetSender()) == 0 { b.SetSender(payload.GetPusher().GetName()) + // todo: sender_scm_id: + // commit_author/pusher has no ID attached in gh api payload + // - (a) auth with repo token and convert username to scm id + // - (b) error out when payload didnt have a sender + // - (c) dont set an scm id... } // ensure the build email is set @@ -275,7 +281,7 @@ func (c *client) processPREvent(h *library.Hook, payload *github.PullRequestEven r.SetPrivate(repo.GetPrivate()) r.SetTopics(repo.Topics) - // convert payload to library build + // convert payload to api build b := new(api.Build) b.SetEvent(constants.EventPull) b.SetEventAction(payload.GetAction()) @@ -285,6 +291,7 @@ func (c *client) processPREvent(h *library.Hook, payload *github.PullRequestEven b.SetMessage(payload.GetPullRequest().GetTitle()) b.SetCommit(payload.GetPullRequest().GetHead().GetSHA()) b.SetSender(payload.GetSender().GetLogin()) + b.SetSenderSCMID(fmt.Sprint(payload.GetSender().GetID())) b.SetAuthor(payload.GetPullRequest().GetUser().GetLogin()) b.SetEmail(payload.GetPullRequest().GetUser().GetEmail()) b.SetBranch(payload.GetPullRequest().GetBase().GetRef()) @@ -305,6 +312,7 @@ func (c *client) processPREvent(h *library.Hook, payload *github.PullRequestEven // ensure the build sender is set if len(b.GetSender()) == 0 { b.SetSender(payload.GetPullRequest().GetUser().GetLogin()) + b.SetSenderSCMID(fmt.Sprint(payload.GetPullRequest().GetUser().GetID())) } // ensure the build email is set @@ -360,7 +368,7 @@ func (c *client) processDeploymentEvent(h *library.Hook, payload *github.Deploym r.SetPrivate(repo.GetPrivate()) r.SetTopics(repo.Topics) - // convert payload to library build + // convert payload to api build b := new(api.Build) b.SetEvent(constants.EventDeploy) b.SetEventAction(constants.ActionCreated) @@ -372,6 +380,8 @@ func (c *client) processDeploymentEvent(h *library.Hook, payload *github.Deploym b.SetMessage(payload.GetDeployment().GetDescription()) b.SetCommit(payload.GetDeployment().GetSHA()) b.SetSender(payload.GetSender().GetLogin()) + b.SetSenderSCMID(fmt.Sprint(payload.GetSender().GetID())) + b.SetAuthor(payload.GetDeployment().GetCreator().GetLogin()) b.SetEmail(payload.GetDeployment().GetCreator().GetEmail()) b.SetBranch(payload.GetDeployment().GetRef()) @@ -485,6 +495,7 @@ func (c *client) processIssueCommentEvent(h *library.Hook, payload *github.Issue b.SetTitle(fmt.Sprintf("%s received from %s", constants.EventComment, repo.GetHTMLURL())) b.SetMessage(payload.Issue.GetTitle()) b.SetSender(payload.GetSender().GetLogin()) + b.SetSenderSCMID(fmt.Sprint(payload.GetSender().GetID())) b.SetAuthor(payload.GetIssue().GetUser().GetLogin()) b.SetEmail(payload.GetIssue().GetUser().GetEmail()) b.SetRef(fmt.Sprintf("refs/pull/%d/head", payload.GetIssue().GetNumber())) diff --git a/scm/github/webhook_test.go b/scm/github/webhook_test.go index baf8bf722..b2f8f5e98 100644 --- a/scm/github/webhook_test.go +++ b/scm/github/webhook_test.go @@ -75,6 +75,7 @@ func TestGithub_ProcessWebhook_Push(t *testing.T) { wantBuild.SetMessage("Update README.md") wantBuild.SetCommit("9c93babf58917cd6f6f6772b5df2b098f507ff95") wantBuild.SetSender("Codertocat") + wantBuild.SetSenderSCMID("21031067") wantBuild.SetAuthor("Codertocat") wantBuild.SetEmail("21031067+Codertocat@users.noreply.github.com") wantBuild.SetBranch("main") @@ -153,6 +154,7 @@ func TestGithub_ProcessWebhook_Push_NoSender(t *testing.T) { wantBuild.SetMessage("Update README.md") wantBuild.SetCommit("9c93babf58917cd6f6f6772b5df2b098f507ff95") wantBuild.SetSender("Codertocat") + wantBuild.SetSenderSCMID("0") wantBuild.SetAuthor("Codertocat") wantBuild.SetEmail("21031067+Codertocat@users.noreply.github.com") wantBuild.SetBranch("main") @@ -171,8 +173,8 @@ func TestGithub_ProcessWebhook_Push_NoSender(t *testing.T) { t.Errorf("ProcessWebhook returned err: %v", err) } - if !reflect.DeepEqual(got, want) { - t.Errorf("ProcessWebhook is %v, want %v", got, want) + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("ProcessWebhook() mismatch (-want +got):\n%s", diff) } } @@ -230,6 +232,7 @@ func TestGithub_ProcessWebhook_Push_Branch_Delete(t *testing.T) { wantBuild.SetMessage("main branch deleted") wantBuild.SetCommit("d3d9188fc87a6977343e922c128f162a86018d76") wantBuild.SetSender("Codertocat") + wantBuild.SetSenderSCMID("21031067") wantBuild.SetAuthor("Codertocat") wantBuild.SetEmail("21031067+Codertocat@users.noreply.github.com") wantBuild.SetBranch("main") @@ -307,6 +310,7 @@ func TestGithub_ProcessWebhook_Push_Tag_Delete(t *testing.T) { wantBuild.SetMessage("v0.1 tag deleted") wantBuild.SetCommit("d3d9188fc87a6977343e922c128f162a86018d76") wantBuild.SetSender("Codertocat") + wantBuild.SetSenderSCMID("21031067") wantBuild.SetAuthor("Codertocat") wantBuild.SetEmail("21031067+Codertocat@users.noreply.github.com") wantBuild.SetBranch("v0.1") @@ -366,6 +370,7 @@ func TestGithub_ProcessWebhook_PullRequest(t *testing.T) { wantBuild.SetMessage("Update the README with new information") wantBuild.SetCommit("34c5c7793cb3b279e22454cb6750c80560547b3a") wantBuild.SetSender("Codertocat") + wantBuild.SetSenderSCMID("21031067") wantBuild.SetAuthor("Codertocat") wantBuild.SetEmail("") wantBuild.SetBranch("main") @@ -382,6 +387,7 @@ func TestGithub_ProcessWebhook_PullRequest(t *testing.T) { wantBuild2.SetMessage("Update the README with new information") wantBuild2.SetCommit("34c5c7793cb3b279e22454cb6750c80560547b3a") wantBuild2.SetSender("Codertocat") + wantBuild2.SetSenderSCMID("21031067") wantBuild2.SetAuthor("Codertocat") wantBuild2.SetEmail("") wantBuild2.SetBranch("main") @@ -398,6 +404,7 @@ func TestGithub_ProcessWebhook_PullRequest(t *testing.T) { wantBuild3.SetMessage("Update the README with new information") wantBuild3.SetCommit("34c5c7793cb3b279e22454cb6750c80560547b3a") wantBuild3.SetSender("Codertocat") + wantBuild3.SetSenderSCMID("21031067") wantBuild3.SetAuthor("Codertocat") wantBuild3.SetEmail("") wantBuild3.SetBranch("main") @@ -414,6 +421,7 @@ func TestGithub_ProcessWebhook_PullRequest(t *testing.T) { wantBuild4.SetMessage("Update the README with new information") wantBuild4.SetCommit("34c5c7793cb3b279e22454cb6750c80560547b3a") wantBuild4.SetSender("Codertocat") + wantBuild4.SetSenderSCMID("21031067") wantBuild4.SetAuthor("Codertocat") wantBuild4.SetEmail("") wantBuild4.SetBranch("main") @@ -599,6 +607,7 @@ func TestGithub_ProcessWebhook_Deployment(t *testing.T) { wantBuild.SetMessage("") wantBuild.SetCommit("f95f852bd8fca8fcc58a9a2d6c842781e32a215e") wantBuild.SetSender("Codertocat") + wantBuild.SetSenderSCMID("21031067") wantBuild.SetAuthor("Codertocat") wantBuild.SetEmail("") wantBuild.SetBranch("main") @@ -734,6 +743,7 @@ func TestGithub_ProcessWebhook_Deployment_Commit(t *testing.T) { wantBuild.SetMessage("") wantBuild.SetCommit("f95f852bd8fca8fcc58a9a2d6c842781e32a215e") wantBuild.SetSender("Codertocat") + wantBuild.SetSenderSCMID("21031067") wantBuild.SetAuthor("Codertocat") wantBuild.SetEmail("") wantBuild.SetBranch("main") @@ -1002,6 +1012,7 @@ func TestGithub_ProcessWebhook_IssueComment_PR(t *testing.T) { wantBuild.SetTitle("comment received from https://github.com/Codertocat/Hello-World") wantBuild.SetMessage("Update the README with new information") wantBuild.SetSender("Codertocat") + wantBuild.SetSenderSCMID("2172") wantBuild.SetAuthor("Codertocat") wantBuild.SetEmail("") wantBuild.SetRef("refs/pull/1/head") @@ -1022,8 +1033,8 @@ func TestGithub_ProcessWebhook_IssueComment_PR(t *testing.T) { t.Errorf("ProcessWebhook returned err: %v", err) } - if !reflect.DeepEqual(got, want) { - t.Errorf("ProcessWebhook is %v, want %v", got, want) + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("ProcessWebhook() mismatch (-want +got):\n%s", diff) } } From a8958cf837c21727487801240645308606628998 Mon Sep 17 00:00:00 2001 From: davidvader Date: Mon, 3 Jun 2024 15:52:33 -0500 Subject: [PATCH 28/34] enhance: add GetUserID to scm interface --- api/build/restart.go | 19 ++++++++++++++----- cmd/vela-server/schedule.go | 17 ++++++++++++++--- scm/github/user.go | 29 +++++++++++++++++++++++++++++ scm/service.go | 6 ++++++ 4 files changed, 63 insertions(+), 8 deletions(-) create mode 100644 scm/github/user.go diff --git a/api/build/restart.go b/api/build/restart.go index 5d42a9737..3a05ad93b 100644 --- a/api/build/restart.go +++ b/api/build/restart.go @@ -89,6 +89,7 @@ func RestartBuild(c *gin.Context) { o := org.Retrieve(c) r := repo.Retrieve(c) u := user.Retrieve(c) + scm := scm.FromContext(c) ctx := c.Request.Context() entry := fmt.Sprintf("%s/%d", r.GetFullName(), b.GetNumber()) @@ -114,10 +115,18 @@ func RestartBuild(c *gin.Context) { // set sender to the user who initiated the restart and b.SetSender(cl.Subject) - // todo: sender_scm_id: - // vela username is the claims subject - // - (a) auth with repo token and convert username to scm id - // - (b) attach scm id to claims + + // fetch user scm id + senderID, err := scm.GetUserID(ctx, u) + if err != nil { + retErr := fmt.Errorf("unable to get user scm id for %s: %w", u.GetName(), err) + + util.HandleError(c, http.StatusInternalServerError, retErr) + + return + } + + b.SetSenderSCMID(senderID) // parent to the previous build b.SetParent(b.GetNumber()) @@ -138,7 +147,7 @@ func RestartBuild(c *gin.Context) { c, config, database.FromContext(c), - scm.FromContext(c), + scm, compiler.FromContext(c), queue.FromContext(c), ) diff --git a/cmd/vela-server/schedule.go b/cmd/vela-server/schedule.go index 1eb715bd4..6a38998e6 100644 --- a/cmd/vela-server/schedule.go +++ b/cmd/vela-server/schedule.go @@ -179,9 +179,20 @@ func processSchedule(ctx context.Context, s *api.Schedule, settings *settings.Pl b.SetRef(fmt.Sprintf("refs/heads/%s", b.GetBranch())) b.SetRepo(r) b.SetSender(s.GetUpdatedBy()) - // todo: sender_scm_id: - // - (a) auth with repo token and convert username to scm id - // - (b) attach scm user id to schedule updated_by_id (lol) + + // send API call to capture the user for the schedule trigger + u, err := database.GetUserForName(ctx, s.GetUpdatedBy()) + if err != nil { + return fmt.Errorf("unable to get user for name %s: %w", s.GetUpdatedBy(), err) + } + + // fetch user scm id + senderID, err := scm.GetUserID(ctx, u) + if err != nil { + return fmt.Errorf("unable to get user scm id for %s: %w", u.GetName(), err) + } + + b.SetSenderSCMID(senderID) b.SetSource(fmt.Sprintf("%s/tree/%s", url, b.GetBranch())) b.SetStatus(constants.StatusPending) diff --git a/scm/github/user.go b/scm/github/user.go new file mode 100644 index 000000000..848684521 --- /dev/null +++ b/scm/github/user.go @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 + +package github + +import ( + "context" + "fmt" + + api "github.com/go-vela/server/api/types" + "github.com/sirupsen/logrus" +) + +// GetUserID captures the user's scm id. +func (c *client) GetUserID(ctx context.Context, u *api.User) (string, error) { + c.Logger.WithFields(logrus.Fields{ + "user": u.GetName(), + }).Tracef("capturing scm id for %s", u.GetName()) + + // create GitHub OAuth client with user's token + client := c.newClientToken(*u.Token) + + // send API call to capture user + user, _, err := client.Users.Get(ctx, u.GetName()) + if err != nil { + return "", err + } + + return fmt.Sprint(user.GetID()), nil +} diff --git a/scm/service.go b/scm/service.go index b65a529eb..d7a21a78e 100644 --- a/scm/service.go +++ b/scm/service.go @@ -41,6 +41,12 @@ type Service interface { // the OAuth workflow for the session. Login(context.Context, http.ResponseWriter, *http.Request) (string, error) + // User SCM Interface Functions + + // GetUserID defines a function that captures + // the scm user attached to a Vela user. + GetUserID(context.Context, *api.User) (string, error) + // Access SCM Interface Functions // OrgAccess defines a function that captures From 242ebf924dabc02e31f2d4020de7553db35fce4a Mon Sep 17 00:00:00 2001 From: davidvader Date: Mon, 3 Jun 2024 16:37:26 -0500 Subject: [PATCH 29/34] fix: apply missing scm id using scm client lookups --- api/build/restart.go | 6 +++--- api/webhook/post.go | 17 +++++++++++++++++ cmd/vela-server/schedule.go | 6 +++--- scm/github/user.go | 11 +++++------ scm/github/webhook.go | 9 ++------- scm/service.go | 4 ++-- 6 files changed, 32 insertions(+), 21 deletions(-) diff --git a/api/build/restart.go b/api/build/restart.go index 3a05ad93b..898c2e75f 100644 --- a/api/build/restart.go +++ b/api/build/restart.go @@ -116,10 +116,10 @@ func RestartBuild(c *gin.Context) { // set sender to the user who initiated the restart and b.SetSender(cl.Subject) - // fetch user scm id - senderID, err := scm.GetUserID(ctx, u) + // fetch scm user id + senderID, err := scm.GetUserID(ctx, u.GetName(), u.GetToken()) if err != nil { - retErr := fmt.Errorf("unable to get user scm id for %s: %w", u.GetName(), err) + retErr := fmt.Errorf("unable to get SCM user id for %s: %w", u.GetName(), err) util.HandleError(c, http.StatusInternalServerError, retErr) diff --git a/api/webhook/post.go b/api/webhook/post.go index 20600d525..c514943b9 100644 --- a/api/webhook/post.go +++ b/api/webhook/post.go @@ -207,6 +207,23 @@ func PostWebhook(c *gin.Context) { return } + // attach a sender SCM id if the webhook payload from the SCM has no sender id + if len(b.GetSenderSCMID()) == 0 || b.GetSenderSCMID() == "0" { + // fetch scm user id for pusher + senderID, err := scm.FromContext(c).GetUserID(ctx, b.GetSender(), repo.GetOwner().GetToken()) + if err != nil { + retErr := fmt.Errorf("unable to assign sender SCM id: %w", err) + util.HandleError(c, http.StatusBadRequest, retErr) + + h.SetStatus(constants.StatusFailure) + h.SetError(retErr.Error()) + + return + } + + b.SetSenderSCMID(senderID) + } + // set the RepoID fields b.SetRepo(repo) h.SetRepoID(repo.GetID()) diff --git a/cmd/vela-server/schedule.go b/cmd/vela-server/schedule.go index 6a38998e6..e7e0df759 100644 --- a/cmd/vela-server/schedule.go +++ b/cmd/vela-server/schedule.go @@ -186,10 +186,10 @@ func processSchedule(ctx context.Context, s *api.Schedule, settings *settings.Pl return fmt.Errorf("unable to get user for name %s: %w", s.GetUpdatedBy(), err) } - // fetch user scm id - senderID, err := scm.GetUserID(ctx, u) + // fetch scm user id + senderID, err := scm.GetUserID(ctx, u.GetName(), u.GetToken()) if err != nil { - return fmt.Errorf("unable to get user scm id for %s: %w", u.GetName(), err) + return fmt.Errorf("unable to get SCM user id for %s: %w", u.GetName(), err) } b.SetSenderSCMID(senderID) diff --git a/scm/github/user.go b/scm/github/user.go index 848684521..e50cc19ff 100644 --- a/scm/github/user.go +++ b/scm/github/user.go @@ -6,21 +6,20 @@ import ( "context" "fmt" - api "github.com/go-vela/server/api/types" "github.com/sirupsen/logrus" ) // GetUserID captures the user's scm id. -func (c *client) GetUserID(ctx context.Context, u *api.User) (string, error) { +func (c *client) GetUserID(ctx context.Context, name string, token string) (string, error) { c.Logger.WithFields(logrus.Fields{ - "user": u.GetName(), - }).Tracef("capturing scm id for %s", u.GetName()) + "user": name, + }).Tracef("capturing SCM user id for %s", name) // create GitHub OAuth client with user's token - client := c.newClientToken(*u.Token) + client := c.newClientToken(token) // send API call to capture user - user, _, err := client.Users.Get(ctx, u.GetName()) + user, _, err := client.Users.Get(ctx, name) if err != nil { return "", err } diff --git a/scm/github/webhook.go b/scm/github/webhook.go index 241f7e6d2..3092ae63e 100644 --- a/scm/github/webhook.go +++ b/scm/github/webhook.go @@ -69,7 +69,7 @@ func (c *client) ProcessWebhook(ctx context.Context, request *http.Request) (*in // process the event from the webhook switch event := event.(type) { case *github.PushEvent: - return c.processPushEvent(h, event) + return c.processPushEvent(ctx, h, event) case *github.PullRequestEvent: return c.processPREvent(h, event) case *github.DeploymentEvent: @@ -128,7 +128,7 @@ func (c *client) RedeliverWebhook(ctx context.Context, u *api.User, r *api.Repo, } // processPushEvent is a helper function to process the push event. -func (c *client) processPushEvent(h *library.Hook, payload *github.PushEvent) (*internal.Webhook, error) { +func (c *client) processPushEvent(ctx context.Context, h *library.Hook, payload *github.PushEvent) (*internal.Webhook, error) { c.Logger.WithFields(logrus.Fields{ "org": payload.GetRepo().GetOwner().GetLogin(), "repo": payload.GetRepo().GetName(), @@ -178,11 +178,6 @@ func (c *client) processPushEvent(h *library.Hook, payload *github.PushEvent) (* // ensure the build sender is set if len(b.GetSender()) == 0 { b.SetSender(payload.GetPusher().GetName()) - // todo: sender_scm_id: - // commit_author/pusher has no ID attached in gh api payload - // - (a) auth with repo token and convert username to scm id - // - (b) error out when payload didnt have a sender - // - (c) dont set an scm id... } // ensure the build email is set diff --git a/scm/service.go b/scm/service.go index d7a21a78e..88bb5ecf3 100644 --- a/scm/service.go +++ b/scm/service.go @@ -44,8 +44,8 @@ type Service interface { // User SCM Interface Functions // GetUserID defines a function that captures - // the scm user attached to a Vela user. - GetUserID(context.Context, *api.User) (string, error) + // the scm user id attached to the username. + GetUserID(context.Context, string, string) (string, error) // Access SCM Interface Functions From 7625a96bd89e340b6595cf99d1ee6e60b26a508b Mon Sep 17 00:00:00 2001 From: davidvader Date: Tue, 4 Jun 2024 10:05:12 -0500 Subject: [PATCH 30/34] chore: verbose comment on fallback user fetch --- api/webhook/post.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/webhook/post.go b/api/webhook/post.go index c514943b9..50d69d315 100644 --- a/api/webhook/post.go +++ b/api/webhook/post.go @@ -208,6 +208,8 @@ func PostWebhook(c *gin.Context) { } // attach a sender SCM id if the webhook payload from the SCM has no sender id + // the code in ProcessWebhook implies that the sender may not always be present + // fallbacks like pusher/commit_author do not have an id if len(b.GetSenderSCMID()) == 0 || b.GetSenderSCMID() == "0" { // fetch scm user id for pusher senderID, err := scm.FromContext(c).GetUserID(ctx, b.GetSender(), repo.GetOwner().GetToken()) From 82c857805600a063a4efaf9f83bc782fa89d5e82 Mon Sep 17 00:00:00 2001 From: davidvader Date: Tue, 4 Jun 2024 10:16:07 -0500 Subject: [PATCH 31/34] chore: comment typo --- api/build/restart.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/build/restart.go b/api/build/restart.go index 898c2e75f..a02d368b4 100644 --- a/api/build/restart.go +++ b/api/build/restart.go @@ -113,7 +113,7 @@ func RestartBuild(c *gin.Context) { return } - // set sender to the user who initiated the restart and + // set sender to the user who initiated the restart b.SetSender(cl.Subject) // fetch scm user id From 167c95bb0347fe019f65466df94fac99cd612c9b Mon Sep 17 00:00:00 2001 From: davidvader Date: Tue, 4 Jun 2024 10:28:05 -0500 Subject: [PATCH 32/34] enhance: use repo owner token in schedule processing --- cmd/vela-server/schedule.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/cmd/vela-server/schedule.go b/cmd/vela-server/schedule.go index e7e0df759..3709df9a8 100644 --- a/cmd/vela-server/schedule.go +++ b/cmd/vela-server/schedule.go @@ -180,16 +180,10 @@ func processSchedule(ctx context.Context, s *api.Schedule, settings *settings.Pl b.SetRepo(r) b.SetSender(s.GetUpdatedBy()) - // send API call to capture the user for the schedule trigger - u, err := database.GetUserForName(ctx, s.GetUpdatedBy()) - if err != nil { - return fmt.Errorf("unable to get user for name %s: %w", s.GetUpdatedBy(), err) - } - // fetch scm user id - senderID, err := scm.GetUserID(ctx, u.GetName(), u.GetToken()) + senderID, err := scm.GetUserID(ctx, s.GetUpdatedBy(), r.GetOwner().GetToken()) if err != nil { - return fmt.Errorf("unable to get SCM user id for %s: %w", u.GetName(), err) + return fmt.Errorf("unable to get SCM user id for %s: %w", s.GetUpdatedBy(), err) } b.SetSenderSCMID(senderID) From 37d5dfccb01b97cd03a502081ca25956bf7998e0 Mon Sep 17 00:00:00 2001 From: davidvader Date: Tue, 4 Jun 2024 10:33:04 -0500 Subject: [PATCH 33/34] enhance: use repo owner token in restart build --- api/build/restart.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/build/restart.go b/api/build/restart.go index a02d368b4..f8798fbb6 100644 --- a/api/build/restart.go +++ b/api/build/restart.go @@ -117,7 +117,7 @@ func RestartBuild(c *gin.Context) { b.SetSender(cl.Subject) // fetch scm user id - senderID, err := scm.GetUserID(ctx, u.GetName(), u.GetToken()) + senderID, err := scm.GetUserID(ctx, u.GetName(), r.GetOwner().GetToken()) if err != nil { retErr := fmt.Errorf("unable to get SCM user id for %s: %w", u.GetName(), err) From 4d69c9bdd11cb07d28c6042c4fcba0fd3e3a78e0 Mon Sep 17 00:00:00 2001 From: davidvader Date: Tue, 4 Jun 2024 11:38:58 -0500 Subject: [PATCH 34/34] enhance: change claims actor_id to actor_scm_id --- api/oi_config.go | 2 +- api/types/oidc.go | 2 +- internal/token/mint.go | 11 ++--------- mock/server/authentication.go | 2 +- 4 files changed, 5 insertions(+), 12 deletions(-) diff --git a/api/oi_config.go b/api/oi_config.go index f174a4b32..02edeb939 100644 --- a/api/oi_config.go +++ b/api/oi_config.go @@ -46,7 +46,7 @@ func GetOpenIDConfig(c *gin.Context) { "repo", "token_type", "actor", - "actor_id", + "actor_scm_id", "commands", "image", "request", diff --git a/api/types/oidc.go b/api/types/oidc.go index fa5f45cbb..7598351f6 100644 --- a/api/types/oidc.go +++ b/api/types/oidc.go @@ -23,7 +23,7 @@ type OpenIDClaims struct { BuildNumber int `json:"build_number,omitempty"` BuildID int64 `json:"build_id,omitempty"` Actor string `json:"actor,omitempty"` - ActorID string `json:"actor_id,omitempty"` + ActorSCMID string `json:"actor_scm_id,omitempty"` Repo string `json:"repo,omitempty"` TokenType string `json:"token_type,omitempty"` Image string `json:"image,omitempty"` diff --git a/internal/token/mint.go b/internal/token/mint.go index ee98724c7..1cbb65663 100644 --- a/internal/token/mint.go +++ b/internal/token/mint.go @@ -6,7 +6,6 @@ import ( "context" "errors" "fmt" - "strconv" "time" "github.com/golang-jwt/jwt/v5" @@ -151,15 +150,9 @@ func (tm *Manager) MintIDToken(ctx context.Context, mto *MintTokenOpts, db datab return "", errors.New("missing build sender for ID token") } - // retrieve the user id for the actor - u, err := db.GetUserForName(ctx, mto.Build.GetSender()) - if err != nil { - return "", errors.New("unable to retrieve build sender user ID for ID token") - } - // set claims based on input claims.Actor = mto.Build.GetSender() - claims.ActorID = strconv.Itoa(int(u.GetID())) + claims.ActorSCMID = mto.Build.GetSenderSCMID() claims.BuildNumber = mto.Build.GetNumber() claims.BuildID = mto.Build.GetID() claims.Repo = mto.Repo @@ -181,7 +174,7 @@ func (tm *Manager) MintIDToken(ctx context.Context, mto *MintTokenOpts, db datab tk := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) // verify key is active in the database before signing - _, err = db.GetActiveJWK(ctx, tm.RSAKeySet.KID) + _, err := db.GetActiveJWK(ctx, tm.RSAKeySet.KID) if err != nil { if !errors.Is(err, gorm.ErrRecordNotFound) { return "", fmt.Errorf("unable to get active public key: %w", err) diff --git a/mock/server/authentication.go b/mock/server/authentication.go index dd2405a24..6e9f4512b 100644 --- a/mock/server/authentication.go +++ b/mock/server/authentication.go @@ -37,7 +37,7 @@ const ( "repo", "token_type", "actor", - "actor_id", + "actor_scm_id", "commands", "image", "request"