// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package token import ( "bytes" "fmt" "io" "os" "path/filepath" "strings" homedir "github.com/mitchellh/go-homedir" "github.com/natefinch/atomic" ) var _ TokenHelper = (*InternalTokenHelper)(nil) // InternalTokenHelper fulfills the TokenHelper interface when no external // token-helper is configured, and avoids shelling out type InternalTokenHelper struct { tokenPath string homeDir string } func NewInternalTokenHelper() (*InternalTokenHelper, error) { homeDir, err := homedir.Dir() if err != nil { panic(fmt.Sprintf("error getting user's home directory: %v", err)) } 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 { return i.tokenPath } // Get gets the value of the stored token, if any func (i *InternalTokenHelper) Get() (string, error) { i.populateTokenPath() f, err := os.Open(i.tokenPath) if os.IsNotExist(err) { return "", nil } if err != nil { return "", err } defer f.Close() buf := bytes.NewBuffer(nil) if _, err := io.Copy(buf, f); err != nil { return "", err } return strings.TrimSpace(buf.String()), nil } // 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() tmpFile := i.tokenPath + ".tmp" f, err := os.OpenFile(tmpFile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o600) if err != nil { return err } defer f.Close() defer os.Remove(tmpFile) _, err = io.WriteString(f, input) if err != nil { return err } err = f.Close() if err != nil { return err } // 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 func (i *InternalTokenHelper) Erase() error { i.populateTokenPath() if err := os.Remove(i.tokenPath); err != nil && !os.IsNotExist(err) { return err } return nil }