def __init__(self, openshift_api_url, openshift_oauth_url, k8s_api_url=None, verbose=False, username=None, password=None, use_kerberos=False, kerberos_keytab=None, kerberos_principal=None, kerberos_ccache=None, client_cert=None, client_key=None, verify_ssl=True, use_auth=None, token=None, namespace=DEFAULT_NAMESPACE): self.os_api_url = openshift_api_url self.k8s_api_url = k8s_api_url self._os_oauth_url = openshift_oauth_url self.namespace = namespace self.verbose = verbose self.verify_ssl = verify_ssl self._con = HttpSession(verbose=self.verbose) self.retries_enabled = True # auth stuff self.use_kerberos = use_kerberos self.username = username self.password = password self.client_cert = client_cert self.client_key = client_key self.kerberos_keytab = kerberos_keytab self.kerberos_principal = kerberos_principal self.kerberos_ccache = kerberos_ccache self.token = token self.ca = None auth_credentials_provided = bool(use_kerberos or token or (username and password)) if use_auth is None: self.use_auth = auth_credentials_provided if not self.use_auth: # Are we running inside a pod? If so, we will have a # token available which can be used for authentication self.use_auth = self.can_use_serviceaccount_token() else: self.use_auth = use_auth if not auth_credentials_provided: # We've been told to use authentication but no # credentials have been given. See if we're running # inside a pod, and if so use the provided token. self.can_use_serviceaccount_token()
def s(): return HttpSession(verbose=True)
class Openshift(object): def __init__(self, openshift_api_url, openshift_oauth_url, k8s_api_url=None, verbose=False, username=None, password=None, use_kerberos=False, kerberos_keytab=None, kerberos_principal=None, kerberos_ccache=None, client_cert=None, client_key=None, verify_ssl=True, use_auth=None, token=None, namespace=DEFAULT_NAMESPACE): self.os_api_url = openshift_api_url self.k8s_api_url = k8s_api_url self._os_oauth_url = openshift_oauth_url self.namespace = namespace self.verbose = verbose self.verify_ssl = verify_ssl self._con = HttpSession(verbose=self.verbose) self.retries_enabled = True # auth stuff self.use_kerberos = use_kerberos self.username = username self.password = password self.client_cert = client_cert self.client_key = client_key self.kerberos_keytab = kerberos_keytab self.kerberos_principal = kerberos_principal self.kerberos_ccache = kerberos_ccache self.token = token self.ca = None auth_credentials_provided = bool(use_kerberos or token or (username and password)) if use_auth is None: self.use_auth = auth_credentials_provided if not self.use_auth: # Are we running inside a pod? If so, we will have a # token available which can be used for authentication self.use_auth = self.can_use_serviceaccount_token() else: self.use_auth = use_auth if not auth_credentials_provided: # We've been told to use authentication but no # credentials have been given. See if we're running # inside a pod, and if so use the provided token. self.can_use_serviceaccount_token() def can_use_serviceaccount_token(self): try: with open(os.path.join(SERVICEACCOUNT_SECRET, SERVICEACCOUNT_TOKEN), mode='rt') as tfp: self.token = tfp.read().rstrip() ca = os.path.join(SERVICEACCOUNT_SECRET, SERVICEACCOUNT_CACRT) if os.access(ca, os.R_OK): self.ca = ca except IOError: # No token available return False else: # We can authenticate using the supplied token logger.info("Using service account's auth token") return True @property def os_oauth_url(self): return self._os_oauth_url def _build_k8s_url(self, url, _prepend_namespace=True, **query): if _prepend_namespace: url = "namespaces/%s/%s" % (self.namespace, url) if query: url += ("?" + urlencode(query)) return urljoin(self.k8s_api_url, url) def build_url(self, api_path, api_version, url, _prepend_namespace=True, **query): if _prepend_namespace: url = "namespaces/%s/%s" % (self.namespace, url) if query: url += ("?" + urlencode(query)) url = f"{api_path}/{api_version}/{url}" return urljoin(self.os_api_url, url) def _request_args(self, with_auth=True, **kwargs): headers = kwargs.pop("headers", {}) if with_auth and self.use_auth: if self.token is None: self.get_oauth_token() if self.token: headers["Authorization"] = "Bearer %s" % self.token else: raise OsbsAuthException( "Please check your credentials. " "Token was not retrieved successfully.") # Use the client certificate both for the OAuth request and OpenShift # API requests. Certificate auth can be used as an alternative to # OAuth, however a scenario where they are used to get OAuth token is # also possible. Certificate is not sent when server does not request it. if self.client_cert or self.client_key: if self.client_cert and self.client_key: kwargs["client_cert"] = self.client_cert kwargs["client_key"] = self.client_key else: raise OsbsAuthException( "You need to provide both client certificate and key.") # Do we have a ca.crt? If so, use it if self.verify_ssl and self.ca is not None: kwargs["ca"] = self.ca return headers, kwargs def post(self, url, with_auth=True, **kwargs): headers, kwargs = self._request_args(with_auth, **kwargs) return self._con.post(url, headers=headers, verify_ssl=self.verify_ssl, retries_enabled=self.retries_enabled, **kwargs) def get(self, url, with_auth=True, **kwargs): headers, kwargs = self._request_args(with_auth, **kwargs) return self._con.get(url, headers=headers, verify_ssl=self.verify_ssl, retries_enabled=self.retries_enabled, **kwargs) def put(self, url, with_auth=True, **kwargs): headers, kwargs = self._request_args(with_auth, **kwargs) return self._con.put(url, headers=headers, verify_ssl=self.verify_ssl, retries_enabled=self.retries_enabled, **kwargs) def patch(self, url, with_auth=True, **kwargs): headers, kwargs = self._request_args(with_auth, **kwargs) return self._con.patch(url, headers=headers, verify_ssl=self.verify_ssl, retries_enabled=self.retries_enabled, **kwargs) def delete(self, url, with_auth=True, **kwargs): headers, kwargs = self._request_args(with_auth, **kwargs) return self._con.delete(url, headers=headers, verify_ssl=self.verify_ssl, retries_enabled=self.retries_enabled, **kwargs) def get_oauth_token(self): url = self.os_oauth_url + "?response_type=token&client_id=openshift-challenging-client" if self.use_auth: if self.username and self.password: logger.debug("using basic authentication") r = self.get( url, with_auth=False, allow_redirects=False, username=self.username, password=self.password, ) elif self.use_kerberos: logger.debug("using kerberos authentication") if self.kerberos_keytab: if not self.kerberos_principal: raise OsbsAuthException( "You need to provide kerberos principal along " "with the keytab path.") kerberos_ccache_init(self.kerberos_principal, self.kerberos_keytab, ccache_file=self.kerberos_ccache) r = self.get(url, with_auth=False, allow_redirects=False, kerberos_auth=True) else: logger.debug("using identity authentication") r = self.get(url, with_auth=False, allow_redirects=False) else: logger.debug( "getting token without any authentication (fingers crossed)") r = self.get(url, with_auth=False, allow_redirects=False) try: redir_url = r.headers['location'] except KeyError: logger.error( "[%s] 'Location' header is missing in response, cannot retrieve token", r.status_code) return "" parsed_url = urlparse(redir_url) fragment = parsed_url.fragment parsed_fragment = parse_qs(fragment) self.token = parsed_fragment['access_token'][0] return self.token def get_serviceaccount_tokens(self, username="******"): result = {} url = self._build_k8s_url("serviceaccounts/%s/" % username, _prepend_namespace=True) response = self.get(url) check_response(response) sa_json = response.json() if not sa_json: return {} if 'secrets' not in sa_json.keys(): logger.debug("No secrets found for service account %s", username) return {} secrets = sa_json['secrets'] for secret in secrets: if 'name' not in secret.keys(): logger.debug("Malformed secret info: missing 'name' key in %r", secret) continue secret_name = secret['name'] if 'token' not in secret_name: logger.debug("Secret %s is not a token", secret_name) continue url = self._build_k8s_url("secrets/%s/" % secret_name, _prepend_namespace=True) response = self.get(url) check_response(response) secret_json = response.json() if not secret_json: continue if 'data' not in secret_json.keys(): logger.debug("Malformed secret info: missing 'data' key in %r", json) continue secret_data = secret_json['data'] if 'token' not in secret_data.keys(): logger.debug( "Malformed secret data: missing 'token' key in %r", secret_data) continue token = secret_data['token'] # Token needs to be base64-decoded result[secret_name] = base64.b64decode(token) return result def watch_resource(self, api_path, api_version, resource_type, resource_name, **request_args): """ Watch for changes in openshift object and return it's json representation after each update to the object """ def log_and_sleep(): logger.debug("connection closed, reconnecting in %ds", WATCH_RETRY_SECS) time.sleep(WATCH_RETRY_SECS) watch_path = f"watch/namespaces/{self.namespace}/{resource_type}/{resource_name}/" watch_url = self.build_url(api_path, api_version, watch_path, _prepend_namespace=False, **request_args) get_url = self.build_url(api_path, api_version, f"{resource_type}/{resource_name}") bad_responses = 0 for _ in range(WATCH_RETRY): logger.debug("watching for updates for %s, %s", resource_type, resource_name) try: response = self.get(watch_url, stream=True, headers={'Connection': 'close'}) check_response(response) # we're already retrying, so there's no need to panic just because of a bad response except OsbsResponseException as exc: bad_responses += 1 if bad_responses > MAX_BAD_RESPONSES: raise exc else: # check_response() already logged the message, so just report that we're # sleeping and retry log_and_sleep() continue for line in response.iter_lines(): encoding = guess_json_utf(line) try: j = json.loads(line.decode(encoding)) except ValueError: logger.error("Cannot decode watch event: %s", line) continue if 'object' not in j: logger.error("Watch event has no 'object': %s", j) continue if 'type' not in j: logger.error("Watch event has no 'type': %s", j) continue # Avoid races. We've already asked the server to tell us # about changes to the object, but now ask for a fresh # copy of the object as well. This is to catch the # situation where the object changed before the call to # this method, or in between retries in this method. logger.debug("retrieving fresh version of object %s", resource_name) fresh_response = self.get(get_url) check_response(fresh_response) yield fresh_response.json() log_and_sleep()