Skip to content

Commit 38aef77

Browse files
authored
allow @ and Log if OIDC username is not consider valid (#2340)
1 parent 1ab7b31 commit 38aef77

File tree

2 files changed

+38
-4
lines changed

2 files changed

+38
-4
lines changed

hscontrol/types/users.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
1212
"github.com/juanfont/headscale/hscontrol/util"
13+
"github.com/rs/zerolog/log"
1314
"google.golang.org/protobuf/types/known/timestamppb"
1415
"gorm.io/gorm"
1516
"tailscale.com/tailcfg"
@@ -173,9 +174,11 @@ func (c *OIDCClaims) Identifier() string {
173174
// FromClaim overrides a User from OIDC claims.
174175
// All fields will be updated, except for the ID.
175176
func (u *User) FromClaim(claims *OIDCClaims) {
176-
err := util.CheckForFQDNRules(claims.Username)
177+
err := util.ValidateUsername(claims.Username)
177178
if err == nil {
178179
u.Name = claims.Username
180+
} else {
181+
log.Debug().Err(err).Msgf("Username %s is not valid", claims.Username)
179182
}
180183

181184
if claims.EmailVerified {

hscontrol/util/dns.go

+34-3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"net/netip"
77
"regexp"
88
"strings"
9+
"unicode"
910

1011
"go4.org/netipx"
1112
"tailscale.com/util/dnsname"
@@ -20,10 +21,40 @@ const (
2021
LabelHostnameLength = 63
2122
)
2223

24+
var invalidDNSRegex = regexp.MustCompile("[^a-z0-9-.]+")
2325
var invalidCharsInUserRegex = regexp.MustCompile("[^a-z0-9-.]+")
2426

2527
var ErrInvalidUserName = errors.New("invalid user name")
2628

29+
func ValidateUsername(username string) error {
30+
// Ensure the username meets the minimum length requirement
31+
if len(username) < 2 {
32+
return errors.New("username must be at least 2 characters long")
33+
}
34+
35+
// Ensure the username does not start with a number
36+
if unicode.IsDigit(rune(username[0])) {
37+
return errors.New("username cannot start with a number")
38+
}
39+
40+
atCount := 0
41+
for _, char := range username {
42+
switch {
43+
case unicode.IsLetter(char), unicode.IsDigit(char), char == '-':
44+
// Valid characters
45+
case char == '@':
46+
atCount++
47+
if atCount > 1 {
48+
return errors.New("username cannot contain more than one '@'")
49+
}
50+
default:
51+
return fmt.Errorf("username contains invalid character: '%c'", char)
52+
}
53+
}
54+
55+
return nil
56+
}
57+
2758
func CheckForFQDNRules(name string) error {
2859
if len(name) > LabelHostnameLength {
2960
return fmt.Errorf(
@@ -39,7 +70,7 @@ func CheckForFQDNRules(name string) error {
3970
ErrInvalidUserName,
4071
)
4172
}
42-
if invalidCharsInUserRegex.MatchString(name) {
73+
if invalidDNSRegex.MatchString(name) {
4374
return fmt.Errorf(
4475
"DNS segment should only be composed of lowercase ASCII letters numbers, hyphen and dots. %v doesn't comply with theses rules: %w",
4576
name,
@@ -52,7 +83,7 @@ func CheckForFQDNRules(name string) error {
5283

5384
func ConvertWithFQDNRules(name string) string {
5485
name = strings.ToLower(name)
55-
name = invalidCharsInUserRegex.ReplaceAllString(name, "")
86+
name = invalidDNSRegex.ReplaceAllString(name, "")
5687

5788
return name
5889
}
@@ -197,7 +228,7 @@ func NormalizeToFQDNRules(name string, stripEmailDomain bool) (string, error) {
197228
} else {
198229
name = strings.ReplaceAll(name, "@", ".")
199230
}
200-
name = invalidCharsInUserRegex.ReplaceAllString(name, "-")
231+
name = invalidDNSRegex.ReplaceAllString(name, "-")
201232

202233
for _, elt := range strings.Split(name, ".") {
203234
if len(elt) > LabelHostnameLength {

0 commit comments

Comments
 (0)