Skip to content

🌱 Migrate identity binding and signature verification to exclusive targetRef - #178

Merged
usize merged 10 commits into
rossoctl:mainfrom
kevincogan:refactor/targetref-migration
Feb 17, 2026
Merged

🌱 Migrate identity binding and signature verification to exclusive targetRef#178
usize merged 10 commits into
rossoctl:mainfrom
kevincogan:refactor/targetref-migration

Conversation

@kevincogan

Copy link
Copy Markdown
Contributor

Summary

This PR removes all selector based fallback paths from the AgentCard Identity Binding and Signature Verification features, making targetRef the sole mechanism for workload resolution.

This cleans up the dual code paths introduced in #176 for backward compatibility and aligns with the broader targetRef migration tracked in #177 .

What Changed

Controllers

  • agentcard_controller.go

    • getWorkload() now exclusively uses targetRef
    • Removed: findMatchingWorkloadBySelector, selectorMatchesAgent, selectorMatchesWorkload, typed ready-check helpers, and ErrMultipleAgentsMatched
  • agentcard_networkpolicy_controller.go

    • resolveWorkload() now requires spec.targetRef
    • Removed status.targetRef and all selector fallback paths
  • agentcardsync_controller.go

    • Removed selector → targetRef migration logic
    • Removed selector-based duplicate detection

Webhook

  • agentcard_webhook.go

    • spec.targetRef is now required
    • selector emits a deprecation warning if present, but is ignored

Configuration

  • cmd/main.go

    • --enable-legacy-agent-crd default changed from truefalse

Tests

  • Migrated all identity binding and signature verification tests from Agent CRD + selectorDeployment + targetRef

  • Removed:

    • findMatchingWorkloadBySelector test block
    • selector-to-targetRef migration tests
    • listAgentCardsForAgent helper

Documentation

  • a2a-signature-verification.md

    • Migrated demos from Agent CRDDeployment + Service + AgentCard using targetRef
  • identity-binding-quickstart.md

    • Added rollout wait steps after AgentCard patches for reliable demo output

Scope: This refactor only touches code paths added by #176 (identity binding + signature verification). It does not remove core legacy Agent CRD support that is tracked in #161 / #168.

Related Issues / PRs

Testing

Unit tests

# 54/54 pass
GOTOOLCHAIN=go1.23.0 go test ./internal/controller/ -count=1
GOTOOLCHAIN=go1.23.0 go test ./internal/signature/ -count=1

End-to-end demos

Verified in a clean kind cluster:

  • Follow: docs/identity-binding-quickstart.md
  • Follow: docs/a2a-signature-verification.md

Fixes #177

…usive targetRef

Signed-off-by: Kevin Cogan <kevin.s.cogan@gmail.com>
@kevincogan
kevincogan force-pushed the refactor/targetref-migration branch from e221bd3 to 6356040 Compare February 17, 2026 00:48
@kevincogan
kevincogan marked this pull request as ready for review February 17, 2026 00:50
…ation

- Remove redundant Error field from VerificationResult; callers now use
  the returned error for infrastructure failures and Details for crypto
  outcomes (contract documented on the struct)
- Add minimum RSA key size enforcement (2048 bits) in VerifyJWS, protecting
  both Secret and JWKS provider paths
- JWKS provider: add singleflight to prevent thundering herd on cache expiry,
  rate-limit forced refreshes (30s cooldown) to block kid-spray attacks,
  cap response body at 1 MiB, warn on non-HTTPS endpoints
- Secret provider: warn when brute-force fallback iterates >10 keys, add
  security comment about not logging key material
- Metrics: use Register instead of MustRegister to avoid panics when the
  package is imported from multiple test suites
- Improve comments throughout (provider contract, algorithm helpers,
  canonical JSON, curve validation)

Signed-off-by: Kevin Cogan <kevin.s.cogan@gmail.com>
- Add recursive _strip_empty() to match Go's removeEmptyFields behavior
  for nested structures (capabilities, skills), replacing the shallow
  dict comprehension that could produce signing mismatches
- Document ECDSA DER vs raw R||S format difference and the Go verifier's
  fallback behavior
- Improve docstrings: note unencrypted-only PEM requirement, add vault
  recommendation for production key storage
- Update argparse help text to reference docstring

Signed-off-by: Kevin Cogan <kevin.s.cogan@gmail.com>
Add indexers.go with a sync.Once-guarded field indexer on
.spec.targetRef.name. All three controllers (AgentCard, NetworkPolicy,
Sync) can register the same index safely — only the first call performs
the registration; subsequent calls are no-ops.

This replaces full-namespace AgentCard list calls with indexed lookups,
reducing reconcile overhead in namespaces with many AgentCards.

Signed-off-by: Kevin Cogan <kevin.s.cogan@gmail.com>
- Extract propagateLabelToWorkload generic helper, replacing duplicated
  Deployment/StatefulSet label propagation code
- Make updateCondition return error instead of silently swallowing it
- Move binding event emission to updateAgentCardStatus with state-
  transition detection to avoid flooding the event stream on every
  reconcile cycle; emit AllowlistOnly warning once on first evaluation
- Add defensive guard for empty allowedSpiffeIDs (bypassed CRD validation)
- Fix StatefulSet readiness check: require readyReplicas > 0 so a
  StatefulSet scaled to 0 is not considered ready
- Use field indexer for workload→AgentCard mapping instead of listing
  all cards in the namespace
- Reduce Reconcile entry log to V(1) to cut noise in production
- Improve getServicePort: log when falling back to default port 8000
- Add computeCardId comment clarifying it is NOT for JWS signing

Signed-off-by: Kevin Cogan <kevin.s.cogan@gmail.com>
…olicy

- Extract upsertNetworkPolicy shared helper that handles create-or-update
  logic with owner references, replacing ~100 lines of duplication between
  createPermissivePolicy and createRestrictivePolicy
- Extract dnsEgressPorts helper for the repeated UDP+TCP port 53 spec
- createPermissivePolicy and createRestrictivePolicy now build only the
  NetworkPolicySpec and delegate persistence to upsertNetworkPolicy
- Use field indexer for workload→AgentCard mapping
- Consider SignatureIdentityMatch for policy decisions when identity
  binding is configured (both signature + binding must pass)
- Improve deletion: prefer spec.targetRef over status.targetRef, warn
  on divergence

Signed-off-by: Kevin Cogan <kevin.s.cogan@gmail.com>
Remove ReconcileAgent, shouldSyncAgent, getAgentCardName,
cleanupOrphanedCards, hasOwnerReference, and the agentReconcilerAdapter
that supported auto-syncing AgentCards from the legacy Agent CRD.

The Agent CRD path is fully superseded by targetRef-based AgentCard
creation via Deployment/StatefulSet watchers.

Also:
- Remove Agent RBAC marker (no longer needed)
- Use field indexer for duplicate-card detection in findExistingCardForWorkload
- Reduce reconcile entry logs to V(1)
- Add isOwnedByAgentCRD TODO noting it can be removed once Agent CRD
  is fully retired
- Remove corresponding Agent CRD tests from agentcardsync_controller_test.go

Signed-off-by: Kevin Cogan <kevin.s.cogan@gmail.com>
- Inject agentcard.NewFetcher() into AgentCardReconciler so card
  fetching works out of the box (was previously nil)
- Log a startup warning when --require-a2a-signature is false, since
  identity binding requires signature verification to function

Signed-off-by: Kevin Cogan <kevin.s.cogan@gmail.com>
Verifier tests (verifier_test.go):
- TestCanonicalJSON_BoolFalse_Preserved: bool(false) must not be stripped
- TestCanonicalJSON_NestedEmptyFields: nested empty maps/slices removed
- TestCanonicalJSON_ExportedVsInternal: exported wrapper matches internal
- TestVerifyJWS_RSA_KeyTooSmall: reject 1024-bit RSA keys
- TestVerifyJWS_AlgorithmConfusion_RSAKeyWithES256: block alg confusion
- TestVerifyJWS_AlgorithmConfusion_ECKeyWithRS256: block alg confusion
- TestVerifyJWS_RSAPSS_PS256: verify RSA-PSS signatures
- TestVerifyJWS_ECDSA_RawRS_Format: verify raw R||S ECDSA format

Controller tests:
- Add getServicePort tests (multi-port, single-port, fallback)
- Add multi-entry allowlist binding test
- Add empty allowedSpiffeIDs defensive guard test
- Refactor buildTestJWS to use signature.CreateCanonicalCardJSON instead
  of maintaining a parallel canonical JSON implementation
- Remove duplicated removeEmptyFieldsTest/marshalCanonicalTest helpers

Integration tests:
- Replace simulateJWSSpiffeID with mock signature provider so tests
  exercise the full reconciler binding path instead of pre-setting status
- Update mockFetcher to return valid AgentCardData (was returning nil)

Signed-off-by: Kevin Cogan <kevin.s.cogan@gmail.com>
Signed-off-by: Kevin Cogan <kevin.s.cogan@gmail.com>
@kevincogan
kevincogan force-pushed the refactor/targetref-migration branch from 06d1b86 to d8805b9 Compare February 17, 2026 13:29

@usize usize 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.

+1,553 -3,329

To add lines of code is human, to remove them is divine. 🙏🏻

Thanks for this cleanup. 🚀

@usize
usize merged commit 83fde8e into rossoctl:main Feb 17, 2026
2 checks passed
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.

refactor: Migrate AgentCard Identity Binding & Signature Verification to exclusive targetRef usage

2 participants