SERVER-111483: Use more secure and reliable form of installing the to… (#41957)

GitOrigin-RevId: 612823c3fad1e41c1bfedca645f36d0a07fad604
This commit is contained in:
Eric Lavigne 2025-09-30 10:43:29 -06:00 committed by MongoDB Bot
parent 27ea8392bc
commit cb9d24f20d
4 changed files with 211 additions and 5 deletions

7
.devcontainer/.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
# Toolchain configuration is checked in to lock the version
# Uncomment the following line if you want each developer to generate their own:
# toolchain_config.env
# Temporary files
*.tar.gz
*.tmp

View File

@ -1,3 +1,4 @@
# syntax=docker/dockerfile:1
ARG BASE_IMAGE=quay.io/mongodb/bazel-remote-execution:ubuntu24-2025_09_05-17_18_29
FROM $BASE_IMAGE
ARG BASE_IMAGE
@ -9,7 +10,7 @@ ARG USER_GID=$USER_UID
# Create the user
RUN groupadd $USERNAME && useradd -s /bin/bash --gid $USER_GID -m $USERNAME
RUN apt-get update && apt-get install -y sudo curl
RUN apt-get update && apt-get install -y sudo curl ca-certificates
# Give user sudo access
RUN echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/devcontaineruser && chmod 0440 /etc/sudoers.d/devcontaineruser
@ -21,11 +22,24 @@ RUN SNIPPET="export PROMPT_COMMAND='history -a' && export HISTFILE=/commandhisto
&& chown -R $USERNAME /commandhistory \
&& echo "$SNIPPET" >> "/home/$USERNAME/.bashrc"
# Toolchain installation
RUN curl -o /toolchain_installer.sh http://mongodbtoolchain.build.10gen.cc/installer.sh && chmod a+x /toolchain_installer.sh
# Toolchain installation with SHA256 verification
# Run "python3 toolchain.py generate" to update toolchain_config.env
COPY .devcontainer/toolchain_config.env /tmp/toolchain_config.env
RUN set -e; \
. /tmp/toolchain_config.env; \
echo "Installing toolchain from: $TOOLCHAIN_URL"; \
echo "Expected SHA256: $TOOLCHAIN_SHA256"; \
curl -fSL "$TOOLCHAIN_URL" -o /tmp/toolchain.tar.gz; \
echo "Verifying checksum..."; \
echo "$TOOLCHAIN_SHA256 /tmp/toolchain.tar.gz" | sha256sum -c -;
RUN echo "Extracting toolchain..."; \
mkdir -p /opt/mongodbtoolchain/revisions && tar -xzf /tmp/toolchain.tar.gz -C /opt/mongodbtoolchain/revisions; \
rm /tmp/toolchain.tar.gz; \
chown -R ${USERNAME} /opt/mongodbtoolchain;
USER $USERNAME
ENV USER ${USERNAME}
RUN /toolchain_installer.sh
ENV USER=${USERNAME}
RUN /opt/mongodbtoolchain/revisions/*/scripts/install.sh; echo "Toolchain installation complete"
# Bazel telemetry
RUN echo "common --bes_keywords=devcontainer:use=true" >> "$HOME/.bazelrc" && \

174
.devcontainer/toolchain.py Executable file
View File

@ -0,0 +1,174 @@
#!/usr/bin/env python3
"""
MongoDB Toolchain Management for DevContainers
This script handles fetching, downloading, and configuring MongoDB toolchains from S3.
Supports both dynamic (latest) and static (locked with SHA256) approaches.
"""
import argparse
import hashlib
import os
import sys
import xml.etree.ElementTree as ET
from datetime import datetime
from typing import Optional
from urllib import request
from urllib.error import HTTPError, URLError
def list_s3_objects(bucket: str, prefix: str) -> list[dict]:
"""Query S3 REST API for objects matching prefix."""
try:
url = f"https://s3.amazonaws.com/{bucket}?list-type=2&prefix={prefix}"
print(f"Querying S3: {url}", file=sys.stderr)
with request.urlopen(url) as response:
xml_data = response.read()
root = ET.fromstring(xml_data)
ns = {"s3": "http://s3.amazonaws.com/doc/2006-03-01/"}
objects = []
for content in root.findall("s3:Contents", ns):
key_elem = content.find("s3:Key", ns)
modified_elem = content.find("s3:LastModified", ns)
if key_elem is not None and modified_elem is not None:
objects.append(
{
"Key": key_elem.text,
"LastModified": datetime.fromisoformat(
modified_elem.text.replace("Z", "+00:00")
),
}
)
return objects
except (HTTPError, URLError, ET.ParseError) as e:
print(f"Error querying S3: {e}", file=sys.stderr)
sys.exit(1)
def find_latest(bucket: str, prefix: str) -> tuple[str, str, datetime]:
"""Find most recently modified artifact."""
objects = list_s3_objects(bucket, prefix)
if not objects:
print(f"No artifacts found with prefix: {prefix}", file=sys.stderr)
sys.exit(1)
latest = max(objects, key=lambda x: x["LastModified"])
url = f"https://s3.amazonaws.com/{bucket}/{latest['Key']}"
print(f"Latest: {latest['Key']}", file=sys.stderr)
print(f"Modified: {latest['LastModified']}", file=sys.stderr)
return url, latest["Key"], latest["LastModified"]
def download_file(url: str, output_path: str) -> None:
"""Download file from URL."""
print(f"Downloading {url}...", file=sys.stderr)
try:
request.urlretrieve(url, output_path)
print(f"Saved to {output_path}", file=sys.stderr)
except (HTTPError, URLError) as e:
print(f"Download failed: {e}", file=sys.stderr)
sys.exit(1)
def calculate_sha256(file_path: str) -> str:
"""Calculate SHA256 checksum of file."""
sha256_hash = hashlib.sha256()
with open(file_path, "rb") as f:
for chunk in iter(lambda: f.read(8192), b""):
sha256_hash.update(chunk)
return sha256_hash.hexdigest()
def generate_config(bucket: str, prefix: str, output_file: str) -> None:
"""Generate locked toolchain configuration with SHA256."""
url, key, last_modified = find_latest(bucket, prefix)
# Download to temp location to calculate checksum
import tempfile
with tempfile.NamedTemporaryFile(delete=False, suffix=".tar.gz") as tmp:
tmp_path = tmp.name
try:
download_file(url, tmp_path)
sha256 = calculate_sha256(tmp_path)
print(f"SHA256: {sha256}", file=sys.stderr)
finally:
os.unlink(tmp_path)
# Write config file
toolchain_id = os.path.basename(key).replace(".tar.gz", "")
with open(output_file, "w") as f:
f.write("# Generated by toolchain.py\n")
f.write("# DO NOT EDIT MANUALLY - run: python3 toolchain.py generate\n")
f.write("#\n")
f.write(f"# Generated: {datetime.now().isoformat()}\n")
f.write(f"# Last Modified: {last_modified.isoformat()}\n")
f.write(f"# Toolchain ID: {toolchain_id}\n")
f.write("\n")
f.write(f'TOOLCHAIN_URL="{url}"\n')
f.write(f'TOOLCHAIN_SHA256="{sha256}"\n')
f.write(f'TOOLCHAIN_KEY="{key}"\n')
f.write(f'TOOLCHAIN_LAST_MODIFIED="{last_modified.isoformat()}"\n')
print(f"\n✅ Configuration written to: {output_file}", file=sys.stderr)
print(f"\nContents:", file=sys.stderr)
with open(output_file) as f:
print(f.read(), file=sys.stderr)
def main():
parser = argparse.ArgumentParser(description="MongoDB Toolchain Management")
subparsers = parser.add_subparsers(dest="command", help="Command")
# find-latest: Just print the latest URL
find_parser = subparsers.add_parser("find-latest", help="Find latest toolchain URL")
find_parser.add_argument("--bucket", default="boxes.10gen.com")
find_parser.add_argument("--prefix", default="build/toolchain/mongodbtoolchain-ubuntu24")
# generate: Generate locked config
gen_parser = subparsers.add_parser("generate", help="Generate locked config with SHA256")
gen_parser.add_argument("--bucket", default="boxes.10gen.com")
gen_parser.add_argument("--prefix", default="build/toolchain/mongodbtoolchain-ubuntu24")
gen_parser.add_argument("--output", default="toolchain_config.env")
# download: Download to file
dl_parser = subparsers.add_parser("download", help="Download toolchain")
dl_parser.add_argument("--bucket", default="boxes.10gen.com")
dl_parser.add_argument("--prefix", default="build/toolchain/mongodbtoolchain-ubuntu24")
dl_parser.add_argument("--output", required=True, help="Output file path")
args = parser.parse_args()
if not args.command:
parser.print_help()
sys.exit(1)
if args.command == "find-latest":
url, _, _ = find_latest(args.bucket, args.prefix)
print(url)
elif args.command == "generate":
script_dir = os.path.dirname(os.path.abspath(__file__))
output_path = (
args.output if os.path.isabs(args.output) else os.path.join(script_dir, args.output)
)
generate_config(args.bucket, args.prefix, output_path)
elif args.command == "download":
url, _, _ = find_latest(args.bucket, args.prefix)
download_file(url, args.output)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,11 @@
# Generated by toolchain.py
# DO NOT EDIT MANUALLY - run: python3 toolchain.py generate
#
# Generated: 2025-09-29T13:23:13.132683
# Last Modified: 2025-07-21T20:49:55+00:00
# Toolchain ID: mongodbtoolchain-ubuntu2404-arm64-c36013b8bab41fcd3cbfd5e4b4590cd0c10ea6ce
TOOLCHAIN_URL="https://s3.amazonaws.com/boxes.10gen.com/build/toolchain/mongodbtoolchain-ubuntu2404-arm64-c36013b8bab41fcd3cbfd5e4b4590cd0c10ea6ce.tar.gz"
TOOLCHAIN_SHA256="cdd2a58ed4a67dfa1be237d6042b7523552b543bb104c20db8f29068f3899fd6"
TOOLCHAIN_KEY="build/toolchain/mongodbtoolchain-ubuntu2404-arm64-c36013b8bab41fcd3cbfd5e4b4590cd0c10ea6ce.tar.gz"
TOOLCHAIN_LAST_MODIFIED="2025-07-21T20:49:55+00:00"