cartography 0.103.0rc1__py3-none-any.whl → 0.104.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/_version.py +2 -2
- cartography/cli.py +97 -1
- cartography/config.py +28 -0
- cartography/graph/cleanupbuilder.py +151 -41
- cartography/intel/anthropic/__init__.py +62 -0
- cartography/intel/anthropic/apikeys.py +72 -0
- cartography/intel/anthropic/users.py +75 -0
- cartography/intel/anthropic/util.py +51 -0
- cartography/intel/anthropic/workspaces.py +95 -0
- cartography/intel/aws/cloudtrail.py +3 -38
- cartography/intel/aws/cloudwatch.py +93 -0
- cartography/intel/aws/ec2/load_balancer_v2s.py +4 -1
- cartography/intel/aws/resources.py +2 -0
- cartography/intel/aws/secretsmanager.py +150 -3
- cartography/intel/aws/ssm.py +71 -0
- 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/entra/ou.py +21 -5
- cartography/intel/openai/__init__.py +86 -0
- cartography/intel/openai/adminapikeys.py +89 -0
- cartography/intel/openai/apikeys.py +96 -0
- cartography/intel/openai/projects.py +97 -0
- cartography/intel/openai/serviceaccounts.py +82 -0
- cartography/intel/openai/users.py +75 -0
- cartography/intel/openai/util.py +45 -0
- 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/anthropic/__init__.py +0 -0
- cartography/models/anthropic/apikey.py +90 -0
- cartography/models/anthropic/organization.py +19 -0
- cartography/models/anthropic/user.py +48 -0
- cartography/models/anthropic/workspace.py +90 -0
- cartography/models/aws/cloudtrail/trail.py +24 -0
- cartography/models/aws/cloudwatch/__init__.py +0 -0
- cartography/models/aws/cloudwatch/loggroup.py +52 -0
- cartography/models/aws/secretsmanager/__init__.py +0 -0
- cartography/models/aws/secretsmanager/secret_version.py +116 -0
- cartography/models/aws/ssm/parameters.py +84 -0
- 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/nodes.py +15 -2
- 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 +89 -0
- cartography/models/openai/serviceaccount.py +50 -0
- cartography/models/openai/user.py +49 -0
- 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/sync.py +8 -0
- {cartography-0.103.0rc1.dist-info → cartography-0.104.0.dist-info}/METADATA +8 -4
- {cartography-0.103.0rc1.dist-info → cartography-0.104.0.dist-info}/RECORD +75 -19
- {cartography-0.103.0rc1.dist-info → cartography-0.104.0.dist-info}/WHEEL +1 -1
- {cartography-0.103.0rc1.dist-info → cartography-0.104.0.dist-info}/entry_points.txt +0 -0
- {cartography-0.103.0rc1.dist-info → cartography-0.104.0.dist-info}/licenses/LICENSE +0 -0
- {cartography-0.103.0rc1.dist-info → cartography-0.104.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any
|
|
3
|
+
from typing import Dict
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
import neo4j
|
|
7
|
+
import requests
|
|
8
|
+
|
|
9
|
+
from cartography.client.core.tx import load
|
|
10
|
+
from cartography.graph.job import GraphJob
|
|
11
|
+
from cartography.intel.openai.util import paginated_get
|
|
12
|
+
from cartography.models.openai.serviceaccount import OpenAIServiceAccountSchema
|
|
13
|
+
from cartography.util import timeit
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
# Connect and read timeouts of 60 seconds each; see https://requests.readthedocs.io/en/master/user/advanced/#timeouts
|
|
17
|
+
_TIMEOUT = (60, 60)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@timeit
|
|
21
|
+
def sync(
|
|
22
|
+
neo4j_session: neo4j.Session,
|
|
23
|
+
api_session: requests.Session,
|
|
24
|
+
common_job_parameters: Dict[str, Any],
|
|
25
|
+
project_id: str,
|
|
26
|
+
) -> None:
|
|
27
|
+
serviceaccountss = get(
|
|
28
|
+
api_session,
|
|
29
|
+
common_job_parameters["BASE_URL"],
|
|
30
|
+
project_id,
|
|
31
|
+
)
|
|
32
|
+
load_serviceaccounts(
|
|
33
|
+
neo4j_session,
|
|
34
|
+
serviceaccountss,
|
|
35
|
+
project_id,
|
|
36
|
+
common_job_parameters["UPDATE_TAG"],
|
|
37
|
+
)
|
|
38
|
+
cleanup(neo4j_session, common_job_parameters)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@timeit
|
|
42
|
+
def get(
|
|
43
|
+
api_session: requests.Session,
|
|
44
|
+
base_url: str,
|
|
45
|
+
project_id: str,
|
|
46
|
+
) -> List[Dict[str, Any]]:
|
|
47
|
+
return list(
|
|
48
|
+
paginated_get(
|
|
49
|
+
api_session,
|
|
50
|
+
"{base_url}/organization/projects/{project_id}/service_accounts".format(
|
|
51
|
+
base_url=base_url,
|
|
52
|
+
project_id=project_id,
|
|
53
|
+
),
|
|
54
|
+
timeout=_TIMEOUT,
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@timeit
|
|
60
|
+
def load_serviceaccounts(
|
|
61
|
+
neo4j_session: neo4j.Session,
|
|
62
|
+
data: List[Dict[str, Any]],
|
|
63
|
+
project_id: str,
|
|
64
|
+
update_tag: int,
|
|
65
|
+
) -> None:
|
|
66
|
+
logger.info("Loading %d OpenAI ServiceAccount into Neo4j.", len(data))
|
|
67
|
+
load(
|
|
68
|
+
neo4j_session,
|
|
69
|
+
OpenAIServiceAccountSchema(),
|
|
70
|
+
data,
|
|
71
|
+
lastupdated=update_tag,
|
|
72
|
+
project_id=project_id,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@timeit
|
|
77
|
+
def cleanup(
|
|
78
|
+
neo4j_session: neo4j.Session, common_job_parameters: Dict[str, Any]
|
|
79
|
+
) -> None:
|
|
80
|
+
GraphJob.from_node_schema(OpenAIServiceAccountSchema(), common_job_parameters).run(
|
|
81
|
+
neo4j_session
|
|
82
|
+
)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any
|
|
3
|
+
from typing import Dict
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
import neo4j
|
|
7
|
+
import requests
|
|
8
|
+
|
|
9
|
+
from cartography.client.core.tx import load
|
|
10
|
+
from cartography.graph.job import GraphJob
|
|
11
|
+
from cartography.intel.openai.util import paginated_get
|
|
12
|
+
from cartography.models.openai.organization import OpenAIOrganizationSchema
|
|
13
|
+
from cartography.models.openai.user import OpenAIUserSchema
|
|
14
|
+
from cartography.util import timeit
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
# Connect and read timeouts of 60 seconds each; see https://requests.readthedocs.io/en/master/user/advanced/#timeouts
|
|
18
|
+
_TIMEOUT = (60, 60)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@timeit
|
|
22
|
+
def sync(
|
|
23
|
+
neo4j_session: neo4j.Session,
|
|
24
|
+
api_session: requests.Session,
|
|
25
|
+
common_job_parameters: Dict[str, Any],
|
|
26
|
+
ORG_ID: str,
|
|
27
|
+
) -> None:
|
|
28
|
+
users = get(
|
|
29
|
+
api_session,
|
|
30
|
+
common_job_parameters["BASE_URL"],
|
|
31
|
+
)
|
|
32
|
+
load_users(neo4j_session, users, ORG_ID, common_job_parameters["UPDATE_TAG"])
|
|
33
|
+
cleanup(neo4j_session, common_job_parameters)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@timeit
|
|
37
|
+
def get(
|
|
38
|
+
api_session: requests.Session,
|
|
39
|
+
base_url: str,
|
|
40
|
+
) -> List[Dict[str, Any]]:
|
|
41
|
+
return list(
|
|
42
|
+
paginated_get(api_session, f"{base_url}/organization/users", timeout=_TIMEOUT)
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@timeit
|
|
47
|
+
def load_users(
|
|
48
|
+
neo4j_session: neo4j.Session,
|
|
49
|
+
data: List[Dict[str, Any]],
|
|
50
|
+
ORG_ID: str,
|
|
51
|
+
update_tag: int,
|
|
52
|
+
) -> None:
|
|
53
|
+
load(
|
|
54
|
+
neo4j_session,
|
|
55
|
+
OpenAIOrganizationSchema(),
|
|
56
|
+
[{"id": ORG_ID}],
|
|
57
|
+
lastupdated=update_tag,
|
|
58
|
+
)
|
|
59
|
+
logger.info("Loading %d OpenAI User into Neo4j.", len(data))
|
|
60
|
+
load(
|
|
61
|
+
neo4j_session,
|
|
62
|
+
OpenAIUserSchema(),
|
|
63
|
+
data,
|
|
64
|
+
lastupdated=update_tag,
|
|
65
|
+
ORG_ID=ORG_ID,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@timeit
|
|
70
|
+
def cleanup(
|
|
71
|
+
neo4j_session: neo4j.Session, common_job_parameters: Dict[str, Any]
|
|
72
|
+
) -> None:
|
|
73
|
+
GraphJob.from_node_schema(OpenAIUserSchema(), common_job_parameters).run(
|
|
74
|
+
neo4j_session
|
|
75
|
+
)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
from typing import Generator
|
|
3
|
+
|
|
4
|
+
import requests
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def paginated_get(
|
|
8
|
+
api_session: requests.Session,
|
|
9
|
+
url: str,
|
|
10
|
+
timeout: tuple[int, int],
|
|
11
|
+
after: str | None = None,
|
|
12
|
+
) -> Generator[dict[str, Any], None, None]:
|
|
13
|
+
"""Helper function to get paginated data from the OpenAI API.
|
|
14
|
+
|
|
15
|
+
This function handles the pagination of the API requests and returns
|
|
16
|
+
the results as a generator. It will continue to make requests until
|
|
17
|
+
all pages of data have been retrieved. The results are returned as a
|
|
18
|
+
list of dictionaries, where each dictionary represents a single
|
|
19
|
+
entity.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
api_session (requests.Session): The requests session to use for making API calls.
|
|
23
|
+
url (str): The URL to make the API call to.
|
|
24
|
+
timeout (tuple[int, int]): The timeout for the API call.
|
|
25
|
+
after (str | None): The ID of the last item retrieved in the previous request.
|
|
26
|
+
If None, the first page of results will be retrieved.
|
|
27
|
+
Returns:
|
|
28
|
+
Generator[dict[str, Any], None, None]: A generator yielding dictionaries representing the results.
|
|
29
|
+
"""
|
|
30
|
+
params = {"after": after} if after else {}
|
|
31
|
+
req = api_session.get(
|
|
32
|
+
url,
|
|
33
|
+
params=params,
|
|
34
|
+
timeout=timeout,
|
|
35
|
+
)
|
|
36
|
+
req.raise_for_status()
|
|
37
|
+
result = req.json()
|
|
38
|
+
yield from result.get("data", [])
|
|
39
|
+
if result.get("has_more"):
|
|
40
|
+
yield from paginated_get(
|
|
41
|
+
api_session,
|
|
42
|
+
url,
|
|
43
|
+
timeout=timeout,
|
|
44
|
+
after=result.get("last_id"),
|
|
45
|
+
)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
import neo4j
|
|
4
|
+
import requests
|
|
5
|
+
|
|
6
|
+
import cartography.intel.tailscale.acls
|
|
7
|
+
import cartography.intel.tailscale.devices
|
|
8
|
+
import cartography.intel.tailscale.postureintegrations
|
|
9
|
+
import cartography.intel.tailscale.tailnets
|
|
10
|
+
import cartography.intel.tailscale.users
|
|
11
|
+
from cartography.config import Config
|
|
12
|
+
from cartography.util import timeit
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@timeit
|
|
18
|
+
def start_tailscale_ingestion(neo4j_session: neo4j.Session, config: Config) -> None:
|
|
19
|
+
"""
|
|
20
|
+
If this module is configured, perform ingestion of Tailscale data. Otherwise warn and exit
|
|
21
|
+
:param neo4j_session: Neo4J session for database interface
|
|
22
|
+
:param config: A cartography.config object
|
|
23
|
+
:return: None
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
if not config.tailscale_token or not config.tailscale_org:
|
|
27
|
+
logger.info(
|
|
28
|
+
"Tailscale import is not configured - skipping this module. "
|
|
29
|
+
"See docs to configure.",
|
|
30
|
+
)
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
# Create requests sessions
|
|
34
|
+
api_session = requests.session()
|
|
35
|
+
api_session.headers.update({"Authorization": f"Bearer {config.tailscale_token}"})
|
|
36
|
+
|
|
37
|
+
common_job_parameters = {
|
|
38
|
+
"UPDATE_TAG": config.update_tag,
|
|
39
|
+
"BASE_URL": config.tailscale_base_url,
|
|
40
|
+
"org": config.tailscale_org,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
cartography.intel.tailscale.tailnets.sync(
|
|
44
|
+
neo4j_session,
|
|
45
|
+
api_session,
|
|
46
|
+
common_job_parameters,
|
|
47
|
+
org=config.tailscale_org,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
users = cartography.intel.tailscale.users.sync(
|
|
51
|
+
neo4j_session,
|
|
52
|
+
api_session,
|
|
53
|
+
common_job_parameters,
|
|
54
|
+
org=config.tailscale_org,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
cartography.intel.tailscale.devices.sync(
|
|
58
|
+
neo4j_session,
|
|
59
|
+
api_session,
|
|
60
|
+
common_job_parameters,
|
|
61
|
+
org=config.tailscale_org,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
cartography.intel.tailscale.postureintegrations.sync(
|
|
65
|
+
neo4j_session,
|
|
66
|
+
api_session,
|
|
67
|
+
common_job_parameters,
|
|
68
|
+
org=config.tailscale_org,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
cartography.intel.tailscale.acls.sync(
|
|
72
|
+
neo4j_session,
|
|
73
|
+
api_session,
|
|
74
|
+
common_job_parameters,
|
|
75
|
+
org=config.tailscale_org,
|
|
76
|
+
users=users,
|
|
77
|
+
)
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any
|
|
3
|
+
from typing import Dict
|
|
4
|
+
from typing import List
|
|
5
|
+
from typing import Tuple
|
|
6
|
+
|
|
7
|
+
import neo4j
|
|
8
|
+
import requests
|
|
9
|
+
|
|
10
|
+
from cartography.client.core.tx import load
|
|
11
|
+
from cartography.graph.job import GraphJob
|
|
12
|
+
from cartography.intel.tailscale.utils import ACLParser
|
|
13
|
+
from cartography.intel.tailscale.utils import role_to_group
|
|
14
|
+
from cartography.models.tailscale.group import TailscaleGroupSchema
|
|
15
|
+
from cartography.models.tailscale.tag import TailscaleTagSchema
|
|
16
|
+
from cartography.util import timeit
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
# Connect and read timeouts of 60 seconds each; see https://requests.readthedocs.io/en/master/user/advanced/#timeouts
|
|
20
|
+
_TIMEOUT = (60, 60)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@timeit
|
|
24
|
+
def sync(
|
|
25
|
+
neo4j_session: neo4j.Session,
|
|
26
|
+
api_session: requests.Session,
|
|
27
|
+
common_job_parameters: Dict[str, Any],
|
|
28
|
+
org: str,
|
|
29
|
+
users: List[Dict[str, Any]],
|
|
30
|
+
) -> None:
|
|
31
|
+
raw_acl = get(
|
|
32
|
+
api_session,
|
|
33
|
+
common_job_parameters["BASE_URL"],
|
|
34
|
+
org,
|
|
35
|
+
)
|
|
36
|
+
groups, tags = transform(raw_acl, users)
|
|
37
|
+
load_groups(
|
|
38
|
+
neo4j_session,
|
|
39
|
+
groups,
|
|
40
|
+
common_job_parameters["UPDATE_TAG"],
|
|
41
|
+
org,
|
|
42
|
+
)
|
|
43
|
+
load_tags(
|
|
44
|
+
neo4j_session,
|
|
45
|
+
tags,
|
|
46
|
+
org,
|
|
47
|
+
common_job_parameters["UPDATE_TAG"],
|
|
48
|
+
)
|
|
49
|
+
cleanup(neo4j_session, common_job_parameters)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@timeit
|
|
53
|
+
def get(
|
|
54
|
+
api_session: requests.Session,
|
|
55
|
+
base_url: str,
|
|
56
|
+
org: str,
|
|
57
|
+
) -> str:
|
|
58
|
+
req = api_session.get(
|
|
59
|
+
f"{base_url}/tailnet/{org}/acl",
|
|
60
|
+
timeout=_TIMEOUT,
|
|
61
|
+
)
|
|
62
|
+
req.raise_for_status()
|
|
63
|
+
return req.text
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def transform(
|
|
67
|
+
raw_acl: str,
|
|
68
|
+
users: List[Dict[str, Any]],
|
|
69
|
+
) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]:
|
|
70
|
+
transformed_groups: Dict[str, Dict[str, Any]] = {}
|
|
71
|
+
transformed_tags: Dict[str, Dict[str, Any]] = {}
|
|
72
|
+
|
|
73
|
+
parser = ACLParser(raw_acl)
|
|
74
|
+
# Extract groups from the ACL
|
|
75
|
+
for group in parser.get_groups():
|
|
76
|
+
for dom in group["domain_members"]:
|
|
77
|
+
for user in users:
|
|
78
|
+
if user["loginName"].endswith(f"@{dom}"):
|
|
79
|
+
group["members"].append(user["loginName"])
|
|
80
|
+
# Ensure domain members are unique
|
|
81
|
+
group["domain_members"] = list(set(group["domain_members"]))
|
|
82
|
+
transformed_groups[group["id"]] = group
|
|
83
|
+
# Extract tags from the ACL
|
|
84
|
+
for tag in parser.get_tags():
|
|
85
|
+
for dom in tag["domain_owners"]:
|
|
86
|
+
for user in users:
|
|
87
|
+
if user["loginName"].endswith(f"@{dom}"):
|
|
88
|
+
tag["owners"].append(user["loginName"])
|
|
89
|
+
# Ensure domain owners are unique
|
|
90
|
+
tag["owners"] = list(set(tag["owners"]))
|
|
91
|
+
transformed_tags[tag["id"]] = tag
|
|
92
|
+
|
|
93
|
+
# Add autogroups based on user roles
|
|
94
|
+
for user in users:
|
|
95
|
+
for g in role_to_group(user["role"]):
|
|
96
|
+
if g not in transformed_groups:
|
|
97
|
+
transformed_groups[g] = {
|
|
98
|
+
"id": g,
|
|
99
|
+
"name": g.split(":")[-1],
|
|
100
|
+
"members": [],
|
|
101
|
+
"sub_groups": [],
|
|
102
|
+
"domain_members": [],
|
|
103
|
+
}
|
|
104
|
+
transformed_groups[g]["members"].append(user["loginName"])
|
|
105
|
+
|
|
106
|
+
return list(transformed_groups.values()), list(transformed_tags.values())
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@timeit
|
|
110
|
+
def load_groups(
|
|
111
|
+
neo4j_session: neo4j.Session,
|
|
112
|
+
groups: List[Dict[str, Any]],
|
|
113
|
+
update_tag: str,
|
|
114
|
+
org: str,
|
|
115
|
+
) -> None:
|
|
116
|
+
logger.info(f"Loading {len(groups)} Tailscale Groups to the graph")
|
|
117
|
+
load(neo4j_session, TailscaleGroupSchema(), groups, lastupdated=update_tag, org=org)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@timeit
|
|
121
|
+
def load_tags(
|
|
122
|
+
neo4j_session: neo4j.Session,
|
|
123
|
+
data: List[Dict[str, Any]],
|
|
124
|
+
org: str,
|
|
125
|
+
update_tag: int,
|
|
126
|
+
) -> None:
|
|
127
|
+
logger.info(f"Loading {len(data)} Tailscale Tags to the graph")
|
|
128
|
+
load(
|
|
129
|
+
neo4j_session,
|
|
130
|
+
TailscaleTagSchema(),
|
|
131
|
+
data,
|
|
132
|
+
lastupdated=update_tag,
|
|
133
|
+
org=org,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@timeit
|
|
138
|
+
def cleanup(
|
|
139
|
+
neo4j_session: neo4j.Session, common_job_parameters: Dict[str, Any]
|
|
140
|
+
) -> None:
|
|
141
|
+
GraphJob.from_node_schema(TailscaleGroupSchema(), common_job_parameters).run(
|
|
142
|
+
neo4j_session
|
|
143
|
+
)
|
|
144
|
+
GraphJob.from_node_schema(TailscaleTagSchema(), common_job_parameters).run(
|
|
145
|
+
neo4j_session
|
|
146
|
+
)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any
|
|
3
|
+
from typing import Dict
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
import neo4j
|
|
7
|
+
import requests
|
|
8
|
+
|
|
9
|
+
from cartography.client.core.tx import load
|
|
10
|
+
from cartography.graph.job import GraphJob
|
|
11
|
+
from cartography.models.tailscale.device import TailscaleDeviceSchema
|
|
12
|
+
from cartography.models.tailscale.tag import TailscaleTagSchema
|
|
13
|
+
from cartography.util import timeit
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
# Connect and read timeouts of 60 seconds each; see https://requests.readthedocs.io/en/master/user/advanced/#timeouts
|
|
17
|
+
_TIMEOUT = (60, 60)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@timeit
|
|
21
|
+
def sync(
|
|
22
|
+
neo4j_session: neo4j.Session,
|
|
23
|
+
api_session: requests.Session,
|
|
24
|
+
common_job_parameters: Dict[str, Any],
|
|
25
|
+
org: str,
|
|
26
|
+
) -> List[Dict]:
|
|
27
|
+
devices = get(
|
|
28
|
+
api_session,
|
|
29
|
+
common_job_parameters["BASE_URL"],
|
|
30
|
+
org,
|
|
31
|
+
)
|
|
32
|
+
tags = transform(devices)
|
|
33
|
+
load_devices(
|
|
34
|
+
neo4j_session,
|
|
35
|
+
devices,
|
|
36
|
+
org,
|
|
37
|
+
common_job_parameters["UPDATE_TAG"],
|
|
38
|
+
)
|
|
39
|
+
load_tags(
|
|
40
|
+
neo4j_session,
|
|
41
|
+
tags,
|
|
42
|
+
org,
|
|
43
|
+
common_job_parameters["UPDATE_TAG"],
|
|
44
|
+
)
|
|
45
|
+
cleanup(neo4j_session, common_job_parameters)
|
|
46
|
+
return devices
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@timeit
|
|
50
|
+
def get(
|
|
51
|
+
api_session: requests.Session,
|
|
52
|
+
base_url: str,
|
|
53
|
+
org: str,
|
|
54
|
+
) -> List[Dict[str, Any]]:
|
|
55
|
+
results: List[Dict[str, Any]] = []
|
|
56
|
+
req = api_session.get(
|
|
57
|
+
f"{base_url}/tailnet/{org}/devices",
|
|
58
|
+
timeout=_TIMEOUT,
|
|
59
|
+
)
|
|
60
|
+
req.raise_for_status()
|
|
61
|
+
results = req.json()["devices"]
|
|
62
|
+
return results
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def transform(
|
|
66
|
+
raw_data: List[Dict[str, Any]],
|
|
67
|
+
) -> List[Dict[str, Any]]:
|
|
68
|
+
"""Extracts tags from the raw data and returns a list of dictionaries"""
|
|
69
|
+
transformed_tags: Dict[str, Dict[str, Any]] = {}
|
|
70
|
+
# Transform the raw data into the format expected by the load function
|
|
71
|
+
for device in raw_data:
|
|
72
|
+
for raw_tag in device.get("tags", []):
|
|
73
|
+
if raw_tag not in transformed_tags:
|
|
74
|
+
transformed_tags[raw_tag] = {
|
|
75
|
+
"id": raw_tag,
|
|
76
|
+
"name": raw_tag.split(":")[-1],
|
|
77
|
+
"devices": [device["nodeId"]],
|
|
78
|
+
}
|
|
79
|
+
else:
|
|
80
|
+
transformed_tags[raw_tag]["devices"].append(device["nodeId"])
|
|
81
|
+
return list(transformed_tags.values())
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@timeit
|
|
85
|
+
def load_devices(
|
|
86
|
+
neo4j_session: neo4j.Session,
|
|
87
|
+
data: List[Dict[str, Any]],
|
|
88
|
+
org: str,
|
|
89
|
+
update_tag: int,
|
|
90
|
+
) -> None:
|
|
91
|
+
logger.info(f"Loading {len(data)} Tailscale Devices to the graph")
|
|
92
|
+
load(
|
|
93
|
+
neo4j_session,
|
|
94
|
+
TailscaleDeviceSchema(),
|
|
95
|
+
data,
|
|
96
|
+
lastupdated=update_tag,
|
|
97
|
+
org=org,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@timeit
|
|
102
|
+
def load_tags(
|
|
103
|
+
neo4j_session: neo4j.Session,
|
|
104
|
+
data: List[Dict[str, Any]],
|
|
105
|
+
org: str,
|
|
106
|
+
update_tag: int,
|
|
107
|
+
) -> None:
|
|
108
|
+
logger.info(f"Loading {len(data)} Tailscale Tags to the graph")
|
|
109
|
+
load(
|
|
110
|
+
neo4j_session,
|
|
111
|
+
TailscaleTagSchema(),
|
|
112
|
+
data,
|
|
113
|
+
lastupdated=update_tag,
|
|
114
|
+
org=org,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@timeit
|
|
119
|
+
def cleanup(
|
|
120
|
+
neo4j_session: neo4j.Session, common_job_parameters: Dict[str, Any]
|
|
121
|
+
) -> None:
|
|
122
|
+
GraphJob.from_node_schema(TailscaleDeviceSchema(), common_job_parameters).run(
|
|
123
|
+
neo4j_session
|
|
124
|
+
)
|
|
125
|
+
GraphJob.from_node_schema(TailscaleTagSchema(), common_job_parameters).run(
|
|
126
|
+
neo4j_session
|
|
127
|
+
)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any
|
|
3
|
+
from typing import Dict
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
import neo4j
|
|
7
|
+
import requests
|
|
8
|
+
|
|
9
|
+
from cartography.client.core.tx import load
|
|
10
|
+
from cartography.graph.job import GraphJob
|
|
11
|
+
from cartography.models.tailscale.postureintegration import (
|
|
12
|
+
TailscalePostureIntegrationSchema,
|
|
13
|
+
)
|
|
14
|
+
from cartography.util import timeit
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
# Connect and read timeouts of 60 seconds each; see https://requests.readthedocs.io/en/master/user/advanced/#timeouts
|
|
18
|
+
_TIMEOUT = (60, 60)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@timeit
|
|
22
|
+
def sync(
|
|
23
|
+
neo4j_session: neo4j.Session,
|
|
24
|
+
api_session: requests.Session,
|
|
25
|
+
common_job_parameters: Dict[str, Any],
|
|
26
|
+
org: str,
|
|
27
|
+
) -> None:
|
|
28
|
+
postureintegrations = get(
|
|
29
|
+
api_session,
|
|
30
|
+
common_job_parameters["BASE_URL"],
|
|
31
|
+
org,
|
|
32
|
+
)
|
|
33
|
+
load_postureintegrations(
|
|
34
|
+
neo4j_session,
|
|
35
|
+
postureintegrations,
|
|
36
|
+
org,
|
|
37
|
+
common_job_parameters["UPDATE_TAG"],
|
|
38
|
+
)
|
|
39
|
+
cleanup(neo4j_session, common_job_parameters)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@timeit
|
|
43
|
+
def get(
|
|
44
|
+
api_session: requests.Session,
|
|
45
|
+
base_url: str,
|
|
46
|
+
org: str,
|
|
47
|
+
) -> List[Dict[str, Any]]:
|
|
48
|
+
results: List[Dict[str, Any]] = []
|
|
49
|
+
req = api_session.get(
|
|
50
|
+
f"{base_url}/tailnet/{org}/posture/integrations",
|
|
51
|
+
timeout=_TIMEOUT,
|
|
52
|
+
)
|
|
53
|
+
req.raise_for_status()
|
|
54
|
+
results = req.json()["integrations"]
|
|
55
|
+
return results
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@timeit
|
|
59
|
+
def load_postureintegrations(
|
|
60
|
+
neo4j_session: neo4j.Session,
|
|
61
|
+
data: List[Dict[str, Any]],
|
|
62
|
+
org: str,
|
|
63
|
+
update_tag: int,
|
|
64
|
+
) -> None:
|
|
65
|
+
logger.info(f"Loading {len(data)} Tailscale PostureIntegrations to the graph")
|
|
66
|
+
load(
|
|
67
|
+
neo4j_session,
|
|
68
|
+
TailscalePostureIntegrationSchema(),
|
|
69
|
+
data,
|
|
70
|
+
lastupdated=update_tag,
|
|
71
|
+
org=org,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@timeit
|
|
76
|
+
def cleanup(
|
|
77
|
+
neo4j_session: neo4j.Session, common_job_parameters: Dict[str, Any]
|
|
78
|
+
) -> None:
|
|
79
|
+
GraphJob.from_node_schema(
|
|
80
|
+
TailscalePostureIntegrationSchema(), common_job_parameters
|
|
81
|
+
).run(neo4j_session)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any
|
|
3
|
+
from typing import Dict
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
import neo4j
|
|
7
|
+
import requests
|
|
8
|
+
|
|
9
|
+
from cartography.client.core.tx import load
|
|
10
|
+
from cartography.graph.job import GraphJob
|
|
11
|
+
from cartography.models.tailscale.tailnet import TailscaleTailnetSchema
|
|
12
|
+
from cartography.util import timeit
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
# Connect and read timeouts of 60 seconds each; see https://requests.readthedocs.io/en/master/user/advanced/#timeouts
|
|
16
|
+
_TIMEOUT = (60, 60)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@timeit
|
|
20
|
+
def sync(
|
|
21
|
+
neo4j_session: neo4j.Session,
|
|
22
|
+
api_session: requests.Session,
|
|
23
|
+
common_job_parameters: Dict[str, Any],
|
|
24
|
+
org: str,
|
|
25
|
+
) -> None:
|
|
26
|
+
tailnet = get(
|
|
27
|
+
api_session,
|
|
28
|
+
common_job_parameters["BASE_URL"],
|
|
29
|
+
org,
|
|
30
|
+
)
|
|
31
|
+
load_tailnets(
|
|
32
|
+
neo4j_session,
|
|
33
|
+
[tailnet],
|
|
34
|
+
org,
|
|
35
|
+
common_job_parameters["UPDATE_TAG"],
|
|
36
|
+
)
|
|
37
|
+
cleanup(neo4j_session, common_job_parameters)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@timeit
|
|
41
|
+
def get(
|
|
42
|
+
api_session: requests.Session,
|
|
43
|
+
base_url: str,
|
|
44
|
+
org: str,
|
|
45
|
+
) -> Dict[str, Any]:
|
|
46
|
+
req = api_session.get(
|
|
47
|
+
f"{base_url}/tailnet/{org}/settings",
|
|
48
|
+
timeout=_TIMEOUT,
|
|
49
|
+
)
|
|
50
|
+
req.raise_for_status()
|
|
51
|
+
return req.json()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@timeit
|
|
55
|
+
def load_tailnets(
|
|
56
|
+
neo4j_session: neo4j.Session,
|
|
57
|
+
data: List[Dict[str, Any]],
|
|
58
|
+
org: str,
|
|
59
|
+
update_tag: int,
|
|
60
|
+
) -> None:
|
|
61
|
+
load(
|
|
62
|
+
neo4j_session,
|
|
63
|
+
TailscaleTailnetSchema(),
|
|
64
|
+
data,
|
|
65
|
+
lastupdated=update_tag,
|
|
66
|
+
org=org,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@timeit
|
|
71
|
+
def cleanup(
|
|
72
|
+
neo4j_session: neo4j.Session, common_job_parameters: Dict[str, Any]
|
|
73
|
+
) -> None:
|
|
74
|
+
GraphJob.from_node_schema(TailscaleTailnetSchema(), common_job_parameters).run(
|
|
75
|
+
neo4j_session
|
|
76
|
+
)
|