Skip to content

Commit 3fde540

Browse files
committed
Move authenticators over to being more dynamic, fix bug with authentication attempts
1 parent 03fc035 commit 3fde540

File tree

17 files changed

+435
-236
lines changed

17 files changed

+435
-236
lines changed

go.mod

+2-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ require (
1616
go.etcd.io/etcd/client/v3 v3.5.11
1717
go.etcd.io/etcd/server/v3 v3.5.11
1818
golang.org/x/crypto v0.17.0
19-
golang.org/x/net v0.19.0
19+
golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2
2020
golang.org/x/sys v0.16.0
2121
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
2222
)
@@ -60,7 +60,6 @@ require (
6060
github.com/prometheus/client_model v0.2.0 // indirect
6161
github.com/prometheus/common v0.26.0 // indirect
6262
github.com/prometheus/procfs v0.6.0 // indirect
63-
github.com/r3labs/diff v1.1.0 // indirect
6463
github.com/sirupsen/logrus v1.9.3 // indirect
6564
github.com/soheilhy/cmux v0.1.5 // indirect
6665
github.com/spf13/pflag v1.0.5 // indirect
@@ -84,7 +83,7 @@ require (
8483
go.uber.org/atomic v1.7.0 // indirect
8584
go.uber.org/multierr v1.6.0 // indirect
8685
go.uber.org/zap v1.17.0 // indirect
87-
golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 // indirect
86+
golang.org/x/net v0.19.0 // indirect
8887
golang.org/x/oauth2 v0.13.0 // indirect
8988
golang.org/x/sync v0.3.0 // indirect
9089
golang.org/x/text v0.14.0 // indirect

go.sum

-3
Original file line numberDiff line numberDiff line change
@@ -210,8 +210,6 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT
210210
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
211211
github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
212212
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
213-
github.com/r3labs/diff v1.1.0 h1:V53xhrbTHrWFWq3gI4b94AjgEJOerO1+1l0xyHOBi8M=
214-
github.com/r3labs/diff v1.1.0/go.mod h1:7WjXasNzi0vJetRcB/RqNl5dlIsmXcTTLmF5IoH6Xig=
215213
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
216214
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
217215
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
@@ -231,7 +229,6 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
231229
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
232230
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
233231
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
234-
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
235232
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
236233
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
237234
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=

internal/config/config.go

+9-117
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,14 @@ import (
66
"fmt"
77
"log"
88
"net"
9-
"net/url"
109
"os"
1110
"strconv"
1211
"strings"
1312
"sync"
1413

1514
"github.com/NHAS/wag/internal/acls"
1615
"github.com/NHAS/wag/internal/routetypes"
17-
"github.com/NHAS/wag/internal/webserver/authenticators"
1816
"github.com/NHAS/wag/pkg/control"
19-
"github.com/NHAS/webauthn/webauthn"
2017
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
2118
)
2219

@@ -52,19 +49,19 @@ type Config struct {
5249
path string
5350
Socket string `json:",omitempty"`
5451
GID *int `json:",omitempty"`
55-
CheckUpdates bool `json:",omitempty"`
52+
CheckUpdates bool `json:",omitempty"` // Done
5653
NumberProxies int
5754
Proxied bool
5855
ExposePorts []string `json:",omitempty"`
5956
NAT *bool
6057

6158
MFATemplatesDirectory string `json:",omitempty"`
6259

63-
HelpMail string
64-
Lockout int
65-
ExternalAddress string
66-
MaxSessionLifetimeMinutes int
67-
SessionInactivityTimeoutMinutes int
60+
HelpMail string // Done
61+
Lockout int // Done
62+
ExternalAddress string // Done
63+
MaxSessionLifetimeMinutes int // Done
64+
SessionInactivityTimeoutMinutes int // Done
6865

6966
DownloadConfigFileName string `json:",omitempty"`
7067

@@ -87,10 +84,10 @@ type Config struct {
8784
}
8885

8986
Authenticators struct {
90-
DefaultMethod string `json:",omitempty"`
91-
Issuer string
87+
DefaultMethod string `json:",omitempty"` // Done
88+
Issuer string // Done
9289
Methods []string `json:",omitempty"`
93-
DomainURL string
90+
DomainURL string // Done
9491

9592
OIDC struct {
9693
IssuerURL string
@@ -102,9 +99,6 @@ type Config struct {
10299
PAM struct {
103100
ServiceName string
104101
} `json:",omitempty"`
105-
106-
//Not externally configurable
107-
Webauthn *webauthn.WebAuthn `json:"-"`
108102
}
109103
Wireguard struct {
110104
DevName string
@@ -365,112 +359,10 @@ func load(path string) (c Config, err error) {
365359
}
366360
}
367361

368-
if len(c.Authenticators.Methods) == 0 {
369-
for method := range authenticators.MFA {
370-
c.Authenticators.Methods = append(c.Authenticators.Methods, method)
371-
}
372-
}
373-
374-
resultMFAMap := make(map[string]authenticators.Authenticator)
375-
for _, method := range c.Authenticators.Methods {
376-
_, ok := authenticators.MFA[method]
377-
if !ok {
378-
return c, errors.New("mfa method invalid: " + method)
379-
}
380-
381-
resultMFAMap[method] = authenticators.MFA[method]
382-
383-
settings := make(map[string]string)
384-
switch method {
385-
386-
case "oidc":
387-
if c.Authenticators.DomainURL == "" {
388-
return c, errors.New("Authenticators.DomainURL unset, needed for oidc")
389-
}
390-
391-
if c.Authenticators.OIDC.GroupsClaimName == "" {
392-
c.Authenticators.OIDC.GroupsClaimName = "groups"
393-
}
394-
395-
if c.Authenticators.OIDC.IssuerURL == "" {
396-
return c, errors.New("OIDC issuer url is not set, but oidc authentication method is enabled")
397-
}
398-
399-
tunnelURL, err := url.Parse(c.Authenticators.OIDC.IssuerURL)
400-
if err != nil {
401-
return c, errors.New("unable to parse Authenticators.OIDC.IssuerURL: " + err.Error())
402-
}
403-
404-
if tunnelURL.Scheme != "https" && tunnelURL.Scheme != "http" {
405-
return c, errors.New("Authenticators.OIDC.IssuerURL was not HTTP/HTTPS")
406-
}
407-
408-
if tunnelURL.Scheme == "http" {
409-
log.Println("[WARNING] OIDC issuer url is http, this may be insecure")
410-
}
411-
412-
if c.Authenticators.OIDC.ClientSecret == "" {
413-
return c, errors.New("Authenticators.OIDC.ClientSecret is empty, but oidc authentication method is enabled")
414-
}
415-
416-
if c.Authenticators.OIDC.ClientID == "" {
417-
return c, errors.New("Authenticators.OIDC.ClientID is empty, but oidc authentication method is enabled")
418-
}
419-
420-
settings["ClientID"] = c.Authenticators.OIDC.ClientID
421-
settings["ClientSecret"] = c.Authenticators.OIDC.ClientSecret
422-
settings["IssuerURL"] = c.Authenticators.OIDC.IssuerURL
423-
settings["DomainURL"] = c.Authenticators.DomainURL
424-
425-
case "webauthn":
426-
427-
if c.Authenticators.DomainURL == "" {
428-
return c, errors.New("Authenticators.DomainURL unset, needed for webauthn")
429-
}
430-
431-
tunnelURL, err := url.Parse(c.Authenticators.DomainURL)
432-
if err != nil {
433-
return c, errors.New("unable to parse Authenticators.DomainURL: " + err.Error())
434-
}
435-
436-
if !c.Webserver.Tunnel.SupportsTLS() && c.NumberProxies == 0 {
437-
return c, errors.New("tunnel does not support TLS (no cert/key given) required by webauthn")
438-
}
439-
440-
if tunnelURL.Scheme != "https" {
441-
return c, errors.New("Authenticators.DomainURL was not HTTPS, yet webauthn was enabled (javascript wont be able to access window.PublicKeyCredential)")
442-
}
443-
444-
c.Authenticators.Webauthn, err = webauthn.New(&webauthn.Config{
445-
RPDisplayName: c.Authenticators.Issuer, // Display Name for your site
446-
RPID: strings.Split(tunnelURL.Host, ":")[0], // Generally the domain name for your site
447-
RPOrigin: c.Authenticators.DomainURL, // The origin URL for WebAuthn requests
448-
})
449-
450-
if err != nil {
451-
return c, errors.New("could not configure webauthn domain: " + err.Error())
452-
}
453-
}
454-
455-
if err := resultMFAMap[method].Init(settings); err != nil {
456-
return c, err
457-
}
458-
}
459-
460-
if c.Authenticators.DefaultMethod != "" {
461-
_, ok := resultMFAMap[c.Authenticators.DefaultMethod]
462-
if !ok {
463-
return c, errors.New("default mfa method invalid: " + c.Authenticators.DefaultMethod + " valid methods: " + strings.Join(c.Authenticators.Methods, ","))
464-
}
465-
}
466-
467362
if len(c.Authenticators.Methods) == 1 {
468363
c.Authenticators.DefaultMethod = c.Authenticators.Methods[len(c.Authenticators.Methods)-1]
469364
}
470365

471-
// Remove all uneeded MFA methods from the MFA map
472-
authenticators.MFA = resultMFAMap
473-
474366
return c, nil
475367
}
476368

internal/data/config.go

+144-3
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,50 @@ import (
55
"encoding/json"
66
"errors"
77
"fmt"
8+
"net/url"
89
"strconv"
10+
"strings"
911

1012
clientv3 "go.etcd.io/etcd/client/v3"
1113
)
1214

15+
type OIDC struct {
16+
IssuerURL string
17+
ClientSecret string
18+
ClientID string
19+
GroupsClaimName string `json:",omitempty"`
20+
}
21+
22+
type PAM struct {
23+
ServiceName string
24+
}
25+
26+
type Webauthn struct {
27+
DisplayName string
28+
ID string
29+
Origin string
30+
}
31+
1332
const (
14-
fullJsonConfigKey = "wag-config-full"
33+
fullJsonConfigKey = "wag-config-full"
34+
1535
helpMailKey = "wag-config-general-help-mail"
36+
defaultWGFileNameKey = "wag-config-general-wg-filename"
37+
checkUpdatesKey = "wag-config-general-check-updates"
38+
1639
inactivityTimeoutKey = "wag-config-authentication-inactivity-timeout"
1740
sessionLifetimeKey = "wag-config-authentication-max-session-lifetime"
1841
lockoutKey = "wag-config-authentication-lockout"
1942
issuerKey = "wag-config-authentication-issuer"
2043
domainKey = "wag-config-authentication-domain"
44+
methodsEnabledKey = "wag-config-authentication-methods"
2145
defaultMFAMethodKey = "wag-config-authentication-default-method"
22-
externalAddressKey = "wag-config-network-external-address"
23-
dnsKey = "wag-config-network-dns"
46+
47+
oidcDetailsKey = "wag-config-authentication-oidc"
48+
pamDetailsKey = "wag-config-authentication-pam"
49+
50+
externalAddressKey = "wag-config-network-external-address"
51+
dnsKey = "wag-config-network-dns"
2452
)
2553

2654
func getGeneric(key string) (string, error) {
@@ -36,6 +64,119 @@ func getGeneric(key string) (string, error) {
3664
return string(resp.Kvs[0].Value), nil
3765
}
3866

67+
func SetPAM(details PAM) error {
68+
d, err := json.Marshal(details)
69+
if err != nil {
70+
return err
71+
}
72+
73+
_, err = etcd.Put(context.Background(), pamDetailsKey, string(d))
74+
return err
75+
}
76+
77+
func GetPAM() (details PAM, err error) {
78+
79+
v, err := getGeneric(pamDetailsKey)
80+
if err != nil {
81+
return PAM{}, nil
82+
}
83+
84+
err = json.Unmarshal([]byte(v), &details)
85+
return
86+
}
87+
88+
func SetOidc(details OIDC) error {
89+
d, err := json.Marshal(details)
90+
if err != nil {
91+
return err
92+
}
93+
94+
_, err = etcd.Put(context.Background(), oidcDetailsKey, string(d))
95+
return err
96+
}
97+
98+
func GetOidc() (details OIDC, err error) {
99+
100+
v, err := getGeneric(oidcDetailsKey)
101+
if err != nil {
102+
return OIDC{}, nil
103+
}
104+
105+
err = json.Unmarshal([]byte(v), &details)
106+
return
107+
}
108+
109+
func GetWebauthn() (wba Webauthn, err error) {
110+
111+
txn := etcd.Txn(context.Background())
112+
response, err := txn.Then(clientv3.OpGet(issuerKey),
113+
clientv3.OpGet(domainKey)).Commit()
114+
if err != nil {
115+
return wba, err
116+
}
117+
118+
if response.Responses[0].GetResponseRange().Count != 1 {
119+
return wba, errors.New("no issuer set")
120+
}
121+
122+
if response.Responses[1].GetResponseRange().Count != 1 {
123+
return wba, errors.New("no domain set")
124+
}
125+
126+
tunnelURL, err := url.Parse(string(response.Responses[1].GetResponseRange().Kvs[0].Value))
127+
if err != nil {
128+
return wba, errors.New("unable to parse Authenticators.DomainURL: " + err.Error())
129+
}
130+
131+
wba.Origin = tunnelURL.String()
132+
wba.DisplayName = string(response.Responses[0].GetResponseRange().Kvs[0].Value)
133+
wba.ID = strings.Split(tunnelURL.Host, ":")[0]
134+
135+
return
136+
}
137+
138+
func SetWireguardConfigName(wgConfig string) error {
139+
_, err := etcd.Put(context.Background(), defaultWGFileNameKey, wgConfig)
140+
return err
141+
}
142+
143+
func GetWireguardConfigName() (string, error) {
144+
return getGeneric(defaultWGFileNameKey)
145+
}
146+
147+
func SetAuthenticationMethods(methods []string) error {
148+
data, _ := json.Marshal(methods)
149+
_, err := etcd.Put(context.Background(), methodsEnabledKey, string(data))
150+
return err
151+
}
152+
153+
func GetAuthenicationMethods() (result []string, err error) {
154+
155+
val, err := getGeneric(methodsEnabledKey)
156+
if err != nil {
157+
return nil, err
158+
}
159+
160+
err = json.Unmarshal([]byte(val), &result)
161+
162+
return
163+
}
164+
165+
func SetCheckUpdates(doChecks bool) error {
166+
_, err := etcd.Put(context.Background(), checkUpdatesKey, strconv.FormatBool(doChecks))
167+
return err
168+
}
169+
170+
func CheckUpdates() (bool, error) {
171+
172+
val, err := getGeneric(checkUpdatesKey)
173+
if err != nil {
174+
return false, err
175+
}
176+
177+
return val == "true", nil
178+
}
179+
39180
func SetDomain(domain string) error {
40181
_, err := etcd.Put(context.Background(), domainKey, domain)
41182
return err

0 commit comments

Comments
 (0)