Skip to content

Commit

Permalink
test: added unit test
Browse files Browse the repository at this point in the history
Signed-off-by: Sanskarzz <sanskar.gur@gmail.com>
  • Loading branch information
Sanskarzz authored and anushkamittal2001 committed May 22, 2024
1 parent 40b8524 commit 9fe6978
Show file tree
Hide file tree
Showing 7 changed files with 460 additions and 184 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
github.com/jmespath-community/go-jmespath v1.1.2-0.20240117150817-e430401a2172
github.com/kyverno/kyverno-json v0.0.3-alpha.2
github.com/spf13/cobra v1.8.0
github.com/stretchr/testify v1.8.4
go.uber.org/multierr v1.11.0
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de
google.golang.org/grpc v1.63.2
Expand Down Expand Up @@ -49,6 +50,7 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.18.0 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.46.0 // indirect
Expand Down
187 changes: 3 additions & 184 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,41 +1,11 @@
package main

import (
"context"

"fmt"
"io"
"log"
"net"
"net/http"
"os"
"os/signal"
"syscall"
"time"

authv3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
typev3 "github.com/envoyproxy/go-control-plane/envoy/type/v3"
jsonengine "github.com/kyverno/kyverno-envoy-plugin/pkg/json-engine"
"github.com/kyverno/kyverno-envoy-plugin/pkg/request"
"github.com/kyverno/kyverno-json/pkg/policy"
"github.com/kyverno/kyverno-envoy-plugin/pkg/server"
"github.com/spf13/cobra"
"go.uber.org/multierr"
"google.golang.org/genproto/googleapis/rpc/status"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"k8s.io/apimachinery/pkg/util/wait"
)

type Servers struct {
httpServer *http.Server
grpcServer *grpc.Server
grpcV3 *extAuthzServerV3
}

type (
extAuthzServerV3 struct {
policies []string
}
)

var policies []string
Expand All @@ -48,162 +18,11 @@ var serveCmd = &cobra.Command{
Use: "serve",
Short: "Start the kyverno-envoy-plugin server",
Run: func(cmd *cobra.Command, args []string) {
srv := NewServers(policies)
startServers(srv)
srv := server.NewServers(policies)
server.StartServers(srv)
},
}

func NewServers(policies []string) *Servers {
return &Servers{
grpcV3: &extAuthzServerV3{
policies: policies,
},
}
}

func handler(w http.ResponseWriter, r *http.Request) {
fmt.Printf("Received request from %s %s\n", r.RemoteAddr, r.URL.Path)
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Error reading request body", http.StatusInternalServerError)
return
}
defer r.Body.Close()
fmt.Println("Request payload:", string(body))
}

func (s *extAuthzServerV3) Check(ctx context.Context, req *authv3.CheckRequest) (*authv3.CheckResponse, error) {
// Parse request
attrs := req.GetAttributes()

// Load policies from files
policies, err := policy.Load(s.policies...)
if err != nil {
log.Printf("Failed to load policies: %v", err)
return nil, err
}

resource, err := request.Convert(attrs)
if err != nil {
log.Printf("Error converting request: %v", err)
return nil, err
}

engine := jsonengine.New()
response := engine.Run(ctx, jsonengine.Request{
Resource: resource,
Policies: policies,
})

log.Printf("Request is initialized in kyvernojson engine .\n")

var violations []error

for _, policy := range response.Policies {
for _, rule := range policy.Rules {
if rule.Error != nil {
// If there is an error, add it to the violations error array
violations = append(violations, fmt.Errorf("%v", rule.Error))
log.Printf("Request violation: %v\n", rule.Error.Error())
} else if len(rule.Violations) != 0 {
// If there are violations, add them to the violations error array
for _, violation := range rule.Violations {
violations = append(violations, fmt.Errorf("%v", violation))
}
log.Printf("Request violation: %v\n", rule.Violations.Error())
} else {
// If the rule passed, log it but continue to the next rule/policy
log.Printf("Request passed the %v policy rule.\n", rule.Rule.Name)
}
}
}

if len(violations) == 0 {
return s.allow(), nil
} else {
// combiine all violations errors into a single error
denialReason := multierr.Combine(violations...).Error()
return s.deny(denialReason), nil
}

}

func (s *extAuthzServerV3) allow() *authv3.CheckResponse {
return &authv3.CheckResponse{
Status: &status.Status{Code: int32(codes.OK)},
}
}

func (s *extAuthzServerV3) deny(denialReason string) *authv3.CheckResponse {
return &authv3.CheckResponse{
Status: &status.Status{
Code: int32(codes.PermissionDenied),
},
HttpResponse: &authv3.CheckResponse_DeniedResponse{
DeniedResponse: &authv3.DeniedHttpResponse{
Status: &typev3.HttpStatus{Code: typev3.StatusCode_Forbidden},
Body: fmt.Sprintf("Request denied by Kyverno JSON engine. Reason: %s", denialReason),
},
},
}
}

func (s *Servers) startGRPCServer(ctx context.Context) {
lis, err := net.Listen("tcp", ":9000")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s.grpcServer = grpc.NewServer()
fmt.Println("Starting GRPC server on Port 9000")
authv3.RegisterAuthorizationServer(s.grpcServer, s.grpcV3)

go func() {
<-ctx.Done()
if s.grpcServer != nil {
fmt.Println("GRPC server shutting down...")
s.grpcServer.GracefulStop()
}
}()

if err := s.grpcServer.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}

func (s *Servers) startHTTPServer(ctx context.Context) {
s.httpServer = &http.Server{
Addr: ":8000",
Handler: http.HandlerFunc(handler),
}
fmt.Println("Starting HTTP server on Port 8000")
go func() {
<-ctx.Done()

fmt.Println("HTTP server shutting down...")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := s.httpServer.Shutdown(ctx); err != nil && err != http.ErrServerClosed {
log.Fatal("Shutdown HTTP server:", err)
}
}()

if err := s.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatal("ListenAndServe: ", err)
}
}

func startServers(srv *Servers) {
var group wait.Group
func() {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel()
group.StartWithContext(ctx, srv.startHTTPServer)
group.StartWithContext(ctx, srv.startGRPCServer)
<-ctx.Done()
}()
group.Wait()
}

func main() {
var rootCmd = &cobra.Command{
Use: "kyverno-envoy-plugin",
Expand Down
40 changes: 40 additions & 0 deletions pkg/engine/assert/project_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package assert

import (
"context"
"testing"

"github.com/jmespath-community/go-jmespath/pkg/binding"
tassert "github.com/stretchr/testify/assert"
)

func Test_project(t *testing.T) {
tests := []struct {
name string
key any
value any
bindings binding.Bindings
want *projection
wantErr bool
}{{
name: "map index not found",
key: "foo",
value: map[string]any{
"bar": 42,
},
bindings: nil,
want: nil,
wantErr: false,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := project(context.TODO(), tt.key, tt.value, tt.bindings)
if tt.wantErr {
tassert.Error(t, err)
tassert.Equal(t, tt.want, got)
} else {
tassert.NoError(t, err)
}
})
}
}
87 changes: 87 additions & 0 deletions pkg/request/request_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package request

import (
"reflect"
"testing"

authv3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
)

func TestConvert(t *testing.T) {
type args struct {
attrs *authv3.AttributeContext
}
tests := []struct {
name string
args args
want map[string]interface{}
wantErr bool
}{
{
name: "empty AttributeContext",
args: args{
attrs: &authv3.AttributeContext{
Source: nil,
Destination: nil,
Request: nil,
ContextExtensions: nil,
},
},
want: map[string]interface{}{},
wantErr: false,
},
{
name: "non-empty AttributeContext",
args: args{
attrs: &authv3.AttributeContext{
Source: &authv3.AttributeContext_Peer{
Principal: "test-principal",
},
Destination: &authv3.AttributeContext_Peer{
Principal: "test-destination",
},
Request: &authv3.AttributeContext_Request{
Time: nil,
Http: &authv3.AttributeContext_HttpRequest{
Method: "GET",
Path: "/test",
},
},
ContextExtensions: map[string]string{
"test-key": "test-value",
},
},
},
want: map[string]interface{}{
"source": map[string]interface{}{
"principal": "test-principal",
},
"destination": map[string]interface{}{
"principal": "test-destination",
},
"request": map[string]interface{}{
"http": map[string]interface{}{
"method": "GET",
"path": "/test",
},
},
"contextExtensions": map[string]interface{}{
"test-key": "test-value",
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Convert(tt.args.attrs)
if (err != nil) != tt.wantErr {
t.Errorf("Convert() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Convert() = %v, want %v", got, tt.want)
}
})
}
}
Loading

0 comments on commit 9fe6978

Please sign in to comment.