Skip to content
2 changes: 1 addition & 1 deletion scw/client_option_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ func TestCombinedClientOptions(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
// set up env and config file(s)
setEnv(t, test.env, test.files, dir)
setEnv(t, test.env, test.files, defaultConfigPermission, dir)
test.expectedError = strings.ReplaceAll(test.expectedError, "{HOME}", dir)

// remove config file(s)
Expand Down
11 changes: 9 additions & 2 deletions scw/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package scw
import (
"bytes"
goerrors "errors"
"fmt"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -200,7 +201,7 @@ func LoadConfig() (*Config, error) {
configPath = strings.TrimSuffix(configPath, ".yaml") + ".yml"
cfgYml, errYml := LoadConfigFromPath(configPath)
// If .yml config is not found, return first error when reading .yaml
if errYml == nil || (errYml != nil && !goerrors.As(errYml, &configNotFoundError)) {
if errYml == nil || !goerrors.As(errYml, &configNotFoundError) {
return cfgYml, errYml
}
}
Expand All @@ -211,14 +212,20 @@ func LoadConfig() (*Config, error) {

// LoadConfigFromPath read the config from the given path.
func LoadConfigFromPath(path string) (*Config, error) {
_, err := os.Stat(path)
fileInfo, err := os.Stat(path)
if os.IsNotExist(err) {
return nil, configFileNotFound(path)
}
if err != nil {
return nil, err
}

filePerms := fileInfo.Mode().Perm()
if filePerms > defaultConfigPermission {
fmt.Printf("WARNING: scaleway-sdk-go config file is too permissive. That is insecure.\n"+
"You can fix it with the command 'chmod 0600 %s'\n", path)
}

file, err := os.ReadFile(path)
if err != nil {
return nil, errors.Wrap(err, "cannot read config file")
Expand Down
91 changes: 86 additions & 5 deletions scw/config_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package scw

import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -230,7 +233,7 @@ func TestSaveConfig(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
// set up env and config file(s)
setEnv(t, test.env, test.files, dir)
setEnv(t, test.env, test.files, defaultConfigPermission, dir)

// remove config file(s)
defer cleanEnv(t, test.files, dir)
Expand All @@ -252,12 +255,13 @@ func TestSaveConfig(t *testing.T) {
}
}

// TestLoadConfig tests config getters return correct values
// TestLoadProfileAndActiveProfile tests config getters return correct values
func TestLoadProfileAndActiveProfile(t *testing.T) {
tests := []struct {
name string
env map[string]string
files map[string]string
perms os.FileMode

expectedError string
expectedAccessKey *string
Expand All @@ -269,6 +273,7 @@ func TestLoadProfileAndActiveProfile(t *testing.T) {
expectedDefaultProjectID *string
expectedDefaultRegion *string
expectedDefaultZone *string
expectedOutput string
}{
// no env variables
{
Expand Down Expand Up @@ -406,6 +411,58 @@ func TestLoadProfileAndActiveProfile(t *testing.T) {
expectedDefaultProjectID: s(v2ValidDefaultProjectID),
expectedDefaultRegion: s(v2ValidDefaultRegion),
},
{
name: "Read config.yml too permissive",
env: map[string]string{
"HOME": "{HOME}",
},
files: map[string]string{
".config/scw/config.yml": v2SimpleValidConfigFile,
},
perms: 0o700,
expectedAccessKey: s(v2ValidAccessKey),
expectedSecretKey: s(v2ValidSecretKey),
expectedDefaultOrganizationID: s(v2ValidDefaultOrganizationID),
expectedDefaultProjectID: s(v2ValidDefaultProjectID),
expectedDefaultRegion: s(v2ValidDefaultRegion),
expectedOutput: `WARNING: scaleway-sdk-go config file is too permissive. That is insecure.` + `
You can fix is with the command 'chmod 0600 /tmp/home1208930814/.config/scw/config.yml'
`,
},
{
name: "Read config.yml too permissive",
env: map[string]string{
"HOME": "{HOME}",
},
files: map[string]string{
".config/scw/config.yml": v2SimpleValidConfigFile,
},
perms: 0o650,
expectedAccessKey: s(v2ValidAccessKey),
expectedSecretKey: s(v2ValidSecretKey),
expectedDefaultOrganizationID: s(v2ValidDefaultOrganizationID),
expectedDefaultProjectID: s(v2ValidDefaultProjectID),
expectedDefaultRegion: s(v2ValidDefaultRegion),
expectedOutput: `WARNING: scaleway-sdk-go config file is too permissive. That is insecure.` + `
You can fix it with the command 'chmod 0600`,
},
{
name: "Read config.yml too permissive",
env: map[string]string{
"HOME": "{HOME}",
},
files: map[string]string{
".config/scw/config.yml": v2SimpleValidConfigFile,
},
perms: 0o605,
expectedAccessKey: s(v2ValidAccessKey),
expectedSecretKey: s(v2ValidSecretKey),
expectedDefaultOrganizationID: s(v2ValidDefaultOrganizationID),
expectedDefaultProjectID: s(v2ValidDefaultProjectID),
expectedDefaultRegion: s(v2ValidDefaultRegion),
expectedOutput: `WARNING: scaleway-sdk-go config file is too permissive. That is insecure.` + `
You can fix it with the command 'chmod 0600`,
},
}

// create home dir
Expand All @@ -417,18 +474,38 @@ func TestLoadProfileAndActiveProfile(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
// set up env and config file(s)
setEnv(t, test.env, test.files, dir)
setEnv(t, test.env, test.files, test.perms, dir)
test.expectedError = strings.ReplaceAll(test.expectedError, "{HOME}", dir)

// remove config file(s)
defer cleanEnv(t, test.files, dir)

// Temporarily capturing stdout
originalStdout := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w

config, err := LoadConfig()

// Giving back stdout
w.Close()
os.Stdout = originalStdout

if test.expectedError == "" {
testhelpers.AssertNoError(t, err)
p, err := config.GetActiveProfile()
testhelpers.AssertNoError(t, err)

// Reading captured stdout
var buf bytes.Buffer
_, err = io.Copy(&buf, r)
testhelpers.AssertNoError(t, err)
testhelpers.Assert(
t,
strings.Contains(buf.String(), test.expectedOutput),
fmt.Sprintf("expected\n%s\nto contain\n%s", buf.String(), test.expectedOutput),
)

// assert getters
testhelpers.Equals(t, test.expectedAccessKey, p.AccessKey)
testhelpers.Equals(t, test.expectedSecretKey, p.SecretKey)
Expand Down Expand Up @@ -523,18 +600,22 @@ func cleanEnv(t *testing.T, files map[string]string, homeDir string) {
}
}

func setEnv(t *testing.T, env, files map[string]string, homeDir string) {
func setEnv(t *testing.T, env, files map[string]string, perms os.FileMode, homeDir string) {
t.Helper()
os.Clearenv()
for key, value := range env {
value = strings.ReplaceAll(value, "{HOME}", homeDir)
testhelpers.AssertNoError(t, os.Setenv(key, value))
}

if perms == 0 {
perms = defaultConfigPermission
}

for path, content := range files {
targetPath := filepath.Join(homeDir, path)
testhelpers.AssertNoError(t, os.MkdirAll(filepath.Dir(targetPath), 0o700))
testhelpers.AssertNoError(t, os.WriteFile(targetPath, []byte(content), defaultConfigPermission))
testhelpers.AssertNoError(t, os.WriteFile(targetPath, []byte(content), perms))
}
}

Expand Down
2 changes: 1 addition & 1 deletion scw/env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func TestLoadEnvProfile(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
// set up env and config file(s)
setEnv(t, test.env, nil, dir)
setEnv(t, test.env, nil, defaultConfigPermission, dir)

// remove config file(s)
defer cleanEnv(t, nil, dir)
Expand Down
2 changes: 1 addition & 1 deletion scw/load_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ func TestLoad(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
// set up env and config file(s)
setEnv(t, test.env, test.files, dir)
setEnv(t, test.env, test.files, defaultConfigPermission, dir)
test.expectedError = strings.ReplaceAll(test.expectedError, "{HOME}", dir)

// remove config file(s)
Expand Down