Browse Source

review fix: allow multiple token definitions

pull/2122/head
Shivaram Lingamneni 3 months ago
parent
commit
2dbf871ac4
4 changed files with 76 additions and 38 deletions
  1. 15
    12
      default.yaml
  2. 37
    9
      irc/jwt/bearer.go
  3. 9
    5
      irc/jwt/bearer_test.go
  4. 15
    12
      traditional.yaml

+ 15
- 12
default.yaml View File

@@ -604,18 +604,21 @@ accounts:
604 604
         enabled: false
605 605
         # should we automatically create users on presentation of a valid token?
606 606
         autocreate: true
607
-        algorithm: "hmac" # either 'hmac', 'rsa', or 'eddsa' (ed25519)
608
-        # hmac takes a symmetric key, rsa and eddsa take PEM-encoded public keys;
609
-        # either way, the key can be specified either as a YAML string:
610
-        key: "nANiZ1De4v6WnltCHN2H7Q"
611
-        # or as a path to the file containing the key:
612
-        #key-file: "jwt_pubkey.pem"
613
-        # list of JWT claim names to search for the user's account name (make sure the format
614
-        # is what you expect, especially if using "sub"):
615
-        account-claims: ["preferred_username"]
616
-        # if a claim is formatted as an email address, require it to have the following domain,
617
-        # and then strip off the domain and use the local-part as the account name:
618
-        #strip-domain: "example.com"
607
+        # any of these token definitions can be accepted, allowing for key rotation
608
+        tokens:
609
+            -
610
+                algorithm: "hmac" # either 'hmac', 'rsa', or 'eddsa' (ed25519)
611
+                # hmac takes a symmetric key, rsa and eddsa take PEM-encoded public keys;
612
+                # either way, the key can be specified either as a YAML string:
613
+                key: "nANiZ1De4v6WnltCHN2H7Q"
614
+                # or as a path to the file containing the key:
615
+                #key-file: "jwt_pubkey.pem"
616
+                # list of JWT claim names to search for the user's account name (make sure the format
617
+                # is what you expect, especially if using "sub"):
618
+                account-claims: ["preferred_username"]
619
+                # if a claim is formatted as an email address, require it to have the following domain,
620
+                # and then strip off the domain and use the local-part as the account name:
621
+                #strip-domain: "example.com"
619 622
 
620 623
 # channel options
621 624
 channels:

+ 37
- 9
irc/jwt/bearer.go View File

@@ -19,8 +19,12 @@ var (
19 19
 
20 20
 // JWTAuthConfig is the config for Ergo to accept JWTs via draft/bearer
21 21
 type JWTAuthConfig struct {
22
-	Enabled       bool   `yaml:"enabled"`
23
-	Autocreate    bool   `yaml:"autocreate"`
22
+	Enabled    bool                 `yaml:"enabled"`
23
+	Autocreate bool                 `yaml:"autocreate"`
24
+	Tokens     []JWTAuthTokenConfig `yaml:"tokens"`
25
+}
26
+
27
+type JWTAuthTokenConfig struct {
24 28
 	Algorithm     string `yaml:"algorithm"`
25 29
 	KeyString     string `yaml:"key"`
26 30
 	KeyFile       string `yaml:"key-file"`
@@ -35,6 +39,20 @@ func (j *JWTAuthConfig) Postprocess() error {
35 39
 		return nil
36 40
 	}
37 41
 
42
+	if len(j.Tokens) == 0 {
43
+		return fmt.Errorf("JWT authentication enabled, but no valid tokens defined")
44
+	}
45
+
46
+	for i := range j.Tokens {
47
+		if err := j.Tokens[i].Postprocess(); err != nil {
48
+			return err
49
+		}
50
+	}
51
+
52
+	return nil
53
+}
54
+
55
+func (j *JWTAuthTokenConfig) Postprocess() error {
38 56
 	keyBytes, err := j.keyBytes()
39 57
 	if err != nil {
40 58
 		return err
@@ -74,7 +92,21 @@ func (j *JWTAuthConfig) Postprocess() error {
74 92
 	return nil
75 93
 }
76 94
 
77
-func (j *JWTAuthConfig) keyBytes() (result []byte, err error) {
95
+func (j *JWTAuthConfig) Validate(t string) (accountName string, err error) {
96
+	if !j.Enabled || len(j.Tokens) == 0 {
97
+		return "", ErrAuthDisabled
98
+	}
99
+
100
+	for i := range j.Tokens {
101
+		accountName, err = j.Tokens[i].Validate(t)
102
+		if err == nil {
103
+			return
104
+		}
105
+	}
106
+	return
107
+}
108
+
109
+func (j *JWTAuthTokenConfig) keyBytes() (result []byte, err error) {
78 110
 	if j.KeyFile != "" {
79 111
 		o, err := os.Open(j.KeyFile)
80 112
 		if err != nil {
@@ -89,15 +121,11 @@ func (j *JWTAuthConfig) keyBytes() (result []byte, err error) {
89 121
 }
90 122
 
91 123
 // implements jwt.Keyfunc
92
-func (j *JWTAuthConfig) keyFunc(_ *jwt.Token) (interface{}, error) {
124
+func (j *JWTAuthTokenConfig) keyFunc(_ *jwt.Token) (interface{}, error) {
93 125
 	return j.key, nil
94 126
 }
95 127
 
96
-func (j *JWTAuthConfig) Validate(t string) (accountName string, err error) {
97
-	if !j.Enabled {
98
-		return "", ErrAuthDisabled
99
-	}
100
-
128
+func (j *JWTAuthTokenConfig) Validate(t string) (accountName string, err error) {
101 129
 	token, err := j.parser.Parse(t, j.keyFunc)
102 130
 	if err != nil {
103 131
 		return "", err

+ 9
- 5
irc/jwt/bearer_test.go View File

@@ -49,11 +49,15 @@ s/uzBKNwWf9UPTeIt+4JScg=
49 49
 
50 50
 func TestJWTBearerAuth(t *testing.T) {
51 51
 	j := JWTAuthConfig{
52
-		Enabled:       true,
53
-		Algorithm:     "rsa",
54
-		KeyString:     rsaTestPubKey,
55
-		AccountClaims: []string{"preferred_username", "email"},
56
-		StripDomain:   "example.com",
52
+		Enabled: true,
53
+		Tokens: []JWTAuthTokenConfig{
54
+			{
55
+				Algorithm:     "rsa",
56
+				KeyString:     rsaTestPubKey,
57
+				AccountClaims: []string{"preferred_username", "email"},
58
+				StripDomain:   "example.com",
59
+			},
60
+		},
57 61
 	}
58 62
 
59 63
 	if err := j.Postprocess(); err != nil {

+ 15
- 12
traditional.yaml View File

@@ -577,18 +577,21 @@ accounts:
577 577
         enabled: false
578 578
         # should we automatically create users on presentation of a valid token?
579 579
         autocreate: true
580
-        algorithm: "hmac" # either 'hmac', 'rsa', or 'eddsa' (ed25519)
581
-        # hmac takes a symmetric key, rsa and eddsa take PEM-encoded public keys;
582
-        # either way, the key can be specified either as a YAML string:
583
-        key: "nANiZ1De4v6WnltCHN2H7Q"
584
-        # or as a path to the file containing the key:
585
-        #key-file: "jwt_pubkey.pem"
586
-        # list of JWT claim names to search for the user's account name (make sure the format
587
-        # is what you expect, especially if using "sub"):
588
-        account-claims: ["preferred_username"]
589
-        # if a claim is formatted as an email address, require it to have the following domain,
590
-        # and then strip off the domain and use the local-part as the account name:
591
-        #strip-domain: "example.com"
580
+        # any of these token definitions can be accepted, allowing for key rotation
581
+        tokens:
582
+            -
583
+                algorithm: "hmac" # either 'hmac', 'rsa', or 'eddsa' (ed25519)
584
+                # hmac takes a symmetric key, rsa and eddsa take PEM-encoded public keys;
585
+                # either way, the key can be specified either as a YAML string:
586
+                key: "nANiZ1De4v6WnltCHN2H7Q"
587
+                # or as a path to the file containing the key:
588
+                #key-file: "jwt_pubkey.pem"
589
+                # list of JWT claim names to search for the user's account name (make sure the format
590
+                # is what you expect, especially if using "sub"):
591
+                account-claims: ["preferred_username"]
592
+                # if a claim is formatted as an email address, require it to have the following domain,
593
+                # and then strip off the domain and use the local-part as the account name:
594
+                #strip-domain: "example.com"
592 595
 
593 596
 # channel options
594 597
 channels:

Loading…
Cancel
Save