Skip to content

Commit

Permalink
#354 clarify audience (aud) check
Browse files Browse the repository at this point in the history
* move claims.Sites into claims.Audience
* move check Site to check Audience
* general cleanup
* rename CreateUserTokenString to NewVPJWT
  • Loading branch information
bnfinet committed Jan 31, 2021
1 parent 10f1000 commit f926918
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 43 deletions.
12 changes: 9 additions & 3 deletions handlers/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,13 @@ func AuthStateHandler(w http.ResponseWriter, r *http.Request) {
// SUCCESS!! they are authorized

// issue the jwt
tokenstring := jwtmanager.CreateUserTokenString(user, customClaims, ptokens)

tokenstring, err := jwtmanager.NewVPJWT(user, customClaims, ptokens)
if err != nil {
responses.Error500(w, r, fmt.Errorf("/auth Token creation failure: %w . Please seek support from your administrator", err))
return

}
cookie.SetCookie(w, r, tokenstring)

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

// nothing configured, allow everyone through
default:
Expand Down
4 changes: 2 additions & 2 deletions handlers/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,6 @@ func init() {

lc = jwtmanager.VouchClaims{
u1.Username,
jwtmanager.Sites,
customClaims.Claims,
t1.PAccessToken,
t1.PIdToken,
Expand All @@ -164,7 +163,8 @@ func TestParsedIdPTokens(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
setUp(tt.configFile)
uts := jwtmanager.CreateUserTokenString(u1, customClaims, t1)
uts, err := jwtmanager.NewVPJWT(u1, customClaims, t1)
assert.NoError(t, err)
utsParsed, _ := jwtmanager.ParseTokenString(uts)
utsPtokens, _ := jwtmanager.PTokenClaims(utsParsed)

Expand Down
2 changes: 1 addition & 1 deletion handlers/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func ValidateRequestHandler(w http.ResponseWriter, r *http.Request) {
}

if !cfg.Cfg.AllowAllUsers {
if !claims.SiteInClaims(r.Host) {
if !claims.SiteInAudience(r.Host) {
send401or200PublicAccess(w, r,
fmt.Errorf("http header 'Host: %s' not authorized for configured `vouch.domains` (is Host being sent properly?)", r.Host))
return
Expand Down
16 changes: 10 additions & 6 deletions handlers/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ func BenchmarkValidateRequestHandler(b *testing.B) {
tokens := structs.PTokens{}
customClaims := structs.CustomClaims{}

userTokenString := jwtmanager.CreateUserTokenString(*user, customClaims, tokens)
userTokenString, err := jwtmanager.NewVPJWT(*user, customClaims, tokens)
assert.NoError(b, err)

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

userTokenString := jwtmanager.CreateUserTokenString(*user, customClaims, tokens)
vpjwt, err := jwtmanager.NewVPJWT(*user, customClaims, tokens)
assert.NoError(t, err)

c := &http.Cookie{
// Name: cfg.Cfg.Cookie.Name + "_1of1",
Name: cfg.Cfg.Cookie.Name,
Value: userTokenString,
Value: vpjwt,
Expires: time.Now().Add(1 * time.Hour),
}

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

user := &structs.User{Username: "testuser", Email: "[email protected]", Name: "Test Name"}
userTokenString := jwtmanager.CreateUserTokenString(*user, customClaims, tokens)
vpjwt, err := jwtmanager.NewVPJWT(*user, customClaims, tokens)
assert.NoError(t, err)

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

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

jwt := jwtmanager.CreateUserTokenString(*user, customClaims, tokens)
jwt, err := jwtmanager.NewVPJWT(*user, customClaims, tokens)
assert.NoError(t, err)
badjwt := strings.ReplaceAll(jwt, "a", "z")
badjwt = strings.ReplaceAll(badjwt, "b", "x")

Expand Down
55 changes: 28 additions & 27 deletions pkg/jwtmanager/jwtmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,11 @@ import (
"github.com/vouch/vouch-proxy/pkg/structs"
)

// const numSites = 2
const comma = ","

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

// CustomClaims implementation
// var CustomClaims map[string]interface{}

// Sites added to VouchClaims
var Sites []string
var logger *zap.Logger
var log *zap.SugaredLogger
var aud string

// Configure see main.go configure()
func Configure() {
log = cfg.Logging.Logger
logger = cfg.Logging.FastLogger
cacheConfigure()
aud = audience()
StandardClaims = jwt.StandardClaims{
Issuer: cfg.Cfg.JWT.Issuer,
Issuer: cfg.Cfg.JWT.Issuer,
Audience: aud,
}
populateSites()
}

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

// CreateUserTokenString converts user to signed jwt
func CreateUserTokenString(u structs.User, customClaims structs.CustomClaims, ptokens structs.PTokens) string {
// NewVPJWT issue a signed Vouch Proxy JWT for a user
func NewVPJWT(u structs.User, customClaims structs.CustomClaims, ptokens structs.PTokens) (string, error) {
// User`token`
// u.PrepareUserData()
claims := VouchClaims{
u.Username,
Sites,
customClaims.Claims,
ptokens.PAccessToken,
ptokens.PIdToken,
StandardClaims,
}

claims.Audience = aud
claims.ExpiresAt = time.Now().Add(time.Minute * time.Duration(cfg.Cfg.JWT.MaxAge)).Unix()

// https://github.com/vouch/vouch-proxy/issues/287
if cfg.Cfg.Headers.AccessToken == "" {
claims.PAccessToken = ""
Expand All @@ -95,8 +98,6 @@ func CreateUserTokenString(u structs.User, customClaims structs.CustomClaims, pt
claims.PIdToken = ""
}

claims.StandardClaims.ExpiresAt = time.Now().Add(time.Minute * time.Duration(cfg.Cfg.JWT.MaxAge)).Unix()

// https://godoc.org/github.com/dgrijalva/jwt-go#NewWithClaims
token := jwt.NewWithClaims(jwt.GetSigningMethod("HS256"), claims)

Expand All @@ -107,15 +108,15 @@ func CreateUserTokenString(u structs.User, customClaims structs.CustomClaims, pt
ss, err := token.SignedString([]byte(cfg.Cfg.JWT.Secret))
// ss, err := token.SignedString([]byte("testing"))
if ss == "" || err != nil {
log.Errorf("signed token error: %s", err)
return "", fmt.Errorf("New JWT: signed token error: %s", err)
}
if cfg.Cfg.JWT.Compress {
ss, err = compressAndEncodeTokenString(ss)
if ss == "" || err != nil {
log.Errorf("compressed token error: %s", err)
return "", fmt.Errorf("New JWT: compressed token error: %w", err)
}
}
return ss
return ss, nil
}

// TokenIsValid gett better error reporting
Expand All @@ -141,11 +142,11 @@ func TokenIsValid(token *jwt.Token, err error) bool {
func SiteInToken(site string, token *jwt.Token) bool {
if claims, ok := token.Claims.(*VouchClaims); ok {
log.Debugf("site %s claim %v", site, claims)
if claims.SiteInClaims(site) {
if claims.SiteInAudience(site) {
return true
}
}
log.Errorf("site %s not found in token", site)
log.Errorf("site %s not found in token audience", site)
return false
}

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

}

// SiteInClaims does the claim contain the value?
func (claims *VouchClaims) SiteInClaims(site string) bool {
for _, s := range claims.Sites {
// SiteInAudience does the claim contain the value?
func (claims *VouchClaims) SiteInAudience(site string) bool {
for _, s := range strings.Split(aud, comma) {
if strings.Contains(site, s) {
log.Debugf("site %s is found for claims.Site %s", site, s)
log.Debugf("site %s is found for claims.Audience %s", site, s)
return true
}
}
Expand Down
8 changes: 4 additions & 4 deletions pkg/jwtmanager/jwtmanager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ func init() {

lc = VouchClaims{
u1.Username,
Sites,
customClaims.Claims,
t1.PAccessToken,
t1.PIdToken,
Expand All @@ -60,7 +59,7 @@ func init() {
}

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

0 comments on commit f926918

Please sign in to comment.