cartography 0.102.0rc1__py3-none-any.whl → 0.103.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/__main__.py +1 -2
- cartography/_version.py +2 -2
- cartography/cli.py +376 -249
- cartography/client/core/tx.py +39 -18
- cartography/config.py +28 -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/cloudwatch.py +93 -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 +327 -0
- 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/efs.py +93 -0
- 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 +57 -44
- 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/cloudflare/__init__.py +74 -0
- cartography/intel/cloudflare/accounts.py +57 -0
- cartography/intel/cloudflare/dnsrecords.py +64 -0
- cartography/intel/cloudflare/members.py +75 -0
- cartography/intel/cloudflare/roles.py +65 -0
- cartography/intel/cloudflare/zones.py +64 -0
- 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/openai/__init__.py +86 -0
- cartography/intel/openai/adminapikeys.py +90 -0
- cartography/intel/openai/apikeys.py +96 -0
- cartography/intel/openai/projects.py +94 -0
- cartography/intel/openai/serviceaccounts.py +82 -0
- cartography/intel/openai/users.py +78 -0
- cartography/intel/openai/util.py +29 -0
- 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/intel/tailscale/__init__.py +77 -0
- cartography/intel/tailscale/acls.py +146 -0
- cartography/intel/tailscale/devices.py +127 -0
- cartography/intel/tailscale/postureintegrations.py +81 -0
- cartography/intel/tailscale/tailnets.py +76 -0
- cartography/intel/tailscale/users.py +80 -0
- cartography/intel/tailscale/utils.py +132 -0
- 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/cloudwatch/__init__.py +0 -0
- cartography/models/aws/cloudwatch/loggroup.py +52 -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 +97 -0
- cartography/models/aws/ec2/route_tables.py +128 -0
- cartography/models/aws/ec2/routes.py +85 -0
- 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/efs/__init__.py +0 -0
- cartography/models/aws/efs/mount_target.py +52 -0
- 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/cloudflare/__init__.py +0 -0
- cartography/models/cloudflare/account.py +25 -0
- cartography/models/cloudflare/dnsrecord.py +55 -0
- cartography/models/cloudflare/member.py +82 -0
- cartography/models/cloudflare/role.py +44 -0
- cartography/models/cloudflare/zone.py +59 -0
- 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/openai/__init__.py +0 -0
- cartography/models/openai/adminapikey.py +90 -0
- cartography/models/openai/apikey.py +84 -0
- cartography/models/openai/organization.py +17 -0
- cartography/models/openai/project.py +70 -0
- cartography/models/openai/serviceaccount.py +50 -0
- cartography/models/openai/user.py +49 -0
- 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/models/tailscale/__init__.py +0 -0
- cartography/models/tailscale/device.py +95 -0
- cartography/models/tailscale/group.py +86 -0
- cartography/models/tailscale/postureintegration.py +58 -0
- cartography/models/tailscale/tag.py +102 -0
- cartography/models/tailscale/tailnet.py +29 -0
- cartography/models/tailscale/user.py +52 -0
- cartography/stats.py +3 -3
- cartography/sync.py +113 -31
- cartography/util.py +84 -62
- {cartography-0.102.0rc1.dist-info → cartography-0.103.0.dist-info}/METADATA +8 -15
- cartography-0.103.0.dist-info/RECORD +442 -0
- {cartography-0.102.0rc1.dist-info → cartography-0.103.0.dist-info}/WHEEL +1 -1
- cartography-0.102.0rc1.dist-info/RECORD +0 -377
- {cartography-0.102.0rc1.dist-info → cartography-0.103.0.dist-info}/entry_points.txt +0 -0
- {cartography-0.102.0rc1.dist-info → cartography-0.103.0.dist-info}/licenses/LICENSE +0 -0
- {cartography-0.102.0rc1.dist-info → cartography-0.103.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any
|
|
3
|
+
from typing import Dict
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
import boto3
|
|
7
|
+
import neo4j
|
|
8
|
+
|
|
9
|
+
from cartography.client.core.tx import load
|
|
10
|
+
from cartography.graph.job import GraphJob
|
|
11
|
+
from cartography.intel.aws.ec2.util import get_botocore_config
|
|
12
|
+
from cartography.models.aws.efs.mount_target import EfsMountTargetSchema
|
|
13
|
+
from cartography.util import aws_handle_regions
|
|
14
|
+
from cartography.util import timeit
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@timeit
|
|
20
|
+
@aws_handle_regions
|
|
21
|
+
def get_efs_mount_targets(
|
|
22
|
+
boto3_session: boto3.Session, region: str
|
|
23
|
+
) -> List[Dict[str, Any]]:
|
|
24
|
+
client = boto3_session.client(
|
|
25
|
+
"efs", region_name=region, config=get_botocore_config()
|
|
26
|
+
)
|
|
27
|
+
paginator = client.get_paginator("describe_mount_targets")
|
|
28
|
+
mountTargets = []
|
|
29
|
+
for page in paginator.paginate():
|
|
30
|
+
mountTargets.extend(page["MountTargets"])
|
|
31
|
+
return mountTargets
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@timeit
|
|
35
|
+
def load_efs_mount_targets(
|
|
36
|
+
neo4j_session: neo4j.Session,
|
|
37
|
+
data: List[Dict[str, Any]],
|
|
38
|
+
region: str,
|
|
39
|
+
current_aws_account_id: str,
|
|
40
|
+
aws_update_tag: int,
|
|
41
|
+
) -> None:
|
|
42
|
+
logger.info(
|
|
43
|
+
f"Loading Efs {len(data)} mount targets for region '{region}' into graph.",
|
|
44
|
+
)
|
|
45
|
+
load(
|
|
46
|
+
neo4j_session,
|
|
47
|
+
EfsMountTargetSchema(),
|
|
48
|
+
data,
|
|
49
|
+
lastupdated=aws_update_tag,
|
|
50
|
+
Region=region,
|
|
51
|
+
AWS_ID=current_aws_account_id,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@timeit
|
|
56
|
+
def cleanup(
|
|
57
|
+
neo4j_session: neo4j.Session,
|
|
58
|
+
common_job_parameters: Dict[str, Any],
|
|
59
|
+
) -> None:
|
|
60
|
+
logger.debug("Running Efs cleanup job.")
|
|
61
|
+
cleanup_job = GraphJob.from_node_schema(
|
|
62
|
+
EfsMountTargetSchema(), common_job_parameters
|
|
63
|
+
)
|
|
64
|
+
cleanup_job.run(neo4j_session)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@timeit
|
|
68
|
+
def sync(
|
|
69
|
+
neo4j_session: neo4j.Session,
|
|
70
|
+
boto3_session: boto3.session.Session,
|
|
71
|
+
regions: List[str],
|
|
72
|
+
current_aws_account_id: str,
|
|
73
|
+
update_tag: int,
|
|
74
|
+
common_job_parameters: Dict[str, Any],
|
|
75
|
+
) -> None:
|
|
76
|
+
for region in regions:
|
|
77
|
+
logger.info(
|
|
78
|
+
f"Syncing Efs for region '{region}' in account '{current_aws_account_id}'.",
|
|
79
|
+
)
|
|
80
|
+
mountTargets = get_efs_mount_targets(boto3_session, region)
|
|
81
|
+
mount_target_data: List[Dict[str, Any]] = []
|
|
82
|
+
for mountTarget in mountTargets:
|
|
83
|
+
mount_target_data.append(mountTarget)
|
|
84
|
+
|
|
85
|
+
load_efs_mount_targets(
|
|
86
|
+
neo4j_session,
|
|
87
|
+
mount_target_data,
|
|
88
|
+
region,
|
|
89
|
+
current_aws_account_id,
|
|
90
|
+
update_tag,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
cleanup(neo4j_session, common_job_parameters)
|
cartography/intel/aws/eks.py
CHANGED
|
@@ -18,28 +18,32 @@ logger = logging.getLogger(__name__)
|
|
|
18
18
|
@timeit
|
|
19
19
|
@aws_handle_regions
|
|
20
20
|
def get_eks_clusters(boto3_session: boto3.session.Session, region: str) -> List[str]:
|
|
21
|
-
client = boto3_session.client(
|
|
21
|
+
client = boto3_session.client("eks", region_name=region)
|
|
22
22
|
clusters: List[str] = []
|
|
23
|
-
paginator = client.get_paginator(
|
|
23
|
+
paginator = client.get_paginator("list_clusters")
|
|
24
24
|
for page in paginator.paginate():
|
|
25
|
-
clusters.extend(page[
|
|
25
|
+
clusters.extend(page["clusters"])
|
|
26
26
|
return clusters
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
@timeit
|
|
30
|
-
def get_eks_describe_cluster(
|
|
31
|
-
|
|
30
|
+
def get_eks_describe_cluster(
|
|
31
|
+
boto3_session: boto3.session.Session,
|
|
32
|
+
region: str,
|
|
33
|
+
cluster_name: str,
|
|
34
|
+
) -> Dict:
|
|
35
|
+
client = boto3_session.client("eks", region_name=region)
|
|
32
36
|
response = client.describe_cluster(name=cluster_name)
|
|
33
|
-
return response[
|
|
37
|
+
return response["cluster"]
|
|
34
38
|
|
|
35
39
|
|
|
36
40
|
@timeit
|
|
37
41
|
def load_eks_clusters(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
neo4j_session: neo4j.Session,
|
|
43
|
+
cluster_data: List[Dict[str, Any]],
|
|
44
|
+
region: str,
|
|
45
|
+
current_aws_account_id: str,
|
|
46
|
+
aws_update_tag: int,
|
|
43
47
|
) -> None:
|
|
44
48
|
load(
|
|
45
49
|
neo4j_session,
|
|
@@ -57,50 +61,72 @@ def _process_logging(cluster: Dict) -> bool:
|
|
|
57
61
|
at least one entry has audit logging set to Enabled.
|
|
58
62
|
"""
|
|
59
63
|
logging: bool = False
|
|
60
|
-
cluster_logging: Any = cluster.get(
|
|
64
|
+
cluster_logging: Any = cluster.get("logging", {}).get("clusterLogging")
|
|
61
65
|
if cluster_logging:
|
|
62
|
-
logging = any(filter(lambda x:
|
|
66
|
+
logging = any(filter(lambda x: "audit" in x["types"] and x["enabled"], cluster_logging)) # type: ignore
|
|
63
67
|
return logging
|
|
64
68
|
|
|
65
69
|
|
|
66
70
|
@timeit
|
|
67
|
-
def cleanup(
|
|
71
|
+
def cleanup(
|
|
72
|
+
neo4j_session: neo4j.Session,
|
|
73
|
+
common_job_parameters: Dict[str, Any],
|
|
74
|
+
) -> None:
|
|
68
75
|
logger.info("Running EKS cluster cleanup")
|
|
69
|
-
GraphJob.from_node_schema(EKSClusterSchema(), common_job_parameters).run(
|
|
76
|
+
GraphJob.from_node_schema(EKSClusterSchema(), common_job_parameters).run(
|
|
77
|
+
neo4j_session,
|
|
78
|
+
)
|
|
70
79
|
|
|
71
80
|
|
|
72
81
|
def transform(cluster_data: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
73
82
|
transformed_list = []
|
|
74
83
|
for cluster_name, cluster_dict in cluster_data.items():
|
|
75
84
|
transformed_dict = cluster_dict.copy()
|
|
76
|
-
transformed_dict[
|
|
77
|
-
transformed_dict[
|
|
78
|
-
|
|
85
|
+
transformed_dict["ClusterLogging"] = _process_logging(transformed_dict)
|
|
86
|
+
transformed_dict["ClusterEndpointPublic"] = transformed_dict.get(
|
|
87
|
+
"resourcesVpcConfig",
|
|
88
|
+
{},
|
|
89
|
+
).get(
|
|
90
|
+
"endpointPublicAccess",
|
|
79
91
|
)
|
|
80
|
-
if
|
|
81
|
-
transformed_dict[
|
|
92
|
+
if "createdAt" in transformed_dict:
|
|
93
|
+
transformed_dict["created_at"] = str(transformed_dict["createdAt"])
|
|
82
94
|
transformed_list.append(transformed_dict)
|
|
83
95
|
return transformed_list
|
|
84
96
|
|
|
85
97
|
|
|
86
98
|
@timeit
|
|
87
99
|
def sync(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
100
|
+
neo4j_session: neo4j.Session,
|
|
101
|
+
boto3_session: boto3.session.Session,
|
|
102
|
+
regions: List[str],
|
|
103
|
+
current_aws_account_id: str,
|
|
104
|
+
update_tag: int,
|
|
105
|
+
common_job_parameters: Dict[str, Any],
|
|
94
106
|
) -> None:
|
|
95
107
|
for region in regions:
|
|
96
|
-
logger.info(
|
|
108
|
+
logger.info(
|
|
109
|
+
"Syncing EKS for region '%s' in account '%s'.",
|
|
110
|
+
region,
|
|
111
|
+
current_aws_account_id,
|
|
112
|
+
)
|
|
97
113
|
|
|
98
114
|
clusters: List[str] = get_eks_clusters(boto3_session, region)
|
|
99
115
|
cluster_data = {}
|
|
100
116
|
for cluster_name in clusters:
|
|
101
|
-
cluster_data[cluster_name] = get_eks_describe_cluster(
|
|
117
|
+
cluster_data[cluster_name] = get_eks_describe_cluster(
|
|
118
|
+
boto3_session,
|
|
119
|
+
region,
|
|
120
|
+
cluster_name,
|
|
121
|
+
)
|
|
102
122
|
transformed_list = transform(cluster_data)
|
|
103
123
|
|
|
104
|
-
load_eks_clusters(
|
|
124
|
+
load_eks_clusters(
|
|
125
|
+
neo4j_session,
|
|
126
|
+
transformed_list,
|
|
127
|
+
region,
|
|
128
|
+
current_aws_account_id,
|
|
129
|
+
update_tag,
|
|
130
|
+
)
|
|
105
131
|
|
|
106
132
|
cleanup(neo4j_session, common_job_parameters)
|
|
@@ -17,7 +17,7 @@ stat_handler = get_stats_client(__name__)
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
def _get_topic(cluster: Dict) -> Dict:
|
|
20
|
-
return cluster[
|
|
20
|
+
return cluster["NotificationConfiguration"]
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
def transform_elasticache_topics(cluster_data: List[Dict]) -> List[Dict]:
|
|
@@ -28,7 +28,7 @@ def transform_elasticache_topics(cluster_data: List[Dict]) -> List[Dict]:
|
|
|
28
28
|
topics: List[Dict] = []
|
|
29
29
|
for cluster in cluster_data:
|
|
30
30
|
topic = _get_topic(cluster)
|
|
31
|
-
topic_arn = topic[
|
|
31
|
+
topic_arn = topic["TopicArn"]
|
|
32
32
|
if topic_arn not in seen:
|
|
33
33
|
seen.add(topic_arn)
|
|
34
34
|
topics.append(topic)
|
|
@@ -37,20 +37,26 @@ def transform_elasticache_topics(cluster_data: List[Dict]) -> List[Dict]:
|
|
|
37
37
|
|
|
38
38
|
@timeit
|
|
39
39
|
@aws_handle_regions
|
|
40
|
-
def get_elasticache_clusters(
|
|
40
|
+
def get_elasticache_clusters(
|
|
41
|
+
boto3_session: boto3.session.Session,
|
|
42
|
+
region: str,
|
|
43
|
+
) -> List[Dict]:
|
|
41
44
|
logger.debug(f"Getting ElastiCache Clusters in region '{region}'.")
|
|
42
|
-
client = boto3_session.client(
|
|
43
|
-
paginator = client.get_paginator(
|
|
45
|
+
client = boto3_session.client("elasticache", region_name=region)
|
|
46
|
+
paginator = client.get_paginator("describe_cache_clusters")
|
|
44
47
|
clusters: List[Dict] = []
|
|
45
48
|
for page in paginator.paginate():
|
|
46
|
-
clusters.extend(page[
|
|
49
|
+
clusters.extend(page["CacheClusters"])
|
|
47
50
|
return clusters
|
|
48
51
|
|
|
49
52
|
|
|
50
53
|
@timeit
|
|
51
54
|
def load_elasticache_clusters(
|
|
52
|
-
neo4j_session: neo4j.Session,
|
|
53
|
-
|
|
55
|
+
neo4j_session: neo4j.Session,
|
|
56
|
+
clusters: List[Dict],
|
|
57
|
+
region: str,
|
|
58
|
+
aws_account_id: str,
|
|
59
|
+
update_tag: int,
|
|
54
60
|
) -> None:
|
|
55
61
|
query = """
|
|
56
62
|
UNWIND $clusters as elasticache_cluster
|
|
@@ -85,7 +91,9 @@ def load_elasticache_clusters(
|
|
|
85
91
|
ON CREATE SET r2.firstseen = timestamp()
|
|
86
92
|
SET r2.lastupdated = $aws_update_tag
|
|
87
93
|
"""
|
|
88
|
-
logger.info(
|
|
94
|
+
logger.info(
|
|
95
|
+
f"Loading f{len(clusters)} ElastiCache clusters for region '{region}' into graph.",
|
|
96
|
+
)
|
|
89
97
|
neo4j_session.run(
|
|
90
98
|
query,
|
|
91
99
|
clusters=clusters,
|
|
@@ -96,29 +104,45 @@ def load_elasticache_clusters(
|
|
|
96
104
|
|
|
97
105
|
|
|
98
106
|
@timeit
|
|
99
|
-
def cleanup(
|
|
107
|
+
def cleanup(
|
|
108
|
+
neo4j_session: neo4j.Session,
|
|
109
|
+
current_aws_account_id: str,
|
|
110
|
+
update_tag: int,
|
|
111
|
+
) -> None:
|
|
100
112
|
run_cleanup_job(
|
|
101
|
-
|
|
113
|
+
"aws_import_elasticache_cleanup.json",
|
|
102
114
|
neo4j_session,
|
|
103
|
-
{
|
|
115
|
+
{"UPDATE_TAG": update_tag, "AWS_ID": current_aws_account_id},
|
|
104
116
|
)
|
|
105
117
|
|
|
106
118
|
|
|
107
119
|
@timeit
|
|
108
120
|
def sync(
|
|
109
|
-
neo4j_session: neo4j.Session,
|
|
110
|
-
|
|
121
|
+
neo4j_session: neo4j.Session,
|
|
122
|
+
boto3_session: boto3.session.Session,
|
|
123
|
+
regions: List[str],
|
|
124
|
+
current_aws_account_id: str,
|
|
125
|
+
update_tag: int,
|
|
126
|
+
common_job_parameters: Dict,
|
|
111
127
|
) -> None:
|
|
112
128
|
for region in regions:
|
|
113
|
-
logger.info(
|
|
129
|
+
logger.info(
|
|
130
|
+
f"Syncing ElastiCache clusters for region '{region}' in account {current_aws_account_id}",
|
|
131
|
+
)
|
|
114
132
|
clusters = get_elasticache_clusters(boto3_session, region)
|
|
115
|
-
load_elasticache_clusters(
|
|
133
|
+
load_elasticache_clusters(
|
|
134
|
+
neo4j_session,
|
|
135
|
+
clusters,
|
|
136
|
+
region,
|
|
137
|
+
current_aws_account_id,
|
|
138
|
+
update_tag,
|
|
139
|
+
)
|
|
116
140
|
cleanup(neo4j_session, current_aws_account_id, update_tag)
|
|
117
141
|
merge_module_sync_metadata(
|
|
118
142
|
neo4j_session,
|
|
119
|
-
group_type=
|
|
143
|
+
group_type="AWSAccount",
|
|
120
144
|
group_id=current_aws_account_id,
|
|
121
|
-
synced_type=
|
|
145
|
+
synced_type="ElasticacheCluster",
|
|
122
146
|
update_tag=update_tag,
|
|
123
147
|
stat_handler=stat_handler,
|
|
124
148
|
)
|
|
@@ -20,7 +20,7 @@ logger = logging.getLogger(__name__)
|
|
|
20
20
|
def _get_botocore_config() -> botocore.config.Config:
|
|
21
21
|
return botocore.config.Config(
|
|
22
22
|
retries={
|
|
23
|
-
|
|
23
|
+
"max_attempts": 8,
|
|
24
24
|
},
|
|
25
25
|
)
|
|
26
26
|
|
|
@@ -35,19 +35,26 @@ def _get_es_domains(client: botocore.client.BaseClient) -> List[Dict]:
|
|
|
35
35
|
:return: list of ES domains
|
|
36
36
|
"""
|
|
37
37
|
data = client.list_domain_names()
|
|
38
|
-
domain_names = [d[
|
|
38
|
+
domain_names = [d["DomainName"] for d in data.get("DomainNames", [])]
|
|
39
39
|
# NOTE describe_elasticsearch_domains takes at most 5 domain names
|
|
40
|
-
domain_name_chunks = [
|
|
40
|
+
domain_name_chunks = [
|
|
41
|
+
domain_names[i : i + 5] for i in range(0, len(domain_names), 5)
|
|
42
|
+
]
|
|
41
43
|
domains: List[Dict] = []
|
|
42
44
|
for domain_name_chunk in domain_name_chunks:
|
|
43
|
-
chunk_data = client.describe_elasticsearch_domains(
|
|
44
|
-
|
|
45
|
+
chunk_data = client.describe_elasticsearch_domains(
|
|
46
|
+
DomainNames=domain_name_chunk,
|
|
47
|
+
)
|
|
48
|
+
domains.extend(chunk_data["DomainStatusList"])
|
|
45
49
|
return domains
|
|
46
50
|
|
|
47
51
|
|
|
48
52
|
@timeit
|
|
49
53
|
def _load_es_domains(
|
|
50
|
-
neo4j_session: neo4j.Session,
|
|
54
|
+
neo4j_session: neo4j.Session,
|
|
55
|
+
domain_list: List[Dict],
|
|
56
|
+
aws_account_id: str,
|
|
57
|
+
aws_update_tag: int,
|
|
51
58
|
) -> None:
|
|
52
59
|
"""
|
|
53
60
|
Ingest Elastic Search domains
|
|
@@ -86,7 +93,7 @@ def _load_es_domains(
|
|
|
86
93
|
# TODO this is a hacky workaround -- neo4j doesn't accept datetime objects and this section of the object
|
|
87
94
|
# TODO contains one. we really shouldn't be sending the entire object to neo4j
|
|
88
95
|
for d in domain_list:
|
|
89
|
-
del d[
|
|
96
|
+
del d["ServiceSoftwareOptions"]
|
|
90
97
|
|
|
91
98
|
neo4j_session.run(
|
|
92
99
|
ingest_records,
|
|
@@ -104,7 +111,10 @@ def _load_es_domains(
|
|
|
104
111
|
|
|
105
112
|
@timeit
|
|
106
113
|
def _link_es_domains_to_dns(
|
|
107
|
-
neo4j_session: neo4j.Session,
|
|
114
|
+
neo4j_session: neo4j.Session,
|
|
115
|
+
domain_id: str,
|
|
116
|
+
domain_data: Dict,
|
|
117
|
+
aws_update_tag: int,
|
|
108
118
|
) -> None:
|
|
109
119
|
"""
|
|
110
120
|
Link the ES domain to its DNS FQDN endpoint and create associated nodes in the graph
|
|
@@ -117,15 +127,24 @@ def _link_es_domains_to_dns(
|
|
|
117
127
|
# TODO add support for endpoints to this method
|
|
118
128
|
if domain_data.get("Endpoint"):
|
|
119
129
|
ingest_dns_record_by_fqdn(
|
|
120
|
-
neo4j_session,
|
|
121
|
-
|
|
130
|
+
neo4j_session,
|
|
131
|
+
aws_update_tag,
|
|
132
|
+
domain_data["Endpoint"],
|
|
133
|
+
domain_id,
|
|
134
|
+
record_label="ESDomain",
|
|
135
|
+
dns_node_additional_label="AWSDNSRecord",
|
|
122
136
|
)
|
|
123
137
|
else:
|
|
124
138
|
logger.debug(f"No es endpoint data for domain id {domain_id}")
|
|
125
139
|
|
|
126
140
|
|
|
127
141
|
@timeit
|
|
128
|
-
def _link_es_domain_vpc(
|
|
142
|
+
def _link_es_domain_vpc(
|
|
143
|
+
neo4j_session: neo4j.Session,
|
|
144
|
+
domain_id: str,
|
|
145
|
+
domain_data: Dict,
|
|
146
|
+
aws_update_tag: int,
|
|
147
|
+
) -> None:
|
|
129
148
|
"""
|
|
130
149
|
Link the ES domain to its DNS FQDN endpoint and create associated nodes in the graph
|
|
131
150
|
if needed
|
|
@@ -177,7 +196,11 @@ def _link_es_domain_vpc(neo4j_session: neo4j.Session, domain_id: str, domain_dat
|
|
|
177
196
|
|
|
178
197
|
|
|
179
198
|
@timeit
|
|
180
|
-
def _process_access_policy(
|
|
199
|
+
def _process_access_policy(
|
|
200
|
+
neo4j_session: neo4j.Session,
|
|
201
|
+
domain_id: str,
|
|
202
|
+
domain_data: Dict,
|
|
203
|
+
) -> None:
|
|
181
204
|
"""
|
|
182
205
|
Link the ES domain to its DNS FQDN endpoint and create associated nodes in the graph
|
|
183
206
|
if needed
|
|
@@ -186,12 +209,14 @@ def _process_access_policy(neo4j_session: neo4j.Session, domain_id: str, domain_
|
|
|
186
209
|
:param domain_id: ES domain id
|
|
187
210
|
:param domain_data: domain data
|
|
188
211
|
"""
|
|
189
|
-
tag_es =
|
|
212
|
+
tag_es = (
|
|
213
|
+
"MATCH (es:ESDomain{id: $DomainId}) SET es.exposed_internet = $InternetExposed"
|
|
214
|
+
)
|
|
190
215
|
|
|
191
216
|
exposed_internet = False
|
|
192
217
|
|
|
193
218
|
if domain_data.get("Endpoint") and domain_data.get("AccessPolicies"):
|
|
194
|
-
policy = Policy(json.loads(domain_data[
|
|
219
|
+
policy = Policy(json.loads(domain_data["AccessPolicies"]))
|
|
195
220
|
if policy.is_internet_accessible():
|
|
196
221
|
exposed_internet = True
|
|
197
222
|
|
|
@@ -201,20 +226,32 @@ def _process_access_policy(neo4j_session: neo4j.Session, domain_id: str, domain_
|
|
|
201
226
|
@timeit
|
|
202
227
|
def cleanup(neo4j_session: neo4j.Session, update_tag: int, aws_account_id: int) -> None:
|
|
203
228
|
run_cleanup_job(
|
|
204
|
-
|
|
229
|
+
"aws_import_es_cleanup.json",
|
|
205
230
|
neo4j_session,
|
|
206
|
-
{
|
|
231
|
+
{"UPDATE_TAG": update_tag, "AWS_ID": aws_account_id},
|
|
207
232
|
)
|
|
208
233
|
|
|
209
234
|
|
|
210
235
|
@timeit
|
|
211
236
|
def sync(
|
|
212
|
-
neo4j_session: neo4j.Session,
|
|
213
|
-
|
|
237
|
+
neo4j_session: neo4j.Session,
|
|
238
|
+
boto3_session: boto3.session.Session,
|
|
239
|
+
regions: List[str],
|
|
240
|
+
current_aws_account_id: str,
|
|
241
|
+
update_tag: int,
|
|
242
|
+
common_job_parameters: Dict,
|
|
214
243
|
) -> None:
|
|
215
244
|
for region in regions:
|
|
216
|
-
logger.info(
|
|
217
|
-
|
|
245
|
+
logger.info(
|
|
246
|
+
"Syncing Elasticsearch Service for region '%s' in account '%s'.",
|
|
247
|
+
region,
|
|
248
|
+
current_aws_account_id,
|
|
249
|
+
)
|
|
250
|
+
client = boto3_session.client(
|
|
251
|
+
"es",
|
|
252
|
+
region_name=region,
|
|
253
|
+
config=_get_botocore_config(),
|
|
254
|
+
)
|
|
218
255
|
data = _get_es_domains(client)
|
|
219
256
|
_load_es_domains(neo4j_session, data, current_aws_account_id, update_tag)
|
|
220
257
|
|
cartography/intel/aws/emr.py
CHANGED
|
@@ -24,40 +24,59 @@ DESCRIBE_SLEEP = 1
|
|
|
24
24
|
|
|
25
25
|
@timeit
|
|
26
26
|
@aws_handle_regions
|
|
27
|
-
def get_emr_clusters(
|
|
28
|
-
|
|
27
|
+
def get_emr_clusters(
|
|
28
|
+
boto3_session: boto3.session.Session,
|
|
29
|
+
region: str,
|
|
30
|
+
) -> List[Dict[str, Any]]:
|
|
31
|
+
client = boto3_session.client(
|
|
32
|
+
"emr",
|
|
33
|
+
region_name=region,
|
|
34
|
+
config=get_botocore_config(),
|
|
35
|
+
)
|
|
29
36
|
clusters: List[Dict[str, Any]] = []
|
|
30
|
-
paginator = client.get_paginator(
|
|
37
|
+
paginator = client.get_paginator("list_clusters")
|
|
31
38
|
for page in paginator.paginate():
|
|
32
|
-
cluster = page[
|
|
39
|
+
cluster = page["Clusters"]
|
|
33
40
|
clusters.extend(cluster)
|
|
34
41
|
time.sleep(LIST_SLEEP)
|
|
35
42
|
return clusters
|
|
36
43
|
|
|
37
44
|
|
|
38
45
|
@timeit
|
|
39
|
-
def get_emr_describe_cluster(
|
|
40
|
-
|
|
46
|
+
def get_emr_describe_cluster(
|
|
47
|
+
boto3_session: boto3.session.Session,
|
|
48
|
+
region: str,
|
|
49
|
+
cluster_id: str,
|
|
50
|
+
) -> Dict[str, Any]:
|
|
51
|
+
client = boto3_session.client(
|
|
52
|
+
"emr",
|
|
53
|
+
region_name=region,
|
|
54
|
+
config=get_botocore_config(),
|
|
55
|
+
)
|
|
41
56
|
cluster_details: Dict[str, Any] = {}
|
|
42
57
|
try:
|
|
43
58
|
response = client.describe_cluster(ClusterId=cluster_id)
|
|
44
|
-
cluster_details = response[
|
|
59
|
+
cluster_details = response["Cluster"]
|
|
45
60
|
except botocore.exceptions.ClientError as e:
|
|
46
|
-
code = e.response[
|
|
47
|
-
msg = e.response[
|
|
48
|
-
logger.warning(
|
|
61
|
+
code = e.response["Error"]["Code"]
|
|
62
|
+
msg = e.response["Error"]["Message"]
|
|
63
|
+
logger.warning(
|
|
64
|
+
f"Could not run EMR describe_cluster due to boto3 error {code}: {msg}. Skipping.",
|
|
65
|
+
)
|
|
49
66
|
return cluster_details
|
|
50
67
|
|
|
51
68
|
|
|
52
69
|
@timeit
|
|
53
70
|
def load_emr_clusters(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
71
|
+
neo4j_session: neo4j.Session,
|
|
72
|
+
cluster_data: List[Dict[str, Any]],
|
|
73
|
+
region: str,
|
|
74
|
+
current_aws_account_id: str,
|
|
75
|
+
aws_update_tag: int,
|
|
59
76
|
) -> None:
|
|
60
|
-
logger.info(
|
|
77
|
+
logger.info(
|
|
78
|
+
f"Loading EMR {len(cluster_data)} clusters for region '{region}' into graph.",
|
|
79
|
+
)
|
|
61
80
|
load(
|
|
62
81
|
neo4j_session,
|
|
63
82
|
EMRClusterSchema(),
|
|
@@ -69,7 +88,10 @@ def load_emr_clusters(
|
|
|
69
88
|
|
|
70
89
|
|
|
71
90
|
@timeit
|
|
72
|
-
def cleanup(
|
|
91
|
+
def cleanup(
|
|
92
|
+
neo4j_session: neo4j.Session,
|
|
93
|
+
common_job_parameters: Dict[str, Any],
|
|
94
|
+
) -> None:
|
|
73
95
|
logger.debug("Running EMR cleanup job.")
|
|
74
96
|
cleanup_job = GraphJob.from_node_schema(EMRClusterSchema(), common_job_parameters)
|
|
75
97
|
cleanup_job.run(neo4j_session)
|
|
@@ -77,22 +99,38 @@ def cleanup(neo4j_session: neo4j.Session, common_job_parameters: Dict[str, Any])
|
|
|
77
99
|
|
|
78
100
|
@timeit
|
|
79
101
|
def sync(
|
|
80
|
-
neo4j_session: neo4j.Session,
|
|
81
|
-
|
|
102
|
+
neo4j_session: neo4j.Session,
|
|
103
|
+
boto3_session: boto3.session.Session,
|
|
104
|
+
regions: List[str],
|
|
105
|
+
current_aws_account_id: str,
|
|
106
|
+
update_tag: int,
|
|
107
|
+
common_job_parameters: Dict[str, Any],
|
|
82
108
|
) -> None:
|
|
83
109
|
for region in regions:
|
|
84
|
-
logger.info(
|
|
110
|
+
logger.info(
|
|
111
|
+
f"Syncing EMR for region '{region}' in account '{current_aws_account_id}'.",
|
|
112
|
+
)
|
|
85
113
|
|
|
86
114
|
clusters = get_emr_clusters(boto3_session, region)
|
|
87
115
|
|
|
88
116
|
cluster_data: List[Dict[str, Any]] = []
|
|
89
117
|
for cluster in clusters:
|
|
90
|
-
cluster_id = cluster[
|
|
91
|
-
cluster_details = get_emr_describe_cluster(
|
|
118
|
+
cluster_id = cluster["Id"]
|
|
119
|
+
cluster_details = get_emr_describe_cluster(
|
|
120
|
+
boto3_session,
|
|
121
|
+
region,
|
|
122
|
+
cluster_id,
|
|
123
|
+
)
|
|
92
124
|
if cluster_details:
|
|
93
125
|
cluster_data.append(cluster_details)
|
|
94
126
|
time.sleep(DESCRIBE_SLEEP)
|
|
95
127
|
|
|
96
|
-
load_emr_clusters(
|
|
128
|
+
load_emr_clusters(
|
|
129
|
+
neo4j_session,
|
|
130
|
+
cluster_data,
|
|
131
|
+
region,
|
|
132
|
+
current_aws_account_id,
|
|
133
|
+
update_tag,
|
|
134
|
+
)
|
|
97
135
|
|
|
98
136
|
cleanup(neo4j_session, common_job_parameters)
|