1// Zero Trust — Conditional Access + PIM via Microsoft Entra
2// Infrastructure: Bicep + Azure Policy + Entra ID
3
4// ─── Conditional Access Policy (JSON export) ─────
5const conditionalAccessPolicy = {
6 displayName: "Require MFA for all users",
7 state: "enabled",
8 conditions: {
9 users: { includeUsers: ["All"], excludeGroups: ["BreakGlassAccounts"] },
10 clientAppTypes: ["all"],
11 signInRiskLevels: [], // Additional policy for risky sign-ins
12 },
13 grantControls: {
14 operator: "AND",
15 builtInControls: [
16 "mfa", // Require multi-factor authentication
17 "compliantDevice", // Require Intune-compliant device
18 ],
19 },
20 sessionControls: {
21 signInFrequency: { value: 8, type: "hours", isEnabled: true },
22 continuousAccessEvaluation: { mode: "strictEnforcement" },
23 },
24};
25
26// ─── PIM: Just-In-Time Role Activation ───────────
27async function activatePrivilegedRole(
28 userId: string,
29 roleId: string,
30 justification: string
31) {
32 // Activate for max 1 hour with justification
33 const activation = await graph.post('/roleManagement/directory/roleAssignmentScheduleRequests', {
34 action: 'selfActivate',
35 principalId: userId,
36 roleDefinitionId: roleId, // e.g., Owner, Contributor
37 directoryScopeId: '/subscriptions/sub-id',
38 justification, // Required: business reason
39 scheduleInfo: {
40 startDateTime: new Date().toISOString(),
41 expiration: { type: 'afterDuration', duration: 'PT1H' }, // 1 hour max
42 },
43 });
44 return activation;
45}
46
47// ─── Resource Access with RBAC Check ─────────────
48async function accessKeyVaultSecret(secretName: string, identity: string) {
49 const client = new SecretClient(vaultUrl, new DefaultAzureCredential());
50
51 // DefaultAzureCredential uses Managed Identity in Azure
52 // Entra ID verifies: identity has "Key Vault Secrets User" role?
53 // If yes → return secret. If no → 403 Forbidden.
54 const secret = await client.getSecret(secretName);
55 return secret.value;
56}
57
58// ─── Anomaly Detection Signal ─────────────────────
59function evaluateSignInRisk(signal: SignInSignal): 'allow' | 'mfa' | 'block' {
60 const riskScore =
61 (signal.location === 'unusual' ? 30 : 0) +
62 (signal.deviceCompliant === false ? 40 : 0) +
63 (signal.impossibleTravel === true ? 80 : 0);
64
65 if (riskScore >= 80) return 'block';
66 if (riskScore >= 30) return 'mfa';
67 return 'allow';
68}
69