mirror of
https://github.com/pomerium/pomerium.git
synced 2025-07-28 14:08:43 +02:00
initial core-zero import implementation
This commit is contained in:
parent
c011957389
commit
b598d139e5
34 changed files with 3825 additions and 688 deletions
|
@ -3,11 +3,12 @@ package main
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/pomerium/pomerium/config"
|
||||
"github.com/pomerium/pomerium/internal/log"
|
||||
|
@ -19,43 +20,65 @@ import (
|
|||
"github.com/pomerium/pomerium/pkg/envoy/files"
|
||||
)
|
||||
|
||||
var (
|
||||
versionFlag = flag.Bool("version", false, "prints the version")
|
||||
configFile = flag.String("config", "", "Specify configuration file location")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
if *versionFlag {
|
||||
fmt.Println("pomerium:", version.FullVersion())
|
||||
fmt.Println("envoy:", files.FullVersion())
|
||||
return
|
||||
convertOldStyleFlags()
|
||||
var configFile string
|
||||
root := &cobra.Command{
|
||||
Use: "pomerium",
|
||||
Version: fmt.Sprintf("pomerium: %s\nenvoy: %s\n", version.FullVersion(), files.FullVersion()),
|
||||
SilenceUsage: true,
|
||||
}
|
||||
root.AddCommand(zero_cmd.BuildRootCmd())
|
||||
root.PersistentFlags().StringVar(&configFile, "config", "", "Specify configuration file location")
|
||||
|
||||
ctx := context.Background()
|
||||
log.SetLevel(zerolog.InfoLevel)
|
||||
runFn := run
|
||||
if zero_cmd.IsManagedMode(*configFile) {
|
||||
runFn = func(ctx context.Context) error { return zero_cmd.Run(ctx, *configFile) }
|
||||
if zero_cmd.IsManagedMode(configFile) {
|
||||
runFn = zero_cmd.Run
|
||||
}
|
||||
root.RunE = func(_ *cobra.Command, _ []string) error {
|
||||
defer log.Info(ctx).Msg("cmd/pomerium: exiting")
|
||||
return runFn(ctx, configFile)
|
||||
}
|
||||
|
||||
if err := runFn(ctx); err != nil && !errors.Is(err, context.Canceled) {
|
||||
if err := root.ExecuteContext(ctx); err != nil {
|
||||
log.Fatal().Err(err).Msg("cmd/pomerium")
|
||||
}
|
||||
log.Info(ctx).Msg("cmd/pomerium: exiting")
|
||||
}
|
||||
|
||||
func run(ctx context.Context) error {
|
||||
func run(ctx context.Context, configFile string) error {
|
||||
ctx = log.WithContext(ctx, func(c zerolog.Context) zerolog.Context {
|
||||
return c.Str("config_file_source", *configFile).Bool("bootstrap", true)
|
||||
return c.Str("config_file_source", configFile).Bool("bootstrap", true)
|
||||
})
|
||||
|
||||
var src config.Source
|
||||
|
||||
src, err := config.NewFileOrEnvironmentSource(*configFile, files.FullVersion())
|
||||
src, err := config.NewFileOrEnvironmentSource(configFile, files.FullVersion())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return pomerium.Run(ctx, src)
|
||||
}
|
||||
|
||||
// Converts the "-config" and "-version" single-dash style flags to the
|
||||
// equivalent "--config" and "--version" flags compatible with cobra. These
|
||||
// are the only two flags that existed previously, so we don't need to check
|
||||
// for any others.
|
||||
func convertOldStyleFlags() {
|
||||
for i, arg := range os.Args {
|
||||
var found bool
|
||||
if arg == "-config" || strings.HasPrefix(arg, "-config=") {
|
||||
found = true
|
||||
fmt.Fprintln(os.Stderr, "Warning: syntax '-config' is deprecated, use '--config' instead")
|
||||
} else if arg == "-version" {
|
||||
found = true
|
||||
// don't log a warning here, since it could interfere with tools that
|
||||
// parse the -version output
|
||||
}
|
||||
if found {
|
||||
os.Args[i] = "-" + arg
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"encoding/base64"
|
||||
|
||||
"github.com/pomerium/pomerium/pkg/cryptutil"
|
||||
"github.com/pomerium/pomerium/pkg/grpc/crypt"
|
||||
)
|
||||
|
||||
// A PublicKeyEncryptionKeyOptions represents options for a public key encryption key.
|
||||
|
@ -24,3 +25,13 @@ func (o *Options) GetAuditKey() (*cryptutil.PublicKeyEncryptionKey, error) {
|
|||
}
|
||||
return cryptutil.NewPublicKeyEncryptionKeyWithID(o.AuditKey.ID, raw)
|
||||
}
|
||||
|
||||
func (o *PublicKeyEncryptionKeyOptions) ToProto() *crypt.PublicKeyEncryptionKey {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
return &crypt.PublicKeyEncryptionKey{
|
||||
Id: o.ID,
|
||||
Data: []byte(o.Data),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -192,6 +192,30 @@ func (s *DownstreamMTLSSettings) applySettingsProto(
|
|||
s.Enforcement = mtlsEnforcementFromProtoEnum(ctx, p.Enforcement)
|
||||
}
|
||||
|
||||
func (s *DownstreamMTLSSettings) toSettingsProto() *config.DownstreamMtlsSettings {
|
||||
var settings config.DownstreamMtlsSettings
|
||||
if s.CA != "" || s.CAFile != "" {
|
||||
settings.Ca = valueOrFromFileBase64(s.CA, s.CAFile)
|
||||
}
|
||||
if s.CRL != "" || s.CRLFile != "" {
|
||||
settings.Crl = valueOrFromFileBase64(s.CRL, s.CRLFile)
|
||||
}
|
||||
if s.Enforcement != "" {
|
||||
switch s.Enforcement {
|
||||
case MTLSEnforcementPolicy:
|
||||
settings.Enforcement = config.MtlsEnforcementMode_POLICY.Enum()
|
||||
case MTLSEnforcementPolicyWithDefaultDeny:
|
||||
settings.Enforcement = config.MtlsEnforcementMode_POLICY_WITH_DEFAULT_DENY.Enum()
|
||||
case MTLSEnforcementRejectConnection:
|
||||
settings.Enforcement = config.MtlsEnforcementMode_REJECT_CONNECTION.Enum()
|
||||
}
|
||||
}
|
||||
if settings.Ca == nil && settings.Crl == nil && settings.Enforcement == nil {
|
||||
return nil
|
||||
}
|
||||
return &settings
|
||||
}
|
||||
|
||||
func mtlsEnforcementFromProtoEnum(
|
||||
ctx context.Context, mode *config.MtlsEnforcementMode,
|
||||
) MTLSEnforcement {
|
||||
|
|
|
@ -39,6 +39,7 @@ import (
|
|||
"github.com/pomerium/pomerium/pkg/hpke"
|
||||
"github.com/pomerium/pomerium/pkg/identity/oauth"
|
||||
"github.com/pomerium/pomerium/pkg/identity/oauth/apple"
|
||||
"github.com/pomerium/pomerium/pkg/policy/parser"
|
||||
)
|
||||
|
||||
// DisableHeaderKey is the key used to check whether to disable setting header
|
||||
|
@ -1556,6 +1557,233 @@ func (o *Options) ApplySettings(ctx context.Context, certsIndex *cryptutil.Certi
|
|||
})
|
||||
}
|
||||
|
||||
func (o *Options) ToProto() *config.Config {
|
||||
var settings config.Settings
|
||||
copySrcToOptionalDest(&settings.InstallationId, &o.InstallationID)
|
||||
copySrcToOptionalDest(&settings.LogLevel, (*string)(&o.LogLevel))
|
||||
settings.AccessLogFields = toStringList(o.AccessLogFields)
|
||||
settings.AuthorizeLogFields = toStringList(o.AuthorizeLogFields)
|
||||
copySrcToOptionalDest(&settings.ProxyLogLevel, (*string)(&o.ProxyLogLevel))
|
||||
copySrcToOptionalDest(&settings.SharedSecret, valueOrFromFileBase64(o.SharedKey, o.SharedSecretFile))
|
||||
copySrcToOptionalDest(&settings.Services, &o.Services)
|
||||
copySrcToOptionalDest(&settings.Address, &o.Addr)
|
||||
copySrcToOptionalDest(&settings.InsecureServer, &o.InsecureServer)
|
||||
copySrcToOptionalDest(&settings.DnsLookupFamily, &o.DNSLookupFamily)
|
||||
settings.Certificates = getCertificates(o)
|
||||
copySrcToOptionalDest(&settings.HttpRedirectAddr, &o.HTTPRedirectAddr)
|
||||
settings.TimeoutRead = durationpb.New(o.ReadTimeout)
|
||||
settings.TimeoutWrite = durationpb.New(o.WriteTimeout)
|
||||
settings.TimeoutIdle = durationpb.New(o.IdleTimeout)
|
||||
copySrcToOptionalDest(&settings.AuthenticateServiceUrl, &o.AuthenticateURLString)
|
||||
copySrcToOptionalDest(&settings.AuthenticateInternalServiceUrl, &o.AuthenticateInternalURLString)
|
||||
copySrcToOptionalDest(&settings.SignoutRedirectUrl, &o.SignOutRedirectURLString)
|
||||
copySrcToOptionalDest(&settings.AuthenticateCallbackPath, &o.AuthenticateCallbackPath)
|
||||
copySrcToOptionalDest(&settings.CookieName, &o.CookieName)
|
||||
copySrcToOptionalDest(&settings.CookieSecret, valueOrFromFileBase64(o.CookieSecret, o.CookieSecretFile))
|
||||
copySrcToOptionalDest(&settings.CookieDomain, &o.CookieDomain)
|
||||
copySrcToOptionalDest(&settings.CookieHttpOnly, &o.CookieHTTPOnly)
|
||||
settings.CookieExpire = durationpb.New(o.CookieExpire)
|
||||
copySrcToOptionalDest(&settings.CookieSameSite, &o.CookieSameSite)
|
||||
copySrcToOptionalDest(&settings.IdpClientId, &o.ClientID)
|
||||
copySrcToOptionalDest(&settings.IdpClientSecret, valueOrFromFileBase64(o.ClientSecret, o.ClientSecretFile))
|
||||
copySrcToOptionalDest(&settings.IdpProvider, &o.Provider)
|
||||
copySrcToOptionalDest(&settings.IdpProviderUrl, &o.ProviderURL)
|
||||
settings.Scopes = o.Scopes
|
||||
settings.RequestParams = o.RequestParams
|
||||
settings.AuthorizeServiceUrls = o.AuthorizeURLStrings
|
||||
copySrcToOptionalDest(&settings.AuthorizeInternalServiceUrl, &o.AuthorizeInternalURLString)
|
||||
copySrcToOptionalDest(&settings.OverrideCertificateName, &o.OverrideCertificateName)
|
||||
copySrcToOptionalDest(&settings.CertificateAuthority, valueOrFromFileBase64(o.CA, o.CAFile))
|
||||
settings.DeriveTls = o.DeriveInternalDomainCert
|
||||
copySrcToOptionalDest(&settings.SigningKey, valueOrFromFileBase64(o.SigningKey, o.SigningKeyFile))
|
||||
settings.SetResponseHeaders = o.SetResponseHeaders
|
||||
settings.JwtClaimsHeaders = o.JWTClaimsHeaders
|
||||
settings.DefaultUpstreamTimeout = durationpb.New(o.DefaultUpstreamTimeout)
|
||||
copySrcToOptionalDest(&settings.MetricsAddress, &o.MetricsAddr)
|
||||
copySrcToOptionalDest(&settings.MetricsBasicAuth, &o.MetricsBasicAuth)
|
||||
settings.MetricsCertificate = toCertificateOrFromFile(o.MetricsCertificate, o.MetricsCertificateKey, o.MetricsCertificateFile, o.MetricsCertificateKeyFile)
|
||||
copySrcToOptionalDest(&settings.MetricsClientCa, valueOrFromFileBase64(o.MetricsClientCA, o.MetricsClientCAFile))
|
||||
copySrcToOptionalDest(&settings.TracingProvider, &o.TracingProvider)
|
||||
copySrcToOptionalDest(&settings.TracingSampleRate, &o.TracingSampleRate)
|
||||
copySrcToOptionalDest(&settings.TracingDatadogAddress, &o.TracingDatadogAddress)
|
||||
copySrcToOptionalDest(&settings.TracingJaegerCollectorEndpoint, &o.TracingJaegerCollectorEndpoint)
|
||||
copySrcToOptionalDest(&settings.TracingJaegerAgentEndpoint, &o.TracingJaegerAgentEndpoint)
|
||||
copySrcToOptionalDest(&settings.TracingZipkinEndpoint, &o.ZipkinEndpoint)
|
||||
copySrcToOptionalDest(&settings.GrpcAddress, &o.GRPCAddr)
|
||||
settings.GrpcInsecure = o.GRPCInsecure
|
||||
settings.GrpcClientTimeout = durationpb.New(o.GRPCClientTimeout)
|
||||
copySrcToOptionalDest(&settings.GrpcClientDnsRoundrobin, &o.GRPCClientDNSRoundRobin)
|
||||
settings.DatabrokerServiceUrls = o.DataBrokerURLStrings
|
||||
copySrcToOptionalDest(&settings.DatabrokerInternalServiceUrl, &o.DataBrokerInternalURLString)
|
||||
copySrcToOptionalDest(&settings.DatabrokerStorageType, &o.DataBrokerStorageType)
|
||||
copySrcToOptionalDest(&settings.DatabrokerStorageConnectionString, valueOrFromFileRaw(o.DataBrokerStorageConnectionString, o.DataBrokerStorageConnectionStringFile))
|
||||
settings.DownstreamMtls = o.DownstreamMTLS.toSettingsProto()
|
||||
copySrcToOptionalDest(&settings.GoogleCloudServerlessAuthenticationServiceAccount, &o.GoogleCloudServerlessAuthenticationServiceAccount)
|
||||
copySrcToOptionalDest(&settings.UseProxyProtocol, &o.UseProxyProtocol)
|
||||
copySrcToOptionalDest(&settings.Autocert, &o.AutocertOptions.Enable)
|
||||
copySrcToOptionalDest(&settings.AutocertCa, &o.AutocertOptions.CA)
|
||||
copySrcToOptionalDest(&settings.AutocertEmail, &o.AutocertOptions.Email)
|
||||
copySrcToOptionalDest(&settings.AutocertEabKeyId, &o.AutocertOptions.EABKeyID)
|
||||
copySrcToOptionalDest(&settings.AutocertEabMacKey, &o.AutocertOptions.EABMACKey)
|
||||
copySrcToOptionalDest(&settings.AutocertUseStaging, &o.AutocertOptions.UseStaging)
|
||||
copySrcToOptionalDest(&settings.AutocertMustStaple, &o.AutocertOptions.MustStaple)
|
||||
copySrcToOptionalDest(&settings.AutocertDir, &o.AutocertOptions.Folder)
|
||||
copySrcToOptionalDest(&settings.AutocertTrustedCa, &o.AutocertOptions.TrustedCA)
|
||||
copySrcToOptionalDest(&settings.SkipXffAppend, &o.SkipXffAppend)
|
||||
copySrcToOptionalDest(&settings.XffNumTrustedHops, &o.XffNumTrustedHops)
|
||||
settings.ProgrammaticRedirectDomainWhitelist = o.ProgrammaticRedirectDomainWhitelist
|
||||
settings.AuditKey = o.AuditKey.ToProto()
|
||||
if o.CodecType != "" {
|
||||
codecType := o.CodecType.ToEnvoy()
|
||||
settings.CodecType = &codecType
|
||||
}
|
||||
if o.BrandingOptions != nil {
|
||||
primaryColor := o.BrandingOptions.GetPrimaryColor()
|
||||
secondaryColor := o.BrandingOptions.GetSecondaryColor()
|
||||
darkmodePrimaryColor := o.BrandingOptions.GetDarkmodePrimaryColor()
|
||||
darkmodeSecondaryColor := o.BrandingOptions.GetDarkmodeSecondaryColor()
|
||||
logoURL := o.BrandingOptions.GetLogoUrl()
|
||||
faviconURL := o.BrandingOptions.GetFaviconUrl()
|
||||
errorMessageFirstParagraph := o.BrandingOptions.GetErrorMessageFirstParagraph()
|
||||
copySrcToOptionalDest(&settings.PrimaryColor, &primaryColor)
|
||||
copySrcToOptionalDest(&settings.SecondaryColor, &secondaryColor)
|
||||
copySrcToOptionalDest(&settings.DarkmodePrimaryColor, &darkmodePrimaryColor)
|
||||
copySrcToOptionalDest(&settings.DarkmodeSecondaryColor, &darkmodeSecondaryColor)
|
||||
copySrcToOptionalDest(&settings.LogoUrl, &logoURL)
|
||||
copySrcToOptionalDest(&settings.FaviconUrl, &faviconURL)
|
||||
copySrcToOptionalDest(&settings.ErrorMessageFirstParagraph, &errorMessageFirstParagraph)
|
||||
}
|
||||
copyMap(&settings.RuntimeFlags, o.RuntimeFlags, func(k RuntimeFlag, v bool) (string, bool) {
|
||||
return string(k), v
|
||||
})
|
||||
|
||||
routes := make([]*config.Route, 0, o.NumPolicies())
|
||||
for p := range o.GetAllPolicies() {
|
||||
routepb, err := p.ToProto()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
ppl := p.ToPPL()
|
||||
pplIsEmpty := true
|
||||
for _, rule := range ppl.Rules {
|
||||
if rule.Action == parser.ActionAllow &&
|
||||
len(rule.And) > 0 ||
|
||||
len(rule.Nor) > 0 ||
|
||||
len(rule.Not) > 0 ||
|
||||
len(rule.Or) > 0 {
|
||||
pplIsEmpty = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if !pplIsEmpty {
|
||||
raw, err := ppl.MarshalJSON()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
routepb.PplPolicies = append(routepb.PplPolicies, &config.PPLPolicy{
|
||||
Raw: raw,
|
||||
})
|
||||
}
|
||||
routes = append(routes, routepb)
|
||||
}
|
||||
return &config.Config{
|
||||
Settings: &settings,
|
||||
Routes: routes,
|
||||
}
|
||||
}
|
||||
|
||||
func copySrcToOptionalDest[T comparable](dst **T, src *T) {
|
||||
var zero T
|
||||
if *src == zero {
|
||||
*dst = nil
|
||||
} else {
|
||||
if *dst == nil {
|
||||
*dst = src
|
||||
} else {
|
||||
**dst = *src
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func toStringList[T ~string](s []T) *config.Settings_StringList {
|
||||
if len(s) == 0 {
|
||||
return nil
|
||||
}
|
||||
strings := make([]string, len(s))
|
||||
for i, v := range s {
|
||||
strings[i] = string(v)
|
||||
}
|
||||
return &config.Settings_StringList{Values: strings}
|
||||
}
|
||||
|
||||
func toCertificateOrFromFile(
|
||||
cert string, key string,
|
||||
certFile string, keyFile string,
|
||||
) *config.Settings_Certificate {
|
||||
var crt *tls.Certificate
|
||||
if cert == "" && key == "" {
|
||||
if certFile != "" && keyFile != "" {
|
||||
crt, _ = cryptutil.CertificateFromFile(certFile, keyFile)
|
||||
}
|
||||
} else {
|
||||
crt, _ = cryptutil.CertificateFromBase64(cert, key)
|
||||
}
|
||||
if crt == nil {
|
||||
return nil
|
||||
}
|
||||
certBytes, keyBytes, err := cryptutil.EncodeCertificate(crt)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return &config.Settings_Certificate{
|
||||
CertBytes: certBytes,
|
||||
KeyBytes: keyBytes,
|
||||
}
|
||||
}
|
||||
|
||||
func getCertificates(o *Options) []*config.Settings_Certificate {
|
||||
certs, err := o.GetCertificates()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
out := make([]*config.Settings_Certificate, len(certs))
|
||||
for i, crt := range certs {
|
||||
certBytes, keyBytes, err := cryptutil.EncodeCertificate(&crt)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
out[i] = &config.Settings_Certificate{
|
||||
CertBytes: certBytes,
|
||||
KeyBytes: keyBytes,
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func valueOrFromFileRaw(value string, valueFile string) *string {
|
||||
if value != "" {
|
||||
return &value
|
||||
}
|
||||
if valueFile == "" {
|
||||
return &valueFile
|
||||
}
|
||||
data, _ := os.ReadFile(valueFile)
|
||||
dataStr := string(data)
|
||||
return &dataStr
|
||||
}
|
||||
|
||||
func valueOrFromFileBase64(value string, valueFile string) *string {
|
||||
if value != "" {
|
||||
return &value
|
||||
}
|
||||
if valueFile == "" {
|
||||
return &valueFile
|
||||
}
|
||||
data, _ := os.ReadFile(valueFile)
|
||||
encoded := base64.StdEncoding.EncodeToString(data)
|
||||
return &encoded
|
||||
}
|
||||
|
||||
func dataDir() string {
|
||||
homeDir, _ := os.UserHomeDir()
|
||||
if homeDir == "" {
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"math/rand/v2"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
|
@ -17,16 +18,20 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
envoy_config_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/pomerium/csrf"
|
||||
"github.com/pomerium/protoutil/protorand"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/types/known/fieldmaskpb"
|
||||
|
||||
"github.com/pomerium/csrf"
|
||||
"github.com/pomerium/pomerium/internal/testutil"
|
||||
"github.com/pomerium/pomerium/pkg/cryptutil"
|
||||
"github.com/pomerium/pomerium/pkg/grpc/config"
|
||||
configpb "github.com/pomerium/pomerium/pkg/grpc/config"
|
||||
"github.com/pomerium/pomerium/pkg/identity/oauth/apple"
|
||||
)
|
||||
|
||||
|
@ -932,8 +937,8 @@ func TestOptions_ApplySettings(t *testing.T) {
|
|||
xc1, _ := x509.ParseCertificate(cert1.Certificate[0])
|
||||
certsIndex.Add(xc1)
|
||||
|
||||
settings := &config.Settings{
|
||||
Certificates: []*config.Settings_Certificate{
|
||||
settings := &configpb.Settings{
|
||||
Certificates: []*configpb.Settings_Certificate{
|
||||
{CertBytes: encodeCert(cert2)},
|
||||
{CertBytes: encodeCert(cert3)},
|
||||
},
|
||||
|
@ -944,7 +949,7 @@ func TestOptions_ApplySettings(t *testing.T) {
|
|||
|
||||
t.Run("pass_identity_headers", func(t *testing.T) {
|
||||
options := NewDefaultOptions()
|
||||
options.ApplySettings(ctx, nil, &config.Settings{
|
||||
options.ApplySettings(ctx, nil, &configpb.Settings{
|
||||
PassIdentityHeaders: proto.Bool(true),
|
||||
})
|
||||
assert.Equal(t, proto.Bool(true), options.PassIdentityHeaders)
|
||||
|
@ -1364,8 +1369,111 @@ func encodeCert(cert *tls.Certificate) []byte {
|
|||
return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Certificate[0]})
|
||||
}
|
||||
|
||||
func mustParseWeightedURLs(t *testing.T, urls ...string) []WeightedURL {
|
||||
wu, err := ParseWeightedUrls(urls...)
|
||||
require.NoError(t, err)
|
||||
return wu
|
||||
func TestShallowCopyToProto(t *testing.T) {
|
||||
routeGen := protorand.New[*configpb.Route]()
|
||||
routeGen.MaxCollectionElements = 2
|
||||
routeGen.UseGoDurationLimits = true
|
||||
routeGen.ExcludeMask(&fieldmaskpb.FieldMask{
|
||||
Paths: []string{"from", "name", "to", "load_balancing_weights", "redirect", "response", "envoy_opts"},
|
||||
})
|
||||
redirectGen := protorand.New[*configpb.RouteRedirect]()
|
||||
responseGen := protorand.New[*configpb.RouteDirectResponse]()
|
||||
|
||||
randomDomain := func() string {
|
||||
numSegments := rand.IntN(5) + 1
|
||||
segments := make([]string, numSegments)
|
||||
for i := range segments {
|
||||
b := make([]rune, rand.IntN(10)+10)
|
||||
for j := range b {
|
||||
b[j] = rune(rand.IntN(26) + 'a')
|
||||
}
|
||||
segments[i] = string(b)
|
||||
}
|
||||
return strings.Join(segments, ".")
|
||||
}
|
||||
|
||||
newCompleteRoute := func() *configpb.Route {
|
||||
pb, err := routeGen.Gen()
|
||||
|
||||
require.NoError(t, err)
|
||||
pb.From = "https://" + randomDomain()
|
||||
// EnvoyOpts is set to an empty non-nil message during conversion, if nil
|
||||
pb.EnvoyOpts = &envoy_config_cluster_v3.Cluster{}
|
||||
|
||||
switch rand.IntN(3) {
|
||||
case 0:
|
||||
pb.To = make([]string, rand.IntN(3)+1)
|
||||
for i := range pb.To {
|
||||
pb.To[i] = "https://" + randomDomain()
|
||||
}
|
||||
pb.LoadBalancingWeights = make([]uint32, len(pb.To))
|
||||
for i := range pb.LoadBalancingWeights {
|
||||
pb.LoadBalancingWeights[i] = rand.Uint32N(10000) + 1
|
||||
}
|
||||
case 1:
|
||||
pb.Redirect, err = redirectGen.Gen()
|
||||
require.NoError(t, err)
|
||||
case 2:
|
||||
pb.Response, err = responseGen.Gen()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
return pb
|
||||
}
|
||||
|
||||
t.Run("Round Trip", func(t *testing.T) {
|
||||
for range 100 {
|
||||
route := newCompleteRoute()
|
||||
|
||||
policy, err := NewPolicyFromProto(route)
|
||||
require.NoError(t, err)
|
||||
|
||||
route2, err := policy.ToProto()
|
||||
require.NoError(t, err)
|
||||
route2.Name = ""
|
||||
|
||||
testutil.AssertProtoEqual(t, route, route2)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Multiple routes", func(t *testing.T) {
|
||||
for range 100 {
|
||||
route1 := newCompleteRoute()
|
||||
route2 := newCompleteRoute()
|
||||
|
||||
{
|
||||
// create a new policy every time, since reusing the target will mutate
|
||||
// the underlying route
|
||||
policy1, err := NewPolicyFromProto(route1)
|
||||
require.NoError(t, err)
|
||||
target, err := policy1.ToProto()
|
||||
require.NoError(t, err)
|
||||
target.Name = ""
|
||||
testutil.AssertProtoEqual(t, route1, target)
|
||||
}
|
||||
{
|
||||
policy2, err := NewPolicyFromProto(route2)
|
||||
require.NoError(t, err)
|
||||
target, err := policy2.ToProto()
|
||||
require.NoError(t, err)
|
||||
target.Name = ""
|
||||
testutil.AssertProtoEqual(t, route2, target)
|
||||
}
|
||||
{
|
||||
policy1, err := NewPolicyFromProto(route1)
|
||||
require.NoError(t, err)
|
||||
target, err := policy1.ToProto()
|
||||
require.NoError(t, err)
|
||||
target.Name = ""
|
||||
testutil.AssertProtoEqual(t, route1, target)
|
||||
}
|
||||
{
|
||||
policy2, err := NewPolicyFromProto(route2)
|
||||
require.NoError(t, err)
|
||||
target, err := policy2.ToProto()
|
||||
require.NoError(t, err)
|
||||
target.Name = ""
|
||||
testutil.AssertProtoEqual(t, route2, target)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -302,6 +302,7 @@ func NewPolicyFromProto(pb *configpb.Route) (*Policy, error) {
|
|||
}
|
||||
|
||||
p.To = to
|
||||
p.LbWeights = pb.LoadBalancingWeights
|
||||
}
|
||||
|
||||
p.EnvoyOpts = pb.EnvoyOpts
|
||||
|
@ -333,7 +334,7 @@ func NewPolicyFromProto(pb *configpb.Route) (*Policy, error) {
|
|||
Remediation: sp.GetRemediation(),
|
||||
})
|
||||
}
|
||||
return p, p.Validate()
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// ToProto converts the policy to a protobuf type.
|
||||
|
@ -356,6 +357,8 @@ func (p *Policy) ToProto() (*configpb.Route, error) {
|
|||
AllowedUsers: sp.AllowedUsers,
|
||||
AllowedDomains: sp.AllowedDomains,
|
||||
AllowedIdpClaims: sp.AllowedIDPClaims.ToPB(),
|
||||
Explanation: sp.Explanation,
|
||||
Remediation: sp.Remediation,
|
||||
Rego: sp.Rego,
|
||||
})
|
||||
}
|
||||
|
@ -372,6 +375,7 @@ func (p *Policy) ToProto() (*configpb.Route, error) {
|
|||
PrefixRewrite: p.PrefixRewrite,
|
||||
RegexRewritePattern: p.RegexRewritePattern,
|
||||
RegexRewriteSubstitution: p.RegexRewriteSubstitution,
|
||||
RegexPriorityOrder: p.RegexPriorityOrder,
|
||||
CorsAllowPreflight: p.CORSAllowPreflight,
|
||||
AllowPublicUnauthenticatedAccess: p.AllowPublicUnauthenticatedAccess,
|
||||
AllowAnyAuthenticatedUser: p.AllowAnyAuthenticatedUser,
|
||||
|
@ -396,8 +400,22 @@ func (p *Policy) ToProto() (*configpb.Route, error) {
|
|||
PreserveHostHeader: p.PreserveHostHeader,
|
||||
PassIdentityHeaders: p.PassIdentityHeaders,
|
||||
KubernetesServiceAccountToken: p.KubernetesServiceAccountToken,
|
||||
Policies: sps,
|
||||
SetResponseHeaders: p.SetResponseHeaders,
|
||||
EnableGoogleCloudServerlessAuthentication: p.EnableGoogleCloudServerlessAuthentication,
|
||||
Policies: sps,
|
||||
EnvoyOpts: p.EnvoyOpts,
|
||||
SetResponseHeaders: p.SetResponseHeaders,
|
||||
}
|
||||
if p.HostPathRegexRewritePattern != "" {
|
||||
pb.HostPathRegexRewritePattern = proto.String(p.HostPathRegexRewritePattern)
|
||||
}
|
||||
if p.HostPathRegexRewriteSubstitution != "" {
|
||||
pb.HostPathRegexRewriteSubstitution = proto.String(p.HostPathRegexRewriteSubstitution)
|
||||
}
|
||||
if p.HostRewrite != "" {
|
||||
pb.HostRewrite = proto.String(p.HostRewrite)
|
||||
}
|
||||
if p.HostRewriteHeader != "" {
|
||||
pb.HostRewriteHeader = proto.String(p.HostRewriteHeader)
|
||||
}
|
||||
if p.IDPClientID != "" {
|
||||
pb.IdpClientId = proto.String(p.IDPClientID)
|
||||
|
@ -512,10 +530,11 @@ func (p *Policy) Validate() error {
|
|||
return fmt.Errorf("config: couldn't decode custom ca: %w", err)
|
||||
}
|
||||
} else if p.TLSCustomCAFile != "" {
|
||||
_, err := os.Stat(p.TLSCustomCAFile)
|
||||
ca, err := os.ReadFile(p.TLSCustomCAFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("config: couldn't load client ca file: %w", err)
|
||||
}
|
||||
p.TLSCustomCA = base64.StdEncoding.EncodeToString(ca)
|
||||
}
|
||||
|
||||
const clientCADeprecationMsg = "config: %s is deprecated, see https://www.pomerium.com/docs/" +
|
||||
|
|
|
@ -369,3 +369,9 @@ func TestPolicy_IsTCPUpstream(t *testing.T) {
|
|||
}
|
||||
assert.False(t, p3.IsTCPUpstream())
|
||||
}
|
||||
|
||||
func mustParseWeightedURLs(t testing.TB, urls ...string) []WeightedURL {
|
||||
wu, err := ParseWeightedUrls(urls...)
|
||||
require.NoError(t, err)
|
||||
return wu
|
||||
}
|
||||
|
|
34
go.mod
34
go.mod
|
@ -17,6 +17,11 @@ 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
|
||||
|
@ -43,6 +48,7 @@ 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
|
||||
|
@ -51,6 +57,7 @@ require (
|
|||
github.com/peterbourgon/ff/v3 v3.4.0
|
||||
github.com/pomerium/csrf v1.7.0
|
||||
github.com/pomerium/datasource v0.18.2-0.20221108160055-c6134b5ed524
|
||||
github.com/pomerium/protoutil v0.0.0-20240813175624-47b7ac43ff46
|
||||
github.com/pomerium/webauthn v0.0.0-20240603205124-0428df511172
|
||||
github.com/prometheus/client_golang v1.20.2
|
||||
github.com/prometheus/client_model v0.6.1
|
||||
|
@ -59,6 +66,7 @@ require (
|
|||
github.com/rs/cors v1.11.1
|
||||
github.com/rs/zerolog v1.33.0
|
||||
github.com/shirou/gopsutil/v3 v3.24.5
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/spf13/viper v1.19.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/tniswong/go.rfcx v0.0.0-20181019234604-07783c52761f
|
||||
|
@ -82,7 +90,7 @@ require (
|
|||
golang.org/x/net v0.28.0
|
||||
golang.org/x/oauth2 v0.22.0
|
||||
golang.org/x/sync v0.8.0
|
||||
golang.org/x/sys v0.24.0
|
||||
golang.org/x/sys v0.25.0
|
||||
golang.org/x/time v0.6.0
|
||||
google.golang.org/api v0.196.0
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1
|
||||
|
@ -109,6 +117,7 @@ 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
|
||||
|
@ -124,9 +133,16 @@ 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
|
||||
|
@ -135,6 +151,7 @@ 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
|
||||
|
@ -160,23 +177,29 @@ require (
|
|||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||
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/onsi/gomega v1.30.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||
github.com/opencontainers/runc v1.1.14 // indirect
|
||||
|
@ -188,6 +211,7 @@ 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
|
||||
|
@ -197,6 +221,7 @@ require (
|
|||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/sryoya/protorand v0.0.0-20240429201223-e7440656b2a4 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/tchap/go-patricia/v2 v2.3.1 // indirect
|
||||
|
@ -212,13 +237,14 @@ require (
|
|||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
github.com/zeebo/assert v1.3.1 // indirect
|
||||
github.com/zeebo/blake3 v0.2.3 // indirect
|
||||
github.com/zeebo/xxh3 v1.0.2 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/mod v0.20.0 // indirect
|
||||
golang.org/x/text v0.17.0 // indirect
|
||||
golang.org/x/text v0.18.0 // indirect
|
||||
golang.org/x/tools v0.24.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed // indirect
|
||||
|
@ -226,3 +252,5 @@ require (
|
|||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/charmbracelet/huh => github.com/kralicky/huh v0.0.0-20240910153959-781f516d4413
|
||||
|
|
79
go.sum
79
go.sum
|
@ -67,6 +67,8 @@ 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=
|
||||
|
@ -94,6 +96,8 @@ 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=
|
||||
|
@ -130,6 +134,10 @@ 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=
|
||||
|
@ -143,6 +151,8 @@ 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=
|
||||
|
@ -155,6 +165,22 @@ 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=
|
||||
|
@ -171,6 +197,7 @@ github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3
|
|||
github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI=
|
||||
github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
@ -207,6 +234,8 @@ 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=
|
||||
|
@ -257,6 +286,9 @@ github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpv
|
|||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
||||
|
@ -342,6 +374,8 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
|
|||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg=
|
||||
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM=
|
||||
github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA=
|
||||
|
@ -385,6 +419,8 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
|||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||
|
@ -425,12 +461,18 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
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-20240910153959-781f516d4413 h1:PX6KYaLRxKuc67ONo77pUGBBNwFICIrxi8wzDTxNm1U=
|
||||
github.com/kralicky/huh v0.0.0-20240910153959-781f516d4413/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=
|
||||
|
@ -444,6 +486,10 @@ 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=
|
||||
|
@ -468,6 +514,12 @@ 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=
|
||||
|
@ -484,11 +536,13 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
|
|||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/ginkgo/v2 v2.19.1 h1:QXgq3Z8Crl5EL1WBAC98A5sEBHARrAJNzAmMxzLcRF0=
|
||||
github.com/onsi/ginkgo/v2 v2.19.1/go.mod h1:O3DtEWQkPa/F7fBMgmZQKKsluAy8pd3rEQdrjkPb9zA=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
|
||||
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
||||
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||
github.com/open-policy-agent/opa v0.68.0 h1:Jl3U2vXRjwk7JrHmS19U3HZO5qxQRinQbJ2eCJYSqJQ=
|
||||
github.com/open-policy-agent/opa v0.68.0/go.mod h1:5E5SvaPwTpwt2WM177I9Z3eT7qUpmOGjk1ZdHs+TZ4w=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
|
@ -528,6 +582,8 @@ github.com/pomerium/csrf v1.7.0 h1:Qp4t6oyEod3svQtKfJZs589mdUTWKVf7q0PgCKYCshY=
|
|||
github.com/pomerium/csrf v1.7.0/go.mod h1:hAPZV47mEj2T9xFs+ysbum4l7SF1IdrryYaY6PdoIqw=
|
||||
github.com/pomerium/datasource v0.18.2-0.20221108160055-c6134b5ed524 h1:3YQY1sb54tEEbr0L73rjHkpLB0IB6qh3zl1+XQbMLis=
|
||||
github.com/pomerium/datasource v0.18.2-0.20221108160055-c6134b5ed524/go.mod h1:7fGbUYJnU8RcxZJvUvhukOIBv1G7LWDAHMfDxAf5+Y0=
|
||||
github.com/pomerium/protoutil v0.0.0-20240813175624-47b7ac43ff46 h1:NRTg8JOXCxcIA1lAgD74iYud0rbshbWOB3Ou4+Huil8=
|
||||
github.com/pomerium/protoutil v0.0.0-20240813175624-47b7ac43ff46/go.mod h1:QqZmx6ZgPxz18va7kqoT4t/0yJtP7YFIDiT/W2n2fZ4=
|
||||
github.com/pomerium/webauthn v0.0.0-20240603205124-0428df511172 h1:TqoPqRgXSHpn+tEJq6H72iCS5pv66j3rPprThUEZg0E=
|
||||
github.com/pomerium/webauthn v0.0.0-20240603205124-0428df511172/go.mod h1:kBQ45E9LluzW7FP1Scn3esaiS2WVbvNRLMOTHareZNQ=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||
|
@ -570,6 +626,9 @@ 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=
|
||||
|
@ -580,6 +639,7 @@ github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
|||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
||||
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
||||
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||
|
@ -603,11 +663,15 @@ github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
|||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
|
||||
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
|
||||
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
|
||||
github.com/sryoya/protorand v0.0.0-20240429201223-e7440656b2a4 h1:/jKH9ivHOUkahZs3zPfJfOmkXDFB6OdsHZ4W8gyDb/c=
|
||||
github.com/sryoya/protorand v0.0.0-20240429201223-e7440656b2a4/go.mod h1:9a23nlv6vzBeVlQq6JQCjljZ6sfzsB6aha1m5Ly1W2Y=
|
||||
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
|
@ -675,6 +739,8 @@ github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg=
|
|||
github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ=
|
||||
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
|
||||
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
|
@ -877,6 +943,7 @@ 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=
|
||||
|
@ -893,8 +960,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||
|
@ -912,8 +979,8 @@ golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
|||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
|
|
@ -2,18 +2,23 @@
|
|||
package zero
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/zero/apierror"
|
||||
connect_mux "github.com/pomerium/pomerium/internal/zero/connect-mux"
|
||||
"github.com/pomerium/pomerium/internal/zero/grpcconn"
|
||||
token_api "github.com/pomerium/pomerium/internal/zero/token"
|
||||
"github.com/pomerium/pomerium/pkg/fanout"
|
||||
configpb "github.com/pomerium/pomerium/pkg/grpc/config"
|
||||
cluster_api "github.com/pomerium/pomerium/pkg/zero/cluster"
|
||||
connect_api "github.com/pomerium/pomerium/pkg/zero/connect"
|
||||
)
|
||||
|
@ -116,6 +121,30 @@ func (api *API) GetClusterResourceBundles(ctx context.Context) (*cluster_api.Get
|
|||
)
|
||||
}
|
||||
|
||||
func (api *API) ImportConfig(ctx context.Context, cfg *configpb.Config) (*cluster_api.EmptyResponse, error) {
|
||||
data, err := proto.Marshal(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var compressedData bytes.Buffer
|
||||
w := gzip.NewWriter(&compressedData)
|
||||
_, err = io.Copy(w, bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := w.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return apierror.CheckResponse(api.cluster.ImportConfigurationWithBodyWithResponse(ctx,
|
||||
"application/octet-stream",
|
||||
&compressedData,
|
||||
))
|
||||
}
|
||||
|
||||
func (api *API) GetQuotas(ctx context.Context) (*cluster_api.ConfigQuotas, error) {
|
||||
return apierror.CheckResponse(api.cluster.GetQuotasWithResponse(ctx))
|
||||
}
|
||||
|
||||
func (api *API) GetTelemetryConn() *grpc.ClientConn {
|
||||
return api.telemetryConn
|
||||
}
|
||||
|
|
105
internal/zero/cmd/command_import.go
Normal file
105
internal/zero/cmd/command_import.go
Normal file
|
@ -0,0 +1,105 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/pomerium/pomerium/config"
|
||||
"github.com/pomerium/pomerium/internal/log"
|
||||
"github.com/pomerium/pomerium/pkg/envoy/files"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func BuildImportCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "import",
|
||||
Short: "Import an existing configuration to a Zero cluster",
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
configFlag := cmd.InheritedFlags().Lookup("config")
|
||||
var configFile string
|
||||
if configFlag != nil {
|
||||
configFile = configFlag.Value.String()
|
||||
}
|
||||
if configFile == "" {
|
||||
// try looking up what pid 1 is using, we are likely in a container anyway
|
||||
info, err := os.ReadFile("/proc/1/cmdline")
|
||||
if err == nil {
|
||||
args := bytes.Split(info, []byte{0})
|
||||
if len(args) > 0 && strings.Contains(string(args[0]), "pomerium") {
|
||||
for i, arg := range args {
|
||||
if strings.Contains(string(arg), "-config") {
|
||||
if strings.Contains(string(arg), "-config=") {
|
||||
configFile = strings.Split(string(arg), "=")[1]
|
||||
cmd.PrintErrf("detected config file: %s\n", configFile)
|
||||
} else if len(args) > i+1 {
|
||||
configFile = string(args[i+1])
|
||||
cmd.PrintErrf("detected config file: %s\n", configFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// try some common locations
|
||||
if configFile == "" {
|
||||
if _, err := os.Stat("/pomerium/config.yaml"); err == nil {
|
||||
configFile = "/pomerium/config.yaml"
|
||||
} else if _, err := os.Stat("/etc/pomerium/config.yaml"); err == nil {
|
||||
configFile = "/etc/pomerium/config.yaml"
|
||||
} else if _, err := os.Stat("config.yaml"); err == nil {
|
||||
configFile = "config.yaml"
|
||||
}
|
||||
|
||||
if configFile != "" {
|
||||
cmd.PrintErrf("detected config file: %s\n", configFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
if configFile == "" {
|
||||
return fmt.Errorf("no config file provided")
|
||||
}
|
||||
log.SetLevel(zerolog.ErrorLevel)
|
||||
src, err := config.NewFileOrEnvironmentSource(configFile, files.FullVersion())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cfgC := make(chan *config.Config, 1)
|
||||
src.OnConfigChange(cmd.Context(), func(_ context.Context, cfg *config.Config) {
|
||||
cfgC <- cfg
|
||||
})
|
||||
if cfg := src.GetConfig(); cfg != nil {
|
||||
cfgC <- cfg
|
||||
}
|
||||
|
||||
var cfg *config.Config
|
||||
select {
|
||||
case <-cmd.Context().Done():
|
||||
return cmd.Context().Err()
|
||||
case cfg = <-cfgC:
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
ui.ApplySelections(converted)
|
||||
_, err = client.ImportConfig(cmd.Context(), converted)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error importing config: %w", err)
|
||||
}
|
||||
cmd.PrintErrln("config imported successfully")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
71
internal/zero/cmd/command_root.go
Normal file
71
internal/zero/cmd/command_root.go
Normal file
|
@ -0,0 +1,71 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
zero "github.com/pomerium/pomerium/internal/zero/api"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type zeroClientContextKeyType struct{}
|
||||
|
||||
var zeroClientContextKey zeroClientContextKeyType
|
||||
|
||||
func zeroClientFromContext(ctx context.Context) *zero.API {
|
||||
return ctx.Value(zeroClientContextKey).(*zero.API)
|
||||
}
|
||||
|
||||
func BuildRootCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "zero",
|
||||
Short: "Interact with the Pomerium Zero cloud service",
|
||||
PersistentPreRunE: func(cmd *cobra.Command, _ []string) error {
|
||||
configFlag := cmd.InheritedFlags().Lookup("config")
|
||||
var configFile string
|
||||
if configFlag != nil {
|
||||
configFile = configFlag.Value.String()
|
||||
}
|
||||
|
||||
if err := setupLogger(); err != nil {
|
||||
return err
|
||||
}
|
||||
var token string
|
||||
if tokenFlag := cmd.InheritedFlags().Lookup("token"); tokenFlag != nil && tokenFlag.Changed {
|
||||
token = tokenFlag.Value.String()
|
||||
} else {
|
||||
token = getToken(configFile)
|
||||
}
|
||||
if token == "" {
|
||||
return errors.New("no token provided")
|
||||
}
|
||||
|
||||
var clusterAPIEndpoint string
|
||||
if endpointFlag := cmd.InheritedFlags().Lookup("cluster-api-endpoint"); endpointFlag != nil && endpointFlag.Changed {
|
||||
clusterAPIEndpoint = endpointFlag.Value.String()
|
||||
} else {
|
||||
clusterAPIEndpoint = getClusterAPIEndpoint()
|
||||
}
|
||||
|
||||
client, err := zero.NewAPI(cmd.Context(),
|
||||
zero.WithAPIToken(token),
|
||||
zero.WithClusterAPIEndpoint(clusterAPIEndpoint),
|
||||
zero.WithConnectAPIEndpoint(getConnectAPIEndpoint()),
|
||||
zero.WithOTELEndpoint(getOTELAPIEndpoint()),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.SetContext(context.WithValue(cmd.Context(), zeroClientContextKey, client))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.AddCommand(BuildImportCmd())
|
||||
cmd.PersistentFlags().String("config", "", "Specify configuration file location")
|
||||
cmd.PersistentFlags().String("token", "", "Pomerium Zero Token (default: $POMERIUM_ZERO_TOKEN)")
|
||||
cmd.PersistentFlags().String("cluster-api-endpoint", "", "Pomerium Zero Cluster API Endpoint (default: $CLUSTER_API_ENDPOINT)")
|
||||
cmd.PersistentFlags().Lookup("cluster-api-endpoint").Hidden = true
|
||||
|
||||
return cmd
|
||||
}
|
7
internal/zero/cmd/export_test.go
Normal file
7
internal/zero/cmd/export_test.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
package cmd
|
||||
|
||||
import "github.com/charmbracelet/huh"
|
||||
|
||||
func (ui *ImportUI) XForm() *huh.Form {
|
||||
return ui.form
|
||||
}
|
538
internal/zero/cmd/import_ui.go
Normal file
538
internal/zero/cmd/import_ui.go
Normal file
|
@ -0,0 +1,538 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"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{ Cursor() int }
|
||||
}
|
||||
|
||||
func (u onCursorUpdate) Hash() (uint64, error) {
|
||||
return uint64(u.Field.Cursor()), 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(idx int) string {
|
||||
if idx < 0 || idx > len(presentSettings.Paths) {
|
||||
return ""
|
||||
}
|
||||
path, err := paths.ParseFrom(cfg.Settings.ProtoReflect().Descriptor(), "."+presentSettings.Paths[idx])
|
||||
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 {
|
||||
return fmt.Sprintf("Value: %s", presentSettings.Paths[settingsSelect.Cursor()])
|
||||
}, onCursorUpdate{settingsSelect}).
|
||||
Description(settingsNoteDescription(0)).
|
||||
DescriptionFunc(func() string {
|
||||
return settingsNoteDescription(settingsSelect.Cursor())
|
||||
}, onCursorUpdate{settingsSelect}).
|
||||
Height(3)
|
||||
settingsNote.Focus()
|
||||
|
||||
routeNames := make([]string, len(cfg.Routes))
|
||||
for i, name := range importutil.GenerateRouteNames(cfg.Routes) {
|
||||
routeNames[i] = name
|
||||
cfg.Routes[i].Name = name
|
||||
}
|
||||
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(idx int) string {
|
||||
selected := cfg.Routes[idx]
|
||||
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(0)).
|
||||
DescriptionFunc(func() string {
|
||||
return routesNoteDescription(routesSelect.Cursor())
|
||||
}, 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("]")
|
||||
}
|
112
internal/zero/cmd/import_ui_test.go
Normal file
112
internal/zero/cmd/import_ui_test.go
Normal file
|
@ -0,0 +1,112 @@
|
|||
package cmd_test
|
||||
|
||||
import (
|
||||
"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"
|
||||
|
||||
"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()
|
||||
|
||||
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)
|
||||
}
|
28
internal/zero/cmd/testdata/ca.crt
vendored
Normal file
28
internal/zero/cmd/testdata/ca.crt
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIE1zCCAz+gAwIBAgIQZ139cd/paPdkS2JyAu7kEDANBgkqhkiG9w0BAQsFADCB
|
||||
gzEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMSwwKgYDVQQLDCNjYWxl
|
||||
YkBjYWxlYi1wYy1saW51eCAoQ2FsZWIgRG94c2V5KTEzMDEGA1UEAwwqbWtjZXJ0
|
||||
IGNhbGViQGNhbGViLXBjLWxpbnV4IChDYWxlYiBEb3hzZXkpMB4XDTIxMDgxMDE3
|
||||
MzIwOVoXDTMxMDgxMDE3MzIwOVowgYMxHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9w
|
||||
bWVudCBDQTEsMCoGA1UECwwjY2FsZWJAY2FsZWItcGMtbGludXggKENhbGViIERv
|
||||
eHNleSkxMzAxBgNVBAMMKm1rY2VydCBjYWxlYkBjYWxlYi1wYy1saW51eCAoQ2Fs
|
||||
ZWIgRG94c2V5KTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBANbKyMz5
|
||||
MVW6YKdjh1oIN1Mn7PE2pH5SbJSpWxdAGhdBkBkpAa7OxarjH5KVkCTSa7oncla7
|
||||
qNuJZS6mBmoxF+R+cR3jyGdUAYlozl1jlfqLIfC/+g7V7VmOJn98tjB42fatxLl6
|
||||
WPAw1JDNsWtQfhKhbcHut7RsF0rMOOHcwywTR7LOyCmIel1pcmpV4hbVcT6eVwoP
|
||||
HXyJSa9cqaMQ5Xrdogai4IqZZIGLHeLsTVutOgJFXEevlX/QT3sWomEctzh38Js4
|
||||
9DiAPD6d4Y7/CPLYEfk29JQ9NZhpgDsi9hu5FHHZcXwf1IHlw/CBVgn6j+jmvKKz
|
||||
90Ma1oquv3W6dttid/xCcLGu2S+96Tzrykmoy5VacLtVEP41YmoVls91rlo7olpe
|
||||
QWFbnmco739TI/4h+HodolperQERQl7uCnpKVPZ3WokKuRh5pkqkQp/arQjtwcRt
|
||||
G43CrDpbl+uSjMCAxha958eTYvtojTMnvLtsGID1hGXnqlw+5KjKrgRHrQIDAQAB
|
||||
o0UwQzAOBgNVHQ8BAf8EBAMCAgQwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4E
|
||||
FgQUhYZYWIBHyk6ZVTnp3lRt/tyBP00wDQYJKoZIhvcNAQELBQADggGBAA1F/apr
|
||||
l6pNT3Mp/MxhUUgo6usEJCryGQcLRfexyQXGN3huCmIrP55VFa8ETPAtjsr6PMe7
|
||||
7vvEj8eFu2JtKovlQwNewYU9cjAMCVaFiNbrQa20hzhWc2js6dyildE6/DPzbeds
|
||||
KDAxhFNp35SlwtRtKk1SzxJxsqSwjfxI8fp+R/0wO8g0fWTdM2gCpRwYMNwJELEg
|
||||
+dSlvJCwuu+rzxLalzaPF1PMTW72OELal/j5sD+2VytQ4k+HUDbyt2DnQT7YQ3zo
|
||||
q02x2u2sm1WW/o/uh8pjPxkGQqL2mryZs6VH9VCU3QkKNDssNd71lr3wPoE4YRHe
|
||||
UvzD1eDeelzBUFNIpDCjdCsL55yIPqUsr6lmjpBPL0vea33QTMbcsSxu0umGXDbU
|
||||
66juU4Z1jOE0wClIvaO699J+E2gBe1jUN6At6b8BSoZqCqXYoDHGei9RBUdvgqto
|
||||
kVsoJfDI/TFMekYgpL5UVYmLdfgqLPPRP9pQBLDx3mszeAqnvfTICAzfXg==
|
||||
-----END CERTIFICATE-----
|
179
internal/zero/cmd/testdata/config.yaml
vendored
Normal file
179
internal/zero/cmd/testdata/config.yaml
vendored
Normal file
|
@ -0,0 +1,179 @@
|
|||
authenticate_service_url: https://authenticate.localhost.pomerium.io
|
||||
certificate_file: tls.crt
|
||||
certificate_authority_file: ca.crt
|
||||
certificate_key_file: tls.key
|
||||
cookie_secret: UYgnt8bxxK5G2sFaNzyqi5Z+OgF8m2akNc0xdQx718w=
|
||||
databroker_storage_connection_string: postgres://pomerium:password@postgres:5432/test
|
||||
databroker_storage_type: postgres
|
||||
downstream_mtls:
|
||||
crl_file: crl.pem
|
||||
envoy_admin_address: 0.0.0.0:9091
|
||||
google_cloud_serverless_authentication_service_account: ewoiYXV0aF9wcm92aWRlcl94NTA5X2NlcnRfdXJsIjogImh0dHA6Ly9tb2NrLWlkcDo4MDI0IiwKImF1dGhfdXJpIjogImh0dHA6Ly9tb2NrLWlkcDo4MDI0IiwKImNsaWVudF9lbWFpbCI6ICJyZWRhY3RlZEBwb21lcml1bS1yZWRhY3RlZC5pYW0uZ3NlcnZpY2VhY2NvdW50LmNvbSIsCiJjbGllbnRfaWQiOiAiMTAxMjE1OTkwNDU4MDAwMzM0Mzg3IiwKImNsaWVudF94NTA5X2NlcnRfdXJsIjogImh0dHA6Ly9tb2NrLWlkcDo4MDI0IiwKInByaXZhdGVfa2V5IjogIi0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuTUlJRXZRSUJBREFOQmdrcWhraUc5dzBCQVFFRkFBU0NCS2N3Z2dTakFnRUFBb0lCQVFDOEhMQkFJelhrUGVlZ1xubGRVZlJLSzJqUXhTVlpENWcrcXNqQXpwbXJxL0F0bXdlSzFjR2NPdFo2ZU9MK3A4YnJQRHlWaERUMFFsSS9PL1xuRUtnQ09GRnhVRHFvUjgyaVkwNlNhY0FqSG5pNitQTzl0VlJiRlYwdzE0QkRBSlNwQitWdld5bCtGb1BEVi92c1xuWjMxRnRZdytFd3FrYkR4L2thVDl1emYrTEpkbGtmMTRuUVFqOEVreS84ZDNtV0piYi85dGpPYnNhUWdKNUxMeFxuQ1lkSW1rcjc3WDJMTXVEdy8xdHBINjQyR0UyNU5yZ202UUhseUtTZllYbzM4djgzZWJFcWJaVURHK1ppb0FyUFxubXFta2F3VVd3M2VraGo4MFNKZy9USzlQUmFOL1Z2Y0kxUGdBZDdMWnp0VVJlU21UeTVoZDlyNnJPQnhweHduVFxuRHZIa0JuNnZBZ01CQUFFQ2dnRUFCMjhpMEFZVU5TYjFKbldGYkt6cnVVY3R1M3RDTlhvdkpnNkszQmlQVk1rcVxuRFQxWHJKSWdGNVJISE9scjNPc0xFNnU3WHoyY3RkTUw2UHNoaUtUdEl3dEdwaXZnUnBDaUpFc2xtcjJ6aThBV1xuOGVKZXFSTFpFZnNTU0pPWFRHN1JkR3NuNHFIRkowMHMyWlRsY0lIU1B3bkZtK1hqSmk5OVU4RzRYc1VvWG8wclxuR3krMFZDdVU3TThnSUNFSEhzclFPOVhERDNuVDJqaXU1VGpyS3dqdXQzRW1vSnNzSTVicXgzMytPQnU1QnBDUFxuQ1Q0NzNENDNQOXAzcWkvWG5mdnFHU0cyT2o0T2FqVjRmcjBvOUIzS3ZJeGtNZW03V2xJM2p5eTFrQXB5WHFWVFxuYkxrTEZ5V0JOVFdVWjJSLzJ3eG11b0M2bUxadzg3OU1MQ0tNdmsxZG9RS0JnUURobXdHYWZKTnltVGlFUVpSSVxuU3NReDRzZXFmT0tmZ0ZDN29ocUg5Y1JPT3U4SUoxbzdxMnBNMlc0WGlWK1Mzd1RkUEdtY2E2SU9qWDIzaXNWQlxuMnVxTmk5UzRNbkkyL2QyMkdkL0JSOXJ2QncxZUdKb0ticld4MjJmRThRQ0VXVDFBbk8rRHVEMGpDODV5UmxzN1xuYXh6bGFNcnhFdTNMSTlVRTdOdHJkUWlCeVFLQmdRRFZkSTZjZUlWQlQ2Umd2Vkd0OHprTGpQSUZqaFFFSEFJcFxudWhpcmdxcFM2Q1g5Qmx5ZjIrbzQwem1majNoZTVyQ2NFb0I1TXNlTStEZ0ZiY1ZoMmUvTVZuWWlOTnc2SkNEQlxuQlFrRjQwOHBacFNlS1h2TC9veVYva0ltTVRKL3RVRFkwRVh4TXdTUEpCMFdsdGJXcmVWSUhvcGlnWFJDYmFleVxudUJIVkJ2LzR0d0tCZ0h3SHVlUHk1U1UxczJxU216RDdXYzJMUGZZdTNuQ09ITlJyRkdiMjZNdVJmdVJlcmk3clxuMkc4VGdvRVNGeWNwMFFUSU44KzFKTTBYWUt4TmNKRDZCOFYxd0tiYnBRc3ltbmVJMWdqdXRpQi9JZ3cvUGtES1xuQ0w0VlA0RjRkYTVOV1cxeVdnTnlnTG9KdlovNXFpS0tpc0pjMEdXazRIS3o2bUxnek9qUTJMSnhBb0dCQUxIWlxuZk4yWWVZYnlZY2FNMTFwMVZpbHVsVlRWalkzaS9GWmlEUjRTTC9JR0pXak4vU3pnNGlYWXNLRm11K2R1bE9abFxuY0JBTHBFS3JxcG16WFl0ck42YnN2MTgrNWVPM3FHYksyRHJFcTNlV1ZldjJLb1RNb2J4ejdnKytYQklXSm1MQVxuSGhhYTZJaVBrWUQ1eXlWeUhLRGJlWGdiM285ZXFDUjd3N2ZZTGp5L0FvR0FJNEQrTUZraXZ3VUY3aHFmNWVkU1xuS3JsdHdtb2RIaXFYTmJWa3diVzFBRlBKYmlZYWk0WUZmSzRJQWJpZi9ZbXhmOUc3OGFPa3I5WnBDSXpPa0RQWlxuWXBFd1FHV3NBaEVsQ0Z2YzhFLzVkSEVTU3ArdFd0UCtObHVpbXBGcWlEZzMvU1VuTXdPMnhIMG5oTGEwemVqaFxuZ21MaDR3L0NjUHliOVp5WGNlV1UvblU9XG4tLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tXG4iLAoicHJpdmF0ZV9rZXlfaWQiOiAiZTA3ZjdjOTM4NzBjN2UwM2Y4ODM1NjBlY2Q4ZmQwZjRkMjdiMDA4MSIsCiJwcm9qZWN0X2lkIjogInBvbWVyaXVtLXJlZGFjdGVkIiwKInRva2VuX3VyaSI6ICJodHRwOi8vbW9jay1pZHA6ODAyNC90b2tlbiIsCiJ0eXBlIjogInNlcnZpY2VfYWNjb3VudCIKfQ==
|
||||
idp_client_id: CLIENT_ID
|
||||
idp_client_secret: CLIENT_SECRET
|
||||
idp_provider: oidc
|
||||
idp_provider_url: https://mock-idp.localhost.pomerium.io/
|
||||
jwt_claims_headers: email,groups,user
|
||||
log_level: debug
|
||||
routes:
|
||||
- allow_public_unauthenticated_access: true
|
||||
from: https://mock-idp.localhost.pomerium.io
|
||||
preserve_host_header: true
|
||||
to: http://mock-idp:8024
|
||||
- allow_public_unauthenticated_access: true
|
||||
from: https://envoy.localhost.pomerium.io
|
||||
to: http://localhost:9901
|
||||
- allow_any_authenticated_user: true
|
||||
from: https://verify.localhost.pomerium.io
|
||||
pass_identity_headers: true
|
||||
to: http://verify:80
|
||||
- allow_public_unauthenticated_access: true
|
||||
allow_websockets: true
|
||||
from: https://websocket-echo.localhost.pomerium.io
|
||||
to: http://websocket-echo:80
|
||||
- allow_any_authenticated_user: true
|
||||
from: https://fortio-ui.localhost.pomerium.io
|
||||
to: https://fortio:8080
|
||||
- allow_public_unauthenticated_access: true
|
||||
from: https://fortio-ping.localhost.pomerium.io
|
||||
tls_custom_ca_file: route_ca_1.crt
|
||||
tls_server_name: fortio-ping.localhost.pomerium.io
|
||||
to: https://fortio:8079
|
||||
- allow_public_unauthenticated_access: true
|
||||
from: https://httpdetails-ip-address.localhost.pomerium.io
|
||||
to: https://172.21.0.50:8443
|
||||
- allow_public_unauthenticated_access: true
|
||||
from: https://httpdetails.localhost.pomerium.io
|
||||
path: /tls-skip-verify-enabled
|
||||
tls_skip_verify: true
|
||||
to: https://trusted-httpdetails:8443
|
||||
- allow_public_unauthenticated_access: true
|
||||
from: https://httpdetails.localhost.pomerium.io
|
||||
path: /tls-skip-verify-disabled
|
||||
tls_skip_verify: false
|
||||
to: https://trusted-httpdetails:8443
|
||||
- allow_public_unauthenticated_access: true
|
||||
from: https://httpdetails.localhost.pomerium.io
|
||||
path: /tls-server-name-enabled
|
||||
tls_server_name: httpdetails.localhost.notpomerium.io
|
||||
to: https://wrongly-named-httpdetails:8443
|
||||
- allow_public_unauthenticated_access: true
|
||||
from: https://httpdetails.localhost.pomerium.io
|
||||
path: /tls-server-name-disabled
|
||||
to: https://wrongly-named-httpdetails:8443
|
||||
- allow_public_unauthenticated_access: true
|
||||
from: https://httpdetails.localhost.pomerium.io
|
||||
path: /tls-custom-ca-enabled
|
||||
tls_custom_ca_file: route_ca_2.crt
|
||||
tls_server_name: httpdetails.localhost.pomerium.io
|
||||
to: https://untrusted-httpdetails:8443
|
||||
- allow_public_unauthenticated_access: true
|
||||
from: https://httpdetails.localhost.pomerium.io
|
||||
path: /tls-custom-ca-disabled
|
||||
to: https://untrusted-httpdetails:8443
|
||||
- allow_any_authenticated_user: true
|
||||
from: https://client-cert-required.localhost.pomerium.io
|
||||
tls_downstream_client_ca_file: route_downstream_ca_1.crt
|
||||
to: http://trusted-httpdetails:8080
|
||||
- allow_any_authenticated_user: true
|
||||
from: https://client-cert-overlap.localhost.pomerium.io
|
||||
path: /ca1
|
||||
tls_downstream_client_ca_file: route_downstream_ca_1.crt
|
||||
to: http://trusted-httpdetails:8080
|
||||
- allow_any_authenticated_user: true
|
||||
from: https://client-cert-overlap.localhost.pomerium.io
|
||||
path: /ca2
|
||||
tls_downstream_client_ca_file: route_downstream_ca_2.crt
|
||||
to: http://trusted-httpdetails:8080
|
||||
- cors_allow_preflight: true
|
||||
from: https://httpdetails.localhost.pomerium.io
|
||||
prefix: /cors-enabled
|
||||
to: http://trusted-httpdetails:8080
|
||||
- cors_allow_preflight: false
|
||||
from: https://httpdetails.localhost.pomerium.io
|
||||
prefix: /cors-disabled
|
||||
to: http://trusted-httpdetails:8080
|
||||
- allow_public_unauthenticated_access: true
|
||||
from: https://httpdetails.localhost.pomerium.io
|
||||
prefix: /preserve-host-header-enabled
|
||||
preserve_host_header: true
|
||||
to: http://trusted-httpdetails:8080
|
||||
- allow_public_unauthenticated_access: true
|
||||
from: https://httpdetails.localhost.pomerium.io
|
||||
prefix: /preserve-host-header-disabled
|
||||
preserve_host_header: false
|
||||
to: http://trusted-httpdetails:8080
|
||||
- allow_any_authenticated_user: true
|
||||
from: https://restricted-httpdetails.localhost.pomerium.io
|
||||
pass_identity_headers: true
|
||||
to: http://trusted-httpdetails:8080
|
||||
- from: https://ppl-restricted-httpdetails.localhost.pomerium.io
|
||||
pass_identity_headers: true
|
||||
to: http://trusted-httpdetails:8080
|
||||
policy:
|
||||
- allow:
|
||||
or:
|
||||
- email:
|
||||
is: foo@example.com
|
||||
- email:
|
||||
is: bar@example.com
|
||||
- allowed_domains:
|
||||
- dogs.test
|
||||
from: https://httpdetails.localhost.pomerium.io
|
||||
pass_identity_headers: true
|
||||
prefix: /by-domain
|
||||
to: http://trusted-httpdetails:8080
|
||||
- allowed_users:
|
||||
- user1@dogs.test
|
||||
from: https://httpdetails.localhost.pomerium.io
|
||||
pass_identity_headers: true
|
||||
prefix: /by-user
|
||||
to: http://trusted-httpdetails:8080
|
||||
- allow_any_authenticated_user: true
|
||||
from: https://httpdetails.localhost.pomerium.io
|
||||
prefix: /round-robin
|
||||
to:
|
||||
- http://trusted-1-httpdetails:8080
|
||||
- http://trusted-2-httpdetails:8080
|
||||
- http://trusted-3-httpdetails:8080
|
||||
- allow_any_authenticated_user: true
|
||||
from: https://httpdetails.localhost.pomerium.io
|
||||
prefix: /ring-hash
|
||||
to:
|
||||
- http://trusted-1-httpdetails:8080
|
||||
- http://trusted-2-httpdetails:8080
|
||||
- http://trusted-3-httpdetails:8080
|
||||
- allow_any_authenticated_user: true
|
||||
from: https://httpdetails.localhost.pomerium.io
|
||||
prefix: /maglev
|
||||
to:
|
||||
- http://trusted-1-httpdetails:8080
|
||||
- http://trusted-2-httpdetails:8080
|
||||
- http://trusted-3-httpdetails:8080
|
||||
- allow_public_unauthenticated_access: true
|
||||
from: https://httpdetails.localhost.pomerium.io
|
||||
pass_identity_headers: true
|
||||
set_request_headers:
|
||||
X-Custom-Request-Header: custom-request-header-value
|
||||
to: http://trusted-httpdetails:8080
|
||||
- allow_public_unauthenticated_access: true
|
||||
allow_websockets: true
|
||||
from: https://enabled-ws-echo.localhost.pomerium.io
|
||||
to: http://websocket-echo:80
|
||||
- allow_public_unauthenticated_access: true
|
||||
from: https://disabled-ws-echo.localhost.pomerium.io
|
||||
to: http://websocket-echo:80
|
||||
- allow_public_unauthenticated_access: true
|
||||
enable_google_cloud_serverless_authentication: true
|
||||
from: https://cloudrun.localhost.pomerium.io
|
||||
pass_identity_headers: true
|
||||
set_request_headers:
|
||||
x-idp: oidc
|
||||
to: http://trusted-httpdetails:8080
|
||||
- from: https://200.localhost.pomerium.io
|
||||
response:
|
||||
status: 200
|
||||
body: OK
|
||||
shared_secret: UYgnt8bxxK5G2sFaNzyqi5Z+OgF8m2akNc0xdQx718w=
|
||||
|
||||
tls_derive: example.com # unsupported
|
29
internal/zero/cmd/testdata/crl.pem
vendored
Normal file
29
internal/zero/cmd/testdata/crl.pem
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
-----BEGIN X509 CRL-----
|
||||
MIICWjCBwwIBATANBgkqhkiG9w0BAQsFADA6MR4wHAYDVQQKExVta2NlcnQgZGV2
|
||||
ZWxvcG1lbnQgQ0ExGDAWBgNVBAMTD2Rvd25zdHJlYW0gQ0EgMRcNMjMwNzE5MjEx
|
||||
ODQ1WhcNMzMwNzE2MjExODQ1WjAjMCECEEY9jnU8Vkt2MYueskRd7bwXDTIzMDcx
|
||||
OTIxMTc0N1qgMDAuMB8GA1UdIwQYMBaAFNH1NAz8Uj24PhCGdBkGi0CMQGMLMAsG
|
||||
A1UdFAQEAgIQADANBgkqhkiG9w0BAQsFAAOCAYEA4w3ow4j1DaufiBBXhdC0ECyY
|
||||
zDxOuACdR4zyoYbjN1g2kc0buchJ7+V0eTY/RnSNc+uqNY+LYprXQquZKlr9dFUr
|
||||
vJ/pXJ+uyLR/MzehiTr3HoTLCPliKZDDayPmoZvaqHD8IoGEnQX6kCEhopb7gtqJ
|
||||
U7TfHaexi0p43FH00gnZfaDMkcAd8zClsEXUrAFCQRD1M5PuCOTO7CeQcI53uBvd
|
||||
8aGvyHlKA/2O17gniMngcoCO72NAUltJzMbugqeXOoiGHYoSsKTbY7MdLhY3MEBa
|
||||
3ZkCFgt3HLHTz5S0PeBVrT7/y7Sz5cj0QA0JKL3J3psngVbpS4oHu6cyvg//7NdG
|
||||
KNBqdas+KPAsmV+3y64Cr2hnv+WsWjiuxDgIEFzpQOcyNOZzmISACw7YXjwFuIne
|
||||
OiiMuYs/2NvwQ1OPfq3jg3If8kBUcSVh+Te4FI3+07tWUvN6nVYC4VmXAcG1HuxQ
|
||||
Gnne9f5hgEJPVfLT+uJ31VV16+vBnZD85DZJTrDM
|
||||
-----END X509 CRL-----
|
||||
-----BEGIN X509 CRL-----
|
||||
MIICNTCBngIBATANBgkqhkiG9w0BAQsFADA6MR4wHAYDVQQKExVta2NlcnQgZGV2
|
||||
ZWxvcG1lbnQgQ0ExGDAWBgNVBAMTD2Rvd25zdHJlYW0gQ0EgMhcNMjMwNzE5MjE1
|
||||
MDE1WhcNMzMwNzE2MjE1MDE1WqAwMC4wHwYDVR0jBBgwFoAUCxQ2cBa5YzqVzamp
|
||||
iNCx8KwFFyQwCwYDVR0UBAQCAhAAMA0GCSqGSIb3DQEBCwUAA4IBgQCYamx8pM+R
|
||||
Clyskcu7ouhu/R1Jy1nWGyWtKphYq0XFbOLlnk2Z7eDfAX8Eej2FavqxzapR2x2O
|
||||
4iJNDCmiwYYYUS2X2LJ3rRRJXyXvWhtfHrxURd6BitC2IXpykBtVlf3zAnZ8GZFQ
|
||||
S1jdfyLMuEAiDwIai3Yt8HsDp/qG089oXcoStyQg/uRpmWy05A9uCVOfNHSLSZu8
|
||||
lr4qatleu0wWbV1amL8tO9x4CRkO0o1YaQq4DoOruPr+3NkTmPvGidh3F71V6IEA
|
||||
h+KzdbRXxFmCCWLWmpJDcrgR7KUqZOhUUt+DUqaqhV44qI0nrpR+QZLohoDor9Lw
|
||||
K+ufj3n29eSRX+3Px+oVWPT8YZP2uKPdizi96me2jWTr51x9AjEoJDsTnYRl9+uY
|
||||
ShiUxWnTdQsooknIfcS/0zfgZ87GvUVzinCQzJpwVxd4Alt8AlR+fXAqNIoOguyv
|
||||
p/CtRVnjVE7l7HW/hQRq1J0ijCCKwmyf/KTd6EK4TdrvbX/U9msVM8Y=
|
||||
-----END X509 CRL-----
|
28
internal/zero/cmd/testdata/route_ca_1.crt
vendored
Normal file
28
internal/zero/cmd/testdata/route_ca_1.crt
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIE1zCCAz+gAwIBAgIQZ139cd/paPdkS2JyAu7kEDANBgkqhkiG9w0BAQsFADCB
|
||||
gzEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMSwwKgYDVQQLDCNjYWxl
|
||||
YkBjYWxlYi1wYy1saW51eCAoQ2FsZWIgRG94c2V5KTEzMDEGA1UEAwwqbWtjZXJ0
|
||||
IGNhbGViQGNhbGViLXBjLWxpbnV4IChDYWxlYiBEb3hzZXkpMB4XDTIxMDgxMDE3
|
||||
MzIwOVoXDTMxMDgxMDE3MzIwOVowgYMxHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9w
|
||||
bWVudCBDQTEsMCoGA1UECwwjY2FsZWJAY2FsZWItcGMtbGludXggKENhbGViIERv
|
||||
eHNleSkxMzAxBgNVBAMMKm1rY2VydCBjYWxlYkBjYWxlYi1wYy1saW51eCAoQ2Fs
|
||||
ZWIgRG94c2V5KTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBANbKyMz5
|
||||
MVW6YKdjh1oIN1Mn7PE2pH5SbJSpWxdAGhdBkBkpAa7OxarjH5KVkCTSa7oncla7
|
||||
qNuJZS6mBmoxF+R+cR3jyGdUAYlozl1jlfqLIfC/+g7V7VmOJn98tjB42fatxLl6
|
||||
WPAw1JDNsWtQfhKhbcHut7RsF0rMOOHcwywTR7LOyCmIel1pcmpV4hbVcT6eVwoP
|
||||
HXyJSa9cqaMQ5Xrdogai4IqZZIGLHeLsTVutOgJFXEevlX/QT3sWomEctzh38Js4
|
||||
9DiAPD6d4Y7/CPLYEfk29JQ9NZhpgDsi9hu5FHHZcXwf1IHlw/CBVgn6j+jmvKKz
|
||||
90Ma1oquv3W6dttid/xCcLGu2S+96Tzrykmoy5VacLtVEP41YmoVls91rlo7olpe
|
||||
QWFbnmco739TI/4h+HodolperQERQl7uCnpKVPZ3WokKuRh5pkqkQp/arQjtwcRt
|
||||
G43CrDpbl+uSjMCAxha958eTYvtojTMnvLtsGID1hGXnqlw+5KjKrgRHrQIDAQAB
|
||||
o0UwQzAOBgNVHQ8BAf8EBAMCAgQwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4E
|
||||
FgQUhYZYWIBHyk6ZVTnp3lRt/tyBP00wDQYJKoZIhvcNAQELBQADggGBAA1F/apr
|
||||
l6pNT3Mp/MxhUUgo6usEJCryGQcLRfexyQXGN3huCmIrP55VFa8ETPAtjsr6PMe7
|
||||
7vvEj8eFu2JtKovlQwNewYU9cjAMCVaFiNbrQa20hzhWc2js6dyildE6/DPzbeds
|
||||
KDAxhFNp35SlwtRtKk1SzxJxsqSwjfxI8fp+R/0wO8g0fWTdM2gCpRwYMNwJELEg
|
||||
+dSlvJCwuu+rzxLalzaPF1PMTW72OELal/j5sD+2VytQ4k+HUDbyt2DnQT7YQ3zo
|
||||
q02x2u2sm1WW/o/uh8pjPxkGQqL2mryZs6VH9VCU3QkKNDssNd71lr3wPoE4YRHe
|
||||
UvzD1eDeelzBUFNIpDCjdCsL55yIPqUsr6lmjpBPL0vea33QTMbcsSxu0umGXDbU
|
||||
66juU4Z1jOE0wClIvaO699J+E2gBe1jUN6At6b8BSoZqCqXYoDHGei9RBUdvgqto
|
||||
kVsoJfDI/TFMekYgpL5UVYmLdfgqLPPRP9pQBLDx3mszeAqnvfTICAzfXg==
|
||||
-----END CERTIFICATE-----
|
28
internal/zero/cmd/testdata/route_ca_2.crt
vendored
Normal file
28
internal/zero/cmd/testdata/route_ca_2.crt
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIE2DCCA0CgAwIBAgIRALd9GaJR92qi7qL1eHGM6K0wDQYJKoZIhvcNAQELBQAw
|
||||
gYMxHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTEsMCoGA1UECwwjY2Fs
|
||||
ZWJAY2FsZWItcGMtbGludXggKENhbGViIERveHNleSkxMzAxBgNVBAMMKm1rY2Vy
|
||||
dCBjYWxlYkBjYWxlYi1wYy1saW51eCAoQ2FsZWIgRG94c2V5KTAeFw0yMTA4MTEy
|
||||
MTU2MTBaFw0zMTA4MTEyMTU2MTBaMIGDMR4wHAYDVQQKExVta2NlcnQgZGV2ZWxv
|
||||
cG1lbnQgQ0ExLDAqBgNVBAsMI2NhbGViQGNhbGViLXBjLWxpbnV4IChDYWxlYiBE
|
||||
b3hzZXkpMTMwMQYDVQQDDCpta2NlcnQgY2FsZWJAY2FsZWItcGMtbGludXggKENh
|
||||
bGViIERveHNleSkwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDWYpVe
|
||||
BSnee2cABYofSoWxGMyFaMQ0nJkY0UWM9ckyUh7VfgN+/aFSW2ZSmXuv5drcpi20
|
||||
z3elhPTe98bANbj+/bi0015QWnMenK05ZK6qDtFwo/HVC/Ycaruu96+1J2toeWuE
|
||||
tykW3MCpC1pHYS5g9iVDkpdrznvXKlYuSikjrj7K5toiTvum97LxKkuj6DXjapPD
|
||||
5vteSN1dQgO9CS3sqlcwYA6RjUHwY2VEh2adP37BZrZwO+yJq9qF5y5Glgi8lN4c
|
||||
KlIlFUs/xSpQsxNbNQXtN9mk4imYlZGzYYbbm+foBVPPboa5jVwKDpZ65mOs7JGP
|
||||
6yj+7V7UBMFpW+gKmJtgh/kkAx185h93qwLFPc8/T7n++P1bu+fakXPGPE21rDeL
|
||||
PnUmucIZpJo5NpYVQv4WvTKq/zMR9Sspz2PFJnERTfTvq+F1q3ZNafEziPsB9oeS
|
||||
njxwmaZOSV0vXq/qeoqx4v6MBzVAY0/8R2LcpJ4ug0OZ3w0b2t6yo86P5Q8CAwEA
|
||||
AaNFMEMwDgYDVR0PAQH/BAQDAgIEMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0O
|
||||
BBYEFLcY8EoNofMcrrxzyxIn3W6ZOMVXMA0GCSqGSIb3DQEBCwUAA4IBgQCZzDCv
|
||||
KIHX3GvjNSY5w5bOn4E3w7QHP09ABjT/wuT4LDkZHJMmlrLo3s8bcsQ0sMD1Y///
|
||||
s07cp4xYlqD7BA0AcpvYVYq58xKxsoCwVXmG5cEeOoZmWf3qY2mS8eW96vOFrdIb
|
||||
L4OF4xYUOMRqAOGAAr6VlO7gXa406HzrsA1hYZwreXhOTCZZPZOUnAu05SHFdgaM
|
||||
TJNB/o01tpwQlrTxNmfropoOzyuvH0zU2RrMs0+EbOuC4A2cQ83DIFxvq67lyU0A
|
||||
s1Q6tRM0+UDmJOLz3SdgN+D00hcuuj92GV4bH8BfyUv8NCY0vDij0TSjj4c4Qtc7
|
||||
IPLTZ2g545oczhNgAmT7d+B5InyfiSIKemXqes2jpiAfzPNl9BVxsakcs/YzoYs1
|
||||
+qTjAWuaDsKohEnO4BJuzv0xrce40enRgXyGGFvXu2s4FY2vJqTSo6ysDWnhI3LW
|
||||
dcg6O2F4APCGGe7zsuqiqkpcknBabgzEs9foHq2mfo7XiEzedMN8BNqfSbA=
|
||||
-----END CERTIFICATE-----
|
25
internal/zero/cmd/testdata/route_downstream_ca_1.crt
vendored
Normal file
25
internal/zero/cmd/testdata/route_downstream_ca_1.crt
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIEPDCCAqSgAwIBAgIJAKmtj1u+hOdzMA0GCSqGSIb3DQEBCwUAMDoxHjAcBgNV
|
||||
BAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTEYMBYGA1UEAxMPZG93bnN0cmVhbSBD
|
||||
QSAxMB4XDTIzMDYwODE4NTgyMloXDTMzMDYwODE4NTgyMlowOjEeMBwGA1UEChMV
|
||||
bWtjZXJ0IGRldmVsb3BtZW50IENBMRgwFgYDVQQDEw9kb3duc3RyZWFtIENBIDEw
|
||||
ggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDue3fuI704DazewdWmKJQs
|
||||
YGYR2ZapQQeQynXaqOhqMOLTc7M18uVOnfhvFVtUB5OCtxL2TMmy8/ytIQlU8CUc
|
||||
bUo1AFcXu1MGORJNu5zbJymsrOE8fKqopb3muGNRM6tulIHhpRCcF3m8pKFBZBWs
|
||||
CR7A2MhgKHJvd1yVMc6/GpO/RqIHiFAiCV9XguadKTwapPJ54vJwBDZoDM4/qA34
|
||||
xFR1uCAzob0D4yFW/C7u57SMZDjSy2jxxZkcFQAvmRPPgzutaAHuRUUnPhw3f9PF
|
||||
+DLNDeo6kXdS6aQOb/weCPl/VjlskXyvgNuzGE2xixZYBQwpXAE8AuBcXNvlxT0T
|
||||
1oyoU8aggymnTFWnLmN/ipQ7+9CHS2+apFDG7nrf9q5UgLtRiVLOytoVxWDOhoY5
|
||||
pqbS05aDjWXbXyPf2e318Ntjc6Hl7nSffHlCGsb/zqiJnJX6ti/k0VR1WHJZyu7e
|
||||
CYeu+mtqNATrS7h+nBUMNZ9Bb1EIHQOJ/yyToULy/nECAwEAAaNFMEMwDgYDVR0P
|
||||
AQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFNH1NAz8Uj24
|
||||
PhCGdBkGi0CMQGMLMA0GCSqGSIb3DQEBCwUAA4IBgQBltym8hRgXSAaGTZAciCBc
|
||||
sRtyEkQ584oHUiOmaKvITjnHys/EiETnNaxRw7t/69DKe5g4UaqgdlMwecjJk/Hl
|
||||
jSvXI4mAUERkcIJIEJspMapsEp5QcTAlvskoXjNPFrOW+x0iOLdAM41x5kBDQRkc
|
||||
+N2ie0ITJ5ZX530Ai4ukt76NZNIOio5xoHs1q170kn6xwfS12x1g7CksHlN5Mbw1
|
||||
wtFFeLfQCZVXPNspH7LHJUkrULSTyhleZFJ3ZZqqT9oybpDUhdZB0nZJ6ZC1JiQo
|
||||
2HMwIFV+OsEEG7fNzHhbVKaJmaiOiW2t/CpltebVLSTinz2LmZhzVFRT+y/cdhn3
|
||||
5IsQHzGwEKKtL5XfqJjqWhry+mw/vb+Rze6yy9Li7FkBnetQq8Tb0a2u/UHyzqTA
|
||||
NVhu1wgbRD93vnZqGOkb0gzMRPJC/KibNvFRfaeDXDOiW69Npm/xxXBO/My0CWF1
|
||||
p7cQCkgpkStnWEmm/48WiwGcFWTC2W+mims7JcIpSpc=
|
||||
-----END CERTIFICATE-----
|
25
internal/zero/cmd/testdata/route_downstream_ca_2.crt
vendored
Normal file
25
internal/zero/cmd/testdata/route_downstream_ca_2.crt
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIEPDCCAqSgAwIBAgIJAPjvgLbEIVj/MA0GCSqGSIb3DQEBCwUAMDoxHjAcBgNV
|
||||
BAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTEYMBYGA1UEAxMPZG93bnN0cmVhbSBD
|
||||
QSAyMB4XDTIzMDYwOTAwNDQzOFoXDTMzMDYwOTAwNDQzOFowOjEeMBwGA1UEChMV
|
||||
bWtjZXJ0IGRldmVsb3BtZW50IENBMRgwFgYDVQQDEw9kb3duc3RyZWFtIENBIDIw
|
||||
ggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQC/8Kog2Zz8e68EGpfiXN7u
|
||||
Xgau38h63ydspucrjhtnSTWXtHO1hYLmUYWAewi79iGYzOuYgWCD3cFxd+tMKLrB
|
||||
yoriJ3KioTtY0pmyLDJ1TXMSaFGgnZqjXHmjMvio0x/jQNkCbYkFBGQSZZvkA8sQ
|
||||
m5AsRDeIUPkPlhFMnb2x4iRcLBP6zDNFfX+y1qSolKbh3K9/E3PT4Unja8gObzCJ
|
||||
nrOcF5SBqTOjRHif/S/wZ9TSFWzLmqGLhq73RahyTiaYP46UvJhrNb5Mo9Hbb94/
|
||||
4zS5B2Zuo4pshSZDWpqwvBecQN0VaLVvIymuSyg5TzuH4ktM0ptzv6rXinDla7rz
|
||||
Mu/FrFVQPksOhTDt5UCSqODwPZiO7g5ST0s+jMpbp1XN8KP2prtElUWdabvHlb0M
|
||||
D2E0hHVi444YkQxZaCoed2obrTB2Df2CwHATgFKvLF1SGS2Q9v0pbUc6Z+0o912b
|
||||
nRfGzi2p7iBsWULuINI3nbNAzlmWPmGiwV1SY1Y0dU8CAwEAAaNFMEMwDgYDVR0P
|
||||
AQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFAsUNnAWuWM6
|
||||
lc2pqYjQsfCsBRckMA0GCSqGSIb3DQEBCwUAA4IBgQBU6YiRXQ4jkrqugtuLj2a5
|
||||
AQ+URPlfkFFN0BDpWCIzV50w+Y1ZtH2HvGX44zDjbQTwv+AU4T+F75C8Pnc5yvYo
|
||||
v6FIMOOZIrvilokyVf3dKRC3Y2cQac4u64aQk+XR/qjiYoFK0B9yw8UA3O7wA46b
|
||||
ceoZUFZLc5oSsnB9tW72i8lEkBFt2X62rqSQNGYtzCV64bM+ezCsBYPaCIKW0ARB
|
||||
0CbNFGoaPJzAuuGukvOcBDytJ3RJBXJ7l3626KNGxCLsRMcDcTxvXBf7gFWtetW9
|
||||
kuofvlJMiPi3BDMl/FAE5ikj0UR47rjYUxM2SF6F+z8pEcPcePSYzClMECL9a/02
|
||||
I12sEnU3Rf+RpwSTHSCjyXGtWl4dGSJlOElwrYMBAyX62dfFY9GEGgHCnyO1tj39
|
||||
JIhgiIBEZsBL9LOOK8vTYzZ5kBkZ1NXh2Bj3nS/B/M5zotp4/S6P30Li44/Jbpvc
|
||||
70fXruF69zwPMc5b3x7yX7hPLYHk0hm3BOWaodPI4t0=
|
||||
-----END CERTIFICATE-----
|
26
internal/zero/cmd/testdata/tls.crt
vendored
Normal file
26
internal/zero/cmd/testdata/tls.crt
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIEUjCCArqgAwIBAgIRAKNaEqCmmZfhmcYgZy01WCswDQYJKoZIhvcNAQELBQAw
|
||||
gYMxHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTEsMCoGA1UECwwjY2Fs
|
||||
ZWJAY2FsZWItcGMtbGludXggKENhbGViIERveHNleSkxMzAxBgNVBAMMKm1rY2Vy
|
||||
dCBjYWxlYkBjYWxlYi1wYy1saW51eCAoQ2FsZWIgRG94c2V5KTAeFw0yMzExMTAy
|
||||
MDA4NDRaFw0zMzExMDcyMDA4NDRaMFcxJzAlBgNVBAoTHm1rY2VydCBkZXZlbG9w
|
||||
bWVudCBjZXJ0aWZpY2F0ZTEsMCoGA1UECwwjY2FsZWJAY2FsZWItcGMtbGludXgg
|
||||
KENhbGViIERveHNleSkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC8
|
||||
HLBAIzXkPeegldUfRKK2jQxSVZD5g+qsjAzpmrq/AtmweK1cGcOtZ6eOL+p8brPD
|
||||
yVhDT0QlI/O/EKgCOFFxUDqoR82iY06SacAjHni6+PO9tVRbFV0w14BDAJSpB+Vv
|
||||
Wyl+FoPDV/vsZ31FtYw+EwqkbDx/kaT9uzf+LJdlkf14nQQj8Eky/8d3mWJbb/9t
|
||||
jObsaQgJ5LLxCYdImkr77X2LMuDw/1tpH642GE25Nrgm6QHlyKSfYXo38v83ebEq
|
||||
bZUDG+ZioArPmqmkawUWw3ekhj80SJg/TK9PRaN/VvcI1PgAd7LZztUReSmTy5hd
|
||||
9r6rOBxpxwnTDvHkBn6vAgMBAAGjbDBqMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUE
|
||||
DDAKBggrBgEFBQcDATAfBgNVHSMEGDAWgBSFhlhYgEfKTplVOeneVG3+3IE/TTAi
|
||||
BgNVHREEGzAZghcqLmxvY2FsaG9zdC5wb21lcml1bS5pbzANBgkqhkiG9w0BAQsF
|
||||
AAOCAYEApqVzJ3Qf9VqkujFbc0MBDqWD/8gjfd7mW29fRtMIP3zdJliyevRj73AL
|
||||
ifX5ZZunT7n/j52ZziFib4j8uc4R6VwAE7lLpDesfsL4AgvG6ujJaJLh+q6fPFVm
|
||||
8UwIr3/HjZAGPvbwceAO00mtfqn8aK1KeKxfEk9UhTUWhsquby88EcJVhxkTsAHo
|
||||
kKQkEaf9NLazhZ0P0u9J/14VGhMN8QUHvILVjckCDhIj38IUK7UtZHkM72GmKrj2
|
||||
SC40IDdNt4zb1ATLVeyOLdwKjwEFgKWzkvI/7Uj9pA26/eYGPQ7oxRF+IExVIhDr
|
||||
EJvHrWQ0s0EKNPdpU/Ihqtk0rYkj81peqM8TmI6vqrZqAEPza1tYk6WQszDonpPW
|
||||
uKlfr9GYYf5Mu9a2y26AgluDniAcnfWjRXmr1rvRHBpzsLSD3STnPE5t6HJieP7r
|
||||
v6k/flXQ9SEw0U3lI/nZKKwiLfWC2O5BpKwMz19cZ8/kLSJWHg4lkDb2Uo1JKniW
|
||||
+kMEI9nN
|
||||
-----END CERTIFICATE-----
|
28
internal/zero/cmd/testdata/tls.key
vendored
Normal file
28
internal/zero/cmd/testdata/tls.key
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC8HLBAIzXkPeeg
|
||||
ldUfRKK2jQxSVZD5g+qsjAzpmrq/AtmweK1cGcOtZ6eOL+p8brPDyVhDT0QlI/O/
|
||||
EKgCOFFxUDqoR82iY06SacAjHni6+PO9tVRbFV0w14BDAJSpB+VvWyl+FoPDV/vs
|
||||
Z31FtYw+EwqkbDx/kaT9uzf+LJdlkf14nQQj8Eky/8d3mWJbb/9tjObsaQgJ5LLx
|
||||
CYdImkr77X2LMuDw/1tpH642GE25Nrgm6QHlyKSfYXo38v83ebEqbZUDG+ZioArP
|
||||
mqmkawUWw3ekhj80SJg/TK9PRaN/VvcI1PgAd7LZztUReSmTy5hd9r6rOBxpxwnT
|
||||
DvHkBn6vAgMBAAECggEAB28i0AYUNSb1JnWFbKzruUctu3tCNXovJg6K3BiPVMkq
|
||||
DT1XrJIgF5RHHOlr3OsLE6u7Xz2ctdML6PshiKTtIwtGpivgRpCiJEslmr2zi8AW
|
||||
8eJeqRLZEfsSSJOXTG7RdGsn4qHFJ00s2ZTlcIHSPwnFm+XjJi99U8G4XsUoXo0r
|
||||
Gy+0VCuU7M8gICEHHsrQO9XDD3nT2jiu5TjrKwjut3EmoJssI5bqx33+OBu5BpCP
|
||||
CT473D43P9p3qi/XnfvqGSG2Oj4OajV4fr0o9B3KvIxkMem7WlI3jyy1kApyXqVT
|
||||
bLkLFyWBNTWUZ2R/2wxmuoC6mLZw879MLCKMvk1doQKBgQDhmwGafJNymTiEQZRI
|
||||
SsQx4seqfOKfgFC7ohqH9cROOu8IJ1o7q2pM2W4XiV+S3wTdPGmca6IOjX23isVB
|
||||
2uqNi9S4MnI2/d22Gd/BR9rvBw1eGJoKbrWx22fE8QCEWT1AnO+DuD0jC85yRls7
|
||||
axzlaMrxEu3LI9UE7NtrdQiByQKBgQDVdI6ceIVBT6RgvVGt8zkLjPIFjhQEHAIp
|
||||
uhirgqpS6CX9Blyf2+o40zmfj3he5rCcEoB5MseM+DgFbcVh2e/MVnYiNNw6JCDB
|
||||
BQkF408pZpSeKXvL/oyV/kImMTJ/tUDY0EXxMwSPJB0WltbWreVIHopigXRCbaey
|
||||
uBHVBv/4twKBgHwHuePy5SU1s2qSmzD7Wc2LPfYu3nCOHNRrFGb26MuRfuReri7r
|
||||
2G8TgoESFycp0QTIN8+1JM0XYKxNcJD6B8V1wKbbpQsymneI1gjutiB/Igw/PkDK
|
||||
CL4VP4F4da5NWW1yWgNygLoJvZ/5qiKKisJc0GWk4HKz6mLgzOjQ2LJxAoGBALHZ
|
||||
fN2YeYbyYcaM11p1VilulVTVjY3i/FZiDR4SL/IGJWjN/Szg4iXYsKFmu+dulOZl
|
||||
cBALpEKrqpmzXYtrN6bsv18+5eO3qGbK2DrEq3eWVev2KoTMobxz7g++XBIWJmLA
|
||||
Hhaa6IiPkYD5yyVyHKDbeXgb3o9eqCR7w7fYLjy/AoGAI4D+MFkivwUF7hqf5edS
|
||||
KrltwmodHiqXNbVkwbW1AFPJbiYai4YFfK4IAbif/Ymxf9G78aOkr9ZpCIzOkDPZ
|
||||
YpEwQGWsAhElCFvc8E/5dHESSp+tWtP+NluimpFqiDg3/SUnMwO2xH0nhLa0zejh
|
||||
gmLh4w/CcPyb9ZyXceWU/nU=
|
||||
-----END PRIVATE KEY-----
|
File diff suppressed because it is too large
Load diff
|
@ -108,6 +108,7 @@ message Route {
|
|||
envoy.config.cluster.v3.Cluster envoy_opts = 36;
|
||||
|
||||
repeated Policy policies = 27;
|
||||
repeated PPLPolicy ppl_policies = 63;
|
||||
string id = 28;
|
||||
|
||||
optional string host_rewrite = 50;
|
||||
|
@ -120,6 +121,10 @@ message Route {
|
|||
bool show_error_details = 59;
|
||||
}
|
||||
|
||||
message PPLPolicy {
|
||||
bytes raw = 1;
|
||||
}
|
||||
|
||||
message Policy {
|
||||
string id = 1;
|
||||
string name = 2;
|
||||
|
|
|
@ -103,6 +103,12 @@ type ClientInterface interface {
|
|||
|
||||
ReportClusterResourceBundleStatus(ctx context.Context, bundleId BundleId, body ReportClusterResourceBundleStatusJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
// ImportConfigurationWithBody request with any body
|
||||
ImportConfigurationWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
// GetQuotas request
|
||||
GetQuotas(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
// ExchangeClusterIdentityTokenWithBody request with any body
|
||||
ExchangeClusterIdentityTokenWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
|
@ -174,6 +180,30 @@ func (c *Client) ReportClusterResourceBundleStatus(ctx context.Context, bundleId
|
|||
return c.Client.Do(req)
|
||||
}
|
||||
|
||||
func (c *Client) ImportConfigurationWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) {
|
||||
req, err := NewImportConfigurationRequestWithBody(c.Server, contentType, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Client.Do(req)
|
||||
}
|
||||
|
||||
func (c *Client) GetQuotas(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) {
|
||||
req, err := NewGetQuotasRequest(c.Server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Client.Do(req)
|
||||
}
|
||||
|
||||
func (c *Client) ExchangeClusterIdentityTokenWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) {
|
||||
req, err := NewExchangeClusterIdentityTokenRequestWithBody(c.Server, contentType, body)
|
||||
if err != nil {
|
||||
|
@ -357,6 +387,62 @@ func NewReportClusterResourceBundleStatusRequestWithBody(server string, bundleId
|
|||
return req, nil
|
||||
}
|
||||
|
||||
// NewImportConfigurationRequestWithBody generates requests for ImportConfiguration with any type of body
|
||||
func NewImportConfigurationRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) {
|
||||
var err error
|
||||
|
||||
serverURL, err := url.Parse(server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
operationPath := fmt.Sprintf("/config/import")
|
||||
if operationPath[0] == '/' {
|
||||
operationPath = "." + operationPath
|
||||
}
|
||||
|
||||
queryURL, err := serverURL.Parse(operationPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", queryURL.String(), body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Add("Content-Type", contentType)
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// NewGetQuotasRequest generates requests for GetQuotas
|
||||
func NewGetQuotasRequest(server string) (*http.Request, error) {
|
||||
var err error
|
||||
|
||||
serverURL, err := url.Parse(server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
operationPath := fmt.Sprintf("/config/quotas")
|
||||
if operationPath[0] == '/' {
|
||||
operationPath = "." + operationPath
|
||||
}
|
||||
|
||||
queryURL, err := serverURL.Parse(operationPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", queryURL.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// NewExchangeClusterIdentityTokenRequest calls the generic ExchangeClusterIdentityToken builder with application/json body
|
||||
func NewExchangeClusterIdentityTokenRequest(server string, body ExchangeClusterIdentityTokenJSONRequestBody) (*http.Request, error) {
|
||||
var bodyReader io.Reader
|
||||
|
@ -494,6 +580,12 @@ type ClientWithResponsesInterface interface {
|
|||
|
||||
ReportClusterResourceBundleStatusWithResponse(ctx context.Context, bundleId BundleId, body ReportClusterResourceBundleStatusJSONRequestBody, reqEditors ...RequestEditorFn) (*ReportClusterResourceBundleStatusResp, error)
|
||||
|
||||
// ImportConfigurationWithBodyWithResponse request with any body
|
||||
ImportConfigurationWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ImportConfigurationResp, error)
|
||||
|
||||
// GetQuotasWithResponse request
|
||||
GetQuotasWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetQuotasResp, error)
|
||||
|
||||
// ExchangeClusterIdentityTokenWithBodyWithResponse request with any body
|
||||
ExchangeClusterIdentityTokenWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ExchangeClusterIdentityTokenResp, error)
|
||||
|
||||
|
@ -601,6 +693,53 @@ func (r ReportClusterResourceBundleStatusResp) StatusCode() int {
|
|||
return 0
|
||||
}
|
||||
|
||||
type ImportConfigurationResp struct {
|
||||
Body []byte
|
||||
HTTPResponse *http.Response
|
||||
JSON400 *ErrorResponse
|
||||
JSON500 *ErrorResponse
|
||||
}
|
||||
|
||||
// Status returns HTTPResponse.Status
|
||||
func (r ImportConfigurationResp) Status() string {
|
||||
if r.HTTPResponse != nil {
|
||||
return r.HTTPResponse.Status
|
||||
}
|
||||
return http.StatusText(0)
|
||||
}
|
||||
|
||||
// StatusCode returns HTTPResponse.StatusCode
|
||||
func (r ImportConfigurationResp) StatusCode() int {
|
||||
if r.HTTPResponse != nil {
|
||||
return r.HTTPResponse.StatusCode
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type GetQuotasResp struct {
|
||||
Body []byte
|
||||
HTTPResponse *http.Response
|
||||
JSON200 *ConfigQuotas
|
||||
JSON400 *ErrorResponse
|
||||
JSON500 *ErrorResponse
|
||||
}
|
||||
|
||||
// Status returns HTTPResponse.Status
|
||||
func (r GetQuotasResp) Status() string {
|
||||
if r.HTTPResponse != nil {
|
||||
return r.HTTPResponse.Status
|
||||
}
|
||||
return http.StatusText(0)
|
||||
}
|
||||
|
||||
// StatusCode returns HTTPResponse.StatusCode
|
||||
func (r GetQuotasResp) StatusCode() int {
|
||||
if r.HTTPResponse != nil {
|
||||
return r.HTTPResponse.StatusCode
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type ExchangeClusterIdentityTokenResp struct {
|
||||
Body []byte
|
||||
HTTPResponse *http.Response
|
||||
|
@ -692,6 +831,24 @@ func (c *ClientWithResponses) ReportClusterResourceBundleStatusWithResponse(ctx
|
|||
return ParseReportClusterResourceBundleStatusResp(rsp)
|
||||
}
|
||||
|
||||
// ImportConfigurationWithBodyWithResponse request with arbitrary body returning *ImportConfigurationResp
|
||||
func (c *ClientWithResponses) ImportConfigurationWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ImportConfigurationResp, error) {
|
||||
rsp, err := c.ImportConfigurationWithBody(ctx, contentType, body, reqEditors...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ParseImportConfigurationResp(rsp)
|
||||
}
|
||||
|
||||
// GetQuotasWithResponse request returning *GetQuotasResp
|
||||
func (c *ClientWithResponses) GetQuotasWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetQuotasResp, error) {
|
||||
rsp, err := c.GetQuotas(ctx, reqEditors...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ParseGetQuotasResp(rsp)
|
||||
}
|
||||
|
||||
// ExchangeClusterIdentityTokenWithBodyWithResponse request with arbitrary body returning *ExchangeClusterIdentityTokenResp
|
||||
func (c *ClientWithResponses) ExchangeClusterIdentityTokenWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ExchangeClusterIdentityTokenResp, error) {
|
||||
rsp, err := c.ExchangeClusterIdentityTokenWithBody(ctx, contentType, body, reqEditors...)
|
||||
|
@ -886,6 +1043,79 @@ func ParseReportClusterResourceBundleStatusResp(rsp *http.Response) (*ReportClus
|
|||
return response, nil
|
||||
}
|
||||
|
||||
// ParseImportConfigurationResp parses an HTTP response from a ImportConfigurationWithResponse call
|
||||
func ParseImportConfigurationResp(rsp *http.Response) (*ImportConfigurationResp, error) {
|
||||
bodyBytes, err := io.ReadAll(rsp.Body)
|
||||
defer func() { _ = rsp.Body.Close() }()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response := &ImportConfigurationResp{
|
||||
Body: bodyBytes,
|
||||
HTTPResponse: rsp,
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400:
|
||||
var dest ErrorResponse
|
||||
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.JSON400 = &dest
|
||||
|
||||
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500:
|
||||
var dest ErrorResponse
|
||||
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.JSON500 = &dest
|
||||
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// ParseGetQuotasResp parses an HTTP response from a GetQuotasWithResponse call
|
||||
func ParseGetQuotasResp(rsp *http.Response) (*GetQuotasResp, error) {
|
||||
bodyBytes, err := io.ReadAll(rsp.Body)
|
||||
defer func() { _ = rsp.Body.Close() }()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response := &GetQuotasResp{
|
||||
Body: bodyBytes,
|
||||
HTTPResponse: rsp,
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
|
||||
var dest ConfigQuotas
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
response.JSON400 = &dest
|
||||
|
||||
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500:
|
||||
var dest ErrorResponse
|
||||
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.JSON500 = &dest
|
||||
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// ParseExchangeClusterIdentityTokenResp parses an HTTP response from a ExchangeClusterIdentityTokenWithResponse call
|
||||
func ParseExchangeClusterIdentityTokenResp(rsp *http.Response) (*ExchangeClusterIdentityTokenResp, error) {
|
||||
bodyBytes, err := io.ReadAll(rsp.Body)
|
||||
|
@ -1073,6 +1303,61 @@ func (r *ReportClusterResourceBundleStatusResp) GetValue() *EmptyResponse {
|
|||
return &EmptyResponse{}
|
||||
}
|
||||
|
||||
// GetHTTPResponse implements apierror.APIResponse
|
||||
func (r *ImportConfigurationResp) GetHTTPResponse() *http.Response {
|
||||
return r.HTTPResponse
|
||||
}
|
||||
|
||||
// GetBadRequestError implements apierror.APIResponse
|
||||
func (r *ImportConfigurationResp) GetBadRequestError() (string, bool) {
|
||||
if r.JSON400 == nil {
|
||||
return "", false
|
||||
}
|
||||
return r.JSON400.Error, true
|
||||
}
|
||||
|
||||
// GetInternalServerError implements apierror.APIResponse
|
||||
func (r *ImportConfigurationResp) GetInternalServerError() (string, bool) {
|
||||
if r.JSON500 == nil {
|
||||
return "", false
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// GetValue implements apierror.APIResponse
|
||||
func (r *GetQuotasResp) GetValue() *ConfigQuotas {
|
||||
return r.JSON200
|
||||
}
|
||||
|
||||
// GetBadRequestError implements apierror.APIResponse
|
||||
func (r *GetQuotasResp) GetBadRequestError() (string, bool) {
|
||||
if r.JSON400 == nil {
|
||||
return "", false
|
||||
}
|
||||
return r.JSON400.Error, true
|
||||
}
|
||||
|
||||
// GetInternalServerError implements apierror.APIResponse
|
||||
func (r *GetQuotasResp) GetInternalServerError() (string, bool) {
|
||||
if r.JSON500 == nil {
|
||||
return "", false
|
||||
}
|
||||
return r.JSON500.Error, true
|
||||
}
|
||||
|
||||
// GetHTTPResponse implements apierror.APIResponse
|
||||
func (r *ExchangeClusterIdentityTokenResp) GetHTTPResponse() *http.Response {
|
||||
return r.HTTPResponse
|
||||
|
|
|
@ -13,4 +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[ConfigQuotas] = (*GetQuotasResp)(nil)
|
||||
)
|
||||
|
|
|
@ -62,6 +62,13 @@ type BundleStatusSuccess struct {
|
|||
Metadata map[string]string `json:"metadata"`
|
||||
}
|
||||
|
||||
// ConfigQuotas defines model for ConfigQuotas.
|
||||
type ConfigQuotas struct {
|
||||
Certificates int `json:"certificates"`
|
||||
Policies int `json:"policies"`
|
||||
Routes int `json:"routes"`
|
||||
}
|
||||
|
||||
// DownloadBundleResponse defines model for DownloadBundleResponse.
|
||||
type DownloadBundleResponse struct {
|
||||
// CaptureMetadataHeaders bundle metadata that need be picked up by the client from the download URL
|
||||
|
|
|
@ -148,7 +148,6 @@ paths:
|
|||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
|
||||
/reportUsage:
|
||||
post:
|
||||
description: Report usage for the cluster
|
||||
|
@ -176,6 +175,62 @@ paths:
|
|||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
|
||||
/config/import:
|
||||
post:
|
||||
description: |
|
||||
Apply the raw configuration directly to the cluster.
|
||||
This operation only has an effect before any other modifications to the
|
||||
cluster's configuration have been made.
|
||||
operationId: importConfiguration
|
||||
tags: [cluster]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
# TODO: add max payload size?
|
||||
application/octet-stream:
|
||||
schema:
|
||||
type: string
|
||||
contentMediaType: application/octet-stream
|
||||
contentEncoding: gzip
|
||||
description: type.googleapis.com/pomerium.config.Config
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
"400":
|
||||
description: Bad Request
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
/config/quotas:
|
||||
get:
|
||||
description: Get the cluster's current configuration quotas
|
||||
operationId: getQuotas
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ConfigQuotas"
|
||||
"400":
|
||||
description: Bad Request
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
components:
|
||||
parameters:
|
||||
bundleId:
|
||||
|
@ -333,3 +388,16 @@ components:
|
|||
- lastSignedInAt
|
||||
- pseudonymousEmail
|
||||
- pseudonymousId
|
||||
ConfigQuotas:
|
||||
type: object
|
||||
properties:
|
||||
certificates:
|
||||
type: integer
|
||||
policies:
|
||||
type: integer
|
||||
routes:
|
||||
type: integer
|
||||
required:
|
||||
- certificates
|
||||
- policies
|
||||
- routes
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
@ -29,6 +30,12 @@ type ServerInterface interface {
|
|||
// (POST /bundles/{bundleId}/status)
|
||||
ReportClusterResourceBundleStatus(w http.ResponseWriter, r *http.Request, bundleId BundleId)
|
||||
|
||||
// (POST /config/import)
|
||||
ImportConfiguration(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
// (GET /config/quotas)
|
||||
GetQuotas(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
// (POST /exchangeToken)
|
||||
ExchangeClusterIdentityToken(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
|
@ -60,6 +67,16 @@ func (_ Unimplemented) ReportClusterResourceBundleStatus(w http.ResponseWriter,
|
|||
w.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
// (POST /config/import)
|
||||
func (_ Unimplemented) ImportConfiguration(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
// (GET /config/quotas)
|
||||
func (_ Unimplemented) GetQuotas(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
// (POST /exchangeToken)
|
||||
func (_ Unimplemented) ExchangeClusterIdentityToken(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
|
@ -169,6 +186,40 @@ func (siw *ServerInterfaceWrapper) ReportClusterResourceBundleStatus(w http.Resp
|
|||
handler.ServeHTTP(w, r.WithContext(ctx))
|
||||
}
|
||||
|
||||
// ImportConfiguration operation middleware
|
||||
func (siw *ServerInterfaceWrapper) ImportConfiguration(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
ctx = context.WithValue(ctx, BearerAuthScopes, []string{})
|
||||
|
||||
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.ImportConfiguration(w, r)
|
||||
}))
|
||||
|
||||
for i := len(siw.HandlerMiddlewares) - 1; i >= 0; i-- {
|
||||
handler = siw.HandlerMiddlewares[i](handler)
|
||||
}
|
||||
|
||||
handler.ServeHTTP(w, r.WithContext(ctx))
|
||||
}
|
||||
|
||||
// GetQuotas operation middleware
|
||||
func (siw *ServerInterfaceWrapper) GetQuotas(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
ctx = context.WithValue(ctx, BearerAuthScopes, []string{})
|
||||
|
||||
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.GetQuotas(w, r)
|
||||
}))
|
||||
|
||||
for i := len(siw.HandlerMiddlewares) - 1; i >= 0; i-- {
|
||||
handler = siw.HandlerMiddlewares[i](handler)
|
||||
}
|
||||
|
||||
handler.ServeHTTP(w, r.WithContext(ctx))
|
||||
}
|
||||
|
||||
// ExchangeClusterIdentityToken operation middleware
|
||||
func (siw *ServerInterfaceWrapper) ExchangeClusterIdentityToken(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
@ -326,6 +377,12 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl
|
|||
r.Group(func(r chi.Router) {
|
||||
r.Post(options.BaseURL+"/bundles/{bundleId}/status", wrapper.ReportClusterResourceBundleStatus)
|
||||
})
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Post(options.BaseURL+"/config/import", wrapper.ImportConfiguration)
|
||||
})
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Get(options.BaseURL+"/config/quotas", wrapper.GetQuotas)
|
||||
})
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Post(options.BaseURL+"/exchangeToken", wrapper.ExchangeClusterIdentityToken)
|
||||
})
|
||||
|
@ -483,6 +540,74 @@ func (response ReportClusterResourceBundleStatus500JSONResponse) VisitReportClus
|
|||
return json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
type ImportConfigurationRequestObject struct {
|
||||
Body io.Reader
|
||||
}
|
||||
|
||||
type ImportConfigurationResponseObject interface {
|
||||
VisitImportConfigurationResponse(w http.ResponseWriter) error
|
||||
}
|
||||
|
||||
type ImportConfiguration200Response struct {
|
||||
}
|
||||
|
||||
func (response ImportConfiguration200Response) VisitImportConfigurationResponse(w http.ResponseWriter) error {
|
||||
w.WriteHeader(200)
|
||||
return nil
|
||||
}
|
||||
|
||||
type ImportConfiguration400JSONResponse ErrorResponse
|
||||
|
||||
func (response ImportConfiguration400JSONResponse) VisitImportConfigurationResponse(w http.ResponseWriter) error {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(400)
|
||||
|
||||
return json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
type ImportConfiguration500JSONResponse ErrorResponse
|
||||
|
||||
func (response ImportConfiguration500JSONResponse) VisitImportConfigurationResponse(w http.ResponseWriter) error {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(500)
|
||||
|
||||
return json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
type GetQuotasRequestObject struct {
|
||||
}
|
||||
|
||||
type GetQuotasResponseObject interface {
|
||||
VisitGetQuotasResponse(w http.ResponseWriter) error
|
||||
}
|
||||
|
||||
type GetQuotas200JSONResponse ConfigQuotas
|
||||
|
||||
func (response GetQuotas200JSONResponse) VisitGetQuotasResponse(w http.ResponseWriter) error {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(200)
|
||||
|
||||
return json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
type GetQuotas400JSONResponse ErrorResponse
|
||||
|
||||
func (response GetQuotas400JSONResponse) VisitGetQuotasResponse(w http.ResponseWriter) error {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(400)
|
||||
|
||||
return json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
type GetQuotas500JSONResponse ErrorResponse
|
||||
|
||||
func (response GetQuotas500JSONResponse) VisitGetQuotasResponse(w http.ResponseWriter) error {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(500)
|
||||
|
||||
return json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
type ExchangeClusterIdentityTokenRequestObject struct {
|
||||
Body *ExchangeClusterIdentityTokenJSONRequestBody
|
||||
}
|
||||
|
@ -567,6 +692,12 @@ type StrictServerInterface interface {
|
|||
// (POST /bundles/{bundleId}/status)
|
||||
ReportClusterResourceBundleStatus(ctx context.Context, request ReportClusterResourceBundleStatusRequestObject) (ReportClusterResourceBundleStatusResponseObject, error)
|
||||
|
||||
// (POST /config/import)
|
||||
ImportConfiguration(ctx context.Context, request ImportConfigurationRequestObject) (ImportConfigurationResponseObject, error)
|
||||
|
||||
// (GET /config/quotas)
|
||||
GetQuotas(ctx context.Context, request GetQuotasRequestObject) (GetQuotasResponseObject, error)
|
||||
|
||||
// (POST /exchangeToken)
|
||||
ExchangeClusterIdentityToken(ctx context.Context, request ExchangeClusterIdentityTokenRequestObject) (ExchangeClusterIdentityTokenResponseObject, error)
|
||||
|
||||
|
@ -710,6 +841,56 @@ func (sh *strictHandler) ReportClusterResourceBundleStatus(w http.ResponseWriter
|
|||
}
|
||||
}
|
||||
|
||||
// ImportConfiguration operation middleware
|
||||
func (sh *strictHandler) ImportConfiguration(w http.ResponseWriter, r *http.Request) {
|
||||
var request ImportConfigurationRequestObject
|
||||
|
||||
request.Body = r.Body
|
||||
|
||||
handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) {
|
||||
return sh.ssi.ImportConfiguration(ctx, request.(ImportConfigurationRequestObject))
|
||||
}
|
||||
for _, middleware := range sh.middlewares {
|
||||
handler = middleware(handler, "ImportConfiguration")
|
||||
}
|
||||
|
||||
response, err := handler(r.Context(), w, r, request)
|
||||
|
||||
if err != nil {
|
||||
sh.options.ResponseErrorHandlerFunc(w, r, err)
|
||||
} else if validResponse, ok := response.(ImportConfigurationResponseObject); ok {
|
||||
if err := validResponse.VisitImportConfigurationResponse(w); err != nil {
|
||||
sh.options.ResponseErrorHandlerFunc(w, r, err)
|
||||
}
|
||||
} else if response != nil {
|
||||
sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response))
|
||||
}
|
||||
}
|
||||
|
||||
// GetQuotas operation middleware
|
||||
func (sh *strictHandler) GetQuotas(w http.ResponseWriter, r *http.Request) {
|
||||
var request GetQuotasRequestObject
|
||||
|
||||
handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) {
|
||||
return sh.ssi.GetQuotas(ctx, request.(GetQuotasRequestObject))
|
||||
}
|
||||
for _, middleware := range sh.middlewares {
|
||||
handler = middleware(handler, "GetQuotas")
|
||||
}
|
||||
|
||||
response, err := handler(r.Context(), w, r, request)
|
||||
|
||||
if err != nil {
|
||||
sh.options.ResponseErrorHandlerFunc(w, r, err)
|
||||
} else if validResponse, ok := response.(GetQuotasResponseObject); ok {
|
||||
if err := validResponse.VisitGetQuotasResponse(w); err != nil {
|
||||
sh.options.ResponseErrorHandlerFunc(w, r, err)
|
||||
}
|
||||
} else if response != nil {
|
||||
sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response))
|
||||
}
|
||||
}
|
||||
|
||||
// ExchangeClusterIdentityToken operation middleware
|
||||
func (sh *strictHandler) ExchangeClusterIdentityToken(w http.ResponseWriter, r *http.Request) {
|
||||
var request ExchangeClusterIdentityTokenRequestObject
|
||||
|
|
366
pkg/zero/importutil/namegen.go
Normal file
366
pkg/zero/importutil/namegen.go
Normal file
|
@ -0,0 +1,366 @@
|
|||
package importutil
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"iter"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/cespare/xxhash/v2"
|
||||
configpb "github.com/pomerium/pomerium/pkg/grpc/config"
|
||||
)
|
||||
|
||||
func GenerateCertName(cert *x509.Certificate) *string {
|
||||
var out string
|
||||
if cert.IsCA {
|
||||
if cert.Subject.CommonName != "" {
|
||||
out = cert.Subject.CommonName
|
||||
} else {
|
||||
out = cert.Subject.String()
|
||||
}
|
||||
} else {
|
||||
if cert.Subject.CommonName != "" {
|
||||
out = cert.Subject.CommonName
|
||||
} else if len(cert.DNSNames) > 0 {
|
||||
out = pickDNSName(cert.DNSNames)
|
||||
} else {
|
||||
out = "leaf"
|
||||
}
|
||||
}
|
||||
|
||||
if strings.Contains(out, "-") {
|
||||
out = strings.ReplaceAll(out, " ", "_")
|
||||
} else {
|
||||
out = strings.ReplaceAll(out, " ", "-")
|
||||
}
|
||||
|
||||
suffix := fmt.Sprintf("@%d", cert.NotBefore.Unix())
|
||||
if !strings.Contains(out, suffix) {
|
||||
out += suffix
|
||||
}
|
||||
|
||||
return &out
|
||||
}
|
||||
|
||||
func pickDNSName(names []string) string {
|
||||
if len(names) == 1 {
|
||||
return names[0]
|
||||
}
|
||||
// prefer wildcard names
|
||||
for _, name := range names {
|
||||
if strings.HasPrefix(name, "*.") {
|
||||
return name
|
||||
}
|
||||
}
|
||||
return names[0]
|
||||
}
|
||||
|
||||
func GenerateRouteNames(routes []*configpb.Route) []string {
|
||||
out := make([]string, len(routes))
|
||||
prefixes := make([][]string, len(routes))
|
||||
indexes := map[*configpb.Route]int{}
|
||||
trie := newDomainTrie()
|
||||
for i, route := range routes {
|
||||
trie.Insert(route)
|
||||
indexes[route] = i
|
||||
}
|
||||
trie.Compact()
|
||||
|
||||
trie.Walk(func(parents []string, node *domainTreeNode) {
|
||||
for subdomain, child := range node.children {
|
||||
for route, name := range differentiateRoutes(subdomain, child.routes) {
|
||||
idx := indexes[route]
|
||||
out[idx] = name
|
||||
prefixes[idx] = parents
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
seen := map[string]int{}
|
||||
for idx, name := range out {
|
||||
prevIdx, ok := seen[name]
|
||||
if !ok {
|
||||
out[idx] = name
|
||||
seen[name] = idx
|
||||
continue
|
||||
}
|
||||
delete(seen, name)
|
||||
var b strings.Builder
|
||||
b.WriteString(name)
|
||||
var prevNameB strings.Builder
|
||||
prevNameB.WriteString(out[prevIdx])
|
||||
var nameB strings.Builder
|
||||
nameB.WriteString(name)
|
||||
minLen := min(len(prefixes[prevIdx]), len(prefixes[idx]))
|
||||
maxLen := max(len(prefixes[prevIdx]), len(prefixes[idx]))
|
||||
for j := range maxLen {
|
||||
if j >= minLen {
|
||||
if j < len(prefixes[prevIdx]) {
|
||||
prevNameB.WriteRune('-')
|
||||
prevNameB.WriteString(strings.ReplaceAll(prefixes[prevIdx][j], ".", "-"))
|
||||
} else {
|
||||
nameB.WriteRune('-')
|
||||
nameB.WriteString(strings.ReplaceAll(prefixes[idx][j], ".", "-"))
|
||||
}
|
||||
continue
|
||||
}
|
||||
prevPrefix, prefix := trimCommonSubdomains(prefixes[prevIdx][j], prefixes[idx][j])
|
||||
if prevPrefix != prefix {
|
||||
prevNameB.WriteRune('-')
|
||||
prevNameB.WriteString(prevPrefix)
|
||||
nameB.WriteRune('-')
|
||||
nameB.WriteString(prefix)
|
||||
}
|
||||
}
|
||||
|
||||
out[prevIdx] = prevNameB.String()
|
||||
out[idx] = nameB.String()
|
||||
seen[out[prevIdx]] = prevIdx
|
||||
seen[out[idx]] = idx
|
||||
}
|
||||
|
||||
for i, name := range out {
|
||||
if name == "" {
|
||||
out[i] = fmt.Sprintf("route-%d", i)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func trimCommonSubdomains(a, b string) (string, string) {
|
||||
aParts := strings.Split(a, ".")
|
||||
bParts := strings.Split(b, ".")
|
||||
for len(aParts) > 1 && len(bParts) > 1 && aParts[0] == bParts[0] {
|
||||
aParts = aParts[1:]
|
||||
bParts = bParts[1:]
|
||||
}
|
||||
for len(aParts) > 1 && len(bParts) > 1 && aParts[len(aParts)-1] == bParts[len(bParts)-1] {
|
||||
aParts = aParts[:len(aParts)-1]
|
||||
bParts = bParts[:len(bParts)-1]
|
||||
}
|
||||
return strings.Join(aParts, "-"), strings.Join(bParts, "-")
|
||||
}
|
||||
|
||||
func differentiateRoutes(subdomain string, routes []*configpb.Route) iter.Seq2[*configpb.Route, string] {
|
||||
return func(yield func(*configpb.Route, string) bool) {
|
||||
if len(routes) == 1 {
|
||||
yield(routes[0], subdomain)
|
||||
return
|
||||
}
|
||||
names := map[string][]*configpb.Route{}
|
||||
replacer := strings.NewReplacer(
|
||||
" ", "_",
|
||||
"/", "-",
|
||||
"*", "",
|
||||
)
|
||||
simplePathName := func(pathOrPrefix string) string {
|
||||
if p, err := url.PathUnescape(pathOrPrefix); err == nil {
|
||||
pathOrPrefix = strings.ToLower(p)
|
||||
}
|
||||
return replacer.Replace(strings.Trim(pathOrPrefix, "/ "))
|
||||
}
|
||||
genericRegexCounter := 0
|
||||
regexName := func(regex string) string {
|
||||
if path, pattern, ok := commonRegexPattern(regex); ok {
|
||||
name := simplePathName(path)
|
||||
if name == "" && pattern != "" {
|
||||
return "re-any"
|
||||
}
|
||||
return fmt.Sprintf("re-%s-prefix", name)
|
||||
}
|
||||
genericRegexCounter++
|
||||
return fmt.Sprintf("re-%d", genericRegexCounter)
|
||||
}
|
||||
var prefixCount, pathCount int
|
||||
for _, route := range routes {
|
||||
// each route will have the same domain, but a unique prefix/path/regex.
|
||||
var name string
|
||||
switch {
|
||||
case route.Prefix != "":
|
||||
name = simplePathName(route.Prefix)
|
||||
prefixCount++
|
||||
case route.Path != "":
|
||||
name = simplePathName(route.Path)
|
||||
pathCount++
|
||||
case route.Regex != "":
|
||||
name = regexName(route.Regex)
|
||||
}
|
||||
names[name] = append(names[name], route)
|
||||
}
|
||||
|
||||
nameCounts := map[uint64]int{}
|
||||
for name, routes := range names {
|
||||
if len(routes) == 1 {
|
||||
var b strings.Builder
|
||||
b.WriteString(subdomain)
|
||||
if name != "" {
|
||||
b.WriteRune('-')
|
||||
b.WriteString(name)
|
||||
}
|
||||
if !yield(routes[0], b.String()) {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// assign a "-prefix" or "-path" suffix to routes with the same name
|
||||
// but different configurations
|
||||
prefixSuffix := "-prefix"
|
||||
pathSuffix := "-path"
|
||||
switch {
|
||||
case prefixCount == 1 && pathCount == 1:
|
||||
pathSuffix = ""
|
||||
case prefixCount > 1 && pathCount == 1:
|
||||
prefixSuffix = ""
|
||||
case prefixCount == 1 && pathCount > 1:
|
||||
pathSuffix = ""
|
||||
case prefixCount == 0:
|
||||
pathSuffix = ""
|
||||
case pathCount == 0:
|
||||
prefixSuffix = ""
|
||||
}
|
||||
var b strings.Builder
|
||||
for _, route := range routes {
|
||||
b.Reset()
|
||||
b.WriteString(subdomain)
|
||||
b.WriteRune('-')
|
||||
b.WriteString(name)
|
||||
if route.Prefix != "" {
|
||||
b.WriteString(prefixSuffix)
|
||||
} else if route.Path != "" {
|
||||
b.WriteString(pathSuffix)
|
||||
}
|
||||
|
||||
sum := xxhash.Sum64String(b.String())
|
||||
nameCounts[sum]++
|
||||
if c := nameCounts[sum]; c > 1 {
|
||||
b.WriteString(" (")
|
||||
b.WriteString(strconv.Itoa(c))
|
||||
b.WriteString(")")
|
||||
}
|
||||
if !yield(route, b.String()) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type domainTreeNode struct {
|
||||
parent *domainTreeNode
|
||||
children map[string]*domainTreeNode
|
||||
routes []*configpb.Route
|
||||
}
|
||||
|
||||
func (n *domainTreeNode) insert(key string, route *configpb.Route) *domainTreeNode {
|
||||
if existing, ok := n.children[key]; ok {
|
||||
if route != nil {
|
||||
existing.routes = append(existing.routes, route)
|
||||
}
|
||||
return existing
|
||||
}
|
||||
node := &domainTreeNode{
|
||||
parent: n,
|
||||
children: map[string]*domainTreeNode{},
|
||||
}
|
||||
if route != nil {
|
||||
node.routes = append(node.routes, route)
|
||||
}
|
||||
n.children[key] = node
|
||||
return node
|
||||
}
|
||||
|
||||
type domainTrie struct {
|
||||
root *domainTreeNode
|
||||
}
|
||||
|
||||
func newDomainTrie() *domainTrie {
|
||||
t := &domainTrie{
|
||||
root: &domainTreeNode{
|
||||
children: map[string]*domainTreeNode{},
|
||||
},
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
type walkFn = func(parents []string, node *domainTreeNode)
|
||||
|
||||
func (t *domainTrie) Walk(fn walkFn) {
|
||||
t.root.walk(nil, fn)
|
||||
}
|
||||
|
||||
func (n *domainTreeNode) walk(prefix []string, fn walkFn) {
|
||||
for key, child := range n.children {
|
||||
fn(append(prefix, key), child)
|
||||
child.walk(append(prefix, key), fn)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *domainTrie) Insert(route *configpb.Route) {
|
||||
u, _ := url.Parse(route.From)
|
||||
if u == nil {
|
||||
// ignore invalid urls, they will be assigned generic fallback names
|
||||
return
|
||||
}
|
||||
parts := strings.Split(u.Hostname(), ".")
|
||||
slices.Reverse(parts)
|
||||
cur := t.root
|
||||
for _, part := range parts[:len(parts)-1] {
|
||||
cur = cur.insert(part, nil)
|
||||
}
|
||||
cur.insert(parts[len(parts)-1], route)
|
||||
}
|
||||
|
||||
func (t *domainTrie) Compact() {
|
||||
t.root.compact()
|
||||
}
|
||||
|
||||
func (n *domainTreeNode) compact() {
|
||||
for _, child := range n.children {
|
||||
child.compact()
|
||||
}
|
||||
if n.parent == nil {
|
||||
return
|
||||
}
|
||||
var firstKey string
|
||||
var firstChild *domainTreeNode
|
||||
for key, child := range n.children {
|
||||
firstKey, firstChild = key, child
|
||||
break
|
||||
}
|
||||
// compact intermediate nodes, not leaves
|
||||
if len(n.children) == 1 && len(firstChild.routes) == 0 {
|
||||
firstChild.parent = n.parent
|
||||
for key, child := range n.parent.children {
|
||||
if child == n {
|
||||
delete(n.parent.children, key)
|
||||
n.parent.children[fmt.Sprintf("%s.%s", key, firstKey)] = firstChild
|
||||
*n = domainTreeNode{}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Matches an optional leading slash, then zero or more path segments separated
|
||||
// by '/' characters, where the final path segment contains one of the following
|
||||
// commonly used regex patterns used to match path segments:
|
||||
// - '.*' or '.+'
|
||||
// - '[^/]*', '[^/]+', '[^\/]*', or '[^\/]+'
|
||||
// - '\w*' or '\w+'
|
||||
// - any of the above patterns, enclosed by parentheses
|
||||
// The first capture group contains the path leading up to the wildcard segment
|
||||
// and can be empty or have leading/trailing slashes. The second capture group
|
||||
// contains the wildcard segment with no leading or trailing slashes.
|
||||
var pathPrefixMatchRegex = regexp.MustCompile(`^(\/?(?:\w+\/)*)(\(?(?:\.\+|\.\*|\[\^\\?\/\][\+\*]|\\w[\+\*])\)?)$`)
|
||||
|
||||
func commonRegexPattern(re string) (path string, pattern string, found bool) {
|
||||
re = strings.TrimSuffix(strings.TrimPrefix(re, "^"), "$")
|
||||
if match := pathPrefixMatchRegex.FindStringSubmatch(re); match != nil {
|
||||
return match[1], match[2], true
|
||||
}
|
||||
return "", "", false
|
||||
}
|
377
pkg/zero/importutil/namegen_test.go
Normal file
377
pkg/zero/importutil/namegen_test.go
Normal file
|
@ -0,0 +1,377 @@
|
|||
package importutil_test
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"fmt"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
configpb "github.com/pomerium/pomerium/pkg/grpc/config"
|
||||
"github.com/pomerium/pomerium/pkg/zero/importutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGenerateCertName(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
input x509.Certificate
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "cert with common name",
|
||||
input: x509.Certificate{
|
||||
IsCA: true,
|
||||
Subject: pkix.Name{CommonName: "sample"},
|
||||
},
|
||||
expected: "sample",
|
||||
},
|
||||
{
|
||||
name: "cert with common name and other subject fields",
|
||||
input: x509.Certificate{
|
||||
IsCA: true,
|
||||
Subject: pkix.Name{
|
||||
CommonName: "sample",
|
||||
Organization: []string{"foo"},
|
||||
OrganizationalUnit: []string{"bar"},
|
||||
},
|
||||
},
|
||||
expected: "sample",
|
||||
},
|
||||
{
|
||||
name: "common name with spaces",
|
||||
input: x509.Certificate{
|
||||
IsCA: true,
|
||||
Subject: pkix.Name{CommonName: "sample name"},
|
||||
},
|
||||
expected: "sample-name",
|
||||
},
|
||||
{
|
||||
name: "common name with special characters",
|
||||
input: x509.Certificate{
|
||||
IsCA: true,
|
||||
Subject: pkix.Name{CommonName: "sample common-name"},
|
||||
},
|
||||
expected: "sample_common-name",
|
||||
},
|
||||
{
|
||||
name: "cert with other subject fields but no common name",
|
||||
input: x509.Certificate{
|
||||
IsCA: true,
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"foo"},
|
||||
OrganizationalUnit: []string{"bar"},
|
||||
},
|
||||
},
|
||||
expected: "OU=bar,O=foo",
|
||||
},
|
||||
{
|
||||
name: "leaf cert with common name",
|
||||
input: x509.Certificate{
|
||||
IsCA: false,
|
||||
Subject: pkix.Name{CommonName: "sample"},
|
||||
},
|
||||
expected: "sample",
|
||||
},
|
||||
{
|
||||
name: "leaf cert with dns name",
|
||||
input: x509.Certificate{
|
||||
IsCA: false,
|
||||
DNSNames: []string{"example.com"},
|
||||
},
|
||||
expected: "example.com",
|
||||
},
|
||||
{
|
||||
name: "leaf cert with dns names",
|
||||
input: x509.Certificate{
|
||||
IsCA: false,
|
||||
DNSNames: []string{"example.com", "*.example.com"},
|
||||
},
|
||||
expected: "*.example.com",
|
||||
},
|
||||
{
|
||||
name: "leaf cert with neither common name nor dns names",
|
||||
input: x509.Certificate{
|
||||
IsCA: false,
|
||||
},
|
||||
expected: "leaf",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
nbf := time.Now()
|
||||
tc.input.NotBefore = nbf
|
||||
tc.expected += fmt.Sprintf("@%d", nbf.Unix())
|
||||
out := importutil.GenerateCertName(&tc.input)
|
||||
assert.Equal(t, tc.expected, *out)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateRouteNames(t *testing.T) {
|
||||
const testExample = "https://test.example.com"
|
||||
cases := []struct {
|
||||
name string
|
||||
input []*configpb.Route
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "single domain name",
|
||||
input: []*configpb.Route{
|
||||
{From: "https://foo.example.com"},
|
||||
{From: "https://bar.example.com"},
|
||||
{From: "https://baz.example.com"},
|
||||
},
|
||||
expected: []string{"foo", "bar", "baz"},
|
||||
},
|
||||
{
|
||||
name: "multiple domain names, unique subdomains",
|
||||
input: []*configpb.Route{
|
||||
{From: "https://a.domain1.example.com"},
|
||||
{From: "https://b.domain1.example.com"},
|
||||
{From: "https://c.domain1.example.com"},
|
||||
{From: "https://d.domain2.example.com"},
|
||||
{From: "https://e.domain2.example.com"},
|
||||
{From: "https://f.domain2.example.com"},
|
||||
},
|
||||
expected: []string{"a", "b", "c", "d", "e", "f"},
|
||||
},
|
||||
{
|
||||
name: "multiple domain names, conflicting subdomains",
|
||||
input: []*configpb.Route{
|
||||
{From: "https://a.domain1.example.com"},
|
||||
{From: "https://b.domain1.example.com"},
|
||||
{From: "https://c.domain1.example.com"},
|
||||
{From: "https://a.domain2.example.com"},
|
||||
{From: "https://b.domain2.example.com"},
|
||||
{From: "https://c.domain2.example.com"},
|
||||
},
|
||||
expected: []string{
|
||||
"a-domain1",
|
||||
"b-domain1",
|
||||
"c-domain1",
|
||||
"a-domain2",
|
||||
"b-domain2",
|
||||
"c-domain2",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple nested domain names, conflicting subdomains",
|
||||
input: []*configpb.Route{
|
||||
{From: "https://a.domain1.domain2.domain3.example.com"},
|
||||
{From: "https://b.domain1.domain2.domain3.example.com"},
|
||||
{From: "https://c.domain1.domain2.domain3.example.com"},
|
||||
{From: "https://a.domain1.domain2.domain4.example.com"},
|
||||
{From: "https://b.domain1.domain2.domain4.example.com"},
|
||||
{From: "https://c.domain1.domain2.domain4.example.com"},
|
||||
|
||||
{From: "https://a.domain1.domain2.domain5.example.com"},
|
||||
{From: "https://b.domain2.domain2.domain5.example.com"},
|
||||
{From: "https://c.domain3.domain2.domain5.example.com"},
|
||||
{From: "https://a.domain1.domain2.domain6.example.com"},
|
||||
{From: "https://b.domain2.domain2.domain6.example.com"},
|
||||
{From: "https://c.domain3.domain2.domain6.example.com"},
|
||||
},
|
||||
expected: []string{
|
||||
"a-domain3",
|
||||
"b-domain3",
|
||||
"c-domain3",
|
||||
"a-domain4",
|
||||
"b-domain4",
|
||||
"c-domain4",
|
||||
|
||||
"a-domain5",
|
||||
"b-domain5",
|
||||
"c-domain5",
|
||||
"a-domain6",
|
||||
"b-domain6",
|
||||
"c-domain6",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "conflicting subdomain names nested at different levels",
|
||||
input: []*configpb.Route{
|
||||
{From: "https://a.domain1.domain2.example.com"},
|
||||
{From: "https://a.domain1.example.com"},
|
||||
{From: "https://a.example.com"},
|
||||
{From: "https://a.domain3.domain2.example.com"},
|
||||
{From: "https://a.domain3.example.com"},
|
||||
},
|
||||
expected: []string{
|
||||
"a-domain2-domain1",
|
||||
"a-domain1",
|
||||
"a",
|
||||
"a-domain2-domain3",
|
||||
"a-domain3",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "conflicting subdomain names nested at different levels, unique paths",
|
||||
input: []*configpb.Route{
|
||||
{From: "https://a.domain1.domain2.example.com"},
|
||||
{From: "https://a.domain1.example.com"},
|
||||
{From: "https://a.example.com"},
|
||||
},
|
||||
expected: []string{
|
||||
"a-domain2-domain1",
|
||||
"a-domain1",
|
||||
"a",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "same domain, separate prefix options",
|
||||
input: []*configpb.Route{
|
||||
{From: testExample, Prefix: "/a"},
|
||||
{From: testExample, Prefix: "/b"},
|
||||
{From: testExample, Prefix: "/c"},
|
||||
},
|
||||
expected: []string{"test-a", "test-b", "test-c"},
|
||||
},
|
||||
{
|
||||
name: "same domain, mixed prefix/path options",
|
||||
input: []*configpb.Route{
|
||||
{From: testExample, Prefix: "/a"},
|
||||
{From: testExample, Path: "/b"},
|
||||
{From: testExample, Prefix: "/c"},
|
||||
{From: testExample, Path: "/d"},
|
||||
},
|
||||
expected: []string{"test-a", "test-b", "test-c", "test-d"},
|
||||
},
|
||||
{
|
||||
name: "same domain, name-conflicting prefix/path options (1 prefix/1 path)",
|
||||
input: []*configpb.Route{
|
||||
{From: testExample, Prefix: "/a/"},
|
||||
{From: testExample, Path: "/a"},
|
||||
},
|
||||
expected: []string{"test-a-prefix", "test-a"},
|
||||
},
|
||||
{
|
||||
name: "same domain, name-conflicting prefix/path options (more prefixes than paths)",
|
||||
input: []*configpb.Route{
|
||||
{From: testExample, Prefix: "/a/"},
|
||||
{From: testExample, Prefix: "/b/"},
|
||||
{From: testExample, Prefix: "/c/"},
|
||||
{From: testExample, Path: "/a"},
|
||||
},
|
||||
expected: []string{"test-a", "test-b", "test-c", "test-a-path"},
|
||||
},
|
||||
{
|
||||
name: "same domain, name-conflicting prefix/path options (more paths than prefixes)",
|
||||
input: []*configpb.Route{
|
||||
{From: testExample, Path: "/a"},
|
||||
{From: testExample, Path: "/b"},
|
||||
{From: testExample, Path: "/c"},
|
||||
{From: testExample, Prefix: "/a/"},
|
||||
},
|
||||
expected: []string{"test-a", "test-b", "test-c", "test-a-prefix"},
|
||||
},
|
||||
{
|
||||
name: "same domain, name-conflicting path options, duplicate names",
|
||||
input: []*configpb.Route{
|
||||
{From: testExample, Path: "/a"},
|
||||
{From: testExample, Path: "/a/"},
|
||||
},
|
||||
expected: []string{"test-a", "test-a (2)"},
|
||||
},
|
||||
{
|
||||
name: "same domain, name-conflicting prefix options, duplicate names",
|
||||
input: []*configpb.Route{
|
||||
{From: testExample, Prefix: "/a"},
|
||||
{From: testExample, Prefix: "/a/"},
|
||||
},
|
||||
expected: []string{"test-a", "test-a (2)"},
|
||||
},
|
||||
{
|
||||
name: "missing domain name",
|
||||
input: []*configpb.Route{{From: "https://:1234"}},
|
||||
expected: []string{"route-0"},
|
||||
},
|
||||
{
|
||||
name: "invalid URL",
|
||||
input: []*configpb.Route{{From: "https://\x7f"}},
|
||||
expected: []string{"route-0"},
|
||||
},
|
||||
{
|
||||
name: "regex paths",
|
||||
input: []*configpb.Route{
|
||||
{From: testExample, Regex: `/a/(.*)/b`},
|
||||
{From: testExample, Regex: `/a/(foo|bar)/b`},
|
||||
{From: testExample, Regex: `/(authorize.*|login|logout)`},
|
||||
{From: testExample, Regex: `/foo.+=-())(*+=,;:@~!'''-+_/.*`},
|
||||
{From: testExample, Regex: `/*`},
|
||||
{From: testExample, Regex: `/other/(.*)`},
|
||||
{From: testExample, Regex: `/other/.*`},
|
||||
{From: testExample, Regex: `/other/([^/]+)`},
|
||||
{From: testExample, Regex: `/other/([^/]*)`},
|
||||
{From: testExample, Regex: `/other/([^\/]+)`},
|
||||
{From: testExample, Regex: `/other/([^\/]*)`},
|
||||
{From: testExample, Regex: `/other/[^/]+`},
|
||||
{From: testExample, Regex: `/other/[^/]*`},
|
||||
{From: testExample, Regex: `/other/[^\/]+`},
|
||||
{From: testExample, Regex: `/other/[^\/]*`},
|
||||
{From: testExample, Regex: `/foo/bar/baz/.*`},
|
||||
{From: testExample, Regex: `/.*`},
|
||||
{From: testExample, Regex: `/.*`},
|
||||
{From: testExample, Regex: `/(.*)`},
|
||||
{From: testExample, Regex: `/.+`},
|
||||
{From: testExample, Regex: `/(.+)`},
|
||||
{From: testExample, Regex: `/([^/]+)`},
|
||||
{From: testExample, Regex: `/([^/]*)`},
|
||||
{From: testExample, Regex: `/([^\/]+)`},
|
||||
{From: testExample, Regex: `/([^\/]*)`},
|
||||
{From: testExample, Regex: `/[^/]+`},
|
||||
{From: testExample, Regex: `/[^/]*`},
|
||||
{From: testExample, Regex: `/[^\/]+`},
|
||||
{From: testExample, Regex: `/[^\/]*`},
|
||||
{From: testExample, Regex: `.+`},
|
||||
{From: testExample, Regex: `(.+)`},
|
||||
{From: testExample, Regex: `([^/]+)`},
|
||||
{From: testExample, Regex: `([^/]*)`},
|
||||
{From: testExample, Regex: `([^\/]+)`},
|
||||
{From: testExample, Regex: `([^\/]*)`},
|
||||
{From: testExample, Regex: `[^/]+`},
|
||||
{From: testExample, Regex: `[^/]*`},
|
||||
{From: testExample, Regex: `[^\/]+`},
|
||||
{From: testExample, Regex: `[^\/]*`},
|
||||
{From: testExample, Regex: `\w+`},
|
||||
{From: testExample, Regex: `\w*`},
|
||||
{From: testExample, Regex: `/\w+`},
|
||||
{From: testExample, Regex: `/\w*`},
|
||||
{From: testExample, Regex: `/(\w+)`},
|
||||
{From: testExample, Regex: `/(\w*)`},
|
||||
{From: testExample, Regex: `foo/.*`},
|
||||
{From: testExample, Regex: `/foo/.*`},
|
||||
{From: testExample, Regex: `/foo/\w+`},
|
||||
{From: testExample, Regex: `/foo/\w*`},
|
||||
},
|
||||
expected: slices.Collect(func(yield func(string) bool) {
|
||||
yield("test-re-1")
|
||||
yield("test-re-2")
|
||||
yield("test-re-3")
|
||||
yield("test-re-4")
|
||||
yield("test-re-5")
|
||||
yield("test-re-other-prefix")
|
||||
for i := 2; i <= 10; i++ {
|
||||
yield(fmt.Sprintf("test-re-other-prefix (%d)", i))
|
||||
}
|
||||
yield("test-re-foo-bar-baz-prefix")
|
||||
yield("test-re-any")
|
||||
for i := 2; i <= 29; i++ {
|
||||
yield(fmt.Sprintf("test-re-any (%d)", i))
|
||||
}
|
||||
yield("test-re-foo-prefix")
|
||||
yield("test-re-foo-prefix (2)")
|
||||
yield("test-re-foo-prefix (3)")
|
||||
yield("test-re-foo-prefix (4)")
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert.Equal(t, tc.expected, importutil.GenerateRouteNames(tc.input))
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue