Skip to content
Snippets Groups Projects
Commit 31649855 authored by Yordan Kinkov's avatar Yordan Kinkov
Browse files

Add policy output validation endpoint

parent 1431d527
No related branches found
No related tags found
No related merge requests found
...@@ -42,6 +42,28 @@ var _ = Service("policy", func() { ...@@ -42,6 +42,28 @@ var _ = Service("policy", func() {
}) })
}) })
Method("Validate", func() {
Description("Validate executes a policy with the given 'data' as input and validates the output schema.")
Payload(EvaluateRequest)
Result(EvaluateResult)
HTTP(func() {
GET("/policy/{repository}/{group}/{policyName}/{version}/validation/did.json")
GET("/policy/{repository}/{group}/{policyName}/{version}/validation")
POST("/policy/{repository}/{group}/{policyName}/{version}/validation")
Header("evaluationID:x-evaluation-id", String, "EvaluationID allows overwriting the randomly generated evaluationID", func() {
Example("did:web:example.com")
})
Header("ttl:x-cache-ttl", Int, "Policy result cache TTL in seconds", func() {
Example(60)
})
Body("input")
Response(StatusOK, func() {
Body("result")
Header("ETag")
})
})
})
Method("Lock", func() { Method("Lock", func() {
Description("Lock a policy so that it cannot be evaluated.") Description("Lock a policy so that it cannot be evaluated.")
Payload(LockRequest) Payload(LockRequest)
......
...@@ -14,6 +14,7 @@ import ( ...@@ -14,6 +14,7 @@ import (
"github.com/lestrrat-go/jwx/v2/jws" "github.com/lestrrat-go/jwx/v2/jws"
"github.com/open-policy-agent/opa/rego" "github.com/open-policy-agent/opa/rego"
"github.com/open-policy-agent/opa/storage/inmem" "github.com/open-policy-agent/opa/storage/inmem"
"github.com/santhosh-tekuri/jsonschema/v5"
"go.uber.org/zap" "go.uber.org/zap"
"gitlab.eclipse.org/eclipse/xfsc/tsa/golib/errors" "gitlab.eclipse.org/eclipse/xfsc/tsa/golib/errors"
...@@ -165,6 +166,52 @@ func (s *Service) Evaluate(ctx context.Context, req *policy.EvaluateRequest) (*p ...@@ -165,6 +166,52 @@ func (s *Service) Evaluate(ctx context.Context, req *policy.EvaluateRequest) (*p
}, nil }, nil
} }
// Validate executes a policy with given input and then validates the output against
// a predefined JSON schema.
func (s *Service) Validate(ctx context.Context, req *policy.EvaluateRequest) (*policy.EvaluateResult, error) {
// evaluate the policy and get the result
res, err := s.Evaluate(ctx, req)
if err != nil {
return nil, err
}
logger := s.logger.With(
zap.String("operation", "evaluate"),
zap.String("repository", req.Repository),
zap.String("group", req.Group),
zap.String("name", req.PolicyName),
zap.String("version", req.Version),
zap.String("evaluationID", res.ETag),
)
// retrieve policy
pol, err := s.retrievePolicy(ctx, req.Repository, req.Group, req.PolicyName, req.Version)
if err != nil {
logger.Error("error retrieving policy", zap.Error(err))
return nil, errors.New("error retrieving policy", err)
}
if pol.OutputSchema == "" {
logger.Error("validation schema for policy output is not found")
return nil, errors.New(errors.BadRequest, "validation schema for policy output is not found")
}
// compile the validation schema
sch, err := jsonschema.CompileString("", pol.OutputSchema)
if err != nil {
logger.Error("error compiling output validation schema", zap.Error(err))
return nil, errors.New("error compiling output validation schema")
}
// validate the policy output
if err := sch.Validate(res.Result); err != nil {
logger.Error("invalid policy output schema", zap.Error(err))
return nil, errors.New("invalid policy output schema", err)
}
return res, nil
}
// Lock a policy so that it cannot be evaluated. // Lock a policy so that it cannot be evaluated.
func (s *Service) Lock(ctx context.Context, req *policy.LockRequest) error { func (s *Service) Lock(ctx context.Context, req *policy.LockRequest) error {
logger := s.logger.With( logger := s.logger.With(
...@@ -356,20 +403,10 @@ func (s *Service) SubscribeForPolicyChange(ctx context.Context, req *policy.Subs ...@@ -356,20 +403,10 @@ func (s *Service) SubscribeForPolicyChange(ctx context.Context, req *policy.Subs
// If the policyCache entry is not found, it will try to prepare a new // If the policyCache entry is not found, it will try to prepare a new
// query and will set it into the policyCache for future use. // query and will set it into the policyCache for future use.
func (s *Service) prepareQuery(ctx context.Context, repository, group, policyName, version string, headers map[string]string) (*rego.PreparedEvalQuery, error) { func (s *Service) prepareQuery(ctx context.Context, repository, group, policyName, version string, headers map[string]string) (*rego.PreparedEvalQuery, error) {
// retrieve policy from cache // retrieve policy
key := s.queryCacheKey(repository, group, policyName, version) pol, err := s.retrievePolicy(ctx, repository, group, policyName, version)
pol, ok := s.policyCache.Get(key) if err != nil {
if !ok { return nil, err
// retrieve policy from database storage
var err error
pol, err = s.storage.Policy(ctx, repository, group, policyName, version)
if err != nil {
if errors.Is(errors.NotFound, err) {
return nil, err
}
return nil, errors.New("error getting policy from storage", err)
}
s.policyCache.Set(key, pol)
} }
// if policy is locked, return an error // if policy is locked, return an error
...@@ -426,6 +463,26 @@ func (s *Service) buildRegoArgs(filename, regoPolicy, regoQuery, regoData string ...@@ -426,6 +463,26 @@ func (s *Service) buildRegoArgs(filename, regoPolicy, regoQuery, regoData string
return availableFuncs, nil return availableFuncs, nil
} }
func (s *Service) retrievePolicy(ctx context.Context, repository, group, policyName, version string) (*storage.Policy, error) {
// retrieve policy from cache
key := s.queryCacheKey(repository, group, policyName, version)
p, ok := s.policyCache.Get(key)
if !ok {
// retrieve policy from storage
var err error
p, err = s.storage.Policy(ctx, repository, group, policyName, version)
if err != nil {
if errors.Is(errors.NotFound, err) {
return nil, err
}
return nil, errors.New("error getting policy from storage", err)
}
s.policyCache.Set(key, p)
}
return p, nil
}
func (s *Service) queryCacheKey(repository, group, policyName, version string) string { func (s *Service) queryCacheKey(repository, group, policyName, version string) string {
return fmt.Sprintf("%s,%s,%s,%s", repository, group, policyName, version) return fmt.Sprintf("%s,%s,%s,%s", repository, group, policyName, version)
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment