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,80 @@
|
|
|
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.user import TailscaleUserSchema
|
|
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
|
+
) -> List[Dict]:
|
|
26
|
+
users = get(
|
|
27
|
+
api_session,
|
|
28
|
+
common_job_parameters["BASE_URL"],
|
|
29
|
+
org,
|
|
30
|
+
)
|
|
31
|
+
load_users(
|
|
32
|
+
neo4j_session,
|
|
33
|
+
users,
|
|
34
|
+
org,
|
|
35
|
+
common_job_parameters["UPDATE_TAG"],
|
|
36
|
+
)
|
|
37
|
+
cleanup(neo4j_session, common_job_parameters)
|
|
38
|
+
return users
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@timeit
|
|
42
|
+
def get(
|
|
43
|
+
api_session: requests.Session,
|
|
44
|
+
base_url: str,
|
|
45
|
+
org: str,
|
|
46
|
+
) -> List[Dict[str, Any]]:
|
|
47
|
+
results: List[Dict[str, Any]] = []
|
|
48
|
+
req = api_session.get(
|
|
49
|
+
f"{base_url}/tailnet/{org}/users",
|
|
50
|
+
timeout=_TIMEOUT,
|
|
51
|
+
)
|
|
52
|
+
req.raise_for_status()
|
|
53
|
+
results = req.json()["users"]
|
|
54
|
+
return results
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@timeit
|
|
58
|
+
def load_users(
|
|
59
|
+
neo4j_session: neo4j.Session,
|
|
60
|
+
data: List[Dict[str, Any]],
|
|
61
|
+
org: str,
|
|
62
|
+
update_tag: int,
|
|
63
|
+
) -> None:
|
|
64
|
+
logger.info(f"Loading {len(data)} Tailscale Users to the graph")
|
|
65
|
+
load(
|
|
66
|
+
neo4j_session,
|
|
67
|
+
TailscaleUserSchema(),
|
|
68
|
+
data,
|
|
69
|
+
lastupdated=update_tag,
|
|
70
|
+
org=org,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@timeit
|
|
75
|
+
def cleanup(
|
|
76
|
+
neo4j_session: neo4j.Session, common_job_parameters: Dict[str, Any]
|
|
77
|
+
) -> None:
|
|
78
|
+
GraphJob.from_node_schema(TailscaleUserSchema(), common_job_parameters).run(
|
|
79
|
+
neo4j_session
|
|
80
|
+
)
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import re
|
|
3
|
+
from typing import Any
|
|
4
|
+
from typing import Dict
|
|
5
|
+
from typing import List
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ACLParser:
|
|
9
|
+
"""ACLParser is a class that parses Tailscale ACLs to extract data.
|
|
10
|
+
|
|
11
|
+
It removes comments and trailing commas from the ACL string
|
|
12
|
+
and converts it to a JSON object. It then provides methods
|
|
13
|
+
to extract groups and tags from the ACL.
|
|
14
|
+
The ACL string is expected to be in a format similar to JSON,
|
|
15
|
+
but with some Tailscale-specific syntax. The parser handles
|
|
16
|
+
single-line comments (//) and multi-line comments (/* */)
|
|
17
|
+
and removes trailing commas from the JSON-like structure.
|
|
18
|
+
The parser also handles Tailscale-specific syntax for groups
|
|
19
|
+
and tags, which may include user and group identifiers.
|
|
20
|
+
The parser is initialized with a raw ACL string, which is
|
|
21
|
+
processed to remove comments and trailing commas.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
raw_acl (str): The raw ACL string to be parsed.
|
|
25
|
+
|
|
26
|
+
Attributes:
|
|
27
|
+
data (dict): The parsed JSON object representing the ACL.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
RE_SINGLE_LINE_COMMENT = re.compile(r'("(?:(?=(\\?))\2.)*?")|(?:\/{2,}.*)')
|
|
31
|
+
RE_MULTI_LINE_COMMENT = re.compile(
|
|
32
|
+
r'("(?:(?=(\\?))\2.)*?")|(?:\/\*(?:(?!\*\/).)+\*\/)', flags=re.M | re.DOTALL
|
|
33
|
+
)
|
|
34
|
+
RE_TRAILING_COMMA = re.compile(r",(?=\s*?[\}\]])")
|
|
35
|
+
|
|
36
|
+
def __init__(self, raw_acl: str) -> None:
|
|
37
|
+
# Tailscale ACL use comments and trailing commas
|
|
38
|
+
# that are not valid JSON
|
|
39
|
+
filtered_json_string = self.RE_SINGLE_LINE_COMMENT.sub(r"\1", raw_acl)
|
|
40
|
+
filtered_json_string = self.RE_MULTI_LINE_COMMENT.sub(
|
|
41
|
+
r"\1", filtered_json_string
|
|
42
|
+
)
|
|
43
|
+
filtered_json_string = self.RE_TRAILING_COMMA.sub("", filtered_json_string)
|
|
44
|
+
self.data = json.loads(filtered_json_string)
|
|
45
|
+
|
|
46
|
+
def get_groups(self) -> List[Dict[str, Any]]:
|
|
47
|
+
"""
|
|
48
|
+
Get all groups from the ACL
|
|
49
|
+
|
|
50
|
+
:return: list of groups
|
|
51
|
+
"""
|
|
52
|
+
result: List[Dict[str, Any]] = []
|
|
53
|
+
groups = self.data.get("groups", {})
|
|
54
|
+
for group_id, members in groups.items():
|
|
55
|
+
group_name = group_id.split(":")[-1]
|
|
56
|
+
users_members = []
|
|
57
|
+
sub_groups = []
|
|
58
|
+
domain_members = []
|
|
59
|
+
for member in members:
|
|
60
|
+
if member.startswith("group:") or member.startswith("autogroup:"):
|
|
61
|
+
sub_groups.append(member)
|
|
62
|
+
elif member.startswith("user:*@"):
|
|
63
|
+
domain_members.append(member[7:])
|
|
64
|
+
elif member.startswith("user:"):
|
|
65
|
+
users_members.append(member[5:])
|
|
66
|
+
else:
|
|
67
|
+
users_members.append(member)
|
|
68
|
+
result.append(
|
|
69
|
+
{
|
|
70
|
+
"id": group_id,
|
|
71
|
+
"name": group_name,
|
|
72
|
+
"members": users_members,
|
|
73
|
+
"sub_groups": sub_groups,
|
|
74
|
+
"domain_members": domain_members,
|
|
75
|
+
}
|
|
76
|
+
)
|
|
77
|
+
return result
|
|
78
|
+
|
|
79
|
+
def get_tags(self) -> List[Dict[str, Any]]:
|
|
80
|
+
"""
|
|
81
|
+
Get all tags from the ACL
|
|
82
|
+
|
|
83
|
+
:return: list of tags
|
|
84
|
+
"""
|
|
85
|
+
result: List[Dict[str, Any]] = []
|
|
86
|
+
for tag, owners in self.data.get("tagOwners", {}).items():
|
|
87
|
+
tag_name = tag.split(":")[-1]
|
|
88
|
+
user_owners = []
|
|
89
|
+
group_owners = []
|
|
90
|
+
domain_owners = []
|
|
91
|
+
for owner in owners:
|
|
92
|
+
if owner.startswith("group:") or owner.startswith("autogroup:"):
|
|
93
|
+
group_owners.append(owner)
|
|
94
|
+
elif owner.startswith("user:*@"):
|
|
95
|
+
domain_owners.append(owner[7:])
|
|
96
|
+
elif owner.startswith("user:"):
|
|
97
|
+
user_owners.append(owner[5:])
|
|
98
|
+
else:
|
|
99
|
+
user_owners.append(owner)
|
|
100
|
+
result.append(
|
|
101
|
+
{
|
|
102
|
+
"id": tag,
|
|
103
|
+
"name": tag_name,
|
|
104
|
+
"owners": user_owners,
|
|
105
|
+
"group_owners": group_owners,
|
|
106
|
+
"domain_owners": domain_owners,
|
|
107
|
+
}
|
|
108
|
+
)
|
|
109
|
+
return result
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def role_to_group(role: str) -> list[str]:
|
|
113
|
+
"""Convert Tailscale role to group
|
|
114
|
+
|
|
115
|
+
This function is used to convert Tailscale role to autogroup
|
|
116
|
+
group. The autogroup is used to manage the access control
|
|
117
|
+
in Tailscale.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
role (str): The role of the user in Tailscale. (eg: owner, admin, member, etc)
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
list[str]: The list of autogroup that the user belongs to. (eg: autogroup:admin, autogroup:member, etc)
|
|
124
|
+
"""
|
|
125
|
+
result: list[str] = []
|
|
126
|
+
result.append(f"autogroup:{role}")
|
|
127
|
+
if role == "owner":
|
|
128
|
+
result.append("autogroup:admin")
|
|
129
|
+
result.append("autogroup:member")
|
|
130
|
+
elif role in ("admin", "auditor", "billing-admin", "it-admin", "network-admin"):
|
|
131
|
+
result.append("autogroup:member")
|
|
132
|
+
return result
|
|
File without changes
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from cartography.models.core.common import PropertyRef
|
|
4
|
+
from cartography.models.core.nodes import CartographyNodeProperties
|
|
5
|
+
from cartography.models.core.nodes import CartographyNodeSchema
|
|
6
|
+
from cartography.models.core.relationships import CartographyRelProperties
|
|
7
|
+
from cartography.models.core.relationships import CartographyRelSchema
|
|
8
|
+
from cartography.models.core.relationships import LinkDirection
|
|
9
|
+
from cartography.models.core.relationships import make_target_node_matcher
|
|
10
|
+
from cartography.models.core.relationships import OtherRelationships
|
|
11
|
+
from cartography.models.core.relationships import TargetNodeMatcher
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class AnthropicApiKeyNodeProperties(CartographyNodeProperties):
|
|
16
|
+
id: PropertyRef = PropertyRef("id")
|
|
17
|
+
name: PropertyRef = PropertyRef("name")
|
|
18
|
+
status: PropertyRef = PropertyRef("status")
|
|
19
|
+
created_at: PropertyRef = PropertyRef("created_at")
|
|
20
|
+
last_used_at: PropertyRef = PropertyRef("last_used_at")
|
|
21
|
+
lastupdated: PropertyRef = PropertyRef("lastupdated", set_in_kwargs=True)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class AnthropicApiKeyToOrganizationRelProperties(CartographyRelProperties):
|
|
26
|
+
lastupdated: PropertyRef = PropertyRef("lastupdated", set_in_kwargs=True)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True)
|
|
30
|
+
# (:AnthropicOrganization)-[:RESOURCE]->(:AnthropicApiKey)
|
|
31
|
+
class AnthropicApiKeyToOrganizationRel(CartographyRelSchema):
|
|
32
|
+
target_node_label: str = "AnthropicOrganization"
|
|
33
|
+
target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
|
|
34
|
+
{"id": PropertyRef("ORG_ID", set_in_kwargs=True)},
|
|
35
|
+
)
|
|
36
|
+
direction: LinkDirection = LinkDirection.INWARD
|
|
37
|
+
rel_label: str = "RESOURCE"
|
|
38
|
+
properties: AnthropicApiKeyToOrganizationRelProperties = (
|
|
39
|
+
AnthropicApiKeyToOrganizationRelProperties()
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass(frozen=True)
|
|
44
|
+
class AnthropicApiKeyToUserRelProperties(CartographyRelProperties):
|
|
45
|
+
lastupdated: PropertyRef = PropertyRef("lastupdated", set_in_kwargs=True)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True)
|
|
49
|
+
# (:AnthropicUser)-[:OWNS]->(:AnthropicApiKey)
|
|
50
|
+
class AnthropicApiKeyToUserRel(CartographyRelSchema):
|
|
51
|
+
target_node_label: str = "AnthropicUser"
|
|
52
|
+
target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
|
|
53
|
+
{"id": PropertyRef("created_by.id")},
|
|
54
|
+
)
|
|
55
|
+
direction: LinkDirection = LinkDirection.INWARD
|
|
56
|
+
rel_label: str = "OWNS"
|
|
57
|
+
properties: AnthropicApiKeyToUserRelProperties = (
|
|
58
|
+
AnthropicApiKeyToUserRelProperties()
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass(frozen=True)
|
|
63
|
+
class AnthropicApiKeyToWorkspaceRelProperties(CartographyRelProperties):
|
|
64
|
+
lastupdated: PropertyRef = PropertyRef("lastupdated", set_in_kwargs=True)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass(frozen=True)
|
|
68
|
+
# (:AnthropicWorkspace)-[:CONTAINS]->(:AnthropicApiKey)
|
|
69
|
+
class AnthropicApiKeyToWorkspaceRel(CartographyRelSchema):
|
|
70
|
+
target_node_label: str = "AnthropicWorkspace"
|
|
71
|
+
target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
|
|
72
|
+
{"id": PropertyRef("workspace_id")},
|
|
73
|
+
)
|
|
74
|
+
direction: LinkDirection = LinkDirection.INWARD
|
|
75
|
+
rel_label: str = "CONTAINS"
|
|
76
|
+
properties: AnthropicApiKeyToWorkspaceRelProperties = (
|
|
77
|
+
AnthropicApiKeyToWorkspaceRelProperties()
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass(frozen=True)
|
|
82
|
+
class AnthropicApiKeySchema(CartographyNodeSchema):
|
|
83
|
+
label: str = "AnthropicApiKey"
|
|
84
|
+
properties: AnthropicApiKeyNodeProperties = AnthropicApiKeyNodeProperties()
|
|
85
|
+
sub_resource_relationship: AnthropicApiKeyToOrganizationRel = (
|
|
86
|
+
AnthropicApiKeyToOrganizationRel()
|
|
87
|
+
)
|
|
88
|
+
other_relationships: OtherRelationships = OtherRelationships(
|
|
89
|
+
[AnthropicApiKeyToUserRel(), AnthropicApiKeyToWorkspaceRel()],
|
|
90
|
+
)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from cartography.models.core.common import PropertyRef
|
|
4
|
+
from cartography.models.core.nodes import CartographyNodeProperties
|
|
5
|
+
from cartography.models.core.nodes import CartographyNodeSchema
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True)
|
|
9
|
+
class AnthropicOrganizationNodeProperties(CartographyNodeProperties):
|
|
10
|
+
id: PropertyRef = PropertyRef("id")
|
|
11
|
+
lastupdated: PropertyRef = PropertyRef("lastupdated", set_in_kwargs=True)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class AnthropicOrganizationSchema(CartographyNodeSchema):
|
|
16
|
+
label: str = "AnthropicOrganization"
|
|
17
|
+
properties: AnthropicOrganizationNodeProperties = (
|
|
18
|
+
AnthropicOrganizationNodeProperties()
|
|
19
|
+
)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from cartography.models.core.common import PropertyRef
|
|
4
|
+
from cartography.models.core.nodes import CartographyNodeProperties
|
|
5
|
+
from cartography.models.core.nodes import CartographyNodeSchema
|
|
6
|
+
from cartography.models.core.relationships import CartographyRelProperties
|
|
7
|
+
from cartography.models.core.relationships import CartographyRelSchema
|
|
8
|
+
from cartography.models.core.relationships import LinkDirection
|
|
9
|
+
from cartography.models.core.relationships import make_target_node_matcher
|
|
10
|
+
from cartography.models.core.relationships import TargetNodeMatcher
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class AnthropicUserNodeProperties(CartographyNodeProperties):
|
|
15
|
+
id: PropertyRef = PropertyRef("id")
|
|
16
|
+
name: PropertyRef = PropertyRef("name")
|
|
17
|
+
email: PropertyRef = PropertyRef("email", extra_index=True)
|
|
18
|
+
role: PropertyRef = PropertyRef("role")
|
|
19
|
+
added_at: PropertyRef = PropertyRef("added_at")
|
|
20
|
+
lastupdated: PropertyRef = PropertyRef("lastupdated", set_in_kwargs=True)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True)
|
|
24
|
+
class AnthropicUserToOrganizationRelProperties(CartographyRelProperties):
|
|
25
|
+
lastupdated: PropertyRef = PropertyRef("lastupdated", set_in_kwargs=True)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True)
|
|
29
|
+
# (:AnthropicOrganization)-[:RESOURCE]->(:AnthropicUser)
|
|
30
|
+
class AnthropicUserToOrganizationRel(CartographyRelSchema):
|
|
31
|
+
target_node_label: str = "AnthropicOrganization"
|
|
32
|
+
target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
|
|
33
|
+
{"id": PropertyRef("ORG_ID", set_in_kwargs=True)},
|
|
34
|
+
)
|
|
35
|
+
direction: LinkDirection = LinkDirection.INWARD
|
|
36
|
+
rel_label: str = "RESOURCE"
|
|
37
|
+
properties: AnthropicUserToOrganizationRelProperties = (
|
|
38
|
+
AnthropicUserToOrganizationRelProperties()
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True)
|
|
43
|
+
class AnthropicUserSchema(CartographyNodeSchema):
|
|
44
|
+
label: str = "AnthropicUser"
|
|
45
|
+
properties: AnthropicUserNodeProperties = AnthropicUserNodeProperties()
|
|
46
|
+
sub_resource_relationship: AnthropicUserToOrganizationRel = (
|
|
47
|
+
AnthropicUserToOrganizationRel()
|
|
48
|
+
)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from cartography.models.core.common import PropertyRef
|
|
4
|
+
from cartography.models.core.nodes import CartographyNodeProperties
|
|
5
|
+
from cartography.models.core.nodes import CartographyNodeSchema
|
|
6
|
+
from cartography.models.core.relationships import CartographyRelProperties
|
|
7
|
+
from cartography.models.core.relationships import CartographyRelSchema
|
|
8
|
+
from cartography.models.core.relationships import LinkDirection
|
|
9
|
+
from cartography.models.core.relationships import make_target_node_matcher
|
|
10
|
+
from cartography.models.core.relationships import OtherRelationships
|
|
11
|
+
from cartography.models.core.relationships import TargetNodeMatcher
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class AnthropicWorkspaceNodeProperties(CartographyNodeProperties):
|
|
16
|
+
id: PropertyRef = PropertyRef("id")
|
|
17
|
+
name: PropertyRef = PropertyRef("name")
|
|
18
|
+
created_at: PropertyRef = PropertyRef("created_at")
|
|
19
|
+
archived_at: PropertyRef = PropertyRef("archived_at")
|
|
20
|
+
display_color: PropertyRef = PropertyRef("display_color")
|
|
21
|
+
lastupdated: PropertyRef = PropertyRef("lastupdated", set_in_kwargs=True)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class AnthropicWorkspaceToOrganizationRelProperties(CartographyRelProperties):
|
|
26
|
+
lastupdated: PropertyRef = PropertyRef("lastupdated", set_in_kwargs=True)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True)
|
|
30
|
+
# (:AnthropicOrganization)-[:RESOURCE]->(:AnthropicWorkspace)
|
|
31
|
+
class AnthropicWorkspaceToOrganizationRel(CartographyRelSchema):
|
|
32
|
+
target_node_label: str = "AnthropicOrganization"
|
|
33
|
+
target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
|
|
34
|
+
{"id": PropertyRef("ORG_ID", set_in_kwargs=True)},
|
|
35
|
+
)
|
|
36
|
+
direction: LinkDirection = LinkDirection.INWARD
|
|
37
|
+
rel_label: str = "RESOURCE"
|
|
38
|
+
properties: AnthropicWorkspaceToOrganizationRelProperties = (
|
|
39
|
+
AnthropicWorkspaceToOrganizationRelProperties()
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass(frozen=True)
|
|
44
|
+
class AnthropicWorkspaceToUserRelProperties(CartographyRelProperties):
|
|
45
|
+
lastupdated: PropertyRef = PropertyRef("lastupdated", set_in_kwargs=True)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True)
|
|
49
|
+
# (:AnthropicUser)-[:MEMBER_OF]->(:AnthropicWorkspace)
|
|
50
|
+
class AnthropicWorkspaceToUserRel(CartographyRelSchema):
|
|
51
|
+
target_node_label: str = "AnthropicUser"
|
|
52
|
+
target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
|
|
53
|
+
{"id": PropertyRef("users", one_to_many=True)},
|
|
54
|
+
)
|
|
55
|
+
direction: LinkDirection = LinkDirection.INWARD
|
|
56
|
+
rel_label: str = "MEMBER_OF"
|
|
57
|
+
properties: AnthropicWorkspaceToUserRelProperties = (
|
|
58
|
+
AnthropicWorkspaceToUserRelProperties()
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass(frozen=True)
|
|
63
|
+
class AnthropicWorkspaceToUserAdminRelProperties(CartographyRelProperties):
|
|
64
|
+
lastupdated: PropertyRef = PropertyRef("lastupdated", set_in_kwargs=True)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass(frozen=True)
|
|
68
|
+
# (:AnthropicUser)-[:ADMIN_OF]->(:AnthropicWorkspace)
|
|
69
|
+
class AnthropicWorkspaceToUserAdminRel(CartographyRelSchema):
|
|
70
|
+
target_node_label: str = "AnthropicUser"
|
|
71
|
+
target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
|
|
72
|
+
{"id": PropertyRef("admins", one_to_many=True)},
|
|
73
|
+
)
|
|
74
|
+
direction: LinkDirection = LinkDirection.INWARD
|
|
75
|
+
rel_label: str = "ADMIN_OF"
|
|
76
|
+
properties: AnthropicWorkspaceToUserAdminRelProperties = (
|
|
77
|
+
AnthropicWorkspaceToUserAdminRelProperties()
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass(frozen=True)
|
|
82
|
+
class AnthropicWorkspaceSchema(CartographyNodeSchema):
|
|
83
|
+
label: str = "AnthropicWorkspace"
|
|
84
|
+
properties: AnthropicWorkspaceNodeProperties = AnthropicWorkspaceNodeProperties()
|
|
85
|
+
sub_resource_relationship: AnthropicWorkspaceToOrganizationRel = (
|
|
86
|
+
AnthropicWorkspaceToOrganizationRel()
|
|
87
|
+
)
|
|
88
|
+
other_relationships: OtherRelationships = OtherRelationships(
|
|
89
|
+
[AnthropicWorkspaceToUserRel(), AnthropicWorkspaceToUserAdminRel()],
|
|
90
|
+
)
|
|
@@ -7,6 +7,7 @@ from cartography.models.core.relationships import CartographyRelProperties
|
|
|
7
7
|
from cartography.models.core.relationships import CartographyRelSchema
|
|
8
8
|
from cartography.models.core.relationships import LinkDirection
|
|
9
9
|
from cartography.models.core.relationships import make_target_node_matcher
|
|
10
|
+
from cartography.models.core.relationships import OtherRelationships
|
|
10
11
|
from cartography.models.core.relationships import TargetNodeMatcher
|
|
11
12
|
|
|
12
13
|
|
|
@@ -54,8 +55,31 @@ class CloudTrailToAWSAccountRel(CartographyRelSchema):
|
|
|
54
55
|
)
|
|
55
56
|
|
|
56
57
|
|
|
58
|
+
@dataclass(frozen=True)
|
|
59
|
+
class CloudTrailTrailToS3BucketRelProperties(CartographyRelProperties):
|
|
60
|
+
lastupdated: PropertyRef = PropertyRef("lastupdated", set_in_kwargs=True)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass(frozen=True)
|
|
64
|
+
class CloudTrailTrailToS3BucketRel(CartographyRelSchema):
|
|
65
|
+
target_node_label: str = "S3Bucket"
|
|
66
|
+
target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
|
|
67
|
+
{"name": PropertyRef("S3BucketName")},
|
|
68
|
+
)
|
|
69
|
+
direction: LinkDirection = LinkDirection.OUTWARD
|
|
70
|
+
rel_label: str = "LOGS_TO"
|
|
71
|
+
properties: CloudTrailTrailToS3BucketRelProperties = (
|
|
72
|
+
CloudTrailTrailToS3BucketRelProperties()
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
57
76
|
@dataclass(frozen=True)
|
|
58
77
|
class CloudTrailTrailSchema(CartographyNodeSchema):
|
|
59
78
|
label: str = "CloudTrailTrail"
|
|
60
79
|
properties: CloudTrailTrailNodeProperties = CloudTrailTrailNodeProperties()
|
|
61
80
|
sub_resource_relationship: CloudTrailToAWSAccountRel = CloudTrailToAWSAccountRel()
|
|
81
|
+
other_relationships: OtherRelationships = OtherRelationships(
|
|
82
|
+
[
|
|
83
|
+
CloudTrailTrailToS3BucketRel(),
|
|
84
|
+
]
|
|
85
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from cartography.models.core.common import PropertyRef
|
|
4
|
+
from cartography.models.core.nodes import CartographyNodeProperties
|
|
5
|
+
from cartography.models.core.nodes import CartographyNodeSchema
|
|
6
|
+
from cartography.models.core.relationships import CartographyRelProperties
|
|
7
|
+
from cartography.models.core.relationships import CartographyRelSchema
|
|
8
|
+
from cartography.models.core.relationships import LinkDirection
|
|
9
|
+
from cartography.models.core.relationships import make_target_node_matcher
|
|
10
|
+
from cartography.models.core.relationships import TargetNodeMatcher
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class CloudWatchLogGroupNodeProperties(CartographyNodeProperties):
|
|
15
|
+
id: PropertyRef = PropertyRef("logGroupArn")
|
|
16
|
+
arn: PropertyRef = PropertyRef("logGroupArn", extra_index=True)
|
|
17
|
+
creation_time: PropertyRef = PropertyRef("creationTime")
|
|
18
|
+
data_protection_status: PropertyRef = PropertyRef("dataProtectionStatus")
|
|
19
|
+
inherited_properties: PropertyRef = PropertyRef("inheritedProperties")
|
|
20
|
+
kms_key_id: PropertyRef = PropertyRef("kmsKeyId")
|
|
21
|
+
log_group_arn: PropertyRef = PropertyRef("logGroupArn")
|
|
22
|
+
log_group_class: PropertyRef = PropertyRef("logGroupClass")
|
|
23
|
+
log_group_name: PropertyRef = PropertyRef("logGroupName")
|
|
24
|
+
metric_filter_count: PropertyRef = PropertyRef("metricFilterCount")
|
|
25
|
+
retention_in_days: PropertyRef = PropertyRef("retentionInDays")
|
|
26
|
+
stored_bytes: PropertyRef = PropertyRef("storedBytes")
|
|
27
|
+
lastupdated: PropertyRef = PropertyRef("lastupdated", set_in_kwargs=True)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(frozen=True)
|
|
31
|
+
class CloudWatchLogGroupToAwsAccountRelProperties(CartographyRelProperties):
|
|
32
|
+
lastupdated: PropertyRef = PropertyRef("lastupdated", set_in_kwargs=True)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True)
|
|
36
|
+
class CloudWatchToAWSAccountRel(CartographyRelSchema):
|
|
37
|
+
target_node_label: str = "AWSAccount"
|
|
38
|
+
target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
|
|
39
|
+
{"id": PropertyRef("AWS_ID", set_in_kwargs=True)},
|
|
40
|
+
)
|
|
41
|
+
direction: LinkDirection = LinkDirection.INWARD
|
|
42
|
+
rel_label: str = "RESOURCE"
|
|
43
|
+
properties: CloudWatchLogGroupToAwsAccountRelProperties = (
|
|
44
|
+
CloudWatchLogGroupToAwsAccountRelProperties()
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True)
|
|
49
|
+
class CloudWatchLogGroupSchema(CartographyNodeSchema):
|
|
50
|
+
label: str = "CloudWatchLogGroup"
|
|
51
|
+
properties: CloudWatchLogGroupNodeProperties = CloudWatchLogGroupNodeProperties()
|
|
52
|
+
sub_resource_relationship: CloudWatchToAWSAccountRel = CloudWatchToAWSAccountRel()
|
|
File without changes
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from cartography.models.core.common import PropertyRef
|
|
4
|
+
from cartography.models.core.nodes import CartographyNodeProperties
|
|
5
|
+
from cartography.models.core.nodes import CartographyNodeSchema
|
|
6
|
+
from cartography.models.core.relationships import CartographyRelProperties
|
|
7
|
+
from cartography.models.core.relationships import CartographyRelSchema
|
|
8
|
+
from cartography.models.core.relationships import LinkDirection
|
|
9
|
+
from cartography.models.core.relationships import make_target_node_matcher
|
|
10
|
+
from cartography.models.core.relationships import OtherRelationships
|
|
11
|
+
from cartography.models.core.relationships import TargetNodeMatcher
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class SecretsManagerSecretVersionNodeProperties(CartographyNodeProperties):
|
|
16
|
+
"""
|
|
17
|
+
Properties for AWS Secrets Manager Secret Version
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
# Align property names with the actual keys in the data
|
|
21
|
+
id: PropertyRef = PropertyRef("ARN")
|
|
22
|
+
arn: PropertyRef = PropertyRef("ARN", extra_index=True)
|
|
23
|
+
secret_id: PropertyRef = PropertyRef("SecretId")
|
|
24
|
+
version_id: PropertyRef = PropertyRef("VersionId")
|
|
25
|
+
version_stages: PropertyRef = PropertyRef("VersionStages")
|
|
26
|
+
created_date: PropertyRef = PropertyRef("CreatedDate")
|
|
27
|
+
region: PropertyRef = PropertyRef("Region", set_in_kwargs=True)
|
|
28
|
+
lastupdated: PropertyRef = PropertyRef("lastupdated", set_in_kwargs=True)
|
|
29
|
+
# Make KMS and tags properties without required=False parameter
|
|
30
|
+
kms_key_id: PropertyRef = PropertyRef("KmsKeyId")
|
|
31
|
+
tags: PropertyRef = PropertyRef("Tags")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(frozen=True)
|
|
35
|
+
class SecretsManagerSecretVersionRelProperties(CartographyRelProperties):
|
|
36
|
+
"""
|
|
37
|
+
Properties for relationships between Secret Version and other nodes
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
lastupdated: PropertyRef = PropertyRef("lastupdated", set_in_kwargs=True)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass(frozen=True)
|
|
44
|
+
class SecretsManagerSecretVersionToAWSAccountRel(CartographyRelSchema):
|
|
45
|
+
"""
|
|
46
|
+
Relationship between Secret Version and AWS Account
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
target_node_label: str = "AWSAccount"
|
|
50
|
+
target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
|
|
51
|
+
{"id": PropertyRef("AWS_ID", set_in_kwargs=True)},
|
|
52
|
+
)
|
|
53
|
+
direction: LinkDirection = LinkDirection.INWARD
|
|
54
|
+
rel_label: str = "RESOURCE"
|
|
55
|
+
properties: SecretsManagerSecretVersionRelProperties = (
|
|
56
|
+
SecretsManagerSecretVersionRelProperties()
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass(frozen=True)
|
|
61
|
+
class SecretsManagerSecretVersionToSecretRel(CartographyRelSchema):
|
|
62
|
+
"""
|
|
63
|
+
Relationship between Secret Version and its parent Secret
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
target_node_label: str = "SecretsManagerSecret"
|
|
67
|
+
# Use only one matcher for the id field
|
|
68
|
+
target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
|
|
69
|
+
{"id": PropertyRef("SecretId")},
|
|
70
|
+
)
|
|
71
|
+
direction: LinkDirection = LinkDirection.OUTWARD
|
|
72
|
+
rel_label: str = "VERSION_OF"
|
|
73
|
+
properties: SecretsManagerSecretVersionRelProperties = (
|
|
74
|
+
SecretsManagerSecretVersionRelProperties()
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass(frozen=True)
|
|
79
|
+
class SecretsManagerSecretVersionToKMSKeyRel(CartographyRelSchema):
|
|
80
|
+
"""
|
|
81
|
+
Relationship between Secret Version and its KMS key
|
|
82
|
+
Only created when KmsKeyId is present
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
target_node_label: str = "AWSKMSKey"
|
|
86
|
+
target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
|
|
87
|
+
{"id": PropertyRef("KmsKeyId")},
|
|
88
|
+
)
|
|
89
|
+
direction: LinkDirection = LinkDirection.OUTWARD
|
|
90
|
+
rel_label: str = "ENCRYPTED_BY"
|
|
91
|
+
properties: SecretsManagerSecretVersionRelProperties = (
|
|
92
|
+
SecretsManagerSecretVersionRelProperties()
|
|
93
|
+
)
|
|
94
|
+
# Only create this relationship if KmsKeyId exists
|
|
95
|
+
conditional_match_property: str = "KmsKeyId"
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@dataclass(frozen=True)
|
|
99
|
+
class SecretsManagerSecretVersionSchema(CartographyNodeSchema):
|
|
100
|
+
"""
|
|
101
|
+
Schema for AWS Secrets Manager Secret Version
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
label: str = "SecretsManagerSecretVersion"
|
|
105
|
+
properties: SecretsManagerSecretVersionNodeProperties = (
|
|
106
|
+
SecretsManagerSecretVersionNodeProperties()
|
|
107
|
+
)
|
|
108
|
+
sub_resource_relationship: SecretsManagerSecretVersionToAWSAccountRel = (
|
|
109
|
+
SecretsManagerSecretVersionToAWSAccountRel()
|
|
110
|
+
)
|
|
111
|
+
other_relationships: OtherRelationships = OtherRelationships(
|
|
112
|
+
[
|
|
113
|
+
SecretsManagerSecretVersionToSecretRel(),
|
|
114
|
+
SecretsManagerSecretVersionToKMSKeyRel(),
|
|
115
|
+
],
|
|
116
|
+
)
|