From 3a6558a1af5ba14bc6614d94f768dd1a1fc86d9b Mon Sep 17 00:00:00 2001 From: Petr OspalĂ˝ Date: Mon, 8 Apr 2019 08:39:41 +0200 Subject: Add support for resetting the admin password MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The module rancher1_api can now reset an admin password of the default admin account (account_id: '1a1') and keep local authentication enabled. By default the authentication is not enabled, because the ansible module rancher_k8s_environment.py is not idempotent and it would need to be rewritten. Change-Id: Ib432537651b91216c32438ec1233dba3602e3faf Issue-ID: OOM-1734 Signed-off-by: Petr OspalĂ˝ --- ansible/library/rancher1_api.py | 142 ++++++++++++++++++------- ansible/roles/rancher/defaults/main.yml | 7 ++ ansible/roles/rancher/tasks/rancher_server.yml | 2 + 3 files changed, 110 insertions(+), 41 deletions(-) diff --git a/ansible/library/rancher1_api.py b/ansible/library/rancher1_api.py index d49e6252..5d74da1e 100644 --- a/ansible/library/rancher1_api.py +++ b/ansible/library/rancher1_api.py @@ -5,6 +5,7 @@ from ansible.module_utils.basic import AnsibleModule import requests import json import functools +import time DOCUMENTATION = """ --- @@ -96,8 +97,11 @@ def _decorate_rancher_api_request(request_method): def wrap_request(*args, **kwargs): response = request_method(*args, **kwargs) + authorized = True - if response.status_code != requests.codes.ok: + if response.status_code == 401: + authorized = False + elif response.status_code != requests.codes.ok: response.raise_for_status() try: @@ -105,7 +109,7 @@ def _decorate_rancher_api_request(request_method): except Exception: json_data = None - return json_data + return json_data, authorized return wrap_request @@ -214,17 +218,18 @@ def mode_access_control(api_url, data=None, headers=None, # API get current value try: - json_response = get_rancher_api_value(request_url, - username=access_key, - password=secret_key, - headers=headers, - timeout=timeout) + json_response, authorized = \ + 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: + if not authorized or not json_response: raise ModeError('ERROR: BAD RESPONSE (GET) - no json value in ' + 'the response') @@ -238,6 +243,37 @@ def mode_access_control(api_url, data=None, headers=None, return None + def remove_password(password_id, action): + if action == 'deactivate': + action_status = 'deactivating' + elif action == 'remove': + action_status = 'removing' + + request_url = api_url + 'passwords/' + password_id + \ + '/?action=' + action + + try: + json_response, authorized = \ + set_rancher_api_value(request_url, + {}, + 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 authorized or not json_response: + raise ModeError('ERROR: BAD RESPONSE (POST) - no json value in ' + + 'the response') + + if json_response['state'] != action_status: + raise ModeError("ERROR: Failed to '%s' the password: %s" % + (action, password_id)) + # check if data contains all required fields try: if not isinstance(data['account_id'], str) or data['account_id'] == '': @@ -258,17 +294,18 @@ def mode_access_control(api_url, data=None, headers=None, # API get current value try: - json_response = get_rancher_api_value(request_url, - username=access_key, - password=secret_key, - headers=headers, - timeout=timeout) + json_response, authorized = \ + 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: + if not authorized or not json_response: raise ModeError('ERROR: BAD RESPONSE (GET) - no json value in the ' + 'response') @@ -285,7 +322,7 @@ def mode_access_control(api_url, data=None, headers=None, enabled = False if dry_run: - # we will not set anything, only signal potential change + # we will not set anything and only signal potential change if enabled: changed = False else: @@ -293,22 +330,23 @@ def mode_access_control(api_url, data=None, headers=None, 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) + json_response, authorized = \ + 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)) + # here we ignore authorized status - we will try to reset password if not json_response: - raise ModeError('ERROR: BAD RESPONSE (PUT) - no json value in ' + raise ModeError('ERROR: BAD RESPONSE (POST) - no json value in ' + 'the response') # we check if the admin was already set or not... @@ -318,7 +356,7 @@ def mode_access_control(api_url, data=None, headers=None, elif is_admin_enabled(json_response, data['password']): # we enabled it for the first time changed = True - # ...and reset password if needed + # ...and reset password if needed (unauthorized access) else: # local auth is enabled but the password differs # we must reset the admin's password @@ -328,9 +366,29 @@ def mode_access_control(api_url, data=None, headers=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") + # One of the way to reset the password is to remove it first + # TODO - refactor this + remove_password(password_id, 'deactivate') + time.sleep(2) + remove_password(password_id, 'remove') + time.sleep(1) + + try: + json_response, authorized = \ + 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)) + + # finally we signal the change + changed = True if changed: msg = "Local authentication is enabled, admin has assigned password" @@ -401,17 +459,18 @@ def mode_settings(api_url, data=None, headers=None, timeout=default_timeout, # API get current value try: - json_response = get_rancher_api_value(request_url, - username=access_key, - password=secret_key, - headers=headers, - timeout=timeout) + json_response, authorized = \ + 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: + if not authorized or not json_response: raise ModeError('ERROR: BAD RESPONSE (GET) - no json value in the ' + 'response') @@ -432,18 +491,19 @@ def mode_settings(api_url, data=None, headers=None, timeout=default_timeout, elif valid and differs: # API set new value try: - json_response = set_rancher_api_value(request_url, - payload, - username=access_key, - password=secret_key, - headers=headers, - timeout=timeout) + json_response, authorized = \ + set_rancher_api_value(request_url, + payload, + 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: + if not authorized or not json_response: raise ModeError('ERROR: BAD RESPONSE (PUT) - no json value in ' + 'the response') else: diff --git a/ansible/roles/rancher/defaults/main.yml b/ansible/roles/rancher/defaults/main.yml index e4d5cb9f..6d354e6e 100644 --- a/ansible/roles/rancher/defaults/main.yml +++ b/ansible/roles/rancher/defaults/main.yml @@ -22,5 +22,12 @@ rancher: # Auto-purge Audit Log entries after this long (seconds) audit_log_purge_after_seconds: 2592000 # 30 days + # By default we don't enable local authentication (mainly due to + # to the fact that rancher_k8s_environment.py would have to be + # rewritten completely) + # But if you don't need to run rancher_kubernetes playbook more + # than once (you should not have to under the terms of a regular + # installation), then you can safely enable it. + auth_enabled: false # 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 e93dd0e0..4cda3722 100644 --- a/ansible/roles/rancher/tasks/rancher_server.yml +++ b/ansible/roles/rancher/tasks/rancher_server.yml @@ -48,6 +48,7 @@ rancher_agent_image: "{{ env.data.registration_tokens.image }}" rancher_agent_reg_url: "{{ env.data.registration_tokens.reg_url }}" +# By default disabled - when enabled this playbook cannot be run more than once. - name: Setup rancher admin password and enable authentication rancher1_api: server: "{{ rancher_server_url }}" @@ -56,6 +57,7 @@ data: account_id: 1a1 # default rancher admin account password: "{{ rancher.admin_password }}" + when: "rancher.auth_enabled is defined and rancher.auth_enabled" - name: Configure the size of the rancher cattle db and logs block: -- cgit 1.2.3-korg