diff --git a/internal/concierge/server/server.go b/internal/concierge/server/server.go index b3499b066..1e18a028a 100644 --- a/internal/concierge/server/server.go +++ b/internal/concierge/server/server.go @@ -17,6 +17,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/serializer" apimachineryversion "k8s.io/apimachinery/pkg/version" openapinamer "k8s.io/apiserver/pkg/endpoints/openapi" + "k8s.io/apiserver/pkg/features" genericapiserver "k8s.io/apiserver/pkg/server" genericoptions "k8s.io/apiserver/pkg/server/options" "k8s.io/client-go/rest" @@ -27,6 +28,7 @@ import ( "go.pinniped.dev/internal/concierge/apiserver" conciergescheme "go.pinniped.dev/internal/concierge/scheme" "go.pinniped.dev/internal/config/concierge" + "go.pinniped.dev/internal/config/featuregates" "go.pinniped.dev/internal/controller/authenticator/authncache" "go.pinniped.dev/internal/controllerinit" "go.pinniped.dev/internal/controllermanager" @@ -103,6 +105,9 @@ func addCommandlineFlagsToCommand(cmd *cobra.Command, app *App) { // Boot the aggregated API server, which will in turn boot the controllers. // In practice, the ctx passed in should be one which will be cancelled when the process receives SIGTERM or SIGINT. func (a *App) runServer(ctx context.Context) error { + // Enable the feature gate from https://github.com/kubernetes/kubernetes/pull/121120. + featuregates.EnableKubeFeatureGate(features.UnauthenticatedHTTP2DOSMitigation) + // Read the server config file. cfg, err := concierge.FromPath(ctx, a.configPath) if err != nil { diff --git a/internal/config/featuregates/featuregates.go b/internal/config/featuregates/featuregates.go new file mode 100644 index 000000000..5fb413b52 --- /dev/null +++ b/internal/config/featuregates/featuregates.go @@ -0,0 +1,25 @@ +// Copyright 2023 the Pinniped contributors. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package featuregates + +import ( + "k8s.io/apiserver/pkg/util/feature" + "k8s.io/component-base/featuregate" + + "go.pinniped.dev/internal/plog" +) + +func EnableKubeFeatureGate(f featuregate.Feature) { + initialValue := feature.DefaultFeatureGate.Enabled(f) + + if err := feature.DefaultMutableFeatureGate.SetFromMap(map[string]bool{string(f): true}); err != nil { + panic(err) // this should never happen as long as a feature gate still exists + } + + plog.Always("feature gate status", + "name", f, + "initialEnabledValue", initialValue, + "updatedEnabledValue", feature.DefaultFeatureGate.Enabled(f), + ) +} diff --git a/internal/config/featuregates/featuregates_test.go b/internal/config/featuregates/featuregates_test.go new file mode 100644 index 000000000..3def60120 --- /dev/null +++ b/internal/config/featuregates/featuregates_test.go @@ -0,0 +1,28 @@ +// Copyright 2023 the Pinniped contributors. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package featuregates + +import ( + "testing" + + "github.com/stretchr/testify/require" + "k8s.io/apiserver/pkg/features" + "k8s.io/apiserver/pkg/util/feature" + featuregatetesting "k8s.io/component-base/featuregate/testing" +) + +func TestEnableKubeFeatureGate(t *testing.T) { + f := features.UnauthenticatedHTTP2DOSMitigation + + // This feature gate is currently disabled by default in the Kubernetes library. + // Assert this as a precondition so if that ever changes during a dependency bump + // we will be forced to take note and decide if any code deserves to change. + require.False(t, feature.DefaultFeatureGate.Enabled(f)) + + defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, f, false)() + + require.False(t, feature.DefaultFeatureGate.Enabled(f)) + EnableKubeFeatureGate(f) + require.True(t, feature.DefaultFeatureGate.Enabled(f)) +} diff --git a/internal/supervisor/server/server.go b/internal/supervisor/server/server.go index 0ddf8daa3..3d604cc04 100644 --- a/internal/supervisor/server/server.go +++ b/internal/supervisor/server/server.go @@ -28,6 +28,7 @@ import ( apimachineryversion "k8s.io/apimachinery/pkg/version" genericapifilters "k8s.io/apiserver/pkg/endpoints/filters" openapinamer "k8s.io/apiserver/pkg/endpoints/openapi" + "k8s.io/apiserver/pkg/features" genericapiserver "k8s.io/apiserver/pkg/server" genericoptions "k8s.io/apiserver/pkg/server/options" k8sinformers "k8s.io/client-go/informers" @@ -43,6 +44,7 @@ import ( supervisorinformers "go.pinniped.dev/generated/latest/client/supervisor/informers/externalversions" supervisoropenapi "go.pinniped.dev/generated/latest/client/supervisor/openapi" "go.pinniped.dev/internal/apiserviceref" + "go.pinniped.dev/internal/config/featuregates" "go.pinniped.dev/internal/config/supervisor" "go.pinniped.dev/internal/controller/apicerts" "go.pinniped.dev/internal/controller/supervisorconfig" @@ -386,6 +388,9 @@ func prepareControllers( // and start serving the health endpoint and the endpoints of the configured FederationDomains. // In practice, the ctx passed in should be one which will be cancelled when the process receives SIGTERM or SIGINT. func runSupervisor(ctx context.Context, podInfo *downward.PodInfo, cfg *supervisor.Config) error { //nolint:funlen + // Enable the feature gate from https://github.com/kubernetes/kubernetes/pull/121120. + featuregates.EnableKubeFeatureGate(features.UnauthenticatedHTTP2DOSMitigation) + serverInstallationNamespace := podInfo.Namespace clientSecretSupervisorGroupData := groupsuffix.SupervisorAggregatedGroups(*cfg.APIGroupSuffix) @@ -526,6 +531,10 @@ func runSupervisor(ctx context.Context, podInfo *downward.PodInfo, cfg *supervis } c := ptls.Default(nil) + // Remove "h2" from the list for now, until we have a better idea of how to mitigate + // potential http2 rapid reset vulnerabilities. This disables serving requests using http2. + c.NextProtos = []string{"http/1.1"} + c.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) { cert := dynamicTLSCertProvider.GetTLSCert(strings.ToLower(info.ServerName)) foundServerNameCert := cert != nil