summaryrefslogtreecommitdiff
path: root/cmd
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
parentfea07831eadd35532055ec16fc43b0cde56a54b1 (diff)
downloadwebsocket_proxy-4e995f9e6c3adc43a361b6fa9b976d25378f1594.tar.bz2
websocket_proxy-4e995f9e6c3adc43a361b6fa9b976d25378f1594.tar.xz
websocket_proxy-4e995f9e6c3adc43a361b6fa9b976d25378f1594.zip
Initial import of rewrite
Diffstat (limited to 'cmd')
-rw-r--r--cmd/client/client.go226
-rw-r--r--cmd/client/oauth2.go158
-rw-r--r--cmd/register/register.go71
-rw-r--r--cmd/web/server.go257
4 files changed, 712 insertions, 0 deletions
diff --git a/cmd/client/client.go b/cmd/client/client.go
new file mode 100644
index 0000000..62f1f48
--- /dev/null
+++ b/cmd/client/client.go
@@ -0,0 +1,226 @@
1package client
2
3import (
4 "bytes"
5 "context"
6 "crypto/ed25519"
7 "crypto/rand"
8 "fmt"
9 "io"
10 "log"
11 "net"
12 "net/http"
13 "os"
14
15 "code.crute.us/mcrute/ssh-proxy/app"
16 "code.crute.us/mcrute/ssh-proxy/proxy"
17 "golang.org/x/crypto/ssh"
18 "golang.org/x/crypto/ssh/agent"
19
20 "code.crute.us/mcrute/golib/cli"
21 "github.com/gorilla/websocket"
22 "github.com/mdp/qrterminal"
23 "github.com/spf13/cobra"
24)
25
26// This should be compiled into the binary
27var clientId string
28
29func Register(root *cobra.Command) {
30 clientCmd := &cobra.Command{
31 Use: "client proxy-host ssh-to-host ssh-port username",
32 Short: "Run websocket client",
33 Args: cobra.ExactArgs(4),
34 Run: func(c *cobra.Command, args []string) {
35 cfg := app.Config{}
36 cli.MustGetConfig(c, &cfg)
37 clientMain(cfg, args[0], args[1], args[2], args[3])
38 },
39 }
40 cli.AddFlags(clientCmd, &app.Config{}, app.DefaultConfig, "client")
41 root.AddCommand(clientCmd)
42}
43
44func generateCertificateRequest(username, host string) (ed25519.PrivateKey, []byte, error) {
45 pub, priv, err := ed25519.GenerateKey(rand.Reader)
46 if err != nil {
47 return nil, nil, err
48 }
49
50 pubKey, err := ssh.NewPublicKey(pub)
51 if err != nil {
52 return nil, nil, err
53 }
54
55 cert := &ssh.Certificate{
56 Key: pubKey,
57 CertType: ssh.UserCert,
58 ValidPrincipals: []string{username},
59 Permissions: ssh.Permissions{
60 Extensions: map[string]string{
61 // Used for CA policy checks, removed by the CA server
62 // Server supports a comma separated list without spaces
63 "allowed-hosts": host,
64 },
65 },
66 }
67
68 signer, err := ssh.NewSignerFromKey(priv)
69 if err != nil {
70 return nil, nil, err
71 }
72
73 // Signatures are required to un/marshal to ASCII. The server will
74 // discard this anyhow and replace it with its own signature.
75 if err := cert.SignCert(rand.Reader, signer); err != nil {
76 return nil, nil, err
77 }
78
79 return priv, ssh.MarshalAuthorizedKey(cert), nil
80}
81
82func getCertificateFromCA(ctx context.Context, oauthToken string, certRequest []byte, host string) (*ssh.Certificate, error) {
83 req, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("https://%s/ca/issue", host), bytes.NewReader(certRequest))
84 if err != nil {
85 return nil, err
86 }
87
88 req.Header.Add("Content-Type", "application/x-ssh-certificate")
89 req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", oauthToken))
90
91 resp, err := http.DefaultClient.Do(req)
92 if err != nil {
93 return nil, err
94 }
95
96 res, err := io.ReadAll(resp.Body)
97 if err != nil {
98 return nil, err
99 }
100 defer resp.Body.Close()
101
102 if resp.StatusCode != http.StatusOK {
103 return nil, fmt.Errorf("CA returned error: %s", res)
104 }
105
106 pubkey, _, _, _, err := ssh.ParseAuthorizedKey(res)
107 if err != nil {
108 return nil, err
109 }
110
111 cert, ok := pubkey.(*ssh.Certificate)
112 if !ok {
113 return nil, fmt.Errorf("Parsed certificate is of incorrect type")
114 }
115
116 return cert, nil
117}
118
119func addCertificateToAgent(private any, cert *ssh.Certificate) error {
120 socket := os.Getenv("SSH_AUTH_SOCK")
121 conn, err := net.Dial("unix", socket)
122 if err != nil {
123 return err
124 }
125
126 agentConn := agent.NewClient(conn)
127
128 return agentConn.Add(agent.AddedKey{
129 PrivateKey: private,
130 Certificate: cert,
131 LifetimeSecs: 10,
132 })
133}
134
135func dialProxyHost(ctx context.Context, oauthToken, proxyHost, host, port string) (io.ReadWriteCloser, error) {
136 addr := fmt.Sprintf("wss://%s/proxy-to/%s/%s", proxyHost, host, port)
137
138 hdr := http.Header{}
139 hdr.Add("Authorization", fmt.Sprintf("Bearer %s", oauthToken))
140
141 conn, _, err := websocket.DefaultDialer.DialContext(ctx, addr, hdr)
142 if err != nil {
143 return nil, err
144 }
145
146 return &proxy.WebsocketReadWriter{W: conn}, nil
147}
148
149func fetchOauthToken(ctx context.Context, clientId, proxyHost string) (string, error) {
150 client := &Oauth2PKCEDeviceClient{
151 Host: proxyHost,
152 ClientId: clientId,
153 Scope: "ssh:proxy ca:issue",
154 }
155
156 authResponse, err := client.Authorize(ctx)
157 if err != nil {
158 return "", err
159 }
160
161 fmt.Fprintf(os.Stderr,
162 "To authenticate, please visit: \n\n\t%s \n\nEnter code: %s\n\n",
163 authResponse.VerificationUri, authResponse.UserCode)
164
165 if authResponse.VerificationUriComplete != "" {
166 qrterminal.GenerateWithConfig(authResponse.VerificationUriComplete, qrterminal.Config{
167 Level: qrterminal.M,
168 Writer: os.Stderr,
169 BlackChar: "\033[7m \033[0m", // White
170 WhiteChar: "\033[0m \033[0m", // Black
171 QuietZone: 1,
172 })
173 fmt.Fprintf(os.Stderr, "\n")
174 }
175
176 tokenResponse, err := client.AwaitToken(ctx, authResponse.DeviceCode)
177 if err != nil {
178 return "", err
179 }
180
181 return tokenResponse.AccessToken, nil
182}
183
184func clientMain(cfg app.Config, proxyHost, host, port, username string) {
185 log.SetOutput(os.Stderr)
186
187 ctx, cancel := context.WithCancel(context.Background())
188 defer cancel()
189
190 oauthToken, err := fetchOauthToken(ctx, clientId, proxyHost)
191 if err != nil {
192 log.Fatalf("Error fetching oauth token: %s", err)
193 }
194
195 privateKey, certRequest, err := generateCertificateRequest(username, host)
196 if err != nil {
197 log.Fatalf("Error generating certificate request: %s", err)
198 }
199
200 certificate, err := getCertificateFromCA(ctx, oauthToken, certRequest, proxyHost)
201 if err != nil {
202 log.Fatalf("Error fetching certificate: %s", err)
203 }
204
205 if err := addCertificateToAgent(privateKey, certificate); err != nil {
206 log.Fatalf("Error adding certificate to agent: %s", err)
207 }
208
209 ws, err := dialProxyHost(ctx, oauthToken, proxyHost, host, port)
210 if err != nil {
211 log.Fatalf("Error dialing proxy host: %s", err)
212 }
213 defer ws.Close()
214
215 errc := make(chan error)
216
217 go proxy.CopyWithErrors(os.Stdout, ws, errc)
218 go proxy.CopyWithErrors(ws, os.Stdin, errc)
219
220 err = <-errc
221 if err != nil {
222 log.Printf("Closing client connection: %s", <-errc)
223 } else {
224 log.Printf("Closing client connection")
225 }
226}
diff --git a/cmd/client/oauth2.go b/cmd/client/oauth2.go
new file mode 100644
index 0000000..6667c5a
--- /dev/null
+++ b/cmd/client/oauth2.go
@@ -0,0 +1,158 @@
1package client
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "net/http"
8 "strings"
9 "time"
10
11 "code.crute.us/mcrute/ssh-proxy/app/models"
12
13 "github.com/google/go-querystring/query"
14)
15
16// Oauth2PKCEDeviceClient is not safe for concurrent use and should be
17// created anew for each request.
18type Oauth2PKCEDeviceClient struct {
19 Host string
20 ClientId string
21 Scope string
22 pkce *models.PKCEChallenge
23 interval time.Duration
24}
25
26func (c *Oauth2PKCEDeviceClient) Authorize(ctx context.Context) (*models.DeviceAuthorizationResponse, error) {
27 challenge, err := models.NewPKCEChallenge()
28 if err != nil {
29 return nil, err
30 }
31 c.pkce = challenge
32
33 values, err := query.Values(models.AuthorizationRequest{
34 Challenge: c.pkce.Challenge(),
35 ChallengeMethod: models.ChallengeS256,
36 ClientId: c.ClientId,
37 Scope: c.Scope,
38 })
39 if err != nil {
40 return nil, err
41 }
42
43 url := fmt.Sprintf("https://%s/auth/device", c.Host)
44 req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, strings.NewReader(values.Encode()))
45 if err != nil {
46 return nil, err
47 }
48 req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
49
50 res, err := http.DefaultClient.Do(req)
51 if err != nil {
52 return nil, err
53 }
54 defer res.Body.Close()
55
56 if res.StatusCode != 200 {
57 var resError models.Oauth2Error
58 if err := json.NewDecoder(res.Body).Decode(&resError); err != nil {
59 return nil, err
60 }
61 return nil, resError
62 }
63
64 var resp models.DeviceAuthorizationResponse
65 if err := json.NewDecoder(res.Body).Decode(&resp); err != nil {
66 return nil, err
67 }
68
69 c.interval = time.Duration(resp.Interval) * time.Second
70 if c.interval == 0 {
71 c.interval = 5 * time.Second
72 }
73
74 return &resp, nil
75}
76
77func (c *Oauth2PKCEDeviceClient) fetchToken(ctx context.Context, deviceCode string) (*models.AccessTokenResponse, error) {
78 values, err := query.Values(models.DeviceAccessTokenRequest{
79 GrantType: models.DEVICE_CODE_GRANT_TYPE,
80 DeviceCode: deviceCode,
81 ClientId: c.ClientId,
82 CodeVerifier: c.pkce.Verifier,
83 })
84 if err != nil {
85 return nil, err
86 }
87
88 url := fmt.Sprintf("https://%s/auth/token", c.Host)
89 req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, strings.NewReader(values.Encode()))
90 if err != nil {
91 return nil, err
92 }
93 req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
94
95 res, err := http.DefaultClient.Do(req)
96 if err != nil {
97 return nil, err
98 }
99 defer res.Body.Close()
100
101 if res.StatusCode != 200 {
102 var resError models.Oauth2Error
103 if err := json.NewDecoder(res.Body).Decode(&resError); err != nil {
104 return nil, err
105 }
106
107 if resError.Type == models.ErrSlowDown {
108 c.interval += 5 * time.Second
109 }
110
111 return nil, resError
112 }
113
114 var resp models.AccessTokenResponse
115 if err := json.NewDecoder(res.Body).Decode(&resp); err != nil {
116 return nil, err
117 }
118
119 return &resp, nil
120}
121
122func (c *Oauth2PKCEDeviceClient) AwaitToken(ctx context.Context, deviceCode string) (*models.AccessTokenResponse, error) {
123 t := time.NewTicker(c.interval)
124 defer t.Stop()
125
126 res, err := c.fetchToken(ctx, deviceCode)
127 if err == nil {
128 return res, nil
129 } else if e, ok := err.(models.Oauth2Error); ok {
130 if e.Type == models.ErrSlowDown {
131 t.Reset(c.interval)
132 } else if e.Type != models.ErrAuthorizationPending {
133 return nil, err
134 }
135 } else {
136 return nil, err
137 }
138
139 for {
140 select {
141 case <-t.C:
142 res, err := c.fetchToken(ctx, deviceCode)
143 if err == nil {
144 return res, nil
145 } else if e, ok := err.(models.Oauth2Error); ok {
146 if e.Type == models.ErrSlowDown {
147 t.Reset(c.interval)
148 } else if e.Type != models.ErrAuthorizationPending {
149 return nil, err
150 }
151 } else {
152 return nil, err
153 }
154 case <-ctx.Done():
155 return nil, fmt.Errorf("Context has expired")
156 }
157 }
158}
diff --git a/cmd/register/register.go b/cmd/register/register.go
new file mode 100644
index 0000000..fdd083c
--- /dev/null
+++ b/cmd/register/register.go
@@ -0,0 +1,71 @@
1package register
2
3import (
4 "context"
5 "log"
6 "time"
7
8 "code.crute.us/mcrute/golib/cli"
9 "code.crute.us/mcrute/golib/db/mongodb/v2"
10 glecho "code.crute.us/mcrute/golib/echo"
11 "code.crute.us/mcrute/ssh-proxy/app"
12 "code.crute.us/mcrute/ssh-proxy/app/models"
13 "code.crute.us/mcrute/ssh-proxy/db"
14 "github.com/spf13/cobra"
15)
16
17func Register(root *cobra.Command) {
18 registerCmd := &cobra.Command{
19 Use: "register username",
20 Short: "Create registration invite for user",
21 Args: cobra.ExactArgs(1),
22 Run: func(c *cobra.Command, args []string) {
23 cfg := app.Config{}
24 cli.MustGetConfig(c, &cfg)
25 registerMain(cfg, args[0])
26 },
27 }
28 cli.AddFlags(registerCmd, &app.Config{}, app.DefaultConfig, "register")
29 root.AddCommand(registerCmd)
30}
31
32func registerMain(cfg app.Config, username string) {
33 ctx, cancel := context.WithCancel(context.Background())
34 defer cancel()
35
36 vc, err := glecho.MakeVaultSecretsClient(ctx)
37 if err != nil {
38 log.Fatalf("Error making vault client %s", err)
39 }
40
41 mongo, err := mongodb.Connect(ctx, cfg.MongoDbUri, vc)
42 if err != nil {
43 log.Fatalf("Error connecting to mongodb: %s", err)
44 }
45
46 userStore := &db.MongoDbBasicStore[*models.User]{
47 Db: mongo,
48 CollectionName: "users",
49 }
50
51 authSessionStore := &models.AuthSessionStoreMongodb{
52 MongoDbBasicStore: &db.MongoDbBasicStore[*models.AuthSession]{
53 Db: mongo,
54 CollectionName: "oauth_sessions",
55 },
56 }
57
58 if _, err := userStore.Get(ctx, username); err != nil {
59 log.Fatalf("User %s does not exist", username)
60 }
61
62 authSession := models.NewAuthSession("invite-only", time.Now().Add(cfg.InviteTimeout))
63 authSession.IsRegistration = true
64 authSession.UserId = username
65
66 if err := authSessionStore.Upsert(ctx, authSession); err != nil {
67 log.Fatalf("Error inserting registration: %s", err)
68 }
69
70 log.Printf("Invitation created, user code is: %s", authSession.UserCode)
71}
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}