Merge "backup/swift: Add support sending service user token"

This commit is contained in:
Zuul
2022-08-02 13:06:51 +00:00
committed by Gerrit Code Review
6 changed files with 107 additions and 27 deletions

View File

@@ -57,6 +57,8 @@ from cinder.backup import chunkeddriver
from cinder import exception
from cinder.i18n import _
from cinder import interface
from cinder import service_auth
from cinder.utils import retry
LOG = logging.getLogger(__name__)
@@ -141,6 +143,11 @@ swiftbackup_service_opts = [
default=False,
help='Bypass verification of server certificate when '
'making SSL connection to Swift.'),
cfg.BoolOpt('backup_swift_service_auth',
default=False,
help='Send a X-Service-Token header with service auth '
'credentials. If enabled you also must set the '
'service_user group and enable send_service_user_token.'),
]
CONF = cfg.CONF
@@ -174,6 +181,21 @@ class SwiftBackupDriver(chunkeddriver.ChunkedBackupDriver):
def get_driver_options():
return swiftbackup_service_opts
@retry(Exception, retries=CONF.backup_swift_retry_attempts,
backoff_rate=CONF.backup_swift_retry_backoff)
def _headers(self, headers=None):
"""Add service token to headers if its enabled"""
if not CONF.backup_swift_service_auth:
return headers
result = headers or {}
sa_plugin = service_auth.get_service_auth_plugin()
if sa_plugin is not None:
result['X-Service-Token'] = sa_plugin.get_token()
return result
def initialize(self):
self.swift_attempts = CONF.backup_swift_retry_attempts
self.swift_backoff = CONF.backup_swift_retry_backoff
@@ -274,11 +296,12 @@ class SwiftBackupDriver(chunkeddriver.ChunkedBackupDriver):
cacert=CONF.backup_swift_ca_cert_file)
class SwiftObjectWriter(object):
def __init__(self, container, object_name, conn):
def __init__(self, container, object_name, conn, headers_func=None):
self.container = container
self.object_name = object_name
self.conn = conn
self.data = bytearray()
self.headers_func = headers_func
def __enter__(self):
return self
@@ -292,9 +315,11 @@ class SwiftBackupDriver(chunkeddriver.ChunkedBackupDriver):
def close(self):
reader = io.BytesIO(self.data)
try:
headers = self.headers_func() if self.headers_func else None
etag = self.conn.put_object(self.container, self.object_name,
reader,
content_length=len(self.data))
content_length=len(self.data),
headers=headers)
except socket.error as err:
raise exception.SwiftConnectionFailed(reason=err)
md5 = secretutils.md5(self.data, usedforsecurity=False).hexdigest()
@@ -306,10 +331,11 @@ class SwiftBackupDriver(chunkeddriver.ChunkedBackupDriver):
return md5
class SwiftObjectReader(object):
def __init__(self, container, object_name, conn):
def __init__(self, container, object_name, conn, headers_func=None):
self.container = container
self.object_name = object_name
self.conn = conn
self.headers_func = headers_func
def __enter__(self):
return self
@@ -319,8 +345,10 @@ class SwiftBackupDriver(chunkeddriver.ChunkedBackupDriver):
def read(self):
try:
headers = self.headers_func() if self.headers_func else None
(_resp, body) = self.conn.get_object(self.container,
self.object_name)
self.object_name,
headers=headers)
except socket.error as err:
raise exception.SwiftConnectionFailed(reason=err)
return body
@@ -335,14 +363,15 @@ class SwiftBackupDriver(chunkeddriver.ChunkedBackupDriver):
existing container.
"""
try:
self.conn.head_container(container)
self.conn.head_container(container, headers=self._headers())
except swift_exc.ClientException as e:
if e.http_status == 404:
try:
storage_policy = CONF.backup_swift_create_storage_policy
headers = ({'X-Storage-Policy': storage_policy}
if storage_policy else None)
self.conn.put_container(container, headers=headers)
self.conn.put_container(container,
headers=self._headers(headers))
except socket.error as err:
raise exception.SwiftConnectionFailed(reason=err)
return
@@ -355,9 +384,11 @@ class SwiftBackupDriver(chunkeddriver.ChunkedBackupDriver):
def get_container_entries(self, container, prefix):
"""Get container entry names"""
try:
headers = self._headers()
swift_objects = self.conn.get_container(container,
prefix=prefix,
full_listing=True)[1]
full_listing=True,
headers=headers)[1]
except socket.error as err:
raise exception.SwiftConnectionFailed(reason=err)
swift_object_names = [swift_obj['name'] for swift_obj in swift_objects]
@@ -369,7 +400,8 @@ class SwiftBackupDriver(chunkeddriver.ChunkedBackupDriver):
Returns a writer object that stores a chunk of volume data in a
Swift object store.
"""
return self.SwiftObjectWriter(container, object_name, self.conn)
return self.SwiftObjectWriter(container, object_name, self.conn,
self._headers)
def get_object_reader(self, container, object_name, extra_metadata=None):
"""Return reader object.
@@ -377,12 +409,14 @@ class SwiftBackupDriver(chunkeddriver.ChunkedBackupDriver):
Returns a reader object that retrieves a chunk of backed-up volume data
from a Swift object store.
"""
return self.SwiftObjectReader(container, object_name, self.conn)
return self.SwiftObjectReader(container, object_name, self.conn,
self._headers)
def delete_object(self, container, object_name):
"""Deletes a backup object from a Swift object store."""
try:
self.conn.delete_object(container, object_name)
self.conn.delete_object(container, object_name,
headers=self._headers())
except socket.error as err:
raise exception.SwiftConnectionFailed(reason=err)

View File

@@ -52,12 +52,7 @@ def reset_globals():
_SERVICE_AUTH = None
def get_auth_plugin(context, auth=None):
if auth:
user_auth = auth
else:
user_auth = context.get_auth_plugin()
def get_service_auth_plugin():
if CONF.service_user.send_service_user_token:
global _SERVICE_AUTH
if not _SERVICE_AUTH:
@@ -67,7 +62,19 @@ def get_auth_plugin(context, auth=None):
# This can happen if no auth_type is specified, which probably
# means there's no auth information in the [service_user] group
raise exception.ServiceUserTokenNoAuth()
return _SERVICE_AUTH
return None
def get_auth_plugin(context, auth=None):
if auth:
user_auth = auth
else:
user_auth = context.get_auth_plugin()
service_auth = get_service_auth_plugin()
if service_auth is not None:
return service_token.ServiceTokenAuthWrapper(
user_auth=user_auth, service_auth=_SERVICE_AUTH)
user_auth=user_auth, service_auth=service_auth)
return user_auth

View File

@@ -37,6 +37,7 @@ from cinder import db
from cinder import exception
from cinder.i18n import _
from cinder import objects
from cinder import service_auth
from cinder.tests.unit.backup import fake_swift_client
from cinder.tests.unit.backup import fake_swift_client2
from cinder.tests.unit import fake_constants as fake
@@ -299,6 +300,35 @@ class BackupSwiftTestCase(test.TestCase):
starting_backoff=ANY,
cacert=ANY)
def _test_backup_swift_service_auth_headers_no_impact(self):
service = swift_dr.SwiftBackupDriver(self.ctxt)
self.assertIsNone(service._headers())
current = {'some': 'header'}
self.assertEqual(service._headers(current), current)
def test_backup_swift_service_auth_headers_disabled(self):
self._test_backup_swift_service_auth_headers_no_impact()
def test_backup_swift_service_auth_headers_partial_enabled(self):
self.override_config('send_service_user_token', True,
group='service_user')
self._test_backup_swift_service_auth_headers_no_impact()
@mock.patch.object(service_auth, 'get_service_auth_plugin')
def test_backup_swift_service_auth_headers_enabled(self, mock_plugin):
class FakeServiceAuthPlugin:
def get_token(self):
return "fake"
self.override_config('send_service_user_token', True,
group='service_user')
self.override_config('backup_swift_service_auth', True)
mock_plugin.return_value = FakeServiceAuthPlugin()
service = swift_dr.SwiftBackupDriver(self.ctxt)
expected = {'X-Service-Token': 'fake'}
self.assertEqual(service._headers(), expected)
expected = {'X-Service-Token': 'fake', 'some': 'header'}
self.assertEqual(service._headers({'some': 'header'}), expected)
@mock.patch.object(fake_swift_client.FakeSwiftConnection, 'put_container')
def test_default_backup_swift_create_storage_policy(self, mock_put):
service = swift_dr.SwiftBackupDriver(self.ctxt)

View File

@@ -37,7 +37,7 @@ class FakeSwiftConnection(object):
def __init__(self, *args, **kwargs):
pass
def head_container(self, container):
def head_container(self, container, headers=None):
if container in ['missing_container',
'missing_container_socket_error_on_put']:
raise swift.ClientException('fake exception',
@@ -53,17 +53,17 @@ class FakeSwiftConnection(object):
if container == 'missing_container_socket_error_on_put':
raise socket.error(111, 'ECONNREFUSED')
def get_container(self, container, **kwargs):
def get_container(self, container, headers=None, **kwargs):
fake_header = None
fake_body = [{'name': 'backup_001'},
{'name': 'backup_002'},
{'name': 'backup_003'}]
return fake_header, fake_body
def head_object(self, container, name):
def head_object(self, container, name, headers=None):
return {'etag': 'fake-md5-sum'}
def get_object(self, container, name):
def get_object(self, container, name, headers=None):
if container == 'socket_error_on_get':
raise socket.error(111, 'ECONNREFUSED')
if 'metadata' in name:
@@ -102,7 +102,7 @@ class FakeSwiftConnection(object):
raise socket.error(111, 'ECONNREFUSED')
return 'fake-md5-sum'
def delete_object(self, container, name):
def delete_object(self, container, name, headers=None):
if container == 'socket_error_on_delete':
raise socket.error(111, 'ECONNREFUSED')
pass

View File

@@ -36,7 +36,7 @@ class FakeSwiftConnection2(object):
def __init__(self, *args, **kwargs):
self.tempdir = tempfile.mkdtemp()
def head_container(self, container):
def head_container(self, container, headers=None):
if container == 'missing_container':
raise swift.ClientException('fake exception',
http_status=http_client.NOT_FOUND)
@@ -49,7 +49,7 @@ class FakeSwiftConnection2(object):
def put_container(self, container, headers=None):
pass
def get_container(self, container, **kwargs):
def get_container(self, container, headers=None, **kwargs):
fake_header = None
container_dir = tempfile.gettempdir() + '/' + container
fake_body = []
@@ -62,10 +62,10 @@ class FakeSwiftConnection2(object):
return fake_header, fake_body
def head_object(self, container, name):
def head_object(self, container, name, headers=None):
return {'etag': 'fake-md5-sum'}
def get_object(self, container, name):
def get_object(self, container, name, headers=None):
if container == 'socket_error_on_get':
raise socket.error(111, 'ECONNREFUSED')
object_path = tempfile.gettempdir() + '/' + container + '/' + name
@@ -80,5 +80,5 @@ class FakeSwiftConnection2(object):
object_file.write(reader.read())
return md5(reader.read(), usedforsecurity=False).hexdigest()
def delete_object(self, container, name):
def delete_object(self, container, name, headers=None):
pass

View File

@@ -0,0 +1,9 @@
---
features:
- |
The Swift backup driver now supports sending a X-Service-Token header with
a service token when the new ``backup_swift_service_auth`` config option is
enabled. Please note that you still need to configure the ``[service_user]``
group and also set ``send_service_user_token`` to enable the behavior and not
only the Swift backup driver option. Note ``send_service_user_token`` enables
it globally and will also affect communication with Nova and Glance.