mirror of
https://github.com/openziti/zrok.git
synced 2025-06-26 12:42:18 +02:00
start of the secrets cache (based on sturdyc) (#987)
This commit is contained in:
parent
a993ddabda
commit
4c5f3e77e3
@ -3,6 +3,7 @@ package publicProxy
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/michaelquigley/cf"
|
"github.com/michaelquigley/cf"
|
||||||
"github.com/openziti/zrok/endpoints"
|
"github.com/openziti/zrok/endpoints"
|
||||||
@ -22,6 +23,7 @@ type Config struct {
|
|||||||
Interstitial *InterstitialConfig
|
Interstitial *InterstitialConfig
|
||||||
Oauth *OauthConfig
|
Oauth *OauthConfig
|
||||||
SecretsAccess *SecretsAccessConfig
|
SecretsAccess *SecretsAccessConfig
|
||||||
|
SecretsCache *SecretsCacheConfig
|
||||||
Tls *endpoints.TlsConfig
|
Tls *endpoints.TlsConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,6 +59,13 @@ type SecretsAccessConfig struct {
|
|||||||
ServiceName string
|
ServiceName string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SecretsCacheConfig struct {
|
||||||
|
Capacity int
|
||||||
|
Shards int
|
||||||
|
TTL time.Duration
|
||||||
|
EvictionPercentage int
|
||||||
|
}
|
||||||
|
|
||||||
func (p *OauthProviderConfig) GetEndpoint() oauth2.Endpoint {
|
func (p *OauthProviderConfig) GetEndpoint() oauth2.Endpoint {
|
||||||
return oauth2.Endpoint{
|
return oauth2.Endpoint{
|
||||||
AuthURL: p.AuthURL,
|
AuthURL: p.AuthURL,
|
||||||
@ -68,6 +77,12 @@ func DefaultConfig() *Config {
|
|||||||
return &Config{
|
return &Config{
|
||||||
Identity: "public",
|
Identity: "public",
|
||||||
Address: "0.0.0.0:8080",
|
Address: "0.0.0.0:8080",
|
||||||
|
SecretsCache: &SecretsCacheConfig{
|
||||||
|
Capacity: 10000,
|
||||||
|
Shards: 10,
|
||||||
|
TTL: 2 * time.Hour,
|
||||||
|
EvictionPercentage: 10,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,18 +153,18 @@ func hostTargetReverseProxy(cfg *Config, ctx ziti.Context) *httputil.ReverseProx
|
|||||||
return &httputil.ReverseProxy{Director: director}
|
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) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
shrToken := resolveService(pcfg.HostMatch, r.Host)
|
shrToken := resolveService(cfg.HostMatch, r.Host)
|
||||||
if shrToken != "" {
|
if shrToken != "" {
|
||||||
if svc, found := endpoints.GetRefreshedService(shrToken, ctx); found {
|
if svc, found := endpoints.GetRefreshedService(shrToken, ctx); found {
|
||||||
if cfg, found := svc.Config[sdk.ZrokProxyConfig]; found {
|
if proxyConfig, found := svc.Config[sdk.ZrokProxyConfig]; found {
|
||||||
if r.Method != http.MethodOptions && (pcfg.Interstitial != nil && pcfg.Interstitial.Enabled) {
|
if r.Method != http.MethodOptions && (cfg.Interstitial != nil && cfg.Interstitial.Enabled) {
|
||||||
sendInterstitial := true
|
sendInterstitial := true
|
||||||
if len(pcfg.Interstitial.UserAgentPrefixes) > 0 {
|
if len(cfg.Interstitial.UserAgentPrefixes) > 0 {
|
||||||
ua := r.Header.Get("User-Agent")
|
ua := r.Header.Get("User-Agent")
|
||||||
matched := false
|
matched := false
|
||||||
for _, prefix := range pcfg.Interstitial.UserAgentPrefixes {
|
for _, prefix := range cfg.Interstitial.UserAgentPrefixes {
|
||||||
if strings.HasPrefix(ua, prefix) {
|
if strings.HasPrefix(ua, prefix) {
|
||||||
matched = true
|
matched = true
|
||||||
break
|
break
|
||||||
@ -175,13 +175,13 @@ func shareHandler(handler http.Handler, pcfg *Config, key []byte, ctx ziti.Conte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if sendInterstitial {
|
if sendInterstitial {
|
||||||
if v, istlFound := cfg["interstitial"]; istlFound {
|
if v, istlFound := proxyConfig["interstitial"]; istlFound {
|
||||||
if istlEnabled, ok := v.(bool); ok && istlEnabled {
|
if istlEnabled, ok := v.(bool); ok && istlEnabled {
|
||||||
skip := r.Header.Get("skip_zrok_interstitial")
|
skip := r.Header.Get("skip_zrok_interstitial")
|
||||||
_, zrokOkErr := r.Cookie("zrok_interstitial")
|
_, zrokOkErr := r.Cookie("zrok_interstitial")
|
||||||
if skip == "" && zrokOkErr != nil {
|
if skip == "" && zrokOkErr != nil {
|
||||||
logrus.Debugf("forcing interstitial for '%v'", r.URL)
|
logrus.Debugf("forcing interstitial for '%v'", r.URL)
|
||||||
interstitialUi.WriteInterstitialAnnounce(w, pcfg.Interstitial.HtmlPath)
|
interstitialUi.WriteInterstitialAnnounce(w, cfg.Interstitial.HtmlPath)
|
||||||
return
|
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 {
|
switch scheme {
|
||||||
case string(sdk.None):
|
case string(sdk.None):
|
||||||
logrus.Debugf("auth scheme none '%v'", shrToken)
|
logrus.Debugf("auth scheme none '%v'", shrToken)
|
||||||
@ -206,7 +206,7 @@ func shareHandler(handler http.Handler, pcfg *Config, key []byte, ctx ziti.Conte
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
authed := false
|
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 basicAuth, ok := v.(map[string]interface{}); ok {
|
||||||
if v, found := basicAuth["users"]; found {
|
if v, found := basicAuth["users"]; found {
|
||||||
if arr, ok := v.([]interface{}); ok {
|
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):
|
case string(sdk.Oauth):
|
||||||
logrus.Debugf("auth scheme oauth '%v'", shrToken)
|
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 {
|
if provider, found := oauthCfg.(map[string]interface{})["provider"]; found {
|
||||||
var authCheckInterval time.Duration
|
var authCheckInterval time.Duration
|
||||||
if checkInterval, found := oauthCfg.(map[string]interface{})["authorization_check_interval"]; !found {
|
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")
|
cookie, err := r.Cookie("zrok-access")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Errorf("unable to get 'zrok-access' cookie: %v", err)
|
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
|
return
|
||||||
}
|
}
|
||||||
tkn, err := jwt.ParseWithClaims(cookie.Value, &ZrokClaims{}, func(t *jwt.Token) (interface{}, error) {
|
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 nil, fmt.Errorf("missing oauth configuration for access point; unable to parse jwt")
|
||||||
}
|
}
|
||||||
return key, nil
|
return key, nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Errorf("unable to parse jwt: %v", err)
|
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
|
return
|
||||||
}
|
}
|
||||||
claims := tkn.Claims.(*ZrokClaims)
|
claims := tkn.Claims.(*ZrokClaims)
|
||||||
if claims.Provider != provider {
|
if claims.Provider != provider {
|
||||||
logrus.Error("provider mismatch; restarting auth flow")
|
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
|
return
|
||||||
}
|
}
|
||||||
if claims.AuthorizationCheckInterval != authCheckInterval {
|
if claims.AuthorizationCheckInterval != authCheckInterval {
|
||||||
logrus.Error("authorization check interval mismatch; restarting auth flow")
|
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
|
return
|
||||||
}
|
}
|
||||||
if claims.Audience != r.Host {
|
if claims.Audience != r.Host {
|
||||||
logrus.Errorf("audience claim '%s' does not match requested host '%s'; restarting auth flow", 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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
60
endpoints/publicProxy/secretsAccess.go
Normal file
60
endpoints/publicProxy/secretsAccess.go
Normal file
@ -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)
|
||||||
|
}
|
1
go.mod
1
go.mod
@ -55,6 +55,7 @@ require (
|
|||||||
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8
|
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8
|
||||||
github.com/spf13/cobra v1.9.1
|
github.com/spf13/cobra v1.9.1
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
|
github.com/viccon/sturdyc v1.1.5
|
||||||
github.com/wneessen/go-mail v0.2.7
|
github.com/wneessen/go-mail v0.2.7
|
||||||
github.com/zitadel/oidc/v3 v3.39.0
|
github.com/zitadel/oidc/v3 v3.39.0
|
||||||
go.uber.org/zap v1.27.0
|
go.uber.org/zap v1.27.0
|
||||||
|
2
go.sum
2
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/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.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||||
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
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 h1:4gj1flZjm05htmVj8AS6TbYXLQBYabzuQMmu8pZc/Js=
|
||||||
github.com/wneessen/go-mail v0.2.7/go.mod h1:m25lkU2GYQnlVr6tdwK533/UXxo57V0kLOjaFYmub0E=
|
github.com/wneessen/go-mail v0.2.7/go.mod h1:m25lkU2GYQnlVr6tdwK533/UXxo57V0kLOjaFYmub0E=
|
||||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||||
|
Loading…
x
Reference in New Issue
Block a user