summaryrefslogtreecommitdiffstats
path: root/ansible/library
diff options
context:
space:
mode:
authorPetr OspalĂ˝ <p.ospaly@partner.samsung.com>2019-04-08 04:55:47 +0200
committerMichal Ptacek <m.ptacek@partner.samsung.com>2019-04-24 14:03:19 +0000
commit72b09b1a46200b6544600ff2432693f0b43c3918 (patch)
treec0cc18c0c98c0a80b5d273f188b458cc4d307d89 /ansible/library
parent9dee2011bf2eeddf43c4479935a5219c818e1cfb (diff)
Add support for rancher authentication
This commit adds a new mode to the rancher1_api module, which enables the rancher local authentication (username/password). There is an already predefined rancher admin user called 'admin' and that is the account, which this mode modifies. Due to the complex API and the fact that rancher 1.6 is soon to be obsoleted, this module is going the simpler route and it is just editing this default admin account instead of creating a completely arbitrary username/password credentials. For that reason is using the 'account_id', which is unique for all accounts and the default admin account of rancher has '1a1'. As of now this module cannot handle changed password once the auth. is enabled. Change-Id: Iea8923c71bdb82267c966a00d62f0f43eb5adb76 Issue-ID: OOM-1734 Signed-off-by: Petr OspalĂ˝ <p.ospaly@partner.samsung.com>
Diffstat (limited to 'ansible/library')
-rw-r--r--ansible/library/rancher1_api.py244
1 files changed, 216 insertions, 28 deletions
diff --git a/ansible/library/rancher1_api.py b/ansible/library/rancher1_api.py
index 006de9ce..d49e6252 100644
--- a/ansible/library/rancher1_api.py
+++ b/ansible/library/rancher1_api.py
@@ -64,11 +64,17 @@ options:
description:
- Dictionary with key/value pairs. The actual names and meaning of pairs
depends on the used mode.
- - 'settings' mode:
- option: Option/path in JSON API (url).
- value: A new value to replace the current one.
- - 'access_control' mode:
- None - not yet implemented - placeholder only.
+ - settings mode:
+ option - Option/path in JSON API (url).
+ value - A new value to replace the current one.
+ - access_control mode:
+ account_id - The unique ID of the account - the default rancher admin
+ has '1a1'. Better way would be to just create arbitrary username and
+ set credentials for that, but due to time constraints, the route with
+ an ID is simpler. The designated '1a1' could be hardcoded and hidden
+ but if the user will want to use some other account (there are many),
+ then it can be just changed to some other ID.
+ password - A new password in a plaintext.
required: true
timeout:
description:
@@ -121,19 +127,26 @@ def get_rancher_api_value(url, headers=None, timeout=default_timeout,
@_decorate_rancher_api_request
def set_rancher_api_value(url, payload, headers=None, timeout=default_timeout,
- username=None, password=None):
+ username=None, password=None, method='PUT'):
+
+ if method == 'PUT':
+ request_set_method = requests.put
+ elif method == 'POST':
+ request_set_method = requests.post
+ else:
+ raise ModeError('ERROR: Wrong request method: %s' % str(method))
if username and password:
- return requests.put(url, headers=headers,
- timeout=timeout,
- allow_redirects=False,
- data=json.dumps(payload),
- auth=(username, password))
+ return request_set_method(url, headers=headers,
+ timeout=timeout,
+ allow_redirects=False,
+ data=json.dumps(payload),
+ auth=(username, password))
else:
- return requests.put(url, headers=headers,
- timeout=timeout,
- allow_redirects=False,
- data=json.dumps(payload))
+ return request_set_method(url, headers=headers,
+ timeout=timeout,
+ allow_redirects=False,
+ data=json.dumps(payload))
def create_rancher_api_url(server, mode, option):
@@ -158,6 +171,176 @@ def get_keypair(keypair):
return None, None
+def mode_access_control(api_url, data=None, headers=None,
+ timeout=default_timeout, access_key=None,
+ secret_key=None, dry_run=False):
+
+ # returns true if local auth was enabled or false if passwd changed
+ def is_admin_enabled(json_data, password):
+ try:
+ if json_data['type'] == "localAuthConfig" and \
+ json_data['accessMode'] == "unrestricted" and \
+ json_data['username'] == "admin" and \
+ json_data['password'] == password and \
+ json_data['enabled']:
+ return True
+ except Exception:
+ pass
+
+ try:
+ if json_data['type'] == "error" and \
+ json_data['code'] == "IncorrectPassword":
+ return False
+ except Exception:
+ pass
+
+ # this should never happen
+ raise ModeError('ERROR: Unknown status of the local authentication')
+
+ def create_localauth_payload(password):
+ payload = {
+ "enabled": True,
+ "accessMode": "unrestricted",
+ "username": "admin",
+ "password": password
+ }
+
+ return payload
+
+ def get_admin_password_id():
+ # assemble request URL
+ request_url = api_url + 'accounts/' + data['account_id'].strip('/') \
+ + '/credentials'
+
+ # API get current value
+ try:
+ json_response = get_rancher_api_value(request_url,
+ username=access_key,
+ password=secret_key,
+ headers=headers,
+ timeout=timeout)
+ except requests.HTTPError as e:
+ raise ModeError(str(e))
+ except requests.Timeout as e:
+ raise ModeError(str(e))
+
+ if not json_response:
+ raise ModeError('ERROR: BAD RESPONSE (GET) - no json value in '
+ + 'the response')
+
+ try:
+ for item in json_response['data']:
+ if item['type'] == 'password' and \
+ item['accountId'] == data['account_id']:
+ return item['id']
+ except Exception:
+ pass
+
+ return None
+
+ # check if data contains all required fields
+ try:
+ if not isinstance(data['account_id'], str) or data['account_id'] == '':
+ raise ModeError("ERROR: 'account_id' must contain an id of the "
+ + "affected account")
+ except KeyError:
+ raise ModeError("ERROR: Mode 'access_control' requires the field: "
+ + "'account_id': %s" % str(data))
+ try:
+ if not isinstance(data['password'], str) or data['password'] == '':
+ raise ModeError("ERROR: 'password' must contain some password")
+ except KeyError:
+ raise ModeError("ERROR: Mode 'access_control' requires the field: "
+ + "'password': %s" % str(data))
+
+ # assemble request URL
+ request_url = api_url + 'localauthconfigs'
+
+ # API get current value
+ try:
+ json_response = get_rancher_api_value(request_url,
+ username=access_key,
+ password=secret_key,
+ headers=headers,
+ timeout=timeout)
+ except requests.HTTPError as e:
+ raise ModeError(str(e))
+ except requests.Timeout as e:
+ raise ModeError(str(e))
+
+ if not json_response:
+ raise ModeError('ERROR: BAD RESPONSE (GET) - no json value in the '
+ + 'response')
+
+ # we will check if local auth is enabled or not
+ enabled = False
+ try:
+ for item in json_response['data']:
+ if item['type'] == 'localAuthConfig' and \
+ item['accessMode'] == 'unrestricted' and \
+ item['enabled']:
+ enabled = True
+ break
+ except Exception:
+ enabled = False
+
+ if dry_run:
+ # we will not set anything, only signal potential change
+ if enabled:
+ changed = False
+ else:
+ changed = True
+ else:
+ # we will try to enable again with the same password
+ localauth_payload = create_localauth_payload(data['password'])
+ json_response = None
+ try:
+ json_response = set_rancher_api_value(request_url,
+ localauth_payload,
+ username=access_key,
+ password=secret_key,
+ headers=headers,
+ method='POST',
+ timeout=timeout)
+ except requests.HTTPError as e:
+ raise ModeError(str(e))
+ except requests.Timeout as e:
+ raise ModeError(str(e))
+
+ if not json_response:
+ raise ModeError('ERROR: BAD RESPONSE (PUT) - no json value in '
+ + 'the response')
+
+ # we check if the admin was already set or not...
+ if enabled and is_admin_enabled(json_response, data['password']):
+ # it was enabled before and password is the same - no change
+ changed = False
+ elif is_admin_enabled(json_response, data['password']):
+ # we enabled it for the first time
+ changed = True
+ # ...and reset password if needed
+ else:
+ # local auth is enabled but the password differs
+ # we must reset the admin's password
+ password_id = get_admin_password_id()
+
+ if password_id is None:
+ raise ModeError("ERROR: admin's password is set, but we "
+ + "cannot identify it")
+
+ # TODO
+ raise ModeError("TODO: Reset of the admin password is not yet "
+ + "implemented")
+
+ if changed:
+ msg = "Local authentication is enabled, admin has assigned password"
+ else:
+ msg = "Local authentication was already enabled, admin's password " \
+ + "is unchanged"
+
+ return changed, msg
+
+
def mode_settings(api_url, data=None, headers=None, timeout=default_timeout,
access_key=None, secret_key=None, dry_run=False):
@@ -201,17 +384,17 @@ def mode_settings(api_url, data=None, headers=None, timeout=default_timeout,
# check if data contains all required fields
try:
if not isinstance(data['option'], str) or data['option'] == '':
- raise ModeError("ERROR: 'option' must contain a name of the \
- option")
+ raise ModeError("ERROR: 'option' must contain a name of the "
+ + "option")
except KeyError:
- raise ModeError("ERROR: Mode 'settings' requires the field: 'option': \
- %s" % str(data))
+ raise ModeError("ERROR: Mode 'settings' requires the field: 'option': "
+ + "%s" % str(data))
try:
if not isinstance(data['value'], str) or data['value'] == '':
raise ModeError("ERROR: 'value' must contain a value")
except KeyError:
- raise ModeError("ERROR: Mode 'settings' requires the field: 'value': \
- %s" % str(data))
+ raise ModeError("ERROR: Mode 'settings' requires the field: 'value': "
+ + "%s" % str(data))
# assemble request URL
request_url = api_url + 'settings/' + data['option'].strip('/')
@@ -229,8 +412,8 @@ def mode_settings(api_url, data=None, headers=None, timeout=default_timeout,
raise ModeError(str(e))
if not json_response:
- raise ModeError('ERROR: BAD RESPONSE (GET) - no json value in the \
- response')
+ raise ModeError('ERROR: BAD RESPONSE (GET) - no json value in the '
+ + 'response')
if is_valid_rancher_api_option(json_response):
valid = True
@@ -238,8 +421,8 @@ def mode_settings(api_url, data=None, headers=None, timeout=default_timeout,
differs, payload = create_rancher_api_payload(json_response,
data['value'])
except ValueError:
- raise ModeError('ERROR: INVALID JSON - missing json values in \
- the response')
+ raise ModeError('ERROR: INVALID JSON - missing json values in '
+ + 'the response')
else:
valid = False
@@ -261,8 +444,8 @@ def mode_settings(api_url, data=None, headers=None, timeout=default_timeout,
raise ModeError(str(e))
if not json_response:
- raise ModeError('ERROR: BAD RESPONSE (PUT) - no json value in \
- the response')
+ raise ModeError('ERROR: BAD RESPONSE (PUT) - no json value in '
+ + 'the response')
else:
changed = True
else:
@@ -304,7 +487,12 @@ def mode_handler(server, rancher_mode, data=None, timeout=default_timeout,
secret_key=secret_key,
dry_run=dry_run)
elif rancher_mode == 'access_control':
- msg = "SKIP: 'access_control' Not yet implemented"
+ changed, msg = mode_access_control(api_url, data=data,
+ headers=http_headers,
+ timeout=timeout,
+ access_key=access_key,
+ secret_key=secret_key,
+ dry_run=dry_run)
return changed, msg