cartography 0.102.0rc2__py3-none-any.whl → 0.103.0rc1__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.
- cartography/__main__.py +1 -2
- cartography/_version.py +2 -2
- cartography/cli.py +302 -253
- cartography/client/core/tx.py +39 -18
- cartography/config.py +4 -0
- cartography/driftdetect/__main__.py +1 -2
- cartography/driftdetect/add_shortcut.py +10 -2
- cartography/driftdetect/cli.py +71 -75
- cartography/driftdetect/detect_deviations.py +7 -3
- cartography/driftdetect/get_states.py +20 -8
- cartography/driftdetect/model.py +5 -5
- cartography/driftdetect/serializers.py +8 -6
- cartography/driftdetect/storage.py +2 -2
- cartography/graph/cleanupbuilder.py +35 -15
- cartography/graph/job.py +46 -17
- cartography/graph/querybuilder.py +165 -80
- cartography/graph/statement.py +35 -26
- cartography/intel/analysis.py +4 -1
- cartography/intel/aws/__init__.py +114 -55
- cartography/intel/aws/apigateway.py +134 -63
- cartography/intel/aws/cloudtrail.py +127 -0
- cartography/intel/aws/config.py +56 -20
- cartography/intel/aws/dynamodb.py +108 -40
- cartography/intel/aws/ec2/__init__.py +2 -2
- cartography/intel/aws/ec2/auto_scaling_groups.py +181 -78
- cartography/intel/aws/ec2/elastic_ip_addresses.py +41 -13
- cartography/intel/aws/ec2/images.py +49 -20
- cartography/intel/aws/ec2/instances.py +234 -136
- cartography/intel/aws/ec2/internet_gateways.py +40 -11
- cartography/intel/aws/ec2/key_pairs.py +44 -20
- cartography/intel/aws/ec2/launch_templates.py +101 -59
- cartography/intel/aws/ec2/load_balancer_v2s.py +104 -39
- cartography/intel/aws/ec2/load_balancers.py +82 -42
- cartography/intel/aws/ec2/network_acls.py +89 -65
- cartography/intel/aws/ec2/network_interfaces.py +146 -87
- cartography/intel/aws/ec2/reserved_instances.py +45 -16
- cartography/intel/aws/ec2/route_tables.py +138 -98
- cartography/intel/aws/ec2/security_groups.py +71 -21
- cartography/intel/aws/ec2/snapshots.py +61 -22
- cartography/intel/aws/ec2/subnets.py +54 -18
- cartography/intel/aws/ec2/tgw.py +100 -34
- cartography/intel/aws/ec2/util.py +1 -1
- cartography/intel/aws/ec2/volumes.py +69 -41
- cartography/intel/aws/ec2/vpc.py +37 -12
- cartography/intel/aws/ec2/vpc_peerings.py +83 -24
- cartography/intel/aws/ecr.py +88 -32
- cartography/intel/aws/ecs.py +83 -47
- cartography/intel/aws/eks.py +55 -29
- cartography/intel/aws/elasticache.py +42 -18
- cartography/intel/aws/elasticsearch.py +57 -20
- cartography/intel/aws/emr.py +61 -23
- cartography/intel/aws/iam.py +401 -145
- cartography/intel/aws/iam_instance_profiles.py +22 -22
- cartography/intel/aws/identitycenter.py +71 -37
- cartography/intel/aws/inspector.py +159 -89
- cartography/intel/aws/kms.py +92 -38
- cartography/intel/aws/lambda_function.py +103 -34
- cartography/intel/aws/organizations.py +30 -10
- cartography/intel/aws/permission_relationships.py +133 -51
- cartography/intel/aws/rds.py +249 -85
- cartography/intel/aws/redshift.py +107 -46
- cartography/intel/aws/resourcegroupstaggingapi.py +120 -66
- cartography/intel/aws/resources.py +53 -46
- cartography/intel/aws/route53.py +108 -61
- cartography/intel/aws/s3.py +168 -83
- cartography/intel/aws/s3accountpublicaccessblock.py +157 -0
- cartography/intel/aws/secretsmanager.py +24 -12
- cartography/intel/aws/securityhub.py +20 -9
- cartography/intel/aws/sns.py +166 -0
- cartography/intel/aws/sqs.py +60 -28
- cartography/intel/aws/ssm.py +70 -30
- cartography/intel/aws/util/arns.py +7 -7
- cartography/intel/aws/util/common.py +31 -4
- cartography/intel/azure/__init__.py +78 -19
- cartography/intel/azure/compute.py +101 -27
- cartography/intel/azure/cosmosdb.py +496 -170
- cartography/intel/azure/sql.py +296 -105
- cartography/intel/azure/storage.py +322 -113
- cartography/intel/azure/subscription.py +39 -23
- cartography/intel/azure/tenant.py +13 -4
- cartography/intel/azure/util/credentials.py +95 -55
- cartography/intel/bigfix/__init__.py +2 -2
- cartography/intel/bigfix/computers.py +93 -65
- cartography/intel/create_indexes.py +3 -2
- cartography/intel/crowdstrike/__init__.py +11 -9
- cartography/intel/crowdstrike/endpoints.py +5 -1
- cartography/intel/crowdstrike/spotlight.py +8 -3
- cartography/intel/cve/__init__.py +46 -13
- cartography/intel/cve/feed.py +48 -12
- cartography/intel/digitalocean/__init__.py +22 -13
- cartography/intel/digitalocean/compute.py +75 -108
- cartography/intel/digitalocean/management.py +44 -80
- cartography/intel/digitalocean/platform.py +48 -43
- cartography/intel/dns.py +36 -10
- cartography/intel/duo/__init__.py +21 -16
- cartography/intel/duo/api_host.py +14 -9
- cartography/intel/duo/endpoints.py +50 -45
- cartography/intel/duo/groups.py +18 -14
- cartography/intel/duo/phones.py +37 -34
- cartography/intel/duo/tokens.py +26 -23
- cartography/intel/duo/users.py +54 -50
- cartography/intel/duo/web_authn_credentials.py +30 -25
- cartography/intel/entra/__init__.py +25 -7
- cartography/intel/entra/ou.py +112 -0
- cartography/intel/entra/users.py +69 -63
- cartography/intel/gcp/__init__.py +185 -49
- cartography/intel/gcp/compute.py +418 -231
- cartography/intel/gcp/crm.py +96 -43
- cartography/intel/gcp/dns.py +60 -19
- cartography/intel/gcp/gke.py +72 -38
- cartography/intel/gcp/iam.py +61 -41
- cartography/intel/gcp/storage.py +84 -55
- cartography/intel/github/__init__.py +13 -11
- cartography/intel/github/repos.py +270 -137
- cartography/intel/github/teams.py +170 -88
- cartography/intel/github/users.py +70 -39
- cartography/intel/github/util.py +36 -34
- cartography/intel/gsuite/__init__.py +47 -26
- cartography/intel/gsuite/api.py +73 -30
- cartography/intel/jamf/__init__.py +19 -1
- cartography/intel/jamf/computers.py +30 -7
- cartography/intel/jamf/util.py +7 -2
- cartography/intel/kandji/__init__.py +6 -3
- cartography/intel/kandji/devices.py +14 -8
- cartography/intel/kubernetes/namespaces.py +7 -4
- cartography/intel/kubernetes/pods.py +7 -4
- cartography/intel/kubernetes/services.py +8 -4
- cartography/intel/lastpass/__init__.py +2 -2
- cartography/intel/lastpass/users.py +23 -12
- cartography/intel/oci/__init__.py +44 -11
- cartography/intel/oci/iam.py +134 -38
- cartography/intel/oci/organizations.py +13 -6
- cartography/intel/oci/utils.py +43 -20
- cartography/intel/okta/__init__.py +66 -15
- cartography/intel/okta/applications.py +42 -20
- cartography/intel/okta/awssaml.py +93 -33
- cartography/intel/okta/factors.py +16 -4
- cartography/intel/okta/groups.py +56 -29
- cartography/intel/okta/organization.py +5 -1
- cartography/intel/okta/origins.py +6 -2
- cartography/intel/okta/roles.py +15 -5
- cartography/intel/okta/users.py +20 -8
- cartography/intel/okta/utils.py +6 -4
- cartography/intel/pagerduty/__init__.py +8 -7
- cartography/intel/pagerduty/escalation_policies.py +18 -6
- cartography/intel/pagerduty/schedules.py +12 -4
- cartography/intel/pagerduty/services.py +11 -4
- cartography/intel/pagerduty/teams.py +8 -3
- cartography/intel/pagerduty/users.py +3 -1
- cartography/intel/pagerduty/vendors.py +3 -1
- cartography/intel/semgrep/__init__.py +24 -6
- cartography/intel/semgrep/dependencies.py +50 -28
- cartography/intel/semgrep/deployment.py +3 -1
- cartography/intel/semgrep/findings.py +42 -18
- cartography/intel/snipeit/__init__.py +17 -3
- cartography/intel/snipeit/asset.py +12 -6
- cartography/intel/snipeit/user.py +8 -5
- cartography/intel/snipeit/util.py +9 -4
- cartography/models/aws/apigateway.py +21 -17
- cartography/models/aws/apigatewaycertificate.py +28 -22
- cartography/models/aws/apigatewayresource.py +28 -20
- cartography/models/aws/apigatewaystage.py +33 -25
- cartography/models/aws/cloudtrail/__init__.py +0 -0
- cartography/models/aws/cloudtrail/trail.py +61 -0
- cartography/models/aws/dynamodb/gsi.py +30 -22
- cartography/models/aws/dynamodb/tables.py +25 -17
- cartography/models/aws/ec2/auto_scaling_groups.py +102 -82
- cartography/models/aws/ec2/images.py +36 -34
- cartography/models/aws/ec2/instances.py +51 -45
- cartography/models/aws/ec2/keypair.py +21 -16
- cartography/models/aws/ec2/keypair_instance.py +28 -21
- cartography/models/aws/ec2/launch_configurations.py +30 -26
- cartography/models/aws/ec2/launch_template_versions.py +48 -38
- cartography/models/aws/ec2/launch_templates.py +21 -17
- cartography/models/aws/ec2/load_balancer_listeners.py +27 -23
- cartography/models/aws/ec2/load_balancers.py +47 -37
- cartography/models/aws/ec2/network_acl_rules.py +38 -30
- cartography/models/aws/ec2/network_acls.py +38 -29
- cartography/models/aws/ec2/networkinterface_instance.py +52 -39
- cartography/models/aws/ec2/networkinterfaces.py +53 -37
- cartography/models/aws/ec2/privateip_networkinterface.py +32 -22
- cartography/models/aws/ec2/reservations.py +18 -14
- cartography/models/aws/ec2/route_table_associations.py +44 -34
- cartography/models/aws/ec2/route_tables.py +50 -43
- cartography/models/aws/ec2/routes.py +45 -37
- cartography/models/aws/ec2/securitygroup_instance.py +29 -20
- cartography/models/aws/ec2/securitygroup_networkinterface.py +24 -15
- cartography/models/aws/ec2/subnet_instance.py +24 -19
- cartography/models/aws/ec2/subnet_networkinterface.py +40 -31
- cartography/models/aws/ec2/volumes.py +47 -40
- cartography/models/aws/eks/clusters.py +23 -21
- cartography/models/aws/emr.py +32 -30
- cartography/models/aws/iam/instanceprofile.py +33 -24
- cartography/models/aws/identitycenter/awsidentitycenter.py +18 -14
- cartography/models/aws/identitycenter/awspermissionset.py +37 -29
- cartography/models/aws/identitycenter/awsssouser.py +23 -21
- cartography/models/aws/inspector/findings.py +77 -65
- cartography/models/aws/inspector/packages.py +35 -29
- cartography/models/aws/s3/__init__.py +0 -0
- cartography/models/aws/s3/account_public_access_block.py +51 -0
- cartography/models/aws/sns/__init__.py +0 -0
- cartography/models/aws/sns/topic.py +50 -0
- cartography/models/aws/ssm/instance_information.py +51 -39
- cartography/models/aws/ssm/instance_patch.py +32 -26
- cartography/models/bigfix/bigfix_computer.py +42 -38
- cartography/models/bigfix/bigfix_root.py +3 -3
- cartography/models/core/common.py +12 -10
- cartography/models/core/nodes.py +5 -2
- cartography/models/core/relationships.py +14 -6
- cartography/models/crowdstrike/hosts.py +37 -35
- cartography/models/cve/cve.py +34 -32
- cartography/models/cve/cve_feed.py +6 -6
- cartography/models/digitalocean/__init__.py +0 -0
- cartography/models/digitalocean/account.py +21 -0
- cartography/models/digitalocean/droplet.py +56 -0
- cartography/models/digitalocean/project.py +48 -0
- cartography/models/duo/api_host.py +3 -3
- cartography/models/duo/endpoint.py +43 -41
- cartography/models/duo/group.py +14 -14
- cartography/models/duo/phone.py +27 -27
- cartography/models/duo/token.py +16 -16
- cartography/models/duo/user.py +46 -44
- cartography/models/duo/web_authn_credential.py +27 -19
- cartography/models/entra/ou.py +48 -0
- cartography/models/entra/tenant.py +24 -18
- cartography/models/entra/user.py +64 -48
- cartography/models/gcp/iam.py +23 -23
- cartography/models/github/orgs.py +5 -4
- cartography/models/github/teams.py +37 -31
- cartography/models/github/users.py +34 -23
- cartography/models/kandji/device.py +22 -16
- cartography/models/kandji/tenant.py +6 -4
- cartography/models/lastpass/tenant.py +3 -3
- cartography/models/lastpass/user.py +32 -28
- cartography/models/semgrep/dependencies.py +36 -24
- cartography/models/semgrep/deployment.py +5 -5
- cartography/models/semgrep/findings.py +58 -42
- cartography/models/semgrep/locations.py +27 -21
- cartography/models/snipeit/asset.py +30 -21
- cartography/models/snipeit/tenant.py +6 -4
- cartography/models/snipeit/user.py +19 -12
- cartography/stats.py +3 -3
- cartography/sync.py +107 -31
- cartography/util.py +84 -62
- {cartography-0.102.0rc2.dist-info → cartography-0.103.0rc1.dist-info}/METADATA +3 -14
- cartography-0.103.0rc1.dist-info/RECORD +396 -0
- {cartography-0.102.0rc2.dist-info → cartography-0.103.0rc1.dist-info}/WHEEL +1 -1
- cartography-0.102.0rc2.dist-info/RECORD +0 -381
- {cartography-0.102.0rc2.dist-info → cartography-0.103.0rc1.dist-info}/entry_points.txt +0 -0
- {cartography-0.102.0rc2.dist-info → cartography-0.103.0rc1.dist-info}/licenses/LICENSE +0 -0
- {cartography-0.102.0rc2.dist-info → cartography-0.103.0rc1.dist-info}/top_level.txt +0 -0
cartography/client/core/tx.py
CHANGED
|
@@ -13,7 +13,11 @@ from cartography.models.core.nodes import CartographyNodeSchema
|
|
|
13
13
|
from cartography.util import batch
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
def read_list_of_values_tx(
|
|
16
|
+
def read_list_of_values_tx(
|
|
17
|
+
tx: neo4j.Transaction,
|
|
18
|
+
query: str,
|
|
19
|
+
**kwargs,
|
|
20
|
+
) -> List[Union[str, int]]:
|
|
17
21
|
"""
|
|
18
22
|
Runs the given Neo4j query in the given transaction object and returns a list of either str or int. This is intended
|
|
19
23
|
to be run only with queries that return a list of a single field.
|
|
@@ -39,7 +43,11 @@ def read_list_of_values_tx(tx: neo4j.Transaction, query: str, **kwargs) -> List[
|
|
|
39
43
|
return values
|
|
40
44
|
|
|
41
45
|
|
|
42
|
-
def read_single_value_tx(
|
|
46
|
+
def read_single_value_tx(
|
|
47
|
+
tx: neo4j.Transaction,
|
|
48
|
+
query: str,
|
|
49
|
+
**kwargs,
|
|
50
|
+
) -> Optional[Union[str, int]]:
|
|
43
51
|
"""
|
|
44
52
|
Runs the given Neo4j query in the given transaction object and returns a str, int, or None. This is intended to be
|
|
45
53
|
run only with queries that return a single str, int, or None value.
|
|
@@ -70,7 +78,11 @@ def read_single_value_tx(tx: neo4j.Transaction, query: str, **kwargs) -> Optiona
|
|
|
70
78
|
return value
|
|
71
79
|
|
|
72
80
|
|
|
73
|
-
def read_list_of_dicts_tx(
|
|
81
|
+
def read_list_of_dicts_tx(
|
|
82
|
+
tx: neo4j.Transaction,
|
|
83
|
+
query: str,
|
|
84
|
+
**kwargs,
|
|
85
|
+
) -> List[Dict[str, Any]]:
|
|
74
86
|
"""
|
|
75
87
|
Runs the given Neo4j query in the given transaction object and returns the results as a list of dicts.
|
|
76
88
|
|
|
@@ -92,7 +104,11 @@ def read_list_of_dicts_tx(tx: neo4j.Transaction, query: str, **kwargs) -> List[D
|
|
|
92
104
|
return values
|
|
93
105
|
|
|
94
106
|
|
|
95
|
-
def read_list_of_tuples_tx(
|
|
107
|
+
def read_list_of_tuples_tx(
|
|
108
|
+
tx: neo4j.Transaction,
|
|
109
|
+
query: str,
|
|
110
|
+
**kwargs,
|
|
111
|
+
) -> List[Tuple[Any, ...]]:
|
|
96
112
|
"""
|
|
97
113
|
Runs the given Neo4j query in the given transaction object and returns the results as a list of tuples.
|
|
98
114
|
|
|
@@ -154,9 +170,9 @@ def read_single_dict_tx(tx: neo4j.Transaction, query: str, **kwargs) -> Any:
|
|
|
154
170
|
|
|
155
171
|
|
|
156
172
|
def write_list_of_dicts_tx(
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
173
|
+
tx: neo4j.Transaction,
|
|
174
|
+
query: str,
|
|
175
|
+
**kwargs,
|
|
160
176
|
) -> None:
|
|
161
177
|
"""
|
|
162
178
|
Writes a list of dicts to Neo4j.
|
|
@@ -192,10 +208,10 @@ def write_list_of_dicts_tx(
|
|
|
192
208
|
|
|
193
209
|
|
|
194
210
|
def load_graph_data(
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
211
|
+
neo4j_session: neo4j.Session,
|
|
212
|
+
query: str,
|
|
213
|
+
dict_list: List[Dict[str, Any]],
|
|
214
|
+
**kwargs,
|
|
199
215
|
) -> None:
|
|
200
216
|
"""
|
|
201
217
|
Writes data to the graph.
|
|
@@ -215,7 +231,10 @@ def load_graph_data(
|
|
|
215
231
|
)
|
|
216
232
|
|
|
217
233
|
|
|
218
|
-
def ensure_indexes(
|
|
234
|
+
def ensure_indexes(
|
|
235
|
+
neo4j_session: neo4j.Session,
|
|
236
|
+
node_schema: CartographyNodeSchema,
|
|
237
|
+
) -> None:
|
|
219
238
|
"""
|
|
220
239
|
Creates indexes if they don't exist for the given CartographyNodeSchema object, as well as for all of the
|
|
221
240
|
relationships defined on its `other_relationships` and `sub_resource_relationship` fields. This operation is
|
|
@@ -229,16 +248,18 @@ def ensure_indexes(neo4j_session: neo4j.Session, node_schema: CartographyNodeSch
|
|
|
229
248
|
queries = build_create_index_queries(node_schema)
|
|
230
249
|
|
|
231
250
|
for query in queries:
|
|
232
|
-
if not query.startswith(
|
|
233
|
-
raise ValueError(
|
|
251
|
+
if not query.startswith("CREATE INDEX IF NOT EXISTS"):
|
|
252
|
+
raise ValueError(
|
|
253
|
+
'Query provided to `ensure_indexes()` does not start with "CREATE INDEX IF NOT EXISTS".',
|
|
254
|
+
)
|
|
234
255
|
neo4j_session.run(query)
|
|
235
256
|
|
|
236
257
|
|
|
237
258
|
def load(
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
259
|
+
neo4j_session: neo4j.Session,
|
|
260
|
+
node_schema: CartographyNodeSchema,
|
|
261
|
+
dict_list: List[Dict[str, Any]],
|
|
262
|
+
**kwargs,
|
|
242
263
|
) -> None:
|
|
243
264
|
"""
|
|
244
265
|
Main entrypoint for intel modules to write data to the graph. Ensures that indexes exist for the datatypes loaded
|
cartography/config.py
CHANGED
|
@@ -26,6 +26,8 @@ class Config:
|
|
|
26
26
|
:type aws_sync_all_profiles: bool
|
|
27
27
|
:param aws_sync_all_profiles: If True, AWS sync will run for all non-default profiles in the AWS_CONFIG_FILE. If
|
|
28
28
|
False (default), AWS sync will run using the default credentials only. Optional.
|
|
29
|
+
:type aws_regions: str
|
|
30
|
+
:param aws_regions: Comma-separated list of AWS regions to sync. Optional.
|
|
29
31
|
:type aws_best_effort_mode: bool
|
|
30
32
|
:param aws_best_effort_mode: If True, AWS sync will not raise any exceptions, just log. If False (default),
|
|
31
33
|
exceptions will be raised.
|
|
@@ -133,6 +135,7 @@ class Config:
|
|
|
133
135
|
selected_modules=None,
|
|
134
136
|
update_tag=None,
|
|
135
137
|
aws_sync_all_profiles=False,
|
|
138
|
+
aws_regions=None,
|
|
136
139
|
aws_best_effort_mode=False,
|
|
137
140
|
azure_sync_all_subscriptions=False,
|
|
138
141
|
azure_sp_auth=None,
|
|
@@ -194,6 +197,7 @@ class Config:
|
|
|
194
197
|
self.selected_modules = selected_modules
|
|
195
198
|
self.update_tag = update_tag
|
|
196
199
|
self.aws_sync_all_profiles = aws_sync_all_profiles
|
|
200
|
+
self.aws_regions = aws_regions
|
|
197
201
|
self.aws_best_effort_mode = aws_best_effort_mode
|
|
198
202
|
self.azure_sync_all_subscriptions = azure_sync_all_subscriptions
|
|
199
203
|
self.azure_sp_auth = azure_sp_auth
|
|
@@ -22,7 +22,13 @@ def run_add_shortcut(config):
|
|
|
22
22
|
logger.error("Invalid Drift Detection Directory")
|
|
23
23
|
return
|
|
24
24
|
try:
|
|
25
|
-
add_shortcut(
|
|
25
|
+
add_shortcut(
|
|
26
|
+
FileSystem,
|
|
27
|
+
ShortcutSchema(),
|
|
28
|
+
config.query_directory,
|
|
29
|
+
config.shortcut,
|
|
30
|
+
config.filename,
|
|
31
|
+
)
|
|
26
32
|
except ValidationError as err:
|
|
27
33
|
msg = "Could not load shortcut file from json file {} in query directory {}.".format(
|
|
28
34
|
err.messages,
|
|
@@ -48,7 +54,9 @@ def add_shortcut(storage, shortcut_serializer, query_directory, alias, filename)
|
|
|
48
54
|
:return:
|
|
49
55
|
"""
|
|
50
56
|
if storage.has_file(os.path.join(query_directory, alias)):
|
|
51
|
-
logger.error(
|
|
57
|
+
logger.error(
|
|
58
|
+
f"Shortcut {alias} is the name of another File in directory {query_directory}.",
|
|
59
|
+
)
|
|
52
60
|
return
|
|
53
61
|
shortcut_path = os.path.join(query_directory, "shortcut.json")
|
|
54
62
|
shortcut_data = storage.load(shortcut_path)
|
cartography/driftdetect/cli.py
CHANGED
|
@@ -8,7 +8,6 @@ from cartography.driftdetect.add_shortcut import run_add_shortcut
|
|
|
8
8
|
from cartography.driftdetect.detect_deviations import run_drift_detection
|
|
9
9
|
from cartography.driftdetect.get_states import run_get_states
|
|
10
10
|
|
|
11
|
-
|
|
12
11
|
logger = logging.getLogger(__name__)
|
|
13
12
|
|
|
14
13
|
|
|
@@ -26,138 +25,130 @@ class CLI:
|
|
|
26
25
|
parser = argparse.ArgumentParser(
|
|
27
26
|
prog=self.prog,
|
|
28
27
|
description=(
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
"drift-detection takes database queries along with their expected states in the cartography-generated "
|
|
29
|
+
"graph database and reports the deviations."
|
|
31
30
|
),
|
|
32
|
-
epilog=
|
|
33
|
-
|
|
31
|
+
epilog="For more documentation please visit: "
|
|
32
|
+
"https://cartography-cncf.github.io/cartography/usage/drift-detect.html",
|
|
34
33
|
)
|
|
35
34
|
parser.add_argument(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
action=
|
|
39
|
-
help=
|
|
35
|
+
"-v",
|
|
36
|
+
"--verbose",
|
|
37
|
+
action="store_true",
|
|
38
|
+
help="Enable verbose logging for drift-detection.",
|
|
40
39
|
)
|
|
41
40
|
parser.add_argument(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
action=
|
|
45
|
-
help=
|
|
41
|
+
"-q",
|
|
42
|
+
"--quiet",
|
|
43
|
+
action="store_true",
|
|
44
|
+
help="Restrict drift-detection logging to warnings and errors only.",
|
|
46
45
|
)
|
|
47
46
|
subparsers = parser.add_subparsers(
|
|
48
|
-
dest=
|
|
49
|
-
help=
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
47
|
+
dest="command",
|
|
48
|
+
help="To get the current drift state, use the command `cartography-detectdrift get-state --neo4j_uri <your "
|
|
49
|
+
"neo4j_uri> --drift-detection-directory <your drift detection directory>` \n"
|
|
50
|
+
"To get drift between two state files, use the command `cartography-detectdrift get-drift"
|
|
51
|
+
"--query-directory <path to query directory> --start-state <beginning drift state file> --end-state "
|
|
52
|
+
"<final drift state file>"
|
|
53
|
+
"To add a shortcut between two state files, use the command `cartography-detectdrift add-shortcut"
|
|
54
|
+
"--query-directory <path to query directory> --shortcut <shortcut name> --file <driftstate filename>",
|
|
56
55
|
)
|
|
57
56
|
parser_get_state = subparsers.add_parser(
|
|
58
|
-
name=
|
|
59
|
-
help=
|
|
57
|
+
name="get-state",
|
|
58
|
+
help="generates new drift state for each query with the current status of the neo4j database",
|
|
60
59
|
)
|
|
61
60
|
parser_get_state.add_argument(
|
|
62
|
-
|
|
61
|
+
"--neo4j-uri",
|
|
63
62
|
type=str,
|
|
64
|
-
default=
|
|
63
|
+
default="bolt://localhost:7687",
|
|
65
64
|
help=(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
"A valid Neo4j URI to sync against. See "
|
|
66
|
+
"https://neo4j.com/docs/api/python-driver/current/driver.html#uri for complete documentation on the "
|
|
67
|
+
"structure of a Neo4j URI."
|
|
69
68
|
),
|
|
70
69
|
)
|
|
71
70
|
parser_get_state.add_argument(
|
|
72
|
-
|
|
71
|
+
"--neo4j-user",
|
|
73
72
|
type=str,
|
|
74
73
|
default=None,
|
|
75
|
-
help=
|
|
74
|
+
help="A username with which to authenticate to Neo4j.",
|
|
76
75
|
)
|
|
77
76
|
parser_get_state.add_argument(
|
|
78
|
-
|
|
77
|
+
"--neo4j-password-env-var",
|
|
79
78
|
type=str,
|
|
80
79
|
default=None,
|
|
81
|
-
help=
|
|
80
|
+
help="The name of an environment variable containing a password with which to authenticate to Neo4j.",
|
|
82
81
|
)
|
|
83
82
|
parser_get_state.add_argument(
|
|
84
|
-
|
|
85
|
-
action=
|
|
83
|
+
"--neo4j-password-prompt",
|
|
84
|
+
action="store_true",
|
|
86
85
|
help=(
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
"Present an interactive prompt for a password with which to authenticate to Neo4j. This parameter "
|
|
87
|
+
"supersedes other methods of supplying a Neo4j password."
|
|
89
88
|
),
|
|
90
89
|
)
|
|
91
90
|
parser_get_state.add_argument(
|
|
92
|
-
|
|
91
|
+
"--drift-detection-directory",
|
|
93
92
|
type=str,
|
|
94
93
|
default=None,
|
|
95
94
|
help=(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
"A path to a directory containing drift-states to build. Drift-detection will discover all JSON"
|
|
96
|
+
"files in the given directory (and its subdirectories) and construct detectors from"
|
|
97
|
+
"them. Drift-detection does not guarantee the order in which the detector jobs are executed."
|
|
99
98
|
),
|
|
100
99
|
)
|
|
101
100
|
parser_get_drift = subparsers.add_parser(
|
|
102
|
-
name=
|
|
101
|
+
name="get-drift",
|
|
103
102
|
help=(
|
|
104
|
-
|
|
103
|
+
"gets drift between two drift states. Must be between the same detection directory"
|
|
105
104
|
),
|
|
106
105
|
)
|
|
107
106
|
parser_get_drift.add_argument(
|
|
108
|
-
|
|
107
|
+
"--query-directory",
|
|
109
108
|
type=str,
|
|
110
109
|
default=None,
|
|
111
110
|
help=(
|
|
112
|
-
|
|
113
|
-
|
|
111
|
+
"A path to a directory containing drift-states for a specific query. Drift-detection will read in the"
|
|
112
|
+
"report_info file and specified drift-states from the directory to report the differences between files"
|
|
114
113
|
),
|
|
115
114
|
)
|
|
116
115
|
parser_get_drift.add_argument(
|
|
117
|
-
|
|
116
|
+
"--start-state",
|
|
118
117
|
type=str,
|
|
119
118
|
default=None,
|
|
120
119
|
help=(
|
|
121
|
-
|
|
120
|
+
"The filename of the earlier state chronologically to be compared to."
|
|
122
121
|
),
|
|
123
122
|
)
|
|
124
123
|
parser_get_drift.add_argument(
|
|
125
|
-
|
|
124
|
+
"--end-state",
|
|
126
125
|
type=str,
|
|
127
126
|
default=None,
|
|
128
|
-
help=(
|
|
129
|
-
'The filename of the later state chronologically to be compared to.'
|
|
130
|
-
),
|
|
127
|
+
help=("The filename of the later state chronologically to be compared to."),
|
|
131
128
|
)
|
|
132
129
|
parser_add_shortcut = subparsers.add_parser(
|
|
133
|
-
name=
|
|
134
|
-
help=(
|
|
135
|
-
'Adds a shortcut to a specific file in a query directory.'
|
|
136
|
-
),
|
|
130
|
+
name="add-shortcut",
|
|
131
|
+
help=("Adds a shortcut to a specific file in a query directory."),
|
|
137
132
|
)
|
|
138
133
|
parser_add_shortcut.add_argument(
|
|
139
|
-
|
|
134
|
+
"--query-directory",
|
|
140
135
|
type=str,
|
|
141
136
|
default=None,
|
|
142
137
|
help=(
|
|
143
|
-
|
|
138
|
+
"A path to a directory containing drift-states for a specific query."
|
|
144
139
|
),
|
|
145
140
|
)
|
|
146
141
|
parser_add_shortcut.add_argument(
|
|
147
|
-
|
|
142
|
+
"--shortcut",
|
|
148
143
|
type=str,
|
|
149
144
|
default=None,
|
|
150
|
-
help=(
|
|
151
|
-
'The desired alias for the filename.'
|
|
152
|
-
),
|
|
145
|
+
help=("The desired alias for the filename."),
|
|
153
146
|
)
|
|
154
147
|
parser_add_shortcut.add_argument(
|
|
155
|
-
|
|
148
|
+
"--filename",
|
|
156
149
|
type=str,
|
|
157
150
|
default=None,
|
|
158
|
-
help=(
|
|
159
|
-
'The desired name of the file to be replaced.'
|
|
160
|
-
),
|
|
151
|
+
help=("The desired name of the file to be replaced."),
|
|
161
152
|
)
|
|
162
153
|
return parser
|
|
163
154
|
|
|
@@ -171,13 +162,13 @@ class CLI:
|
|
|
171
162
|
# TODO support parameter lookup in environment variables if not present on command line
|
|
172
163
|
config = self.parser.parse_args(argv)
|
|
173
164
|
if config.verbose:
|
|
174
|
-
logging.getLogger(
|
|
165
|
+
logging.getLogger("driftdetect").setLevel(logging.DEBUG)
|
|
175
166
|
elif config.quiet:
|
|
176
|
-
logging.getLogger(
|
|
167
|
+
logging.getLogger("driftdetect").setLevel(logging.WARNING)
|
|
177
168
|
else:
|
|
178
|
-
logging.getLogger(
|
|
169
|
+
logging.getLogger("driftdetect").setLevel(logging.INFO)
|
|
179
170
|
logger.debug("Launching driftdetect with CLI configuration: %r", vars(config))
|
|
180
|
-
if config.command ==
|
|
171
|
+
if config.command == "get-state":
|
|
181
172
|
config = configure_get_state_neo4j(config)
|
|
182
173
|
return config
|
|
183
174
|
|
|
@@ -190,11 +181,11 @@ class CLI:
|
|
|
190
181
|
"""
|
|
191
182
|
config = self.configure(argv)
|
|
192
183
|
try:
|
|
193
|
-
if config.command ==
|
|
184
|
+
if config.command == "get-state":
|
|
194
185
|
run_get_states(config)
|
|
195
|
-
elif config.command ==
|
|
186
|
+
elif config.command == "get-drift":
|
|
196
187
|
run_drift_detection(config)
|
|
197
|
-
elif config.command ==
|
|
188
|
+
elif config.command == "add-shortcut":
|
|
198
189
|
run_add_shortcut(config)
|
|
199
190
|
else:
|
|
200
191
|
msg = "No command detected. Try --help."
|
|
@@ -215,7 +206,10 @@ def configure_get_state_neo4j(config):
|
|
|
215
206
|
if config.neo4j_user:
|
|
216
207
|
config.neo4j_password = None
|
|
217
208
|
if config.neo4j_password_prompt:
|
|
218
|
-
logger.info(
|
|
209
|
+
logger.info(
|
|
210
|
+
"Reading password for Neo4j user '%s' interactively.",
|
|
211
|
+
config.neo4j_user,
|
|
212
|
+
)
|
|
219
213
|
config.neo4j_password = getpass.getpass()
|
|
220
214
|
elif config.neo4j_password_env_var:
|
|
221
215
|
logger.debug(
|
|
@@ -225,7 +219,9 @@ def configure_get_state_neo4j(config):
|
|
|
225
219
|
)
|
|
226
220
|
config.neo4j_password = os.environ.get(config.neo4j_password_env_var)
|
|
227
221
|
if not config.neo4j_password:
|
|
228
|
-
logger.warning(
|
|
222
|
+
logger.warning(
|
|
223
|
+
"Neo4j username was provided but a password could not be found.",
|
|
224
|
+
)
|
|
229
225
|
else:
|
|
230
226
|
config.neo4j_password = None
|
|
231
227
|
return config
|
|
@@ -239,6 +235,6 @@ def main(argv=None):
|
|
|
239
235
|
:return: The return code.
|
|
240
236
|
"""
|
|
241
237
|
logging.basicConfig(level=logging.INFO)
|
|
242
|
-
logging.getLogger(
|
|
238
|
+
logging.getLogger("neo4j").setLevel(logging.WARNING)
|
|
243
239
|
argv = argv if argv is not None else sys.argv[1:]
|
|
244
240
|
return CLI(prog="cartography-detectdrift").main(argv)
|
|
@@ -23,11 +23,14 @@ def run_drift_detection(config: GetDriftConfig) -> None:
|
|
|
23
23
|
return
|
|
24
24
|
state_serializer = StateSchema()
|
|
25
25
|
shortcut_serializer = ShortcutSchema()
|
|
26
|
-
shortcut_data = FileSystem.load(
|
|
26
|
+
shortcut_data = FileSystem.load(
|
|
27
|
+
os.path.join(config.query_directory, "shortcut.json"),
|
|
28
|
+
)
|
|
27
29
|
shortcut = shortcut_serializer.load(shortcut_data)
|
|
28
30
|
start_state_data = FileSystem.load(
|
|
29
31
|
os.path.join(
|
|
30
|
-
config.query_directory,
|
|
32
|
+
config.query_directory,
|
|
33
|
+
shortcut.shortcuts.get(
|
|
31
34
|
config.start_state,
|
|
32
35
|
config.start_state,
|
|
33
36
|
),
|
|
@@ -36,7 +39,8 @@ def run_drift_detection(config: GetDriftConfig) -> None:
|
|
|
36
39
|
start_state = state_serializer.load(start_state_data)
|
|
37
40
|
end_state_data = FileSystem.load(
|
|
38
41
|
os.path.join(
|
|
39
|
-
config.query_directory,
|
|
42
|
+
config.query_directory,
|
|
43
|
+
shortcut.shortcuts.get(
|
|
40
44
|
config.end_state,
|
|
41
45
|
config.end_state,
|
|
42
46
|
),
|
|
@@ -74,13 +74,25 @@ def run_get_states(config: UpdateConfig) -> None:
|
|
|
74
74
|
return
|
|
75
75
|
|
|
76
76
|
with neo4j_driver.session() as session:
|
|
77
|
-
filename =
|
|
77
|
+
filename = ".".join([str(i) for i in time.gmtime()] + ["json"])
|
|
78
78
|
state_serializer = StateSchema()
|
|
79
79
|
shortcut_serializer = ShortcutSchema()
|
|
80
80
|
for query_directory in FileSystem.walk(config.drift_detection_directory):
|
|
81
81
|
try:
|
|
82
|
-
get_query_state(
|
|
83
|
-
|
|
82
|
+
get_query_state(
|
|
83
|
+
session,
|
|
84
|
+
query_directory,
|
|
85
|
+
state_serializer,
|
|
86
|
+
FileSystem,
|
|
87
|
+
filename,
|
|
88
|
+
)
|
|
89
|
+
add_shortcut(
|
|
90
|
+
FileSystem,
|
|
91
|
+
shortcut_serializer,
|
|
92
|
+
query_directory,
|
|
93
|
+
"most-recent",
|
|
94
|
+
filename,
|
|
95
|
+
)
|
|
84
96
|
except ValidationError as err:
|
|
85
97
|
msg = "Unable to create State for directory {}, with data \n{}".format(
|
|
86
98
|
query_directory,
|
|
@@ -97,11 +109,11 @@ def run_get_states(config: UpdateConfig) -> None:
|
|
|
97
109
|
|
|
98
110
|
|
|
99
111
|
def get_query_state(
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
112
|
+
session: neo4j.Session,
|
|
113
|
+
query_directory: str,
|
|
114
|
+
state_serializer: StateSchema,
|
|
115
|
+
storage,
|
|
116
|
+
filename: str,
|
|
105
117
|
) -> State:
|
|
106
118
|
"""
|
|
107
119
|
Gets the most recent state of a query.
|
cartography/driftdetect/model.py
CHANGED
|
@@ -19,11 +19,11 @@ class State:
|
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
21
|
def __init__(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
self,
|
|
23
|
+
name: str,
|
|
24
|
+
validation_query: str,
|
|
25
|
+
properties: List[str],
|
|
26
|
+
results: List[List[str]],
|
|
27
27
|
):
|
|
28
28
|
|
|
29
29
|
self.name: str = name
|
|
@@ -10,6 +10,7 @@ class StateSchema(Schema):
|
|
|
10
10
|
"""
|
|
11
11
|
Schema to serialize and deserialize DriftStates from JSON.
|
|
12
12
|
"""
|
|
13
|
+
|
|
13
14
|
name = fields.Str()
|
|
14
15
|
validation_query = fields.Str()
|
|
15
16
|
properties = fields.List(fields.Str())
|
|
@@ -18,10 +19,10 @@ class StateSchema(Schema):
|
|
|
18
19
|
@post_load
|
|
19
20
|
def make_state(self, data, **kwargs):
|
|
20
21
|
return State(
|
|
21
|
-
data[
|
|
22
|
-
data[
|
|
23
|
-
data[
|
|
24
|
-
data[
|
|
22
|
+
data["name"],
|
|
23
|
+
data["validation_query"],
|
|
24
|
+
data["properties"],
|
|
25
|
+
data["results"],
|
|
25
26
|
)
|
|
26
27
|
|
|
27
28
|
|
|
@@ -29,12 +30,13 @@ class ShortcutSchema(Schema):
|
|
|
29
30
|
"""
|
|
30
31
|
Schema to serialize and deserialize Shortcuts from JSON.
|
|
31
32
|
"""
|
|
33
|
+
|
|
32
34
|
name = fields.Str()
|
|
33
35
|
shortcuts = fields.Dict(keys=fields.Str(), values=fields.Str())
|
|
34
36
|
|
|
35
37
|
@post_load
|
|
36
38
|
def make_misc(self, data, **kwargs):
|
|
37
39
|
return Shortcut(
|
|
38
|
-
data[
|
|
39
|
-
data[
|
|
40
|
+
data["name"],
|
|
41
|
+
data["shortcuts"],
|
|
40
42
|
)
|
|
@@ -25,9 +25,9 @@ class FileSystem:
|
|
|
25
25
|
:param file_path: Filepath to be written to.
|
|
26
26
|
:return:
|
|
27
27
|
"""
|
|
28
|
-
with open(file_path,
|
|
28
|
+
with open(file_path, "w") as json_file:
|
|
29
29
|
json.dump(data, json_file, sort_keys=True, indent=4)
|
|
30
|
-
json_file.write(
|
|
30
|
+
json_file.write("\n")
|
|
31
31
|
|
|
32
32
|
@classmethod
|
|
33
33
|
def walk(cls, drift_detection_directory):
|