cartography 0.102.0rc2__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 +138 -98
- cartography/intel/aws/ec2/security_groups.py +71 -21
- cartography/intel/aws/ec2/snapshots.py +61 -22
- cartography/intel/aws/ec2/subnets.py +54 -18
- cartography/intel/aws/ec2/tgw.py +100 -34
- cartography/intel/aws/ec2/util.py +1 -1
- cartography/intel/aws/ec2/volumes.py +69 -41
- cartography/intel/aws/ec2/vpc.py +37 -12
- cartography/intel/aws/ec2/vpc_peerings.py +83 -24
- cartography/intel/aws/ecr.py +88 -32
- cartography/intel/aws/ecs.py +83 -47
- cartography/intel/aws/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 -46
- cartography/intel/aws/route53.py +108 -61
- cartography/intel/aws/s3.py +168 -83
- cartography/intel/aws/s3accountpublicaccessblock.py +157 -0
- cartography/intel/aws/secretsmanager.py +24 -12
- cartography/intel/aws/securityhub.py +20 -9
- cartography/intel/aws/sns.py +166 -0
- cartography/intel/aws/sqs.py +60 -28
- cartography/intel/aws/ssm.py +70 -30
- cartography/intel/aws/util/arns.py +7 -7
- cartography/intel/aws/util/common.py +31 -4
- cartography/intel/azure/__init__.py +78 -19
- cartography/intel/azure/compute.py +101 -27
- cartography/intel/azure/cosmosdb.py +496 -170
- cartography/intel/azure/sql.py +296 -105
- cartography/intel/azure/storage.py +322 -113
- cartography/intel/azure/subscription.py +39 -23
- cartography/intel/azure/tenant.py +13 -4
- cartography/intel/azure/util/credentials.py +95 -55
- cartography/intel/bigfix/__init__.py +2 -2
- cartography/intel/bigfix/computers.py +93 -65
- cartography/intel/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 +44 -34
- cartography/models/aws/ec2/route_tables.py +50 -43
- cartography/models/aws/ec2/routes.py +45 -37
- cartography/models/aws/ec2/securitygroup_instance.py +29 -20
- cartography/models/aws/ec2/securitygroup_networkinterface.py +24 -15
- cartography/models/aws/ec2/subnet_instance.py +24 -19
- cartography/models/aws/ec2/subnet_networkinterface.py +40 -31
- cartography/models/aws/ec2/volumes.py +47 -40
- cartography/models/aws/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.0rc2.dist-info → cartography-0.103.0.dist-info}/METADATA +8 -15
- cartography-0.103.0.dist-info/RECORD +442 -0
- {cartography-0.102.0rc2.dist-info → cartography-0.103.0.dist-info}/WHEEL +1 -1
- cartography-0.102.0rc2.dist-info/RECORD +0 -381
- {cartography-0.102.0rc2.dist-info → cartography-0.103.0.dist-info}/entry_points.txt +0 -0
- {cartography-0.102.0rc2.dist-info → cartography-0.103.0.dist-info}/licenses/LICENSE +0 -0
- {cartography-0.102.0rc2.dist-info → cartography-0.103.0.dist-info}/top_level.txt +0 -0
|
@@ -1,67 +1,72 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
from typing import Any
|
|
3
|
+
from typing import Dict
|
|
4
|
+
from typing import List
|
|
2
5
|
|
|
3
6
|
import neo4j
|
|
4
7
|
from digitalocean import Account
|
|
8
|
+
from digitalocean import Manager
|
|
5
9
|
|
|
10
|
+
from cartography.client.core.tx import load
|
|
11
|
+
from cartography.graph.job import GraphJob
|
|
12
|
+
from cartography.models.digitalocean.account import DOAccountSchema
|
|
6
13
|
from cartography.util import timeit
|
|
7
14
|
|
|
8
|
-
|
|
9
15
|
logger = logging.getLogger(__name__)
|
|
10
16
|
|
|
11
17
|
|
|
12
18
|
@timeit
|
|
13
19
|
def sync(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
) ->
|
|
19
|
-
|
|
20
|
+
neo4j_session: neo4j.Session,
|
|
21
|
+
manager: Manager,
|
|
22
|
+
update_tag: int,
|
|
23
|
+
common_job_parameters: dict,
|
|
24
|
+
) -> str:
|
|
25
|
+
logger.info("Syncing Account")
|
|
26
|
+
account = get_account(manager)
|
|
27
|
+
account_transformed = transform_account(account)
|
|
28
|
+
load_account(
|
|
29
|
+
neo4j_session,
|
|
30
|
+
[
|
|
31
|
+
account_transformed,
|
|
32
|
+
],
|
|
33
|
+
update_tag,
|
|
34
|
+
)
|
|
35
|
+
cleanup(neo4j_session, common_job_parameters)
|
|
36
|
+
|
|
37
|
+
return account_transformed["id"]
|
|
20
38
|
|
|
21
39
|
|
|
22
40
|
@timeit
|
|
23
|
-
def
|
|
24
|
-
|
|
25
|
-
account: Account,
|
|
26
|
-
digitalocean_update_tag: int,
|
|
27
|
-
common_job_parameters: dict,
|
|
28
|
-
) -> None:
|
|
29
|
-
logger.info("Syncing Account")
|
|
30
|
-
account_transformed = transform_account(account)
|
|
31
|
-
load_account(neo4j_session, account_transformed, digitalocean_update_tag)
|
|
32
|
-
return
|
|
41
|
+
def get_account(manager: Manager) -> Account:
|
|
42
|
+
return manager.get_account()
|
|
33
43
|
|
|
34
44
|
|
|
35
45
|
@timeit
|
|
36
46
|
def transform_account(account_res: Account) -> dict:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
47
|
+
return {
|
|
48
|
+
"id": account_res.uuid,
|
|
49
|
+
"uuid": account_res.uuid,
|
|
50
|
+
"droplet_limit": account_res.droplet_limit,
|
|
51
|
+
"floating_ip_limit": account_res.floating_ip_limit,
|
|
52
|
+
"status": account_res.status,
|
|
43
53
|
}
|
|
44
|
-
return account
|
|
45
54
|
|
|
46
55
|
|
|
47
56
|
@timeit
|
|
48
|
-
def load_account(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
neo4j_session.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
FloatingIpLimit=account['floating_ip_limit'],
|
|
64
|
-
Status=account['status'],
|
|
65
|
-
digitalocean_update_tag=digitalocean_update_tag,
|
|
57
|
+
def load_account(
|
|
58
|
+
neo4j_session: neo4j.Session,
|
|
59
|
+
data: List[Dict[str, Any]],
|
|
60
|
+
update_tag: int,
|
|
61
|
+
) -> None:
|
|
62
|
+
load(neo4j_session, DOAccountSchema(), data, lastupdated=update_tag)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@timeit
|
|
66
|
+
def cleanup(
|
|
67
|
+
neo4j_session: neo4j.Session,
|
|
68
|
+
common_job_parameters: Dict[str, Any],
|
|
69
|
+
) -> None:
|
|
70
|
+
GraphJob.from_node_schema(DOAccountSchema(), common_job_parameters).run(
|
|
71
|
+
neo4j_session,
|
|
66
72
|
)
|
|
67
|
-
return
|
cartography/intel/dns.py
CHANGED
|
@@ -15,7 +15,11 @@ logger = logging.getLogger(__name__)
|
|
|
15
15
|
|
|
16
16
|
@timeit
|
|
17
17
|
def ingest_dns_record_by_fqdn(
|
|
18
|
-
neo4j_session: neo4j.Session,
|
|
18
|
+
neo4j_session: neo4j.Session,
|
|
19
|
+
update_tag: int,
|
|
20
|
+
fqdn: str,
|
|
21
|
+
points_to_record: str,
|
|
22
|
+
record_label: str,
|
|
19
23
|
dns_node_additional_label: Optional[str] = None,
|
|
20
24
|
) -> None:
|
|
21
25
|
"""
|
|
@@ -43,7 +47,7 @@ def ingest_dns_record_by_fqdn(
|
|
|
43
47
|
fqdn_data = get_dns_resolution_by_fqdn(fqdn)
|
|
44
48
|
record_type = get_dns_record_type(fqdn_data)
|
|
45
49
|
|
|
46
|
-
if record_type ==
|
|
50
|
+
if record_type == "A":
|
|
47
51
|
ip_list = []
|
|
48
52
|
for result in fqdn_data:
|
|
49
53
|
ip = str(result)
|
|
@@ -51,8 +55,14 @@ def ingest_dns_record_by_fqdn(
|
|
|
51
55
|
|
|
52
56
|
value = ",".join(ip_list)
|
|
53
57
|
record_id = ingest_dns_record(
|
|
54
|
-
neo4j_session,
|
|
55
|
-
|
|
58
|
+
neo4j_session,
|
|
59
|
+
fqdn,
|
|
60
|
+
value,
|
|
61
|
+
record_type,
|
|
62
|
+
update_tag,
|
|
63
|
+
points_to_record,
|
|
64
|
+
record_label,
|
|
65
|
+
dns_node_additional_label, # type: ignore
|
|
56
66
|
)
|
|
57
67
|
_link_ip_to_A_record(neo4j_session, update_tag, ip_list, record_id)
|
|
58
68
|
|
|
@@ -67,7 +77,12 @@ def ingest_dns_record_by_fqdn(
|
|
|
67
77
|
|
|
68
78
|
|
|
69
79
|
@timeit
|
|
70
|
-
def _link_ip_to_A_record(
|
|
80
|
+
def _link_ip_to_A_record(
|
|
81
|
+
neo4j_session: neo4j.Session,
|
|
82
|
+
update_tag: int,
|
|
83
|
+
ip_list: List[str],
|
|
84
|
+
parent_record: str,
|
|
85
|
+
) -> None:
|
|
71
86
|
"""
|
|
72
87
|
Link A record to to its IP
|
|
73
88
|
|
|
@@ -99,8 +114,14 @@ def _link_ip_to_A_record(neo4j_session: neo4j.Session, update_tag: int, ip_list:
|
|
|
99
114
|
|
|
100
115
|
@timeit
|
|
101
116
|
def ingest_dns_record(
|
|
102
|
-
neo4j_session: neo4j.Session,
|
|
103
|
-
|
|
117
|
+
neo4j_session: neo4j.Session,
|
|
118
|
+
name: neo4j.Session,
|
|
119
|
+
value: str,
|
|
120
|
+
type: str,
|
|
121
|
+
update_tag: int,
|
|
122
|
+
points_to_record: str,
|
|
123
|
+
record_label: str,
|
|
124
|
+
dns_node_additional_label: str,
|
|
104
125
|
) -> str:
|
|
105
126
|
"""
|
|
106
127
|
Ingest a new DNS record
|
|
@@ -115,7 +136,8 @@ def ingest_dns_record(
|
|
|
115
136
|
:param dns_node_additional_label: The specific label of the DNSRecord, e.g. AWSDNSRecord.
|
|
116
137
|
:return: the intel graph node id for the new/merged record
|
|
117
138
|
"""
|
|
118
|
-
template = Template(
|
|
139
|
+
template = Template(
|
|
140
|
+
"""
|
|
119
141
|
MERGE (record:DNSRecord:$dns_node_additional_label{id: $Id})
|
|
120
142
|
ON CREATE SET record.firstseen = timestamp(), record.name = $Name, record.type = $Type
|
|
121
143
|
SET record.lastupdated = $update_tag, record.value = $Value
|
|
@@ -124,12 +146,16 @@ def ingest_dns_record(
|
|
|
124
146
|
MERGE (record)-[r:DNS_POINTS_TO]->(n)
|
|
125
147
|
ON CREATE SET r.firstseen = timestamp()
|
|
126
148
|
SET r.lastupdated = $update_tag
|
|
127
|
-
"""
|
|
149
|
+
""",
|
|
150
|
+
)
|
|
128
151
|
|
|
129
152
|
record_id = f"{name}+{type}"
|
|
130
153
|
|
|
131
154
|
neo4j_session.run(
|
|
132
|
-
template.safe_substitute(
|
|
155
|
+
template.safe_substitute(
|
|
156
|
+
record_label=record_label,
|
|
157
|
+
dns_node_additional_label=dns_node_additional_label,
|
|
158
|
+
),
|
|
133
159
|
Id=record_id,
|
|
134
160
|
Name=name,
|
|
135
161
|
Type=type,
|
|
@@ -13,31 +13,34 @@ from cartography.intel.duo.groups import sync_duo_groups
|
|
|
13
13
|
from cartography.intel.duo.phones import sync as sync_duo_phones
|
|
14
14
|
from cartography.intel.duo.tokens import sync as sync_duo_tokens
|
|
15
15
|
from cartography.intel.duo.users import sync_duo_users
|
|
16
|
-
from cartography.intel.duo.web_authn_credentials import
|
|
16
|
+
from cartography.intel.duo.web_authn_credentials import (
|
|
17
|
+
sync as sync_duo_web_authn_credentials,
|
|
18
|
+
)
|
|
17
19
|
from cartography.util import timeit
|
|
18
20
|
|
|
19
|
-
|
|
20
21
|
logger = logging.getLogger(__name__)
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
@timeit
|
|
24
25
|
def get_client(config: Config) -> duo_client.Admin:
|
|
25
|
-
|
|
26
|
+
"""
|
|
26
27
|
Return a duo Admin client with the creds in the config object
|
|
27
|
-
|
|
28
|
+
"""
|
|
28
29
|
client = duo_client.Admin(
|
|
29
30
|
ikey=config.duo_api_key,
|
|
30
31
|
skey=config.duo_api_secret,
|
|
31
32
|
host=config.duo_api_hostname,
|
|
32
33
|
)
|
|
33
34
|
# Duo's library does not automatically respect the HTTP_PROXY env variable
|
|
34
|
-
proxy_url = os.environ.get(
|
|
35
|
+
proxy_url = os.environ.get("HTTP_PROXY")
|
|
35
36
|
if proxy_url:
|
|
36
37
|
proxy_config = urlparse(proxy_url)
|
|
37
38
|
headers = {}
|
|
38
39
|
if proxy_config.username:
|
|
39
|
-
proxy_auth_token = b64encode(
|
|
40
|
-
|
|
40
|
+
proxy_auth_token = b64encode(
|
|
41
|
+
f"{proxy_config.username}:{proxy_config.password}".encode(),
|
|
42
|
+
).decode("ascii")
|
|
43
|
+
headers["Proxy-Authorization"] = f"Basic {proxy_auth_token}"
|
|
41
44
|
client.set_proxy(
|
|
42
45
|
host=proxy_config.hostname,
|
|
43
46
|
port=proxy_config.port,
|
|
@@ -48,20 +51,22 @@ def get_client(config: Config) -> duo_client.Admin:
|
|
|
48
51
|
|
|
49
52
|
@timeit
|
|
50
53
|
def start_duo_ingestion(neo4j_session: neo4j.Session, config: Config) -> None:
|
|
51
|
-
|
|
54
|
+
"""
|
|
52
55
|
If this module is configured, perform ingestion of duo data. Otherwise warn and exit
|
|
53
56
|
:param neo4j_session: Neo4J session for database interface
|
|
54
57
|
:param config: A cartography.config object
|
|
55
58
|
:return: None
|
|
56
|
-
|
|
57
|
-
if not all(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
"""
|
|
60
|
+
if not all(
|
|
61
|
+
[
|
|
62
|
+
config.duo_api_key,
|
|
63
|
+
config.duo_api_secret,
|
|
64
|
+
config.duo_api_hostname,
|
|
65
|
+
],
|
|
66
|
+
):
|
|
62
67
|
logger.info(
|
|
63
|
-
|
|
64
|
-
|
|
68
|
+
"Duo import is not configured - skipping this module. "
|
|
69
|
+
"See docs to configure.",
|
|
65
70
|
)
|
|
66
71
|
return
|
|
67
72
|
|
|
@@ -8,31 +8,36 @@ from cartography.client.core.tx import load
|
|
|
8
8
|
from cartography.models.duo.api_host import DuoApiHostSchema
|
|
9
9
|
from cartography.util import timeit
|
|
10
10
|
|
|
11
|
-
|
|
12
11
|
logger = logging.getLogger(__name__)
|
|
13
12
|
|
|
14
13
|
|
|
15
14
|
@timeit
|
|
16
|
-
def sync_duo_api_host(
|
|
17
|
-
|
|
15
|
+
def sync_duo_api_host(
|
|
16
|
+
neo4j_session: neo4j.Session,
|
|
17
|
+
common_job_parameters: Dict[str, Any],
|
|
18
|
+
) -> None:
|
|
19
|
+
"""
|
|
18
20
|
Add the DuoApiHost subresource
|
|
19
|
-
|
|
21
|
+
"""
|
|
20
22
|
_load_api_host(neo4j_session, common_job_parameters)
|
|
21
23
|
|
|
22
24
|
|
|
23
25
|
@timeit
|
|
24
|
-
def _load_api_host(
|
|
25
|
-
|
|
26
|
+
def _load_api_host(
|
|
27
|
+
neo4j_session: neo4j.Session,
|
|
28
|
+
common_job_parameters: Dict[str, Any],
|
|
29
|
+
) -> None:
|
|
30
|
+
"""
|
|
26
31
|
Load the host node into the graph
|
|
27
|
-
|
|
32
|
+
"""
|
|
28
33
|
data = [
|
|
29
34
|
{
|
|
30
|
-
|
|
35
|
+
"id": common_job_parameters["DUO_API_HOSTNAME"],
|
|
31
36
|
},
|
|
32
37
|
]
|
|
33
38
|
load(
|
|
34
39
|
neo4j_session,
|
|
35
40
|
DuoApiHostSchema(),
|
|
36
41
|
data,
|
|
37
|
-
lastupdated=common_job_parameters[
|
|
42
|
+
lastupdated=common_job_parameters["UPDATE_TAG"],
|
|
38
43
|
)
|
|
@@ -21,9 +21,9 @@ def sync_duo_endpoints(
|
|
|
21
21
|
neo4j_session: neo4j.Session,
|
|
22
22
|
common_job_parameters: Dict[str, Any],
|
|
23
23
|
) -> None:
|
|
24
|
-
|
|
24
|
+
"""
|
|
25
25
|
Sync Duo Endpoints
|
|
26
|
-
|
|
26
|
+
"""
|
|
27
27
|
endpoints = _get_endpoints(client)
|
|
28
28
|
transformed_endpoints = _transform_endpoints(endpoints)
|
|
29
29
|
_load_endpoints(neo4j_session, transformed_endpoints, common_job_parameters)
|
|
@@ -32,52 +32,52 @@ def sync_duo_endpoints(
|
|
|
32
32
|
|
|
33
33
|
@timeit
|
|
34
34
|
def _get_endpoints(client: duo_client.Admin) -> List[Dict[str, Any]]:
|
|
35
|
-
|
|
35
|
+
"""
|
|
36
36
|
Fetch all endpoint data
|
|
37
37
|
https://duo.com/docs/adminapi#endpoints
|
|
38
|
-
|
|
38
|
+
"""
|
|
39
39
|
logger.info("Fetching Duo endpoints")
|
|
40
40
|
return client.get_endpoints()
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
@timeit
|
|
44
44
|
def _transform_endpoints(endpoints: 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(endpoints)} duo endpoints")
|
|
49
49
|
transformed_endpoints = []
|
|
50
50
|
for endpoint in endpoints:
|
|
51
51
|
transformed_endpoint = {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
52
|
+
"browsers": [dumps(browser) for browser in endpoint["browsers"]],
|
|
53
|
+
"computer_sid": endpoint["computer_sid"],
|
|
54
|
+
"cpu_id": endpoint["cpu_id"],
|
|
55
|
+
"device_id": endpoint["device_id"],
|
|
56
|
+
"device_identifier": endpoint["device_identifier"],
|
|
57
|
+
"device_identifier_type": endpoint["device_identifier_type"],
|
|
58
|
+
"device_name": endpoint["device_name"],
|
|
59
|
+
"device_udid": endpoint["device_udid"],
|
|
60
|
+
"device_username": endpoint["device_username"],
|
|
61
|
+
"device_username_type": endpoint["device_username_type"],
|
|
62
|
+
"disk_encryption_status": endpoint["disk_encryption_status"],
|
|
63
|
+
"domain_sid": endpoint["domain_sid"],
|
|
64
|
+
"email": endpoint["email"],
|
|
65
|
+
"epkey": endpoint["epkey"],
|
|
66
|
+
"firewall_status": endpoint["firewall_status"],
|
|
67
|
+
"hardware_uuid": endpoint["hardware_uuid"],
|
|
68
|
+
"health_app_client_version": endpoint["health_app_client_version"],
|
|
69
|
+
"health_data_last_collected": endpoint["health_data_last_collected"],
|
|
70
|
+
"last_updated": endpoint["last_updated"],
|
|
71
|
+
"machine_guid": endpoint["machine_guid"],
|
|
72
|
+
"model": endpoint["model"],
|
|
73
|
+
"os_build": endpoint["os_build"],
|
|
74
|
+
"os_family": endpoint["os_family"],
|
|
75
|
+
"os_version": endpoint["os_version"],
|
|
76
|
+
"password_status": endpoint["password_status"],
|
|
77
|
+
"security_agents": [dumps(agent) for agent in endpoint["security_agents"]],
|
|
78
|
+
"trusted_endpoint": endpoint["trusted_endpoint"],
|
|
79
|
+
"type": endpoint["type"],
|
|
80
|
+
"username": endpoint["username"],
|
|
81
81
|
}
|
|
82
82
|
transformed_endpoints.append(transformed_endpoint)
|
|
83
83
|
return transformed_endpoints
|
|
@@ -89,22 +89,27 @@ def _load_endpoints(
|
|
|
89
89
|
endpoints: List[Dict[str, Any]],
|
|
90
90
|
common_job_parameters: Dict[str, Any],
|
|
91
91
|
) -> None:
|
|
92
|
-
|
|
92
|
+
"""
|
|
93
93
|
Load the endpoints into the database
|
|
94
|
-
|
|
95
|
-
logger.info(f
|
|
94
|
+
"""
|
|
95
|
+
logger.info(f"Loading {len(endpoints)} duo endpoints")
|
|
96
96
|
load(
|
|
97
97
|
neo4j_session,
|
|
98
98
|
DuoEndpointSchema(),
|
|
99
99
|
endpoints,
|
|
100
|
-
DUO_API_HOSTNAME=common_job_parameters[
|
|
101
|
-
lastupdated=common_job_parameters[
|
|
100
|
+
DUO_API_HOSTNAME=common_job_parameters["DUO_API_HOSTNAME"],
|
|
101
|
+
lastupdated=common_job_parameters["UPDATE_TAG"],
|
|
102
102
|
)
|
|
103
103
|
|
|
104
104
|
|
|
105
105
|
@timeit
|
|
106
|
-
def _cleanup_endpoints(
|
|
107
|
-
|
|
106
|
+
def _cleanup_endpoints(
|
|
107
|
+
neo4j_session: neo4j.Session,
|
|
108
|
+
common_job_parameters: Dict[str, Any],
|
|
109
|
+
) -> None:
|
|
110
|
+
"""
|
|
108
111
|
Cleanup endpoints
|
|
109
|
-
|
|
110
|
-
GraphJob.from_node_schema(DuoEndpointSchema(), common_job_parameters).run(
|
|
112
|
+
"""
|
|
113
|
+
GraphJob.from_node_schema(DuoEndpointSchema(), common_job_parameters).run(
|
|
114
|
+
neo4j_session,
|
|
115
|
+
)
|
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)
|