Proto definitions for 4 services (RegistryService, PolicyService, AuditService, AdminService) with hand-written Go stubs using JSON codec until protobuf tooling is available. Interceptor chain: logging (method, peer IP, duration, never logs auth metadata) → auth (bearer token via MCIAS, Health bypasses) → admin (role check for GC, policy, delete, audit RPCs). All RPCs share business logic with REST handlers via internal/db and internal/gc packages. TLS 1.3 minimum on gRPC listener. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
307 lines
7.5 KiB
Go
307 lines
7.5 KiB
Go
package grpcserver
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
|
|
pb "git.wntrmute.dev/kyle/mcr/gen/mcr/v1"
|
|
"git.wntrmute.dev/kyle/mcr/internal/policy"
|
|
)
|
|
|
|
type fakePolicyReloader struct {
|
|
reloadCount int
|
|
}
|
|
|
|
func (f *fakePolicyReloader) Reload(_ policy.RuleStore) error {
|
|
f.reloadCount++
|
|
return nil
|
|
}
|
|
|
|
func TestCreatePolicyRule(t *testing.T) {
|
|
deps := adminDeps(t)
|
|
reloader := &fakePolicyReloader{}
|
|
deps.Engine = reloader
|
|
cc := startTestServer(t, deps)
|
|
client := pb.NewPolicyServiceClient(cc)
|
|
|
|
resp, err := client.CreatePolicyRule(adminCtx(), &pb.CreatePolicyRuleRequest{
|
|
Priority: 10,
|
|
Description: "allow pull for all",
|
|
Effect: "allow",
|
|
Actions: []string{"registry:pull"},
|
|
Enabled: true,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("CreatePolicyRule: %v", err)
|
|
}
|
|
if resp.GetId() == 0 {
|
|
t.Fatal("expected non-zero ID")
|
|
}
|
|
if resp.GetDescription() != "allow pull for all" {
|
|
t.Fatalf("description: got %q, want %q", resp.Description, "allow pull for all")
|
|
}
|
|
if resp.GetEffect() != "allow" {
|
|
t.Fatalf("effect: got %q, want %q", resp.Effect, "allow")
|
|
}
|
|
if !resp.GetEnabled() {
|
|
t.Fatal("expected enabled=true")
|
|
}
|
|
if reloader.reloadCount != 1 {
|
|
t.Fatalf("reloadCount: got %d, want 1", reloader.reloadCount)
|
|
}
|
|
}
|
|
|
|
func TestCreatePolicyRuleValidation(t *testing.T) {
|
|
deps := adminDeps(t)
|
|
cc := startTestServer(t, deps)
|
|
client := pb.NewPolicyServiceClient(cc)
|
|
|
|
tests := []struct {
|
|
name string
|
|
req *pb.CreatePolicyRuleRequest
|
|
code codes.Code
|
|
}{
|
|
{
|
|
name: "zero priority",
|
|
req: &pb.CreatePolicyRuleRequest{
|
|
Priority: 0,
|
|
Description: "test",
|
|
Effect: "allow",
|
|
Actions: []string{"registry:pull"},
|
|
},
|
|
code: codes.InvalidArgument,
|
|
},
|
|
{
|
|
name: "empty description",
|
|
req: &pb.CreatePolicyRuleRequest{
|
|
Priority: 1,
|
|
Effect: "allow",
|
|
Actions: []string{"registry:pull"},
|
|
},
|
|
code: codes.InvalidArgument,
|
|
},
|
|
{
|
|
name: "invalid effect",
|
|
req: &pb.CreatePolicyRuleRequest{
|
|
Priority: 1,
|
|
Description: "test",
|
|
Effect: "maybe",
|
|
Actions: []string{"registry:pull"},
|
|
},
|
|
code: codes.InvalidArgument,
|
|
},
|
|
{
|
|
name: "no actions",
|
|
req: &pb.CreatePolicyRuleRequest{
|
|
Priority: 1,
|
|
Description: "test",
|
|
Effect: "allow",
|
|
},
|
|
code: codes.InvalidArgument,
|
|
},
|
|
{
|
|
name: "invalid action",
|
|
req: &pb.CreatePolicyRuleRequest{
|
|
Priority: 1,
|
|
Description: "test",
|
|
Effect: "allow",
|
|
Actions: []string{"registry:fly"},
|
|
},
|
|
code: codes.InvalidArgument,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
_, err := client.CreatePolicyRule(adminCtx(), tt.req)
|
|
if err == nil {
|
|
t.Fatal("expected error")
|
|
}
|
|
st, ok := status.FromError(err)
|
|
if !ok {
|
|
t.Fatalf("expected gRPC status, got %v", err)
|
|
}
|
|
if st.Code() != tt.code {
|
|
t.Fatalf("code: got %v, want %v", st.Code(), tt.code)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetPolicyRule(t *testing.T) {
|
|
deps := adminDeps(t)
|
|
cc := startTestServer(t, deps)
|
|
client := pb.NewPolicyServiceClient(cc)
|
|
|
|
// Create a rule first.
|
|
created, err := client.CreatePolicyRule(adminCtx(), &pb.CreatePolicyRuleRequest{
|
|
Priority: 5,
|
|
Description: "test rule",
|
|
Effect: "deny",
|
|
Actions: []string{"registry:push"},
|
|
Enabled: true,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("CreatePolicyRule: %v", err)
|
|
}
|
|
|
|
// Fetch it.
|
|
got, err := client.GetPolicyRule(adminCtx(), &pb.GetPolicyRuleRequest{Id: created.Id})
|
|
if err != nil {
|
|
t.Fatalf("GetPolicyRule: %v", err)
|
|
}
|
|
if got.Id != created.Id {
|
|
t.Fatalf("id: got %d, want %d", got.Id, created.Id)
|
|
}
|
|
if got.Effect != "deny" {
|
|
t.Fatalf("effect: got %q, want %q", got.Effect, "deny")
|
|
}
|
|
}
|
|
|
|
func TestGetPolicyRuleNotFound(t *testing.T) {
|
|
deps := adminDeps(t)
|
|
cc := startTestServer(t, deps)
|
|
client := pb.NewPolicyServiceClient(cc)
|
|
|
|
_, err := client.GetPolicyRule(adminCtx(), &pb.GetPolicyRuleRequest{Id: 99999})
|
|
if err == nil {
|
|
t.Fatal("expected error")
|
|
}
|
|
st, ok := status.FromError(err)
|
|
if !ok {
|
|
t.Fatalf("expected gRPC status, got %v", err)
|
|
}
|
|
if st.Code() != codes.NotFound {
|
|
t.Fatalf("code: got %v, want NotFound", st.Code())
|
|
}
|
|
}
|
|
|
|
func TestListPolicyRules(t *testing.T) {
|
|
deps := adminDeps(t)
|
|
cc := startTestServer(t, deps)
|
|
client := pb.NewPolicyServiceClient(cc)
|
|
|
|
// Create two rules.
|
|
for i := range 2 {
|
|
_, err := client.CreatePolicyRule(adminCtx(), &pb.CreatePolicyRuleRequest{
|
|
Priority: int32(i + 1),
|
|
Description: "rule",
|
|
Effect: "allow",
|
|
Actions: []string{"registry:pull"},
|
|
Enabled: true,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("CreatePolicyRule %d: %v", i, err)
|
|
}
|
|
}
|
|
|
|
resp, err := client.ListPolicyRules(adminCtx(), &pb.ListPolicyRulesRequest{})
|
|
if err != nil {
|
|
t.Fatalf("ListPolicyRules: %v", err)
|
|
}
|
|
if len(resp.GetRules()) < 2 {
|
|
t.Fatalf("expected at least 2 rules, got %d", len(resp.Rules))
|
|
}
|
|
}
|
|
|
|
func TestDeletePolicyRule(t *testing.T) {
|
|
deps := adminDeps(t)
|
|
reloader := &fakePolicyReloader{}
|
|
deps.Engine = reloader
|
|
cc := startTestServer(t, deps)
|
|
client := pb.NewPolicyServiceClient(cc)
|
|
|
|
// Create then delete.
|
|
created, err := client.CreatePolicyRule(adminCtx(), &pb.CreatePolicyRuleRequest{
|
|
Priority: 1,
|
|
Description: "to be deleted",
|
|
Effect: "allow",
|
|
Actions: []string{"registry:pull"},
|
|
Enabled: true,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("CreatePolicyRule: %v", err)
|
|
}
|
|
initialReloads := reloader.reloadCount
|
|
|
|
_, err = client.DeletePolicyRule(adminCtx(), &pb.DeletePolicyRuleRequest{Id: created.Id})
|
|
if err != nil {
|
|
t.Fatalf("DeletePolicyRule: %v", err)
|
|
}
|
|
|
|
// Verify it was reloaded.
|
|
if reloader.reloadCount != initialReloads+1 {
|
|
t.Fatalf("reloadCount: got %d, want %d", reloader.reloadCount, initialReloads+1)
|
|
}
|
|
|
|
// Verify it's gone.
|
|
_, err = client.GetPolicyRule(adminCtx(), &pb.GetPolicyRuleRequest{Id: created.Id})
|
|
if err == nil {
|
|
t.Fatal("expected error after deletion")
|
|
}
|
|
st, ok := status.FromError(err)
|
|
if !ok {
|
|
t.Fatalf("expected gRPC status, got %v", err)
|
|
}
|
|
if st.Code() != codes.NotFound {
|
|
t.Fatalf("code: got %v, want NotFound", st.Code())
|
|
}
|
|
}
|
|
|
|
func TestDeletePolicyRuleNotFound(t *testing.T) {
|
|
deps := adminDeps(t)
|
|
cc := startTestServer(t, deps)
|
|
client := pb.NewPolicyServiceClient(cc)
|
|
|
|
_, err := client.DeletePolicyRule(adminCtx(), &pb.DeletePolicyRuleRequest{Id: 99999})
|
|
if err == nil {
|
|
t.Fatal("expected error")
|
|
}
|
|
st, ok := status.FromError(err)
|
|
if !ok {
|
|
t.Fatalf("expected gRPC status, got %v", err)
|
|
}
|
|
if st.Code() != codes.NotFound {
|
|
t.Fatalf("code: got %v, want NotFound", st.Code())
|
|
}
|
|
}
|
|
|
|
func TestUpdatePolicyRule(t *testing.T) {
|
|
deps := adminDeps(t)
|
|
reloader := &fakePolicyReloader{}
|
|
deps.Engine = reloader
|
|
cc := startTestServer(t, deps)
|
|
client := pb.NewPolicyServiceClient(cc)
|
|
|
|
// Create a rule.
|
|
created, err := client.CreatePolicyRule(adminCtx(), &pb.CreatePolicyRuleRequest{
|
|
Priority: 10,
|
|
Description: "original",
|
|
Effect: "allow",
|
|
Actions: []string{"registry:pull"},
|
|
Enabled: true,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("CreatePolicyRule: %v", err)
|
|
}
|
|
|
|
// Update description.
|
|
updated, err := client.UpdatePolicyRule(adminCtx(), &pb.UpdatePolicyRuleRequest{
|
|
Id: created.Id,
|
|
Description: "updated description",
|
|
UpdateMask: []string{"description"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("UpdatePolicyRule: %v", err)
|
|
}
|
|
if updated.Description != "updated description" {
|
|
t.Fatalf("description: got %q, want %q", updated.Description, "updated description")
|
|
}
|
|
// Effect should be unchanged.
|
|
if updated.Effect != "allow" {
|
|
t.Fatalf("effect: got %q, want %q", updated.Effect, "allow")
|
|
}
|
|
}
|