Phase 10: gRPC admin API with interceptor chain

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>
This commit is contained in:
2026-03-19 20:46:21 -07:00
parent 562b69e875
commit 185b68ff6d
30 changed files with 3616 additions and 4 deletions

15
proto/mcr/v1/admin.proto Normal file
View File

@@ -0,0 +1,15 @@
syntax = "proto3";
package mcr.v1;
option go_package = "git.wntrmute.dev/kyle/mcr/gen/mcr/v1;mcrv1";
service AdminService {
rpc Health(HealthRequest) returns (HealthResponse);
}
message HealthRequest {}
message HealthResponse {
string status = 1;
}

35
proto/mcr/v1/audit.proto Normal file
View File

@@ -0,0 +1,35 @@
syntax = "proto3";
package mcr.v1;
option go_package = "git.wntrmute.dev/kyle/mcr/gen/mcr/v1;mcrv1";
import "mcr/v1/common.proto";
service AuditService {
rpc ListAuditEvents(ListAuditEventsRequest) returns (ListAuditEventsResponse);
}
message AuditEvent {
int64 id = 1;
string event_time = 2;
string event_type = 3;
string actor_id = 4;
string repository = 5;
string digest = 6;
string ip_address = 7;
map<string, string> details = 8;
}
message ListAuditEventsRequest {
PaginationRequest pagination = 1;
string event_type = 2;
string actor_id = 3;
string repository = 4;
string since = 5;
string until = 6;
}
message ListAuditEventsResponse {
repeated AuditEvent events = 1;
}

11
proto/mcr/v1/common.proto Normal file
View File

@@ -0,0 +1,11 @@
syntax = "proto3";
package mcr.v1;
option go_package = "git.wntrmute.dev/kyle/mcr/gen/mcr/v1;mcrv1";
// Pagination controls for list RPCs.
message PaginationRequest {
int32 limit = 1;
int32 offset = 2;
}

76
proto/mcr/v1/policy.proto Normal file
View File

@@ -0,0 +1,76 @@
syntax = "proto3";
package mcr.v1;
option go_package = "git.wntrmute.dev/kyle/mcr/gen/mcr/v1;mcrv1";
import "mcr/v1/common.proto";
service PolicyService {
rpc ListPolicyRules(ListPolicyRulesRequest) returns (ListPolicyRulesResponse);
rpc CreatePolicyRule(CreatePolicyRuleRequest) returns (PolicyRule);
rpc GetPolicyRule(GetPolicyRuleRequest) returns (PolicyRule);
rpc UpdatePolicyRule(UpdatePolicyRuleRequest) returns (PolicyRule);
rpc DeletePolicyRule(DeletePolicyRuleRequest) returns (DeletePolicyRuleResponse);
}
message PolicyRule {
int64 id = 1;
int32 priority = 2;
string description = 3;
string effect = 4;
repeated string roles = 5;
repeated string account_types = 6;
string subject_uuid = 7;
repeated string actions = 8;
repeated string repositories = 9;
bool enabled = 10;
string created_by = 11;
string created_at = 12;
string updated_at = 13;
}
message ListPolicyRulesRequest {
PaginationRequest pagination = 1;
}
message ListPolicyRulesResponse {
repeated PolicyRule rules = 1;
}
message CreatePolicyRuleRequest {
int32 priority = 1;
string description = 2;
string effect = 3;
repeated string roles = 4;
repeated string account_types = 5;
string subject_uuid = 6;
repeated string actions = 7;
repeated string repositories = 8;
bool enabled = 9;
}
message GetPolicyRuleRequest {
int64 id = 1;
}
message UpdatePolicyRuleRequest {
int64 id = 1;
int32 priority = 2;
string description = 3;
string effect = 4;
repeated string roles = 5;
repeated string account_types = 6;
string subject_uuid = 7;
repeated string actions = 8;
repeated string repositories = 9;
bool enabled = 10;
// Field mask for partial updates — only fields listed here are applied.
repeated string update_mask = 11;
}
message DeletePolicyRuleRequest {
int64 id = 1;
}
message DeletePolicyRuleResponse {}

View File

@@ -0,0 +1,81 @@
syntax = "proto3";
package mcr.v1;
option go_package = "git.wntrmute.dev/kyle/mcr/gen/mcr/v1;mcrv1";
import "mcr/v1/common.proto";
service RegistryService {
rpc ListRepositories(ListRepositoriesRequest) returns (ListRepositoriesResponse);
rpc GetRepository(GetRepositoryRequest) returns (GetRepositoryResponse);
rpc DeleteRepository(DeleteRepositoryRequest) returns (DeleteRepositoryResponse);
rpc GarbageCollect(GarbageCollectRequest) returns (GarbageCollectResponse);
rpc GetGCStatus(GetGCStatusRequest) returns (GetGCStatusResponse);
}
message RepositoryMetadata {
string name = 1;
int32 tag_count = 2;
int32 manifest_count = 3;
int64 total_size = 4;
string created_at = 5;
}
message TagInfo {
string name = 1;
string digest = 2;
}
message ManifestInfo {
string digest = 1;
string media_type = 2;
int64 size = 3;
string created_at = 4;
}
message ListRepositoriesRequest {
PaginationRequest pagination = 1;
}
message ListRepositoriesResponse {
repeated RepositoryMetadata repositories = 1;
}
message GetRepositoryRequest {
string name = 1;
}
message GetRepositoryResponse {
string name = 1;
repeated TagInfo tags = 2;
repeated ManifestInfo manifests = 3;
int64 total_size = 4;
string created_at = 5;
}
message DeleteRepositoryRequest {
string name = 1;
}
message DeleteRepositoryResponse {}
message GarbageCollectRequest {}
message GarbageCollectResponse {
string id = 1;
}
message GetGCStatusRequest {}
message GCLastRun {
string started_at = 1;
string completed_at = 2;
int32 blobs_removed = 3;
int64 bytes_freed = 4;
}
message GetGCStatusResponse {
bool running = 1;
GCLastRun last_run = 2;
}