| name | access-control-policy |
| description | Identity and access management: RBAC, least privilege, MFA, quarterly reviews per ISO 27001 A.5.15, A.8.2, A.8.3 |
Access Control Policy Skill
Purpose
This skill provides systematic guidance for implementing zero-trust access control within the CIA platform, ensuring identity-centric security through role-based access control (RBAC), least privilege principle, multi-factor authentication (MFA), and regular access reviews per ISO 27001 A.5.15, A.8.2, and A.8.3.
When to Use This Skill
Apply this skill when:
- ✅ Implementing authentication and authorization systems
- ✅ Designing role-based access control (RBAC) models
- ✅ Configuring MFA for privileged accounts
- ✅ Establishing access review procedures
- ✅ Implementing least privilege access controls
- ✅ Setting up identity federation (AWS IAM Identity Center, GitHub SSO)
- ✅ Conducting quarterly access audits
- ✅ Managing privileged access (break-glass procedures)
Do NOT skip for:
- ❌ Internal-only systems (still require authentication)
- ❌ Development environments (may contain production data)
- ❌ Administrative interfaces (highest risk, require MFA)
- ❌ API access (requires API keys, OAuth tokens)
Zero-Trust Access Architecture
Identity-Centric Design Principle
graph TD
subgraph Identity["🔐 Identity Provider"]
PRIMARY["Primary IdP<br/>🔐 MFA Enforced<br/>Central Authentication"]
end
subgraph Cloud["☁️ Cloud Infrastructure"]
IDC["AWS Identity Center<br/>🔐 SSO + MFA"]
IAM["IAM Roles<br/>🛡️ Least Privilege"]
ACCOUNTS["AWS Accounts<br/>📊 Federated Access"]
end
subgraph Dev["📝 Development"]
GITHUB["GitHub Organization<br/>🔐 MFA Required"]
CICD["CI/CD Pipeline<br/>🔑 Service Accounts"]
end
subgraph Business["💼 Business Systems"]
FINANCE["Financial Systems<br/>💰 Hardware Token"]
MARKETING["Marketing Tools<br/>📱 Platform MFA"]
SECURITY["Security Tools<br/>🛡️ Enhanced MFA"]
end
PRIMARY --> IDC
PRIMARY --> GITHUB
PRIMARY --> FINANCE
IDC --> IAM
IDC --> ACCOUNTS
GITHUB --> CICD
style Identity fill:#1565C0
style Cloud fill:#4CAF50
style Dev fill:#FF9800
style Business fill:#7B1FA2
Access Control Matrix
Integration with Classification Framework:
| Asset Category | Classification | Access Method | MFA Requirement | Session Timeout | Review Frequency |
|---|
| RESTRICTED Data | Extreme | Hardware MFA + Zero Trust | FIDO2 + Backup | 1 hour | Monthly |
| Cloud Infrastructure | Very High | Identity Center SSO | Hardware + TOTP | 4 hours | Monthly |
| Development Platform | High | Platform MFA + SSH Keys | TOTP + SSH Cert | 8 hours | Quarterly |
| Financial Systems | Very High | Provider MFA | Hardware Token | 1 hour | Monthly |
| Business Intelligence | Moderate | SSO Integration | TOTP | 24 hours | Semi-Annual |
| Marketing Platforms | Public/Internal | Platform Native | Platform MFA | 7 days | Annual |
Role-Based Access Control (RBAC)
RBAC Implementation in Spring Security
Permission Levels and Scope:
@Configuration
@EnableGlobalMethodSecurity(
prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true
)
public class SecurityConfig {
@Bean
public RoleHierarchy roleHierarchy() {
RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
hierarchy.setHierarchy(
"ROLE_ADMIN > ROLE_PARTY_ANALYST\n" +
"ROLE_PARTY_ANALYST > ROLE_AUTHENTICATED_USER\n" +
"ROLE_AUTHENTICATED_USER > ROLE_GUEST"
);
return hierarchy;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authz -> authz
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/", "/login", "/register").permitAll()
.requestMatchers("/api/internal/**").authenticated()
.requestMatchers("/api/party-financial/**")
.hasRole("PARTY_ANALYST")
.requestMatchers("/api/admin/**")
.hasRole("ADMIN")
.access("@mfaVerifier.isMfaAuthenticated(authentication)")
.anyRequest().authenticated()
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.maximumSessions(1)
.maxSessionsPreventsLogin(true)
)
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
);
return http.build();
}
}
Role Definitions
| Role | Scope | Permissions | Use Cases | Access Pattern |
|---|
| ROLE_ADMIN | System-wide | Full access, user management, security config | Emergency operations, security incidents | Break-glass only |
| ROLE_PARTY_ANALYST | Party data | Read/write party financial records, voting analysis | Political analysis, report generation | Daily operations |
| ROLE_AUTHENTICATED_USER | Public + internal | Read public data, basic operations | Registered users, researchers | Standard usage |
| ROLE_GUEST | Public only | Read public voting records, party information | Anonymous visitors | Public access |
Least Privilege Enforcement
@Service
public class PartyFinancialService {
private final AuditLogger auditLogger;
private final PartyFinancialRepository repository;
@PreAuthorize("hasRole('PARTY_ANALYST') or hasRole('ADMIN')")
@PostAuthorize("returnObject.userId == authentication.principal.username or hasRole('ADMIN')")
public PartyFinancialRecord getFinancialRecord(Long recordId) {
auditLogger.logAccess(
"CONFIDENTIAL",
"party_financial_record",
recordId,
SecurityContextHolder.getContext().getAuthentication().getName()
);
return repository.findById(recordId)
.orElseThrow(() -> new AccessDeniedException("Record not found or access denied"));
}
@PreAuthorize("hasRole('PARTY_ANALYST')")
@Audited(action = "CREATE", resourceType = "party_financial_record")
public PartyFinancialRecord createRecord(PartyFinancialRecord record) {
return repository.save(record);
}
@PreAuthorize("hasRole('ADMIN') and @mfaVerifier.isMfaAuthenticated(authentication)")
@Audited(action = "DELETE", resourceType = "party_financial_record", severity = "HIGH")
public void deleteRecord(Long recordId) {
auditLogger.logPrivilegedAction(
"DELETE_FINANCIAL_RECORD",
recordId,
SecurityContextHolder.getContext().getAuthentication().getName()
);
repository.deleteById(recordId);
}
}
Multi-Factor Authentication (MFA)
MFA Requirements by Access Level
graph TD
subgraph Access["🔐 Access Levels"]
ADMIN["Admin Access<br/>🚨 Break-Glass"]
PRIVILEGED["Privileged Data<br/>💰 Financial Systems"]
STANDARD["Standard Access<br/>📊 Business Data"]
PUBLIC["Public Access<br/>🌐 No Authentication"]
end
subgraph MFA["🔑 MFA Methods"]
HARDWARE["Hardware Token<br/>🔐 FIDO2, YubiKey"]
TOTP["TOTP App<br/>📱 Authenticator"]
SMS["SMS Backup<br/>📞 Secondary"]
PLATFORM["Platform Native<br/>🏢 GitHub, AWS"]
end
subgraph Verify["✅ Verification"]
CONTINUOUS["Continuous Verification<br/>⏱️ Session Monitoring"]
PERIODIC["Periodic Re-auth<br/>🔄 Timeout-Based"]
ONETIME["One-Time Verification<br/>✅ Login Only"]
end
ADMIN --> HARDWARE
PRIVILEGED --> HARDWARE
PRIVILEGED --> TOTP
STANDARD --> TOTP
STANDARD --> PLATFORM
HARDWARE --> CONTINUOUS
TOTP --> PERIODIC
PLATFORM --> ONETIME
style Access fill:#1565C0
style MFA fill:#FF9800
style Verify fill:#4CAF50
MFA Implementation Example
@Service
public class MfaVerificationService {
private final TotpService totpService;
private final AuditLogger auditLogger;
public boolean isMfaAuthenticated(Authentication authentication) {
if (authentication == null || !authentication.isAuthenticated()) {
return false;
}
Object principal = authentication.getPrincipal();
if (!(principal instanceof UserDetails)) {
return false;
}
UserDetails user = (UserDetails) principal;
String username = user.getUsername();
LocalDateTime mfaVerifiedAt = getMfaVerificationTime(username);
LocalDateTime now = LocalDateTime.now();
if (mfaVerifiedAt == null ||
Duration.between(mfaVerifiedAt, now).toHours() >= 1) {
auditLogger.logMfaRequired(username);
return false;
}
return true;
}
public boolean verifyTotpCode(String username, String totpCode) {
boolean isValid = totpService.verifyCode(username, totpCode);
if (isValid) {
updateMfaVerificationTime(username, LocalDateTime.now());
auditLogger.logMfaSuccess(username);
} else {
auditLogger.logMfaFailure(username);
}
return isValid;
}
public void requireMfaReVerification(String username) {
clearMfaVerificationTime(username);
auditLogger.logMfaRequired(username, "PRIVILEGED_OPERATION");
}
}
@RestController
@RequestMapping("/api/auth/mfa")
public class MfaController {
private final MfaVerificationService mfaService;
@PostMapping("/verify")
@PreAuthorize("isAuthenticated()")
public ResponseEntity<MfaVerificationResponse> verifyMfa(
@RequestBody @Valid MfaVerificationRequest request,
Authentication authentication) {
String username = authentication.getName();
boolean isValid = mfaService.verifyTotpCode(username, request.getTotpCode());
if (isValid) {
return ResponseEntity.ok(new MfaVerificationResponse(true, "MFA verified"));
} else {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(new MfaVerificationResponse(false, "Invalid MFA code"));
}
}
@GetMapping("/status")
@PreAuthorize("isAuthenticated()")
public ResponseEntity<MfaStatusResponse> getMfaStatus(Authentication authentication) {
String username = authentication.getName();
boolean isMfaVerified = mfaService.isMfaAuthenticated(authentication);
return ResponseEntity.ok(new MfaStatusResponse(isMfaVerified));
}
}
Session Management by Classification
@Configuration
public class SessionConfig {
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
@Component
public class SessionTimeoutCustomizer implements ServletContextInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.setSessionTimeout(30);
}
}
@Component
public class DynamicSessionTimeoutManager {
public int getSessionTimeout(Authentication authentication) {
Collection<? extends GrantedAuthority> authorities =
authentication.getAuthorities();
if (authorities.stream().anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"))) {
return 60;
}
if (authorities.stream().anyMatch(a -> a.getAuthority().equals("ROLE_PARTY_ANALYST"))) {
return 240;
}
if (authorities.stream().anyMatch(a -> a.getAuthority().equals("ROLE_AUTHENTICATED_USER"))) {
return 480;
}
return 30;
}
}
}
Quarterly Access Reviews
Access Review Procedure
graph TD
START["🔍 Start Access Review"] --> EXTRACT["📊 Extract Access Data"]
EXTRACT --> ANALYZE{"📈 Analysis"}
ANALYZE --> DORMANT["🚫 Identify Dormant Accounts<br/>>90 days no activity"]
ANALYZE --> EXCESSIVE["⚠️ Identify Excessive Permissions<br/>More than role requires"]
ANALYZE --> SEGREGATION["🔀 Check Segregation of Duties<br/>Incompatible roles"]
DORMANT --> REVIEW{"👤 CEO Review"}
EXCESSIVE --> REVIEW
SEGREGATION --> REVIEW
REVIEW -->|Revoke| REVOKE["🔒 Revoke Access"]
REVIEW -->|Retain| DOCUMENT["📝 Document Justification"]
REVIEW -->|Modify| ADJUST["⚙️ Adjust Permissions"]
REVOKE --> AUDIT["📋 Audit Trail"]
DOCUMENT --> AUDIT
ADJUST --> AUDIT
AUDIT --> REPORT["📊 Review Report"]
REPORT --> STORE["💾 Store Evidence"]
style START fill:#1565C0
style REVIEW fill:#FF9800
style AUDIT fill:#4CAF50
Automated Access Review Script
#!/bin/bash
set -euo pipefail
REPORT_DATE=$(date +%Y-%m-%d)
REVIEW_PERIOD_DAYS=90
REPORT_FILE="access-review-${REPORT_DATE}.md"
echo "# Quarterly Access Review - ${REPORT_DATE}" > "${REPORT_FILE}"
echo "" >> "${REPORT_FILE}"
echo "## Dormant Accounts (>90 days inactive)" >> "${REPORT_FILE}"
echo "" >> "${REPORT_FILE}"
aws accessanalyzer list-analyzers --region us-east-1 --output json | \
jq -r '.analyzers[] | select(.status=="ACTIVE") | .name' | \
while read -r analyzer; do
echo "### AWS Analyzer: ${analyzer}" >> "${REPORT_FILE}"
aws accessanalyzer list-findings \
--analyzer-arn "arn:aws:access-analyzer:us-east-1:ACCOUNT_ID:analyzer/${analyzer}" \
--filter 'resourceType=AWS::IAM::User,status=ACTIVE' \
--region us-east-1 \
--output json | \
jq -r '.findings[] | "- User: \(.resource) - Last accessed: \(.analyzedAt)"' \
>> "${REPORT_FILE}"
done
echo "" >> "${REPORT_FILE}"
echo "## GitHub Dormant Users" >> "${REPORT_FILE}"
echo "" >> "${REPORT_FILE}"
gh api orgs/Hack23/members --paginate | \
jq -r '.[].login' | \
while read -r user; do
LAST_ACTIVITY=$(gh api "users/${user}/events" --jq '.[0].created_at' 2>/dev/null || echo "never")
if [[ "${LAST_ACTIVITY}" == "never" ]] || \
[[ $(date -d "${LAST_ACTIVITY}" +%s) -lt $(date -d "${REVIEW_PERIOD_DAYS} days ago" +%s) ]]; then
echo "- ${user}: Last activity ${LAST_ACTIVITY}" >> "${REPORT_FILE}"
fi
done
echo "" >> "${REPORT_FILE}"
echo "## Privileged Access Audit" >> "${REPORT_FILE}"
echo "" >> "${REPORT_FILE}"
echo "### AWS Admin Users" >> "${REPORT_FILE}"
aws iam list-users --output json | \
jq -r '.Users[].UserName' | \
while read -r user; do
aws iam list-attached-user-policies --user-name "${user}" --output json | \
jq -r '.AttachedPolicies[] | select(.PolicyName | contains("Admin")) | .PolicyName' | \
while read -r policy; do
echo "- ${user}: ${policy}" >> "${REPORT_FILE}"
done
done
echo "" >> "${REPORT_FILE}"
echo "## MFA Compliance Status" >> "${REPORT_FILE}"
echo "" >> "${REPORT_FILE}"
echo "### AWS Users Without MFA" >> "${REPORT_FILE}"
aws iam list-users --output json | \
jq -r '.Users[].UserName' | \
while read -r user; do
MFA_DEVICES=$(aws iam list-mfa-devices --user-name "${user}" --output json | jq '.MFADevices | length')
if [[ "${MFA_DEVICES}" -eq 0 ]]; then
echo "- ${user}: No MFA configured" >> "${REPORT_FILE}"
fi
done
echo "" >> "${REPORT_FILE}"
echo "## Review Summary" >> "${REPORT_FILE}"
echo "" >> "${REPORT_FILE}"
echo "- Review Date: ${REPORT_DATE}" >> "${REPORT_FILE}"
echo "- Review Period: Last ${REVIEW_PERIOD_DAYS} days" >> "${REPORT_FILE}"
echo "- Reviewer: CEO" >> "${REPORT_FILE}"
echo "- Next Review Date: $(date -d '+3 months' +%Y-%m-%d)" >> "${REPORT_FILE}"
echo "" >> "${REPORT_FILE}"
echo "## Actions Required" >> "${REPORT_FILE}"
echo "" >> "${REPORT_FILE}"
echo "- [ ] Review and revoke dormant accounts" >> "${REPORT_FILE}"
echo "- [ ] Verify privileged access justifications" >> "${REPORT_FILE}"
echo "- [ ] Enforce MFA for non-compliant users" >> "${REPORT_FILE}"
echo "- [ ] Document decisions in audit trail" >> "${REPORT_FILE}"
echo "Access review report generated: ${REPORT_FILE}"
AWS IAM Access Analyzer Integration
Continuous Access Monitoring
Resources:
AccessAnalyzer:
Type: AWS::AccessAnalyzer::Analyzer
Properties:
AnalyzerName: cia-access-analyzer
Type: ACCOUNT
Tags:
- Key: Purpose
Value: ContinuousAccessMonitoring
- Key: ISMSControl
Value: ISO27001-A.5.18
AccessAnalyzerEventRule:
Type: AWS::Events::Rule
Properties:
Name: access-analyzer-findings
Description: Alert on new IAM Access Analyzer findings
EventPattern:
source:
- aws.access-analyzer
detail-type:
- Access Analyzer Finding
detail:
status:
- ACTIVE
State: ENABLED
Targets:
- Arn: !GetAtt AccessAnalyzerTopic.Arn
Id: AccessAnalyzerSNS
AccessAnalyzerTopic:
Type: AWS::SNS::Topic
Properties:
TopicName: access-analyzer-findings
DisplayName: IAM Access Analyzer Findings
Subscription:
- Endpoint: security@hack23.com
Protocol: email
AccessAnalyzerRemediationFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName: access-analyzer-remediation
Runtime: python3.12
Handler: index.lambda_handler
Role: !GetAtt RemediationFunctionRole.Arn
Code:
ZipFile: |
import boto3
import json
accessanalyzer = boto3.client('accessanalyzer')
iam = boto3.client('iam')
def lambda_handler(event, context):
"""
Automated remediation for Access Analyzer findings
"""
finding = event['detail']
resource = finding['resource']
finding_type = finding['resourceType']
print(f"Processing finding for {resource} of type {finding_type}")
# Check if unused access (>90 days)
if 'UnusedAccess' in finding.get('findingType', ''):
# Flag for manual review
add_tag_for_review(resource)
# Check for external access
if 'ExternalAccess' in finding.get('findingType', ''):
# Alert and require justification
send_alert(resource, finding)
return {
'statusCode': 200,
'body': json.dumps('Finding processed')
}
def add_tag_for_review(resource):
"""Tag resource for quarterly review"""
pass
def send_alert(resource, finding):
"""Send alert for external access"""
pass
Timeout: 60
Tags:
- Key: ISMSControl
Value: ISO27001-A.5.18
Segregation of Duties
See Segregation of Duties Policy for:
- Incompatible role pairs requiring separation
- Single-person organization compensating controls
- Temporal and tool-based separation mechanisms
Key Access Control Aspects Supporting SoD:
- Least Privilege: Default to minimum permissions required
- Time-Limited Elevation: Admin access with automatic expiration
- Audit Trail: All permission changes logged
- Quarterly Review: Access permissions reviewed against least privilege
ISO 27001 Control Mapping
A.5.15 - Access Control
Control Objective: Business requirements for controlling access to information and systems.
Implementation:
- ✅ RBAC model with role hierarchy
- ✅ Least privilege enforcement via @PreAuthorize
- ✅ Zero-trust architecture
- ✅ Access control matrix by classification level
A.8.2 - Privileged Access Rights
Control Objective: Allocation and use of privileged access rights restricted and controlled.
Implementation:
- ✅ Break-glass procedures for emergency access
- ✅ MFA required for privileged operations
- ✅ Time-limited privileged access
- ✅ Comprehensive audit logging
A.8.3 - Information Access Restriction
Control Objective: Access to information and systems restricted per access control policy.
Implementation:
- ✅ Data classification integrated with access controls
- ✅ Row-level security for sensitive data
- ✅ API access controls with OAuth/JWT
- ✅ Network segmentation (security groups)
A.5.18 - Access Rights Review
Control Objective: User access rights reviewed at regular intervals.
Implementation:
- ✅ Quarterly automated access reviews
- ✅ AWS IAM Access Analyzer for unused access detection
- ✅ GitHub audit log review
- ✅ Dormant account identification and revocation
NIST Cybersecurity Framework Mapping
PR.AC-1: Identities and credentials issued, managed, verified, revoked
- ✅ Centralized identity provider (AWS Identity Center, GitHub)
- ✅ MFA enforcement for privileged access
PR.AC-4: Access permissions managed, incorporating least privilege
- ✅ RBAC with role hierarchy
- ✅ @PreAuthorize annotations for method-level security
PR.AC-7: Users, devices authenticated (MFA for remote access)
- ✅ TOTP, FIDO2 hardware tokens
- ✅ Platform-native MFA (GitHub, AWS)
CIS Controls Mapping
CIS Control 5: Account Management
- 5.2: Use unique passwords - ✅ Spring Security password encoding
- 5.3: Disable dormant accounts - ✅ Quarterly review script
- 5.4: Restrict administrator privileges - ✅ Least privilege RBAC
- 5.5: Establish access restrictions - ✅ Classification-based controls
CIS Control 6: Access Control Management
- 6.1: Establish access granting process - ✅ RBAC role assignment
- 6.2: Establish access revoking process - ✅ Automated revocation
- 6.7: Centralize account management - ✅ AWS Identity Center
- 6.8: Define and maintain role-based access control - ✅ Spring Security RBAC
Practical Implementation Checklist
For New Systems
For Existing Systems
Related Policies
References
- ISO 27001:2022 - A.5.15 Access Control
- ISO 27001:2022 - A.8.2 Privileged Access Rights
- ISO 27001:2022 - A.8.3 Information Access Restriction
- ISO 27001:2022 - A.5.18 Access Rights Review
- NIST SP 800-53r5 - AC (Access Control) Family
- CIS Controls v8 - Control 5: Account Management
- CIS Controls v8 - Control 6: Access Control Management