diff options
author | Mike Crute <mike@crute.us> | 2022-12-21 22:06:29 -0800 |
---|---|---|
committer | Mike Crute <mike@crute.us> | 2022-12-21 22:06:29 -0800 |
commit | ed1504c2826f6a5d406dd72e51f5a90b77ffea45 (patch) | |
tree | 159733ceaf63026d36c39117fff6159e6247bed7 /cmd | |
parent | e5629fb163c7cf303438afc5be6075299cfc6071 (diff) | |
download | cloud-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.go | 119 |
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 | ||
28 | func Register(root *cobra.Command, embeddedTemplates fs.FS, version string) { | 31 | func 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 | ||
56 | func webMain(cfg app.Config, embeddedTemplates fs.FS, version string) { | 46 | func 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 | ||
106 | func 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 | } |