AuthN: Use tokens for unified storage server authentication (#95086)
* Extract server code --------- Co-authored-by: Claudiu Dragalina-Paraipan <drclau@users.noreply.github.com>
This commit is contained in:
co-authored by
Claudiu Dragalina-Paraipan
parent
9ab064bfc5
commit
b68b69c2b4
@@ -0,0 +1,45 @@
|
||||
package grpcutils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"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
|
||||
}
|
||||
|
||||
func ReadGrpcServerConfig(cfg *setting.Cfg) (*GrpcServerConfig, error) {
|
||||
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),
|
||||
}, nil
|
||||
}
|
||||
@@ -1,9 +1,21 @@
|
||||
package grpcutils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
authnlib "github.com/grafana/authlib/authn"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/grpcserver/interceptors"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
var once sync.Once
|
||||
|
||||
func NewInProcGrpcAuthenticator() *authnlib.GrpcAuthenticator {
|
||||
// In proc grpc ID token signature verification can be skipped
|
||||
return authnlib.NewUnsafeGrpcAuthenticator(
|
||||
@@ -12,3 +24,109 @@ func NewInProcGrpcAuthenticator() *authnlib.GrpcAuthenticator {
|
||||
authnlib.WithIDTokenAuthOption(true),
|
||||
)
|
||||
}
|
||||
|
||||
func NewGrpcAuthenticator(cfg *setting.Cfg) (*authnlib.GrpcAuthenticator, error) {
|
||||
authCfg, err := ReadGrpcServerConfig(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
grpcAuthCfg := authnlib.GrpcAuthenticatorConfig{
|
||||
KeyRetrieverConfig: authnlib.KeyRetrieverConfig{
|
||||
SigningKeysURL: authCfg.SigningKeysURL,
|
||||
},
|
||||
VerifierConfig: authnlib.VerifierConfig{
|
||||
AllowedAudiences: authCfg.AllowedAudiences,
|
||||
},
|
||||
}
|
||||
|
||||
client := http.DefaultClient
|
||||
if cfg.Env == setting.Dev {
|
||||
// allow insecure connections in development mode to facilitate testing
|
||||
client = &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}}
|
||||
}
|
||||
keyRetriever := authnlib.NewKeyRetriever(grpcAuthCfg.KeyRetrieverConfig, authnlib.WithHTTPClientKeyRetrieverOpt(client))
|
||||
|
||||
grpcOpts := []authnlib.GrpcAuthenticatorOption{
|
||||
authnlib.WithIDTokenAuthOption(true),
|
||||
authnlib.WithKeyRetrieverOption(keyRetriever),
|
||||
}
|
||||
if authCfg.Mode == ModeOnPrem {
|
||||
grpcOpts = append(grpcOpts,
|
||||
// Access token are not yet available on-prem
|
||||
authnlib.WithDisableAccessTokenAuthOption(),
|
||||
)
|
||||
}
|
||||
|
||||
return authnlib.NewGrpcAuthenticator(
|
||||
&grpcAuthCfg,
|
||||
grpcOpts...,
|
||||
)
|
||||
}
|
||||
|
||||
type AuthenticatorWithFallback struct {
|
||||
authenticator *authnlib.GrpcAuthenticator
|
||||
fallback interceptors.Authenticator
|
||||
metrics *metrics
|
||||
}
|
||||
|
||||
func NewGrpcAuthenticatorWithFallback(cfg *setting.Cfg, reg prometheus.Registerer, fallback interceptors.Authenticator) (interceptors.Authenticator, error) {
|
||||
authCfg, err := ReadGrpcServerConfig(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authenticator, err := NewGrpcAuthenticator(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !authCfg.LegacyFallback {
|
||||
return authenticator, nil
|
||||
}
|
||||
|
||||
return &AuthenticatorWithFallback{
|
||||
authenticator: authenticator,
|
||||
fallback: fallback,
|
||||
metrics: newMetrics(reg),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f *AuthenticatorWithFallback) Authenticate(ctx context.Context) (context.Context, error) {
|
||||
// Try to authenticate with the new authenticator first
|
||||
newCtx, err := f.authenticator.Authenticate(ctx)
|
||||
if err != nil {
|
||||
// In case of error, fallback to the legacy authenticator
|
||||
newCtx, err = f.fallback.Authenticate(ctx)
|
||||
f.metrics.fallbackCounter.WithLabelValues(fmt.Sprintf("%t", err == nil)).Inc()
|
||||
}
|
||||
return newCtx, err
|
||||
}
|
||||
|
||||
const (
|
||||
metricsNamespace = "grafana"
|
||||
metricsSubSystem = "grpc_authenticator"
|
||||
)
|
||||
|
||||
type metrics struct {
|
||||
fallbackCounter *prometheus.CounterVec
|
||||
}
|
||||
|
||||
func newMetrics(reg prometheus.Registerer) *metrics {
|
||||
m := &metrics{
|
||||
fallbackCounter: prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Namespace: metricsNamespace,
|
||||
Subsystem: metricsSubSystem,
|
||||
Name: "fallback_total",
|
||||
Help: "Number of times the fallback authenticator was used",
|
||||
}, []string{"result"}),
|
||||
}
|
||||
|
||||
if reg != nil {
|
||||
once.Do(func() {
|
||||
reg.MustRegister(m.fallbackCounter)
|
||||
})
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user