From ecea0612db4deb947203fb3c4a5c030e0d065aa7 Mon Sep 17 00:00:00 2001 From: Daniel Gordon Date: Tue, 28 Jan 2025 14:12:17 +1100 Subject: [PATCH 1/4] Added a zerotier extension to talos --- Makefile | 2 +- network/vars.yaml | 2 ++ network/zerotier/manifest.yaml | 10 +++++++ network/zerotier/pkg.yaml | 50 +++++++++++++++++++++++++++++++ network/zerotier/vars.yaml | 1 + network/zerotier/zerotier.yaml | 55 ++++++++++++++++++++++++++++++++++ 6 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 network/zerotier/manifest.yaml create mode 100644 network/zerotier/pkg.yaml create mode 100644 network/zerotier/vars.yaml create mode 100644 network/zerotier/zerotier.yaml diff --git a/Makefile b/Makefile index 68bda405..07b9ce19 100644 --- a/Makefile +++ b/Makefile @@ -103,6 +103,7 @@ TARGETS += v4l-uvc-drivers TARGETS += vmtoolsd-guest-agent TARGETS += wasmedge TARGETS += xen-guest-agent +TARGETS += zerotier TARGETS += zfs NONFREE_TARGETS = nonfree-kmod-nvidia-lts NONFREE_TARGETS += nonfree-kmod-nvidia-production @@ -249,4 +250,3 @@ release-notes: $(ARTIFACTS) conformance: @docker pull $(CONFORMANCE_IMAGE) @docker run --rm -it -v $(PWD):/src -w /src $(CONFORMANCE_IMAGE) enforce - diff --git a/network/vars.yaml b/network/vars.yaml index d19e09ba..7f3ec613 100644 --- a/network/vars.yaml +++ b/network/vars.yaml @@ -6,3 +6,5 @@ LLDPD_VERSION: 1.0.19 CLOUDFLARED_VERSION: 2024.12.1 # renovate: datasource=github-releases extractVersion=^v(?.*)$ depName=slackhq/nebula NEBULA_VERSION: 1.9.5 + +ZEROTIER_VERSION: 1.14.2 diff --git a/network/zerotier/manifest.yaml b/network/zerotier/manifest.yaml new file mode 100644 index 00000000..e53ce0d8 --- /dev/null +++ b/network/zerotier/manifest.yaml @@ -0,0 +1,10 @@ +version: v1alpha1 +metadata: + name: zerotier + version: "$VERSION" + author: Hive Technologies + description: | + Connect your Talos cluster into a zerotier network + compatibility: + talos: + version: ">= v1.8.0" diff --git a/network/zerotier/pkg.yaml b/network/zerotier/pkg.yaml new file mode 100644 index 00000000..17308bba --- /dev/null +++ b/network/zerotier/pkg.yaml @@ -0,0 +1,50 @@ +name: zerotier +variant: alpine +shell: /toolchain/bin/bash +dependencies: + - stage: base +install: + - libstdc++ +steps: + - + sources: + - url: https://github.com/zerotier/ZeroTierOne/archive/refs/tags/{{ .ZEROTIER_VERSION }}.tar.gz + destination: zerotier.tar.gz + sha256: c2f64339fccf5148a7af089b896678d655fbfccac52ddce7714314a59d7bddbb + sha512: 9d022afcf81543d6ee938219a3712da846fe895b0fd65cfd6ec8ed173f0e208516031b6d2303ab42fd21806d9ba5ff6fdb0d850a0cbb32b268d53accb093cdf5 + env: + CXXFLAGS: '-Os -fstack-protector -std=c++17 -pthread' + LDFLAGS: '-static' + prepare: + - | + sed -i 's#$VERSION#{{ .VERSION }}#' /pkg/manifest.yaml + - | + tar -xzvf zerotier.tar.gz --strip-components=1 + build: + - | + sed -i '2i #include ' ext/prometheus-cpp-lite-1.0/core/include/prometheus/text_serializer.h + make ONE_THREAD=1 ZT_SSO_SUPPORTED=0 STATIC=1 -j $(nproc) + install: + - | + mkdir -p /rootfs/usr/local/lib/containers/zerotier/usr/local/bin/ + cp -pr zerotier-one /rootfs/usr/local/lib/containers/zerotier/usr/local/bin/ + chmod +x /rootfs/usr/local/lib/containers/zerotier/usr/local/bin/zerotier-* + cd /rootfs/usr/local/lib/containers/zerotier/usr/local/bin + ln -sf zerotier-one zerotier-cli + ln -sf zerotier-one zerotier-idtool + - | + mkdir -p /rootfs/usr/local/etc/containers/zerotier/usr/local/etc/zerotier/state + cp /pkg/zerotier.yaml /rootfs/usr/local/etc/containers/ + test: + - | + mkdir -p /extensions-validator-rootfs + cp -r /rootfs/ /extensions-validator-rootfs/rootfs + cp /pkg/manifest.yaml /extensions-validator-rootfs/manifest.yaml + /extensions-validator validate --rootfs=/extensions-validator-rootfs --pkg-name="${PKG_NAME}" + - | + [[ $(/rootfs/usr/local/lib/containers/zerotier/usr/local/bin/zerotier-cli -v) == *{{ .ZEROTIER_VERSION }}* ]] +finalize: + - from: /rootfs + to: /rootfs + - from: /pkg/manifest.yaml + to: / diff --git a/network/zerotier/vars.yaml b/network/zerotier/vars.yaml new file mode 100644 index 00000000..7d89fa50 --- /dev/null +++ b/network/zerotier/vars.yaml @@ -0,0 +1 @@ +VERSION: "{{ .ZEROTIER_VERSION }}" diff --git a/network/zerotier/zerotier.yaml b/network/zerotier/zerotier.yaml new file mode 100644 index 00000000..c92e4939 --- /dev/null +++ b/network/zerotier/zerotier.yaml @@ -0,0 +1,55 @@ +name: zerotier +depends: + - service: cri + - network: + - addresses + - connectivity + - etcfiles + - configuration: true +container: + entrypoint: /usr/local/bin/zerotier-one + args: + - /var/lib/zerotier-one + environment: + - PATH=/sbin:/usr/local/bin + security: + writeableRootfs: false + writeableSysfs: true + mounts: + # libs + - source: /lib + destination: /lib + type: bind + options: + - bind + - ro + # more libs + - source: /usr/lib + destination: /usr/lib + type: bind + options: + - bind + - ro + ## Required for zerotier. Ip addr and other commands + - source: /sbin + destination: /sbin + type: bind + options: + - bind + - ro + ## Zerotier needs to write to this to create the interfaces + - source: /dev/net/tun + destination: /dev/net/tun + type: bind + options: + - bind + - rw + ## Zerotier state. + - source: /var/lib/zerotier-one/ + destination: /var/lib/zerotier-one/ + type: bind + options: + - bind + - rw +restart: always +logToConsole: true From e49ae14c0cdeb9f428e90d75fecc104272c58dbe Mon Sep 17 00:00:00 2001 From: Daniel Gordon Date: Wed, 5 Feb 2025 20:53:04 +1100 Subject: [PATCH 2/4] Removed unnecessary debugging --- network/zerotier/zerotier.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/network/zerotier/zerotier.yaml b/network/zerotier/zerotier.yaml index c92e4939..0c75ddbd 100644 --- a/network/zerotier/zerotier.yaml +++ b/network/zerotier/zerotier.yaml @@ -51,5 +51,4 @@ container: options: - bind - rw -restart: always -logToConsole: true +restart: always \ No newline at end of file From af68fe2454fa0f62705f1d8b2d912e14d5eb720e Mon Sep 17 00:00:00 2001 From: Rob <178471500+rob-htl@users.noreply.github.com> Date: Fri, 21 Feb 2025 11:11:30 +1300 Subject: [PATCH 3/4] Add zerotier-wrapper Changes: * Add zerotier-wrapper as extension entrypoint, this wrapper: * handles PID cleanup and creation * works instead of the upstream zerotier symlinks for zerotier-cli and zerotier-idtool * takes configuration from ENV vars ZEROTIER_NETWORK and optionally ZEROTIER_IDENTITY_SECRET * ZEROTIER_NETWORK must be set, this is the ID of the network that zerotier attempts to join after start up (typically a manual process) * If ZEROTIER_IDENTITY_SECRET is optionally set this is written out and used by zerotier to authenticate as the node * If ZEROTIER_IDENTITY_SECRET is not set a new identity is created * logs the various lifecycle steps in a verbose way * Remove unused mounts (zerotier is compiled statically so /lib etc aren't needed) * Removes the aforementioned symlinks for zerotier-cli and zerotier-idtool as they aren't relevant without shell * Adds some usage docs in network/zerotier/README.md * Adds the renovate comment for zerotier version --- network/vars.yaml | 2 +- network/zerotier/README.md | 60 ++++++ network/zerotier/pkg.yaml | 15 +- network/zerotier/zerotier-wrapper/go.mod | 5 + network/zerotier/zerotier-wrapper/go.sum | 2 + network/zerotier/zerotier-wrapper/main.go | 232 +++++++++++++++++++++ network/zerotier/zerotier-wrapper/pkg.yaml | 29 +++ network/zerotier/zerotier.yaml | 36 +--- 8 files changed, 347 insertions(+), 34 deletions(-) create mode 100644 network/zerotier/README.md create mode 100644 network/zerotier/zerotier-wrapper/go.mod create mode 100644 network/zerotier/zerotier-wrapper/go.sum create mode 100644 network/zerotier/zerotier-wrapper/main.go create mode 100644 network/zerotier/zerotier-wrapper/pkg.yaml diff --git a/network/vars.yaml b/network/vars.yaml index 7f3ec613..047fd6c3 100644 --- a/network/vars.yaml +++ b/network/vars.yaml @@ -6,5 +6,5 @@ LLDPD_VERSION: 1.0.19 CLOUDFLARED_VERSION: 2024.12.1 # renovate: datasource=github-releases extractVersion=^v(?.*)$ depName=slackhq/nebula NEBULA_VERSION: 1.9.5 - +# renovate: datasource=github-releases depName=zerotier/ZeroTierOne ZEROTIER_VERSION: 1.14.2 diff --git a/network/zerotier/README.md b/network/zerotier/README.md new file mode 100644 index 00000000..b2aea511 --- /dev/null +++ b/network/zerotier/README.md @@ -0,0 +1,60 @@ +# ZeroTier + +Adds https://zerotier.com network interfaces as system extensions. +This means you can access your Talos nodes from machines you have configured +with ZeroTier, creating a secure overlay network. + +## Installation + +See [Installing Extensions](https://github.com/siderolabs/extensions#installing-extensions). + +## Usage + +Configure the extension via `ExtensionServiceConfig` document. + +```yaml +--- +apiVersion: v1alpha1 +kind: ExtensionServiceConfig +name: zerotier +environment: + - ZEROTIER_NETWORK= +``` + +Then apply the patch to your node's MachineConfigs + +```bash +talosctl patch mc -p @zerotier-config.yaml +``` + +You can then verify that it is in place with the following command + +```bash +talosctl get extensionserviceconfigs + +NODE NAMESPACE TYPE ID VERSION +mynode runtime ExtensionServiceConfig zerotier 1 +``` + +## Configuration + +The extension can be configured through environment variables: + +- `ZEROTIER_NETWORK`: The network ID to join (required) +- `ZEROTIER_IDENTITY_SECRET`: Optional pre-existing identity to use (format: "address:0:public:private") + +### Using an existing identity + +If you want to maintain the same ZeroTier identity across rebuilds or different nodes, you can specify an existing identity: + +```yaml +--- +apiVersion: v1alpha1 +kind: ExtensionServiceConfig +name: zerotier +environment: + - ZEROTIER_NETWORK= + - ZEROTIER_IDENTITY_SECRET= +``` + +If no identity is provided, a new one will be generated automatically. (You may need to authorize this node in your Zerotier network according to your network policies before it will recieve an IP address). diff --git a/network/zerotier/pkg.yaml b/network/zerotier/pkg.yaml index 17308bba..5a6ccdf3 100644 --- a/network/zerotier/pkg.yaml +++ b/network/zerotier/pkg.yaml @@ -1,9 +1,14 @@ name: zerotier variant: alpine -shell: /toolchain/bin/bash +shell: /bin/bash dependencies: - stage: base + from: / + to: /base-rootfs + - stage: zerotier-wrapper install: + - build-base + - linux-headers - libstdc++ steps: - @@ -28,10 +33,8 @@ steps: - | mkdir -p /rootfs/usr/local/lib/containers/zerotier/usr/local/bin/ cp -pr zerotier-one /rootfs/usr/local/lib/containers/zerotier/usr/local/bin/ + cp -pr /rootfs/usr/local/bin/zerotier-wrapper /rootfs/usr/local/lib/containers/zerotier/usr/local/bin/ chmod +x /rootfs/usr/local/lib/containers/zerotier/usr/local/bin/zerotier-* - cd /rootfs/usr/local/lib/containers/zerotier/usr/local/bin - ln -sf zerotier-one zerotier-cli - ln -sf zerotier-one zerotier-idtool - | mkdir -p /rootfs/usr/local/etc/containers/zerotier/usr/local/etc/zerotier/state cp /pkg/zerotier.yaml /rootfs/usr/local/etc/containers/ @@ -40,9 +43,9 @@ steps: mkdir -p /extensions-validator-rootfs cp -r /rootfs/ /extensions-validator-rootfs/rootfs cp /pkg/manifest.yaml /extensions-validator-rootfs/manifest.yaml - /extensions-validator validate --rootfs=/extensions-validator-rootfs --pkg-name="${PKG_NAME}" + /base-rootfs/extensions-validator validate --rootfs=/extensions-validator-rootfs --pkg-name="${PKG_NAME}" - | - [[ $(/rootfs/usr/local/lib/containers/zerotier/usr/local/bin/zerotier-cli -v) == *{{ .ZEROTIER_VERSION }}* ]] + [[ $(/rootfs/usr/local/lib/containers/zerotier/usr/local/bin/zerotier-one -v) == *{{ .ZEROTIER_VERSION }}* ]] finalize: - from: /rootfs to: /rootfs diff --git a/network/zerotier/zerotier-wrapper/go.mod b/network/zerotier/zerotier-wrapper/go.mod new file mode 100644 index 00000000..4821dca4 --- /dev/null +++ b/network/zerotier/zerotier-wrapper/go.mod @@ -0,0 +1,5 @@ +module zerotier-wrapper + +go 1.23.0 + +require golang.org/x/sys v0.30.0 diff --git a/network/zerotier/zerotier-wrapper/go.sum b/network/zerotier/zerotier-wrapper/go.sum new file mode 100644 index 00000000..241f4caf --- /dev/null +++ b/network/zerotier/zerotier-wrapper/go.sum @@ -0,0 +1,2 @@ +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/network/zerotier/zerotier-wrapper/main.go b/network/zerotier/zerotier-wrapper/main.go new file mode 100644 index 00000000..6ef0b54a --- /dev/null +++ b/network/zerotier/zerotier-wrapper/main.go @@ -0,0 +1,232 @@ +package main + +import ( + "bytes" + "errors" + "fmt" + "log" + "os" + "os/exec" + "os/signal" + "strconv" + "strings" + "time" + + "golang.org/x/sys/unix" +) + +const ( + zerotierPath = "/var/lib/zerotier-one" + identityPath = "/var/lib/zerotier-one/identity.secret" + identityPubPath = "/var/lib/zerotier-one/identity.public" + pidFile = "/var/lib/zerotier-one/zerotier-one.pid" + zerotierBinPath = "/usr/local/bin/zerotier-one" +) + +func main() { + log.Printf("zerotier-wrapper: initializing...") + + // Ensure the ZeroTier state directory exists. + if err := os.MkdirAll(zerotierPath, 0755); err != nil { + log.Fatalf("failed to create state directory: %v", err) + } + + // Ensure identity configuration. + identitySource, err := ensureIdentity() + if err != nil { + log.Fatalf("identity configuration failed: %v", err) + } + log.Printf("identity configured (source: %s)", identitySource) + + // Cleanup any existing zerotier-one process. + if err := cleanupProcess(); err != nil { + log.Fatalf("process cleanup failed: %v", err) + } + + // If ZEROTIER_NETWORK env var is set, join network using zerotier-one -q. + if network := os.Getenv("ZEROTIER_NETWORK"); network != "" { + log.Printf("will join network %s after startup", network) + go func() { + time.Sleep(2 * time.Second) + if err := joinNetwork(network); err != nil { + log.Printf("failed to join network: %v", err) + } else { + log.Printf("joined network %s", network) + } + }() + } + + // Start zerotier-one process. + cmd := exec.Command(zerotierBinPath, "-U", zerotierPath) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + if err := cmd.Start(); err != nil { + log.Fatalf("error starting zerotier-one: %v", err) + } + + // Write the PID file. + pidStr := strconv.Itoa(cmd.Process.Pid) + if err := os.WriteFile(pidFile, []byte(pidStr), 0644); err != nil { + log.Printf("failed to write PID file: %v", err) + } + + // Forward termination signals to the zerotier-one process. + ch := make(chan os.Signal, 1) + signal.Notify(ch, unix.SIGINT, unix.SIGTERM) + sig := <-ch + log.Printf("received signal %v, forwarding to zerotier-one process", sig) + if err := cmd.Process.Signal(sig); err != nil { + log.Fatalf("error sending signal to zerotier-one: %v", err) + } + + if err := cmd.Wait(); err != nil { + log.Fatalf("zerotier-one exited with error: %v", err) + } +} + +// ensureIdentity checks for an existing identity file, validates it if found, +// or else uses the identity from the ZEROTIER_IDENTITY_SECRET environment variable (after validation) +// or generates a new one using "zerotier-one -i generate". +func ensureIdentity() (string, error) { + // If the identity file exists, validate its contents. + if _, err := os.Stat(identityPath); err == nil { + data, err := os.ReadFile(identityPath) + if err != nil { + return "", fmt.Errorf("failed to read existing identity: %w", err) + } + identity := strings.TrimSpace(string(data)) + log.Printf("found existing identity at %s, validating...", identityPath) + if err := validateIdentity(identity); err != nil { + return "", fmt.Errorf("existing identity failed validation: %w", err) + } + log.Printf("existing identity validated") + return "existing", nil + } else if !errors.Is(err, os.ErrNotExist) { + return "", fmt.Errorf("failed to stat identity file: %w", err) + } + + // Check for identity in environment. + if identity := os.Getenv("ZEROTIER_IDENTITY_SECRET"); identity != "" { + log.Printf("found identity in ZEROTIER_IDENTITY_SECRET environment variable, validating...") + if err := validateIdentity(identity); err != nil { + return "", fmt.Errorf("environment identity invalid: %w", err) + } + log.Printf("environment identity validated") + if err := writeIdentity(identity); err != nil { + return "", fmt.Errorf("failed to write identity from environment: %w", err) + } + return "environment", nil + } + + // Generate a new identity using "zerotier-one -i generate". + log.Printf("generating new identity using zerotier-one -i generate") + cmd := exec.Command(zerotierBinPath, "-i", "generate") + var out bytes.Buffer + cmd.Stdout = &out + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + return "", fmt.Errorf("failed to generate identity: %w", err) + } + identity := strings.TrimSpace(out.String()) + if err := validateIdentity(identity); err != nil { + return "", fmt.Errorf("generated identity failed validation: %w", err) + } + if err := writeIdentity(identity); err != nil { + return "", fmt.Errorf("failed to write generated identity: %w", err) + } + return "generated", nil +} + +// validateIdentity runs "zerotier-one -i validate " to ensure the identity is valid. +func validateIdentity(identity string) error { + cmd := exec.Command(zerotierBinPath, "-i", "validate", identity) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + return fmt.Errorf("identity validation failed: %w", err) + } + return nil +} + +// writeIdentity writes the complete identity string (all four parts) to identity.secret, +// while writing only the first three parts (separated by ':') to identity.public. +func writeIdentity(identity string) error { + parts := strings.Split(identity, ":") + if len(parts) != 4 { + return fmt.Errorf("invalid identity format: expected 4 parts, got %d", len(parts)) + } + + // Write the secret identity file with the full identity. + if err := os.WriteFile(identityPath, []byte(identity), 0600); err != nil { + return fmt.Errorf("failed to write secret identity: %w", err) + } + log.Printf("wrote secret identity to %s", identityPath) + + // Write the public identity file with only the first 3 parts. + public := strings.Join(parts[:3], ":") + if err := os.WriteFile(identityPubPath, []byte(public), 0644); err != nil { + return fmt.Errorf("failed to write public identity: %w", err) + } + log.Printf("wrote public identity to %s", identityPubPath) + + return nil +} + +// cleanupProcess checks for an existing PID file; if found, it kills the process and removes the file. +func cleanupProcess() error { + if _, err := os.Stat(pidFile); err == nil { + pid, err := getProcessId() + if err != nil { + return fmt.Errorf("error reading pid file: %w", err) + } + if err := killProcess(pid); err != nil { + return fmt.Errorf("error killing process: %w", err) + } + if err := os.Remove(pidFile); err != nil { + return fmt.Errorf("error removing pid file: %w", err) + } + log.Printf("cleaned up existing process (PID %d)", pid) + } else if !errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("failed to stat pid file: %w", err) + } else { + log.Printf("no PID file found, no existing process to clean up") + } + return nil +} + +func getProcessId() (int, error) { + pidData, err := os.ReadFile(pidFile) + if err != nil { + return 0, err + } + pidData = bytes.TrimRight(pidData, "\n") + pid, err := strconv.Atoi(string(pidData)) + if err != nil { + return 0, err + } + return pid, nil +} + +func killProcess(pid int) error { + p, err := os.FindProcess(pid) + if err != nil { + return err + } + if err := p.Kill(); err != nil && !errors.Is(err, os.ErrProcessDone) { + return err + } + return nil +} + +// joinNetwork uses "zerotier-one -q join " to join the specified network. +func joinNetwork(network string) error { + log.Printf("attempting to join network %s", network) + cmd := exec.Command(zerotierBinPath, "-q", "join", network) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + return fmt.Errorf("join network failed: %w", err) + } + return nil +} diff --git a/network/zerotier/zerotier-wrapper/pkg.yaml b/network/zerotier/zerotier-wrapper/pkg.yaml new file mode 100644 index 00000000..ed5d0847 --- /dev/null +++ b/network/zerotier/zerotier-wrapper/pkg.yaml @@ -0,0 +1,29 @@ +name: zerotier-wrapper +variant: scratch +shell: /bin/bash +dependencies: + - stage: base +steps: + - env: + GOPATH: /tmp/go + - cachePaths: + - /.cache/go-build + - /tmp/go/pkg + - network: default + prepare: + - | + cp -r /pkg/* . + - | + go mod download + - network: none + build: + - | + CGO_ENABLED=0 go build -o zerotier-wrapper main.go + install: + - | + mkdir -p /rootfs/usr/local/bin + + cp zerotier-wrapper /rootfs/usr/local/bin/zerotier-wrapper +finalize: + - from: /rootfs + to: /rootfs \ No newline at end of file diff --git a/network/zerotier/zerotier.yaml b/network/zerotier/zerotier.yaml index 0c75ddbd..43f55a19 100644 --- a/network/zerotier/zerotier.yaml +++ b/network/zerotier/zerotier.yaml @@ -7,48 +7,30 @@ depends: - etcfiles - configuration: true container: - entrypoint: /usr/local/bin/zerotier-one - args: - - /var/lib/zerotier-one - environment: - - PATH=/sbin:/usr/local/bin + entrypoint: /usr/local/bin/zerotier-wrapper security: writeableRootfs: false writeableSysfs: true mounts: - # libs - - source: /lib - destination: /lib + # Zerotier binaries + - source: /usr/local/lib/containers/zerotier/usr/local/bin + destination: /usr/local/bin type: bind options: - bind - ro - # more libs - - source: /usr/lib - destination: /usr/lib - type: bind - options: - - bind - - ro - ## Required for zerotier. Ip addr and other commands - - source: /sbin - destination: /sbin - type: bind - options: - - bind - - ro - ## Zerotier needs to write to this to create the interfaces + # Zerotier needs to write to this to create the interfaces - source: /dev/net/tun destination: /dev/net/tun type: bind options: - bind - rw - ## Zerotier state. - - source: /var/lib/zerotier-one/ - destination: /var/lib/zerotier-one/ + # Zerotier state + - source: /var/lib/zerotier-one + destination: /var/lib/zerotier-one type: bind options: - bind - rw -restart: always \ No newline at end of file +restart: always From 2f70a19d033e2a64df30f716fe805c989d23c4ac Mon Sep 17 00:00:00 2001 From: Rob <178471500+rob-htl@users.noreply.github.com> Date: Mon, 24 Feb 2025 12:24:01 +1300 Subject: [PATCH 4/4] Zerotier: use toolchain to build, don't manage zammad PID --- network/zerotier/pkg.yaml | 12 +-- network/zerotier/zerotier-wrapper/main.go | 119 +++++----------------- network/zerotier/zerotier.yaml | 7 -- 3 files changed, 28 insertions(+), 110 deletions(-) diff --git a/network/zerotier/pkg.yaml b/network/zerotier/pkg.yaml index 5a6ccdf3..ea42da23 100644 --- a/network/zerotier/pkg.yaml +++ b/network/zerotier/pkg.yaml @@ -1,15 +1,9 @@ name: zerotier -variant: alpine -shell: /bin/bash +variant: scratch +shell: /bin/sh dependencies: - stage: base - from: / - to: /base-rootfs - stage: zerotier-wrapper -install: - - build-base - - linux-headers - - libstdc++ steps: - sources: @@ -43,7 +37,7 @@ steps: mkdir -p /extensions-validator-rootfs cp -r /rootfs/ /extensions-validator-rootfs/rootfs cp /pkg/manifest.yaml /extensions-validator-rootfs/manifest.yaml - /base-rootfs/extensions-validator validate --rootfs=/extensions-validator-rootfs --pkg-name="${PKG_NAME}" + /extensions-validator validate --rootfs=/extensions-validator-rootfs --pkg-name="${PKG_NAME}" - | [[ $(/rootfs/usr/local/lib/containers/zerotier/usr/local/bin/zerotier-one -v) == *{{ .ZEROTIER_VERSION }}* ]] finalize: diff --git a/network/zerotier/zerotier-wrapper/main.go b/network/zerotier/zerotier-wrapper/main.go index 6ef0b54a..586a2338 100644 --- a/network/zerotier/zerotier-wrapper/main.go +++ b/network/zerotier/zerotier-wrapper/main.go @@ -7,10 +7,8 @@ import ( "log" "os" "os/exec" - "os/signal" - "strconv" + "path/filepath" "strings" - "time" "golang.org/x/sys/unix" ) @@ -19,7 +17,6 @@ const ( zerotierPath = "/var/lib/zerotier-one" identityPath = "/var/lib/zerotier-one/identity.secret" identityPubPath = "/var/lib/zerotier-one/identity.public" - pidFile = "/var/lib/zerotier-one/zerotier-one.pid" zerotierBinPath = "/usr/local/bin/zerotier-one" ) @@ -38,50 +35,18 @@ func main() { } log.Printf("identity configured (source: %s)", identitySource) - // Cleanup any existing zerotier-one process. - if err := cleanupProcess(); err != nil { - log.Fatalf("process cleanup failed: %v", err) - } - - // If ZEROTIER_NETWORK env var is set, join network using zerotier-one -q. + // If ZEROTIER_NETWORK env var is set, join the network. if network := os.Getenv("ZEROTIER_NETWORK"); network != "" { - log.Printf("will join network %s after startup", network) - go func() { - time.Sleep(2 * time.Second) - if err := joinNetwork(network); err != nil { - log.Printf("failed to join network: %v", err) - } else { - log.Printf("joined network %s", network) - } - }() + log.Printf("joining network %s", network) + if err := joinNetwork(network); err != nil { + log.Fatalf("failed to join network: %v", err) + } + log.Printf("joined network %s", network) } // Start zerotier-one process. - cmd := exec.Command(zerotierBinPath, "-U", zerotierPath) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - if err := cmd.Start(); err != nil { - log.Fatalf("error starting zerotier-one: %v", err) - } - - // Write the PID file. - pidStr := strconv.Itoa(cmd.Process.Pid) - if err := os.WriteFile(pidFile, []byte(pidStr), 0644); err != nil { - log.Printf("failed to write PID file: %v", err) - } - - // Forward termination signals to the zerotier-one process. - ch := make(chan os.Signal, 1) - signal.Notify(ch, unix.SIGINT, unix.SIGTERM) - sig := <-ch - log.Printf("received signal %v, forwarding to zerotier-one process", sig) - if err := cmd.Process.Signal(sig); err != nil { - log.Fatalf("error sending signal to zerotier-one: %v", err) - } - - if err := cmd.Wait(); err != nil { - log.Fatalf("zerotier-one exited with error: %v", err) + if err := unix.Exec(zerotierBinPath, []string{zerotierBinPath, "-U", zerotierPath}, os.Environ()); err != nil { + log.Fatalf("error executing zerotier-one: %v", err) } } @@ -173,60 +138,26 @@ func writeIdentity(identity string) error { return nil } -// cleanupProcess checks for an existing PID file; if found, it kills the process and removes the file. -func cleanupProcess() error { - if _, err := os.Stat(pidFile); err == nil { - pid, err := getProcessId() - if err != nil { - return fmt.Errorf("error reading pid file: %w", err) - } - if err := killProcess(pid); err != nil { - return fmt.Errorf("error killing process: %w", err) - } - if err := os.Remove(pidFile); err != nil { - return fmt.Errorf("error removing pid file: %w", err) - } - log.Printf("cleaned up existing process (PID %d)", pid) - } else if !errors.Is(err, os.ErrNotExist) { - return fmt.Errorf("failed to stat pid file: %w", err) - } else { - log.Printf("no PID file found, no existing process to clean up") - } - return nil -} - -func getProcessId() (int, error) { - pidData, err := os.ReadFile(pidFile) - if err != nil { - return 0, err - } - pidData = bytes.TrimRight(pidData, "\n") - pid, err := strconv.Atoi(string(pidData)) - if err != nil { - return 0, err +// joinNetwork creates a config file for the relevant network if it doesn't already exist. +// This is typically done while the service is running via `zerotier-one -q join `, +// however this just creates an empty file with the network name, so we do that instead. +func joinNetwork(network string) error { + networkConfDir := filepath.Join(zerotierPath, "networks.d") + if err := os.MkdirAll(networkConfDir, 0755); err != nil { + return fmt.Errorf("failed to create networks.d directory: %w", err) } - return pid, nil -} + networkConfFile := filepath.Join(networkConfDir, network+".conf") -func killProcess(pid int) error { - p, err := os.FindProcess(pid) + file, err := os.OpenFile(networkConfFile, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644) if err != nil { - return err - } - if err := p.Kill(); err != nil && !errors.Is(err, os.ErrProcessDone) { - return err + if os.IsExist(err) { + log.Printf("network configuration file %s already exists", networkConfFile) + return nil + } + return fmt.Errorf("failed to create network conf file: %w", err) } - return nil -} + defer file.Close() -// joinNetwork uses "zerotier-one -q join " to join the specified network. -func joinNetwork(network string) error { - log.Printf("attempting to join network %s", network) - cmd := exec.Command(zerotierBinPath, "-q", "join", network) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - if err := cmd.Run(); err != nil { - return fmt.Errorf("join network failed: %w", err) - } + log.Printf("created network configuration file %s", networkConfFile) return nil } diff --git a/network/zerotier/zerotier.yaml b/network/zerotier/zerotier.yaml index 43f55a19..5274bdc3 100644 --- a/network/zerotier/zerotier.yaml +++ b/network/zerotier/zerotier.yaml @@ -12,13 +12,6 @@ container: writeableRootfs: false writeableSysfs: true mounts: - # Zerotier binaries - - source: /usr/local/lib/containers/zerotier/usr/local/bin - destination: /usr/local/bin - type: bind - options: - - bind - - ro # Zerotier needs to write to this to create the interfaces - source: /dev/net/tun destination: /dev/net/tun