Add shared image support
- If image availability is shared and conductor_project_id is in the image shared member list, allows access Closes-Bug: #2099276 Change-Id: I6b8a10fe82b41aa37b4f14bca9d3c0c498882bd1
This commit is contained in:
parent
854f059b82
commit
a39f11cece
@ -33,6 +33,13 @@ the Image service for each one as it is generated.
|
||||
Images from Glance used by Ironic must be flagged as ``public``, which
|
||||
requires administrative privileges with the Glance image service to set.
|
||||
|
||||
.. note::
|
||||
Starting with the 2025.2 release, the Ironic conductor can access images that
|
||||
are shared with its project, in addition to those it owns.
|
||||
|
||||
To use this feature, ensure the required images are shared with the project
|
||||
associated with the conductor's credentials.
|
||||
|
||||
- For *whole disk images* just upload the image:
|
||||
|
||||
.. code-block:: console
|
||||
|
@ -22,6 +22,7 @@ from oslo_utils import timeutils
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from ironic.common import exception
|
||||
from ironic.common import image_service as service
|
||||
from ironic.common import keystone
|
||||
from ironic.conf import CONF
|
||||
|
||||
@ -140,10 +141,13 @@ def is_image_available(context, image):
|
||||
image_visibility = getattr(image, 'visibility', None)
|
||||
image_owner = getattr(image, 'owner', None)
|
||||
image_id = getattr(image, 'id', 'unknown')
|
||||
image_shared_member_list = get_image_member_list(image_id, context)
|
||||
is_admin = 'admin' in getattr(context, 'roles', [])
|
||||
project = getattr(context, 'project', 'unknown')
|
||||
|
||||
# If an auth token is present and the config allows access via auth token,
|
||||
# allow image access.
|
||||
# NOTE(satoshi): This config should be removed in the H (2026.2) cycle
|
||||
if CONF.allow_image_access_via_auth_token and auth_token:
|
||||
# We return true here since we want the *user* request context to
|
||||
# be able to be used.
|
||||
@ -159,7 +163,11 @@ def is_image_available(context, image):
|
||||
# allow access.
|
||||
if image_visibility == 'private' and image_owner == conductor_project_id:
|
||||
return True
|
||||
|
||||
# If the image is shared and the conductor_project_id is in the shared
|
||||
# member list, allow access
|
||||
if image_visibility == 'shared'\
|
||||
and conductor_project_id in image_shared_member_list:
|
||||
return True
|
||||
LOG.info(
|
||||
'Access to %s owned by %s denied to requester %s',
|
||||
image_id, image_owner, project
|
||||
@ -198,3 +206,17 @@ def get_conductor_project_id():
|
||||
except Exception as e:
|
||||
LOG.debug("Error getting conductor project ID: %s", str(e))
|
||||
return None
|
||||
|
||||
|
||||
def get_image_member_list(image_id, context):
|
||||
try:
|
||||
glance_service = service.GlanceImageService(context=context)
|
||||
members = glance_service.client.image.members(image_id)
|
||||
return [
|
||||
member['member_id']
|
||||
for member in members
|
||||
]
|
||||
except Exception as e:
|
||||
LOG.error("Unable to retrieve image members for image %s: %s",
|
||||
image_id, e)
|
||||
return []
|
||||
|
@ -1041,8 +1041,24 @@ class TestIsImageAvailable(base.TestCase):
|
||||
self.assertTrue(service_utils.is_image_available(
|
||||
self.context, self.image))
|
||||
|
||||
@mock.patch.object(service_utils, 'get_image_member_list', autospec=True)
|
||||
def test_allow_shared_image_if_member(self, mock_get_members):
|
||||
self.image.visibility = 'shared'
|
||||
self.image.id = 'shared-image-id'
|
||||
self.image.owner = 'some-other-project'
|
||||
|
||||
self.context.project = 'test-project'
|
||||
|
||||
# Mock the conductor project ID and the shared member list
|
||||
conductor_id = service_utils.get_conductor_project_id()
|
||||
mock_get_members.return_value = [conductor_id]
|
||||
|
||||
self.assertTrue(service_utils.is_image_available(
|
||||
self.context, self.image))
|
||||
mock_get_members.assert_called_once_with('shared-image-id',
|
||||
self.context)
|
||||
|
||||
def test_deny_private_image_different_owner(self):
|
||||
self.config(allow_image_access_via_auth_token=False)
|
||||
self.config(ignore_project_check_for_admin_tasks=False)
|
||||
|
||||
self.image.visibility = 'private'
|
||||
|
@ -0,0 +1,8 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
The Ironic conductor can now access images that are
|
||||
shared with its project, in addition to those it owns.
|
||||
|
||||
To use the feature, ensure the images are shared with
|
||||
the project associated with the conductor's credentials.
|
Loading…
x
Reference in New Issue
Block a user