Esempio n. 1
0
    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()
Esempio n. 2
0
def s():
    return HttpSession(verbose=True)
Esempio n. 3
0
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()