From 0136e4739d9ff77d3c94c2ff2bf9112d200f7044 Mon Sep 17 00:00:00 2001 From: Chmouel Boudjnah Date: Wed, 3 Jun 2026 13:37:31 +0200 Subject: [PATCH 1/6] fix(security): backport app token safeguards Validate GitHub App webhook payloads before minting tokens and avoid trusting a forged enterprise host header. Also include the upstream App token scoping and remote-resource deep-copy fixes needed for the same supported release stream. Signed-off-by: Chmouel Boudjnah --- pkg/adapter/incoming.go | 1 + pkg/adapter/sinker.go | 7 +- pkg/provider/github/app/token.go | 10 +- pkg/provider/github/app/token_test.go | 76 +++++++++++- pkg/provider/github/github.go | 28 +++++ pkg/provider/github/parse_payload.go | 143 ++++++++++++++++++++-- pkg/provider/github/parse_payload_test.go | 107 ++++++++++++---- pkg/resolve/remote.go | 8 +- pkg/resolve/remote_test.go | 78 ++++++++++++ 9 files changed, 411 insertions(+), 47 deletions(-) diff --git a/pkg/adapter/incoming.go b/pkg/adapter/incoming.go index 31d1a9e17c..350a3ffb09 100644 --- a/pkg/adapter/incoming.go +++ b/pkg/adapter/incoming.go @@ -183,6 +183,7 @@ func (l *listener) detectIncoming(ctx context.Context, req *http.Request, payloa return false, nil, err } l.event.Provider.URL = enterpriseURL + l.event.GHEURL = enterpriseURL l.event.Provider.Token = token l.event.InstallationID = installationID // Github app is not installed for provided repository url diff --git a/pkg/adapter/sinker.go b/pkg/adapter/sinker.go index 98099a0e53..98ded24def 100644 --- a/pkg/adapter/sinker.go +++ b/pkg/adapter/sinker.go @@ -67,12 +67,7 @@ func (s *sinker) processEventPayload(ctx context.Context, request *http.Request) } func (s *sinker) processEvent(ctx context.Context, request *http.Request) error { - if s.event.EventType == "incoming" { - if request.Header.Get("X-GitHub-Enterprise-Host") != "" { - s.event.Provider.URL = request.Header.Get("X-GitHub-Enterprise-Host") - s.event.GHEURL = request.Header.Get("X-GitHub-Enterprise-Host") - } - } else { + if s.event.EventType != "incoming" { if err := s.processEventPayload(ctx, request); err != nil { return err } diff --git a/pkg/provider/github/app/token.go b/pkg/provider/github/app/token.go index 41d3b98d93..034081ea4c 100644 --- a/pkg/provider/github/app/token.go +++ b/pkg/provider/github/app/token.go @@ -7,6 +7,7 @@ import ( "net/url" "strings" + "github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/keys" "github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/v1alpha1" "github.com/openshift-pipelines/pipelines-as-code/pkg/params" "github.com/openshift-pipelines/pipelines-as-code/pkg/provider/github" @@ -65,9 +66,12 @@ func (ip *Install) GetAndUpdateInstallationID(ctx context.Context) (string, stri return "", "", 0, fmt.Errorf("github client APIURL is nil") } apiURL := *ip.ghClient.APIURL - enterpriseHost := ip.request.Header.Get("X-GitHub-Enterprise-Host") - if enterpriseHost != "" { - apiURL = fmt.Sprintf("https://%s/api/v3", strings.TrimSuffix(enterpriseHost, "/")) + enterpriseHost := "" + if repoURL.Host != "" && repoURL.Host != "github.com" { + enterpriseHost = repoURL.Host + if apiURL == keys.PublicGithubAPIURL { + apiURL = fmt.Sprintf("https://%s/api/v3", strings.TrimSuffix(enterpriseHost, "/")) + } } client, _, _ := github.MakeClient(ctx, apiURL, jwtToken) diff --git a/pkg/provider/github/app/token_test.go b/pkg/provider/github/app/token_test.go index 6f1c3c9d5b..381049e96f 100644 --- a/pkg/provider/github/app/token_test.go +++ b/pkg/provider/github/app/token_test.go @@ -266,6 +266,76 @@ func Test_GetAndUpdateInstallationID(t *testing.T) { assert.Equal(t, token, wantToken) } +func TestGetAndUpdateInstallationIDIgnoresEnterpriseHostHeader(t *testing.T) { + tdata := testclient.Data{ + Namespaces: []*corev1.Namespace{testNamespace}, + Secret: []*corev1.Secret{validSecret}, + } + wantToken := "GOODTOKEN" + wantID := int64(120) + orgName := "org" + repoName := "repo" + + fakeghclient, mux, serverURL, teardown := ghtesthelper.SetupGH() + defer teardown() + + ctx, _ := rtesting.SetupFakeContext(t) + stdata, _ := testclient.SeedTestData(t, ctx, tdata) + logger, _ := logger.GetLogger() + run := ¶ms.Run{ + Clients: clients.Clients{ + Log: logger, + PipelineAsCode: stdata.PipelineAsCode, + Kube: stdata.Kube, + }, + Info: info.Info{ + Pac: &info.PacOpts{ + Settings: settings.Settings{}, + }, + Controller: &info.ControllerInfo{Secret: validSecret.GetName()}, + }, + } + ctx = info.StoreCurrentControllerName(ctx, "default") + ctx = info.StoreNS(ctx, testNamespace.GetName()) + + gh := github.New() + gh.Run = run + jwtToken, err := gh.GenerateJWT(ctx, testNamespace.GetName(), stdata.Kube) + assert.NilError(t, err) + + mux.HandleFunc(fmt.Sprintf("/repos/%s/%s/installation", orgName, repoName), func(w http.ResponseWriter, _ *http.Request) { + _, _ = fmt.Fprintf(w, `{"id": %d}`, wantID) + }) + mux.HandleFunc(fmt.Sprintf("/app/installations/%d/access_tokens", wantID), func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r) + w.Header().Set("Authorization", "Bearer "+jwtToken) + w.Header().Set("Accept", "application/vnd.github+json") + _, _ = fmt.Fprintf(w, `{"token": "%s"}`, wantToken) + }) + + repo := &v1alpha1.Repository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "repo", + }, + Spec: v1alpha1.RepositorySpec{ + URL: fmt.Sprintf("https://github.com/%s/%s", orgName, repoName), + }, + } + + gprovider := &github.Provider{APIURL: &serverURL, Run: run} + gprovider.SetGithubClient(fakeghclient) + t.Setenv("PAC_GIT_PROVIDER_TOKEN_APIURL", serverURL+"/api/v3") + + req := httptest.NewRequestWithContext(ctx, http.MethodGet, "http://localhost", strings.NewReader("")) + req.Header.Set("X-GitHub-Enterprise-Host", "127.0.0.1:1") + ip := NewInstallation(req, run, repo, gprovider, testNamespace.GetName()) + enterpriseURL, token, installationID, err := ip.GetAndUpdateInstallationID(ctx) + assert.NilError(t, err) + assert.Equal(t, enterpriseURL, "") + assert.Equal(t, installationID, wantID) + assert.Equal(t, token, wantToken) +} + func testMethod(t *testing.T, r *http.Request) { want := "POST" t.Helper() @@ -293,6 +363,7 @@ func TestGetAndUpdateInstallationID_Fallbacks(t *testing.T) { wantErr bool wantInstallationID int64 wantToken string + wantEnterpriseHost string skip bool expectedErrorString string }{ @@ -316,6 +387,7 @@ func TestGetAndUpdateInstallationID_Fallbacks(t *testing.T) { wantErr: false, wantInstallationID: orgID, wantToken: wantToken, + wantEnterpriseHost: "matched", }, { name: "repo and org installation fail, user installation succeeds", @@ -340,6 +412,7 @@ func TestGetAndUpdateInstallationID_Fallbacks(t *testing.T) { wantErr: false, wantInstallationID: userID, wantToken: wantToken, + wantEnterpriseHost: "matched", }, { name: "all installations fail", @@ -416,7 +489,7 @@ func TestGetAndUpdateInstallationID_Fallbacks(t *testing.T) { t.Setenv("PAC_GIT_PROVIDER_TOKEN_APIURL", serverURL) ip := NewInstallation(httptest.NewRequestWithContext(ctx, http.MethodGet, "http://localhost", strings.NewReader("")), run, repo, gprovider, testNamespace.GetName()) - _, token, installationID, err := ip.GetAndUpdateInstallationID(ctx) + enterpriseHost, token, installationID, err := ip.GetAndUpdateInstallationID(ctx) if tt.wantErr { assert.Assert(t, err != nil) @@ -429,6 +502,7 @@ func TestGetAndUpdateInstallationID_Fallbacks(t *testing.T) { assert.NilError(t, err) assert.Equal(t, installationID, tt.wantInstallationID) assert.Equal(t, token, tt.wantToken) + assert.Equal(t, enterpriseHost, tt.wantEnterpriseHost) }) } } diff --git a/pkg/provider/github/github.go b/pkg/provider/github/github.go index 56802acc60..92bc87d9f1 100644 --- a/pkg/provider/github/github.go +++ b/pkg/provider/github/github.go @@ -327,6 +327,34 @@ func (v *Provider) SetClient(ctx context.Context, run *params.Run, event *info.E } } + // Handle GitHub App token scoping for both global and repo-level configuration + if event.InstallationID > 0 { + token := "" + if repo != nil && v.pacInfo != nil && v.Logger != nil && v.eventEmitter != nil { + v.Logger.Debugf("setupAuthenticatedClient: scoping github app token") + scopedToken, err := ScopeTokenToListOfRepos(ctx, v, v.pacInfo, repo, run, event, v.eventEmitter, v.Logger) + if err != nil { + return fmt.Errorf("failed to scope token: %w", err) + } + token = scopedToken + } + // If Global and Repo level configurations are not provided then lets not override the provider token. + if token != "" { + event.Provider.Token = token + } else if len(v.RepositoryIDs) > 0 { + // We need to keep the token unscoped until ScopeTokenToListOfRepos so that CreateToken can + // look up the extra repos from the configmap. + // Token is scoped to only the calling repo if no additional scoping repos are configured + // so that no unwanted remote tasks are executed. + ns := info.GetNS(ctx) + scopedToken, err := v.GetAppToken(ctx, run.Clients.Kube, event.Provider.URL, event.InstallationID, ns) + if err != nil { + return fmt.Errorf("failed to scope token to triggering repository: %w", err) + } + event.Provider.Token = scopedToken + } + } + return nil } diff --git a/pkg/provider/github/parse_payload.go b/pkg/provider/github/parse_payload.go index 4c63502906..8d5037dfb0 100644 --- a/pkg/provider/github/parse_payload.go +++ b/pkg/provider/github/parse_payload.go @@ -1,11 +1,13 @@ package github import ( + "bytes" "context" "encoding/json" "errors" "fmt" "net/http" + "net/url" "os" "path" "strconv" @@ -29,6 +31,10 @@ const ( maxRetriesForMergeCommit = 3 githubNoreplyEmail = "noreply@github.com" githubWebFlowUser = "web-flow" + + githubAppTokenMintBlockedLog = "[SECURITY] Blocked GitHub App token minting before webhook signature validation completed" + githubAppTokenExfiltrationBlockedLog = "[SECURITY][CRITICAL] Averted GitHub App credential exfiltration attempt before token minting" + controllerWebhookSecretKey = "webhook.secret" ) // GetAppIDAndPrivateKey retrieves the GitHub application ID and private key from a secret in the specified namespace. @@ -97,6 +103,35 @@ func (v *Provider) GetAppToken(ctx context.Context, kube kubernetes.Interface, g return token, err } +func validateAppWebhookSignature(ctx context.Context, run *params.Run, event *info.Event) error { + signature := event.Request.Header.Get(github.SHA256SignatureHeader) + if signature == "" { + signature = event.Request.Header.Get(github.SHA1SignatureHeader) + } + if signature == "" || signature == "sha1=" { + return fmt.Errorf("no signature has been detected, for security reason we are not allowing webhooks that has no secret") + } + + var err error + event.Provider.WebhookSecret, err = getCurrentNSWebhookSecret(ctx, run) + if err != nil { + return err + } + if event.Provider.WebhookSecret == "" { + return fmt.Errorf("no webhook secret has been set in controller secret") + } + return github.ValidateSignature(signature, event.Request.Payload, []byte(event.Provider.WebhookSecret)) +} + +func getCurrentNSWebhookSecret(ctx context.Context, run *params.Run) (string, error) { + ns := info.GetNS(ctx) + secret, err := run.Clients.Kube.CoreV1().Secrets(ns).Get(ctx, run.Info.Controller.Secret, metav1.GetOptions{}) + if err != nil { + return "", err + } + return strings.TrimSpace(string(secret.Data[controllerWebhookSecretKey])), nil +} + func (v *Provider) parseEventType(request *http.Request, event *info.Event) error { event.EventType = request.Header.Get("X-GitHub-Event") if event.EventType == "" { @@ -118,18 +153,91 @@ type Payload struct { Installation struct { ID *int64 `json:"id"` } `json:"installation"` + Repository struct { + HTMLURL string `json:"html_url"` + ID *int64 `json:"id"` + } `json:"repository"` } -func getInstallationIDFromPayload(payload string) (int64, error) { +func getInstallationAndRepoIDFromPayload(payload string) (int64, int64, error) { var data Payload err := json.Unmarshal([]byte(payload), &data) if err != nil { - return -1, err + return -1, -1, err } + + var installationID int64 = -1 + var repoID int64 = -1 if data.Installation.ID != nil { - return *data.Installation.ID, nil + installationID = *data.Installation.ID + } + + if data.Repository.ID != nil { + repoID = *data.Repository.ID + } + + return installationID, repoID, nil +} + +func validateEnterpriseHostMatchesPayload(gheURL, payload string) error { + if gheURL == "" { + return nil } - return -1, nil + if !strings.HasPrefix(gheURL, "https://") && !strings.HasPrefix(gheURL, "http://") { + gheURL = "https://" + gheURL + } + enterpriseURL, err := url.Parse(gheURL) + if err != nil || enterpriseURL.Host == "" { + return fmt.Errorf("invalid X-GitHub-Enterprise-Host header") + } + + var data Payload + if err := json.Unmarshal([]byte(payload), &data); err != nil { + return err + } + if data.Repository.HTMLURL == "" { + return fmt.Errorf("repository HTML URL is missing in payload, cannot validate enterprise host") + } + repoURL, err := url.Parse(data.Repository.HTMLURL) + if err != nil || repoURL.Host == "" { + return fmt.Errorf("invalid repository URL in GitHub payload") + } + if !strings.EqualFold(enterpriseURL.Host, repoURL.Host) { + return fmt.Errorf("github enterprise host %q does not match repository host %q", enterpriseURL.Host, repoURL.Host) + } + return nil +} + +func (v *Provider) logBlockedGitHubAppTokenMint(request *http.Request, event *info.Event, installationID int64, reason string, err error) { + if v.Logger == nil { + return + } + v.Logger.Errorw(githubAppTokenExfiltrationBlockedLog, + "severity", "critical", + "security-impact", "github-app-jwt-exfiltration-blocked", + "reason", reason, + "error", err, + "event-type", event.EventType, + "installation-id", installationID, + "github-enterprise-host-present", request.Header.Get("X-GitHub-Enterprise-Host") != "", + "remote-addr", request.RemoteAddr, + ) +} + +func (v *Provider) logGitHubAppTokenMintValidationFailure(request *http.Request, event *info.Event, installationID int64, reason string, err error) { + if v.Logger == nil { + return + } + v.Logger.Warnw(githubAppTokenMintBlockedLog, + "severity", "warning", + "security-impact", "github-app-token-mint-blocked", + "reason", reason, + "error", err, + "event-type", event.EventType, + "installation-id", installationID, + "github-enterprise-host-present", request.Header.Get("X-GitHub-Enterprise-Host") != "", + "remote-addr", request.RemoteAddr, + ) } // ParsePayload will parse the payload and return the event @@ -146,32 +254,46 @@ func getInstallationIDFromPayload(payload string) (int64, error) { // app on a github org which has a mixed of private and public repos and some of // the public users should not have access to the private repos. // -// Another thing: The payload is protected by the webhook signature so it cannot be tempered but even tho if it's -// tempered with and somehow a malicious user found the token and set their own github endpoint to hijack and -// exfiltrate the token, it would fail since the jwt token generation will fail, so we are safe here. -// a bit too far fetched but i don't see any way we can exploit this. +// Validate the webhook signature before generating an app token because the token +// request signs a GitHub App JWT locally and sends it to the selected API host. func (v *Provider) ParsePayload(ctx context.Context, run *params.Run, request *http.Request, payload string) (*info.Event, error) { // ParsePayload is really happening before SetClient so let's set this at first here. // Only apply for GitHub provider since we do fancy token creation at payload parsing v.Run = run event := info.NewEvent() + event.Request = &info.Request{ + Header: request.Header, + Payload: bytes.TrimSpace([]byte(payload)), + } systemNS := info.GetNS(ctx) if err := v.parseEventType(request, event); err != nil { return nil, err } - installationIDFrompayload, err := getInstallationIDFromPayload(payload) + installationIDFrompayload, repoIDFromPayload, err := getInstallationAndRepoIDFromPayload(payload) if err != nil { return nil, err } if installationIDFrompayload != -1 { var err error + if err := validateAppWebhookSignature(ctx, run, event); err != nil { + v.logGitHubAppTokenMintValidationFailure(request, event, installationIDFrompayload, "webhook-signature-validation-failed", err) + return nil, err + } + if err := validateEnterpriseHostMatchesPayload(event.Provider.URL, payload); err != nil { + v.logBlockedGitHubAppTokenMint(request, event, installationIDFrompayload, "enterprise-host-validation-failed", err) + return nil, err + } // TODO: move this out of here when we move al config inside context if event.Provider.Token, err = v.GetAppToken(ctx, run.Clients.Kube, event.Provider.URL, installationIDFrompayload, systemNS); err != nil { return nil, err } } + if repoIDFromPayload > 0 { + v.RepositoryIDs = []int64{repoIDFromPayload} + } + eventInt, err := github.ParseWebHook(event.EventType, []byte(payload)) if err != nil { return nil, err @@ -479,6 +601,7 @@ func (v *Provider) handleReRequestEvent(ctx context.Context, event *github.Check // fine because you can't do a rereq without being a github owner? runevent.Sender = event.GetSender().GetLogin() v.userType = event.GetSender().GetType() + v.RepositoryIDs = []int64{event.GetRepo().GetID()} return runevent, nil } runevent.PullRequestNumber = event.GetCheckRun().GetCheckSuite().PullRequests[0].GetNumber() @@ -510,6 +633,7 @@ func (v *Provider) handleCheckSuites(ctx context.Context, event *github.CheckSui // fine because you can't do a rereq without being a github owner? runevent.Sender = event.GetSender().GetLogin() v.userType = event.GetSender().GetType() + v.RepositoryIDs = []int64{event.GetRepo().GetID()} return runevent, nil // return nil, fmt.Errorf("check suite event is not supported for push events") } @@ -623,6 +747,7 @@ func (v *Provider) handleCommitCommentEvent(ctx context.Context, event *github.C runevent.TriggerTarget = triggertype.Push opscomments.SetEventTypeAndTargetPR(runevent, event.GetComment().GetBody()) v.userType = event.GetSender().GetType() + v.RepositoryIDs = []int64{event.GetRepo().GetID()} defaultBranch := event.GetRepo().GetDefaultBranch() // Set Event.Repository.DefaultBranch as default branch to runevent.HeadBranch, runevent.BaseBranch diff --git a/pkg/provider/github/parse_payload_test.go b/pkg/provider/github/parse_payload_test.go index 86f3d9446a..6cf0e7ffb7 100644 --- a/pkg/provider/github/parse_payload_test.go +++ b/pkg/provider/github/parse_payload_test.go @@ -2,6 +2,9 @@ package github import ( "context" + "crypto/hmac" + "crypto/sha256" + "encoding/hex" "encoding/json" "fmt" "net/http" @@ -9,7 +12,6 @@ import ( "github.com/google/go-github/v81/github" "gotest.tools/v3/assert" - "gotest.tools/v3/env" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" rtesting "knative.dev/pkg/reconciler/testing" @@ -70,6 +72,12 @@ var samplePRevent = github.PullRequestEvent{ Repo: sampleRepo, } +func githubSHA256Signature(secret string, payload []byte) string { + hm := hmac.New(sha256.New, []byte(secret)) + hm.Write(payload) + return "sha256=" + hex.EncodeToString(hm.Sum(nil)) +} + var samplePR = github.PullRequest{ Number: github.Ptr(54321), Head: &github.PullRequestBranch{ @@ -1200,6 +1208,7 @@ func TestAppTokenGeneration(t *testing.T) { secretName := "pipelines-as-code-secret" ctx, _ := rtesting.SetupFakeContext(t) + webhookSecret := "webhook-secret" vaildSecret, _ := testclient.SeedTestData(t, ctx, testclient.Data{ Secret: []*corev1.Secret{ { @@ -1210,6 +1219,7 @@ func TestAppTokenGeneration(t *testing.T) { Data: map[string][]byte{ "github-application-id": []byte("12345"), "github-private-key": []byte(fakePrivateKey), + "webhook.secret": []byte(webhookSecret), }, }, }, @@ -1226,6 +1236,7 @@ func TestAppTokenGeneration(t *testing.T) { Data: map[string][]byte{ "github-application-id": []byte("abcd"), "github-private-key": []byte(fakePrivateKey), + "webhook.secret": []byte(webhookSecret), }, }, }, @@ -1242,26 +1253,59 @@ func TestAppTokenGeneration(t *testing.T) { Data: map[string][]byte{ "github-application-id": []byte("12345"), "github-private-key": []byte("invalid-key"), + "webhook.secret": []byte(webhookSecret), }, }, }, }) tests := []struct { - ctx context.Context - ctxNS string - name string - wantErrSubst string - nilClient bool - seedData testclient.Clients - envs map[string]string + ctx context.Context + ctxNS string + name string + wantErrSubst string + nilClient bool + seedData testclient.Clients + omitSignature bool + enterpriseHost string + payload string + wantLogMessage string }{ { - name: "secret not found", - ctx: ctxNoSecret, - ctxNS: "foo", - seedData: noSecret, - wantErrSubst: `secrets "pipelines-as-code-secret" not found`, + name: "secret not found", + ctx: ctxNoSecret, + ctxNS: "foo", + seedData: noSecret, + wantErrSubst: `secrets "pipelines-as-code-secret" not found`, + wantLogMessage: githubAppTokenMintBlockedLog, + }, + { + ctx: ctx, + name: "missing webhook signature", + ctxNS: testNamespace, + seedData: vaildSecret, + omitSignature: true, + wantErrSubst: "no signature has been detected", + wantLogMessage: githubAppTokenMintBlockedLog, + }, + { + ctx: ctx, + name: "enterprise host does not match signed repository payload", + ctxNS: testNamespace, + seedData: vaildSecret, + enterpriseHost: "127.0.0.1:1", + wantErrSubst: `github enterprise host "127.0.0.1:1" does not match repository host "github.com"`, + wantLogMessage: githubAppTokenExfiltrationBlockedLog, + }, + { + ctx: ctx, + name: "enterprise host with missing repository HTML URL", + ctxNS: testNamespace, + seedData: vaildSecret, + enterpriseHost: "127.0.0.1:1", + payload: fmt.Sprintf(`{"installation":{"id":%d},"repository":{}}`, testInstallationID), + wantErrSubst: "repository HTML URL is missing in payload, cannot validate enterprise host", + wantLogMessage: githubAppTokenExfiltrationBlockedLog, }, { ctx: ctx, @@ -1292,8 +1336,6 @@ func TestAppTokenGeneration(t *testing.T) { mux.HandleFunc(fmt.Sprintf("/app/installations/%d/access_tokens", testInstallationID), func(w http.ResponseWriter, _ *http.Request) { _, _ = fmt.Fprint(w, "{}") }) - envRemove := env.PatchAll(t, tt.envs) - defer envRemove() // adding installation id to event to enforce client creation samplePRevent.Installation = &github.Installation{ @@ -1301,24 +1343,21 @@ func TestAppTokenGeneration(t *testing.T) { } jeez, _ := json.Marshal(samplePRevent) - logger, _ := logger.GetLogger() + if tt.payload != "" { + jeez = []byte(tt.payload) + } + testLogger, observedLogs := logger.GetLogger() gprovider := Provider{ - Logger: logger, + Logger: testLogger, ghClient: fakeghclient, pacInfo: &info.PacOpts{ Settings: settings.Settings{}, }, } - request := &http.Request{Header: map[string][]string{}} - request.Header.Set("X-GitHub-Event", "pull_request") - // a bit of a pain but works - request.Header.Set("X-GitHub-Enterprise-Host", serverURL) - tt.envs = make(map[string]string) - tt.envs["PAC_GIT_PROVIDER_TOKEN_APIURL"] = serverURL + "/api/v3" run := ¶ms.Run{ Clients: clients.Clients{ - Log: logger, + Log: testLogger, Kube: tt.seedData.Kube, }, @@ -1327,6 +1366,16 @@ func TestAppTokenGeneration(t *testing.T) { }, } + request := &http.Request{Header: map[string][]string{}} + request.Header.Set("X-GitHub-Event", "pull_request") + if !tt.omitSignature { + request.Header.Set(github.SHA256SignatureHeader, githubSHA256Signature(webhookSecret, jeez)) + } + if tt.enterpriseHost != "" { + request.Header.Set("X-GitHub-Enterprise-Host", tt.enterpriseHost) + } + t.Setenv("PAC_GIT_PROVIDER_TOKEN_APIURL", serverURL+"/api/v3") + tt.ctx = info.StoreCurrentControllerName(tt.ctx, "default") tt.ctx = info.StoreNS(tt.ctx, tt.ctxNS) @@ -1334,6 +1383,16 @@ func TestAppTokenGeneration(t *testing.T) { if tt.wantErrSubst != "" { assert.Assert(t, err != nil) assert.ErrorContains(t, err, tt.wantErrSubst) + if tt.wantLogMessage != "" { + found := false + for _, entry := range observedLogs.All() { + if entry.Message == tt.wantLogMessage { + found = true + break + } + } + assert.Assert(t, found, "expected log message %q for blocked GitHub App token mint", tt.wantLogMessage) + } return } assert.NilError(t, err) diff --git a/pkg/resolve/remote.go b/pkg/resolve/remote.go index 3b97caa20b..e36f62a380 100644 --- a/pkg/resolve/remote.go +++ b/pkg/resolve/remote.go @@ -119,8 +119,8 @@ func resolveRemoteResources(ctx context.Context, rt *matcher.RemoteTasks, types // making sure that the pipeline with same annotation name is not fetched if alreadyFetchedResource(fetchedResourcesForEvent.Pipelines, remotePipeline) { rt.Logger.Debugf("skipping already fetched pipeline %s in annotations on pipelinerun %s", remotePipeline, pipelinerun.GetName()) - // already fetched, then just get the pipeline to add to run specific Resources - pipeline = fetchedResourcesForEvent.Pipelines[remotePipeline] + // already fetched, deep-copy so inlining for this run doesn't mutate the cached original + pipeline = fetchedResourcesForEvent.Pipelines[remotePipeline].DeepCopy() } else { // seems like a new pipeline, fetch it based on name in annotation pipeline, err = rt.GetPipelineFromAnnotationName(ctx, remotePipeline) @@ -178,7 +178,7 @@ func resolveRemoteResources(ctx context.Context, rt *matcher.RemoteTasks, types // if task is already fetched in the event, then just copy the task if alreadyFetchedResource(fetchedResourcesForEvent.Tasks, remoteTask) { rt.Logger.Debugf("skipping already fetched task %s in annotations on pipelinerun %s", remoteTask, pipelinerun.GetName()) - task = fetchedResourcesForEvent.Tasks[remoteTask] + task = fetchedResourcesForEvent.Tasks[remoteTask].DeepCopy() } else { // get the task from annotation name task, err = rt.GetTaskFromAnnotationName(ctx, remoteTask) @@ -210,7 +210,7 @@ func resolveRemoteResources(ctx context.Context, rt *matcher.RemoteTasks, types // if PipelineRef is used then, first resolve pipeline and replace all taskRef{Finally/Task} of Pipeline, then put inlinePipeline in PipelineRun if pipelinerun.Spec.PipelineRef != nil && pipelinerun.Spec.PipelineRef.Resolver == "" { - pipelineResolved := fetchedResourcesForPipelineRun.Pipeline + pipelineResolved := fetchedResourcesForPipelineRun.Pipeline.DeepCopy() turns, err := inlineTasks(pipelineResolved.Spec.Tasks, ropt, fetchedResourcesForPipelineRun) if err != nil { return nil, err diff --git a/pkg/resolve/remote_test.go b/pkg/resolve/remote_test.go index ec67e12269..5e9cf12654 100644 --- a/pkg/resolve/remote_test.go +++ b/pkg/resolve/remote_test.go @@ -512,6 +512,84 @@ func TestRemote(t *testing.T) { } } +// Verifies that cached remote pipelines are deep-copied before modification. +// Without deep copy, when the first PipelineRun applies its task annotation, +// it would mutate the cached pipeline and leak that task into the second run. +func TestSharedRemotePipelineCacheNotMutated(t *testing.T) { + taskName := "shared-task" + + pipelineTask := tektonv1.TaskSpec{ + Steps: []tektonv1.Step{ + {Name: "from-pipeline", Image: "alpine", Command: []string{"true"}}, + }, + } + pipelinerunTask := tektonv1.TaskSpec{ + Steps: []tektonv1.Step{ + {Name: "from-pipelinerun", Image: "busybox", Command: []string{"false"}}, + }, + } + + // remote pipeline with a single TaskRef + pipeline := ttkn.MakePipeline("shared-pipeline", []tektonv1.PipelineTask{ + {Name: taskName, TaskRef: &tektonv1.TaskRef{Name: taskName}}, + }, map[string]string{ + apipac.Task: "http://remote/shared-task", + }) + pipelineB, err := yaml.Marshal(pipeline) + assert.NilError(t, err) + + pipelineTaskB, err := ttkn.MakeTaskB(taskName, pipelineTask) + assert.NilError(t, err) + pipelinerunTaskB, err := ttkn.MakeTaskB(taskName, pipelinerunTask) + assert.NilError(t, err) + + remoteURLS := map[string]map[string]string{ + "http://remote/shared-pipeline": {"body": string(pipelineB), "code": "200"}, + "http://remote/shared-task": {"body": string(pipelineTaskB), "code": "200"}, + "http://remote/pr-task": {"body": string(pipelinerunTaskB), "code": "200"}, + } + + // first run: overrides the task via pipelinerun annotation + // second run: same pipeline, no override — should get the original task + pipelineruns := []*tektonv1.PipelineRun{ + ttkn.MakePR("first-run", map[string]string{ + apipac.Pipeline: "http://remote/shared-pipeline", + apipac.Task: "http://remote/pr-task", + }, tektonv1.PipelineRunSpec{ + PipelineRef: &tektonv1.PipelineRef{Name: "shared-pipeline"}, + }), + ttkn.MakePR("second-run", map[string]string{ + apipac.Pipeline: "http://remote/shared-pipeline", + }, tektonv1.PipelineRunSpec{ + PipelineRef: &tektonv1.PipelineRef{Name: "shared-pipeline"}, + }), + } + + observer, _ := zapobserver.New(zap.InfoLevel) + logger := zap.New(observer).Sugar() + ctx, _ := rtesting.SetupFakeContext(t) + httpTestClient := httptesthelper.MakeHTTPTestClient(remoteURLS) + rt := &matcher.RemoteTasks{ + ProviderInterface: &testprovider.TestProviderImp{}, + Logger: logger, + Run: ¶ms.Run{ + Clients: clients.Clients{HTTP: *httpTestClient}, + }, + } + + ret, err := resolveRemoteResources(ctx, rt, TektonTypes{PipelineRuns: pipelineruns}, &Opts{RemoteTasks: true, GenerateName: true}) + assert.NilError(t, err) + assert.Equal(t, len(ret), 2) + + firstStep := ret[0].Spec.PipelineSpec.Tasks[0].TaskSpec.Steps[0] + assert.Equal(t, firstStep.Name, "from-pipelinerun") + assert.Equal(t, firstStep.Image, "busybox") + + secondStep := ret[1].Spec.PipelineSpec.Tasks[0].TaskSpec.Steps[0] + assert.Equal(t, secondStep.Name, "from-pipeline") + assert.Equal(t, secondStep.Image, "alpine") +} + func TestAssembleTaskFQDNs(t *testing.T) { tests := []struct { name string From 454d15eba0145a16920d3d9b008bb91b4c7e78b0 Mon Sep 17 00:00:00 2001 From: Chmouel Boudjnah Date: Wed, 3 Jun 2026 13:37:38 +0200 Subject: [PATCH 2/6] build(lint): pin golangci toolchain Download the branch-compatible golangci-lint binary into tmp and use it from make lint-go. Pin GOTOOLCHAIN for local make targets and PAC Go steps so linting does not drift with the host Go installation. Signed-off-by: Chmouel Boudjnah --- .golangci.yml | 3 +++ .tekton/go.yaml | 8 +++++++- Makefile | 28 +++++++++++++++++++++++----- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 308f663187..34baa5c268 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -57,6 +57,9 @@ linters: - whitespace - zerologlint settings: + govet: + disable: + - inline misspell: ignore-rules: - cancelled diff --git a/.tekton/go.yaml b/.tekton/go.yaml index 4fb27981c5..e214b6171b 100644 --- a/.tekton/go.yaml +++ b/.tekton/go.yaml @@ -62,6 +62,8 @@ spec: value: $(workspaces.source.path)/go-build-cache/cache - name: GOMODCACHE value: $(workspaces.source.path)/go-build-cache/mod + - name: GOTOOLCHAIN + value: go1.25.0 workingDir: $(workspaces.source.path) script: | #!/usr/bin/env bash @@ -82,6 +84,8 @@ spec: value: $(workspaces.source.path)/go-build-cache/cache - name: GOMODCACHE value: $(workspaces.source.path)/go-build-cache/mod + - name: GOTOOLCHAIN + value: go1.25.0 - name: GITHUB_REPOSITORY value: "{{repo_owner}}/{{repo_name}}" - name: GITHUB_PULL_REQUEST_ID @@ -97,13 +101,15 @@ spec: chmod +x ./codecov ./codecov -P $GITHUB_PULL_REQUEST_ID -C {{revision}} -v - name: lint - image: docker.io/golangci/golangci-lint:v2.10.1 + image: docker.io/golang:1.25 workingDir: $(workspaces.source.path) env: - name: GOCACHE value: $(workspaces.source.path)/go-build-cache/cache - name: GOMODCACHE value: $(workspaces.source.path)/go-build-cache/mod + - name: GOTOOLCHAIN + value: go1.25.0 - name: GOLANGCILINT_CACHE value: $(workspaces.source.path)/go-build-cache/golangci-cache script: | diff --git a/Makefile b/Makefile index d84016b866..20cea596c8 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,8 @@ TARGET_NAMESPACE=pipelines-as-code HUGO_VERSION=0.146.0 HTMLTEST_VERSION=0.17.0 -GOLANGCI_LINT=golangci-lint +GOLANGCI_LINT_VERSION ?= v2.12.2 +GO_TOOLCHAIN ?= go1.25.0 GOFUMPT=gofumpt TKN_BINARY_NAME := tkn TKN_BINARY_URL := https://tekton.dev/docs/cli/\#installation @@ -12,12 +13,21 @@ TIMEOUT_UNIT = 20m TIMEOUT_E2E = 45m DEFAULT_GO_TEST_FLAGS := -race -failfast GO_TEST_FLAGS := +GOTOOLCHAIN ?= $(GO_TOOLCHAIN) +export GOTOOLCHAIN SHELL := bash TOPDIR := $(shell git rev-parse --show-toplevel) TMPDIR := $(TOPDIR)/tmp HUGO_BIN := $(TMPDIR)/hugo/hugo HTMLTEST_BIN := $(TMPDIR)/htmltest/htmltest +GOLANGCI_LINT_OS ?= $(shell uname -s | tr '[:upper:]' '[:lower:]') +GOLANGCI_LINT_ARCH ?= $(shell uname -m | sed -e 's/x86_64/amd64/' -e 's/aarch64/arm64/') +GOLANGCI_LINT_PACKAGE := golangci-lint-$(patsubst v%,%,$(GOLANGCI_LINT_VERSION))-$(GOLANGCI_LINT_OS)-$(GOLANGCI_LINT_ARCH) +GOLANGCI_LINT_DIR := $(TMPDIR)/golangci-lint/$(GOLANGCI_LINT_VERSION) +GOLANGCI_LINT_BIN := $(GOLANGCI_LINT_DIR)/golangci-lint +GOLANGCI_LINT ?= $(GOLANGCI_LINT_BIN) +GOLANGCI_LINT_EXTRA_ARGS ?= --concurrency=1 # Safe file list helpers using null-delimited output # Usage: $(call GIT_LS_FILES,,) @@ -93,13 +103,21 @@ lint-e2e-naming: ## check e2e test naming conventions @./hack/check-e2e-test-naming.sh .PHONY: lint-go -lint-go: ## runs go linter on all go files +lint-go: golangci-lint ## runs go linter on all go files @echo "Linting go files..." - @$(GOLANGCI_LINT) run ./pkg/... ./test/... --modules-download-mode=vendor \ + @$(GOLANGCI_LINT) run $(GOLANGCI_LINT_EXTRA_ARGS) ./pkg/... ./test/... --modules-download-mode=vendor \ --max-issues-per-linter=0 \ --max-same-issues=0 \ --timeout $(TIMEOUT_UNIT) +.PHONY: golangci-lint +golangci-lint: $(GOLANGCI_LINT_BIN) ## download pinned golangci-lint into tmp + +$(GOLANGCI_LINT_BIN): + @mkdir -p $(GOLANGCI_LINT_DIR) + @echo "Downloading golangci-lint $(GOLANGCI_LINT_VERSION) for $(GOLANGCI_LINT_OS)-$(GOLANGCI_LINT_ARCH)" + @curl -fsSL "https://github.com/golangci/golangci-lint/releases/download/$(GOLANGCI_LINT_VERSION)/$(GOLANGCI_LINT_PACKAGE).tar.gz" | tar -xz -C "$(GOLANGCI_LINT_DIR)" --strip-components=1 "$(GOLANGCI_LINT_PACKAGE)/golangci-lint" + .PHONY: lint-yaml lint-yaml: ## runs yamllint on all yaml files @echo "Linting yaml files..." @@ -158,9 +176,9 @@ fix-python-errors: ## fix all python errors generated by ruff [[ -n "$$STATUS" ]] && { echo "Python files has been cleaned 🧹. Cleaned Files: "; echo "$$STATUS" ;} || echo "Python files are clean ✨" .PHONY: fix-golangci-lint -fix-golangci-lint: ## run golangci-lint and fix on all go files +fix-golangci-lint: golangci-lint ## run golangci-lint and fix on all go files @echo "Fixing some golangi-lint files..." - @$(GOLANGCI_LINT) run ./... --modules-download-mode=vendor \ + @$(GOLANGCI_LINT) run $(GOLANGCI_LINT_EXTRA_ARGS) ./... --modules-download-mode=vendor \ --max-issues-per-linter=0 \ --max-same-issues=0 \ --timeout $(TIMEOUT_UNIT) \ From 6ca896be79a4a8b7f7f24f93d8322100c2891f93 Mon Sep 17 00:00:00 2001 From: Chmouel Boudjnah Date: Wed, 3 Jun 2026 13:57:18 +0200 Subject: [PATCH 3/6] chore(deps): bump go-jose v3 to v3.0.5 Signed-off-by: Chmouel Boudjnah --- go.mod | 2 +- go.sum | 4 +-- .../go-jose/go-jose/v3/asymmetric.go | 10 ++++++- .../go-jose/go-jose/v3/cipher/key_wrap.go | 10 ++++++- .../go-jose/go-jose/v3/symmetric.go | 26 +++++++++++++------ vendor/modules.txt | 2 +- 6 files changed, 40 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index e200aadf38..57f53037f3 100644 --- a/go.mod +++ b/go.mod @@ -54,7 +54,7 @@ require ( github.com/cloudevents/sdk-go/sql/v2 v2.16.2 // indirect github.com/coreos/go-oidc/v3 v3.17.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect - github.com/go-jose/go-jose/v3 v3.0.4 // indirect + github.com/go-jose/go-jose/v3 v3.0.5 // indirect github.com/go-jose/go-jose/v4 v4.1.3 // indirect github.com/go-openapi/errors v0.22.6 // indirect github.com/go-openapi/strfmt v0.25.0 // indirect diff --git a/go.sum b/go.sum index 29b7cfd5f9..79aa8f7c82 100644 --- a/go.sum +++ b/go.sum @@ -140,8 +140,8 @@ github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e/go.mod h1:RCMrTZv github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY= -github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= +github.com/go-jose/go-jose/v3 v3.0.5 h1:BLLJWbC4nMZOfuPVxoZIxeYsn6Nl2r1fITaJ78UQlVQ= +github.com/go-jose/go-jose/v3 v3.0.5/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= diff --git a/vendor/github.com/go-jose/go-jose/v3/asymmetric.go b/vendor/github.com/go-jose/go-jose/v3/asymmetric.go index d4d4961b24..79d9ee57cf 100644 --- a/vendor/github.com/go-jose/go-jose/v3/asymmetric.go +++ b/vendor/github.com/go-jose/go-jose/v3/asymmetric.go @@ -414,6 +414,9 @@ func (ctx ecKeyGenerator) genKey() ([]byte, rawHeader, error) { // Decrypt the given payload and return the content encryption key. func (ctx ecDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) { + if recipient == nil { + return nil, errors.New("go-jose/go-jose: missing recipient") + } epk, err := headers.getEPK() if err != nil { return nil, errors.New("go-jose/go-jose: invalid epk header") @@ -461,13 +464,18 @@ func (ctx ecDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientI return nil, ErrUnsupportedAlgorithm } + encryptedKey := recipient.encryptedKey + if len(encryptedKey) == 0 { + return nil, errors.New("go-jose/go-jose: missing JWE Encrypted Key") + } + key := deriveKey(string(algorithm), keySize) block, err := aes.NewCipher(key) if err != nil { return nil, err } - return josecipher.KeyUnwrap(block, recipient.encryptedKey) + return josecipher.KeyUnwrap(block, encryptedKey) } func (ctx edDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) { diff --git a/vendor/github.com/go-jose/go-jose/v3/cipher/key_wrap.go b/vendor/github.com/go-jose/go-jose/v3/cipher/key_wrap.go index b9effbca8a..a2f86e3db9 100644 --- a/vendor/github.com/go-jose/go-jose/v3/cipher/key_wrap.go +++ b/vendor/github.com/go-jose/go-jose/v3/cipher/key_wrap.go @@ -66,12 +66,20 @@ func KeyWrap(block cipher.Block, cek []byte) ([]byte, error) { } // KeyUnwrap implements NIST key unwrapping; it unwraps a content encryption key (cek) with the given block cipher. +// +// https://datatracker.ietf.org/doc/html/rfc7518#section-4.4 +// https://datatracker.ietf.org/doc/html/rfc7518#section-4.6 +// https://datatracker.ietf.org/doc/html/rfc7518#section-4.8 func KeyUnwrap(block cipher.Block, ciphertext []byte) ([]byte, error) { + n := (len(ciphertext) / 8) - 1 + if n <= 0 { + return nil, errors.New("go-jose/go-jose: JWE Encrypted Key too short") + } + if len(ciphertext)%8 != 0 { return nil, errors.New("go-jose/go-jose: key wrap input must be 8 byte blocks") } - n := (len(ciphertext) / 8) - 1 r := make([][]byte, n) for i := range r { diff --git a/vendor/github.com/go-jose/go-jose/v3/symmetric.go b/vendor/github.com/go-jose/go-jose/v3/symmetric.go index 10d8e19fd1..4a4bba2cb6 100644 --- a/vendor/github.com/go-jose/go-jose/v3/symmetric.go +++ b/vendor/github.com/go-jose/go-jose/v3/symmetric.go @@ -364,11 +364,21 @@ func (ctx *symmetricKeyCipher) encryptKey(cek []byte, alg KeyAlgorithm) (recipie // Decrypt the content encryption key. func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) { - switch headers.getAlgorithm() { - case DIRECT: - cek := make([]byte, len(ctx.key)) - copy(cek, ctx.key) - return cek, nil + if recipient == nil { + return nil, fmt.Errorf("go-jose/go-jose: missing recipient") + } + + alg := headers.getAlgorithm() + if alg == DIRECT { + return bytes.Clone(ctx.key), nil + } + + encryptedKey := recipient.encryptedKey + if len(encryptedKey) == 0 { + return nil, fmt.Errorf("go-jose/go-jose: missing JWE Encrypted Key") + } + + switch alg { case A128GCMKW, A192GCMKW, A256GCMKW: aead := newAESGCM(len(ctx.key)) @@ -383,7 +393,7 @@ func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipien parts := &aeadParts{ iv: iv.bytes(), - ciphertext: recipient.encryptedKey, + ciphertext: encryptedKey, tag: tag.bytes(), } @@ -399,7 +409,7 @@ func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipien return nil, err } - cek, err := josecipher.KeyUnwrap(block, recipient.encryptedKey) + cek, err := josecipher.KeyUnwrap(block, encryptedKey) if err != nil { return nil, err } @@ -440,7 +450,7 @@ func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipien return nil, err } - cek, err := josecipher.KeyUnwrap(block, recipient.encryptedKey) + cek, err := josecipher.KeyUnwrap(block, encryptedKey) if err != nil { return nil, err } diff --git a/vendor/modules.txt b/vendor/modules.txt index f2f8d70319..2786cd88dd 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -126,7 +126,7 @@ github.com/fxamacker/cbor/v2 # github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e ## explicit; go 1.13 github.com/go-fed/httpsig -# github.com/go-jose/go-jose/v3 v3.0.4 +# github.com/go-jose/go-jose/v3 v3.0.5 ## explicit; go 1.12 github.com/go-jose/go-jose/v3 github.com/go-jose/go-jose/v3/cipher From 5eb65a034f24b2345768d67519a16ead15d25604 Mon Sep 17 00:00:00 2001 From: Chmouel Boudjnah Date: Wed, 3 Jun 2026 13:58:01 +0200 Subject: [PATCH 4/6] chore(deps): bump go-jose v4 to v4.1.4 Signed-off-by: Chmouel Boudjnah --- go.mod | 4 +- go.sum | 4 +- .../go-jose/go-jose/v4/CHANGELOG.md | 96 ------------------- .../github.com/go-jose/go-jose/v4/README.md | 76 ++++++++------- .../go-jose/go-jose/v4/asymmetric.go | 10 +- .../go-jose/go-jose/v4/cipher/key_wrap.go | 10 +- .../github.com/go-jose/go-jose/v4/crypter.go | 20 ++-- vendor/github.com/go-jose/go-jose/v4/jwe.go | 19 +++- vendor/github.com/go-jose/go-jose/v4/jwk.go | 59 ++++++++---- vendor/github.com/go-jose/go-jose/v4/jws.go | 74 +++++++++++--- .../github.com/go-jose/go-jose/v4/shared.go | 33 ++++++- .../github.com/go-jose/go-jose/v4/signing.go | 44 ++++++--- .../go-jose/go-jose/v4/symmetric.go | 39 +++++--- vendor/modules.txt | 6 +- 14 files changed, 278 insertions(+), 216 deletions(-) delete mode 100644 vendor/github.com/go-jose/go-jose/v4/CHANGELOG.md diff --git a/go.mod b/go.mod index 57f53037f3..825168f5d2 100644 --- a/go.mod +++ b/go.mod @@ -55,7 +55,7 @@ require ( github.com/coreos/go-oidc/v3 v3.17.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-jose/go-jose/v3 v3.0.5 // indirect - github.com/go-jose/go-jose/v4 v4.1.3 // indirect + github.com/go-jose/go-jose/v4 v4.1.4 // indirect github.com/go-openapi/errors v0.22.6 // indirect github.com/go-openapi/strfmt v0.25.0 // indirect github.com/go-openapi/swag/cmdutils v0.25.4 // indirect @@ -160,7 +160,7 @@ require ( ) replace ( - github.com/go-jose/go-jose/v4 => github.com/go-jose/go-jose/v4 v4.0.5 + github.com/go-jose/go-jose/v4 => github.com/go-jose/go-jose/v4 v4.1.4 github.com/google/gnostic-models => github.com/google/gnostic-models v0.6.9 k8s.io/api => k8s.io/api v0.32.8 k8s.io/apimachinery => k8s.io/apimachinery v0.32.8 diff --git a/go.sum b/go.sum index 79aa8f7c82..0ea43950c7 100644 --- a/go.sum +++ b/go.sum @@ -142,8 +142,8 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-jose/go-jose/v3 v3.0.5 h1:BLLJWbC4nMZOfuPVxoZIxeYsn6Nl2r1fITaJ78UQlVQ= github.com/go-jose/go-jose/v3 v3.0.5/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= -github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= -github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= +github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= +github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= diff --git a/vendor/github.com/go-jose/go-jose/v4/CHANGELOG.md b/vendor/github.com/go-jose/go-jose/v4/CHANGELOG.md deleted file mode 100644 index 6f717dbd86..0000000000 --- a/vendor/github.com/go-jose/go-jose/v4/CHANGELOG.md +++ /dev/null @@ -1,96 +0,0 @@ -# v4.0.4 - -## Fixed - - - Reverted "Allow unmarshalling JSONWebKeySets with unsupported key types" as a - breaking change. See #136 / #137. - -# v4.0.3 - -## Changed - - - Allow unmarshalling JSONWebKeySets with unsupported key types (#130) - - Document that OpaqueKeyEncrypter can't be implemented (for now) (#129) - - Dependency updates - -# v4.0.2 - -## Changed - - - Improved documentation of Verify() to note that JSONWebKeySet is a supported - argument type (#104) - - Defined exported error values for missing x5c header and unsupported elliptic - curves error cases (#117) - -# v4.0.1 - -## Fixed - - - An attacker could send a JWE containing compressed data that used large - amounts of memory and CPU when decompressed by `Decrypt` or `DecryptMulti`. - Those functions now return an error if the decompressed data would exceed - 250kB or 10x the compressed size (whichever is larger). Thanks to - Enze Wang@Alioth and Jianjun Chen@Zhongguancun Lab (@zer0yu and @chenjj) - for reporting. - -# v4.0.0 - -This release makes some breaking changes in order to more thoroughly -address the vulnerabilities discussed in [Three New Attacks Against JSON Web -Tokens][1], "Sign/encrypt confusion", "Billion hash attack", and "Polyglot -token". - -## Changed - - - Limit JWT encryption types (exclude password or public key types) (#78) - - Enforce minimum length for HMAC keys (#85) - - jwt: match any audience in a list, rather than requiring all audiences (#81) - - jwt: accept only Compact Serialization (#75) - - jws: Add expected algorithms for signatures (#74) - - Require specifying expected algorithms for ParseEncrypted, - ParseSigned, ParseDetached, jwt.ParseEncrypted, jwt.ParseSigned, - jwt.ParseSignedAndEncrypted (#69, #74) - - Usually there is a small, known set of appropriate algorithms for a program - to use and it's a mistake to allow unexpected algorithms. For instance the - "billion hash attack" relies in part on programs accepting the PBES2 - encryption algorithm and doing the necessary work even if they weren't - specifically configured to allow PBES2. - - Revert "Strip padding off base64 strings" (#82) - - The specs require base64url encoding without padding. - - Minimum supported Go version is now 1.21 - -## Added - - - ParseSignedCompact, ParseSignedJSON, ParseEncryptedCompact, ParseEncryptedJSON. - - These allow parsing a specific serialization, as opposed to ParseSigned and - ParseEncrypted, which try to automatically detect which serialization was - provided. It's common to require a specific serialization for a specific - protocol - for instance JWT requires Compact serialization. - -[1]: https://i.blackhat.com/BH-US-23/Presentations/US-23-Tervoort-Three-New-Attacks-Against-JSON-Web-Tokens.pdf - -# v3.0.2 - -## Fixed - - - DecryptMulti: handle decompression error (#19) - -## Changed - - - jwe/CompactSerialize: improve performance (#67) - - Increase the default number of PBKDF2 iterations to 600k (#48) - - Return the proper algorithm for ECDSA keys (#45) - -## Added - - - Add Thumbprint support for opaque signers (#38) - -# v3.0.1 - -## Fixed - - - Security issue: an attacker specifying a large "p2c" value can cause - JSONWebEncryption.Decrypt and JSONWebEncryption.DecryptMulti to consume large - amounts of CPU, causing a DoS. Thanks to Matt Schwager (@mschwager) for the - disclosure and to Tom Tervoort for originally publishing the category of attack. - https://i.blackhat.com/BH-US-23/Presentations/US-23-Tervoort-Three-New-Attacks-Against-JSON-Web-Tokens.pdf diff --git a/vendor/github.com/go-jose/go-jose/v4/README.md b/vendor/github.com/go-jose/go-jose/v4/README.md index 02b5749546..abd9a9e3ce 100644 --- a/vendor/github.com/go-jose/go-jose/v4/README.md +++ b/vendor/github.com/go-jose/go-jose/v4/README.md @@ -3,7 +3,6 @@ [![godoc](https://pkg.go.dev/badge/github.com/go-jose/go-jose/v4.svg)](https://pkg.go.dev/github.com/go-jose/go-jose/v4) [![godoc](https://pkg.go.dev/badge/github.com/go-jose/go-jose/v4/jwt.svg)](https://pkg.go.dev/github.com/go-jose/go-jose/v4/jwt) [![license](https://img.shields.io/badge/license-apache_2.0-blue.svg?style=flat)](https://raw.githubusercontent.com/go-jose/go-jose/master/LICENSE) -[![test](https://img.shields.io/github/checks-status/go-jose/go-jose/v4)](https://github.com/go-jose/go-jose/actions) Package jose aims to provide an implementation of the Javascript Object Signing and Encryption set of standards. This includes support for JSON Web Encryption, @@ -29,17 +28,20 @@ libraries in other languages. ### Versions -[Version 4](https://github.com/go-jose/go-jose) -([branch](https://github.com/go-jose/go-jose/tree/main), -[doc](https://pkg.go.dev/github.com/go-jose/go-jose/v4), [releases](https://github.com/go-jose/go-jose/releases)) is the current stable version: +The forthcoming Version 5 will be released with several breaking API changes, +and will require Golang's `encoding/json/v2`, which is currently requires +Go 1.25 built with GOEXPERIMENT=jsonv2. + +Version 4 is the current stable version: import "github.com/go-jose/go-jose/v4" -The old [square/go-jose](https://github.com/square/go-jose) repo contains the prior v1 and v2 versions, which -are still useable but not actively developed anymore. +It supports at least the current and previous Golang release. Currently it +requires Golang 1.24. + +Version 3 is only receiving critical security updates. Migration to Version 4 is recommended. -Version 3, in this repo, is still receiving security fixes but not functionality -updates. +Versions 1 and 2 are obsolete, but can be found in the old repository, [square/go-jose](https://github.com/square/go-jose). ### Supported algorithms @@ -47,36 +49,36 @@ See below for a table of supported algorithms. Algorithm identifiers match the names in the [JSON Web Algorithms](https://dx.doi.org/10.17487/RFC7518) standard where possible. The Godoc reference has a list of constants. - Key encryption | Algorithm identifier(s) - :------------------------- | :------------------------------ - RSA-PKCS#1v1.5 | RSA1_5 - RSA-OAEP | RSA-OAEP, RSA-OAEP-256 - AES key wrap | A128KW, A192KW, A256KW - AES-GCM key wrap | A128GCMKW, A192GCMKW, A256GCMKW - ECDH-ES + AES key wrap | ECDH-ES+A128KW, ECDH-ES+A192KW, ECDH-ES+A256KW - ECDH-ES (direct) | ECDH-ES1 - Direct encryption | dir1 +| Key encryption | Algorithm identifier(s) | +|:-----------------------|:-----------------------------------------------| +| RSA-PKCS#1v1.5 | RSA1_5 | +| RSA-OAEP | RSA-OAEP, RSA-OAEP-256 | +| AES key wrap | A128KW, A192KW, A256KW | +| AES-GCM key wrap | A128GCMKW, A192GCMKW, A256GCMKW | +| ECDH-ES + AES key wrap | ECDH-ES+A128KW, ECDH-ES+A192KW, ECDH-ES+A256KW | +| ECDH-ES (direct) | ECDH-ES1 | +| Direct encryption | dir1 | 1. Not supported in multi-recipient mode - Signing / MAC | Algorithm identifier(s) - :------------------------- | :------------------------------ - RSASSA-PKCS#1v1.5 | RS256, RS384, RS512 - RSASSA-PSS | PS256, PS384, PS512 - HMAC | HS256, HS384, HS512 - ECDSA | ES256, ES384, ES512 - Ed25519 | EdDSA2 +| Signing / MAC | Algorithm identifier(s) | +|:------------------|:------------------------| +| RSASSA-PKCS#1v1.5 | RS256, RS384, RS512 | +| RSASSA-PSS | PS256, PS384, PS512 | +| HMAC | HS256, HS384, HS512 | +| ECDSA | ES256, ES384, ES512 | +| Ed25519 | EdDSA2 | 2. Only available in version 2 of the package - Content encryption | Algorithm identifier(s) - :------------------------- | :------------------------------ - AES-CBC+HMAC | A128CBC-HS256, A192CBC-HS384, A256CBC-HS512 - AES-GCM | A128GCM, A192GCM, A256GCM +| Content encryption | Algorithm identifier(s) | +|:-------------------|:--------------------------------------------| +| AES-CBC+HMAC | A128CBC-HS256, A192CBC-HS384, A256CBC-HS512 | +| AES-GCM | A128GCM, A192GCM, A256GCM | - Compression | Algorithm identifiers(s) - :------------------------- | ------------------------------- - DEFLATE (RFC 1951) | DEF +| Compression | Algorithm identifiers(s) | +|:-------------------|--------------------------| +| DEFLATE (RFC 1951) | DEF | ### Supported key types @@ -85,12 +87,12 @@ library, and can be passed to corresponding functions such as `NewEncrypter` or `NewSigner`. Each of these keys can also be wrapped in a JWK if desired, which allows attaching a key id. - Algorithm(s) | Corresponding types - :------------------------- | ------------------------------- - RSA | *[rsa.PublicKey](https://pkg.go.dev/crypto/rsa/#PublicKey), *[rsa.PrivateKey](https://pkg.go.dev/crypto/rsa/#PrivateKey) - ECDH, ECDSA | *[ecdsa.PublicKey](https://pkg.go.dev/crypto/ecdsa/#PublicKey), *[ecdsa.PrivateKey](https://pkg.go.dev/crypto/ecdsa/#PrivateKey) - EdDSA1 | [ed25519.PublicKey](https://pkg.go.dev/crypto/ed25519#PublicKey), [ed25519.PrivateKey](https://pkg.go.dev/crypto/ed25519#PrivateKey) - AES, HMAC | []byte +| Algorithm(s) | Corresponding types | +|:------------------|--------------------------------------------------------------------------------------------------------------------------------------| +| RSA | *[rsa.PublicKey](https://pkg.go.dev/crypto/rsa/#PublicKey), *[rsa.PrivateKey](https://pkg.go.dev/crypto/rsa/#PrivateKey) | +| ECDH, ECDSA | *[ecdsa.PublicKey](https://pkg.go.dev/crypto/ecdsa/#PublicKey), *[ecdsa.PrivateKey](https://pkg.go.dev/crypto/ecdsa/#PrivateKey) | +| EdDSA1 | [ed25519.PublicKey](https://pkg.go.dev/crypto/ed25519#PublicKey), [ed25519.PrivateKey](https://pkg.go.dev/crypto/ed25519#PrivateKey) | +| AES, HMAC | []byte | 1. Only available in version 2 or later of the package diff --git a/vendor/github.com/go-jose/go-jose/v4/asymmetric.go b/vendor/github.com/go-jose/go-jose/v4/asymmetric.go index f8d5774ef5..7784cd4584 100644 --- a/vendor/github.com/go-jose/go-jose/v4/asymmetric.go +++ b/vendor/github.com/go-jose/go-jose/v4/asymmetric.go @@ -414,6 +414,9 @@ func (ctx ecKeyGenerator) genKey() ([]byte, rawHeader, error) { // Decrypt the given payload and return the content encryption key. func (ctx ecDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) { + if recipient == nil { + return nil, errors.New("go-jose/go-jose: missing recipient") + } epk, err := headers.getEPK() if err != nil { return nil, errors.New("go-jose/go-jose: invalid epk header") @@ -461,13 +464,18 @@ func (ctx ecDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientI return nil, ErrUnsupportedAlgorithm } + encryptedKey := recipient.encryptedKey + if len(encryptedKey) == 0 { + return nil, errors.New("go-jose/go-jose: missing JWE Encrypted Key") + } + key := deriveKey(string(algorithm), keySize) block, err := aes.NewCipher(key) if err != nil { return nil, err } - return josecipher.KeyUnwrap(block, recipient.encryptedKey) + return josecipher.KeyUnwrap(block, encryptedKey) } func (ctx edDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) { diff --git a/vendor/github.com/go-jose/go-jose/v4/cipher/key_wrap.go b/vendor/github.com/go-jose/go-jose/v4/cipher/key_wrap.go index b9effbca8a..a2f86e3db9 100644 --- a/vendor/github.com/go-jose/go-jose/v4/cipher/key_wrap.go +++ b/vendor/github.com/go-jose/go-jose/v4/cipher/key_wrap.go @@ -66,12 +66,20 @@ func KeyWrap(block cipher.Block, cek []byte) ([]byte, error) { } // KeyUnwrap implements NIST key unwrapping; it unwraps a content encryption key (cek) with the given block cipher. +// +// https://datatracker.ietf.org/doc/html/rfc7518#section-4.4 +// https://datatracker.ietf.org/doc/html/rfc7518#section-4.6 +// https://datatracker.ietf.org/doc/html/rfc7518#section-4.8 func KeyUnwrap(block cipher.Block, ciphertext []byte) ([]byte, error) { + n := (len(ciphertext) / 8) - 1 + if n <= 0 { + return nil, errors.New("go-jose/go-jose: JWE Encrypted Key too short") + } + if len(ciphertext)%8 != 0 { return nil, errors.New("go-jose/go-jose: key wrap input must be 8 byte blocks") } - n := (len(ciphertext) / 8) - 1 r := make([][]byte, n) for i := range r { diff --git a/vendor/github.com/go-jose/go-jose/v4/crypter.go b/vendor/github.com/go-jose/go-jose/v4/crypter.go index d81b03b447..31290fc871 100644 --- a/vendor/github.com/go-jose/go-jose/v4/crypter.go +++ b/vendor/github.com/go-jose/go-jose/v4/crypter.go @@ -286,6 +286,10 @@ func makeJWERecipient(alg KeyAlgorithm, encryptionKey interface{}) (recipientKey return newSymmetricRecipient(alg, encryptionKey) case string: return newSymmetricRecipient(alg, []byte(encryptionKey)) + case JSONWebKey: + recipient, err := makeJWERecipient(alg, encryptionKey.Key) + recipient.keyID = encryptionKey.KeyID + return recipient, err case *JSONWebKey: recipient, err := makeJWERecipient(alg, encryptionKey.Key) recipient.keyID = encryptionKey.KeyID @@ -450,13 +454,9 @@ func (obj JSONWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error) return nil, errors.New("go-jose/go-jose: too many recipients in payload; expecting only one") } - critical, err := headers.getCritical() + err := headers.checkNoCritical() if err != nil { - return nil, fmt.Errorf("go-jose/go-jose: invalid crit header") - } - - if len(critical) > 0 { - return nil, fmt.Errorf("go-jose/go-jose: unsupported crit header") + return nil, err } key, err := tryJWKS(decryptionKey, obj.Header) @@ -523,13 +523,9 @@ func (obj JSONWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error) func (obj JSONWebEncryption) DecryptMulti(decryptionKey interface{}) (int, Header, []byte, error) { globalHeaders := obj.mergedHeaders(nil) - critical, err := globalHeaders.getCritical() + err := globalHeaders.checkNoCritical() if err != nil { - return -1, Header{}, nil, fmt.Errorf("go-jose/go-jose: invalid crit header") - } - - if len(critical) > 0 { - return -1, Header{}, nil, fmt.Errorf("go-jose/go-jose: unsupported crit header") + return -1, Header{}, nil, err } key, err := tryJWKS(decryptionKey, obj.Header) diff --git a/vendor/github.com/go-jose/go-jose/v4/jwe.go b/vendor/github.com/go-jose/go-jose/v4/jwe.go index 9f1322dccc..6102f91000 100644 --- a/vendor/github.com/go-jose/go-jose/v4/jwe.go +++ b/vendor/github.com/go-jose/go-jose/v4/jwe.go @@ -274,7 +274,7 @@ func validateAlgEnc(headers rawHeader, keyAlgorithms []KeyAlgorithm, contentEncr if alg != "" && !containsKeyAlgorithm(keyAlgorithms, alg) { return fmt.Errorf("unexpected key algorithm %q; expected %q", alg, keyAlgorithms) } - if alg != "" && !containsContentEncryption(contentEncryption, enc) { + if enc != "" && !containsContentEncryption(contentEncryption, enc) { return fmt.Errorf("unexpected content encryption algorithm %q; expected %q", enc, contentEncryption) } return nil @@ -288,11 +288,20 @@ func ParseEncryptedCompact( keyAlgorithms []KeyAlgorithm, contentEncryption []ContentEncryption, ) (*JSONWebEncryption, error) { - // Five parts is four separators - if strings.Count(input, ".") != 4 { - return nil, fmt.Errorf("go-jose/go-jose: compact JWE format must have five parts") + var parts [5]string + var ok bool + + for i := range 4 { + parts[i], input, ok = strings.Cut(input, ".") + if !ok { + return nil, errors.New("go-jose/go-jose: compact JWE format must have five parts") + } + } + // Validate that the last part does not contain more dots + if strings.ContainsRune(input, '.') { + return nil, errors.New("go-jose/go-jose: compact JWE format must have five parts") } - parts := strings.SplitN(input, ".", 5) + parts[4] = input rawProtected, err := base64.RawURLEncoding.DecodeString(parts[0]) if err != nil { diff --git a/vendor/github.com/go-jose/go-jose/v4/jwk.go b/vendor/github.com/go-jose/go-jose/v4/jwk.go index 9e57e93ba2..164d6a1619 100644 --- a/vendor/github.com/go-jose/go-jose/v4/jwk.go +++ b/vendor/github.com/go-jose/go-jose/v4/jwk.go @@ -175,6 +175,8 @@ func (k JSONWebKey) MarshalJSON() ([]byte, error) { } // UnmarshalJSON reads a key from its JSON representation. +// +// Returns ErrUnsupportedKeyType for unrecognized or unsupported "kty" header values. func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) { var raw rawJSONWebKey err = json.Unmarshal(data, &raw) @@ -228,7 +230,7 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) { } key, err = raw.symmetricKey() case "OKP": - if raw.Crv == "Ed25519" && raw.X != nil { + if raw.Crv == "Ed25519" { if raw.D != nil { key, err = raw.edPrivateKey() if err == nil { @@ -238,17 +240,27 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) { key, err = raw.edPublicKey() keyPub = key } - } else { - return fmt.Errorf("go-jose/go-jose: unknown curve %s'", raw.Crv) } - default: - return fmt.Errorf("go-jose/go-jose: unknown json web key type '%s'", raw.Kty) + case "": + // kty MUST be present + err = fmt.Errorf("go-jose/go-jose: missing json web key type") } if err != nil { return } + if key == nil { + // RFC 7517: + // 5. JWK Set Format + // ... + // Implementations SHOULD ignore JWKs within a JWK Set that use "kty" + // (key type) values that are not understood by them, that are missing + // required members, or for which values are out of the supported + // ranges. + return ErrUnsupportedKeyType + } + if certPub != nil && keyPub != nil { if !reflect.DeepEqual(certPub, keyPub) { return errors.New("go-jose/go-jose: invalid JWK, public keys in key and x5c fields do not match") @@ -581,10 +593,10 @@ func fromEcPublicKey(pub *ecdsa.PublicKey) (*rawJSONWebKey, error) { func (key rawJSONWebKey) edPrivateKey() (ed25519.PrivateKey, error) { var missing []string - switch { - case key.D == nil: + if key.D == nil { missing = append(missing, "D") - case key.X == nil: + } + if key.X == nil { missing = append(missing, "X") } @@ -611,19 +623,21 @@ func (key rawJSONWebKey) edPublicKey() (ed25519.PublicKey, error) { func (key rawJSONWebKey) rsaPrivateKey() (*rsa.PrivateKey, error) { var missing []string - switch { - case key.N == nil: + if key.N == nil { missing = append(missing, "N") - case key.E == nil: + } + if key.E == nil { missing = append(missing, "E") - case key.D == nil: + } + if key.D == nil { missing = append(missing, "D") - case key.P == nil: + } + if key.P == nil { missing = append(missing, "P") - case key.Q == nil: + } + if key.Q == nil { missing = append(missing, "Q") } - if len(missing) > 0 { return nil, fmt.Errorf("go-jose/go-jose: invalid RSA private key, missing %s value(s)", strings.Join(missing, ", ")) } @@ -698,8 +712,19 @@ func (key rawJSONWebKey) ecPrivateKey() (*ecdsa.PrivateKey, error) { return nil, fmt.Errorf("go-jose/go-jose: unsupported elliptic curve '%s'", key.Crv) } - if key.X == nil || key.Y == nil || key.D == nil { - return nil, fmt.Errorf("go-jose/go-jose: invalid EC private key, missing x/y/d values") + var missing []string + if key.X == nil { + missing = append(missing, "X") + } + if key.Y == nil { + missing = append(missing, "Y") + } + if key.D == nil { + missing = append(missing, "D") + } + + if len(missing) > 0 { + return nil, fmt.Errorf("go-jose/go-jose: invalid EC private key, missing %s value(s)", strings.Join(missing, ", ")) } // The length of this octet string MUST be the full size of a coordinate for diff --git a/vendor/github.com/go-jose/go-jose/v4/jws.go b/vendor/github.com/go-jose/go-jose/v4/jws.go index d09d8ba507..c40bd3ec10 100644 --- a/vendor/github.com/go-jose/go-jose/v4/jws.go +++ b/vendor/github.com/go-jose/go-jose/v4/jws.go @@ -75,7 +75,14 @@ type Signature struct { original *rawSignatureInfo } -// ParseSigned parses a signed message in JWS Compact or JWS JSON Serialization. +// ParseSigned parses a signed message in JWS Compact or JWS JSON Serialization. Validation fails if +// the JWS is signed with an algorithm that isn't in the provided list of signature algorithms. +// Applications should decide for themselves which signature algorithms are acceptable. If you're +// not sure which signature algorithms your application might receive, consult the documentation of +// the program which provides them or the protocol that you are implementing. You can also try +// getting an example JWS and decoding it with a tool like https://jwt.io to see what its "alg" +// header parameter indicates. The signature on the JWS does not get validated during parsing. Call +// Verify() after parsing to validate the signature and obtain the payload. // // https://datatracker.ietf.org/doc/html/rfc7515#section-7 func ParseSigned( @@ -90,7 +97,14 @@ func ParseSigned( return parseSignedCompact(signature, nil, signatureAlgorithms) } -// ParseSignedCompact parses a message in JWS Compact Serialization. +// ParseSignedCompact parses a message in JWS Compact Serialization. Validation fails if the JWS is +// signed with an algorithm that isn't in the provided list of signature algorithms. Applications +// should decide for themselves which signature algorithms are acceptable.If you're not sure which +// signature algorithms your application might receive, consult the documentation of the program +// which provides them or the protocol that you are implementing. You can also try getting an +// example JWS and decoding it with a tool like https://jwt.io to see what its "alg" header +// parameter indicates. The signature on the JWS does not get validated during parsing. Call +// Verify() after parsing to validate the signature and obtain the payload. // // https://datatracker.ietf.org/doc/html/rfc7515#section-7.1 func ParseSignedCompact( @@ -101,6 +115,15 @@ func ParseSignedCompact( } // ParseDetached parses a signed message in compact serialization format with detached payload. +// Validation fails if the JWS is signed with an algorithm that isn't in the provided list of +// signature algorithms. Applications should decide for themselves which signature algorithms are +// acceptable. If you're not sure which signature algorithms your application might receive, consult +// the documentation of the program which provides them or the protocol that you are implementing. +// You can also try getting an example JWS and decoding it with a tool like https://jwt.io to see +// what its "alg" header parameter indicates. The signature on the JWS does not get validated during +// parsing. Call Verify() after parsing to validate the signature and obtain the payload. +// +// https://datatracker.ietf.org/doc/html/rfc7515#appendix-F func ParseDetached( signature string, payload []byte, @@ -181,6 +204,25 @@ func containsSignatureAlgorithm(haystack []SignatureAlgorithm, needle SignatureA return false } +// ErrUnexpectedSignatureAlgorithm is returned when the signature algorithm in +// the JWS header does not match one of the expected algorithms. +type ErrUnexpectedSignatureAlgorithm struct { + // Got is the signature algorithm found in the JWS header. + Got SignatureAlgorithm + expected []SignatureAlgorithm +} + +func (e *ErrUnexpectedSignatureAlgorithm) Error() string { + return fmt.Sprintf("unexpected signature algorithm %q; expected %q", e.Got, e.expected) +} + +func newErrUnexpectedSignatureAlgorithm(got SignatureAlgorithm, expected []SignatureAlgorithm) error { + return &ErrUnexpectedSignatureAlgorithm{ + Got: got, + expected: expected, + } +} + // sanitized produces a cleaned-up JWS object from the raw JSON. func (parsed *rawJSONWebSignature) sanitized(signatureAlgorithms []SignatureAlgorithm) (*JSONWebSignature, error) { if len(signatureAlgorithms) == 0 { @@ -236,8 +278,7 @@ func (parsed *rawJSONWebSignature) sanitized(signatureAlgorithms []SignatureAlgo alg := SignatureAlgorithm(signature.Header.Algorithm) if !containsSignatureAlgorithm(signatureAlgorithms, alg) { - return nil, fmt.Errorf("go-jose/go-jose: unexpected signature algorithm %q; expected %q", - alg, signatureAlgorithms) + return nil, newErrUnexpectedSignatureAlgorithm(alg, signatureAlgorithms) } if signature.header != nil { @@ -285,8 +326,7 @@ func (parsed *rawJSONWebSignature) sanitized(signatureAlgorithms []SignatureAlgo alg := SignatureAlgorithm(obj.Signatures[i].Header.Algorithm) if !containsSignatureAlgorithm(signatureAlgorithms, alg) { - return nil, fmt.Errorf("go-jose/go-jose: unexpected signature algorithm %q; expected %q", - alg, signatureAlgorithms) + return nil, newErrUnexpectedSignatureAlgorithm(alg, signatureAlgorithms) } if obj.Signatures[i].header != nil { @@ -321,35 +361,43 @@ func (parsed *rawJSONWebSignature) sanitized(signatureAlgorithms []SignatureAlgo return obj, nil } +const tokenDelim = "." + // parseSignedCompact parses a message in compact format. func parseSignedCompact( input string, payload []byte, signatureAlgorithms []SignatureAlgorithm, ) (*JSONWebSignature, error) { - // Three parts is two separators - if strings.Count(input, ".") != 2 { + protected, s, ok := strings.Cut(input, tokenDelim) + if !ok { // no period found + return nil, fmt.Errorf("go-jose/go-jose: compact JWS format must have three parts") + } + claims, sig, ok := strings.Cut(s, tokenDelim) + if !ok { // only one period found + return nil, fmt.Errorf("go-jose/go-jose: compact JWS format must have three parts") + } + if strings.ContainsRune(sig, '.') { // too many periods found return nil, fmt.Errorf("go-jose/go-jose: compact JWS format must have three parts") } - parts := strings.SplitN(input, ".", 3) - if parts[1] != "" && payload != nil { + if claims != "" && payload != nil { return nil, fmt.Errorf("go-jose/go-jose: payload is not detached") } - rawProtected, err := base64.RawURLEncoding.DecodeString(parts[0]) + rawProtected, err := base64.RawURLEncoding.DecodeString(protected) if err != nil { return nil, err } if payload == nil { - payload, err = base64.RawURLEncoding.DecodeString(parts[1]) + payload, err = base64.RawURLEncoding.DecodeString(claims) if err != nil { return nil, err } } - signature, err := base64.RawURLEncoding.DecodeString(parts[2]) + signature, err := base64.RawURLEncoding.DecodeString(sig) if err != nil { return nil, err } diff --git a/vendor/github.com/go-jose/go-jose/v4/shared.go b/vendor/github.com/go-jose/go-jose/v4/shared.go index 1ec3396126..35130b3aa8 100644 --- a/vendor/github.com/go-jose/go-jose/v4/shared.go +++ b/vendor/github.com/go-jose/go-jose/v4/shared.go @@ -77,6 +77,9 @@ var ( // ErrUnsupportedEllipticCurve indicates unsupported or unknown elliptic curve has been found. ErrUnsupportedEllipticCurve = errors.New("go-jose/go-jose: unsupported/unknown elliptic curve") + + // ErrUnsupportedCriticalHeader is returned when a header is marked critical but not supported by go-jose. + ErrUnsupportedCriticalHeader = errors.New("go-jose/go-jose: unsupported critical header") ) // Key management algorithms @@ -167,8 +170,8 @@ const ( ) // supportedCritical is the set of supported extensions that are understood and processed. -var supportedCritical = map[string]bool{ - headerB64: true, +var supportedCritical = map[string]struct{}{ + headerB64: {}, } // rawHeader represents the JOSE header for JWE/JWS objects (used for parsing). @@ -346,6 +349,32 @@ func (parsed rawHeader) getCritical() ([]string, error) { return q, nil } +// checkNoCritical verifies there are no critical headers present. +func (parsed rawHeader) checkNoCritical() error { + if _, ok := parsed[headerCritical]; ok { + return ErrUnsupportedCriticalHeader + } + + return nil +} + +// checkSupportedCritical verifies there are no unsupported critical headers. +// Supported headers are passed in as a set: map of names to empty structs +func (parsed rawHeader) checkSupportedCritical(supported map[string]struct{}) error { + crit, err := parsed.getCritical() + if err != nil { + return err + } + + for _, name := range crit { + if _, ok := supported[name]; !ok { + return ErrUnsupportedCriticalHeader + } + } + + return nil +} + // getS2C extracts parsed "p2c" from the raw JSON. func (parsed rawHeader) getP2C() (int, error) { v := parsed[headerP2C] diff --git a/vendor/github.com/go-jose/go-jose/v4/signing.go b/vendor/github.com/go-jose/go-jose/v4/signing.go index 3dec0112b6..5dbd04c278 100644 --- a/vendor/github.com/go-jose/go-jose/v4/signing.go +++ b/vendor/github.com/go-jose/go-jose/v4/signing.go @@ -404,15 +404,23 @@ func (obj JSONWebSignature) DetachedVerify(payload []byte, verificationKey inter } signature := obj.Signatures[0] - headers := signature.mergedHeaders() - critical, err := headers.getCritical() - if err != nil { - return err + + if signature.header != nil { + // Per https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.11, + // 4.1.11. "crit" (Critical) Header Parameter + // "When used, this Header Parameter MUST be integrity + // protected; therefore, it MUST occur only within the JWS + // Protected Header." + err = signature.header.checkNoCritical() + if err != nil { + return err + } } - for _, name := range critical { - if !supportedCritical[name] { - return ErrCryptoFailure + if signature.protected != nil { + err = signature.protected.checkSupportedCritical(supportedCritical) + if err != nil { + return err } } @@ -421,6 +429,7 @@ func (obj JSONWebSignature) DetachedVerify(payload []byte, verificationKey inter return ErrCryptoFailure } + headers := signature.mergedHeaders() alg := headers.getSignatureAlgorithm() err = verifier.verifyPayload(input, signature.Signature, alg) if err == nil { @@ -469,14 +478,22 @@ func (obj JSONWebSignature) DetachedVerifyMulti(payload []byte, verificationKey outer: for i, signature := range obj.Signatures { - headers := signature.mergedHeaders() - critical, err := headers.getCritical() - if err != nil { - continue + if signature.header != nil { + // Per https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.11, + // 4.1.11. "crit" (Critical) Header Parameter + // "When used, this Header Parameter MUST be integrity + // protected; therefore, it MUST occur only within the JWS + // Protected Header." + err = signature.header.checkNoCritical() + if err != nil { + continue outer + } } - for _, name := range critical { - if !supportedCritical[name] { + if signature.protected != nil { + // Check for only supported critical headers + err = signature.protected.checkSupportedCritical(supportedCritical) + if err != nil { continue outer } } @@ -486,6 +503,7 @@ outer: continue } + headers := signature.mergedHeaders() alg := headers.getSignatureAlgorithm() err = verifier.verifyPayload(input, signature.Signature, alg) if err == nil { diff --git a/vendor/github.com/go-jose/go-jose/v4/symmetric.go b/vendor/github.com/go-jose/go-jose/v4/symmetric.go index a69103b084..f2ff29e179 100644 --- a/vendor/github.com/go-jose/go-jose/v4/symmetric.go +++ b/vendor/github.com/go-jose/go-jose/v4/symmetric.go @@ -21,6 +21,7 @@ import ( "crypto/aes" "crypto/cipher" "crypto/hmac" + "crypto/pbkdf2" "crypto/rand" "crypto/sha256" "crypto/sha512" @@ -30,8 +31,6 @@ import ( "hash" "io" - "golang.org/x/crypto/pbkdf2" - josecipher "github.com/go-jose/go-jose/v4/cipher" ) @@ -330,7 +329,10 @@ func (ctx *symmetricKeyCipher) encryptKey(cek []byte, alg KeyAlgorithm) (recipie // derive key keyLen, h := getPbkdf2Params(alg) - key := pbkdf2.Key(ctx.key, salt, ctx.p2c, keyLen, h) + key, err := pbkdf2.Key(h, string(ctx.key), salt, ctx.p2c, keyLen) + if err != nil { + return recipientInfo{}, nil + } // use AES cipher with derived key block, err := aes.NewCipher(key) @@ -364,11 +366,21 @@ func (ctx *symmetricKeyCipher) encryptKey(cek []byte, alg KeyAlgorithm) (recipie // Decrypt the content encryption key. func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) { - switch headers.getAlgorithm() { - case DIRECT: - cek := make([]byte, len(ctx.key)) - copy(cek, ctx.key) - return cek, nil + if recipient == nil { + return nil, fmt.Errorf("go-jose/go-jose: missing recipient") + } + + alg := headers.getAlgorithm() + if alg == DIRECT { + return bytes.Clone(ctx.key), nil + } + + encryptedKey := recipient.encryptedKey + if len(encryptedKey) == 0 { + return nil, fmt.Errorf("go-jose/go-jose: missing JWE Encrypted Key") + } + + switch alg { case A128GCMKW, A192GCMKW, A256GCMKW: aead := newAESGCM(len(ctx.key)) @@ -383,7 +395,7 @@ func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipien parts := &aeadParts{ iv: iv.bytes(), - ciphertext: recipient.encryptedKey, + ciphertext: encryptedKey, tag: tag.bytes(), } @@ -399,7 +411,7 @@ func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipien return nil, err } - cek, err := josecipher.KeyUnwrap(block, recipient.encryptedKey) + cek, err := josecipher.KeyUnwrap(block, encryptedKey) if err != nil { return nil, err } @@ -432,7 +444,10 @@ func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipien // derive key keyLen, h := getPbkdf2Params(alg) - key := pbkdf2.Key(ctx.key, salt, p2c, keyLen, h) + key, err := pbkdf2.Key(h, string(ctx.key), salt, p2c, keyLen) + if err != nil { + return nil, err + } // use AES cipher with derived key block, err := aes.NewCipher(key) @@ -440,7 +455,7 @@ func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipien return nil, err } - cek, err := josecipher.KeyUnwrap(block, recipient.encryptedKey) + cek, err := josecipher.KeyUnwrap(block, encryptedKey) if err != nil { return nil, err } diff --git a/vendor/modules.txt b/vendor/modules.txt index 2786cd88dd..d2f8ed3f59 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -132,8 +132,8 @@ github.com/go-jose/go-jose/v3 github.com/go-jose/go-jose/v3/cipher github.com/go-jose/go-jose/v3/json github.com/go-jose/go-jose/v3/jwt -# github.com/go-jose/go-jose/v4 v4.1.3 => github.com/go-jose/go-jose/v4 v4.0.5 -## explicit; go 1.21 +# github.com/go-jose/go-jose/v4 v4.1.4 => github.com/go-jose/go-jose/v4 v4.1.4 +## explicit; go 1.24.0 github.com/go-jose/go-jose/v4 github.com/go-jose/go-jose/v4/cipher github.com/go-jose/go-jose/v4/json @@ -1341,7 +1341,7 @@ sigs.k8s.io/structured-merge-diff/v4/value ## explicit; go 1.22 sigs.k8s.io/yaml sigs.k8s.io/yaml/goyaml.v2 -# github.com/go-jose/go-jose/v4 => github.com/go-jose/go-jose/v4 v4.0.5 +# github.com/go-jose/go-jose/v4 => github.com/go-jose/go-jose/v4 v4.1.4 # github.com/google/gnostic-models => github.com/google/gnostic-models v0.6.9 # k8s.io/api => k8s.io/api v0.32.8 # k8s.io/apimachinery => k8s.io/apimachinery v0.32.8 From c499229e059aab972814866915c70b78cc216138 Mon Sep 17 00:00:00 2001 From: Chmouel Boudjnah Date: Wed, 3 Jun 2026 13:58:54 +0200 Subject: [PATCH 5/6] chore(deps): bump tekton pipeline to v1.9.3 Signed-off-by: Chmouel Boudjnah --- go.mod | 5 +- go.sum | 26 ++-- .../github.com/google/cel-go/cel/BUILD.bazel | 7 +- .../github.com/google/cel-go/cel/library.go | 1 - .../github.com/google/cel-go/cel/optimizer.go | 54 +++++++-- .../google/cel-go/checker/checker.go | 59 +++++++-- .../github.com/google/cel-go/checker/env.go | 112 ++++++++++++++---- .../google/cel-go/checker/scopes.go | 25 ++++ .../google/cel-go/common/ast/ast.go | 55 +++++++++ .../google/cel-go/common/debug/debug.go | 15 +++ .../google/cel-go/common/env/BUILD.bazel | 2 +- .../google/cel-go/common/env/env.go | 2 +- .../google/cel-go/common/types/BUILD.bazel | 1 - .../google/cel-go/common/types/bool.go | 2 +- .../google/cel-go/common/types/bytes.go | 2 +- .../google/cel-go/common/types/double.go | 2 +- .../google/cel-go/common/types/duration.go | 2 +- .../google/cel-go/common/types/int.go | 2 +- .../google/cel-go/common/types/json_value.go | 9 +- .../google/cel-go/common/types/list.go | 9 +- .../google/cel-go/common/types/map.go | 48 ++++++-- .../google/cel-go/common/types/null.go | 8 +- .../google/cel-go/common/types/object.go | 2 +- .../google/cel-go/common/types/string.go | 2 +- .../google/cel-go/common/types/timestamp.go | 2 +- .../google/cel-go/common/types/uint.go | 2 +- .../cel-go/interpreter/attribute_patterns.go | 12 +- .../google/cel-go/interpreter/attributes.go | 53 +++++++-- .../cel-go/interpreter/interpretable.go | 5 + .../google/cel-go/interpreter/interpreter.go | 16 +++ .../google/cel-go/interpreter/planner.go | 104 +++++++++++----- .../github.com/google/cel-go/parser/helper.go | 2 +- .../github.com/stoewer/go-strcase/.gitignore | 17 --- .../stoewer/go-strcase/.golangci.yml | 19 --- vendor/github.com/stoewer/go-strcase/LICENSE | 21 ---- .../github.com/stoewer/go-strcase/README.md | 50 -------- vendor/github.com/stoewer/go-strcase/camel.go | 43 ------- vendor/github.com/stoewer/go-strcase/doc.go | 8 -- .../github.com/stoewer/go-strcase/helper.go | 77 ------------ vendor/github.com/stoewer/go-strcase/kebab.go | 14 --- vendor/github.com/stoewer/go-strcase/snake.go | 58 --------- .../pod/affinity_assitant_template.go | 8 ++ .../pkg/apis/pipeline/pod/template.go | 20 +++- .../pipeline/pod/zz_generated.deepcopy.go | 5 + .../apis/pipeline/v1/container_validation.go | 6 +- .../pkg/apis/pipeline/v1/openapi_generated.go | 15 +++ .../apis/pipeline/v1/pipeline_validation.go | 26 +++- .../pkg/apis/pipeline/v1/pipelinerun_types.go | 2 +- .../pkg/apis/pipeline/v1/swagger.json | 11 ++ .../pkg/apis/pipeline/v1/taskrun_types.go | 4 + .../pipeline/v1alpha1/openapi_generated.go | 14 +++ .../pkg/apis/pipeline/v1alpha1/swagger.json | 8 ++ .../pipeline/v1beta1/openapi_generated.go | 15 +++ .../pipeline/v1beta1/pipeline_validation.go | 26 +++- .../pipeline/v1beta1/pipelinerun_types.go | 2 +- .../pkg/apis/pipeline/v1beta1/swagger.json | 11 ++ .../apis/pipeline/v1beta1/task_validation.go | 5 +- vendor/modules.txt | 11 +- 58 files changed, 687 insertions(+), 457 deletions(-) delete mode 100644 vendor/github.com/stoewer/go-strcase/.gitignore delete mode 100644 vendor/github.com/stoewer/go-strcase/.golangci.yml delete mode 100644 vendor/github.com/stoewer/go-strcase/LICENSE delete mode 100644 vendor/github.com/stoewer/go-strcase/README.md delete mode 100644 vendor/github.com/stoewer/go-strcase/camel.go delete mode 100644 vendor/github.com/stoewer/go-strcase/doc.go delete mode 100644 vendor/github.com/stoewer/go-strcase/helper.go delete mode 100644 vendor/github.com/stoewer/go-strcase/kebab.go delete mode 100644 vendor/github.com/stoewer/go-strcase/snake.go diff --git a/go.mod b/go.mod index 825168f5d2..5add39981c 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/cloudevents/sdk-go/v2 v2.16.2 github.com/fvbommel/sortorder v1.1.0 github.com/gobwas/glob v0.2.3 - github.com/google/cel-go v0.26.1 + github.com/google/cel-go v0.27.0 github.com/google/go-cmp v0.7.0 github.com/google/go-github/scrape v0.0.0-20260114152324-5458fbc09dc5 github.com/google/go-github/v75 v75.0.0 @@ -27,7 +27,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/spf13/cobra v1.10.2 github.com/stretchr/testify v1.11.1 - github.com/tektoncd/pipeline v1.7.0 + github.com/tektoncd/pipeline v1.9.3 gitlab.com/gitlab-org/api/client-go v1.14.0 go.opencensus.io v0.24.0 go.uber.org/zap v1.27.1 @@ -136,7 +136,6 @@ require ( github.com/prometheus/statsd_exporter v0.28.0 // indirect github.com/rickb777/date v1.22.0 // indirect github.com/spf13/pflag v1.0.10 // indirect - github.com/stoewer/go-strcase v1.3.1 // indirect github.com/xlzd/gotp v0.1.0 // indirect go.uber.org/automaxprocs v1.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect diff --git a/go.sum b/go.sum index 0ea43950c7..384d54e9a3 100644 --- a/go.sum +++ b/go.sum @@ -245,8 +245,8 @@ github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6 github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ= -github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= +github.com/google/cel-go v0.27.0 h1:e7ih85+4qVrBuqQWTW4FKSqZYokVuc3HnhH5keboFTo= +github.com/google/cel-go v0.27.0/go.mod h1:tTJ11FWqnhw5KKpnWpvW9CJC3Y9GK4EIS0WXnBbebzw= github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -474,8 +474,6 @@ github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiT github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stoewer/go-strcase v1.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs= -github.com/stoewer/go-strcase v1.3.1/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -492,8 +490,8 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/stvp/go-udp-testing v0.0.0-20201019212854-469649b16807/go.mod h1:7jxmlfBCDBXRzr0eAQJ48XC1hBu1np4CS5+cHEYfwpc= -github.com/tektoncd/pipeline v1.7.0 h1:+Rd/YGpxV8sgEmW9unSiS8RgBE4DqbPdz6zPh2pYDnk= -github.com/tektoncd/pipeline v1.7.0/go.mod h1:+HsDce5knjq77Xv9FWg1W2wTuJRznR9lLEbkVjo62lU= +github.com/tektoncd/pipeline v1.9.3 h1:7Z+V2VX5wjz9LoNa16E1RbgH9mpYy5B1KnAMm3H0czc= +github.com/tektoncd/pipeline v1.9.3/go.mod h1:pEruzPp4JM8JK8Nnnih46IPgdtxRPot/i9pUZo8lA9I= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= @@ -519,16 +517,16 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= diff --git a/vendor/github.com/google/cel-go/cel/BUILD.bazel b/vendor/github.com/google/cel-go/cel/BUILD.bazel index c12e4904da..89cf460d3a 100644 --- a/vendor/github.com/google/cel-go/cel/BUILD.bazel +++ b/vendor/github.com/google/cel-go/cel/BUILD.bazel @@ -21,7 +21,7 @@ go_library( "prompt.go", "validator.go", ], - embedsrcs = ["//cel/templates"], + embedsrcs = ["templates/authoring.tmpl"], importpath = "github.com/google/cel-go/cel", visibility = ["//visibility:public"], deps = [ @@ -96,3 +96,8 @@ go_test( "@org_golang_google_protobuf//types/known/wrapperspb:go_default_library", ], ) + +exports_files( + ["templates/authoring.tmpl"], + visibility = ["//visibility:public"], +) \ No newline at end of file diff --git a/vendor/github.com/google/cel-go/cel/library.go b/vendor/github.com/google/cel-go/cel/library.go index 59a10e81de..bc13add890 100644 --- a/vendor/github.com/google/cel-go/cel/library.go +++ b/vendor/github.com/google/cel-go/cel/library.go @@ -182,7 +182,6 @@ func (lib *stdLibrary) CompileOptions() []EnvOption { if err = lib.subset.Validate(); err != nil { return nil, err } - e.variables = append(e.variables, stdlib.Types()...) for _, fn := range funcs { existing, found := e.functions[fn.Name()] if found { diff --git a/vendor/github.com/google/cel-go/cel/optimizer.go b/vendor/github.com/google/cel-go/cel/optimizer.go index 9a2a97a647..6e260a93cf 100644 --- a/vendor/github.com/google/cel-go/cel/optimizer.go +++ b/vendor/github.com/google/cel-go/cel/optimizer.go @@ -15,6 +15,7 @@ package cel import ( + "fmt" "sort" "github.com/google/cel-go/common" @@ -29,17 +30,43 @@ import ( // passes to ensure that the final optimized output is a valid expression with metadata consistent // with what would have been generated from a parsed and checked expression. // -// Note: source position information is best-effort and likely wrong, but optimized expressions +// Note: source position information is best-effort and incomplete, but optimized expressions // should be suitable for calls to parser.Unparse. type StaticOptimizer struct { optimizers []ASTOptimizer + // If set, Optimize() will use this Source instead of the one from the AST. + sourceOverride *Source } +type OptimizerOption func(*StaticOptimizer) (*StaticOptimizer, error) + // NewStaticOptimizer creates a StaticOptimizer with a sequence of ASTOptimizer's to be applied // to a checked expression. -func NewStaticOptimizer(optimizers ...ASTOptimizer) *StaticOptimizer { - return &StaticOptimizer{ - optimizers: optimizers, +func NewStaticOptimizer(options ...any) (*StaticOptimizer, error) { + so := &StaticOptimizer{} + var err error + for _, opt := range options { + switch v := opt.(type) { + case ASTOptimizer: + so.optimizers = append(so.optimizers, v) + case OptimizerOption: + so, err = v(so) + if err != nil { + return nil, err + } + default: + return nil, fmt.Errorf("unsupported option: %v", v) + } + } + return so, nil +} + +// OptimizeWithSource overrides the source used by the optimizer. +// Note this will cause the source info from the AST passed to Optimize() to be discarded. +func OptimizeWithSource(source Source) OptimizerOption { + return func(so *StaticOptimizer) (*StaticOptimizer, error) { + so.sourceOverride = &source + return so, nil } } @@ -49,15 +76,21 @@ func NewStaticOptimizer(optimizers ...ASTOptimizer) *StaticOptimizer { func (opt *StaticOptimizer) Optimize(env *Env, a *Ast) (*Ast, *Issues) { // Make a copy of the AST to be optimized. optimized := ast.Copy(a.NativeRep()) + source := a.Source() + sourceInfo := optimized.SourceInfo() + if opt.sourceOverride != nil { + source = *opt.sourceOverride + sourceInfo = ast.NewSourceInfo(*opt.sourceOverride) + } ids := newIDGenerator(ast.MaxID(a.NativeRep())) // Create the optimizer context, could be pooled in the future. - issues := NewIssues(common.NewErrors(a.Source())) + issues := NewIssues(common.NewErrors(source)) baseFac := ast.NewExprFactory() exprFac := &optimizerExprFactory{ idGenerator: ids, fac: baseFac, - sourceInfo: optimized.SourceInfo(), + sourceInfo: sourceInfo, } ctx := &OptimizerContext{ optimizerExprFactory: exprFac, @@ -80,7 +113,7 @@ func (opt *StaticOptimizer) Optimize(env *Env, a *Ast) (*Ast, *Issues) { // Recheck the updated expression for any possible type-agreement or validation errors. parsed := &Ast{ - source: a.Source(), + source: source, impl: ast.NewAST(expr, info)} checked, iss := ctx.Check(parsed) if iss.Err() != nil { @@ -91,7 +124,7 @@ func (opt *StaticOptimizer) Optimize(env *Env, a *Ast) (*Ast, *Issues) { // Return the optimized result. return &Ast{ - source: a.Source(), + source: source, impl: optimized, }, nil } @@ -100,6 +133,8 @@ func (opt *StaticOptimizer) Optimize(env *Env, a *Ast) (*Ast, *Issues) { // that the ids within the expression correspond to the ids within macros. func normalizeIDs(idGen ast.IDGenerator, optimized ast.Expr, info *ast.SourceInfo) { optimized.RenumberIDs(idGen) + info.RenumberIDs(idGen) + if len(info.MacroCalls()) == 0 { return } @@ -260,6 +295,9 @@ func (opt *optimizerExprFactory) CopyASTAndMetadata(a *ast.AST) ast.Expr { for macroID, call := range copyInfo.MacroCalls() { opt.SetMacroCall(macroID, call) } + for id, offset := range copyInfo.OffsetRanges() { + opt.sourceInfo.SetOffsetRange(id, offset) + } return copyExpr } diff --git a/vendor/github.com/google/cel-go/checker/checker.go b/vendor/github.com/google/cel-go/checker/checker.go index 0057c16ccb..d07d8e799e 100644 --- a/vendor/github.com/google/cel-go/checker/checker.go +++ b/vendor/github.com/google/cel-go/checker/checker.go @@ -19,6 +19,8 @@ package checker import ( "fmt" "reflect" + "slices" + "strings" "github.com/google/cel-go/common" "github.com/google/cel-go/common/ast" @@ -65,6 +67,10 @@ func Check(parsed *ast.AST, source common.Source, env *Env) (*ast.AST, *common.E for id, t := range c.TypeMap() { c.SetType(id, substitute(c.mappings, t, true)) } + // Remove source info for IDs without a corresponding AST node. This can happen because + // check() deletes some nodes while rewriting the AST. For example the Select operand is + // deleted when a variable reference is replaced with a Ident expression. + c.AST.ClearUnusedIDs() return c.AST, errs } @@ -104,11 +110,15 @@ func (c *checker) check(e ast.Expr) { func (c *checker) checkIdent(e ast.Expr) { identName := e.AsIdent() // Check to see if the identifier is declared. - if ident := c.env.LookupIdent(identName); ident != nil { + if ident := c.env.resolveSimpleIdent(identName); ident != nil { + name := strings.TrimPrefix(ident.Name(), ".") + if ident.requiresDisambiguation { + name = "." + name + } c.setType(e, ident.Type()) - c.setReference(e, ast.NewIdentReference(ident.Name(), ident.Value())) + c.setReference(e, ast.NewIdentReference(name, ident.Value())) // Overwrite the identifier with its fully qualified name. - e.SetKindCase(c.NewIdent(e.ID(), ident.Name())) + e.SetKindCase(c.NewIdent(e.ID(), name)) return } @@ -119,18 +129,22 @@ func (c *checker) checkIdent(e ast.Expr) { func (c *checker) checkSelect(e ast.Expr) { sel := e.AsSelect() // Before traversing down the tree, try to interpret as qualified name. - qname, found := containers.ToQualifiedName(e) + qualifiers, found := c.computeQualifiers(e) if found { - ident := c.env.LookupIdent(qname) + ident := c.env.resolveQualifiedIdent(qualifiers...) if ident != nil { // We don't check for a TestOnly expression here since the `found` result is // always going to be false for TestOnly expressions. // Rewrite the node to be a variable reference to the resolved fully-qualified // variable name. + name := ident.Name() + if ident.requiresDisambiguation { + name = "." + name + } c.setType(e, ident.Type()) - c.setReference(e, ast.NewIdentReference(ident.Name(), ident.Value())) - e.SetKindCase(c.NewIdent(e.ID(), ident.Name())) + c.setReference(e, ast.NewIdentReference(name, ident.Value())) + e.SetKindCase(c.NewIdent(e.ID(), name)) return } } @@ -142,6 +156,29 @@ func (c *checker) checkSelect(e ast.Expr) { c.setType(e, substitute(c.mappings, resultType, false)) } +// computeQualifiers computes the qualified names parts of a select expression. +func (c *checker) computeQualifiers(e ast.Expr) ([]string, bool) { + var qualifiers []string + for e.Kind() == ast.SelectKind { + sel := e.AsSelect() + // test only expressions are not considered for qualified name selection. + if sel.IsTestOnly() { + return qualifiers, false + } + // otherwise append the select field name to the qualifier list (reverse order) + qualifiers = append(qualifiers, sel.FieldName()) + e = sel.Operand() + // If the next operand is an identifier, then append it, reverse the name sequence + // and return it to the caller.s + if e.Kind() == ast.IdentKind { + qualifiers = append(qualifiers, e.AsIdent()) + slices.Reverse(qualifiers) + return qualifiers, true + } + } + return qualifiers, false +} + func (c *checker) checkOptSelect(e ast.Expr) { // Collect metadata related to the opt select call packaged by the parser. call := e.AsCall() @@ -234,7 +271,7 @@ func (c *checker) checkCall(e ast.Expr) { // Regular static call with simple name. if !call.IsMemberFunction() { // Check for the existence of the function. - fn := c.env.LookupFunction(fnName) + fn := c.env.lookupFunction(fnName) if fn == nil { c.errors.undeclaredReference(e.ID(), c.location(e), c.env.container.Name(), fnName) c.setType(e, types.ErrorType) @@ -256,7 +293,7 @@ func (c *checker) checkCall(e ast.Expr) { qualifiedPrefix, maybeQualified := containers.ToQualifiedName(target) if maybeQualified { maybeQualifiedName := qualifiedPrefix + "." + fnName - fn := c.env.LookupFunction(maybeQualifiedName) + fn := c.env.lookupFunction(maybeQualifiedName) if fn != nil { // The function name is namespaced and so preserving the target operand would // be an inaccurate representation of the desired evaluation behavior. @@ -269,7 +306,7 @@ func (c *checker) checkCall(e ast.Expr) { // Regular instance call. c.check(target) - fn := c.env.LookupFunction(fnName) + fn := c.env.lookupFunction(fnName) // Function found, attempt overload resolution. if fn != nil { c.resolveOverloadOrError(e, fn, target, args) @@ -441,7 +478,7 @@ func (c *checker) checkCreateStruct(e ast.Expr) { msgVal := e.AsStruct() // Determine the type of the message. resultType := types.ErrorType - ident := c.env.LookupIdent(msgVal.TypeName()) + ident := c.env.resolveTypeIdent(msgVal.TypeName()) if ident == nil { c.errors.undeclaredReference( e.ID(), c.location(e), c.env.container.Name(), msgVal.TypeName()) diff --git a/vendor/github.com/google/cel-go/checker/env.go b/vendor/github.com/google/cel-go/checker/env.go index d5ac05014e..6d991eba10 100644 --- a/vendor/github.com/google/cel-go/checker/env.go +++ b/vendor/github.com/google/cel-go/checker/env.go @@ -129,45 +129,111 @@ func (e *Env) AddFunctions(declarations ...*decls.FunctionDecl) error { return formatError(errMsgs) } -// LookupIdent returns a Decl proto for typeName as an identifier in the Env. -// Returns nil if no such identifier is found in the Env. -func (e *Env) LookupIdent(name string) *decls.VariableDecl { +// newAttrResolution creates a new attribute resolution value. +func newAttrResolution(ident *decls.VariableDecl, requiresDisambiguation bool) *attributeResolution { + return &attributeResolution{ + VariableDecl: ident, + requiresDisambiguation: requiresDisambiguation, + } +} + +// attributeResolution wraps an existing variable and denotes whether disambiguation is needed +// during variable resolution. +type attributeResolution struct { + *decls.VariableDecl + + // requiresDisambiguation indicates the variable name should be dot-prefixed. + requiresDisambiguation bool +} + +// resolveSimpleIdent determines the resolved attribute for a single identifier. +func (e *Env) resolveSimpleIdent(name string) *attributeResolution { + local := e.lookupLocalIdent(name) + if local != nil && !strings.HasPrefix(name, ".") { + return newAttrResolution(local, false) + } for _, candidate := range e.container.ResolveCandidateNames(name) { - if ident := e.declarations.FindIdent(candidate); ident != nil { - return ident + if ident := e.lookupGlobalIdent(candidate); ident != nil { + return newAttrResolution(ident, local != nil) } + } + return nil +} - // Next try to import the name as a reference to a message type. If found, - // the declaration is added to the outest (global) scope of the - // environment, so next time we can access it faster. - if t, found := e.provider.FindStructType(candidate); found { - decl := decls.NewVariable(candidate, t) - e.declarations.AddIdent(decl) - return decl +// resolveQualifiedIdent determines the resolved attribute for a qualified identifier. +func (e *Env) resolveQualifiedIdent(qualifiers ...string) *attributeResolution { + if len(qualifiers) == 1 { + return e.resolveSimpleIdent(qualifiers[0]) + } + local := e.lookupLocalIdent(qualifiers[0]) + if local != nil && !strings.HasPrefix(qualifiers[0], ".") { + // this should resolve through a field selection rather than a qualified identifier + return nil + } + // The qualifiers are concatenated together to indicate the qualified name to search + // for as a global identifier. Since select expressions are resolved from leaf to root + // if the fully concatenated string doesn't match a global identifier, indicate that + // no variable was found to continue the traversal up to the next simpler name. + varName := strings.Join(qualifiers, ".") + for _, candidate := range e.container.ResolveCandidateNames(varName) { + if ident := e.lookupGlobalIdent(candidate); ident != nil { + return newAttrResolution(ident, local != nil) } + } + return nil +} +// resolveTypeIdent returns a Decl proto for typeName as an identifier in the Env. +// Returns nil if no such identifier is found in the Env. +func (e *Env) resolveTypeIdent(name string) *decls.VariableDecl { + for _, candidate := range e.container.ResolveCandidateNames(name) { + // Try to import the name as a reference to a message type. if i, found := e.provider.FindIdent(candidate); found { if t, ok := i.(*types.Type); ok { - decl := decls.NewVariable(candidate, types.NewTypeTypeWithParam(t)) - e.declarations.AddIdent(decl) - return decl + return decls.NewVariable(candidate, types.NewTypeTypeWithParam(t)) } } + // Next, try to find the struct type. + if t, found := e.provider.FindStructType(candidate); found { + return decls.NewVariable(candidate, t) + } + } + return nil +} + +// lookupLocalIdent finds the variable candidate in a local scope, returning nil if +// the candidate variable name is not a local variable. +func (e *Env) lookupLocalIdent(candidate string) *decls.VariableDecl { + return e.declarations.FindLocalIdent(candidate) +} - // Next try to import this as an enum value by splitting the name in a type prefix and - // the enum inside. - if enumValue := e.provider.EnumValue(candidate); enumValue.Type() != types.ErrType { - decl := decls.NewConstant(candidate, types.IntType, enumValue) - e.declarations.AddIdent(decl) - return decl +// lookupGlobalIdent finds a candidate variable name in the root scope, returning +// nil if the identifier is not in the global scope. +func (e *Env) lookupGlobalIdent(candidate string) *decls.VariableDecl { + // Try to resolve the global identifier first. + if ident := e.declarations.FindGlobalIdent(candidate); ident != nil { + return ident + } + // Next try to import the name as a reference to a message type. + if i, found := e.provider.FindIdent(candidate); found { + if t, ok := i.(*types.Type); ok { + return decls.NewVariable(candidate, types.NewTypeTypeWithParam(t)) } } + if t, found := e.provider.FindStructType(candidate); found { + return decls.NewVariable(candidate, t) + } + // Next try to import this as an enum value by splitting the name in a type prefix and + // the enum inside. + if enumValue := e.provider.EnumValue(candidate); enumValue.Type() != types.ErrType { + return decls.NewConstant(candidate, types.IntType, enumValue) + } return nil } -// LookupFunction returns a Decl proto for typeName as a function in env. +// lookupFunction returns a Decl proto for typeName as a function in env. // Returns nil if no such function is found in env. -func (e *Env) LookupFunction(name string) *decls.FunctionDecl { +func (e *Env) lookupFunction(name string) *decls.FunctionDecl { for _, candidate := range e.container.ResolveCandidateNames(name) { if fn := e.declarations.FindFunction(candidate); fn != nil { return fn diff --git a/vendor/github.com/google/cel-go/checker/scopes.go b/vendor/github.com/google/cel-go/checker/scopes.go index 8bb73ddb6a..9ae9832e15 100644 --- a/vendor/github.com/google/cel-go/checker/scopes.go +++ b/vendor/github.com/google/cel-go/checker/scopes.go @@ -15,6 +15,8 @@ package checker import ( + "strings" + "github.com/google/cel-go/common/decls" ) @@ -76,6 +78,7 @@ func (s *Scopes) AddIdent(decl *decls.VariableDecl) { // found. // Note: The search is performed from innermost to outermost. func (s *Scopes) FindIdent(name string) *decls.VariableDecl { + name = strings.TrimPrefix(name, ".") if ident, found := s.scopes.idents[name]; found { return ident } @@ -89,12 +92,33 @@ func (s *Scopes) FindIdent(name string) *decls.VariableDecl { // nil if one does not exist. // Note: The search is only performed on the current scope and does not search outer scopes. func (s *Scopes) FindIdentInScope(name string) *decls.VariableDecl { + name = strings.TrimPrefix(name, ".") if ident, found := s.scopes.idents[name]; found { return ident } return nil } +// FindLocalIdent finds a locally scoped variable with a given name, ignoring the root scope. +func (s *Scopes) FindLocalIdent(name string) *decls.VariableDecl { + if s == nil || s.parent == nil { + return nil + } + if ident := s.FindIdentInScope(name); ident != nil { + return ident + } + return s.parent.FindLocalIdent(name) +} + +// FindGlobalIdent finds an identifier in the global scope, ignoring all local scopes. +func (s *Scopes) FindGlobalIdent(name string) *decls.VariableDecl { + scope := s + for scope.parent != nil { + scope = scope.parent + } + return scope.FindIdentInScope(name) +} + // SetFunction adds the function Decl to the current scope. // Note: Any previous entry for a function in the current scope with the same name is overwritten. func (s *Scopes) SetFunction(fn *decls.FunctionDecl) { @@ -105,6 +129,7 @@ func (s *Scopes) SetFunction(fn *decls.FunctionDecl) { // The search is performed from innermost to outermost. // Returns nil if no such function in Scopes. func (s *Scopes) FindFunction(name string) *decls.FunctionDecl { + name = strings.TrimPrefix(name, ".") if fn, found := s.scopes.functions[name]; found { return fn } diff --git a/vendor/github.com/google/cel-go/common/ast/ast.go b/vendor/github.com/google/cel-go/common/ast/ast.go index 62c09cfc64..3c5ee0c805 100644 --- a/vendor/github.com/google/cel-go/common/ast/ast.go +++ b/vendor/github.com/google/cel-go/common/ast/ast.go @@ -16,6 +16,8 @@ package ast import ( + "slices" + "github.com/google/cel-go/common" "github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types/ref" @@ -160,6 +162,26 @@ func MaxID(a *AST) int64 { return visitor.maxID + 1 } +// IDs returns the set of AST node IDs, including macro calls. +func (a *AST) IDs() map[int64]bool { + visitor := make(idVisitor) + PostOrderVisit(a.Expr(), visitor) + for _, call := range a.SourceInfo().MacroCalls() { + PostOrderVisit(call, visitor) + } + return visitor +} + +// ClearUnusedIDs removes IDs not used in the AST or macro calls from SourceInfo. +func (a *AST) ClearUnusedIDs() { + ids := a.IDs() + for id := range a.SourceInfo().OffsetRanges() { + if !ids[id] { + a.SourceInfo().ClearOffsetRange(id) + } + } +} + // Heights computes the heights of all AST expressions and returns a map from expression id to height. func Heights(a *AST) map[int64]int { visitor := make(heightVisitor) @@ -232,6 +254,23 @@ type SourceInfo struct { macroCalls map[int64]Expr } +// RenumberIDs performs an in-place update of the expression IDs within the SourceInfo. +func (s *SourceInfo) RenumberIDs(idGen IDGenerator) { + if s == nil { + return + } + oldIDs := []int64{} + for id := range s.offsetRanges { + oldIDs = append(oldIDs, id) + } + slices.Sort(oldIDs) + newRanges := make(map[int64]OffsetRange) + for _, id := range oldIDs { + newRanges[idGen(id)] = s.offsetRanges[id] + } + s.offsetRanges = newRanges +} + // SyntaxVersion returns the syntax version associated with the text expression. func (s *SourceInfo) SyntaxVersion() string { if s == nil { @@ -365,6 +404,12 @@ func (s *SourceInfo) ComputeOffset(line, col int32) int32 { line = s.baseLine + line col = s.baseCol + col } + return s.ComputeOffsetAbsolute(line, col) +} + +// ComputeOffsetAbsolute calculates the 0-based character offset from a 1-based line and 0-based column +// based on the absolute line and column of the SourceInfo. +func (s *SourceInfo) ComputeOffsetAbsolute(line, col int32) int32 { if line == 1 { return col } @@ -533,3 +578,13 @@ func (hv heightVisitor) maxEntryHeight(entries ...EntryExpr) int { } return max } + +type idVisitor map[int64]bool + +func (v idVisitor) VisitExpr(e Expr) { + v[e.ID()] = true +} + +func (v idVisitor) VisitEntryExpr(e EntryExpr) { + v[e.ID()] = true +} diff --git a/vendor/github.com/google/cel-go/common/debug/debug.go b/vendor/github.com/google/cel-go/common/debug/debug.go index 75f5f0d636..fbc847f0c1 100644 --- a/vendor/github.com/google/cel-go/common/debug/debug.go +++ b/vendor/github.com/google/cel-go/common/debug/debug.go @@ -312,3 +312,18 @@ func (w *debugWriter) removeIndent() { func (w *debugWriter) String() string { return w.buffer.String() } + +type idAdorner struct{} + +func (a *idAdorner) GetMetadata(elem any) string { + e, isExpr := elem.(ast.Expr) + if !isExpr { + return "" + } + return fmt.Sprintf("@id:%d ", e.ID()) +} + +// ToDebugStringWithIDs returns a string representation with AST node IDs. +func ToDebugStringWithIDs(e ast.Expr) string { + return ToAdornedDebugString(e, &idAdorner{}) +} diff --git a/vendor/github.com/google/cel-go/common/env/BUILD.bazel b/vendor/github.com/google/cel-go/common/env/BUILD.bazel index aebe1e544c..b2e0c29313 100644 --- a/vendor/github.com/google/cel-go/common/env/BUILD.bazel +++ b/vendor/github.com/google/cel-go/common/env/BUILD.bazel @@ -45,6 +45,6 @@ go_test( "//common/operators:go_default_library", "//common/overloads:go_default_library", "//common/types:go_default_library", - "@in_gopkg_yaml_v3//:go_default_library", + "@in_yaml_go_yaml_v3//:go_default_library", ], ) diff --git a/vendor/github.com/google/cel-go/common/env/env.go b/vendor/github.com/google/cel-go/common/env/env.go index d848860c2c..e9c86d3eac 100644 --- a/vendor/github.com/google/cel-go/common/env/env.go +++ b/vendor/github.com/google/cel-go/common/env/env.go @@ -122,7 +122,7 @@ func (c *Config) AddVariableDecls(vars ...*decls.VariableDecl) *Config { return c.AddVariables(convVars...) } -// AddVariables adds one or more vairables to the config. +// AddVariables adds one or more variables to the config. func (c *Config) AddVariables(vars ...*Variable) *Config { c.Variables = append(c.Variables, vars...) return c diff --git a/vendor/github.com/google/cel-go/common/types/BUILD.bazel b/vendor/github.com/google/cel-go/common/types/BUILD.bazel index 7082bc7550..37d4df4954 100644 --- a/vendor/github.com/google/cel-go/common/types/BUILD.bazel +++ b/vendor/github.com/google/cel-go/common/types/BUILD.bazel @@ -40,7 +40,6 @@ go_library( "//common/types/pb:go_default_library", "//common/types/ref:go_default_library", "//common/types/traits:go_default_library", - "@com_github_stoewer_go_strcase//:go_default_library", "@dev_cel_expr//:expr", "@org_golang_google_genproto_googleapis_api//expr/v1alpha1:go_default_library", "@org_golang_google_protobuf//encoding/protojson:go_default_library", diff --git a/vendor/github.com/google/cel-go/common/types/bool.go b/vendor/github.com/google/cel-go/common/types/bool.go index 1f9e107392..5f1e4573e1 100644 --- a/vendor/github.com/google/cel-go/common/types/bool.go +++ b/vendor/github.com/google/cel-go/common/types/bool.go @@ -69,7 +69,7 @@ func (b Bool) ConvertToNative(typeDesc reflect.Type) (any, error) { case boolWrapperType: // Convert the bool to a wrapperspb.BoolValue. return wrapperspb.Bool(bool(b)), nil - case jsonValueType: + case JSONValueType: // Return the bool as a new structpb.Value. return structpb.NewBoolValue(bool(b)), nil default: diff --git a/vendor/github.com/google/cel-go/common/types/bytes.go b/vendor/github.com/google/cel-go/common/types/bytes.go index b59e1fc208..88da05315c 100644 --- a/vendor/github.com/google/cel-go/common/types/bytes.go +++ b/vendor/github.com/google/cel-go/common/types/bytes.go @@ -79,7 +79,7 @@ func (b Bytes) ConvertToNative(typeDesc reflect.Type) (any, error) { case byteWrapperType: // Convert the bytes to a wrapperspb.BytesValue. return wrapperspb.Bytes([]byte(b)), nil - case jsonValueType: + case JSONValueType: // CEL follows the proto3 to JSON conversion by encoding bytes to a string via base64. // The encoding below matches the golang 'encoding/json' behavior during marshaling, // which uses base64.StdEncoding. diff --git a/vendor/github.com/google/cel-go/common/types/double.go b/vendor/github.com/google/cel-go/common/types/double.go index 1e7de9d6e1..02abfee2dc 100644 --- a/vendor/github.com/google/cel-go/common/types/double.go +++ b/vendor/github.com/google/cel-go/common/types/double.go @@ -89,7 +89,7 @@ func (d Double) ConvertToNative(typeDesc reflect.Type) (any, error) { case floatWrapperType: // Convert to a wrapperspb.FloatValue (with truncation). return wrapperspb.Float(float32(d)), nil - case jsonValueType: + case JSONValueType: // Note, there are special cases for proto3 to json conversion that // expect the floating point value to be converted to a NaN, // Infinity, or -Infinity string values, but the jsonpb string diff --git a/vendor/github.com/google/cel-go/common/types/duration.go b/vendor/github.com/google/cel-go/common/types/duration.go index be58d567ed..2207147734 100644 --- a/vendor/github.com/google/cel-go/common/types/duration.go +++ b/vendor/github.com/google/cel-go/common/types/duration.go @@ -106,7 +106,7 @@ func (d Duration) ConvertToNative(typeDesc reflect.Type) (any, error) { case durationValueType: // Unwrap the CEL value to its underlying proto value. return dpb.New(d.Duration), nil - case jsonValueType: + case JSONValueType: // CEL follows the proto3 to JSON conversion. // Note, using jsonpb would wrap the result in extra double quotes. v := d.ConvertToType(StringType) diff --git a/vendor/github.com/google/cel-go/common/types/int.go b/vendor/github.com/google/cel-go/common/types/int.go index 0ac1997b70..60d5a71606 100644 --- a/vendor/github.com/google/cel-go/common/types/int.go +++ b/vendor/github.com/google/cel-go/common/types/int.go @@ -120,7 +120,7 @@ func (i Int) ConvertToNative(typeDesc reflect.Type) (any, error) { case int64WrapperType: // Convert the value to a wrapperspb.Int64Value. return wrapperspb.Int64(int64(i)), nil - case jsonValueType: + case JSONValueType: // The proto-to-JSON conversion rules would convert all 64-bit integer values to JSON // decimal strings. Because CEL ints might come from the automatic widening of 32-bit // values in protos, the JSON type is chosen dynamically based on the value. diff --git a/vendor/github.com/google/cel-go/common/types/json_value.go b/vendor/github.com/google/cel-go/common/types/json_value.go index 13a4efe7ad..90acfe7df3 100644 --- a/vendor/github.com/google/cel-go/common/types/json_value.go +++ b/vendor/github.com/google/cel-go/common/types/json_value.go @@ -22,8 +22,9 @@ import ( // JSON type constants representing the reflected types of protobuf JSON values. var ( - jsonValueType = reflect.TypeOf(&structpb.Value{}) - jsonListValueType = reflect.TypeOf(&structpb.ListValue{}) - jsonStructType = reflect.TypeOf(&structpb.Struct{}) - jsonNullType = reflect.TypeOf(structpb.NullValue_NULL_VALUE) + // JSONValueType describes the protobuf native type for a JSON value. + JSONValueType = reflect.TypeFor[*structpb.Value]() + JSONListType = reflect.TypeFor[*structpb.ListValue]() + JSONStructType = reflect.TypeFor[*structpb.Struct]() + JSONNullType = reflect.TypeFor[structpb.NullValue]() ) diff --git a/vendor/github.com/google/cel-go/common/types/list.go b/vendor/github.com/google/cel-go/common/types/list.go index 8c023f8910..324c0f9694 100644 --- a/vendor/github.com/google/cel-go/common/types/list.go +++ b/vendor/github.com/google/cel-go/common/types/list.go @@ -153,6 +153,9 @@ func (l *baseList) Contains(elem ref.Val) ref.Val { // ConvertToNative implements the ref.Val interface method. func (l *baseList) ConvertToNative(typeDesc reflect.Type) (any, error) { + if typeDesc == reflect.TypeFor[any]() { + typeDesc = reflect.TypeFor[[]any]() + } // If the underlying list value is assignable to the reflected type return it. if reflect.TypeOf(l.value).AssignableTo(typeDesc) { return l.value, nil @@ -164,19 +167,19 @@ func (l *baseList) ConvertToNative(typeDesc reflect.Type) (any, error) { // Attempt to convert the list to a set of well known protobuf types. switch typeDesc { case anyValueType: - json, err := l.ConvertToNative(jsonListValueType) + json, err := l.ConvertToNative(JSONListType) if err != nil { return nil, err } return anypb.New(json.(proto.Message)) - case jsonValueType, jsonListValueType: + case JSONValueType, JSONListType: jsonValues, err := l.ConvertToNative(reflect.TypeOf([]*structpb.Value{})) if err != nil { return nil, err } jsonList := &structpb.ListValue{Values: jsonValues.([]*structpb.Value)} - if typeDesc == jsonListValueType { + if typeDesc == JSONListType { return jsonList, nil } return structpb.NewListValue(jsonList), nil diff --git a/vendor/github.com/google/cel-go/common/types/map.go b/vendor/github.com/google/cel-go/common/types/map.go index b33096197c..e4d6f76574 100644 --- a/vendor/github.com/google/cel-go/common/types/map.go +++ b/vendor/github.com/google/cel-go/common/types/map.go @@ -19,8 +19,8 @@ import ( "reflect" "sort" "strings" + "unicode" - "github.com/stoewer/go-strcase" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protoreflect" @@ -156,6 +156,9 @@ func (m *baseMap) Contains(index ref.Val) ref.Val { func (m *baseMap) ConvertToNative(typeDesc reflect.Type) (any, error) { // If the map is already assignable to the desired type return it, e.g. interfaces and // maps with the same key value types. + if typeDesc == reflect.TypeFor[any]() { + typeDesc = reflect.TypeFor[map[any]any]() + } if reflect.TypeOf(m.value).AssignableTo(typeDesc) { return m.value, nil } @@ -164,19 +167,19 @@ func (m *baseMap) ConvertToNative(typeDesc reflect.Type) (any, error) { } switch typeDesc { case anyValueType: - json, err := m.ConvertToNative(jsonStructType) + json, err := m.ConvertToNative(JSONStructType) if err != nil { return nil, err } return anypb.New(json.(proto.Message)) - case jsonValueType, jsonStructType: + case JSONValueType, JSONStructType: jsonEntries, err := m.ConvertToNative(reflect.TypeOf(map[string]*structpb.Value{})) if err != nil { return nil, err } jsonMap := &structpb.Struct{Fields: jsonEntries.(map[string]*structpb.Value)} - if typeDesc == jsonStructType { + if typeDesc == JSONStructType { return jsonMap, nil } return structpb.NewStructValue(jsonMap), nil @@ -226,7 +229,7 @@ func (m *baseMap) ConvertToNative(typeDesc reflect.Type) (any, error) { return nil, fieldName.(*Err) } name := string(fieldName.(String)) - name = strcase.UpperCamelCase(name) + name = upperCamelCase(name) fieldRef := nativeStruct.FieldByName(name) if !fieldRef.IsValid() { return nil, fmt.Errorf("type conversion error, no such field '%s' in type '%v'", name, typeDesc) @@ -703,12 +706,12 @@ func (m *protoMap) ConvertToNative(typeDesc reflect.Type) (any, error) { // maps with the same key value types. switch typeDesc { case anyValueType: - json, err := m.ConvertToNative(jsonStructType) + json, err := m.ConvertToNative(JSONStructType) if err != nil { return nil, err } return anypb.New(json.(proto.Message)) - case jsonValueType, jsonStructType: + case JSONValueType, JSONStructType: jsonEntries, err := m.ConvertToNative(reflect.TypeOf(map[string]*structpb.Value{})) if err != nil { @@ -716,7 +719,7 @@ func (m *protoMap) ConvertToNative(typeDesc reflect.Type) (any, error) { } jsonMap := &structpb.Struct{ Fields: jsonEntries.(map[string]*structpb.Value)} - if typeDesc == jsonStructType { + if typeDesc == JSONStructType { return jsonMap, nil } return structpb.NewStructValue(jsonMap), nil @@ -1036,3 +1039,32 @@ func InsertMapKeyValue(m traits.Mapper, k, v ref.Val) ref.Val { } return NewErr("insert failed: key %v already exists", k) } + +func upperCamelCase(s string) string { + var newStr strings.Builder + s = strings.TrimSpace(s) + var prev rune + for _, curr := range s { + if prev == 0 || isDelim(prev) { + if !isDelim(curr) { + newStr.WriteRune(unicode.ToUpper(curr)) + } + } else if !isDelim(curr) { + if isLower(prev) { + newStr.WriteRune(curr) + } else { + newStr.WriteRune(unicode.ToLower(curr)) + } + } + prev = curr + } + return newStr.String() +} + +func isDelim(r rune) bool { + return r == '_' || r == '-' +} + +func isLower(r rune) bool { + return r >= 'a' && r <= 'z' +} diff --git a/vendor/github.com/google/cel-go/common/types/null.go b/vendor/github.com/google/cel-go/common/types/null.go index 2c0297fe65..671e1ee5c0 100644 --- a/vendor/github.com/google/cel-go/common/types/null.go +++ b/vendor/github.com/google/cel-go/common/types/null.go @@ -45,7 +45,7 @@ func (n Null) ConvertToNative(typeDesc reflect.Type) (any, error) { switch typeDesc.Kind() { case reflect.Int32: switch typeDesc { - case jsonNullType: + case JSONNullType: return structpb.NullValue_NULL_VALUE, nil case nullReflectType: return n, nil @@ -55,18 +55,18 @@ func (n Null) ConvertToNative(typeDesc reflect.Type) (any, error) { case anyValueType: // Convert to a JSON-null before packing to an Any field since the enum value for JSON // null cannot be packed directly. - pb, err := n.ConvertToNative(jsonValueType) + pb, err := n.ConvertToNative(JSONValueType) if err != nil { return nil, err } return anypb.New(pb.(proto.Message)) - case jsonValueType: + case JSONValueType: return structpb.NewNullValue(), nil case boolWrapperType, byteWrapperType, doubleWrapperType, floatWrapperType, int32WrapperType, int64WrapperType, stringWrapperType, uint32WrapperType, uint64WrapperType, durationValueType, timestampValueType, protoIfaceType: return nil, nil - case jsonListValueType, jsonStructType: + case JSONListType, JSONStructType: // skip handling default: if typeDesc.Implements(protoIfaceType) { diff --git a/vendor/github.com/google/cel-go/common/types/object.go b/vendor/github.com/google/cel-go/common/types/object.go index 776f6954a9..c44eaa942e 100644 --- a/vendor/github.com/google/cel-go/common/types/object.go +++ b/vendor/github.com/google/cel-go/common/types/object.go @@ -71,7 +71,7 @@ func (o *protoObj) ConvertToNative(typeDesc reflect.Type) (any, error) { return srcPB, nil } return anypb.New(srcPB) - case jsonValueType: + case JSONValueType: // Marshal the proto to JSON first, and then rehydrate as protobuf.Value as there is no // support for direct conversion from proto.Message to protobuf.Value. bytes, err := protojson.Marshal(srcPB) diff --git a/vendor/github.com/google/cel-go/common/types/string.go b/vendor/github.com/google/cel-go/common/types/string.go index 8aad4701cc..5f5a43358e 100644 --- a/vendor/github.com/google/cel-go/common/types/string.go +++ b/vendor/github.com/google/cel-go/common/types/string.go @@ -72,7 +72,7 @@ func (s String) ConvertToNative(typeDesc reflect.Type) (any, error) { case anyValueType: // Primitives must be wrapped before being set on an Any field. return anypb.New(wrapperspb.String(string(s))) - case jsonValueType: + case JSONValueType: // Convert to a protobuf representation of a JSON String. return structpb.NewStringValue(string(s)), nil case stringWrapperType: diff --git a/vendor/github.com/google/cel-go/common/types/timestamp.go b/vendor/github.com/google/cel-go/common/types/timestamp.go index f7be585916..060caf6bbe 100644 --- a/vendor/github.com/google/cel-go/common/types/timestamp.go +++ b/vendor/github.com/google/cel-go/common/types/timestamp.go @@ -91,7 +91,7 @@ func (t Timestamp) ConvertToNative(typeDesc reflect.Type) (any, error) { case anyValueType: // Pack the underlying time as a tpb.Timestamp into an Any value. return anypb.New(tpb.New(t.Time)) - case jsonValueType: + case JSONValueType: // CEL follows the proto3 to JSON conversion which formats as an RFC 3339 encoded JSON // string. v := t.ConvertToType(StringType) diff --git a/vendor/github.com/google/cel-go/common/types/uint.go b/vendor/github.com/google/cel-go/common/types/uint.go index a93405a134..91d5369daa 100644 --- a/vendor/github.com/google/cel-go/common/types/uint.go +++ b/vendor/github.com/google/cel-go/common/types/uint.go @@ -100,7 +100,7 @@ func (i Uint) ConvertToNative(typeDesc reflect.Type) (any, error) { case anyValueType: // Primitives must be wrapped before being set on an Any field. return anypb.New(wrapperspb.UInt64(uint64(i))) - case jsonValueType: + case JSONValueType: // JSON can accurately represent 32-bit uints as floating point values. if i.isJSONSafe() { return structpb.NewNumberValue(float64(i)), nil diff --git a/vendor/github.com/google/cel-go/interpreter/attribute_patterns.go b/vendor/github.com/google/cel-go/interpreter/attribute_patterns.go index 7d0759e378..41ca5cd219 100644 --- a/vendor/github.com/google/cel-go/interpreter/attribute_patterns.go +++ b/vendor/github.com/google/cel-go/interpreter/attribute_patterns.go @@ -16,6 +16,7 @@ package interpreter import ( "fmt" + "strings" "github.com/google/cel-go/common/containers" "github.com/google/cel-go/common/types" @@ -207,10 +208,19 @@ func (fac *partialAttributeFactory) AbsoluteAttribute(id int64, names ...string) // 'maybe' NamespacedAttribute values are produced using the partialAttributeFactory rather than // the base AttributeFactory implementation. func (fac *partialAttributeFactory) MaybeAttribute(id int64, name string) Attribute { + var names []string + // When there's a single name with a dot prefix, it indicates that the 'maybe' attribute is a + // globally namespaced identifier. + if strings.HasPrefix(name, ".") { + names = append(names, name) + } else { + // In all other cases, the candidate names should be inferred. + names = fac.container.ResolveCandidateNames(name) + } return &maybeAttribute{ id: id, attrs: []NamespacedAttribute{ - fac.AbsoluteAttribute(id, fac.container.ResolveCandidateNames(name)...), + fac.AbsoluteAttribute(id, names...), }, adapter: fac.adapter, provider: fac.provider, diff --git a/vendor/github.com/google/cel-go/interpreter/attributes.go b/vendor/github.com/google/cel-go/interpreter/attributes.go index b1b3aacc83..053cb68510 100644 --- a/vendor/github.com/google/cel-go/interpreter/attributes.go +++ b/vendor/github.com/google/cel-go/interpreter/attributes.go @@ -166,9 +166,17 @@ type attrFactory struct { // The namespaceNames represent the names the variable could have based on namespace // resolution rules. func (r *attrFactory) AbsoluteAttribute(id int64, names ...string) NamespacedAttribute { + disambiguateNames := false + for idx, name := range names { + if strings.HasPrefix(name, ".") { + disambiguateNames = true + names[idx] = strings.TrimPrefix(name, ".") + } + } return &absoluteAttribute{ id: id, namespaceNames: names, + disambiguateNames: disambiguateNames, qualifiers: []Qualifier{}, adapter: r.adapter, provider: r.provider, @@ -193,10 +201,19 @@ func (r *attrFactory) ConditionalAttribute(id int64, expr Interpretable, t, f At // MaybeAttribute collects variants of unchecked AbsoluteAttribute values which could either be // direct variable accesses or some combination of variable access with qualification. func (r *attrFactory) MaybeAttribute(id int64, name string) Attribute { + var names []string + // When there's a single name with a dot prefix, it indicates that the 'maybe' attribute is a + // globally namespaced identifier. + if strings.HasPrefix(name, ".") { + names = append(names, name) + } else { + // In all other cases, the candidate names should be inferred. + names = r.container.ResolveCandidateNames(name) + } return &maybeAttribute{ id: id, attrs: []NamespacedAttribute{ - r.AbsoluteAttribute(id, r.container.ResolveCandidateNames(name)...), + r.AbsoluteAttribute(id, names...), }, adapter: r.adapter, provider: r.provider, @@ -242,10 +259,13 @@ type absoluteAttribute struct { // namespaceNames represent the names the variable could have based on declared container // (package) of the expression. namespaceNames []string - qualifiers []Qualifier - adapter types.Adapter - provider types.Provider - fac AttributeFactory + // disambiguateNames indicates whether the namespaceNames require disambiguation with local variables. + disambiguateNames bool + + qualifiers []Qualifier + adapter types.Adapter + provider types.Provider + fac AttributeFactory errorOnBadPresenceTest bool } @@ -304,15 +324,34 @@ func (a *absoluteAttribute) String() string { // a type, then the result is `nil`, `error` with the error indicating the name of the first // variable searched as missing. func (a *absoluteAttribute) Resolve(vars Activation) (any, error) { + // unwrap any local activations to ensure that we reach the variables provided as input + // to the expression in the event that we need to disambiguate between global and local + // variables. + // + // Presently, only dynamic and constant slot activations created during comprehensions + // support 'unwrapping', which is consistent with how local variables are introduced into CEL. + var inputVars Activation + if a.disambiguateNames { + inputVars = vars + wrapped, ok := inputVars.(activationWrapper) + for ok { + inputVars = wrapped.Unwrap() + wrapped, ok = inputVars.(activationWrapper) + } + } for _, nm := range a.namespaceNames { // If the variable is found, process it. Otherwise, wait until the checks to // determine whether the type is unknown before returning. - obj, found := vars.ResolveName(nm) + v := vars + if a.disambiguateNames { + v = inputVars + } + obj, found := v.ResolveName(nm) if found { if celErr, ok := obj.(*types.Err); ok { return nil, celErr.Unwrap() } - obj, isOpt, err := applyQualifiers(vars, obj, a.qualifiers) + obj, isOpt, err := applyQualifiers(v, obj, a.qualifiers) if err != nil { return nil, err } diff --git a/vendor/github.com/google/cel-go/interpreter/interpretable.go b/vendor/github.com/google/cel-go/interpreter/interpretable.go index 96b5a8ffc0..9c8575db5f 100644 --- a/vendor/github.com/google/cel-go/interpreter/interpretable.go +++ b/vendor/github.com/google/cel-go/interpreter/interpretable.go @@ -1404,6 +1404,11 @@ func (f *folder) Parent() Activation { return f.activation } +// Unwrap returns the parent activation, thus omitting access to local state +func (f *folder) Unwrap() Activation { + return f.activation +} + // UnknownAttributePatterns implements the PartialActivation interface returning the unknown patterns // if they were provided to the input activation, or an empty set if the proxied activation is not partial. func (f *folder) UnknownAttributePatterns() []*AttributePattern { diff --git a/vendor/github.com/google/cel-go/interpreter/interpreter.go b/vendor/github.com/google/cel-go/interpreter/interpreter.go index be57e74392..d81ef1280f 100644 --- a/vendor/github.com/google/cel-go/interpreter/interpreter.go +++ b/vendor/github.com/google/cel-go/interpreter/interpreter.go @@ -137,11 +137,27 @@ func (esa evalStateActivation) asEvalState() EvalState { return esa.state } +// activationWrapper identifies an object carrying local variables which should not be exposed to the user +// Activations used for such purposes can be unwrapped to return the activation which omits local state. +type activationWrapper interface { + // Unwrap returns the Activation which omits local state. + Unwrap() Activation +} + // asEvalState walks the Activation hierarchy and returns the first EvalState found, if present. func asEvalState(vars Activation) (EvalState, bool) { if conv, ok := vars.(evalStateConverter); ok { return conv.asEvalState(), true } + // Check if the current activation wraps another activation. This is used to support + // wrappers such as the @block() activation which may be composed of a dynamicSlotActivation or a + // constantSlotActivation. In this case, the underlying activation is the portion which interacts + // with the EvalState. + if wrapper, ok := vars.(activationWrapper); ok { + unwrapped := wrapper.Unwrap() + // Recursively call asEvalState on the unwrapped activation. This will check the unwrapped value and its parents. + return asEvalState(unwrapped) + } if vars.Parent() != nil { return asEvalState(vars.Parent()) } diff --git a/vendor/github.com/google/cel-go/interpreter/planner.go b/vendor/github.com/google/cel-go/interpreter/planner.go index f0e0d43054..0bc38449ce 100644 --- a/vendor/github.com/google/cel-go/interpreter/planner.go +++ b/vendor/github.com/google/cel-go/interpreter/planner.go @@ -61,13 +61,20 @@ type planner struct { observers []StatefulObserver } +type planBuilder struct { + *planner + + localVars map[string]int +} + // Plan implements the interpretablePlanner interface. This implementation of the Plan method also // applies decorators to each Interpretable generated as part of the overall plan. Decorators are // useful for layering functionality into the evaluation that is not natively understood by CEL, // such as state-tracking, expression re-write, and possibly efficient thread-safe memoization of // repeated expressions. func (p *planner) Plan(expr ast.Expr) (Interpretable, error) { - i, err := p.plan(expr) + pb := &planBuilder{planner: p, localVars: make(map[string]int)} + i, err := pb.plan(expr) if err != nil { return nil, err } @@ -77,7 +84,7 @@ func (p *planner) Plan(expr ast.Expr) (Interpretable, error) { return &ObservableInterpretable{Interpretable: i, observers: p.observers}, nil } -func (p *planner) plan(expr ast.Expr) (Interpretable, error) { +func (p *planBuilder) plan(expr ast.Expr) (Interpretable, error) { switch expr.Kind() { case ast.CallKind: return p.decorate(p.planCall(expr)) @@ -102,7 +109,7 @@ func (p *planner) plan(expr ast.Expr) (Interpretable, error) { // decorate applies the InterpretableDecorator functions to the given Interpretable. // Both the Interpretable and error generated by a Plan step are accepted as arguments // for convenience. -func (p *planner) decorate(i Interpretable, err error) (Interpretable, error) { +func (p *planBuilder) decorate(i Interpretable, err error) (Interpretable, error) { if err != nil { return nil, err } @@ -116,20 +123,26 @@ func (p *planner) decorate(i Interpretable, err error) (Interpretable, error) { } // planIdent creates an Interpretable that resolves an identifier from an Activation. -func (p *planner) planIdent(expr ast.Expr) (Interpretable, error) { +func (p *planBuilder) planIdent(expr ast.Expr) (Interpretable, error) { // Establish whether the identifier is in the reference map. if identRef, found := p.refMap[expr.ID()]; found { return p.planCheckedIdent(expr.ID(), identRef) } // Create the possible attribute list for the unresolved reference. ident := expr.AsIdent() + if p.isLocalVar(ident) { + return &evalAttr{ + adapter: p.adapter, + attr: p.attrFactory.AbsoluteAttribute(expr.ID(), ident), + }, nil + } return &evalAttr{ adapter: p.adapter, attr: p.attrFactory.MaybeAttribute(expr.ID(), ident), }, nil } -func (p *planner) planCheckedIdent(id int64, identRef *ast.ReferenceInfo) (Interpretable, error) { +func (p *planBuilder) planCheckedIdent(id int64, identRef *ast.ReferenceInfo) (Interpretable, error) { // Plan a constant reference if this is the case for this simple identifier. if identRef.Value != nil { return NewConstValue(id, identRef.Value), nil @@ -158,7 +171,7 @@ func (p *planner) planCheckedIdent(id int64, identRef *ast.ReferenceInfo) (Inter // a) selects a field from a map or proto. // b) creates a field presence test for a select within a has() macro. // c) resolves the select expression to a namespaced identifier. -func (p *planner) planSelect(expr ast.Expr) (Interpretable, error) { +func (p *planBuilder) planSelect(expr ast.Expr) (Interpretable, error) { // If the Select id appears in the reference map from the CheckedExpr proto then it is either // a namespaced identifier or enum value. if identRef, found := p.refMap[expr.ID()]; found { @@ -214,7 +227,7 @@ func (p *planner) planSelect(expr ast.Expr) (Interpretable, error) { // planCall creates a callable Interpretable while specializing for common functions and invocation // patterns. Specifically, conditional operators &&, ||, ?:, and (in)equality functions result in // optimized Interpretable values. -func (p *planner) planCall(expr ast.Expr) (Interpretable, error) { +func (p *planBuilder) planCall(expr ast.Expr) (Interpretable, error) { call := expr.AsCall() target, fnName, oName := p.resolveFunction(expr) argCount := len(call.Args()) @@ -291,7 +304,7 @@ func (p *planner) planCall(expr ast.Expr) (Interpretable, error) { } // planCallZero generates a zero-arity callable Interpretable. -func (p *planner) planCallZero(expr ast.Expr, +func (p *planBuilder) planCallZero(expr ast.Expr, function string, overload string, impl *functions.Overload) (Interpretable, error) { @@ -307,7 +320,7 @@ func (p *planner) planCallZero(expr ast.Expr, } // planCallUnary generates a unary callable Interpretable. -func (p *planner) planCallUnary(expr ast.Expr, +func (p *planBuilder) planCallUnary(expr ast.Expr, function string, overload string, impl *functions.Overload, @@ -335,7 +348,7 @@ func (p *planner) planCallUnary(expr ast.Expr, } // planCallBinary generates a binary callable Interpretable. -func (p *planner) planCallBinary(expr ast.Expr, +func (p *planBuilder) planCallBinary(expr ast.Expr, function string, overload string, impl *functions.Overload, @@ -364,7 +377,7 @@ func (p *planner) planCallBinary(expr ast.Expr, } // planCallVarArgs generates a variable argument callable Interpretable. -func (p *planner) planCallVarArgs(expr ast.Expr, +func (p *planBuilder) planCallVarArgs(expr ast.Expr, function string, overload string, impl *functions.Overload, @@ -392,7 +405,7 @@ func (p *planner) planCallVarArgs(expr ast.Expr, } // planCallEqual generates an equals (==) Interpretable. -func (p *planner) planCallEqual(expr ast.Expr, args []Interpretable) (Interpretable, error) { +func (p *planBuilder) planCallEqual(expr ast.Expr, args []Interpretable) (Interpretable, error) { return &evalEq{ id: expr.ID(), lhs: args[0], @@ -401,7 +414,7 @@ func (p *planner) planCallEqual(expr ast.Expr, args []Interpretable) (Interpreta } // planCallNotEqual generates a not equals (!=) Interpretable. -func (p *planner) planCallNotEqual(expr ast.Expr, args []Interpretable) (Interpretable, error) { +func (p *planBuilder) planCallNotEqual(expr ast.Expr, args []Interpretable) (Interpretable, error) { return &evalNe{ id: expr.ID(), lhs: args[0], @@ -410,7 +423,7 @@ func (p *planner) planCallNotEqual(expr ast.Expr, args []Interpretable) (Interpr } // planCallLogicalAnd generates a logical and (&&) Interpretable. -func (p *planner) planCallLogicalAnd(expr ast.Expr, args []Interpretable) (Interpretable, error) { +func (p *planBuilder) planCallLogicalAnd(expr ast.Expr, args []Interpretable) (Interpretable, error) { return &evalAnd{ id: expr.ID(), terms: args, @@ -418,7 +431,7 @@ func (p *planner) planCallLogicalAnd(expr ast.Expr, args []Interpretable) (Inter } // planCallLogicalOr generates a logical or (||) Interpretable. -func (p *planner) planCallLogicalOr(expr ast.Expr, args []Interpretable) (Interpretable, error) { +func (p *planBuilder) planCallLogicalOr(expr ast.Expr, args []Interpretable) (Interpretable, error) { return &evalOr{ id: expr.ID(), terms: args, @@ -426,7 +439,7 @@ func (p *planner) planCallLogicalOr(expr ast.Expr, args []Interpretable) (Interp } // planCallConditional generates a conditional / ternary (c ? t : f) Interpretable. -func (p *planner) planCallConditional(expr ast.Expr, args []Interpretable) (Interpretable, error) { +func (p *planBuilder) planCallConditional(expr ast.Expr, args []Interpretable) (Interpretable, error) { cond := args[0] t := args[1] var tAttr Attribute @@ -454,7 +467,7 @@ func (p *planner) planCallConditional(expr ast.Expr, args []Interpretable) (Inte // planCallIndex either extends an attribute with the argument to the index operation, or creates // a relative attribute based on the return of a function call or operation. -func (p *planner) planCallIndex(expr ast.Expr, args []Interpretable, optional bool) (Interpretable, error) { +func (p *planBuilder) planCallIndex(expr ast.Expr, args []Interpretable, optional bool) (Interpretable, error) { op := args[0] ind := args[1] opType := p.typeMap[op.ID()] @@ -489,7 +502,7 @@ func (p *planner) planCallIndex(expr ast.Expr, args []Interpretable, optional bo } // planCreateList generates a list construction Interpretable. -func (p *planner) planCreateList(expr ast.Expr) (Interpretable, error) { +func (p *planBuilder) planCreateList(expr ast.Expr) (Interpretable, error) { list := expr.AsList() optionalIndices := list.OptionalIndices() elements := list.Elements() @@ -518,7 +531,7 @@ func (p *planner) planCreateList(expr ast.Expr) (Interpretable, error) { } // planCreateStruct generates a map or object construction Interpretable. -func (p *planner) planCreateMap(expr ast.Expr) (Interpretable, error) { +func (p *planBuilder) planCreateMap(expr ast.Expr) (Interpretable, error) { m := expr.AsMap() entries := m.Entries() optionals := make([]bool, len(entries)) @@ -552,7 +565,7 @@ func (p *planner) planCreateMap(expr ast.Expr) (Interpretable, error) { } // planCreateObj generates an object construction Interpretable. -func (p *planner) planCreateStruct(expr ast.Expr) (Interpretable, error) { +func (p *planBuilder) planCreateStruct(expr ast.Expr) (Interpretable, error) { obj := expr.AsStruct() typeName, defined := p.resolveTypeName(obj.TypeName()) if !defined { @@ -586,7 +599,7 @@ func (p *planner) planCreateStruct(expr ast.Expr) (Interpretable, error) { } // planComprehension generates an Interpretable fold operation. -func (p *planner) planComprehension(expr ast.Expr) (Interpretable, error) { +func (p *planBuilder) planComprehension(expr ast.Expr) (Interpretable, error) { fold := expr.AsComprehension() accu, err := p.plan(fold.AccuInit()) if err != nil { @@ -596,6 +609,7 @@ func (p *planner) planComprehension(expr ast.Expr) (Interpretable, error) { if err != nil { return nil, err } + p.pushLocalVars(fold.AccuVar(), fold.IterVar(), fold.IterVar2()) cond, err := p.plan(fold.LoopCondition()) if err != nil { return nil, err @@ -604,10 +618,12 @@ func (p *planner) planComprehension(expr ast.Expr) (Interpretable, error) { if err != nil { return nil, err } + p.popLocalVars(fold.IterVar(), fold.IterVar2()) result, err := p.plan(fold.Result()) if err != nil { return nil, err } + p.popLocalVars(fold.AccuVar()) return &evalFold{ id: expr.ID(), accuVar: fold.AccuVar(), @@ -623,13 +639,13 @@ func (p *planner) planComprehension(expr ast.Expr) (Interpretable, error) { } // planConst generates a constant valued Interpretable. -func (p *planner) planConst(expr ast.Expr) (Interpretable, error) { +func (p *planBuilder) planConst(expr ast.Expr) (Interpretable, error) { return NewConstValue(expr.ID(), expr.AsLiteral()), nil } // resolveTypeName takes a qualified string constructed at parse time, applies the proto // namespace resolution rules to it in a scan over possible matching types in the TypeProvider. -func (p *planner) resolveTypeName(typeName string) (string, bool) { +func (p *planBuilder) resolveTypeName(typeName string) (string, bool) { for _, qualifiedTypeName := range p.container.ResolveCandidateNames(typeName) { if _, found := p.provider.FindStructType(qualifiedTypeName); found { return qualifiedTypeName, true @@ -646,7 +662,7 @@ func (p *planner) resolveTypeName(typeName string) (string, bool) { // - The target expression may only consist of ident and select expressions. // - The function is declared in the environment using its fully-qualified name. // - The fully-qualified function name matches the string serialized target value. -func (p *planner) resolveFunction(expr ast.Expr) (ast.Expr, string, string) { +func (p *planBuilder) resolveFunction(expr ast.Expr) (ast.Expr, string, string) { // Note: similar logic exists within the `checker/checker.go`. If making changes here // please consider the impact on checker.go and consolidate implementations or mirror code // as appropriate. @@ -687,7 +703,7 @@ func (p *planner) resolveFunction(expr ast.Expr) (ast.Expr, string, string) { // namespaced identifiers must be stripped, as all declarations already use fully-qualified // names. This stripping behavior is handled automatically by the ResolveCandidateNames // call. - return target, stripLeadingDot(fnName), "" + return target, strings.TrimPrefix(fnName, "."), "" } // Handle the situation where the function target actually indicates a qualified function name. @@ -710,7 +726,7 @@ func (p *planner) resolveFunction(expr ast.Expr) (ast.Expr, string, string) { // relativeAttr indicates that the attribute in this case acts as a qualifier and as such needs to // be observed to ensure that it's evaluation value is properly recorded for state tracking. -func (p *planner) relativeAttr(id int64, eval Interpretable, opt bool) (InterpretableAttribute, error) { +func (p *planBuilder) relativeAttr(id int64, eval Interpretable, opt bool) (InterpretableAttribute, error) { eAttr, ok := eval.(InterpretableAttribute) if !ok { eAttr = &evalAttr{ @@ -733,7 +749,7 @@ func (p *planner) relativeAttr(id int64, eval Interpretable, opt bool) (Interpre // toQualifiedName converts an expression AST into a qualified name if possible, with a boolean // 'found' value that indicates if the conversion is successful. -func (p *planner) toQualifiedName(operand ast.Expr) (string, bool) { +func (p *planBuilder) toQualifiedName(operand ast.Expr) (string, bool) { // If the checker identified the expression as an attribute by the type-checker, then it can't // possibly be part of qualified name in a namespace. _, isAttr := p.refMap[operand.ID()] @@ -759,9 +775,35 @@ func (p *planner) toQualifiedName(operand ast.Expr) (string, bool) { return "", false } -func stripLeadingDot(name string) string { - if strings.HasPrefix(name, ".") { - return name[1:] +func (p *planBuilder) pushLocalVars(names ...string) { + for _, name := range names { + if name == "" { + continue + } + if cnt, found := p.localVars[name]; found { + p.localVars[name] = cnt + 1 + } else { + p.localVars[name] = 1 + } + } +} + +func (p *planBuilder) popLocalVars(names ...string) { + for _, name := range names { + if name == "" { + continue + } + if cnt, found := p.localVars[name]; found { + if cnt == 1 { + delete(p.localVars, name) + } else { + p.localVars[name] = cnt - 1 + } + } } - return name +} + +func (p *planBuilder) isLocalVar(name string) bool { + _, found := p.localVars[name] + return found } diff --git a/vendor/github.com/google/cel-go/parser/helper.go b/vendor/github.com/google/cel-go/parser/helper.go index c13296dd5c..f960be20ed 100644 --- a/vendor/github.com/google/cel-go/parser/helper.go +++ b/vendor/github.com/google/cel-go/parser/helper.go @@ -159,7 +159,7 @@ func (p *parserHelper) id(ctx any) int64 { offset.Start = p.sourceInfo.ComputeOffset(int32(c.GetLine()), int32(c.GetColumn())) offset.Stop = offset.Start + int32(len(c.GetText())) case common.Location: - offset.Start = p.sourceInfo.ComputeOffset(int32(c.Line()), int32(c.Column())) + offset.Start = p.sourceInfo.ComputeOffsetAbsolute(int32(c.Line()), int32(c.Column())) offset.Stop = offset.Start case ast.OffsetRange: offset = c diff --git a/vendor/github.com/stoewer/go-strcase/.gitignore b/vendor/github.com/stoewer/go-strcase/.gitignore deleted file mode 100644 index db5247b944..0000000000 --- a/vendor/github.com/stoewer/go-strcase/.gitignore +++ /dev/null @@ -1,17 +0,0 @@ -# Compiled Object files, Static and Dynamic libs (Shared Objects) -*.o -*.a -*.so - -# Folders -vendor -doc - -# Temporary files -*~ -*.swp - -# Editor and IDE config -.idea -*.iml -.vscode diff --git a/vendor/github.com/stoewer/go-strcase/.golangci.yml b/vendor/github.com/stoewer/go-strcase/.golangci.yml deleted file mode 100644 index 0e75d86ae0..0000000000 --- a/vendor/github.com/stoewer/go-strcase/.golangci.yml +++ /dev/null @@ -1,19 +0,0 @@ -version: "2" - -linters: - enable: - - dupl - - goconst - - gocyclo - - godox - - gosec - - lll - - misspell - - prealloc - - staticcheck - - unconvert - - unparam - -formatters: - enable: - - gofmt diff --git a/vendor/github.com/stoewer/go-strcase/LICENSE b/vendor/github.com/stoewer/go-strcase/LICENSE deleted file mode 100644 index a105a3819a..0000000000 --- a/vendor/github.com/stoewer/go-strcase/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2017, Adrian Stoewer - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/vendor/github.com/stoewer/go-strcase/README.md b/vendor/github.com/stoewer/go-strcase/README.md deleted file mode 100644 index 84a640e714..0000000000 --- a/vendor/github.com/stoewer/go-strcase/README.md +++ /dev/null @@ -1,50 +0,0 @@ -[![GH Actions](https://github.com/stoewer/go-strcase/actions/workflows/lint-test.yml/badge.svg?branch=master)](https://github.com/stoewer/go-strcase/actions) -[![codecov](https://codecov.io/github/stoewer/go-strcase/branch/master/graph/badge.svg?token=c0UokYnop5)](https://codecov.io/github/stoewer/go-strcase) -[![GoDoc](https://godoc.org/github.com/stoewer/go-strcase?status.svg)](https://pkg.go.dev/github.com/stoewer/go-strcase) ---- - -Go strcase -========== - -The package `strcase` converts between different kinds of naming formats such as camel case -(`CamelCase`), snake case (`snake_case`) or kebab case (`kebab-case`). -The package is designed to work only with strings consisting of standard ASCII letters. -Unicode is currently not supported. - -Versioning and stability ------------------------- - -Although the master branch is supposed to remain always backward compatible, the repository -contains version tags in order to support vendoring tools. -The tag names follow semantic versioning conventions and have the following format `v1.0.0`. -This package supports Go modules introduced with version 1.11. - -Example -------- - -```go -import "github.com/stoewer/go-strcase" - -var snake = strcase.SnakeCase("CamelCase") -``` - -Dependencies ------------- - -### Build dependencies - -* none - -### Test dependencies - -* `github.com/stretchr/testify` - -Run linters and unit tests --------------------------- - -To run the static code analysis, linters and tests use the following commands: - -``` -golangci-lint run --config .golangci.yml ./... -go test ./... -``` diff --git a/vendor/github.com/stoewer/go-strcase/camel.go b/vendor/github.com/stoewer/go-strcase/camel.go deleted file mode 100644 index 7a9bec7c10..0000000000 --- a/vendor/github.com/stoewer/go-strcase/camel.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) 2017, A. Stoewer -// All rights reserved. - -package strcase - -import ( - "strings" -) - -// UpperCamelCase converts a string into camel case starting with a upper case letter. -func UpperCamelCase(s string) string { - return camelCase(s, true) -} - -// LowerCamelCase converts a string into camel case starting with a lower case letter. -func LowerCamelCase(s string) string { - return camelCase(s, false) -} - -func camelCase(s string, upper bool) string { - s = strings.TrimSpace(s) - buffer := make([]rune, 0, len(s)) - - stringIter(s, func(prev, curr, next rune) { - if !isDelimiter(curr) { - if isDelimiter(prev) || (upper && prev == 0) { - buffer = append(buffer, toUpper(curr)) - } else if isLower(prev) { - buffer = append(buffer, curr) - } else if isUpper(prev) && isUpper(curr) && isLower(next) { - // Assume a case like "R" for "XRequestId" - buffer = append(buffer, curr) - } else if isUpper(curr) && isDigit(prev) { - // Preserve uppercase letters after numbers - buffer = append(buffer, curr) - } else { - buffer = append(buffer, toLower(curr)) - } - } - }) - - return string(buffer) -} diff --git a/vendor/github.com/stoewer/go-strcase/doc.go b/vendor/github.com/stoewer/go-strcase/doc.go deleted file mode 100644 index 3e441ca3ef..0000000000 --- a/vendor/github.com/stoewer/go-strcase/doc.go +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2017, A. Stoewer -// All rights reserved. - -// Package strcase converts between different kinds of naming formats such as camel case -// (CamelCase), snake case (snake_case) or kebab case (kebab-case). The package is designed -// to work only with strings consisting of standard ASCII letters. Unicode is currently not -// supported. -package strcase diff --git a/vendor/github.com/stoewer/go-strcase/helper.go b/vendor/github.com/stoewer/go-strcase/helper.go deleted file mode 100644 index 96e79d6e13..0000000000 --- a/vendor/github.com/stoewer/go-strcase/helper.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) 2017, A. Stoewer -// All rights reserved. - -package strcase - -// isLower checks if a character is lower case. More precisely it evaluates if it is -// in the range of ASCII character 'a' to 'z'. -func isLower(ch rune) bool { - return ch >= 'a' && ch <= 'z' -} - -// toLower converts a character in the range of ASCII characters 'A' to 'Z' to its lower -// case counterpart. Other characters remain the same. -func toLower(ch rune) rune { - if ch >= 'A' && ch <= 'Z' { - return ch + 32 - } - return ch -} - -// isLower checks if a character is upper case. More precisely it evaluates if it is -// in the range of ASCII characters 'A' to 'Z'. -func isUpper(ch rune) bool { - return ch >= 'A' && ch <= 'Z' -} - -// toLower converts a character in the range of ASCII characters 'a' to 'z' to its lower -// case counterpart. Other characters remain the same. -func toUpper(ch rune) rune { - if ch >= 'a' && ch <= 'z' { - return ch - 32 - } - return ch -} - -// isSpace checks if a character is some kind of whitespace. -func isSpace(ch rune) bool { - return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' -} - -// isDigit checks if a character is a digit. More precisely it evaluates if it is -// in the range of ASCII characters '0' to '9'. -func isDigit(ch rune) bool { - return ch >= '0' && ch <= '9' -} - -// isDelimiter checks if a character is some kind of whitespace or '_' or '-'. -func isDelimiter(ch rune) bool { - return ch == '-' || ch == '_' || isSpace(ch) -} - -// iterFunc is a callback that is called fro a specific position in a string. Its arguments are the -// rune at the respective string position as well as the previous and the next rune. If curr is at the -// first position of the string prev is zero. If curr is at the end of the string next is zero. -type iterFunc func(prev, curr, next rune) - -// stringIter iterates over a string, invoking the callback for every single rune in the string. -func stringIter(s string, callback iterFunc) { - var prev rune - var curr rune - for _, next := range s { - if curr == 0 { - prev = curr - curr = next - continue - } - - callback(prev, curr, next) - - prev = curr - curr = next - } - - if len(s) > 0 { - callback(prev, curr, 0) - } -} diff --git a/vendor/github.com/stoewer/go-strcase/kebab.go b/vendor/github.com/stoewer/go-strcase/kebab.go deleted file mode 100644 index e9a6487579..0000000000 --- a/vendor/github.com/stoewer/go-strcase/kebab.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) 2017, A. Stoewer -// All rights reserved. - -package strcase - -// KebabCase converts a string into kebab case. -func KebabCase(s string) string { - return delimiterCase(s, '-', false) -} - -// UpperKebabCase converts a string into kebab case with capital letters. -func UpperKebabCase(s string) string { - return delimiterCase(s, '-', true) -} diff --git a/vendor/github.com/stoewer/go-strcase/snake.go b/vendor/github.com/stoewer/go-strcase/snake.go deleted file mode 100644 index 1b216e20cf..0000000000 --- a/vendor/github.com/stoewer/go-strcase/snake.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) 2017, A. Stoewer -// All rights reserved. - -package strcase - -import ( - "strings" -) - -// SnakeCase converts a string into snake case. -func SnakeCase(s string) string { - return delimiterCase(s, '_', false) -} - -// UpperSnakeCase converts a string into snake case with capital letters. -func UpperSnakeCase(s string) string { - return delimiterCase(s, '_', true) -} - -// delimiterCase converts a string into snake_case or kebab-case depending on the delimiter passed -// as second argument. When upperCase is true the result will be UPPER_SNAKE_CASE or UPPER-KEBAB-CASE. -func delimiterCase(s string, delimiter rune, upperCase bool) string { - s = strings.TrimSpace(s) - buffer := make([]rune, 0, len(s)+3) - - adjustCase := toLower - if upperCase { - adjustCase = toUpper - } - - var prev rune - var curr rune - for _, next := range s { - if isDelimiter(curr) { - if !isDelimiter(prev) { - buffer = append(buffer, delimiter) - } - } else if isUpper(curr) { - if isLower(prev) || (isUpper(prev) && isLower(next)) { - buffer = append(buffer, delimiter) - } - buffer = append(buffer, adjustCase(curr)) - } else if curr != 0 { - buffer = append(buffer, adjustCase(curr)) - } - prev = curr - curr = next - } - - if len(s) > 0 { - if isUpper(curr) && isLower(prev) && prev != 0 { - buffer = append(buffer, delimiter) - } - buffer = append(buffer, adjustCase(curr)) - } - - return string(buffer) -} diff --git a/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/pod/affinity_assitant_template.go b/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/pod/affinity_assitant_template.go index 214ec6a870..fe96d51a22 100644 --- a/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/pod/affinity_assitant_template.go +++ b/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/pod/affinity_assitant_template.go @@ -55,6 +55,14 @@ type AffinityAssistantTemplate struct { // default. // +optional PriorityClassName *string `json:"priorityClassName,omitempty"` + + // ServiceAccountName is the name of the ServiceAccount to use for the affinity assistant pod. + // If not specified, the affinity assistant will inherit the serviceAccountName from the + // PipelineRun's taskRunTemplate. If that is also not specified, the pod will use the + // namespace's default ServiceAccount. + // More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ + // +optional + ServiceAccountName string `json:"serviceAccountName,omitempty"` } // Equals checks if this Template is identical to the given Template. diff --git a/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/pod/template.go b/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/pod/template.go index 5d23e967cb..b5c692fd0f 100644 --- a/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/pod/template.go +++ b/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/pod/template.go @@ -112,7 +112,7 @@ type Template struct { // If not specified, the pod priority will be default or zero if there is no // default. // +optional - PriorityClassName *string `json:"priorityClassName,omitempty" protobuf:"bytes,7,opt,name=priorityClassName"` + PriorityClassName *string `json:"priorityClassName,omitempty" protobuf:"bytes,8,opt,name=priorityClassName"` // SchedulerName specifies the scheduler to be used to dispatch the Pod // +optional SchedulerName string `json:"schedulerName,omitempty"` @@ -132,6 +132,18 @@ type Template struct { // +optional HostNetwork bool `json:"hostNetwork,omitempty"` + // HostUsers indicates whether the pod will use the host's user namespace. + // Optional: Default to true. + // If set to true or not present, the pod will be run in the host user namespace, useful + // for when the pod needs a feature only available to the host user namespace, such as + // loading a kernel module with CAP_SYS_MODULE. + // When set to false, a new user namespace is created for the pod. Setting false + // is useful to mitigating container breakout vulnerabilities such as allowing + // containers to run as root without their user having root privileges on the host. + // This field depends on the kubernetes feature gate UserNamespacesSupport being enabled. + // +optional + HostUsers *bool `json:"hostUsers,omitempty"` + // TopologySpreadConstraints controls how Pods are spread across your cluster among // failure-domains such as regions, zones, nodes, and other user-defined topology domains. // +optional @@ -229,6 +241,9 @@ func MergePodTemplateWithDefault(tpl, defaultTpl *PodTemplate) *PodTemplate { if !tpl.HostNetwork && defaultTpl.HostNetwork { tpl.HostNetwork = true } + if tpl.HostUsers == nil { + tpl.HostUsers = defaultTpl.HostUsers + } if tpl.TopologySpreadConstraints == nil { tpl.TopologySpreadConstraints = defaultTpl.TopologySpreadConstraints } @@ -266,6 +281,9 @@ func MergeAAPodTemplateWithDefault(tpl, defaultTpl *AAPodTemplate) *AAPodTemplat if tpl.PriorityClassName == nil { tpl.PriorityClassName = defaultTpl.PriorityClassName } + if tpl.ServiceAccountName == "" { + tpl.ServiceAccountName = defaultTpl.ServiceAccountName + } return tpl } diff --git a/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/pod/zz_generated.deepcopy.go b/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/pod/zz_generated.deepcopy.go index 350f3b7e1b..054c9e0346 100644 --- a/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/pod/zz_generated.deepcopy.go +++ b/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/pod/zz_generated.deepcopy.go @@ -153,6 +153,11 @@ func (in *Template) DeepCopyInto(out *Template) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.HostUsers != nil { + in, out := &in.HostUsers, &out.HostUsers + *out = new(bool) + **out = **in + } if in.TopologySpreadConstraints != nil { in, out := &in.TopologySpreadConstraints, &out.TopologySpreadConstraints *out = make([]v1.TopologySpreadConstraint, len(*in)) diff --git a/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1/container_validation.go b/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1/container_validation.go index becf35411d..5167e8bb9b 100644 --- a/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1/container_validation.go +++ b/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1/container_validation.go @@ -20,6 +20,7 @@ import ( "context" "errors" "fmt" + "path/filepath" "regexp" "slices" "strings" @@ -197,8 +198,9 @@ func (s *Step) Validate(ctx context.Context) (errs *apis.FieldError) { } for j, vm := range s.VolumeMounts { - if strings.HasPrefix(vm.MountPath, "/tekton/") && - !strings.HasPrefix(vm.MountPath, "/tekton/home") { + cleanMountPath := filepath.Clean(vm.MountPath) + if strings.HasPrefix(cleanMountPath, "/tekton/") && + !strings.HasPrefix(cleanMountPath, "/tekton/home") { errs = errs.Also(apis.ErrGeneric(fmt.Sprintf("volumeMount cannot be mounted under /tekton/ (volumeMount %q mounted at %q)", vm.Name, vm.MountPath), "mountPath").ViaFieldIndex("volumeMounts", j)) } if strings.HasPrefix(vm.Name, "tekton-internal-") { diff --git a/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1/openapi_generated.go b/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1/openapi_generated.go index 37a66c8494..aa927ddf8d 100644 --- a/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1/openapi_generated.go +++ b/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1/openapi_generated.go @@ -173,6 +173,13 @@ func schema_pkg_apis_pipeline_pod_AffinityAssistantTemplate(ref common.Reference Format: "", }, }, + "serviceAccountName": { + SchemaProps: spec.SchemaProps{ + Description: "ServiceAccountName is the name of the ServiceAccount to use for the affinity assistant pod. If not specified, the affinity assistant will inherit the serviceAccountName from the PipelineRun's taskRunTemplate. If that is also not specified, the pod will use the namespace's default ServiceAccount. More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/", + Type: []string{"string"}, + Format: "", + }, + }, }, }, }, @@ -369,6 +376,13 @@ func schema_pkg_apis_pipeline_pod_Template(ref common.ReferenceCallback) common. Format: "", }, }, + "hostUsers": { + SchemaProps: spec.SchemaProps{ + Description: "HostUsers indicates whether the pod will use the host's user namespace. Optional: Default to true. If set to true or not present, the pod will be run in the host user namespace, useful for when the pod needs a feature only available to the host user namespace, such as loading a kernel module with CAP_SYS_MODULE. When set to false, a new user namespace is created for the pod. Setting false is useful to mitigating container breakout vulnerabilities such as allowing containers to run as root without their user having root privileges on the host. This field depends on the kubernetes feature gate UserNamespacesSupport being enabled.", + Type: []string{"boolean"}, + Format: "", + }, + }, "topologySpreadConstraints": { VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ @@ -1250,6 +1264,7 @@ func schema_pkg_apis_pipeline_v1_PipelineRunList(ref common.ReferenceCallback) c }, }, }, + Required: []string{"items"}, }, }, Dependencies: []string{ diff --git a/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1/pipeline_validation.go b/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1/pipeline_validation.go index 8d109c10fa..0037d2973d 100644 --- a/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1/pipeline_validation.go +++ b/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1/pipeline_validation.go @@ -336,6 +336,19 @@ func (pt PipelineTask) validateRefOrSpec(ctx context.Context) (errs *apis.FieldE return errs } +// isValidAPIVersion validates the format of an apiVersion string. +// Valid formats are "group/version" where both group and version are non-empty. +// For custom tasks, apiVersion must always be in the "group/version" format. +func isValidAPIVersion(apiVersion string) bool { + parts := strings.Split(apiVersion, "/") + if len(parts) != 2 { + return false + } + group := parts[0] + version := parts[1] + return group != "" && version != "" +} + // validateCustomTask validates custom task specifications - checking kind and fail if not yet supported features specified func (pt PipelineTask) validateCustomTask() (errs *apis.FieldError) { if pt.TaskRef != nil && pt.TaskRef.Kind == "" { @@ -344,10 +357,19 @@ func (pt PipelineTask) validateCustomTask() (errs *apis.FieldError) { if pt.TaskSpec != nil && pt.TaskSpec.Kind == "" { errs = errs.Also(apis.ErrInvalidValue("custom task spec must specify kind", "taskSpec.kind")) } - if pt.TaskRef != nil && pt.TaskRef.APIVersion == "" { + // Validate apiVersion format for custom tasks + if pt.TaskRef != nil && pt.TaskRef.APIVersion != "" { + if !isValidAPIVersion(pt.TaskRef.APIVersion) { + errs = errs.Also(apis.ErrInvalidValue(fmt.Sprintf("invalid apiVersion format %q, must be in the format \"group/version\"", pt.TaskRef.APIVersion), "taskRef.apiVersion")) + } + } else if pt.TaskRef != nil { errs = errs.Also(apis.ErrInvalidValue("custom task ref must specify apiVersion", "taskRef.apiVersion")) } - if pt.TaskSpec != nil && pt.TaskSpec.APIVersion == "" { + if pt.TaskSpec != nil && pt.TaskSpec.APIVersion != "" { + if !isValidAPIVersion(pt.TaskSpec.APIVersion) { + errs = errs.Also(apis.ErrInvalidValue(fmt.Sprintf("invalid apiVersion format %q, must be in the format \"group/version\"", pt.TaskSpec.APIVersion), "taskSpec.apiVersion")) + } + } else if pt.TaskSpec != nil { errs = errs.Also(apis.ErrInvalidValue("custom task spec must specify apiVersion", "taskSpec.apiVersion")) } return errs diff --git a/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1/pipelinerun_types.go b/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1/pipelinerun_types.go index aa340b0c7f..7cbd9cafcf 100644 --- a/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1/pipelinerun_types.go +++ b/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1/pipelinerun_types.go @@ -646,7 +646,7 @@ type PipelineRunList struct { metav1.TypeMeta `json:",inline"` // +optional metav1.ListMeta `json:"metadata,omitempty"` - Items []PipelineRun `json:"items,omitempty"` + Items []PipelineRun `json:"items"` } // PipelineTaskRun reports the results of running a step in the Task. Each diff --git a/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1/swagger.json b/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1/swagger.json index 2dd13f3a2a..eaba36c0a3 100644 --- a/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1/swagger.json +++ b/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1/swagger.json @@ -36,6 +36,10 @@ "description": "SecurityContext sets the security context for the pod", "$ref": "#/definitions/v1.PodSecurityContext" }, + "serviceAccountName": { + "description": "ServiceAccountName is the name of the ServiceAccount to use for the affinity assistant pod. If not specified, the affinity assistant will inherit the serviceAccountName from the PipelineRun's taskRunTemplate. If that is also not specified, the pod will use the namespace's default ServiceAccount. More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/", + "type": "string" + }, "tolerations": { "description": "If specified, the pod's tolerations.", "type": "array", @@ -95,6 +99,10 @@ "description": "HostNetwork specifies whether the pod may use the node network namespace", "type": "boolean" }, + "hostUsers": { + "description": "HostUsers indicates whether the pod will use the host's user namespace. Optional: Default to true. If set to true or not present, the pod will be run in the host user namespace, useful for when the pod needs a feature only available to the host user namespace, such as loading a kernel module with CAP_SYS_MODULE. When set to false, a new user namespace is created for the pod. Setting false is useful to mitigating container breakout vulnerabilities such as allowing containers to run as root without their user having root privileges on the host. This field depends on the kubernetes feature gate UserNamespacesSupport being enabled.", + "type": "boolean" + }, "imagePullSecrets": { "description": "ImagePullSecrets gives the name of the secret used by the pod to pull the image if specified", "type": "array", @@ -593,6 +601,9 @@ "v1.PipelineRunList": { "description": "PipelineRunList contains a list of PipelineRun", "type": "object", + "required": [ + "items" + ], "properties": { "apiVersion": { "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", diff --git a/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1/taskrun_types.go b/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1/taskrun_types.go index 2e36ecf3fc..4ec6351d4a 100644 --- a/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1/taskrun_types.go +++ b/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1/taskrun_types.go @@ -212,6 +212,10 @@ const ( TaskRunReasonResolvingStepActionRef = "ResolvingStepActionRef" // TaskRunReasonImagePullFailed is the reason set when the step of a task fails due to image not being pulled TaskRunReasonImagePullFailed TaskRunReason = "TaskRunImagePullFailed" + // TaskRunReasonCreateContainerConfigError is the reason set when the step of a task fails due to config error (e.g., missing ConfigMap or Secret) + TaskRunReasonCreateContainerConfigError TaskRunReason = "CreateContainerConfigError" + // TaskRunReasonPodCreationFailed is the reason set when the pod backing the TaskRun fails to be created (e.g., CreateContainerError) + TaskRunReasonPodCreationFailed TaskRunReason = "PodCreationFailed" // TaskRunReasonResultLargerThanAllowedLimit is the reason set when one of the results exceeds its maximum allowed limit of 1 KB TaskRunReasonResultLargerThanAllowedLimit TaskRunReason = "TaskRunResultLargerThanAllowedLimit" // TaskRunReasonStopSidecarFailed indicates that the sidecar is not properly stopped. diff --git a/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1/openapi_generated.go b/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1/openapi_generated.go index 5b2dc4e7cf..c5a5e9416d 100644 --- a/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1/openapi_generated.go +++ b/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1/openapi_generated.go @@ -120,6 +120,13 @@ func schema_pkg_apis_pipeline_pod_AffinityAssistantTemplate(ref common.Reference Format: "", }, }, + "serviceAccountName": { + SchemaProps: spec.SchemaProps{ + Description: "ServiceAccountName is the name of the ServiceAccount to use for the affinity assistant pod. If not specified, the affinity assistant will inherit the serviceAccountName from the PipelineRun's taskRunTemplate. If that is also not specified, the pod will use the namespace's default ServiceAccount. More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/", + Type: []string{"string"}, + Format: "", + }, + }, }, }, }, @@ -316,6 +323,13 @@ func schema_pkg_apis_pipeline_pod_Template(ref common.ReferenceCallback) common. Format: "", }, }, + "hostUsers": { + SchemaProps: spec.SchemaProps{ + Description: "HostUsers indicates whether the pod will use the host's user namespace. Optional: Default to true. If set to true or not present, the pod will be run in the host user namespace, useful for when the pod needs a feature only available to the host user namespace, such as loading a kernel module with CAP_SYS_MODULE. When set to false, a new user namespace is created for the pod. Setting false is useful to mitigating container breakout vulnerabilities such as allowing containers to run as root without their user having root privileges on the host. This field depends on the kubernetes feature gate UserNamespacesSupport being enabled.", + Type: []string{"boolean"}, + Format: "", + }, + }, "topologySpreadConstraints": { VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ diff --git a/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1/swagger.json b/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1/swagger.json index 0efc19a9a2..3f3fb55e7e 100644 --- a/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1/swagger.json +++ b/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1/swagger.json @@ -36,6 +36,10 @@ "description": "SecurityContext sets the security context for the pod", "$ref": "#/definitions/v1.PodSecurityContext" }, + "serviceAccountName": { + "description": "ServiceAccountName is the name of the ServiceAccount to use for the affinity assistant pod. If not specified, the affinity assistant will inherit the serviceAccountName from the PipelineRun's taskRunTemplate. If that is also not specified, the pod will use the namespace's default ServiceAccount. More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/", + "type": "string" + }, "tolerations": { "description": "If specified, the pod's tolerations.", "type": "array", @@ -95,6 +99,10 @@ "description": "HostNetwork specifies whether the pod may use the node network namespace", "type": "boolean" }, + "hostUsers": { + "description": "HostUsers indicates whether the pod will use the host's user namespace. Optional: Default to true. If set to true or not present, the pod will be run in the host user namespace, useful for when the pod needs a feature only available to the host user namespace, such as loading a kernel module with CAP_SYS_MODULE. When set to false, a new user namespace is created for the pod. Setting false is useful to mitigating container breakout vulnerabilities such as allowing containers to run as root without their user having root privileges on the host. This field depends on the kubernetes feature gate UserNamespacesSupport being enabled.", + "type": "boolean" + }, "imagePullSecrets": { "description": "ImagePullSecrets gives the name of the secret used by the pod to pull the image if specified", "type": "array", diff --git a/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1/openapi_generated.go b/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1/openapi_generated.go index 92b32cb19e..ad9657cb66 100644 --- a/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1/openapi_generated.go +++ b/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1/openapi_generated.go @@ -198,6 +198,13 @@ func schema_pkg_apis_pipeline_pod_AffinityAssistantTemplate(ref common.Reference Format: "", }, }, + "serviceAccountName": { + SchemaProps: spec.SchemaProps{ + Description: "ServiceAccountName is the name of the ServiceAccount to use for the affinity assistant pod. If not specified, the affinity assistant will inherit the serviceAccountName from the PipelineRun's taskRunTemplate. If that is also not specified, the pod will use the namespace's default ServiceAccount. More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/", + Type: []string{"string"}, + Format: "", + }, + }, }, }, }, @@ -394,6 +401,13 @@ func schema_pkg_apis_pipeline_pod_Template(ref common.ReferenceCallback) common. Format: "", }, }, + "hostUsers": { + SchemaProps: spec.SchemaProps{ + Description: "HostUsers indicates whether the pod will use the host's user namespace. Optional: Default to true. If set to true or not present, the pod will be run in the host user namespace, useful for when the pod needs a feature only available to the host user namespace, such as loading a kernel module with CAP_SYS_MODULE. When set to false, a new user namespace is created for the pod. Setting false is useful to mitigating container breakout vulnerabilities such as allowing containers to run as root without their user having root privileges on the host. This field depends on the kubernetes feature gate UserNamespacesSupport being enabled.", + Type: []string{"boolean"}, + Format: "", + }, + }, "topologySpreadConstraints": { VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ @@ -1789,6 +1803,7 @@ func schema_pkg_apis_pipeline_v1beta1_PipelineRunList(ref common.ReferenceCallba }, }, }, + Required: []string{"items"}, }, }, Dependencies: []string{ diff --git a/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1/pipeline_validation.go b/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1/pipeline_validation.go index 65ef7a5edc..08eb1a60b3 100644 --- a/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1/pipeline_validation.go +++ b/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1/pipeline_validation.go @@ -341,6 +341,19 @@ func (pt PipelineTask) validateRefOrSpec(ctx context.Context) (errs *apis.FieldE return errs } +// isValidAPIVersion validates the format of an apiVersion string. +// Valid formats are "group/version" where both group and version are non-empty. +// For custom tasks, apiVersion must always be in the "group/version" format. +func isValidAPIVersion(apiVersion string) bool { + parts := strings.Split(apiVersion, "/") + if len(parts) != 2 { + return false + } + group := parts[0] + version := parts[1] + return group != "" && version != "" +} + // validateCustomTask validates custom task specifications - checking kind and fail if not yet supported features specified func (pt PipelineTask) validateCustomTask() (errs *apis.FieldError) { if pt.TaskRef != nil && pt.TaskRef.Kind == "" { @@ -349,10 +362,19 @@ func (pt PipelineTask) validateCustomTask() (errs *apis.FieldError) { if pt.TaskSpec != nil && pt.TaskSpec.Kind == "" { errs = errs.Also(apis.ErrInvalidValue("custom task spec must specify kind", "taskSpec.kind")) } - if pt.TaskRef != nil && pt.TaskRef.APIVersion == "" { + // Validate apiVersion format for custom tasks + if pt.TaskRef != nil && pt.TaskRef.APIVersion != "" { + if !isValidAPIVersion(pt.TaskRef.APIVersion) { + errs = errs.Also(apis.ErrInvalidValue(fmt.Sprintf("invalid apiVersion format %q, must be in the format \"group/version\"", pt.TaskRef.APIVersion), "taskRef.apiVersion")) + } + } else if pt.TaskRef != nil { errs = errs.Also(apis.ErrInvalidValue("custom task ref must specify apiVersion", "taskRef.apiVersion")) } - if pt.TaskSpec != nil && pt.TaskSpec.APIVersion == "" { + if pt.TaskSpec != nil && pt.TaskSpec.APIVersion != "" { + if !isValidAPIVersion(pt.TaskSpec.APIVersion) { + errs = errs.Also(apis.ErrInvalidValue(fmt.Sprintf("invalid apiVersion format %q, must be in the format \"group/version\"", pt.TaskSpec.APIVersion), "taskSpec.apiVersion")) + } + } else if pt.TaskSpec != nil { errs = errs.Also(apis.ErrInvalidValue("custom task spec must specify apiVersion", "taskSpec.apiVersion")) } return errs diff --git a/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1/pipelinerun_types.go b/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1/pipelinerun_types.go index a935f5b720..ac6d327c42 100644 --- a/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1/pipelinerun_types.go +++ b/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1/pipelinerun_types.go @@ -599,7 +599,7 @@ type PipelineRunList struct { metav1.TypeMeta `json:",inline"` // +optional metav1.ListMeta `json:"metadata,omitempty"` - Items []PipelineRun `json:"items,omitempty"` + Items []PipelineRun `json:"items"` } // PipelineTaskRun reports the results of running a step in the Task. Each diff --git a/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1/swagger.json b/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1/swagger.json index 087f55fcd5..8c5e97efe5 100644 --- a/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1/swagger.json +++ b/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1/swagger.json @@ -36,6 +36,10 @@ "description": "SecurityContext sets the security context for the pod", "$ref": "#/definitions/v1.PodSecurityContext" }, + "serviceAccountName": { + "description": "ServiceAccountName is the name of the ServiceAccount to use for the affinity assistant pod. If not specified, the affinity assistant will inherit the serviceAccountName from the PipelineRun's taskRunTemplate. If that is also not specified, the pod will use the namespace's default ServiceAccount. More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/", + "type": "string" + }, "tolerations": { "description": "If specified, the pod's tolerations.", "type": "array", @@ -95,6 +99,10 @@ "description": "HostNetwork specifies whether the pod may use the node network namespace", "type": "boolean" }, + "hostUsers": { + "description": "HostUsers indicates whether the pod will use the host's user namespace. Optional: Default to true. If set to true or not present, the pod will be run in the host user namespace, useful for when the pod needs a feature only available to the host user namespace, such as loading a kernel module with CAP_SYS_MODULE. When set to false, a new user namespace is created for the pod. Setting false is useful to mitigating container breakout vulnerabilities such as allowing containers to run as root without their user having root privileges on the host. This field depends on the kubernetes feature gate UserNamespacesSupport being enabled.", + "type": "boolean" + }, "imagePullSecrets": { "description": "ImagePullSecrets gives the name of the secret used by the pod to pull the image if specified", "type": "array", @@ -879,6 +887,9 @@ "v1beta1.PipelineRunList": { "description": "PipelineRunList contains a list of PipelineRun", "type": "object", + "required": [ + "items" + ], "properties": { "apiVersion": { "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", diff --git a/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1/task_validation.go b/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1/task_validation.go index c2d918d4b3..8578375af7 100644 --- a/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1/task_validation.go +++ b/vendor/github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1/task_validation.go @@ -434,8 +434,9 @@ func validateStep(ctx context.Context, s Step, names sets.String) (errs *apis.Fi } for j, vm := range s.VolumeMounts { - if strings.HasPrefix(vm.MountPath, "/tekton/") && - !strings.HasPrefix(vm.MountPath, "/tekton/home") { + cleanMountPath := filepath.Clean(vm.MountPath) + if strings.HasPrefix(cleanMountPath, "/tekton/") && + !strings.HasPrefix(cleanMountPath, "/tekton/home") { errs = errs.Also(apis.ErrGeneric(fmt.Sprintf("volumeMount cannot be mounted under /tekton/ (volumeMount %q mounted at %q)", vm.Name, vm.MountPath), "mountPath").ViaFieldIndex("volumeMounts", j)) } if strings.HasPrefix(vm.Name, "tekton-internal-") { diff --git a/vendor/modules.txt b/vendor/modules.txt index d2f8ed3f59..c0a07c0a7d 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -220,8 +220,8 @@ github.com/golang/groupcache/lru ## explicit; go 1.17 github.com/golang/protobuf/proto github.com/golang/protobuf/ptypes/timestamp -# github.com/google/cel-go v0.26.1 -## explicit; go 1.22.0 +# github.com/google/cel-go v0.27.0 +## explicit; go 1.23.0 github.com/google/cel-go/cel github.com/google/cel-go/checker github.com/google/cel-go/checker/decls @@ -410,15 +410,12 @@ github.com/spf13/cobra # github.com/spf13/pflag v1.0.10 ## explicit; go 1.12 github.com/spf13/pflag -# github.com/stoewer/go-strcase v1.3.1 -## explicit; go 1.11 -github.com/stoewer/go-strcase # github.com/stretchr/testify v1.11.1 ## explicit; go 1.17 github.com/stretchr/testify/assert github.com/stretchr/testify/assert/yaml -# github.com/tektoncd/pipeline v1.7.0 -## explicit; go 1.24.0 +# github.com/tektoncd/pipeline v1.9.3 +## explicit; go 1.24.13 github.com/tektoncd/pipeline/internal/artifactref github.com/tektoncd/pipeline/pkg/apis/config github.com/tektoncd/pipeline/pkg/apis/pipeline From e98673d7387a9d293c351448a7ac896fd100449b Mon Sep 17 00:00:00 2001 From: Shubham Bhardwaj Date: Thu, 4 Jun 2026 19:29:15 +0200 Subject: [PATCH 6/6] fix(security): redact query string from incoming webhook log Signed-off-by: Shubham Bhardwaj --- pkg/adapter/incoming.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/adapter/incoming.go b/pkg/adapter/incoming.go index 350a3ffb09..97c2b95386 100644 --- a/pkg/adapter/incoming.go +++ b/pkg/adapter/incoming.go @@ -117,7 +117,7 @@ func (l *listener) detectIncoming(ctx context.Context, req *http.Request, payloa return false, nil, nil } - l.logger.Infof("incoming request has been requested: %v", req.URL) + l.logger.Infof("incoming request has been requested: %v", req.URL.Path) payload, err := parseIncomingPayload(req, payloadBody) if payload.legacyMode { // Log this, even if the request is invalid