Support protected directories

by setting the sticky bit to all explicitly requested directories.
This commit is contained in:
Maximilian Paß
2022-10-25 11:38:16 +01:00
committed by Sebastian Serth
parent b1de6faa03
commit b3eee17846
3 changed files with 96 additions and 2 deletions

View File

@ -404,7 +404,7 @@ paths:
type: object type: object
properties: properties:
path: path:
description: Location where the file should be placed. Can be absolute (starting with /) or relative to the workspace directory. Missing parent directories are created. If this ends with a /, the path is interpreted as a directory and content is ignored description: Location where the file should be placed. Can be absolute (starting with /) or relative to the workspace directory. Missing parent directories are created. If this ends with a /, the path is interpreted as a directory and content is ignored. Currently, every file/directory is owned by root but the directories have the sticky bit set to allow unprivileged file creation.
type: string type: string
example: /etc/passwd example: /etc/passwd
content: content:

View File

@ -342,7 +342,7 @@ func tarHeader(file dto.File) *tar.Header {
return &tar.Header{ return &tar.Header{
Typeflag: tar.TypeDir, Typeflag: tar.TypeDir,
Name: file.CleanedPath(), Name: file.CleanedPath(),
Mode: 0o755, Mode: 0o1777, // See #236. Sticky bit is to allow creating files next to write-protected files.
} }
} else { } else {
return &tar.Header{ return &tar.Header{

View File

@ -325,6 +325,100 @@ func (s *E2ETestSuite) TestCopyFilesRoute_PermissionDenied() {
}) })
} }
func (s *E2ETestSuite) TestCopyFilesRoute_ProtectedFolders() {
runnerID, err := ProvideRunner(&dto.RunnerRequest{ExecutionEnvironmentID: tests.DefaultEnvironmentIDAsInteger})
s.NoError(err)
// Initialization of protected folder
newFileContent := []byte("New content")
protectedFolderPath := dto.FilePath("protectedFolder/")
copyFilesRequestByteString, err := json.Marshal(&dto.UpdateFileSystemRequest{
Copy: []dto.File{
{Path: protectedFolderPath},
{Path: protectedFolderPath + tests.DefaultFileName, Content: newFileContent},
},
})
s.Require().NoError(err)
resp, err := helpers.HTTPPatch(helpers.BuildURL(api.BasePath, api.RunnersPath, runnerID, api.UpdateFileSystemPath),
"application/json", bytes.NewReader(copyFilesRequestByteString))
s.NoError(err)
s.Equal(http.StatusNoContent, resp.StatusCode)
// User manipulates protected folder
s.Run("User can create files", func() {
webSocketURL, err := ProvideWebSocketURL(&s.Suite, runnerID, &dto.ExecutionRequest{
Command: fmt.Sprintf("touch %s/userfile", protectedFolderPath),
TimeLimit: int(tests.DefaultTestTimeout.Seconds()),
PrivilegedExecution: false,
})
s.Require().NoError(err)
connection, err := ConnectToWebSocket(webSocketURL)
s.Require().NoError(err)
messages, err := helpers.ReceiveAllWebSocketMessages(connection)
s.Require().Error(err)
s.Equal(&websocket.CloseError{Code: websocket.CloseNormalClosure}, err)
stdout, stderr, _ := helpers.WebSocketOutputMessages(messages)
s.Empty(stdout)
s.Empty(stderr)
})
s.Run("User can not delete protected folder", func() {
webSocketURL, err := ProvideWebSocketURL(&s.Suite, runnerID, &dto.ExecutionRequest{
Command: fmt.Sprintf("rm -fr %s", protectedFolderPath),
TimeLimit: int(tests.DefaultTestTimeout.Seconds()),
PrivilegedExecution: false,
})
s.Require().NoError(err)
connection, err := ConnectToWebSocket(webSocketURL)
s.Require().NoError(err)
messages, err := helpers.ReceiveAllWebSocketMessages(connection)
s.Require().Error(err)
s.Equal(&websocket.CloseError{Code: websocket.CloseNormalClosure}, err)
stdout, stderr, _ := helpers.WebSocketOutputMessages(messages)
s.Empty(stdout)
s.Contains(stderr, "Operation not permitted")
})
s.Run("User can not delete protected file", func() {
webSocketURL, err := ProvideWebSocketURL(&s.Suite, runnerID, &dto.ExecutionRequest{
Command: fmt.Sprintf("rm -f %s", protectedFolderPath+tests.DefaultFileName),
TimeLimit: int(tests.DefaultTestTimeout.Seconds()),
PrivilegedExecution: false,
})
s.Require().NoError(err)
connection, err := ConnectToWebSocket(webSocketURL)
s.Require().NoError(err)
messages, err := helpers.ReceiveAllWebSocketMessages(connection)
s.Require().Error(err)
s.Equal(&websocket.CloseError{Code: websocket.CloseNormalClosure}, err)
stdout, stderr, _ := helpers.WebSocketOutputMessages(messages)
s.Empty(stdout)
s.Contains(stderr, "Operation not permitted")
})
s.Run("User can not write protected file", func() {
webSocketURL, err := ProvideWebSocketURL(&s.Suite, runnerID, &dto.ExecutionRequest{
Command: fmt.Sprintf("echo Hi >> %s", protectedFolderPath+tests.DefaultFileName),
TimeLimit: int(tests.DefaultTestTimeout.Seconds()),
PrivilegedExecution: false,
})
s.Require().NoError(err)
connection, err := ConnectToWebSocket(webSocketURL)
s.Require().NoError(err)
messages, err := helpers.ReceiveAllWebSocketMessages(connection)
s.Require().Error(err)
s.Equal(&websocket.CloseError{Code: websocket.CloseNormalClosure}, err)
stdout, stderr, _ := helpers.WebSocketOutputMessages(messages)
s.Empty(stdout)
s.Contains(stderr, "Permission denied")
})
s.Run("File content is not manipulated", func() {
s.assertFileContent(runnerID, string(protectedFolderPath+tests.DefaultFileName), string(newFileContent))
})
}
func (s *E2ETestSuite) TestGetFileContent_Nomad() { func (s *E2ETestSuite) TestGetFileContent_Nomad() {
runnerID, err := ProvideRunner(&dto.RunnerRequest{ runnerID, err := ProvideRunner(&dto.RunnerRequest{
ExecutionEnvironmentID: tests.DefaultEnvironmentIDAsInteger, ExecutionEnvironmentID: tests.DefaultEnvironmentIDAsInteger,