mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-09-10 05:30:26 +00:00
Refactor the client (again) to better support auth
* Allows consumers to provide their own transports for common cases. * Supports KUBE_API_VERSION on test cases for controlling which api version they test against * Provides a common flag registration method for CLIs that need to connect to an API server (to avoid duplicating flags) * Ensures errors are properly returned by the server * Add a Context field to client.Config
This commit is contained in:
231
pkg/client/helper.go
Normal file
231
pkg/client/helper.go
Normal file
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
||||
)
|
||||
|
||||
// Config holds the common attributes that can be passed to a Kubernetes client on
|
||||
// initialization.
|
||||
type Config struct {
|
||||
// Host must be a host string, a host:port pair, or a URL to the base of the API.
|
||||
Host string
|
||||
Version string
|
||||
|
||||
// Server requires Basic authentication
|
||||
Username string
|
||||
Password string
|
||||
|
||||
// Server requires Bearer authentication. This client will not attempt to use
|
||||
// refresh tokens for an OAuth2 flow.
|
||||
// TODO: demonstrate an OAuth2 compatible client.
|
||||
BearerToken string
|
||||
|
||||
// Server requires TLS client certificate authentication
|
||||
CertFile string
|
||||
KeyFile string
|
||||
CAFile string
|
||||
|
||||
// Server should be accessed without verifying the TLS
|
||||
// certificate. For testing only.
|
||||
Insecure bool
|
||||
|
||||
// Transport may be used for custom HTTP behavior. This attribute may not
|
||||
// be specified with the TLS client certificate options.
|
||||
Transport http.RoundTripper
|
||||
|
||||
// Context is the context that should be passed down to the server. If nil, the
|
||||
// context will be set to the appropriate default.
|
||||
Context api.Context
|
||||
}
|
||||
|
||||
// New creates a Kubernetes client for the given config. This client works with pods,
|
||||
// replication controllers and services. It allows operations such as list, get, update
|
||||
// and delete on these objects. An error is returned if the provided configuration
|
||||
// is not valid.
|
||||
func New(c *Config) (*Client, error) {
|
||||
client, err := RESTClientFor(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Client{client}, nil
|
||||
}
|
||||
|
||||
// NewOrDie creates a Kubernetes client and panics if the provided API version is not recognized.
|
||||
func NewOrDie(c *Config) *Client {
|
||||
client, err := New(c)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
// RESTClientFor returns a RESTClient that satisfies the requested attributes on a client Config
|
||||
// object.
|
||||
func RESTClientFor(config *Config) (*RESTClient, error) {
|
||||
version := defaultVersionFor(config)
|
||||
|
||||
// Set version
|
||||
versionInterfaces, err := latest.InterfacesFor(version)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("API version '%s' is not recognized (valid values: %s)", version, strings.Join(latest.Versions, ", "))
|
||||
}
|
||||
|
||||
baseURL, err := defaultServerUrlFor(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := NewRESTClient(baseURL, versionInterfaces.Codec)
|
||||
|
||||
transport, err := TransportFor(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if transport != http.DefaultTransport {
|
||||
client.Client = &http.Client{Transport: transport}
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// TransportFor returns an http.RoundTripper that will provide the authentication
|
||||
// or transport level security defined by the provided Config. Will return the
|
||||
// default http.DefaultTransport if no special case behavior is needed.
|
||||
func TransportFor(config *Config) (http.RoundTripper, error) {
|
||||
// Set transport level security
|
||||
if config.Transport != nil && (config.CertFile != "" || config.Insecure) {
|
||||
return nil, fmt.Errorf("using a custom transport with TLS certificate options or the insecure flag is not allowed")
|
||||
}
|
||||
var transport http.RoundTripper
|
||||
switch {
|
||||
case config.Transport != nil:
|
||||
transport = config.Transport
|
||||
case config.CertFile != "":
|
||||
t, err := NewClientCertTLSTransport(config.CertFile, config.KeyFile, config.CAFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
transport = t
|
||||
case config.Insecure:
|
||||
transport = NewUnsafeTLSTransport()
|
||||
default:
|
||||
transport = http.DefaultTransport
|
||||
}
|
||||
|
||||
// Set authentication wrappers
|
||||
hasBasicAuth := config.Username != "" || config.Password != ""
|
||||
if hasBasicAuth && config.BearerToken != "" {
|
||||
return nil, fmt.Errorf("username/password or bearer token may be set, but not both")
|
||||
}
|
||||
switch {
|
||||
case config.BearerToken != "":
|
||||
transport = NewBearerAuthRoundTripper(config.BearerToken, transport)
|
||||
case hasBasicAuth:
|
||||
transport = NewBasicAuthRoundTripper(config.Username, config.Password, transport)
|
||||
}
|
||||
|
||||
// TODO: use the config context to wrap a transport
|
||||
|
||||
return transport, nil
|
||||
}
|
||||
|
||||
// DefaultServerURL converts a host, host:port, or URL string to the default base server API path
|
||||
// to use with a Client at a given API version following the standard conventions for a
|
||||
// Kubernetes API.
|
||||
func DefaultServerURL(host, version string, defaultSecure bool) (*url.URL, error) {
|
||||
if host == "" {
|
||||
return nil, fmt.Errorf("host must be a URL or a host:port pair")
|
||||
}
|
||||
if version == "" {
|
||||
return nil, fmt.Errorf("version must be set")
|
||||
}
|
||||
base := host
|
||||
hostURL, err := url.Parse(base)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if hostURL.Scheme == "" {
|
||||
scheme := "http://"
|
||||
if defaultSecure {
|
||||
scheme = "https://"
|
||||
}
|
||||
hostURL, err = url.Parse(scheme + base)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if hostURL.Path != "" && hostURL.Path != "/" {
|
||||
return nil, fmt.Errorf("host must be a URL or a host:port pair: %s", base)
|
||||
}
|
||||
}
|
||||
|
||||
// If the user specified a URL without a path component (http://server.com), automatically
|
||||
// append the default API prefix
|
||||
if hostURL.Path == "" {
|
||||
hostURL.Path = "/api"
|
||||
}
|
||||
|
||||
// Add the version to the end of the path
|
||||
hostURL.Path = path.Join(hostURL.Path, version)
|
||||
|
||||
return hostURL, nil
|
||||
}
|
||||
|
||||
// IsConfigTransportSecure returns true iff the provided config will result in a protected
|
||||
// connection to the server when it is passed to client.New() or client.RESTClientFor().
|
||||
// Use to determine when to send credentials over the wire.
|
||||
//
|
||||
// Note: the Insecure flag is ignored when testing for this value, so MITM attacks are
|
||||
// still possible.
|
||||
func IsConfigTransportSecure(config *Config) bool {
|
||||
baseURL, err := defaultServerUrlFor(config)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return baseURL.Scheme == "https"
|
||||
}
|
||||
|
||||
// defaultServerUrlFor is shared between IsConfigSecure and RESTClientFor
|
||||
func defaultServerUrlFor(config *Config) (*url.URL, error) {
|
||||
version := defaultVersionFor(config)
|
||||
// TODO: move the default to secure when the apiserver supports TLS by default
|
||||
defaultSecure := config.CertFile != ""
|
||||
host := config.Host
|
||||
if host == "" {
|
||||
host = "localhost"
|
||||
}
|
||||
return DefaultServerURL(host, version, defaultSecure)
|
||||
}
|
||||
|
||||
// defaultVersionFor is shared between defaultServerUrlFor and RESTClientFor
|
||||
func defaultVersionFor(config *Config) string {
|
||||
version := config.Version
|
||||
if version == "" {
|
||||
// Clients default to the preferred code API version
|
||||
// TODO: implement version negotiation (highest version supported by server)
|
||||
version = latest.Version
|
||||
}
|
||||
return version
|
||||
}
|
Reference in New Issue
Block a user