From ee31dff01e4e48ed2115f7051cdb94d6d3daa504 Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Thu, 11 Jun 2026 11:06:43 -0700 Subject: [PATCH] sso: support public MCIAS authorize URL for non-Tailnet browsers Add [sso].public_url: the browser-facing MCIAS base URL for the SSO authorize redirect, kept separate from [mcias].server_url (the internal address used for the server-to-server code exchange). Enables public SSO without routing internal auth through the edge. Bumps mcdsl to v1.9.0. Co-Authored-By: Claude Opus 4.8 --- go.mod | 2 +- go.sum | 4 ++-- internal/config/config.go | 10 ++++++++- internal/webserver/server.go | 8 ++++++- vendor/git.wntrmute.dev/mc/mcdsl/db/db.go | 14 +++++------- vendor/git.wntrmute.dev/mc/mcdsl/sso/sso.go | 25 ++++++++++++++++++--- vendor/modules.txt | 2 +- 7 files changed, 48 insertions(+), 17 deletions(-) diff --git a/go.mod b/go.mod index c6930c1..093dc6b 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.25.7 require ( git.wntrmute.dev/kyle/goutils v1.21.0 - git.wntrmute.dev/mc/mcdsl v1.7.0 + git.wntrmute.dev/mc/mcdsl v1.9.0 github.com/go-chi/chi/v5 v5.2.5 github.com/spf13/cobra v1.10.2 github.com/spf13/viper v1.21.0 diff --git a/go.sum b/go.sum index b97dc76..2dc5bc1 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ git.wntrmute.dev/kyle/goutils v1.21.0 h1:ZR7ovV400hsF09zc8tkdHs6vyen8TDJ7flong/dnFXM= git.wntrmute.dev/kyle/goutils v1.21.0/go.mod h1:JQ8NL5lHSEYl719UMf20p4G1ei70RVGma0hjjNXCR2c= -git.wntrmute.dev/mc/mcdsl v1.7.0 h1:dAh2SGdzjhz0H66i3KAMDm1eRYYgMaxqQ0Pj5NzF7fc= -git.wntrmute.dev/mc/mcdsl v1.7.0/go.mod h1:MhYahIu7Sg53lE2zpQ20nlrsoNRjQzOJBAlCmom2wJc= +git.wntrmute.dev/mc/mcdsl v1.9.0 h1:TGqVhf9uhhh5jpMhN+8eNtBPSi/wwNXQn/NFDAcU4wg= +git.wntrmute.dev/mc/mcdsl v1.9.0/go.mod h1:MhYahIu7Sg53lE2zpQ20nlrsoNRjQzOJBAlCmom2wJc= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= diff --git a/internal/config/config.go b/internal/config/config.go index f9654c2..40179e2 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -37,8 +37,16 @@ type MCIASConfig struct { // SSOConfig holds SSO redirect settings for the web UI. type SSOConfig struct { // RedirectURI is the callback URL that MCIAS redirects to after login. - // Must exactly match the redirect_uri registered in MCIAS config. + // Must exactly match the redirect_uri registered in MCIAS config. For + // public (non-Tailnet) browser access this must be the public hostname. RedirectURI string `toml:"redirect_uri"` + + // PublicURL is the browser-facing MCIAS base URL used to build the SSO + // authorize redirect (e.g. "https://mcias.metacircular.net"). When empty, + // the backend [mcias].server_url is used for the redirect too. Set this + // when browsers cannot resolve the internal MCIAS name; the + // server-to-server code exchange still uses [mcias].server_url. + PublicURL string `toml:"public_url"` } // WebConfig holds settings for the standalone web UI server (metacrypt-web). diff --git a/internal/webserver/server.go b/internal/webserver/server.go index 53004c6..d29a8e6 100644 --- a/internal/webserver/server.go +++ b/internal/webserver/server.go @@ -175,6 +175,7 @@ func New(cfg *config.Config, logger *slog.Logger) (*WebServer, error) { if cfg.SSO.RedirectURI != "" { ssoClient, ssoErr := mcdsso.New(mcdsso.Config{ MciasURL: cfg.MCIAS.ServerURL, + PublicURL: cfg.SSO.PublicURL, ClientID: "metacrypt", RedirectURI: cfg.SSO.RedirectURI, CACert: cfg.MCIAS.CACert, @@ -183,7 +184,12 @@ func New(cfg *config.Config, logger *slog.Logger) (*WebServer, error) { return nil, fmt.Errorf("webserver: create SSO client: %w", ssoErr) } ws.ssoClient = ssoClient - logger.Info("SSO enabled: redirecting to MCIAS for login", "mcias_url", cfg.MCIAS.ServerURL) + authorizeURL := cfg.SSO.PublicURL + if authorizeURL == "" { + authorizeURL = cfg.MCIAS.ServerURL + } + logger.Info("SSO enabled: redirecting to MCIAS for login", + "authorize_url", authorizeURL, "exchange_url", cfg.MCIAS.ServerURL) } if tok := cfg.MCIAS.ServiceToken; tok != "" { diff --git a/vendor/git.wntrmute.dev/mc/mcdsl/db/db.go b/vendor/git.wntrmute.dev/mc/mcdsl/db/db.go index 2ab1b32..81362c7 100644 --- a/vendor/git.wntrmute.dev/mc/mcdsl/db/db.go +++ b/vendor/git.wntrmute.dev/mc/mcdsl/db/db.go @@ -65,11 +65,11 @@ func Open(path string) (*sql.DB, error) { // connection to serialize all access and eliminate busy errors. database.SetMaxOpenConns(1) - // Ensure permissions are correct even if the file already existed. - if err := os.Chmod(path, 0600); err != nil { - _ = database.Close() - return nil, fmt.Errorf("db: chmod %s: %w", path, err) - } + // Best-effort permissions tightening. This may fail inside rootless + // podman containers where fchmod is denied in the user namespace. + // The database still functions correctly without it. + // See: log/2026-04-03-uid-incident.md + _ = os.Chmod(path, 0600) return database, nil } @@ -168,9 +168,7 @@ func Snapshot(database *sql.DB, destPath string) error { return fmt.Errorf("db: snapshot: %w", err) } - if err := os.Chmod(destPath, 0600); err != nil { - return fmt.Errorf("db: chmod snapshot %s: %w", destPath, err) - } + _ = os.Chmod(destPath, 0600) // best-effort; may fail in rootless containers return nil } diff --git a/vendor/git.wntrmute.dev/mc/mcdsl/sso/sso.go b/vendor/git.wntrmute.dev/mc/mcdsl/sso/sso.go index e2d7a79..1c5ea14 100644 --- a/vendor/git.wntrmute.dev/mc/mcdsl/sso/sso.go +++ b/vendor/git.wntrmute.dev/mc/mcdsl/sso/sso.go @@ -39,9 +39,18 @@ const ( // Config holds the SSO client configuration. The values must match the // SSO client registration in MCIAS config. type Config struct { - // MciasURL is the base URL of the MCIAS server. + // MciasURL is the base URL of the MCIAS server used for the + // server-to-server authorization-code exchange. This is typically the + // internal/Tailnet address so the exchange does not depend on the public + // edge. MciasURL string + // PublicURL, when set, is the browser-facing MCIAS base URL used to build + // the authorize redirect. It must be resolvable and reachable by end-user + // browsers (e.g. the public hostname). When empty, MciasURL is used for + // both, which only works when MciasURL is itself browser-reachable. + PublicURL string + // ClientID is the registered SSO client identifier. ClientID string @@ -104,9 +113,19 @@ func New(cfg Config) (*Client, error) { }, nil } -// AuthorizeURL returns the MCIAS authorize URL with the given state parameter. +// authorizeBase returns the browser-facing MCIAS base URL: PublicURL when +// configured, otherwise MciasURL. +func (c *Client) authorizeBase() string { + if c.cfg.PublicURL != "" { + return c.cfg.PublicURL + } + return c.cfg.MciasURL +} + +// AuthorizeURL returns the MCIAS authorize URL (browser-facing) with the +// given state parameter. func (c *Client) AuthorizeURL(state string) string { - base := strings.TrimRight(c.cfg.MciasURL, "/") + base := strings.TrimRight(c.authorizeBase(), "/") return base + "/sso/authorize?" + url.Values{ "client_id": {c.cfg.ClientID}, "redirect_uri": {c.cfg.RedirectURI}, diff --git a/vendor/modules.txt b/vendor/modules.txt index aebc4bc..37333b9 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -2,7 +2,7 @@ ## explicit; go 1.24.0 git.wntrmute.dev/kyle/goutils/certlib/certgen git.wntrmute.dev/kyle/goutils/lib -# git.wntrmute.dev/mc/mcdsl v1.7.0 +# git.wntrmute.dev/mc/mcdsl v1.9.0 ## explicit; go 1.25.7 git.wntrmute.dev/mc/mcdsl/auth git.wntrmute.dev/mc/mcdsl/config