From 801f85f9fd766abe03c1490dc1bf7435f9fb2c90 Mon Sep 17 00:00:00 2001 From: Aleksandra Gacek Date: Mon, 31 May 2021 12:17:52 +0200 Subject: [PATCH] Allow communicating with summary API via authenticated kubelet port using certificate. --- hack/verify-flags/known-flags.txt | 1 + kubelet-to-gcm/monitor/config/initialize.go | 8 +++-- kubelet-to-gcm/monitor/kubelet/client.go | 8 +++-- kubelet-to-gcm/monitor/kubelet/source.go | 34 ++++++++++++++++++++- kubelet-to-gcm/monitor/main/daemon.go | 19 ++++++------ kubelet-to-gcm/monitor/poll.go | 8 ++--- 6 files changed, 60 insertions(+), 18 deletions(-) diff --git a/hack/verify-flags/known-flags.txt b/hack/verify-flags/known-flags.txt index 36fe99a35..76daa3941 100644 --- a/hack/verify-flags/known-flags.txt +++ b/hack/verify-flags/known-flags.txt @@ -9,6 +9,7 @@ batch-url block-path-config blunderbuss-config blunderbuss-reassign +certificate-location change-permissions chart-url cla-status-context diff --git a/kubelet-to-gcm/monitor/config/initialize.go b/kubelet-to-gcm/monitor/config/initialize.go index 9637c5959..b4e7246ed 100644 --- a/kubelet-to-gcm/monitor/config/initialize.go +++ b/kubelet-to-gcm/monitor/config/initialize.go @@ -33,7 +33,7 @@ const ( // NewConfigs returns the SourceConfigs for all monitored endpoints, and // hits the GCE Metadata server if required. -func NewConfigs(zone, projectID, cluster, clusterLocation, host, instance, schemaPrefix string, monitoredResourceLabels map[string]string, kubeletPort, ctrlPort uint, resolution time.Duration) (*monitor.SourceConfig, *monitor.SourceConfig, error) { +func NewConfigs(zone, projectID, cluster, clusterLocation, host, instance, schemaPrefix, certificateLocation string, monitoredResourceLabels map[string]string, kubeletPort, ctrlPort uint, resolution time.Duration) (*monitor.SourceConfig, *monitor.SourceConfig, error) { zone, err := getZone(zone) if err != nil { return nil, nil, err @@ -81,6 +81,7 @@ func NewConfigs(zone, projectID, cluster, clusterLocation, host, instance, schem MonitoredResourceLabels: monitoredResourceLabels, Port: kubeletPort, Resolution: resolution, + CertificateLocation: certificateLocation, }, &monitor.SourceConfig{ Zone: zone, Project: projectID, @@ -172,7 +173,7 @@ func getClusterLocation(clusterLocation string) (string, error) { return clusterLocation, nil } -// getKubeletHost returns the kubelet host if given, or gets ip of network interface 0 from gce. +// getKubeletHost returns the kubelet host if given, or gets ip of network interface 0 from gce, or gets instance name if set to use-instance-name. func getKubeletHost(kubeletHost string) (string, error) { if kubeletHost == "use-gce" { body, err := getGCEMetaData(metaDataURI("/instance/network-interfaces/0/ip")) @@ -181,6 +182,9 @@ func getKubeletHost(kubeletHost string) (string, error) { } kubeletHost = string(body) } + if kubeletHost == "use-instance-name" { + return getInstance("use-gce") + } return kubeletHost, nil } diff --git a/kubelet-to-gcm/monitor/kubelet/client.go b/kubelet-to-gcm/monitor/kubelet/client.go index 7e309782c..97c78c509 100644 --- a/kubelet-to-gcm/monitor/kubelet/client.go +++ b/kubelet-to-gcm/monitor/kubelet/client.go @@ -34,9 +34,13 @@ type Client struct { } // NewClient returns a new Client. -func NewClient(host string, port uint, client *http.Client) (*Client, error) { +func NewClient(host string, port uint, client *http.Client, useAuthPort bool) (*Client, error) { // Parse our URL upfront, so we can fail fast. - urlStr := fmt.Sprintf("http://%s:%d/stats/summary", host, port) + protocol := "http" + if useAuthPort { + protocol = "https" + } + urlStr := fmt.Sprintf("%s://%s:%d/stats/summary", protocol, host, port) summaryURL, err := url.Parse(urlStr) if err != nil { return nil, err diff --git a/kubelet-to-gcm/monitor/kubelet/source.go b/kubelet-to-gcm/monitor/kubelet/source.go index 273f0d0a3..afa8d43c8 100644 --- a/kubelet-to-gcm/monitor/kubelet/source.go +++ b/kubelet-to-gcm/monitor/kubelet/source.go @@ -17,7 +17,10 @@ limitations under the License. package kubelet import ( + "crypto/tls" + "crypto/x509" "fmt" + "io/ioutil" "net/http" v3 "google.golang.org/api/monitoring/v3" @@ -38,7 +41,17 @@ func NewSource(cfg *monitor.SourceConfig) (*Source, error) { trans := NewTranslator(cfg.Zone, cfg.Project, cfg.Cluster, cfg.ClusterLocation, cfg.Instance, cfg.InstanceID, cfg.SchemaPrefix, cfg.MonitoredResourceLabels, cfg.Resolution) // NewClient validates its own inputs. - client, err := NewClient(cfg.Host, cfg.Port, &http.Client{}) + httpClient := &http.Client{} + var err error + useAuthPort := false + if cfg.CertificateLocation != "" { + httpClient, err = getSecuredHttpClient(cfg.CertificateLocation) + if err != nil { + return nil, fmt.Errorf("failed to create secure http client: %s", err) + } + useAuthPort = true + } + client, err := NewClient(cfg.Host, cfg.Port, httpClient, useAuthPort) if err != nil { return nil, fmt.Errorf("Failed to create a kubelet client with config %v: %v", cfg, err) } @@ -76,3 +89,22 @@ func (s *Source) Name() string { func (s *Source) ProjectPath() string { return s.projectPath } + +func getSecuredHttpClient(certLocation string) (*http.Client, error) { + cert, err := ioutil.ReadFile(certLocation) + if err != nil { + return nil, fmt.Errorf("failed to read file with kubelet certificate: %s", err) + } + certPool := x509.NewCertPool() + ok := certPool.AppendCertsFromPEM(cert) + if !ok { + return nil, fmt.Errorf("failed to parse kubelet certificate") + } + return &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + RootCAs: certPool, + }, + }, + }, nil +} diff --git a/kubelet-to-gcm/monitor/main/daemon.go b/kubelet-to-gcm/monitor/main/daemon.go index 94b9de229..41bf6011c 100644 --- a/kubelet-to-gcm/monitor/main/daemon.go +++ b/kubelet-to-gcm/monitor/main/daemon.go @@ -48,14 +48,15 @@ var ( "When empty, old resource model (gke_container) is used. k8s_ prefix uses new model, with separate pod/container/node.") monitoredResourceLabels = flag.String("monitored-resource-labels", "", "Manually specified MonitoredResource labels.") // Flags to identify the Kubelet. - zone = flag.String("zone", "use-gce", "The zone where this kubelet lives.") - project = flag.String("project", "use-gce", "The project where this kubelet's host lives.") - cluster = flag.String("cluster", "use-gce", "The cluster where this kubelet holds membership.") - clusterLocation = flag.String("cluster-location", "use-gce", "The location of the cluster where this kubelet holds membership.") - kubeletInstance = flag.String("kubelet-instance", "use-gce", "The instance name the kubelet resides on.") - kubeletHost = flag.String("kubelet-host", "use-gce", "The kubelet's host name.") - kubeletPort = flag.Uint("kubelet-port", 10255, "The kubelet's port.") - ctrlPort = flag.Uint("controller-manager-port", 10252, "The kube-controller's port. Can be set to 0 to disable kube-controller-manager metrics collection.") + zone = flag.String("zone", "use-gce", "The zone where this kubelet lives.") + project = flag.String("project", "use-gce", "The project where this kubelet's host lives.") + cluster = flag.String("cluster", "use-gce", "The cluster where this kubelet holds membership.") + clusterLocation = flag.String("cluster-location", "use-gce", "The location of the cluster where this kubelet holds membership.") + kubeletInstance = flag.String("kubelet-instance", "use-gce", "The instance name the kubelet resides on.") + kubeletHost = flag.String("kubelet-host", "use-gce", "The kubelet's host name.") + kubeletPort = flag.Uint("kubelet-port", 10255, "The kubelet's port.") + ctrlPort = flag.Uint("controller-manager-port", 10252, "The kube-controller's port. Can be set to 0 to disable kube-controller-manager metrics collection.") + certificateLocation = flag.String("certificate-location", "", "Location under which kubelet certificate is available, needed when using secure kubelet port.") // Flags to control runtime behavior. res = flag.Uint("resolution", 10, "The time, in seconds, to poll the Kubelet.") gcmEndpoint = flag.String("gcm-endpoint", "", "The GCM endpoint to hit. Defaults to the default endpoint.") @@ -73,7 +74,7 @@ func main() { monitoredResourceLabels := parseMonitoredResourceLabels(*monitoredResourceLabels) // Initialize the configuration. - kubeletCfg, ctrlCfg, err := config.NewConfigs(*zone, *project, *cluster, *clusterLocation, *kubeletHost, *kubeletInstance, *schemaPrefix, monitoredResourceLabels, *kubeletPort, *ctrlPort, resolution) + kubeletCfg, ctrlCfg, err := config.NewConfigs(*zone, *project, *cluster, *clusterLocation, *kubeletHost, *kubeletInstance, *schemaPrefix, *certificateLocation, monitoredResourceLabels, *kubeletPort, *ctrlPort, resolution) if err != nil { log.Fatalf("Failed to initialize configuration: %v", err) } diff --git a/kubelet-to-gcm/monitor/poll.go b/kubelet-to-gcm/monitor/poll.go index 16ea77b90..3c9ae57b3 100644 --- a/kubelet-to-gcm/monitor/poll.go +++ b/kubelet-to-gcm/monitor/poll.go @@ -28,10 +28,10 @@ const maxTimeSeriesPerRequest = 200 // SourceConfig is the set of data required to configure a kubernetes // data source (e.g., kubelet or kube-controller). type SourceConfig struct { - Zone, Project, Cluster, ClusterLocation, Host, Instance, InstanceID, SchemaPrefix string - MonitoredResourceLabels map[string]string - Port uint - Resolution time.Duration + Zone, Project, Cluster, ClusterLocation, Host, Instance, InstanceID, SchemaPrefix, CertificateLocation string + MonitoredResourceLabels map[string]string + Port uint + Resolution time.Duration } // MetricsSource is an object that provides kubernetes metrics in