[re2o_lookup] Use cache_plugin if available to store authentication token

certbot_on_virtu
_pollion 2020-05-03 15:49:06 +02:00
parent 80dd183a86
commit f73b136b1e
2 changed files with 73 additions and 46 deletions

View File

@ -50,5 +50,8 @@ use_cpasswords = True
cache = jsonfile cache = jsonfile
# Time in second before the cache expired. 0 means never expire cache. # Time in second before the cache expired. 0 means never expire cache.
# Default is 120 seconds. # Default is 24 hours.
timeout = 120 timeout = 86400
# Default is 12 hours.
timeout_token = 43200

View File

@ -30,38 +30,67 @@ from ansible.config.manager import ConfigManager
# Ansible Logger to stdout # Ansible Logger to stdout
display = Display() display = Display()
# Number of seconds before expiration where renewing the token is done
TIME_FOR_RENEW = 120
# Default name of the file to store tokens. Path $HOME/{DEFAUlt_TOKEN_FILENAME} # Default name of the file to store tokens. Path $HOME/{DEFAUlt_TOKEN_FILENAME}
DEFAULT_TOKEN_FILENAME = '.re2o.token' DEFAULT_TOKEN_FILENAME = '.re2o.token'
# If no plugin is used, then use this as token timeout.
# Overriden by key timeout_token from ansible configuration.
TIME_FOR_RENEW = 43200 # 12 jours
class Client: class Client:
""" """
Class based client to contact re2o API. Class based client to contact re2o API.
""" """
def __init__(self, hostname, username, password, use_tls=True): def __init__(self, hostname, username, password,
use_tls=True, cachetoken=None):
""" """
:arg hostname: The hostname of the Re2o instance to use. :arg hostname: The hostname of the Re2o instance to use.
:arg username: The username to use. :arg username: The username to use.
:arg password: The password to use. :arg password: The password to use.
:arg use_tls: A boolean to specify whether the client should use a :arg use_tls: A boolean to specify whether the client should use a
a TLS connection. Default is True. Please, keep it. a TLS connection. Default is True. Please, keep it.
:arg cachetoken: The cache to use to manage authentication token.
If it is None, then store the token in a file.
""" """
self.use_tls = use_tls self.use_tls = use_tls
self.hostname = hostname self.hostname = hostname
self._username = username self._username = username
self._password = password self._password = password
self._cachetoken = cachetoken
self.token_file = Path.home() / DEFAULT_TOKEN_FILENAME self.token_file = None
if self._cachetoken is None:
self.token_file = Path.home() / DEFAULT_TOKEN_FILENAME
display.vvv("Setting token file to {}".format(self.token_file))
else:
try:
display.vvv("Using {} as cache plugin"
.format(self._cachetoken.plugin_name))
except AttributeError:
# Happens when plugin_name is not implemented...
# For example with memcached
display.vvv("Using cache plugin specified in configuration.")
display.v("Connecting to {hostname} as user {user}".format( display.v("Connecting to {hostname} as user {user}".format(
hostname=to_native(self.hostname), user=to_native(self._username))) hostname=to_native(self.hostname), user=to_native(self._username)))
try:
self.token = self._get_token_from_file() @property
except AnsibleFileNotFound: def token(self):
display.vv("Force renew the token") if self._cachetoken:
self._force_renew_token() display.vvv("Trying to get token from cache.")
if self._cachetoken.contains("auth_token"):
display.vvv("Found token in cache.")
return self._cachetoken.get("auth_token")
else:
display.vvv("Token not found. Forcing renew.")
return self._force_renew_token()
else:
try:
token = self._get_token_from_file()
if token['expiration'] < datetime.datetime.now() + \
datetime.timedelta(seconds=TIME_FOR_RENEW):
return self._force_renew_token()
except AnsibleError:
return self._force_renew_token()
def _get_token_from_file(self): def _get_token_from_file(self):
display.vv("Trying to fetch token from {}".format(self.token_file)) display.vv("Trying to fetch token from {}".format(self.token_file))
@ -95,13 +124,18 @@ class Client:
) )
) )
else: else:
display.vv("""Token successfully retreived from display.vv("Token successfully retreived from "
file {token}""".format(token=self.token_file)) "file {token}".format(token=self.token_file))
return ret return ret
def _force_renew_token(self): def _force_renew_token(self):
self.token = self._get_token_from_server() token = self._get_token_from_server()
self._save_token_to_file() if self._cachetoken:
display.vvv("Storing authentication token in cache")
self._cachetoken.set("auth_token", token.get('token'))
else:
self._save_token_to_file(token)
return token.get('token')
def _get_token_from_server(self): def _get_token_from_server(self):
display.vv("Requesting a new token for {user}@{host}".format( display.vv("Requesting a new token for {user}@{host}".format(
@ -141,7 +175,7 @@ class Client:
def _parse_date(self, date, date_format="%Y-%m-%dT%H:%M:%S"): def _parse_date(self, date, date_format="%Y-%m-%dT%H:%M:%S"):
return datetime.datetime.strptime(date.split('.')[0], date_format) return datetime.datetime.strptime(date.split('.')[0], date_format)
def _save_token_to_file(self): def _save_token_to_file(self, token):
display.vv("Saving token to file {}".format(self.token_file)) display.vv("Saving token to file {}".format(self.token_file))
try: try:
# Read previous data to avoid erasures # Read previous data to avoid erasures
@ -155,8 +189,8 @@ class Client:
if self.hostname not in data.keys(): if self.hostname not in data.keys():
data[self.hostname] = {} data[self.hostname] = {}
data[self.hostname][self._username] = { data[self.hostname][self._username] = {
'token': self.token['token'], 'token': token['token'],
'expiration': self.token['expiration'].isoformat(), 'expiration': token['expiration'].isoformat(),
} }
try: try:
@ -171,22 +205,6 @@ class Client:
display.vv("Token successfully written to file {}" display.vv("Token successfully written to file {}"
.format(self.token_file)) .format(self.token_file))
def get_token(self):
"""
Retrieves the token to use for the current connection.
Automatically renewed if needed.
"""
if self.need_renew_token:
self._force_renew_token()
return self.token['token']
@property
def need_renew_token(self):
return self.token['expiration'] < \
datetime.datetime.now() + \
datetime.timedelta(seconds=TIME_FOR_RENEW)
def _request(self, method, url, headers={}, params={}, *args, **kwargs): def _request(self, method, url, headers={}, params={}, *args, **kwargs):
display.vv("Building the {method} request to {url}.".format( display.vv("Building the {method} request to {url}.".format(
method=method.upper(), method=method.upper(),
@ -194,9 +212,9 @@ class Client:
)) ))
# Force the 'Authorization' field with the right token. # Force the 'Authorization' field with the right token.
display.vvv("Forcing authentication token.") display.vvv("Forcing authentication token in headers.")
headers.update({ headers.update({
'Authorization': 'Token {}'.format(self.get_token()) 'Authorization': 'Token {}'.format(self.token)
}) })
# Use a json format unless the user already specified something # Use a json format unless the user already specified something
@ -215,10 +233,10 @@ class Client:
# Force re-login to the server (case of a wrong token but valid # Force re-login to the server (case of a wrong token but valid
# credentials) and then retry the request without catching errors. # credentials) and then retry the request without catching errors.
display.vv("Token refused. Trying to refresh the token.") display.vv("Token refused. Trying to refresh the token.")
self._force_renew_token() token = self._force_renew_token()
headers.update({ headers.update({
'Authorization': 'Token {}'.format(self.get_token()) 'Authorization': 'Token {}'.format(token)
}) })
display.vv("Re-performing the request {method} {url}".format( display.vv("Re-performing the request {method} {url}".format(
method=method.upper(), method=method.upper(),
@ -342,11 +360,11 @@ class LookupModule(LookupBase):
- debug: var=dnszones - debug: var=dnszones
""" """
def _readconfig(self, section="re2o", key=None, boolean=False, def _readconfig(self, section="re2o", key=None, default=None,
integer=False): boolean=False, integer=False):
config = self._config config = self._config
if not config: if not config:
return None return default
else: else:
if config.has_option(section, key): if config.has_option(section, key):
display.vvv("Found key {} in configuration file".format(key)) display.vvv("Found key {} in configuration file".format(key))
@ -373,7 +391,9 @@ class LookupModule(LookupBase):
self._use_cpasswords = None self._use_cpasswords = None
self._cache_plugin = None self._cache_plugin = None
self._cache = None self._cache = None
self._timeout = 120 self._timeout = 86400 # 1 day
self._cachetoken = None
self._timeouttoken = TIME_FOR_RENEW # 12 hours
if self._config.has_section("re2o"): if self._config.has_section("re2o"):
display.vvv("Found section re2o in configuration file") display.vvv("Found section re2o in configuration file")
@ -382,7 +402,11 @@ class LookupModule(LookupBase):
self._use_cpasswords = self._readconfig(key="use_cpasswords", self._use_cpasswords = self._readconfig(key="use_cpasswords",
boolean=True) boolean=True)
self._cache_plugin = self._readconfig(key="cache") self._cache_plugin = self._readconfig(key="cache")
self._timeout = self._readconfig(key="timeout", integer=True) self._timeout = self._readconfig(key="timeout", integer=True,
default=86400)
self._timeouttoken = self._readconfig(key="timeout_token",
integer=True,
default=TIME_FOR_RENEW)
if self._cache_plugin is not None: if self._cache_plugin is not None:
display.vvv("Using {} as cache plugin".format(self._cache_plugin)) display.vvv("Using {} as cache plugin".format(self._cache_plugin))
@ -450,8 +474,8 @@ class LookupModule(LookupBase):
'You must specify a valid password to connect to re2oAPI' 'You must specify a valid password to connect to re2oAPI'
)) ))
api_client = Client(api_hostname, api_username, api_client = Client(api_hostname, api_username, api_password,
api_password, use_tls=True) use_tls=True, cachetoken=self._cachetoken)
res = [] res = []
dterms = collections.deque(terms) dterms = collections.deque(terms)