authelia/internal/configuration/reader.go
Amir Zarrinkafsh a9b0caf4ee
[MISC] Catch and warn on malformed configuration yaml (#1089)
If the configuration yaml is poorly indented or special values are not appropriately escaped Authelia attempts to load said configuration and fails.

This attempts to unmarshal the config into an empty interface to catch and warn on malformed yaml.

Using the example from issue https://github.com/authelia/authelia/issues/1053#issuecomment-634791662

```yaml
host: 0.0.0.0
port: 9091
log_level: debug

jwt_secret: RUtG9TnbXrOl1XLLmDgySw1DGgx9QcrtepIf1uDDBlBVKFZxkVBruYKBi32PvaU

default_redirection_url: example.com

totp:
  issuer: example.com
  period: 30
  skew: 1

authentication_backend:
  file:
    path: /etc/authelia/users_database.yml

access_control:
  default_policy: deny
  rules:
    - domain: example.com
      policy: bypass
    - domain: "*.example.com"
      policy: one_factor

session:
  name: authelia_session
  secret: TVPMIcDFbBwhnW3kLJzKhdjeHhtqisr7m28FgRY8oLh2A4lwuV2jV2ZGdGbh4aa
  expiration: 3600
  inactivity: 300
  domain: example.com

regulation:
  max_retries: 3
  find_time: 120
  ban_time: 300

storage:
  mysql:
    host: example.com
    port: 3306
    database: authelia
    username: authelia
    password: example.com

notifier:
  smtp:
    username: example.com
    password: example.com
    host: smtp.gmail.com
    port: 465
    sender: example.com
```

We would actually get a more meaningful error which helps pinpoint the issue:
`Error malformed yaml: line 23: did not find expected alphabetic or numeric character`
2020-06-09 08:22:41 +10:00

70 lines
2.6 KiB
Go

package configuration
import (
"errors"
"fmt"
"io/ioutil"
"os"
"strings"
"github.com/spf13/viper"
"gopkg.in/yaml.v2"
"github.com/authelia/authelia/internal/configuration/schema"
"github.com/authelia/authelia/internal/configuration/validator"
)
// Read a YAML configuration and create a Configuration object out of it.
func Read(configPath string) (*schema.Configuration, []error) {
if configPath == "" {
return nil, []error{errors.New("No config file path provided")}
}
_, err := os.Stat(configPath)
if err != nil {
return nil, []error{fmt.Errorf("Unable to find config file: %v", configPath)}
}
file, err := ioutil.ReadFile(configPath)
if err != nil {
return nil, []error{fmt.Errorf("Failed to %v", err)}
}
var data interface{}
err = yaml.Unmarshal(file, &data)
if err != nil {
return nil, []error{fmt.Errorf("Error malformed %v", err)}
}
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.BindEnv("authelia.jwt_secret.file") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
viper.BindEnv("authelia.duo_api.secret_key.file") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
viper.BindEnv("authelia.session.secret.file") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
viper.BindEnv("authelia.authentication_backend.ldap.password.file") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
viper.BindEnv("authelia.notifier.smtp.password.file") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
viper.BindEnv("authelia.session.redis.password.file") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
viper.BindEnv("authelia.storage.mysql.password.file") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
viper.BindEnv("authelia.storage.postgres.password.file") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
viper.SetConfigFile(configPath)
_ = viper.ReadInConfig()
var configuration schema.Configuration
viper.Unmarshal(&configuration) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
val := schema.NewStructValidator()
validator.ValidateSecrets(&configuration, val, viper.GetViper())
validator.ValidateConfiguration(&configuration, val)
validator.ValidateKeys(val, viper.AllKeys())
if val.HasErrors() {
return nil, val.Errors()
}
return &configuration, nil
}