diff --git a/age/keysource.go b/age/keysource.go index a83d1a5c22..9ee6be491b 100644 --- a/age/keysource.go +++ b/age/keysource.go @@ -7,6 +7,7 @@ import ( "io" "os" "path/filepath" + "runtime" "strings" "filippo.io/age" @@ -23,8 +24,10 @@ const ( // age keys file. SopsAgeKeyFileEnv = "SOPS_AGE_KEY_FILE" // SopsAgeKeyUserConfigPath is the default age keys file path in - // os.UserConfigDir. + // getUserConfigDir(). SopsAgeKeyUserConfigPath = "sops/age/keys.txt" + // On macOS, os.UserConfigDir() ignores XDG_CONFIG_HOME. So we handle that manually. + xdgConfigHome = "XDG_CONFIG_HOME" ) // log is the global logger for any age MasterKey. @@ -222,6 +225,15 @@ func (key *MasterKey) ToMap() map[string]interface{} { return out } +func getUserConfigDir() (string, error) { + if runtime.GOOS == "darwin" { + if userConfigDir, ok := os.LookupEnv(xdgConfigHome); ok && userConfigDir != "" { + return userConfigDir, nil + } + } + return os.UserConfigDir() +} + // loadIdentities attempts to load the age identities based on runtime // environment configurations (e.g. SopsAgeKeyEnv, SopsAgeKeyFileEnv, // SopsAgeKeyUserConfigPath). It will load all found references, and expects @@ -242,7 +254,7 @@ func (key *MasterKey) loadIdentities() (ParsedIdentities, error) { readers[SopsAgeKeyFileEnv] = f } - userConfigDir, err := os.UserConfigDir() + userConfigDir, err := getUserConfigDir() if err != nil && len(readers) == 0 { return nil, fmt.Errorf("user config directory could not be determined: %w", err) } diff --git a/age/keysource_test.go b/age/keysource_test.go index dd56351eac..62d0fcf78a 100644 --- a/age/keysource_test.go +++ b/age/keysource_test.go @@ -380,11 +380,23 @@ func overwriteUserConfigDir(t *testing.T, path string) { switch runtime.GOOS { case "windows": t.Setenv("AppData", path) - case "darwin", "ios": // This adds "/Library/Application Support" as a suffix to $HOME - t.Setenv("HOME", path) case "plan9": // This adds "/lib" as a suffix to $home t.Setenv("home", path) default: // Unix t.Setenv("XDG_CONFIG_HOME", path) } } + +// Make sure that on all supported platforms but Windows, XDG_CONFIG_HOME +// can be used to specify the user's home directory. For most platforms +// this is handled by Go's os.UserConfigDir(), but for Darwin our code +// in getUserConfigDir() handles this explicitly. +func TestUserConfigDir(t *testing.T) { + if runtime.GOOS != "windows" { + const dir = "/test/home/dir" + t.Setenv("XDG_CONFIG_HOME", dir) + home, err := getUserConfigDir() + assert.Nil(t, err) + assert.Equal(t, home, dir) + } +}