mirror of
https://github.com/pomerium/pomerium.git
synced 2025-04-29 18:36:30 +02:00
Currently, policy evaluation and authorize logging are coupled to the Envoy CheckRequest proto message (part of the ext_authz API). In the context of ssh proxy authentication, we won't have a CheckRequest. Instead, let's make the existing evaluator.Request type the source of truth for the authorize log fields. This way, whether we populate the evaluator.Request struct from an ext_authz request or from an ssh proxy request, we can use the same logAuthorizeCheck() method for logging. Add some additional fields to evaluator.RequestHTTP for the authorize log fields that are not currently represented in this struct.
223 lines
7.3 KiB
Go
223 lines
7.3 KiB
Go
package authorize
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/url"
|
|
"testing"
|
|
|
|
envoy_config_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
|
envoy_service_auth_v3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
"google.golang.org/protobuf/types/known/structpb"
|
|
|
|
"github.com/pomerium/pomerium/authorize/evaluator"
|
|
"github.com/pomerium/pomerium/config"
|
|
"github.com/pomerium/pomerium/internal/atomicutil"
|
|
"github.com/pomerium/pomerium/pkg/grpc/databroker"
|
|
"github.com/pomerium/pomerium/pkg/storage"
|
|
)
|
|
|
|
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) {
|
|
a := &Authorize{currentConfig: atomicutil.NewValue(&config.Config{
|
|
Options: &config.Options{
|
|
Policies: []config.Policy{{
|
|
From: "https://example.com",
|
|
SubPolicies: []config.SubPolicy{{
|
|
Rego: []string{"allow = true"},
|
|
}},
|
|
}},
|
|
},
|
|
}), state: atomicutil.NewValue(new(authorizeState))}
|
|
|
|
actual, err := a.getEvaluatorRequestFromCheckRequest(context.Background(),
|
|
&envoy_service_auth_v3.CheckRequest{
|
|
Attributes: &envoy_service_auth_v3.AttributeContext{
|
|
Request: &envoy_service_auth_v3.AttributeContext_Request{
|
|
Http: &envoy_service_auth_v3.AttributeContext_HttpRequest{
|
|
Id: "id-1234",
|
|
Method: http.MethodGet,
|
|
Headers: map[string]string{
|
|
"accept": "text/html",
|
|
"x-forwarded-proto": "https",
|
|
},
|
|
Path: "/some/path?qs=1",
|
|
Host: "example.com",
|
|
Scheme: "http",
|
|
Body: "BODY",
|
|
},
|
|
},
|
|
MetadataContext: &envoy_config_core_v3.Metadata{
|
|
FilterMetadata: map[string]*structpb.Struct{
|
|
"com.pomerium.client-certificate-info": {
|
|
Fields: map[string]*structpb.Value{
|
|
"presented": structpb.NewBoolValue(true),
|
|
"chain": structpb.NewStringValue(url.QueryEscape(certPEM)),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
)
|
|
require.NoError(t, err)
|
|
expect := &evaluator.Request{
|
|
Policy: &a.currentConfig.Load().Options.Policies[0],
|
|
HTTP: evaluator.RequestHTTP{
|
|
Method: http.MethodGet,
|
|
Host: "example.com",
|
|
Hostname: "example.com",
|
|
Path: "/some/path",
|
|
RawPath: "/some/path",
|
|
RawQuery: "qs=1",
|
|
URL: "http://example.com/some/path?qs=1",
|
|
Headers: map[string]string{
|
|
"Accept": "text/html",
|
|
"X-Forwarded-Proto": "https",
|
|
},
|
|
ClientCertificate: evaluator.ClientCertificateInfo{
|
|
Presented: true,
|
|
Leaf: certPEM[1:] + "\n",
|
|
Intermediates: "",
|
|
},
|
|
IP: "",
|
|
},
|
|
}
|
|
assert.Equal(t, expect, actual)
|
|
}
|
|
|
|
func Test_getEvaluatorRequestWithPortInHostHeader(t *testing.T) {
|
|
a := &Authorize{currentConfig: atomicutil.NewValue(&config.Config{
|
|
Options: &config.Options{
|
|
Policies: []config.Policy{{
|
|
From: "https://example.com",
|
|
SubPolicies: []config.SubPolicy{{
|
|
Rego: []string{"allow = true"},
|
|
}},
|
|
}},
|
|
},
|
|
}), state: atomicutil.NewValue(new(authorizeState))}
|
|
|
|
actual, err := a.getEvaluatorRequestFromCheckRequest(context.Background(),
|
|
&envoy_service_auth_v3.CheckRequest{
|
|
Attributes: &envoy_service_auth_v3.AttributeContext{
|
|
Request: &envoy_service_auth_v3.AttributeContext_Request{
|
|
Http: &envoy_service_auth_v3.AttributeContext_HttpRequest{
|
|
Id: "id-1234",
|
|
Method: http.MethodGet,
|
|
Headers: map[string]string{
|
|
"accept": "text/html",
|
|
"x-forwarded-proto": "https",
|
|
},
|
|
Path: "/some/path?qs=1",
|
|
Host: "example.com:80",
|
|
Scheme: "http",
|
|
Body: "BODY",
|
|
},
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
expect := &evaluator.Request{
|
|
Policy: &a.currentConfig.Load().Options.Policies[0],
|
|
Session: evaluator.RequestSession{},
|
|
HTTP: evaluator.RequestHTTP{
|
|
Method: http.MethodGet,
|
|
Host: "example.com:80",
|
|
Hostname: "example.com",
|
|
Path: "/some/path",
|
|
RawPath: "/some/path",
|
|
RawQuery: "qs=1",
|
|
URL: "http://example.com/some/path?qs=1",
|
|
Headers: map[string]string{
|
|
"Accept": "text/html",
|
|
"X-Forwarded-Proto": "https",
|
|
},
|
|
ClientCertificate: evaluator.ClientCertificateInfo{},
|
|
IP: "",
|
|
},
|
|
}
|
|
assert.Equal(t, expect, actual)
|
|
}
|
|
|
|
type mockDataBrokerServiceClient struct {
|
|
databroker.DataBrokerServiceClient
|
|
|
|
get func(ctx context.Context, in *databroker.GetRequest, opts ...grpc.CallOption) (*databroker.GetResponse, error)
|
|
put func(ctx context.Context, in *databroker.PutRequest, opts ...grpc.CallOption) (*databroker.PutResponse, error)
|
|
}
|
|
|
|
func (m mockDataBrokerServiceClient) Get(ctx context.Context, in *databroker.GetRequest, opts ...grpc.CallOption) (*databroker.GetResponse, error) {
|
|
return m.get(ctx, in, opts...)
|
|
}
|
|
|
|
func (m mockDataBrokerServiceClient) Put(ctx context.Context, in *databroker.PutRequest, opts ...grpc.CallOption) (*databroker.PutResponse, error) {
|
|
return m.put(ctx, in, opts...)
|
|
}
|
|
|
|
// Patch emulates the patch operation using Get and Put. (This is not atomic.)
|
|
func (m mockDataBrokerServiceClient) Patch(ctx context.Context, in *databroker.PatchRequest, opts ...grpc.CallOption) (*databroker.PatchResponse, error) {
|
|
var records []*databroker.Record
|
|
for _, record := range in.GetRecords() {
|
|
getResponse, err := m.Get(ctx, &databroker.GetRequest{
|
|
Type: record.GetType(),
|
|
Id: record.GetId(),
|
|
}, opts...)
|
|
if storage.IsNotFound(err) {
|
|
continue
|
|
} else if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
existing := getResponse.GetRecord()
|
|
if err := storage.PatchRecord(existing, record, in.GetFieldMask()); err != nil {
|
|
return nil, status.Error(codes.Unknown, err.Error())
|
|
}
|
|
|
|
records = append(records, record)
|
|
}
|
|
putResponse, err := m.Put(ctx, &databroker.PutRequest{Records: records}, opts...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &databroker.PatchResponse{
|
|
ServerVersion: putResponse.GetServerVersion(),
|
|
Records: putResponse.GetRecords(),
|
|
}, nil
|
|
}
|
|
|
|
func mustParseURL(rawURL string) url.URL {
|
|
u, err := url.Parse(rawURL)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return *u
|
|
}
|