diff --git a/api/swagger.yaml b/api/swagger.yaml index e527e6b..3fd6094 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -330,6 +330,12 @@ paths: format: pct-encoded # rfc 3986 default: "./" required: false + - name: privilegedExecution + in: query + description: Specifies if the command should be executed as an privileged user. + schema: + type: boolean + default: false responses: "200": description: Success. Returns the listing of the runner's filesystem. @@ -423,6 +429,12 @@ paths: format: pct-encoded # rfc 3986 example: "./flag.txt" required: true + - name: privilegedExecution + in: query + description: Specifies if the command should be executed as an privileged user. + schema: + type: boolean + default: false responses: "200": description: Success. Returns the file. diff --git a/internal/api/runners.go b/internal/api/runners.go index 874951c..afeb10a 100644 --- a/internal/api/runners.go +++ b/internal/api/runners.go @@ -28,6 +28,7 @@ const ( ExecutionIDKey = "executionID" PathKey = "path" RecursiveKey = "recursive" + PrivilegedExecutionKey = "privilegedExecution" ) type RunnerController struct { @@ -93,9 +94,13 @@ func (r *RunnerController) listFileSystem(writer http.ResponseWriter, request *h if path == "" { path = "./" } + privilegedExecution, err := strconv.ParseBool(request.URL.Query().Get(PrivilegedExecutionKey)) + if err != nil { + privilegedExecution = false + } 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) { writeClientError(writer, err, http.StatusFailedDependency) return @@ -129,9 +134,13 @@ func (r *RunnerController) updateFileSystem(writer http.ResponseWriter, request func (r *RunnerController) fileContent(writer http.ResponseWriter, request *http.Request) { targetRunner, _ := runner.FromContext(request.Context()) 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") - err := targetRunner.GetFileContent(path, writer, request.Context()) + err = targetRunner.GetFileContent(path, writer, privilegedExecution, request.Context()) if errors.Is(err, runner.ErrFileNotFound) { writeClientError(writer, err, http.StatusFailedDependency) return diff --git a/internal/api/runners_test.go b/internal/api/runners_test.go index 257fdcc..4f7bf78 100644 --- a/internal/api/runners_test.go +++ b/internal/api/runners_test.go @@ -315,8 +315,8 @@ func (s *UpdateFileSystemRouteTestSuite) TestUpdateFileSystemReturnsInternalServ func (s *UpdateFileSystemRouteTestSuite) TestListFileSystem() { routeURL, err := s.router.Get(UpdateFileSystemPath).URL(RunnerIDKey, tests.DefaultMockID) s.Require().NoError(err) - mockCall := s.runnerMock.On("ListFileSystem", - mock.AnythingOfType("string"), mock.AnythingOfType("bool"), mock.Anything, mock.Anything) + mockCall := s.runnerMock.On("ListFileSystem", mock.AnythingOfType("string"), + mock.AnythingOfType("bool"), mock.Anything, mock.AnythingOfType("bool"), mock.Anything) s.Run("default parameters", func() { mockCall.Run(func(args mock.Arguments) { @@ -375,8 +375,8 @@ func (s *UpdateFileSystemRouteTestSuite) TestListFileSystem() { func (s *UpdateFileSystemRouteTestSuite) TestFileContent() { routeURL, err := s.router.Get(FileContentRawPath).URL(RunnerIDKey, tests.DefaultMockID) s.Require().NoError(err) - mockCall := s.runnerMock. - On("GetFileContent", mock.AnythingOfType("string"), mock.Anything, mock.Anything) + mockCall := s.runnerMock.On("GetFileContent", + mock.AnythingOfType("string"), mock.Anything, mock.AnythingOfType("bool"), mock.Anything) s.Run("Not Found", func() { mockCall.Return(runner.ErrFileNotFound) diff --git a/internal/runner/aws_runner.go b/internal/runner/aws_runner.go index e62aa8a..6c1e13c 100644 --- a/internal/runner/aws_runner.go +++ b/internal/runner/aws_runner.go @@ -104,7 +104,7 @@ func (w *AWSFunctionWorkload) ExecuteInteractively(id string, _ io.ReadWriter, s // ListFileSystem is currently not supported with this aws serverless function. // 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. -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 } @@ -125,7 +125,7 @@ func (w *AWSFunctionWorkload) UpdateFileSystem(request *dto.UpdateFileSystemRequ // GetFileContent is currently not supported with this aws serverless function. // 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. -func (w *AWSFunctionWorkload) GetFileContent(_ string, _ io.Writer, _ context.Context) error { +func (w *AWSFunctionWorkload) GetFileContent(_ string, _ io.Writer, _ bool, _ context.Context) error { return dto.ErrNotSupported } diff --git a/internal/runner/nomad_runner.go b/internal/runner/nomad_runner.go index d7d3442..d2e3deb 100644 --- a/internal/runner/nomad_runner.go +++ b/internal/runner/nomad_runner.go @@ -118,7 +118,8 @@ func (r *NomadJob) ExecuteInteractively( 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() command := "ls -l --time-style=+%s -1 --literal" if recursive { @@ -128,7 +129,8 @@ func (r *NomadJob) ListFileSystem(path string, recursive bool, content io.Writer ls2json := &nullio.Ls2JsonWriter{Target: content} defer ls2json.Close() 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 { return fmt.Errorf("%w: nomad error during retrieve file headers: %v", nomad.ErrorExecutorCommunicationFailed, err) @@ -172,12 +174,13 @@ func (r *NomadJob) UpdateFileSystem(copyRequest *dto.UpdateFileSystemRequest) er 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() 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. - 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 { return fmt.Errorf("%w: nomad error during retrieve file content copy: %v", diff --git a/internal/runner/nomad_runner_test.go b/internal/runner/nomad_runner_test.go index 5fa2d5f..3571b41 100644 --- a/internal/runner/nomad_runner_test.go +++ b/internal/runner/nomad_runner_test.go @@ -403,6 +403,6 @@ func NewRunner(id string, manager Accessor) Runner { func (s *UpdateFileSystemTestSuite) TestGetFileContentReturnsErrorIfExitCodeIsNotZero() { s.mockedExecuteCommandCall.RunFn = 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) } diff --git a/internal/runner/runner.go b/internal/runner/runner.go index 14750f5..e177f8d 100644 --- a/internal/runner/runner.go +++ b/internal/runner/runner.go @@ -46,7 +46,7 @@ type Runner interface { // 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. - 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 // 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. // 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() error diff --git a/internal/runner/runner_mock.go b/internal/runner/runner_mock.go index e912c84..6a91d3b 100644 --- a/internal/runner/runner_mock.go +++ b/internal/runner/runner_mock.go @@ -92,13 +92,13 @@ func (_m *RunnerMock) ExecutionExists(id string) bool { return r0 } -// GetFileContent provides a mock function with given fields: path, content, ctx -func (_m *RunnerMock) GetFileContent(path string, content io.Writer, ctx context.Context) error { - ret := _m.Called(path, content, ctx) +// GetFileContent provides a mock function with given fields: path, content, privilegedExecution, ctx +func (_m *RunnerMock) GetFileContent(path string, content io.Writer, privilegedExecution bool, ctx context.Context) error { + ret := _m.Called(path, content, privilegedExecution, ctx) var r0 error - if rf, ok := ret.Get(0).(func(string, io.Writer, context.Context) error); ok { - r0 = rf(path, content, ctx) + if rf, ok := ret.Get(0).(func(string, io.Writer, bool, context.Context) error); ok { + r0 = rf(path, content, privilegedExecution, ctx) } else { r0 = ret.Error(0) } @@ -120,13 +120,13 @@ func (_m *RunnerMock) ID() string { return r0 } -// ListFileSystem provides a mock function with given fields: path, recursive, result, ctx -func (_m *RunnerMock) ListFileSystem(path string, recursive bool, result io.Writer, ctx context.Context) error { - ret := _m.Called(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, privilegedExecution bool, ctx context.Context) error { + ret := _m.Called(path, recursive, result, privilegedExecution, ctx) var r0 error - if rf, ok := ret.Get(0).(func(string, bool, io.Writer, context.Context) error); ok { - r0 = rf(path, recursive, result, ctx) + if rf, ok := ret.Get(0).(func(string, bool, io.Writer, bool, context.Context) error); ok { + r0 = rf(path, recursive, result, privilegedExecution, ctx) } else { r0 = ret.Error(0) }