diff options
| author | mo khan <mo@mokhan.ca> | 2025-03-05 13:02:36 -0700 |
|---|---|---|
| committer | mo khan <mo@mokhan.ca> | 2025-03-05 13:02:36 -0700 |
| commit | e1fe97ff76ac966039347465f79dc96e705f7f25 (patch) | |
| tree | c0168a0884ce57d8827c4add7667219f04d69faa | |
| parent | 06a4e0783c1886ca46468c4caeb42a41d56fd956 (diff) | |
feat: connect the reverse proxy to a casbin policy enforcement and separate hostnames
| -rw-r--r-- | cmd/gtwy/main.go | 41 | ||||
| -rw-r--r-- | go.mod | 11 | ||||
| -rw-r--r-- | go.sum | 24 | ||||
| -rw-r--r-- | magefile.go | 44 | ||||
| -rw-r--r-- | model.conf | 17 | ||||
| -rw-r--r-- | policy.csv | 8 |
6 files changed, 121 insertions, 24 deletions
diff --git a/cmd/gtwy/main.go b/cmd/gtwy/main.go index 9b21c149..3d4a2472 100644 --- a/cmd/gtwy/main.go +++ b/cmd/gtwy/main.go @@ -7,23 +7,37 @@ import ( "net/http/httputil" "strings" "time" + + "github.com/casbin/casbin/v2" + "github.com/xlgmokha/x/pkg/env" + "github.com/xlgmokha/x/pkg/x" ) -func NewProxy(from, to string) http.Handler { +func NewRouter(routes map[string]string) http.Handler { + authz := x.Must(casbin.NewEnforcer("model.conf", "policy.csv")) + return &httputil.ReverseProxy{ Director: func(r *http.Request) { - log.Printf("%v (from: %v to: %v)\n", r.URL, from, to) - r.URL.Scheme = "http" - r.Host = to - r.URL.Host = to - r.URL.Path = strings.TrimPrefix(r.URL.Path, strings.TrimSuffix(from, "/*")) - r.URL.RawPath = strings.TrimPrefix(r.URL.RawPath, strings.TrimSuffix(from, "/*")) + segments := strings.SplitN(r.Host, ":", 2) + host := segments[0] + destinationHost := routes[host] + + log.Printf("%v (from: %v to: %v)\n", r.URL, host, destinationHost) + + subject := "71cbc18e-bd41-4229-9ad2-749546a2a4a7" // TODO:: unpack sub claim in JWT + if x.Must(authz.Enforce(subject, host, r.Method, r.URL.Path)) { + r.URL.Scheme = "http" // TODO:: use TLS + r.Host = destinationHost + r.URL.Host = destinationHost + } else { + log.Println("UNAUTHORIZED") // TODO:: Return forbidden, unauthorized or not found status code + } }, Transport: http.DefaultTransport, FlushInterval: -1, ErrorLog: nil, ModifyResponse: func(r *http.Response) error { - r.Header.Add("Via", fmt.Sprintf("%v gateway", r.Proto)) + r.Header.Add("Via", fmt.Sprintf("%v gtwy", r.Proto)) return nil }, ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) { @@ -34,11 +48,16 @@ func NewProxy(from, to string) http.Handler { func main() { mux := http.NewServeMux() - mux.Handle("/idp/", NewProxy("/idp", "localhost:8282")) - mux.Handle("/sp/", NewProxy("/sp", "localhost:8283")) + routes := map[string]string{ + "idp.example.com": "localhost:8282", + "ui.example.com": "localhost:8283", + "api.example.com": "localhost:8284", + } + mux.Handle("/", NewRouter(routes)) + bindAddress := env.Fetch("BIND_ADDR", ":8080") log.Fatal((&http.Server{ - Addr: ":8080", + Addr: bindAddress, Handler: mux, ReadHeaderTimeout: 10 * time.Second, ReadTimeout: 30 * time.Second, @@ -2,4 +2,13 @@ module gitlab.com/mokhax/spike go 1.24.0 -require github.com/magefile/mage v1.15.0 +require ( + github.com/casbin/casbin/v2 v2.103.0 + github.com/magefile/mage v1.15.0 + github.com/xlgmokha/x v0.0.0-20240605230110-5cbcac4d8ff8 +) + +require ( + github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect + github.com/casbin/govaluate v1.3.0 // indirect +) @@ -1,2 +1,26 @@ +github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I= +github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/casbin/casbin/v2 v2.103.0 h1:dHElatNXNrr8XcseUov0ZSiWjauwmZZE6YMV3eU1yic= +github.com/casbin/casbin/v2 v2.103.0/go.mod h1:Ee33aqGrmES+GNL17L0h9X28wXuo829wnNUnS0edAco= +github.com/casbin/govaluate v1.3.0 h1:VA0eSY0M2lA86dYd5kPPuNZMUD9QkWnOCnavGrw9myc= +github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/xlgmokha/x v0.0.0-20240605230110-5cbcac4d8ff8 h1:Hmyf8pgNUs3l8TW0YdUarBVAU+hWX87efBukspg4nWc= +github.com/xlgmokha/x v0.0.0-20240605230110-5cbcac4d8ff8/go.mod h1:C9MUZ3A7PTPbrLNTvu2lKhpM0dFpPHt5yH8YGuYzmKQ= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/magefile.go b/magefile.go index 40df5ade..8e1b969b 100644 --- a/magefile.go +++ b/magefile.go @@ -16,35 +16,55 @@ import ( var Default = Run // Run the Identity Provider -func RunIdp() error { - return sh.RunV("ruby", "./bin/idp") +func Idp() error { + env := map[string]string{ + "SCHEME": "http", + "PORT": "8282", + "HOST": "idp.example.com:8080", + } + return sh.RunWithV(env, "ruby", "./bin/idp") } -// Run the Service Provider -func RunSp() error { - return sh.RunV("ruby", "./bin/sp") +// Run the UI (a.k.a Service Provider) +func UI() error { + env := map[string]string{ + "SCHEME": "http", + "PORT": "8283", + "HOST": "ui.example.com:8080", + "IDP_HOST": "idp.example.com:8080", + } + return sh.RunWithV(env, "ruby", "./bin/ui") } // Run the API Gateway -func RunGateway() error { - return sh.RunV("go", "run", "./cmd/gtwy/main.go") +func Gateway() error { + env := map[string]string{ + "BIND_ADDR": ":8080", + } + return sh.RunWithV(env, "go", "run", "./cmd/gtwy/main.go") } // Run the REST API -func RunApi() error { - return sh.RunV("ruby", "./bin/rest-api") +func Api() error { + env := map[string]string{ + "SCHEME": "http", + "PORT": "8284", + "HOST": "localhost:8284", + } + return sh.RunWithV(env, "ruby", "./bin/api") } // Open a web browser to the login page func Browser() error { + url := "http://localhost:8080/ui/sessions/new" if runtime.GOOS == "linux" { - return sh.RunV("xdg-open", "http://localhost:8080/sp/sessions/new") + return sh.RunV("xdg-open", url) } else { - return sh.RunV("open", "http://localhost:8080/sp/sessions/new") + return sh.RunV("open", url) } } // Run All the servers func Run(ctx context.Context) { - mg.CtxDeps(ctx, RunIdp, RunSp, RunApi, RunGateway) + mg.CtxDeps(ctx, Idp, UI, Api, Gateway) } diff --git a/model.conf b/model.conf new file mode 100644 index 00000000..efe93e0f --- /dev/null +++ b/model.conf @@ -0,0 +1,17 @@ +[request_definition] +r = subject, domain, action, object + +[policy_definition] +p = subject, domain, action, object + +[policy_effect] +e = some(where (p.eft == allow)) + +[matchers] +m =\ + (\ + (p.subject == "*" || r.subject == p.subject || regexMatch(r.subject, p.subject))\ + && (p.domain == "*" || r.domain == p.domain)\ + && (p.action == "*" || regexMatch(r.action, p.action))\ + && keyMatch(r.object, p.object)\ + ) diff --git a/policy.csv b/policy.csv new file mode 100644 index 00000000..e662398a --- /dev/null +++ b/policy.csv @@ -0,0 +1,8 @@ +p, "\A[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}\z", api.example.com, (GET)|(POST)|(PATCH)|(PUT)|(DELETE)|(HEAD), /* +p, *, *, (GET)|(HEAD), /health +p, *, *, GET, /.well-known/* +p, *, idp.example.com, (GET)|(POST), /oauth/* +p, *, idp.example.com, (GET)|(POST), /saml/* +p, *, ui.example.com, (GET)|(POST), /oauth/* +p, *, ui.example.com, (GET)|(POST), /saml/* +p, 71cbc18e-bd41-4229-9ad2-749546a2a4a7, *, *, /* |
