Package proxy :: Module rhnProxyAuth
[hide private]
[frames] | no frames]

Source Code for Module proxy.rhnProxyAuth

  1  # Spacewalk Proxy Server authentication manager. 
  2  # 
  3  # Copyright (c) 2008--2020 Red Hat, Inc. 
  4  # 
  5  # This software is licensed to you under the GNU General Public License, 
  6  # version 2 (GPLv2). There is NO WARRANTY for this software, express or 
  7  # implied, including the implied warranties of MERCHANTABILITY or FITNESS 
  8  # FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 
  9  # along with this software; if not, see 
 10  # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. 
 11  # 
 12  # Red Hat trademarks are not licensed under GPLv2. No permission is 
 13  # granted to use or replicate Red Hat trademarks that are incorporated 
 14  # in this software or its documentation. 
 15  # 
 16  # ----------------------------------------------------------------------------- 
 17   
 18  # system imports 
 19  import os 
 20  import time 
 21  import socket 
 22  import xmlrpclib 
 23  import sys 
 24  # pylint: disable=E0611 
 25  from hashlib import sha1 
 26   
 27  # common imports 
 28  from spacewalk.common.rhnLib import parseUrl 
 29  from spacewalk.common.rhnTB import Traceback 
 30  from spacewalk.common.rhnLog import log_debug, log_error 
 31  from spacewalk.common.rhnConfig import CFG 
 32  from spacewalk.common.rhnException import rhnFault 
 33  from spacewalk.common import rhnCache 
 34  from spacewalk.common.rhnTranslate import _ 
 35   
 36  # local imports 
 37  from rhn import rpclib 
 38  from rhn import SSL 
 39  import rhnAuthCacheClient 
 40   
 41   
 42  sys.path.append('/usr/share/rhn') 
 43  from up2date_client import config # pylint: disable=E0012, C0413, wrong-import-order 
 44   
 45  # To avoid doing unnecessary work, keep ProxyAuth object global 
 46  __PROXY_AUTH = None 
 47  UP2DATE_CONFIG = config.Config('/etc/sysconfig/rhn/up2date') 
48 49 50 -def get_proxy_auth(hostname=None):
51 global __PROXY_AUTH 52 if not __PROXY_AUTH: 53 __PROXY_AUTH = ProxyAuth(hostname) 54 if __PROXY_AUTH.hostname != hostname: 55 __PROXY_AUTH = ProxyAuth(hostname) 56 return __PROXY_AUTH
57
58 59 -class ProxyAuth:
60 61 __serverid = None 62 __systemid = None 63 __systemid_mtime = None 64 __systemid_filename = UP2DATE_CONFIG['systemIdPath'] 65 66 __nRetries = 3 # number of login retries 67 68 hostname = None 69
70 - def __init__(self, hostname):
74
75 - def __processSystemid(self):
76 """ update the systemid/serverid but only if they stat differently. 77 returns 0=no updates made; or 1=updates were made 78 """ 79 if not os.access(ProxyAuth.__systemid_filename, os.R_OK): 80 log_error("unable to access %s" % ProxyAuth.__systemid_filename) 81 raise rhnFault(1000, 82 _("Spacewalk Proxy error (Spacewalk Proxy systemid has wrong permissions?). " 83 "Please contact your system administrator.")) 84 85 mtime = None 86 try: 87 mtime = os.stat(ProxyAuth.__systemid_filename)[-2] 88 except IOError, e: 89 log_error("unable to stat %s: %s" % (ProxyAuth.__systemid_filename, repr(e))) 90 raise rhnFault(1000, 91 _("Spacewalk Proxy error (Spacewalk Proxy systemid has wrong permissions?). " 92 "Please contact your system administrator.")), None, sys.exc_info()[2] 93 94 if not self.__systemid_mtime: 95 ProxyAuth.__systemid_mtime = mtime 96 97 if self.__systemid_mtime == mtime \ 98 and self.__systemid and self.__serverid: 99 # nothing to do 100 return 0 101 102 # get systemid 103 try: 104 ProxyAuth.__systemid = open(ProxyAuth.__systemid_filename, 'r').read() 105 except IOError, e: 106 log_error("unable to read %s" % ProxyAuth.__systemid_filename) 107 raise rhnFault(1000, 108 _("Spacewalk Proxy error (Spacewalk Proxy systemid has wrong permissions?). " 109 "Please contact your system administrator.")), None, sys.exc_info()[2] 110 111 # get serverid 112 sysid, _cruft = xmlrpclib.loads(ProxyAuth.__systemid) 113 ProxyAuth.__serverid = sysid[0]['system_id'][3:] 114 115 log_debug(7, 'SystemId: "%s[...snip snip...]%s"' 116 % (ProxyAuth.__systemid[:20], ProxyAuth.__systemid[-20:])) 117 log_debug(7, 'ServerId: %s' % ProxyAuth.__serverid) 118 119 # ids were updated 120 return 1
121
122 - def get_system_id(self):
123 """ return the system id""" 124 self.__processSystemid() 125 return self.__systemid
126
127 - def check_cached_token(self, forceRefresh=0):
128 """ check cache, login if need be, and cache. 129 """ 130 log_debug(3) 131 oldToken = self.get_cached_token() 132 token = oldToken 133 if not token or forceRefresh or self.__processSystemid(): 134 token = self.login() 135 if token and token != oldToken: 136 self.set_cached_token(token) 137 return token
138
139 - def get_cached_token(self):
140 """ Fetches this proxy's token (or None) from the cache 141 """ 142 log_debug(3) 143 # Try to connect to the token-cache. 144 shelf = get_auth_shelf() 145 # Fetch the token 146 key = self.__cache_proxy_key() 147 if shelf.has_key(key): 148 return shelf[key] 149 return None
150
151 - def set_cached_token(self, token):
152 """ Caches current token in the auth cache. 153 """ 154 log_debug(3) 155 # Try to connect to the token-cache. 156 shelf = get_auth_shelf() 157 # Cache the token. 158 try: 159 shelf[self.__cache_proxy_key()] = token 160 except: 161 text = _("""\ 162 Caching of authentication token for proxy id %s failed! 163 Either the authentication caching daemon is experiencing 164 problems, isn't running, or the token is somehow corrupt. 165 """) % self.__serverid 166 Traceback("ProxyAuth.set_cached_token", extra=text) 167 raise rhnFault(1000, 168 _("Spacewalk Proxy error (auth caching issue). " 169 "Please contact your system administrator.")), None, sys.exc_info()[2] 170 log_debug(4, "successfully returning") 171 return token
172
173 - def del_cached_token(self):
174 """Removes the token from the cache 175 """ 176 log_debug(3) 177 # Connect to the token cache 178 shelf = get_auth_shelf() 179 key = self.__cache_proxy_key() 180 try: 181 del shelf[key] 182 except KeyError: 183 # no problem 184 pass
185
186 - def login(self):
187 """ Login and fetch new token (proxy token). 188 189 How it works in a nutshell. 190 Only the broker component uses this. We perform a xmlrpc request 191 to rhn_parent. This occurs outside of the http process we are 192 currently working on. So, we do this all on our own; do all of 193 our own SSL decisionmaking etc. We use CFG.RHN_PARENT as we always 194 bypass the SSL redirect. 195 196 DESIGN NOTES: what is the proxy auth token? 197 ------------------------------------------- 198 An Spacewalk Proxy auth token is a token fetched upon login from 199 Red Hat Satellite or hosted. 200 201 It has this format: 202 'S:U:ST:EO:SIG' 203 Where: 204 S = server ID 205 U = username 206 ST = server time 207 EO = expiration offset 208 SIG = signature 209 H = hostname (important later) 210 211 Within this function within the Spacewalk Proxy Broker we also tag on 212 the hostname to the end of the token. The token as described above 213 is enough for authentication purposes, but we need a to identify 214 the exact hostname (as the Spacewalk Proxy sees it). So now the token 215 becomes (token:hostname): 216 'S:U:ST:EO:SIG:H' 217 218 DESIGN NOTES: what is X-RHN-Proxy-Auth? 219 ------------------------------------------- 220 This is where we use the auth token beyond Spacewalk Proxy login 221 purposes. This a header used to track request routes through 222 a hierarchy of RHN Proxies. 223 224 X-RHN-Proxy-Auth is a header that passes proxy authentication 225 information around in the form of an ordered list of tokens. This 226 list is used to gain information as to how a client request is 227 routed throughout an RHN topology. 228 229 Format: 'S1:U1:ST1:EO1:SIG1:H1,S2:U2:ST2:EO2:SIG2:H2,...' 230 |_________1_________| |_________2_________| |__... 231 token token 232 where token is really: token:hostname 233 234 leftmost token was the first token hit by a client request. 235 rightmost token was the last token hit by a client request. 236 237 """ 238 # pylint: disable=R0915 239 240 log_debug(3) 241 server = self.__getXmlrpcServer() 242 error = None 243 token = None 244 # update the systemid/serverid if need be. 245 self.__processSystemid() 246 # Makes three attempts to login 247 for _i in range(self.__nRetries): 248 try: 249 token = server.proxy.login(self.__systemid) 250 except (socket.error, socket.sslerror), e: 251 if CFG.HTTP_PROXY: 252 # socket error, check to see if your HTTP proxy is running... 253 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 254 httpProxy, httpProxyPort = CFG.HTTP_PROXY.split(':') 255 try: 256 s.connect((httpProxy, int(httpProxyPort))) 257 except socket.error, e: 258 error = ['socket.error', 'HTTP Proxy not running? ' 259 '(%s) %s' % (CFG.HTTP_PROXY, e)] 260 # rather big problem: http proxy not running. 261 log_error("*** ERROR ***: %s" % error[1]) 262 Traceback(mail=0) 263 except socket.sslerror, e: 264 error = ['socket.sslerror', 265 '(%s) %s' % (CFG.HTTP_PROXY, e)] 266 # rather big problem: http proxy not running. 267 log_error("*** ERROR ***: %s" % error[1]) 268 Traceback(mail=0) 269 else: 270 error = ['socket', str(e)] 271 log_error(error) 272 Traceback(mail=0) 273 else: 274 log_error("Socket error", e) 275 Traceback(mail=0) 276 Traceback(mail=1) 277 token = None 278 time.sleep(.25) 279 continue 280 except SSL.SSL.Error, e: 281 token = None 282 error = ['rhn.SSL.SSL.Error', repr(e), str(e)] 283 log_error(error) 284 Traceback(mail=0) 285 time.sleep(.25) 286 continue 287 except xmlrpclib.ProtocolError, e: 288 token = None 289 log_error('xmlrpclib.ProtocolError', e) 290 time.sleep(.25) 291 continue 292 except xmlrpclib.Fault, e: 293 # Report it through the mail 294 # Traceback will try to walk over all the values 295 # in each stack frame, and eventually will try to stringify 296 # the method object itself 297 # This should trick it, since the originator of the exception 298 # is this function, instead of a deep call into xmlrpclib 299 log_error("%s" % e) 300 if e.faultCode == 10000: 301 # reraise it for the users (outage or "important message" 302 # coming through") 303 raise rhnFault(e.faultCode, e.faultString), None, sys.exc_info()[2] 304 # ok... it's some other fault 305 Traceback("ProxyAuth.login (Fault) - Spacewalk Proxy not " 306 "able to log in.") 307 # And raise a Proxy Error - the server made its point loud and 308 # clear 309 raise rhnFault(1000, 310 _("Spacewalk Proxy error (during proxy login). " 311 "Please contact your system administrator.")), None, sys.exc_info()[2] 312 except Exception, e: # pylint: disable=E0012, W0703 313 token = None 314 log_error("Unhandled exception", e) 315 Traceback(mail=0) 316 time.sleep(.25) 317 continue 318 else: 319 break 320 321 if not token: 322 if error: 323 if error[0] in ('xmlrpclib.ProtocolError', 'socket.error', 'socket'): 324 raise rhnFault(1000, 325 _("Spacewalk Proxy error (error: %s). " 326 "Please contact your system administrator.") % error[0]) 327 if error[0] in ('rhn.SSL.SSL.Error', 'socket.sslerror'): 328 raise rhnFault(1000, 329 _("Spacewalk Proxy error (SSL issues? Error: %s). " 330 "Please contact your system administrator.") % error[0]) 331 else: 332 raise rhnFault(1002, err_text='%s' % e) 333 else: 334 raise rhnFault(1001) 335 if self.hostname: 336 token = token + ':' + self.hostname 337 log_debug(6, "New proxy token: %s" % token) 338 return token
339 340 @staticmethod
341 - def get_client_token(clientid):
342 shelf = get_auth_shelf() 343 if shelf.has_key(clientid): 344 return shelf[clientid] 345 return None
346 347 @staticmethod
348 - def set_client_token(clientid, token):
349 shelf = get_auth_shelf() 350 shelf[clientid] = token
351
352 - def update_client_token_if_valid(self, clientid, token):
353 # Maybe a load-balanced proxie and client logged in through a 354 # different one? Ask upstream if token is valid. If it is, 355 # upate cache. 356 # copy to simple dict for transmission. :-/ 357 dumbToken = {} 358 satInfo = None 359 for key in ('X-RHN-Server-Id', 'X-RHN-Auth-User-Id', 'X-RHN-Auth', 360 'X-RHN-Auth-Server-Time', 'X-RHN-Auth-Expire-Offset'): 361 if token.has_key(key): 362 dumbToken[key] = token[key] 363 try: 364 s = self.__getXmlrpcServer() 365 satInfo = s.proxy.checkTokenValidity( 366 dumbToken, self.get_system_id()) 367 except Exception: # pylint: disable=E0012, W0703 368 pass # Satellite is not updated enough, keep old behavior 369 370 # False if not valid token, a dict of info we need otherwise 371 # We have to calculate the proxy-clock-skew between Sat and this 372 # Proxy, as well as store the subscribed channels for this client 373 # (which the client does not pass up in headers and which we 374 # wouldn't trust even if it did). 375 if satInfo: 376 clockSkew = time.time() - float(satInfo['X-RHN-Auth-Server-Time']) 377 dumbToken['X-RHN-Auth-Proxy-Clock-Skew'] = clockSkew 378 dumbToken['X-RHN-Auth-Channels'] = satInfo['X-RHN-Auth-Channels'] 379 # update our cache so we don't have to ask next time 380 self.set_client_token(clientid, dumbToken) 381 return dumbToken 382 return None
383 384 # __private methods__ 385 386 @staticmethod
387 - def __getXmlrpcServer():
388 """ get an xmlrpc server object 389 390 WARNING: if CFG.USE_SSL is off, we are sending info 391 in the clear. 392 """ 393 log_debug(3) 394 395 # build the URL 396 url = CFG.RHN_PARENT or '' 397 url = parseUrl(url)[1].split(':')[0] 398 if CFG.USE_SSL: 399 url = 'https://' + url + '/XMLRPC' 400 else: 401 url = 'http://' + url + '/XMLRPC' 402 log_debug(3, 'server url: %s' % url) 403 404 if CFG.HTTP_PROXY: 405 serverObj = rpclib.Server(url, 406 proxy=CFG.HTTP_PROXY, 407 username=CFG.HTTP_PROXY_USERNAME, 408 password=CFG.HTTP_PROXY_PASSWORD) 409 else: 410 serverObj = rpclib.Server(url) 411 if CFG.USE_SSL and CFG.CA_CHAIN: 412 if not os.access(CFG.CA_CHAIN, os.R_OK): 413 log_error('ERROR: missing or cannot access (for ca_chain): %s' % CFG.CA_CHAIN) 414 raise rhnFault(1000, 415 _("Spacewalk Proxy error (file access issues). " 416 "Please contact your system administrator. " 417 "Please refer to Spacewalk Proxy logs.")) 418 serverObj.add_trusted_cert(CFG.CA_CHAIN) 419 serverObj.add_header('X-RHN-Client-Version', 2) 420 return serverObj
421
422 - def __cache_proxy_key(self):
423 return 'p' + str(self.__serverid) + sha1(self.hostname).hexdigest()
424
425 - def getProxyServerId(self):
426 return self.__serverid
427
428 429 -def get_auth_shelf():
430 if CFG.USE_LOCAL_AUTH: 431 return AuthLocalBackend() 432 server, port = CFG.AUTH_CACHE_SERVER.split(':') 433 port = int(port) 434 return rhnAuthCacheClient.Shelf((server, port))
435
436 437 -class AuthLocalBackend:
438 _cache_prefix = "proxy-auth" 439
440 - def __init__(self):
441 pass
442
443 - def has_key(self, key):
444 rkey = self._compute_key(key) 445 return rhnCache.has_key(rkey)
446
447 - def __getitem__(self, key):
448 rkey = self._compute_key(key) 449 # We want a dictionary-like behaviour, so if the key is not present, 450 # raise an exception (that's what missing_is_null=0 does) 451 val = rhnCache.get(rkey, missing_is_null=0) 452 return val
453
454 - def __setitem__(self, key, val):
455 rkey = self._compute_key(key) 456 return rhnCache.set(rkey, val)
457
458 - def __delitem__(self, key):
459 rkey = self._compute_key(key) 460 return rhnCache.delete(rkey)
461
462 - def _compute_key(self, key):
463 return os.path.join(self._cache_prefix, os.path.basename(str(key)))
464
465 - def __len__(self):
466 pass
467 468 # ============================================================================== 469