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

Ensure that the .vault-token file writen by vault login always has the correct permissions and ownership. #8867

Merged
merged 2 commits into from Apr 27, 2020
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion command/config/util.go
Expand Up @@ -13,7 +13,7 @@ func DefaultTokenHelper() (token.TokenHelper, error) {

path := config.TokenHelper
if path == "" {
return &token.InternalTokenHelper{}, nil
return token.NewInternalTokenHelper()
}

path, err = token.ExternalTokenHelperPath(path)
Expand Down
40 changes: 30 additions & 10 deletions command/token/helper_internal.go
Expand Up @@ -9,6 +9,7 @@ import (
"strings"

homedir "github.com/mitchellh/go-homedir"
"github.com/natefinch/atomic"
)

var _ TokenHelper = (*InternalTokenHelper)(nil)
Expand All @@ -17,16 +18,21 @@ var _ TokenHelper = (*InternalTokenHelper)(nil)
// token-helper is configured, and avoids shelling out
type InternalTokenHelper struct {
tokenPath string
homeDir string
}

// populateTokenPath figures out the token path using homedir to get the user's
// home directory
func (i *InternalTokenHelper) populateTokenPath() {
homePath, err := homedir.Dir()
func NewInternalTokenHelper() (*InternalTokenHelper, error) {
homeDir, err := homedir.Dir()
if err != nil {
panic(fmt.Sprintf("error getting user's home directory: %v", err))
}
i.tokenPath = filepath.Join(homePath, ".vault-token")
return &InternalTokenHelper{homeDir: homeDir}, err
}

// populateTokenPath figures out the token path using homedir to get the user's
// home directory
func (i *InternalTokenHelper) populateTokenPath() {
i.tokenPath = filepath.Join(i.homeDir, ".vault-token")
}

func (i *InternalTokenHelper) Path() string {
Expand All @@ -53,21 +59,35 @@ func (i *InternalTokenHelper) Get() (string, error) {
return strings.TrimSpace(buf.String()), nil
}

// Store stores the value of the token to the file
// Store stores the value of the token to the file. We always overwrite any
// existing file atomically to ensure that ownership and permissions are set
// appropriately.
func (i *InternalTokenHelper) Store(input string) error {
i.populateTokenPath()
f, err := os.OpenFile(i.tokenPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
tmpFile := i.tokenPath + ".tmp"
f, err := os.OpenFile(tmpFile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
if err != nil {
return err
}
defer f.Close()
defer os.Remove(tmpFile)

buf := bytes.NewBufferString(input)
if _, err := io.Copy(f, buf); err != nil {
_, err = io.WriteString(f, input)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think we could use f.WriteString(input) here.

if err != nil {
return err
}
err = f.Close()
if err != nil {
return err
}

return nil
// We don't care so much about atomic writes here. We're using this package
// because we don't have a portable way of verifying that the target file
// is owned by the correct user. The simplest way of ensuring that is
// to simply re-write it, and the simplest way to ensure that we don't
// damage an existing working file due to error is the write-rename pattern.
// os.Rename on Windows will return an error if the target already exists.
return atomic.ReplaceFile(tmpFile, i.tokenPath)
}

// Erase erases the value of the token
Expand Down
53 changes: 52 additions & 1 deletion command/token/helper_internal_test.go
@@ -1,11 +1,62 @@
package token

import (
"io/ioutil"
"os"
"path/filepath"
"testing"
)

// TestCommand re-uses the existing Test function to ensure proper behavior of
// the internal token helper
func TestCommand(t *testing.T) {
Test(t, &InternalTokenHelper{})
helper, err := NewInternalTokenHelper()
if err != nil {
t.Fatal(err)
}
Test(t, helper)
}

func TestInternalHelperFilePerms(t *testing.T) {
tmpDir, err := ioutil.TempDir("", t.Name())
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpDir)

helper, err := NewInternalTokenHelper()
if err != nil {
t.Fatal(err)
}
helper.homeDir = tmpDir

tmpFile := filepath.Join(tmpDir, ".vault-token")
f, err := os.Create(tmpFile)
if err != nil {
t.Fatal(err)
}
defer f.Close()

fi, err := os.Stat(tmpFile)
if err != nil {
t.Fatal(err)
}

if fi.Mode().Perm()&004 != 004 {
t.Fatalf("expected world-readable/writable permission bits, got: %o", fi.Mode().Perm())
}

err = helper.Store("bogus_token")
if err != nil {
t.Fatal(err)
}

fi, err = os.Stat(tmpFile)
if err != nil {
t.Fatal(err)
}

if fi.Mode().Perm()&004 != 0 {
t.Fatalf("expected no world-readable/writable permission bits, got: %o", fi.Mode().Perm())
}
}
1 change: 1 addition & 0 deletions go.mod
Expand Up @@ -106,6 +106,7 @@ require (
github.com/mitchellh/go-testing-interface v1.0.0
github.com/mitchellh/mapstructure v1.1.2
github.com/mitchellh/reflectwalk v1.0.1
github.com/natefinch/atomic v0.0.0-20150920032501-a62ce929ffcc
github.com/ncw/swift v1.0.47
github.com/nwaples/rardecode v1.0.0 // indirect
github.com/oklog/run v1.0.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Expand Up @@ -578,6 +578,8 @@ github.com/mwielbut/pointy v1.1.0 h1:U5/YEfoIkaGCHv0St3CgjduqXID4FNRoyZgLM1kY9vg
github.com/mwielbut/pointy v1.1.0/go.mod h1:MvvO+uMFj9T5DMda33HlvogsFBX7pWWKAkFIn4teYwY=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/natefinch/atomic v0.0.0-20150920032501-a62ce929ffcc h1:7xGrl4tTpBQu5Zjll08WupHyq+Sp0Z/adtyf1cfk3Q8=
github.com/natefinch/atomic v0.0.0-20150920032501-a62ce929ffcc/go.mod h1:1rLVY/DWf3U6vSZgH16S7pymfrhK2lcUlXjgGglw/lY=
github.com/ncw/swift v1.0.47 h1:4DQRPj35Y41WogBxyhOXlrI37nzGlyEcsforeudyYPQ=
github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM=
github.com/nwaples/rardecode v1.0.0 h1:r7vGuS5akxOnR4JQSkko62RJ1ReCMXxQRPtxsiFMBOs=
Expand Down
4 changes: 4 additions & 0 deletions vendor/github.com/hashicorp/vault/api/sys_raft.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions vendor/github.com/natefinch/atomic/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions vendor/github.com/natefinch/atomic/LICENSE

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 35 additions & 0 deletions vendor/github.com/natefinch/atomic/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

58 changes: 58 additions & 0 deletions vendor/github.com/natefinch/atomic/atomic.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions vendor/github.com/natefinch/atomic/file_unix.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 33 additions & 0 deletions vendor/github.com/natefinch/atomic/file_windows.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.