diff --git a/cerbos/client.go b/cerbos/client.go index c05c188..5cd3a5a 100644 --- a/cerbos/client.go +++ b/cerbos/client.go @@ -7,6 +7,7 @@ import ( "context" policyv1 "github.com/cerbos/cerbos/api/genpb/cerbos/policy/v1" + responsev1 "github.com/cerbos/cerbos/api/genpb/cerbos/response/v1" schemav1 "github.com/cerbos/cerbos/api/genpb/cerbos/schema/v1" ) @@ -43,7 +44,8 @@ type PrincipalContext interface { type AdminClient interface { AddOrUpdatePolicy(ctx context.Context, policies *PolicySet) error AuditLogs(ctx context.Context, opts AuditLogOptions) (<-chan *AuditLogEntry, error) - ListPolicies(ctx context.Context, opts ...ListPoliciesOption) ([]string, error) + ListPolicies(ctx context.Context, opts ...FilterOption) ([]string, error) + InspectPolicies(ctx context.Context, opts ...FilterOption) (*responsev1.InspectPoliciesResponse, error) GetPolicy(ctx context.Context, ids ...string) ([]*policyv1.Policy, error) DisablePolicy(ctx context.Context, ids ...string) (uint32, error) EnablePolicy(ctx context.Context, ids ...string) (uint32, error) diff --git a/cerbos/grpc_admin.go b/cerbos/grpc_admin.go index 34238d8..8a983ef 100644 --- a/cerbos/grpc_admin.go +++ b/cerbos/grpc_admin.go @@ -170,10 +170,16 @@ func (c *GRPCAdminClient) auditLogs(ctx context.Context, opts AuditLogOptions) ( return resp, nil } -func (c *GRPCAdminClient) ListPolicies(ctx context.Context, opts ...ListPoliciesOption) ([]string, error) { - req := &requestv1.ListPoliciesRequest{} +func (c *GRPCAdminClient) ListPolicies(ctx context.Context, opts ...FilterOption) ([]string, error) { + options := &FilterOptions{} for _, opt := range opts { - opt(req) + opt(options) + } + req := &requestv1.ListPoliciesRequest{ + IncludeDisabled: options.IncludeDisabled, + NameRegexp: options.NameRegexp, + ScopeRegexp: options.ScopeRegexp, + VersionRegexp: options.VersionRegexp, } if err := internal.Validate(req); err != nil { return nil, fmt.Errorf("could not validate list policies request: %w", err) @@ -187,6 +193,29 @@ func (c *GRPCAdminClient) ListPolicies(ctx context.Context, opts ...ListPolicies return p.PolicyIds, nil } +func (c *GRPCAdminClient) InspectPolicies(ctx context.Context, opts ...FilterOption) (*responsev1.InspectPoliciesResponse, error) { + options := &FilterOptions{} + for _, opt := range opts { + opt(options) + } + req := &requestv1.InspectPoliciesRequest{ + IncludeDisabled: options.IncludeDisabled, + NameRegexp: options.NameRegexp, + ScopeRegexp: options.ScopeRegexp, + VersionRegexp: options.VersionRegexp, + } + if err := internal.Validate(req); err != nil { + return nil, fmt.Errorf("could not validate get inspect policies request: %w", err) + } + + resp, err := c.client.InspectPolicies(metadata.AppendToOutgoingContext(ctx, c.headers...), req, grpc.PerRPCCredentials(c.creds)) + if err != nil { + return nil, fmt.Errorf("could not inspect policies: %w", err) + } + + return resp, nil +} + func (c *GRPCAdminClient) GetPolicy(ctx context.Context, ids ...string) ([]*policyv1.Policy, error) { req := &requestv1.GetPolicyRequest{ Id: ids, diff --git a/cerbos/grpc_admin_test.go b/cerbos/grpc_admin_test.go index 91c5bbd..f827110 100644 --- a/cerbos/grpc_admin_test.go +++ b/cerbos/grpc_admin_test.go @@ -161,7 +161,7 @@ func TestAdminClient(t *testing.T) { t.Run("ListPolicies", func(t *testing.T) { testCases := []struct { name string - options []ListPoliciesOption + options []FilterOption want map[string]string }{ { @@ -170,7 +170,7 @@ func TestAdminClient(t *testing.T) { }, { name: "NameRegexp", - options: []ListPoliciesOption{WithNameRegexp("leave_req")}, + options: []FilterOption{WithNameRegexp("leave_req")}, want: map[string]string{ "resource.leave_request.v20210210": "", "resource.leave_request.vdefault": "", @@ -181,7 +181,7 @@ func TestAdminClient(t *testing.T) { }, { name: "ScopeRegexp", - options: []ListPoliciesOption{WithScopeRegexp("acme")}, + options: []FilterOption{WithScopeRegexp("acme")}, want: map[string]string{ "principal.donald_duck.vdefault/acme": "", "principal.donald_duck.vdefault/acme.hr": "", @@ -192,14 +192,14 @@ func TestAdminClient(t *testing.T) { }, { name: "VersionRegexp", - options: []ListPoliciesOption{WithVersionRegexp(`\d+`)}, + options: []FilterOption{WithVersionRegexp(`\d+`)}, want: map[string]string{ "resource.leave_request.v20210210": "", }, }, { name: "AllRegexp", - options: []ListPoliciesOption{WithNameRegexp(`.*`), WithScopeRegexp(`.*`), WithVersionRegexp("def")}, + options: []FilterOption{WithNameRegexp(`.*`), WithScopeRegexp(`.*`), WithVersionRegexp("def")}, want: map[string]string{ "principal.donald_duck.vdefault": "", "principal.donald_duck.vdefault/acme": "", @@ -225,6 +225,87 @@ func TestAdminClient(t *testing.T) { } }) + t.Run("InspectPolicies", func(t *testing.T) { + testCases := []struct { + name string + options []FilterOption + want map[string][]string + }{ + { + name: "NoFilter", + want: map[string][]string{ + "principal.donald_duck.vdefault": {"*"}, + "principal.donald_duck.vdefault/acme": {"*"}, + "principal.donald_duck.vdefault/acme.hr": {"view:*"}, + "resource.leave_request.v20210210": {"*", "approve", "create", "defer", "delete", "remind", "view", "view:*", "view:public"}, + "resource.leave_request.vdefault": {"*"}, + "resource.leave_request.vdefault/acme": {"create", "view:public"}, + "resource.leave_request.vdefault/acme.hr": {"approve", "defer", "delete", "view:*"}, + "resource.leave_request.vdefault/acme.hr.uk": {"defer", "delete"}, + }, + }, + { + name: "NameRegexp", + options: []FilterOption{WithNameRegexp("leave_req")}, + want: map[string][]string{ + "resource.leave_request.v20210210": {"*", "approve", "create", "defer", "delete", "remind", "view", "view:*", "view:public"}, + "resource.leave_request.vdefault": {"*"}, + "resource.leave_request.vdefault/acme": {"create", "view:public"}, + "resource.leave_request.vdefault/acme.hr": {"approve", "defer", "delete", "view:*"}, + "resource.leave_request.vdefault/acme.hr.uk": {"defer", "delete"}, + }, + }, + { + name: "ScopeRegexp", + options: []FilterOption{WithScopeRegexp("acme")}, + want: map[string][]string{ + "principal.donald_duck.vdefault/acme": {"*"}, + "principal.donald_duck.vdefault/acme.hr": {"view:*"}, + "resource.leave_request.vdefault/acme": {"create", "view:public"}, + "resource.leave_request.vdefault/acme.hr": {"approve", "defer", "delete", "view:*"}, + "resource.leave_request.vdefault/acme.hr.uk": {"defer", "delete"}, + }, + }, + { + name: "VersionRegexp", + options: []FilterOption{WithVersionRegexp(`\d+`)}, + want: map[string][]string{ + "resource.leave_request.v20210210": {"*", "approve", "create", "defer", "delete", "remind", "view", "view:*", "view:public"}, + }, + }, + { + name: "AllRegexp", + options: []FilterOption{WithNameRegexp(`.*`), WithScopeRegexp(`.*`), WithVersionRegexp("def")}, + want: map[string][]string{ + "principal.donald_duck.vdefault": {"*"}, + "principal.donald_duck.vdefault/acme": {"*"}, + "principal.donald_duck.vdefault/acme.hr": {"view:*"}, + "resource.leave_request.vdefault": {"*"}, + "resource.leave_request.vdefault/acme": {"create", "view:public"}, + "resource.leave_request.vdefault/acme.hr": {"approve", "defer", "delete", "view:*"}, + "resource.leave_request.vdefault/acme.hr.uk": {"defer", "delete"}, + }, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + have, err := ac.InspectPolicies(context.Background(), tc.options...) + require.NoError(t, err) + require.NotNil(t, have) + require.NotNil(t, have.Results) + for fqn, actions := range tc.want { + t.Run(fqn, func(t *testing.T) { + require.NotNil(t, have.Results[fqn]) + require.Len(t, have.Results[fqn].Actions, len(actions)) + require.ElementsMatch(t, have.Results[fqn].Actions, actions) + }) + } + }) + } + }) + t.Run("AddOrUpdateSchema", func(t *testing.T) { ss := NewSchemaSet() for k, s := range schemas { diff --git a/cerbos/model.go b/cerbos/model.go index 714bf3d..11a6e31 100644 --- a/cerbos/model.go +++ b/cerbos/model.go @@ -1212,29 +1212,39 @@ type PlanResourcesResponse struct { } type ( - ListPoliciesOption func(*requestv1.ListPoliciesRequest) + FilterOptions struct { + NameRegexp string + ScopeRegexp string + VersionRegexp string + IncludeDisabled bool + } + // FilterOption allows filtering policies while calling InspectPolicies and ListPolicies. + FilterOption func(*FilterOptions) + // ListPoliciesOption allows filtering policies while calling ListPolicies + // Deprecated: ListPoliciesOption is deprecated, use FilterOption instead. + ListPoliciesOption = FilterOption ) -func WithIncludeDisabled() ListPoliciesOption { - return func(request *requestv1.ListPoliciesRequest) { - request.IncludeDisabled = true +func WithIncludeDisabled() FilterOption { + return func(fo *FilterOptions) { + fo.IncludeDisabled = true } } -func WithNameRegexp(re string) ListPoliciesOption { - return func(request *requestv1.ListPoliciesRequest) { - request.NameRegexp = re +func WithNameRegexp(re string) FilterOption { + return func(fo *FilterOptions) { + fo.NameRegexp = re } } -func WithScopeRegexp(re string) ListPoliciesOption { - return func(request *requestv1.ListPoliciesRequest) { - request.ScopeRegexp = re +func WithScopeRegexp(re string) FilterOption { + return func(fo *FilterOptions) { + fo.ScopeRegexp = re } } -func WithVersionRegexp(v string) ListPoliciesOption { - return func(request *requestv1.ListPoliciesRequest) { - request.VersionRegexp = v +func WithVersionRegexp(v string) FilterOption { + return func(fo *FilterOptions) { + fo.VersionRegexp = v } } diff --git a/go.mod b/go.mod index c10ac3d..caaf9be 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.20 require ( github.com/bufbuild/protovalidate-go v0.6.1 github.com/cenkalti/backoff/v4 v4.3.0 - github.com/cerbos/cerbos/api/genpb v0.34.0 + github.com/cerbos/cerbos/api/genpb v0.34.1-0.20240404120519-19d38a48998f github.com/ghodss/yaml v1.0.0 github.com/google/go-cmp v0.6.0 github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 diff --git a/go.sum b/go.sum index 7495178..6572479 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,8 @@ github.com/bufbuild/protovalidate-go v0.6.1 h1:uzW8r0CDvqApUChNj87VzZVoQSKhcVdw5 github.com/bufbuild/protovalidate-go v0.6.1/go.mod h1:4BR3rKEJiUiTy+sqsusFn2ladOf0kYmA2Reo6BHSBgQ= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/cerbos/cerbos/api/genpb v0.34.0 h1:HY8k9HVHv000EKU61KrhAia5TmjW4sX2AXNee4I+DZg= -github.com/cerbos/cerbos/api/genpb v0.34.0/go.mod h1:KEUMaRkMsCvEcOI8aptMFscKh6H2bfWgjjjSmRWKg8g= +github.com/cerbos/cerbos/api/genpb v0.34.1-0.20240404120519-19d38a48998f h1:2QY32KPwny2zJCE7fWBdarci+r6hwYeVeHp7xK/BYq8= +github.com/cerbos/cerbos/api/genpb v0.34.1-0.20240404120519-19d38a48998f/go.mod h1:DcozdAIUztxXwtVs88gGgdyCITru7WCTF9vGA6j+H8k= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=