Skip to content

Latest commit

 

History

History
730 lines (577 loc) · 23 KB

File metadata and controls

730 lines (577 loc) · 23 KB

Security Center Integration - Complete Implementation

Status:FULLY INTEGRATED AND OPERATIONAL

Date: January 2025
Integration Version: lib-common-application v1.0.0-SNAPSHOT
Security Center Version: common-platform-security-center v1.0.0-SNAPSHOT


Overview

The Firefly Security Center has been fully integrated into lib-common-application, providing automatic session management, role resolution, permission derivation, and authorization for all Application Layer microservices.

What Was Integrated

The integration connects the Security Center's FireflySessionManager with the Application Layer's context resolution and authorization framework, enabling:

  1. Automatic Role Resolution - Roles extracted from party contracts
  2. Automatic Permission Resolution - Permissions derived from role scopes
  3. Product Access Validation - Validates party access to specific products/contracts
  4. Session Caching - High-performance caching of party sessions
  5. Graceful Degradation - Application continues to function even if Security Center is temporarily unavailable

Architecture

Integration Points

┌─────────────────────────────────────────────────────────────┐
│           Application Layer Microservice                    │
│         (using lib-common-application)                      │
│                                                              │
│  ┌────────────────────────────────────────────────────┐    │
│  │   Controller (extends AbstractResourceController)  │    │
│  │   - Extracts contractId, productId from path       │    │
│  │   - Calls resolveExecutionContext(...)             │    │
│  └─────────────────────┬──────────────────────────────┘    │
│                        │                                     │
│                        ↓                                     │
│  ┌────────────────────────────────────────────────────┐    │
│  │   DefaultContextResolver                           │    │
│  │   - Extracts partyId from X-Party-Id header        │    │
│  │   - Calls FireflySessionManager.createOrGetSession │    │
│  │   - Uses SessionContextMapper to extract roles     │    │
│  │   - Uses SessionContextMapper to extract perms     │    │
│  └─────────────────────┬──────────────────────────────┘    │
│                        │                                     │
│                        ↓                                     │
│  ┌────────────────────────────────────────────────────┐    │
│  │   DefaultSecurityAuthorizationService              │    │
│  │   - Validates product access via sessionManager    │    │
│  │   - Checks roles and permissions                   │    │
│  └────────────────────────────────────────────────────┘    │
└─────────────────────┬────────────────────────────────────────┘
                      │
                      ↓
┌─────────────────────────────────────────────────────────────┐
│          Firefly Security Center                            │
│                                                              │
│  ┌────────────────────────────────────────────────────┐    │
│  │   FireflySessionManager                            │    │
│  │   - Aggregates session from multiple services      │    │
│  │   - Returns SessionContextDTO with:                │    │
│  │     * Customer info                                │    │
│  │     * Active contracts                             │    │
│  │     * Roles in each contract                       │    │
│  │     * Role scopes (permissions)                    │    │
│  │     * Product information                          │    │
│  └────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────┘

Data Flow

1. Request Arrives at Controller

@GetMapping("/contracts/{contractId}/products/{productId}/transactions")
public Mono<List<Transaction>> getTransactions(
        @PathVariable UUID contractId,
        @PathVariable UUID productId,
        ServerWebExchange exchange) {
    
    return resolveExecutionContext(exchange, contractId, productId)
        .flatMap(context -> transactionService.list(context));
}

2. DefaultContextResolver Resolves Context

// Extract partyId from Istio header
UUID partyId = extractFromHeader(exchange, "X-Party-Id");

// Call Security Center to get enriched session
sessionManager.createOrGetSession(exchange)
    .map(session -> {
        // Extract roles based on context scope
        Set<String> roles = SessionContextMapper.extractRoles(
            session, contractId, productId
        );
        
        // Extract permissions from role scopes
        Set<String> permissions = SessionContextMapper.extractPermissions(
            session, contractId, productId
        );
        
        return AppContext.builder()
            .partyId(partyId)
            .contractId(contractId)
            .productId(productId)
            .roles(roles)
            .permissions(permissions)
            .build();
    });

3. SessionContextDTO Structure

{
  "sessionId": "550e8400-e29b-41d4-a716-446655440000",
  "partyId": "123e4567-e89b-12d3-a456-426614174000",
  "customerInfo": { ... },
  "activeContracts": [
    {
      "contractId": "abc-123",
      "roleInContract": {
        "roleCode": "owner",
        "scopes": [
          {
            "actionType": "READ",
            "resourceType": "BALANCE"
          },
          {
            "actionType": "WRITE",
            "resourceType": "TRANSACTION"
          }
        ]
      },
      "product": {
        "productId": "def-456",
        "productName": "Checking Account"
      }
    }
  ]
}

4. Roles and Permissions Extracted

  • Roles: ["owner"]
  • Permissions: ["owner:READ:BALANCE", "owner:WRITE:TRANSACTION"]

5. Authorization Performed

@Secure(requireProduct = true, requireRole = "owner")
  • ✅ Product access validated via sessionManager.hasAccessToProduct()
  • ✅ Role checked via context.hasRole("owner")
  • ✅ Request authorized

Implementation Details

1. Dependency Added

File: lib-common-application/pom.xml

<dependency>
    <groupId>org.fireflyframework</groupId>
    <artifactId>common-platform-security-center-session</artifactId>
    <version>${project.version}</version>
</dependency>

This brings in:

  • FireflySessionManager interface
  • SessionContextDTO and related DTOs
  • Session management abstractions

2. SessionContextMapper Utility

File: lib-common-application/src/main/java/org/fireflyframework/common/application/util/SessionContextMapper.java

Purpose: Utility class for extracting roles and permissions from SessionContextDTO.

Key Methods:

public static Set<String> extractRoles(
    SessionContextDTO session, 
    UUID contractId, 
    UUID productId
)
  • Extracts role codes from active contracts
  • Applies scoping: party-level, contract-level, or product-level
  • Returns role codes like "owner", "account_viewer"
public static Set<String> extractPermissions(
    SessionContextDTO session, 
    UUID contractId, 
    UUID productId
)
  • Extracts permissions from role scopes
  • Format: {roleCode}:{actionType}:{resourceType}
  • Examples: "owner:READ:BALANCE", "account_viewer:READ:TRANSACTION"
public static boolean hasAccessToProduct(
    SessionContextDTO session, 
    UUID productId
)
  • Checks if party has access to a specific product through any active contract
public static boolean hasPermission(
    SessionContextDTO session, 
    UUID productId, 
    String actionType, 
    String resourceType
)
  • Checks if party has a specific permission (action + resource) for a product

Scoping Rules:

  • Party-level: contractId=null, productId=null → All roles from all contracts
  • Contract-level: contractId=X, productId=null → Roles from contract X
  • Product-level: contractId=X, productId=Y → Roles from contract X + product Y

3. DefaultContextResolver Integration

File: lib-common-application/src/main/java/org/fireflyframework/common/application/resolver/DefaultContextResolver.java

Changes:

  1. Injected FireflySessionManager
@Autowired(required = false)
private final FireflySessionManager sessionManager;
  1. Updated resolveRoles()
@Override
protected Mono<Set<String>> resolveRoles(AppContext context, ServerWebExchange exchange) {
    if (sessionManager == null) {
        log.warn("FireflySessionManager not available - returning empty roles");
        return Mono.just(Set.of());
    }
    
    return sessionManager.createOrGetSession(exchange)
        .map(session -> SessionContextMapper.extractRoles(
            session, context.getContractId(), context.getProductId()
        ))
        .onErrorReturn(Set.of()); // Graceful degradation
}
  1. Updated resolvePermissions()
@Override
protected Mono<Set<String>> resolvePermissions(AppContext context, ServerWebExchange exchange) {
    if (sessionManager == null) {
        log.warn("FireflySessionManager not available - returning empty permissions");
        return Mono.just(Set.of());
    }
    
    return sessionManager.createOrGetSession(exchange)
        .map(session -> SessionContextMapper.extractPermissions(
            session, context.getContractId(), context.getProductId()
        ))
        .onErrorReturn(Set.of()); // Graceful degradation
}

Key Features:

  • ✅ Automatic role/permission resolution from Security Center
  • ✅ Graceful degradation if Security Center unavailable
  • ✅ Error handling with fallback to empty sets
  • ✅ Logging for observability

4. DefaultSecurityAuthorizationService Integration

File: lib-common-application/src/main/java/org/fireflyframework/common/application/security/DefaultSecurityAuthorizationService.java

Changes:

  1. Injected FireflySessionManager
@Autowired(required = false)
private final FireflySessionManager sessionManager;
  1. Enhanced authorizeWithSecurityCenter()
@Override
protected Mono<AppSecurityContext> authorizeWithSecurityCenter(
        AppContext context, 
        AppSecurityContext securityContext) {
    
    if (sessionManager == null) {
        log.warn("FireflySessionManager not available - falling back to basic checks");
        return super.authorizeWithSecurityCenter(context, securityContext);
    }
    
    // Validate product access
    if (context.getProductId() != null) {
        return sessionManager.hasAccessToProduct(context.getPartyId(), context.getProductId())
            .flatMap(hasAccess -> {
                if (!hasAccess) {
                    return Mono.just(createUnauthorizedContext(
                        securityContext, "No access to requested product"
                    ));
                }
                return performRolePermissionChecks(context, securityContext);
            })
            .onErrorResume(error -> {
                // Graceful degradation
                log.warn("Falling back to basic checks due to error");
                return performRolePermissionChecks(context, securityContext);
            });
    }
    
    return performRolePermissionChecks(context, securityContext);
}
  1. Added performRolePermissionChecks()
  • Performs standard role and permission validation
  • Used after product access is validated

Key Features:

  • ✅ Product access validation via Security Center
  • ✅ Graceful degradation on errors
  • ✅ Falls back to basic role/permission checks if needed
  • ✅ Detailed logging for troubleshooting

Usage Examples

Example 1: Resource Endpoint with Full Context

@RestController
@RequestMapping("/api/v1/contracts/{contractId}/products/{productId}/transactions")
public class TransactionController extends AbstractResourceController {
    
    @Autowired
    private TransactionApplicationService transactionService;
    
    @GetMapping
    @Secure(
        requireParty = true, 
        requireContract = true, 
        requireProduct = true, 
        requireRole = "owner"
    )
    public Mono<List<TransactionDTO>> listTransactions(
            @PathVariable UUID contractId,
            @PathVariable UUID productId,
            ServerWebExchange exchange) {
        
        // Automatic resolution:
        // 1. Extracts partyId from X-Party-Id header
        // 2. Calls Security Center to get session
        // 3. Extracts roles for this specific contract+product
        // 4. Validates party has "owner" role
        // 5. Validates party has access to this product
        return resolveExecutionContext(exchange, contractId, productId)
            .flatMap(context -> transactionService.listTransactions(context));
    }
}

What happens automatically:

  1. ✅ Party ID extracted from Istio header
  2. ✅ Security Center session fetched
  3. ✅ Roles extracted: ["owner"] (for this contract+product)
  4. ✅ Permissions extracted: ["owner:READ:TRANSACTION", "owner:WRITE:TRANSACTION", ...]
  5. ✅ Product access validated
  6. ✅ Role requirement checked
  7. ✅ Request authorized

Example 2: Application Endpoint (No Contract/Product)

@RestController
@RequestMapping("/api/v1/onboarding")
public class OnboardingController extends AbstractApplicationController {
    
    @Autowired
    private OnboardingApplicationService onboardingService;
    
    @PostMapping("/start")
    @Secure(requireParty = true, requireRole = "customer:onboard")
    public Mono<OnboardingResponse> startOnboarding(
            @RequestBody OnboardingRequest request,
            ServerWebExchange exchange) {
        
        // Automatic resolution:
        // 1. Extracts partyId
        // 2. Gets session from Security Center
        // 3. Extracts ALL roles from ALL contracts (party-level)
        // 4. Validates party has "customer:onboard" role
        return resolveExecutionContext(exchange)
            .flatMap(context -> onboardingService.startOnboarding(context, request));
    }
}

What happens automatically:

  1. ✅ Party ID extracted
  2. ✅ Security Center session fetched
  3. ✅ Roles extracted: ["customer:onboard", "owner", "account_viewer", ...] (all contracts)
  4. ✅ Role requirement checked
  5. ✅ Request authorized

Example 3: Programmatic Authorization Checks

@Service
public class AccountApplicationService extends AbstractApplicationService {
    
    public Mono<Account> getAccount(ApplicationExecutionContext context, UUID accountId) {
        // Check if user has specific permission
        return requirePermission(context.getContext(), "owner:READ:BALANCE")
            .then(accountDomainService.getAccount(accountId))
            .map(account -> enrichWithBalance(account, context));
    }
    
    public Mono<Transaction> createTransaction(
            ApplicationExecutionContext context, 
            TransactionRequest request) {
        
        // Check if user has write permission
        return requirePermission(context.getContext(), "owner:WRITE:TRANSACTION")
            .then(transactionDomainService.createTransaction(request));
    }
}

Permission Format

Permissions are formatted as: {roleCode}:{actionType}:{resourceType}

Examples

Permission String Description
owner:READ:BALANCE Owner can read balance
owner:WRITE:TRANSACTION Owner can create transactions
account_viewer:READ:TRANSACTION Viewer can read transactions
account_viewer:READ:BALANCE Viewer can read balance
transaction_creator:WRITE:TRANSACTION Can create transactions

Action Types (from Security Center)

  • READ - Read/view access
  • WRITE - Create/update access
  • DELETE - Delete access
  • EXECUTE - Execute operations
  • APPROVE - Approve requests

Resource Types (from Security Center)

  • BALANCE - Account balance
  • TRANSACTION - Transactions
  • ACCOUNT - Account information
  • PRODUCT - Product details
  • CONTRACT - Contract data

Configuration

Enable/Disable Security Center Integration

application.yml

firefly:
  application:
    security:
      enabled: true                    # Enable security features
      use-security-center: true        # Use Security Center (when available)
      fail-on-missing: false           # Don't fail if Security Center unavailable

Graceful Degradation

The integration is designed to work gracefully even when Security Center is unavailable:

When Security Center is Available:

  • ✅ Full role/permission resolution
  • ✅ Product access validation
  • ✅ Session caching for performance

When Security Center is Unavailable:

  • ⚠️ Roles/permissions return empty sets
  • ⚠️ Authorization based on @Secure annotations may fail
  • ✅ Application continues to run
  • ✅ Logs warnings for troubleshooting

Testing

Unit Tests

@ExtendWith(MockitoExtension.class)
class SessionContextMapperTest {
    
    @Test
    void shouldExtractRolesForContractAndProduct() {
        // Given
        SessionContextDTO session = createTestSession();
        UUID contractId = UUID.randomUUID();
        UUID productId = UUID.randomUUID();
        
        // When
        Set<String> roles = SessionContextMapper.extractRoles(
            session, contractId, productId
        );
        
        // Then
        assertThat(roles).containsExactly("owner", "account_viewer");
    }
}

Integration Tests

@SpringBootTest
@AutoConfigureWebTestClient
class TransactionControllerIntegrationTest {
    
    @Autowired
    private WebTestClient webClient;
    
    @MockBean
    private FireflySessionManager sessionManager;
    
    @Test
    void shouldAuthorizeTransactionListWithValidSession() {
        // Given
        SessionContextDTO session = createSessionWithOwnerRole();
        when(sessionManager.createOrGetSession(any()))
            .thenReturn(Mono.just(session));
        
        // When/Then
        webClient.get()
            .uri("/api/v1/contracts/{c}/products/{p}/transactions", 
                 contractId, productId)
            .header("X-Party-Id", partyId.toString())
            .exchange()
            .expectStatus().isOk();
    }
}

Troubleshooting

Issue: "FireflySessionManager not available"

Cause: Security Center is not deployed or not accessible.

Solution:

  1. Deploy common-platform-security-center microservice
  2. Ensure network connectivity between services
  3. Check Kubernetes/Docker service discovery

Workaround: Application will continue with empty roles/permissions


Issue: "No access to requested product"

Cause: Party does not have a contract for the requested product.

Solution:

  1. Verify party has active contracts in contract management
  2. Check contract status is ACTIVE
  3. Verify product is linked to contract
  4. Check Security Center session data

Issue: "Required roles not present"

Cause: Party's role in contract doesn't match required role.

Solution:

  1. Check role assignments in contract management
  2. Verify role codes match (case-sensitive)
  3. Check Security Center session includes correct roles
  4. Enable DEBUG logging to see resolved roles

Debug Logging

Enable detailed logging to troubleshoot:

logging:
  level:
    org.fireflyframework.application.resolver: DEBUG
    org.fireflyframework.application.security: DEBUG
    org.fireflyframework.application.util: DEBUG
    org.fireflyframework.security.center: DEBUG

Logs to check:

  • Resolved X roles for party Y: [...]
  • Resolved X permissions for party Y: [...]
  • FireflySessionManager not available - returning empty roles
  • Access check for product X: true/false

Performance

Caching

Security Center caches sessions with 30-minute TTL by default:

First Request:
  Controller → DefaultContextResolver → Security Center → (500ms)
  
Subsequent Requests (within 30 min):
  Controller → DefaultContextResolver → Cache → (5ms)
  
  → 100x faster! 🚀

Metrics

Monitor Security Center integration via Actuator:

# Session cache hit rate
GET /actuator/metrics/cache.gets?tag=cache:session-cache

# Context resolution time
GET /actuator/metrics/firefly.context.resolution.time

# Authorization time
GET /actuator/metrics/firefly.authorization.time

Migration Notes

Before Integration

// Manual security checks
if (!hasRole(context, "owner")) {
    throw new AccessDeniedException("Not authorized");
}

After Integration

// Automatic via @Secure annotation
@Secure(requireRole = "owner")
public Mono<Account> getAccount(...) {
    // Roles automatically resolved and validated
}

Key Changes

  1. ✅ Remove manual role/permission resolution code
  2. ✅ Use @Secure annotations on controllers
  3. ✅ Extend appropriate base controller
  4. ✅ Call resolveExecutionContext() in handlers
  5. ✅ Trust the framework to handle authorization

Future Enhancements

Planned

  • SpEL expression evaluation for complex authorization rules
  • Policy-based authorization via Security Center
  • Attribute-based access control (ABAC)
  • Audit trail integration

Under Consideration

  • Fine-grained permissions at field level
  • Dynamic role assignment
  • Time-based access restrictions
  • Geographic access restrictions

Support

For questions or issues:

  • Review this documentation
  • Check SECURITY_GUIDE.md
  • Enable DEBUG logging
  • Contact the Firefly Security Team

Status:Production Ready
Last Updated: January 2025
Maintained By: Firefly Development Team