cartography 0.114.0__py3-none-any.whl → 0.116.0__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.

Potentially problematic release.


This version of cartography might be problematic. Click here for more details.

Files changed (58) hide show
  1. cartography/_version.py +2 -2
  2. cartography/cli.py +2 -2
  3. cartography/client/core/tx.py +12 -1
  4. cartography/intel/aws/config.py +7 -3
  5. cartography/intel/aws/ecr.py +9 -9
  6. cartography/intel/aws/ecr_image_layers.py +664 -0
  7. cartography/intel/aws/identitycenter.py +240 -13
  8. cartography/intel/aws/lambda_function.py +69 -2
  9. cartography/intel/aws/organizations.py +3 -1
  10. cartography/intel/aws/permission_relationships.py +3 -1
  11. cartography/intel/aws/redshift.py +9 -4
  12. cartography/intel/aws/resources.py +2 -0
  13. cartography/intel/aws/route53.py +53 -3
  14. cartography/intel/aws/securityhub.py +3 -1
  15. cartography/intel/azure/__init__.py +16 -0
  16. cartography/intel/azure/logic_apps.py +101 -0
  17. cartography/intel/azure/resource_groups.py +82 -0
  18. cartography/intel/create_indexes.py +2 -1
  19. cartography/intel/dns.py +5 -2
  20. cartography/intel/gcp/dns.py +2 -1
  21. cartography/intel/github/repos.py +3 -6
  22. cartography/intel/gsuite/api.py +17 -4
  23. cartography/intel/okta/applications.py +9 -4
  24. cartography/intel/okta/awssaml.py +5 -2
  25. cartography/intel/okta/factors.py +3 -1
  26. cartography/intel/okta/groups.py +5 -2
  27. cartography/intel/okta/organization.py +3 -1
  28. cartography/intel/okta/origins.py +3 -1
  29. cartography/intel/okta/roles.py +5 -2
  30. cartography/intel/okta/users.py +3 -1
  31. cartography/models/aws/ecr/image.py +21 -0
  32. cartography/models/aws/ecr/image_layer.py +107 -0
  33. cartography/models/aws/identitycenter/awspermissionset.py +24 -1
  34. cartography/models/aws/identitycenter/awssogroup.py +70 -0
  35. cartography/models/aws/identitycenter/awsssouser.py +37 -1
  36. cartography/models/aws/lambda_function/lambda_function.py +2 -0
  37. cartography/models/azure/logic_apps.py +56 -0
  38. cartography/models/azure/resource_groups.py +52 -0
  39. cartography/models/entra/user.py +18 -0
  40. cartography/rules/README.md +1 -0
  41. cartography/rules/__init__.py +0 -0
  42. cartography/rules/cli.py +342 -0
  43. cartography/rules/data/__init__.py +0 -0
  44. cartography/rules/data/frameworks/__init__.py +12 -0
  45. cartography/rules/data/frameworks/mitre_attack/__init__.py +14 -0
  46. cartography/rules/data/frameworks/mitre_attack/requirements/__init__.py +0 -0
  47. cartography/rules/data/frameworks/mitre_attack/requirements/t1190_exploit_public_facing_application/__init__.py +135 -0
  48. cartography/rules/formatters.py +46 -0
  49. cartography/rules/runners.py +338 -0
  50. cartography/rules/spec/__init__.py +0 -0
  51. cartography/rules/spec/model.py +88 -0
  52. cartography/rules/spec/result.py +46 -0
  53. {cartography-0.114.0.dist-info → cartography-0.116.0.dist-info}/METADATA +19 -4
  54. {cartography-0.114.0.dist-info → cartography-0.116.0.dist-info}/RECORD +58 -38
  55. {cartography-0.114.0.dist-info → cartography-0.116.0.dist-info}/entry_points.txt +1 -0
  56. {cartography-0.114.0.dist-info → cartography-0.116.0.dist-info}/WHEEL +0 -0
  57. {cartography-0.114.0.dist-info → cartography-0.116.0.dist-info}/licenses/LICENSE +0 -0
  58. {cartography-0.114.0.dist-info → cartography-0.116.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,338 @@
1
+ """
2
+ Framework and Fact execution logic for Cartography rules.
3
+ """
4
+
5
+ import json
6
+ from dataclasses import asdict
7
+
8
+ from neo4j import Driver
9
+ from neo4j import GraphDatabase
10
+
11
+ from cartography.client.core.tx import read_list_of_dicts_tx
12
+ from cartography.rules.data.frameworks import FRAMEWORKS
13
+ from cartography.rules.formatters import _generate_neo4j_browser_url
14
+ from cartography.rules.spec.model import Fact
15
+ from cartography.rules.spec.model import Framework
16
+ from cartography.rules.spec.model import Requirement
17
+ from cartography.rules.spec.result import FactResult
18
+ from cartography.rules.spec.result import FrameworkResult
19
+ from cartography.rules.spec.result import RequirementResult
20
+
21
+
22
+ def _run_fact(
23
+ fact: Fact,
24
+ requirement: Requirement,
25
+ framework: Framework,
26
+ driver: Driver,
27
+ database: str,
28
+ fact_counter: int,
29
+ total_facts: int,
30
+ output_format: str,
31
+ neo4j_uri: str,
32
+ ):
33
+ """Execute a single fact and return the result."""
34
+ if output_format == "text":
35
+ print(f"\n\033[1mFact {fact_counter}/{total_facts}: {fact.name}\033[0m")
36
+ print(f" \033[36m{'Framework:':<12}\033[0m {framework.name}")
37
+ # Display requirement with optional clickable link
38
+ if requirement.requirement_url:
39
+ print(
40
+ f" \033[36m{'Requirement:':<12}\033[0m \033]8;;{requirement.requirement_url}\033\\{requirement.id}\033]8;;\033\\ - {requirement.name}"
41
+ )
42
+ else:
43
+ print(
44
+ f" \033[36m{'Requirement:':<12}\033[0m {requirement.id} - {requirement.name}"
45
+ )
46
+ print(f" \033[36m{'Fact ID:':<12}\033[0m {fact.id}")
47
+ print(f" \033[36m{'Description:':<12}\033[0m {fact.description}")
48
+ print(f" \033[36m{'Provider:':<12}\033[0m {fact.module.value}")
49
+
50
+ # Generate and display clickable Neo4j Browser URL
51
+ browser_url = _generate_neo4j_browser_url(neo4j_uri, fact.cypher_visual_query)
52
+ print(
53
+ f" \033[36m{'Neo4j Query:':<12}\033[0m \033]8;;{browser_url}\033\\Click to run visual query\033]8;;\033\\"
54
+ )
55
+
56
+ with driver.session(database=database) as session:
57
+ findings = session.execute_read(read_list_of_dicts_tx, fact.cypher_query)
58
+ finding_count = len(findings)
59
+
60
+ if output_format == "text":
61
+ if finding_count > 0:
62
+ print(f" \033[36m{'Results:':<12}\033[0m {finding_count} item(s) found")
63
+
64
+ # Show sample findings
65
+ print(" Sample results:")
66
+ for idx, finding in enumerate(findings[:3]): # Show first 3
67
+ # Format finding nicely
68
+ formatted_items = []
69
+ for key, value in finding.items():
70
+ if value is not None:
71
+ # Truncate long values
72
+ str_value = str(value)
73
+ if len(str_value) > 50:
74
+ str_value = str_value[:47] + "..."
75
+ formatted_items.append(f"{key}={str_value}")
76
+
77
+ if formatted_items:
78
+ print(f" {idx + 1}. {', '.join(formatted_items)}")
79
+
80
+ if finding_count > 3:
81
+ print(
82
+ f" ... and {finding_count - 3} more (use --output json to see all)"
83
+ )
84
+ else:
85
+ print(f" \033[36m{'Results:':<12}\033[0m No items found")
86
+
87
+ # Create and return fact result
88
+ return FactResult(
89
+ fact_id=fact.id,
90
+ fact_name=fact.name,
91
+ fact_description=fact.description,
92
+ fact_provider=fact.module.value,
93
+ finding_count=finding_count,
94
+ findings=findings if output_format == "json" else findings[:10],
95
+ )
96
+
97
+
98
+ def _run_single_requirement(
99
+ requirement: Requirement,
100
+ framework: Framework,
101
+ driver: Driver,
102
+ database: str,
103
+ output_format: str,
104
+ neo4j_uri: str,
105
+ fact_counter_start: int,
106
+ total_facts: int,
107
+ fact_filter: str | None = None,
108
+ ) -> tuple[RequirementResult, int]:
109
+ """
110
+ Execute a single requirement and return its result.
111
+
112
+ Returns:
113
+ A tuple of (RequirementResult, facts_executed_count)
114
+ """
115
+ # Filter facts if needed
116
+ facts_to_run = requirement.facts
117
+ if fact_filter:
118
+ facts_to_run = tuple(
119
+ f for f in requirement.facts if f.id.lower() == fact_filter.lower()
120
+ )
121
+
122
+ fact_results = []
123
+ requirement_findings = 0
124
+ fact_counter = fact_counter_start
125
+
126
+ for fact in facts_to_run:
127
+ fact_counter += 1
128
+ fact_result = _run_fact(
129
+ fact,
130
+ requirement,
131
+ framework,
132
+ driver,
133
+ database,
134
+ fact_counter,
135
+ total_facts,
136
+ output_format,
137
+ neo4j_uri,
138
+ )
139
+ fact_results.append(fact_result)
140
+ requirement_findings += fact_result.finding_count
141
+
142
+ # Create requirement result
143
+ requirement_result = RequirementResult(
144
+ requirement_id=requirement.id,
145
+ requirement_name=requirement.name,
146
+ requirement_url=requirement.requirement_url,
147
+ facts=fact_results,
148
+ total_facts=len(fact_results),
149
+ total_findings=requirement_findings,
150
+ )
151
+
152
+ return requirement_result, len(facts_to_run)
153
+
154
+
155
+ def _run_single_framework(
156
+ framework_name: str,
157
+ driver: GraphDatabase.driver,
158
+ database: str,
159
+ output_format: str,
160
+ neo4j_uri: str,
161
+ requirement_filter: str | None = None,
162
+ fact_filter: str | None = None,
163
+ ) -> FrameworkResult:
164
+ """Execute a single framework and return results."""
165
+ framework = FRAMEWORKS[framework_name]
166
+
167
+ # Filter requirements if needed
168
+ requirements_to_run = framework.requirements
169
+ if requirement_filter:
170
+ requirements_to_run = tuple(
171
+ req
172
+ for req in framework.requirements
173
+ if req.id.lower() == requirement_filter.lower()
174
+ )
175
+
176
+ # Count total facts for display (before filtering)
177
+ total_facts_display = sum(len(req.facts) for req in requirements_to_run)
178
+
179
+ if output_format == "text":
180
+ print(f"Executing {framework.name} framework")
181
+ if requirement_filter:
182
+ print(f"Filtered to requirement: {requirement_filter}")
183
+ if fact_filter:
184
+ print(f"Filtered to fact: {fact_filter}")
185
+ print(f"Requirements: {len(requirements_to_run)}")
186
+ print(f"Total facts: {total_facts_display}")
187
+
188
+ # Execute requirements and collect results
189
+ total_findings = 0
190
+ total_facts_executed = 0
191
+ requirement_results = []
192
+ fact_counter = 0
193
+
194
+ for requirement in requirements_to_run:
195
+ requirement_result, facts_executed = _run_single_requirement(
196
+ requirement,
197
+ framework,
198
+ driver,
199
+ database,
200
+ output_format,
201
+ neo4j_uri,
202
+ fact_counter,
203
+ total_facts_display,
204
+ fact_filter,
205
+ )
206
+ requirement_results.append(requirement_result)
207
+ total_findings += requirement_result.total_findings
208
+ total_facts_executed += facts_executed
209
+ fact_counter += facts_executed
210
+
211
+ # Create and return framework result
212
+ return FrameworkResult(
213
+ framework_id=framework.id,
214
+ framework_name=framework.name,
215
+ framework_version=framework.version,
216
+ requirements=requirement_results,
217
+ total_requirements=len(requirements_to_run),
218
+ total_facts=total_facts_executed, # Use actual executed count
219
+ total_findings=total_findings,
220
+ )
221
+
222
+
223
+ def _format_and_output_results(
224
+ all_results: list[FrameworkResult],
225
+ framework_names: list[str],
226
+ output_format: str,
227
+ total_requirements: int,
228
+ total_facts: int,
229
+ total_findings: int,
230
+ ):
231
+ """Format and output the results of framework execution."""
232
+ if output_format == "json":
233
+ combined_output = [asdict(result) for result in all_results]
234
+ print(json.dumps(combined_output, indent=2))
235
+ else:
236
+ # Text summary
237
+ print("\n" + "=" * 60)
238
+ if len(framework_names) == 1:
239
+ print(f"EXECUTION SUMMARY - {FRAMEWORKS[framework_names[0]].name}")
240
+ else:
241
+ print("OVERALL SUMMARY")
242
+ print("=" * 60)
243
+
244
+ if len(framework_names) > 1:
245
+ print(f"Frameworks executed: {len(framework_names)}")
246
+ print(f"Requirements: {total_requirements}")
247
+ print(f"Total facts: {total_facts}")
248
+ print(f"Total results: {total_findings}")
249
+
250
+ if total_findings > 0:
251
+ print(
252
+ f"\n\033[36mFramework execution completed with {total_findings} total results\033[0m"
253
+ )
254
+ else:
255
+ print("\n\033[90mFramework execution completed with no results\033[0m")
256
+
257
+
258
+ def run_frameworks(
259
+ framework_names: list[str],
260
+ uri: str,
261
+ neo4j_user: str,
262
+ neo4j_password: str,
263
+ neo4j_database: str,
264
+ output_format: str = "text",
265
+ requirement_filter: str | None = None,
266
+ fact_filter: str | None = None,
267
+ ):
268
+ """
269
+ Execute the specified frameworks and present results.
270
+
271
+ :param framework_names: The names of the frameworks to execute.
272
+ :param uri: The URI of the Neo4j database. E.g. "bolt://localhost:7687" or "neo4j+s://tenant123.databases.neo4j.io:7687"
273
+ :param neo4j_user: The username for the Neo4j database.
274
+ :param neo4j_password: The password for the Neo4j database.
275
+ :param neo4j_database: The name of the Neo4j database.
276
+ :param output_format: Either "text" or "json". Defaults to "text".
277
+ :param requirement_filter: Optional requirement ID to filter execution (case-insensitive).
278
+ :param fact_filter: Optional fact ID to filter execution (case-insensitive).
279
+ :return: The exit code.
280
+ """
281
+ # Validate all frameworks exist
282
+ for framework_name in framework_names:
283
+ if framework_name not in FRAMEWORKS:
284
+ if output_format == "text":
285
+ print(f"Unknown framework: {framework_name}")
286
+ print(f"Available frameworks: {', '.join(FRAMEWORKS.keys())}")
287
+ return 1
288
+
289
+ # Connect to Neo4j
290
+ if output_format == "text":
291
+ print(f"Connecting to Neo4j at {uri}...")
292
+ driver = GraphDatabase.driver(uri, auth=(neo4j_user, neo4j_password))
293
+
294
+ try:
295
+ driver.verify_connectivity()
296
+
297
+ # Execute frameworks
298
+ all_results = []
299
+ total_requirements = 0
300
+ total_facts = 0
301
+ total_findings = 0
302
+
303
+ for i, framework_name in enumerate(framework_names):
304
+ if output_format == "text" and len(framework_names) > 1:
305
+ if i > 0:
306
+ print("\n" + "=" * 60)
307
+ print(
308
+ f"Executing framework {i + 1}/{len(framework_names)}: {framework_name}"
309
+ )
310
+
311
+ framework_result = _run_single_framework(
312
+ framework_name,
313
+ driver,
314
+ neo4j_database,
315
+ output_format,
316
+ uri,
317
+ requirement_filter,
318
+ fact_filter,
319
+ )
320
+ all_results.append(framework_result)
321
+
322
+ total_requirements += framework_result.total_requirements
323
+ total_facts += framework_result.total_facts
324
+ total_findings += framework_result.total_findings
325
+
326
+ # Output results
327
+ _format_and_output_results(
328
+ all_results,
329
+ framework_names,
330
+ output_format,
331
+ total_requirements,
332
+ total_facts,
333
+ total_findings,
334
+ )
335
+
336
+ return 0
337
+ finally:
338
+ driver.close()
File without changes
@@ -0,0 +1,88 @@
1
+ from dataclasses import dataclass
2
+ from enum import Enum
3
+ from typing import Any
4
+
5
+
6
+ class Module(str, Enum):
7
+ """Services that can be monitored"""
8
+
9
+ AWS = "AWS"
10
+ """Amazon Web Services"""
11
+
12
+ AZURE = "Azure"
13
+ """Microsoft Azure"""
14
+
15
+ GCP = "GCP"
16
+ """Google Cloud Platform"""
17
+
18
+ GITHUB = "GitHub"
19
+ """GitHub source code management"""
20
+
21
+ OKTA = "Okta"
22
+ """Okta identity and access management"""
23
+
24
+ CROSS_CLOUD = "CROSS_CLOUD"
25
+ """Multi-cloud or provider-agnostic rules"""
26
+
27
+
28
+ @dataclass(frozen=True)
29
+ class Fact:
30
+ """A Fact gathers information about the environment using a Cypher query."""
31
+
32
+ id: str
33
+ """A descriptive identifier for the Fact. Should be globally unique within Cartography."""
34
+ name: str
35
+ """A descriptive name for the Fact."""
36
+ description: str
37
+ """More details about the Fact. Information on details that we're querying for."""
38
+ module: Module
39
+ """The Module that the Fact is associated with e.g. AWS, Azure, GCP, etc."""
40
+ # TODO can we lint the queries. full-on integ tests here are overkill though.
41
+ cypher_query: str
42
+ """The Cypher query to gather information about the environment. Returns data field by field e.g. `RETURN node.prop1, node.prop2`."""
43
+ cypher_visual_query: str
44
+ """
45
+ Same as `cypher_query`, returns it in a visual format for the web interface with `.. RETURN *`.
46
+ Often includes additional relationships to help give context.
47
+ """
48
+
49
+
50
+ @dataclass(frozen=True)
51
+ class Requirement:
52
+ """
53
+ A requirement within a security framework with one or more facts.
54
+
55
+ Notes:
56
+ - `attributes` is reserved for metadata such as tags, categories, or references.
57
+ - Do NOT put evaluation logic, thresholds, or org-specific preferences here.
58
+ """
59
+
60
+ id: str
61
+ name: str
62
+ description: str
63
+ facts: tuple[Fact, ...]
64
+ attributes: dict[str, Any] | None = None
65
+ """
66
+ Metadata attributes for the requirement. Example:
67
+ ```json
68
+ {
69
+ "tactic": "initial_access",
70
+ "technique_id": "T1190",
71
+ "services": ["ec2", "s3", "rds", "azure_storage"],
72
+ "providers": ["AWS", "AZURE"],
73
+ }
74
+ ```
75
+ """
76
+ requirement_url: str | None = None
77
+
78
+
79
+ @dataclass(frozen=True)
80
+ class Framework:
81
+ """A security framework containing requirements for comprehensive assessment."""
82
+
83
+ id: str
84
+ name: str
85
+ description: str
86
+ version: str
87
+ requirements: tuple[Requirement, ...]
88
+ source_url: str | None = None
@@ -0,0 +1,46 @@
1
+ # Execution result classes
2
+ from dataclasses import dataclass
3
+ from typing import Any
4
+
5
+
6
+ @dataclass
7
+ class FactResult:
8
+ """
9
+ Results for a single Fact.
10
+ """
11
+
12
+ fact_id: str
13
+ fact_name: str
14
+ fact_description: str
15
+ fact_provider: str
16
+ finding_count: int = 0
17
+ findings: list[dict[str, Any]] | None = None
18
+
19
+
20
+ @dataclass
21
+ class RequirementResult:
22
+ """
23
+ Results for a single requirement, containing all its Facts.
24
+ """
25
+
26
+ requirement_id: str
27
+ requirement_name: str
28
+ requirement_url: str | None
29
+ facts: list[FactResult]
30
+ total_facts: int
31
+ total_findings: int
32
+
33
+
34
+ @dataclass
35
+ class FrameworkResult:
36
+ """
37
+ The formal object output by `--output json` from the `cartography-rules` CLI.
38
+ """
39
+
40
+ framework_id: str
41
+ framework_name: str
42
+ framework_version: str
43
+ requirements: list[RequirementResult]
44
+ total_requirements: int
45
+ total_facts: int
46
+ total_findings: int
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cartography
3
- Version: 0.114.0
3
+ Version: 0.116.0
4
4
  Summary: Explore assets and their relationships across your technical infrastructure.
5
5
  Maintainer: Cartography Contributors
6
6
  License-Expression: Apache-2.0
@@ -23,9 +23,11 @@ Description-Content-Type: text/markdown
23
23
  License-File: LICENSE
24
24
  Requires-Dist: backoff>=2.1.2
25
25
  Requires-Dist: boto3>=1.15.1
26
+ Requires-Dist: aioboto3>=13.0.0
27
+ Requires-Dist: httpx>=0.24.0
26
28
  Requires-Dist: botocore>=1.18.1
27
29
  Requires-Dist: dnspython>=1.15.0
28
- Requires-Dist: neo4j>=5.28.2
30
+ Requires-Dist: neo4j<6.0.0,>=5.28.2
29
31
  Requires-Dist: policyuniverse>=1.1.0.0
30
32
  Requires-Dist: google-api-python-client>=1.7.8
31
33
  Requires-Dist: google-auth>=2.37.0
@@ -43,6 +45,7 @@ Requires-Dist: azure-mgmt-compute>=5.0.0
43
45
  Requires-Dist: azure-mgmt-resource>=10.2.0
44
46
  Requires-Dist: azure-mgmt-cosmosdb>=6.0.0
45
47
  Requires-Dist: azure-mgmt-web>=7.0.0
48
+ Requires-Dist: azure-mgmt-logic>=10.0.0
46
49
  Requires-Dist: msrestazure>=0.6.4
47
50
  Requires-Dist: azure-mgmt-storage>=16.0.0
48
51
  Requires-Dist: azure-mgmt-sql<=1.0.0
@@ -57,6 +60,7 @@ Requires-Dist: duo-client
57
60
  Requires-Dist: cloudflare<5.0.0,>=4.1.0
58
61
  Requires-Dist: scaleway>=2.9.0
59
62
  Requires-Dist: google-cloud-resource-manager>=1.14.2
63
+ Requires-Dist: typer>=0.9.0
60
64
  Dynamic: license-file
61
65
 
62
66
  ![Cartography](docs/root/images/logo-horizontal.png)
@@ -83,7 +87,7 @@ You can learn more about the story behind Cartography in our [presentation at BS
83
87
 
84
88
  ## Supported platforms
85
89
  - [Airbyte](https://cartography-cncf.github.io/cartography/modules/airbyte/index.html) - Organization, Workspace, User, Source, Destination, Connection, Tag, Stream
86
- - [Amazon Web Services](https://cartography-cncf.github.io/cartography/modules/aws/index.html) - ACM, API Gateway, CloudWatch, CodeBuild, Config, Cognito, EC2, ECS, ECR, EFS, Elasticsearch, Elastic Kubernetes Service (EKS), DynamoDB, Glue, GuardDuty, IAM, Inspector, KMS, Lambda, RDS, Redshift, Route53, S3, Secrets Manager(Secret Versions), Security Hub, SNS, SQS, SSM, STS, Tags
90
+ - [Amazon Web Services](https://cartography-cncf.github.io/cartography/modules/aws/index.html) - ACM, API Gateway, CloudWatch, CodeBuild, Config, Cognito, EC2, ECS, ECR (including image layers), EFS, Elasticsearch, Elastic Kubernetes Service (EKS), DynamoDB, Glue, GuardDuty, IAM, Inspector, KMS, Lambda, RDS, Redshift, Route53, S3, Secrets Manager(Secret Versions), Security Hub, SNS, SQS, SSM, STS, Tags
87
91
  - [Anthropic](https://cartography-cncf.github.io/cartography/modules/anthropic/index.html) - Organization, ApiKey, User, Workspace
88
92
  - [BigFix](https://cartography-cncf.github.io/cartography/modules/bigfix/index.html) - Computers
89
93
  - [Cloudflare](https://cartography-cncf.github.io/cartography/modules/cloudflare/index.html) - Account, Role, Member, Zone, DNSRecord
@@ -97,7 +101,7 @@ You can learn more about the story behind Cartography in our [presentation at BS
97
101
  - [Keycloak](https://cartography-cncf.github.io/cartography/modules/keycloak/index.html) - Realms, Users, Groups, Roles, Scopes, Clients, IdentityProviders, Authentication Flows, Authentication Executions, Organizations, Organization Domains
98
102
  - [Kubernetes](https://cartography-cncf.github.io/cartography/modules/kubernetes/index.html) - Cluster, Namespace, Service, Pod, Container, ServiceAccount, Role, RoleBinding, ClusterRole, ClusterRoleBinding, OIDCProvider
99
103
  - [Lastpass](https://cartography-cncf.github.io/cartography/modules/lastpass/index.html) - users
100
- - [Microsoft Azure](https://cartography-cncf.github.io/cartography/modules/azure/index.html) - App Service, CosmosDB, Functions, SQL, Storage, Virtual Machine
104
+ - [Microsoft Azure](https://cartography-cncf.github.io/cartography/modules/azure/index.html) - App Service, CosmosDB, Functions, Logic Apps, Resource Group, SQL, Storage, Virtual Machine
101
105
  - [Microsoft Entra ID](https://cartography-cncf.github.io/cartography/modules/entra/index.html) - Users, Groups, Applications, OUs, App Roles, federation to AWS Identity Center
102
106
  - [NIST CVE](https://cartography-cncf.github.io/cartography/modules/cve/index.html) - Common Vulnerabilities and Exposures (CVE) data from NIST database
103
107
  - [Okta](https://cartography-cncf.github.io/cartography/modules/okta/index.html) - users, groups, organizations, roles, applications, factors, trusted origins, reply URIs, federation to AWS roles, federation to AWS Identity Center
@@ -144,6 +148,17 @@ When you are ready to try it in production, read [here](https://cartography-cncf
144
148
 
145
149
  ## Usage
146
150
 
151
+ ### Running rules
152
+
153
+ You can check your environment against common security frameworks using the `cartography-rules` command.
154
+
155
+ ```bash
156
+ cartography-rules run all
157
+ ```
158
+
159
+ See [the rules docs](https://cartography-cncf.github.io/cartography/usage/rules.html) for more detail.
160
+
161
+
147
162
  ### Querying the database directly
148
163
 
149
164
  ![poweruser.png](docs/root/images/poweruser.png)