mirror of
https://github.com/pomerium/pomerium.git
synced 2025-04-29 18:36:30 +02:00
300 lines
8.5 KiB
Go
300 lines
8.5 KiB
Go
package authorize
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
envoy_service_auth_v2 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v2"
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/pomerium/pomerium/authorize/evaluator"
|
|
"github.com/pomerium/pomerium/config"
|
|
"github.com/pomerium/pomerium/internal/cryptutil"
|
|
"github.com/pomerium/pomerium/internal/encoding/jws"
|
|
"github.com/stretchr/testify/assert"
|
|
"gopkg.in/square/go-jose.v2/jwt"
|
|
)
|
|
|
|
const certPEM = `
|
|
-----BEGIN CERTIFICATE-----
|
|
MIIDujCCAqKgAwIBAgIIE31FZVaPXTUwDQYJKoZIhvcNAQEFBQAwSTELMAkGA1UE
|
|
BhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMTHEdvb2dsZSBJbnRl
|
|
cm5ldCBBdXRob3JpdHkgRzIwHhcNMTQwMTI5MTMyNzQzWhcNMTQwNTI5MDAwMDAw
|
|
WjBpMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwN
|
|
TW91bnRhaW4gVmlldzETMBEGA1UECgwKR29vZ2xlIEluYzEYMBYGA1UEAwwPbWFp
|
|
bC5nb29nbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfRrObuSW5T7q
|
|
5CnSEqefEmtH4CCv6+5EckuriNr1CjfVvqzwfAhopXkLrq45EQm8vkmf7W96XJhC
|
|
7ZM0dYi1/qOCAU8wggFLMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAa
|
|
BgNVHREEEzARgg9tYWlsLmdvb2dsZS5jb20wCwYDVR0PBAQDAgeAMGgGCCsGAQUF
|
|
BwEBBFwwWjArBggrBgEFBQcwAoYfaHR0cDovL3BraS5nb29nbGUuY29tL0dJQUcy
|
|
LmNydDArBggrBgEFBQcwAYYfaHR0cDovL2NsaWVudHMxLmdvb2dsZS5jb20vb2Nz
|
|
cDAdBgNVHQ4EFgQUiJxtimAuTfwb+aUtBn5UYKreKvMwDAYDVR0TAQH/BAIwADAf
|
|
BgNVHSMEGDAWgBRK3QYWG7z2aLV29YG2u2IaulqBLzAXBgNVHSAEEDAOMAwGCisG
|
|
AQQB1nkCBQEwMAYDVR0fBCkwJzAloCOgIYYfaHR0cDovL3BraS5nb29nbGUuY29t
|
|
L0dJQUcyLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAH6RYHxHdcGpMpFE3oxDoFnP+
|
|
gtuBCHan2yE2GRbJ2Cw8Lw0MmuKqHlf9RSeYfd3BXeKkj1qO6TVKwCh+0HdZk283
|
|
TZZyzmEOyclm3UGFYe82P/iDFt+CeQ3NpmBg+GoaVCuWAARJN/KfglbLyyYygcQq
|
|
0SgeDh8dRKUiaW3HQSoYvTvdTuqzwK4CXsr3b5/dAOY8uMuG/IAR3FgwTbZ1dtoW
|
|
RvOTa8hYiU6A475WuZKyEHcwnGYe57u2I2KbMgcKjPniocj4QzgYsVAVKW3IwaOh
|
|
yE+vPxsiUkvQHdO2fojCkY8jg70jxM+gu59tPDNbw3Uh/2Ij310FgTHsnGQMyA==
|
|
-----END CERTIFICATE-----`
|
|
|
|
func Test_getEvaluatorRequest(t *testing.T) {
|
|
actual := getEvaluatorRequestFromCheckRequest(&envoy_service_auth_v2.CheckRequest{
|
|
Attributes: &envoy_service_auth_v2.AttributeContext{
|
|
Source: &envoy_service_auth_v2.AttributeContext_Peer{
|
|
Certificate: url.QueryEscape(certPEM),
|
|
},
|
|
Request: &envoy_service_auth_v2.AttributeContext_Request{
|
|
Http: &envoy_service_auth_v2.AttributeContext_HttpRequest{
|
|
Id: "id-1234",
|
|
Method: "GET",
|
|
Headers: map[string]string{
|
|
"accept": "text/html",
|
|
"x-forwarded-proto": "https",
|
|
},
|
|
Path: "/some/path?qs=1",
|
|
Host: "example.com",
|
|
Scheme: "http",
|
|
Body: "BODY",
|
|
},
|
|
},
|
|
},
|
|
}, []byte("HELLO WORLD"))
|
|
expect := &evaluator.Request{
|
|
User: "HELLO WORLD",
|
|
Method: "GET",
|
|
URL: "https://example.com/some/path?qs=1",
|
|
Header: map[string][]string{
|
|
"Accept": {"text/html"},
|
|
"X-Forwarded-Proto": {"https"},
|
|
},
|
|
Host: "example.com",
|
|
RequestURI: "https://example.com/some/path?qs=1",
|
|
ClientCertificate: certPEM,
|
|
}
|
|
assert.Equal(t, expect, actual)
|
|
}
|
|
|
|
func Test_handleForwardAuth(t *testing.T) {
|
|
checkReq := &envoy_service_auth_v2.CheckRequest{
|
|
Attributes: &envoy_service_auth_v2.AttributeContext{
|
|
Source: &envoy_service_auth_v2.AttributeContext_Peer{
|
|
Certificate: url.QueryEscape(certPEM),
|
|
},
|
|
Request: &envoy_service_auth_v2.AttributeContext_Request{
|
|
Http: &envoy_service_auth_v2.AttributeContext_HttpRequest{
|
|
Method: "GET",
|
|
Path: "/verify?uri=" + url.QueryEscape("https://example.com/some/path?qs=1"),
|
|
Host: "forward-auth.example.com",
|
|
Scheme: "https",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
t.Run("enabled", func(t *testing.T) {
|
|
a := new(Authorize)
|
|
a.currentOptions.Store(config.Options{
|
|
ForwardAuthURL: mustParseURL("https://forward-auth.example.com"),
|
|
})
|
|
isForwardAuth := a.handleForwardAuth(checkReq)
|
|
assert.True(t, isForwardAuth)
|
|
assert.Equal(t, &envoy_service_auth_v2.AttributeContext_HttpRequest{
|
|
Method: "GET",
|
|
Path: "/some/path?qs=1",
|
|
Host: "example.com",
|
|
Scheme: "https",
|
|
}, checkReq.Attributes.Request.Http)
|
|
})
|
|
t.Run("disabled", func(t *testing.T) {
|
|
a := new(Authorize)
|
|
a.currentOptions.Store(config.Options{
|
|
ForwardAuthURL: nil,
|
|
})
|
|
isForwardAuth := a.handleForwardAuth(checkReq)
|
|
assert.False(t, isForwardAuth)
|
|
})
|
|
}
|
|
|
|
func Test_refreshSession(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
_ = json.NewEncoder(w).Encode(struct {
|
|
Authorization string
|
|
}{
|
|
Authorization: r.Header.Get("Authorization"),
|
|
})
|
|
}))
|
|
defer srv.Close()
|
|
|
|
sharedKey := make([]byte, 32)
|
|
a := new(Authorize)
|
|
a.currentOptions.Store(config.Options{
|
|
AuthenticateURL: mustParseURL(srv.URL),
|
|
SharedKey: base64.StdEncoding.EncodeToString(sharedKey),
|
|
})
|
|
|
|
newSession, err := a.refreshSession(context.Background(), []byte("ABCD"))
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, `{"Authorization":"Pomerium ABCD"}`, strings.TrimSpace(string(newSession)))
|
|
}
|
|
|
|
func mustParseURL(str string) *url.URL {
|
|
u, err := url.Parse(str)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return u
|
|
}
|
|
|
|
func TestAuthorize_Check(t *testing.T) {
|
|
// golden policy
|
|
p := config.Policy{
|
|
From: "http://test.example.com",
|
|
To: "http://localhost",
|
|
AllowedUsers: []string{"bob@example.com"},
|
|
}
|
|
err := p.Validate()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ps := []config.Policy{p}
|
|
|
|
type user struct {
|
|
// Standard claims (as specified in RFC 7519).
|
|
jwt.Claims
|
|
// Pomerium claims (not standard claims)
|
|
Email string `json:"email"`
|
|
Groups []string `json:"groups,omitempty"`
|
|
User string `json:"user,omitempty"`
|
|
ImpersonateEmail string `json:"impersonate_email,omitempty"`
|
|
ImpersonateGroups []string `json:"impersonate_groups,omitempty"`
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
ctx context.Context
|
|
sk string
|
|
inUser string
|
|
inExpiry time.Time
|
|
inIssuer string
|
|
inAudience string
|
|
in *envoy_service_auth_v2.CheckRequest
|
|
want string
|
|
wantErr bool
|
|
}{
|
|
{"good",
|
|
context.TODO(),
|
|
cryptutil.NewBase64Key(),
|
|
"bob@example.com",
|
|
time.Now().Add(1 * time.Hour),
|
|
"authN.example.com",
|
|
"test.example.com",
|
|
nil,
|
|
"OK",
|
|
false},
|
|
{"bad user, alice",
|
|
context.TODO(),
|
|
cryptutil.NewBase64Key(),
|
|
"alice@example.com",
|
|
time.Now().Add(1 * time.Hour),
|
|
"authN.example.com",
|
|
"test.example.com",
|
|
nil,
|
|
"Access Denied",
|
|
false},
|
|
{"expired",
|
|
context.TODO(),
|
|
cryptutil.NewBase64Key(),
|
|
"bob@example.com",
|
|
time.Now().Add(-1 * time.Hour),
|
|
"authN.example.com",
|
|
"test.example.com",
|
|
nil,
|
|
"Access Denied",
|
|
false},
|
|
{"bad audience",
|
|
context.TODO(),
|
|
cryptutil.NewBase64Key(),
|
|
"bob@example.com",
|
|
time.Now().Add(1 * time.Hour),
|
|
"authN.example.com",
|
|
"bad.example.com",
|
|
nil,
|
|
"Access Denied",
|
|
false},
|
|
{"bad issuer",
|
|
context.TODO(),
|
|
cryptutil.NewBase64Key(),
|
|
"bob@example.com",
|
|
time.Now().Add(1 * time.Hour),
|
|
"bad.example.com",
|
|
"test.example.com",
|
|
nil,
|
|
"OK",
|
|
false},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var sa user
|
|
sa.Expiry = jwt.NewNumericDate(tt.inExpiry)
|
|
sa.IssuedAt = jwt.NewNumericDate(time.Now())
|
|
sa.NotBefore = jwt.NewNumericDate(time.Now())
|
|
sa.Email = tt.inUser
|
|
sa.Subject = sa.Email
|
|
sa.Issuer = tt.inIssuer
|
|
sa.Audience = jwt.Audience{tt.inAudience}
|
|
|
|
sharedKey := tt.sk
|
|
|
|
encoder, err := jws.NewHS256Signer([]byte(sharedKey), tt.inIssuer)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
raw, err := encoder.Marshal(sa)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
opts := config.Options{
|
|
Policies: ps,
|
|
CookieName: "_pomerium",
|
|
AuthenticateURL: mustParseURL("https://authN.example.com"),
|
|
SharedKey: sharedKey}
|
|
a, err := New(opts)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
in := &envoy_service_auth_v2.CheckRequest{
|
|
Attributes: &envoy_service_auth_v2.AttributeContext{
|
|
Request: &envoy_service_auth_v2.AttributeContext_Request{
|
|
Http: &envoy_service_auth_v2.AttributeContext_HttpRequest{
|
|
Id: "id-1234",
|
|
Method: "GET",
|
|
Headers: map[string]string{
|
|
"accept": "text/json",
|
|
"cookie": "_pomerium=" + string(raw),
|
|
},
|
|
Host: "test.example.com",
|
|
Scheme: "http",
|
|
Body: "BODY",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
got, err := a.Check(tt.ctx, in)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("Authorize.Check() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if diff := cmp.Diff(got.Status.GetMessage(), tt.want); diff != "" {
|
|
t.Errorf("Authorize.Check() = %v", diff)
|
|
}
|
|
})
|
|
}
|
|
}
|