add insecurebackendproxy
This commit is contained in:
@@ -4203,6 +4203,15 @@ type PodLogOptions struct {
|
|||||||
// log output. This may not display a complete final line of logging, and may return
|
// log output. This may not display a complete final line of logging, and may return
|
||||||
// slightly more or slightly less than the specified limit.
|
// slightly more or slightly less than the specified limit.
|
||||||
LimitBytes *int64
|
LimitBytes *int64
|
||||||
|
|
||||||
|
// insecureSkipTLSVerifyBackend indicates that the apiserver should not confirm the validity of the
|
||||||
|
// serving certificate of the backend it is connecting to. This will make the HTTPS connection between the apiserver
|
||||||
|
// and the backend insecure. This means the apiserver cannot verify the log data it is receiving came from the real
|
||||||
|
// kubelet. If the kubelet is configured to verify the apiserver's TLS credentials, it does not mean the
|
||||||
|
// connection to the real kubelet is vulnerable to a man in the middle attack (e.g. an attacker could not intercept
|
||||||
|
// the actual log data coming from the real kubelet).
|
||||||
|
// +optional
|
||||||
|
InsecureSkipTLSVerifyBackend bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
@@ -484,6 +484,12 @@ const (
|
|||||||
//
|
//
|
||||||
// Enables the startupProbe in kubelet worker.
|
// Enables the startupProbe in kubelet worker.
|
||||||
StartupProbe featuregate.Feature = "StartupProbe"
|
StartupProbe featuregate.Feature = "StartupProbe"
|
||||||
|
|
||||||
|
// owner: @deads2k
|
||||||
|
// beta: v1.17
|
||||||
|
//
|
||||||
|
// Enables the users to skip TLS verification of kubelets on pod logs requests
|
||||||
|
AllowInsecureBackendProxy featuregate.Feature = "AllowInsecureBackendProxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -563,6 +569,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
|
|||||||
EndpointSlice: {Default: false, PreRelease: featuregate.Alpha},
|
EndpointSlice: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
EvenPodsSpread: {Default: false, PreRelease: featuregate.Alpha},
|
EvenPodsSpread: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
StartupProbe: {Default: false, PreRelease: featuregate.Alpha},
|
StartupProbe: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
AllowInsecureBackendProxy: {Default: true, PreRelease: featuregate.Beta},
|
||||||
|
|
||||||
// inherited features from generic apiserver, relisted here to get a conflict if it is changed
|
// inherited features from generic apiserver, relisted here to get a conflict if it is changed
|
||||||
// unintentionally on either side:
|
// unintentionally on either side:
|
||||||
|
@@ -65,10 +65,11 @@ type KubeletClientConfig struct {
|
|||||||
|
|
||||||
// ConnectionInfo provides the information needed to connect to a kubelet
|
// ConnectionInfo provides the information needed to connect to a kubelet
|
||||||
type ConnectionInfo struct {
|
type ConnectionInfo struct {
|
||||||
Scheme string
|
Scheme string
|
||||||
Hostname string
|
Hostname string
|
||||||
Port string
|
Port string
|
||||||
Transport http.RoundTripper
|
Transport http.RoundTripper
|
||||||
|
InsecureSkipTLSVerifyTransport http.RoundTripper
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConnectionInfoGetter provides ConnectionInfo for the kubelet running on a named node
|
// ConnectionInfoGetter provides ConnectionInfo for the kubelet running on a named node
|
||||||
@@ -76,9 +77,28 @@ type ConnectionInfoGetter interface {
|
|||||||
GetConnectionInfo(ctx context.Context, nodeName types.NodeName) (*ConnectionInfo, error)
|
GetConnectionInfo(ctx context.Context, nodeName types.NodeName) (*ConnectionInfo, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeTransport creates a RoundTripper for HTTP Transport.
|
// MakeTransport creates a secure RoundTripper for HTTP Transport.
|
||||||
func MakeTransport(config *KubeletClientConfig) (http.RoundTripper, error) {
|
func MakeTransport(config *KubeletClientConfig) (http.RoundTripper, error) {
|
||||||
tlsConfig, err := transport.TLSConfigFor(config.transportConfig())
|
return makeTransport(config, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeInsecureTransport creates an insecure RoundTripper for HTTP Transport.
|
||||||
|
func MakeInsecureTransport(config *KubeletClientConfig) (http.RoundTripper, error) {
|
||||||
|
return makeTransport(config, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeTransport creates a RoundTripper for HTTP Transport.
|
||||||
|
func makeTransport(config *KubeletClientConfig, insecureSkipTLSVerify bool) (http.RoundTripper, error) {
|
||||||
|
// do the insecureSkipTLSVerify on the pre-transport *before* we go get a potentially cached connection.
|
||||||
|
// transportConfig always produces a new struct pointer.
|
||||||
|
preTLSConfig := config.transportConfig()
|
||||||
|
if insecureSkipTLSVerify && preTLSConfig != nil {
|
||||||
|
preTLSConfig.TLS.Insecure = true
|
||||||
|
preTLSConfig.TLS.CAData = nil
|
||||||
|
preTLSConfig.TLS.CAFile = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConfig, err := transport.TLSConfigFor(preTLSConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -148,6 +168,8 @@ type NodeConnectionInfoGetter struct {
|
|||||||
defaultPort int
|
defaultPort int
|
||||||
// transport is the transport to use to send a request to all kubelets
|
// transport is the transport to use to send a request to all kubelets
|
||||||
transport http.RoundTripper
|
transport http.RoundTripper
|
||||||
|
// insecureSkipTLSVerifyTransport is the transport to use if the kube-apiserver wants to skip verifying the TLS certificate of the kubelet
|
||||||
|
insecureSkipTLSVerifyTransport http.RoundTripper
|
||||||
// preferredAddressTypes specifies the preferred order to use to find a node address
|
// preferredAddressTypes specifies the preferred order to use to find a node address
|
||||||
preferredAddressTypes []v1.NodeAddressType
|
preferredAddressTypes []v1.NodeAddressType
|
||||||
}
|
}
|
||||||
@@ -163,6 +185,10 @@ func NewNodeConnectionInfoGetter(nodes NodeGetter, config KubeletClientConfig) (
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
insecureSkipTLSVerifyTransport, err := MakeInsecureTransport(&config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
types := []v1.NodeAddressType{}
|
types := []v1.NodeAddressType{}
|
||||||
for _, t := range config.PreferredAddressTypes {
|
for _, t := range config.PreferredAddressTypes {
|
||||||
@@ -170,10 +196,11 @@ func NewNodeConnectionInfoGetter(nodes NodeGetter, config KubeletClientConfig) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &NodeConnectionInfoGetter{
|
return &NodeConnectionInfoGetter{
|
||||||
nodes: nodes,
|
nodes: nodes,
|
||||||
scheme: scheme,
|
scheme: scheme,
|
||||||
defaultPort: int(config.Port),
|
defaultPort: int(config.Port),
|
||||||
transport: transport,
|
transport: transport,
|
||||||
|
insecureSkipTLSVerifyTransport: insecureSkipTLSVerifyTransport,
|
||||||
|
|
||||||
preferredAddressTypes: types,
|
preferredAddressTypes: types,
|
||||||
}, nil
|
}, nil
|
||||||
@@ -199,9 +226,10 @@ func (k *NodeConnectionInfoGetter) GetConnectionInfo(ctx context.Context, nodeNa
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &ConnectionInfo{
|
return &ConnectionInfo{
|
||||||
Scheme: k.scheme,
|
Scheme: k.scheme,
|
||||||
Hostname: host,
|
Hostname: host,
|
||||||
Port: strconv.Itoa(port),
|
Port: strconv.Itoa(port),
|
||||||
Transport: k.transport,
|
Transport: k.transport,
|
||||||
|
InsecureSkipTLSVerifyTransport: k.insecureSkipTLSVerifyTransport,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@@ -17,6 +17,12 @@ limitations under the License.
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
restclient "k8s.io/client-go/rest"
|
restclient "k8s.io/client-go/rest"
|
||||||
@@ -63,3 +69,59 @@ func TestMakeTransportValid(t *testing.T) {
|
|||||||
t.Error("rt should not be nil")
|
t.Error("rt should not be nil")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMakeInsecureTransport(t *testing.T) {
|
||||||
|
testServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}))
|
||||||
|
defer testServer.Close()
|
||||||
|
|
||||||
|
testURL, err := url.Parse(testServer.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, portStr, err := net.SplitHostPort(testURL.Host)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
port, err := strconv.ParseUint(portStr, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &KubeletClientConfig{
|
||||||
|
Port: uint(port),
|
||||||
|
EnableHTTPS: true,
|
||||||
|
TLSClientConfig: restclient.TLSClientConfig{
|
||||||
|
CertFile: "../../client/testdata/mycertvalid.cer",
|
||||||
|
// TLS Configuration, only applies if EnableHTTPS is true.
|
||||||
|
KeyFile: "../../client/testdata/mycertvalid.key",
|
||||||
|
// TLS Configuration, only applies if EnableHTTPS is true.
|
||||||
|
CAFile: "../../client/testdata/myCA.cer",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
rt, err := MakeInsecureTransport(config)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Not expecting an error #%v", err)
|
||||||
|
}
|
||||||
|
if rt == nil {
|
||||||
|
t.Error("rt should not be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodGet, testServer.URL, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
response, err := rt.RoundTrip(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if response.StatusCode != http.StatusOK {
|
||||||
|
dump, err := httputil.DumpResponse(response, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Fatal(string(dump))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -19,6 +19,7 @@ go_library(
|
|||||||
"//pkg/apis/core:go_default_library",
|
"//pkg/apis/core:go_default_library",
|
||||||
"//pkg/apis/core/helper/qos:go_default_library",
|
"//pkg/apis/core/helper/qos:go_default_library",
|
||||||
"//pkg/apis/core/validation:go_default_library",
|
"//pkg/apis/core/validation:go_default_library",
|
||||||
|
"//pkg/features:go_default_library",
|
||||||
"//pkg/kubelet/client:go_default_library",
|
"//pkg/kubelet/client:go_default_library",
|
||||||
"//pkg/proxy/util:go_default_library",
|
"//pkg/proxy/util:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||||
@@ -32,6 +33,7 @@ go_library(
|
|||||||
"//staging/src/k8s.io/apiserver/pkg/registry/generic:go_default_library",
|
"//staging/src/k8s.io/apiserver/pkg/registry/generic:go_default_library",
|
||||||
"//staging/src/k8s.io/apiserver/pkg/storage:go_default_library",
|
"//staging/src/k8s.io/apiserver/pkg/storage:go_default_library",
|
||||||
"//staging/src/k8s.io/apiserver/pkg/storage/names:go_default_library",
|
"//staging/src/k8s.io/apiserver/pkg/storage/names:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@@ -17,6 +17,7 @@ go_library(
|
|||||||
"//pkg/apis/core:go_default_library",
|
"//pkg/apis/core:go_default_library",
|
||||||
"//pkg/apis/core/validation:go_default_library",
|
"//pkg/apis/core/validation:go_default_library",
|
||||||
"//pkg/capabilities:go_default_library",
|
"//pkg/capabilities:go_default_library",
|
||||||
|
"//pkg/features:go_default_library",
|
||||||
"//pkg/kubelet/client:go_default_library",
|
"//pkg/kubelet/client:go_default_library",
|
||||||
"//pkg/registry/core/pod:go_default_library",
|
"//pkg/registry/core/pod:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||||
|
@@ -25,8 +25,10 @@ import (
|
|||||||
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
|
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
|
||||||
genericrest "k8s.io/apiserver/pkg/registry/generic/rest"
|
genericrest "k8s.io/apiserver/pkg/registry/generic/rest"
|
||||||
"k8s.io/apiserver/pkg/registry/rest"
|
"k8s.io/apiserver/pkg/registry/rest"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
"k8s.io/kubernetes/pkg/apis/core/validation"
|
"k8s.io/kubernetes/pkg/apis/core/validation"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/client"
|
"k8s.io/kubernetes/pkg/kubelet/client"
|
||||||
"k8s.io/kubernetes/pkg/registry/core/pod"
|
"k8s.io/kubernetes/pkg/registry/core/pod"
|
||||||
)
|
)
|
||||||
@@ -67,6 +69,10 @@ func (r *LogREST) Get(ctx context.Context, name string, opts runtime.Object) (ru
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("invalid options object: %#v", opts)
|
return nil, fmt.Errorf("invalid options object: %#v", opts)
|
||||||
}
|
}
|
||||||
|
if !utilfeature.DefaultFeatureGate.Enabled(features.AllowInsecureBackendProxy) {
|
||||||
|
logOpts.InsecureSkipTLSVerifyBackend = false
|
||||||
|
}
|
||||||
|
|
||||||
if errs := validation.ValidatePodLogOptions(logOpts); len(errs) > 0 {
|
if errs := validation.ValidatePodLogOptions(logOpts); len(errs) > 0 {
|
||||||
return nil, errors.NewInvalid(api.Kind("PodLogOptions"), name, errs)
|
return nil, errors.NewInvalid(api.Kind("PodLogOptions"), name, errs)
|
||||||
}
|
}
|
||||||
|
@@ -37,11 +37,13 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/registry/generic"
|
"k8s.io/apiserver/pkg/registry/generic"
|
||||||
"k8s.io/apiserver/pkg/storage"
|
"k8s.io/apiserver/pkg/storage"
|
||||||
"k8s.io/apiserver/pkg/storage/names"
|
"k8s.io/apiserver/pkg/storage/names"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||||
podutil "k8s.io/kubernetes/pkg/api/pod"
|
podutil "k8s.io/kubernetes/pkg/api/pod"
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
"k8s.io/kubernetes/pkg/apis/core/helper/qos"
|
"k8s.io/kubernetes/pkg/apis/core/helper/qos"
|
||||||
"k8s.io/kubernetes/pkg/apis/core/validation"
|
"k8s.io/kubernetes/pkg/apis/core/validation"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/client"
|
"k8s.io/kubernetes/pkg/kubelet/client"
|
||||||
proxyutil "k8s.io/kubernetes/pkg/proxy/util"
|
proxyutil "k8s.io/kubernetes/pkg/proxy/util"
|
||||||
)
|
)
|
||||||
@@ -382,6 +384,10 @@ func LogLocation(
|
|||||||
Path: fmt.Sprintf("/containerLogs/%s/%s/%s", pod.Namespace, pod.Name, container),
|
Path: fmt.Sprintf("/containerLogs/%s/%s/%s", pod.Namespace, pod.Name, container),
|
||||||
RawQuery: params.Encode(),
|
RawQuery: params.Encode(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if opts.InsecureSkipTLSVerifyBackend && utilfeature.DefaultFeatureGate.Enabled(features.AllowInsecureBackendProxy) {
|
||||||
|
return loc, nodeInfo.InsecureSkipTLSVerifyTransport, nil
|
||||||
|
}
|
||||||
return loc, nodeInfo.Transport, nil
|
return loc, nodeInfo.Transport, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -18,6 +18,7 @@ package pod
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -320,31 +321,56 @@ func (g mockPodGetter) Get(context.Context, string, *metav1.GetOptions) (runtime
|
|||||||
func TestCheckLogLocation(t *testing.T) {
|
func TestCheckLogLocation(t *testing.T) {
|
||||||
ctx := genericapirequest.NewDefaultContext()
|
ctx := genericapirequest.NewDefaultContext()
|
||||||
tcs := []struct {
|
tcs := []struct {
|
||||||
in *api.Pod
|
name string
|
||||||
opts *api.PodLogOptions
|
in *api.Pod
|
||||||
expectedErr error
|
opts *api.PodLogOptions
|
||||||
|
expectedErr error
|
||||||
|
expectedTransport http.RoundTripper
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
in: &api.Pod{
|
name: "simple",
|
||||||
Spec: api.PodSpec{},
|
|
||||||
Status: api.PodStatus{},
|
|
||||||
},
|
|
||||||
opts: &api.PodLogOptions{},
|
|
||||||
expectedErr: errors.NewBadRequest("a container name must be specified for pod test"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
in: &api.Pod{
|
in: &api.Pod{
|
||||||
Spec: api.PodSpec{
|
Spec: api.PodSpec{
|
||||||
Containers: []api.Container{
|
Containers: []api.Container{
|
||||||
{Name: "mycontainer"},
|
{Name: "mycontainer"},
|
||||||
},
|
},
|
||||||
|
NodeName: "foo",
|
||||||
},
|
},
|
||||||
Status: api.PodStatus{},
|
Status: api.PodStatus{},
|
||||||
},
|
},
|
||||||
opts: &api.PodLogOptions{},
|
opts: &api.PodLogOptions{},
|
||||||
expectedErr: nil,
|
expectedErr: nil,
|
||||||
|
expectedTransport: fakeSecureRoundTripper,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name: "insecure",
|
||||||
|
in: &api.Pod{
|
||||||
|
Spec: api.PodSpec{
|
||||||
|
Containers: []api.Container{
|
||||||
|
{Name: "mycontainer"},
|
||||||
|
},
|
||||||
|
NodeName: "foo",
|
||||||
|
},
|
||||||
|
Status: api.PodStatus{},
|
||||||
|
},
|
||||||
|
opts: &api.PodLogOptions{
|
||||||
|
InsecureSkipTLSVerifyBackend: true,
|
||||||
|
},
|
||||||
|
expectedErr: nil,
|
||||||
|
expectedTransport: fakeInsecureRoundTripper,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing container",
|
||||||
|
in: &api.Pod{
|
||||||
|
Spec: api.PodSpec{},
|
||||||
|
Status: api.PodStatus{},
|
||||||
|
},
|
||||||
|
opts: &api.PodLogOptions{},
|
||||||
|
expectedErr: errors.NewBadRequest("a container name must be specified for pod test"),
|
||||||
|
expectedTransport: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "choice of two containers",
|
||||||
in: &api.Pod{
|
in: &api.Pod{
|
||||||
Spec: api.PodSpec{
|
Spec: api.PodSpec{
|
||||||
Containers: []api.Container{
|
Containers: []api.Container{
|
||||||
@@ -354,10 +380,12 @@ func TestCheckLogLocation(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Status: api.PodStatus{},
|
Status: api.PodStatus{},
|
||||||
},
|
},
|
||||||
opts: &api.PodLogOptions{},
|
opts: &api.PodLogOptions{},
|
||||||
expectedErr: errors.NewBadRequest("a container name must be specified for pod test, choose one of: [container1 container2]"),
|
expectedErr: errors.NewBadRequest("a container name must be specified for pod test, choose one of: [container1 container2]"),
|
||||||
|
expectedTransport: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name: "initcontainers",
|
||||||
in: &api.Pod{
|
in: &api.Pod{
|
||||||
Spec: api.PodSpec{
|
Spec: api.PodSpec{
|
||||||
Containers: []api.Container{
|
Containers: []api.Container{
|
||||||
@@ -370,10 +398,12 @@ func TestCheckLogLocation(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Status: api.PodStatus{},
|
Status: api.PodStatus{},
|
||||||
},
|
},
|
||||||
opts: &api.PodLogOptions{},
|
opts: &api.PodLogOptions{},
|
||||||
expectedErr: errors.NewBadRequest("a container name must be specified for pod test, choose one of: [container1 container2] or one of the init containers: [initcontainer1]"),
|
expectedErr: errors.NewBadRequest("a container name must be specified for pod test, choose one of: [container1 container2] or one of the init containers: [initcontainer1]"),
|
||||||
|
expectedTransport: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name: "bad container",
|
||||||
in: &api.Pod{
|
in: &api.Pod{
|
||||||
Spec: api.PodSpec{
|
Spec: api.PodSpec{
|
||||||
Containers: []api.Container{
|
Containers: []api.Container{
|
||||||
@@ -386,30 +416,44 @@ func TestCheckLogLocation(t *testing.T) {
|
|||||||
opts: &api.PodLogOptions{
|
opts: &api.PodLogOptions{
|
||||||
Container: "unknown",
|
Container: "unknown",
|
||||||
},
|
},
|
||||||
expectedErr: errors.NewBadRequest("container unknown is not valid for pod test"),
|
expectedErr: errors.NewBadRequest("container unknown is not valid for pod test"),
|
||||||
|
expectedTransport: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name: "good with two containers",
|
||||||
in: &api.Pod{
|
in: &api.Pod{
|
||||||
Spec: api.PodSpec{
|
Spec: api.PodSpec{
|
||||||
Containers: []api.Container{
|
Containers: []api.Container{
|
||||||
{Name: "container1"},
|
{Name: "container1"},
|
||||||
{Name: "container2"},
|
{Name: "container2"},
|
||||||
},
|
},
|
||||||
|
NodeName: "foo",
|
||||||
},
|
},
|
||||||
Status: api.PodStatus{},
|
Status: api.PodStatus{},
|
||||||
},
|
},
|
||||||
opts: &api.PodLogOptions{
|
opts: &api.PodLogOptions{
|
||||||
Container: "container2",
|
Container: "container2",
|
||||||
},
|
},
|
||||||
expectedErr: nil,
|
expectedErr: nil,
|
||||||
|
expectedTransport: fakeSecureRoundTripper,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range tcs {
|
for _, tc := range tcs {
|
||||||
getter := &mockPodGetter{tc.in}
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
_, _, err := LogLocation(getter, nil, ctx, "test", tc.opts)
|
getter := &mockPodGetter{tc.in}
|
||||||
if !reflect.DeepEqual(err, tc.expectedErr) {
|
connectionGetter := &mockConnectionInfoGetter{&client.ConnectionInfo{
|
||||||
t.Errorf("expected %v, got %v", tc.expectedErr, err)
|
Transport: fakeSecureRoundTripper,
|
||||||
}
|
InsecureSkipTLSVerifyTransport: fakeInsecureRoundTripper,
|
||||||
|
}}
|
||||||
|
|
||||||
|
_, actualTransport, err := LogLocation(getter, connectionGetter, ctx, "test", tc.opts)
|
||||||
|
if !reflect.DeepEqual(err, tc.expectedErr) {
|
||||||
|
t.Errorf("expected %v, got %v", tc.expectedErr, err)
|
||||||
|
}
|
||||||
|
if actualTransport != tc.expectedTransport {
|
||||||
|
t.Errorf("expected %v, got %v", tc.expectedTransport, actualTransport)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -564,3 +608,16 @@ func TestGetPodIP(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type fakeTransport struct {
|
||||||
|
val string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fakeTransport) RoundTrip(*http.Request) (*http.Response, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
fakeSecureRoundTripper = fakeTransport{val: "secure"}
|
||||||
|
fakeInsecureRoundTripper = fakeTransport{val: "insecure"}
|
||||||
|
)
|
||||||
|
@@ -4808,6 +4808,15 @@ type PodLogOptions struct {
|
|||||||
// slightly more or slightly less than the specified limit.
|
// slightly more or slightly less than the specified limit.
|
||||||
// +optional
|
// +optional
|
||||||
LimitBytes *int64 `json:"limitBytes,omitempty" protobuf:"varint,8,opt,name=limitBytes"`
|
LimitBytes *int64 `json:"limitBytes,omitempty" protobuf:"varint,8,opt,name=limitBytes"`
|
||||||
|
|
||||||
|
// insecureSkipTLSVerifyBackend indicates that the apiserver should not confirm the validity of the
|
||||||
|
// serving certificate of the backend it is connecting to. This will make the HTTPS connection between the apiserver
|
||||||
|
// and the backend insecure. This means the apiserver cannot verify the log data it is receiving came from the real
|
||||||
|
// kubelet. If the kubelet is configured to verify the apiserver's TLS credentials, it does not mean the
|
||||||
|
// connection to the real kubelet is vulnerable to a man in the middle attack (e.g. an attacker could not intercept
|
||||||
|
// the actual log data coming from the real kubelet).
|
||||||
|
// +optional
|
||||||
|
InsecureSkipTLSVerifyBackend bool `json:"insecureSkipTLSVerifyBackend,omitempty" protobuf:"varint,9,opt,name=insecureSkipTLSVerifyBackend"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
@@ -89,6 +89,7 @@ filegroup(
|
|||||||
":package-srcs",
|
":package-srcs",
|
||||||
"//test/integration/apiserver/admissionwebhook:all-srcs",
|
"//test/integration/apiserver/admissionwebhook:all-srcs",
|
||||||
"//test/integration/apiserver/apply:all-srcs",
|
"//test/integration/apiserver/apply:all-srcs",
|
||||||
|
"//test/integration/apiserver/podlogs:all-srcs",
|
||||||
],
|
],
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
)
|
)
|
||||||
|
30
test/integration/apiserver/podlogs/BUILD
Normal file
30
test/integration/apiserver/podlogs/BUILD
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_test")
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = [
|
||||||
|
"main_test.go",
|
||||||
|
"podlogs_test.go",
|
||||||
|
],
|
||||||
|
tags = ["integration"],
|
||||||
|
deps = [
|
||||||
|
"//cmd/kube-apiserver/app/options:go_default_library",
|
||||||
|
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
|
"//test/integration/framework:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
27
test/integration/apiserver/podlogs/main_test.go
Normal file
27
test/integration/apiserver/podlogs/main_test.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
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 podlogs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/test/integration/framework"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
framework.EtcdMain(m.Run)
|
||||||
|
}
|
165
test/integration/apiserver/podlogs/podlogs_test.go
Normal file
165
test/integration/apiserver/podlogs/podlogs_test.go
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
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 podlogs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/kubernetes/cmd/kube-apiserver/app/options"
|
||||||
|
"k8s.io/kubernetes/test/integration/framework"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInsecurePodLogs(t *testing.T) {
|
||||||
|
stopCh := make(chan struct{})
|
||||||
|
defer close(stopCh)
|
||||||
|
clientSet, _ := framework.StartTestServer(t, stopCh, framework.TestServerSetup{
|
||||||
|
ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
|
||||||
|
opts.GenericServerRunOptions.MaxRequestBodyBytes = 1024 * 1024
|
||||||
|
// I have no idea what this cert is, but it doesn't matter, we just want something that always fails validation
|
||||||
|
opts.KubeletConfig.CAData = []byte(` -----BEGIN CERTIFICATE-----
|
||||||
|
MIIDMDCCAhigAwIBAgIIHNPD7sig7YIwDQYJKoZIhvcNAQELBQAwNjESMBAGA1UE
|
||||||
|
CxMJb3BlbnNoaWZ0MSAwHgYDVQQDExdhZG1pbi1rdWJlY29uZmlnLXNpZ25lcjAe
|
||||||
|
Fw0xOTA1MzAxNTA3MzlaFw0yOTA1MjcxNTA3MzlaMDYxEjAQBgNVBAsTCW9wZW5z
|
||||||
|
aGlmdDEgMB4GA1UEAxMXYWRtaW4ta3ViZWNvbmZpZy1zaWduZXIwggEiMA0GCSqG
|
||||||
|
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQD0dHk23lHRcuq06FzYDOl9J9+s8pnGxqA3
|
||||||
|
IPcARI6ag/98aYe3ENwAB5e1i7AU2F2WiDZgj444w374XLdVgIK8zgQEm9yoqrlc
|
||||||
|
+/ayO7ceKklrKHOMwh63LvGLEOqzhol2nFmBhXAZt+HyIoZHXN0IqlA92196+Dml
|
||||||
|
0WOn1F4ce6JbAtEceFHPgLeI7KFmVaPz2796pBXh23ii6r7WvV1Rn9MKlMSBJQR4
|
||||||
|
0LZzu9/j+GdnFXewdLAAMfgPzwEqv6h3PzvtUCjgdraHEm8Rs7s15S3PUmLK4RQS
|
||||||
|
PsThx5BhJEGd/W6EzQ3BKoQfochhu3mnAQtW1J07CullySQ5Gg9fAgMBAAGjQjBA
|
||||||
|
MA4GA1UdDwEB/wQEAwICpDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQkTaaw
|
||||||
|
YJSZ5k2Wd+OsM4GFMTGdqzANBgkqhkiG9w0BAQsFAAOCAQEAHK7+zBZPLqK+f9DT
|
||||||
|
UEnpwRmZ0aeGS4YgbGIkqpjxJymVOwkRd5A1wslvVfGZ6yOQthF6KlCmqnPyJJMR
|
||||||
|
I7FHw8j0h2ci90fEQ6IS90Y/ZJXkcgiK9Ncwa35GFGs8QrBxN4leGhtm84BnnBHN
|
||||||
|
cTWpa4zcBwru0CRG7iHc66VX16X8jHB1iFeZ5W/FgY4MsE+G1Vze4mCXSPVI4BZ2
|
||||||
|
/qlAgogjBivvSwQ9SFuCszg7IPjvT2ksm+Cf+8eT4YBqW41F85vBGR+FYK14yIla
|
||||||
|
Bgqc+dJN9xS9Ah5gLiGQJ6C4niUA11piCpvMsy+j/LQ1Erx47KMar5fuMXYk7iPq
|
||||||
|
1vqIwg==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
`)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
fakeKubeletServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.Write([]byte("fake-log"))
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}))
|
||||||
|
defer fakeKubeletServer.Close()
|
||||||
|
|
||||||
|
fakeKubeletURL, err := url.Parse(fakeKubeletServer.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
fakeKubeletHost, fakeKubeletPortStr, err := net.SplitHostPort(fakeKubeletURL.Host)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
fakeKubeletPort, err := strconv.ParseUint(fakeKubeletPortStr, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
node, err := clientSet.CoreV1().Nodes().Create(&corev1.Node{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "fake"},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
node.Status = corev1.NodeStatus{
|
||||||
|
Addresses: []corev1.NodeAddress{
|
||||||
|
{
|
||||||
|
Type: corev1.NodeExternalIP,
|
||||||
|
Address: fakeKubeletHost,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
DaemonEndpoints: corev1.NodeDaemonEndpoints{
|
||||||
|
KubeletEndpoint: corev1.DaemonEndpoint{
|
||||||
|
Port: int32(fakeKubeletPort),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
node, err = clientSet.CoreV1().Nodes().UpdateStatus(node)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = clientSet.CoreV1().Namespaces().Create(&corev1.Namespace{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "ns"},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = clientSet.CoreV1().ServiceAccounts("ns").Create(&corev1.ServiceAccount{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: "ns"},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
falseRef := false
|
||||||
|
pod, err := clientSet.CoreV1().Pods("ns").Create(&corev1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "test-pod", Namespace: "ns"},
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "foo",
|
||||||
|
Image: "some/image:latest",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NodeName: node.Name,
|
||||||
|
AutomountServiceAccountToken: &falseRef,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
insecureResult := clientSet.CoreV1().Pods("ns").GetLogs(pod.Name, &corev1.PodLogOptions{InsecureSkipTLSVerifyBackend: true}).Do()
|
||||||
|
if err := insecureResult.Error(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
insecureStatusCode := 0
|
||||||
|
insecureResult.StatusCode(&insecureStatusCode)
|
||||||
|
if insecureStatusCode != http.StatusOK {
|
||||||
|
t.Fatal(insecureStatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
secureResult := clientSet.CoreV1().Pods("ns").GetLogs(pod.Name, &corev1.PodLogOptions{}).Do()
|
||||||
|
if err := secureResult.Error(); err == nil || !strings.Contains(err.Error(), "x509: certificate signed by unknown authority") {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
secureStatusCode := 0
|
||||||
|
secureResult.StatusCode(&secureStatusCode)
|
||||||
|
if secureStatusCode == http.StatusOK {
|
||||||
|
raw, rawErr := secureResult.Raw()
|
||||||
|
if rawErr != nil {
|
||||||
|
t.Log(rawErr)
|
||||||
|
}
|
||||||
|
t.Log(string(raw))
|
||||||
|
t.Fatal(secureStatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user