Content-Length: 900794 | pFad | http://github.com/saltstack/salt/commit/53fb6df35a114863edb650e203064ca060949844

7A Provide token storage using the salt.cache interface · saltstack/salt@53fb6df · GitHub
Skip to content

Commit 53fb6df

Browse files
committed
Provide token storage using the salt.cache interface
Rather than implement custom drivers for tokens, the existing cache drivers can be leveraged for token management. This also moves token expiry responsibility to the token(cache) implementation with a naive fallback. Implicitly the default token backend will move to either what the global 'cache' is set to, or, if overridden, the eauth_tokens.cache_driver opt. This means on upgrade, tokens will be invalidated. The only other token driver is 'rediscluster', which I've added a deprecation for, since cache.redis_cache provides the same functionality.
1 parent 591d22d commit 53fb6df

File tree

8 files changed

+235
-92
lines changed

8 files changed

+235
-92
lines changed

changelog/68039.changed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Provide token storage using the salt.cache interface

salt/auth/__init__.py

Lines changed: 154 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,18 @@
1313
# 6. Interface to verify tokens
1414

1515
import getpass
16+
import hashlib
1617
import logging
18+
import os
1719
import random
1820
import time
1921
from collections.abc import Iterable, Mapping
2022

23+
import salt.cache
2124
import salt.channel.client
22-
import salt.config
2325
import salt.exceptions
2426
import salt.loader
25-
import salt.payload
2627
import salt.utils.args
27-
import salt.utils.dictupdate
2828
import salt.utils.files
2929
import salt.utils.minions
3030
import salt.utils.network
@@ -60,6 +60,7 @@ def __init__(self, opts, ckminions=None):
6060
self.max_fail = 1.0
6161
self.auth = salt.loader.auth(opts)
6262
self.tokens = salt.loader.eauth_tokens(opts)
63+
self.cache = salt.cache.factory(opts, driver=opts["eauth_tokens.cache_driver"])
6364
self.ckminions = ckminions or salt.utils.minions.CkMinions(opts)
6465

6566
def load_name(self, load):
@@ -230,54 +231,168 @@ def mk_token(self, load):
230231
if groups:
231232
tdata["groups"] = groups
232233

233-
return self.tokens["{}.mk_token".format(self.opts["eauth_tokens"])](
234-
self.opts, tdata
235-
)
234+
if self.opts["eauth_tokens.cache_driver"] == "rediscluster":
235+
salt.utils.versions.warn_until(
236+
3009,
237+
"The 'rediscluster' token backend has been deprecated, and will be removed "
238+
"in the Potassium release. Please use the 'redis_cache' cache backend instead.",
239+
)
240+
return self.tokens["{}.mk_token".format(self.opts["eauth_tokens"])](
241+
self.opts, tdata
242+
)
243+
else:
244+
hash_type = getattr(hashlib, self.opts.get("hash_type", "md5"))
245+
new_token = str(hash_type(os.urandom(512)).hexdigest())
246+
tdata["token"] = new_token
247+
try:
248+
self.cache.store("tokens", new_token, tdata, expires=tdata["expire"])
249+
except salt.exceptions.SaltCacheError as err:
250+
log.error(
251+
"Cannot mk_token from tokens cache using %s: %s",
252+
self.opts["eauth_tokens.cache_driver"],
253+
err,
254+
)
255+
return {}
256+
257+
return tdata
236258

237259
def get_tok(self, tok):
238260
"""
239261
Return the name associated with the token, or False if the token is
240262
not valid
241263
"""
242-
tdata = {}
243-
try:
244-
tdata = self.tokens["{}.get_token".format(self.opts["eauth_tokens"])](
245-
self.opts, tok
264+
if self.opts["eauth_tokens.cache_driver"] == "rediscluster":
265+
salt.utils.versions.warn_until(
266+
3009,
267+
"The 'rediscluster' token backend has been deprecated, and will be removed "
268+
"in the Potassium release. Please use the 'redis_cache' cache backend instead.",
246269
)
247-
except salt.exceptions.SaltDeserializationError:
248-
log.warning("Failed to load token %r - removing broken/empty file.", tok)
249-
rm_tok = True
250-
else:
251-
if not tdata:
270+
271+
tdata = {}
272+
try:
273+
tdata = self.tokens["{}.get_token".format(self.opts["eauth_tokens"])](
274+
self.opts, tok
275+
)
276+
except salt.exceptions.SaltDeserializationError:
277+
log.warning(
278+
"Failed to load token %r - removing broken/empty file.", tok
279+
)
280+
rm_tok = True
281+
else:
282+
if not tdata:
283+
return {}
284+
rm_tok = False
285+
286+
if tdata.get("expire", 0) < time.time():
287+
# If expire isn't present in the token it's invalid and needs
288+
# to be removed. Also, if it's present and has expired - in
289+
# other words, the expiration is before right now, it should
290+
# be removed.
291+
rm_tok = True
292+
293+
if rm_tok:
294+
self.rm_token(tok)
252295
return {}
253-
rm_tok = False
254296

255-
if tdata.get("expire", 0) < time.time():
256-
# If expire isn't present in the token it's invalid and needs
257-
# to be removed. Also, if it's present and has expired - in
258-
# other words, the expiration is before right now, it should
259-
# be removed.
260-
rm_tok = True
297+
return tdata
298+
else:
299+
try:
300+
tdata = self.cache.fetch("tokens", tok)
261301

262-
if rm_tok:
263-
self.rm_token(tok)
264-
return {}
302+
if tdata.get("expire", 0) < time.time():
303+
raise salt.exceptions.TokenExpiredError
265304

266-
return tdata
305+
return tdata
306+
except (
307+
salt.exceptions.SaltDeserializationError,
308+
salt.exceptions.TokenExpiredError,
309+
):
310+
log.warning(
311+
"Failed to load token %r - removing broken/empty file.", tok
312+
)
313+
self.rm_token(tok)
314+
except salt.exceptions.SaltCacheError as err:
315+
log.error(
316+
"Cannot get token %s from tokens cache using %s: %s",
317+
tok,
318+
self.opts["eauth_tokens.cache_driver"],
319+
err,
320+
)
321+
return {}
267322

268323
def list_tokens(self):
269324
"""
270325
List all tokens in eauth_tokens storage.
271326
"""
272-
return self.tokens["{}.list_tokens".format(self.opts["eauth_tokens"])](
273-
self.opts
274-
)
327+
if self.opts["eauth_tokens.cache_driver"] == "rediscluster":
328+
salt.utils.versions.warn_until(
329+
3009,
330+
"The 'rediscluster' token backend has been deprecated, and will be removed "
331+
"in the Potassium release. Please use the 'redis_cache' cache backend instead.",
332+
)
333+
334+
return self.tokens["{}.list_tokens".format(self.opts["eauth_tokens"])](
335+
self.opts
336+
)
337+
else:
338+
try:
339+
return self.cache.list("tokens")
340+
except salt.exceptions.SaltCacheError as err:
341+
log.error(
342+
"Cannot list tokens from tokens cache using %s: %s",
343+
self.opts["eauth_tokens.cache_driver"],
344+
err,
345+
)
346+
return []
275347

276348
def rm_token(self, tok):
277349
"""
278350
Remove the given token from token storage.
279351
"""
280-
self.tokens["{}.rm_token".format(self.opts["eauth_tokens"])](self.opts, tok)
352+
if self.opts["eauth_tokens.cache_driver"] == "rediscluster":
353+
salt.utils.versions.warn_until(
354+
3009,
355+
"The 'rediscluster' token backend has been deprecated, and will be removed "
356+
"in the Potassium release. Please use the 'redis_cache' cache backend instead.",
357+
)
358+
359+
self.tokens["{}.rm_token".format(self.opts["eauth_tokens"])](self.opts, tok)
360+
else:
361+
try:
362+
return self.cache.flush("tokens", tok)
363+
except salt.exceptions.SaltCacheError as err:
364+
log.error(
365+
"Cannot rm token %s from tokens cache using %s: %s",
366+
tok,
367+
self.opts["eauth_tokens.cache_driver"],
368+
err,
369+
)
370+
return {}
371+
372+
def clean_expired_tokens(self):
373+
"""
374+
Clean expired tokens
375+
"""
376+
if self.opts["eauth_tokens.cache_driver"] == "rediscluster":
377+
salt.utils.versions.warn_until(
378+
3009,
379+
"The 'rediscluster' token backend has been deprecated, and will be removed "
380+
"in the Potassium release. Please use the 'redis_cache' cache backend instead.",
381+
)
382+
log.debug(
383+
"cleaning expired tokens using token driver: {}".format(
384+
self.opts["eauth_tokens"]
385+
)
386+
)
387+
for token in self.list_tokens():
388+
token_data = self.get_tok(token)
389+
if (
390+
"expire" not in token_data
391+
or token_data.get("expire", 0) < time.time()
392+
):
393+
self.rm_token(token)
394+
else:
395+
self.cache.clean_expired("tokens")
281396

282397
def authenticate_token(self, load):
283398
"""
@@ -604,6 +719,15 @@ def get_token(self, token):
604719
tdata = self._send_token_request(load)
605720
return tdata
606721

722+
def rm_token(self, token):
723+
"""
724+
Delete a token from the master
725+
"""
726+
load = {}
727+
load["token"] = token
728+
load["cmd"] = "rm_token"
729+
self._send_token_request(load)
730+
607731

608732
class AuthUser:
609733
"""

salt/cache/__init__.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,32 @@ def contains(self, bank, key=None):
288288
fun = f"{self.driver}.contains"
289289
return self.modules[fun](bank, key, **self._kwargs)
290290

291+
def clean_expired(self, bank, *args, **kwargs):
292+
"""
293+
Clean expired keys
294+
295+
:param bank:
296+
The name of the location inside the cache which will hold the key
297+
and its associated data.
298+
299+
:raises SaltCacheError:
300+
Raises an exception if cache driver detected an error accessing data
301+
in the cache backend (auth, permissions, etc).
302+
"""
303+
# If the cache driver has a clean_expired() func, call it to clean up
304+
# expired keys.
305+
clean_expired = f"{self.driver}.clean_expired"
306+
if clean_expired in self.modules:
307+
self.modules[clean_expired](bank, *args, **kwargs)
308+
else:
309+
list_ = f"{self.driver}.list"
310+
updated = f"{self.driver}.updated"
311+
flush = f"{self.driver}.flush"
312+
for key in self.modules[list_](bank, **self._kwargs):
313+
ts = self.modules[updated](bank, key, **self._kwargs)
314+
if ts is not None and ts <= time.time():
315+
self.modules[flush](bank, key, **self._kwargs)
316+
291317

292318
class MemCache(Cache):
293319
"""

salt/config/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,6 +1018,8 @@ def _gather_buffer_space():
10181018
"publish_signing_algorithm": str,
10191019
# optional cache driver for pillar cache
10201020
"pillar.cache_driver": (type(None), str),
1021+
# optional cache driver for eauth_tokens cache
1022+
"eauth_tokens.cache_driver": (type(None), str),
10211023
}
10221024
)
10231025

@@ -1683,6 +1685,7 @@ def _gather_buffer_space():
16831685
"features": {},
16841686
"publish_signing_algorithm": "PKCS1v15-SHA1",
16851687
"pillar.cache_driver": None,
1688+
"eauth_tokens.cache_driver": None,
16861689
}
16871690
)
16881691

salt/daemons/masterapi.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,7 @@ def clean_expired_tokens(opts):
120120
Clean expired tokens from the master
121121
"""
122122
loadauth = salt.auth.LoadAuth(opts)
123-
for tok in loadauth.list_tokens():
124-
token_data = loadauth.get_tok(tok)
125-
if "expire" not in token_data or token_data.get("expire", 0) < time.time():
126-
loadauth.rm_token(tok)
123+
loadauth.clean_expired_tokens()
127124

128125

129126
def clean_pub_auth(opts):

salt/exceptions.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,12 @@ class TokenAuthenticationError(SaltException):
350350
"""
351351

352352

353+
class TokenExpiredError(SaltException):
354+
"""
355+
Thrown when token is expired
356+
"""
357+
358+
353359
class SaltDeserializationError(SaltException):
354360
"""
355361
Thrown when salt cannot deserialize data.

tests/pytests/unit/auth/test_auth.py

Lines changed: 0 additions & 33 deletions
This file was deleted.

0 commit comments

Comments
 (0)








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: http://github.com/saltstack/salt/commit/53fb6df35a114863edb650e203064ca060949844

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy