diff --git a/cmd/postgres-operator/main.go b/cmd/postgres-operator/main.go index f61123c4b1..a2058f8a52 100644 --- a/cmd/postgres-operator/main.go +++ b/cmd/postgres-operator/main.go @@ -6,6 +6,7 @@ package main import ( "context" + "crypto/tls" "os" goruntime "runtime" "strconv" @@ -157,13 +158,15 @@ func main() { func addControllersToManager(ctx context.Context, mgr manager.Manager) error { os.Setenv("REGISTRATION_REQUIRED", "false") + openShift := isOpenshift(ctx, mgr.GetConfig()) + r := &postgrescluster.Reconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), Owner: postgrescluster.ControllerName, Recorder: mgr.GetEventRecorderFor(postgrescluster.ControllerName), Tracer: otel.Tracer(postgrescluster.ControllerName), - IsOpenShift: isOpenshift(ctx, mgr.GetConfig()), + IsOpenShift: openShift, CertManagerCtrlFunc: certmanager.NewController, RestConfig: mgr.GetConfig(), } @@ -193,10 +196,10 @@ func addControllersToManager(ctx context.Context, mgr manager.Manager) error { Owner: pgcluster.PGClusterControllerName, Recorder: mgr.GetEventRecorderFor(pgcluster.PGClusterControllerName), Tracer: otel.Tracer(pgcluster.PGClusterControllerName), - Platform: detectPlatform(ctx, mgr.GetConfig()), + Platform: detectPlatform(ctx, mgr.GetConfig(), openShift), KubeVersion: getServerVersion(ctx, mgr.GetConfig()), CrunchyController: cm.Controller(), - IsOpenShift: isOpenshift(ctx, mgr.GetConfig()), + IsOpenShift: openShift, Cron: pgcluster.NewCronRegistry(), ExternalChan: externalEvents, StopExternalWatchers: stopChan, @@ -272,7 +275,7 @@ func addControllersToManager(ctx context.Context, mgr manager.Manager) error { Client: mgr.GetClient(), Owner: "pgadmin-controller", Recorder: mgr.GetEventRecorderFor(naming.ControllerPGAdmin), - IsOpenShift: isOpenshift(ctx, mgr.GetConfig()), + IsOpenShift: openShift, } if err := pgAdminReconciler.SetupWithManager(mgr); err != nil { @@ -358,83 +361,105 @@ func initManager(ctx context.Context) (runtime.Options, error) { return options, nil } -func isGKE(ctx context.Context, cfg *rest.Config) bool { +// hasAPIGroup returns true if the cluster exposes the given API group name. +// Uses the discovery API; note that hardened clusters may restrict discovery access. +func hasAPIGroup(ctx context.Context, cfg *rest.Config, groupName string) bool { log := logging.FromContext(ctx) - - const groupName, kind = "cloud.google.com", "BackendConfig" - client, err := discovery.NewDiscoveryClientForConfig(cfg) - assertNoError(err) - + if err != nil { + log.V(1).Info("platform detection: could not create discovery client", "error", err.Error()) + return false + } groups, err := client.ServerGroups() if err != nil { - assertNoError(err) + log.V(1).Info("platform detection: could not list API groups", "error", err.Error()) + return false } for _, g := range groups.Groups { - if g.Name != groupName { - continue - } - for _, v := range g.Versions { - resourceList, err := client.ServerResourcesForGroupVersion(v.GroupVersion) - if err != nil { - assertNoError(err) - } - for _, r := range resourceList.APIResources { - if r.Kind == kind { - log.Info("detected GKE environment") - return true - } - } + if g.Name == groupName { + return true } } - return false } -func isEKS(ctx context.Context, cfg *rest.Config) bool { - log := logging.FromContext(ctx) +type platformProbe struct { + name string + label string + apiGroups []string + hosts []string + custom func(ctx context.Context, cfg *rest.Config) bool +} - const groupName, kind = "vpcresources.k8s.aws", "SecurityGroupPolicy" +func (p platformProbe) detect(ctx context.Context, cfg *rest.Config) bool { + if p.custom != nil { + return p.custom(ctx, cfg) + } + for _, g := range p.apiGroups { + if hasAPIGroup(ctx, cfg, g) { + return true + } + } + for _, h := range p.hosts { + if strings.Contains(cfg.Host, h) { + return true + } + } + return false +} - client, err := discovery.NewDiscoveryClientForConfig(cfg) - assertNoError(err) +var platformProbes = []platformProbe{ + {name: "gke", label: "GKE", apiGroups: []string{"networking.gke.io"}}, + // crd.k8s.amazonaws.com (VPC CNI) and metrics.eks.amazonaws.com are independent EKS signals. + {name: "eks", label: "EKS", apiGroups: []string{"crd.k8s.amazonaws.com", "metrics.eks.amazonaws.com"}}, + // AKS exposes no unique API groups; inspect the API server TLS cert SAN instead. + {name: "aks", label: "AKS", custom: detectAKS}, + {name: "doks", label: "DOKS", apiGroups: []string{"dataplane-operator.doks.digitalocean.com"}}, + {name: "oke", label: "OKE", apiGroups: []string{"oci.oraclecloud.com"}, hosts: []string{".oraclecloud.com"}}, + {name: "ack", label: "ACK", apiGroups: []string{"alibabacloud.com"}, hosts: []string{".aliyuncs.com"}}, + // kommander.mesosphere.io is the legacy D2iQ/Konvoy group name for NKP. + {name: "nkp", label: "NKP", apiGroups: []string{"nkp.nutanix.com", "kommander.mesosphere.io"}}, + {name: "platform9", label: "Platform9", hosts: []string{".platform9.io", ".platform9.net"}}, + {name: "tanzu", label: "Tanzu", apiGroups: []string{"run.tanzu.vmware.com"}}, + {name: "rancher", label: "Rancher", apiGroups: []string{"management.cattle.io"}}, +} - groups, err := client.ServerGroups() +func detectAKS(ctx context.Context, cfg *rest.Config) bool { + tlsCfg, err := rest.TLSConfigFor(cfg) if err != nil { - assertNoError(err) + logging.FromContext(ctx).V(1).Info("platform detection: could not build TLS config", "error", err.Error()) + return false } - for _, g := range groups.Groups { - if g.Name != groupName { - continue - } - for _, v := range g.Versions { - resourceList, err := client.ServerResourcesForGroupVersion(v.GroupVersion) - if err != nil { - assertNoError(err) - } - for _, r := range resourceList.APIResources { - if r.Kind == kind { - log.Info("detected EKS environment") - return true - } + host := strings.TrimPrefix(cfg.Host, "https://") + host = strings.TrimPrefix(host, "http://") + netConn, err := (&tls.Dialer{Config: tlsCfg}).DialContext(ctx, "tcp", host) + if err != nil { + logging.FromContext(ctx).V(1).Info("platform detection: could not dial API server", "error", err.Error()) + return false + } + defer netConn.Close() + conn := netConn.(*tls.Conn) + for _, cert := range conn.ConnectionState().PeerCertificates { + for _, san := range cert.DNSNames { + if strings.HasSuffix(san, ".azmk8s.io") { + return true } } } - return false } -func detectPlatform(ctx context.Context, cfg *rest.Config) string { - switch { - case isOpenshift(ctx, cfg): +func detectPlatform(ctx context.Context, cfg *rest.Config, openShift bool) string { + if openShift { return "openshift" - case isGKE(ctx, cfg): - return "gke" - case isEKS(ctx, cfg): - return "eks" - default: - return "unknown" } + for _, probe := range platformProbes { + if probe.detect(ctx, cfg) { + logging.FromContext(ctx).Info("detected " + probe.label + " environment") + return probe.name + } + } + return "unknown" } // getServerVersion returns the stringified server version (i.e., the same info `kubectl version` @@ -496,35 +521,10 @@ func getLogLevel() zapcore.LevelEnabler { } func isOpenshift(ctx context.Context, cfg *rest.Config) bool { - log := logging.FromContext(ctx) - - const sccGroupName, sccKind = "security.openshift.io", "SecurityContextConstraints" - - client, err := discovery.NewDiscoveryClientForConfig(cfg) - assertNoError(err) - - groups, err := client.ServerGroups() - if err != nil { - assertNoError(err) + if hasAPIGroup(ctx, cfg, "security.openshift.io") { + logging.FromContext(ctx).Info("detected Openshift environment") + return true } - for _, g := range groups.Groups { - if g.Name != sccGroupName { - continue - } - for _, v := range g.Versions { - resourceList, err := client.ServerResourcesForGroupVersion(v.GroupVersion) - if err != nil { - assertNoError(err) - } - for _, r := range resourceList.APIResources { - if r.Kind == sccKind { - log.Info("detected Openshift environment") - return true - } - } - } - } - return false }