...

Source file src/edge-infra.dev/pkg/sds/remoteaccess/authserver/auth_server.go

Documentation: edge-infra.dev/pkg/sds/remoteaccess/authserver

     1  package authserver
     2  
     3  import (
     4  	"database/sql"
     5  	"encoding/gob"
     6  	"errors"
     7  	"flag"
     8  	"net/http"
     9  	"os"
    10  	"time"
    11  
    12  	"github.com/gin-contrib/cors"
    13  	"github.com/gin-contrib/requestid"
    14  	"github.com/gin-contrib/sessions"
    15  	"github.com/gin-contrib/sessions/postgres"
    16  	"github.com/gin-gonic/gin"
    17  	"github.com/go-logr/logr"
    18  	"github.com/peterbourgon/ff/v3"
    19  
    20  	"edge-infra.dev/pkg/edge/api/middleware"
    21  	authproxy "edge-infra.dev/pkg/edge/auth-proxy/types"
    22  )
    23  
    24  var (
    25  	// sessionIdentifier identifier string for the session.
    26  	sessionIdentifier = "edge-session"
    27  	bannerHeaderName  = "Banner"
    28  	// sessionDurationMinutes is the session max time duration. This wouldn't be used here because the authserver does not create or update sessions.
    29  	sessionDurationMinutes = 15
    30  
    31  	// cookie security config
    32  	// https://owasp.org/www-project-web-security-testing-guide/v41/4-Web_Application_Security_Testing/06-Session_Management_Testing/02-Testing_for_Cookies_Attributes#:~:text=Strong%20Practices
    33  	sessionOptions = sessions.Options{
    34  		Secure:   true,
    35  		HttpOnly: true,
    36  		MaxAge:   sessionDurationMinutes * 60,
    37  		SameSite: http.SameSiteStrictMode,
    38  		Path:     "/",
    39  	}
    40  
    41  	// ErrSessionExpired is an error returned when the session exists but has expired.
    42  	ErrSessionExpired = errors.New("access denied, session expired")
    43  	// ErrSessionHasNoExpiration is an error returned when session exists but the expiration is empty (probably would never happen).
    44  	ErrSessionHasNoExpiration = errors.New("an error occurred, session has no expiration")
    45  	// ErrInvalidCredentials is an error returned when no session is present.
    46  	ErrInvalidCredentials = errors.New("an error occurred, invalid credentials provided")
    47  )
    48  
    49  type AuthServer struct {
    50  	GinEngine              *gin.Engine
    51  	Log                    logr.Logger
    52  	GinMode                string
    53  	databaseHost           string
    54  	databaseConnectionName string
    55  	databaseName           string
    56  	databaseUsername       string
    57  	databasePassword       string
    58  	databasePort           string
    59  	sessionSecret          string
    60  	db                     *sql.DB
    61  	checks                 []check
    62  }
    63  
    64  func init() {
    65  	// register other types for cookie codec
    66  	gob.Register(time.Time{})
    67  }
    68  
    69  func NewAuthServer(args []string, router *gin.Engine) (*AuthServer, error) {
    70  	authServer := AuthServer{
    71  		GinEngine: router,
    72  		checks:    allChecks,
    73  	}
    74  	fs := flag.NewFlagSet("authserver", flag.ExitOnError)
    75  	fs.StringVar(
    76  		&authServer.GinMode,
    77  		"gin-mode",
    78  		gin.ReleaseMode,
    79  		"gin release mode (debug or release)",
    80  	)
    81  
    82  	fs.StringVar(&authServer.databaseHost,
    83  		"database-host",
    84  		"",
    85  		"Database Host",
    86  	)
    87  
    88  	fs.StringVar(&authServer.databaseConnectionName,
    89  		"database-connection-name",
    90  		"",
    91  		"Database Connection Name",
    92  	)
    93  
    94  	fs.StringVar(&authServer.databaseName,
    95  		"database-name",
    96  		"",
    97  		"Database Name",
    98  	)
    99  
   100  	fs.StringVar(&authServer.databaseUsername,
   101  		"database-username",
   102  		"",
   103  		"Database User Name",
   104  	)
   105  
   106  	fs.StringVar(&authServer.databasePassword,
   107  		"database-password",
   108  		"",
   109  		"Database Password",
   110  	)
   111  
   112  	fs.StringVar(&authServer.databasePort,
   113  		"database-port",
   114  		"",
   115  		"Database Port",
   116  	)
   117  
   118  	fs.StringVar(&authServer.sessionSecret,
   119  		"session-secret",
   120  		"",
   121  		"Session Secret",
   122  	)
   123  
   124  	if err := ff.Parse(fs, args[1:], ff.WithEnvVarNoPrefix()); err != nil {
   125  		return nil, err
   126  	}
   127  
   128  	gin.SetMode(authServer.GinMode)
   129  	if err := authServer.newGinServer(); err != nil {
   130  		return nil, err
   131  	}
   132  
   133  	// set logger
   134  	newLogger(&authServer)
   135  
   136  	return &authServer, nil
   137  }
   138  
   139  func (as *AuthServer) newGinServer() error {
   140  	router := as.GinEngine
   141  
   142  	useMetrics(router)
   143  
   144  	// Create contextual id at beginning of request
   145  	router.Use(middleware.SetRequestContext())
   146  	// Http logging
   147  	router.Use(requestid.New())
   148  	router.Use(gin.Recovery())
   149  
   150  	router.Any("/health", func(c *gin.Context) {
   151  		c.String(http.StatusOK, "ok")
   152  	})
   153  
   154  	router.Any("/ready", func(c *gin.Context) {
   155  		c.String(http.StatusOK, "ok")
   156  	})
   157  
   158  	router.NoRoute(as.handleAuthRequest())
   159  
   160  	if err := as.createSessionStore(router); err != nil {
   161  		return err
   162  	}
   163  
   164  	corsConfig := cors.DefaultConfig()
   165  	corsConfig.AllowAllOrigins = true
   166  	corsConfig.AllowHeaders = []string{"*"}
   167  	router.Use(cors.New(corsConfig))
   168  	return nil
   169  }
   170  
   171  func Run() error {
   172  	ginEngine := gin.New()
   173  	authServer, err := NewAuthServer(os.Args, ginEngine)
   174  	if err != nil {
   175  		return err
   176  	}
   177  	authServer.Log.Info("starting auth server")
   178  	return authServer.GinEngine.Run()
   179  }
   180  
   181  func (as *AuthServer) createSessionStore(ginEngine *gin.Engine) error {
   182  	db, err := as.connectDatabase()
   183  	if err != nil {
   184  		return err
   185  	}
   186  	as.db = db
   187  	store, err := postgres.NewStore(db, []byte(as.sessionSecret))
   188  	if err != nil {
   189  		return err
   190  	}
   191  	store.Options(sessionOptions)
   192  	session := sessions.Sessions(sessionIdentifier, store)
   193  	ginEngine.Use(session)
   194  	return nil
   195  }
   196  
   197  func (as *AuthServer) handleAuthRequest() gin.HandlerFunc {
   198  	fn := func(ctx *gin.Context) {
   199  		startTime := time.Now().UTC()
   200  		session := sessions.Default(ctx)
   201  		auth := session.Get(authproxy.SessionIDField)
   202  		switch auth {
   203  		case nil:
   204  			as.handleNotAuthenticated(ctx, startTime)
   205  		default:
   206  			as.handleAuthenticated(ctx, startTime, session)
   207  		}
   208  	}
   209  	return fn
   210  }
   211  

View as plain text