diff --git a/.golangci.yml b/.golangci.yml
index 1cad27edae..50dac6c54a 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -57,6 +57,9 @@ linters:
- whitespace
- zerologlint
settings:
+ govet:
+ disable:
+ - inline
misspell:
ignore-words:
- cancelled
diff --git a/.tekton/go.yaml b/.tekton/go.yaml
index 4c721477b5..d8a1ce3cd9 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.24.13
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.24.13
- 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: golangci/golangci-lint:latest
+ image: golang:1.24
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.24.13
- name: GOLANGCILINT_CACHE
value: $(workspaces.source.path)/go-build-cache/golangci-cache
script: |
diff --git a/Makefile b/Makefile
index 935be90e12..81be39160e 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,7 @@
TARGET_NAMESPACE=pipelines-as-code
HUGO_VERSION=0.96.0
-GOLANGCI_LINT=golangci-lint
+GOLANGCI_LINT_VERSION ?= v2.12.2
+GO_TOOLCHAIN ?= go1.24.13
GOFUMPT=gofumpt
TKN_BINARY_NAME := tkn
TKN_BINARY_URL := https://tekton.dev/docs/cli/\#installation
@@ -11,11 +12,20 @@ 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
+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
PY_FILES := $(shell find . -type f -regex ".*\.py" -not -regex ".*\.venv/.*" -print)
SH_FILES := $(shell find hack/ -type f -regex ".*\.sh" -not -regex ".*\.venv/.*" -print)
YAML_FILES := $(shell find . -not -regex '^./vendor/.*' -type f -regex ".*y[a]ml" -print)
@@ -81,13 +91,21 @@ html-coverage: ## generate html coverage
lint: lint-go lint-yaml lint-md lint-python lint-shell ## run all linters
.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: ${YAML_FILES} ## runs yamllint on all yaml files
@echo "Linting yaml files..."
@@ -144,9 +162,9 @@ fix-python-errors: ## fix all python errors generated by ruff
@[[ -n `git status --porcelain $(PY_FILES)` ]] && { echo "Python files has been cleaned ๐งน. Cleaned Files: ";git status --porcelain $(PY_FILES) ;} || 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) \
@@ -210,5 +228,3 @@ dev-docs: download-hugo ## preview live your docs with hugo
.PHONY: clean
clean: ## clean build artifacts
rm -fR bin
-
-
diff --git a/go.mod b/go.mod
index 0cf1a3e1d1..9ef80dfaee 100644
--- a/go.mod
+++ b/go.mod
@@ -157,7 +157,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 097b8d9105..83c25e1bdd 100644
--- a/go.sum
+++ b/go.sum
@@ -144,8 +144,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/pkg/adapter/incoming.go b/pkg/adapter/incoming.go
index 14f3840ae7..2e7d2ba807 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
@@ -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/incoming_test.go b/pkg/adapter/incoming_test.go
index 0a4ce8a2b6..1cc86d6997 100644
--- a/pkg/adapter/incoming_test.go
+++ b/pkg/adapter/incoming_test.go
@@ -1,6 +1,7 @@
package adapter
import (
+ "context"
"fmt"
"net/http"
"net/http/httptest"
@@ -833,7 +834,7 @@ func Test_listener_detectIncoming(t *testing.T) {
}
// make a new request
- req := httptest.NewRequest(tt.args.method,
+ req := httptest.NewRequestWithContext(context.Background(), tt.args.method,
fmt.Sprintf("http://localhost%s?repository=%s&secret=%s&pipelinerun=%s&branch=%s&namespace=%s", tt.args.queryURL,
tt.args.queryRepository, tt.args.querySecret, tt.args.queryPipelineRun, tt.args.queryBranch, tt.args.queryNamespace),
strings.NewReader(tt.args.incomingBody))
@@ -1107,7 +1108,7 @@ func Test_detectIncoming_legacy_warning(t *testing.T) {
}{
{
name: "legacy mode - params in URL",
- req: httptest.NewRequest(http.MethodPost,
+ req: httptest.NewRequestWithContext(context.Background(), http.MethodPost,
"http://localhost/incoming?repository=test-good&secret=verysecrete&pipelinerun=pipelinerun1&branch=main",
strings.NewReader("")),
body: nil,
@@ -1123,7 +1124,7 @@ func Test_detectIncoming_legacy_warning(t *testing.T) {
"secret": "verysecrete",
"params": {"foo": "bar"}
}`
- r := httptest.NewRequest(http.MethodPost,
+ r := httptest.NewRequestWithContext(context.Background(), http.MethodPost,
"http://localhost/incoming",
strings.NewReader(payload))
r.Header.Set("Content-Type", "application/json")
@@ -1205,7 +1206,7 @@ func Test_detectIncoming_body_params_are_parsed(t *testing.T) {
"secret": "verysecrete",
"params": {"foo": "bar", "bar": "baz"}
}`
- req := httptest.NewRequest(http.MethodPost,
+ req := httptest.NewRequestWithContext(context.Background(), http.MethodPost,
"http://localhost/incoming",
strings.NewReader(payload))
req.Header.Set("Content-Type", "application/json")
diff --git a/pkg/adapter/sinker.go b/pkg/adapter/sinker.go
index f52746f47b..07ed5739d7 100644
--- a/pkg/adapter/sinker.go
+++ b/pkg/adapter/sinker.go
@@ -60,12 +60,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/llm/providers/common.go b/pkg/llm/providers/common.go
index 8b9c19bcf4..545dff0edc 100644
--- a/pkg/llm/providers/common.go
+++ b/pkg/llm/providers/common.go
@@ -106,7 +106,7 @@ func BuildPrompt(request *ltypes.AnalysisRequest) (string, error) {
promptBuilder.WriteString("Context Information:\n")
for key, value := range request.Context {
- promptBuilder.WriteString(fmt.Sprintf("=== %s ===\n", strings.ToUpper(key)))
+ fmt.Fprintf(&promptBuilder, "=== %s ===\n", strings.ToUpper(key))
switch v := value.(type) {
case string:
@@ -118,7 +118,7 @@ func BuildPrompt(request *ltypes.AnalysisRequest) (string, error) {
}
promptBuilder.Write(jsonData)
default:
- promptBuilder.WriteString(fmt.Sprintf("%v", v))
+ fmt.Fprintf(&promptBuilder, "%v", v)
}
promptBuilder.WriteString("\n\n")
diff --git a/pkg/provider/github/app/token.go b/pkg/provider/github/app/token.go
index 949b4eecd4..ee0d9ff209 100644
--- a/pkg/provider/github/app/token.go
+++ b/pkg/provider/github/app/token.go
@@ -9,6 +9,7 @@ import (
"time"
"github.com/golang-jwt/jwt/v4"
+ "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"
@@ -67,9 +68,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 8bea5e84dc..3ab6a421c1 100644
--- a/pkg/provider/github/app/token_test.go
+++ b/pkg/provider/github/app/token_test.go
@@ -1,6 +1,7 @@
package app
import (
+ "context"
"fmt"
"net/http"
"net/http/httptest"
@@ -144,7 +145,7 @@ func Test_GenerateJWT(t *testing.T) {
},
}
- ip := NewInstallation(httptest.NewRequest(http.MethodGet, "http://localhost", strings.NewReader("")), run, &v1alpha1.Repository{}, &github.Provider{}, tt.namespace.GetName())
+ ip := NewInstallation(httptest.NewRequestWithContext(context.Background(), http.MethodGet, "http://localhost", strings.NewReader("")), run, &v1alpha1.Repository{}, &github.Provider{}, tt.namespace.GetName())
token, err := ip.GenerateJWT(ctx)
if tt.wantErr {
assert.Assert(t, err != nil)
@@ -206,10 +207,10 @@ func Test_GetAndUpdateInstallationID(t *testing.T) {
ctx = info.StoreCurrentControllerName(ctx, "default")
ctx = info.StoreNS(ctx, testNamespace.GetName())
- ip := NewInstallation(httptest.NewRequest(http.MethodGet, "http://localhost", strings.NewReader("")), run, &v1alpha1.Repository{}, &github.Provider{}, testNamespace.GetName())
+ ip := NewInstallation(httptest.NewRequestWithContext(context.Background(), http.MethodGet, "http://localhost", strings.NewReader("")), run, &v1alpha1.Repository{}, &github.Provider{}, testNamespace.GetName())
jwtToken, err := ip.GenerateJWT(ctx)
assert.NilError(t, err)
- req := httptest.NewRequest(http.MethodGet, "http://localhost", strings.NewReader(""))
+ req := httptest.NewRequestWithContext(context.Background(), http.MethodGet, "http://localhost", strings.NewReader(""))
repo := &v1alpha1.Repository{
ObjectMeta: metav1.ObjectMeta{
Name: "repo",
@@ -264,6 +265,75 @@ 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())
+
+ jwtInstallation := &Install{run: run, namespace: testNamespace.GetName()}
+ jwtToken, err := jwtInstallation.GenerateJWT(ctx)
+ 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()
@@ -291,6 +361,7 @@ func TestGetAndUpdateInstallationID_Fallbacks(t *testing.T) {
wantErr bool
wantInstallationID int64
wantToken string
+ wantEnterpriseHost string
skip bool
expectedErrorString string
}{
@@ -314,6 +385,7 @@ func TestGetAndUpdateInstallationID_Fallbacks(t *testing.T) {
wantErr: false,
wantInstallationID: orgID,
wantToken: wantToken,
+ wantEnterpriseHost: "matched",
},
{
name: "repo and org installation fail, user installation succeeds",
@@ -338,6 +410,7 @@ func TestGetAndUpdateInstallationID_Fallbacks(t *testing.T) {
wantErr: false,
wantInstallationID: userID,
wantToken: wantToken,
+ wantEnterpriseHost: "matched",
},
{
name: "all installations fail",
@@ -393,7 +466,7 @@ func TestGetAndUpdateInstallationID_Fallbacks(t *testing.T) {
ctx = info.StoreCurrentControllerName(ctx, "default")
ctx = info.StoreNS(ctx, testNamespace.GetName())
- ip := NewInstallation(httptest.NewRequest(http.MethodGet, "http://localhost", strings.NewReader("")), run, &v1alpha1.Repository{}, &github.Provider{}, testNamespace.GetName())
+ ip := NewInstallation(httptest.NewRequestWithContext(context.Background(), http.MethodGet, "http://localhost", strings.NewReader("")), run, &v1alpha1.Repository{}, &github.Provider{}, testNamespace.GetName())
jwtToken, err := ip.GenerateJWT(ctx)
assert.NilError(t, err)
@@ -412,8 +485,8 @@ func TestGetAndUpdateInstallationID_Fallbacks(t *testing.T) {
gprovider.SetGithubClient(fakeghclient)
t.Setenv("PAC_GIT_PROVIDER_TOKEN_APIURL", serverURL)
- ip = NewInstallation(httptest.NewRequest(http.MethodGet, "http://localhost", strings.NewReader("")), run, repo, gprovider, testNamespace.GetName())
- _, token, installationID, err := ip.GetAndUpdateInstallationID(ctx)
+ ip = NewInstallation(httptest.NewRequestWithContext(context.Background(), http.MethodGet, "http://localhost", strings.NewReader("")), run, repo, gprovider, testNamespace.GetName())
+ enterpriseHost, token, installationID, err := ip.GetAndUpdateInstallationID(ctx)
if tt.wantErr {
assert.Assert(t, err != nil)
@@ -426,6 +499,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 dc74ebec04..69be721b35 100644
--- a/pkg/provider/github/github.go
+++ b/pkg/provider/github/github.go
@@ -319,6 +319,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 e532920d87..0190fc7cff 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"
@@ -24,6 +26,12 @@ import (
"k8s.io/client-go/kubernetes"
)
+const (
+ 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.
// It takes a context, namespace, and Kubernetes client as input parameters.
// It returns the application ID (int64), private key ([]byte), and an error if any.
@@ -90,6 +98,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 == "" {
@@ -111,18 +148,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
+ }
+ 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
}
- return -1, nil
+ 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
@@ -139,32 +249,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
@@ -453,6 +577,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()
@@ -484,6 +609,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")
}
@@ -596,6 +722,7 @@ func (v *Provider) handleCommitCommentEvent(ctx context.Context, event *github.C
runevent.BaseURL = runevent.HeadURL
runevent.TriggerTarget = triggertype.Push
opscomments.SetEventTypeAndTargetPR(runevent, event.GetComment().GetBody())
+ 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 55b77b38e1..b14fc9ee18 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"
@@ -11,7 +14,6 @@ import (
"github.com/google/go-github/v74/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"
@@ -72,6 +74,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{
@@ -1064,6 +1072,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{
{
@@ -1074,6 +1083,7 @@ func TestAppTokenGeneration(t *testing.T) {
Data: map[string][]byte{
"github-application-id": []byte("12345"),
"github-private-key": []byte(fakePrivateKey),
+ "webhook.secret": []byte(webhookSecret),
},
},
},
@@ -1090,6 +1100,7 @@ func TestAppTokenGeneration(t *testing.T) {
Data: map[string][]byte{
"github-application-id": []byte("abcd"),
"github-private-key": []byte(fakePrivateKey),
+ "webhook.secret": []byte(webhookSecret),
},
},
},
@@ -1106,6 +1117,7 @@ func TestAppTokenGeneration(t *testing.T) {
Data: map[string][]byte{
"github-application-id": []byte("12345"),
"github-private-key": []byte("invalid-key"),
+ "webhook.secret": []byte(webhookSecret),
},
},
},
@@ -1118,17 +1130,49 @@ func TestAppTokenGeneration(t *testing.T) {
wantErrSubst string
nilClient bool
seedData testclient.Clients
- envs map[string]string
resultBaseURL string
checkInstallIDs []int64
extraRepoInstallIDs map[string]string
+ 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,
@@ -1176,8 +1220,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{
@@ -1196,24 +1238,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,
},
@@ -1242,6 +1281,16 @@ func TestAppTokenGeneration(t *testing.T) {
gprovider.pacInfo.SecretGhAppTokenScopedExtraRepos = extras
}
+ 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)
@@ -1249,6 +1298,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 f162966212..67ada6bef1 100644
--- a/pkg/resolve/remote.go
+++ b/pkg/resolve/remote.go
@@ -101,8 +101,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)
@@ -157,7 +157,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)
@@ -188,7 +188,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 60d819f4d1..bfb378589a 100644
--- a/pkg/resolve/remote_test.go
+++ b/pkg/resolve/remote_test.go
@@ -464,3 +464,81 @@ 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")
+}
diff --git a/pkg/test/nonoai/main.go b/pkg/test/nonoai/main.go
index 041a12739a..5304af077c 100644
--- a/pkg/test/nonoai/main.go
+++ b/pkg/test/nonoai/main.go
@@ -216,7 +216,7 @@ func healthHandler(w http.ResponseWriter, _ *http.Request) {
func openaiHandler(w http.ResponseWriter, r *http.Request) {
if *verbose {
- log.Printf("๐จ OpenAI request from %s", r.RemoteAddr)
+ log.Printf("๐จ OpenAI request from %q", r.RemoteAddr) //nolint:gosec // Test server logs request source for debugging.
}
// Check authorization header
@@ -314,7 +314,7 @@ func openaiHandler(w http.ResponseWriter, r *http.Request) {
func geminiHandler(w http.ResponseWriter, r *http.Request) {
if *verbose {
- log.Printf("๐จ Gemini request from %s", r.RemoteAddr)
+ log.Printf("๐จ Gemini request from %q", r.RemoteAddr) //nolint:gosec // Test server logs request source for debugging.
}
// Simulate latency
diff --git a/test/pkg/github/instrumentation.go b/test/pkg/github/instrumentation.go
index 7a80e3d59e..59d9ed4507 100644
--- a/test/pkg/github/instrumentation.go
+++ b/test/pkg/github/instrumentation.go
@@ -243,7 +243,7 @@ func parseAPICallLog(logLine string) *InstrumentationAPICall {
// outputTestResultToFile writes the test result to a JSON file.
func (g *PRTest) outputTestResultToFile(outputDir string, apiCalls []string, lastOAuth2Index, totalLines int) {
// Create output directory if it doesn't exist
- if err := os.MkdirAll(outputDir, 0o755); err != nil {
+ if err := os.MkdirAll(outputDir, 0o755); err != nil { //nolint:gosec // E2E instrumentation writes to a caller-provided artifact directory.
g.Logger.Warnf("Failed to create output directory %s: %v", outputDir, err)
return
}
@@ -287,7 +287,7 @@ func (g *PRTest) outputTestResultToFile(outputDir string, apiCalls []string, las
return
}
- if err := os.WriteFile(filepath, jsonData, 0o600); err != nil {
+ if err := os.WriteFile(filepath, jsonData, 0o600); err != nil { //nolint:gosec // E2E instrumentation writes a generated artifact filename under outputDir.
g.Logger.Warnf("Failed to write test result to file %s: %v", filepath, err)
return
}
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 @@
[](https://pkg.go.dev/github.com/go-jose/go-jose/v4)
[](https://pkg.go.dev/github.com/go-jose/go-jose/v4/jwt)
[](https://raw.githubusercontent.com/go-jose/go-jose/master/LICENSE)
-[](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 a34809ce1b..ceac3f3b4c 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -134,8 +134,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.4 => 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
@@ -1325,7 +1325,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