Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/s3_encryption/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from botocore.exceptions import ClientError
from botocore.response import StreamingBody

from ._utils import safe_get_dict
from ._utils import _USER_AGENT_SUFFIX, append_user_agent, safe_get_dict
from .exceptions import S3EncryptionClientError
from .instruction_file import parse_instruction_file
from .instruction_file_config import InstructionFileConfig
Expand Down Expand Up @@ -350,6 +350,8 @@ def __attrs_post_init__(self):
# Expose plugin context on wrapped client for instruction file fetching
self.wrapped_s3_client._s3ec_plugin_context = self._plugin._context

append_user_agent(self.wrapped_s3_client, _USER_AGENT_SUFFIX)

# Register event handlers using boto3's event system
event_system = self.wrapped_s3_client.meta.events
event_system.register("before-call.s3.PutObject", self._plugin.on_put_object_before_call)
Expand Down
12 changes: 12 additions & 0 deletions src/s3_encryption/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
# SPDX-License-Identifier: Apache-2.0
"""Internal utility helpers for the S3 Encryption Client."""

import importlib.metadata

_PACKAGE_VERSION = importlib.metadata.version("amazon-s3-encryption-client-python")
_USER_AGENT_SUFFIX = f"S3ECPy/{_PACKAGE_VERSION}"


def safe_get_dict(source: dict, key: str) -> dict:
"""Get a dict value from *source*, defaulting to {} if missing or None.
Expand All @@ -10,3 +15,10 @@ def safe_get_dict(source: dict, key: str) -> dict:
when the key exists but its value is explicitly None.
"""
return source.get(key, {}) or {}


def append_user_agent(client, suffix: str):
"""Append a suffix to the User-Agent header of a boto3 client."""
existing = client.meta.config.user_agent_extra or ""
sep = " " if existing else ""
client.meta.config.user_agent_extra = f"{existing}{sep}{suffix}"
5 changes: 5 additions & 0 deletions src/s3_encryption/materials/kms_keyring.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ class KmsKeyring(S3Keyring):
##% The KmsV1 mode MUST be only enabled when legacy wrapping algorithms are enabled.
enable_legacy_wrapping_algorithms: bool = field(default=False)

def __attrs_post_init__(self): # noqa: D105
from .._utils import _USER_AGENT_SUFFIX, append_user_agent

append_user_agent(self.kms_client, _USER_AGENT_SUFFIX)

def on_encrypt(self, enc_materials):
"""Process encryption materials using KMS.

Expand Down
43 changes: 43 additions & 0 deletions test/test_user_agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
"""Unit tests for user agent string injection."""

import boto3

from s3_encryption import S3EncryptionClient, S3EncryptionClientConfig
from s3_encryption._utils import _PACKAGE_VERSION, _USER_AGENT_SUFFIX
from s3_encryption.materials.kms_keyring import KmsKeyring


class TestUserAgent:
def test_user_agent_suffix_format(self):
assert f"S3ECPy/{_PACKAGE_VERSION}" == _USER_AGENT_SUFFIX

def test_s3_client_gets_user_agent(self):
s3 = boto3.client("s3", region_name="us-east-1")
kms = boto3.client("kms", region_name="us-east-1")
keyring = KmsKeyring(kms, "arn:aws:kms:us-east-1:000000000000:key/fake")
config = S3EncryptionClientConfig(keyring=keyring)

S3EncryptionClient(s3, config)

assert _USER_AGENT_SUFFIX in s3.meta.config.user_agent_extra

def test_kms_client_gets_user_agent(self):
kms = boto3.client("kms", region_name="us-east-1")
KmsKeyring(kms, "arn:aws:kms:us-east-1:000000000000:key/fake")

assert _USER_AGENT_SUFFIX in kms.meta.config.user_agent_extra

def test_existing_user_agent_extra_preserved(self):
s3 = boto3.client("s3", region_name="us-east-1")
s3.meta.config.user_agent_extra = "existing-agent/1.0"

kms = boto3.client("kms", region_name="us-east-1")
keyring = KmsKeyring(kms, "arn:aws:kms:us-east-1:000000000000:key/fake")
config = S3EncryptionClientConfig(keyring=keyring)

S3EncryptionClient(s3, config)

assert "existing-agent/1.0" in s3.meta.config.user_agent_extra
assert _USER_AGENT_SUFFIX in s3.meta.config.user_agent_extra
Loading