Commit 30bbdad

mo khan <mo@mokhan.ca>
2025-03-28 23:49:09
refactor: move authorizers into authz package
1 parent e47813e
cmd/authzd/main.go
@@ -1,27 +1,17 @@
 package main
 
 import (
-	"context"
 	"log"
 	"net"
 
 	"github.com/xlgmokha/x/pkg/env"
 	"github.com/xlgmokha/x/pkg/x"
+	"gitlab.com/mokhax/spike/pkg/authz"
 	"gitlab.com/mokhax/spike/pkg/rpc"
-	"google.golang.org/grpc"
 )
 
-type CedarServer struct {
-	rpc.UnimplementedAbilityServer
-}
-
-func (srv *CedarServer) Allowed(context.Context, *rpc.AllowRequest) (*rpc.AllowReply, error) {
-	return &rpc.AllowReply{Result: true}, nil
-}
-
 func main() {
-	server := grpc.NewServer()
-	rpc.RegisterAbilityServer(server, &CedarServer{})
+	server := rpc.New(authz.WithCedar())
 	log.Fatal(server.Serve(x.Must(net.Listen(
 		"tcp",
 		env.Fetch("BIND_ADDR", "localhost:50051"),
pkg/app/app.go
@@ -1,95 +1,13 @@
 package app
 
 import (
-	"encoding/json"
-	"fmt"
-	"net"
-	"net/http"
-	"os"
-
-	"github.com/casbin/casbin/v3"
-	cedar "github.com/cedar-policy/cedar-go"
-	"github.com/cedar-policy/cedar-go/types"
-	"github.com/xlgmokha/x/pkg/x"
 	"gitlab.com/mokhax/spike/pkg/authz"
 	"gitlab.com/mokhax/spike/pkg/cfg"
-	xlog "gitlab.com/mokhax/spike/pkg/log"
 	"gitlab.com/mokhax/spike/pkg/srv"
 )
 
-func WithCasbin() authz.Authorizer {
-	enforcer := x.Must(casbin.NewEnforcer("casbin.conf", "casbin.csv"))
-
-	return authz.AuthorizerFunc(func(r *http.Request) bool {
-		host, _, err := net.SplitHostPort(r.Host)
-		if err != nil {
-			xlog.WithFields(r, xlog.Fields{"error": err})
-			return false
-		}
-
-		subject, found := authz.TokenFrom(r).Subject()
-		if !found {
-			subject = "*"
-		}
-		ok, err := enforcer.Enforce(subject, host, r.Method, r.URL.Path)
-		if err != nil {
-			xlog.WithFields(r, xlog.Fields{"error": err})
-			return false
-		}
-
-		fmt.Printf("%v: %v -> %v %v%v\n", ok, subject, r.Method, host, r.URL.Path)
-		xlog.WithFields(r, xlog.Fields{
-			"ok":      ok,
-			"subject": subject,
-			"action":  r.Method,
-			"domain":  host,
-			"object":  r.URL.Path,
-		})
-		return ok
-	})
-}
-
-func WithCedar() authz.Authorizer {
-	var policy cedar.Policy
-	x.Check(policy.UnmarshalCedar(x.Must(os.ReadFile("cedar.conf"))))
-
-	policies := cedar.NewPolicySet()
-	policies.Add("cedar.conf", &policy)
-
-	var entities cedar.EntityMap
-	if err := json.Unmarshal(x.Must(os.ReadFile("cedar.json")), &entities); err != nil {
-		xlog.Logger.Error("Error", "error", err)
-		return nil
-	}
-
-	return authz.AuthorizerFunc(func(r *http.Request) bool {
-		host, _, err := net.SplitHostPort(r.Host)
-		if err != nil {
-			return false
-		}
-
-		subject, found := authz.TokenFrom(r).Subject()
-		if !found {
-			subject = "*"
-		}
-
-		req := cedar.Request{
-			Principal: cedar.NewEntityUID("Subject", cedar.String(subject)),
-			Action:    cedar.NewEntityUID("Action", cedar.String(r.Method)),
-			Resource:  cedar.NewEntityUID("Path", cedar.String(r.URL.Path)),
-			Context: cedar.NewRecord(cedar.RecordMap{
-				"Host": cedar.String(host),
-			}),
-		}
-
-		ok, diagnostic := policies.IsAuthorized(entities, req)
-		fmt.Printf("%v: %v -> %v %v%v %v\n", ok, subject, r.Method, host, r.URL.Path, diagnostic.Reasons)
-		return ok == types.Allow
-	})
-}
-
 func Start(bindAddr string) error {
-	mux := authz.HTTP(WithCasbin(), Routes())
+	mux := authz.HTTP(authz.WithCasbin(), Routes())
 	return srv.Run(cfg.New(
 		bindAddr,
 		cfg.WithMux(mux),
pkg/app/routes.go
@@ -9,9 +9,10 @@ import (
 func Routes() http.Handler {
 	mux := http.NewServeMux()
 	mux.Handle("/", prxy.New(map[string]string{
-		"idp.example.com": "http://localhost:8282",
-		"ui.example.com":  "http://localhost:8283",
-		"api.example.com": "http://localhost:8284",
+		"api.example.com":    "http://localhost:8284",
+		"authzd.example.com": "http://localhost:50051",
+		"idp.example.com":    "http://localhost:8282",
+		"ui.example.com":     "http://localhost:8283",
 	}))
 	return mux
 }
pkg/authz/casbin.go
@@ -0,0 +1,43 @@
+package authz
+
+import (
+	"fmt"
+	"net"
+	"net/http"
+
+	"github.com/casbin/casbin/v3"
+	"github.com/xlgmokha/x/pkg/x"
+	xlog "gitlab.com/mokhax/spike/pkg/log"
+)
+
+func WithCasbin() Authorizer {
+	enforcer := x.Must(casbin.NewEnforcer("casbin.conf", "casbin.csv"))
+
+	return AuthorizerFunc(func(r *http.Request) bool {
+		host, _, err := net.SplitHostPort(r.Host)
+		if err != nil {
+			xlog.WithFields(r, xlog.Fields{"error": err})
+			return false
+		}
+
+		subject, found := TokenFrom(r).Subject()
+		if !found {
+			subject = "*"
+		}
+		ok, err := enforcer.Enforce(subject, host, r.Method, r.URL.Path)
+		if err != nil {
+			xlog.WithFields(r, xlog.Fields{"error": err})
+			return false
+		}
+
+		fmt.Printf("%v: %v -> %v %v%v\n", ok, subject, r.Method, host, r.URL.Path)
+		xlog.WithFields(r, xlog.Fields{
+			"ok":      ok,
+			"subject": subject,
+			"action":  r.Method,
+			"domain":  host,
+			"object":  r.URL.Path,
+		})
+		return ok
+	})
+}
pkg/authz/cedar.go
@@ -0,0 +1,53 @@
+package authz
+
+import (
+	"encoding/json"
+	"fmt"
+	"net"
+	"net/http"
+	"os"
+
+	cedar "github.com/cedar-policy/cedar-go"
+	"github.com/cedar-policy/cedar-go/types"
+	"github.com/xlgmokha/x/pkg/x"
+	xlog "gitlab.com/mokhax/spike/pkg/log"
+)
+
+func WithCedar() Authorizer {
+	var policy cedar.Policy
+	x.Check(policy.UnmarshalCedar(x.Must(os.ReadFile("cedar.conf"))))
+
+	policies := cedar.NewPolicySet()
+	policies.Add("cedar.conf", &policy)
+
+	var entities cedar.EntityMap
+	if err := json.Unmarshal(x.Must(os.ReadFile("cedar.json")), &entities); err != nil {
+		xlog.Logger.Error("Error", "error", err)
+		return nil
+	}
+
+	return AuthorizerFunc(func(r *http.Request) bool {
+		host, _, err := net.SplitHostPort(r.Host)
+		if err != nil {
+			return false
+		}
+
+		subject, found := TokenFrom(r).Subject()
+		if !found {
+			subject = "*"
+		}
+
+		req := cedar.Request{
+			Principal: cedar.NewEntityUID("Subject", cedar.String(subject)),
+			Action:    cedar.NewEntityUID("Action", cedar.String(r.Method)),
+			Resource:  cedar.NewEntityUID("Path", cedar.String(r.URL.Path)),
+			Context: cedar.NewRecord(cedar.RecordMap{
+				"Host": cedar.String(host),
+			}),
+		}
+
+		ok, diagnostic := policies.IsAuthorized(entities, req)
+		fmt.Printf("%v: %v -> %v %v%v %v\n", ok, subject, r.Method, host, r.URL.Path, diagnostic.Reasons)
+		return ok == types.Allow
+	})
+}
pkg/rpc/ability_handler.go
@@ -0,0 +1,20 @@
+package rpc
+
+import (
+	context "context"
+	"net/http"
+
+	"github.com/xlgmokha/x/pkg/x"
+	"gitlab.com/mokhax/spike/pkg/authz"
+)
+
+type AbilityHandler struct {
+	authorizer authz.Authorizer
+	UnimplementedAbilityServer
+}
+
+func (h *AbilityHandler) Allowed(ctx context.Context, req *AllowRequest) (*AllowReply, error) {
+	// TODO:: Replace http.Request with authz.Request
+	request := x.Must(http.NewRequestWithContext(ctx, req.Permission, req.Resource, nil))
+	return &AllowReply{Result: h.authorizer.Authorize(request)}, nil
+}
pkg/rpc/server.go
@@ -0,0 +1,14 @@
+package rpc
+
+import (
+	"gitlab.com/mokhax/spike/pkg/authz"
+	grpc "google.golang.org/grpc"
+)
+
+func New(authorizer authz.Authorizer, options ...grpc.ServerOption) *grpc.Server {
+	server := grpc.NewServer(options...)
+	RegisterAbilityServer(server, &AbilityHandler{
+		authorizer: authorizer,
+	})
+	return server
+}