cartography 0.102.0rc1__py3-none-any.whl → 0.103.0rc1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of cartography might be problematic. Click here for more details.
- cartography/__main__.py +1 -2
- cartography/_version.py +2 -2
- cartography/cli.py +302 -253
- cartography/client/core/tx.py +39 -18
- cartography/config.py +4 -0
- cartography/driftdetect/__main__.py +1 -2
- cartography/driftdetect/add_shortcut.py +10 -2
- cartography/driftdetect/cli.py +71 -75
- cartography/driftdetect/detect_deviations.py +7 -3
- cartography/driftdetect/get_states.py +20 -8
- cartography/driftdetect/model.py +5 -5
- cartography/driftdetect/serializers.py +8 -6
- cartography/driftdetect/storage.py +2 -2
- cartography/graph/cleanupbuilder.py +35 -15
- cartography/graph/job.py +46 -17
- cartography/graph/querybuilder.py +165 -80
- cartography/graph/statement.py +35 -26
- cartography/intel/analysis.py +4 -1
- cartography/intel/aws/__init__.py +114 -55
- cartography/intel/aws/apigateway.py +134 -63
- cartography/intel/aws/cloudtrail.py +127 -0
- cartography/intel/aws/config.py +56 -20
- cartography/intel/aws/dynamodb.py +108 -40
- cartography/intel/aws/ec2/__init__.py +2 -2
- cartography/intel/aws/ec2/auto_scaling_groups.py +181 -78
- cartography/intel/aws/ec2/elastic_ip_addresses.py +41 -13
- cartography/intel/aws/ec2/images.py +49 -20
- cartography/intel/aws/ec2/instances.py +234 -136
- cartography/intel/aws/ec2/internet_gateways.py +40 -11
- cartography/intel/aws/ec2/key_pairs.py +44 -20
- cartography/intel/aws/ec2/launch_templates.py +101 -59
- cartography/intel/aws/ec2/load_balancer_v2s.py +104 -39
- cartography/intel/aws/ec2/load_balancers.py +82 -42
- cartography/intel/aws/ec2/network_acls.py +89 -65
- cartography/intel/aws/ec2/network_interfaces.py +146 -87
- cartography/intel/aws/ec2/reserved_instances.py +45 -16
- cartography/intel/aws/ec2/route_tables.py +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/eks.py +55 -29
- cartography/intel/aws/elasticache.py +42 -18
- cartography/intel/aws/elasticsearch.py +57 -20
- cartography/intel/aws/emr.py +61 -23
- cartography/intel/aws/iam.py +401 -145
- cartography/intel/aws/iam_instance_profiles.py +22 -22
- cartography/intel/aws/identitycenter.py +71 -37
- cartography/intel/aws/inspector.py +159 -89
- cartography/intel/aws/kms.py +92 -38
- cartography/intel/aws/lambda_function.py +103 -34
- cartography/intel/aws/organizations.py +30 -10
- cartography/intel/aws/permission_relationships.py +133 -51
- cartography/intel/aws/rds.py +249 -85
- cartography/intel/aws/redshift.py +107 -46
- cartography/intel/aws/resourcegroupstaggingapi.py +120 -66
- cartography/intel/aws/resources.py +53 -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/create_indexes.py +3 -2
- cartography/intel/crowdstrike/__init__.py +11 -9
- cartography/intel/crowdstrike/endpoints.py +5 -1
- cartography/intel/crowdstrike/spotlight.py +8 -3
- cartography/intel/cve/__init__.py +46 -13
- cartography/intel/cve/feed.py +48 -12
- cartography/intel/digitalocean/__init__.py +22 -13
- cartography/intel/digitalocean/compute.py +75 -108
- cartography/intel/digitalocean/management.py +44 -80
- cartography/intel/digitalocean/platform.py +48 -43
- cartography/intel/dns.py +36 -10
- cartography/intel/duo/__init__.py +21 -16
- cartography/intel/duo/api_host.py +14 -9
- cartography/intel/duo/endpoints.py +50 -45
- cartography/intel/duo/groups.py +18 -14
- cartography/intel/duo/phones.py +37 -34
- cartography/intel/duo/tokens.py +26 -23
- cartography/intel/duo/users.py +54 -50
- cartography/intel/duo/web_authn_credentials.py +30 -25
- cartography/intel/entra/__init__.py +25 -7
- cartography/intel/entra/ou.py +112 -0
- cartography/intel/entra/users.py +69 -63
- cartography/intel/gcp/__init__.py +185 -49
- cartography/intel/gcp/compute.py +418 -231
- cartography/intel/gcp/crm.py +96 -43
- cartography/intel/gcp/dns.py +60 -19
- cartography/intel/gcp/gke.py +72 -38
- cartography/intel/gcp/iam.py +61 -41
- cartography/intel/gcp/storage.py +84 -55
- cartography/intel/github/__init__.py +13 -11
- cartography/intel/github/repos.py +270 -137
- cartography/intel/github/teams.py +170 -88
- cartography/intel/github/users.py +70 -39
- cartography/intel/github/util.py +36 -34
- cartography/intel/gsuite/__init__.py +47 -26
- cartography/intel/gsuite/api.py +73 -30
- cartography/intel/jamf/__init__.py +19 -1
- cartography/intel/jamf/computers.py +30 -7
- cartography/intel/jamf/util.py +7 -2
- cartography/intel/kandji/__init__.py +6 -3
- cartography/intel/kandji/devices.py +14 -8
- cartography/intel/kubernetes/namespaces.py +7 -4
- cartography/intel/kubernetes/pods.py +7 -4
- cartography/intel/kubernetes/services.py +8 -4
- cartography/intel/lastpass/__init__.py +2 -2
- cartography/intel/lastpass/users.py +23 -12
- cartography/intel/oci/__init__.py +44 -11
- cartography/intel/oci/iam.py +134 -38
- cartography/intel/oci/organizations.py +13 -6
- cartography/intel/oci/utils.py +43 -20
- cartography/intel/okta/__init__.py +66 -15
- cartography/intel/okta/applications.py +42 -20
- cartography/intel/okta/awssaml.py +93 -33
- cartography/intel/okta/factors.py +16 -4
- cartography/intel/okta/groups.py +56 -29
- cartography/intel/okta/organization.py +5 -1
- cartography/intel/okta/origins.py +6 -2
- cartography/intel/okta/roles.py +15 -5
- cartography/intel/okta/users.py +20 -8
- cartography/intel/okta/utils.py +6 -4
- cartography/intel/pagerduty/__init__.py +8 -7
- cartography/intel/pagerduty/escalation_policies.py +18 -6
- cartography/intel/pagerduty/schedules.py +12 -4
- cartography/intel/pagerduty/services.py +11 -4
- cartography/intel/pagerduty/teams.py +8 -3
- cartography/intel/pagerduty/users.py +3 -1
- cartography/intel/pagerduty/vendors.py +3 -1
- cartography/intel/semgrep/__init__.py +24 -6
- cartography/intel/semgrep/dependencies.py +50 -28
- cartography/intel/semgrep/deployment.py +3 -1
- cartography/intel/semgrep/findings.py +42 -18
- cartography/intel/snipeit/__init__.py +17 -3
- cartography/intel/snipeit/asset.py +12 -6
- cartography/intel/snipeit/user.py +8 -5
- cartography/intel/snipeit/util.py +9 -4
- cartography/models/aws/apigateway.py +21 -17
- cartography/models/aws/apigatewaycertificate.py +28 -22
- cartography/models/aws/apigatewayresource.py +28 -20
- cartography/models/aws/apigatewaystage.py +33 -25
- cartography/models/aws/cloudtrail/__init__.py +0 -0
- cartography/models/aws/cloudtrail/trail.py +61 -0
- cartography/models/aws/dynamodb/gsi.py +30 -22
- cartography/models/aws/dynamodb/tables.py +25 -17
- cartography/models/aws/ec2/auto_scaling_groups.py +102 -82
- cartography/models/aws/ec2/images.py +36 -34
- cartography/models/aws/ec2/instances.py +51 -45
- cartography/models/aws/ec2/keypair.py +21 -16
- cartography/models/aws/ec2/keypair_instance.py +28 -21
- cartography/models/aws/ec2/launch_configurations.py +30 -26
- cartography/models/aws/ec2/launch_template_versions.py +48 -38
- cartography/models/aws/ec2/launch_templates.py +21 -17
- cartography/models/aws/ec2/load_balancer_listeners.py +27 -23
- cartography/models/aws/ec2/load_balancers.py +47 -37
- cartography/models/aws/ec2/network_acl_rules.py +38 -30
- cartography/models/aws/ec2/network_acls.py +38 -29
- cartography/models/aws/ec2/networkinterface_instance.py +52 -39
- cartography/models/aws/ec2/networkinterfaces.py +53 -37
- cartography/models/aws/ec2/privateip_networkinterface.py +32 -22
- cartography/models/aws/ec2/reservations.py +18 -14
- cartography/models/aws/ec2/route_table_associations.py +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/eks/clusters.py +23 -21
- cartography/models/aws/emr.py +32 -30
- cartography/models/aws/iam/instanceprofile.py +33 -24
- cartography/models/aws/identitycenter/awsidentitycenter.py +18 -14
- cartography/models/aws/identitycenter/awspermissionset.py +37 -29
- cartography/models/aws/identitycenter/awsssouser.py +23 -21
- cartography/models/aws/inspector/findings.py +77 -65
- cartography/models/aws/inspector/packages.py +35 -29
- cartography/models/aws/s3/__init__.py +0 -0
- cartography/models/aws/s3/account_public_access_block.py +51 -0
- cartography/models/aws/sns/__init__.py +0 -0
- cartography/models/aws/sns/topic.py +50 -0
- cartography/models/aws/ssm/instance_information.py +51 -39
- cartography/models/aws/ssm/instance_patch.py +32 -26
- cartography/models/bigfix/bigfix_computer.py +42 -38
- cartography/models/bigfix/bigfix_root.py +3 -3
- cartography/models/core/common.py +12 -10
- cartography/models/core/nodes.py +5 -2
- cartography/models/core/relationships.py +14 -6
- cartography/models/crowdstrike/hosts.py +37 -35
- cartography/models/cve/cve.py +34 -32
- cartography/models/cve/cve_feed.py +6 -6
- cartography/models/digitalocean/__init__.py +0 -0
- cartography/models/digitalocean/account.py +21 -0
- cartography/models/digitalocean/droplet.py +56 -0
- cartography/models/digitalocean/project.py +48 -0
- cartography/models/duo/api_host.py +3 -3
- cartography/models/duo/endpoint.py +43 -41
- cartography/models/duo/group.py +14 -14
- cartography/models/duo/phone.py +27 -27
- cartography/models/duo/token.py +16 -16
- cartography/models/duo/user.py +46 -44
- cartography/models/duo/web_authn_credential.py +27 -19
- cartography/models/entra/ou.py +48 -0
- cartography/models/entra/tenant.py +24 -18
- cartography/models/entra/user.py +64 -48
- cartography/models/gcp/iam.py +23 -23
- cartography/models/github/orgs.py +5 -4
- cartography/models/github/teams.py +37 -31
- cartography/models/github/users.py +34 -23
- cartography/models/kandji/device.py +22 -16
- cartography/models/kandji/tenant.py +6 -4
- cartography/models/lastpass/tenant.py +3 -3
- cartography/models/lastpass/user.py +32 -28
- cartography/models/semgrep/dependencies.py +36 -24
- cartography/models/semgrep/deployment.py +5 -5
- cartography/models/semgrep/findings.py +58 -42
- cartography/models/semgrep/locations.py +27 -21
- cartography/models/snipeit/asset.py +30 -21
- cartography/models/snipeit/tenant.py +6 -4
- cartography/models/snipeit/user.py +19 -12
- cartography/stats.py +3 -3
- cartography/sync.py +107 -31
- cartography/util.py +84 -62
- {cartography-0.102.0rc1.dist-info → cartography-0.103.0rc1.dist-info}/METADATA +3 -14
- cartography-0.103.0rc1.dist-info/RECORD +396 -0
- {cartography-0.102.0rc1.dist-info → cartography-0.103.0rc1.dist-info}/WHEEL +1 -1
- cartography-0.102.0rc1.dist-info/RECORD +0 -377
- {cartography-0.102.0rc1.dist-info → cartography-0.103.0rc1.dist-info}/entry_points.txt +0 -0
- {cartography-0.102.0rc1.dist-info → cartography-0.103.0rc1.dist-info}/licenses/LICENSE +0 -0
- {cartography-0.102.0rc1.dist-info → cartography-0.103.0rc1.dist-info}/top_level.txt +0 -0
cartography/intel/duo/groups.py
CHANGED
|
@@ -11,7 +11,6 @@ from cartography.graph.job import GraphJob
|
|
|
11
11
|
from cartography.models.duo.group import DuoGroupSchema
|
|
12
12
|
from cartography.util import timeit
|
|
13
13
|
|
|
14
|
-
|
|
15
14
|
logger = logging.getLogger(__name__)
|
|
16
15
|
|
|
17
16
|
|
|
@@ -21,9 +20,9 @@ def sync_duo_groups(
|
|
|
21
20
|
neo4j_session: neo4j.Session,
|
|
22
21
|
common_job_parameters: Dict[str, Any],
|
|
23
22
|
) -> None:
|
|
24
|
-
|
|
23
|
+
"""
|
|
25
24
|
Sync Duo groups
|
|
26
|
-
|
|
25
|
+
"""
|
|
27
26
|
groups = _get_groups(client)
|
|
28
27
|
_load_groups(neo4j_session, groups, common_job_parameters)
|
|
29
28
|
_cleanup_groups(neo4j_session, common_job_parameters)
|
|
@@ -31,10 +30,10 @@ def sync_duo_groups(
|
|
|
31
30
|
|
|
32
31
|
@timeit
|
|
33
32
|
def _get_groups(client: duo_client.Admin) -> List[Dict[str, Any]]:
|
|
34
|
-
|
|
33
|
+
"""
|
|
35
34
|
Fetch all group data
|
|
36
35
|
https://duo.com/docs/adminapi#users
|
|
37
|
-
|
|
36
|
+
"""
|
|
38
37
|
logger.info("Fetching Duo groups")
|
|
39
38
|
return client.get_groups()
|
|
40
39
|
|
|
@@ -45,22 +44,27 @@ def _load_groups(
|
|
|
45
44
|
groups: List[Dict[str, Any]],
|
|
46
45
|
common_job_parameters: Dict[str, Any],
|
|
47
46
|
) -> None:
|
|
48
|
-
|
|
47
|
+
"""
|
|
49
48
|
Load the groups into the graph
|
|
50
|
-
|
|
51
|
-
logger.info(f
|
|
49
|
+
"""
|
|
50
|
+
logger.info(f"Loading {len(groups)} duo groups")
|
|
52
51
|
load(
|
|
53
52
|
neo4j_session,
|
|
54
53
|
DuoGroupSchema(),
|
|
55
54
|
groups,
|
|
56
|
-
DUO_API_HOSTNAME=common_job_parameters[
|
|
57
|
-
lastupdated=common_job_parameters[
|
|
55
|
+
DUO_API_HOSTNAME=common_job_parameters["DUO_API_HOSTNAME"],
|
|
56
|
+
lastupdated=common_job_parameters["UPDATE_TAG"],
|
|
58
57
|
)
|
|
59
58
|
|
|
60
59
|
|
|
61
60
|
@timeit
|
|
62
|
-
def _cleanup_groups(
|
|
63
|
-
|
|
61
|
+
def _cleanup_groups(
|
|
62
|
+
neo4j_session: neo4j.Session,
|
|
63
|
+
common_job_parameters: Dict[str, Any],
|
|
64
|
+
) -> None:
|
|
65
|
+
"""
|
|
64
66
|
Cleanup endpoints
|
|
65
|
-
|
|
66
|
-
GraphJob.from_node_schema(DuoGroupSchema(), common_job_parameters).run(
|
|
67
|
+
"""
|
|
68
|
+
GraphJob.from_node_schema(DuoGroupSchema(), common_job_parameters).run(
|
|
69
|
+
neo4j_session,
|
|
70
|
+
)
|
cartography/intel/duo/phones.py
CHANGED
|
@@ -20,9 +20,9 @@ def sync(
|
|
|
20
20
|
neo4j_session: neo4j.Session,
|
|
21
21
|
common_job_parameters: Dict[str, Any],
|
|
22
22
|
) -> None:
|
|
23
|
-
|
|
23
|
+
"""
|
|
24
24
|
Sync
|
|
25
|
-
|
|
25
|
+
"""
|
|
26
26
|
data = _get(client)
|
|
27
27
|
transformed_data = _transform(data)
|
|
28
28
|
_load(neo4j_session, transformed_data, common_job_parameters)
|
|
@@ -31,45 +31,45 @@ def sync(
|
|
|
31
31
|
|
|
32
32
|
@timeit
|
|
33
33
|
def _get(client: duo_client.Admin) -> List[Dict[str, Any]]:
|
|
34
|
-
|
|
34
|
+
"""
|
|
35
35
|
Fetch all data
|
|
36
36
|
https://duo.com/docs/adminapi#endpoints
|
|
37
|
-
|
|
38
|
-
logger.info(f
|
|
37
|
+
"""
|
|
38
|
+
logger.info(f"Fetching data for {Schema.label}")
|
|
39
39
|
return client.get_phones()
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
@timeit
|
|
43
43
|
def _transform(data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
44
|
-
|
|
44
|
+
"""
|
|
45
45
|
Reformat the data before loading
|
|
46
|
-
|
|
47
|
-
logger.info(f
|
|
46
|
+
"""
|
|
47
|
+
logger.info(f"Transforming {len(data)} items for {Schema.label}")
|
|
48
48
|
transformed_data = []
|
|
49
49
|
for datum in data:
|
|
50
50
|
transformed_datum = {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
51
|
+
"activated": datum["activated"],
|
|
52
|
+
"capabilities": datum["capabilities"],
|
|
53
|
+
"encrypted": datum["encrypted"],
|
|
54
|
+
"extension": datum["extension"],
|
|
55
|
+
"fingerprint": datum["fingerprint"],
|
|
56
|
+
"last_seen": datum["last_seen"],
|
|
57
|
+
"model": datum["model"],
|
|
58
|
+
"name": datum["name"],
|
|
59
|
+
"phone_id": datum["phone_id"],
|
|
60
|
+
"platform": datum["platform"],
|
|
61
|
+
"postdelay": datum["postdelay"],
|
|
62
|
+
"predelay": datum["predelay"],
|
|
63
|
+
"screenlock": datum["screenlock"],
|
|
64
|
+
"sms_passcodes_sent": datum["sms_passcodes_sent"],
|
|
65
|
+
"tampered": datum["tampered"],
|
|
66
|
+
"type": datum["type"],
|
|
67
67
|
}
|
|
68
68
|
transformed_data.append(transformed_datum)
|
|
69
|
-
for user in datum[
|
|
69
|
+
for user in datum["users"]:
|
|
70
70
|
match_datum = {
|
|
71
71
|
**transformed_datum,
|
|
72
|
-
|
|
72
|
+
"user_id": user["user_id"],
|
|
73
73
|
}
|
|
74
74
|
transformed_data.append(match_datum)
|
|
75
75
|
return transformed_data
|
|
@@ -81,22 +81,25 @@ def _load(
|
|
|
81
81
|
data: List[Dict[str, Any]],
|
|
82
82
|
common_job_parameters: Dict[str, Any],
|
|
83
83
|
) -> None:
|
|
84
|
-
|
|
84
|
+
"""
|
|
85
85
|
Load the data into the database
|
|
86
|
-
|
|
87
|
-
logger.info(f
|
|
86
|
+
"""
|
|
87
|
+
logger.info(f"Loading {len(data)} items for {Schema.label}")
|
|
88
88
|
load(
|
|
89
89
|
neo4j_session,
|
|
90
90
|
Schema(),
|
|
91
91
|
data,
|
|
92
|
-
DUO_API_HOSTNAME=common_job_parameters[
|
|
93
|
-
lastupdated=common_job_parameters[
|
|
92
|
+
DUO_API_HOSTNAME=common_job_parameters["DUO_API_HOSTNAME"],
|
|
93
|
+
lastupdated=common_job_parameters["UPDATE_TAG"],
|
|
94
94
|
)
|
|
95
95
|
|
|
96
96
|
|
|
97
97
|
@timeit
|
|
98
|
-
def _cleanup(
|
|
99
|
-
|
|
98
|
+
def _cleanup(
|
|
99
|
+
neo4j_session: neo4j.Session,
|
|
100
|
+
common_job_parameters: Dict[str, Any],
|
|
101
|
+
) -> None:
|
|
102
|
+
"""
|
|
100
103
|
Cleanup nodes
|
|
101
|
-
|
|
104
|
+
"""
|
|
102
105
|
GraphJob.from_node_schema(Schema(), common_job_parameters).run(neo4j_session)
|
cartography/intel/duo/tokens.py
CHANGED
|
@@ -21,9 +21,9 @@ def sync(
|
|
|
21
21
|
neo4j_session: neo4j.Session,
|
|
22
22
|
common_job_parameters: Dict[str, Any],
|
|
23
23
|
) -> None:
|
|
24
|
-
|
|
24
|
+
"""
|
|
25
25
|
Sync
|
|
26
|
-
|
|
26
|
+
"""
|
|
27
27
|
data = _get(client)
|
|
28
28
|
transformed_data = _transform(data)
|
|
29
29
|
_load(neo4j_session, transformed_data, common_job_parameters)
|
|
@@ -32,34 +32,34 @@ def sync(
|
|
|
32
32
|
|
|
33
33
|
@timeit
|
|
34
34
|
def _get(client: duo_client.Admin) -> List[Dict[str, Any]]:
|
|
35
|
-
|
|
35
|
+
"""
|
|
36
36
|
Fetch all data
|
|
37
37
|
https://duo.com/docs/adminapi#endpoints
|
|
38
|
-
|
|
39
|
-
logger.info(f
|
|
38
|
+
"""
|
|
39
|
+
logger.info(f"Fetching data for {Schema.label}")
|
|
40
40
|
return client.get_tokens()
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
@timeit
|
|
44
44
|
def _transform(data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
45
|
-
|
|
45
|
+
"""
|
|
46
46
|
Reformat the data before loading
|
|
47
|
-
|
|
48
|
-
logger.info(f
|
|
47
|
+
"""
|
|
48
|
+
logger.info(f"Transforming {len(data)} items for {Schema.label}")
|
|
49
49
|
transformed_data = []
|
|
50
50
|
for datum in data:
|
|
51
51
|
transformed_datum = {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
"admins": json.dumps(datum["admins"]),
|
|
53
|
+
"serial": datum["serial"],
|
|
54
|
+
"token_id": datum["token_id"],
|
|
55
|
+
"totp_step": datum["totp_step"],
|
|
56
|
+
"type": datum["type"],
|
|
57
57
|
}
|
|
58
58
|
transformed_data.append(transformed_datum)
|
|
59
|
-
for user in datum[
|
|
59
|
+
for user in datum["users"]:
|
|
60
60
|
match_datum = {
|
|
61
61
|
**transformed_datum,
|
|
62
|
-
|
|
62
|
+
"user_id": user["user_id"],
|
|
63
63
|
}
|
|
64
64
|
transformed_data.append(match_datum)
|
|
65
65
|
return transformed_data
|
|
@@ -71,22 +71,25 @@ def _load(
|
|
|
71
71
|
data: List[Dict[str, Any]],
|
|
72
72
|
common_job_parameters: Dict[str, Any],
|
|
73
73
|
) -> None:
|
|
74
|
-
|
|
74
|
+
"""
|
|
75
75
|
Load the data into the database
|
|
76
|
-
|
|
77
|
-
logger.info(f
|
|
76
|
+
"""
|
|
77
|
+
logger.info(f"Loading {len(data)} items for {Schema.label}")
|
|
78
78
|
load(
|
|
79
79
|
neo4j_session,
|
|
80
80
|
Schema(),
|
|
81
81
|
data,
|
|
82
|
-
DUO_API_HOSTNAME=common_job_parameters[
|
|
83
|
-
lastupdated=common_job_parameters[
|
|
82
|
+
DUO_API_HOSTNAME=common_job_parameters["DUO_API_HOSTNAME"],
|
|
83
|
+
lastupdated=common_job_parameters["UPDATE_TAG"],
|
|
84
84
|
)
|
|
85
85
|
|
|
86
86
|
|
|
87
87
|
@timeit
|
|
88
|
-
def _cleanup(
|
|
89
|
-
|
|
88
|
+
def _cleanup(
|
|
89
|
+
neo4j_session: neo4j.Session,
|
|
90
|
+
common_job_parameters: Dict[str, Any],
|
|
91
|
+
) -> None:
|
|
92
|
+
"""
|
|
90
93
|
Cleanup nodes
|
|
91
|
-
|
|
94
|
+
"""
|
|
92
95
|
GraphJob.from_node_schema(Schema(), common_job_parameters).run(neo4j_session)
|
cartography/intel/duo/users.py
CHANGED
|
@@ -21,9 +21,9 @@ def sync_duo_users(
|
|
|
21
21
|
neo4j_session: neo4j.Session,
|
|
22
22
|
common_job_parameters: Dict[str, Any],
|
|
23
23
|
) -> None:
|
|
24
|
-
|
|
24
|
+
"""
|
|
25
25
|
Sync Duo Users
|
|
26
|
-
|
|
26
|
+
"""
|
|
27
27
|
users = _get_users(client)
|
|
28
28
|
transformed_users = _transform_users(users)
|
|
29
29
|
_load_users(neo4j_session, transformed_users, common_job_parameters)
|
|
@@ -32,77 +32,78 @@ def sync_duo_users(
|
|
|
32
32
|
|
|
33
33
|
@timeit
|
|
34
34
|
def _get_users(client: duo_client.Admin) -> List[Dict[str, Any]]:
|
|
35
|
-
|
|
35
|
+
"""
|
|
36
36
|
Fetch all users data
|
|
37
37
|
https://duo.com/docs/adminapi#users
|
|
38
|
-
|
|
38
|
+
"""
|
|
39
39
|
logger.info("Fetching Duo users")
|
|
40
40
|
return client.get_users()
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
@timeit
|
|
44
44
|
def _transform_users(users: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
45
|
-
|
|
45
|
+
"""
|
|
46
46
|
Reformat the data before loading
|
|
47
|
-
|
|
48
|
-
logger.info(f
|
|
47
|
+
"""
|
|
48
|
+
logger.info(f"Transforming {len(users)} duo users")
|
|
49
49
|
transformed_users = []
|
|
50
50
|
for user in users:
|
|
51
51
|
transformed_user = {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
dumps(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
52
|
+
"alias1": user["alias1"],
|
|
53
|
+
"alias2": user["alias2"],
|
|
54
|
+
"alias3": user["alias3"],
|
|
55
|
+
"alias4": user["alias4"],
|
|
56
|
+
"created": user["created"],
|
|
57
|
+
"email": user["email"],
|
|
58
|
+
"firstname": user["firstname"],
|
|
59
|
+
"is_enrolled": user["is_enrolled"],
|
|
60
|
+
"last_directory_sync": user["last_directory_sync"],
|
|
61
|
+
"last_login": user["last_login"],
|
|
62
|
+
"lastname": user["lastname"],
|
|
63
|
+
"notes": user["notes"],
|
|
64
|
+
"phones": [
|
|
65
|
+
dumps(
|
|
66
|
+
{
|
|
67
|
+
**phone,
|
|
68
|
+
"number": None,
|
|
69
|
+
},
|
|
70
|
+
)
|
|
71
|
+
for phone in user["phones"]
|
|
70
72
|
],
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
73
|
+
"realname": user["realname"],
|
|
74
|
+
"status": user["status"],
|
|
75
|
+
"tokens": [dumps(token) for token in user["tokens"]],
|
|
76
|
+
"u2ftokens": [dumps(u2ftoken) for u2ftoken in user["u2ftokens"]],
|
|
77
|
+
"user_id": user["user_id"],
|
|
78
|
+
"username": user["username"],
|
|
79
|
+
"webauthncredentials": [
|
|
78
80
|
dumps(webauthncredential)
|
|
79
|
-
for webauthncredential
|
|
80
|
-
in user['webauthncredentials']
|
|
81
|
+
for webauthncredential in user["webauthncredentials"]
|
|
81
82
|
],
|
|
82
83
|
}
|
|
83
84
|
transformed_users.append(transformed_user)
|
|
84
|
-
for group in user[
|
|
85
|
+
for group in user["groups"]:
|
|
85
86
|
match_user = {
|
|
86
87
|
**transformed_user,
|
|
87
|
-
|
|
88
|
+
"group_id": group["group_id"],
|
|
88
89
|
}
|
|
89
90
|
transformed_users.append(match_user)
|
|
90
|
-
for phone in user[
|
|
91
|
+
for phone in user["phones"]:
|
|
91
92
|
match_user = {
|
|
92
93
|
**transformed_user,
|
|
93
|
-
|
|
94
|
+
"phone_id": phone["phone_id"],
|
|
94
95
|
}
|
|
95
96
|
transformed_users.append(match_user)
|
|
96
|
-
for token in user[
|
|
97
|
+
for token in user["tokens"]:
|
|
97
98
|
match_user = {
|
|
98
99
|
**transformed_user,
|
|
99
|
-
|
|
100
|
+
"token_id": token["token_id"],
|
|
100
101
|
}
|
|
101
102
|
transformed_users.append(match_user)
|
|
102
|
-
for webauthncredential in user[
|
|
103
|
+
for webauthncredential in user["webauthncredentials"]:
|
|
103
104
|
match_user = {
|
|
104
105
|
**transformed_user,
|
|
105
|
-
|
|
106
|
+
"webauthnkey": webauthncredential["webauthnkey"],
|
|
106
107
|
}
|
|
107
108
|
transformed_users.append(match_user)
|
|
108
109
|
return transformed_users
|
|
@@ -114,22 +115,25 @@ def _load_users(
|
|
|
114
115
|
users: List[Dict[str, Any]],
|
|
115
116
|
common_job_parameters: Dict[str, Any],
|
|
116
117
|
) -> None:
|
|
117
|
-
|
|
118
|
+
"""
|
|
118
119
|
Load the users into the database
|
|
119
|
-
|
|
120
|
-
logger.info(f
|
|
120
|
+
"""
|
|
121
|
+
logger.info(f"Loading {len(users)} duo users")
|
|
121
122
|
load(
|
|
122
123
|
neo4j_session,
|
|
123
124
|
DuoUserSchema(),
|
|
124
125
|
users,
|
|
125
|
-
DUO_API_HOSTNAME=common_job_parameters[
|
|
126
|
-
lastupdated=common_job_parameters[
|
|
126
|
+
DUO_API_HOSTNAME=common_job_parameters["DUO_API_HOSTNAME"],
|
|
127
|
+
lastupdated=common_job_parameters["UPDATE_TAG"],
|
|
127
128
|
)
|
|
128
129
|
|
|
129
130
|
|
|
130
131
|
@timeit
|
|
131
|
-
def _cleanup_users(
|
|
132
|
-
|
|
132
|
+
def _cleanup_users(
|
|
133
|
+
neo4j_session: neo4j.Session,
|
|
134
|
+
common_job_parameters: Dict[str, Any],
|
|
135
|
+
) -> None:
|
|
136
|
+
"""
|
|
133
137
|
Cleanup endpoints
|
|
134
|
-
|
|
138
|
+
"""
|
|
135
139
|
GraphJob.from_node_schema(DuoUserSchema(), common_job_parameters).run(neo4j_session)
|
|
@@ -8,7 +8,9 @@ import neo4j
|
|
|
8
8
|
|
|
9
9
|
from cartography.client.core.tx import load
|
|
10
10
|
from cartography.graph.job import GraphJob
|
|
11
|
-
from cartography.models.duo.web_authn_credential import
|
|
11
|
+
from cartography.models.duo.web_authn_credential import (
|
|
12
|
+
DuoWebAuthnCredentialSchema as Schema,
|
|
13
|
+
)
|
|
12
14
|
from cartography.util import timeit
|
|
13
15
|
|
|
14
16
|
logger = logging.getLogger(__name__)
|
|
@@ -20,9 +22,9 @@ def sync(
|
|
|
20
22
|
neo4j_session: neo4j.Session,
|
|
21
23
|
common_job_parameters: Dict[str, Any],
|
|
22
24
|
) -> None:
|
|
23
|
-
|
|
25
|
+
"""
|
|
24
26
|
Sync
|
|
25
|
-
|
|
27
|
+
"""
|
|
26
28
|
data = _get(client)
|
|
27
29
|
transformed_data = _transform(data)
|
|
28
30
|
_load(neo4j_session, transformed_data, common_job_parameters)
|
|
@@ -31,37 +33,37 @@ def sync(
|
|
|
31
33
|
|
|
32
34
|
@timeit
|
|
33
35
|
def _get(client: duo_client.Admin) -> List[Dict[str, Any]]:
|
|
34
|
-
|
|
36
|
+
"""
|
|
35
37
|
Fetch all data
|
|
36
38
|
https://duo.com/docs/adminapi#endpoints
|
|
37
|
-
|
|
38
|
-
logger.info(f
|
|
39
|
+
"""
|
|
40
|
+
logger.info(f"Fetching data for {Schema.label}")
|
|
39
41
|
return client.get_webauthncredentials()
|
|
40
42
|
|
|
41
43
|
|
|
42
44
|
@timeit
|
|
43
45
|
def _transform(data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
44
|
-
|
|
46
|
+
"""
|
|
45
47
|
Reformat the data before loading
|
|
46
|
-
|
|
47
|
-
logger.info(f
|
|
48
|
+
"""
|
|
49
|
+
logger.info(f"Transforming {len(data)} items for {Schema.label}")
|
|
48
50
|
transformed_data = []
|
|
49
51
|
for datum in data:
|
|
50
52
|
transformed_datum = {
|
|
51
53
|
# The admin property may be null if the cred is attached to a user
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
"admin": datum.get("admin"),
|
|
55
|
+
"credential_name": datum["credential_name"],
|
|
56
|
+
"date_added": datum["date_added"],
|
|
57
|
+
"label": datum["label"],
|
|
58
|
+
"user": datum["user"],
|
|
59
|
+
"webauthnkey": datum["webauthnkey"],
|
|
58
60
|
}
|
|
59
61
|
transformed_data.append(transformed_datum)
|
|
60
62
|
# The user property may be null if the cred is attached to an admin
|
|
61
|
-
if datum.get(
|
|
63
|
+
if datum.get("user"):
|
|
62
64
|
match_datum = {
|
|
63
65
|
**transformed_datum,
|
|
64
|
-
|
|
66
|
+
"user_id": datum["user"]["user_id"],
|
|
65
67
|
}
|
|
66
68
|
transformed_data.append(match_datum)
|
|
67
69
|
return transformed_data
|
|
@@ -73,22 +75,25 @@ def _load(
|
|
|
73
75
|
data: List[Dict[str, Any]],
|
|
74
76
|
common_job_parameters: Dict[str, Any],
|
|
75
77
|
) -> None:
|
|
76
|
-
|
|
78
|
+
"""
|
|
77
79
|
Load the data into the database
|
|
78
|
-
|
|
79
|
-
logger.info(f
|
|
80
|
+
"""
|
|
81
|
+
logger.info(f"Loading {len(data)} items for {Schema.label}")
|
|
80
82
|
load(
|
|
81
83
|
neo4j_session,
|
|
82
84
|
Schema(),
|
|
83
85
|
data,
|
|
84
|
-
DUO_API_HOSTNAME=common_job_parameters[
|
|
85
|
-
lastupdated=common_job_parameters[
|
|
86
|
+
DUO_API_HOSTNAME=common_job_parameters["DUO_API_HOSTNAME"],
|
|
87
|
+
lastupdated=common_job_parameters["UPDATE_TAG"],
|
|
86
88
|
)
|
|
87
89
|
|
|
88
90
|
|
|
89
91
|
@timeit
|
|
90
|
-
def _cleanup(
|
|
91
|
-
|
|
92
|
+
def _cleanup(
|
|
93
|
+
neo4j_session: neo4j.Session,
|
|
94
|
+
common_job_parameters: Dict[str, Any],
|
|
95
|
+
) -> None:
|
|
96
|
+
"""
|
|
92
97
|
Cleanup nodes
|
|
93
|
-
|
|
98
|
+
"""
|
|
94
99
|
GraphJob.from_node_schema(Schema(), common_job_parameters).run(neo4j_session)
|
|
@@ -4,6 +4,7 @@ import logging
|
|
|
4
4
|
import neo4j
|
|
5
5
|
|
|
6
6
|
from cartography.config import Config
|
|
7
|
+
from cartography.intel.entra.ou import sync_entra_ous
|
|
7
8
|
from cartography.intel.entra.users import sync_entra_users
|
|
8
9
|
from cartography.util import timeit
|
|
9
10
|
|
|
@@ -19,10 +20,14 @@ def start_entra_ingestion(neo4j_session: neo4j.Session, config: Config) -> None:
|
|
|
19
20
|
:return: None
|
|
20
21
|
"""
|
|
21
22
|
|
|
22
|
-
if
|
|
23
|
+
if (
|
|
24
|
+
not config.entra_tenant_id
|
|
25
|
+
or not config.entra_client_id
|
|
26
|
+
or not config.entra_client_secret
|
|
27
|
+
):
|
|
23
28
|
logger.info(
|
|
24
|
-
|
|
25
|
-
|
|
29
|
+
"Entra import is not configured - skipping this module. "
|
|
30
|
+
"See docs to configure.",
|
|
26
31
|
)
|
|
27
32
|
return
|
|
28
33
|
|
|
@@ -31,13 +36,26 @@ def start_entra_ingestion(neo4j_session: neo4j.Session, config: Config) -> None:
|
|
|
31
36
|
"TENANT_ID": config.entra_tenant_id,
|
|
32
37
|
}
|
|
33
38
|
|
|
34
|
-
|
|
35
|
-
|
|
39
|
+
async def main() -> None:
|
|
40
|
+
# Run user sync
|
|
41
|
+
await sync_entra_users(
|
|
36
42
|
neo4j_session,
|
|
37
43
|
config.entra_tenant_id,
|
|
38
44
|
config.entra_client_id,
|
|
39
45
|
config.entra_client_secret,
|
|
40
46
|
config.update_tag,
|
|
41
47
|
common_job_parameters,
|
|
42
|
-
)
|
|
43
|
-
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Run OU sync
|
|
51
|
+
await sync_entra_ous(
|
|
52
|
+
neo4j_session,
|
|
53
|
+
config.entra_tenant_id,
|
|
54
|
+
config.entra_client_id,
|
|
55
|
+
config.entra_client_secret,
|
|
56
|
+
config.update_tag,
|
|
57
|
+
common_job_parameters,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Execute both syncs in sequence
|
|
61
|
+
asyncio.run(main())
|