aboutsummaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
authorMike Crute <mike@crute.us>2022-12-21 22:06:29 -0800
committerMike Crute <mike@crute.us>2022-12-21 22:06:29 -0800
commited1504c2826f6a5d406dd72e51f5a90b77ffea45 (patch)
tree159733ceaf63026d36c39117fff6159e6247bed7 /cmd
parente5629fb163c7cf303438afc5be6075299cfc6071 (diff)
downloadcloud-identity-broker-ed1504c2826f6a5d406dd72e51f5a90b77ffea45.tar.bz2
cloud-identity-broker-ed1504c2826f6a5d406dd72e51f5a90b77ffea45.tar.xz
cloud-identity-broker-ed1504c2826f6a5d406dd72e51f5a90b77ffea45.zip
Upgrade to latest golib
Diffstat (limited to 'cmd')
-rw-r--r--cmd/web/server.go119
1 files changed, 67 insertions, 52 deletions
diff --git a/cmd/web/server.go b/cmd/web/server.go
index 610cc72..9c29544 100644
--- a/cmd/web/server.go
+++ b/cmd/web/server.go
@@ -14,59 +14,56 @@ import (
14 "code.crute.us/mcrute/cloud-identity-broker/auth" 14 "code.crute.us/mcrute/cloud-identity-broker/auth"
15 "code.crute.us/mcrute/cloud-identity-broker/auth/github" 15 "code.crute.us/mcrute/cloud-identity-broker/auth/github"
16 16
17 "code.crute.us/mcrute/golib/crypto/tls" 17 "code.crute.us/mcrute/golib/cli"
18 "code.crute.us/mcrute/golib/db/mongodb" 18 "code.crute.us/mcrute/golib/clients/autocert/v2"
19 "code.crute.us/mcrute/golib/clients/netbox/v3"
20 "code.crute.us/mcrute/golib/db/mongodb/v2"
19 glecho "code.crute.us/mcrute/golib/echo" 21 glecho "code.crute.us/mcrute/golib/echo"
20 glmw "code.crute.us/mcrute/golib/echo/middleware" 22 glmw "code.crute.us/mcrute/golib/echo/middleware"
21 "code.crute.us/mcrute/golib/service" 23 "code.crute.us/mcrute/golib/secrets"
22 "github.com/labstack/echo/v4"
23 echomw "github.com/labstack/echo/v4/middleware" 24 echomw "github.com/labstack/echo/v4/middleware"
25
26 "github.com/labstack/echo/v4"
24 "github.com/spf13/cobra" 27 "github.com/spf13/cobra"
25 "golang.org/x/time/rate" 28 "golang.org/x/time/rate"
26) 29)
27 30
28func Register(root *cobra.Command, embeddedTemplates fs.FS, version string) { 31func Register(root *cobra.Command, embeddedTemplates fs.FS, appVersion string) {
29 webCmd := &cobra.Command{ 32 webCmd := &cobra.Command{
30 Use: "web [options]", 33 Use: "web [options]",
31 Short: "Run web server", 34 Short: "Run web server",
32 Run: func(c *cobra.Command, args []string) { 35 Run: func(c *cobra.Command, args []string) {
33 webMain(app.NewConfigFromCmd(c), embeddedTemplates, version) 36 cfg := app.Config{}
37 cli.MustGetConfig(c, &cfg)
38 webMain(cfg, embeddedTemplates, appVersion)
34 }, 39 },
35 } 40 }
36 41 cli.AddFlags(webCmd, &app.Config{}, app.DefaultConfig, "web")
37 webCmd.Flags().StringSlice("bind", []string{":8169"}, "Addresses and ports to bind http server")
38 webCmd.Flags().StringSlice("bind-tls", []string{":8170"}, "Addresses and ports to bind https server")
39 webCmd.Flags().String("log-file", "", "Log file for combined host logs")
40 webCmd.Flags().String("tls-cache-dir", "ssl/", "Cache directory for TLS certificates")
41 webCmd.Flags().StringSlice("trusted-ip-ranges", []string{"172.19.0.0/22", "2602:803:4072::/48"}, "Comma separated list of IP ranges for trusted XFF proxies")
42 webCmd.Flags().StringSlice("management-ip-ranges", []string{"127.0.0.1/32", "::1/128", "172.19.0.0/22", "2602:803:4072::/48"}, "IP ranges for management systems")
43 webCmd.Flags().StringSlice("hostname", []string{"dev.aws-access.crute.us"}, "Hostname this server serves (can be specified multiple times)")
44 webCmd.Flags().Duration("rate-limit", 30*time.Second, "Number seconds between requests for credential resources")
45 webCmd.Flags().Duration("auth-cookie-duration", 24*time.Hour, "Expiration duration of the auth cookies")
46 webCmd.Flags().Int("rate-limit-burst", 30, "Number of burst requests allowed to credential endpoints")
47 webCmd.Flags().String("issuer-endpoint", "https://aws-access.crute.us", "Oauth issuer endpoint")
48 webCmd.Flags().String("jwt-audience", "aws-access", "Audience for issued JWTs")
49
50 webCmd.Flags().String("github-oauth-vault-path", "", "Vault material name for GitHub auth credentials")
51 webCmd.MarkFlagRequired("github-oauth-vault-path")
52
53 root.AddCommand(webCmd) 42 root.AddCommand(webCmd)
54} 43}
55 44
45// webMain does the pretty standard golib/Echo setup
56func webMain(cfg app.Config, embeddedTemplates fs.FS, version string) { 46func webMain(cfg app.Config, embeddedTemplates fs.FS, version string) {
57 ctx := context.Background() 47 ctx, cancel := context.WithCancel(context.Background())
48 defer cancel()
49
50 s, err := glecho.NewEchoWrapper(ctx, cfg.Debug)
51 if err != nil {
52 log.Fatalf("Error building echo: %s", err)
53 }
54
55 vc, err := glecho.MakeVaultSecretsClient(ctx)
56 if err != nil {
57 log.Fatalf("Error making vault client %s", err)
58 }
58 59
59 s, err := glecho.NewDefaultEchoWithConfig(glecho.EchoConfig{ 60 if err = s.Configure(glecho.EchoConfig{
60 Debug: cfg.Debug, 61 ApplicationName: "cloud-identity-broker",
61 Hostnames: cfg.Hostnames, 62 ApplicationVersion: version,
62 BindAddresses: cfg.Bind, 63 BindAddresses: cfg.Bind,
63 BindTLSAddresses: cfg.BindTLS, 64 DiskTemplates: os.DirFS("templates/"),
64 TLSCacheDir: cfg.TLSCacheDir,
65 TrustedProxyIPRanges: cfg.TrustedIPRanges,
66 ManagementIPRanges: cfg.ManagementIPRanges,
67 DiskTemplates: os.DirFS(cfg.TemplatePath),
68 EmbeddedTemplates: embeddedTemplates, 65 EmbeddedTemplates: embeddedTemplates,
69 TemplateGlob: &cfg.TemplateGlob, 66 TrustedProxyIPRanges: cfg.TrustedIPRanges,
70 CombinedHostLogFile: cfg.LogFile, 67 CombinedHostLogFile: cfg.LogFile,
71 ContentSecurityPolicy: &glmw.ContentSecurityPolicyConfig{ 68 ContentSecurityPolicy: &glmw.ContentSecurityPolicyConfig{
72 DefaultSrc: []glmw.CSPDirective{ 69 DefaultSrc: []glmw.CSPDirective{
@@ -79,20 +76,34 @@ func webMain(cfg app.Config, embeddedTemplates fs.FS, version string) {
79 glmw.CSPUnsafeInline, 76 glmw.CSPUnsafeInline,
80 }, 77 },
81 }, 78 },
82 }) 79 Autocert: autocert.MustNewAutocertWrapper(ctx, autocert.AutocertConfig{
83 if err != nil { 80 ApiKey: secrets.MustGetApiKey(vc, ctx, cfg.DNSApiKeyVaultPath).Key,
84 log.Fatalf("Error building echo: %w", err) 81 Hosts: cfg.Hostnames,
85 } 82 Email: cfg.AutocertEmail,
86 if err = s.Init(); err != nil { 83 CertHost: cfg.AutocertHost,
87 log.Fatalf("Error initializing echo: %w", err) 84 }),
85 NetboxClient: &netbox.BasicNetboxClient{
86 Endpoint: cfg.NetboxHost,
87 ApiKey: secrets.MustGetApiKey(vc, ctx, cfg.NetboxApiKeyVaultPath).Key,
88 },
89 }); err != nil {
90 log.Fatalf("Error building echo: %s", err)
88 } 91 }
89 s.Use(middleware.AddServerHeader(version)) 92 glecho.AttachSecretsClient(vc, cancel, s.Runner(), s.Logger)
90 93
91 mongo, err := mongodb.Connect(ctx, cfg.MongoDbUri, cfg.MongodbVaultPath) 94 mongo, err := mongodb.Connect(ctx, cfg.MongoDbUri, vc)
92 if err != nil { 95 if err != nil {
93 log.Fatalf("Error connecting to mongodb: %w", err) 96 log.Fatalf("Error connecting to mongodb: %s", err)
94 } 97 }
95 98
99 setupApplication(ctx, cfg, s, mongo, vc)
100
101 s.RunForever(!cfg.DisableBackgroundJobs)
102}
103
104// setupApplication does the setup work that's specific to this
105// application
106func setupApplication(ctx context.Context, cfg app.Config, s *glecho.EchoWrapper, mongo *mongodb.Mongo, vc secrets.Client) {
96 rateLimit := echomw.RateLimiter( 107 rateLimit := echomw.RateLimiter(
97 echomw.NewRateLimiterMemoryStoreWithConfig( 108 echomw.NewRateLimiterMemoryStoreWithConfig(
98 echomw.RateLimiterMemoryStoreConfig{ 109 echomw.RateLimiterMemoryStoreConfig{
@@ -103,26 +114,35 @@ func webMain(cfg app.Config, embeddedTemplates fs.FS, version string) {
103 ), 114 ),
104 ) 115 )
105 116
117 // These admin prefixed stores have unsafe-by-default behavior that's
118 // necessary for some admin workflows. They should never be used in
119 // non-admin workflows.
106 adminAccountStore := &models.MongoDbAccountStore{ 120 adminAccountStore := &models.MongoDbAccountStore{
107 Db: mongo, 121 Db: mongo,
108 ReturnDeleted: true, 122 ReturnDeleted: true,
109 } 123 }
110 as := &models.MongoDbAccountStore{Db: mongo}
111
112 adminUserStore := &models.MongoDbUserStore{ 124 adminUserStore := &models.MongoDbUserStore{
113 Db: mongo, 125 Db: mongo,
114 ReturnDeleted: true, 126 ReturnDeleted: true,
115 } 127 }
128
129 // Use these for non-admin workflows
130 as := &models.MongoDbAccountStore{Db: mongo}
116 us := &models.MongoDbUserStore{Db: mongo} 131 us := &models.MongoDbUserStore{Db: mongo}
117 132
118 aws := &controllers.AWSAPI{Store: as} 133 aws := &controllers.AWSAPI{Store: as}
119 134
135 ghCred := &app.GitHubOauthCreds{}
136 if _, err := vc.Secret(ctx, cfg.GitHubOauthCreds, &ghCred); err != nil {
137 log.Fatalf("Error retrieving GitHub credentials: %s", err)
138 }
139
120 am := &middleware.AuthenticationMiddleware{ 140 am := &middleware.AuthenticationMiddleware{
121 Store: us, 141 Store: us,
122 CookieDuration: cfg.AuthCookieDuration, 142 CookieDuration: cfg.AuthCookieDuration,
123 GitHub: &github.GitHubAuthenticator{ 143 GitHub: &github.GitHubAuthenticator{
124 ClientId: cfg.GitHubOauthCreds.ClientId, 144 ClientId: ghCred.ClientId,
125 ClientSecret: cfg.GitHubOauthCreds.ClientSecret, 145 ClientSecret: ghCred.ClientSecret,
126 }, 146 },
127 JWTManager: &auth.JWTManager{ 147 JWTManager: &auth.JWTManager{
128 Store: us, 148 Store: us,
@@ -171,14 +191,9 @@ func webMain(cfg app.Config, embeddedTemplates fs.FS, version string) {
171 }).Register("/:user", "", user) 191 }).Register("/:user", "", user)
172 } 192 }
173 } 193 }
194
174 s.GET("/favicon.ico", echo.NotFoundHandler) 195 s.GET("/favicon.ico", echo.NotFoundHandler)
175 s.GET("/logout", controllers.LogoutHandler) 196 s.GET("/logout", controllers.LogoutHandler)
176 s.CachedStaticRoute("/assets", "assets") 197 s.CachedStaticRoute("/assets", "assets")
177 s.GET("/", controllers.IndexHandler, am.Middleware) 198 s.GET("/", controllers.IndexHandler, am.Middleware)
178
179 runner := service.NewAppRunner(ctx, s.Logger)
180 runner.AddJob(s.RunCertificateManager) // Cert manager has to start before server
181 runner.AddJob(tls.OcspErrorLogger(s.Logger, s.OcspErrors()))
182 runner.AddJobs(s.MakeServerJobs())
183 runner.RunForever(!cfg.DisableBackgroundJobs)
184} 199}