Skip to content

feat: OIDC execution tokens for CI workflow authorization (POC)#5700

Open
shiroyasha wants to merge 7 commits into
mainfrom
poc/ci-oidc-verify
Open

feat: OIDC execution tokens for CI workflow authorization (POC)#5700
shiroyasha wants to merge 7 commits into
mainfrom
poc/ci-oidc-verify

Conversation

@shiroyasha

Copy link
Copy Markdown
Collaborator

Summary

  • SuperPlane issues signed OIDC execution tokens when semaphore.runWorkflow triggers a pipeline, injected as the SUPERPLANE_OIDC_TOKEN workflow parameter alongside existing SUPERPLANE_* IDs.
  • Adds a public POST /api/v1/oidc/verify endpoint that validates the JWT signature and confirms the canvas/node is authorized in the live canvas spec.
  • Adds superplane oidc verify CLI command for CI pre-job steps (Semaphore, GitHub Actions, GitLab, etc.) to abort unauthorized production deploys or artifact uploads.

Test plan

  • go test ./pkg/oidc/... ./pkg/public/... ./pkg/integrations/semaphore/components/...
  • Trigger a Semaphore workflow from a canvas and confirm SUPERPLANE_OIDC_TOKEN is present in workflow parameters
  • Run superplane oidc verify --pipeline-file ".semaphore/..." in a Semaphore job pre-step; confirm exit 0 for valid token and non-zero for missing/invalid token
  • Verify unauthorized canvas/node or mismatched pipeline file is rejected by the verify API

Made with Cursor

Issue signed OIDC tokens when triggering Semaphore workflows and expose
a verify API plus CLI command so CI jobs can confirm a canvas node is
authorized before running production deploys or artifact uploads.

Signed-off-by: Igor Šarčević <igor@operately.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@superplanehq-integration

Copy link
Copy Markdown

👋 Commands for maintainers:

  • /sp start - Start an ephemeral machine (takes ~30s)
  • /sp stop - Stop a running machine (auto-executed on pr close)

@superplane-gh-integration-9000

superplane-gh-integration-9000 Bot commented Jun 25, 2026

Copy link
Copy Markdown

Docs Impact Review

This PR introduces a new superplane oidc verify CLI command and a new OIDC execution-token security feature for Semaphore workflows — both are user-facing and not yet covered in the docs.

Suggested docs updates:

  • docs.superplane.com/cli/ — new page or new section on an existing CLI page — Document the superplane oidc verify subcommand, including its three flags (--token / SUPERPLANE_OIDC_TOKEN env var, --url, and --claim key=value repeatable), its exit-code contract, and a usage example. The current CLI docs have no mention of the oidc command group.

  • docs.superplane.com/security/ — new dedicated page: "OIDC Execution Tokens" — Explain the concept: SuperPlane signs a short-lived JWT for each Semaphore trigger, what claims are included (org_id, canvas_id, node_id, execution_id, component, project_id, pipeline_file, ref, commit_sha), how the token is delivered (SUPERPLANE_OIDC_TOKEN parameter), and how to verify it in CI using superplane oidc verify. Include guidance on pinning expected claims as hardcoded literals rather than trigger-time parameters, and the commit_sha / ref binding pattern. This is a new security concept that does not fit any existing security page.

  • docs.superplane.com/components/semaphore — "Run Workflow" section — The Go Documentation() string was updated in this PR and will auto-regenerate the component reference page, so the injected-parameters table and the oidc verify example will appear there automatically. No manual edit needed to that page itself, but the auto-generated section should cross-link to the new OIDC security page once it exists.

Why: The PR ships a new CLI subcommand (superplane oidc verify) that is the primary user-facing tool for verifying OIDC execution tokens in CI pipelines, and it changes Semaphore's RunWorkflow to unconditionally inject SUPERPLANE_OIDC_TOKEN into every triggered workflow. Neither the CLI docs nor the Security section currently mention OIDC tokens, so users have no documentation for this new security mechanism or how to use the CLI command that accompanies it.


Maintainer commands

Command What it does
/docs-agree Open a tracking issue in superplanehq/docs and assign it to you.
/docs-reject <reason> Dismiss this check. A short reason is required (e.g. /docs-reject not user-facing).

Posted automatically by warp-gateway · commit 761864b

@superplane-gh-integration-9000

superplane-gh-integration-9000 Bot commented Jun 25, 2026

Copy link
Copy Markdown

Risk: 72/100 (high)

Summary

Introduces RSA-signed OIDC JWT execution tokens injected into Semaphore CI workflow parameters, plus a CLI superplane oidc verify command for in-pipeline attestation checks.

Concerns

  • POC label in a security-sensitive token-signing feature suggests it is not production-ready.
  • No jti claim in signed JWTs; tokens are replayable within the 1-hour validity window.
  • Token delivered as a plain workflow parameter, which may be logged or exposed in Semaphore's UI.
  • buildParameters now hard-fails when OIDC is nil — breaking change for existing Semaphore integrations without OIDC_KEYS_PATH.
  • Server panics on startup if OIDC_KEYS_PATH is unset; no graceful degradation path for current deployments.
  • Actual verification error swallowed in verify.go and replaced with a generic message, complicating incident diagnosis.
  • fetchIssuer makes an unauthenticated HTTP request to a caller-controlled URL; potential SSRF in attacker-influenced CI environments.
  • No issuer-binding check: the discovered issuer is trusted at face value with no additional constraint against the configured API URL.

Recommended reviewers: bender-rodriguez-unit1, forestileao

shiroyasha and others added 4 commits June 25, 2026 13:37
Abort Semaphore workflow triggers when execution tokens cannot be signed,
and return a single generic verification failure message to callers while
logging detailed reasons server-side.

Signed-off-by: Igor Šarčević <igor@operately.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Move CI token parsing to pkg/ciauth and let semaphore.runWorkflow own
signing. Drop SuperPlane-side AuthorizeExecution; the verify API now
checks signatures and optional expected claims while Semaphore enforces
pipeline policy via CLI flags.

Signed-off-by: Igor Šarčević <igor@operately.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Igor Šarčević <igor@operately.com>
Remove the /api/v1/oidc/verify endpoint and validate execution tokens in the
CLI by fetching OIDC discovery and JWKS, so CI verifies independently.

Signed-off-by: Igor Šarčević <igor@operately.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@shiroyasha shiroyasha marked this pull request as draft June 25, 2026 12:22
Signed-off-by: Igor Šarčević <igor@operately.com>
@shiroyasha shiroyasha marked this pull request as ready for review June 25, 2026 12:44
Signed-off-by: Igor Šarčević <igor@operately.com>
Comment thread pkg/integrations/semaphore/components/run_workflow.go
semaphoreOIDCTokenDuration = time.Hour

semaphoreClaimOrgID = "org_id"
semaphoreClaimCanvasID = "canvas_id"

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

app_id

}

return ctx.OIDC.Sign(
fmt.Sprintf("execution:%s", ctx.ID),

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

do we need the execution: prefix?

}

parameters["SUPERPLANE_EXECUTION_ID"] = ctx.ID
parameters["SUPERPLANE_EXECUTION_ID"] = ctx.ID.String()

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

why do we suddenly need .String()?

return parameters
if ctx.OIDC == nil {
return nil, fmt.Errorf("OIDC provider is not configured")
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

No need to do this.


token, err := r.signOIDCToken(ctx, spec, metadata)
if err != nil {
return nil, fmt.Errorf("failed to sign OIDC execution token: %w", err)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Check if this error is leaking some data to users.

semaphoreClaimCommitSha = "commit_sha"
)

func (r *RunWorkflow) signOIDCToken(ctx core.ExecutionContext, spec RunWorkflowSpec, metadata RunWorkflowNodeMetadata) (string, error) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Don't break a class into multiple files.

// The approval component doesn't call Pass() in Execute(), so it should remain in started state.
//
executor := NewNodeExecutor(r.Encryptor, r.Registry, r.GitProvider, "http://localhost", "http://localhost", "", r.AuthService)
executor := NewNodeExecutor(r.Encryptor, r.Registry, r.GitProvider, nil, "http://localhost", "http://localhost", "", r.AuthService)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

can we use r.oidc provider?

Comment thread go.mod
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-jose/go-jose/v4 v4.1.4 // indirect

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Why, for the love of god would you add v4 if you already have v5?

Comment thread go.mod
github.com/bradleyfalzon/ghinstallation/v2 v2.17.0
github.com/casbin/casbin/v2 v2.134.0
github.com/casbin/gorm-adapter/v3 v3.37.0
github.com/coreos/go-oidc/v3 v3.19.0

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Can we switch the OIDC to use this too?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Seperate PR of course.

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.

1 participant