The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/storage/unified/resource/client.go

206 lines
6.3 KiB

package resource
import (
"context"
"crypto/tls"
"encoding/base64"
"encoding/json"
"fmt"
"log/slog"
"net/http"
"github.com/fullstorydev/grpchan"
"github.com/fullstorydev/grpchan/inprocgrpc"
"github.com/go-jose/go-jose/v3/jwt"
grpcAuth "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
"google.golang.org/grpc"
authnlib "github.com/grafana/authlib/authn"
"github.com/grafana/authlib/grpcutils"
"github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/apimachinery/identity"
grpcUtils "github.com/grafana/grafana/pkg/storage/unified/resource/grpc"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
type ResourceClient interface {
resourcepb.ResourceStoreClient
resourcepb.ResourceIndexClient
resourcepb.ManagedObjectIndexClient
resourcepb.BulkStoreClient
resourcepb.BlobStoreClient
resourcepb.DiagnosticsClient
}
// Internal implementation
type resourceClient struct {
resourcepb.ResourceStoreClient
resourcepb.ResourceIndexClient
resourcepb.ManagedObjectIndexClient
resourcepb.BulkStoreClient
resourcepb.BlobStoreClient
resourcepb.DiagnosticsClient
}
func NewLegacyResourceClient(channel grpc.ClientConnInterface) ResourceClient {
cc := grpchan.InterceptClientConn(channel, grpcUtils.UnaryClientInterceptor, grpcUtils.StreamClientInterceptor)
return &resourceClient{
ResourceStoreClient: resourcepb.NewResourceStoreClient(cc),
ResourceIndexClient: resourcepb.NewResourceIndexClient(cc),
ManagedObjectIndexClient: resourcepb.NewManagedObjectIndexClient(cc),
BulkStoreClient: resourcepb.NewBulkStoreClient(cc),
BlobStoreClient: resourcepb.NewBlobStoreClient(cc),
DiagnosticsClient: resourcepb.NewDiagnosticsClient(cc),
}
}
func NewLocalResourceClient(server ResourceServer) ResourceClient {
// scenario: local in-proc
channel := &inprocgrpc.Channel{}
tracer := otel.Tracer("github.com/grafana/grafana/pkg/storage/unified/resource")
grpcAuthInt := grpcutils.NewUnsafeAuthenticator(tracer)
for _, desc := range []*grpc.ServiceDesc{
&resourcepb.ResourceStore_ServiceDesc,
&resourcepb.ResourceIndex_ServiceDesc,
&resourcepb.ManagedObjectIndex_ServiceDesc,
&resourcepb.BlobStore_ServiceDesc,
&resourcepb.BulkStore_ServiceDesc,
&resourcepb.Diagnostics_ServiceDesc,
} {
channel.RegisterService(
grpchan.InterceptServer(
desc,
grpcAuth.UnaryServerInterceptor(grpcAuthInt),
grpcAuth.StreamServerInterceptor(grpcAuthInt),
),
server,
)
}
clientInt := authnlib.NewGrpcClientInterceptor(
ProvideInProcExchanger(),
authnlib.WithClientInterceptorIDTokenExtractor(idTokenExtractor),
)
cc := grpchan.InterceptClientConn(channel, clientInt.UnaryClientInterceptor, clientInt.StreamClientInterceptor)
return &resourceClient{
ResourceStoreClient: resourcepb.NewResourceStoreClient(cc),
ResourceIndexClient: resourcepb.NewResourceIndexClient(cc),
ManagedObjectIndexClient: resourcepb.NewManagedObjectIndexClient(cc),
BulkStoreClient: resourcepb.NewBulkStoreClient(cc),
BlobStoreClient: resourcepb.NewBlobStoreClient(cc),
DiagnosticsClient: resourcepb.NewDiagnosticsClient(cc),
}
}
type RemoteResourceClientConfig struct {
Token string
TokenExchangeURL string
Audiences []string
Namespace string
AllowInsecure bool
}
func NewRemoteResourceClient(tracer trace.Tracer, conn grpc.ClientConnInterface, cfg RemoteResourceClientConfig) (ResourceClient, error) {
exchangeOpts := []authnlib.ExchangeClientOpts{}
if cfg.AllowInsecure {
exchangeOpts = append(exchangeOpts, authnlib.WithHTTPClient(&http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}}))
}
tc, err := authnlib.NewTokenExchangeClient(authnlib.TokenExchangeConfig{
Token: cfg.Token,
TokenExchangeURL: cfg.TokenExchangeURL,
}, exchangeOpts...)
if err != nil {
return nil, err
}
clientInt := authnlib.NewGrpcClientInterceptor(
tc,
authnlib.WithClientInterceptorTracer(tracer),
authnlib.WithClientInterceptorNamespace(cfg.Namespace),
authnlib.WithClientInterceptorAudience(cfg.Audiences),
authnlib.WithClientInterceptorIDTokenExtractor(idTokenExtractor),
)
cc := grpchan.InterceptClientConn(conn, clientInt.UnaryClientInterceptor, clientInt.StreamClientInterceptor)
return &resourceClient{
ResourceStoreClient: resourcepb.NewResourceStoreClient(cc),
ResourceIndexClient: resourcepb.NewResourceIndexClient(cc),
BlobStoreClient: resourcepb.NewBlobStoreClient(cc),
BulkStoreClient: resourcepb.NewBulkStoreClient(cc),
ManagedObjectIndexClient: resourcepb.NewManagedObjectIndexClient(cc),
DiagnosticsClient: resourcepb.NewDiagnosticsClient(cc),
}, nil
}
var authLogger = slog.Default().With("logger", "resource-client-auth-interceptor")
func idTokenExtractor(ctx context.Context) (string, error) {
if identity.IsServiceIdentity(ctx) {
return "", nil
}
info, ok := types.AuthInfoFrom(ctx)
if !ok {
return "", fmt.Errorf("no claims found")
}
if token := info.GetIDToken(); len(token) != 0 {
return token, nil
}
if !types.IsIdentityType(info.GetIdentityType(), types.TypeAccessPolicy) {
authLogger.Warn(
"calling resource store as the service without id token or marking it as the service identity",
"subject", info.GetSubject(),
"uid", info.GetUID(),
)
}
return "", nil
}
func ProvideInProcExchanger() authnlib.StaticTokenExchanger {
token, err := createInProcToken()
if err != nil {
panic(err)
}
return authnlib.NewStaticTokenExchanger(token)
}
func createInProcToken() (string, error) {
claims := authnlib.Claims[authnlib.AccessTokenClaims]{
Claims: jwt.Claims{
Issuer: "grafana",
Subject: types.NewTypeID(types.TypeAccessPolicy, "grafana"),
Audience: []string{"resourceStore"},
},
Rest: authnlib.AccessTokenClaims{
Namespace: "*",
Permissions: identity.ServiceIdentityClaims.Rest.Permissions,
DelegatedPermissions: identity.ServiceIdentityClaims.Rest.DelegatedPermissions,
},
}
header, err := json.Marshal(map[string]string{
"alg": "none",
"typ": authnlib.TokenTypeAccess,
})
if err != nil {
return "", err
}
payload, err := json.Marshal(claims)
if err != nil {
return "", err
}
return base64.RawURLEncoding.EncodeToString(header) + "." + base64.RawURLEncoding.EncodeToString(payload) + ".", nil
}