- name
- access-control
- description
- Least privilege principle, RBAC, authentication, authorization, and session management following Hack23 Access Control Policy
- license
- Apache-2.0
# Access Control Skill
## Purpose
This skill enforces access control requirements as defined in the [Hack23 ISMS Access Control Policy](https://github.com/Hack23/ISMS-PUBLIC/blob/main/Access_Control_Policy.md). It ensures that all systems implement proper authentication, authorization, and session management based on the principle of least privilege.
## Rules
### Principle of Least Privilege
**MUST:**
- Grant minimum permissions necessary to perform job functions
- Default to deny access (allowlist approach)
- Separate duties for critical functions (no single person has complete control)
- Regularly review and revoke unnecessary permissions
- Document permission requirements for each role
- Implement time-limited access for temporary needs
**MUST NOT:**
- Grant administrative privileges by default
- Share user accounts between multiple people
- Use shared service accounts without audit trails
- Grant permanent access when temporary access is sufficient
- Allow users to escalate their own privileges
### Role-Based Access Control (RBAC)
**MUST:**
- Define roles based on job functions
- Assign permissions to roles, not individual users
- Document role definitions and their permissions
- Implement role hierarchy where appropriate
- Enforce separation of duties through role conflicts
- Review role assignments quarterly
**Roles for Hack23 Projects:**
- **Public**: Unauthenticated users (read-only access to public content)
- **User**: Authenticated regular users (basic functionality)
- **Contributor**: Code contributors (read/write to repositories)
- **Maintainer**: Project maintainers (merge, release, moderate)
- **Administrator**: System administrators (full control, user management)
- **Security Officer**: Security team (audit logs, security settings)
**MUST NOT:**
- Create user-specific permissions (use roles instead)
- Assign multiple conflicting roles to same user
- Allow role self-assignment without approval
- Create overly broad roles that violate least privilege
### Authentication Requirements
**MUST:**
- Require authentication for all non-public resources
- Implement multi-factor authentication (MFA) for:
- Administrative accounts
- Access to Confidential or Restricted data
- Remote access to internal systems
- Privileged operations (deployments, configuration changes)
- Enforce strong password requirements:
- Minimum 12 characters
- At least one uppercase, lowercase, number, symbol
- No common passwords (check against known breached passwords)
- No reuse of last 5 passwords
- Implement account lockout after 5 failed attempts
- Require password change every 90 days for privileged accounts
- Terminate sessions after 30 minutes of inactivity
- Support passwordless authentication (FIDO2/WebAuthn) where possible
**MUST NOT:**
- Use basic authentication over unencrypted connections
- Store passwords in plaintext or reversible encryption
- Allow anonymous access to sensitive resources
- Use weak hashing algorithms (MD5, SHA-1) for passwords
- Implement custom authentication without security review
- Bypass authentication for "trusted" networks
### Authorization Requirements
**MUST:**
- Verify authorization at every access point
- Check permissions server-side (never rely on client-side checks)
- Validate user permissions for each resource accessed
- Implement attribute-based access control (ABAC) for complex policies
- Deny access by default (fail closed)
- Log all authorization failures
- Enforce authorization consistently across all interfaces (web, API, CLI)
**MUST NOT:**
- Assume authentication implies authorization
- Use client-side authorization checks as primary control
- Grant access based solely on URL/endpoint knowledge
- Skip authorization checks for internal APIs
- Use security through obscurity (hidden URLs don't require authentication)
### Session Management
**MUST:**
- Generate cryptographically random session IDs (minimum 128 bits entropy)
- Set HTTPOnly flag on session cookies (prevent JavaScript access)
- Set Secure flag on session cookies (HTTPS only)
- Set SameSite=Strict or Lax on session cookies (CSRF protection)
- Implement session timeout (30 minutes inactivity, 8 hours absolute)
- Regenerate session ID after authentication
- Invalidate sessions on logout
- Store session data securely (encrypted, integrity-protected)
- Implement concurrent session limits
- Provide users visibility into active sessions
**MUST NOT:**
- Include session IDs in URLs
- Use predictable session IDs
- Transmit session cookies over unencrypted connections
- Store sensitive data in client-side storage (localStorage, sessionStorage)
- Allow session fixation attacks
- Share sessions across different security contexts
### API Authentication
**MUST:**
- Require authentication for all non-public API endpoints
- Use OAuth 2.0 or OpenID Connect for third-party integrations
- Implement API key rotation (at least annually)
- Rate limit API requests to prevent abuse
- Validate API tokens on every request
- Use short-lived access tokens (< 1 hour) with refresh tokens
- Scope API tokens to minimum necessary permissions
**MUST NOT:**
- Include API keys in URLs or logs
- Use long-lived API keys without rotation
- Share API keys across multiple applications
- Transmit API keys over unencrypted connections
- Store API keys in client-side code
### Access Reviews
**MUST:**
- Review user access rights quarterly
- Remove access for terminated employees immediately
- Review privileged access monthly
- Document access review results
- Revoke unused accounts after 90 days of inactivity
- Audit access control changes
**MUST NOT:**
- Skip access reviews for "trusted" users
- Allow indefinite access without review
- Ignore orphaned accounts
- Delay access revocation for terminated users
## Examples
### Example 1: RBAC Implementation (Node.js/Express)
```javascript
// Define roles and permissions
const ROLES = {
PUBLIC: 'public',
USER: 'user',
CONTRIBUTOR: 'contributor',
MAINTAINER: 'maintainer',
ADMIN: 'administrator'
};
const PERMISSIONS = {
READ_PUBLIC: 'read:public',
READ_PRIVATE: 'read:private',
WRITE_CODE: 'write:code',
MERGE_PR: 'merge:pr',
MANAGE_USERS: 'manage:users',
MANAGE_SECURITY: 'manage:security'
};
// Role to permissions mapping
const rolePermissions = {
[ROLES.PUBLIC]: [PERMISSIONS.READ_PUBLIC],
[ROLES.USER]: [PERMISSIONS.READ_PUBLIC, PERMISSIONS.READ_PRIVATE],
[ROLES.CONTRIBUTOR]: [PERMISSIONS.READ_PUBLIC, PERMISSIONS.READ_PRIVATE, PERMISSIONS.WRITE_CODE],
[ROLES.MAINTAINER]: [PERMISSIONS.READ_PUBLIC, PERMISSIONS.READ_PRIVATE, PERMISSIONS.WRITE_CODE, PERMISSIONS.MERGE_PR],
[ROLES.ADMIN]: Object.values(PERMISSIONS) // All permissions
};
// Middleware to check permission
function requirePermission(permission) {
return (req, res, next) => {
// Must be authenticated
if (!req.user) {
return res.status(401).json({ error: 'Authentication required' });
}
// Get user's permissions from their role
const userPermissions = rolePermissions[req.user.role] || [];
// Check if user has required permission
if (!userPermissions.includes(permission)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
}
// Usage in routes
app.get('/api/public/data', requirePermission(PERMISSIONS.READ_PUBLIC), (req, res) => {
// Public data endpoint
});
app.post('/api/code/commit', requirePermission(PERMISSIONS.WRITE_CODE), (req, res) => {
// Commit code endpoint
});
app.post('/api/pr/merge', requirePermission(PERMISSIONS.MERGE_PR), (req, res) => {
// Merge PR endpoint
});
```
### Example 2: Secure Session Management (Express)
```javascript
const session = require('express-session');
const RedisStore = require('connect-redis')(session);
const redis = require('redis');
const redisClient = redis.createClient({
host: process.env.REDIS_HOST,
port: process.env.REDIS_PORT,
password: process.env.REDIS_PASSWORD
});
// GOOD: Secure session configuration
app.use(session({
store: new RedisStore({
client: redisClient,
prefix: 'sess:'
}),
secret: process.env.SESSION_SECRET, // Strong random secret from env
name: 'sid', // Don't use default name
resave: false,
saveUninitialized: false,
rolling: true, // Reset expiry on activity
cookie: {
secure: true, // HTTPS only
httpOnly: true, // No JavaScript access
maxAge: 1800000, // 30 minutes
sameSite: 'strict', // CSRF protection
domain: 'hack23.com' // Explicit domain
}
}));
// Session timeout middleware
app.use((req, res, next) => {
if (req.session) {
const now = Date.now();
const lastActivity = req.session.lastActivity || now;
const inactivityTimeout = 30 * 60 * 1000; // 30 minutes
if (now - lastActivity > inactivityTimeout) {
req.session.destroy();
return res.status(401).json({ error: 'Session expired due to inactivity' });
}
req.session.lastActivity = now;
}
next();
});
// Regenerate session ID after authentication
app.post('/login', async (req, res) => {
const { username, password } = req.body;
const user = await authenticateUser(username, password);
if (!user) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Regenerate session to prevent fixation
req.session.regenerate((err) => {
if (err) {
return res.status(500).json({ error: 'Session error' });
}
req.session.userId = user.id;
req.session.role = user.role;
req.session.lastActivity = Date.now();
res.json({ success: true, user: { id: user.id, role: user.role } });
});
});
// Logout
app.post('/logout', (req, res) => {
req.session.destroy((err) => {
if (err) {
return res.status(500).json({ error: 'Logout failed' });
}
res.clearCookie('sid');
res.json({ success: true });
});
});
```
### Example 3: MFA Implementation (TOTP)
```javascript
const speakeasy = require('speakeasy');
const QRCode = require('qrcode');
// Enable MFA for user
app.post('/api/user/mfa/enable', requireAuth, async (req, res) => {
const user = req.user;
// Generate secret
const secret = speakeasy.generateSecret({
name: `Hack23 (${user.email})`,
issuer: 'Hack23'
});
// Generate QR code
const qrCodeUrl = await QRCode.toDataURL(secret.otpauth_url);
// Store secret (encrypted) in database
await db.users.update(user.id, {
mfa_secret: encrypt(secret.base32),
mfa_enabled: false // Not enabled until verified
});
res.json({
secret: secret.base32,
qrCode: qrCodeUrl
});
});
// Verify and activate MFA
app.post('/api/user/mfa/verify', requireAuth, async (req, res) => {
const { token } = req.body;
const user = req.user;
const userData = await db.users.findById(user.id);
const secret = decrypt(userData.mfa_secret);
// Verify token
const verified = speakeasy.totp.verify({
secret: secret,
encoding: 'base32',
token: token,
window: 1 // Allow 1 step before/after for clock skew
});
if (!verified) {
return res.status(400).json({ error: 'Invalid verification code' });
}
// Enable MFA
await db.users.update(user.id, {
mfa_enabled: true
});
res.json({ success: true });
});
// Login with MFA
app.post('/api/login', async (req, res) => {
const { username, password, mfa_token } = req.body;
// Verify username/password
const user = await authenticateUser(username, password);
if (!user) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Check if MFA is enabled
if (user.mfa_enabled) {
if (!mfa_token) {
return res.status(401).json({
error: 'MFA token required',
mfa_required: true
});
}
// Verify MFA token
const secret = decrypt(user.mfa_secret);
const verified = speakeasy.totp.verify({
secret: secret,
encoding: 'base32',
token: mfa_token,
window: 1
});
if (!verified) {
return res.status(401).json({ error: 'Invalid MFA token' });
}
}
// Create session
req.session.regenerate((err) => {
if (err) {
return res.status(500).json({ error: 'Session error' });
}
req.session.userId = user.id;
req.session.role = user.role;
res.json({ success: true, user: { id: user.id, role: user.role } });
});
});
```
### Example 4: API Key Management
```javascript
const crypto = require('crypto');
// Generate API key
function generateApiKey() {
// Generate 32 bytes (256 bits) of random data
const key = crypto.randomBytes(32).toString('base64');
return key;
}
// Hash API key for storage
function hashApiKey(apiKey) {
return crypto.createHash('sha256').update(apiKey).digest('hex');
}
// Create API key with scopes
app.post('/api/keys', requireAuth, async (req, res) => {
const { name, scopes, expiresInDays } = req.body;
// Generate key
const apiKey = generateApiKey();
const hashedKey = hashApiKey(apiKey);
// Calculate expiry
const expiresAt = new Date();
expiresAt.setDate(expiresAt.getDate() + (expiresInDays || 365));
// Store hashed key
await db.apiKeys.create({
user_id: req.user.id,
name: name,
key_hash: hashedKey,
scopes: scopes,
expires_at: expiresAt,
created_at: new Date()
});
// Return plaintext key ONCE (user must save it)
res.json({
apiKey: apiKey,
name: name,
scopes: scopes,
expiresAt: expiresAt,
Voir sur GitHub