diff --git a/confluent_client/bin/confetty b/confluent_client/bin/confetty index 011fdf9c..175b2e5a 100755 --- a/confluent_client/bin/confetty +++ b/confluent_client/bin/confetty @@ -961,8 +961,9 @@ def main(): except IOError: pass if powerstate is None or powertime < time.time() - 10: # Check powerstate every 10 seconds + if powerstate == None: + powerstate = True powertime = time.time() - powerstate = True check_power_state() else: currcommand = prompt() diff --git a/confluent_client/bin/nodesensors b/confluent_client/bin/nodesensors index 61d4d602..2222abfd 100755 --- a/confluent_client/bin/nodesensors +++ b/confluent_client/bin/nodesensors @@ -67,7 +67,7 @@ argparser.add_option('-n', '--numreadings', type='int', argparser.add_option('-c', '--csv', action='store_true', help='Output in CSV format') argparser.add_option('-s', '--skipnumberless', action='store_true', - help='Output in CSV format') + help='Do not show non-numeric sensors') (options, args) = argparser.parse_args() repeatmode = False if options.interval: diff --git a/confluent_client/confluent/textgroup.py b/confluent_client/confluent/textgroup.py index e2f0dc7f..2297c4e5 100644 --- a/confluent_client/confluent/textgroup.py +++ b/confluent_client/confluent/textgroup.py @@ -171,7 +171,9 @@ class GroupedData(object): self.byoutput[outdata]))) currout += '\n====================================\n' currout += outdata - currout += '\n\n' + if currout[-1] != '\n': + currout += '\n' + currout += '\n' output.write(currout) output.flush() @@ -211,7 +213,9 @@ class GroupedData(object): else: currout += '\n'.join(colordiff(modaloutput.split('\n'), outdata.split('\n'))) - currout += '\n\n' + if currout[-1] != '\n': + currout += '\n' + currout += '\n' if reverse: revoutput.append(currout) else: diff --git a/confluent_osdeploy/common/profile/scripts/confignet b/confluent_osdeploy/common/profile/scripts/confignet index 9092f631..71c156a7 100644 --- a/confluent_osdeploy/common/profile/scripts/confignet +++ b/confluent_osdeploy/common/profile/scripts/confignet @@ -226,7 +226,11 @@ class WickedManager(object): self.cfgbydev[devname] = currcfg for cfg in open(ifcfg).read().splitlines(): cfg = cfg.split('#', 1)[0] - kv = ' '.join(shlex.split(cfg)).split('=', 1) + try: + kv = ' '.join(shlex.split(cfg)).split('=', 1) + except Exception: + # unparseable line, likely having something we can't handle + del self.cfgbydev[devname] if len(kv) != 2: continue k, v = kv @@ -348,7 +352,8 @@ class NetworkManager(object): bondcfg[stg] = deats[stg] if member in self.uuidbyname: subprocess.check_call(['nmcli', 'c', 'del', self.uuidbyname[member]]) - subprocess.check_call(['nmcli', 'c', 'add', 'type', 'bond-slave', 'master', team, 'con-name', member, 'connection.interface-name', member]) + devtype = self.devtypes.get(member, 'bond-slave') + subprocess.check_call(['nmcli', 'c', 'add', 'type', devtype, 'master', team, 'con-name', member, 'connection.interface-name', member]) if bondcfg: args = [] for parm in bondcfg: diff --git a/confluent_osdeploy/common/profile/scripts/setupssh b/confluent_osdeploy/common/profile/scripts/setupssh index eb989bb7..06ae3e01 100644 --- a/confluent_osdeploy/common/profile/scripts/setupssh +++ b/confluent_osdeploy/common/profile/scripts/setupssh @@ -10,6 +10,15 @@ for pubkey in /etc/ssh/ssh_host*key.pub; do rm $certfile confluentpython $confapiclient /confluent-api/self/sshcert $pubkey -o $certfile done +if [ -d /etc/ssh/sshd_config.d/ -a ! -e /etc/ssh/sshd_config.d/90-confluent.conf ]; then + for cert in /etc/ssh/ssh*-cert.pub; do + echo HostCertificate $cert >> /etc/ssh/sshd_config.d/90-confluent.conf + done + echo HostbasedAuthentication yes >> /etc/ssh/sshd_config.d/90-confluent.conf + echo HostbasedUsesNameFromPacketOnly yes >> /etc/ssh/sshd_config.d/90-confluent.conf + echo IgnoreRhosts no >> /etc/ssh/sshd_config.d/90-confluent.conf +fi + TMPDIR=$(mktemp -d) cd $TMPDIR confluentpython $confapiclient /confluent-public/site/initramfs.tgz -o initramfs.tgz diff --git a/confluent_osdeploy/confluent_osdeploy.spec.tmpl b/confluent_osdeploy/confluent_osdeploy.spec.tmpl index 5faab31f..26beb74f 100644 --- a/confluent_osdeploy/confluent_osdeploy.spec.tmpl +++ b/confluent_osdeploy/confluent_osdeploy.spec.tmpl @@ -28,11 +28,15 @@ This contains support utilities for enabling deployment of x86_64 architecture s #cp start_root urlmount ../stateless-bin/ #cd .. ln -s el8 el9 -for os in rhvh4 el7 genesis el8 suse15 ubuntu18.04 ubuntu20.04 ubuntu22.04 ubuntu24.04 coreos el9; do +cp -a el8 el10 +mv el10/initramfs/usr el10/initramfs/var +for os in rhvh4 el7 genesis el8 suse15 ubuntu18.04 ubuntu20.04 ubuntu22.04 ubuntu24.04 coreos el9 el10; do mkdir ${os}out cd ${os}out if [ -d ../${os}bin ]; then cp -a ../${os}bin/opt . + elif [ $os = el10 ]; then + cp -a ../el9bin/opt . else cp -a ../el8bin/opt . fi @@ -78,7 +82,7 @@ cp -a esxi7 esxi8 %install mkdir -p %{buildroot}/opt/confluent/share/licenses/confluent_osdeploy/ cp LICENSE %{buildroot}/opt/confluent/share/licenses/confluent_osdeploy/ -for os in rhvh4 el7 el8 el9 genesis suse15 ubuntu20.04 ubuntu18.04 ubuntu22.04 ubuntu24.04 esxi6 esxi7 esxi8 coreos; do +for os in rhvh4 el7 el8 el9 el10 genesis suse15 ubuntu20.04 ubuntu18.04 ubuntu22.04 ubuntu24.04 esxi6 esxi7 esxi8 coreos; do mkdir -p %{buildroot}/opt/confluent/lib/osdeploy/$os/initramfs mkdir -p %{buildroot}/opt/confluent/lib/osdeploy/$os/profiles cp ${os}out/addons.* %{buildroot}/opt/confluent/lib/osdeploy/$os/initramfs diff --git a/confluent_osdeploy/el7-diskless/profiles/default/scripts/getinstalldisk b/confluent_osdeploy/el7-diskless/profiles/default/scripts/getinstalldisk index 04c7708e..c954a254 100644 --- a/confluent_osdeploy/el7-diskless/profiles/default/scripts/getinstalldisk +++ b/confluent_osdeploy/el7-diskless/profiles/default/scripts/getinstalldisk @@ -1,3 +1,4 @@ +#!/usr/bin/python3 import subprocess import os @@ -10,8 +11,9 @@ class DiskInfo(object): self.path = None self.model = '' self.size = 0 - self.driver = None + self.driver = '' self.mdcontainer = '' + self.subsystype = '' devnode = '/dev/{0}'.format(devname) qprop = subprocess.check_output( ['udevadm', 'info', '--query=property', devnode]) @@ -46,7 +48,9 @@ class DiskInfo(object): elif (k == 'DRIVERS' and not self.driver and v not in ('"sd"', '""')): self.driver = v.replace('"', '') - if not self.driver and 'imsm' not in self.mdcontainer: + elif k == 'ATTRS{subsystype}': + self.subsystype = v.replace('"', '') + if not self.driver and 'imsm' not in self.mdcontainer and self.subsystype != 'nvm': raise Exception("No driver detected") if os.path.exists('/sys/block/{0}/size'.format(self.name)): with open('/sys/block/{0}/size'.format(self.name), 'r') as sizesrc: diff --git a/confluent_osdeploy/el7/profiles/default/scripts/getinstalldisk b/confluent_osdeploy/el7/profiles/default/scripts/getinstalldisk index 04c7708e..c954a254 100644 --- a/confluent_osdeploy/el7/profiles/default/scripts/getinstalldisk +++ b/confluent_osdeploy/el7/profiles/default/scripts/getinstalldisk @@ -1,3 +1,4 @@ +#!/usr/bin/python3 import subprocess import os @@ -10,8 +11,9 @@ class DiskInfo(object): self.path = None self.model = '' self.size = 0 - self.driver = None + self.driver = '' self.mdcontainer = '' + self.subsystype = '' devnode = '/dev/{0}'.format(devname) qprop = subprocess.check_output( ['udevadm', 'info', '--query=property', devnode]) @@ -46,7 +48,9 @@ class DiskInfo(object): elif (k == 'DRIVERS' and not self.driver and v not in ('"sd"', '""')): self.driver = v.replace('"', '') - if not self.driver and 'imsm' not in self.mdcontainer: + elif k == 'ATTRS{subsystype}': + self.subsystype = v.replace('"', '') + if not self.driver and 'imsm' not in self.mdcontainer and self.subsystype != 'nvm': raise Exception("No driver detected") if os.path.exists('/sys/block/{0}/size'.format(self.name)): with open('/sys/block/{0}/size'.format(self.name), 'r') as sizesrc: diff --git a/confluent_osdeploy/el8-diskless/profiles/default/scripts/getinstalldisk b/confluent_osdeploy/el8-diskless/profiles/default/scripts/getinstalldisk index 04c7708e..c954a254 100644 --- a/confluent_osdeploy/el8-diskless/profiles/default/scripts/getinstalldisk +++ b/confluent_osdeploy/el8-diskless/profiles/default/scripts/getinstalldisk @@ -1,3 +1,4 @@ +#!/usr/bin/python3 import subprocess import os @@ -10,8 +11,9 @@ class DiskInfo(object): self.path = None self.model = '' self.size = 0 - self.driver = None + self.driver = '' self.mdcontainer = '' + self.subsystype = '' devnode = '/dev/{0}'.format(devname) qprop = subprocess.check_output( ['udevadm', 'info', '--query=property', devnode]) @@ -46,7 +48,9 @@ class DiskInfo(object): elif (k == 'DRIVERS' and not self.driver and v not in ('"sd"', '""')): self.driver = v.replace('"', '') - if not self.driver and 'imsm' not in self.mdcontainer: + elif k == 'ATTRS{subsystype}': + self.subsystype = v.replace('"', '') + if not self.driver and 'imsm' not in self.mdcontainer and self.subsystype != 'nvm': raise Exception("No driver detected") if os.path.exists('/sys/block/{0}/size'.format(self.name)): with open('/sys/block/{0}/size'.format(self.name), 'r') as sizesrc: diff --git a/confluent_osdeploy/el8/profiles/default/scripts/getinstalldisk b/confluent_osdeploy/el8/profiles/default/scripts/getinstalldisk index 04c7708e..c954a254 100644 --- a/confluent_osdeploy/el8/profiles/default/scripts/getinstalldisk +++ b/confluent_osdeploy/el8/profiles/default/scripts/getinstalldisk @@ -1,3 +1,4 @@ +#!/usr/bin/python3 import subprocess import os @@ -10,8 +11,9 @@ class DiskInfo(object): self.path = None self.model = '' self.size = 0 - self.driver = None + self.driver = '' self.mdcontainer = '' + self.subsystype = '' devnode = '/dev/{0}'.format(devname) qprop = subprocess.check_output( ['udevadm', 'info', '--query=property', devnode]) @@ -46,7 +48,9 @@ class DiskInfo(object): elif (k == 'DRIVERS' and not self.driver and v not in ('"sd"', '""')): self.driver = v.replace('"', '') - if not self.driver and 'imsm' not in self.mdcontainer: + elif k == 'ATTRS{subsystype}': + self.subsystype = v.replace('"', '') + if not self.driver and 'imsm' not in self.mdcontainer and self.subsystype != 'nvm': raise Exception("No driver detected") if os.path.exists('/sys/block/{0}/size'.format(self.name)): with open('/sys/block/{0}/size'.format(self.name), 'r') as sizesrc: diff --git a/confluent_osdeploy/el9-diskless/profiles/default/scripts/getinstalldisk b/confluent_osdeploy/el9-diskless/profiles/default/scripts/getinstalldisk index 04c7708e..c954a254 100644 --- a/confluent_osdeploy/el9-diskless/profiles/default/scripts/getinstalldisk +++ b/confluent_osdeploy/el9-diskless/profiles/default/scripts/getinstalldisk @@ -1,3 +1,4 @@ +#!/usr/bin/python3 import subprocess import os @@ -10,8 +11,9 @@ class DiskInfo(object): self.path = None self.model = '' self.size = 0 - self.driver = None + self.driver = '' self.mdcontainer = '' + self.subsystype = '' devnode = '/dev/{0}'.format(devname) qprop = subprocess.check_output( ['udevadm', 'info', '--query=property', devnode]) @@ -46,7 +48,9 @@ class DiskInfo(object): elif (k == 'DRIVERS' and not self.driver and v not in ('"sd"', '""')): self.driver = v.replace('"', '') - if not self.driver and 'imsm' not in self.mdcontainer: + elif k == 'ATTRS{subsystype}': + self.subsystype = v.replace('"', '') + if not self.driver and 'imsm' not in self.mdcontainer and self.subsystype != 'nvm': raise Exception("No driver detected") if os.path.exists('/sys/block/{0}/size'.format(self.name)): with open('/sys/block/{0}/size'.format(self.name), 'r') as sizesrc: diff --git a/confluent_osdeploy/el9-diskless/profiles/default/scripts/image2disk.py b/confluent_osdeploy/el9-diskless/profiles/default/scripts/image2disk.py index 6a924964..ccf36036 100644 --- a/confluent_osdeploy/el9-diskless/profiles/default/scripts/image2disk.py +++ b/confluent_osdeploy/el9-diskless/profiles/default/scripts/image2disk.py @@ -65,7 +65,11 @@ def get_image_metadata(imgpath): continue yield md else: - raise Exception('Installation from single part image not supported') + # plausible filesystem structure to apply to a nominally "diskless" image + yield {'mount': '/', 'filesystem': 'xfs', 'minsize': 39513563136, 'initsize': 954128662528, 'flags': 'rw,seclabel,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota', 'device': '/dev/mapper/root', 'compressed_size': 27022069760} + yield {'mount': '/boot', 'filesystem': 'xfs', 'minsize': 232316928, 'initsize': 1006632960, 'flags': 'rw,seclabel,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota', 'device': '/dev/nvme1n1p2', 'compressed_size': 171462656} + yield {'mount': '/boot/efi', 'filesystem': 'vfat', 'minsize': 7835648, 'initsize': 627900416, 'flags': 'rw,relatime,fmask=0077,dmask=0077,codepage=437,iocharset=ascii,shortname=winnt,errors=remount-ro', 'device': '/dev/nvme1n1p1', 'compressed_size': 1576960} + #raise Exception('Installation from single part image not supported') class PartedRunner(): def __init__(self, disk): @@ -84,8 +88,17 @@ def fixup(rootdir, vols): for vol in vols: devbymount[vol['mount']] = vol['targetdisk'] fstabfile = os.path.join(rootdir, 'etc/fstab') - with open(fstabfile) as tfile: - fstab = tfile.read().split('\n') + if os.path.exists(fstabfile): + with open(fstabfile) as tfile: + fstab = tfile.read().split('\n') + else: + #diskless image, need to invent fstab + fstab = [ + "#ORIGFSTAB#/dev/mapper/root# / xfs defaults 0 0", + "#ORIGFSTAB#UUID=aaf9e0f9-aa4d-4d74-9e75-3537620cfe23# /boot xfs defaults 0 0", + "#ORIGFSTAB#UUID=C21D-B881# /boot/efi vfat umask=0077,shortname=winnt 0 2", + "#ORIGFSTAB#/dev/mapper/swap# none swap defaults 0 0", + ] while not fstab[0]: fstab = fstab[1:] if os.path.exists(os.path.join(rootdir, '.autorelabel')): @@ -135,8 +148,10 @@ def fixup(rootdir, vols): newcfg = ifcfg.split('/')[-1] newcfg = os.path.join(rootdir, 'etc/NetworkManager/system-connections/{0}'.format(newcfg)) shutil.copy2(ifcfg, newcfg) - shutil.rmtree(os.path.join(rootdir, 'etc/confluent/')) - shutil.copytree('/etc/confluent', os.path.join(rootdir, 'etc/confluent')) + rootconfluentdir = os.path.join(rootdir, 'etc/confluent/') + if os.path.exists(rootconfluentdir): + shutil.rmtree(rootconfluentdir) + shutil.copytree('/etc/confluent', rootconfluentdir) if policy: sys.stdout.write('Applying SELinux labeling...') sys.stdout.flush() @@ -191,8 +206,24 @@ def fixup(rootdir, vols): else: newcfgparts.append(cfgpart) loadentout.write(' '.join(newcfgparts) + '\n') - with open(grubsyscfg) as defgrubin: - defgrub = defgrubin.read().split('\n') + if os.path.exists(grubsyscfg): + with open(grubsyscfg) as defgrubin: + defgrub = defgrubin.read().split('\n') + else: + defgrub = [ + 'GRUB_TIMEOUT=5', + 'GRUB_DISTRIBUTOR="$(sed ' + "'s, release .*$,,g'" + ' /etc/system-release)"', + 'GRUB_DEFAULT=saved', + 'GRUB_DISABLE_SUBMENU=true', + 'GRUB_TERMINAL=""', + 'GRUB_SERIAL_COMMAND=""', + 'GRUB_CMDLINE_LINUX="crashkernel=1G-4G:192M,4G-64G:256M,64G-:512M rd.lvm.lv=vg/root rd.lvm.lv=vg/swap"', + 'GRUB_DISABLE_RECOVERY="true"', + 'GRUB_ENABLE_BLSCFG=true', + ] + if not os.path.exists(os.path.join(rootdir, "etc/kernel/cmdline")): + with open(os.path.join(rootdir, "etc/kernel/cmdline"), "w") as cmdlineout: + cmdlineout.write("root=/dev/mapper/localstorage-root rd.lvm.lv=localstorage/root") with open(grubsyscfg, 'w') as defgrubout: for gline in defgrub: gline = gline.split() @@ -217,6 +248,12 @@ def fixup(rootdir, vols): grubcfg = grubcfg[:-1] if len(grubcfg) == 1: grubcfg = grubcfg[0] + elif not grubcfg: + grubcfg = '/boot/grub2/grub.cfg' + paths = glob.glob(os.path.join(rootdir, 'boot/efi/EFI/*')) + for path in paths: + with open(os.path.join(path, 'grub.cfg'), 'w') as stubgrubout: + stubgrubout.write("search --no-floppy --root-dev-only --fs-uuid --set=dev " + bootuuid + "\nset prefix=($dev)/grub2\nexport $prefix\nconfigfile $prefix/grub.cfg\n") else: for gcfg in grubcfg: rgcfg = os.path.join(rootdir, gcfg[1:]) # gcfg has a leading / to get rid of @@ -272,10 +309,19 @@ def fixup(rootdir, vols): shimpath = subprocess.check_output(['find', os.path.join(rootdir, 'boot/efi'), '-name', 'shimx64.efi']).decode('utf8').strip() shimpath = shimpath.replace(rootdir, '/').replace('/boot/efi', '').replace('//', '/').replace('/', '\\') subprocess.check_call(['efibootmgr', '-c', '-d', targblock, '-l', shimpath, '--part', partnum]) + + try: + os.makedirs(os.path.join(rootdir, 'opt/confluent/bin')) + except Exception: + pass + shutil.copy2('/opt/confluent/bin/apiclient', os.path.join(rootdir, 'opt/confluent/bin/apiclient')) #other network interfaces def had_swap(): + if not os.path.exists('/etc/fstab'): + # diskless source, assume swap + return True with open('/etc/fstab') as tabfile: tabs = tabfile.read().split('\n') for tab in tabs: @@ -440,6 +486,8 @@ def install_to_disk(imgpath): subprocess.check_call(['mount', vol['targetdisk'], '/run/imginst/targ']) source = vol['mount'].replace('/', '_') source = '/run/imginst/sources/' + source + if not os.path.exists(source): + source = '/run/imginst/sources/_' + vol['mount'] blankfsstat = os.statvfs('/run/imginst/targ') blankused = (blankfsstat.f_blocks - blankfsstat.f_bfree) * blankfsstat.f_bsize sys.stdout.write('\nWriting {0}: '.format(vol['mount'])) diff --git a/confluent_osdeploy/el9-diskless/profiles/default/scripts/installimage b/confluent_osdeploy/el9-diskless/profiles/default/scripts/installimage index 56597086..c461173b 100644 --- a/confluent_osdeploy/el9-diskless/profiles/default/scripts/installimage +++ b/confluent_osdeploy/el9-diskless/profiles/default/scripts/installimage @@ -41,6 +41,8 @@ echo "Port 22" >> /etc/ssh/sshd_config echo 'Match LocalPort 22' >> /etc/ssh/sshd_config echo ' ChrootDirectory /sysroot/run/imginst/targ' >> /etc/ssh/sshd_config kill -HUP $(cat /run/sshd.pid) +cp /sysroot/etc/pki/ca-trust/source/anchors/* /sysroot/run/imginst/targ/etc/pki/ca-trust/source/anchors/ +chroot /sysroot/run/imginst/targ update-ca-trust chroot /sysroot/run/imginst/targ bash -c "source /etc/confluent/functions; run_remote post.sh" chroot /sysroot bash -c "umount \$(tac /proc/mounts|awk '{print \$2}'|grep ^/run/imginst/targ)" diff --git a/confluent_osdeploy/el9-diskless/profiles/default/scripts/post.sh b/confluent_osdeploy/el9-diskless/profiles/default/scripts/post.sh index 7a7ac01e..914a12c3 100644 --- a/confluent_osdeploy/el9-diskless/profiles/default/scripts/post.sh +++ b/confluent_osdeploy/el9-diskless/profiles/default/scripts/post.sh @@ -2,7 +2,10 @@ # This script is executed 'chrooted' into a cloned disk target before rebooting # - +if [ -f /etc/dracut.conf.d/diskless.conf ]; then + rm /etc/dracut.conf.d/diskless.conf +fi +for kver in /lib/modules/*; do kver=$(basename $kver); kernel-install add $kver /boot/vmlinuz-$kver; done nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}') confluent_apikey=$(cat /etc/confluent/confluent.apikey) confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|awk '{print $2}') @@ -16,6 +19,7 @@ if [[ "$confluent_mgr" == *:* ]]; then fi export nodename confluent_mgr confluent_profile confluent_websrv . /etc/confluent/functions +run_remote setupssh mkdir -p /var/log/confluent chmod 700 /var/log/confluent exec >> /var/log/confluent/confluent-post.log @@ -40,6 +44,9 @@ run_remote_parts post.d # Induce execution of remote configuration, e.g. ansible plays in ansible/post.d/ run_remote_config post.d +# rebuild initrd, pick up new drivers if needed +dracut -f /boot/initramfs-$(uname -r).img $(uname -r) + curl -sf -X POST -d 'status: staged' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$confluent_websrv/confluent-api/self/updatestatus kill $logshowpid diff --git a/confluent_osdeploy/esxi7/profiles/hypervisor/profile.yaml b/confluent_osdeploy/esxi7/profiles/hypervisor/profile.yaml index dc0d1a33..b9c84687 100644 --- a/confluent_osdeploy/esxi7/profiles/hypervisor/profile.yaml +++ b/confluent_osdeploy/esxi7/profiles/hypervisor/profile.yaml @@ -1,3 +1,3 @@ -label: Confluent installation of VMware ESXi %%VERSION%% Hypervisor +label: VMware ESXi %%VERSION%% Hypervisor ostype: esxi kernelargs: runweasel diff --git a/confluent_osdeploy/rhvh4/profiles/default/scripts/getinstalldisk b/confluent_osdeploy/rhvh4/profiles/default/scripts/getinstalldisk index 04c7708e..c954a254 100644 --- a/confluent_osdeploy/rhvh4/profiles/default/scripts/getinstalldisk +++ b/confluent_osdeploy/rhvh4/profiles/default/scripts/getinstalldisk @@ -1,3 +1,4 @@ +#!/usr/bin/python3 import subprocess import os @@ -10,8 +11,9 @@ class DiskInfo(object): self.path = None self.model = '' self.size = 0 - self.driver = None + self.driver = '' self.mdcontainer = '' + self.subsystype = '' devnode = '/dev/{0}'.format(devname) qprop = subprocess.check_output( ['udevadm', 'info', '--query=property', devnode]) @@ -46,7 +48,9 @@ class DiskInfo(object): elif (k == 'DRIVERS' and not self.driver and v not in ('"sd"', '""')): self.driver = v.replace('"', '') - if not self.driver and 'imsm' not in self.mdcontainer: + elif k == 'ATTRS{subsystype}': + self.subsystype = v.replace('"', '') + if not self.driver and 'imsm' not in self.mdcontainer and self.subsystype != 'nvm': raise Exception("No driver detected") if os.path.exists('/sys/block/{0}/size'.format(self.name)): with open('/sys/block/{0}/size'.format(self.name), 'r') as sizesrc: diff --git a/confluent_osdeploy/suse15-diskless/profiles/default/scripts/imageboot.sh b/confluent_osdeploy/suse15-diskless/profiles/default/scripts/imageboot.sh index 91e62ebb..1fb4e6a2 100644 --- a/confluent_osdeploy/suse15-diskless/profiles/default/scripts/imageboot.sh +++ b/confluent_osdeploy/suse15-diskless/profiles/default/scripts/imageboot.sh @@ -140,4 +140,5 @@ mv /lib/modules/$(uname -r) /lib/modules/$(uname -r)-ramfs ln -s /sysroot/lib/modules/$(uname -r) /lib/modules/ mv /lib/firmware /lib/firmware-ramfs ln -s /sysroot/lib/firmware /lib/firmware +chroot /sysroot chkstat --system --set --noheader > /dev/null exec /opt/confluent/bin/start_root diff --git a/confluent_osdeploy/suse15/profiles/hpc/scripts/getinstalldisk b/confluent_osdeploy/suse15/profiles/hpc/scripts/getinstalldisk index 04c7708e..c954a254 100644 --- a/confluent_osdeploy/suse15/profiles/hpc/scripts/getinstalldisk +++ b/confluent_osdeploy/suse15/profiles/hpc/scripts/getinstalldisk @@ -1,3 +1,4 @@ +#!/usr/bin/python3 import subprocess import os @@ -10,8 +11,9 @@ class DiskInfo(object): self.path = None self.model = '' self.size = 0 - self.driver = None + self.driver = '' self.mdcontainer = '' + self.subsystype = '' devnode = '/dev/{0}'.format(devname) qprop = subprocess.check_output( ['udevadm', 'info', '--query=property', devnode]) @@ -46,7 +48,9 @@ class DiskInfo(object): elif (k == 'DRIVERS' and not self.driver and v not in ('"sd"', '""')): self.driver = v.replace('"', '') - if not self.driver and 'imsm' not in self.mdcontainer: + elif k == 'ATTRS{subsystype}': + self.subsystype = v.replace('"', '') + if not self.driver and 'imsm' not in self.mdcontainer and self.subsystype != 'nvm': raise Exception("No driver detected") if os.path.exists('/sys/block/{0}/size'.format(self.name)): with open('/sys/block/{0}/size'.format(self.name), 'r') as sizesrc: diff --git a/confluent_osdeploy/suse15/profiles/server/scripts/getinstalldisk b/confluent_osdeploy/suse15/profiles/server/scripts/getinstalldisk index 04c7708e..c954a254 100644 --- a/confluent_osdeploy/suse15/profiles/server/scripts/getinstalldisk +++ b/confluent_osdeploy/suse15/profiles/server/scripts/getinstalldisk @@ -1,3 +1,4 @@ +#!/usr/bin/python3 import subprocess import os @@ -10,8 +11,9 @@ class DiskInfo(object): self.path = None self.model = '' self.size = 0 - self.driver = None + self.driver = '' self.mdcontainer = '' + self.subsystype = '' devnode = '/dev/{0}'.format(devname) qprop = subprocess.check_output( ['udevadm', 'info', '--query=property', devnode]) @@ -46,7 +48,9 @@ class DiskInfo(object): elif (k == 'DRIVERS' and not self.driver and v not in ('"sd"', '""')): self.driver = v.replace('"', '') - if not self.driver and 'imsm' not in self.mdcontainer: + elif k == 'ATTRS{subsystype}': + self.subsystype = v.replace('"', '') + if not self.driver and 'imsm' not in self.mdcontainer and self.subsystype != 'nvm': raise Exception("No driver detected") if os.path.exists('/sys/block/{0}/size'.format(self.name)): with open('/sys/block/{0}/size'.format(self.name), 'r') as sizesrc: diff --git a/confluent_osdeploy/ubuntu20.04-diskless/profiles/default/scripts/getinstalldisk b/confluent_osdeploy/ubuntu20.04-diskless/profiles/default/scripts/getinstalldisk index 04c7708e..c954a254 100644 --- a/confluent_osdeploy/ubuntu20.04-diskless/profiles/default/scripts/getinstalldisk +++ b/confluent_osdeploy/ubuntu20.04-diskless/profiles/default/scripts/getinstalldisk @@ -1,3 +1,4 @@ +#!/usr/bin/python3 import subprocess import os @@ -10,8 +11,9 @@ class DiskInfo(object): self.path = None self.model = '' self.size = 0 - self.driver = None + self.driver = '' self.mdcontainer = '' + self.subsystype = '' devnode = '/dev/{0}'.format(devname) qprop = subprocess.check_output( ['udevadm', 'info', '--query=property', devnode]) @@ -46,7 +48,9 @@ class DiskInfo(object): elif (k == 'DRIVERS' and not self.driver and v not in ('"sd"', '""')): self.driver = v.replace('"', '') - if not self.driver and 'imsm' not in self.mdcontainer: + elif k == 'ATTRS{subsystype}': + self.subsystype = v.replace('"', '') + if not self.driver and 'imsm' not in self.mdcontainer and self.subsystype != 'nvm': raise Exception("No driver detected") if os.path.exists('/sys/block/{0}/size'.format(self.name)): with open('/sys/block/{0}/size'.format(self.name), 'r') as sizesrc: diff --git a/confluent_osdeploy/ubuntu20.04/initramfs/scripts/init-premount/confluent b/confluent_osdeploy/ubuntu20.04/initramfs/scripts/init-premount/confluent index ef09db40..528b27d6 100755 --- a/confluent_osdeploy/ubuntu20.04/initramfs/scripts/init-premount/confluent +++ b/confluent_osdeploy/ubuntu20.04/initramfs/scripts/init-premount/confluent @@ -31,23 +31,26 @@ if [ -e /dev/disk/by-label/CNFLNT_IDNT ]; then MYGW="" fi MYNM=$(grep ^ipv4_netmask: $tcfg | awk '{print $2}') - for NICGUESS in $(ip link|grep LOWER_UP|grep -v LOOPBACK|cut -d ' ' -f 2 | sed -e 's/:$//'); do - ip addr add dev $NICGUESS $v4addr - if [ ! -z "$MYGW" ]; then - ip route add default via $MYGW - fi - for dsrv in $deploysrvs; do - if openssl s_client -connect $dsrv:443 > /dev/null 2>&1; then - deploysrvs=$dsrv - NIC=$NICGUESS + NIC="" + while [ -z "$NIC" ]; do + for NICGUESS in $(ip link|grep LOWER_UP|grep -v LOOPBACK|cut -d ' ' -f 2 | sed -e 's/:$//'); do + ip addr add dev $NICGUESS $v4addr + if [ ! -z "$MYGW" ]; then + ip route add default via $MYGW + fi + for dsrv in $deploysrvs; do + if openssl s_client -connect $dsrv:443 > /dev/null 2>&1; then + deploysrvs=$dsrv + NIC=$NICGUESS + break + fi + done + if [ -z "$NIC" ]; then + ip -4 a flush dev $NICGUESS + else break fi done - if [ -z "$NIC" ]; then - ip -4 a flush dev $NICGUESS - else - break - fi done ipconfig -d $MYIP::$MYGW:$MYNM::$NIC echo $NIC > /tmp/autodetectnic diff --git a/confluent_osdeploy/ubuntu20.04/profiles/default/scripts/getinstalldisk b/confluent_osdeploy/ubuntu20.04/profiles/default/scripts/getinstalldisk index 04c7708e..c954a254 100644 --- a/confluent_osdeploy/ubuntu20.04/profiles/default/scripts/getinstalldisk +++ b/confluent_osdeploy/ubuntu20.04/profiles/default/scripts/getinstalldisk @@ -1,3 +1,4 @@ +#!/usr/bin/python3 import subprocess import os @@ -10,8 +11,9 @@ class DiskInfo(object): self.path = None self.model = '' self.size = 0 - self.driver = None + self.driver = '' self.mdcontainer = '' + self.subsystype = '' devnode = '/dev/{0}'.format(devname) qprop = subprocess.check_output( ['udevadm', 'info', '--query=property', devnode]) @@ -46,7 +48,9 @@ class DiskInfo(object): elif (k == 'DRIVERS' and not self.driver and v not in ('"sd"', '""')): self.driver = v.replace('"', '') - if not self.driver and 'imsm' not in self.mdcontainer: + elif k == 'ATTRS{subsystype}': + self.subsystype = v.replace('"', '') + if not self.driver and 'imsm' not in self.mdcontainer and self.subsystype != 'nvm': raise Exception("No driver detected") if os.path.exists('/sys/block/{0}/size'.format(self.name)): with open('/sys/block/{0}/size'.format(self.name), 'r') as sizesrc: diff --git a/confluent_osdeploy/ubuntu22.04/initramfs/scripts/init-premount/confluent b/confluent_osdeploy/ubuntu22.04/initramfs/scripts/init-premount/confluent index 8a7e3777..6315ba5d 100755 --- a/confluent_osdeploy/ubuntu22.04/initramfs/scripts/init-premount/confluent +++ b/confluent_osdeploy/ubuntu22.04/initramfs/scripts/init-premount/confluent @@ -31,23 +31,26 @@ if [ -e /dev/disk/by-label/CNFLNT_IDNT ]; then MYGW="" fi MYNM=$(grep ^ipv4_netmask: $tcfg | awk '{print $2}') - for NICGUESS in $(ip link|grep LOWER_UP|grep -v LOOPBACK|cut -d ' ' -f 2 | sed -e 's/:$//'); do - ip addr add dev $NICGUESS $v4addr - if [ ! -z "$MYGW" ]; then - ip route add default via $MYGW - fi - for dsrv in $deploysrvs; do - if openssl s_client -connect $dsrv:443 > /dev/null 2>&1; then - deploysrvs=$dsrv - NIC=$NICGUESS + NIC="" + while [ -z "$NIC" ]; do + for NICGUESS in $(ip link|grep LOWER_UP|grep -v LOOPBACK|cut -d ' ' -f 2 | sed -e 's/:$//'); do + ip addr add dev $NICGUESS $v4addr + if [ ! -z "$MYGW" ]; then + ip route add default via $MYGW + fi + for dsrv in $deploysrvs; do + if openssl s_client -connect $dsrv:443 > /dev/null 2>&1; then + deploysrvs=$dsrv + NIC=$NICGUESS + break + fi + done + if [ -z "$NIC" ]; then + ip -4 a flush dev $NICGUESS + else break fi done - if [ -z "$NIC" ]; then - ip -4 a flush dev $NICGUESS - else - break - fi done ipconfig -d $MYIP::$MYGW:$MYNM::$NIC echo $NIC > /tmp/autodetectnic diff --git a/confluent_osdeploy/ubuntu22.04/profiles/default/scripts/getinstalldisk b/confluent_osdeploy/ubuntu22.04/profiles/default/scripts/getinstalldisk index 04c7708e..c954a254 100644 --- a/confluent_osdeploy/ubuntu22.04/profiles/default/scripts/getinstalldisk +++ b/confluent_osdeploy/ubuntu22.04/profiles/default/scripts/getinstalldisk @@ -1,3 +1,4 @@ +#!/usr/bin/python3 import subprocess import os @@ -10,8 +11,9 @@ class DiskInfo(object): self.path = None self.model = '' self.size = 0 - self.driver = None + self.driver = '' self.mdcontainer = '' + self.subsystype = '' devnode = '/dev/{0}'.format(devname) qprop = subprocess.check_output( ['udevadm', 'info', '--query=property', devnode]) @@ -46,7 +48,9 @@ class DiskInfo(object): elif (k == 'DRIVERS' and not self.driver and v not in ('"sd"', '""')): self.driver = v.replace('"', '') - if not self.driver and 'imsm' not in self.mdcontainer: + elif k == 'ATTRS{subsystype}': + self.subsystype = v.replace('"', '') + if not self.driver and 'imsm' not in self.mdcontainer and self.subsystype != 'nvm': raise Exception("No driver detected") if os.path.exists('/sys/block/{0}/size'.format(self.name)): with open('/sys/block/{0}/size'.format(self.name), 'r') as sizesrc: diff --git a/confluent_server/confluent/certutil.py b/confluent_server/confluent/certutil.py index a3c27633..2cf3dad2 100644 --- a/confluent_server/confluent/certutil.py +++ b/confluent_server/confluent/certutil.py @@ -215,13 +215,19 @@ async def create_certificate(keyout=None, certout=None, csrout=None): longname = shortname # socket.getfqdn() if not csrout: await util.check_call( - 'openssl', 'ecparam', '-name', 'secp384r1', '-genkey', '-out', - keyout) - san = ['IP:{0}'.format(x) async for x in get_ip_addresses()] + ['openssl', 'ecparam', '-name', 'secp384r1', '-genkey', '-out', + keyout]) + ipaddrs = list(get_ip_addresses()) + san = ['IP:{0}'.format(x) for x in ipaddrs] # It is incorrect to put IP addresses as DNS type. However # there exists non-compliant clients that fail with them as IP - san.extend(['DNS:{0}'.format(x) async for x in get_ip_addresses()]) - san.append('DNS:{0}'.format(shortname)) + # san.extend(['DNS:{0}'.format(x) for x in ipaddrs]) + dnsnames = set(ipaddrs) + dnsnames.add(shortname) + for currip in ipaddrs: + dnsnames.add(socket.getnameinfo((currip, 0), 0)[0]) + for currname in dnsnames: + san.append('DNS:{0}'.format(currname)) #san.append('DNS:{0}'.format(longname)) san = ','.join(san) sslcfg = get_openssl_conf_location() diff --git a/confluent_server/confluent/config/attributes.py b/confluent_server/confluent/config/attributes.py index f926c962..ff2aa90a 100644 --- a/confluent_server/confluent/config/attributes.py +++ b/confluent_server/confluent/config/attributes.py @@ -94,8 +94,8 @@ node = { 'considered a member'), }, 'type': { - 'description': ('Classification of node as server or switch. By default a node is presumed to be a server.'), - 'validvalues': ('switch', 'server'), + 'description': ('The type of node. This may be switch, server, rackmount, dense, enclosure or not set to be generic.'), + 'validvalues': ('switch', 'server', 'rackmount', 'dense', 'enclosure', ''), }, 'crypted.rootpassword': { 'description': 'The password of the local root password. ' diff --git a/confluent_server/confluent/config/configmanager.py b/confluent_server/confluent/config/configmanager.py index a657687b..c1ac620a 100644 --- a/confluent_server/confluent/config/configmanager.py +++ b/confluent_server/confluent/config/configmanager.py @@ -2367,6 +2367,9 @@ class ConfigManager(object): lidx = self._cfgstore['nodes'][node]['groups'].index(name) self._cfgstore['nodes'][node]['groups'][lidx] = renamemap[name] _mark_dirtykey('nodes', node, self.tenant) + for node in self._cfgstore['nodegroups'][renamemap[name]].get('nodes', []): + self._node_removed_from_group(node, name, {}) + self._node_added_to_group(node, renamemap[name], {}) self._bg_sync_to_file() diff --git a/confluent_server/confluent/consoleserver.py b/confluent_server/confluent/consoleserver.py index 7385e6c9..f6cd873f 100644 --- a/confluent_server/confluent/consoleserver.py +++ b/confluent_server/confluent/consoleserver.py @@ -704,10 +704,11 @@ class ProxyConsole(object): try: remote = await collective.connect_to_collective(None, self.managerinfo['address']) except Exception as e: - print(repr(e)) + if _tracelog: + _tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event, event=log.Events.stacktrace) await asyncio.sleep(3) if self.clisession: - await self.clisession.detach() + await self.clisession.detach(False) await self.detachsession(None) return await tlvdata.recv(remote) @@ -836,7 +837,7 @@ class ConsoleSession(object): self._evt = None self.reghdl = None - async def detach(self): + async def detach(self, reattach=True): """Handler for the console handler to detach so it can reattach, currently to facilitate changing from one collective.manager to another @@ -844,9 +845,10 @@ class ConsoleSession(object): :return: """ await self.conshdl.detachsession(self) - self.connect_session() - await self.conshdl.attachsession(self) - self.write = self.conshdl.write + if reattach: + self.connect_session() + await self.conshdl.attachsession(self) + self.write = self.conshdl.write def got_data(self, data): """Receive data from console and buffer diff --git a/confluent_server/confluent/core.py b/confluent_server/confluent/core.py index 450b2eb1..64ca3a38 100644 --- a/confluent_server/confluent/core.py +++ b/confluent_server/confluent/core.py @@ -63,7 +63,10 @@ import msgpack import os import struct import sys +import uuid import yaml +import shutil + pluginmap = {} dispatch_plugins = (b'ipmi', u'ipmi', b'redfish', u'redfish', b'tsmsol', u'tsmsol', b'geist', u'geist', b'deltapdu', u'deltapdu', b'eatonpdu', u'eatonpdu', b'affluent', u'affluent', b'cnos', u'cnos', b'enos', u'enos') @@ -180,8 +183,9 @@ def _merge_dict(original, custom): rootcollections = ['deployment/', 'discovery/', 'events/', 'networking/', - 'noderange/', 'nodes/', 'nodegroups/', 'storage/', 'usergroups/' , - 'users/', 'uuid', 'version'] + 'noderange/', 'nodes/', 'nodegroups/', 'storage/', 'usergroups/', + 'users/', 'uuid', 'version', 'staging/'] + class PluginRoute(object): @@ -1294,6 +1298,98 @@ def handle_discovery(pathcomponents, operation, configmanager, inputdata): if pathcomponents[0] == 'detected': pass + +class Staging: + def __init__(self, user, uuid): + self.uuid_str = uuid + self.storage_folder = '/var/lib/confluent/client_assets/' + self.uuid_str + self.filename = None + self.user = user + self.base_folder = os.path.exists('/var/lib/confluent/client_assets/') + + if not self.base_folder: + try: + os.mkdir('/var/lib/confluent/client_assets/') + except Exception as e: + raise OSError(str(e)) + + def getUUID(self): + return self.uuid_str + + def get_push_url(self): + return 'staging/{0}/{1}'.format(self.user,self.uuid_str) + + def create_directory(self): + try: + os.mkdir(self.storage_folder) + return True + except OSError as e: + raise exc.InvalidArgumentException(str(e)) + + def get_file_name(self): + stage_file = '{}/filename.txt'.format(self.storage_folder) + try: + with open(stage_file, 'r') as f: + filename = f.readline() + os.remove(stage_file) + return self.storage_folder + '/{}'.format(filename) + except FileNotFoundError: + file = None + return False + + @staticmethod + def remove_directory(directory): + storage_folder = '/var/lib/confluent/client_assets/' + directory + if os.path.exists(storage_folder): + shutil.rmtree(storage_folder) + else: + raise FileNotFoundError + return directory + +def handle_staging(pathcomponents, operation, configmanager, inputdata): + ''' + e.g push_url: /confluent-api/staging/user/ + ''' + if operation == 'create': + if len(pathcomponents) == 1: + stage = Staging(inputdata['user'],str(uuid.uuid1())) + if stage.create_directory(): + if 'filename' in inputdata: + data_file = stage.storage_folder + '/filename.txt' + with open(data_file, 'w') as f: + f.write(inputdata['filename']) + else: + raise Exception('Error: Missing filename arg') + push_url = stage.get_push_url() + yield msg.CreatedResource(push_url) + + elif len(pathcomponents) == 3: + stage = Staging(pathcomponents[1], pathcomponents[2]) + file = stage.get_file_name() + if 'filedata' in inputdata and file: + content_length = inputdata['content_length'] + remaining_length = content_length + filedata = inputdata['filedata'] + chunk_size = 16384 + progress = 0.0 + with open(file, 'wb') as f: + while remaining_length > 0: + progress = (1 - (remaining_length/content_length)) * 100 + datachunk = filedata['wsgi.input'].read(min(chunk_size, remaining_length)) + f.write(datachunk) + remaining_length -= len(datachunk) + eventlet.sleep(0) + yield msg.FileUploadProgress(progress) + yield msg.FileUploadProgress(100) + + + elif operation == 'delete': + if len(pathcomponents) == 3: + asset = Staging.remove_directory(pathcomponents[2]) + yield msg.DeletedResource(asset) + else: + raise Exception("Invalid url") + async def handle_path(path, operation, configmanager, inputdata=None, autostrip=True): """Given a full path request, return an object. @@ -1402,5 +1498,7 @@ async def handle_path(path, operation, configmanager, inputdata=None, autostrip= elif pathcomponents[0] == 'discovery': return handle_discovery(pathcomponents[1:], operation, configmanager, inputdata) + elif pathcomponents[0] == 'staging': + return handle_staging(pathcomponents, operation, configmanager, inputdata) else: raise exc.NotFoundException() diff --git a/confluent_server/confluent/discovery/core.py b/confluent_server/confluent/discovery/core.py index cf4b516e..693d0b9e 100644 --- a/confluent_server/confluent/discovery/core.py +++ b/confluent_server/confluent/discovery/core.py @@ -76,6 +76,7 @@ import confluent.discovery.handlers.pxe as pxeh import confluent.discovery.handlers.smm as smm import confluent.discovery.handlers.xcc as xcc import confluent.discovery.handlers.xcc3 as xcc3 +import confluent.discovery.handlers.smm3 as smm3 import confluent.discovery.handlers.megarac as megarac import confluent.exceptions as exc import confluent.log as log @@ -110,6 +111,7 @@ class nesteddict(dict): nodehandlers = { 'service:lenovo-smm': smm, 'service:lenovo-smm2': smm, + 'lenovo-smm3': smm3, 'lenovo-xcc': xcc, 'lenovo-xcc3': xcc3, 'megarac-bmc': megarac, @@ -130,6 +132,7 @@ servicenames = { 'cumulus-switch': 'cumulus-switch', 'service:lenovo-smm': 'lenovo-smm', 'service:lenovo-smm2': 'lenovo-smm2', + 'lenovo-smm3': 'lenovo-smm3', 'affluent-switch': 'affluent-switch', 'lenovo-xcc': 'lenovo-xcc', 'lenovo-xcc3': 'lenovo-xcc3', @@ -147,6 +150,7 @@ servicebyname = { 'cumulus-switch': 'cumulus-switch', 'lenovo-smm': 'service:lenovo-smm', 'lenovo-smm2': 'service:lenovo-smm2', + 'lenovo-smm3': 'lenovo-smm3', 'affluent-switch': 'affluent-switch', 'lenovo-xcc': 'lenovo-xcc', 'lenovo-xcc3': 'lenovo-xcc3', diff --git a/confluent_server/confluent/discovery/handlers/redfishbmc.py b/confluent_server/confluent/discovery/handlers/redfishbmc.py index f0099aa5..5298a181 100644 --- a/confluent_server/confluent/discovery/handlers/redfishbmc.py +++ b/confluent_server/confluent/discovery/handlers/redfishbmc.py @@ -31,7 +31,7 @@ async def get_host_interface_urls(wc, mginfo): returls = [] hifurl = mginfo.get('HostInterfaces', {}).get('@odata.id', None) if not hifurl: - return None + return [] hifinfo = await wc.grab_json_response(hifurl) hifurls = hifinfo.get('Members', []) for hifurl in hifurls: @@ -111,7 +111,7 @@ class NodeHandler(generic.NodeHandler): await self.target_account_url(wc)) acctinfo = acctinfo[0] actypes = acctinfo['AccountTypes'] - candidates = acctinfo['AccountTypes@Redfish.AllowableValues'] + candidates = acctinfo.get('AccountTypes@Redfish.AllowableValues', []) if 'IPMI' not in actypes and 'IPMI' in candidates: actypes.append('IPMI') acctupd = { @@ -137,7 +137,14 @@ class NodeHandler(generic.NodeHandler): rsp = json.loads(rsp) currerr = rsp.get('error', {}) ecode = currerr.get('code', None) - if ecode.endswith('PasswordChangeRequired'): + if not ecode: + for msg in rsp['@Message.ExtendedInfo']: + if 'PasswordChangeRequired' in msg['MessageId']: + chgurl = msg['MessageArgs'][0] + break + else: + raise Exception("Failed to ascertain login failure reason") + elif ecode.endswith('PasswordChangeRequired'): for einfo in currerr.get('@Message.ExtendedInfo', []): if einfo.get('MessageId', None).endswith('PasswordChangeRequired'): for msgarg in einfo.get('MessageArgs'): diff --git a/confluent_server/confluent/discovery/handlers/smm3.py b/confluent_server/confluent/discovery/handlers/smm3.py new file mode 100644 index 00000000..7e663dc2 --- /dev/null +++ b/confluent_server/confluent/discovery/handlers/smm3.py @@ -0,0 +1,69 @@ +# Copyright 2024 Lenovo +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import confluent.discovery.handlers.redfishbmc as redfishbmc +import eventlet.support.greendns +import confluent.util as util + +webclient = eventlet.import_patched('pyghmi.util.webclient') + + + +getaddrinfo = eventlet.support.greendns.getaddrinfo + + +class NodeHandler(redfishbmc.NodeHandler): + devname = 'SMM3' + + def scan(self): + attrs = self.info.get('attributes', {}) + mtm = attrs.get('enclosure-machinetype-model', None) + if mtm: + self.info['modelnumber'] = mtm.strip() + sn = attrs.get('enclosure-serial-number', None) + if sn: + self.info['serialnumber'] = sn.strip() + modelname = attrs.get('enclosure-component-name', None) + if modelname: + modelname = modelname.split(' MT:')[0] + self.info['modelname'] = modelname + + def get_firmware_default_account_info(self): + return ('USERID', 'PASSW0RD') + + +def remote_nodecfg(nodename, cfm): + cfg = cfm.get_node_attributes( + nodename, 'hardwaremanagement.manager') + ipaddr = cfg.get(nodename, {}).get('hardwaremanagement.manager', {}).get( + 'value', None) + ipaddr = ipaddr.split('/', 1)[0] + ipaddr = getaddrinfo(ipaddr, 0)[0][-1] + if not ipaddr: + raise Exception('Cannot remote configure a system without known ' + 'address') + info = {'addresses': [ipaddr]} + nh = NodeHandler(info, cfm) + nh.config(nodename) + + +if __name__ == '__main__': + import confluent.config.configmanager as cfm + c = cfm.ConfigManager(None) + import sys + info = {'addresses': [[sys.argv[1]]]} + print(repr(info)) + testr = NodeHandler(info, c) + testr.config(sys.argv[2]) + diff --git a/confluent_server/confluent/discovery/handlers/xcc.py b/confluent_server/confluent/discovery/handlers/xcc.py index 2cb2ea9e..73f5bc05 100644 --- a/confluent_server/confluent/discovery/handlers/xcc.py +++ b/confluent_server/confluent/discovery/handlers/xcc.py @@ -602,6 +602,7 @@ class NodeHandler(immhandler.NodeHandler): '/redfish/v1/AccountService/Accounts/1', updateinf, method='PATCH') if targbmc and not targbmc.startswith('fe80::'): + attribsuffix = '' newip = targbmc.split('/', 1)[0] newipinfo = socket.getaddrinfo(newip, 0)[0] newip = newipinfo[-1][0] @@ -611,6 +612,25 @@ class NodeHandler(immhandler.NodeHandler): newmask = netutil.cidr_to_mask(netconfig['prefix']) currinfo = await wc.grab_json_response('/api/providers/logoninfo') currip = currinfo.get('items', [{}])[0].get('ipv4_address', '') + curreth1 = wc.grab_json_response('/api/dataset/imm_ethernet') + if curreth1: + if self.ipaddr.startswith('fe80::'): + ipkey = 'ipv6_link_local_address' + elif '.' in self.ipaddr: + ipkey = 'ipv4_address' + else: + raise Exception('Non-Link-Local IPv6 TODO') + nic1ip = curreth1.get('items', [{}])[0].get(ipkey, None) + if nic1ip != self.ipaddr: + # check second nic instead + curreth2 = wc.grab_json_response('/api/dataset/imm_ethernet_2') + if curreth2 and curreth2.get('items', [{}])[0].get('if_second_port_exist', 0): + nic2ip = curreth2.get('items', [{}])[0].get(ipkey + '_2', None) + if nic2ip != self.ipaddr: + raise Exception("Unable to determine which NIC is active") + # ok, second nic is active, target it + currip = curreth2.get('items', [{}])[0].get("ipv4_address", None) + attribsuffix = '_2' # do not change the ipv4_config if the current config looks right already if currip != newip: statargs = { @@ -621,6 +641,10 @@ class NodeHandler(immhandler.NodeHandler): statargs['ENET_IPv4GatewayIPAddr'] = netconfig['ipv4_gateway'] elif not netutil.address_is_local(newip): raise exc.InvalidArgumentException('Will not remotely configure a device with no gateway') + if attribsuffix: + for currkey in list(statargs): + statargs[currkey + attribsuffix] = statargs[currkey] + del statargs[currkey] netset, status = await wc.grab_json_response_with_status('/api/dataset', statargs) print(repr(netset)) print(repr(status)) diff --git a/confluent_server/confluent/discovery/protocols/ssdp.py b/confluent_server/confluent/discovery/protocols/ssdp.py index 0305a3ba..d5721ea6 100644 --- a/confluent_server/confluent/discovery/protocols/ssdp.py +++ b/confluent_server/confluent/discovery/protocols/ssdp.py @@ -57,7 +57,7 @@ smsg = ('M-SEARCH * HTTP/1.1\r\n' async def active_scan(handler, protocol=None): known_peers = set([]) - async for scanned in scan(['urn:dmtf-org:service:redfish-rest:1', 'urn::service:affluent']): + async for scanned in scan(['urn:dmtf-org:service:redfish-rest:1', 'urn::dmtf-org:service:redfish-rest:', 'urn::service:affluent']): for addr in scanned['addresses']: addr = addr[0:1] + addr[2:] if addr in known_peers: @@ -452,10 +452,10 @@ async def _find_service(service, target): mya['enclosure-machinetype-model'] = [val] yield peerdata[nid] continue - if '/redfish/v1/' not in peerdata[nid].get('urls', ()) and '/redfish/v1' not in peerdata[nid].get('urls', ()): - continue if '/DeviceDescription.json' in peerdata[nid]['urls']: pooltargs.append(('/DeviceDescription.json', peerdata[nid], 'lenovo-xcc')) + elif '/redfish/v1/' not in peerdata[nid].get('urls', ()) and '/redfish/v1' not in peerdata[nid].get('urls', ()): + continue else: for targurl in peerdata[nid]['urls']: if '/eth' in targurl and targurl.endswith('.xml'): @@ -493,6 +493,12 @@ async def check_fish(urldata, port=443, verifycallback=None): return None if url == '/DeviceDescription.json': if not peerinfo: + if data.get('services', None) == ['urn::dmtf-org:service:redfish-rest:']: + peerinfo = wc.grab_json_response('/redfish/v1/') + if peerinfo: + data['services'] = ['lenovo-smm3'] + data['uuid'] = peerinfo['UUID'].lower() + return data return None try: peerinfo = peerinfo[0] @@ -509,6 +515,12 @@ async def check_fish(urldata, port=443, verifycallback=None): data['services'] = ['lenovo-xcc'] if 'xcc-variant' not in peerinfo else ['lenovo-xcc' + peerinfo['xcc-variant']] return data except (IndexError, KeyError): + if 'type' in peerinfo and peerinfo['type'].lower() == 'lenovo-smm3': + del peerinfo['xcc-variant'] + data['uuid'] = peerinfo['enclosure-uuid'] + data['services'] = ['lenovo-smm3'] + data['attributes'] = peerinfo + return data return None url = '/redfish/v1/' peerinfo = await wc.grab_json_response('/redfish/v1/') diff --git a/confluent_server/confluent/httpapi.py b/confluent_server/confluent/httpapi.py index 6d05d9be..38eeb301 100644 --- a/confluent_server/confluent/httpapi.py +++ b/confluent_server/confluent/httpapi.py @@ -44,6 +44,7 @@ import confluent.asynctlvdata as tlvdata import confluent.util as util import copy import json +import os import socket import sys import traceback @@ -435,6 +436,8 @@ def websockify_data(data): data = data.decode('utf8') except UnicodeDecodeError: data = data.decode('cp437') + except AttributeError: # already str + pass data = u' ' + data return data @@ -641,7 +644,6 @@ async def resourcehandler(request): rsp.content_type = mimetype await rsp.prepare(request) return rsp - try: if 'Sec-WebSocket-Version' in request.headers: return await wsock_handler(request) @@ -667,6 +669,11 @@ async def resourcehandler_backend(req, make_response): reqpath = req.rel_url.path if reqpath.startswith('/self/'): return await selfservice.handle_request(req, make_response, mimetype) + if reqpath == '/httpapi_initialized': + if (len(configmanager.ConfigManager(None).list_usergroups()) > 0 + or len(configmanager.ConfigManager(None).list_users()) > 0): + return await make_response(mimetype, 200, "OK") + return await make_response(mimetype, 500, "No authorized users") if reqpath.startswith('/boot/'): request = reqpath.split('/') if not request[0]: @@ -688,14 +695,14 @@ async def resourcehandler_backend(req, make_response): if not pprofile: return await make_response(mimetype, 404, 'Not Found') redir = '/confluent-public/os/{0}/boot.{1}'.format(pprofile, bootfile) - rsp = make_response(mimetype, 302, 'Found', {'Location': redir}) + rsp = await make_response(mimetype, 302, 'Found', {'Location': redir}) return - if req.content_length: + if req.content_length and nat '/staging' in reqpath: reqbody = await req.read() reqtype = req.content_type operation = opmap.get(req.method, None) if not operation: - rsp = make_response(mimetype, 400, 'Bad Request') + rsp = await make_response(mimetype, 400, 'Bad Request') await rsp.write(b'Unsupported method') return rsp querydict = _get_query_dict(req, reqbody, reqtype) @@ -893,6 +900,67 @@ async def resourcehandler_backend(req, make_response): return rsp else: # no keys, but a session, means it's hooking to receive data raise Exception("long polling console sessions are discontinued") +====== + elif (operation == 'create' and ('/firmware/updates/active' in reqpath)): + if 'application/json' in reqtype: + if not isinstance(reqbody, str): + reqbody = reqbody.decode('utf8') + pbody = json.loads(reqbody) + args = pbody['args'] + file_directory = '/var/lib/confluent/client_assets/{}'.format(args.split('/')[-1]) + filepath = '{0}/{1}'.format(file_directory, os.listdir(file_directory)[0]) # TODO find a way to validate that the file is found and its the expected one + args_dict = {'filename': filepath} + noderrs = {} + nodeurls = {} + hdlr = pluginapi.handle_path(reqpath, operation, cfgmgr, args_dict) + for res in hdlr: + if isinstance(res, confluent.messages.CreatedResource): + watchurl = res.kvpairs['created'] + currnode = watchurl.split('/')[1] + nodeurls[currnode] = '/' + watchurl + + rsp = await make_response(mimetype, 200, 'OK', headers=headers) + await rsp.write(json.dumps({'data': nodeurls})) + return + elif (operation == 'create' and ('/staging' in reqpath)): + url = reqpath + args_dict = {} + content_length = int(req.content_length) + if content_length > 0 and (len(url.split('/')) > 2): + # check if the user and the url defined user are the same + if authorized['username'] == url.split('/')[2]: + args_dict.update({'filedata':env, 'content_length': content_length}) # TODO: replace env + hdlr = pluginapi.handle_path(url, operation, cfgmgr, args_dict) + for resp in hdlr: + if isinstance(resp, confluent.messages.FileUploadProgress): + if resp.kvpairs['progress']['value'] == 100: + progress = resp.kvpairs['progress']['value'] + rsp = await make_response(mimetype, 200, 'OK', headers=headers) + await rsp.write(json.dumps({'data': 'done'})) + return + else: + rsp = await make_response(mimetype, 401 'Unauthorized', headers=headers) + await rsp.write(json.dumps({'data': 'You do not have permission to write to file'})) + return + elif len(url.split('/')) == 2: + reqbody = env['wsgi.input'].read(int(env['CONTENT_LENGTH'])) # TODO: replace env + reqtype = env['CONTENT_TYPE'] + if not isinstance(reqbody, str): + reqbody = reqbody.decode('utf8') + pbody = json.loads(reqbody) + args = pbody['args'] + args_dict.update({'filename': args, 'user': authorized['username']}) + try: + args_dict.update({'bank': pbody['bank']}) + except KeyError: + pass + hdlr = pluginapi.handle_path(url, operation, cfgmgr, args_dict) + for res in hdlr: + if isinstance(res, confluent.messages.CreatedResource): + stageurl = res.kvpairs['created'] + rsp = await make_response(mimetype, 200, 'OK', headers=headers) + await rsp.write(json.dumps({'data': stageurl})) + return else: # normal request url = reqpath diff --git a/confluent_server/confluent/messages.py b/confluent_server/confluent/messages.py index fab8fe9e..641cff80 100644 --- a/confluent_server/confluent/messages.py +++ b/confluent_server/confluent/messages.py @@ -645,6 +645,18 @@ class SavedFile(ConfluentMessage): self.myargs = (node, file) self.kvpairs = {node: {'filename': file}} +class FileUploadProgress(ConfluentMessage): + readonly = True + + def __init__(self, progress, name=None): + self.myargs = (progress) + self.stripped = False + self.notnode = name is None + if self.notnode: + self.kvpairs = {'progress': {'value': progress}} + else: + self.kvpairs = {name: {'progress': {'value': progress}}} + class InputAlertData(ConfluentMessage): def __init__(self, path, inputdata, nodes=None): diff --git a/confluent_server/confluent/osimage.py b/confluent_server/confluent/osimage.py index b2115494..d998f3b5 100644 --- a/confluent_server/confluent/osimage.py +++ b/confluent_server/confluent/osimage.py @@ -91,10 +91,13 @@ async def update_boot_esxi(profiledir, profile, label): newbootcfg = '' efibootcfg = '' filesneeded = [] + localabel = label + if 'installation of' not in localabel: + localabel = 'Confluent installation of {}'.format(localabel) for cfgline in bootcfg: if cfgline.startswith('title='): - newbootcfg += 'title={0}\n'.format(label) - efibootcfg += 'title={0}\n'.format(label) + newbootcfg += 'title={0}\n'.format(localabel) + efibootcfg += 'title={0}\n'.format(localabel) elif cfgline.startswith('kernelopt='): newbootcfg += 'kernelopt={0}\n'.format(kernelargs) efibootcfg += 'kernelopt={0}\n'.format(kernelargs) @@ -312,6 +315,7 @@ def check_alma(isoinfo): ver = None arch = None cat = None + suffix = "" for entry in isoinfo[0]: if 'almalinux-release-8' in entry: ver = entry.split('-')[2] @@ -323,6 +327,12 @@ def check_alma(isoinfo): arch = entry.split('.')[-2] cat = 'el9' break + elif 'almalinux-kitten-release-10' in entry: + ver = entry.split('-')[3] + arch = entry.split('.')[-2] + cat = 'el10' + suffix = '_kitten' + break else: return None if arch == 'noarch' and '.discinfo' in isoinfo[1]: @@ -330,7 +340,7 @@ def check_alma(isoinfo): arch = prodinfo.split(b'\n')[2] if not isinstance(arch, str): arch = arch.decode('utf-8') - return {'name': 'alma-{0}-{1}'.format(ver, arch), 'method': EXTRACT, 'category': cat} + return {'name': 'alma{0}-{1}-{2}'.format(suffix, ver, arch), 'method': EXTRACT, 'category': cat} def check_centos(isoinfo): @@ -362,6 +372,12 @@ def check_centos(isoinfo): cat = 'el9' isstream = '_stream' break + elif 'centos-stream-release-10' in entry: + ver = entry.split('-')[3] + arch = entry.split('.')[-2] + cat = 'el10' + isstream = '_stream' + break elif 'centos-linux-release-8' in entry: ver = entry.split('-')[3] arch = entry.split('.')[-2] diff --git a/confluent_server/confluent/plugins/console/openbmc.py b/confluent_server/confluent/plugins/console/openbmc.py index baf86ef1..715348b9 100644 --- a/confluent_server/confluent/plugins/console/openbmc.py +++ b/confluent_server/confluent/plugins/console/openbmc.py @@ -104,6 +104,17 @@ class OpenBmcConsole(conapi.Console): except asyncio.CancelledError: pass + def recvdata(self): + while self.connected: + try: + pendingdata = self.ws.recv() + except websocket.WebSocketConnectionClosedException: + pendingdata = '' + if pendingdata == '': + self.datacallback(conapi.ConsoleEvent.Disconnect) + return + self.datacallback(pendingdata) + async def connect(self, callback): self.datacallback = callback @@ -134,7 +145,11 @@ class OpenBmcConsole(conapi.Console): return async def write(self, data): - await self.ws.send_str(data.decode()) + try: + await self.ws.send_str(data.decode()) + except Exception as e: + print(repr(e)) + await self.datacallback(conapi.ConsoleEvent.Disconnect) async def close(self): if self.recvr: diff --git a/confluent_server/confluent/plugins/hardwaremanagement/enlogic.py b/confluent_server/confluent/plugins/hardwaremanagement/enlogic.py new file mode 100644 index 00000000..196b79df --- /dev/null +++ b/confluent_server/confluent/plugins/hardwaremanagement/enlogic.py @@ -0,0 +1,279 @@ +# Copyright 2022 Lenovo +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pyghmi.util.webclient as wc +import confluent.util as util +import confluent.messages as msg +import confluent.exceptions as exc +import eventlet.green.time as time +import eventlet +import eventlet.greenpool as greenpool + + + +def simplify_name(name): + return name.lower().replace(' ', '_').replace('/', '-').replace('_-_', '-') + + +pdupool = greenpool.GreenPool(128) + +_pduclients = {} + + +class EnlogicClient(object): + def __init__(self, pdu, configmanager): + self.node = pdu + self.configmanager = configmanager + self._token = None + self._wc = None + self.username = None + + @property + def token(self): + if not self._token: + self._token = self.login(self.configmanager) + return self._token + + @property + def wc(self): + if self._wc: + print("done cache") + return self._wc + print("loggin") + targcfg = self.configmanager.get_node_attributes( + self.node, ['hardwaremanagement.manager'], decrypt=True + ) + targcfg = targcfg.get(self.node, {}) + target = targcfg.get('hardwaremanagement.manager', {}).get('value', None) + if not target: + target = self.node + target = target.split('/', 1)[0] + cv = util.TLSCertVerifier( + self.configmanager, self.node, 'pubkeys.tls_hardwaremanager' + ).verify_cert + self._wc = wc.SecureHTTPConnection(target, port=443, verifycallback=cv) + return self._wc + + def grab_json_response(self, url, body=None): + rsp, status = self.wc.grab_json_response_with_status(url, body) + if status == 401: + self._token = None + if body and 'cookie' in body: + body['cookie'] = self.token + rsp, status = self.wc.grab_json_response_with_status(url, body) + if status < 300: + return rsp + return {} + + def login(self, configmanager): + credcfg = configmanager.get_node_attributes( + self.node, + ['secret.hardwaremanagementuser', 'secret.hardwaremanagementpassword'], + decrypt=True, + ) + credcfg = credcfg.get(self.node, {}) + username = credcfg.get('secret.hardwaremanagementuser', {}).get('value', None) + passwd = credcfg.get('secret.hardwaremanagementpassword', {}).get('value', None) + + if not isinstance(username, str): + username = username.decode('utf8') + if not isinstance(passwd, str): + passwd = passwd.decode('utf8') + if not username or not passwd: + raise Exception('Missing username or password') + self.username = username + rsp = self.wc.grab_json_response( + '/xhrlogin.jsp', + {'username': username, 'password': passwd, 'cookie': 0} + ) + print(repr(rsp)) + self.authtoken = rsp['cookie'] + self.wc.set_header('Authorization', self.authtoken) + return self.authtoken + + def logout(self): + if self._token: + self.wc.grab_json_response( + '/xhrlogout.jsp', + {'timeout': 0, 'cookie': self._token}, + ) + self._token = None + + def get_outlet(self, outlet): + rsp = self.grab_json_response( + '/xhroutgetgrid.jsp', { + 'cookie': self.token, + 'pduid': 1 + }) + outlets = rsp['outlet'] + for olet in outlets: + if olet['id'] == int(outlet): + state = "on" if olet['powstat'] == 1 else "off" + return state + + def set_outlet(self, outlet, state): + bitflags = 2**(int(outlet) - 1) + outlet1 = bitflags & (2**24-1) + outlet2 = bitflags >> 24 + if state == 'off': + state = 0 + elif state == 'on': + state = 1 + else: + raise Exception("Unrecognized state " + repr(state)) + request = { + 'cookie': self.token, + 'outlet1': outlet1, + 'outlet2': outlet2, + 'pduid': 1, + 'powstat': state + } + rsp = self.grab_json_response('/xhroutpowstatset.jsp', request) + + +_sensors_by_node = {} + + +def read_sensors(element, node, configmanager): + category, name = element[-2:] + justnames = False + if len(element) == 3: + # just get names + category = name + name = 'all' + justnames = True + if category in ('leds, fans', 'temperature'): + return + if justnames: + yield msg.ChildCollection('total_energy') + yield msg.ChildCollection('total_apparent_power') + yield msg.ChildCollection('total_real_power') + return + sn = _sensors_by_node.get(node, None) + if not sn or sn[1] < time.time(): + gc = get_client(node, configmanager) + adev = gc.grab_json_response('/energy_get', {'cookie': gc.token, 'end': 1, 'start': 1}) + _sensors_by_node[node] = (adev, time.time() + 1) + sn = _sensors_by_node.get(node, None) + if sn: + sn = sn[0] + readings = [ + { + 'name': 'Total Energy', + 'value': float(sn[0]['total_energy']) * 0.001, + 'units': 'kWh', + 'type': 'Energy', + }, + { + 'name': 'Total Real Power', + 'value': float(sn[0]['active_power']), + 'units': 'W', + 'type': 'Power', + }, + { + 'name': 'Total Apparent Power', + 'value': float(sn[0]['apparent_power']), + 'units': 'W', + 'type': 'Power', + }, + ] + yield msg.SensorReadings(readings, name=node) + return + +def get_client(node, configmanager): + if node not in _pduclients: + _pduclients[node] = EnlogicClient(node, configmanager) + return _pduclients[node] + +def get_outlet(element, node, configmanager): + gc = get_client(node, configmanager) + state = gc.get_outlet(element[-1]) + return msg.PowerState(node=node, state=state) + + +def read_firmware(node, configmanager): + gc = get_client(node, configmanager) + adev = gc.grab_json_response('/xhrgetuserlist.jsp') + myversion = adev[0]['fwver'] + yield msg.Firmware([{'PDU Firmware': {'version': myversion}}], node) + + +def read_inventory(element, node, configmanager): + _inventory = {} + inventory = {} + gc = get_client(node, configmanager) + adev = gc.grab_json_response('/sys_info_get', { + 'cookie': gc.token, 'pduid': 1 + }) + inventory['present'] = True + inventory['name'] = 'PDU' + info = {} + info['Serial Number'] = adev['pdu'][0]['serial_number'] + info['Product Name'] = adev['pdu'][0]['model'] + info['Model'] = adev['pdu'][0]['part_number'] + inventory['information'] = info + yield msg.KeyValueData({'inventory': [inventory]}, node) + +def retrieve(nodes, element, configmanager, inputdata): + + if 'outlets' in element: + gp = greenpool.GreenPile(pdupool) + for node in nodes: + + gp.spawn(get_outlet, element, node, configmanager) + for res in gp: + yield res + + return + elif element[0] == 'sensors': + gp = greenpool.GreenPile(pdupool) + for node in nodes: + gp.spawn(read_sensors, element, node, configmanager) + for rsp in gp: + for datum in rsp: + yield datum + return + elif '/'.join(element).startswith('inventory/firmware/all'): + gp = greenpool.GreenPile(pdupool) + for node in nodes: + gp.spawn(read_firmware, node, configmanager) + for rsp in gp: + for datum in rsp: + yield datum + + elif '/'.join(element).startswith('inventory/hardware/all'): + gp = greenpool.GreenPile(pdupool) + for node in nodes: + gp.spawn(read_inventory, element, node, configmanager) + for rsp in gp: + for datum in rsp: + yield datum + else: + for node in nodes: + yield msg.ConfluentResourceUnavailable(node, 'Not implemented') + return + + +def update(nodes, element, configmanager, inputdata): + if 'outlets' not in element: + for node in nodes: + yield msg.ConfluentResourceUnavailable(node, 'Not implemented') + return + for node in nodes: + gc = get_client(node, configmanager) + newstate = inputdata.powerstate(node) + gc.set_outlet(element[-1], newstate) + eventlet.sleep(1) + for res in retrieve(nodes, element, configmanager, inputdata): + yield res diff --git a/confluent_server/confluent/plugins/hardwaremanagement/ipmi.py b/confluent_server/confluent/plugins/hardwaremanagement/ipmi.py index d364e146..2dd44654 100644 --- a/confluent_server/confluent/plugins/hardwaremanagement/ipmi.py +++ b/confluent_server/confluent/plugins/hardwaremanagement/ipmi.py @@ -1371,10 +1371,8 @@ class IpmiHandler: def identify(self): if 'update' == self.op: identifystate = self.inputdata.inputbynode[self.node] == 'on' - if self.inputdata.inputbynode[self.node] == 'blink': - raise exc.InvalidArgumentException( - '"blink" is not supported with ipmi') - self.ipmicmd.set_identify(on=identifystate) + blinkstate = self.inputdata.inputbynode[self.node] == 'blink' + self.ipmicmd.set_identify(on=identifystate, blink=blinkstate) self.output.put(msg.IdentifyState( node=self.node, state=self.inputdata.inputbynode[self.node])) return diff --git a/confluent_server/confluent/selfservice.py b/confluent_server/confluent/selfservice.py index e8114848..de70bcdb 100644 --- a/confluent_server/confluent/selfservice.py +++ b/confluent_server/confluent/selfservice.py @@ -52,7 +52,7 @@ def listdump(input): def get_extra_names(nodename, cfg, myip=None): - names = set([]) + names = set(['127.0.0.1', '::1', 'localhost', 'localhost.localdomain']) dnsinfo = cfg.get_node_attributes(nodename, ('dns.*', 'net.*hostname')) dnsinfo = dnsinfo.get(nodename, {}) domain = dnsinfo.get('dns.domain', {}).get('value', None) @@ -625,4 +625,8 @@ def get_cluster_list(nodename=None, cfg=None): nodes.add(myname) if domain and domain not in myname: nodes.add('{0}.{1}'.format(myname, domain)) + nodes.add('::1') + nodes.add('127.0.0.1') + nodes.add('localhost') + nodes.add('localhost.domain') return nodes, domain diff --git a/confluent_server/confluent/vinzmanager.py b/confluent_server/confluent/vinzmanager.py index 1bfdaac7..8a2d672a 100644 --- a/confluent_server/confluent/vinzmanager.py +++ b/confluent_server/confluent/vinzmanager.py @@ -16,26 +16,36 @@ _vinztoken = None import socket import aiohmi.util.webclient as webclient +startingup = False + # Handle the vinz VNC session async def assure_vinz(): global _vinzfd global _vinztoken - if _vinzfd is None: - _vinztoken = base64.b64encode(os.urandom(33), altchars=b'_-').decode() - os.environ['VINZ_TOKEN'] = _vinztoken - os.makedirs('/var/run/confluent/vinz/sessions', exist_ok=True) - - _vinzfd = await asyncio.subprocess.create_subprocess_exec( + global startingup + while startingup: + await asyncio.sleep(0.5) + try: + startingup = True + if _vinzfd is None: + _vinztoken = base64.b64encode(os.urandom(33), altchars=b'_-').decode() + os.environ['VINZ_TOKEN'] = _vinztoken + os.makedirs('/var/run/confluent/vinz/sessions', exist_ok=True) + os.chmod('/var/run/confluent/vinz', 0o711) + os.chmod('/var/run/confluent/vinz/sessions', 0o711) + _vinzfd = await asyncio.subprocess.create_subprocess_exec( '/opt/confluent/bin/vinz', '-c', '/var/run/confluent/vinz/control', '-w', '127.0.0.1:4007', '-a', '/var/run/confluent/vinz/approval', # vinz supports unix domain websocket, however apache reverse proxy is dicey that way in some versions '-d', '/var/run/confluent/vinz/sessions') - while not os.path.exists('/var/run/confluent/vinz/control'): - await asyncio.sleep(0.5) - util.spawn(monitor_requests()) + while not os.path.exists('/var/run/confluent/vinz/control'): + await asyncio.sleep(0.5) + util.spawn(monitor_requests) + finally: + startingup = False _unix_by_nodename = {} async def get_url(nodename, inputdata): diff --git a/confluent_server/confluent/webauthn.py b/confluent_server/confluent/webauthn.py index 7e3b148f..4ca39057 100644 --- a/confluent_server/confluent/webauthn.py +++ b/confluent_server/confluent/webauthn.py @@ -1,110 +1,323 @@ -import base64 import confluent.tlvdata as tlvdata import confluent.util as util import json -import pywarp -import pywarp.backends -import pywarp.credentials +import copy +import base64 +import secrets, time +from typing import Any, Optional +from webauthn import ( + generate_registration_options, + options_to_json, + generate_authentication_options, + ) +from webauthn.helpers.structs import ( + AuthenticatorSelectionCriteria, + UserVerificationRequirement, +) + +from webauthn import verify_registration_response +from webauthn import verify_authentication_response + challenges = {} -class ConfluentBackend(pywarp.backends.CredentialStorageBackend): - def __init__(self, cfg): - self.cfg = cfg +CONFIG_MANAGER = None - def get_credential_ids_by_email(self, email): - if not isinstance(email, str): - email = email.decode('utf8') - authenticators = self.cfg.get_user(email).get('authenticators', {}) - if not authenticators: - raise Exception('No authenticators found') - for cid in authenticators: - yield base64.b64decode(cid) +class Credential(): + def __init__(self, id, signature_count, public_key): + self.id = id + self.signature_count = signature_count + self.credential_public_key = public_key - def get_credential_by_email_id(self, email, id): - if not isinstance(email, str): - email = email.decode('utf8') - authenticators = self.cfg.get_user(email).get('authenticators', {}) - cid = base64.b64encode(id).decode('utf8') - pk = authenticators[cid]['cpk'] - pk = base64.b64decode(pk) - return pywarp.credentials.Credential(credential_id=id, credential_public_key=pk) +class Challenge(): + def __init__(self, request, id=None) -> None: + if id is None: + self.id = util.randomstring(16) + else: + self.id = id + self.request = request - def get_credential_by_email(self, email): - if not isinstance(email, str): - email = email.decode('utf8') - authenticators = self.cfg.get_user(email) - cid = list(authenticators)[0] - cred = authenticators[cid] - cid = base64.b64decode(cred['cid']) - cpk = base64.b64decode(cred['cpk']) - return pywarp.credentials.Credential(credential_id=cid, credential_public_key=cpk) +def _load_credentials(creds): + if creds is None: + return None + ret = copy.deepcopy(creds) + ret['credential_public_key'] = base64.b64decode(creds['credential_public_key']) + ret['id'] = base64.b64decode(creds['id']) + return ret - def save_credential_for_user(self, email, credential): - if not isinstance(email, str): - email = email.decode('utf8') - cid = base64.b64encode(credential.id).decode('utf8') - credential = {'cid': cid, 'cpk': base64.b64encode(bytes(credential.public_key)).decode('utf8')} - authenticators = self.cfg.get_user(email).get('authenticators', {}) - authenticators[cid] = credential - self.cfg.set_user(email, {'authenticators': authenticators}) +def _load_authenticators(authenticators): + ret = authenticators + if 'challenges' in ret: + if not ret['challenges'] is None: + ret['challenges']['request'] = base64.b64decode(ret['challenges']['request']) + if 'credentials' in ret: + ret['credentials'] = _load_credentials(ret['credentials']) + return ret - def save_challenge_for_user(self, email, challenge, type): - if not isinstance(email, str): - email = email.decode('utf8') - challenges[email] = challenge +class User(): + def __init__(self, id, username, user_handle, challenge: Challenge = None, credential: Credential = None): + self.id = id + self.username = username + self.user_handle = user_handle + self.challenges = challenge + self.credentials = credential - def get_challenge_for_user(self, email, type): - if not isinstance(email, str): - email = email.decode('utf8') - return challenges[email] + def __parse_credentials(self): + if self.credentials: + credid = base64.b64encode(self.credentials.id).decode() + pubkey = base64.b64encode(self.credentials.credential_public_key).decode() + return {"id": credid, "signature_count": self.credentials.signature_count, "credential_public_key": pubkey} + def __parse_challenges(self): + if self.challenges: + request = base64.b64encode(self.challenges.request).decode() + return {"id": self.challenges.id, 'request': request} + + + @staticmethod + def seek_credential_by_id(credential_id): + """ + There certainly is a better way to do this but for now lets try the wrong way that works + """ + for username in CONFIG_MANAGER.list_users(): + authenticators = CONFIG_MANAGER.get_user(username).get('authenticators', {}) + authenticators = _load_authenticators(authenticators) + try: + credential = authenticators['credentials'] + except KeyError: + continue + if "id" in credential.keys() and credential["id"] == credential_id: + #for now leaving signature count as None + return (Credential(id=credential["id"], signature_count=None, public_key=credential["credential_public_key"]), username) + return None + + + @staticmethod + def get_credential(credential_id, username): + if not isinstance(username, str): + username = username.decode('utf8') + authenticators = CONFIG_MANAGER.get_user(username).get('authenticators', {}) + authenticators = _load_authenticators(authenticators) + credential = authenticators.get('credentials', None) + if credential is None: + return None + + if credential_id is None: + return Credential(id=credential["id"], signature_count=credential["signature_count"], public_key=credential["credential_public_key"]) + + return None + + @staticmethod + def get_challenge(username): + if not isinstance(username, str): + username = username.decode('utf8') + authuser = CONFIG_MANAGER.get_user(username) + if not authuser: + return None + authenticators = authuser.get('authenticators', {}) + authenticators = _load_authenticators(authenticators) + challenge = authenticators['challenges'] + return Challenge(request=challenge["request"], id=challenge["id"]) + + + @staticmethod + def get(username): + challenges_return = None + credentials_return = None + if not CONFIG_MANAGER: + raise Exception('config manager is not set up') + if not isinstance(username, str): + username = username.decode('utf8') + userinfo = CONFIG_MANAGER.get_user(username) + try: + authenticators = CONFIG_MANAGER.get_user(username).get('authenticators', {}) + except AttributeError: + return None + if userinfo is None: + return None + authenticators = _load_authenticators(authenticators) + b64authid = userinfo.get('webauthid', None) + if b64authid is None: + authid = None + else: + authid = base64.b64decode(b64authid) + challenge = authenticators.get("challenges", None) + if challenge: + challenges_return = Challenge(challenge['request'], id=challenge["id"]) + + credential = authenticators.get("credentials", None) + if credential: + credentials_return = (Credential(credential['id'], credential['signature_count'], credential["credential_public_key"])) + + return User(id=None, username=username, user_handle=authid, challenge=challenges_return, credential=credentials_return) + + def save(self): + authenticators = CONFIG_MANAGER.get_user(self.username).get('authenticators', {}) + authenticators = _load_authenticators(authenticators) + authenticators['challenges'] = self.__parse_challenges() # Looks like the bigger the array we encounter problems changing to just save one challenge + authenticators['credentials'] = self.__parse_credentials() + + CONFIG_MANAGER.set_user(self.username, {'authenticators': authenticators}) + + + def add(self, item): + if isinstance(item, Challenge): + self.challenges = item + elif isinstance(item, Credential): + self.credentials = item + + def update(self, item): + if isinstance(item, Challenge): + self.challenges = item + elif isinstance(item, Credential): + self.credentials = item + return + #raise Exception("Credential item not found") + + +def registration_request(username, cfg, APP_RELYING_PARTY): + user_model = User.get(username) + if user_model is None: + raise Exception("User not foud") + + options = generate_registration_options( + rp_name=APP_RELYING_PARTY.name, + rp_id=APP_RELYING_PARTY.id, + user_id=user_model.user_handle, + user_name=username, + authenticator_selection=AuthenticatorSelectionCriteria( + user_verification=UserVerificationRequirement.REQUIRED, + ), + ) + + challenge = Challenge(options.challenge) + user_model.add(challenge) + user_model.save() + options_json = options_to_json(options) + return options_json + + +def registration_response(request, username, APP_RELYING_PARTY, APP_ORIGIN): + challenge_model = User.get_challenge(username) + if not challenge_model: + raise Exception("Could not find challenge matching given id") + + user_model = User.get(username) + if not user_model: + raise Exception("Invalid Username") + + try: + registration_verification = verify_registration_response( + credential=request, + expected_challenge=challenge_model.request, + expected_rp_id=APP_RELYING_PARTY.id, + expected_origin=APP_ORIGIN, + require_user_verification=True, + ) + except Exception as err: + raise Exception("Could not handle credential attestation") + + credential = Credential(id=registration_verification.credential_id, signature_count=registration_verification.sign_count, public_key=registration_verification.credential_public_key) + user_model.add(credential) + user_model.save() + + return {"verified": True} + + +def authentication_request(username, APP_RELYING_PARTY): + user_model = User.get(username) + if not user_model: + raise Exception("Invalid Username") + + options = generate_authentication_options( + rp_id=APP_RELYING_PARTY.id, + user_verification=UserVerificationRequirement.REQUIRED, + ) + + challenge = Challenge(options.challenge) + user_model.add(challenge) + user_model.save() + opts = options_to_json(options) + return opts + +def authentication_response(request, username, APP_RELYING_PARTY, APP_ORIGIN): + user_model = User.get(username) + if not user_model: + raise Exception("Invalid Username") + + challenge_model = User.get_challenge(username) + if not challenge_model: + raise Exception("Could not find challenge matching given id") + + credential_model = User.get_credential(credential_id=None, username=username) + if not credential_model: + raise Exception("No credential for user") + + verification = verify_authentication_response( + credential=request, + expected_challenge=challenge_model.request, + expected_rp_id=APP_RELYING_PARTY.id, + expected_origin=APP_ORIGIN, + credential_public_key = credential_model.credential_public_key, + credential_current_sign_count = 0, + require_user_verification = True + + ) + + return {"verified": True} + +class RpEntity(object): + def __init__(self, name, id): + self.name = name + self.id = id + def handle_api_request(url, env, start_response, username, cfm, headers, reqbody, authorized): + """ + For now webauth is going to be limited to just one passkey per user + If you try to register a new passkey this will just clear the old one and register the new passkey + """ + global CONFIG_MANAGER + CONFIG_MANAGER = cfm + + APP_ORIGIN = 'https://' + env['HTTP_X_FORWARDED_HOST'] + HOST = env['HTTP_X_FORWARDED_HOST'] + APP_RELYING_PARTY = RpEntity(name='Confluent Web UI', id=HOST) + if env['REQUEST_METHOD'] != 'POST': raise Exception('Only POST supported for webauthn operations') url = url.replace('/sessions/current/webauthn', '') if url == '/registration_options': - rp = pywarp.RelyingPartyManager('Confluent Web UI', credential_storage_backend=ConfluentBackend(cfm), require_attestation=False) userinfo = cfm.get_user(username) if not userinfo: cfm.create_user(username, role='Stub') userinfo = cfm.get_user(username) - authid = userinfo.get('authid', None) + authid = userinfo.get('webauthid', None) if not authid: - authid = util.randomstring(64) - cfm.set_user(username, {'authid': authid}) - opts = rp.get_registration_options(username) - # pywarp generates an id derived - # from username, which is a 'must not' in the spec - # we replace that with a complying approach - opts['user']['id'] = authid - if 'icon' in opts['user']: - del opts['user']['icon'] - if 'id' in opts['rp']: - del opts['rp']['id'] + authid = secrets.token_bytes(64) + b64authid = base64.b64encode(authid).decode() + cfm.set_user(username, {'webauthid': b64authid}) + opts = registration_request(username, cfm, APP_RELYING_PARTY) start_response('200 OK', headers) - yield json.dumps(opts) + yield opts elif url.startswith('/registered_credentials/'): username = url.rsplit('/', 1)[-1] - rp = pywarp.RelyingPartyManager('Confluent Web UI', credential_storage_backend=ConfluentBackend(cfm)) + userinfo = cfm.get_user(username) if not isinstance(username, bytes): username = username.encode('utf8') - opts = rp.get_authentication_options(username) - opts['challenge'] = base64.b64encode(opts['challenge']).decode('utf8') + opts = authentication_request(username, APP_RELYING_PARTY) start_response('200 OK', headers) - yield json.dumps(opts) + yield opts elif url.startswith('/validate/'): username = url.rsplit('/', 1)[-1] + userinfo = cfm.get_user(username) if not isinstance(username, bytes): username = username.encode('utf8') - rp = pywarp.RelyingPartyManager('Confluent Web UI', credential_storage_backend=ConfluentBackend(cfm)) req = json.loads(reqbody) - for x in req: - req[x] = base64.b64decode(req[x].replace('-', '+').replace('_', '/')) - req['email'] = username - rsp = rp.verify(**req) - if start_response: + rsp = authentication_response(req, username, APP_RELYING_PARTY, APP_ORIGIN) + if rsp == 'Timeout': + start_response('408 Timeout', headers) + elif rsp['verified'] and start_response: start_response('200 OK', headers) sessinfo = {'username': username} if 'authtoken' in authorized: @@ -116,13 +329,13 @@ def handle_api_request(url, env, start_response, username, cfm, headers, reqbody else: yield rsp elif url == '/register_credential': - rp = pywarp.RelyingPartyManager('Confluent Web UI', credential_storage_backend=ConfluentBackend(cfm), require_attestation=False) req = json.loads(reqbody) - for x in req: - req[x] = base64.b64decode(req[x].replace('-', '+').replace('_', '/')) + userinfo = cfm.get_user(username) if not isinstance(username, bytes): username = username.encode('utf8') - req['email'] = username - rsp = rp.register(**req) - start_response('200 OK', headers) - yield json.dumps(rsp) \ No newline at end of file + rsp = registration_response(req, username, APP_RELYING_PARTY, APP_ORIGIN) + if rsp.get('verified', False): + start_response('200 OK', headers) + yield json.dumps({'status': 'Success'}) + + diff --git a/confluent_server/confluent_server.spec.tmpl b/confluent_server/confluent_server.spec.tmpl index 0a36390c..1fb62d71 100644 --- a/confluent_server/confluent_server.spec.tmpl +++ b/confluent_server/confluent_server.spec.tmpl @@ -18,15 +18,17 @@ Prefix: %{_prefix} BuildArch: noarch Requires: confluent_vtbufferd %if "%{dist}" == ".el7" -Requires: python-pyghmi >= 1.5.71, python-eventlet, python-greenlet, python-pycryptodomex >= 3.4.7, confluent_client == %{version}, python-pyparsing, python-paramiko, python-dnspython, python-netifaces, python2-pyasn1 >= 0.2.3, python-pysnmp >= 4.3.4, python-lxml, python-eficompressor, python-setuptools, python-dateutil, python-websocket-client python2-msgpack python-libarchive-c python-yaml python-monotonic cpio + +Requires: python-pyghmi >= 1.5.71, python-eventlet, python-greenlet, python-pycryptodomex >= 3.4.7, confluent_client == %{version}, python-pyparsing, python-paramiko, python-dnspython, python-netifaces, python2-pyasn1 >= 0.2.3, python-pysnmp >= 4.3.4, python-lxml, python-eficompressor, python-setuptools, python-dateutil, python-websocket-client python2-msgpack python-libarchive-c python-yaml python-monotonic %else %if "%{dist}" == ".el8" -Requires: python3-pyghmi >= 1.5.71, python3-eventlet, python3-greenlet, python3-pycryptodomex >= 3.4.7, confluent_client == %{version}, python3-pyparsing, python3-paramiko, python3-dns, python3-netifaces, python3-pyasn1 >= 0.2.3, python3-pysnmp >= 4.3.4, python3-lxml, python3-eficompressor, python3-setuptools, python3-dateutil, python3-enum34, python3-asn1crypto, python3-cffi, python3-pyOpenSSL, python3-websocket-client python3-msgpack python3-libarchive-c python3-yaml openssl iproute cpio +Requires: python3-pyghmi >= 1.5.71, python3-eventlet, python3-greenlet, python3-pycryptodomex >= 3.4.7, confluent_client == %{version}, python3-pyparsing, python3-paramiko, python3-dns, python3-netifaces, python3-pyasn1 >= 0.2.3, python3-pysnmp >= 4.3.4, python3-lxml, python3-eficompressor, python3-setuptools, python3-dateutil, python3-enum34, python3-asn1crypto, python3-cffi, python3-pyOpenSSL, python3-websocket-client python3-msgpack python3-libarchive-c python3-yaml openssl iproute %else %if "%{dist}" == ".el9" -Requires: python3-pyghmi >= 1.5.71, python3-eventlet, python3-greenlet, python3-pycryptodomex >= 3.4.7, confluent_client == %{version}, python3-pyparsing, python3-paramiko, python3-dns, python3-netifaces, python3-pyasn1 >= 0.2.3, python3-pysnmp >= 4.3.4, python3-lxml, python3-eficompressor, python3-setuptools, python3-dateutil, python3-cffi, python3-pyOpenSSL, python3-websocket-client python3-msgpack python3-libarchive-c python3-yaml openssl iproute cpio +Requires: python3-pyghmi >= 1.5.71, python3-eventlet, python3-greenlet, python3-pycryptodomex >= 3.4.7, confluent_client == %{version}, python3-pyparsing, python3-paramiko, python3-dns, python3-webauthn, python3-netifaces, python3-pyasn1 >= 0.2.3, python3-pysnmp >= 4.3.4, python3-lxml, python3-eficompressor, python3-setuptools, python3-dateutil, python3-cffi, python3-pyOpenSSL, python3-websocket-client python3-msgpack python3-libarchive-c python3-yaml openssl iproute %else Requires: python3-dbm,python3-pyghmi >= 1.5.71, python3-eventlet, python3-greenlet, python3-pycryptodome >= 3.4.7, confluent_client == %{version}, python3-pyparsing, python3-paramiko, python3-dnspython, python3-netifaces, python3-pyasn1 >= 0.2.3, python3-pysnmp >= 4.3.4, python3-lxml, python3-eficompressor, python3-setuptools, python3-dateutil, python3-cffi, python3-pyOpenSSL, python3-websocket-client python3-msgpack python3-libarchive-c python3-PyYAML openssl iproute + %endif %endif %endif diff --git a/genesis/confluent-genesis.spec b/genesis/confluent-genesis.spec index beaeb5cd..e652ed5e 100644 --- a/genesis/confluent-genesis.spec +++ b/genesis/confluent-genesis.spec @@ -1,5 +1,5 @@ %define arch x86_64 -Version: 3.10.0 +Version: 3.12.0 Release: 1 Name: confluent-genesis-%{arch} BuildArch: noarch diff --git a/genesis/exlicenses/bash/NOTICE b/genesis/exlicenses/bash/NOTICE new file mode 100644 index 00000000..5d581e30 --- /dev/null +++ b/genesis/exlicenses/bash/NOTICE @@ -0,0 +1,54 @@ +Copyright and licensing for additional files included in the bash package: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Files: lib/sh/inet_aton.c +Copyright: 1983, 1990, 1993 The Regents of the University of California. All rights reserved. +License: BSD-4-UC AND HPND + +* Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * - + * Portions Copyright (c) 1993 by Digital Equipment Corporation. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies, and that + * the name of Digital Equipment Corporation not be used in advertising or + * publicity pertaining to distribution of the document or software without + * specific, written prior permission. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND DIGITAL EQUIPMENT CORP. DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DIGITAL EQUIPMENT + * CORPORATION BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR + * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS + * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + * - + * --Copyright-- \ No newline at end of file diff --git a/genesis/exlicenses/dhcp-common/NOTICE b/genesis/exlicenses/dhcp-common/NOTICE new file mode 100644 index 00000000..08aa9830 --- /dev/null +++ b/genesis/exlicenses/dhcp-common/NOTICE @@ -0,0 +1,157 @@ +Copyright and licensing for additional files included in the dhcp package: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Files: + server/ldap_krb_helper.c + omapip/inet_addr.c +Copyright: Copyright (c) 2015 by Internet Systems Consortium, Inc. ("ISC") All rights reserved. +License: BSD-3 + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of The Internet Software Consortium nor the names + * of its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND + * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This helper was written by William Brown , + * inspired by krb5_helper.c from bind-dyndb-ldap by Simo Sorce (Redhat) + +Files: server/ldap_casa.c +Copyright: Copyright (c) 2006 Novell, Inc. +License: BSD-3 AND ISC + + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1.Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2.Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3.Neither the name of ISC, ISC DHCP, nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + + * THIS SOFTWARE IS PROVIDED BY INTERNET SYSTEMS CONSORTIUM AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ISC OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + + * This file was written by S Kalyanasundaram + */ + +/* + * Copyright (c) 2004-2010 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1995-2003 by Internet Software Consortium + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * + * https://www.isc.org/ + +Files: server/ldap.c.cloexec + server/ldap.c +Copyright: + Copyright (c) 2010,2015-2016 by Internet Systems Consortium, Inc. ("ISC") + Copyright (c) 2003-2006 Ntelos, Inc. + All rights reserved. +License: BSD-3 + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of The Internet Software Consortium nor the names + * of its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND + * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This LDAP module was written by Brian Masney . Its + * development was sponsored by Ntelos, Inc. (www.ntelos.com). + +Files: omapip/inet_addr.c +Copyright: Copyright (c) 1983, 1990, 1993 The Regents of the University of California. All rights reserved. +License: BSD-3-UC + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. \ No newline at end of file diff --git a/genesis/exlicenses/libgcrypt/LICENSES.ppc-aes-gcm b/genesis/exlicenses/libgcrypt/LICENSES.ppc-aes-gcm new file mode 100644 index 00000000..f6733a69 --- /dev/null +++ b/genesis/exlicenses/libgcrypt/LICENSES.ppc-aes-gcm @@ -0,0 +1,238 @@ +Additional license notices for Libgcrypt. -*- org -*- + +This file contains the copying permission notices for various files in +the Libgcrypt distribution which are not covered by the GNU Lesser +General Public License (LGPL) or the GNU General Public License (GPL). + +These notices all require that a copy of the notice be included +in the accompanying documentation and be distributed with binary +distributions of the code, so be sure to include this file along +with any binary distributions derived from the GNU C Library. + +* BSD_3Clause + + For files: + - cipher/sha256-avx-amd64.S + - cipher/sha256-avx2-bmi2-amd64.S + - cipher/sha256-ssse3-amd64.S + - cipher/sha512-avx-amd64.S + - cipher/sha512-avx2-bmi2-amd64.S + - cipher/sha512-ssse3-amd64.S + +#+begin_quote + Copyright (c) 2012, Intel Corporation + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the + distribution. + + * Neither the name of the Intel Corporation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + + THIS SOFTWARE IS PROVIDED BY INTEL CORPORATION "AS IS" AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTEL CORPORATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#+end_quote + + + For files: + - random/jitterentropy-base.c + - random/jitterentropy.h + - random/rndjent.c (plus common Libgcrypt copyright holders) + +#+begin_quote + * Copyright Stephan Mueller , 2013 + * + * License + * ======= + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU General Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF + * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. +#+end_quote + +* X License + + For files: + - install.sh + +#+begin_quote + Copyright (C) 1994 X Consortium + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to + deal in the Software without restriction, including without limitation the + rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC- + TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of the X Consortium shall not + be used in advertising or otherwise to promote the sale, use or other deal- + ings in this Software without prior written authorization from the X Consor- + tium. +#+end_quote + +* Public domain + + For files: + - cipher/arcfour-amd64.S + +#+begin_quote + Author: Marc Bevand + Licence: I hereby disclaim the copyright on this code and place it + in the public domain. +#+end_quote + +* OCB license 1 + + For files: + - cipher/cipher-ocb.c + +#+begin_quote + OCB is covered by several patents but may be used freely by most + software. See http://web.cs.ucdavis.edu/~rogaway/ocb/license.htm . + In particular license 1 is suitable for Libgcrypt: See + http://web.cs.ucdavis.edu/~rogaway/ocb/license1.pdf for the full + license document; it basically says: + + License 1 — License for Open-Source Software Implementations of OCB + (Jan 9, 2013) + + Under this license, you are authorized to make, use, and + distribute open-source software implementations of OCB. This + license terminates for you if you sue someone over their + open-source software implementation of OCB claiming that you have + a patent covering their implementation. + + + + License for Open Source Software Implementations of OCB + January 9, 2013 + + 1 Definitions + + 1.1 “Licensor” means Phillip Rogaway. + + 1.2 “Licensed Patents” means any patent that claims priority to United + States Patent Application No. 09/918,615 entitled “Method and Apparatus + for Facilitating Efficient Authenticated Encryption,” and any utility, + divisional, provisional, continuation, continuations-in-part, reexamination, + reissue, or foreign counterpart patents that may issue with respect to the + aforesaid patent application. This includes, but is not limited to, United + States Patent No. 7,046,802; United States Patent No. 7,200,227; United + States Patent No. 7,949,129; United States Patent No. 8,321,675 ; and any + patent that issues out of United States Patent Application No. 13/669,114. + + 1.3 “Use” means any practice of any invention claimed in the Licensed Patents. + + 1.4 “Software Implementation” means any practice of any invention + claimed in the Licensed Patents that takes the form of software executing on + a user-programmable, general-purpose computer or that takes the form of a + computer-readable medium storing such software. Software Implementation does + not include, for example, application-specific integrated circuits (ASICs), + field-programmable gate arrays (FPGAs), embedded systems, or IP cores. + + 1.5 “Open Source Software” means software whose source code is published + and made available for inspection and use by anyone because either (a) the + source code is subject to a license that permits recipients to copy, modify, + and distribute the source code without payment of fees or royalties, or + (b) the source code is in the public domain, including code released for + public use through a CC0 waiver. All licenses certified by the Open Source + Initiative at opensource.org as of January 9, 2013 and all Creative Commons + licenses identified on the creativecommons.org website as of January 9, + 2013, including the Public License Fallback of the CC0 waiver, satisfy these + requirements for the purposes of this license. + + 1.6 “Open Source Software Implementation” means a Software + Implementation in which the software implicating the Licensed Patents is + Open Source Software. Open Source Software Implementation does not include + any Software Implementation in which the software implicating the Licensed + Patents is combined, so as to form a larger program, with software that is + not Open Source Software. + + 2 License Grant + + 2.1 License. Subject to your compliance with the term s of this license, + including the restriction set forth in Section 2.2, Licensor hereby + grants to you a perpetual, worldwide, non-exclusive, non-transferable, + non-sublicenseable, no-charge, royalty-free, irrevocable license to practice + any invention claimed in the Licensed Patents in any Open Source Software + Implementation. + + 2.2 Restriction. If you or your affiliates institute patent litigation + (including, but not limited to, a cross-claim or counterclaim in a lawsuit) + against any entity alleging that any Use authorized by this license + infringes another patent, then any rights granted to you under this license + automatically terminate as of the date such litigation is filed. + + 3 Disclaimer + YOUR USE OF THE LICENSED PATENTS IS AT YOUR OWN RISK AND UNLESS REQUIRED + BY APPLICABLE LAW, LICENSOR MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY + KIND CONCERNING THE LICENSED PATENTS OR ANY PRODUCT EMBODYING ANY LICENSED + PATENT, EXPRESS OR IMPLIED, STATUT ORY OR OTHERWISE, INCLUDING, WITHOUT + LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR + PURPOSE, OR NONINFRINGEMENT. IN NO EVENT WILL LICENSOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + ARISING FROM OR RELATED TO ANY USE OF THE LICENSED PATENTS, INCLUDING, + WITHOUT LIMITATION, DIRECT, INDIRECT, INCIDENTAL, CONSEQUENTIAL, PUNITIVE + OR SPECIAL DAMAGES, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF + SUCH DAMAGES PRIOR TO SUCH AN OCCURRENCE. +#+end_quote diff --git a/genesis/exlicenses/libsepol/NOTICE b/genesis/exlicenses/libsepol/NOTICE new file mode 100644 index 00000000..4bb15d21 --- /dev/null +++ b/genesis/exlicenses/libsepol/NOTICE @@ -0,0 +1,54 @@ +Copyright and licensing for additional files included in the libsepol package: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Files: cil/* +Copyright: Copyright 2011, 2014 Tresys Technology, LLC. All rights reserved. +License: BSD-2 + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY TRESYS TECHNOLOGY, LLC ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL TRESYS TECHNOLOGY, LLC OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those + * of the authors and should not be interpreted as representing official policies, + * either expressed or implied, of Tresys Technology, LLC. + +Files: cil/test/unit/CuTest.c +Copyright: Copyright (c) 2003 Asim Jalis +License: Zlib + + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in + * a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not + * be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. \ No newline at end of file diff --git a/genesis/getlicenses.py b/genesis/getlicenses.py index e87e2786..380fc517 100644 --- a/genesis/getlicenses.py +++ b/genesis/getlicenses.py @@ -76,7 +76,7 @@ manuallicenses = [ '/usr/share/licenses/lz4/LICENSE.BSD', '/usr/share/licenses/nss/LICENSE.APACHE', # http://www.apache.org/licenses/LICENSE-2.0 '/usr/share/licenses/openssh/COPYING.blowfish', # from header of blowfish file in bsd-compat - '/usr/share/licenses/bc/COPYING.GPLv2', + '/usr/share/licenses/bc/COPYING.GPLv2', # generic copy of GPLv2 '/usr/share/licenses/bind-license/LICENSE', # MPLv2 from the source code '/usr/share/licenses/procps-ng/COPYING.LIBv2.1', # fetched internet # cp /usr/share/doc/lz4-libs/LICENSE /usr/share/licenses/lz4/LICENSE.BSD @@ -87,7 +87,7 @@ manuallicenses = [ '/usr/share/licenses/pcre/LICENSE.BSD2', # stack-less just in time compiler, Zoltan Herzeg '/usr/share/licenses/sqlite/LICENSE.md', # https://raw.githubusercontent.com/sqlite/sqlite/master/LICENSE.md '/usr/share/licenses/pcre2/LICENSE.BSD2', - '/usr/share/licenses/dhcp-common/NOTICE', + '/usr/share/licenses/dhcp-common/NOTICE', # from exlicenses '/usr/share/licenses/xz/COPYING.GPLv3', # manually extracted from xz source '/usr/share/licenses/bash/NOTICE', '/usr/share/licenses/libsepol/NOTICE', @@ -111,6 +111,7 @@ manuallicenses = [ '/usr/share/licenses/tmux/NOTICE', # built by extracttmuxlicenses.py '/usr/share/licenses/tmux/COPYING', # extracted from source '/usr/share/licenses/tmux/README', # extracted from source + # yum download kernel soruce, cp -a from LICENSES to kernel-extra '/usr/share/licenses/kernel-extra/preferred/BSD-2-Clause', '/usr/share/licenses/kernel-extra/preferred/BSD-3-Clause', '/usr/share/licenses/kernel-extra/preferred/BSD-3-Clause-Clear', @@ -132,10 +133,10 @@ manuallicenses = [ '/usr/share/licenses/kernel-extra/exceptions/GCC-exception-2.0', '/usr/share/licenses/kernel-extra/exceptions/Linux-syscall-note', '/usr/share/licenses/util-linux/COPYING.GPLv3', # extract from parse-date.c, from srpm - '/usr/share/licenses/kmod/COPYING', # GPL not LGPL, must extract from kmod srpm + '/usr/share/licenses/kmod/COPYING', # GPL not LGPL, must extract from kmod srpm, tools subdir '/usr/share/licenses/krb5-libs/NOTICE', # copy it verbatim from LICENSE, exact same file '/usr/share/doc/less/README', - '/usr/share/centos-release/EULA', + '/usr/share/almalinux-release/EULA', #'/usr/share/doc/almalinux-release/GPL', '/usr/share/licenses/libcap-ng-utils/COPYING', '/usr/share/licenses/libdb/copyright', # from libdb, db-5.3.28, lang/sql/odbc/debian/copyright diff --git a/imgutil/imgutil b/imgutil/imgutil index c5446069..276ff601 100644 --- a/imgutil/imgutil +++ b/imgutil/imgutil @@ -612,6 +612,22 @@ class SuseHandler(OsHandler): else: subprocess.check_call(['zypper', '-n', '-R', self.targpath, 'install'] + self.zyppargs) os.symlink('/usr/lib/systemd/system/sshd.service', os.path.join(self.targpath, 'etc/systemd/system/multi-user.target.wants/sshd.service')) + with open(os.path.join(self.targpath, 'etc/permissions.local'), 'a') as permout: + permout.write( + '/usr/lib/ssh/ssh-keysign root:ssh_keys 2711\n' + '/etc/ssh/ssh_host_dsa_key root:ssh_keys 640\n' + '/etc/ssh/ssh_host_ecdsa_key root:ssh_keys 640\n' + '/etc/ssh/ssh_host_ed25519_key root:ssh_keys 640\n' + '/etc/ssh/ssh_host_rsa_key root:ssh_keys 640\n' + ) + args.cmd = ['groupadd', 'ssh_keys'] + run_constrainedx(fancy_chroot, (args, + self.targpath)) + args.cmd = ['chkstat', '--system', '--set'], + run_constrainedx(fancy_chroot, (args, + self.targpath)) + + if os.path.exists(os.path.join(self.targpath, 'sbin/mkinitrd')): args.cmd = ['mkinitrd'] else: diff --git a/misc/grabscreenshot.py b/misc/grabscreenshot.py new file mode 100644 index 00000000..f1fe8a58 --- /dev/null +++ b/misc/grabscreenshot.py @@ -0,0 +1,31 @@ +import base64 +import pyghmi.redfish.command as ic +import pyghmi.util.webclient as webclient +import sys +import os +import time + +def iterm_draw(databuf): + datalen = len(databuf) + data = base64.b64encode(databuf).decode('utf8') + sys.stdout.write( + '\x1b]1337;File=inline=1;size={}:'.format(datalen)) + sys.stdout.write(data) + sys.stdout.write('\a') + sys.stdout.write('\n') + sys.stdout.flush() + + +i = ic.Command(sys.argv[1], os.environ['XCCUSER'], os.environ['XCCPASS'], verifycallback=lambda x: True) +i.get_health() +#url = '/download/Mini_ScreenShot.png?t={}'.format(int(time.time()*1000)) +i.oem.wc.grab_json_response('/api/providers/rp_screenshot') +url = '/download/HostScreenShot.png' +fd = webclient.FileDownloader(i.oem.wc, url, sys.argv[2]) +fd.start() +fd.join() +if sys.argv[3]: + imgdata = open(sys.argv[2], 'rb').read() + iterm_draw(imgdata) + + diff --git a/misc/makealtca.py b/misc/makealtca.py new file mode 100644 index 00000000..644cc9ad --- /dev/null +++ b/misc/makealtca.py @@ -0,0 +1,103 @@ +# This creates a locked down variant of a confluent CA + +# The confluent CA naturally doesn't have any name constraints, which +# is great for flexibility. + +# However, if actually being imported into a web browser, it is likely +# to want to limit the CA to apply to cluster resources rather +# than having a blank check to vouch for any and all subjects. + +# This particular approach causes a certificate authority that +# can be imported and vouch for the subset of certificates +# that were issued by the normal CA that match name constraints + +# Unfortunately, *ALL* names are checked, whether they are relevant +# to the conversation or not, so we reproduce the logic and the resultant +# CA is fragile to IP addresses. + +# The better approach would be to issue a separate, full name only certificate +# and have a simpler alt CA. The same CA can be used to sign both certificates, +# and still import the limited one + + +import subprocess +import socket +import confluent.certutil as certutil +import sys +def create_alt_ca(certout, permitdomains): + # This is to create a constrained variant of the existing authority + # this will allow a client browser to only trust it for select domains + # while the nodes can more broadly trust it (e.g. to vouch for IP addresses) + sslcfg = certutil.get_openssl_conf_location() + newcfg = '/etc/confluent/tls/ca-alt/openssl-alt.cfg' + subj = subprocess.check_output( + ['openssl', 'x509', '-subject', '-noout', '-in', '/etc/confluent/tls/ca/cacert.pem'] + ).decode().replace('subject=', '') + serial = subprocess.check_output( + ['openssl', 'x509', '-serial', '-noout', '-in', '/etc/confluent/tls/ca/cacert.pem'] + ).decode().replace('serial=', '') + with open('/etc/confluent/tls/ca-alt/serial', 'w') as srl: + srl.write(serial) + settings = { + 'dir': '/etc/confluent/tls/ca-alt', + 'certificate': '$dir/cacert.pem', + 'private_key': '$dir/private/cakey.pem', + 'countryName': 'optional', + 'stateOrProvinceName': 'optional', + 'organizationName': 'optional', + } + keyin = '/etc/confluent/tls/ca/private/cakey.pem' + csrin = '/etc/confluent/tls/ca/ca.csr' + + shortname = 'r3u20' + + + ipaddrs = list(certutil.get_ip_addresses()) + san = [] # 'IP:{0}'.format(x) for x in ipaddrs] + # It is incorrect to put IP addresses as DNS type. However + # there exists non-compliant clients that fail with them as IP + # san.extend(['DNS:{0}'.format(x) for x in ipaddrs]) + dnsnames = set(ipaddrs) + dnsnames.add(shortname) + for currip in ipaddrs: + dnsnames.add(socket.getnameinfo((currip, 0), 0)[0]) + for currname in dnsnames: + san.append('DNS:{0}'.format(currname)) + + + + if permitdomains[0] == '': + permitdomains = [] + nameconstraints = ['permitted;DNS:{}'.format(x) for x in permitdomains] + nameconstraints.extend(['permitted;{}'.format(x) for x in san]) + nameconstraints = ','.join(nameconstraints) + if nameconstraints: + nameconstraints = f'nameConstraints = critical,{nameconstraints}\n' + certutil.mkdirp('/etc/confluent/tls/ca-alt/newcerts') + with open('/etc/confluent/tls/ca-alt/index.txt', 'w') as idx: + pass + + with open(sslcfg, 'r') as cfgin: + with open(newcfg, 'w') as cfgfile: + for line in cfgin.readlines(): + cfg = line.split('#')[0] + if '=' in cfg: + key, val = cfg.split('=', 1) + for stg in settings: + if certutil.substitute_cfg(stg, key, val, settings[stg], cfgfile, line): + break + else: + cfgfile.write(line.strip() + '\n') + continue + cfgfile.write(line.strip() + '\n') + cfgfile.write(f'\n[CACert]\nbasicConstraints = CA:true\n{nameconstraints}[ca_confluent]\n') + subprocess.check_call( + ['openssl', 'ca', '-config', newcfg, '-batch', '-selfsign', + '-extensions', 'CACert', '-extfile', newcfg, + '-notext', '-startdate', + '19700101010101Z', '-enddate', '21000101010101Z', '-keyfile', + keyin, '-out', certout, '-in', csrin] + ) +if __name__ == '__main__': + create_alt_ca(sys.argv[1], sys.argv[2].split(',')) +