From 5aa63e1b0769ba41c5d7e9d1867c2f1064a79dd7 Mon Sep 17 00:00:00 2001 From: Bikouo Aubin <79859644+abikouo@users.noreply.github.com> Date: Thu, 12 Jan 2023 16:53:42 +0100 Subject: [PATCH] openshift_auth - fix discard token (#178) * openshift_auth: when revoking token, compute the right name of the openshift resource to delete from token name * conditional check to revoke token --- .../178-openshift_auth-fix-revoke-token.yml | 2 + molecule/default/prepare.yml | 2 +- molecule/default/tasks/openshift_auth.yml | 101 ++++++++++++++---- plugins/modules/openshift_auth.py | 50 +++++++-- 4 files changed, 120 insertions(+), 35 deletions(-) create mode 100644 changelogs/fragments/178-openshift_auth-fix-revoke-token.yml diff --git a/changelogs/fragments/178-openshift_auth-fix-revoke-token.yml b/changelogs/fragments/178-openshift_auth-fix-revoke-token.yml new file mode 100644 index 0000000..a136af0 --- /dev/null +++ b/changelogs/fragments/178-openshift_auth-fix-revoke-token.yml @@ -0,0 +1,2 @@ +bugfixes: + - openshift_auth - Review the way the discard process is working, add openshift algorithm to convert token to resource object name (https://github.com/openshift/community.okd/issues/176). diff --git a/molecule/default/prepare.yml b/molecule/default/prepare.yml index 28cfce9..f155ec1 100644 --- a/molecule/default/prepare.yml +++ b/molecule/default/prepare.yml @@ -54,7 +54,7 @@ roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole - name: cluster-reader + name: cluster-admin subjects: - apiGroup: rbac.authorization.k8s.io kind: User diff --git a/molecule/default/tasks/openshift_auth.yml b/molecule/default/tasks/openshift_auth.yml index be0d2c9..aeeee4c 100644 --- a/molecule/default/tasks/openshift_auth.yml +++ b/molecule/default/tasks/openshift_auth.yml @@ -1,5 +1,9 @@ --- - block: + - set_fact: + admin_user: test + admin_pass: testing123 + - name: Retrieve cluster info kubernetes.core.k8s_cluster_info: register: k8s_cluster @@ -10,47 +14,98 @@ - name: Log in (obtain access token) community.okd.openshift_auth: - username: test - password: testing123 + username: "{{ admin_user }}" + password: "{{ admin_pass }}" host: '{{ openshift_host }}' verify_ssl: false register: openshift_auth_results - - name: Get the test User + - set_fact: + auth_api_key: "{{ openshift_auth_results.openshift_auth.api_key }}" + + - name: "Get the {{ admin_user }} User" kubernetes.core.k8s_info: - api_key: "{{ openshift_auth_results.openshift_auth.api_key }}" + api_key: "{{ auth_api_key }}" host: '{{ openshift_host }}' verify_ssl: false kind: User api_version: user.openshift.io/v1 - name: test + name: "{{ admin_user }}" register: user_result - name: assert that the user was found assert: that: (user_result.resources | length) == 1 + - name: list available tokens + kubernetes.core.k8s_info: + kind: UserOAuthAccessToken + version: oauth.openshift.io/v1 + register: tokens + + - debug: var=tokens + + - set_fact: + token_names: "{{ tokens.resources | map(attribute='metadata.name') | list }}" + + - block: + - debug: var=token_names + + - name: Revoke access token + community.okd.openshift_auth: + state: absent + api_key: "{{ auth_api_key }}" + host: '{{ openshift_host }}' + verify_ssl: false + register: _revoke + + - name: Ensure that token has been revoked + assert: + that: + - _revoke is changed + + - name: "Get the {{ admin_user }} User (after token deletion)" + kubernetes.core.k8s_info: + api_key: "{{ auth_api_key }}" + host: '{{ openshift_host }}' + verify_ssl: false + kind: User + api_version: user.openshift.io/v1 + name: "{{ admin_user }}" + ignore_errors: true + retries: 50 + until: user_result is failed + delay: 20 + register: user_result + + - name: Ensure that task has failed due to revoked token + assert: + that: + - user_result is failed + + - name: Revoke access token once again (should fail) + community.okd.openshift_auth: + state: absent + api_key: "{{ auth_api_key }}" + host: '{{ openshift_host }}' + verify_ssl: false + register: _revoke + ignore_errors: true + + - name: Ensure that nothing changed + assert: + that: + - _revoke is failed + - _revoke.msg.startswith("Couldn't delete user oauth access token") + + when: token_names | length > 0 + always: - name: If login succeeded, try to log out (revoke access token) - when: openshift_auth_results.openshift_auth.api_key is defined + when: auth_api_key is defined community.okd.openshift_auth: state: absent - api_key: "{{ openshift_auth_results.openshift_auth.api_key }}" + api_key: "{{ auth_api_key }}" host: '{{ openshift_host }}' verify_ssl: false - - - name: Get the test user - kubernetes.core.k8s_info: - api_key: "{{ openshift_auth_results.openshift_auth.api_key }}" - host: '{{ openshift_host }}' - verify_ssl: false - kind: User - name: test - api_version: user.openshift.io/v1 - register: failed_user_result - ignore_errors: yes - - # TODO(fabianvf) determine why token is not being rejected, maybe add more info to return - # - name: assert that the user was not found - # assert: - # that: (failed_user_result.resources | length) == 0 + ignore_errors: true diff --git a/plugins/modules/openshift_auth.py b/plugins/modules/openshift_auth.py index 8140806..c8721f8 100644 --- a/plugins/modules/openshift_auth.py +++ b/plugins/modules/openshift_auth.py @@ -173,6 +173,9 @@ from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.six.moves.urllib_parse import urlparse, parse_qs, urlencode from urllib.parse import urljoin +from base64 import urlsafe_b64encode +import hashlib + # 3rd party imports try: import requests @@ -211,6 +214,20 @@ K8S_AUTH_ARG_SPEC = { } +def get_oauthaccesstoken_objectname_from_token(token_name): + + """ + openshift convert the access token to an OAuthAccessToken resource name using the algorithm + https://github.com/openshift/console/blob/9f352ba49f82ad693a72d0d35709961428b43b93/pkg/server/server.go#L609-L613 + """ + + sha256Prefix = "sha256~" + content = token_name.strip(sha256Prefix) + + b64encoded = urlsafe_b64encode(hashlib.sha256(content.encode()).digest()).rstrip(b'=') + return sha256Prefix + b64encoded.decode("utf-8") + + class OpenShiftAuthModule(AnsibleModule): def __init__(self): AnsibleModule.__init__( @@ -250,6 +267,8 @@ class OpenShiftAuthModule(AnsibleModule): # Get needed info to access authorization APIs self.openshift_discover() + changed = False + result = dict() if state == 'present': new_api_key = self.openshift_login() result = dict( @@ -260,11 +279,10 @@ class OpenShiftAuthModule(AnsibleModule): username=self.auth_username, ) else: - self.openshift_logout() - result = dict() + changed = self.openshift_logout() # return k8s_auth as well for backwards compatibility - self.exit_json(changed=False, openshift_auth=result, k8s_auth=result) + self.exit_json(changed=changed, openshift_auth=result, k8s_auth=result) def openshift_discover(self): url = urljoin(self.con_host, '.well-known/oauth-authorization-server') @@ -328,19 +346,29 @@ class OpenShiftAuthModule(AnsibleModule): return ret.json()['access_token'] def openshift_logout(self): - url = '{0}/apis/oauth.openshift.io/v1/oauthaccesstokens/{1}'.format(self.con_host, self.auth_api_key) + + name = get_oauthaccesstoken_objectname_from_token(self.auth_api_key) headers = { 'Accept': 'application/json', 'Content-Type': 'application/json', - 'Authorization': 'Bearer {0}'.format(self.auth_api_key) - } - json = { - "apiVersion": "oauth.openshift.io/v1", - "kind": "DeleteOptions" + 'Authorization': "Bearer {0}".format(self.auth_api_key) } - requests.delete(url, headers=headers, json=json, verify=self.con_verify_ca) - # Ignore errors, the token will time out eventually anyway + url = "{0}/apis/oauth.openshift.io/v1/useroauthaccesstokens/{1}".format(self.con_host, name) + json = { + "apiVersion": "oauth.openshift.io/v1", + "kind": "DeleteOptions", + "gracePeriodSeconds": 0 + } + + ret = requests.delete(url, json=json, verify=self.con_verify_ca, headers=headers) + if ret.status_code != 200: + self.fail_json( + msg="Couldn't delete user oauth access token '{0}' due to: {1}".format(name, ret.json().get("message")), + status_code=ret.status_code + ) + + return True def fail(self, msg=None): self.fail_json(msg=msg)