1use crate::session::CloudProvider;
2
3struct Mapping {
5 key: &'static str,
6 aws: &'static str,
7 gcp: &'static str,
8 azure: &'static str,
9}
10
11const MAPPINGS: &[Mapping] = &[
12 Mapping {
13 key: "storage:read",
14 aws: "s3:GetObject,s3:ListBucket,s3:GetBucketLocation,s3:ListAllMyBuckets",
15 gcp: "storage.objects.get,storage.objects.list,storage.buckets.get,storage.buckets.list",
16 azure: "Microsoft.Storage/storageAccounts/read,Microsoft.Storage/storageAccounts/blobServices/containers/read",
17 },
18 Mapping {
19 key: "storage:write",
20 aws: "s3:PutObject,s3:DeleteObject",
21 gcp: "storage.objects.create,storage.objects.delete",
22 azure: "Microsoft.Storage/storageAccounts/write,Microsoft.Storage/storageAccounts/blobServices/containers/write",
23 },
24 Mapping {
25 key: "storage:readwrite",
26 aws: "s3:GetObject,s3:PutObject,s3:DeleteObject,s3:ListBucket,s3:GetBucketLocation,s3:ListAllMyBuckets",
27 gcp: "storage.objects.get,storage.objects.list,storage.objects.create,storage.objects.delete,storage.buckets.get,storage.buckets.list",
28 azure: "Microsoft.Storage/storageAccounts/read,Microsoft.Storage/storageAccounts/write,Microsoft.Storage/storageAccounts/listKeys/action,Microsoft.Storage/storageAccounts/blobServices/containers/read,Microsoft.Storage/storageAccounts/blobServices/containers/write",
29 },
30 Mapping {
31 key: "compute:read",
32 aws: "ec2:DescribeInstances,ec2:DescribeSecurityGroups,ec2:DescribeSubnets,ec2:DescribeVpcs,ec2:DescribeImages",
33 gcp: "compute.instances.get,compute.instances.list,compute.zones.list,compute.regions.list",
34 azure: "Microsoft.Compute/virtualMachines/read,Microsoft.Compute/virtualMachines/instanceView/read,Microsoft.Network/networkInterfaces/read,Microsoft.Network/publicIPAddresses/read",
35 },
36 Mapping {
37 key: "functions:read",
38 aws: "lambda:GetFunction,lambda:ListFunctions",
39 gcp: "cloudfunctions.functions.get,cloudfunctions.functions.list",
40 azure: "Microsoft.Web/sites/read,Microsoft.Web/sites/functions/read",
41 },
42 Mapping {
43 key: "functions:deploy",
44 aws: "lambda:UpdateFunctionCode,lambda:UpdateFunctionConfiguration,lambda:GetFunction,lambda:ListFunctions",
45 gcp: "cloudfunctions.functions.get,cloudfunctions.functions.list,cloudfunctions.functions.update,cloudfunctions.functions.create",
46 azure: "Microsoft.Web/sites/read,Microsoft.Web/sites/write,Microsoft.Web/sites/functions/read,Microsoft.Web/sites/functions/write,Microsoft.Web/sites/restart/action",
47 },
48 Mapping {
49 key: "database:read",
50 aws: "dynamodb:GetItem,dynamodb:Query,dynamodb:Scan,dynamodb:BatchGetItem,dynamodb:DescribeTable,dynamodb:ListTables",
51 gcp: "bigquery.jobs.create,bigquery.tables.getData,bigquery.tables.list,bigquery.datasets.get",
52 azure: "Microsoft.DocumentDB/databaseAccounts/read,Microsoft.DocumentDB/databaseAccounts/sqlDatabases/read,Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/read",
53 },
54 Mapping {
55 key: "database:readwrite",
56 aws: "dynamodb:GetItem,dynamodb:PutItem,dynamodb:UpdateItem,dynamodb:DeleteItem,dynamodb:Query,dynamodb:Scan,dynamodb:BatchGetItem,dynamodb:BatchWriteItem,dynamodb:DescribeTable,dynamodb:ListTables",
57 gcp: "bigquery.jobs.create,bigquery.tables.getData,bigquery.tables.list,bigquery.tables.updateData,bigquery.datasets.get",
58 azure: "Microsoft.DocumentDB/databaseAccounts/read,Microsoft.DocumentDB/databaseAccounts/write,Microsoft.DocumentDB/databaseAccounts/sqlDatabases/read,Microsoft.DocumentDB/databaseAccounts/sqlDatabases/write,Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/read,Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/write",
59 },
60 Mapping {
61 key: "secrets:read",
62 aws: "ssm:GetParameter,ssm:GetParameters,ssm:GetParametersByPath,ssm:DescribeParameters",
63 gcp: "secretmanager.versions.access,secretmanager.secrets.get,secretmanager.secrets.list",
64 azure: "Microsoft.KeyVault/vaults/read,Microsoft.KeyVault/vaults/secrets/read",
65 },
66 Mapping {
67 key: "containers:pull",
68 aws: "ecr:GetAuthorizationToken,ecr:BatchCheckLayerAvailability,ecr:GetDownloadUrlForLayer,ecr:BatchGetImage,ecr:DescribeRepositories",
69 gcp: "artifactregistry.repositories.get,artifactregistry.repositories.downloadArtifacts",
70 azure: "Microsoft.ContainerRegistry/registries/read,Microsoft.ContainerRegistry/registries/pull/read",
71 },
72 Mapping {
73 key: "containers:push",
74 aws: "ecr:GetAuthorizationToken,ecr:BatchCheckLayerAvailability,ecr:GetDownloadUrlForLayer,ecr:BatchGetImage,ecr:PutImage,ecr:InitiateLayerUpload,ecr:UploadLayerPart,ecr:CompleteLayerUpload,ecr:DescribeRepositories,ecr:CreateRepository",
75 gcp: "artifactregistry.repositories.get,artifactregistry.repositories.uploadArtifacts,artifactregistry.repositories.downloadArtifacts",
76 azure: "Microsoft.ContainerRegistry/registries/read,Microsoft.ContainerRegistry/registries/pull/read,Microsoft.ContainerRegistry/registries/push/write",
77 },
78 Mapping {
79 key: "logs:read",
80 aws: "logs:GetLogEvents,logs:DescribeLogGroups,logs:DescribeLogStreams,logs:FilterLogEvents",
81 gcp: "logging.logEntries.list,logging.logs.list,logging.logServices.list",
82 azure: "Microsoft.Insights/logs/read,Microsoft.OperationalInsights/workspaces/query/read",
83 },
84 Mapping {
85 key: "queue:readwrite",
86 aws: "sqs:SendMessage,sqs:ReceiveMessage,sqs:DeleteMessage,sqs:GetQueueAttributes,sqs:ListQueues",
87 gcp: "pubsub.topics.publish,pubsub.subscriptions.consume,pubsub.topics.list,pubsub.subscriptions.list",
88 azure: "Microsoft.ServiceBus/namespaces/queues/read,Microsoft.ServiceBus/namespaces/queues/write",
89 },
90];
91
92pub fn expand(allow: &str, provider: &CloudProvider) -> String {
98 let parts: Vec<&str> = allow.split(',').map(|s| s.trim()).collect();
99 let mut expanded = Vec::new();
100
101 for part in parts {
102 if let Some(mapping) = MAPPINGS.iter().find(|m| m.key == part) {
103 let provider_actions = match provider {
104 CloudProvider::Aws => mapping.aws,
105 CloudProvider::Gcp => mapping.gcp,
106 CloudProvider::Azure => mapping.azure,
107 };
108 expanded.push(provider_actions.to_string());
109 } else {
110 expanded.push(part.to_string());
111 }
112 }
113
114 expanded.join(",")
115}
116
117pub fn list_keys() -> Vec<&'static str> {
119 MAPPINGS.iter().map(|m| m.key).collect()
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125
126 #[test]
127 fn test_expand_storage_read_aws() {
128 let result = expand("storage:read", &CloudProvider::Aws);
129 assert!(result.contains("s3:GetObject"));
130 assert!(result.contains("s3:ListBucket"));
131 }
132
133 #[test]
134 fn test_expand_storage_read_gcp() {
135 let result = expand("storage:read", &CloudProvider::Gcp);
136 assert!(result.contains("storage.objects.get"));
137 assert!(result.contains("storage.buckets.list"));
138 }
139
140 #[test]
141 fn test_expand_storage_read_azure() {
142 let result = expand("storage:read", &CloudProvider::Azure);
143 assert!(result.contains("Microsoft.Storage/storageAccounts/read"));
144 }
145
146 #[test]
147 fn test_expand_multiple() {
148 let result = expand("storage:read, compute:read", &CloudProvider::Aws);
149 assert!(result.contains("s3:GetObject"));
150 assert!(result.contains("ec2:DescribeInstances"));
151 }
152
153 #[test]
154 fn test_passthrough_unknown() {
155 let result = expand("s3:PutObject", &CloudProvider::Aws);
156 assert_eq!(result, "s3:PutObject");
157 }
158
159 #[test]
160 fn test_mixed_universal_and_specific() {
161 let result = expand("storage:read,lambda:InvokeFunction", &CloudProvider::Aws);
162 assert!(result.contains("s3:GetObject"));
163 assert!(result.contains("lambda:InvokeFunction"));
164 }
165
166 #[test]
167 fn test_all_keys_expand() {
168 for key in list_keys() {
169 let aws = expand(key, &CloudProvider::Aws);
170 let gcp = expand(key, &CloudProvider::Gcp);
171 let azure = expand(key, &CloudProvider::Azure);
172 assert!(!aws.is_empty(), "AWS expansion empty for {}", key);
173 assert!(!gcp.is_empty(), "GCP expansion empty for {}", key);
174 assert!(!azure.is_empty(), "Azure expansion empty for {}", key);
175 }
176 }
177}