summaryrefslogtreecommitdiff
path: root/cmd/web/server.go
diff options
context:
space:
mode:
authorMike Crute <mike@crute.us>2023-07-29 12:15:13 -0700
committerMike Crute <mike@crute.us>2023-07-29 12:15:13 -0700
commit4e995f9e6c3adc43a361b6fa9b976d25378f1594 (patch)
tree862642149583fa4ad662edfe0b31a7d65b8e302e /cmd/web/server.go
parentfea07831eadd35532055ec16fc43b0cde56a54b1 (diff)
downloadwebsocket_proxy-4e995f9e6c3adc43a361b6fa9b976d25378f1594.tar.bz2
websocket_proxy-4e995f9e6c3adc43a361b6fa9b976d25378f1594.tar.xz
websocket_proxy-4e995f9e6c3adc43a361b6fa9b976d25378f1594.zip
Initial import of rewrite
Diffstat (limited to 'cmd/web/server.go')
-rw-r--r--cmd/web/server.go257
1 files changed, 257 insertions, 0 deletions
diff --git a/cmd/web/server.go b/cmd/web/server.go
new file mode 100644
index 0000000..6eb585a
--- /dev/null
+++ b/cmd/web/server.go
@@ -0,0 +1,257 @@
1package web
2
3import (
4 "context"
5 "fmt"
6 "io/fs"
7 "log"
8 "os"
9 "strings"
10 "text/template"
11 "time"
12
13 "code.crute.us/mcrute/ssh-proxy/app"
14 "code.crute.us/mcrute/ssh-proxy/app/controllers"
15 "code.crute.us/mcrute/ssh-proxy/app/middleware"
16 "code.crute.us/mcrute/ssh-proxy/app/models"
17 "code.crute.us/mcrute/ssh-proxy/db"
18
19 "code.crute.us/mcrute/golib/cli"
20 "code.crute.us/mcrute/golib/clients/autocert/v2"
21 "code.crute.us/mcrute/golib/clients/netbox/v3"
22 "code.crute.us/mcrute/golib/db/mongodb/v2"
23 glecho "code.crute.us/mcrute/golib/echo"
24 glcontroller "code.crute.us/mcrute/golib/echo/controller"
25 glmiddleware "code.crute.us/mcrute/golib/echo/middleware"
26 "code.crute.us/mcrute/golib/echo/session"
27 "code.crute.us/mcrute/golib/secrets"
28
29 "github.com/go-webauthn/webauthn/webauthn"
30 "github.com/gorilla/websocket"
31 "github.com/labstack/echo/v4"
32 "github.com/spf13/cobra"
33)
34
35func Register(root *cobra.Command, embeddedTemplates fs.FS, appVersion string) {
36 webCmd := &cobra.Command{
37 Use: "web [options]",
38 Short: "Run web server",
39 Run: func(c *cobra.Command, args []string) {
40 cfg := app.Config{}
41 cli.MustGetConfig(c, &cfg)
42 webMain(cfg, embeddedTemplates, appVersion)
43 },
44 }
45 cli.AddFlags(webCmd, &app.Config{}, app.DefaultConfig, "web")
46 root.AddCommand(webCmd)
47}
48
49func PopulateTemplateContext(c echo.Context) (interface{}, error) {
50 // May not be set if we're being called from something other than
51 // the generic template controller, which can happen in the order
52 // redirect controller.
53 cp, _ := c.Get("CanonicalPath").(string)
54
55 return &app.PageContext{
56 PageName: strings.SplitN(cp, ".", 2)[0],
57 Year: time.Now().Year(),
58 RenderTime: time.Now().Format(time.RFC1123),
59 Flags: glcontroller.NewFeatureFlags(),
60 CSRFToken: glmiddleware.GetCSRFToken(c),
61 Context: glcontroller.NewPageContext(),
62 }, nil
63}
64
65func webMain(cfg app.Config, embeddedTemplates fs.FS, appVersion string) {
66 ctx, cancel := context.WithCancel(context.Background())
67 defer cancel()
68
69 gt := &glcontroller.GenericTemplateHandler{Render: PopulateTemplateContext}
70
71 s, err := glecho.NewEchoWrapper(ctx, cfg.Debug)
72 if err != nil {
73 log.Fatalf("Error building echo: %s", err)
74 }
75
76 vc, err := glecho.MakeVaultSecretsClient(ctx)
77 if err != nil {
78 log.Fatalf("Error making vault client %s", err)
79 }
80
81 if err = s.Configure(glecho.EchoConfig{
82 ApplicationName: "app-server",
83 ApplicationVersion: appVersion,
84 BindAddresses: cfg.Bind,
85 DiskTemplates: os.DirFS("templates/"),
86 EmbeddedTemplates: embeddedTemplates,
87 RedirectToWWW: false,
88 TrustedProxyIPRanges: cfg.TrustedIPRanges,
89 ContentSecurityPolicy: &glmiddleware.ContentSecurityPolicyConfig{
90 DefaultSrc: []glmiddleware.CSPDirective{
91 glmiddleware.CSPSelf,
92 glmiddleware.CSPUnsafeInline,
93 },
94 },
95 TemplateFunctions: template.FuncMap{
96 "cacheBustUrl": gt.TmplMakeCacheBustUrl,
97 },
98 Autocert: autocert.MustNewAutocertWrapper(ctx, autocert.AutocertConfig{
99 ApiKey: secrets.MustGetApiKey(vc, ctx, cfg.DNSApiKeyVaultPath).Key,
100 Hosts: cfg.Hostnames,
101 Email: cfg.AutocertEmail,
102 CertHost: cfg.AutocertHost,
103 }),
104 NetboxClient: &netbox.BasicNetboxClient{
105 Endpoint: cfg.NetboxHost,
106 ApiKey: secrets.MustGetApiKey(vc, ctx, cfg.NetboxApiKeyVaultPath).Key,
107 },
108 }); err != nil {
109 log.Fatalf("Error configuring echo: %s", err)
110 }
111
112 glecho.AttachSecretsClient(vc, cancel, s.Runner(), s.Logger)
113
114 mongo, err := mongodb.Connect(ctx, cfg.MongoDbUri, vc)
115 if err != nil {
116 log.Fatalf("Error connecting to mongodb: %s", err)
117 }
118
119 cookieKey := secrets.MustGetRSAKey(vc, ctx, cfg.CookieKeyPath)
120 pk, err := cookieKey.RSAPrivateKey()
121 if err != nil {
122 log.Fatalf("Error fetching cookie key from vault: %s", err)
123 }
124
125 ss, err := session.NewCookieStore[*app.Session](pk, app.NewSession)
126 if err != nil {
127 log.Fatalf("Error creating session store: %s", err)
128 }
129
130 userStore := &db.MongoDbBasicStore[*models.User]{
131 Db: mongo,
132 CollectionName: "users",
133 }
134
135 oauthClientStore := &db.MongoDbBasicStore[*models.OauthClient]{
136 Db: mongo,
137 CollectionName: "oauth_clients",
138 }
139
140 authSessionStore := &models.AuthSessionStoreMongodb{
141 MongoDbBasicStore: &db.MongoDbBasicStore[*models.AuthSession]{
142 Db: mongo,
143 CollectionName: "oauth_sessions",
144 },
145 }
146
147 wauthn, err := webauthn.New(&webauthn.Config{
148 RPDisplayName: cfg.OauthRPName,
149 RPID: cfg.Hostnames[0],
150 RPOrigins: []string{
151 fmt.Sprintf("https://%s:8070", cfg.Hostnames[0]), // TODO: Expose port in echo server for use here
152 },
153 })
154 if err != nil {
155 log.Fatalf("Error constructing webauthn: %s", err)
156 }
157
158 lc := &controllers.LoginController[*app.Session]{
159 Logger: s.Logger,
160 Sessions: ss,
161 Users: userStore,
162 AuthSessions: authSessionStore,
163 Webauthn: wauthn,
164 SessionExpiration: cfg.OauthSessionTimeout,
165 }
166
167 rc := &controllers.RegisterController[*app.Session]{
168 Logger: s.Logger,
169 Sessions: ss,
170 Users: userStore,
171 AuthSessions: authSessionStore,
172 Webauthn: wauthn,
173 }
174
175 o2dc := &controllers.OAuth2DeviceController[*app.Session]{
176 Logger: s.Logger,
177 AuthSessions: authSessionStore,
178 OauthClients: oauthClientStore,
179 Hostname: fmt.Sprintf("https://%s:8070", cfg.Hostnames[0]), // TODO
180 PollSeconds: cfg.OauthDevicePollSecs,
181 SessionExpiration: cfg.OauthSessionTimeout,
182 }
183
184 ph := &controllers.ProxyHandler{
185 Logger: s.Logger,
186 Users: userStore,
187 Upgrader: websocket.Upgrader{
188 ReadBufferSize: 1024,
189 WriteBufferSize: 1024,
190 },
191 }
192
193 caAuthMw := &middleware.TokenAuthMiddleware{
194 Logger: s.Logger,
195 RequiredScope: "ca:issue",
196 AuthSessions: authSessionStore,
197 }
198
199 proxyAuthMw := &middleware.TokenAuthMiddleware{
200 Logger: s.Logger,
201 RequiredScope: "ssh:proxy",
202 AuthSessions: authSessionStore,
203 }
204
205 var caSecret controllers.CASecret
206 if _, err := vc.Secret(ctx, cfg.SSHCAKeyPath, &caSecret); err != nil {
207 log.Fatalf("Error fetching SSH CA secret from Vault: %s", err)
208 }
209
210 ca, err := controllers.NewCAHandler(controllers.CAHandlerConfig{
211 Logger: s.Logger,
212 Users: userStore,
213 Expiration: cfg.SSHCertificateExpiration,
214 Secret: caSecret,
215 })
216 if err != nil {
217 log.Fatalf("Error building CA controller: %s", err)
218 }
219
220 s.Use(session.Middleware(ss))
221
222 csm := glmiddleware.CSRFProtect(ss)
223
224 s.GET("/login", gt.Handle, csm)
225 s.GET("/register", gt.Handle, csm)
226
227 ag := s.Group("/auth")
228 {
229 ag.POST("/device", o2dc.HandleStart)
230 ag.POST("/token", o2dc.HandleToken)
231
232 lg := ag.Group("/login")
233 lg.Use(csm)
234 {
235 lg.GET("/:username", lc.HandleStart)
236 lg.POST("/:username", lc.HandleFinish)
237 }
238
239 rg := ag.Group("/register")
240 rg.Use(csm)
241 {
242 rg.GET("/:username", rc.HandleStart)
243 rg.POST("/:username", rc.HandleFinish)
244 }
245 }
246
247 s.POST("/ca/issue", ca.HandleIssue, caAuthMw.Middleware)
248
249 pg := s.Group("/proxy-to")
250 pg.Use(proxyAuthMw.Middleware)
251 {
252 pg.GET("/:host", ph.Handle)
253 pg.GET("/:host/:port", ph.Handle)
254 }
255
256 s.RunForever(!cfg.DisableBackgroundJobs)
257}