mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-28 14:07:14 +00:00
Merge pull request #3959 from smarterclayton/expose_tls_config_and_wrappers
Allow client.Config to be used for HTTP2 and WebSocket connections
This commit is contained in:
commit
f398fd2084
@ -61,22 +61,8 @@ type Config struct {
|
|||||||
// TODO: demonstrate an OAuth2 compatible client.
|
// TODO: demonstrate an OAuth2 compatible client.
|
||||||
BearerToken string
|
BearerToken string
|
||||||
|
|
||||||
// Server requires TLS client certificate authentication
|
// TLSClientConfig contains settings to enable transport layer security
|
||||||
CertFile string
|
TLSClientConfig
|
||||||
// Server requires TLS client certificate authentication
|
|
||||||
KeyFile string
|
|
||||||
// Trusted root certificates for server
|
|
||||||
CAFile string
|
|
||||||
|
|
||||||
// CertData holds PEM-encoded bytes (typically read from a client certificate file).
|
|
||||||
// CertData takes precedence over CertFile
|
|
||||||
CertData []byte
|
|
||||||
// KeyData holds PEM-encoded bytes (typically read from a client certificate key file).
|
|
||||||
// KeyData takes precedence over KeyFile
|
|
||||||
KeyData []byte
|
|
||||||
// CAData holds PEM-encoded bytes (typically read from a root certificates bundle).
|
|
||||||
// CAData takes precedence over CAFile
|
|
||||||
CAData []byte
|
|
||||||
|
|
||||||
// Server should be accessed without verifying the TLS
|
// Server should be accessed without verifying the TLS
|
||||||
// certificate. For testing only.
|
// certificate. For testing only.
|
||||||
@ -92,11 +78,17 @@ type KubeletConfig struct {
|
|||||||
Port uint
|
Port uint
|
||||||
EnableHttps bool
|
EnableHttps bool
|
||||||
|
|
||||||
// TLS Configuration, only applies if EnableHttps is true.
|
// TLSClientConfig contains settings to enable transport layer security
|
||||||
|
TLSClientConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLSClientConfig contains settings to enable transport layer security
|
||||||
|
type TLSClientConfig struct {
|
||||||
|
// Server requires TLS client certificate authentication
|
||||||
CertFile string
|
CertFile string
|
||||||
// TLS Configuration, only applies if EnableHttps is true.
|
// Server requires TLS client certificate authentication
|
||||||
KeyFile string
|
KeyFile string
|
||||||
// TLS Configuration, only applies if EnableHttps is true.
|
// Trusted root certificates for server
|
||||||
CAFile string
|
CAFile string
|
||||||
|
|
||||||
// CertData holds PEM-encoded bytes (typically read from a client certificate file).
|
// CertData holds PEM-encoded bytes (typically read from a client certificate file).
|
||||||
@ -215,47 +207,40 @@ func TransportFor(config *Config) (http.RoundTripper, error) {
|
|||||||
if config.Transport != nil && (hasCA || hasCert || config.Insecure) {
|
if config.Transport != nil && (hasCA || hasCert || config.Insecure) {
|
||||||
return nil, fmt.Errorf("using a custom transport with TLS certificate options or the insecure flag is not allowed")
|
return nil, fmt.Errorf("using a custom transport with TLS certificate options or the insecure flag is not allowed")
|
||||||
}
|
}
|
||||||
if hasCA && config.Insecure {
|
|
||||||
return nil, fmt.Errorf("specifying a root certificates file with the insecure flag is not allowed")
|
tlsConfig, err := TLSConfigFor(config)
|
||||||
}
|
if err != nil {
|
||||||
var transport http.RoundTripper
|
|
||||||
switch {
|
|
||||||
case config.Transport != nil:
|
|
||||||
transport = config.Transport
|
|
||||||
case hasCert:
|
|
||||||
var (
|
|
||||||
certData, keyData, caData []byte
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
if certData, err = dataFromSliceOrFile(config.CertData, config.CertFile); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if keyData, err = dataFromSliceOrFile(config.KeyData, config.KeyFile); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if caData, err = dataFromSliceOrFile(config.CAData, config.CAFile); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if transport, err = NewClientCertTLSTransport(certData, keyData, caData); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
case hasCA:
|
|
||||||
var (
|
|
||||||
caData []byte
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
if caData, err = dataFromSliceOrFile(config.CAData, config.CAFile); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if transport, err = NewTLSTransport(caData); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
case config.Insecure:
|
|
||||||
transport = NewUnsafeTLSTransport()
|
|
||||||
default:
|
|
||||||
transport = http.DefaultTransport
|
|
||||||
}
|
|
||||||
|
|
||||||
|
var transport http.RoundTripper
|
||||||
|
if config.Transport != nil {
|
||||||
|
transport = config.Transport
|
||||||
|
} else {
|
||||||
|
if tlsConfig != nil {
|
||||||
|
transport = &http.Transport{
|
||||||
|
TLSClientConfig: tlsConfig,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
transport = http.DefaultTransport
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transport, err = HTTPWrappersForConfig(config, transport)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: use the config context to wrap a transport
|
||||||
|
|
||||||
|
return transport, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPWrappersForConfig wraps a round tripper with any relevant layered behavior from the
|
||||||
|
// config. Exposed to allow more clients that need HTTP-like behavior but then must hijack
|
||||||
|
// the underlying connection (like WebSocket or HTTP2 clients). Pure HTTP clients should use
|
||||||
|
// the higher level TransportFor or RESTClientFor methods.
|
||||||
|
func HTTPWrappersForConfig(config *Config, rt http.RoundTripper) (http.RoundTripper, error) {
|
||||||
// Set authentication wrappers
|
// Set authentication wrappers
|
||||||
hasBasicAuth := config.Username != "" || config.Password != ""
|
hasBasicAuth := config.Username != "" || config.Password != ""
|
||||||
if hasBasicAuth && config.BearerToken != "" {
|
if hasBasicAuth && config.BearerToken != "" {
|
||||||
@ -263,14 +248,11 @@ func TransportFor(config *Config) (http.RoundTripper, error) {
|
|||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
case config.BearerToken != "":
|
case config.BearerToken != "":
|
||||||
transport = NewBearerAuthRoundTripper(config.BearerToken, transport)
|
rt = NewBearerAuthRoundTripper(config.BearerToken, rt)
|
||||||
case hasBasicAuth:
|
case hasBasicAuth:
|
||||||
transport = NewBasicAuthRoundTripper(config.Username, config.Password, transport)
|
rt = NewBasicAuthRoundTripper(config.Username, config.Password, rt)
|
||||||
}
|
}
|
||||||
|
return rt, nil
|
||||||
// TODO: use the config context to wrap a transport
|
|
||||||
|
|
||||||
return transport, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// dataFromSliceOrFile returns data from the slice (if non-empty), or from the file,
|
// dataFromSliceOrFile returns data from the slice (if non-empty), or from the file,
|
||||||
|
@ -104,56 +104,70 @@ func TestTransportFor(t *testing.T) {
|
|||||||
"ca transport": {
|
"ca transport": {
|
||||||
TLS: true,
|
TLS: true,
|
||||||
Config: &Config{
|
Config: &Config{
|
||||||
|
TLSClientConfig: TLSClientConfig{
|
||||||
CAData: []byte(rootCACert),
|
CAData: []byte(rootCACert),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
"bad ca file transport": {
|
"bad ca file transport": {
|
||||||
Err: true,
|
Err: true,
|
||||||
Config: &Config{
|
Config: &Config{
|
||||||
|
TLSClientConfig: TLSClientConfig{
|
||||||
CAFile: "invalid file",
|
CAFile: "invalid file",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
"ca data overriding bad ca file transport": {
|
"ca data overriding bad ca file transport": {
|
||||||
TLS: true,
|
TLS: true,
|
||||||
Config: &Config{
|
Config: &Config{
|
||||||
|
TLSClientConfig: TLSClientConfig{
|
||||||
CAData: []byte(rootCACert),
|
CAData: []byte(rootCACert),
|
||||||
CAFile: "invalid file",
|
CAFile: "invalid file",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
|
||||||
"cert transport": {
|
"cert transport": {
|
||||||
TLS: true,
|
TLS: true,
|
||||||
Config: &Config{
|
Config: &Config{
|
||||||
|
TLSClientConfig: TLSClientConfig{
|
||||||
CertData: []byte(certData),
|
CertData: []byte(certData),
|
||||||
KeyData: []byte(keyData),
|
KeyData: []byte(keyData),
|
||||||
CAData: []byte(rootCACert),
|
CAData: []byte(rootCACert),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
"bad cert data transport": {
|
"bad cert data transport": {
|
||||||
Err: true,
|
Err: true,
|
||||||
Config: &Config{
|
Config: &Config{
|
||||||
|
TLSClientConfig: TLSClientConfig{
|
||||||
CertData: []byte(certData),
|
CertData: []byte(certData),
|
||||||
KeyData: []byte("bad key data"),
|
KeyData: []byte("bad key data"),
|
||||||
CAData: []byte(rootCACert),
|
CAData: []byte(rootCACert),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
"bad file cert transport": {
|
"bad file cert transport": {
|
||||||
Err: true,
|
Err: true,
|
||||||
Config: &Config{
|
Config: &Config{
|
||||||
|
TLSClientConfig: TLSClientConfig{
|
||||||
CertData: []byte(certData),
|
CertData: []byte(certData),
|
||||||
KeyFile: "invalid file",
|
KeyFile: "invalid file",
|
||||||
CAData: []byte(rootCACert),
|
CAData: []byte(rootCACert),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
"key data overriding bad file cert transport": {
|
"key data overriding bad file cert transport": {
|
||||||
TLS: true,
|
TLS: true,
|
||||||
Config: &Config{
|
Config: &Config{
|
||||||
|
TLSClientConfig: TLSClientConfig{
|
||||||
CertData: []byte(certData),
|
CertData: []byte(certData),
|
||||||
KeyData: []byte(keyData),
|
KeyData: []byte(keyData),
|
||||||
KeyFile: "invalid file",
|
KeyFile: "invalid file",
|
||||||
CAData: []byte(rootCACert),
|
CAData: []byte(rootCACert),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for k, testCase := range testCases {
|
for k, testCase := range testCases {
|
||||||
transport, err := TransportFor(testCase.Config)
|
transport, err := TransportFor(testCase.Config)
|
||||||
@ -207,15 +221,19 @@ func TestIsConfigTransportTLS(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Config: &Config{
|
Config: &Config{
|
||||||
Host: "localhost",
|
Host: "localhost",
|
||||||
|
TLSClientConfig: TLSClientConfig{
|
||||||
CertFile: "foo",
|
CertFile: "foo",
|
||||||
},
|
},
|
||||||
|
},
|
||||||
TransportTLS: true,
|
TransportTLS: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Config: &Config{
|
Config: &Config{
|
||||||
Host: "///:://localhost",
|
Host: "///:://localhost",
|
||||||
|
TLSClientConfig: TLSClientConfig{
|
||||||
CertFile: "foo",
|
CertFile: "foo",
|
||||||
},
|
},
|
||||||
|
},
|
||||||
TransportTLS: false,
|
TransportTLS: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -59,41 +59,25 @@ type HTTPKubeletClient struct {
|
|||||||
EnableHttps bool
|
EnableHttps bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: this structure is questionable, it should be using client.Config and overriding defaults.
|
||||||
func NewKubeletClient(config *KubeletConfig) (KubeletClient, error) {
|
func NewKubeletClient(config *KubeletConfig) (KubeletClient, error) {
|
||||||
transport := http.DefaultTransport
|
transport := http.DefaultTransport
|
||||||
hasCA := len(config.CAFile) > 0 || len(config.CAData) > 0
|
|
||||||
hasCert := len(config.CertFile) > 0 || len(config.CertData) > 0
|
tlsConfig, err := TLSConfigFor(&Config{
|
||||||
if hasCert {
|
TLSClientConfig: config.TLSClientConfig,
|
||||||
var (
|
})
|
||||||
certData, keyData, caData []byte
|
if err != nil {
|
||||||
err error
|
|
||||||
)
|
|
||||||
if certData, err = dataFromSliceOrFile(config.CertData, config.CertFile); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if keyData, err = dataFromSliceOrFile(config.KeyData, config.KeyFile); err != nil {
|
if tlsConfig != nil {
|
||||||
return nil, err
|
transport = &http.Transport{
|
||||||
}
|
TLSClientConfig: tlsConfig,
|
||||||
if caData, err = dataFromSliceOrFile(config.CAData, config.CAFile); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if transport, err = NewClientCertTLSTransport(certData, keyData, caData); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else if hasCA {
|
|
||||||
var (
|
|
||||||
caData []byte
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
if caData, err = dataFromSliceOrFile(config.CAData, config.CAFile); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if transport, err = NewTLSTransport(caData); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c := &http.Client{Transport: transport}
|
c := &http.Client{
|
||||||
|
Transport: transport,
|
||||||
|
}
|
||||||
return &HTTPKubeletClient{
|
return &HTTPKubeletClient{
|
||||||
Client: c,
|
Client: c,
|
||||||
Port: config.Port,
|
Port: config.Port,
|
||||||
|
@ -187,9 +187,11 @@ func TestNewKubeletClientTLSInvalid(t *testing.T) {
|
|||||||
Port: 9000,
|
Port: 9000,
|
||||||
EnableHttps: true,
|
EnableHttps: true,
|
||||||
//Invalid certificate and key path
|
//Invalid certificate and key path
|
||||||
|
TLSClientConfig: TLSClientConfig{
|
||||||
CertFile: "./testdata/mycertinvalid.cer",
|
CertFile: "./testdata/mycertinvalid.cer",
|
||||||
KeyFile: "./testdata/mycertinvalid.key",
|
KeyFile: "./testdata/mycertinvalid.key",
|
||||||
CAFile: "./testdata/myCA.cer",
|
CAFile: "./testdata/myCA.cer",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := NewKubeletClient(config)
|
client, err := NewKubeletClient(config)
|
||||||
@ -205,11 +207,13 @@ func TestNewKubeletClientTLSValid(t *testing.T) {
|
|||||||
config := &KubeletConfig{
|
config := &KubeletConfig{
|
||||||
Port: 9000,
|
Port: 9000,
|
||||||
EnableHttps: true,
|
EnableHttps: true,
|
||||||
|
TLSClientConfig: TLSClientConfig{
|
||||||
CertFile: "./testdata/mycertvalid.cer",
|
CertFile: "./testdata/mycertvalid.cer",
|
||||||
// TLS Configuration, only applies if EnableHttps is true.
|
// TLS Configuration, only applies if EnableHttps is true.
|
||||||
KeyFile: "./testdata/mycertvalid.key",
|
KeyFile: "./testdata/mycertvalid.key",
|
||||||
// TLS Configuration, only applies if EnableHttps is true.
|
// TLS Configuration, only applies if EnableHttps is true.
|
||||||
CAFile: "./testdata/myCA.cer",
|
CAFile: "./testdata/myCA.cer",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := NewKubeletClient(config)
|
client, err := NewKubeletClient(config)
|
||||||
|
@ -54,15 +54,60 @@ func (rt *bearerAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response,
|
|||||||
return rt.rt.RoundTrip(req)
|
return rt.rt.RoundTrip(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClientCertTLSTransport(certData, keyData, caData []byte) (*http.Transport, error) {
|
// TLSConfigFor returns a tls.Config that will provide the transport level security defined
|
||||||
|
// by the provided Config. Will return nil if no transport level security is requested.
|
||||||
|
func TLSConfigFor(config *Config) (*tls.Config, error) {
|
||||||
|
hasCA := len(config.CAFile) > 0 || len(config.CAData) > 0
|
||||||
|
hasCert := len(config.CertFile) > 0 || len(config.CertData) > 0
|
||||||
|
|
||||||
|
if hasCA && config.Insecure {
|
||||||
|
return nil, fmt.Errorf("specifying a root certificates file with the insecure flag is not allowed")
|
||||||
|
}
|
||||||
|
var tlsConfig *tls.Config
|
||||||
|
switch {
|
||||||
|
case hasCert:
|
||||||
|
certData, err := dataFromSliceOrFile(config.CertData, config.CertFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
keyData, err := dataFromSliceOrFile(config.KeyData, config.KeyFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
caData, err := dataFromSliceOrFile(config.CAData, config.CAFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cfg, err := NewClientCertTLSConfig(certData, keyData, caData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tlsConfig = cfg
|
||||||
|
case hasCA:
|
||||||
|
caData, err := dataFromSliceOrFile(config.CAData, config.CAFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cfg, err := NewTLSConfig(caData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tlsConfig = cfg
|
||||||
|
case config.Insecure:
|
||||||
|
tlsConfig = NewUnsafeTLSConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
return tlsConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClientCertTLSConfig(certData, keyData, caData []byte) (*tls.Config, error) {
|
||||||
cert, err := tls.X509KeyPair(certData, keyData)
|
cert, err := tls.X509KeyPair(certData, keyData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
certPool := x509.NewCertPool()
|
certPool := x509.NewCertPool()
|
||||||
certPool.AppendCertsFromPEM(caData)
|
certPool.AppendCertsFromPEM(caData)
|
||||||
return &http.Transport{
|
return &tls.Config{
|
||||||
TLSClientConfig: &tls.Config{
|
|
||||||
// Change default from SSLv3 to TLSv1.0 (because of POODLE vulnerability)
|
// Change default from SSLv3 to TLSv1.0 (because of POODLE vulnerability)
|
||||||
MinVersion: tls.VersionTLS10,
|
MinVersion: tls.VersionTLS10,
|
||||||
Certificates: []tls.Certificate{
|
Certificates: []tls.Certificate{
|
||||||
@ -71,27 +116,22 @@ func NewClientCertTLSTransport(certData, keyData, caData []byte) (*http.Transpor
|
|||||||
RootCAs: certPool,
|
RootCAs: certPool,
|
||||||
ClientCAs: certPool,
|
ClientCAs: certPool,
|
||||||
ClientAuth: tls.RequireAndVerifyClientCert,
|
ClientAuth: tls.RequireAndVerifyClientCert,
|
||||||
},
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTLSTransport(caData []byte) (*http.Transport, error) {
|
func NewTLSConfig(caData []byte) (*tls.Config, error) {
|
||||||
certPool := x509.NewCertPool()
|
certPool := x509.NewCertPool()
|
||||||
certPool.AppendCertsFromPEM(caData)
|
certPool.AppendCertsFromPEM(caData)
|
||||||
return &http.Transport{
|
return &tls.Config{
|
||||||
TLSClientConfig: &tls.Config{
|
|
||||||
// Change default from SSLv3 to TLSv1.0 (because of POODLE vulnerability)
|
// Change default from SSLv3 to TLSv1.0 (because of POODLE vulnerability)
|
||||||
MinVersion: tls.VersionTLS10,
|
MinVersion: tls.VersionTLS10,
|
||||||
RootCAs: certPool,
|
RootCAs: certPool,
|
||||||
},
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUnsafeTLSTransport() *http.Transport {
|
func NewUnsafeTLSConfig() *tls.Config {
|
||||||
return &http.Transport{
|
return &tls.Config{
|
||||||
TLSClientConfig: &tls.Config{
|
|
||||||
InsecureSkipVerify: true,
|
InsecureSkipVerify: true,
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,9 +23,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestUnsecuredTLSTransport(t *testing.T) {
|
func TestUnsecuredTLSTransport(t *testing.T) {
|
||||||
transport := NewUnsafeTLSTransport()
|
cfg := NewUnsafeTLSConfig()
|
||||||
if !transport.TLSClientConfig.InsecureSkipVerify {
|
if !cfg.InsecureSkipVerify {
|
||||||
t.Errorf("expected transport to be insecure")
|
t.Errorf("expected config to be insecure")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user