Unverified Commit bf18712a authored by Lemon-miaow's avatar Lemon-miaow
Browse files

feat(build): kaniko/trivy 与扫描库改用 registry 内的 mirror 副本,定时刷新并在过期时告警

parent c0643af1
Loading
Loading
Loading
Loading
+3 −11
Changes for cmd/felis/api.go: 3 added lines, 11 removed lines.
Original line number Diff line number Diff line
@@ -466,10 +466,8 @@ func buildConfig(cfg *config.Config) build.Config {
	return build.Config{
		Namespace:   cfg.Registry.BuildNamespace,
		RegistryURL: cfg.Registry.URL,
		// Empty overrides fall back to the build package's defaults, so an
		// install that has not imported kaniko/trivy keeps the compiled-in refs
		// (and fails loudly on pull rather than silently building with the wrong
		// image).
		// Empty overrides fall back to the registry's copies of the tools
		// (build.Tools), which felis mirror-build-tools keeps current.
		KanikoImage: cfg.Registry.KanikoImage,
		TrivyImage:  cfg.Registry.TrivyImage,
		CPULimit:    cfg.Registry.BuildCPULimit,
@@ -480,8 +478,6 @@ func buildConfig(cfg *config.Config) build.Config {
		UserNamespacesProbe:   new(atomic.Bool),
		RuntimeClass:          cfg.Registry.BuildRuntimeClass,
		MaxConcurrent:         cfg.Registry.MaxConcurrentBuilds,
		// Empty keeps Trivy's own default; an install with builds points this at
		// the internal DB mirror (see config.RegistryConfig.TrivyDBRepository).
		TrivyDBRepository:     cfg.Registry.TrivyDBRepository,
		TrivyJavaDBRepository: cfg.Registry.TrivyJavaDBRepository,
		// The submit lane's derived context URLs live here; the fetch step's
@@ -669,11 +665,7 @@ func registryPruner(cfg *config.Config, store imageRefStore, servers serverListe
		fmt.Fprintf(stderr, "felis api: registry pruner disabled (%s unset) — images nothing uses are never deleted from the registry\n", platform.RegistryPruneTokenEnv)
		return nil
	}
	static := []string{
		os.Getenv("FELIS_IMAGE"),
		cfg.Registry.KanikoImage, cfg.Registry.TrivyImage,
		cfg.Registry.TrivyDBRepository, cfg.Registry.TrivyJavaDBRepository,
	}
	static := append([]string{os.Getenv("FELIS_IMAGE")}, buildConfig(cfg).ToolRefs()...)
	return &registryprune.Pruner{
		Registry: &registryprune.Client{Endpoint: "http://" + cfg.Registry.URL, Token: token},
		Host:     cfg.Registry.URL,
+125 −0
Changes for cmd/felis/mirrortools.go: 125 added lines, 0 removed lines.
Original line number Diff line number Diff line
package main

import (
	"context"
	"errors"
	"flag"
	"fmt"
	"io"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"felis.lolicon.best/internal/build"
	"felis.lolicon.best/internal/imagepush"
	"felis.lolicon.best/internal/registrygate"
)

// defaultBuildToolsStatus is where mirror-build-tools records its last run; the
// watchdog reads it to tell a vulnerability DB that stopped refreshing.
const defaultBuildToolsStatus = "/var/lib/felis/build-tools/status.json"

// cmdMirrorBuildTools copies the build lane's tools (build.Tools: the kaniko and
// trivy images, Trivy's vulnerability and Java DBs) from upstream into the
// platform registry, where build Jobs pull them. deploy/bootstrap.sh runs it at
// install and from felis-build-tools.timer twice a day, which is what keeps the
// DBs fresh; a root shell can run it the same way to refresh now.
//
// It writes as the platform principal through the node's loopback hostPort, the
// same way the installer pushes, reading the token from the environment or from
// /etc/felis/secrets.env.
func cmdMirrorBuildTools(args []string, stdout, stderr io.Writer) int {
	fs := flag.NewFlagSet("mirror-build-tools", flag.ContinueOnError)
	fs.SetOutput(stderr)
	endpoint := fs.String("endpoint", "127.0.0.1:5000", "host[:port] of the registry to write to (plain HTTP)")
	only := fs.String("only", "", "comma-separated tool names to copy (default: all of "+toolNames()+")")
	status := fs.String("status", defaultBuildToolsStatus, `file to record the run in ("" records nothing)`)
	secrets := fs.String("secrets-env", "/etc/felis/secrets.env", "installer secrets file holding REGISTRY_PLATFORM_TOKEN, read when FELIS_REGISTRY_PASSWORD is unset")
	platformFlag := fs.String("platform", "", "os/arch of the images to copy (default: this machine's)")
	if err := fs.Parse(args); err != nil {
		if errors.Is(err, flag.ErrHelp) {
			return 0
		}
		return 2
	}
	tools, err := selectTools(*only)
	if err != nil {
		fmt.Fprintf(stderr, "felis mirror-build-tools: %v\n", err)
		return 2
	}
	if err := loadEnvFile(*secrets); err != nil {
		fmt.Fprintf(stderr, "felis mirror-build-tools: read %s: %v\n", *secrets, err)
		return 1
	}
	user, pass := os.Getenv("FELIS_REGISTRY_USERNAME"), os.Getenv("FELIS_REGISTRY_PASSWORD")
	if pass == "" {
		user, pass = registrygate.PrincipalPlatform, os.Getenv("REGISTRY_PLATFORM_TOKEN")
	}
	if pass == "" {
		fmt.Fprintln(stderr, "felis mirror-build-tools: no registry credential: set FELIS_REGISTRY_PASSWORD or run as root on the node (REGISTRY_PLATFORM_TOKEN in /etc/felis/secrets.env)")
		return 2
	}

	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
	defer stop()
	p := &imagepush.Pusher{Scheme: "http", Username: user, Password: pass, Log: stdout}
	src := &imagepush.Source{Platform: *platformFlag}
	started := time.Now()
	var failed []string
	for _, t := range tools {
		dst := strings.TrimSuffix(*endpoint, "/") + "/" + t.Mirror
		if _, err := p.Mirror(ctx, src, t.Source, dst); err != nil {
			fmt.Fprintf(stderr, "felis mirror-build-tools: %s: %v\n", t.Name, err)
			failed = append(failed, t.Name+": "+err.Error())
		}
	}
	if *status != "" {
		st, err := imagepush.ReadMirrorStatus(*status)
		if err != nil || st == nil {
			st = &imagepush.MirrorStatus{}
		}
		st.LastAttempt = started
		st.LastError = strings.Join(failed, "; ")
		if len(failed) == 0 {
			st.LastSuccess = started
		}
		if err := imagepush.WriteMirrorStatus(*status, *st); err != nil {
			fmt.Fprintf(stderr, "felis mirror-build-tools: record %s: %v\n", *status, err)
		}
	}
	if len(failed) > 0 {
		return 1
	}
	return 0
}

func toolNames() string {
	var names []string
	for _, t := range build.Tools {
		names = append(names, t.Name)
	}
	return strings.Join(names, ",")
}

func selectTools(only string) ([]build.Tool, error) {
	if only == "" {
		return build.Tools, nil
	}
	var out []build.Tool
	for _, name := range strings.Split(only, ",") {
		name = strings.TrimSpace(name)
		found := false
		for _, t := range build.Tools {
			if t.Name == name {
				out = append(out, t)
				found = true
			}
		}
		if !found {
			return nil, fmt.Errorf("unknown tool %q (known: %s)", name, toolNames())
		}
	}
	return out, nil
}
+5 −5
Changes for cmd/felis/offsite.go: 5 added lines, 5 removed lines.
Original line number Diff line number Diff line
@@ -90,10 +90,10 @@ type offsiteEnv struct {
	key    []byte
}

// loadOffsiteEnvFile sets each KEY=VALUE of path that is not already in the
// environment, so a root shell reaches the bucket the same way the unit does.
// loadEnvFile sets each KEY=VALUE of path that is not already in the
// environment, so a root shell runs a command the same way its unit does.
// A missing file is not an error.
func loadOffsiteEnvFile(path string) error {
func loadEnvFile(path string) error {
	if path == "" {
		return nil
	}
@@ -167,7 +167,7 @@ func resolveOffsite(c config.OffsiteConfig) (*offsiteEnv, error) {

// loadOffsite loads felis.toml and the env file and resolves [offsite].
func loadOffsite(cfgPath, envFile string) (*config.Config, *offsiteEnv, error) {
	if err := loadOffsiteEnvFile(envFile); err != nil {
	if err := loadEnvFile(envFile); err != nil {
		return nil, nil, fmt.Errorf("read %s: %w", envFile, err)
	}
	cfg, err := config.Load(cfgPath)
@@ -454,7 +454,7 @@ func offsiteFetchDB(fs *flag.FlagSet, args []string, stdout, stderr io.Writer) i
		fmt.Fprint(stderr, offsiteUsage)
		return 2
	}
	if err := loadOffsiteEnvFile(*envFile); err != nil {
	if err := loadEnvFile(*envFile); err != nil {
		fmt.Fprintf(stderr, "felis offsite fetch-db: read %s: %v\n", *envFile, err)
		return 1
	}
+2 −2
Changes for cmd/felis/offsite_test.go: 2 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -26,7 +26,7 @@ not a line
	t.Setenv("FELIS_OFFSITE_ACCESS_KEY", "from-the-shell")
	t.Setenv("FELIS_OFFSITE_SECRET_KEY", "")
	t.Setenv("FELIS_OFFSITE_KEY", "")
	if err := loadOffsiteEnvFile(path); err != nil {
	if err := loadEnvFile(path); err != nil {
		t.Fatal(err)
	}
	for k, want := range map[string]string{
@@ -38,7 +38,7 @@ not a line
			t.Errorf("%s = %q, want %q", k, got, want)
		}
	}
	if err := loadOffsiteEnvFile(filepath.Join(t.TempDir(), "absent")); err != nil {
	if err := loadEnvFile(filepath.Join(t.TempDir(), "absent")); err != nil {
		t.Errorf("a missing env file is not an error: %v", err)
	}
}
+2 −0
Changes for cmd/felis/run.go: 2 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ Commands:
  egress-gate       Hold a build pod until its egress NetworkPolicy is enforced (internal Job entrypoint)
  fetch-context     Fetch and extract a submission's build context (internal Job entrypoint)
  push-image        Push a scanned image tarball to the registry (internal Job entrypoint)
  mirror-build-tools Copy kaniko, trivy and Trivy's DBs into the registry (run by felis-build-tools.timer)
  registry-gate     Authorize registry writes in front of registry:2 (internal sidecar entrypoint)
  manifests         Render the control-plane RBAC + NetworkPolicy install bundle as YAML
  apply             Create a MinecraftServer CRD (direct K8s write; use -f server.json)
@@ -60,6 +61,7 @@ var commands = map[string]func(args []string, stdout, stderr io.Writer) int{
	"egress-gate":        cmdEgressGate,
	"fetch-context":      cmdFetchContext,
	"push-image":         cmdPushImage,
	"mirror-build-tools": cmdMirrorBuildTools,
	"registry-gate":      cmdRegistryGate,
	"manifests":          cmdManifests,
	"apply":              cmdApply,
Loading