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.

Files changed (75) hide show
  1. cartography/_version.py +2 -2
  2. cartography/cli.py +97 -1
  3. cartography/config.py +28 -0
  4. cartography/graph/cleanupbuilder.py +151 -41
  5. cartography/intel/anthropic/__init__.py +62 -0
  6. cartography/intel/anthropic/apikeys.py +72 -0
  7. cartography/intel/anthropic/users.py +75 -0
  8. cartography/intel/anthropic/util.py +51 -0
  9. cartography/intel/anthropic/workspaces.py +95 -0
  10. cartography/intel/aws/cloudtrail.py +3 -38
  11. cartography/intel/aws/cloudwatch.py +93 -0
  12. cartography/intel/aws/ec2/load_balancer_v2s.py +4 -1
  13. cartography/intel/aws/resources.py +2 -0
  14. cartography/intel/aws/secretsmanager.py +150 -3
  15. cartography/intel/aws/ssm.py +71 -0
  16. cartography/intel/cloudflare/__init__.py +74 -0
  17. cartography/intel/cloudflare/accounts.py +57 -0
  18. cartography/intel/cloudflare/dnsrecords.py +64 -0
  19. cartography/intel/cloudflare/members.py +75 -0
  20. cartography/intel/cloudflare/roles.py +65 -0
  21. cartography/intel/cloudflare/zones.py +64 -0
  22. cartography/intel/entra/ou.py +21 -5
  23. cartography/intel/openai/__init__.py +86 -0
  24. cartography/intel/openai/adminapikeys.py +89 -0
  25. cartography/intel/openai/apikeys.py +96 -0
  26. cartography/intel/openai/projects.py +97 -0
  27. cartography/intel/openai/serviceaccounts.py +82 -0
  28. cartography/intel/openai/users.py +75 -0
  29. cartography/intel/openai/util.py +45 -0
  30. cartography/intel/tailscale/__init__.py +77 -0
  31. cartography/intel/tailscale/acls.py +146 -0
  32. cartography/intel/tailscale/devices.py +127 -0
  33. cartography/intel/tailscale/postureintegrations.py +81 -0
  34. cartography/intel/tailscale/tailnets.py +76 -0
  35. cartography/intel/tailscale/users.py +80 -0
  36. cartography/intel/tailscale/utils.py +132 -0
  37. cartography/models/anthropic/__init__.py +0 -0
  38. cartography/models/anthropic/apikey.py +90 -0
  39. cartography/models/anthropic/organization.py +19 -0
  40. cartography/models/anthropic/user.py +48 -0
  41. cartography/models/anthropic/workspace.py +90 -0
  42. cartography/models/aws/cloudtrail/trail.py +24 -0
  43. cartography/models/aws/cloudwatch/__init__.py +0 -0
  44. cartography/models/aws/cloudwatch/loggroup.py +52 -0
  45. cartography/models/aws/secretsmanager/__init__.py +0 -0
  46. cartography/models/aws/secretsmanager/secret_version.py +116 -0
  47. cartography/models/aws/ssm/parameters.py +84 -0
  48. cartography/models/cloudflare/__init__.py +0 -0
  49. cartography/models/cloudflare/account.py +25 -0
  50. cartography/models/cloudflare/dnsrecord.py +55 -0
  51. cartography/models/cloudflare/member.py +82 -0
  52. cartography/models/cloudflare/role.py +44 -0
  53. cartography/models/cloudflare/zone.py +59 -0
  54. cartography/models/core/nodes.py +15 -2
  55. cartography/models/openai/__init__.py +0 -0
  56. cartography/models/openai/adminapikey.py +90 -0
  57. cartography/models/openai/apikey.py +84 -0
  58. cartography/models/openai/organization.py +17 -0
  59. cartography/models/openai/project.py +89 -0
  60. cartography/models/openai/serviceaccount.py +50 -0
  61. cartography/models/openai/user.py +49 -0
  62. cartography/models/tailscale/__init__.py +0 -0
  63. cartography/models/tailscale/device.py +95 -0
  64. cartography/models/tailscale/group.py +86 -0
  65. cartography/models/tailscale/postureintegration.py +58 -0
  66. cartography/models/tailscale/tag.py +102 -0
  67. cartography/models/tailscale/tailnet.py +29 -0
  68. cartography/models/tailscale/user.py +52 -0
  69. cartography/sync.py +8 -0
  70. {cartography-0.103.0rc1.dist-info → cartography-0.104.0.dist-info}/METADATA +8 -4
  71. {cartography-0.103.0rc1.dist-info → cartography-0.104.0.dist-info}/RECORD +75 -19
  72. {cartography-0.103.0rc1.dist-info → cartography-0.104.0.dist-info}/WHEEL +1 -1
  73. {cartography-0.103.0rc1.dist-info → cartography-0.104.0.dist-info}/entry_points.txt +0 -0
  74. {cartography-0.103.0rc1.dist-info → cartography-0.104.0.dist-info}/licenses/LICENSE +0 -0
  75. {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
+ )