Skip to content

Commit 124aae9

Browse files
committed
Merge branch 'main' into nap-default-server
2 parents 7ba2ab7 + 5642dbe commit 124aae9

File tree

3 files changed

+225
-1
lines changed

3 files changed

+225
-1
lines changed

internal/file/file_manager_service.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"os"
1616
"path"
1717
"path/filepath"
18+
"strconv"
1819
"sync"
1920

2021
"google.golang.org/grpc"
@@ -36,6 +37,7 @@ const (
3637
maxAttempts = 5
3738
dirPerm = 0o755
3839
filePerm = 0o600
40+
executePerm = 0o111
3941
)
4042

4143
type (
@@ -165,6 +167,11 @@ func (fms *FileManagerService) ConfigApply(ctx context.Context,
165167
return model.Error, allowedErr
166168
}
167169

170+
permissionErr := fms.validateAndUpdateFilePermissions(ctx, fileOverview.GetFiles())
171+
if permissionErr != nil {
172+
return model.RollbackRequired, permissionErr
173+
}
174+
168175
diffFiles, compareErr := fms.DetermineFileActions(
169176
ctx,
170177
fms.currentFilesOnDisk,
@@ -660,6 +667,49 @@ func (fms *FileManagerService) checkAllowedDirectory(checkFiles []*mpi.File) err
660667
return nil
661668
}
662669

670+
func (fms *FileManagerService) validateAndUpdateFilePermissions(ctx context.Context, fileList []*mpi.File) error {
671+
for _, file := range fileList {
672+
if fms.areExecuteFilePermissionsSet(file) {
673+
resetErr := fms.removeExecuteFilePermissions(ctx, file)
674+
if resetErr != nil {
675+
return fmt.Errorf("failed to reset permissions for %s: %w", file.GetFileMeta().GetName(), resetErr)
676+
}
677+
}
678+
}
679+
680+
return nil
681+
}
682+
683+
func (fms *FileManagerService) areExecuteFilePermissionsSet(file *mpi.File) bool {
684+
filePermission := file.GetFileMeta().GetPermissions()
685+
686+
permissionOctal, err := strconv.ParseUint(filePermission, 8, 32)
687+
if err != nil || len(filePermission) != 4 {
688+
return false
689+
}
690+
691+
return permissionOctal&executePerm > 0
692+
}
693+
694+
func (fms *FileManagerService) removeExecuteFilePermissions(ctx context.Context, file *mpi.File) error {
695+
filePermission := file.GetFileMeta().GetPermissions()
696+
697+
permissionOctal, err := strconv.ParseUint(filePermission, 8, 32)
698+
if err != nil {
699+
return fmt.Errorf("falied to parse file permissions: %w", err)
700+
}
701+
702+
permissionOctal &^= executePerm
703+
704+
newPermission := "0" + strconv.FormatUint(permissionOctal, 8)
705+
file.FileMeta.Permissions = newPermission
706+
707+
slog.DebugContext(ctx, "Permissions have been changed", "file", file.GetFileMeta().GetName(),
708+
"old_permissions", filePermission, "new_permissions", newPermission)
709+
710+
return nil
711+
}
712+
663713
func (fms *FileManagerService) convertToManifestFileMap(
664714
currentFiles map[string]*mpi.File,
665715
referenced bool,

internal/file/file_manager_service_test.go

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,54 @@ func TestFileManagerService_ConfigApply_Failed(t *testing.T) {
301301
assert.False(t, fileManagerService.rollbackManifest)
302302
}
303303

304+
func TestFileManagerService_ConfigApply_FileWithExecutePermissions(t *testing.T) {
305+
ctx := context.Background()
306+
tempDir := t.TempDir()
307+
308+
filePath := filepath.Join(tempDir, "nginx.conf")
309+
310+
fileContent := []byte("location /test {\n return 200 \"Test location\\n\";\n}")
311+
fileHash := files.GenerateHash(fileContent)
312+
defer helpers.RemoveFileWithErrorCheck(t, filePath)
313+
314+
overview := protos.FileOverview(filePath, fileHash)
315+
316+
overview.GetFiles()[0].GetFileMeta().Permissions = "0755"
317+
318+
manifestDirPath := tempDir
319+
manifestFilePath := filepath.Join(manifestDirPath, "manifest.json")
320+
helpers.CreateFileWithErrorCheck(t, manifestDirPath, "manifest.json")
321+
322+
fakeFileServiceClient := &v1fakes.FakeFileServiceClient{}
323+
fakeFileServiceClient.GetOverviewReturns(&mpi.GetOverviewResponse{
324+
Overview: overview,
325+
}, nil)
326+
fakeFileServiceClient.GetFileReturns(&mpi.GetFileResponse{
327+
Contents: &mpi.FileContents{
328+
Contents: fileContent,
329+
},
330+
}, nil)
331+
agentConfig := types.AgentConfig()
332+
agentConfig.AllowedDirectories = []string{tempDir}
333+
334+
fileManagerService := NewFileManagerService(fakeFileServiceClient, agentConfig, &sync.RWMutex{})
335+
fileManagerService.configPath = filepath.Dir(filePath)
336+
fileManagerService.agentConfig.LibDir = manifestDirPath
337+
fileManagerService.manifestFilePath = manifestFilePath
338+
339+
request := protos.CreateConfigApplyRequest(overview)
340+
writeStatus, err := fileManagerService.ConfigApply(ctx, request)
341+
require.NoError(t, err)
342+
assert.Equal(t, model.OK, writeStatus)
343+
assert.Equal(t, "0644", fileManagerService.fileActions[filePath].File.GetFileMeta().GetPermissions())
344+
data, readErr := os.ReadFile(filePath)
345+
require.NoError(t, readErr)
346+
assert.Equal(t, fileContent, data)
347+
assert.Equal(t, fileManagerService.fileActions[filePath].File, overview.GetFiles()[0])
348+
assert.Equal(t, 1, fakeFileServiceClient.GetFileCallCount())
349+
assert.True(t, fileManagerService.rollbackManifest)
350+
}
351+
304352
func TestFileManagerService_checkAllowedDirectory(t *testing.T) {
305353
fakeFileServiceClient := &v1fakes.FakeFileServiceClient{}
306354
fileManagerService := NewFileManagerService(fakeFileServiceClient, types.AgentConfig(), &sync.RWMutex{})
@@ -335,6 +383,132 @@ func TestFileManagerService_checkAllowedDirectory(t *testing.T) {
335383
require.Error(t, err)
336384
}
337385

386+
func TestFileManagerService_validateAndUpdateFilePermissions(t *testing.T) {
387+
ctx := context.Background()
388+
fileManagerService := NewFileManagerService(nil, types.AgentConfig(), &sync.RWMutex{})
389+
390+
testFiles := []*mpi.File{
391+
{
392+
FileMeta: &mpi.FileMeta{
393+
Name: "exec.conf",
394+
Permissions: "0700",
395+
},
396+
},
397+
{
398+
FileMeta: &mpi.FileMeta{
399+
Name: "normal.conf",
400+
Permissions: "0620",
401+
},
402+
},
403+
}
404+
405+
err := fileManagerService.validateAndUpdateFilePermissions(ctx, testFiles)
406+
require.NoError(t, err)
407+
assert.Equal(t, "0600", testFiles[0].GetFileMeta().GetPermissions())
408+
assert.Equal(t, "0620", testFiles[1].GetFileMeta().GetPermissions())
409+
}
410+
411+
func TestFileManagerService_areExecuteFilePermissionsSet(t *testing.T) {
412+
fileManagerService := NewFileManagerService(nil, types.AgentConfig(), &sync.RWMutex{})
413+
414+
tests := []struct {
415+
name string
416+
permissions string
417+
expectBool bool
418+
}{
419+
{
420+
name: "Test 1: File with read and write permissions for owner",
421+
permissions: "0600",
422+
expectBool: false,
423+
},
424+
{
425+
name: "Test 2: File with read/write and execute permissions for owner",
426+
permissions: "0700",
427+
expectBool: true,
428+
},
429+
{
430+
name: "Test 3: File with read/write and execute permissions for owner and group",
431+
permissions: "0770",
432+
expectBool: true,
433+
},
434+
{
435+
name: "Test 4: File with read and execute permissions for everyone",
436+
permissions: "0555",
437+
expectBool: true,
438+
},
439+
{
440+
name: "Test 5: File with malformed permissions",
441+
permissions: "abcde",
442+
expectBool: false,
443+
},
444+
{
445+
name: "Test 6: File with invalid permissions",
446+
permissions: "000070",
447+
expectBool: false,
448+
},
449+
}
450+
451+
for _, test := range tests {
452+
t.Run(test.name, func(t *testing.T) {
453+
file := &mpi.File{
454+
FileMeta: &mpi.FileMeta{
455+
Name: "test.conf",
456+
Permissions: test.permissions,
457+
},
458+
}
459+
460+
got := fileManagerService.areExecuteFilePermissionsSet(file)
461+
assert.Equal(t, test.expectBool, got)
462+
})
463+
}
464+
}
465+
466+
func TestFileManagerService_removeExecuteFilePermissions(t *testing.T) {
467+
fileManagerService := NewFileManagerService(nil, types.AgentConfig(), &sync.RWMutex{})
468+
469+
tests := []struct {
470+
name string
471+
permissions string
472+
errorMsg string
473+
expectPermissions string
474+
expectError bool
475+
}{
476+
{
477+
name: "Test 1: File with execute permissions for owner and others",
478+
permissions: "0703",
479+
expectError: false,
480+
expectPermissions: "0602",
481+
},
482+
{
483+
name: "Test 2: File with malformed permissions",
484+
permissions: "abcde",
485+
expectError: true,
486+
errorMsg: "falied to parse file permissions",
487+
},
488+
}
489+
490+
for _, test := range tests {
491+
t.Run(test.name, func(t *testing.T) {
492+
file := &mpi.File{
493+
FileMeta: &mpi.FileMeta{
494+
Name: "test.conf",
495+
Permissions: test.permissions,
496+
},
497+
}
498+
499+
parseErr := fileManagerService.removeExecuteFilePermissions(t.Context(), file)
500+
501+
if test.expectError {
502+
require.Error(t, parseErr)
503+
assert.Contains(t, parseErr.Error(), test.errorMsg)
504+
} else {
505+
require.NoError(t, parseErr)
506+
assert.Equal(t, test.expectPermissions, file.GetFileMeta().GetPermissions())
507+
}
508+
})
509+
}
510+
}
511+
338512
//nolint:usetesting // need to use MkDirTemp instead of t.tempDir for rollback as t.tempDir does not accept a pattern
339513
func TestFileManagerService_ClearCache(t *testing.T) {
340514
tempDir := t.TempDir()

test/helpers/test_containers_utils.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import (
1818
"github.com/testcontainers/testcontainers-go/wait"
1919
)
2020

21-
const configFilePermissions = 0o700
21+
const configFilePermissions = 0o600
2222

2323
type Parameters struct {
2424
NginxConfigPath string

0 commit comments

Comments
 (0)