diff --git a/endpoints/publicProxy/config.go b/endpoints/publicProxy/config.go index 2c9284a4..143a2e0f 100644 --- a/endpoints/publicProxy/config.go +++ b/endpoints/publicProxy/config.go @@ -3,6 +3,7 @@ package publicProxy import ( "context" "crypto/md5" + "time" "github.com/michaelquigley/cf" "github.com/openziti/zrok/endpoints" @@ -22,6 +23,7 @@ type Config struct { Interstitial *InterstitialConfig Oauth *OauthConfig SecretsAccess *SecretsAccessConfig + SecretsCache *SecretsCacheConfig Tls *endpoints.TlsConfig } @@ -57,6 +59,13 @@ type SecretsAccessConfig struct { ServiceName string } +type SecretsCacheConfig struct { + Capacity int + Shards int + TTL time.Duration + EvictionPercentage int +} + func (p *OauthProviderConfig) GetEndpoint() oauth2.Endpoint { return oauth2.Endpoint{ AuthURL: p.AuthURL, @@ -68,6 +77,12 @@ func DefaultConfig() *Config { return &Config{ Identity: "public", Address: "0.0.0.0:8080", + SecretsCache: &SecretsCacheConfig{ + Capacity: 10000, + Shards: 10, + TTL: 2 * time.Hour, + EvictionPercentage: 10, + }, } } diff --git a/endpoints/publicProxy/http.go b/endpoints/publicProxy/http.go index 5b0c8e9c..37e40807 100644 --- a/endpoints/publicProxy/http.go +++ b/endpoints/publicProxy/http.go @@ -153,18 +153,18 @@ func hostTargetReverseProxy(cfg *Config, ctx ziti.Context) *httputil.ReverseProx return &httputil.ReverseProxy{Director: director} } -func shareHandler(handler http.Handler, pcfg *Config, key []byte, ctx ziti.Context) http.HandlerFunc { +func shareHandler(handler http.Handler, cfg *Config, key []byte, ctx ziti.Context) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - shrToken := resolveService(pcfg.HostMatch, r.Host) + shrToken := resolveService(cfg.HostMatch, r.Host) if shrToken != "" { if svc, found := endpoints.GetRefreshedService(shrToken, ctx); found { - if cfg, found := svc.Config[sdk.ZrokProxyConfig]; found { - if r.Method != http.MethodOptions && (pcfg.Interstitial != nil && pcfg.Interstitial.Enabled) { + if proxyConfig, found := svc.Config[sdk.ZrokProxyConfig]; found { + if r.Method != http.MethodOptions && (cfg.Interstitial != nil && cfg.Interstitial.Enabled) { sendInterstitial := true - if len(pcfg.Interstitial.UserAgentPrefixes) > 0 { + if len(cfg.Interstitial.UserAgentPrefixes) > 0 { ua := r.Header.Get("User-Agent") matched := false - for _, prefix := range pcfg.Interstitial.UserAgentPrefixes { + for _, prefix := range cfg.Interstitial.UserAgentPrefixes { if strings.HasPrefix(ua, prefix) { matched = true break @@ -175,13 +175,13 @@ func shareHandler(handler http.Handler, pcfg *Config, key []byte, ctx ziti.Conte } } if sendInterstitial { - if v, istlFound := cfg["interstitial"]; istlFound { + if v, istlFound := proxyConfig["interstitial"]; istlFound { if istlEnabled, ok := v.(bool); ok && istlEnabled { skip := r.Header.Get("skip_zrok_interstitial") _, zrokOkErr := r.Cookie("zrok_interstitial") if skip == "" && zrokOkErr != nil { logrus.Debugf("forcing interstitial for '%v'", r.URL) - interstitialUi.WriteInterstitialAnnounce(w, pcfg.Interstitial.HtmlPath) + interstitialUi.WriteInterstitialAnnounce(w, cfg.Interstitial.HtmlPath) return } } @@ -189,7 +189,7 @@ func shareHandler(handler http.Handler, pcfg *Config, key []byte, ctx ziti.Conte } } - if scheme, found := cfg["auth_scheme"]; found { + if scheme, found := proxyConfig["auth_scheme"]; found { switch scheme { case string(sdk.None): logrus.Debugf("auth scheme none '%v'", shrToken) @@ -206,7 +206,7 @@ func shareHandler(handler http.Handler, pcfg *Config, key []byte, ctx ziti.Conte return } authed := false - if v, found := cfg["basic_auth"]; found { + if v, found := proxyConfig["basic_auth"]; found { if basicAuth, ok := v.(map[string]interface{}); ok { if v, found := basicAuth["users"]; found { if arr, ok := v.([]interface{}); ok { @@ -247,7 +247,7 @@ func shareHandler(handler http.Handler, pcfg *Config, key []byte, ctx ziti.Conte case string(sdk.Oauth): logrus.Debugf("auth scheme oauth '%v'", shrToken) - if oauthCfg, found := cfg["oauth"]; found { + if oauthCfg, found := proxyConfig["oauth"]; found { if provider, found := oauthCfg.(map[string]interface{})["provider"]; found { var authCheckInterval time.Duration if checkInterval, found := oauthCfg.(map[string]interface{})["authorization_check_interval"]; !found { @@ -268,34 +268,34 @@ func shareHandler(handler http.Handler, pcfg *Config, key []byte, ctx ziti.Conte cookie, err := r.Cookie("zrok-access") if err != nil { logrus.Errorf("unable to get 'zrok-access' cookie: %v", err) - oauthLoginRequired(w, r, pcfg.Oauth, provider.(string), target, authCheckInterval) + oauthLoginRequired(w, r, cfg.Oauth, provider.(string), target, authCheckInterval) return } tkn, err := jwt.ParseWithClaims(cookie.Value, &ZrokClaims{}, func(t *jwt.Token) (interface{}, error) { - if pcfg.Oauth == nil { + if cfg.Oauth == nil { return nil, fmt.Errorf("missing oauth configuration for access point; unable to parse jwt") } return key, nil }) if err != nil { logrus.Errorf("unable to parse jwt: %v", err) - oauthLoginRequired(w, r, pcfg.Oauth, provider.(string), target, authCheckInterval) + oauthLoginRequired(w, r, cfg.Oauth, provider.(string), target, authCheckInterval) return } claims := tkn.Claims.(*ZrokClaims) if claims.Provider != provider { logrus.Error("provider mismatch; restarting auth flow") - oauthLoginRequired(w, r, pcfg.Oauth, provider.(string), target, authCheckInterval) + oauthLoginRequired(w, r, cfg.Oauth, provider.(string), target, authCheckInterval) return } if claims.AuthorizationCheckInterval != authCheckInterval { logrus.Error("authorization check interval mismatch; restarting auth flow") - oauthLoginRequired(w, r, pcfg.Oauth, provider.(string), target, authCheckInterval) + oauthLoginRequired(w, r, cfg.Oauth, provider.(string), target, authCheckInterval) return } if claims.Audience != r.Host { logrus.Errorf("audience claim '%s' does not match requested host '%s'; restarting auth flow", claims.Audience, r.Host) - oauthLoginRequired(w, r, pcfg.Oauth, provider.(string), target, authCheckInterval) + oauthLoginRequired(w, r, cfg.Oauth, provider.(string), target, authCheckInterval) return } diff --git a/endpoints/publicProxy/secretsAccess.go b/endpoints/publicProxy/secretsAccess.go new file mode 100644 index 00000000..2908c9bc --- /dev/null +++ b/endpoints/publicProxy/secretsAccess.go @@ -0,0 +1,60 @@ +package publicProxy + +import ( + "context" + "net" + "time" + + "github.com/openziti/sdk-golang/ziti" + "github.com/openziti/zrok/controller/secretsGrpc" + "github.com/viccon/sturdyc" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/resolver" +) + +type Secret struct { + Key string + Value string +} + +func GetSecrets(shareToken string, cfg *Config) ([]Secret, error) { + cacheClient := sturdyc.New[[]Secret](cfg.SecretsCache.Capacity, cfg.SecretsCache.Shards, cfg.SecretsCache.TTL, cfg.SecretsCache.EvictionPercentage) + fetch := func(ctx context.Context) ([]Secret, error) { + opts := []grpc.DialOption{ + grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) { + zcfg, err := ziti.NewConfigFromFile(cfg.SecretsAccess.IdentityPath) + if err != nil { + return nil, err + } + zctx, err := ziti.NewContext(zcfg) + if err != nil { + return nil, err + } + conn, err := zctx.DialWithOptions(addr, &ziti.DialOptions{ConnectTimeout: 30 * time.Second}) + if err != nil { + return nil, err + } + return conn, nil + }), + grpc.WithTransportCredentials(insecure.NewCredentials()), + } + resolver.SetDefaultScheme("passthrough") + conn, err := grpc.NewClient(cfg.SecretsAccess.ServiceName, opts...) + if err != nil { + return nil, err + } + defer conn.Close() + client := secretsGrpc.NewSecretsClient(conn) + resp, err := client.FetchSecrets(ctx, &secretsGrpc.SecretsRequest{ShareToken: shareToken}) + if err != nil { + return nil, err + } + var secrets []Secret + for _, secret := range resp.GetSecrets() { + secrets = append(secrets, Secret{Key: secret.Key, Value: secret.Value}) + } + return secrets, nil + } + return cacheClient.GetOrFetch(context.Background(), shareToken, fetch) +} diff --git a/go.mod b/go.mod index bd05469b..d410cffe 100644 --- a/go.mod +++ b/go.mod @@ -55,6 +55,7 @@ require ( github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 github.com/spf13/cobra v1.9.1 github.com/stretchr/testify v1.10.0 + github.com/viccon/sturdyc v1.1.5 github.com/wneessen/go-mail v0.2.7 github.com/zitadel/oidc/v3 v3.39.0 go.uber.org/zap v1.27.0 diff --git a/go.sum b/go.sum index c6a88f8d..9ebb00de 100644 --- a/go.sum +++ b/go.sum @@ -940,6 +940,8 @@ github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOH github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/viccon/sturdyc v1.1.5 h1:GLQDnsyKt3L/tpdWCIARIRefn+5DAyvqu+0irBwt+vk= +github.com/viccon/sturdyc v1.1.5/go.mod h1:OCBEgG/i48uugKQ498UQlfMHmf5j8MYY8a4BApfVnMo= github.com/wneessen/go-mail v0.2.7 h1:4gj1flZjm05htmVj8AS6TbYXLQBYabzuQMmu8pZc/Js= github.com/wneessen/go-mail v0.2.7/go.mod h1:m25lkU2GYQnlVr6tdwK533/UXxo57V0kLOjaFYmub0E= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=