Commit e1fe97f
Changed files (6)
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,
go.mod
@@ -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
+)
go.sum
@@ -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=
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)
}
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)\
+ )
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, *, *, /*