package agones import ( "crypto/tls" "crypto/x509" "fmt" "net/http" "net/url" "os" "strings" "time" ) // NewKubernetesHTTPClient builds an in-cluster client for the Kubernetes API. // The bearer token is read for every request so kubelet token rotation does not // leave a long-running allocator with an expired credential. func NewKubernetesHTTPClient(baseURL, tokenPath, caPath string, timeout time.Duration) (*http.Client, error) { origin, err := url.Parse(baseURL) if err != nil || origin.Scheme != "https" || origin.Host == "" || origin.User != nil || origin.Path != "" || origin.RawQuery != "" || origin.Fragment != "" { return nil, fmt.Errorf("Kubernetes API base URL must be an HTTPS origin") } if strings.TrimSpace(tokenPath) == "" || strings.TrimSpace(caPath) == "" || timeout <= 0 { return nil, fmt.Errorf("Kubernetes API token path, CA path, and positive timeout are required") } caPEM, err := os.ReadFile(caPath) if err != nil { return nil, fmt.Errorf("read Kubernetes API CA: %w", err) } roots := x509.NewCertPool() if !roots.AppendCertsFromPEM(caPEM) { return nil, fmt.Errorf("Kubernetes API CA contains no certificates") } transport := http.DefaultTransport.(*http.Transport).Clone() transport.TLSClientConfig = &tls.Config{RootCAs: roots, MinVersion: tls.VersionTLS12} return &http.Client{ Timeout: timeout, Transport: bearerTokenTransport{ tokenPath: tokenPath, expectedOrigin: origin.Scheme + "://" + origin.Host, base: transport, }, }, nil } type bearerTokenTransport struct { tokenPath string expectedOrigin string base http.RoundTripper } func (t bearerTokenTransport) RoundTrip(request *http.Request) (*http.Response, error) { if request.URL.Scheme+"://"+request.URL.Host != t.expectedOrigin { return nil, fmt.Errorf("refusing to send Kubernetes API credential to unexpected origin") } tokenBytes, err := os.ReadFile(t.tokenPath) if err != nil { return nil, fmt.Errorf("read Kubernetes API bearer token: %w", err) } token := strings.TrimSpace(string(tokenBytes)) if token == "" || strings.ContainsAny(token, " \t\r\n") { return nil, fmt.Errorf("Kubernetes API bearer token is empty or malformed") } cloned := request.Clone(request.Context()) cloned.Header = request.Header.Clone() cloned.Header.Set("Authorization", "Bearer "+token) return t.base.RoundTrip(cloned) }