Files
grafana/pkg/registry/apis/secret/inline/grpc_client.go
T

200 lines
5.4 KiB
Go

package inline
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"os"
"github.com/fullstorydev/grpchan"
authnlib "github.com/grafana/authlib/authn"
"github.com/grafana/authlib/types"
"go.opentelemetry.io/otel/trace"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
inlinev1beta1 "github.com/grafana/grafana/apps/secret/inline/v1beta1"
secretv1beta1 "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1"
"github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
)
type GRPCInlineClient struct {
conn *grpc.ClientConn
tracer trace.Tracer
tokenExchanger authnlib.TokenExchanger
}
var _ contracts.InlineSecureValueSupport = &GRPCInlineClient{}
type TLSConfig struct {
UseTLS bool
CAFile string
ServerName string
InsecureSkipVerify bool
}
func NewGRPCInlineClient(tokenExchanger authnlib.TokenExchanger, tracer trace.Tracer, address string, tlsConfig TLSConfig, clientLoadBalancingEnabled bool) (*GRPCInlineClient, error) {
var opts []grpc.DialOption
if tlsConfig.UseTLS {
creds, err := createTLSCredentials(tlsConfig)
if err != nil {
return nil, fmt.Errorf("failed to setup TLS: %w", err)
}
opts = append(opts, grpc.WithTransportCredentials(creds))
} else {
opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
}
if clientLoadBalancingEnabled {
// Use round_robin to balances requests more evenly over the available replicas.
opts = append(opts, grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"round_robin"}`))
// Disable looking up service config from TXT DNS records.
// This reduces the number of requests made to the DNS servers.
opts = append(opts, grpc.WithDisableServiceConfig())
}
conn, err := grpc.NewClient(address, opts...)
if err != nil {
return nil, fmt.Errorf("failed to connect to grpc server at %s: %w", address, err)
}
return &GRPCInlineClient{
conn: conn,
tracer: tracer,
tokenExchanger: tokenExchanger,
}, nil
}
func createTLSCredentials(config TLSConfig) (credentials.TransportCredentials, error) {
tlsConfig := &tls.Config{}
if config.CAFile != "" {
caCert, err := os.ReadFile(config.CAFile)
if err != nil {
return nil, fmt.Errorf("failed to read CA: %w", err)
}
caCertPool := x509.NewCertPool()
if !caCertPool.AppendCertsFromPEM(caCert) {
return nil, fmt.Errorf("failed to append CA")
}
tlsConfig.RootCAs = caCertPool
}
if config.ServerName != "" {
tlsConfig.ServerName = config.ServerName
}
if config.InsecureSkipVerify {
tlsConfig.InsecureSkipVerify = true
}
return credentials.NewTLS(tlsConfig), nil
}
// Close will close the underlying gRPC connection. After it is closed, the client cannot be used anymore.
func (g *GRPCInlineClient) Close() error {
if g.conn != nil {
return g.conn.Close()
}
return nil
}
func (g *GRPCInlineClient) CanReference(ctx context.Context, owner v0alpha1.ObjectReference, names ...string) error {
client, err := g.getClient(owner.Namespace)
if err != nil {
return err
}
req := &inlinev1beta1.CanReferenceRequest{
Owner: &inlinev1beta1.ObjectReference{
ApiGroup: owner.APIGroup,
ApiVersion: owner.APIVersion,
Kind: owner.Kind,
Namespace: owner.Namespace,
Name: owner.Name,
},
Names: names,
}
_, err = client.CanReference(ctx, req)
return err
}
func (g *GRPCInlineClient) CreateInline(ctx context.Context, owner v0alpha1.ObjectReference, value v0alpha1.RawSecureValue) (string, error) {
client, err := g.getClient(owner.Namespace)
if err != nil {
return "", err
}
if value.IsZero() {
return "", fmt.Errorf("empty value provided for CreateInline")
}
req := &inlinev1beta1.CreateInlineRequest{
Owner: &inlinev1beta1.ObjectReference{
ApiGroup: owner.APIGroup,
ApiVersion: owner.APIVersion,
Kind: owner.Kind,
Namespace: owner.Namespace,
Name: owner.Name,
},
Value: value.DangerouslyExposeAndConsumeValue(),
}
resp, err := client.CreateInline(ctx, req)
if err != nil {
return "", err
}
return resp.GetName(), nil
}
func (g *GRPCInlineClient) DeleteWhenOwnedByResource(ctx context.Context, owner v0alpha1.ObjectReference, names ...string) error {
client, err := g.getClient(owner.Namespace)
if err != nil {
return err
}
req := &inlinev1beta1.DeleteWhenOwnedByResourceRequest{
Owner: &inlinev1beta1.ObjectReference{
ApiGroup: owner.APIGroup,
ApiVersion: owner.APIVersion,
Kind: owner.Kind,
Namespace: owner.Namespace,
Name: owner.Name,
},
Names: names,
}
_, err = client.DeleteWhenOwnedByResource(ctx, req)
return err
}
func (g *GRPCInlineClient) getClient(namespace string) (inlinev1beta1.InlineSecureValueServiceClient, error) {
_, err := types.ParseNamespace(namespace)
if err != nil {
return nil, err
}
tokenExchangerInterceptor := authnlib.NewGrpcClientInterceptor(
g.tokenExchanger,
authnlib.WithClientInterceptorTracer(g.tracer),
authnlib.WithClientInterceptorNamespace(namespace),
authnlib.WithClientInterceptorAudience([]string{secretv1beta1.APIGroup}),
)
clientConn := grpchan.InterceptClientConn(
g.conn,
tokenExchangerInterceptor.UnaryClientInterceptor,
tokenExchangerInterceptor.StreamClientInterceptor,
)
return inlinev1beta1.NewInlineSecureValueServiceClient(clientConn), nil
}