diff --git a/controller/config/config.go b/controller/config/config.go index 71c8720dc..a5a79b368 100644 --- a/controller/config/config.go +++ b/controller/config/config.go @@ -25,6 +25,7 @@ import ( "github.com/openziti/identity" "github.com/pkg/errors" "net" + "net/url" "os" "reflect" "strconv" @@ -51,6 +52,8 @@ const ( DefaultHttpReadTimeout = 5000 * time.Millisecond DefaultHttpReadHeaderTimeout = 5000 * time.Millisecond DefaultHttpWriteTimeout = 100000 * time.Millisecond + + DefaultTotpDomain = "openziti.io" ) type Enrollment struct { @@ -65,6 +68,10 @@ type EnrollmentOption struct { Duration time.Duration } +type Totp struct { + Hostname string +} + type Api struct { SessionTimeout time.Duration ActivityUpdateBatchSize int @@ -83,6 +90,7 @@ type Config struct { caPems *bytes.Buffer caPemsOnce sync.Once + Totp Totp } type HttpTimeouts struct { @@ -133,6 +141,47 @@ func (c *Config) RefreshCaPems() { c.caPems = CalculateCaPems(c.caPems) } +func (c *Config) loadTotpSection(edgeConfigMap map[any]any) error { + c.Totp = Totp{} + c.Totp.Hostname = DefaultTotpDomain + + if value, found := edgeConfigMap["totp"]; found { + if value == nil { + return nil + } + + totpMap := value.(map[interface{}]interface{}) + + if totpMap != nil { + if hostnameVal, found := totpMap["hostname"]; found { + + if hostnameVal == nil { + return nil + } + + if hostname, ok := hostnameVal.(string); ok { + testUrl := "https://" + hostname + parsedUrl, err := url.Parse(testUrl) + + if err != nil { + return fmt.Errorf("could not parse URL: %w", err) + } + + if parsedUrl.Hostname() != hostname { + return fmt.Errorf("invalid hostname in [edge.totp.hostname]: %s", hostname) + } + + c.Totp.Hostname = hostname + } else { + return fmt.Errorf("[edge.totp.hostname] must be a string") + } + } + } + } + + return nil +} + func (c *Config) loadApiSection(edgeConfigMap map[interface{}]interface{}) error { c.Api = Api{} c.Api.HttpTimeouts = *DefaultHttpTimeouts() @@ -367,6 +416,10 @@ func LoadFromMap(configMap map[interface{}]interface{}) (*Config, error) { return nil, err } + if err = edgeConfig.loadTotpSection(edgeConfigMap); err != nil { + return nil, err + } + if err = edgeConfig.loadEnrollmentSection(edgeConfigMap); err != nil { return nil, err } diff --git a/controller/model/mfa_manager.go b/controller/model/mfa_manager.go index 114661846..52391ce3e 100644 --- a/controller/model/mfa_manager.go +++ b/controller/model/mfa_manager.go @@ -224,7 +224,7 @@ func (self *MfaManager) GetProvisioningUrl(mfa *Mfa) string { WindowSize: WindowSizeTOTP, UTC: true, } - return otcConfig.ProvisionURIWithIssuer(mfa.Identity.Name, "ziti.dev") + return otcConfig.ProvisionURIWithIssuer(mfa.Identity.Name, self.env.GetConfig().Totp.Hostname) } func (self *MfaManager) RecreateRecoveryCodes(mfa *Mfa, ctx *change.Context) error { diff --git a/tests/mfa_ziti_test.go b/tests/mfa_ziti_test.go index 93ada8ed7..87a0a3e1d 100644 --- a/tests/mfa_ziti_test.go +++ b/tests/mfa_ziti_test.go @@ -212,7 +212,8 @@ func Test_MFA(t *testing.T) { mfaUrl, err := url.Parse(provisionString) ctx.Req.NoError(err) ctx.Req.Equal(mfaUrl.Host, "totp") - ctx.Req.Equal(mfaUrl.Path, "/ziti.dev:"+mfaStartedIdentityName) + + ctx.Req.Equal(mfaUrl.Path, "/"+ctx.EdgeController.AppEnv.Config.Totp.Hostname+":"+mfaStartedIdentityName) ctx.Req.Equal(mfaUrl.Scheme, "otpauth") }) })