Skip to content

Commit

Permalink
Add CHAP and authNetwork for SCALE (#63)
Browse files Browse the repository at this point in the history
Signed-off-by: Michael Mattsson <[email protected]>
  • Loading branch information
datamattsson authored Sep 12, 2024
1 parent 0ee5215 commit a253391
Show file tree
Hide file tree
Showing 9 changed files with 181 additions and 18 deletions.
60 changes: 53 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ test:
-H 'X-Array-IP: $(backend)' \
$(csp)/containers/v1/hosts/41302701-0196-420f-b319-834a79891db1 -f

# Delete host 3
- $(curl) $(curl_args) -XDELETE -H 'Content-Type: application/json' \
-H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \
$(csp)/containers/v1/hosts/41302701-0196-420f-b319-834a79891db2 -f

# Unpublish volume host 1
- $(curl) $(curl_args) -XPUT -d @tests/csp/unpublish.yaml -H 'Content-Type: application/json' \
-H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \
Expand All @@ -44,6 +49,11 @@ test:
-H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \
$(csp)/containers/v1/volumes/tank_my-new-volume16/actions/unpublish -f

# Unpublish CHAP volume
- $(curl) $(curl_args) -XPUT -d @tests/csp/unpublish-chap.yaml -H 'Content-Type: application/json' \
-H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \
$(csp)/containers/v1/volumes/tank_my-new-volume377/actions/unpublish -f

# Delete volume
- $(curl) $(curl_args) -XDELETE -H 'Content-Type: application/json' -H 'X-Auth-Token: $(password)' \
-H 'X-Array-IP: $(backend)' \
Expand All @@ -54,14 +64,19 @@ test:
-H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \
$(csp)/containers/v1/volumes/tank_my-new-volume18 -f

# Delete CHAP volume
- $(curl) $(curl_args) -XDELETE -H 'Content-Type: application/json' \
-H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \
$(csp)/containers/v1/volumes/tank_my-new-volume377 -f

# "Create" password
$(curl) $(curl_args) -XPOST \
-d '{ "array_ip": "$(backend)", "username": "$(username)", "password": "$(password)"}' \
-H 'Content-Type: application/json' $(csp)/containers/v1/tokens -f

# Delete password
$(curl) $(curl_args) -XDELETE -H 'Content-Type: application/json' $(csp)/containers/v1/tokens/123 -f

# Fail auth
$(curl) $(curl_args) -XGET -H 'Content-Type: application/json' $(csp)/containers/v1/tokens/123 -f || true

Expand All @@ -88,16 +103,16 @@ test:
# Get volume
$(curl) $(curl_args) -XGET -H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \
$(csp)/containers/v1/volumes?name=my-new-volume16 -f

# Mutate volume
$(curl) $(curl_args) -XPUT -d @tests/csp/mutator.yaml -H 'Content-Type: application/json' \
-H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \
$(csp)/containers/v1/volumes/tank_my-new-volume16 -f

$(curl) $(curl_args) -XPUT -d @tests/csp/mutators.yaml -H 'Content-Type: application/json' \
-H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \
$(csp)/containers/v1/volumes/tank_my-new-volume16 -f

$(curl) $(curl_args) -XPUT -d @tests/csp/mutator-negative.yaml -H 'Content-Type: application/json' \
-H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \
$(csp)/containers/v1/volumes/tank_my-new-volume16 -f || true
Expand All @@ -111,7 +126,7 @@ test:
$(curl) $(curl_args) -XPUT -d @tests/csp/publish-multi.yaml -H 'Content-Type: application/json' \
-H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \
$(csp)/containers/v1/volumes/tank_my-new-volume16/actions/publish -f

# Create snapshots
$(curl) $(curl_args) -XPOST -d @tests/csp/snapshot1.yaml -H 'Content-Type: application/json' \
-H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \
Expand All @@ -128,12 +143,12 @@ test:
$(curl) $(curl_args) -XGET -H 'Content-Type: application/json' \
-H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \
$(csp)/containers/v1/snapshots/tank_my-new-volume16@my-first-snapshot -f

# Create clone
$(curl) $(curl_args) -XPOST -d @tests/csp/clone.yaml -H 'Content-Type: application/json' \
-H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \
$(csp)/containers/v1/volumes -f

# Delete clone
$(curl) $(curl_args) -XDELETE -H 'Content-Type: application/json' -H 'X-Auth-Token: $(password)' \
-H 'X-Array-IP: $(backend)' $(csp)/containers/v1/volumes/tank_my-new-volume17 -f
Expand Down Expand Up @@ -163,6 +178,37 @@ test:
-H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \
$(csp)/containers/v1/volumes/tank_my-new-volume18 -f

# Create host 3 CHAP
$(curl) $(curl_args) -XPOST -d @tests/csp/initiator-chap.yaml -H 'Content-Type: application/json' \
-H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \
$(csp)/containers/v1/hosts -f

# Create CHAP volume
$(curl) $(curl_args) -XPOST -d @tests/csp/volume-chap.yaml -H 'Content-Type: application/json' \
-H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \
$(csp)/containers/v1/volumes -f

# Publish CHAP volume
$(curl) $(curl_args) -XPUT -d @tests/csp/publish-chap.yaml -H 'Content-Type: application/json' \
-H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \
$(csp)/containers/v1/volumes/tank_my-new-volume377/actions/publish -f


# Unpublish CHAP volume
$(curl) $(curl_args) -XPUT -d @tests/csp/unpublish-chap.yaml -H 'Content-Type: application/json' \
-H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \
$(csp)/containers/v1/volumes/tank_my-new-volume377/actions/unpublish -f

# Delete volume CHAP
$(curl) $(curl_args) -XDELETE -H 'Content-Type: application/json' \
-H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \
$(csp)/containers/v1/volumes/tank_my-new-volume377 -f

# Delete host CHAP
$(curl) $(curl_args) -XDELETE -H 'Content-Type: application/json' \
-H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \
$(csp)/containers/v1/hosts/41302701-0196-420f-b319-834a79891db2 -f

# Delete host 1
- $(curl) $(curl_args) -XDELETE -H 'Content-Type: application/json' \
-H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \
Expand Down
3 changes: 3 additions & 0 deletions e2e/tests/storage-class-rwo.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,8 @@ parameters:
csi.storage.k8s.io/provisioner-secret-namespace: hpe-storage
description: "Volume created by the HPE CSI Driver for Kubernetes from {namespace}"
root: tank/csi-e2e
chapSecretNamespace: hpe-storage
chapSecretName: my-chap-secret
authNetworks: 192.168.20.0/24, 192.168.10.0/24
reclaimPolicy: Delete
allowVolumeExpansion: true
10 changes: 5 additions & 5 deletions e2e/tests/test-driver-rwo.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ DriverInfo:
Name: csi.hpe.com
RequiredAccessModes:
- ReadWriteOnce
# UNDER TEST
# TopologyKeys:
# - csi.hpe.com/zone
# NumAllowedTopologies: 1
# UNDER TEST
TopologyKeys:
- csi.hpe.com/zone
NumAllowedTopologies: 1
Capabilities:
persistence: true
block: true
Expand All @@ -21,7 +21,7 @@ DriverInfo:
nodeExpansion: true
volumeLimits: false
# UNDER TEST
# topology: true
topology: true
singleNodeVolume: false
RWX: false
pvcDataSource: true
Expand Down
16 changes: 16 additions & 0 deletions tests/csp/initiator-chap.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "host-02.lab.internet.com",
"uuid": "41302701-0196-420f-b319-834a79891db2",
"iqns": [
"iqn.2019-07.com.nimblestorage:foo-iqn",
"iqn.2019-06.com.cloudvolumes:foo-iqn"
],
"networks": [
"172.28.12.162/20",
"10.234.63.106/20",
"8.8.8.8/8"
],
"chap_user": "makefile",
"chap_password": "make567890abcdef",
"wwpns": []
}
4 changes: 4 additions & 0 deletions tests/csp/publish-chap.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"host_uuid": "41302701-0196-420f-b319-834a79891db2",
"access_protocol": "iscsi"
}
3 changes: 3 additions & 0 deletions tests/csp/unpublish-chap.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"host_uuid": "41302701-0196-420f-b319-834a79891db2"
}
16 changes: 16 additions & 0 deletions tests/csp/volume-chap.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "my-new-volume377",
"size": "1073741824",
"description": "my CHAP volume {pvc}",
"config": {
"csi.storage.k8s.io/pvc/name": "my-whatever-volume",
"authNetworks": "192.168.30.0/24, 192.168.31.0/24",
"compression": "GZIP-1",
"deduplication": "OFF",
"sparse": "true",
"volblocksize": "16K",
"sync": "ALWAYS",
"zpool": "tank"

}
}
78 changes: 72 additions & 6 deletions truenascsp/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ def __init__(self):
self.resp_msg = '100 Continue'
self.target_basenames = [ 'iqn.2011-08.org.truenas.ctl', 'iqn.2005-10.org.freenas.ctl' ]
self.target_portal = 'hpe-csi'
self.chap_tag = environ.get('DEFAULT_CHAP_TAG', '4730274')
self.backend_retries = 15
self.backend_delay = 1.5
self.access_name = '{dataset_name}'
Expand Down Expand Up @@ -165,6 +166,36 @@ def initiator_exists(self, dataset):
return True
return False


def apply_auths(self, chap_user, chap_password):
# check if auths already exist
auth = self.fetch('iscsi/auth', field='tag', value=int(self.chap_tag), returnBy=dict)

# if exists, check credentials
if auth:
self.logger.info('CHAP found: %s', self.chap_tag)

# if different, change them
if auth.get('user') != chap_user or auth.get('secret') != chap_password:
req_backend = {
'user': chap_user,
'secret': chap_password
}
self.put('iscsi/auth/id/{aid}'.format(aid=auth.get('id')), req_backend)
self.logger.info('CHAP updated: %s', self.chap_tag)
else:
# if not, create it
req_backend = {
'tag': self.chap_tag,
'user': chap_user,
'secret': chap_password
}
self.post('iscsi/auth', req_backend)
self.logger.info('CHAP created: %s', self.chap_tag)

return self.req_backend.json()


def apply_initiator(self, name, **kwargs):

content = {}
Expand All @@ -175,9 +206,11 @@ def apply_initiator(self, name, **kwargs):
if kwargs.get('content'):
content = kwargs.get('content')

# iqns are passed when?
if kwargs.get('iqns'):
iqns = kwargs.get('iqns')
chap_user = content.get('chap_user')
chap_password = content.get('chap_password')

if chap_user and chap_password:
self.apply_auths(chap_user, chap_password)

current_initiator = self.fetch('iscsi/initiator', field='comment',
value=name, returnBy=dict)
Expand Down Expand Up @@ -340,6 +373,10 @@ def fetch(self, resource, **kwargs):

self.logger.debug('API fetch caught %d items (last resort)', len(results))

if len(results) == 0:
if kwargs.get('returnBy') == dict:
return {}

return results

def uri_id(self, resource, rid):
Expand Down Expand Up @@ -498,7 +535,12 @@ def get_target(self, access_name, **kwargs):


def create_target(self, dataset, **kwargs):
content = kwargs.get('content')
# content will only be available at provisioning
content = kwargs.get('content', {})
config = content.get('config', {})

self.logger.debug('Content during target creation: %s', content)
self.logger.debug('Config during target creation: %s', config)

try:
dataset_name = self.xlst_name_from_id(dataset.get('id'))
Expand All @@ -520,7 +562,13 @@ def create_target(self, dataset, **kwargs):

# treat SCALE
if system_version == "SCALE":
req_backend['auth_networks'] = self.ipaddrs_to_networks(discovery_ips)
custom_networks = config.get('auth_networks')
if custom_networks:
req_backend['auth_networks'] = self.auth_networks_validate(custom_networks)
self.logger.debug('Using custom auth_networks: %s', req_backend['auth_networks'])
else:
req_backend['auth_networks'] = self.ipaddrs_to_networks(discovery_ips)
self.logger.debug('Using discovery auth_networks: %s', req_backend['auth_networks'])

target = self.fetch('iscsi/target', field='name', value=access_name)

Expand Down Expand Up @@ -612,7 +660,7 @@ def apply_publish(self, access_name, **kwargs):
if initiator:
self.logger.debug('Existing target initiator: %s', initiator.get('id'))
else:
initiator = self.apply_initiator(access_name, iqns=[])
initiator = self.apply_initiator(access_name)

# merge host initiators to target initiators
initiators = list(set(initiator['initiators'] + host.get('initiators')))
Expand Down Expand Up @@ -644,6 +692,13 @@ def apply_publish(self, access_name, **kwargs):
'initiator': publish.get('initiator', {}).get('id')
}

# deal with CHAP
auth = self.fetch('iscsi/auth', field='tag', value=int(self.chap_tag), returnBy=dict)

if auth:
portal_group['auth'] = self.chap_tag
portal_group['authmethod'] = "CHAP"

# need global iSCSI config
publish['iscsi_config'] = self.fetch('iscsi/global')

Expand All @@ -663,3 +718,14 @@ def apply_publish(self, access_name, **kwargs):
publish = {}

return publish


def auth_networks_validate(self, networks):
res = []
cidrs = re.split(r'\s*,\s*', networks)

for cidr in cidrs:
if ipaddress.ip_network(cidr):
res.append(cidr)

return res
9 changes: 9 additions & 0 deletions truenascsp/truenascsp.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,13 +120,17 @@ def on_put(self, req, resp, volume_id):
api.logger.debug('Backend publish results: %s', publish)
api.logger.debug('Frontend publish content: %s', content)

auth = api.fetch('iscsi/auth', field='tag', value=int(api.chap_tag), returnBy=dict)

# respond to CSI
if publish.get('target', {}).get('extent', {}).get('naa') and publish.get('iscsi_config', {}).get('basename'):
csi_resp = {
'discovery_ips': api.discovery_ips(),
'access_protocol': 'iscsi',
'lun_id': 0,
'serial_number': publish.get('target').get('extent').get('naa').lstrip('0x'),
'chap_user': auth.get('user', ''),
'chap_password': auth.get('secret',''),
'target_names': [
'{base}:{access_name}'.format(
base=publish.get('iscsi_config').get('basename'),
Expand Down Expand Up @@ -342,7 +346,10 @@ def on_post(self, req, resp):
resp.status = falcon.HTTP_500
return

# create target
res = api.create_target(dataset, content=content)

# respond to CSI driver
csi_resp = api.dataset_to_volume(dataset)
resp.body = json.dumps(csi_resp)

Expand Down Expand Up @@ -371,6 +378,8 @@ def on_post(self, req, resp):
'uuid': payload.get('comment'),
'iqns': payload.get('initiators'),
'networks': content.get('networks'),
'chap_user': content.get('chap_user', ''),
'chap_password': content.get('chap_password', ''),
'wwpns': []
}

Expand Down

0 comments on commit a253391

Please sign in to comment.