diff --git a/.golangci.yml b/.golangci.yml index a943edee4f4a8..49b0d229c3819 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -30,6 +30,7 @@ linters: - unconvert - unparam - whitespace + - wsl issues: exclude: diff --git a/cmd/authelia-scripts/cmd_bootstrap.go b/cmd/authelia-scripts/cmd_bootstrap.go index 4aac5076376b8..33c3448e2a9c4 100644 --- a/cmd/authelia-scripts/cmd_bootstrap.go +++ b/cmd/authelia-scripts/cmd_bootstrap.go @@ -99,6 +99,7 @@ func prepareHostsFile() { for _, entry := range hostEntries { domainInHostFile := false + for i, line := range lines { domainFound := strings.Contains(line, entry.Domain) ipFound := strings.Contains(line, entry.IP) @@ -154,6 +155,7 @@ func readHostsFile() ([]byte, error) { if err != nil { return nil, err } + return bs, nil } @@ -188,6 +190,7 @@ func Bootstrap(cobraCmd *cobra.Command, args []string) { bootstrapPrintln("Checking if GOPATH is set") goPathFound := false + for _, v := range os.Environ() { if strings.HasPrefix(v, "GOPATH=") { goPathFound = true diff --git a/cmd/authelia-scripts/cmd_build.go b/cmd/authelia-scripts/cmd_build.go index 8e6b98c28f505..9c955d6e133dd 100644 --- a/cmd/authelia-scripts/cmd_build.go +++ b/cmd/authelia-scripts/cmd_build.go @@ -12,6 +12,7 @@ import ( func buildAutheliaBinary() { cmd := utils.CommandWithStdout("go", "build", "-o", "../../"+OutputDir+"/authelia") cmd.Dir = "cmd/authelia" + cmd.Env = append(os.Environ(), "GOOS=linux", "GOARCH=amd64", "CGO_ENABLED=1") @@ -34,6 +35,7 @@ func buildFrontend() { // Then build the frontend. cmd = utils.CommandWithStdout("yarn", "build") cmd.Dir = webDirectory + cmd.Env = append(os.Environ(), "INLINE_RUNTIME_CHUNK=false") if err := cmd.Run(); err != nil { diff --git a/cmd/authelia-scripts/cmd_ci.go b/cmd/authelia-scripts/cmd_ci.go index 92f7f90cbeda4..da9e8572c3195 100644 --- a/cmd/authelia-scripts/cmd_ci.go +++ b/cmd/authelia-scripts/cmd_ci.go @@ -10,11 +10,13 @@ import ( // RunCI run the CI scripts. func RunCI(cmd *cobra.Command, args []string) { log.Info("=====> Build stage <=====") + if err := utils.CommandWithStdout("authelia-scripts", "--log-level", "debug", "build").Run(); err != nil { log.Fatal(err) } log.Info("=====> Unit testing stage <=====") + if err := utils.CommandWithStdout("authelia-scripts", "--log-level", "debug", "unittest").Run(); err != nil { log.Fatal(err) } diff --git a/cmd/authelia-scripts/cmd_docker.go b/cmd/authelia-scripts/cmd_docker.go index 9a9bb2b644847..1d9e99421d212 100644 --- a/cmd/authelia-scripts/cmd_docker.go +++ b/cmd/authelia-scripts/cmd_docker.go @@ -37,6 +37,7 @@ func checkArchIsSupported(arch string) { return } } + log.Fatal("Architecture is not supported. Please select one of " + strings.Join(supportedArch, ", ") + ".") } @@ -90,9 +91,11 @@ func dockerBuildOfficialImage(arch string) error { cmd.Stdout = nil cmd.Stderr = nil commitBytes, err := cmd.Output() + if err != nil { log.Fatal(err) } + commitHash := strings.Trim(string(commitBytes), "\n") return docker.Build(IntermediateDockerImageName, dockerfile, ".", gitTag, commitHash) @@ -202,9 +205,9 @@ func publishDockerImage(arch string) { if ciTag != "" { if len(tags) == 4 { log.Infof("Detected tags: '%s' | '%s' | '%s'", tags[1], tags[2], tags[3]) - login(docker) deploy(docker, tags[1]+"-"+arch) + if !ignoredSuffixes.MatchString(ciTag) { deploy(docker, tags[2]+"-"+arch) deploy(docker, tags[3]+"-"+arch) @@ -233,7 +236,6 @@ func publishDockerManifest() { if ciTag != "" { if len(tags) == 4 { log.Infof("Detected tags: '%s' | '%s' | '%s'", tags[1], tags[2], tags[3]) - login(docker) deployManifest(docker, tags[1], tags[1]+"-amd64", tags[1]+"-arm32v7", tags[1]+"-arm64v8") publishDockerReadme(docker) diff --git a/cmd/authelia-scripts/cmd_suites.go b/cmd/authelia-scripts/cmd_suites.go index ae4e231d71cf9..aa28428558de1 100644 --- a/cmd/authelia-scripts/cmd_suites.go +++ b/cmd/authelia-scripts/cmd_suites.go @@ -108,6 +108,7 @@ func listSuites() []string { suiteNames := make([]string, 0) suiteNames = append(suiteNames, suites.GlobalRegistry.Suites()...) sort.Strings(suiteNames) + return suiteNames } @@ -119,6 +120,7 @@ func checkSuiteAvailable(suite string) error { return nil } } + return ErrNotAvailableSuite } @@ -130,6 +132,7 @@ func runSuiteSetupTeardown(command string, suite string) error { if err == ErrNotAvailableSuite { log.Fatal(errors.New("Suite named " + selectedSuite + " does not exist")) } + log.Fatal(err) } @@ -139,6 +142,7 @@ func runSuiteSetupTeardown(command string, suite string) error { cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Env = os.Environ() + return utils.RunCommandWithTimeout(cmd, s.SetUpTimeout) } @@ -147,6 +151,7 @@ func runOnSetupTimeout(suite string) error { cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Env = os.Environ() + return utils.RunCommandWithTimeout(cmd, 15*time.Second) } @@ -155,11 +160,13 @@ func runOnError(suite string) error { cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Env = os.Environ() + return utils.RunCommandWithTimeout(cmd, 15*time.Second) } func setupSuite(suiteName string) error { log.Infof("Setup environment for suite %s...", suiteName) + signalChannel := make(chan os.Signal) signal.Notify(signalChannel, os.Interrupt, syscall.SIGTERM) @@ -167,6 +174,7 @@ func setupSuite(suiteName string) error { go func() { <-signalChannel + interrupted = true }() @@ -174,7 +182,9 @@ func setupSuite(suiteName string) error { if errSetup == utils.ErrTimeoutReached { runOnSetupTimeout(suiteName) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting. } + teardownSuite(suiteName) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting. + return errSetup } @@ -230,6 +240,7 @@ func getRunningSuite() (string, error) { } b, err := ioutil.ReadFile(runningSuiteFile) + return string(b), err } @@ -247,6 +258,7 @@ func runSuiteTests(suiteName string, withEnv bool) error { if suite.TestTimeout > 0 { timeout = fmt.Sprintf("%ds", int64(suite.TestTimeout/time.Second)) } + testCmdLine := fmt.Sprintf("go test -count=1 -v ./internal/suites -timeout %s ", timeout) if testPattern != "" { @@ -262,6 +274,7 @@ func runSuiteTests(suiteName string, withEnv bool) error { cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Env = os.Environ() + if headless { cmd.Env = append(cmd.Env, "HEADLESS=y") } @@ -293,16 +306,20 @@ func runMultipleSuitesTests(suiteNames []string, withEnv bool) error { return err } } + return nil } func runAllSuites() error { log.Info("Start running all suites") + for _, s := range listSuites() { if err := runSuiteTests(s, true); err != nil { return err } } + log.Info("All suites passed successfully") + return nil } diff --git a/cmd/authelia-scripts/cmd_unittest.go b/cmd/authelia-scripts/cmd_unittest.go index f09947dd60738..df4368ba61e54 100644 --- a/cmd/authelia-scripts/cmd_unittest.go +++ b/cmd/authelia-scripts/cmd_unittest.go @@ -12,13 +12,16 @@ import ( // RunUnitTest run the unit tests. func RunUnitTest(cobraCmd *cobra.Command, args []string) { log.SetLevel(log.TraceLevel) + if err := utils.Shell("go test $(go list ./... | grep -v suites)").Run(); err != nil { log.Fatal(err) } cmd := utils.Shell("yarn test") cmd.Dir = webDirectory + cmd.Env = append(os.Environ(), "CI=true") + if err := cmd.Run(); err != nil { log.Fatal(err) } diff --git a/cmd/authelia-scripts/main.go b/cmd/authelia-scripts/main.go index d81f8a15d4fde..44905e3f46e8c 100755 --- a/cmd/authelia-scripts/main.go +++ b/cmd/authelia-scripts/main.go @@ -85,11 +85,13 @@ func levelStringToLevel(level string) log.Level { } else if level == "warning" { return log.WarnLevel } + return log.InfoLevel } func main() { var rootCmd = &cobra.Command{Use: "authelia-scripts"} + cobraCommands := make([]*cobra.Command, 0) for _, autheliaCommand := range Commands { @@ -99,6 +101,7 @@ func main() { cmdline := autheliaCommand.CommandLine fn = func(cobraCmd *cobra.Command, args []string) { cmd := utils.CommandWithStdout(cmdline, args...) + err := cmd.Run() if err != nil { panic(err) @@ -131,6 +134,7 @@ func main() { cobraCommands = append(cobraCommands, command) } + cobraCommands = append(cobraCommands, commands.HashPasswordCmd) rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "Set the log level for the command") diff --git a/cmd/authelia-suites/main.go b/cmd/authelia-suites/main.go index 66f9fac8bfa2e..5e64e5a202d78 100644 --- a/cmd/authelia-suites/main.go +++ b/cmd/authelia-suites/main.go @@ -55,6 +55,7 @@ func main() { rootCmd.AddCommand(setupTimeoutCmd) rootCmd.AddCommand(errorCmd) rootCmd.AddCommand(stopCmd) + if err := rootCmd.Execute(); err != nil { log.Fatal(err) } @@ -125,6 +126,7 @@ func setupTimeoutSuite(cmd *cobra.Command, args []string) { if s.OnSetupTimeout == nil { return } + if err := s.OnSetupTimeout(); err != nil { log.Fatal(err) } @@ -137,6 +139,7 @@ func runErrorCallback(cmd *cobra.Command, args []string) { if s.OnError == nil { return } + if err := s.OnError(); err != nil { log.Fatal(err) } diff --git a/cmd/authelia/main.go b/cmd/authelia/main.go index 6b740c347754b..0b49a8921d962 100644 --- a/cmd/authelia/main.go +++ b/cmd/authelia/main.go @@ -37,6 +37,7 @@ func startServer() { for _, err := range errs { logging.Logger().Error(err) } + panic(errors.New("Some errors have been reported")) } @@ -89,6 +90,7 @@ func startServer() { } else { log.Fatalf("Unrecognized notifier") } + if !config.Notifier.DisableStartupCheck { _, err := notifier.StartupCheck() if err != nil { diff --git a/internal/authentication/file_user_provider.go b/internal/authentication/file_user_provider.go index bb6d41d0b7651..bbfa9da90a0df 100644 --- a/internal/authentication/file_user_provider.go +++ b/internal/authentication/file_user_provider.go @@ -57,6 +57,7 @@ func NewFileUserProvider(configuration *schema.FileAuthenticationBackendConfigur if configuration.Password.Algorithm == sha512 { cryptAlgo = HashingAlgorithmSHA512 } + settings := getCryptSettings(utils.RandomString(configuration.Password.SaltLength, HashingPossibleSaltCharacters), cryptAlgo, configuration.Password.Iterations, configuration.Password.Memory*1024, configuration.Password.Parallelism, configuration.Password.KeyLength) @@ -78,6 +79,7 @@ func checkPasswordHashes(database *DatabaseModel) error { return fmt.Errorf("Unable to parse hash of user %s: %s", u, err) } } + return nil } @@ -86,7 +88,9 @@ func readDatabase(path string) (*DatabaseModel, error) { if err != nil { return nil, fmt.Errorf("Unable to read database from file %s: %s", path, err) } + db := DatabaseModel{} + err = yaml.Unmarshal(content, &db) if err != nil { return nil, fmt.Errorf("Unable to parse database: %s", err) @@ -100,6 +104,7 @@ func readDatabase(path string) (*DatabaseModel, error) { if !ok { return nil, fmt.Errorf("The database format is invalid: %s", err) } + return &db, nil } @@ -107,10 +112,12 @@ func readDatabase(path string) (*DatabaseModel, error) { func (p *FileUserProvider) CheckUserPassword(username string, password string) (bool, error) { if details, ok := p.database.Users[username]; ok { hashedPassword := strings.ReplaceAll(details.HashedPassword, "{CRYPT}", "") + ok, err := CheckPassword(password, hashedPassword) if err != nil { return false, err } + return ok, nil } @@ -130,6 +137,7 @@ func (p *FileUserProvider) GetDetails(username string) (*UserDetails, error) { Emails: []string{details.Email}, }, nil } + return nil, fmt.Errorf("User '%s' does not exist in database", username) } @@ -153,11 +161,12 @@ func (p *FileUserProvider) UpdatePassword(username string, newPassword string) e newPassword, "", algorithm, p.configuration.Password.Iterations, p.configuration.Password.Memory*1024, p.configuration.Password.Parallelism, p.configuration.Password.KeyLength, p.configuration.Password.SaltLength) - if err != nil { return err } + details.HashedPassword = hash + p.lock.Lock() p.database.Users[username] = details @@ -166,7 +175,9 @@ func (p *FileUserProvider) UpdatePassword(username string, newPassword string) e p.lock.Unlock() return err } + err = ioutil.WriteFile(p.configuration.Path, b, 0644) //nolint:gosec // Fixed in future PR. p.lock.Unlock() + return err } diff --git a/internal/authentication/ldap_connection_factory.go b/internal/authentication/ldap_connection_factory.go index bf5df2fd3f054..7d635b4bb2312 100644 --- a/internal/authentication/ldap_connection_factory.go +++ b/internal/authentication/ldap_connection_factory.go @@ -69,6 +69,7 @@ func (lcf *LDAPConnectionFactoryImpl) DialTLS(network, addr string, config *tls. if err != nil { return nil, err } + return NewLDAPConnectionImpl(conn), nil } @@ -78,5 +79,6 @@ func (lcf *LDAPConnectionFactoryImpl) Dial(network, addr string) (LDAPConnection if err != nil { return nil, err } + return NewLDAPConnectionImpl(conn), nil } diff --git a/internal/authentication/ldap_user_provider.go b/internal/authentication/ldap_user_provider.go index eebbdace93844..9d8baf0dd7839 100644 --- a/internal/authentication/ldap_user_provider.go +++ b/internal/authentication/ldap_user_provider.go @@ -47,12 +47,14 @@ func (p *LDAPUserProvider) connect(userDN string, password string) (LDAPConnecti if url.Scheme == "ldaps" { logging.Logger().Trace("LDAP client starts a TLS session") + conn, err := p.connectionFactory.DialTLS("tcp", url.Host, &tls.Config{ InsecureSkipVerify: p.configuration.SkipVerify, //nolint:gosec // This is a configurable option, is desirable in some situations and is off by default }) if err != nil { return nil, err } + newConnection = conn } else { logging.Logger().Trace("LDAP client starts a session over raw TCP") @@ -66,6 +68,7 @@ func (p *LDAPUserProvider) connect(userDN string, password string) (LDAPConnecti if err := newConnection.Bind(userDN, password); err != nil { return nil, err } + return newConnection, nil } @@ -100,6 +103,7 @@ func (p *LDAPUserProvider) ldapEscape(inputUsername string) string { for _, c := range specialLDAPRunes { inputUsername = strings.ReplaceAll(inputUsername, string(c), fmt.Sprintf("\\%c", c)) } + return inputUsername } @@ -122,6 +126,7 @@ func (p *LDAPUserProvider) resolveUsersFilter(userFilter string, inputUsername s // in configuration. userFilter = strings.ReplaceAll(userFilter, "{username_attribute}", p.configuration.UsernameAttribute) userFilter = strings.ReplaceAll(userFilter, "{mail_attribute}", p.configuration.MailAttribute) + return userFilter } @@ -160,15 +165,18 @@ func (p *LDAPUserProvider) getUserProfile(conn LDAPConnection, inputUsername str userProfile := ldapUserProfile{ DN: sr.Entries[0].DN, } + for _, attr := range sr.Entries[0].Attributes { if attr.Name == p.configuration.MailAttribute { userProfile.Emails = attr.Values } + if attr.Name == p.configuration.UsernameAttribute { if len(attr.Values) != 1 { return nil, fmt.Errorf("User %s cannot have multiple value for attribute %s", inputUsername, p.configuration.UsernameAttribute) } + userProfile.Username = attr.Values[0] } } @@ -186,6 +194,7 @@ func (p *LDAPUserProvider) resolveGroupsFilter(inputUsername string, profile *ld // We temporarily keep placeholder {0} for backward compatibility. groupFilter := strings.ReplaceAll(p.configuration.GroupsFilter, "{0}", inputUsername) groupFilter = strings.ReplaceAll(groupFilter, "{input}", inputUsername) + if profile != nil { // We temporarily keep placeholder {1} for backward compatibility. groupFilter = strings.ReplaceAll(groupFilter, "{1}", ldap.EscapeFilter(profile.Username)) @@ -213,6 +222,7 @@ func (p *LDAPUserProvider) GetDetails(inputUsername string) (*UserDetails, error if err != nil { return nil, fmt.Errorf("Unable to create group filter for user %s. Cause: %s", inputUsername, err) } + logging.Logger().Tracef("Computed groups filter is %s", groupsFilter) groupBaseDN := p.configuration.BaseDN @@ -233,6 +243,7 @@ func (p *LDAPUserProvider) GetDetails(inputUsername string) (*UserDetails, error } groups := make([]string, 0) + for _, res := range sr.Entries { if len(res.Attributes) == 0 { logging.Logger().Warningf("No groups retrieved from LDAP for user %s", inputUsername) diff --git a/internal/authentication/password_hash.go b/internal/authentication/password_hash.go index 9586147f05702..c2e87aea160b9 100644 --- a/internal/authentication/password_hash.go +++ b/internal/authentication/password_hash.go @@ -38,6 +38,7 @@ func ParseHash(hash string) (passwordHash *PasswordHash, err error) { if h.Key != parts[len(parts)-1] { return nil, fmt.Errorf("Hash key is not the last parameter, the hash is likely malformed (%s)", hash) } + if h.Key == "" { return nil, fmt.Errorf("Hash key contains no characters or the field length is invalid (%s)", hash) } @@ -50,6 +51,7 @@ func ParseHash(hash string) (passwordHash *PasswordHash, err error) { if code == HashingAlgorithmSHA512 { h.Iterations = parameters.GetInt("rounds", HashingDefaultSHA512Iterations) h.Algorithm = HashingAlgorithmSHA512 + if parameters["rounds"] != "" && parameters["rounds"] != strconv.Itoa(h.Iterations) { return nil, fmt.Errorf("SHA512 iterations is not numeric (%s)", parameters["rounds"]) } @@ -79,6 +81,7 @@ func ParseHash(hash string) (passwordHash *PasswordHash, err error) { } else { return nil, fmt.Errorf("Authelia only supports salted SHA512 hashing ($6$) and salted argon2id ($argon2id$), not $%s$", code) } + return h, nil } @@ -110,28 +113,33 @@ func HashPassword(password, salt string, algorithm CryptAlgo, iterations, memory if memory < 8 { return "", fmt.Errorf("Memory (argon2id) input of %d is invalid, it must be 8 or higher", memory) } + if parallelism < 1 { return "", fmt.Errorf("Parallelism (argon2id) input of %d is invalid, it must be 1 or higher", parallelism) } + if memory < parallelism*8 { return "", fmt.Errorf("Memory (argon2id) input of %d is invalid with a parallelism input of %d, it must be %d (parallelism * 8) or higher", memory, parallelism, parallelism*8) } + if keyLength < 16 { return "", fmt.Errorf("Key length (argon2id) input of %d is invalid, it must be 16 or higher", keyLength) } + if iterations < 1 { return "", fmt.Errorf("Iterations (argon2id) input of %d is invalid, it must be 1 or more", iterations) } - // Caution: Increasing any of the values in the above block has a high chance in old passwords that cannot be verified. } if salt == "" { salt = utils.RandomString(saltLength, HashingPossibleSaltCharacters) } + settings = getCryptSettings(salt, algorithm, iterations, memory, parallelism, keyLength) // This error can be ignored because we check for it before a user gets here. hash, _ = crypt.Crypt(password, settings) + return hash, nil } @@ -141,10 +149,12 @@ func CheckPassword(password, hash string) (ok bool, err error) { if err != nil { return false, err } + expectedHash, err := HashPassword(password, passwordHash.Salt, passwordHash.Algorithm, passwordHash.Iterations, passwordHash.Memory, passwordHash.Parallelism, passwordHash.KeyLength, len(passwordHash.Salt)) if err != nil { return false, err } + return hash == expectedHash, nil } @@ -156,5 +166,6 @@ func getCryptSettings(salt string, algorithm CryptAlgo, iterations, memory, para } else { panic("invalid password hashing algorithm provided") } + return settings } diff --git a/internal/authentication/password_hash_test.go b/internal/authentication/password_hash_test.go index b169cb465d79d..9017cee005d6d 100644 --- a/internal/authentication/password_hash_test.go +++ b/internal/authentication/password_hash_test.go @@ -47,10 +47,13 @@ func TestShouldHashArgon2idPassword(t *testing.T) { // This checks the method of hashing (for argon2id) supports all the characters we allow in Authelia's hash function. func TestArgon2idHashSaltValidValues(t *testing.T) { + var err error + + var hash string + data := string(HashingPossibleSaltCharacters) datas := utils.SliceString(data, 16) - var hash string - var err error + for _, salt := range datas { hash, err = HashPassword("password", salt, HashingAlgorithmArgon2id, 1, 8, 1, 32, 16) assert.NoError(t, err) @@ -60,10 +63,13 @@ func TestArgon2idHashSaltValidValues(t *testing.T) { // This checks the method of hashing (for sha512) supports all the characters we allow in Authelia's hash function. func TestSHA512HashSaltValidValues(t *testing.T) { + var err error + + var hash string + data := string(HashingPossibleSaltCharacters) datas := utils.SliceString(data, 16) - var hash string - var err error + for _, salt := range datas { hash, err = HashPassword("password", salt, HashingAlgorithmSHA512, 1000, 0, 0, 0, 16) assert.NoError(t, err) diff --git a/internal/authorization/authorizer.go b/internal/authorization/authorizer.go index e138c3c24518d..5395afdf307d6 100644 --- a/internal/authorization/authorizer.go +++ b/internal/authorization/authorizer.go @@ -71,6 +71,7 @@ func selectMatchingObjectRules(rules []schema.ACLRule, object Object) []schema.A selectedRules = append(selectedRules, rule) } } + return selectedRules } @@ -123,6 +124,7 @@ func (p *Authorizer) GetRequiredLevel(subject Subject, requestURL url.URL) Level if len(matchingRules) > 0 { return PolicyToLevel(matchingRules[0].Policy) } + logging.Logger().Tracef("No matching rule for subject %s and url %s... Applying default policy.", subject.String(), requestURL.String()) @@ -141,5 +143,6 @@ func (p *Authorizer) IsURLMatchingRuleWithGroupSubjects(requestURL url.URL) (has } } } + return false } diff --git a/internal/authorization/domain_matcher.go b/internal/authorization/domain_matcher.go index e4a11f390915b..c34c8249d240c 100644 --- a/internal/authorization/domain_matcher.go +++ b/internal/authorization/domain_matcher.go @@ -10,5 +10,6 @@ func isDomainMatching(domain string, domainRules []string) bool { return true } } + return false } diff --git a/internal/authorization/ip_matcher.go b/internal/authorization/ip_matcher.go index c5814a5372107..ec8822417aaf2 100644 --- a/internal/authorization/ip_matcher.go +++ b/internal/authorization/ip_matcher.go @@ -17,9 +17,12 @@ func isIPMatching(ip net.IP, networks []string) bool { if ip.String() == network { return true } + continue } + _, ipNet, err := net.ParseCIDR(network) + if err != nil { // TODO(c.michaud): make sure the rule is valid at startup to // to such a case here. @@ -30,5 +33,6 @@ func isIPMatching(ip net.IP, networks []string) bool { return true } } + return false } diff --git a/internal/authorization/path_matcher.go b/internal/authorization/path_matcher.go index b2b3b11afaeb7..ea90c8e751544 100644 --- a/internal/authorization/path_matcher.go +++ b/internal/authorization/path_matcher.go @@ -20,5 +20,6 @@ func isPathMatching(path string, pathRegexps []string) bool { return true } } + return false } diff --git a/internal/authorization/subject_matcher.go b/internal/authorization/subject_matcher.go index 5161dc8841b1e..d47093b95a699 100644 --- a/internal/authorization/subject_matcher.go +++ b/internal/authorization/subject_matcher.go @@ -25,5 +25,6 @@ func isSubjectMatching(subject Subject, subjectRule string) bool { return true } } + return false } diff --git a/internal/commands/certificates.go b/internal/commands/certificates.go index 67ac099f48d9d..fa50684af5cdc 100644 --- a/internal/commands/certificates.go +++ b/internal/commands/certificates.go @@ -34,6 +34,7 @@ var ( func init() { CertificatesGenerateCmd.PersistentFlags().StringVar(&host, "host", "", "Comma-separated hostnames and IPs to generate a certificate for") err := CertificatesGenerateCmd.MarkPersistentFlagRequired("host") + if err != nil { log.Fatal(err) } @@ -66,7 +67,9 @@ func publicKey(priv interface{}) interface{} { func generateSelfSignedCertificate(cmd *cobra.Command, args []string) { // implementation retrieved from https://golang.org/src/crypto/tls/generate_cert.go var priv interface{} + var err error + switch ecdsaCurve { case "": if ed25519Key { @@ -85,6 +88,7 @@ func generateSelfSignedCertificate(cmd *cobra.Command, args []string) { default: log.Fatalf("Unrecognized elliptic curve: %q", ecdsaCurve) } + if err != nil { log.Fatalf("Failed to generate private key: %v", err) } @@ -103,6 +107,7 @@ func generateSelfSignedCertificate(cmd *cobra.Command, args []string) { serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { log.Fatalf("Failed to generate serial number: %v", err) } @@ -141,33 +146,42 @@ func generateSelfSignedCertificate(cmd *cobra.Command, args []string) { certPath := path.Join(targetDirectory, "cert.pem") certOut, err := os.Create(certPath) + if err != nil { log.Fatalf("Failed to open %s for writing: %v", certPath, err) } + if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { log.Fatalf("Failed to write data to cert.pem: %v", err) } + if err := certOut.Close(); err != nil { log.Fatalf("Error closing %s: %v", certPath, err) } + log.Printf("wrote %s\n", certPath) keyPath := path.Join(targetDirectory, "key.pem") keyOut, err := os.OpenFile(keyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { log.Fatalf("Failed to open %s for writing: %v", keyPath, err) return } + privBytes, err := x509.MarshalPKCS8PrivateKey(priv) if err != nil { log.Fatalf("Unable to marshal private key: %v", err) } + if err := pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil { log.Fatalf("Failed to write data to %s: %v", keyPath, err) } + if err := keyOut.Close(); err != nil { log.Fatalf("Error closing %s: %v", keyPath, err) } + log.Printf("wrote %s\n", keyPath) } diff --git a/internal/configuration/reader.go b/internal/configuration/reader.go index f963fe55af5df..039e902a9f4ca 100644 --- a/internal/configuration/reader.go +++ b/internal/configuration/reader.go @@ -43,6 +43,7 @@ func Read(configPath string) (*schema.Configuration, []error) { } var configuration schema.Configuration + viper.Unmarshal(&configuration) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting. val := schema.NewStructValidator() diff --git a/internal/configuration/reader_test.go b/internal/configuration/reader_test.go index 75a716c48d8d8..e2fe399b3eb09 100644 --- a/internal/configuration/reader_test.go +++ b/internal/configuration/reader_test.go @@ -58,6 +58,7 @@ func TestShouldParseConfigFile(t *testing.T) { func TestShouldParseAltConfigFile(t *testing.T) { require.NoError(t, os.Setenv("AUTHELIA_STORAGE_POSTGRES_PASSWORD", "postgres_secret_from_env")) + config, errors := Read("./test_resources/config_alt.yml") require.Len(t, errors, 0) @@ -98,6 +99,7 @@ func TestShouldNotParseConfigFileWithOldOrUnexpectedKeys(t *testing.T) { func TestShouldValidateConfigurationTemplate(t *testing.T) { resetEnv() + _, errors := Read("../../config.template.yml") assert.Len(t, errors, 0) } @@ -112,6 +114,7 @@ func TestShouldOnlyAllowOneEnvType(t *testing.T) { require.NoError(t, os.Setenv("AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD", "ldap_secret_from_env")) require.NoError(t, os.Setenv("AUTHELIA_NOTIFIER_SMTP_PASSWORD", "smtp_secret_from_env")) require.NoError(t, os.Setenv("AUTHELIA_SESSION_REDIS_PASSWORD", "redis_secret_from_env")) + _, errors := Read("./test_resources/config_alt.yml") require.Len(t, errors, 2) @@ -128,6 +131,7 @@ func TestShouldOnlyAllowEnvOrConfig(t *testing.T) { require.NoError(t, os.Setenv("AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD", "ldap_secret_from_env")) require.NoError(t, os.Setenv("AUTHELIA_NOTIFIER_SMTP_PASSWORD", "smtp_secret_from_env")) require.NoError(t, os.Setenv("AUTHELIA_SESSION_REDIS_PASSWORD", "redis_secret_from_env")) + _, errors := Read("./test_resources/config_with_secret.yml") require.Len(t, errors, 1) diff --git a/internal/configuration/schema/validator.go b/internal/configuration/schema/validator.go index c69bd37ff46f7..bd290b91ec67b 100644 --- a/internal/configuration/schema/validator.go +++ b/internal/configuration/schema/validator.go @@ -23,6 +23,7 @@ type Validator struct { func NewValidator() *Validator { validator := new(Validator) validator.errors = make(map[string][]error) + return validator } @@ -39,6 +40,7 @@ func (v *Validator) validateOne(item QueueItem, q *queue.Queue) error { //nolint } elem := item.value.Elem() + q.Put(QueueItem{ //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting. value: elem, path: item.path, @@ -64,6 +66,7 @@ func (v *Validator) validateOne(item QueueItem, q *queue.Queue) error { //nolint }) } } + return nil } @@ -77,12 +80,15 @@ func (v *Validator) Validate(s interface{}) error { if err != nil { return err } + item, ok := val[0].(QueueItem) if !ok { return fmt.Errorf("Cannot convert item into QueueItem") } + v.validateOne(item, q) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting. } + return nil } @@ -90,6 +96,7 @@ func (v *Validator) Validate(s interface{}) error { func (v *Validator) PrintErrors() { for path, errs := range v.errors { fmt.Printf("Errors at %s:\n", path) + for _, err := range errs { fmt.Printf("--> %s\n", err) } @@ -110,6 +117,7 @@ type StructValidator struct { func NewStructValidator() *StructValidator { val := new(StructValidator) val.errors = make([]error, 0) + return val } diff --git a/internal/configuration/validator/configuration.go b/internal/configuration/validator/configuration.go index 8e0bc09df7ad3..f410ff2e23af7 100644 --- a/internal/configuration/validator/configuration.go +++ b/internal/configuration/validator/configuration.go @@ -45,6 +45,7 @@ func ValidateConfiguration(configuration *schema.Configuration, validator *schem if configuration.TOTP == nil { configuration.TOTP = &schema.DefaultTOTPConfiguration } + ValidateTOTP(configuration.TOTP, validator) ValidateAuthenticationBackend(&configuration.AuthenticationBackend, validator) @@ -58,6 +59,7 @@ func ValidateConfiguration(configuration *schema.Configuration, validator *schem if configuration.Regulation == nil { configuration.Regulation = &schema.DefaultRegulationConfiguration } + ValidateRegulation(configuration.Regulation, validator) ValidateServer(&configuration.Server, validator) diff --git a/internal/configuration/validator/configuration_test.go b/internal/configuration/validator/configuration_test.go index fe3b2cd73a77d..3d3dc9af4fcf1 100644 --- a/internal/configuration/validator/configuration_test.go +++ b/internal/configuration/validator/configuration_test.go @@ -30,6 +30,7 @@ func newDefaultConfig() schema.Configuration { Filename: "/tmp/file", }, } + return config } diff --git a/internal/configuration/validator/keys.go b/internal/configuration/validator/keys.go index 78bc39a291432..bc20effcfd823 100644 --- a/internal/configuration/validator/keys.go +++ b/internal/configuration/validator/keys.go @@ -11,6 +11,7 @@ import ( // ValidateKeys determines if a provided key is valid. func ValidateKeys(validator *schema.StructValidator, keys []string) { var errStrings []string + for _, key := range keys { if utils.IsStringInSlice(key, validKeys) { continue @@ -24,6 +25,7 @@ func ValidateKeys(validator *schema.StructValidator, keys []string) { validator.Push(fmt.Errorf("config key not expected: %s", key)) } } + for _, err := range errStrings { validator.Push(errors.New(err)) } diff --git a/internal/configuration/validator/keys_test.go b/internal/configuration/validator/keys_test.go index d5b156ce654d4..4ec5a2c3bd5d5 100644 --- a/internal/configuration/validator/keys_test.go +++ b/internal/configuration/validator/keys_test.go @@ -34,11 +34,13 @@ func TestShouldNotValidateBadKeys(t *testing.T) { func TestAllSpecificErrorKeys(t *testing.T) { var configKeys []string //nolint:prealloc // This is because the test is dynamic based on the keys that exist in the map + var uniqueValues []string // Setup configKeys and uniqueValues expected. for key, value := range specificErrorKeys { configKeys = append(configKeys, key) + if !utils.IsStringInSlice(value, uniqueValues) { uniqueValues = append(uniqueValues, value) } diff --git a/internal/configuration/validator/notifier.go b/internal/configuration/validator/notifier.go index 8227506aa46dc..ccb455f1ac26c 100644 --- a/internal/configuration/validator/notifier.go +++ b/internal/configuration/validator/notifier.go @@ -22,6 +22,7 @@ func ValidateNotifier(configuration *schema.NotifierConfiguration, validator *sc if configuration.FileSystem.Filename == "" { validator.Push(fmt.Errorf("Filename of filesystem notifier must not be empty")) } + return } @@ -29,6 +30,7 @@ func ValidateNotifier(configuration *schema.NotifierConfiguration, validator *sc if configuration.SMTP.StartupCheckAddress == "" { configuration.SMTP.StartupCheckAddress = "test@authelia.com" } + if configuration.SMTP.Host == "" { validator.Push(fmt.Errorf("Host of SMTP notifier must be provided")) } @@ -44,6 +46,7 @@ func ValidateNotifier(configuration *schema.NotifierConfiguration, validator *sc if configuration.SMTP.Subject == "" { configuration.SMTP.Subject = schema.DefaultSMTPNotifierConfiguration.Subject } + return } } diff --git a/internal/configuration/validator/regulation.go b/internal/configuration/validator/regulation.go index 1dbf2989a7d2f..037d386a950b3 100644 --- a/internal/configuration/validator/regulation.go +++ b/internal/configuration/validator/regulation.go @@ -12,17 +12,21 @@ func ValidateRegulation(configuration *schema.RegulationConfiguration, validator if configuration.FindTime == "" { configuration.FindTime = schema.DefaultRegulationConfiguration.FindTime // 2 min } + if configuration.BanTime == "" { configuration.BanTime = schema.DefaultRegulationConfiguration.BanTime // 5 min } + findTime, err := utils.ParseDurationString(configuration.FindTime) if err != nil { validator.Push(fmt.Errorf("Error occurred parsing regulation find_time string: %s", err)) } + banTime, err := utils.ParseDurationString(configuration.BanTime) if err != nil { validator.Push(fmt.Errorf("Error occurred parsing regulation ban_time string: %s", err)) } + if findTime > banTime { validator.Push(fmt.Errorf("find_time cannot be greater than ban_time")) } diff --git a/internal/configuration/validator/secrets.go b/internal/configuration/validator/secrets.go index 66af45363e2ee..9481a548c6f5f 100644 --- a/internal/configuration/validator/secrets.go +++ b/internal/configuration/validator/secrets.go @@ -50,6 +50,7 @@ func getSecretValue(name string, validator *schema.StructValidator, viper *viper if envValue != "" && fileEnvValue != "" { validator.Push(fmt.Errorf("secret is defined in multiple areas: %s", name)) } + if (envValue != "" || fileEnvValue != "") && configValue != "" { validator.Push(fmt.Errorf("error loading secret (%s): it's already defined in the config file", name)) } @@ -63,9 +64,11 @@ func getSecretValue(name string, validator *schema.StructValidator, viper *viper return strings.Replace(string(content), "\n", "", -1) } } + if envValue != "" { logging.Logger().Warnf("The following secret is defined as an environment variable, this is insecure and being removed in 4.18.0+, it's recommended to use the file secrets instead (https://docs.authelia.com/configuration/secrets.html): %s", name) return envValue } + return configValue } diff --git a/internal/configuration/validator/session_test.go b/internal/configuration/validator/session_test.go index 830c6b8499ba4..2478edf34671e 100644 --- a/internal/configuration/validator/session_test.go +++ b/internal/configuration/validator/session_test.go @@ -12,6 +12,7 @@ func newDefaultSessionConfig() schema.SessionConfiguration { config := schema.SessionConfiguration{} config.Secret = testJWTSecret config.Domain = "example.com" + return config } diff --git a/internal/configuration/validator/totp.go b/internal/configuration/validator/totp.go index 68c94a44e9479..cbf91b89ed9cc 100644 --- a/internal/configuration/validator/totp.go +++ b/internal/configuration/validator/totp.go @@ -11,6 +11,7 @@ func ValidateTOTP(configuration *schema.TOTPConfiguration, validator *schema.Str if configuration.Issuer == "" { configuration.Issuer = schema.DefaultTOTPConfiguration.Issuer } + if configuration.Period == 0 { configuration.Period = schema.DefaultTOTPConfiguration.Period } else if configuration.Period < 0 { diff --git a/internal/configuration/validator/totp_test.go b/internal/configuration/validator/totp_test.go index 3357fa639e2e5..c95759f4eb305 100644 --- a/internal/configuration/validator/totp_test.go +++ b/internal/configuration/validator/totp_test.go @@ -23,6 +23,7 @@ func TestShouldSetDefaultTOTPValues(t *testing.T) { func TestShouldRaiseErrorWhenInvalidTOTPMinimumValues(t *testing.T) { var badSkew = -1 + validator := schema.NewStructValidator() config := schema.TOTPConfiguration{ Period: -5, diff --git a/internal/duo/duo.go b/internal/duo/duo.go index 67979b826d4d2..7bf80806c1e1c 100644 --- a/internal/duo/duo.go +++ b/internal/duo/duo.go @@ -13,23 +13,25 @@ import ( func NewDuoAPI(duoAPI *duoapi.DuoApi) *APIImpl { api := new(APIImpl) api.DuoApi = duoAPI + return api } // Call call to the DuoAPI. func (d *APIImpl) Call(values url.Values, ctx *middlewares.AutheliaCtx) (*Response, error) { - _, responseBytes, err := d.DuoApi.SignedCall("POST", "/auth/v2/auth", values) + var response Response + _, responseBytes, err := d.DuoApi.SignedCall("POST", "/auth/v2/auth", values) if err != nil { return nil, err } ctx.Logger.Tracef("Duo Push Auth Response Raw Data for %s from IP %s: %s", ctx.GetSession().Username, ctx.RemoteIP().String(), string(responseBytes)) - var response Response err = json.Unmarshal(responseBytes, &response) if err != nil { return nil, err } + return &response, nil } diff --git a/internal/handlers/handler_extended_configuration_test.go b/internal/handlers/handler_extended_configuration_test.go index e109a6f31c394..b974b075814cc 100644 --- a/internal/handlers/handler_extended_configuration_test.go +++ b/internal/handlers/handler_extended_configuration_test.go @@ -38,6 +38,7 @@ func (s *SecondFactorAvailableMethodsFixture) TestShouldServeDefaultMethods() { SecondFactorEnabled: false, TOTPPeriod: schema.DefaultTOTPConfiguration.Period, } + ExtendedConfigurationGet(s.mock.Ctx) s.mock.Assert200OK(s.T(), expectedBody) } @@ -54,6 +55,7 @@ func (s *SecondFactorAvailableMethodsFixture) TestShouldServeDefaultMethodsAndMo SecondFactorEnabled: false, TOTPPeriod: schema.DefaultTOTPConfiguration.Period, } + ExtendedConfigurationGet(s.mock.Ctx) s.mock.Assert200OK(s.T(), expectedBody) } diff --git a/internal/handlers/handler_firstfactor.go b/internal/handlers/handler_firstfactor.go index 54561d4f713ab..1ae05b558ba0b 100644 --- a/internal/handlers/handler_firstfactor.go +++ b/internal/handlers/handler_firstfactor.go @@ -28,7 +28,9 @@ func FirstFactorPost(ctx *middlewares.AutheliaCtx) { ctx.Error(fmt.Errorf("User %s is banned until %s", bodyJSON.Username, bannedUntil), userBannedMessage) return } + ctx.Error(fmt.Errorf("Unable to regulate authentication: %s", err), authenticationFailedMessage) + return } @@ -39,6 +41,7 @@ func FirstFactorPost(ctx *middlewares.AutheliaCtx) { ctx.Providers.Regulator.Mark(bodyJSON.Username, false) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting. ctx.Error(fmt.Errorf("Error while checking password for user %s: %s", bodyJSON.Username, err.Error()), authenticationFailedMessage) + return } @@ -47,6 +50,7 @@ func FirstFactorPost(ctx *middlewares.AutheliaCtx) { ctx.Providers.Regulator.Mark(bodyJSON.Username, false) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting. ctx.ReplyError(fmt.Errorf("Credentials are wrong for user %s", bodyJSON.Username), authenticationFailedMessage) + return } @@ -106,9 +110,11 @@ func FirstFactorPost(ctx *middlewares.AutheliaCtx) { userSession.LastActivity = time.Now().Unix() userSession.KeepMeLoggedIn = keepMeLoggedIn refresh, refreshInterval := getProfileRefreshSettings(ctx.Configuration.AuthenticationBackend) + if refresh { userSession.RefreshTTL = ctx.Clock.Now().Add(refreshInterval) } + err = ctx.SaveSession(userSession) if err != nil { diff --git a/internal/handlers/handler_register_u2f_step1.go b/internal/handlers/handler_register_u2f_step1.go index 9476fa3fb874b..736cba804ddee 100644 --- a/internal/handlers/handler_register_u2f_step1.go +++ b/internal/handlers/handler_register_u2f_step1.go @@ -36,6 +36,7 @@ func secondFactorU2FIdentityFinish(ctx *middlewares.AutheliaCtx, username string appID := fmt.Sprintf("%s://%s", ctx.XForwardedProto(), ctx.XForwardedHost()) ctx.Logger.Tracef("U2F appID is %s", appID) + var trustedFacets = []string{appID} challenge, err := u2f.NewChallenge(appID, trustedFacets) diff --git a/internal/handlers/handler_register_u2f_step1_test.go b/internal/handlers/handler_register_u2f_step1_test.go index fd7d470f6b968..3887f2e3e7337 100644 --- a/internal/handlers/handler_register_u2f_step1_test.go +++ b/internal/handlers/handler_register_u2f_step1_test.go @@ -43,6 +43,7 @@ func createToken(secret string, username string, action string, expiresAt time.T } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) ss, _ := token.SignedString([]byte(secret)) + return ss } diff --git a/internal/handlers/handler_sign_duo.go b/internal/handlers/handler_sign_duo.go index 98624e7a0a56b..6962ce9a8f96b 100644 --- a/internal/handlers/handler_sign_duo.go +++ b/internal/handlers/handler_sign_duo.go @@ -31,6 +31,7 @@ func SecondFactorDuoPost(duoAPI duo.API) middlewares.RequestHandler { values.Set("ipaddr", remoteIP) values.Set("factor", "push") values.Set("device", "auto") + if requestBody.TargetURL != "" { values.Set("pushinfo", fmt.Sprintf("target%%20url=%s", requestBody.TargetURL)) } diff --git a/internal/handlers/handler_sign_totp.go b/internal/handlers/handler_sign_totp.go index fe91a657c590d..c06181af00e69 100644 --- a/internal/handlers/handler_sign_totp.go +++ b/internal/handlers/handler_sign_totp.go @@ -19,6 +19,7 @@ func SecondFactorTOTPPost(totpVerifier TOTPVerifier) middlewares.RequestHandler } userSession := ctx.GetSession() + secret, err := ctx.Providers.StorageProvider.LoadTOTPSecret(userSession.Username) if err != nil { ctx.Error(fmt.Errorf("Unable to load TOTP secret: %s", err), mfaValidationFailedMessage) diff --git a/internal/handlers/handler_sign_u2f_step1.go b/internal/handlers/handler_sign_u2f_step1.go index abeb74b47f3ca..6402989615ee2 100644 --- a/internal/handlers/handler_sign_u2f_step1.go +++ b/internal/handlers/handler_sign_u2f_step1.go @@ -24,6 +24,7 @@ func SecondFactorU2FSignGet(ctx *middlewares.AutheliaCtx) { } appID := fmt.Sprintf("%s://%s", ctx.XForwardedProto(), ctx.XForwardedHost()) + var trustedFacets = []string{appID} challenge, err := u2f.NewChallenge(appID, trustedFacets) @@ -40,7 +41,9 @@ func SecondFactorU2FSignGet(ctx *middlewares.AutheliaCtx) { ctx.Error(fmt.Errorf("No device handle found for user %s", userSession.Username), mfaValidationFailedMessage) return } + ctx.Error(fmt.Errorf("Unable to retrieve U2F device handle: %s", err), mfaValidationFailedMessage) + return } diff --git a/internal/handlers/handler_user_info.go b/internal/handlers/handler_user_info.go index 7f6dc4d3b0963..7066c64028d90 100644 --- a/internal/handlers/handler_user_info.go +++ b/internal/handlers/handler_user_info.go @@ -15,17 +15,22 @@ import ( func loadInfo(username string, storageProvider storage.Provider, preferences *UserPreferences, logger *logrus.Entry) []error { var wg sync.WaitGroup + wg.Add(3) errors := make([]error, 0) + go func() { defer wg.Done() + method, err := storageProvider.LoadPreferred2FAMethod(username) if err != nil { errors = append(errors, err) logger.Error(err) + return } + if method == "" { preferences.Method = authentication.PossibleMethods[0] } else { @@ -35,33 +40,42 @@ func loadInfo(username string, storageProvider storage.Provider, preferences *Us go func() { defer wg.Done() + _, _, err := storageProvider.LoadU2FDeviceHandle(username) if err != nil { if err == storage.ErrNoU2FDeviceHandle { return } + errors = append(errors, err) logger.Error(err) + return } + preferences.HasU2F = true }() go func() { defer wg.Done() + _, err := storageProvider.LoadTOTPSecret(username) if err != nil { if err == storage.ErrNoTOTPSecret { return } + errors = append(errors, err) logger.Error(err) + return } + preferences.HasTOTP = true }() wg.Wait() + return errors } @@ -76,6 +90,7 @@ func UserInfoGet(ctx *middlewares.AutheliaCtx) { ctx.Error(fmt.Errorf("Unable to load user information"), operationFailedMessage) return } + ctx.SetJSONBody(preferences) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting. } @@ -87,6 +102,7 @@ type MethodBody struct { // MethodPreferencePost update the user preferences regarding 2FA method. func MethodPreferencePost(ctx *middlewares.AutheliaCtx) { bodyJSON := MethodBody{} + err := ctx.ParseBody(&bodyJSON) if err != nil { ctx.Error(err, operationFailedMessage) diff --git a/internal/handlers/handler_verify.go b/internal/handlers/handler_verify.go index a3e64f1b18984..4ee4afb7677ce 100644 --- a/internal/handlers/handler_verify.go +++ b/internal/handlers/handler_verify.go @@ -38,7 +38,9 @@ func getOriginalURL(ctx *middlewares.AutheliaCtx) (*url.URL, error) { if err != nil { return nil, fmt.Errorf("Unable to parse URL extracted from X-Original-URL header: %v", err) } + ctx.Logger.Trace("Using X-Original-URL header content as targeted site URL") + return url, nil } @@ -55,6 +57,7 @@ func getOriginalURL(ctx *middlewares.AutheliaCtx) (*url.URL, error) { } var requestURI string + scheme := append(forwardedProto, protoHostSeparator...) requestURI = string(append(scheme, append(forwardedHost, forwardedURI...)...)) @@ -63,8 +66,10 @@ func getOriginalURL(ctx *middlewares.AutheliaCtx) (*url.URL, error) { if err != nil { return nil, fmt.Errorf("Unable to parse URL %s: %v", requestURI, err) } + ctx.Logger.Tracef("Using X-Fowarded-Proto, X-Forwarded-Host and X-Forwarded-URI headers " + "to construct targeted site URL") + return url, nil } @@ -74,15 +79,19 @@ func parseBasicAuth(auth string) (username, password string, err error) { if !strings.HasPrefix(auth, authPrefix) { return "", "", fmt.Errorf("%s prefix not found in %s header", strings.Trim(authPrefix, " "), AuthorizationHeader) } + c, err := base64.StdEncoding.DecodeString(auth[len(authPrefix):]) if err != nil { return "", "", err } + cs := string(c) s := strings.IndexByte(cs, ':') + if s < 0 { return "", "", fmt.Errorf("Format of %s header must be user:password", AuthorizationHeader) } + return cs[:s], cs[s+1:], nil } @@ -114,6 +123,7 @@ func isTargetURLAuthorized(authorizer *authorization.Authorizer, targetURL url.U return Authorized } } + return NotAuthorized } @@ -208,8 +218,10 @@ func verifySessionCookie(ctx *middlewares.AutheliaCtx, targetURL *url.URL, userS if err != nil { ctx.Logger.Error(fmt.Errorf("Unable to destroy user session after provider refresh didn't find the user: %s", err)) } + return userSession.Username, userSession.Groups, authentication.NotAuthenticated, err } + ctx.Logger.Warnf("Error occurred while attempting to update user details from LDAP: %s", err) } @@ -226,6 +238,7 @@ func handleUnauthorized(ctx *middlewares.AutheliaCtx, targetURL fmt.Stringer, us if strings.Contains(redirectionURL, "/%23/") { ctx.Logger.Warn("Characters /%23/ have been detected in redirection URL. This is not needed anymore, please strip it") } + ctx.Logger.Infof("Access to %s is not authorized to user %s, redirecting to %s", targetURL.String(), username, redirectionURL) ctx.Redirect(redirectionURL, 302) ctx.SetBodyString(fmt.Sprintf("Found. Redirecting to %s", redirectionURL)) @@ -248,6 +261,7 @@ func updateActivityTimestamp(ctx *middlewares.AutheliaCtx, isBasicAuth bool, use // Mark current activity. userSession.LastActivity = ctx.Clock.Now().Unix() + return ctx.SaveSession(userSession) } @@ -263,9 +277,11 @@ func generateVerifySessionHasUpToDateProfileTraceLogs(ctx *middlewares.AutheliaC if len(groupsAdded) != 0 { groupsDelta = append(groupsDelta, fmt.Sprintf("Added: %s.", strings.Join(groupsAdded, ", "))) } + if len(groupsRemoved) != 0 { groupsDelta = append(groupsDelta, fmt.Sprintf("Removed: %s.", strings.Join(groupsRemoved, ", "))) } + if len(groupsDelta) != 0 { ctx.Logger.Tracef("Updated groups detected for %s. %s", userSession.Username, strings.Join(groupsDelta, " ")) } else { @@ -277,9 +293,11 @@ func generateVerifySessionHasUpToDateProfileTraceLogs(ctx *middlewares.AutheliaC if len(emailsAdded) != 0 { emailsDelta = append(emailsDelta, fmt.Sprintf("Added: %s.", strings.Join(emailsAdded, ", "))) } + if len(emailsRemoved) != 0 { emailsDelta = append(emailsDelta, fmt.Sprintf("Removed: %s.", strings.Join(emailsRemoved, ", "))) } + if len(emailsDelta) != 0 { ctx.Logger.Tracef("Updated emails detected for %s. %s", userSession.Username, strings.Join(emailsDelta, " ")) } else { @@ -291,8 +309,8 @@ func verifySessionHasUpToDateProfile(ctx *middlewares.AutheliaCtx, targetURL *ur refreshProfile bool, refreshProfileInterval time.Duration) error { // TODO: Add a check for LDAP password changes based on a time format attribute. // See https://docs.authelia.com/security/threat-model.html#potential-future-guarantees - ctx.Logger.Tracef("Checking if we need check the authentication backend for an updated profile for %s.", userSession.Username) + if refreshProfile && userSession.Username != "" && targetURL != nil && ctx.Providers.Authorizer.IsURLMatchingRuleWithGroupSubjects(*targetURL) && (refreshProfileInterval == schema.RefreshIntervalAlways || userSession.RefreshTTL.Before(ctx.Clock.Now())) { @@ -305,6 +323,7 @@ func verifySessionHasUpToDateProfile(ctx *middlewares.AutheliaCtx, targetURL *ur groupsDiff := utils.IsStringSlicesDifferent(userSession.Groups, details.Groups) emailsDiff := utils.IsStringSlicesDifferent(userSession.Emails, details.Emails) + if !groupsDiff && !emailsDiff { ctx.Logger.Tracef("Updated profile not detected for %s.", userSession.Username) } else { @@ -329,6 +348,7 @@ func verifySessionHasUpToDateProfile(ctx *middlewares.AutheliaCtx, targetURL *ur return ctx.SaveSession(*userSession) } } + return nil } @@ -336,6 +356,7 @@ func getProfileRefreshSettings(cfg schema.AuthenticationBackendConfiguration) (r if cfg.Ldap != nil { if cfg.RefreshInterval != schema.ProfileRefreshDisabled { refresh = true + if cfg.RefreshInterval != schema.ProfileRefreshAlways { // Skip Error Check since validator checks it refreshInterval, _ = utils.ParseDurationString(cfg.RefreshInterval) @@ -344,6 +365,7 @@ func getProfileRefreshSettings(cfg schema.AuthenticationBackendConfiguration) (r } } } + return refresh, refreshInterval } @@ -364,6 +386,7 @@ func VerifyGet(cfg schema.AuthenticationBackendConfiguration) middlewares.Reques ctx.Logger.Error(fmt.Errorf("Scheme of target URL %s must be secure since cookies are "+ "only transported over a secure connection for security reasons", targetURL.String())) ctx.ReplyUnauthorized() + return } @@ -371,11 +394,14 @@ func VerifyGet(cfg schema.AuthenticationBackendConfiguration) middlewares.Reques ctx.Logger.Error(fmt.Errorf("The target URL %s is not under the protected domain %s", targetURL.String(), ctx.Configuration.Session.Domain)) ctx.ReplyUnauthorized() + return } var username string + var groups []string + var authLevel authentication.Level proxyAuthorization := ctx.Request.Header.Peek(AuthorizationHeader) @@ -391,11 +417,14 @@ func VerifyGet(cfg schema.AuthenticationBackendConfiguration) middlewares.Reques if err != nil { ctx.Logger.Error(fmt.Sprintf("Error caught when verifying user authorization: %s", err)) + if err := updateActivityTimestamp(ctx, isBasicAuth, username); err != nil { ctx.Error(fmt.Errorf("Unable to update last activity: %s", err), operationFailedMessage) return } + handleUnauthorized(ctx, targetURL, username) + return } diff --git a/internal/handlers/handler_verify_test.go b/internal/handlers/handler_verify_test.go index 512a60d3f6793..a7a81b8b836fe 100644 --- a/internal/handlers/handler_verify_test.go +++ b/internal/handlers/handler_verify_test.go @@ -153,6 +153,7 @@ func TestShouldCheckAuthorizationMatching(t *testing.T) { AuthLevel authentication.Level ExpectedMatching authorizationMatching } + rules := []Rule{ {"bypass", authentication.NotAuthenticated, Authorized}, {"bypass", authentication.OneFactor, Authorized}, @@ -679,6 +680,7 @@ func TestIsDomainProtected(t *testing.T) { GetURL := func(u string) *url.URL { x, err := url.ParseRequestURI(u) require.NoError(t, err) + return x } @@ -701,6 +703,7 @@ func TestSchemeIsHTTPS(t *testing.T) { GetURL := func(u string) *url.URL { x, err := url.ParseRequestURI(u) require.NoError(t, err) + return x } @@ -718,6 +721,7 @@ func TestSchemeIsWSS(t *testing.T) { GetURL := func(u string) *url.URL { x, err := url.ParseRequestURI(u) require.NoError(t, err) + return x } @@ -854,6 +858,7 @@ func TestShouldGetRemovedUserGroupsFromBackend(t *testing.T) { } verifyGet := VerifyGet(verifyGetCfg) + mock.UserProviderMock.EXPECT().GetDetails("john").Return(user, nil).Times(2) clock := mocks.TestingClock{} @@ -968,6 +973,7 @@ func TestShouldGetAddedUserGroupsFromBackend(t *testing.T) { // Reset otherwise we get the last 403 when we check the Response. Is there a better way to do this? mock.Close() + mock = mocks.NewMockAutheliaCtx(t) defer mock.Close() err = mock.Ctx.SaveSession(userSession) diff --git a/internal/handlers/response.go b/internal/handlers/response.go index 0119f6703547e..25d4961cc3c07 100644 --- a/internal/handlers/response.go +++ b/internal/handlers/response.go @@ -17,6 +17,7 @@ func Handle1FAResponse(ctx *middlewares.AutheliaCtx, targetURI string, username } else { ctx.ReplyOK() } + return } @@ -37,6 +38,7 @@ func Handle1FAResponse(ctx *middlewares.AutheliaCtx, targetURI string, username if requiredLevel == authorization.TwoFactor { ctx.Logger.Warnf("%s requires 2FA, cannot be redirected yet", targetURI) ctx.ReplyOK() + return } @@ -48,10 +50,12 @@ func Handle1FAResponse(ctx *middlewares.AutheliaCtx, targetURI string, username } else { ctx.ReplyOK() } + return } ctx.Logger.Debugf("Redirection URL %s is safe", targetURI) + response := redirectResponse{Redirect: targetURI} ctx.SetJSONBody(response) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting. } @@ -64,6 +68,7 @@ func Handle2FAResponse(ctx *middlewares.AutheliaCtx, targetURI string) { } else { ctx.ReplyOK() } + return } diff --git a/internal/handlers/totp.go b/internal/handlers/totp.go index a8bfacfd90136..fe4e7c2a38790 100644 --- a/internal/handlers/totp.go +++ b/internal/handlers/totp.go @@ -26,5 +26,6 @@ func (tv *TOTPVerifierImpl) Verify(token, secret string) (bool, error) { Digits: otp.DigitsSix, Algorithm: otp.AlgorithmSHA1, } + return totp.ValidateCustom(token, secret, time.Now().UTC(), opts) } diff --git a/internal/handlers/u2f.go b/internal/handlers/u2f.go index 034da42759f92..1638e8423ea8d 100644 --- a/internal/handlers/u2f.go +++ b/internal/handlers/u2f.go @@ -27,5 +27,6 @@ func (uv *U2FVerifierImpl) Verify(keyHandle []byte, publicKey []byte, // TODO(c.michaud): store the counter to help detecting cloned U2F keys. _, err := registration.Authenticate( signResponse, challenge, 0) + return err } diff --git a/internal/logging/logger.go b/internal/logging/logger.go index 0438d53aa88fe..8746e156d0343 100644 --- a/internal/logging/logger.go +++ b/internal/logging/logger.go @@ -28,7 +28,9 @@ func InitializeLogger(filename string) error { if err != nil { return err } + logrus.SetOutput(f) } + return nil } diff --git a/internal/logging/logger_test.go b/internal/logging/logger_test.go index 6a92ca874e6b2..24e3e052d1779 100644 --- a/internal/logging/logger_test.go +++ b/internal/logging/logger_test.go @@ -16,6 +16,7 @@ func TestShouldWriteLogsToFile(t *testing.T) { if err != nil { log.Fatal(err) } + defer os.RemoveAll(dir) path := fmt.Sprintf("%s/authelia.log", dir) diff --git a/internal/middlewares/authelia_context.go b/internal/middlewares/authelia_context.go index 7afa86961afc0..c4f8a22740a5a 100644 --- a/internal/middlewares/authelia_context.go +++ b/internal/middlewares/authelia_context.go @@ -32,6 +32,7 @@ func NewAutheliaCtx(ctx *fasthttp.RequestCtx, configuration schema.Configuration autheliaCtx.Configuration = configuration autheliaCtx.Logger = NewRequestLogger(autheliaCtx) autheliaCtx.Clock = utils.RealClock{} + return autheliaCtx, nil } @@ -44,6 +45,7 @@ func AutheliaMiddleware(configuration schema.Configuration, providers Providers) autheliaCtx.Error(err, operationFailedMessage) return } + next(autheliaCtx) } } @@ -78,7 +80,6 @@ func (c *AutheliaCtx) ReplyError(err error, message string) { // ReplyUnauthorized response sent when user is unauthorized. func (c *AutheliaCtx) ReplyUnauthorized() { c.RequestCtx.Error(fasthttp.StatusMessage(fasthttp.StatusUnauthorized), fasthttp.StatusUnauthorized) - // c.Response.Header.Set("WWW-Authenticate", "Basic realm=Restricted") } // ReplyForbidden response sent when access is forbidden to user. @@ -113,6 +114,7 @@ func (c *AutheliaCtx) GetSession() session.UserSession { c.Logger.Error("Unable to retrieve user session") return session.NewDefaultUserSession() } + return userSession } @@ -144,6 +146,7 @@ func (c *AutheliaCtx) ParseBody(value interface{}) error { if !valid { return fmt.Errorf("Body is not valid") } + return nil } @@ -156,6 +159,7 @@ func (c *AutheliaCtx) SetJSONBody(value interface{}) error { c.SetContentType("application/json") c.SetBody(b) + return nil } @@ -169,5 +173,6 @@ func (c *AutheliaCtx) RemoteIP() net.IP { return net.ParseIP(strings.Trim(ips[0], " ")) } } + return c.RequestCtx.RemoteIP() } diff --git a/internal/middlewares/identity_verification.go b/internal/middlewares/identity_verification.go index c56cd7f86b4a5..cebcd3bf6b4a2 100644 --- a/internal/middlewares/identity_verification.go +++ b/internal/middlewares/identity_verification.go @@ -24,6 +24,7 @@ func IdentityVerificationStart(args IdentityVerificationStartArgs) RequestHandle // In that case we reply ok to avoid user enumeration. ctx.Logger.Error(err) ctx.ReplyOK() + return } @@ -78,6 +79,7 @@ func IdentityVerificationStart(args IdentityVerificationStartArgs) RequestHandle ctx.Logger.Debugf("Sending an email to user %s (%s) to confirm identity for registering a device.", identity.Username, identity.Email) + err = ctx.Providers.Notifier.Send(identity.Email, args.MailTitle, buf.String()) if err != nil { @@ -93,6 +95,7 @@ func IdentityVerificationStart(args IdentityVerificationStartArgs) RequestHandle func IdentityVerificationFinish(args IdentityVerificationFinishArgs, next func(ctx *AutheliaCtx, username string)) RequestHandler { return func(ctx *AutheliaCtx) { var finishBody IdentityVerificationFinishBody + b := ctx.PostBody() err := json.Unmarshal(b, &finishBody) @@ -139,7 +142,9 @@ func IdentityVerificationFinish(args IdentityVerificationFinishArgs, next func(c return } } + ctx.Error(err, operationFailedMessage) + return } diff --git a/internal/middlewares/identity_verification_test.go b/internal/middlewares/identity_verification_test.go index 4634eed27680e..1c00699342bcb 100644 --- a/internal/middlewares/identity_verification_test.go +++ b/internal/middlewares/identity_verification_test.go @@ -174,6 +174,7 @@ func createToken(secret string, username string, action string, expiresAt time.T } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) ss, _ := token.SignedString([]byte(secret)) + return ss } diff --git a/internal/middlewares/log_request_test.go b/internal/middlewares/log_request_test.go index 4b521efb6f440..ee32b986b6154 100644 --- a/internal/middlewares/log_request_test.go +++ b/internal/middlewares/log_request_test.go @@ -9,6 +9,7 @@ import ( func TestShouldCallNextFunction(t *testing.T) { var val = false + f := func(ctx *fasthttp.RequestCtx) { val = true } context := &fasthttp.RequestCtx{} diff --git a/internal/middlewares/require_first_factor.go b/internal/middlewares/require_first_factor.go index 05d412d6bf723..9f9aa125937bd 100644 --- a/internal/middlewares/require_first_factor.go +++ b/internal/middlewares/require_first_factor.go @@ -11,6 +11,7 @@ func RequireFirstFactor(next RequestHandler) RequestHandler { ctx.ReplyForbidden() return } + next(ctx) } } diff --git a/internal/mocks/mock_authelia_ctx.go b/internal/mocks/mock_authelia_ctx.go index 8115a29be449a..9a54d681a2b0b 100644 --- a/internal/mocks/mock_authelia_ctx.go +++ b/internal/mocks/mock_authelia_ctx.go @@ -123,6 +123,7 @@ func NewMockAutheliaCtx(t *testing.T) *MockAutheliaCtx { mockAuthelia.Hook = hook mockAuthelia.Ctx.Logger = logrus.NewEntry(logger) + return mockAuthelia } @@ -141,6 +142,7 @@ func (m *MockAutheliaCtx) Assert200KO(t *testing.T, message string) { // Assert200OK assert a successful response from the service. func (m *MockAutheliaCtx) Assert200OK(t *testing.T, data interface{}) { assert.Equal(t, 200, m.Ctx.Response.StatusCode()) + response := middlewares.OKResponse{ Status: "OK", Data: data, diff --git a/internal/notification/file_notifier.go b/internal/notification/file_notifier.go index 0cf307b3ef7e9..f3a00c9cfa7fd 100644 --- a/internal/notification/file_notifier.go +++ b/internal/notification/file_notifier.go @@ -42,9 +42,11 @@ func (n *FileNotifier) StartupCheck() (bool, error) { return false, err } } + if err := ioutil.WriteFile(n.path, []byte(""), fileNotifierMode); err != nil { return false, err } + return true, nil } @@ -57,5 +59,6 @@ func (n *FileNotifier) Send(recipient, subject, body string) error { if err != nil { return err } + return nil } diff --git a/internal/notification/smtp_login_auth.go b/internal/notification/smtp_login_auth.go index 90f720318168c..4251178d3f1b6 100644 --- a/internal/notification/smtp_login_auth.go +++ b/internal/notification/smtp_login_auth.go @@ -21,9 +21,11 @@ func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) { if !server.TLS && !(server.Name == "localhost" || server.Name == "127.0.0.1" || server.Name == "::1") { return "", nil, errors.New("connection over plain-text") } + if server.Name != a.host { return "", nil, errors.New("unexpected hostname from server") } + return "LOGIN", []byte{}, nil } @@ -31,6 +33,7 @@ func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) { if !more { return nil, nil } + switch { case bytes.Equal(fromServer, []byte("Username:")): return []byte(a.username), nil diff --git a/internal/notification/smtp_notifier.go b/internal/notification/smtp_notifier.go index f694d8f4cb959..e6f22e582aca3 100644 --- a/internal/notification/smtp_notifier.go +++ b/internal/notification/smtp_notifier.go @@ -48,6 +48,7 @@ func NewSMTPNotifier(configuration schema.SMTPNotifierConfiguration) *SMTPNotifi startupCheckAddress: configuration.StartupCheckAddress, } notifier.initializeTLSConfig() + return notifier } @@ -64,6 +65,7 @@ func (n *SMTPNotifier) initializeTLSConfig() { if n.trustedCert != "" { log.Debugf("Notifier SMTP client attempting to load certificate from %s", n.trustedCert) + if exists, err := utils.FileExists(n.trustedCert); exists { pem, err := ioutil.ReadFile(n.trustedCert) if err != nil { @@ -83,6 +85,7 @@ func (n *SMTPNotifier) initializeTLSConfig() { log.Warnf("Notifier SMTP failed to load cert from file (file does not exist) with error: %s", err) } } + n.tlsConfig = &tls.Config{ InsecureSkipVerify: n.disableVerifyCert, //nolint:gosec // This is an intended config, we never default true, provide alternate options, and we constantly warn the user. ServerName: n.host, @@ -105,12 +108,14 @@ func (n *SMTPNotifier) startTLS() error { if err := n.client.StartTLS(n.tlsConfig); err != nil { return err } + log.Debug("Notifier SMTP STARTTLS completed without error") } else if n.disableRequireTLS { log.Warn("Notifier SMTP server does not support STARTTLS and SMTP configuration is set to disable the TLS requirement (only useful for unauthenticated emails over plain text)") } else { return errors.New("Notifier SMTP server does not support TLS and it is required by default (see documentation if you want to disable this highly recommended requirement)") } + return nil } @@ -126,16 +131,19 @@ func (n *SMTPNotifier) auth() error { // Check the server supports AUTH, and get the mechanisms. ok, m := n.client.Extension("AUTH") if ok { + var auth smtp.Auth + log.Debugf("Notifier SMTP server supports authentication with the following mechanisms: %s", m) mechanisms := strings.Split(m, " ") - var auth smtp.Auth // Adaptively select the AUTH mechanism to use based on what the server advertised. if utils.IsStringInSlice("PLAIN", mechanisms) { auth = smtp.PlainAuth("", n.username, n.password, n.host) + log.Debug("Notifier SMTP client attempting AUTH PLAIN with server") } else if utils.IsStringInSlice("LOGIN", mechanisms) { auth = newLoginAuth(n.username, n.password, n.host) + log.Debug("Notifier SMTP client attempting AUTH LOGIN with server") } @@ -148,23 +156,30 @@ func (n *SMTPNotifier) auth() error { if err := n.client.Auth(auth); err != nil { return err } + log.Debug("Notifier SMTP client authenticated successfully with the server") + return nil } + return errors.New("Notifier SMTP server does not advertise the AUTH extension but config requires AUTH (password specified), either disable AUTH, or use an SMTP host that supports AUTH PLAIN or AUTH LOGIN") } + log.Debug("Notifier SMTP config has no password specified so authentication is being skipped") + return nil } func (n *SMTPNotifier) compose(recipient, subject, body string) error { log.Debugf("Notifier SMTP client attempting to send email body to %s", recipient) + if !n.disableRequireTLS { _, ok := n.client.TLSConnectionState() if !ok { return errors.New("Notifier SMTP client can't send an email over plain text connection") } } + wc, err := n.client.Data() if err != nil { log.Debugf("Notifier SMTP client error while obtaining WriteCloser: %s", err) @@ -188,31 +203,39 @@ func (n *SMTPNotifier) compose(recipient, subject, body string) error { log.Debugf("Notifier SMTP client error while closing the WriteCloser: %s", err) return err } + return nil } // Dial the SMTP server with the SMTPNotifier config. func (n *SMTPNotifier) dial() error { log.Debugf("Notifier SMTP client attempting connection to %s", n.address) + if n.port == 465 { log.Warnf("Notifier SMTP client configured to connect to a SMTPS server. It's highly recommended you use a non SMTPS port and STARTTLS instead of SMTPS, as the protocol is long deprecated.") + conn, err := tls.Dial("tcp", n.address, n.tlsConfig) if err != nil { return err } + client, err := smtp.NewClient(conn, n.host) if err != nil { return err } + n.client = client } else { client, err := smtp.Dial(n.address) if err != nil { return err } + n.client = client } + log.Debug("Notifier SMTP client connected successfully") + return nil } @@ -258,6 +281,7 @@ func (n *SMTPNotifier) StartupCheck() (bool, error) { // Send is used to send an email to a recipient. func (n *SMTPNotifier) Send(recipient, title, body string) error { subject := strings.ReplaceAll(n.subject, "{title}", title) + if err := n.dial(); err != nil { return err } @@ -269,6 +293,7 @@ func (n *SMTPNotifier) Send(recipient, title, body string) error { if err := n.startTLS(); err != nil { return err } + if err := n.auth(); err != nil { return err } @@ -278,6 +303,7 @@ func (n *SMTPNotifier) Send(recipient, title, body string) error { log.Debugf("Notifier SMTP failed while sending MAIL FROM (using sender) with error: %s", err) return err } + if err := n.client.Rcpt(recipient); err != nil { log.Debugf("Notifier SMTP failed while sending RCPT TO (using recipient) with error: %s", err) return err @@ -289,5 +315,6 @@ func (n *SMTPNotifier) Send(recipient, title, body string) error { } log.Debug("Notifier SMTP client successfully sent email") + return nil } diff --git a/internal/regulation/regulator.go b/internal/regulation/regulator.go index 2a7069dfed79f..2056ef33cb753 100644 --- a/internal/regulation/regulator.go +++ b/internal/regulation/regulator.go @@ -14,11 +14,13 @@ import ( func NewRegulator(configuration *schema.RegulationConfiguration, provider storage.Provider, clock utils.Clock) *Regulator { regulator := &Regulator{storageProvider: provider} regulator.clock = clock + if configuration != nil { findTime, err := utils.ParseDurationString(configuration.FindTime) if err != nil { panic(err) } + banTime, err := utils.ParseDurationString(configuration.BanTime) if err != nil { panic(err) @@ -34,6 +36,7 @@ func NewRegulator(configuration *schema.RegulationConfiguration, provider storag regulator.findTime = findTime regulator.banTime = banTime } + return regulator } @@ -55,6 +58,7 @@ func (r *Regulator) Regulate(username string) (time.Time, error) { if !r.enabled { return time.Time{}, nil } + now := r.clock.Now() // TODO(c.michaud): make sure FindTime < BanTime. @@ -65,6 +69,7 @@ func (r *Regulator) Regulate(username string) (time.Time, error) { } latestFailedAttempts := make([]models.AuthenticationAttempt, 0, r.maxRetries) + for _, attempt := range attempts { if attempt.Successful || len(latestFailedAttempts) >= r.maxRetries { // We stop appending failed attempts once we find the first successful attempts or we reach @@ -90,5 +95,6 @@ func (r *Regulator) Regulate(username string) (time.Time, error) { bannedUntil := latestFailedAttempts[0].Time.Add(r.banTime) return bannedUntil, ErrUserIsBanned } + return time.Time{}, nil } diff --git a/internal/server/index.go b/internal/server/index.go index 6495f0ba0c988..0db06fc014aae 100644 --- a/internal/server/index.go +++ b/internal/server/index.go @@ -34,12 +34,15 @@ func ServeIndex(publicDir string) fasthttp.RequestHandler { return func(ctx *fasthttp.RequestCtx) { nonce := utils.RandomString(32, alphaNumericRunes) + ctx.SetContentType("text/html; charset=utf-8") ctx.Response.Header.Add("Content-Security-Policy", fmt.Sprintf("default-src 'self'; style-src 'self' 'nonce-%s'", nonce)) + err := tmpl.Execute(ctx.Response.BodyWriter(), struct{ CSPNonce string }{CSPNonce: nonce}) if err != nil { ctx.Error("An error occurred", 503) logging.Logger().Errorf("Unable to execute template: %v", err) + return } } diff --git a/internal/session/encrypting_serializer.go b/internal/session/encrypting_serializer.go index 68b57b690ef96..1232ddca6f439 100644 --- a/internal/session/encrypting_serializer.go +++ b/internal/session/encrypting_serializer.go @@ -46,6 +46,7 @@ func (e *EncryptingSerializer) Decode(dst *session.Dict, src []byte) error { } dst.Reset() + decryptedSrc, err := utils.Decrypt(src, &e.key) if err != nil { // If an error is thrown while decrypting, it's probably an old unencrypted session @@ -56,9 +57,11 @@ func (e *EncryptingSerializer) Decode(dst *session.Dict, src []byte) error { if uerr != nil { return fmt.Errorf("Unable to decrypt session: %s", err) } + return nil } _, err = dst.UnmarshalMsg(decryptedSrc) + return err } diff --git a/internal/session/provider.go b/internal/session/provider.go index 90af6643ebebe..2129521040cb4 100644 --- a/internal/session/provider.go +++ b/internal/session/provider.go @@ -29,18 +29,21 @@ func NewProvider(configuration schema.SessionConfiguration) *Provider { if err != nil { panic(err) } + provider.RememberMe = duration duration, err = utils.ParseDurationString(configuration.Inactivity) if err != nil { panic(err) } + provider.Inactivity = duration err = provider.sessionHolder.SetProvider(providerConfig.providerName, providerConfig.providerConfig) if err != nil { panic(err) } + return provider } @@ -59,6 +62,7 @@ func (p *Provider) GetSession(ctx *fasthttp.RequestCtx) (UserSession, error) { if !ok { userSession := NewDefaultUserSession() store.Set(userSessionStorerKey, userSession) + return userSession, nil } @@ -88,6 +92,7 @@ func (p *Provider) SaveSession(ctx *fasthttp.RequestCtx, userSession UserSession store.Set(userSessionStorerKey, userSessionJSON) p.sessionHolder.Save(ctx, store) + return nil } @@ -117,6 +122,7 @@ func (p *Provider) UpdateExpiration(ctx *fasthttp.RequestCtx, expiration time.Du } p.sessionHolder.Save(ctx, store) + return nil } diff --git a/internal/session/provider_config.go b/internal/session/provider_config.go index 7551b0567bda6..9ebfa1c65c58a 100644 --- a/internal/session/provider_config.go +++ b/internal/session/provider_config.go @@ -32,6 +32,7 @@ func NewProviderConfig(configuration schema.SessionConfiguration) ProviderConfig } var providerConfig session.ProviderConfig + var providerName string // If redis configuration is provided, then use the redis provider. @@ -54,6 +55,7 @@ func NewProviderConfig(configuration schema.SessionConfiguration) ProviderConfig providerName = "memory" providerConfig = &memory.Config{} } + return ProviderConfig{ config: config, providerName: providerName, diff --git a/internal/storage/mysql_provider.go b/internal/storage/mysql_provider.go index 9646bd23fc9b7..c9f328181e723 100644 --- a/internal/storage/mysql_provider.go +++ b/internal/storage/mysql_provider.go @@ -31,8 +31,8 @@ func NewMySQLProvider(configuration schema.MySQLStorageConfiguration) *MySQLProv if configuration.Port > 0 { address += fmt.Sprintf(":%d", configuration.Port) } - connectionString += fmt.Sprintf("tcp(%s)", address) + connectionString += fmt.Sprintf("tcp(%s)", address) if configuration.Database != "" { connectionString += fmt.Sprintf("/%s", configuration.Database) } @@ -71,5 +71,6 @@ func NewMySQLProvider(configuration schema.MySQLStorageConfiguration) *MySQLProv if err := provider.initialize(db); err != nil { logging.Logger().Fatalf("Unable to initialize SQL database: %v", err) } + return &provider } diff --git a/internal/storage/postgres_provider.go b/internal/storage/postgres_provider.go index ba766ccdd553b..fc4ce2fb6eead 100644 --- a/internal/storage/postgres_provider.go +++ b/internal/storage/postgres_provider.go @@ -80,5 +80,6 @@ func NewPostgreSQLProvider(configuration schema.PostgreSQLStorageConfiguration) if err := provider.initialize(db); err != nil { logging.Logger().Fatalf("Unable to initialize SQL database: %v", err) } + return &provider } diff --git a/internal/storage/sql_provider.go b/internal/storage/sql_provider.go index 993895c8a9462..f71bcff6dd181 100644 --- a/internal/storage/sql_provider.go +++ b/internal/storage/sql_provider.go @@ -75,21 +75,26 @@ func (p *SQLProvider) initialize(db *sql.DB) error { return fmt.Errorf("Unable to create table %s: %v", authenticationLogsTableName, err) } } + return nil } // LoadPreferred2FAMethod load the preferred method for 2FA from sqlite db. func (p *SQLProvider) LoadPreferred2FAMethod(username string) (string, error) { + var method string + rows, err := p.db.Query(p.sqlGetPreferencesByUsername, username) if err != nil { return "", err } defer rows.Close() + if !rows.Next() { return "", nil } - var method string + err = rows.Scan(&method) + return method, err } @@ -102,10 +107,12 @@ func (p *SQLProvider) SavePreferred2FAMethod(username string, method string) err // FindIdentityVerificationToken look for an identity verification token in DB. func (p *SQLProvider) FindIdentityVerificationToken(token string) (bool, error) { var found bool + err := p.db.QueryRow(p.sqlTestIdentityVerificationTokenExistence, token).Scan(&found) if err != nil { return false, err } + return found, nil } @@ -134,8 +141,10 @@ func (p *SQLProvider) LoadTOTPSecret(username string) (string, error) { if err == sql.ErrNoRows { return "", ErrNoTOTPSecret } + return "", err } + return secret, nil } @@ -151,6 +160,7 @@ func (p *SQLProvider) SaveU2FDeviceHandle(username string, keyHandle []byte, pub username, base64.StdEncoding.EncodeToString(keyHandle), base64.StdEncoding.EncodeToString(publicKey)) + return err } @@ -161,6 +171,7 @@ func (p *SQLProvider) LoadU2FDeviceHandle(username string) ([]byte, []byte, erro if err == sql.ErrNoRows { return nil, nil, ErrNoU2FDeviceHandle } + return nil, nil, err } @@ -187,6 +198,8 @@ func (p *SQLProvider) AppendAuthenticationLog(attempt models.AuthenticationAttem // LoadLatestAuthenticationLogs retrieve the latest marks from the authentication log. func (p *SQLProvider) LoadLatestAuthenticationLogs(username string, fromDate time.Time) ([]models.AuthenticationAttempt, error) { + var t int64 + rows, err := p.db.Query(p.sqlGetLatestAuthenticationLogs, fromDate.Unix(), username) if err != nil { @@ -194,18 +207,20 @@ func (p *SQLProvider) LoadLatestAuthenticationLogs(username string, fromDate tim } attempts := make([]models.AuthenticationAttempt, 0, 10) + for rows.Next() { attempt := models.AuthenticationAttempt{ Username: username, } - var t int64 err = rows.Scan(&attempt.Successful, &t) attempt.Time = time.Unix(t, 0) if err != nil { return nil, err } + attempts = append(attempts, attempt) } + return attempts, nil } diff --git a/internal/storage/sqlite_provider.go b/internal/storage/sqlite_provider.go index e568c56cc7717..5f823c6aa52cb 100644 --- a/internal/storage/sqlite_provider.go +++ b/internal/storage/sqlite_provider.go @@ -51,5 +51,6 @@ func NewSQLiteProvider(path string) *SQLiteProvider { if err := provider.initialize(db); err != nil { logging.Logger().Fatalf("Unable to initialize SQLite database %s: %s", path, err) } + return &provider } diff --git a/internal/suites/action_http.go b/internal/suites/action_http.go index 7b0e33a2e7b02..3f10965bf7771 100644 --- a/internal/suites/action_http.go +++ b/internal/suites/action_http.go @@ -19,5 +19,6 @@ func doHTTPGetQuery(t *testing.T, url string) []byte { defer resp.Body.Close() body, _ := ioutil.ReadAll(resp.Body) + return body } diff --git a/internal/suites/action_login.go b/internal/suites/action_login.go index ffca640664997..399363ad4dda8 100644 --- a/internal/suites/action_login.go +++ b/internal/suites/action_login.go @@ -51,6 +51,7 @@ func (wds *WebDriverSession) doLoginAndRegisterTOTP(ctx context.Context, t *test secret := wds.doRegisterTOTP(ctx, t) wds.doVisit(t, LoginBaseURL) wds.verifyIsSecondFactorPage(ctx, t) + return secret } @@ -59,5 +60,6 @@ func (wds *WebDriverSession) doRegisterAndLogin2FA(ctx context.Context, t *testi // Register TOTP secret and logout. secret := wds.doRegisterThenLogout(ctx, t, username, password) wds.doLoginTwoFactor(ctx, t, username, password, keepMeLoggedIn, secret, targetURL) + return secret } diff --git a/internal/suites/action_mail.go b/internal/suites/action_mail.go index e2f8772b62e9d..e127fdbea778b 100644 --- a/internal/suites/action_mail.go +++ b/internal/suites/action_mail.go @@ -28,5 +28,6 @@ func doGetLinkFromLastMail(t *testing.T) string { matches := re.FindStringSubmatch(string(res)) assert.Len(t, matches, 2, "Number of match for link in email is not equal to one") + return matches[1] } diff --git a/internal/suites/action_register.go b/internal/suites/action_register.go index 4278e949214d9..728e721d1f0b4 100644 --- a/internal/suites/action_register.go +++ b/internal/suites/action_register.go @@ -8,5 +8,6 @@ import ( func (wds *WebDriverSession) doRegisterThenLogout(ctx context.Context, t *testing.T, username, password string) string { secret := wds.doLoginAndRegisterTOTP(ctx, t, username, password, false) wds.doLogout(ctx, t) + return secret } diff --git a/internal/suites/action_totp.go b/internal/suites/action_totp.go index 3d88c3527d45b..bfb4d71dec1c3 100644 --- a/internal/suites/action_totp.go +++ b/internal/suites/action_totp.go @@ -18,6 +18,7 @@ func (wds *WebDriverSession) doRegisterTOTP(ctx context.Context, t *testing.T) s assert.NoError(t, err) assert.NotEqual(t, "", secret) assert.NotNil(t, secret) + return secret } diff --git a/internal/suites/action_visit.go b/internal/suites/action_visit.go index e9be4f57448e2..56edca00b0f29 100644 --- a/internal/suites/action_visit.go +++ b/internal/suites/action_visit.go @@ -23,5 +23,6 @@ func (wds *WebDriverSession) doVisitLoginPage(ctx context.Context, t *testing.T, if targetURL != "" { suffix = fmt.Sprintf("?rd=%s", targetURL) } + wds.doVisitAndVerifyOneFactorStep(ctx, t, fmt.Sprintf("%s/%s", LoginBaseURL, suffix)) } diff --git a/internal/suites/docker.go b/internal/suites/docker.go index eb6e78e489013..5adf1dab6431b 100644 --- a/internal/suites/docker.go +++ b/internal/suites/docker.go @@ -27,18 +27,21 @@ func NewDockerEnvironment(files []string) *DockerEnvironment { files[i] = strings.ReplaceAll(files[i], "{}", "dev") } } + return &DockerEnvironment{dockerComposeFiles: files} } func (de *DockerEnvironment) createCommandWithStdout(cmd string) *exec.Cmd { dockerCmdLine := fmt.Sprintf("docker-compose -p authelia -f %s %s", strings.Join(de.dockerComposeFiles, " -f "), cmd) log.Trace(dockerCmdLine) + return utils.CommandWithStdout("bash", "-c", dockerCmdLine) } func (de *DockerEnvironment) createCommand(cmd string) *exec.Cmd { dockerCmdLine := fmt.Sprintf("docker-compose -p authelia -f %s %s", strings.Join(de.dockerComposeFiles, " -f "), cmd) log.Trace(dockerCmdLine) + return utils.Command("bash", "-c", dockerCmdLine) } @@ -61,5 +64,6 @@ func (de *DockerEnvironment) Down() error { func (de *DockerEnvironment) Logs(service string, flags []string) (string, error) { cmd := de.createCommand(fmt.Sprintf("logs %s %s", strings.Join(flags, " "), service)) content, err := cmd.Output() + return string(content), err } diff --git a/internal/suites/environment.go b/internal/suites/environment.go index c47c6dc6462e2..70498327be040 100644 --- a/internal/suites/environment.go +++ b/internal/suites/environment.go @@ -19,6 +19,7 @@ func waitUntilServiceLogDetected( service string, logPatterns []string) error { log.Debug("Waiting for service " + service + " to be ready...") + err := utils.CheckUntil(5*time.Second, 1*time.Minute, func() (bool, error) { logs, err := dockerEnvironment.Logs(service, []string{"--tail", "20"}) fmt.Printf(".") @@ -35,6 +36,7 @@ func waitUntilServiceLogDetected( }) fmt.Print("\n") + return err } @@ -68,6 +70,8 @@ func waitUntilAutheliaIsReady(dockerEnvironment *DockerEnvironment) error { return err } } + log.Info("Authelia is now ready!") + return nil } diff --git a/internal/suites/http.go b/internal/suites/http.go index a4f07e99fe7eb..522562afb214c 100644 --- a/internal/suites/http.go +++ b/internal/suites/http.go @@ -12,6 +12,7 @@ func NewHTTPClient() *http.Client { InsecureSkipVerify: true, //nolint:gosec // Needs to be enabled in suites. Not used in production. }, } + return &http.Client{ Transport: tr, CheckRedirect: func(req *http.Request, via []*http.Request) error { diff --git a/internal/suites/kubernetes.go b/internal/suites/kubernetes.go index af2085dde6bb5..5f154ba964265 100644 --- a/internal/suites/kubernetes.go +++ b/internal/suites/kubernetes.go @@ -39,6 +39,7 @@ func (k Kind) CreateCluster() error { if err := cmd.Run(); err != nil { return err } + return nil } @@ -92,6 +93,7 @@ func (k Kubectl) StartDashboard() error { if err := utils.Shell("docker-compose -p authelia -f internal/suites/docker-compose.yml -f internal/suites/example/compose/kind/docker-compose.yml up -d kube-dashboard").Run(); err != nil { return err } + return nil } diff --git a/internal/suites/registry.go b/internal/suites/registry.go index 524eb7e5f59dd..de5c5db219191 100644 --- a/internal/suites/registry.go +++ b/internal/suites/registry.go @@ -49,6 +49,7 @@ func (sr *Registry) Register(name string, suite Suite) { if _, found := sr.registry[name]; found { log.Fatal(fmt.Sprintf("Trying to register the suite %s multiple times", name)) } + sr.registry[name] = suite } @@ -58,6 +59,7 @@ func (sr *Registry) Get(name string) Suite { if !found { log.Fatal(fmt.Sprintf("The suite %s does not exist", name)) } + return s } @@ -67,5 +69,6 @@ func (sr *Registry) Suites() []string { for k := range sr.registry { suites = append(suites, k) } + return suites } diff --git a/internal/suites/scenario_available_methods_test.go b/internal/suites/scenario_available_methods_test.go index a0cacdc81ecf3..328cac3d8b8b7 100644 --- a/internal/suites/scenario_available_methods_test.go +++ b/internal/suites/scenario_available_methods_test.go @@ -54,6 +54,7 @@ func IsStringInList(str string, list []string) bool { return true } } + return false } @@ -73,9 +74,11 @@ func (s *AvailableMethodsScenario) TestShouldCheckAvailableMethods() { s.Assert().Len(options, len(s.methods)) optionsList := make([]string, 0) + for _, o := range options { txt, err := o.Text() s.Assert().NoError(err) + optionsList = append(optionsList, txt) } diff --git a/internal/suites/scenario_inactivity_test.go b/internal/suites/scenario_inactivity_test.go index bfca5dd5e3db0..83ca6728325bc 100644 --- a/internal/suites/scenario_inactivity_test.go +++ b/internal/suites/scenario_inactivity_test.go @@ -60,8 +60,8 @@ func (s *InactivityScenario) TestShouldRequireReauthenticationAfterInactivityPer defer cancel() targetURL := fmt.Sprintf("%s/secret.html", AdminBaseURL) - s.doLoginTwoFactor(ctx, s.T(), "john", "password", false, s.secret, "") + s.doLoginTwoFactor(ctx, s.T(), "john", "password", false, s.secret, "") s.doVisit(s.T(), HomeBaseURL) s.verifyIsHome(ctx, s.T()) @@ -76,6 +76,7 @@ func (s *InactivityScenario) TestShouldRequireReauthenticationAfterCookieExpirat defer cancel() targetURL := fmt.Sprintf("%s/secret.html", AdminBaseURL) + s.doLoginTwoFactor(ctx, s.T(), "john", "password", false, s.secret, "") for i := 0; i < 3; i++ { @@ -83,6 +84,7 @@ func (s *InactivityScenario) TestShouldRequireReauthenticationAfterCookieExpirat s.verifyIsHome(ctx, s.T()) time.Sleep(2 * time.Second) + s.doVisit(s.T(), targetURL) s.verifySecretAuthorized(ctx, s.T()) } @@ -101,8 +103,8 @@ func (s *InactivityScenario) TestShouldDisableCookieExpirationAndInactivity() { defer cancel() targetURL := fmt.Sprintf("%s/secret.html", AdminBaseURL) - s.doLoginTwoFactor(ctx, s.T(), "john", "password", true, s.secret, "") + s.doLoginTwoFactor(ctx, s.T(), "john", "password", true, s.secret, "") s.doVisit(s.T(), HomeBaseURL) s.verifyIsHome(ctx, s.T()) diff --git a/internal/suites/scenario_two_factor_test.go b/internal/suites/scenario_two_factor_test.go index d00f620ecc805..6a71c2bd080f3 100644 --- a/internal/suites/scenario_two_factor_test.go +++ b/internal/suites/scenario_two_factor_test.go @@ -90,10 +90,10 @@ func (s *TwoFactorSuite) TestShouldFailTwoFactor() { s.doRegisterThenLogout(ctx, s.T(), testUsername, testPassword) wrongPasscode := "123456" + s.doLoginOneFactor(ctx, s.T(), testUsername, testPassword, false, "") s.verifyIsSecondFactorPage(ctx, s.T()) s.doEnterOTP(ctx, s.T(), wrongPasscode) - s.verifyNotificationDisplayed(ctx, s.T(), "The one-time password might be wrong") } diff --git a/internal/suites/suite_bypass_all.go b/internal/suites/suite_bypass_all.go index 94030a0baa85a..3f1937d180940 100644 --- a/internal/suites/suite_bypass_all.go +++ b/internal/suites/suite_bypass_all.go @@ -33,13 +33,16 @@ func init() { if err != nil { return err } + fmt.Println(backendLogs) frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil) if err != nil { return err } + fmt.Println(frontendLogs) + return nil } diff --git a/internal/suites/suite_docker.go b/internal/suites/suite_docker.go index dd17ca74b4448..170bd989c2176 100644 --- a/internal/suites/suite_docker.go +++ b/internal/suites/suite_docker.go @@ -29,13 +29,16 @@ func init() { if err != nil { return err } + fmt.Println(backendLogs) frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil) if err != nil { return err } + fmt.Println(frontendLogs) + return nil } diff --git a/internal/suites/suite_duo_push.go b/internal/suites/suite_duo_push.go index 115e32cca7ef9..f6a53f1e0c578 100644 --- a/internal/suites/suite_duo_push.go +++ b/internal/suites/suite_duo_push.go @@ -31,13 +31,16 @@ func init() { if err != nil { return err } + fmt.Println(backendLogs) frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil) if err != nil { return err } + fmt.Println(frontendLogs) + return nil } diff --git a/internal/suites/suite_haproxy.go b/internal/suites/suite_haproxy.go index df76888b16925..4fe94f01e81b4 100644 --- a/internal/suites/suite_haproxy.go +++ b/internal/suites/suite_haproxy.go @@ -33,13 +33,16 @@ func init() { if err != nil { return err } + fmt.Println(backendLogs) frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil) if err != nil { return err } + fmt.Println(frontendLogs) + return nil } diff --git a/internal/suites/suite_high_availability.go b/internal/suites/suite_high_availability.go index 66d22aa63c7cf..df0faf4145ba6 100644 --- a/internal/suites/suite_high_availability.go +++ b/internal/suites/suite_high_availability.go @@ -36,13 +36,16 @@ func init() { if err != nil { return err } + fmt.Println(backendLogs) frontendLogs, err := haDockerEnvironment.Logs("authelia-frontend", nil) if err != nil { return err } + fmt.Println(frontendLogs) + return nil } diff --git a/internal/suites/suite_high_availability_test.go b/internal/suites/suite_high_availability_test.go index 683f48f10e239..a87dbc8d1de2f 100644 --- a/internal/suites/suite_high_availability_test.go +++ b/internal/suites/suite_high_availability_test.go @@ -137,6 +137,7 @@ func (s *HighAvailabilityWebDriverSuite) TestShouldVerifyAccessControl() { verifyUserIsAuthorized := func(ctx context.Context, t *testing.T, username, targetURL string, authorized bool) { //nolint:unparam s.doVisit(t, targetURL) s.verifyURLIs(ctx, t, targetURL) + if authorized { s.verifySecretAuthorized(ctx, t) } else { @@ -182,6 +183,7 @@ func DoGetWithAuth(t *testing.T, username, password string) int { res, err := client.Do(req) assert.NoError(t, err) + return res.StatusCode } diff --git a/internal/suites/suite_kubernetes.go b/internal/suites/suite_kubernetes.go index a6c40d4f7900f..a7af84f36dac2 100644 --- a/internal/suites/suite_kubernetes.go +++ b/internal/suites/suite_kubernetes.go @@ -44,6 +44,7 @@ func init() { } log.Debug("Building authelia:dist image or use cache if already built...") + if os.Getenv("CI") != stringTrue { if err := utils.Shell("authelia-scripts docker build").Run(); err != nil { return err @@ -51,45 +52,54 @@ func init() { } log.Debug("Loading images into Kubernetes container...") + if err := loadDockerImages(); err != nil { return err } log.Debug("Starting Kubernetes dashboard...") + if err := kubectl.StartDashboard(); err != nil { return err } log.Debug("Deploying thirdparties...") + if err := kubectl.DeployThirdparties(); err != nil { return err } log.Debug("Waiting for services to be ready...") + if err := waitAllPodsAreReady(5 * time.Minute); err != nil { return err } log.Debug("Deploying Authelia...") + if err = kubectl.DeployAuthelia(); err != nil { return err } log.Debug("Waiting for services to be ready...") + if err := waitAllPodsAreReady(2 * time.Minute); err != nil { return err } log.Debug("Starting proxy...") + if err := kubectl.StartProxy(); err != nil { return err } + return nil } teardown := func(suitePath string) error { kubectl.StopDashboard() //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting. kubectl.StopProxy() //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting. + return kind.DeleteCluster() } @@ -123,9 +133,12 @@ func waitAllPodsAreReady(timeout time.Duration) error { // Wait in case the deployment has just been done and some services do not appear in kubectl logs. time.Sleep(1 * time.Second) fmt.Println("Check services are running") + if err := kubectl.WaitPodsReady(timeout); err != nil { return err } + fmt.Println("All pods are ready") + return nil } diff --git a/internal/suites/suite_ldap.go b/internal/suites/suite_ldap.go index 736ff0e4e46d0..be2d21610bfef 100644 --- a/internal/suites/suite_ldap.go +++ b/internal/suites/suite_ldap.go @@ -35,13 +35,16 @@ func init() { if err != nil { return err } + fmt.Println(backendLogs) frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil) if err != nil { return err } + fmt.Println(frontendLogs) + return nil } diff --git a/internal/suites/suite_mariadb.go b/internal/suites/suite_mariadb.go index 004f07881a0d5..d837f4aa75226 100644 --- a/internal/suites/suite_mariadb.go +++ b/internal/suites/suite_mariadb.go @@ -33,13 +33,16 @@ func init() { if err != nil { return err } + fmt.Println(backendLogs) frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil) if err != nil { return err } + fmt.Println(frontendLogs) + return nil } diff --git a/internal/suites/suite_mysql.go b/internal/suites/suite_mysql.go index c2557e8ce22ea..7f492264637e0 100644 --- a/internal/suites/suite_mysql.go +++ b/internal/suites/suite_mysql.go @@ -33,13 +33,16 @@ func init() { if err != nil { return err } + fmt.Println(backendLogs) frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil) if err != nil { return err } + fmt.Println(frontendLogs) + return nil } diff --git a/internal/suites/suite_network_acl.go b/internal/suites/suite_network_acl.go index 6101be075e4e2..a4cbf6523901f 100644 --- a/internal/suites/suite_network_acl.go +++ b/internal/suites/suite_network_acl.go @@ -34,13 +34,16 @@ func init() { if err != nil { return err } + fmt.Println(backendLogs) frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil) if err != nil { return err } + fmt.Println(frontendLogs) + return nil } diff --git a/internal/suites/suite_network_acl_test.go b/internal/suites/suite_network_acl_test.go index 5626e0f2e8a7e..a9e3e8174bef3 100644 --- a/internal/suites/suite_network_acl_test.go +++ b/internal/suites/suite_network_acl_test.go @@ -23,6 +23,7 @@ func (s *NetworkACLSuite) TestShouldAccessSecretUpon2FA() { wds, err := StartWebDriver() s.Require().NoError(err) + defer wds.Stop() //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting. targetURL := fmt.Sprintf("%s/secret.html", SecureBaseURL) @@ -40,6 +41,7 @@ func (s *NetworkACLSuite) TestShouldAccessSecretUpon1FA() { wds, err := StartWebDriverWithProxy("http://proxy-client1.example.com:3128", 4444) s.Require().NoError(err) + defer wds.Stop() //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting. targetURL := fmt.Sprintf("%s/secret.html", SecureBaseURL) @@ -58,6 +60,7 @@ func (s *NetworkACLSuite) TestShouldAccessSecretUpon0FA() { wds, err := StartWebDriverWithProxy("http://proxy-client2.example.com:3128", 4444) s.Require().NoError(err) + defer wds.Stop() //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting. wds.doVisit(s.T(), fmt.Sprintf("%s/secret.html", SecureBaseURL)) diff --git a/internal/suites/suite_one_factor_only.go b/internal/suites/suite_one_factor_only.go index 42cf856a470dd..23fdf6a2967b2 100644 --- a/internal/suites/suite_one_factor_only.go +++ b/internal/suites/suite_one_factor_only.go @@ -30,13 +30,16 @@ func init() { if err != nil { return err } + fmt.Println(backendLogs) frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil) if err != nil { return err } + fmt.Println(frontendLogs) + return nil } diff --git a/internal/suites/suite_postgres.go b/internal/suites/suite_postgres.go index 65e7d4728786d..50e19e2636a2e 100644 --- a/internal/suites/suite_postgres.go +++ b/internal/suites/suite_postgres.go @@ -33,13 +33,16 @@ func init() { if err != nil { return err } + fmt.Println(backendLogs) frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil) if err != nil { return err } + fmt.Println(frontendLogs) + return nil } diff --git a/internal/suites/suite_short_timeouts.go b/internal/suites/suite_short_timeouts.go index fc12ed863506b..7088d234a0d29 100644 --- a/internal/suites/suite_short_timeouts.go +++ b/internal/suites/suite_short_timeouts.go @@ -31,13 +31,16 @@ func init() { if err != nil { return err } + fmt.Println(backendLogs) frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil) if err != nil { return err } + fmt.Println(frontendLogs) + return nil } diff --git a/internal/suites/suite_standalone.go b/internal/suites/suite_standalone.go index 783f5eba0c03d..95d729f68b477 100644 --- a/internal/suites/suite_standalone.go +++ b/internal/suites/suite_standalone.go @@ -33,13 +33,16 @@ func init() { if err != nil { return err } + fmt.Println(backendLogs) frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil) if err != nil { return err } + fmt.Println(frontendLogs) + return nil } diff --git a/internal/suites/suite_traefik.go b/internal/suites/suite_traefik.go index 1fd5655bace1c..99cb4606756d3 100644 --- a/internal/suites/suite_traefik.go +++ b/internal/suites/suite_traefik.go @@ -33,13 +33,16 @@ func init() { if err != nil { return err } + fmt.Println(backendLogs) frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil) if err != nil { return err } + fmt.Println(frontendLogs) + return nil } diff --git a/internal/suites/suite_traefik2.go b/internal/suites/suite_traefik2.go index ae574e96a2503..85e5ce0d3e265 100644 --- a/internal/suites/suite_traefik2.go +++ b/internal/suites/suite_traefik2.go @@ -33,13 +33,16 @@ func init() { if err != nil { return err } + fmt.Println(backendLogs) frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil) if err != nil { return err } + fmt.Println(frontendLogs) + return nil } diff --git a/internal/suites/webdriver.go b/internal/suites/webdriver.go index 52834cb09c038..54fdce6fd2ce5 100644 --- a/internal/suites/webdriver.go +++ b/internal/suites/webdriver.go @@ -94,6 +94,7 @@ func WithWebdriver(fn func(webdriver selenium.WebDriver) error) error { // Wait wait until condition holds true. func (wds *WebDriverSession) Wait(ctx context.Context, condition selenium.Condition) error { done := make(chan error, 1) + go func() { done <- wds.WebDriver.Wait(condition) }() @@ -108,6 +109,7 @@ func (wds *WebDriverSession) Wait(ctx context.Context, condition selenium.Condit func (wds *WebDriverSession) waitElementLocated(ctx context.Context, t *testing.T, by, value string) selenium.WebElement { var el selenium.WebElement + err := wds.Wait(ctx, func(driver selenium.WebDriver) (bool, error) { var err error el, err = driver.FindElement(by, value) @@ -124,11 +126,13 @@ func (wds *WebDriverSession) waitElementLocated(ctx context.Context, t *testing. require.NoError(t, err) require.NotNil(t, el) + return el } func (wds *WebDriverSession) waitElementsLocated(ctx context.Context, t *testing.T, by, value string) []selenium.WebElement { var el []selenium.WebElement + err := wds.Wait(ctx, func(driver selenium.WebDriver) (bool, error) { var err error el, err = driver.FindElements(by, value) @@ -145,6 +149,7 @@ func (wds *WebDriverSession) waitElementsLocated(ctx context.Context, t *testing require.NoError(t, err) require.NotNil(t, el) + return el } diff --git a/internal/utils/aes.go b/internal/utils/aes.go index 2cfb648115a6d..3fdfcc3fb8ca3 100644 --- a/internal/utils/aes.go +++ b/internal/utils/aes.go @@ -26,6 +26,7 @@ func Encrypt(plaintext []byte, key *[32]byte) (ciphertext []byte, err error) { } nonce := make([]byte, gcm.NonceSize()) + _, err = io.ReadFull(rand.Reader, nonce) if err != nil { return nil, err diff --git a/internal/utils/exec.go b/internal/utils/exec.go index 1f1fad26570c6..ce48c4b4940fc 100644 --- a/internal/utils/exec.go +++ b/internal/utils/exec.go @@ -24,7 +24,9 @@ func Command(name string, args ...string) *exec.Cmd { for !strings.HasSuffix(wd, "authelia") { wd = filepath.Dir(wd) } + cmd.Dir = wd + return cmd } @@ -33,6 +35,7 @@ func CommandWithStdout(name string, args ...string) *exec.Cmd { cmd := Command(name, args...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr + return cmd } @@ -52,6 +55,7 @@ func RunCommandUntilCtrlC(cmd *exec.Cmd) { go func() { mutex.Lock() + f := bufio.NewWriter(os.Stdout) defer f.Flush() @@ -63,6 +67,7 @@ func RunCommandUntilCtrlC(cmd *exec.Cmd) { fmt.Println(err) cond.Broadcast() mutex.Unlock() + return } @@ -86,6 +91,7 @@ func RunFuncUntilCtrlC(fn func() error) error { go func() { mutex.Lock() + f := bufio.NewWriter(os.Stdout) defer f.Flush() @@ -98,16 +104,19 @@ func RunFuncUntilCtrlC(fn func() error) error { fmt.Println(err) cond.Broadcast() mutex.Unlock() + return } errorChannel <- nil + <-signalChannel cond.Broadcast() mutex.Unlock() }() cond.Wait() + return <-errorChannel } @@ -120,6 +129,7 @@ func RunCommandWithTimeout(cmd *exec.Cmd, timeout time.Duration) error { // Wait for the process to finish or kill it after a timeout (whichever happens first): done := make(chan error, 1) + go func() { done <- cmd.Wait() }() @@ -131,6 +141,7 @@ func RunCommandWithTimeout(cmd *exec.Cmd, timeout time.Duration) error { if err := cmd.Process.Kill(); err != nil { return err } + return ErrTimeoutReached case err := <-done: return err diff --git a/internal/utils/files.go b/internal/utils/files.go index 841c6ca78d9de..a70d11c041059 100644 --- a/internal/utils/files.go +++ b/internal/utils/files.go @@ -10,8 +10,10 @@ func FileExists(path string) (bool, error) { if err == nil { return true, nil } + if os.IsNotExist(err) { return false, nil } + return true, err } diff --git a/internal/utils/safe_redirection.go b/internal/utils/safe_redirection.go index 37b3e48c017bd..d161c8b364941 100644 --- a/internal/utils/safe_redirection.go +++ b/internal/utils/safe_redirection.go @@ -14,5 +14,6 @@ func IsRedirectionSafe(url url.URL, protectedDomain string) bool { if !strings.HasSuffix(url.Hostname(), protectedDomain) { return false } + return true } diff --git a/internal/utils/strings.go b/internal/utils/strings.go index d1cb06d620414..9ee77b15c3f5c 100644 --- a/internal/utils/strings.go +++ b/internal/utils/strings.go @@ -12,6 +12,7 @@ func IsStringInSlice(a string, list []string) (inSlice bool) { return true } } + return false } @@ -21,12 +22,14 @@ func SliceString(s string, d int) (array []string) { n := len(s) q := n / d r := n % d + for i := 0; i < q; i++ { array = append(array, s[i*d:i*d+d]) if i+1 == q && r != 0 { array = append(array, s[i*d+d:]) } } + return } @@ -38,11 +41,13 @@ func IsStringSlicesDifferent(a, b []string) (different bool) { return true } } + for _, s := range b { if !IsStringInSlice(s, a) { return true } } + return false } @@ -53,20 +58,24 @@ func StringSlicesDelta(before, after []string) (added, removed []string) { removed = append(removed, s) } } + for _, s := range after { if !IsStringInSlice(s, before) { added = append(added, s) } } + return added, removed } // RandomString generate a random string of n characters. func RandomString(n int, characters []rune) (randomString string) { rand.Seed(time.Now().UnixNano()) + b := make([]rune, n) for i := range b { b[i] = characters[rand.Intn(len(characters))] } + return string(b) } diff --git a/internal/utils/time.go b/internal/utils/time.go index 26a819e6c996f..b1f0c1241974e 100644 --- a/internal/utils/time.go +++ b/internal/utils/time.go @@ -12,9 +12,11 @@ import ( // Example 1y is the same as 1 year. func ParseDurationString(input string) (time.Duration, error) { var duration time.Duration + matches := parseDurationRegexp.FindStringSubmatch(input) if len(matches) == 3 && matches[2] != "" { d, _ := strconv.Atoi(matches[1]) + switch matches[2] { case "y": duration = time.Duration(d) * Year @@ -41,5 +43,6 @@ func ParseDurationString(input string) (time.Duration, error) { // Throw this error if input is anything other than a blank string, blank string will default to a duration of nothing return 0, fmt.Errorf("Could not convert the input string of %s into a duration", input) } + return duration, nil }