mirror of
https://github.com/kubernetes/kubectl.git
synced 2026-06-09 08:51:18 -04:00
Escape path inside the container
Signed-off-by: Maciej Szulik <soltysh@gmail.com> Kubernetes-commit: e7440a68607b937ebd3e24629a9d371b77e28bc3
This commit is contained in:
parent
ef67d87dbf
commit
1a37cc3b35
2 changed files with 119 additions and 4 deletions
|
|
@ -76,6 +76,7 @@ type CopyOptions struct {
|
|||
ClientConfig *restclient.Config
|
||||
Clientset kubernetes.Interface
|
||||
ExecParentCmdName string
|
||||
Executor exec.RemoteExecutor
|
||||
|
||||
args []string
|
||||
|
||||
|
|
@ -204,6 +205,7 @@ func (o *CopyOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []str
|
|||
if cmd.Parent() != nil {
|
||||
o.ExecParentCmdName = cmd.Parent().CommandPath()
|
||||
}
|
||||
o.Executor = &exec.DefaultRemoteExecutor{}
|
||||
|
||||
var err error
|
||||
o.Namespace, _, err = f.ToRawKubeConfigLoader().Namespace()
|
||||
|
|
@ -277,7 +279,7 @@ func (o *CopyOptions) checkDestinationIsDir(dest fileSpec) error {
|
|||
},
|
||||
|
||||
Command: []string{"test", "-d", dest.File.String()},
|
||||
Executor: &exec.DefaultRemoteExecutor{},
|
||||
Executor: o.Executor,
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
|
|
@ -345,7 +347,7 @@ func (o *CopyOptions) copyToPod(src, dest fileSpec, options *exec.ExecOptions) e
|
|||
}
|
||||
|
||||
options.Command = cmdArr
|
||||
options.Executor = &exec.DefaultRemoteExecutor{}
|
||||
options.Executor = o.Executor
|
||||
return o.execute(options)
|
||||
}
|
||||
|
||||
|
|
@ -391,10 +393,11 @@ func (t *TarPipe) initReadFrom(n uint64) {
|
|||
},
|
||||
|
||||
Command: []string{"tar", "cf", "-", t.src.File.String()},
|
||||
Executor: &exec.DefaultRemoteExecutor{},
|
||||
Executor: t.o.Executor,
|
||||
}
|
||||
if t.o.MaxTries != 0 {
|
||||
options.Command = []string{"sh", "-c", fmt.Sprintf("tar cf - %s | tail -c+%d", t.src.File, n)}
|
||||
escapedPath := strings.ReplaceAll(t.src.File.String(), "'", `'\''`)
|
||||
options.Command = []string{"sh", "-c", fmt.Sprintf("tar cf - '%s' | tail -c+%d", escapedPath, n)}
|
||||
}
|
||||
|
||||
go func() {
|
||||
|
|
|
|||
|
|
@ -19,9 +19,11 @@ package cp
|
|||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
|
|
@ -33,10 +35,13 @@ import (
|
|||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/rest/fake"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
kexec "k8s.io/kubectl/pkg/cmd/exec"
|
||||
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
|
|
@ -674,6 +679,113 @@ func TestCopyToPod(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCopyFromPod(t *testing.T) {
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
ns := scheme.Codecs.WithoutConversion()
|
||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
|
||||
tf.Client = &fake.RESTClient{
|
||||
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
|
||||
NegotiatedSerializer: ns,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
responsePod := &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "pod-name", Namespace: "pod-ns"},
|
||||
Spec: v1.PodSpec{Containers: []v1.Container{{Name: "container"}}},
|
||||
}
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: io.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, responsePod))))}, nil
|
||||
}),
|
||||
}
|
||||
|
||||
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
|
||||
ioStreams, _, _, _ := genericiooptions.NewTestIOStreams()
|
||||
|
||||
cmd := NewCmdCp(tf, ioStreams)
|
||||
|
||||
destDir, err := os.MkdirTemp("", "test")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(destDir)
|
||||
|
||||
tests := map[string]struct {
|
||||
src string
|
||||
dest string
|
||||
podName string
|
||||
retries int
|
||||
expectedErr string
|
||||
expectedCommand string
|
||||
}{
|
||||
"copy from pod to empty path": {
|
||||
src: "pod-ns/pod-name:/tmp/foo",
|
||||
dest: "",
|
||||
expectedErr: "filepath can not be empty",
|
||||
},
|
||||
"path without single quotes": {
|
||||
src: "pod-ns/pod-name:/tmp/foo",
|
||||
dest: destDir,
|
||||
podName: "pod-name",
|
||||
expectedCommand: "tar cf - /tmp/foo",
|
||||
},
|
||||
"path with single quotes": {
|
||||
src: "pod-ns/pod-name:/tmp/path'with'quotes",
|
||||
dest: destDir,
|
||||
podName: "pod-name",
|
||||
retries: 1,
|
||||
expectedCommand: `sh -c tar cf - '/tmp/path'\''with'\''quotes' | tail -c+1`,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
opts := NewCopyOptions(ioStreams)
|
||||
opts.MaxTries = test.retries
|
||||
if err := opts.Complete(tf, cmd, []string{test.src, test.dest}); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
remoteExec := &testingRemoteExecutor{}
|
||||
opts.Executor = remoteExec
|
||||
t.Run(name, func(t *testing.T) {
|
||||
err := opts.Run()
|
||||
if len(test.expectedErr) > 0 {
|
||||
if err == nil {
|
||||
t.Fatalf("expected error but got none")
|
||||
}
|
||||
if !strings.Contains(err.Error(), test.expectedErr) {
|
||||
t.Errorf("expected error to contain %q, got: %v", test.expectedErr, err)
|
||||
}
|
||||
}
|
||||
if len(test.expectedErr) == 0 && err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if !strings.Contains(remoteExec.capturedPath, test.podName) {
|
||||
t.Errorf("missing pod name %q in the captured path: %q", test.podName, remoteExec.capturedPath)
|
||||
}
|
||||
query, err := url.ParseQuery(remoteExec.capturedQuery)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error parsing captured query: %v", err)
|
||||
}
|
||||
actualQuery := strings.Join(query["command"], " ")
|
||||
if actualQuery != test.expectedCommand {
|
||||
t.Errorf("unexpected command, got %q, expected: %q", actualQuery, test.expectedCommand)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type testingRemoteExecutor struct {
|
||||
capturedPath string
|
||||
capturedQuery string
|
||||
}
|
||||
|
||||
func (t *testingRemoteExecutor) Execute(url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue remotecommand.TerminalSizeQueue) error {
|
||||
return t.ExecuteWithContext(context.Background(), url, config, stdin, stdout, stderr, tty, terminalSizeQueue)
|
||||
}
|
||||
|
||||
func (t *testingRemoteExecutor) ExecuteWithContext(ctx context.Context, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue remotecommand.TerminalSizeQueue) error {
|
||||
t.capturedPath = url.Path
|
||||
t.capturedQuery = url.RawQuery
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestCopyToPodNoPreserve(t *testing.T) {
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
ns := scheme.Codecs.WithoutConversion()
|
||||
|
|
|
|||
Loading…
Reference in a new issue