mirror of
https://github.com/pomerium/pomerium.git
synced 2025-08-06 10:21:05 +02:00
add unit tests, update internal policy evaluator
This commit is contained in:
parent
8d38e6b47d
commit
5ff3e9794e
4 changed files with 154 additions and 3 deletions
|
@ -238,9 +238,15 @@ func (e *Evaluator) Evaluate(ctx context.Context, req *Request) (*Result, error)
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Internal endpoints that require a logged-in user.
|
||||||
|
var internalPathsNeedingLogin = map[string]struct{}{
|
||||||
|
"/.pomerium/jwt": {},
|
||||||
|
"/.pomerium/user": {},
|
||||||
|
"/.pomerium/webauthn": {},
|
||||||
|
}
|
||||||
|
|
||||||
func (e *Evaluator) evaluateInternal(_ context.Context, req *Request) (*PolicyResponse, error) {
|
func (e *Evaluator) evaluateInternal(_ context.Context, req *Request) (*PolicyResponse, error) {
|
||||||
// these endpoints require a logged-in user
|
if _, needsLogin := internalPathsNeedingLogin[req.HTTP.Path]; needsLogin {
|
||||||
if req.HTTP.Path == "/.pomerium/webauthn" || req.HTTP.Path == "/.pomerium/jwt" {
|
|
||||||
if req.Session.ID == "" {
|
if req.Session.ID == "" {
|
||||||
return &PolicyResponse{
|
return &PolicyResponse{
|
||||||
Allow: NewRuleResult(false, criteria.ReasonUserUnauthenticated),
|
Allow: NewRuleResult(false, criteria.ReasonUserUnauthenticated),
|
||||||
|
|
|
@ -554,6 +554,72 @@ func TestEvaluator(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEvaluator_EvaluateInternal(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
store := store.New()
|
||||||
|
evaluator, err := New(ctx, store, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Internal paths that do not require login.
|
||||||
|
for _, path := range []string{
|
||||||
|
"/.pomerium/",
|
||||||
|
"/.pomerium/device-enrolled",
|
||||||
|
"/.pomerium/sign_out",
|
||||||
|
} {
|
||||||
|
t.Run(path, func(t *testing.T) {
|
||||||
|
req := Request{
|
||||||
|
IsInternal: true,
|
||||||
|
HTTP: RequestHTTP{
|
||||||
|
Path: path,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
result, err := evaluator.Evaluate(ctx, &req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, RuleResult{
|
||||||
|
Value: true,
|
||||||
|
Reasons: criteria.NewReasons(criteria.ReasonPomeriumRoute),
|
||||||
|
AdditionalData: map[string]any{},
|
||||||
|
}, result.Allow)
|
||||||
|
assert.Equal(t, RuleResult{}, result.Deny)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal paths that do require login.
|
||||||
|
for _, path := range []string{
|
||||||
|
"/.pomerium/jwt",
|
||||||
|
"/.pomerium/user",
|
||||||
|
"/.pomerium/webauthn",
|
||||||
|
} {
|
||||||
|
t.Run(path, func(t *testing.T) {
|
||||||
|
req := Request{
|
||||||
|
IsInternal: true,
|
||||||
|
HTTP: RequestHTTP{
|
||||||
|
Path: path,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
result, err := evaluator.Evaluate(ctx, &req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, RuleResult{
|
||||||
|
Value: false,
|
||||||
|
Reasons: criteria.NewReasons(criteria.ReasonUserUnauthenticated),
|
||||||
|
AdditionalData: map[string]any{},
|
||||||
|
}, result.Allow)
|
||||||
|
assert.Equal(t, RuleResult{}, result.Deny)
|
||||||
|
|
||||||
|
// Simulate a logged-in user by setting a non-empty session ID.
|
||||||
|
req.Session.ID = "123456"
|
||||||
|
result, err = evaluator.Evaluate(ctx, &req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, RuleResult{
|
||||||
|
Value: true,
|
||||||
|
Reasons: criteria.NewReasons(criteria.ReasonPomeriumRoute),
|
||||||
|
AdditionalData: map[string]any{},
|
||||||
|
}, result.Allow)
|
||||||
|
assert.Equal(t, RuleResult{}, result.Deny)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPolicyEvaluatorReuse(t *testing.T) {
|
func TestPolicyEvaluatorReuse(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
|
|
@ -182,7 +182,9 @@ func userInfoFromJWT(rawJWT string) map[string]any {
|
||||||
}
|
}
|
||||||
|
|
||||||
var payload map[string]any
|
var payload map[string]any
|
||||||
if parsed.UnsafeClaimsWithoutVerification(&payload) != nil || payload["sub"] == "" {
|
if parsed.UnsafeClaimsWithoutVerification(&payload) != nil {
|
||||||
|
return nil
|
||||||
|
} else if sub, ok := payload["sub"].(string); !ok || sub == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,16 @@ package proxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/pomerium/pomerium/config"
|
"github.com/pomerium/pomerium/config"
|
||||||
"github.com/pomerium/pomerium/internal/atomicutil"
|
"github.com/pomerium/pomerium/internal/atomicutil"
|
||||||
|
@ -183,3 +186,77 @@ func TestProxy_jwt(t *testing.T) {
|
||||||
assert.Equal(t, "application/jwt", w.Header().Get("Content-Type"))
|
assert.Equal(t, "application/jwt", w.Header().Get("Content-Type"))
|
||||||
assert.Equal(t, w.Body.String(), rawJWT)
|
assert.Equal(t, w.Body.String(), rawJWT)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestProxy_jsonUserInfo(t *testing.T) {
|
||||||
|
proxy := &Proxy{
|
||||||
|
state: atomicutil.NewValue(&proxyState{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("no_jwt", func(t *testing.T) {
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/.pomerium/user", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
err := proxy.jsonUserInfo(w, req)
|
||||||
|
assert.ErrorContains(t, err, "not found")
|
||||||
|
})
|
||||||
|
t.Run("no_sub_claim", func(t *testing.T) {
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/.pomerium/user", nil)
|
||||||
|
req.Header.Set("X-Pomerium-Jwt-Assertion", "eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJmb28iOiJiYXIifQ.")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
err := proxy.jsonUserInfo(w, req)
|
||||||
|
assert.ErrorContains(t, err, "not found")
|
||||||
|
})
|
||||||
|
t.Run("valid_jwt", func(t *testing.T) {
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/.pomerium/user", nil)
|
||||||
|
req.Header.Set("X-Pomerium-Jwt-Assertion",
|
||||||
|
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTY3MDg4OTI0MSwiZXhwIjoxNjcwODkyODQxfQ.YoROB12_-a8VxikPqrYOA576pLYoLFeGwXAOWCGpXgM")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
err := proxy.jsonUserInfo(w, req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
result := w.Result()
|
||||||
|
assert.Equal(t, http.StatusOK, result.StatusCode)
|
||||||
|
assert.Equal(t, "application/json", result.Header.Get("Content-Type"))
|
||||||
|
b, _ := io.ReadAll(result.Body)
|
||||||
|
assert.Equal(t, `{"admin":true,"name":"John Doe","sub":"1234567890"}`, string(b))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// The /.pomerium/jwt endpoint should be registered only if explicitly enabled.
|
||||||
|
func TestProxy_registerDashboardHandlers_jwtEndpoint(t *testing.T) {
|
||||||
|
proxy := &Proxy{
|
||||||
|
state: atomicutil.NewValue(&proxyState{}),
|
||||||
|
}
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/.pomerium/jwt", nil)
|
||||||
|
rawJWT := "eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJzdWIiOiIxMjM0NTY3ODkwIn0."
|
||||||
|
req.Header.Set("X-Pomerium-Jwt-Assertion", rawJWT)
|
||||||
|
|
||||||
|
t.Run("disabled", func(t *testing.T) {
|
||||||
|
opts := config.NewDefaultOptions()
|
||||||
|
opts.RuntimeFlags[config.RuntimeFlagPomeriumJWTEndpoint] = false
|
||||||
|
m := mux.NewRouter()
|
||||||
|
proxy.registerDashboardHandlers(m, opts)
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
m.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
result := w.Result()
|
||||||
|
assert.Equal(t, http.StatusNotFound, result.StatusCode)
|
||||||
|
assert.Equal(t, "text/plain; charset=utf-8", result.Header.Get("Content-Type"))
|
||||||
|
b, _ := io.ReadAll(result.Body)
|
||||||
|
assert.Equal(t, "404 page not found\n", string(b))
|
||||||
|
})
|
||||||
|
t.Run("enabled", func(t *testing.T) {
|
||||||
|
opts := config.NewDefaultOptions()
|
||||||
|
opts.RuntimeFlags[config.RuntimeFlagPomeriumJWTEndpoint] = true
|
||||||
|
m := mux.NewRouter()
|
||||||
|
proxy.registerDashboardHandlers(m, opts)
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
m.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
result := w.Result()
|
||||||
|
assert.Equal(t, http.StatusOK, result.StatusCode)
|
||||||
|
assert.Equal(t, "application/jwt", result.Header.Get("Content-Type"))
|
||||||
|
b, _ := io.ReadAll(result.Body)
|
||||||
|
assert.Equal(t, rawJWT, string(b))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue