2020-05-12 13:48:27 +00:00
|
|
|
#!/usr/bin/python2
|
|
|
|
|
|
|
|
import argparse
|
2020-05-21 21:07:58 +00:00
|
|
|
import glob
|
2020-05-22 15:05:35 +00:00
|
|
|
import os
|
2020-05-12 19:02:18 +00:00
|
|
|
import os.path
|
2020-05-27 16:16:24 +00:00
|
|
|
import shutil
|
2020-05-12 13:48:27 +00:00
|
|
|
import sys
|
|
|
|
import time
|
|
|
|
|
2020-05-12 19:02:18 +00:00
|
|
|
path = os.path.dirname(os.path.realpath(__file__))
|
|
|
|
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
|
|
|
if path.startswith('/opt'):
|
|
|
|
sys.path.append(path)
|
2020-07-30 19:03:36 +00:00
|
|
|
import confluent.collective.manager as collective
|
|
|
|
import eventlet.green.subprocess as subprocess
|
|
|
|
import confluent.selfservice as selfservice
|
|
|
|
import confluent.util as util
|
2020-05-12 19:02:18 +00:00
|
|
|
import confluent.client as client
|
2020-05-21 21:07:58 +00:00
|
|
|
import confluent.sshutil as sshutil
|
|
|
|
import confluent.certutil as certutil
|
|
|
|
try:
|
|
|
|
input = raw_input
|
|
|
|
except NameError:
|
|
|
|
pass
|
2020-05-12 19:02:18 +00:00
|
|
|
|
2020-06-26 16:40:07 +00:00
|
|
|
def emprint(txt):
|
|
|
|
if sys.stdout.isatty():
|
|
|
|
print('\x1b[1m\x1b[4m' + txt + '\x1b[0m')
|
|
|
|
else:
|
|
|
|
print(txt)
|
|
|
|
|
2020-05-22 15:05:35 +00:00
|
|
|
fnamechars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.^'
|
2020-05-12 13:48:27 +00:00
|
|
|
def main(args):
|
|
|
|
ap = argparse.ArgumentParser(description='Manage OS deployment resources')
|
|
|
|
sp = ap.add_subparsers(dest='command')
|
2020-05-21 21:07:58 +00:00
|
|
|
wiz = sp.add_parser('initialize', help='Do OS deployment preparation')
|
|
|
|
wiz.add_argument('-u', help='Pull in root user key for node deployment', action='store_true')
|
|
|
|
wiz.add_argument('-s', help='Set up SSH CA for managing node to node ssh and known hosts', action='store_true')
|
2020-05-22 19:03:56 +00:00
|
|
|
wiz.add_argument('-k', help='Update local global known hosts file with confluent CA', action='store_true')
|
2020-05-21 21:07:58 +00:00
|
|
|
wiz.add_argument('-t', help='Generate new TLS key for HTTPS operation and register with confluent repository', action='store_true')
|
2020-05-22 19:03:56 +00:00
|
|
|
wiz.add_argument('-p', help='Copy in TFTP contents required for PXE support', action='store_true')
|
2020-05-21 21:07:58 +00:00
|
|
|
wiz.add_argument('-i', help='Interactively prompt for behaviors', action='store_true')
|
2020-07-30 19:03:36 +00:00
|
|
|
wiz.add_argument('-l', help='Set local management node to have SSH certificates and hosts.equiv/.shosts', action='store_true')
|
2020-05-12 13:53:46 +00:00
|
|
|
osip = sp.add_parser('import', help='Import an OS image from an ISO image')
|
2020-05-12 13:48:27 +00:00
|
|
|
osip.add_argument('imagefile', help='File to use for source of importing')
|
|
|
|
cmdset = ap.parse_args()
|
2020-05-12 13:53:46 +00:00
|
|
|
if cmdset.command == 'import':
|
2020-05-22 15:05:35 +00:00
|
|
|
return osimport(cmdset.imagefile)
|
2020-05-21 21:07:58 +00:00
|
|
|
if cmdset.command == 'initialize':
|
2020-05-22 15:05:35 +00:00
|
|
|
return initialize(cmdset)
|
|
|
|
ap.print_help()
|
2020-05-21 21:07:58 +00:00
|
|
|
|
2020-07-30 18:44:02 +00:00
|
|
|
|
|
|
|
def local_node_trust_setup():
|
|
|
|
allnodes, domain = selfservice.get_cluster_list()
|
|
|
|
myname = collective.get_myname()
|
|
|
|
myprincipals = set([myname])
|
2020-07-30 19:41:27 +00:00
|
|
|
restorecon = os.path.exists('/usr/sbin/restorecon')
|
2020-07-30 18:44:02 +00:00
|
|
|
neededlines = set([
|
|
|
|
'HostbasedAuthentication yes', 'HostbasedUsesNameFromPacketOnly yes',
|
|
|
|
'IgnoreRhosts no'])
|
|
|
|
if not myname.endswith(domain):
|
|
|
|
myprincipals.add('{0}.{1}'.format(myname, domain))
|
|
|
|
for pubkey in glob.glob('/etc/ssh/ssh_host_*key.pub'):
|
2020-07-30 19:03:36 +00:00
|
|
|
currpubkey = open(pubkey, 'rb').read()
|
|
|
|
cert = sshutil.sign_host_key(currpubkey, myname, myprincipals)
|
|
|
|
certfile = pubkey.replace('key.pub', 'key-cert.pub')
|
2020-07-30 18:44:02 +00:00
|
|
|
neededlines.add('HostCertificate {0}'.format(certfile))
|
|
|
|
if os.path.exists(certfile):
|
|
|
|
os.unlink(certfile)
|
2020-07-30 19:03:36 +00:00
|
|
|
with open(certfile, 'w') as certout:
|
2020-07-30 18:44:02 +00:00
|
|
|
certout.write(cert)
|
2020-07-30 19:41:27 +00:00
|
|
|
if restorecon:
|
|
|
|
subprocess.check_call(['/usr/sbin/restorecon', certfile])
|
2020-07-30 18:44:02 +00:00
|
|
|
with open('/etc/ssh/sshd_config', 'r') as sshconf:
|
2020-07-30 19:20:53 +00:00
|
|
|
currconfig = sshconf.read().split('\n')
|
2020-07-30 18:44:02 +00:00
|
|
|
for conline in currconfig:
|
|
|
|
conline = conline.strip()
|
|
|
|
neededlines.discard(conline)
|
|
|
|
if neededlines:
|
|
|
|
with open('/etc/ssh/sshd_config', 'a') as cfgout:
|
|
|
|
for currline in neededlines:
|
|
|
|
cfgout.write(currline)
|
|
|
|
cfgout.write('\n')
|
|
|
|
with open('/etc/ssh/shosts.equiv', 'w') as equivout:
|
|
|
|
for node in util.natural_sort(allnodes):
|
|
|
|
equivout.write(node + '\n')
|
|
|
|
with open('/root/.shosts', 'w') as equivout:
|
|
|
|
for node in util.natural_sort(allnodes):
|
|
|
|
equivout.write(node + '\n')
|
2020-07-30 19:41:27 +00:00
|
|
|
if restorecon:
|
|
|
|
subprocess.check_call(
|
|
|
|
['/usr/sbin/restorecon',
|
|
|
|
'/etc/ssh/shosts.equiv', '/root/.shosts'])
|
2020-07-30 18:44:02 +00:00
|
|
|
|
|
|
|
|
2020-05-22 19:03:56 +00:00
|
|
|
def install_tftp_content():
|
2020-05-27 21:05:04 +00:00
|
|
|
tftplocation = None
|
2020-07-15 14:59:30 +00:00
|
|
|
candidates = ('/tftpboot', '/var/lib/tftpboot', '/srv/tftpboot', '/srv/tftp')
|
2020-05-27 21:05:04 +00:00
|
|
|
for cand in candidates:
|
|
|
|
if os.path.isdir(cand):
|
|
|
|
tftplocation = cand
|
|
|
|
break
|
|
|
|
if not tftplocation:
|
2020-07-30 17:57:42 +00:00
|
|
|
raise Exception('Unable to detect an installed tftp location (check that tftp server is installed)')
|
|
|
|
if os.path.exists('/usr/lib/systemd/system/tftp.socket'):
|
|
|
|
subprocess.check_call(['systemctl', 'enable', 'tftp.socket', '--now'])
|
|
|
|
else:
|
|
|
|
print(
|
|
|
|
'Unable to automatically enable TFTP server, please enable manually')
|
2020-05-27 21:05:04 +00:00
|
|
|
tftplocation = '{0}/confluent/x86_64'.format(tftplocation)
|
|
|
|
try:
|
|
|
|
os.makedirs(tftplocation)
|
|
|
|
except OSError as e:
|
|
|
|
if e.errno == 17:
|
|
|
|
raise
|
2020-07-15 16:53:41 +00:00
|
|
|
shutil.copy('/opt/confluent/lib/ipxe/ipxe.efi', tftplocation)
|
|
|
|
shutil.copy('/opt/confluent/lib/ipxe/ipxe.kkpxe', tftplocation)
|
2020-05-27 21:05:04 +00:00
|
|
|
|
2020-05-22 19:03:56 +00:00
|
|
|
|
2020-05-21 21:07:58 +00:00
|
|
|
def initialize(cmdset):
|
|
|
|
if os.getuid() != 0:
|
|
|
|
sys.stderr.write('This command must run as root user\n')
|
|
|
|
sys.exit(1)
|
|
|
|
if cmdset.i:
|
|
|
|
didsomething = True
|
|
|
|
sys.stdout.write('Add root user key to be authorized to log into nodes (-u)? (y/n): ')
|
|
|
|
sys.stdout.flush()
|
|
|
|
cmdset.u = input().strip().lower().startswith('y')
|
|
|
|
sys.stdout.write('Set up an SSH authority to help manage known_hosts and node to node ssh for all users (-s)? (y/n): ')
|
|
|
|
cmdset.s = input().strip().lower().startswith('y')
|
2020-05-22 19:03:56 +00:00
|
|
|
sys.stdout.write('Update global known hosts on this server to trust local CA certificates (-k)? (y/n): ')
|
|
|
|
cmdset.k = input().strip().lower().startswith('y')
|
2020-08-03 13:34:18 +00:00
|
|
|
sys.stdout.write('Allow nodes to ssh to this management node without a password (-l)? (y/n): ')
|
|
|
|
cmdset.l = input().strip().lower().startswith('y')
|
2020-05-22 19:03:56 +00:00
|
|
|
sys.stdout.write('Update tftp directory with binaries to support PXE (-p) (y/n): ')
|
|
|
|
cmdset.p = input().strip().lower().startswith('y')
|
2020-05-21 21:07:58 +00:00
|
|
|
sys.stdout.write('Generate new TLS certificates for HTTP, replacing any existing certificate (-t)? (y/n): ')
|
|
|
|
cmdset.t = input().strip().lower().startswith('y')
|
|
|
|
if not cmdset.t:
|
|
|
|
print(
|
|
|
|
'In order to use your own certificate authority, make sure '
|
|
|
|
'to put the certificate authority into '
|
|
|
|
'/var/lib/confluent/public/site/tls/ directory as a .pem file '
|
|
|
|
'as well as named (hash).0 where (hash) is the hash of the '
|
|
|
|
'subject.')
|
|
|
|
else:
|
|
|
|
didsomething = False
|
|
|
|
if cmdset.u:
|
2020-06-26 12:11:30 +00:00
|
|
|
didsomething = True
|
2020-05-21 21:07:58 +00:00
|
|
|
if not glob.glob('/root/.ssh/*.pub'):
|
|
|
|
sys.stderr.write('No user keys for root detected, it is recommended '
|
|
|
|
'to run ssh-keygen -t ed25519 to generate a user '
|
|
|
|
'key. For optimal security, a passphrase should be '
|
|
|
|
'used. ssh-agent may be used to make use of a '
|
|
|
|
'passphrase protected ssh key easier.\n')
|
|
|
|
sys.exit(1)
|
|
|
|
sshutil.initialize_root_key(False)
|
|
|
|
if cmdset.t:
|
|
|
|
didsomething = True
|
|
|
|
certutil.create_certificate()
|
2020-06-26 16:49:28 +00:00
|
|
|
if os.path.exists('/usr/lib/systemd/system/httpd.service'):
|
2020-06-26 18:11:50 +00:00
|
|
|
subprocess.check_call(['systemctl', 'try-restart', 'httpd'])
|
2020-06-26 16:49:28 +00:00
|
|
|
print('HTTP server has been restarted if it was running')
|
|
|
|
elif os.path.exists('/usr/lib/systemd/system/apache2.service'):
|
2020-06-26 18:11:50 +00:00
|
|
|
subprocess.check_call(['systemctl', 'try-restart', 'apache2'])
|
2020-06-26 16:49:28 +00:00
|
|
|
print('HTTP server has been restarted if it was running')
|
|
|
|
else:
|
|
|
|
emprint('New HTTPS certificates generated, restart the web server manually')
|
2020-05-22 15:05:35 +00:00
|
|
|
if cmdset.s:
|
|
|
|
didsomething = True
|
|
|
|
sshutil.initialize_ca()
|
2020-07-15 15:12:27 +00:00
|
|
|
if cmdset.p:
|
|
|
|
didsomething = True
|
|
|
|
install_tftp_content()
|
2020-07-30 19:03:36 +00:00
|
|
|
if cmdset.l:
|
|
|
|
local_node_trust_setup()
|
2020-07-30 19:20:53 +00:00
|
|
|
if not didsomething:
|
|
|
|
sys.exit(0)
|
2020-05-21 21:07:58 +00:00
|
|
|
if not didsomething:
|
|
|
|
sys.stderr.write('Nothing was done, use initialize -i for '
|
|
|
|
'interactive mode, or see initialize -h for more options\n')
|
2020-05-22 15:05:35 +00:00
|
|
|
sys.exit(1)
|
|
|
|
tmpname = '/var/lib/confluent/public/site/initramfs.cpio.'
|
|
|
|
for x in bytearray(os.urandom(22)):
|
|
|
|
tmpname += fnamechars[x >> 2]
|
|
|
|
topack = []
|
2020-05-22 15:40:54 +00:00
|
|
|
opath = os.getcwd()
|
|
|
|
os.chdir('/var/lib/confluent/public/site')
|
2020-05-22 16:49:33 +00:00
|
|
|
topack.append('ssh/')
|
|
|
|
for currd, _, files in os.walk('ssh'):
|
2020-05-22 15:05:35 +00:00
|
|
|
for fname in files:
|
|
|
|
topack.append(os.path.join(currd, fname))
|
2020-05-22 16:49:33 +00:00
|
|
|
topack.append('tls/')
|
|
|
|
for currd, _, files in os.walk('tls'):
|
2020-05-22 15:05:35 +00:00
|
|
|
for fname in files:
|
|
|
|
topack.append(os.path.join(currd, fname))
|
|
|
|
with open(tmpname, 'wb') as initramfs:
|
|
|
|
packit = subprocess.Popen(['cpio', '-H', 'newc', '-o'],
|
|
|
|
stdout=initramfs, stdin=subprocess.PIPE)
|
|
|
|
for packfile in topack:
|
2020-05-22 15:40:54 +00:00
|
|
|
if not isinstance(packfile, bytes):
|
|
|
|
packfile = packfile.encode('utf8')
|
2020-05-22 15:05:35 +00:00
|
|
|
packit.stdin.write(packfile)
|
2020-05-22 15:40:54 +00:00
|
|
|
packit.stdin.write(b'\n')
|
2020-05-22 15:05:35 +00:00
|
|
|
packit.stdin.close()
|
|
|
|
res = packit.wait()
|
|
|
|
if res:
|
|
|
|
sys.stderr.write('Error occurred while packing site initramfs')
|
|
|
|
sys.exit(1)
|
2020-06-10 13:42:44 +00:00
|
|
|
os.rename(tmpname, '/var/lib/confluent/public/site/initramfs.cpio')
|
2020-06-10 14:17:54 +00:00
|
|
|
tmptarname = tmpname.replace('cpio', 'tgz')
|
2020-06-10 16:17:51 +00:00
|
|
|
tarcmd = ['tar', '-czf', tmptarname, 'ssh', 'tls']
|
2020-06-08 18:03:58 +00:00
|
|
|
subprocess.check_call(tarcmd)
|
2020-05-22 15:40:54 +00:00
|
|
|
os.chdir(opath)
|
2020-06-10 14:17:54 +00:00
|
|
|
os.rename(tmptarname, '/var/lib/confluent/public/site/initramfs.tgz')
|
2020-06-26 12:19:22 +00:00
|
|
|
print('Site initramfs content packed successfully')
|
2020-05-22 19:03:56 +00:00
|
|
|
if cmdset.k:
|
|
|
|
with open('/etc/ssh/ssh_known_hosts', 'a+b') as skh:
|
|
|
|
for cafile in glob.glob('/var/lib/confluent/public/site/ssh/*.ca'):
|
|
|
|
cacert = open(cafile, 'rb').read()
|
|
|
|
cacert = b'@cert-authority * ' + cacert
|
|
|
|
skh.write(cacert)
|
|
|
|
# ok, also need to think on how to handle getinstalldisk
|
|
|
|
if not os.path.exists('/etc/confluent/srvcert.pem'):
|
|
|
|
subprocess.check_call(['collective', 'gencert'])
|
2020-06-08 18:03:58 +00:00
|
|
|
# TODO: check selinux and segetbool for httpd_can_network_connect
|
2020-05-27 16:16:24 +00:00
|
|
|
# tftp-server available and enabled?
|
|
|
|
# httpd available and enabled?
|
2020-05-22 19:27:25 +00:00
|
|
|
|
2020-05-12 13:48:27 +00:00
|
|
|
|
|
|
|
def osimport(imagefile):
|
|
|
|
c = client.Command()
|
2020-05-12 19:02:18 +00:00
|
|
|
imagefile = os.path.abspath(imagefile)
|
2020-05-12 13:48:27 +00:00
|
|
|
importing = False
|
|
|
|
shortname = None
|
|
|
|
for rsp in c.create('/deployment/importing/', {'filename': imagefile}):
|
|
|
|
if 'target' in rsp:
|
|
|
|
importing = True
|
2020-05-12 18:47:00 +00:00
|
|
|
shortname = rsp['name']
|
2020-05-12 13:48:27 +00:00
|
|
|
print('Importing from {0} to {1}'.format(imagefile, rsp['target']))
|
|
|
|
else:
|
|
|
|
print(repr(rsp))
|
|
|
|
while importing:
|
|
|
|
for rsp in c.read('/deployment/importing/{0}'.format(shortname)):
|
|
|
|
if 'progress' in rsp:
|
|
|
|
sys.stdout.write('{0}: {1:.2f}% \r'.format(rsp['phase'],
|
|
|
|
rsp['progress']))
|
|
|
|
if rsp['phase'] == 'complete':
|
|
|
|
importing = False
|
|
|
|
sys.stdout.write('\n')
|
|
|
|
for profile in rsp['profiles']:
|
|
|
|
print('Deployment profile created: {0}'.format(profile))
|
|
|
|
sys.stdout.flush()
|
|
|
|
else:
|
|
|
|
print(repr(rsp))
|
|
|
|
time.sleep(0.5)
|
2020-06-18 15:11:53 +00:00
|
|
|
if shortname:
|
|
|
|
list(c.delete('/deployment/importing/{0}'.format(shortname)))
|
2020-05-12 13:48:27 +00:00
|
|
|
|
|
|
|
if __name__ == '__main__':
|
2020-07-15 14:59:30 +00:00
|
|
|
main(sys.argv)
|