Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

agent: allow changing file ownership in file sink #27123

Merged
merged 8 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions changelog/27123.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
```release-note:improvement
agent/sink: Allow configuration of the user and group ID of the file sink.
```
```release-note:improvement
proxy/sink: Allow configuration of the user and group ID of the file sink.
```
30 changes: 29 additions & 1 deletion command/agentproxyshared/sink/file/file_sink.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import (
type fileSink struct {
path string
mode os.FileMode
owner int
group int
logger hclog.Logger
}

Expand All @@ -33,6 +35,8 @@ func NewFileSink(conf *sink.SinkConfig) (sink.Sink, error) {
f := &fileSink{
logger: conf.Logger,
mode: 0o640,
owner: os.Getuid(),
group: os.Getgid(),
}

pathRaw, ok := conf.Config["path"]
Expand Down Expand Up @@ -61,11 +65,31 @@ func NewFileSink(conf *sink.SinkConfig) (sink.Sink, error) {
f.mode = os.FileMode(mode)
}

if modeRaw, ok := conf.Config["owner"]; ok {
owner, typeOK := modeRaw.(int)
if !typeOK {
return nil, errors.New("could not parse 'owner' as integer")
}

f.logger.Debug("overriding default file sink", "owner", owner)
f.owner = owner
}

if modeRaw, ok := conf.Config["group"]; ok {
group, typeOK := modeRaw.(int)
if !typeOK {
return nil, errors.New("could not parse 'group' as integer")
}

f.logger.Debug("overriding default file sink", "group", group)
f.group = group
}

if err := f.WriteToken(""); err != nil {
return nil, fmt.Errorf("error during write check: %w", err)
}

f.logger.Info("file sink configured", "path", f.path, "mode", f.mode)
f.logger.Info("file sink configured", "path", f.path, "mode", f.mode, "owner", f.owner, "group", f.group)

return f, nil
}
Expand Down Expand Up @@ -93,6 +117,10 @@ func (f *fileSink) WriteToken(token string) error {
return fmt.Errorf("error opening temp file in dir %s for writing: %w", targetDir, err)
}

if err := tmpFile.Chown(f.owner, f.group); err != nil {
return fmt.Errorf("error changing ownership of %s: %w", tmpFile.Name(), err)
}

valToWrite := token
if token == "" {
valToWrite = u
Expand Down
92 changes: 72 additions & 20 deletions command/agentproxyshared/sink/file/file_sink_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@
package file

import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"syscall"
"testing"

hclog "github.com/hashicorp/go-hclog"
Expand All @@ -16,15 +15,8 @@ import (
"github.com/hashicorp/vault/sdk/helper/logging"
)

const (
fileServerTestDir = "vault-agent-file-test"
)

func testFileSink(t *testing.T, log hclog.Logger) (*sink.SinkConfig, string) {
tmpDir, err := ioutil.TempDir("", fmt.Sprintf("%s.", fileServerTestDir))
if err != nil {
t.Fatal(err)
}
tmpDir := t.TempDir()

path := filepath.Join(tmpDir, "token")

Expand Down Expand Up @@ -74,7 +66,7 @@ func TestFileSink(t *testing.T) {
t.Fatal(err)
}

fileBytes, err := ioutil.ReadFile(path)
fileBytes, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
Expand All @@ -84,19 +76,17 @@ func TestFileSink(t *testing.T) {
}
}

func testFileSinkMode(t *testing.T, log hclog.Logger) (*sink.SinkConfig, string) {
tmpDir, err := ioutil.TempDir("", fmt.Sprintf("%s.", fileServerTestDir))
if err != nil {
t.Fatal(err)
}
func testFileSinkMode(t *testing.T, log hclog.Logger, gid int) (*sink.SinkConfig, string) {
tmpDir := t.TempDir()

path := filepath.Join(tmpDir, "token")

config := &sink.SinkConfig{
Logger: log.Named("sink.file"),
Config: map[string]interface{}{
"path": path,
"mode": 0o644,
"path": path,
"mode": 0o644,
"group": gid,
},
}

Expand All @@ -112,7 +102,65 @@ func testFileSinkMode(t *testing.T, log hclog.Logger) (*sink.SinkConfig, string)
func TestFileSinkMode(t *testing.T) {
log := logging.NewVaultLogger(hclog.Trace)

fs, tmpDir := testFileSinkMode(t, log)
fs, tmpDir := testFileSinkMode(t, log, os.Getegid())
defer os.RemoveAll(tmpDir)

path := filepath.Join(tmpDir, "token")

uuidStr, _ := uuid.GenerateUUID()
if err := fs.WriteToken(uuidStr); err != nil {
t.Fatal(err)
}

file, err := os.Open(path)
if err != nil {
t.Fatal(err)
}
defer file.Close()

fi, err := file.Stat()
if err != nil {
t.Fatal(err)
}
if fi.Mode() != os.FileMode(0o644) {
t.Fatalf("wrong file mode was detected at %s", path)
}

fileBytes, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}

if string(fileBytes) != uuidStr {
t.Fatalf("expected %s, got %s", uuidStr, string(fileBytes))
}
}

// TestFileSinkMode_Ownership tests that the file is owned by the group specified
// in the configuration. This test requires the current user to be in at least two
// groups. If the user is not in two groups, the test will be skipped.
func TestFileSinkMode_Ownership(t *testing.T) {
groups, err := os.Getgroups()
if err != nil {
t.Fatal(err)
}

if len(groups) < 2 {
t.Skip("not enough groups to test file ownership")
}

// find a group that is not the current group
var gid int
for _, g := range groups {
if g != os.Getegid() {
gid = g
break
}
}

log := logging.NewVaultLogger(hclog.Trace)

fs, tmpDir := testFileSinkMode(t, log, gid)
defer os.RemoveAll(tmpDir)

path := filepath.Join(tmpDir, "token")
Expand All @@ -135,8 +183,12 @@ func TestFileSinkMode(t *testing.T) {
if fi.Mode() != os.FileMode(0o644) {
t.Fatalf("wrong file mode was detected at %s", path)
}
// check if file is owned by the group
if fi.Sys().(*syscall.Stat_t).Gid != uint32(gid) {
t.Fatalf("file is not owned by the group %d", gid)
}

fileBytes, err := ioutil.ReadFile(path)
fileBytes, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ written with `0640` permissions as default, but can be overridden with the optio
## Configuration

- `path` `(string: required)` - The path to use to write the token file
- `mode` `(int: optional)` - A string containing an octal number representing the bit pattern for the file mode, similar to chmod. Set to `0000` to prevent Vault from modifying the file mode. Note: This configuration option is only available in Vault 1.3.0 and above.
- `mode` `(int: optional)` - Octal number string representing the bit pattern for the file mode, similar to `chmod`.
- `owner` `(int: optional)` - The UID to use for the token file. Defaults to the current user ID.
- `group` `(int: optional)` - The GID to use for token file. Defaults to the current group ID.

~> Note: Configuration options for response-wrapping and encryption for the sink
file are located within the [options common to all sinks](/vault/docs/agent-and-proxy/autoauth#configuration-sinks) documentation.
Loading