Skip to content
Snippets Groups Projects
Commit 08532fca authored by Rohith's avatar Rohith
Browse files

- switching the claim matches to regexp rather than simple strings

parent d030ccbc
Branches
Tags
No related merge requests found
...@@ -179,12 +179,13 @@ cx.Request.Header.Add("X-Forwarded-Proto", <CLIENT_PROTO>) ...@@ -179,12 +179,13 @@ cx.Request.Header.Add("X-Forwarded-Proto", <CLIENT_PROTO>)
#### **Encryption Key** #### **Encryption Key**
In order to remain stateless and not have to rely on a central cache to persist the 'refresh_tokens', the refresh token is encrypted and added as a cookie using *crypto/aes*. Naturally the key must be the same if your running behind a load balancer etc. In order to remain stateless and not have to rely on a central cache to persist the 'refresh_tokens', the refresh token is encrypted and added as a cookie using *crypto/aes*.
Naturally the key must be the same if your running behind a load balancer etc.
#### **Claim Matching** #### **Claim Matching**
Note, you can add a variable list of claim matches on the presented token by using the --claim 'key=pair' command option or a map 'claims' in the config file (see the example file), before permitting The proxy supports adding a variable list of claim matches against the presented tokens for additional access control. So for example you can match the 'iss' or 'aud' to the token or custom attributes;
access via the proxy each of the claims inside the token are evaluated. note each of the matches are regex's. Examples, --claim 'aud=sso.*' --claim iss=https://.*'
#### **Custom Pages** #### **Custom Pages**
......
...@@ -21,6 +21,7 @@ import ( ...@@ -21,6 +21,7 @@ import (
"io/ioutil" "io/ioutil"
"net/url" "net/url"
"path/filepath" "path/filepath"
"regexp"
"strings" "strings"
"time" "time"
...@@ -92,11 +93,19 @@ func (r *Config) isValid() error { ...@@ -92,11 +93,19 @@ func (r *Config) isValid() error {
r.MaxSession = time.Duration(6) * time.Hour r.MaxSession = time.Duration(6) * time.Hour
} }
} }
// step: valid the resources
for _, resource := range r.Resources { for _, resource := range r.Resources {
if err := resource.isValid(); err != nil { if err := resource.isValid(); err != nil {
return err return err
} }
} }
// step: validate the claims are validate regex's
for k, claim := range r.ClaimsMatch {
// step: validate the regex
if _, err := regexp.Compile(claim); err != nil {
return fmt.Errorf("the claim matcher: %s for claim: %s is not a valid regex", claim, k)
}
}
return nil return nil
} }
......
...@@ -106,6 +106,19 @@ func TestIsConfig(t *testing.T) { ...@@ -106,6 +106,19 @@ func TestIsConfig(t *testing.T) {
}, },
Ok: true, Ok: true,
}, },
{
Config: &Config{
Listen: ":8080",
DiscoveryURL: "http://127.0.0.1:8080",
ClientID: "client",
Secret: "client",
RedirectionURL: "http://120.0.0.1",
Upstream: "http://120.0.0.1",
ClaimsMatch: map[string]string{
"test": "&&&[",
},
},
},
{ {
Config: &Config{ Config: &Config{
Listen: ":8080", Listen: ":8080",
......
...@@ -115,6 +115,6 @@ type Config struct { ...@@ -115,6 +115,6 @@ type Config struct {
SkipTokenVerification bool SkipTokenVerification bool
// Verbose switches on debug logging // Verbose switches on debug logging
Verbose bool `json:"verbose" yaml:"verbose"` Verbose bool `json:"verbose" yaml:"verbose"`
// Hostname is a list of hostnames the service should response to // Hostname is a list of hostname's the service should response to
Hostnames []string `json:"hostnames" yaml:"hostnames"` Hostnames []string `json:"hostnames" yaml:"hostnames"`
} }
...@@ -18,6 +18,7 @@ package main ...@@ -18,6 +18,7 @@ package main
import ( import (
"net/http" "net/http"
"path" "path"
"regexp"
"strings" "strings"
"time" "time"
...@@ -252,6 +253,12 @@ func (r *KeycloakProxy) authenticationHandler() gin.HandlerFunc { ...@@ -252,6 +253,12 @@ func (r *KeycloakProxy) authenticationHandler() gin.HandlerFunc {
// - if everything is ok, we permit the request to pass through // - if everything is ok, we permit the request to pass through
// //
func (r *KeycloakProxy) admissionHandler() gin.HandlerFunc { func (r *KeycloakProxy) admissionHandler() gin.HandlerFunc {
// step: compile the regexs for the claims
claimMatches := make(map[string]*regexp.Regexp, 0)
for k, v := range r.config.ClaimsMatch {
claimMatches[k] = regexp.MustCompile(v)
}
return func(cx *gin.Context) { return func(cx *gin.Context) {
// step: if authentication is required on this, grab the resource spec // step: if authentication is required on this, grab the resource spec
ur, found := cx.Get(cxEnforce) ur, found := cx.Get(cxEnforce)
...@@ -286,7 +293,7 @@ func (r *KeycloakProxy) admissionHandler() gin.HandlerFunc { ...@@ -286,7 +293,7 @@ func (r *KeycloakProxy) admissionHandler() gin.HandlerFunc {
// step: if we have any claim matching, validate the tokens has the claims // step: if we have any claim matching, validate the tokens has the claims
// @TODO we should probably convert the claim checks to regexs // @TODO we should probably convert the claim checks to regexs
for claimName, match := range r.config.ClaimsMatch { for claimName, match := range claimMatches {
// step: if the claim is NOT in the token, we access deny // step: if the claim is NOT in the token, we access deny
value, found, err := identity.claims.StringClaim(claimName) value, found, err := identity.claims.StringClaim(claimName)
if err != nil { if err != nil {
...@@ -316,7 +323,7 @@ func (r *KeycloakProxy) admissionHandler() gin.HandlerFunc { ...@@ -316,7 +323,7 @@ func (r *KeycloakProxy) admissionHandler() gin.HandlerFunc {
} }
// step: check the claim is the same // step: check the claim is the same
if value != match { if !match.MatchString(value) {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"access": "denied", "access": "denied",
"username": identity.name, "username": identity.name,
......
...@@ -20,6 +20,7 @@ import ( ...@@ -20,6 +20,7 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/gambol99/go-oidc/jose"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
...@@ -266,6 +267,95 @@ func TestAdmissionHandlerRoles(t *testing.T) { ...@@ -266,6 +267,95 @@ func TestAdmissionHandlerRoles(t *testing.T) {
} }
} }
func TestAdmissionHandlerClaims(t *testing.T) {
// allow any fake authed users
proxy := newFakeKeycloakProxyWithResources(t, []*Resource{
{
URL: "/admin",
Methods: []string{"ANY"},
},
})
tests := []struct {
Matches map[string]string
Context *gin.Context
UserContext *userContext
HTTPCode int
}{
{
Matches: map[string]string{"iss": "test"},
Context: newFakeGinContext("GET", "/admin"),
UserContext: &userContext{
claims: jose.Claims{},
},
HTTPCode: http.StatusForbidden,
},
{
Matches: map[string]string{"iss": "^tes$"},
Context: newFakeGinContext("GET", "/admin"),
UserContext: &userContext{
claims: jose.Claims{"iss": 1},
},
HTTPCode: http.StatusForbidden,
},
{
Matches: map[string]string{"iss": "^tes$"},
Context: newFakeGinContext("GET", "/admin"),
UserContext: &userContext{
claims: jose.Claims{"iss": "bad_match"},
},
HTTPCode: http.StatusForbidden,
},
{
Matches: map[string]string{"iss": "^test", "notfound": "someting"},
Context: newFakeGinContext("GET", "/admin"),
UserContext: &userContext{
claims: jose.Claims{"iss": "test"},
},
HTTPCode: http.StatusForbidden,
},
{
Matches: map[string]string{"iss": "^test", "notfound": "someting"},
Context: newFakeGinContext("GET", "/admin"),
UserContext: &userContext{
claims: jose.Claims{"iss": "test"},
},
HTTPCode: http.StatusForbidden,
},
{
Matches: map[string]string{"iss": ".*"},
Context: newFakeGinContext("GET", "/admin"),
UserContext: &userContext{
claims: jose.Claims{"iss": "test"},
},
HTTPCode: http.StatusOK,
},
{
Matches: map[string]string{"iss": "^t.*$"},
Context: newFakeGinContext("GET", "/admin"),
UserContext: &userContext{
claims: jose.Claims{"iss": "test"},
},
HTTPCode: http.StatusOK,
},
}
for i, c := range tests {
// step: if closure so we need to get the handler each time
proxy.config.ClaimsMatch = c.Matches
handler := proxy.admissionHandler()
// step: inject a resource
c.Context.Set(cxEnforce, proxy.config.Resources[0])
c.Context.Set(userContextName, c.UserContext)
handler(c.Context)
if c.Context.Writer.Status() != c.HTTPCode {
t.Errorf("test case %d should have recieved code: %d, got %d", i, c.HTTPCode, c.Context.Writer.Status())
}
}
}
func TestSecurityHandler(t *testing.T) { func TestSecurityHandler(t *testing.T) {
kc := newFakeKeycloakProxy(t) kc := newFakeKeycloakProxy(t)
handler := kc.securityHandler() handler := kc.securityHandler()
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment