1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

Add support for Redfish session create, delete, and authenticate (#2027)

* Add support for Redfish session create and delete

* add changelog fragment

* Apply suggestions from code review

Co-authored-by: Felix Fontein <felix@fontein.de>

Co-authored-by: Felix Fontein <felix@fontein.de>
This commit is contained in:
Bill Dodd 2021-03-19 15:14:33 -05:00 committed by GitHub
parent 79fb3e9852
commit efd441407f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 246 additions and 62 deletions

View file

@ -0,0 +1,2 @@
minor_changes:
- redfish_* modules, redfish_utils module utils - add support for Redfish session create, delete, and authenticate (https://github.com/ansible-collections/community.general/issues/1975).

View file

@ -38,13 +38,34 @@ class RedfishUtils(object):
self.data_modification = data_modification
self._init_session()
def _auth_params(self, headers):
"""
Return tuple of required authentication params based on the presence
of a token in the self.creds dict. If using a token, set the
X-Auth-Token header in the `headers` param.
:param headers: dict containing headers to send in request
:return: tuple of username, password and force_basic_auth
"""
if self.creds.get('token'):
username = None
password = None
force_basic_auth = False
headers['X-Auth-Token'] = self.creds['token']
else:
username = self.creds['user']
password = self.creds['pswd']
force_basic_auth = True
return username, password, force_basic_auth
# The following functions are to send GET/POST/PATCH/DELETE requests
def get_request(self, uri):
req_headers = dict(GET_HEADERS)
username, password, basic_auth = self._auth_params(req_headers)
try:
resp = open_url(uri, method="GET", headers=GET_HEADERS,
url_username=self.creds['user'],
url_password=self.creds['pswd'],
force_basic_auth=True, validate_certs=False,
resp = open_url(uri, method="GET", headers=req_headers,
url_username=username, url_password=password,
force_basic_auth=basic_auth, validate_certs=False,
follow_redirects='all',
use_proxy=True, timeout=self.timeout)
data = json.loads(to_native(resp.read()))
@ -65,14 +86,16 @@ class RedfishUtils(object):
return {'ret': True, 'data': data, 'headers': headers}
def post_request(self, uri, pyld):
req_headers = dict(POST_HEADERS)
username, password, basic_auth = self._auth_params(req_headers)
try:
resp = open_url(uri, data=json.dumps(pyld),
headers=POST_HEADERS, method="POST",
url_username=self.creds['user'],
url_password=self.creds['pswd'],
force_basic_auth=True, validate_certs=False,
headers=req_headers, method="POST",
url_username=username, url_password=password,
force_basic_auth=basic_auth, validate_certs=False,
follow_redirects='all',
use_proxy=True, timeout=self.timeout)
headers = dict((k.lower(), v) for (k, v) in resp.info().items())
except HTTPError as e:
msg = self._get_extended_message(e)
return {'ret': False,
@ -86,10 +109,10 @@ class RedfishUtils(object):
except Exception as e:
return {'ret': False,
'msg': "Failed POST request to '%s': '%s'" % (uri, to_text(e))}
return {'ret': True, 'resp': resp}
return {'ret': True, 'headers': headers, 'resp': resp}
def patch_request(self, uri, pyld):
headers = PATCH_HEADERS
req_headers = dict(PATCH_HEADERS)
r = self.get_request(uri)
if r['ret']:
# Get etag from etag header or @odata.etag property
@ -97,15 +120,13 @@ class RedfishUtils(object):
if not etag:
etag = r['data'].get('@odata.etag')
if etag:
# Make copy of headers and add If-Match header
headers = dict(headers)
headers['If-Match'] = etag
req_headers['If-Match'] = etag
username, password, basic_auth = self._auth_params(req_headers)
try:
resp = open_url(uri, data=json.dumps(pyld),
headers=headers, method="PATCH",
url_username=self.creds['user'],
url_password=self.creds['pswd'],
force_basic_auth=True, validate_certs=False,
headers=req_headers, method="PATCH",
url_username=username, url_password=password,
force_basic_auth=basic_auth, validate_certs=False,
follow_redirects='all',
use_proxy=True, timeout=self.timeout)
except HTTPError as e:
@ -124,13 +145,14 @@ class RedfishUtils(object):
return {'ret': True, 'resp': resp}
def delete_request(self, uri, pyld=None):
req_headers = dict(DELETE_HEADERS)
username, password, basic_auth = self._auth_params(req_headers)
try:
data = json.dumps(pyld) if pyld else None
resp = open_url(uri, data=data,
headers=DELETE_HEADERS, method="DELETE",
url_username=self.creds['user'],
url_password=self.creds['pswd'],
force_basic_auth=True, validate_certs=False,
headers=req_headers, method="DELETE",
url_username=username, url_password=password,
force_basic_auth=basic_auth, validate_certs=False,
follow_redirects='all',
use_proxy=True, timeout=self.timeout)
except HTTPError as e:
@ -1192,6 +1214,54 @@ class RedfishUtils(object):
return {'ret': True, 'changed': True, 'msg': "Clear all sessions successfully"}
def create_session(self):
if not self.creds.get('user') or not self.creds.get('pswd'):
return {'ret': False, 'msg':
'Must provide the username and password parameters for '
'the CreateSession command'}
payload = {
'UserName': self.creds['user'],
'Password': self.creds['pswd']
}
response = self.post_request(self.root_uri + self.sessions_uri, payload)
if response['ret'] is False:
return response
headers = response['headers']
if 'x-auth-token' not in headers:
return {'ret': False, 'msg':
'The service did not return the X-Auth-Token header in '
'the response from the Sessions collection POST'}
if 'location' not in headers:
self.module.warn(
'The service did not return the Location header for the '
'session URL in the response from the Sessions collection '
'POST')
session_uri = None
else:
session_uri = urlparse(headers.get('location')).path
session = dict()
session['token'] = headers.get('x-auth-token')
session['uri'] = session_uri
return {'ret': True, 'changed': True, 'session': session,
'msg': 'Session created successfully'}
def delete_session(self, session_uri):
if not session_uri:
return {'ret': False, 'msg':
'Must provide the session_uri parameter for the '
'DeleteSession command'}
response = self.delete_request(self.root_uri + session_uri)
if response['ret'] is False:
return response
return {'ret': True, 'changed': True,
'msg': 'Session deleted successfully'}
def get_firmware_update_capabilities(self):
result = {}
response = self.get_request(self.root_uri + self.update_uri)

View file

@ -33,15 +33,18 @@ options:
- Base URI of OOB controller
type: str
username:
required: true
description:
- User for authentication with OOB controller
type: str
password:
required: true
description:
- Password for authentication with OOB controller
type: str
auth_token:
description:
- Security token for authentication with OOB controller
type: str
version_added: 2.3.0
timeout:
description:
- Timeout in seconds for URL requests to OOB controller
@ -137,11 +140,21 @@ def main():
category=dict(required=True),
command=dict(required=True, type='list', elements='str'),
baseuri=dict(required=True),
username=dict(required=True),
password=dict(required=True, no_log=True),
username=dict(),
password=dict(no_log=True),
auth_token=dict(no_log=True),
timeout=dict(type='int', default=10),
resource_id=dict()
),
required_together=[
('username', 'password'),
],
required_one_of=[
('username', 'auth_token'),
],
mutually_exclusive=[
('username', 'auth_token'),
],
supports_check_mode=False
)
@ -150,7 +163,8 @@ def main():
# admin credentials used for authentication
creds = {'user': module.params['username'],
'pswd': module.params['password']}
'pswd': module.params['password'],
'token': module.params['auth_token']}
# timeout
timeout = module.params['timeout']

View file

@ -36,15 +36,18 @@ options:
- Base URI of iDRAC
type: str
username:
required: true
description:
- User for authentication with iDRAC
type: str
password:
required: true
description:
- Password for authentication with iDRAC
type: str
auth_token:
description:
- Security token for authentication with OOB controller
type: str
version_added: 2.3.0
manager_attributes:
required: false
description:
@ -232,12 +235,22 @@ def main():
category=dict(required=True),
command=dict(required=True, type='list', elements='str'),
baseuri=dict(required=True),
username=dict(required=True),
password=dict(required=True, no_log=True),
username=dict(),
password=dict(no_log=True),
auth_token=dict(no_log=True),
manager_attributes=dict(type='dict', default={}),
timeout=dict(type='int', default=10),
resource_id=dict()
),
required_together=[
('username', 'password'),
],
required_one_of=[
('username', 'auth_token'),
],
mutually_exclusive=[
('username', 'auth_token'),
],
supports_check_mode=False
)
@ -246,7 +259,8 @@ def main():
# admin credentials used for authentication
creds = {'user': module.params['username'],
'pswd': module.params['password']}
'pswd': module.params['password'],
'token': module.params['auth_token']}
# timeout
timeout = module.params['timeout']

View file

@ -37,15 +37,18 @@ options:
- Base URI of iDRAC controller
type: str
username:
required: true
description:
- User for authentication with iDRAC controller
type: str
password:
required: true
description:
- Password for authentication with iDRAC controller
type: str
auth_token:
description:
- Security token for authentication with OOB controller
type: str
version_added: 2.3.0
timeout:
description:
- Timeout in seconds for URL requests to OOB controller
@ -174,10 +177,20 @@ def main():
category=dict(required=True),
command=dict(required=True, type='list', elements='str'),
baseuri=dict(required=True),
username=dict(required=True),
password=dict(required=True, no_log=True),
username=dict(),
password=dict(no_log=True),
auth_token=dict(no_log=True),
timeout=dict(type='int', default=10)
),
required_together=[
('username', 'password'),
],
required_one_of=[
('username', 'auth_token'),
],
mutually_exclusive=[
('username', 'auth_token'),
],
supports_check_mode=False
)
is_old_facts = module._name in ('idrac_redfish_facts', 'community.general.idrac_redfish_facts')
@ -191,7 +204,8 @@ def main():
# admin credentials used for authentication
creds = {'user': module.params['username'],
'pswd': module.params['password']}
'pswd': module.params['password'],
'token': module.params['auth_token']}
# timeout
timeout = module.params['timeout']

View file

@ -35,15 +35,23 @@ options:
- Base URI of OOB controller
type: str
username:
required: true
description:
- Username for authentication with OOB controller
type: str
password:
required: true
description:
- Password for authentication with OOB controller
type: str
auth_token:
description:
- Security token for authentication with OOB controller
type: str
version_added: 2.3.0
session_uri:
description:
- URI of the session resource
type: str
version_added: 2.3.0
id:
required: false
aliases: [ account_id ]
@ -284,15 +292,6 @@ EXAMPLES = '''
category: Systems
command: DisableBootOverride
- name: Set chassis indicator LED to blink
community.general.redfish_command:
category: Chassis
command: IndicatorLedBlink
resource_id: 1U
baseuri: "{{ baseuri }}"
username: "{{ username }}"
password: "{{ password }}"
- name: Add user
community.general.redfish_command:
category: Accounts
@ -414,6 +413,31 @@ EXAMPLES = '''
password: "{{ password }}"
timeout: 20
- name: Create session
community.general.redfish_command:
category: Sessions
command: CreateSession
baseuri: "{{ baseuri }}"
username: "{{ username }}"
password: "{{ password }}"
register: result
- name: Set chassis indicator LED to blink using security token for auth
community.general.redfish_command:
category: Chassis
command: IndicatorLedBlink
resource_id: 1U
baseuri: "{{ baseuri }}"
auth_token: "{{ result.session.token }}"
- name: Delete session using security token created by CreateSesssion above
community.general.redfish_command:
category: Sessions
command: DeleteSession
baseuri: "{{ baseuri }}"
auth_token: "{{ result.session.token }}"
session_uri: "{{ result.session.uri }}"
- name: Clear Sessions
community.general.redfish_command:
category: Sessions
@ -538,7 +562,7 @@ CATEGORY_COMMANDS_ALL = {
"Accounts": ["AddUser", "EnableUser", "DeleteUser", "DisableUser",
"UpdateUserRole", "UpdateUserPassword", "UpdateUserName",
"UpdateAccountServiceProperties"],
"Sessions": ["ClearSessions"],
"Sessions": ["ClearSessions", "CreateSession", "DeleteSession"],
"Manager": ["GracefulRestart", "ClearLogs", "VirtualMediaInsert",
"VirtualMediaEject", "PowerOn", "PowerForceOff", "PowerForceRestart",
"PowerGracefulRestart", "PowerGracefulShutdown", "PowerReboot"],
@ -553,8 +577,10 @@ def main():
category=dict(required=True),
command=dict(required=True, type='list', elements='str'),
baseuri=dict(required=True),
username=dict(required=True),
password=dict(required=True, no_log=True),
username=dict(),
password=dict(no_log=True),
auth_token=dict(no_log=True),
session_uri=dict(),
id=dict(aliases=["account_id"]),
new_username=dict(aliases=["account_username"]),
new_password=dict(aliases=["account_password"], no_log=True),
@ -590,6 +616,15 @@ def main():
)
)
),
required_together=[
('username', 'password'),
],
required_one_of=[
('username', 'auth_token'),
],
mutually_exclusive=[
('username', 'auth_token'),
],
supports_check_mode=False
)
@ -598,7 +633,8 @@ def main():
# admin credentials used for authentication
creds = {'user': module.params['username'],
'pswd': module.params['password']}
'pswd': module.params['password'],
'token': module.params['auth_token']}
# user to add/modify/delete
user = {'account_id': module.params['id'],
@ -712,6 +748,10 @@ def main():
for command in command_list:
if command == "ClearSessions":
result = rf_utils.clear_sessions()
elif command == "CreateSession":
result = rf_utils.create_session()
elif command == "DeleteSession":
result = rf_utils.delete_session(module.params['session_uri'])
elif category == "Manager":
# execute only if we find a Manager service resource
@ -748,7 +788,9 @@ def main():
if result['ret'] is True:
del result['ret']
changed = result.get('changed', True)
module.exit_json(changed=changed, msg='Action was successful')
session = result.get('session', dict())
module.exit_json(changed=changed, session=session,
msg='Action was successful')
else:
module.fail_json(msg=to_native(result['msg']))

View file

@ -34,15 +34,18 @@ options:
- Base URI of OOB controller
type: str
username:
required: true
description:
- User for authentication with OOB controller
type: str
password:
required: true
description:
- Password for authentication with OOB controller
type: str
auth_token:
description:
- Security token for authentication with OOB controller
type: str
version_added: 2.3.0
bios_attributes:
required: false
description:
@ -219,8 +222,9 @@ def main():
category=dict(required=True),
command=dict(required=True, type='list', elements='str'),
baseuri=dict(required=True),
username=dict(required=True),
password=dict(required=True, no_log=True),
username=dict(),
password=dict(no_log=True),
auth_token=dict(no_log=True),
bios_attributes=dict(type='dict', default={}),
timeout=dict(type='int', default=10),
boot_order=dict(type='list', elements='str', default=[]),
@ -235,6 +239,15 @@ def main():
default={}
)
),
required_together=[
('username', 'password'),
],
required_one_of=[
('username', 'auth_token'),
],
mutually_exclusive=[
('username', 'auth_token'),
],
supports_check_mode=False
)
@ -243,7 +256,8 @@ def main():
# admin credentials used for authentication
creds = {'user': module.params['username'],
'pswd': module.params['password']}
'pswd': module.params['password'],
'token': module.params['auth_token']}
# timeout
timeout = module.params['timeout']

View file

@ -37,15 +37,18 @@ options:
- Base URI of OOB controller
type: str
username:
required: true
description:
- User for authentication with OOB controller
type: str
password:
required: true
description:
- Password for authentication with OOB controller
type: str
auth_token:
description:
- Security token for authentication with OOB controller
type: str
version_added: 2.3.0
timeout:
description:
- Timeout in seconds for URL requests to OOB controller
@ -301,10 +304,20 @@ def main():
category=dict(type='list', elements='str', default=['Systems']),
command=dict(type='list', elements='str'),
baseuri=dict(required=True),
username=dict(required=True),
password=dict(required=True, no_log=True),
username=dict(),
password=dict(no_log=True),
auth_token=dict(no_log=True),
timeout=dict(type='int', default=10)
),
required_together=[
('username', 'password'),
],
required_one_of=[
('username', 'auth_token'),
],
mutually_exclusive=[
('username', 'auth_token'),
],
supports_check_mode=False
)
is_old_facts = module._name in ('redfish_facts', 'community.general.redfish_facts')
@ -315,7 +328,8 @@ def main():
# admin credentials used for authentication
creds = {'user': module.params['username'],
'pswd': module.params['password']}
'pswd': module.params['password'],
'token': module.params['auth_token']}
# timeout
timeout = module.params['timeout']