зеркало из https://github.com/mozilla/doorman.git
Define policies in doorman package
This commit is contained in:
Родитель
f22de05d8f
Коммит
4805963990
|
@ -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)
|
||||
|
|
Загрузка…
Ссылка в новой задаче