1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
|
Description: Swift patch for LP: 2119646
Author: Tim Burke
Origin: upstream, https://bugs.launchpad.net/keystone/+bug/2119646 comment 28
Last-Update: 2025-10-31
diff --git a/swift/common/middleware/s3api/s3token.py b/swift/common/middleware/s3api/s3token.py
index 0c3f15d89..b1a3ca476 100644
--- a/swift/common/middleware/s3api/s3token.py
+++ b/swift/common/middleware/s3api/s3token.py
@@ -180,31 +180,45 @@ class S3Token(object):
self._secret_cache_duration = int(conf.get('secret_cache_duration', 0))
if self._secret_cache_duration < 0:
raise ValueError('secret_cache_duration must be non-negative')
- if self._secret_cache_duration:
- try:
- auth_plugin = keystone_loading.get_plugin_loader(
- conf.get('auth_type', 'password'))
- available_auth_options = auth_plugin.get_options()
- auth_options = {}
- for option in available_auth_options:
- name = option.name.replace('-', '_')
- value = conf.get(name)
- if value:
- auth_options[name] = value
+ # Service authentication for s3tokens API calls
+ self.keystoneclient = None
+ try:
+ auth_plugin = keystone_loading.get_plugin_loader(
+ conf.get('auth_type', 'password'))
+ available_auth_options = auth_plugin.get_options()
+ auth_options = {}
+ for option in available_auth_options:
+ name = option.name.replace('-', '_')
+ value = conf.get(name)
+ if value:
+ auth_options[name] = value
+
+ if not auth_options:
+ self._logger.warning(
+ "No service auth configuration. "
+ "s3tokens API calls will be unauthenticated. "
+ "New versions of keystone require service auth.")
+ else:
auth = auth_plugin.load_from_options(**auth_options)
session = keystone_session.Session(auth=auth)
self.keystoneclient = keystone_client.Client(
session=session,
region_name=conf.get('region_name'))
- self._logger.info("Caching s3tokens for %s seconds",
- self._secret_cache_duration)
- except Exception:
- self._logger.warning("Unable to load keystone auth_plugin. "
- "Secret caching will be unavailable.",
- exc_info=True)
- self.keystoneclient = None
- self._secret_cache_duration = 0
+ self._logger.info(
+ "Service authentication configured for s3tokens API")
+ except Exception:
+ self._logger.warning(
+ "Unable to load service auth configuration. "
+ "s3tokens API calls will be unauthenticated "
+ "and secret caching will be unavailable.",
+ exc_info=True)
+
+ if self._secret_cache_duration and self.keystoneclient:
+ self._logger.info("Caching s3tokens for %s seconds",
+ self._secret_cache_duration)
+ else:
+ self._secret_cache_duration = 0
def _deny_request(self, code):
error_cls, message = {
@@ -222,6 +236,16 @@ class S3Token(object):
def _json_request(self, creds_json):
headers = {'Content-Type': 'application/json'}
+
+ # Add service authentication headers if configured
+ if self.keystoneclient:
+ try:
+ headers.update(
+ self.keystoneclient.session.get_auth_headers())
+ except Exception:
+ self._logger.warning("Failed to get service token",
+ exc_info=True)
+
try:
response = requests.post(self._request_uri,
headers=headers, data=creds_json,
diff --git a/test/unit/common/middleware/s3api/test_s3token.py b/test/unit/common/middleware/s3api/test_s3token.py
index ee02c243d..2ff197ff0 100644
--- a/test/unit/common/middleware/s3api/test_s3token.py
+++ b/test/unit/common/middleware/s3api/test_s3token.py
@@ -589,6 +589,9 @@ class S3TokenMiddlewareTestGood(S3TokenMiddlewareTestBase):
cache.get.return_value = None
keystone_client = MOCK_KEYSTONE.return_value
+ keystone_client.session.get_auth_headers.return_value = {
+ 'X-Auth-Token': 'bearer token',
+ }
keystone_client.ec2.get.return_value = mock.Mock(secret='secret')
MOCK_REQUEST.return_value = FakeResponse({
@@ -615,6 +618,18 @@ class S3TokenMiddlewareTestGood(S3TokenMiddlewareTestBase):
}
self.assertTrue(MOCK_REQUEST.called)
+ self.assertEqual(MOCK_REQUEST.mock_calls, [
+ mock.call('http://example.com/s3tokens', headers={
+ 'Content-Type': 'application/json',
+ 'X-Auth-Token': 'bearer token',
+ }, data=json.dumps({
+ "credentials": {
+ "access": "access",
+ "token": "dG9rZW4=",
+ "signature": "signature",
+ }
+ }), verify=None, timeout=10.0)
+ ])
tenant = GOOD_RESPONSE_V2['access']['token']['tenant']
expected_cache = (expected_headers, tenant, 'secret')
cache.set.assert_called_once_with('s3secret/access', expected_cache,
|