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() {
})
})
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() {
Description("Lock a policy so that it cannot be evaluated.")
Payload(LockRequest)
......
......@@ -14,6 +14,7 @@ import (
"github.com/lestrrat-go/jwx/v2/jws"
"github.com/open-policy-agent/opa/rego"
"github.com/open-policy-agent/opa/storage/inmem"
"github.com/santhosh-tekuri/jsonschema/v5"
"go.uber.org/zap"
"gitlab.eclipse.org/eclipse/xfsc/tsa/golib/errors"
......@@ -165,6 +166,52 @@ func (s *Service) Evaluate(ctx context.Context, req *policy.EvaluateRequest) (*p
}, 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.
func (s *Service) Lock(ctx context.Context, req *policy.LockRequest) error {
logger := s.logger.With(
......@@ -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
// 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) {
// retrieve policy from cache
key := s.queryCacheKey(repository, group, policyName, version)
pol, ok := s.policyCache.Get(key)
if !ok {
// 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)
// retrieve policy
pol, err := s.retrievePolicy(ctx, repository, group, policyName, version)
if err != nil {
return nil, err
}
// if policy is locked, return an error
......@@ -426,6 +463,26 @@ func (s *Service) buildRegoArgs(filename, regoPolicy, regoQuery, regoData string
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 {
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