diff --git a/confluent_osdeploy/suse15/profiles/server/ansible/firstboot.d/README.txt b/confluent_osdeploy/suse15/profiles/server/ansible/firstboot.d/README.txt new file mode 100644 index 00000000..97e5f506 --- /dev/null +++ b/confluent_osdeploy/suse15/profiles/server/ansible/firstboot.d/README.txt @@ -0,0 +1,25 @@ +Ansible playbooks ending in .yml or .yaml that are placed into this directory will be executed at the +appropriate phase of the install process. + +The 'hosts' may be omitted, and if included will be ignored, replaced with the host that is specifically +requesting the playbooks be executed. + +Also, the playbooks will be executed on the deployment server. Hence it may be slower in aggregate than +running content under scripts/ which ask much less of the deployment server + +Here is an example of what a playbook would look like broadly: + +- name: Example + gather_facts: no + tasks: + - name: Example1 + lineinfile: + path: /etc/hosts + line: 1.2.3.4 test1 + create: yes + - name: Example2 + lineinfile: + path: /etc/hosts + line: 1.2.3.5 test2 + create: yes + diff --git a/confluent_osdeploy/suse15/profiles/server/ansible/post.d/README.txt b/confluent_osdeploy/suse15/profiles/server/ansible/post.d/README.txt new file mode 100644 index 00000000..97e5f506 --- /dev/null +++ b/confluent_osdeploy/suse15/profiles/server/ansible/post.d/README.txt @@ -0,0 +1,25 @@ +Ansible playbooks ending in .yml or .yaml that are placed into this directory will be executed at the +appropriate phase of the install process. + +The 'hosts' may be omitted, and if included will be ignored, replaced with the host that is specifically +requesting the playbooks be executed. + +Also, the playbooks will be executed on the deployment server. Hence it may be slower in aggregate than +running content under scripts/ which ask much less of the deployment server + +Here is an example of what a playbook would look like broadly: + +- name: Example + gather_facts: no + tasks: + - name: Example1 + lineinfile: + path: /etc/hosts + line: 1.2.3.4 test1 + create: yes + - name: Example2 + lineinfile: + path: /etc/hosts + line: 1.2.3.5 test2 + create: yes + diff --git a/confluent_osdeploy/suse15/profiles/server/autoyast b/confluent_osdeploy/suse15/profiles/server/autoyast new file mode 100644 index 00000000..59e4c7c9 --- /dev/null +++ b/confluent_osdeploy/suse15/profiles/server/autoyast @@ -0,0 +1,148 @@ + + + + + + UTC + + + + false + + false + + + %%IFSLE%% + + + sle-module-basesystem/Module-Basesystem + sle-module-server-applications/Module-Server-Applications + sle-module-containers/Module-Containers + SLES/Product-SLES + Legacy-Module/Module-Legacy + + + %%ENDIFSLE%% + + + %%IFSLE%% + + SLES + + %%ENDIFSLE%% + + base + + + openssh + iputils + python3 + openssl + chrony + rsync + screen + vim + binutils + pciutils + usbutils + nfs-client + ethtool + + + + + %%INSTDISK%% + true + all + + + xfs + / + max + + + swap + auto + + + /boot + 500M + + + + + + + root + %%ROOTPASSWORD%% + true + + + + + + %%NODENAME%% + + true + + + + + sshd + + + + + + + + + + + + + + + + + diff --git a/confluent_osdeploy/suse15/profiles/server/initprofile.sh b/confluent_osdeploy/suse15/profiles/server/initprofile.sh new file mode 100644 index 00000000..8f338896 --- /dev/null +++ b/confluent_osdeploy/suse15/profiles/server/initprofile.sh @@ -0,0 +1,23 @@ +#!/bin/sh +discnum=$(basename $1) +if [ "$discnum" != 1 ]; then exit 0; fi +if [ -e $2/boot/kernel ]; then exit 0; fi +profile=$(basename $2) + +if [[ $profile =~ ^sle.* ]]; then + if ls $1/Product-* >& /dev/null; then + ln -s $1 $2/product + else + ln -s ${1%1}2 $2/product + fi +fi +sed -i 's/sle 15/SUSE Linux Enterprise 15/; s/opensuse_leap/openSUSE Leap/' $2/profile.yaml +ln -s $1/boot/x86_64/loader/linux $2/boot/kernel && \ +ln -s $1/boot/x86_64/loader/initrd $2/boot/initramfs/distribution && \ +mkdir -p $2/boot/efi/boot && \ +ln -s $1/EFI/BOOT/bootx64.efi $1/EFI/BOOT/grub.efi $2/boot/efi/boot/ +if [[ $profile =~ ^sle.* ]]; then + sed -i 's/%%IFSLE%%//;s/%%ENDIFSLE%%//' $2/autoyast +else + sed -i '/%%IFSLE%%/,/%%ENDIFSLE%%/d' $2/autoyast +fi diff --git a/confluent_osdeploy/suse15/profiles/server/profile.yaml b/confluent_osdeploy/suse15/profiles/server/profile.yaml new file mode 100644 index 00000000..99e1a44d --- /dev/null +++ b/confluent_osdeploy/suse15/profiles/server/profile.yaml @@ -0,0 +1,3 @@ +label: %%DISTRO%% %%VERSION%% %%ARCH%% (Default Profile) +kernelargs: quiet # These arguments are passed to the installer +#installedargs: example # These arguments would be added to the installed system diff --git a/confluent_osdeploy/suse15/profiles/server/scripts/firstboot.custom b/confluent_osdeploy/suse15/profiles/server/scripts/firstboot.custom new file mode 100644 index 00000000..e2b7f793 --- /dev/null +++ b/confluent_osdeploy/suse15/profiles/server/scripts/firstboot.custom @@ -0,0 +1,9 @@ +#!/bin/sh + +# This script runs at the end of the final boot + +. /etc/confluent/functions + +# Custom scripts may go here +# run_remote example.sh +# run_remote_python example.py diff --git a/confluent_osdeploy/suse15/profiles/server/scripts/firstboot.sh b/confluent_osdeploy/suse15/profiles/server/scripts/firstboot.sh new file mode 100644 index 00000000..8cd7ac9b --- /dev/null +++ b/confluent_osdeploy/suse15/profiles/server/scripts/firstboot.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +# This script runs at the end of the final boot, updating status +exec >> /var/log/confluent/confluent-firstboot.log +exec 2>> /var/log/confluent/confluent-firstboot.log +chmod 600 /var/log/confluent/confluent-firstboot.log + +nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}') +v6cfg=$(grep ^ipv6_method: /etc/confluent/confluent.deploycfg) +v6cfg=${v6cfg#ipv6_method: } +if [ "$v6cfg" = "static" ]; then + confluent_mgr=$(grep ^deploy_server_v6: /etc/confluent/confluent.deploycfg) + confluent_mgr=${confluent_mgr#deploy_server_v6: } + confluent_mgr="[$confluent_mgr]" +else + confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg) + confluent_mgr=${confluent_mgr#deploy_server: } +fi +confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|sed -e 's/^profile: //') +proto=$(grep ^protocol: /etc/confluent/confluent.deploycfg |awk '{print $2}') +confluent_apikey=$(cat /etc/confluent/confluent.apikey) +. /etc/confluent/functions +while ! ping -c 1 $confluent_mgr >& /dev/null; do + sleep 1 +done + +for i in /etc/ssh/ssh_host*key.pub; do + certname=${i/.pub/-cert.pub} + curl -f -X POST -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" -d @$i https://$confluent_mgr/confluent-api/self/sshcert > $certname +done +systemctl restart sshd +run_remote_python confignet +run_remote firstboot.custom + +# Firstboot scripts may be placed into firstboot.d, e.g. firstboot.d/01-firstaction.sh, firstboot.d/02-secondaction.sh +run_remote_parts firstboot.d + +# Induce execution of remote configuration, e.g. ansible plays in ansible/firstboot.d/ +run_remote_config firstboot.d + +curl --capath /etc/confluent/tls -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" -f -X POST -d "status: complete" https://$confluent_mgr/confluent-api/self/updatestatus diff --git a/confluent_osdeploy/suse15/profiles/server/scripts/functions b/confluent_osdeploy/suse15/profiles/server/scripts/functions new file mode 100644 index 00000000..d70c63db --- /dev/null +++ b/confluent_osdeploy/suse15/profiles/server/scripts/functions @@ -0,0 +1,196 @@ +#!/bin/bash +function test_mgr() { + if curl -s https://${1}/confluent-api/ > /dev/null; then + return 0 + fi + return 1 +} + +function confluentpython() { + if [ -x /usr/libexec/platform-python ]; then + /usr/libexec/platform-python $* + elif [ -x /usr/bin/python3 ]; then + /usr/bin/python3 $* + elif [ -x /usr/bin/python ]; then + /usr/bin/python $* + elif [ -x /usr/bin/python2 ]; then + /usr/bin/python2 $* + fi +} + +function set_confluent_vars() { + if [ -z "$nodename" ]; then + nodename=$(grep ^NODENAME: /etc/confluent/confluent.info | awk '{print $2}') + fi + if [[ "$confluent_mgr" == *"%"* ]]; then + confluent_mgr="" + fi + if [ -z "$confluent_mgr" ]; then + confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //') + if ! test_mgr $confluent_mgr; then + confluent_mgr=$(grep ^deploy_server_v6: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //') + if [[ "$confluent_mgr" = *":"* ]]; then + confluent_mgr="[$confluent_mgr]" + fi + fi + if ! test_mgr $confluent_mgr; then + BESTMGRS=$(grep ^EXTMGRINFO: /etc/confluent/confluent.info | grep '|1$' | sed -e 's/EXTMGRINFO: //' -e 's/|.*//') + OKMGRS=$(grep ^EXTMGRINFO: /etc/confluent/confluent.info | grep '|0$' | sed -e 's/EXTMGRINFO: //' -e 's/|.*//') + for confluent_mgr in $BESTMGRS $OKMGRS; do + if [[ $confluent_mgr == *":"* ]]; then + confluent_mgr="[$confluent_mgr]" + fi + if test_mgr $confluent_mgr; then + break + fi + done + fi + fi + if [ -z "$confluent_profile" ]; then + confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //') + fi +} + +fetch_remote() { + curlargs="" + if [ -f /etc/confluent/ca.pem ]; then + curlargs=" --cacert /etc/confluent/ca.pem" + fi + set_confluent_vars + mkdir -p $(dirname $1) + curl -f -sS $curlargs https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/$1 > $1 + if [ $? != 0 ]; then echo $1 failed to download; return 1; fi +} + +source_remote_parts() { + confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX) + apiclient=/opt/confluent/bin/apiclient + if [ -f /etc/confluent/apiclient ]; then + apiclient=/etc/confluent/apiclient + fi + scriptlist=$(confluentpython $apiclient /confluent-api/self/scriptlist/$1|sed -e 's/^- //') + for script in $scriptlist; do + source_remote $1/$script + done + rm -rf $confluentscripttmpdir + unset confluentscripttmpdir +} + +run_remote_parts() { + confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX) + apiclient=/opt/confluent/bin/apiclient + if [ -f /etc/confluent/apiclient ]; then + apiclient=/etc/confluent/apiclient + fi + scriptlist=$(confluentpython $apiclient /confluent-api/self/scriptlist/$1|sed -e 's/^- //') + for script in $scriptlist; do + run_remote $1/$script + done + rm -rf $confluentscripttmpdir + unset confluentscripttmpdir +} + +source_remote() { + set_confluent_vars + unsettmpdir=0 + echo + echo '---------------------------------------------------------------------------' + echo Sourcing $1 from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/ + if [ -z "$confluentscripttmpdir" ]; then + confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX) + unsettmpdir=1 + fi + echo Sourcing from $confluentscripttmpdir + cd $confluentscripttmpdir + fetch_remote $1 + if [ $? != 0 ]; then echo $1 failed to download; return 1; fi + chmod +x $1 + cmd=$1 + shift + source ./$cmd + cd - > /dev/null + if [ "$unsettmpdir" = 1 ]; then + rm -rf $confluentscripttmpdir + unset confluentscripttmpdir + unsettmpdir=0 + fi + rm -rf $confluentscripttmpdir + return $retcode +} + +run_remote() { + requestedcmd="'$*'" + unsettmpdir=0 + set_confluent_vars + echo + echo '---------------------------------------------------------------------------' + echo Running $requestedcmd from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/ + if [ -z "$confluentscripttmpdir" ]; then + confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX) + unsettmpdir=1 + fi + echo Executing in $confluentscripttmpdir + cd $confluentscripttmpdir + fetch_remote $1 + if [ $? != 0 ]; then echo $requestedcmd failed to download; return 1; fi + chmod +x $1 + cmd=$1 + if [ -x /usr/bin/chcon ]; then + chcon system_u:object_r:bin_t:s0 $cmd + fi + shift + ./$cmd $* + retcode=$? + if [ $retcode -ne 0 ]; then + echo "$requestedcmd exited with code $retcode" + fi + cd - > /dev/null + if [ "$unsettmpdir" = 1 ]; then + rm -rf $confluentscripttmpdir + unset confluentscripttmpdir + unsettmpdir=0 + fi + return $retcode +} + +run_remote_python() { + echo + set_confluent_vars + if [ -f /etc/confluent/ca.pem ]; then + curlargs=" --cacert /etc/confluent/ca.pem" + fi + echo '---------------------------------------------------------------------------' + echo Running python script "'$*'" from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/ + confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX) + echo Executing in $confluentscripttmpdir + cd $confluentscripttmpdir + mkdir -p $(dirname $1) + curl -f -sS $curlargs https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/$1 > $1 + if [ $? != 0 ]; then echo "'$*'" failed to download; return 1; fi + confluentpython $* + retcode=$? + echo "'$*' exited with code $retcode" + cd - > /dev/null + rm -rf $confluentscripttmpdir + unset confluentscripttmpdir + return $retcode +} + +run_remote_config() { + echo + set_confluent_vars + apiclient=/opt/confluent/bin/apiclient + if [ -f /etc/confluent/apiclient ]; then + apiclient=/etc/confluent/apiclient + fi + echo '---------------------------------------------------------------------------' + echo Requesting to run remote configuration for "'$*'" from $confluent_mgr under profile $confluent_profile + confluentpython $apiclient /confluent-api/self/remoteconfig/"$*" -d {} + confluentpython $apiclient /confluent-api/self/remoteconfig/status -w 204 + echo + echo 'Completed remote configuration' + echo '---------------------------------------------------------------------------' + return +} +#If invoked as a command, use the arguments to actually run a function +(return 0 2>/dev/null) || $1 "${@:2}" diff --git a/confluent_osdeploy/suse15/profiles/server/scripts/getinstalldisk b/confluent_osdeploy/suse15/profiles/server/scripts/getinstalldisk new file mode 100644 index 00000000..4af31c0b --- /dev/null +++ b/confluent_osdeploy/suse15/profiles/server/scripts/getinstalldisk @@ -0,0 +1,88 @@ +import subprocess +import os + +class DiskInfo(object): + def __init__(self, devname): + self.name = devname + self.wwn = None + self.path = None + self.model = '' + self.size = 0 + self.driver = None + self.mdcontainer = '' + devnode = '/dev/{0}'.format(devname) + qprop = subprocess.check_output( + ['udevadm', 'info', '--query=property', devnode]) + if not isinstance(qprop, str): + qprop = qprop.decode('utf8') + for prop in qprop.split('\n'): + if '=' not in prop: + continue + k, v = prop.split('=', 1) + if k == 'DEVTYPE' and v != 'disk': + raise Exception('Not a disk') + elif k == 'DM_NAME': + raise Exception('Device Mapper') + elif k == 'ID_MODEL': + self.model = v + elif k == 'DEVPATH': + self.path = v + elif k == 'ID_WWN': + self.wwn = v + elif k == 'MD_CONTAINER': + self.mdcontainer = v + attrs = subprocess.check_output(['udevadm', 'info', '-a', devnode]) + if not isinstance(attrs, str): + attrs = attrs.decode('utf8') + for attr in attrs.split('\n'): + if '==' not in attr: + continue + k, v = attr.split('==', 1) + k = k.strip() + if k == 'ATTRS{size}': + self.size = v.replace('"', '') + 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: + raise Exception("No driver detected") + + @property + def priority(self): + if self.model.lower() in ('thinksystem_m.2_vd', 'thinksystem m.2', 'thinksystem_m.2'): + return 0 + if 'imsm' in self.mdcontainer: + return 1 + if self.driver == 'ahci': + return 2 + if self.driver.startswith('megaraid'): + return 3 + if self.driver.startswith('mpt'): + return 4 + return 99 + + def __repr__(self): + return repr({ + 'name': self.name, + 'path': self.path, + 'wwn': self.wwn, + 'driver': self.driver, + 'size': self.size, + 'model': self.model, + }) + + +def main(): + disks = [] + for disk in sorted(os.listdir('/sys/class/block')): + try: + disk = DiskInfo(disk) + disks.append(disk) + except Exception as e: + print("Skipping {0}: {1}".format(disk, str(e))) + nd = [x.name for x in sorted(disks, key=lambda x: x.priority)] + if nd: + open('/tmp/installdisk', 'w').write(nd[0]) + +if __name__ == '__main__': + main() diff --git a/confluent_osdeploy/suse15/profiles/server/scripts/post.custom b/confluent_osdeploy/suse15/profiles/server/scripts/post.custom new file mode 100644 index 00000000..a841271b --- /dev/null +++ b/confluent_osdeploy/suse15/profiles/server/scripts/post.custom @@ -0,0 +1,16 @@ +#!/bin/sh + +# This script runs at the end of install in the installed system +# but still under the installer kernel. + +# This is a good place to run most customizations that do not have any +# dependency upon the install target kernel being active. + +# If there are dependencies on the kernel (drivers or special filesystems) +# then firstboot.sh would be the script to customize. + +. /etc/confluent/functions + +# Examples: +# run_remote script.sh +# run_remote_python script.py diff --git a/confluent_osdeploy/suse15/profiles/server/scripts/post.sh b/confluent_osdeploy/suse15/profiles/server/scripts/post.sh new file mode 100644 index 00000000..be4e2d80 --- /dev/null +++ b/confluent_osdeploy/suse15/profiles/server/scripts/post.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +# This script runs at the end of install in the installed system +# but still under the installer kernel. + +# This is a good place to run most customizations that do not have any +# dependency upon the install target kernel being active. + +# If there are dependencies on the kernel (drivers or special filesystems) +# then firstboot.sh would be the script to customize. + +chmod 700 /var/log/confluent +exec >> /var/log/confluent/confluent-post.log +exec 2>> /var/log/confluent/confluent-post.log +chmod 600 /var/log/confluent/confluent-post.log +confluent_mgr=$(grep ^deploy_server /etc/confluent/confluent.deploycfg|awk '{print $2}') +confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|sed -e 's/^profile: //') +nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}') +confluent_apikey=$(cat /etc/confluent/confluent.apikey) + +chmod 700 /etc/confluent +chmod og-rwx /etc/confluent/* + + +export confluent_mgr confluent_profile nodename +. /etc/confluent/functions + +# This will induce server side processing of the syncfile contents if +# present +run_remote_python syncfileclient + +run_remote post.custom + +# Also, scripts may be placed into 'post.d', e.g. post.d/01-runfirst.sh, post.d/02-runsecond.sh +run_remote_parts post.d + +# Induce execution of remote configuration, e.g. ansible plays in ansible/post.d/ +run_remote_config post.d + +curl -X POST -d 'status: staged' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$confluent_mgr/confluent-api/self/updatestatus + diff --git a/confluent_osdeploy/suse15/profiles/server/scripts/pre.sh b/confluent_osdeploy/suse15/profiles/server/scripts/pre.sh new file mode 100644 index 00000000..54fea7c2 --- /dev/null +++ b/confluent_osdeploy/suse15/profiles/server/scripts/pre.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +# This script runs before the installer executes, and sets up ssh during install as well +# as rewriting the autoyast file with any substitutions prior to it being evaluated for real + +exec >> /tmp/confluent-pre.log +exec 2>> /tmp/confluent-pre.log +chmod 600 /tmp/confluent-pre.log +nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}') +rootpw=$(grep rootpassword: /etc/confluent/confluent.deploycfg|sed -e 's/^rootpassword: //') +if [ "$rootpw" = "null" ]; then + rootpw="!" +fi +cryptboot=$(grep encryptboot: /etc/confluent/confluent.deploycfg|sed -e 's/^encryptboot: //') +if [ "$cryptboot" != "" ] && [ "$cryptboot" != "none" ] && [ "$cryptboot" != "null" ]; then + echo "****Encrypted boot requested, but not implemented for this OS, halting install" > /dev/console + [ -f '/tmp/autoconsdev' ] && (echo "****Encryptod boot requested, but not implemented for this OS,halting install" >> $(cat /tmp/autoconsdev)) + while :; do sleep 86400; done +fi + +mkdir ~/.ssh +cat /ssh/*pubkey > ~/.ssh/authorized_keys 2>/dev/null + +ssh-keygen -A +for i in /etc/ssh/ssh_host*key.pub; do + certname=${i/.pub/-cert.pub} + curl -f -X POST -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" -d @$i https://$confluent_mgr/confluent-api/self/sshcert > $certname + echo HostKey ${i%.pub} >> /etc/ssh/sshd_config + echo HostCertificate $certname >> /etc/ssh/sshd_config +done +/usr/sbin/sshd +curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/functions > /tmp/functions +. /tmp/functions +ntpcfg="" +if grep ^ntpservers: /etc/confluent/confluent.deploycfg > /dev/null; then + echo '' > /tmp/ntp.cfg + sed -n '/^ntpservers:/,/^[^-]/p' /etc/confluent/confluent.deploycfg | sed 1d|sed '$d'| sed -e 's/^- /
/' -e 's!$!
!' >> /tmp/ntp.cfg + echo '
' >> /tmp/ntp.cfg + ntpcfg=$(paste -sd '' /tmp/ntp.cfg) +fi +mdadm --assemble --scan +run_remote_python getinstalldisk +if grep ^md /tmp/installdisk > /dev/null; then + for md in /dev/disk/*/*; do + rmd=$(readlink $md) + if echo $rmd|grep $(cat /tmp/installdisk)$ > /dev/null; then + echo ${md#/dev/} > /tmp/installdisk + fi + done +fi +sed -e s'!'%%INSTDISK%%'!'/dev/$(cat /tmp/installdisk)'!' -e s'!'%%NODENAME%%'!'$nodename'!' -e 's!!'"$ntpcfg"'!' -e "s?%%ROOTPASSWORD%%?${rootpw}?" /tmp/profile/autoinst.xml > /tmp/profile/modified.xml +if grep append /tmp/bootloader.xml > /dev/null; then + sed -i 's@@'"$(tr -d '\n' < /tmp/bootloader.xml)"'@' /tmp/profile/modified.xml +fi +sed -i 's#root#root'"$(tr -d '\n' < /tmp/rootkeys.xml)"'#' /tmp/profile/modified.xml +sed -i 's@/hwclock>@/hwclock>'"$(tr -d '\n' < /tmp/timezone)"'@' /tmp/profile/modified.xml +sed -i 's@@'"$(tr -d '\n' < /tmp/pkgurl)"'@' /tmp/profile/modified.xml diff --git a/confluent_osdeploy/suse15/profiles/server/scripts/prechroot.sh b/confluent_osdeploy/suse15/profiles/server/scripts/prechroot.sh new file mode 100644 index 00000000..527703e4 --- /dev/null +++ b/confluent_osdeploy/suse15/profiles/server/scripts/prechroot.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +# This script runs when install is finished, but while the installer +# is still running, with the to-be-booted system mounted in /mnt + +# carry over deployment configuration and api key for OS install action +confluent_mgr=$(grep ^deploy_server /etc/confluent/confluent.deploycfg|awk '{print $2}') +confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|sed -e 's/^profile: //') +nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}') +export confluent_mgr confluent_profile nodename +mkdir -p /mnt/etc/confluent +chmod 700 /mnt/etc/confluent +cp /tmp/functions /mnt/etc/confluent/ +. /tmp/functions +cp -a /etc/confluent/* /mnt/etc/confluent/ +cp -a /tls /mnt/etc/confluent/ +cp -a /tls/* /mnt/var/lib/ca-certificates/openssl +cp -a /tls/* /mnt/var/lib/ca-certificates/pem +cp -a /tls/*.pem /mnt/etc/pki/trust/anchors +cat /tls/*.pem > /mnt/etc/confluent/ca.pem +mkdir -p /mnt/opt/confluent/bin +cp /opt/confluent/bin/apiclient /mnt/opt/confluent/bin/ + +run_remote setupssh.sh + +echo Port 22 >> /etc/ssh/sshd_config +echo Port 2222 >> /etc/ssh/sshd_config +echo Match LocalPort 22 >> /etc/ssh/sshd_config +echo " ChrootDirectory /mnt" >> /etc/ssh/sshd_config +kill -HUP $(cat /run/sshd.pid) +mkdir -p /mnt/var/log/confluent +cp /tmp/confluent*log /mnt/var/log/confluent + diff --git a/confluent_osdeploy/suse15/profiles/server/scripts/setupssh.sh b/confluent_osdeploy/suse15/profiles/server/scripts/setupssh.sh new file mode 100644 index 00000000..181e225a --- /dev/null +++ b/confluent_osdeploy/suse15/profiles/server/scripts/setupssh.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# Carry over install-time ssh material into installed system +mkdir -p /mnt/root/.ssh/ +chmod 700 /mnt/root/.ssh/ +cp /root/.ssh/authorized_keys /mnt/root/.ssh/ +chmd 600 /mnt/root/.ssh/authorized_keys +cp /etc/ssh/*key* /mnt/etc/ssh/ +for i in /etc/ssh/*-cert.pub; do + echo HostCertificate $i >> /mnt/etc/ssh/sshd_config +done +for i in /ssh/*.ca; do + echo '@cert-authority *' $(cat $i) >> /mnt/etc/ssh/ssh_known_hosts +done +# Enable ~/.shosts, for the sake of root user, who is forbidden from using shosts.equiv +echo IgnoreRhosts no >> /mnt/etc/ssh/sshd_config +echo HostbasedAuthentication yes >> /mnt/etc/ssh/sshd_config +echo HostbasedUsesNameFromPacketOnly yes >> /mnt/etc/ssh/sshd_config +echo Host '*' >> /mnt/etc/ssh/ssh_config +echo " HostbasedAuthentication yes" >> /mnt/etc/ssh/ssh_config +echo " EnableSSHKeysign yes" >> /mnt/etc/ssh/ssh_config +# Limit the attempts of using host key. This prevents client from using 3 or 4 +# authentication attempts through host based attempts +echo " HostbasedKeyTypes *ed25519*" >> /mnt/etc/ssh/ssh_config + +# In SUSE platform, setuid for ssh-keysign is required for host based, +# and also must be opted into. +echo /usr/lib/ssh/ssh-keysign root:root 4711 >> /mnt/etc/permissions.local +chmod 4711 /mnt/usr/lib/ssh/ssh-keysign + +# Download list of nodes from confluent, and put it into shosts.equiv (for most users) and .shosts (for root) +curl -f -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" https://$confluent_mgr/confluent-api/self/nodelist > /tmp/allnodes +cp /tmp/allnodes /mnt/root/.shosts +cp /tmp/allnodes /mnt/etc/ssh/shosts.equiv + diff --git a/confluent_osdeploy/suse15/profiles/server/scripts/syncfileclient b/confluent_osdeploy/suse15/profiles/server/scripts/syncfileclient new file mode 100644 index 00000000..3fdd1f08 --- /dev/null +++ b/confluent_osdeploy/suse15/profiles/server/scripts/syncfileclient @@ -0,0 +1,272 @@ +#!/usr/bin/python +import importlib +import tempfile +import json +import os +import shutil +import pwd +import grp +from importlib.machinery import SourceFileLoader +try: + apiclient = SourceFileLoader('apiclient', '/opt/confluent/bin/apiclient').load_module() +except FileNotFoundError: + apiclient = SourceFileLoader('apiclient', '/etc/confluent/apiclient').load_module() + + +def partitionhostsline(line): + comment = '' + try: + cmdidx = line.index('#') + comment = line[cmdidx:] + line = line[:cmdidx].strip() + except ValueError: + pass + if not line: + return '', [], comment + ipaddr, names = line.split(maxsplit=1) + names = names.split() + return ipaddr, names, comment + +class HostMerger(object): + def __init__(self): + self.byip = {} + self.byname = {} + self.sourcelines = [] + self.targlines = [] + + def read_source(self, sourcefile): + with open(sourcefile, 'r') as hfile: + self.sourcelines = hfile.read().split('\n') + while not self.sourcelines[-1]: + self.sourcelines = self.sourcelines[:-1] + for x in range(len(self.sourcelines)): + line = self.sourcelines[x] + currip, names, comment = partitionhostsline(line) + if currip: + self.byip[currip] = x + for name in names: + self.byname[name] = x + + def read_target(self, targetfile): + with open(targetfile, 'r') as hfile: + lines = hfile.read().split('\n') + if not lines[-1]: + lines = lines[:-1] + for y in range(len(lines)): + line = lines[y] + currip, names, comment = partitionhostsline(line) + if currip in self.byip: + x = self.byip[currip] + if self.sourcelines[x] is None: + # have already consumed this enntry + continue + self.targlines.append(self.sourcelines[x]) + self.sourcelines[x] = None + continue + for name in names: + if name in self.byname: + x = self.byname[name] + if self.sourcelines[x] is None: + break + self.targlines.append(self.sourcelines[x]) + self.sourcelines[x] = None + break + else: + self.targlines.append(line) + + def write_out(self, targetfile): + while not self.targlines[-1]: + self.targlines = self.targlines[:-1] + if not self.targlines: + break + while not self.sourcelines[-1]: + self.sourcelines = self.sourcelines[:-1] + if not self.sourcelines: + break + with open(targetfile, 'w') as hosts: + for line in self.targlines: + hosts.write(line + '\n') + for line in self.sourcelines: + if line is not None: + hosts.write(line + '\n') + + +class CredMerger: + def __init__(self): + try: + with open('/etc/login.defs', 'r') as ldefs: + defs = ldefs.read().split('\n') + except FileNotFoundError: + defs = [] + lkup = {} + self.discardnames = {} + self.shadowednames = {} + for line in defs: + try: + line = line[:line.index('#')] + except ValueError: + pass + keyval = line.split() + if len(keyval) < 2: + continue + lkup[keyval[0]] = keyval[1] + self.uidmin = int(lkup.get('UID_MIN', 1000)) + self.uidmax = int(lkup.get('UID_MAX', 60000)) + self.gidmin = int(lkup.get('GID_MIN', 1000)) + self.gidmax = int(lkup.get('GID_MAX', 60000)) + self.shadowlines = None + + def read_passwd(self, source, targfile=False): + self.read_generic(source, self.uidmin, self.uidmax, targfile) + + def read_group(self, source, targfile=False): + self.read_generic(source, self.gidmin, self.gidmax, targfile) + + def read_generic(self, source, minid, maxid, targfile): + if targfile: + self.targdata = [] + else: + self.sourcedata = [] + with open(source, 'r') as inputfile: + for line in inputfile.read().split('\n'): + try: + name, _, uid, _ = line.split(':', 3) + uid = int(uid) + except ValueError: + continue + if targfile: + if uid < minid or uid > maxid: + self.targdata.append(line) + else: + self.discardnames[name] = 1 + else: + if name[0] in ('+', '#', '@'): + self.sourcedata.append(line) + elif uid >= minid and uid <= maxid: + self.sourcedata.append(line) + + def read_shadow(self, source): + self.shadowlines = [] + try: + with open(source, 'r') as inshadow: + for line in inshadow.read().split('\n'): + try: + name, _ = line.split(':' , 1) + except ValueError: + continue + if name in self.discardnames: + continue + self.shadowednames[name] = 1 + self.shadowlines.append(line) + except FileNotFoundError: + return + + def write_out(self, outfile): + with open(outfile, 'w') as targ: + for line in self.targdata: + targ.write(line + '\n') + for line in self.sourcedata: + targ.write(line + '\n') + if outfile == '/etc/passwd': + if self.shadowlines is None: + self.read_shadow('/etc/shadow') + with open('/etc/shadow', 'w') as shadout: + for line in self.shadowlines: + shadout.write(line + '\n') + for line in self.sourcedata: + name, _ = line.split(':', 1) + if name[0] in ('+', '#', '@'): + continue + if name in self.shadowednames: + continue + shadout.write(name + ':!:::::::\n') + if outfile == '/etc/group': + if self.shadowlines is None: + self.read_shadow('/etc/gshadow') + with open('/etc/gshadow', 'w') as shadout: + for line in self.shadowlines: + shadout.write(line + '\n') + for line in self.sourcedata: + name, _ = line.split(':' , 1) + if name in self.shadowednames: + continue + shadout.write(name + ':!::\n') + +def appendonce(basepath, filename): + with open(filename, 'rb') as filehdl: + thedata = filehdl.read() + targname = filename.replace(basepath, '') + try: + with open(targname, 'rb') as filehdl: + targdata = filehdl.read() + except IOError: + targdata = b'' + if thedata in targdata: + return + with open(targname, 'ab') as targhdl: + targhdl.write(thedata) + +def synchronize(): + tmpdir = tempfile.mkdtemp() + appendoncedir = tempfile.mkdtemp() + try: + ac = apiclient.HTTPSClient() + data = json.dumps({'merge': tmpdir, 'appendonce': appendoncedir}) + status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles', data) + if status == 202: + lastrsp = '' + while status != 204: + status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles') + if not isinstance(rsp, str): + rsp = rsp.decode('utf8') + if status == 200: + lastrsp = rsp + pendpasswd = os.path.join(tmpdir, 'etc/passwd') + if os.path.exists(pendpasswd): + cm = CredMerger() + cm.read_passwd(pendpasswd, targfile=False) + cm.read_passwd('/etc/passwd', targfile=True) + cm.write_out('/etc/passwd') + pendgroup = os.path.join(tmpdir, 'etc/group') + if os.path.exists(pendgroup): + cm = CredMerger() + cm.read_group(pendgroup, targfile=False) + cm.read_group('/etc/group', targfile=True) + cm.write_out('/etc/group') + pendhosts = os.path.join(tmpdir, 'etc/hosts') + if os.path.exists(pendhosts): + cm = HostMerger() + cm.read_source(pendhosts) + cm.read_target('/etc/hosts') + cm.write_out('/etc/hosts') + for dirn in os.walk(appendoncedir): + for filen in dirn[2]: + appendonce(appendoncedir, os.path.join(dirn[0], filen)) + if lastrsp: + lastrsp = json.loads(lastrsp) + opts = lastrsp.get('options', {}) + for fname in opts: + uid = -1 + gid = -1 + for opt in opts[fname]: + if opt == 'owner': + try: + uid = pwd.getpwnam(opts[fname][opt]['name']).pw_uid + except KeyError: + uid = opts[fname][opt]['id'] + elif opt == 'group': + try: + gid = grp.getgrnam(opts[fname][opt]['name']).gr_gid + except KeyError: + gid = opts[fname][opt]['id'] + elif opt == 'permissions': + os.chmod(fname, int(opts[fname][opt], 8)) + if uid != -1 or gid != -1: + os.chown(fname, uid, gid) + finally: + shutil.rmtree(tmpdir) + shutil.rmtree(appendoncedir) + + +if __name__ == '__main__': + synchronize()