Authn: use authenticator for grpc (#99573)
* Remove usage of grpc-authenticator * Cleanup client construction code
This commit is contained in:
@@ -133,7 +133,7 @@ func (o *StorageOptions) ApplyTo(serverConfig *genericapiserver.RecommendedConfi
|
||||
Namespace: o.GrpcClientAuthenticationTokenNamespace,
|
||||
},
|
||||
}
|
||||
unified, err := resource.NewCloudResourceClient(tracer, conn, authCfg, o.GrpcClientAuthenticationAllowInsecure)
|
||||
unified, err := resource.NewRemoteResourceClient(tracer, conn, authCfg, o.GrpcClientAuthenticationAllowInsecure)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,32 +1,14 @@
|
||||
package grpcutils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
type Mode string
|
||||
|
||||
func (s Mode) IsValid() bool {
|
||||
switch s {
|
||||
case ModeOnPrem, ModeCloud:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const (
|
||||
ModeOnPrem Mode = "on-prem"
|
||||
ModeCloud Mode = "cloud"
|
||||
)
|
||||
|
||||
type GrpcServerConfig struct {
|
||||
SigningKeysURL string
|
||||
AllowedAudiences []string
|
||||
Mode Mode
|
||||
LegacyFallback bool
|
||||
AllowInsecure bool
|
||||
}
|
||||
@@ -35,21 +17,15 @@ func (c *GrpcServerConfig) AddFlags(fs *pflag.FlagSet) {
|
||||
fs.StringVar(&c.SigningKeysURL, "grpc-server-authentication.signing-keys-url", "", "gRPC server authentication signing keys URL")
|
||||
}
|
||||
|
||||
func ReadGrpcServerConfig(cfg *setting.Cfg) (*GrpcServerConfig, error) {
|
||||
func ReadGrpcServerConfig(cfg *setting.Cfg) *GrpcServerConfig {
|
||||
section := cfg.SectionWithEnvOverrides("grpc_server_authentication")
|
||||
|
||||
mode := Mode(section.Key("mode").MustString(string(ModeOnPrem)))
|
||||
if !mode.IsValid() {
|
||||
return nil, fmt.Errorf("grpc_server_authentication: invalid mode %q", mode)
|
||||
}
|
||||
|
||||
return &GrpcServerConfig{
|
||||
SigningKeysURL: section.Key("signing_keys_url").MustString(""),
|
||||
AllowedAudiences: section.Key("allowed_audiences").Strings(","),
|
||||
Mode: mode,
|
||||
LegacyFallback: section.Key("legacy_fallback").MustBool(true),
|
||||
AllowInsecure: cfg.Env == setting.Dev,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
type GrpcClientConfig struct {
|
||||
|
||||
@@ -19,15 +19,54 @@ import (
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
var once sync.Once
|
||||
|
||||
func NewInProcGrpcAuthenticator() interceptors.Authenticator {
|
||||
return newAuthenticator(
|
||||
authn.NewDefaultAuthenticator(
|
||||
authn.NewUnsafeAccessTokenVerifier(authn.VerifierConfig{}),
|
||||
authn.NewUnsafeIDTokenVerifier(authn.VerifierConfig{}),
|
||||
),
|
||||
tracing.NewNoopTracerService(),
|
||||
)
|
||||
}
|
||||
|
||||
func NewAuthenticator(cfg *GrpcServerConfig, tracer tracing.Tracer) interceptors.Authenticator {
|
||||
client := http.DefaultClient
|
||||
if cfg.AllowInsecure {
|
||||
client = &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}}
|
||||
}
|
||||
|
||||
kr := authn.NewKeyRetriever(authn.KeyRetrieverConfig{
|
||||
SigningKeysURL: cfg.SigningKeysURL,
|
||||
}, authn.WithHTTPClientKeyRetrieverOpt(client))
|
||||
|
||||
auth := authn.NewDefaultAuthenticator(
|
||||
authn.NewUnsafeAccessTokenVerifier(authn.VerifierConfig{}),
|
||||
authn.NewUnsafeIDTokenVerifier(authn.VerifierConfig{}),
|
||||
authn.NewAccessTokenVerifier(authn.VerifierConfig{AllowedAudiences: cfg.AllowedAudiences}, kr),
|
||||
authn.NewIDTokenVerifier(authn.VerifierConfig{}, kr),
|
||||
)
|
||||
|
||||
return newAuthenticator(auth, tracer)
|
||||
}
|
||||
|
||||
func NewAuthenticatorWithFallback(cfg *setting.Cfg, reg prometheus.Registerer, tracer tracing.Tracer, fallback interceptors.Authenticator) interceptors.Authenticator {
|
||||
authCfg := ReadGrpcServerConfig(cfg)
|
||||
authenticator := NewAuthenticator(authCfg, tracer)
|
||||
if !authCfg.LegacyFallback {
|
||||
return authenticator
|
||||
}
|
||||
|
||||
return &authenticatorWithFallback{
|
||||
authenticator: authenticator,
|
||||
fallback: fallback,
|
||||
tracer: tracer,
|
||||
metrics: newMetrics(reg),
|
||||
}
|
||||
}
|
||||
|
||||
func newAuthenticator(auth authn.Authenticator, tracer tracing.Tracer) interceptors.Authenticator {
|
||||
return interceptors.AuthenticatorFunc(func(ctx context.Context) (context.Context, error) {
|
||||
ctx, span := tracer.Start(ctx, "grpcutils.Authenticate")
|
||||
defer span.End()
|
||||
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New("missing metedata in context")
|
||||
@@ -35,85 +74,31 @@ func NewInProcGrpcAuthenticator() interceptors.Authenticator {
|
||||
|
||||
info, err := auth.Authenticate(ctx, authn.NewGRPCTokenProvider(md))
|
||||
if err != nil {
|
||||
span.RecordError(err)
|
||||
return ctx, err
|
||||
}
|
||||
|
||||
// FIXME: Add attribute with service subject once https://github.com/grafana/authlib/issues/139 is closed.
|
||||
span.SetAttributes(attribute.String("subject", info.GetUID()))
|
||||
span.SetAttributes(attribute.Bool("service", types.IsIdentityType(info.GetIdentityType(), types.TypeAccessPolicy)))
|
||||
return types.WithAuthInfo(ctx, info), nil
|
||||
})
|
||||
}
|
||||
|
||||
func NewGrpcAuthenticator(authCfg *GrpcServerConfig, tracer tracing.Tracer) (*authn.GrpcAuthenticator, error) {
|
||||
grpcAuthCfg := authn.GrpcAuthenticatorConfig{
|
||||
KeyRetrieverConfig: authn.KeyRetrieverConfig{
|
||||
SigningKeysURL: authCfg.SigningKeysURL,
|
||||
},
|
||||
VerifierConfig: authn.VerifierConfig{
|
||||
AllowedAudiences: authCfg.AllowedAudiences,
|
||||
},
|
||||
}
|
||||
|
||||
client := http.DefaultClient
|
||||
if authCfg.AllowInsecure {
|
||||
// allow insecure connections in development mode to facilitate testing
|
||||
client = &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}}
|
||||
}
|
||||
keyRetriever := authn.NewKeyRetriever(grpcAuthCfg.KeyRetrieverConfig, authn.WithHTTPClientKeyRetrieverOpt(client))
|
||||
|
||||
grpcOpts := []authn.GrpcAuthenticatorOption{
|
||||
authn.WithKeyRetrieverOption(keyRetriever),
|
||||
authn.WithTracerAuthOption(tracer),
|
||||
authn.WithIDTokenAuthOption(false),
|
||||
}
|
||||
|
||||
if authCfg.Mode == ModeOnPrem {
|
||||
grpcOpts = append(grpcOpts,
|
||||
// Access token are not yet available on-prem
|
||||
authn.WithDisableAccessTokenAuthOption(),
|
||||
)
|
||||
}
|
||||
return authn.NewGrpcAuthenticator(
|
||||
&grpcAuthCfg,
|
||||
grpcOpts...,
|
||||
)
|
||||
}
|
||||
|
||||
type contextFallbackKey struct{}
|
||||
|
||||
type AuthenticatorWithFallback struct {
|
||||
authenticator *authn.GrpcAuthenticator
|
||||
type authenticatorWithFallback struct {
|
||||
authenticator interceptors.Authenticator
|
||||
fallback interceptors.Authenticator
|
||||
metrics *metrics
|
||||
tracer tracing.Tracer
|
||||
}
|
||||
|
||||
func NewGrpcAuthenticatorWithFallback(cfg *setting.Cfg, reg prometheus.Registerer, tracer tracing.Tracer, fallback interceptors.Authenticator) (interceptors.Authenticator, error) {
|
||||
authCfg, err := ReadGrpcServerConfig(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authenticator, err := NewGrpcAuthenticator(authCfg, tracer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !authCfg.LegacyFallback {
|
||||
return authenticator, nil
|
||||
}
|
||||
|
||||
return &AuthenticatorWithFallback{
|
||||
authenticator: authenticator,
|
||||
fallback: fallback,
|
||||
metrics: newMetrics(reg),
|
||||
tracer: tracer,
|
||||
}, nil
|
||||
}
|
||||
type contextFallbackKey struct{}
|
||||
|
||||
func FallbackUsed(ctx context.Context) bool {
|
||||
return ctx.Value(contextFallbackKey{}) != nil
|
||||
}
|
||||
|
||||
func (f *AuthenticatorWithFallback) Authenticate(ctx context.Context) (context.Context, error) {
|
||||
func (f *authenticatorWithFallback) Authenticate(ctx context.Context) (context.Context, error) {
|
||||
ctx, span := f.tracer.Start(ctx, "grpcutils.AuthenticatorWithFallback.Authenticate")
|
||||
defer span.End()
|
||||
|
||||
@@ -145,6 +130,8 @@ type metrics struct {
|
||||
requestsTotal *prometheus.CounterVec
|
||||
}
|
||||
|
||||
var once sync.Once
|
||||
|
||||
func newMetrics(reg prometheus.Registerer) *metrics {
|
||||
m := &metrics{
|
||||
requestsTotal: prometheus.NewCounterVec(
|
||||
|
||||
@@ -151,11 +151,5 @@ func newResourceClient(conn *grpc.ClientConn, cfg *setting.Cfg, features feature
|
||||
if !features.IsEnabledGlobally(featuremgmt.FlagAppPlatformGrpcClientAuth) {
|
||||
return resource.NewLegacyResourceClient(conn), nil
|
||||
}
|
||||
if cfg.StackID == "" {
|
||||
return resource.NewGRPCResourceClient(tracer, conn)
|
||||
}
|
||||
|
||||
grpcClientCfg := grpcutils.ReadGrpcClientConfig(cfg)
|
||||
|
||||
return resource.NewCloudResourceClient(tracer, conn, clientCfgMapping(grpcClientCfg), cfg.Env == setting.Dev)
|
||||
return resource.NewRemoteResourceClient(tracer, conn, clientCfgMapping(grpcutils.ReadGrpcClientConfig(cfg)), cfg.Env == setting.Dev)
|
||||
}
|
||||
|
||||
@@ -92,31 +92,7 @@ func NewLocalResourceClient(server ResourceServer) ResourceClient {
|
||||
}
|
||||
}
|
||||
|
||||
func NewGRPCResourceClient(tracer tracing.Tracer, conn *grpc.ClientConn) (ResourceClient, error) {
|
||||
// scenario: remote on-prem
|
||||
clientInt, err := authnlib.NewGrpcClientInterceptor(
|
||||
&authnlib.GrpcClientConfig{},
|
||||
authnlib.WithDisableAccessTokenOption(),
|
||||
authnlib.WithIDTokenExtractorOption(idTokenExtractor),
|
||||
authnlib.WithTracerOption(tracer),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cc := grpchan.InterceptClientConn(conn, clientInt.UnaryClientInterceptor, clientInt.StreamClientInterceptor)
|
||||
return &resourceClient{
|
||||
ResourceStoreClient: NewResourceStoreClient(cc),
|
||||
ResourceIndexClient: NewResourceIndexClient(cc),
|
||||
BlobStoreClient: NewBlobStoreClient(cc),
|
||||
BatchStoreClient: NewBatchStoreClient(cc),
|
||||
RepositoryIndexClient: NewRepositoryIndexClient(cc),
|
||||
DiagnosticsClient: NewDiagnosticsClient(cc),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewCloudResourceClient(tracer tracing.Tracer, conn *grpc.ClientConn, cfg authnlib.GrpcClientConfig, allowInsecure bool) (ResourceClient, error) {
|
||||
// scenario: remote cloud
|
||||
func NewRemoteResourceClient(tracer tracing.Tracer, conn *grpc.ClientConn, cfg authnlib.GrpcClientConfig, allowInsecure bool) (ResourceClient, error) {
|
||||
opts := []authnlib.GrpcClientInterceptorOption{
|
||||
authnlib.WithIDTokenExtractorOption(idTokenExtractor),
|
||||
authnlib.WithTracerOption(tracer),
|
||||
|
||||
@@ -81,11 +81,7 @@ func ProvideUnifiedStorageGrpcService(
|
||||
|
||||
// FIXME: This is a temporary solution while we are migrating to the new authn interceptor
|
||||
// grpcutils.NewGrpcAuthenticator should be used instead.
|
||||
fallback := &grpc.Authenticator{Tracer: tracing}
|
||||
authn, err := grpcutils.NewGrpcAuthenticatorWithFallback(cfg, reg, tracing, fallback)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authn := grpcutils.NewAuthenticatorWithFallback(cfg, reg, tracing, &grpc.Authenticator{Tracer: tracing})
|
||||
|
||||
s := &service{
|
||||
cfg: cfg,
|
||||
|
||||
@@ -512,7 +512,7 @@ func TestClientServer(t *testing.T) {
|
||||
t.Run("Create a client", func(t *testing.T) {
|
||||
conn, err := grpc.NewClient(svc.GetAddress(), grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
require.NoError(t, err)
|
||||
client, err = resource.NewGRPCResourceClient(tracing.NewNoopTracerService(), conn)
|
||||
client, err = resource.NewRemoteResourceClient(tracing.NewNoopTracerService(), conn, authn.GrpcClientConfig{}, true)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
|
||||
@@ -191,7 +191,7 @@ func TestIntegrationPlaylist(t *testing.T) {
|
||||
doPlaylistTests(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
|
||||
AppModeProduction: false,
|
||||
DisableAnonymous: true,
|
||||
APIServerStorageType: options.StorageTypeUnifiedGrpc, // start a real grpc server
|
||||
APIServerStorageType: options.StorageTypeUnified,
|
||||
EnableFeatureToggles: []string{},
|
||||
}))
|
||||
})
|
||||
@@ -200,7 +200,7 @@ func TestIntegrationPlaylist(t *testing.T) {
|
||||
doPlaylistTests(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
|
||||
AppModeProduction: false, // required for unified storage
|
||||
DisableAnonymous: true,
|
||||
APIServerStorageType: "unified", // use the entity api tables
|
||||
APIServerStorageType: options.StorageTypeUnified, // use the entity api tables
|
||||
EnableFeatureToggles: []string{
|
||||
featuremgmt.FlagKubernetesPlaylists, // Required so that legacy calls are also written
|
||||
},
|
||||
@@ -216,7 +216,7 @@ func TestIntegrationPlaylist(t *testing.T) {
|
||||
doPlaylistTests(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
|
||||
AppModeProduction: false, // required for unified storage
|
||||
DisableAnonymous: true,
|
||||
APIServerStorageType: "unified", // use the entity api tables
|
||||
APIServerStorageType: options.StorageTypeUnified, // use the entity api tables
|
||||
EnableFeatureToggles: []string{
|
||||
featuremgmt.FlagKubernetesPlaylists, // Required so that legacy calls are also written
|
||||
},
|
||||
@@ -232,7 +232,7 @@ func TestIntegrationPlaylist(t *testing.T) {
|
||||
doPlaylistTests(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
|
||||
AppModeProduction: false, // required for unified storage
|
||||
DisableAnonymous: true,
|
||||
APIServerStorageType: "unified", // use the entity api tables
|
||||
APIServerStorageType: options.StorageTypeUnified, // use the entity api tables
|
||||
EnableFeatureToggles: []string{
|
||||
featuremgmt.FlagKubernetesPlaylists, // Required so that legacy calls are also written
|
||||
},
|
||||
@@ -251,7 +251,7 @@ func TestIntegrationPlaylist(t *testing.T) {
|
||||
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
|
||||
AppModeProduction: true,
|
||||
DisableAnonymous: true,
|
||||
APIServerStorageType: "etcd", // requires etcd running on localhost:2379
|
||||
APIServerStorageType: options.StorageTypeEtcd, // requires etcd running on localhost:2379
|
||||
UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{
|
||||
RESOURCEGROUP: {
|
||||
DualWriterMode: grafanarest.Mode0,
|
||||
@@ -280,7 +280,7 @@ func TestIntegrationPlaylist(t *testing.T) {
|
||||
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
|
||||
AppModeProduction: true,
|
||||
DisableAnonymous: true,
|
||||
APIServerStorageType: "etcd", // requires etcd running on localhost:2379
|
||||
APIServerStorageType: options.StorageTypeEtcd, // requires etcd running on localhost:2379
|
||||
EnableFeatureToggles: []string{
|
||||
featuremgmt.FlagKubernetesPlaylists, // Required so that legacy calls are also written
|
||||
},
|
||||
@@ -309,7 +309,7 @@ func TestIntegrationPlaylist(t *testing.T) {
|
||||
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
|
||||
AppModeProduction: true,
|
||||
DisableAnonymous: true,
|
||||
APIServerStorageType: "etcd", // requires etcd running on localhost:2379
|
||||
APIServerStorageType: options.StorageTypeEtcd, // requires etcd running on localhost:2379
|
||||
EnableFeatureToggles: []string{
|
||||
featuremgmt.FlagKubernetesPlaylists, // Required so that legacy calls are also written
|
||||
},
|
||||
@@ -338,7 +338,7 @@ func TestIntegrationPlaylist(t *testing.T) {
|
||||
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
|
||||
AppModeProduction: true,
|
||||
DisableAnonymous: true,
|
||||
APIServerStorageType: "etcd", // requires etcd running on localhost:2379
|
||||
APIServerStorageType: options.StorageTypeEtcd, // requires etcd running on localhost:2379
|
||||
EnableFeatureToggles: []string{
|
||||
featuremgmt.FlagKubernetesPlaylists, // Required so that legacy calls are also written
|
||||
},
|
||||
@@ -367,7 +367,7 @@ func TestIntegrationPlaylist(t *testing.T) {
|
||||
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
|
||||
AppModeProduction: true,
|
||||
DisableAnonymous: true,
|
||||
APIServerStorageType: "etcd", // requires etcd running on localhost:2379
|
||||
APIServerStorageType: options.StorageTypeEtcd, // requires etcd running on localhost:2379
|
||||
EnableFeatureToggles: []string{
|
||||
featuremgmt.FlagKubernetesPlaylists, // Required so that legacy calls are also written
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user