Skip to content

Commit f926918

Browse files
committed
#354 clarify audience (aud) check
* move claims.Sites into claims.Audience * move check Site to check Audience * general cleanup * rename CreateUserTokenString to NewVPJWT
1 parent 10f1000 commit f926918

6 files changed

Lines changed: 54 additions & 43 deletions

File tree

handlers/auth.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,13 @@ func AuthStateHandler(w http.ResponseWriter, r *http.Request) {
9797
// SUCCESS!! they are authorized
9898

9999
// issue the jwt
100-
tokenstring := jwtmanager.CreateUserTokenString(user, customClaims, ptokens)
100+
101+
tokenstring, err := jwtmanager.NewVPJWT(user, customClaims, ptokens)
102+
if err != nil {
103+
responses.Error500(w, r, fmt.Errorf("/auth Token creation failure: %w . Please seek support from your administrator", err))
104+
return
105+
106+
}
101107
cookie.SetCookie(w, r, tokenstring)
102108

103109
// get the originally requested URL so we can send them on their way
@@ -156,10 +162,10 @@ func verifyUser(u interface{}) (bool, error) {
156162
// Domains
157163
case len(cfg.Cfg.Domains) != 0:
158164
if domains.IsUnderManagement(user.Email) {
159-
log.Debugf("verifyUser: Success! Email %s found within a "+cfg.Branding.FullName+" managed domain", user.Email)
165+
log.Debugf("verifyUser: Success! Email %s found within a %s managed domain", user.Email, cfg.Branding.FullName)
160166
return true, nil
161167
}
162-
return false, fmt.Errorf("verifyUser: Email %s is not within a "+cfg.Branding.FullName+" managed domain", user.Email)
168+
return false, fmt.Errorf("verifyUser: Email %s is not within a %s managed domain", user.Email, cfg.Branding.FullName)
163169

164170
// nothing configured, allow everyone through
165171
default:

handlers/handlers_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,6 @@ func init() {
142142

143143
lc = jwtmanager.VouchClaims{
144144
u1.Username,
145-
jwtmanager.Sites,
146145
customClaims.Claims,
147146
t1.PAccessToken,
148147
t1.PIdToken,
@@ -164,7 +163,8 @@ func TestParsedIdPTokens(t *testing.T) {
164163
for _, tt := range tests {
165164
t.Run(tt.name, func(t *testing.T) {
166165
setUp(tt.configFile)
167-
uts := jwtmanager.CreateUserTokenString(u1, customClaims, t1)
166+
uts, err := jwtmanager.NewVPJWT(u1, customClaims, t1)
167+
assert.NoError(t, err)
168168
utsParsed, _ := jwtmanager.ParseTokenString(uts)
169169
utsPtokens, _ := jwtmanager.PTokenClaims(utsParsed)
170170

handlers/validate.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func ValidateRequestHandler(w http.ResponseWriter, r *http.Request) {
5151
}
5252

5353
if !cfg.Cfg.AllowAllUsers {
54-
if !claims.SiteInClaims(r.Host) {
54+
if !claims.SiteInAudience(r.Host) {
5555
send401or200PublicAccess(w, r,
5656
fmt.Errorf("http header 'Host: %s' not authorized for configured `vouch.domains` (is Host being sent properly?)", r.Host))
5757
return

handlers/validate_test.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ func BenchmarkValidateRequestHandler(b *testing.B) {
3232
tokens := structs.PTokens{}
3333
customClaims := structs.CustomClaims{}
3434

35-
userTokenString := jwtmanager.CreateUserTokenString(*user, customClaims, tokens)
35+
userTokenString, err := jwtmanager.NewVPJWT(*user, customClaims, tokens)
36+
assert.NoError(b, err)
3637

3738
c := &http.Cookie{
3839
// Name: cfg.Cfg.Cookie.Name + "_1of1",
@@ -70,12 +71,13 @@ func TestValidateRequestHandlerPerf(t *testing.T) {
7071
tokens := structs.PTokens{}
7172
customClaims := structs.CustomClaims{}
7273

73-
userTokenString := jwtmanager.CreateUserTokenString(*user, customClaims, tokens)
74+
vpjwt, err := jwtmanager.NewVPJWT(*user, customClaims, tokens)
75+
assert.NoError(t, err)
7476

7577
c := &http.Cookie{
7678
// Name: cfg.Cfg.Cookie.Name + "_1of1",
7779
Name: cfg.Cfg.Cookie.Name,
78-
Value: userTokenString,
80+
Value: vpjwt,
7981
Expires: time.Now().Add(1 * time.Hour),
8082
}
8183

@@ -154,7 +156,8 @@ func TestValidateRequestHandlerWithGroupClaims(t *testing.T) {
154156
tokens := structs.PTokens{}
155157

156158
user := &structs.User{Username: "testuser", Email: "test@example.com", Name: "Test Name"}
157-
userTokenString := jwtmanager.CreateUserTokenString(*user, customClaims, tokens)
159+
vpjwt, err := jwtmanager.NewVPJWT(*user, customClaims, tokens)
160+
assert.NoError(t, err)
158161

159162
req, err := http.NewRequest("GET", "/validate", nil)
160163
if err != nil {
@@ -164,7 +167,7 @@ func TestValidateRequestHandlerWithGroupClaims(t *testing.T) {
164167
req.AddCookie(&http.Cookie{
165168
// Name: cfg.Cfg.Cookie.Name + "_1of1",
166169
Name: cfg.Cfg.Cookie.Name,
167-
Value: userTokenString,
170+
Value: vpjwt,
168171
Expires: time.Now().Add(1 * time.Hour),
169172
})
170173

@@ -209,7 +212,8 @@ func TestJWTCacheHandler(t *testing.T) {
209212
tokens := structs.PTokens{}
210213
customClaims := structs.CustomClaims{}
211214

212-
jwt := jwtmanager.CreateUserTokenString(*user, customClaims, tokens)
215+
jwt, err := jwtmanager.NewVPJWT(*user, customClaims, tokens)
216+
assert.NoError(t, err)
213217
badjwt := strings.ReplaceAll(jwt, "a", "z")
214218
badjwt = strings.ReplaceAll(badjwt, "b", "x")
215219

pkg/jwtmanager/jwtmanager.go

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,11 @@ import (
2929
"github.com/vouch/vouch-proxy/pkg/structs"
3030
)
3131

32-
// const numSites = 2
32+
const comma = ","
3333

3434
// VouchClaims jwt Claims specific to vouch
3535
type VouchClaims struct {
36-
Username string `json:"username"`
37-
Sites []string `json:"sites"` // tempting to make this a map but the array is fewer characters in the jwt
36+
Username string `json:"username"`
3837
CustomClaims map[string]interface{}
3938
PAccessToken string
4039
PIdToken string
@@ -44,48 +43,52 @@ type VouchClaims struct {
4443
// StandardClaims jwt.StandardClaims implementation
4544
var StandardClaims jwt.StandardClaims
4645

47-
// CustomClaims implementation
48-
// var CustomClaims map[string]interface{}
49-
50-
// Sites added to VouchClaims
51-
var Sites []string
5246
var logger *zap.Logger
5347
var log *zap.SugaredLogger
48+
var aud string
5449

5550
// Configure see main.go configure()
5651
func Configure() {
5752
log = cfg.Logging.Logger
5853
logger = cfg.Logging.FastLogger
5954
cacheConfigure()
55+
aud = audience()
6056
StandardClaims = jwt.StandardClaims{
61-
Issuer: cfg.Cfg.JWT.Issuer,
57+
Issuer: cfg.Cfg.JWT.Issuer,
58+
Audience: aud,
6259
}
63-
populateSites()
6460
}
6561

66-
func populateSites() {
67-
Sites = make([]string, 0)
62+
// `aud` of the issued JWT https://tools.ietf.org/html/rfc7519#section-4.1.3
63+
func audience() string {
64+
aud := make([]string, 0)
6865
// TODO: the Sites that end up in the JWT come from here
6966
// if we add fine grain ability (ACL?) to the equation
7067
// then we're going to have to add something fancier here
7168
for i := 0; i < len(cfg.Cfg.Domains); i++ {
72-
Sites = append(Sites, cfg.Cfg.Domains[i])
69+
aud = append(aud, cfg.Cfg.Domains[i])
70+
}
71+
if cfg.Cfg.Cookie.Domain != "" {
72+
aud = append(aud, cfg.Cfg.Cookie.Domain)
7373
}
74+
return strings.Join(aud, comma)
7475
}
7576

76-
// CreateUserTokenString converts user to signed jwt
77-
func CreateUserTokenString(u structs.User, customClaims structs.CustomClaims, ptokens structs.PTokens) string {
77+
// NewVPJWT issue a signed Vouch Proxy JWT for a user
78+
func NewVPJWT(u structs.User, customClaims structs.CustomClaims, ptokens structs.PTokens) (string, error) {
7879
// User`token`
7980
// u.PrepareUserData()
8081
claims := VouchClaims{
8182
u.Username,
82-
Sites,
8383
customClaims.Claims,
8484
ptokens.PAccessToken,
8585
ptokens.PIdToken,
8686
StandardClaims,
8787
}
8888

89+
claims.Audience = aud
90+
claims.ExpiresAt = time.Now().Add(time.Minute * time.Duration(cfg.Cfg.JWT.MaxAge)).Unix()
91+
8992
// https://github.com/vouch/vouch-proxy/issues/287
9093
if cfg.Cfg.Headers.AccessToken == "" {
9194
claims.PAccessToken = ""
@@ -95,8 +98,6 @@ func CreateUserTokenString(u structs.User, customClaims structs.CustomClaims, pt
9598
claims.PIdToken = ""
9699
}
97100

98-
claims.StandardClaims.ExpiresAt = time.Now().Add(time.Minute * time.Duration(cfg.Cfg.JWT.MaxAge)).Unix()
99-
100101
// https://godoc.org/github.com/dgrijalva/jwt-go#NewWithClaims
101102
token := jwt.NewWithClaims(jwt.GetSigningMethod("HS256"), claims)
102103

@@ -107,15 +108,15 @@ func CreateUserTokenString(u structs.User, customClaims structs.CustomClaims, pt
107108
ss, err := token.SignedString([]byte(cfg.Cfg.JWT.Secret))
108109
// ss, err := token.SignedString([]byte("testing"))
109110
if ss == "" || err != nil {
110-
log.Errorf("signed token error: %s", err)
111+
return "", fmt.Errorf("New JWT: signed token error: %s", err)
111112
}
112113
if cfg.Cfg.JWT.Compress {
113114
ss, err = compressAndEncodeTokenString(ss)
114115
if ss == "" || err != nil {
115-
log.Errorf("compressed token error: %s", err)
116+
return "", fmt.Errorf("New JWT: compressed token error: %w", err)
116117
}
117118
}
118-
return ss
119+
return ss, nil
119120
}
120121

121122
// TokenIsValid gett better error reporting
@@ -141,11 +142,11 @@ func TokenIsValid(token *jwt.Token, err error) bool {
141142
func SiteInToken(site string, token *jwt.Token) bool {
142143
if claims, ok := token.Claims.(*VouchClaims); ok {
143144
log.Debugf("site %s claim %v", site, claims)
144-
if claims.SiteInClaims(site) {
145+
if claims.SiteInAudience(site) {
145146
return true
146147
}
147148
}
148-
log.Errorf("site %s not found in token", site)
149+
log.Errorf("site %s not found in token audience", site)
149150
return false
150151
}
151152

@@ -168,11 +169,11 @@ func ParseTokenString(tokenString string) (*jwt.Token, error) {
168169

169170
}
170171

171-
// SiteInClaims does the claim contain the value?
172-
func (claims *VouchClaims) SiteInClaims(site string) bool {
173-
for _, s := range claims.Sites {
172+
// SiteInAudience does the claim contain the value?
173+
func (claims *VouchClaims) SiteInAudience(site string) bool {
174+
for _, s := range strings.Split(aud, comma) {
174175
if strings.Contains(site, s) {
175-
log.Debugf("site %s is found for claims.Site %s", site, s)
176+
log.Debugf("site %s is found for claims.Audience %s", site, s)
176177
return true
177178
}
178179
}

pkg/jwtmanager/jwtmanager_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ func init() {
5050

5151
lc = VouchClaims{
5252
u1.Username,
53-
Sites,
5453
customClaims.Claims,
5554
t1.PAccessToken,
5655
t1.PIdToken,
@@ -60,7 +59,7 @@ func init() {
6059
}
6160

6261
func TestClaims(t *testing.T) {
63-
populateSites()
62+
aud = audience()
6463
log.Debugf("jwt config %s %d", string(cfg.Cfg.JWT.Secret), cfg.Cfg.JWT.MaxAge)
6564
assert.NotEmpty(t, cfg.Cfg.JWT.Secret)
6665
assert.NotEmpty(t, cfg.Cfg.JWT.MaxAge)
@@ -70,9 +69,10 @@ func TestClaims(t *testing.T) {
7069
// log.Infof("lc d %s", d.String())
7170
// lc.StandardClaims.ExpiresAt = now.Add(time.Duration(ExpiresAtMinutes) * time.Minute).Unix()
7271
// log.Infof("lc expiresAt %d", now.Unix()-lc.StandardClaims.ExpiresAt)
73-
uts := CreateUserTokenString(u1, customClaims, t1)
72+
uts, err := NewVPJWT(u1, customClaims, t1)
73+
assert.NoError(t, err)
7474
utsParsed, _ := ParseTokenString(uts)
7575
log.Infof("utsParsed: %+v", utsParsed)
76-
log.Infof("Sites: %+v", Sites)
76+
log.Infof("Audience: %+v", aud)
7777
assert.True(t, SiteInToken(cfg.Cfg.Domains[0], utsParsed))
7878
}

0 commit comments

Comments
 (0)