Files
substrate/cmd/podcertcontroller/main.go
T
Taahir Ahmed 2d64d10d74 Deflake pod identity signer test using synctest (#1610)
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
```
2026-09-11 10:35:57 -07:00

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
}