cartography 0.73.1__py3-none-any.whl → 0.75.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.
- cartography/client/core/tx.py +42 -0
- cartography/data/indexes.cypher +1 -3
- cartography/data/jobs/analysis/aws_ec2_asset_exposure.json +5 -5
- cartography/data/jobs/analysis/aws_ec2_keypair_analysis.json +2 -2
- cartography/data/jobs/analysis/aws_eks_asset_exposure.json +1 -1
- cartography/data/jobs/analysis/aws_foreign_accounts.json +2 -2
- cartography/data/jobs/analysis/gcp_compute_asset_inet_exposure.json +4 -4
- cartography/data/jobs/analysis/gcp_gke_asset_exposure.json +1 -1
- cartography/data/jobs/analysis/gcp_gke_basic_auth.json +2 -2
- cartography/data/jobs/cleanup/aws_apigateway_details.json +1 -1
- cartography/data/jobs/cleanup/aws_kms_details.json +1 -1
- cartography/data/jobs/cleanup/aws_s3_details.json +1 -1
- cartography/graph/cleanupbuilder.py +18 -38
- cartography/graph/job.py +1 -3
- cartography/graph/querybuilder.py +80 -3
- cartography/graph/statement.py +14 -3
- cartography/intel/aws/apigateway.py +1 -1
- cartography/intel/aws/ecs.py +26 -19
- cartography/intel/aws/emr.py +17 -22
- cartography/intel/aws/iam.py +15 -10
- cartography/intel/aws/kms.py +1 -1
- cartography/intel/aws/rds.py +1 -1
- cartography/intel/aws/s3.py +3 -3
- cartography/intel/azure/util/credentials.py +13 -3
- cartography/intel/oci/iam.py +1 -1
- cartography/models/aws/emr.py +1 -1
- cartography/models/core/common.py +21 -1
- {cartography-0.73.1.dist-info → cartography-0.75.0.dist-info}/METADATA +1 -1
- {cartography-0.73.1.dist-info → cartography-0.75.0.dist-info}/RECORD +34 -34
- {cartography-0.73.1.dist-info → cartography-0.75.0.dist-info}/WHEEL +1 -1
- {cartography-0.73.1.dist-info → cartography-0.75.0.dist-info}/LICENSE +0 -0
- {cartography-0.73.1.dist-info → cartography-0.75.0.dist-info}/NOTICE +0 -0
- {cartography-0.73.1.dist-info → cartography-0.75.0.dist-info}/entry_points.txt +0 -0
- {cartography-0.73.1.dist-info → cartography-0.75.0.dist-info}/top_level.txt +0 -0
cartography/client/core/tx.py
CHANGED
|
@@ -7,6 +7,9 @@ from typing import Union
|
|
|
7
7
|
|
|
8
8
|
import neo4j
|
|
9
9
|
|
|
10
|
+
from cartography.graph.querybuilder import build_create_index_queries
|
|
11
|
+
from cartography.graph.querybuilder import build_ingestion_query
|
|
12
|
+
from cartography.models.core.nodes import CartographyNodeSchema
|
|
10
13
|
from cartography.util import batch
|
|
11
14
|
|
|
12
15
|
|
|
@@ -210,3 +213,42 @@ def load_graph_data(
|
|
|
210
213
|
DictList=data_batch,
|
|
211
214
|
**kwargs,
|
|
212
215
|
)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def ensure_indexes(neo4j_session: neo4j.Session, node_schema: CartographyNodeSchema) -> None:
|
|
219
|
+
"""
|
|
220
|
+
Creates indexes if they don't exist for the given CartographyNodeSchema object, as well as for all of the
|
|
221
|
+
relationships defined on its `other_relationships` and `sub_resource_relationship` fields. This operation is
|
|
222
|
+
idempotent.
|
|
223
|
+
|
|
224
|
+
This ensures that every time we need to MATCH on a node to draw a relationship to it, the field used for the MATCH
|
|
225
|
+
will be indexed, making the operation fast.
|
|
226
|
+
:param neo4j_session: The neo4j session
|
|
227
|
+
:param node_schema: The node_schema object to create indexes for.
|
|
228
|
+
"""
|
|
229
|
+
queries = build_create_index_queries(node_schema)
|
|
230
|
+
|
|
231
|
+
for query in queries:
|
|
232
|
+
if not query.startswith('CREATE INDEX IF NOT EXISTS'):
|
|
233
|
+
raise ValueError('Query provided to `ensure_indexes()` does not start with "CREATE INDEX IF NOT EXISTS".')
|
|
234
|
+
neo4j_session.run(query)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def load(
|
|
238
|
+
neo4j_session: neo4j.Session,
|
|
239
|
+
node_schema: CartographyNodeSchema,
|
|
240
|
+
dict_list: List[Dict[str, Any]],
|
|
241
|
+
**kwargs,
|
|
242
|
+
) -> None:
|
|
243
|
+
"""
|
|
244
|
+
Main entrypoint for intel modules to write data to the graph. Ensures that indexes exist for the datatypes loaded
|
|
245
|
+
to the graph and then performs the load operation.
|
|
246
|
+
:param neo4j_session: The Neo4j session
|
|
247
|
+
:param node_schema: The CartographyNodeSchema object to create indexes for and generate a query.
|
|
248
|
+
:param dict_list: The data to load to the graph represented as a list of dicts.
|
|
249
|
+
:param kwargs: Allows additional keyword args to be supplied to the Neo4j query.
|
|
250
|
+
:return: None
|
|
251
|
+
"""
|
|
252
|
+
ensure_indexes(neo4j_session, node_schema)
|
|
253
|
+
ingestion_query = build_ingestion_query(node_schema)
|
|
254
|
+
load_graph_data(neo4j_session, ingestion_query, dict_list, **kwargs)
|
cartography/data/indexes.cypher
CHANGED
|
@@ -140,6 +140,7 @@ CREATE INDEX IF NOT EXISTS FOR (n:ECSContainerInstance) ON (n.lastupdated);
|
|
|
140
140
|
CREATE INDEX IF NOT EXISTS FOR (n:ECSService) ON (n.id);
|
|
141
141
|
CREATE INDEX IF NOT EXISTS FOR (n:ECSService) ON (n.lastupdated);
|
|
142
142
|
CREATE INDEX IF NOT EXISTS FOR (n:ECSTaskDefinition) ON (n.id);
|
|
143
|
+
CREATE INDEX IF NOT EXISTS FOR (n:ECSTaskDefinition) ON (n.arn);
|
|
143
144
|
CREATE INDEX IF NOT EXISTS FOR (n:ECSTaskDefinition) ON (n.lastupdated);
|
|
144
145
|
CREATE INDEX IF NOT EXISTS FOR (n:ECSTask) ON (n.id);
|
|
145
146
|
CREATE INDEX IF NOT EXISTS FOR (n:ECSTask) ON (n.lastupdated);
|
|
@@ -158,9 +159,6 @@ CREATE INDEX IF NOT EXISTS FOR (n:ELBListener) ON (n.id);
|
|
|
158
159
|
CREATE INDEX IF NOT EXISTS FOR (n:ELBListener) ON (n.lastupdated);
|
|
159
160
|
CREATE INDEX IF NOT EXISTS FOR (n:ELBV2Listener) ON (n.id);
|
|
160
161
|
CREATE INDEX IF NOT EXISTS FOR (n:ELBV2Listener) ON (n.lastupdated);
|
|
161
|
-
CREATE INDEX IF NOT EXISTS FOR (n:EMRCluster) ON (n.id);
|
|
162
|
-
CREATE INDEX IF NOT EXISTS FOR (n:EMRCluster) ON (n.arn);
|
|
163
|
-
CREATE INDEX IF NOT EXISTS FOR (n:EMRCluster) ON (n.lastupdated);
|
|
164
162
|
CREATE INDEX IF NOT EXISTS FOR (n:Endpoint) ON (n.id);
|
|
165
163
|
CREATE INDEX IF NOT EXISTS FOR (n:Endpoint) ON (n.lastupdated);
|
|
166
164
|
CREATE INDEX IF NOT EXISTS FOR (n:ESDomain) ON (n.arn);
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"statements": [
|
|
3
3
|
{
|
|
4
|
-
"query": "MATCH (n) where
|
|
4
|
+
"query": "MATCH (n) where n.exposed_internet IS NOT NULL AND labels(n) IN ['AutoScalingGroup', 'EC2Instance', 'LoadBalancer', 'LoadBalancerV2'] WITH n LIMIT $LIMIT_SIZE REMOVE n.exposed_internet, n.exposed_internet_type return COUNT(*) as TotalCompleted",
|
|
5
5
|
"iterative": true,
|
|
6
6
|
"iterationsize": 1000
|
|
7
7
|
},
|
|
8
8
|
{
|
|
9
|
-
"query": "MATCH (:IpRange{id: '0.0.0.0/0'})-[:MEMBER_OF_IP_RULE]->(:IpPermissionInbound)-[:MEMBER_OF_EC2_SECURITY_GROUP]->(group:EC2SecurityGroup)<-[:MEMBER_OF_EC2_SECURITY_GROUP|NETWORK_INTERFACE*..2]-(instance:EC2Instance)\nWITH instance\nWHERE (
|
|
9
|
+
"query": "MATCH (:IpRange{id: '0.0.0.0/0'})-[:MEMBER_OF_IP_RULE]->(:IpPermissionInbound)-[:MEMBER_OF_EC2_SECURITY_GROUP]->(group:EC2SecurityGroup)<-[:MEMBER_OF_EC2_SECURITY_GROUP|NETWORK_INTERFACE*..2]-(instance:EC2Instance)\nWITH instance\nWHERE (instance.publicipaddress IS NOT NULL) AND (instance.exposed_internet_type IS NULL) OR (NOT 'direct' IN instance.exposed_internet_type)\nSET instance.exposed_internet = true, instance.exposed_internet_type = coalesce(instance.exposed_internet_type , []) + 'direct';",
|
|
10
10
|
"iterative": false
|
|
11
11
|
},
|
|
12
12
|
{
|
|
@@ -18,15 +18,15 @@
|
|
|
18
18
|
"iterative": false
|
|
19
19
|
},
|
|
20
20
|
{
|
|
21
|
-
"query": "MATCH (elb:LoadBalancer{exposed_internet: true})-[:EXPOSE]->(e:EC2Instance)\nWITH e\nWHERE (
|
|
21
|
+
"query": "MATCH (elb:LoadBalancer{exposed_internet: true})-[:EXPOSE]->(e:EC2Instance)\nWITH e\nWHERE (e.exposed_internet_type IS NULL) OR (NOT 'elb' IN e.exposed_internet_type)\nSET e.exposed_internet = true, e.exposed_internet_type = coalesce(e.exposed_internet_type, []) + 'elb'",
|
|
22
22
|
"iterative": false
|
|
23
23
|
},
|
|
24
24
|
{
|
|
25
|
-
"query": "MATCH (elbv2:LoadBalancerV2{exposed_internet: true})-[:EXPOSE]->(e:EC2Instance)\nWITH e\nWHERE (
|
|
25
|
+
"query": "MATCH (elbv2:LoadBalancerV2{exposed_internet: true})-[:EXPOSE]->(e:EC2Instance)\nWITH e\nWHERE (e.exposed_internet_type IS NULL) OR (NOT 'elbv2' IN e.exposed_internet_type)\nSET e.exposed_internet = true, e.exposed_internet_type = coalesce(e.exposed_internet_type, []) + 'elbv2'",
|
|
26
26
|
"iterative": false
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
|
-
"query": "MATCH (instance:EC2Instance{exposed_internet: true})-[:MEMBER_AUTO_SCALE_GROUP]->(asg:AutoScalingGroup)\nWITH distinct instance.exposed_internet_type as types, asg\nUNWIND types as type\nWITH type, asg\nWHERE
|
|
29
|
+
"query": "MATCH (instance:EC2Instance{exposed_internet: true})-[:MEMBER_AUTO_SCALE_GROUP]->(asg:AutoScalingGroup)\nWITH distinct instance.exposed_internet_type as types, asg\nUNWIND types as type\nWITH type, asg\nWHERE asg.exposed_internet_type IS NULL OR (NOT type IN asg.exposed_internet_type)\nSET asg.exposed_internet = true, asg.exposed_internet_type = coalesce(asg.exposed_internet_type, []) + type;",
|
|
30
30
|
"iterative": false
|
|
31
31
|
}
|
|
32
32
|
],
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
"statements": [
|
|
4
4
|
{
|
|
5
5
|
"__comment__": "Delete the attribute user_uploaded",
|
|
6
|
-
"query": "MATCH (k:EC2KeyPair) WHERE
|
|
6
|
+
"query": "MATCH (k:EC2KeyPair) WHERE k.user_uploaded IS NOT NULL REMOVE k.user_uploaded return COUNT(*) as TotalCompleted",
|
|
7
7
|
"iterative": false
|
|
8
8
|
},
|
|
9
9
|
{
|
|
10
10
|
"__comment__": "Delete the attribute duplicate_keyfingerprint",
|
|
11
|
-
"query": "MATCH (k:EC2KeyPair) WHERE
|
|
11
|
+
"query": "MATCH (k:EC2KeyPair) WHERE k.duplicate_keyfingerprint IS NOT NULL REMOVE k.duplicate_keyfingerprint return COUNT(*) as TotalCompleted",
|
|
12
12
|
"iterative": false
|
|
13
13
|
},
|
|
14
14
|
{
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"statements": [
|
|
3
3
|
{
|
|
4
4
|
"__comment": "This is a clean-up statement to remove custom attributes",
|
|
5
|
-
"query": "MATCH (cluster:EKSCluster) WHERE
|
|
5
|
+
"query": "MATCH (cluster:EKSCluster) WHERE cluster.exposed_internet IS NOT NULL REMOVE cluster.exposed_internet return COUNT(*) as TotalCompleted",
|
|
6
6
|
"iterative": false
|
|
7
7
|
},
|
|
8
8
|
{
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
"statements": [
|
|
3
3
|
{
|
|
4
4
|
"__comment": "This analyze AWS accounts we created and tag the ones that are foreign. Foreign accounts are ones that were not in the sync scope",
|
|
5
|
-
"query": "MATCH (foreign:AWSAccount) where
|
|
5
|
+
"query": "MATCH (foreign:AWSAccount) where foreign.inscope IS NULL SET foreign.foreign = true",
|
|
6
6
|
"iterative": false
|
|
7
7
|
},
|
|
8
8
|
{
|
|
9
9
|
"__comment": "Remove accounts that were set with foreign and inscope. This can happen as we finish the list of sync accounts through assume role mapping and vpc peering",
|
|
10
|
-
"query": "MATCH (a:AWSAccount) where
|
|
10
|
+
"query": "MATCH (a:AWSAccount) where a.inscope IS NOT NULL AND a.foreign IS NOT NULL REMOVE a.foreign",
|
|
11
11
|
"iterative": false
|
|
12
12
|
}
|
|
13
13
|
],
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"statements": [
|
|
3
3
|
{
|
|
4
|
-
"query": "MATCH (n) where
|
|
4
|
+
"query": "MATCH (n) where n.exposed_internet IS NOT NULL AND labels(n) IN ['GCPInstance'] WITH n LIMIT $LIMIT_SIZE REMOVE n.exposed_internet, n.exposed_internet_type return COUNT(*) as TotalCompleted",
|
|
5
5
|
"iterative": true,
|
|
6
6
|
"iterationsize": 1000,
|
|
7
7
|
"__comment__": "Delete exposed_internet off nodes so we can start fresh"
|
|
@@ -22,17 +22,17 @@
|
|
|
22
22
|
"__comment__": "Delete stale firewall ingress relationships"
|
|
23
23
|
},
|
|
24
24
|
{
|
|
25
|
-
"query": "MATCH (ac:GCPNicAccessConfig)<-[:RESOURCE]-(:GCPNetworkInterface)<-[:NETWORK_INTERFACE]-(n:GCPInstance)<-[:FIREWALL_INGRESS]-(firewall_a:GCPFirewall)<-[:ALLOWED_BY]-(allow_rule:GCPIpRule{protocol:'tcp'})<-[:MEMBER_OF_IP_RULE]-(:IpRange{id:\"0.0.0.0/0\"})\nOPTIONAL MATCH (n)<-[:FIREWALL_INGRESS]-(firewall_b:GCPFirewall)<-[:DENIED_BY]-(deny_rule:GCPIpRule{protocol:'tcp'})\nWHERE
|
|
25
|
+
"query": "MATCH (ac:GCPNicAccessConfig)<-[:RESOURCE]-(:GCPNetworkInterface)<-[:NETWORK_INTERFACE]-(n:GCPInstance)<-[:FIREWALL_INGRESS]-(firewall_a:GCPFirewall)<-[:ALLOWED_BY]-(allow_rule:GCPIpRule{protocol:'tcp'})<-[:MEMBER_OF_IP_RULE]-(:IpRange{id:\"0.0.0.0/0\"})\nOPTIONAL MATCH (n)<-[:FIREWALL_INGRESS]-(firewall_b:GCPFirewall)<-[:DENIED_BY]-(deny_rule:GCPIpRule{protocol:'tcp'})\nWHERE ac.public_ip IS NOT NULL and (\n\tdeny_rule is NULL\n\tOR firewall_b.priority > firewall_a.priority\n\tOR NOT allow_rule.fromport IN RANGE(deny_rule.fromport, deny_rule.toport)\n\tOR NOT allow_rule.toport IN RANGE(deny_rule.fromport, deny_rule.toport)\n)\nSET n.exposed_internet = True, n.exposed_internet_type='direct'\nRETURN count(*) as TotalCompleted",
|
|
26
26
|
"iterative": false,
|
|
27
27
|
"__comment__": "Mark a GCP instance with exposed_internet = True and exposed_internet_type = 'direct' if its attached firewalls and TCP rules expose it to the internet."
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
|
-
"query": "MATCH (ac:GCPNicAccessConfig)<-[:RESOURCE]-(:GCPNetworkInterface)<-[:NETWORK_INTERFACE]-(n:GCPInstance)<-[:FIREWALL_INGRESS]-(firewall_a:GCPFirewall)<-[:ALLOWED_BY]-(allow_rule:GCPIpRule{protocol:'udp'})<-[:MEMBER_OF_IP_RULE]-(:IpRange{id:\"0.0.0.0/0\"})\nOPTIONAL MATCH (n)<-[:FIREWALL_INGRESS]-(firewall_b:GCPFirewall)<-[:DENIED_BY]-(deny_rule:GCPIpRule{protocol:'udp'})\nWHERE
|
|
30
|
+
"query": "MATCH (ac:GCPNicAccessConfig)<-[:RESOURCE]-(:GCPNetworkInterface)<-[:NETWORK_INTERFACE]-(n:GCPInstance)<-[:FIREWALL_INGRESS]-(firewall_a:GCPFirewall)<-[:ALLOWED_BY]-(allow_rule:GCPIpRule{protocol:'udp'})<-[:MEMBER_OF_IP_RULE]-(:IpRange{id:\"0.0.0.0/0\"})\nOPTIONAL MATCH (n)<-[:FIREWALL_INGRESS]-(firewall_b:GCPFirewall)<-[:DENIED_BY]-(deny_rule:GCPIpRule{protocol:'udp'})\nWHERE ac.public_ip IS NOT NULL and (\n\tdeny_rule is NULL\n\tOR firewall_b.priority > firewall_a.priority\n\tOR NOT allow_rule.fromport IN RANGE(deny_rule.fromport, deny_rule.toport)\n\tOR NOT allow_rule.toport IN RANGE(deny_rule.fromport, deny_rule.toport)\n)\nSET n.exposed_internet = True, n.exposed_internet_type='direct'\nRETURN count(*) as TotalCompleted",
|
|
31
31
|
"iterative": false,
|
|
32
32
|
"__comment__": "Mark a GCP instance with exposed_internet = True and exposed_internet_type = 'direct' if its attached firewalls and UDP rules expose it to the internet."
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
|
-
"query": "MATCH (ac:GCPNicAccessConfig)<-[:RESOURCE]-(:GCPNetworkInterface)<-[:NETWORK_INTERFACE]-(n:GCPInstance)<-[:FIREWALL_INGRESS]-(firewall_a:GCPFirewall)<-[:ALLOWED_BY]-(allow_rule:GCPIpRule{protocol:'all'})<-[:MEMBER_OF_IP_RULE]-(:IpRange{id:\"0.0.0.0/0\"})\nOPTIONAL MATCH (n)<-[:FIREWALL_INGRESS]-(firewall_b:GCPFirewall)<-[:DENIED_BY]-(deny_rule:GCPIpRule{protocol:'all'})\nWHERE
|
|
35
|
+
"query": "MATCH (ac:GCPNicAccessConfig)<-[:RESOURCE]-(:GCPNetworkInterface)<-[:NETWORK_INTERFACE]-(n:GCPInstance)<-[:FIREWALL_INGRESS]-(firewall_a:GCPFirewall)<-[:ALLOWED_BY]-(allow_rule:GCPIpRule{protocol:'all'})<-[:MEMBER_OF_IP_RULE]-(:IpRange{id:\"0.0.0.0/0\"})\nOPTIONAL MATCH (n)<-[:FIREWALL_INGRESS]-(firewall_b:GCPFirewall)<-[:DENIED_BY]-(deny_rule:GCPIpRule{protocol:'all'})\nWHERE ac.public_ip IS NOT NULL and allow_rule.fromport IS NOT NULL and allow_rule.toport IS NOT NULL and (\n\tdeny_rule is NULL\n\tOR firewall_b.priority > firewall_a.priority\n\tOR NOT allow_rule.fromport IN RANGE(deny_rule.fromport, deny_rule.toport)\n\tOR NOT allow_rule.toport IN RANGE(deny_rule.fromport, deny_rule.toport)\n)\nSET n.exposed_internet = True, n.exposed_internet_type='direct'\nRETURN count(*) as TotalCompleted",
|
|
36
36
|
"iterative": false,
|
|
37
37
|
"__comment__": "Mark a GCP instance with exposed_internet = True and exposed_internet_type = 'direct' if its attached firewalls and ALL rules expose it to the internet."
|
|
38
38
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"statements": [
|
|
3
3
|
{
|
|
4
4
|
"__comment": "This is a clean-up statement to remove custom attributes",
|
|
5
|
-
"query": "MATCH (cluster:GKECluster) WHERE
|
|
5
|
+
"query": "MATCH (cluster:GKECluster) WHERE cluster.exposed_internet IS NOT NULL REMOVE cluster.exposed_internet return COUNT(*) as TotalCompleted",
|
|
6
6
|
"iterative": false
|
|
7
7
|
},
|
|
8
8
|
{
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
"statements": [
|
|
3
3
|
{
|
|
4
4
|
"__comment": "This is a clean-up statement to remove custom attributes",
|
|
5
|
-
"query": "MATCH (cluster:GKECluster) WHERE
|
|
5
|
+
"query": "MATCH (cluster:GKECluster) WHERE cluster.basic_auth IS NOT NULL REMOVE cluster.basic_auth return COUNT(*) as TotalCompleted",
|
|
6
6
|
"iterative": false
|
|
7
7
|
},
|
|
8
8
|
{
|
|
9
9
|
"__comment": "This sets the basic_auth attribute",
|
|
10
|
-
"query": "MATCH (cluster:GKECluster) WHERE (
|
|
10
|
+
"query": "MATCH (cluster:GKECluster) WHERE (cluster.masterauth_username IS NOT NULL AND NOT cluster.masterauth_username = '') AND (cluster.masterauth_password IS NOT NULL AND NOT cluster.masterauth.password = '') SET cluster.basic_auth = true",
|
|
11
11
|
"iterative": false
|
|
12
12
|
}
|
|
13
13
|
],
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"statements": [
|
|
3
3
|
{
|
|
4
|
-
"query": "MATCH (:AWSAccount{id: $AWS_ID})-[:RESOURCE]->(s:RestAPI) WHERE
|
|
4
|
+
"query": "MATCH (:AWSAccount{id: $AWS_ID})-[:RESOURCE]->(s:RestAPI) WHERE s.anonymous_access IS NOT NULL\n WITH s LIMIT $LIMIT_SIZE\nREMOVE s.anonymous_access, s.anonymous_actions",
|
|
5
5
|
"iterative": true,
|
|
6
6
|
"iterationsize": 100
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"statements": [
|
|
3
3
|
{
|
|
4
|
-
"query": "MATCH (:AWSAccount{id: $AWS_ID})-[:RESOURCE]->(s:KMSKey) WHERE
|
|
4
|
+
"query": "MATCH (:AWSAccount{id: $AWS_ID})-[:RESOURCE]->(s:KMSKey) WHERE s.anonymous_access IS NOT NULL\n WITH s LIMIT $LIMIT_SIZE\nREMOVE s.anonymous_access, s.anonymous_actions",
|
|
5
5
|
"iterative": true,
|
|
6
6
|
"iterationsize": 100
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"statements": [
|
|
3
3
|
{
|
|
4
|
-
"query": "MATCH (:AWSAccount{id: $AWS_ID})-[:RESOURCE]->(s:S3Bucket) WHERE
|
|
4
|
+
"query": "MATCH (:AWSAccount{id: $AWS_ID})-[:RESOURCE]->(s:S3Bucket) WHERE s.anonymous_access IS NOT NULL\n WITH s LIMIT $LIMIT_SIZE\nREMOVE s.anonymous_access, s.anonymous_actions",
|
|
5
5
|
"iterative": true,
|
|
6
6
|
"iterationsize": 100
|
|
7
7
|
}
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
from dataclasses import asdict
|
|
2
2
|
from string import Template
|
|
3
3
|
from typing import List
|
|
4
|
-
from typing import Optional
|
|
5
|
-
from typing import Set
|
|
6
4
|
|
|
7
5
|
from cartography.graph.querybuilder import _build_match_clause
|
|
8
|
-
from cartography.graph.querybuilder import filter_selected_relationships
|
|
9
6
|
from cartography.graph.querybuilder import rel_present_on_node_schema
|
|
10
7
|
from cartography.models.core.nodes import CartographyNodeSchema
|
|
11
8
|
from cartography.models.core.relationships import CartographyRelSchema
|
|
@@ -13,49 +10,32 @@ from cartography.models.core.relationships import LinkDirection
|
|
|
13
10
|
from cartography.models.core.relationships import TargetNodeMatcher
|
|
14
11
|
|
|
15
12
|
|
|
16
|
-
def build_cleanup_queries(
|
|
17
|
-
node_schema: CartographyNodeSchema,
|
|
18
|
-
selected_rels: Optional[Set[CartographyRelSchema]] = None,
|
|
19
|
-
) -> List[str]:
|
|
13
|
+
def build_cleanup_queries(node_schema: CartographyNodeSchema) -> List[str]:
|
|
20
14
|
"""
|
|
21
15
|
Generates queries to clean up stale nodes and relationships from the given CartographyNodeSchema.
|
|
22
|
-
|
|
23
|
-
:
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
leaving orphaned objects behind.
|
|
32
|
-
Note also that we return the empty list if the node_schema has no relationships. Doing cleanups of nodes without
|
|
33
|
-
relationships can be resource expensive for a large graph, and you might risk deleting unintended objects. Please
|
|
34
|
-
write a manual cleanup job if you wish to do this.
|
|
16
|
+
Note that auto-cleanups for a node with no relationships is not currently supported.
|
|
17
|
+
Algorithm:
|
|
18
|
+
1. First delete all stale nodes attached to the node_schema's sub resource
|
|
19
|
+
2. Delete all stale node to sub resource relationships
|
|
20
|
+
- We don't expect this to be very common (never for AWS resources, at least), but in case it is possible for an
|
|
21
|
+
asset to change sub resources, we want to handle it properly.
|
|
22
|
+
3. For all relationships defined on the node schema, delete all stale ones.
|
|
23
|
+
:param node_schema: The given CartographyNodeSchema
|
|
24
|
+
:return: A list of Neo4j queries to clean up nodes and relationships.
|
|
35
25
|
"""
|
|
36
|
-
|
|
37
|
-
sub_resource_rel = node_schema.sub_resource_relationship
|
|
38
|
-
|
|
39
|
-
if selected_rels:
|
|
40
|
-
# Ensure that the selected rels actually exist on the node_schema
|
|
41
|
-
sub_resource_rel, other_rels = filter_selected_relationships(node_schema, selected_rels)
|
|
42
|
-
|
|
43
|
-
if not sub_resource_rel:
|
|
26
|
+
if not node_schema.sub_resource_relationship:
|
|
44
27
|
raise ValueError(
|
|
45
28
|
"Auto-creating a cleanup job for a node_schema without a sub resource relationship is not supported. "
|
|
46
|
-
f'Please check the class definition of "{node_schema.__class__.__name__}".
|
|
47
|
-
'param was specified to build_cleanup_queries(), then ensure that the sub resource relationship is '
|
|
48
|
-
'present.',
|
|
29
|
+
f'Please check the class definition of "{node_schema.__class__.__name__}".',
|
|
49
30
|
)
|
|
50
31
|
|
|
51
|
-
result =
|
|
52
|
-
if
|
|
53
|
-
for rel in
|
|
54
|
-
|
|
32
|
+
result = _build_cleanup_node_and_rel_queries(node_schema, node_schema.sub_resource_relationship)
|
|
33
|
+
if node_schema.other_relationships:
|
|
34
|
+
for rel in node_schema.other_relationships.rels:
|
|
35
|
+
# [0] is the delete node query, [1] is the delete relationship query. We only want the latter.
|
|
36
|
+
_, rel_query = _build_cleanup_node_and_rel_queries(node_schema, rel)
|
|
37
|
+
result.append(rel_query)
|
|
55
38
|
|
|
56
|
-
# Make sure that the sub resource cleanup job is last in the list; order matters.
|
|
57
|
-
result.extend(_build_cleanup_node_and_rel_queries(node_schema, sub_resource_rel))
|
|
58
|
-
# Note that auto-cleanups for a node with no relationships does not happen at all - we don't support it.
|
|
59
39
|
return result
|
|
60
40
|
|
|
61
41
|
|
cartography/graph/job.py
CHANGED
|
@@ -16,7 +16,6 @@ from cartography.graph.cleanupbuilder import build_cleanup_queries
|
|
|
16
16
|
from cartography.graph.statement import get_job_shortname
|
|
17
17
|
from cartography.graph.statement import GraphStatement
|
|
18
18
|
from cartography.models.core.nodes import CartographyNodeSchema
|
|
19
|
-
from cartography.models.core.relationships import CartographyRelSchema
|
|
20
19
|
|
|
21
20
|
logger = logging.getLogger(__name__)
|
|
22
21
|
|
|
@@ -129,14 +128,13 @@ class GraphJob:
|
|
|
129
128
|
cls,
|
|
130
129
|
node_schema: CartographyNodeSchema,
|
|
131
130
|
parameters: Dict[str, Any],
|
|
132
|
-
selected_rels: Optional[Set[CartographyRelSchema]] = None,
|
|
133
131
|
) -> 'GraphJob':
|
|
134
132
|
"""
|
|
135
133
|
Create a cleanup job from a CartographyNodeSchema object.
|
|
136
134
|
For a given node, the fields used in the node_schema.sub_resource_relationship.target_node_node_matcher.keys()
|
|
137
135
|
must be provided as keys and values in the params dict.
|
|
138
136
|
"""
|
|
139
|
-
queries: List[str] = build_cleanup_queries(node_schema
|
|
137
|
+
queries: List[str] = build_cleanup_queries(node_schema)
|
|
140
138
|
|
|
141
139
|
expected_param_keys: Set[str] = get_parameters(queries)
|
|
142
140
|
actual_param_keys: Set[str] = set(parameters.keys())
|
|
@@ -2,6 +2,7 @@ import logging
|
|
|
2
2
|
from dataclasses import asdict
|
|
3
3
|
from string import Template
|
|
4
4
|
from typing import Dict
|
|
5
|
+
from typing import List
|
|
5
6
|
from typing import Optional
|
|
6
7
|
from typing import Set
|
|
7
8
|
from typing import Tuple
|
|
@@ -108,6 +109,28 @@ def _build_match_clause(matcher: TargetNodeMatcher) -> str:
|
|
|
108
109
|
return ', '.join(match.safe_substitute(Key=key, PropRef=prop_ref) for key, prop_ref in matcher_asdict.items())
|
|
109
110
|
|
|
110
111
|
|
|
112
|
+
def _build_where_clause_for_rel_match(node_var: str, matcher: TargetNodeMatcher) -> str:
|
|
113
|
+
"""
|
|
114
|
+
Same as _build_match_clause, but puts the matching logic in a WHERE clause.
|
|
115
|
+
This is intended specifically to use for joining with relationships where we need a case-insensitive match.
|
|
116
|
+
:param matcher: A TargetNodeMatcher object
|
|
117
|
+
:return: a Neo4j where clause
|
|
118
|
+
"""
|
|
119
|
+
match = Template("$node_var.$key = $prop_ref")
|
|
120
|
+
case_insensitive_match = Template("toLower($node_var.$key) = toLower($prop_ref)")
|
|
121
|
+
|
|
122
|
+
matcher_asdict = asdict(matcher)
|
|
123
|
+
|
|
124
|
+
result = []
|
|
125
|
+
for key, prop_ref in matcher_asdict.items():
|
|
126
|
+
if prop_ref.ignore_case:
|
|
127
|
+
prop_line = case_insensitive_match.safe_substitute(node_var=node_var, key=key, prop_ref=prop_ref)
|
|
128
|
+
else:
|
|
129
|
+
prop_line = match.safe_substitute(node_var=node_var, key=key, prop_ref=prop_ref)
|
|
130
|
+
result.append(prop_line)
|
|
131
|
+
return ' AND\n'.join(result)
|
|
132
|
+
|
|
133
|
+
|
|
111
134
|
def _asdict_with_validate_relprops(link: CartographyRelSchema) -> Dict[str, PropertyRef]:
|
|
112
135
|
"""
|
|
113
136
|
Give a helpful error message when forgetting to put `()` when instantiating a CartographyRelSchema, as this
|
|
@@ -145,6 +168,7 @@ def _build_attach_sub_resource_statement(sub_resource_link: Optional[Cartography
|
|
|
145
168
|
|
|
146
169
|
sub_resource_attach_template = Template(
|
|
147
170
|
"""
|
|
171
|
+
WITH i, item
|
|
148
172
|
OPTIONAL MATCH (j:$SubResourceLabel{$MatchClause})
|
|
149
173
|
WITH i, item, j WHERE j IS NOT NULL
|
|
150
174
|
$RelMergeClause
|
|
@@ -191,7 +215,9 @@ def _build_attach_additional_links_statement(
|
|
|
191
215
|
additional_links_template = Template(
|
|
192
216
|
"""
|
|
193
217
|
WITH i, item
|
|
194
|
-
OPTIONAL MATCH ($node_var:$AddlLabel
|
|
218
|
+
OPTIONAL MATCH ($node_var:$AddlLabel)
|
|
219
|
+
WHERE
|
|
220
|
+
$WhereClause
|
|
195
221
|
WITH i, item, $node_var WHERE $node_var IS NOT NULL
|
|
196
222
|
$RelMerge
|
|
197
223
|
ON CREATE SET $rel_var.firstseen = timestamp()
|
|
@@ -219,7 +245,7 @@ def _build_attach_additional_links_statement(
|
|
|
219
245
|
|
|
220
246
|
additional_ref = additional_links_template.safe_substitute(
|
|
221
247
|
AddlLabel=link.target_node_label,
|
|
222
|
-
|
|
248
|
+
WhereClause=_build_where_clause_for_rel_match(node_var, link.target_node_matcher),
|
|
223
249
|
node_var=node_var,
|
|
224
250
|
rel_var=rel_var,
|
|
225
251
|
RelMerge=rel_merge,
|
|
@@ -258,7 +284,6 @@ def _build_attach_relationships_statement(
|
|
|
258
284
|
"""
|
|
259
285
|
WITH i, item
|
|
260
286
|
CALL {
|
|
261
|
-
WITH i, item
|
|
262
287
|
$attach_relationships_statement
|
|
263
288
|
}
|
|
264
289
|
""",
|
|
@@ -374,3 +399,55 @@ def build_ingestion_query(
|
|
|
374
399
|
attach_relationships_statement=_build_attach_relationships_statement(sub_resource_rel, other_rels),
|
|
375
400
|
)
|
|
376
401
|
return ingest_query
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def build_create_index_queries(node_schema: CartographyNodeSchema) -> List[str]:
|
|
405
|
+
"""
|
|
406
|
+
Generate queries to create indexes for the given CartographyNodeSchema and all node types attached to it via its
|
|
407
|
+
relationships.
|
|
408
|
+
:param node_schema: The Cartography node_schema object
|
|
409
|
+
:return: A list of queries of the form `CREATE INDEX IF NOT EXISTS FOR (n:$TargetNodeLabel) ON (n.$TargetAttribute)`
|
|
410
|
+
"""
|
|
411
|
+
index_template = Template('CREATE INDEX IF NOT EXISTS FOR (n:$TargetNodeLabel) ON (n.$TargetAttribute);')
|
|
412
|
+
|
|
413
|
+
# First ensure an index exists for the node_schema and all extra labels on the `id` and `lastupdated` fields
|
|
414
|
+
result = [
|
|
415
|
+
index_template.safe_substitute(
|
|
416
|
+
TargetNodeLabel=node_schema.label,
|
|
417
|
+
TargetAttribute='id',
|
|
418
|
+
),
|
|
419
|
+
index_template.safe_substitute(
|
|
420
|
+
TargetNodeLabel=node_schema.label,
|
|
421
|
+
TargetAttribute='lastupdated',
|
|
422
|
+
),
|
|
423
|
+
]
|
|
424
|
+
if node_schema.extra_node_labels:
|
|
425
|
+
result.extend([
|
|
426
|
+
index_template.safe_substitute(
|
|
427
|
+
TargetNodeLabel=label,
|
|
428
|
+
TargetAttribute='id', # Precondition: 'id' is defined on all cartography node_schema objects.
|
|
429
|
+
) for label in node_schema.extra_node_labels.labels
|
|
430
|
+
])
|
|
431
|
+
|
|
432
|
+
# Next, for all relationships possible out of this node, ensure that indexes exist for all target nodes' properties
|
|
433
|
+
# as specified in their TargetNodeMatchers.
|
|
434
|
+
rel_schemas = []
|
|
435
|
+
if node_schema.sub_resource_relationship:
|
|
436
|
+
rel_schemas.extend([node_schema.sub_resource_relationship])
|
|
437
|
+
if node_schema.other_relationships:
|
|
438
|
+
rel_schemas.extend(node_schema.other_relationships.rels)
|
|
439
|
+
for rs in rel_schemas:
|
|
440
|
+
for target_key in asdict(rs.target_node_matcher).keys():
|
|
441
|
+
result.append(
|
|
442
|
+
index_template.safe_substitute(TargetNodeLabel=rs.target_node_label, TargetAttribute=target_key),
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
# Now, include extra indexes defined by the module author on the node schema's property refs.
|
|
446
|
+
node_props_as_dict: Dict[str, PropertyRef] = asdict(node_schema.properties)
|
|
447
|
+
result.extend([
|
|
448
|
+
index_template.safe_substitute(
|
|
449
|
+
TargetNodeLabel=node_schema.label,
|
|
450
|
+
TargetAttribute=prop_name,
|
|
451
|
+
) for prop_name, prop_ref in node_props_as_dict.items() if prop_ref.extra_index
|
|
452
|
+
])
|
|
453
|
+
return result
|
cartography/graph/statement.py
CHANGED
|
@@ -4,6 +4,7 @@ import os
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
from typing import Any
|
|
6
6
|
from typing import Dict
|
|
7
|
+
from typing import Optional
|
|
7
8
|
from typing import Union
|
|
8
9
|
|
|
9
10
|
import neo4j
|
|
@@ -40,8 +41,13 @@ class GraphStatement:
|
|
|
40
41
|
"""
|
|
41
42
|
|
|
42
43
|
def __init__(
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
self,
|
|
45
|
+
query: str,
|
|
46
|
+
parameters: Optional[Dict[Any, Any]] = None,
|
|
47
|
+
iterative: bool = False,
|
|
48
|
+
iterationsize: int = 0,
|
|
49
|
+
parent_job_name: Optional[str] = None,
|
|
50
|
+
parent_job_sequence_num: Optional[int] = None,
|
|
45
51
|
):
|
|
46
52
|
self.query = query
|
|
47
53
|
self.parameters = parameters or {}
|
|
@@ -122,7 +128,12 @@ class GraphStatement:
|
|
|
122
128
|
result.consume()
|
|
123
129
|
|
|
124
130
|
@classmethod
|
|
125
|
-
def create_from_json(
|
|
131
|
+
def create_from_json(
|
|
132
|
+
cls,
|
|
133
|
+
json_obj: Dict[str, Any],
|
|
134
|
+
short_job_name: Optional[str] = None,
|
|
135
|
+
job_sequence_num: Optional[int] = None,
|
|
136
|
+
):
|
|
126
137
|
"""
|
|
127
138
|
Create a statement from a JSON blob.
|
|
128
139
|
"""
|
|
@@ -171,7 +171,7 @@ def _load_apigateway_policies(
|
|
|
171
171
|
def _set_default_values(neo4j_session: neo4j.Session, aws_account_id: str) -> None:
|
|
172
172
|
set_defaults = """
|
|
173
173
|
MATCH (:AWSAccount{id: $AWS_ID})-[:RESOURCE]->(restApi:APIGatewayRestAPI)
|
|
174
|
-
where
|
|
174
|
+
where restApi.anonymous_actions IS NULL
|
|
175
175
|
SET restApi.anonymous_access = false, restApi.anonymous_actions = []
|
|
176
176
|
"""
|
|
177
177
|
|
cartography/intel/aws/ecs.py
CHANGED
|
@@ -91,16 +91,16 @@ def get_ecs_services(cluster_arn: str, boto3_session: boto3.session.Session, reg
|
|
|
91
91
|
|
|
92
92
|
@timeit
|
|
93
93
|
@aws_handle_regions
|
|
94
|
-
def get_ecs_task_definitions(
|
|
94
|
+
def get_ecs_task_definitions(
|
|
95
|
+
boto3_session: boto3.session.Session,
|
|
96
|
+
region: str,
|
|
97
|
+
tasks: List[Dict[str, Any]],
|
|
98
|
+
) -> List[Dict[str, Any]]:
|
|
95
99
|
client = boto3_session.client('ecs', region_name=region)
|
|
96
|
-
paginator = client.get_paginator('list_task_definitions')
|
|
97
100
|
task_definitions: List[Dict[str, Any]] = []
|
|
98
|
-
|
|
99
|
-
for page in paginator.paginate():
|
|
100
|
-
task_definition_arns.extend(page.get('taskDefinitionArns', []))
|
|
101
|
-
for arn in task_definition_arns:
|
|
101
|
+
for task in tasks:
|
|
102
102
|
task_definition = client.describe_task_definition(
|
|
103
|
-
taskDefinition=
|
|
103
|
+
taskDefinition=task['taskDefinitionArn'],
|
|
104
104
|
)
|
|
105
105
|
task_definitions.append(task_definition['taskDefinition'])
|
|
106
106
|
return task_definitions
|
|
@@ -294,7 +294,8 @@ def load_ecs_task_definitions(
|
|
|
294
294
|
UNWIND $Definitions AS def
|
|
295
295
|
MERGE (d:ECSTaskDefinition{id: def.taskDefinitionArn})
|
|
296
296
|
ON CREATE SET d.firstseen = timestamp()
|
|
297
|
-
SET d.arn = def.taskDefinitionArn,
|
|
297
|
+
SET d.arn = def.taskDefinitionArn,
|
|
298
|
+
d.region = $Region,
|
|
298
299
|
d.family = def.family,
|
|
299
300
|
d.task_role_arn = def.taskRoleArn,
|
|
300
301
|
d.execution_role_arn = def.executionRoleArn,
|
|
@@ -317,6 +318,11 @@ def load_ecs_task_definitions(
|
|
|
317
318
|
d.ephemeral_storage_size_in_gib = def.ephemeralStorage.sizeInGiB,
|
|
318
319
|
d.lastupdated = $aws_update_tag
|
|
319
320
|
WITH d
|
|
321
|
+
MATCH (task:ECSTask{task_definition_arn: d.arn})
|
|
322
|
+
MERGE (task)-[r:HAS_TASK_DEFINITION]->(d)
|
|
323
|
+
ON CREATE SET r.firstseen = timestamp()
|
|
324
|
+
SET r.lastupdated = $aws_update_tag
|
|
325
|
+
WITH d
|
|
320
326
|
MATCH (owner:AWSAccount{id: $AWS_ACCOUNT_ID})
|
|
321
327
|
MERGE (owner)-[r:RESOURCE]->(d)
|
|
322
328
|
ON CREATE SET r.firstseen = timestamp()
|
|
@@ -565,17 +571,6 @@ def sync(
|
|
|
565
571
|
current_aws_account_id,
|
|
566
572
|
update_tag,
|
|
567
573
|
)
|
|
568
|
-
task_definitions = get_ecs_task_definitions(
|
|
569
|
-
boto3_session,
|
|
570
|
-
region,
|
|
571
|
-
)
|
|
572
|
-
load_ecs_task_definitions(
|
|
573
|
-
neo4j_session,
|
|
574
|
-
task_definitions,
|
|
575
|
-
region,
|
|
576
|
-
current_aws_account_id,
|
|
577
|
-
update_tag,
|
|
578
|
-
)
|
|
579
574
|
services = get_ecs_services(
|
|
580
575
|
cluster_arn,
|
|
581
576
|
boto3_session,
|
|
@@ -602,4 +597,16 @@ def sync(
|
|
|
602
597
|
current_aws_account_id,
|
|
603
598
|
update_tag,
|
|
604
599
|
)
|
|
600
|
+
task_definitions = get_ecs_task_definitions(
|
|
601
|
+
boto3_session,
|
|
602
|
+
region,
|
|
603
|
+
tasks,
|
|
604
|
+
)
|
|
605
|
+
load_ecs_task_definitions(
|
|
606
|
+
neo4j_session,
|
|
607
|
+
task_definitions,
|
|
608
|
+
region,
|
|
609
|
+
current_aws_account_id,
|
|
610
|
+
update_tag,
|
|
611
|
+
)
|
|
605
612
|
cleanup_ecs(neo4j_session, common_job_parameters)
|
cartography/intel/aws/emr.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import time
|
|
3
|
+
from typing import Any
|
|
3
4
|
from typing import Dict
|
|
4
5
|
from typing import List
|
|
5
6
|
|
|
@@ -7,9 +8,8 @@ import boto3
|
|
|
7
8
|
import botocore.exceptions
|
|
8
9
|
import neo4j
|
|
9
10
|
|
|
10
|
-
from cartography.client.core.tx import
|
|
11
|
+
from cartography.client.core.tx import load
|
|
11
12
|
from cartography.graph.job import GraphJob
|
|
12
|
-
from cartography.graph.querybuilder import build_ingestion_query
|
|
13
13
|
from cartography.intel.aws.ec2.util import get_botocore_config
|
|
14
14
|
from cartography.models.aws.emr import EMRClusterSchema
|
|
15
15
|
from cartography.util import aws_handle_regions
|
|
@@ -24,9 +24,9 @@ DESCRIBE_SLEEP = 1
|
|
|
24
24
|
|
|
25
25
|
@timeit
|
|
26
26
|
@aws_handle_regions
|
|
27
|
-
def get_emr_clusters(boto3_session: boto3.session.Session, region: str) -> List[Dict]:
|
|
27
|
+
def get_emr_clusters(boto3_session: boto3.session.Session, region: str) -> List[Dict[str, Any]]:
|
|
28
28
|
client = boto3_session.client('emr', region_name=region, config=get_botocore_config())
|
|
29
|
-
clusters: List[Dict] = []
|
|
29
|
+
clusters: List[Dict[str, Any]] = []
|
|
30
30
|
paginator = client.get_paginator('list_clusters')
|
|
31
31
|
for page in paginator.paginate():
|
|
32
32
|
cluster = page['Clusters']
|
|
@@ -36,36 +36,31 @@ def get_emr_clusters(boto3_session: boto3.session.Session, region: str) -> List[
|
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
@timeit
|
|
39
|
-
def get_emr_describe_cluster(boto3_session: boto3.session.Session, region: str, cluster_id: str) -> Dict:
|
|
39
|
+
def get_emr_describe_cluster(boto3_session: boto3.session.Session, region: str, cluster_id: str) -> Dict[str, Any]:
|
|
40
40
|
client = boto3_session.client('emr', region_name=region, config=get_botocore_config())
|
|
41
|
-
cluster_details: Dict = {}
|
|
41
|
+
cluster_details: Dict[str, Any] = {}
|
|
42
42
|
try:
|
|
43
43
|
response = client.describe_cluster(ClusterId=cluster_id)
|
|
44
44
|
cluster_details = response['Cluster']
|
|
45
45
|
except botocore.exceptions.ClientError as e:
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
e.response['Error']['Message'],
|
|
50
|
-
)
|
|
46
|
+
code = e.response['Error']['Code']
|
|
47
|
+
msg = e.response['Error']['Message']
|
|
48
|
+
logger.warning(f"Could not run EMR describe_cluster due to boto3 error {code}: {msg}. Skipping.")
|
|
51
49
|
return cluster_details
|
|
52
50
|
|
|
53
51
|
|
|
54
52
|
@timeit
|
|
55
53
|
def load_emr_clusters(
|
|
56
54
|
neo4j_session: neo4j.Session,
|
|
57
|
-
cluster_data: List[Dict],
|
|
55
|
+
cluster_data: List[Dict[str, Any]],
|
|
58
56
|
region: str,
|
|
59
57
|
current_aws_account_id: str,
|
|
60
58
|
aws_update_tag: int,
|
|
61
59
|
) -> None:
|
|
62
|
-
logger.info("Loading EMR
|
|
63
|
-
|
|
64
|
-
ingestion_query = build_ingestion_query(EMRClusterSchema())
|
|
65
|
-
|
|
66
|
-
load_graph_data(
|
|
60
|
+
logger.info(f"Loading EMR {len(cluster_data)} clusters for region '{region}' into graph.")
|
|
61
|
+
load(
|
|
67
62
|
neo4j_session,
|
|
68
|
-
|
|
63
|
+
EMRClusterSchema(),
|
|
69
64
|
cluster_data,
|
|
70
65
|
lastupdated=aws_update_tag,
|
|
71
66
|
Region=region,
|
|
@@ -74,7 +69,7 @@ def load_emr_clusters(
|
|
|
74
69
|
|
|
75
70
|
|
|
76
71
|
@timeit
|
|
77
|
-
def cleanup(neo4j_session: neo4j.Session, common_job_parameters: Dict) -> None:
|
|
72
|
+
def cleanup(neo4j_session: neo4j.Session, common_job_parameters: Dict[str, Any]) -> None:
|
|
78
73
|
logger.debug("Running EMR cleanup job.")
|
|
79
74
|
cleanup_job = GraphJob.from_node_schema(EMRClusterSchema(), common_job_parameters)
|
|
80
75
|
cleanup_job.run(neo4j_session)
|
|
@@ -83,14 +78,14 @@ def cleanup(neo4j_session: neo4j.Session, common_job_parameters: Dict) -> None:
|
|
|
83
78
|
@timeit
|
|
84
79
|
def sync(
|
|
85
80
|
neo4j_session: neo4j.Session, boto3_session: boto3.session.Session, regions: List[str], current_aws_account_id: str,
|
|
86
|
-
update_tag: int, common_job_parameters: Dict,
|
|
81
|
+
update_tag: int, common_job_parameters: Dict[str, Any],
|
|
87
82
|
) -> None:
|
|
88
83
|
for region in regions:
|
|
89
|
-
logger.info("Syncing EMR for region '
|
|
84
|
+
logger.info(f"Syncing EMR for region '{region}' in account '{current_aws_account_id}'.")
|
|
90
85
|
|
|
91
86
|
clusters = get_emr_clusters(boto3_session, region)
|
|
92
87
|
|
|
93
|
-
cluster_data: List[Dict] = []
|
|
88
|
+
cluster_data: List[Dict[str, Any]] = []
|
|
94
89
|
for cluster in clusters:
|
|
95
90
|
cluster_id = cluster['Id']
|
|
96
91
|
cluster_details = get_emr_describe_cluster(boto3_session, region, cluster_id)
|
cartography/intel/aws/iam.py
CHANGED
|
@@ -309,11 +309,15 @@ def load_roles(
|
|
|
309
309
|
neo4j_session: neo4j.Session, roles: List[Dict], current_aws_account_id: str, aws_update_tag: int,
|
|
310
310
|
) -> None:
|
|
311
311
|
ingest_role = """
|
|
312
|
-
MERGE (rnode:
|
|
313
|
-
ON CREATE SET rnode
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
312
|
+
MERGE (rnode:AWSPrincipal{arn: $Arn})
|
|
313
|
+
ON CREATE SET rnode.firstseen = timestamp()
|
|
314
|
+
SET
|
|
315
|
+
rnode:AWSRole,
|
|
316
|
+
rnode.roleid = $RoleId,
|
|
317
|
+
rnode.createdate = $CreateDate,
|
|
318
|
+
rnode.name = $RoleName,
|
|
319
|
+
rnode.path = $Path,
|
|
320
|
+
rnode.lastupdated = $aws_update_tag
|
|
317
321
|
WITH rnode
|
|
318
322
|
MATCH (aa:AWSAccount{id: $AWS_ACCOUNT_ID})
|
|
319
323
|
MERGE (aa)-[r:RESOURCE]->(rnode)
|
|
@@ -540,11 +544,12 @@ def transform_policy_data(policy_map: Dict, policy_type: str) -> None:
|
|
|
540
544
|
for principal_arn, policy_statement_map in policy_map.items():
|
|
541
545
|
logger.debug(f"Transforming IAM {policy_type} policies for principal {principal_arn}")
|
|
542
546
|
for policy_key, statements in policy_statement_map.items():
|
|
543
|
-
policy_id = transform_policy_id(
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
)
|
|
547
|
+
policy_id = transform_policy_id(
|
|
548
|
+
principal_arn,
|
|
549
|
+
policy_type,
|
|
550
|
+
policy_key,
|
|
551
|
+
) if policy_type == PolicyType.inline.value else policy_key
|
|
552
|
+
policy_statement_map[policy_key] = _transform_policy_statements(statements, policy_id)
|
|
548
553
|
|
|
549
554
|
|
|
550
555
|
def transform_policy_id(principal_arn: str, policy_type: str, name: str) -> str:
|
cartography/intel/aws/kms.py
CHANGED
|
@@ -189,7 +189,7 @@ def _load_kms_key_policies(neo4j_session: neo4j.Session, policies: List[Dict], u
|
|
|
189
189
|
|
|
190
190
|
def _set_default_values(neo4j_session: neo4j.Session, aws_account_id: str) -> None:
|
|
191
191
|
set_defaults = """
|
|
192
|
-
MATCH (:AWSAccount{id: $AWS_ID})-[:RESOURCE]->(kmskey:KMSKey) where
|
|
192
|
+
MATCH (:AWSAccount{id: $AWS_ID})-[:RESOURCE]->(kmskey:KMSKey) where kmskey.anonymous_actions IS NULL
|
|
193
193
|
SET kmskey.anonymous_access = false, kmskey.anonymous_actions = []
|
|
194
194
|
"""
|
|
195
195
|
|
cartography/intel/aws/rds.py
CHANGED
cartography/intel/aws/s3.py
CHANGED
|
@@ -345,7 +345,7 @@ def _load_s3_public_access_block(
|
|
|
345
345
|
MATCH (s:S3Bucket) where s.name = public_access_block.bucket
|
|
346
346
|
SET s.block_public_acls = public_access_block.block_public_acls,
|
|
347
347
|
s.ignore_public_acls = public_access_block.ignore_public_acls,
|
|
348
|
-
s.
|
|
348
|
+
s.block_public_policy = public_access_block.block_public_policy,
|
|
349
349
|
s.restrict_public_buckets = public_access_block.restrict_public_buckets,
|
|
350
350
|
s.lastupdated = $UpdateTag
|
|
351
351
|
"""
|
|
@@ -359,7 +359,7 @@ def _load_s3_public_access_block(
|
|
|
359
359
|
|
|
360
360
|
def _set_default_values(neo4j_session: neo4j.Session, aws_account_id: str) -> None:
|
|
361
361
|
set_defaults = """
|
|
362
|
-
MATCH (:AWSAccount{id: $AWS_ID})-[:RESOURCE]->(s:S3Bucket) where
|
|
362
|
+
MATCH (:AWSAccount{id: $AWS_ID})-[:RESOURCE]->(s:S3Bucket) where s.anonymous_actions IS NULL
|
|
363
363
|
SET s.anonymous_access = false, s.anonymous_actions = []
|
|
364
364
|
"""
|
|
365
365
|
neo4j_session.run(
|
|
@@ -368,7 +368,7 @@ def _set_default_values(neo4j_session: neo4j.Session, aws_account_id: str) -> No
|
|
|
368
368
|
)
|
|
369
369
|
|
|
370
370
|
set_encryption_defaults = """
|
|
371
|
-
MATCH (:AWSAccount{id: $AWS_ID})-[:RESOURCE]->(s:S3Bucket) where
|
|
371
|
+
MATCH (:AWSAccount{id: $AWS_ID})-[:RESOURCE]->(s:S3Bucket) where s.default_encryption IS NULL
|
|
372
372
|
SET s.default_encryption = false
|
|
373
373
|
"""
|
|
374
374
|
neo4j_session.run(
|
|
@@ -19,8 +19,13 @@ AUTHORITY_HOST_URI = 'https://login.microsoftonline.com'
|
|
|
19
19
|
class Credentials:
|
|
20
20
|
|
|
21
21
|
def __init__(
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
self,
|
|
23
|
+
arm_credentials: Any,
|
|
24
|
+
aad_graph_credentials: Any,
|
|
25
|
+
tenant_id: Optional[str] = None,
|
|
26
|
+
subscription_id: Optional[str] = None,
|
|
27
|
+
context: Optional[adal.AuthenticationContext] = None,
|
|
28
|
+
current_user: Optional[str] = None,
|
|
24
29
|
) -> None:
|
|
25
30
|
self.arm_credentials = arm_credentials # Azure Resource Manager API credentials
|
|
26
31
|
self.aad_graph_credentials = aad_graph_credentials # Azure AD Graph API credentials
|
|
@@ -129,7 +134,12 @@ class Authenticator:
|
|
|
129
134
|
|
|
130
135
|
raise e
|
|
131
136
|
|
|
132
|
-
def authenticate_sp(
|
|
137
|
+
def authenticate_sp(
|
|
138
|
+
self,
|
|
139
|
+
tenant_id: Optional[str] = None,
|
|
140
|
+
client_id: Optional[str] = None,
|
|
141
|
+
client_secret: Optional[str] = None,
|
|
142
|
+
) -> Credentials:
|
|
133
143
|
"""
|
|
134
144
|
Implements authentication for the Azure provider
|
|
135
145
|
"""
|
cartography/intel/oci/iam.py
CHANGED
|
@@ -316,7 +316,7 @@ def sync_policies(
|
|
|
316
316
|
"Syncing OCI policies for compartment '%s' in account '%s'.", compartment['ocid'], current_tenancy_id,
|
|
317
317
|
)
|
|
318
318
|
data = get_policy_list_data(iam, compartment["ocid"])
|
|
319
|
-
if(data["Policies"]):
|
|
319
|
+
if (data["Policies"]):
|
|
320
320
|
load_policies(neo4j_session, data["Policies"], current_tenancy_id, oci_update_tag)
|
|
321
321
|
run_cleanup_job('oci_import_policies_cleanup.json', neo4j_session, common_job_parameters)
|
|
322
322
|
|
cartography/models/aws/emr.py
CHANGED
|
@@ -12,7 +12,7 @@ from cartography.models.core.relationships import TargetNodeMatcher
|
|
|
12
12
|
|
|
13
13
|
@dataclass(frozen=True)
|
|
14
14
|
class EMRClusterNodeProperties(CartographyNodeProperties):
|
|
15
|
-
arn: PropertyRef = PropertyRef('ClusterArn')
|
|
15
|
+
arn: PropertyRef = PropertyRef('ClusterArn', extra_index=True)
|
|
16
16
|
auto_terminate: PropertyRef = PropertyRef('AutoTerminate')
|
|
17
17
|
autoscaling_role: PropertyRef = PropertyRef('AutoScalingRole')
|
|
18
18
|
custom_ami_id: PropertyRef = PropertyRef('CustomAmiId')
|
|
@@ -8,16 +8,36 @@ class PropertyRef:
|
|
|
8
8
|
(PropertyRef.set_in_kwargs=True).
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
-
def __init__(self, name: str, set_in_kwargs=False):
|
|
11
|
+
def __init__(self, name: str, set_in_kwargs=False, extra_index=False, ignore_case=False):
|
|
12
12
|
"""
|
|
13
13
|
:param name: The name of the property
|
|
14
14
|
:param set_in_kwargs: Optional. If True, the property is not defined on the data dict, and we expect to find the
|
|
15
15
|
property in the kwargs.
|
|
16
16
|
If False, looks for the property in the data dict.
|
|
17
17
|
Defaults to False.
|
|
18
|
+
:param extra_index: If True, make sure that we create an index for this property name.
|
|
19
|
+
Notes:
|
|
20
|
+
- extra_index is available for the case where you anticipate a property will be queried frequently.
|
|
21
|
+
- The `id` and `lastupdated` properties will always have indexes created for them automatically by
|
|
22
|
+
`ensure_indexes()`.
|
|
23
|
+
- All properties included in target node matchers will always have indexes created for them.
|
|
24
|
+
Defaults to False.
|
|
25
|
+
:param ignore_case: If True, performs a case-insensitive match when comparing the value of this property during
|
|
26
|
+
relationship creation. Defaults to False. This only has effect as part of a TargetNodeMatcher, and this is not
|
|
27
|
+
supported for the sub resource relationship.
|
|
28
|
+
Example on why you would set this to True:
|
|
29
|
+
GitHub usernames can have both uppercase and lowercase characters, but GitHub itself treats usernames as
|
|
30
|
+
case-insensitive. Suppose your company's internal personnel database stores GitHub usernames all as
|
|
31
|
+
lowercase. If you wanted to map your company's employees to their GitHub identities, you would need to
|
|
32
|
+
perform a case-insensitive match between your company's record of a user's GitHub username and your
|
|
33
|
+
cartography catalog of GitHubUser nodes. Therefore, you would need `ignore_case=True` in the PropertyRef
|
|
34
|
+
that points to the GitHubUser node's name field, otherwise if one of your employees' GitHub usernames
|
|
35
|
+
contains capital letters, you would not be able to map them properly to a GitHubUser node in your graph.
|
|
18
36
|
"""
|
|
19
37
|
self.name = name
|
|
20
38
|
self.set_in_kwargs = set_in_kwargs
|
|
39
|
+
self.extra_index = extra_index
|
|
40
|
+
self.ignore_case = ignore_case
|
|
21
41
|
|
|
22
42
|
def _parameterize_name(self) -> str:
|
|
23
43
|
return f"${self.name}"
|
|
@@ -10,27 +10,27 @@ cartography/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuF
|
|
|
10
10
|
cartography/client/aws/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
11
|
cartography/client/aws/iam.py,sha256=dYsGikc36DEsSeR2XVOVFFUDwuU9yWj_EVkpgVYCFgM,1293
|
|
12
12
|
cartography/client/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
-
cartography/client/core/tx.py,sha256=
|
|
13
|
+
cartography/client/core/tx.py,sha256=4_kTBxrtlwsOM-e8Xtjf7wmmzwZ-DGRJL0rPFp0Xj0Q,10805
|
|
14
14
|
cartography/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
-
cartography/data/indexes.cypher,sha256=
|
|
15
|
+
cartography/data/indexes.cypher,sha256=7whG4zv9GpDwqhf46w6NN3RrJgRiaVj9es_2sMOegrM,30253
|
|
16
16
|
cartography/data/permission_relationships.yaml,sha256=RuKGGc_3ZUQ7ag0MssB8k_zaonCkVM5E8I_svBWTmGc,969
|
|
17
17
|
cartography/data/jobs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
18
|
cartography/data/jobs/analysis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
|
-
cartography/data/jobs/analysis/aws_ec2_asset_exposure.json,sha256=
|
|
19
|
+
cartography/data/jobs/analysis/aws_ec2_asset_exposure.json,sha256=PRM7TJwO3gLkGtez-hg5v5avDkuAcF-bQQCK6stecLg,2726
|
|
20
20
|
cartography/data/jobs/analysis/aws_ec2_iaminstance.json,sha256=98wfe8pjwS-iQIjUpM1rxuASdxk7BGl9ooigrwfpmvU,500
|
|
21
21
|
cartography/data/jobs/analysis/aws_ec2_iaminstanceprofile.json,sha256=7M8k0p0SvSYVzCk358FnUQ865IX25TyumtHTXqZS-t8,526
|
|
22
|
-
cartography/data/jobs/analysis/aws_ec2_keypair_analysis.json,sha256=
|
|
23
|
-
cartography/data/jobs/analysis/aws_eks_asset_exposure.json,sha256=
|
|
24
|
-
cartography/data/jobs/analysis/aws_foreign_accounts.json,sha256=
|
|
22
|
+
cartography/data/jobs/analysis/aws_ec2_keypair_analysis.json,sha256=_ZBczunZbx5NHAbfVc4v6vJsbyl1xaePLzj4cyxW-4A,1741
|
|
23
|
+
cartography/data/jobs/analysis/aws_eks_asset_exposure.json,sha256=Z6z4YTNJJqUJl2JqONeAYAvfH_2A9qEBxkFn-aou8D8,561
|
|
24
|
+
cartography/data/jobs/analysis/aws_foreign_accounts.json,sha256=b8Li_KQLwIvNXxWt0F1bVTV1Vg9dxsbr7ZE8LH2-woc,686
|
|
25
25
|
cartography/data/jobs/analysis/aws_lambda_ecr.json,sha256=wM10Gn0HoNP-sOj3S_Sjqh4mLsh-f2QkonkFuOohs_U,641
|
|
26
26
|
cartography/data/jobs/analysis/aws_s3acl_analysis.json,sha256=EDf1VhDoP7khhGf1QTI9GIsp08foyPL4wE9mB9JQDH4,2493
|
|
27
|
-
cartography/data/jobs/analysis/gcp_compute_asset_inet_exposure.json,sha256=
|
|
28
|
-
cartography/data/jobs/analysis/gcp_gke_asset_exposure.json,sha256=
|
|
29
|
-
cartography/data/jobs/analysis/gcp_gke_basic_auth.json,sha256=
|
|
27
|
+
cartography/data/jobs/analysis/gcp_compute_asset_inet_exposure.json,sha256=lIurpVOAHZ3u_pfpRhcC5zprxXKqOWG4miGIkMeCfcE,4695
|
|
28
|
+
cartography/data/jobs/analysis/gcp_gke_asset_exposure.json,sha256=7SJc9TeeIWFMDmHOWgmjgaIzjmCClLjJTPS4bGlaEF0,643
|
|
29
|
+
cartography/data/jobs/analysis/gcp_gke_basic_auth.json,sha256=qLkrw1eZvV9ETtkIQN3v9hXnYN3ujAMyfIpqUj5YGo8,681
|
|
30
30
|
cartography/data/jobs/analysis/gsuite_human_link.json,sha256=ArWA51fNIeeXwYiroJbKnuqN8y-TLrndOq0ZdC9q5os,541
|
|
31
31
|
cartography/data/jobs/cleanup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
32
32
|
cartography/data/jobs/cleanup/aws_account_cleanup.json,sha256=DEB4h6Z4NSpYaLXECPhNk8HHbT0XNlqUA01pXIskBxc,473
|
|
33
|
-
cartography/data/jobs/cleanup/aws_apigateway_details.json,sha256=
|
|
33
|
+
cartography/data/jobs/cleanup/aws_apigateway_details.json,sha256=rh7cMTebjyUoEu_s7nKl3m-GLQ3gXgw0s3kMJfWKjro,323
|
|
34
34
|
cartography/data/jobs/cleanup/aws_dns_cleanup.json,sha256=H5uMZV4tN6HcjTYwh2I1dENzNGqu2D2Rs2q-5CK5e0Y,3498
|
|
35
35
|
cartography/data/jobs/cleanup/aws_import_account_access_key_cleanup.json,sha256=dZBpz_yY1M_XpEueAjFq14rf2HMu0paUibsaXrWlF3w,305
|
|
36
36
|
cartography/data/jobs/cleanup/aws_import_apigateway_cleanup.json,sha256=wCV95ydo3dmlhK7VrDrxCqrP6dbhCCMTzcz_qaJQ4Jo,2189
|
|
@@ -81,9 +81,9 @@ cartography/data/jobs/cleanup/aws_ingest_load_balancers_cleanup.json,sha256=1vid
|
|
|
81
81
|
cartography/data/jobs/cleanup/aws_ingest_load_balancers_v2_cleanup.json,sha256=kIMc5YTkWDOLdHCOAHPb7Q8Oz9pP6GzGO9gjBo0ZhR0,1596
|
|
82
82
|
cartography/data/jobs/cleanup/aws_ingest_network_interfaces_cleanup.json,sha256=Qkx0VhiEwUqc_6Jq6U5ov7VEPximGy1y3MxsZZjIoog,2241
|
|
83
83
|
cartography/data/jobs/cleanup/aws_ingest_subnets_cleanup.json,sha256=HtTXrA50PzBJ6LFnKZkO4hnnKxKqF6xUwqUrtFrp29Y,527
|
|
84
|
-
cartography/data/jobs/cleanup/aws_kms_details.json,sha256=
|
|
84
|
+
cartography/data/jobs/cleanup/aws_kms_details.json,sha256=awluz0DhiAcdABiTYL9M8EUQz2FAuXVpu8lICRpTEnU,319
|
|
85
85
|
cartography/data/jobs/cleanup/aws_post_ingestion_principals_cleanup.json,sha256=h3A6jNk7DGLFq6ob06-V9qzuYN-CltyHq_r2Gj8DqCI,266
|
|
86
|
-
cartography/data/jobs/cleanup/aws_s3_details.json,sha256
|
|
86
|
+
cartography/data/jobs/cleanup/aws_s3_details.json,sha256=-GEnBicRjLBNEDS2k2MzE2PUFbQ5WmxXUbYeVTvlJzQ,316
|
|
87
87
|
cartography/data/jobs/cleanup/azure_cosmosdb_cassandra_keyspace_cleanup.json,sha256=_WIFvR-OGvG2q163NKsNXPqbDATz7rv7FLUTlWtnvK4,1358
|
|
88
88
|
cartography/data/jobs/cleanup/azure_cosmosdb_cors_details.json,sha256=meF4119r0TzUwiU0ecygJRc2ZikSFJ9SSCKiayEXRCU,662
|
|
89
89
|
cartography/data/jobs/cleanup/azure_cosmosdb_mongodb_database_cleanup.json,sha256=7PLCkq5HWvQ43ct3dZ0biUaOu7NcYPAMP5WAFpyhSlw,1354
|
|
@@ -141,37 +141,37 @@ cartography/driftdetect/shortcut.py,sha256=0AvX9vZGNRWeLiRqxU7eOOo77gcfufHx4IHZv
|
|
|
141
141
|
cartography/driftdetect/storage.py,sha256=6ziscwJh34_1ylrSAK6B-U6o09xDhIa7Yb-V1kisveQ,1549
|
|
142
142
|
cartography/driftdetect/util.py,sha256=Lqxv8QoFn3_3Fz18qCOjkjJ6yBwgrHjrxXmArBAEdkc,929
|
|
143
143
|
cartography/graph/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
144
|
-
cartography/graph/cleanupbuilder.py,sha256=
|
|
144
|
+
cartography/graph/cleanupbuilder.py,sha256=Kj6vNQYGlwrNuz2bExgtZzeXMBLoasHNTgqrRiALPgM,7760
|
|
145
145
|
cartography/graph/context.py,sha256=RGxGb8EnxowcqjR0nFF86baNhgRHeUF9wjIoFUoG8LU,1230
|
|
146
|
-
cartography/graph/job.py,sha256=
|
|
147
|
-
cartography/graph/querybuilder.py,sha256=
|
|
148
|
-
cartography/graph/statement.py,sha256=
|
|
146
|
+
cartography/graph/job.py,sha256=VBKc0VLbDz1zm5jslF49nbPbQS7DkdQwfPG7rdLSc1w,7288
|
|
147
|
+
cartography/graph/querybuilder.py,sha256=MMXzUEg4td-YmHMNM97KAqDZ6-1wNClO2jmJoG47BTY,20108
|
|
148
|
+
cartography/graph/statement.py,sha256=VsqG46ty_Mm87fr8YdIwfr6a82OUXU7yZe6S-Py9hZg,5345
|
|
149
149
|
cartography/intel/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
150
150
|
cartography/intel/analysis.py,sha256=gHtN42NqqLL1G5MOm2Q6rMyg-V5lU_wqbnKp5hbOOao,1499
|
|
151
151
|
cartography/intel/create_indexes.py,sha256=HM2jB2v3NZvGqgVDtNoBQRVpkei_JXcYXqM14w8Rjss,741
|
|
152
152
|
cartography/intel/dns.py,sha256=M-WSiGQoxWZsl0sg-2SDR8OD8e1Rexkt2Tbb2OpeioA,5596
|
|
153
153
|
cartography/intel/aws/__init__.py,sha256=q98Jaw0nNI6QKUcp36ZuMs9C-ISlfo8OoPYTVoRf9eY,10295
|
|
154
|
-
cartography/intel/aws/apigateway.py,sha256=
|
|
154
|
+
cartography/intel/aws/apigateway.py,sha256=se0plc9ZnkGQAEtlfqgrJGTa2JeWljvwC3E3JFM7YbI,12764
|
|
155
155
|
cartography/intel/aws/config.py,sha256=wrZbz7bc8vImLmRvTLkPcWnjjPzk3tOG4bB_BFS2lq8,7329
|
|
156
156
|
cartography/intel/aws/dynamodb.py,sha256=OrFHKNrYDJzyMjkgFW4K4suy01OCP9E3Yqu_rnlsYXw,5106
|
|
157
157
|
cartography/intel/aws/ecr.py,sha256=xTYnefMyt4_dr903rBPvknVoH4S7NTrNe0WWEJHfEuo,5988
|
|
158
|
-
cartography/intel/aws/ecs.py,sha256=
|
|
158
|
+
cartography/intel/aws/ecs.py,sha256=gulrIZ--iEFLpkkPH58MJIkctsxWeWdO2ofM9amDNZA,23654
|
|
159
159
|
cartography/intel/aws/eks.py,sha256=6ey28ifnPvGMReVvFczBWBwBBSyrUWpuBiv8-NahNwY,4118
|
|
160
160
|
cartography/intel/aws/elasticache.py,sha256=fCI47aDFmIDyE26GiReKYb6XIZUwrzcvsXBQ4ruFhuI,4427
|
|
161
161
|
cartography/intel/aws/elasticsearch.py,sha256=ZL7MkXF_bXRSoXuDSI1dwGckRLG2zDB8LuAD07vSLnE,8374
|
|
162
|
-
cartography/intel/aws/emr.py,sha256=
|
|
163
|
-
cartography/intel/aws/iam.py,sha256=
|
|
162
|
+
cartography/intel/aws/emr.py,sha256=xhWBVZngxJRFjMEDxwq3G6SgytRGLq0v2a_CeDvByR0,3372
|
|
163
|
+
cartography/intel/aws/iam.py,sha256=GhiRzsw0S87fdLDMVPdnhuFNArQW6C22VJM4DP5_wCw,31425
|
|
164
164
|
cartography/intel/aws/inspector.py,sha256=mL57NQrpOMF0pvbqtxGIn_NG6-yM5D0opoXoxyhMR1Y,12491
|
|
165
|
-
cartography/intel/aws/kms.py,sha256=
|
|
165
|
+
cartography/intel/aws/kms.py,sha256=bZUzMxAH_DsAcGTJBs08gg2tLKYu-QWjvMvV9C-6v50,11731
|
|
166
166
|
cartography/intel/aws/lambda_function.py,sha256=KKTyn53xpaMI9WvIqxmsOASFwflHt-2_5ow-zUFc2wg,9890
|
|
167
167
|
cartography/intel/aws/organizations.py,sha256=HaQZ3J5XF15BuykuDypqFORDYpnoHuRRr4DuceewH4s,4485
|
|
168
168
|
cartography/intel/aws/permission_relationships.py,sha256=PhOnag0a1gZHtUg82546MKhj-8IcGJ7wLbvPASUBXlg,14792
|
|
169
|
-
cartography/intel/aws/rds.py,sha256=
|
|
169
|
+
cartography/intel/aws/rds.py,sha256=vnlNYmrO2Cc0PNn31CeG2QwYhwjVosbQFE9Ol1vQyLE,25252
|
|
170
170
|
cartography/intel/aws/redshift.py,sha256=KOqiXIllHmtPTeaNGl-cX4srY5pFE6o12j8MQ5-zWpc,6694
|
|
171
171
|
cartography/intel/aws/resourcegroupstaggingapi.py,sha256=aq4kPF6t8QZZoTxdkQVLXH65Di41CDJVM9llJNe6iaY,10278
|
|
172
172
|
cartography/intel/aws/resources.py,sha256=exmPQXk9V75ubwgzL7sksVI9mKIdfEbNSSXGW206fvg,3181
|
|
173
173
|
cartography/intel/aws/route53.py,sha256=IYqeQud1HuHnf11A7T-Jeif5DWgjpaaU-Jfr2cLUc_o,14099
|
|
174
|
-
cartography/intel/aws/s3.py,sha256=
|
|
174
|
+
cartography/intel/aws/s3.py,sha256=6hBtQKrclOpEmKQx0fYQHfjCjssLz2qZQbfB-tpHPeI,26381
|
|
175
175
|
cartography/intel/aws/secretsmanager.py,sha256=zhgQS-Js8Co_9UIothN-uKQvg8oqLE3kdgoQRvDDUVM,3272
|
|
176
176
|
cartography/intel/aws/securityhub.py,sha256=8FF7vW0ykdqn07xGExtsOLxYTyCTTbDiRuA1pxiRNlM,2266
|
|
177
177
|
cartography/intel/aws/sqs.py,sha256=cosScBKxAm_6GsM9zzg4U12KvAjXUzxpJ1zGv0lsVZI,6199
|
|
@@ -206,7 +206,7 @@ cartography/intel/azure/storage.py,sha256=56QyHafWTNproCOjITyAaWsmsqKhyFwJQlVEfw
|
|
|
206
206
|
cartography/intel/azure/subscription.py,sha256=aEyCYYWD4tFG5hBu_na7B4bohzlgiRr2aQ2B1YJ5bWw,3409
|
|
207
207
|
cartography/intel/azure/tenant.py,sha256=7Tz85e2isPRsq5krScQLdh_dYubM_pzOtj_sRx-nyDE,1454
|
|
208
208
|
cartography/intel/azure/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
209
|
-
cartography/intel/azure/util/credentials.py,sha256=
|
|
209
|
+
cartography/intel/azure/util/credentials.py,sha256=99PjTs0vZ2iu0tHD7TohN1VJYjuXYstfMg27F4CE0xU,7416
|
|
210
210
|
cartography/intel/crowdstrike/__init__.py,sha256=dAtgI-0vZAQZ3cTFQhMEzzt7aqiNSNuiIYiw0qbut-I,1896
|
|
211
211
|
cartography/intel/crowdstrike/endpoints.py,sha256=o45309ZDNdlYdG0YicIiBain-nkr3bQahBaRx3N9Mow,3738
|
|
212
212
|
cartography/intel/crowdstrike/spotlight.py,sha256=yNhj44-RYF6ubck-hHMKhKiNU0fCfhQf4Oagopc31EM,4754
|
|
@@ -241,7 +241,7 @@ cartography/intel/kubernetes/secrets.py,sha256=oaX90O4SFfrqObEzNaXCREiz98I5Y7tMG
|
|
|
241
241
|
cartography/intel/kubernetes/services.py,sha256=kzx8QoB_p5q6syAl7SZscGxubYEdWDdmWu5-ZZUg7YM,3516
|
|
242
242
|
cartography/intel/kubernetes/util.py,sha256=pal5MyGrPjShqml_wdEIM8h7M2hMacVM5FA39wg2Dvs,1512
|
|
243
243
|
cartography/intel/oci/__init__.py,sha256=AZmRX6EO4LUnynDtIKHxtZ_Ab2-CYPPc2u5d0Q2S3S4,7926
|
|
244
|
-
cartography/intel/oci/iam.py,sha256=
|
|
244
|
+
cartography/intel/oci/iam.py,sha256=zPrJeoMoO3ZkjBfWbTttjrcUvxxMuWquLTmsDH5MgOI,17712
|
|
245
245
|
cartography/intel/oci/organizations.py,sha256=tzQkZfE4LPoS-6lXBRQGyhq8aJLZUJ1_q75Q9eTBke0,4086
|
|
246
246
|
cartography/intel/oci/utils.py,sha256=UbX9jib4sWEdKeAt2CeCo4k9shUiWY08oTfQz_nDvjA,3223
|
|
247
247
|
cartography/intel/okta/__init__.py,sha256=HYw9wlE27dHJ2fwSlHgbJyHcxhdFzbYWBcZdQ6bqfIo,3813
|
|
@@ -264,15 +264,15 @@ cartography/intel/pagerduty/users.py,sha256=oltGssxrnzYsV6QTGP1SsPoA1rCUDStj6vGl
|
|
|
264
264
|
cartography/intel/pagerduty/vendors.py,sha256=WlDHExrWRBegDQKtxBV5nJiYgwoTLxNee4HrQDJ-Pdg,1559
|
|
265
265
|
cartography/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
266
266
|
cartography/models/aws/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
267
|
-
cartography/models/aws/emr.py,sha256=
|
|
267
|
+
cartography/models/aws/emr.py,sha256=TkuwoZnw_VHbJ5bwkac7-ZfwSLe_TeK3gxkuwGQOUk4,3037
|
|
268
268
|
cartography/models/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
269
|
-
cartography/models/core/common.py,sha256=
|
|
269
|
+
cartography/models/core/common.py,sha256=twXdP5gprpmDJYpBmSYL5GWaWDu1Xn-0EVzFsRJ42QQ,3583
|
|
270
270
|
cartography/models/core/nodes.py,sha256=h5dwBOk_a2uCHZWeQz3pidr7gkqMKf7buIZgl6M1Ox4,3699
|
|
271
271
|
cartography/models/core/relationships.py,sha256=6AwXvk0dq48BxqyxBpHyBXZ3dJNm65t1y4vNg4n25uA,5103
|
|
272
|
-
cartography-0.
|
|
273
|
-
cartography-0.
|
|
274
|
-
cartography-0.
|
|
275
|
-
cartography-0.
|
|
276
|
-
cartography-0.
|
|
277
|
-
cartography-0.
|
|
278
|
-
cartography-0.
|
|
272
|
+
cartography-0.75.0.dist-info/LICENSE,sha256=489ZXeW9G90up6ep-D1n-lJgk9ciNT2yxXpFgRSidtk,11341
|
|
273
|
+
cartography-0.75.0.dist-info/METADATA,sha256=Wpv0TT2-zbALE2LE5ds3jjWo9rFs65VrfckhnWMHC9s,1988
|
|
274
|
+
cartography-0.75.0.dist-info/NOTICE,sha256=YOGAsjFtbyKj5tslYIg6V5jEYRuEvnSsIuDOUKj0Qj4,97
|
|
275
|
+
cartography-0.75.0.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
|
|
276
|
+
cartography-0.75.0.dist-info/entry_points.txt,sha256=GVIAWD0o0_K077qMA_k1oZU4v-M0a8GLKGJR8tZ-qH8,112
|
|
277
|
+
cartography-0.75.0.dist-info/top_level.txt,sha256=BHqsNJQiI6Q72DeypC1IINQJE59SLhU4nllbQjgJi9g,12
|
|
278
|
+
cartography-0.75.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|