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 }})
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 }})
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
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}})
def snoop(handler, byehandler=None, protocol=None, uuidlookup=None): """Watch for SSDP notify messages The handler shall be called on any service coming online. byehandler is called whenever a system advertises that it is departing. If no byehandler is specified, byebye messages are ignored. The handler is given (as possible), the mac address, a list of viable sockaddrs to reference the peer, and the notification type (e.g. 'urn:dmtf-org:service:redfish-rest:1' :param handler: A handler for online notifications from network :param byehandler: Optional handler for devices going off the network """ # Normally, I like using v6/v4 agnostic socket. However, since we are # dabbling in multicast wizardry here, such sockets can cause big problems, # so we will have two distinct sockets tracelog = log.Logger('trace') known_peers = set([]) net6 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) net6.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1) for ifidx in util.list_interface_indexes(): v6grp = ssdp6mcast + struct.pack('=I', ifidx) net6.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, v6grp) net6.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) net4 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) for i4 in util.list_ips(): ssdp4mcast = socket.inet_pton(socket.AF_INET, mcastv4addr) + \ socket.inet_aton(i4['addr']) try: net4.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, ssdp4mcast) except socket.error as e: if e.errno != 98: # errno 98 can happen if aliased, skip for now raise net4.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) net4.bind(('', 1900)) net6.bind(('', 1900)) peerbymacaddress = {} while True: try: newmacs = set([]) machandlers = {} r, _, _ = select.select((net4, net6), (), (), 60) while r: for s in r: (rsp, peer) = s.recvfrom(9000) if rsp[:4] == b'PING': continue rsp = rsp.split(b'\r\n') method, _, _ = rsp[0].split(b' ', 2) if method == b'NOTIFY': ip = peer[0].partition('%')[0] if peer in known_peers: continue if ip not in neighutil.neightable: neighutil.update_neigh() if ip not in neighutil.neightable: continue mac = neighutil.neightable[ip] known_peers.add(peer) newmacs.add(mac) if mac in peerbymacaddress: peerbymacaddress[mac]['addresses'].append(peer) else: peerbymacaddress[mac] = { 'hwaddr': mac, 'addresses': [peer], } peerdata = peerbymacaddress[mac] for headline in rsp[1:]: if not headline: continue headline = util.stringify(headline) header, _, value = headline.partition(':') header = header.strip() value = value.strip() if header == 'NT': peerdata['service'] = value elif header == 'NTS': if value == 'ssdp:byebye': machandlers[mac] = byehandler elif value == 'ssdp:alive': machandlers[mac] = None # handler elif method == b'M-SEARCH': if not uuidlookup: continue #ip = peer[0].partition('%')[0] for headline in rsp[1:]: if not headline: continue headline = util.stringify(headline) headline = headline.partition(':') if len(headline) < 3: continue if headline[0] == 'ST' and headline[-1].startswith( ' urn:xcat.org:service:confluent:'): try: cfm.check_quorum() except Exception: continue for query in headline[-1].split('/'): if query.startswith('uuid='): curruuid = query.split('=', 1)[1].lower() node = uuidlookup(curruuid) if not node: break # Do not bother replying to a node that # we have no deployment activity # planned for cfg = cfm.ConfigManager(None) cfd = cfg.get_node_attributes( node, [ 'deployment.pendingprofile', 'collective.managercandidates' ]) if not cfd.get(node, {}).get( 'deployment.pendingprofile', {}).get('value', None): break candmgrs = cfd.get(node, {}).get( 'collective.managercandidates', {}).get('value', None) if candmgrs: candmgrs = noderange.NodeRange( candmgrs, cfg).nodes if collective.get_myname( ) not in candmgrs: break currtime = time.time() seconds = int(currtime) msecs = int(currtime * 1000 % 1000) reply = 'HTTP/1.1 200 OK\r\nNODENAME: {0}\r\nCURRTIME: {1}\r\nCURRMSECS: {2}\r\n'.format( node, seconds, msecs) if '%' in peer[0]: iface = peer[0].split('%', 1)[1] reply += 'MGTIFACE: {0}\r\n'.format( peer[0].split('%', 1)[1]) ncfg = netutil.get_nic_config( cfg, node, ifidx=iface) if ncfg.get( 'matchesnodename', None): reply += 'DEFAULTNET: 1\r\n' elif not netutil.address_is_local( peer[0]): continue if not isinstance(reply, bytes): reply = reply.encode('utf8') s.sendto(reply, peer) r, _, _ = select.select((net4, net6), (), (), 0.2) for mac in newmacs: thehandler = machandlers.get(mac, None) if thehandler: thehandler(peerbymacaddress[mac]) except Exception: tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event, event=log.Events.stacktrace)
def 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)
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'
def config(self, nodename): self.nodename = nodename creds = self.configmanager.get_node_attributes( nodename, ['secret.hardwaremanagementuser', 'secret.hardwaremanagementpassword', 'hardwaremanagement.manager', 'hardwaremanagement.method', 'console.method'], True) cd = creds.get(nodename, {}) user, passwd, _ = self.get_node_credentials( nodename, creds, self.DEFAULT_USER, self.DEFAULT_PASS) user = util.stringify(user) passwd = util.stringify(passwd) self.targuser = user self.targpass = passwd wc = self._get_wc() wc.set_header('X-CSRFTOKEN', self.csrftok) curruserinfo = {} authupdate = False wc.set_header('Content-Type', 'application/json') if user != self.curruser: authupdate = True if not curruserinfo: curruserinfo = wc.grab_json_response('/api/settings/users') authchg = curruserinfo[1] authchg['name'] = user if passwd != self.currpass: authupdate = True if not curruserinfo: curruserinfo = wc.grab_json_response('/api/settings/users') authchg = curruserinfo[1] authchg['changepassword'] = 0 authchg['password_size'] = 'bytes_20' authchg['password'] = passwd authchg['confirm_password'] = passwd if authupdate: rsp, status = wc.grab_json_response_with_status('/api/settings/users/2', authchg, method='PUT') if (cd.get('hardwaremanagement.method', {}).get('value', 'ipmi') != 'redfish' or cd.get('console.method', {}).get('value', None) == 'ipmi'): # IPMI must be enabled per user config wc.grab_json_response('/api/settings/ipmilanconfig', { 'ipv4_enable': 1, 'ipv6_enable': 1, 'uncheckedipv4lanEnable': 0, 'uncheckedipv6lanEnable': 0, 'checkedipv4lanEnable': 1, 'checkedipv6lanEnable': 1}) if ('hardwaremanagement.manager' in cd and cd['hardwaremanagement.manager']['value'] and not cd['hardwaremanagement.manager']['value'].startswith( 'fe80::')): newip = cd['hardwaremanagement.manager']['value'] newipinfo = getaddrinfo(newip, 0)[0] newip = newipinfo[-1][0] if ':' in newip: raise exc.NotImplementedException('IPv6 remote config TODO') currnet = wc.grab_json_response('/api/settings/network') for net in currnet: if net['channel_number'] == self.channel and net['lan_enable'] == 0: # ignore false indication and switch to 8 (dedicated) self.channel = 8 if net['channel_number'] == self.channel: # we have found the interface to potentially manipulate if net['ipv4_address'] != newip: netconfig = netutil.get_nic_config(self.configmanager, nodename, ip=newip) newmask = netutil.cidr_to_mask(netconfig['prefix']) net['ipv4_address'] = newip net['ipv4_subnet'] = newmask if netconfig['ipv4_gateway']: net['ipv4_gateway'] = netconfig['ipv4_gateway'] net['ipv4_dhcp_enable'] = 0 rsp, status = wc.grab_json_response_with_status( '/api/settings/network/{0}'.format(net['id']), net, method='PUT') break elif self.ipaddr.startswith('fe80::'): self.configmanager.set_node_attributes( {nodename: {'hardwaremanagement.manager': self.ipaddr}}) else: raise exc.TargetEndpointUnreachable( 'hardwaremanagement.manager must be set to desired address (No IPv6 Link Local detected)') rsp, status = wc.grab_json_response_with_status('/api/session', method='DELETE')
def 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'