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
26 sessionIdentifier = "edge-session"
27 bannerHeaderName = "Banner"
28
29 sessionDurationMinutes = 15
30
31
32
33 sessionOptions = sessions.Options{
34 Secure: true,
35 HttpOnly: true,
36 MaxAge: sessionDurationMinutes * 60,
37 SameSite: http.SameSiteStrictMode,
38 Path: "/",
39 }
40
41
42 ErrSessionExpired = errors.New("access denied, session expired")
43
44 ErrSessionHasNoExpiration = errors.New("an error occurred, session has no expiration")
45
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
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
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
145 router.Use(middleware.SetRequestContext())
146
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