diff --git a/RaspberryPi/LICENSE b/RaspberryPi/LICENSE new file mode 100644 index 000000000..1fb9f59df --- /dev/null +++ b/RaspberryPi/LICENSE @@ -0,0 +1,27 @@ +Copyright © 2017, Michael Stapelberg and contributors +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 Michael Stapelberg nor the + names of contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY Michael Stapelberg ''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 Michael Stapelberg 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. diff --git a/RaspberryPi/Makefile b/RaspberryPi/Makefile new file mode 100644 index 000000000..be26056d4 --- /dev/null +++ b/RaspberryPi/Makefile @@ -0,0 +1,75 @@ +all: shasums + +# List all the supported and built Pi platforms here. They get expanded +# to names like 'raspi_2_buster.yaml' and 'raspi_3_bullseye.img.xz'. +BUILD_FAMILIES := 1 2 3 4 +BUILD_RELEASES := bullseye bookworm trixie + +platforms := $(foreach plat, $(BUILD_FAMILIES),$(foreach rel, $(BUILD_RELEASES), raspi_$(plat)_$(rel))) + +shasums: $(addsuffix .img.sha256,$(platforms)) $(addsuffix .img.xz.sha256,$(platforms)) +xzimages: $(addsuffix .img.xz,$(platforms)) +images: $(addsuffix .img,$(platforms)) +yaml: $(addsuffix .yaml,$(platforms)) + +ifeq ($(shell id -u),0) +as_root = +else ifneq (,$(wildcard /usr/bin/fakemachine)) +$(warning "This should normally be run as root, but found 'fakemachine', so using that.") +as_root = fakemachine -v $(CURDIR) -- env --chdir $(CURDIR) +else +$(error "This must be run as root") +endif + +target_platforms: + @echo $(platforms) + +# Generate targets based on all family * release combinations: +define dynamic_yaml_target = + raspi_$(1)_$(2).yaml: raspi_master.yaml generate-recipe.py + raspi_$(1)_$(2).yaml: + ./generate-recipe.py $(1) $(2) +endef +$(foreach release,$(BUILD_RELEASES), \ + $(foreach family,$(BUILD_FAMILIES), \ + $(eval $(call dynamic_yaml_target,$(family),$(release))))) + +%.img.sha256: %.img + echo $@ + sha256sum $< > $@ + +%.img.xz.sha256: %.img.xz + echo $@ + sha256sum $< > $@ + +%.img.xz: %.img + xz -f -k -z -9 $< + +%.img.bmap: %.img + bmaptool create -o $@ $< + +%.img: %.yaml + touch $(@:.img=.log) + time nice $(as_root) vmdb2 --verbose --rootfs-tarball=$(subst .img,.tar.gz,$@) --output=$@ $(subst .img,.yaml,$@) --log $(subst .img,.log,$@) + chmod 0644 $@ $(@,.img=.log) + +_ck_root: + [ `whoami` = 'root' ] # Only root can summon vmdb2 ☹ + +_clean_yaml: + rm -f $(addsuffix .yaml,$(platforms)) raspi_base_bullseye.yaml raspi_base_bookworm.yaml raspi_base_trixie.yaml +_clean_images: + rm -f $(addsuffix .img,$(platforms)) +_clean_xzimages: + rm -f $(addsuffix .img.xz,$(platforms)) +_clean_bmaps: + rm -f $(addsuffix .img.bmap,$(platforms)) +_clean_shasums: + rm -f $(addsuffix .img.sha256,$(platforms)) $(addsuffix .img.xz.sha256,$(platforms)) +_clean_logs: + rm -f $(addsuffix .log,$(platforms)) +_clean_tarballs: + rm -f $(addsuffix .tar.gz,$(platforms)) +clean: _clean_xzimages _clean_images _clean_shasums _clean_yaml _clean_tarballs _clean_logs _clean_bmaps + +.PHONY: _ck_root _build_img clean _clean_images _clean_yaml _clean_tarballs _clean_logs diff --git a/RaspberryPi/README.md b/RaspberryPi/README.md new file mode 100644 index 000000000..e2a1d35f6 --- /dev/null +++ b/RaspberryPi/README.md @@ -0,0 +1,118 @@ +# Raspberry Pi Hedgehog Sensor Build + +This build process builds upon the Debian RPi build process located here: +http://salsa.debian.org/raspi-team/image-specs + +## Building an image + +Steps to build a Hedgehog Raspberry Pi image. +If you are reading this document online, you should first +clone this repository: + +```shell +git clone https://github.com/cisagov/Malcolm.git +cd Malcolm/RaspberryPi +``` + +For this you will first need to install the following packages on a +Debian Bullseye (11), Devuan Daedalus (5), or higher system: + +* binfmt-support +* bmap-tools +* debootstrap +* dosfstools +* fakemachine (optional, only available on amd64) +* kpartx +* qemu-utils +* qemu-user-static +* time +* vmdb2 (>= 0.17) +* python3 + +To install these (as root): +```shell + apt install -y vmdb2 dosfstools qemu-utils qemu-user-static debootstrap binfmt-support time kpartx bmap-tools python3 + apt install -y fakemachine +``` + +If debootstrap still fails with exec format error, try +running `dpkg-reconfigure qemu-user-static`. This calls +`/var/lib/dpkg/info/qemu-user-static.postinst` which uses binfmt-support +to register the executable format with /usr/bin/qemu-$fmt-static + +This repository includes a master YAML recipe (which is basically a +configuration file) for all of the generated images, diverting as +little as possible in a parametrized way. The master recipe is +[raspi_master.yaml](raspi_master.yaml). + +A Makefile is supplied to drive the build of the recipes into images. +If `fakemachine` is installed, it can be run as an unprivileged user. +Otherwise, because some steps of building the image require root privileges, +you'll need to execute `make` as root. + +The argument to `make` is constructed as follows: +`raspi__.` + +Whereby is one of `1`, `2`, `3` or `4`, is either +`bullseye`, `bookworm`, or `trixie`; and is `img` or `yaml`. + +Model `1` should be used for the Raspberry Pi 0, 0w and 1, models A and +B. Model `2` for the Raspberry Pi 2 models A and B. Model `3` for all +models of the Raspberry Pi 3 and model `4` for all models of the +Raspberry Pi 4. +So if you want to build the default image for a Raspberry Pi 3B+ with +Bullseye, you can just issue: + +```shell + make raspi_4_bookworm.img +``` + +**NOTE:** While this setup will build hedgehog for all raspberry pi variants, it is highly unlikely +that any variant other than RPI 4 (8GB version) will have adequate resources to function effectively as a sensor. + +At this point; it might be wise to go do something else. The build **WILL** take a while. +Initial testing on a 8-core 16GB build machine took approximately 5.5 hours to complete the image. + +## Installing the image onto the Raspberry Pi + +If the build completes properly, it can be tested locally before writing to an SD card if desired. +To do so, simply run (as root): + +```shell + mount -o loop,offset=$((1048576*512)) raspi_4_bookworm.img /mnt && chroot /mnt +``` + +If an error is returned by the mount command, there is a chance the image was corrupted during the build. +It is, unfortunately, advised to run `make clean` and rebuild the image. + +Plug an SD card which you would like to **entirely OVERWRITE** into the Build machine's SD card reader. + +Assuming your SD card reader provides the device `/dev/mmcblk0` +(**Beware** If you choose the wrong device, you might overwrite +important parts of your system. Double check it's the correct +device!), copy the image onto the SD card: + +```shell +bmaptool copy raspi_3_bullseye.img.xz /dev/mmcblk0 +``` + +Alternatively, if you don't have `bmap-tools` installed, you can use +`dd` with the compressed image: + +```shell +xzcat raspi_3_bullseye.img | dd of=/dev/mmcblk0 bs=64k oflag=dsync status=progress +``` + +Or with the uncompressed image: + +```shell +dd if=raspi_3_bullseye.img of=/dev/mmcblk0 bs=64k oflag=dsync status=progress +``` + +Then, plug the SD card into the Raspberry Pi, and power it up. + +The image uses the hostname `Hedgehog-rpi-0w`, `Hedgehog-rpi-2`, `Hedgehog-rpi-3`, or `Hedgehog-rpi-4` depending on the +target build. The provided image will allow you to log in with the +`sensor` account with a default password of `Hedgehog_Linux` or +`root` account with a default password of `Hedgehog_Linux_Root`, but only logging in at the +physical console (be it serial or by USB keyboard and HDMI monitor). diff --git a/RaspberryPi/debian/salsa-ci.yml b/RaspberryPi/debian/salsa-ci.yml new file mode 100644 index 000000000..a06e92265 --- /dev/null +++ b/RaspberryPi/debian/salsa-ci.yml @@ -0,0 +1,43 @@ +--- +stages: + # Garbage in is garbage out, so verify our input + - check input + - build + +variables: + DEBIAN_FRONTEND: "noninteractive" + # At https://salsa.debian.org/salsa-ci-team/pipeline/container_registry one can see which images are available + SALSA_CI_IMAGES: registry.salsa.debian.org/salsa-ci-team/pipeline + BASE_CI_IMAGES: ${SALSA_CI_IMAGES}/base + +yamllint: + stage: check input + image: $BASE_CI_IMAGES:unstable + dependencies: [] + script: + - apt-get update && apt-get upgrade -y + - apt-get install -y yamllint + - yamllint -c debian/yamllint.yml . + +shellcheck: + stage: check input + image: $BASE_CI_IMAGES:unstable + dependencies: [] + script: + - apt-get update && apt-get upgrade -y + - apt-get install -y shellcheck + - shellcheck -e SC1090,SC1091 -s dash $(find rootfs/etc/initramfs-tools -type f -executable | xargs grep -l '^#!/bin/sh') + +build yamls: + stage: build + image: $BASE_CI_IMAGES:unstable + dependencies: [] + script: + - apt-get update && apt-get upgrade -y + - apt-get install -y python3 make git + - make yaml + - mkdir build + - cp raspi*.yaml build/ + artifacts: + paths: + - build/ diff --git a/RaspberryPi/debian/yamllint.yml b/RaspberryPi/debian/yamllint.yml new file mode 100644 index 000000000..53974a025 --- /dev/null +++ b/RaspberryPi/debian/yamllint.yml @@ -0,0 +1,6 @@ +--- + +extends: default + +rules: + line-length: disable diff --git a/RaspberryPi/generate-recipe.py b/RaspberryPi/generate-recipe.py new file mode 100755 index 000000000..fe3433b52 --- /dev/null +++ b/RaspberryPi/generate-recipe.py @@ -0,0 +1,185 @@ +#!/usr/bin/python3 + +import re +import sys +import subprocess +import os + +SCRIPT_DIR = os.getcwd() +MALCOLM_DIR = os.path.split(SCRIPT_DIR)[-2] +SENSOR_DIR = MALCOLM_DIR + "/sensor-iso" + +# pylint: disable=invalid-name + +### Sanity/usage checks + +if len(sys.argv) != 3: + print("E: need 2 arguments", file=sys.stderr) + sys.exit(1) + +version = sys.argv[1] +if version not in ["1", "2", "3", "4"]: + print("E: unsupported version %s" % version, file=sys.stderr) + sys.exit(1) + +suite = sys.argv[2] +if suite not in ['bullseye', 'bookworm', 'trixie']: + print("E: unsupported suite %s" % suite, file=sys.stderr) + sys.exit(1) +target_yaml = 'raspi_%s_%s.yaml' % (version, suite) + + +### Setting variables based on suite and version starts here + +# Arch, kernel, DTB: +if version == '1': + arch = 'armel' + linux = 'linux-image-rpi' + dtb = '/usr/lib/linux-image-*-rpi/bcm*rpi-*.dtb' +elif version == '2': + arch = 'armhf' + linux = 'linux-image-armmp' + dtb = '/usr/lib/linux-image-*-armmp/bcm*rpi*.dtb' +elif version in ['3', '4']: + arch = 'arm64' + linux = 'linux-image-arm64' + dtb = '/usr/lib/linux-image-*-arm64/broadcom/bcm*rpi*.dtb' + +# Bookworm introduced the 'non-free-firmware' component¹; before that, +# raspi-firmware was in 'non-free' +# +# ¹ https://www.debian.org/vote/2022/vote_003 +if suite != 'bullseye': + firmware_component = 'non-free-firmware' + firmware_component_old = 'non-free' +else: + firmware_component = 'non-free' + firmware_component_old = '' + +# wireless firmware: +if version != '2': + wireless_firmware = 'firmware-brcm80211' +else: + wireless_firmware = '' + +# bluetooth firmware: +if version != '2': + bluetooth_firmware = 'bluez-firmware' +else: + bluetooth_firmware = '' + +# Pi 4 on buster required some backports. Let's keep variables around, ready to +# be used whenever we need to pull specific things from backports. +backports_enable = False +backports_suite = '%s-backports' % suite + +# Serial console: +if version in ['1', '2']: + serial = 'ttyAMA0,115200' +elif version in ['3', '4']: + serial = 'ttyS1,115200' + +# CMA fixup: +extra_chroot_shell_cmds = [] +if version == '4': + extra_chroot_shell_cmds = [ + "sed -i 's/cma=64M //' /boot/firmware/cmdline.txt", + ] + +# Hostname: +hostname = 'Hedgehog-rpi-%s' % version + +# Nothing yet! +extra_root_shell_cmds = [ +'cp sensor_install.sh "${ROOT?}/root/"', +'/bin/bash -c \'mkdir -p "${ROOT?}/opt/"{deps,hooks,patches,sensor,arkime/etc,zeek/bin}\'', +'cp "%s/arkime/arkime_patch/"* "${ROOT?}/opt/patches/" || true' % SENSOR_DIR, +'cp "%s/arkime/arkime_etc/"* "${ROOT?}/opt/arkime/etc" || true' % SENSOR_DIR, +'cp -r "%s/interface/"* "${ROOT?}/opt/sensor"' % SENSOR_DIR, +'cp -r "%s/shared/bin/"* "${ROOT?}/usr/local/bin"' % MALCOLM_DIR, +'cp "%s/scripts/malcolm_utils.py" "${ROOT?}/usr/local/bin/"' % MALCOLM_DIR, +'cp "%s/config/archives/beats.list.chroot" "${ROOT?}/etc/apt/sources.list.d/beats.list"' % SENSOR_DIR, +'cp "%s/config/archives/beats.key.chroot" "${ROOT?}/etc/apt/keyrings/"' % SENSOR_DIR, +'cp "%s/config/archives/fluentbit.list.chroot" "${ROOT?}/etc/apt/sources.list.d/fluentbit.list"' % SENSOR_DIR, +'cp "%s/config/archives/fluentbit.key.chroot" "${ROOT?}/etc/apt/keyrings/"' % SENSOR_DIR, +'cp -r "%s/config/includes.chroot/"* "${ROOT?}/"' % SENSOR_DIR, +'rm -r "${ROOT?}/etc/live"', +'cp -r "%s/config/hooks/normal/"* "${ROOT?}/opt/hooks/"' % SENSOR_DIR, +'cp -r "%s/config/package-lists/"* "${ROOT?}/opt/deps/"' % SENSOR_DIR, +'cp -r "%s/docs/images/hedgehog/logo/hedgehog-ascii-text.txt"* "${ROOT?}/root/"' % MALCOLM_DIR, +] + +# Extend list just in case version is 4 +extra_chroot_shell_cmds.extend ([ +'chmod 755 /root/sensor_install.sh', +'/root/sensor_install.sh 2>&1 | tee -a /root/sensor_install_debug', +]) + +### The following prepares substitutions based on variables set earlier + +# Enable backports with a reason, or add commented-out entry: +if backports_enable: + backports_stanza = """ +%s +deb http://deb.debian.org/debian/ %s main %s +""" % (backports_enable, backports_suite, firmware_component) +else: + # ugh + backports_stanza = """ +# Backports are _not_ enabled by default. +# Enable them by uncommenting the following line: +# deb http://deb.debian.org/debian %s main %s +""" % (backports_suite, firmware_component) + +#gitcommit = subprocess.getoutput("git show -s --pretty='format:%C(auto)%h (%s, %ad)' --date=short ") +buildtime = subprocess.getoutput("date --utc +'%Y-%m-%d %H:%M'") + +### Write results: + +def align_replace(text, pattern, replacement): + """ + This helper lets us keep the indentation of the matched pattern + with the upcoming replacement, across multiple lines. Naive + implementation, please make it more pythonic! + """ + lines = text.splitlines() + for i, line in enumerate(lines): + m = re.match(r'^(\s+)%s' % pattern, line) + if m: + indent = m.group(1) + del lines[i] + for r in replacement: + lines.insert(i, '%s%s' % (indent, r)) + i = i + 1 + break + return '\n'. join(lines) + '\n' + + +with open('raspi_master.yaml', 'r') as in_file: + with open(target_yaml, 'w') as out_file: + in_text = in_file.read() + out_text = in_text \ + .replace('__RELEASE__', suite) \ + .replace('__ARCH__', arch) \ + .replace('__FIRMWARE_COMPONENT__', firmware_component) \ + .replace('__FIRMWARE_COMPONENT_OLD__', firmware_component_old) \ + .replace('__LINUX_IMAGE__', linux) \ + .replace('__DTB__', dtb) \ + .replace('__WIRELESS_FIRMWARE__', wireless_firmware) \ + .replace('__BLUETOOTH_FIRMWARE__', bluetooth_firmware) \ + .replace('__SERIAL_CONSOLE__', serial) \ + .replace('__HOST__', hostname) \ + .replace('__BUILDTIME__', buildtime) +# .replace('__GITCOMMIT__', gitcommit) \ +# .replace('__BUILDTIME__', buildtime) + + out_text = align_replace(out_text, '__EXTRA_ROOT_SHELL_CMDS__', extra_root_shell_cmds) + out_text = align_replace(out_text, '__EXTRA_CHROOT_SHELL_CMDS__', extra_chroot_shell_cmds) + out_text = align_replace(out_text, '__BACKPORTS__', backports_stanza.splitlines()) + + # Try not to keep lines where the placeholder was replaced + # with nothing at all (including on a "list item" line): + filtered = [x for x in out_text.splitlines() + if not re.match(r'^\s+$', x) + and not re.match(r'^\s+-\s*$', x)] + out_file.write('\n'.join(filtered) + "\n") diff --git a/RaspberryPi/raspi_master.yaml b/RaspberryPi/raspi_master.yaml new file mode 100644 index 000000000..85976d98f --- /dev/null +++ b/RaspberryPi/raspi_master.yaml @@ -0,0 +1,182 @@ +--- +# See https://wiki.debian.org/RaspberryPi3 for known issues and more details. +# image.yml based on revision: __GITCOMMIT__ + +steps: + - mkimg: "{{ output }}" + size: 20480M + + - mklabel: msdos + device: "{{ output }}" + + - mkpart: primary + fs-type: 'fat32' + device: "{{ output }}" + start: 4MiB + end: 512MiB + tag: tag-firmware + + - mkpart: primary + device: "{{ output }}" + start: 512MiB + end: 100% + tag: tag-root + + - kpartx: "{{ output }}" + + - mkfs: vfat + partition: tag-firmware + label: RASPIFIRM + + - mkfs: ext4 + partition: tag-root + label: RASPIROOT + + - mount: tag-root + + - mount: tag-firmware + mount-on: tag-root + dirname: '/boot/firmware' + + - unpack-rootfs: tag-root + + - qemu-debootstrap: __RELEASE__ + mirror: http://deb.debian.org/debian + target: tag-root + arch: __ARCH__ + components: + - main + - __FIRMWARE_COMPONENT__ + - __FIRMWARE_COMPONENT_OLD__ + unless: rootfs_unpacked + + - create-file: /etc/apt/sources.list + contents: |+ + deb http://deb.debian.org/debian __RELEASE__ main contrib __FIRMWARE_COMPONENT__ __FIRMWARE_COMPONENT_OLD__ + deb http://deb.debian.org/debian __RELEASE__-updates main contrib __FIRMWARE_COMPONENT__ __FIRMWARE_COMPONENT_OLD__ + deb http://security.debian.org/debian-security __RELEASE__-security main contrib __FIRMWARE_COMPONENT__ __FIRMWARE_COMPONENT_OLD__ + __BACKPORTS__ + + unless: rootfs_unpacked + + - copy-file: /etc/initramfs-tools/hooks/rpi-resizerootfs + src: rootfs/etc/initramfs-tools/hooks/rpi-resizerootfs + perm: 0755 + unless: rootfs_unpacked + + - copy-file: /etc/initramfs-tools/scripts/local-bottom/rpi-resizerootfs + src: rootfs/etc/initramfs-tools/scripts/local-bottom/rpi-resizerootfs + perm: 0755 + unless: rootfs_unpacked + + - apt: install + packages: + - aide + - aide-common + - curl + - dosfstools + - clamav + - clamav-daemon + - clamav-freshclam + - iw + - gpg + - parted + - ssh + - wpasupplicant + - systemd-timesyncd + - __LINUX_IMAGE__ + - raspi-firmware + - __WIRELESS_FIRMWARE__ + - __BLUETOOTH_FIRMWARE__ + tag: tag-root + unless: rootfs_unpacked + + - cache-rootfs: tag-root + unless: rootfs_unpacked + + - shell: | + echo "__HOST__-$(date +%Y%m%d)" > "${ROOT?}/etc/hostname" + + # Allow root logins locally with no password + sed -i 's,root:[^:]*:,root::,' "${ROOT?}/etc/shadow" + + install -m 644 -o root -g root rootfs/etc/fstab "${ROOT?}/etc/fstab" + + install -m 644 -o root -g root rootfs/etc/network/interfaces.d/eth0 "${ROOT?}/etc/network/interfaces.d/eth0" + install -m 600 -o root -g root rootfs/etc/network/interfaces.d/wlan0 "${ROOT?}/etc/network/interfaces.d/wlan0" + + install -m 755 -o root -g root rootfs/usr/local/sbin/rpi-set-sysconf "${ROOT?}/usr/local/sbin/rpi-set-sysconf" + install -m 644 -o root -g root rootfs/etc/systemd/system/rpi-set-sysconf.service "${ROOT?}/etc/systemd/system/" + install -m 644 -o root -g root rootfs/boot/firmware/sysconf.txt "${ROOT?}/boot/firmware/sysconf.txt" + mkdir -p "${ROOT?}/etc/systemd/system/basic.target.requires/" + ln -s /etc/systemd/system/rpi-set-sysconf.service "${ROOT?}/etc/systemd/system/basic.target.requires/rpi-set-sysconf.service" + + install -m 644 -o root -g root rootfs/etc/systemd/system/rpi-reconfigure-raspi-firmware.service "${ROOT?}/etc/systemd/system/" + mkdir -p "${ROOT?}/etc/systemd/system/multi-user.target.requires/" + ln -s /etc/systemd/system/rpi-reconfigure-raspi-firmware.service "${ROOT?}/etc/systemd/system/multi-user.target.requires/rpi-reconfigure-raspi-firmware.service" + + install -m 644 -o root -g root rootfs/etc/systemd/system/rpi-generate-ssh-host-keys.service "${ROOT?}/etc/systemd/system/" + ln -s /etc/systemd/system/rpi-generate-ssh-host-keys.service "${ROOT?}/etc/systemd/system/multi-user.target.requires/rpi-generate-ssh-host-keys.service" + rm -f "${ROOT?}"/etc/ssh/ssh_host_*_key* + + __EXTRA_ROOT_SHELL_CMDS__ + root-fs: tag-root + + # Copy the relevant device tree files to the boot partition + - chroot: tag-root + shell: | + install -m 644 -o root -g root __DTB__ /boot/firmware/ + + # Clean up archive cache (likely not useful) and lists (likely outdated) to + # reduce image size by several hundred megabytes. + - chroot: tag-root + shell: | + apt-get clean + rm -rf /var/lib/apt/lists + + # Modify the kernel commandline we take from the firmware to boot from + # the partition labeled raspiroot instead of forcing it to mmcblk0p2. + # Also insert the serial console right before the root= parameter. + # + # These changes will be overwritten after the hardware is probed + # after dpkg reconfigures raspi-firmware (upon first boot), so make + # sure we don't lose label-based booting. + - chroot: tag-root + shell: | + sed -i 's/root=/console=__SERIAL_CONSOLE__ root=/' /boot/firmware/cmdline.txt + sed -i 's#root=/dev/mmcblk0p2#root=LABEL=RASPIROOT#' /boot/firmware/cmdline.txt + sed -i 's/^#ROOTPART=.*/ROOTPART=LABEL=RASPIROOT/' /etc/default/raspi*-firmware + + __EXTRA_CHROOT_SHELL_CMDS__ + + # TODO(https://github.com/larswirzenius/vmdb2/issues/24): remove once vmdb + # clears /etc/resolv.conf on its own. + - shell: | + rm "${ROOT?}/etc/resolv.conf" + root-fs: tag-root + + # Clear /etc/machine-id and /var/lib/dbus/machine-id, as both should + # be auto-generated upon first boot. From the manpage + # (machine-id(5)): + # + # For normal operating system installations, where a custom image is + # created for a specific machine, /etc/machine-id should be + # populated during installation. + # + # Note this will also trigger ConditionFirstBoot=yes for systemd. + # On Buster, /etc/machine-id should be an emtpy file, not an absent file + # On Bullseye, /etc/machine-id should not exist in an image + - chroot: tag-root + shell: | + rm -f /etc/machine-id /var/lib/dbus/machine-id + echo "uninitialized" > /etc/machine-id + + # Resize script is now in the initrd for first boot; no need to ship it. + # Removing here to avoid any update-initramfs triggers from removing script during build process + rm -f "/etc/initramfs-tools/hooks/rpi-resizerootfs" + rm -f "/etc/initramfs-tools/scripts/local-bottom/rpi-resizerootfs" + + # Create /etc/raspi-image-id to know, from what commit the image was built + - chroot: tag-root + shell: | + echo "Image built on __BUILDTIME__ (UTC)" > "/etc/raspi-image-id" diff --git a/RaspberryPi/rootfs/boot/firmware/sysconf.txt b/RaspberryPi/rootfs/boot/firmware/sysconf.txt new file mode 100644 index 000000000..4ea5a247a --- /dev/null +++ b/RaspberryPi/rootfs/boot/firmware/sysconf.txt @@ -0,0 +1,35 @@ +# This file will be automatically evaluated and installed at next boot +# time, and regenerated (to avoid leaking passwords and such information). +# +# To force it to be evaluated immediately, you can run (as root): +# +# /usr/sbin/rpi-set-sysconf +# +# You can disable the file evaluation by disabling the rpi-set-sysconf +# service in systemd: +# +# systemctl disable rpi-set-sysconf +# +# Comments (all portions of a line following a '#' character) are +# ignored. This file is read line by line. Valid +# configuration lines are of the form 'key=value'. Whitespace around +# 'key' and 'value' is ignored. This file will be _regenerated_ every +# time it is evaluated. +# +# We follow the convention to indent with one space comments, and +# leave no space to indicate the line is an example that could be +# uncommented. + +# root_pw - Set a password for the root user (by default, it allows +# for a passwordless login) +#root_pw=FooBar + +# root_authorized_key - Set an authorized key for a root ssh login +#root_authorized_key= + +# hostname - Set the system hostname. +#hostname=rpi + +# We found the following unhandled keys - That means, the +# configuration script does not know how to handle them. Please +# double-check them! diff --git a/RaspberryPi/rootfs/etc/fstab b/RaspberryPi/rootfs/etc/fstab new file mode 100644 index 000000000..805b59973 --- /dev/null +++ b/RaspberryPi/rootfs/etc/fstab @@ -0,0 +1,4 @@ +# The root file system has fs_passno=1 as per fstab(5) for automatic fsck. +LABEL=RASPIROOT / ext4 rw 0 1 +# All other file systems have fs_passno=2 as per fstab(5) for automatic fsck. +LABEL=RASPIFIRM /boot/firmware vfat rw 0 2 diff --git a/RaspberryPi/rootfs/etc/initramfs-tools/hooks/rpi-resizerootfs b/RaspberryPi/rootfs/etc/initramfs-tools/hooks/rpi-resizerootfs new file mode 100755 index 000000000..35e67fce3 --- /dev/null +++ b/RaspberryPi/rootfs/etc/initramfs-tools/hooks/rpi-resizerootfs @@ -0,0 +1,52 @@ +#!/bin/sh +set -e + +# +# List the soft prerequisites here. This is a space separated list of +# names, of scripts that are in the same directory as this one, that +# must be run before this one can be. +# +PREREQS="" +case $1 in + prereqs) echo "$PREREQS"; exit 0;; +esac + +. /usr/share/initramfs-tools/hook-functions + +# List ALL the programs we need, because we explicitly call them +# Don't *assume* it will be included! +# The update-initramfs script will figure out any dependencies +# that also need to be included, so lets not do that +# +# Find the path as used by the package itself; usrmerge may not be used + +# from coreutils +copy_exec /usr/bin/realpath +copy_exec /usr/bin/tail +copy_exec /usr/bin/test + +# from dosfstools +copy_exec /sbin/fsck.vfat + +# from e2fsprogs +copy_exec /sbin/resize2fs +copy_exec /sbin/fsck.ext4 + +# from grep +copy_exec /bin/grep + +# from logsave +copy_exec /sbin/logsave + +# from mount +copy_exec /bin/mount +copy_exec /bin/umount + +# from parted +copy_exec /sbin/parted +copy_exec /sbin/partprobe + +# from util-linux +copy_exec /bin/lsblk +copy_exec /sbin/blkid +copy_exec /sbin/fsck diff --git a/RaspberryPi/rootfs/etc/initramfs-tools/scripts/local-bottom/rpi-resizerootfs b/RaspberryPi/rootfs/etc/initramfs-tools/scripts/local-bottom/rpi-resizerootfs new file mode 100755 index 000000000..382684776 --- /dev/null +++ b/RaspberryPi/rootfs/etc/initramfs-tools/scripts/local-bottom/rpi-resizerootfs @@ -0,0 +1,59 @@ +#!/bin/sh +set -e + +# +# List the soft prerequisites here. This is a space separated list of +# names, of scripts that are in the same directory as this one, that +# must be run before this one can be. +# +PREREQS="" +case $1 in + prereqs) echo "$PREREQS"; exit 0;; +esac + +. /scripts/functions + +# Given the root partition, get the underlying device and partition number +rootpart=$(realpath "$ROOT") +rootpart_nr=$(blkid -sPART_ENTRY_NUMBER -o value -p "$rootpart") +rootdev="/dev/$(lsblk -no pkname "$rootpart")" + +# Parted will detect if the GPT label is messed up and fix it +# automatically, we just need to tell it to do so. +parted -s "$rootdev" print 2>&1 | grep -z "fix the GPT" && { + echo "Fix" | parted ---pretend-input-tty "$rootdev" print +} + +# Check if there's free space at the end of the device +free_space="$(parted -m -s "$rootdev" print free | tail -n1 | grep free)" +if test -z "$free_space"; then + # Great, we already resized; nothing left to do! + exit 0 +fi + +log_begin_msg "$0 resizing $ROOT" + +# Unmount for safety; fail if unset or empty (shellcheck SC2154) +umount "${rootmnt:?}" + +# Expand the partition size to fill the entire device +parted -s "$rootdev" resizepart "$rootpart_nr" 100% + +wait_for_udev 5 + +# Now resize the filesystem +partprobe "$rootdev" +resize2fs "$rootpart" + +# After resizing, (re)check the root partition's filesystem +fsck "$rootpart" + +# Remount root +# Don't quote ${ROOTFLAGS} as that results in an extra (empty) argument +# to 'mount', which in turn causes a boot failure +# shellcheck disable=SC2086 +if ! mount -r ${FSTYPE:+-t "${FSTYPE}"} ${ROOTFLAGS} "${ROOT}" "${rootmnt?}"; then + panic "Failed to mount ${ROOT} as root file system." +fi + +log_end_msg diff --git a/RaspberryPi/rootfs/etc/network/interfaces.d/eth0 b/RaspberryPi/rootfs/etc/network/interfaces.d/eth0 new file mode 100644 index 000000000..0caae5ffb --- /dev/null +++ b/RaspberryPi/rootfs/etc/network/interfaces.d/eth0 @@ -0,0 +1,3 @@ +auto eth0 +iface eth0 inet dhcp +iface eth0 inet6 auto diff --git a/RaspberryPi/rootfs/etc/network/interfaces.d/wlan0 b/RaspberryPi/rootfs/etc/network/interfaces.d/wlan0 new file mode 100644 index 000000000..7d57fb959 --- /dev/null +++ b/RaspberryPi/rootfs/etc/network/interfaces.d/wlan0 @@ -0,0 +1,8 @@ +# To enable wireless networking, uncomment the following lines and -naturally- +# replace with your network's details. +# +# allow-hotplug wlan0 +# iface wlan0 inet dhcp +# iface wlan0 inet6 dhcp +# wpa-ssid my-network-ssid +# wpa-psk s3kr3t_P4ss diff --git a/RaspberryPi/rootfs/etc/systemd/system/rpi-generate-ssh-host-keys.service b/RaspberryPi/rootfs/etc/systemd/system/rpi-generate-ssh-host-keys.service new file mode 100644 index 000000000..496f2c47f --- /dev/null +++ b/RaspberryPi/rootfs/etc/systemd/system/rpi-generate-ssh-host-keys.service @@ -0,0 +1,10 @@ +[Unit] +Description=generate SSH host keys +ConditionPathExistsGlob=!/etc/ssh/ssh_host_*_key + +[Service] +Type=oneshot +ExecStart=/usr/sbin/dpkg-reconfigure -fnoninteractive openssh-server + +[Install] +RequiredBy=multi-user.target diff --git a/RaspberryPi/rootfs/etc/systemd/system/rpi-reconfigure-raspi-firmware.service b/RaspberryPi/rootfs/etc/systemd/system/rpi-reconfigure-raspi-firmware.service new file mode 100644 index 000000000..d8c555826 --- /dev/null +++ b/RaspberryPi/rootfs/etc/systemd/system/rpi-reconfigure-raspi-firmware.service @@ -0,0 +1,14 @@ +[Unit] +Description=Reconfigure raspi-firmware to regenerate config.txt matching actual hardware +Before=sysinit.target +DefaultDependencies=no +RequiresMountsFor=/boot/firmware + +[Service] +Type=oneshot +TimeoutSec=infinity +ExecStart=/usr/sbin/dpkg-reconfigure raspi-firmware +ExecStart=/bin/systemctl --no-reload disable %n + +[Install] +RequiredBy=sysinit.target diff --git a/RaspberryPi/rootfs/etc/systemd/system/rpi-set-sysconf.service b/RaspberryPi/rootfs/etc/systemd/system/rpi-set-sysconf.service new file mode 100644 index 000000000..46bddcfd3 --- /dev/null +++ b/RaspberryPi/rootfs/etc/systemd/system/rpi-set-sysconf.service @@ -0,0 +1,9 @@ +[Unit] +Description=Set up system configuration + +[Service] +Type=oneshot +ExecStart=/usr/local/sbin/rpi-set-sysconf + +[Install] +RequiredBy=basic.target diff --git a/RaspberryPi/rootfs/usr/local/sbin/rpi-set-sysconf b/RaspberryPi/rootfs/usr/local/sbin/rpi-set-sysconf new file mode 100644 index 000000000..f4f1b00c2 --- /dev/null +++ b/RaspberryPi/rootfs/usr/local/sbin/rpi-set-sysconf @@ -0,0 +1,157 @@ +#!/usr/bin/perl +use strict; +use warnings; +use IO::File; +use IO::Pipe; +use feature 'switch'; + +my ($filename, $conf); + +$filename = '/boot/firmware/sysconf.txt'; + +logger('info', "Reading the system configuration settings from $filename"); +$conf = read_conf($filename); + +if (my $pass = delete($conf->{root_pw})) { + my $pipe; + logger('debug', 'Resetting root password'); + unless (open($pipe, '|-', '/usr/sbin/chpasswd')) { + my $err = $!; + logger('error', "Could not run chpasswd: $err"); + die $err; + } + $pipe->print("root:$pass"); + close($pipe); +} + +if (my $root_authorized_key = delete($conf->{root_authorized_key})) { + my $fh; + logger('debug', "Adding key to root's authorized_keys"); + if(! -d "/root/.ssh") { + if(!mkdir("/root/.ssh", 0700)) { + my $err = sprintf("Could not create /root/.ssh directory: %s", $!); + logger('error', $err); + die $err; + } + } + + unless ($fh = IO::File->new('/root/.ssh/authorized_keys', 'w', 0600)) { + my $err = $!; + logger('error', "Could not write /root/.ssh/authorized_keys: $err"); + die $err; + } + $fh->print($root_authorized_key); + $fh->close; +} + +if (my $name = delete($conf->{hostname})) { + my $fh; + logger('debug', "Setting hostname to '$name'"); + unless ($fh = IO::File->new('/etc/hostname', 'w')) { + my $err = $!; + logger('error', "Could not write hostname '$name': $err"); + die $err; + } + $fh->print($name); + $fh->close; + system('hostname', '--file', '/etc/hostname'); +} + +rewrite_conf_file($filename, $conf); + +exit 0; + +sub read_conf { + my ($file, $conf, $fh); + $file = shift; + + $conf = {}; + unless ($fh = IO::File->new($filename, 'r')) { + my $err = $!; + logger('error', "Could not read from configuration file '$filename': $err"); + # Not finding the config file is not fatal: there is just + # nothing to configure! + return $conf; + } + while (my $line = $fh->getline) { + my ($key, $value); + # Allow for comments, and properly ignore them + $line =~ s/#.+//; + if ( ($key, $value) = ($line =~ m/^\s*([^=]+)\s*=\s*(.*)\s*$/)) { + $key = lc($key); + if (exists($conf->{$key})) { + logger('warn', + "Repeated configuration key: $key. " . + "Overwriting with new value ($value)"); + } + $conf->{$key} = $value; + } + } + $fh->close; + + return $conf; +} + +sub logger { + my ($prio, $msg) = @_; + system('logger', '-p', "daemon.$prio", + '-t', 'rpi-set-sysconf', $msg); +} + +sub rewrite_conf_file { + my ($filename, $conf) = @_; + my $fh; + unless ($fh = IO::File->new($filename, 'w')) { + my $err = $!; + logger('error', "Could not write to configuration file '$filename': $err"); + die $err; + } + $fh->print( +q(# This file will be automatically evaluated and installed at next boot +# time, and regenerated (to avoid leaking passwords and such information). +# +# To force it to be evaluated immediately, you can run (as root): +# +# /usr/local/sbin/rpi-set-sysconf +# +# You can disable the file evaluation by disabling the rpi-set-sysconf +# service in systemd: +# +# systemctl disable rpi-set-sysconf +# +# Comments (all portions of a line following a '#' character) are +# ignored. This file is read line by line. Valid +# configuration lines are of the form 'key=value'. Whitespace around +# 'key' and 'value' is ignored. This file will be _regenerated_ every +# time it is evaluated. +# +# We follow the convention to indent with one space comments, and +# leave no space to indicate the line is an example that could be +# uncommented. + +# root_pw - Set a password for the root user (by default, it allows +# for a passwordless login) +#root_pw=FooBar + +# root_authorized_key - Set an authorized key for a root ssh login +#root_authorized_key= + +# hostname - Set the system hostname. +#hostname=rpi +)); + + if (scalar keys %$conf) { + logger('warn', 'Unprocessed keys left in $filename: ' . + join(', ', sort keys %$conf)); + $fh->print( +q( +# We found the following unhandled keys - That means, the +# configuration script does not know how to handle them. Please +# double-check them! +)); + $fh->print(join('', map {sprintf("%s=%s\n", $_, $conf->{$_})} sort keys %$conf)); + } + $fh->close; +} + + diff --git a/RaspberryPi/sensor_install.sh b/RaspberryPi/sensor_install.sh new file mode 100644 index 000000000..b1fd1468a --- /dev/null +++ b/RaspberryPi/sensor_install.sh @@ -0,0 +1,468 @@ +#!/bin/bash -e +# This script is copied into a chroot'd environment +# Paths will be absolute and will reflect the path on the RPI Sensor + +PATH='/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/sbin:/usr/local/bin' +umask 0022 + +if [ "$(id -u)" != "0" ]; then + echo "This script must be run as root" 1>&2 + exit $BUILD_ERROR_CODE +fi + +IMAGE_NAME=hedgehog +IMAGE_PUBLISHER=cisagov +IMAGE_VERSION=1.0.0 +IMAGE_DISTRIBUTION=bookworm + +# Determine number of proc cores available +# Use caution messing with this value; build process may trigger OOM and fail!! +PROC_CNT=$(nproc) +ARCH="$(dpkg --print-architecture)" +export DEBIAN_FRONTEND=noninteractive + +# Used to build RPI without graphical features +# Changing to 1 is mostly unimplemented +BUILD_GUI=0 + +RUN_PATH="(pwd)" +DEBS_DIR="${HOME}/debs" +DEPS_DIR='/opt/deps' +WORK_DIR="$(mktemp -d -t hedgehog-XXXXXX)" +SENSOR_DIR='/opt/sensor' + +BEATS_VER="8.11.3" +BEATS_OSS="-oss" + +# Option to build from sources if desired +# Building from source will increase build time A LOT (especially Zeek)! +BUILD_ARKIME_FROM_SOURCE=1 +BUILD_YARA_FROM_SOURCE=1 +BUILD_ZEEK_FROM_SOURCE=0 + +# Build time dependencies for arkime, htpdate, capa, and yara +BUILD_DEPS='build-essential meson ninja-build patch python3-dev python3-pip python3-setuptools ' +BUILD_DEPS+='ruby ruby-dev ruby-rubygems wget automake checkinstall flex gcc libjansson-dev ' +BUILD_DEPS+='libmagic-dev libssl-dev libtool make pkg-config libnl-genl-3-dev python3-venv ' + +# Build time dependencies for zeek when built from source +if [ $BUILD_ZEEK_FROM_SOURCE -ne 0 ]; then + BUILD_DEPS+='ccache clang cmake git libc++-dev libc++abi-dev libfl-dev libgoogle-perftools-dev ' + BUILD_DEPS+='libgoogle-perftools4 libkrb5-3 libkrb5-dev libmaxminddb-dev libpcap-dev ' + BUILD_DEPS+='libtcmalloc-minimal4 python3-git python3-semantic-version swig zlib1g-dev ' +fi + +BUILD_ERROR_CODE=1 + +################################ +######### Functions ############ +################################ + +build_arkime_src(){ + + arkime_repo='https://github.com/arkime/arkime.git' + arkime_ver='4.6.0' + arkime_dir='/opt/arkime' + build_jobs=$((PROC_CNT/2)) + + apt install $BUILD_DEPS -y --no-install-suggests + + gem install --no-document fpm + + mkdir -p "${WORK_DIR}/arkime" && cd "$_" + git clone --recurse-submodules --branch="v${arkime_ver}" "$arkime_repo" ./ + + for patch_file in /opt/patches/*; do + patch -p 1 -r - --no-backup-if-mismatch < $patch_file || true + done + + export PATH="${arkime_dir}/bin:${WORK_DIR}/arkime/node_modules/.bin:${PATH}" + + sed -i "s/MAKE=make/MAKE='make -j${build_jobs}'/" easybutton-build.sh + ./easybutton-build.sh --dir "$arkime_dir" + + npm -g config set user root + + make install -j${build_jobs} + + cp -r ./capture/plugins/lua/samples "${arkime_dir}"/lua + + npm install license-checker + release/notice.txt.pl "${arkime_dir}/" NOTICE release/CAPTURENOTICE > "${arkime_dir}/NOTICE.txt" + + rm -f $arkime_dir/etc/*.systemd.service + + fpm -s dir -t deb -n arkime -x opt/arkime/logs -x opt/arkime/raw \ + -v "$arkime_ver" --iteration 1 --template-scripts --after-install "release/afterinstall.sh" \ + --url "https://arkime.com" --description "Arkime Full Packet System" \ + -d libwww-perl -d libjson-perl -d ethtool -d libyaml-dev \ + -p "${DEBS_DIR}/arkime_${arkime_ver}_${ARCH}.deb" "$arkime_dir" + + cd "${WORK_DIR}" + rm -rf "${WORK_DIR}/arkime" "$arkime_dir" + + dpkg -i "${DEBS_DIR}/arkime_${arkime_ver}_${ARCH}.deb" +} + +build_htpdate() { + + # Htpdate in Debian repos doesn't compile https functionality + + htpdate_url='https://github.com/twekkel/htpdate' + htpdate_vers="$(curl -sqI $htpdate_url/releases/latest | awk -F '/' '/^location/ {print substr($NF,2,length($NF)-2)}')" + htpdate_release=1 + + apt install $BUILD_DEPS -y --no-install-suggests + + mkdir -p "${WORK_DIR}"/htpdate && cd "$_" + curl -sSL "$htpdate_url/tarball/v$htpdate_vers" | tar xzf - --strip-components=1 + + sed -i '/.*man8.*/d' Makefile + + make https + + checkinstall -y -D --nodoc --strip=yes --stripso=yes --install=no --fstrans=no \ + --pkgname=htpdate --pkgversion=$htpdate_vers --pkgarch="$ARCH" --pkgsource="$htpdate_url" \ + --pkgrelease="$htpdate_release" --pakdir "$DEBS_DIR" + + # htpdate is installed outside of dpkg with checkinstall + make uninstall + + cd "${WORK_DIR}" + + dpkg -i "${DEBS_DIR}/htpdate_${htpdate_vers}-${htpdate_release}_${ARCH}.deb" +} + +build_interface() { + + interface_dir="${SENSOR_DIR}" + cd "$interface_dir" + + if [[ $BUILD_GUI -eq 1 ]]; then + # Items below required for GUI interface. Requires graphical DE to be useful + sed -i "s@/home/sensor/sensor_interface@${SENSOR_DIR}@g" "${interface_dir}/kiosk.service" + python3 -m pip install --break-system-packages --no-compile --no-cache-dir --force-reinstall \ + --upgrade -r requirements.txt + rm -rf "${interface_dir}/.git" "${interface_dir}/requirements.txt" + else + cd "${interface_dir}" + rm -rf .git requirements.txt init.sh kiosk.service sensor_interface/ + cd "$OLDPWD" + fi + + sed -i 's/CAPTURE_INTERFACE=.*/CAPTURE_INTERFACE=xxxx/g' "${interface_dir}/sensor_ctl/control_vars.conf" + +} + +build_yara_src() { + + # Build Yara from source code + + apt install $BUILD_DEPS -y --no-install-suggests + + yara_url="https://github.com/VirusTotal/YARA" + yara_ver="$(curl -sqI ${yara_url}/releases/latest | awk -F '/' '/^location/ {print substr($NF,2,length($NF)-2)}')" + yara_release=1 + + mkdir -p "${WORK_DIR}/yara" && cd "$_" + curl -sSL "${yara_url}/tarball/v${yara_ver}" | tar xzf - --strip-components=1 + ./bootstrap.sh + ./configure --prefix=/usr --with-crypto --enable-magic --enable-cuckoo + make -j $PROC_CNT + + checkinstall -y -D --strip=yes --stripso=yes --nodoc --install=no --fstrans=no --pkgname="yara" \ + --pkgversion="$yara_ver" --pkgrelease="$yara_release" --pkgarch="$ARCH" --pkgsource="$yara_url" --pakdir="$DEBS_DIR" + + # Files are installed by checkinstall outside of DPKG + # Remove them since a deb has been created for later installation + make uninstall + + cd "${WORK_DIR}" + rm -rf "${WORK_DIR}/yara" + + dpkg -i "${DEBS_DIR}/yara_${yara_ver}-${yara_release}_${ARCH}.deb" +} + +build_zeek() { + + # Build zeek from lts debs from OpenSUSE + + zeek_repo='https://download.opensuse.org/repositories/security:/zeek/Debian_12/' + zeek_gpg_key=/etc/apt/keyrings/zeek-lts.gpg + zeek_apt_file=/etc/apt/sources.list.d/zeek.list + zeek_ver='lts' + zeek_dir=/opt/zeek + + curl -L "${zeek_repo}/Release.key" | gpg --dearmor --batch --yes -o "$zeek_gpg_key" + + echo "#This is an unofficial build of Zeek LTS" > "$zeek_apt_file" + echo "deb [signed-by=${zeek_gpg_key}] $zeek_repo ./" >> "$zeek_apt_file" + + apt update + apt install zeek-${zeek_ver} -y --no-install-suggests + +} + +build_zeek_src() { + + # Build Zeek from source code + # Leaving this code here for future use if needed + + export CCACHE_DIR=/var/spool/ccache + export CCACHE_COMPRESS=1 + export PYTHONDONTWRITEBYTECODE=1 + export PYTHONUNBUFFERED=1 + + zeek_url=https://github.com/zeek/zeek.git + zeek_version=6.0.1 + zeek_release=1 + zeek_dir=/opt/zeek + # Zeek's build eats a ton of resources; prevent OOM from the killing build process + build_jobs=$((PROC_CNT/2)) + # Testing was done on a 8 cpu host with 16GB of ram. + # Successful Zeek from source build alone took: 6.5 hours + output_dir=/tmp + unset VERBOSE + + mkdir -p "${WORK_DIR}/zeek" && cd "$_" + curl -sSL "https://download.zeek.org/zeek-${zeek_version}.tar.gz" | tar xzf - --strip-components=1 + + mkdir -p "${CCACHE_DIR}" + ./configure --prefix="${zeek_dir}" --disable-broker-tests --disable-cpp-tests \ + --disable-btest-pcaps --disable-btest --generator=Ninja --ccache --enable-perftools + + mkdir -p build && cd "$_" + ninja -j "$build_jobs" + + checkinstall -y -D --strip=yes --stripso=yes --nodoc --install=no --fstrans=no \ + --pkgname="zeek" --pkgversion="$zeek_version" --pkgarch="$ARCH" --pkgsource="$zeek_url" \ + --pkgrelease="$zeek_release" --pakdir="$DEBS_DIR" ninja install + + # Files are installed by checkinstall outside of DPKG + # Remove them since a deb has been created for later installation + ninja uninstall + + cd "${WORK_DIR}" + rm -rf "${WORK_DIR}/zeek" + + dpkg -i "${DEBS_DIR}/zeek_${zeek_ver}-${zeek_release}_${ARCH}.deb" + +} + +clean_up() { + + # Set Hedgehog banner + mv /root/hedgehog-ascii-text.txt /etc/issue + + # Remove network interface files left by installation + # Sensor setup will create necessary files when user runs setup + rm -f /etc/network/interfaces.d/* + + # Ensure user network conf goes into proper file + touch /etc/network/interfaces.d/sensor + + # Remove this script and any debugging files + # Comment this out in order to troubleshoot the build process in a chroot + # Build process writes to /root/sensor_install_debug by default + rm -f /root/sensor_install* + + # Remove extra installation files + rm -rf /opt/hooks /opt/patches $WORK_DIR /tmp/* /opt/deps /root/.wget-hsts /root/.bash_history + + # Remove unnecessary build components + apt remove $BUILD_DEPS -y + apt autoremove -y + apt clean + + # Ensure locale, term, and console are setup correct + echo 'TERM=linux' > /etc/environment + locale-gen en_US.UTF-8 en.UTF-8 + update-locale LANG=en_US.UTF-8 LANGUAGE=en.UTF-8 + sed -i -e 's/CHARMAP=.*/CHARMAP="UTF-8"/' -e 's/CODESET=.*/CODESET="Lat15"/' /etc/default/console-setup + dpkg-reconfigure console-setup + + umount -A -f /dev/pts /run /dev /proc /sys + +} + +clean_up_gui_files() { + + rm -rf /etc/skel/.config/autostart/* +} + +create_user() { + + # Set defaults but it is STRONGLY recommended that these be changed before deploying Sensor + local user='sensor' + local pass='Hedgehog_Linux' + local root_pass='Hedgehog_Linux_Root' + + groupadd "$user" + useradd -m -g sensor -u 1000 -s /bin/bash "$user" + + echo -n "${user}:${pass}" | chpasswd --crypt-method YESCRYPT + echo -n "root:${root_pass}" | chpasswd --crypt-method YESCRYPT + +} + +install_deps() { + + local deps='' + + if [ $BUILD_GUI -eq 0 ]; then + rm -f "${DEPS_DIR}/"{desktopmanager,live,virtualguest}.list.chroot + rm -f "${DEPS_DIR}/grub.list.binary" + fi + + for file in "${DEPS_DIR}/"*.chroot; do + sed -i '$a\' "$file" + deps+=$(tr '\n' ' ' < "$file") + done + + # Remove Sensor-ISO packages not relevant to RPI + # Rar is excluded because Debian doesn't have an ARM package + # htpdate removed because repo version doesn't support https + declare -a graphical_deps=( efibootmgr fonts-dejavu fuseext2 fusefat fuseiso gdb gparted gdebi ) + graphical_deps+=( google-perftools gvfs gvfs-daemons gvfs-fuse ghostscript ghostscript-x hfsplus ) + graphical_deps+=( hfsprogs hfsutils htpdate libgtk2.0-bin menu neofetch pmount rar samba-libs ) + graphical_deps+=( ssh-askpass tmux udisks2 upower user-setup xbitmaps zenity zenity-common ) + + deps=$(echo ${deps} ${graphical_deps[@]} | tr ' ' '\n' | sort | uniq -u | tr '\n' ' ') + + apt update + # Hedgehog conf files are copied into env before this runs; keep those config files by default + apt -o Dpkg::Options::="--force-confold" install -q $deps -y --no-install-suggests + apt clean + +} + +install_files() { + + sensor_ver_file="${SENSOR_DIR}/.os-info" + + # Shared Scripts setup + ln -s /usr/local/bin/malcolm_utils.py "/opt/zeek/bin/" + mv /usr/local/bin/zeekdeploy.sh "/opt/zeek/bin/" + + # Setup OS information + echo "BUILD_ID=\"$(date +\'%Y-%m-%d\')-${IMAGE_VERSION}\"" > "$sensor_ver_file" + echo "VARIANT=\"Hedgehog Linux (Sensor) v${IMAGE_VERSION}\"" >> "$sensor_ver_file" + echo "VARIANT_ID=\"hedgehog-sensor\"" >> "$sensor_ver_file" + echo "ID_LIKE=\"debian\"" >> "$sensor_ver_file" + echo "HOME_URL=\"https://malcolm.fyi\"" >> "$sensor_ver_file" + echo "DOCUMENTATION_URL=\"https://malcolm.fyi/hedgehog/\"" >> "$sensor_ver_file" + echo "SUPPORT_URL=\"https://github.com/${IMAGE_PUBLISHER}\"" >> "$sensor_ver_file" + echo "BUG_REPORT_URL=\"https://github.com/${IMAGE_PUBLISHER}/malcolm/issues\"" >> "$sensor_ver_file" + +##################################################### +# NEED TO FIND OUT WHERE THE MM FILE COMES FROM!!!! # +##################################################### + + # Setup MaxMind Geo IP info + MAXMIND_GEOIP_DB_LICENSE_KEY="" + + if [[ -f "$SCRIPT_PATH/shared/maxmind_license.txt" ]]; then + MAXMIND_GEOIP_DB_LICENSE_KEY="$(cat "$SCRIPT_PATH/shared/maxmind_license.txt" | head -n 1)" + if [[ ${#MAXMIND_GEOIP_DB_LICENSE_KEY} -gt 1 ]]; then + for DB in ASN Country City; do + curl -s -S -L -o "/opt/arkime/GeoLite2-$DB.mmdb.tar.gz" "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-$DB&license_key=$MAXMIND_GEOIP_DB_LICENSE_KEY&suffix=tar.gz" + tar xvf "GeoLite2-$DB.mmdb.tar.gz" --wildcards --no-anchored '*.mmdb' --strip=1 --no-same-owner + rm -f "GeoLite2-$DB.mmdb.tar.gz" + done + fi + fi + curl -s -S -L -o /opt/arkime/etc/ipv4-address-space.csv "https://www.iana.org/assignments/ipv4-address-space/ipv4-address-space.csv" + curl -s -S -L -o /opt/arkime/etc/oui.txt "https://www.wireshark.org/download/automated/data/manuf" + + + # Prepare Fluentbit and Beats repo GPG keys + local apt_lists='/etc/apt/sources.list.d' + local apt_keys='/etc/apt/keyrings' + local beats_key="${apt_keys}/beats.gpg" + local fluentbit_key="${apt_keys}/fluentbit.gpg" + + gpg --dearmor --batch --yes -o "$beats_key" "${apt_keys}/beats.key.chroot" + gpg --dearmor --batch --yes -o "$fluentbit_key" "${apt_keys}/fluentbit.key.chroot" + + rm "${apt_keys}/beats.key.chroot" "${apt_keys}/fluentbit.key.chroot" + + sed -i -e "s|deb |deb [signed-by=${beats_key}] |" "${apt_lists}/beats.list" + sed -i -e "s|deb |deb [signed-by=${fluentbit_key}] |" "${apt_lists}/fluentbit.list" + + # Prepare debs directory for other packages + mkdir -p "${DEBS_DIR}" + + # Disable ipv6 + echo 'ipv6.disable=1' > /etc/default/raspi-extra-cmdline + + # Add RPI hostname to /etc/hosts + echo "127.0.0.1 $(hostname)" >> /etc/hosts + +} + +install_hooks() { + + set -e + + local hooks_dir='/opt/hooks' + + if [[ $BUILD_GUI -eq 0 ]]; then + rm -f "${hooks_dir}"/*firefox-install.hook.chroot + rm -f "${hooks_dir}"/*login.hook.chroot + fi + + for file in ${hooks_dir}/*.hook.chroot; do + /bin/bash "$file" + done + +} + +################################ +########## Main ################ +################################ + +# Make sure necessary virtual filesystems available in chroot +mount -t proc /proc /proc +mount -t devtmpfs /dev /dev +mount -t devpts /dev/pts /dev/pts +mount -t sysfs /sys /sys +mount -t tmpfs /run /run + +install_files +install_deps +build_interface + +# Remove GUI related files if not building RPI with a DE +# See comment above about BUILD_GUI usage +if [[ $BUILD_GUI -eq 0 ]]; then + clean_up_gui_files +fi + +if [ $BUILD_ARKIME_FROM_SOURCE -eq 1 ]; then + build_arkime_src +else + # Not implemented currently + #build_arkime + build_arkime_src +fi + +if [ $BUILD_YARA_FROM_SOURCE -eq 1 ]; then + build_yara_src +else + # Not implemented currently + #build_yara + build_yara_src +fi + +if [ $BUILD_ZEEK_FROM_SOURCE -eq 1 ]; then + build_zeek_src +else + build_zeek +fi + +install_hooks +build_htpdate +create_user +clean_up + +exit 0 diff --git a/docs/images/hedgehog/logo/hedgehog-ascii-text.txt b/docs/images/hedgehog/logo/hedgehog-ascii-text.txt new file mode 100644 index 000000000..c3a56cfe9 --- /dev/null +++ b/docs/images/hedgehog/logo/hedgehog-ascii-text.txt @@ -0,0 +1,40 @@ + + ,cc:,.. .:' + :dddddddoc,. ;,. oddo:. .c;. + :dddddddddddo;:ddc:dddddd; ldddl, + .dddddddddddddddddxdddddddo:odddddo' cl;. + ........ :ddddddddddddddddOkdddddddddxdddddd;,dddd' + .;lddddddddddolcddddddddddddddddk0kddddddddOxdddddddddddo. + 'dddddddddddddddddxkdddddddddddddx00xdddddddxkddddddoodddd, + .odddddddddddddddddO0OxdddddddddddO0Oddddddddoccloddc':xxd; + .:dddddddddddddddddxO00kdddddddddx00kdddddo;'....',;,'dddc. .,;,. + .cdddxOkkxdddddddddxO00kddddddddO00ddddo,..cxxxl'...........;O0000: + .',,,,,,':ddddkO00OxddddddddxO00kdddddddOOddddc...l0000l............',o0c + cddddddddddddddddxO00kddddddddx000xdddddddddddo'...:dxo,..............'' + 'lddddddddddddddddxO0Odddddddddk00xdddddddddddc'...................... + 'lddddddddddddddddddddddddddddxkdddddddddddddl,.............':lc:;. + .:dxkkkxxddddddddddddddddddddocc:;;;;;;;::cll,............,:,... + ;clooooddxkOOOdddoc:;,'''',:ooc;'................................. + odddddddddddddl:,...........'................................... + cdddddddddl:'............................................. + .,coddoc,........................................... + .'........................................... + ............................................ + ................. ............. ........ + .................. .......... ....... + .......... ...... ........ ...... + ........ ..... ...... .... + ..... .... .... .. + + HH HH EEEE DDDDD GGGGG EEEE HH HH OOOO GGGGG + HH HH EE DD DD GG EE HH HH OO OO GG + HHHHHHH EEEEE DD DD GGGGGGG EEEEE HHHHHHH OO OO GGGGGGG + HH HH EE DD DD GG GG EE HH HH OO OO GG GG + HH HH EEEE DDDDD GGGGGG EEEE HH HH OOOO GGGGGG + + LL II NN NN UU UU XX XX + LL II NNN NN UU UU XXX + LL II NN NNN UU UU XXX + LLLLL II NN NN UUUU XX XX + + diff --git a/sensor-iso/config/hooks/normal/0900-setup-rc-local.hook.chroot b/sensor-iso/config/hooks/normal/0900-setup-rc-local.hook.chroot index a97c39031..6104811ba 100755 --- a/sensor-iso/config/hooks/normal/0900-setup-rc-local.hook.chroot +++ b/sensor-iso/config/hooks/normal/0900-setup-rc-local.hook.chroot @@ -22,6 +22,7 @@ if [ -f "$CAPTURE_STORAGE_FORMAT_FILE" ]; then fi # other sensor-specific initialization prior to starting capture/forwarding jobs +echo "Running Sensor initialization" > /dev/tty0 /usr/local/bin/sensor-init.sh # enable firewall @@ -35,6 +36,7 @@ fi systemctl mask ctrl-alt-del.target if [ ! -s /var/lib/aide/aide.db ]; then + echo "Running Aide init" > /dev/tty0 > /var/lib/aide/aide.db /usr/sbin/aideinit --yes --force fi diff --git a/sensor-iso/config/hooks/normal/0910-sensor-build.hook.chroot b/sensor-iso/config/hooks/normal/0910-sensor-build.hook.chroot index 144e70778..16f1a667b 100755 --- a/sensor-iso/config/hooks/normal/0910-sensor-build.hook.chroot +++ b/sensor-iso/config/hooks/normal/0910-sensor-build.hook.chroot @@ -12,6 +12,8 @@ export CXX="$CMAKE_CXX_COMPILER" export CXXFLAGS="-stdlib=libc++ -lc++abi" export PYTHONDONTWRITEBYTECODE=1 export PYTHONUNBUFFERED=1 +ARCH=$(dpkg --print-architecture) + cat > /etc/environment << EOF CMAKE_C_COMPILER="clang-14" @@ -189,9 +191,41 @@ mv ./yara-rules-src-hedgehog.tar.gz /opt/hedgehog_install_artifacts/ # capa cd /tmp -curl -o ./capa.zip "${GITHUB_API_CURL_ARGS[@]}" "$(curl "${GITHUB_API_CURL_ARGS[@]}" "$(curl "${GITHUB_API_CURL_ARGS[@]}" "$CAPA_RELEASE_URL" | jq '.assets_url' | tr -d '"')" | jq '.[] | select(.browser_download_url|test("-linux\\.zip$")) | .browser_download_url' | tr -d '"')" -unzip ./capa.zip -mv ./capa /usr/local/bin/capa + +capa_assets_url="$(curl "${GITHUB_API_CURL_ARGS[@]}" "$CAPA_RELEASE_URL" | jq '.assets_url' | tr -d '"')" + +if [ "${ARCH,,}" == 'arm64' ] || [ "${ARCH,,}" == 'arm' ]; then + #Build from source for ARM... + #Not sure if there is an easier way to get the latest release tag + capa_latest_ver=$(curl "${GITHUB_API_CURL_ARGS[@]}" "$capa_assets_url" | jq ".[] | select(.name | contains(\"-linux.zip\")) | .name") + # Retrieves strings like "capa-v6.1.0-linux.zip"; below trims out the x.x.x + capa_latest_ver=$(echo ${capa_latest_ver#*v}) + capa_latest_ver=$(echo ${capa_latest_ver%%-*}) + capa_latest_src_url="https://github.com/mandiant/capa/archive/refs/tags/v${capa_latest_ver}.zip" + + python3 -m venv capa + source capa/bin/activate + cd capa + + curl "${GITHUB_API_CURL_ARGS[@]}" "${capa_latest_src_url}" -o capa.zip + unzip -q capa.zip + + cd capa-${capa_latest_ver} + pip install -e .[build] + python scripts/cache-ruleset.py rules/ cache/ + pyinstaller .github/pyinstaller/pyinstaller.spec + mv dist/capa /usr/local/bin/capa + + deactivate +else + # Assume 64-bit Linux otherwise + capa_zip_url=$(curl "${GITHUB_API_CURL_ARGS[@]}" "$capa_assets_url" | jq ".[] | select(.browser_download_url | contains(\"-linux.zip\")) | .browser_download_url" | tr -d '"') + curl -o capa.zip "${GITHUB_API_CURL_ARGS[@]}" "${capa_zip_url}" + unzip ./capa.zip + mv ./capa /usr/local/bin/capa + +fi + chmod 755 /usr/local/bin/capa rm -rf /tmp/capa* @@ -200,19 +234,28 @@ cp /usr/local/bin/capa /opt/hedgehog_install_artifacts/ # yq cd /tmp -curl "${GITHUB_API_CURL_ARGS[@]}" "$(curl "${GITHUB_API_CURL_ARGS[@]}" "$(curl "${GITHUB_API_CURL_ARGS[@]}" "$YQ_RELEASE_URL" | jq '.assets_url' | tr -d '"')" | jq '.[] | select(.browser_download_url|test("linux_amd64\\.tar\\.gz$")) | .browser_download_url' | tr -d '"')" | tar zxvf - ./yq_linux_amd64 -mv ./yq_linux_amd64 /usr/bin/yq +yq_assets_url="$(curl "${GITHUB_API_CURL_ARGS[@]}" "$YQ_RELEASE_URL" | jq '.assets_url' | tr -d '"')" +yq_tar_url="$(curl "${GITHUB_API_CURL_ARGS[@]}" "$yq_assets_url" | jq ".[] | select(.browser_download_url | contains(\"yq_linux_${ARCH}.tar.gz\")) | .browser_download_url" | tr -d '"')" +curl "${GITHUB_API_CURL_ARGS[@]}" "${yq_tar_url}" | tar -xzf - ./yq_linux_${ARCH} + +mv ./yq_linux_${ARCH} /usr/bin/yq chmod 755 /usr/bin/yq ### # supercronic -curl -o /usr/local/bin/supercronic "${GITHUB_API_CURL_ARGS[@]}" "$(curl "${GITHUB_API_CURL_ARGS[@]}" "$(curl "${GITHUB_API_CURL_ARGS[@]}" "$SUPERCRONIC_RELEASE_URL" | jq '.assets_url' | tr -d '"')" | jq '.[] | select(.browser_download_url|test("-linux-amd64$")) | .browser_download_url' | tr -d '"')" +supercronic_assets_url="$(curl "${GITHUB_API_CURL_ARGS[@]}" "$SUPERCRONIC_RELEASE_URL" | jq '.assets_url' | tr -d '"')" +supercronic_bin_url="$(curl "${GITHUB_API_CURL_ARGS[@]}" "$supercronic_assets_url" | jq ".[] | select(.browser_download_url | contains(\"supercronic-linux-${ARCH}\")) | .browser_download_url" | tr -d '"')" +curl -o /usr/local/bin/supercronic "${GITHUB_API_CURL_ARGS[@]}" "$supercronic_bin_url" + chmod 755 /usr/local/bin/supercronic ### # croc cd /tmp -curl "${GITHUB_API_CURL_ARGS[@]}" "$(curl "${GITHUB_API_CURL_ARGS[@]}" "$(curl "${GITHUB_API_CURL_ARGS[@]}" "$CROC_RELEASE_URL" | jq '.assets_url' | tr -d '"')" | jq '.[] | select(.browser_download_url|test("Linux-64bit\\.tar\\.gz$")) | .browser_download_url' | tr -d '"')" | tar zxvf - croc +croc_assets_url="$(curl "${GITHUB_API_CURL_ARGS[@]}" "$CROC_RELEASE_URL" | jq '.assets_url' | tr -d '"')" +croc_tar_url="$(curl "${GITHUB_API_CURL_ARGS[@]}" "$croc_assets_url" | jq ".[] | select(.browser_download_url | contains(\"_Linux-${ARCH^^}.tar.gz\")) | .browser_download_url" | tr -d '"')" +curl "${GITHUB_API_CURL_ARGS[@]}" "${croc_tar_url}" | tar -xzf - croc + mv ./croc /usr/local/bin/croc chmod 755 /usr/local/bin/croc ### diff --git a/sensor-iso/config/hooks/normal/0991-security-performance.hook.chroot b/sensor-iso/config/hooks/normal/0991-security-performance.hook.chroot index 93afb4f60..3116aa3c8 100755 --- a/sensor-iso/config/hooks/normal/0991-security-performance.hook.chroot +++ b/sensor-iso/config/hooks/normal/0991-security-performance.hook.chroot @@ -2,21 +2,51 @@ # Copyright (c) 2024 Battelle Energy Alliance, LLC. All rights reserved. +ARCH="$(dpkg --print-architecture)" + # configure firewall sed -i "s/LOGLEVEL=.*/LOGLEVEL=off/" /etc/ufw/ufw.conf -/usr/sbin/ufw --force enable -/usr/sbin/ufw default deny incoming -/usr/sbin/ufw default allow outgoing -UFW_ALLOW_RULES=( - ntp - ssh - 9009:9013/tcp -) -for i in ${UFW_ALLOW_RULES[@]}; do - ufw allow "$i" -done -# will re-enable on boot -/usr/sbin/ufw --force disable + +if [ "${ARCH,,}" == 'arm' ] || [ "${ARCH,,}" == 'arm64' ]; then + # Known modules issue when building RPI images requires a 'restart' + # In arm builds, we're in a chroot reboot will be ignored + + run_once='/etc/init.d/run_once.sh' + + cat <<- 'EOF' > $run_once + #!/bin/bash + ufw=$(which ufw) + UFW_ALLOW_RULES=( ntp ssh 9009:9013/tcp ) + $ufw default deny incoming + $ufw default allow outgoing + for i in ${UFW_ALLOW_RULES[@]}; do $ufw allow $i; done + $ufw reload + + # Update initramfs to remove rpi-resize script from current initramfs on first boot + echo "Updating initramfs to remove rpi-resize script. This may take a few minutes..." > /dev/tty0 + /usr/sbin/update-initramfs -u + EOF + + echo "sed -i '\|$run_once|d' /etc/rc.local" >> $run_once + echo -e "rm -f $run_once\nexit 0" >> $run_once + chmod 755 $run_once + + sed -i "\|/bin/sh|a $run_once" /etc/rc.local + +else + + /usr/sbin/ufw --force enable + /usr/sbin/ufw default deny incoming + /usr/sbin/ufw default allow outgoing + UFW_ALLOW_RULES=( ntp ssh 9009:9013/tcp ) + + for i in ${UFW_ALLOW_RULES[@]}; do + ufw allow "$i" + done + + # will re-enable on boot + /usr/sbin/ufw --force disable +fi # performance parameters for networking, disk, etc. cat << 'EOF' >> /etc/sysctl.conf diff --git a/shared/bin/configure-interfaces.py b/shared/bin/configure-interfaces.py index 56eafb508..379b6b7a2 100755 --- a/shared/bin/configure-interfaces.py +++ b/shared/bin/configure-interfaces.py @@ -66,7 +66,7 @@ class Constants: MSG_CONFIG_SSH = ('SSH Authentication', 'Configure SSH authentication') MSG_CONFIG_STATIC_TITLE = 'Provide the values for static IP configuration' MSG_ERR_ROOT_REQUIRED = 'Elevated privileges required, run as root' - MSG_ERR_BAD_HOST = 'Invalid host or port' + MSG_ERR_BAD_HOST = 'Invalid method, host, or port' MSG_MESSAGE_DHCP = 'Configuring for DHCP provided address...' MSG_MESSAGE_ERROR = 'Error: {}\n\nPlease try again.' MSG_MESSAGE_STATIC = 'Configuring for static IP address...' @@ -298,30 +298,32 @@ def main(): elif time_sync_mode == Constants.TIME_SYNC_HTPDATE: # sync time via htpdate, run via cron + http_method = '' http_host = '' http_port = '' while True: # host/port for htpdate code, values = d.form( Constants.MSG_TIME_SYNC_HTPDATE_CONFIG, - [('Host', 1, 1, '', 1, 25, 30, 255), ('Port', 2, 1, '9200', 2, 25, 6, 5)], + [('Method', 1, 1, 'http', 1, 25, 30, 255), ('Host', 2, 1, '', 2, 25, 30, 255), ('Port', 3, 1, '9200', 3, 25, 6, 5)], ) values = [x.strip() for x in values] if (code == Dialog.CANCEL) or (code == Dialog.ESC): raise CancelledError - elif (len(values[0]) <= 0) or (len(values[1]) <= 0) or (not values[1].isnumeric()): + elif (len(values[0]) <= 0) or ((values[0].lower() != 'http') and (values[0].lower() != 'https')) or (len(values[1]) <= 0) or (len(values[2]) <= 0) or (not values[2].isnumeric()): code = d.msgbox(text=Constants.MSG_ERR_BAD_HOST) else: - http_host = values[0] - http_port = values[1] + http_method = values[0].lower() + http_host = values[1] + http_port = values[2] break # test with htpdate to see if we can connect ecode, test_output = run_subprocess( - f"{Constants.TIME_SYNC_HTPDATE_TEST_COMMAND} {http_host}:{http_port}" + f"{Constants.TIME_SYNC_HTPDATE_TEST_COMMAND} {http_method}://{http_host}:{http_port}" ) if ecode == 0: emsg_str = '\n'.join(test_output) @@ -353,13 +355,13 @@ def main(): f.write('PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin\n') f.write('\n') f.write( - f'*/{htpdate_interval} * * * * root {Constants.TIME_SYNC_HTPDATE_COMMAND} {http_host}:{http_port}\n' + f'*/{htpdate_interval} * * * * root {Constants.TIME_SYNC_HTPDATE_COMMAND} {http_method}://{http_host}:{http_port}\n' ) f.write('\n') # now actually do the sync "for real" one time (so we can get in sync before waiting for the interval) ecode, sync_output = run_subprocess( - f"{Constants.TIME_SYNC_HTPDATE_COMMAND} {http_host}:{http_port}" + f"{Constants.TIME_SYNC_HTPDATE_COMMAND} {http_method}://{http_host}:{http_port}" ) emsg_str = '\n'.join(sync_output) code = d.msgbox(text=f"{Constants.MSG_TIME_SYNC_CONFIG_SUCCESS if (ecode == 0) else ''}{emsg_str}") @@ -480,7 +482,7 @@ def main(): if code != Dialog.OK: raise CancelledError - # which interface are wer configuring? + # which interface are we configuring? selected_iface = tag # check if selected_iface already has entry in system configuration