ADR-073: Knowledge Security Layer

Status

Proposed

Date

2025-12-27

Context

The knowledge system exposes sensitive information through multiple interfaces:

Security requirements:

  1. Authentication - Verify user identity
  2. Authorization - Domain-based access control (RBAC)
  3. Rate limiting - Prevent abuse
  4. Audit logging - Track access for compliance

This ADR focuses on security architecture. Related:

Decision

1. Authentication via OIDC

Use Quarkus OIDC for JWT-based authentication:

# application.properties
quarkus.oidc.enabled=true
quarkus.oidc.auth-server-url=${OIDC_SERVER_URL}
quarkus.oidc.client-id=${OIDC_CLIENT_ID}
quarkus.oidc.credentials.secret=${OIDC_CLIENT_SECRET}
quarkus.oidc.application-type=service

Token validation:

2. Role-Based Access Control (RBAC)

Domain access controlled by user roles:

Role Accessible Domains
developer idempiere, cloudempiere, angular, mobile
senior_dev All of developer + devops
support support, business
team_lead All domains
admin All domains + admin functions

Domain-role mapping in database:

CREATE TABLE knowledge_domains (
    name VARCHAR(50) PRIMARY KEY,
    required_roles TEXT[],  -- e.g., ARRAY['developer', 'team_lead']
    ...
);

DomainAccessService implementation:

@ApplicationScoped
public class DomainAccessService {

    private static final Set<String> ADMIN_ROLES = Set.of("admin", "superuser");
    private static final Set<String> PUBLIC_DOMAINS = Set.of("idempiere", "support");

    public boolean canAccess(List<String> userRoles, String domainName) {
        // Admin bypass
        if (hasAdminRole(userRoles)) return true;

        // Public domains
        if (PUBLIC_DOMAINS.contains(domainName)) return true;

        // Check domain-specific roles
        return domainRepository.findByName(domainName)
            .map(domain -> domain.canAccess(userRoles))
            .orElse(false);
    }
}

Implementation Status: DomainAccessService.java - Implemented

3. Rate Limiting

Prevent abuse with tiered rate limits:

Tier Requests/min Applies To
Anonymous 10 Unauthenticated
Authenticated 60 Normal users
Premium 300 Paid/enterprise
Unlimited - Internal services

Implementation using Bucket4j:

@ApplicationScoped
public class RateLimiter {

    private final Map<String, Bucket> buckets = new ConcurrentHashMap<>();

    public boolean tryConsume(String clientId, RateTier tier) {
        Bucket bucket = buckets.computeIfAbsent(clientId,
            k -> createBucket(tier));
        return bucket.tryConsume(1);
    }

    private Bucket createBucket(RateTier tier) {
        return Bucket.builder()
            .addLimit(Bandwidth.classic(tier.requestsPerMinute,
                Refill.greedy(tier.requestsPerMinute, Duration.ofMinutes(1))))
            .build();
    }
}

4. Audit Logging

Track all knowledge access:

CREATE TABLE knowledge_audit_log (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id VARCHAR(255),
    action VARCHAR(50) NOT NULL,      -- 'search', 'read', 'ingest'
    domain VARCHAR(50),
    resource_path TEXT,
    request_params JSONB,
    response_size INTEGER,
    latency_ms INTEGER,
    ip_address INET,
    user_agent TEXT,
    created_at TIMESTAMP DEFAULT now()
);

CREATE INDEX idx_audit_user ON knowledge_audit_log(user_id, created_at);
CREATE INDEX idx_audit_domain ON knowledge_audit_log(domain, created_at);

Audit service:

@ApplicationScoped
public class KnowledgeAuditService {

    public void logAccess(AuditEvent event) {
        // Async insert to avoid blocking
        executor.submit(() -> auditRepository.insert(event));
    }

    public void logSearch(String userId, String query, List<String> domains) {
        logAccess(AuditEvent.builder()
            .userId(userId)
            .action("search")
            .requestParams(Map.of("query", query, "domains", domains))
            .build());
    }
}

5. MCP Security Configuration

For MCP server specifically:

# application-mcp.properties
quarkus.http.port=8765

# OIDC for MCP
quarkus.oidc.enabled=true
quarkus.http.auth.basic=false

# CORS for Claude Desktop
quarkus.http.cors=true
quarkus.http.cors.origins=*

# Rate limiting
app.rate-limit.enabled=true
app.rate-limit.default-tier=authenticated

6. Security Layers by Interface

Interface Auth Rate Limit Audit
MCP Server OIDC (required) Per-token Full
Docs Server Optional Per-IP Read-only
Chat API OIDC (required) Per-user Full
CLI API key None Commands only

Implementation Status

Component File Status
DomainAccessService DomainAccessService.java Implemented
Rate Limiter RateLimiter.java Implemented
MCP Rate Limiting McpKnowledgeTools.java Wired
Audit Service KnowledgeAuditService.java Not implemented
OIDC Config application.properties Not configured
Audit Table V1__...schema.sql Schema created

RateLimiter Implementation

The RateLimiter.java provides in-memory token bucket rate limiting:

// Usage
@Inject RateLimiter rateLimiter;

// Check and consume
if (rateLimiter.tryConsume(clientId, RateLimiter.Tier.AUTHENTICATED)) {
    // Request allowed
} else {
    // Rate limited - return 429
}

// Check with details
RateLimiter.RateLimitResult result = rateLimiter.check(clientId, tier);
if (!result.allowed()) {
    // Include Retry-After header: result.retryAfterSeconds()
}

Configuration:

app.rate-limit.enabled=true
app.rate-limit.default-tier=AUTHENTICATED

MCP Rate Limiting Integration

Rate limiting is wired into McpKnowledgeTools.java for key search operations:

// Rate-limited MCP tools (ADR-073)
- searchKnowledge    // Main search tool
- askSupport         // Support question answering
- searchDomain       // Multi-domain federated search

// Usage in MCP tool methods:
ToolResponse rateLimitError = checkRateLimit("searchKnowledge");
if (rateLimitError != null) {
    return rateLimitError; // Returns 429-style error to AI client
}

Client Identification:

Consequences

Benefits

Drawbacks

Next Steps

  1. ✅ ~~Implement RateLimiter~~ (in-memory token bucket)
  2. ✅ ~~Wire rate limiting to MCP tools~~ (searchKnowledge, askSupport, searchDomain)
  3. Configure OIDC provider (Keycloak or Auth0)
  4. Implement KnowledgeAuditService
  5. Add @RolesAllowed annotations to endpoints
  6. Configure MCP server auth

References

Path: /docs/developers/architecture/idempiere-hub/073-knowledge-security-layer