cartography 0.95.0__py3-none-any.whl → 0.96.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/cli.py +15 -0
- cartography/client/core/tx.py +1 -1
- cartography/config.py +6 -2
- cartography/data/indexes.cypher +1 -2
- cartography/data/jobs/cleanup/aws_import_identity_center_cleanup.json +16 -0
- cartography/data/jobs/cleanup/{github_users_cleanup.json → github_org_and_users_cleanup.json} +5 -0
- cartography/data/jobs/cleanup/github_repos_cleanup.json +25 -0
- cartography/graph/querybuilder.py +4 -0
- cartography/intel/aws/apigateway.py +3 -3
- cartography/intel/aws/ec2/auto_scaling_groups.py +147 -185
- cartography/intel/aws/ec2/instances.py +2 -0
- cartography/intel/aws/ec2/network_acls.py +209 -0
- cartography/intel/aws/ec2/subnets.py +2 -0
- cartography/intel/aws/iam.py +4 -3
- cartography/intel/aws/identitycenter.py +307 -0
- cartography/intel/aws/resources.py +4 -0
- cartography/intel/cve/__init__.py +1 -1
- cartography/intel/cve/feed.py +10 -7
- cartography/intel/github/repos.py +176 -27
- cartography/intel/github/users.py +156 -39
- cartography/intel/okta/users.py +2 -1
- cartography/intel/semgrep/__init__.py +1 -1
- cartography/intel/semgrep/dependencies.py +54 -22
- cartography/models/aws/ec2/auto_scaling_groups.py +204 -0
- cartography/models/aws/ec2/launch_configurations.py +55 -0
- cartography/models/aws/ec2/network_acl_rules.py +98 -0
- cartography/models/aws/ec2/network_acls.py +86 -0
- cartography/models/aws/identitycenter/__init__.py +0 -0
- cartography/models/aws/identitycenter/awsidentitycenter.py +44 -0
- cartography/models/aws/identitycenter/awspermissionset.py +84 -0
- cartography/models/aws/identitycenter/awsssouser.py +68 -0
- cartography/models/core/common.py +18 -1
- cartography/models/github/orgs.py +26 -0
- cartography/models/github/users.py +119 -0
- cartography/models/semgrep/dependencies.py +13 -0
- cartography-0.96.0.dist-info/METADATA +53 -0
- {cartography-0.95.0.dist-info → cartography-0.96.0.dist-info}/RECORD +41 -28
- {cartography-0.95.0.dist-info → cartography-0.96.0.dist-info}/WHEEL +1 -1
- cartography-0.95.0.dist-info/METADATA +0 -53
- {cartography-0.95.0.dist-info → cartography-0.96.0.dist-info}/LICENSE +0 -0
- {cartography-0.95.0.dist-info → cartography-0.96.0.dist-info}/entry_points.txt +0 -0
- {cartography-0.95.0.dist-info → cartography-0.96.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from collections import namedtuple
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import boto3
|
|
6
|
+
import neo4j
|
|
7
|
+
|
|
8
|
+
from .util import get_botocore_config
|
|
9
|
+
from cartography.client.core.tx import load
|
|
10
|
+
from cartography.graph.job import GraphJob
|
|
11
|
+
from cartography.models.aws.ec2.network_acl_rules import EC2NetworkAclEgressRuleSchema
|
|
12
|
+
from cartography.models.aws.ec2.network_acl_rules import EC2NetworkAclInboundRuleSchema
|
|
13
|
+
from cartography.models.aws.ec2.network_acls import EC2NetworkAclSchema
|
|
14
|
+
from cartography.util import aws_handle_regions
|
|
15
|
+
from cartography.util import timeit
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
Ec2AclObjects = namedtuple(
|
|
20
|
+
"Ec2AclObjects", [
|
|
21
|
+
'network_acls',
|
|
22
|
+
'inbound_rules',
|
|
23
|
+
'outbound_rules',
|
|
24
|
+
],
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@timeit
|
|
29
|
+
@aws_handle_regions
|
|
30
|
+
def get_network_acl_data(boto3_session: boto3.session.Session, region: str) -> list[dict[str, Any]]:
|
|
31
|
+
client = boto3_session.client('ec2', region_name=region, config=get_botocore_config())
|
|
32
|
+
paginator = client.get_paginator('describe_network_acls')
|
|
33
|
+
acls = []
|
|
34
|
+
for page in paginator.paginate():
|
|
35
|
+
acls.extend(page['NetworkAcls'])
|
|
36
|
+
return acls
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def transform_network_acl_data(
|
|
40
|
+
data_list: list[dict[str, Any]],
|
|
41
|
+
region: str,
|
|
42
|
+
current_aws_account_id: str,
|
|
43
|
+
) -> Ec2AclObjects:
|
|
44
|
+
network_acls = []
|
|
45
|
+
inbound_rules = []
|
|
46
|
+
outbound_rules = []
|
|
47
|
+
|
|
48
|
+
for network_acl in data_list:
|
|
49
|
+
network_acl_id = network_acl['NetworkAclId']
|
|
50
|
+
base_network_acl = {
|
|
51
|
+
'Id': network_acl_id,
|
|
52
|
+
'Arn': f'arn:aws:ec2:{region}:{current_aws_account_id}:network-acl/{network_acl_id}',
|
|
53
|
+
'IsDefault': network_acl['IsDefault'],
|
|
54
|
+
'VpcId': network_acl['VpcId'],
|
|
55
|
+
'OwnerId': network_acl['OwnerId'],
|
|
56
|
+
}
|
|
57
|
+
if network_acl.get('Associations') and network_acl['Associations']:
|
|
58
|
+
# Include subnet associations in the data object if they exist
|
|
59
|
+
for association in network_acl['Associations']:
|
|
60
|
+
base_network_acl['NetworkAclAssociationId'] = association['NetworkAclAssociationId']
|
|
61
|
+
base_network_acl['SubnetId'] = association['SubnetId']
|
|
62
|
+
network_acls.append(base_network_acl)
|
|
63
|
+
else:
|
|
64
|
+
# Otherwise if there's no associations then don't include that in the data object
|
|
65
|
+
network_acls.append(base_network_acl)
|
|
66
|
+
|
|
67
|
+
if network_acl.get("Entries"):
|
|
68
|
+
for rule in network_acl["Entries"]:
|
|
69
|
+
direction = 'egress' if rule['Egress'] else 'inbound'
|
|
70
|
+
transformed_rule = {
|
|
71
|
+
'Id': f"{network_acl['NetworkAclId']}/{direction}/{rule['RuleNumber']}",
|
|
72
|
+
'CidrBlock': rule.get('CidrBlock'),
|
|
73
|
+
'Ipv6CidrBlock': rule.get('Ipv6CidrBlock'),
|
|
74
|
+
'Egress': rule['Egress'],
|
|
75
|
+
'Protocol': rule['Protocol'],
|
|
76
|
+
'RuleAction': rule['RuleAction'],
|
|
77
|
+
'RuleNumber': rule['RuleNumber'],
|
|
78
|
+
# Add pointer back to the nacl to create an edge
|
|
79
|
+
'NetworkAclId': network_acl_id,
|
|
80
|
+
'FromPort': rule.get('PortRange', {}).get('FromPort'),
|
|
81
|
+
'ToPort': rule.get('PortRange', {}).get('ToPort'),
|
|
82
|
+
}
|
|
83
|
+
if transformed_rule['Egress']:
|
|
84
|
+
outbound_rules.append(transformed_rule)
|
|
85
|
+
else:
|
|
86
|
+
inbound_rules.append(transformed_rule)
|
|
87
|
+
return Ec2AclObjects(
|
|
88
|
+
network_acls=network_acls,
|
|
89
|
+
inbound_rules=inbound_rules,
|
|
90
|
+
outbound_rules=outbound_rules,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@timeit
|
|
95
|
+
def load_all_nacl_data(
|
|
96
|
+
neo4j_session: neo4j.Session,
|
|
97
|
+
ec2_acl_objects: Ec2AclObjects,
|
|
98
|
+
region: str,
|
|
99
|
+
aws_account_id: str,
|
|
100
|
+
update_tag: int,
|
|
101
|
+
) -> None:
|
|
102
|
+
load_network_acls(
|
|
103
|
+
neo4j_session,
|
|
104
|
+
ec2_acl_objects.network_acls,
|
|
105
|
+
region,
|
|
106
|
+
aws_account_id,
|
|
107
|
+
update_tag,
|
|
108
|
+
)
|
|
109
|
+
load_network_acl_inbound_rules(
|
|
110
|
+
neo4j_session,
|
|
111
|
+
ec2_acl_objects.inbound_rules,
|
|
112
|
+
region,
|
|
113
|
+
aws_account_id,
|
|
114
|
+
update_tag,
|
|
115
|
+
)
|
|
116
|
+
load_network_acl_egress_rules(
|
|
117
|
+
neo4j_session,
|
|
118
|
+
ec2_acl_objects.outbound_rules,
|
|
119
|
+
region,
|
|
120
|
+
aws_account_id,
|
|
121
|
+
update_tag,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@timeit
|
|
126
|
+
def load_network_acls(
|
|
127
|
+
neo4j_session: neo4j.Session,
|
|
128
|
+
data: list[dict[str, Any]],
|
|
129
|
+
region: str,
|
|
130
|
+
aws_account_id: str,
|
|
131
|
+
update_tag: int,
|
|
132
|
+
) -> None:
|
|
133
|
+
logger.info(f"Loading {len(data)} network acls in {region}.")
|
|
134
|
+
load(
|
|
135
|
+
neo4j_session,
|
|
136
|
+
EC2NetworkAclSchema(),
|
|
137
|
+
data,
|
|
138
|
+
Region=region,
|
|
139
|
+
AWS_ID=aws_account_id,
|
|
140
|
+
lastupdated=update_tag,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@timeit
|
|
145
|
+
def load_network_acl_inbound_rules(
|
|
146
|
+
neo4j_session: neo4j.Session,
|
|
147
|
+
data: list[dict[str, Any]],
|
|
148
|
+
region: str,
|
|
149
|
+
aws_account_id: str,
|
|
150
|
+
update_tag: int,
|
|
151
|
+
) -> None:
|
|
152
|
+
logger.info(f"Loading {len(data)} network acl inbound rules in {region}.")
|
|
153
|
+
load(
|
|
154
|
+
neo4j_session,
|
|
155
|
+
EC2NetworkAclInboundRuleSchema(),
|
|
156
|
+
data,
|
|
157
|
+
Region=region,
|
|
158
|
+
AWS_ID=aws_account_id,
|
|
159
|
+
lastupdated=update_tag,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@timeit
|
|
164
|
+
def load_network_acl_egress_rules(
|
|
165
|
+
neo4j_session: neo4j.Session,
|
|
166
|
+
data: list[dict[str, Any]],
|
|
167
|
+
region: str,
|
|
168
|
+
aws_account_id: str,
|
|
169
|
+
update_tag: int,
|
|
170
|
+
) -> None:
|
|
171
|
+
logger.info(f"Loading {len(data)} network acl egress rules in {region}.")
|
|
172
|
+
load(
|
|
173
|
+
neo4j_session,
|
|
174
|
+
EC2NetworkAclEgressRuleSchema(),
|
|
175
|
+
data,
|
|
176
|
+
Region=region,
|
|
177
|
+
AWS_ID=aws_account_id,
|
|
178
|
+
lastupdated=update_tag,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@timeit
|
|
183
|
+
def cleanup_network_acls(neo4j_session: neo4j.Session, common_job_parameters: dict[str, Any]) -> None:
|
|
184
|
+
GraphJob.from_node_schema(EC2NetworkAclSchema(), common_job_parameters).run(neo4j_session)
|
|
185
|
+
GraphJob.from_node_schema(EC2NetworkAclInboundRuleSchema(), common_job_parameters).run(neo4j_session)
|
|
186
|
+
GraphJob.from_node_schema(EC2NetworkAclEgressRuleSchema(), common_job_parameters).run(neo4j_session)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@timeit
|
|
190
|
+
def sync_network_acls(
|
|
191
|
+
neo4j_session: neo4j.Session,
|
|
192
|
+
boto3_session: boto3.session.Session,
|
|
193
|
+
regions: list[str],
|
|
194
|
+
current_aws_account_id: str,
|
|
195
|
+
update_tag: int,
|
|
196
|
+
common_job_parameters: dict[str, Any],
|
|
197
|
+
) -> None:
|
|
198
|
+
for region in regions:
|
|
199
|
+
logger.info(f"Syncing EC2 network ACLs for region '{region}' in account '{current_aws_account_id}'.")
|
|
200
|
+
data = get_network_acl_data(boto3_session, region)
|
|
201
|
+
ec2_acl_data = transform_network_acl_data(data, region, current_aws_account_id)
|
|
202
|
+
load_all_nacl_data(
|
|
203
|
+
neo4j_session,
|
|
204
|
+
ec2_acl_data,
|
|
205
|
+
region,
|
|
206
|
+
current_aws_account_id,
|
|
207
|
+
update_tag,
|
|
208
|
+
)
|
|
209
|
+
cleanup_network_acls(neo4j_session, common_job_parameters)
|
|
@@ -7,6 +7,7 @@ import neo4j
|
|
|
7
7
|
|
|
8
8
|
from .util import get_botocore_config
|
|
9
9
|
from cartography.graph.job import GraphJob
|
|
10
|
+
from cartography.models.aws.ec2.auto_scaling_groups import EC2SubnetAutoScalingGroupSchema
|
|
10
11
|
from cartography.models.aws.ec2.subnet_instance import EC2SubnetInstanceSchema
|
|
11
12
|
from cartography.util import aws_handle_regions
|
|
12
13
|
from cartography.util import run_cleanup_job
|
|
@@ -79,6 +80,7 @@ def load_subnets(
|
|
|
79
80
|
def cleanup_subnets(neo4j_session: neo4j.Session, common_job_parameters: Dict) -> None:
|
|
80
81
|
run_cleanup_job('aws_ingest_subnets_cleanup.json', neo4j_session, common_job_parameters)
|
|
81
82
|
GraphJob.from_node_schema(EC2SubnetInstanceSchema(), common_job_parameters).run(neo4j_session)
|
|
83
|
+
GraphJob.from_node_schema(EC2SubnetAutoScalingGroupSchema(), common_job_parameters).run(neo4j_session)
|
|
82
84
|
|
|
83
85
|
|
|
84
86
|
@timeit
|
cartography/intel/aws/iam.py
CHANGED
|
@@ -539,11 +539,12 @@ def _transform_policy_statements(statements: Any, policy_id: str) -> List[Dict]:
|
|
|
539
539
|
if not isinstance(statements, list):
|
|
540
540
|
statements = [statements]
|
|
541
541
|
for stmt in statements:
|
|
542
|
-
if "Sid"
|
|
542
|
+
if "Sid" in stmt and stmt["Sid"]:
|
|
543
|
+
statement_id = stmt["Sid"]
|
|
544
|
+
else:
|
|
543
545
|
statement_id = count
|
|
544
546
|
count += 1
|
|
545
|
-
|
|
546
|
-
statement_id = stmt["Sid"]
|
|
547
|
+
|
|
547
548
|
stmt["id"] = f"{policy_id}/statement/{statement_id}"
|
|
548
549
|
if "Resource" in stmt:
|
|
549
550
|
stmt["Resource"] = ensure_list(stmt["Resource"])
|
|
@@ -0,0 +1,307 @@
|
|
|
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.models.aws.identitycenter.awsidentitycenter import AWSIdentityCenterInstanceSchema
|
|
12
|
+
from cartography.models.aws.identitycenter.awspermissionset import AWSPermissionSetSchema
|
|
13
|
+
from cartography.models.aws.identitycenter.awsssouser import AWSSSOUserSchema
|
|
14
|
+
from cartography.util import aws_handle_regions
|
|
15
|
+
from cartography.util import run_cleanup_job
|
|
16
|
+
from cartography.util import timeit
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@timeit
|
|
21
|
+
@aws_handle_regions
|
|
22
|
+
def get_identity_center_instances(boto3_session: boto3.session.Session, region: str) -> List[Dict]:
|
|
23
|
+
"""
|
|
24
|
+
Get all AWS IAM Identity Center instances in the current region
|
|
25
|
+
"""
|
|
26
|
+
client = boto3_session.client('sso-admin', region_name=region)
|
|
27
|
+
instances = []
|
|
28
|
+
|
|
29
|
+
paginator = client.get_paginator('list_instances')
|
|
30
|
+
for page in paginator.paginate():
|
|
31
|
+
instances.extend(page.get('Instances', []))
|
|
32
|
+
|
|
33
|
+
return instances
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@timeit
|
|
37
|
+
def load_identity_center_instances(
|
|
38
|
+
neo4j_session: neo4j.Session,
|
|
39
|
+
instance_data: List[Dict],
|
|
40
|
+
region: str,
|
|
41
|
+
current_aws_account_id: str,
|
|
42
|
+
aws_update_tag: int,
|
|
43
|
+
) -> None:
|
|
44
|
+
"""
|
|
45
|
+
Load Identity Center instances into the graph
|
|
46
|
+
"""
|
|
47
|
+
logger.info(f"Loading {len(instance_data)} Identity Center instances for region {region}")
|
|
48
|
+
load(
|
|
49
|
+
neo4j_session,
|
|
50
|
+
AWSIdentityCenterInstanceSchema(),
|
|
51
|
+
instance_data,
|
|
52
|
+
lastupdated=aws_update_tag,
|
|
53
|
+
Region=region,
|
|
54
|
+
AWS_ID=current_aws_account_id,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@timeit
|
|
59
|
+
def get_permission_sets(boto3_session: boto3.session.Session, instance_arn: str, region: str) -> List[Dict]:
|
|
60
|
+
"""
|
|
61
|
+
Get all permission sets for a given Identity Center instance
|
|
62
|
+
"""
|
|
63
|
+
client = boto3_session.client('sso-admin', region_name=region)
|
|
64
|
+
permission_sets = []
|
|
65
|
+
|
|
66
|
+
paginator = client.get_paginator('list_permission_sets')
|
|
67
|
+
for page in paginator.paginate(InstanceArn=instance_arn):
|
|
68
|
+
# Get detailed info for each permission set
|
|
69
|
+
for arn in page.get('PermissionSets', []):
|
|
70
|
+
details = client.describe_permission_set(
|
|
71
|
+
InstanceArn=instance_arn,
|
|
72
|
+
PermissionSetArn=arn,
|
|
73
|
+
)
|
|
74
|
+
permission_set = details.get('PermissionSet', {})
|
|
75
|
+
if permission_set:
|
|
76
|
+
permission_set['RoleHint'] = (
|
|
77
|
+
f":role/aws-reserved/sso.amazonaws.com/AWSReservedSSO_{permission_set.get('Name')}"
|
|
78
|
+
)
|
|
79
|
+
permission_sets.append(permission_set)
|
|
80
|
+
|
|
81
|
+
return permission_sets
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@timeit
|
|
85
|
+
def get_permission_set_roles(
|
|
86
|
+
boto3_session: boto3.session.Session,
|
|
87
|
+
instance_arn: str,
|
|
88
|
+
permission_set_arn: str,
|
|
89
|
+
region: str,
|
|
90
|
+
) -> List[Dict]:
|
|
91
|
+
"""
|
|
92
|
+
Get all accounts associated with a given permission set
|
|
93
|
+
"""
|
|
94
|
+
client = boto3_session.client('sso-admin', region_name=region)
|
|
95
|
+
accounts = []
|
|
96
|
+
|
|
97
|
+
paginator = client.get_paginator('list_accounts_for_provisioned_permission_set')
|
|
98
|
+
for page in paginator.paginate(InstanceArn=instance_arn, PermissionSetArn=permission_set_arn):
|
|
99
|
+
accounts.extend(page.get('AccountIds', []))
|
|
100
|
+
|
|
101
|
+
return accounts
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@timeit
|
|
105
|
+
def load_permission_sets(
|
|
106
|
+
neo4j_session: neo4j.Session,
|
|
107
|
+
permission_sets: List[Dict],
|
|
108
|
+
instance_arn: str,
|
|
109
|
+
region: str,
|
|
110
|
+
aws_account_id: str,
|
|
111
|
+
aws_update_tag: int,
|
|
112
|
+
) -> None:
|
|
113
|
+
"""
|
|
114
|
+
Load Identity Center permission sets into the graph
|
|
115
|
+
"""
|
|
116
|
+
logger.info(f"Loading {len(permission_sets)} permission sets for instance {instance_arn} in region {region}")
|
|
117
|
+
|
|
118
|
+
load(
|
|
119
|
+
neo4j_session,
|
|
120
|
+
AWSPermissionSetSchema(),
|
|
121
|
+
permission_sets,
|
|
122
|
+
lastupdated=aws_update_tag,
|
|
123
|
+
InstanceArn=instance_arn,
|
|
124
|
+
Region=region,
|
|
125
|
+
AWS_ID=aws_account_id,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@timeit
|
|
130
|
+
def get_sso_users(
|
|
131
|
+
boto3_session: boto3.session.Session,
|
|
132
|
+
identity_store_id: str,
|
|
133
|
+
region: str,
|
|
134
|
+
) -> List[Dict]:
|
|
135
|
+
"""
|
|
136
|
+
Get all SSO users for a given Identity Store
|
|
137
|
+
"""
|
|
138
|
+
client = boto3_session.client('identitystore', region_name=region)
|
|
139
|
+
users = []
|
|
140
|
+
|
|
141
|
+
paginator = client.get_paginator('list_users')
|
|
142
|
+
for page in paginator.paginate(IdentityStoreId=identity_store_id):
|
|
143
|
+
user_page = page.get('Users', [])
|
|
144
|
+
for user in user_page:
|
|
145
|
+
if user.get('ExternalIds', None):
|
|
146
|
+
user['ExternalId'] = user.get('ExternalIds')[0].get('Id')
|
|
147
|
+
users.append(user)
|
|
148
|
+
|
|
149
|
+
return users
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@timeit
|
|
153
|
+
def load_sso_users(
|
|
154
|
+
neo4j_session: neo4j.Session,
|
|
155
|
+
users: List[Dict],
|
|
156
|
+
identity_store_id: str,
|
|
157
|
+
region: str,
|
|
158
|
+
aws_account_id: str,
|
|
159
|
+
aws_update_tag: int,
|
|
160
|
+
) -> None:
|
|
161
|
+
"""
|
|
162
|
+
Load SSO users into the graph
|
|
163
|
+
"""
|
|
164
|
+
logger.info(f"Loading {len(users)} SSO users for identity store {identity_store_id} in region {region}")
|
|
165
|
+
|
|
166
|
+
load(
|
|
167
|
+
neo4j_session,
|
|
168
|
+
AWSSSOUserSchema(),
|
|
169
|
+
users,
|
|
170
|
+
lastupdated=aws_update_tag,
|
|
171
|
+
IdentityStoreId=identity_store_id,
|
|
172
|
+
AWS_ID=aws_account_id,
|
|
173
|
+
Region=region,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@timeit
|
|
178
|
+
def get_role_assignments(
|
|
179
|
+
boto3_session: boto3.session.Session,
|
|
180
|
+
users: List[Dict],
|
|
181
|
+
instance_arn: str,
|
|
182
|
+
region: str,
|
|
183
|
+
) -> List[Dict]:
|
|
184
|
+
"""
|
|
185
|
+
Get role assignments for SSO users
|
|
186
|
+
"""
|
|
187
|
+
|
|
188
|
+
logger.info(f"Getting role assignments for {len(users)} users")
|
|
189
|
+
client = boto3_session.client('sso-admin', region_name=region)
|
|
190
|
+
role_assignments = []
|
|
191
|
+
|
|
192
|
+
for user in users:
|
|
193
|
+
user_id = user['UserId']
|
|
194
|
+
paginator = client.get_paginator('list_account_assignments_for_principal')
|
|
195
|
+
for page in paginator.paginate(InstanceArn=instance_arn, PrincipalId=user_id, PrincipalType='USER'):
|
|
196
|
+
for assignment in page.get('AccountAssignments', []):
|
|
197
|
+
role_assignments.append({
|
|
198
|
+
'UserId': user_id,
|
|
199
|
+
'PermissionSetArn': assignment.get('PermissionSetArn'),
|
|
200
|
+
'AccountId': assignment.get('AccountId'),
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
return role_assignments
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@timeit
|
|
207
|
+
def load_role_assignments(
|
|
208
|
+
neo4j_session: neo4j.Session,
|
|
209
|
+
role_assignments: List[Dict],
|
|
210
|
+
aws_update_tag: int,
|
|
211
|
+
) -> None:
|
|
212
|
+
"""
|
|
213
|
+
Load role assignments into the graph
|
|
214
|
+
"""
|
|
215
|
+
logger.info(f"Loading {len(role_assignments)} role assignments")
|
|
216
|
+
if role_assignments:
|
|
217
|
+
neo4j_session.run(
|
|
218
|
+
"""
|
|
219
|
+
UNWIND $role_assignments AS ra
|
|
220
|
+
MATCH (acc:AWSAccount{id:ra.AccountId}) -[:RESOURCE]->
|
|
221
|
+
(role:AWSRole)<-[:ASSIGNED_TO_ROLE]-
|
|
222
|
+
(permset:AWSPermissionSet {id: ra.PermissionSetArn})
|
|
223
|
+
MATCH (sso:AWSSSOUser {id: ra.UserId})
|
|
224
|
+
MERGE (role)-[r:ALLOWED_BY]->(sso)
|
|
225
|
+
SET r.lastupdated = $aws_update_tag,
|
|
226
|
+
r.permission_set_arn = ra.PermissionSetArn
|
|
227
|
+
""",
|
|
228
|
+
role_assignments=role_assignments,
|
|
229
|
+
aws_update_tag=aws_update_tag,
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def cleanup(neo4j_session: neo4j.Session, common_job_parameters: Dict[str, Any]) -> None:
|
|
234
|
+
GraphJob.from_node_schema(AWSIdentityCenterInstanceSchema(), common_job_parameters).run(neo4j_session)
|
|
235
|
+
GraphJob.from_node_schema(AWSPermissionSetSchema(), common_job_parameters).run(neo4j_session)
|
|
236
|
+
GraphJob.from_node_schema(AWSSSOUserSchema(), common_job_parameters).run(neo4j_session)
|
|
237
|
+
run_cleanup_job(
|
|
238
|
+
'aws_import_identity_center_cleanup.json',
|
|
239
|
+
neo4j_session,
|
|
240
|
+
common_job_parameters,
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
@timeit
|
|
245
|
+
def sync_identity_center_instances(
|
|
246
|
+
neo4j_session: neo4j.Session,
|
|
247
|
+
boto3_session: boto3.session.Session,
|
|
248
|
+
regions: List[str],
|
|
249
|
+
current_aws_account_id: str,
|
|
250
|
+
update_tag: int,
|
|
251
|
+
common_job_parameters: Dict,
|
|
252
|
+
) -> None:
|
|
253
|
+
"""
|
|
254
|
+
Sync Identity Center instances, their permission sets, and SSO users
|
|
255
|
+
"""
|
|
256
|
+
logger.info(f"Syncing Identity Center instances for regions {regions}")
|
|
257
|
+
for region in regions:
|
|
258
|
+
logger.info(f"Syncing Identity Center instances for region {region}")
|
|
259
|
+
instances = get_identity_center_instances(boto3_session, region)
|
|
260
|
+
load_identity_center_instances(
|
|
261
|
+
neo4j_session,
|
|
262
|
+
instances,
|
|
263
|
+
region,
|
|
264
|
+
current_aws_account_id,
|
|
265
|
+
update_tag,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
# For each instance, get and load its permission sets and SSO users
|
|
269
|
+
for instance in instances:
|
|
270
|
+
instance_arn = instance['InstanceArn']
|
|
271
|
+
identity_store_id = instance['IdentityStoreId']
|
|
272
|
+
|
|
273
|
+
permission_sets = get_permission_sets(boto3_session, instance_arn, region)
|
|
274
|
+
|
|
275
|
+
load_permission_sets(
|
|
276
|
+
neo4j_session,
|
|
277
|
+
permission_sets,
|
|
278
|
+
instance_arn,
|
|
279
|
+
region,
|
|
280
|
+
current_aws_account_id,
|
|
281
|
+
update_tag,
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
users = get_sso_users(boto3_session, identity_store_id, region)
|
|
285
|
+
load_sso_users(
|
|
286
|
+
neo4j_session,
|
|
287
|
+
users,
|
|
288
|
+
identity_store_id,
|
|
289
|
+
region,
|
|
290
|
+
current_aws_account_id,
|
|
291
|
+
update_tag,
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
# Get and load role assignments
|
|
295
|
+
role_assignments = get_role_assignments(
|
|
296
|
+
boto3_session,
|
|
297
|
+
users,
|
|
298
|
+
instance_arn,
|
|
299
|
+
region,
|
|
300
|
+
)
|
|
301
|
+
load_role_assignments(
|
|
302
|
+
neo4j_session,
|
|
303
|
+
role_assignments,
|
|
304
|
+
update_tag,
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
cleanup(neo4j_session, common_job_parameters)
|
|
@@ -10,6 +10,7 @@ from . import elasticache
|
|
|
10
10
|
from . import elasticsearch
|
|
11
11
|
from . import emr
|
|
12
12
|
from . import iam
|
|
13
|
+
from . import identitycenter
|
|
13
14
|
from . import inspector
|
|
14
15
|
from . import kms
|
|
15
16
|
from . import lambda_function
|
|
@@ -32,6 +33,7 @@ from .ec2.key_pairs import sync_ec2_key_pairs
|
|
|
32
33
|
from .ec2.launch_templates import sync_ec2_launch_templates
|
|
33
34
|
from .ec2.load_balancer_v2s import sync_load_balancer_v2s
|
|
34
35
|
from .ec2.load_balancers import sync_load_balancers
|
|
36
|
+
from .ec2.network_acls import sync_network_acls
|
|
35
37
|
from .ec2.network_interfaces import sync_network_interfaces
|
|
36
38
|
from .ec2.reserved_instances import sync_ec2_reserved_instances
|
|
37
39
|
from .ec2.security_groups import sync_ec2_security_groupinfo
|
|
@@ -55,6 +57,7 @@ RESOURCE_FUNCTIONS: Dict = {
|
|
|
55
57
|
'ec2:keypair': sync_ec2_key_pairs,
|
|
56
58
|
'ec2:load_balancer': sync_load_balancers,
|
|
57
59
|
'ec2:load_balancer_v2': sync_load_balancer_v2s,
|
|
60
|
+
'ec2:network_acls': sync_network_acls,
|
|
58
61
|
'ec2:network_interface': sync_network_interfaces,
|
|
59
62
|
'ec2:security_group': sync_ec2_security_groupinfo,
|
|
60
63
|
'ec2:subnet': sync_subnets,
|
|
@@ -86,4 +89,5 @@ RESOURCE_FUNCTIONS: Dict = {
|
|
|
86
89
|
'ssm': ssm.sync,
|
|
87
90
|
'inspector': inspector.sync,
|
|
88
91
|
'config': config.sync,
|
|
92
|
+
'identitycenter': identitycenter.sync_identity_center_instances,
|
|
89
93
|
}
|
|
@@ -25,7 +25,7 @@ def start_cve_ingestion(
|
|
|
25
25
|
"""
|
|
26
26
|
if not config.cve_enabled:
|
|
27
27
|
return
|
|
28
|
-
cve_api_key = config.cve_api_key if config.cve_api_key else None
|
|
28
|
+
cve_api_key: str | None = config.cve_api_key if config.cve_api_key else None
|
|
29
29
|
|
|
30
30
|
# sync CVE year archives, if not yet synced
|
|
31
31
|
existing_years = feed.get_cve_sync_metadata(neo4j_session)
|
cartography/intel/cve/feed.py
CHANGED
|
@@ -22,9 +22,9 @@ from cartography.util import timeit
|
|
|
22
22
|
|
|
23
23
|
logger = logging.getLogger(__name__)
|
|
24
24
|
|
|
25
|
-
MAX_RETRIES =
|
|
26
|
-
# Connect and read timeouts of
|
|
27
|
-
CONNECT_AND_READ_TIMEOUT = (
|
|
25
|
+
MAX_RETRIES = 8
|
|
26
|
+
# Connect and read timeouts of 120 seconds each; see https://requests.readthedocs.io/en/master/user/advanced/#timeouts
|
|
27
|
+
CONNECT_AND_READ_TIMEOUT = (30, 120)
|
|
28
28
|
CVE_FEED_ID = "NIST_NVD"
|
|
29
29
|
BATCH_SIZE_DAYS = 120
|
|
30
30
|
RESULTS_PER_PAGE = 2000
|
|
@@ -68,7 +68,7 @@ def _map_cve_dict(cve_dict: Dict[Any, Any], data: Dict[Any, Any]) -> None:
|
|
|
68
68
|
cve_dict["startIndex"] = data["startIndex"]
|
|
69
69
|
|
|
70
70
|
|
|
71
|
-
def _call_cves_api(url: str, api_key: str, params: Dict[str, Any]) -> Dict[Any, Any]:
|
|
71
|
+
def _call_cves_api(url: str, api_key: str | None, params: Dict[str, Any]) -> Dict[Any, Any]:
|
|
72
72
|
totalResults = 0
|
|
73
73
|
sleep_time = DEFAULT_SLEEP_TIME
|
|
74
74
|
retries = 0
|
|
@@ -98,6 +98,9 @@ def _call_cves_api(url: str, api_key: str, params: Dict[str, Any]) -> Dict[Any,
|
|
|
98
98
|
retries += 1
|
|
99
99
|
if retries >= MAX_RETRIES:
|
|
100
100
|
raise
|
|
101
|
+
# Exponential backoff
|
|
102
|
+
sleep_time *= 2
|
|
103
|
+
time.sleep(sleep_time)
|
|
101
104
|
continue
|
|
102
105
|
data = res.json()
|
|
103
106
|
_map_cve_dict(results, data)
|
|
@@ -114,7 +117,7 @@ def get_cves_in_batches(
|
|
|
114
117
|
start_date: datetime,
|
|
115
118
|
end_date: datetime,
|
|
116
119
|
date_param_names: Dict[str, str],
|
|
117
|
-
api_key: str,
|
|
120
|
+
api_key: str | None,
|
|
118
121
|
) -> Dict[Any, Any]:
|
|
119
122
|
cves: Dict[Any, Any] = dict()
|
|
120
123
|
current_start_date: datetime = start_date
|
|
@@ -153,7 +156,7 @@ def get_cves_in_batches(
|
|
|
153
156
|
|
|
154
157
|
|
|
155
158
|
def get_modified_cves(
|
|
156
|
-
nist_cve_url: str, last_modified_date: str, api_key: str,
|
|
159
|
+
nist_cve_url: str, last_modified_date: str, api_key: str | None,
|
|
157
160
|
) -> Dict[Any, Any]:
|
|
158
161
|
cves = dict()
|
|
159
162
|
end_date = datetime.now(tz=timezone.utc)
|
|
@@ -171,7 +174,7 @@ def get_modified_cves(
|
|
|
171
174
|
|
|
172
175
|
|
|
173
176
|
def get_published_cves_per_year(
|
|
174
|
-
nist_cve_url: str, year: str, api_key: str,
|
|
177
|
+
nist_cve_url: str, year: str, api_key: str | None,
|
|
175
178
|
) -> Dict[Any, Any]:
|
|
176
179
|
cves = {}
|
|
177
180
|
start_of_year = datetime.strptime(f"{year}-01-01", "%Y-%m-%d")
|