200 lines
5.4 KiB
Go
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
|
|
}
|