Skip to content

Commit

Permalink
CBG-3507: Add unsupported.allow_dbconfig_env_vars (#6496)
Browse files Browse the repository at this point in the history
* Add unsupported.disallow_dbconfig_env_vars to opt out of db config env var expansion

* Add test for DisallowDbConfigEnvVars coverage

* Flip bool (default to true)
  • Loading branch information
bbrks authored Oct 5, 2023
1 parent ea958eb commit ce51e97
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 9 deletions.
1 change: 1 addition & 0 deletions rest/config_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ func registerConfigFlags(config *StartupConfig, fs *flag.FlagSet) map[string]con
"unsupported.serverless.enabled": {&config.Unsupported.Serverless.Enabled, fs.Bool("unsupported.serverless.enabled", false, "Settings for running Sync Gateway in serverless mode.")},
"unsupported.serverless.min_config_fetch_interval": {&config.Unsupported.Serverless.MinConfigFetchInterval, fs.String("unsupported.serverless.min_config_fetch_interval", "", "How long to cache configs fetched from the buckets for. This cache is used for requested databases that SG does not know about.")},
"unsupported.use_xattr_config": {&config.Unsupported.UseXattrConfig, fs.Bool("unsupported.use_xattr_config", false, "Store database configurations in system xattrs")},
"unsupported.allow_dbconfig_env_vars": {&config.Unsupported.AllowDbConfigEnvVars, fs.Bool("unsupported.allow_dbconfig_env_vars", true, "Can be set to false to skip environment variable expansion in database configs")},

"unsupported.user_queries": {&config.Unsupported.UserQueries, fs.Bool("unsupported.user_queries", false, "Whether user-query APIs are enabled")},

Expand Down
14 changes: 8 additions & 6 deletions rest/config_startup.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ func DefaultStartupConfig(defaultLogFilePath string) StartupConfig {
Enabled: base.BoolPtr(false),
MinConfigFetchInterval: base.NewConfigDuration(DefaultMinConfigFetchInterval),
},
AllowDbConfigEnvVars: base.BoolPtr(true),
},
MaxFileDescriptors: DefaultMaxFileDescriptors,
}
Expand Down Expand Up @@ -141,12 +142,13 @@ type ReplicatorConfig struct {
}

type UnsupportedConfig struct {
StatsLogFrequency *base.ConfigDuration `json:"stats_log_frequency,omitempty" help:"How often should stats be written to stats logs"`
UseStdlibJSON *bool `json:"use_stdlib_json,omitempty" help:"Bypass the jsoniter package and use Go's stdlib instead"`
Serverless ServerlessConfig `json:"serverless,omitempty"`
HTTP2 *HTTP2Config `json:"http2,omitempty"`
UserQueries *bool `json:"user_queries,omitempty" help:"Feature flag for user N1QL/JS/GraphQL queries"`
UseXattrConfig *bool `json:"use_xattr_config,omitempty" help:"Store database configurations in system xattrs"`
StatsLogFrequency *base.ConfigDuration `json:"stats_log_frequency,omitempty" help:"How often should stats be written to stats logs"`
UseStdlibJSON *bool `json:"use_stdlib_json,omitempty" help:"Bypass the jsoniter package and use Go's stdlib instead"`
Serverless ServerlessConfig `json:"serverless,omitempty"`
HTTP2 *HTTP2Config `json:"http2,omitempty"`
UserQueries *bool `json:"user_queries,omitempty" help:"Feature flag for user N1QL/JS/GraphQL queries"`
UseXattrConfig *bool `json:"use_xattr_config,omitempty" help:"Store database configurations in system xattrs"`
AllowDbConfigEnvVars *bool `json:"allow_dbconfig_env_vars,omitempty" help:"Can be set to false to skip environment variable expansion in database configs"`
}

type ServerlessConfig struct {
Expand Down
80 changes: 80 additions & 0 deletions rest/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1306,6 +1306,86 @@ func TestExpandEnv(t *testing.T) {
}
}

// TestDbConfigEnvVarsToggle ensures that AllowDbConfigEnvVars toggles the ability to use env vars in db config.
func TestDbConfigEnvVarsToggle(t *testing.T) {
// Set up an env var with a secret value and use it as a channel name to assert its value.
const (
varName = "SECRET_SG_TEST_VAR"
secretVal = "secret"
defaultVal = "substitute"
)
// ${SECRET_SG_TEST_VAR:-substitute}
unexpandedVal := fmt.Sprintf("${%s:-%s}", varName, defaultVal)

tests := []struct {
allowDbConfigEnvVars *bool
setEnvVar bool
expectedChannel string
unexpectedChannels []string
}{
{
allowDbConfigEnvVars: nil, // defaults to true - so use nil to check default handling
setEnvVar: true,
expectedChannel: secretVal,
unexpectedChannels: []string{defaultVal, unexpandedVal},
},
{
allowDbConfigEnvVars: base.BoolPtr(true),
setEnvVar: false,
expectedChannel: defaultVal,
unexpectedChannels: []string{secretVal, unexpandedVal},
},
{
allowDbConfigEnvVars: base.BoolPtr(false),
setEnvVar: true,
expectedChannel: unexpandedVal,
unexpectedChannels: []string{secretVal, defaultVal},
},
}

for _, test := range tests {
t.Run(fmt.Sprintf("allowDbConfigEnvVars=%v_setEnvVar=%v", test.allowDbConfigEnvVars, test.setEnvVar), func(t *testing.T) {
rt := NewRestTesterDefaultCollection(t, &RestTesterConfig{
PersistentConfig: true,
allowDbConfigEnvVars: test.allowDbConfigEnvVars,
})
defer rt.Close()

if test.setEnvVar {
require.NoError(t, os.Setenv(varName, secretVal))
t.Cleanup(func() {
require.NoError(t, os.Unsetenv(varName))
})
}

dbc := rt.NewDbConfig()
dbc.Sync = base.StringPtr(fmt.Sprintf(
"function(doc) {channel('%s');}",
unexpandedVal,
))

resp := rt.CreateDatabase("db", dbc)
AssertStatus(t, resp, http.StatusCreated)

docID := "doc"
putDocResp := rt.PutDoc(docID, `{"foo":"bar"}`)
require.True(t, putDocResp.Ok)

require.NoError(t, rt.WaitForPendingChanges())

// ensure doc is in expected channel and is not in the unexpected channels
changes, err := rt.WaitForChanges(1, "/{{.keyspace}}/_changes?filter=sync_gateway/bychannel&channels="+test.expectedChannel, "", true)
require.NoError(t, err)
changes.RequireDocIDs(t, []string{docID})

channels := strings.Join(test.unexpectedChannels, ",")
changes, err = rt.WaitForChanges(0, "/{{.keyspace}}/_changes?filter=sync_gateway/bychannel&channels="+channels, "", true)
require.NoError(t, err)
changes.RequireDocIDs(t, []string{})
})
}
}

// createTempFile creates a temporary file with the given content.
func createTempFile(t *testing.T, content []byte) *os.File {
file, err := os.CreateTemp("", "*-sync_gateway.conf")
Expand Down
9 changes: 6 additions & 3 deletions rest/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -1109,10 +1109,13 @@ func (h *handler) readSanitizeJSON(val interface{}) error {
}

// Expand environment variables.
content, err = expandEnv(h.ctx(), content)
if err != nil {
return err
if base.BoolDefault(h.server.Config.Unsupported.AllowDbConfigEnvVars, true) {
content, err = expandEnv(h.ctx(), content)
if err != nil {
return err
}
}

// Convert the back quotes into double-quotes, escapes literal
// backslashes, newlines or double-quotes with backslashes.
content = base.ConvertBackQuotedStrings(content)
Expand Down
2 changes: 2 additions & 0 deletions rest/utilities_testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ type RestTesterConfig struct {
serverless bool // Runs SG in serverless mode. Must be used in conjunction with persistent config
collectionConfig collectionConfiguration
numCollections int
allowDbConfigEnvVars *bool
}

type collectionConfiguration uint8
Expand Down Expand Up @@ -215,6 +216,7 @@ func (rt *RestTester) Bucket() base.Bucket {
sc.Bootstrap.UseTLSServer = &rt.RestTesterConfig.useTLSServer
sc.Bootstrap.ServerTLSSkipVerify = base.BoolPtr(base.TestTLSSkipVerify())
sc.Unsupported.Serverless.Enabled = &rt.serverless
sc.Unsupported.AllowDbConfigEnvVars = rt.RestTesterConfig.allowDbConfigEnvVars
if rt.serverless {
if !rt.PersistentConfig {
rt.TB.Fatalf("Persistent config must be used when running in serverless mode")
Expand Down

0 comments on commit ce51e97

Please sign in to comment.