mirror of
https://github.com/pomerium/pomerium.git
synced 2025-06-11 07:12:59 +02:00
proxy: add route portal json (#5428)
* proxy: add route portal json * fix 405 issue * add link to issue * Update proxy/portal/filter_test.go Co-authored-by: Kenneth Jenkins <51246568+kenjenkins@users.noreply.github.com> --------- Co-authored-by: Kenneth Jenkins <51246568+kenjenkins@users.noreply.github.com>
This commit is contained in:
parent
6e1fabec0b
commit
e816cef2a1
10 changed files with 628 additions and 5 deletions
|
@ -40,11 +40,32 @@ func (p *Proxy) registerDashboardHandlers(r *mux.Router, opts *config.Options) *
|
|||
c.Path("/").Handler(httputil.HandlerFunc(p.Callback)).Methods(http.MethodGet)
|
||||
|
||||
// Programmatic API handlers and middleware
|
||||
a := r.PathPrefix(dashboardPath + "/api").Subrouter()
|
||||
// login api handler generates a user-navigable login url to authenticate
|
||||
a.Path("/v1/login").Handler(httputil.HandlerFunc(p.ProgrammaticLogin)).
|
||||
Queries(urlutil.QueryRedirectURI, "").
|
||||
Methods(http.MethodGet)
|
||||
// gorilla mux has a bug that prevents HTTP 405 errors from being returned properly so we do all this manually
|
||||
// https://github.com/gorilla/mux/issues/739
|
||||
r.PathPrefix(dashboardPath + "/api").
|
||||
Handler(httputil.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
|
||||
switch r.URL.Path {
|
||||
// login api handler generates a user-navigable login url to authenticate
|
||||
case dashboardPath + "/api/v1/login":
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
||||
return nil
|
||||
}
|
||||
if !r.URL.Query().Has(urlutil.QueryRedirectURI) {
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
return nil
|
||||
}
|
||||
return p.ProgrammaticLogin(w, r)
|
||||
case dashboardPath + "/api/v1/routes":
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
||||
return nil
|
||||
}
|
||||
return p.routesPortalJSON(w, r)
|
||||
}
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
return nil
|
||||
}))
|
||||
|
||||
return r
|
||||
}
|
||||
|
|
56
proxy/handlers_portal.go
Normal file
56
proxy/handlers_portal.go
Normal file
|
@ -0,0 +1,56 @@
|
|||
package proxy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/pomerium/pomerium/config"
|
||||
"github.com/pomerium/pomerium/internal/handlers"
|
||||
"github.com/pomerium/pomerium/internal/httputil"
|
||||
"github.com/pomerium/pomerium/proxy/portal"
|
||||
)
|
||||
|
||||
func (p *Proxy) routesPortalJSON(w http.ResponseWriter, r *http.Request) error {
|
||||
u := p.getUserInfoData(r)
|
||||
rs := p.getPortalRoutes(u)
|
||||
m := map[string]any{}
|
||||
m["routes"] = rs
|
||||
|
||||
b, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return httputil.NewError(http.StatusInternalServerError, err)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write(b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Proxy) getPortalRoutes(u handlers.UserInfoData) []portal.Route {
|
||||
options := p.currentOptions.Load()
|
||||
pu := p.getPortalUser(u)
|
||||
var routes []*config.Policy
|
||||
for route := range options.GetAllPolicies() {
|
||||
if portal.CheckRouteAccess(pu, route) {
|
||||
routes = append(routes, route)
|
||||
}
|
||||
}
|
||||
return portal.RoutesFromConfigRoutes(routes)
|
||||
}
|
||||
|
||||
func (p *Proxy) getPortalUser(u handlers.UserInfoData) portal.User {
|
||||
pu := portal.User{}
|
||||
pu.SessionID = u.Session.GetId()
|
||||
pu.UserID = u.User.GetId()
|
||||
pu.Email = u.User.GetEmail()
|
||||
for _, dg := range u.DirectoryGroups {
|
||||
if v := dg.ID; v != "" {
|
||||
pu.Groups = append(pu.Groups, dg.ID)
|
||||
}
|
||||
if v := dg.Name; v != "" {
|
||||
pu.Groups = append(pu.Groups, dg.Name)
|
||||
}
|
||||
}
|
||||
return pu
|
||||
}
|
51
proxy/handlers_portal_test.go
Normal file
51
proxy/handlers_portal_test.go
Normal file
|
@ -0,0 +1,51 @@
|
|||
package proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/pomerium/pomerium/config"
|
||||
"github.com/pomerium/pomerium/internal/httputil"
|
||||
)
|
||||
|
||||
func TestProxy_routesPortalJSON(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
cfg := &config.Config{Options: config.NewDefaultOptions()}
|
||||
to, err := config.ParseWeightedUrls("https://to.example.com")
|
||||
require.NoError(t, err)
|
||||
cfg.Options.Routes = append(cfg.Options.Routes, config.Policy{
|
||||
Name: "public",
|
||||
Description: "PUBLIC ROUTE",
|
||||
LogoURL: "https://logo.example.com",
|
||||
From: "https://from.example.com",
|
||||
To: to,
|
||||
AllowPublicUnauthenticatedAccess: true,
|
||||
})
|
||||
proxy, err := New(ctx, cfg)
|
||||
require.NoError(t, err)
|
||||
|
||||
r := httptest.NewRequest(http.MethodGet, "/.pomerium/api/v1/routes", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router := httputil.NewRouter()
|
||||
router = proxy.registerDashboardHandlers(router, cfg.Options)
|
||||
router.ServeHTTP(w, r)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Equal(t, "application/json", w.Header().Get("Content-Type"))
|
||||
assert.JSONEq(t, `{"routes":[
|
||||
{
|
||||
"id": "4e71df99c0317efb",
|
||||
"name": "public",
|
||||
"from": "https://from.example.com",
|
||||
"type": "http",
|
||||
"description": "PUBLIC ROUTE",
|
||||
"logo_url": "https://logo.example.com"
|
||||
}
|
||||
]}`, w.Body.String())
|
||||
}
|
105
proxy/portal/filter.go
Normal file
105
proxy/portal/filter.go
Normal file
|
@ -0,0 +1,105 @@
|
|||
package portal
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/pomerium/pomerium/config"
|
||||
"github.com/pomerium/pomerium/pkg/policy/parser"
|
||||
)
|
||||
|
||||
// User is the computed user information needed for access decisions.
|
||||
type User struct {
|
||||
SessionID string
|
||||
UserID string
|
||||
Email string
|
||||
Groups []string
|
||||
}
|
||||
|
||||
// CheckRouteAccess checks if the user has access to the route.
|
||||
func CheckRouteAccess(user User, route *config.Policy) bool {
|
||||
// check the main policy
|
||||
ppl := route.ToPPL()
|
||||
if checkPPLAccess(user, ppl) {
|
||||
return true
|
||||
}
|
||||
|
||||
// check sub-policies
|
||||
for _, sp := range route.SubPolicies {
|
||||
if sp.SourcePPL == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
ppl, err := parser.New().ParseYAML(strings.NewReader(sp.SourcePPL))
|
||||
if err != nil {
|
||||
// ignore invalid PPL
|
||||
continue
|
||||
}
|
||||
|
||||
if checkPPLAccess(user, ppl) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// nothing matched
|
||||
return false
|
||||
}
|
||||
|
||||
func checkPPLAccess(user User, ppl *parser.Policy) bool {
|
||||
for _, r := range ppl.Rules {
|
||||
// ignore deny rules
|
||||
if r.Action != parser.ActionAllow {
|
||||
continue
|
||||
}
|
||||
|
||||
// ignore complex rules
|
||||
if len(r.Nor) > 0 || len(r.Not) > 0 || len(r.And) > 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
cs := append(append([]parser.Criterion{}, r.Or...), r.And...)
|
||||
for _, c := range cs {
|
||||
ok := checkPPLCriterionAccess(user, c)
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func checkPPLCriterionAccess(user User, criterion parser.Criterion) bool {
|
||||
switch criterion.Name {
|
||||
case "accept":
|
||||
return true
|
||||
}
|
||||
|
||||
// require a session
|
||||
if user.SessionID == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
switch criterion.Name {
|
||||
case "authenticated_user":
|
||||
return true
|
||||
}
|
||||
|
||||
// require a user
|
||||
if user.UserID == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
switch criterion.Name {
|
||||
case "domain":
|
||||
parts := strings.SplitN(user.Email, "@", 2)
|
||||
return len(parts) == 2 && matchString(parts[1], criterion.Data)
|
||||
case "email":
|
||||
return matchString(user.Email, criterion.Data)
|
||||
case "groups":
|
||||
return matchStringList(user.Groups, criterion.Data)
|
||||
case "user":
|
||||
return matchString(user.UserID, criterion.Data)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
67
proxy/portal/filter_test.go
Normal file
67
proxy/portal/filter_test.go
Normal file
|
@ -0,0 +1,67 @@
|
|||
package portal_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/pomerium/pomerium/config"
|
||||
"github.com/pomerium/pomerium/pkg/policy/parser"
|
||||
"github.com/pomerium/pomerium/proxy/portal"
|
||||
)
|
||||
|
||||
func TestCheckRouteAccess(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
u1 := portal.User{}
|
||||
u2 := portal.User{SessionID: "s2", UserID: "u2", Email: "u2@example.com", Groups: []string{"g2"}}
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
user portal.User
|
||||
route *config.Policy
|
||||
}{
|
||||
{"no ppl", u1, &config.Policy{}},
|
||||
{"allow_any_authenticated_user", u1, &config.Policy{AllowAnyAuthenticatedUser: true}},
|
||||
{"allowed_domains", u2, &config.Policy{AllowedDomains: []string{"not.example.com"}}},
|
||||
{"allowed_users", u2, &config.Policy{AllowedUsers: []string{"u3"}}},
|
||||
{"not conditionals", u2, &config.Policy{Policy: mustParsePPL(t, `{"allow": {"not": [{"accept": 1}]}}`)}},
|
||||
{"nor conditionals", u2, &config.Policy{Policy: mustParsePPL(t, `{"allow": {"nor": [{"accept": 1}]}}`)}},
|
||||
{"and conditionals", u2, &config.Policy{Policy: mustParsePPL(t, `{"allow": {"and": [{"accept": 1}, {"accept": 1}]}}`)}},
|
||||
{"authenticated_user", u1, &config.Policy{Policy: mustParsePPL(t, `{"allow": {"or": [{"authenticated_user": 1}]}}`)}},
|
||||
{"domain", u2, &config.Policy{Policy: mustParsePPL(t, `{"allow": {"or": [{"domain": "not.example.com"}]}}`)}},
|
||||
{"email", u1, &config.Policy{Policy: mustParsePPL(t, `{"allow": {"or": [{"email": "u2@example.com"}]}}`)}},
|
||||
{"groups", u2, &config.Policy{Policy: mustParsePPL(t, `{"allow": {"or": [{"groups": {"has": "g3"}}]}}`)}},
|
||||
} {
|
||||
assert.False(t, portal.CheckRouteAccess(tc.user, tc.route), "%s: should deny access for %v to %v",
|
||||
tc.name, tc.user, tc.route)
|
||||
}
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
user portal.User
|
||||
route *config.Policy
|
||||
}{
|
||||
{"allow_public_unauthenticated_access", u1, &config.Policy{AllowPublicUnauthenticatedAccess: true}},
|
||||
{"allow_any_authenticated_user", u2, &config.Policy{AllowAnyAuthenticatedUser: true}},
|
||||
{"allowed_domains", u2, &config.Policy{AllowedDomains: []string{"example.com"}}},
|
||||
{"allowed_users", u2, &config.Policy{AllowedUsers: []string{"u2"}}},
|
||||
{"and conditionals", u2, &config.Policy{Policy: mustParsePPL(t, `{"allow": {"and": [{"accept": 1}]}}`)}},
|
||||
{"or conditionals", u2, &config.Policy{Policy: mustParsePPL(t, `{"allow": {"or": [{"reject": 1}, {"accept": 1}]}}`)}},
|
||||
{"authenticated_user", u2, &config.Policy{Policy: mustParsePPL(t, `{"allow": {"or": [{"authenticated_user": 1}]}}`)}},
|
||||
{"domain", u2, &config.Policy{Policy: mustParsePPL(t, `{"allow": {"or": [{"domain": "example.com"}]}}`)}},
|
||||
{"email", u2, &config.Policy{Policy: mustParsePPL(t, `{"allow": {"or": [{"email": "u2@example.com"}]}}`)}},
|
||||
{"groups", u2, &config.Policy{Policy: mustParsePPL(t, `{"allow": {"or": [{"groups": {"has": "g2"}}]}}`)}},
|
||||
} {
|
||||
assert.True(t, portal.CheckRouteAccess(tc.user, tc.route), "%s: should grant access for %v to %v",
|
||||
tc.name, tc.user, tc.route)
|
||||
}
|
||||
}
|
||||
|
||||
func mustParsePPL(t testing.TB, raw string) *config.PPLPolicy {
|
||||
ppl, err := parser.New().ParseJSON(strings.NewReader(raw))
|
||||
require.NoError(t, err)
|
||||
return &config.PPLPolicy{Policy: ppl}
|
||||
}
|
108
proxy/portal/matchers.go
Normal file
108
proxy/portal/matchers.go
Normal file
|
@ -0,0 +1,108 @@
|
|||
package portal
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/pomerium/pomerium/pkg/policy/parser"
|
||||
)
|
||||
|
||||
type matcher[T any] func(left T, right parser.Value) bool
|
||||
|
||||
var stringMatchers = map[string]matcher[string]{
|
||||
"contains": matchStringContains,
|
||||
"ends_with": matchStringEndsWith,
|
||||
"is": matchStringIs,
|
||||
"starts_with": matchStringStartsWith,
|
||||
}
|
||||
|
||||
var stringListMatchers = map[string]matcher[[]string]{
|
||||
"has": matchStringListHas,
|
||||
"is": matchStringListIs,
|
||||
}
|
||||
|
||||
func matchString(left string, right parser.Value) bool {
|
||||
obj, ok := right.(parser.Object)
|
||||
if !ok {
|
||||
obj = parser.Object{
|
||||
"is": right,
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range obj {
|
||||
f, ok := stringMatchers[k]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
ok = f(left, v)
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func matchStringContains(left string, right parser.Value) bool {
|
||||
str, ok := right.(parser.String)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return strings.Contains(left, string(str))
|
||||
}
|
||||
|
||||
func matchStringEndsWith(left string, right parser.Value) bool {
|
||||
str, ok := right.(parser.String)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return strings.HasSuffix(left, string(str))
|
||||
}
|
||||
|
||||
func matchStringIs(left string, right parser.Value) bool {
|
||||
str, ok := right.(parser.String)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return left == string(str)
|
||||
}
|
||||
|
||||
func matchStringStartsWith(left string, right parser.Value) bool {
|
||||
str, ok := right.(parser.String)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return strings.HasPrefix(left, string(str))
|
||||
}
|
||||
|
||||
func matchStringList(left []string, right parser.Value) bool {
|
||||
obj, ok := right.(parser.Object)
|
||||
if !ok {
|
||||
obj = parser.Object{
|
||||
"has": right,
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range obj {
|
||||
f, ok := stringListMatchers[k]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
ok = f(left, v)
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func matchStringListHas(left []string, right parser.Value) bool {
|
||||
for _, str := range left {
|
||||
if matchStringIs(str, right) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func matchStringListIs(left []string, right parser.Value) bool {
|
||||
return len(left) == 1 && matchStringListHas(left, right)
|
||||
}
|
83
proxy/portal/matchers_test.go
Normal file
83
proxy/portal/matchers_test.go
Normal file
|
@ -0,0 +1,83 @@
|
|||
package portal
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/pomerium/pomerium/pkg/policy/parser"
|
||||
)
|
||||
|
||||
func Test_matchString(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("string", func(t *testing.T) {
|
||||
assert.True(t, matchString("TEST", mustParseValue(t, `"TEST"`)))
|
||||
})
|
||||
t.Run("bool", func(t *testing.T) {
|
||||
assert.False(t, matchString("true", mustParseValue(t, `true`)))
|
||||
})
|
||||
t.Run("number", func(t *testing.T) {
|
||||
assert.False(t, matchString("1", mustParseValue(t, `1`)))
|
||||
})
|
||||
t.Run("null", func(t *testing.T) {
|
||||
assert.False(t, matchString("null", mustParseValue(t, `null`)))
|
||||
})
|
||||
t.Run("array", func(t *testing.T) {
|
||||
assert.False(t, matchString("[]", mustParseValue(t, `[]`)))
|
||||
})
|
||||
t.Run("contains", func(t *testing.T) {
|
||||
assert.True(t, matchString("XYZ", mustParseValue(t, `{"contains":"Y"}`)))
|
||||
assert.False(t, matchString("XYZ", mustParseValue(t, `{"contains":"A"}`)))
|
||||
})
|
||||
t.Run("ends_with", func(t *testing.T) {
|
||||
assert.True(t, matchString("XYZ", mustParseValue(t, `{"ends_with":"Z"}`)))
|
||||
assert.False(t, matchString("XYZ", mustParseValue(t, `{"ends_with":"X"}`)))
|
||||
})
|
||||
t.Run("is", func(t *testing.T) {
|
||||
assert.True(t, matchString("XYZ", mustParseValue(t, `{"is":"XYZ"}`)))
|
||||
assert.False(t, matchString("XYZ", mustParseValue(t, `{"is":"X"}`)))
|
||||
})
|
||||
t.Run("starts_with", func(t *testing.T) {
|
||||
assert.True(t, matchString("XYZ", mustParseValue(t, `{"starts_with":"X"}`)))
|
||||
assert.False(t, matchString("XYZ", mustParseValue(t, `{"starts_with":"Z"}`)))
|
||||
})
|
||||
}
|
||||
|
||||
func Test_matchStringList(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("string", func(t *testing.T) {
|
||||
assert.True(t, matchStringList([]string{"X", "Y", "Z"}, mustParseValue(t, `"Y"`)))
|
||||
assert.False(t, matchStringList([]string{"X", "Y", "Z"}, mustParseValue(t, `"A"`)))
|
||||
})
|
||||
t.Run("bool", func(t *testing.T) {
|
||||
assert.False(t, matchStringList([]string{"true"}, mustParseValue(t, `true`)))
|
||||
})
|
||||
t.Run("number", func(t *testing.T) {
|
||||
assert.False(t, matchStringList([]string{"1"}, mustParseValue(t, `1`)))
|
||||
})
|
||||
t.Run("null", func(t *testing.T) {
|
||||
assert.False(t, matchStringList([]string{"null"}, mustParseValue(t, `null`)))
|
||||
})
|
||||
t.Run("array", func(t *testing.T) {
|
||||
assert.False(t, matchStringList([]string{"[]"}, mustParseValue(t, `[]`)))
|
||||
})
|
||||
t.Run("has", func(t *testing.T) {
|
||||
assert.True(t, matchStringList([]string{"X", "Y", "Z"}, mustParseValue(t, `{"has":"Y"}`)))
|
||||
assert.False(t, matchStringList([]string{"X", "Y", "Z"}, mustParseValue(t, `{"has":"A"}`)))
|
||||
})
|
||||
t.Run("is", func(t *testing.T) {
|
||||
assert.True(t, matchStringList([]string{"X"}, mustParseValue(t, `{"is":"X"}`)))
|
||||
assert.False(t, matchStringList([]string{"X", "Y", "Z"}, mustParseValue(t, `{"is":"Y"}`)))
|
||||
assert.False(t, matchStringList([]string{"X", "Y", "Z"}, mustParseValue(t, `{"is":"A"}`)))
|
||||
})
|
||||
}
|
||||
|
||||
func mustParseValue(t testing.TB, raw string) parser.Value {
|
||||
v, err := parser.ParseValue(strings.NewReader(raw))
|
||||
require.NoError(t, err)
|
||||
return v
|
||||
}
|
60
proxy/portal/portal.go
Normal file
60
proxy/portal/portal.go
Normal file
|
@ -0,0 +1,60 @@
|
|||
// Package portal contains the code for the routes portal
|
||||
package portal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pomerium/pomerium/config"
|
||||
"github.com/pomerium/pomerium/internal/urlutil"
|
||||
"github.com/pomerium/pomerium/pkg/zero/importutil"
|
||||
)
|
||||
|
||||
// A Route is a portal route.
|
||||
type Route struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
From string `json:"from"`
|
||||
Description string `json:"description"`
|
||||
ConnectCommand string `json:"connect_command,omitempty"`
|
||||
LogoURL string `json:"logo_url"`
|
||||
}
|
||||
|
||||
// RoutesFromConfigRoutes converts config routes into portal routes.
|
||||
func RoutesFromConfigRoutes(routes []*config.Policy) []Route {
|
||||
prs := make([]Route, len(routes))
|
||||
for i, route := range routes {
|
||||
pr := Route{}
|
||||
pr.ID = route.ID
|
||||
if pr.ID == "" {
|
||||
pr.ID = fmt.Sprintf("%x", route.MustRouteID())
|
||||
}
|
||||
pr.Name = route.Name
|
||||
pr.From = route.From
|
||||
fromURL, err := urlutil.ParseAndValidateURL(route.From)
|
||||
if err == nil {
|
||||
if strings.HasPrefix(fromURL.Scheme, "tcp+") {
|
||||
pr.Type = "tcp"
|
||||
pr.ConnectCommand = "pomerium-cli tcp " + fromURL.Host
|
||||
} else if strings.HasPrefix(fromURL.Scheme, "udp+") {
|
||||
pr.Type = "udp"
|
||||
pr.ConnectCommand = "pomerium-cli udp " + fromURL.Host
|
||||
} else {
|
||||
pr.Type = "http"
|
||||
}
|
||||
} else {
|
||||
pr.Type = "http"
|
||||
}
|
||||
pr.Description = route.Description
|
||||
pr.LogoURL = route.LogoURL
|
||||
prs[i] = pr
|
||||
}
|
||||
// generate names if they're empty
|
||||
for i, name := range importutil.GenerateRouteNames(routes) {
|
||||
if prs[i].Name == "" {
|
||||
prs[i].Name = name
|
||||
}
|
||||
}
|
||||
return prs
|
||||
}
|
71
proxy/portal/portal_test.go
Normal file
71
proxy/portal/portal_test.go
Normal file
|
@ -0,0 +1,71 @@
|
|||
package portal_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/pomerium/pomerium/config"
|
||||
"github.com/pomerium/pomerium/proxy/portal"
|
||||
)
|
||||
|
||||
func TestRouteFromConfigRoute(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
to1, err := config.ParseWeightedUrls("https://to.example.com")
|
||||
require.NoError(t, err)
|
||||
to2, err := config.ParseWeightedUrls("tcp://postgres:5432")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, []portal.Route{
|
||||
{
|
||||
ID: "4e71df99c0317efb",
|
||||
Name: "from",
|
||||
Type: "http",
|
||||
From: "https://from.example.com",
|
||||
Description: "ROUTE #1",
|
||||
LogoURL: "https://logo.example.com",
|
||||
},
|
||||
{
|
||||
ID: "7c377f11cdb9700e",
|
||||
Name: "from-path",
|
||||
Type: "http",
|
||||
From: "https://from.example.com",
|
||||
},
|
||||
{
|
||||
ID: "708e3cbd0bbe8547",
|
||||
Name: "postgres",
|
||||
Type: "tcp",
|
||||
From: "tcp+https://postgres.example.com:5432",
|
||||
ConnectCommand: "pomerium-cli tcp postgres.example.com:5432",
|
||||
},
|
||||
{
|
||||
ID: "2dd08d87486e051a",
|
||||
Name: "dns",
|
||||
Type: "udp",
|
||||
From: "udp+https://dns.example.com:53",
|
||||
ConnectCommand: "pomerium-cli udp dns.example.com:53",
|
||||
},
|
||||
}, portal.RoutesFromConfigRoutes([]*config.Policy{
|
||||
{
|
||||
From: "https://from.example.com",
|
||||
To: to1,
|
||||
Description: "ROUTE #1",
|
||||
LogoURL: "https://logo.example.com",
|
||||
},
|
||||
{
|
||||
From: "https://from.example.com",
|
||||
To: to1,
|
||||
Path: "/path",
|
||||
},
|
||||
{
|
||||
From: "tcp+https://postgres.example.com:5432",
|
||||
To: to2,
|
||||
},
|
||||
{
|
||||
From: "udp+https://dns.example.com:53",
|
||||
To: to2,
|
||||
},
|
||||
}))
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue