Beispiel #1
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')
         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 = 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
             }})
Beispiel #2
0
 def _webconfignet(self, wc, nodename):
     cfg = self.configmanager
     if 'service:lenovo-smm2' in self.info.get('services', []):
         # need to enable ipmi for now..
         wc.request('POST', '/data', 'set=DoCmd(0x06,0x40,0x01,0x82,0x84)')
         rsp = wc.getresponse()
         rsp.read()
         wc.request('POST', '/data', 'set=DoCmd(0x06,0x40,0x01,0x42,0x44)')
         rsp = wc.getresponse()
         rsp.read()
     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
             }})
Beispiel #3
0
 def _bmcconfig(self, nodename, reset=False, customconfig=None, vc=None):
     # TODO(jjohnson2): set ip parameters, user/pass, alert cfg maybe
     # In general, try to use https automation, to make it consistent
     # between hypothetical secure path and today.
     creds = self.configmanager.get_node_attributes(nodename, [
         'secret.hardwaremanagementuser',
         'secret.hardwaremanagementpassword'
     ],
                                                    decrypt=True)
     user = creds.get(nodename, {}).get('secret.hardwaremanagementuser',
                                        {}).get('value', None)
     passwd = creds.get(nodename,
                        {}).get('secret.hardwaremanagementpassword',
                                {}).get('value', None)
     try:
         ic = self._get_ipmicmd()
         passwd = self.DEFAULT_PASS
     except pygexc.IpmiException as pi:
         havecustomcreds = False
         if user is not None and user != self.DEFAULT_USER:
             havecustomcreds = True
         else:
             user = self.DEFAULT_USER
         if passwd is not None and passwd != self.DEFAULT_PASS:
             havecustomcreds = True
         else:
             passwd = self.DEFAULT_PASS
         if havecustomcreds:
             ic = self._get_ipmicmd(user, passwd)
         else:
             raise
     if vc:
         ic.register_key_handler(vc)
     currusers = ic.get_users()
     lanchan = ic.get_network_channel()
     userdata = ic.xraw_command(netfn=6, command=0x44, data=(lanchan, 1))
     userdata = bytearray(userdata['data'])
     maxusers = userdata[0] & 0b111111
     enabledusers = userdata[1] & 0b111111
     lockedusers = userdata[2] & 0b111111
     cfg = self.configmanager
     cd = cfg.get_node_attributes(nodename, [
         'secret.hardwaremanagementuser',
         'secret.hardwaremanagementpassword', 'hardwaremanagement.manager'
     ], True)
     cd = cd.get(nodename, {})
     if ('secret.hardwaremanagementuser' not in cd
             or 'secret.hardwaremanagementpassword' not in cd):
         raise exc.TargetEndpointBadCredentials(
             'secret.hardwaremanagementuser and/or '
             'secret.hardwaremanagementpassword was not configured')
     newuser = cd['secret.hardwaremanagementuser']['value']
     newpass = cd['secret.hardwaremanagementpassword']['value']
     for uid in currusers:
         if currusers[uid]['name'] == newuser:
             # Use existing account that has been created
             newuserslot = uid
             if newpass != passwd:  # don't mess with existing if no change
                 ic.set_user_password(newuserslot, password=newpass)
                 ic = self._get_ipmicmd(user, passwd)
                 if vc:
                     ic.register_key_handler(vc)
             break
     else:
         newuserslot = lockedusers + 1
         if newuserslot < 2:
             newuserslot = 2
         if newpass != passwd:  # don't mess with existing if no change
             ic.set_user_password(newuserslot, password=newpass)
         ic.set_user_name(newuserslot, newuser)
         if havecustomcreds:
             ic = self._get_ipmicmd(user, passwd)
             if vc:
                 ic.register_key_handler(vc)
         #We are remote operating on the account we are
         #using, no need to try to set user access
         #ic.set_user_access(newuserslot, lanchan,
         #                   privilege_level='administrator')
     # Now to zap others
     for uid in currusers:
         if uid != newuserslot:
             if uid <= lockedusers:  # we cannot delete, settle for disable
                 ic.disable_user(uid, 'disable')
             else:
                 # lead with the most critical thing, removing user access
                 ic.set_user_access(uid,
                                    channel=None,
                                    callback=False,
                                    link_auth=False,
                                    ipmi_msg=False,
                                    privilege_level='no_access')
                 # next, try to disable the password
                 ic.set_user_password(uid, mode='disable', password=None)
                 # ok, now we can be less paranoid
                 try:
                     ic.user_delete(uid)
                 except pygexc.IpmiException as ie:
                     if ie.ipmicode != 0xd5:  # some response to the 0xff
                         # name...
                         # the user will remain, but that is life
                         raise
     if customconfig:
         customconfig(ic)
     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]
         # This getaddrinfo is repeated in get_nic_config, could be
         # optimized, albeit with a more convoluted api..
         newip = newipinfo[-1][0]
         if ':' in newip:
             raise exc.NotImplementedException('IPv6 remote config TODO')
         netconfig = netutil.get_nic_config(cfg, nodename, ip=newip)
         plen = netconfig['prefix']
         newip = '{0}/{1}'.format(newip, plen)
         currcfg = ic.get_net_configuration()
         if currcfg['ipv4_address'] != newip:
             # do not change the ipv4_config if the current config looks
             # like it is already accurate
             ic.set_net_configuration(
                 ipv4_address=newip,
                 ipv4_configuration='static',
                 ipv4_gateway=netconfig['ipv4_gateway'])
     elif self.ipaddr.startswith('fe80::'):
         cfg.set_node_attributes(
             {nodename: {
                 'hardwaremanagement.manager': self.ipaddr
             }})
     else:
         raise exc.TargetEndpointUnreachable(
             'hardwaremanagement.manager must be set to desired address')
     if reset:
         ic.reset_bmc()
     return ic
Beispiel #4
0
 def config(self, nodename, reset=False):
     self.nodename = nodename
     # TODO(jjohnson2): set ip parameters, user/pass, alert cfg maybe
     # In general, try to use https automation, to make it consistent
     # between hypothetical secure path and today.
     dpp = self.configmanager.get_node_attributes(
         nodename, 'discovery.passwordrules')
     strruleset = dpp.get(nodename, {}).get(
         'discovery.passwordrules', {}).get('value', '')
     wc = self.wc
     creds = self.configmanager.get_node_attributes(
         self.nodename, ['secret.hardwaremanagementuser',
         'secret.hardwaremanagementpassword'], decrypt=True)
     user, passwd, isdefault = self.get_node_credentials(nodename, creds, 'USERID', 'PASSW0RD')
     self.set_password_policy(strruleset)
     if self._atdefaultcreds:
         if not isdefault:
             self._setup_xcc_account(user, passwd, wc)
     self._convert_sha256account(user, passwd, wc)
     cd = self.configmanager.get_node_attributes(
         nodename, ['secret.hardwaremanagementuser',
                    'secret.hardwaremanagementpassword',
                    'hardwaremanagement.manager', 'hardwaremanagement.method', 'console.method'],
                    True)
     cd = cd.get(nodename, {})
     if (cd.get('hardwaremanagement.method', {}).get('value', 'ipmi') != 'redfish'
             or cd.get('console.method', {}).get('value', None) == 'ipmi'):
         nwc = wc.dupe()
         nwc.set_basic_credentials(self._currcreds[0], self._currcreds[1])
         rsp = nwc.grab_json_response('/redfish/v1/Managers/1/NetworkProtocol')
         if not rsp.get('IPMI', {}).get('ProtocolEnabled', True):
             # User has indicated IPMI support, but XCC is currently disabled
             # change XCC to be consistent
             _, _ = nwc.grab_json_response_with_status(
                     '/redfish/v1/Managers/1/NetworkProtocol',
                     {'IPMI': {'ProtocolEnabled': True}}, method='PATCH')
     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')
         netconfig = netutil.get_nic_config(self.configmanager, nodename, ip=newip)
         newmask = netutil.cidr_to_mask(netconfig['prefix'])
         currinfo = wc.grab_json_response('/api/providers/logoninfo')
         currip = currinfo.get('items', [{}])[0].get('ipv4_address', '')
         # do not change the ipv4_config if the current config looks right already
         if currip != newip:
             statargs = {
                 'ENET_IPv4Ena': '1', 'ENET_IPv4AddrSource': '0',
                 'ENET_IPv4StaticIPAddr': newip, 'ENET_IPv4StaticIPNetMask': newmask
                 }
             if netconfig['ipv4_gateway']:
                 statargs['ENET_IPv4GatewayIPAddr'] = netconfig['ipv4_gateway']
             wc.grab_json_response('/api/dataset', statargs)
     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)')
     wc.grab_json_response('/api/providers/logout')
     ff = self.info.get('attributes', {}).get('enclosure-form-factor', '')
     if ff not in ('dense-computing', [u'dense-computing']):
         return
     enclosureuuid = self.info.get('attributes', {}).get('chassis-uuid', [None])[0]
     if enclosureuuid:
         enclosureuuid = enclosureuuid.lower()
         em = self.configmanager.get_node_attributes(nodename,
                                                     'enclosure.manager')
         em = em.get(nodename, {}).get('enclosure.manager', {}).get(
             'value', None)
         # ok, set the uuid of the manager...
         if em:
             self.configmanager.set_node_attributes(
                 {em: {'id.uuid': enclosureuuid}})
Beispiel #5
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)
Beispiel #6
0
def check_reply(node, info, packet, sock, cfg, reqview):
    httpboot = info['architecture'] == 'uefi-httpboot'
    replen = 275  # default is going to be 286
    cfd = cfg.get_node_attributes(node, ('deployment.*'))
    profile = cfd.get(node, {}).get('deployment.pendingprofile',
                                    {}).get('value', None)
    myipn = info['netinfo']['recvip']
    myipn = socket.inet_aton(myipn)
    if not profile:
        return
    rqtype = packet[53][0]
    insecuremode = cfd.get(node, {}).get('deployment.useinsecureprotocols',
                                         'never')
    if not insecuremode:
        insecuremode = 'never'
    if insecuremode == 'never' and not httpboot:
        if rqtype == 1 and info['architecture']:
            log.log({
                'info':
                'Boot attempt by {0} detected in insecure mode, but '
                'insecure mode is disabled.  Set the attribute '
                '`deployment.useinsecureprotocols` to `firmware` or '
                '`always` to enable support, or use UEFI HTTP boot '
                'with HTTPS.'.format(node)
            })
        return
    reply = bytearray(512)
    repview = memoryview(reply)
    repview[:20] = iphdr
    repview[12:16] = myipn
    repview[20:28] = udphdr
    repview = repview[28:]
    repview[0:1] = b'\x02'
    repview[1:10] = reqview[1:10]  # duplicate txid, hwlen, and others
    repview[10:11] = b'\x80'  # always set broadcast
    repview[28:44] = reqview[28:44]  # copy chaddr field
    if httpboot:
        proto = 'https' if insecuremode == 'never' else 'http'
        bootfile = '{0}://{1}/confluent-public/os/{2}/boot.img'.format(
            proto, info['netinfo']['recvip'], profile)
        if not isinstance(bootfile, bytes):
            bootfile = bootfile.encode('utf8')
        repview[108:108 + len(bootfile)] = bootfile
    repview[20:24] = myipn
    gateway = None
    netmask = None
    niccfg = netutil.get_nic_config(cfg, node, ifidx=info['netinfo']['ifidx'])
    if niccfg.get('ipv4_broken', False):
        # Received a request over a nic with no ipv4 configured, ignore it
        return
    clipn = None
    if niccfg['ipv4_address']:
        clipn = socket.inet_aton(niccfg['ipv4_address'])
        repview[16:20] = clipn
        gateway = niccfg['ipv4_gateway']
        if gateway:
            gateway = socket.inet_aton(gateway)
        netmask = niccfg['prefix']
        netmask = (2**32 - 1) ^ (2**(32 - netmask) - 1)
        netmask = struct.pack('!I', netmask)
    repview[236:240] = b'\x63\x82\x53\x63'
    repview[240:242] = b'\x35\x01'
    if rqtype == 1:  # if discover, then offer
        repview[242:243] = b'\x02'
    elif rqtype == 3:  # if request, then ack
        repview[242:243] = b'\x05'
    repview[243:245] = b'\x36\x04'  # DHCP server identifier
    repview[245:249] = myipn
    repview[249:255] = b'\x33\x04\x00\x00\x00\xf0'  # fixed short lease time
    repview[255:257] = b'\x61\x11'
    repview[257:274] = packet[97]
    # Note that sending PXEClient kicks off the proxyDHCP procedure, ignoring
    # boot filename and such in the DHCP packet
    # we will simply always do it to provide the boot payload in a consistent
    # matter to both dhcp-elsewhere and fixed ip clients
    if info['architecture'] == 'uefi-httpboot':
        repview[replen - 1:replen + 11] = b'\x3c\x0aHTTPClient'
        replen += 12
    else:
        repview[replen - 1:replen + 10] = b'\x3c\x09PXEClient'
        replen += 11
    hwlen = bytearray(reqview[2:3].tobytes())[0]
    fulladdr = repview[28:28 + hwlen].tobytes()
    myipbypeer[fulladdr] = myipn
    if hwlen == 8:  # omnipath may present a mangled proxydhcp request later
        shortaddr = bytearray(6)
        shortaddr[0] = 2
        shortaddr[1:] = fulladdr[3:]
        myipbypeer[bytes(shortaddr)] = myipn
    if netmask:
        repview[replen - 1:replen + 1] = b'\x01\x04'
        repview[replen + 1:replen + 5] = netmask
        replen += 6
    if gateway:
        repview[replen - 1:replen + 1] = b'\x03\x04'
        repview[replen + 1:replen + 5] = gateway
        replen += 6
    repview[replen -
            1:replen] = b'\xff'  # end of options, should always be last byte
    repview = memoryview(reply)
    pktlen = struct.pack('!H', replen + 28)  # ip+udp = 28
    repview[2:4] = pktlen
    curripsum = ~(_ipsum(constiphdrsum + pktlen + myipn)) & 0xffff
    repview[10:12] = struct.pack('!H', curripsum)
    repview[24:26] = struct.pack('!H', replen + 8)
    datasum = _ipsum(b'\x00\x11' + repview[24:26].tobytes() +
                     repview[12:replen + 28].tobytes())
    datasum = ~datasum & 0xffff
    repview[26:28] = struct.pack('!H', datasum)
    if clipn:
        staticassigns[fulladdr] = (clipn, repview[:replen + 28].tobytes())
    elif fulladdr in staticassigns:
        del staticassigns[fulladdr]
    send_raw_packet(repview, replen + 28, reqview, info)
Beispiel #7
0
def handle_request(env, start_response):
    global currtz
    global keymap
    global currlocale
    global currtzvintage
    configmanager.check_quorum()
    nodename = env.get('HTTP_CONFLUENT_NODENAME', None)
    apikey = env.get('HTTP_CONFLUENT_APIKEY', None)
    if not (nodename and apikey):
        start_response('401 Unauthorized', [])
        yield 'Unauthorized'
        return
    cfg = configmanager.ConfigManager(None)
    ea = cfg.get_node_attributes(nodename,
                                 ['crypted.selfapikey', 'deployment.apiarmed'])
    eak = ea.get(nodename, {}).get('crypted.selfapikey',
                                   {}).get('hashvalue', None)
    if not eak:
        start_response('401 Unauthorized', [])
        yield 'Unauthorized'
        return
    salt = '$'.join(eak.split('$', 3)[:-1]) + '$'
    if crypt.crypt(apikey, salt) != eak:
        start_response('401 Unauthorized', [])
        yield 'Unauthorized'
        return
    if ea.get(nodename, {}).get('deployment.apiarmed', {}).get('value',
                                                               None) == 'once':
        cfg.set_node_attributes({nodename: {'deployment.apiarmed': ''}})
    retype = env.get('HTTP_ACCEPT', 'application/yaml')
    isgeneric = False
    if retype == '*/*':
        isgeneric = True
        retype = 'application/yaml'
    if retype == 'application/yaml':
        dumper = yamldump
    elif retype == 'application/json':
        dumper = json.dumps
    else:
        start_response('406 Not supported', [])
        yield 'Unsupported content type in ACCEPT: ' + retype
        return
    operation = env['REQUEST_METHOD']
    if operation not in ('HEAD', 'GET') and 'CONTENT_LENGTH' in env and int(
            env['CONTENT_LENGTH']) > 0:
        reqbody = env['wsgi.input'].read(int(env['CONTENT_LENGTH']))
    if env['PATH_INFO'] == '/self/bmcconfig':
        hmattr = cfg.get_node_attributes(nodename, 'hardwaremanagement.*')
        hmattr = hmattr.get(nodename, {})
        res = {}
        port = hmattr.get('hardwaremanagement.port', {}).get('value', None)
        if port is not None:
            res['bmcport'] = port
        vlan = hmattr.get('hardwaremanagement.vlan', {}).get('value', None)
        if vlan is not None:
            res['bmcvlan'] = vlan
        bmcaddr = hmattr.get('hardwaremanagement.manager',
                             {}).get('value', None)
        bmcaddr = socket.getaddrinfo(bmcaddr, 0)[0]
        bmcaddr = bmcaddr[-1][0]
        if '.' in bmcaddr:  # ipv4 is allowed
            netconfig = netutil.get_nic_config(cfg, nodename, ip=bmcaddr)
            res['bmcipv4'] = bmcaddr
            res['prefixv4'] = netconfig['prefix']
            res['bmcgw'] = netconfig.get('ipv4_gateway', None)
        # credential security results in user/password having to be deferred
        start_response('200 OK', (('Content-Type', retype), ))
        yield dumper(res)
    elif env['PATH_INFO'] == '/self/deploycfg':
        if 'HTTP_CONFLUENT_MGTIFACE' in env:
            ncfg = netutil.get_nic_config(cfg,
                                          nodename,
                                          ifidx=env['HTTP_CONFLUENT_MGTIFACE'])
        else:
            myip = env.get('HTTP_X_FORWARDED_HOST', None)
            if ']' in myip:
                myip = myip.split(']', 1)[0]
            else:
                myip = myip.split(':', 1)[0]
            myip = myip.replace('[', '').replace(']', '')
            ncfg = netutil.get_nic_config(cfg, nodename, serverip=myip)
        if ncfg['prefix']:
            ncfg['ipv4_netmask'] = netutil.cidr_to_mask(ncfg['prefix'])
        if ncfg['ipv4_method'] == 'firmwaredhcp':
            ncfg['ipv4_method'] = 'static'
        deployinfo = cfg.get_node_attributes(
            nodename,
            ('deployment.*', 'console.method', 'crypted.*', 'dns.*', 'ntp.*'))
        deployinfo = deployinfo.get(nodename, {})
        profile = deployinfo.get('deployment.pendingprofile',
                                 {}).get('value', '')
        ncfg['encryptboot'] = deployinfo.get('deployment.encryptboot',
                                             {}).get('value', None)
        if ncfg['encryptboot'] in ('', 'none'):
            ncfg['encryptboot'] = None
        ncfg['profile'] = profile
        protocol = deployinfo.get('deployment.useinsecureprotocols',
                                  {}).get('value', 'never')
        ncfg['textconsole'] = bool(
            deployinfo.get('console.method', {}).get('value', None))
        if protocol == 'always':
            ncfg['protocol'] = 'http'
        else:
            ncfg['protocol'] = 'https'
        ncfg['rootpassword'] = deployinfo.get('crypted.rootpassword',
                                              {}).get('hashvalue', None)
        ncfg['grubpassword'] = deployinfo.get('crypted.grubpassword',
                                              {}).get('grubhashvalue', None)
        if currtzvintage and currtzvintage > (time.time() - 30.0):
            ncfg['timezone'] = currtz
        else:
            langinfo = subprocess.check_output(['localectl',
                                                'status']).split(b'\n')
            for line in langinfo:
                line = line.strip()
                if line.startswith(b'System Locale:'):
                    ccurrlocale = line.split(b'=')[-1]
                    if not ccurrlocale:
                        continue
                    if not isinstance(ccurrlocale, str):
                        ccurrlocale = ccurrlocale.decode('utf8')
                    if ccurrlocale == 'n/a':
                        continue
                    currlocale = ccurrlocale
                elif line.startswith(b'VC Keymap:'):
                    ckeymap = line.split(b':')[-1]
                    ckeymap = ckeymap.strip()
                    if not ckeymap:
                        continue
                    if not isinstance(ckeymap, str):
                        ckeymap = ckeymap.decode('utf8')
                    if ckeymap == 'n/a':
                        continue
                    keymap = ckeymap
            tdc = subprocess.check_output(['timedatectl']).split(b'\n')
            for ent in tdc:
                ent = ent.strip()
                if ent.startswith(b'Time zone:'):
                    currtz = ent.split(b': ', 1)[1].split(b'(', 1)[0].strip()
                    if not isinstance(currtz, str):
                        currtz = currtz.decode('utf8')
                    currtzvintage = time.time()
                    ncfg['timezone'] = currtz
                    break
        ncfg['locale'] = currlocale
        ncfg['keymap'] = keymap
        ncfg['nameservers'] = []
        for dns in deployinfo.get('dns.servers', {}).get('value',
                                                         '').split(','):
            ncfg['nameservers'].append(dns)
        ntpsrvs = deployinfo.get('ntp.servers', {}).get('value', '')
        if ntpsrvs:
            ntpsrvs = ntpsrvs.split(',')
        if ntpsrvs:
            ncfg['ntpservers'] = []
            for ntpsrv in ntpsrvs:
                ncfg['ntpservers'].append(ntpsrv)
        dnsdomain = deployinfo.get('dns.domain', {}).get('value', None)
        ncfg['dnsdomain'] = dnsdomain
        start_response('200 OK', (('Content-Type', retype), ))
        yield dumper(ncfg)
    elif env['PATH_INFO'] == '/self/sshcert' and reqbody:
        if not sshutil.ca_exists():
            start_response('500 Unconfigured', ())
            yield 'CA is not configured on this system (run ...)'
            return
        pals = get_extra_names(nodename, cfg)
        cert = sshutil.sign_host_key(reqbody, nodename, pals)
        start_response('200 OK', (('Content-Type', 'text/plain'), ))
        yield cert
    elif env['PATH_INFO'] == '/self/nodelist':
        nodes, _ = get_cluster_list(nodename, cfg)
        if isgeneric:
            start_response('200 OK', (('Content-Type', 'text/plain'), ))
            for node in util.natural_sort(nodes):
                yield node + '\n'
        else:
            start_response('200 OK', (('Content-Type', retype), ))
            yield dumper(sorted(nodes))
    elif env['PATH_INFO'] == '/self/remoteconfigbmc' and reqbody:
        try:
            reqbody = yaml.safe_load(reqbody)
        except Exception:
            reqbody = None
        cfgmod = reqbody.get('configmod', 'unspecified')
        if cfgmod == 'xcc':
            xcc.remote_nodecfg(nodename, cfg)
        elif cfgmod == 'tsm':
            tsm.remote_nodecfg(nodename, cfg)
        else:
            start_response('500 unsupported configmod', ())
            yield 'Unsupported configmod "{}"'.format(cfgmod)
        start_response('200 Ok', ())
        yield 'complete'
    elif env['PATH_INFO'] == '/self/updatestatus' and reqbody:
        update = yaml.safe_load(reqbody)
        if update['status'] == 'staged':
            targattr = 'deployment.stagedprofile'
        elif update['status'] == 'complete':
            targattr = 'deployment.profile'
        else:
            raise Exception('Unknown update status request')
        currattr = cfg.get_node_attributes(nodename,
                                           'deployment.*').get(nodename, {})
        pending = None
        if targattr == 'deployment.profile':
            pending = currattr.get('deployment.stagedprofile',
                                   {}).get('value', '')
        if not pending:
            pending = currattr.get('deployment.pendingprofile',
                                   {}).get('value', '')
        updates = {}
        if pending:
            updates['deployment.pendingprofile'] = {'value': ''}
            if targattr == 'deployment.profile':
                updates['deployment.stagedprofile'] = {'value': ''}
            currprof = currattr.get(targattr, {}).get('value', '')
            if currprof != pending:
                updates[targattr] = {'value': pending}
            cfg.set_node_attributes({nodename: updates})
            start_response('200 OK', (('Content-Type', 'text/plain'), ))
            yield 'OK'
        else:
            start_response('500 Error', (('Content-Type', 'text/plain'), ))
            yield 'No pending profile detected, unable to accept status update'
    elif env['PATH_INFO'] == '/self/saveapikey' and reqbody:
        if not isinstance(reqbody, str):
            reqbody = reqbody.decode('utf8')
        cfg.set_node_attributes(
            {nodename: {
                'deployment.sealedapikey': {
                    'value': reqbody
                }
            }})
        start_response('200 OK', ())
        yield ''
    elif env['PATH_INFO'].startswith(
            '/self/remoteconfig/') and 'POST' == operation:
        scriptcat = env['PATH_INFO'].replace('/self/remoteconfig/', '')
        slist, profile = get_scriptlist(
            scriptcat, cfg, nodename,
            '/var/lib/confluent/public/os/{0}/ansible/{1}')
        playlist = []
        dirname = '/var/lib/confluent/public/os/{0}/ansible/{1}/'.format(
            profile, scriptcat)
        if not os.path.isdir(dirname):
            dirname = '/var/lib/confluent/public/os/{0}/ansible/{1}.d/'.format(
                profile, scriptcat)
        for filename in slist:
            if filename.endswith('.yaml') or filename.endswith('.yml'):
                playlist.append(os.path.join(dirname, filename))
        if playlist:
            runansible.run_playbooks(playlist, [nodename])
            start_response('202 Queued', ())
            yield ''
        else:
            start_response('200 OK', ())
            yield ''
            return
    elif env['PATH_INFO'].startswith('/self/remotesyncfiles'):
        if 'POST' == operation:
            result = syncfiles.start_syncfiles(nodename, cfg,
                                               json.loads(reqbody))
            start_response(result, ())
            yield ''
            return
        if 'GET' == operation:
            status, output = syncfiles.get_syncresult(nodename)
            start_response(status, ())
            yield output
            return
    elif env['PATH_INFO'].startswith('/self/remoteconfig/status'):
        rst = runansible.running_status.get(nodename, None)
        if not rst:
            start_response('204 Not Running', (('Content-Length', '0'), ))
            yield ''
            return
        start_response('200 OK', ())
        if rst.complete:
            del runansible.running_status[nodename]
        yield rst.dump_text()
        return
    elif env['PATH_INFO'].startswith('/self/scriptlist/'):
        scriptcat = env['PATH_INFO'].replace('/self/scriptlist/', '')
        slist, _ = get_scriptlist(
            scriptcat, cfg, nodename,
            '/var/lib/confluent/public/os/{0}/scripts/{1}')
        if slist:
            start_response('200 OK', (('Content-Type', 'application/yaml'), ))
            yield yaml.safe_dump(util.natural_sort(slist),
                                 default_flow_style=False)
        else:
            start_response('200 OK', ())
            yield ''
    else:
        start_response('404 Not Found', ())
        yield 'Not found'
Beispiel #8
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')
Beispiel #9
0
def handle_request(env, start_response):
    global currtz
    global keymap
    global currlocale
    global currtzvintage
    configmanager.check_quorum()
    nodename = env.get('HTTP_CONFLUENT_NODENAME', None)
    apikey = env.get('HTTP_CONFLUENT_APIKEY', None)
    if not (nodename and apikey):
        start_response('401 Unauthorized', [])
        yield 'Unauthorized'
        return
    cfg = configmanager.ConfigManager(None)
    eak = cfg.get_node_attributes(nodename, 'crypted.selfapikey').get(
        nodename, {}).get('crypted.selfapikey', {}).get('hashvalue', None)
    if not eak:
        start_response('401 Unauthorized', [])
        yield 'Unauthorized'
        return
    salt = '$'.join(eak.split('$', 3)[:-1]) + '$'
    if crypt.crypt(apikey, salt) != eak:
        start_response('401 Unauthorized', [])
        yield 'Unauthorized'
        return
    retype = env.get('HTTP_ACCEPT', 'application/yaml')
    isgeneric = False
    if retype == '*/*':
        isgeneric = True
        retype = 'application/yaml'
    if retype == 'application/yaml':
        dumper = yamldump
    elif retype == 'application/json':
        dumper = json.dumps
    else:
        start_response('406 Not supported', [])
        yield 'Unsupported content type in ACCEPT: ' + retype
        return
    if env['REQUEST_METHOD'] not in (
            'HEAD', 'GET') and 'CONTENT_LENGTH' in env and int(
                env['CONTENT_LENGTH']) > 0:
        reqbody = env['wsgi.input'].read(int(env['CONTENT_LENGTH']))
    if env['PATH_INFO'] == '/self/deploycfg':
        if 'HTTP_CONFLUENT_MGTIFACE' in env:
            ncfg = netutil.get_nic_config(cfg,
                                          nodename,
                                          ifidx=env['HTTP_CONFLUENT_MGTIFACE'])
        else:
            myip = env.get('HTTP_X_FORWARDED_HOST', None)
            if ']' in myip:
                myip = myip.split(']', 1)[0]
            else:
                myip = myip.split(':', 1)[0]
            myip = myip.replace('[', '').replace(']', '')
            ncfg = netutil.get_nic_config(cfg, nodename, serverip=myip)
        if ncfg['prefix']:
            ncfg['ipv4_netmask'] = netutil.cidr_to_mask(ncfg['prefix'])
        deployinfo = cfg.get_node_attributes(
            nodename, ('deployment.*', 'console.method', 'crypted.*', 'dns.*'))
        deployinfo = deployinfo.get(nodename, {})
        profile = deployinfo.get('deployment.pendingprofile',
                                 {}).get('value', '')
        ncfg['encryptboot'] = deployinfo.get('deployment.encryptboot',
                                             {}).get('value', None)
        if ncfg['encryptboot'] in ('', 'none'):
            ncfg['encryptboot'] = None
        ncfg['profile'] = profile
        protocol = deployinfo.get('deployment.useinsecureprotocols',
                                  {}).get('value', 'never')
        ncfg['textconsole'] = bool(
            deployinfo.get('console.method', {}).get('value', None))
        if protocol == 'always':
            ncfg['protocol'] = 'http'
        else:
            ncfg['protocol'] = 'https'
        ncfg['rootpassword'] = deployinfo.get('crypted.rootpassword',
                                              {}).get('hashvalue', None)
        ncfg['grubpassword'] = deployinfo.get('crypted.grubpassword',
                                              {}).get('grubhashvalue', None)
        if currtzvintage and currtzvintage > (time.time() - 30.0):
            ncfg['timezone'] = currtz
        else:
            langinfo = subprocess.check_output(['localectl',
                                                'status']).split(b'\n')
            for line in langinfo:
                line = line.strip()
                if line.startswith(b'System Locale:'):
                    ccurrlocale = line.split(b'=')[-1]
                    if not ccurrlocale:
                        continue
                    if not isinstance(ccurrlocale, str):
                        ccurrlocale = ccurrlocale.decode('utf8')
                    if ccurrlocale == 'n/a':
                        continue
                    currlocale = ccurrlocale
                elif line.startswith(b'VC Keymap:'):
                    ckeymap = line.split(b':')[-1]
                    ckeymap = ckeymap.strip()
                    if not ckeymap:
                        continue
                    if not isinstance(ckeymap, str):
                        ckeymap = ckeymap.decode('utf8')
                    if ckeymap == 'n/a':
                        continue
                    keymap = ckeymap
            tdc = subprocess.check_output(['timedatectl']).split(b'\n')
            for ent in tdc:
                ent = ent.strip()
                if ent.startswith(b'Time zone:'):
                    currtz = ent.split(b': ', 1)[1].split(b'(', 1)[0].strip()
                    if not isinstance(currtz, str):
                        currtz = currtz.decode('utf8')
                    currtzvintage = time.time()
                    ncfg['timezone'] = currtz
                    break
        ncfg['locale'] = currlocale
        ncfg['keymap'] = keymap
        ncfg['nameservers'] = []
        for dns in deployinfo.get('dns.servers', {}).get('value',
                                                         '').split(','):
            ncfg['nameservers'].append(dns)
        dnsdomain = deployinfo.get('dns.domain', {}).get('value', None)
        ncfg['dnsdomain'] = dnsdomain
        start_response('200 OK', (('Content-Type', retype), ))
        yield dumper(ncfg)
    elif env['PATH_INFO'] == '/self/sshcert':
        if not sshutil.ca_exists():
            start_response('500 Unconfigured', ())
            yield 'CA is not configured on this system (run ...)'
            return
        dnsinfo = cfg.get_node_attributes(nodename, ('dns.*'))
        dnsinfo = dnsinfo.get(nodename, {}).get('dns.domain',
                                                {}).get('value', None)
        if dnsinfo in nodename:
            dnsinfo = ''
        cert = sshutil.sign_host_key(reqbody, nodename, [dnsinfo])
        start_response('200 OK', (('Content-Type', 'text/plain'), ))
        yield cert
    elif env['PATH_INFO'] == '/self/nodelist':
        nodes = set(cfg.list_nodes())
        domaininfo = cfg.get_node_attributes(nodes, 'dns.domain')
        for node in list(util.natural_sort(nodes)):
            domain = domaininfo.get(node, {}).get('dns.domain',
                                                  {}).get('value', None)
            if domain and domain not in node:
                nodes.add('{0}.{1}'.format(node, domain))
        for mgr in configmanager.list_collective():
            nodes.add(mgr)
            if domain and domain not in mgr:
                nodes.add('{0}.{1}'.format(mgr, domain))
        myname = collective.get_myname()
        nodes.add(myname)
        if domain and domain not in myname:
            nodes.add('{0}.{1}'.format(myname, domain))
        if isgeneric:
            start_response('200 OK', (('Content-Type', 'text/plain'), ))
            for node in util.natural_sort(nodes):
                yield node + '\n'
        else:
            start_response('200 OK', (('Content-Type', retype), ))
            yield dumper(sorted(nodes))
    elif env['PATH_INFO'] == '/self/updatestatus':
        update = yaml.safe_load(reqbody)
        if update['status'] == 'staged':
            targattr = 'deployment.stagedprofile'
        elif update['status'] == 'complete':
            targattr = 'deployment.profile'
        else:
            raise Exception('Unknown update status request')
        currattr = cfg.get_node_attributes(nodename,
                                           'deployment.*').get(nodename, {})
        pending = None
        if targattr == 'deployment.profile':
            pending = currattr.get('deployment.stagedprofile',
                                   {}).get('value', '')
        if not pending:
            pending = currattr.get('deployment.pendingprofile',
                                   {}).get('value', '')
        updates = {}
        if pending:
            updates['deployment.pendingprofile'] = {'value': ''}
            if targattr == 'deployment.profile':
                updates['deployment.stagedprofile'] = {'value': ''}
            currprof = currattr.get(targattr, {}).get('value', '')
            if currprof != pending:
                updates[targattr] = {'value': pending}
            cfg.set_node_attributes({nodename: updates})
            start_response('200 OK', (('Content-Type', 'text/plain'), ))
            yield 'OK'
        else:
            start_response('500 Error', (('Content-Type', 'text/plain'), ))
            yield 'No pending profile detected, unable to accept status update'
    else:
        start_response('404 Not Found', ())
        yield 'Not found'