diff options
Diffstat (limited to 'ansible')
-rw-r--r-- | ansible/library/rancher1_api.py | 244 | ||||
-rw-r--r-- | ansible/roles/rancher/defaults/main.yml | 3 | ||||
-rw-r--r-- | ansible/roles/rancher/tasks/rancher_server.yml | 17 |
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 |