def _get_usertenant(name, tenant=False): """_get_usertenant Convenience function to parse name into username and tenant. If tenant is explicitly passed in, then name must be the username tenant name with '/' is forbidden. If '/' is seen in name, tenant is assumed to preface the /. If the username is a tenant name, then it is to be the implied administrator account a tenant gets. Otherwise, just assume a user in the default tenant """ if not isinstance(name, bytes): name = name.encode('utf-8') if not isinstance(tenant, bool): # if not boolean, it must be explicit tenant user = name elif b'/' in name: # tenant scoped name tenant, user = name.split(b'/', 1) elif configmanager.is_tenant(name): # the account is the implicit tenant owner account user = name tenant = name else: # assume it is a non-tenant user account user = name tenant = None user = util.stringify(user) if tenant: tenant = util.stringify(tenant) yield user yield tenant
def _webconfignet(self, wc, nodename): cfg = self.configmanager cd = cfg.get_node_attributes( nodename, ['hardwaremanagement.manager']) smmip = cd.get(nodename, {}).get('hardwaremanagement.manager', {}).get('value', None) if smmip and ':' not in smmip: smmip = getaddrinfo(smmip, 0)[0] smmip = smmip[-1][0] if smmip and ':' in smmip: raise exc.NotImplementedException('IPv6 not supported') wc.request('POST', '/data', 'get=hostname') rsp = wc.getresponse() rspdata = fromstring(util.stringify(rsp.read())) currip = rspdata.find('netConfig').find('ifConfigEntries').find( 'ifConfig').find('v4IPAddr').text if currip == smmip: return netconfig = netutil.get_nic_config(cfg, nodename, ip=smmip) netmask = netutil.cidr_to_mask(netconfig['prefix']) setdata = 'set=ifIndex:0,v4DHCPEnabled:0,v4IPAddr:{0},v4NetMask:{1}'.format(smmip, netmask) gateway = netconfig.get('ipv4_gateway', None) if gateway: setdata += ',v4Gateway:{0}'.format(gateway) wc.request('POST', '/data', setdata) rsp = wc.getresponse() rspdata = util.stringify(rsp.read()) if '<statusCode>0' not in rspdata: raise Exception("Error configuring SMM Network") return if smmip and ':' in smmip and not smmip.startswith('fe80::'): raise exc.NotImplementedException('IPv6 configuration TODO') if self.ipaddr.startswith('fe80::'): cfg.set_node_attributes( {nodename: {'hardwaremanagement.manager': self.ipaddr}})
def get_webclient(self, username, password, newpassword): wc = self._wc.dupe() try: wc.connect() except socket.error as se: if se.errno != errno.ECONNREFUSED: raise return (None, None) pwdchanged = False adata = json.dumps({'username': util.stringify(username), 'password': util.stringify(password) }) headers = {'Connection': 'keep-alive', 'Content-Type': 'application/json'} wc.request('POST', '/api/login', adata, headers) rsp = wc.getresponse() if rsp.status != 200 and password == 'PASSW0RD': rsp.read() adata = json.dumps({ 'username': username, 'password': newpassword, }) headers = {'Connection': 'keep-alive', 'Content-Type': 'application/json'} wc.request('POST', '/api/login', adata, headers) rsp = wc.getresponse() if rsp.status == 200: pwdchanged = True password = newpassword else: rsp.read() return (None, None) if rsp.status == 200: self._currcreds = (username, password) wc.set_basic_credentials(username, password) rspdata = json.loads(rsp.read()) wc.set_header('Content-Type', 'application/json') wc.set_header('Authorization', 'Bearer ' + rspdata['access_token']) if '_csrf_token' in wc.cookies: wc.set_header('X-XSRF-TOKEN', wc.cookies['_csrf_token']) if rspdata.get('pwchg_required', None) == 'true': wc.request('POST', '/api/function', json.dumps( {'USER_UserPassChange': '1,{0}'.format(newpassword)})) rsp = wc.getresponse() rsp.read() if rsp.status != 200: return (None, None) self._currcreds = (username, newpassword) wc.set_basic_credentials(username, newpassword) pwdchanged = True if '_csrf_token' in wc.cookies: wc.set_header('X-XSRF-TOKEN', wc.cookies['_csrf_token']) if pwdchanged: # Remove the minimum change interval, to allow sane # password changes after provisional changes wc = self.wc self.set_password_policy('', wc) return (wc, pwdchanged) return (None, None)
def _webconfigcreds(self, username, password): wc = webclient.SecureHTTPConnection(self.ipaddr, 443, verifycallback=self._validate_cert) wc.connect() authdata = { # start by trying factory defaults 'user': '******', 'password': '******', } headers = {'Connection': 'keep-alive', 'Content-Type': 'application/x-www-form-urlencoded'} wc.request('POST', '/data/login', urlencode(authdata), headers) rsp = wc.getresponse() rspdata = util.stringify(rsp.read()) if 'authResult>0' not in rspdata: # default credentials are refused, try with the actual authdata['user'] = username authdata['password'] = password wc.request('POST', '/data/login', urlencode(authdata), headers) rsp = wc.getresponse() rspdata = util.stringify(rsp.read()) if 'renew_account' in rspdata: raise Exception('Configured password has expired') if 'authResult>0' not in rspdata: raise Exception('Unknown username/password on SMM') tokens = fromstring(rspdata) st2 = tokens.findall('st2')[0].text wc.set_header('ST2', st2) return wc if 'renew_account' in rspdata: passwdchange = {'oripwd': 'PASSW0RD', 'newpwd': password} tokens = fromstring(rspdata) st2 = tokens.findall('st2')[0].text wc.set_header('ST2', st2) wc.request('POST', '/data/changepwd', urlencode(passwdchange)) rsp = wc.getresponse() rspdata = rsp.read() authdata['password'] = password wc.request('POST', '/data/login', urlencode(authdata), headers) rsp = wc.getresponse() rspdata = util.stringify(rsp.read()) if 'authResult>0' in rspdata: tokens = fromstring(rspdata) st2 = tokens.findall('st2')[0].text wc.set_header('ST2', st2) if username == 'USERID': return wc wc.request('POST', '/data', 'set=user(2,1,{0},511,,4,15,0)'.format(username)) rsp = wc.getresponse() rspdata = rsp.read() wc.request('POST', '/data/logout') rsp = wc.getresponse() rspdata = rsp.read() authdata['user'] = username wc.request('POST', '/data/login', urlencode(authdata, headers)) rsp = wc.getresponse() rspdata = rsp.read() tokens = fromstring(rspdata) st2 = tokens.findall('st2')[0].text wc.set_header('ST2', st2) return wc
def _convert_sha256account(self, user, passwd, wc): # First check if the specified user is sha256... userinfo = wc.grab_json_response('/api/dataset/imm_users') curruser = None uid = None user = util.stringify(user) passwd = util.stringify(passwd) for userent in userinfo['items'][0]['users']: if userent['users_user_name'] == user: curruser = userent break if curruser.get('users_pass_is_sha256', 0): self._wc = None wc = self.wc nwc = wc.dupe() # Have to convert it for being useful with most Lenovo automation tools # This requires deleting the account entirely and trying again tmpuid = self._get_next_userid(wc) try: tpass = base64.b64encode(os.urandom(9)) + 'Iw47$' userparams = "{0},6pmu0ezczzcp,{1},1,4,0,0,0,0,,8,".format(tmpuid, tpass) result = wc.grab_json_response('/api/function', {'USER_UserCreate': userparams}) wc.grab_json_response('/api/providers/logout') adata = json.dumps({ 'username': '******', 'password': tpass, }) headers = {'Connection': 'keep-alive', 'Content-Type': 'application/json'} nwc.request('POST', '/api/login', adata, headers) rsp = nwc.getresponse() if rsp.status == 200: rspdata = json.loads(rsp.read()) nwc.set_header('Content-Type', 'application/json') nwc.set_header('Authorization', 'Bearer ' + rspdata['access_token']) if '_csrf_token' in wc.cookies: nwc.set_header('X-XSRF-TOKEN', wc.cookies['_csrf_token']) if rspdata.get('reason', False): newpass = base64.b64encode(os.urandom(9)) + 'q4J$' nwc.grab_json_response( '/api/function', {'USER_UserPassChange': '{0},{1}'.format(tmpuid, newpass)}) nwc.grab_json_response('/api/function', {'USER_UserDelete': "{0},{1}".format(curruser['users_user_id'], user)}) userparams = "{0},{1},{2},1,4,0,0,0,0,,8,".format(curruser['users_user_id'], user, tpass) nwc.grab_json_response('/api/function', {'USER_UserCreate': userparams}) nwc.grab_json_response('/api/providers/logout') nwc, pwdchanged = self.get_webclient(user, tpass, passwd) if not pwdchanged: nwc.grab_json_response( '/api/function', {'USER_UserPassChange': '{0},{1}'.format(curruser['users_user_id'], passwd)}) nwc.grab_json_response('/api/providers/logout') finally: self._wc = None wc = self.wc wc.grab_json_response('/api/function', {'USER_UserDelete': "{0},{1}".format(tmpuid, '6pmu0ezczzcp')}) wc.grab_json_response('/api/providers/logout')
def handle_client(self, client, peer): try: if not netutil.address_is_local(peer[0]): client.close() return client.send(b'\xc2\xd1-\xa8\x80\xd8j\xba') tlv = bytearray(client.recv(2)) if tlv[0] != 1: client.close() return nodename = util.stringify(client.recv(tlv[1])) tlv = bytearray(client.recv(2)) apiarmed = self.cfm.get_node_attributes(nodename, 'deployment.apiarmed') apiarmed = apiarmed.get(nodename, {}).get('deployment.apiarmed', {}).get( 'value', None) if not apiarmed: client.close() return if apiarmed not in ('once', 'continuous'): now = datetime.datetime.utcnow() expiry = datetime.datetime.strptime(apiarmed, "%Y-%m-%dT%H:%M:%SZ") if now > expiry: self.cfm.set_node_attributes({nodename: {'deployment.apiarmed': ''}}) client.close() return client.send(b'\x02\x20') rttoken = os.urandom(32) client.send(rttoken) client.send(b'\x00\x00') tlv = bytearray(client.recv(2)) if tlv[0] != 3: client.close() return echotoken = client.recv(tlv[1]) if echotoken != rttoken: client.close() return tlv = bytearray(client.recv(2)) if tlv[0] != 4: client.close() return echotoken = util.stringify(client.recv(tlv[1])) cfgupdate = {nodename: {'crypted.selfapikey': {'hashvalue': echotoken}, 'deployment.apiarmed': ''}} if apiarmed == 'continuous': del cfgupdate[nodename]['deployment.apiarmed'] self.cfm.set_node_attributes(cfgupdate) client.recv(2) # drain end of message client.send(b'\x05\x00') # report success finally: client.close()
def scan(self): slpattrs = self.info.get('attributes', {}) self.isdense = False try: ff = slpattrs.get('enclosure-form-factor', [''])[0] except IndexError: return wronguuid = slpattrs.get('node-uuid', [''])[0] if wronguuid: # we need to fix the first three portions of the uuid uuidprefix = wronguuid.split('-')[:3] uuidprefix = codecs.encode(struct.pack( '<IHH', *[int(x, 16) for x in uuidprefix]), 'hex') uuidprefix = util.stringify(uuidprefix) uuidprefix = uuidprefix[:8] + '-' + uuidprefix[8:12] + '-' + \ uuidprefix[12:16] self.info['uuid'] = uuidprefix + '-' + '-'.join( wronguuid.split('-')[3:]) self.info['uuid'] = self.info['uuid'].lower() if ff not in ('dense-computing', 'BC2'): # do not probe unless it's a dense platform return self.isdense = True slot = int(slpattrs.get('slot', ['0'])[0]) if slot != 0: self.info['enclosure.bay'] = slot
def fixup_uuid(uuidprop): baduuid = ''.join(uuidprop.split()) uuidprefix = (baduuid[:8], baduuid[8:12], baduuid[12:16]) a = codecs.encode(struct.pack('<IHH', *[int(x, 16) for x in uuidprefix]), 'hex') a = util.stringify(a) uuid = (a[:8], a[8:12], a[12:16], baduuid[16:20], baduuid[20:]) return '-'.join(uuid).upper()
def update_neigh(): global neightable global neightime neightable = {} if os.name == 'nt': return ipn = subprocess.Popen(['ip', 'neigh'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (neighdata, err) = ipn.communicate() neighdata = util.stringify(neighdata) for entry in neighdata.split('\n'): entry = entry.split(' ') if len(entry) < 5 or not entry[4]: continue if entry[0] in ('192.168.0.100', '192.168.70.100', '192.168.70.125'): # Note that these addresses are common static ip addresses # that are hopelessly ambiguous if there are many # so ignore such entries and move on # ideally the system network steers clear of this landmine of # a subnet, but just in case continue if not _validmac.match(entry[4]): continue neightable[entry[0]] = entry[4] neightime = os.times()[4]
def _assemble_json(responses, resource=None, url=None, extension=None): #NOTE(jbjohnso) I'm considering giving up on yielding bit by bit #in json case over http. Notably, duplicate key values from plugin #overwrite, but we'd want to preserve them into an array instead. #the downside is that http would just always blurt it ll out at #once and hold on to all the data in memory links = {} if resource is not None: links['self'] = {"href": resource + extension} if url == '/': pass elif resource[-1] == '/': links['collection'] = {"href": "../" + extension} else: links['collection'] = {"href": "./" + extension} rspdata = {} for rsp in responses: if isinstance(rsp, confluent.messages.LinkRelation): haldata = rsp.raw() for hk in haldata: if 'href' in haldata[hk]: if isinstance(haldata[hk]['href'], int): haldata[hk]['href'] = str(haldata[hk]['href']) haldata[hk]['href'] += extension if hk in links: if isinstance(links[hk], list): links[hk].append(haldata[hk]) else: links[hk] = [links[hk], haldata[hk]] elif hk == 'item': links[hk] = [ haldata[hk], ] else: links[hk] = haldata[hk] else: rsp = rsp.raw() for dk in rsp: if dk in rspdata: if isinstance(rspdata[dk], list): if isinstance(rsp[dk], list): rspdata[dk].extend(rsp[dk]) else: rspdata[dk].append(rsp[dk]) else: rspdata[dk] = [rspdata[dk], rsp[dk]] else: if dk == 'databynode' or dk == 'asyncresponse': # a quirk, databynode suggests noderange # multi response. This should *always* be a list, # even if it will be length 1 rspdata[dk] = [rsp[dk]] else: rspdata[dk] = rsp[dk] rspdata["_links"] = links tlvdata.unicode_dictvalues(rspdata) yield util.stringify( json.dumps(rspdata, sort_keys=True, indent=4, ensure_ascii=False).encode('utf-8'))
def fixuuid(baduuid): # SMM dumps it out in hex uuidprefix = (baduuid[:8], baduuid[8:12], baduuid[12:16]) a = codecs.encode(struct.pack('<IHH', *[int(x, 16) for x in uuidprefix]), 'hex') a = util.stringify(a) uuid = (a[:8], a[8:12], a[12:16], baduuid[16:20], baduuid[20:]) return '-'.join(uuid).lower()
def grouplist(username): username = util.stringify(username) pent = pwd.getpwnam(username) try: groups = getgrouplist(pent.pw_name, pent.pw_gid) except TooSmallException as e: groups = getgrouplist(pent.pw_name, pent.pw_gid, e.count) return list(groups)
def _parse_attrlist(attrstr): attribs = {} previousattrlen = None attrstr = util.stringify(attrstr) while attrstr: if len(attrstr) == previousattrlen: raise Exception('Looping in attrstr parsing') previousattrlen = len(attrstr) if attrstr[0] == '(': if ')' not in attrstr: attribs['INCOMPLETE'] = True return attribs currattr = attrstr[1:attrstr.index(')')] if '=' not in currattr: # Not allegedly kosher, but still.. attribs[currattr] = None else: attrname, attrval = currattr.split('=', 1) attribs[attrname] = [] for val in attrval.split(','): if val[:3] == '\\FF': # we should make this bytes finalval = bytearray([]) for bnum in attrval[3:].split('\\'): if bnum == '': continue finalval.append(int(bnum, 16)) val = finalval if 'uuid' in attrname and len(val) == 16: lebytes = struct.unpack_from( '<IHH', memoryview(val[:8])) bebytes = struct.unpack_from( '>HHI', memoryview(val[8:])) val = '{0:08X}-{1:04X}-{2:04X}-{3:04X}-' \ '{4:04X}{5:08X}'.format( lebytes[0], lebytes[1], lebytes[2], bebytes[0], bebytes[1], bebytes[2] ).lower() attribs[attrname].append(val) attrstr = attrstr[attrstr.index(')'):] elif attrstr[0] == ','[0]: attrstr = attrstr[1:] elif ',' in attrstr: currattr = attrstr[:attrstr.index(',')] attribs[currattr] = None attrstr = attrstr[attrstr.index(','):] else: currattr = attrstr attribs[currattr] = None attrstr = None return attribs
def scan(self): slpattrs = self.info.get('attributes', {}) self.isdense = False try: ff = slpattrs.get('enclosure-form-factor', [''])[0] except IndexError: return wronguuid = slpattrs.get('node-uuid', [''])[0] if wronguuid: # we need to fix the first three portions of the uuid uuidprefix = wronguuid.split('-')[:3] uuidprefix = codecs.encode( struct.pack('<IHH', *[int(x, 16) for x in uuidprefix]), 'hex') uuidprefix = util.stringify(uuidprefix) uuidprefix = uuidprefix[:8] + '-' + uuidprefix[8:12] + '-' + \ uuidprefix[12:16] self.info['uuid'] = uuidprefix + '-' + '-'.join( wronguuid.split('-')[3:]) self.info['uuid'] = self.info['uuid'].lower() room = slpattrs.get('room-id', [None])[0] if room: self.info['room'] = room rack = slpattrs.get('rack-id', [None])[0] if rack: self.info['rack'] = rack name = slpattrs.get('name', [None])[0] if name: self.info['hostname'] = name unumber = slpattrs.get('lowest-u', [None])[0] if unumber: self.info['u'] = unumber location = slpattrs.get('location', [None])[0] if location: self.info['location'] = location if ff not in ('dense-computing', 'BC2'): # do not probe unless it's a dense platform return self.isdense = True encuuid = slpattrs.get('chassis-uuid', [None])[0] if encuuid: self.info['enclosure.uuid'] = encuuid slot = int(slpattrs.get('slot', ['0'])[0]) if slot != 0: self.info['enclosure.bay'] = slot
def _get_wc(self): authdata = { # start by trying factory defaults 'username': self.DEFAULT_USER, 'password': self.DEFAULT_PASS, } wc = webclient.SecureHTTPConnection(self.ipaddr, 443, verifycallback=self.validate_cert) wc.set_header('Content-Type', 'application/json') authmode = 0 if not self.trieddefault: rsp, status = wc.grab_json_response_with_status( '/api/session', authdata) if status == 403: wc.set_header('Content-Type', 'application/x-www-form-urlencoded') authmode = 1 rsp, status = wc.grab_json_response_with_status( '/api/session', urlencode(authdata)) else: authmode = 2 if status > 400: rsp = util.stringify(rsp) self.trieddefault = True if '555' in rsp: passchange = { 'Password': self.targpass, 'RetypePassword': self.targpass, 'param': 4, 'default_password': self.DEFAULT_PASS, 'username': self.DEFAULT_USER } if authmode == 2: rsp, status = wc.grab_json_response_with_status( '/api/reset-pass', passchange) else: rsp, status = wc.grab_json_response_with_status( '/api/reset-pass', urlencode(passchange)) authdata['password'] = self.targpass if authmode == 2: rsp, status = wc.grab_json_response_with_status( '/api/session', authdata) else: rsp, status = wc.grab_json_response_with_status( '/api/session', urlencode(authdata)) self.csrftok = rsp['CSRFToken'] self.channel = rsp['channel'] self.curruser = self.DEFAULT_USER self.currpass = self.targpass return wc else: self.curruser = self.DEFAULT_USER self.currpass = self.DEFAULT_PASS self.csrftok = rsp['CSRFToken'] self.channel = rsp['channel'] return wc if self.curruser: authdata['username'] = self.curruser authdata['password'] = self.currpass if authmode != 1: rsp, status = wc.grab_json_response_with_status( '/api/session', authdata) if authmode == 1 or rsp.status == 403: wc.set_header('Content-Type', 'application/x-www-form-urlencoded') rsp, status = wc.grab_json_response_with_status( '/api/session', urlencode(authdata)) if rsp.status != 200: return None self.csrftok = rsp['CSRFToken'] self.channel = rsp['channel'] return wc authdata['username'] = self.targuser authdata['password'] = self.targpass if authmode != 1: rsp, status = wc.grab_json_response_with_status( '/api/session', authdata) if authmode == 1 or rsp.status == 403: wc.set_header('Content-Type', 'application/x-www-form-urlencoded') rsp, status = wc.grab_json_response_with_status( '/api/session', urlencode(authdata)) if status != 200: return None self.curruser = self.targuser self.currpass = self.targpass self.csrftok = rsp['CSRFToken'] self.channel = rsp['channel'] return wc
def config(self, nodename): self.nodename = nodename creds = self.configmanager.get_node_attributes( nodename, ['secret.hardwaremanagementuser', 'secret.hardwaremanagementpassword', 'hardwaremanagement.manager', 'hardwaremanagement.method', 'console.method'], True) cd = creds.get(nodename, {}) user, passwd, _ = self.get_node_credentials( nodename, creds, self.DEFAULT_USER, self.DEFAULT_PASS) user = util.stringify(user) passwd = util.stringify(passwd) self.targuser = user self.targpass = passwd wc = self._get_wc() wc.set_header('X-CSRFTOKEN', self.csrftok) curruserinfo = {} authupdate = False wc.set_header('Content-Type', 'application/json') if user != self.curruser: authupdate = True if not curruserinfo: curruserinfo = wc.grab_json_response('/api/settings/users') authchg = curruserinfo[1] authchg['name'] = user if passwd != self.currpass: authupdate = True if not curruserinfo: curruserinfo = wc.grab_json_response('/api/settings/users') authchg = curruserinfo[1] authchg['changepassword'] = 0 authchg['password_size'] = 'bytes_20' authchg['password'] = passwd authchg['confirm_password'] = passwd if authupdate: rsp, status = wc.grab_json_response_with_status('/api/settings/users/2', authchg, method='PUT') if (cd.get('hardwaremanagement.method', {}).get('value', 'ipmi') != 'redfish' or cd.get('console.method', {}).get('value', None) == 'ipmi'): # IPMI must be enabled per user config wc.grab_json_response('/api/settings/ipmilanconfig', { 'ipv4_enable': 1, 'ipv6_enable': 1, 'uncheckedipv4lanEnable': 0, 'uncheckedipv6lanEnable': 0, 'checkedipv4lanEnable': 1, 'checkedipv6lanEnable': 1}) if ('hardwaremanagement.manager' in cd and cd['hardwaremanagement.manager']['value'] and not cd['hardwaremanagement.manager']['value'].startswith( 'fe80::')): newip = cd['hardwaremanagement.manager']['value'] newipinfo = getaddrinfo(newip, 0)[0] newip = newipinfo[-1][0] if ':' in newip: raise exc.NotImplementedException('IPv6 remote config TODO') currnet = wc.grab_json_response('/api/settings/network') for net in currnet: if net['channel_number'] == self.channel and net['lan_enable'] == 0: # ignore false indication and switch to 8 (dedicated) self.channel = 8 if net['channel_number'] == self.channel: # we have found the interface to potentially manipulate if net['ipv4_address'] != newip: netconfig = netutil.get_nic_config(self.configmanager, nodename, ip=newip) newmask = netutil.cidr_to_mask(netconfig['prefix']) net['ipv4_address'] = newip net['ipv4_subnet'] = newmask if netconfig['ipv4_gateway']: net['ipv4_gateway'] = netconfig['ipv4_gateway'] net['ipv4_dhcp_enable'] = 0 rsp, status = wc.grab_json_response_with_status( '/api/settings/network/{0}'.format(net['id']), net, method='PUT') break elif self.ipaddr.startswith('fe80::'): self.configmanager.set_node_attributes( {nodename: {'hardwaremanagement.manager': self.ipaddr}}) else: raise exc.TargetEndpointUnreachable( 'hardwaremanagement.manager must be set to desired address (No IPv6 Link Local detected)') rsp, status = wc.grab_json_response_with_status('/api/session', method='DELETE')
def _get_wc(self): authdata = { # start by trying factory defaults 'username': self.DEFAULT_USER, 'password': self.DEFAULT_PASS, } wc = webclient.SecureHTTPConnection(self.ipaddr, 443, verifycallback=self.validate_cert) wc.set_header('Content-Type', 'application/json') authmode = 0 if not self.trieddefault: rsp, status = wc.grab_json_response_with_status('/api/session', authdata) if status == 403: wc.set_header('Content-Type', 'application/x-www-form-urlencoded') authmode = 1 rsp, status = wc.grab_json_response_with_status('/api/session', urlencode(authdata)) else: authmode = 2 if status > 400: rsp = util.stringify(rsp) self.trieddefault = True if '555' in rsp: passchange = { 'Password': self.targpass, 'RetypePassword': self.targpass, 'param': 4, 'default_password': self.DEFAULT_PASS, 'username': self.DEFAULT_USER } if authmode == 2: passchange = { 'Password': self.targpass, } rwc = webclient.SecureHTTPConnection( self.ipaddr, 443, verifycallback=self.validate_cert) rwc.set_basic_credentials(authdata['username'], authdata['password']) rwc.set_header('If-Match', '*') rwc.set_header('Content-Type', 'application/json') rsp, status = rwc.grab_json_response_with_status( '/redfish/v1/AccountService/Accounts/1', passchange, method='PATCH') if status >= 200 and status < 300: authdata['password'] = self.targpass eventlet.sleep(10) else: raise Exception("Redfish may not have been ready yet") else: rsp, status = wc.grab_json_response_with_status('/api/reset-pass', urlencode(passchange)) authdata['password'] = self.targpass if authmode == 2: rsp, status = wc.grab_json_response_with_status('/api/session', authdata) else: rsp, status = wc.grab_json_response_with_status('/api/session', urlencode(authdata)) self.csrftok = rsp['CSRFToken'] self.channel = rsp['channel'] self.curruser = self.DEFAULT_USER self.currpass = self.targpass return wc else: self.curruser = self.DEFAULT_USER self.currpass = self.DEFAULT_PASS self.csrftok = rsp['CSRFToken'] self.channel = rsp['channel'] return wc if self.curruser: authdata['username'] = self.curruser authdata['password'] = self.currpass if authmode != 1: rsp, status = wc.grab_json_response_with_status('/api/session', authdata) if authmode == 1 or status == 403: wc.set_header('Content-Type', 'application/x-www-form-urlencoded') rsp, status = wc.grab_json_response_with_status('/api/session', urlencode(authdata)) if status != 200: return None self.csrftok = rsp['CSRFToken'] self.channel = rsp['channel'] return wc authdata['username'] = self.targuser authdata['password'] = self.targpass if authmode != 1: rsp, status = wc.grab_json_response_with_status('/api/session', authdata) if authmode == 1 or status == 403: wc.set_header('Content-Type', 'application/x-www-form-urlencoded') rsp, status = wc.grab_json_response_with_status('/api/session', urlencode(authdata)) if status != 200: return None self.curruser = self.targuser self.currpass = self.targpass self.csrftok = rsp['CSRFToken'] self.channel = rsp['channel'] return wc
def snoop(handler, byehandler=None, protocol=None, uuidlookup=None): """Watch for SSDP notify messages The handler shall be called on any service coming online. byehandler is called whenever a system advertises that it is departing. If no byehandler is specified, byebye messages are ignored. The handler is given (as possible), the mac address, a list of viable sockaddrs to reference the peer, and the notification type (e.g. 'urn:dmtf-org:service:redfish-rest:1' :param handler: A handler for online notifications from network :param byehandler: Optional handler for devices going off the network """ # Normally, I like using v6/v4 agnostic socket. However, since we are # dabbling in multicast wizardry here, such sockets can cause big problems, # so we will have two distinct sockets tracelog = log.Logger('trace') known_peers = set([]) net6 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) net6.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1) for ifidx in util.list_interface_indexes(): v6grp = ssdp6mcast + struct.pack('=I', ifidx) net6.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, v6grp) net6.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) net4 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) for i4 in util.list_ips(): ssdp4mcast = socket.inet_pton(socket.AF_INET, mcastv4addr) + \ socket.inet_aton(i4['addr']) try: net4.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, ssdp4mcast) except socket.error as e: if e.errno != 98: # errno 98 can happen if aliased, skip for now raise net4.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) net4.bind(('', 1900)) net6.bind(('', 1900)) peerbymacaddress = {} while True: try: newmacs = set([]) machandlers = {} r, _, _ = select.select((net4, net6), (), (), 60) while r: for s in r: (rsp, peer) = s.recvfrom(9000) if rsp[:4] == b'PING': continue rsp = rsp.split(b'\r\n') method, _, _ = rsp[0].split(b' ', 2) if method == b'NOTIFY': ip = peer[0].partition('%')[0] if peer in known_peers: continue if ip not in neighutil.neightable: neighutil.update_neigh() if ip not in neighutil.neightable: continue mac = neighutil.neightable[ip] known_peers.add(peer) newmacs.add(mac) if mac in peerbymacaddress: peerbymacaddress[mac]['addresses'].append(peer) else: peerbymacaddress[mac] = { 'hwaddr': mac, 'addresses': [peer], } peerdata = peerbymacaddress[mac] for headline in rsp[1:]: if not headline: continue headline = util.stringify(headline) header, _, value = headline.partition(':') header = header.strip() value = value.strip() if header == 'NT': peerdata['service'] = value elif header == 'NTS': if value == 'ssdp:byebye': machandlers[mac] = byehandler elif value == 'ssdp:alive': machandlers[mac] = None # handler elif method == b'M-SEARCH': if not uuidlookup: continue #ip = peer[0].partition('%')[0] for headline in rsp[1:]: if not headline: continue headline = util.stringify(headline) headline = headline.partition(':') if len(headline) < 3: continue if headline[0] == 'ST' and headline[-1].startswith( ' urn:xcat.org:service:confluent:'): try: cfm.check_quorum() except Exception: continue for query in headline[-1].split('/'): if query.startswith('uuid='): curruuid = query.split('=', 1)[1].lower() node = uuidlookup(curruuid) if not node: break # Do not bother replying to a node that # we have no deployment activity # planned for cfg = cfm.ConfigManager(None) cfd = cfg.get_node_attributes( node, [ 'deployment.pendingprofile', 'collective.managercandidates' ]) if not cfd.get(node, {}).get( 'deployment.pendingprofile', {}).get('value', None): break candmgrs = cfd.get(node, {}).get( 'collective.managercandidates', {}).get('value', None) if candmgrs: candmgrs = noderange.NodeRange( candmgrs, cfg).nodes if collective.get_myname( ) not in candmgrs: break currtime = time.time() seconds = int(currtime) msecs = int(currtime * 1000 % 1000) reply = 'HTTP/1.1 200 OK\r\nNODENAME: {0}\r\nCURRTIME: {1}\r\nCURRMSECS: {2}\r\n'.format( node, seconds, msecs) if '%' in peer[0]: iface = peer[0].split('%', 1)[1] reply += 'MGTIFACE: {0}\r\n'.format( peer[0].split('%', 1)[1]) ncfg = netutil.get_nic_config( cfg, node, ifidx=iface) if ncfg.get( 'matchesnodename', None): reply += 'DEFAULTNET: 1\r\n' elif not netutil.address_is_local( peer[0]): continue if not isinstance(reply, bytes): reply = reply.encode('utf8') s.sendto(reply, peer) r, _, _ = select.select((net4, net6), (), (), 0.2) for mac in newmacs: thehandler = machandlers.get(mac, None) if thehandler: thehandler(peerbymacaddress[mac]) except Exception: tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event, event=log.Events.stacktrace)
def resourcehandler_backend(env, start_response): """Function to handle new wsgi requests """ mimetype, extension = _pick_mimetype(env) headers = [('Content-Type', mimetype), ('Cache-Control', 'no-store'), ('Pragma', 'no-cache'), ('X-Content-Type-Options', 'nosniff'), ('Content-Security-Policy', "default-src 'self'"), ('X-XSS-Protection', '1; mode=block'), ('X-Frame-Options', 'deny'), ('Strict-Transport-Security', 'max-age=86400'), ('X-Permitted-Cross-Domain-Policies', 'none')] reqbody = None reqtype = None if 'CONTENT_LENGTH' in env and int(env['CONTENT_LENGTH']) > 0: reqbody = env['wsgi.input'].read(int(env['CONTENT_LENGTH'])) reqtype = env['CONTENT_TYPE'] operation = opmap[env['REQUEST_METHOD']] querydict = _get_query_dict(env, reqbody, reqtype) if 'restexplorerop' in querydict: operation = querydict['restexplorerop'] del querydict['restexplorerop'] authorized = _authorize_request(env, operation) if 'logout' in authorized: start_response('200 Successful logout', headers) yield ('{"result": "200 - Successful logout"}') return if 'HTTP_SUPPRESSAUTHHEADER' in env or 'HTTP_CONFLUENTAUTHTOKEN' in env: badauth = [('Content-type', 'text/plain')] else: badauth = [('Content-type', 'text/plain'), ('WWW-Authenticate', 'Basic realm="confluent"')] if authorized['code'] == 401: start_response('401 Authentication Required', badauth) yield 'authentication required' return if authorized['code'] == 403: start_response('403 Forbidden', badauth) yield 'Forbidden' return if authorized['code'] != 200: raise Exception("Unrecognized code from auth engine") headers.extend(("Set-Cookie", m.OutputString()) for m in authorized['cookie'].values()) cfgmgr = authorized['cfgmgr'] if (operation == 'create') and env['PATH_INFO'] == '/sessions/current/async': pagecontent = "" try: for rsp in _assemble_json( confluent.asynchttp.handle_async( env, querydict, httpsessions[authorized['sessionid']]['inflight'])): pagecontent += rsp start_response("200 OK", headers) if not isinstance(pagecontent, bytes): pagecontent = pagecontent.encode('utf-8') yield pagecontent return except exc.ConfluentException as e: if e.apierrorcode == 500: # raise generics to trigger the tracelog raise start_response('{0} {1}'.format(e.apierrorcode, e.apierrorstr), headers) yield e.get_error_body() elif (env['PATH_INFO'].endswith('/forward/web') and env['PATH_INFO'].startswith('/nodes/')): prefix, _, _ = env['PATH_INFO'].partition('/forward/web') _, _, nodename = prefix.rpartition('/') hm = cfgmgr.get_node_attributes(nodename, 'hardwaremanagement.manager') targip = hm.get(nodename, {}).get('hardwaremanagement.manager', {}).get('value', None) if not targip: start_response('404 Not Found', headers) yield 'No hardwaremanagemnet.manager defined for node' return funport = forwarder.get_port(targip, env['HTTP_X_FORWARDED_FOR'], authorized['sessionid']) host = env['HTTP_X_FORWARDED_HOST'] if ']' in host: host = host.split(']')[0] + ']' elif ':' in host: host = host.rsplit(':', 1)[0] url = 'https://{0}:{1}/'.format(host, funport) start_response('302', [('Location', url)]) yield 'Our princess is in another castle!' return elif (operation == 'create' and ('/console/session' in env['PATH_INFO'] or '/shell/sessions/' in env['PATH_INFO'])): #hard bake JSON into this path, do not support other incarnations if '/console/session' in env['PATH_INFO']: prefix, _, _ = env['PATH_INFO'].partition('/console/session') shellsession = False elif '/shell/sessions/' in env['PATH_INFO']: prefix, _, _ = env['PATH_INFO'].partition('/shell/sessions') shellsession = True _, _, nodename = prefix.rpartition('/') if 'session' not in querydict.keys() or not querydict['session']: auditmsg = { 'operation': 'start', 'target': env['PATH_INFO'], 'user': util.stringify(authorized['username']), } if 'tenant' in authorized: auditmsg['tenant'] = authorized['tenant'] auditlog.log(auditmsg) # Request for new session skipreplay = False if 'skipreplay' in querydict and querydict['skipreplay']: skipreplay = True width = querydict.get('width', 80) height = querydict.get('height', 24) datacallback = None async = None if 'HTTP_CONFLUENTASYNCID' in env: async = confluent.asynchttp.get_async(env, querydict) termrel = async .set_term_relation(env) datacallback = termrel.got_data try: if shellsession: consession = shellserver.ShellSession( node=nodename, configmanager=cfgmgr, username=authorized['username'], skipreplay=skipreplay, datacallback=datacallback, width=width, height=height) else: consession = consoleserver.ConsoleSession( node=nodename, configmanager=cfgmgr, username=authorized['username'], skipreplay=skipreplay, datacallback=datacallback, width=width, height=height) except exc.NotFoundException: start_response("404 Not found", headers) yield "404 - Request Path not recognized" return if not consession: start_response("500 Internal Server Error", headers) return sessid = _assign_consessionid(consession) if async: async .add_console_session(sessid) start_response('200 OK', headers) yield '{"session":"%s","data":""}' % sessid return elif 'bytes' in querydict.keys(): # not keycodes... myinput = querydict['bytes'] sessid = querydict['session'] if sessid not in consolesessions: start_response('400 Expired Session', headers) return consolesessions[sessid]['expiry'] = time.time() + 90 consolesessions[sessid]['session'].write(myinput) start_response('200 OK', headers) yield json.dumps({'session': querydict['session']}) return # client has requests to send or receive, not both... elif 'closesession' in querydict: consolesessions[querydict['session']]['session'].destroy() del consolesessions[querydict['session']] start_response('200 OK', headers) yield '{"sessionclosed": true}' return elif 'action' in querydict: if querydict['action'] == 'break': consolesessions[querydict['session']]['session'].send_break() elif querydict['action'] == 'resize': consolesessions[querydict['session']]['session'].resize( width=querydict['width'], height=querydict['height']) elif querydict['action'] == 'reopen': consolesessions[querydict['session']]['session'].reopen() else: start_response('400 Bad Request') yield 'Unrecognized action ' + querydict['action'] return start_response('200 OK', headers) yield json.dumps({'session': querydict['session']}) else: # no keys, but a session, means it's hooking to receive data sessid = querydict['session'] if sessid not in consolesessions: start_response('400 Expired Session', headers) yield '' return consolesessions[sessid]['expiry'] = time.time() + 90 # add our thread to the 'inflight' to have a hook to terminate # a long polling request loggedout = None mythreadid = greenlet.getcurrent() httpsessions[authorized['sessionid']]['inflight'].add(mythreadid) try: outdata = consolesessions[sessid]['session'].get_next_output( timeout=25) except greenlet.GreenletExit as ge: loggedout = ge httpsessions[authorized['sessionid']]['inflight'].discard( mythreadid) if sessid not in consolesessions: start_response('400 Expired Session', headers) yield '' return if loggedout is not None: consolesessions[sessid]['session'].destroy() start_response('401 Logged out', headers) yield '{"loggedout": 1}' return bufferage = False if 'stampsent' not in consolesessions[sessid]: consolesessions[sessid]['stampsent'] = True bufferage = consolesessions[sessid]['session'].get_buffer_age() if isinstance(outdata, dict): rspdata = outdata rspdata['session'] = querydict['session'] else: rspdata = {'session': querydict['session'], 'data': outdata} if bufferage is not False: rspdata['bufferage'] = bufferage try: rsp = json.dumps(rspdata) except UnicodeDecodeError: try: rsp = json.dumps(rspdata, encoding='cp437') except UnicodeDecodeError: rsp = json.dumps({ 'session': querydict['session'], 'data': 'DECODEERROR' }) start_response('200 OK', headers) yield rsp return else: # normal request url = env['PATH_INFO'] url = url.replace('.json', '') url = url.replace('.html', '') if url == '/sessions/current/info': start_response('200 OK', headers) sessinfo = {'username': authorized['username']} if 'authtoken' in authorized: sessinfo['authtoken'] = authorized['authtoken'] tlvdata.unicode_dictvalues(sessinfo) yield json.dumps(sessinfo) return resource = '.' + url[url.rindex('/'):] lquerydict = copy.deepcopy(querydict) try: hdlr = pluginapi.handle_path(url, operation, cfgmgr, querydict) if 'HTTP_CONFLUENTASYNCID' in env: confluent.asynchttp.run_handler(hdlr, env) start_response('202 Accepted', headers) yield 'Request queued' return pagecontent = "" if mimetype == 'text/html': for datum in _assemble_html(hdlr, resource, lquerydict, url, extension): pagecontent += datum else: for datum in _assemble_json(hdlr, resource, url, extension): pagecontent += datum start_response('200 OK', headers) if not isinstance(pagecontent, bytes): pagecontent = pagecontent.encode('utf-8') yield pagecontent except exc.ConfluentException as e: if ((not isinstance(e, exc.LockedCredentials)) and e.apierrorcode == 500): # raise generics to trigger the tracelog raise start_response('{0} {1}'.format(e.apierrorcode, e.apierrorstr), headers) yield e.get_error_body()
def handle_connection(connection, cert, request, local=False): global currentleader global retrythread operation = request['operation'] if cert: cert = crypto.dump_certificate(crypto.FILETYPE_ASN1, cert) else: if not local: return if operation in ('show', 'delete'): if not list(cfm.list_collective()): tlvdata.send( connection, { 'collective': { 'error': 'Collective mode not ' 'enabled on this ' 'system' } }) return if follower: linfo = cfm.get_collective_member_by_address(currentleader) remote = socket.create_connection((currentleader, 13001)) remote = ssl.wrap_socket(remote, cert_reqs=ssl.CERT_NONE, keyfile='/etc/confluent/privkey.pem', certfile='/etc/confluent/srvcert.pem') cert = remote.getpeercert(binary_form=True) if not (linfo and util.cert_matches(linfo['fingerprint'], cert)): remote.close() tlvdata.send(connection, { 'error': 'Invalid certificate, ' 'redo invitation process' }) connection.close() return tlvdata.recv(remote) # ignore banner tlvdata.recv(remote) # ignore authpassed: 0 tlvdata.send(remote, { 'collective': { 'operation': 'getinfo', 'name': get_myname() } }) collinfo = tlvdata.recv(remote) else: collinfo = {} populate_collinfo(collinfo) try: cfm.check_quorum() collinfo['quorum'] = True except exc.DegradedCollective: collinfo['quorum'] = False if operation == 'show': tlvdata.send(connection, {'collective': collinfo}) elif operation == 'delete': todelete = request['member'] if (todelete == collinfo['leader'] or todelete in collinfo['active']): tlvdata.send( connection, { 'collective': { 'error': '{0} is still active, stop the confluent service to remove it' .format(todelete) } }) return if todelete not in collinfo['offline']: tlvdata.send( connection, { 'collective': { 'error': '{0} is not a recognized collective member'. format(todelete) } }) return cfm.del_collective_member(todelete) tlvdata.send( connection, { 'collective': { 'status': 'Successfully deleted {0}'.format(todelete) } }) connection.close() return if 'invite' == operation: try: cfm.check_quorum() except exc.DegradedCollective: tlvdata.send(connection, { 'collective': { 'error': 'Collective does not have quorum' } }) return #TODO(jjohnson2): Cannot do the invitation if not the head node, the certificate hand-carrying #can't work in such a case. name = request['name'] invitation = invites.create_server_invitation(name) tlvdata.send(connection, {'collective': { 'invitation': invitation }}) connection.close() if 'join' == operation: invitation = request['invitation'] try: invitation = base64.b64decode(invitation) name, invitation = invitation.split(b'@', 1) name = util.stringify(name) except Exception: tlvdata.send( connection, {'collective': { 'status': 'Invalid token format' }}) connection.close() return host = request['server'] try: remote = socket.create_connection((host, 13001)) # This isn't what it looks like. We do CERT_NONE to disable # openssl verification, but then use the invitation as a # shared secret to validate the certs as part of the join # operation remote = ssl.wrap_socket(remote, cert_reqs=ssl.CERT_NONE, keyfile='/etc/confluent/privkey.pem', certfile='/etc/confluent/srvcert.pem') except Exception: tlvdata.send( connection, { 'collective': { 'status': 'Failed to connect to {0}'.format(host) } }) connection.close() return mycert = util.get_certificate_from_file( '/etc/confluent/srvcert.pem') cert = remote.getpeercert(binary_form=True) proof = base64.b64encode( invites.create_client_proof(invitation, mycert, cert)) tlvdata.recv(remote) # ignore banner tlvdata.recv(remote) # ignore authpassed: 0 tlvdata.send(remote, { 'collective': { 'operation': 'enroll', 'name': name, 'hmac': proof } }) rsp = tlvdata.recv(remote) if 'error' in rsp: tlvdata.send(connection, {'collective': { 'status': rsp['error'] }}) connection.close() return proof = rsp['collective']['approval'] proof = base64.b64decode(proof) j = invites.check_server_proof(invitation, mycert, cert, proof) if not j: remote.close() tlvdata.send(connection, {'collective': { 'status': 'Bad server token' }}) connection.close() return tlvdata.send(connection, {'collective': {'status': 'Success'}}) connection.close() currentleader = rsp['collective']['leader'] f = open('/etc/confluent/cfg/myname', 'w') f.write(name) f.close() log.log({ 'info': 'Connecting to collective due to join', 'subsystem': 'collective' }) eventlet.spawn_n(connect_to_leader, rsp['collective']['fingerprint'], name) if 'enroll' == operation: #TODO(jjohnson2): error appropriately when asked to enroll, but the master is elsewhere mycert = util.get_certificate_from_file('/etc/confluent/srvcert.pem') proof = base64.b64decode(request['hmac']) myrsp = invites.check_client_proof(request['name'], mycert, cert, proof) if not myrsp: tlvdata.send(connection, {'error': 'Invalid token'}) connection.close() return myrsp = base64.b64encode(myrsp) fprint = util.get_fingerprint(cert) myfprint = util.get_fingerprint(mycert) cfm.add_collective_member(get_myname(), connection.getsockname()[0], myfprint) cfm.add_collective_member(request['name'], connection.getpeername()[0], fprint) myleader = get_leader(connection) ldrfprint = cfm.get_collective_member_by_address( myleader)['fingerprint'] tlvdata.send( connection, { 'collective': { 'approval': myrsp, 'fingerprint': ldrfprint, 'leader': get_leader(connection) } }) if 'assimilate' == operation: drone = request['name'] droneinfo = cfm.get_collective_member(drone) if not droneinfo: tlvdata.send( connection, {'error': 'Unrecognized leader, ' 'redo invitation process'}) return if not util.cert_matches(droneinfo['fingerprint'], cert): tlvdata.send( connection, {'error': 'Invalid certificate, ' 'redo invitation process'}) return if request['txcount'] < cfm._txcount: tlvdata.send( connection, { 'error': 'Refusing to be assimilated by inferior' 'transaction count', 'txcount': cfm._txcount, }) return if connecting.active: # don't try to connect while actively already trying to connect tlvdata.send(connection, {'status': 0}) connection.close() return if (currentleader == connection.getpeername()[0] and follower and not follower.dead): # if we are happily following this leader already, don't stir # the pot tlvdata.send(connection, {'status': 0}) connection.close() return log.log({ 'info': 'Connecting in response to assimilation', 'subsystem': 'collective' }) eventlet.spawn_n(connect_to_leader, None, None, leader=connection.getpeername()[0]) tlvdata.send(connection, {'status': 0}) connection.close() if 'getinfo' == operation: drone = request['name'] droneinfo = cfm.get_collective_member(drone) if not (droneinfo and util.cert_matches(droneinfo['fingerprint'], cert)): tlvdata.send( connection, {'error': 'Invalid certificate, ' 'redo invitation process'}) connection.close() return collinfo = {} populate_collinfo(collinfo) tlvdata.send(connection, collinfo) if 'connect' == operation: drone = request['name'] droneinfo = cfm.get_collective_member(drone) if not (droneinfo and util.cert_matches(droneinfo['fingerprint'], cert)): tlvdata.send( connection, {'error': 'Invalid certificate, ' 'redo invitation process'}) connection.close() return myself = connection.getsockname()[0] if connecting.active: tlvdata.send(connection, { 'error': 'Connecting right now', 'backoff': True }) connection.close() return if myself != get_leader(connection): tlvdata.send( connection, { 'error': 'Cannot assimilate, our leader is ' 'in another castle', 'leader': currentleader }) connection.close() return if request['txcount'] > cfm._txcount: retire_as_leader() tlvdata.send( connection, { 'error': 'Client has higher tranasaction count, ' 'should assimilate me, connecting..', 'txcount': cfm._txcount }) log.log({ 'info': 'Connecting to leader due to superior ' 'transaction count', 'subsystem': collective }) eventlet.spawn_n(connect_to_leader, None, None, connection.getpeername()[0]) connection.close() return if retrythread: retrythread.cancel() retrythread = None with leader_init: cfm.update_collective_address(request['name'], connection.getpeername()[0]) tlvdata.send(connection, cfm._dump_keys(None, False)) tlvdata.send(connection, cfm._cfgstore['collective']) tlvdata.send(connection, {}) # cfm.get_globals()) cfgdata = cfm.ConfigManager(None)._dump_to_json() tlvdata.send(connection, { 'txcount': cfm._txcount, 'dbsize': len(cfgdata) }) connection.sendall(cfgdata) #tlvdata.send(connection, {'tenants': 0}) # skip the tenants for now, # so far unused anyway if not cfm.relay_slaved_requests(drone, connection): if not retrythread: # start a recovery if everyone else seems # to have disappeared retrythread = eventlet.spawn_after(30 + random.random(), start_collective)
def _authorize_request(env, operation): """Grant/Deny access based on data from wsgi env """ authdata = None name = '' sessionid = None cookie = Cookie.SimpleCookie() element = env['PATH_INFO'] if element.startswith('/sessions/current/'): element = None if 'HTTP_COOKIE' in env: #attempt to use the cookie. If it matches cc = RobustCookie() cc.load(env['HTTP_COOKIE']) if 'confluentsessionid' in cc: sessionid = cc['confluentsessionid'].value sessid = sessionid if sessionid in httpsessions: if _csrf_valid(env, httpsessions[sessionid]): if env['PATH_INFO'] == '/sessions/current/logout': targets = [] for mythread in httpsessions[sessionid]['inflight']: targets.append(mythread) for mythread in targets: eventlet.greenthread.kill(mythread) forwarder.close_session(sessionid) del httpsessions[sessionid] return ('logout', ) httpsessions[sessionid]['expiry'] = time.time() + 90 name = httpsessions[sessionid]['name'] authdata = auth.authorize( name, element=element, operation=operation, skipuserobj=httpsessions[sessionid]['skipuserobject']) if (not authdata) and 'HTTP_AUTHORIZATION' in env: if env['PATH_INFO'] == '/sessions/current/logout': if 'HTTP_REFERER' in env: # note that this doesn't actually do harm # otherwise, but this way do not give appearance # of something having a side effect if it has the smell # of a CSRF return {'code': 401} return ('logout', ) name, passphrase = base64.b64decode(env['HTTP_AUTHORIZATION'].replace( 'Basic ', '')).split(b':', 1) authdata = auth.check_user_passphrase(name, passphrase, operation=operation, element=element) if authdata is False: return {'code': 403} elif not authdata: return {'code': 401} sessid = util.randomstring(32) while sessid in httpsessions: sessid = util.randomstring(32) httpsessions[sessid] = { 'name': name, 'expiry': time.time() + 90, 'skipuserobject': authdata[4], 'inflight': set([]) } if 'HTTP_CONFLUENTAUTHTOKEN' in env: httpsessions[sessid]['csrftoken'] = util.randomstring(32) cookie['confluentsessionid'] = util.stringify(sessid) cookie['confluentsessionid']['secure'] = 1 cookie['confluentsessionid']['httponly'] = 1 cookie['confluentsessionid']['path'] = '/' skiplog = _should_skip_authlog(env) if authdata: auditmsg = { 'user': util.stringify(name), 'operation': operation, 'target': env['PATH_INFO'], } authinfo = { 'code': 200, 'cookie': cookie, 'cfgmgr': authdata[1], 'username': authdata[2], 'userdata': authdata[0] } if authdata[3] is not None: auditmsg['tenant'] = authdata[3] authinfo['tenant'] = authdata[3] auditmsg['user'] = util.stringify(authdata[2]) if sessid is not None: authinfo['sessionid'] = sessid if not skiplog: auditlog.log(auditmsg) if 'csrftoken' in httpsessions[sessid]: authinfo['authtoken'] = httpsessions[sessid]['csrftoken'] return authinfo elif authdata is None: return {'code': 401} else: return {'code': 403}