mirror of
https://github.com/pomerium/pomerium.git
synced 2025-08-06 10:21:05 +02:00
authorize: add support for cidr lookups (#3277)
This commit is contained in:
parent
9dbe12fe99
commit
c19048649a
15 changed files with 372 additions and 89 deletions
|
@ -9,6 +9,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pomerium/pomerium/authorize/evaluator"
|
"github.com/pomerium/pomerium/authorize/evaluator"
|
||||||
|
"github.com/pomerium/pomerium/authorize/internal/store"
|
||||||
"github.com/pomerium/pomerium/config"
|
"github.com/pomerium/pomerium/config"
|
||||||
"github.com/pomerium/pomerium/internal/log"
|
"github.com/pomerium/pomerium/internal/log"
|
||||||
"github.com/pomerium/pomerium/internal/telemetry/metrics"
|
"github.com/pomerium/pomerium/internal/telemetry/metrics"
|
||||||
|
@ -21,7 +22,7 @@ import (
|
||||||
// Authorize struct holds
|
// Authorize struct holds
|
||||||
type Authorize struct {
|
type Authorize struct {
|
||||||
state *atomicAuthorizeState
|
state *atomicAuthorizeState
|
||||||
store *evaluator.Store
|
store *store.Store
|
||||||
currentOptions *config.AtomicOptions
|
currentOptions *config.AtomicOptions
|
||||||
accessTracker *AccessTracker
|
accessTracker *AccessTracker
|
||||||
|
|
||||||
|
@ -37,7 +38,7 @@ type Authorize struct {
|
||||||
func New(cfg *config.Config) (*Authorize, error) {
|
func New(cfg *config.Config) (*Authorize, error) {
|
||||||
a := &Authorize{
|
a := &Authorize{
|
||||||
currentOptions: config.NewAtomicOptions(),
|
currentOptions: config.NewAtomicOptions(),
|
||||||
store: evaluator.NewStore(),
|
store: store.New(),
|
||||||
dataBrokerInitialSync: make(chan struct{}),
|
dataBrokerInitialSync: make(chan struct{}),
|
||||||
}
|
}
|
||||||
a.accessTracker = NewAccessTracker(a, accessTrackerMaxSize, accessTrackerDebouncePeriod)
|
a.accessTracker = NewAccessTracker(a, accessTrackerMaxSize, accessTrackerDebouncePeriod)
|
||||||
|
@ -85,7 +86,7 @@ func validateOptions(o *config.Options) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// newPolicyEvaluator returns an policy evaluator.
|
// newPolicyEvaluator returns an policy evaluator.
|
||||||
func newPolicyEvaluator(opts *config.Options, store *evaluator.Store) (*evaluator.Evaluator, error) {
|
func newPolicyEvaluator(opts *config.Options, store *store.Store) (*evaluator.Evaluator, error) {
|
||||||
metrics.AddPolicyCountCallback("pomerium-authorize", func() int64 {
|
metrics.AddPolicyCountCallback("pomerium-authorize", func() int64 {
|
||||||
return int64(len(opts.GetAllPolicies()))
|
return int64(len(opts.GetAllPolicies()))
|
||||||
})
|
})
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"google.golang.org/protobuf/encoding/protojson"
|
"google.golang.org/protobuf/encoding/protojson"
|
||||||
|
|
||||||
"github.com/pomerium/pomerium/authorize/evaluator"
|
"github.com/pomerium/pomerium/authorize/evaluator"
|
||||||
|
"github.com/pomerium/pomerium/authorize/internal/store"
|
||||||
"github.com/pomerium/pomerium/config"
|
"github.com/pomerium/pomerium/config"
|
||||||
"github.com/pomerium/pomerium/internal/encoding/jws"
|
"github.com/pomerium/pomerium/internal/encoding/jws"
|
||||||
"github.com/pomerium/pomerium/internal/testutil"
|
"github.com/pomerium/pomerium/internal/testutil"
|
||||||
|
@ -39,7 +40,7 @@ func TestAuthorize_okResponse(t *testing.T) {
|
||||||
encoder, _ := jws.NewHS256Signer([]byte{0, 0, 0, 0})
|
encoder, _ := jws.NewHS256Signer([]byte{0, 0, 0, 0})
|
||||||
a.state.Load().encoder = encoder
|
a.state.Load().encoder = encoder
|
||||||
a.currentOptions.Store(opt)
|
a.currentOptions.Store(opt)
|
||||||
a.store = evaluator.NewStoreFromProtos(0,
|
a.store = store.NewFromProtos(0,
|
||||||
&session.Session{
|
&session.Session{
|
||||||
Id: "SESSION_ID",
|
Id: "SESSION_ID",
|
||||||
UserId: "USER_ID",
|
UserId: "USER_ID",
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/go-jose/go-jose/v3"
|
"github.com/go-jose/go-jose/v3"
|
||||||
"github.com/open-policy-agent/opa/rego"
|
"github.com/open-policy-agent/opa/rego"
|
||||||
|
|
||||||
|
"github.com/pomerium/pomerium/authorize/internal/store"
|
||||||
"github.com/pomerium/pomerium/config"
|
"github.com/pomerium/pomerium/config"
|
||||||
"github.com/pomerium/pomerium/internal/httputil"
|
"github.com/pomerium/pomerium/internal/httputil"
|
||||||
"github.com/pomerium/pomerium/internal/log"
|
"github.com/pomerium/pomerium/internal/log"
|
||||||
|
@ -77,14 +78,14 @@ type Result struct {
|
||||||
|
|
||||||
// An Evaluator evaluates policies.
|
// An Evaluator evaluates policies.
|
||||||
type Evaluator struct {
|
type Evaluator struct {
|
||||||
store *Store
|
store *store.Store
|
||||||
policyEvaluators map[uint64]*PolicyEvaluator
|
policyEvaluators map[uint64]*PolicyEvaluator
|
||||||
headersEvaluators *HeadersEvaluator
|
headersEvaluators *HeadersEvaluator
|
||||||
clientCA []byte
|
clientCA []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new Evaluator.
|
// New creates a new Evaluator.
|
||||||
func New(ctx context.Context, store *Store, options ...Option) (*Evaluator, error) {
|
func New(ctx context.Context, store *store.Store, options ...Option) (*Evaluator, error) {
|
||||||
e := &Evaluator{store: store}
|
e := &Evaluator{store: store}
|
||||||
|
|
||||||
cfg := getConfig(options...)
|
cfg := getConfig(options...)
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
|
"github.com/pomerium/pomerium/authorize/internal/store"
|
||||||
"github.com/pomerium/pomerium/config"
|
"github.com/pomerium/pomerium/config"
|
||||||
"github.com/pomerium/pomerium/internal/httputil"
|
"github.com/pomerium/pomerium/internal/httputil"
|
||||||
"github.com/pomerium/pomerium/pkg/cryptutil"
|
"github.com/pomerium/pomerium/pkg/cryptutil"
|
||||||
|
@ -35,7 +36,7 @@ func TestEvaluator(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
eval := func(t *testing.T, options []Option, data []proto.Message, req *Request) (*Result, error) {
|
eval := func(t *testing.T, options []Option, data []proto.Message, req *Request) (*Result, error) {
|
||||||
store := NewStoreFromProtos(math.MaxUint64, data...)
|
store := store.NewFromProtos(math.MaxUint64, data...)
|
||||||
store.UpdateIssuer("authenticate.example.com")
|
store.UpdateIssuer("authenticate.example.com")
|
||||||
store.UpdateJWTClaimHeaders(config.NewJWTClaimHeaders("email", "groups", "user", "CUSTOM_KEY"))
|
store.UpdateJWTClaimHeaders(config.NewJWTClaimHeaders("email", "groups", "user", "CUSTOM_KEY"))
|
||||||
store.UpdateSigningKey(privateJWK)
|
store.UpdateSigningKey(privateJWK)
|
||||||
|
@ -512,7 +513,7 @@ func mustParseURL(str string) *url.URL {
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkEvaluator_Evaluate(b *testing.B) {
|
func BenchmarkEvaluator_Evaluate(b *testing.B) {
|
||||||
store := NewStore()
|
store := store.New()
|
||||||
|
|
||||||
policies := []config.Policy{
|
policies := []config.Policy{
|
||||||
{
|
{
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/open-policy-agent/opa/rego"
|
"github.com/open-policy-agent/opa/rego"
|
||||||
|
|
||||||
"github.com/pomerium/pomerium/authorize/evaluator/opa"
|
"github.com/pomerium/pomerium/authorize/evaluator/opa"
|
||||||
|
"github.com/pomerium/pomerium/authorize/internal/store"
|
||||||
"github.com/pomerium/pomerium/config"
|
"github.com/pomerium/pomerium/config"
|
||||||
"github.com/pomerium/pomerium/internal/telemetry/trace"
|
"github.com/pomerium/pomerium/internal/telemetry/trace"
|
||||||
"github.com/pomerium/pomerium/internal/urlutil"
|
"github.com/pomerium/pomerium/internal/urlutil"
|
||||||
|
@ -56,7 +57,7 @@ type HeadersEvaluator struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHeadersEvaluator creates a new HeadersEvaluator.
|
// NewHeadersEvaluator creates a new HeadersEvaluator.
|
||||||
func NewHeadersEvaluator(ctx context.Context, store *Store) (*HeadersEvaluator, error) {
|
func NewHeadersEvaluator(ctx context.Context, store *store.Store) (*HeadersEvaluator, error) {
|
||||||
r := rego.New(
|
r := rego.New(
|
||||||
rego.Store(store),
|
rego.Store(store),
|
||||||
rego.Module("pomerium.headers", opa.HeadersRego),
|
rego.Module("pomerium.headers", opa.HeadersRego),
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
"google.golang.org/protobuf/types/known/structpb"
|
"google.golang.org/protobuf/types/known/structpb"
|
||||||
|
|
||||||
|
"github.com/pomerium/pomerium/authorize/internal/store"
|
||||||
"github.com/pomerium/pomerium/config"
|
"github.com/pomerium/pomerium/config"
|
||||||
"github.com/pomerium/pomerium/pkg/cryptutil"
|
"github.com/pomerium/pomerium/pkg/cryptutil"
|
||||||
"github.com/pomerium/pomerium/pkg/grpc/directory"
|
"github.com/pomerium/pomerium/pkg/grpc/directory"
|
||||||
|
@ -50,7 +51,7 @@ func TestHeadersEvaluator(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
eval := func(t *testing.T, data []proto.Message, input *HeadersRequest) (*HeadersResponse, error) {
|
eval := func(t *testing.T, data []proto.Message, input *HeadersRequest) (*HeadersResponse, error) {
|
||||||
store := NewStoreFromProtos(math.MaxUint64, data...)
|
store := store.NewFromProtos(math.MaxUint64, data...)
|
||||||
store.UpdateIssuer("authenticate.example.com")
|
store.UpdateIssuer("authenticate.example.com")
|
||||||
store.UpdateJWTClaimHeaders(config.NewJWTClaimHeaders("email", "groups", "user", "CUSTOM_KEY"))
|
store.UpdateJWTClaimHeaders(config.NewJWTClaimHeaders("email", "groups", "user", "CUSTOM_KEY"))
|
||||||
store.UpdateSigningKey(privateJWK)
|
store.UpdateSigningKey(privateJWK)
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"github.com/open-policy-agent/opa/rego"
|
"github.com/open-policy-agent/opa/rego"
|
||||||
octrace "go.opencensus.io/trace"
|
octrace "go.opencensus.io/trace"
|
||||||
|
|
||||||
|
"github.com/pomerium/pomerium/authorize/internal/store"
|
||||||
"github.com/pomerium/pomerium/config"
|
"github.com/pomerium/pomerium/config"
|
||||||
"github.com/pomerium/pomerium/internal/log"
|
"github.com/pomerium/pomerium/internal/log"
|
||||||
"github.com/pomerium/pomerium/internal/telemetry/trace"
|
"github.com/pomerium/pomerium/internal/telemetry/trace"
|
||||||
|
@ -97,7 +98,7 @@ type PolicyEvaluator struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPolicyEvaluator creates a new PolicyEvaluator.
|
// NewPolicyEvaluator creates a new PolicyEvaluator.
|
||||||
func NewPolicyEvaluator(ctx context.Context, store *Store, configPolicy *config.Policy) (*PolicyEvaluator, error) {
|
func NewPolicyEvaluator(ctx context.Context, store *store.Store, configPolicy *config.Policy) (*PolicyEvaluator, error) {
|
||||||
e := new(PolicyEvaluator)
|
e := new(PolicyEvaluator)
|
||||||
|
|
||||||
// generate the base rego script for the policy
|
// generate the base rego script for the policy
|
||||||
|
|
|
@ -9,7 +9,9 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
|
"google.golang.org/protobuf/types/known/structpb"
|
||||||
|
|
||||||
|
"github.com/pomerium/pomerium/authorize/internal/store"
|
||||||
"github.com/pomerium/pomerium/config"
|
"github.com/pomerium/pomerium/config"
|
||||||
"github.com/pomerium/pomerium/pkg/cryptutil"
|
"github.com/pomerium/pomerium/pkg/cryptutil"
|
||||||
"github.com/pomerium/pomerium/pkg/grpc/session"
|
"github.com/pomerium/pomerium/pkg/grpc/session"
|
||||||
|
@ -27,7 +29,7 @@ func TestPolicyEvaluator(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
eval := func(t *testing.T, policy *config.Policy, data []proto.Message, input *PolicyRequest) (*PolicyResponse, error) {
|
eval := func(t *testing.T, policy *config.Policy, data []proto.Message, input *PolicyRequest) (*PolicyResponse, error) {
|
||||||
store := NewStoreFromProtos(math.MaxUint64, data...)
|
store := store.NewFromProtos(math.MaxUint64, data...)
|
||||||
store.UpdateIssuer("authenticate.example.com")
|
store.UpdateIssuer("authenticate.example.com")
|
||||||
store.UpdateJWTClaimHeaders(config.NewJWTClaimHeaders("email", "groups", "user", "CUSTOM_KEY"))
|
store.UpdateJWTClaimHeaders(config.NewJWTClaimHeaders("email", "groups", "user", "CUSTOM_KEY"))
|
||||||
store.UpdateSigningKey(privateJWK)
|
store.UpdateSigningKey(privateJWK)
|
||||||
|
@ -196,4 +198,41 @@ func TestPolicyEvaluator(t *testing.T) {
|
||||||
}, output)
|
}, output)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
t.Run("cidr", func(t *testing.T) {
|
||||||
|
r1 := &structpb.Struct{Fields: map[string]*structpb.Value{
|
||||||
|
"$index": structpb.NewStructValue(&structpb.Struct{Fields: map[string]*structpb.Value{
|
||||||
|
"cidr": structpb.NewStringValue("192.168.0.0/16"),
|
||||||
|
}}),
|
||||||
|
"country": structpb.NewStringValue("US"),
|
||||||
|
}}
|
||||||
|
|
||||||
|
p := &config.Policy{
|
||||||
|
From: "https://from.example.com",
|
||||||
|
To: config.WeightedURLs{{URL: *mustParseURL("https://to.example.com")}},
|
||||||
|
SubPolicies: []config.SubPolicy{
|
||||||
|
{Rego: []string{`
|
||||||
|
package pomerium.policy
|
||||||
|
|
||||||
|
allow {
|
||||||
|
record := get_databroker_record("type.googleapis.com/google.protobuf.Struct", "192.168.0.7")
|
||||||
|
record.country == "US"
|
||||||
|
}
|
||||||
|
`}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
output, err := eval(t,
|
||||||
|
p,
|
||||||
|
[]proto.Message{s1, u1, s2, u2, r1},
|
||||||
|
&PolicyRequest{
|
||||||
|
HTTP: RequestHTTP{Method: "GET", URL: "https://from.example.com/path"},
|
||||||
|
Session: RequestSession{ID: "s1"},
|
||||||
|
|
||||||
|
IsValidClientCertificate: true,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, &PolicyResponse{
|
||||||
|
Allow: NewRuleResult(true),
|
||||||
|
Deny: NewRuleResult(false, criteria.ReasonValidClientCertificateOrNoneRequired),
|
||||||
|
}, output)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
196
authorize/internal/store/index.go
Normal file
196
authorize/internal/store/index.go
Normal file
|
@ -0,0 +1,196 @@
|
||||||
|
package store
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/kentik/patricia"
|
||||||
|
"github.com/kentik/patricia/string_tree"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
"google.golang.org/protobuf/types/known/structpb"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
indexField = "$index"
|
||||||
|
cidrField = "cidr"
|
||||||
|
)
|
||||||
|
|
||||||
|
type index struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
byType map[string]*recordIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
func newIndex() *index {
|
||||||
|
idx := new(index)
|
||||||
|
idx.clear()
|
||||||
|
return idx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idx *index) clear() {
|
||||||
|
idx.mu.Lock()
|
||||||
|
defer idx.mu.Unlock()
|
||||||
|
idx.byType = map[string]*recordIndex{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idx *index) delete(typeURL, id string) {
|
||||||
|
idx.mu.Lock()
|
||||||
|
defer idx.mu.Unlock()
|
||||||
|
|
||||||
|
ridx, ok := idx.byType[typeURL]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ridx.delete(id)
|
||||||
|
|
||||||
|
if len(ridx.byID) == 0 {
|
||||||
|
delete(idx.byType, typeURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idx *index) find(typeURL, id string) proto.Message {
|
||||||
|
idx.mu.RLock()
|
||||||
|
defer idx.mu.RUnlock()
|
||||||
|
|
||||||
|
ridx, ok := idx.byType[typeURL]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ridx.find(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idx *index) get(typeURL, id string) proto.Message {
|
||||||
|
idx.mu.RLock()
|
||||||
|
defer idx.mu.RUnlock()
|
||||||
|
|
||||||
|
ridx, ok := idx.byType[typeURL]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ridx.get(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idx *index) set(typeURL, id string, msg proto.Message) {
|
||||||
|
idx.mu.Lock()
|
||||||
|
defer idx.mu.Unlock()
|
||||||
|
|
||||||
|
ridx, ok := idx.byType[typeURL]
|
||||||
|
if !ok {
|
||||||
|
ridx = newRecordIndex()
|
||||||
|
idx.byType[typeURL] = ridx
|
||||||
|
}
|
||||||
|
ridx.set(id, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// a recordIndex indexes records for of a specific type
|
||||||
|
type recordIndex struct {
|
||||||
|
byID map[string]proto.Message
|
||||||
|
byCIDRV4 *string_tree.TreeV4
|
||||||
|
byCIDRV6 *string_tree.TreeV6
|
||||||
|
}
|
||||||
|
|
||||||
|
// newRecordIndex creates a new record index.
|
||||||
|
func newRecordIndex() *recordIndex {
|
||||||
|
return &recordIndex{
|
||||||
|
byID: map[string]proto.Message{},
|
||||||
|
byCIDRV4: string_tree.NewTreeV4(),
|
||||||
|
byCIDRV6: string_tree.NewTreeV6(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idx *recordIndex) delete(id string) {
|
||||||
|
r, ok := idx.byID[id]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(idx.byID, id)
|
||||||
|
|
||||||
|
addr4, addr6 := getIndexCIDR(r)
|
||||||
|
if addr4 != nil {
|
||||||
|
idx.byCIDRV4.Delete(*addr4, func(payload, val string) bool {
|
||||||
|
return payload == val
|
||||||
|
}, id)
|
||||||
|
}
|
||||||
|
if addr6 != nil {
|
||||||
|
idx.byCIDRV6.Delete(*addr6, func(payload, val string) bool {
|
||||||
|
return payload == val
|
||||||
|
}, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idx *recordIndex) find(idOrString string) proto.Message {
|
||||||
|
r, ok := idx.byID[idOrString]
|
||||||
|
if ok {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
addrv4, addrv6, _ := patricia.ParseIPFromString(idOrString)
|
||||||
|
if addrv4 != nil {
|
||||||
|
found, id := idx.byCIDRV4.FindDeepestTag(*addrv4)
|
||||||
|
if found {
|
||||||
|
return idx.byID[id]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if addrv6 != nil {
|
||||||
|
found, id := idx.byCIDRV6.FindDeepestTag(*addrv6)
|
||||||
|
if found {
|
||||||
|
return idx.byID[id]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idx *recordIndex) get(id string) proto.Message {
|
||||||
|
return idx.byID[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idx *recordIndex) set(id string, msg proto.Message) {
|
||||||
|
_, ok := idx.byID[id]
|
||||||
|
if ok {
|
||||||
|
idx.delete(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
idx.byID[id] = msg
|
||||||
|
addr4, addr6 := getIndexCIDR(msg)
|
||||||
|
if addr4 != nil {
|
||||||
|
idx.byCIDRV4.Set(*addr4, id)
|
||||||
|
}
|
||||||
|
if addr6 != nil {
|
||||||
|
idx.byCIDRV6.Set(*addr6, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getIndexCIDR(msg proto.Message) (*patricia.IPv4Address, *patricia.IPv6Address) {
|
||||||
|
var s *structpb.Struct
|
||||||
|
if sv, ok := msg.(*structpb.Value); ok {
|
||||||
|
s = sv.GetStructValue()
|
||||||
|
} else {
|
||||||
|
s, _ = msg.(*structpb.Struct)
|
||||||
|
}
|
||||||
|
if s == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
f, ok := s.Fields[indexField]
|
||||||
|
if !ok {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
obj := f.GetStructValue()
|
||||||
|
if obj == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cf, ok := obj.Fields[cidrField]
|
||||||
|
if !ok {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
c := cf.GetStringValue()
|
||||||
|
if c == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
addr4, addr6, _ := patricia.ParseIPFromString(c)
|
||||||
|
return addr4, addr6
|
||||||
|
}
|
74
authorize/internal/store/index_test.go
Normal file
74
authorize/internal/store/index_test.go
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
package store
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"google.golang.org/protobuf/types/known/structpb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestByID(t *testing.T) {
|
||||||
|
idx := newIndex()
|
||||||
|
|
||||||
|
r1 := &structpb.Struct{Fields: map[string]*structpb.Value{
|
||||||
|
"id": structpb.NewStringValue("r1"),
|
||||||
|
}}
|
||||||
|
|
||||||
|
idx.set("example.com/record", "r1", r1)
|
||||||
|
assert.Equal(t, r1, idx.get("example.com/record", "r1"))
|
||||||
|
idx.delete("example.com/record", "r1")
|
||||||
|
assert.Nil(t, idx.get("example.com/record", "r1"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestByCIDR(t *testing.T) {
|
||||||
|
t.Run("ipv4", func(t *testing.T) {
|
||||||
|
idx := newIndex()
|
||||||
|
|
||||||
|
r1 := &structpb.Struct{Fields: map[string]*structpb.Value{
|
||||||
|
"$index": structpb.NewStructValue(&structpb.Struct{Fields: map[string]*structpb.Value{
|
||||||
|
"cidr": structpb.NewStringValue("192.168.0.0/16"),
|
||||||
|
}}),
|
||||||
|
"id": structpb.NewStringValue("r1"),
|
||||||
|
}}
|
||||||
|
idx.set("example.com/record", "r1", r1)
|
||||||
|
|
||||||
|
r2 := &structpb.Struct{Fields: map[string]*structpb.Value{
|
||||||
|
"$index": structpb.NewStructValue(&structpb.Struct{Fields: map[string]*structpb.Value{
|
||||||
|
"cidr": structpb.NewStringValue("192.168.0.0/24"),
|
||||||
|
}}),
|
||||||
|
"id": structpb.NewStringValue("r2"),
|
||||||
|
}}
|
||||||
|
idx.set("example.com/record", "r2", r2)
|
||||||
|
|
||||||
|
assert.Equal(t, r2, idx.find("example.com/record", "192.168.0.7"))
|
||||||
|
idx.delete("example.com/record", "r2")
|
||||||
|
assert.Equal(t, r1, idx.find("example.com/record", "192.168.0.7"))
|
||||||
|
idx.delete("example.com/record", "r1")
|
||||||
|
assert.Nil(t, idx.find("example.com/record", "192.168.0.7"))
|
||||||
|
})
|
||||||
|
t.Run("ipv6", func(t *testing.T) {
|
||||||
|
idx := newIndex()
|
||||||
|
|
||||||
|
r1 := &structpb.Struct{Fields: map[string]*structpb.Value{
|
||||||
|
"$index": structpb.NewStructValue(&structpb.Struct{Fields: map[string]*structpb.Value{
|
||||||
|
"cidr": structpb.NewStringValue("2001:db8::/32"),
|
||||||
|
}}),
|
||||||
|
"id": structpb.NewStringValue("r1"),
|
||||||
|
}}
|
||||||
|
idx.set("example.com/record", "r1", r1)
|
||||||
|
|
||||||
|
r2 := &structpb.Struct{Fields: map[string]*structpb.Value{
|
||||||
|
"$index": structpb.NewStructValue(&structpb.Struct{Fields: map[string]*structpb.Value{
|
||||||
|
"cidr": structpb.NewStringValue("2001:db8::/48"),
|
||||||
|
}}),
|
||||||
|
"id": structpb.NewStringValue("r2"),
|
||||||
|
}}
|
||||||
|
idx.set("example.com/record", "r2", r2)
|
||||||
|
|
||||||
|
assert.Equal(t, r2, idx.find("example.com/record", "2001:db8::"))
|
||||||
|
idx.delete("example.com/record", "r2")
|
||||||
|
assert.Equal(t, r1, idx.find("example.com/record", "2001:db8::"))
|
||||||
|
idx.delete("example.com/record", "r1")
|
||||||
|
assert.Nil(t, idx.find("example.com/record", "2001:db8::"))
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
package evaluator
|
// Package store contains a datastore for authorization policy evaluation.
|
||||||
|
package store
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/go-jose/go-jose/v3"
|
"github.com/go-jose/go-jose/v3"
|
||||||
|
@ -24,83 +24,25 @@ import (
|
||||||
"github.com/pomerium/pomerium/pkg/protoutil"
|
"github.com/pomerium/pomerium/pkg/protoutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
type dataBrokerData struct {
|
|
||||||
mu sync.RWMutex
|
|
||||||
m map[string]map[string]proto.Message
|
|
||||||
}
|
|
||||||
|
|
||||||
func newDataBrokerData() *dataBrokerData {
|
|
||||||
return &dataBrokerData{
|
|
||||||
m: map[string]map[string]proto.Message{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dbd *dataBrokerData) clear() {
|
|
||||||
dbd.mu.Lock()
|
|
||||||
defer dbd.mu.Unlock()
|
|
||||||
|
|
||||||
dbd.m = map[string]map[string]proto.Message{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dbd *dataBrokerData) delete(typeURL, id string) {
|
|
||||||
dbd.mu.Lock()
|
|
||||||
defer dbd.mu.Unlock()
|
|
||||||
|
|
||||||
m, ok := dbd.m[typeURL]
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(m, id)
|
|
||||||
|
|
||||||
if len(m) == 0 {
|
|
||||||
delete(dbd.m, typeURL)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dbd *dataBrokerData) get(typeURL, id string) proto.Message {
|
|
||||||
dbd.mu.RLock()
|
|
||||||
defer dbd.mu.RUnlock()
|
|
||||||
|
|
||||||
m, ok := dbd.m[typeURL]
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return m[id]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dbd *dataBrokerData) set(typeURL, id string, msg proto.Message) {
|
|
||||||
dbd.mu.Lock()
|
|
||||||
defer dbd.mu.Unlock()
|
|
||||||
|
|
||||||
m, ok := dbd.m[typeURL]
|
|
||||||
if !ok {
|
|
||||||
m = map[string]proto.Message{}
|
|
||||||
dbd.m[typeURL] = m
|
|
||||||
}
|
|
||||||
m[id] = msg
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Store stores data for the OPA rego policy evaluation.
|
// A Store stores data for the OPA rego policy evaluation.
|
||||||
type Store struct {
|
type Store struct {
|
||||||
storage.Store
|
storage.Store
|
||||||
|
index *index
|
||||||
dataBrokerData *dataBrokerData
|
|
||||||
|
|
||||||
dataBrokerServerVersion, dataBrokerRecordVersion uint64
|
dataBrokerServerVersion, dataBrokerRecordVersion uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewStore creates a new Store.
|
// New creates a new Store.
|
||||||
func NewStore() *Store {
|
func New() *Store {
|
||||||
return &Store{
|
return &Store{
|
||||||
Store: inmem.New(),
|
Store: inmem.New(),
|
||||||
dataBrokerData: newDataBrokerData(),
|
index: newIndex(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewStoreFromProtos creates a new Store from an existing set of protobuf messages.
|
// NewFromProtos creates a new Store from an existing set of protobuf messages.
|
||||||
func NewStoreFromProtos(serverVersion uint64, msgs ...proto.Message) *Store {
|
func NewFromProtos(serverVersion uint64, msgs ...proto.Message) *Store {
|
||||||
s := NewStore()
|
s := New()
|
||||||
for _, msg := range msgs {
|
for _, msg := range msgs {
|
||||||
any := protoutil.NewAny(msg)
|
any := protoutil.NewAny(msg)
|
||||||
record := new(databroker.Record)
|
record := new(databroker.Record)
|
||||||
|
@ -120,7 +62,7 @@ func NewStoreFromProtos(serverVersion uint64, msgs ...proto.Message) *Store {
|
||||||
|
|
||||||
// ClearRecords removes all the records from the store.
|
// ClearRecords removes all the records from the store.
|
||||||
func (s *Store) ClearRecords() {
|
func (s *Store) ClearRecords() {
|
||||||
s.dataBrokerData.clear()
|
s.index.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDataBrokerVersions gets the databroker versions.
|
// GetDataBrokerVersions gets the databroker versions.
|
||||||
|
@ -131,8 +73,8 @@ func (s *Store) GetDataBrokerVersions() (serverVersion, recordVersion uint64) {
|
||||||
|
|
||||||
// GetRecordData gets a record's data from the store. `nil` is returned
|
// GetRecordData gets a record's data from the store. `nil` is returned
|
||||||
// if no record exists for the given type and id.
|
// if no record exists for the given type and id.
|
||||||
func (s *Store) GetRecordData(typeURL, id string) proto.Message {
|
func (s *Store) GetRecordData(typeURL, idOrValue string) proto.Message {
|
||||||
return s.dataBrokerData.get(typeURL, id)
|
return s.index.find(typeURL, idOrValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateIssuer updates the issuer in the store. The issuer is used as part of JWT construction.
|
// UpdateIssuer updates the issuer in the store. The issuer is used as part of JWT construction.
|
||||||
|
@ -159,10 +101,10 @@ func (s *Store) UpdateRoutePolicies(routePolicies []config.Policy) {
|
||||||
// UpdateRecord updates a record in the store.
|
// UpdateRecord updates a record in the store.
|
||||||
func (s *Store) UpdateRecord(serverVersion uint64, record *databroker.Record) {
|
func (s *Store) UpdateRecord(serverVersion uint64, record *databroker.Record) {
|
||||||
if record.GetDeletedAt() != nil {
|
if record.GetDeletedAt() != nil {
|
||||||
s.dataBrokerData.delete(record.GetType(), record.GetId())
|
s.index.delete(record.GetType(), record.GetId())
|
||||||
} else {
|
} else {
|
||||||
msg, _ := record.GetData().UnmarshalNew()
|
msg, _ := record.GetData().UnmarshalNew()
|
||||||
s.dataBrokerData.set(record.GetType(), record.GetId(), msg)
|
s.index.set(record.GetType(), record.GetId(), msg)
|
||||||
}
|
}
|
||||||
s.write("/databroker_server_version", fmt.Sprint(serverVersion))
|
s.write("/databroker_server_version", fmt.Sprint(serverVersion))
|
||||||
s.write("/databroker_record_version", fmt.Sprint(record.GetVersion()))
|
s.write("/databroker_record_version", fmt.Sprint(record.GetVersion()))
|
|
@ -1,9 +1,10 @@
|
||||||
package evaluator
|
package store
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"google.golang.org/protobuf/types/known/structpb"
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
"github.com/pomerium/pomerium/pkg/grpc/databroker"
|
"github.com/pomerium/pomerium/pkg/grpc/databroker"
|
||||||
|
@ -12,8 +13,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestStore(t *testing.T) {
|
func TestStore(t *testing.T) {
|
||||||
s := NewStore()
|
|
||||||
t.Run("records", func(t *testing.T) {
|
t.Run("records", func(t *testing.T) {
|
||||||
|
s := New()
|
||||||
u := &user.User{
|
u := &user.User{
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
Id: "u1",
|
Id: "u1",
|
||||||
|
@ -61,4 +62,22 @@ func TestStore(t *testing.T) {
|
||||||
v = s.GetRecordData(any.GetTypeUrl(), u.GetId())
|
v = s.GetRecordData(any.GetTypeUrl(), u.GetId())
|
||||||
assert.Nil(t, v)
|
assert.Nil(t, v)
|
||||||
})
|
})
|
||||||
|
t.Run("cidr", func(t *testing.T) {
|
||||||
|
s := New()
|
||||||
|
any := protoutil.NewAny(&structpb.Struct{Fields: map[string]*structpb.Value{
|
||||||
|
"$index": structpb.NewStructValue(&structpb.Struct{Fields: map[string]*structpb.Value{
|
||||||
|
"cidr": structpb.NewStringValue("192.168.0.0/16"),
|
||||||
|
}}),
|
||||||
|
"id": structpb.NewStringValue("r1"),
|
||||||
|
}})
|
||||||
|
s.UpdateRecord(0, &databroker.Record{
|
||||||
|
Version: 1,
|
||||||
|
Type: any.GetTypeUrl(),
|
||||||
|
Id: "r1",
|
||||||
|
Data: any,
|
||||||
|
})
|
||||||
|
|
||||||
|
v := s.GetRecordData(any.GetTypeUrl(), "192.168.0.7")
|
||||||
|
assert.NotNil(t, v)
|
||||||
|
})
|
||||||
}
|
}
|
|
@ -8,6 +8,7 @@ import (
|
||||||
googlegrpc "google.golang.org/grpc"
|
googlegrpc "google.golang.org/grpc"
|
||||||
|
|
||||||
"github.com/pomerium/pomerium/authorize/evaluator"
|
"github.com/pomerium/pomerium/authorize/evaluator"
|
||||||
|
"github.com/pomerium/pomerium/authorize/internal/store"
|
||||||
"github.com/pomerium/pomerium/config"
|
"github.com/pomerium/pomerium/config"
|
||||||
"github.com/pomerium/pomerium/internal/encoding"
|
"github.com/pomerium/pomerium/internal/encoding"
|
||||||
"github.com/pomerium/pomerium/internal/encoding/jws"
|
"github.com/pomerium/pomerium/internal/encoding/jws"
|
||||||
|
@ -27,7 +28,7 @@ type authorizeState struct {
|
||||||
auditEncryptor *protoutil.Encryptor
|
auditEncryptor *protoutil.Encryptor
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAuthorizeStateFromConfig(cfg *config.Config, store *evaluator.Store) (*authorizeState, error) {
|
func newAuthorizeStateFromConfig(cfg *config.Config, store *store.Store) (*authorizeState, error) {
|
||||||
if err := validateOptions(cfg.Options); err != nil {
|
if err := validateOptions(cfg.Options); err != nil {
|
||||||
return nil, fmt.Errorf("authorize: bad options: %w", err)
|
return nil, fmt.Errorf("authorize: bad options: %w", err)
|
||||||
}
|
}
|
||||||
|
|
5
go.mod
5
go.mod
|
@ -75,7 +75,10 @@ require (
|
||||||
sigs.k8s.io/yaml v1.3.0
|
sigs.k8s.io/yaml v1.3.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require github.com/CAFxX/httpcompression v0.0.8
|
require (
|
||||||
|
github.com/CAFxX/httpcompression v0.0.8
|
||||||
|
github.com/kentik/patricia v1.0.0
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
4d63.com/gochecknoglobals v0.1.0 // indirect
|
4d63.com/gochecknoglobals v0.1.0 // indirect
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -855,6 +855,8 @@ github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8
|
||||||
github.com/julz/importas v0.1.0 h1:F78HnrsjY3cR7j0etXy5+TU1Zuy7Xt08X/1aJnH5xXY=
|
github.com/julz/importas v0.1.0 h1:F78HnrsjY3cR7j0etXy5+TU1Zuy7Xt08X/1aJnH5xXY=
|
||||||
github.com/julz/importas v0.1.0/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSXO0DV0=
|
github.com/julz/importas v0.1.0/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSXO0DV0=
|
||||||
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
|
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
|
||||||
|
github.com/kentik/patricia v1.0.0 h1:jx/8kXf0JvQEHNPX4njL+PDzpxxqNKg0RjA8hJcX38A=
|
||||||
|
github.com/kentik/patricia v1.0.0/go.mod h1:e0nkPLU9NQl8v05ukfHU6+R5ykbKcXO+NqaC3ifTm0Y=
|
||||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue