diff --git a/config/options_test.go b/config/options_test.go index 14ba1c8cb..8dcdb5587 100644 --- a/config/options_test.go +++ b/config/options_test.go @@ -964,6 +964,17 @@ func TestOptions_ApplySettings(t *testing.T) { }) } +func TestXXX(t *testing.T) { + dir, _ := os.MkdirTemp("", "asdf") + t.Log(dir) + for i := 1; i <= 100; i++ { + crt, _ := cryptutil.GenerateCertificate(nil, fmt.Sprintf("route%d.localhost.pomerium.io", i)) + crtBytes, keyBytes, _ := cryptutil.EncodeCertificate(crt) + os.WriteFile(fmt.Sprintf("%s/%d.crt", dir, i), crtBytes, 0o644) + os.WriteFile(fmt.Sprintf("%s/%d.key", dir, i), keyBytes, 0o600) + } +} + func TestOptions_GetSetResponseHeaders(t *testing.T) { t.Run("lax", func(t *testing.T) { options := NewDefaultOptions() diff --git a/go.mod b/go.mod index d7f6025a5..0f37e32c8 100644 --- a/go.mod +++ b/go.mod @@ -17,11 +17,6 @@ require ( github.com/caddyserver/certmagic v0.21.3 github.com/cenkalti/backoff/v4 v4.3.0 github.com/cespare/xxhash/v2 v2.3.0 - github.com/charmbracelet/bubbletea v1.1.1 - github.com/charmbracelet/huh v0.6.0 - github.com/charmbracelet/lipgloss v0.13.0 - github.com/charmbracelet/x/ansi v0.2.3 - github.com/charmbracelet/x/exp/teatest v0.0.0-20240913162256-9ef7ff40e654 github.com/cloudflare/circl v1.4.0 github.com/coreos/go-oidc/v3 v3.11.0 github.com/docker/docker v27.2.0+incompatible @@ -48,7 +43,6 @@ require ( github.com/minio/minio-go/v7 v7.0.76 github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c - github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a github.com/natefinch/atomic v1.0.1 github.com/oapi-codegen/runtime v1.1.1 github.com/open-policy-agent/opa v0.68.0 @@ -117,7 +111,6 @@ require ( github.com/agnivade/levenshtein v1.1.1 // indirect github.com/andybalholm/brotli v1.0.5 // indirect github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect - github.com/atotto/clipboard v0.1.4 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.17.31 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13 // indirect @@ -133,16 +126,9 @@ require ( github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.6 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.30.6 // indirect github.com/aws/smithy-go v1.20.4 // indirect - github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect - github.com/aymanbagabas/go-udiff v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/caddyserver/zerossl v0.1.3 // indirect - github.com/catppuccin/go v0.2.0 // indirect github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect - github.com/charmbracelet/bubbles v0.20.0 // indirect - github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b // indirect - github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect - github.com/charmbracelet/x/term v0.2.0 // indirect github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b // indirect github.com/containerd/continuity v0.4.3 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -151,7 +137,6 @@ require ( github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.6.0 // indirect @@ -184,20 +169,15 @@ require ( github.com/klauspost/cpuid/v2 v2.2.8 // indirect github.com/kralicky/go-adaptive-radix-tree v0.0.0-20240624235931-330eb762e74c // indirect github.com/libdns/libdns v0.2.2 // indirect - github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-localereader v0.0.1 // indirect - github.com/mattn/go-runewidth v0.0.16 // indirect github.com/miekg/dns v1.1.59 // indirect github.com/minio/md5-simd v1.1.2 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/term v0.5.0 // indirect github.com/morikuni/aec v1.0.0 // indirect - github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect - github.com/muesli/cancelreader v0.2.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/onsi/ginkgo v1.16.5 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect @@ -211,7 +191,6 @@ require ( github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/prometheus/statsd_exporter v0.22.7 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect - github.com/rivo/uniseg v0.4.7 // indirect github.com/rs/xid v1.6.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect diff --git a/go.sum b/go.sum index ed7da91c6..42c53b229 100644 --- a/go.sum +++ b/go.sum @@ -67,8 +67,6 @@ github.com/DataDog/datadog-go v3.5.0+incompatible h1:AShr9cqkF+taHjyQgcBcQUt/ZNK github.com/DataDog/datadog-go v3.5.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/opencensus-go-exporter-datadog v0.0.0-20200406135749-5c268882acf0 h1:Y6HFfo8UuntPOpfmUmLb0o3MNYKfUuH2aNmvypsDbY4= github.com/DataDog/opencensus-go-exporter-datadog v0.0.0-20200406135749-5c268882acf0/go.mod h1:/VV3EFO/hTNQZHAqaj+CPGy2+ioFrP4EX3iRwozubhQ= -github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= -github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= @@ -96,8 +94,6 @@ github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7D github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= -github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= -github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aws/aws-sdk-go-v2 v1.30.5 h1:mWSRTwQAb0aLE17dSzztCVJWI9+cRMgqebndjwDyK0g= github.com/aws/aws-sdk-go-v2 v1.30.5/go.mod h1:CT+ZPWXbYrci8chcARI3OmI/qgd+f6WtuLOoaIA8PR0= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4 h1:70PVAiL15/aBMh5LThwgXdSQorVr91L127ttckI9QQU= @@ -134,10 +130,6 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.30.6 h1:TrQadF7GcqvQ63kgwEcjlrVc2Fa0 github.com/aws/aws-sdk-go-v2/service/sts v1.30.6/go.mod h1:NXi1dIAGteSaRLqYgarlhP/Ij0cFT+qmCwiJqWh/U5o= github.com/aws/smithy-go v1.20.4 h1:2HK1zBdPgRbjFOHlfeQZfpC4r72MOb9bZkiFwggKO+4= github.com/aws/smithy-go v1.20.4/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= -github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= -github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= -github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= -github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -151,8 +143,6 @@ github.com/caddyserver/certmagic v0.21.3 h1:pqRRry3yuB4CWBVq9+cUqu+Y6E2z8TswbhNx github.com/caddyserver/certmagic v0.21.3/go.mod h1:Zq6pklO9nVRl3DIFUw9gVUfXKdpc/0qwTUAQMBlfgtI= github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= -github.com/catppuccin/go v0.2.0 h1:ktBeIrIP42b/8FGiScP9sgrWOss3lw0Z5SktRoithGA= -github.com/catppuccin/go v0.2.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -165,22 +155,6 @@ github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE= -github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU= -github.com/charmbracelet/bubbletea v1.1.1 h1:KJ2/DnmpfqFtDNVTvYZ6zpPFL9iRCRr0qqKOCvppbPY= -github.com/charmbracelet/bubbletea v1.1.1/go.mod h1:9Ogk0HrdbHolIKHdjfFpyXJmiCzGwy+FesYkZr7hYU4= -github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw= -github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY= -github.com/charmbracelet/x/ansi v0.2.3 h1:VfFN0NUpcjBRd4DnKfRaIRo53KRgey/nhOoEqosGDEY= -github.com/charmbracelet/x/ansi v0.2.3/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= -github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b h1:MnAMdlwSltxJyULnrYbkZpp4k58Co7Tah3ciKhSNo0Q= -github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= -github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 h1:qko3AQ4gK1MTS/de7F5hPGx6/k1u0w4TeYmBFwzYVP4= -github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ= -github.com/charmbracelet/x/exp/teatest v0.0.0-20240913162256-9ef7ff40e654 h1:d+B9FUkxeEex8Q5p4pafFxZbUMzE/TJ64Y5bFDPKcd4= -github.com/charmbracelet/x/exp/teatest v0.0.0-20240913162256-9ef7ff40e654/go.mod h1:NDRRSMP6bZbCs4jyc4i1/4UG4M+0PEiQdpivQgD0Mio= -github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0= -github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -234,8 +208,6 @@ github.com/envoyproxy/go-control-plane v0.13.0/go.mod h1:GRaKG3dwvFoTg4nj7aXdZnv github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM= github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= -github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= -github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= @@ -463,16 +435,12 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kralicky/go-adaptive-radix-tree v0.0.0-20240624235931-330eb762e74c h1:TRkEV8M5PhQU55WI49FKTszEIpFlwZ1wfxcACCRT7SE= github.com/kralicky/go-adaptive-radix-tree v0.0.0-20240624235931-330eb762e74c/go.mod h1:oJwexVSshEat0E3evyKOH6QzN8GFWrhLvEoh8GiJzss= -github.com/kralicky/huh v0.0.0-20240918162853-8fb158fa8e43 h1:0FzXYOXrusgxJNC8e71PuwtahoeLCpqEvk3EM3gHnGA= -github.com/kralicky/huh v0.0.0-20240918162853-8fb158fa8e43/go.mod h1:GGNKeWCeNzKpEOh/OJD8WBwTQjV3prFAtQPpLv+AVwU= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s= github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= -github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= -github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae h1:dIZY4ULFcto4tAFlj1FYZl8ztUZ13bdq+PLY+NOfbyI= github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= @@ -486,10 +454,6 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= -github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= -github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= -github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mholt/acmez/v2 v2.0.2 h1:OmK6xckte2JfKGPz4OAA8aNHTiLvGp8tLzmrd/wfSyw= github.com/mholt/acmez/v2 v2.0.2/go.mod h1:fX4c9r5jYwMyMsC+7tkYRxHibkOTgta5DIFGoe67e1U= @@ -514,12 +478,6 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= -github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= -github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= -github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= -github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a h1:2MaM6YC3mGu54x+RKAA6JiFFHlHDY1UbkxqppT7wYOg= -github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a/go.mod h1:hxSnBBYLK21Vtq/PHd0S2FYCxBXzBua8ov5s1RobyRQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -626,9 +584,6 @@ github.com/prometheus/statsd_exporter v0.22.7/go.mod h1:N/TevpjkIh9ccs6nuzY3jQn9 github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= -github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= @@ -943,7 +898,6 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/internal/zero/api/api.go b/internal/zero/api/api.go index 3c4c76c24..fa846fbdf 100644 --- a/internal/zero/api/api.go +++ b/internal/zero/api/api.go @@ -3,7 +3,6 @@ package zero import ( "bytes" - "compress/gzip" "context" "fmt" "io" @@ -13,6 +12,7 @@ import ( "google.golang.org/grpc/keepalive" "google.golang.org/protobuf/proto" + "github.com/klauspost/compress/zstd" "github.com/pomerium/pomerium/internal/zero/apierror" connect_mux "github.com/pomerium/pomerium/internal/zero/connect-mux" "github.com/pomerium/pomerium/internal/zero/grpcconn" @@ -121,13 +121,16 @@ func (api *API) GetClusterResourceBundles(ctx context.Context) (*cluster_api.Get ) } -func (api *API) ImportConfig(ctx context.Context, cfg *configpb.Config) (*cluster_api.EmptyResponse, error) { +func (api *API) ImportConfig(ctx context.Context, cfg *configpb.Config) (*cluster_api.ImportResponse, error) { data, err := proto.Marshal(cfg) if err != nil { return nil, err } var compressedData bytes.Buffer - w := gzip.NewWriter(&compressedData) + w, err := zstd.NewWriter(&compressedData, zstd.WithEncoderLevel(zstd.SpeedBestCompression)) + if err != nil { + panic(fmt.Sprintf("bug: %v", err)) + } _, err = io.Copy(w, bytes.NewReader(data)) if err != nil { return nil, err diff --git a/internal/zero/cmd/command_import.go b/internal/zero/cmd/command_import.go index fd9c4662c..8703fa2cc 100644 --- a/internal/zero/cmd/command_import.go +++ b/internal/zero/cmd/command_import.go @@ -9,6 +9,7 @@ import ( "github.com/pomerium/pomerium/config" "github.com/pomerium/pomerium/internal/log" "github.com/pomerium/pomerium/pkg/envoy/files" + "github.com/pomerium/pomerium/pkg/zero/importutil" "github.com/rs/zerolog" "github.com/spf13/cobra" ) @@ -69,21 +70,24 @@ func BuildImportCmd() *cobra.Command { cfg := src.GetConfig() client := zeroClientFromContext(cmd.Context()) - quotas, err := client.GetQuotas(cmd.Context()) - if err != nil { - return fmt.Errorf("error getting quotas: %w", err) - } converted := cfg.Options.ToProto() - ui := NewImportUI(converted, quotas) - if err := ui.Run(cmd.Context()); err != nil { - return err + for i, name := range importutil.GenerateRouteNames(converted.Routes) { + converted.Routes[i].Name = name } - ui.ApplySelections(converted) - _, err = client.ImportConfig(cmd.Context(), converted) + resp, err := client.ImportConfig(cmd.Context(), converted) if err != nil { return fmt.Errorf("error importing config: %w", err) } - cmd.PrintErrln("config imported successfully") + if resp.Warnings != nil { + for _, warn := range *resp.Warnings { + cmd.Printf("warning: %s\n", warn) + } + } + if resp.Messages != nil { + for _, msg := range *resp.Messages { + cmd.Printf("✔ %s\n", msg) + } + } return nil }, } diff --git a/internal/zero/cmd/export_test.go b/internal/zero/cmd/export_test.go deleted file mode 100644 index 0290488d8..000000000 --- a/internal/zero/cmd/export_test.go +++ /dev/null @@ -1,7 +0,0 @@ -package cmd - -import "github.com/charmbracelet/huh" - -func (ui *ImportUI) XForm() *huh.Form { - return ui.form -} diff --git a/internal/zero/cmd/import_ui.go b/internal/zero/cmd/import_ui.go deleted file mode 100644 index 65bd4a3ed..000000000 --- a/internal/zero/cmd/import_ui.go +++ /dev/null @@ -1,553 +0,0 @@ -package cmd - -import ( - "bytes" - "context" - "crypto/x509" - "encoding/base64" - "encoding/pem" - "errors" - "fmt" - "os" - "slices" - "strconv" - "strings" - - "github.com/cespare/xxhash/v2" - "github.com/charmbracelet/huh" - "github.com/charmbracelet/lipgloss" - http_connection_managerv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" - "github.com/muesli/termenv" - "github.com/pomerium/pomerium/config" - "github.com/pomerium/pomerium/internal/log" - configpb "github.com/pomerium/pomerium/pkg/grpc/config" - cluster_api "github.com/pomerium/pomerium/pkg/zero/cluster" - "github.com/pomerium/pomerium/pkg/zero/importutil" - "github.com/pomerium/protoutil/fieldmasks" - "github.com/pomerium/protoutil/paths" - "google.golang.org/protobuf/encoding/protojson" - "google.golang.org/protobuf/reflect/protopath" - "google.golang.org/protobuf/reflect/protoreflect" - "google.golang.org/protobuf/types/known/fieldmaskpb" -) - -type onCursorUpdate struct { - Field interface{ Hovered() (string, bool) } -} - -func (u onCursorUpdate) Hash() (uint64, error) { - op, ok := u.Field.Hovered() - if !ok { - return ^uint64(0), nil - } - return xxhash.Sum64String(op), nil -} - -var ( - yellowText = lipgloss.NewStyle().Foreground(lipgloss.ANSIColor(3)) - faintText = lipgloss.NewStyle().Faint(true).UnsetForeground() - redText = lipgloss.NewStyle().Foreground(lipgloss.ANSIColor(1)) -) - -func errText(err error) string { - return redText.Render(fmt.Sprintf("(error: %v)", err)) -} - -func certInfoFromSettingsCertificate(v protoreflect.Value) string { - switch v := v.Interface().(type) { - case protoreflect.List: - buf := bytes.Buffer{} - for i, l := 0, v.Len(); i < l; i++ { - crtBytes := string(v.Get(i).Message().Interface().(*configpb.Settings_Certificate).GetCertBytes()) - buf.WriteString(crtBytes) - if i < l-1 { - buf.WriteRune('\n') - } - } - return certInfoFromBytes(buf.Bytes()) - case protoreflect.Message: - crtBytes := string(v.Interface().(*configpb.Settings_Certificate).GetCertBytes()) - return certInfoFromBytes([]byte(crtBytes)) - default: - panic(fmt.Sprintf("bug: unexpected value type %T", v)) - } -} - -func certInfoFromBase64(v protoreflect.Value) string { - crtBytes, err := base64.StdEncoding.DecodeString(v.String()) - if err != nil { - return errText(err) - } - return certInfoFromBytes(crtBytes) -} - -func certInfoFromBytes(b []byte) string { - if len(b) == 0 { - return faintText.Render("(empty)") - } - block, rest := pem.Decode(b) - if block == nil { - return errText(errors.New("no PEM data found")) - } - extraBlocks := []*pem.Block{} - for len(rest) > 0 { - block, rest = pem.Decode(rest) - if block != nil { - extraBlocks = append(extraBlocks, block) - } - } - blockType := block.Type - var info string - switch block.Type { - case "X509 CRL": - crl, err := x509.ParseRevocationList(block.Bytes) - if err != nil { - return errText(err) - } - info = fmt.Sprintf("%d entries", len(crl.RevokedCertificateEntries)) - default: - cert, err := x509.ParseCertificate(block.Bytes) - if err != nil { - return errText(err) - } - info = *importutil.GenerateCertName(cert) - } - out := yellowText.Render(fmt.Sprintf("(%s: %s)", blockType, info)) - if len(extraBlocks) > 0 { - s := "" - if len(extraBlocks) != 1 { - s = "s" - } - out += faintText.Render(fmt.Sprintf(" ...+%d block%s", len(extraBlocks), s)) - } - return out -} - -func secret(s protoreflect.Value) string { - length := len(s.String()) - return yellowText.Render(fmt.Sprintf("(secret: %d bytes)", length)) -} - -var customSettingsInfoByPath = map[string]func(v protoreflect.Value) string{ - "(pomerium.config.Settings).metrics_certificate": certInfoFromSettingsCertificate, - "(pomerium.config.Settings).metrics_client_ca": certInfoFromBase64, - "(pomerium.config.Settings).certificates": certInfoFromSettingsCertificate, - "(pomerium.config.Settings).certificate_authority": certInfoFromBase64, - "(pomerium.config.Settings).downstream_mtls.ca": certInfoFromBase64, - "(pomerium.config.Settings).downstream_mtls.crl": certInfoFromBase64, - "(pomerium.config.Settings).shared_secret": secret, - "(pomerium.config.Settings).cookie_secret": secret, - "(pomerium.config.Settings).google_cloud_serverless_authentication_service_account": secret, - "(pomerium.config.Settings).idp_client_secret": secret, - "(pomerium.config.Settings).databroker_storage_connection_string": secret, -} - -type ImportHints struct { - // Indicates that the field is ignored during Zero import - Ignored bool - // Indicates that the field is entirely unsupported by Zero, and will likely - // break an existing configuration if imported. If any of these fields are - // selected, an error will be displayed. - Unsupported bool - // An optional note explaining why a field is ignored or unsupported, if - // additional context would be helpful. This message will be user facing. - Note string - // Indicates that the field is treated as a secret, and will be encrypted. - Secret bool -} - -const ( - noteSplitService = "split-service mode" - noteEnterpriseOnly = "enterprise only" - noteFeatureNotYetAvailable = "feature not yet available" -) - -func noteCertificate(n int) string { - suffix := "" - if n != 1 { - suffix = "s" - } - return fmt.Sprintf("+%d certificate%s", n, suffix) -} - -func notePolicy(n int) string { - suffix := "y" - if n != 1 { - suffix = "ies" - } - return fmt.Sprintf("+%d polic%s", n, suffix) -} - -func computeSettingsImportHints(cfg *configpb.Config) map[string]ImportHints { - m := map[string]ImportHints{ - "authenticate_callback_path": {Ignored: true}, - "shared_secret": {Ignored: true}, - "cookie_secret": {Ignored: true}, - "signing_key": {Ignored: true}, - "authenticate_internal_service_url": {Unsupported: true, Note: noteSplitService}, - "authorize_internal_service_url": {Unsupported: true, Note: noteSplitService}, - "databroker_internal_service_url": {Unsupported: true, Note: noteSplitService}, - "derive_tls": {Unsupported: true, Note: noteSplitService}, - "audit_key": {Unsupported: true, Note: noteEnterpriseOnly}, - "primary_color": {Unsupported: true, Note: noteEnterpriseOnly}, - "secondary_color": {Unsupported: true, Note: noteEnterpriseOnly}, - "darkmode_primary_color": {Unsupported: true, Note: noteEnterpriseOnly}, - "darkmode_secondary_color": {Unsupported: true, Note: noteEnterpriseOnly}, - "logo_url": {Unsupported: true, Note: noteEnterpriseOnly}, - "favicon_url": {Unsupported: true, Note: noteEnterpriseOnly}, - "error_message_first_paragraph": {Unsupported: true, Note: noteEnterpriseOnly}, - "use_proxy_protocol": {Unsupported: true, Note: noteFeatureNotYetAvailable}, - "programmatic_redirect_domain_whitelist": {Unsupported: true, Note: noteFeatureNotYetAvailable}, - "grpc_client_timeout": {Unsupported: true, Note: noteFeatureNotYetAvailable}, - "grpc_client_dns_roundrobin": {Unsupported: true, Note: noteFeatureNotYetAvailable}, - "envoy_bind_config_freebind": {Unsupported: true, Note: noteFeatureNotYetAvailable}, - "envoy_bind_config_source_address": {Unsupported: true, Note: noteFeatureNotYetAvailable}, - "google_cloud_serverless_authentication_service_account": {Secret: true}, - "idp_client_secret": {Secret: true}, - "databroker_storage_connection_string": {Secret: true}, - "metrics_certificate": {Unsupported: true, Note: noteFeatureNotYetAvailable}, - "metrics_client_ca": {Unsupported: true, Note: noteFeatureNotYetAvailable}, - // "metrics_certificate": {Note: noteCertificate(1)}, - // "metrics_client_ca": {Note: noteCertificate(1)}, - "certificate_authority": {Note: noteCertificate(1)}, - "certificates": {Note: noteCertificate(len(cfg.GetSettings().GetCertificates()))}, - "downstream_mtls.crl": {Unsupported: true, Note: noteFeatureNotYetAvailable}, - "downstream_mtls.ca": {Note: noteCertificate(1)}, - } - if dm := cfg.GetSettings().GetDownstreamMtls(); dm != nil { - if dm.Enforcement != nil { - switch *dm.Enforcement { - case configpb.MtlsEnforcementMode_POLICY: - case configpb.MtlsEnforcementMode_POLICY_WITH_DEFAULT_DENY: - case configpb.MtlsEnforcementMode_REJECT_CONNECTION: - // this is a special case - zero does not support this mode, but we cannot continue - // with a partial import because it fundamentally changes the behavior of all routes - // and policies in the system - log.Fatal().Msg("downstream mtls enforcement mode 'reject_connection' is not supported") - } - } - } - if cfg.GetSettings().GetServices() != "all" { - m["services"] = ImportHints{Ignored: true, Note: `only "all" is supported`} - } - if cfg.GetSettings().GetCodecType() != http_connection_managerv3.HttpConnectionManager_AUTO { - m["codec_type"] = ImportHints{Ignored: true, Note: `only "auto" is supported`} - } - return m -} - -type ImportUI struct { - form *huh.Form - selectedSettings []string - selectedRoutes []string -} - -func NewImportUI(cfg *configpb.Config, quotas *cluster_api.ConfigQuotas) *ImportUI { - settingsImportHints := computeSettingsImportHints(cfg) - - presentSettings := fieldmasks.Leaves( - fieldmasks.Diff( - config.NewDefaultOptions().ToProto().GetSettings().ProtoReflect(), - cfg.GetSettings().ProtoReflect(), - ), - cfg.Settings.ProtoReflect().Descriptor(), - ) - slices.Sort(presentSettings.Paths) - settingsOptions := huh.NewOptions(presentSettings.Paths...) - - ui := &ImportUI{ - selectedSettings: slices.Clone(presentSettings.Paths), - } - - for i, value := range presentSettings.Paths { - if hints, ok := settingsImportHints[value]; ok { - switch { - case hints.Ignored: - note := "" - if hints.Note != "" { - note = fmt.Sprintf(": %s", hints.Note) - } - settingsOptions[i].Key = fmt.Sprintf("\x1b[9m%s\x1b[29m \x1b[2m(ignored%s)\x1b[22m", settingsOptions[i].Key, note) - ui.selectedSettings[i] = "" - case hints.Unsupported: - note := "" - if hints.Note != "" { - note = fmt.Sprintf(": %s", hints.Note) - } - settingsOptions[i].Key = fmt.Sprintf("\x1b[9m%s\x1b[29m \x1b[2m(unsupported%s)\x1b[22m", settingsOptions[i].Key, note) - ui.selectedSettings[i] = "" - case hints.Secret: - settingsOptions[i].Key += " \x1b[2m(secret)\x1b[22m" - default: - if hints.Note != "" { - settingsOptions[i].Key += fmt.Sprintf(" \x1b[2m(%s)\x1b[22m", hints.Note) - } - } - } - } - ui.selectedSettings = slices.DeleteFunc(ui.selectedSettings, func(s string) bool { - return s == "" - }) - settingsSelect := huh.NewMultiSelect[string](). - Filterable(false). - Title("Import Settings"). - Description("Choose settings to import from your existing configuration"). - Options(settingsOptions...). - Validate(func(selected []string) error { - var unsupportedCount int - for _, s := range selected { - if hints, ok := settingsImportHints[s]; ok && hints.Unsupported { - unsupportedCount++ - } - } - if unsupportedCount == 1 { - return fmt.Errorf("1 selected setting is unsupported") - } else if unsupportedCount > 1 { - return fmt.Errorf("%d selected settings are unsupported", unsupportedCount) - } - return nil - }). - Value(&ui.selectedSettings) - settingsSelect.Focus() - - escapeNoteText := strings.NewReplacer( - "*", "\\*", - "_", "\\_", - "`", "\\`", - ) - settingsNoteDescription := func(value string) string { - path, err := paths.ParseFrom(cfg.Settings.ProtoReflect().Descriptor(), "."+value) - if err != nil { - return errText(err) - } - val, err := paths.Evaluate(cfg.Settings, path) - if err != nil { - return errText(err) - } - if infoFunc, ok := customSettingsInfoByPath[path.String()]; ok { - return escapeNoteText.Replace(infoFunc(val)) - } - return escapeNoteText.Replace(formatValue(path, val)) - } - settingsNote := huh.NewNote(). - Title(fmt.Sprintf("Value: %s", presentSettings.Paths[0])). - TitleFunc(func() string { - field, ok := settingsSelect.Hovered() - if !ok { - return "" - } - return fmt.Sprintf("Value: %s", field) - }, onCursorUpdate{settingsSelect}). - Description(settingsNoteDescription(presentSettings.Paths[0])). - DescriptionFunc(func() string { - field, ok := settingsSelect.Hovered() - if !ok { - return "" - } - return settingsNoteDescription(field) - }, onCursorUpdate{settingsSelect}). - Height(3) - settingsNote.Focus() - - routeNames := make([]string, len(cfg.Routes)) - routesByName := make(map[string]*configpb.Route) - for i, name := range importutil.GenerateRouteNames(cfg.Routes) { - routeNames[i] = name - cfg.Routes[i].Name = name - routesByName[name] = cfg.Routes[i] - } - routeOptions := huh.NewOptions(routeNames...) - for i, name := range routeNames { - if i < quotas.Routes { - ui.selectedRoutes = append(ui.selectedRoutes, name) - } - if n := includedCertificatesInRoute(cfg.Routes[i]); n > 0 { - routeOptions[i].Key += fmt.Sprintf(" \x1b[2m(%s)\x1b[22m", noteCertificate(n)) - } - if n := includedPoliciesInRoute(cfg.Routes[i]); n > 0 { - routeOptions[i].Key += fmt.Sprintf(" \x1b[2m(%s)\x1b[22m", notePolicy(n)) - } - } - - routesSelectDescription := func() string { - return fmt.Sprintf(` -Choose routes to import from your existing configuration. Policies and -certificates associated with selected routes will also be imported. - -Pomerium Zero routes require unique names. We've generated default names -from the contents of each route, but these can always be changed later on. - -Selected: %d/%d`[1:], len(ui.selectedRoutes), quotas.Routes) - } - topMarginLines := 1 + len(strings.Split(routesSelectDescription(), "\n")) - routesSelect := huh.NewMultiSelect[string](). - Filterable(true). - Title("Import Routes"). - Description(routesSelectDescription()). - DescriptionFunc(routesSelectDescription, &ui.selectedRoutes). - Height(min(30, len(cfg.Routes)) + topMarginLines). - Options(routeOptions...). - Validate(func(_ []string) error { - if len(ui.selectedRoutes) > quotas.Routes { - return fmt.Errorf("A maximum of %d routes can be imported", quotas.Routes) //nolint:stylecheck - } - return nil - }). - Value(&ui.selectedRoutes) - - var ( - labelFrom = yellowText.Render(" from: ") - labelPath = yellowText.Render(" path: ") - labelPrefix = yellowText.Render(" prefix: ") - labelRegex = yellowText.Render(" regex: ") - labelTo = yellowText.Render(" to: ") - labelRedirect = yellowText.Render("redirect: ") - labelResponse = yellowText.Render("response: ") - ) - routesNoteDescription := func(selected *configpb.Route) string { - var b strings.Builder - b.WriteString(labelFrom) - b.WriteString(selected.From) - switch { - case selected.Path != "": - b.WriteRune('\n') - b.WriteString(labelPath) - b.WriteString(selected.Path) - case selected.Prefix != "": - b.WriteRune('\n') - b.WriteString(labelPrefix) - b.WriteString(selected.Prefix) - case selected.Regex != "": - b.WriteRune('\n') - b.WriteString(labelRegex) - b.WriteString(selected.Regex) - } - switch { - case len(selected.To) > 0: - b.WriteRune('\n') - b.WriteString(labelTo) - b.WriteString(selected.To[0]) - for _, t := range selected.To[1:] { - b.WriteString(", ") - b.WriteString(t) - } - case selected.Redirect != nil: - b.WriteRune('\n') - b.WriteString(labelRedirect) - b.WriteString(selected.Redirect.String()) - case selected.Response != nil: - b.WriteRune('\n') - b.WriteString(labelResponse) - b.WriteString(fmt.Sprint(selected.Response.Status)) - b.WriteRune(' ') - b.WriteString(strconv.Quote(selected.Response.Body)) - } - return b.String() - } - routesNote := huh.NewNote(). - Title("Route Info"). - Description(routesNoteDescription(cfg.Routes[0])). - DescriptionFunc(func() string { - name, ok := routesSelect.Hovered() - if !ok { - return "" - } - return routesNoteDescription(routesByName[name]) - }, onCursorUpdate{routesSelect}).Height(3) - routesNote.Focus() - - ui.form = huh.NewForm( - huh.NewGroup(settingsSelect, settingsNote), - huh.NewGroup(routesSelect, routesNote), - ).WithTheme(huh.ThemeBase16()) - return ui -} - -func (ui *ImportUI) Run(ctx context.Context) error { - if lipgloss.ColorProfile() == termenv.Ascii && - !termenv.EnvNoColor() && os.Getenv("TERM") != "dumb" { - lipgloss.SetColorProfile(termenv.ANSI) - } - return ui.form.RunWithContext(ctx) -} - -func (ui *ImportUI) ApplySelections(cfg *configpb.Config) { - fieldmasks.ExclusiveKeep(cfg.Settings, &fieldmaskpb.FieldMask{ - Paths: ui.selectedSettings, - }) - cfg.Routes = slices.DeleteFunc(cfg.Routes, func(route *configpb.Route) bool { - return !slices.Contains(ui.selectedRoutes, route.Name) - }) -} - -func includedCertificatesInRoute(route *configpb.Route) int { - n := 0 - if route.TlsClientCert != "" && route.TlsClientKey != "" { - n++ - } - if route.TlsCustomCa != "" { - n++ - } - if route.TlsDownstreamClientCa != "" { - n++ - } - return n -} - -func includedPoliciesInRoute(route *configpb.Route) int { - n := 0 - for _, policy := range route.PplPolicies { - // skip over common generated policies - switch string(policy.Raw) { - case `[{"allow":{"or":[{"accept":true}]}}]`: - case `[{"allow":{"or":[{"authenticated_user":true}]}}]`: - case `[{"allow":{"or":[{"cors_preflight":true}]}}]`: - default: - n++ - } - } - return n -} - -func formatValue(path protopath.Path, val protoreflect.Value) string { - switch vi := val.Interface().(type) { - case protoreflect.Message: - jsonData, err := protojson.Marshal(vi.Interface()) - if err != nil { - return err.Error() - } - return string(jsonData) - case protoreflect.List: - values := []string{} - for i := 0; i < vi.Len(); i++ { - values = append(values, formatValue(path, vi.Get(i))) - } - return renderStringSlice(values) - case protoreflect.Map: - values := []string{} - vi.Range(func(mk protoreflect.MapKey, v protoreflect.Value) bool { - values = append(values, mk.String()+yellowText.Render("=")+formatValue(path, v)) - return true - }) - slices.Sort(values) - return renderStringSlice(values) - case protoreflect.EnumNumber: - var field protoreflect.FieldDescriptor - switch step := path.Index(-1); step.Kind() { - case protopath.FieldAccessStep: - field = step.FieldDescriptor() - case protopath.ListIndexStep, protopath.MapIndexStep: - field = path.Index(-2).FieldDescriptor() - } - if field != nil { - return strings.ToLower(string(field.Enum().Values().ByNumber(vi).Name())) - } - return fmt.Sprint(vi) - default: - return val.String() - } -} - -func renderStringSlice(values []string) string { - return yellowText.Render("[") + strings.Join(values, yellowText.Render(", ")) + yellowText.Render("]") -} diff --git a/internal/zero/cmd/import_ui_test.go b/internal/zero/cmd/import_ui_test.go deleted file mode 100644 index 060d47675..000000000 --- a/internal/zero/cmd/import_ui_test.go +++ /dev/null @@ -1,125 +0,0 @@ -package cmd_test - -import ( - "bytes" - "compress/gzip" - "context" - "embed" - "fmt" - "os" - "path/filepath" - "slices" - "strings" - "testing" - "time" - - tea "github.com/charmbracelet/bubbletea" - "github.com/pomerium/pomerium/config" - "github.com/pomerium/pomerium/pkg/envoy/files" - "github.com/pomerium/pomerium/pkg/zero/cluster" - "github.com/pomerium/pomerium/pkg/zero/importutil" - "github.com/pomerium/protoutil/fieldmasks" - "google.golang.org/protobuf/proto" - - "github.com/charmbracelet/x/ansi" - "github.com/charmbracelet/x/exp/teatest" - "github.com/pomerium/pomerium/internal/zero/cmd" - "github.com/stretchr/testify/require" -) - -//go:embed testdata -var testdata embed.FS - -func TestImportUI(t *testing.T) { - tmp := t.TempDir() - require.NoError(t, os.CopyFS(tmp, testdata)) - dir, err := os.Getwd() - require.NoError(t, err) - defer os.Chdir(dir) - os.Chdir(filepath.Join(tmp, "testdata")) - - src, err := config.NewFileOrEnvironmentSource("config.yaml", files.FullVersion()) - require.NoError(t, err) - - cfgC := make(chan *config.Config, 1) - src.OnConfigChange(context.Background(), func(_ context.Context, cfg *config.Config) { - cfgC <- cfg - }) - if cfg := src.GetConfig(); cfg != nil { - cfgC <- cfg - } - cfg := (<-cfgC).Options.ToProto() - - b, err := proto.Marshal(cfg) - require.NoError(t, err) - var compressed bytes.Buffer - w := gzip.NewWriter(&compressed) - require.NoError(t, err) - w.Write(b) - w.Close() - size := len(compressed.Bytes()) - t.Logf("payload size: %d kB", size/1024) - - ui := cmd.NewImportUI(cfg, &cluster.ConfigQuotas{ - Certificates: 10, - Policies: 10, - Routes: 10, - }) - - form := ui.XForm() - form.SubmitCmd = tea.Quit - form.CancelCmd = tea.Quit - - tm := teatest.NewTestModel(t, form, teatest.WithInitialTermSize(80, 80)) - - presentSettings := fieldmasks.Leaves( - fieldmasks.Diff( - config.NewDefaultOptions().ToProto().GetSettings().ProtoReflect(), - cfg.GetSettings().ProtoReflect(), - ), - cfg.Settings.ProtoReflect().Descriptor(), - ) - slices.Sort(presentSettings.Paths) - - for i, setting := range presentSettings.Paths { - if i > 0 { - tm.Send(tea.KeyMsg{Type: tea.KeyDown}) - } - var foundSelect bool - teatest.WaitFor(t, tm.Output(), func(bts []byte) bool { - str := ansi.Strip(string(bts)) - if !foundSelect { - if strings.Contains(str, fmt.Sprintf("> [•] %s", setting)) || - strings.Contains(str, fmt.Sprintf("> [ ] %s", setting)) { - foundSelect = true - } - return false - } - return strings.Contains(str, fmt.Sprintf("Value: %s", setting)) - }, teatest.WithDuration(1*time.Second), teatest.WithCheckInterval(1*time.Millisecond)) - } - tm.Send(tea.KeyMsg{Type: tea.KeyTab}) - names := importutil.GenerateRouteNames(cfg.Routes) - for i, route := range cfg.Routes { - if i > 0 { - tm.Send(tea.KeyMsg{Type: tea.KeyDown}) - } - var foundSelect bool - teatest.WaitFor(t, tm.Output(), func(bts []byte) bool { - str := ansi.Strip(string(bts)) - if !foundSelect { - if strings.Contains(str, fmt.Sprintf("> [•] %s", names[i])) || - strings.Contains(str, fmt.Sprintf("> [ ] %s", names[i])) { - foundSelect = true - } - return false - } - if i == 0 || cfg.Routes[i-1].From != route.From { - return strings.Contains(str, fmt.Sprintf("from: %s", route.From)) - } - return true - }, teatest.WithDuration(1*time.Second), teatest.WithCheckInterval(1*time.Millisecond)) - } - tm.Send(tea.KeyMsg{Type: tea.KeyEnter}) - tm.WaitFinished(t) -} diff --git a/pkg/zero/cluster/client.gen.go b/pkg/zero/cluster/client.gen.go index 265859053..c1062363e 100644 --- a/pkg/zero/cluster/client.gen.go +++ b/pkg/zero/cluster/client.gen.go @@ -696,7 +696,9 @@ func (r ReportClusterResourceBundleStatusResp) StatusCode() int { type ImportConfigurationResp struct { Body []byte HTTPResponse *http.Response + JSON200 *ImportResponse JSON400 *ErrorResponse + JSON403 *ErrorResponse JSON413 *ErrorResponse JSON500 *ErrorResponse } @@ -1058,6 +1060,13 @@ func ParseImportConfigurationResp(rsp *http.Response) (*ImportConfigurationResp, } switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest ImportResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: var dest ErrorResponse if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -1065,6 +1074,13 @@ func ParseImportConfigurationResp(rsp *http.Response) (*ImportConfigurationResp, } response.JSON400 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: + var dest ErrorResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON403 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 413: var dest ErrorResponse if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -1316,6 +1332,11 @@ func (r *ImportConfigurationResp) GetHTTPResponse() *http.Response { return r.HTTPResponse } +// GetValue implements apierror.APIResponse +func (r *ImportConfigurationResp) GetValue() *ImportResponse { + return r.JSON200 +} + // GetBadRequestError implements apierror.APIResponse func (r *ImportConfigurationResp) GetBadRequestError() (string, bool) { if r.JSON400 == nil { @@ -1324,6 +1345,13 @@ func (r *ImportConfigurationResp) GetBadRequestError() (string, bool) { return r.JSON400.Error, true } +func (r *ImportConfigurationResp) GetForbiddenError() (string, bool) { + if r.JSON403 == nil { + return "", false + } + return r.JSON403.Error, true +} + // GetInternalServerError implements apierror.APIResponse func (r *ImportConfigurationResp) GetInternalServerError() (string, bool) { if r.JSON500 == nil { @@ -1332,14 +1360,6 @@ func (r *ImportConfigurationResp) GetInternalServerError() (string, bool) { return r.JSON500.Error, true } -// GetValue implements apierror.APIResponse -func (r *ImportConfigurationResp) GetValue() *EmptyResponse { - if r.StatusCode()/100 != 2 { - return nil - } - return &EmptyResponse{} -} - // GetHTTPResponse implements apierror.APIResponse func (r *GetQuotasResp) GetHTTPResponse() *http.Response { return r.HTTPResponse diff --git a/pkg/zero/cluster/client_errors.go b/pkg/zero/cluster/client_errors.go index fd6ffa4cc..4944f928e 100644 --- a/pkg/zero/cluster/client_errors.go +++ b/pkg/zero/cluster/client_errors.go @@ -13,6 +13,6 @@ var ( _ apierror.APIResponse[GetBundlesResponse] = (*GetClusterResourceBundlesResp)(nil) _ apierror.APIResponse[DownloadBundleResponse] = (*DownloadClusterResourceBundleResp)(nil) _ apierror.APIResponse[EmptyResponse] = (*ReportClusterResourceBundleStatusResp)(nil) - _ apierror.APIResponse[EmptyResponse] = (*ImportConfigurationResp)(nil) + _ apierror.APIResponse[ImportResponse] = (*ImportConfigurationResp)(nil) _ apierror.APIResponse[ConfigQuotas] = (*GetQuotasResp)(nil) ) diff --git a/pkg/zero/cluster/models.gen.go b/pkg/zero/cluster/models.gen.go index 5656ced00..5f503c698 100644 --- a/pkg/zero/cluster/models.gen.go +++ b/pkg/zero/cluster/models.gen.go @@ -107,6 +107,12 @@ type GetBundlesResponse struct { Bundles []Bundle `json:"bundles"` } +// ImportResponse defines model for ImportResponse. +type ImportResponse struct { + Messages *[]string `json:"messages,omitempty"` + Warnings *[]string `json:"warnings,omitempty"` +} + // ReportUsageRequest defines model for ReportUsageRequest. type ReportUsageRequest struct { Users []ReportUsageUser `json:"users"` diff --git a/pkg/zero/cluster/openapi.yaml b/pkg/zero/cluster/openapi.yaml index 49c4bcf2f..ad967f04a 100644 --- a/pkg/zero/cluster/openapi.yaml +++ b/pkg/zero/cluster/openapi.yaml @@ -196,12 +196,22 @@ paths: responses: "200": description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/ImportResponse" "400": description: Bad Request content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" + "403": + description: Forbidden + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" "413": description: Content Too Large content: @@ -340,6 +350,17 @@ components: description: Error message required: - error + ImportResponse: + type: object + properties: + messages: + type: array + items: + type: string + warnings: + type: array + items: + type: string ExchangeTokenRequest: type: object properties: diff --git a/pkg/zero/cluster/server.gen.go b/pkg/zero/cluster/server.gen.go index c21c07bf3..19678c40c 100644 --- a/pkg/zero/cluster/server.gen.go +++ b/pkg/zero/cluster/server.gen.go @@ -548,12 +548,13 @@ type ImportConfigurationResponseObject interface { VisitImportConfigurationResponse(w http.ResponseWriter) error } -type ImportConfiguration200Response struct { -} +type ImportConfiguration200JSONResponse ImportResponse -func (response ImportConfiguration200Response) VisitImportConfigurationResponse(w http.ResponseWriter) error { +func (response ImportConfiguration200JSONResponse) VisitImportConfigurationResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) - return nil + + return json.NewEncoder(w).Encode(response) } type ImportConfiguration400JSONResponse ErrorResponse @@ -565,6 +566,15 @@ func (response ImportConfiguration400JSONResponse) VisitImportConfigurationRespo return json.NewEncoder(w).Encode(response) } +type ImportConfiguration403JSONResponse ErrorResponse + +func (response ImportConfiguration403JSONResponse) VisitImportConfigurationResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(403) + + return json.NewEncoder(w).Encode(response) +} + type ImportConfiguration413JSONResponse ErrorResponse func (response ImportConfiguration413JSONResponse) VisitImportConfigurationResponse(w http.ResponseWriter) error { diff --git a/pkg/zero/importutil/namegen.go b/pkg/zero/importutil/namegen.go index 468f7fc94..48a8bf503 100644 --- a/pkg/zero/importutil/namegen.go +++ b/pkg/zero/importutil/namegen.go @@ -225,8 +225,10 @@ func differentiateRoutes(subdomain string, routes []*configpb.Route) iter.Seq2[* for _, route := range routes { b.Reset() b.WriteString(subdomain) - b.WriteRune('-') - b.WriteString(name) + if name != "" { + b.WriteRune('-') + b.WriteString(name) + } if route.Prefix != "" { b.WriteString(prefixSuffix) } else if route.Path != "" { diff --git a/pkg/zero/importutil/namegen_test.go b/pkg/zero/importutil/namegen_test.go index ea200c104..1694f6f0a 100644 --- a/pkg/zero/importutil/namegen_test.go +++ b/pkg/zero/importutil/namegen_test.go @@ -367,6 +367,23 @@ func TestGenerateRouteNames(t *testing.T) { yield("test-re-foo-prefix (4)") }), }, + { + name: "duplicate routes", + input: []*configpb.Route{ + {From: "https://route1.localhost.pomerium.io:8443"}, + {From: "https://route1.localhost.pomerium.io:8443"}, + {From: "https://route2.localhost.pomerium.io:8443"}, + {From: "https://route3.localhost.pomerium.io:8443"}, + {From: "https://route4.localhost.pomerium.io:8443"}, + }, + expected: []string{ + "route1", + "route1 (2)", + "route2", + "route3", + "route4", + }, + }, } for _, tc := range cases {