mirror of https://github.com/authelia/authelia.git
435 lines
12 KiB
Go
435 lines
12 KiB
Go
package commands
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"fmt"
|
|
"os"
|
|
"os/user"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/spf13/cobra"
|
|
"github.com/spf13/pflag"
|
|
|
|
"github.com/authelia/authelia/v4/internal/authentication"
|
|
"github.com/authelia/authelia/v4/internal/authorization"
|
|
"github.com/authelia/authelia/v4/internal/clock"
|
|
"github.com/authelia/authelia/v4/internal/configuration"
|
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
|
"github.com/authelia/authelia/v4/internal/configuration/validator"
|
|
"github.com/authelia/authelia/v4/internal/logging"
|
|
"github.com/authelia/authelia/v4/internal/metrics"
|
|
"github.com/authelia/authelia/v4/internal/middlewares"
|
|
"github.com/authelia/authelia/v4/internal/notification"
|
|
"github.com/authelia/authelia/v4/internal/ntp"
|
|
"github.com/authelia/authelia/v4/internal/oidc"
|
|
"github.com/authelia/authelia/v4/internal/random"
|
|
"github.com/authelia/authelia/v4/internal/regulation"
|
|
"github.com/authelia/authelia/v4/internal/session"
|
|
"github.com/authelia/authelia/v4/internal/storage"
|
|
"github.com/authelia/authelia/v4/internal/templates"
|
|
"github.com/authelia/authelia/v4/internal/totp"
|
|
"github.com/authelia/authelia/v4/internal/utils"
|
|
)
|
|
|
|
// NewCmdCtx returns a new CmdCtx.
|
|
func NewCmdCtx() *CmdCtx {
|
|
ctx := context.Background()
|
|
|
|
return &CmdCtx{
|
|
Context: ctx,
|
|
log: logging.Logger(),
|
|
providers: middlewares.Providers{
|
|
Random: &random.Cryptographical{},
|
|
},
|
|
config: &schema.Configuration{},
|
|
}
|
|
}
|
|
|
|
// CmdCtx is a context.Context used for the root command.
|
|
type CmdCtx struct {
|
|
context.Context
|
|
|
|
log *logrus.Logger
|
|
|
|
config *schema.Configuration
|
|
providers middlewares.Providers
|
|
trusted *x509.CertPool
|
|
|
|
cconfig *CmdCtxConfig
|
|
}
|
|
|
|
// NewCmdCtxConfig returns a new CmdCtxConfig.
|
|
func NewCmdCtxConfig() *CmdCtxConfig {
|
|
return &CmdCtxConfig{
|
|
validator: schema.NewStructValidator(),
|
|
}
|
|
}
|
|
|
|
// CmdCtxConfig is the configuration for the CmdCtx.
|
|
type CmdCtxConfig struct {
|
|
files []string
|
|
filters []string
|
|
defaults configuration.Source
|
|
sources []configuration.Source
|
|
keys []string
|
|
validator *schema.StructValidator
|
|
}
|
|
|
|
// CobraRunECmd describes a function that can be used as a *cobra.Command RunE, PreRunE, or PostRunE.
|
|
type CobraRunECmd func(cmd *cobra.Command, args []string) (err error)
|
|
|
|
func (ctx *CmdCtx) CheckSchemaVersion() (err error) {
|
|
if ctx.providers.StorageProvider == nil {
|
|
return fmt.Errorf("storage not loaded")
|
|
}
|
|
|
|
var version, latest int
|
|
|
|
if version, err = ctx.providers.StorageProvider.SchemaVersion(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
if latest, err = ctx.providers.StorageProvider.SchemaLatestVersion(); err != nil {
|
|
return err
|
|
}
|
|
|
|
switch {
|
|
case version > latest:
|
|
return fmt.Errorf("%w: version %d is not compatible with this version of the binary as the latest compatible version is %d", errStorageSchemaIncompatible, version, latest)
|
|
case version < latest:
|
|
return fmt.Errorf("%w: version %d is outdated please migrate to version %d in order to use this command or use an older binary", errStorageSchemaOutdated, version, latest)
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// CheckSchema is a utility function which checks the schema version and encryption key.
|
|
func (ctx *CmdCtx) CheckSchema() (err error) {
|
|
if err = ctx.CheckSchemaVersion(); err != nil {
|
|
return err
|
|
}
|
|
|
|
var result storage.EncryptionValidationResult
|
|
|
|
if result, err = ctx.providers.StorageProvider.SchemaEncryptionCheckKey(ctx, false); !result.Checked() || !result.Success() {
|
|
if err != nil {
|
|
return fmt.Errorf("failed to check the schema encryption key: %w", err)
|
|
}
|
|
|
|
return fmt.Errorf("failed to check the schema encryption key: the key is not valid for the schema")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// LoadTrustedCertificates loads the trusted certificates into the CmdCtx.
|
|
func (ctx *CmdCtx) LoadTrustedCertificates() (warns, errs []error) {
|
|
ctx.trusted, warns, errs = utils.NewX509CertPool(ctx.config.CertificatesDirectory)
|
|
|
|
return warns, errs
|
|
}
|
|
|
|
// LoadProviders loads all providers into the CmdCtx.
|
|
func (ctx *CmdCtx) LoadProviders() (warns, errs []error) {
|
|
// TODO: Adjust this so the CertPool can be used like a provider.
|
|
if warns, errs = ctx.LoadTrustedCertificates(); len(warns) != 0 || len(errs) != 0 {
|
|
return warns, errs
|
|
}
|
|
|
|
ctx.providers.StorageProvider = getStorageProvider(ctx)
|
|
|
|
ctx.providers.Authorizer = authorization.NewAuthorizer(ctx.config)
|
|
ctx.providers.NTP = ntp.NewProvider(&ctx.config.NTP)
|
|
ctx.providers.PasswordPolicy = middlewares.NewPasswordPolicyProvider(ctx.config.PasswordPolicy)
|
|
ctx.providers.Regulator = regulation.NewRegulator(ctx.config.Regulation, ctx.providers.StorageProvider, clock.New())
|
|
ctx.providers.SessionProvider = session.NewProvider(ctx.config.Session, ctx.trusted)
|
|
ctx.providers.TOTP = totp.NewTimeBasedProvider(ctx.config.TOTP)
|
|
|
|
var err error
|
|
|
|
switch {
|
|
case ctx.config.AuthenticationBackend.File != nil:
|
|
ctx.providers.UserProvider = authentication.NewFileUserProvider(ctx.config.AuthenticationBackend.File)
|
|
case ctx.config.AuthenticationBackend.LDAP != nil:
|
|
ctx.providers.UserProvider = authentication.NewLDAPUserProvider(ctx.config.AuthenticationBackend, ctx.trusted)
|
|
}
|
|
|
|
if ctx.providers.Templates, err = templates.New(templates.Config{EmailTemplatesPath: ctx.config.Notifier.TemplatePath}); err != nil {
|
|
errs = append(errs, err)
|
|
}
|
|
|
|
switch {
|
|
case ctx.config.Notifier.SMTP != nil:
|
|
ctx.providers.Notifier = notification.NewSMTPNotifier(ctx.config.Notifier.SMTP, ctx.trusted)
|
|
case ctx.config.Notifier.FileSystem != nil:
|
|
ctx.providers.Notifier = notification.NewFileNotifier(*ctx.config.Notifier.FileSystem)
|
|
}
|
|
|
|
ctx.providers.OpenIDConnect = oidc.NewOpenIDConnectProvider(ctx.config.IdentityProviders.OIDC, ctx.providers.StorageProvider, ctx.providers.Templates)
|
|
|
|
if ctx.config.Telemetry.Metrics.Enabled {
|
|
ctx.providers.Metrics = metrics.NewPrometheus()
|
|
}
|
|
|
|
return warns, errs
|
|
}
|
|
|
|
// ChainRunE runs multiple CobraRunECmd funcs one after the other returning errors.
|
|
func (ctx *CmdCtx) ChainRunE(cmdRunEs ...CobraRunECmd) CobraRunECmd {
|
|
return func(cmd *cobra.Command, args []string) (err error) {
|
|
for _, cmdRunE := range cmdRunEs {
|
|
if err = cmdRunE(cmd, args); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// HelperConfigSetFlagsMapRunE adds a command line source with flags mapping.
|
|
func (ctx *CmdCtx) HelperConfigSetFlagsMapRunE(flags *pflag.FlagSet, flagsMap map[string]string, includeInvalidKeys, includeUnchangedKeys bool) (err error) {
|
|
if ctx.cconfig == nil {
|
|
ctx.cconfig = NewCmdCtxConfig()
|
|
}
|
|
|
|
ctx.cconfig.sources = append(ctx.cconfig.sources, configuration.NewCommandLineSourceWithMapping(flags, flagsMap, includeInvalidKeys, includeUnchangedKeys))
|
|
|
|
return nil
|
|
}
|
|
|
|
// HelperConfigSetDefaultsRunE adds a defaults configuration source.
|
|
func (ctx *CmdCtx) HelperConfigSetDefaultsRunE(defaults map[string]any) CobraRunECmd {
|
|
return func(cmd *cobra.Command, args []string) (err error) {
|
|
if ctx.cconfig == nil {
|
|
ctx.cconfig = NewCmdCtxConfig()
|
|
}
|
|
|
|
ctx.cconfig.defaults = configuration.NewMapSource(defaults)
|
|
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// HelperConfigValidateKeysRunE validates the configuration (keys).
|
|
func (ctx *CmdCtx) HelperConfigValidateKeysRunE(_ *cobra.Command, _ []string) (err error) {
|
|
if ctx.cconfig == nil {
|
|
return fmt.Errorf("HelperConfigValidateKeysRunE must be used with HelperConfigLoadRunE")
|
|
}
|
|
|
|
validator.ValidateKeys(ctx.cconfig.keys, configuration.GetMultiKeyMappedDeprecationKeys(), configuration.DefaultEnvPrefix, ctx.cconfig.validator)
|
|
|
|
return nil
|
|
}
|
|
|
|
// HelperConfigValidateRunE validates the configuration (structure).
|
|
func (ctx *CmdCtx) HelperConfigValidateRunE(_ *cobra.Command, _ []string) (err error) {
|
|
tc := &tls.Config{
|
|
RootCAs: ctx.trusted,
|
|
MinVersion: tls.VersionTLS12,
|
|
MaxVersion: tls.VersionTLS13,
|
|
}
|
|
|
|
validator.ValidateConfiguration(ctx.config, ctx.cconfig.validator, validator.WithTLSConfig(tc))
|
|
|
|
return nil
|
|
}
|
|
|
|
func (ctx *CmdCtx) LogConfigure(_ *cobra.Command, _ []string) (err error) {
|
|
config := ctx.config.Log
|
|
|
|
switch config.Level {
|
|
case logging.LevelError, logging.LevelWarn, logging.LevelInfo, logging.LevelDebug, logging.LevelTrace:
|
|
break
|
|
default:
|
|
config.Level = logging.LevelTrace
|
|
}
|
|
|
|
switch config.Format {
|
|
case logging.FormatText, logging.FormatJSON:
|
|
break
|
|
default:
|
|
config.Format = logging.FormatText
|
|
}
|
|
|
|
config.KeepStdout = true
|
|
|
|
if err = logging.InitializeLogger(schema.Log{Level: ctx.config.Log.Level}, false); err != nil {
|
|
return fmt.Errorf("Cannot initialize logger: %w", err)
|
|
}
|
|
|
|
ctx.log.WithFields(map[string]any{"filters": ctx.cconfig.filters, "files": ctx.cconfig.files}).Debug("Loaded Configuration Sources")
|
|
ctx.log.WithFields(map[string]any{"level": ctx.config.Log.Level, "format": ctx.config.Log.Format, "file": ctx.config.Log.FilePath, "keep_stdout": ctx.config.Log.KeepStdout}).Debug("Logging Initialized")
|
|
|
|
return nil
|
|
}
|
|
|
|
func (ctx *CmdCtx) LogProcessCurrentUserRunE(_ *cobra.Command, _ []string) (err error) {
|
|
var current *user.User
|
|
|
|
if current, err = user.Current(); err != nil {
|
|
current = &user.User{Uid: strconv.Itoa(syscall.Getuid()), Gid: strconv.Itoa(syscall.Getgid())}
|
|
}
|
|
|
|
fields := map[string]any{"uid": current.Uid, "gid": current.Gid}
|
|
|
|
if current.Username != "" {
|
|
fields["username"] = current.Username
|
|
}
|
|
|
|
if current.Name != "" {
|
|
fields["name"] = current.Name
|
|
}
|
|
|
|
var gids []string
|
|
|
|
if gids, err = current.GroupIds(); err == nil && len(gids) != 0 {
|
|
gidsFinal := []string{}
|
|
|
|
for _, gid := range gids {
|
|
if gid == current.Gid {
|
|
continue
|
|
}
|
|
|
|
gidsFinal = append(gidsFinal, gid)
|
|
}
|
|
|
|
if len(gidsFinal) != 0 {
|
|
fields["gids"] = strings.Join(gidsFinal, ",")
|
|
}
|
|
}
|
|
|
|
ctx.log.WithFields(fields).Debug("Process user information")
|
|
|
|
return nil
|
|
}
|
|
|
|
// ConfigValidateLogRunE logs the warnings and errors detected during the validations that have ran.
|
|
func (ctx *CmdCtx) ConfigValidateLogRunE(_ *cobra.Command, _ []string) (err error) {
|
|
warnings := ctx.cconfig.validator.Warnings()
|
|
if len(warnings) != 0 {
|
|
for _, warning := range warnings {
|
|
ctx.log.Warnf("Configuration: %+v", warning)
|
|
}
|
|
}
|
|
|
|
errs := ctx.cconfig.validator.Errors()
|
|
if len(errs) != 0 {
|
|
for _, err = range errs {
|
|
ctx.log.Errorf("Configuration: %+v", err)
|
|
}
|
|
|
|
ctx.log.Fatalf("Can't continue due to the errors loading the configuration")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ConfigValidateSectionPasswordRunE validates the configuration (structure, password section).
|
|
func (ctx *CmdCtx) ConfigValidateSectionPasswordRunE(cmd *cobra.Command, _ []string) (err error) {
|
|
if ctx.config.AuthenticationBackend.File == nil {
|
|
return fmt.Errorf("password configuration was not initialized")
|
|
}
|
|
|
|
val := &schema.StructValidator{}
|
|
|
|
validator.ValidatePasswordConfiguration(&ctx.config.AuthenticationBackend.File.Password, val)
|
|
|
|
errs := val.Errors()
|
|
|
|
if len(errs) == 0 {
|
|
return nil
|
|
}
|
|
|
|
for i, e := range errs {
|
|
if i == 0 {
|
|
err = e
|
|
continue
|
|
}
|
|
|
|
err = fmt.Errorf("%v, %w", err, e)
|
|
}
|
|
|
|
return fmt.Errorf("errors occurred validating the password configuration: %w", err)
|
|
}
|
|
|
|
// ConfigEnsureExistsRunE logs the warnings and errors detected during the validations that have ran.
|
|
func (ctx *CmdCtx) ConfigEnsureExistsRunE(cmd *cobra.Command, _ []string) (err error) {
|
|
var (
|
|
configs []string
|
|
created bool
|
|
result XEnvCLIResult
|
|
)
|
|
|
|
if configs, result, err = loadXEnvCLIStringSliceValue(cmd, cmdFlagEnvNameConfig, cmdFlagNameConfig); err != nil {
|
|
return err
|
|
}
|
|
|
|
switch {
|
|
case result == XEnvCLIResultCLIExplicit:
|
|
return nil
|
|
case result == XEnvCLIResultEnvironment && len(configs) == 1:
|
|
switch configs[0] {
|
|
case cmdConfigDefaultContainer, cmdConfigDefaultDaemon:
|
|
break
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
if created, err = configuration.EnsureConfigurationExists(configs[0]); err != nil {
|
|
ctx.log.Fatal(err)
|
|
}
|
|
|
|
if created {
|
|
ctx.log.Warnf("Configuration did not exist so a default one has been generated at %s, you will need to configure this", configs[0])
|
|
os.Exit(0)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// HelperConfigLoadRunE loads the configuration into the CmdCtx.
|
|
func (ctx *CmdCtx) HelperConfigLoadRunE(cmd *cobra.Command, _ []string) (err error) {
|
|
var (
|
|
filters []configuration.BytesFilter
|
|
)
|
|
|
|
if ctx.cconfig == nil {
|
|
ctx.cconfig = NewCmdCtxConfig()
|
|
}
|
|
|
|
if ctx.cconfig.files, filters, err = loadXEnvCLIConfigValues(cmd); err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx.cconfig.filters = make([]string, len(filters))
|
|
|
|
for i, filter := range filters {
|
|
ctx.cconfig.filters[i] = filter.Name()
|
|
}
|
|
|
|
ctx.cconfig.sources = configuration.NewDefaultSourcesWithDefaults(
|
|
ctx.cconfig.files,
|
|
filters,
|
|
configuration.DefaultEnvPrefix,
|
|
configuration.DefaultEnvDelimiter,
|
|
ctx.cconfig.defaults,
|
|
ctx.cconfig.sources...)
|
|
|
|
if ctx.cconfig.keys, err = configuration.LoadAdvanced(
|
|
ctx.cconfig.validator,
|
|
"",
|
|
ctx.config,
|
|
ctx.cconfig.sources...); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|