Skip to content

Commit

Permalink
fix #183 clean claims to present proper headers
Browse files Browse the repository at this point in the history
  • Loading branch information
bnfinet committed Apr 22, 2020
1 parent 247d322 commit 7b5cb57
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 121 deletions.
4 changes: 2 additions & 2 deletions handlers/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ func MapClaims(claims []byte, customClaims *structs.CustomClaims) error {
m := f.(map[string]interface{})
for k := range m {
var found = false
for _, e := range cfg.Cfg.Headers.Claims {
if k == e {
for claim := range cfg.Cfg.Headers.ClaimsCleaned {
if k == claim {
found = true
}
}
Expand Down
16 changes: 8 additions & 8 deletions handlers/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,29 +214,29 @@ func ValidateRequestHandler(w http.ResponseWriter, r *http.Request) {
return
}
}
if len(cfg.Cfg.Headers.Claims) > 0 {

if len(cfg.Cfg.Headers.ClaimsCleaned) > 0 {
log.Debug("Found claims in config, finding specific keys...")
// Run through all the claims found
for k, v := range claims.CustomClaims {
// Run through the claims we are looking for
for _, cv := range cfg.Cfg.Headers.Claims {
for claim, header := range cfg.Cfg.Headers.ClaimsCleaned {
// Check for matching claim
if cv == k {
if claim == k {
log.Debug("Found matching claim key: ", k)
customHeader := strings.Join([]string{cfg.Cfg.Headers.ClaimHeader, k}, "")
// convert to string
val := fmt.Sprint(v)
if reflect.TypeOf(val).Kind() == reflect.String {
// if val, ok := v.(string); ok {
w.Header().Add(customHeader, val)
log.Debug("Adding header for claim: ", k, " Name: ", customHeader, " Value: ", val)
log.Debugf("Adding header for claim %s - %s: %s", k, header, val)
w.Header().Add(header, val)
} else if val, ok := v.([]interface{}); ok {
strs := make([]string, len(val))
for i, v := range val {
strs[i] = fmt.Sprintf("\"%s\"", v)
}
log.Debug("Adding header for claim: ", k, " Name: ", customHeader, " Value: ", strings.Join(strs, ","))
w.Header().Add(customHeader, strings.Join(strs, ","))
log.Debugf("Adding header for claim %s - %s: %s", k, header, val)
w.Header().Add(header, strings.Join(strs, ","))
} else {
log.Errorf("Couldn't parse header type for %s %+v. Please submit an issue.", k, v)
}
Expand Down
164 changes: 54 additions & 110 deletions pkg/cfg/cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@ import (
"errors"
"flag"
"fmt"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"strings"

"golang.org/x/oauth2"
"golang.org/x/oauth2/github"
"golang.org/x/oauth2/google"

"github.com/spf13/viper"
securerandom "github.com/theckman/go-securerandom"
Expand Down Expand Up @@ -45,15 +43,16 @@ type Config struct {
}

Headers struct {
JWT string `mapstructure:"jwt"`
User string `mapstructure:"user"`
QueryString string `mapstructure:"querystring"`
Redirect string `mapstructure:"redirect"`
Success string `mapstructure:"success"`
ClaimHeader string `mapstructure:"claimheader"`
Claims []string `mapstructure:"claims"`
AccessToken string `mapstructure:"accesstoken"`
IDToken string `mapstructure:"idtoken"`
JWT string `mapstructure:"jwt"`
User string `mapstructure:"user"`
QueryString string `mapstructure:"querystring"`
Redirect string `mapstructure:"redirect"`
Success string `mapstructure:"success"`
ClaimHeader string `mapstructure:"claimheader"`
Claims []string `mapstructure:"claims"`
AccessToken string `mapstructure:"accesstoken"`
IDToken string `mapstructure:"idtoken"`
ClaimsCleaned map[string]string // the rawClaim is mapped to the actual claims header
}
Session struct {
Name string `mapstructure:"name"`
Expand Down Expand Up @@ -183,7 +182,7 @@ func Configure() {
parseConfig()
Logging.configure()
setDefaults()

cleanClaimsHeaders()
if *CmdLine.port != -1 {
Cfg.Port = *CmdLine.port
}
Expand Down Expand Up @@ -244,6 +243,7 @@ func InitForTestPurposesWithProvider(provider string) {
GenOAuth.Provider = provider
setProviderDefaults()
}
cleanClaimsHeaders()

}

Expand Down Expand Up @@ -485,110 +485,54 @@ func setDefaults() {
}
}

func setProviderDefaults() {
if GenOAuth.Provider == Providers.Google {
setDefaultsGoogle()
// setDefaultsGoogle also configures the OAuthClient
} else if GenOAuth.Provider == Providers.GitHub {
setDefaultsGitHub()
configureOAuthClient()
} else if GenOAuth.Provider == Providers.ADFS {
setDefaultsADFS()
configureOAuthClient()
} else {
// IndieAuth, OIDC, OpenStax, Nextcloud
configureOAuthClient()
}
}

func setDefaultsGoogle() {
log.Info("configuring Google OAuth")
GenOAuth.UserInfoURL = "https://www.googleapis.com/oauth2/v3/userinfo"
if len(GenOAuth.Scopes) == 0 {
// You have to select a scope from
// https://developers.google.com/identity/protocols/googlescopes#google_sign-in
GenOAuth.Scopes = []string{"email"}
}
OAuthClient = &oauth2.Config{
ClientID: GenOAuth.ClientID,
ClientSecret: GenOAuth.ClientSecret,
Scopes: GenOAuth.Scopes,
Endpoint: google.Endpoint,
}
if GenOAuth.PreferredDomain != "" {
log.Infof("setting Google OAuth preferred login domain param 'hd' to %s", GenOAuth.PreferredDomain)
OAuthopts = oauth2.SetAuthURLParam("hd", GenOAuth.PreferredDomain)
}
}

func setDefaultsADFS() {
log.Info("configuring ADFS OAuth")
OAuthopts = oauth2.SetAuthURLParam("resource", GenOAuth.RedirectURL) // Needed or all claims won't be included
}

func setDefaultsGitHub() {
// log.Info("configuring GitHub OAuth")
if GenOAuth.AuthURL == "" {
GenOAuth.AuthURL = github.Endpoint.AuthURL
}
if GenOAuth.TokenURL == "" {
GenOAuth.TokenURL = github.Endpoint.TokenURL
}
if GenOAuth.UserInfoURL == "" {
GenOAuth.UserInfoURL = "https://api.github.com/user?access_token="
}
if GenOAuth.UserTeamURL == "" {
GenOAuth.UserTeamURL = "https://api.github.com/orgs/:org_id/teams/:team_slug/memberships/:username?access_token="
}
if GenOAuth.UserOrgURL == "" {
GenOAuth.UserOrgURL = "https://api.github.com/orgs/:org_id/members/:username?access_token="
}
if len(GenOAuth.Scopes) == 0 {
// https://github.com/vouch/vouch-proxy/issues/63
// https://developer.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps/
GenOAuth.Scopes = []string{"read:user"}

if len(Cfg.TeamWhiteList) > 0 {
GenOAuth.Scopes = append(GenOAuth.Scopes, "read:org")
func claimToHeader(claim string) (string, error) {
was := claim

// Auth0 allows "namespaceing" of claims and represents them as URLs
claim = strings.TrimPrefix(claim, "http://")
claim = strings.TrimPrefix(claim, "https://")

// not allowed in header: "(),/:;<=>?@[\]{}"
// https://greenbytes.de/tech/webdav/rfc7230.html#rfc.section.3.2.6
// and we don't allow underscores because nginx doesn't like them
// http://nginx.org/en/docs/http/ngx_http_core_module.html#underscores_in_headers
for _, r := range `"(),/\:;<=>?@[]{}_` {
claim = strings.ReplaceAll(claim, string(r), "-")
}

// The field-name must be composed of printable ASCII characters (i.e., characters)
// that have values between 33. and 126., decimal, except colon).
// https://github.com/vouch/vouch-proxy/issues/183#issuecomment-564427548
// get the rune (char) for each claim character
for _, r := range claim {
// log.Debugf("claimToHeader rune %c - %d", r, r)
if r < 33 || r > 126 {
log.Debugf("%s.header.claims %s includes character %c, replacing with '-'", Branding.CcName, was, r)
claim = strings.Replace(claim, string(r), "-", 1)
}
}
}

func configureOAuthClient() {
log.Infof("configuring %s OAuth with Endpoint %s", GenOAuth.Provider, GenOAuth.AuthURL)
OAuthClient = &oauth2.Config{
ClientID: GenOAuth.ClientID,
ClientSecret: GenOAuth.ClientSecret,
Endpoint: oauth2.Endpoint{
AuthURL: GenOAuth.AuthURL,
TokenURL: GenOAuth.TokenURL,
},
RedirectURL: GenOAuth.RedirectURL,
Scopes: GenOAuth.Scopes,
claim = Cfg.Headers.ClaimHeader + http.CanonicalHeaderKey(claim)
if claim != was {
log.Infof("%s.header.claims %s will be forwarded downstream in the Header %s", Branding.CcName, was, claim)
log.Debugf("nginx will popultate the variable $auth_resp_%s", strings.ReplaceAll(strings.ToLower(claim), "-", "_"))
}
// log.Errorf("%s.header.claims %s will be forwarded in the Header %s", Branding.CcName, was, claim)
return claim, nil

}

func getOrGenerateJWTSecret() string {
b, err := ioutil.ReadFile(secretFile)
if err == nil {
log.Info("jwt.secret read from " + secretFile)
} else {
// then generate a new secret and store it in the file
log.Debug(err)
log.Info("jwt.secret not found in " + secretFile)
log.Warn("generating random jwt.secret and storing it in " + secretFile)
// fix the claims headers
// https://github.com/vouch/vouch-proxy/issues/183

// make sure to create 256 bits for the secret
// see https://github.com/vouch/vouch-proxy/issues/54
rstr, err := securerandom.Base64OfBytes(base64Bytes)
func cleanClaimsHeaders() error {
cleanedHeaders := make(map[string]string)
for _, claim := range Cfg.Headers.Claims {
header, err := claimToHeader(claim)
if err != nil {
log.Fatal(err)
}
b = []byte(rstr)
err = ioutil.WriteFile(secretFile, b, 0600)
if err != nil {
log.Debug(err)
return err
}
cleanedHeaders[claim] = header
}
return string(b)
Cfg.Headers.ClaimsCleaned = cleanedHeaders
return nil
}
26 changes: 26 additions & 0 deletions pkg/cfg/cfg_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,29 @@ func TestSetGitHubDefaultsWithTeamWhitelist(t *testing.T) {
assert.Contains(t, GenOAuth.Scopes, "read:user")
assert.Contains(t, GenOAuth.Scopes, "read:org")
}

func Test_claimToHeader(t *testing.T) {
tests := []struct {
name string
arg string
want string
wantErr bool
}{
{"remove http://", "http://test.example.com", Cfg.Headers.ClaimHeader + "Test.example.com", false},
{"remove https://", "https://test.example.com", Cfg.Headers.ClaimHeader + "Test.example.com", false},
{"auth0 fix https://", "https://test.auth0.com/user", Cfg.Headers.ClaimHeader + "Test.auth0.com-User", false},
{"cognito user:groups", "user:groups", Cfg.Headers.ClaimHeader + "User-Groups", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := claimToHeader(tt.arg)
if (err != nil) != tt.wantErr {
t.Errorf("claimToHeader() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("claimToHeader() = %v, want %v", got, tt.want)
}
})
}
}
2 changes: 1 addition & 1 deletion pkg/jwtmanager/jwtmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ type VouchClaims struct {
var StandardClaims jwt.StandardClaims

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

// Sites added to VouchClaims
var Sites []string
Expand Down

0 comments on commit 7b5cb57

Please sign in to comment.