fedramp-20x-mcp 0.4.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fedramp_20x_mcp/__init__.py +14 -0
- fedramp_20x_mcp/__main__.py +12 -0
- fedramp_20x_mcp/data_loader.py +673 -0
- fedramp_20x_mcp/prompts/__init__.py +62 -0
- fedramp_20x_mcp/prompts/api_design_guide.txt +432 -0
- fedramp_20x_mcp/prompts/ato_package_checklist.txt +75 -0
- fedramp_20x_mcp/prompts/audit_preparation.txt +592 -0
- fedramp_20x_mcp/prompts/authorization_boundary_review.txt +76 -0
- fedramp_20x_mcp/prompts/azure_ksi_automation.txt +997 -0
- fedramp_20x_mcp/prompts/continuous_monitoring_setup.txt +61 -0
- fedramp_20x_mcp/prompts/documentation_generator.txt +499 -0
- fedramp_20x_mcp/prompts/gap_analysis.txt +25 -0
- fedramp_20x_mcp/prompts/initial_assessment_roadmap.txt +202 -0
- fedramp_20x_mcp/prompts/ksi_implementation_priorities.txt +283 -0
- fedramp_20x_mcp/prompts/migration_from_rev5.txt +440 -0
- fedramp_20x_mcp/prompts/quarterly_review_checklist.txt +231 -0
- fedramp_20x_mcp/prompts/significant_change_assessment.txt +50 -0
- fedramp_20x_mcp/prompts/vendor_evaluation.txt +349 -0
- fedramp_20x_mcp/prompts/vulnerability_remediation_timeline.txt +45 -0
- fedramp_20x_mcp/server.py +270 -0
- fedramp_20x_mcp/templates/__init__.py +75 -0
- fedramp_20x_mcp/templates/bicep/afr.txt +33 -0
- fedramp_20x_mcp/templates/bicep/cna.txt +48 -0
- fedramp_20x_mcp/templates/bicep/generic.txt +47 -0
- fedramp_20x_mcp/templates/bicep/iam.txt +211 -0
- fedramp_20x_mcp/templates/bicep/mla.txt +82 -0
- fedramp_20x_mcp/templates/bicep/rpl.txt +44 -0
- fedramp_20x_mcp/templates/bicep/svc.txt +54 -0
- fedramp_20x_mcp/templates/code/generic_csharp.txt +65 -0
- fedramp_20x_mcp/templates/code/generic_powershell.txt +65 -0
- fedramp_20x_mcp/templates/code/generic_python.txt +63 -0
- fedramp_20x_mcp/templates/code/iam_csharp.txt +150 -0
- fedramp_20x_mcp/templates/code/iam_powershell.txt +162 -0
- fedramp_20x_mcp/templates/code/iam_python.txt +224 -0
- fedramp_20x_mcp/templates/code/mla_python.txt +124 -0
- fedramp_20x_mcp/templates/terraform/afr.txt +29 -0
- fedramp_20x_mcp/templates/terraform/cna.txt +50 -0
- fedramp_20x_mcp/templates/terraform/generic.txt +40 -0
- fedramp_20x_mcp/templates/terraform/iam.txt +219 -0
- fedramp_20x_mcp/templates/terraform/mla.txt +29 -0
- fedramp_20x_mcp/templates/terraform/rpl.txt +32 -0
- fedramp_20x_mcp/templates/terraform/svc.txt +46 -0
- fedramp_20x_mcp/tools/__init__.py +167 -0
- fedramp_20x_mcp/tools/definitions.py +154 -0
- fedramp_20x_mcp/tools/documentation.py +155 -0
- fedramp_20x_mcp/tools/enhancements.py +2256 -0
- fedramp_20x_mcp/tools/evidence.py +701 -0
- fedramp_20x_mcp/tools/export.py +753 -0
- fedramp_20x_mcp/tools/ksi.py +90 -0
- fedramp_20x_mcp/tools/requirements.py +163 -0
- fedramp_20x_mcp-0.4.8.dist-info/METADATA +877 -0
- fedramp_20x_mcp-0.4.8.dist-info/RECORD +55 -0
- fedramp_20x_mcp-0.4.8.dist-info/WHEEL +4 -0
- fedramp_20x_mcp-0.4.8.dist-info/entry_points.txt +2 -0
- fedramp_20x_mcp-0.4.8.dist-info/licenses/LICENSE +27 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
## PowerShell Code
|
|
2
|
+
|
|
3
|
+
```powershell
|
|
4
|
+
#Requires -Modules Az.Accounts, Az.Storage
|
|
5
|
+
|
|
6
|
+
<#
|
|
7
|
+
.SYNOPSIS
|
|
8
|
+
Generic KSI evidence collector for FedRAMP 20x
|
|
9
|
+
#>
|
|
10
|
+
|
|
11
|
+
param(
|
|
12
|
+
[Parameter(Mandatory=$true)]
|
|
13
|
+
[string]$KsiId,
|
|
14
|
+
|
|
15
|
+
[Parameter(Mandatory=$false)]
|
|
16
|
+
[string]$StorageAccountName = $env:EVIDENCE_STORAGE_ACCOUNT
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
function Get-KSIEvidence {
|
|
20
|
+
param([string]$KsiId)
|
|
21
|
+
|
|
22
|
+
$evidence = @{
|
|
23
|
+
collection_date = (Get-Date).ToUniversalTime().ToString("o")
|
|
24
|
+
ksi_id = $KsiId
|
|
25
|
+
compliance_status = "compliant"
|
|
26
|
+
evidence_data = @{
|
|
27
|
+
# Add your evidence data here
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return $evidence
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function Save-Evidence {
|
|
35
|
+
param(
|
|
36
|
+
[hashtable]$Evidence,
|
|
37
|
+
[string]$StorageAccount
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
$context = New-AzStorageContext -StorageAccountName $StorageAccount -UseConnectedAccount
|
|
41
|
+
|
|
42
|
+
$timestamp = Get-Date -Format "yyyy-MM-dd"
|
|
43
|
+
$blobName = "evidence/$timestamp.json"
|
|
44
|
+
|
|
45
|
+
$json = $Evidence | ConvertTo-Json -Depth 10
|
|
46
|
+
$tempFile = [System.IO.Path]::GetTempFileName()
|
|
47
|
+
$json | Out-File -FilePath $tempFile -Encoding UTF8
|
|
48
|
+
|
|
49
|
+
Set-AzStorageBlobContent `
|
|
50
|
+
-File $tempFile `
|
|
51
|
+
-Container "evidence" `
|
|
52
|
+
-Blob $blobName `
|
|
53
|
+
-Context $context `
|
|
54
|
+
-Force
|
|
55
|
+
|
|
56
|
+
Remove-Item $tempFile
|
|
57
|
+
Write-Host "✓ Evidence stored: $blobName"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# Main
|
|
61
|
+
$evidence = Get-KSIEvidence -KsiId $KsiId
|
|
62
|
+
Save-Evidence -Evidence $evidence -StorageAccount $StorageAccountName
|
|
63
|
+
|
|
64
|
+
Write-Host "Status: $($evidence.compliance_status)"
|
|
65
|
+
```
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
## Python Code
|
|
2
|
+
|
|
3
|
+
```python
|
|
4
|
+
#!/usr/bin/env python3
|
|
5
|
+
\"\"\"
|
|
6
|
+
Generic KSI Evidence Collector
|
|
7
|
+
Collects compliance evidence and stores in Azure Blob Storage
|
|
8
|
+
\"\"\"
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import json
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from azure.identity import DefaultAzureCredential
|
|
14
|
+
from azure.storage.blob import BlobServiceClient
|
|
15
|
+
|
|
16
|
+
def collect_evidence():
|
|
17
|
+
\"\"\"Collect evidence for the KSI\"\"\"
|
|
18
|
+
# Customize this function based on your KSI requirements
|
|
19
|
+
evidence = {
|
|
20
|
+
"collection_date": datetime.utcnow().isoformat(),
|
|
21
|
+
"ksi_id": "KSI-XXX-XX",
|
|
22
|
+
"compliance_status": "compliant",
|
|
23
|
+
"evidence_data": {
|
|
24
|
+
# Add your evidence data here
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return evidence
|
|
28
|
+
|
|
29
|
+
def store_evidence(evidence_data: dict):
|
|
30
|
+
\"\"\"Store evidence in Azure Blob Storage\"\"\"
|
|
31
|
+
credential = DefaultAzureCredential()
|
|
32
|
+
storage_account = os.environ["EVIDENCE_STORAGE_ACCOUNT"]
|
|
33
|
+
|
|
34
|
+
blob_service_client = BlobServiceClient(
|
|
35
|
+
account_url=f"https://{storage_account}.blob.core.windows.net",
|
|
36
|
+
credential=credential
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
timestamp = datetime.utcnow().strftime("%Y-%m-%d")
|
|
40
|
+
blob_name = f"evidence/{timestamp}.json"
|
|
41
|
+
|
|
42
|
+
blob_client = blob_service_client.get_blob_client(
|
|
43
|
+
container="evidence",
|
|
44
|
+
blob=blob_name
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
evidence_json = json.dumps(evidence_data, indent=2)
|
|
48
|
+
blob_client.upload_blob(evidence_json, overwrite=True)
|
|
49
|
+
|
|
50
|
+
return blob_name
|
|
51
|
+
|
|
52
|
+
def main():
|
|
53
|
+
print(f"Starting evidence collection: {datetime.utcnow()}")
|
|
54
|
+
|
|
55
|
+
evidence = collect_evidence()
|
|
56
|
+
blob_name = store_evidence(evidence)
|
|
57
|
+
|
|
58
|
+
print(f"✓ Evidence stored: {blob_name}")
|
|
59
|
+
print(f"Status: {evidence['compliance_status']}")
|
|
60
|
+
|
|
61
|
+
if __name__ == "__main__":
|
|
62
|
+
main()
|
|
63
|
+
```
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
## C# Code (.NET 8)
|
|
2
|
+
|
|
3
|
+
```csharp
|
|
4
|
+
using Azure.Identity;
|
|
5
|
+
using Azure.Storage.Blobs;
|
|
6
|
+
using Microsoft.Graph;
|
|
7
|
+
using System.Text.Json;
|
|
8
|
+
|
|
9
|
+
public class IAMEvidenceCollector
|
|
10
|
+
{
|
|
11
|
+
private readonly GraphServiceClient _graphClient;
|
|
12
|
+
private readonly BlobServiceClient _blobClient;
|
|
13
|
+
private readonly string _evidenceContainer = "iam-evidence";
|
|
14
|
+
|
|
15
|
+
public IAMEvidenceCollector()
|
|
16
|
+
{
|
|
17
|
+
var credential = new DefaultAzureCredential();
|
|
18
|
+
_graphClient = new GraphServiceClient(credential);
|
|
19
|
+
|
|
20
|
+
var storageAccount = Environment.GetEnvironmentVariable("EVIDENCE_STORAGE_ACCOUNT");
|
|
21
|
+
_blobClient = new BlobServiceClient(
|
|
22
|
+
new Uri($"https://{storageAccount}.blob.core.windows.net"),
|
|
23
|
+
credential
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public async Task<MFAEvidence> CollectMFAEvidenceAsync()
|
|
28
|
+
{
|
|
29
|
+
var evidence = new MFAEvidence
|
|
30
|
+
{
|
|
31
|
+
CollectionDate = DateTime.UtcNow,
|
|
32
|
+
KsiId = "KSI-IAM-01"
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Get all users
|
|
36
|
+
var users = await _graphClient.Users.GetAsync();
|
|
37
|
+
|
|
38
|
+
foreach (var user in users.Value)
|
|
39
|
+
{
|
|
40
|
+
evidence.TotalUsers++;
|
|
41
|
+
|
|
42
|
+
// Get authentication methods
|
|
43
|
+
var authMethods = await _graphClient.Users[user.Id]
|
|
44
|
+
.Authentication.Methods.GetAsync();
|
|
45
|
+
|
|
46
|
+
bool hasPhishingResistant = authMethods.Value
|
|
47
|
+
.Any(m => m.OdataType.Contains("fido2", StringComparison.OrdinalIgnoreCase));
|
|
48
|
+
|
|
49
|
+
if (hasPhishingResistant)
|
|
50
|
+
{
|
|
51
|
+
evidence.UsersWithMFA++;
|
|
52
|
+
}
|
|
53
|
+
else
|
|
54
|
+
{
|
|
55
|
+
evidence.NonCompliantUsers.Add(new UserInfo
|
|
56
|
+
{
|
|
57
|
+
UserId = user.Id,
|
|
58
|
+
UserPrincipalName = user.UserPrincipalName
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
evidence.CompliancePercentage = evidence.TotalUsers > 0
|
|
64
|
+
? (double)evidence.UsersWithMFA / evidence.TotalUsers * 100
|
|
65
|
+
: 0;
|
|
66
|
+
|
|
67
|
+
return evidence;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public async Task StoreEvidenceAsync<T>(T evidence, string evidenceType)
|
|
71
|
+
{
|
|
72
|
+
var timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd");
|
|
73
|
+
var blobName = $"{evidenceType}/{timestamp}.json";
|
|
74
|
+
|
|
75
|
+
var containerClient = _blobClient.GetBlobContainerClient(_evidenceContainer);
|
|
76
|
+
await containerClient.CreateIfNotExistsAsync();
|
|
77
|
+
|
|
78
|
+
var blobClient = containerClient.GetBlobClient(blobName);
|
|
79
|
+
var json = JsonSerializer.Serialize(evidence, new JsonSerializerOptions
|
|
80
|
+
{
|
|
81
|
+
WriteIndented = true
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
await blobClient.UploadAsync(
|
|
85
|
+
BinaryData.FromString(json),
|
|
86
|
+
overwrite: true
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
Console.WriteLine($"✓ Evidence stored: {blobName}");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public async Task RunCollectionAsync()
|
|
93
|
+
{
|
|
94
|
+
Console.WriteLine($"Starting IAM evidence collection: {DateTime.UtcNow}");
|
|
95
|
+
|
|
96
|
+
var mfaEvidence = await CollectMFAEvidenceAsync();
|
|
97
|
+
await StoreEvidenceAsync(mfaEvidence, "ksi-iam-01-mfa");
|
|
98
|
+
|
|
99
|
+
Console.WriteLine($"MFA Compliance: {mfaEvidence.CompliancePercentage:F1}%");
|
|
100
|
+
Console.WriteLine("✓ IAM evidence collection complete");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
public class MFAEvidence
|
|
105
|
+
{
|
|
106
|
+
public DateTime CollectionDate { get; set; }
|
|
107
|
+
public string KsiId { get; set; }
|
|
108
|
+
public int TotalUsers { get; set; }
|
|
109
|
+
public int UsersWithMFA { get; set; }
|
|
110
|
+
public double CompliancePercentage { get; set; }
|
|
111
|
+
public List<UserInfo> NonCompliantUsers { get; set; } = new();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
public class UserInfo
|
|
115
|
+
{
|
|
116
|
+
public string UserId { get; set; }
|
|
117
|
+
public string UserPrincipalName { get; set; }
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Azure Function entry point
|
|
121
|
+
public static class IAMEvidenceFunction
|
|
122
|
+
{
|
|
123
|
+
[FunctionName("CollectIAMEvidence")]
|
|
124
|
+
public static async Task Run(
|
|
125
|
+
[TimerTrigger("0 0 2 * * *")] TimerInfo timer, // Daily at 2 AM
|
|
126
|
+
ILogger log)
|
|
127
|
+
{
|
|
128
|
+
log.LogInformation("IAM evidence collection triggered");
|
|
129
|
+
|
|
130
|
+
var collector = new IAMEvidenceCollector();
|
|
131
|
+
await collector.RunCollectionAsync();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Project File (csproj)
|
|
137
|
+
```xml
|
|
138
|
+
<Project Sdk="Microsoft.NET.Sdk">
|
|
139
|
+
<PropertyGroup>
|
|
140
|
+
<TargetFramework>net8.0</TargetFramework>
|
|
141
|
+
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
|
|
142
|
+
</PropertyGroup>
|
|
143
|
+
<ItemGroup>
|
|
144
|
+
<PackageReference Include="Azure.Identity" Version="1.10.4" />
|
|
145
|
+
<PackageReference Include="Azure.Storage.Blobs" Version="12.19.1" />
|
|
146
|
+
<PackageReference Include="Microsoft.Graph" Version="5.36.0" />
|
|
147
|
+
<PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.20.0" />
|
|
148
|
+
</ItemGroup>
|
|
149
|
+
</Project>
|
|
150
|
+
```
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
## PowerShell Code
|
|
2
|
+
|
|
3
|
+
```powershell
|
|
4
|
+
#Requires -Modules Az.Accounts, Az.Storage, Microsoft.Graph
|
|
5
|
+
|
|
6
|
+
<#
|
|
7
|
+
.SYNOPSIS
|
|
8
|
+
Collects IAM evidence for FedRAMP 20x KSI-IAM-01
|
|
9
|
+
.DESCRIPTION
|
|
10
|
+
Gathers MFA enrollment and usage data from Microsoft Entra ID
|
|
11
|
+
and stores evidence in Azure Blob Storage
|
|
12
|
+
#>
|
|
13
|
+
|
|
14
|
+
[CmdletBinding()]
|
|
15
|
+
param(
|
|
16
|
+
[Parameter(Mandatory=$false)]
|
|
17
|
+
[string]$StorageAccountName = $env:EVIDENCE_STORAGE_ACCOUNT,
|
|
18
|
+
|
|
19
|
+
[Parameter(Mandatory=$false)]
|
|
20
|
+
[string]$ContainerName = "iam-evidence"
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# Connect to Microsoft Graph
|
|
24
|
+
Connect-MgGraph -Scopes "User.Read.All", "UserAuthenticationMethod.Read.All"
|
|
25
|
+
|
|
26
|
+
function Get-MFAEvidence {
|
|
27
|
+
Write-Host "Collecting MFA evidence..." -ForegroundColor Cyan
|
|
28
|
+
|
|
29
|
+
$evidence = @{
|
|
30
|
+
collection_date = (Get-Date).ToUniversalTime().ToString("o")
|
|
31
|
+
ksi_id = "KSI-IAM-01"
|
|
32
|
+
total_users = 0
|
|
33
|
+
users_with_mfa = 0
|
|
34
|
+
non_compliant_users = @()
|
|
35
|
+
mfa_methods = @{}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
# Get all users
|
|
39
|
+
$users = Get-MgUser -All
|
|
40
|
+
|
|
41
|
+
foreach ($user in $users) {
|
|
42
|
+
$evidence.total_users++
|
|
43
|
+
|
|
44
|
+
# Get authentication methods
|
|
45
|
+
$authMethods = Get-MgUserAuthenticationMethod -UserId $user.Id
|
|
46
|
+
|
|
47
|
+
$hasPhishingResistant = $false
|
|
48
|
+
$userMethods = @()
|
|
49
|
+
|
|
50
|
+
foreach ($method in $authMethods) {
|
|
51
|
+
$methodType = $method.AdditionalProperties.'@odata.type'
|
|
52
|
+
$userMethods += $methodType
|
|
53
|
+
|
|
54
|
+
# Check for FIDO2
|
|
55
|
+
if ($methodType -like "*fido2*") {
|
|
56
|
+
$hasPhishingResistant = $true
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if ($hasPhishingResistant) {
|
|
61
|
+
$evidence.users_with_mfa++
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
$evidence.non_compliant_users += @{
|
|
65
|
+
user_id = $user.Id
|
|
66
|
+
upn = $user.UserPrincipalName
|
|
67
|
+
methods = $userMethods
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
# Count method types
|
|
72
|
+
foreach ($method in $userMethods) {
|
|
73
|
+
if ($evidence.mfa_methods.ContainsKey($method)) {
|
|
74
|
+
$evidence.mfa_methods[$method]++
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
$evidence.mfa_methods[$method] = 1
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
# Calculate compliance
|
|
83
|
+
if ($evidence.total_users -gt 0) {
|
|
84
|
+
$evidence.compliance_percentage = [math]::Round(
|
|
85
|
+
($evidence.users_with_mfa / $evidence.total_users) * 100, 2
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
$evidence.compliance_percentage = 0
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return $evidence
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function Save-Evidence {
|
|
96
|
+
param(
|
|
97
|
+
[Parameter(Mandatory=$true)]
|
|
98
|
+
[hashtable]$Evidence,
|
|
99
|
+
|
|
100
|
+
[Parameter(Mandatory=$true)]
|
|
101
|
+
[string]$EvidenceType
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
Write-Host "Storing evidence..." -ForegroundColor Cyan
|
|
105
|
+
|
|
106
|
+
# Connect to Azure Storage
|
|
107
|
+
$storageContext = New-AzStorageContext -StorageAccountName $StorageAccountName -UseConnectedAccount
|
|
108
|
+
|
|
109
|
+
# Create container if it doesn't exist
|
|
110
|
+
$container = Get-AzStorageContainer -Name $ContainerName -Context $storageContext -ErrorAction SilentlyContinue
|
|
111
|
+
if (-not $container) {
|
|
112
|
+
New-AzStorageContainer -Name $ContainerName -Context $storageContext -Permission Off
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
# Create blob name with timestamp
|
|
116
|
+
$timestamp = Get-Date -Format "yyyy-MM-dd"
|
|
117
|
+
$blobName = "$EvidenceType/$timestamp.json"
|
|
118
|
+
|
|
119
|
+
# Convert to JSON and upload
|
|
120
|
+
$json = $Evidence | ConvertTo-Json -Depth 10
|
|
121
|
+
$tempFile = [System.IO.Path]::GetTempFileName()
|
|
122
|
+
$json | Out-File -FilePath $tempFile -Encoding UTF8
|
|
123
|
+
|
|
124
|
+
Set-AzStorageBlobContent `
|
|
125
|
+
-File $tempFile `
|
|
126
|
+
-Container $ContainerName `
|
|
127
|
+
-Blob $blobName `
|
|
128
|
+
-Context $storageContext `
|
|
129
|
+
-Force
|
|
130
|
+
|
|
131
|
+
Remove-Item $tempFile
|
|
132
|
+
|
|
133
|
+
Write-Host "✓ Evidence stored: $blobName" -ForegroundColor Green
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
# Main execution
|
|
137
|
+
try {
|
|
138
|
+
Write-Host "Starting IAM evidence collection: $(Get-Date)" -ForegroundColor Yellow
|
|
139
|
+
|
|
140
|
+
# Collect MFA evidence
|
|
141
|
+
$mfaEvidence = Get-MFAEvidence
|
|
142
|
+
Save-Evidence -Evidence $mfaEvidence -EvidenceType "ksi-iam-01-mfa"
|
|
143
|
+
|
|
144
|
+
Write-Host "MFA Compliance: $($mfaEvidence.compliance_percentage)%" -ForegroundColor Green
|
|
145
|
+
Write-Host "✓ IAM evidence collection complete" -ForegroundColor Green
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
Write-Error "Evidence collection failed: $_"
|
|
149
|
+
exit 1
|
|
150
|
+
}
|
|
151
|
+
finally {
|
|
152
|
+
Disconnect-MgGraph
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Azure Automation Runbook
|
|
157
|
+
To deploy this as an Azure Automation Runbook:
|
|
158
|
+
|
|
159
|
+
1. Create the runbook in Azure Automation
|
|
160
|
+
2. Add required modules: Az.Accounts, Az.Storage, Microsoft.Graph
|
|
161
|
+
3. Configure Managed Identity with appropriate permissions
|
|
162
|
+
4. Schedule for daily execution
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
## Python Code
|
|
2
|
+
|
|
3
|
+
```python
|
|
4
|
+
#!/usr/bin/env python3
|
|
5
|
+
\"\"\"
|
|
6
|
+
IAM Evidence Collector for FedRAMP 20x
|
|
7
|
+
Collects MFA usage, authentication logs, and access control evidence
|
|
8
|
+
\"\"\"
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import json
|
|
12
|
+
from datetime import datetime, timedelta
|
|
13
|
+
from azure.identity import DefaultAzureCredential
|
|
14
|
+
from azure.mgmt.resource import ResourceManagementClient
|
|
15
|
+
from azure.storage.blob import BlobServiceClient
|
|
16
|
+
from msgraph import GraphServiceClient
|
|
17
|
+
|
|
18
|
+
# Configuration
|
|
19
|
+
SUBSCRIPTION_ID = os.environ["AZURE_SUBSCRIPTION_ID"]
|
|
20
|
+
STORAGE_ACCOUNT = os.environ["EVIDENCE_STORAGE_ACCOUNT"]
|
|
21
|
+
EVIDENCE_CONTAINER = "iam-evidence"
|
|
22
|
+
|
|
23
|
+
async def collect_mfa_evidence():
|
|
24
|
+
\"\"\"Collect MFA enrollment and usage evidence for KSI-IAM-01\"\"\"
|
|
25
|
+
# WAF Security: Use managed identity with explicit configuration
|
|
26
|
+
credential = DefaultAzureCredential(
|
|
27
|
+
exclude_environment_credential=False,
|
|
28
|
+
exclude_managed_identity_credential=False,
|
|
29
|
+
exclude_shared_token_cache_credential=True,
|
|
30
|
+
logging_enable=True # Enable for troubleshooting
|
|
31
|
+
)
|
|
32
|
+
graph_client = GraphServiceClient(credential)
|
|
33
|
+
|
|
34
|
+
# Get all users
|
|
35
|
+
users = await graph_client.users.get()
|
|
36
|
+
|
|
37
|
+
mfa_report = {
|
|
38
|
+
"collection_date": datetime.utcnow().isoformat(),
|
|
39
|
+
"ksi_id": "KSI-IAM-01",
|
|
40
|
+
"total_users": 0,
|
|
41
|
+
"users_with_mfa": 0,
|
|
42
|
+
"mfa_methods": {},
|
|
43
|
+
"non_compliant_users": []
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
for user in users.value:
|
|
47
|
+
mfa_report["total_users"] += 1
|
|
48
|
+
|
|
49
|
+
# Get authentication methods
|
|
50
|
+
auth_methods = await graph_client.users.by_user_id(user.id).authentication.methods.get()
|
|
51
|
+
|
|
52
|
+
has_phishing_resistant = False
|
|
53
|
+
user_methods = []
|
|
54
|
+
|
|
55
|
+
for method in auth_methods.value:
|
|
56
|
+
method_type = method.odata_type
|
|
57
|
+
user_methods.append(method_type)
|
|
58
|
+
|
|
59
|
+
# Check for FIDO2 (phishing-resistant)
|
|
60
|
+
if "fido2" in method_type.lower():
|
|
61
|
+
has_phishing_resistant = True
|
|
62
|
+
|
|
63
|
+
if has_phishing_resistant:
|
|
64
|
+
mfa_report["users_with_mfa"] += 1
|
|
65
|
+
else:
|
|
66
|
+
mfa_report["non_compliant_users"].append({
|
|
67
|
+
"user_id": user.id,
|
|
68
|
+
"upn": user.user_principal_name,
|
|
69
|
+
"methods": user_methods
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
# Count method types
|
|
73
|
+
for method in user_methods:
|
|
74
|
+
mfa_report["mfa_methods"][method] = mfa_report["mfa_methods"].get(method, 0) + 1
|
|
75
|
+
|
|
76
|
+
# Calculate compliance percentage
|
|
77
|
+
mfa_report["compliance_percentage"] = (
|
|
78
|
+
mfa_report["users_with_mfa"] / mfa_report["total_users"] * 100
|
|
79
|
+
if mfa_report["total_users"] > 0 else 0
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
return mfa_report
|
|
83
|
+
|
|
84
|
+
async def collect_conditional_access_evidence():
|
|
85
|
+
\"\"\"Collect Conditional Access policy evidence for KSI-IAM-05\"\"\"
|
|
86
|
+
credential = DefaultAzureCredential(
|
|
87
|
+
exclude_environment_credential=False,
|
|
88
|
+
exclude_managed_identity_credential=False,
|
|
89
|
+
exclude_shared_token_cache_credential=True
|
|
90
|
+
)
|
|
91
|
+
graph_client = GraphServiceClient(credential)
|
|
92
|
+
|
|
93
|
+
# Get all CA policies
|
|
94
|
+
policies = await graph_client.identity.conditional_access.policies.get()
|
|
95
|
+
|
|
96
|
+
ca_report = {
|
|
97
|
+
"collection_date": datetime.utcnow().isoformat(),
|
|
98
|
+
"ksi_id": "KSI-IAM-05",
|
|
99
|
+
"total_policies": len(policies.value),
|
|
100
|
+
"enabled_policies": 0,
|
|
101
|
+
"policies": []
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
for policy in policies.value:
|
|
105
|
+
policy_info = {
|
|
106
|
+
"id": policy.id,
|
|
107
|
+
"display_name": policy.display_name,
|
|
108
|
+
"state": policy.state,
|
|
109
|
+
"conditions": str(policy.conditions),
|
|
110
|
+
"grant_controls": str(policy.grant_controls)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if policy.state == "enabled":
|
|
114
|
+
ca_report["enabled_policies"] += 1
|
|
115
|
+
|
|
116
|
+
ca_report["policies"].append(policy_info)
|
|
117
|
+
|
|
118
|
+
return ca_report
|
|
119
|
+
|
|
120
|
+
async def store_evidence(evidence_data: dict, evidence_type: str):
|
|
121
|
+
\"\"\"Store evidence in Azure Blob Storage with immutability and metadata\"\"\"
|
|
122
|
+
# WAF Security: Use managed identity for authentication
|
|
123
|
+
credential = DefaultAzureCredential(
|
|
124
|
+
exclude_environment_credential=False, # Allow environment variables in dev
|
|
125
|
+
exclude_managed_identity_credential=False, # Use managed identity in Azure
|
|
126
|
+
exclude_shared_token_cache_credential=True, # Don't use cached tokens
|
|
127
|
+
logging_enable=True # Enable SDK logging for troubleshooting
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
blob_service_client = BlobServiceClient(
|
|
131
|
+
account_url=f"https://{STORAGE_ACCOUNT}.blob.core.windows.net",
|
|
132
|
+
credential=credential,
|
|
133
|
+
connection_timeout=30, # WAF Reliability: Set timeout
|
|
134
|
+
read_timeout=30
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# Create blob name with timestamp
|
|
138
|
+
timestamp = datetime.utcnow().strftime("%Y-%m-%d-%H%M%S")
|
|
139
|
+
blob_name = f"{evidence_type}/{timestamp}.json"
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
# Get blob client
|
|
143
|
+
blob_client = blob_service_client.get_blob_client(
|
|
144
|
+
container=EVIDENCE_CONTAINER,
|
|
145
|
+
blob=blob_name
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
evidence_json = json.dumps(evidence_data, indent=2)
|
|
149
|
+
|
|
150
|
+
# WAF Security: Add metadata for evidence chain of custody
|
|
151
|
+
metadata = {
|
|
152
|
+
"ksi_id": evidence_data.get("ksi_id", "unknown"),
|
|
153
|
+
"collection_date": evidence_data.get("collection_date", ""),
|
|
154
|
+
"evidence_type": evidence_type,
|
|
155
|
+
"version": "1.0"
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
# Upload evidence with metadata
|
|
159
|
+
blob_client.upload_blob(
|
|
160
|
+
evidence_json,
|
|
161
|
+
overwrite=False, # Prevent accidental overwrites
|
|
162
|
+
metadata=metadata,
|
|
163
|
+
tags={"compliance": "fedramp", "type": evidence_type}
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# WAF Operational Excellence: Log successful upload
|
|
167
|
+
print(f"✓ Evidence stored: {blob_name}")
|
|
168
|
+
print(f" Size: {len(evidence_json)} bytes")
|
|
169
|
+
return blob_name
|
|
170
|
+
|
|
171
|
+
except Exception as e:
|
|
172
|
+
# WAF Reliability: Proper error handling
|
|
173
|
+
print(f"✗ Failed to store evidence: {str(e)}")
|
|
174
|
+
raise
|
|
175
|
+
|
|
176
|
+
async def main():
|
|
177
|
+
\"\"\"Main evidence collection function\"\"\"
|
|
178
|
+
print(f"Starting IAM evidence collection: {datetime.utcnow()}")
|
|
179
|
+
|
|
180
|
+
# Collect MFA evidence
|
|
181
|
+
mfa_evidence = await collect_mfa_evidence()
|
|
182
|
+
await store_evidence(mfa_evidence, "ksi-iam-01-mfa")
|
|
183
|
+
print(f"MFA Compliance: {mfa_evidence['compliance_percentage']:.1f}%")
|
|
184
|
+
|
|
185
|
+
# Collect Conditional Access evidence
|
|
186
|
+
ca_evidence = await collect_conditional_access_evidence()
|
|
187
|
+
await store_evidence(ca_evidence, "ksi-iam-05-conditional-access")
|
|
188
|
+
print(f"CA Policies: {ca_evidence['enabled_policies']}/{ca_evidence['total_policies']} enabled")
|
|
189
|
+
|
|
190
|
+
print("✓ IAM evidence collection complete")
|
|
191
|
+
|
|
192
|
+
if __name__ == "__main__":
|
|
193
|
+
import asyncio
|
|
194
|
+
asyncio.run(main())
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Requirements (requirements.txt)
|
|
198
|
+
```
|
|
199
|
+
# Azure SDK for Python - Use latest stable versions
|
|
200
|
+
azure-identity>=1.18.0 # Latest stable as of Dec 2025
|
|
201
|
+
azure-mgmt-resource>=23.1.0
|
|
202
|
+
azure-storage-blob>=12.23.0
|
|
203
|
+
msgraph-sdk>=1.10.0
|
|
204
|
+
azure-monitor-query>=1.5.0
|
|
205
|
+
|
|
206
|
+
# Optional: Retry and resilience
|
|
207
|
+
tenacity>=8.2.0 # For custom retry logic
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Environment Variables
|
|
211
|
+
```bash
|
|
212
|
+
# Required for local development and Azure deployment
|
|
213
|
+
export AZURE_SUBSCRIPTION_ID="your-subscription-id"
|
|
214
|
+
export EVIDENCE_STORAGE_ACCOUNT="stfedrampprodevidence"
|
|
215
|
+
export AZURE_TENANT_ID="your-tenant-id"
|
|
216
|
+
|
|
217
|
+
# Optional: For local development only
|
|
218
|
+
export AZURE_CLIENT_ID="your-app-registration-client-id"
|
|
219
|
+
export AZURE_CLIENT_SECRET="your-client-secret"
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Managed Identity Configuration (Azure Deployment)
|
|
223
|
+
When deployed to Azure Function, the managed identity is automatically used.
|
|
224
|
+
No environment variables or secrets needed - WAF Security best practice.
|