2021-08-03 09:55:21 +00:00
package commands
import (
"fmt"
2022-08-08 21:50:12 +00:00
"net"
2021-08-03 09:55:21 +00:00
"os"
2022-08-08 21:50:12 +00:00
"os/signal"
2022-10-17 11:31:23 +00:00
"path/filepath"
2021-09-17 09:53:59 +00:00
"strings"
2022-08-08 21:50:12 +00:00
"syscall"
2021-08-03 09:55:21 +00:00
2022-10-17 11:31:23 +00:00
"github.com/fsnotify/fsnotify"
2021-08-03 09:55:21 +00:00
"github.com/spf13/cobra"
2022-08-08 21:50:12 +00:00
"github.com/valyala/fasthttp"
2021-08-03 09:55:21 +00:00
2022-10-17 11:31:23 +00:00
"github.com/authelia/authelia/v4/internal/authentication"
2021-08-11 01:04:35 +00:00
"github.com/authelia/authelia/v4/internal/logging"
2022-03-06 05:47:40 +00:00
"github.com/authelia/authelia/v4/internal/model"
2021-08-11 01:04:35 +00:00
"github.com/authelia/authelia/v4/internal/server"
"github.com/authelia/authelia/v4/internal/utils"
2021-08-03 09:55:21 +00:00
)
// NewRootCmd returns a new Root Cmd.
func NewRootCmd ( ) ( cmd * cobra . Command ) {
2022-12-22 00:21:29 +00:00
ctx := NewCmdCtx ( )
2021-08-03 09:55:21 +00:00
version := utils . Version ( )
cmd = & cobra . Command {
Use : "authelia" ,
2022-06-14 12:40:00 +00:00
Short : fmt . Sprintf ( fmtCmdAutheliaShort , version ) ,
Long : fmt . Sprintf ( fmtCmdAutheliaLong , version ) ,
2021-08-03 09:55:21 +00:00
Example : cmdAutheliaExample ,
Version : version ,
Args : cobra . NoArgs ,
2022-12-22 00:21:29 +00:00
PreRunE : ctx . ChainRunE (
ctx . ConfigEnsureExistsRunE ,
ctx . ConfigLoadRunE ,
ctx . ConfigValidateKeysRunE ,
ctx . ConfigValidateRunE ,
ctx . ConfigValidateLogRunE ,
) ,
RunE : ctx . RootRunE ,
2022-09-01 02:24:47 +00:00
DisableAutoGenTag : true ,
2021-08-03 09:55:21 +00:00
}
2022-12-23 00:03:50 +00:00
cmd . PersistentFlags ( ) . StringSliceP ( cmdFlagNameConfig , "c" , [ ] string { "configuration.yml" } , "configuration files or directories to load, for more information run 'authelia -h authelia config'" )
2021-08-03 09:55:21 +00:00
2022-12-23 00:03:50 +00:00
cmd . PersistentFlags ( ) . StringSlice ( cmdFlagNameConfigExpFilters , nil , "list of filters to apply to all configuration files, for more information run 'authelia -h authelia filters'" )
2022-12-21 09:48:14 +00:00
2021-08-03 09:55:21 +00:00
cmd . AddCommand (
2022-12-22 00:21:29 +00:00
newAccessControlCommand ( ctx ) ,
newBuildInfoCmd ( ctx ) ,
newCryptoCmd ( ctx ) ,
newStorageCmd ( ctx ) ,
newValidateConfigCmd ( ctx ) ,
2022-12-22 06:34:20 +00:00
2022-12-23 00:03:50 +00:00
newHelpTopic ( "config" , "Help for the config file/directory paths" , helpTopicConfig ) ,
newHelpTopic ( "filters" , "help topic for the config filters" , helpTopicConfigFilters ) ,
2021-08-03 09:55:21 +00:00
)
return cmd
}
2022-12-22 00:21:29 +00:00
func ( ctx * CmdCtx ) RootRunE ( _ * cobra . Command , _ [ ] string ) ( err error ) {
ctx . log . Infof ( "Authelia %s is starting" , utils . Version ( ) )
2021-08-03 09:55:21 +00:00
if os . Getenv ( "ENVIRONMENT" ) == "dev" {
2022-12-22 00:21:29 +00:00
ctx . log . Info ( "===> Authelia is running in development mode. <===" )
2021-08-03 09:55:21 +00:00
}
2022-12-22 00:21:29 +00:00
if err = logging . InitializeLogger ( ctx . config . Log , true ) ; err != nil {
ctx . log . Fatalf ( "Cannot initialize logger: %v" , err )
2021-08-03 09:55:21 +00:00
}
2022-12-22 00:21:29 +00:00
warns , errs := ctx . LoadProviders ( )
if len ( warns ) != 0 {
for _ , err = range warns {
ctx . log . Warn ( err )
2021-08-03 09:55:21 +00:00
}
}
2022-12-22 00:21:29 +00:00
if len ( errs ) != 0 {
for _ , err = range errs {
ctx . log . Error ( err )
2021-08-03 09:55:21 +00:00
}
2022-12-22 00:21:29 +00:00
ctx . log . Fatalf ( "Errors occurred provisioning providers." )
2021-08-03 09:55:21 +00:00
}
2022-12-22 00:21:29 +00:00
doStartupChecks ( ctx )
2022-12-22 06:34:20 +00:00
ctx . cconfig = nil
2022-12-22 00:21:29 +00:00
runServices ( ctx )
2021-09-17 09:53:59 +00:00
2022-12-22 00:21:29 +00:00
return nil
2022-06-14 07:20:13 +00:00
}
2022-09-04 12:26:03 +00:00
//nolint:gocyclo // Complexity is required in this function.
2022-12-22 00:21:29 +00:00
func runServices ( ctx * CmdCtx ) {
defer ctx . cancel ( )
2022-08-08 21:50:12 +00:00
quit := make ( chan os . Signal , 1 )
signal . Notify ( quit , syscall . SIGINT , syscall . SIGTERM )
defer signal . Stop ( quit )
2022-06-14 07:20:13 +00:00
2022-08-08 21:50:12 +00:00
var (
mainServer , metricsServer * fasthttp . Server
mainListener , metricsListener net . Listener
)
2022-06-14 07:20:13 +00:00
2022-12-22 00:21:29 +00:00
ctx . group . Go ( func ( ) ( err error ) {
2022-08-08 21:50:12 +00:00
defer func ( ) {
2022-09-04 12:26:03 +00:00
if r := recover ( ) ; r != nil {
2022-12-22 00:21:29 +00:00
ctx . log . WithError ( recoverErr ( r ) ) . Errorf ( "Critical error in server caught (recovered)" )
2022-08-08 21:50:12 +00:00
}
} ( )
2022-12-22 00:21:29 +00:00
if mainServer , mainListener , err = server . CreateDefaultServer ( * ctx . config , ctx . providers ) ; err != nil {
2023-01-03 06:08:49 +00:00
ctx . log . WithError ( err ) . Error ( "Create Server (main) returned error" )
2022-08-08 21:50:12 +00:00
return err
2022-06-14 07:20:13 +00:00
}
2022-08-08 21:50:12 +00:00
if err = mainServer . Serve ( mainListener ) ; err != nil {
2022-12-22 00:21:29 +00:00
ctx . log . WithError ( err ) . Error ( "Server (main) returned error" )
2022-09-04 12:26:03 +00:00
2022-08-08 21:50:12 +00:00
return err
}
2022-06-14 07:20:13 +00:00
2022-08-08 21:50:12 +00:00
return nil
} )
2022-06-14 07:20:13 +00:00
2022-12-22 00:21:29 +00:00
ctx . group . Go ( func ( ) ( err error ) {
if ctx . providers . Metrics == nil {
2022-08-08 21:50:12 +00:00
return nil
2022-06-14 07:20:13 +00:00
}
2022-08-08 21:50:12 +00:00
defer func ( ) {
2022-09-04 12:26:03 +00:00
if r := recover ( ) ; r != nil {
2022-12-22 00:21:29 +00:00
ctx . log . WithError ( recoverErr ( r ) ) . Errorf ( "Critical error in metrics server caught (recovered)" )
2022-08-08 21:50:12 +00:00
}
} ( )
2022-12-22 00:21:29 +00:00
if metricsServer , metricsListener , err = server . CreateMetricsServer ( ctx . config . Telemetry . Metrics ) ; err != nil {
2023-01-03 06:08:49 +00:00
ctx . log . WithError ( err ) . Error ( "Create Server (metrics) returned error" )
2022-08-08 21:50:12 +00:00
return err
}
if err = metricsServer . Serve ( metricsListener ) ; err != nil {
2022-12-22 00:21:29 +00:00
ctx . log . WithError ( err ) . Error ( "Server (metrics) returned error" )
2022-09-04 12:26:03 +00:00
2022-08-08 21:50:12 +00:00
return err
}
2022-06-14 07:20:13 +00:00
return nil
2022-08-08 21:50:12 +00:00
} )
2022-12-22 00:21:29 +00:00
if ctx . config . AuthenticationBackend . File != nil && ctx . config . AuthenticationBackend . File . Watch {
provider := ctx . providers . UserProvider . ( * authentication . FileUserProvider )
if watcher , err := runServiceFileWatcher ( ctx , ctx . config . AuthenticationBackend . File . Path , provider ) ; err != nil {
ctx . log . WithError ( err ) . Errorf ( "Error opening file watcher" )
2022-10-17 11:31:23 +00:00
} else {
2023-01-03 06:08:49 +00:00
defer func ( watcher * fsnotify . Watcher ) {
if err := watcher . Close ( ) ; err != nil {
ctx . log . WithError ( err ) . Errorf ( "Error closing file watcher" )
}
} ( watcher )
2022-10-17 11:31:23 +00:00
}
}
2022-08-08 21:50:12 +00:00
select {
2022-09-04 12:26:03 +00:00
case s := <- quit :
switch s {
case syscall . SIGINT :
2022-12-22 00:21:29 +00:00
ctx . log . Debugf ( "Shutdown started due to SIGINT" )
2022-09-04 12:26:03 +00:00
case syscall . SIGQUIT :
2022-12-22 00:21:29 +00:00
ctx . log . Debugf ( "Shutdown started due to SIGQUIT" )
2022-09-04 12:26:03 +00:00
}
2022-08-08 21:50:12 +00:00
case <- ctx . Done ( ) :
2022-12-22 00:21:29 +00:00
ctx . log . Debugf ( "Shutdown started due to context completion" )
2022-06-14 07:20:13 +00:00
}
2022-12-22 00:21:29 +00:00
ctx . cancel ( )
2022-08-08 21:50:12 +00:00
2022-12-22 00:21:29 +00:00
ctx . log . Infof ( "Shutting down" )
2022-06-14 07:20:13 +00:00
2022-08-08 21:50:12 +00:00
var err error
2022-09-04 12:26:03 +00:00
if mainServer != nil {
if err = mainServer . Shutdown ( ) ; err != nil {
2022-12-22 00:21:29 +00:00
ctx . log . WithError ( err ) . Errorf ( "Error occurred shutting down the server" )
2022-09-04 12:26:03 +00:00
}
2022-08-08 21:50:12 +00:00
}
if metricsServer != nil {
if err = metricsServer . Shutdown ( ) ; err != nil {
2022-12-22 00:21:29 +00:00
ctx . log . WithError ( err ) . Errorf ( "Error occurred shutting down the metrics server" )
2022-06-14 07:20:13 +00:00
}
}
2022-04-04 23:57:47 +00:00
2022-12-22 00:21:29 +00:00
if err = ctx . providers . StorageProvider . Close ( ) ; err != nil {
ctx . log . WithError ( err ) . Errorf ( "Error occurred closing the database connection" )
2022-10-25 05:12:42 +00:00
}
2022-12-22 00:21:29 +00:00
if err = ctx . group . Wait ( ) ; err != nil {
ctx . log . WithError ( err ) . Errorf ( "Error occurred waiting for shutdown" )
2022-08-08 21:50:12 +00:00
}
2021-08-03 09:55:21 +00:00
}
2022-10-17 11:31:23 +00:00
type ReloadFilter func ( path string ) ( skipped bool )
type ProviderReload interface {
Reload ( ) ( reloaded bool , err error )
}
2022-12-22 00:21:29 +00:00
func runServiceFileWatcher ( ctx * CmdCtx , path string , reload ProviderReload ) ( watcher * fsnotify . Watcher , err error ) {
2022-10-17 11:31:23 +00:00
if watcher , err = fsnotify . NewWatcher ( ) ; err != nil {
return nil , err
}
failed := make ( chan struct { } )
var directory , filename string
if path != "" {
directory , filename = filepath . Dir ( path ) , filepath . Base ( path )
}
2022-12-22 00:21:29 +00:00
ctx . group . Go ( func ( ) error {
2022-10-17 11:31:23 +00:00
for {
select {
case <- failed :
return nil
case event , ok := <- watcher . Events :
if ! ok {
return nil
}
if filename != filepath . Base ( event . Name ) {
2022-12-22 00:21:29 +00:00
ctx . log . WithField ( "file" , event . Name ) . WithField ( "op" , event . Op ) . Tracef ( "File modification detected to irrelevant file" )
2022-10-17 11:31:23 +00:00
break
}
switch {
case event . Op & fsnotify . Write == fsnotify . Write , event . Op & fsnotify . Create == fsnotify . Create :
2022-12-22 00:21:29 +00:00
ctx . log . WithField ( "file" , event . Name ) . WithField ( "op" , event . Op ) . Debug ( "File modification detected" )
2022-10-17 11:31:23 +00:00
switch reloaded , err := reload . Reload ( ) ; {
case err != nil :
2022-12-22 00:21:29 +00:00
ctx . log . WithField ( "file" , event . Name ) . WithField ( "op" , event . Op ) . WithError ( err ) . Error ( "Error occurred reloading file" )
2022-10-17 11:31:23 +00:00
case reloaded :
2022-12-22 00:21:29 +00:00
ctx . log . WithField ( "file" , event . Name ) . Info ( "Reloaded file successfully" )
2022-10-17 11:31:23 +00:00
default :
2022-12-22 00:21:29 +00:00
ctx . log . WithField ( "file" , event . Name ) . Debug ( "Reload of file was triggered but it was skipped" )
2022-10-17 11:31:23 +00:00
}
case event . Op & fsnotify . Remove == fsnotify . Remove :
2022-12-22 00:21:29 +00:00
ctx . log . WithField ( "file" , event . Name ) . WithField ( "op" , event . Op ) . Debug ( "Remove of file was detected" )
2022-10-17 11:31:23 +00:00
}
case err , ok := <- watcher . Errors :
if ! ok {
return nil
}
2022-12-22 00:21:29 +00:00
ctx . log . WithError ( err ) . Errorf ( "Error while watching files" )
2022-10-17 11:31:23 +00:00
}
}
} )
if err := watcher . Add ( directory ) ; err != nil {
failed <- struct { } { }
return nil , err
}
2022-12-22 00:21:29 +00:00
ctx . log . WithField ( "directory" , directory ) . WithField ( "file" , filename ) . Debug ( "Directory is being watched for changes to the file" )
2022-10-17 11:31:23 +00:00
return watcher , nil
}
2022-12-22 00:21:29 +00:00
func doStartupChecks ( ctx * CmdCtx ) {
2021-09-17 09:53:59 +00:00
var (
failures [ ] string
err error
)
2022-12-22 00:21:29 +00:00
if err = doStartupCheck ( ctx , "storage" , ctx . providers . StorageProvider , false ) ; err != nil {
ctx . log . Errorf ( "Failure running the storage provider startup check: %+v" , err )
2021-11-23 09:45:38 +00:00
failures = append ( failures , "storage" )
}
2022-12-22 00:21:29 +00:00
if err = doStartupCheck ( ctx , "user" , ctx . providers . UserProvider , false ) ; err != nil {
ctx . log . Errorf ( "Failure running the user provider startup check: %+v" , err )
2021-09-17 09:53:59 +00:00
failures = append ( failures , "user" )
}
2022-12-22 00:21:29 +00:00
if err = doStartupCheck ( ctx , "notification" , ctx . providers . Notifier , ctx . config . Notifier . DisableStartupCheck ) ; err != nil {
ctx . log . Errorf ( "Failure running the notification provider startup check: %+v" , err )
2021-09-17 09:53:59 +00:00
failures = append ( failures , "notification" )
}
2022-12-22 00:21:29 +00:00
if ! ctx . config . NTP . DisableStartupCheck && ! ctx . providers . Authorizer . IsSecondFactorEnabled ( ) {
ctx . log . Debug ( "The NTP startup check was skipped due to there being no configured 2FA access control rules" )
} else if err = doStartupCheck ( ctx , "ntp" , ctx . providers . NTP , ctx . config . NTP . DisableStartupCheck ) ; err != nil {
ctx . log . Errorf ( "Failure running the ntp provider startup check: %+v" , err )
2021-09-17 09:53:59 +00:00
2022-12-22 00:21:29 +00:00
if ! ctx . config . NTP . DisableFailure {
2022-02-03 03:04:24 +00:00
failures = append ( failures , "ntp" )
}
2021-09-17 09:53:59 +00:00
}
if len ( failures ) != 0 {
2022-12-22 00:21:29 +00:00
ctx . log . Fatalf ( "The following providers had fatal failures during startup: %s" , strings . Join ( failures , ", " ) )
2021-09-17 09:53:59 +00:00
}
}
2022-12-22 00:21:29 +00:00
func doStartupCheck ( ctx * CmdCtx , name string , provider model . StartupCheck , disabled bool ) error {
2021-09-17 09:53:59 +00:00
if disabled {
2022-12-22 00:21:29 +00:00
ctx . log . Debugf ( "%s provider: startup check skipped as it is disabled" , name )
2021-09-17 09:53:59 +00:00
return nil
}
if provider == nil {
return fmt . Errorf ( "unrecognized provider or it is not configured properly" )
}
2022-02-09 22:07:53 +00:00
return provider . StartupCheck ( )
2021-09-17 09:53:59 +00:00
}