mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-30 02:02:43 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			161 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			161 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright (c) HashiCorp, Inc.
 | |
| // SPDX-License-Identifier: BUSL-1.1
 | |
| 
 | |
| package file
 | |
| 
 | |
| import (
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"strings"
 | |
| 
 | |
| 	hclog "github.com/hashicorp/go-hclog"
 | |
| 	uuid "github.com/hashicorp/go-uuid"
 | |
| 	"github.com/hashicorp/vault/command/agentproxyshared/sink"
 | |
| 	"github.com/hashicorp/vault/helper/osutil"
 | |
| )
 | |
| 
 | |
| // fileSink is a Sink implementation that writes a token to a file
 | |
| type fileSink struct {
 | |
| 	path   string
 | |
| 	mode   os.FileMode
 | |
| 	owner  int
 | |
| 	group  int
 | |
| 	logger hclog.Logger
 | |
| }
 | |
| 
 | |
| // NewFileSink creates a new file sink with the given configuration
 | |
| func NewFileSink(conf *sink.SinkConfig) (sink.Sink, error) {
 | |
| 	if conf.Logger == nil {
 | |
| 		return nil, errors.New("nil logger provided")
 | |
| 	}
 | |
| 
 | |
| 	conf.Logger.Info("creating file sink")
 | |
| 
 | |
| 	f := &fileSink{
 | |
| 		logger: conf.Logger,
 | |
| 		mode:   0o640,
 | |
| 		owner:  os.Getuid(),
 | |
| 		group:  os.Getgid(),
 | |
| 	}
 | |
| 
 | |
| 	pathRaw, ok := conf.Config["path"]
 | |
| 	if !ok {
 | |
| 		return nil, errors.New("'path' not specified for file sink")
 | |
| 	}
 | |
| 	path, ok := pathRaw.(string)
 | |
| 	if !ok {
 | |
| 		return nil, errors.New("could not parse 'path' as string")
 | |
| 	}
 | |
| 
 | |
| 	f.path = path
 | |
| 
 | |
| 	if modeRaw, ok := conf.Config["mode"]; ok {
 | |
| 		f.logger.Debug("verifying override for default file sink mode")
 | |
| 		mode, typeOK := modeRaw.(int)
 | |
| 		if !typeOK {
 | |
| 			return nil, errors.New("could not parse 'mode' as integer")
 | |
| 		}
 | |
| 
 | |
| 		if !os.FileMode(mode).IsRegular() {
 | |
| 			return nil, fmt.Errorf("file mode does not represent a regular file")
 | |
| 		}
 | |
| 
 | |
| 		f.logger.Debug("overriding default file sink", "mode", mode)
 | |
| 		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, "owner", f.owner, "group", f.group)
 | |
| 
 | |
| 	return f, nil
 | |
| }
 | |
| 
 | |
| // WriteToken implements the Server interface and writes the token to a path on
 | |
| // disk. It writes into the path's directory into a temp file and does an
 | |
| // atomic rename to ensure consistency. If a blank token is passed in, it
 | |
| // performs a write check but does not write a blank value to the final
 | |
| // location.
 | |
| func (f *fileSink) WriteToken(token string) error {
 | |
| 	f.logger.Trace("enter write_token", "path", f.path)
 | |
| 	defer f.logger.Trace("exit write_token", "path", f.path)
 | |
| 
 | |
| 	u, err := uuid.GenerateUUID()
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("error generating a uuid during write check: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	targetDir := filepath.Dir(f.path)
 | |
| 	fileName := filepath.Base(f.path)
 | |
| 	tmpSuffix := strings.Split(u, "-")[0]
 | |
| 
 | |
| 	tmpFile, err := os.OpenFile(filepath.Join(targetDir, fmt.Sprintf("%s.tmp.%s", fileName, tmpSuffix)), os.O_WRONLY|os.O_CREATE, f.mode)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("error opening temp file in dir %s for writing: %w", targetDir, err)
 | |
| 	}
 | |
| 
 | |
| 	if err := osutil.Chown(tmpFile, f.owner, f.group); err != nil {
 | |
| 		return fmt.Errorf("error changing ownership of %s: %w", tmpFile.Name(), err)
 | |
| 	}
 | |
| 
 | |
| 	valToWrite := token
 | |
| 	if token == "" {
 | |
| 		valToWrite = u
 | |
| 	}
 | |
| 
 | |
| 	_, err = tmpFile.WriteString(valToWrite)
 | |
| 	if err != nil {
 | |
| 		// Attempt closing and deleting but ignore any error
 | |
| 		tmpFile.Close()
 | |
| 		os.Remove(tmpFile.Name())
 | |
| 		return fmt.Errorf("error writing to %s: %w", tmpFile.Name(), err)
 | |
| 	}
 | |
| 
 | |
| 	err = tmpFile.Close()
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("error closing %s: %w", tmpFile.Name(), err)
 | |
| 	}
 | |
| 
 | |
| 	// Now, if we were just doing a write check (blank token), remove the file
 | |
| 	// and exit; otherwise, atomically rename it
 | |
| 	if token == "" {
 | |
| 		err = os.Remove(tmpFile.Name())
 | |
| 		if err != nil {
 | |
| 			return fmt.Errorf("error removing temp file %s during write check: %w", tmpFile.Name(), err)
 | |
| 		}
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	err = os.Rename(tmpFile.Name(), f.path)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("error renaming temp file %s to target file %s: %w", tmpFile.Name(), f.path, err)
 | |
| 	}
 | |
| 
 | |
| 	f.logger.Info("token written", "path", f.path)
 | |
| 	return nil
 | |
| }
 | 
