summaryrefslogtreecommitdiffstats
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
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>
-rw-r--r--ansible/library/rancher1_api.py244
-rw-r--r--ansible/roles/rancher/defaults/main.yml3
-rw-r--r--ansible/roles/rancher/tasks/rancher_server.yml17
3 files changed, 236 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
diff --git a/ansible/roles/rancher/defaults/main.yml b/ansible/roles/rancher/defaults/main.yml
index 67e581cd..e4d5cb9f 100644
--- a/ansible/roles/rancher/defaults/main.yml
+++ b/ansible/roles/rancher/defaults/main.yml
@@ -21,3 +21,6 @@ rancher:
service_log_purge_after_seconds: 86400 # 1 day
# Auto-purge Audit Log entries after this long (seconds)
audit_log_purge_after_seconds: 2592000 # 30 days
+
+ # Set this password for the rancher admin account:
+ admin_password: "admin"
diff --git a/ansible/roles/rancher/tasks/rancher_server.yml b/ansible/roles/rancher/tasks/rancher_server.yml
index b71bf8d1..e93dd0e0 100644
--- a/ansible/roles/rancher/tasks/rancher_server.yml
+++ b/ansible/roles/rancher/tasks/rancher_server.yml
@@ -32,6 +32,14 @@
delay: 5
until: env.data is defined
+# There is a lack of idempotency in the previous task and so there are new api
+# key-pairs created with each run.
+#
+# ToDo: fix idempotency of rancher role
+#
+# Anyway as rke will be default k8s orchestrator in Dublin, it's supposed to be
+# low prio topic. The following tasks dealing with the API are ignoring this problem
+# and they simply use the new created API key-pair, which is set as a fact here:
- name: Set apikey values
set_fact:
k8s_env_id: "{{ env.data.environment.id }}"
@@ -40,6 +48,15 @@
rancher_agent_image: "{{ env.data.registration_tokens.image }}"
rancher_agent_reg_url: "{{ env.data.registration_tokens.reg_url }}"
+- name: Setup rancher admin password and enable authentication
+ rancher1_api:
+ server: "{{ rancher_server_url }}"
+ account_key: "{{ key_public }}:{{ key_private }}"
+ mode: access_control
+ data:
+ account_id: 1a1 # default rancher admin account
+ password: "{{ rancher.admin_password }}"
+
- name: Configure the size of the rancher cattle db and logs
block:
- name: Main tables