cyntrisec 0.1.7__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.
Files changed (65) hide show
  1. cyntrisec/__init__.py +3 -0
  2. cyntrisec/__main__.py +6 -0
  3. cyntrisec/aws/__init__.py +6 -0
  4. cyntrisec/aws/collectors/__init__.py +17 -0
  5. cyntrisec/aws/collectors/ec2.py +30 -0
  6. cyntrisec/aws/collectors/iam.py +116 -0
  7. cyntrisec/aws/collectors/lambda_.py +45 -0
  8. cyntrisec/aws/collectors/network.py +70 -0
  9. cyntrisec/aws/collectors/rds.py +38 -0
  10. cyntrisec/aws/collectors/s3.py +68 -0
  11. cyntrisec/aws/collectors/usage.py +188 -0
  12. cyntrisec/aws/credentials.py +153 -0
  13. cyntrisec/aws/normalizers/__init__.py +17 -0
  14. cyntrisec/aws/normalizers/ec2.py +115 -0
  15. cyntrisec/aws/normalizers/iam.py +182 -0
  16. cyntrisec/aws/normalizers/lambda_.py +83 -0
  17. cyntrisec/aws/normalizers/network.py +225 -0
  18. cyntrisec/aws/normalizers/rds.py +130 -0
  19. cyntrisec/aws/normalizers/s3.py +184 -0
  20. cyntrisec/aws/relationship_builder.py +1359 -0
  21. cyntrisec/aws/scanner.py +303 -0
  22. cyntrisec/cli/__init__.py +5 -0
  23. cyntrisec/cli/analyze.py +747 -0
  24. cyntrisec/cli/ask.py +412 -0
  25. cyntrisec/cli/can.py +307 -0
  26. cyntrisec/cli/comply.py +226 -0
  27. cyntrisec/cli/cuts.py +231 -0
  28. cyntrisec/cli/diff.py +332 -0
  29. cyntrisec/cli/errors.py +105 -0
  30. cyntrisec/cli/explain.py +348 -0
  31. cyntrisec/cli/main.py +114 -0
  32. cyntrisec/cli/manifest.py +893 -0
  33. cyntrisec/cli/output.py +117 -0
  34. cyntrisec/cli/remediate.py +643 -0
  35. cyntrisec/cli/report.py +462 -0
  36. cyntrisec/cli/scan.py +207 -0
  37. cyntrisec/cli/schemas.py +391 -0
  38. cyntrisec/cli/serve.py +164 -0
  39. cyntrisec/cli/setup.py +260 -0
  40. cyntrisec/cli/validate.py +101 -0
  41. cyntrisec/cli/waste.py +323 -0
  42. cyntrisec/core/__init__.py +31 -0
  43. cyntrisec/core/business_config.py +110 -0
  44. cyntrisec/core/business_logic.py +131 -0
  45. cyntrisec/core/compliance.py +437 -0
  46. cyntrisec/core/cost_estimator.py +301 -0
  47. cyntrisec/core/cuts.py +360 -0
  48. cyntrisec/core/diff.py +361 -0
  49. cyntrisec/core/graph.py +202 -0
  50. cyntrisec/core/paths.py +830 -0
  51. cyntrisec/core/schema.py +317 -0
  52. cyntrisec/core/simulator.py +371 -0
  53. cyntrisec/core/waste.py +309 -0
  54. cyntrisec/mcp/__init__.py +5 -0
  55. cyntrisec/mcp/server.py +862 -0
  56. cyntrisec/storage/__init__.py +7 -0
  57. cyntrisec/storage/filesystem.py +344 -0
  58. cyntrisec/storage/memory.py +113 -0
  59. cyntrisec/storage/protocol.py +92 -0
  60. cyntrisec-0.1.7.dist-info/METADATA +672 -0
  61. cyntrisec-0.1.7.dist-info/RECORD +65 -0
  62. cyntrisec-0.1.7.dist-info/WHEEL +4 -0
  63. cyntrisec-0.1.7.dist-info/entry_points.txt +2 -0
  64. cyntrisec-0.1.7.dist-info/licenses/LICENSE +190 -0
  65. cyntrisec-0.1.7.dist-info/licenses/NOTICE +5 -0
@@ -0,0 +1,303 @@
1
+ """
2
+ AWS Scanner - Orchestrate collection, normalization, and analysis.
3
+
4
+ This is the main entry point for AWS scanning.
5
+ No database or queue dependencies.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+ import time
12
+ from collections.abc import Sequence
13
+ from datetime import datetime
14
+
15
+ from cyntrisec.aws.collectors import (
16
+ Ec2Collector,
17
+ IamCollector,
18
+ LambdaCollector,
19
+ NetworkCollector,
20
+ RdsCollector,
21
+ S3Collector,
22
+ )
23
+ from cyntrisec.aws.credentials import CredentialProvider
24
+ from cyntrisec.aws.normalizers import (
25
+ Ec2Normalizer,
26
+ IamNormalizer,
27
+ LambdaNormalizer,
28
+ NetworkNormalizer,
29
+ RdsNormalizer,
30
+ S3Normalizer,
31
+ )
32
+ from cyntrisec.core.graph import GraphBuilder
33
+ from cyntrisec.core.paths import PathFinder
34
+ from cyntrisec.core.schema import (
35
+ Asset,
36
+ Finding,
37
+ Relationship,
38
+ Snapshot,
39
+ SnapshotStatus,
40
+ )
41
+ from cyntrisec.storage.protocol import StorageBackend
42
+
43
+ log = logging.getLogger(__name__)
44
+
45
+
46
+ class AwsScanner:
47
+ """
48
+ Orchestrate AWS scanning.
49
+
50
+ Coordinates:
51
+ 1. Credential acquisition (AssumeRole)
52
+ 2. Resource collection (EC2, IAM, S3, Lambda, RDS, Network)
53
+ 3. Normalization to canonical schema
54
+ 4. Graph construction
55
+ 5. Attack path analysis
56
+ 6. Storage of results
57
+
58
+ Example:
59
+ storage = FileSystemStorage()
60
+ scanner = AwsScanner(storage)
61
+ snapshot = scanner.scan(
62
+ role_arn="arn:aws:iam::123456789012:role/ReadOnly",
63
+ regions=["us-east-1", "eu-west-1"]
64
+ )
65
+ """
66
+
67
+ def __init__(self, storage: StorageBackend):
68
+ self._storage = storage
69
+
70
+ def scan(
71
+ self,
72
+ regions: Sequence[str],
73
+ *,
74
+ role_arn: str | None = None,
75
+ external_id: str | None = None,
76
+ role_session_name: str | None = None,
77
+ profile: str | None = None,
78
+ business_config: str | None = None,
79
+ ) -> Snapshot:
80
+ """
81
+ Run a full AWS scan.
82
+
83
+ Args:
84
+ regions: AWS regions to scan
85
+ role_arn: IAM role to assume (optional - uses default creds if not provided)
86
+ external_id: External ID for role assumption
87
+ profile: AWS CLI profile for base credentials
88
+
89
+ Returns:
90
+ Snapshot with scan results
91
+ """
92
+ datetime.utcnow()
93
+ start_time = time.monotonic()
94
+
95
+ try:
96
+ if role_arn:
97
+ log.info("Assuming role: %s", role_arn)
98
+ creds = CredentialProvider(profile=profile, region=regions[0])
99
+ session = creds.assume_role(
100
+ role_arn,
101
+ external_id=external_id,
102
+ session_name=role_session_name or "cyntrisec-scan",
103
+ )
104
+ else:
105
+ log.info("Using default AWS credentials")
106
+ import boto3
107
+
108
+ session = boto3.Session(profile_name=profile, region_name=regions[0])
109
+
110
+ # Get account ID
111
+ identity = session.client("sts").get_caller_identity()
112
+ account_id = identity["Account"]
113
+ log.info("Connected to AWS account: %s", account_id)
114
+ except Exception as e:
115
+ # Catch-all for credential/connection errors during init
116
+ print(f"DEBUG: Caught exception in scanner: {type(e)} {e}")
117
+ raise ConnectionError(f"Failed to authenticate with AWS: {e}") from e
118
+
119
+ # 2. Initialize storage
120
+ scan_id = self._storage.new_scan(account_id)
121
+ snapshot = Snapshot(
122
+ aws_account_id=account_id,
123
+ regions=list(regions),
124
+ scan_params={
125
+ "role_arn": role_arn,
126
+ "regions": list(regions),
127
+ "business_config": business_config,
128
+ },
129
+ )
130
+ self._storage.save_snapshot(snapshot)
131
+
132
+ # 3. Collect and normalize
133
+ all_assets: list[Asset] = []
134
+ all_relationships: list[Relationship] = []
135
+ all_findings: list[Finding] = []
136
+ collector_errors: list[dict[str, str]] = []
137
+
138
+ # Collect global resources (IAM, S3)
139
+ log.info("Collecting global resources (IAM, S3)...")
140
+ try:
141
+ iam_data = IamCollector(session).collect_all()
142
+ assets, rels, findings = IamNormalizer(snapshot_id=snapshot.id).normalize(iam_data)
143
+ all_assets.extend(assets)
144
+ all_relationships.extend(rels)
145
+ all_findings.extend(findings)
146
+ log.info(" IAM: %d assets, %d relationships", len(assets), len(rels))
147
+ except Exception as e:
148
+ log.error("Error collecting IAM: %s", e)
149
+ collector_errors.append({"service": "iam", "error": str(e)})
150
+
151
+ try:
152
+ s3_data = S3Collector(session).collect_all()
153
+ assets, rels, findings = S3Normalizer(snapshot_id=snapshot.id).normalize(s3_data)
154
+ all_assets.extend(assets)
155
+ all_relationships.extend(rels)
156
+ all_findings.extend(findings)
157
+ log.info(" S3: %d assets, %d findings", len(assets), len(findings))
158
+ except Exception as e:
159
+ log.error("Error collecting S3: %s", e)
160
+ collector_errors.append({"service": "s3", "error": str(e)})
161
+
162
+ # Collect regional resources
163
+ for region in regions:
164
+ log.info("Scanning region: %s", region)
165
+
166
+ # EC2
167
+ try:
168
+ ec2_data = Ec2Collector(session, region).collect_all()
169
+ assets, rels, findings = Ec2Normalizer(
170
+ snapshot_id=snapshot.id,
171
+ region=region,
172
+ account_id=account_id,
173
+ ).normalize(ec2_data)
174
+ all_assets.extend(assets)
175
+ all_relationships.extend(rels)
176
+ all_findings.extend(findings)
177
+ log.info(" EC2: %d assets", len(assets))
178
+ except Exception as e:
179
+ log.error("Error collecting EC2 in %s: %s", region, e)
180
+ collector_errors.append({"service": "ec2", "region": region, "error": str(e)})
181
+
182
+ # Network (VPC, subnets, security groups)
183
+ try:
184
+ network_data = NetworkCollector(session, region).collect_all()
185
+ assets, rels, findings = NetworkNormalizer(
186
+ snapshot_id=snapshot.id,
187
+ region=region,
188
+ account_id=account_id,
189
+ ).normalize(network_data)
190
+ all_assets.extend(assets)
191
+ all_relationships.extend(rels)
192
+ all_findings.extend(findings)
193
+ log.info(" Network: %d assets, %d relationships", len(assets), len(rels))
194
+ except Exception as e:
195
+ log.error("Error collecting Network in %s: %s", region, e)
196
+ collector_errors.append({"service": "network", "region": region, "error": str(e)})
197
+
198
+ # Lambda
199
+ try:
200
+ lambda_data = LambdaCollector(session, region).collect_all()
201
+ assets, rels, findings = LambdaNormalizer(
202
+ snapshot_id=snapshot.id,
203
+ region=region,
204
+ account_id=account_id,
205
+ ).normalize(lambda_data)
206
+ all_assets.extend(assets)
207
+ all_relationships.extend(rels)
208
+ all_findings.extend(findings)
209
+ log.info(" Lambda: %d assets", len(assets))
210
+ except Exception as e:
211
+ log.error("Error collecting Lambda in %s: %s", region, e)
212
+ collector_errors.append({"service": "lambda", "region": region, "error": str(e)})
213
+
214
+ # RDS
215
+ try:
216
+ rds_data = RdsCollector(session, region).collect_all()
217
+ assets, rels, findings = RdsNormalizer(
218
+ snapshot_id=snapshot.id,
219
+ region=region,
220
+ account_id=account_id,
221
+ ).normalize(rds_data)
222
+ all_assets.extend(assets)
223
+ all_relationships.extend(rels)
224
+ all_findings.extend(findings)
225
+ log.info(" RDS: %d assets", len(assets))
226
+ except Exception as e:
227
+ log.error("Error collecting RDS in %s: %s", region, e)
228
+ collector_errors.append({"service": "rds", "region": region, "error": str(e)})
229
+
230
+ # 4. Build cross-service relationships
231
+ log.info("Building cross-service relationships...")
232
+ from cyntrisec.aws.relationship_builder import RelationshipBuilder
233
+
234
+ extra_rels = RelationshipBuilder(snapshot.id).build(all_assets)
235
+ all_relationships.extend(extra_rels)
236
+ log.info(" Added %d cross-service relationships", len(extra_rels))
237
+
238
+ # 5. Save collected data
239
+ self._storage.save_assets(all_assets)
240
+ self._storage.save_relationships(all_relationships)
241
+ self._storage.save_findings(all_findings)
242
+
243
+ log.info(
244
+ "Collection complete: %d assets, %d relationships, %d findings",
245
+ len(all_assets),
246
+ len(all_relationships),
247
+ len(all_findings),
248
+ )
249
+
250
+ # 5. Build graph
251
+ log.info("Building capability graph...")
252
+ graph = GraphBuilder().build(
253
+ assets=all_assets,
254
+ relationships=all_relationships,
255
+ )
256
+ log.info(
257
+ "Graph: %d nodes, %d edges",
258
+ graph.asset_count(),
259
+ graph.relationship_count(),
260
+ )
261
+
262
+ # 6. Find attack paths
263
+ log.info("Analyzing attack paths...")
264
+ entry_count = len(graph.entry_points())
265
+ target_count = len(graph.sensitive_targets())
266
+ log.info(" Entry points: %d, Sensitive targets: %d", entry_count, target_count)
267
+
268
+ if business_config:
269
+ try:
270
+ from cyntrisec.core.business_config import BusinessConfig
271
+ from cyntrisec.core.business_logic import BusinessLogicEngine
272
+
273
+ log.info("Loading business config from: %s", business_config)
274
+ cfg = BusinessConfig.load(business_config)
275
+ logic = BusinessLogicEngine(graph, cfg)
276
+ logic.apply_labels()
277
+ except Exception as e:
278
+ log.error("Failed to apply business config: %s", e)
279
+ if collector_errors is not None:
280
+ collector_errors.append({"service": "business_logic", "error": str(e)})
281
+
282
+ paths = PathFinder().find_paths(graph, snapshot.id)
283
+ self._storage.save_attack_paths(paths)
284
+ log.info(" Attack paths found: %d", len(paths))
285
+
286
+ # 7. Finalize snapshot
287
+ duration = time.monotonic() - start_time
288
+ if collector_errors:
289
+ snapshot.status = SnapshotStatus.completed_with_errors
290
+ snapshot.errors = collector_errors
291
+ else:
292
+ snapshot.status = SnapshotStatus.completed
293
+ snapshot.completed_at = datetime.utcnow()
294
+ snapshot.asset_count = len(all_assets)
295
+ snapshot.relationship_count = len(all_relationships)
296
+ snapshot.finding_count = len(all_findings)
297
+ snapshot.path_count = len(paths)
298
+ self._storage.save_snapshot(snapshot)
299
+
300
+ log.info("Scan complete in %.1fs", duration)
301
+ log.info("Results saved to: ~/.cyntrisec/scans/%s/", scan_id)
302
+
303
+ return snapshot
@@ -0,0 +1,5 @@
1
+ """CLI module."""
2
+
3
+ from cyntrisec.cli.main import app
4
+
5
+ __all__ = ["app"]