Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
bundle/** linguist-generated=true
4 changes: 2 additions & 2 deletions bundle.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ LABEL operators.operatorframework.io.bundle.metadata.v1=metadata/
LABEL operators.operatorframework.io.bundle.package.v1=file-integrity-operator
LABEL operators.operatorframework.io.bundle.channels.v1=alpha
LABEL operators.operatorframework.io.bundle.channel.default.v1=alpha
LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.42.2
LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.27.0
LABEL operators.operatorframework.io.metrics.mediatype.v1=metrics+v1
LABEL operators.operatorframework.io.metrics.project_layout=go.kubebuilder.io/v4
LABEL operators.operatorframework.io.metrics.project_layout=go.kubebuilder.io/v3

# Labels for testing.
LABEL operators.operatorframework.io.test.mediatype.v1=scorecard+v1
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions bundle/metadata/annotations.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions cmd/manager/manager_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ package manager
import (
"testing"

"github.com/go-logr/logr/testr"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
ctrl "sigs.k8s.io/controller-runtime"
)

func TestManager(t *testing.T) {
ctrl.SetLogger(testr.New(t))
RegisterFailHandler(Fail)
RunSpecs(t, "Manager Suite")
}
35 changes: 23 additions & 12 deletions cmd/manager/operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ package manager

import (
"context"
"crypto/tls"
"errors"
"flag"
"fmt"
Expand All @@ -28,6 +27,7 @@ import (
"github.com/cenkalti/backoff/v4"
"github.com/spf13/cobra"

configv1 "github.com/openshift/api/config/v1"
v1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
kerr "k8s.io/apimachinery/pkg/api/errors"
Expand All @@ -47,7 +47,6 @@ import (
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
"sigs.k8s.io/controller-runtime/pkg/webhook"

monitoring "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
monclientv1 "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned/typed/monitoring/v1"
Expand Down Expand Up @@ -83,6 +82,7 @@ func init() {
utilruntime.Must(clientgoscheme.AddToScheme(scheme))

utilruntime.Must(v1alpha1.AddToScheme(scheme))
utilruntime.Must(configv1.Install(scheme))
//+kubebuilder:scaffold:scheme
}

Expand All @@ -93,7 +93,6 @@ var (
defaultPrometheusAlertName = "file-integrity"
metricsServiceName = "metrics"
leaderElectionID = "962a0cf2.openshift.io"
enableHTTP2 = false
)

func printVersion() {
Expand Down Expand Up @@ -127,16 +126,12 @@ func RunOperator(cmd *cobra.Command, args []string) {
kubeClient := kubernetes.NewForConfigOrDie(cfg)
monitoringClient := monclientv1.NewForConfigOrDie(cfg)

ctx := context.TODO()
ctx, cancel := context.WithCancel(context.TODO())
defer cancel()

log.Info("Registering Components.")

disableHTTP2 := func(c *tls.Config) {
if enableHTTP2 {
return
}
c.NextProtos = []string{"http/1.1"}
}
tlsSettings := makeClusterTLSSettings(ctx, cfg)
c := cache.Options{DefaultNamespaces: map[string]cache.Config{namespace: {}}}
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Cache: c,
Expand All @@ -152,7 +147,7 @@ func RunOperator(cmd *cobra.Command, args []string) {
Scheme: scheme,
Metrics: metricsserver.Options{BindAddress: fmt.Sprintf("%s:%d", metricsHost, metricsPort)},
HealthProbeBindAddress: ":8081",
WebhookServer: webhook.NewServer(webhook.Options{Port: 9443, TLSOpts: []func(config *tls.Config){disableHTTP2}}),
WebhookServer: makeWebhookServer(tlsSettings),
LeaderElection: true,
LeaderElectionID: leaderElectionID,
})
Expand All @@ -166,6 +161,13 @@ func RunOperator(cmd *cobra.Command, args []string) {
log.Error(err, "Error registering metrics")
os.Exit(1)
}
configureMetricsTLSProfile(met, tlsSettings)

watcher := makeSecurityProfileWatcher(mgr.GetClient(), tlsSettings, cancel)
if err := watcher.SetupWithManager(mgr); err != nil {
log.Error(err, "Unable to set up TLS security profile watcher")
os.Exit(1)
}

//+kubebuilder:scaffold:builder

Expand Down Expand Up @@ -223,7 +225,16 @@ func RunOperator(cmd *cobra.Command, args []string) {
}

log.Info("Starting manager")
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {

// Start the Cmd. The context is cancelled either by OS signals or by
// the SecurityProfileWatcher when TLS configuration changes, causing
// a graceful shutdown so the pod restarts with updated TLS settings.
sigCtx := ctrl.SetupSignalHandler()
go func() {
<-sigCtx.Done()
cancel()
}()
if err := mgr.Start(ctx); err != nil {
log.Error(err, "Manager exited non-zero")
os.Exit(1)
}
Expand Down
142 changes: 142 additions & 0 deletions cmd/manager/tls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package manager

import (
"context"
"crypto/tls"
"os"
"time"

configv1 "github.com/openshift/api/config/v1"
tlspkg "github.com/openshift/controller-runtime-common/pkg/tls"
"github.com/openshift/file-integrity-operator/pkg/controller/metrics"
libgocrypto "github.com/openshift/library-go/pkg/crypto"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook"
)

// tlsLookupTimeout bounds the cluster TLS profile/adherence lookups so an
// unresponsive API server cannot block operator startup or the result server
// indefinitely; on timeout the caller falls back to secure defaults.
const tlsLookupTimeout = 30 * time.Second

// clusterTLSSettings captures the cluster TLS profile and adherence policy
// used to configure the manager's TLS endpoints and watch for future changes.
type clusterTLSSettings struct {
profile configv1.TLSProfileSpec
adherence configv1.TLSAdherencePolicy
}

// fetchClusterTLSSettings fetches the cluster's TLS security profile and adherence
// policy from the APIServer resource using c, bounding both lookups with
// tlsLookupTimeout (a child of ctx). On any lookup error it falls back to
// secure defaults (default ciphers/min version and a "no opinion" adherence
// policy) so callers always receive a usable pair.
func fetchClusterTLSSettings(ctx context.Context, c client.Client) (configv1.TLSProfileSpec, configv1.TLSAdherencePolicy) {
lookupCtx, cancel := context.WithTimeout(ctx, tlsLookupTimeout)
defer cancel()

profile, err := tlspkg.FetchAPIServerTLSProfile(lookupCtx, c)
if err != nil {
log.Info("Could not fetch APIServer TLS profile, using defaults", "error", err)
profile = configv1.TLSProfileSpec{
Ciphers: tlspkg.DefaultTLSCiphers,
MinTLSVersion: tlspkg.DefaultMinTLSVersion,
}
}

adherence, err := tlspkg.FetchAPIServerTLSAdherencePolicy(lookupCtx, c)
if err != nil {
log.Info("Could not fetch APIServer TLS adherence policy, using defaults", "error", err)
adherence = configv1.TLSAdherencePolicyNoOpinion
}

return profile, adherence
}

// applyClusterTLSSettings conditionally applies the cluster TLS security
// settings to c. It is a no-op when the adherence policy does not require
// strict adherence. Returns any cipher suites unsupported by Go.
func applyClusterTLSSettings(c *tls.Config, s *clusterTLSSettings) []string {
if !libgocrypto.ShouldHonorClusterTLSProfile(s.adherence) {
return nil
}
fn, unsupported := tlspkg.NewTLSConfigFromProfile(s.profile)
fn(c)
return unsupported
}

// makeClusterTLSSettings returns the cluster TLS security profile and adherence
// policy. These are used to configure all TLS endpoints at startup and to
// detect changes later via the SecurityProfileWatcher. The lookups are bounded
// by a timeout and fall back to secure defaults on error.
func makeClusterTLSSettings(ctx context.Context, cfg *rest.Config) *clusterTLSSettings {
// Build a client for fetching the cluster TLS profile and adherence
// policy before the manager is started.
preStartClient, err := client.New(cfg, client.Options{Scheme: scheme})
if err != nil {
log.Error(err, "Failed to create pre-start client for TLS profile lookup")
os.Exit(1)
}

profile, adherence := fetchClusterTLSSettings(ctx, preStartClient)
return &clusterTLSSettings{profile, adherence}
}

// makeWebhookServer returns a webhook.Server configured with the cluster TLS
// security profile and adherence policy. It logs any cipher suites unsupported
// by Go.
func makeWebhookServer(state *clusterTLSSettings) webhook.Server {
opts := []func(config *tls.Config){
func(c *tls.Config) {
c.NextProtos = []string{"http/1.1"}
},
func(c *tls.Config) {
if unsupported := applyClusterTLSSettings(c, state); len(unsupported) > 0 {
log.Info("TLS profile contains ciphers unsupported by Go", "unsupported", unsupported)
}
},
}
return webhook.NewServer(webhook.Options{Port: 9443, TLSOpts: opts})
}

// configureMetricsTLSProfile configures the metrics server with the cluster TLS
// security profile and adherence policy. It is a no-op when the adherence policy
// does not require strict adherence.
func configureMetricsTLSProfile(met *metrics.Metrics, s *clusterTLSSettings) {
// If the tlsAdherence policy requires strict adherence, configure the
// metrics server to use the cluster's TLS security profile.
if libgocrypto.ShouldHonorClusterTLSProfile(s.adherence) {
met.SetTLSProfileSpec(s.profile)
}
}

// makeSecurityProfileWatcher returns a SecurityProfileWatcher configured with
// the cluster TLS security profile and adherence policy. It sets up callbacks
// to cancel the manager context on any changes, so the pod restarts with the
// new TLS configuration applied.
func makeSecurityProfileWatcher(client client.Client, s *clusterTLSSettings, cancel func()) *tlspkg.SecurityProfileWatcher {
// Set up the SecurityProfileWatcher to detect APIServer TLS profile
// and adherence policy changes. On change, cancel the context so the
// manager shuts down gracefully and the pod restarts with the new
// TLS configuration applied.
return &tlspkg.SecurityProfileWatcher{
Client: client,
InitialTLSProfileSpec: s.profile,
InitialTLSAdherencePolicy: s.adherence,
OnProfileChange: func(_ context.Context, oldProfile, newProfile configv1.TLSProfileSpec) {
log.Info("Cluster TLS profile changed, initiating graceful shutdown to reload",
"oldMinTLSVersion", oldProfile.MinTLSVersion,
"newMinTLSVersion", newProfile.MinTLSVersion,
)
cancel()
},
OnAdherencePolicyChange: func(_ context.Context, oldPolicy, newPolicy configv1.TLSAdherencePolicy) {
log.Info("Cluster TLS adherence policy changed, initiating graceful shutdown to reload",
"oldPolicy", oldPolicy,
"newPolicy", newPolicy,
)
cancel()
},
}
}
Loading