Skip to content

Commit 45168fc

Browse files
manavgupclaude
andauthored
feat: SPIFFE/SPIRE Integration Architecture for Agent Identity (#695)
* feat: add SPIFFE/SPIRE configuration for agent identity Add environment variables to support SPIFFE workload identity integration for AI agents and services. This enables cryptographic machine identity with configurable migration phases: - SPIFFE_ENABLED: Toggle SPIFFE integration - SPIFFE_AUTH_MODE: Migration phases (disabled→optional→preferred→required) - SPIFFE_ENDPOINT_SOCKET: SPIRE Agent Workload API socket - SPIFFE_TRUST_DOMAIN: Trust domain for identity hierarchy - SPIFFE_LEGACY_JWT_WARNING: Track legacy auth usage during migration - SPIFFE_SVID_TTL_SECONDS: Certificate lifetime configuration - SPIFFE_JWT_AUDIENCES: Allowed JWT-SVID audiences Related to: MCP Context Forge integration (PR #684) * docs: add SPIFFE/SPIRE integration architecture for agent identity This architecture document outlines how to integrate SPIRE (SPIFFE Runtime Environment) into RAG Modulo to provide cryptographic workload identities for AI agents. This enables zero-trust agent authentication and secure agent-to-agent (A2A) communication. Key architectural decisions: - JWT-SVIDs for stateless verification (vs X.509 for mTLS) - Trust domain: spiffe://rag-modulo.example.com - Integration with IBM MCP Context Forge (PR #684) - Capability-based access control for agents - 5-phase implementation plan Agent types defined: - search-enricher: MCP tool invocation - cot-reasoning: Chain of Thought orchestration - question-decomposer: Query decomposition - source-attribution: Document source tracking - entity-extraction: Named entity recognition - answer-synthesis: Answer generation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * feat(spiffe): implement SPIFFE/SPIRE agent authentication This commit implements the SPIFFE/SPIRE integration for AI agent authentication as designed in docs/architecture/spire-integration-architecture.md. Key changes: - Add py-spiffe dependency for SPIFFE JWT-SVID support - Create core SPIFFE authentication module (spiffe_auth.py) with: - SPIFFEConfig for environment-based configuration - AgentPrincipal dataclass for authenticated agent identity - SPIFFEAuthenticator for JWT-SVID validation - AgentType and AgentCapability enums - Helper functions for SPIFFE ID parsing and building - Create Agent data model with SQLAlchemy: - Agent model with SPIFFE ID, type, capabilities, status - Relationships to User (owner) and Team - Status management (active, suspended, revoked) - Add Agent repository, service, and router layers: - Full CRUD operations for agents - Agent registration with SPIFFE ID generation - Status and capability management - JWT-SVID validation endpoint - Extend AuthenticationMiddleware to detect and validate SPIFFE JWT-SVIDs - Add SPIRE deployment configuration templates: - server.conf, agent.conf for SPIRE configuration - docker-compose.spire.yml for local development - README.md with deployment instructions - Add comprehensive unit tests for all SPIFFE components Reference: PR #695 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix(spiffe): address PR review feedback for SPIFFE/SPIRE integration Critical fixes: - Add database migration for agents table (migrations/add_agents_table.sql) - Fix signature verification security: failed validation now always rejects (prevents fallback bypass attack) - Fix timezone handling: use UTC consistently for JWT timestamps Improvements: - Align env vars with .env.example (SPIFFE_JWT_AUDIENCES, SPIFFE_SVID_TTL_SECONDS) - Add capability enforcement decorator (require_capabilities) - Add OpenAPI tags metadata for agents endpoint - Update and expand unit tests (47 tests passing) Addresses review comments from PR #695. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix(spiffe): rename metadata to agent_metadata to avoid SQLAlchemy reserved word SQLAlchemy's Declarative API reserves the 'metadata' attribute name. Renamed the field to 'agent_metadata' in the model while keeping the database column name as 'metadata' via explicit column name mapping. This also updates the schema to use validation_alias for proper model_validate() from ORM objects. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix(test): add missing trust_domain to AgentPrincipal in test The test_validate_jwt_svid_valid test was failing because AgentPrincipal requires a trust_domain field which was not being provided. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix(spiffe): Address comprehensive PR review feedback Critical fixes: - Fix timezone-naive datetime to use UTC throughout (agent.py, agent_repository.py) - Change default agent status from ACTIVE to PENDING for approval workflow - Add RuntimeError when SPIFFE enabled but py-spiffe library missing - Restrict trust domain to configured value only (security fix) High priority security fixes: - Add capability validation per agent type (ALLOWED_CAPABILITIES_BY_TYPE) - Add authentication requirement to SPIFFE validation endpoint - Reject user-specified trust domains that don't match server config Code quality improvements: - Add OpenAPI tags metadata for agent router documentation - Fix require_capabilities decorator type hints (ParamSpec, TypeVar) - Add composite database indexes (owner+status, type+status, team+status) - Update migration script with new composite indexes Test updates: - Update test_register_agent_with_custom_trust_domain to verify rejection - Fix test_authenticator_creates_principal_with_fallback to mock spiffe module 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 6701795 commit 45168fc

File tree

23 files changed

+5956
-3
lines changed

23 files changed

+5956
-3
lines changed

.env.example

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,3 +180,32 @@ RERANKER_BATCH_SIZE=10
180180
BACKEND_IMAGE=ghcr.io/manavgup/rag_modulo/backend:latest
181181
FRONTEND_IMAGE=ghcr.io/manavgup/rag_modulo/frontend:latest
182182
TEST_IMAGE=ghcr.io/manavgup/rag_modulo/backend:latest
183+
184+
# ================================
185+
# SPIFFE/SPIRE IDENTITY (Agent/Machine Identity)
186+
# ================================
187+
# Enable SPIFFE workload identity for agents and services
188+
# See: https://spiffe.io/docs/latest/spire-about/spire-concepts/
189+
SPIFFE_ENABLED=false
190+
191+
# Authentication mode for migration (disabled|optional|preferred|required)
192+
# - disabled: No SPIFFE support (current default)
193+
# - optional: Accept both user JWT and SPIFFE JWT-SVID
194+
# - preferred: Prefer SPIFFE, log warning on legacy JWT
195+
# - required: Only SPIFFE JWT-SVIDs accepted for workloads
196+
SPIFFE_AUTH_MODE=disabled
197+
198+
# SPIRE Agent Workload API socket path
199+
SPIFFE_ENDPOINT_SOCKET=unix:///run/spire/agent/api.sock
200+
201+
# SPIFFE trust domain for this environment
202+
SPIFFE_TRUST_DOMAIN=rag-modulo.local
203+
204+
# Log warning when legacy JWT is used (for migration tracking)
205+
SPIFFE_LEGACY_JWT_WARNING=false
206+
207+
# Default SVID TTL in seconds (default: 3600 = 1 hour)
208+
SPIFFE_SVID_TTL_SECONDS=3600
209+
210+
# Allowed JWT-SVID audiences (comma-separated)
211+
SPIFFE_JWT_AUDIENCES=rag-modulo,mcp-gateway

backend/core/authentication_middleware.py

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
"""Authentication middleware for FastAPI application.
22
33
This module provides middleware for handling JWT-based authentication,
4-
including support for development/testing modes and mock user creation.
4+
including support for development/testing modes, mock user creation,
5+
and SPIFFE JWT-SVID authentication for AI agents.
6+
7+
SPIFFE Integration:
8+
This middleware supports SPIFFE JWT-SVIDs for agent authentication.
9+
When a Bearer token with a SPIFFE ID in the 'sub' claim is detected,
10+
it validates the token and creates an agent principal instead of a user.
11+
12+
Reference: docs/architecture/spire-integration-architecture.md
513
"""
614

715
import logging
@@ -19,6 +27,10 @@
1927
from core.config import get_settings
2028
from core.mock_auth import create_mock_user_data, ensure_mock_user_exists, is_bypass_mode_active, is_mock_token
2129
from core.request_context import RequestContext
30+
from core.spiffe_auth import (
31+
get_spiffe_authenticator,
32+
is_spiffe_jwt_svid,
33+
)
2234

2335
# Get settings safely for middleware
2436
settings = get_settings()
@@ -206,9 +218,64 @@ def _handle_mock_token(self, request: Request, token: str) -> bool: # pylint: d
206218
logger.info("AuthMiddleware: Using mock test token")
207219
return True
208220

221+
def _handle_spiffe_jwt_svid(self, request: Request, token: str) -> bool:
222+
"""Handle SPIFFE JWT-SVID authentication for agents.
223+
224+
This method validates a SPIFFE JWT-SVID and sets up the agent
225+
principal in the request state.
226+
227+
Args:
228+
request: The FastAPI request object.
229+
token: The SPIFFE JWT-SVID token.
230+
231+
Returns:
232+
True if SPIFFE JWT-SVID was handled successfully.
233+
"""
234+
try:
235+
authenticator = get_spiffe_authenticator()
236+
principal = authenticator.validate_jwt_svid(token)
237+
238+
if principal is None:
239+
logger.warning("AuthMiddleware: Invalid SPIFFE JWT-SVID")
240+
return False
241+
242+
# Check if the agent is expired
243+
if principal.is_expired():
244+
logger.warning("AuthMiddleware: Expired SPIFFE JWT-SVID for %s", principal.spiffe_id)
245+
return False
246+
247+
# Set agent principal in request state
248+
request.state.agent = principal
249+
request.state.identity_type = "agent"
250+
251+
# Also set a unified principal representation for compatibility
252+
agent_data = {
253+
"identity_type": "agent",
254+
"spiffe_id": principal.spiffe_id,
255+
"agent_type": principal.agent_type.value,
256+
"agent_id": principal.agent_id,
257+
"capabilities": [cap.value for cap in principal.capabilities],
258+
"audiences": principal.audiences,
259+
}
260+
request.state.user = agent_data # For backward compatibility
261+
RequestContext.set_user(agent_data)
262+
263+
logger.info(
264+
"AuthMiddleware: SPIFFE JWT-SVID validated successfully. Agent: %s (type: %s)",
265+
principal.spiffe_id,
266+
principal.agent_type.value,
267+
)
268+
return True
269+
except Exception as e:
270+
logger.warning("AuthMiddleware: Error validating SPIFFE JWT-SVID - %s", e)
271+
return False
272+
209273
def _handle_jwt_token(self, request: Request, token: str) -> bool:
210274
"""Handle JWT token authentication and cache user data.
211275
276+
This method handles both traditional user JWTs and SPIFFE JWT-SVIDs.
277+
It detects the token type and delegates to the appropriate handler.
278+
212279
Args:
213280
request: The FastAPI request object.
214281
token: The JWT token.
@@ -221,16 +288,22 @@ def _handle_jwt_token(self, request: Request, token: str) -> bool:
221288
if is_mock_token(token):
222289
return self._handle_mock_token(request, token)
223290

224-
# Verify JWT using the verify_jwt_token function
291+
# Check if this is a SPIFFE JWT-SVID (agent authentication)
292+
if is_spiffe_jwt_svid(token):
293+
return self._handle_spiffe_jwt_svid(request, token)
294+
295+
# Verify JWT using the verify_jwt_token function (user authentication)
225296
payload = verify_jwt_token(token)
226297
user_data = {
298+
"identity_type": "user",
227299
"id": payload.get("sub"),
228300
"email": payload.get("email"),
229301
"name": payload.get("name"),
230302
"uuid": payload.get("uuid"),
231303
"role": payload.get("role"),
232304
}
233305
request.state.user = user_data
306+
request.state.identity_type = "user"
234307
# Cache user data in request context to eliminate N+1 queries
235308
RequestContext.set_user(user_data)
236309
logger.info("AuthMiddleware: JWT token validated successfully. User: %s", request.state.user)

0 commit comments

Comments
 (0)