Пример #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
             }})
Пример #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
             }})
Пример #3
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}})
Пример #4
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'
Пример #5
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')
Пример #6
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'