Skip to content

Commit

Permalink
feat(config): hash web.basic_auth.(username|password) locally (#358)
Browse files Browse the repository at this point in the history
* sha256 the username/password and store in the config as 'h__HASH'
* web.basic_auth removed if both username and password are empty
* web.favicon removed if both svg and png overrides are empty
  • Loading branch information
JosephKav authored Feb 5, 2024
1 parent 28b8b6e commit 1ed247b
Show file tree
Hide file tree
Showing 11 changed files with 424 additions and 52 deletions.
12 changes: 8 additions & 4 deletions config/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,26 +233,30 @@ func mapEnvToStruct(src interface{}, prefix string, envVars *[]string) (err erro

// CheckValues are valid.
func (d *Defaults) CheckValues(prefix string) (errs error) {
itemPrefix := prefix + " "
itemPrefix := prefix + " "

// Service
if err := d.Service.CheckValues(itemPrefix); err != nil {
errs = fmt.Errorf("%s%sservice:\\%w",
errs = fmt.Errorf("%s%s service:\\%w",
util.ErrorToString(errs), prefix, err)
}

// Notify
if err := d.Notify.CheckValues(prefix); err != nil {
if err := d.Notify.CheckValues(prefix + " "); err != nil {
errs = fmt.Errorf("%s%w",
util.ErrorToString(errs), err)
}

// WebHook
if err := d.WebHook.CheckValues(itemPrefix); err != nil {
errs = fmt.Errorf("%s%swebhook:\\%w",
errs = fmt.Errorf("%s%s webhook:\\%w",
util.ErrorToString(errs), prefix, err)
}

if errs != nil {
errs = fmt.Errorf("%sdefaults:\\%w",
prefix, errs)
}
return
}

Expand Down
53 changes: 29 additions & 24 deletions config/defaults_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ func TestDefaults_MapEnvToStruct(t *testing.T) {
"ARGUS_SERVICE_LATEST_VERSION_REQUIRE_DOCKER_HUB_TOKEN": "tokenForDockerHub",
"ARGUS_SERVICE_LATEST_VERSION_REQUIRE_DOCKER_HUB_USERNAME": "usernameForDockerHub",
"ARGUS_SERVICE_LATEST_VERSION_REQUIRE_DOCKER_QUAY_TOKEN": "tokenForQuay"},
errRegex: `service:[^ ]+ latest_version:[^ ]+ require:[^ ]+ docker:[^ ]+ type: "foo" <invalid> `,
errRegex: `defaults[^ ]+ service:[^ ]+ latest_version:[^ ]+ require:[^ ]+ docker:[^ ]+ type: "foo" <invalid> `,
},
"service.latest_version - invalid bool - allow_invalid_certs": {
env: map[string]string{
Expand Down Expand Up @@ -374,7 +374,7 @@ func TestDefaults_MapEnvToStruct(t *testing.T) {
&map[string]string{
"delay": "foo"},
nil, nil)}},
errRegex: `notify:[^ ]+ discord:[^ ]+ options:[^ ]+ delay: "foo" <invalid>`,
errRegex: `defaults:[^ ]+ notify:[^ ]+ discord:[^ ]+ options:[^ ]+ delay: "foo" <invalid>`,
},
"notify.smtp": {
env: map[string]string{
Expand Down Expand Up @@ -930,7 +930,7 @@ func TestDefaults_MapEnvToStruct(t *testing.T) {
case string:
if !regexp.MustCompile(tc.errRegex).MatchString(r.(string)) {
t.Errorf("want error matching:\n%v\ngot:\n%v",
tc.errRegex, t)
tc.errRegex, r.(string))
}
default:
t.Fatalf("unexpected panic: %v", r)
Expand Down Expand Up @@ -966,9 +966,10 @@ func TestDefaults_CheckValues(t *testing.T) {
input: &Defaults{Service: service.Defaults{
Options: *opt.NewDefaults("10x", nil)}},
errRegex: []string{
`^service:$`,
`^ options:$`,
`^ interval: "10x" <invalid>`},
`^defaults:$`,
`^ service:$`,
`^ options:$`,
`^ interval: "10x" <invalid>`},
},
"Service.LatestVersion.Require.Docker.Type": {
input: &Defaults{Service: service.Defaults{
Expand All @@ -978,11 +979,12 @@ func TestDefaults_CheckValues(t *testing.T) {
"pizza",
"", "", "", "", nil)}}}},
errRegex: []string{
`^service:$`,
`^ latest_version:$`,
`^ require:$`,
`^ docker:$`,
`^ type: "pizza" <invalid>`},
`^defaults:$`,
`^ service:$`,
`^ latest_version:$`,
`^ require:$`,
`^ docker:$`,
`^ type: "pizza" <invalid>`},
},
"Service.Interval + Service.DeployedVersionLookup.Regex": {
input: &Defaults{Service: service.Defaults{
Expand All @@ -993,13 +995,14 @@ func TestDefaults_CheckValues(t *testing.T) {
"pizza",
"", "", "", "", nil)}}}},
errRegex: []string{
`^service:$`,
`^ options:$`,
`^ interval: "10x" <invalid>`,
`^ latest_version:$`,
`^ require:$`,
`^ docker:$`,
`^ type: "pizza" <invalid>`},
`^defaults:$`,
`^ service:$`,
`^ options:$`,
`^ interval: "10x" <invalid>`,
`^ latest_version:$`,
`^ require:$`,
`^ docker:$`,
`^ type: "pizza" <invalid>`},
},
"Notify.x.Delay": {
input: &Defaults{Notify: shoutrrr.SliceDefaults{
Expand All @@ -1008,17 +1011,19 @@ func TestDefaults_CheckValues(t *testing.T) {
&map[string]string{"delay": "10x"},
nil, nil)}},
errRegex: []string{
`^notify:$`,
`^ slack:$`,
`^ options:`,
`^ delay: "10x" <invalid>`},
`^defaults:$`,
`^ notify:$`,
`^ slack:$`,
`^ options:`,
`^ delay: "10x" <invalid>`},
},
"WebHook.Delay": {
input: &Defaults{WebHook: *webhook.NewDefaults(
nil, nil, "10x", nil, nil, "", nil, "", "")},
errRegex: []string{
`^webhook:$`,
`^ delay: "10x" <invalid>`},
`^defaults:$`,
`^ webhook:$`,
`^ delay: "10x" <invalid>`},
},
}

Expand Down
72 changes: 70 additions & 2 deletions config/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
package config

import (
"crypto/sha256"
"encoding/hex"
"flag"
"fmt"
"os"
Expand Down Expand Up @@ -95,10 +97,70 @@ type WebSettings struct {
Favicon *FaviconSettings `yaml:"favicon,omitempty"` // Favicon settings
}

// String returns a string representation of the WebSettings.
func (s *WebSettings) String(prefix string) (str string) {
if s != nil {
str = util.ToYAMLString(s, prefix)
}
return
}

func (s *WebSettings) CheckValues() {
// BasicAuth
if s.BasicAuth != nil {
// Remove the BasicAuth if both the Username and Password are empty.
if s.BasicAuth.Username == "" && s.BasicAuth.Password == "" {
s.BasicAuth = nil
} else {
s.BasicAuth.CheckValues()
}
}

// Favicon
if s.Favicon != nil {
// Remove the Favicon override if both the SVG and PNG are empty.
if s.Favicon.SVG == "" && s.Favicon.PNG == "" {
s.Favicon = nil
}
}
}

// WebSettingsBasicAuth contains the basic auth credentials to use (if any)
type WebSettingsBasicAuth struct {
Username string `yaml:"username,omitempty"`
Password string `yaml:"password,omitempty"`
Username string `yaml:"username,omitempty"`
UsernameHash [32]byte `yaml:"-"` // SHA256 hash
Password string `yaml:"password,omitempty"`
PasswordHash [32]byte `yaml:"-"` // SHA256 hash
}

// String returns a string representation of the WebSettingsBasicAuth.
func (s *WebSettingsBasicAuth) String(prefix string) (str string) {
if s != nil {
str = util.ToYAMLString(s, prefix)
}
return
}

// CheckValues will ensure that the values are SHA256 hashed.
func (ba *WebSettingsBasicAuth) CheckValues() {
// Ensure it's hashed.
sha256Regex := "^h__[a-f0-9]{64}$"
// Username - Hash if not already hashed.
if !util.RegexCheck(sha256Regex, ba.Username) {
ba.UsernameHash = sha256.Sum256([]byte(ba.Username))
ba.Username = fmt.Sprintf("h__%x", ba.UsernameHash)
} else {
hash, _ := hex.DecodeString(ba.Username[3:])
copy(ba.UsernameHash[:], hash)
}
// Password - Hash if not already hashed.
if !util.RegexCheck(sha256Regex, ba.Password) {
ba.PasswordHash = sha256.Sum256([]byte(ba.Password))
ba.Password = fmt.Sprintf("h__%x", ba.PasswordHash)
} else {
hash, _ := hex.DecodeString(ba.Password[3:])
copy(ba.PasswordHash[:], hash)
}
}

// FaviconSettings contains the favicon override settings.
Expand Down Expand Up @@ -289,3 +351,9 @@ func (s *Settings) WebKeyFile() *string {
}
return keyFile
}

// CheckValues of the Settings.
func (s *Settings) CheckValues() {
// Web
s.Web.CheckValues()
}
Loading

0 comments on commit 1ed247b

Please sign in to comment.