Skip to content

Commit

Permalink
feat(config): more env var support (#367)
Browse files Browse the repository at this point in the history
* settings.web.basic_auth.password
* settings.web.basic_auth.username
* webhook.*.custom_headers.*.key
* webhook.*.custom_headers.*.value
  • Loading branch information
JosephKav authored Feb 22, 2024
1 parent 9c74b5c commit 87d63d6
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 28 deletions.
10 changes: 7 additions & 3 deletions config/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,14 @@ func (s *WebSettingsBasicAuth) String(prefix string) (str string) {
// CheckValues will ensure that the values are SHA256 hashed.
func (ba *WebSettingsBasicAuth) CheckValues() {
// Username
ba.UsernameHash = util.GetHash(ba.Username)
ba.UsernameHash = util.GetHash(util.EvalEnvVars(ba.Username))
// Password
ba.PasswordHash = util.GetHash(ba.Password)
ba.Password = util.FmtHash(ba.PasswordHash)
password := util.EvalEnvVars(ba.Password)
ba.PasswordHash = util.GetHash(password)
if password == ba.Password {
// Password doesn't include an env var, so hash the config val.
ba.Password = util.FmtHash(ba.PasswordHash)
}
}

// FaviconSettings contains the favicon override settings.
Expand Down
146 changes: 133 additions & 13 deletions config/settings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
func TestSettingsBase_CheckValues(t *testing.T) {
// GIVEN a Settings struct with some values set
tests := map[string]struct {
env map[string]string
had Settings
want Settings
wantUsernameHash string
Expand All @@ -44,19 +45,23 @@ func TestSettingsBase_CheckValues(t *testing.T) {
Web: WebSettings{
BasicAuth: nil}}},
},
"BasicAuth - hashed Username and str Password": {
"BasicAuth - hashed Username and str env Password": {
env: map[string]string{
"TESTSETTINGSBASE_CHECKVALUES_ONE": "ass"},
had: Settings{
SettingsBase: SettingsBase{
Web: WebSettings{
BasicAuth: &WebSettingsBasicAuth{
Username: util.FmtHash(util.GetHash("user")),
Password: "pass"}}}},
Password: "p${TESTSETTINGSBASE_CHECKVALUES_ONE}"}}}},
want: Settings{
SettingsBase: SettingsBase{
Web: WebSettings{
BasicAuth: &WebSettingsBasicAuth{
Username: util.FmtHash(util.GetHash("user")),
Password: util.FmtHash(util.GetHash("pass"))}}}},
Password: "p${TESTSETTINGSBASE_CHECKVALUES_ONE}"}}}},
wantUsernameHash: util.FmtHash(util.GetHash("user")),
wantPasswordHash: util.FmtHash(util.GetHash("pass")),
},
"Favicon - empty": {
had: Settings{
Expand Down Expand Up @@ -88,6 +93,11 @@ func TestSettingsBase_CheckValues(t *testing.T) {
t.Run(name, func(t *testing.T) {
t.Parallel()

for k, v := range tc.env {
os.Setenv(k, v)
defer os.Unsetenv(k)
}

// WHEN CheckValues is called on it
tc.had.CheckValues()

Expand All @@ -100,14 +110,20 @@ func TestSettingsBase_CheckValues(t *testing.T) {
}
// AND the BasicAuth username and password are hashed (if they exist)
if tc.want.Web.BasicAuth != nil {
wantUsernameHash := util.GetHash(tc.want.Web.BasicAuth.Username)
if tc.had.Web.BasicAuth.UsernameHash != wantUsernameHash {
t.Errorf("want: %x\ngot: %x",
wantUsernameHash := util.FmtHash(util.GetHash(tc.want.Web.BasicAuth.Username))
if tc.wantUsernameHash != "" {
wantUsernameHash = tc.wantUsernameHash
}
if util.FmtHash(tc.had.Web.BasicAuth.UsernameHash) != wantUsernameHash {
t.Errorf("want: %q\ngot: %q",
wantUsernameHash, tc.had.Web.BasicAuth.UsernameHash)
}
wantPasswordHash := util.GetHash(tc.want.Web.BasicAuth.Password)
if tc.had.Web.BasicAuth.PasswordHash != wantPasswordHash {
t.Errorf("want: %x\ngot: %x",
wantPasswordHash := util.FmtHash(util.GetHash(tc.want.Web.BasicAuth.Password))
if tc.wantPasswordHash != "" {
wantPasswordHash = tc.wantPasswordHash
}
if util.FmtHash(tc.had.Web.BasicAuth.PasswordHash) != wantPasswordHash {
t.Errorf("want: %q\ngot: %q",
wantPasswordHash, tc.had.Web.BasicAuth.PasswordHash)
}
}
Expand Down Expand Up @@ -680,8 +696,10 @@ func TestSettings_WebBasicAuthPasswordHash(t *testing.T) {
func TestWebSettings_CheckValues(t *testing.T) {
// GIVEN a WebSettings struct with some values set
tests := map[string]struct {
had WebSettings
want WebSettings
env map[string]string
had WebSettings
want WebSettings
wantUsernameHash string
}{
"BasicAuth - empty": {
had: WebSettings{
Expand Down Expand Up @@ -709,6 +727,34 @@ func TestWebSettings_CheckValues(t *testing.T) {
Username: "user",
Password: util.FmtHash(util.GetHash("pass"))}},
},
"BasicAuth - Username and password from env vars": {
env: map[string]string{
"TESTWEBSETTINGS_CHECKVALUES_ONE": "user",
"TESTWEBSETTINGS_CHECKVALUES_TWO": "pass"},
had: WebSettings{
BasicAuth: &WebSettingsBasicAuth{
Username: "${TESTWEBSETTINGS_CHECKVALUES_ONE}",
Password: "${TESTWEBSETTINGS_CHECKVALUES_TWO}"}},
want: WebSettings{
BasicAuth: &WebSettingsBasicAuth{
Username: "${TESTWEBSETTINGS_CHECKVALUES_ONE}",
Password: "${TESTWEBSETTINGS_CHECKVALUES_TWO}"}},
wantUsernameHash: util.FmtHash(util.GetHash("user")),
},
"BasicAuth - Username and password from env vars partial": {
env: map[string]string{
"TESTWEBSETTINGS_CHECKVALUES_THREE": "er",
"TESTWEBSETTINGS_CHECKVALUES_FOUR": "ss"},
had: WebSettings{
BasicAuth: &WebSettingsBasicAuth{
Username: "us${TESTWEBSETTINGS_CHECKVALUES_THREE}",
Password: "pa${TESTWEBSETTINGS_CHECKVALUES_FOUR}"}},
want: WebSettings{
BasicAuth: &WebSettingsBasicAuth{
Username: "us${TESTWEBSETTINGS_CHECKVALUES_THREE}",
Password: "pa${TESTWEBSETTINGS_CHECKVALUES_FOUR}"}},
wantUsernameHash: util.FmtHash(util.GetHash("user")),
},
"Favicon - empty": {
had: WebSettings{
Favicon: &FaviconSettings{}},
Expand Down Expand Up @@ -747,6 +793,11 @@ func TestWebSettings_CheckValues(t *testing.T) {
t.Run(name, func(t *testing.T) {
t.Parallel()

for k, v := range tc.env {
os.Setenv(k, v)
defer os.Unsetenv(k)
}

// WHEN CheckValues is called on it
tc.had.CheckValues()

Expand All @@ -757,15 +808,25 @@ func TestWebSettings_CheckValues(t *testing.T) {
t.Errorf("want:\n%v\ngot:\n%v",
wantStr, hadStr)
}
if tc.wantUsernameHash != "" {
got := util.FmtHash(tc.had.BasicAuth.UsernameHash)
if got != tc.wantUsernameHash {
t.Errorf("Username hash\nwant: %q\ngot: %q",
tc.wantUsernameHash, got)
}
}
})
}
}

func TestWebSettingsBasicAuth_CheckValues(t *testing.T) {
// GIVEN a WebSettingsBasicAuth struct with some values set
tests := map[string]struct {
had WebSettingsBasicAuth
want WebSettingsBasicAuth
env map[string]string
had WebSettingsBasicAuth
want WebSettingsBasicAuth
wantUsernameHash string
wantPasswordHash string
}{
"str Username": {
had: WebSettingsBasicAuth{
Expand All @@ -789,6 +850,38 @@ func TestWebSettingsBasicAuth_CheckValues(t *testing.T) {
Username: "user",
Password: util.FmtHash(util.GetHash("pass"))},
},
"str env Web.BasicAuth.Username and str env Web.BasicAuth.Password": {
env: map[string]string{
"TESTWEBSETTINGSBASICAUTH_CHECKVALUES_ONE": "user",
"TESTWEBSETTINGSBASICAUTH_CHECKVALUES_TWO": "pass"},
had: WebSettingsBasicAuth{
Username: "${TESTWEBSETTINGSBASICAUTH_CHECKVALUES_ONE}",
Password: "${TESTWEBSETTINGSBASICAUTH_CHECKVALUES_TWO}"},
want: WebSettingsBasicAuth{
Username: "${TESTWEBSETTINGSBASICAUTH_CHECKVALUES_ONE}",
Password: "${TESTWEBSETTINGSBASICAUTH_CHECKVALUES_TWO}"},
},
"str env partial Web.BasicAuth.Username and str env partial Web.BasicAuth.Password": {
env: map[string]string{
"TESTWEBSETTINGSBASICAUTH_CHECKVALUES_THREE": "user",
"TESTWEBSETTINGSBASICAUTH_CHECKVALUES_FOUR": "pass"},
had: WebSettingsBasicAuth{
Username: "a${TESTWEBSETTINGSBASICAUTH_CHECKVALUES_THREE}",
Password: "b${TESTWEBSETTINGSBASICAUTH_CHECKVALUES_FOUR}"},
want: WebSettingsBasicAuth{
Username: "a${TESTWEBSETTINGSBASICAUTH_CHECKVALUES_THREE}",
Password: "b${TESTWEBSETTINGSBASICAUTH_CHECKVALUES_FOUR}"},
},
"str env undefined Web.BasicAuth.Username and str env undefined Web.BasicAuth.Password": {
had: WebSettingsBasicAuth{
Username: "a${TESTWEBSETTINGSBASICAUTH_CHECKVALUES_UNDEFINED}",
Password: "b${TESTWEBSETTINGSBASICAUTH_CHECKVALUES_UNDEFINED}"},
want: WebSettingsBasicAuth{
Username: "a${TESTWEBSETTINGSBASICAUTH_CHECKVALUES_UNDEFINED}",
Password: util.FmtHash(util.GetHash("b${TESTWEBSETTINGSBASICAUTH_CHECKVALUES_UNDEFINED}"))},
wantUsernameHash: util.FmtHash(util.GetHash("a${TESTWEBSETTINGSBASICAUTH_CHECKVALUES_UNDEFINED}")),
wantPasswordHash: util.FmtHash(util.GetHash("b${TESTWEBSETTINGSBASICAUTH_CHECKVALUES_UNDEFINED}")),
},
"str Web.BasicAuth.Username and Web.BasicAuth.Password already hashed": {
had: WebSettingsBasicAuth{
Username: "user",
Expand Down Expand Up @@ -819,6 +912,11 @@ func TestWebSettingsBasicAuth_CheckValues(t *testing.T) {
t.Run(name, func(t *testing.T) {
t.Parallel()

for k, v := range tc.env {
os.Setenv(k, v)
defer os.Unsetenv(k)
}

// WHEN CheckValues is called on it
tc.had.CheckValues()

Expand All @@ -829,6 +927,28 @@ func TestWebSettingsBasicAuth_CheckValues(t *testing.T) {
t.Errorf("want:\n%v\ngot:\n%v",
wantStr, hadStr)
}
// AND the UsernameHash is calculated correctly
want := util.FmtHash(util.GetHash(
util.EvalEnvVars(tc.want.Username)))
if tc.wantUsernameHash != "" {
want = tc.wantUsernameHash
}
got := util.FmtHash(tc.had.UsernameHash)
if got != want {
t.Errorf("Username Hash\nwant: %s\ngot: %s",
want, got)
}
// AND the PasswordHash is calculated correctly
want = util.FmtHash(util.GetHash(
util.EvalEnvVars(tc.want.Password)))
if tc.wantPasswordHash != "" {
want = tc.wantPasswordHash
}
got = util.FmtHash(tc.had.PasswordHash)
if got != want {
t.Errorf("Password Hash\nwant: %s\ngot: %s",
want, got)
}
})
}
}
19 changes: 10 additions & 9 deletions service/deployed_version/query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,23 +116,23 @@ func TestLookup_Query(t *testing.T) {
wantVersion: "[0-9]{4}",
errRegex: "^$",
url: "https://release-argus.io",
regex: "([0-9]+) The Argus Developers",
regex: `([0-9]+)\s<[^>]+>The Argus Developers`,
},
"url from env": {
env: map[string]string{"TESTLOOKUP_DV_QUERY_ONE": "https://release-argus.io"},
noSemanticVersioning: true,
wantVersion: "[0-9]{4}",
errRegex: "^$",
env: map[string]string{"TESTLOOKUP_DV_QUERY_ONE": "https://release-argus.io"},
url: "${TESTLOOKUP_DV_QUERY_ONE}",
regex: "([0-9]+) The Argus Developers",
regex: `([0-9]+)\s<[^>]+>The Argus Developers`,
},
"url from env partial": {
env: map[string]string{"TESTLOOKUP_DV_QUERY_TWO": "release-argus"},
noSemanticVersioning: true,
wantVersion: "[0-9]{4}",
errRegex: "^$",
env: map[string]string{"TESTLOOKUP_DV_QUERY_TWO": "release-argus"},
url: "https://${TESTLOOKUP_DV_QUERY_TWO}.io",
regex: "([0-9]+) The Argus Developers",
regex: `([0-9]+)\s<[^>]+>The Argus Developers`,
},
"passing regex with no capture group": {
noSemanticVersioning: true,
Expand All @@ -145,7 +145,7 @@ func TestLookup_Query(t *testing.T) {
noSemanticVersioning: true,
errRegex: "^$",
url: "https://release-argus.io",
regex: "([0-9]+) (The) (Argus) (Developers)",
regex: `([0-9]+)\s<[^>]+>(The) (Argus) (Developers)`,
regexTemplate: stringPtr("$2 $1 $4, $3"),
wantVersion: "The [0-9]+ Developers, Argus",
},
Expand All @@ -157,19 +157,19 @@ func TestLookup_Query(t *testing.T) {
"handle non-semantic (only major) version": {
noSemanticVersioning: false,
url: "https://release-argus.io",
regex: "([0-9]+) The Argus Developers",
regex: `([0-9]+)\s<[^>]+>The Argus Developers`,
},
"want semantic versioning but get non-semantic version": {
noSemanticVersioning: false,
errRegex: "failed converting .* to a semantic version",
url: "https://release-argus.io",
regex: "([0-9]+ )The Argus Developers",
regex: `([0-9]+\s)<[^>]+>The Argus Developers`,
},
"allow non-semantic versioning and get non-semantic version": {
noSemanticVersioning: true,
errRegex: "^$",
url: "https://release-argus.io",
regex: "([0-9]+) The Argus Developers",
regex: `([0-9]+\s)<[^>]+>The Argus Developers`,
},
"valid semantic version": {
errRegex: "^$",
Expand All @@ -196,6 +196,7 @@ func TestLookup_Query(t *testing.T) {

for k, v := range tc.env {
os.Setenv(k, v)
defer os.Unsetenv(k)
}
dvl := testLookup()
dvl.URL = tc.url
Expand Down
2 changes: 1 addition & 1 deletion web/help_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ func testDeployedVersion() *deployedver.Lookup {
var (
allowInvalidCerts = false
json = "something"
regex = "([0-9]+) The Argus Developers"
regex = `([0-9]+)\s<[^>]+>The Argus Developers`
regexTemplate = "v$1"
url = "https://release-argus.io"
)
Expand Down
5 changes: 3 additions & 2 deletions webhook/headers.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ func (w *WebHook) setCustomHeaders(req *http.Request) {
ID: *w.ServiceStatus.ServiceID,
LatestVersion: w.ServiceStatus.LatestVersion()}
for _, header := range *customHeaders {
value := util.TemplateString(header.Value, serviceInfo)
req.Header[header.Key] = []string{value}
key := util.EvalEnvVars(header.Key)
value := util.TemplateString(util.EvalEnvVars(header.Value), serviceInfo)
req.Header[key] = []string{value}
}
}
17 changes: 17 additions & 0 deletions webhook/headers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package webhook
import (
"net/http"
"net/http/httptest"
"os"
"testing"

svcstatus "github.com/release-argus/Argus/service/status"
Expand All @@ -29,6 +30,7 @@ func TestWebHook_SetCustomHeaders(t *testing.T) {
latestVersion := "1.2.3"
serviceID := "service"
tests := map[string]struct {
env map[string]string
root *Headers
main *Headers
dfault *Headers
Expand Down Expand Up @@ -115,12 +117,27 @@ func TestWebHook_SetCustomHeaders(t *testing.T) {
"X-Service": serviceID,
"X-Version": latestVersion},
},
"header with env var": {
env: map[string]string{"FOO": "bar"},
root: &Headers{
{Key: "X-Service", Value: "{{ service_id }}"},
{Key: "X-Version", Value: "{{ version }}"},
{Key: "X-Foo", Value: "b${FOO}r"}},
want: map[string]string{
"X-Service": serviceID,
"X-Version": latestVersion,
"X-Foo": "bbarr"},
},
}

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
t.Parallel()

for k, v := range tc.env {
os.Setenv(k, v)
defer os.Unsetenv(k)
}
req := httptest.NewRequest(http.MethodGet, "/approvals", nil)
webhook := WebHook{
ServiceStatus: &svcstatus.Status{
Expand Down

0 comments on commit 87d63d6

Please sign in to comment.