ArielGlenn has submitted this change and it was merged.

Change subject: 2014.7.5 jessie, backport patches for singleton SAuth class
......................................................................


2014.7.5 jessie, 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.

Revert "Revert "2014.7.5 jessie, backport patches for singleton SAuth class""
This reverts commit 195dfff609f86089444f19d241f544825f65273c.
resubmitting to go through gerrit

Change-Id: I646fd08e6dec4a690c40c3d1b68208fc6a28f655
---
M debian/patches/series
A debian/patches/singleton_sauth_WMF.patch
2 files changed, 240 insertions(+), 0 deletions(-)

Approvals:
  ArielGlenn: Verified; Looks good to me, approved



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/260374
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I646fd08e6dec4a690c40c3d1b68208fc6a28f655
Gerrit-PatchSet: 3
Gerrit-Project: operations/debs/salt
Gerrit-Branch: jessie
Gerrit-Owner: ArielGlenn <[email protected]>
Gerrit-Reviewer: ArielGlenn <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to