mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-10 12:32:03 +00:00
Merge pull request #83419 from deads2k/insecure-backend-proxy
Insecure backend proxy
This commit is contained in:
commit
cb19b56831
7
api/openapi-spec/swagger.json
generated
7
api/openapi-spec/swagger.json
generated
@ -25327,6 +25327,13 @@
|
||||
"type": "boolean",
|
||||
"uniqueItems": true
|
||||
},
|
||||
{
|
||||
"description": "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).",
|
||||
"in": "query",
|
||||
"name": "insecureSkipTLSVerifyBackend",
|
||||
"type": "boolean",
|
||||
"uniqueItems": true
|
||||
},
|
||||
{
|
||||
"description": "If set, the number of bytes to read from the server before terminating the log output. This may not display a complete final line of logging, and may return slightly more or slightly less than the specified limit.",
|
||||
"in": "query",
|
||||
|
@ -4203,6 +4203,15 @@ type PodLogOptions struct {
|
||||
// log output. This may not display a complete final line of logging, and may return
|
||||
// slightly more or slightly less than the specified limit.
|
||||
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
|
||||
|
2
pkg/apis/core/v1/zz_generated.conversion.go
generated
2
pkg/apis/core/v1/zz_generated.conversion.go
generated
@ -5665,6 +5665,7 @@ func autoConvert_v1_PodLogOptions_To_core_PodLogOptions(in *v1.PodLogOptions, ou
|
||||
out.Timestamps = in.Timestamps
|
||||
out.TailLines = (*int64)(unsafe.Pointer(in.TailLines))
|
||||
out.LimitBytes = (*int64)(unsafe.Pointer(in.LimitBytes))
|
||||
out.InsecureSkipTLSVerifyBackend = in.InsecureSkipTLSVerifyBackend
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -5682,6 +5683,7 @@ func autoConvert_core_PodLogOptions_To_v1_PodLogOptions(in *core.PodLogOptions,
|
||||
out.Timestamps = in.Timestamps
|
||||
out.TailLines = (*int64)(unsafe.Pointer(in.TailLines))
|
||||
out.LimitBytes = (*int64)(unsafe.Pointer(in.LimitBytes))
|
||||
out.InsecureSkipTLSVerifyBackend = in.InsecureSkipTLSVerifyBackend
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -484,6 +484,12 @@ const (
|
||||
//
|
||||
// Enables the startupProbe in kubelet worker.
|
||||
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() {
|
||||
@ -563,6 +569,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
|
||||
EndpointSlice: {Default: false, PreRelease: featuregate.Alpha},
|
||||
EvenPodsSpread: {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
|
||||
// unintentionally on either side:
|
||||
|
@ -65,10 +65,11 @@ type KubeletClientConfig struct {
|
||||
|
||||
// ConnectionInfo provides the information needed to connect to a kubelet
|
||||
type ConnectionInfo struct {
|
||||
Scheme string
|
||||
Hostname string
|
||||
Port string
|
||||
Transport http.RoundTripper
|
||||
Scheme string
|
||||
Hostname string
|
||||
Port string
|
||||
Transport http.RoundTripper
|
||||
InsecureSkipTLSVerifyTransport http.RoundTripper
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// MakeTransport creates a RoundTripper for HTTP Transport.
|
||||
// MakeTransport creates a secure RoundTripper for HTTP Transport.
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
@ -148,6 +168,8 @@ type NodeConnectionInfoGetter struct {
|
||||
defaultPort int
|
||||
// transport is the transport to use to send a request to all kubelets
|
||||
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 []v1.NodeAddressType
|
||||
}
|
||||
@ -163,6 +185,10 @@ func NewNodeConnectionInfoGetter(nodes NodeGetter, config KubeletClientConfig) (
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
insecureSkipTLSVerifyTransport, err := MakeInsecureTransport(&config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
types := []v1.NodeAddressType{}
|
||||
for _, t := range config.PreferredAddressTypes {
|
||||
@ -170,10 +196,11 @@ func NewNodeConnectionInfoGetter(nodes NodeGetter, config KubeletClientConfig) (
|
||||
}
|
||||
|
||||
return &NodeConnectionInfoGetter{
|
||||
nodes: nodes,
|
||||
scheme: scheme,
|
||||
defaultPort: int(config.Port),
|
||||
transport: transport,
|
||||
nodes: nodes,
|
||||
scheme: scheme,
|
||||
defaultPort: int(config.Port),
|
||||
transport: transport,
|
||||
insecureSkipTLSVerifyTransport: insecureSkipTLSVerifyTransport,
|
||||
|
||||
preferredAddressTypes: types,
|
||||
}, nil
|
||||
@ -199,9 +226,10 @@ func (k *NodeConnectionInfoGetter) GetConnectionInfo(ctx context.Context, nodeNa
|
||||
}
|
||||
|
||||
return &ConnectionInfo{
|
||||
Scheme: k.scheme,
|
||||
Hostname: host,
|
||||
Port: strconv.Itoa(port),
|
||||
Transport: k.transport,
|
||||
Scheme: k.scheme,
|
||||
Hostname: host,
|
||||
Port: strconv.Itoa(port),
|
||||
Transport: k.transport,
|
||||
InsecureSkipTLSVerifyTransport: k.insecureSkipTLSVerifyTransport,
|
||||
}, nil
|
||||
}
|
||||
|
@ -17,6 +17,12 @@ limitations under the License.
|
||||
package client
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
restclient "k8s.io/client-go/rest"
|
||||
@ -63,3 +69,59 @@ func TestMakeTransportValid(t *testing.T) {
|
||||
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/helper/qos:go_default_library",
|
||||
"//pkg/apis/core/validation:go_default_library",
|
||||
"//pkg/features:go_default_library",
|
||||
"//pkg/kubelet/client:go_default_library",
|
||||
"//pkg/proxy/util: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/storage: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/validation:go_default_library",
|
||||
"//pkg/capabilities:go_default_library",
|
||||
"//pkg/features:go_default_library",
|
||||
"//pkg/kubelet/client:go_default_library",
|
||||
"//pkg/registry/core/pod: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"
|
||||
genericrest "k8s.io/apiserver/pkg/registry/generic/rest"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/apis/core/validation"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
"k8s.io/kubernetes/pkg/kubelet/client"
|
||||
"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 {
|
||||
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 {
|
||||
return nil, errors.NewInvalid(api.Kind("PodLogOptions"), name, errs)
|
||||
}
|
||||
|
@ -37,11 +37,13 @@ import (
|
||||
"k8s.io/apiserver/pkg/registry/generic"
|
||||
"k8s.io/apiserver/pkg/storage"
|
||||
"k8s.io/apiserver/pkg/storage/names"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
podutil "k8s.io/kubernetes/pkg/api/pod"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/apis/core/helper/qos"
|
||||
"k8s.io/kubernetes/pkg/apis/core/validation"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
"k8s.io/kubernetes/pkg/kubelet/client"
|
||||
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),
|
||||
RawQuery: params.Encode(),
|
||||
}
|
||||
|
||||
if opts.InsecureSkipTLSVerifyBackend && utilfeature.DefaultFeatureGate.Enabled(features.AllowInsecureBackendProxy) {
|
||||
return loc, nodeInfo.InsecureSkipTLSVerifyTransport, nil
|
||||
}
|
||||
return loc, nodeInfo.Transport, nil
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@ package pod
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"testing"
|
||||
@ -320,31 +321,56 @@ func (g mockPodGetter) Get(context.Context, string, *metav1.GetOptions) (runtime
|
||||
func TestCheckLogLocation(t *testing.T) {
|
||||
ctx := genericapirequest.NewDefaultContext()
|
||||
tcs := []struct {
|
||||
in *api.Pod
|
||||
opts *api.PodLogOptions
|
||||
expectedErr error
|
||||
name string
|
||||
in *api.Pod
|
||||
opts *api.PodLogOptions
|
||||
expectedErr error
|
||||
expectedTransport http.RoundTripper
|
||||
}{
|
||||
{
|
||||
in: &api.Pod{
|
||||
Spec: api.PodSpec{},
|
||||
Status: api.PodStatus{},
|
||||
},
|
||||
opts: &api.PodLogOptions{},
|
||||
expectedErr: errors.NewBadRequest("a container name must be specified for pod test"),
|
||||
},
|
||||
{
|
||||
name: "simple",
|
||||
in: &api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{Name: "mycontainer"},
|
||||
},
|
||||
NodeName: "foo",
|
||||
},
|
||||
Status: api.PodStatus{},
|
||||
},
|
||||
opts: &api.PodLogOptions{},
|
||||
expectedErr: nil,
|
||||
opts: &api.PodLogOptions{},
|
||||
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{
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
@ -354,10 +380,12 @@ func TestCheckLogLocation(t *testing.T) {
|
||||
},
|
||||
Status: api.PodStatus{},
|
||||
},
|
||||
opts: &api.PodLogOptions{},
|
||||
expectedErr: errors.NewBadRequest("a container name must be specified for pod test, choose one of: [container1 container2]"),
|
||||
opts: &api.PodLogOptions{},
|
||||
expectedErr: errors.NewBadRequest("a container name must be specified for pod test, choose one of: [container1 container2]"),
|
||||
expectedTransport: nil,
|
||||
},
|
||||
{
|
||||
name: "initcontainers",
|
||||
in: &api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
@ -370,10 +398,12 @@ func TestCheckLogLocation(t *testing.T) {
|
||||
},
|
||||
Status: api.PodStatus{},
|
||||
},
|
||||
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]"),
|
||||
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]"),
|
||||
expectedTransport: nil,
|
||||
},
|
||||
{
|
||||
name: "bad container",
|
||||
in: &api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
@ -386,30 +416,44 @@ func TestCheckLogLocation(t *testing.T) {
|
||||
opts: &api.PodLogOptions{
|
||||
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{
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{Name: "container1"},
|
||||
{Name: "container2"},
|
||||
},
|
||||
NodeName: "foo",
|
||||
},
|
||||
Status: api.PodStatus{},
|
||||
},
|
||||
opts: &api.PodLogOptions{
|
||||
Container: "container2",
|
||||
},
|
||||
expectedErr: nil,
|
||||
expectedErr: nil,
|
||||
expectedTransport: fakeSecureRoundTripper,
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
getter := &mockPodGetter{tc.in}
|
||||
_, _, err := LogLocation(getter, nil, ctx, "test", tc.opts)
|
||||
if !reflect.DeepEqual(err, tc.expectedErr) {
|
||||
t.Errorf("expected %v, got %v", tc.expectedErr, err)
|
||||
}
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
getter := &mockPodGetter{tc.in}
|
||||
connectionGetter := &mockConnectionInfoGetter{&client.ConnectionInfo{
|
||||
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"}
|
||||
)
|
||||
|
1731
staging/src/k8s.io/api/core/v1/generated.pb.go
generated
1731
staging/src/k8s.io/api/core/v1/generated.pb.go
generated
File diff suppressed because it is too large
Load Diff
@ -3145,6 +3145,15 @@ message PodLogOptions {
|
||||
// slightly more or slightly less than the specified limit.
|
||||
// +optional
|
||||
optional int64 limitBytes = 8;
|
||||
|
||||
// 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
|
||||
optional bool insecureSkipTLSVerifyBackend = 9;
|
||||
}
|
||||
|
||||
// PodPortForwardOptions is the query options to a Pod's port forward call
|
||||
|
@ -4808,6 +4808,15 @@ type PodLogOptions struct {
|
||||
// slightly more or slightly less than the specified limit.
|
||||
// +optional
|
||||
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
|
||||
|
@ -1528,15 +1528,16 @@ func (PodList) SwaggerDoc() map[string]string {
|
||||
}
|
||||
|
||||
var map_PodLogOptions = map[string]string{
|
||||
"": "PodLogOptions is the query options for a Pod's logs REST call.",
|
||||
"container": "The container for which to stream logs. Defaults to only container if there is one container in the pod.",
|
||||
"follow": "Follow the log stream of the pod. Defaults to false.",
|
||||
"previous": "Return previous terminated container logs. Defaults to false.",
|
||||
"sinceSeconds": "A relative time in seconds before the current time from which to show logs. If this value precedes the time a pod was started, only logs since the pod start will be returned. If this value is in the future, no logs will be returned. Only one of sinceSeconds or sinceTime may be specified.",
|
||||
"sinceTime": "An RFC3339 timestamp from which to show logs. If this value precedes the time a pod was started, only logs since the pod start will be returned. If this value is in the future, no logs will be returned. Only one of sinceSeconds or sinceTime may be specified.",
|
||||
"timestamps": "If true, add an RFC3339 or RFC3339Nano timestamp at the beginning of every line of log output. Defaults to false.",
|
||||
"tailLines": "If set, the number of lines from the end of the logs to show. If not specified, logs are shown from the creation of the container or sinceSeconds or sinceTime",
|
||||
"limitBytes": "If set, the number of bytes to read from the server before terminating the log output. This may not display a complete final line of logging, and may return slightly more or slightly less than the specified limit.",
|
||||
"": "PodLogOptions is the query options for a Pod's logs REST call.",
|
||||
"container": "The container for which to stream logs. Defaults to only container if there is one container in the pod.",
|
||||
"follow": "Follow the log stream of the pod. Defaults to false.",
|
||||
"previous": "Return previous terminated container logs. Defaults to false.",
|
||||
"sinceSeconds": "A relative time in seconds before the current time from which to show logs. If this value precedes the time a pod was started, only logs since the pod start will be returned. If this value is in the future, no logs will be returned. Only one of sinceSeconds or sinceTime may be specified.",
|
||||
"sinceTime": "An RFC3339 timestamp from which to show logs. If this value precedes the time a pod was started, only logs since the pod start will be returned. If this value is in the future, no logs will be returned. Only one of sinceSeconds or sinceTime may be specified.",
|
||||
"timestamps": "If true, add an RFC3339 or RFC3339Nano timestamp at the beginning of every line of log output. Defaults to false.",
|
||||
"tailLines": "If set, the number of lines from the end of the logs to show. If not specified, logs are shown from the creation of the container or sinceSeconds or sinceTime",
|
||||
"limitBytes": "If set, the number of bytes to read from the server before terminating the log output. This may not display a complete final line of logging, and may return slightly more or slightly less than the specified limit.",
|
||||
"insecureSkipTLSVerifyBackend": "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).",
|
||||
}
|
||||
|
||||
func (PodLogOptions) SwaggerDoc() map[string]string {
|
||||
|
@ -6,5 +6,6 @@
|
||||
"sinceSeconds": 1002466899136229878,
|
||||
"timestamps": true,
|
||||
"tailLines": -6357999603795826160,
|
||||
"limitBytes": 5323465663502687351
|
||||
"limitBytes": 5323465663502687351,
|
||||
"insecureSkipTLSVerifyBackend": true
|
||||
}
|
Binary file not shown.
@ -1,6 +1,7 @@
|
||||
apiVersion: v1
|
||||
container: "2"
|
||||
follow: true
|
||||
insecureSkipTLSVerifyBackend: true
|
||||
kind: PodLogOptions
|
||||
limitBytes: 5323465663502687351
|
||||
sinceSeconds: 1002466899136229878
|
||||
|
BIN
staging/src/k8s.io/api/testdata/v1.15.0/core.v1.PodLogOptions.after_roundtrip.pb
vendored
Normal file
BIN
staging/src/k8s.io/api/testdata/v1.15.0/core.v1.PodLogOptions.after_roundtrip.pb
vendored
Normal file
Binary file not shown.
BIN
staging/src/k8s.io/api/testdata/v1.16.0/core.v1.PodLogOptions.after_roundtrip.pb
vendored
Normal file
BIN
staging/src/k8s.io/api/testdata/v1.16.0/core.v1.PodLogOptions.after_roundtrip.pb
vendored
Normal file
Binary file not shown.
@ -89,6 +89,7 @@ filegroup(
|
||||
":package-srcs",
|
||||
"//test/integration/apiserver/admissionwebhook:all-srcs",
|
||||
"//test/integration/apiserver/apply:all-srcs",
|
||||
"//test/integration/apiserver/podlogs:all-srcs",
|
||||
],
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user