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
|
@@ -7,10 +7,11 @@ import neo4j
|
|
|
7
7
|
from azure.core.exceptions import HttpResponseError
|
|
8
8
|
from azure.mgmt.resource import SubscriptionClient
|
|
9
9
|
|
|
10
|
-
from .util.credentials import Credentials
|
|
11
10
|
from cartography.util import run_cleanup_job
|
|
12
11
|
from cartography.util import timeit
|
|
13
12
|
|
|
13
|
+
from .util.credentials import Credentials
|
|
14
|
+
|
|
14
15
|
logger = logging.getLogger(__name__)
|
|
15
16
|
|
|
16
17
|
|
|
@@ -24,26 +25,31 @@ def get_all_azure_subscriptions(credentials: Credentials) -> List[Dict]:
|
|
|
24
25
|
|
|
25
26
|
except HttpResponseError as e:
|
|
26
27
|
logger.error(
|
|
27
|
-
f
|
|
28
|
+
f"failed to fetch subscriptions for the credentials \
|
|
28
29
|
The provided credentials do not have access to any subscriptions - \
|
|
29
|
-
{e}
|
|
30
|
+
{e}",
|
|
30
31
|
)
|
|
31
32
|
|
|
32
33
|
return []
|
|
33
34
|
|
|
34
35
|
subscriptions = []
|
|
35
36
|
for sub in subs:
|
|
36
|
-
subscriptions.append(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
subscriptions.append(
|
|
38
|
+
{
|
|
39
|
+
"id": sub.id,
|
|
40
|
+
"subscriptionId": sub.subscription_id,
|
|
41
|
+
"displayName": sub.display_name,
|
|
42
|
+
"state": sub.state,
|
|
43
|
+
},
|
|
44
|
+
)
|
|
42
45
|
|
|
43
46
|
return subscriptions
|
|
44
47
|
|
|
45
48
|
|
|
46
|
-
def get_current_azure_subscription(
|
|
49
|
+
def get_current_azure_subscription(
|
|
50
|
+
credentials: Credentials,
|
|
51
|
+
subscription_id: Optional[str],
|
|
52
|
+
) -> List[Dict]:
|
|
47
53
|
try:
|
|
48
54
|
# Create the client
|
|
49
55
|
client = SubscriptionClient(credentials.arm_credentials)
|
|
@@ -53,25 +59,28 @@ def get_current_azure_subscription(credentials: Credentials, subscription_id: Op
|
|
|
53
59
|
|
|
54
60
|
except HttpResponseError as e:
|
|
55
61
|
logger.error(
|
|
56
|
-
f
|
|
62
|
+
f"failed to fetch subscription for the credentials \
|
|
57
63
|
The provided credentials do not have access to this subscription: {subscription_id} - \
|
|
58
|
-
{e}
|
|
64
|
+
{e}",
|
|
59
65
|
)
|
|
60
66
|
|
|
61
67
|
return []
|
|
62
68
|
|
|
63
69
|
return [
|
|
64
70
|
{
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
71
|
+
"id": sub.id,
|
|
72
|
+
"subscriptionId": sub.subscription_id,
|
|
73
|
+
"displayName": sub.display_name,
|
|
74
|
+
"state": sub.state,
|
|
69
75
|
},
|
|
70
76
|
]
|
|
71
77
|
|
|
72
78
|
|
|
73
79
|
def load_azure_subscriptions(
|
|
74
|
-
neo4j_session: neo4j.Session,
|
|
80
|
+
neo4j_session: neo4j.Session,
|
|
81
|
+
tenant_id: str,
|
|
82
|
+
subscriptions: List[Dict],
|
|
83
|
+
update_tag: int,
|
|
75
84
|
) -> None:
|
|
76
85
|
query = """
|
|
77
86
|
MERGE (at:AzureTenant{id: $TENANT_ID})
|
|
@@ -90,21 +99,28 @@ def load_azure_subscriptions(
|
|
|
90
99
|
neo4j_session.run(
|
|
91
100
|
query,
|
|
92
101
|
TENANT_ID=tenant_id,
|
|
93
|
-
SUBSCRIPTION_ID=sub[
|
|
94
|
-
SUBSCRIPTION_PATH=sub[
|
|
95
|
-
SUBSCRIPTION_NAME=sub[
|
|
96
|
-
SUBSCRIPTION_STATE=sub[
|
|
102
|
+
SUBSCRIPTION_ID=sub["subscriptionId"],
|
|
103
|
+
SUBSCRIPTION_PATH=sub["id"],
|
|
104
|
+
SUBSCRIPTION_NAME=sub["displayName"],
|
|
105
|
+
SUBSCRIPTION_STATE=sub["state"],
|
|
97
106
|
update_tag=update_tag,
|
|
98
107
|
)
|
|
99
108
|
|
|
100
109
|
|
|
101
110
|
def cleanup(neo4j_session: neo4j.Session, common_job_parameters: Dict) -> None:
|
|
102
|
-
run_cleanup_job(
|
|
111
|
+
run_cleanup_job(
|
|
112
|
+
"azure_subscriptions_cleanup.json",
|
|
113
|
+
neo4j_session,
|
|
114
|
+
common_job_parameters,
|
|
115
|
+
)
|
|
103
116
|
|
|
104
117
|
|
|
105
118
|
@timeit
|
|
106
119
|
def sync(
|
|
107
|
-
neo4j_session: neo4j.Session,
|
|
120
|
+
neo4j_session: neo4j.Session,
|
|
121
|
+
tenant_id: str,
|
|
122
|
+
subscriptions: List[Dict],
|
|
123
|
+
update_tag: int,
|
|
108
124
|
common_job_parameters: Dict,
|
|
109
125
|
) -> None:
|
|
110
126
|
load_azure_subscriptions(neo4j_session, tenant_id, subscriptions, update_tag)
|
|
@@ -3,10 +3,11 @@ from typing import Dict
|
|
|
3
3
|
|
|
4
4
|
import neo4j
|
|
5
5
|
|
|
6
|
-
from .util.credentials import Credentials
|
|
7
6
|
from cartography.util import run_cleanup_job
|
|
8
7
|
from cartography.util import timeit
|
|
9
8
|
|
|
9
|
+
from .util.credentials import Credentials
|
|
10
|
+
|
|
10
11
|
logger = logging.getLogger(__name__)
|
|
11
12
|
|
|
12
13
|
|
|
@@ -14,7 +15,12 @@ def get_tenant_id(credentials: Credentials) -> str:
|
|
|
14
15
|
return credentials.get_tenant_id()
|
|
15
16
|
|
|
16
17
|
|
|
17
|
-
def load_azure_tenant(
|
|
18
|
+
def load_azure_tenant(
|
|
19
|
+
neo4j_session: neo4j.Session,
|
|
20
|
+
tenant_id: str,
|
|
21
|
+
current_user: str,
|
|
22
|
+
update_tag: int,
|
|
23
|
+
) -> None:
|
|
18
24
|
query = """
|
|
19
25
|
MERGE (at:AzureTenant{id: $TENANT_ID})
|
|
20
26
|
ON CREATE SET at.firstseen = timestamp()
|
|
@@ -37,12 +43,15 @@ def load_azure_tenant(neo4j_session: neo4j.Session, tenant_id: str, current_user
|
|
|
37
43
|
|
|
38
44
|
|
|
39
45
|
def cleanup(neo4j_session: neo4j.Session, common_job_parameters: Dict) -> None:
|
|
40
|
-
run_cleanup_job(
|
|
46
|
+
run_cleanup_job("azure_tenant_cleanup.json", neo4j_session, common_job_parameters)
|
|
41
47
|
|
|
42
48
|
|
|
43
49
|
@timeit
|
|
44
50
|
def sync(
|
|
45
|
-
neo4j_session: neo4j.Session,
|
|
51
|
+
neo4j_session: neo4j.Session,
|
|
52
|
+
tenant_id: str,
|
|
53
|
+
current_user: str,
|
|
54
|
+
update_tag: int,
|
|
46
55
|
common_job_parameters: Dict,
|
|
47
56
|
) -> None:
|
|
48
57
|
load_azure_tenant(neo4j_session, tenant_id, current_user, update_tag)
|
|
@@ -13,22 +13,24 @@ from azure.identity import ClientSecretCredential
|
|
|
13
13
|
from msrestazure.azure_active_directory import AADTokenCredentials
|
|
14
14
|
|
|
15
15
|
logger = logging.getLogger(__name__)
|
|
16
|
-
AUTHORITY_HOST_URI =
|
|
16
|
+
AUTHORITY_HOST_URI = "https://login.microsoftonline.com"
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class Credentials:
|
|
20
20
|
|
|
21
21
|
def __init__(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
self,
|
|
23
|
+
arm_credentials: Any,
|
|
24
|
+
aad_graph_credentials: Any,
|
|
25
|
+
tenant_id: Optional[str] = None,
|
|
26
|
+
subscription_id: Optional[str] = None,
|
|
27
|
+
context: Optional[adal.AuthenticationContext] = None,
|
|
28
|
+
current_user: Optional[str] = None,
|
|
29
29
|
) -> None:
|
|
30
30
|
self.arm_credentials = arm_credentials # Azure Resource Manager API credentials
|
|
31
|
-
self.aad_graph_credentials =
|
|
31
|
+
self.aad_graph_credentials = (
|
|
32
|
+
aad_graph_credentials # Azure AD Graph API credentials
|
|
33
|
+
)
|
|
32
34
|
self.tenant_id = tenant_id
|
|
33
35
|
self.subscription_id = subscription_id
|
|
34
36
|
self.context = context
|
|
@@ -40,35 +42,46 @@ class Credentials:
|
|
|
40
42
|
def get_tenant_id(self) -> Any:
|
|
41
43
|
if self.tenant_id:
|
|
42
44
|
return self.tenant_id
|
|
43
|
-
elif
|
|
44
|
-
return self.aad_graph_credentials.token[
|
|
45
|
+
elif "tenant_id" in self.aad_graph_credentials.token:
|
|
46
|
+
return self.aad_graph_credentials.token["tenant_id"]
|
|
45
47
|
else:
|
|
46
48
|
# This is a last resort, e.g. for MSI authentication
|
|
47
49
|
try:
|
|
48
|
-
h = {
|
|
49
|
-
|
|
50
|
+
h = {
|
|
51
|
+
"Authorization": "Bearer {}".format(
|
|
52
|
+
self.arm_credentials.token["access_token"],
|
|
53
|
+
),
|
|
54
|
+
}
|
|
55
|
+
r = requests.get(
|
|
56
|
+
"https://management.azure.com/tenants?api-version=2020-01-01",
|
|
57
|
+
headers=h,
|
|
58
|
+
)
|
|
50
59
|
r2 = r.json()
|
|
51
|
-
return r2.get(
|
|
60
|
+
return r2.get("value")[0].get("tenantId")
|
|
52
61
|
except requests.ConnectionError as e:
|
|
53
|
-
logger.error(f
|
|
62
|
+
logger.error(f"Unable to infer tenant ID: {e}")
|
|
54
63
|
return None
|
|
55
64
|
|
|
56
65
|
def get_credentials(self, resource: str) -> Any:
|
|
57
|
-
if resource ==
|
|
66
|
+
if resource == "arm":
|
|
58
67
|
self.arm_credentials = self.get_fresh_credentials(self.arm_credentials)
|
|
59
68
|
return self.arm_credentials
|
|
60
|
-
elif resource ==
|
|
61
|
-
self.aad_graph_credentials = self.get_fresh_credentials(
|
|
69
|
+
elif resource == "aad_graph":
|
|
70
|
+
self.aad_graph_credentials = self.get_fresh_credentials(
|
|
71
|
+
self.aad_graph_credentials,
|
|
72
|
+
)
|
|
62
73
|
return self.aad_graph_credentials
|
|
63
74
|
else:
|
|
64
|
-
raise Exception(
|
|
75
|
+
raise Exception("Invalid credentials resource type")
|
|
65
76
|
|
|
66
77
|
def get_fresh_credentials(self, credentials: Any) -> Any:
|
|
67
78
|
"""
|
|
68
79
|
Check if credentials are outdated and if so refresh them.
|
|
69
80
|
"""
|
|
70
|
-
if self.context and hasattr(credentials,
|
|
71
|
-
expiration_datetime = datetime.fromtimestamp(
|
|
81
|
+
if self.context and hasattr(credentials, "token"):
|
|
82
|
+
expiration_datetime = datetime.fromtimestamp(
|
|
83
|
+
credentials.token["expires_on"],
|
|
84
|
+
)
|
|
72
85
|
current_datetime = datetime.now()
|
|
73
86
|
expiration_delta = expiration_datetime - current_datetime
|
|
74
87
|
if expiration_delta < timedelta(minutes=5):
|
|
@@ -79,8 +92,8 @@ class Credentials:
|
|
|
79
92
|
"""
|
|
80
93
|
Refresh credentials
|
|
81
94
|
"""
|
|
82
|
-
logger.debug(
|
|
83
|
-
authority_uri = AUTHORITY_HOST_URI +
|
|
95
|
+
logger.debug("Refreshing credentials")
|
|
96
|
+
authority_uri = AUTHORITY_HOST_URI + "/" + self.get_tenant_id()
|
|
84
97
|
if self.context:
|
|
85
98
|
existing_cache = self.context.cache
|
|
86
99
|
context = adal.AuthenticationContext(authority_uri, cache=existing_cache)
|
|
@@ -89,10 +102,15 @@ class Credentials:
|
|
|
89
102
|
context = adal.AuthenticationContext(authority_uri)
|
|
90
103
|
|
|
91
104
|
new_token = context.acquire_token(
|
|
92
|
-
credentials.token[
|
|
105
|
+
credentials.token["resource"],
|
|
106
|
+
credentials.token["user_id"],
|
|
107
|
+
credentials.token["_client_id"],
|
|
93
108
|
)
|
|
94
109
|
|
|
95
|
-
new_credentials = AADTokenCredentials(
|
|
110
|
+
new_credentials = AADTokenCredentials(
|
|
111
|
+
new_token,
|
|
112
|
+
credentials.token.get("_client_id"),
|
|
113
|
+
)
|
|
96
114
|
return new_credentials
|
|
97
115
|
|
|
98
116
|
|
|
@@ -105,40 +123,54 @@ class Authenticator:
|
|
|
105
123
|
try:
|
|
106
124
|
|
|
107
125
|
# Set logging level to error for libraries as otherwise generates a lot of warnings
|
|
108
|
-
logging.getLogger(
|
|
109
|
-
logging.getLogger(
|
|
110
|
-
logging.getLogger(
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
126
|
+
logging.getLogger("adal-python").setLevel(logging.ERROR)
|
|
127
|
+
logging.getLogger("msrest").setLevel(logging.ERROR)
|
|
128
|
+
logging.getLogger("msrestazure.azure_active_directory").setLevel(
|
|
129
|
+
logging.ERROR,
|
|
130
|
+
)
|
|
131
|
+
logging.getLogger("urllib3").setLevel(logging.ERROR)
|
|
132
|
+
logging.getLogger(
|
|
133
|
+
"azure.core.pipeline.policies.http_logging_policy",
|
|
134
|
+
).setLevel(logging.ERROR)
|
|
135
|
+
|
|
136
|
+
arm_credentials, subscription_id, tenant_id = get_azure_cli_credentials(
|
|
137
|
+
with_tenant=True,
|
|
138
|
+
)
|
|
139
|
+
aad_graph_credentials, placeholder_1, placeholder_2 = (
|
|
140
|
+
get_azure_cli_credentials(
|
|
141
|
+
with_tenant=True,
|
|
142
|
+
resource="https://graph.windows.net",
|
|
143
|
+
)
|
|
117
144
|
)
|
|
118
145
|
|
|
119
146
|
profile = get_cli_profile()
|
|
120
147
|
|
|
121
148
|
return Credentials(
|
|
122
|
-
arm_credentials,
|
|
123
|
-
|
|
149
|
+
arm_credentials,
|
|
150
|
+
aad_graph_credentials,
|
|
151
|
+
tenant_id=tenant_id,
|
|
152
|
+
current_user=profile.get_current_account_user(),
|
|
153
|
+
subscription_id=subscription_id,
|
|
124
154
|
)
|
|
125
155
|
|
|
126
156
|
except HttpResponseError as e:
|
|
127
|
-
if
|
|
128
|
-
|
|
157
|
+
if (
|
|
158
|
+
", AdalError: Unsupported wstrust endpoint version. "
|
|
159
|
+
"Current supported version is wstrust2005 or wstrust13." in e.args
|
|
160
|
+
):
|
|
129
161
|
logger.error(
|
|
130
|
-
f
|
|
162
|
+
f"You are likely authenticating with a Microsoft Account. \
|
|
131
163
|
This authentication mode only supports Azure Active Directory principal authentication.\
|
|
132
|
-
{e}
|
|
164
|
+
{e}",
|
|
133
165
|
)
|
|
134
166
|
|
|
135
167
|
raise e
|
|
136
168
|
|
|
137
169
|
def authenticate_sp(
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
170
|
+
self,
|
|
171
|
+
tenant_id: Optional[str] = None,
|
|
172
|
+
client_id: Optional[str] = None,
|
|
173
|
+
client_secret: Optional[str] = None,
|
|
142
174
|
) -> Credentials:
|
|
143
175
|
"""
|
|
144
176
|
Implements authentication for the Azure provider
|
|
@@ -146,11 +178,15 @@ class Authenticator:
|
|
|
146
178
|
try:
|
|
147
179
|
|
|
148
180
|
# Set logging level to error for libraries as otherwise generates a lot of warnings
|
|
149
|
-
logging.getLogger(
|
|
150
|
-
logging.getLogger(
|
|
151
|
-
logging.getLogger(
|
|
152
|
-
|
|
153
|
-
|
|
181
|
+
logging.getLogger("adal-python").setLevel(logging.ERROR)
|
|
182
|
+
logging.getLogger("msrest").setLevel(logging.ERROR)
|
|
183
|
+
logging.getLogger("msrestazure.azure_active_directory").setLevel(
|
|
184
|
+
logging.ERROR,
|
|
185
|
+
)
|
|
186
|
+
logging.getLogger("urllib3").setLevel(logging.ERROR)
|
|
187
|
+
logging.getLogger(
|
|
188
|
+
"azure.core.pipeline.policies.http_logging_policy",
|
|
189
|
+
).setLevel(logging.ERROR)
|
|
154
190
|
|
|
155
191
|
arm_credentials = ClientSecretCredential(
|
|
156
192
|
client_id=client_id,
|
|
@@ -162,21 +198,25 @@ class Authenticator:
|
|
|
162
198
|
client_id=client_id,
|
|
163
199
|
client_secret=client_secret,
|
|
164
200
|
tenant_id=tenant_id,
|
|
165
|
-
resource=
|
|
201
|
+
resource="https://graph.windows.net",
|
|
166
202
|
)
|
|
167
203
|
|
|
168
204
|
return Credentials(
|
|
169
|
-
arm_credentials,
|
|
205
|
+
arm_credentials,
|
|
206
|
+
aad_graph_credentials,
|
|
207
|
+
tenant_id=tenant_id,
|
|
170
208
|
current_user=client_id,
|
|
171
209
|
)
|
|
172
210
|
|
|
173
211
|
except HttpResponseError as e:
|
|
174
|
-
if
|
|
175
|
-
|
|
212
|
+
if (
|
|
213
|
+
", AdalError: Unsupported wstrust endpoint version. "
|
|
214
|
+
"Current supported version is wstrust2005 or wstrust13." in e.args
|
|
215
|
+
):
|
|
176
216
|
logger.error(
|
|
177
|
-
f
|
|
217
|
+
f"You are likely authenticating with a Microsoft Account. \
|
|
178
218
|
This authentication mode only supports Azure Active Directory principal authentication.\
|
|
179
|
-
{e}
|
|
219
|
+
{e}",
|
|
180
220
|
)
|
|
181
221
|
|
|
182
222
|
raise e
|
|
@@ -20,8 +20,8 @@ def start_bigfix_ingestion(neo4j_session: neo4j.Session, config: Config) -> None
|
|
|
20
20
|
|
|
21
21
|
if not config.bigfix_username or not config.bigfix_password:
|
|
22
22
|
logger.info(
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
"BigFix import is not configured - skipping this module. "
|
|
24
|
+
"See docs to configure.",
|
|
25
25
|
)
|
|
26
26
|
return
|
|
27
27
|
|
|
@@ -22,49 +22,49 @@ logger = logging.getLogger(__name__)
|
|
|
22
22
|
_TIMEOUT = (60, 60)
|
|
23
23
|
|
|
24
24
|
DEFAULT_SUPPORTED_KEYS = {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
25
|
+
"Active Directory Path",
|
|
26
|
+
"Agent Type",
|
|
27
|
+
"Agent Version",
|
|
28
|
+
"Average Evaluation Cycle",
|
|
29
|
+
"BES Relay Selection Method",
|
|
30
|
+
"BES Root Server",
|
|
31
|
+
"BIOS",
|
|
32
|
+
"Computer Name",
|
|
33
|
+
"Computer Type",
|
|
34
|
+
"CPU",
|
|
35
|
+
"Device Type",
|
|
36
|
+
"Distance to BES Relay",
|
|
37
|
+
"DNS Name",
|
|
38
|
+
"Enrollment Date",
|
|
39
|
+
"Free Space on System Drive",
|
|
40
|
+
"ID",
|
|
41
|
+
"IP Address",
|
|
42
|
+
"IPv6 Address",
|
|
43
|
+
"Last Report Time",
|
|
44
|
+
"Location By IP Range",
|
|
45
|
+
"Locked",
|
|
46
|
+
"Logged on User",
|
|
47
|
+
"MAC Address",
|
|
48
|
+
"OS",
|
|
49
|
+
"Provider Name",
|
|
50
|
+
"RAM",
|
|
51
|
+
"Relay",
|
|
52
|
+
"Remote Desktop Enabled",
|
|
53
|
+
"Subnet Address",
|
|
54
|
+
"Total Size of System Drive",
|
|
55
|
+
"User Name",
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
@timeit
|
|
60
60
|
def sync(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
61
|
+
neo4j_session: neo4j.Session,
|
|
62
|
+
bigfix_root_url: str,
|
|
63
|
+
bigfix_username: str,
|
|
64
|
+
bigfix_password: str,
|
|
65
|
+
update_tag: int,
|
|
66
|
+
common_job_parameters: Dict[str, Any],
|
|
67
|
+
computer_keys: Optional[Set[str]] = None,
|
|
68
68
|
) -> None:
|
|
69
69
|
if not computer_keys:
|
|
70
70
|
computer_keys = DEFAULT_SUPPORTED_KEYS
|
|
@@ -75,36 +75,50 @@ def sync(
|
|
|
75
75
|
|
|
76
76
|
|
|
77
77
|
@timeit
|
|
78
|
-
def get(
|
|
78
|
+
def get(
|
|
79
|
+
bigfix_api_url: str,
|
|
80
|
+
headers: Dict[str, str],
|
|
81
|
+
computer_keys: Set[str],
|
|
82
|
+
) -> List[Dict[str, Any]]:
|
|
79
83
|
result = []
|
|
80
84
|
computer_list = get_computer_list(bigfix_api_url, headers)
|
|
81
85
|
logger.info(f"Retrieving details for {len(computer_list)} BigFix Computers")
|
|
82
86
|
for computer in computer_list:
|
|
83
|
-
details = get_computer_details(
|
|
87
|
+
details = get_computer_details(
|
|
88
|
+
bigfix_api_url,
|
|
89
|
+
headers,
|
|
90
|
+
computer["ID"],
|
|
91
|
+
computer_keys,
|
|
92
|
+
)
|
|
84
93
|
processed_comp: Dict[str, Any] = computer.copy()
|
|
85
94
|
|
|
86
95
|
# Property names have spaces. Neo4j properties can't have spaces so let's clean this up.
|
|
87
96
|
for key, val in details.items():
|
|
88
|
-
transformed_key = key.replace(
|
|
97
|
+
transformed_key = key.replace(" ", "")
|
|
89
98
|
processed_comp[transformed_key] = val
|
|
90
99
|
|
|
91
100
|
# Post-processing transforms
|
|
92
|
-
processed_comp[
|
|
93
|
-
processed_comp[
|
|
101
|
+
processed_comp["EnrollmentDateTime"] = datetime.strptime(
|
|
102
|
+
processed_comp["EnrollmentDate"],
|
|
94
103
|
"%a, %d %b %Y %H:%M:%S %z",
|
|
95
104
|
)
|
|
96
|
-
processed_comp[
|
|
97
|
-
processed_comp[
|
|
105
|
+
processed_comp["LastReportDateTime"] = datetime.strptime(
|
|
106
|
+
processed_comp["LastReportTime"],
|
|
98
107
|
"%a, %d %b %Y %H:%M:%S %z",
|
|
99
108
|
)
|
|
100
|
-
processed_comp[
|
|
101
|
-
|
|
109
|
+
processed_comp["RemoteDesktopIsEnabled"] = (
|
|
110
|
+
processed_comp["RemoteDesktopEnabled"] == "True"
|
|
111
|
+
)
|
|
112
|
+
processed_comp["IsLocked"] = processed_comp["Locked"] == "Yes"
|
|
102
113
|
|
|
103
114
|
result.append(processed_comp)
|
|
104
115
|
return result
|
|
105
116
|
|
|
106
117
|
|
|
107
|
-
def get_computer_list(
|
|
118
|
+
def get_computer_list(
|
|
119
|
+
bigfix_api_url: str,
|
|
120
|
+
headers: Dict[str, str],
|
|
121
|
+
) -> List[Dict[str, str]]:
|
|
108
122
|
"""
|
|
109
123
|
Returned shape: [
|
|
110
124
|
{'@Resource': 'https://{URI}/api/computer/123', 'LastReportTime': 'Tue, 18 Apr 2023 21:59:44 +0000', 'ID': '123'},
|
|
@@ -112,22 +126,22 @@ def get_computer_list(bigfix_api_url: str, headers: Dict[str, str]) -> List[Dict
|
|
|
112
126
|
"""
|
|
113
127
|
xml_text = _get_computer_list_raw_xml(bigfix_api_url, headers)
|
|
114
128
|
as_dict = xmltodict.parse(xml_text)
|
|
115
|
-
return as_dict[
|
|
129
|
+
return as_dict["BESAPI"]["Computer"]
|
|
116
130
|
|
|
117
131
|
|
|
118
132
|
def get_computer_details(
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
133
|
+
bigfix_api_url: str,
|
|
134
|
+
headers: Dict[str, str],
|
|
135
|
+
computer_id: str,
|
|
136
|
+
computer_keys: Set[str],
|
|
123
137
|
) -> Dict[str, Any]:
|
|
124
138
|
xml_text = _get_computer_details_raw_xml(bigfix_api_url, headers, computer_id)
|
|
125
139
|
as_dict = xmltodict.parse(xml_text)
|
|
126
140
|
processed_computer = {}
|
|
127
|
-
for prop in as_dict[
|
|
128
|
-
prop_name = prop[
|
|
141
|
+
for prop in as_dict["BESAPI"]["Computer"]["Property"]:
|
|
142
|
+
prop_name = prop["@Name"]
|
|
129
143
|
if prop_name in computer_keys:
|
|
130
|
-
processed_computer[prop_name] = prop[
|
|
144
|
+
processed_computer[prop_name] = prop["#text"]
|
|
131
145
|
return processed_computer
|
|
132
146
|
|
|
133
147
|
|
|
@@ -138,24 +152,33 @@ def _get_computer_list_raw_xml(bigfix_api_url: str, headers: Dict[str, str]) ->
|
|
|
138
152
|
return resp.text
|
|
139
153
|
|
|
140
154
|
|
|
141
|
-
def _get_computer_details_raw_xml(
|
|
155
|
+
def _get_computer_details_raw_xml(
|
|
156
|
+
bigfix_api_url: str,
|
|
157
|
+
headers: Dict[str, str],
|
|
158
|
+
computer_id: str,
|
|
159
|
+
) -> str:
|
|
142
160
|
details_endpoint = f"{bigfix_api_url}/api/computer/{computer_id}"
|
|
143
|
-
resp = requests.get(
|
|
161
|
+
resp = requests.get(
|
|
162
|
+
details_endpoint,
|
|
163
|
+
headers=headers,
|
|
164
|
+
verify=False,
|
|
165
|
+
timeout=_TIMEOUT,
|
|
166
|
+
)
|
|
144
167
|
resp.raise_for_status()
|
|
145
168
|
return resp.text
|
|
146
169
|
|
|
147
170
|
|
|
148
171
|
@timeit
|
|
149
172
|
def load_computers(
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
173
|
+
neo4j_session: neo4j.Session,
|
|
174
|
+
data: List[Dict[str, Any]],
|
|
175
|
+
bigfix_root_url: str,
|
|
176
|
+
update_tag: int,
|
|
154
177
|
) -> None:
|
|
155
178
|
load(
|
|
156
179
|
neo4j_session,
|
|
157
180
|
BigfixRootSchema(),
|
|
158
|
-
[{
|
|
181
|
+
[{"id": bigfix_root_url}],
|
|
159
182
|
lastupdated=update_tag,
|
|
160
183
|
)
|
|
161
184
|
|
|
@@ -170,8 +193,13 @@ def load_computers(
|
|
|
170
193
|
|
|
171
194
|
|
|
172
195
|
@timeit
|
|
173
|
-
def cleanup(
|
|
174
|
-
|
|
196
|
+
def cleanup(
|
|
197
|
+
neo4j_session: neo4j.Session,
|
|
198
|
+
common_job_parameters: Dict[str, Any],
|
|
199
|
+
) -> None:
|
|
200
|
+
GraphJob.from_node_schema(BigfixComputerSchema(), common_job_parameters).run(
|
|
201
|
+
neo4j_session,
|
|
202
|
+
)
|
|
175
203
|
|
|
176
204
|
|
|
177
205
|
def _get_headers(bigfix_username: str, bigfix_password: str) -> Dict[str, str]:
|