Don't include user data in CRI streaming redirect URLs

parent 330c9227
...@@ -606,7 +606,7 @@ func (s *Server) getAttach(request *restful.Request, response *restful.Response) ...@@ -606,7 +606,7 @@ func (s *Server) getAttach(request *restful.Request, response *restful.Response)
podFullName := kubecontainer.GetPodFullName(pod) podFullName := kubecontainer.GetPodFullName(pod)
redirect, err := s.host.GetAttach(podFullName, params.podUID, params.containerName, *streamOpts) redirect, err := s.host.GetAttach(podFullName, params.podUID, params.containerName, *streamOpts)
if err != nil { if err != nil {
response.WriteError(streaming.HTTPStatus(err), err) streaming.WriteError(err, response.ResponseWriter)
return return
} }
if redirect != nil { if redirect != nil {
...@@ -644,7 +644,7 @@ func (s *Server) getExec(request *restful.Request, response *restful.Response) { ...@@ -644,7 +644,7 @@ func (s *Server) getExec(request *restful.Request, response *restful.Response) {
podFullName := kubecontainer.GetPodFullName(pod) podFullName := kubecontainer.GetPodFullName(pod)
redirect, err := s.host.GetExec(podFullName, params.podUID, params.containerName, params.cmd, *streamOpts) redirect, err := s.host.GetExec(podFullName, params.podUID, params.containerName, params.cmd, *streamOpts)
if err != nil { if err != nil {
response.WriteError(streaming.HTTPStatus(err), err) streaming.WriteError(err, response.ResponseWriter)
return return
} }
if redirect != nil { if redirect != nil {
...@@ -714,7 +714,7 @@ func (s *Server) getPortForward(request *restful.Request, response *restful.Resp ...@@ -714,7 +714,7 @@ func (s *Server) getPortForward(request *restful.Request, response *restful.Resp
redirect, err := s.host.GetPortForward(pod.Name, pod.Namespace, pod.UID) redirect, err := s.host.GetPortForward(pod.Name, pod.Namespace, pod.UID)
if err != nil { if err != nil {
response.WriteError(streaming.HTTPStatus(err), err) streaming.WriteError(err, response.ResponseWriter)
return return
} }
if redirect != nil { if redirect != nil {
......
...@@ -12,14 +12,15 @@ go_library( ...@@ -12,14 +12,15 @@ go_library(
name = "go_default_library", name = "go_default_library",
srcs = [ srcs = [
"errors.go", "errors.go",
"request_cache.go",
"server.go", "server.go",
], ],
tags = ["automanaged"], tags = ["automanaged"],
deps = [ deps = [
"//pkg/api:go_default_library",
"//pkg/kubelet/api/v1alpha1/runtime:go_default_library", "//pkg/kubelet/api/v1alpha1/runtime:go_default_library",
"//pkg/kubelet/server/portforward:go_default_library", "//pkg/kubelet/server/portforward:go_default_library",
"//pkg/kubelet/server/remotecommand:go_default_library", "//pkg/kubelet/server/remotecommand:go_default_library",
"//pkg/util/clock:go_default_library",
"//pkg/util/term:go_default_library", "//pkg/util/term:go_default_library",
"//vendor:github.com/emicklei/go-restful", "//vendor:github.com/emicklei/go-restful",
"//vendor:google.golang.org/grpc", "//vendor:google.golang.org/grpc",
...@@ -30,7 +31,10 @@ go_library( ...@@ -30,7 +31,10 @@ go_library(
go_test( go_test(
name = "go_default_test", name = "go_default_test",
srcs = ["server_test.go"], srcs = [
"request_cache_test.go",
"server_test.go",
],
library = ":go_default_library", library = ":go_default_library",
tags = ["automanaged"], tags = ["automanaged"],
deps = [ deps = [
...@@ -43,6 +47,7 @@ go_test( ...@@ -43,6 +47,7 @@ go_test(
"//vendor:github.com/stretchr/testify/assert", "//vendor:github.com/stretchr/testify/assert",
"//vendor:github.com/stretchr/testify/require", "//vendor:github.com/stretchr/testify/require",
"//vendor:k8s.io/client-go/pkg/api", "//vendor:k8s.io/client-go/pkg/api",
"//vendor:k8s.io/client-go/pkg/util/clock",
], ],
) )
......
...@@ -19,6 +19,7 @@ package streaming ...@@ -19,6 +19,7 @@ package streaming
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"strconv"
"time" "time"
"google.golang.org/grpc" "google.golang.org/grpc"
...@@ -33,12 +34,27 @@ func ErrorTimeout(op string, timeout time.Duration) error { ...@@ -33,12 +34,27 @@ func ErrorTimeout(op string, timeout time.Duration) error {
return grpc.Errorf(codes.DeadlineExceeded, fmt.Sprintf("%s timed out after %s", op, timeout.String())) return grpc.Errorf(codes.DeadlineExceeded, fmt.Sprintf("%s timed out after %s", op, timeout.String()))
} }
// Translates a CRI streaming error into an HTTP status code. // The error returned when the maximum number of in-flight requests is exceeded.
func HTTPStatus(err error) int { func ErrorTooManyInFlight() error {
return grpc.Errorf(codes.ResourceExhausted, "maximum number of in-flight requests exceeded")
}
// Translates a CRI streaming error into an appropriate HTTP response.
func WriteError(err error, w http.ResponseWriter) error {
var status int
switch grpc.Code(err) { switch grpc.Code(err) {
case codes.NotFound: case codes.NotFound:
return http.StatusNotFound status = http.StatusNotFound
case codes.ResourceExhausted:
// We only expect to hit this if there is a DoS, so we just wait the full TTL.
// If this is ever hit in steady-state operations, consider increasing the MaxInFlight requests,
// or plumbing through the time to next expiration.
w.Header().Set("Retry-After", strconv.Itoa(int(CacheTTL.Seconds())))
status = http.StatusTooManyRequests
default: default:
return http.StatusInternalServerError status = http.StatusInternalServerError
} }
w.WriteHeader(status)
_, writeErr := w.Write([]byte(err.Error()))
return writeErr
} }
/*
Copyright 2016 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 streaming
import (
"container/list"
"crypto/rand"
"encoding/base64"
"fmt"
"math"
"sync"
"time"
"k8s.io/kubernetes/pkg/util/clock"
)
var (
// Timeout after which tokens become invalid.
CacheTTL = 1 * time.Minute
// The maximum number of in-flight requests to allow.
MaxInFlight = 1000
// Length of the random base64 encoded token identifying the request.
TokenLen = 8
)
// requestCache caches streaming (exec/attach/port-forward) requests and generates a single-use
// random token for their retrieval. The requestCache is used for building streaming URLs without
// the need to encode every request parameter in the URL.
type requestCache struct {
// clock is used to obtain the current time
clock clock.Clock
// tokens maps the generate token to the request for fast retrieval.
tokens map[string]*list.Element
// ll maintains an age-ordered request list for faster garbage collection of expired requests.
ll *list.List
lock sync.Mutex
}
// Type representing an *ExecRequest, *AttachRequest, or *PortForwardRequest.
type request interface{}
type cacheEntry struct {
token string
req request
expireTime time.Time
}
func newRequestCache() *requestCache {
return &requestCache{
clock: clock.RealClock{},
ll: list.New(),
tokens: make(map[string]*list.Element),
}
}
// Insert the given request into the cache and returns the token used for fetching it out.
func (c *requestCache) Insert(req request) (token string, err error) {
c.lock.Lock()
defer c.lock.Unlock()
// Remove expired entries.
c.gc()
// If the cache is full, reject the request.
if c.ll.Len() == MaxInFlight {
return "", ErrorTooManyInFlight()
}
token, err = c.uniqueToken()
if err != nil {
return "", err
}
ele := c.ll.PushFront(&cacheEntry{token, req, c.clock.Now().Add(CacheTTL)})
c.tokens[token] = ele
return token, nil
}
// Consume the token (remove it from the cache) and return the cached request, if found.
func (c *requestCache) Consume(token string) (req request, found bool) {
c.lock.Lock()
defer c.lock.Unlock()
ele, ok := c.tokens[token]
if !ok {
return nil, false
}
c.ll.Remove(ele)
delete(c.tokens, token)
entry := ele.Value.(*cacheEntry)
if c.clock.Now().After(entry.expireTime) {
// Entry already expired.
return nil, false
}
return entry.req, true
}
// uniqueToken generates a random URL-safe token and ensures uniqueness.
func (c *requestCache) uniqueToken() (string, error) {
const maxTries = 10
// Number of bytes to be TokenLen when base64 encoded.
tokenSize := math.Ceil(float64(TokenLen) * 6 / 8)
rawToken := make([]byte, int(tokenSize))
for i := 0; i < maxTries; i++ {
if _, err := rand.Read(rawToken); err != nil {
return "", err
}
encoded := base64.RawURLEncoding.EncodeToString(rawToken)
token := encoded[:TokenLen]
// If it's unique, return it. Otherwise retry.
if _, exists := c.tokens[encoded]; !exists {
return token, nil
}
}
return "", fmt.Errorf("failed to generate unique token")
}
// Must be write-locked prior to calling.
func (c *requestCache) gc() {
now := c.clock.Now()
for c.ll.Len() > 0 {
oldest := c.ll.Back()
entry := oldest.Value.(*cacheEntry)
if !now.After(entry.expireTime) {
return
}
// Oldest value is expired; remove it.
c.ll.Remove(oldest)
delete(c.tokens, entry.token)
}
}
/*
Copyright 2016 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 streaming
import (
"net/http"
"net/http/httptest"
"strconv"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/client-go/pkg/util/clock"
)
func TestInsert(t *testing.T) {
c, _ := newTestCache()
// Insert normal
oldestTok, err := c.Insert(nextRequest())
require.NoError(t, err)
assert.Len(t, oldestTok, TokenLen)
assertCacheSize(t, c, 1)
// Insert until full
for i := 0; i < MaxInFlight-2; i++ {
tok, err := c.Insert(nextRequest())
require.NoError(t, err)
assert.Len(t, tok, TokenLen)
}
assertCacheSize(t, c, MaxInFlight-1)
newestReq := nextRequest()
newestTok, err := c.Insert(newestReq)
require.NoError(t, err)
assert.Len(t, newestTok, TokenLen)
assertCacheSize(t, c, MaxInFlight)
require.Contains(t, c.tokens, oldestTok, "oldest request should still be cached")
// Consume newest token.
req, ok := c.Consume(newestTok)
assert.True(t, ok, "newest request should still be cached")
assert.Equal(t, newestReq, req)
require.Contains(t, c.tokens, oldestTok, "oldest request should still be cached")
// Insert again (still full)
tok, err := c.Insert(nextRequest())
require.NoError(t, err)
assert.Len(t, tok, TokenLen)
assertCacheSize(t, c, MaxInFlight)
// Insert again (should evict)
_, err = c.Insert(nextRequest())
assert.Error(t, err, "should reject further requests")
errResponse := httptest.NewRecorder()
require.NoError(t, WriteError(err, errResponse))
assert.Equal(t, errResponse.Code, http.StatusTooManyRequests)
assert.Equal(t, strconv.Itoa(int(CacheTTL.Seconds())), errResponse.HeaderMap.Get("Retry-After"))
assertCacheSize(t, c, MaxInFlight)
_, ok = c.Consume(oldestTok)
assert.True(t, ok, "oldest request should be valid")
}
func TestConsume(t *testing.T) {
c, clock := newTestCache()
{ // Insert & consume.
req := nextRequest()
tok, err := c.Insert(req)
require.NoError(t, err)
assertCacheSize(t, c, 1)
cachedReq, ok := c.Consume(tok)
assert.True(t, ok)
assert.Equal(t, req, cachedReq)
assertCacheSize(t, c, 0)
}
{ // Insert & consume out of order
req1 := nextRequest()
tok1, err := c.Insert(req1)
require.NoError(t, err)
assertCacheSize(t, c, 1)
req2 := nextRequest()
tok2, err := c.Insert(req2)
require.NoError(t, err)
assertCacheSize(t, c, 2)
cachedReq2, ok := c.Consume(tok2)
assert.True(t, ok)
assert.Equal(t, req2, cachedReq2)
assertCacheSize(t, c, 1)
cachedReq1, ok := c.Consume(tok1)
assert.True(t, ok)
assert.Equal(t, req1, cachedReq1)
assertCacheSize(t, c, 0)
}
{ // Consume a second time
req := nextRequest()
tok, err := c.Insert(req)
require.NoError(t, err)
assertCacheSize(t, c, 1)
cachedReq, ok := c.Consume(tok)
assert.True(t, ok)
assert.Equal(t, req, cachedReq)
assertCacheSize(t, c, 0)
_, ok = c.Consume(tok)
assert.False(t, ok)
assertCacheSize(t, c, 0)
}
{ // Consume without insert
_, ok := c.Consume("fooBAR")
assert.False(t, ok)
assertCacheSize(t, c, 0)
}
{ // Consume expired
tok, err := c.Insert(nextRequest())
require.NoError(t, err)
assertCacheSize(t, c, 1)
clock.Step(2 * CacheTTL)
_, ok := c.Consume(tok)
assert.False(t, ok)
assertCacheSize(t, c, 0)
}
}
func TestGC(t *testing.T) {
c, clock := newTestCache()
// When empty
c.gc()
assertCacheSize(t, c, 0)
tok1, err := c.Insert(nextRequest())
require.NoError(t, err)
assertCacheSize(t, c, 1)
clock.Step(10 * time.Second)
tok2, err := c.Insert(nextRequest())
require.NoError(t, err)
assertCacheSize(t, c, 2)
// expired: tok1, tok2
// non-expired: tok3, tok4
clock.Step(2 * CacheTTL)
tok3, err := c.Insert(nextRequest())
require.NoError(t, err)
assertCacheSize(t, c, 1)
clock.Step(10 * time.Second)
tok4, err := c.Insert(nextRequest())
require.NoError(t, err)
assertCacheSize(t, c, 2)
_, ok := c.Consume(tok1)
assert.False(t, ok)
_, ok = c.Consume(tok2)
assert.False(t, ok)
_, ok = c.Consume(tok3)
assert.True(t, ok)
_, ok = c.Consume(tok4)
assert.True(t, ok)
// When full, nothing is expired.
for i := 0; i < MaxInFlight; i++ {
_, err := c.Insert(nextRequest())
require.NoError(t, err)
}
assertCacheSize(t, c, MaxInFlight)
// When everything is expired
clock.Step(2 * CacheTTL)
_, err = c.Insert(nextRequest())
require.NoError(t, err)
assertCacheSize(t, c, 1)
}
func newTestCache() (*requestCache, *clock.FakeClock) {
c := newRequestCache()
fakeClock := clock.NewFakeClock(time.Now())
c.clock = fakeClock
return c, fakeClock
}
func assertCacheSize(t *testing.T, cache *requestCache, expectedSize int) {
tokenLen := len(cache.tokens)
llLen := cache.ll.Len()
assert.Equal(t, tokenLen, llLen, "inconsistent cache size! len(tokens)=%d; len(ll)=%d", tokenLen, llLen)
assert.Equal(t, expectedSize, tokenLen, "unexpected cache size!")
}
var requestUID = 0
func nextRequest() interface{} {
requestUID++
return requestUID
}
...@@ -25,10 +25,12 @@ import ( ...@@ -25,10 +25,12 @@ import (
"path" "path"
"time" "time"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
restful "github.com/emicklei/go-restful" restful "github.com/emicklei/go-restful"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/kubernetes/pkg/api"
runtimeapi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime" runtimeapi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
"k8s.io/kubernetes/pkg/kubelet/server/portforward" "k8s.io/kubernetes/pkg/kubelet/server/portforward"
"k8s.io/kubernetes/pkg/kubelet/server/remotecommand" "k8s.io/kubernetes/pkg/kubelet/server/remotecommand"
...@@ -97,6 +99,7 @@ func NewServer(config Config, runtime Runtime) (Server, error) { ...@@ -97,6 +99,7 @@ func NewServer(config Config, runtime Runtime) (Server, error) {
s := &server{ s := &server{
config: config, config: config,
runtime: &criAdapter{runtime}, runtime: &criAdapter{runtime},
cache: newRequestCache(),
} }
if s.config.BaseURL == nil { if s.config.BaseURL == nil {
...@@ -114,9 +117,9 @@ func NewServer(config Config, runtime Runtime) (Server, error) { ...@@ -114,9 +117,9 @@ func NewServer(config Config, runtime Runtime) (Server, error) {
path string path string
handler restful.RouteFunction handler restful.RouteFunction
}{ }{
{"/exec/{containerID}", s.serveExec}, {"/exec/{token}", s.serveExec},
{"/attach/{containerID}", s.serveAttach}, {"/attach/{token}", s.serveAttach},
{"/portforward/{podSandboxID}", s.servePortForward}, {"/portforward/{token}", s.servePortForward},
} }
// If serving relative to a base path, set that here. // If serving relative to a base path, set that here.
pathPrefix := path.Dir(s.config.BaseURL.Path) pathPrefix := path.Dir(s.config.BaseURL.Path)
...@@ -139,37 +142,45 @@ type server struct { ...@@ -139,37 +142,45 @@ type server struct {
config Config config Config
runtime *criAdapter runtime *criAdapter
handler http.Handler handler http.Handler
cache *requestCache
} }
func (s *server) GetExec(req *runtimeapi.ExecRequest) (*runtimeapi.ExecResponse, error) { func (s *server) GetExec(req *runtimeapi.ExecRequest) (*runtimeapi.ExecResponse, error) {
url := s.buildURL("exec", req.GetContainerId(), streamOpts{ if req.GetContainerId() == "" {
stdin: req.GetStdin(), return nil, grpc.Errorf(codes.InvalidArgument, "missing required container_id")
stdout: true, }
stderr: !req.GetTty(), // For TTY connections, both stderr is combined with stdout. token, err := s.cache.Insert(req)
tty: req.GetTty(), if err != nil {
command: req.GetCmd(), return nil, err
}) }
return &runtimeapi.ExecResponse{ return &runtimeapi.ExecResponse{
Url: &url, Url: s.buildURL("exec", token),
}, nil }, nil
} }
func (s *server) GetAttach(req *runtimeapi.AttachRequest) (*runtimeapi.AttachResponse, error) { func (s *server) GetAttach(req *runtimeapi.AttachRequest) (*runtimeapi.AttachResponse, error) {
url := s.buildURL("attach", req.GetContainerId(), streamOpts{ if req.GetContainerId() == "" {
stdin: req.GetStdin(), return nil, grpc.Errorf(codes.InvalidArgument, "missing required container_id")
stdout: true, }
stderr: !req.GetTty(), // For TTY connections, both stderr is combined with stdout. token, err := s.cache.Insert(req)
tty: req.GetTty(), if err != nil {
}) return nil, err
}
return &runtimeapi.AttachResponse{ return &runtimeapi.AttachResponse{
Url: &url, Url: s.buildURL("attach", token),
}, nil }, nil
} }
func (s *server) GetPortForward(req *runtimeapi.PortForwardRequest) (*runtimeapi.PortForwardResponse, error) { func (s *server) GetPortForward(req *runtimeapi.PortForwardRequest) (*runtimeapi.PortForwardResponse, error) {
url := s.buildURL("portforward", req.GetPodSandboxId(), streamOpts{}) if req.GetPodSandboxId() == "" {
return nil, grpc.Errorf(codes.InvalidArgument, "missing required pod_sandbox_id")
}
token, err := s.cache.Insert(req)
if err != nil {
return nil, err
}
return &runtimeapi.PortForwardResponse{ return &runtimeapi.PortForwardResponse{
Url: &url, Url: s.buildURL("portforward", token),
}, nil }, nil
} }
...@@ -200,63 +211,32 @@ func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) { ...@@ -200,63 +211,32 @@ func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.handler.ServeHTTP(w, r) s.handler.ServeHTTP(w, r)
} }
type streamOpts struct { func (s *server) buildURL(method, token string) *string {
stdin bool loc := s.config.BaseURL.ResolveReference(&url.URL{
stdout bool Path: path.Join(method, token),
stderr bool }).String()
tty bool return &loc
command []string
port []int32
}
const (
urlParamStdin = api.ExecStdinParam
urlParamStdout = api.ExecStdoutParam
urlParamStderr = api.ExecStderrParam
urlParamTTY = api.ExecTTYParam
urlParamCommand = api.ExecCommandParamm
)
func (s *server) buildURL(method, id string, opts streamOpts) string {
loc := &url.URL{
Path: path.Join(method, id),
}
query := url.Values{}
if opts.stdin {
query.Add(urlParamStdin, "1")
}
if opts.stdout {
query.Add(urlParamStdout, "1")
}
if opts.stderr {
query.Add(urlParamStderr, "1")
}
if opts.tty {
query.Add(urlParamTTY, "1")
}
for _, c := range opts.command {
query.Add(urlParamCommand, c)
}
loc.RawQuery = query.Encode()
return s.config.BaseURL.ResolveReference(loc).String()
} }
func (s *server) serveExec(req *restful.Request, resp *restful.Response) { func (s *server) serveExec(req *restful.Request, resp *restful.Response) {
containerID := req.PathParameter("containerID") token := req.PathParameter("token")
if containerID == "" { cachedRequest, ok := s.cache.Consume(token)
resp.WriteError(http.StatusBadRequest, errors.New("missing required containerID path parameter")) if !ok {
http.NotFound(resp.ResponseWriter, req.Request)
return return
} }
exec, ok := cachedRequest.(*runtimeapi.ExecRequest)
streamOpts, err := remotecommand.NewOptions(req.Request) if !ok {
if err != nil { http.NotFound(resp.ResponseWriter, req.Request)
resp.WriteError(http.StatusBadRequest, err)
return return
} }
cmd := req.Request.URL.Query()[api.ExecCommandParamm]
streamOpts := &remotecommand.Options{
Stdin: exec.GetStdin(),
Stdout: true,
Stderr: !exec.GetTty(),
TTY: exec.GetTty(),
}
remotecommand.ServeExec( remotecommand.ServeExec(
resp.ResponseWriter, resp.ResponseWriter,
...@@ -264,8 +244,8 @@ func (s *server) serveExec(req *restful.Request, resp *restful.Response) { ...@@ -264,8 +244,8 @@ func (s *server) serveExec(req *restful.Request, resp *restful.Response) {
s.runtime, s.runtime,
"", // unused: podName "", // unused: podName
"", // unusued: podUID "", // unusued: podUID
containerID, exec.GetContainerId(),
cmd, exec.GetCmd(),
streamOpts, streamOpts,
s.config.StreamIdleTimeout, s.config.StreamIdleTimeout,
s.config.StreamCreationTimeout, s.config.StreamCreationTimeout,
...@@ -273,25 +253,31 @@ func (s *server) serveExec(req *restful.Request, resp *restful.Response) { ...@@ -273,25 +253,31 @@ func (s *server) serveExec(req *restful.Request, resp *restful.Response) {
} }
func (s *server) serveAttach(req *restful.Request, resp *restful.Response) { func (s *server) serveAttach(req *restful.Request, resp *restful.Response) {
containerID := req.PathParameter("containerID") token := req.PathParameter("token")
if containerID == "" { cachedRequest, ok := s.cache.Consume(token)
resp.WriteError(http.StatusBadRequest, errors.New("missing required containerID path parameter")) if !ok {
http.NotFound(resp.ResponseWriter, req.Request)
return return
} }
attach, ok := cachedRequest.(*runtimeapi.AttachRequest)
streamOpts, err := remotecommand.NewOptions(req.Request) if !ok {
if err != nil { http.NotFound(resp.ResponseWriter, req.Request)
resp.WriteError(http.StatusBadRequest, err)
return return
} }
streamOpts := &remotecommand.Options{
Stdin: attach.GetStdin(),
Stdout: true,
Stderr: !attach.GetTty(),
TTY: attach.GetTty(),
}
remotecommand.ServeAttach( remotecommand.ServeAttach(
resp.ResponseWriter, resp.ResponseWriter,
req.Request, req.Request,
s.runtime, s.runtime,
"", // unused: podName "", // unused: podName
"", // unusued: podUID "", // unusued: podUID
containerID, attach.GetContainerId(),
streamOpts, streamOpts,
s.config.StreamIdleTimeout, s.config.StreamIdleTimeout,
s.config.StreamCreationTimeout, s.config.StreamCreationTimeout,
...@@ -299,9 +285,15 @@ func (s *server) serveAttach(req *restful.Request, resp *restful.Response) { ...@@ -299,9 +285,15 @@ func (s *server) serveAttach(req *restful.Request, resp *restful.Response) {
} }
func (s *server) servePortForward(req *restful.Request, resp *restful.Response) { func (s *server) servePortForward(req *restful.Request, resp *restful.Response) {
podSandboxID := req.PathParameter("podSandboxID") token := req.PathParameter("token")
if podSandboxID == "" { cachedRequest, ok := s.cache.Consume(token)
resp.WriteError(http.StatusBadRequest, errors.New("missing required podSandboxID path parameter")) if !ok {
http.NotFound(resp.ResponseWriter, req.Request)
return
}
pf, ok := cachedRequest.(*runtimeapi.PortForwardRequest)
if !ok {
http.NotFound(resp.ResponseWriter, req.Request)
return return
} }
...@@ -309,7 +301,7 @@ func (s *server) servePortForward(req *restful.Request, resp *restful.Response) ...@@ -309,7 +301,7 @@ func (s *server) servePortForward(req *restful.Request, resp *restful.Response)
resp.ResponseWriter, resp.ResponseWriter,
req.Request, req.Request,
s.runtime, s.runtime,
podSandboxID, pf.GetPodSandboxId(),
"", // unused: podUID "", // unused: podUID
s.config.StreamIdleTimeout, s.config.StreamIdleTimeout,
s.config.StreamCreationTimeout) s.config.StreamCreationTimeout)
......
...@@ -391,6 +391,14 @@ var _ = framework.KubeDescribe("Kubectl client", func() { ...@@ -391,6 +391,14 @@ var _ = framework.KubeDescribe("Kubectl client", func() {
framework.Failf("Unexpected kubectl exec output. Wanted %q, got %q", e, a) framework.Failf("Unexpected kubectl exec output. Wanted %q, got %q", e, a)
} }
By("executing a very long command in the container")
veryLongData := make([]rune, 20000)
for i := 0; i < len(veryLongData); i++ {
veryLongData[i] = 'a'
}
execOutput = framework.RunKubectlOrDie("exec", fmt.Sprintf("--namespace=%v", ns), simplePodName, "echo", string(veryLongData))
Expect(string(veryLongData)).To(Equal(strings.TrimSpace(execOutput)), "Unexpected kubectl exec output")
By("executing a command in the container with noninteractive stdin") By("executing a command in the container with noninteractive stdin")
execOutput = framework.NewKubectlCommand("exec", fmt.Sprintf("--namespace=%v", ns), "-i", simplePodName, "cat"). execOutput = framework.NewKubectlCommand("exec", fmt.Sprintf("--namespace=%v", ns), "-i", simplePodName, "cat").
WithStdinData("abcd1234"). WithStdinData("abcd1234").
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment