Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 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
58 changes: 47 additions & 11 deletions age/keysource.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (

"github.com/getsops/sops/v3/logging"
"github.com/google/shlex"
"sync"
)

const (
Expand Down Expand Up @@ -55,6 +56,8 @@ const (
// log is the global logger for any age MasterKey.
var log *logrus.Logger

var fileStreamCache sync.Map

func init() {
log = logging.NewLogger("AGE")
}
Expand Down Expand Up @@ -399,14 +402,45 @@ func getUserConfigDir() (string, error) {
return os.UserConfigDir()
}

// ClearFileStreamCache wipes the cached stream secrets from memory by overwriting
// the byte slices with zeros before deleting them from the map.
// This is critical for security to prevent keys from lingering in RAM.
func ClearFileStreamCache() {
fileStreamCache.Range(func(key, value interface{}) bool {
if byte, ok := value.([]byte); ok {
for i := range byte {
byte[i] = 0
}
}
fileStreamCache.Delete(key)
return true
})
}

// reads a file from the given path, if it is a stream (e.g., /dev/fd/* or /proc/*)
// it caches the content in memory to avoid issues with multiple reads from the same stream.
func readStreamSafe(path string) ([]byte, error) {
isStream := strings.HasPrefix(path, "/dev/fd/") || strings.HasPrefix(path, "/proc/") || strings.HasPrefix(path, "/dev/stdin")
Comment thread
Dexmachi marked this conversation as resolved.
Outdated

if isStream {
if cached, ok := fileStreamCache.Load(path); ok {
return cached.([]byte), nil
}
}

b, err := os.ReadFile(path)
if err == nil && isStream {
fileStreamCache.Store(path, b)
}
return b, err
}

// loadIdentities attempts to load the age identities based on runtime
// environment configurations (e.g. SopsAgeKeyEnv, SopsAgeKeyFileEnv,
// SopsAgeSshPrivateKeyFileEnv, SopsAgeKeyUserConfigPath). It will load all
// environment configurations (e.g. SopsAgeKeyUserConfigPath). It will load all
// found references, and expects at least one configuration to be present.
func (key *MasterKey) loadIdentities() (ParsedIdentities, []string, errSet) {
identities, unusedLocations, errs := key.loadAgeSSHIdentities()

var readers = make(map[string]io.Reader, 0)
readers := make(map[string]io.Reader)

if ageKey, ok := os.LookupEnv(SopsAgeKeyEnv); ok {
readers[SopsAgeKeyEnv] = strings.NewReader(ageKey)
Expand All @@ -415,19 +449,21 @@ func (key *MasterKey) loadIdentities() (ParsedIdentities, []string, errSet) {
}

if ageKeyFile, ok := os.LookupEnv(SopsAgeKeyFileEnv); ok {
f, err := os.Open(ageKeyFile)
b, err := readStreamSafe(ageKeyFile)
if err != nil {
errs = append(errs, fmt.Errorf("failed to open %s file: %w", SopsAgeKeyFileEnv, err))
} else {
defer f.Close()
readers[SopsAgeKeyFileEnv] = f
readers[SopsAgeKeyFileEnv] = bytes.NewReader(b)
}
} else {
unusedLocations = append(unusedLocations, SopsAgeKeyFileEnv)
}

if ageKeyCmd, ok := os.LookupEnv(SopsAgeKeyCmdEnv); ok {
out, err := getOutputFromCmd(ageKeyCmd, []string{fmt.Sprintf("%s=%s", SopsAgeRecipientEnv, key.Recipient)})
out, err := getOutputFromCmd(
ageKeyCmd,
[]string{fmt.Sprintf("%s=%s", SopsAgeRecipientEnv, key.Recipient)},
)
if err != nil {
errs = append(errs, err)
} else {
Expand All @@ -442,14 +478,13 @@ func (key *MasterKey) loadIdentities() (ParsedIdentities, []string, errSet) {
errs = append(errs, fmt.Errorf("user config directory could not be determined: %w", err))
} else if userConfigDir != "" {
ageKeyFilePath := filepath.Join(userConfigDir, filepath.FromSlash(SopsAgeKeyUserConfigPath))
f, err := os.Open(ageKeyFilePath)
b, err := readStreamSafe(ageKeyFilePath)
if err != nil && !errors.Is(err, os.ErrNotExist) {
errs = append(errs, fmt.Errorf("failed to open file: %w", err))
} else if errors.Is(err, os.ErrNotExist) && len(readers) == 0 && len(identities) == 0 {
unusedLocations = append(unusedLocations, ageKeyFilePath)
} else if err == nil {
defer f.Close()
readers[ageKeyFilePath] = f
readers[ageKeyFilePath] = bytes.NewReader(b)
}
}

Expand All @@ -464,6 +499,7 @@ func (key *MasterKey) loadIdentities() (ParsedIdentities, []string, errSet) {
}
}
}

return identities, unusedLocations, errs
}

Expand Down
2 changes: 2 additions & 0 deletions cmd/sops/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ func warnMoreThanOnePositionalArgument(c *cli.Context) {
}

func main() {
defer age.ClearFileStreamCache()

cli.VersionPrinter = version.PrintVersion
app := cli.NewApp()

Expand Down
Loading