From d07c432f5d88d16001cd2274320a3f234aaa8936 Mon Sep 17 00:00:00 2001 From: Geoffrey Date: Thu, 18 Sep 2025 09:28:32 +0200 Subject: [PATCH 1/9] feat: check and warn about config file permissions #2574 --- scw/config.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scw/config.go b/scw/config.go index af0995df4..5f39a83f8 100644 --- a/scw/config.go +++ b/scw/config.go @@ -211,7 +211,7 @@ 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) } @@ -219,6 +219,12 @@ func LoadConfigFromPath(path string) (*Config, error) { 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") From 5ebb2131a6dbb26e8d5bacfbc6a08e60627984bc Mon Sep 17 00:00:00 2001 From: Geoffrey Date: Thu, 18 Sep 2025 09:29:35 +0200 Subject: [PATCH 2/9] chore: rm redundant condition --- scw/config.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scw/config.go b/scw/config.go index 5f39a83f8..3ef279110 100644 --- a/scw/config.go +++ b/scw/config.go @@ -3,6 +3,7 @@ package scw import ( "bytes" goerrors "errors" + "fmt" "os" "path/filepath" "strings" @@ -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 } } From e509d19e1b998c3e53a1cefd571a3d735b12ac31 Mon Sep 17 00:00:00 2001 From: Geoffrey Date: Thu, 18 Sep 2025 09:30:01 +0200 Subject: [PATCH 3/9] test: add tests for the permissions check --- scw/client_option_test.go | 2 +- scw/config_test.go | 91 ++++++++++++++++++++++++++++++++++++--- scw/env_test.go | 2 +- scw/load_config_test.go | 2 +- 4 files changed, 89 insertions(+), 8 deletions(-) diff --git a/scw/client_option_test.go b/scw/client_option_test.go index 6041defc2..501fc1bb6 100644 --- a/scw/client_option_test.go +++ b/scw/client_option_test.go @@ -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) diff --git a/scw/config_test.go b/scw/config_test.go index fa269e943..257e12eec 100644 --- a/scw/config_test.go +++ b/scw/config_test.go @@ -1,6 +1,9 @@ package scw import ( + "bytes" + "fmt" + "io" "os" "path/filepath" "strings" @@ -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) @@ -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 @@ -269,6 +273,7 @@ func TestLoadProfileAndActiveProfile(t *testing.T) { expectedDefaultProjectID *string expectedDefaultRegion *string expectedDefaultZone *string + expectedOutput string }{ // no env variables { @@ -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 @@ -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) @@ -523,7 +600,7 @@ 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 { @@ -531,10 +608,14 @@ func setEnv(t *testing.T, env, files map[string]string, homeDir string) { 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)) } } diff --git a/scw/env_test.go b/scw/env_test.go index fb3e77dab..1d37a1c05 100644 --- a/scw/env_test.go +++ b/scw/env_test.go @@ -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) diff --git a/scw/load_config_test.go b/scw/load_config_test.go index 7f3d9a312..cf511bf13 100644 --- a/scw/load_config_test.go +++ b/scw/load_config_test.go @@ -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) From a17c6679b13431752babff38a82c39e51164e203 Mon Sep 17 00:00:00 2001 From: Geoffrey <44409380+pypaut@users.noreply.github.com> Date: Mon, 22 Sep 2025 11:41:21 +0200 Subject: [PATCH 4/9] fix : edit warning text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rémy Léone --- scw/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scw/config.go b/scw/config.go index 3ef279110..175b7e1e8 100644 --- a/scw/config.go +++ b/scw/config.go @@ -222,7 +222,7 @@ func LoadConfigFromPath(path string) (*Config, error) { filePerms := fileInfo.Mode().Perm() if filePerms > defaultConfigPermission { - fmt.Printf("WARNING: scaleway-sdk-go config file is too permissive. That is insecure.\n"+ + fmt.Printf("WARNING: Scaleway configuration file permissions are too permissive. That is insecure.\n"+ "You can fix it with the command 'chmod 0600 %s'\n", path) } From 8fef03c64a739fe5ff8d9841edb123a3162e0655 Mon Sep 17 00:00:00 2001 From: Geoffrey Date: Mon, 22 Sep 2025 16:09:49 +0200 Subject: [PATCH 5/9] test: check for the entire warning --- scw/config_test.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/scw/config_test.go b/scw/config_test.go index 257e12eec..c9f1e16db 100644 --- a/scw/config_test.go +++ b/scw/config_test.go @@ -425,9 +425,8 @@ func TestLoadProfileAndActiveProfile(t *testing.T) { 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' -`, + expectedOutput: `WARNING: Scaleway configuration file permissions are too permissive. That is insecure.` + ` +You can fix it with the command 'chmod 0600 {HOME}/.config/scw/config.yml'`, }, { name: "Read config.yml too permissive", @@ -443,8 +442,8 @@ You can fix is with the command 'chmod 0600 /tmp/home1208930814/.config/scw/conf 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`, + expectedOutput: `WARNING: Scaleway configuration file permissions are too permissive. That is insecure.` + ` +You can fix it with the command 'chmod 0600 {HOME}/.config/scw/config.yml'`, }, { name: "Read config.yml too permissive", @@ -460,8 +459,9 @@ You can fix it with the command 'chmod 0600`, 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`, + expectedOutput: `WARNING: Scaleway configuration file permissions are too permissive. That is insecure.` + ` +You can fix it with the command 'chmod 0600 {HOME}/.config/scw/config.yml'`, + }, }, } @@ -476,6 +476,7 @@ You can fix it with the command 'chmod 0600`, // set up env and config file(s) setEnv(t, test.env, test.files, test.perms, dir) test.expectedError = strings.ReplaceAll(test.expectedError, "{HOME}", dir) + test.expectedOutput = strings.ReplaceAll(test.expectedOutput, "{HOME}", dir) // remove config file(s) defer cleanEnv(t, test.files, dir) From ba6e5286916c4df54ef70155a4c4dea1ea875863 Mon Sep 17 00:00:00 2001 From: Geoffrey Jount Date: Mon, 22 Sep 2025 16:11:04 +0200 Subject: [PATCH 6/9] test: check output even with expected error --- scw/config_test.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/scw/config_test.go b/scw/config_test.go index c9f1e16db..0f1a78b55 100644 --- a/scw/config_test.go +++ b/scw/config_test.go @@ -497,16 +497,6 @@ You can fix it with the command 'chmod 0600 {HOME}/.config/scw/config.yml'`, 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) @@ -520,6 +510,16 @@ You can fix it with the command 'chmod 0600 {HOME}/.config/scw/config.yml'`, } else { testhelpers.Equals(t, test.expectedError, err.Error()) } + + // In both cases, read 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), + ) }) } } From a109c1ce062bf1d12549a04618539523b2cb7b87 Mon Sep 17 00:00:00 2001 From: Geoffrey Jount Date: Mon, 22 Sep 2025 16:11:37 +0200 Subject: [PATCH 7/9] feat: check permissions are not too restrictive --- scw/config.go | 4 ++++ scw/config_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/scw/config.go b/scw/config.go index 175b7e1e8..5aa98faed 100644 --- a/scw/config.go +++ b/scw/config.go @@ -18,6 +18,7 @@ import ( const ( documentationLink = "https://github.com/scaleway/scaleway-sdk-go/blob/master/scw/README.md" defaultConfigPermission = 0o600 + minimalConfigPermission = 0o400 // Reserved name for the default profile. DefaultProfileName = "default" @@ -224,6 +225,9 @@ func LoadConfigFromPath(path string) (*Config, error) { if filePerms > defaultConfigPermission { fmt.Printf("WARNING: Scaleway configuration file permissions are too permissive. That is insecure.\n"+ "You can fix it with the command 'chmod 0600 %s'\n", path) + } else if filePerms < minimalConfigPermission { + fmt.Printf("WARNING: Scaleway configuration file permissions are too restrictive. It is not readable.\n"+ + "You can fix it with the command 'chmod 0600 %s'\n", path) } file, err := os.ReadFile(path) diff --git a/scw/config_test.go b/scw/config_test.go index 0f1a78b55..20e912c9d 100644 --- a/scw/config_test.go +++ b/scw/config_test.go @@ -462,6 +462,31 @@ You can fix it with the command 'chmod 0600 {HOME}/.config/scw/config.yml'`, expectedOutput: `WARNING: Scaleway configuration file permissions are too permissive. That is insecure.` + ` You can fix it with the command 'chmod 0600 {HOME}/.config/scw/config.yml'`, }, + { + name: "Read config.yml too restrictive", + env: map[string]string{ + "HOME": "{HOME}", + }, + files: map[string]string{ + ".config/scw/config.yml": v2SimpleValidConfigFile, + }, + perms: 0o300, + expectedError: "scaleway-sdk-go: cannot read config file: open {HOME}/.config/scw/config.yml: permission denied", + expectedOutput: `WARNING: Scaleway configuration file permissions are too restrictive. Is it not readable.` + ` +You can fix it with the command 'chmod 0600 {HOME}/.config/scw/config.yml'`, + }, + { + name: "Read config.yml too restrictive", + env: map[string]string{ + "HOME": "{HOME}", + }, + files: map[string]string{ + ".config/scw/config.yml": v2SimpleValidConfigFile, + }, + perms: 0o355, + expectedError: "scaleway-sdk-go: cannot read config file: open {HOME}/.config/scw/config.yml: permission denied", + expectedOutput: `WARNING: Scaleway configuration file permissions are too restrictive. It is not readable.` + ` +You can fix it with the command 'chmod 0600 {HOME}/.config/scw/config.yml'`, }, } From c6012ff00a3d5b686194299b7795de9535607798 Mon Sep 17 00:00:00 2001 From: Geoffrey Jount Date: Mon, 22 Sep 2025 16:25:00 +0200 Subject: [PATCH 8/9] fix: allow too restrictive without warning I misunderstood the comment. --- scw/config.go | 4 ---- scw/config_test.go | 15 --------------- 2 files changed, 19 deletions(-) diff --git a/scw/config.go b/scw/config.go index 5aa98faed..175b7e1e8 100644 --- a/scw/config.go +++ b/scw/config.go @@ -18,7 +18,6 @@ import ( const ( documentationLink = "https://github.com/scaleway/scaleway-sdk-go/blob/master/scw/README.md" defaultConfigPermission = 0o600 - minimalConfigPermission = 0o400 // Reserved name for the default profile. DefaultProfileName = "default" @@ -225,9 +224,6 @@ func LoadConfigFromPath(path string) (*Config, error) { if filePerms > defaultConfigPermission { fmt.Printf("WARNING: Scaleway configuration file permissions are too permissive. That is insecure.\n"+ "You can fix it with the command 'chmod 0600 %s'\n", path) - } else if filePerms < minimalConfigPermission { - fmt.Printf("WARNING: Scaleway configuration file permissions are too restrictive. It is not readable.\n"+ - "You can fix it with the command 'chmod 0600 %s'\n", path) } file, err := os.ReadFile(path) diff --git a/scw/config_test.go b/scw/config_test.go index 20e912c9d..369638803 100644 --- a/scw/config_test.go +++ b/scw/config_test.go @@ -472,21 +472,6 @@ You can fix it with the command 'chmod 0600 {HOME}/.config/scw/config.yml'`, }, perms: 0o300, expectedError: "scaleway-sdk-go: cannot read config file: open {HOME}/.config/scw/config.yml: permission denied", - expectedOutput: `WARNING: Scaleway configuration file permissions are too restrictive. Is it not readable.` + ` -You can fix it with the command 'chmod 0600 {HOME}/.config/scw/config.yml'`, - }, - { - name: "Read config.yml too restrictive", - env: map[string]string{ - "HOME": "{HOME}", - }, - files: map[string]string{ - ".config/scw/config.yml": v2SimpleValidConfigFile, - }, - perms: 0o355, - expectedError: "scaleway-sdk-go: cannot read config file: open {HOME}/.config/scw/config.yml: permission denied", - expectedOutput: `WARNING: Scaleway configuration file permissions are too restrictive. It is not readable.` + ` -You can fix it with the command 'chmod 0600 {HOME}/.config/scw/config.yml'`, }, } From a77c7f029c88efc4d549529f93d116d99ba530ec Mon Sep 17 00:00:00 2001 From: Geoffrey Jount Date: Mon, 22 Sep 2025 16:33:57 +0200 Subject: [PATCH 9/9] fix: check permissions more thoroughly --- scw/config.go | 18 ++++++++++++++++-- scw/config_test.go | 17 +++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/scw/config.go b/scw/config.go index 175b7e1e8..f69dd8aae 100644 --- a/scw/config.go +++ b/scw/config.go @@ -4,6 +4,7 @@ import ( "bytes" goerrors "errors" "fmt" + "io/fs" "os" "path/filepath" "strings" @@ -220,8 +221,7 @@ func LoadConfigFromPath(path string) (*Config, error) { return nil, err } - filePerms := fileInfo.Mode().Perm() - if filePerms > defaultConfigPermission { + if permsAreTooPermissive(fileInfo.Mode().Perm()) { fmt.Printf("WARNING: Scaleway configuration file permissions are too permissive. That is insecure.\n"+ "You can fix it with the command 'chmod 0600 %s'\n", path) } @@ -239,6 +239,20 @@ func LoadConfigFromPath(path string) (*Config, error) { return confV2, nil } +func permsAreTooPermissive(perms fs.FileMode) bool { + if perms > defaultConfigPermission { + return true + } + + strPerms := perms.String() + if strPerms[4:7] != "---" || + strPerms[7:9] != "---" { + return true + } + + return false +} + // GetProfile returns the profile corresponding to the given profile name. func (c *Config) GetProfile(profileName string) (*Profile, error) { if profileName == "" { diff --git a/scw/config_test.go b/scw/config_test.go index 369638803..fcc76fddc 100644 --- a/scw/config_test.go +++ b/scw/config_test.go @@ -443,6 +443,23 @@ You can fix it with the command 'chmod 0600 {HOME}/.config/scw/config.yml'`, expectedDefaultProjectID: s(v2ValidDefaultProjectID), expectedDefaultRegion: s(v2ValidDefaultRegion), expectedOutput: `WARNING: Scaleway configuration file permissions are too permissive. That is insecure.` + ` +You can fix it with the command 'chmod 0600 {HOME}/.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: 0o477, + expectedAccessKey: s(v2ValidAccessKey), + expectedSecretKey: s(v2ValidSecretKey), + expectedDefaultOrganizationID: s(v2ValidDefaultOrganizationID), + expectedDefaultProjectID: s(v2ValidDefaultProjectID), + expectedDefaultRegion: s(v2ValidDefaultRegion), + expectedOutput: `WARNING: Scaleway configuration file permissions are too permissive. That is insecure.` + ` You can fix it with the command 'chmod 0600 {HOME}/.config/scw/config.yml'`, }, {