Skip to content

Commit be0dcbe

Browse files
authored
Add support for non-root user mode to the init image (#955)
* init: add non-root user mode support The aws-for-fluent-bit init image creates configs and other resources in the /init directory (root filesystem). If a customer wants to run a container (with this image), with non-root user configured, the init setup fails. This commit adds a fallback logic. When write to /init fails, it falls back to /tmp/init. It enables non-root user mode support. * go.mod: update the module name Currently, the go.mod only pulls in dependencies for the load_tests/validation module. This update allows go.mod to pull in dependencies for other submodules like init.
1 parent 7acd76a commit be0dcbe

File tree

5 files changed

+117
-15
lines changed

5 files changed

+117
-15
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
bin
22
integ/out/
33
.venv
4-
/.idea/
4+
/.idea/
5+
.vscode/

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module validation
1+
module github.com/aws/aws-for-fluent-bit
22

33
go 1.24.3
44

init/fluent_bit_init_entrypoint.sh

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,14 @@
1+
#!/bin/bash
2+
3+
# Run the init Go application first, that sets up the fluent bit configs and the invoke script as needed
14
./init/fluent_bit_init_process
2-
source /init/invoke_fluent_bit.sh
5+
6+
# Source the invoke script from the appropriate location
7+
if [ -f /init/invoke_fluent_bit.sh ]; then
8+
source /init/invoke_fluent_bit.sh
9+
elif [ -f /tmp/init/invoke_fluent_bit.sh ]; then
10+
source /tmp/init/invoke_fluent_bit.sh
11+
else
12+
echo "Error: invoke_fluent_bit.sh not found in /init or /tmp/init"
13+
exit 1
14+
fi

init/fluent_bit_init_process.go

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,26 +19,74 @@ import (
1919
"github.com/sirupsen/logrus"
2020
)
2121

22-
// static paths
2322
const (
24-
s3FileDirectoryPath = "/init/fluent-bit-init-s3-files/"
25-
mainConfigFile = "/init/fluent-bit-init.conf"
23+
// Default paths
24+
primaryS3FileDirectoryPath = "/init/fluent-bit-init-s3-files/"
25+
primaryMainConfigFile = "/init/fluent-bit-init.conf"
26+
primaryInvokeFile = "/init/invoke_fluent_bit.sh"
27+
28+
// Fallback paths when we do not have write permissions to the /init/ directory
29+
fallbackS3FileDirectoryPath = "/tmp/init/fluent-bit-init-s3-files/"
30+
fallbackMainConfigFile = "/tmp/init/fluent-bit-init.conf"
31+
fallbackInvokeFile = "/tmp/init/invoke_fluent_bit.sh"
32+
33+
// This is typically configured by the orchestrator like ECS during container start
2634
originalMainConfigFile = "/fluent-bit/etc/fluent-bit.conf"
27-
invokeFile = "/init/invoke_fluent_bit.sh"
2835
)
2936

3037
var (
31-
// default Fluent Bit command
38+
// Default Fluent Bit command
3239
baseCommand = "exec /fluent-bit/bin/fluent-bit -e /fluent-bit/firehose.so -e /fluent-bit/cloudwatch.so -e /fluent-bit/kinesis.so"
3340

34-
// global s3 client and flag
41+
// Global S3 client and flag
3542
s3Client *s3.Client
3643
s3ClientCreated bool = false
3744

38-
// global ecs metadata region
45+
// Global ECS metadata region
3946
metadataRegion string = ""
47+
48+
// Runtime-determined paths (set in the init() function)
49+
s3FileDirectoryPath string
50+
mainConfigFile string
51+
invokeFile string
52+
53+
// osCreate holds the os.Create function, allowing it to be mocked in tests
54+
osCreate = os.Create
4055
)
4156

57+
// Initialize paths based on write permissions
58+
func init() {
59+
if canWriteToDir("/init") {
60+
logrus.Info("[FluentBit Init Process] Using /init/ directory")
61+
s3FileDirectoryPath = primaryS3FileDirectoryPath
62+
mainConfigFile = primaryMainConfigFile
63+
invokeFile = primaryInvokeFile
64+
} else {
65+
logrus.Info("[FluentBit Init Process] Using /tmp/init/ directory since I do not have write access to the /init/ directory")
66+
s3FileDirectoryPath = fallbackS3FileDirectoryPath
67+
mainConfigFile = fallbackMainConfigFile
68+
invokeFile = fallbackInvokeFile
69+
}
70+
}
71+
72+
// canWriteToDir tests whether we can write to the specified directory
73+
func canWriteToDir(dir string) bool {
74+
testFile := filepath.Join(dir, ".write-test")
75+
file, err := osCreate(testFile)
76+
if err != nil {
77+
return false
78+
}
79+
err = file.Close()
80+
if err != nil {
81+
logrus.Warnf("[FluentBit Init Process] Unable to close test file %s, err: %v", testFile, err)
82+
}
83+
err = os.Remove(testFile)
84+
if err != nil {
85+
logrus.Warnf("[FluentBit Init Process] Unable to remove test file %s, err: %v", testFile, err)
86+
}
87+
return true
88+
}
89+
4290
// HTTPClient interface
4391
type HTTPClient interface {
4492
Get(url string) (*http.Response, error)
@@ -152,7 +200,7 @@ func createCommand(command *string, filePath string) {
152200

153201
// get our built in config files or files from s3
154202
// process built-in config files directly
155-
// add S3 config files to directory "/init/fluent-bit-init-s3-files/"
203+
// add S3 config files to directory "{init-directory}/fluent-bit-init-s3-files/"
156204
func getAllConfigFiles() {
157205
// get all env vars in the container
158206
envs := os.Environ()
@@ -246,7 +294,7 @@ func getS3ConfigFile(userInput string) string {
246294
// create a downloader
247295
s3Downloader := createS3Downloader(bucketRegion)
248296

249-
// download file from S3 and store in the directory "/init/fluent-bit-init-s3-files/"
297+
// download file from S3 and store in the directory "{init-directory}/fluent-bit-init-s3-files/"
250298
downloadS3ConfigFile(s3Downloader, s3FilePath, bucketName, s3FileDirectoryPath)
251299

252300
return s3FilePath
@@ -399,7 +447,7 @@ func main() {
399447

400448
// get our built in config files or files from s3
401449
// process built-in config files directly
402-
// add S3 config files to directory "/init/fluent-bit-init-s3-files/"
450+
// add S3 config files to directory "{init-directory}/fluent-bit-init-s3-files/"
403451
getAllConfigFiles()
404452

405453
// modify invoke_fluent_bit.sh, invoke fluent bit

init/fluent_bit_init_process_test.go

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,6 @@ func TestDownloadS3ConfigFile(t *testing.T) {
265265
for _, test := range cases {
266266
testName := fmt.Sprintf("FailFirstAttempt-%t-downloadCount-%d", test.failFirstAttempt, test.downloadCount)
267267
t.Run(testName, func(t *testing.T) {
268-
os.RemoveAll(s3FileDirectoryPathTest)
269268
downloader := &MockS3Downloader{
270269
FailFirstAttempt: test.failFirstAttempt,
271270
}
@@ -278,6 +277,7 @@ func TestDownloadS3ConfigFile(t *testing.T) {
278277

279278
// Verify downloader was called with correct parameters
280279
assert.Equal(t, test.downloadCount, downloader.DownloadCount)
280+
os.RemoveAll(s3FileDirectoryPathTest)
281281
})
282282
}
283283
}
@@ -365,8 +365,13 @@ func TestCreateFile(t *testing.T) {
365365
t.Run(testName, func(t *testing.T) {
366366
fileName := path.Join(filePath, testName)
367367
file := createFile(fileName, test.autoClose)
368-
369368
assert.NotNil(t, file)
369+
370+
// Verify directory permissions are 0700
371+
dirInfo, err := os.Stat(path.Dir(fileName))
372+
assert.NoError(t, err)
373+
assert.Equal(t, os.FileMode(0700), dirInfo.Mode().Perm(), "Directory should have 0700 permissions")
374+
370375
if !test.autoClose {
371376
file.Close()
372377
}
@@ -507,3 +512,39 @@ func writeFileHelper(filePath, writeContent string) {
507512
fmt.Printf("Cannot write %s in file %s", writeContent, filePath)
508513
}
509514
}
515+
516+
func TestCanWriteToDir(t *testing.T) {
517+
originalOSCreate := osCreate
518+
defer func() {
519+
osCreate = originalOSCreate
520+
}()
521+
522+
testCases := []struct {
523+
name string
524+
mockOsCreate func(name string) (*os.File, error)
525+
expectedResult bool
526+
}{
527+
{
528+
name: "Can write",
529+
mockOsCreate: func(name string) (*os.File, error) {
530+
dir := t.TempDir()
531+
return os.CreateTemp(dir, "mock")
532+
},
533+
expectedResult: true,
534+
},
535+
{
536+
name: "Cannot write",
537+
mockOsCreate: func(name string) (*os.File, error) {
538+
return nil, fmt.Errorf("Permission denied")
539+
},
540+
expectedResult: false,
541+
},
542+
}
543+
for _, tc := range testCases {
544+
t.Run(tc.name, func(t *testing.T) {
545+
osCreate = tc.mockOsCreate
546+
result := canWriteToDir("/init/")
547+
assert.Equal(t, tc.expectedResult, result)
548+
})
549+
}
550+
}

0 commit comments

Comments
 (0)