mirror of
https://github.com/pgsty/minio.git
synced 2026-09-05 18:16:16 +03:00
6b0998157c
Trim whitespace around assignments, preserve whitespace inside matching quotes, validate portable variable names, and report redacted file-and-line diagnostics. Check config-file Setenv failures instead of silently ignoring invalid entries.\n\nFixes #65
345 lines
8.3 KiB
Go
345 lines
8.3 KiB
Go
// Copyright (c) 2015-2021 MinIO, Inc.
|
|
//
|
|
// This file is part of MinIO Object Storage stack
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package cmd
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func Test_readFromSecret(t *testing.T) {
|
|
testCases := []struct {
|
|
content string
|
|
expectedErr bool
|
|
expectedValue string
|
|
}{
|
|
{
|
|
"value\n",
|
|
false,
|
|
"value",
|
|
},
|
|
{
|
|
" \t\n Hello, Gophers \n\t\r\n",
|
|
false,
|
|
"Hello, Gophers",
|
|
},
|
|
}
|
|
|
|
for _, testCase := range testCases {
|
|
t.Run("", func(t *testing.T) {
|
|
tmpfile, err := os.CreateTemp(t.TempDir(), "testfile")
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
tmpfile.WriteString(testCase.content)
|
|
tmpfile.Sync()
|
|
tmpfile.Close()
|
|
|
|
value, err := readFromSecret(tmpfile.Name())
|
|
if err != nil && !testCase.expectedErr {
|
|
t.Error(err)
|
|
}
|
|
if err == nil && testCase.expectedErr {
|
|
t.Error(errors.New("expected error, found success"))
|
|
}
|
|
if value != testCase.expectedValue {
|
|
t.Errorf("Expected %s, got %s", testCase.expectedValue, value)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_minioEnvironFromFile(t *testing.T) {
|
|
testCases := []struct {
|
|
content string
|
|
expectedErr bool
|
|
expectedEkvs []envKV
|
|
}{
|
|
{
|
|
`
|
|
export MINIO_ROOT_USER=minio
|
|
export MINIO_ROOT_PASSWORD=minio123`,
|
|
false,
|
|
[]envKV{
|
|
{
|
|
Key: "MINIO_ROOT_USER",
|
|
Value: "minio",
|
|
},
|
|
{
|
|
Key: "MINIO_ROOT_PASSWORD",
|
|
Value: "minio123",
|
|
},
|
|
},
|
|
},
|
|
// Value with double quotes
|
|
{
|
|
`export MINIO_ROOT_USER="minio"`,
|
|
false,
|
|
[]envKV{
|
|
{
|
|
Key: "MINIO_ROOT_USER",
|
|
Value: "minio",
|
|
},
|
|
},
|
|
},
|
|
// Value with single quotes
|
|
{
|
|
`export MINIO_ROOT_USER='minio'`,
|
|
false,
|
|
[]envKV{
|
|
{
|
|
Key: "MINIO_ROOT_USER",
|
|
Value: "minio",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
`
|
|
MINIO_ROOT_USER=minio
|
|
MINIO_ROOT_PASSWORD=minio123`,
|
|
false,
|
|
[]envKV{
|
|
{
|
|
Key: "MINIO_ROOT_USER",
|
|
Value: "minio",
|
|
},
|
|
{
|
|
Key: "MINIO_ROOT_PASSWORD",
|
|
Value: "minio123",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
`
|
|
export MINIO_ROOT_USERminio
|
|
export MINIO_ROOT_PASSWORD=minio123`,
|
|
true,
|
|
nil,
|
|
},
|
|
{
|
|
`
|
|
# simple comment
|
|
# MINIO_ROOT_USER=minioadmin
|
|
# MINIO_ROOT_PASSWORD=minioadmin
|
|
MINIO_ROOT_USER=minio
|
|
MINIO_ROOT_PASSWORD=minio123`,
|
|
false,
|
|
[]envKV{
|
|
{
|
|
Key: "MINIO_ROOT_USER",
|
|
Value: "minio",
|
|
},
|
|
{
|
|
Key: "MINIO_ROOT_PASSWORD",
|
|
Value: "minio123",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
for _, testCase := range testCases {
|
|
t.Run("", func(t *testing.T) {
|
|
tmpfile, err := os.CreateTemp(t.TempDir(), "testfile")
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
tmpfile.WriteString(testCase.content)
|
|
tmpfile.Sync()
|
|
tmpfile.Close()
|
|
|
|
ekvs, err := minioEnvironFromFile(tmpfile.Name())
|
|
if err != nil && !testCase.expectedErr {
|
|
t.Error(err)
|
|
}
|
|
if err == nil && testCase.expectedErr {
|
|
t.Error(errors.New("expected error, found success"))
|
|
}
|
|
|
|
if len(ekvs) != len(testCase.expectedEkvs) {
|
|
t.Errorf("expected %v keys, got %v keys", len(testCase.expectedEkvs), len(ekvs))
|
|
}
|
|
|
|
if !reflect.DeepEqual(ekvs, testCase.expectedEkvs) {
|
|
t.Errorf("expected %v, got %v", testCase.expectedEkvs, ekvs)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_minioEnvironFromFileWhitespaceAndValidation(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
content string
|
|
want []envKV
|
|
errLine int
|
|
errContains string
|
|
errExcludes string
|
|
}{
|
|
{
|
|
name: "spaces and tabs around separator",
|
|
content: "MINIO_ROOT_USER = minio\nMINIO_ROOT_PASSWORD\t=\tminio123",
|
|
want: []envKV{
|
|
{Key: "MINIO_ROOT_USER", Value: "minio"},
|
|
{Key: "MINIO_ROOT_PASSWORD", Value: "minio123"},
|
|
},
|
|
},
|
|
{
|
|
name: "export tab and quoted spaces",
|
|
content: "export\tMINIO_ROOT_USER = \" minio user \"\nexport MINIO_ROOT_PASSWORD = ' minio secret '",
|
|
want: []envKV{
|
|
{Key: "MINIO_ROOT_USER", Value: " minio user "},
|
|
{Key: "MINIO_ROOT_PASSWORD", Value: " minio secret "},
|
|
},
|
|
},
|
|
{
|
|
name: "export Unicode whitespace",
|
|
content: "export\u00a0MINIO_ROOT_USER=value",
|
|
want: []envKV{
|
|
{Key: "MINIO_ROOT_USER", Value: "value"},
|
|
},
|
|
},
|
|
{
|
|
name: "export is only a standalone prefix",
|
|
content: "export=value\nexportFOO=bar",
|
|
want: []envKV{
|
|
{Key: "export", Value: "value"},
|
|
{Key: "exportFOO", Value: "bar"},
|
|
},
|
|
},
|
|
{
|
|
name: "unquoted whitespace empty value and additional separators",
|
|
content: "UNQUOTED = value \nEMPTY =\nTOKEN = scheme://user:password@example.com?a=b",
|
|
want: []envKV{
|
|
{Key: "UNQUOTED", Value: "value"},
|
|
{Key: "EMPTY", Value: ""},
|
|
{Key: "TOKEN", Value: "scheme://user:password@example.com?a=b"},
|
|
},
|
|
},
|
|
{
|
|
name: "valid underscore and digits",
|
|
content: "_MINIO_2=value",
|
|
want: []envKV{
|
|
{Key: "_MINIO_2", Value: "value"},
|
|
},
|
|
},
|
|
{
|
|
name: "missing separator redacts the line",
|
|
content: "MINIO_ROOT_PASSWORD=valid\nsuper-secret-without-equals",
|
|
errLine: 2,
|
|
errContains: "missing '='",
|
|
errExcludes: "super-secret-without-equals",
|
|
},
|
|
{
|
|
name: "empty name",
|
|
content: "=empty-name-secret",
|
|
errLine: 1,
|
|
errContains: `invalid environment variable name ""`,
|
|
errExcludes: "empty-name-secret",
|
|
},
|
|
{
|
|
name: "digit leading name",
|
|
content: "1MINIO_ROOT_USER=digit-leading-secret",
|
|
errLine: 1,
|
|
errContains: `invalid environment variable name "1MINIO_ROOT_USER"`,
|
|
errExcludes: "digit-leading-secret",
|
|
},
|
|
{
|
|
name: "hyphenated name",
|
|
content: "MINIO-ROOT-USER=hyphen-secret",
|
|
errLine: 1,
|
|
errContains: `invalid environment variable name "MINIO-ROOT-USER"`,
|
|
errExcludes: "hyphen-secret",
|
|
},
|
|
{
|
|
name: "whitespace in name",
|
|
content: "MINIO ROOT USER=whitespace-secret",
|
|
errLine: 1,
|
|
errContains: `invalid environment variable name "MINIO ROOT USER"`,
|
|
errExcludes: "whitespace-secret",
|
|
},
|
|
{
|
|
name: "NUL in name",
|
|
content: "MINIO\x00ROOT=nul-name-secret",
|
|
errLine: 1,
|
|
errContains: "invalid environment variable name",
|
|
errExcludes: "nul-name-secret",
|
|
},
|
|
{
|
|
name: "NUL in value",
|
|
content: "MINIO_ROOT_USER=before\x00nul-value-secret",
|
|
errLine: 1,
|
|
errContains: "environment variable value contains NUL",
|
|
errExcludes: "nul-value-secret",
|
|
},
|
|
{
|
|
name: "diagnostic has file and line but no value",
|
|
content: "MINIO_ROOT_USER=valid\nBAD KEY=super-secret-value",
|
|
errLine: 2,
|
|
errContains: `invalid environment variable name "BAD KEY"`,
|
|
errExcludes: "super-secret-value",
|
|
},
|
|
}
|
|
|
|
for _, testCase := range testCases {
|
|
t.Run(testCase.name, func(t *testing.T) {
|
|
tmpfile, err := os.CreateTemp(t.TempDir(), "testfile")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err = tmpfile.WriteString(testCase.content); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err = tmpfile.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
got, err := minioEnvironFromFile(tmpfile.Name())
|
|
if testCase.errContains == "" {
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !reflect.DeepEqual(got, testCase.want) {
|
|
t.Errorf("expected %v, got %v", testCase.want, got)
|
|
}
|
|
return
|
|
}
|
|
|
|
if err == nil {
|
|
t.Fatal("expected an error")
|
|
}
|
|
errText := err.Error()
|
|
location := fmt.Sprintf("%s:%d:", tmpfile.Name(), testCase.errLine)
|
|
if !strings.Contains(errText, location) {
|
|
t.Errorf("expected error to contain %q, got %q", location, errText)
|
|
}
|
|
if !strings.Contains(errText, testCase.errContains) {
|
|
t.Errorf("expected error to contain %q, got %q", testCase.errContains, errText)
|
|
}
|
|
if testCase.errExcludes != "" && strings.Contains(errText, testCase.errExcludes) {
|
|
t.Errorf("expected error to redact %q, got %q", testCase.errExcludes, errText)
|
|
}
|
|
if got != nil {
|
|
t.Errorf("expected no entries on parse error, got %v", got)
|
|
}
|
|
})
|
|
}
|
|
}
|