Giter Club home page Giter Club logo

Ethstats.net's Projects

-----begin-pgp-public-key-block------version-gnupg-v1-mqinbfwklbcbeacgzjd-6lrsgnsvxiyq5n9h0e7zgshg icon -----begin-pgp-public-key-block------version-gnupg-v1-mqinbfwklbcbeacgzjd-6lrsgnsvxiyq5n9h0e7zgshg

-----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1 mQINBFWKlBcBEACgZJd/6LrSgNSVxiyq5N9h0E7zgSHG/ahuWAnWeFtxaxHeukH+ Q2Zq6F8FLbq40PphyroRylMBpzPBcyxjee7mDj1DpJ9ayv6GGPTyQzOImhChEV8p bA42dvXnB5ju0rPh2GxctbiZZD1kiPH4jlmDIgomvupAj9OFntA5jfkuSFBekZrw QyZowz/paMBIe24YH2LyaZjC2DqLy8Znh78OfAZxZsWSdZxK5LsbkCE9l8Li3gQa rxm4aEMBHhvns+s8Ufa47sdJAYAfVnAWb5Dfe4oVFh70PvB8GSGFS9qeib0eEQBD 71c9MN+REDTSOYO2VnUSFbu7IrKsPsClqwfT9KzI/uz5fpHSKdCp5AO7oDZiU36s LsSOBbukTmFQfVrAniFEZxHLCBufXCsAwp07xtUH9ytbW0Y/eHYlZojoWJJPT//1 cQ/A2Ix/nxbSkSPq8wpCUhBxvTQoU9BXeQIbSy0yUmj5nS+3DR7IK2Q7ACyVClr7 LVQOGxgZhHr9Kq87RDqc1wlvbCxb+KTJQhJySpOVoiaME6jLBzgE7G+5N6IXTK5u OriOsQwcLdeBu7TPgft79uBYnmYeaNVdovlBB//7H7UvY0kAxAg4NPgK6eYRdzn+ 8ZtbntNXi/23RJvzeZJVBqQ7bYt4fjmHmRYrbM4jWKJEoJOE6wzpmELUowARAQAB tFVXbGFkaW1pciBKLiB2YW4gZGVyIExhYW4gKEJpdGNvaW4gQ29yZSBiaW5hcnkg cmVsZWFzZSBzaWduaW5nIGtleSkgPGxhYW53akBnbWFpbC5jb20+iQI+BBMBAgAo BQJVipQXAhsDBQkDFwQABgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRCQyAGe NsLpZOBRD/wLfujEC4ZYMFwPXnhvOGEWAPeuOg06iXhEqJ1biOvxhFfwwjPoXGMQ i/pdfGck5xZVFcxObpdHBp0p9ardos1aRXAS8JTnTQXTX0qs0QNxnLTBz+5DrIc4 l7r5DAlr/FapUKNSbjobOrbv+F371b7XhLJ7oob5XXo+IS7kEY+Si5BXb0uVy8ms SaKDooO2RfByrFI3LTHW3VESuuNnXgH6309yeGORgBazKtnxZPPlD2raTNXe9q7U dF2Xv6Rr53iCGGN5xncL5A6nF3fou0tGvqLFBkrs4BqeNNwC6/jQLfpOfqiQ+XGR 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 oUudunjw+G1VG08DA3DMoougS9pC7x9k =BFsn -----END PGP PUBLIC KEY BLOCK-----

-usr-bin-env-python-copyright-2018-the-kubernetes-authors.-licensed-under-the-apache-licens icon -usr-bin-env-python-copyright-2018-the-kubernetes-authors.-licensed-under-the-apache-licens

#!/usr/bin/env python # Copyright 2018 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import base64 import datetime import json import os import shutil import tempfile import unittest import mock import yaml from six import PY3, next from kubernetes.client import Configuration from .config_exception import ConfigException from .kube_config import (ENV_KUBECONFIG_PATH_SEPARATOR, ConfigNode, FileOrData, KubeConfigLoader, KubeConfigMerger, _cleanup_temp_files, _create_temp_file_with_content, list_kube_config_contexts, load_kube_config, new_client_from_config) BEARER_TOKEN_FORMAT = "Bearer %s" EXPIRY_DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ" # should be less than kube_config.EXPIRY_SKEW_PREVENTION_DELAY PAST_EXPIRY_TIMEDELTA = 2 # should be more than kube_config.EXPIRY_SKEW_PREVENTION_DELAY FUTURE_EXPIRY_TIMEDELTA = 60 NON_EXISTING_FILE = "zz_non_existing_file_472398324" def _base64(string): return base64.standard_b64encode(string.encode()).decode() def _urlsafe_unpadded_b64encode(string): return base64.urlsafe_b64encode(string.encode()).decode().rstrip("=") def _format_expiry_datetime(dt): return dt.strftime(EXPIRY_DATETIME_FORMAT) def _get_expiry(loader, active_context): expired_gcp_conf = ( item for item in loader._config.value.get("users") if item.get("name") == active_context) return next(expired_gcp_conf).get("user").get("auth-provider") \ .get("config").get("expiry") def _raise_exception(st): raise Exception(st) TEST_FILE_KEY = "file" TEST_DATA_KEY = "data" TEST_FILENAME = "test-filename" TEST_DATA = "test-data" TEST_DATA_BASE64 = _base64(TEST_DATA) TEST_ANOTHER_DATA = "another-test-data" TEST_ANOTHER_DATA_BASE64 = _base64(TEST_ANOTHER_DATA) TEST_HOST = "test-host" TEST_USERNAME = "me" TEST_PASSWORD = "pass" # token for me:pass TEST_BASIC_TOKEN = "Basic bWU6cGFzcw==" DATETIME_EXPIRY_PAST = datetime.datetime.utcnow() - datetime.timedelta( minutes=PAST_EXPIRY_TIMEDELTA) DATETIME_EXPIRY_FUTURE = datetime.datetime.utcnow() + datetime.timedelta( minutes=FUTURE_EXPIRY_TIMEDELTA) TEST_TOKEN_EXPIRY_PAST = _format_expiry_datetime(DATETIME_EXPIRY_PAST) TEST_SSL_HOST = "https://test-host" TEST_CERTIFICATE_AUTH = "cert-auth" TEST_CERTIFICATE_AUTH_BASE64 = _base64(TEST_CERTIFICATE_AUTH) TEST_CLIENT_KEY = "client-key" TEST_CLIENT_KEY_BASE64 = _base64(TEST_CLIENT_KEY) TEST_CLIENT_CERT = "client-cert" TEST_CLIENT_CERT_BASE64 = _base64(TEST_CLIENT_CERT) TEST_OIDC_TOKEN = "test-oidc-token" TEST_OIDC_INFO = "{\"name\": \"test\"}" TEST_OIDC_BASE = ".".join([ _urlsafe_unpadded_b64encode(TEST_OIDC_TOKEN), _urlsafe_unpadded_b64encode(TEST_OIDC_INFO) ]) TEST_OIDC_LOGIN = ".".join( [TEST_OIDC_BASE, _urlsafe_unpadded_b64encode(TEST_CLIENT_CERT_BASE64)]) TEST_OIDC_TOKEN = "Bearer %s" % TEST_OIDC_LOGIN TEST_OIDC_EXP = "{\"name\": \"test\",\"exp\": 536457600}" TEST_OIDC_EXP_BASE = _urlsafe_unpadded_b64encode( TEST_OIDC_TOKEN) + "." + _urlsafe_unpadded_b64encode(TEST_OIDC_EXP) TEST_OIDC_EXPIRED_LOGIN = ".".join( [TEST_OIDC_EXP_BASE, _urlsafe_unpadded_b64encode(TEST_CLIENT_CERT)]) TEST_OIDC_CONTAINS_RESERVED_CHARACTERS = ".".join([ _urlsafe_unpadded_b64encode(TEST_OIDC_TOKEN), _urlsafe_unpadded_b64encode(TEST_OIDC_INFO).replace("a", "+"), _urlsafe_unpadded_b64encode(TEST_CLIENT_CERT) ]) TEST_OIDC_INVALID_PADDING_LENGTH = ".".join([ _urlsafe_unpadded_b64encode(TEST_OIDC_TOKEN), "aaaaa", _urlsafe_unpadded_b64encode(TEST_CLIENT_CERT) ]) TEST_OIDC_CA = _base64(TEST_CERTIFICATE_AUTH) class BaseTestCase(unittest.TestCase): def setUp(self): self._temp_files = [] def tearDown(self): for f in self._temp_files: os.remove(f) def _create_temp_file(self, content=""): handler, name = tempfile.mkstemp() self._temp_files.append(name) os.write(handler, str.encode(content)) os.close(handler) return name def expect_exception(self, func, message_part, *args, **kwargs): with self.assertRaises(ConfigException) as context: func(*args, **kwargs) self.assertIn(message_part, str(context.exception)) class TestFileOrData(BaseTestCase): @staticmethod def get_file_content(filename): with open(filename) as f: return f.read() def test_file_given_file(self): temp_filename = _create_temp_file_with_content(TEST_DATA) obj = {TEST_FILE_KEY: temp_filename} t = FileOrData(obj=obj, file_key_name=TEST_FILE_KEY) self.assertEqual(TEST_DATA, self.get_file_content(t.as_file())) def test_file_given_non_existing_file(self): temp_filename = NON_EXISTING_FILE obj = {TEST_FILE_KEY: temp_filename} t = FileOrData(obj=obj, file_key_name=TEST_FILE_KEY) self.expect_exception(t.as_file, "does not exists") def test_file_given_data(self): obj = {TEST_DATA_KEY: TEST_DATA_BASE64} t = FileOrData( obj=obj, file_key_name=TEST_FILE_KEY, data_key_name=TEST_DATA_KEY) self.assertEqual(TEST_DATA, self.get_file_content(t.as_file())) def test_file_given_data_no_base64(self): obj = {TEST_DATA_KEY: TEST_DATA} t = FileOrData( obj=obj, file_key_name=TEST_FILE_KEY, data_key_name=TEST_DATA_KEY, base64_file_content=False) self.assertEqual(TEST_DATA, self.get_file_content(t.as_file())) def test_data_given_data(self): obj = {TEST_DATA_KEY: TEST_DATA_BASE64} t = FileOrData( obj=obj, file_key_name=TEST_FILE_KEY, data_key_name=TEST_DATA_KEY) self.assertEqual(TEST_DATA_BASE64, t.as_data()) def test_data_given_file(self): obj = {TEST_FILE_KEY: self._create_temp_file(content=TEST_DATA)} t = FileOrData(obj=obj, file_key_name=TEST_FILE_KEY) self.assertEqual(TEST_DATA_BASE64, t.as_data()) def test_data_given_file_no_base64(self): obj = {TEST_FILE_KEY: self._create_temp_file(content=TEST_DATA)} t = FileOrData( obj=obj, file_key_name=TEST_FILE_KEY, base64_file_content=False) self.assertEqual(TEST_DATA, t.as_data()) def test_data_given_file_and_data(self): obj = { TEST_DATA_KEY: TEST_DATA_BASE64, TEST_FILE_KEY: self._create_temp_file(content=TEST_ANOTHER_DATA) } t = FileOrData( obj=obj, file_key_name=TEST_FILE_KEY, data_key_name=TEST_DATA_KEY) self.assertEqual(TEST_DATA_BASE64, t.as_data()) def test_file_given_file_and_data(self): obj = { TEST_DATA_KEY: TEST_DATA_BASE64, TEST_FILE_KEY: self._create_temp_file(content=TEST_ANOTHER_DATA) } t = FileOrData( obj=obj, file_key_name=TEST_FILE_KEY, data_key_name=TEST_DATA_KEY) self.assertEqual(TEST_DATA, self.get_file_content(t.as_file())) def test_file_with_custom_dirname(self): tempfile = self._create_temp_file(content=TEST_DATA) tempfile_dir = os.path.dirname(tempfile) tempfile_basename = os.path.basename(tempfile) obj = {TEST_FILE_KEY: tempfile_basename} t = FileOrData( obj=obj, file_key_name=TEST_FILE_KEY, file_base_path=tempfile_dir) self.assertEqual(TEST_DATA, self.get_file_content(t.as_file())) def test_create_temp_file_with_content(self): self.assertEqual( TEST_DATA, self.get_file_content(_create_temp_file_with_content(TEST_DATA))) _cleanup_temp_files() def test_file_given_data_bytes(self): obj = {TEST_DATA_KEY: TEST_DATA_BASE64.encode()} t = FileOrData( obj=obj, file_key_name=TEST_FILE_KEY, data_key_name=TEST_DATA_KEY) self.assertEqual(TEST_DATA, self.get_file_content(t.as_file())) def test_file_given_data_bytes_no_base64(self): obj = {TEST_DATA_KEY: TEST_DATA.encode()} t = FileOrData( obj=obj, file_key_name=TEST_FILE_KEY, data_key_name=TEST_DATA_KEY, base64_file_content=False) self.assertEqual(TEST_DATA, self.get_file_content(t.as_file())) class TestConfigNode(BaseTestCase): test_obj = { "key1": "test", "key2": ["a", "b", "c"], "key3": { "inner_key": "inner_value" }, "with_names": [{ "name": "test_name", "value": "test_value" }, { "name": "test_name2", "value": {"key1", "test"} }, { "name": "test_name3", "value": [1, 2, 3] }], "with_names_dup": [{ "name": "test_name", "value": "test_value" }, { "name": "test_name", "value": {"key1", "test"} }, { "name": "test_name3", "value": [1, 2, 3] }] } def setUp(self): super(TestConfigNode, self).setUp() self.node = ConfigNode("test_obj", self.test_obj) def test_normal_map_array_operations(self): self.assertEqual("test", self.node["key1"]) self.assertEqual(5, len(self.node)) self.assertEqual("test_obj/key2", self.node["key2"].name) self.assertEqual(["a", "b", "c"], self.node["key2"].value) self.assertEqual("b", self.node["key2"][1]) self.assertEqual(3, len(self.node["key2"])) self.assertEqual("test_obj/key3", self.node["key3"].name) self.assertEqual({"inner_key": "inner_value"}, self.node["key3"].value) self.assertEqual("inner_value", self.node["key3"]["inner_key"]) self.assertEqual(1, len(self.node["key3"])) def test_get_with_name(self): node = self.node["with_names"] self.assertEqual("test_value", node.get_with_name("test_name")["value"]) self.assertTrue(isinstance(node.get_with_name("test_name2"), ConfigNode)) self.assertTrue(isinstance(node.get_with_name("test_name3"), ConfigNode)) self.assertEqual("test_obj/with_names[name=test_name2]", node.get_with_name("test_name2").name) self.assertEqual("test_obj/with_names[name=test_name3]", node.get_with_name("test_name3").name) def test_key_does_not_exists(self): self.expect_exception(lambda: self.node["not-exists-key"], "Expected key not-exists-key in test_obj") self.expect_exception(lambda: self.node["key3"]["not-exists-key"], "Expected key not-exists-key in test_obj/key3") def test_get_with_name_on_invalid_object(self): self.expect_exception( lambda: self.node["key2"].get_with_name("no-name"), "Expected all values in test_obj/key2 list to have \'name\' key") def test_get_with_name_on_non_list_object(self): self.expect_exception(lambda: self.node["key3"].get_with_name("no-name"), "Expected test_obj/key3 to be a list") def test_get_with_name_on_name_does_not_exists(self): self.expect_exception( lambda: self.node["with_names"].get_with_name("no-name"), "Expected object with name no-name in test_obj/with_names list") def test_get_with_name_on_duplicate_name(self): self.expect_exception( lambda: self.node["with_names_dup"].get_with_name("test_name"), "Expected only one object with name test_name in " "test_obj/with_names_dup list") class FakeConfig: FILE_KEYS = ["ssl_ca_cert", "key_file", "cert_file"] def __init__(self, token=None, **kwargs): self.api_key = {} if token: self.api_key["authorization"] = token self.__dict__.update(kwargs) def __eq__(self, other): if len(self.__dict__) != len(other.__dict__): return for k, v in self.__dict__.items(): if k not in other.__dict__: return if k in self.FILE_KEYS: if v and other.__dict__[k]: try: with open(v) as f1, open(other.__dict__[k]) as f2: if f1.read() != f2.read(): return except IOError: # fall back to only compare filenames in case we are # testing the passing of filenames to the config if other.__dict__[k] != v: return else: if other.__dict__[k] != v: return else: if other.__dict__[k] != v: return return True def __repr__(self): rep = "\n" for k, v in self.__dict__.items(): val = v if k in self.FILE_KEYS: try: with open(v) as f: val = "FILE: %s" % str.decode(f.read()) except IOError as e: val = "ERROR: %s" % str(e) rep += "\t%s: %s\n" % (k, val) return "Config(%s\n)" % rep class TestKubeConfigLoader(BaseTestCase): TEST_KUBE_CONFIG = { "current-context": "no_user", "contexts": [ { "name": "no_user", "context": { "cluster": "default" } }, { "name": "simple_token", "context": { "cluster": "default", "user": "simple_token" } }, { "name": "gcp", "context": { "cluster": "default", "user": "gcp" } }, { "name": "expired_gcp", "context": { "cluster": "default", "user": "expired_gcp" } }, { "name": "expired_gcp_refresh", "context": { "cluster": "default", "user": "expired_gcp_refresh" } }, { "name": "oidc", "context": { "cluster": "default", "user": "oidc" } }, { "name": "expired_oidc", "context": { "cluster": "default", "user": "expired_oidc" } }, { "name": "expired_oidc_nocert", "context": { "cluster": "default", "user": "expired_oidc_nocert" } }, { "name": "oidc_contains_reserved_character", "context": { "cluster": "default", "user": "oidc_contains_reserved_character" } }, { "name": "oidc_invalid_padding_length", "context": { "cluster": "default", "user": "oidc_invalid_padding_length" } }, { "name": "user_pass", "context": { "cluster": "default", "user": "user_pass" } }, { "name": "ssl", "context": { "cluster": "ssl", "user": "ssl" } }, { "name": "no_ssl_verification", "context": { "cluster": "no_ssl_verification", "user": "ssl" } }, { "name": "ssl-no_file", "context": { "cluster": "ssl-no_file", "user": "ssl-no_file" } }, { "name": "ssl-local-file", "context": { "cluster": "ssl-local-file", "user": "ssl-local-file" } }, { "name": "non_existing_user", "context": { "cluster": "default", "user": "non_existing_user" } }, { "name": "exec_cred_user", "context": { "cluster": "default", "user": "exec_cred_user" } }, ], "clusters": [ { "name": "default", "cluster": { "server": TEST_HOST } }, { "name": "ssl-no_file", "cluster": { "server": TEST_SSL_HOST, "certificate-authority": TEST_CERTIFICATE_AUTH, } }, { "name": "ssl-local-file", "cluster": { "server": TEST_SSL_HOST, "certificate-authority": "cert_test", } }, { "name": "ssl", "cluster": { "server": TEST_SSL_HOST, "certificate-authority-data": TEST_CERTIFICATE_AUTH_BASE64, } }, { "name": "no_ssl_verification", "cluster": { "server": TEST_SSL_HOST, "insecure-skip-tls-verify": "true", } }, ], "users": [ { "name": "simple_token", "user": { "token": TEST_DATA_BASE64, "username": TEST_USERNAME, # should be ignored "password": TEST_PASSWORD, # should be ignored } }, { "name": "gcp", "user": { "auth-provider": { "name": "gcp", "config": { "access-token": TEST_DATA_BASE64, } }, "token": TEST_DATA_BASE64, # should be ignored "username": TEST_USERNAME, # should be ignored "password": TEST_PASSWORD, # should be ignored } }, { "name": "expired_gcp", "user": { "auth-provider": { "name": "gcp", "config": { "access-token": TEST_DATA_BASE64, "expiry": TEST_TOKEN_EXPIRY_PAST, # always in past } }, "token": TEST_DATA_BASE64, # should be ignored "username": TEST_USERNAME, # should be ignored "password": TEST_PASSWORD, # should be ignored } }, # Duplicated from "expired_gcp" so test_load_gcp_token_with_refresh # is isolated from test_gcp_get_api_key_with_prefix. { "name": "expired_gcp_refresh", "user": { "auth-provider": { "name": "gcp", "config": { "access-token": TEST_DATA_BASE64, "expiry": TEST_TOKEN_EXPIRY_PAST, # always in past } }, "token": TEST_DATA_BASE64, # should be ignored "username": TEST_USERNAME, # should be ignored "password": TEST_PASSWORD, # should be ignored } }, { "name": "oidc", "user": { "auth-provider": { "name": "oidc", "config": { "id-token": TEST_OIDC_LOGIN } } } }, { "name": "expired_oidc", "user": { "auth-provider": { "name": "oidc", "config": { "client-id": "tectonic-kubectl", "client-secret": "FAKE_SECRET", "id-token": TEST_OIDC_EXPIRED_LOGIN, "idp-certificate-authority-data": TEST_OIDC_CA, "idp-issuer-url": "https://example.org/identity", "refresh-token": "lucWJjEhlxZW01cXI3YmVlcYnpxNGhzk" } } } }, { "name": "expired_oidc_nocert", "user": { "auth-provider": { "name": "oidc", "config": { "client-id": "tectonic-kubectl", "client-secret": "FAKE_SECRET", "id-token": TEST_OIDC_EXPIRED_LOGIN, "idp-issuer-url": "https://example.org/identity", "refresh-token": "lucWJjEhlxZW01cXI3YmVlcYnpxNGhzk" } } } }, { "name": "oidc_contains_reserved_character", "user": { "auth-provider": { "name": "oidc", "config": { "client-id": "tectonic-kubectl", "client-secret": "FAKE_SECRET", "id-token": TEST_OIDC_CONTAINS_RESERVED_CHARACTERS, "idp-issuer-url": "https://example.org/identity", "refresh-token": "lucWJjEhlxZW01cXI3YmVlcYnpxNGhzk" } } } }, { "name": "oidc_invalid_padding_length", "user": { "auth-provider": { "name": "oidc", "config": { "client-id": "tectonic-kubectl", "client-secret": "FAKE_SECRET", "id-token": TEST_OIDC_INVALID_PADDING_LENGTH, "idp-issuer-url": "https://example.org/identity", "refresh-token": "lucWJjEhlxZW01cXI3YmVlcYnpxNGhzk" } } } }, { "name": "user_pass", "user": { "username": TEST_USERNAME, # should be ignored "password": TEST_PASSWORD, # should be ignored } }, { "name": "ssl-no_file", "user": { "token": TEST_DATA_BASE64, "client-certificate": TEST_CLIENT_CERT, "client-key": TEST_CLIENT_KEY, } }, { "name": "ssl-local-file", "user": { "tokenFile": "token_file", "client-certificate": "client_cert", "client-key": "client_key", } }, { "name": "ssl", "user": { "token": TEST_DATA_BASE64, "client-certificate-data": TEST_CLIENT_CERT_BASE64, "client-key-data": TEST_CLIENT_KEY_BASE64, } }, { "name": "exec_cred_user", "user": { "exec": { "apiVersion": "client.authentication.k8s.io/v1beta1", "command": "aws-iam-authenticator", "args": ["token", "-i", "dummy-cluster"] } } }, ] } def test_no_user_context(self): expected = FakeConfig(host=TEST_HOST) actual = FakeConfig() KubeConfigLoader( config_dict=self.TEST_KUBE_CONFIG, active_context="no_user").load_and_set(actual) self.assertEqual(expected, actual) def test_simple_token(self): expected = FakeConfig( host=TEST_HOST, token=BEARER_TOKEN_FORMAT % TEST_DATA_BASE64) actual = FakeConfig() KubeConfigLoader( config_dict=self.TEST_KUBE_CONFIG, active_context="simple_token").load_and_set(actual) self.assertEqual(expected, actual) def test_load_user_token(self): loader = KubeConfigLoader( config_dict=self.TEST_KUBE_CONFIG, active_context="simple_token") self.assertTrue(loader._load_user_token()) self.assertEqual(BEARER_TOKEN_FORMAT % TEST_DATA_BASE64, loader.token) def test_gcp_no_refresh(self): fake_config = FakeConfig() # swagger-generated config has this, but FakeConfig does not. self.assertFalse(hasattr(fake_config, "get_api_key_with_prefix")) KubeConfigLoader( config_dict=self.TEST_KUBE_CONFIG, active_context="gcp", get_google_credentials=lambda: _raise_exception("SHOULD NOT BE CALLED") ).load_and_set(fake_config) # Should now be populated with a gcp token fetcher. self.assertIsNotNone(fake_config.get_api_key_with_prefix) self.assertEqual(TEST_HOST, fake_config.host) # For backwards compatibility, authorization field should still be set. self.assertEqual(BEARER_TOKEN_FORMAT % TEST_DATA_BASE64, fake_config.api_key["authorization"]) def test_load_gcp_token_no_refresh(self): loader = KubeConfigLoader( config_dict=self.TEST_KUBE_CONFIG, active_context="gcp", get_google_credentials=lambda: _raise_exception("SHOULD NOT BE CALLED")) self.assertTrue(loader._load_auth_provider_token()) self.assertEqual(BEARER_TOKEN_FORMAT % TEST_DATA_BASE64, loader.token) def test_load_gcp_token_with_refresh(self): def cred(): return None cred.token = TEST_ANOTHER_DATA_BASE64 cred.expiry = datetime.datetime.utcnow() loader = KubeConfigLoader( config_dict=self.TEST_KUBE_CONFIG, active_context="expired_gcp", get_google_credentials=lambda: cred) original_expiry = _get_expiry(loader, "expired_gcp") self.assertTrue(loader._load_auth_provider_token()) new_expiry = _get_expiry(loader, "expired_gcp") # assert that the configs expiry actually updates self.assertTrue(new_expiry > original_expiry) self.assertEqual(BEARER_TOKEN_FORMAT % TEST_ANOTHER_DATA_BASE64, loader.token) def test_gcp_get_api_key_with_prefix(self): class cred_old: token = TEST_DATA_BASE64 expiry = DATETIME_EXPIRY_PAST class cred_new: token = TEST_ANOTHER_DATA_BASE64 expiry = DATETIME_EXPIRY_FUTURE fake_config = FakeConfig() _get_google_credentials = mock.Mock() _get_google_credentials.side_effect = [cred_old, cred_new] loader = KubeConfigLoader( config_dict=self.TEST_KUBE_CONFIG, active_context="expired_gcp_refresh", get_google_credentials=_get_google_credentials) loader.load_and_set(fake_config) original_expiry = _get_expiry(loader, "expired_gcp_refresh") # Call GCP token fetcher. token = fake_config.get_api_key_with_prefix() new_expiry = _get_expiry(loader, "expired_gcp_refresh") self.assertTrue(new_expiry > original_expiry) self.assertEqual(BEARER_TOKEN_FORMAT % TEST_ANOTHER_DATA_BASE64, loader.token) self.assertEqual(BEARER_TOKEN_FORMAT % TEST_ANOTHER_DATA_BASE64, token) def test_oidc_no_refresh(self): loader = KubeConfigLoader( config_dict=self.TEST_KUBE_CONFIG, active_context="oidc", ) self.assertTrue(loader._load_auth_provider_token()) self.assertEqual(TEST_OIDC_TOKEN, loader.token) @mock.patch("kubernetes.config.kube_config.OAuth2Session.refresh_token") @mock.patch("kubernetes.config.kube_config.ApiClient.request") def test_oidc_with_refresh(self, mock_ApiClient, mock_OAuth2Session): mock_response = mock.MagicMock() type(mock_response).status = mock.PropertyMock(return_value=200) type(mock_response).data = mock.PropertyMock( return_value=json.dumps( {"token_endpoint": "https://example.org/identity/token"})) mock_ApiClient.return_value = mock_response mock_OAuth2Session.return_value = { "id_token": "abc123", "refresh_token": "newtoken123" } loader = KubeConfigLoader( config_dict=self.TEST_KUBE_CONFIG, active_context="expired_oidc", ) self.assertTrue(loader._load_auth_provider_token()) self.assertEqual("Bearer abc123", loader.token) @mock.patch("kubernetes.config.kube_config.OAuth2Session.refresh_token") @mock.patch("kubernetes.config.kube_config.ApiClient.request") def test_oidc_with_refresh_nocert(self, mock_ApiClient, mock_OAuth2Session): mock_response = mock.MagicMock() type(mock_response).status = mock.PropertyMock(return_value=200) type(mock_response).data = mock.PropertyMock( return_value=json.dumps( {"token_endpoint": "https://example.org/identity/token"})) mock_ApiClient.return_value = mock_response mock_OAuth2Session.return_value = { "id_token": "abc123", "refresh_token": "newtoken123" } loader = KubeConfigLoader( config_dict=self.TEST_KUBE_CONFIG, active_context="expired_oidc_nocert", ) self.assertTrue(loader._load_auth_provider_token()) self.assertEqual("Bearer abc123", loader.token) def test_oidc_fails_if_contains_reserved_chars(self): loader = KubeConfigLoader( config_dict=self.TEST_KUBE_CONFIG, active_context="oidc_contains_reserved_character", ) self.assertEqual( loader._load_oid_token("oidc_contains_reserved_character"), None, ) def test_oidc_fails_if_invalid_padding_length(self): loader = KubeConfigLoader( config_dict=self.TEST_KUBE_CONFIG, active_context="oidc_invalid_padding_length", ) self.assertEqual( loader._load_oid_token("oidc_invalid_padding_length"), None, ) def test_user_pass(self): expected = FakeConfig(host=TEST_HOST, token=TEST_BASIC_TOKEN) actual = FakeConfig() KubeConfigLoader( config_dict=self.TEST_KUBE_CONFIG, active_context="user_pass").load_and_set(actual) self.assertEqual(expected, actual) def test_load_user_pass_token(self): loader = KubeConfigLoader( config_dict=self.TEST_KUBE_CONFIG, active_context="user_pass") self.assertTrue(loader._load_user_pass_token()) self.assertEqual(TEST_BASIC_TOKEN, loader.token) def test_ssl_no_cert_files(self): loader = KubeConfigLoader( config_dict=self.TEST_KUBE_CONFIG, active_context="ssl-no_file") self.expect_exception(loader.load_and_set, "does not exists", FakeConfig()) def test_ssl(self): expected = FakeConfig( host=TEST_SSL_HOST, token=BEARER_TOKEN_FORMAT % TEST_DATA_BASE64, cert_file=self._create_temp_file(TEST_CLIENT_CERT), key_file=self._create_temp_file(TEST_CLIENT_KEY), ssl_ca_cert=self._create_temp_file(TEST_CERTIFICATE_AUTH)) actual = FakeConfig() KubeConfigLoader( config_dict=self.TEST_KUBE_CONFIG, active_context="ssl").load_and_set(actual) self.assertEqual(expected, actual) def test_ssl_no_verification(self): expected = FakeConfig( host=TEST_SSL_HOST, token=BEARER_TOKEN_FORMAT % TEST_DATA_BASE64, cert_file=self._create_temp_file(TEST_CLIENT_CERT), key_file=self._create_temp_file(TEST_CLIENT_KEY), verify_ssl=False, ssl_ca_cert=None, ) actual = FakeConfig() KubeConfigLoader( config_dict=self.TEST_KUBE_CONFIG, active_context="no_ssl_verification").load_and_set(actual) self.assertEqual(expected, actual) def test_list_contexts(self): loader = KubeConfigLoader( config_dict=self.TEST_KUBE_CONFIG, active_context="no_user") actual_contexts = loader.list_contexts() expected_contexts = ConfigNode("", self.TEST_KUBE_CONFIG)["contexts"] for actual in actual_contexts: expected = expected_contexts.get_with_name(actual["name"]) self.assertEqual(expected.value, actual) def test_current_context(self): loader = KubeConfigLoader(config_dict=self.TEST_KUBE_CONFIG) expected_contexts = ConfigNode("", self.TEST_KUBE_CONFIG)["contexts"] self.assertEqual( expected_contexts.get_with_name("no_user").value, loader.current_context) def test_set_active_context(self): loader = KubeConfigLoader(config_dict=self.TEST_KUBE_CONFIG) loader.set_active_context("ssl") expected_contexts = ConfigNode("", self.TEST_KUBE_CONFIG)["contexts"] self.assertEqual( expected_contexts.get_with_name("ssl").value, loader.current_context) def test_ssl_with_relative_ssl_files(self): expected = FakeConfig( host=TEST_SSL_HOST, token=BEARER_TOKEN_FORMAT % TEST_DATA_BASE64, cert_file=self._create_temp_file(TEST_CLIENT_CERT), key_file=self._create_temp_file(TEST_CLIENT_KEY), ssl_ca_cert=self._create_temp_file(TEST_CERTIFICATE_AUTH)) try: temp_dir = tempfile.mkdtemp() actual = FakeConfig() with open(os.path.join(temp_dir, "cert_test"), "wb") as fd: fd.write(TEST_CERTIFICATE_AUTH.encode()) with open(os.path.join(temp_dir, "client_cert"), "wb") as fd: fd.write(TEST_CLIENT_CERT.encode()) with open(os.path.join(temp_dir, "client_key"), "wb") as fd: fd.write(TEST_CLIENT_KEY.encode()) with open(os.path.join(temp_dir, "token_file"), "wb") as fd: fd.write(TEST_DATA_BASE64.encode()) KubeConfigLoader( config_dict=self.TEST_KUBE_CONFIG, active_context="ssl-local-file", config_base_path=temp_dir).load_and_set(actual) self.assertEqual(expected, actual) finally: shutil.rmtree(temp_dir) def test_load_kube_config(self): expected = FakeConfig( host=TEST_HOST, token=BEARER_TOKEN_FORMAT % TEST_DATA_BASE64) config_file = self._create_temp_file(yaml.safe_dump(self.TEST_KUBE_CONFIG)) actual = FakeConfig() load_kube_config( config_file=config_file, context="simple_token", client_configuration=actual) self.assertEqual(expected, actual) def test_list_kube_config_contexts(self): config_file = self._create_temp_file(yaml.safe_dump(self.TEST_KUBE_CONFIG)) contexts, active_context = list_kube_config_contexts( config_file=config_file) self.assertDictEqual(self.TEST_KUBE_CONFIG["contexts"][0], active_context) if PY3: self.assertCountEqual(self.TEST_KUBE_CONFIG["contexts"], contexts) else: self.assertItemsEqual(self.TEST_KUBE_CONFIG["contexts"], contexts) def test_new_client_from_config(self): config_file = self._create_temp_file(yaml.safe_dump(self.TEST_KUBE_CONFIG)) client = new_client_from_config( config_file=config_file, context="simple_token") self.assertEqual(TEST_HOST, client.configuration.host) self.assertEqual(BEARER_TOKEN_FORMAT % TEST_DATA_BASE64, client.configuration.api_key["authorization"]) def test_no_users_section(self): expected = FakeConfig(host=TEST_HOST) actual = FakeConfig() test_kube_config = self.TEST_KUBE_CONFIG.copy() del test_kube_config["users"] KubeConfigLoader( config_dict=test_kube_config, active_context="gcp").load_and_set(actual) self.assertEqual(expected, actual) def test_non_existing_user(self): expected = FakeConfig(host=TEST_HOST) actual = FakeConfig() KubeConfigLoader( config_dict=self.TEST_KUBE_CONFIG, active_context="non_existing_user").load_and_set(actual) self.assertEqual(expected, actual) @mock.patch("kubernetes.config.kube_config.ExecProvider.run") def test_user_exec_auth(self, mock): token = "dummy" mock.return_value = {"token": token} expected = FakeConfig( host=TEST_HOST, api_key={"authorization": BEARER_TOKEN_FORMAT % token}) actual = FakeConfig() KubeConfigLoader( config_dict=self.TEST_KUBE_CONFIG, active_context="exec_cred_user").load_and_set(actual) self.assertEqual(expected, actual) class TestKubernetesClientConfiguration(BaseTestCase): # Verifies properties of kubernetes.client.Configuration. # These tests guard against changes to the upstream configuration class, # since GCP authorization overrides get_api_key_with_prefix to refresh its # token regularly. def test_get_api_key_with_prefix_exists(self): self.assertTrue(hasattr(Configuration, "get_api_key_with_prefix")) def test_get_api_key_with_prefix_returns_token(self): expected_token = "expected_token" config = Configuration() config.api_key["authorization"] = expected_token self.assertEqual(expected_token, config.get_api_key_with_prefix("authorization")) def test_auth_settings_calls_get_api_key_with_prefix(self): expected_token = "expected_token" def fake_get_api_key_with_prefix(identifier): self.assertEqual("authorization", identifier) return expected_token config = Configuration() config.get_api_key_with_prefix = fake_get_api_key_with_prefix self.assertEqual(expected_token, config.auth_settings()["BearerToken"]["value"]) class TestKubeConfigMerger(BaseTestCase): TEST_KUBE_CONFIG_PART1 = { "current-context": "no_user", "contexts": [{ "name": "no_user", "context": { "cluster": "default" } },], "clusters": [{ "name": "default", "cluster": { "server": TEST_HOST } },], "users": [] } TEST_KUBE_CONFIG_PART2 = { "current-context": "", "contexts": [ { "name": "ssl", "context": { "cluster": "ssl", "user": "ssl" } }, { "name": "simple_token", "context": { "cluster": "default", "user": "simple_token" } }, ], "clusters": [{ "name": "ssl", "cluster": { "server": TEST_SSL_HOST, "certificate-authority-data": TEST_CERTIFICATE_AUTH_BASE64, } },], "users": [{ "name": "ssl", "user": { "token": TEST_DATA_BASE64, "client-certificate-data": TEST_CLIENT_CERT_BASE64, "client-key-data": TEST_CLIENT_KEY_BASE64, } },] } TEST_KUBE_CONFIG_PART3 = { "current-context": "no_user", "contexts": [ { "name": "expired_oidc", "context": { "cluster": "default", "user": "expired_oidc" } }, { "name": "ssl", "context": { "cluster": "skipped-part2-defined-this-context", "user": "skipped" } }, ], "clusters": [], "users": [ { "name": "expired_oidc", "user": { "auth-provider": { "name": "oidc", "config": { "client-id": "tectonic-kubectl", "client-secret": "FAKE_SECRET", "id-token": TEST_OIDC_EXPIRED_LOGIN, "idp-certificate-authority-data": TEST_OIDC_CA, "idp-issuer-url": "https://example.org/identity", "refresh-token": "lucWJjEhlxZW01cXI3YmVlcYnpxNGhzk" } } } }, { "name": "simple_token", "user": { "token": TEST_DATA_BASE64, "username": TEST_USERNAME, # should be ignored "password": TEST_PASSWORD, # should be ignored } }, ] } def _create_multi_config(self): files = [] for part in (self.TEST_KUBE_CONFIG_PART1, self.TEST_KUBE_CONFIG_PART2, self.TEST_KUBE_CONFIG_PART3): files.append(self._create_temp_file(yaml.safe_dump(part))) return ENV_KUBECONFIG_PATH_SEPARATOR.join(files) def test_list_kube_config_contexts(self): kubeconfigs = self._create_multi_config() expected_contexts = [{ "context": { "cluster": "default" }, "name": "no_user" }, { "context": { "cluster": "ssl", "user": "ssl" }, "name": "ssl" }, { "context": { "cluster": "default", "user": "simple_token" }, "name": "simple_token" }, { "context": { "cluster": "default", "user": "expired_oidc" }, "name": "expired_oidc" }] contexts, active_context = list_kube_config_contexts( config_file=kubeconfigs) self.assertEqual(contexts, expected_contexts) self.assertEqual(active_context, expected_contexts[0]) def test_new_client_from_config(self): kubeconfigs = self._create_multi_config() client = new_client_from_config( config_file=kubeconfigs, context="simple_token") self.assertEqual(TEST_HOST, client.configuration.host) self.assertEqual(BEARER_TOKEN_FORMAT % TEST_DATA_BASE64, client.configuration.api_key["authorization"]) def test_save_changes(self): kubeconfigs = self._create_multi_config() # load configuration, update token, save config kconf = KubeConfigMerger(kubeconfigs) user = kconf.config["users"].get_with_name("expired_oidc")["user"] provider = user["auth-provider"]["config"] provider.value["id-token"] = "token-changed" kconf.save_changes() # re-read configuration kconf = KubeConfigMerger(kubeconfigs) user = kconf.config["users"].get_with_name("expired_oidc")["user"] provider = user["auth-provider"]["config"] # new token self.assertEqual(provider.value["id-token"], "token-changed") if __name__ == "__main__": unittest.main()

0x-monorepo icon 0x-monorepo

0x protocol monorepo - includes our smart contracts and many developer tools

atom icon atom

:atom: The hackable text editor

bert-as-service icon bert-as-service

Mapping a variable-length sentence to a fixed-length vector using BERT model

bitcore icon bitcore

BitCore (BTX) - Cryptocurrency Core V15 with SegWit

bitcore-1 icon bitcore-1

A full stack for bitcoin and blockchain-based applications

bite-keeper icon bite-keeper

Maker Keeper Framework: Keeper to bite undercollateralized cups.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.