kubernetes apiserver integration (#1063)

* sessions: support bearer tokens in authorization

* wip

* remove dead code

* refactor signed jwt code

* use function

* update per comments

* fix test
This commit is contained in:
Caleb Doxsey 2020-07-14 08:33:24 -06:00 committed by GitHub
parent 5f6a67e6eb
commit a70254ab76
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 140 additions and 57 deletions

View file

@ -38,8 +38,9 @@ const (
// Evaluator specifies the interface for a policy engine.
type Evaluator struct {
rego *rego.Rego
query rego.PreparedEvalQuery
rego *rego.Rego
query rego.PreparedEvalQuery
policies []config.Policy
clientCA string
authenticateHost string
@ -51,6 +52,7 @@ type Evaluator struct {
func New(options *config.Options) (*Evaluator, error) {
e := &Evaluator{
authenticateHost: options.AuthenticateURL.Host,
policies: options.Policies,
}
if options.ClientCA != "" {
e.clientCA = options.ClientCA
@ -129,33 +131,40 @@ func (e *Evaluator) Evaluate(ctx context.Context, req *Request) (*Result, error)
return &deny[0], nil
}
signedJWT, err := e.SignedJWT(req)
payload := e.JWTPayload(req)
signedJWT, err := e.SignedJWT(payload)
if err != nil {
return nil, fmt.Errorf("error signing JWT: %w", err)
}
evalResult := &Result{
MatchingPolicy: getMatchingPolicy(res[0].Bindings.WithoutWildcards(), e.policies),
SignedJWT: signedJWT,
}
if e, ok := payload["email"].(string); ok {
evalResult.UserEmail = e
}
if gs, ok := payload["groups"].([]string); ok {
evalResult.UserGroups = gs
}
allow := allowed(res[0].Bindings.WithoutWildcards())
if allow {
return &Result{
Status: http.StatusOK,
Message: "OK",
SignedJWT: signedJWT,
}, nil
evalResult.Status = http.StatusOK
evalResult.Message = "OK"
return evalResult, nil
}
if req.Session.ID == "" {
return &Result{
Status: http.StatusUnauthorized,
Message: "login required",
SignedJWT: signedJWT,
}, nil
evalResult.Status = http.StatusUnauthorized
evalResult.Message = "login required"
return evalResult, nil
}
return &Result{
Status: http.StatusForbidden,
Message: "forbidden",
SignedJWT: signedJWT,
}, nil
evalResult.Status = http.StatusForbidden
evalResult.Message = "forbidden"
return evalResult, nil
}
// ParseSignedJWT parses the input signature and return its payload.
@ -167,17 +176,8 @@ func (e *Evaluator) ParseSignedJWT(signature string) ([]byte, error) {
return object.Verify(&(e.jwk.(*ecdsa.PrivateKey).PublicKey))
}
// SignedJWT returns the signature of given request.
func (e *Evaluator) SignedJWT(req *Request) (string, error) {
signerOpt := &jose.SignerOptions{}
signer, err := jose.NewSigner(jose.SigningKey{
Algorithm: jose.ES256,
Key: e.jwk,
}, signerOpt.WithHeader("kid", e.kid))
if err != nil {
return "", err
}
// JWTPayload returns the JWT payload for a request.
func (e *Evaluator) JWTPayload(req *Request) map[string]interface{} {
payload := map[string]interface{}{
"iss": e.authenticateHost,
}
@ -200,6 +200,19 @@ func (e *Evaluator) SignedJWT(req *Request) (string, error) {
payload["groups"] = du.GetGroups()
}
}
return payload
}
// SignedJWT returns the signature of given request.
func (e *Evaluator) SignedJWT(payload map[string]interface{}) (string, error) {
signerOpt := &jose.SignerOptions{}
signer, err := jose.NewSigner(jose.SigningKey{
Algorithm: jose.ES256,
Key: e.jwk,
}, signerOpt.WithHeader("kid", e.kid))
if err != nil {
return "", err
}
bs, err := json.Marshal(payload)
if err != nil {
@ -266,9 +279,31 @@ type (
// Result is the result of evaluation.
type Result struct {
Status int
Message string
SignedJWT string
Status int
Message string
SignedJWT string
MatchingPolicy *config.Policy
UserEmail string
UserGroups []string
}
func getMatchingPolicy(vars rego.Vars, policies []config.Policy) *config.Policy {
result, ok := vars["result"].(map[string]interface{})
if !ok {
return nil
}
idx, err := strconv.Atoi(fmt.Sprint(result["route_policy_idx"]))
if err != nil {
return nil
}
if idx >= len(policies) {
return nil
}
return &policies[idx]
}
func allowed(vars rego.Vars) bool {

View file

@ -80,7 +80,7 @@ func TestEvaluator_SignedJWT(t *testing.T) {
URL: "https://example.com",
},
}
signedJWT, err := e.SignedJWT(req)
signedJWT, err := e.SignedJWT(e.JWTPayload(req))
require.NoError(t, err)
assert.NotEmpty(t, signedJWT)
@ -101,7 +101,7 @@ func TestEvaluator_JWTWithKID(t *testing.T) {
URL: "https://example.com",
},
}
signedJWT, err := e.SignedJWT(req)
signedJWT, err := e.SignedJWT(e.JWTPayload(req))
require.NoError(t, err)
assert.NotEmpty(t, signedJWT)

View file

@ -3,7 +3,8 @@ package pomerium.authz
default allow = false
route_policy := first_allowed_route_policy(input.http.url)
route_policy_idx := first_allowed_route_policy_idx(input.http.url)
route_policy := data.route_policies[route_policy_idx]
session := input.databroker_data.session
user := input.databroker_data.user
directory_user := input.databroker_data.directory_user
@ -84,8 +85,8 @@ deny[reason] {
}
# returns the first matching route
first_allowed_route_policy(input_url) = first_policy {
first_policy := [policy | some i, policy; policy = data.route_policies[i]; allowed_route(input.http.url, policy)][0]
first_allowed_route_policy_idx(input_url) = first_policy_idx {
first_policy_idx := [idx | some idx, policy; policy = data.route_policies[idx]; allowed_route(input.http.url, policy)][0]
}
allowed_route(input_url, policy){

File diff suppressed because one or more lines are too long