2025-04-01 10:38:02 +09:00

162 lines
4.7 KiB
Go

package resource
import (
"context"
"crypto/tls"
"fmt"
"log/slog"
"net/http"
"github.com/fullstorydev/grpchan"
"github.com/fullstorydev/grpchan/inprocgrpc"
grpcAuth "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth"
"google.golang.org/grpc"
authnlib "github.com/grafana/authlib/authn"
"github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/authn/grpcutils"
grpcUtils "github.com/grafana/grafana/pkg/storage/unified/resource/grpc"
)
type ResourceClient interface {
ResourceStoreClient
ResourceIndexClient
RepositoryIndexClient
BulkStoreClient
BlobStoreClient
DiagnosticsClient
}
// Internal implementation
type resourceClient struct {
ResourceStoreClient
ResourceIndexClient
RepositoryIndexClient
BulkStoreClient
BlobStoreClient
DiagnosticsClient
}
func NewLegacyResourceClient(channel *grpc.ClientConn) ResourceClient {
cc := grpchan.InterceptClientConn(channel, grpcUtils.UnaryClientInterceptor, grpcUtils.StreamClientInterceptor)
return &resourceClient{
ResourceStoreClient: NewResourceStoreClient(cc),
ResourceIndexClient: NewResourceIndexClient(cc),
RepositoryIndexClient: NewRepositoryIndexClient(cc),
BulkStoreClient: NewBulkStoreClient(cc),
BlobStoreClient: NewBlobStoreClient(cc),
DiagnosticsClient: NewDiagnosticsClient(cc),
}
}
func NewLocalResourceClient(server ResourceServer) ResourceClient {
// scenario: local in-proc
channel := &inprocgrpc.Channel{}
grpcAuthInt := grpcutils.NewInProcGrpcAuthenticator()
for _, desc := range []*grpc.ServiceDesc{
&ResourceStore_ServiceDesc,
&ResourceIndex_ServiceDesc,
&RepositoryIndex_ServiceDesc,
&BlobStore_ServiceDesc,
&BulkStore_ServiceDesc,
&Diagnostics_ServiceDesc,
} {
channel.RegisterService(
grpchan.InterceptServer(
desc,
grpcAuth.UnaryServerInterceptor(grpcAuthInt.Authenticate),
grpcAuth.StreamServerInterceptor(grpcAuthInt.Authenticate),
),
server,
)
}
clientInt := authnlib.NewGrpcClientInterceptor(
grpcutils.ProvideInProcExchanger(),
authnlib.WithClientInterceptorIDTokenExtractor(idTokenExtractor),
)
cc := grpchan.InterceptClientConn(channel, clientInt.UnaryClientInterceptor, clientInt.StreamClientInterceptor)
return &resourceClient{
ResourceStoreClient: NewResourceStoreClient(cc),
ResourceIndexClient: NewResourceIndexClient(cc),
RepositoryIndexClient: NewRepositoryIndexClient(cc),
BulkStoreClient: NewBulkStoreClient(cc),
BlobStoreClient: NewBlobStoreClient(cc),
DiagnosticsClient: NewDiagnosticsClient(cc),
}
}
type RemoteResourceClientConfig struct {
Token string
TokenExchangeURL string
Audiences []string
Namespace string
AllowInsecure bool
}
func NewRemoteResourceClient(tracer tracing.Tracer, conn *grpc.ClientConn, 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: NewResourceStoreClient(cc),
ResourceIndexClient: NewResourceIndexClient(cc),
BlobStoreClient: NewBlobStoreClient(cc),
BulkStoreClient: NewBulkStoreClient(cc),
RepositoryIndexClient: NewRepositoryIndexClient(cc),
DiagnosticsClient: 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
}