Add support for resetting the admin password 72/84472/5
authorPetr Ospalý <p.ospaly@partner.samsung.com>
Mon, 8 Apr 2019 06:39:41 +0000 (08:39 +0200)
committerMichal Ptacek <m.ptacek@partner.samsung.com>
Wed, 24 Apr 2019 14:04:17 +0000 (14:04 +0000)
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ý <p.ospaly@partner.samsung.com>
ansible/library/rancher1_api.py
ansible/roles/rancher/defaults/main.yml
ansible/roles/rancher/tasks/rancher_server.yml

index d49e625..5d74da1 100644 (file)
@@ -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:
index e4d5cb9..6d354e6 100644 (file)
@@ -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"
index e93dd0e..4cda372 100644 (file)
@@ -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: