Skip to content

Commit

Permalink
hooks: Add support for gRPC mTLS (#1114)
Browse files Browse the repository at this point in the history
* feat: adds mTLS support for gRPC hook. grpc hook now have a hooks-grpc-secure flag

* fix: fix gRPC call when Authorization header is missing

* ignore .vscode ide settings

* Remove automatic forwarding of Authorization header

* Fix imports and format

* Minor code and comment improvements

---------

Co-authored-by: Tarida George Cristian <[email protected]>
Co-authored-by: Marius Kleidl <[email protected]>
  • Loading branch information
3 people authored Sep 18, 2024
1 parent eff0a43 commit 061eb11
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 12 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ tusd_*_*
__pycache__/
examples/hooks/plugin/hook_handler
.idea/
.vscode/
.vscode/
8 changes: 8 additions & 0 deletions cmd/tusd/cli/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,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 @@ -165,6 +169,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")
f.StringVar(&Flags.GrpcHooksClientTLSCertFile, "hooks-grpc-client-tls-certificate", "", "Path to the file containing the client certificate for mTLS")
f.StringVar(&Flags.GrpcHooksClientTLSKeyFile, "hooks-grpc-client-tls-key", "", "Path to the file containing the client key for mTLS")
})

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
62 changes: 54 additions & 8 deletions pkg/hooks/grpc/grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,78 @@ 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"
)

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 {
if g.ServerTLSCertificateFilePath == "" {
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")
}

// Load the server's TLS certificate if provided
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 {
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 Down

0 comments on commit 061eb11

Please sign in to comment.