Define policies in doorman package

This commit is contained in:
Mathieu Leplatre 2017-12-01 11:54:23 +01:00
Родитель f22de05d8f
Коммит 4805963990
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 767B105F81A15CDD
5 изменённых файлов: 105 добавлений и 79 удалений

Просмотреть файл

@ -9,6 +9,56 @@ type Config struct {
Sources []string
}
// Tags map tag names to principals.
type Tags map[string]Principals
// Condition either do or do not fulfill an access request.
type Condition struct {
Type string
Options map[string]interface{}
}
// Conditions is a collection of conditions.
type Conditions map[string]Condition
// Policy represents an access control.
type Policy struct {
ID string
Description string
Principals []string
Effect string
Resources []string
Actions []string
Conditions Conditions
}
// Policies is a collection of policies.
type Policies []Policy
// ServiceConfig represents the policies file content.
type ServiceConfig struct {
Service string
JWTIssuer string `yaml:"jwtIssuer"`
Tags Tags
Policies Policies
}
// GetTags returns the tags principals for the ones specified.
func (c *ServiceConfig) GetTags(principals Principals) Principals {
result := Principals{}
for tag, members := range c.Tags {
for _, member := range members {
for _, principal := range principals {
if principal == member {
prefixed := fmt.Sprintf("tag:%s", tag)
result = append(result, prefixed)
}
}
}
}
return result
}
// Context is used as request's context.
type Context map[string]interface{}

Просмотреть файл

@ -1,6 +1,7 @@
package doorman
import (
"encoding/json"
"fmt"
"github.com/ory/ladon"
@ -10,48 +11,23 @@ import (
const maxInt int64 = 1<<63 - 1
// Tags map tag names to principals.
type Tags map[string]Principals
// LadonDoorman is the backend in charge of checking requests against policies.
type LadonDoorman struct {
config Config
services map[string]*ServiceConfig
_auditLogger *auditLogger
}
// ServiceConfig represents the policies file content.
type ServiceConfig struct {
Service string
JWTIssuer string `json:"jwtIssuer"`
Tags Tags
Policies []*ladon.DefaultPolicy
ladon *ladon.Ladon
jwtValidator JWTValidator
}
// GetTags returns the tags principals for the ones specified.
func (c *ServiceConfig) GetTags(principals Principals) Principals {
result := Principals{}
for tag, members := range c.Tags {
for _, member := range members {
for _, principal := range principals {
if principal == member {
prefixed := fmt.Sprintf("tag:%s", tag)
result = append(result, prefixed)
}
}
}
}
return result
ladons map[string]*ladon.Ladon
jwtValidators map[string]JWTValidator
}
// NewDefaultLadon instantiates a new doorman.
func NewDefaultLadon(config Config) *LadonDoorman {
w := &LadonDoorman{
config: config,
services: map[string]*ServiceConfig{},
config: config,
services: map[string]*ServiceConfig{},
ladons: map[string]*ladon.Ladon{},
jwtValidators: map[string]JWTValidator{},
}
return w
}
@ -66,6 +42,8 @@ func (doorman *LadonDoorman) auditLogger() *auditLogger {
// LoadPolicies (re)loads configuration and policies from the YAML files.
func (doorman *LadonDoorman) LoadPolicies() error {
// First, load each configuration file.
newLadons := map[string]*ladon.Ladon{}
newJWTValidators := map[string]JWTValidator{}
newConfigs := map[string]*ServiceConfig{}
for _, source := range doorman.config.Sources {
services, err := loadSource(source)
@ -84,18 +62,45 @@ func (doorman *LadonDoorman) LoadPolicies() error {
if err != nil {
return err
}
config.jwtValidator = v
newJWTValidators[config.Service] = v
} else {
log.Warningf("No JWT verification for %q.", config.Service)
}
config.ladon = &ladon.Ladon{
newLadons[config.Service] = &ladon.Ladon{
Manager: manager.NewMemoryManager(),
AuditLogger: doorman.auditLogger(),
}
for _, pol := range config.Policies {
log.Debugf("Load policy %q: %s", pol.GetID(), pol.GetDescription())
err := config.ladon.Manager.Create(pol)
log.Debugf("Load policy %q: %s", pol.ID, pol.Description)
var conditions = ladon.Conditions{}
for field, cond := range pol.Conditions {
factory, found := ladon.ConditionFactories[cond.Type]
if !found {
return fmt.Errorf("unknown condition type %s", cond.Type)
}
c := factory()
if len(cond.Options) > 0 {
// Leverage Ladon JSON unmarshall code to instantiate conditions.
str, _ := json.Marshal(cond.Options)
if err := json.Unmarshal(str, c); err != nil {
return err
}
}
conditions.AddCondition(field, c)
}
policy := &ladon.DefaultPolicy{
ID: pol.ID,
Description: pol.Description,
Subjects: pol.Principals,
Effect: pol.Effect,
Resources: pol.Resources,
Actions: pol.Actions,
Conditions: conditions,
}
err := newLadons[config.Service].Manager.Create(policy)
if err != nil {
return err
}
@ -105,16 +110,18 @@ func (doorman *LadonDoorman) LoadPolicies() error {
}
// Only if everything went well, replace existing services with new ones.
doorman.services = newConfigs
doorman.ladons = newLadons
doorman.jwtValidators = newJWTValidators
return nil
}
// JWTValidator returns the JWT validator for the specified service.
func (doorman *LadonDoorman) JWTValidator(service string) (JWTValidator, error) {
c, ok := doorman.services[service]
v, ok := doorman.jwtValidators[service]
if !ok {
return nil, fmt.Errorf("unknown service %q", service)
}
return c.jwtValidator, nil
return v, nil
}
// IsAllowed is responsible for deciding if subject can perform action on a resource with a context.
@ -131,7 +138,7 @@ func (doorman *LadonDoorman) IsAllowed(service string, request *Request) bool {
Context: context,
}
c, ok := doorman.services[service]
l, ok := doorman.ladons[service]
if !ok {
// Explicitly log denied request using audit logger.
doorman.auditLogger().logRequest(false, r, ladon.Policies{})
@ -141,7 +148,7 @@ func (doorman *LadonDoorman) IsAllowed(service string, request *Request) bool {
// For each principal, use it as the subject and query ladon backend.
for _, principal := range request.Principals {
r.Subject = principal
if err := c.ladon.IsAllowed(r); err == nil {
if err := l.IsAllowed(r); err == nil {
return true
}
}

Просмотреть файл

@ -1,8 +1,6 @@
package doorman
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"os"
@ -10,8 +8,6 @@ import (
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v2"
"github.com/mozilla/doorman/utilities"
)
type fileLoader struct{}
@ -67,23 +63,9 @@ func loadFile(filename string) (*ServiceConfig, error) {
if len(fileContent) == 0 {
return nil, fmt.Errorf("empty file %q", filename)
}
// Replace "principals" in config by "subjects" (ladon vocabulary)
adjusted := bytes.Replace(fileContent, []byte("principals:"), []byte("subjects:"), -1)
// Ladon does not support un/marshaling YAML.
// https://github.com/ory/ladon/issues/83
var generic interface{}
if err := yaml.Unmarshal(adjusted, &generic); err != nil {
return nil, err
}
asJSON := utilities.Yaml2JSON(generic)
jsonData, err := json.Marshal(asJSON)
if err != nil {
return nil, err
}
var config ServiceConfig
if err := json.Unmarshal(jsonData, &config); err != nil {
if err := yaml.Unmarshal(fileContent, &config); err != nil {
return nil, err
}

Просмотреть файл

@ -78,16 +78,6 @@ policies:
`)
assert.Nil(t, err)
// Bad service
_, err = loadTempFiles(`
service: 1
policies:
-
id: "1"
effect: allow
`)
assert.NotNil(t, err)
// Bad policies conditions
_, err = loadTempFiles(`
service: a
@ -222,18 +212,18 @@ policies:
func TestReloadPolicies(t *testing.T) {
doorman := sampleDoorman()
loaded, _ := doorman.services["https://sample.yaml"].ladon.Manager.GetAll(0, maxInt)
loaded, _ := doorman.ladons["https://sample.yaml"].Manager.GetAll(0, maxInt)
assert.Equal(t, 6, len(loaded))
// Second load.
doorman.LoadPolicies()
loaded, _ = doorman.services["https://sample.yaml"].ladon.Manager.GetAll(0, maxInt)
loaded, _ = doorman.ladons["https://sample.yaml"].Manager.GetAll(0, maxInt)
assert.Equal(t, 6, len(loaded))
// Load bad policies, does not affect existing.
doorman.config.Sources = []string{"/tmp/unknown.yaml"}
doorman.LoadPolicies()
_, ok := doorman.services["https://sample.yaml"]
_, ok := doorman.ladons["https://sample.yaml"]
assert.True(t, ok)
}

Просмотреть файл

@ -38,9 +38,7 @@ func TestJWTMiddleware(t *testing.T) {
// Associate a fake JWT validator to this issuer.
v := &TestValidator{}
doorman.services[audience] = &ServiceConfig{
jwtValidator: v,
}
doorman.jwtValidators[audience] = v
// Extract claims is ran on every request.
claims := &Claims{
@ -84,9 +82,8 @@ func TestJWTMiddleware(t *testing.T) {
assert.False(t, ok)
// JWT not configured for this origin.
doorman.services["https://open"] = &ServiceConfig{
jwtValidator: nil,
}
doorman.jwtValidators["https://open"] = nil
c.Request, _ = http.NewRequest("GET", "/get", nil)
c.Request.Header.Set("Origin", "https://open")
handler(c)
@ -100,7 +97,7 @@ func TestJWTMiddleware(t *testing.T) {
}
v = &TestValidator{}
v.On("ExtractClaims", mock.Anything).Return(claims, nil)
doorman.services[audience].jwtValidator = v
doorman.jwtValidators[audience] = v
c, _ = gin.CreateTestContext(httptest.NewRecorder())
c.Request, _ = http.NewRequest("GET", "/get", nil)
c.Request.Header.Set("Origin", audience)
@ -115,7 +112,7 @@ func TestJWTMiddleware(t *testing.T) {
}
v = &TestValidator{}
v.On("ExtractClaims", mock.Anything).Return(claims, nil)
doorman.services[audience].jwtValidator = v
doorman.jwtValidators[audience] = v
c, _ = gin.CreateTestContext(httptest.NewRecorder())
c.Request, _ = http.NewRequest("GET", "/get", nil)
c.Request.Header.Set("Origin", audience)