-
-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
403 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package policy | ||
|
||
import ( | ||
"fmt" | ||
"github.com/reddec/trusted-cgi/application" | ||
"github.com/reddec/trusted-cgi/types" | ||
"net" | ||
) | ||
|
||
func checkPolicy(policy application.PolicyDefinition, req *types.Request) error { | ||
host, _, _ := net.SplitHostPort(req.RemoteAddress) | ||
if len(policy.AllowedIP) > 0 && !policy.AllowedIP.Has(host) { | ||
return fmt.Errorf("IP restricted") | ||
} | ||
if len(policy.AllowedOrigin) > 0 && !policy.AllowedOrigin.Has(req.Headers["Origin"]) { | ||
return fmt.Errorf("origin restricted") | ||
} | ||
|
||
if !policy.Public { | ||
_, ok := policy.Tokens[req.Headers["Authorization"]] | ||
if !ok { | ||
return fmt.Errorf("token restricted") | ||
} | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package policy | ||
|
||
import ( | ||
"github.com/reddec/trusted-cgi/application" | ||
"github.com/reddec/trusted-cgi/internal" | ||
"os" | ||
"sync" | ||
) | ||
|
||
type naiveFileStorePayload struct { | ||
Policies []application.Policy `json:"policies"` | ||
} | ||
|
||
func FileConfig(filename string) *naiveFileStore { | ||
return &naiveFileStore{file: filename} | ||
} | ||
|
||
type naiveFileStore struct { | ||
file string | ||
lock sync.RWMutex | ||
} | ||
|
||
func (nfs *naiveFileStore) SetPolicies(policies []application.Policy) error { | ||
nfs.lock.Lock() | ||
defer nfs.lock.Unlock() | ||
return internal.AtomicWriteJson(nfs.file, &naiveFileStorePayload{Policies: policies}) | ||
} | ||
|
||
func (nfs *naiveFileStore) GetPolicies() ([]application.Policy, error) { | ||
nfs.lock.RLock() | ||
defer nfs.lock.RUnlock() | ||
var pd naiveFileStorePayload | ||
err := internal.ReadJson(nfs.file, &pd) | ||
if err == nil { | ||
return pd.Policies, nil | ||
} | ||
if os.IsNotExist(err) { | ||
return nil, nil | ||
} | ||
return nil, err | ||
} | ||
|
||
func Mock(policies ...application.Policy) *mockStore { | ||
return &mockStore{policies: policies} | ||
} | ||
|
||
type mockStore struct { | ||
lock sync.RWMutex | ||
policies []application.Policy | ||
} | ||
|
||
func (msc *mockStore) SetPolicies(policies []application.Policy) error { | ||
msc.lock.Lock() | ||
defer msc.lock.Unlock() | ||
msc.policies = make([]application.Policy, len(policies)) | ||
copy(msc.policies, policies) | ||
return nil | ||
} | ||
|
||
func (msc *mockStore) GetPolicies() ([]application.Policy, error) { | ||
msc.lock.RLock() | ||
defer msc.lock.RUnlock() | ||
out := make([]application.Policy, len(msc.policies)) | ||
copy(out, msc.policies) | ||
return out, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
package policy | ||
|
||
import ( | ||
"fmt" | ||
"github.com/reddec/trusted-cgi/application" | ||
"github.com/reddec/trusted-cgi/types" | ||
"sync" | ||
) | ||
|
||
// Store contains policies configuration for reload | ||
type Store interface { | ||
// Save policies list | ||
SetPolicies(policies []application.Policy) error | ||
// Load policies list | ||
GetPolicies() ([]application.Policy, error) | ||
} | ||
|
||
func New(store Store) (*policiesImpl, error) { | ||
impl := &policiesImpl{ | ||
store: store, | ||
policiesByID: map[string]*application.Policy{}, | ||
policiesByLambda: map[string]string{}, | ||
} | ||
return impl, impl.load() | ||
} | ||
|
||
type policiesImpl struct { | ||
store Store | ||
lock sync.RWMutex | ||
policiesByID map[string]*application.Policy | ||
policiesByLambda map[string]string | ||
} | ||
|
||
func (policies *policiesImpl) load() error { | ||
list, err := policies.store.GetPolicies() | ||
if err != nil { | ||
return err | ||
} | ||
for _, item := range list { | ||
policies.policiesByID[item.ID] = &item | ||
for lambda := range item.Lambdas { | ||
policies.policiesByLambda[lambda] = item.ID | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func (policies *policiesImpl) List() []application.Policy { | ||
policies.lock.RLock() | ||
defer policies.lock.RUnlock() | ||
return policies.unsafeList() | ||
} | ||
|
||
func (policies *policiesImpl) Create(policy string, definition application.PolicyDefinition) (*application.Policy, error) { | ||
policies.lock.Lock() | ||
defer policies.lock.Unlock() | ||
_, exist := policies.policiesByID[policy] | ||
if exist { | ||
return nil, fmt.Errorf("policy %s already exists", policy) | ||
} | ||
info := &application.Policy{ | ||
ID: policy, | ||
Definition: definition, | ||
Lambdas: make(types.JsonStringSet), | ||
} | ||
policies.policiesByID[policy] = info | ||
return info, policies.store.SetPolicies(policies.unsafeList()) | ||
} | ||
|
||
func (policies *policiesImpl) Remove(policy string) error { | ||
policies.lock.Lock() | ||
defer policies.lock.Unlock() | ||
info, exist := policies.policiesByID[policy] | ||
if !exist { | ||
return fmt.Errorf("policy %s does not exists", policy) | ||
} | ||
for lambda := range info.Lambdas { | ||
delete(policies.policiesByLambda, lambda) | ||
} | ||
delete(policies.policiesByID, policy) | ||
return policies.store.SetPolicies(policies.unsafeList()) | ||
} | ||
|
||
func (policies *policiesImpl) Update(policy string, definition application.PolicyDefinition) error { | ||
policies.lock.Lock() | ||
defer policies.lock.Unlock() | ||
info, exist := policies.policiesByID[policy] | ||
if !exist { | ||
return fmt.Errorf("policy %s does not exists", policy) | ||
} | ||
info.Definition = definition | ||
return policies.store.SetPolicies(policies.unsafeList()) | ||
} | ||
|
||
func (policies *policiesImpl) Apply(lambda string, policy string) error { | ||
policies.lock.Lock() | ||
defer policies.lock.Unlock() | ||
info, exists := policies.policiesByID[policy] | ||
if !exists { | ||
return fmt.Errorf("policy %s does not exist", policy) | ||
} | ||
if info.Lambdas.Has(lambda) { | ||
// already applied | ||
return nil | ||
} | ||
policies.unsafeUnlink(lambda) | ||
info.Lambdas.Set(lambda) | ||
policies.policiesByLambda[lambda] = policy | ||
return policies.store.SetPolicies(policies.unsafeList()) | ||
} | ||
|
||
func (policies *policiesImpl) Inspect(lambda string, request *types.Request) error { | ||
policy, applicable, err := policies.findPolicy(lambda) | ||
if err != nil { | ||
return err | ||
} | ||
if !applicable { | ||
return nil | ||
} | ||
return checkPolicy(policy, request) | ||
} | ||
|
||
func (policies *policiesImpl) Clear(lambda string) error { | ||
policies.lock.Lock() | ||
defer policies.lock.Unlock() | ||
if !policies.unsafeUnlink(lambda) { | ||
return nil | ||
} | ||
return policies.store.SetPolicies(policies.unsafeList()) | ||
} | ||
|
||
func (policies *policiesImpl) unsafeUnlink(lambda string) bool { | ||
policyId, hasPolicy := policies.policiesByLambda[lambda] | ||
if !hasPolicy { | ||
return false | ||
} | ||
// remove direct ref | ||
delete(policies.policiesByLambda, lambda) | ||
|
||
// remove back ref | ||
if policy, exist := policies.policiesByID[policyId]; exist { | ||
policy.Lambdas.Del(lambda) | ||
} | ||
return true | ||
} | ||
|
||
func (policies *policiesImpl) unsafeList() []application.Policy { | ||
var ans = make([]application.Policy, 0, len(policies.policiesByID)) | ||
for _, policy := range policies.policiesByID { | ||
ans = append(ans, *policy) | ||
} | ||
return ans | ||
} | ||
|
||
func (policies *policiesImpl) findPolicy(lambda string) (policy application.PolicyDefinition, applicable bool, err error) { | ||
policies.lock.RLock() | ||
defer policies.lock.RUnlock() | ||
policyId, exists := policies.policiesByLambda[lambda] | ||
if !exists { | ||
applicable = false | ||
return // no applied policy | ||
} | ||
info, exists := policies.policiesByID[policyId] | ||
if !exists { | ||
err = fmt.Errorf("corrupted policy data: lambda %s linked to unknown policy %s", lambda, policyId) | ||
return | ||
} | ||
return info.Definition, true, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
package policy | ||
|
||
import ( | ||
"bytes" | ||
"github.com/reddec/trusted-cgi/application" | ||
"github.com/reddec/trusted-cgi/types" | ||
"github.com/stretchr/testify/assert" | ||
"io/ioutil" | ||
"testing" | ||
) | ||
|
||
func TestNew(t *testing.T) { | ||
policy, err := New(Mock(application.Policy{ | ||
ID: "foo", | ||
Definition: application.PolicyDefinition{ | ||
Public: false, | ||
Tokens: map[string]string{ | ||
"DEADBEAF": "Consumer 1", | ||
"BEAFDEAD": "Consumer 2", | ||
}, | ||
}, | ||
Lambdas: map[string]bool{ | ||
"lambda-1": true, | ||
"lambda-2": true, | ||
}, | ||
})) | ||
if err != nil { | ||
t.Error(err) | ||
return | ||
} | ||
t.Run("no applied policy", func(t *testing.T) { | ||
err := policy.Inspect("lambda-3", mockRequest("hello")) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
}) | ||
t.Run("valid token", func(t *testing.T) { | ||
req := mockRequest("hello") | ||
req.Headers["Authorization"] = "DEADBEAF" | ||
err := policy.Inspect("lambda-1", req) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
}) | ||
t.Run("invalid token", func(t *testing.T) { | ||
req := mockRequest("hello") | ||
req.Headers["Authorization"] = "1111" | ||
err := policy.Inspect("lambda-1", req) | ||
if err == nil { | ||
t.Error("should fail") | ||
} | ||
}) | ||
t.Run("list policies", func(t *testing.T) { | ||
list := policy.List() | ||
assert.Len(t, list, 1) | ||
assert.Equal(t, "foo", list[0].ID) | ||
assert.Equal(t, list[0].Lambdas, types.StringSet("lambda-1", "lambda-2")) | ||
}) | ||
t.Run("clear", func(t *testing.T) { | ||
err := policy.Clear("lambda-2") | ||
assert.NoError(t, err) | ||
list := policy.List() | ||
assert.Len(t, list, 1) | ||
assert.Equal(t, "foo", list[0].ID) | ||
assert.Equal(t, list[0].Lambdas, types.StringSet("lambda-1")) | ||
}) | ||
t.Run("apply", func(t *testing.T) { | ||
err := policy.Apply("lambda-4", "foo") | ||
assert.NoError(t, err) | ||
list := policy.List() | ||
assert.Len(t, list, 1) | ||
assert.Equal(t, "foo", list[0].ID) | ||
assert.Contains(t, list[0].Lambdas, "lambda-4") | ||
}) | ||
t.Run("update", func(t *testing.T) { | ||
err := policy.Update("foo", application.PolicyDefinition{ | ||
AllowedOrigin: types.StringSet("google"), | ||
Public: true, | ||
}) | ||
assert.NoError(t, err) | ||
req := mockRequest("hello") | ||
req.Headers["Origin"] = "google" | ||
err = policy.Inspect("lambda-1", req) | ||
assert.NoError(t, err) | ||
}) | ||
} | ||
|
||
func mockRequest(payload string) *types.Request { | ||
return &types.Request{ | ||
Method: "POST", | ||
URL: "http://example.com:8889/sample/" + payload, | ||
Path: "/sample/" + payload, | ||
RemoteAddress: "127.0.0.2:9992", | ||
Form: map[string]string{ | ||
"USER": "user1", | ||
}, | ||
Headers: map[string]string{ | ||
"Content-Type": "text/plain", | ||
}, | ||
Body: ioutil.NopCloser(bytes.NewBufferString(payload)), | ||
} | ||
} |
Oops, something went wrong.