From f326e3ae5ec43fe9bfb9e4f92c232cefdaacc875 Mon Sep 17 00:00:00 2001 From: NHAS Date: Mon, 7 Aug 2023 21:22:11 +1200 Subject: [PATCH 1/3] Add bounded token use as per #50 --- commands/registration.go | 6 ++- internal/config/config.go | 2 +- .../20230807204056_multiple_registrations.sql | 3 ++ internal/data/registration.go | 45 +++++++++++++++---- internal/webserver/web.go | 2 +- pkg/control/server/registrations.go | 12 ++++- pkg/control/shared.go | 1 + pkg/control/wagctl/client.go | 3 +- ui/src/js/tokens.js | 9 +++- ui/structs.go | 1 + .../management/registration_tokens.html | 5 +++ ui/ui_webserver.go | 4 +- 12 files changed, 77 insertions(+), 16 deletions(-) create mode 100644 internal/data/migrations/20230807204056_multiple_registrations.sql diff --git a/commands/registration.go b/commands/registration.go index 2ac129f9..2f43c4ca 100644 --- a/commands/registration.go +++ b/commands/registration.go @@ -32,6 +32,8 @@ type registration struct { groups arrayFlags groupsString string overwrite string + + uses int } func Registration() *registration { @@ -49,6 +51,8 @@ func Registration() *registration { gc.fs.StringVar(&gc.overwrite, "overwrite", "", "Add registration token for an existing user device, will overwrite wireguard public key (but not 2FA)") + gc.fs.IntVar(&gc.uses, "uses", 1, "Number of times a registration token can be used") + gc.fs.Bool("add", false, "Create a new enrolment token") gc.fs.Bool("del", false, "Delete existing enrolment token") gc.fs.Bool("list", false, "List tokens") @@ -113,7 +117,7 @@ func (g *registration) Run() error { switch g.action { case "add": - result, err := ctl.NewRegistration(g.token, g.username, g.overwrite, g.groups...) + result, err := ctl.NewRegistration(g.token, g.username, g.overwrite, g.uses, g.groups...) if err != nil { return err } diff --git a/internal/config/config.go b/internal/config/config.go index 607938be..93fa6fee 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -70,7 +70,7 @@ func (a Acls) GetUserGroups(username string) (result []string) { type Config struct { path string Socket string `json:",omitempty"` - GID int `json:,omitempty` + GID int `json:",omitempty"` CheckUpdates bool `json:",omitempty"` Proxied bool ExposePorts []string `json:",omitempty"` diff --git a/internal/data/migrations/20230807204056_multiple_registrations.sql b/internal/data/migrations/20230807204056_multiple_registrations.sql new file mode 100644 index 00000000..a2803b56 --- /dev/null +++ b/internal/data/migrations/20230807204056_multiple_registrations.sql @@ -0,0 +1,3 @@ +-- version 10 +ALTER TABLE RegistrationTokens ADD uses INTEGER; + diff --git a/internal/data/registration.go b/internal/data/registration.go index 828d9ec4..637d67a4 100644 --- a/internal/data/registration.go +++ b/internal/data/registration.go @@ -24,6 +24,8 @@ func GetRegistrationToken(token string) (username, overwrites string, group []st RegistrationTokens WHERE token = ? + AND + uses > 0 `, token).Scan(&token, &username, &overwrites, &groupsJson) if err != nil { return @@ -41,7 +43,7 @@ func GetRegistrationToken(token string) (username, overwrites string, group []st // Returns list of tokens func GetRegistrationTokens() (result []control.RegistrationResult, err error) { - rows, err := database.Query("SELECT token, username, overwrite, groups FROM RegistrationTokens ORDER by ROWID DESC") + rows, err := database.Query("SELECT token, username, overwrite, groups, uses FROM RegistrationTokens ORDER by ROWID DESC") if err != nil { return nil, err } @@ -74,26 +76,53 @@ func DeleteRegistrationToken(identifier string) error { DELETE FROM RegistrationTokens WHERE - token = $1 OR username = $1 + (token = $1 OR username = $1) or uses <= 0 `, identifier) return err } +// FinaliseRegistration may or may not delete the token in question depending on whether the number of uses is <= 0 +func FinaliseRegistration(token string) error { + _, err := database.Exec(`UPDATE + RegistrationTokens + SET + uses = uses - 1 + WHERE + token = ?`, + token) + if err != nil { + return err + } + + var uses int + err = database.QueryRow(`SELECT uses FROM RegistrationTokens WHERE token = ?`, token).Scan(&uses) + // Due to the (token = $1 OR username = $1) or uses <= 0 in DeleteRegistrationToken it is possible for tokens to get deleted between update and now + if err != nil && err != sql.ErrNoRows { + return err + } + + if uses <= 0 && err != sql.ErrNoRows { + return DeleteRegistrationToken(token) + } + + return nil +} + // Randomly generate a token for a specific username -func GenerateToken(username, overwrite string, groups []string) (token string, err error) { +func GenerateToken(username, overwrite string, groups []string, uses int) (token string, err error) { tokenBytes, err := generateRandomBytes(32) if err != nil { return "", err } token = hex.EncodeToString(tokenBytes) - err = AddRegistrationToken(token, username, overwrite, groups) + err = AddRegistrationToken(token, username, overwrite, groups, uses) return } // Add a token to the database to add or overwrite a device for a user, may fail of the token does not meet complexity requirements -func AddRegistrationToken(token, username, overwrite string, groups []string) error { +func AddRegistrationToken(token, username, overwrite string, groups []string, uses int) error { if len(token) < 32 { return errors.New("registration token is too short") } @@ -130,10 +159,10 @@ func AddRegistrationToken(token, username, overwrite string, groups []string) er _, err = database.Exec(` INSERT INTO - RegistrationTokens (token, username, overwrite) + RegistrationTokens (token, username, overwrite, uses) VALUES - (?, ?, ?) -`, token, username, overwrite) + (?, ?, ?, )) +`, token, username, overwrite, uses) return err } diff --git a/internal/webserver/web.go b/internal/webserver/web.go index 14c21697..fa50859d 100644 --- a/internal/webserver/web.go +++ b/internal/webserver/web.go @@ -502,7 +502,7 @@ func registerDevice(w http.ResponseWriter, r *http.Request) { } //Finish registration process - err = data.DeleteRegistrationToken(key) + err = data.FinaliseRegistration(key) if err != nil { log.Println(username, remoteAddr, "expiring registration token failed:", err) http.Error(w, "Server Error", 500) diff --git a/pkg/control/server/registrations.go b/pkg/control/server/registrations.go index 28222b82..d259b5e6 100644 --- a/pkg/control/server/registrations.go +++ b/pkg/control/server/registrations.go @@ -5,6 +5,7 @@ import ( "errors" "log" "net/http" + "strconv" "strings" "github.com/NHAS/wag/internal/config" @@ -52,6 +53,7 @@ func newRegistration(w http.ResponseWriter, r *http.Request) { overwrite := r.FormValue("overwrite") groupsString := r.FormValue("groups") + usesString := r.FormValue("groups") var groups []string = nil err = json.Unmarshal([]byte(groupsString), &groups) @@ -72,6 +74,12 @@ func newRegistration(w http.ResponseWriter, r *http.Request) { config.AddVirtualUser(username, groups) } + uses, err := strconv.Atoi(usesString) + if err != nil { + http.Error(w, "invalid number of uses for registration token: "+err.Error(), 500) + return + } + resp := control.RegistrationResult{Token: token, Username: username, Groups: groups} tokenType := "registration" @@ -80,7 +88,7 @@ func newRegistration(w http.ResponseWriter, r *http.Request) { } if token != "" { - err := data.AddRegistrationToken(token, username, overwrite, groups) + err := data.AddRegistrationToken(token, username, overwrite, groups, uses) if err != nil { http.Error(w, err.Error(), 500) return @@ -98,7 +106,7 @@ func newRegistration(w http.ResponseWriter, r *http.Request) { return } - token, err = data.GenerateToken(username, overwrite, groups) + token, err = data.GenerateToken(username, overwrite, groups, uses) if err != nil { http.Error(w, err.Error(), 500) return diff --git a/pkg/control/shared.go b/pkg/control/shared.go index 824d42ab..727eef38 100644 --- a/pkg/control/shared.go +++ b/pkg/control/shared.go @@ -5,6 +5,7 @@ type RegistrationResult struct { Username string Groups []string Overwrites string + NumUses int } type PolicyData struct { diff --git a/pkg/control/wagctl/client.go b/pkg/control/wagctl/client.go index 87771d40..7fbb2c43 100644 --- a/pkg/control/wagctl/client.go +++ b/pkg/control/wagctl/client.go @@ -526,12 +526,13 @@ func (c *CtrlClient) Registrations() (result []control.RegistrationResult, err e return } -func (c *CtrlClient) NewRegistration(token, username, overwrite string, groups ...string) (r control.RegistrationResult, err error) { +func (c *CtrlClient) NewRegistration(token, username, overwrite string, uses int, groups ...string) (r control.RegistrationResult, err error) { form := url.Values{} form.Add("username", username) form.Add("token", token) form.Add("overwrite", overwrite) + form.Add("uses", fmt.Sprintf("%d", uses)) for _, group := range groups { if !strings.HasPrefix(group, "group:") { diff --git a/ui/src/js/tokens.js b/ui/src/js/tokens.js index c96d43cd..1a069418 100755 --- a/ui/src/js/tokens.js +++ b/ui/src/js/tokens.js @@ -67,6 +67,12 @@ $(function () { sortable: true, align: 'center', escape: "true" + }, { + field: 'uses', + title: 'Uses', + sortable: true, + align: 'center', + escape: "true" } ]) @@ -110,7 +116,8 @@ $(function () { "username": $('#recipient-name').val(), "token": $('#token').val(), "overwrites": $('#overwrite').val(), - "groups": $('#groups').val() + "groups": $('#groups').val(), + "uses": $("#uses").vaul() } fetch("/management/registration_tokens/data", { diff --git a/ui/structs.go b/ui/structs.go index b4946e17..3aa642f2 100644 --- a/ui/structs.go +++ b/ui/structs.go @@ -80,6 +80,7 @@ type TokensData struct { Username string `json:"username"` Groups []string `json:"groups"` Overwrites string `json:"overwrites"` + Uses int `json:"uses"` } type WgDevicesData struct { diff --git a/ui/templates/management/registration_tokens.html b/ui/templates/management/registration_tokens.html index 7dbc618c..418c3fe1 100755 --- a/ui/templates/management/registration_tokens.html +++ b/ui/templates/management/registration_tokens.html @@ -66,6 +66,11 @@ +
+ + +
+ diff --git a/ui/ui_webserver.go b/ui/ui_webserver.go index cb947980..9f181739 100644 --- a/ui/ui_webserver.go +++ b/ui/ui_webserver.go @@ -1148,6 +1148,7 @@ func registrationTokens(w http.ResponseWriter, r *http.Request) { Token: reg.Token, Groups: reg.Groups, Overwrites: reg.Overwrites, + Uses: reg.NumUses, }) } @@ -1184,6 +1185,7 @@ func registrationTokens(w http.ResponseWriter, r *http.Request) { Token string Overwrites string Groups string + Uses int } defer r.Body.Close() @@ -1198,7 +1200,7 @@ func registrationTokens(w http.ResponseWriter, r *http.Request) { groups = strings.Split(b.Groups, ",") } - _, err = ctrl.NewRegistration(b.Token, b.Username, b.Overwrites, groups...) + _, err = ctrl.NewRegistration(b.Token, b.Username, b.Overwrites, b.Uses, groups...) if err != nil { http.Error(w, err.Error(), 400) return From 643e740d011598950be888b6821230db63230c92 Mon Sep 17 00:00:00 2001 From: NHAS Date: Mon, 7 Aug 2023 21:47:24 +1200 Subject: [PATCH 2/3] Multiple fixes for multiple registration uses --- internal/data/registration.go | 10 +++++----- internal/router/bpf_bpfeb.o | Bin 10080 -> 10080 bytes internal/router/bpf_bpfel.o | Bin 10120 -> 10120 bytes pkg/control/server/registrations.go | 9 +++++++-- pkg/control/wagctl/client.go | 5 +++++ ui/src/js/tokens.js | 2 +- .../management/registration_tokens.html | 2 +- ui/ui_webserver.go | 18 +++++++++++++++--- 8 files changed, 34 insertions(+), 12 deletions(-) diff --git a/internal/data/registration.go b/internal/data/registration.go index 637d67a4..f1b692cd 100644 --- a/internal/data/registration.go +++ b/internal/data/registration.go @@ -53,7 +53,7 @@ func GetRegistrationTokens() (result []control.RegistrationResult, err error) { groupsJson sql.NullString registration control.RegistrationResult ) - err = rows.Scan(®istration.Token, ®istration.Username, ®istration.Overwrites, &groupsJson) + err = rows.Scan(®istration.Token, ®istration.Username, ®istration.Overwrites, &groupsJson, ®istration.NumUses) if err != nil { return nil, err } @@ -149,10 +149,10 @@ func AddRegistrationToken(token, username, overwrite string, groups []string, us _, err = database.Exec(` INSERT INTO - RegistrationTokens (token, username, overwrite, groups) + RegistrationTokens (token, username, overwrite, groups, uses) VALUES - (?, ?, ?, ?) - `, token, username, overwrite, string(result)) + (?, ?, ?, ?, ?) + `, token, username, overwrite, string(result), uses) return err } @@ -161,7 +161,7 @@ func AddRegistrationToken(token, username, overwrite string, groups []string, us INSERT INTO RegistrationTokens (token, username, overwrite, uses) VALUES - (?, ?, ?, )) + (?, ?, ?, ?) `, token, username, overwrite, uses) return err diff --git a/internal/router/bpf_bpfeb.o b/internal/router/bpf_bpfeb.o index 7527874d26316266db38632fad4e0feb01c2a2ad..d74c1df5a3427b4fd7ebf94aa29782ff96be5ecc 100644 GIT binary patch delta 1867 zcmZ9NU1$_n6vxk2x zolwMvwhvpT1s^Idh>(XAgF%VlLntX-L?P;n^rh44L+C>ZC6uKW>Hpk0V}=};k;iM^(F#r3u=(VL2#_;_~_h1FFlw_$)QJO+n>-U7_5?0b7$LN@YB;fUl|2moTxN@GW5H=A{3iN*=Gu93N2=?a;gKje4@B&g% z@D7;<67r;?;0&1wZ023WN*vGp7I-|zAE6N0!1<4Whjacj;D+#1GBY^R!Z`Nl1@#&- zxAFTaOr`;PE15?{@SN~@GD#0O$?@CZmT-&A6BOVBc|G$gnHF=Ga4%WEjKnkl5ZMxZ zL4`jHJmD(Y3eF_x6V3uggzu3ZZJ2Baj!<6|KQqeLH+ zZtGMf`U#5I-48vpPc7Nq4_@r5Cq7Z!wmr;w6LInI2cB$~1{*{k#Iwb1yFHM2ea<$M zKcR@-T@0}+{u+wiG~@o_OOZSFY}(&TZrAN>+Qub&z)i^HUnp5}{h1Ntot&KyK}7Ml yFx$T6j8i{%&g-yu7abfJsC>BNjriuKiTHS_qb delta 1728 zcmZ9NU1(fI6vzK_ckj&X*0x5IrtL1KHQP^-Zncp3QerB#5B*pKAxgom^dX9hAD{uD zH$ssWDlCj(9|YG2X&wTNq|ksqL=oA()QAZ6$tx<zRR6R?<(5};-m}+X#CLWQ<|N<};(teJ&kK8fn080_3Uqm#cDKO$X!nGF zgs%Jir2_i}R{}>B=w?iNE#qT|#-Upe5Yo&M*jKzR{48|;LH5!Le*oQOyxN|#YmJVb z`x1JcWhv=HFlO&F2uy+FqeFeR2n}G(_;-$SM;}F7Ww|@a)FPM7Yck1D9sf33|Tr6 z`}{Y^y`U%YJ(yJ!vQ_uzml)0JzZ zn}23Vi4TWfa8tEy5!PL^zHLkPl?4InfIDA1beKnx$Es8$KmE!le@D+dWc=X1tsP2O zc&?Ux0r~iohGb<2Dt^UAM#A2BGW*bR?6 zZo_Z3gh)>qG|dGuxHM(ZtYR-b$8JNuY|0=DHdxM(HF~@)XOz7 z9b+zFrtEESFl*8Py9cfqHEB@sCU{tJ2i%Ui%Q?vUR=8Vn51cJHNed__%i{)IP-fsG zd1O+KT@Qz6unFvbQ5USV`_a2~7E9;edD#6)e!zvtE=TB|Hz~?a!nH1ZD0rHj7IulE zaf=$+H(_tWqRor)ee4&qzTKk1g2iOeXO~(;ReLQO=NjpHIInmMJgs;;jL*fH!wES) zZPA?K)o=o9k#l^He7p;DQtX493${uA@d{nGsFw@6;b6H%eTsE>fPD>lZ61qY%klLX zpI5vS?#5!}1Y|x9o$mY)gvMkk7~)Wk+`kl PpGsZLacxu#)qMU7SB~lK delta 1636 zcmZA2T}TvB6bJA#>3sRwrpr3+lG?7NNws}|_9AIt5`|k47#MBT!xjZvSVG_!gl$kD z+@KzU^dYPWS~2Lu9yZF*7c)Yzr;Li~&BBK*3i{vK6J7Sg%$(mn=g!=FcSe)#$@bJs zOZy`xtOwd=(POW2n+r%bEgz?99Q(2^v1KQYPS_;M4(?qTG06;4#!bv{xQI22bcs9oi;!r_A%ZQ$W@v*gqeFfs4f0vt- zw@h1P;nIg=6i4Z#MRn|HT#l@Pa{Y+$-&eW9-i| zzi3d0Vn3Wv>gD(@c4;2=Jz`RleN0RQs@#j{t&EzK=H6+zc#}z!>>scbGiiohj+ZjD z+oZ4TMtEz&q>SR_a0O;3*Cvp+Pny)keh3#{G%1#~I9lOzr_fiEG^v+s!aBxW!A#jZ zVQ0vsLG~HA;=V~Y6gR_n6d!>*Fn2izS>FzKDei`cvfe;{P*9e}4Y;3IivQi{C> z4$ff{*aM>0sd5LjJ2e)PI(o+2Ks=Q`VFBhDK9n7L%u?`QiZy~Q`un4vs-+=LH#e3i` zELKiH=HoD456{E!!JDY}jGYgO*~*QgC|It&6E(qrGloG#dEUq7g?xG{_67r5y*M6R z=h%2zWzD>I{a^J#jH2d^m_m)lHFNm&)b>sILIKC&Yzos*+jG%^?02s#&)XC=TO+!W z{mI!nELm++VmK7gd}B{SA=7T8-L0+B!y6jK`|w?HDN>`YAG;fIHT*-VUwKYTiQ$!B F{sIs|=zstK diff --git a/pkg/control/server/registrations.go b/pkg/control/server/registrations.go index d259b5e6..270b90d0 100644 --- a/pkg/control/server/registrations.go +++ b/pkg/control/server/registrations.go @@ -53,7 +53,7 @@ func newRegistration(w http.ResponseWriter, r *http.Request) { overwrite := r.FormValue("overwrite") groupsString := r.FormValue("groups") - usesString := r.FormValue("groups") + usesString := r.FormValue("uses") var groups []string = nil err = json.Unmarshal([]byte(groupsString), &groups) @@ -80,7 +80,12 @@ func newRegistration(w http.ResponseWriter, r *http.Request) { return } - resp := control.RegistrationResult{Token: token, Username: username, Groups: groups} + if uses <= 0 { + http.Error(w, "invalid number of uses for registration token: "+usesString, 400) + return + } + + resp := control.RegistrationResult{Token: token, Username: username, Groups: groups, NumUses: uses} tokenType := "registration" if overwrite != "" { diff --git a/pkg/control/wagctl/client.go b/pkg/control/wagctl/client.go index 7fbb2c43..b683cc5f 100644 --- a/pkg/control/wagctl/client.go +++ b/pkg/control/wagctl/client.go @@ -528,6 +528,11 @@ func (c *CtrlClient) Registrations() (result []control.RegistrationResult, err e func (c *CtrlClient) NewRegistration(token, username, overwrite string, uses int, groups ...string) (r control.RegistrationResult, err error) { + if uses <= 0 { + err = errors.New("unable to create token with <= 0 uses") + return + } + form := url.Values{} form.Add("username", username) form.Add("token", token) diff --git a/ui/src/js/tokens.js b/ui/src/js/tokens.js index 1a069418..36f27eff 100755 --- a/ui/src/js/tokens.js +++ b/ui/src/js/tokens.js @@ -117,7 +117,7 @@ $(function () { "token": $('#token').val(), "overwrites": $('#overwrite').val(), "groups": $('#groups').val(), - "uses": $("#uses").vaul() + "uses": ($("#uses").val() == "" ? "1" : $("#uses").val()) } fetch("/management/registration_tokens/data", { diff --git a/ui/templates/management/registration_tokens.html b/ui/templates/management/registration_tokens.html index 418c3fe1..a2b5eb68 100755 --- a/ui/templates/management/registration_tokens.html +++ b/ui/templates/management/registration_tokens.html @@ -68,7 +68,7 @@
- +
diff --git a/ui/ui_webserver.go b/ui/ui_webserver.go index 9f181739..6c957214 100644 --- a/ui/ui_webserver.go +++ b/ui/ui_webserver.go @@ -12,6 +12,7 @@ import ( "log" "net/http" "os" + "strconv" "strings" "time" @@ -1185,13 +1186,24 @@ func registrationTokens(w http.ResponseWriter, r *http.Request) { Token string Overwrites string Groups string - Uses int + Uses string } defer r.Body.Close() err := json.NewDecoder(r.Body).Decode(&b) if err != nil { - http.Error(w, "Bad request", 400) + http.Error(w, err.Error(), 400) + return + } + + uses, err := strconv.Atoi(b.Uses) + if err != nil { + http.Error(w, err.Error(), 400) + return + } + + if uses <= 0 { + http.Error(w, "cannot create token with <= 0 uses", 400) return } @@ -1200,7 +1212,7 @@ func registrationTokens(w http.ResponseWriter, r *http.Request) { groups = strings.Split(b.Groups, ",") } - _, err = ctrl.NewRegistration(b.Token, b.Username, b.Overwrites, b.Uses, groups...) + _, err = ctrl.NewRegistration(b.Token, b.Username, b.Overwrites, uses, groups...) if err != nil { http.Error(w, err.Error(), 400) return From 833ebb2567df9128fde32fa281a0b18a0ee96e19 Mon Sep 17 00:00:00 2001 From: NHAS Date: Mon, 7 Aug 2023 21:49:39 +1200 Subject: [PATCH 3/3] Improve GID handling --- internal/config/config.go | 2 +- pkg/control/server/server.go | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index 93fa6fee..eb72c9c5 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -70,7 +70,7 @@ func (a Acls) GetUserGroups(username string) (result []string) { type Config struct { path string Socket string `json:",omitempty"` - GID int `json:",omitempty"` + GID *int `json:",omitempty"` CheckUpdates bool `json:",omitempty"` Proxied bool ExposePorts []string `json:",omitempty"` diff --git a/pkg/control/server/server.go b/pkg/control/server/server.go index 818aa51b..c7eb0c92 100644 --- a/pkg/control/server/server.go +++ b/pkg/control/server/server.go @@ -92,8 +92,10 @@ func StartControlSocket() error { return err } - if err := os.Chown(config.Values().Socket, -1, config.Values().GID); err != nil { - return err + if config.Values().GID != nil { + if err := os.Chown(config.Values().Socket, -1, *config.Values().GID); err != nil { + return err + } } log.Println("Started control socket: \n\t\t\t", config.Values().Socket)