diff --git a/config/manager/deployment.yaml b/config/manager/deployment.yaml index e354b00e3..24495671c 100644 --- a/config/manager/deployment.yaml +++ b/config/manager/deployment.yaml @@ -66,8 +66,8 @@ spec: path: /healthz readinessProbe: httpGet: - port: http - path: / + path: /readyz + port: healthz resources: limits: cpu: 1000m diff --git a/docs/README.md b/docs/README.md index eb02d5ee5..b86716adb 100644 --- a/docs/README.md +++ b/docs/README.md @@ -6,6 +6,8 @@ |---------------------------------------|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `--artifact-retention-records` | int | The maximum number of artifacts to be kept in storage after a garbage collection. (default 2) | | `--artifact-retention-ttl` | duration | The duration of time that artifacts from previous reconciliations will be kept in storage before being garbage collected. (default 1m0s) | +| `--authorized-artifact-client-spiffe-id` | strings | SPIFFE ID authorized to fetch artifacts from the artifact server. Can be repeated. When set, the artifact server uses SPIFFE mTLS. | +| `--authorized-event-server-spiffe-id` | strings | SPIFFE ID of an event server authorized to receive events from source-controller. Can be repeated. When set, the event recorder uses SPIFFE mTLS. | | `--concurrent` | int | The number of concurrent reconciles per controller. (default 2) | | `--default-service-account` | string | Default service account to use for workload identity when not specified in resources. | | `--enable-leader-election` | boolean | Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager. | @@ -39,6 +41,36 @@ | `--watch-label-selector` | string | Watch for resources with matching labels e.g. 'sharding.fluxcd.io/key=shard1'. | | `--feature-gates` | mapStringBool | A comma separated list of key=value pairs defining the state of experimental features. | +### SPIFFE mTLS + +When at least one of the repeatable flags `--authorized-artifact-client-spiffe-id` +or `--authorized-event-server-spiffe-id` is set, source-controller acquires an +X.509-SVID from the SPIFFE Workload API. The `SPIFFE_ENDPOINT_SOCKET` environment +variable must point to the Workload API socket. + +- `--authorized-artifact-client-spiffe-id` authorizes the given SPIFFE IDs to + fetch artifacts from the artifact server. When set, the artifact server uses + mTLS. +- `--authorized-event-server-spiffe-id` authorizes the given SPIFFE IDs of + event servers to which source-controller sends events. When set, the event + recorder uses mTLS. + +Each traffic type is protected independently. SPIFFE mTLS is transparent to the +Flux API: the scheme of `.status.artifact.url` does not change. + +Each flag value is interpreted as follows: + +- `spiffe:///` authorizes exactly that SPIFFE ID. +- `spiffe:////` (with a trailing slash) authorizes any + SPIFFE ID with that path prefix. +- `spiffe://` (without a path) authorizes any SPIFFE ID in that + trust domain. + +Whenever `` equals the special value `self`, source-controller +understands it as the trust domain of the controller's own X509-SVID. + +Invalid values cause source-controller to fail at startup. + ### Feature Gates | Name | Default Value | Description | diff --git a/go.mod b/go.mod index 33914af27..6bb8f77d6 100644 --- a/go.mod +++ b/go.mod @@ -23,20 +23,21 @@ require ( github.com/docker/go-units v0.5.0 github.com/elazarl/goproxy v1.8.4 github.com/fluxcd/cli-utils v1.4.0 - github.com/fluxcd/pkg/apis/crypto v0.4.1-0.20261020182949-c5194b17fdad + github.com/fluxcd/pkg/apis/crypto v0.4.1-0.20261022125904-05e84321fa52 github.com/fluxcd/pkg/apis/event v0.32.0 - github.com/fluxcd/pkg/apis/meta v1.36.1-0.20261020182949-c5194b17fdad - github.com/fluxcd/pkg/artifact v0.25.1-0.20261020182949-c5194b17fdad - github.com/fluxcd/pkg/auth v0.63.1-0.20261020182949-c5194b17fdad - github.com/fluxcd/pkg/cache v0.17.1-0.20261020182949-c5194b17fdad + github.com/fluxcd/pkg/apis/meta v1.36.1-0.20261022125904-05e84321fa52 + github.com/fluxcd/pkg/artifact v0.25.1-0.20261022125904-05e84321fa52 + github.com/fluxcd/pkg/auth v0.63.1-0.20261022125904-05e84321fa52 + github.com/fluxcd/pkg/cache v0.17.1-0.20261022125904-05e84321fa52 github.com/fluxcd/pkg/git v0.55.0 github.com/fluxcd/pkg/gittestserver v0.32.0 github.com/fluxcd/pkg/helmtestserver v0.44.0 github.com/fluxcd/pkg/http/transport v0.9.0 github.com/fluxcd/pkg/masktoken v0.10.0 github.com/fluxcd/pkg/oci v0.72.0 - github.com/fluxcd/pkg/runtime v0.120.1-0.20261020182949-c5194b17fdad + github.com/fluxcd/pkg/runtime v0.120.1-0.20261022125904-05e84321fa52 github.com/fluxcd/pkg/sourceignore v0.21.0 + github.com/fluxcd/pkg/spiffe v0.0.0-20261022125904-05e84321fa52 github.com/fluxcd/pkg/ssh v0.28.0 github.com/fluxcd/pkg/tar v1.3.0 github.com/fluxcd/pkg/testserver v0.15.0 @@ -64,6 +65,7 @@ require ( github.com/sigstore/sigstore-go v1.2.1 github.com/sirupsen/logrus v1.10.2 github.com/spf13/pflag v1.0.10 + github.com/spiffe/go-spiffe/v2 v2.9.0 golang.org/x/crypto v0.58.0 golang.org/x/sync v0.24.0 google.golang.org/api v0.297.0 @@ -327,7 +329,6 @@ require ( github.com/spf13/cast v1.10.0 // indirect github.com/spf13/cobra v1.10.2 // indirect github.com/spf13/viper v1.21.0 // indirect - github.com/spiffe/go-spiffe/v2 v2.9.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 // indirect diff --git a/go.sum b/go.sum index de030fc81..dfbdc4469 100644 --- a/go.sum +++ b/go.sum @@ -350,18 +350,18 @@ github.com/fluxcd/gitkit v0.6.0 h1:iNg5LTx6ePo+Pl0ZwqHTAkhbUHxGVSY3YCxCdw7VIFg= github.com/fluxcd/gitkit v0.6.0/go.mod h1:svOHuKi0fO9HoawdK4HfHAJJseZDHHjk7I3ihnCIqNo= github.com/fluxcd/pkg/apis/acl v0.12.0 h1:UWpZDqKxpKDW38wdXIMHV8Uv3POPu2vUrra0JH1frH0= github.com/fluxcd/pkg/apis/acl v0.12.0/go.mod h1:PJrGIu5Bw/6/siO95uhS+pi5oKSU+gVJqHqsPHb8Kok= -github.com/fluxcd/pkg/apis/crypto v0.4.1-0.20261020182949-c5194b17fdad h1:SAzeZibDaqyyq7CaccwDEySCcsOMGGuHmEN28LcRPvM= -github.com/fluxcd/pkg/apis/crypto v0.4.1-0.20261020182949-c5194b17fdad/go.mod h1:bEwkDgBjLXMVJ2BwmVbbj32PkirmNSicabgl2Jvw7Dk= +github.com/fluxcd/pkg/apis/crypto v0.4.1-0.20261022125904-05e84321fa52 h1:YkDjmLFe0+RhzlFTwHgoO6xLpPYuhoiqQlMo+Qj2tW0= +github.com/fluxcd/pkg/apis/crypto v0.4.1-0.20261022125904-05e84321fa52/go.mod h1:bEwkDgBjLXMVJ2BwmVbbj32PkirmNSicabgl2Jvw7Dk= github.com/fluxcd/pkg/apis/event v0.32.0 h1:3zyjdUspwQN+EQJPlUXwCvCMbeqtB+azlNmeenAcbuE= github.com/fluxcd/pkg/apis/event v0.32.0/go.mod h1:ALma8dTqo+NbI+z4BawSzt0pAPjetCSNNUSSS/9p0VU= -github.com/fluxcd/pkg/apis/meta v1.36.1-0.20261020182949-c5194b17fdad h1:p/W9d+gf9FuNNmiwUZJT7ReGzTzwuYLUago5szqkq5I= -github.com/fluxcd/pkg/apis/meta v1.36.1-0.20261020182949-c5194b17fdad/go.mod h1:1MLvK2Xu3UdTKBzg+Q1Oj7H9PHeiKsUB1UW3uwxAwiM= -github.com/fluxcd/pkg/artifact v0.25.1-0.20261020182949-c5194b17fdad h1:r1anWXtWivFjkI6F0FDSHPnLw2dSMXYQpVpu+Esq+nc= -github.com/fluxcd/pkg/artifact v0.25.1-0.20261020182949-c5194b17fdad/go.mod h1:9wKz9vfH7zuhteNxQkGw9l7sCN1xW8YZttEWNN8M/L0= -github.com/fluxcd/pkg/auth v0.63.1-0.20261020182949-c5194b17fdad h1:lUjY+hEXSYG8RdJhuNYl5mjkrnLpwuG/+A+iSybCRlE= -github.com/fluxcd/pkg/auth v0.63.1-0.20261020182949-c5194b17fdad/go.mod h1:gWbB4MzuHWalVyI0o4lE84aINidor9+S63ithckm+94= -github.com/fluxcd/pkg/cache v0.17.1-0.20261020182949-c5194b17fdad h1:Lf7S9vl776ZW7SDxdQtZcEu1vjWroZS86ePd8gost4M= -github.com/fluxcd/pkg/cache v0.17.1-0.20261020182949-c5194b17fdad/go.mod h1:OW3fncHhPnPZFZav5FdEa//cyMHSndI0JS+IM69LXOU= +github.com/fluxcd/pkg/apis/meta v1.36.1-0.20261022125904-05e84321fa52 h1:vKtcpfELWz1l2eOQjWeEbCJZMNH29W4Fwa3EPM37zW0= +github.com/fluxcd/pkg/apis/meta v1.36.1-0.20261022125904-05e84321fa52/go.mod h1:1MLvK2Xu3UdTKBzg+Q1Oj7H9PHeiKsUB1UW3uwxAwiM= +github.com/fluxcd/pkg/artifact v0.25.1-0.20261022125904-05e84321fa52 h1:xUbaB4s9PtJ771fZirtqH8GDg3tnZ8qtTYQup322K4k= +github.com/fluxcd/pkg/artifact v0.25.1-0.20261022125904-05e84321fa52/go.mod h1:9wKz9vfH7zuhteNxQkGw9l7sCN1xW8YZttEWNN8M/L0= +github.com/fluxcd/pkg/auth v0.63.1-0.20261022125904-05e84321fa52 h1:QoZGEf9JonMNcEAacH8EGtSq55dplp18TTSH//M2r7o= +github.com/fluxcd/pkg/auth v0.63.1-0.20261022125904-05e84321fa52/go.mod h1:gWbB4MzuHWalVyI0o4lE84aINidor9+S63ithckm+94= +github.com/fluxcd/pkg/cache v0.17.1-0.20261022125904-05e84321fa52 h1:9ZSGSkmi4OxVCSj9Uzrt6E1Ph0ypPUajsc76pYqUg2s= +github.com/fluxcd/pkg/cache v0.17.1-0.20261022125904-05e84321fa52/go.mod h1:OW3fncHhPnPZFZav5FdEa//cyMHSndI0JS+IM69LXOU= github.com/fluxcd/pkg/git v0.55.0 h1:jMdW0uAYuzcwIL5rJq5pmDd+jSrgOe6eIOZfdrcAvO8= github.com/fluxcd/pkg/git v0.55.0/go.mod h1:s9AK9ZEG9CtAgkcX4QhQphLsaDBTje32tT5V3knpj1I= github.com/fluxcd/pkg/gittestserver v0.32.0 h1:y1wbnEaOXpEYqAxGMxcsXZTH6W4QjFyTj6m1d1ytjY0= @@ -376,10 +376,12 @@ github.com/fluxcd/pkg/masktoken v0.10.0 h1:jY9tuBIS1T7z9F8nJc9ozT0QrI391Se9vMu0e github.com/fluxcd/pkg/masktoken v0.10.0/go.mod h1:zOCZp3trYmD5/csLOizvIGTi4gn76nnkTFu+vKOJufo= github.com/fluxcd/pkg/oci v0.72.0 h1:nUTGHTd/2qq2yT66anHfXiwE19uJl6VL36zm74iFwbU= github.com/fluxcd/pkg/oci v0.72.0/go.mod h1:5ngbB6SDvo6daD29HZtBbC6CcnHxuI+5JJR/YqtZhKM= -github.com/fluxcd/pkg/runtime v0.120.1-0.20261020182949-c5194b17fdad h1:AcQzxznBRZyRthcVgFjPV49uLfu7eDOoHO1B5WyRsO8= -github.com/fluxcd/pkg/runtime v0.120.1-0.20261020182949-c5194b17fdad/go.mod h1:Cw0CQ/tVKS9lgJoyKVouZr1qVnoJcYnSHw3QDzIOOjA= +github.com/fluxcd/pkg/runtime v0.120.1-0.20261022125904-05e84321fa52 h1:UD7MGD096C02MqQFDujvSGktyuDhYUnvQhjBePw3bLk= +github.com/fluxcd/pkg/runtime v0.120.1-0.20261022125904-05e84321fa52/go.mod h1:Cw0CQ/tVKS9lgJoyKVouZr1qVnoJcYnSHw3QDzIOOjA= github.com/fluxcd/pkg/sourceignore v0.21.0 h1:p65j81RzXAeL4KJKgbkFRou+xiA0841CUdAsOCSAb5o= github.com/fluxcd/pkg/sourceignore v0.21.0/go.mod h1:N9NFuxbEMm+UPtP9t+Oyx5LEs0uHu7W+WVPmdlIbCeY= +github.com/fluxcd/pkg/spiffe v0.0.0-20261022125904-05e84321fa52 h1:nl8eRotsfHEZS7m7SwZbZidq8F/MGj3yxyH5Vh6bas4= +github.com/fluxcd/pkg/spiffe v0.0.0-20261022125904-05e84321fa52/go.mod h1:tZ+T8ljAM9ZE0VriHSWM4vfvhlq5zv5bNcPKqwZK1lk= github.com/fluxcd/pkg/ssh v0.28.0 h1:DbqDtvJGKzrSfXfB8Hi9T2mbTnNBodijpRZRfxx59wE= github.com/fluxcd/pkg/ssh v0.28.0/go.mod h1:FyaNc49GQcFIHdyZzwmG/9UApGaD5nKXT6CtzWAsaE0= github.com/fluxcd/pkg/tar v1.3.0 h1:PbL5jNN5q/F6nXA8aCH+msM75BS6lSnPX2RNd48f8ls= diff --git a/main.go b/main.go index 8f5e76c46..df069b33c 100644 --- a/main.go +++ b/main.go @@ -17,12 +17,14 @@ limitations under the License. package main import ( + "crypto/tls" "errors" "fmt" "os" "time" flag "github.com/spf13/pflag" + "github.com/spiffe/go-spiffe/v2/workloadapi" "helm.sh/helm/v4/pkg/getter" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" @@ -54,6 +56,7 @@ import ( "github.com/fluxcd/pkg/runtime/metrics" "github.com/fluxcd/pkg/runtime/pprof" "github.com/fluxcd/pkg/runtime/probes" + fluxspiffe "github.com/fluxcd/pkg/spiffe" sourcev1 "github.com/fluxcd/source-controller/api/v1" @@ -96,27 +99,29 @@ func main() { ) var ( - metricsAddr string - eventsAddr string - healthAddr string - concurrent int - requeueDependency time.Duration - helmIndexLimit int64 - helmChartLimit int64 - helmChartFileLimit int64 - artifactOptions artcfg.Options - clientOptions client.Options - logOptions logger.Options - leaderElectionOptions leaderelection.Options - rateLimiterOptions helper.RateLimiterOptions - featureGates feathelper.FeatureGates - watchOptions helper.WatchOptions - intervalJitterOptions jitter.IntervalOptions - helmCacheMaxSize int - helmCacheTTL string - helmCachePurgeInterval string - tokenCacheOptions pkgcache.TokenFlags - defaultServiceAccount string + metricsAddr string + eventsAddr string + healthAddr string + concurrent int + requeueDependency time.Duration + helmIndexLimit int64 + helmChartLimit int64 + helmChartFileLimit int64 + artifactOptions artcfg.Options + clientOptions client.Options + logOptions logger.Options + leaderElectionOptions leaderelection.Options + rateLimiterOptions helper.RateLimiterOptions + featureGates feathelper.FeatureGates + watchOptions helper.WatchOptions + intervalJitterOptions jitter.IntervalOptions + helmCacheMaxSize int + helmCacheTTL string + helmCachePurgeInterval string + tokenCacheOptions pkgcache.TokenFlags + defaultServiceAccount string + authorizedArtifactClients []string + authorizedEventServers []string ) flag.StringVar(&metricsAddr, "metrics-addr", envOrDefault("METRICS_ADDR", ":8080"), @@ -145,6 +150,10 @@ func main() { "The list of hostkey algorithms to use for ssh connections, arranged from most preferred to the least.") flag.StringVar(&defaultServiceAccount, auth.ControllerFlagDefaultServiceAccount, "", "Default service account to use for workload identity when not specified in resources.") + flag.StringArrayVar(&authorizedArtifactClients, fluxspiffe.ControllerFlagAuthorizedArtifactClientSPIFFEID, nil, + "SPIFFE ID authorized to fetch artifacts from the artifact server. Can be repeated.") + flag.StringArrayVar(&authorizedEventServers, fluxspiffe.ControllerFlagAuthorizedEventServerSPIFFEID, nil, + "SPIFFE ID of an event server authorized to receive events. Can be repeated.") artifactOptions.BindFlags(flag.CommandLine) clientOptions.BindFlags(flag.CommandLine) @@ -160,6 +169,8 @@ func main() { logger.SetLogger(logger.NewLogger(logOptions)) + ctx := ctrl.SetupSignalHandler() + if err := featureGates.WithLogger(setupLog).SupportedFeatures(features.FeatureGates()); err != nil { setupLog.Error(err, "unable to load feature gates") os.Exit(1) @@ -186,11 +197,52 @@ func main() { mgr := mustSetupManager(metricsAddr, healthAddr, concurrent, watchOptions, clientOptions, leaderElectionOptions) probes.SetupChecks(mgr, setupLog) + if err := mgr.AddReadyzCheck("leader-election", probes.LeaderElectedReadyz(mgr.Elected())); err != nil { + setupLog.Error(err, "unable to create leader election readiness check") + os.Exit(1) + } metrics := helper.NewMetrics(mgr, metrics.MustMakeRecorder(), sourcev1.SourceFinalizer) cacheRecorder := cache.MustMakeMetrics() - eventRecorder, err := events.NewRecorder(ctrl.Log, eventsAddr, controllerName, events.WithManager(mgr)) + var eventTLSConfig *tls.Config + if len(authorizedArtifactClients) > 0 || len(authorizedEventServers) > 0 { + source, err := workloadapi.NewX509Source(ctx) + if err != nil { + setupLog.Error(err, "unable to create SPIFFE X.509 source") + os.Exit(1) + } + defer func() { + if err := source.Close(); err != nil { + setupLog.Error(err, "unable to close SPIFFE X.509 source") + } + }() + + if len(authorizedArtifactClients) > 0 { + artifactTLSConfig, err := fluxspiffe.BuildTLSConfig(source, authorizedArtifactClients...) + if err != nil { + setupLog.Error(err, "unable to configure SPIFFE mTLS for the artifact server") + os.Exit(1) + } + setupLog.Info("artifact server will use inter-controller mTLS via SPIFFE", "spiffeIDs", authorizedArtifactClients) + artifactOptions.TLSConfig = artifactTLSConfig + } + + if len(authorizedEventServers) > 0 { + eventTLSConfig, err = fluxspiffe.BuildTLSConfig(source, authorizedEventServers...) + if err != nil { + setupLog.Error(err, "unable to configure SPIFFE mTLS for the event server") + os.Exit(1) + } + setupLog.Info("event server will use inter-controller mTLS via SPIFFE", "spiffeIDs", authorizedEventServers) + } + } + + eventRecorderOpts := []events.RecorderOption{events.WithManager(mgr)} + if eventTLSConfig != nil { + eventRecorderOpts = append(eventRecorderOpts, events.WithTLSConfig(eventTLSConfig)) + } + eventRecorder, err := events.NewRecorder(ctrl.Log, eventsAddr, controllerName, eventRecorderOpts...) if err != nil { setupLog.Error(err, "unable to create event recorder") os.Exit(1) @@ -225,8 +277,6 @@ func main() { } } - ctx := ctrl.SetupSignalHandler() - if err := (&controller.GitRepositoryReconciler{ Client: mgr.GetClient(), Recorder: eventRecorder,