Skip to content

feat: audit trail in stdout logs #411

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 40 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
8839bd1
feat: audit trail in stdout logs
fredmaggiowski Dec 3, 2024
97bb1ed
optional auditing
fredmaggiowski Dec 3, 2024
ef76ca4
minor fixes + coverage
fredmaggiowski Dec 3, 2024
074a75c
Update audit/agent_log_test.go
fredmaggiowski Dec 3, 2024
e221502
more tests
fredmaggiowski Dec 3, 2024
afe956a
fix
fredmaggiowski Dec 3, 2024
f9d4e2c
fix
fredmaggiowski Dec 3, 2024
8f92bb4
refactor: renamed builtin, ensure context value and error in response…
fredmaggiowski Dec 3, 2024
58c50bc
test: null logger
fredmaggiowski Dec 3, 2024
6fe8c42
golang upgrade
fredmaggiowski Dec 3, 2024
76bd7b8
dump tests
fredmaggiowski Dec 3, 2024
498be87
Update service/opa_transport_test.go
fredmaggiowski Dec 3, 2024
f69eea8
chore: restore go mod
fredmaggiowski Dec 3, 2024
25ff45d
test audit factory
fredmaggiowski Dec 3, 2024
8e9c277
refactir: map[string]any
fredmaggiowski Dec 3, 2024
17af74c
Apply suggestions from code review
fredmaggiowski Dec 3, 2024
62d9a99
fix: copyright
fredmaggiowski Dec 3, 2024
99a357b
refactor: move audit package to interanl
fredmaggiowski Dec 3, 2024
6f85293
test: coverage for core custom builtins
fredmaggiowski Dec 3, 2024
667ea71
refactor: append instead of for-loop
fredmaggiowski Dec 3, 2024
f63ab87
refactor: import
fredmaggiowski Dec 3, 2024
9c1bdfd
test: unit agent log
fredmaggiowski Dec 3, 2024
86e3dac
feat: trace policy failures as well
fredmaggiowski Dec 3, 2024
e2a143a
rem: enabled flag
fredmaggiowski Dec 3, 2024
11d2717
rem: enabled flag
fredmaggiowski Dec 3, 2024
a75053e
fix: omitempty + refactor moved code
fredmaggiowski Dec 3, 2024
031747e
add extra unit
fredmaggiowski Dec 3, 2024
6d38186
more coverage
fredmaggiowski Dec 3, 2024
5055223
fix: trace request verb and path
fredmaggiowski Dec 4, 2024
96872ce
test case for requrst policy
fredmaggiowski Dec 4, 2024
4a7f695
test case for requrst policy
fredmaggiowski Dec 4, 2024
6f7d8e1
more test
fredmaggiowski Dec 4, 2024
3bdbbef
more tests
fredmaggiowski Dec 4, 2024
3e74554
refactor: constant fr audit tag
fredmaggiowski Dec 5, 2024
ceee236
fix: trace enabled for service
fredmaggiowski Dec 5, 2024
93ecebb
Merge branch 'main' into feat/audit-trail-in-stdout-logs
fredmaggiowski Dec 5, 2024
935cd76
target service name
fredmaggiowski Dec 5, 2024
eebc0e4
log service name + user agent
fredmaggiowski Dec 5, 2024
5c92be6
refactor: use constant for user-agent
fredmaggiowski Dec 5, 2024
ef1c3bf
refactor: use constant for user-agent
fredmaggiowski Dec 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 34 additions & 4 deletions core/opaevaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ package core

import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"time"

"github.com/rond-authz/rond/custom_builtins"
"github.com/rond-authz/rond/internal/audit"
"github.com/rond-authz/rond/internal/opatranslator"
"github.com/rond-authz/rond/internal/utils"
"github.com/rond-authz/rond/logging"
Expand All @@ -33,6 +35,10 @@ import (
"go.mongodb.org/mongo-driver/bson/primitive"
)

var (
ErrModuleCompilationFailed = errors.New("failed module compilation")
)

type RondConfig struct {
RequestFlow RequestFlow `json:"requestFlow"`
ResponseFlow ResponseFlow `json:"responseFlow"`
Expand Down Expand Up @@ -75,6 +81,7 @@ type OPAEvaluatorOptions struct {
EnablePrintStatements bool
MongoClient custom_builtins.IMongoClient
Logger logging.Logger
Builtins []func(*rego.Rego)
}

func (opaEval *OPAEvaluator) partiallyEvaluate(logger logging.Logger, input EvalInput, options *PolicyEvaluationOptions) (primitive.M, error) {
Expand Down Expand Up @@ -230,12 +237,21 @@ type Module struct {
Content string
}

func NewOPAModuleConfig(modules []Module) (*OPAModuleConfig, error) {
compiler := ast.NewCompiler().WithBuiltins(map[string]*ast.Builtin{
type BuiltinDeclarations map[string]*ast.Builtin

func NewOPAModuleConfigWithBuiltins(modules []Module, builtinsDeclarations BuiltinDeclarations) (*OPAModuleConfig, error) {
builtins := map[string]*ast.Builtin{
custom_builtins.GetHeaderDecl.Name: custom_builtins.GetHeaderDecl,
custom_builtins.MongoFindOneDecl.Name: custom_builtins.MongoFindOneDecl,
custom_builtins.MongoFindManyDecl.Name: custom_builtins.MongoFindManyDecl,
})
audit.SetLabelsDecl.Name: audit.SetLabelsDecl,
}

for name, decl := range builtinsDeclarations {
builtins[name] = decl
}

compiler := ast.NewCompiler().WithBuiltins(builtins)

modulesToCompile := map[string]*ast.Module{}
for _, m := range modules {
Expand All @@ -245,7 +261,7 @@ func NewOPAModuleConfig(modules []Module) (*OPAModuleConfig, error) {
compiler.Compile(modulesToCompile)

if compiler.Failed() {
return nil, fmt.Errorf("fails to compile the module: %s", compiler.Errors)
return nil, fmt.Errorf("%w: %s", ErrModuleCompilationFailed, compiler.Errors)
}

return &OPAModuleConfig{
Expand All @@ -254,6 +270,11 @@ func NewOPAModuleConfig(modules []Module) (*OPAModuleConfig, error) {
}, nil
}

func NewOPAModuleConfig(modules []Module) (*OPAModuleConfig, error) {
return NewOPAModuleConfigWithBuiltins(modules, nil)
}

// MustNewOPAModuleConfig is an helper function for tests purposes only!!
func MustNewOPAModuleConfig(modules []Module) *OPAModuleConfig {
opaModule, err := NewOPAModuleConfig(modules)
if err != nil {
Expand All @@ -262,6 +283,15 @@ func MustNewOPAModuleConfig(modules []Module) *OPAModuleConfig {
return opaModule
}

// MustNewOPAModuleConfigWithBuiltins is an helper function for tests purposes only!!
func MustNewOPAModuleConfigWithBuiltins(modules []Module, builtins BuiltinDeclarations) *OPAModuleConfig {
opaModule, err := NewOPAModuleConfigWithBuiltins(modules, builtins)
if err != nil {
panic(err)
}
return opaModule
}

type PermissionOnResourceKey string

type PermissionsOnResourceMap map[PermissionOnResourceKey]bool
Expand Down
6 changes: 3 additions & 3 deletions core/partialevaluators.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func (policyEvaluators PartialResultsEvaluators) AddFromConfig(ctx context.Conte
}

if _, ok := policyEvaluators[allowPolicy]; !ok {
evaluator, err := createPartialEvaluator(ctx, logger, allowPolicy, opaModuleConfig, options, isFilterQuery)
evaluator, err := createEvaluator(ctx, logger, allowPolicy, opaModuleConfig, options, isFilterQuery)
if err != nil {
return fmt.Errorf("%w: %s", ErrEvaluatorCreationFailed, err.Error())
}
Expand All @@ -57,7 +57,7 @@ func (policyEvaluators PartialResultsEvaluators) AddFromConfig(ctx context.Conte

if responsePolicy != "" {
if _, ok := policyEvaluators[responsePolicy]; !ok {
evaluator, err := createPartialEvaluator(ctx, logger, responsePolicy, opaModuleConfig, options, false)
evaluator, err := createEvaluator(ctx, logger, responsePolicy, opaModuleConfig, options, false)
if err != nil {
return fmt.Errorf("%w: %s", ErrEvaluatorCreationFailed, err.Error())
}
Expand Down Expand Up @@ -87,7 +87,7 @@ func (partialEvaluators PartialResultsEvaluators) GetEvaluatorFromPolicy(ctx con
return nil, fmt.Errorf("%w: %s", ErrEvaluatorNotFound, policy)
}

func createPartialEvaluator(ctx context.Context, logger logging.Logger, policy string, opaModuleConfig *OPAModuleConfig, options *OPAEvaluatorOptions, isPartial bool) (*PartialEvaluator, error) {
func createEvaluator(ctx context.Context, logger logging.Logger, policy string, opaModuleConfig *OPAModuleConfig, options *OPAEvaluatorOptions, isPartial bool) (*PartialEvaluator, error) {
logger.WithField("policyName", policy).Info("precomputing rego policy")

preparedPartialEvaluator := &PartialEvaluator{}
Expand Down
60 changes: 60 additions & 0 deletions core/partialevaluators_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ import (
"github.com/rond-authz/rond/logging"
"github.com/rond-authz/rond/metrics"

"github.com/open-policy-agent/opa/ast"
"github.com/open-policy-agent/opa/rego"
"github.com/open-policy-agent/opa/types"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -406,4 +409,61 @@ func TestPartialResultEvaluators(t *testing.T) {
require.Empty(t, query)
require.Empty(t, res)
})

t.Run("evaluates policy with custom builtins", func(t *testing.T) {
partialEvaluators := PartialResultsEvaluators{}
rondConfig := &RondConfig{
RequestFlow: RequestFlow{
PolicyName: "check_metadata",
},
}

builtinDecl := &ast.Builtin{
Name: "some_builtin",
Decl: types.NewFunction(types.Args(types.A), types.A),
}

opaModule := MustNewOPAModuleConfigWithBuiltins([]Module{
{
Name: "example.rego",
Content: `package policies
check_metadata {
some_builtin("test")
}`,
},
}, BuiltinDeclarations{
"some_builtin": builtinDecl,
})

someBuiltinInvoked := false
builtins := []func(*rego.Rego){
rego.Function1(
&rego.Function{
Name: "some_builtin",
Decl: types.NewFunction(types.Args(types.A), types.A),
},
func(bctx rego.BuiltinContext, dataToStore *ast.Term) (*ast.Term, error) {
someBuiltinInvoked = true
return ast.BooleanTerm(true), nil
},
),
}
evalOpts := OPAEvaluatorOptions{
Logger: logger,
Builtins: builtins,
}

err := partialEvaluators.AddFromConfig(context.Background(), logger, opaModule, rondConfig, &evalOpts)
require.NoError(t, err)

input, err := CreateRegoQueryInput(logger, rondInput, RegoInputOptions{})
require.NoError(t, err)
evaluator, err := partialEvaluators.GetEvaluatorFromPolicy(context.Background(), "check_metadata", &evalOpts)
require.NoError(t, err)
res, query, err := evaluator.PolicyEvaluation(logger, input, nil)
require.NoError(t, err)
require.Empty(t, query)
require.Empty(t, res)
require.True(t, someBuiltinInvoked)
})
}
7 changes: 7 additions & 0 deletions core/rego.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/open-policy-agent/opa/ast"
"github.com/open-policy-agent/opa/rego"
"github.com/rond-authz/rond/custom_builtins"
"github.com/rond-authz/rond/internal/audit"
)

func newRegoInstanceBuilder(policy string, opaModuleConfig *OPAModuleConfig, evaluatorOptions *OPAEvaluatorOptions) (*rego.Rego, error) {
Expand All @@ -44,7 +45,13 @@ func newRegoInstanceBuilder(policy string, opaModuleConfig *OPAModuleConfig, eva
rego.PrintHook(NewPrintHook(os.Stdout, policy)),
rego.Capabilities(ast.CapabilitiesForThisVersion()),
custom_builtins.GetHeaderFunction,
audit.SetLabels,
}

if len(evaluatorOptions.Builtins) > 0 {
options = append(options, evaluatorOptions.Builtins...)
}

if evaluatorOptions.MongoClient != nil {
options = append(options, custom_builtins.MongoFindOne, custom_builtins.MongoFindMany)
}
Expand Down
34 changes: 34 additions & 0 deletions internal/audit/agent.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright 2024 Mia srl
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package audit

import "context"

type Labels = map[string]any

type Agent interface {
Trace(context.Context, Audit)
Cache() AuditCache
SetGlobalLabels(labels Labels)
}

// noopAgent is a lazy agent that does nothing :(
type noopAgent struct{}

func NewNoopAgent() Agent { return &noopAgent{} }

func (a *noopAgent) Trace(context.Context, Audit) {}
func (a *noopAgent) Cache() AuditCache { return &SingleRecordCache{} }
func (a *noopAgent) SetGlobalLabels(labels Labels) {}
59 changes: 59 additions & 0 deletions internal/audit/agent_log.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright 2024 Mia srl
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package audit

import (
"context"

"github.com/rond-authz/rond/internal/utils"
"github.com/rond-authz/rond/logging"
)

type logAgent struct {
l logging.Logger
cache AuditCache
globals map[string]any
}

func NewLogAgent(l logging.Logger) Agent {
return &logAgent{
l: l,
cache: &SingleRecordCache{},
}
}
func (a *logAgent) SetGlobalLabels(labels Labels) {
a.globals = labels
}

func (a *logAgent) Trace(_ context.Context, auditInput Audit) {
data := a.cache.Load()

auditData := auditInput.toPrint()
if a.globals != nil {
auditData.applyDataFromPolicy(a.globals)
}

if data != nil {
auditData.applyDataFromPolicy(data)
}

a.l.
WithField("trail", utils.ToMap(auditSerializerTagAnnotation, auditData)).
Info("audit trail")
}

func (a *logAgent) Cache() AuditCache {
return a.cache
}
Loading
Loading