Skip to content

Commit 880f227

Browse files
committed
Fix PermissionError in async OIDC token refresh on Windows
_refresh_oidc passed the name of an open NamedTemporaryFile to OpenIDRequestor. On Windows the file cannot be reopened by path while the original handle is alive, so any consumer reading the CA cert fails. Add the module-level _create_temp_file_with_content helper, mirroring kubernetes/base/config/kube_config.py, and use it for the CA cert. The FileOrData method now delegates to it, which also closes the descriptor that mkstemp returned. Follow-up to #2644.
1 parent 9bc5eac commit 880f227

1 file changed

Lines changed: 40 additions & 30 deletions

File tree

kubernetes/aio/config/kube_config.py

Lines changed: 40 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,26 @@ def _cleanup_temp_files():
5252
_temp_files = {}
5353

5454

55+
def _create_temp_file_with_content(content, temp_file_path=None, force_recreate=False):
56+
if len(_temp_files) == 0:
57+
atexit.register(_cleanup_temp_files)
58+
59+
# Because we may change context several times, try to remember files we
60+
# created and reuse them at a small memory cost.
61+
content_key = str(content)
62+
63+
if not force_recreate and content_key in _temp_files:
64+
return _temp_files[content_key]
65+
if temp_file_path and not os.path.isdir(temp_file_path):
66+
os.makedirs(name=temp_file_path)
67+
fd, name = tempfile.mkstemp(dir=temp_file_path)
68+
os.close(fd)
69+
_temp_files[content_key] = name
70+
with open(name, 'wb') as fd:
71+
fd.write(content.encode() if isinstance(content, str) else content)
72+
return name
73+
74+
5575
def _is_expired(expiry):
5676
return ((parse_rfc3339(expiry) - EXPIRY_SKEW_PREVENTION_DELAY)
5777
<= datetime.datetime.utcnow().replace(tzinfo=UTC))
@@ -85,18 +105,7 @@ def __init__(self, obj, file_key_name, data_key_name=None,
85105
os.path.join(file_base_path, obj[file_key_name]))
86106

87107
def _create_temp_file_with_content(self, content):
88-
if len(_temp_files) == 0:
89-
atexit.register(_cleanup_temp_files)
90-
# Because we may change context several times, try to remember files we
91-
# created and reuse them at a small memory cost.
92-
content_key = str(content)
93-
if content_key in _temp_files:
94-
return _temp_files[content_key]
95-
_, name = tempfile.mkstemp(dir=self._temp_file_path)
96-
_temp_files[content_key] = name
97-
with open(name, 'wb') as fd:
98-
fd.write(content.encode() if isinstance(content, str) else content)
99-
return name
108+
return _create_temp_file_with_content(content, self._temp_file_path)
100109

101110
def as_file(self):
102111
"""If obj[%data_key_name] exists, return name of a file with base64
@@ -282,28 +291,29 @@ async def _refresh_oidc(self, provider):
282291
if 'refresh-token' not in provider['config']:
283292
raise ConfigException('oidc: No valid id-token, and cannot refresh without refresh-token')
284293

285-
with tempfile.NamedTemporaryFile(delete=True) as certfile:
286-
ssl_ca_cert = None
287-
cert_auth_data = self._retrieve_oidc_cacert(provider)
288-
if cert_auth_data is not None:
289-
certfile.write(cert_auth_data)
290-
certfile.flush()
291-
ssl_ca_cert = certfile.name
294+
ssl_ca_cert = None
295+
cert_auth_data = self._retrieve_oidc_cacert(provider)
292296

293-
requestor = OpenIDRequestor(
294-
provider['config']['client-id'],
295-
provider['config']['client-secret'],
296-
provider['config']['idp-issuer-url'],
297-
ssl_ca_cert,
298-
)
297+
if cert_auth_data is not None:
298+
# Write through the shared helper instead of handing out the name
299+
# of an open NamedTemporaryFile, which cannot be reopened by path
300+
# on Windows while the original handle is still open.
301+
ssl_ca_cert = _create_temp_file_with_content(cert_auth_data)
299302

300-
resp = await requestor.refresh_token(provider['config']['refresh-token'])
303+
requestor = OpenIDRequestor(
304+
provider['config']['client-id'],
305+
provider['config']['client-secret'],
306+
provider['config']['idp-issuer-url'],
307+
ssl_ca_cert,
308+
)
301309

302-
provider['config'].value['id-token'] = resp['id_token']
303-
provider['config'].value['refresh-token'] = resp['refresh_token']
310+
resp = await requestor.refresh_token(provider['config']['refresh-token'])
304311

305-
if self._config_persister:
306-
self._config_persister(self._config.value)
312+
provider['config'].value['id-token'] = resp['id_token']
313+
provider['config'].value['refresh-token'] = resp['refresh_token']
314+
315+
if self._config_persister:
316+
self._config_persister(self._config.value)
307317

308318
def _retrieve_oidc_cacert(self, provider):
309319
if 'idp-certificate-authority-data' in provider['config']:

0 commit comments

Comments
 (0)