Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NAS-130390 / 25.04 / Move discovery auth from per-portal to system-wide #14160

Closed
wants to merge 5 commits into from
Closed
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
"""Add iSCSI discoverauth

Flatten the per-portal discovery auth to a system-wide discovery auth.

Revision ID: 208a066c65f7
Revises: 81b8bae8fb11
Create Date: 2024-08-02 15:57:30.527787+00:00

"""
import json

import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision = '208a066c65f7'
down_revision = '81b8bae8fb11'
branch_labels = None
depends_on = None


def upgrade():
op.create_table('services_iscsidiscoveryauth',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('iscsi_discoveryauth_authmethod', sa.String(length=120), nullable=False),
sa.Column('iscsi_discoveryauth_authgroup', sa.Integer(), nullable=False),
sa.PrimaryKeyConstraint('id', name=op.f('pk_services_iscsidiscoveryauth')),
sa.UniqueConstraint('iscsi_discoveryauth_authgroup', name=op.f('uq_services_iscsidiscoveryauth_iscsi_discoveryauth_authgroup')),
sqlite_autoincrement=True
)

conn = op.get_bind()
data = conn.execute("SELECT iscsi_target_portal_discoveryauthgroup, iscsi_target_portal_discoveryauthmethod, id FROM services_iscsitargetportal").fetchall()

# Migrate the data into the new table.
# - Mutual CHAP first.
mutual_chap_auth_groups = []
for authgroup, authmethod, _portal_id in data:
if authmethod == 'CHAP Mutual' and authgroup not in mutual_chap_auth_groups:
# Let's not carry around 'CHAP Mutual' anymore.
conn.execute('INSERT INTO services_iscsidiscoveryauth (iscsi_discoveryauth_authmethod, iscsi_discoveryauth_authgroup) VALUES ("CHAP_MUTUAL",?)', authgroup)
mutual_chap_auth_groups.append(authgroup)
# - Simple CHAP next.
simple_chap_auth_groups = []
for authgroup, authmethod, _portal_id in data:
if authmethod == 'CHAP' and authgroup not in mutual_chap_auth_groups + simple_chap_auth_groups:
conn.execute('INSERT INTO services_iscsidiscoveryauth (iscsi_discoveryauth_authmethod, iscsi_discoveryauth_authgroup) VALUES ("CHAP",?)', authgroup)
simple_chap_auth_groups.append(authgroup)

# Things to test
# 1. Do we have a mix of None and non-None?
# 2. If not, do we have more than one CHAP/Mutual CHAP
# 3. Do we have more than one Mutual CHAP peeruser/secret?
none_list = list(filter(lambda t: t[1] == 'None', data))
none_count = len(none_list)
non_none_count = len(mutual_chap_auth_groups) + len(simple_chap_auth_groups)

if none_count and non_none_count:
portal_id_strs = list(str(item[2]) for item in none_list)
none_ips = conn.execute("SELECT iscsi_target_portalip_ip FROM services_iscsitargetportalip WHERE iscsi_target_portalip_portal_id IN (?)", ','.join(portal_id_strs)).fetchall()
conn.execute("INSERT INTO system_keyvalue (\"key\", value) VALUES (?, ?)",
("ISCSIDiscoveryAuthMixed", json.dumps({'ips': [ip[0] for ip in none_ips]})))
elif non_none_count > 1:
conn.execute("INSERT INTO system_keyvalue (\"key\", value) VALUES (?, ?)",
("ISCSIDiscoveryAuthMultipleCHAP", json.dumps({})))

if mutual_chap_auth_groups:
if len(mutual_chap_auth_groups) == 1:
data = conn.execute(f"SELECT DISTINCT iscsi_target_auth_peeruser FROM services_iscsitargetauthcredential WHERE iscsi_target_auth_tag = {mutual_chap_auth_groups[0]} AND iscsi_target_auth_peeruser != ''").fetchall()
else:
tags = ','.join(str(x) for x in mutual_chap_auth_groups)
data = conn.execute(f"SELECT DISTINCT iscsi_target_auth_peeruser FROM services_iscsitargetauthcredential WHERE iscsi_target_auth_tag in ({tags}) AND iscsi_target_auth_peeruser != ''").fetchall()
if len(list(data)) > 1:
active_peeruser = data[0][0]
conn.execute("INSERT INTO system_keyvalue (\"key\", value) VALUES (?, ?)",
("ISCSIDiscoveryAuthMultipleMutualCHAP", json.dumps({'peeruser': active_peeruser})))

# Remove the obsolete columns
with op.batch_alter_table('services_iscsitargetportal', schema=None) as batch_op:
batch_op.drop_column('iscsi_target_portal_discoveryauthgroup')
batch_op.drop_column('iscsi_target_portal_discoveryauthmethod')


def downgrade():
pass
24 changes: 24 additions & 0 deletions src/middlewared/middlewared/alert/source/discovery_auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from middlewared.alert.base import AlertCategory, AlertClass, AlertLevel, SimpleOneShotAlertClass

UPGRADE_ALERTS = ['ISCSIDiscoveryAuthMixed', 'ISCSIDiscoveryAuthMultipleCHAP', 'ISCSIDiscoveryAuthMultipleMutualCHAP']


class ISCSIDiscoveryAuthMixedAlertClass(AlertClass, SimpleOneShotAlertClass):
category = AlertCategory.SHARING
level = AlertLevel.WARNING
title = "iSCSI Discovery Authorization Global"
text = "Prior to upgrade had specified iSCSI discovery auth on only some portals, now applies globally. May need to update client configuration when using %(ips)s"


class ISCSIDiscoveryAuthMultipleCHAPAlertClass(AlertClass, SimpleOneShotAlertClass):
category = AlertCategory.SHARING
level = AlertLevel.WARNING
title = "iSCSI Discovery Authorization merged"
text = "Prior to upgrade different portals had different iSCSI discovery auth, now applies globally."


class ISCSIDiscoveryAuthMultipleMutualCHAPAlertClass(AlertClass, SimpleOneShotAlertClass):
category = AlertCategory.SHARING
level = AlertLevel.WARNING
title = "iSCSI Discovery Authorization Multiple Mutual CHAP"
text = "Multiple mutual CHAP peers defined for discovery auth, but only first one (\"%(peeruser)s\") applies. May need to update client configuration."
31 changes: 23 additions & 8 deletions src/middlewared/middlewared/plugins/iscsi_/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,14 @@ async def do_create(self, data):

verrors.check()

orig_peerusers = await self.middleware.call('iscsi.discoveryauth.mutual_chap_peerusers')

data['id'] = await self.middleware.call(
'datastore.insert', self._config.datastore, data,
{'prefix': self._config.datastore_prefix}
)

await self.middleware.call('iscsi.discoveryauth.recalc_mutual_chap_alert', orig_peerusers)
await self._service_change('iscsitarget', 'reload')

return await self.get_instance(data['id'])
Expand Down Expand Up @@ -95,17 +98,20 @@ async def do_update(self, audit_callback, id_, data):
verrors = ValidationErrors()
await self.validate(new, 'iscsi_auth_update', verrors)
if new['tag'] != old['tag'] and not await self.query([['tag', '=', old['tag']], ['id', '!=', id_]]):
usages = await self.is_in_use_by_portals_targets(id_)
usages = await self.is_in_use(id_)
if usages['in_use']:
verrors.add('iscsi_auth_update.tag', usages['usages'])

verrors.check()

orig_peerusers = await self.middleware.call('iscsi.discoveryauth.mutual_chap_peerusers')

await self.middleware.call(
'datastore.update', self._config.datastore, id_, new,
{'prefix': self._config.datastore_prefix}
)

await self.middleware.call('iscsi.discoveryauth.recalc_mutual_chap_alert', orig_peerusers)
await self._service_change('iscsitarget', 'reload')

return await self.get_instance(id_)
Expand All @@ -121,25 +127,34 @@ async def do_delete(self, audit_callback, id_):
audit_callback(_auth_summary(config))

if not await self.query([['tag', '=', config['tag']], ['id', '!=', id_]]):
usages = await self.is_in_use_by_portals_targets(id_)
# We are attempting to delete the last auth in a particular group (aka tag)
usages = await self.is_in_use(id_)
if usages['in_use']:
raise CallError(usages['usages'])

return await self.middleware.call(
orig_peerusers = await self.middleware.call('iscsi.discoveryauth.mutual_chap_peerusers')

result = await self.middleware.call(
'datastore.delete', self._config.datastore, id_
)
if orig_peerusers:
await self.middleware.call('iscsi.discoveryauth.recalc_mutual_chap_alert', orig_peerusers)

return result

@private
async def is_in_use_by_portals_targets(self, id_):
async def is_in_use(self, id_):
config = await self.get_instance(id_)
usages = []
portals = await self.middleware.call(
'iscsi.portal.query', [['discovery_authgroup', '=', config['tag']]], {'select': ['id']}
# Check discovery auth
discovery_auths = await self.middleware.call(
'iscsi.discoveryauth.query', [['authgroup', '=', config['tag']]], {'select': ['id']}
)
if portals:
if discovery_auths:
usages.append(
f'Authorized access of {id_} is being used by portal(s): {", ".join(str(p["id"]) for p in portals)}'
f'Authorized access of {id_} is being used by discovery auth(s): {", ".join(str(a["id"]) for a in discovery_auths)}'
)
# Check targets
groups = await self.middleware.call(
'datastore.query', 'services.iscsitargetgroups', [['iscsi_target_authgroup', '=', config['tag']]]
)
Expand Down
Loading
Loading