Skip to content

Commit ddfe416

Browse files
authored
Merge branch 'master' into test-integration-gcp-crm-dns
2 parents 9585422 + 6b4ab69 commit ddfe416

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+2144
-884
lines changed

.github/workflows/ossf-scorecard.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,6 @@ jobs:
5757

5858
# Upload the results to GitHub's code scanning dashboard.
5959
- name: "Upload to code-scanning"
60-
uses: github/codeql-action/upload-sarif@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4
60+
uses: github/codeql-action/upload-sarif@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5
6161
with:
6262
sarif_file: results.sarif

.github/workflows/publish-to-ghcr-and-pypi.yml

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,48 +19,34 @@ jobs:
1919
runs-on: ubuntu-latest
2020
steps:
2121
# 1. Publish to PyPI
22-
# We still need to use pypa/build because uv does not yet support dynamic version
23-
# see: https://github.com/astral-sh/uv/issues/8714
2422
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
25-
- name: Set up Python 3.10
26-
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
23+
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
2724
with:
2825
python-version: "3.10"
29-
- name: Install pypa/build
30-
run: >-
31-
python -m
32-
pip install
33-
build[uv]
34-
--user
26+
- name: Install uv
27+
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
28+
with:
29+
enable-cache: true
30+
cache-dependency-glob: "uv.lock"
3531
- name: Build a binary wheel and a source tarball
36-
run: >-
37-
python -m
38-
build
39-
--installer=uv
40-
--sdist
41-
--wheel
42-
--outdir dist/
43-
.
32+
run: uv build
4433
- name: Publish distribution 📦 to PyPI
4534
if: startsWith(github.ref, 'refs/tags')
46-
uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4
47-
with:
48-
user: __token__
49-
password: ${{ secrets.PYPI_API_TOKEN }}
50-
skip-existing: true
35+
run: uv publish --username __token__ --password ${{ secrets.PYPI_API_TOKEN }}
36+
5137
# 2. Publish to GHCR
5238
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
5339
- name: Extract metadata (tags, labels) for Docker
5440
id: meta
55-
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
41+
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
5642
with:
5743
images: ghcr.io/${{ github.repository }}
5844

5945
- name: Set up Docker Buildx
6046
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
6147

6248
- name: Login to GitHub Container Registry
63-
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
49+
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
6450
with:
6551
registry: ghcr.io
6652
# This is the user that triggered the Workflow. In this case, it will

.github/workflows/test_suite.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ jobs:
9393
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
9494
- name: Extract metadata (tags, labels) for Docker
9595
id: meta
96-
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
96+
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
9797
with:
9898
images: ghcr.io/${{ github.repository }}
9999

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ You can learn more about the story behind Cartography in our [presentation at BS
2222

2323
## Supported platforms
2424
- [Airbyte](https://cartography-cncf.github.io/cartography/modules/airbyte/index.html) - Organization, Workspace, User, Source, Destination, Connection, Tag, Stream
25-
- [Amazon Web Services](https://cartography-cncf.github.io/cartography/modules/aws/index.html) - ACM, API Gateway, CloudWatch, CodeBuild, Config, EC2, ECS, ECR, EFS, Elasticsearch, Elastic Kubernetes Service (EKS), DynamoDB, Glue, GuardDuty, IAM, Inspector, KMS, Lambda, RDS, Redshift, Route53, S3, Secrets Manager(Secret Versions), Security Hub, SNS, SQS, SSM, STS, Tags
25+
- [Amazon Web Services](https://cartography-cncf.github.io/cartography/modules/aws/index.html) - ACM, API Gateway, CloudWatch, CodeBuild, Config, Cognito, EC2, ECS, ECR, EFS, Elasticsearch, Elastic Kubernetes Service (EKS), DynamoDB, Glue, GuardDuty, IAM, Inspector, KMS, Lambda, RDS, Redshift, Route53, S3, Secrets Manager(Secret Versions), Security Hub, SNS, SQS, SSM, STS, Tags
2626
- [Anthropic](https://cartography-cncf.github.io/cartography/modules/anthropic/index.html) - Organization, ApiKey, User, Workspace
2727
- [BigFix](https://cartography-cncf.github.io/cartography/modules/bigfix/index.html) - Computers
2828
- [Cloudflare](https://cartography-cncf.github.io/cartography/modules/cloudflare/index.html) - Account, Role, Member, Zone, DNSRecord

cartography/cli.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,15 @@ def _build_parser(self):
700700
"Required if you are using the Trivy module. Ignored otherwise."
701701
),
702702
)
703+
parser.add_argument(
704+
"--trivy-results-dir",
705+
type=str,
706+
default=None,
707+
help=(
708+
"Path to a directory containing Trivy JSON results on disk. "
709+
"Required if you are using the Trivy module with local results."
710+
),
711+
)
703712
parser.add_argument(
704713
"--scaleway-org",
705714
type=str,
@@ -1089,6 +1098,9 @@ def main(self, argv: str) -> int:
10891098
if config.trivy_s3_prefix:
10901099
logger.debug(f"Trivy S3 prefix: {config.trivy_s3_prefix}")
10911100

1101+
if config.trivy_results_dir:
1102+
logger.debug(f"Trivy results dir: {config.trivy_results_dir}")
1103+
10921104
# Scaleway config
10931105
if config.scaleway_secret_key_env_var:
10941106
logger.debug(
@@ -1118,6 +1130,8 @@ def main(self, argv: str) -> int:
11181130
config.sentinelone_api_token = os.environ.get(
11191131
config.sentinelone_api_token_env_var
11201132
)
1133+
else:
1134+
config.sentinelone_api_token = None
11211135

11221136
# Run cartography
11231137
try:

cartography/config.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,8 @@ class Config:
152152
:param trivy_s3_bucket: The S3 bucket name containing Trivy scan results. Optional.
153153
:type trivy_s3_prefix: str
154154
:param trivy_s3_prefix: The S3 prefix path containing Trivy scan results. Optional.
155+
:type trivy_results_dir: str
156+
:param trivy_results_dir: Local directory containing Trivy scan results. Optional.
155157
:type scaleway_access_key: str
156158
:param scaleway_access_key: Scaleway access key. Optional.
157159
:type scaleway_secret_key: str
@@ -243,6 +245,7 @@ def __init__(
243245
airbyte_api_url=None,
244246
trivy_s3_bucket=None,
245247
trivy_s3_prefix=None,
248+
trivy_results_dir=None,
246249
scaleway_access_key=None,
247250
scaleway_secret_key=None,
248251
scaleway_org=None,
@@ -327,6 +330,7 @@ def __init__(
327330
self.airbyte_api_url = airbyte_api_url
328331
self.trivy_s3_bucket = trivy_s3_bucket
329332
self.trivy_s3_prefix = trivy_s3_prefix
333+
self.trivy_results_dir = trivy_results_dir
330334
self.scaleway_access_key = scaleway_access_key
331335
self.scaleway_secret_key = scaleway_secret_key
332336
self.scaleway_org = scaleway_org

cartography/intel/aws/cognito.py

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
import logging
2+
from typing import Any
3+
from typing import Dict
4+
from typing import List
5+
6+
import boto3
7+
import neo4j
8+
9+
from cartography.client.core.tx import load
10+
from cartography.graph.job import GraphJob
11+
from cartography.intel.aws.ec2.util import get_botocore_config
12+
from cartography.models.aws.cognito.identity_pool import CognitoIdentityPoolSchema
13+
from cartography.models.aws.cognito.user_pool import CognitoUserPoolSchema
14+
from cartography.util import aws_handle_regions
15+
from cartography.util import timeit
16+
17+
logger = logging.getLogger(__name__)
18+
19+
20+
@timeit
21+
@aws_handle_regions
22+
def get_identity_pools(
23+
boto3_session: boto3.Session, region: str
24+
) -> List[Dict[str, Any]]:
25+
client = boto3_session.client(
26+
"cognito-identity", region_name=region, config=get_botocore_config()
27+
)
28+
paginator = client.get_paginator("list_identity_pools")
29+
30+
all_identity_pools = []
31+
32+
for page in paginator.paginate(MaxResults=50):
33+
identity_pools = page.get("IdentityPools", [])
34+
all_identity_pools.extend(identity_pools)
35+
return all_identity_pools
36+
37+
38+
@timeit
39+
@aws_handle_regions
40+
def get_identity_pool_roles(
41+
boto3_session: boto3.Session, identity_pools: List[Dict[str, Any]], region: str
42+
) -> List[Dict[str, Any]]:
43+
client = boto3_session.client(
44+
"cognito-identity", region_name=region, config=get_botocore_config()
45+
)
46+
all_identity_pool_details = []
47+
for identity_pool in identity_pools:
48+
response = client.get_identity_pool_roles(
49+
IdentityPoolId=identity_pool["IdentityPoolId"]
50+
)
51+
all_identity_pool_details.append(response)
52+
return all_identity_pool_details
53+
54+
55+
@timeit
56+
@aws_handle_regions
57+
def get_user_pools(boto3_session: boto3.Session, region: str) -> List[Dict[str, Any]]:
58+
client = boto3_session.client(
59+
"cognito-idp", region_name=region, config=get_botocore_config()
60+
)
61+
paginator = client.get_paginator("list_user_pools")
62+
all_user_pools = []
63+
64+
for page in paginator.paginate(MaxResults=50):
65+
user_pools = page.get("UserPools", [])
66+
all_user_pools.extend(user_pools)
67+
return all_user_pools
68+
69+
70+
def transform_identity_pools(
71+
identity_pools: List[Dict[str, Any]], region: str
72+
) -> List[Dict[str, Any]]:
73+
transformed_identity_pools = []
74+
for pool in identity_pools:
75+
transformed_pool = {
76+
"IdentityPoolId": pool["IdentityPoolId"],
77+
"Region": region,
78+
"Roles": list(pool.get("Roles", {}).values()),
79+
}
80+
transformed_identity_pools.append(transformed_pool)
81+
return transformed_identity_pools
82+
83+
84+
def transform_user_pools(
85+
user_pools: List[Dict[str, Any]], region: str
86+
) -> List[Dict[str, Any]]:
87+
transformed_user_pools = []
88+
for pool in user_pools:
89+
transformed_pool = {
90+
"Id": pool["Id"],
91+
"Region": region,
92+
"Name": pool["Name"],
93+
"Status": pool.get("Status"),
94+
}
95+
transformed_user_pools.append(transformed_pool)
96+
return transformed_user_pools
97+
98+
99+
@timeit
100+
def load_identity_pools(
101+
neo4j_session: neo4j.Session,
102+
data: List[Dict[str, Any]],
103+
region: str,
104+
current_aws_account_id: str,
105+
aws_update_tag: int,
106+
) -> None:
107+
logger.info(
108+
f"Loading Cognito Identity Pools {len(data)} for region '{region}' into graph.",
109+
)
110+
load(
111+
neo4j_session,
112+
CognitoIdentityPoolSchema(),
113+
data,
114+
lastupdated=aws_update_tag,
115+
Region=region,
116+
AWS_ID=current_aws_account_id,
117+
)
118+
119+
120+
@timeit
121+
def load_user_pools(
122+
neo4j_session: neo4j.Session,
123+
data: List[Dict[str, Any]],
124+
region: str,
125+
current_aws_account_id: str,
126+
aws_update_tag: int,
127+
) -> None:
128+
logger.info(
129+
f"Loading Cognito User Pools {len(data)} for region '{region}' into graph.",
130+
)
131+
load(
132+
neo4j_session,
133+
CognitoUserPoolSchema(),
134+
data,
135+
lastupdated=aws_update_tag,
136+
Region=region,
137+
AWS_ID=current_aws_account_id,
138+
)
139+
140+
141+
@timeit
142+
def cleanup(
143+
neo4j_session: neo4j.Session,
144+
common_job_parameters: Dict[str, Any],
145+
) -> None:
146+
logger.debug("Running Efs cleanup job.")
147+
GraphJob.from_node_schema(CognitoIdentityPoolSchema(), common_job_parameters).run(
148+
neo4j_session
149+
)
150+
GraphJob.from_node_schema(CognitoUserPoolSchema(), common_job_parameters).run(
151+
neo4j_session
152+
)
153+
154+
155+
@timeit
156+
def sync(
157+
neo4j_session: neo4j.Session,
158+
boto3_session: boto3.session.Session,
159+
regions: List[str],
160+
current_aws_account_id: str,
161+
update_tag: int,
162+
common_job_parameters: Dict[str, Any],
163+
) -> None:
164+
for region in regions:
165+
logger.info(
166+
f"Syncing Cognito Identity Pools for region '{region}' in account '{current_aws_account_id}'.",
167+
)
168+
169+
identity_pools = get_identity_pools(boto3_session, region)
170+
if not identity_pools:
171+
logger.info(
172+
f"No Cognito Identity Pools found in region '{region}'. Skipping sync."
173+
)
174+
else:
175+
identity_pool_roles = get_identity_pool_roles(
176+
boto3_session, identity_pools, region
177+
)
178+
transformed_identity_pools = transform_identity_pools(
179+
identity_pool_roles, region
180+
)
181+
182+
load_identity_pools(
183+
neo4j_session,
184+
transformed_identity_pools,
185+
region,
186+
current_aws_account_id,
187+
update_tag,
188+
)
189+
190+
user_pools = get_user_pools(boto3_session, region)
191+
transformed_user_pools = transform_user_pools(user_pools, region)
192+
193+
load_user_pools(
194+
neo4j_session,
195+
transformed_user_pools,
196+
region,
197+
current_aws_account_id,
198+
update_tag,
199+
)
200+
201+
cleanup(neo4j_session, common_job_parameters)

cartography/intel/aws/ecs.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,9 +171,15 @@ def _get_containers_from_tasks(tasks: list[dict[str, Any]]) -> list[dict[str, An
171171

172172
def transform_ecs_tasks(tasks: list[dict[str, Any]]) -> list[dict[str, Any]]:
173173
"""
174-
Extract network interface ID from task attachments.
174+
Extract network interface ID from task attachments and service name from group.
175175
"""
176176
for task in tasks:
177+
# Extract serviceName from group field
178+
group = task.get("group")
179+
if group and group.startswith("service:"):
180+
task["serviceName"] = group.split("service:", 1)[1]
181+
182+
# Extract network interface ID from task attachments
177183
for attachment in task.get("attachments", []):
178184
if attachment.get("type") == "ElasticNetworkInterface":
179185
details = attachment.get("details", [])

0 commit comments

Comments
 (0)