Skip to content

feat(pkg): shared identity and certauth for session mTLS#337

Open
Agent-Hellboy wants to merge 1 commit into
mainfrom
pkg/identity_certauth
Open

feat(pkg): shared identity and certauth for session mTLS#337
Agent-Hellboy wants to merge 1 commit into
mainfrom
pkg/identity_certauth

Conversation

@Agent-Hellboy

Copy link
Copy Markdown
Owner

Summary

  • Add pkg/identity for session-bound SPIFFE ID construction/parsing and client-cert URI checks.
  • Add pkg/certauth for session CSR generation, CSR validation, and safe credential file writes.
  • Refactor gateway, adapter CLI, and runtime-api adapter certificate signing to use the shared packages (Traefik Yaegi plugin stays separate).

Test plan

  • go test ./pkg/identity/... ./pkg/certauth/...
  • go test ./internal/cli/adapter/...
  • go test in services/mcp-gateway and services/runtime-api/internal/runtimeapi

Part of the post-#331 follow-up queue (#6). Next: runtime-api workload mTLS (#5), then live data-path E2E (#1).

Made with Cursor

Extract session SPIFFE parsing, CSR build/validation, and safe credential
file writes into pkg/identity and pkg/certauth so the gateway, adapter CLI,
and runtime-api share one contract for session-bound mTLS credentials.

Co-authored-by: Cursor <cursoragent@cursor.com>

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

Copy link
Copy Markdown
Contributor

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 refactors certificate signing request (CSR) generation, validation, and SPIFFE identity parsing by extracting these shared helpers into common packages (pkg/certauth and pkg/identity). This eliminates duplicate code across the CLI adapter, gateway, and runtime API. The review feedback highlights several key improvements: URL-escaping the namespace and session parameters in SessionSPIFFEID to prevent path-splitting bugs, strictly rejecting query parameters and fragments in SPIFFE IDs to align with the SPIFFE standard, adding corresponding test cases for these edge cases, and separating CSR parsing and signature verification in ValidateCSRPEM to preserve specific error details for debugging.

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.

Comment thread pkg/identity/session.go
Comment on lines +13 to +18
func SessionSPIFFEID(trustDomain, namespace, session string) string {
trustDomain = strings.TrimSpace(trustDomain)
namespace = strings.TrimSpace(namespace)
session = strings.TrimSpace(session)
return fmt.Sprintf("spiffe://%s/ns/%s/session/%s", trustDomain, namespace, session)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The namespace and session parameters are not URL-escaped when constructing the SPIFFE ID. If either contains special characters (such as /), it will break the path splitting logic in ParseSessionSPIFFE. Using url.PathEscape ensures that they are safely encoded, matching the url.PathUnescape calls already present in the parser.

Suggested change
func SessionSPIFFEID(trustDomain, namespace, session string) string {
trustDomain = strings.TrimSpace(trustDomain)
namespace = strings.TrimSpace(namespace)
session = strings.TrimSpace(session)
return fmt.Sprintf("spiffe://%s/ns/%s/session/%s", trustDomain, namespace, session)
}
func SessionSPIFFEID(trustDomain, namespace, session string) string {
trustDomain = strings.TrimSpace(trustDomain)
namespace = strings.TrimSpace(namespace)
session = strings.TrimSpace(session)
return fmt.Sprintf("spiffe://%s/ns/%s/session/%s", trustDomain, url.PathEscape(namespace), url.PathEscape(session))
}

Comment thread pkg/identity/session.go
Comment on lines +28 to +30
if !strings.EqualFold(uri.Scheme, "spiffe") || !strings.EqualFold(uri.Host, strings.TrimSpace(trustDomain)) {
return "", "", false
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

According to the SPIFFE standard, a SPIFFE ID must not contain query parameters or fragments. Strictly rejecting these prevents potential security bypasses or parameter injection.

	if !strings.EqualFold(uri.Scheme, "spiffe") || !strings.EqualFold(uri.Host, strings.TrimSpace(trustDomain)) {
		return "", "", false
	}
	if uri.RawQuery != "" || uri.Fragment != "" {
		return "", "", false
	}

Comment on lines +24 to +31
{
name: "valid",
raw: "spiffe://example.org/ns/team-a/session/session-1",
trust: "example.org",
wantNS: "team-a",
wantSess: "session-1",
wantOK: true,
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Add test cases to verify that special characters in the namespace and session are correctly escaped/unescaped, and that SPIFFE IDs with query parameters or fragments are strictly rejected.

		{
			name:     "valid",
			raw:      "spiffe://example.org/ns/team-a/session/session-1",
			trust:    "example.org",
			wantNS:   "team-a",
			wantSess: "session-1",
			wantOK:   true,
		},
		{
			name:     "valid with special characters",
			raw:      "spiffe://example.org/ns/team%2Fa/session/session%231",
			trust:    "example.org",
			wantNS:   "team/a",
			wantSess: "session#1",
			wantOK:   true,
		},
		{
			name:   "rejected with query",
			raw:    "spiffe://example.org/ns/team-a/session/session-1?foo=bar",
			trust:  "example.org",
			wantOK: false,
		},
		{
			name:   "rejected with fragment",
			raw:    "spiffe://example.org/ns/team-a/session/session-1#baz",
			trust:  "example.org",
			wantOK: false,
		},

Comment thread pkg/certauth/csr.go
Comment on lines +58 to +61
csr, err := x509.ParseCertificateRequest(block.Bytes)
if err != nil || csr.CheckSignature() != nil {
return nil, fmt.Errorf("csr is invalid or has an invalid signature")
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Separating the CSR parsing and signature verification checks improves readability and preserves the underlying error details, which is highly beneficial for debugging.

Suggested change
csr, err := x509.ParseCertificateRequest(block.Bytes)
if err != nil || csr.CheckSignature() != nil {
return nil, fmt.Errorf("csr is invalid or has an invalid signature")
}
csr, err := x509.ParseCertificateRequest(block.Bytes)
if err != nil {
return nil, fmt.Errorf("parse csr: %w", err)
}
if err := csr.CheckSignature(); err != nil {
return nil, fmt.Errorf("csr signature verification failed: %w", err)
}

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