Skip to content

feat(remote-tasks): gate remote annotation URLs#2745

Draft
chmouel wants to merge 1 commit into
tektoncd:mainfrom
chmouel:fix-remote-annotation-url-policy
Draft

feat(remote-tasks): gate remote annotation URLs#2745
chmouel wants to merge 1 commit into
tektoncd:mainfrom
chmouel:fix-remote-annotation-url-policy

Conversation

@chmouel
Copy link
Copy Markdown
Member

@chmouel chmouel commented May 27, 2026

📝 Description of the Change

This PR implements URL allowlisting for remote task and pipeline annotations to prevent SSRF (Server-Side Request Forgery) vulnerabilities.

Problem: Pipelines-as-Code previously fetched remote task and pipeline URLs referenced in annotations without any restrictions, creating a potential SSRF attack surface.

Solution: This change introduces an operator-configurable URL allowlist that:

  • HTTPS URLs: Work by default (secure-by-default approach)
  • HTTP URLs: Remain blocked unless explicitly allowed via the remote-tasks-url-allowlist ConfigMap entry
  • Same-provider URLs: Subject to the same policy checks
  • Large provider responses: Protected from being incorrectly parsed as pipeline resources

The allowlist is configured via the remote-tasks-url-allowlist field in the pac-config ConfigMap, supporting both hostname patterns and full URL patterns with http/https schemes.

🔗 Linked GitHub Issue

Fixes https://redhat.atlassian.net/browse/SRVKP-12181

🧪 Testing Strategy

  • Unit tests
  • Integration tests
  • End-to-end tests
  • Manual testing
  • Not Applicable

Test coverage:

  • Unit tests added in annotation_tasks_install_test.go and clients_test.go covering allowlist policy enforcement
  • Integration tests via updated test fixtures for remote pipeline resolution scenarios
  • Manual testing can be performed by configuring remote-tasks-url-allowlist in the ConfigMap and observing enforcement behavior

🤖 AI Assistance

  • I have used AI assistance for this PR.

Details: Claude AI was used to generate the majority of the code for this PR, including:

  • Core policy enforcement logic in the new remote_resource.go module
  • Test implementations following the project's table-driven test patterns
  • Documentation updates

All generated code has been reviewed and verified to meet project standards. The implementation has been validated with make test and make lint locally.

✅ Submitter Checklist

  • 📝 My commit messages are clear, informative, and follow the project's How to write a git commit message guide. The Gitlint linter ensures in CI it's properly validated
  • ✨ I have ensured my commit message prefix (e.g., feat:, feat:) matches the "Type of Change" I selected above.
  • ♽ I have run make test and make lint locally to check for and fix any issues. For an efficient workflow, I have considered installing pre-commit and running pre-commit install to automate these checks.
  • 📖 I have added or updated documentation for any user-facing changes.
  • 🧪 I have added sufficient unit tests for my code changes.
  • 🎁 I have added end-to-end tests where feasible. See README for more details. (Not applicable - this is a policy change, not a user-facing flow)
  • 🔎 I have addressed any CI test flakiness or provided a clear reason to bypass it.
  • If adding a provider feature, I have filled in the following and updated the provider documentation:
    • GitHub App
    • GitHub Webhook
    • Gitea/Forgejo
    • GitLab
    • Bitbucket Cloud
    • Bitbucket Data Center (Not applicable - this is cross-provider infrastructure change)

@chmouel chmouel marked this pull request as draft May 27, 2026 17:54
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented May 27, 2026

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 49.26108% with 206 lines in your changes missing coverage. Please review.
✅ Project coverage is 59.46%. Comparing base (67cfa52) to head (ab12ebd).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
pkg/params/clients/remote_resource.go 55.05% 93 Missing and 27 partials ⚠️
pkg/test/http/http.go 0.00% 49 Missing ⚠️
pkg/params/settings/config.go 62.74% 12 Missing and 7 partials ⚠️
pkg/matcher/annotation_tasks_install.go 55.26% 14 Missing and 3 partials ⚠️
pkg/test/provider/testwebvcs.go 0.00% 1 Missing ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2745      +/-   ##
==========================================
- Coverage   59.67%   59.46%   -0.21%     
==========================================
  Files         210      211       +1     
  Lines       21007    21404     +397     
==========================================
+ Hits        12536    12728     +192     
- Misses       7685     7853     +168     
- Partials      786      823      +37     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces security controls for fetching remote tasks and pipelines in Pipelines-as-Code. It adds configuration options for a host allowlist, blocked CIDRs, and a maximum response size limit, along with documentation and comprehensive tests. The review feedback highlights two critical security vulnerabilities in the remote resource client implementation: a potential SSRF bypass if the HTTP client uses a custom wrapped transport, and another bypass for HTTPS requests if DialTLSContext or DialTLS is configured on the transport. Actionable suggestions are provided to address both issues by falling back to a default transport and clearing TLS dialers to guarantee security enforcement.

Comment thread pkg/params/clients/remote_resource.go
Comment thread pkg/params/clients/remote_resource.go
@chmouel chmouel force-pushed the fix-remote-annotation-url-policy branch 5 times, most recently from 44f707e to 2ce187b Compare May 28, 2026 12:52
@chmouel
Copy link
Copy Markdown
Member Author

chmouel commented May 28, 2026

/gemini review

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces security controls for fetching remote tasks and pipelines via annotations in Pipelines-as-Code. It adds new configuration options to allowlist hosts, block specific CIDRs, and limit response sizes, alongside implementing validation logic to prevent unsafe fetches (such as loopback, private IPs, and unauthorized redirects). Documentation and tests have been updated accordingly. Feedback on the changes highlights that if a custom HTTP client's transport is not of type *http.Transport, it is silently replaced with http.DefaultTransport, which could break custom transport configurations in enterprise environments; logging a warning when this fallback occurs is recommended.

Comment thread pkg/params/clients/remote_resource.go
remote-tasks-url-blocked-cidrs: ""

# Maximum response size, in bytes, for remote annotation fetches.
remote-tasks-url-max-response-size: "1048576"
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I don't like exposing low level stuff as configuration but if for whatever reason someone has a task/prun bigger than 1mb to fetch then we let them fetch it

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds SSRF-focused safety controls for fetching remote Tekton resources referenced via Pipelines-as-Code annotations, by introducing an operator-configurable allowlist, additional IP-range blocking, and maximum response-size enforcement (including for provider-backed fetch paths).

Changes:

  • Introduces a guarded remote resource fetcher (GetRemoteResourceURL) with scheme/allowlist enforcement, DNS/IP-range blocks, redirect checks, and response-size limits.
  • Adds new pac-config settings (remote-tasks-url-allowlist, remote-tasks-url-blocked-cidrs, remote-tasks-url-max-response-size) with validation, docs, and default configmap entries.
  • Updates unit/integration tests and fixtures to use HTTPS defaults and to cover allowlist/size-limit behavior.

Reviewed changes

Copilot reviewed 23 out of 23 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
test/gitea_test.go Adds an integration test asserting allowlist enforcement surfaces as a failure comment.
pkg/test/provider/testwebvcs.go Extends the test provider to return provider-fetched remote task content.
pkg/test/http/http.go Adds a transport-backed test HTTP client to exercise real http.Transport behavior and dial controls.
pkg/resolve/testdata/skip-fetching-multiple-tasks-of-the-same-name-from-pipelinerun-annotations-and-tektondir.yaml Switches remote annotation URLs to HTTPS in testdata.
pkg/resolve/testdata/skip-fetching-multiple-tasks-of-the-same-name-from-pipelinerun-annotations-and-pipeline-annotation.yaml Switches remote annotation URLs to HTTPS in testdata.
pkg/resolve/testdata/skip-fetching-multiple-pipelines-of-the-same-name-from-pipelinerun-annotations-and-tektondir.yaml Switches remote annotation URLs to HTTPS in testdata.
pkg/resolve/testdata/remote-pipeline-with-remote-task-from-pipelinerun.yaml Switches remote annotation URLs to HTTPS in testdata.
pkg/resolve/testdata/remote-pipeline-with-remote-task-from-pipeline.yaml Switches remote annotation URLs to HTTPS in testdata.
pkg/resolve/testdata/remote-pipeline-with-relative-tasks.yaml Switches remote pipeline URL to HTTPS in testdata.
pkg/resolve/testdata/remote-pipeline-with-relative-tasks-same-pipeline.yaml Switches remote pipeline URL to HTTPS in testdata.
pkg/resolve/testdata/remote-pipeline-with-relative-tasks-1.yaml Switches remote pipeline URL to HTTPS in testdata.
pkg/resolve/remote_test.go Updates remote resolution tests to use HTTPS and the new transport-backed HTTP test client.
pkg/params/settings/config.go Adds new settings fields and validators for allowlist/CIDRs/max-size.
pkg/params/settings/config_test.go Adds coverage for the new settings validation behavior.
pkg/params/clients/remote_resource.go Implements remote URL policy validation and safe fetching (redirect validation, DNS/IP checks, size limits).
pkg/params/clients/clients.go Adds a default max response size constant for remote resources.
pkg/params/clients/clients_test.go Adds tests for allowlist behavior, IP blocking, redirects, and transport guarding.
pkg/matcher/annotation_tasks_install.go Routes remote annotation fetches through the new guarded remote resource client and enforces provider-path size limits.
pkg/matcher/annotation_tasks_install_test.go Expands tests for allowlist enforcement, response-size checks, and transport-backed URL fetching.
docs/content/docs/guides/pipeline-resolution/_index.md Updates docs to describe “Remote URL” behavior and http-vs-https allowlisting.
docs/content/docs/guides/creating-pipelines/_index.md Updates docs links/wording to match the new “Remote URL” section.
docs/content/docs/api/configmap.md Documents the new configmap keys and provides examples/defaults.
config/302-pac-configmap.yaml Adds the new remote URL policy configuration entries with inline comments.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg/params/clients/remote_resource.go
Comment thread pkg/params/clients/remote_resource.go
Comment thread pkg/params/settings/config.go
Comment thread config/302-pac-configmap.yaml
@chmouel chmouel marked this pull request as ready for review June 2, 2026 13:14
@chmouel chmouel changed the title fix(remote-tasks): gate remote annotation URLs feat(remote-tasks): gate remote annotation URLs Jun 2, 2026
Check remote task and pipeline URLs before fetching them. HTTPS URLs
continue to work by default, and operators can restrict them with the
remote task URL allowlist.

HTTP URLs stay blocked unless an operator explicitly allows that host
with an http:// allowlist entry. Apply the same checks before
same-provider fetches, and keep large provider responses from being
parsed as pipeline resources.

Jira: https://redhat.atlassian.net/browse/SRVKP-12181

Signed-off-by: Chmouel Boudjnah <chmouel@redhat.com>
Co-Authored-By: Claude <noreply@anthropic.com>
@chmouel chmouel force-pushed the fix-remote-annotation-url-policy branch from 2ce187b to ab12ebd Compare June 2, 2026 13:35
Comment on lines +121 to +152
if strings.HasPrefix(loweredURI, "http://") || strings.HasPrefix(loweredURI, "https://") {
if err := clients.ValidateRemoteResourceProviderURL(uri, fetchOptions); err != nil {
return "", err
}
if fetchedFromURIFromProvider, task, err := rt.ProviderInterface.GetTaskURI(ctx, rt.Event, uri); fetchedFromURIFromProvider {
rt.Logger.Debugf("getRemote: fetched %s via provider hook for uri=%s", kind, uri)
if err != nil {
return task, err
}
if err := clients.CheckRemoteResourceResponseSize(int64(len(task)), fetchOptions); err != nil {
return "", err
}
return task, nil
}
rt.Logger.Debugf("getRemote: fetching %s from http(s) url", kind)
data, err := rt.Run.Clients.GetURL(ctx, uri)
data, err := rt.Run.Clients.GetRemoteResourceURL(ctx, uri, fetchOptions)
if err != nil {
return "", err
}
rt.Logger.Infof("successfully fetched %s from remote HTTPS URL", uri)
rt.Logger.Infof("successfully fetched %s from remote URL", uri)
return string(data), nil
}
if fetchedFromURIFromProvider, task, err := rt.ProviderInterface.GetTaskURI(ctx, rt.Event, uri); fetchedFromURIFromProvider {
rt.Logger.Debugf("getRemote: fetched %s via provider hook for uri=%s", kind, uri)
if err != nil {
return task, err
}
if err := clients.CheckRemoteResourceResponseSize(int64(len(task)), fetchOptions); err != nil {
return "", err
}
return task, nil
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why not create the policy as part of remoteResourceFetchOptions?
It is a small overhead but the same newRemoteResourcePolicy call is being made multiple times by the clients.Func methods.

Comment on lines +127 to +129
if err != nil {
return task, err
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
if err != nil {
return task, err
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

if fetchedFromURIFromProvider is true, then there is no chance of err being non-nil

return "", err
}
return task, nil
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
}
} else if err != nil {
return task, err
}

rt.Logger.Infof("successfully fetched %s from remote URL", uri)
return string(data), nil
}
if fetchedFromURIFromProvider, task, err := rt.ProviderInterface.GetTaskURI(ctx, rt.Event, uri); fetchedFromURIFromProvider {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

same here

@chmouel
Copy link
Copy Markdown
Member Author

chmouel commented Jun 4, 2026

/gemini review

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces security controls for fetching remote tasks and pipelines in Pipelines-as-Code. It adds configuration options to allowlist specific hosts, block specific CIDRs, and limit the maximum response size for remote annotation fetches. It also implements safe URL validation, including blocking loopback, private, and multicast IP ranges by default, and enforces these policies during HTTP requests and redirects. There are no review comments, so no feedback is provided.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 23 out of 23 changed files in this pull request and generated 3 comments.

Comment on lines +147 to +151
safeTransport := baseTransport.Clone()
originalDialContext := safeTransport.DialContext
if originalDialContext == nil {
originalDialContext = (&net.Dialer{Timeout: ConnectMaxWaitTime}).DialContext
}
Comment on lines +123 to +131
httpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
if len(via) >= 10 {
return fmt.Errorf("stopped after 10 redirects")
}
if err := policy.validateURL(req.URL); err != nil {
return fmt.Errorf("blocked redirect target: %w", err)
}
return nil
}
Comment on lines +189 to +203
if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" {
return fmt.Errorf("host %q must use http:// or https:// when including a URL scheme", host)
}
if parsedURL.User != nil || parsedURL.RawQuery != "" || parsedURL.Fragment != "" || (parsedURL.Path != "" && parsedURL.Path != "/") {
return fmt.Errorf("host %q must not include a URL path, query, fragment, or userinfo", host)
}
host = parsedURL.Host
}
if strings.Contains(host, "*") && !strings.HasPrefix(host, "*.") {
return fmt.Errorf("host %q has an invalid wildcard", host)
}
trimmedHost := strings.TrimPrefix(host, "*.")
if trimmedHost == "" || strings.ContainsAny(trimmedHost, "/?#") {
return fmt.Errorf("host %q is invalid", host)
}
@chmouel
Copy link
Copy Markdown
Member Author

chmouel commented Jun 4, 2026

holding on this until i get time to address the comments

@chmouel chmouel marked this pull request as draft June 4, 2026 12:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants