mirror of
https://github.com/pomerium/pomerium.git
synced 2025-04-28 18:06:34 +02:00
Remove the previous conversion logic in NewPolicyFromProto() for the jwt_issuer_format field. This would prevent the new "unset" state from working correctly. Add a unit test to verify that all three values (unset, "hostOnly" and "uri") will successfully round trip to the proto format and back again. Also add a test case for the Options.ApplySettings() method to verify that an unset jwt_issuer_format will not overwrite the existing value (if any) in the settings.
638 lines
33 KiB
Go
638 lines
33 KiB
Go
package config
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
mathrand "math/rand/v2"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
|
|
envoy_config_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"google.golang.org/protobuf/proto"
|
|
|
|
"github.com/pomerium/pomerium/internal/urlutil"
|
|
"github.com/pomerium/pomerium/pkg/cryptutil"
|
|
)
|
|
|
|
func Test_PolicyValidate(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
policy Policy
|
|
wantErr bool
|
|
}{
|
|
{"good", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.notatld")}, false},
|
|
{"empty to host", Policy{From: "https://httpbin.corp.example", To: []WeightedURL{{URL: url.URL{Scheme: "https", Path: "/"}}}}, true},
|
|
{"empty from host", Policy{From: "https://", To: mustParseWeightedURLs(t, "https://httpbin.corp.example")}, true},
|
|
{"empty from scheme", Policy{From: "httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.example")}, true},
|
|
{"empty to scheme", Policy{From: "https://httpbin.corp.example", To: []WeightedURL{{URL: url.URL{Host: "httpbin.corp.example"}}}}, true},
|
|
{"path in from", Policy{From: "https://httpbin.corp.example/some/path", To: mustParseWeightedURLs(t, "https://httpbin.corp.example")}, true},
|
|
{"cors policy", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.notatld"), CORSAllowPreflight: true}, false},
|
|
{"public policy", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.notatld"), AllowPublicUnauthenticatedAccess: true}, false},
|
|
{"public and whitelist", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.notatld"), AllowPublicUnauthenticatedAccess: true, AllowedUsers: []string{"test@domain.example"}}, true},
|
|
{"route must have", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.notatld"), AllowPublicUnauthenticatedAccess: true, AllowedUsers: []string{"test@domain.example"}}, true},
|
|
{"any authenticated user policy", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.notatld"), AllowAnyAuthenticatedUser: true}, false},
|
|
{"any authenticated user and whitelist", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.notatld"), AllowAnyAuthenticatedUser: true, AllowedUsers: []string{"test@domain.example"}}, true},
|
|
{"good client cert", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.notatld"), TLSClientKey: "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcGdJQkFBS0NBUUVBNjdLanFtUVlHcTBNVnRBQ1ZwZUNtWG1pbmxRYkRQR0xtc1pBVUV3dWVIUW5ydDNXCnR2cERPbTZBbGFKTVVuVytIdTU1ampva2FsS2VWalRLbWdZR2JxVXpWRG9NYlBEYUhla2x0ZEJUTUdsT1VGc1AKNFVKU0RyTzR6ZE4rem80MjhUWDJQbkcyRkNkVktHeTRQRThpbEhiV0xjcjg3MVlqVjUxZnc4Q0xEWDlQWkpOdQo4NjFDRjdWOWlFSm02c1NmUWxtbmhOOGozK1d6VmJQUU55MVdzUjdpOWU5ajYzRXFLdDIyUTlPWEwrV0FjS3NrCm9JU21DTlZSVUFqVThZUlZjZ1FKQit6UTM0QVFQbHowT3A1Ty9RTi9NZWRqYUY4d0xTK2l2L3p2aVM4Y3FQYngKbzZzTHE2Rk5UbHRrL1FreGVDZUtLVFFlLzNrUFl2UUFkbmw2NVFJREFRQUJBb0lCQVFEQVQ0eXN2V2pSY3pxcgpKcU9SeGFPQTJEY3dXazJML1JXOFhtQWhaRmRTWHV2MkNQbGxhTU1yelBmTG41WUlmaHQzSDNzODZnSEdZc3pnClo4aWJiYWtYNUdFQ0t5N3lRSDZuZ3hFS3pRVGpiampBNWR3S0h0UFhQUnJmamQ1Y2FMczVpcDcxaWxCWEYxU3IKWERIaXUycnFtaC9kVTArWGRMLzNmK2VnVDl6bFQ5YzRyUm84dnZueWNYejFyMnVhRVZ2VExsWHVsb2NpeEVrcgoySjlTMmxveWFUb2tFTnNlMDNpSVdaWnpNNElZcVowOGJOeG9IWCszQXVlWExIUStzRkRKMlhaVVdLSkZHMHUyClp3R2w3YlZpRTFQNXdiQUdtZzJDeDVCN1MrdGQyUEpSV3Frb2VxY3F2RVdCc3RFL1FEcDFpVThCOHpiQXd0Y3IKZHc5TXZ6Q2hBb0dCQVBObzRWMjF6MGp6MWdEb2tlTVN5d3JnL2E4RkJSM2R2Y0xZbWV5VXkybmd3eHVucnFsdwo2U2IrOWdrOGovcXEvc3VQSDhVdzNqSHNKYXdGSnNvTkVqNCt2b1ZSM3UrbE5sTEw5b21rMXBoU0dNdVp0b3huCm5nbUxVbkJUMGI1M3BURkJ5WGsveE5CbElreWdBNlg5T2MreW5na3RqNlRyVnMxUERTdnVJY0s1QW9HQkFQZmoKcEUzR2F6cVFSemx6TjRvTHZmQWJBdktCZ1lPaFNnemxsK0ZLZkhzYWJGNkdudFd1dWVhY1FIWFpYZTA1c2tLcApXN2xYQ3dqQU1iUXI3QmdlazcrOSszZElwL1RnYmZCYnN3Syt6Vng3Z2doeWMrdytXRWExaHByWTZ6YXdxdkFaCkhRU2lMUEd1UGp5WXBQa1E2ZFdEczNmWHJGZ1dlTmd4SkhTZkdaT05Bb0dCQUt5WTF3MUM2U3Y2c3VuTC8vNTcKQ2Z5NTAwaXlqNUZBOWRqZkRDNWt4K1JZMnlDV0ExVGsybjZyVmJ6dzg4czBTeDMrYS9IQW1CM2dMRXBSRU5NKwo5NHVwcENFWEQ3VHdlcGUxUnlrTStKbmp4TzlDSE41c2J2U25sUnBQWlMvZzJRTVhlZ3grK2trbkhXNG1ITkFyCndqMlRrMXBBczFXbkJ0TG9WaGVyY01jSkFvR0JBSTYwSGdJb0Y5SysvRUcyY21LbUg5SDV1dGlnZFU2eHEwK0IKWE0zMWMzUHE0amdJaDZlN3pvbFRxa2d0dWtTMjBraE45dC9ibkI2TmhnK1N1WGVwSXFWZldVUnlMejVwZE9ESgo2V1BMTTYzcDdCR3cwY3RPbU1NYi9VRm5Yd0U4OHlzRlNnOUF6VjdVVUQvU0lDYkI5ZHRVMWh4SHJJK0pZRWdWCkFrZWd6N2lCQW9HQkFJRncrQVFJZUIwM01UL0lCbGswNENQTDJEak0rNDhoVGRRdjgwMDBIQU9mUWJrMEVZUDEKQ2FLR3RDbTg2MXpBZjBzcS81REtZQ0l6OS9HUzNYRk00Qm1rRk9nY1NXVENPNmZmTGdLM3FmQzN4WDJudlpIOQpYZGNKTDQrZndhY0x4c2JJKzhhUWNOVHRtb3pkUjEzQnNmUmIrSGpUL2o3dkdrYlFnSkhCT0syegotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=", TLSClientCert: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVJVENDQWdtZ0F3SUJBZ0lSQVBqTEJxS1lwcWU0ekhQc0dWdFR6T0F3RFFZSktvWklodmNOQVFFTEJRQXcKRWpFUU1BNEdBMVVFQXhNSFoyOXZaQzFqWVRBZUZ3MHhPVEE0TVRBeE9EUTVOREJhRncweU1UQXlNVEF4TnpRdwpNREZhTUJNeEVUQVBCZ05WQkFNVENIQnZiV1Z5YVhWdE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBCk1JSUJDZ0tDQVFFQTY3S2pxbVFZR3EwTVZ0QUNWcGVDbVhtaW5sUWJEUEdMbXNaQVVFd3VlSFFucnQzV3R2cEQKT202QWxhSk1VblcrSHU1NWpqb2thbEtlVmpUS21nWUdicVV6VkRvTWJQRGFIZWtsdGRCVE1HbE9VRnNQNFVKUwpEck80emROK3pvNDI4VFgyUG5HMkZDZFZLR3k0UEU4aWxIYldMY3I4NzFZalY1MWZ3OENMRFg5UFpKTnU4NjFDCkY3VjlpRUptNnNTZlFsbW5oTjhqMytXelZiUFFOeTFXc1I3aTllOWo2M0VxS3QyMlE5T1hMK1dBY0tza29JU20KQ05WUlVBalU4WVJWY2dRSkIrelEzNEFRUGx6ME9wNU8vUU4vTWVkamFGOHdMUytpdi96dmlTOGNxUGJ4bzZzTApxNkZOVGx0ay9Ra3hlQ2VLS1RRZS8za1BZdlFBZG5sNjVRSURBUUFCbzNFd2J6QU9CZ05WSFE4QkFmOEVCQU1DCkE3Z3dIUVlEVlIwbEJCWXdGQVlJS3dZQkJRVUhBd0VHQ0NzR0FRVUZCd01DTUIwR0ExVWREZ1FXQkJRQ1FYbWIKc0hpcS9UQlZUZVhoQ0dpNjhrVy9DakFmQmdOVkhTTUVHREFXZ0JSNTRKQ3pMRlg0T0RTQ1J0dWNBUGZOdVhWegpuREFOQmdrcWhraUc5dzBCQVFzRkFBT0NBZ0VBcm9XL2trMllleFN5NEhaQXFLNDVZaGQ5ay9QVTFiaDlFK1BRCk5jZFgzTUdEY2NDRUFkc1k4dll3NVE1cnhuMGFzcSt3VGFCcGxoYS9rMi9VVW9IQ1RqUVp1Mk94dEF3UTdPaWIKVE1tMEorU3NWT3d4YnFQTW9rK1RqVE16NFdXaFFUTzVwRmNoZDZXZXNCVHlJNzJ0aG1jcDd1c2NLU2h3YktIegpQY2h1QTQ4SzhPdi96WkxmZnduQVNZb3VCczJjd1ZiRDI3ZXZOMzdoMGFzR1BrR1VXdm1PSDduTHNVeTh3TTdqCkNGL3NwMmJmTC9OYVdNclJnTHZBMGZMS2pwWTQrVEpPbkVxQmxPcCsrbHlJTEZMcC9qMHNybjRNUnlKK0t6UTEKR1RPakVtQ1QvVEFtOS9XSThSL0FlYjcwTjEzTytYNEtaOUJHaDAxTzN3T1Vqd3BZZ3lxSnNoRnNRUG50VmMrSQpKQmF4M2VQU3NicUcwTFkzcHdHUkpRNmMrd1lxdGk2Y0tNTjliYlRkMDhCNUk1N1RRTHhNcUoycTFnWmw1R1VUCmVFZGNWRXltMnZmd0NPd0lrbGNBbThxTm5kZGZKV1FabE5VaHNOVWFBMkVINnlDeXdaZm9aak9hSDEwTXowV20KeTNpZ2NSZFQ3Mi9NR2VkZk93MlV0MVVvRFZmdEcxcysrditUQ1lpNmpUQU05dkZPckJ4UGlOeGFkUENHR2NZZAowakZIc2FWOGFPV1dQQjZBQ1JteHdDVDdRTnRTczM2MlpIOUlFWWR4Q00yMDUrZmluVHhkOUcwSmVRRTd2Kyt6CldoeWo2ZmJBWUIxM2wvN1hkRnpNSW5BOGxpekdrVHB2RHMxeTBCUzlwV3ppYmhqbVFoZGZIejdCZGpGTHVvc2wKZzlNZE5sND0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo="}, false},
|
|
{"bad base64 client cert", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.notatld"), TLSClientKey: "!=", TLSClientCert: "!="}, true},
|
|
{"bad one client cert empty", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.notatld"), TLSClientKey: "", TLSClientCert: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVJVENDQWdtZ0F3SUJBZ0lSQVBqTEJxS1lwcWU0ekhQc0dWdFR6T0F3RFFZSktvWklodmNOQVFFTEJRQXcKRWpFUU1BNEdBMVVFQXhNSFoyOXZaQzFqWVRBZUZ3MHhPVEE0TVRBeE9EUTVOREJhRncweU1UQXlNVEF4TnpRdwpNREZhTUJNeEVUQVBCZ05WQkFNVENIQnZiV1Z5YVhWdE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBCk1JSUJDZ0tDQVFFQTY3S2pxbVFZR3EwTVZ0QUNWcGVDbVhtaW5sUWJEUEdMbXNaQVVFd3VlSFFucnQzV3R2cEQKT202QWxhSk1VblcrSHU1NWpqb2thbEtlVmpUS21nWUdicVV6VkRvTWJQRGFIZWtsdGRCVE1HbE9VRnNQNFVKUwpEck80emROK3pvNDI4VFgyUG5HMkZDZFZLR3k0UEU4aWxIYldMY3I4NzFZalY1MWZ3OENMRFg5UFpKTnU4NjFDCkY3VjlpRUptNnNTZlFsbW5oTjhqMytXelZiUFFOeTFXc1I3aTllOWo2M0VxS3QyMlE5T1hMK1dBY0tza29JU20KQ05WUlVBalU4WVJWY2dRSkIrelEzNEFRUGx6ME9wNU8vUU4vTWVkamFGOHdMUytpdi96dmlTOGNxUGJ4bzZzTApxNkZOVGx0ay9Ra3hlQ2VLS1RRZS8za1BZdlFBZG5sNjVRSURBUUFCbzNFd2J6QU9CZ05WSFE4QkFmOEVCQU1DCkE3Z3dIUVlEVlIwbEJCWXdGQVlJS3dZQkJRVUhBd0VHQ0NzR0FRVUZCd01DTUIwR0ExVWREZ1FXQkJRQ1FYbWIKc0hpcS9UQlZUZVhoQ0dpNjhrVy9DakFmQmdOVkhTTUVHREFXZ0JSNTRKQ3pMRlg0T0RTQ1J0dWNBUGZOdVhWegpuREFOQmdrcWhraUc5dzBCQVFzRkFBT0NBZ0VBcm9XL2trMllleFN5NEhaQXFLNDVZaGQ5ay9QVTFiaDlFK1BRCk5jZFgzTUdEY2NDRUFkc1k4dll3NVE1cnhuMGFzcSt3VGFCcGxoYS9rMi9VVW9IQ1RqUVp1Mk94dEF3UTdPaWIKVE1tMEorU3NWT3d4YnFQTW9rK1RqVE16NFdXaFFUTzVwRmNoZDZXZXNCVHlJNzJ0aG1jcDd1c2NLU2h3YktIegpQY2h1QTQ4SzhPdi96WkxmZnduQVNZb3VCczJjd1ZiRDI3ZXZOMzdoMGFzR1BrR1VXdm1PSDduTHNVeTh3TTdqCkNGL3NwMmJmTC9OYVdNclJnTHZBMGZMS2pwWTQrVEpPbkVxQmxPcCsrbHlJTEZMcC9qMHNybjRNUnlKK0t6UTEKR1RPakVtQ1QvVEFtOS9XSThSL0FlYjcwTjEzTytYNEtaOUJHaDAxTzN3T1Vqd3BZZ3lxSnNoRnNRUG50VmMrSQpKQmF4M2VQU3NicUcwTFkzcHdHUkpRNmMrd1lxdGk2Y0tNTjliYlRkMDhCNUk1N1RRTHhNcUoycTFnWmw1R1VUCmVFZGNWRXltMnZmd0NPd0lrbGNBbThxTm5kZGZKV1FabE5VaHNOVWFBMkVINnlDeXdaZm9aak9hSDEwTXowV20KeTNpZ2NSZFQ3Mi9NR2VkZk93MlV0MVVvRFZmdEcxcysrditUQ1lpNmpUQU05dkZPckJ4UGlOeGFkUENHR2NZZAowakZIc2FWOGFPV1dQQjZBQ1JteHdDVDdRTnRTczM2MlpIOUlFWWR4Q00yMDUrZmluVHhkOUcwSmVRRTd2Kyt6CldoeWo2ZmJBWUIxM2wvN1hkRnpNSW5BOGxpekdrVHB2RHMxeTBCUzlwV3ppYmhqbVFoZGZIejdCZGpGTHVvc2wKZzlNZE5sND0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo="}, true},
|
|
{"bad th other client cert empty", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.notatld"), TLSClientKey: "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcGdJQkFBS0NBUUVBNjdLanFtUVlHcTBNVnRBQ1ZwZUNtWG1pbmxRYkRQR0xtc1pBVUV3dWVIUW5ydDNXCnR2cERPbTZBbGFKTVVuVytIdTU1ampva2FsS2VWalRLbWdZR2JxVXpWRG9NYlBEYUhla2x0ZEJUTUdsT1VGc1AKNFVKU0RyTzR6ZE4rem80MjhUWDJQbkcyRkNkVktHeTRQRThpbEhiV0xjcjg3MVlqVjUxZnc4Q0xEWDlQWkpOdQo4NjFDRjdWOWlFSm02c1NmUWxtbmhOOGozK1d6VmJQUU55MVdzUjdpOWU5ajYzRXFLdDIyUTlPWEwrV0FjS3NrCm9JU21DTlZSVUFqVThZUlZjZ1FKQit6UTM0QVFQbHowT3A1Ty9RTi9NZWRqYUY4d0xTK2l2L3p2aVM4Y3FQYngKbzZzTHE2Rk5UbHRrL1FreGVDZUtLVFFlLzNrUFl2UUFkbmw2NVFJREFRQUJBb0lCQVFEQVQ0eXN2V2pSY3pxcgpKcU9SeGFPQTJEY3dXazJML1JXOFhtQWhaRmRTWHV2MkNQbGxhTU1yelBmTG41WUlmaHQzSDNzODZnSEdZc3pnClo4aWJiYWtYNUdFQ0t5N3lRSDZuZ3hFS3pRVGpiampBNWR3S0h0UFhQUnJmamQ1Y2FMczVpcDcxaWxCWEYxU3IKWERIaXUycnFtaC9kVTArWGRMLzNmK2VnVDl6bFQ5YzRyUm84dnZueWNYejFyMnVhRVZ2VExsWHVsb2NpeEVrcgoySjlTMmxveWFUb2tFTnNlMDNpSVdaWnpNNElZcVowOGJOeG9IWCszQXVlWExIUStzRkRKMlhaVVdLSkZHMHUyClp3R2w3YlZpRTFQNXdiQUdtZzJDeDVCN1MrdGQyUEpSV3Frb2VxY3F2RVdCc3RFL1FEcDFpVThCOHpiQXd0Y3IKZHc5TXZ6Q2hBb0dCQVBObzRWMjF6MGp6MWdEb2tlTVN5d3JnL2E4RkJSM2R2Y0xZbWV5VXkybmd3eHVucnFsdwo2U2IrOWdrOGovcXEvc3VQSDhVdzNqSHNKYXdGSnNvTkVqNCt2b1ZSM3UrbE5sTEw5b21rMXBoU0dNdVp0b3huCm5nbUxVbkJUMGI1M3BURkJ5WGsveE5CbElreWdBNlg5T2MreW5na3RqNlRyVnMxUERTdnVJY0s1QW9HQkFQZmoKcEUzR2F6cVFSemx6TjRvTHZmQWJBdktCZ1lPaFNnemxsK0ZLZkhzYWJGNkdudFd1dWVhY1FIWFpYZTA1c2tLcApXN2xYQ3dqQU1iUXI3QmdlazcrOSszZElwL1RnYmZCYnN3Syt6Vng3Z2doeWMrdytXRWExaHByWTZ6YXdxdkFaCkhRU2lMUEd1UGp5WXBQa1E2ZFdEczNmWHJGZ1dlTmd4SkhTZkdaT05Bb0dCQUt5WTF3MUM2U3Y2c3VuTC8vNTcKQ2Z5NTAwaXlqNUZBOWRqZkRDNWt4K1JZMnlDV0ExVGsybjZyVmJ6dzg4czBTeDMrYS9IQW1CM2dMRXBSRU5NKwo5NHVwcENFWEQ3VHdlcGUxUnlrTStKbmp4TzlDSE41c2J2U25sUnBQWlMvZzJRTVhlZ3grK2trbkhXNG1ITkFyCndqMlRrMXBBczFXbkJ0TG9WaGVyY01jSkFvR0JBSTYwSGdJb0Y5SysvRUcyY21LbUg5SDV1dGlnZFU2eHEwK0IKWE0zMWMzUHE0amdJaDZlN3pvbFRxa2d0dWtTMjBraE45dC9ibkI2TmhnK1N1WGVwSXFWZldVUnlMejVwZE9ESgo2V1BMTTYzcDdCR3cwY3RPbU1NYi9VRm5Yd0U4OHlzRlNnOUF6VjdVVUQvU0lDYkI5ZHRVMWh4SHJJK0pZRWdWCkFrZWd6N2lCQW9HQkFJRncrQVFJZUIwM01UL0lCbGswNENQTDJEak0rNDhoVGRRdjgwMDBIQU9mUWJrMEVZUDEKQ2FLR3RDbTg2MXpBZjBzcS81REtZQ0l6OS9HUzNYRk00Qm1rRk9nY1NXVENPNmZmTGdLM3FmQzN4WDJudlpIOQpYZGNKTDQrZndhY0x4c2JJKzhhUWNOVHRtb3pkUjEzQnNmUmIrSGpUL2o3dkdrYlFnSkhCT0syegotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=", TLSClientCert: ""}, true},
|
|
{"good root ca pool", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.notatld"), TLSCustomCA: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUU1RENDQXN5Z0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBREFTTVJBd0RnWURWUVFERXdkbmIyOWsKTFdOaE1CNFhEVEU1TURneE1ERTNOREF3TWxvWERUSXhNREl4TURFM05EQXdNbG93RWpFUU1BNEdBMVVFQXhNSApaMjl2WkMxallUQ0NBaUl3RFFZSktvWklodmNOQVFFQkJRQURnZ0lQQURDQ0Fnb0NnZ0lCQUw3b2VldEovNmNFCkdicTcvanNtcU9FM2VyVE1aRHR0eFM4STVGV1c0TkRXbWNpOE5IdWRMZDhlM1JtOEh6Y09jSjRQL0ErcDVsYmsKTjhySzY4OUlsQzhqM28yaEhSdEk2T21saFY3NEoxaUlIOGtkSXU2V2xPMWtOdUx5dGRrbjhRaytJOUNEWjlGSAorZzhRbnVka0tMUWJkZFdDVXJzUjR4cEcyK0VkNWdua0JJNG4zbmNLMFgvWEZocWhDTEU1eFBaQk5OWktGbHJxCm1lYUl4dHoyc2ZvWVY1NmcwMnNGS1QxSUlMNTVFMG14djRUa2JtSWw5Rk9qZEtCdkhFZnJHeXl5OFRGTHErUzMKTXo2em9xNDhuOEhGMUc5cHBLVk9OMUp0Mks1UWEvV2hpbjVrcWNhYTNwNE0vN2tiNmtxU0tMWG1iN0gyN3kvVQpEYjZDUG01d2lodjA2c1FobXN2MHhuS2hqMm8vQzhlcWxzNzZZWDF1Y2NqMzlmSTRlQ1E4cENFbTlVcDh5ZkkvCkxlYVpXbGE0NEZneWw3N1lyc2MvM0U5dk1hS0ZVeGRjR3VtMXQrNUZZYWpkY0EvTlFreTJBeTJqcHRwVXV1SFUKNnhYSzdEcXY5Z01jQS8zM1VYOFpHZklPRk0rY3FlOTQxaTVPT1hGSHJoRDlqeTRQR2M4Z2kxSTRyK1VXd0tCYgoxSGg1clQ3ckJZK1NLTTBzZmtpQlZ1RU9pbnk2dDF1Z2tEdjY4dXNFWFlIWlZXaWl6b1hmcDVHbjZmckUvd1IxCkRkak13TGEvT2tQTnVEVVQ4eU1GS2hWRnFHcXdHQzY2bys1cjQyMlVwa0s4SHJ5K2tsQ3pUTys3U0RodTJiWk4KUVFGT0NLSVVldnR3bGdabVBNck1BNTZ3dzVSSnNhVnhBZ01CQUFHalJUQkRNQTRHQTFVZER3RUIvd1FFQXdJQgpCakFTQmdOVkhSTUJBZjhFQ0RBR0FRSC9BZ0VBTUIwR0ExVWREZ1FXQkJSNTRKQ3pMRlg0T0RTQ1J0dWNBUGZOCnVYVnpuREFOQmdrcWhraUc5dzBCQVFzRkFBT0NBZ0VBZituUmpBVnZuT0pSckpBQWpKWVY3aVF3bHExUXZYRGcKbHZhY0JoVFJyWFh4OW5GaVRZUzV4MkFMbXZ5WHhubTdIS2VDSUZEclJwOE5MVFkyYjJXR01BcTFxc3JBT0QvegpTNmNSSW1OQ21QNmd0UHNUNDlabzBYajNrZjZyTXBPeHBiSUlnSmZMY056UGZpL25jeC9oRDNBOHl6Zk4wQTZZCnFFd2QvSkZPajdEa3RaQmdlSXZETlJXS0pveEpJRlZ4anJqLzFiVmkxZTRWVjVvWmhOako4SzlyV1FRK1EvK3QKZ3lGK0sycGxDQ1RiRWR6eU9heDY1djh5UDJ5RCs2WkFIRk9sRjI2TnZpUkw4OWJ1VHIwaEpZa0N5VXZ3MmJZaQo4Q3MyWDZkd0NDdXVhZUdVR2VRemszMGxQeUdWSmVKL3ZJMGJRSzlpZ2I5dFozY3d0WHBQdjN6a1B1TDE3d01WCitCMXo2RW1HZVVLNXlTQ0xFWjc2aVliNU0vY3ZjTUVOMWdoeFNIN0FmaDhMS0c0eWszT21SQ253akVqdTFhaWoKZGs3cjJuc0xmYU9KWFBRNU1wMzRYU1ltdTlpTVl0VytMbWZiSDJxMW9vS3dKZDhHNVhhRWRmQmpHUEQ5Q3FkWAphSlh0MDA0cVdsalJOS3p1MFNFRmJ6UldGNHRoeXlUTzE4QVI4eTNHV0Vwak95amdKSzlFeU1sQm9Qa3RYQVVVCjZzTFhqT3ZZU0ovd202NUhxVVZBTTVsRy96WVN3TGdCTDAwc1pJKzVGa0QwblU0Rkx6QWRLV05LWkRXZFVNbUwKVi9lV0ZGNGwwVFBvNTVhM0pUL1BGc2J0RFBLVWxvWVFXeTFybmFqR3J1L0Y5bGRCcHB1bUVUa2FOS2ZWT05Jcgp4cERnc1FhVkVXOD0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo="}, false},
|
|
{"bad root ca pool", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.notatld"), TLSCustomCA: "!"}, true},
|
|
{"good custom ca file", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.notatld"), TLSCustomCAFile: "testdata/ca.pem"}, false},
|
|
{"bad custom ca file", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.notatld"), TLSCustomCAFile: "testdata/404.pem"}, true},
|
|
{"good client certificate files", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.notatld"), TLSClientCertFile: "testdata/example-cert.pem", TLSClientKeyFile: "testdata/example-key.pem"}, false},
|
|
{"bad certificate file", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.notatld"), TLSClientCertFile: "testdata/example-cert-404.pem", TLSClientKeyFile: "testdata/example-key.pem"}, true},
|
|
{"bad key file", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.notatld"), TLSClientCertFile: "testdata/example-cert.pem", TLSClientKeyFile: "testdata/example-key-404.pem"}, true},
|
|
{"good tls server name", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://internal-host-name"), TLSServerName: "httpbin.corp.notatld"}, false},
|
|
{"good kube service account token file", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://internal-host-name"), KubernetesServiceAccountTokenFile: "testdata/kubeserviceaccount.token"}, false},
|
|
{"good kube service account token", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://internal-host-name"), KubernetesServiceAccountToken: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE1OTY1MDk4MjIsImV4cCI6MTYyODA0NTgyMiwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6IkpvaG5ueSIsIlN1cm5hbWUiOiJSb2NrZXQiLCJFbWFpbCI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJSb2xlIjpbIk1hbmFnZXIiLCJQcm9qZWN0IEFkbWluaXN0cmF0b3IiXX0.H0I6ccQrL6sKobsKQj9dqNcLw_INhU9_xJsVyCkgkiY"}, false},
|
|
{"bad kube service account token and file", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://internal-host-name"), KubernetesServiceAccountToken: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE1OTY1MDk4MjIsImV4cCI6MTYyODA0NTgyMiwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6IkpvaG5ueSIsIlN1cm5hbWUiOiJSb2NrZXQiLCJFbWFpbCI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJSb2xlIjpbIk1hbmFnZXIiLCJQcm9qZWN0IEFkbWluaXN0cmF0b3IiXX0.H0I6ccQrL6sKobsKQj9dqNcLw_INhU9_xJsVyCkgkiY", KubernetesServiceAccountTokenFile: "testdata/kubeserviceaccount.token"}, true},
|
|
{"TCP To URLs", Policy{From: "tcp+https://httpbin.corp.example:4000", To: mustParseWeightedURLs(t, "tcp://one.example.com:5000", "tcp://two.example.com:5000")}, false},
|
|
{"mix of TCP and non-TCP To URLs", Policy{From: "tcp+https://httpbin.corp.example:4000", To: mustParseWeightedURLs(t, "https://example.com", "tcp://example.com:5000")}, true},
|
|
{"UDP To URLs", Policy{From: "udp+https://httpbin.corp.example:4000", To: mustParseWeightedURLs(t, "udp://one.example.com:5000", "udp://two.example.com:5000")}, false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := tt.policy.Validate()
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("Validate() error = %v, want %v", err, tt.wantErr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_PolicyValidate_RedirectResponseCode(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var r PolicyRedirect
|
|
p := Policy{
|
|
From: "http://example.com",
|
|
Redirect: &r,
|
|
}
|
|
|
|
cases := []struct {
|
|
Code *int32
|
|
ExpectedError string
|
|
}{
|
|
{nil, ""},
|
|
{proto.Int32(0), "unsupported redirect response code 0"},
|
|
{proto.Int32(100), "unsupported redirect response code 100"},
|
|
{proto.Int32(200), "unsupported redirect response code 200"},
|
|
{proto.Int32(300), "unsupported redirect response code 300"},
|
|
{proto.Int32(301), ""},
|
|
{proto.Int32(302), ""},
|
|
{proto.Int32(303), ""},
|
|
{proto.Int32(304), "unsupported redirect response code 304"},
|
|
{proto.Int32(305), "unsupported redirect response code 305"},
|
|
{proto.Int32(306), "unsupported redirect response code 306"},
|
|
{proto.Int32(307), ""},
|
|
{proto.Int32(308), ""},
|
|
{proto.Int32(309), "unsupported redirect response code 309"},
|
|
{proto.Int32(400), "unsupported redirect response code 400"},
|
|
{proto.Int32(500), "unsupported redirect response code 500"},
|
|
{proto.Int32(600), "unsupported redirect response code 600"},
|
|
}
|
|
|
|
for i := range cases {
|
|
c := &cases[i]
|
|
t.Run(fmt.Sprint(c.Code), func(t *testing.T) {
|
|
r.ResponseCode = c.Code
|
|
err := p.Validate()
|
|
if c.ExpectedError != "" {
|
|
assert.ErrorContains(t, err, c.ExpectedError)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPolicy_String(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
From string
|
|
To []WeightedURL
|
|
want string
|
|
wantFrom string
|
|
}{
|
|
{"good", "https://pomerium.io", []WeightedURL{{URL: url.URL{Scheme: "https", Host: "localhost"}}}, "https://pomerium.io → https://localhost", `"https://pomerium.io"`},
|
|
{"invalid", "https://pomerium.io", nil, "https://pomerium.io → ?", `"https://pomerium.io"`},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
p := &Policy{
|
|
From: tt.From,
|
|
To: tt.To,
|
|
}
|
|
p.Validate()
|
|
if got := p.String(); got != tt.want {
|
|
t.Errorf("Policy.String() = %v, want %v", got, tt.want)
|
|
}
|
|
out, err := json.Marshal(p.From)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if diff := cmp.Diff(string(out), tt.wantFrom); diff != "" {
|
|
t.Errorf("json diff() = %s", diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_PolicyRouteID(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
basePolicy *Policy
|
|
comparePolicy *Policy
|
|
wantSame bool
|
|
}{
|
|
{
|
|
"same",
|
|
&Policy{From: "https://pomerium.io", To: mustParseWeightedURLs(t, "http://localhost"), AllowedUsers: []string{"foo@bar.com"}},
|
|
&Policy{From: "https://pomerium.io", To: mustParseWeightedURLs(t, "http://localhost")},
|
|
true,
|
|
},
|
|
{
|
|
"different from",
|
|
&Policy{From: "https://pomerium.io", To: mustParseWeightedURLs(t, "http://localhost")},
|
|
&Policy{From: "https://notpomerium.io", To: mustParseWeightedURLs(t, "http://localhost")},
|
|
false,
|
|
},
|
|
{
|
|
"different path",
|
|
&Policy{From: "https://pomerium.io", To: mustParseWeightedURLs(t, "http://localhost")},
|
|
&Policy{From: "https://pomerium.io", To: mustParseWeightedURLs(t, "http://localhost"), Path: "/foo"},
|
|
false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
assert.NoError(t, tt.basePolicy.Validate())
|
|
assert.NoError(t, tt.comparePolicy.Validate())
|
|
|
|
id1, err := tt.basePolicy.RouteID()
|
|
assert.NoError(t, err)
|
|
|
|
id2, err := tt.comparePolicy.RouteID()
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, tt.wantSame, id1 == id2)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPolicy_Checksum(t *testing.T) {
|
|
t.Parallel()
|
|
p := &Policy{From: "https://pomerium.io", To: mustParseWeightedURLs(t, "http://localhost"), AllowedUsers: []string{"foo@bar.com"}}
|
|
oldChecksum := p.Checksum()
|
|
p.AllowedUsers = []string{"foo@pomerium.io"}
|
|
newChecksum := p.Checksum()
|
|
|
|
if newChecksum == oldChecksum {
|
|
t.Errorf("Checksum() failed to update old = %d, new = %d", oldChecksum, newChecksum)
|
|
}
|
|
|
|
if newChecksum == 0 || oldChecksum == 0 {
|
|
t.Error("Checksum() not returning data")
|
|
}
|
|
|
|
if p.Checksum() != newChecksum {
|
|
t.Error("Checksum() inconsistent")
|
|
}
|
|
}
|
|
|
|
func TestPolicy_FromToPb(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
t.Run("normal", func(t *testing.T) {
|
|
p := &Policy{
|
|
Name: "ROUTE_NAME",
|
|
Description: "DESCRIPTION",
|
|
LogoURL: "LOGO_URL",
|
|
From: "https://pomerium.io",
|
|
To: mustParseWeightedURLs(t, "http://localhost"),
|
|
AllowedUsers: []string{"foo@bar.com"},
|
|
SubPolicies: []SubPolicy{
|
|
{
|
|
ID: "sub_policy_id",
|
|
Name: "sub_policy",
|
|
Rego: []string{"deny = true"},
|
|
},
|
|
},
|
|
EnableGoogleCloudServerlessAuthentication: true,
|
|
}
|
|
pbPolicy, err := p.ToProto()
|
|
require.NoError(t, err)
|
|
|
|
policyFromPb, err := NewPolicyFromProto(pbPolicy)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, p.Name, policyFromPb.Name)
|
|
assert.Equal(t, p.Description, policyFromPb.Description)
|
|
assert.Equal(t, p.LogoURL, policyFromPb.LogoURL)
|
|
assert.Equal(t, p.From, policyFromPb.From)
|
|
assert.Equal(t, p.To, policyFromPb.To)
|
|
assert.Equal(t, p.AllowedUsers, policyFromPb.AllowedUsers)
|
|
})
|
|
|
|
t.Run("envoy cluster name", func(t *testing.T) {
|
|
p := &Policy{
|
|
From: "https://pomerium.io",
|
|
To: mustParseWeightedURLs(t, "http://localhost"),
|
|
AllowedUsers: []string{"foo@bar.com"},
|
|
}
|
|
|
|
pbPolicy, err := p.ToProto()
|
|
require.NoError(t, err)
|
|
|
|
cases := []struct {
|
|
pbPolicyName string
|
|
pbEnvoyOpts *envoy_config_cluster_v3.Cluster
|
|
expectedPolicyName string
|
|
}{
|
|
{"", nil, ""},
|
|
{"pb-name", nil, "pb-name"},
|
|
{"", &envoy_config_cluster_v3.Cluster{Name: "pb-envoy-name"}, "pb-envoy-name"},
|
|
{"pb-name", &envoy_config_cluster_v3.Cluster{Name: "pb-envoy-name"}, "pb-envoy-name"},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
pbPolicy.Name = tc.pbPolicyName
|
|
pbPolicy.EnvoyOpts = tc.pbEnvoyOpts
|
|
|
|
policyFromPb, err := NewPolicyFromProto(pbPolicy)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tc.expectedPolicyName, policyFromPb.EnvoyOpts.Name)
|
|
}
|
|
})
|
|
|
|
t.Run("redirect route", func(t *testing.T) {
|
|
p := &Policy{
|
|
From: "https://pomerium.io",
|
|
Redirect: &PolicyRedirect{
|
|
HTTPSRedirect: proto.Bool(true),
|
|
},
|
|
}
|
|
|
|
pbPolicy, err := p.ToProto()
|
|
require.NoError(t, err)
|
|
|
|
policyFromProto, err := NewPolicyFromProto(pbPolicy)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, p.Redirect.HTTPSRedirect, policyFromProto.Redirect.HTTPSRedirect)
|
|
})
|
|
|
|
t.Run("JWT issuer format", func(t *testing.T) {
|
|
for f := range knownJWTIssuerFormats {
|
|
p := &Policy{
|
|
From: "https://pomerium.io",
|
|
To: mustParseWeightedURLs(t, "http://localhost"),
|
|
JWTIssuerFormat: f,
|
|
}
|
|
pbPolicy, err := p.ToProto()
|
|
require.NoError(t, err)
|
|
|
|
policyFromPb, err := NewPolicyFromProto(pbPolicy)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, f, policyFromPb.JWTIssuerFormat)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestPolicy_Matches(t *testing.T) {
|
|
t.Run("full", func(t *testing.T) {
|
|
p := &Policy{
|
|
From: "https://www.example.com",
|
|
To: mustParseWeightedURLs(t, "https://localhost"),
|
|
Regex: `/foo`,
|
|
}
|
|
assert.NoError(t, p.Validate())
|
|
|
|
assert.False(t, p.Matches(urlutil.MustParseAndValidateURL(`https://www.example.com/foo/bar`), true),
|
|
"regex should only match full string")
|
|
})
|
|
t.Run("issue2952", func(t *testing.T) {
|
|
p := &Policy{
|
|
From: "https://www.example.com",
|
|
To: mustParseWeightedURLs(t, "https://localhost"),
|
|
Regex: `^\/foo\/bar\/[0-9a-f]\/{0,1}$`,
|
|
}
|
|
assert.NoError(t, p.Validate())
|
|
|
|
assert.True(t, p.Matches(urlutil.MustParseAndValidateURL(`https://www.example.com/foo/bar/0`), true))
|
|
})
|
|
t.Run("issue2592-test2", func(t *testing.T) {
|
|
p := &Policy{
|
|
From: "https://www.example.com",
|
|
To: mustParseWeightedURLs(t, "https://localhost"),
|
|
Regex: `/admin/.*`,
|
|
}
|
|
assert.NoError(t, p.Validate())
|
|
|
|
assert.True(t, p.Matches(urlutil.MustParseAndValidateURL(`https://www.example.com/admin/foo`), true))
|
|
assert.True(t, p.Matches(urlutil.MustParseAndValidateURL(`https://www.example.com/admin/bar`), true))
|
|
})
|
|
t.Run("tcp", func(t *testing.T) {
|
|
p := &Policy{
|
|
From: "tcp+https://proxy.example.com/tcp.example.com:6379",
|
|
To: mustParseWeightedURLs(t, "tcp://localhost:6379"),
|
|
}
|
|
assert.NoError(t, p.Validate())
|
|
|
|
assert.True(t, p.Matches(urlutil.MustParseAndValidateURL(`https://tcp.example.com:6379`), true))
|
|
})
|
|
}
|
|
|
|
func TestPolicy_SortOrder(t *testing.T) {
|
|
ptr := func(i int64) *int64 {
|
|
return &i
|
|
}
|
|
|
|
testCases := []struct {
|
|
name string
|
|
policies []Policy
|
|
wantIDs []string
|
|
}{
|
|
{
|
|
name: "regexPriorityOrder DESC NULLS LAST",
|
|
policies: []Policy{
|
|
{From: "a", Path: "/a", RegexPriorityOrder: nil, ID: "3"},
|
|
{From: "a", Path: "/a", RegexPriorityOrder: ptr(2), ID: "2"},
|
|
{From: "a", Path: "/a", RegexPriorityOrder: ptr(1), ID: "1"},
|
|
},
|
|
wantIDs: []string{"2", "1", "3"},
|
|
},
|
|
{
|
|
name: "from ASC",
|
|
policies: []Policy{
|
|
{From: "", Path: "", RegexPriorityOrder: nil, ID: "B"},
|
|
{From: "", Path: "", RegexPriorityOrder: ptr(0), ID: "C"},
|
|
{From: "source", Path: "/a", RegexPriorityOrder: ptr(1), ID: "A"},
|
|
},
|
|
wantIDs: []string{"C", "B", "A"},
|
|
},
|
|
{
|
|
name: "id ASC",
|
|
policies: []Policy{
|
|
{From: "source", Path: "/a", RegexPriorityOrder: ptr(1), Regex: "regex", Prefix: "prefix", ID: "2"},
|
|
{From: "source", Path: "/a", RegexPriorityOrder: ptr(1), Regex: "regex", Prefix: "prefix", ID: "1"},
|
|
},
|
|
wantIDs: []string{"1", "2"},
|
|
},
|
|
{
|
|
name: "path DESC",
|
|
policies: []Policy{
|
|
{From: "source", Path: "/b", RegexPriorityOrder: ptr(1), ID: "3"},
|
|
{From: "source", Path: "/a", RegexPriorityOrder: nil, ID: "2"},
|
|
{From: "source", Path: "/a", RegexPriorityOrder: ptr(2), ID: "1"},
|
|
},
|
|
wantIDs: []string{"3", "1", "2"},
|
|
},
|
|
}
|
|
|
|
for _, tt := range testCases {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
SortPolicies(tt.policies)
|
|
|
|
gotIDs := make([]string, 0, len(tt.policies))
|
|
for _, entity := range tt.policies {
|
|
gotIDs = append(gotIDs, entity.ID)
|
|
}
|
|
|
|
assert.Equal(t, tt.wantIDs, gotIDs)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPolicy_IsTCP(t *testing.T) {
|
|
p1 := Policy{From: "https://example.com"}
|
|
assert.False(t, p1.IsTCP())
|
|
|
|
p2 := Policy{From: "tcp+https://example.com"}
|
|
assert.True(t, p2.IsTCP())
|
|
}
|
|
|
|
func TestPolicy_IsTCPUpstream(t *testing.T) {
|
|
p1 := Policy{
|
|
From: "tcp+https://example.com:1234",
|
|
To: mustParseWeightedURLs(t, "https://one.example.com", "https://two.example.com"),
|
|
}
|
|
assert.False(t, p1.IsTCPUpstream())
|
|
|
|
p2 := Policy{
|
|
From: "tcp+https://example.com:1234",
|
|
To: mustParseWeightedURLs(t, "tcp://one.example.com:4000", "tcp://two.example.com:4000"),
|
|
}
|
|
assert.True(t, p2.IsTCPUpstream())
|
|
|
|
p3 := Policy{
|
|
From: "tcp+https://example.com:1234",
|
|
}
|
|
assert.False(t, p3.IsTCPUpstream())
|
|
}
|
|
|
|
func mustParseWeightedURLs(t testing.TB, urls ...string) []WeightedURL {
|
|
wu, err := ParseWeightedUrls(urls...)
|
|
require.NoError(t, err)
|
|
return wu
|
|
}
|
|
|
|
func TestRouteID(t *testing.T) {
|
|
randomString := func() string {
|
|
return strings.TrimSuffix(cryptutil.NewRandomStringN(mathrand.IntN(31)+1), "=")
|
|
}
|
|
randomBool := func() bool {
|
|
return mathrand.N(2) == 0
|
|
}
|
|
randomURL := func() *url.URL {
|
|
u, err := url.Parse(fmt.Sprintf("https://%s.example.com/%s?foo=%s#%s",
|
|
randomString(), randomString(), randomString(), randomString()))
|
|
require.NoError(t, err)
|
|
return u
|
|
}
|
|
baseFieldMutators := []func(p *Policy){
|
|
func(p *Policy) { p.From = randomString() },
|
|
func(p *Policy) { p.Prefix = randomString() },
|
|
func(p *Policy) { p.Path = randomString() },
|
|
func(p *Policy) { p.Regex = randomString() },
|
|
}
|
|
toMutators := func(p *Policy) {
|
|
p.To = make(WeightedURLs, mathrand.N(9)+1)
|
|
for i := 0; i < len(p.To); i++ {
|
|
p.To[i] = WeightedURL{URL: *randomURL(), LbWeight: mathrand.Uint32()}
|
|
}
|
|
}
|
|
redirectMutators := []func(p *PolicyRedirect){
|
|
func(p *PolicyRedirect) { p.HTTPSRedirect = randomPtr(10, randomBool()) },
|
|
func(p *PolicyRedirect) { p.SchemeRedirect = randomPtr(10, randomString()) },
|
|
func(p *PolicyRedirect) { p.HostRedirect = randomPtr(10, randomString()) },
|
|
func(p *PolicyRedirect) { p.PortRedirect = randomPtr(10, mathrand.Uint32()) },
|
|
func(p *PolicyRedirect) { p.PathRedirect = randomPtr(10, randomString()) },
|
|
func(p *PolicyRedirect) { p.PrefixRewrite = randomPtr(10, randomString()) },
|
|
func(p *PolicyRedirect) { p.ResponseCode = randomPtr(10, mathrand.Int32()) },
|
|
func(p *PolicyRedirect) { p.StripQuery = randomPtr(10, randomBool()) },
|
|
}
|
|
responseMutators := []func(p *DirectResponse){
|
|
func(p *DirectResponse) { p.Status = mathrand.Int() },
|
|
func(p *DirectResponse) { p.Body = randomString() },
|
|
}
|
|
|
|
t.Run("random policies", func(t *testing.T) {
|
|
hashes := make(map[uint64]struct{}, 10000)
|
|
for i := 0; i < 10000; i++ {
|
|
p := Policy{}
|
|
for _, m := range baseFieldMutators {
|
|
m(&p)
|
|
}
|
|
switch mathrand.IntN(3) {
|
|
case 0:
|
|
toMutators(&p)
|
|
case 1:
|
|
p.Redirect = &PolicyRedirect{}
|
|
for _, m := range redirectMutators {
|
|
m(p.Redirect)
|
|
}
|
|
case 2:
|
|
p.Response = &DirectResponse{}
|
|
for _, m := range responseMutators {
|
|
m(p.Response)
|
|
}
|
|
}
|
|
|
|
routeID, err := p.RouteID()
|
|
require.NoError(t, err)
|
|
hashes[routeID] = struct{}{} // odds of a collision should be pretty low here
|
|
|
|
// check that computing the route id again results in the same value
|
|
routeID2, err := p.RouteID()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, routeID, routeID2)
|
|
}
|
|
assert.Len(t, hashes, 10000)
|
|
})
|
|
t.Run("incremental policy", func(t *testing.T) {
|
|
hashes := make(map[uint64]Policy, 5000)
|
|
|
|
p := Policy{}
|
|
|
|
checkAdd := func(p *Policy) {
|
|
routeID, err := p.RouteID()
|
|
require.NoError(t, err)
|
|
if existing, ok := hashes[routeID]; ok {
|
|
require.Equal(t, existing, *p)
|
|
} else {
|
|
hashes[routeID] = *p
|
|
}
|
|
|
|
// check that computing the route id again results in the same value
|
|
routeID2, err := p.RouteID()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, routeID, routeID2)
|
|
}
|
|
|
|
// to
|
|
toMutators(&p)
|
|
checkAdd(&p)
|
|
|
|
// set base fields
|
|
for _, m := range baseFieldMutators {
|
|
m(&p)
|
|
checkAdd(&p)
|
|
}
|
|
|
|
// redirect
|
|
p.To = nil
|
|
p.Redirect = &PolicyRedirect{}
|
|
for range 1000 {
|
|
for _, m := range redirectMutators {
|
|
m(p.Redirect)
|
|
checkAdd(&p)
|
|
}
|
|
}
|
|
|
|
// update base fields
|
|
for _, m := range baseFieldMutators {
|
|
m(&p)
|
|
checkAdd(&p)
|
|
}
|
|
|
|
// direct response
|
|
p.Redirect = nil
|
|
p.Response = &DirectResponse{}
|
|
for range 1000 {
|
|
for _, m := range responseMutators {
|
|
m(p.Response)
|
|
checkAdd(&p)
|
|
}
|
|
}
|
|
|
|
// update base fields
|
|
for _, m := range baseFieldMutators {
|
|
m(&p)
|
|
checkAdd(&p)
|
|
}
|
|
|
|
// sanity check
|
|
assert.Greater(t, len(hashes), 2000)
|
|
})
|
|
t.Run("field separation", func(t *testing.T) {
|
|
cases := []struct {
|
|
a, b *Policy
|
|
}{
|
|
{
|
|
&Policy{From: "foo", Prefix: "bar"},
|
|
&Policy{From: "f", Prefix: "oobar"},
|
|
},
|
|
{
|
|
&Policy{From: "foo", Prefix: "bar"},
|
|
&Policy{From: "foobar", Prefix: ""},
|
|
},
|
|
{
|
|
&Policy{From: "foobar", Prefix: ""},
|
|
&Policy{From: "", Prefix: "foobar"},
|
|
},
|
|
{
|
|
&Policy{From: "foo", Prefix: "", Path: "bar"},
|
|
&Policy{From: "foo", Prefix: "bar", Path: ""},
|
|
},
|
|
{
|
|
&Policy{From: "", Prefix: "foo", Path: "bar"},
|
|
&Policy{From: "foo", Prefix: "bar", Path: ""},
|
|
},
|
|
{
|
|
&Policy{From: "", Prefix: "foo", Path: "bar"},
|
|
&Policy{From: "foo", Prefix: "", Path: "bar"},
|
|
},
|
|
}
|
|
for _, c := range cases {
|
|
c.a.To = mustParseWeightedURLs(t, "https://foo")
|
|
c.b.To = mustParseWeightedURLs(t, "https://foo")
|
|
}
|
|
|
|
for _, c := range cases {
|
|
a, err := c.a.RouteID()
|
|
require.NoError(t, err)
|
|
b, err := c.b.RouteID()
|
|
require.NoError(t, err)
|
|
assert.NotEqual(t, a, b)
|
|
}
|
|
})
|
|
}
|
|
|
|
func randomPtr[T any](nilChance int, t T) *T {
|
|
if mathrand.N(nilChance) == 0 {
|
|
return nil
|
|
}
|
|
return &t
|
|
}
|