ArielGlenn has uploaded a new change for review. https://gerrit.wikimedia.org/r/260563
Change subject: 2014.7.5 trusty, backport patches for singleton SAuth class ...................................................................... 2014.7.5 trusty, backport patches for singleton SAuth class This permits the minion to cache the its authentication information instead of needing to reauthenticate on each command received. Authentication is quite expensive, using public keys and all. See the patch header for the list of specific upstream commits. The 2015.8 salt series has these patches already. Change-Id: I221a1213dc8a4aacdda2db7e545727d0e301c9f6 --- M debian/patches/series A debian/patches/singleton_sauth_WMF.patch 2 files changed, 240 insertions(+), 0 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/operations/debs/salt refs/changes/63/260563/1 diff --git a/debian/patches/series b/debian/patches/series index 469243b..2f6a5f9 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -7,3 +7,4 @@ batch_returns_bad_dict_WMF.patch event_reading_returns_prematurely_WMF.patch ping_minions_without_data_cache_WMF.patch +singleton_sauth_WMF.patch diff --git a/debian/patches/singleton_sauth_WMF.patch b/debian/patches/singleton_sauth_WMF.patch new file mode 100644 index 0000000..f79d3d8 --- /dev/null +++ b/debian/patches/singleton_sauth_WMF.patch @@ -0,0 +1,239 @@ +Description: Backport of patches for singleton SAuth class + This is a combination of three upstream commits: + 20fcfbc6b3f0dcdd9a09ca520a18e65dd8b0a950 + Refactor SAuth() to return singletons for each master/minion pair. + 18bc50e6dcce5410c3d2c6a549e043ee59891ee9 + Rework to expose __authenticate() + a1dd24b5ea7e09fb3198ea2a83a71de167e52d92 + Remove clear() method +Author: Ariel T. Glenn <[email protected]> +Forwarded: no +--- a/salt/minion.py ++++ b/salt/minion.py +@@ -883,19 +883,7 @@ + try: + data = self.crypticle.loads(load) + except AuthenticationError: +- # decryption of the payload failed, try to re-auth but wait +- # random seconds if set in config with random_reauth_delay +- if 'random_reauth_delay' in self.opts: +- reauth_delay = randint(0, float(self.opts['random_reauth_delay'])) +- # This mitigates the issue wherein a long-running job might not return +- # on a master key rotation. However, new commands issued during the re-auth +- # splay period will still fail to return. +- if not salt.utils.minion.running(self.opts): +- log.debug('Waiting {0} seconds to re-authenticate'.format(reauth_delay)) +- time.sleep(reauth_delay) +- else: +- log.warning('Ignoring re-auth delay because jobs are running') +- ++ # decryption of the payload failed, try to re-auth + self.authenticate() + data = self.crypticle.loads(load) + +@@ -1367,7 +1355,8 @@ + self.opts['master_ip'] + ) + ) +- auth = salt.crypt.Auth(self.opts) ++ auth = salt.crypt.SAuth(self.opts) ++ auth.authenticate() + self.tok = auth.gen_token('salt') + acceptance_wait_time = self.opts['acceptance_wait_time'] + acceptance_wait_time_max = self.opts['acceptance_wait_time_max'] +--- a/salt/transport/__init__.py ++++ b/salt/transport/__init__.py +@@ -229,15 +229,13 @@ + self.crypt = kwargs.get('crypt', 'aes') + + self.serial = salt.payload.Serial(opts) +- if self.crypt != 'clear': +- if 'auth' in kwargs: +- self.auth = kwargs['auth'] +- else: +- self.auth = salt.crypt.SAuth(opts) + if 'master_uri' in kwargs: + self.master_uri = kwargs['master_uri'] + else: + self.master_uri = opts['master_uri'] ++ if self.crypt != 'clear': ++ # we don't need to worry about auth as a kwarg, since its a singleton ++ self.auth = salt.crypt.SAuth(self.opts) + + def crypted_transfer_decode_dictentry(self, load, dictkey=None, tries=3, timeout=60): + ret = self.sreq.send('aes', self.auth.crypticle.dumps(load), tries, timeout) +@@ -269,7 +267,7 @@ + try: + return _do_transfer() + except salt.crypt.AuthenticationError: +- self.auth = salt.crypt.SAuth(self.opts) ++ self.auth.authenticate() + return _do_transfer() + + def _uncrypted_transfer(self, load, tries=3, timeout=60): +--- a/salt/crypt.py ++++ b/salt/crypt.py +@@ -286,12 +286,36 @@ + return self.pub_signature + + +-class Auth(object): ++class SAuth(object): + ''' +- The Auth class provides the sequence for setting up communication with +- the master server from a minion. ++ Set up an object to maintain authentication with the salt master + ''' ++ # This class is only a singleton per minion/master pair ++ instances = {} ++ ++ def __new__(cls, opts): ++ ''' ++ Only create one instance of SAuth per __key() ++ ''' ++ key = cls.__key(opts) ++ if key not in SAuth.instances: ++ SAuth.instances[key] = object.__new__(cls) ++ SAuth.instances[key].__singleton_init__(opts) ++ return SAuth.instances[key] ++ ++ @classmethod ++ def __key(cls, opts): ++ return (opts['pki_dir'], # where the keys are stored ++ opts['id'], # minion ID ++ opts['master_uri'], # master ID ++ ) ++ ++ # has to remain empty for singletons, since __init__ will *always* be called + def __init__(self, opts): ++ pass ++ ++ # an init for the singleton instance to call ++ def __singleton_init__(self, opts): + ''' + Init an Auth instance + +@@ -313,6 +337,41 @@ + if not os.path.isfile(self.pub_path): + self.get_keys() + ++ self.authenticate() ++ ++ def authenticate(self): ++ ''' ++ Authenticate with the master, this method breaks the functional ++ paradigm, it will update the master information from a fresh sign ++ in, signing in can occur as often as needed to keep up with the ++ revolving master AES key. ++ ++ :rtype: Crypticle ++ :returns: A crypticle used for encryption operations ++ ''' ++ acceptance_wait_time = self.opts['acceptance_wait_time'] ++ acceptance_wait_time_max = self.opts['acceptance_wait_time_max'] ++ if not acceptance_wait_time_max: ++ acceptance_wait_time_max = acceptance_wait_time ++ ++ while True: ++ creds = self.sign_in() ++ if creds == 'retry': ++ if self.opts.get('caller'): ++ print('Minion failed to authenticate with the master, ' ++ 'has the minion key been accepted?') ++ sys.exit(2) ++ if acceptance_wait_time: ++ log.info('Waiting {0} seconds before retry.'.format(acceptance_wait_time)) ++ time.sleep(acceptance_wait_time) ++ if acceptance_wait_time < acceptance_wait_time_max: ++ acceptance_wait_time += acceptance_wait_time ++ log.debug('Authentication wait time is {0}'.format(acceptance_wait_time)) ++ continue ++ break ++ self.creds = creds ++ self.crypticle = Crypticle(self.opts, creds['aes']) ++ + def get_keys(self): + ''' + Return keypair object for the minion. +@@ -630,8 +689,21 @@ + + ''' + auth = {} ++ ++ auth_timeout = self.opts.get('auth_timeout', None) ++ if auth_timeout is not None: ++ timeout = auth_timeout ++ auth_safemode = self.opts.get('auth_safemode', None) ++ if auth_safemode is not None: ++ safe = auth_safemode ++ auth_tries = self.opts.get('auth_tries', None) ++ if auth_tries is not None: ++ tries = auth_tries ++ + m_pub_fn = os.path.join(self.opts['pki_dir'], self.mpub) + ++ auth['master_uri'] = self.opts['master_uri'] ++ + sreq = salt.payload.SREQ( + self.opts['master_uri'], + opts=self.opts +@@ -733,7 +805,8 @@ + SIG_SIZE = hashlib.sha256().digest_size + + def __init__(self, opts, key_string, key_size=192): +- self.keys = self.extract_keys(key_string, key_size) ++ self.key_string = key_string ++ self.keys = self.extract_keys(self.key_string, key_size) + self.key_size = key_size + self.serial = salt.payload.Serial(opts) + +@@ -799,49 +872,3 @@ + if not data.startswith(self.PICKLE_PAD): + return {} + return self.serial.loads(data[len(self.PICKLE_PAD):]) +- +- +-class SAuth(Auth): +- ''' +- Set up an object to maintain the standalone authentication session +- with the salt master +- ''' +- def __init__(self, opts): +- super(SAuth, self).__init__(opts) +- self.crypticle = self.__authenticate() +- +- def __authenticate(self): +- ''' +- Authenticate with the master, this method breaks the functional +- paradigm, it will update the master information from a fresh sign +- in, signing in can occur as often as needed to keep up with the +- revolving master AES key. +- +- :rtype: Crypticle +- :returns: A crypticle used for encryption operations +- ''' +- acceptance_wait_time = self.opts['acceptance_wait_time'] +- acceptance_wait_time_max = self.opts['acceptance_wait_time_max'] +- if not acceptance_wait_time_max: +- acceptance_wait_time_max = acceptance_wait_time +- +- while True: +- creds = self.sign_in( +- self.opts.get('auth_timeout', 60), +- self.opts.get('auth_safemode', self.opts.get('_safe_auth', True)), +- self.opts.get('auth_tries', 1) +- ) +- if creds == 'retry': +- if self.opts.get('caller'): +- print('Minion failed to authenticate with the master, ' +- 'has the minion key been accepted?') +- sys.exit(2) +- if acceptance_wait_time: +- log.info('Waiting {0} seconds before retry.'.format(acceptance_wait_time)) +- time.sleep(acceptance_wait_time) +- if acceptance_wait_time < acceptance_wait_time_max: +- acceptance_wait_time += acceptance_wait_time +- log.debug('Authentication wait time is {0}'.format(acceptance_wait_time)) +- continue +- break +- return Crypticle(self.opts, creds['aes']) -- To view, visit https://gerrit.wikimedia.org/r/260563 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I221a1213dc8a4aacdda2db7e545727d0e301c9f6 Gerrit-PatchSet: 1 Gerrit-Project: operations/debs/salt Gerrit-Branch: trusty Gerrit-Owner: ArielGlenn <[email protected]> _______________________________________________ MediaWiki-commits mailing list [email protected] https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits
