2021-08-03 09:55:21 +00:00
package commands
import (
2022-08-08 21:50:12 +00:00
"context"
2021-08-03 09:55:21 +00:00
"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-09-17 09:53:59 +00:00
"github.com/sirupsen/logrus"
2021-08-03 09:55:21 +00:00
"github.com/spf13/cobra"
2022-08-08 21:50:12 +00:00
"github.com/valyala/fasthttp"
"golang.org/x/sync/errgroup"
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/configuration/schema"
"github.com/authelia/authelia/v4/internal/logging"
"github.com/authelia/authelia/v4/internal/middlewares"
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 ) {
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 ,
PreRun : newCmdWithConfigPreRun ( true , true , true ) ,
Run : cmdRootRun ,
2022-09-01 02:24:47 +00:00
DisableAutoGenTag : true ,
2021-08-03 09:55:21 +00:00
}
2022-02-28 03:15:01 +00:00
cmdWithConfigFlags ( cmd , false , [ ] string { } )
2021-08-03 09:55:21 +00:00
2022-12-21 09:48:14 +00:00
cmd . Flags ( ) . StringSlice ( cmdFlagNameConfigExpFilters , nil , "Applies filters in order to the configuration file before the YAML parser. Options are 'template', 'expand-env'" )
2021-08-03 09:55:21 +00:00
cmd . AddCommand (
2022-10-17 10:51:59 +00:00
newAccessControlCommand ( ) ,
2021-08-03 09:55:21 +00:00
newBuildInfoCmd ( ) ,
2022-06-27 08:27:57 +00:00
newCryptoCmd ( ) ,
2022-06-14 12:40:00 +00:00
newHashPasswordCmd ( ) ,
newStorageCmd ( ) ,
2021-08-03 09:55:21 +00:00
newValidateConfigCmd ( ) ,
)
return cmd
}
func cmdRootRun ( _ * cobra . Command , _ [ ] string ) {
logger := logging . Logger ( )
logger . Infof ( "Authelia %s is starting" , utils . Version ( ) )
if os . Getenv ( "ENVIRONMENT" ) == "dev" {
logger . Info ( "===> Authelia is running in development mode. <===" )
}
if err := logging . InitializeLogger ( config . Log , true ) ; err != nil {
logger . Fatalf ( "Cannot initialize logger: %v" , err )
}
2021-11-25 01:56:58 +00:00
providers , warnings , errors := getProviders ( )
2021-08-03 09:55:21 +00:00
if len ( warnings ) != 0 {
for _ , err := range warnings {
logger . Warn ( err )
}
}
if len ( errors ) != 0 {
for _ , err := range errors {
logger . Error ( err )
}
logger . Fatalf ( "Errors occurred provisioning providers." )
}
2022-08-08 21:50:12 +00:00
doStartupChecks ( config , & providers , logger )
2021-09-17 09:53:59 +00:00
2022-10-17 11:31:23 +00:00
runServices ( config , providers , logger )
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-10-17 11:31:23 +00:00
func runServices ( config * schema . Configuration , providers middlewares . Providers , log * logrus . Logger ) {
2022-08-08 21:50:12 +00:00
ctx := context . Background ( )
2022-06-14 07:20:13 +00:00
2022-08-08 21:50:12 +00:00
ctx , cancel := context . WithCancel ( ctx )
2022-06-14 07:20:13 +00:00
2022-08-08 21:50:12 +00:00
defer cancel ( )
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
g , ctx := errgroup . WithContext ( ctx )
var (
mainServer , metricsServer * fasthttp . Server
mainListener , metricsListener net . Listener
)
2022-06-14 07:20:13 +00:00
2022-08-08 21:50:12 +00:00
g . Go ( func ( ) ( err error ) {
defer func ( ) {
2022-09-04 12:26:03 +00:00
if r := recover ( ) ; r != nil {
log . WithError ( recoverErr ( r ) ) . Errorf ( "Critical error in server caught (recovered)" )
2022-08-08 21:50:12 +00:00
}
} ( )
if mainServer , mainListener , err = server . CreateDefaultServer ( * config , providers ) ; err != nil {
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-09-04 12:26:03 +00:00
log . WithError ( err ) . Error ( "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
return nil
} )
2022-06-14 07:20:13 +00:00
2022-08-08 21:50:12 +00:00
g . Go ( func ( ) ( err error ) {
if providers . Metrics == nil {
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 {
log . WithError ( recoverErr ( r ) ) . Errorf ( "Critical error in metrics server caught (recovered)" )
2022-08-08 21:50:12 +00:00
}
} ( )
if metricsServer , metricsListener , err = server . CreateMetricsServer ( config . Telemetry . Metrics ) ; err != nil {
return err
}
if err = metricsServer . Serve ( metricsListener ) ; err != nil {
2022-09-04 12:26:03 +00:00
log . WithError ( err ) . Error ( "Server (metrics) returned error" )
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-10-17 11:31:23 +00:00
if config . AuthenticationBackend . File != nil && config . AuthenticationBackend . File . Watch {
provider := providers . UserProvider . ( * authentication . FileUserProvider )
if watcher , err := runServiceFileWatcher ( g , log , config . AuthenticationBackend . File . Path , provider ) ; err != nil {
log . WithError ( err ) . Errorf ( "Error opening file watcher" )
} else {
defer watcher . Close ( )
}
}
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 :
log . Debugf ( "Shutdown started due to SIGINT" )
case syscall . SIGQUIT :
log . Debugf ( "Shutdown started due to SIGQUIT" )
}
2022-08-08 21:50:12 +00:00
case <- ctx . Done ( ) :
2022-09-04 12:26:03 +00:00
log . Debugf ( "Shutdown started due to context completion" )
2022-06-14 07:20:13 +00:00
}
2022-08-08 21:50:12 +00:00
cancel ( )
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 {
log . WithError ( err ) . Errorf ( "Error occurred shutting down the server" )
}
2022-08-08 21:50:12 +00:00
}
if metricsServer != nil {
if err = metricsServer . Shutdown ( ) ; err != nil {
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-10-25 05:12:42 +00:00
if err = providers . StorageProvider . Close ( ) ; err != nil {
log . WithError ( err ) . Errorf ( "Error occurred closing the database connection" )
}
2022-08-08 21:50:12 +00:00
if err = g . Wait ( ) ; err != nil {
log . WithError ( err ) . Errorf ( "Error occurred waiting for shutdown" )
}
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 )
}
func runServiceFileWatcher ( g * errgroup . Group , log * logrus . Logger , path string , reload ProviderReload ) ( watcher * fsnotify . Watcher , err error ) {
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 )
}
g . Go ( func ( ) error {
for {
select {
case <- failed :
return nil
case event , ok := <- watcher . Events :
if ! ok {
return nil
}
if filename != filepath . Base ( event . Name ) {
log . WithField ( "file" , event . Name ) . WithField ( "op" , event . Op ) . Tracef ( "File modification detected to irrelevant file" )
break
}
switch {
case event . Op & fsnotify . Write == fsnotify . Write , event . Op & fsnotify . Create == fsnotify . Create :
log . WithField ( "file" , event . Name ) . WithField ( "op" , event . Op ) . Debug ( "File modification detected" )
switch reloaded , err := reload . Reload ( ) ; {
case err != nil :
log . WithField ( "file" , event . Name ) . WithField ( "op" , event . Op ) . WithError ( err ) . Error ( "Error occurred reloading file" )
case reloaded :
log . WithField ( "file" , event . Name ) . Info ( "Reloaded file successfully" )
default :
log . WithField ( "file" , event . Name ) . Debug ( "Reload of file was triggered but it was skipped" )
}
case event . Op & fsnotify . Remove == fsnotify . Remove :
log . WithField ( "file" , event . Name ) . WithField ( "op" , event . Op ) . Debug ( "Remove of file was detected" )
}
case err , ok := <- watcher . Errors :
if ! ok {
return nil
}
log . WithError ( err ) . Errorf ( "Error while watching files" )
}
}
} )
if err := watcher . Add ( directory ) ; err != nil {
failed <- struct { } { }
return nil , err
}
log . WithField ( "directory" , directory ) . WithField ( "file" , filename ) . Debug ( "Directory is being watched for changes to the file" )
return watcher , nil
}
2022-08-08 21:50:12 +00:00
func doStartupChecks ( config * schema . Configuration , providers * middlewares . Providers , log * logrus . Logger ) {
2021-09-17 09:53:59 +00:00
var (
failures [ ] string
err error
)
2022-08-08 21:50:12 +00:00
if err = doStartupCheck ( log , "storage" , providers . StorageProvider , false ) ; err != nil {
log . Errorf ( "Failure running the storage provider startup check: %+v" , err )
2021-11-23 09:45:38 +00:00
failures = append ( failures , "storage" )
}
2022-08-08 21:50:12 +00:00
if err = doStartupCheck ( log , "user" , providers . UserProvider , false ) ; err != nil {
log . Errorf ( "Failure running the user provider startup check: %+v" , err )
2021-09-17 09:53:59 +00:00
failures = append ( failures , "user" )
}
2022-08-08 21:50:12 +00:00
if err = doStartupCheck ( log , "notification" , providers . Notifier , config . Notifier . DisableStartupCheck ) ; err != nil {
log . Errorf ( "Failure running the notification provider startup check: %+v" , err )
2021-09-17 09:53:59 +00:00
failures = append ( failures , "notification" )
}
if ! config . NTP . DisableStartupCheck && ! providers . Authorizer . IsSecondFactorEnabled ( ) {
2022-08-08 21:50:12 +00:00
log . Debug ( "The NTP startup check was skipped due to there being no configured 2FA access control rules" )
} else if err = doStartupCheck ( log , "ntp" , providers . NTP , config . NTP . DisableStartupCheck ) ; err != nil {
log . Errorf ( "Failure running the ntp provider startup check: %+v" , err )
2021-09-17 09:53:59 +00:00
2022-02-03 03:04:24 +00:00
if ! config . NTP . DisableFailure {
failures = append ( failures , "ntp" )
}
2021-09-17 09:53:59 +00:00
}
if len ( failures ) != 0 {
2022-08-08 21:50:12 +00:00
log . Fatalf ( "The following providers had fatal failures during startup: %s" , strings . Join ( failures , ", " ) )
2021-09-17 09:53:59 +00:00
}
}
2022-03-06 05:47:40 +00:00
func doStartupCheck ( logger * logrus . Logger , name string , provider model . StartupCheck , disabled bool ) error {
2021-09-17 09:53:59 +00:00
if disabled {
logger . Debugf ( "%s provider: startup check skipped as it is disabled" , name )
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
}