Exemple #1
0
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
Exemple #2
0
 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}})
Exemple #3
0
 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)
Exemple #4
0
 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
Exemple #5
0
 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')
Exemple #6
0
 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()
Exemple #7
0
 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
Exemple #8
0
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()
Exemple #9
0
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]
Exemple #10
0
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'))
Exemple #11
0
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()
Exemple #12
0
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)
Exemple #13
0
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
Exemple #14
0
 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
Exemple #15
0
 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
Exemple #16
0
 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')
Exemple #17
0
 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
Exemple #18
0
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)
Exemple #19
0
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()
Exemple #20
0
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)
Exemple #21
0
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}