Start working on #8: Support basic authentication for the dashboard

This commit is contained in:
TwinProduction 2020-10-14 19:22:58 -04:00
parent 70c9c4b87c
commit 9220a777bb
4 changed files with 117 additions and 4 deletions

View File

@ -5,6 +5,7 @@ import (
"github.com/TwinProduction/gatus/alerting" "github.com/TwinProduction/gatus/alerting"
"github.com/TwinProduction/gatus/alerting/provider" "github.com/TwinProduction/gatus/alerting/provider"
"github.com/TwinProduction/gatus/core" "github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/security"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
"io/ioutil" "io/ioutil"
"log" "log"
@ -18,16 +19,18 @@ const (
) )
var ( var (
ErrNoServiceInConfig = errors.New("configuration file should contain at least 1 service") ErrNoServiceInConfig = errors.New("configuration file should contain at least 1 service")
ErrConfigFileNotFound = errors.New("configuration file not found") ErrConfigFileNotFound = errors.New("configuration file not found")
ErrConfigNotLoaded = errors.New("configuration is nil") ErrConfigNotLoaded = errors.New("configuration is nil")
config *Config ErrInvalidSecurityConfig = errors.New("invalid security configuration")
config *Config
) )
// Config is the main configuration structure // Config is the main configuration structure
type Config struct { type Config struct {
Metrics bool `yaml:"metrics"` Metrics bool `yaml:"metrics"`
Debug bool `yaml:"debug"` Debug bool `yaml:"debug"`
Security *security.Config `yaml:"security"`
Alerting *alerting.Config `yaml:"alerting"` Alerting *alerting.Config `yaml:"alerting"`
Services []*core.Service `yaml:"services"` Services []*core.Service `yaml:"services"`
} }
@ -83,6 +86,7 @@ func parseAndValidateConfigBytes(yamlBytes []byte) (config *Config, err error) {
err = ErrNoServiceInConfig err = ErrNoServiceInConfig
} else { } else {
validateAlertingConfig(config) validateAlertingConfig(config)
validateSecurityConfig(config)
validateServicesConfig(config) validateServicesConfig(config)
} }
return return
@ -98,6 +102,20 @@ func validateServicesConfig(config *Config) {
log.Printf("[config][validateServicesConfig] Validated %d services", len(config.Services)) log.Printf("[config][validateServicesConfig] Validated %d services", len(config.Services))
} }
func validateSecurityConfig(config *Config) {
if config.Security != nil {
if config.Security.IsValid() {
if config.Debug {
log.Printf("[config][validateSecurityConfig] Basic security configuration has been validated")
}
} else {
// If there was an attempt to configure security, then it must mean that some confidential or private
// data are exposed. As a result, we'll force a panic because it's better to be safe than sorry.
panic(ErrInvalidSecurityConfig)
}
}
}
func validateAlertingConfig(config *Config) { func validateAlertingConfig(config *Config) {
if config.Alerting == nil { if config.Alerting == nil {
log.Printf("[config][validateAlertingConfig] Alerting is not configured") log.Printf("[config][validateAlertingConfig] Alerting is not configured")

View File

@ -1,6 +1,7 @@
package config package config
import ( import (
"fmt"
"github.com/TwinProduction/gatus/core" "github.com/TwinProduction/gatus/core"
"testing" "testing"
"time" "time"
@ -217,3 +218,56 @@ services:
t.Fatal("PagerDuty alerting config should've been invalid") t.Fatal("PagerDuty alerting config should've been invalid")
} }
} }
func TestParseAndValidateConfigBytesWithInvalidSecurityConfig(t *testing.T) {
defer func() { recover() }()
_, _ = parseAndValidateConfigBytes([]byte(`
security:
basic:
username: "admin"
password-sha512: "invalid-sha512-hash"
services:
- name: twinnation
url: https://twinnation.org/actuator/health
conditions:
- "[STATUS] == 200"
`))
t.Error("Function should've panicked")
}
func TestParseAndValidateConfigBytesWithValidSecurityConfig(t *testing.T) {
const expectedUsername = "admin"
const expectedPasswordHash = "6b97ed68d14eb3f1aa959ce5d49c7dc612e1eb1dafd73b1e705847483fd6a6c809f2ceb4e8df6ff9984c6298ff0285cace6614bf8daa9f0070101b6c89899e22"
config, err := parseAndValidateConfigBytes([]byte(fmt.Sprintf(`
security:
basic:
username: "%s"
password-sha512: "%s"
services:
- name: twinnation
url: https://twinnation.org/actuator/health
conditions:
- "[STATUS] == 200"
`, expectedUsername, expectedPasswordHash)))
if err != nil {
t.Error("No error should've been returned")
}
if config == nil {
t.Fatal("Config shouldn't have been nil")
}
if config.Security == nil {
t.Fatal("config.Security shouldn't have been nil")
}
if !config.Security.IsValid() {
t.Error("Security config should've been valid")
}
if config.Security.Basic == nil {
t.Fatal("config.Security.Basic shouldn't have been nil")
}
if config.Security.Basic.Username != expectedUsername {
t.Errorf("config.Security.Basic.Username should've been %s, but was %s", expectedUsername, config.Security.Basic.Username)
}
if config.Security.Basic.PasswordSha512Hash != expectedPasswordHash {
t.Errorf("config.Security.Basic.PasswordSha512Hash should've been %s, but was %s", expectedPasswordHash, config.Security.Basic.PasswordSha512Hash)
}
}

18
security/security.go Normal file
View File

@ -0,0 +1,18 @@
package security
type Config struct {
Basic *BasicConfig `yaml:"basic"`
}
func (c *Config) IsValid() bool {
return c.Basic != nil && c.Basic.IsValid()
}
type BasicConfig struct {
Username string `yaml:"username"`
PasswordSha512Hash string `yaml:"password-sha512"`
}
func (c *BasicConfig) IsValid() bool {
return len(c.Username) > 0 && len(c.PasswordSha512Hash) == 128
}

23
security/security_test.go Normal file
View File

@ -0,0 +1,23 @@
package security
import "testing"
func TestBasicConfig_IsValid(t *testing.T) {
basicConfig := &BasicConfig{
Username: "admin",
PasswordSha512Hash: Sha512("test"),
}
if !basicConfig.IsValid() {
t.Error("basicConfig should've been valid")
}
}
func TestBasicConfig_IsValidWhenPasswordIsInvalid(t *testing.T) {
basicConfig := &BasicConfig{
Username: "admin",
PasswordSha512Hash: "",
}
if basicConfig.IsValid() {
t.Error("basicConfig shouldn't have been valid")
}
}