Enable unprivileged retrieve of file listing and content.
This commit is contained in:
@ -330,6 +330,12 @@ paths:
|
|||||||
format: pct-encoded # rfc 3986
|
format: pct-encoded # rfc 3986
|
||||||
default: "./"
|
default: "./"
|
||||||
required: false
|
required: false
|
||||||
|
- name: privilegedExecution
|
||||||
|
in: query
|
||||||
|
description: Specifies if the command should be executed as an privileged user.
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: Success. Returns the listing of the runner's filesystem.
|
description: Success. Returns the listing of the runner's filesystem.
|
||||||
@ -423,6 +429,12 @@ paths:
|
|||||||
format: pct-encoded # rfc 3986
|
format: pct-encoded # rfc 3986
|
||||||
example: "./flag.txt"
|
example: "./flag.txt"
|
||||||
required: true
|
required: true
|
||||||
|
- name: privilegedExecution
|
||||||
|
in: query
|
||||||
|
description: Specifies if the command should be executed as an privileged user.
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: Success. Returns the file.
|
description: Success. Returns the file.
|
||||||
|
@ -28,6 +28,7 @@ const (
|
|||||||
ExecutionIDKey = "executionID"
|
ExecutionIDKey = "executionID"
|
||||||
PathKey = "path"
|
PathKey = "path"
|
||||||
RecursiveKey = "recursive"
|
RecursiveKey = "recursive"
|
||||||
|
PrivilegedExecutionKey = "privilegedExecution"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RunnerController struct {
|
type RunnerController struct {
|
||||||
@ -93,9 +94,13 @@ func (r *RunnerController) listFileSystem(writer http.ResponseWriter, request *h
|
|||||||
if path == "" {
|
if path == "" {
|
||||||
path = "./"
|
path = "./"
|
||||||
}
|
}
|
||||||
|
privilegedExecution, err := strconv.ParseBool(request.URL.Query().Get(PrivilegedExecutionKey))
|
||||||
|
if err != nil {
|
||||||
|
privilegedExecution = false
|
||||||
|
}
|
||||||
|
|
||||||
writer.Header().Set("Content-Type", "application/json")
|
writer.Header().Set("Content-Type", "application/json")
|
||||||
err = targetRunner.ListFileSystem(path, recursive, writer, request.Context())
|
err = targetRunner.ListFileSystem(path, recursive, writer, privilegedExecution, request.Context())
|
||||||
if errors.Is(err, runner.ErrFileNotFound) {
|
if errors.Is(err, runner.ErrFileNotFound) {
|
||||||
writeClientError(writer, err, http.StatusFailedDependency)
|
writeClientError(writer, err, http.StatusFailedDependency)
|
||||||
return
|
return
|
||||||
@ -129,9 +134,13 @@ func (r *RunnerController) updateFileSystem(writer http.ResponseWriter, request
|
|||||||
func (r *RunnerController) fileContent(writer http.ResponseWriter, request *http.Request) {
|
func (r *RunnerController) fileContent(writer http.ResponseWriter, request *http.Request) {
|
||||||
targetRunner, _ := runner.FromContext(request.Context())
|
targetRunner, _ := runner.FromContext(request.Context())
|
||||||
path := request.URL.Query().Get(PathKey)
|
path := request.URL.Query().Get(PathKey)
|
||||||
|
privilegedExecution, err := strconv.ParseBool(request.URL.Query().Get(PrivilegedExecutionKey))
|
||||||
|
if err != nil {
|
||||||
|
privilegedExecution = false
|
||||||
|
}
|
||||||
|
|
||||||
writer.Header().Set("Content-Type", "application/octet-stream")
|
writer.Header().Set("Content-Type", "application/octet-stream")
|
||||||
err := targetRunner.GetFileContent(path, writer, request.Context())
|
err = targetRunner.GetFileContent(path, writer, privilegedExecution, request.Context())
|
||||||
if errors.Is(err, runner.ErrFileNotFound) {
|
if errors.Is(err, runner.ErrFileNotFound) {
|
||||||
writeClientError(writer, err, http.StatusFailedDependency)
|
writeClientError(writer, err, http.StatusFailedDependency)
|
||||||
return
|
return
|
||||||
|
@ -315,8 +315,8 @@ func (s *UpdateFileSystemRouteTestSuite) TestUpdateFileSystemReturnsInternalServ
|
|||||||
func (s *UpdateFileSystemRouteTestSuite) TestListFileSystem() {
|
func (s *UpdateFileSystemRouteTestSuite) TestListFileSystem() {
|
||||||
routeURL, err := s.router.Get(UpdateFileSystemPath).URL(RunnerIDKey, tests.DefaultMockID)
|
routeURL, err := s.router.Get(UpdateFileSystemPath).URL(RunnerIDKey, tests.DefaultMockID)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
mockCall := s.runnerMock.On("ListFileSystem",
|
mockCall := s.runnerMock.On("ListFileSystem", mock.AnythingOfType("string"),
|
||||||
mock.AnythingOfType("string"), mock.AnythingOfType("bool"), mock.Anything, mock.Anything)
|
mock.AnythingOfType("bool"), mock.Anything, mock.AnythingOfType("bool"), mock.Anything)
|
||||||
|
|
||||||
s.Run("default parameters", func() {
|
s.Run("default parameters", func() {
|
||||||
mockCall.Run(func(args mock.Arguments) {
|
mockCall.Run(func(args mock.Arguments) {
|
||||||
@ -375,8 +375,8 @@ func (s *UpdateFileSystemRouteTestSuite) TestListFileSystem() {
|
|||||||
func (s *UpdateFileSystemRouteTestSuite) TestFileContent() {
|
func (s *UpdateFileSystemRouteTestSuite) TestFileContent() {
|
||||||
routeURL, err := s.router.Get(FileContentRawPath).URL(RunnerIDKey, tests.DefaultMockID)
|
routeURL, err := s.router.Get(FileContentRawPath).URL(RunnerIDKey, tests.DefaultMockID)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
mockCall := s.runnerMock.
|
mockCall := s.runnerMock.On("GetFileContent",
|
||||||
On("GetFileContent", mock.AnythingOfType("string"), mock.Anything, mock.Anything)
|
mock.AnythingOfType("string"), mock.Anything, mock.AnythingOfType("bool"), mock.Anything)
|
||||||
|
|
||||||
s.Run("Not Found", func() {
|
s.Run("Not Found", func() {
|
||||||
mockCall.Return(runner.ErrFileNotFound)
|
mockCall.Return(runner.ErrFileNotFound)
|
||||||
|
@ -104,7 +104,7 @@ func (w *AWSFunctionWorkload) ExecuteInteractively(id string, _ io.ReadWriter, s
|
|||||||
// ListFileSystem is currently not supported with this aws serverless function.
|
// ListFileSystem is currently not supported with this aws serverless function.
|
||||||
// This is because the function execution ends with the termination of the workload code.
|
// This is because the function execution ends with the termination of the workload code.
|
||||||
// So an on-demand file system listing after the termination is not possible. Also, we do not want to copy all files.
|
// So an on-demand file system listing after the termination is not possible. Also, we do not want to copy all files.
|
||||||
func (w *AWSFunctionWorkload) ListFileSystem(_ string, _ bool, _ io.Writer, _ context.Context) error {
|
func (w *AWSFunctionWorkload) ListFileSystem(_ string, _ bool, _ io.Writer, _ bool, _ context.Context) error {
|
||||||
return dto.ErrNotSupported
|
return dto.ErrNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,7 +125,7 @@ func (w *AWSFunctionWorkload) UpdateFileSystem(request *dto.UpdateFileSystemRequ
|
|||||||
// GetFileContent is currently not supported with this aws serverless function.
|
// GetFileContent is currently not supported with this aws serverless function.
|
||||||
// This is because the function execution ends with the termination of the workload code.
|
// This is because the function execution ends with the termination of the workload code.
|
||||||
// So an on-demand file streaming after the termination is not possible. Also, we do not want to copy all files.
|
// So an on-demand file streaming after the termination is not possible. Also, we do not want to copy all files.
|
||||||
func (w *AWSFunctionWorkload) GetFileContent(_ string, _ io.Writer, _ context.Context) error {
|
func (w *AWSFunctionWorkload) GetFileContent(_ string, _ io.Writer, _ bool, _ context.Context) error {
|
||||||
return dto.ErrNotSupported
|
return dto.ErrNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,7 +118,8 @@ func (r *NomadJob) ExecuteInteractively(
|
|||||||
return exit, cancel, nil
|
return exit, cancel, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *NomadJob) ListFileSystem(path string, recursive bool, content io.Writer, ctx context.Context) error {
|
func (r *NomadJob) ListFileSystem(
|
||||||
|
path string, recursive bool, content io.Writer, privilegedExecution bool, ctx context.Context) error {
|
||||||
r.ResetTimeout()
|
r.ResetTimeout()
|
||||||
command := "ls -l --time-style=+%s -1 --literal"
|
command := "ls -l --time-style=+%s -1 --literal"
|
||||||
if recursive {
|
if recursive {
|
||||||
@ -128,7 +129,8 @@ func (r *NomadJob) ListFileSystem(path string, recursive bool, content io.Writer
|
|||||||
ls2json := &nullio.Ls2JsonWriter{Target: content}
|
ls2json := &nullio.Ls2JsonWriter{Target: content}
|
||||||
defer ls2json.Close()
|
defer ls2json.Close()
|
||||||
retrieveCommand := (&dto.ExecutionRequest{Command: fmt.Sprintf("%s %q", command, path)}).FullCommand()
|
retrieveCommand := (&dto.ExecutionRequest{Command: fmt.Sprintf("%s %q", command, path)}).FullCommand()
|
||||||
exitCode, err := r.api.ExecuteCommand(r.id, ctx, retrieveCommand, false, &nullio.Reader{}, ls2json, io.Discard)
|
exitCode, err := r.api.ExecuteCommand(r.id, ctx, retrieveCommand, false, privilegedExecution,
|
||||||
|
&nullio.Reader{}, ls2json, io.Discard)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%w: nomad error during retrieve file headers: %v",
|
return fmt.Errorf("%w: nomad error during retrieve file headers: %v",
|
||||||
nomad.ErrorExecutorCommunicationFailed, err)
|
nomad.ErrorExecutorCommunicationFailed, err)
|
||||||
@ -172,12 +174,13 @@ func (r *NomadJob) UpdateFileSystem(copyRequest *dto.UpdateFileSystemRequest) er
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *NomadJob) GetFileContent(path string, content io.Writer, ctx context.Context) error {
|
func (r *NomadJob) GetFileContent(path string, content io.Writer, privilegedExecution bool, ctx context.Context) error {
|
||||||
r.ResetTimeout()
|
r.ResetTimeout()
|
||||||
|
|
||||||
retrieveCommand := (&dto.ExecutionRequest{Command: fmt.Sprintf("cat %q", path)}).FullCommand()
|
retrieveCommand := (&dto.ExecutionRequest{Command: fmt.Sprintf("cat %q", path)}).FullCommand()
|
||||||
// Improve: Instead of using io.Discard use a **fixed-sized** buffer. With that we could improve the error message.
|
// Improve: Instead of using io.Discard use a **fixed-sized** buffer. With that we could improve the error message.
|
||||||
exitCode, err := r.api.ExecuteCommand(r.id, ctx, retrieveCommand, false, &nullio.Reader{}, content, io.Discard)
|
exitCode, err := r.api.ExecuteCommand(r.id, ctx, retrieveCommand, false, privilegedExecution,
|
||||||
|
&nullio.Reader{}, content, io.Discard)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%w: nomad error during retrieve file content copy: %v",
|
return fmt.Errorf("%w: nomad error during retrieve file content copy: %v",
|
||||||
|
@ -403,6 +403,6 @@ func NewRunner(id string, manager Accessor) Runner {
|
|||||||
func (s *UpdateFileSystemTestSuite) TestGetFileContentReturnsErrorIfExitCodeIsNotZero() {
|
func (s *UpdateFileSystemTestSuite) TestGetFileContentReturnsErrorIfExitCodeIsNotZero() {
|
||||||
s.mockedExecuteCommandCall.RunFn = nil
|
s.mockedExecuteCommandCall.RunFn = nil
|
||||||
s.mockedExecuteCommandCall.Return(1, nil)
|
s.mockedExecuteCommandCall.Return(1, nil)
|
||||||
err := s.runner.GetFileContent("", &bytes.Buffer{}, context.Background())
|
err := s.runner.GetFileContent("", &bytes.Buffer{}, false, context.Background())
|
||||||
s.ErrorIs(err, ErrFileNotFound)
|
s.ErrorIs(err, ErrFileNotFound)
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ type Runner interface {
|
|||||||
|
|
||||||
// ListFileSystem streams the listing of the file system of the requested directory into the Writer provided.
|
// ListFileSystem streams the listing of the file system of the requested directory into the Writer provided.
|
||||||
// The result is streamed via the io.Writer in order to not overload the memory with user input.
|
// The result is streamed via the io.Writer in order to not overload the memory with user input.
|
||||||
ListFileSystem(path string, recursive bool, result io.Writer, ctx context.Context) error
|
ListFileSystem(path string, recursive bool, result io.Writer, privilegedExecution bool, ctx context.Context) error
|
||||||
|
|
||||||
// UpdateFileSystem processes a dto.UpdateFileSystemRequest by first deleting each given dto.FilePath recursively
|
// UpdateFileSystem processes a dto.UpdateFileSystemRequest by first deleting each given dto.FilePath recursively
|
||||||
// and then copying each given dto.File to the runner.
|
// and then copying each given dto.File to the runner.
|
||||||
@ -54,7 +54,7 @@ type Runner interface {
|
|||||||
|
|
||||||
// GetFileContent streams the file content at the requested path into the Writer provided at content.
|
// GetFileContent streams the file content at the requested path into the Writer provided at content.
|
||||||
// The result is streamed via the io.Writer in order to not overload the memory with user input.
|
// The result is streamed via the io.Writer in order to not overload the memory with user input.
|
||||||
GetFileContent(path string, content io.Writer, ctx context.Context) error
|
GetFileContent(path string, content io.Writer, privilegedExecution bool, ctx context.Context) error
|
||||||
|
|
||||||
// Destroy destroys the Runner in Nomad.
|
// Destroy destroys the Runner in Nomad.
|
||||||
Destroy() error
|
Destroy() error
|
||||||
|
@ -92,13 +92,13 @@ func (_m *RunnerMock) ExecutionExists(id string) bool {
|
|||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFileContent provides a mock function with given fields: path, content, ctx
|
// GetFileContent provides a mock function with given fields: path, content, privilegedExecution, ctx
|
||||||
func (_m *RunnerMock) GetFileContent(path string, content io.Writer, ctx context.Context) error {
|
func (_m *RunnerMock) GetFileContent(path string, content io.Writer, privilegedExecution bool, ctx context.Context) error {
|
||||||
ret := _m.Called(path, content, ctx)
|
ret := _m.Called(path, content, privilegedExecution, ctx)
|
||||||
|
|
||||||
var r0 error
|
var r0 error
|
||||||
if rf, ok := ret.Get(0).(func(string, io.Writer, context.Context) error); ok {
|
if rf, ok := ret.Get(0).(func(string, io.Writer, bool, context.Context) error); ok {
|
||||||
r0 = rf(path, content, ctx)
|
r0 = rf(path, content, privilegedExecution, ctx)
|
||||||
} else {
|
} else {
|
||||||
r0 = ret.Error(0)
|
r0 = ret.Error(0)
|
||||||
}
|
}
|
||||||
@ -120,13 +120,13 @@ func (_m *RunnerMock) ID() string {
|
|||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListFileSystem provides a mock function with given fields: path, recursive, result, ctx
|
// ListFileSystem provides a mock function with given fields: path, recursive, result, privilegedExecution, ctx
|
||||||
func (_m *RunnerMock) ListFileSystem(path string, recursive bool, result io.Writer, ctx context.Context) error {
|
func (_m *RunnerMock) ListFileSystem(path string, recursive bool, result io.Writer, privilegedExecution bool, ctx context.Context) error {
|
||||||
ret := _m.Called(path, recursive, result, ctx)
|
ret := _m.Called(path, recursive, result, privilegedExecution, ctx)
|
||||||
|
|
||||||
var r0 error
|
var r0 error
|
||||||
if rf, ok := ret.Get(0).(func(string, bool, io.Writer, context.Context) error); ok {
|
if rf, ok := ret.Get(0).(func(string, bool, io.Writer, bool, context.Context) error); ok {
|
||||||
r0 = rf(path, recursive, result, ctx)
|
r0 = rf(path, recursive, result, privilegedExecution, ctx)
|
||||||
} else {
|
} else {
|
||||||
r0 = ret.Error(0)
|
r0 = ret.Error(0)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user