mirror of
https://github.com/siderolabs/omni.git
synced 2026-05-05 06:36:12 +02:00
feat: rewrite Omni config management
Some checks are pending
default / default (push) Waiting to run
default / e2e-backups (push) Blocked by required conditions
default / e2e-forced-removal (push) Blocked by required conditions
default / e2e-scaling (push) Blocked by required conditions
default / e2e-short (push) Blocked by required conditions
default / e2e-short-secureboot (push) Blocked by required conditions
default / e2e-templates (push) Blocked by required conditions
default / e2e-upgrades (push) Blocked by required conditions
default / e2e-workload-proxy (push) Blocked by required conditions
Some checks are pending
default / default (push) Waiting to run
default / e2e-backups (push) Blocked by required conditions
default / e2e-forced-removal (push) Blocked by required conditions
default / e2e-scaling (push) Blocked by required conditions
default / e2e-short (push) Blocked by required conditions
default / e2e-short-secureboot (push) Blocked by required conditions
default / e2e-templates (push) Blocked by required conditions
default / e2e-upgrades (push) Blocked by required conditions
default / e2e-workload-proxy (push) Blocked by required conditions
Omni can now be configured via a config file instead of the command line flags. The flags `--config-path` will now read the config provided in the YAML format. The config structure was completely changed. It was not public before, so it's fine to ignore backward compatibility. The command line flags were not changed. Signed-off-by: Artem Chernyshev <artem.chernyshev@talos-systems.com>
This commit is contained in:
parent
05aad4d86f
commit
ccd55cc8fb
32
.github/workflows/ci.yaml
vendored
32
.github/workflows/ci.yaml
vendored
@ -1,6 +1,6 @@
|
||||
# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
|
||||
#
|
||||
# Generated on 2025-06-02T21:18:31Z by kres 99b55ad-dirty.
|
||||
# Generated on 2025-06-06T17:20:38Z by kres fc6afbe-dirty.
|
||||
|
||||
name: default
|
||||
concurrency:
|
||||
@ -34,7 +34,7 @@ jobs:
|
||||
steps:
|
||||
- name: gather-system-info
|
||||
id: system-info
|
||||
uses: kenchan0130/actions-system-info@v1.3.0
|
||||
uses: kenchan0130/actions-system-info@v1.3.1
|
||||
continue-on-error: true
|
||||
- name: print-system-info
|
||||
run: |
|
||||
@ -154,21 +154,21 @@ jobs:
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
- name: image-integration-test
|
||||
- name: image-omni-integration-test
|
||||
run: |
|
||||
make image-integration-test
|
||||
- name: push-integration-test
|
||||
make image-omni-integration-test
|
||||
- name: push-omni-integration-test
|
||||
if: github.event_name != 'pull_request'
|
||||
env:
|
||||
PUSH: "true"
|
||||
run: |
|
||||
make image-integration-test
|
||||
- name: push-integration-test-latest
|
||||
make image-omni-integration-test
|
||||
- name: push-omni-integration-test-latest
|
||||
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
|
||||
env:
|
||||
PUSH: "true"
|
||||
run: |
|
||||
make image-integration-test IMAGE_TAG=latest
|
||||
make image-omni-integration-test IMAGE_TAG=latest
|
||||
- name: run-integration-test
|
||||
if: github.event_name == 'pull_request'
|
||||
env:
|
||||
@ -246,7 +246,7 @@ jobs:
|
||||
steps:
|
||||
- name: gather-system-info
|
||||
id: system-info
|
||||
uses: kenchan0130/actions-system-info@v1.3.0
|
||||
uses: kenchan0130/actions-system-info@v1.3.1
|
||||
continue-on-error: true
|
||||
- name: print-system-info
|
||||
run: |
|
||||
@ -325,7 +325,7 @@ jobs:
|
||||
steps:
|
||||
- name: gather-system-info
|
||||
id: system-info
|
||||
uses: kenchan0130/actions-system-info@v1.3.0
|
||||
uses: kenchan0130/actions-system-info@v1.3.1
|
||||
continue-on-error: true
|
||||
- name: print-system-info
|
||||
run: |
|
||||
@ -404,7 +404,7 @@ jobs:
|
||||
steps:
|
||||
- name: gather-system-info
|
||||
id: system-info
|
||||
uses: kenchan0130/actions-system-info@v1.3.0
|
||||
uses: kenchan0130/actions-system-info@v1.3.1
|
||||
continue-on-error: true
|
||||
- name: print-system-info
|
||||
run: |
|
||||
@ -483,7 +483,7 @@ jobs:
|
||||
steps:
|
||||
- name: gather-system-info
|
||||
id: system-info
|
||||
uses: kenchan0130/actions-system-info@v1.3.0
|
||||
uses: kenchan0130/actions-system-info@v1.3.1
|
||||
continue-on-error: true
|
||||
- name: print-system-info
|
||||
run: |
|
||||
@ -562,7 +562,7 @@ jobs:
|
||||
steps:
|
||||
- name: gather-system-info
|
||||
id: system-info
|
||||
uses: kenchan0130/actions-system-info@v1.3.0
|
||||
uses: kenchan0130/actions-system-info@v1.3.1
|
||||
continue-on-error: true
|
||||
- name: print-system-info
|
||||
run: |
|
||||
@ -642,7 +642,7 @@ jobs:
|
||||
steps:
|
||||
- name: gather-system-info
|
||||
id: system-info
|
||||
uses: kenchan0130/actions-system-info@v1.3.0
|
||||
uses: kenchan0130/actions-system-info@v1.3.1
|
||||
continue-on-error: true
|
||||
- name: print-system-info
|
||||
run: |
|
||||
@ -721,7 +721,7 @@ jobs:
|
||||
steps:
|
||||
- name: gather-system-info
|
||||
id: system-info
|
||||
uses: kenchan0130/actions-system-info@v1.3.0
|
||||
uses: kenchan0130/actions-system-info@v1.3.1
|
||||
continue-on-error: true
|
||||
- name: print-system-info
|
||||
run: |
|
||||
@ -800,7 +800,7 @@ jobs:
|
||||
steps:
|
||||
- name: gather-system-info
|
||||
id: system-info
|
||||
uses: kenchan0130/actions-system-info@v1.3.0
|
||||
uses: kenchan0130/actions-system-info@v1.3.1
|
||||
continue-on-error: true
|
||||
- name: print-system-info
|
||||
run: |
|
||||
|
||||
4
.github/workflows/e2e-backups-cron.yaml
vendored
4
.github/workflows/e2e-backups-cron.yaml
vendored
@ -1,6 +1,6 @@
|
||||
# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
|
||||
#
|
||||
# Generated on 2025-06-02T21:18:31Z by kres 99b55ad-dirty.
|
||||
# Generated on 2025-06-06T17:20:38Z by kres fc6afbe-dirty.
|
||||
|
||||
name: e2e-backups-cron
|
||||
concurrency:
|
||||
@ -17,7 +17,7 @@ jobs:
|
||||
steps:
|
||||
- name: gather-system-info
|
||||
id: system-info
|
||||
uses: kenchan0130/actions-system-info@v1.3.0
|
||||
uses: kenchan0130/actions-system-info@v1.3.1
|
||||
continue-on-error: true
|
||||
- name: print-system-info
|
||||
run: |
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
|
||||
#
|
||||
# Generated on 2025-06-02T21:18:31Z by kres 99b55ad-dirty.
|
||||
# Generated on 2025-06-06T17:20:38Z by kres fc6afbe-dirty.
|
||||
|
||||
name: e2e-forced-removal-cron
|
||||
concurrency:
|
||||
@ -17,7 +17,7 @@ jobs:
|
||||
steps:
|
||||
- name: gather-system-info
|
||||
id: system-info
|
||||
uses: kenchan0130/actions-system-info@v1.3.0
|
||||
uses: kenchan0130/actions-system-info@v1.3.1
|
||||
continue-on-error: true
|
||||
- name: print-system-info
|
||||
run: |
|
||||
|
||||
4
.github/workflows/e2e-scaling-cron.yaml
vendored
4
.github/workflows/e2e-scaling-cron.yaml
vendored
@ -1,6 +1,6 @@
|
||||
# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
|
||||
#
|
||||
# Generated on 2025-06-02T21:18:31Z by kres 99b55ad-dirty.
|
||||
# Generated on 2025-06-06T17:20:38Z by kres fc6afbe-dirty.
|
||||
|
||||
name: e2e-scaling-cron
|
||||
concurrency:
|
||||
@ -17,7 +17,7 @@ jobs:
|
||||
steps:
|
||||
- name: gather-system-info
|
||||
id: system-info
|
||||
uses: kenchan0130/actions-system-info@v1.3.0
|
||||
uses: kenchan0130/actions-system-info@v1.3.1
|
||||
continue-on-error: true
|
||||
- name: print-system-info
|
||||
run: |
|
||||
|
||||
4
.github/workflows/e2e-short-cron.yaml
vendored
4
.github/workflows/e2e-short-cron.yaml
vendored
@ -1,6 +1,6 @@
|
||||
# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
|
||||
#
|
||||
# Generated on 2025-06-02T21:18:31Z by kres 99b55ad-dirty.
|
||||
# Generated on 2025-06-06T17:20:38Z by kres fc6afbe-dirty.
|
||||
|
||||
name: e2e-short-cron
|
||||
concurrency:
|
||||
@ -17,7 +17,7 @@ jobs:
|
||||
steps:
|
||||
- name: gather-system-info
|
||||
id: system-info
|
||||
uses: kenchan0130/actions-system-info@v1.3.0
|
||||
uses: kenchan0130/actions-system-info@v1.3.1
|
||||
continue-on-error: true
|
||||
- name: print-system-info
|
||||
run: |
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
|
||||
#
|
||||
# Generated on 2025-06-02T21:18:31Z by kres 99b55ad-dirty.
|
||||
# Generated on 2025-06-06T17:20:38Z by kres fc6afbe-dirty.
|
||||
|
||||
name: e2e-short-secureboot-cron
|
||||
concurrency:
|
||||
@ -17,7 +17,7 @@ jobs:
|
||||
steps:
|
||||
- name: gather-system-info
|
||||
id: system-info
|
||||
uses: kenchan0130/actions-system-info@v1.3.0
|
||||
uses: kenchan0130/actions-system-info@v1.3.1
|
||||
continue-on-error: true
|
||||
- name: print-system-info
|
||||
run: |
|
||||
|
||||
4
.github/workflows/e2e-templates-cron.yaml
vendored
4
.github/workflows/e2e-templates-cron.yaml
vendored
@ -1,6 +1,6 @@
|
||||
# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
|
||||
#
|
||||
# Generated on 2025-06-02T21:18:31Z by kres 99b55ad-dirty.
|
||||
# Generated on 2025-06-06T17:20:38Z by kres fc6afbe-dirty.
|
||||
|
||||
name: e2e-templates-cron
|
||||
concurrency:
|
||||
@ -17,7 +17,7 @@ jobs:
|
||||
steps:
|
||||
- name: gather-system-info
|
||||
id: system-info
|
||||
uses: kenchan0130/actions-system-info@v1.3.0
|
||||
uses: kenchan0130/actions-system-info@v1.3.1
|
||||
continue-on-error: true
|
||||
- name: print-system-info
|
||||
run: |
|
||||
|
||||
4
.github/workflows/e2e-upgrades-cron.yaml
vendored
4
.github/workflows/e2e-upgrades-cron.yaml
vendored
@ -1,6 +1,6 @@
|
||||
# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
|
||||
#
|
||||
# Generated on 2025-06-02T21:18:31Z by kres 99b55ad-dirty.
|
||||
# Generated on 2025-06-06T17:20:38Z by kres fc6afbe-dirty.
|
||||
|
||||
name: e2e-upgrades-cron
|
||||
concurrency:
|
||||
@ -17,7 +17,7 @@ jobs:
|
||||
steps:
|
||||
- name: gather-system-info
|
||||
id: system-info
|
||||
uses: kenchan0130/actions-system-info@v1.3.0
|
||||
uses: kenchan0130/actions-system-info@v1.3.1
|
||||
continue-on-error: true
|
||||
- name: print-system-info
|
||||
run: |
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
|
||||
#
|
||||
# Generated on 2025-06-02T21:18:31Z by kres 99b55ad-dirty.
|
||||
# Generated on 2025-06-06T17:20:38Z by kres fc6afbe-dirty.
|
||||
|
||||
name: e2e-workload-proxy-cron
|
||||
concurrency:
|
||||
@ -17,7 +17,7 @@ jobs:
|
||||
steps:
|
||||
- name: gather-system-info
|
||||
id: system-info
|
||||
uses: kenchan0130/actions-system-info@v1.3.0
|
||||
uses: kenchan0130/actions-system-info@v1.3.1
|
||||
continue-on-error: true
|
||||
- name: print-system-info
|
||||
run: |
|
||||
|
||||
4
.github/workflows/helm.yaml
vendored
4
.github/workflows/helm.yaml
vendored
@ -1,6 +1,6 @@
|
||||
# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
|
||||
#
|
||||
# Generated on 2025-06-02T21:18:31Z by kres 99b55ad-dirty.
|
||||
# Generated on 2025-06-06T17:20:38Z by kres fc6afbe-dirty.
|
||||
|
||||
name: helm
|
||||
concurrency:
|
||||
@ -31,7 +31,7 @@ jobs:
|
||||
steps:
|
||||
- name: gather-system-info
|
||||
id: system-info
|
||||
uses: kenchan0130/actions-system-info@v1.3.0
|
||||
uses: kenchan0130/actions-system-info@v1.3.1
|
||||
continue-on-error: true
|
||||
- name: print-system-info
|
||||
run: |
|
||||
|
||||
@ -25,6 +25,7 @@ spec:
|
||||
- path: internal/integration
|
||||
name: integration-test
|
||||
enableDockerImage: true
|
||||
imageName: omni-integration-test
|
||||
outputs:
|
||||
linux-amd64:
|
||||
GOOS: linux
|
||||
|
||||
12
Dockerfile
12
Dockerfile
@ -1,8 +1,8 @@
|
||||
# syntax = docker/dockerfile-upstream:1.15.1-labs
|
||||
# syntax = docker/dockerfile-upstream:1.16.0-labs
|
||||
|
||||
# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
|
||||
#
|
||||
# Generated on 2025-06-02T21:18:31Z by kres 99b55ad-dirty.
|
||||
# Generated on 2025-06-06T17:20:38Z by kres fc6afbe-dirty.
|
||||
|
||||
ARG JS_TOOLCHAIN
|
||||
ARG TOOLCHAIN
|
||||
@ -20,7 +20,7 @@ ENV GOPATH=/go
|
||||
ENV PATH=${PATH}:/usr/local/go/bin
|
||||
|
||||
# runs markdownlint
|
||||
FROM docker.io/oven/bun:1.2.13-alpine AS lint-markdown
|
||||
FROM docker.io/oven/bun:1.2.15-alpine AS lint-markdown
|
||||
WORKDIR /src
|
||||
RUN bun i markdownlint-cli@0.45.0 sentences-per-line@0.3.0
|
||||
COPY .markdownlint.json .
|
||||
@ -586,13 +586,13 @@ COPY --from=omnictl-linux-amd64 / /
|
||||
COPY --from=omnictl-linux-arm64 / /
|
||||
COPY --from=omnictl-windows-amd64.exe / /
|
||||
|
||||
FROM scratch AS image-integration-test
|
||||
FROM scratch AS image-omni-integration-test
|
||||
ARG TARGETARCH
|
||||
COPY --from=integration-test integration-test-linux-${TARGETARCH} /integration-test
|
||||
COPY --from=integration-test integration-test-linux-${TARGETARCH} /omni-integration-test
|
||||
COPY --from=image-fhs / /
|
||||
COPY --from=image-ca-certificates / /
|
||||
LABEL org.opencontainers.image.source=https://github.com/siderolabs/omni
|
||||
ENTRYPOINT ["/integration-test"]
|
||||
ENTRYPOINT ["/omni-integration-test"]
|
||||
|
||||
FROM scratch AS image-omni
|
||||
ARG TARGETARCH
|
||||
|
||||
12
Makefile
12
Makefile
@ -1,6 +1,6 @@
|
||||
# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
|
||||
#
|
||||
# Generated on 2025-06-02T21:18:31Z by kres 99b55ad-dirty.
|
||||
# Generated on 2025-06-06T17:20:38Z by kres fc6afbe-dirty.
|
||||
|
||||
# common variables
|
||||
|
||||
@ -79,7 +79,7 @@ COMMON_ARGS += --build-arg=DEEPCOPY_VERSION="$(DEEPCOPY_VERSION)"
|
||||
COMMON_ARGS += --build-arg=GOLANGCILINT_VERSION="$(GOLANGCILINT_VERSION)"
|
||||
COMMON_ARGS += --build-arg=GOFUMPT_VERSION="$(GOFUMPT_VERSION)"
|
||||
COMMON_ARGS += --build-arg=TESTPKGS="$(TESTPKGS)"
|
||||
JS_TOOLCHAIN ?= docker.io/oven/bun:1.2.13-alpine
|
||||
JS_TOOLCHAIN ?= docker.io/oven/bun:1.2.15-alpine
|
||||
TOOLCHAIN ?= docker.io/golang:1.24-alpine
|
||||
|
||||
# extra variables
|
||||
@ -148,7 +148,7 @@ else
|
||||
GO_LDFLAGS += -s
|
||||
endif
|
||||
|
||||
all: unit-tests-frontend lint-eslint frontend unit-tests-client unit-tests acompat make-cookies omni image-omni omnictl helm integration-test image-integration-test lint
|
||||
all: unit-tests-frontend lint-eslint frontend unit-tests-client unit-tests acompat make-cookies omni image-omni omnictl helm integration-test image-omni-integration-test lint
|
||||
|
||||
$(ARTIFACTS): ## Creates artifacts directory.
|
||||
@mkdir -p $(ARTIFACTS)
|
||||
@ -386,9 +386,9 @@ integration-test-linux-arm64: $(ARTIFACTS)/integration-test-linux-arm64 ## Buil
|
||||
.PHONY: integration-test
|
||||
integration-test: integration-test-darwin-amd64 integration-test-darwin-arm64 integration-test-linux-amd64 integration-test-linux-arm64 ## Builds executables for integration-test.
|
||||
|
||||
.PHONY: image-integration-test
|
||||
image-integration-test: ## Builds image for integration-test.
|
||||
@$(MAKE) registry-$@ IMAGE_NAME="integration-test"
|
||||
.PHONY: image-omni-integration-test
|
||||
image-omni-integration-test: ## Builds image for omni-integration-test.
|
||||
@$(MAKE) registry-$@ IMAGE_NAME="omni-integration-test"
|
||||
|
||||
.PHONY: dev-server
|
||||
dev-server:
|
||||
|
||||
@ -28,7 +28,7 @@ require (
|
||||
github.com/mattn/go-isatty v0.0.20
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20241121165744-79df5c4772f2
|
||||
github.com/sergi/go-diff v1.3.1
|
||||
github.com/siderolabs/gen v0.8.1
|
||||
github.com/siderolabs/gen v0.8.2
|
||||
github.com/siderolabs/go-api-signature v0.3.6
|
||||
github.com/siderolabs/go-kubeconfig v0.1.1
|
||||
github.com/siderolabs/go-pointer v1.0.1
|
||||
|
||||
@ -164,8 +164,8 @@ github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
|
||||
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
|
||||
github.com/siderolabs/crypto v0.5.1 h1:aZEUTZBoP8rH+0TqQAlUgazriPh89MrXf4R+th+m6ps=
|
||||
github.com/siderolabs/crypto v0.5.1/go.mod h1:7RHC7eUKBx6RLS2lDaNXrQ83zY9iPH/aQSTxk1I4/j4=
|
||||
github.com/siderolabs/gen v0.8.1 h1:i26KvarXfkXYqsmIicjGr2DN68uuuKDse8J4Z2J0O/Y=
|
||||
github.com/siderolabs/gen v0.8.1/go.mod h1:CIhFWgYkOKtxKD8Zct5NcJMgRzefnF2XIqeGOA+um0U=
|
||||
github.com/siderolabs/gen v0.8.2 h1:ZUSyehTbqm6h6K7bMyGN2X99Z7q8hg9KU0XLqRaebp0=
|
||||
github.com/siderolabs/gen v0.8.2/go.mod h1:CRrktDXQf3yDJI7xKv+cDYhBbKdfd/YE16OpgcHoT9E=
|
||||
github.com/siderolabs/go-api-signature v0.3.6 h1:wDIsXbpl7Oa/FXvxB6uz4VL9INA9fmr3EbmjEZYFJrU=
|
||||
github.com/siderolabs/go-api-signature v0.3.6/go.mod h1:hoH13AfunHflxbXfh+NoploqV13ZTDfQ1mQJWNVSW9U=
|
||||
github.com/siderolabs/go-kubeconfig v0.1.1 h1:tZlgpelj/OqrcHVUwISPN0NRgObcflpH9WtE41mtQZ0=
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -8,46 +8,64 @@ package cmd
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/cosi-project/runtime/pkg/resource"
|
||||
"github.com/cosi-project/runtime/pkg/state"
|
||||
"github.com/go-logr/zapr"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/siderolabs/talos/pkg/machinery/config/generate"
|
||||
"github.com/siderolabs/talos/pkg/machinery/config/merge"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"gopkg.in/yaml.v3"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"github.com/siderolabs/omni/client/pkg/compression"
|
||||
"github.com/siderolabs/omni/client/pkg/constants"
|
||||
authres "github.com/siderolabs/omni/client/pkg/omni/resources/auth"
|
||||
omnires "github.com/siderolabs/omni/client/pkg/omni/resources/omni"
|
||||
"github.com/siderolabs/omni/client/pkg/panichandler"
|
||||
"github.com/siderolabs/omni/internal/backend"
|
||||
"github.com/siderolabs/omni/internal/backend/discovery"
|
||||
"github.com/siderolabs/omni/internal/backend/dns"
|
||||
"github.com/siderolabs/omni/internal/backend/imagefactory"
|
||||
"github.com/siderolabs/omni/internal/backend/logging"
|
||||
"github.com/siderolabs/omni/internal/backend/resourcelogger"
|
||||
"github.com/siderolabs/omni/internal/backend/runtime/omni"
|
||||
"github.com/siderolabs/omni/internal/backend/runtime/omni/virtual"
|
||||
"github.com/siderolabs/omni/internal/backend/runtime/talos"
|
||||
"github.com/siderolabs/omni/internal/backend/workloadproxy"
|
||||
"github.com/siderolabs/omni/internal/pkg/auth"
|
||||
"github.com/siderolabs/omni/internal/pkg/auth/actor"
|
||||
"github.com/siderolabs/omni/internal/pkg/auth/user"
|
||||
"github.com/siderolabs/omni/internal/pkg/config"
|
||||
"github.com/siderolabs/omni/internal/pkg/ctxstore"
|
||||
"github.com/siderolabs/omni/internal/pkg/features"
|
||||
"github.com/siderolabs/omni/internal/pkg/siderolink"
|
||||
"github.com/siderolabs/omni/internal/version"
|
||||
)
|
||||
|
||||
// RunService starts the main Omni server.
|
||||
func RunService(ctx context.Context, logger *zap.Logger, params *config.Params) error {
|
||||
func RunService(ctx context.Context, logger *zap.Logger, paramsList ...*config.Params) error {
|
||||
cfg := config.InitDefault()
|
||||
|
||||
raw, err := yaml.Marshal(params)
|
||||
if err != nil {
|
||||
return err
|
||||
for _, params := range paramsList {
|
||||
if err := merge.Merge(cfg, params); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err = yaml.Unmarshal(raw, &cfg); err != nil {
|
||||
cfg.PopulateFallbacks()
|
||||
|
||||
if err := cfg.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config.Config = cfg
|
||||
|
||||
if err = compression.InitConfig(config.Config.ConfigDataCompression.Enabled); err != nil {
|
||||
if err := compression.InitConfig(config.Config.Features.EnableConfigDataCompression); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Info("initialized resource compression config", zap.Bool("enabled", config.Config.ConfigDataCompression.Enabled))
|
||||
logger.Info("initialized resource compression config", zap.Bool("enabled", config.Config.Features.EnableConfigDataCompression))
|
||||
|
||||
// set kubernetes logger to use warn log level and use zap
|
||||
klog.SetLogger(zapr.NewLogger(logger.WithOptions(zap.IncreaseLevel(zapcore.WarnLevel)).With(logging.Component("kubernetes"))))
|
||||
@ -56,32 +74,127 @@ func RunService(ctx context.Context, logger *zap.Logger, params *config.Params)
|
||||
logger.Warn("running debug build")
|
||||
}
|
||||
|
||||
for _, registryMirror := range rootCmdArgs.registryMirrors {
|
||||
hostname, endpoint, ok := strings.Cut(registryMirror, "=")
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid registry mirror spec: %q", registryMirror)
|
||||
}
|
||||
|
||||
config.Config.DefaultConfigGenOptions = append(config.Config.DefaultConfigGenOptions, generate.WithRegistryMirror(hostname, endpoint))
|
||||
}
|
||||
|
||||
logger.Info("starting Omni", zap.String("version", version.Tag))
|
||||
|
||||
logger.Debug("using config", zap.Any("config", config.Config))
|
||||
|
||||
if cfg.RunDebugServer {
|
||||
if cfg.Debug.Server.Endpoint != "" {
|
||||
panichandler.Go(func() {
|
||||
runDebugServer(ctx, logger)
|
||||
runDebugServer(ctx, logger, cfg.Debug.Server.Endpoint)
|
||||
}, logger)
|
||||
}
|
||||
|
||||
// this global context propagates into all controllers and any other background activities
|
||||
ctx = actor.MarkContextAsInternalActor(ctx)
|
||||
|
||||
err = omni.NewState(ctx, config.Config, logger, prometheus.DefaultRegisterer, runWithState(logger))
|
||||
err := omni.NewState(ctx, config.Config, logger, prometheus.DefaultRegisterer, runWithState(logger))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to run Omni: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func runWithState(logger *zap.Logger) func(context.Context, state.State, *virtual.State) error {
|
||||
return func(ctx context.Context, resourceState state.State, virtualState *virtual.State) error {
|
||||
auditWrap, auditErr := omni.NewAuditWrap(resourceState, config.Config, logger)
|
||||
if auditErr != nil {
|
||||
return auditErr
|
||||
}
|
||||
|
||||
resourceState = auditWrap.WrapState(resourceState)
|
||||
|
||||
talosClientFactory := talos.NewClientFactory(resourceState, logger)
|
||||
prometheus.MustRegister(talosClientFactory)
|
||||
|
||||
dnsService := dns.NewService(resourceState, logger)
|
||||
workloadProxyReconciler := workloadproxy.NewReconciler(logger.With(logging.Component("workload_proxy_reconciler")), zapcore.DebugLevel)
|
||||
|
||||
var resourceLogger *resourcelogger.Logger
|
||||
|
||||
if len(config.Config.Logs.ResourceLogger.Types) > 0 {
|
||||
var err error
|
||||
|
||||
resourceLogger, err = resourcelogger.New(ctx, resourceState, logger.With(logging.Component("resourcelogger")),
|
||||
config.Config.Logs.ResourceLogger.LogLevel, config.Config.Logs.ResourceLogger.Types...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set up resource logger: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
imageFactoryClient, err := imagefactory.NewClient(resourceState, config.Config.Registries.ImageFactoryBaseURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set up image factory client: %w", err)
|
||||
}
|
||||
|
||||
linkCounterDeltaCh := make(chan siderolink.LinkCounterDeltas)
|
||||
siderolinkEventsCh := make(chan *omnires.MachineStatusSnapshot)
|
||||
installEventCh := make(chan resource.ID)
|
||||
|
||||
discoveryClientCache := discovery.NewClientCache(logger.With(logging.Component("discovery_client_factory")))
|
||||
defer discoveryClientCache.Close()
|
||||
|
||||
prometheus.MustRegister(discoveryClientCache)
|
||||
|
||||
omniRuntime, err := omni.New(talosClientFactory, dnsService, workloadProxyReconciler, resourceLogger,
|
||||
imageFactoryClient, linkCounterDeltaCh, siderolinkEventsCh, installEventCh, resourceState, virtualState,
|
||||
prometheus.DefaultRegisterer, discoveryClientCache, logger.With(logging.Component("omni_runtime")))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set up the controller runtime: %w", err)
|
||||
}
|
||||
|
||||
machineMap := siderolink.NewMachineMap(siderolink.NewStateStorage(omniRuntime.State()))
|
||||
|
||||
logHandler, err := siderolink.NewLogHandler(
|
||||
machineMap,
|
||||
resourceState,
|
||||
&config.Config.Logs.Machine,
|
||||
logger.With(logging.Component("siderolink_log_handler")),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set up log handler: %w", err)
|
||||
}
|
||||
|
||||
talosRuntime := talos.New(talosClientFactory, logger)
|
||||
|
||||
err = user.EnsureInitialResources(ctx, omniRuntime.State(), logger, config.Config.Auth.Auth0.InitialUsers)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write initial user resources to state: %w", err)
|
||||
}
|
||||
|
||||
authConfig, err := auth.EnsureAuthConfigResource(ctx, omniRuntime.State(), logger, config.Config.Auth)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write Auth0 parameters to state: %w", err)
|
||||
}
|
||||
|
||||
if err = features.UpdateResources(ctx, omniRuntime.State(), logger); err != nil {
|
||||
return fmt.Errorf("failed to update features config resources: %w", err)
|
||||
}
|
||||
|
||||
ctx = ctxstore.WithValue(ctx, auth.EnabledAuthContextKey{Enabled: authres.Enabled(authConfig)})
|
||||
|
||||
server, err := backend.NewServer(
|
||||
dnsService,
|
||||
workloadProxyReconciler,
|
||||
imageFactoryClient,
|
||||
linkCounterDeltaCh,
|
||||
siderolinkEventsCh,
|
||||
installEventCh,
|
||||
omniRuntime,
|
||||
talosRuntime,
|
||||
logHandler,
|
||||
authConfig,
|
||||
auditWrap,
|
||||
logger,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create server: %w", err)
|
||||
}
|
||||
|
||||
if err := server.Run(ctx); err != nil {
|
||||
return fmt.Errorf("failed to run server: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
9
go.mod
9
go.mod
@ -3,6 +3,7 @@ module github.com/siderolabs/omni
|
||||
go 1.24.2
|
||||
|
||||
replace (
|
||||
github.com/siderolabs/gen => github.com/unix4ever/gen v0.0.0-20250606184729-3e319e7e52c5
|
||||
// use nested module
|
||||
github.com/siderolabs/omni/client => ./client
|
||||
// forked go-yaml that introduces RawYAML interface, which can be used to populate YAML fields using bytes
|
||||
@ -37,6 +38,7 @@ require (
|
||||
github.com/gertd/go-pluralize v0.2.1
|
||||
github.com/go-jose/go-jose/v4 v4.1.0
|
||||
github.com/go-logr/zapr v1.3.0
|
||||
github.com/go-playground/validator/v10 v10.26.0
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/google/go-containerregistry v0.20.3
|
||||
@ -113,6 +115,8 @@ require (
|
||||
sigs.k8s.io/controller-runtime v0.20.4
|
||||
)
|
||||
|
||||
require github.com/go-logr/logr v1.4.2
|
||||
|
||||
require (
|
||||
cel.dev/expr v0.23.1 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.2.0 // indirect
|
||||
@ -153,13 +157,15 @@ require (
|
||||
github.com/evanphx/json-patch v5.9.11+incompatible // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.8.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/go-chi/chi/v5 v5.2.1 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.1 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.1 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
@ -184,6 +190,7 @@ require (
|
||||
github.com/josharian/native v1.1.0 // indirect
|
||||
github.com/jsimonetti/rtnetlink/v2 v2.0.3 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mailru/easyjson v0.9.0 // indirect
|
||||
github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
|
||||
16
go.sum
16
go.sum
@ -152,6 +152,8 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU=
|
||||
github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||
github.com/gertd/go-pluralize v0.2.1 h1:M3uASbVjMnTsPb0PNqg+E/24Vwigyo/tvyMTtAlLgiA=
|
||||
github.com/gertd/go-pluralize v0.2.1/go.mod h1:rbYaKDbsXxmRfr8uygAEKhOWsjyrrqrkHVpZvoOp8zk=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
@ -175,6 +177,14 @@ github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF
|
||||
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
||||
github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
|
||||
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
|
||||
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
@ -302,6 +312,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU=
|
||||
@ -401,8 +413,6 @@ github.com/siderolabs/discovery-client v0.1.11 h1:Au+7QZ+CIB6g4C7ZCC4m5Ai5Uso1g/
|
||||
github.com/siderolabs/discovery-client v0.1.11/go.mod h1:Iw5XUphGNNV0m2czHjbj9aLhQvfM8hYEfWCc6fdQ4ko=
|
||||
github.com/siderolabs/discovery-service v1.0.10 h1:GSd5p+bC+PJjIpCqiDtVFKKU18LpsS6jmv+3OF55+Bw=
|
||||
github.com/siderolabs/discovery-service v1.0.10/go.mod h1:tzeHcfftQQHZSShuSTcrgIN3BY6fmhlum/Z9yOJ61lk=
|
||||
github.com/siderolabs/gen v0.8.1 h1:i26KvarXfkXYqsmIicjGr2DN68uuuKDse8J4Z2J0O/Y=
|
||||
github.com/siderolabs/gen v0.8.1/go.mod h1:CIhFWgYkOKtxKD8Zct5NcJMgRzefnF2XIqeGOA+um0U=
|
||||
github.com/siderolabs/go-api-signature v0.3.6 h1:wDIsXbpl7Oa/FXvxB6uz4VL9INA9fmr3EbmjEZYFJrU=
|
||||
github.com/siderolabs/go-api-signature v0.3.6/go.mod h1:hoH13AfunHflxbXfh+NoploqV13ZTDfQ1mQJWNVSW9U=
|
||||
github.com/siderolabs/go-circular v0.2.3 h1:GKkA1Tw79kEFGtWdl7WTxEUTbwtklITeiRT0V1McHrA=
|
||||
@ -477,6 +487,8 @@ github.com/stripe/stripe-go/v76 v76.25.0 h1:kmDoOTvdQSTQssQzWZQQkgbAR2Q8eXdMWbN/
|
||||
github.com/stripe/stripe-go/v76 v76.25.0/go.mod h1:rw1MxjlAKKcZ+3FOXgTHgwiOa2ya6CPq6ykpJ0Q6Po4=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk=
|
||||
github.com/unix4ever/gen v0.0.0-20250606184729-3e319e7e52c5 h1:+dggEUpyHfLUWap8CITVdbPdZrv2tH7m2uqjOoQqin4=
|
||||
github.com/unix4ever/gen v0.0.0-20250606184729-3e319e7e52c5/go.mod h1:CRrktDXQf3yDJI7xKv+cDYhBbKdfd/YE16OpgcHoT9E=
|
||||
github.com/unix4ever/yaml v0.0.0-20220527175918-f17b0f05cf2c h1:Vn6nVVu9MdOYvXPkJP83iX5jVIfvxFC9v9xIKb+DlaQ=
|
||||
github.com/unix4ever/yaml v0.0.0-20220527175918-f17b0f05cf2c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo=
|
||||
|
||||
@ -146,6 +146,7 @@ SIDEROLINK_DEV_JOIN_TOKEN="${JOIN_TOKEN}" \
|
||||
--cert hack/certs/localhost.pem \
|
||||
--etcd-embedded-unsafe-fsync=true \
|
||||
--etcd-backup-s3 \
|
||||
--join-tokens-mode strict \
|
||||
--audit-log-dir /tmp/omni-data/audit-log \
|
||||
--enable-talos-pre-release-versions="${ENABLE_TALOS_PRERELEASE_VERSIONS}" \
|
||||
"${REGISTRY_MIRROR_FLAGS[@]}" \
|
||||
|
||||
@ -30,8 +30,18 @@ import (
|
||||
|
||||
// Handler of image requests.
|
||||
type Handler struct {
|
||||
State state.State
|
||||
Logger *zap.Logger
|
||||
state state.State
|
||||
logger *zap.Logger
|
||||
config *config.Registries
|
||||
}
|
||||
|
||||
// NewHandler creates a new factory proxy handler.
|
||||
func NewHandler(state state.State, logger *zap.Logger, config *config.Registries) *Handler {
|
||||
return &Handler{
|
||||
state: state,
|
||||
logger: logger,
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
func setContentHeaders(w http.ResponseWriter, contentType, filename string) {
|
||||
@ -45,7 +55,7 @@ func httpNotFound(w http.ResponseWriter) {
|
||||
}
|
||||
|
||||
func (handler *Handler) handleError(msg string, w http.ResponseWriter, err error) {
|
||||
handler.Logger.Error(msg, zap.Error(err))
|
||||
handler.logger.Error(msg, zap.Error(err))
|
||||
|
||||
switch status.Code(err) { //nolint:exhaustive
|
||||
case codes.Unauthenticated:
|
||||
@ -71,7 +81,7 @@ func (handler *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
params, err := parseRequest(r, handler.State)
|
||||
params, err := parseRequest(r, handler.state, handler.config)
|
||||
if err != nil {
|
||||
if errors.Is(err, errNotFound) {
|
||||
httpNotFound(w)
|
||||
@ -84,7 +94,7 @@ func (handler *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
handler.Logger.Info("proxy request", zap.String("url", params.ProxyURL))
|
||||
handler.logger.Info("proxy request", zap.String("url", params.ProxyURL))
|
||||
|
||||
proxyReq, err := http.NewRequestWithContext(r.Context(), r.Method, params.ProxyURL, nil)
|
||||
if err != nil {
|
||||
@ -138,7 +148,7 @@ type ProxyParams struct {
|
||||
DestinationFilename string
|
||||
}
|
||||
|
||||
func parseRequest(r *http.Request, st state.State) (*ProxyParams, error) {
|
||||
func parseRequest(r *http.Request, st state.State, config *config.Registries) (*ProxyParams, error) {
|
||||
segments := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
|
||||
|
||||
if len(segments) < 4 {
|
||||
@ -177,7 +187,7 @@ func parseRequest(r *http.Request, st state.State) (*ProxyParams, error) {
|
||||
DestinationFilename: fmt.Sprintf("%s-%s.%s", media.TypedSpec().Value.DestFilePrefix, srcFilename, media.TypedSpec().Value.Extension),
|
||||
}
|
||||
|
||||
proxyURL, err := url.Parse(config.Config.ImageFactoryBaseURL)
|
||||
proxyURL, err := url.Parse(config.ImageFactoryBaseURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -20,6 +20,7 @@ import (
|
||||
"github.com/siderolabs/omni/client/pkg/omni/resources"
|
||||
"github.com/siderolabs/omni/client/pkg/omni/resources/omni"
|
||||
"github.com/siderolabs/omni/internal/backend/factory"
|
||||
"github.com/siderolabs/omni/internal/pkg/config"
|
||||
)
|
||||
|
||||
func TestParseRequest(t *testing.T) {
|
||||
@ -92,7 +93,9 @@ func TestParseRequest(t *testing.T) {
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodHead, "https://localhost"+tt.incomingURL, nil)
|
||||
require.NoError(err)
|
||||
|
||||
params, err := factory.ParseRequest(request, state)
|
||||
params, err := factory.ParseRequest(request, state, &config.Registries{
|
||||
ImageFactoryBaseURL: "https://factory.talos.dev",
|
||||
})
|
||||
if tt.shouldFail {
|
||||
require.Error(err)
|
||||
|
||||
|
||||
@ -269,7 +269,7 @@ func verifiedEmail(ctx context.Context) (string, error) {
|
||||
}
|
||||
|
||||
func (s *authServer) buildLoginURL(pgpKeyID string) (string, error) {
|
||||
loginURL, err := url.Parse(config.Config.APIURL)
|
||||
loginURL, err := url.Parse(config.Config.Services.API.URL())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@ -92,10 +92,10 @@ func TestGenerateConfigs(t *testing.T) {
|
||||
require.Equal(t, codes.PermissionDenied, status.Code(err), err)
|
||||
})
|
||||
|
||||
config.Config.EnableBreakGlassConfigs = true
|
||||
config.Config.Features.EnableBreakGlassConfigs = true
|
||||
|
||||
defer func() {
|
||||
config.Config.EnableBreakGlassConfigs = false
|
||||
config.Config.Features.EnableBreakGlassConfigs = false
|
||||
}()
|
||||
|
||||
t.Run("kubeconfig enabled no cluster", func(t *testing.T) {
|
||||
|
||||
@ -52,7 +52,7 @@ func MakeServiceServers(
|
||||
logger *zap.Logger,
|
||||
auditor AuditLogger,
|
||||
) iter.Seq2[ServiceServer, error] {
|
||||
dest, err := generateDest(config.Config.APIURL)
|
||||
dest, err := generateDest(config.Config.Services.API.URL())
|
||||
if err != nil {
|
||||
return func(yield func(ServiceServer, error) bool) {
|
||||
yield(nil, fmt.Errorf("error generating destination: %w", err))
|
||||
|
||||
@ -330,7 +330,7 @@ func getBreakGlass(ctx context.Context, clusterName string) ([]byte, error) {
|
||||
}
|
||||
|
||||
func (s *managementServer) breakGlassTalosconfig(ctx context.Context, raw bool) (*management.TalosconfigResponse, error) {
|
||||
if !constants.IsDebugBuild && !config.Config.EnableBreakGlassConfigs {
|
||||
if !constants.IsDebugBuild && !config.Config.Features.EnableBreakGlassConfigs {
|
||||
return nil, status.Error(codes.PermissionDenied, "not allowed")
|
||||
}
|
||||
|
||||
@ -372,7 +372,7 @@ func (s *managementServer) breakGlassKubeconfig(ctx context.Context) (*managemen
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !constants.IsDebugBuild && !config.Config.EnableBreakGlassConfigs {
|
||||
if !constants.IsDebugBuild && !config.Config.Features.EnableBreakGlassConfigs {
|
||||
return nil, status.Error(codes.PermissionDenied, "not allowed")
|
||||
}
|
||||
|
||||
|
||||
@ -278,7 +278,7 @@ func (s *managementServer) generateServiceAccountJWT(ctx context.Context, req *m
|
||||
now := time.Now()
|
||||
token := jwt.NewWithClaims(signingMethod, jwt.MapClaims{
|
||||
"iat": now.Unix(),
|
||||
"iss": fmt.Sprintf("omni-%s-service-account-issuer", config.Config.Name),
|
||||
"iss": fmt.Sprintf("omni-%s-service-account-issuer", config.Config.Account.Name),
|
||||
"exp": now.Add(req.GetServiceAccountTtl().AsDuration()).Unix(),
|
||||
"sub": req.GetServiceAccountUser(),
|
||||
"groups": req.GetServiceAccountGroups(),
|
||||
@ -292,7 +292,7 @@ func (s *managementServer) generateServiceAccountJWT(ctx context.Context, req *m
|
||||
}
|
||||
|
||||
func (s *managementServer) buildServiceAccountKubeconfig(cluster, user, token string) ([]byte, error) {
|
||||
clusterName := config.Config.Name + "-" + cluster + "-" + user
|
||||
clusterName := config.Config.Account.Name + "-" + cluster + "-" + user
|
||||
contextName := clusterName
|
||||
|
||||
conf := clientcmdapi.Config{
|
||||
@ -301,7 +301,7 @@ func (s *managementServer) buildServiceAccountKubeconfig(cluster, user, token st
|
||||
CurrentContext: contextName,
|
||||
Clusters: map[string]*clientcmdapi.Cluster{
|
||||
clusterName: {
|
||||
Server: config.Config.KubernetesProxyURL,
|
||||
Server: config.Config.Services.KubernetesProxy.URL(),
|
||||
},
|
||||
},
|
||||
Contexts: map[string]*clientcmdapi.Context{
|
||||
|
||||
@ -74,5 +74,5 @@ func Build(imageFactoryHost string, resID resource.ID, installImage *specs.Machi
|
||||
return imageFactoryHost + "/" + installerName + "/" + schematicID + ":" + desiredTalosVersion, nil
|
||||
}
|
||||
|
||||
return appconfig.Config.TalosRegistry + ":" + desiredTalosVersion, nil
|
||||
return appconfig.Config.Registries.Talos + ":" + desiredTalosVersion, nil
|
||||
}
|
||||
|
||||
@ -303,11 +303,11 @@ func (r *Runtime) GetOIDCKubeconfig(context *common.Context, identity string, ex
|
||||
Identity string
|
||||
ExtraOptions []string
|
||||
}{
|
||||
InstanceName: config.Config.Name,
|
||||
InstanceName: config.Config.Account.Name,
|
||||
ClusterName: context.Name,
|
||||
|
||||
EndpointOIDC: issuerEndpoint,
|
||||
KubernetesProxyEndpoint: config.Config.KubernetesProxyURL,
|
||||
KubernetesProxyEndpoint: config.Config.Services.KubernetesProxy.URL(),
|
||||
|
||||
ClientID: external.DefaultClientID,
|
||||
Identity: identity,
|
||||
|
||||
@ -12,6 +12,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/cosi-project/runtime/pkg/controller"
|
||||
@ -52,7 +53,7 @@ const ClusterMachineConfigControllerName = "ClusterMachineConfigController"
|
||||
type ClusterMachineConfigController = qtransform.QController[*omni.ClusterMachine, *omni.ClusterMachineConfig]
|
||||
|
||||
// NewClusterMachineConfigController initializes ClusterMachineConfigController.
|
||||
func NewClusterMachineConfigController(imageFactoryHost string, defaultGenOptions []generate.Option, eventSinkPort int) *ClusterMachineConfigController {
|
||||
func NewClusterMachineConfigController(imageFactoryHost string, registryMirrors []string, eventSinkPort int) *ClusterMachineConfigController {
|
||||
return qtransform.NewQController(
|
||||
qtransform.Settings[*omni.ClusterMachine, *omni.ClusterMachineConfig]{
|
||||
Name: ClusterMachineConfigControllerName,
|
||||
@ -63,7 +64,7 @@ func NewClusterMachineConfigController(imageFactoryHost string, defaultGenOption
|
||||
return omni.NewClusterMachine(resources.DefaultNamespace, machineConfig.Metadata().ID())
|
||||
},
|
||||
TransformFunc: func(ctx context.Context, r controller.Reader, logger *zap.Logger, clusterMachine *omni.ClusterMachine, machineConfig *omni.ClusterMachineConfig) error {
|
||||
return reconcileClusterMachineConfig(ctx, r, logger, clusterMachine, machineConfig, defaultGenOptions, eventSinkPort, imageFactoryHost)
|
||||
return reconcileClusterMachineConfig(ctx, r, logger, clusterMachine, machineConfig, registryMirrors, eventSinkPort, imageFactoryHost)
|
||||
},
|
||||
},
|
||||
qtransform.WithExtraMappedInput(
|
||||
@ -104,7 +105,7 @@ func reconcileClusterMachineConfig(
|
||||
logger *zap.Logger,
|
||||
clusterMachine *omni.ClusterMachine,
|
||||
machineConfig *omni.ClusterMachineConfig,
|
||||
defaultGenOptions []generate.Option,
|
||||
registryMirrors []string,
|
||||
eventSinkPort int,
|
||||
imageFactoryHost string,
|
||||
) error {
|
||||
@ -247,8 +248,19 @@ func reconcileClusterMachineConfig(
|
||||
imageFactoryHost: imageFactoryHost,
|
||||
}
|
||||
|
||||
configGenOptions := make([]generate.Option, 0, len(registryMirrors))
|
||||
|
||||
for _, registryMirror := range registryMirrors {
|
||||
hostname, endpoint, ok := strings.Cut(registryMirror, "=")
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid registry mirror spec: %q", registryMirror)
|
||||
}
|
||||
|
||||
configGenOptions = append(configGenOptions, generate.WithRegistryMirror(hostname, endpoint))
|
||||
}
|
||||
|
||||
data, err := helper.generateConfig(clusterMachine, clusterMachineConfigPatches, secrets, loadBalancerConfig,
|
||||
cluster, clusterConfigVersion, machineConfigGenOptions, defaultGenOptions, connectionParams, link, eventSinkPort)
|
||||
cluster, clusterConfigVersion, machineConfigGenOptions, configGenOptions, connectionParams, link, eventSinkPort)
|
||||
if err != nil {
|
||||
machineConfig.TypedSpec().Value.GenerationError = err.Error()
|
||||
|
||||
|
||||
@ -103,7 +103,7 @@ func (suite *ClusterMachineConfigSuite) TestReconcile() {
|
||||
)
|
||||
}
|
||||
|
||||
newImage := fmt.Sprintf("%s:v1.0.2", conf.Config.TalosRegistry)
|
||||
newImage := fmt.Sprintf("%s:v1.0.2", conf.Config.Registries.Talos)
|
||||
|
||||
_, err = safe.StateUpdateWithConflicts(suite.ctx, suite.state, omni.NewClusterMachineConfigPatches(resources.DefaultNamespace, machines[0].Metadata().ID()).Metadata(),
|
||||
func(config *omni.ClusterMachineConfigPatches) error {
|
||||
|
||||
@ -396,7 +396,7 @@ func (ctrl *InstallationMediaController) Run(ctx context.Context, r controller.R
|
||||
newMedia.TypedSpec().Value.Name = m.Name
|
||||
newMedia.TypedSpec().Value.Profile = m.Profile
|
||||
newMedia.TypedSpec().Value.ContentType = m.ContentType
|
||||
newMedia.TypedSpec().Value.DestFilePrefix = fmt.Sprintf("%s-omni-%s", fname.srcPrefix, config.Config.Name)
|
||||
newMedia.TypedSpec().Value.DestFilePrefix = fmt.Sprintf("%s-omni-%s", fname.srcPrefix, config.Config.Account.Name)
|
||||
newMedia.TypedSpec().Value.Extension = fname.extension
|
||||
newMedia.TypedSpec().Value.NoSecureBoot = m.SBC
|
||||
newMedia.TypedSpec().Value.MinTalosVersion = m.MinTalosVersion
|
||||
|
||||
@ -30,12 +30,12 @@ func DefaultNew(bindAddress string, bindPort int, logger *zap.Logger) (LoadBalan
|
||||
bindAddress,
|
||||
bindPort,
|
||||
logger.WithOptions(zap.IncreaseLevel(zap.ErrorLevel)), // silence the load balancer logs
|
||||
controlplane.WithDialTimeout(config.Config.LoadBalancer.DialTimeout),
|
||||
controlplane.WithKeepAlivePeriod(config.Config.LoadBalancer.KeepAlivePeriod),
|
||||
controlplane.WithTCPUserTimeout(config.Config.LoadBalancer.TCPUserTimeout),
|
||||
controlplane.WithDialTimeout(config.Config.Services.LoadBalancer.DialTimeout),
|
||||
controlplane.WithKeepAlivePeriod(config.Config.Services.LoadBalancer.KeepAlivePeriod),
|
||||
controlplane.WithTCPUserTimeout(config.Config.Services.LoadBalancer.TCPUserTimeout),
|
||||
controlplane.WithHealthCheckOptions(
|
||||
upstream.WithHealthcheckInterval(config.Config.LoadBalancer.HealthCheckInterval),
|
||||
upstream.WithHealthcheckTimeout(config.Config.LoadBalancer.HealthCheckTimeout),
|
||||
upstream.WithHealthcheckInterval(config.Config.Services.LoadBalancer.HealthCheckInterval),
|
||||
upstream.WithHealthcheckTimeout(config.Config.Services.LoadBalancer.HealthCheckTimeout),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@ -402,7 +402,7 @@ func (ctrl *KubernetesStatusController) startWatcher(ctx context.Context, logger
|
||||
w.podsSync(ctx, notifyCh)
|
||||
}, logger)
|
||||
|
||||
if config.Config.WorkloadProxying.Enabled {
|
||||
if config.Config.Services.WorkloadProxy.Enabled {
|
||||
w.serviceFactory = informers.NewSharedInformerFactory(w.client.Clientset(), 0)
|
||||
|
||||
if _, err = w.serviceFactory.Core().V1().Services().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
|
||||
@ -239,12 +239,12 @@ func forAllCompatibleVersions(
|
||||
func (ctrl *VersionsController) reconcileTalosVersions(ctx context.Context, r controller.Runtime, k8sVersions []*compatibility.KubernetesVersion, logger *zap.Logger) error {
|
||||
tracker := trackResource(r, resources.DefaultNamespace, omni.TalosVersionType)
|
||||
|
||||
allVersions, err := ctrl.fetchTalosVersions(ctx, config.Config.ImageFactoryBaseURL)
|
||||
allVersions, err := ctrl.fetchTalosVersions(ctx, config.Config.Registries.ImageFactoryBaseURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
talosVersions := ctrl.getVersionsAfter(allVersions, minDiscoveredTalosVersion, config.Config.EnableTalosPreReleaseVersions)
|
||||
talosVersions := ctrl.getVersionsAfter(allVersions, minDiscoveredTalosVersion, config.Config.Features.EnableTalosPreReleaseVersions)
|
||||
|
||||
talosVersions = xslices.FilterInPlace(talosVersions, func(v string) bool {
|
||||
return consts.DenylistedTalosVersions.IsAllowed(v)
|
||||
@ -286,7 +286,7 @@ func (ctrl *VersionsController) reconcileTalosVersions(ctx context.Context, r co
|
||||
func (ctrl *VersionsController) reconcileKubernetesVersions(ctx context.Context, r controller.Runtime, logger *zap.Logger) ([]string, error) {
|
||||
tracker := trackResource(r, resources.DefaultNamespace, omni.KubernetesVersionType)
|
||||
|
||||
allVersions, err := ctrl.fetchVersionsFromRegistry(ctx, config.Config.KubernetesRegistry)
|
||||
allVersions, err := ctrl.fetchVersionsFromRegistry(ctx, config.Config.Registries.Kubernetes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ func EtcdElections(ctx context.Context, client *clientv3.Client, electionKey str
|
||||
return etcdElections(ctx, client, electionKey, logger, f)
|
||||
}
|
||||
|
||||
func ClusterValidationOptions(st state.State, etcdBackupConfig config.EtcdBackupParams, embeddedDiscoveryServiceConfig config.EmbeddedDiscoveryServiceParams) []validated.StateOption {
|
||||
func ClusterValidationOptions(st state.State, etcdBackupConfig config.EtcdBackup, embeddedDiscoveryServiceConfig config.EmbeddedDiscoveryService) []validated.StateOption {
|
||||
return clusterValidationOptions(st, etcdBackupConfig, embeddedDiscoveryServiceConfig)
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@ func MachineClassValidationOptions(st state.State) []validated.StateOption {
|
||||
return machineClassValidationOptions(st)
|
||||
}
|
||||
|
||||
func IdentityValidationOptions(samlConfig config.SAMLParams) []validated.StateOption {
|
||||
func IdentityValidationOptions(samlConfig config.SAML) []validated.StateOption {
|
||||
return identityValidationOptions(samlConfig)
|
||||
}
|
||||
|
||||
|
||||
@ -18,6 +18,7 @@ import (
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/siderolabs/omni/internal/backend/runtime/keyprovider"
|
||||
"github.com/siderolabs/omni/internal/pkg/config"
|
||||
)
|
||||
|
||||
// Loader is an interface that returns a private key.
|
||||
@ -102,12 +103,19 @@ func makeVaultHTTPLoader(source string, logger *zap.Logger) (Loader, error) {
|
||||
|
||||
token, ok := os.LookupEnv("VAULT_TOKEN")
|
||||
if !ok {
|
||||
return nil, errors.New("VAULT_TOKEN is not set")
|
||||
token = config.Config.Storage.Vault.Token
|
||||
|
||||
if token == "" {
|
||||
return nil, errors.New("VAULT_TOKEN is not set")
|
||||
}
|
||||
}
|
||||
|
||||
addr, ok := os.LookupEnv("VAULT_ADDR")
|
||||
if !ok {
|
||||
return nil, errors.New("VAULT_ADDR is not set")
|
||||
addr = config.Config.Storage.Vault.URL
|
||||
if addr == "" {
|
||||
return nil, errors.New("VAULT_ADDR is not set")
|
||||
}
|
||||
}
|
||||
|
||||
return &VaultHTTPLoader{
|
||||
|
||||
@ -481,7 +481,7 @@ func (suite *MigrationSuite) TestUpdateConfigPatchLabels() {
|
||||
ctx := suite.T().Context()
|
||||
|
||||
cluster := omni.NewCluster(resources.DefaultNamespace, "cluster")
|
||||
cluster.TypedSpec().Value.InstallImage = fmt.Sprintf("%s:v%s", config.Config.TalosRegistry, constants.DefaultTalosVersion) //nolint:staticcheck
|
||||
cluster.TypedSpec().Value.InstallImage = fmt.Sprintf("%s:v%s", config.Config.Registries.Talos, constants.DefaultTalosVersion) //nolint:staticcheck
|
||||
|
||||
machineSet := omni.NewMachineSet(resources.DefaultNamespace, "machine-set")
|
||||
machineSet.Metadata().Labels().Set("cluster", cluster.Metadata().ID())
|
||||
|
||||
@ -93,7 +93,7 @@ func New(talosClientFactory *talos.ClientFactory, dnsService *dns.Service, workl
|
||||
) (*Runtime, error) {
|
||||
var opts []options.Option
|
||||
|
||||
if !config.Config.DisableControllerRuntimeCache {
|
||||
if !config.Config.Features.DisableControllerRuntimeCache {
|
||||
opts = append(opts,
|
||||
safe.WithResourceCache[*omni.BackupData](),
|
||||
safe.WithResourceCache[*omni.ConfigPatch](),
|
||||
@ -215,7 +215,7 @@ func New(talosClientFactory *talos.ClientFactory, dnsService *dns.Service, workl
|
||||
TalosClientFactory: talosClientFactory,
|
||||
NodeResolver: dnsService,
|
||||
}),
|
||||
omnictrl.NewKubernetesStatusController(config.Config.APIURL, config.Config.WorkloadProxying.Subdomain),
|
||||
omnictrl.NewKubernetesStatusController(config.Config.Services.API.URL(), config.Config.Services.WorkloadProxy.Subdomain),
|
||||
&omnictrl.LoadBalancerController{},
|
||||
&omnictrl.MachineSetNodeController{},
|
||||
&omnictrl.MachineSetDestroyStatusController{},
|
||||
@ -224,12 +224,12 @@ func New(talosClientFactory *talos.ClientFactory, dnsService *dns.Service, workl
|
||||
&omnictrl.MachineStatusMetricsController{},
|
||||
&omnictrl.VersionsController{},
|
||||
omnictrl.NewClusterLoadBalancerController(
|
||||
config.Config.LoadBalancer.MinPort,
|
||||
config.Config.LoadBalancer.MaxPort,
|
||||
config.Config.Services.LoadBalancer.MinPort,
|
||||
config.Config.Services.LoadBalancer.MaxPort,
|
||||
),
|
||||
&omnictrl.InstallationMediaController{},
|
||||
omnictrl.NewKeyPrunerController(
|
||||
config.Config.KeyPruner.Interval,
|
||||
config.Config.Auth.KeyPruner.Interval,
|
||||
),
|
||||
&omnictrl.OngoingTaskController{},
|
||||
omnictrl.NewMachineRequestStatusCleanupController(),
|
||||
@ -247,18 +247,22 @@ func New(talosClientFactory *talos.ClientFactory, dnsService *dns.Service, workl
|
||||
omnictrl.NewClusterConfigVersionController(),
|
||||
omnictrl.NewClusterEndpointController(),
|
||||
omnictrl.NewClusterKubernetesNodesController(),
|
||||
omnictrl.NewClusterMachineConfigController(imageFactoryHost, config.Config.DefaultConfigGenOptions, config.Config.EventSinkPort),
|
||||
omnictrl.NewClusterMachineConfigController(
|
||||
imageFactoryHost,
|
||||
config.Config.Registries.Mirrors,
|
||||
config.Config.Services.Siderolink.EventSinkPort,
|
||||
),
|
||||
omnictrl.NewClusterMachineTeardownController(),
|
||||
omnictrl.NewMachineConfigGenOptionsController(),
|
||||
omnictrl.NewMachineStatusController(imageFactoryClient),
|
||||
omnictrl.NewClusterMachineConfigStatusController(imageFactoryHost),
|
||||
omnictrl.NewClusterMachineEncryptionKeyController(),
|
||||
omnictrl.NewClusterMachineStatusController(),
|
||||
omnictrl.NewClusterStatusController(config.Config.EmbeddedDiscoveryService.Enabled),
|
||||
omnictrl.NewClusterStatusController(config.Config.Services.EmbeddedDiscoveryService.Enabled),
|
||||
omnictrl.NewClusterDiagnosticsController(),
|
||||
omnictrl.NewClusterUUIDController(),
|
||||
omnictrl.NewControlPlaneStatusController(),
|
||||
omnictrl.NewDiscoveryServiceConfigPatchController(config.Config.EmbeddedDiscoveryService.Port),
|
||||
omnictrl.NewDiscoveryServiceConfigPatchController(config.Config.Services.EmbeddedDiscoveryService.Port),
|
||||
omnictrl.NewKubernetesNodeAuditController(nil, time.Minute),
|
||||
omnictrl.NewEtcdBackupEncryptionController(),
|
||||
omnictrl.NewClusterWorkloadProxyStatusController(workloadProxyReconciler),
|
||||
@ -288,7 +292,7 @@ func New(talosClientFactory *talos.ClientFactory, dnsService *dns.Service, workl
|
||||
omnictrl.NewLinkStatusController[*siderolinkresources.Link](peers),
|
||||
omnictrl.NewLinkStatusController[*siderolinkresources.PendingMachine](peers),
|
||||
omnictrl.NewPendingMachineStatusController(),
|
||||
omnictrl.NewMaintenanceConfigStatusController(nil, siderolink.ListenHost, config.Config.EventSinkPort, config.Config.LogServerPort),
|
||||
omnictrl.NewMaintenanceConfigStatusController(nil, siderolink.ListenHost, config.Config.Services.Siderolink.EventSinkPort, config.Config.Services.Siderolink.LogServerPort),
|
||||
omnictrl.NewDiscoveryAffiliateDeleteTaskController(clockwork.NewRealClock(), discoveryClientCache),
|
||||
omnictrl.NewInfraProviderCombinedStatusController(),
|
||||
omnictrl.NewServiceAccountStatusController(),
|
||||
@ -300,7 +304,7 @@ func New(talosClientFactory *talos.ClientFactory, dnsService *dns.Service, workl
|
||||
)
|
||||
}
|
||||
|
||||
if config.Config.EnableStripeReporting {
|
||||
if config.Config.Logs.Stripe.Enabled {
|
||||
stripeAPIKey, ok := os.LookupEnv("STRIPE_API_KEY")
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("environment variable STRIPE_API_KEY is not set")
|
||||
@ -359,7 +363,7 @@ func New(talosClientFactory *talos.ClientFactory, dnsService *dns.Service, workl
|
||||
metricsRegistry.MustRegister(expvarCollector)
|
||||
|
||||
validationOptions := slices.Concat(
|
||||
clusterValidationOptions(resourceState, config.Config.EtcdBackup, config.Config.EmbeddedDiscoveryService),
|
||||
clusterValidationOptions(resourceState, config.Config.EtcdBackup, config.Config.Services.EmbeddedDiscoveryService),
|
||||
relationLabelsValidationOptions(),
|
||||
accessPolicyValidationOptions(),
|
||||
authorizationValidationOptions(resourceState),
|
||||
|
||||
@ -66,19 +66,19 @@ func NewState(ctx context.Context, params *config.Params, logger *zap.Logger, me
|
||||
)
|
||||
}
|
||||
|
||||
switch params.Storage.Kind {
|
||||
switch params.Storage.Default.Kind {
|
||||
case "boltdb":
|
||||
return buildBoltPersistentState(ctx, params.Storage.Boltdb.Path, logger, stateFunc)
|
||||
return buildBoltPersistentState(ctx, params.Storage.Default.Boltdb.Path, logger, stateFunc)
|
||||
case "etcd":
|
||||
return buildEtcdPersistentState(ctx, params, logger, stateFunc)
|
||||
default:
|
||||
return fmt.Errorf("unknown storage kind %q", params.Storage.Kind)
|
||||
return fmt.Errorf("unknown storage kind %q", params.Storage.Default.Kind)
|
||||
}
|
||||
}
|
||||
|
||||
func newNamespacedState(params *config.Params, primaryStorageCoreState state.CoreState, virtualState *virtual.State, logger *zap.Logger) (*namespaced.State, func(), error) {
|
||||
secondaryStorageCoreState, secondaryStorageBackingStore, err := newBoltPersistentState(
|
||||
params.SecondaryStorage.Path, &bbolt.Options{
|
||||
params.Storage.Secondary.Path, &bbolt.Options{
|
||||
NoSync: true, // we do not need fsync for the secondary storage
|
||||
}, true, logger)
|
||||
if err != nil {
|
||||
@ -166,7 +166,7 @@ func initResources(ctx context.Context, resourceState state.State, logger *zap.L
|
||||
|
||||
sysVersion := system.NewSysVersion(resources.EphemeralNamespace, system.SysVersionID)
|
||||
sysVersion.TypedSpec().Value.BackendVersion = version.Tag
|
||||
sysVersion.TypedSpec().Value.InstanceName = config.Config.Name
|
||||
sysVersion.TypedSpec().Value.InstanceName = config.Config.Account.Name
|
||||
sysVersion.TypedSpec().Value.BackendApiVersion = version.API
|
||||
|
||||
if err := resourceState.Create(ctx, sysVersion); err != nil {
|
||||
@ -190,22 +190,22 @@ func stateWithMetrics(namespacedState *namespaced.State, metricsRegistry prometh
|
||||
|
||||
// NewAuditWrap creates a new audit wrap.
|
||||
func NewAuditWrap(resState state.State, params *config.Params, logger *zap.Logger) (*AuditWrap, error) {
|
||||
if params.AuditLogDir == "" {
|
||||
if params.Logs.Audit.Path == "" {
|
||||
logger.Info("audit log disabled")
|
||||
|
||||
return &AuditWrap{state: resState}, nil
|
||||
}
|
||||
|
||||
logger.Info("audit log enabled", zap.String("dir", params.AuditLogDir))
|
||||
logger.Info("audit log enabled", zap.String("dir", params.Logs.Audit.Path))
|
||||
|
||||
a, err := audit.NewLog(params.AuditLogDir, logger)
|
||||
a, err := audit.NewLog(params.Logs.Audit.Path, logger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hooks.Init(a)
|
||||
|
||||
return &AuditWrap{state: resState, log: a, dir: params.AuditLogDir}, nil
|
||||
return &AuditWrap{state: resState, log: a, dir: params.Logs.Audit.Path}, nil
|
||||
}
|
||||
|
||||
// AuditWrap is builder/wrapper for creating logged access to Omni and Talos nodes.
|
||||
|
||||
@ -42,16 +42,16 @@ import (
|
||||
const compressionThresholdBytes = 2048
|
||||
|
||||
func buildEtcdPersistentState(ctx context.Context, params *config.Params, logger *zap.Logger, f func(context.Context, namespaced.StateBuilder) error) error {
|
||||
prefix := fmt.Sprintf("/omni/%s", url.PathEscape(params.AccountID))
|
||||
prefix := fmt.Sprintf("/omni/%s", url.PathEscape(params.Account.ID))
|
||||
|
||||
return getEtcdClient(ctx, ¶ms.Storage.Etcd, logger, func(ctx context.Context, etcdClient *clientv3.Client) error {
|
||||
return getEtcdClient(ctx, ¶ms.Storage.Default.Etcd, logger, func(ctx context.Context, etcdClient *clientv3.Client) error {
|
||||
return etcdElections(ctx, etcdClient, prefix, logger, func(ctx context.Context, _ *clientv3.Client) error {
|
||||
cipher, err := makeCipher(params.AccountID, params.Storage.Etcd, etcdClient, logger) //nolint:contextcheck
|
||||
cipher, err := makeCipher(params.Account.ID, params.Storage.Default.Etcd, etcdClient, logger) //nolint:contextcheck
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
salt := sha256.Sum256([]byte(params.AccountID))
|
||||
salt := sha256.Sum256([]byte(params.Account.ID))
|
||||
|
||||
etcdState := etcd.NewState(
|
||||
etcdClient,
|
||||
@ -261,15 +261,15 @@ func getExternalEtcdClient(ctx context.Context, params *config.EtcdParams, logge
|
||||
|
||||
logger.Info("starting etcd client",
|
||||
zap.Strings("endpoints", params.Endpoints),
|
||||
zap.String("cert_path", params.CertPath),
|
||||
zap.String("key_path", params.KeyPath),
|
||||
zap.String("ca_path", params.CAPath),
|
||||
zap.String("cert_path", params.CertFile),
|
||||
zap.String("key_path", params.KeyFile),
|
||||
zap.String("ca_path", params.CAFile),
|
||||
)
|
||||
|
||||
tlsInfo := transport.TLSInfo{
|
||||
CertFile: params.CertPath,
|
||||
KeyFile: params.KeyPath,
|
||||
TrustedCAFile: params.CAPath,
|
||||
CertFile: params.CertFile,
|
||||
KeyFile: params.KeyFile,
|
||||
TrustedCAFile: params.CAFile,
|
||||
}
|
||||
|
||||
tlsConfig, err := tlsInfo.ClientConfig()
|
||||
|
||||
@ -123,14 +123,18 @@ func TestEtcdInitialization(t *testing.T) {
|
||||
for _, step := range steps {
|
||||
res := t.Run(step.name, func(t *testing.T) {
|
||||
err := omniruntime.BuildEtcdPersistentState(t.Context(), &config.Params{
|
||||
Name: "instance-name",
|
||||
Storage: config.StorageParams{
|
||||
Etcd: config.EtcdParams{
|
||||
Embedded: true,
|
||||
EmbeddedDBPath: etcdDir,
|
||||
PrivateKeySource: step.args.privateKeySource,
|
||||
PublicKeyFiles: step.args.publicKeyFiles,
|
||||
Endpoints: []string{"http://localhost:0"},
|
||||
Account: config.Account{
|
||||
Name: "instance-name",
|
||||
},
|
||||
Storage: config.Storage{
|
||||
Default: config.StorageDefault{
|
||||
Etcd: config.EtcdParams{
|
||||
Embedded: true,
|
||||
EmbeddedDBPath: etcdDir,
|
||||
PrivateKeySource: step.args.privateKeySource,
|
||||
PublicKeyFiles: step.args.publicKeyFiles,
|
||||
Endpoints: []string{"http://localhost:0"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, zaptest.NewLogger(t), func(context.Context, namespaced.StateBuilder) error {
|
||||
@ -190,14 +194,18 @@ func TestEncryptDecrypt(t *testing.T) {
|
||||
for _, step := range steps {
|
||||
res := t.Run(step.name, func(t *testing.T) {
|
||||
err := omniruntime.BuildEtcdPersistentState(t.Context(), &config.Params{
|
||||
Name: "instance-name",
|
||||
Storage: config.StorageParams{
|
||||
Etcd: config.EtcdParams{
|
||||
Embedded: true,
|
||||
EmbeddedDBPath: etcdDir,
|
||||
PrivateKeySource: step.args.privateKeySource,
|
||||
PublicKeyFiles: step.args.publicKeyFiles,
|
||||
Endpoints: []string{"http://localhost:0"},
|
||||
Account: config.Account{
|
||||
Name: "instance-name",
|
||||
},
|
||||
Storage: config.Storage{
|
||||
Default: config.StorageDefault{
|
||||
Etcd: config.EtcdParams{
|
||||
Embedded: true,
|
||||
EmbeddedDBPath: etcdDir,
|
||||
PrivateKeySource: step.args.privateKeySource,
|
||||
PublicKeyFiles: step.args.publicKeyFiles,
|
||||
Endpoints: []string{"http://localhost:0"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, zaptest.NewLogger(t),
|
||||
|
||||
@ -42,7 +42,7 @@ import (
|
||||
// Validation is only syntactic - they are checked whether they are valid semver strings.
|
||||
//
|
||||
//nolint:gocognit,gocyclo,cyclop
|
||||
func clusterValidationOptions(st state.State, etcdBackupConfig config.EtcdBackupParams, embeddedDiscoveryServiceConfig config.EmbeddedDiscoveryServiceParams) []validated.StateOption {
|
||||
func clusterValidationOptions(st state.State, etcdBackupConfig config.EtcdBackup, embeddedDiscoveryServiceConfig config.EmbeddedDiscoveryService) []validated.StateOption {
|
||||
validateVersions := func(ctx context.Context, existingRes *omni.Cluster, res *omni.Cluster, skipTalosVersion, skipKubernetesVersion bool) error {
|
||||
if skipTalosVersion && skipKubernetesVersion {
|
||||
return nil
|
||||
@ -710,7 +710,7 @@ func hasUppercaseLetters(s string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func identityValidationOptions(samlConfig config.SAMLParams) []validated.StateOption {
|
||||
func identityValidationOptions(samlConfig config.SAML) []validated.StateOption {
|
||||
return []validated.StateOption{
|
||||
validated.WithCreateValidations(validated.NewCreateValidationForType(func(ctx context.Context, res *authres.Identity, _ ...state.CreateOption) error {
|
||||
var errs error
|
||||
|
||||
@ -51,14 +51,14 @@ func TestClusterValidation(t *testing.T) {
|
||||
|
||||
talos15 := "1.5.0"
|
||||
|
||||
etcdBackupConfig := config.EtcdBackupParams{
|
||||
etcdBackupConfig := config.EtcdBackup{
|
||||
TickInterval: time.Minute,
|
||||
MinInterval: time.Hour,
|
||||
MaxInterval: 24 * time.Hour,
|
||||
}
|
||||
|
||||
innerSt := state.WrapCore(namespaced.NewState(inmem.Build))
|
||||
st := validated.NewState(innerSt, omni.ClusterValidationOptions(state.WrapCore(innerSt), etcdBackupConfig, config.EmbeddedDiscoveryServiceParams{})...)
|
||||
st := validated.NewState(innerSt, omni.ClusterValidationOptions(state.WrapCore(innerSt), etcdBackupConfig, config.EmbeddedDiscoveryService{})...)
|
||||
|
||||
talosVersion1 := omnires.NewTalosVersion(resources.DefaultNamespace, "1.4.0")
|
||||
talosVersion1.TypedSpec().Value.CompatibleKubernetesVersions = []string{"1.27.0", "1.27.1"}
|
||||
@ -172,9 +172,9 @@ func TestClusterUseEmbeddedDiscoveryServiceValidation(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(t.Context(), 3*time.Second)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
buildState := func(conf config.EmbeddedDiscoveryServiceParams) (inner, outer state.State) {
|
||||
buildState := func(conf config.EmbeddedDiscoveryService) (inner, outer state.State) {
|
||||
innerSt := state.WrapCore(namespaced.NewState(inmem.Build))
|
||||
st := validated.NewState(innerSt, omni.ClusterValidationOptions(state.WrapCore(innerSt), config.EtcdBackupParams{}, conf)...)
|
||||
st := validated.NewState(innerSt, omni.ClusterValidationOptions(state.WrapCore(innerSt), config.EtcdBackup{}, conf)...)
|
||||
|
||||
return innerSt, state.WrapCore(st)
|
||||
}
|
||||
@ -182,7 +182,7 @@ func TestClusterUseEmbeddedDiscoveryServiceValidation(t *testing.T) {
|
||||
t.Run("disabled instance-wide - create", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, st := buildState(config.EmbeddedDiscoveryServiceParams{
|
||||
_, st := buildState(config.EmbeddedDiscoveryService{
|
||||
Enabled: false,
|
||||
})
|
||||
|
||||
@ -202,7 +202,7 @@ func TestClusterUseEmbeddedDiscoveryServiceValidation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// prepare a cluster which has the feature enabled, while it is disabled instance-wide
|
||||
innerSt, st := buildState(config.EmbeddedDiscoveryServiceParams{
|
||||
innerSt, st := buildState(config.EmbeddedDiscoveryService{
|
||||
Enabled: false,
|
||||
})
|
||||
|
||||
@ -231,7 +231,7 @@ func TestClusterUseEmbeddedDiscoveryServiceValidation(t *testing.T) {
|
||||
t.Run("enabled instance-wide", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, st := buildState(config.EmbeddedDiscoveryServiceParams{
|
||||
_, st := buildState(config.EmbeddedDiscoveryService{
|
||||
Enabled: true,
|
||||
})
|
||||
|
||||
@ -580,7 +580,7 @@ func TestIdentitySAMLValidation(t *testing.T) {
|
||||
t.Cleanup(cancel)
|
||||
|
||||
innerSt := state.WrapCore(namespaced.NewState(inmem.Build))
|
||||
st := validated.NewState(innerSt, omni.IdentityValidationOptions(config.SAMLParams{
|
||||
st := validated.NewState(innerSt, omni.IdentityValidationOptions(config.SAML{
|
||||
Enabled: true,
|
||||
})...)
|
||||
|
||||
@ -633,7 +633,7 @@ func TestCreateIdentityValidation(t *testing.T) {
|
||||
t.Cleanup(cancel)
|
||||
|
||||
innerSt := state.WrapCore(namespaced.NewState(inmem.Build))
|
||||
st := validated.NewState(innerSt, omni.IdentityValidationOptions(config.SAMLParams{})...)
|
||||
st := validated.NewState(innerSt, omni.IdentityValidationOptions(config.SAML{})...)
|
||||
|
||||
assert := assert.New(t)
|
||||
|
||||
|
||||
@ -305,7 +305,7 @@ func (v *State) advertisedEndpoints(_ context.Context, ptr resource.Pointer) (*v
|
||||
|
||||
res := virtual.NewAdvertisedEndpoints()
|
||||
|
||||
res.TypedSpec().Value.GrpcApiUrl = config.Config.APIURL
|
||||
res.TypedSpec().Value.GrpcApiUrl = config.Config.Services.API.URL()
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
@ -199,8 +199,8 @@ func (r *Runtime) GetTalosconfigRaw(context *common.Context, identity string) ([
|
||||
Identity: identity,
|
||||
}
|
||||
|
||||
contextName := config.Config.Name
|
||||
apiURL := config.Config.APIURL
|
||||
contextName := config.Config.Account.Name
|
||||
apiURL := config.Config.Services.API.URL()
|
||||
|
||||
cluster := ""
|
||||
|
||||
|
||||
@ -32,7 +32,7 @@ func NewHandler(state state.State, cfg *specs.AuthConfigSpec_SAML, logger *zap.L
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rootURL, err := url.Parse(config.Config.APIURL)
|
||||
rootURL, err := url.Parse(config.Config.Services.API.URL())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -9,7 +9,6 @@ package backend
|
||||
import (
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@ -22,7 +21,6 @@ import (
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
@ -35,7 +33,6 @@ import (
|
||||
"github.com/cosi-project/runtime/pkg/state"
|
||||
protobufserver "github.com/cosi-project/runtime/pkg/state/protobuf/server"
|
||||
"github.com/crewjam/saml/samlsp"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
|
||||
grpc_zap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap"
|
||||
grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery"
|
||||
@ -63,7 +60,6 @@ import (
|
||||
"github.com/siderolabs/omni/client/pkg/omni/resources"
|
||||
authres "github.com/siderolabs/omni/client/pkg/omni/resources/auth"
|
||||
omnires "github.com/siderolabs/omni/client/pkg/omni/resources/omni"
|
||||
"github.com/siderolabs/omni/client/pkg/panichandler"
|
||||
"github.com/siderolabs/omni/internal/backend/debug"
|
||||
"github.com/siderolabs/omni/internal/backend/dns"
|
||||
"github.com/siderolabs/omni/internal/backend/factory"
|
||||
@ -80,6 +76,7 @@ import (
|
||||
"github.com/siderolabs/omni/internal/backend/runtime/omni"
|
||||
"github.com/siderolabs/omni/internal/backend/runtime/talos"
|
||||
"github.com/siderolabs/omni/internal/backend/saml"
|
||||
"github.com/siderolabs/omni/internal/backend/services"
|
||||
"github.com/siderolabs/omni/internal/backend/workloadproxy"
|
||||
"github.com/siderolabs/omni/internal/frontend"
|
||||
"github.com/siderolabs/omni/internal/memconn"
|
||||
@ -116,19 +113,16 @@ type Server struct {
|
||||
siderolinkEventsCh chan<- *omnires.MachineStatusSnapshot
|
||||
installEventCh chan<- resource.ID
|
||||
|
||||
proxyServer Proxy
|
||||
bindAddress string
|
||||
metricsBindAddress string
|
||||
pprofBindAddress string
|
||||
k8sProxyBindAddress string
|
||||
keyFile string
|
||||
certFile string
|
||||
workloadProxyKey []byte
|
||||
pprofBindAddress string
|
||||
apiService config.Service
|
||||
metricsService config.Service
|
||||
devServerProxy config.DevServerProxyService
|
||||
k8sProxyService config.KubernetesProxyService
|
||||
workloadProxyKey []byte
|
||||
}
|
||||
|
||||
// NewServer creates new HTTP server.
|
||||
func NewServer(
|
||||
bindAddress, metricsBindAddress, k8sProxyBindAddress, pprofBindAddress string,
|
||||
dnsService *dns.Service,
|
||||
workloadProxyReconciler *workloadproxy.Reconciler,
|
||||
imageFactoryClient *imagefactory.Client,
|
||||
@ -139,8 +133,6 @@ func NewServer(
|
||||
talosRuntime *talos.Runtime,
|
||||
logHandler *siderolink.LogHandler,
|
||||
authConfig *authres.Config,
|
||||
keyFile, certFile string,
|
||||
proxyServer Proxy,
|
||||
auditor Auditor,
|
||||
logger *zap.Logger,
|
||||
) (*Server, error) {
|
||||
@ -156,13 +148,11 @@ func NewServer(
|
||||
linkCounterDeltaCh: linkCounterDeltaCh,
|
||||
siderolinkEventsCh: siderolinkEventsCh,
|
||||
installEventCh: installEventCh,
|
||||
proxyServer: proxyServer,
|
||||
bindAddress: bindAddress,
|
||||
metricsBindAddress: metricsBindAddress,
|
||||
pprofBindAddress: pprofBindAddress,
|
||||
k8sProxyBindAddress: k8sProxyBindAddress,
|
||||
keyFile: keyFile,
|
||||
certFile: certFile,
|
||||
devServerProxy: config.Config.Services.DevServerProxy,
|
||||
apiService: config.Config.Services.API,
|
||||
metricsService: config.Config.Services.Metrics,
|
||||
pprofBindAddress: config.Config.Debug.Pprof.Endpoint,
|
||||
k8sProxyService: config.Config.Services.KubernetesProxy,
|
||||
}
|
||||
|
||||
k8sruntime, err := kubernetes.New(omniRuntime.State())
|
||||
@ -237,28 +227,22 @@ func (s *Server) Run(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
var crtData *certData
|
||||
|
||||
if s.certFile != "" && s.keyFile != "" {
|
||||
crtData = &certData{certFile: s.certFile, keyFile: s.keyFile}
|
||||
}
|
||||
|
||||
workloadProxyHandler, err := s.workloadProxyHandler(mux)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create workload proxy handler: %w", err)
|
||||
}
|
||||
|
||||
apiSrv := s.makeAPIServer(workloadProxyHandler, proxyServer, crtData)
|
||||
apiSrv := s.makeAPIServer(workloadProxyHandler, proxyServer)
|
||||
|
||||
fns := []func() error{
|
||||
func() error { return proxyServer.Serve(ctx, gtwyDialsTo) },
|
||||
func() error { return actualSrv.Serve(ctx, prxDialsTo) },
|
||||
func() error { return apiSrv.Run(ctx) },
|
||||
func() error { return runMetricsServer(ctx, s.metricsBindAddress, s.logger) },
|
||||
func() error { return s.runMetricsServer(ctx) },
|
||||
func() error {
|
||||
return runK8sProxyServer(ctx, s.k8sProxyBindAddress, oidcStorage, crtData, s.omniRuntime.State(), s.auditor, s.logger)
|
||||
return s.runK8sProxyServer(ctx, oidcStorage)
|
||||
},
|
||||
func() error { return s.proxyServer.Run(ctx, apiSrv.Handler(), s.logger) },
|
||||
func() error { return s.runDevProxyServer(ctx, apiSrv.Handler()) },
|
||||
func() error { return s.logHandler.Start(ctx) },
|
||||
func() error { return s.runMachineAPI(ctx) },
|
||||
func() error { return s.auditor.RunCleanup(ctx) },
|
||||
@ -272,11 +256,13 @@ func (s *Server) Run(ctx context.Context) error {
|
||||
eg.Go(fn)
|
||||
}
|
||||
|
||||
if err = runLocalResourceServer(ctx, runtimeState, serverOptions, eg, s.logger); err != nil {
|
||||
return fmt.Errorf("failed to run local resource server: %w", err)
|
||||
if config.Config.Services.LocalResourceService.Enabled {
|
||||
if err = runLocalResourceServer(ctx, runtimeState, serverOptions, eg, s.logger); err != nil {
|
||||
return fmt.Errorf("failed to run local resource server: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if config.Config.EmbeddedDiscoveryService.Enabled {
|
||||
if config.Config.Services.EmbeddedDiscoveryService.Enabled {
|
||||
eg.Go(func() error {
|
||||
if err = runEmbeddedDiscoveryService(ctx, s.logger); err != nil {
|
||||
return fmt.Errorf("failed to run discovery server over Siderolink: %w", err)
|
||||
@ -286,7 +272,7 @@ func (s *Server) Run(ctx context.Context) error {
|
||||
})
|
||||
}
|
||||
|
||||
if config.Config.InitialServiceAccount.Enabled {
|
||||
if config.Config.Auth.InitialServiceAccount.Enabled {
|
||||
if err = s.createInitialServiceAccount(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -298,10 +284,11 @@ func (s *Server) Run(ctx context.Context) error {
|
||||
func (s *Server) makeMux(oidcProvider *oidc.Provider) (*http.ServeMux, error) {
|
||||
imageFactoryHandler := handler.NewAuthConfig(
|
||||
handler.NewSignature(
|
||||
&factory.Handler{
|
||||
State: s.omniRuntime.State(),
|
||||
Logger: s.logger.With(logging.Component("factory_proxy")),
|
||||
},
|
||||
factory.NewHandler(
|
||||
s.omniRuntime.State(),
|
||||
s.logger.With(logging.Component("factory_proxy")),
|
||||
&config.Config.Registries,
|
||||
),
|
||||
s.authenticatorFunc(),
|
||||
s.logger,
|
||||
),
|
||||
@ -557,15 +544,15 @@ func (s *Server) authenticatorFunc() auth.AuthenticatorFunc {
|
||||
}
|
||||
|
||||
func (s *Server) runMachineAPI(ctx context.Context) error {
|
||||
wgAddress := config.Config.SiderolinkWireguardBindAddress
|
||||
wgAddress := config.Config.Services.Siderolink.WireGuard.BindEndpoint
|
||||
|
||||
params := siderolink.Params{
|
||||
WireguardEndpoint: wgAddress,
|
||||
AdvertisedEndpoint: config.Config.SiderolinkWireguardAdvertisedAddress,
|
||||
APIEndpoint: config.Config.MachineAPIBindAddress,
|
||||
Cert: config.Config.MachineAPICertFile,
|
||||
Key: config.Config.MachineAPIKeyFile,
|
||||
EventSinkPort: strconv.Itoa(config.Config.EventSinkPort),
|
||||
AdvertisedEndpoint: config.Config.Services.Siderolink.WireGuard.AdvertisedEndpoint,
|
||||
MachineAPIEndpoint: config.Config.Services.MachineAPI.BindEndpoint,
|
||||
MachineAPITLSCert: config.Config.Services.MachineAPI.CertFile,
|
||||
MachineAPITLSKey: config.Config.Services.MachineAPI.KeyFile,
|
||||
EventSinkPort: strconv.Itoa(config.Config.Services.Siderolink.EventSinkPort),
|
||||
}
|
||||
|
||||
omniState := s.omniRuntime.State()
|
||||
@ -614,9 +601,9 @@ func (s *Server) runMachineAPI(ctx context.Context) error {
|
||||
eg.Go(func() error {
|
||||
return slink.Run(groupCtx,
|
||||
siderolink.ListenHost,
|
||||
strconv.Itoa(config.Config.EventSinkPort),
|
||||
strconv.Itoa(config.Config.Services.Siderolink.EventSinkPort),
|
||||
strconv.Itoa(talosconstants.TrustdPort),
|
||||
strconv.Itoa(config.Config.LogServerPort),
|
||||
strconv.Itoa(config.Config.Services.Siderolink.LogServerPort),
|
||||
)
|
||||
})
|
||||
|
||||
@ -637,7 +624,7 @@ func (s *Server) workloadProxyHandler(next http.Handler) (http.Handler, error) {
|
||||
return nil, fmt.Errorf("failed to create pgp signature validator: %w", err)
|
||||
}
|
||||
|
||||
mainURL, err := url.Parse(config.Config.APIURL)
|
||||
mainURL, err := url.Parse(config.Config.Services.API.URL())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse API URL: %w", err)
|
||||
}
|
||||
@ -647,15 +634,15 @@ func (s *Server) workloadProxyHandler(next http.Handler) (http.Handler, error) {
|
||||
s.workloadProxyReconciler,
|
||||
pgpSignatureValidator,
|
||||
mainURL,
|
||||
config.Config.WorkloadProxying.Subdomain,
|
||||
config.Config.Services.WorkloadProxy.Subdomain,
|
||||
s.logger.With(logging.Component("workload_proxy_handler")),
|
||||
s.workloadProxyKey,
|
||||
)
|
||||
}
|
||||
|
||||
func (s *Server) makeAPIServer(regular http.Handler, grpcServer *grpcServer, data *certData) *apiServer {
|
||||
func (s *Server) makeAPIServer(regular http.Handler, grpcServer *grpcServer) *apiServer {
|
||||
wrap := func(fn func(w http.ResponseWriter, req *http.Request)) http.Handler {
|
||||
if data != nil {
|
||||
if s.apiService.IsSecure() {
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
|
||||
@ -663,40 +650,40 @@ func (s *Server) makeAPIServer(regular http.Handler, grpcServer *grpcServer, dat
|
||||
return h2c.NewHandler(http.HandlerFunc(fn), &http2.Server{})
|
||||
}
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: s.bindAddress,
|
||||
Handler: wrap(func(w http.ResponseWriter, req *http.Request) {
|
||||
if req.ProtoMajor == 2 && strings.HasPrefix(
|
||||
req.Header.Get("Content-Type"), "application/grpc") {
|
||||
// grpcServer provides top-level gRPC proxy handler.
|
||||
grpcServer.ServeHTTP(w, setRealIPRequest(req))
|
||||
handler := wrap(func(w http.ResponseWriter, req *http.Request) {
|
||||
if req.ProtoMajor == 2 && strings.HasPrefix(
|
||||
req.Header.Get("Content-Type"), "application/grpc") {
|
||||
// grpcServer provides top-level gRPC proxy handler.
|
||||
grpcServer.ServeHTTP(w, setRealIPRequest(req))
|
||||
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// handler contains "regular" HTTP handlers
|
||||
regular.ServeHTTP(w, req)
|
||||
}),
|
||||
}
|
||||
// handler contains "regular" HTTP handlers
|
||||
regular.ServeHTTP(w, req)
|
||||
})
|
||||
|
||||
return &apiServer{
|
||||
srv: srv,
|
||||
cert: data,
|
||||
logger: s.logger.With(zap.String("server", s.bindAddress), zap.String("server_type", "api")),
|
||||
srv: services.NewFromConfig(
|
||||
&s.apiService,
|
||||
handler,
|
||||
),
|
||||
handler: handler,
|
||||
logger: s.logger.With(zap.String("server", s.apiService.BindEndpoint), zap.String("server_type", "api")),
|
||||
}
|
||||
}
|
||||
|
||||
type apiServer struct {
|
||||
srv *http.Server
|
||||
cert *certData
|
||||
logger *zap.Logger
|
||||
srv *services.Server
|
||||
handler http.Handler
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
func (s *apiServer) Run(ctx context.Context) error {
|
||||
return (&server{server: s.srv, certData: s.cert}).Run(ctx, s.logger)
|
||||
return s.srv.Run(ctx, s.logger)
|
||||
}
|
||||
|
||||
func (s *apiServer) Handler() http.Handler { return s.srv.Handler }
|
||||
func (s *apiServer) Handler() http.Handler { return s.handler }
|
||||
|
||||
func recoveryHandler(logger *zap.Logger) grpc_recovery.RecoveryHandlerFunc {
|
||||
return func(p any) error {
|
||||
@ -865,33 +852,32 @@ func getOmnictlDownloads(dir string) (http.Handler, error) {
|
||||
return http.FileServer(http.Dir(dir)), nil
|
||||
}
|
||||
|
||||
func runMetricsServer(ctx context.Context, bindAddress string, logger *zap.Logger) error {
|
||||
func (s *Server) runDevProxyServer(ctx context.Context, next http.Handler) error {
|
||||
handler, err := services.NewFrontendHandler(s.devServerProxy.ProxyTo, s.logger)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set up frontend handler: %w", err)
|
||||
}
|
||||
|
||||
return services.NewProxy(s.devServerProxy, handler).Run(ctx, next, s.logger)
|
||||
}
|
||||
|
||||
func (s *Server) runMetricsServer(ctx context.Context) error {
|
||||
var metricsMux http.ServeMux
|
||||
|
||||
metricsMux.Handle("/metrics", promhttp.Handler())
|
||||
|
||||
metricsServer := &http.Server{
|
||||
Addr: bindAddress,
|
||||
Handler: &metricsMux,
|
||||
}
|
||||
logger := s.logger.With(zap.String("server", s.metricsService.BindEndpoint), zap.String("server_type", "metrics"))
|
||||
|
||||
logger = logger.With(zap.String("server", bindAddress), zap.String("server_type", "metrics"))
|
||||
|
||||
return (&server{server: metricsServer}).Run(ctx, logger)
|
||||
return services.NewFromConfig(&s.metricsService, &metricsMux).Run(ctx, logger)
|
||||
}
|
||||
|
||||
type oidcStore interface {
|
||||
GetPublicKeyByID(keyID string) (any, error)
|
||||
}
|
||||
|
||||
func runK8sProxyServer(
|
||||
func (s *Server) runK8sProxyServer(
|
||||
ctx context.Context,
|
||||
bindAddress string,
|
||||
oidcStorage oidcStore,
|
||||
data *certData,
|
||||
runtimeState state.State,
|
||||
wrapper k8sproxy.MiddlewareWrapper,
|
||||
logger *zap.Logger,
|
||||
) error {
|
||||
keyFunc := func(_ context.Context, keyID string) (any, error) {
|
||||
return oidcStorage.GetPublicKeyByID(keyID)
|
||||
@ -900,7 +886,7 @@ func runK8sProxyServer(
|
||||
clusterUUIDResolver := func(ctx context.Context, clusterID string) (resource.ID, error) {
|
||||
ctx = actor.MarkContextAsInternalActor(ctx)
|
||||
|
||||
uuid, resolveErr := safe.StateGetByID[*omnires.ClusterUUID](ctx, runtimeState, clusterID)
|
||||
uuid, resolveErr := safe.StateGetByID[*omnires.ClusterUUID](ctx, s.omniRuntime.State(), clusterID)
|
||||
if resolveErr != nil {
|
||||
return "", fmt.Errorf("failed to resolve cluster ID to UUID: %w", resolveErr)
|
||||
}
|
||||
@ -908,7 +894,7 @@ func runK8sProxyServer(
|
||||
return uuid.TypedSpec().Value.Uuid, nil
|
||||
}
|
||||
|
||||
k8sProxyHandler, err := k8sproxy.NewHandler(keyFunc, clusterUUIDResolver, wrapper, logger)
|
||||
k8sProxyHandler, err := k8sproxy.NewHandler(keyFunc, clusterUUIDResolver, s.auditor, s.logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -918,19 +904,14 @@ func runK8sProxyServer(
|
||||
k8sProxy := monitoring.NewHandler(
|
||||
logging.NewHandler(
|
||||
k8sProxyHandler,
|
||||
logger.With(zap.String("handler", "k8s_proxy")),
|
||||
s.logger.With(zap.String("handler", "k8s_proxy")),
|
||||
),
|
||||
prometheus.Labels{"handler": "k8s-proxy"},
|
||||
)
|
||||
|
||||
k8sProxyServer := &http.Server{
|
||||
Addr: bindAddress,
|
||||
Handler: k8sProxy,
|
||||
}
|
||||
logger := s.logger.With(zap.String("server", s.k8sProxyService.BindEndpoint), zap.String("server_type", "k8s_proxy"))
|
||||
|
||||
logger = logger.With(zap.String("server", bindAddress), zap.String("server_type", "k8s_proxy"))
|
||||
|
||||
return (&server{server: k8sProxyServer, certData: data}).Run(ctx, logger)
|
||||
return services.NewFromConfig(&s.k8sProxyService, k8sProxy).Run(ctx, logger)
|
||||
}
|
||||
|
||||
// setRealIPRequest extracts ip from the request and sets it to the X-Forwarded-For header if there is no
|
||||
@ -952,183 +933,8 @@ func setRealIPRequest(req *http.Request) *http.Request {
|
||||
return newReq
|
||||
}
|
||||
|
||||
type server struct {
|
||||
server *http.Server
|
||||
certData *certData
|
||||
}
|
||||
|
||||
type certData struct {
|
||||
cert tls.Certificate
|
||||
certFile string
|
||||
keyFile string
|
||||
mu sync.Mutex
|
||||
loaded bool
|
||||
}
|
||||
|
||||
func (c *certData) load() error {
|
||||
cert, err := tls.LoadX509KeyPair(c.certFile, c.keyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
c.loaded = true
|
||||
c.cert = cert
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *certData) getCert() (*tls.Certificate, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if !c.loaded {
|
||||
return nil, fmt.Errorf("the cert wasn't loaded yet")
|
||||
}
|
||||
|
||||
return &c.cert, nil
|
||||
}
|
||||
|
||||
func (c *certData) runWatcher(ctx context.Context, logger *zap.Logger) error {
|
||||
w, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating fsnotify watcher: %w", err)
|
||||
}
|
||||
defer w.Close() //nolint:errcheck
|
||||
|
||||
if err = w.Add(c.certFile); err != nil {
|
||||
return fmt.Errorf("error adding watch for file %s: %w", c.certFile, err)
|
||||
}
|
||||
|
||||
if err = w.Add(c.keyFile); err != nil {
|
||||
return fmt.Errorf("error adding watch for file %s: %w", c.keyFile, err)
|
||||
}
|
||||
|
||||
handleEvent := func(e fsnotify.Event) error {
|
||||
defer func() {
|
||||
if err = c.load(); err != nil {
|
||||
logger.Error("failed to load certs", zap.Error(err))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info("reloaded certs")
|
||||
}()
|
||||
|
||||
if !e.Has(fsnotify.Remove) && !e.Has(fsnotify.Rename) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err = w.Remove(e.Name); err != nil {
|
||||
logger.Error("failed to remove file watch, it may have been deleted", zap.String("file", e.Name), zap.Error(err))
|
||||
}
|
||||
|
||||
if err = w.Add(e.Name); err != nil {
|
||||
return fmt.Errorf("error adding watch for file %s: %w", e.Name, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case e := <-w.Events:
|
||||
if err = handleEvent(e); err != nil {
|
||||
return err
|
||||
}
|
||||
case err = <-w.Errors:
|
||||
return fmt.Errorf("received fsnotify error: %w", err)
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) Run(ctx context.Context, logger *zap.Logger) error {
|
||||
logger.Info("server starting")
|
||||
defer logger.Info("server stopped")
|
||||
|
||||
stop := xcontext.AfterFuncSync(ctx, func() { //nolint:contextcheck
|
||||
logger.Info("server stopping")
|
||||
|
||||
shutdownCtx, shutdownCtxCancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer shutdownCtxCancel()
|
||||
|
||||
err := s.shutdown(shutdownCtx)
|
||||
if err != nil {
|
||||
logger.Error("failed to gracefully stop server", zap.Error(err))
|
||||
}
|
||||
})
|
||||
|
||||
defer stop()
|
||||
|
||||
if err := s.listenAndServe(ctx, logger); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
return fmt.Errorf("failed to serve: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *server) listenAndServe(ctx context.Context, logger *zap.Logger) error {
|
||||
if s.certData == nil {
|
||||
return s.server.ListenAndServe()
|
||||
}
|
||||
|
||||
if err := s.certData.load(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.server.TLSConfig = &tls.Config{
|
||||
GetCertificate: func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
return s.certData.getCert()
|
||||
},
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
eg := panichandler.NewErrGroup()
|
||||
|
||||
eg.Go(func() error {
|
||||
for {
|
||||
err := s.certData.runWatcher(ctx, logger)
|
||||
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
logger.Error("cert watcher crashed, restarting in 5 seconds", zap.Error(err))
|
||||
|
||||
time.Sleep(time.Second * 5)
|
||||
}
|
||||
})
|
||||
|
||||
eg.Go(func() error {
|
||||
defer cancel()
|
||||
|
||||
return s.server.ListenAndServeTLS("", "")
|
||||
})
|
||||
|
||||
return eg.Wait()
|
||||
}
|
||||
|
||||
func (s *server) shutdown(ctx context.Context) error {
|
||||
err := s.server.Shutdown(ctx)
|
||||
if !errors.Is(ctx.Err(), err) {
|
||||
return err
|
||||
}
|
||||
|
||||
if closeErr := s.server.Close(); closeErr != nil {
|
||||
return fmt.Errorf("failed to close server: %w", closeErr)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func runLocalResourceServer(ctx context.Context, st state.CoreState, serverOptions []grpc.ServerOption, eg *errgroup.Group, logger *zap.Logger) error {
|
||||
listener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", config.Config.LocalResourceServerPort))
|
||||
listener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", config.Config.Services.LocalResourceService.Port))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to listen: %w", err)
|
||||
}
|
||||
@ -1191,7 +997,7 @@ func runLocalResourceServer(ctx context.Context, st state.CoreState, serverOptio
|
||||
}
|
||||
|
||||
func (s *Server) createInitialServiceAccount(ctx context.Context) error {
|
||||
serviceAccountEmail := config.Config.InitialServiceAccount.Name + access.ServiceAccountNameSuffix
|
||||
serviceAccountEmail := config.Config.Auth.InitialServiceAccount.Name + access.ServiceAccountNameSuffix
|
||||
|
||||
identity, err := safe.ReaderGetByID[*authres.Identity](ctx, s.omniRuntime.State(), serviceAccountEmail)
|
||||
if err != nil && !state.IsNotFoundError(err) {
|
||||
@ -1203,7 +1009,7 @@ func (s *Server) createInitialServiceAccount(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
key, err := pgp.GenerateKey(config.Config.InitialServiceAccount.Name, "automation initial", serviceAccountEmail, config.Config.InitialServiceAccount.Lifetime)
|
||||
key, err := pgp.GenerateKey(config.Config.Auth.InitialServiceAccount.Name, "automation initial", serviceAccountEmail, config.Config.Auth.InitialServiceAccount.Lifetime)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create initial service account key, generate failed: %w", err)
|
||||
}
|
||||
@ -1216,8 +1022,8 @@ func (s *Server) createInitialServiceAccount(ctx context.Context) error {
|
||||
_, err = serviceaccountmgmt.Create(
|
||||
ctx,
|
||||
s.omniRuntime.State(),
|
||||
config.Config.InitialServiceAccount.Name,
|
||||
config.Config.InitialServiceAccount.Role,
|
||||
config.Config.Auth.InitialServiceAccount.Name,
|
||||
config.Config.Auth.InitialServiceAccount.Role,
|
||||
false,
|
||||
[]byte(k),
|
||||
)
|
||||
@ -1225,15 +1031,15 @@ func (s *Server) createInitialServiceAccount(ctx context.Context) error {
|
||||
return fmt.Errorf("failed to create initial service account key: %w", err)
|
||||
}
|
||||
|
||||
data, err := serviceaccount.Encode(config.Config.InitialServiceAccount.Name, key)
|
||||
data, err := serviceaccount.Encode(config.Config.Auth.InitialServiceAccount.Name, key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create initial service account key, failed to encode: %w", err)
|
||||
}
|
||||
|
||||
if err = os.WriteFile(config.Config.InitialServiceAccount.KeyPath, []byte(data), 0o640); err != nil {
|
||||
if err = os.WriteFile(config.Config.Auth.InitialServiceAccount.KeyPath, []byte(data), 0o640); err != nil {
|
||||
return fmt.Errorf(
|
||||
"failed to create initial service account key, failed to write key to path %q: %w",
|
||||
config.Config.InitialServiceAccount.KeyPath,
|
||||
config.Config.Auth.InitialServiceAccount.KeyPath,
|
||||
err,
|
||||
)
|
||||
}
|
||||
@ -1243,7 +1049,7 @@ func (s *Server) createInitialServiceAccount(ctx context.Context) error {
|
||||
|
||||
// runEmbeddedDiscoveryService runs an embedded discovery service over Siderolink.
|
||||
func runEmbeddedDiscoveryService(ctx context.Context, logger *zap.Logger) error {
|
||||
logLevel, err := zapcore.ParseLevel(config.Config.EmbeddedDiscoveryService.LogLevel)
|
||||
logLevel, err := zapcore.ParseLevel(config.Config.Services.EmbeddedDiscoveryService.LogLevel)
|
||||
if err != nil {
|
||||
logLevel = zapcore.WarnLevel
|
||||
|
||||
@ -1254,13 +1060,13 @@ func runEmbeddedDiscoveryService(ctx context.Context, logger *zap.Logger) error
|
||||
|
||||
if err = retry.Constant(30*time.Second, retry.WithUnits(time.Second)).RetryWithContext(ctx, func(context.Context) error {
|
||||
err = service.Run(ctx, service.Options{
|
||||
ListenAddr: net.JoinHostPort(siderolink.ListenHost, strconv.Itoa(config.Config.EmbeddedDiscoveryService.Port)),
|
||||
ListenAddr: net.JoinHostPort(siderolink.ListenHost, strconv.Itoa(config.Config.Services.EmbeddedDiscoveryService.Port)),
|
||||
GCInterval: time.Minute,
|
||||
MetricsRegisterer: registerer,
|
||||
|
||||
SnapshotsEnabled: config.Config.EmbeddedDiscoveryService.SnapshotsEnabled,
|
||||
SnapshotInterval: config.Config.EmbeddedDiscoveryService.SnapshotInterval,
|
||||
SnapshotPath: config.Config.EmbeddedDiscoveryService.SnapshotPath,
|
||||
SnapshotsEnabled: config.Config.Services.EmbeddedDiscoveryService.SnapshotsEnabled,
|
||||
SnapshotInterval: config.Config.Services.EmbeddedDiscoveryService.SnapshotsInterval,
|
||||
SnapshotPath: config.Config.Services.EmbeddedDiscoveryService.SnapshotsPath,
|
||||
}, logger.WithOptions(zap.IncreaseLevel(logLevel)).With(logging.Component("discovery_service")))
|
||||
|
||||
if errors.Is(err, syscall.EADDRNOTAVAIL) {
|
||||
@ -1284,14 +1090,9 @@ func runPprofServer(ctx context.Context, bindAddress string, l *zap.Logger) erro
|
||||
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
||||
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: bindAddress,
|
||||
Handler: mux,
|
||||
}
|
||||
|
||||
l = l.With(zap.String("server", bindAddress), zap.String("server_type", "pprof"))
|
||||
|
||||
return (&server{server: srv}).Run(ctx, l)
|
||||
return services.NewInsecure(bindAddress, mux).Run(ctx, l)
|
||||
}
|
||||
|
||||
//nolint:unparam
|
||||
|
||||
233
internal/backend/services/http_server.go
Normal file
233
internal/backend/services/http_server.go
Normal file
@ -0,0 +1,233 @@
|
||||
// Copyright (c) 2025 Sidero Labs, Inc.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the LICENSE file.
|
||||
|
||||
// Package services contains HTTP servers.
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/siderolabs/omni/client/pkg/panichandler"
|
||||
"github.com/siderolabs/omni/internal/pkg/config"
|
||||
"github.com/siderolabs/omni/internal/pkg/xcontext"
|
||||
)
|
||||
|
||||
// NewFromConfig creates a new Server from the config.Service.
|
||||
func NewFromConfig(service config.HTTPService, handler http.Handler) *Server {
|
||||
var cert *certData
|
||||
|
||||
if service.IsSecure() {
|
||||
cert = &certData{
|
||||
certFile: service.GetCertFile(),
|
||||
keyFile: service.GetKeyFile(),
|
||||
}
|
||||
}
|
||||
|
||||
return &Server{
|
||||
server: &http.Server{
|
||||
Addr: service.GetBindEndpoint(),
|
||||
Handler: handler,
|
||||
},
|
||||
certData: cert,
|
||||
}
|
||||
}
|
||||
|
||||
// NewInsecure creates a new Server.
|
||||
func NewInsecure(endpoint string, handler http.Handler) *Server {
|
||||
return &Server{
|
||||
server: &http.Server{
|
||||
Addr: endpoint,
|
||||
Handler: handler,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Server is the HTTP server.
|
||||
type Server struct {
|
||||
server *http.Server
|
||||
certData *certData
|
||||
}
|
||||
|
||||
type certData struct {
|
||||
cert tls.Certificate
|
||||
certFile string
|
||||
keyFile string
|
||||
mu sync.Mutex
|
||||
loaded bool
|
||||
}
|
||||
|
||||
func (c *certData) load() error {
|
||||
cert, err := tls.LoadX509KeyPair(c.certFile, c.keyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
c.loaded = true
|
||||
c.cert = cert
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *certData) getCert() (*tls.Certificate, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if !c.loaded {
|
||||
return nil, fmt.Errorf("the cert wasn't loaded yet")
|
||||
}
|
||||
|
||||
return &c.cert, nil
|
||||
}
|
||||
|
||||
func (c *certData) runWatcher(ctx context.Context, logger *zap.Logger) error {
|
||||
w, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating fsnotify watcher: %w", err)
|
||||
}
|
||||
defer w.Close() //nolint:errcheck
|
||||
|
||||
if err = w.Add(c.certFile); err != nil {
|
||||
return fmt.Errorf("error adding watch for file %s: %w", c.certFile, err)
|
||||
}
|
||||
|
||||
if err = w.Add(c.keyFile); err != nil {
|
||||
return fmt.Errorf("error adding watch for file %s: %w", c.keyFile, err)
|
||||
}
|
||||
|
||||
handleEvent := func(e fsnotify.Event) error {
|
||||
defer func() {
|
||||
if err = c.load(); err != nil {
|
||||
logger.Error("failed to load certs", zap.Error(err))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info("reloaded certs")
|
||||
}()
|
||||
|
||||
if !e.Has(fsnotify.Remove) && !e.Has(fsnotify.Rename) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err = w.Remove(e.Name); err != nil {
|
||||
logger.Error("failed to remove file watch, it may have been deleted", zap.String("file", e.Name), zap.Error(err))
|
||||
}
|
||||
|
||||
if err = w.Add(e.Name); err != nil {
|
||||
return fmt.Errorf("error adding watch for file %s: %w", e.Name, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case e := <-w.Events:
|
||||
if err = handleEvent(e); err != nil {
|
||||
return err
|
||||
}
|
||||
case err = <-w.Errors:
|
||||
return fmt.Errorf("received fsnotify error: %w", err)
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run the server.
|
||||
func (s *Server) Run(ctx context.Context, logger *zap.Logger) error {
|
||||
logger.Info("server starting", zap.Bool("secure", s.certData != nil))
|
||||
defer logger.Info("server stopped")
|
||||
|
||||
stop := xcontext.AfterFuncSync(ctx, func() { //nolint:contextcheck
|
||||
logger.Info("server stopping")
|
||||
|
||||
shutdownCtx, shutdownCtxCancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer shutdownCtxCancel()
|
||||
|
||||
err := s.shutdown(shutdownCtx)
|
||||
if err != nil {
|
||||
logger.Error("failed to gracefully stop server", zap.Error(err))
|
||||
}
|
||||
})
|
||||
|
||||
defer stop()
|
||||
|
||||
if err := s.listenAndServe(ctx, logger); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
logger.Error("failed to serve", zap.Error(err))
|
||||
|
||||
return fmt.Errorf("failed to serve: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) listenAndServe(ctx context.Context, logger *zap.Logger) error {
|
||||
if s.certData == nil {
|
||||
return s.server.ListenAndServe()
|
||||
}
|
||||
|
||||
if err := s.certData.load(); err != nil {
|
||||
return fmt.Errorf("failed to load certs: %w", err)
|
||||
}
|
||||
|
||||
s.server.TLSConfig = &tls.Config{
|
||||
GetCertificate: func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
return s.certData.getCert()
|
||||
},
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
eg := panichandler.NewErrGroup()
|
||||
|
||||
eg.Go(func() error {
|
||||
for {
|
||||
err := s.certData.runWatcher(ctx, logger)
|
||||
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
logger.Error("cert watcher crashed, restarting in 5 seconds", zap.Error(err))
|
||||
|
||||
time.Sleep(time.Second * 5)
|
||||
}
|
||||
})
|
||||
|
||||
eg.Go(func() error {
|
||||
defer cancel()
|
||||
|
||||
return s.server.ListenAndServeTLS("", "")
|
||||
})
|
||||
|
||||
return eg.Wait()
|
||||
}
|
||||
|
||||
func (s *Server) shutdown(ctx context.Context) error {
|
||||
err := s.server.Shutdown(ctx)
|
||||
if !errors.Is(ctx.Err(), err) {
|
||||
return err
|
||||
}
|
||||
|
||||
if closeErr := s.server.Close(); closeErr != nil {
|
||||
return fmt.Errorf("failed to close server: %w", closeErr)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
@ -3,7 +3,7 @@
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the LICENSE file.
|
||||
|
||||
package backend
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -16,6 +16,8 @@ import (
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/siderolabs/omni/internal/pkg/config"
|
||||
)
|
||||
|
||||
// Proxy is a proxy server.
|
||||
@ -23,28 +25,24 @@ type Proxy interface {
|
||||
Run(ctx context.Context, next http.Handler, logger *zap.Logger) error
|
||||
}
|
||||
|
||||
// NewProxyServer creates a new proxy server. If the destination is empty, the proxy server will be a no-op.
|
||||
func NewProxyServer(bindAddr string, proxyTo http.Handler, keyFile, certFile string) Proxy {
|
||||
// NewProxy creates a new proxy server. If the destination is empty, the proxy server will be a no-op.
|
||||
func NewProxy(config config.DevServerProxyService, handler http.Handler) Proxy {
|
||||
switch {
|
||||
case bindAddr == "":
|
||||
case config.BindEndpoint == "":
|
||||
return &nopProxy{reason: "bind address is empty"}
|
||||
case proxyTo == nopHandler:
|
||||
case config.ProxyTo == "":
|
||||
return &nopProxy{reason: "proxy destination is empty"}
|
||||
default:
|
||||
return &httpProxy{
|
||||
bindAddr: bindAddr,
|
||||
proxyTo: proxyTo,
|
||||
keyFile: keyFile,
|
||||
certFile: certFile,
|
||||
config: config,
|
||||
proxyTo: handler,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type httpProxy struct {
|
||||
proxyTo http.Handler
|
||||
bindAddr string
|
||||
keyFile string
|
||||
certFile string
|
||||
proxyTo http.Handler
|
||||
config config.DevServerProxyService
|
||||
}
|
||||
|
||||
func hasPrefix(s string, prefixes ...string) bool {
|
||||
@ -58,26 +56,20 @@ func hasPrefix(s string, prefixes ...string) bool {
|
||||
}
|
||||
|
||||
func (prx *httpProxy) Run(ctx context.Context, next http.Handler, logger *zap.Logger) error {
|
||||
srv := &server{
|
||||
server: &http.Server{
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if hasPrefix(r.URL.Path, "/api/", "/omnictl/", "/talosctl/", "/image/") {
|
||||
next.ServeHTTP(w, r)
|
||||
srv := NewFromConfig(
|
||||
&prx.config,
|
||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if hasPrefix(r.URL.Path, "/api/", "/omnictl/", "/talosctl/", "/image/") {
|
||||
next.ServeHTTP(w, r)
|
||||
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
prx.proxyTo.ServeHTTP(w, r)
|
||||
}),
|
||||
Addr: prx.bindAddr,
|
||||
},
|
||||
certData: &certData{
|
||||
certFile: prx.certFile,
|
||||
keyFile: prx.keyFile,
|
||||
},
|
||||
}
|
||||
prx.proxyTo.ServeHTTP(w, r)
|
||||
}),
|
||||
)
|
||||
|
||||
logger = logger.With(zap.String("server", prx.bindAddr), zap.String("server_type", "proxy_server"))
|
||||
logger = logger.With(zap.String("server", prx.config.BindEndpoint), zap.String("server_type", "proxy_server"))
|
||||
|
||||
return srv.Run(ctx, logger)
|
||||
}
|
||||
@ -220,7 +220,7 @@ func (h *HTTPHandler) getSignatureCookies(request *http.Request) (publicKeyID st
|
||||
}
|
||||
|
||||
func (h *HTTPHandler) redirectToLogin(writer http.ResponseWriter, request *http.Request) {
|
||||
loginURL, err := url.Parse(config.Config.APIURL)
|
||||
loginURL, err := url.Parse(config.Config.Services.API.URL())
|
||||
if err != nil {
|
||||
h.logger.Warn("failed to redirect to login", zap.Error(err))
|
||||
|
||||
|
||||
@ -20,7 +20,7 @@ import (
|
||||
)
|
||||
|
||||
// EnsureAuthConfigResource creates/configures the auth config resource.
|
||||
func EnsureAuthConfigResource(ctx context.Context, st state.State, logger *zap.Logger, authParams config.AuthParams) (*auth.Config, error) {
|
||||
func EnsureAuthConfigResource(ctx context.Context, st state.State, logger *zap.Logger, authParams config.Auth) (*auth.Config, error) {
|
||||
err := validateParams(authParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -46,7 +46,7 @@ func EnsureAuthConfigResource(ctx context.Context, st state.State, logger *zap.L
|
||||
res.TypedSpec().Value.Auth0.ClientId = authParams.Auth0.ClientID
|
||||
res.TypedSpec().Value.Auth0.UseFormData = authParams.Auth0.UseFormData
|
||||
res.TypedSpec().Value.Saml.Enabled = authParams.SAML.Enabled
|
||||
res.TypedSpec().Value.Saml.Url = authParams.SAML.URL
|
||||
res.TypedSpec().Value.Saml.Url = authParams.SAML.MetadataURL
|
||||
res.TypedSpec().Value.Saml.Metadata = authParams.SAML.Metadata
|
||||
res.TypedSpec().Value.Saml.LabelRules = authParams.SAML.LabelRules
|
||||
|
||||
@ -105,7 +105,7 @@ func EnsureAuthConfigResource(ctx context.Context, st state.State, logger *zap.L
|
||||
return authConfig, nil
|
||||
}
|
||||
|
||||
func validateParams(authParams config.AuthParams) error {
|
||||
func validateParams(authParams config.Auth) error {
|
||||
if !authParams.SAML.Enabled && !authParams.Auth0.Enabled && !authParams.WebAuthn.Enabled {
|
||||
return errors.New("no authentication is enabled")
|
||||
}
|
||||
@ -114,7 +114,7 @@ func validateParams(authParams config.AuthParams) error {
|
||||
return errors.New("both auth0 and SAML auth are enabled, only one can be enabled at the same time")
|
||||
}
|
||||
|
||||
if authParams.SAML.Enabled && authParams.SAML.URL == "" && authParams.SAML.Metadata == "" {
|
||||
if authParams.SAML.Enabled && authParams.SAML.MetadataURL == "" && authParams.SAML.Metadata == "" {
|
||||
return errors.New("SAML is enabled but neither URL nor metadata is set")
|
||||
}
|
||||
|
||||
|
||||
@ -31,8 +31,8 @@ func TestEnsureAuthConfigResource(t *testing.T) {
|
||||
|
||||
for _, tt := range []struct { //nolint:govet
|
||||
name string
|
||||
initialConfig config.AuthParams
|
||||
updatedConfig *config.AuthParams
|
||||
initialConfig config.Auth
|
||||
updatedConfig *config.Auth
|
||||
expected *specs.AuthConfigSpec
|
||||
expectInitError bool
|
||||
expectUpdateError bool
|
||||
@ -43,8 +43,8 @@ func TestEnsureAuthConfigResource(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "enable auth0",
|
||||
initialConfig: config.AuthParams{
|
||||
Auth0: config.Auth0Params{
|
||||
initialConfig: config.Auth{
|
||||
Auth0: config.Auth0{
|
||||
Enabled: true,
|
||||
ClientID: "client-id",
|
||||
Domain: "domain",
|
||||
@ -62,8 +62,8 @@ func TestEnsureAuthConfigResource(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "enable webauthn",
|
||||
initialConfig: config.AuthParams{
|
||||
WebAuthn: config.WebAuthnParams{
|
||||
initialConfig: config.Auth{
|
||||
WebAuthn: config.WebAuthn{
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
@ -77,14 +77,14 @@ func TestEnsureAuthConfigResource(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "make webauthn not required",
|
||||
initialConfig: config.AuthParams{
|
||||
WebAuthn: config.WebAuthnParams{
|
||||
initialConfig: config.Auth{
|
||||
WebAuthn: config.WebAuthn{
|
||||
Enabled: true,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
updatedConfig: &config.AuthParams{
|
||||
WebAuthn: config.WebAuthnParams{
|
||||
updatedConfig: &config.Auth{
|
||||
WebAuthn: config.WebAuthn{
|
||||
Enabled: true,
|
||||
Required: false,
|
||||
},
|
||||
@ -99,14 +99,14 @@ func TestEnsureAuthConfigResource(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "fail to disable webauthn",
|
||||
initialConfig: config.AuthParams{
|
||||
WebAuthn: config.WebAuthnParams{
|
||||
initialConfig: config.Auth{
|
||||
WebAuthn: config.WebAuthn{
|
||||
Enabled: true,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
updatedConfig: &config.AuthParams{
|
||||
WebAuthn: config.WebAuthnParams{
|
||||
updatedConfig: &config.Auth{
|
||||
WebAuthn: config.WebAuthn{
|
||||
Enabled: false,
|
||||
},
|
||||
},
|
||||
@ -114,29 +114,29 @@ func TestEnsureAuthConfigResource(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "fail to disable auth0",
|
||||
initialConfig: config.AuthParams{
|
||||
Auth0: config.Auth0Params{
|
||||
initialConfig: config.Auth{
|
||||
Auth0: config.Auth0{
|
||||
Enabled: true,
|
||||
ClientID: "client-id",
|
||||
Domain: "domain",
|
||||
},
|
||||
},
|
||||
updatedConfig: &config.AuthParams{},
|
||||
updatedConfig: &config.Auth{},
|
||||
expectUpdateError: true,
|
||||
},
|
||||
{
|
||||
name: "switch from auth0 to SAML",
|
||||
initialConfig: config.AuthParams{
|
||||
Auth0: config.Auth0Params{
|
||||
initialConfig: config.Auth{
|
||||
Auth0: config.Auth0{
|
||||
Enabled: true,
|
||||
ClientID: "client-id",
|
||||
Domain: "domain",
|
||||
},
|
||||
},
|
||||
updatedConfig: &config.AuthParams{
|
||||
SAML: config.SAMLParams{
|
||||
Enabled: true,
|
||||
URL: "http://samltest.sp/idp",
|
||||
updatedConfig: &config.Auth{
|
||||
SAML: config.SAML{
|
||||
Enabled: true,
|
||||
MetadataURL: "http://samltest.sp/idp",
|
||||
},
|
||||
},
|
||||
expected: &specs.AuthConfigSpec{
|
||||
@ -150,13 +150,13 @@ func TestEnsureAuthConfigResource(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "fail to disable SAML",
|
||||
initialConfig: config.AuthParams{
|
||||
SAML: config.SAMLParams{
|
||||
Enabled: true,
|
||||
URL: "http://samltest.sp/idp",
|
||||
initialConfig: config.Auth{
|
||||
SAML: config.SAML{
|
||||
Enabled: true,
|
||||
MetadataURL: "http://samltest.sp/idp",
|
||||
},
|
||||
},
|
||||
updatedConfig: &config.AuthParams{},
|
||||
updatedConfig: &config.Auth{},
|
||||
expectUpdateError: true,
|
||||
},
|
||||
} {
|
||||
|
||||
@ -5,39 +5,74 @@
|
||||
|
||||
package config
|
||||
|
||||
import "encoding/json"
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
// AuthParams configures authentication.
|
||||
// Auth configures authentication.
|
||||
//
|
||||
//nolint:govet
|
||||
type AuthParams struct {
|
||||
Auth0 Auth0Params `yaml:"auth0"`
|
||||
WebAuthn WebAuthnParams `yaml:"webauthn"`
|
||||
SAML SAMLParams `yaml:"saml"`
|
||||
type Auth struct {
|
||||
// Auth0 auth type configuration.
|
||||
Auth0 Auth0 `yaml:"auth0" validate:"excluded_with=SAML"`
|
||||
// WebAuthn auth type configuration.
|
||||
WebAuthn WebAuthn `yaml:"webauthn"`
|
||||
// SAML auth type configuration.
|
||||
SAML SAML `yaml:"saml" validate:"excluded_with=Auth0"`
|
||||
|
||||
// KeyPruner automatically removes the unused public keys registered in Omni.
|
||||
KeyPruner KeyPrunerConfig `yaml:"keyPruner"`
|
||||
|
||||
// Suspended makes the account readonly.
|
||||
Suspended bool `yaml:"suspended"`
|
||||
|
||||
// InitialServiceAccount creates a service account on the first Omni start up.
|
||||
// Writes the service account key to the file defined in the keyPath param.
|
||||
//
|
||||
// This service account can be used if it is required to run omnictl or a provider using
|
||||
// some automation scripts.
|
||||
InitialServiceAccount InitialServiceAccount `yaml:"initialServiceAccount"`
|
||||
}
|
||||
|
||||
// Auth0Params holds configuration parameters for Auth0.
|
||||
type Auth0Params struct {
|
||||
Domain string `yaml:"domain"`
|
||||
ClientID string `yaml:"clientID"`
|
||||
UseFormData bool `yaml:"useFormData"`
|
||||
Enabled bool `yaml:"enabled"`
|
||||
// Auth0 holds configuration parameters for Auth0.
|
||||
//
|
||||
//nolint:govet
|
||||
type Auth0 struct {
|
||||
// InitialUsers adds the user to the account on the first Omni start up.
|
||||
InitialUsers []string `yaml:"initialUsers"`
|
||||
Domain string `yaml:"domain"`
|
||||
ClientID string `yaml:"clientID"`
|
||||
UseFormData bool `yaml:"useFormData"`
|
||||
Enabled bool `yaml:"enabled"`
|
||||
}
|
||||
|
||||
// WebAuthnParams holds configuration parameters for WebAuthn.
|
||||
type WebAuthnParams struct {
|
||||
// WebAuthn holds configuration parameters for WebAuthn.
|
||||
type WebAuthn struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
Required bool `yaml:"required"`
|
||||
}
|
||||
|
||||
// SAMLParams holds configuration parameters for SAML auth.
|
||||
type SAMLParams struct {
|
||||
LabelRules SAMLLabelRules `yaml:"labelRules"`
|
||||
URL string `yaml:"url"`
|
||||
Metadata string `yaml:"metadata"`
|
||||
Enabled bool `yaml:"enabled"`
|
||||
// SAML holds configuration parameters for SAML auth.
|
||||
type SAML struct {
|
||||
LabelRules SAMLLabelRules `yaml:"labelRules"`
|
||||
MetadataURL string `yaml:"url" validate:"excluded_with=Metadata"`
|
||||
Metadata string `yaml:"metadata" validate:"excluded_with=MetadataURL"`
|
||||
Enabled bool `yaml:"enabled"`
|
||||
}
|
||||
|
||||
// KeyPrunerConfig defines key pruner configs.
|
||||
type KeyPrunerConfig struct {
|
||||
Interval time.Duration `yaml:"interval"`
|
||||
}
|
||||
|
||||
// InitialServiceAccount allows creating a service account for automated omnictl runs on the Omni service deployment.
|
||||
type InitialServiceAccount struct {
|
||||
Role string `yaml:"role"`
|
||||
KeyPath string `yaml:"keyPath"`
|
||||
Name string `yaml:"name"`
|
||||
Lifetime time.Duration `yaml:"lifetime"`
|
||||
Enabled bool `yaml:"enabled"`
|
||||
}
|
||||
|
||||
// SAMLLabelRules defines mapping of SAML assertion attributes to Omni identity labels.
|
||||
|
||||
41
internal/pkg/config/backup.go
Normal file
41
internal/pkg/config/backup.go
Normal file
@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2025 Sidero Labs, Inc.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the LICENSE file.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// EtcdBackup defines etcd backup configs.
|
||||
type EtcdBackup struct {
|
||||
LocalPath string `yaml:"localPath" validate:"excluded_with=S3Enabled"`
|
||||
S3Enabled bool `yaml:"s3Enabled" validate:"excluded_with=LocalPath"`
|
||||
TickInterval time.Duration `yaml:"tickInterval"`
|
||||
MinInterval time.Duration `yaml:"minInterval"`
|
||||
MaxInterval time.Duration `yaml:"maxInterval"`
|
||||
UploadLimitMbps uint64 `yaml:"uploadLimitMbps"`
|
||||
DownloadLimitMbps uint64 `yaml:"downloadLimitMbps"`
|
||||
Jitter time.Duration `yaml:"jitter"`
|
||||
}
|
||||
|
||||
// GetStorageType returns the storage type.
|
||||
func (ebp EtcdBackup) GetStorageType() (EtcdBackupStorage, error) {
|
||||
if ebp.LocalPath != "" && ebp.S3Enabled {
|
||||
return "", errors.New("both localPath and s3 are set")
|
||||
}
|
||||
|
||||
switch {
|
||||
case ebp.LocalPath == "" && !ebp.S3Enabled:
|
||||
return EtcdBackupTypeS3, nil
|
||||
case ebp.LocalPath != "":
|
||||
return EtcdBackupTypeFS, nil
|
||||
case ebp.S3Enabled:
|
||||
return EtcdBackupTypeS3, nil
|
||||
default:
|
||||
return "", errors.New("unknown backup storage type")
|
||||
}
|
||||
}
|
||||
@ -7,15 +7,17 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/url"
|
||||
"slices"
|
||||
"strings"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/siderolabs/talos/pkg/machinery/config/generate"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/siderolabs/gen/xyaml"
|
||||
"go.uber.org/zap/zapcore"
|
||||
|
||||
consts "github.com/siderolabs/omni/client/pkg/constants"
|
||||
@ -27,251 +29,84 @@ const (
|
||||
wireguardDefaultPort = "50180"
|
||||
)
|
||||
|
||||
// FromBytes loads the config from bytes.
|
||||
func FromBytes(data []byte) (*Params, error) {
|
||||
return parseConfig(bytes.NewBuffer(data))
|
||||
}
|
||||
|
||||
// LoadFromFile loads the config from the file.
|
||||
func LoadFromFile(path string) (*Params, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer f.Close() //nolint:errcheck
|
||||
|
||||
return parseConfig(f)
|
||||
}
|
||||
|
||||
func parseConfig(r io.Reader) (*Params, error) {
|
||||
data, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var config Params
|
||||
|
||||
if err := xyaml.UnmarshalStrict(data, &config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
// Params defines application configs.
|
||||
//
|
||||
//nolint:govet
|
||||
type Params struct {
|
||||
// AccountID is the stable identifier of the instance.
|
||||
Account Account `yaml:"account" validate:"required"`
|
||||
Services Services `yaml:"services" validate:"required"`
|
||||
Auth Auth `yaml:"auth" validate:"required"`
|
||||
Logs Logs `yaml:"logs" validate:"required"`
|
||||
Storage Storage `yaml:"storage" validate:"required"`
|
||||
EtcdBackup EtcdBackup `yaml:"etcdBackup"`
|
||||
Registries Registries `yaml:"registries" validate:"required"`
|
||||
Debug Debug `yaml:"debug"`
|
||||
Features Features `yaml:"features"`
|
||||
}
|
||||
|
||||
// Validate Omni params.
|
||||
func (p *Params) Validate() error {
|
||||
validate := validator.New(validator.WithRequiredStructEnabled())
|
||||
|
||||
return validate.Struct(p)
|
||||
}
|
||||
|
||||
// Account defines Omni account settings.
|
||||
type Account struct {
|
||||
// ID is the stable identifier of the instance.
|
||||
//
|
||||
// Omni will use that to build paths to etcd storage, etc.
|
||||
AccountID string `yaml:"accountID"`
|
||||
ID string `yaml:"id" validate:"required"`
|
||||
// Name is the user-facing name of the instance.
|
||||
//
|
||||
// Omni will use to present some information to the user.
|
||||
// Name can be changed at any time.
|
||||
Name string `yaml:"name"`
|
||||
|
||||
APIURL string `yaml:"apiURL"`
|
||||
MachineAPIBindAddress string `yaml:"apiBindAddress"`
|
||||
MachineAPICertFile string `yaml:"apiCertFile"`
|
||||
MachineAPIKeyFile string `yaml:"apiKeyFile"`
|
||||
|
||||
KubernetesProxyURL string `yaml:"kubernetesProxyURL"`
|
||||
SiderolinkEnabled bool `yaml:"siderolinkEnabled"`
|
||||
SiderolinkWireguardBindAddress string `yaml:"siderolinkWireguardBindAddress"`
|
||||
SiderolinkWireguardAdvertisedAddress string `yaml:"siderolinkWireguardAdvertisedAddress"`
|
||||
SiderolinkDisableLastEndpoint bool `yaml:"siderolinkDisableLastEndpoint"`
|
||||
SiderolinkUseGRPCTunnel bool `yaml:"siderolinkUseGRPCTunnel"`
|
||||
RunDebugServer bool `yaml:"runDebugServer"`
|
||||
|
||||
EventSinkPort int `yaml:"eventSinkPort"`
|
||||
SideroLinkAPIURL string `yaml:"siderolinkAPIURL"`
|
||||
LoadBalancer LoadBalancerParams `yaml:"loadbalancer"`
|
||||
LogServerPort int `yaml:"logServerPort"`
|
||||
|
||||
MachineLogConfig MachineLogConfigParams `yaml:"machineLogConfig"`
|
||||
|
||||
Auth AuthParams `yaml:"auth"`
|
||||
|
||||
InitialUsers []string `yaml:"initialUsers"`
|
||||
|
||||
TalosRegistry string `yaml:"talosRegistry"`
|
||||
|
||||
KubernetesRegistry string `yaml:"kubernetesRegistry"`
|
||||
|
||||
ImageFactoryBaseURL string `yaml:"imageFactoryAddress"`
|
||||
ImageFactoryPXEBaseURL string `yaml:"imageFactoryProxyAddress"`
|
||||
|
||||
Storage StorageParams `yaml:"storage"`
|
||||
|
||||
SecondaryStorage BoltDBParams `yaml:"secondaryStorage"`
|
||||
|
||||
DefaultConfigGenOptions []generate.Option `yaml:"-" json:"-"`
|
||||
|
||||
KeyPruner KeyPrunerParams `yaml:"keyPruner"`
|
||||
|
||||
EnableTalosPreReleaseVersions bool `yaml:"enableTalosPreReleaseVersions"`
|
||||
|
||||
WorkloadProxying WorkloadProxyingParams `yaml:"workloadProxying"`
|
||||
|
||||
ConfigDataCompression ConfigDataCompressionParams `yaml:"configDataCompression"`
|
||||
|
||||
LocalResourceServerPort int `yaml:"localResourceServerPort"`
|
||||
|
||||
EtcdBackup EtcdBackupParams `yaml:"etcdBackup"`
|
||||
|
||||
DisableControllerRuntimeCache bool `yaml:"disableControllerRuntimeCache"`
|
||||
|
||||
LogResourceUpdatesTypes []string `yaml:"logResourceUpdatesTypes"`
|
||||
LogResourceUpdatesLogLevel string `yaml:"logResourceUpdatesLogLevel"`
|
||||
|
||||
EmbeddedDiscoveryService EmbeddedDiscoveryServiceParams `yaml:"embeddedDiscoveryService"`
|
||||
|
||||
EnableBreakGlassConfigs bool `yaml:"enableBreakGlassConfigs"`
|
||||
|
||||
AuditLogDir string `yaml:"auditLogDir"`
|
||||
|
||||
InitialServiceAccount InitialServiceAccount `yaml:"initialServiceAccount"`
|
||||
|
||||
EnableStripeReporting bool `yaml:"enableStripeReporting"`
|
||||
|
||||
JoinTokensMode JoinTokensMode `yaml:"joinTokensMode"`
|
||||
Name string `yaml:"name" validate:"required"`
|
||||
}
|
||||
|
||||
// JoinTokensMode is the join token operation mode config.
|
||||
//
|
||||
//nolint:recvcheck
|
||||
type JoinTokensMode string
|
||||
// Registries configures docker registries to be used for the Talos and Kubernetes images.
|
||||
// Also it has URLs for the image factory.
|
||||
type Registries struct {
|
||||
Talos string `yaml:"talos" validate:"required"`
|
||||
Kubernetes string `yaml:"kubernetes" validate:"required"`
|
||||
|
||||
// String implements pflag.Value.
|
||||
func (s JoinTokensMode) String() string {
|
||||
return string(s)
|
||||
}
|
||||
ImageFactoryBaseURL string `yaml:"imageFactoryBaseURL" validate:"required"`
|
||||
ImageFactoryPXEBaseURL string `yaml:"imageFactoryPXEBaseURL"`
|
||||
|
||||
// Set implements pflag.Value.
|
||||
func (s *JoinTokensMode) Set(value string) error {
|
||||
if !slices.Contains(s.values(), value) {
|
||||
return fmt.Errorf("should be one of %s", strings.Join(s.values(), ", "))
|
||||
}
|
||||
|
||||
*s = JoinTokensMode(value)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type implements pflag.Value.
|
||||
func (s JoinTokensMode) Type() string {
|
||||
return fmt.Sprintf("[%s]", strings.Join(s.values(), ","))
|
||||
}
|
||||
|
||||
func (JoinTokensMode) values() []string {
|
||||
return []string{JoinTokensModeLegacyOnly, JoinTokensModeLegacyAllowed, JoinTokensModeStrict}
|
||||
}
|
||||
|
||||
const (
|
||||
// JoinTokensModeLegacyOnly disables node unique token flow, uses only join token when letting the machine into the system.
|
||||
JoinTokensModeLegacyOnly = "legacy"
|
||||
// JoinTokensModeLegacyAllowed allows joining Talos nodes which do not support node unique token flow
|
||||
// uses unique token flow only for the machines which support it.
|
||||
JoinTokensModeLegacyAllowed = "legacyAllowed"
|
||||
// JoinTokensModeStrict rejects the machines that do not support node unique tokens flow.
|
||||
JoinTokensModeStrict = "strict"
|
||||
)
|
||||
|
||||
// InitialServiceAccount allows creating a service account for automated omnictl runs on the Omni service deployment.
|
||||
type InitialServiceAccount struct {
|
||||
Role string
|
||||
KeyPath string
|
||||
Name string
|
||||
Lifetime time.Duration
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
// EmbeddedDiscoveryServiceParams defines embedded discovery service configs.
|
||||
type EmbeddedDiscoveryServiceParams struct {
|
||||
SnapshotPath string `yaml:"snapshotPath"`
|
||||
LogLevel string `yaml:"logLevel"`
|
||||
Enabled bool `yaml:"enabled"`
|
||||
SnapshotsEnabled bool `yaml:"snapshotsEnabled"`
|
||||
Port int `yaml:"port"`
|
||||
SnapshotInterval time.Duration `yaml:"snapshotInterval"`
|
||||
}
|
||||
|
||||
// EtcdBackupParams defines etcd backup configs.
|
||||
type EtcdBackupParams struct {
|
||||
LocalPath string `yaml:"localPath"`
|
||||
S3Enabled bool `yaml:"s3Enabled"`
|
||||
TickInterval time.Duration `yaml:"tickInterval"`
|
||||
MinInterval time.Duration `yaml:"minInterval"`
|
||||
MaxInterval time.Duration `yaml:"maxInterval"`
|
||||
UploadLimitMbps uint64 `yaml:"uploadLimitMbps"`
|
||||
DownloadLimitMbps uint64 `yaml:"downloadLimitMbps"`
|
||||
Jitter time.Duration `yaml:"jitter"`
|
||||
}
|
||||
|
||||
// GetStorageType returns the storage type.
|
||||
func (ebp EtcdBackupParams) GetStorageType() (EtcdBackupStorage, error) {
|
||||
if ebp.LocalPath != "" && ebp.S3Enabled {
|
||||
return "", errors.New("both localPath and s3 are set")
|
||||
}
|
||||
|
||||
switch {
|
||||
case ebp.LocalPath == "" && !ebp.S3Enabled:
|
||||
return EtcdBackupTypeS3, nil
|
||||
case ebp.LocalPath != "":
|
||||
return EtcdBackupTypeFS, nil
|
||||
case ebp.S3Enabled:
|
||||
return EtcdBackupTypeS3, nil
|
||||
default:
|
||||
return "", errors.New("unknown backup storage type")
|
||||
}
|
||||
}
|
||||
|
||||
// WorkloadProxyingParams defines workload proxying configs.
|
||||
type WorkloadProxyingParams struct {
|
||||
Subdomain string `yaml:"subdomain"`
|
||||
Enabled bool `yaml:"enabled"`
|
||||
}
|
||||
|
||||
// ConfigDataCompressionParams defines config data compression configs.
|
||||
//
|
||||
//nolint:revive
|
||||
type ConfigDataCompressionParams struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
}
|
||||
|
||||
// LoadBalancerParams defines load balancer configs.
|
||||
type LoadBalancerParams struct {
|
||||
MinPort int `yaml:"minPort"`
|
||||
MaxPort int `yaml:"maxPort"`
|
||||
|
||||
DialTimeout time.Duration `yaml:"dialTimeout"`
|
||||
KeepAlivePeriod time.Duration `yaml:"keepAlivePeriod"`
|
||||
TCPUserTimeout time.Duration `yaml:"tcpUserTimeout"`
|
||||
|
||||
HealthCheckInterval time.Duration `yaml:"healthCheckInterval"`
|
||||
HealthCheckTimeout time.Duration `yaml:"healthCheckTimeout"`
|
||||
}
|
||||
|
||||
// StorageParams defines storage configs.
|
||||
type StorageParams struct {
|
||||
// Kind can be either 'boltdb' or 'etcd'.
|
||||
Kind string `yaml:"kind"`
|
||||
Boltdb BoltDBParams `yaml:"boltdb"`
|
||||
Etcd EtcdParams `yaml:"etcd"`
|
||||
}
|
||||
|
||||
// BoltDBParams defines boltdb storage configs.
|
||||
type BoltDBParams struct {
|
||||
Path string `yaml:"path"`
|
||||
}
|
||||
|
||||
// EtcdParams defines etcd storage configs.
|
||||
type EtcdParams struct { ///nolint:govet
|
||||
// External etcd: list of endpoints, as host:port pairs.
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
DialKeepAliveTime time.Duration `yaml:"dialKeepAliveTime"`
|
||||
DialKeepAliveTimeout time.Duration `yaml:"dialKeepAliveTimeout"`
|
||||
CAPath string `yaml:"caPath"`
|
||||
CertPath string `yaml:"certPath"`
|
||||
KeyPath string `yaml:"keyPath"`
|
||||
|
||||
// Use embedded etcd server (no clustering).
|
||||
Embedded bool `yaml:"embedded"`
|
||||
EmbeddedDBPath string `yaml:"embeddedDBPath"`
|
||||
EmbeddedUnsafeFsync bool `yaml:"embeddedUnsafeFsync"`
|
||||
|
||||
PrivateKeySource string `yaml:"privateKeySource"`
|
||||
PublicKeyFiles []string `yaml:"publicKeysFiles"`
|
||||
}
|
||||
|
||||
// KeyPrunerParams defines key pruner configs.
|
||||
type KeyPrunerParams struct {
|
||||
Interval time.Duration `yaml:"interval"`
|
||||
}
|
||||
|
||||
// MachineLogConfigParams defines log storage configuration.
|
||||
type MachineLogConfigParams struct {
|
||||
StoragePath string `yaml:"directory"`
|
||||
|
||||
BufferInitialCapacity int `yaml:"bufferInitialCapacity"`
|
||||
BufferMaxCapacity int `yaml:"bufferMaxCapacity"`
|
||||
BufferSafetyGap int `yaml:"bufferSafetyGap"`
|
||||
NumCompressedChunks int `yaml:"numCompressedChunks"`
|
||||
|
||||
StorageFlushPeriod time.Duration `yaml:"flushPeriod"`
|
||||
StorageFlushJitter float64 `yaml:"flushJitter"`
|
||||
StorageEnabled bool `yaml:"enabled"`
|
||||
// Mirrors enables registry mirrors for all Talos machines connected to Omni.
|
||||
Mirrors []string `yaml:"mirrors"`
|
||||
}
|
||||
|
||||
var (
|
||||
@ -283,11 +118,11 @@ var (
|
||||
|
||||
// GetImageFactoryPXEBaseURL reads image factory PXE address from the args.
|
||||
func (p *Params) GetImageFactoryPXEBaseURL() (*url.URL, error) {
|
||||
if p.ImageFactoryPXEBaseURL != "" {
|
||||
return url.Parse(p.ImageFactoryPXEBaseURL)
|
||||
if p.Registries.ImageFactoryPXEBaseURL != "" {
|
||||
return url.Parse(p.Registries.ImageFactoryPXEBaseURL)
|
||||
}
|
||||
|
||||
url, err := url.Parse(p.ImageFactoryBaseURL)
|
||||
url, err := url.Parse(p.Registries.ImageFactoryBaseURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid URL specified for the image factory: %w", err)
|
||||
}
|
||||
@ -297,24 +132,9 @@ func (p *Params) GetImageFactoryPXEBaseURL() (*url.URL, error) {
|
||||
return url, nil
|
||||
}
|
||||
|
||||
// GetAdvertisedAPIHost returns the advertised host (IP or domain) of the API without the port.
|
||||
func (p *Params) GetAdvertisedAPIHost() (string, error) {
|
||||
apiURL, err := url.Parse(p.SideroLinkAPIURL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
apiHost, _, err := net.SplitHostPort(apiURL.Host)
|
||||
if err != nil {
|
||||
apiHost = apiURL.Host
|
||||
}
|
||||
|
||||
return apiHost, nil
|
||||
}
|
||||
|
||||
// GetOIDCIssuerEndpoint returns the OIDC issuer endpoint.
|
||||
func (p *Params) GetOIDCIssuerEndpoint() (string, error) {
|
||||
u, err := url.Parse(p.APIURL)
|
||||
u, err := url.Parse(p.Services.API.URL())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -327,109 +147,152 @@ func (p *Params) GetOIDCIssuerEndpoint() (string, error) {
|
||||
return u.String(), nil
|
||||
}
|
||||
|
||||
// PopulateFallbacks in the config file.
|
||||
func (p *Params) PopulateFallbacks() {
|
||||
// copy the keys from the main API server if kubernetes proxy doesn't have certs defined explicitly.
|
||||
if !p.Services.KubernetesProxy.IsSecure() {
|
||||
p.Services.KubernetesProxy.CertFile = p.Services.API.CertFile
|
||||
p.Services.KubernetesProxy.KeyFile = p.Services.API.KeyFile
|
||||
}
|
||||
|
||||
// copy the keys from the main API server if dev server proxy doesn't have certs defined explicitly.
|
||||
if !p.Services.DevServerProxy.IsSecure() {
|
||||
p.Services.DevServerProxy.CertFile = p.Services.API.CertFile
|
||||
p.Services.DevServerProxy.KeyFile = p.Services.API.KeyFile
|
||||
}
|
||||
}
|
||||
|
||||
// InitDefault creates the default config.
|
||||
func InitDefault() *Params {
|
||||
return &Params{
|
||||
AccountID: "edd2822a-7834-4fe0-8172-cc5581f13a8d",
|
||||
Name: "default",
|
||||
APIURL: fmt.Sprintf("http://%s", net.JoinHostPort("localhost", "8080")),
|
||||
KubernetesProxyURL: fmt.Sprintf("https://%s", net.JoinHostPort("localhost", "8095")),
|
||||
SiderolinkEnabled: true,
|
||||
SiderolinkWireguardBindAddress: net.JoinHostPort("0.0.0.0", wireguardDefaultPort),
|
||||
SiderolinkWireguardAdvertisedAddress: net.JoinHostPort(localIP, wireguardDefaultPort),
|
||||
MachineAPIBindAddress: net.JoinHostPort(localIP, "8090"),
|
||||
EventSinkPort: 8090,
|
||||
SideroLinkAPIURL: fmt.Sprintf("grpc://%s", net.JoinHostPort(localIP, "8090")),
|
||||
LoadBalancer: LoadBalancerParams{
|
||||
MinPort: 10000,
|
||||
MaxPort: 35000,
|
||||
|
||||
DialTimeout: 15 * time.Second,
|
||||
KeepAlivePeriod: 30 * time.Second,
|
||||
TCPUserTimeout: 30 * time.Second,
|
||||
|
||||
HealthCheckInterval: 20 * time.Second,
|
||||
HealthCheckTimeout: 15 * time.Second,
|
||||
Account: Account{
|
||||
ID: "edd2822a-7834-4fe0-8172-cc5581f13a8d",
|
||||
Name: "default",
|
||||
},
|
||||
KeyPruner: KeyPrunerParams{
|
||||
Interval: 10 * time.Minute,
|
||||
},
|
||||
LogServerPort: 8092,
|
||||
MachineLogConfig: MachineLogConfigParams{
|
||||
BufferInitialCapacity: 16384,
|
||||
BufferMaxCapacity: 131072,
|
||||
BufferSafetyGap: 256,
|
||||
NumCompressedChunks: 5,
|
||||
StorageEnabled: true,
|
||||
StoragePath: "_out/logs",
|
||||
StorageFlushPeriod: 10 * time.Minute,
|
||||
StorageFlushJitter: 0.1,
|
||||
},
|
||||
TalosRegistry: consts.TalosRegistry,
|
||||
KubernetesRegistry: consts.KubernetesRegistry,
|
||||
ImageFactoryBaseURL: consts.ImageFactoryBaseURL,
|
||||
Storage: StorageParams{
|
||||
Kind: "etcd",
|
||||
Boltdb: BoltDBParams{
|
||||
Path: "_out/omni.db",
|
||||
Services: Services{
|
||||
API: Service{
|
||||
BindEndpoint: net.JoinHostPort("localhost", "8080"),
|
||||
},
|
||||
Etcd: EtcdParams{
|
||||
Endpoints: []string{"http://localhost:2379"},
|
||||
DialKeepAliveTime: 30 * time.Second,
|
||||
DialKeepAliveTimeout: 5 * time.Second,
|
||||
CAPath: "etcd/ca.crt",
|
||||
CertPath: "etcd/client.crt",
|
||||
KeyPath: "etcd/client.key",
|
||||
KubernetesProxy: KubernetesProxyService{
|
||||
BindEndpoint: net.JoinHostPort("localhost", "8095"),
|
||||
},
|
||||
Metrics: Service{
|
||||
BindEndpoint: net.JoinHostPort("0.0.0.0", "2122"),
|
||||
},
|
||||
Siderolink: SiderolinkService{
|
||||
WireGuard: SiderolinkWireGuard{
|
||||
BindEndpoint: net.JoinHostPort("0.0.0.0", wireguardDefaultPort),
|
||||
AdvertisedEndpoint: net.JoinHostPort(localIP, wireguardDefaultPort),
|
||||
},
|
||||
EventSinkPort: 8090,
|
||||
LogServerPort: 8092,
|
||||
JoinTokensMode: JoinTokensModeLegacyOnly,
|
||||
},
|
||||
MachineAPI: MachineAPI{
|
||||
BindEndpoint: net.JoinHostPort(localIP, "8090"),
|
||||
},
|
||||
LoadBalancer: LoadBalancerService{
|
||||
MinPort: 10000,
|
||||
MaxPort: 35000,
|
||||
|
||||
Embedded: true,
|
||||
EmbeddedDBPath: "_out/etcd/",
|
||||
DialTimeout: 15 * time.Second,
|
||||
KeepAlivePeriod: 30 * time.Second,
|
||||
TCPUserTimeout: 30 * time.Second,
|
||||
|
||||
HealthCheckInterval: 20 * time.Second,
|
||||
HealthCheckTimeout: 15 * time.Second,
|
||||
},
|
||||
LocalResourceService: LocalResourceService{
|
||||
Enabled: true,
|
||||
Port: 8081,
|
||||
},
|
||||
EmbeddedDiscoveryService: EmbeddedDiscoveryService{
|
||||
Enabled: true,
|
||||
Port: 8093,
|
||||
SnapshotsEnabled: true,
|
||||
SnapshotsPath: "_out/secondary-storage/discovery-service-state.binpb",
|
||||
SnapshotsInterval: 10 * time.Minute,
|
||||
LogLevel: zapcore.WarnLevel.String(),
|
||||
},
|
||||
WorkloadProxy: WorkloadProxy{
|
||||
Subdomain: "proxy-us",
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
|
||||
SecondaryStorage: BoltDBParams{
|
||||
Path: "_out/secondary-storage/bolt.db",
|
||||
Auth: Auth{
|
||||
KeyPruner: KeyPrunerConfig{
|
||||
Interval: 10 * time.Minute,
|
||||
},
|
||||
InitialServiceAccount: InitialServiceAccount{
|
||||
Enabled: false,
|
||||
Role: string(role.Admin),
|
||||
KeyPath: "_out/initial-service-account-key",
|
||||
Name: "automation",
|
||||
Lifetime: time.Hour,
|
||||
},
|
||||
},
|
||||
|
||||
WorkloadProxying: WorkloadProxyingParams{
|
||||
Enabled: true,
|
||||
Subdomain: "proxy-us",
|
||||
Registries: Registries{
|
||||
Talos: consts.TalosRegistry,
|
||||
Kubernetes: consts.KubernetesRegistry,
|
||||
ImageFactoryBaseURL: consts.ImageFactoryBaseURL,
|
||||
},
|
||||
|
||||
ConfigDataCompression: ConfigDataCompressionParams{
|
||||
Enabled: true,
|
||||
Logs: Logs{
|
||||
Audit: LogsAudit{
|
||||
Path: "_out/audit",
|
||||
},
|
||||
ResourceLogger: ResourceLoggerConfig{
|
||||
LogLevel: zapcore.InfoLevel.String(),
|
||||
Types: common.UserManagedResourceTypes,
|
||||
},
|
||||
Machine: LogsMachine{
|
||||
BufferInitialCapacity: 16384,
|
||||
BufferMaxCapacity: 131072,
|
||||
BufferSafetyGap: 256,
|
||||
Storage: LogsMachineStorage{
|
||||
Enabled: true,
|
||||
Path: "_out/logs",
|
||||
FlushPeriod: 10 * time.Minute,
|
||||
FlushJitter: 0.1,
|
||||
NumCompressedChunks: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
Storage: Storage{
|
||||
Secondary: BoltDB{
|
||||
Path: "_out/secondary-storage/bolt.db",
|
||||
},
|
||||
Default: StorageDefault{
|
||||
Kind: "etcd",
|
||||
Boltdb: BoltDB{
|
||||
Path: "_out/omni.db",
|
||||
},
|
||||
Etcd: EtcdParams{
|
||||
Endpoints: []string{"http://localhost:2379"},
|
||||
DialKeepAliveTime: 30 * time.Second,
|
||||
DialKeepAliveTimeout: 5 * time.Second,
|
||||
CAFile: "etcd/ca.crt",
|
||||
CertFile: "etcd/client.crt",
|
||||
KeyFile: "etcd/client.key",
|
||||
|
||||
InitialServiceAccount: InitialServiceAccount{
|
||||
Enabled: false,
|
||||
Role: string(role.Admin),
|
||||
KeyPath: "_out/initial-service-account-key",
|
||||
Name: "automation",
|
||||
Lifetime: time.Hour,
|
||||
Embedded: true,
|
||||
EmbeddedDBPath: "_out/etcd/",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
LocalResourceServerPort: 8081,
|
||||
|
||||
EtcdBackup: EtcdBackupParams{
|
||||
Features: Features{
|
||||
EnableConfigDataCompression: true,
|
||||
},
|
||||
EtcdBackup: EtcdBackup{
|
||||
TickInterval: time.Minute,
|
||||
MinInterval: time.Hour,
|
||||
MaxInterval: 24 * time.Hour,
|
||||
Jitter: 10 * time.Minute,
|
||||
},
|
||||
|
||||
LogResourceUpdatesLogLevel: zapcore.InfoLevel.String(),
|
||||
LogResourceUpdatesTypes: common.UserManagedResourceTypes,
|
||||
|
||||
EmbeddedDiscoveryService: EmbeddedDiscoveryServiceParams{
|
||||
Enabled: true,
|
||||
Port: 8093,
|
||||
SnapshotsEnabled: true,
|
||||
SnapshotPath: "_out/secondary-storage/discovery-service-state.binpb",
|
||||
SnapshotInterval: 10 * time.Minute,
|
||||
LogLevel: zapcore.WarnLevel.String(),
|
||||
Debug: Debug{
|
||||
Server: DebugServer{
|
||||
Endpoint: ":9988",
|
||||
},
|
||||
},
|
||||
|
||||
JoinTokensMode: JoinTokensModeLegacyOnly,
|
||||
RunDebugServer: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
90
internal/pkg/config/config_test.go
Normal file
90
internal/pkg/config/config_test.go
Normal file
@ -0,0 +1,90 @@
|
||||
// Copyright (c) 2025 Sidero Labs, Inc.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the LICENSE file.
|
||||
|
||||
// Package config contains the application config loading functions.
|
||||
package config_test
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/siderolabs/omni/internal/pkg/config"
|
||||
)
|
||||
|
||||
//go:embed testdata/config-full.yaml
|
||||
var configFull []byte
|
||||
|
||||
//go:embed testdata/invalid-join-token-mode.yaml
|
||||
var configInvalidJoinTokenMode []byte
|
||||
|
||||
//go:embed testdata/conflicting-auth.yaml
|
||||
var conflictingAuth []byte
|
||||
|
||||
//go:embed testdata/backups.yaml
|
||||
var backups []byte
|
||||
|
||||
//go:embed testdata/unknown-keys.yaml
|
||||
var unknownKeys []byte
|
||||
|
||||
func TestValidateConfig(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
validateErr string
|
||||
loadErr string
|
||||
config []byte
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
config: []byte("{}"),
|
||||
validateErr: "required",
|
||||
},
|
||||
{
|
||||
name: "full",
|
||||
config: configFull,
|
||||
},
|
||||
{
|
||||
name: "invalid join tokens mode",
|
||||
config: configInvalidJoinTokenMode,
|
||||
validateErr: "JoinTokensMode",
|
||||
},
|
||||
{
|
||||
name: "conflicting auth",
|
||||
config: conflictingAuth,
|
||||
validateErr: "Field validation for 'Auth0' failed",
|
||||
},
|
||||
{
|
||||
name: "conflicting backups",
|
||||
config: backups,
|
||||
validateErr: "Field validation for 'LocalPath' failed",
|
||||
},
|
||||
{
|
||||
name: "unknown keys",
|
||||
config: unknownKeys,
|
||||
loadErr: "unknown keys found",
|
||||
},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cfg, err := config.FromBytes(tt.config)
|
||||
if tt.loadErr != "" {
|
||||
require.ErrorContains(t, err, tt.loadErr)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
err = cfg.Validate()
|
||||
if tt.validateErr != "" {
|
||||
require.ErrorContains(t, err, tt.validateErr)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
22
internal/pkg/config/debug.go
Normal file
22
internal/pkg/config/debug.go
Normal file
@ -0,0 +1,22 @@
|
||||
// Copyright (c) 2025 Sidero Labs, Inc.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the LICENSE file.
|
||||
|
||||
package config
|
||||
|
||||
// Debug configures debugging tools of the Omni instance.
|
||||
type Debug struct {
|
||||
Server DebugServer `yaml:"server"`
|
||||
Pprof DebugPprof `yaml:"pprof"`
|
||||
}
|
||||
|
||||
// DebugServer enables the debug server.
|
||||
type DebugServer struct {
|
||||
Endpoint string `yaml:"endpoint"`
|
||||
}
|
||||
|
||||
// DebugPprof enables pprof server.
|
||||
type DebugPprof struct {
|
||||
Endpoint string `yaml:"endpoint"`
|
||||
}
|
||||
15
internal/pkg/config/features.go
Normal file
15
internal/pkg/config/features.go
Normal file
@ -0,0 +1,15 @@
|
||||
// Copyright (c) 2025 Sidero Labs, Inc.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the LICENSE file.
|
||||
|
||||
package config
|
||||
|
||||
// Features contains all Omni feature flags.
|
||||
type Features struct {
|
||||
EnableTalosPreReleaseVersions bool `yaml:"enableTalosPreReleaseVersions"`
|
||||
EnableBreakGlassConfigs bool `yaml:"enableBreakGlassConfigs"`
|
||||
EnableConfigDataCompression bool `yaml:"enableConfigDataCompression"`
|
||||
|
||||
DisableControllerRuntimeCache bool `yaml:"disableControllerRuntimeCache"`
|
||||
}
|
||||
67
internal/pkg/config/logs.go
Normal file
67
internal/pkg/config/logs.go
Normal file
@ -0,0 +1,67 @@
|
||||
// Copyright (c) 2025 Sidero Labs, Inc.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the LICENSE file.
|
||||
|
||||
package config
|
||||
|
||||
import "time"
|
||||
|
||||
// Logs configures logging of the Omni instance.
|
||||
//
|
||||
//nolint:govet
|
||||
type Logs struct {
|
||||
// Machine configures Talos machine logs handler.
|
||||
Machine LogsMachine `yaml:"machine"`
|
||||
// Audit configures audit logs handler.
|
||||
Audit LogsAudit `yaml:"audit"`
|
||||
// ResourceLogger configures resource logger.
|
||||
ResourceLogger ResourceLoggerConfig `yaml:"resourceLogger"`
|
||||
// Stripe enables reporting to stripe.
|
||||
Stripe LogsStripe `yaml:"stripe"`
|
||||
}
|
||||
|
||||
// LogsMachine configures Talos machine logs handler.
|
||||
type LogsMachine struct {
|
||||
// Storage configures persistent machine log storage of the Omni instance.
|
||||
Storage LogsMachineStorage `yaml:"storage"`
|
||||
|
||||
BufferInitialCapacity int `yaml:"bufferInitialCapacity"`
|
||||
BufferMaxCapacity int `yaml:"bufferMaxCapacity"`
|
||||
BufferSafetyGap int `yaml:"bufferSafetyGap"`
|
||||
}
|
||||
|
||||
// LogsMachineStorage configures the machine logs storage.
|
||||
//
|
||||
//nolint:govet
|
||||
type LogsMachineStorage struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
// Path to store the logs in.
|
||||
Path string `yaml:"path"`
|
||||
// FlushPeriod is the period to use to flush the logs to disk.
|
||||
FlushPeriod time.Duration `yaml:"flushPeriod"`
|
||||
// FlushJitter flush period jitter.
|
||||
FlushJitter float64 `yaml:"flushJitter"`
|
||||
// NumCompressedChunks is the count of log chunks to keep in the logs history.
|
||||
NumCompressedChunks int `yaml:"numCompressedChunks"`
|
||||
}
|
||||
|
||||
// LogsAudit configures audit logs peristence.
|
||||
type LogsAudit struct {
|
||||
// Path to store the audit logs in.
|
||||
Path string `yaml:"path"`
|
||||
}
|
||||
|
||||
// ResourceLoggerConfig is the config for the Omni resource logger.
|
||||
// This is the debug tool, that allows logging all resource changes to the stdout.
|
||||
type ResourceLoggerConfig struct {
|
||||
// LogLevel is the level of the logs to use when writing the data.
|
||||
LogLevel string `yaml:"logLevel"`
|
||||
// Types is the list of the resource types to log to stdout.
|
||||
Types []string `yaml:"types"`
|
||||
}
|
||||
|
||||
// LogsStripe report usage metrics to stripe.
|
||||
type LogsStripe struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
}
|
||||
273
internal/pkg/config/services.go
Normal file
273
internal/pkg/config/services.go
Normal file
@ -0,0 +1,273 @@
|
||||
// Copyright (c) 2025 Sidero Labs, Inc.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the LICENSE file.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// HTTPService defines the interface for HTTP like services.
|
||||
type HTTPService interface {
|
||||
URL() string
|
||||
GetCertFile() string
|
||||
GetKeyFile() string
|
||||
GetBindEndpoint() string
|
||||
IsSecure() bool
|
||||
}
|
||||
|
||||
// Services configs.
|
||||
//
|
||||
//nolint:govet
|
||||
type Services struct {
|
||||
// API is the Omni gRPC API service, gateway and the frontend.
|
||||
API Service `yaml:"api"`
|
||||
// DevServerProxy is used in Omni development and allows proxying through Omni to the node JS dev server.
|
||||
DevServerProxy DevServerProxyService `yaml:"devServerProxy"`
|
||||
// Metrics exposes prometheus metrics.
|
||||
Metrics Service `yaml:"metrics"`
|
||||
// KubernetesProxy proxies the Kubernetes API to the clusters managed by Omni.
|
||||
KubernetesProxy KubernetesProxyService `yaml:"kubernetesProxy"`
|
||||
// Siderolink manages WireGuard connections to the Talos machines connected to Omni.
|
||||
Siderolink SiderolinkService `yaml:"siderolink"`
|
||||
// MachineAPI is the public API of Omni that helps to establish WireGuard connections.
|
||||
MachineAPI MachineAPI `yaml:"machineAPI"`
|
||||
// LocalResourceService runs COSI API service that gives readonly access to all resources.
|
||||
LocalResourceService LocalResourceService `yaml:"localResourceService"`
|
||||
// EmbeddedDiscoveryService runs https://discovery.talos.dev/ inside Omni.
|
||||
EmbeddedDiscoveryService EmbeddedDiscoveryService `yaml:"embeddedDiscoveryService"`
|
||||
// LoadBalancer configures Omni Kubernetes loadbalancer runner.
|
||||
LoadBalancer LoadBalancerService `yaml:"loadBalancer"`
|
||||
// WorkloadProxy runs the workload proxy service in Omni.
|
||||
WorkloadProxy WorkloadProxy `yaml:"workloadProxy"`
|
||||
}
|
||||
|
||||
// Service is the base service config.
|
||||
type Service struct {
|
||||
BindEndpoint string `yaml:"endpoint"`
|
||||
// AdvertisedURL should be used when Omni runs behind an ingress.
|
||||
// This value is used in the machine join config, kernel params and schematics generation.
|
||||
AdvertisedURL string `yaml:"advertisedURL"`
|
||||
// CertFile is the TLS cert.
|
||||
CertFile string `yaml:"certFile"`
|
||||
// KeyFile is the TLS key.
|
||||
KeyFile string `yaml:"keyFile"`
|
||||
}
|
||||
|
||||
// GetBindEndpoint implements HTTPService.
|
||||
func (s *Service) GetBindEndpoint() string {
|
||||
return s.BindEndpoint
|
||||
}
|
||||
|
||||
// GetCertFile implements HTTPService.
|
||||
func (s *Service) GetCertFile() string {
|
||||
return s.CertFile
|
||||
}
|
||||
|
||||
// GetKeyFile implements HTTPService.
|
||||
func (s *Service) GetKeyFile() string {
|
||||
return s.KeyFile
|
||||
}
|
||||
|
||||
// IsSecure returns true if both cert file and key file are present.
|
||||
func (s *Service) IsSecure() bool {
|
||||
return s.CertFile != "" && s.KeyFile != ""
|
||||
}
|
||||
|
||||
// URL gets the URL from the endpoint.
|
||||
func (s *Service) URL() string {
|
||||
if s.AdvertisedURL != "" {
|
||||
return s.AdvertisedURL
|
||||
}
|
||||
|
||||
schema := "http"
|
||||
if s.IsSecure() {
|
||||
schema = "https"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s://%s", schema, s.BindEndpoint)
|
||||
}
|
||||
|
||||
// DevServerProxyService is used in Omni development and allows proxying through Omni to the node JS dev server.
|
||||
type DevServerProxyService struct {
|
||||
Service `yaml:",inline"`
|
||||
ProxyTo string `yaml:"proxyTo"`
|
||||
}
|
||||
|
||||
// KubernetesProxyService is the base service config.
|
||||
type KubernetesProxyService struct {
|
||||
BindEndpoint string `yaml:"endpoint"`
|
||||
// AdvertisedURL should be used when Omni runs behind an ingress.
|
||||
// This value is used in the machine join config, kernel params and schematics generation.
|
||||
AdvertisedURL string `yaml:"advertisedURL"`
|
||||
// CertFile is the TLS cert.
|
||||
CertFile string `yaml:"certFile" validate:"required"`
|
||||
// KeyFile is the TLS key.
|
||||
KeyFile string `yaml:"keyFile" validate:"required"`
|
||||
}
|
||||
|
||||
// GetBindEndpoint implements HTTPService.
|
||||
func (ks *KubernetesProxyService) GetBindEndpoint() string {
|
||||
return ks.BindEndpoint
|
||||
}
|
||||
|
||||
// GetCertFile implements HTTPKubernetesProxyService.
|
||||
func (ks *KubernetesProxyService) GetCertFile() string {
|
||||
return ks.CertFile
|
||||
}
|
||||
|
||||
// GetKeyFile implements HTTPKubernetesProxyService.
|
||||
func (ks *KubernetesProxyService) GetKeyFile() string {
|
||||
return ks.KeyFile
|
||||
}
|
||||
|
||||
// IsSecure returns true if both cert file and key file are present.
|
||||
func (ks *KubernetesProxyService) IsSecure() bool {
|
||||
return ks.CertFile != "" && ks.KeyFile != ""
|
||||
}
|
||||
|
||||
// URL returns kubernetes services URL.
|
||||
// It is always HTTPS.
|
||||
func (ks *KubernetesProxyService) URL() string {
|
||||
if ks.AdvertisedURL != "" {
|
||||
return ks.AdvertisedURL
|
||||
}
|
||||
|
||||
return fmt.Sprintf("https://%s", ks.BindEndpoint)
|
||||
}
|
||||
|
||||
// SiderolinkService manages WireGuard connections to the Talos machines connected to Omni.
|
||||
type SiderolinkService struct {
|
||||
WireGuard SiderolinkWireGuard `yaml:"wireGuard"`
|
||||
// JoinTokensMode controls Talos machine join tokens operation mode.
|
||||
// - strict - only for Talos >= 1.6.x
|
||||
// - legacyAllowed - relies on the legacy join tokens mode for Talos < 1.6.x (less secure, use only if Talos upgrade is not an option)
|
||||
// - legacy - does not use node unique join tokens mode
|
||||
JoinTokensMode JoinTokensMode `yaml:"joinTokensMode" validate:"oneof=strict legacyAllowed legacy"`
|
||||
// DisableLastEndpoint disables populating last known peer endpoint for the WireGuard peers.
|
||||
// Using last known peer endpoints helps Omni quicker re-establish WireGuard connection to the nodes
|
||||
// after it is restarted.
|
||||
// Enable this flag if Omni runs behind the ingress and doesn't see the real node IPs.
|
||||
DisableLastEndpoint bool `yaml:"disableLastEndpoint"`
|
||||
// UsegRPCTunnel forces using WireGuard over gRPC for all machines on the account.
|
||||
UseGRPCTunnel bool `yaml:"useGRPCTunnel"`
|
||||
// EventSinkPort is the port where Talos nodes send Talos events.
|
||||
// This port is only open on the WireGuard tunnel Omni endpoint.
|
||||
EventSinkPort int `yaml:"eventSinkPort"`
|
||||
// LogServerPort is the port where Talos nodes send console logs.
|
||||
// This port is only open on the WireGuard tunnel Omni endpoint.
|
||||
LogServerPort int `yaml:"logServerPort"`
|
||||
}
|
||||
|
||||
// SiderolinkWireGuard defines siderolink wireguard endpoint config.
|
||||
type SiderolinkWireGuard struct {
|
||||
BindEndpoint string `yaml:"endpoint"`
|
||||
AdvertisedEndpoint string `yaml:"advertisedEndpoint"`
|
||||
}
|
||||
|
||||
// MachineAPI is the public API of Omni that helps to establish WireGuard connections.
|
||||
// This API used to exchange WireGuard keys, assign IP addresses.
|
||||
// If gRPC tunnel mode is used, WireGuard traffic goes over this endpoint too.
|
||||
type MachineAPI Service
|
||||
|
||||
// URL composes URL for Talos to connect.
|
||||
func (m MachineAPI) URL() string {
|
||||
if m.AdvertisedURL != "" {
|
||||
return m.AdvertisedURL
|
||||
}
|
||||
|
||||
schema := "grpc"
|
||||
if m.CertFile != "" && m.KeyFile != "" {
|
||||
schema = "https"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s://%s", schema, m.BindEndpoint)
|
||||
}
|
||||
|
||||
// LocalResourceService runs COSI API service that gives readonly access to all resources.
|
||||
type LocalResourceService struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
Port int `yaml:"port"`
|
||||
}
|
||||
|
||||
// EmbeddedDiscoveryService runs https://discovery.talos.dev/ inside Omni.
|
||||
// Discovery service is only available inside the WireGuard tunnel
|
||||
//
|
||||
//nolint:govet
|
||||
type EmbeddedDiscoveryService struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
Port int `yaml:"port"`
|
||||
|
||||
// SnapshotsEnabled turns on the discovery service persistence.
|
||||
SnapshotsEnabled bool `yaml:"snapshotsEnabled"`
|
||||
// SnapshotsPath is the path on disk where to store the discovery service state.
|
||||
SnapshotsPath string `yaml:"snapshotsPath"`
|
||||
SnapshotsInterval time.Duration `yaml:"snapshotsInterval"`
|
||||
LogLevel string `yaml:"logLevel"`
|
||||
}
|
||||
|
||||
// LoadBalancerService configures Omni Kubernetes loadbalancer.
|
||||
type LoadBalancerService struct {
|
||||
// MinPort is the minimum port number used for load balancer endpoints.
|
||||
MinPort int `yaml:"minPort"`
|
||||
// MaxPort is the maximum port number used for load balancer endpoints.
|
||||
MaxPort int `yaml:"maxPort"`
|
||||
|
||||
DialTimeout time.Duration `yaml:"dialTimeout"`
|
||||
KeepAlivePeriod time.Duration `yaml:"keepAlivePeriod"`
|
||||
TCPUserTimeout time.Duration `yaml:"tcpUserTimeout"`
|
||||
|
||||
HealthCheckInterval time.Duration `yaml:"healthCheckInterval"`
|
||||
HealthCheckTimeout time.Duration `yaml:"healthCheckTimeout"`
|
||||
}
|
||||
|
||||
// WorkloadProxy configures workload proxy.
|
||||
type WorkloadProxy struct {
|
||||
Subdomain string `yaml:"subdomain"`
|
||||
Enabled bool `yaml:"enabled"`
|
||||
}
|
||||
|
||||
// JoinTokensMode is the join token operation mode config.
|
||||
//
|
||||
//nolint:recvcheck
|
||||
type JoinTokensMode string
|
||||
|
||||
// String implements pflag.Value.
|
||||
func (s JoinTokensMode) String() string {
|
||||
return string(s)
|
||||
}
|
||||
|
||||
// Set implements pflag.Value.
|
||||
func (s *JoinTokensMode) Set(value string) error {
|
||||
if !slices.Contains(s.values(), value) {
|
||||
return fmt.Errorf("should be one of %s", strings.Join(s.values(), ", "))
|
||||
}
|
||||
|
||||
*s = JoinTokensMode(value)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type implements pflag.Value.
|
||||
func (s JoinTokensMode) Type() string {
|
||||
return fmt.Sprintf("[%s]", strings.Join(s.values(), ","))
|
||||
}
|
||||
|
||||
func (JoinTokensMode) values() []string {
|
||||
return []string{JoinTokensModeLegacyOnly, JoinTokensModeLegacyAllowed, JoinTokensModeStrict}
|
||||
}
|
||||
|
||||
const (
|
||||
// JoinTokensModeLegacyOnly disables node unique token flow, uses only join token when letting the machine into the system.
|
||||
JoinTokensModeLegacyOnly = "legacy"
|
||||
// JoinTokensModeLegacyAllowed allows joining Talos nodes which do not support node unique token flow
|
||||
// uses unique token flow only for the machines which support it.
|
||||
JoinTokensModeLegacyAllowed = "legacyAllowed"
|
||||
// JoinTokensModeStrict rejects the machines that do not support node unique tokens flow.
|
||||
JoinTokensModeStrict = "strict"
|
||||
)
|
||||
57
internal/pkg/config/storage.go
Normal file
57
internal/pkg/config/storage.go
Normal file
@ -0,0 +1,57 @@
|
||||
// Copyright (c) 2025 Sidero Labs, Inc.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the LICENSE file.
|
||||
|
||||
package config
|
||||
|
||||
import "time"
|
||||
|
||||
// Storage defines Omni COSI state storage configuration.
|
||||
type Storage struct {
|
||||
// Vault configuration where the state encryption keys are present.
|
||||
Vault Vault `yaml:"vault"`
|
||||
// Secondary storage is used to store the metrics and any frequently changed resources
|
||||
// which might overflow etcd resource history.
|
||||
Secondary BoltDB `yaml:"secondary" validate:"required"`
|
||||
// Default is the storage used for the default resource namespace in Omni.
|
||||
Default StorageDefault `yaml:"default" validate:"required"`
|
||||
}
|
||||
|
||||
// Vault allows setting vault configuration through the config file.
|
||||
type Vault struct {
|
||||
URL string `yaml:"url"`
|
||||
Token string `yaml:"token"`
|
||||
}
|
||||
|
||||
// StorageDefault defines storage configs.
|
||||
type StorageDefault struct {
|
||||
// Kind can be either 'boltdb' or 'etcd'.
|
||||
Kind string `yaml:"kind" validate:"oneof=etcd boltdb"`
|
||||
Boltdb BoltDB `yaml:"boltdb"`
|
||||
Etcd EtcdParams `yaml:"etcd"`
|
||||
}
|
||||
|
||||
// BoltDB defines boltdb storage configs.
|
||||
type BoltDB struct {
|
||||
Path string `yaml:"path"`
|
||||
}
|
||||
|
||||
// EtcdParams defines etcd storage configs.
|
||||
type EtcdParams struct { ///nolint:govet
|
||||
// External etcd: list of endpoints, as host:port pairs.
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
DialKeepAliveTime time.Duration `yaml:"dialKeepAliveTime"`
|
||||
DialKeepAliveTimeout time.Duration `yaml:"dialKeepAliveTimeout"`
|
||||
CAFile string `yaml:"caFile"`
|
||||
CertFile string `yaml:"certFile"`
|
||||
KeyFile string `yaml:"keyFile"`
|
||||
|
||||
// Use embedded etcd server (no clustering).
|
||||
Embedded bool `yaml:"embedded"`
|
||||
EmbeddedDBPath string `yaml:"embeddedDBPath"`
|
||||
EmbeddedUnsafeFsync bool `yaml:"embeddedUnsafeFsync"`
|
||||
|
||||
PrivateKeySource string `yaml:"privateKeySource" validate:"required"`
|
||||
PublicKeyFiles []string `yaml:"publicKeyFiles"`
|
||||
}
|
||||
69
internal/pkg/config/testdata/backups.yaml
vendored
Normal file
69
internal/pkg/config/testdata/backups.yaml
vendored
Normal file
@ -0,0 +1,69 @@
|
||||
---
|
||||
account:
|
||||
id: "uuid"
|
||||
name: "artem"
|
||||
|
||||
services:
|
||||
api:
|
||||
endpoint: 0.0.0.0:8099
|
||||
kubernetesProxy:
|
||||
endpoint: 0.0.0.0:8095
|
||||
certFile: certFile
|
||||
keyFile: keyFile
|
||||
siderolink:
|
||||
joinTokensMode: strict
|
||||
|
||||
auth:
|
||||
keyPruner:
|
||||
interval: 1m
|
||||
auth0:
|
||||
enabled: true
|
||||
|
||||
logs:
|
||||
machine:
|
||||
storage:
|
||||
enabled: true
|
||||
path: "_out/logs"
|
||||
flushPeriod: 10m
|
||||
flushJitter: 0.1
|
||||
audit:
|
||||
path: _out/audit
|
||||
resourceLogger:
|
||||
types:
|
||||
- Links.omni.siderolabs.dev
|
||||
logLevel: Info
|
||||
stripe:
|
||||
enabled: true
|
||||
|
||||
registries:
|
||||
talos: ghcr.io/siderolabs/installer
|
||||
kubernetes: ghcr.io/siderolabs/kubelet
|
||||
imageFactoryBaseURL: https://factory.talos.dev
|
||||
|
||||
storage:
|
||||
vault:
|
||||
url: http://127.0.0.1:8200
|
||||
token: dev-o-token
|
||||
secondary:
|
||||
path: "_out/secondary-storage/bolt.db"
|
||||
default:
|
||||
kind: etcd
|
||||
boltdb:
|
||||
path: "_out/omni.db"
|
||||
etcd:
|
||||
endpoints:
|
||||
- http://localhost:2379
|
||||
dialKeepAliveTime: 30s
|
||||
dialKeepAliveTimeout: 5s
|
||||
caFile: etcd/ca.crt
|
||||
certFile: etcd/client.crt
|
||||
keyFile: etcd/client.key
|
||||
embedded: true
|
||||
privateKeySource: "vault://secret/omni-private-key"
|
||||
publicKeyFiles:
|
||||
- "internal/backend/runtime/omni/testdata/pgp/new_key.public"
|
||||
embeddedUnsafeFsync: true
|
||||
embeddedDBPath: _out/etcd/
|
||||
etcdBackup:
|
||||
s3Enabled: true
|
||||
localPath: "/hi"
|
||||
120
internal/pkg/config/testdata/config-full.yaml
vendored
Normal file
120
internal/pkg/config/testdata/config-full.yaml
vendored
Normal file
@ -0,0 +1,120 @@
|
||||
---
|
||||
account:
|
||||
id: "uuid"
|
||||
name: "artem"
|
||||
|
||||
services:
|
||||
api:
|
||||
endpoint: 0.0.0.0:8099
|
||||
metrics:
|
||||
endpoint: 0.0.0.0:2122
|
||||
kubernetesProxy:
|
||||
endpoint: 0.0.0.0:8095
|
||||
certFile: certFile
|
||||
keyFile: keyFile
|
||||
siderolink:
|
||||
wireGuard:
|
||||
endpoint: localhost:50180
|
||||
advertisedEndpoint: 192.168.88.219:50180
|
||||
disableLastEndpoint: true
|
||||
useGRPCTunnel: true
|
||||
eventSinkPort: 8091
|
||||
logServerPort: 8092
|
||||
joinTokensMode: strict
|
||||
machineAPI:
|
||||
endpoint: 0.0.0.0:8090
|
||||
advertisedURL: "grpc://192.168.88.219:8090"
|
||||
certFile: hack/certs/api.cert
|
||||
keyFile: hack/certs/api.key
|
||||
localResourceService:
|
||||
enabled: false
|
||||
port: 8081
|
||||
embeddedDiscoveryService:
|
||||
enabled: true
|
||||
port: 8093
|
||||
snapshotsEnabled: true
|
||||
snapshotsPath: "_out/secondary-storage/discovery-service-state.binpb"
|
||||
logLevel: Warn
|
||||
loadBalancer:
|
||||
minPort: 10000
|
||||
maxPort: 20000
|
||||
dialTimeout: 30s
|
||||
devServerProxy:
|
||||
endpoint: 0.0.0.0:8120
|
||||
workloadProxy:
|
||||
enabled: true
|
||||
subdomain: "proxy-us"
|
||||
|
||||
debug:
|
||||
server:
|
||||
endpoint: 0.0.0.0:9988
|
||||
pprof:
|
||||
endpoint: 0.0.0.0:8124
|
||||
|
||||
auth:
|
||||
keyPruner:
|
||||
interval: 1m
|
||||
auth0:
|
||||
enabled: true
|
||||
clientID: TODO
|
||||
domain: TODO
|
||||
initialUsers:
|
||||
- test-user@siderolabs.com
|
||||
initialServiceAccount:
|
||||
enabled: true
|
||||
role: Admin
|
||||
keyPath: _out/test-sa
|
||||
name: tests
|
||||
lifetime: 1m
|
||||
|
||||
registries:
|
||||
talos: ghcr.io/siderolabs/installer
|
||||
kubernetes: ghcr.io/siderolabs/kubelet
|
||||
imageFactoryBaseURL: https://factory.talos.dev
|
||||
|
||||
storage:
|
||||
vault:
|
||||
url: http://127.0.0.1:8200
|
||||
token: dev-o-token
|
||||
secondary:
|
||||
path: "_out/secondary-storage/bolt.db"
|
||||
default:
|
||||
kind: etcd
|
||||
boltdb:
|
||||
path: "_out/omni.db"
|
||||
etcd:
|
||||
endpoints:
|
||||
- http://localhost:2379
|
||||
dialKeepAliveTime: 30s
|
||||
dialKeepAliveTimeout: 5s
|
||||
caFile: etcd/ca.crt
|
||||
certFile: etcd/client.crt
|
||||
keyFile: etcd/client.key
|
||||
embedded: true
|
||||
privateKeySource: "vault://secret/omni-private-key"
|
||||
publicKeyFiles:
|
||||
- "internal/backend/runtime/omni/testdata/pgp/new_key.public"
|
||||
embeddedUnsafeFsync: true
|
||||
embeddedDBPath: _out/etcd/
|
||||
|
||||
logs:
|
||||
machine:
|
||||
storage:
|
||||
enabled: true
|
||||
path: "_out/logs"
|
||||
flushPeriod: 10m
|
||||
flushJitter: 0.1
|
||||
audit:
|
||||
path: _out/audit
|
||||
resourceLogger:
|
||||
types:
|
||||
- Links.omni.siderolabs.dev
|
||||
logLevel: Info
|
||||
stripe:
|
||||
enabled: true
|
||||
|
||||
features:
|
||||
enableTalosPreReleaseVersions: true
|
||||
enableConfigDataCompression: true
|
||||
enableBreakGlassConfigs: true
|
||||
disableControllerRuntimeCache: false
|
||||
72
internal/pkg/config/testdata/conflicting-auth.yaml
vendored
Normal file
72
internal/pkg/config/testdata/conflicting-auth.yaml
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
---
|
||||
account:
|
||||
id: "uuid"
|
||||
name: "artem"
|
||||
|
||||
services:
|
||||
api:
|
||||
endpoint: 0.0.0.0:8099
|
||||
kubernetesProxy:
|
||||
endpoint: 0.0.0.0:8095
|
||||
certFile: certFile
|
||||
keyFile: keyFile
|
||||
|
||||
auth:
|
||||
keyPruner:
|
||||
interval: 1m
|
||||
auth0:
|
||||
enabled: true
|
||||
saml:
|
||||
enabled: true
|
||||
|
||||
registries:
|
||||
talos: ghcr.io/siderolabs/installer
|
||||
kubernetes: ghcr.io/siderolabs/kubelet
|
||||
imageFactoryBaseURL: https://factory.talos.dev
|
||||
|
||||
storage:
|
||||
vault:
|
||||
url: http://127.0.0.1:8200
|
||||
token: dev-o-token
|
||||
secondary:
|
||||
path: "_out/secondary-storage/bolt.db"
|
||||
default:
|
||||
kind: etcd
|
||||
boltdb:
|
||||
path: "_out/omni.db"
|
||||
etcd:
|
||||
endpoints:
|
||||
- http://localhost:2379
|
||||
dialKeepAliveTime: 30s
|
||||
dialKeepAliveTimeout: 5s
|
||||
caFile: etcd/ca.crt
|
||||
certFile: etcd/client.crt
|
||||
keyFile: etcd/client.key
|
||||
embedded: true
|
||||
privateKeySource: "vault://secret/omni-private-key"
|
||||
publicKeyFiles:
|
||||
- "internal/backend/runtime/omni/testdata/pgp/new_key.public"
|
||||
embeddedUnsafeFsync: true
|
||||
embeddedDBPath: _out/etcd/
|
||||
|
||||
logs:
|
||||
machine:
|
||||
storage:
|
||||
enabled: true
|
||||
path: "_out/logs"
|
||||
flushPeriod: 10m
|
||||
flushJitter: 0.1
|
||||
audit:
|
||||
path: _out/audit
|
||||
resourceLogger:
|
||||
types:
|
||||
- Links.omni.siderolabs.dev
|
||||
logLevel: Info
|
||||
stripe:
|
||||
enabled: true
|
||||
|
||||
features:
|
||||
enableTalosPreReleaseVersions: true
|
||||
enableConfigDataCompression: true
|
||||
enableBreakGlassConfigs: true
|
||||
disableControllerRuntimeCache: false
|
||||
71
internal/pkg/config/testdata/invalid-join-token-mode.yaml
vendored
Normal file
71
internal/pkg/config/testdata/invalid-join-token-mode.yaml
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
|
||||
---
|
||||
account:
|
||||
id: "uuid"
|
||||
name: "artem"
|
||||
|
||||
services:
|
||||
api:
|
||||
endpoint: 0.0.0.0:8099
|
||||
kubernetesProxy:
|
||||
endpoint: 0.0.0.0:8095
|
||||
certFile: certFile
|
||||
keyFile: keyFile
|
||||
siderolink:
|
||||
joinTokensMode: nope
|
||||
|
||||
auth:
|
||||
keyPruner:
|
||||
interval: 1m
|
||||
|
||||
registries:
|
||||
talos: ghcr.io/siderolabs/installer
|
||||
kubernetes: ghcr.io/siderolabs/kubelet
|
||||
imageFactoryBaseURL: https://factory.talos.dev
|
||||
|
||||
storage:
|
||||
vault:
|
||||
url: http://127.0.0.1:8200
|
||||
token: dev-o-token
|
||||
secondary:
|
||||
path: "_out/secondary-storage/bolt.db"
|
||||
default:
|
||||
kind: etcd
|
||||
boltdb:
|
||||
path: "_out/omni.db"
|
||||
etcd:
|
||||
endpoints:
|
||||
- http://localhost:2379
|
||||
dialKeepAliveTime: 30s
|
||||
dialKeepAliveTimeout: 5s
|
||||
caFile: etcd/ca.crt
|
||||
certFile: etcd/client.crt
|
||||
keyFile: etcd/client.key
|
||||
embedded: true
|
||||
privateKeySource: "vault://secret/omni-private-key"
|
||||
publicKeyFiles:
|
||||
- "internal/backend/runtime/omni/testdata/pgp/new_key.public"
|
||||
embeddedUnsafeFsync: true
|
||||
embeddedDBPath: _out/etcd/
|
||||
|
||||
logs:
|
||||
machine:
|
||||
storage:
|
||||
enabled: true
|
||||
path: "_out/logs"
|
||||
flushPeriod: 10m
|
||||
flushJitter: 0.1
|
||||
audit:
|
||||
path: _out/audit
|
||||
resourceLogger:
|
||||
types:
|
||||
- Links.omni.siderolabs.dev
|
||||
logLevel: Info
|
||||
stripe:
|
||||
enabled: true
|
||||
|
||||
features:
|
||||
enableTalosPreReleaseVersions: true
|
||||
enableConfigDataCompression: true
|
||||
enableBreakGlassConfigs: true
|
||||
disableControllerRuntimeCache: false
|
||||
124
internal/pkg/config/testdata/unknown-keys.yaml
vendored
Normal file
124
internal/pkg/config/testdata/unknown-keys.yaml
vendored
Normal file
@ -0,0 +1,124 @@
|
||||
---
|
||||
account:
|
||||
id: "uuid"
|
||||
name: "artem"
|
||||
|
||||
services:
|
||||
api:
|
||||
endpoint: 0.0.0.0:8099
|
||||
metrics:
|
||||
endpoint: 0.0.0.0:2122
|
||||
kubernetesProxy:
|
||||
endpoint: 0.0.0.0:8095
|
||||
certFile: certFile
|
||||
keyFile: keyFile
|
||||
siderolink:
|
||||
wireGuard:
|
||||
endpoint: localhost:50180
|
||||
advertisedEndpoint: 192.168.88.219:50180
|
||||
disableLastEndpoint: true
|
||||
useGRPCTunnel: true
|
||||
eventSinkPort: 8091
|
||||
logServerPort: 8092
|
||||
joinTokensMode: strict
|
||||
machineAPI:
|
||||
endpoint: 0.0.0.0:8090
|
||||
advertisedURL: "grpc://192.168.88.219:8090"
|
||||
certFile: hack/certs/api.cert
|
||||
keyFile: hack/certs/api.key
|
||||
localResourceService:
|
||||
enabled: false
|
||||
port: 8081
|
||||
embeddedDiscoveryService:
|
||||
enabled: true
|
||||
port: 8093
|
||||
snapshotsEnabled: true
|
||||
snapshotsPath: "_out/secondary-storage/discovery-service-state.binpb"
|
||||
logLevel: Warn
|
||||
loadBalancer:
|
||||
minPort: 10000
|
||||
maxPort: 20000
|
||||
dialTimeout: 30s
|
||||
devServerProxy:
|
||||
endpoint: 0.0.0.0:8120
|
||||
workloadProxy:
|
||||
enabled: true
|
||||
subdomain: "proxy-us"
|
||||
|
||||
debug:
|
||||
server:
|
||||
enabled: true
|
||||
endpoint: 0.0.0.0:9988
|
||||
pprof:
|
||||
enabled: true
|
||||
endpoint: 0.0.0.0:8124
|
||||
|
||||
auth:
|
||||
keyPruner:
|
||||
interval: 1m
|
||||
auth0:
|
||||
enabled: true
|
||||
clientID: TODO
|
||||
domain: TODO
|
||||
initialUsers:
|
||||
- test-user@siderolabs.com
|
||||
initialServiceAccount:
|
||||
enabled: true
|
||||
role: Admin
|
||||
keyPath: _out/test-sa
|
||||
name: tests
|
||||
lifetime: 1m
|
||||
|
||||
registries:
|
||||
talos: ghcr.io/siderolabs/installer
|
||||
kubernetes: ghcr.io/siderolabs/kubelet
|
||||
imageFactoryBaseURL: https://factory.talos.dev
|
||||
|
||||
storage:
|
||||
vault:
|
||||
url: http://127.0.0.1:8200
|
||||
token: dev-o-token
|
||||
secondary:
|
||||
path: "_out/secondary-storage/bolt.db"
|
||||
default:
|
||||
kind: etcd
|
||||
boltdb:
|
||||
path: "_out/omni.db"
|
||||
etcd:
|
||||
endpoints:
|
||||
- http://localhost:2379
|
||||
dialKeepAliveTime: 30s
|
||||
dialKeepAliveTimeout: 5s
|
||||
caFile: etcd/ca.crt
|
||||
certFile: etcd/client.crt
|
||||
keyFile: etcd/client.key
|
||||
embedded: true
|
||||
privateKeySource: "vault://secret/omni-private-key"
|
||||
publicKeyFiles:
|
||||
- "internal/backend/runtime/omni/testdata/pgp/new_key.public"
|
||||
embeddedUnsafeFsync: true
|
||||
embeddedDBPath: _out/etcd/
|
||||
|
||||
logs:
|
||||
machine:
|
||||
storage:
|
||||
enabled: true
|
||||
path: "_out/logs"
|
||||
flushPeriod: 10m
|
||||
flushJitter: 0.1
|
||||
audit:
|
||||
path: _out/audit
|
||||
resourceLogger:
|
||||
types:
|
||||
- Links.omni.siderolabs.dev
|
||||
logLevel: Info
|
||||
stripe:
|
||||
enabled: true
|
||||
|
||||
features:
|
||||
enableTalosPreReleaseVersions: true
|
||||
enableConfigDataCompression: true
|
||||
enableBreakGlassConfigs: true
|
||||
disableControllerRuntimeCache: false
|
||||
|
||||
johndoe: unknown
|
||||
@ -24,16 +24,16 @@ import (
|
||||
// UpdateResources creates or updates the features omni.FeaturesConfig resource with the current feature flags.
|
||||
func UpdateResources(ctx context.Context, st state.State, logger *zap.Logger) error {
|
||||
updateFeaturesConfig := func(res *omni.FeaturesConfig) error {
|
||||
res.TypedSpec().Value.EnableWorkloadProxying = config.Config.WorkloadProxying.Enabled
|
||||
res.TypedSpec().Value.EmbeddedDiscoveryService = config.Config.EmbeddedDiscoveryService.Enabled
|
||||
res.TypedSpec().Value.EnableWorkloadProxying = config.Config.Services.WorkloadProxy.Enabled
|
||||
res.TypedSpec().Value.EmbeddedDiscoveryService = config.Config.Services.EmbeddedDiscoveryService.Enabled
|
||||
res.TypedSpec().Value.EtcdBackupSettings = &specs.EtcdBackupSettings{
|
||||
TickInterval: durationpb.New(config.Config.EtcdBackup.TickInterval),
|
||||
MinInterval: durationpb.New(config.Config.EtcdBackup.MinInterval),
|
||||
MaxInterval: durationpb.New(config.Config.EtcdBackup.MaxInterval),
|
||||
}
|
||||
|
||||
res.TypedSpec().Value.AuditLogEnabled = config.Config.AuditLogDir != ""
|
||||
res.TypedSpec().Value.ImageFactoryBaseUrl = config.Config.ImageFactoryBaseURL
|
||||
res.TypedSpec().Value.AuditLogEnabled = config.Config.Logs.Audit.Path != ""
|
||||
res.TypedSpec().Value.ImageFactoryBaseUrl = config.Config.Registries.ImageFactoryBaseURL
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -55,7 +55,7 @@ func UpdateResources(ctx context.Context, st state.State, logger *zap.Logger) er
|
||||
return fmt.Errorf("failed to create features config: %w", err)
|
||||
}
|
||||
|
||||
logger.Info("created features config resource", zap.Bool("enable_workload_proxying", config.Config.WorkloadProxying.Enabled))
|
||||
logger.Info("created features config resource", zap.Bool("enable_workload_proxying", config.Config.Services.WorkloadProxy.Enabled))
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -64,7 +64,7 @@ func UpdateResources(ctx context.Context, st state.State, logger *zap.Logger) er
|
||||
return fmt.Errorf("failed to update features config: %w", err)
|
||||
}
|
||||
|
||||
logger.Info("updated features config resource", zap.Bool("enable_workload_proxying", config.Config.WorkloadProxying.Enabled))
|
||||
logger.Info("updated features config resource", zap.Bool("enable_workload_proxying", config.Config.Services.WorkloadProxy.Enabled))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@ import (
|
||||
)
|
||||
|
||||
// NewLogHandler returns a new LogHandler.
|
||||
func NewLogHandler(machineMap *MachineMap, omniState state.State, storageConfig *config.MachineLogConfigParams, logger *zap.Logger) (*LogHandler, error) {
|
||||
func NewLogHandler(machineMap *MachineMap, omniState state.State, storageConfig *config.LogsMachine, logger *zap.Logger) (*LogHandler, error) {
|
||||
cache, err := NewMachineCache(storageConfig, logger)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create machine cache: %w", err)
|
||||
|
||||
@ -35,13 +35,15 @@ import (
|
||||
)
|
||||
|
||||
func TestLogHandler_HandleMessage(t *testing.T) {
|
||||
storageConfig := config.MachineLogConfigParams{
|
||||
storageConfig := config.LogsMachine{
|
||||
BufferInitialCapacity: 16,
|
||||
BufferMaxCapacity: 128,
|
||||
BufferSafetyGap: 16,
|
||||
NumCompressedChunks: 5,
|
||||
StorageFlushPeriod: time.Second,
|
||||
StorageEnabled: false,
|
||||
Storage: config.LogsMachineStorage{
|
||||
NumCompressedChunks: 5,
|
||||
FlushPeriod: time.Second,
|
||||
Enabled: false,
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("empty log message", func(t *testing.T) {
|
||||
@ -189,11 +191,13 @@ func TestLogHandlerStorage(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
logger := zaptest.NewLogger(t)
|
||||
|
||||
logHandler, err := siderolink.NewLogHandler(machineMap, st, &config.MachineLogConfigParams{
|
||||
StorageEnabled: true,
|
||||
StoragePath: tempDir,
|
||||
StorageFlushPeriod: 2 * time.Second,
|
||||
NumCompressedChunks: 5,
|
||||
logHandler, err := siderolink.NewLogHandler(machineMap, st, &config.LogsMachine{
|
||||
Storage: config.LogsMachineStorage{
|
||||
Enabled: true,
|
||||
Path: tempDir,
|
||||
FlushPeriod: 2 * time.Second,
|
||||
NumCompressedChunks: 5,
|
||||
},
|
||||
BufferInitialCapacity: 16,
|
||||
BufferMaxCapacity: 128,
|
||||
BufferSafetyGap: 16,
|
||||
@ -274,11 +278,13 @@ func TestLogHandlerStorageLegacyMigration(t *testing.T) {
|
||||
require.NoError(t, os.WriteFile(legacyLogPath, legacyLog, 0o644))
|
||||
require.NoError(t, os.WriteFile(legacyLogHashPath, []byte(hex.EncodeToString(legacyHash[:])), 0o644))
|
||||
|
||||
logHandler, err := siderolink.NewLogHandler(machineMap, st, &config.MachineLogConfigParams{
|
||||
StorageEnabled: true,
|
||||
StoragePath: tempDir,
|
||||
StorageFlushPeriod: 1 * time.Second,
|
||||
NumCompressedChunks: 5,
|
||||
logHandler, err := siderolink.NewLogHandler(machineMap, st, &config.LogsMachine{
|
||||
Storage: config.LogsMachineStorage{
|
||||
Enabled: true,
|
||||
Path: tempDir,
|
||||
FlushPeriod: 1 * time.Second,
|
||||
NumCompressedChunks: 5,
|
||||
},
|
||||
BufferInitialCapacity: 16,
|
||||
BufferMaxCapacity: 128,
|
||||
BufferSafetyGap: 16,
|
||||
@ -327,10 +333,12 @@ func TestLogHandlerStorageDisabled(t *testing.T) {
|
||||
require.NoError(t, st.Create(ctx, omni.NewMachine(resources.DefaultNamespace, "machine-2")))
|
||||
|
||||
tempDir := t.TempDir()
|
||||
storageConfig := config.MachineLogConfigParams{
|
||||
StorageEnabled: false,
|
||||
StoragePath: tempDir,
|
||||
StorageFlushPeriod: 100 * time.Millisecond,
|
||||
storageConfig := config.LogsMachine{
|
||||
Storage: config.LogsMachineStorage{
|
||||
Enabled: false,
|
||||
Path: tempDir,
|
||||
FlushPeriod: 100 * time.Millisecond,
|
||||
},
|
||||
}
|
||||
|
||||
handler, err := siderolink.NewLogHandler(machineMap, st, &storageConfig, zaptest.NewLogger(t))
|
||||
|
||||
@ -36,14 +36,14 @@ import (
|
||||
type MachineCache struct {
|
||||
machineBuffers containers.LazyMap[MachineID, *circular.Buffer]
|
||||
logger *zap.Logger
|
||||
logStorageConfig *config.MachineLogConfigParams
|
||||
logStorageConfig *config.LogsMachine
|
||||
compressor *zstd.Compressor
|
||||
mx sync.Mutex
|
||||
inited bool
|
||||
}
|
||||
|
||||
// NewMachineCache returns a new MachineCache.
|
||||
func NewMachineCache(logStorageConfig *config.MachineLogConfigParams, logger *zap.Logger) (*MachineCache, error) {
|
||||
func NewMachineCache(logStorageConfig *config.LogsMachine, logger *zap.Logger) (*MachineCache, error) {
|
||||
compressor, err := zstd.NewCompressor()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create log compressor: %w", err)
|
||||
@ -92,13 +92,13 @@ func (m *MachineCache) GetBuffer(id MachineID) (*circular.Buffer, error) {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
if !m.logStorageConfig.StorageEnabled {
|
||||
if !m.logStorageConfig.Storage.Enabled {
|
||||
return nil, &BufferNotFoundError{id: id}
|
||||
}
|
||||
|
||||
// Probe the file system to check if a log file exists for this machine.
|
||||
// Check both for the old (/path/machine-id.log) and the new (/path/machine-id.log.NUM) format.
|
||||
glob := filepath.Join(m.logStorageConfig.StoragePath, string(id)+".log*")
|
||||
glob := filepath.Join(m.logStorageConfig.Storage.Path, string(id)+".log*")
|
||||
|
||||
matches, err := filepath.Glob(glob)
|
||||
if err != nil {
|
||||
@ -149,7 +149,7 @@ func (m *MachineCache) Remove(id MachineID) error {
|
||||
|
||||
m.machineBuffers.Remove(id)
|
||||
|
||||
matches, err := filepath.Glob(filepath.Join(m.logStorageConfig.StoragePath, string(id)+".log*"))
|
||||
matches, err := filepath.Glob(filepath.Join(m.logStorageConfig.Storage.Path, string(id)+".log*"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list log files for machine %q: %w", id, err)
|
||||
}
|
||||
@ -194,15 +194,15 @@ func (m *MachineCache) init() {
|
||||
circular.WithInitialCapacity(m.logStorageConfig.BufferInitialCapacity),
|
||||
circular.WithMaxCapacity(m.logStorageConfig.BufferMaxCapacity),
|
||||
circular.WithSafetyGap(m.logStorageConfig.BufferSafetyGap),
|
||||
circular.WithNumCompressedChunks(m.logStorageConfig.NumCompressedChunks, m.compressor),
|
||||
circular.WithNumCompressedChunks(m.logStorageConfig.Storage.NumCompressedChunks, m.compressor),
|
||||
circular.WithLogger(m.logger),
|
||||
}
|
||||
|
||||
if m.logStorageConfig.StorageEnabled {
|
||||
if m.logStorageConfig.Storage.Enabled {
|
||||
bufferOpts = append(bufferOpts, circular.WithPersistence(circular.PersistenceOptions{
|
||||
ChunkPath: filepath.Join(m.logStorageConfig.StoragePath, string(id)+".log"),
|
||||
FlushInterval: m.logStorageConfig.StorageFlushPeriod,
|
||||
FlushJitter: m.logStorageConfig.StorageFlushJitter,
|
||||
ChunkPath: filepath.Join(m.logStorageConfig.Storage.Path, string(id)+".log"),
|
||||
FlushInterval: m.logStorageConfig.Storage.FlushPeriod,
|
||||
FlushJitter: m.logStorageConfig.Storage.FlushJitter,
|
||||
}))
|
||||
}
|
||||
|
||||
@ -211,7 +211,7 @@ func (m *MachineCache) init() {
|
||||
return nil, fmt.Errorf("failed to create circular buffer for machine '%s': %w", id, err)
|
||||
}
|
||||
|
||||
if m.logStorageConfig.StorageEnabled {
|
||||
if m.logStorageConfig.Storage.Enabled {
|
||||
loadLegacyLogs(m.logStorageConfig, id, buffer, m.logger)
|
||||
}
|
||||
|
||||
@ -327,8 +327,8 @@ func IsBufferNotFoundError(err error) bool {
|
||||
// It removes the old log file and its hash file regardless of the result.
|
||||
//
|
||||
// It is a best-effort function and does not return any error.
|
||||
func loadLegacyLogs(config *config.MachineLogConfigParams, id MachineID, writer io.Writer, logger *zap.Logger) {
|
||||
filePath := filepath.Join(config.StoragePath, fmt.Sprintf("%s.log", id))
|
||||
func loadLegacyLogs(config *config.LogsMachine, id MachineID, writer io.Writer, logger *zap.Logger) {
|
||||
filePath := filepath.Join(config.Storage.Path, fmt.Sprintf("%s.log", id))
|
||||
shaSumPath := filePath + ".sha256sum"
|
||||
|
||||
defer func() {
|
||||
|
||||
@ -109,8 +109,8 @@ func NewManager(
|
||||
provisionServer: NewProvisionHandler(
|
||||
logger,
|
||||
state,
|
||||
config.Config.JoinTokensMode,
|
||||
config.Config.SiderolinkUseGRPCTunnel,
|
||||
config.Config.Services.Siderolink.JoinTokensMode,
|
||||
config.Config.Services.Siderolink.UseGRPCTunnel,
|
||||
),
|
||||
}
|
||||
|
||||
@ -192,34 +192,34 @@ func generateJoinToken() (string, error) {
|
||||
type Params struct {
|
||||
WireguardEndpoint string
|
||||
AdvertisedEndpoint string
|
||||
APIEndpoint string
|
||||
Cert string
|
||||
Key string
|
||||
MachineAPIEndpoint string
|
||||
MachineAPITLSCert string
|
||||
MachineAPITLSKey string
|
||||
EventSinkPort string
|
||||
}
|
||||
|
||||
// NewListener creates a new listener.
|
||||
func (p *Params) NewListener() (net.Listener, error) {
|
||||
if p.APIEndpoint == "" {
|
||||
if p.MachineAPIEndpoint == "" {
|
||||
return nil, errors.New("no siderolink API endpoint specified")
|
||||
}
|
||||
|
||||
switch {
|
||||
case p.Cert == "" && p.Key == "":
|
||||
case p.MachineAPITLSCert == "" && p.MachineAPITLSKey == "":
|
||||
// no key, no cert - use plain TCP
|
||||
return net.Listen("tcp", p.APIEndpoint)
|
||||
case p.Cert == "":
|
||||
return net.Listen("tcp", p.MachineAPIEndpoint)
|
||||
case p.MachineAPITLSCert == "":
|
||||
return nil, errors.New("siderolink cert is required")
|
||||
case p.Key == "":
|
||||
case p.MachineAPITLSKey == "":
|
||||
return nil, errors.New("siderolink key is required")
|
||||
}
|
||||
|
||||
cert, err := tls.LoadX509KeyPair(p.Cert, p.Key)
|
||||
cert, err := tls.LoadX509KeyPair(p.MachineAPITLSCert, p.MachineAPITLSKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load siderolink cert/key: %w", err)
|
||||
}
|
||||
|
||||
return tls.Listen("tcp", p.APIEndpoint, &tls.Config{
|
||||
return tls.Listen("tcp", p.MachineAPIEndpoint, &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
NextProtos: []string{"h2"},
|
||||
})
|
||||
@ -541,7 +541,7 @@ func (manager *Manager) pollWireguardPeers(ctx context.Context) error {
|
||||
spec.Connected = sinceLastHandshake < wireguard.PeerDownInterval
|
||||
}
|
||||
|
||||
if config.Config.SiderolinkDisableLastEndpoint {
|
||||
if config.Config.Services.Siderolink.DisableLastEndpoint {
|
||||
spec.LastEndpoint = ""
|
||||
|
||||
return nil
|
||||
@ -579,12 +579,12 @@ func (manager *Manager) updateConnectionParams(ctx context.Context, siderolinkCo
|
||||
if _, err = safe.StateUpdateWithConflicts(ctx, manager.state, connectionParams.Metadata(), func(res *siderolink.ConnectionParams) error {
|
||||
spec := res.TypedSpec().Value
|
||||
|
||||
spec.ApiEndpoint = config.Config.SideroLinkAPIURL
|
||||
spec.ApiEndpoint = config.Config.Services.MachineAPI.URL()
|
||||
spec.JoinToken = siderolinkConfig.TypedSpec().Value.JoinToken
|
||||
spec.WireguardEndpoint = siderolinkConfig.TypedSpec().Value.AdvertisedEndpoint
|
||||
spec.UseGrpcTunnel = config.Config.SiderolinkUseGRPCTunnel
|
||||
spec.LogsPort = int32(config.Config.LogServerPort)
|
||||
spec.EventsPort = int32(config.Config.EventSinkPort)
|
||||
spec.UseGrpcTunnel = config.Config.Services.Siderolink.UseGRPCTunnel
|
||||
spec.LogsPort = int32(config.Config.Services.Siderolink.LogServerPort)
|
||||
spec.EventsPort = int32(config.Config.Services.Siderolink.EventSinkPort)
|
||||
|
||||
var url string
|
||||
|
||||
@ -604,7 +604,7 @@ func (manager *Manager) updateConnectionParams(ctx context.Context, siderolinkCo
|
||||
talosconstants.KernelParamLoggingKernel,
|
||||
net.JoinHostPort(
|
||||
siderolinkConfig.TypedSpec().Value.ServerAddress,
|
||||
strconv.Itoa(config.Config.LogServerPort),
|
||||
strconv.Itoa(config.Config.Services.Siderolink.LogServerPort),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@ -121,8 +121,8 @@ func (suite *SiderolinkSuite) SetupTest() {
|
||||
|
||||
params := sideromanager.Params{
|
||||
WireguardEndpoint: "127.0.0.1:0",
|
||||
AdvertisedEndpoint: config.Config.SiderolinkWireguardAdvertisedAddress + "," + TestIP,
|
||||
APIEndpoint: "127.0.0.1:0",
|
||||
AdvertisedEndpoint: config.Config.Services.Siderolink.WireGuard.AdvertisedEndpoint + "," + TestIP,
|
||||
MachineAPIEndpoint: "127.0.0.1:0",
|
||||
}
|
||||
|
||||
nodeUniqueToken, err := jointoken.NewNodeUniqueToken("fingerprint", "test-token").Encode()
|
||||
@ -321,7 +321,7 @@ func (suite *SiderolinkSuite) TestNodeWithSeveralAdvertisedIPs() {
|
||||
},
|
||||
))(suite.T())
|
||||
|
||||
require.Equal(suite.T(), []string{config.Config.SiderolinkWireguardAdvertisedAddress, TestIP}, resp.GetEndpoints())
|
||||
require.Equal(suite.T(), []string{config.Config.Services.Siderolink.WireGuard.AdvertisedEndpoint, TestIP}, resp.GetEndpoints())
|
||||
}
|
||||
|
||||
func (suite *SiderolinkSuite) TestVirtualNodes() {
|
||||
@ -403,7 +403,7 @@ func (suite *SiderolinkSuite) TestVirtualNodes() {
|
||||
|
||||
expectedResp := resp.CloneVT()
|
||||
expectedResp.GrpcPeerAddrPort = ""
|
||||
expectedResp.ServerEndpoint = pb.MakeEndpoints(config.Config.SiderolinkWireguardAdvertisedAddress, TestIP)
|
||||
expectedResp.ServerEndpoint = pb.MakeEndpoints(config.Config.Services.Siderolink.WireGuard.AdvertisedEndpoint, TestIP)
|
||||
|
||||
suite.Assert().NoError(err)
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user