Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Send Authorization Header to gRPC endpoint, adds gRPC mTLS support #1114

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ tusd_*_*
__pycache__/
examples/hooks/plugin/hook_handler
.idea/
.vscode
8 changes: 8 additions & 0 deletions cmd/tusd/cli/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ var Flags struct {
GrpcHooksEndpoint string
GrpcHooksRetry int
GrpcHooksBackoff time.Duration
GrpcHooksSecure bool
GrpcHooksServerTLSCertFile string
GrpcHooksClientTLSCertFile string
GrpcHooksClientTLSKeyFile string
EnabledHooks []hooks.HookType
ProgressHooksInterval time.Duration
ShowVersion bool
Expand Down Expand Up @@ -163,6 +167,10 @@ func ParseFlags() {
f.StringVar(&Flags.GrpcHooksEndpoint, "hooks-grpc", "", "An gRPC endpoint to which hook events will be sent to")
f.IntVar(&Flags.GrpcHooksRetry, "hooks-grpc-retry", 3, "Number of times to retry on a server error or network timeout")
f.DurationVar(&Flags.GrpcHooksBackoff, "hooks-grpc-backoff", 1*time.Second, "Wait period before retrying each retry")
f.BoolVar(&Flags.GrpcHooksSecure, "hooks-grpc-secure", false, "Enables secure connection via TLS certificates to the specified gRPC endpoint")
f.StringVar(&Flags.GrpcHooksServerTLSCertFile, "hooks-grpc-server-tls-certificate", "", "Path to the file containing the TLS certificate of the remote gRPC server. This is used in order to add the gRPC server as trusted.")
f.StringVar(&Flags.GrpcHooksClientTLSCertFile, "hooks-grpc-client-tls-certificate", "", "Path to the file containing TLS certificate to be used as client.")
f.StringVar(&Flags.GrpcHooksClientTLSKeyFile, "hooks-grpc-client-tls-key", "", "Path to the file containing the key for the Client TLS certificate.")
})

fs.AddGroup("Plugin hook options", func(f *flag.FlagSet) {
Expand Down
10 changes: 7 additions & 3 deletions cmd/tusd/cli/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,13 @@ func getHookHandler(config *handler.Config) hooks.HookHandler {
stdout.Printf("Using '%s' as the endpoint for gRPC hooks", Flags.GrpcHooksEndpoint)

return &grpc.GrpcHook{
Endpoint: Flags.GrpcHooksEndpoint,
MaxRetries: Flags.GrpcHooksRetry,
Backoff: Flags.GrpcHooksBackoff,
Endpoint: Flags.GrpcHooksEndpoint,
MaxRetries: Flags.GrpcHooksRetry,
Backoff: Flags.GrpcHooksBackoff,
Secure: Flags.GrpcHooksSecure,
ServerTLSCertificateFilePath: Flags.GrpcHooksServerTLSCertFile,
ClientTLSCertificateFilePath: Flags.GrpcHooksClientTLSCertFile,
ClientTLSCertificateKeyFilePath: Flags.GrpcHooksClientTLSKeyFile,
}
} else if Flags.PluginHookPath != "" {
stdout.Printf("Using '%s' to load plugin for hooks", Flags.PluginHookPath)
Expand Down
71 changes: 62 additions & 9 deletions pkg/hooks/grpc/grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,79 @@ package grpc

import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"net/http"
"os"
"time"

grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry"
"github.com/tus/tusd/v2/pkg/hooks"
pb "github.com/tus/tusd/v2/pkg/hooks/grpc/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/metadata"
)

type GrpcHook struct {
Endpoint string
MaxRetries int
Backoff time.Duration
Client pb.HookHandlerClient
Endpoint string
MaxRetries int
Backoff time.Duration
Client pb.HookHandlerClient
Secure bool
ServerTLSCertificateFilePath string
ClientTLSCertificateFilePath string
ClientTLSCertificateKeyFilePath string
}

func (g *GrpcHook) Setup() error {
grpcOpts := []grpc.DialOption{}

if g.Secure {
// Load the server's TLS certificate if provided
if g.ServerTLSCertificateFilePath != "" {
serverCert, err := os.ReadFile(g.ServerTLSCertificateFilePath)
if err != nil {
return err
}

// Create a certificate pool and add the server's certificate
certPool := x509.NewCertPool()
certPool.AppendCertsFromPEM(serverCert)

// Create TLS configuration with the server's CA certificate
tlsConfig := &tls.Config{
RootCAs: certPool,
}

// If client's TLS certificate and key file paths are provided, use mutual TLS
if g.ClientTLSCertificateFilePath != "" && g.ClientTLSCertificateKeyFilePath != "" {
// Load the client's TLS certificate and private key
clientCert, err := tls.LoadX509KeyPair(g.ClientTLSCertificateFilePath, g.ClientTLSCertificateKeyFilePath)
if err != nil {
return err
}

// Append client certificate to the TLS configuration
tlsConfig.Certificates = append(tlsConfig.Certificates, clientCert)
}

grpcOpts = append(grpcOpts, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)))
} else {
return errors.New("hooks-grpc-secure was set to true but no gRPC server TLS certificate file was provided. A value for hooks-grpc-server-tls-certificate is missing")
}
} else {
grpcOpts = append(grpcOpts, grpc.WithTransportCredentials(insecure.NewCredentials()))
}

opts := []grpc_retry.CallOption{
grpc_retry.WithBackoff(grpc_retry.BackoffLinear(g.Backoff)),
grpc_retry.WithMax(uint(g.MaxRetries)),
}
grpcOpts := []grpc.DialOption{
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithUnaryInterceptor(grpc_retry.UnaryClientInterceptor(opts...)),
}
grpcOpts = append(grpcOpts, grpc.WithUnaryInterceptor(grpc_retry.UnaryClientInterceptor(opts...)))

conn, err := grpc.Dial(g.Endpoint, grpcOpts...)
if err != nil {
return err
Expand All @@ -40,8 +87,14 @@ func (g *GrpcHook) Setup() error {
}

func (g *GrpcHook) InvokeHook(hookReq hooks.HookRequest) (hookRes hooks.HookResponse, err error) {
ctx := context.Background()
req := marshal(hookReq)

authorizationHeader, authorizationHeaderExists := req.Event.HttpRequest.Header["Authorization"]
ctx := context.Background()
if authorizationHeaderExists {
ctx = metadata.AppendToOutgoingContext(context.Background(), "Authorization", authorizationHeader)
}

res, err := g.Client.InvokeHook(ctx, req)
if err != nil {
return hookRes, err
Expand Down