2424import yaml
2525from six import PY3 , next
2626
27+ from kubernetes .client import Configuration
28+
2729from .config_exception import ConfigException
2830from .kube_config import (ConfigNode , FileOrData , KubeConfigLoader ,
2931 _cleanup_temp_files , _create_temp_file_with_content ,
3436
3537EXPIRY_DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
3638# should be less than kube_config.EXPIRY_SKEW_PREVENTION_DELAY
37- EXPIRY_TIMEDELTA = 2
39+ PAST_EXPIRY_TIMEDELTA = 2
40+ # should be more than kube_config.EXPIRY_SKEW_PREVENTION_DELAY
41+ FUTURE_EXPIRY_TIMEDELTA = 60
3842
3943NON_EXISTING_FILE = "zz_non_existing_file_472398324"
4044
@@ -47,9 +51,9 @@ def _format_expiry_datetime(dt):
4751 return dt .strftime (EXPIRY_DATETIME_FORMAT )
4852
4953
50- def _get_expiry (loader ):
54+ def _get_expiry (loader , active_context ):
5155 expired_gcp_conf = (item for item in loader ._config .value .get ("users" )
52- if item .get ("name" ) == "expired_gcp" )
56+ if item .get ("name" ) == active_context )
5357 return next (expired_gcp_conf ).get ("user" ).get ("auth-provider" ) \
5458 .get ("config" ).get ("expiry" )
5559
@@ -73,8 +77,11 @@ def _raise_exception(st):
7377TEST_PASSWORD = "pass"
7478# token for me:pass
7579TEST_BASIC_TOKEN = "Basic bWU6cGFzcw=="
76- TEST_TOKEN_EXPIRY = _format_expiry_datetime (
77- datetime .datetime .utcnow () - datetime .timedelta (minutes = EXPIRY_TIMEDELTA ))
80+ DATETIME_EXPIRY_PAST = datetime .datetime .utcnow (
81+ ) - datetime .timedelta (minutes = PAST_EXPIRY_TIMEDELTA )
82+ DATETIME_EXPIRY_FUTURE = datetime .datetime .utcnow (
83+ ) + datetime .timedelta (minutes = FUTURE_EXPIRY_TIMEDELTA )
84+ TEST_TOKEN_EXPIRY_PAST = _format_expiry_datetime (DATETIME_EXPIRY_PAST )
7885
7986TEST_SSL_HOST = "https://test-host"
8087TEST_CERTIFICATE_AUTH = "cert-auth"
@@ -371,6 +378,13 @@ class TestKubeConfigLoader(BaseTestCase):
371378 "user" : "expired_gcp"
372379 }
373380 },
381+ {
382+ "name" : "expired_gcp_refresh" ,
383+ "context" : {
384+ "cluster" : "default" ,
385+ "user" : "expired_gcp_refresh"
386+ }
387+ },
374388 {
375389 "name" : "oidc" ,
376390 "context" : {
@@ -509,7 +523,24 @@ class TestKubeConfigLoader(BaseTestCase):
509523 "name" : "gcp" ,
510524 "config" : {
511525 "access-token" : TEST_DATA_BASE64 ,
512- "expiry" : TEST_TOKEN_EXPIRY , # always in past
526+ "expiry" : TEST_TOKEN_EXPIRY_PAST , # always in past
527+ }
528+ },
529+ "token" : TEST_DATA_BASE64 , # should be ignored
530+ "username" : TEST_USERNAME , # should be ignored
531+ "password" : TEST_PASSWORD , # should be ignored
532+ }
533+ },
534+ # Duplicated from "expired_gcp" so test_load_gcp_token_with_refresh
535+ # is isolated from test_gcp_get_api_key_with_prefix.
536+ {
537+ "name" : "expired_gcp_refresh" ,
538+ "user" : {
539+ "auth-provider" : {
540+ "name" : "gcp" ,
541+ "config" : {
542+ "access-token" : TEST_DATA_BASE64 ,
543+ "expiry" : TEST_TOKEN_EXPIRY_PAST , # always in past
513544 }
514545 },
515546 "token" : TEST_DATA_BASE64 , # should be ignored
@@ -630,16 +661,20 @@ def test_load_user_token(self):
630661 self .assertEqual (BEARER_TOKEN_FORMAT % TEST_DATA_BASE64 , loader .token )
631662
632663 def test_gcp_no_refresh (self ):
633- expected = FakeConfig (
634- host = TEST_HOST ,
635- token = BEARER_TOKEN_FORMAT % TEST_DATA_BASE64 )
636- actual = FakeConfig ()
664+ fake_config = FakeConfig ()
665+ # swagger-generated config has this, but FakeConfig does not.
666+ self .assertFalse (hasattr (fake_config , 'get_api_key_with_prefix' ))
637667 KubeConfigLoader (
638668 config_dict = self .TEST_KUBE_CONFIG ,
639669 active_context = "gcp" ,
640670 get_google_credentials = lambda : _raise_exception (
641- "SHOULD NOT BE CALLED" )).load_and_set (actual )
642- self .assertEqual (expected , actual )
671+ "SHOULD NOT BE CALLED" )).load_and_set (fake_config )
672+ # Should now be populated with a gcp token fetcher.
673+ self .assertIsNotNone (fake_config .get_api_key_with_prefix )
674+ self .assertEqual (TEST_HOST , fake_config .host )
675+ # For backwards compatibility, authorization field should still be set.
676+ self .assertEqual (BEARER_TOKEN_FORMAT % TEST_DATA_BASE64 ,
677+ fake_config .api_key ['authorization' ])
643678
644679 def test_load_gcp_token_no_refresh (self ):
645680 loader = KubeConfigLoader (
@@ -654,20 +689,48 @@ def test_load_gcp_token_no_refresh(self):
654689 def test_load_gcp_token_with_refresh (self ):
655690 def cred (): return None
656691 cred .token = TEST_ANOTHER_DATA_BASE64
657- cred .expiry = datetime .datetime .now ()
692+ cred .expiry = datetime .datetime .utcnow ()
658693
659694 loader = KubeConfigLoader (
660695 config_dict = self .TEST_KUBE_CONFIG ,
661696 active_context = "expired_gcp" ,
662697 get_google_credentials = lambda : cred )
663- original_expiry = _get_expiry (loader )
698+ original_expiry = _get_expiry (loader , "expired_gcp" )
664699 self .assertTrue (loader ._load_auth_provider_token ())
665- new_expiry = _get_expiry (loader )
700+ new_expiry = _get_expiry (loader , "expired_gcp" )
666701 # assert that the configs expiry actually updates
667702 self .assertTrue (new_expiry > original_expiry )
668703 self .assertEqual (BEARER_TOKEN_FORMAT % TEST_ANOTHER_DATA_BASE64 ,
669704 loader .token )
670705
706+ def test_gcp_get_api_key_with_prefix (self ):
707+ class cred_old :
708+ token = TEST_DATA_BASE64
709+ expiry = DATETIME_EXPIRY_PAST
710+
711+ class cred_new :
712+ token = TEST_ANOTHER_DATA_BASE64
713+ expiry = DATETIME_EXPIRY_FUTURE
714+ fake_config = FakeConfig ()
715+ _get_google_credentials = mock .Mock ()
716+ _get_google_credentials .side_effect = [cred_old , cred_new ]
717+
718+ loader = KubeConfigLoader (
719+ config_dict = self .TEST_KUBE_CONFIG ,
720+ active_context = "expired_gcp_refresh" ,
721+ get_google_credentials = _get_google_credentials )
722+ loader .load_and_set (fake_config )
723+ original_expiry = _get_expiry (loader , "expired_gcp_refresh" )
724+ # Call GCP token fetcher.
725+ token = fake_config .get_api_key_with_prefix ()
726+ new_expiry = _get_expiry (loader , "expired_gcp_refresh" )
727+
728+ self .assertTrue (new_expiry > original_expiry )
729+ self .assertEqual (BEARER_TOKEN_FORMAT % TEST_ANOTHER_DATA_BASE64 ,
730+ loader .token )
731+ self .assertEqual (BEARER_TOKEN_FORMAT % TEST_ANOTHER_DATA_BASE64 ,
732+ token )
733+
671734 def test_oidc_no_refresh (self ):
672735 loader = KubeConfigLoader (
673736 config_dict = self .TEST_KUBE_CONFIG ,
@@ -893,5 +956,33 @@ def test_user_exec_auth(self, mock):
893956 self .assertEqual (expected , actual )
894957
895958
959+ class TestKubernetesClientConfiguration (BaseTestCase ):
960+ # Verifies properties of kubernetes.client.Configuration.
961+ # These tests guard against changes to the upstream configuration class,
962+ # since GCP authorization overrides get_api_key_with_prefix to refresh its
963+ # token regularly.
964+
965+ def test_get_api_key_with_prefix_exists (self ):
966+ self .assertTrue (hasattr (Configuration , 'get_api_key_with_prefix' ))
967+
968+ def test_get_api_key_with_prefix_returns_token (self ):
969+ expected_token = 'expected_token'
970+ config = Configuration ()
971+ config .api_key ['authorization' ] = expected_token
972+ self .assertEqual (expected_token ,
973+ config .get_api_key_with_prefix ('authorization' ))
974+
975+ def test_auth_settings_calls_get_api_key_with_prefix (self ):
976+ expected_token = 'expected_token'
977+
978+ def fake_get_api_key_with_prefix (identifier ):
979+ self .assertEqual ('authorization' , identifier )
980+ return expected_token
981+ config = Configuration ()
982+ config .get_api_key_with_prefix = fake_get_api_key_with_prefix
983+ self .assertEqual (expected_token ,
984+ config .auth_settings ()['BearerToken' ]['value' ])
985+
986+
896987if __name__ == '__main__' :
897988 unittest .main ()
0 commit comments