diff --git a/README.md b/README.md index c4e6df4..68afc2c 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,55 @@ flags to build_image.py as long as the modules are compatible with each other. Stock modules: +### autoimage.py + +Build an image that will partition, mkfs, and install an image from a different +URL onto a target system. Used to create install USB drives, PXE boot, etc. +Use the build_image.py flag: + +```bash +--module="server/modules/autoimage.py --base-url=http://yourhost/ --ca-cert=/path/to/signing/cert.pem --device=/dev/sdx --persistent-percent=50" +``` + +`--device` specifies the device to partition and install to on the target +system. + +Optional flags: + +`--persistent-percent`, if non-zero, specifies the percent of the target +device to allocate to a LABEL=PERSISTENT filesystem. If the inner image uses +persistent.py, this filesystem will be automatically mounted. + +`--https-ca-cert` specifies a local path to a PEM-encoded certificate to +validate the HTTPS image server cert against. This differs from `--ca-cert`, +which is used to validate the manifest.json signature. + +`--https-client-cert` and `--https-client-key` are used together to specify +local paths to a PEM-encoded certificate and key pair that will be provided +to the server over HTTPS. This can be used to limit image availability. + +### certclient.py + +Use a local master key/cert pair to authenticate to a +[certserver](https://github.com/robot-tools/certserver) instance and retrieve +a system-specific key. Mainly intended to be used with autoimage.py and +systemid.py. + +Use the build_image.py flag: + +```bash +--module="server/modules/certclient.py --server=https://certserver/ --ca-cert=/path/to/server/cert.pem --client-cert=/path/to/client/cert.pem --client-key=/path/to/client/key.pem --tag=www --subject='/C=US/ST=California/O=XXXX/OU=XXXX Test/CN=SYSTEMID'" +``` + +The new key and cert are saved to /systemid + +`--tag` specifies a value added to the filename, so certclient.py can be +used more than once with different servers (e.g. once for an HTTPS client +key/cert pair, and once for an EAP-TLS key/cert pair). + +`--subject` specifics the subject string passed to openssl. `SYSTEMID` is +replaced with the system hostname, possibly as set by systemid.py + ### iconograph.py Install icon inside the image. This allows the image to auto-update over HTTP. @@ -95,6 +144,10 @@ Use the build_image.py flag: Optional flags: +`--https-ca-cert` specifies a local path to a PEM-encoded certificate to +validate the HTTPS image server cert against. This differs from `--ca-cert`, +which is used to validate the manifest.json signature. + `--max-images` sets the number of recent images to keep. Older images are deleted. Defaults to 5. 0 means unlimited. @@ -108,23 +161,24 @@ Use the build_image.py flag: --module="server/modules/persistent.py" ``` -### autoimage.py +See [imager/image.py](imager/image.py)'s or +[server/module/autoimage.py](autoimage.py)'s `--persistent-percent` flag to +create this partition. -Build an image that will partition, mkfs, and install an image from a different -URL onto a target system. Used to create install USB drives, PXE boot, etc. -Use the build_image.py flag: +### systemid.py + +Mount a /systemid partition from a filesystem with LABEL=SYSTEMID. This is +intended to a be separate device (possibly a USB flash drive, SD card, etc.) +which contains data that persists across re-images and identifies the system, +including system-specific keys and certificates. + +It also sets the hostname to the value found in the systemid config on the +device. ```bash ---module="server/modules/autoimage.py --base-url=http://yourhost/ --ca-cert=/path/to/signing/cert.pem --device=/dev/sdx --persistent-percent=50" +--module="server/modules/systemid.py" ``` -`--device` specifies the device to partition and install to on the target -system. - -`--persistent-percent`, if non-zero, specifies the percent of the target -device to allocate to a LABEL=PERSISTENT filesystem. If the inner image uses -persistent.py, this filesystem will be automatically mounted. - ## Module API Modules are passed the following long-style arguments: @@ -184,3 +238,13 @@ or manually write them to a drive. To do so: # Needs sudo to partition and mkfs devices sudo imager/image.py --base-url=http://yourhost/ --ca-cert=/path/to/signing/cert.pem --device=/dev/sdx --persistent-percent=50 ``` + +Optional flags: + +`--https-ca-cert` specifies a local path to a PEM-encoded certificate to +validate the HTTPS image server cert against. This differs from `--ca-cert`, +which is used to validate the manifest.json signature. + +`--https-client-cert` and `--https-client-key` are used together to specify +local paths to a PEM-encoded certificate and key pair that will be provided +to the server over HTTPS. This can be used to limit image availability. diff --git a/client/.gitignore b/client/.gitignore deleted file mode 100644 index 2cdfa72..0000000 --- a/client/.gitignore +++ /dev/null @@ -1 +0,0 @@ -flags diff --git a/client/fetch_and_update.sh b/client/fetch_and_update.sh index 0011327..ef7b7a7 100755 --- a/client/fetch_and_update.sh +++ b/client/fetch_and_update.sh @@ -1,15 +1,30 @@ -#!/bin/sh +#!/bin/bash set -ex BASE=$(dirname $0) -IMAGES=/isodevice/iconograph +IMAGES="/isodevice/iconograph" mkdir -p "${IMAGES}" -BOOT=/isodevice +BOOT="/isodevice" -FLAGS=$(cat ${BASE}/flags) +FETCHER_FLAGS="$(cat /icon/config/fetcher.flags)" +if -f /icon/config/update_grub.flags; then + UPDATE_GRUB_FLAGS="$(cat /icon/config/update_grub.flags)" +fi +CA_CERT="/icon/config/ca.image.cert.pem" -${BASE}/fetcher.py --image-dir="${IMAGES}" --ca-cert=${BASE}/../config/ca.cert.pem ${FLAGS} -${BASE}/update_grub.py --image-dir="${IMAGES}" --boot-dir="${BOOT}" > ${BOOT}/grub/grub.cfg.tmp && mv ${BOOT}/grub/grub.cfg.tmp ${BOOT}/grub/grub.cfg +HTTPS_CLIENT_KEY="/systemid/$(hostname).www.key.pem" +HTTPS_CLIENT_CERT="/systemid/$(hostname).www.cert.pem" +HTTPS_CA_CERT="/icon/config/ca.www.cert.pem" + +if test -e "${HTTPS_CLIENT_KEY}" -a -e "${HTTPS_CLIENT_CERT}"; then + HTTPS_CLIENT_FLAGS="--https-client-cert=${HTTPS_CLIENT_CERT} --https-client-key=${HTTPS_CLIENT_KEY}" +fi +if test -e "${HTTPS_CA_CERT}"; then + HTTPS_CA_FLAGS="--https-ca-cert=${HTTPS_CA_CERT}" +fi + +"${BASE}/fetcher.py" --image-dir="${IMAGES}" --ca-cert="${CA_CERT}" ${FETCHER_FLAGS} ${HTTPS_CLIENT_FLAGS} ${HTTPS_CA_FLAGS} +"${BASE}/update_grub.py" --image-dir="${IMAGES}" --boot-dir="${BOOT}" ${UPDATE_GRUB_FLAGS} > "${BOOT}/grub/grub.cfg.tmp" && mv "${BOOT}/grub/grub.cfg.tmp" "${BOOT}/grub/grub.cfg" diff --git a/server/build_image.py b/server/build_image.py index 1563a4f..a79db6c 100755 --- a/server/build_image.py +++ b/server/build_image.py @@ -25,6 +25,10 @@ parser.add_argument( dest='image_dir', action='store', required=True) +parser.add_argument( + '--kernel-arg', + dest='kernel_args', + action='append') parser.add_argument( '--module', dest='modules', @@ -35,8 +39,13 @@ parser.add_argument( action='store', required=True) parser.add_argument( - '--shell', - dest='shell', + '--post-module-shell', + dest='post_module_shell', + action='store_true', + default=False) +parser.add_argument( + '--pre-module-shell', + dest='pre_module_shell', action='store_true', default=False) parser.add_argument( @@ -55,7 +64,6 @@ class ImageBuilder(object): 'iputils-ping', 'linux-firmware', 'linux-firmware-nonfree', - 'openssh-server', 'ubuntu-minimal', 'ubuntu-standard', 'user-setup', @@ -83,13 +91,14 @@ class ImageBuilder(object): 'loopback.cfg': 'boot/grub/loopback.cfg', } - def __init__(self, source_iso, image_dir, archive, arch, release, modules): + def __init__(self, source_iso, image_dir, archive, arch, release, modules, kernel_args): self._source_iso = source_iso self._image_dir = image_dir self._archive = archive self._arch = arch self._release = release self._modules = modules or [] + self._kernel_args = kernel_args or [] self._ico_server_path = os.path.dirname(sys.argv[0]) @@ -100,6 +109,27 @@ class ImageBuilder(object): def _ExecChroot(self, chroot_path, *args, **kwargs): self._Exec('chroot', chroot_path, *args, **kwargs) + def _CreateRoot(self, timestamp): + root = tempfile.mkdtemp() + self._rmtree.append(root) + self._Exec( + 'mount', + '--types', 'tmpfs', + 'none', + root) + self._umount.append(root) + return root + + def _MountProc(self, chroot_path): + path = os.path.join(chroot_path, 'proc') + self._Exec( + 'mount', + '--types', 'proc', + 'none', + path) + self._umount.append(path) + return path + def _Debootstrap(self, root): path = os.path.join(root, 'chroot') os.mkdir(path) @@ -219,6 +249,12 @@ class ImageBuilder(object): source) os.unlink(os.path.join(chroot_path, 'usr', 'sbin', 'policy-rc.d')) + def _Unmount(self, path): + self._Exec( + 'umount', + path) + self._umount.remove(path) + def _Squash(self, chroot_path, union_path): self._Exec( 'mksquashfs', @@ -228,9 +264,11 @@ class ImageBuilder(object): def _CopyISOFiles(self, union_path): for source, dest in self._ISO_COPIES.items(): - shutil.copyfile( - os.path.join(self._ico_server_path, 'iso_files', source), - os.path.join(union_path, dest)) + source_path = os.path.join(self._ico_server_path, 'iso_files', source) + dest_path = os.path.join(union_path, dest) + with open(source_path, 'r') as source_fh, open(dest_path, 'w') as dest_fh: + for line in source_fh: + dest_fh.write(line.replace('$KERNEL_ARGS', ' '.join(self._kernel_args))) def _CreateISO(self, union_path, timestamp): dest_iso = os.path.join(self._image_dir, '%d.iso' % timestamp) @@ -241,31 +279,25 @@ class ImageBuilder(object): return dest_iso def _BuildImage(self): - root = tempfile.mkdtemp() - self._rmtree.append(root) - timestamp = int(time.time()) - + root = self._CreateRoot(timestamp) print('Building image in:', root) - self._Exec( - 'mount', - '--types', 'tmpfs', - 'none', - root) - self._umount.append(root) - chroot_path = self._Debootstrap(root) union_path = self._CreateUnion(root) + proc_path = self._MountProc(chroot_path) self._FixSourcesList(chroot_path) self._AddDiversions(chroot_path) self._InstallPackages(chroot_path) self._WriteVersion(chroot_path, timestamp) + if FLAGS.pre_module_shell: + self._Exec('bash', cwd=root) self._RunModules(chroot_path) self._CleanPackages(chroot_path) self._RemoveDiversions(chroot_path) - if FLAGS.shell: + if FLAGS.post_module_shell: self._Exec('bash', cwd=root) + self._Unmount(proc_path) self._Squash(chroot_path, union_path) self._CopyISOFiles(union_path) iso_path = self._CreateISO(union_path, timestamp) @@ -297,7 +329,8 @@ def main(): FLAGS.archive, FLAGS.arch, FLAGS.release, - FLAGS.modules) + FLAGS.modules, + FLAGS.kernel_args) builder.BuildImage() diff --git a/server/build_manifest.py b/server/build_manifest.py index e79330c..fef112d 100755 --- a/server/build_manifest.py +++ b/server/build_manifest.py @@ -43,14 +43,14 @@ class ManifestBuilder(object): self._image_dir = image_dir self._old_manifest = old_manifest - def _Rollouts(self): + def _OldImages(self): if not self._old_manifest: return {} try: with open(self._old_manifest, 'r') as fh: parsed = json.load(fh) return dict( - (image['timestamp'], image['rollout_‱']) + (image['timestamp'], image) for image in parsed['images']) except FileNotFoundError: return {} @@ -60,7 +60,7 @@ class ManifestBuilder(object): 'timestamp': int(time.time()), 'images': [], } - rollouts = self._Rollouts() + old_images = self._OldImages() for filename in os.listdir(self._image_dir): match = self._FILE_REGEX.match(filename) if not match: @@ -68,8 +68,15 @@ class ManifestBuilder(object): timestamp = int(match.group('timestamp')) image = { 'timestamp': timestamp, - 'rollout_‱': rollouts.get(timestamp, FLAGS.default_rollout), + 'rollout_‱': FLAGS.default_rollout, } + ret['images'].append(image) + + old_image = old_images.get(timestamp) + if old_image: + image.update(old_image) + continue + with open(os.path.join(self._image_dir, filename), 'rb') as fh: hash_obj = hashlib.sha256() while True: @@ -78,7 +85,7 @@ class ManifestBuilder(object): break hash_obj.update(data) image['hash'] = hash_obj.hexdigest() - ret['images'].append(image) + ret['images'].sort(key=lambda x: x['timestamp'], reverse=True) return ret diff --git a/server/iso_files/grub.cfg b/server/iso_files/grub.cfg index 95e87de..2cc51d4 100644 --- a/server/iso_files/grub.cfg +++ b/server/iso_files/grub.cfg @@ -2,6 +2,6 @@ set timeout=5 terminal_output console menuentry "Ubuntu Server Live Image" { - linux /casper/vmlinuz.efi boot=casper root=LABEL=ISOIMAGE + linux /casper/vmlinuz.efi boot=casper root=LABEL=ISOIMAGE nomodeset $KERNEL_ARGS initrd /casper/initrd.lz } diff --git a/server/iso_files/loopback.cfg b/server/iso_files/loopback.cfg index 03c6b46..ef37d44 100644 --- a/server/iso_files/loopback.cfg +++ b/server/iso_files/loopback.cfg @@ -2,6 +2,6 @@ set timeout=5 terminal_output console menuentry "Ubuntu Server Live Image" { - linux /casper/vmlinuz.efi boot=casper iso-scan/filename=${iso_path} + linux /casper/vmlinuz.efi boot=casper iso-scan/filename=${iso_path} nomodeset $KERNEL_ARGS initrd /casper/initrd.lz } diff --git a/server/modules/autoimage.py b/server/modules/autoimage.py index 16f3547..8d931a5 100755 --- a/server/modules/autoimage.py +++ b/server/modules/autoimage.py @@ -28,6 +28,18 @@ parser.add_argument( dest='device', action='store', required=True) +parser.add_argument( + '--https-ca-cert', + dest='https_ca_cert', + action='store') +parser.add_argument( + '--https-client-cert', + dest='https_client_cert', + action='store') +parser.add_argument( + '--https-client-key', + dest='https_client_key', + action='store') parser.add_argument( '--persistent-percent', dest='persistent_percent', @@ -51,18 +63,46 @@ def main(): 'apt-get', 'install', '--assume-yes', - 'git', 'grub-pc', 'python3-openssl') + 'git', 'grub-pc', 'python3-openssl', 'python3-requests') - ExecChroot( - 'git', - 'clone', - 'https://github.com/robot-tools/iconograph.git', - 'autoimage') + os.makedirs(os.path.join(FLAGS.chroot_path, 'icon', 'config'), exist_ok=True) + + if not os.path.exists(os.path.join(FLAGS.chroot_path, 'icon', 'iconograph')): + ExecChroot( + 'git', + 'clone', + 'https://github.com/robot-tools/iconograph.git', + 'icon/iconograph') - os.mkdir(os.path.join(FLAGS.chroot_path, 'autoimage', 'config')) shutil.copyfile( FLAGS.ca_cert, - os.path.join(FLAGS.chroot_path, 'autoimage', 'config', 'ca.cert.pem')) + os.path.join(FLAGS.chroot_path, 'icon', 'config', 'ca.image.cert.pem')) + + image_flags = [] + + if FLAGS.https_ca_cert: + https_ca_cert_path = os.path.join('icon', 'config', 'ca.www.cert.pem') + shutil.copyfile( + FLAGS.https_ca_cert, + os.path.join(FLAGS.chroot_path, https_ca_cert_path)) + image_flags.extend([ + '--https-ca-cert', os.path.join('/', https_ca_cert_path), + ]) + + if FLAGS.https_client_cert and FLAGS.https_client_key: + https_client_cert_path = os.path.join('icon', 'config', 'client.www.cert.pem') + shutil.copyfile( + FLAGS.https_client_cert, + os.path.join(FLAGS.chroot_path, https_client_cert_path)) + https_client_key_path = os.path.join('icon', 'config', 'client.www.key.pem') + shutil.copyfile( + FLAGS.https_client_key, + os.path.join(FLAGS.chroot_path, https_client_key_path)) + os.chmod(os.path.join(FLAGS.chroot_path, https_client_key_path), 0o400) + image_flags.extend([ + '--https-client-cert', os.path.join('/', https_client_cert_path), + '--https-client-key', os.path.join('/', https_client_key_path), + ]) parsed = parse.urlparse(FLAGS.base_url) @@ -71,23 +111,22 @@ def main(): fh.write(""" description "AutoImage" -start on stopped rc RUNLEVEL=[2345] - -stop on runlevel [!2345] +start on runlevel [2345] script - chvt 7 - /autoimage/client/wait_for_service.py --host=%(host)s --service=%(service)s /dev/tty7 2>&1 - chvt 7 - /autoimage/imager/image.py --device=%(device)s --persistent-percent=%(persistent_percent)d --ca-cert=/autoimage/config/ca.cert.pem --base-url=%(base_url)s /dev/tty7 2>&1 - chvt 7 + exec /dev/tty8 2>&1 + chvt 8 + /icon/iconograph/client/wait_for_service.py --host=%(host)s --service=%(service)s + chvt 8 + /icon/iconograph/imager/image.py --device=%(device)s --persistent-percent=%(persistent_percent)d --ca-cert=/icon/config/ca.image.cert.pem --base-url=%(base_url)s %(image_flags)s + chvt 8 - echo >/dev/tty7 - echo "==================" >/dev/tty7 - echo "autoimage complete" >/dev/tty7 - echo "==================" >/dev/tty7 + echo + echo "==================" + echo "autoimage complete" + echo "==================" - /autoimage/client/alert.py --type=happy /dev/tty7 + /icon/iconograph/client/alert.py --type=happy end script """ % { 'host': parsed.hostname, @@ -95,6 +134,7 @@ end script 'device': FLAGS.device, 'persistent_percent': FLAGS.persistent_percent, 'base_url': FLAGS.base_url, + 'image_flags': ' '.join(image_flags), }) diff --git a/server/modules/certclient.py b/server/modules/certclient.py new file mode 100755 index 0000000..7d8b4f1 --- /dev/null +++ b/server/modules/certclient.py @@ -0,0 +1,144 @@ +#!/usr/bin/python3 + +import argparse +import os +import shutil +import subprocess +from urllib import parse + + +parser = argparse.ArgumentParser(description='iconograph autoimage') +parser.add_argument( + '--chroot-path', + dest='chroot_path', + action='store', + required=True) +parser.add_argument( + '--ca-cert', + dest='ca_cert', + action='store', + required=True) +parser.add_argument( + '--client-cert', + dest='client_cert', + action='store', + required=True) +parser.add_argument( + '--client-key', + dest='client_key', + action='store', + required=True) +parser.add_argument( + '--subject', + dest='subject', + action='store', + required=True) +parser.add_argument( + '--tag', + dest='tag', + action='store', + required=True) +parser.add_argument( + '--server', + dest='server', + action='store', + required=True) +FLAGS = parser.parse_args() + + +def Exec(*args, **kwargs): + print('+', args) + subprocess.check_call(args, **kwargs) + + +def ExecChroot(*args, **kwargs): + Exec('chroot', FLAGS.chroot_path, *args, **kwargs) + + +def main(): + ExecChroot( + 'apt-get', + 'install', + '--assume-yes', + 'git', 'python3-requests', 'openssl') + + os.makedirs(os.path.join(FLAGS.chroot_path, 'icon', 'config'), exist_ok=True) + + if not os.path.exists(os.path.join(FLAGS.chroot_path, 'icon', 'iconograph')): + ExecChroot( + 'git', + 'clone', + 'https://github.com/robot-tools/iconograph.git', + 'icon/iconograph') + + if not os.path.exists(os.path.join(FLAGS.chroot_path, 'icon', 'certserver')): + ExecChroot( + 'git', + 'clone', + 'https://github.com/robot-tools/certserver.git', + 'icon/certserver') + + ca_cert_path = os.path.join('icon', 'config', 'ca.%s.certserver.cert.pem' % FLAGS.tag) + shutil.copyfile( + FLAGS.ca_cert, + os.path.join(FLAGS.chroot_path, ca_cert_path)) + + client_cert_path = os.path.join('icon', 'config', 'client.%s.certserver.cert.pem' % FLAGS.tag) + shutil.copyfile( + FLAGS.client_cert, + os.path.join(FLAGS.chroot_path, client_cert_path)) + client_key_path = os.path.join('icon', 'config', 'client.%s.certserver.key.pem' % FLAGS.tag) + shutil.copyfile( + FLAGS.client_key, + os.path.join(FLAGS.chroot_path, client_key_path)) + os.chmod(os.path.join(FLAGS.chroot_path, client_key_path), 0o400) + + parsed = parse.urlparse(FLAGS.server) + + init = os.path.join(FLAGS.chroot_path, 'etc', 'init', 'certclient.%s.conf' % FLAGS.tag) + with open(init, 'w') as fh: + fh.write(""" +description "CertClient %(tag)s" + +start on systemid-ready + +script + exec /dev/tty9 2>&1 + chvt 9 + + KEY="/systemid/$(hostname).%(tag)s.key.pem" + CERT="/systemid/$(hostname).%(tag)s.cert.pem" + SUBJECT="$(echo '%(subject)s' | sed s/SYSTEMID/$(hostname)/g)" + + if test ! -s "${KEY}"; then + openssl ecparam -name secp384r1 -genkey | openssl ec -out "${KEY}" + chmod 0400 "${KEY}" + fi + + chvt 9 + /icon/iconograph/client/wait_for_service.py --host=%(host)s --service=%(service)s + chvt 9 + + if test ! -s "${CERT}"; then + openssl req -new -key "${KEY}" -subj "${SUBJECT}" | /icon/certserver/certclient.py --ca-cert=/icon/config/ca.%(tag)s.certserver.cert.pem --client-cert=/icon/config/client.%(tag)s.certserver.cert.pem --client-key=/icon/config/client.%(tag)s.certserver.key.pem --server=%(server)s > "${CERT}" + chmod 0444 "${CERT}" + fi + + chvt 9 + + echo + echo "==================" + echo "certclient %(tag)s complete" + echo "==================" +end script +""" % { + 'host': parsed.hostname, + 'service': parsed.port or parsed.scheme, + 'subject': FLAGS.subject, + 'tag': FLAGS.tag, + 'server': FLAGS.server, + }) + + +if __name__ == '__main__': + main() diff --git a/server/modules/iconograph.py b/server/modules/iconograph.py index 345e5e8..2e50656 100755 --- a/server/modules/iconograph.py +++ b/server/modules/iconograph.py @@ -22,6 +22,10 @@ parser.add_argument( dest='chroot_path', action='store', required=True) +parser.add_argument( + '--https-ca-cert', + dest='https_ca_cert', + action='store') parser.add_argument( '--max-images', dest='max_images', @@ -45,19 +49,28 @@ def main(): 'apt-get', 'install', '--assume-yes', - 'daemontools-run', 'git', 'python3-openssl') + 'daemontools-run', 'git', 'python3-openssl', 'python3-requests') - ExecChroot( - 'git', - 'clone', - 'https://github.com/robot-tools/iconograph.git') + os.makedirs(os.path.join(FLAGS.chroot_path, 'icon', 'config'), exist_ok=True) + + if not os.path.exists(os.path.join(FLAGS.chroot_path, 'icon', 'iconograph')): + ExecChroot( + 'git', + 'clone', + 'https://github.com/robot-tools/iconograph.git', + 'icon/iconograph') - os.mkdir(os.path.join(FLAGS.chroot_path, 'iconograph', 'config')) shutil.copyfile( FLAGS.ca_cert, - os.path.join(FLAGS.chroot_path, 'iconograph', 'config', 'ca.cert.pem')) + os.path.join(FLAGS.chroot_path, 'icon', 'config', 'ca.image.cert.pem')) - path = os.path.join(FLAGS.chroot_path, 'iconograph', 'client', 'flags') + if FLAGS.https_ca_cert: + shutil.copyfile( + FLAGS.https_ca_cert, + os.path.join(FLAGS.chroot_path, 'icon', 'config', 'ca.www.cert.pem')) + + + path = os.path.join(FLAGS.chroot_path, 'icon', 'config', 'fetcher.flags') with open(path, 'w') as fh: fh.write('--base-url=%(base_url)s --max-images=%(max_images)d\n' % { 'base_url': FLAGS.base_url, @@ -65,8 +78,8 @@ def main(): }) os.symlink( - '/iconograph/client', - os.path.join(FLAGS.chroot_path, 'etc', 'service', 'iconograph')) + '/icon/iconograph/client', + os.path.join(FLAGS.chroot_path, 'etc', 'service', 'iconograph-client')) if __name__ == '__main__': diff --git a/server/modules/openssh.py b/server/modules/openssh.py new file mode 100755 index 0000000..2643cd9 --- /dev/null +++ b/server/modules/openssh.py @@ -0,0 +1,43 @@ +#!/usr/bin/python3 + +import argparse +import glob +import os +import subprocess + + +parser = argparse.ArgumentParser(description='iconograph openssh') +parser.add_argument( + '--chroot-path', + dest='chroot_path', + action='store', + required=True) +FLAGS = parser.parse_args() + + +def Exec(*args, **kwargs): + print('+', args) + subprocess.check_call(args, **kwargs) + + +def ExecChroot(*args, **kwargs): + Exec('chroot', FLAGS.chroot_path, *args, **kwargs) + + +def main(): + ExecChroot( + 'apt-get', + 'install', + '--assume-yes', + 'openssh-server') + + for path in glob.glob(os.path.join(FLAGS.chroot_path, 'etc', 'ssh', 'ssh_host_*')): + os.unlink(path) + + os.symlink( + '/systemid/ssh_host_ed25519_key', + os.path.join(FLAGS.chroot_path, 'etc', 'ssh', 'ssh_host_ed25519_key')) + + +if __name__ == '__main__': + main() diff --git a/server/modules/persistent.py b/server/modules/persistent.py index 1b30e91..1448507 100755 --- a/server/modules/persistent.py +++ b/server/modules/persistent.py @@ -22,9 +22,13 @@ def main(): description "Mount /persistent" start on filesystem +task + +emits persistent-ready script mount LABEL=PERSISTENT /persistent + initctl emit --no-wait persistent-ready end script """) diff --git a/server/modules/systemid.py b/server/modules/systemid.py index d496d3e..8159c54 100755 --- a/server/modules/systemid.py +++ b/server/modules/systemid.py @@ -22,6 +22,9 @@ def main(): description "Mount /systemid" start on filesystem +task + +emits systemid-ready script mount LABEL=SYSTEMID /systemid @@ -29,6 +32,7 @@ script echo ${SYSTEMID} > /etc/hostname hostname --file /etc/hostname grep ${SYSTEMID} /etc/hosts >/dev/null || echo "127.0.2.1 ${SYSTEMID}" >> /etc/hosts + initctl emit --no-wait systemid-ready end script """) diff --git a/systemid/image.py b/systemid/image.py index 231c854..3f048cf 100755 --- a/systemid/image.py +++ b/systemid/image.py @@ -121,6 +121,14 @@ SYSTEMID=%(system_id)s 'system_id': new_id, }) + def _GenerateSSHKey(self, root): + self._Exec( + 'ssh-keygen', + '-f', os.path.join(root, 'ssh_host_ed25519_key'), + '-N', '', + '-t', 'ed25519', + ) + def _Image(self): self._PartitionAndMkFS() root = self._Mount() @@ -136,6 +144,7 @@ New ID: \033[91m%s\033[00m ============== """ % new_id) + self._GenerateSSHKey(root) def Image(self): self._umount = [] diff --git a/util/fetch_archive.py b/util/fetch_archive.py new file mode 100755 index 0000000..78f9b96 --- /dev/null +++ b/util/fetch_archive.py @@ -0,0 +1,70 @@ +#!/usr/bin/python3 + +import argparse +import os +import requests +import shutil +import subprocess + + +parser = argparse.ArgumentParser(description='iconograph fetch_archive') +parser.add_argument( + '--dest-dir', + dest='dest_dir', + action='store', + default='.') +parser.add_argument( + '--https-ca-cert', + dest='https_ca_cert', + action='store') +parser.add_argument( + '--https-client-cert', + dest='https_client_cert', + action='store') +parser.add_argument( + '--https-client-key', + dest='https_client_key', + action='store') +parser.add_argument( + '--url', + dest='url', + action='store', + required=True) +FLAGS = parser.parse_args() + + +class ArchiveFetcher(object): + + _BUF_SIZE = 2 ** 16 + + def __init__(self, https_ca_cert, https_client_cert, https_client_key): + self._session = requests.Session() + if https_ca_cert: + self._session.verify = https_ca_cert + if https_client_cert and https_client_key: + self._session.cert = (https_client_cert, https_client_key) + + def Fetch(self, url, dest_dir='.'): + resp = self._session.get(url, stream=True) + + tar = subprocess.Popen( + ['tar', '--extract'], + stdin=subprocess.PIPE, + cwd=dest_dir) + for data in resp.iter_content(self._BUF_SIZE): + tar.stdin.write(data) + tar.wait() + + +def main(): + fetcher = ArchiveFetcher( + FLAGS.https_ca_cert, + FLAGS.https_client_cert, + FLAGS.https_client_key) + fetcher.Fetch( + FLAGS.url, + FLAGS.dest_dir) + + +if __name__ == '__main__': + main() diff --git a/www/code-39.woff b/www/code-39.woff new file mode 100644 index 0000000..a9daeb1 Binary files /dev/null and b/www/code-39.woff differ diff --git a/www/systemid.html b/www/systemid.html new file mode 100644 index 0000000..de0a4bc --- /dev/null +++ b/www/systemid.html @@ -0,0 +1,101 @@ + +
+