summaryrefslogtreecommitdiff
path: root/pkg/authz/check_service.go
diff options
context:
space:
mode:
authormo khan <mo@mokhan.ca>2025-07-15 16:37:08 -0600
committermo khan <mo@mokhan.ca>2025-07-17 16:30:22 -0600
commit45df4d0d9b577fecee798d672695fe24ff57fb1b (patch)
tree1b99bf645035b58e0d6db08c7a83521f41f7a75b /pkg/authz/check_service.go
parentf94f79608393d4ab127db63cc41668445ef6b243 (diff)
feat: migrate from Cedar to SpiceDB authorization system
This is a major architectural change that replaces the Cedar policy-based authorization system with SpiceDB's relation-based authorization. Key changes: - Migrate from Rust to Go implementation - Replace Cedar policies with SpiceDB schema and relationships - Switch from envoy `ext_authz` with Cedar to SpiceDB permission checks - Update build system and dependencies for Go ecosystem - Maintain Envoy integration for external authorization This change enables more flexible permission modeling through SpiceDB's Google Zanzibar inspired relation-based system, supporting complex hierarchical permissions that were difficult to express in Cedar. Breaking change: Existing Cedar policies and Rust-based configuration will no longer work and need to be migrated to SpiceDB schema.
Diffstat (limited to 'pkg/authz/check_service.go')
-rw-r--r--pkg/authz/check_service.go158
1 files changed, 158 insertions, 0 deletions
diff --git a/pkg/authz/check_service.go b/pkg/authz/check_service.go
new file mode 100644
index 00000000..4df0ebe7
--- /dev/null
+++ b/pkg/authz/check_service.go
@@ -0,0 +1,158 @@
+package authz
+
+import (
+ "context"
+ "net/http"
+ "path/filepath"
+
+ v1 "github.com/authzed/authzed-go/proto/authzed/api/v1"
+ authzed "github.com/authzed/authzed-go/v1"
+ core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
+ auth "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
+ types "github.com/envoyproxy/go-control-plane/envoy/type/v3"
+ "github.com/xlgmokha/x/pkg/log"
+ "github.com/xlgmokha/x/pkg/x"
+ "gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd.git/pkg/pls"
+ status "google.golang.org/genproto/googleapis/rpc/status"
+ "google.golang.org/grpc/codes"
+)
+
+var StaticAssets []string = []string{".png", ".css", ".html", ".js", ".ico"}
+
+type CheckService struct {
+ client *authzed.Client
+ auth.UnimplementedAuthorizationServer
+}
+
+func NewCheckService(client *authzed.Client) auth.AuthorizationServer {
+ return &CheckService{
+ client: client,
+ }
+}
+
+func (svc *CheckService) Check(ctx context.Context, request *auth.CheckRequest) (*auth.CheckResponse, error) {
+ if svc.isAuthorized(ctx, request) {
+ return svc.OK(ctx), nil
+ }
+ return svc.Denied(ctx), nil
+}
+
+func (svc *CheckService) isAuthorized(ctx context.Context, r *auth.CheckRequest) bool {
+ if !svc.validRequest(ctx, r) {
+ return false
+ }
+ log.WithFields(ctx, svc.fieldsFor(r))
+
+ if svc.isStaticAsset(ctx, r) {
+ return true
+ }
+
+ if x.IsZero(svc.client) {
+ return false
+ }
+
+ response, err := svc.client.CheckPermission(ctx, svc.mapFrom(ctx, r))
+ if err != nil {
+ pls.LogError(ctx, err)
+ return false
+ }
+ return response.Permissionship == v1.CheckPermissionResponse_PERMISSIONSHIP_HAS_PERMISSION
+}
+
+func (svc *CheckService) isStaticAsset(ctx context.Context, r *auth.CheckRequest) bool {
+ if r.Attributes.Request.Http.Method != http.MethodGet {
+ return false
+ }
+
+ extension := filepath.Ext(r.Attributes.Request.Http.Path)
+ return x.Contains(StaticAssets, func(item string) bool {
+ return item == extension
+ })
+}
+
+func (svc *CheckService) validRequest(ctx context.Context, r *auth.CheckRequest) bool {
+ return x.IsPresent(r) &&
+ x.IsPresent(r.Attributes) &&
+ x.IsPresent(r.Attributes.Request) &&
+ x.IsPresent(r.Attributes.Request.Http)
+}
+
+func (svc *CheckService) OK(ctx context.Context) *auth.CheckResponse {
+ log.WithFields(ctx, log.Fields{"authorized": true})
+ return &auth.CheckResponse{
+ Status: &status.Status{
+ Code: int32(codes.OK),
+ },
+ HttpResponse: &auth.CheckResponse_OkResponse{
+ OkResponse: &auth.OkHttpResponse{
+ Headers: []*core.HeaderValueOption{},
+ HeadersToRemove: []string{},
+ ResponseHeadersToAdd: []*core.HeaderValueOption{},
+ },
+ },
+ }
+}
+
+func (svc *CheckService) Denied(ctx context.Context) *auth.CheckResponse {
+ log.WithFields(ctx, log.Fields{"authorized": false})
+ return &auth.CheckResponse{
+ Status: &status.Status{
+ Code: int32(codes.PermissionDenied),
+ },
+ HttpResponse: &auth.CheckResponse_DeniedResponse{
+ DeniedResponse: &auth.DeniedHttpResponse{
+ Status: &types.HttpStatus{
+ Code: types.StatusCode_Unauthorized,
+ },
+ Headers: []*core.HeaderValueOption{},
+ },
+ },
+ }
+}
+
+func (svc *CheckService) fieldsFor(r *auth.CheckRequest) log.Fields {
+ return log.Fields{
+ "host": r.Attributes.Request.Http.Host,
+ "id": r.Attributes.Request.Http.Id,
+ "method": r.Attributes.Request.Http.Method,
+ "path": r.Attributes.Request.Http.Path,
+ "protocol": r.Attributes.Request.Http.Protocol,
+ "request_id": r.Attributes.Request.Http.Headers["x-request-id"],
+ "scheme": r.Attributes.Request.Http.Scheme,
+ "subject": r.Attributes.Request.Http.Headers["x-jwt-claim-username"],
+ }
+}
+
+func (svc *CheckService) mapFrom(ctx context.Context, r *auth.CheckRequest) *v1.CheckPermissionRequest {
+ return &v1.CheckPermissionRequest{
+ Resource: svc.resourceFrom(ctx, r),
+ Permission: svc.permissionFrom(ctx, r),
+ Subject: svc.subjectFrom(ctx, r),
+ }
+}
+
+func (svc *CheckService) resourceFrom(ctx context.Context, r *auth.CheckRequest) *v1.ObjectReference {
+ return &v1.ObjectReference{
+ ObjectType: "project",
+ ObjectId: "1",
+ }
+}
+
+func (svc *CheckService) subjectFrom(ctx context.Context, r *auth.CheckRequest) *v1.SubjectReference {
+ //TODO:: username is not ideal but it works for demo purposes
+ username := r.Attributes.Request.Http.Headers["x-jwt-claim-username"]
+ if x.IsZero(username) {
+ username = "public"
+ }
+
+ return &v1.SubjectReference{
+ Object: &v1.ObjectReference{
+ ObjectType: "user",
+ ObjectId: username,
+ },
+ }
+}
+
+func (svc *CheckService) permissionFrom(ctx context.Context, r *auth.CheckRequest) string {
+ return "read"
+}