Files
Raúl MarínandClaude Opus 4.8 0a2022fdf7 Restrict server-managed S3 credentials in user queries
Add a session setting `s3_allow_server_credentials_in_user_queries` (default off in
clickhouse-server, no effect in clickhouse-local). When off, S3 access driven by user
SQL cannot resolve the server's own ambient credentials (environment, IMDS, IRSA,
`role_arn` STS, AWS config files, or the GCP OAuth metadata service) at the single
credential-resolution chokepoint; a request with no explicit keys and no NOSIGN is
refused with ACCESS_DENIED. Covers the `s3`/`s3Cluster` functions, the `S3`/`S3Queue`
engines, named collections, dynamic S3 disks, BACKUP/RESTORE TO S3, DataLake reads,
and Glue/BigLake catalogs. A constraint can pin it for untrusted users. Operator-
provisioned static credentials in config are intentionally out of scope.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-09 14:40:34 +00:00

75 lines
2.1 KiB
Python

import os
import pytest
import random
from helpers.cluster import ClickHouseCluster
from helpers.mock_servers import start_mock_servers
METADATA_SERVER_HOSTNAME = "resolver"
METADATA_SERVER_PORT = 8080
cluster = ClickHouseCluster(__file__)
node = cluster.add_instance(
"node",
with_minio=True,
main_configs=["configs/use_environment_credentials.xml"],
user_configs=["configs/allow_server_credentials.xml"],
env_variables={
"AWS_EC2_METADATA_SERVICE_ENDPOINT": f"{METADATA_SERVER_HOSTNAME}:{METADATA_SERVER_PORT}",
},
)
def start_metadata_server():
script_dir = os.path.join(os.path.dirname(__file__), "metadata_servers")
start_mock_servers(
cluster,
script_dir,
[
(
"server_with_session_tokens.py",
METADATA_SERVER_HOSTNAME,
METADATA_SERVER_PORT,
)
],
)
@pytest.fixture(scope="module", autouse=True)
def start_cluster():
try:
cluster.start()
start_metadata_server()
yield
finally:
cluster.shutdown()
def test_credentials_from_metadata():
file_index = random.randint(0, 1000000000)
node.query(
f"INSERT INTO FUNCTION s3('http://{cluster.minio_host}:{cluster.minio_port}/{cluster.minio_bucket}/test{file_index}.jsonl') SELECT * FROM numbers(100)"
)
assert (
"100"
== node.query(
f"SELECT count() FROM s3('http://{cluster.minio_host}:{cluster.minio_port}/{cluster.minio_bucket}/test{file_index}.jsonl')"
).strip()
)
expected_logs = [
"Calling EC2MetadataService to get token",
"Calling EC2MetadataService resource, /latest/meta-data/iam/security-credentials with token returned profile string myrole",
"Calling EC2MetadataService resource resolver:8080/latest/meta-data/iam/security-credentials/myrole with token",
"Successfully pulled credentials from EC2MetadataService with access key",
]
node.query("SYSTEM FLUSH LOGS")
for expected_msg in expected_logs:
assert node.contains_in_log(
"AWSEC2InstanceProfileConfigLoader: " + expected_msg
)