mirror of
https://github.com/agent-substrate/substrate.git
synced 2026-11-11 10:23:17 +08:00
I've seen this test flake at least once due to (I think) the way we are truncating the timestamp for the NotBefore checks. Replace all the clock injection stuff with synctest. No failures after 100 runs using `stress`. ``` taahm@taahm1 /u/l/g/h/t/p/s/substrate (deflake-signer)> go test ./cmd/podcertcontroller/... ? github.com/agent-substrate/substrate/cmd/podcertcontroller [no test files] ok github.com/agent-substrate/substrate/cmd/podcertcontroller/internal/podcertificate 0.013s ok github.com/agent-substrate/substrate/cmd/podcertcontroller/internal/podidentitysigner 0.031s ? github.com/agent-substrate/substrate/cmd/podcertcontroller/internal/rendezvous [no test files] ? github.com/agent-substrate/substrate/cmd/podcertcontroller/internal/servicednssigner [no test files] ? github.com/agent-substrate/substrate/cmd/podcertcontroller/internal/signercontroller [no test files] ``` ``` taahm@taahm1 /u/l/g/h/t/p/s/substrate (deflake-signer) [1]> stress -count 100 go test ./cmd/podcertcontroller/... 5s: 24 runs so far, 0 failures, 8 active 10s: 48 runs so far, 0 failures, 8 active 15s: 63 runs so far, 0 failures, 8 active 20s: 78 runs so far, 0 failures, 8 active 25s: 89 runs so far, 0 failures, 8 active 27s: 100 runs total, 0 failures ```
178 lines
5.8 KiB
Go
178 lines
5.8 KiB
Go
// Copyright 2026 Google LLC
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// Command podcertcontroller is a pod certificate controller that implements two signers.
|
|
// - servicedns.ate.dev/identity: Issues certificate for Kubernetes service DNS names, backed by a
|
|
// local CA.
|
|
// - podid.ate.dev/identity: Issues certificates equivalent to KSA tokens, backed by a local CA.
|
|
//
|
|
// These signers are not unique to Agent Substrate, and will eventually be replaced by signers that
|
|
// are being developed as part of upstream Kubernetes.
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
"os"
|
|
"os/signal"
|
|
"path/filepath"
|
|
"syscall"
|
|
|
|
"github.com/agent-substrate/substrate/cmd/podcertcontroller/internal/podidentitysigner"
|
|
"github.com/agent-substrate/substrate/cmd/podcertcontroller/internal/rendezvous"
|
|
"github.com/agent-substrate/substrate/cmd/podcertcontroller/internal/servicednssigner"
|
|
"github.com/agent-substrate/substrate/cmd/podcertcontroller/internal/signercontroller"
|
|
"github.com/agent-substrate/substrate/internal/localca"
|
|
"github.com/agent-substrate/substrate/internal/version"
|
|
"github.com/spf13/pflag"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"k8s.io/client-go/kubernetes"
|
|
"k8s.io/client-go/rest"
|
|
"k8s.io/client-go/tools/clientcmd"
|
|
"k8s.io/client-go/util/homedir"
|
|
"k8s.io/utils/clock"
|
|
)
|
|
|
|
var kubeConfigDefault string
|
|
|
|
func init() {
|
|
if home := homedir.HomeDir(); home != "" {
|
|
kubeConfigDefault = filepath.Join(home, ".kube", "config")
|
|
}
|
|
}
|
|
|
|
var (
|
|
kubeconfig = pflag.String("kubeconfig", kubeConfigDefault, "absolute path to the kubeconfig file")
|
|
inCluster = pflag.Bool("in-cluster", false, "Is the controller running in the cluster it should connect to?")
|
|
|
|
shardingNamespace = pflag.String("sharding-pod-namespace", "", "(Work Sharding) The namespace the controller is running in")
|
|
shardingPodName = pflag.String("sharding-pod-name", "", "(Work Sharding) The pod name of the controller")
|
|
shardingPodUID = pflag.String("sharding-pod-uid", "", "(Work Sharding) The pod UID of the controller")
|
|
shardingApplicationName = pflag.String("sharding-application-name", "", "(Work Sharding) The application name to disambiguate Leases")
|
|
|
|
serviceDNSCAPoolFile = pflag.String(
|
|
"service-dns-ca-pool",
|
|
"",
|
|
"File that contains the CA pool state for "+servicednssigner.Name,
|
|
)
|
|
|
|
podCAPoolFile = pflag.String(
|
|
"pod-identity-ca-pool",
|
|
"",
|
|
"File that contains the CA pool state for "+podidentitysigner.Name,
|
|
)
|
|
|
|
workersPerSigner = pflag.Int(
|
|
"workers-per-signer",
|
|
1,
|
|
"Number of concurrent worker goroutines per signer.",
|
|
)
|
|
|
|
kubeAPIQPS = pflag.Float32(
|
|
"kube-api-qps",
|
|
0,
|
|
"Sustained queries per second allowed against the Kubernetes API. 0 keeps the client-go default.",
|
|
)
|
|
kubeAPIBurst = pflag.Int(
|
|
"kube-api-burst",
|
|
0,
|
|
"Burst queries allowed against the Kubernetes API. 0 keeps the client-go default.",
|
|
)
|
|
|
|
showVersion = pflag.Bool("version", false, "Print version and exit.")
|
|
)
|
|
|
|
func main() {
|
|
ctx := context.Background()
|
|
|
|
pflag.Parse()
|
|
if *showVersion {
|
|
fmt.Println(version.String())
|
|
return
|
|
}
|
|
slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, nil)))
|
|
slog.InfoContext(ctx, "podcertcontroller starting", slog.String("version", version.Version))
|
|
|
|
var kconfig *rest.Config
|
|
var err error
|
|
if *inCluster {
|
|
kconfig, err = rest.InClusterConfig()
|
|
if err != nil {
|
|
slog.ErrorContext(ctx, "Error creating in-cluster config", slog.Any("err", err))
|
|
os.Exit(1)
|
|
}
|
|
} else {
|
|
// use the current context in kubeconfig
|
|
kconfig, err = clientcmd.BuildConfigFromFlags("", *kubeconfig)
|
|
if err != nil {
|
|
slog.ErrorContext(ctx, "Error reading kubeconfig", slog.Any("err", err))
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
if *kubeAPIQPS < 0 || *kubeAPIBurst < 0 {
|
|
slog.ErrorContext(ctx, "Invalid rate limits: --kube-api-qps and --kube-api-burst must not be negative",
|
|
slog.Any("kube-api-qps", *kubeAPIQPS), slog.Int("kube-api-burst", *kubeAPIBurst))
|
|
os.Exit(1)
|
|
}
|
|
if *kubeAPIQPS > 0 {
|
|
kconfig.QPS = *kubeAPIQPS
|
|
}
|
|
if *kubeAPIBurst > 0 {
|
|
kconfig.Burst = *kubeAPIBurst
|
|
}
|
|
|
|
kc, err := kubernetes.NewForConfig(kconfig)
|
|
if err != nil {
|
|
slog.ErrorContext(ctx, "Error creating Kubernetes client", slog.Any("err", err))
|
|
os.Exit(1)
|
|
}
|
|
|
|
hasher := rendezvous.New(
|
|
kc,
|
|
*shardingNamespace,
|
|
*shardingApplicationName,
|
|
*shardingPodName,
|
|
types.UID(*shardingPodUID),
|
|
clock.RealClock{},
|
|
)
|
|
go hasher.Run(ctx)
|
|
|
|
// Create a signer for servicedns.ate.dev/identity
|
|
serviceDNSCAPool, err := localca.NewRefreshingPool(*serviceDNSCAPoolFile)
|
|
if err != nil {
|
|
slog.ErrorContext(ctx, "Error loading servicedns.ate.dev/identity CA pool state", slog.Any("err", err))
|
|
os.Exit(1)
|
|
}
|
|
serviceDNSSignerController := signercontroller.New(clock.RealClock{}, servicednssigner.NewImpl(kc, serviceDNSCAPool), kc, hasher)
|
|
go serviceDNSSignerController.Run(ctx, *workersPerSigner)
|
|
|
|
// Create a signer for podidentity.podcert.ate.dev/identity
|
|
podIdentityCAPool, err := localca.NewRefreshingPool(*podCAPoolFile)
|
|
if err != nil {
|
|
slog.ErrorContext(ctx, "Error loading podidentity.podcert.ate.dev/identity CA pool state", slog.Any("err", err))
|
|
os.Exit(1)
|
|
}
|
|
podIdentitySignerController := signercontroller.New(clock.RealClock{}, podidentitysigner.NewImpl(kc, podIdentityCAPool), kc, hasher)
|
|
go podIdentitySignerController.Run(ctx, *workersPerSigner)
|
|
|
|
// TODO: Reload when the file changes.
|
|
|
|
signalCh := make(chan os.Signal, 1)
|
|
signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM)
|
|
|
|
<-signalCh
|
|
}
|