Skip to content

Commit

Permalink
Add locking of mfa methods when pre-reqs arent met
Browse files Browse the repository at this point in the history
  • Loading branch information
NHAS committed Nov 28, 2024
1 parent 3c51ce8 commit d003119
Show file tree
Hide file tree
Showing 12 changed files with 161 additions and 145 deletions.
1 change: 0 additions & 1 deletion adminui/frontend/src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,6 @@ export interface LoginSettingsResponseDTO {
default_mfa_method: string
enabled_mfa_methods: string[]

domain: string
issuer: string

oidc: OidcResponseDTO
Expand Down
15 changes: 9 additions & 6 deletions adminui/frontend/src/pages/Settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,14 @@ function isAcmeValid(): boolean {
function serverCanHaveTLS(domain: string): boolean {
return isAcmeValid() && domain.length > 0
}
function doesTunnelHaveDomain() {
return webserversSettingsData.value.some((x) => x.server_name == 'tunnel' && x.domain.length > 0)
}
function doesTunnelHaveTLS() {
return webserversSettingsData.value.some((x) => x.server_name == 'tunnel' && x.tls)
}
</script>

<template>
Expand Down Expand Up @@ -338,6 +346,7 @@ function serverCanHaveTLS(domain: string): boolean {
class="toggle toggle-primary"
:value="method.method"
v-model="loginSettingsData.enabled_mfa_methods"
:disabled="!doesTunnelHaveDomain() && (method.method == 'oidc' || method.method == 'webauthn') || (!doesTunnelHaveTLS() && method.method == 'webauthn')"
:checked="loginSettingsData.enabled_mfa_methods.indexOf(method.method) != -1"
/>
</label>
Expand Down Expand Up @@ -370,12 +379,6 @@ function serverCanHaveTLS(domain: string): boolean {
</label>
<input v-model="loginSettingsData.issuer" type="text" required class="input input-bordered w-full" />
</div>
<div class="form-control">
<label class="label font-bold">
<span class="label-text">Internal VPN Domain</span>
</label>
<input v-model="loginSettingsData.domain" type="text" required class="input input-bordered w-full" />
</div>
</div>
</div>

Expand Down
26 changes: 13 additions & 13 deletions adminui/ui_webserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,11 @@ func New(firewall *router.Firewall, errs chan<- error) (ui *AdminUI, err error)
return nil, fmt.Errorf("admin ui failed to start as we could not get wag version: %s", err)
}

if !config.Values.ManagementUI.OIDC.Enabled && !*config.Values.ManagementUI.Password.Enabled {
if !config.Values.Webserver.Management.OIDC.Enabled && !*config.Values.Webserver.Management.Password.Enabled {
return nil, errors.New("neither oidc or password authentication was enabled for the admin user interface, you wont be able to log in despite having it enabled")
}

if config.Values.ManagementUI.OIDC.Enabled {
if config.Values.Webserver.Management.OIDC.Enabled {
key, err := utils.GenerateRandom(32)
if err != nil {
return nil, errors.New("failed to get random key: " + err.Error())
Expand All @@ -86,18 +86,18 @@ func New(firewall *router.Firewall, errs chan<- error) (ui *AdminUI, err error)
rp.WithVerifierOpts(rp.WithIssuedAtOffset(5 * time.Second)),
}

u, err := url.Parse(config.Values.ManagementUI.Domain)
u, err := url.Parse(config.Values.Webserver.Management.Domain)
if err != nil {
return nil, fmt.Errorf("failed to parse admin url: %q, err: %s", config.Values.ManagementUI.Domain, err)
return nil, fmt.Errorf("failed to parse admin url: %q, err: %s", config.Values.Webserver.Management.Domain, err)
}

u.Path = path.Join(u.Path, "/login/oidc/callback")
log.Println("[ADMINUI] OIDC callback: ", u.String())
log.Println("[ADMINUI] Connecting to OIDC provider: ", config.Values.ManagementUI.OIDC.IssuerURL)
log.Println("[ADMINUI] Connecting to OIDC provider: ", config.Values.Webserver.Management.OIDC.IssuerURL)

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)

adminUI.oidcProvider, err = rp.NewRelyingPartyOIDC(ctx, config.Values.ManagementUI.OIDC.IssuerURL, config.Values.ManagementUI.OIDC.ClientID, config.Values.ManagementUI.OIDC.ClientSecret, u.String(), []string{"openid"}, options...)
adminUI.oidcProvider, err = rp.NewRelyingPartyOIDC(ctx, config.Values.Webserver.Management.OIDC.IssuerURL, config.Values.Webserver.Management.OIDC.ClientID, config.Values.Webserver.Management.OIDC.ClientSecret, u.String(), []string{"openid"}, options...)
cancel()
if err != nil {
return nil, fmt.Errorf("unable to connect to oidc provider for admin ui. err %s", err)
Expand All @@ -106,7 +106,7 @@ func New(firewall *router.Firewall, errs chan<- error) (ui *AdminUI, err error)
log.Println("[ADMINUI] Connected to admin oidc provider!")
}

if *config.Values.ManagementUI.Password.Enabled {
if *config.Values.Webserver.Management.Password.Enabled {

admins, err := adminUI.ctrl.ListAdminUsers("")
if err != nil {
Expand Down Expand Up @@ -172,7 +172,7 @@ func New(firewall *router.Firewall, errs chan<- error) (ui *AdminUI, err error)
allRoutes.HandleFunc("GET /api/config", adminUI.uiConfig)
allRoutes.HandleFunc("POST /api/refresh", adminUI.doAuthRefresh)

if config.Values.ManagementUI.OIDC.Enabled {
if config.Values.Webserver.Management.OIDC.Enabled {
allRoutes.HandleFunc("GET /login/oidc", func(w http.ResponseWriter, r *http.Request) {
rp.AuthURLHandler(func() string {
r, _ := utils.GenerateRandomHex(32)
Expand Down Expand Up @@ -286,15 +286,15 @@ func New(firewall *router.Firewall, errs chan<- error) (ui *AdminUI, err error)
return nil, err
}

log.Println("[ADMINUI] Started Managemnt UI listening:", config.Values.ManagementUI.ListenAddress)
log.Println("[ADMINUI] Started Managemnt UI listening:", config.Values.Webserver.Management.ListenAddress)

return &adminUI, nil
}

func (au *AdminUI) uiConfig(w http.ResponseWriter, r *http.Request) {
m := ConfigResponseDTO{
SSO: config.Values.ManagementUI.OIDC.Enabled,
Password: *config.Values.ManagementUI.Password.Enabled,
SSO: config.Values.Webserver.Management.OIDC.Enabled,
Password: *config.Values.Webserver.Management.Password.Enabled,
}

json.NewEncoder(w).Encode(m)
Expand Down Expand Up @@ -330,7 +330,7 @@ func (au *AdminUI) doAuthRefresh(w http.ResponseWriter, r *http.Request) {

func (au *AdminUI) doLogin(w http.ResponseWriter, r *http.Request) {

if !*config.Values.ManagementUI.Password.Enabled {
if !*config.Values.Webserver.Management.Password.Enabled {
http.NotFound(w, r)
return
}
Expand Down Expand Up @@ -428,7 +428,7 @@ func (au *AdminUI) Close() {

autotls.Do.Close(data.Management)

if config.Values.ManagementUI.Enabled {
if config.Values.Webserver.Management.Enabled {
log.Println("Stopped Management UI")
}

Expand Down
2 changes: 1 addition & 1 deletion commands/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ func startWag(noIptables bool, cancel <-chan bool, errorChan chan<- error) func(
return
}

if config.Values.ManagementUI.Enabled {
if config.Values.Webserver.Management.Enabled {
adminUI, err = adminui.New(routerFw, errorChan)
if err != nil {
errorChan <- fmt.Errorf("unable to start management web server: %v", err)
Expand Down
115 changes: 59 additions & 56 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,70 +52,73 @@ type Config struct {
CheckUpdates bool `json:",omitempty"`
NumberProxies int
Proxied bool
ExposePorts []string `json:",omitempty"`
NAT *bool `json:",omitempty"`

ExposePorts []string `json:",omitempty"`
NAT *bool `json:",omitempty"`

MFATemplatesDirectory string `json:",omitempty"`

HelpMail string
Lockout int
ExternalAddress string
MaxSessionLifetimeMinutes int
SessionInactivityTimeoutMinutes int
Webserver struct {
Acme struct {
Email string
CAProvider string
CloudflareDNSToken string
}

DownloadConfigFileName string `json:",omitempty"`
Public struct {
webserverDetails
DownloadConfigFileName string `json:",omitempty"`
ExternalAddress string
}

Acme struct {
Email string
CAProvider string
CloudflareDNSToken string
}
Lockout int

ManagementUI struct {
Enabled bool
Tunnel struct {
Port string
Domain string
TLS bool

webserverDetails
HelpMail string

Password struct {
Enabled *bool `json:",omitempty"`
} `json:",omitempty"`
MaxSessionLifetimeMinutes int
SessionInactivityTimeoutMinutes int

OIDC struct {
IssuerURL string
ClientSecret string
ClientID string
Enabled bool
} `json:",omitempty"`
} `json:",omitempty"`
DefaultMethod string `json:",omitempty"`
Issuer string
Methods []string `json:",omitempty"`

Webserver struct {
Public webserverDetails
OIDC struct {
IssuerURL string
ClientSecret string
ClientID string
GroupsClaimName string `json:",omitempty"`
} `json:",omitempty"`

Tunnel struct {
Port string
Domain string
TLS bool
PAM struct {
ServiceName string
} `json:",omitempty"`
}
}

Clustering ClusteringDetails
Management struct {
webserverDetails

Authenticators struct {
DefaultMethod string `json:",omitempty"`
Issuer string
Methods []string `json:",omitempty"`
Enabled bool

OIDC struct {
IssuerURL string
ClientSecret string
ClientID string
GroupsClaimName string `json:",omitempty"`
} `json:",omitempty"`
Password struct {
Enabled *bool `json:",omitempty"`
} `json:",omitempty"`

PAM struct {
ServiceName string
OIDC struct {
IssuerURL string
ClientSecret string
ClientID string
Enabled bool
} `json:",omitempty"`
} `json:",omitempty"`
}

Clustering ClusteringDetails

Wireguard struct {
DevName string
ListenPort int
Expand Down Expand Up @@ -157,8 +160,8 @@ func load(path string) (c Config, err error) {
c.Socket = control.DefaultWagSocket
}

if c.DownloadConfigFileName == "" {
c.DownloadConfigFileName = "wg0.conf"
if c.Webserver.Public.DownloadConfigFileName == "" {
c.Webserver.Public.DownloadConfigFileName = "wg0.conf"
}

if c.Proxied {
Expand Down Expand Up @@ -225,20 +228,20 @@ func load(path string) (c Config, err error) {
*c.NAT = true
}

err = validators.ValidExternalAddresses(c.ExternalAddress)
err = validators.ValidExternalAddresses(c.Webserver.Public.ExternalAddress)
if err != nil {
return c, err
}

if c.Lockout <= 0 {
if c.Webserver.Lockout <= 0 {
return c, errors.New("lockout policy unconfigured")
}

if c.MaxSessionLifetimeMinutes == 0 {
if c.Webserver.Tunnel.MaxSessionLifetimeMinutes == 0 {
return c, errors.New("session max lifetime policy is not set (may be disabled by setting it to -1)")
}

if c.SessionInactivityTimeoutMinutes == 0 {
if c.Webserver.Tunnel.SessionInactivityTimeoutMinutes == 0 {
return c, errors.New("session inactivity timeout policy is not set (may be disabled by setting it to -1)")
}

Expand Down Expand Up @@ -329,13 +332,13 @@ func load(path string) (c Config, err error) {
}
}

if len(c.Authenticators.Methods) == 1 {
c.Authenticators.DefaultMethod = c.Authenticators.Methods[len(c.Authenticators.Methods)-1]
if len(c.Webserver.Tunnel.Methods) == 1 {
c.Webserver.Tunnel.DefaultMethod = c.Webserver.Tunnel.Methods[len(c.Webserver.Tunnel.Methods)-1]
}

if c.ManagementUI.Password.Enabled == nil {
if c.Webserver.Management.Password.Enabled == nil {
enabled := true
c.ManagementUI.Password.Enabled = &enabled
c.Webserver.Management.Password.Enabled = &enabled
}

return c, nil
Expand Down
4 changes: 2 additions & 2 deletions internal/data/clustering.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,8 +226,8 @@ func AddMember(name, etcPeerUrlAddress, newManagerAddressURL string) (joinToken
copyValues.Acls = config.Acls{}
copyValues.Acls.Groups = map[string][]string{}

copyValues.ManagementUI.Enabled = false
copyValues.ManagementUI.ListenAddress = ""
copyValues.Webserver.Management.Enabled = false
copyValues.Webserver.Management.ListenAddress = ""

b, _ := json.Marshal(copyValues)
token.SetAdditional("config.json", string(b))
Expand Down
Loading

0 comments on commit d003119

Please sign in to comment.