Skip to content

Commit 061eb11

Browse files
TaridaGeorgeTarida George CristianAcconut
authored
hooks: Add support for gRPC mTLS (#1114)
* 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]>
1 parent eff0a43 commit 061eb11

File tree

4 files changed

+70
-12
lines changed

4 files changed

+70
-12
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ tusd_*_*
88
__pycache__/
99
examples/hooks/plugin/hook_handler
1010
.idea/
11-
.vscode/
11+
.vscode/

cmd/tusd/cli/flags.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ var Flags struct {
5555
GrpcHooksEndpoint string
5656
GrpcHooksRetry int
5757
GrpcHooksBackoff time.Duration
58+
GrpcHooksSecure bool
59+
GrpcHooksServerTLSCertFile string
60+
GrpcHooksClientTLSCertFile string
61+
GrpcHooksClientTLSKeyFile string
5862
EnabledHooks []hooks.HookType
5963
ProgressHooksInterval time.Duration
6064
ShowVersion bool
@@ -165,6 +169,10 @@ func ParseFlags() {
165169
f.StringVar(&Flags.GrpcHooksEndpoint, "hooks-grpc", "", "An gRPC endpoint to which hook events will be sent to")
166170
f.IntVar(&Flags.GrpcHooksRetry, "hooks-grpc-retry", 3, "Number of times to retry on a server error or network timeout")
167171
f.DurationVar(&Flags.GrpcHooksBackoff, "hooks-grpc-backoff", 1*time.Second, "Wait period before retrying each retry")
172+
f.BoolVar(&Flags.GrpcHooksSecure, "hooks-grpc-secure", false, "Enables secure connection via TLS certificates to the specified gRPC endpoint")
173+
f.StringVar(&Flags.GrpcHooksServerTLSCertFile, "hooks-grpc-server-tls-certificate", "", "Path to the file containing the TLS certificate of the remote gRPC server")
174+
f.StringVar(&Flags.GrpcHooksClientTLSCertFile, "hooks-grpc-client-tls-certificate", "", "Path to the file containing the client certificate for mTLS")
175+
f.StringVar(&Flags.GrpcHooksClientTLSKeyFile, "hooks-grpc-client-tls-key", "", "Path to the file containing the client key for mTLS")
168176
})
169177

170178
fs.AddGroup("Plugin hook options", func(f *flag.FlagSet) {

cmd/tusd/cli/hooks.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,13 @@ func getHookHandler(config *handler.Config) hooks.HookHandler {
3131
stdout.Printf("Using '%s' as the endpoint for gRPC hooks", Flags.GrpcHooksEndpoint)
3232

3333
return &grpc.GrpcHook{
34-
Endpoint: Flags.GrpcHooksEndpoint,
35-
MaxRetries: Flags.GrpcHooksRetry,
36-
Backoff: Flags.GrpcHooksBackoff,
34+
Endpoint: Flags.GrpcHooksEndpoint,
35+
MaxRetries: Flags.GrpcHooksRetry,
36+
Backoff: Flags.GrpcHooksBackoff,
37+
Secure: Flags.GrpcHooksSecure,
38+
ServerTLSCertificateFilePath: Flags.GrpcHooksServerTLSCertFile,
39+
ClientTLSCertificateFilePath: Flags.GrpcHooksClientTLSCertFile,
40+
ClientTLSCertificateKeyFilePath: Flags.GrpcHooksClientTLSKeyFile,
3741
}
3842
} else if Flags.PluginHookPath != "" {
3943
stdout.Printf("Using '%s' to load plugin for hooks", Flags.PluginHookPath)

pkg/hooks/grpc/grpc.go

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,78 @@ package grpc
55

66
import (
77
"context"
8+
"crypto/tls"
9+
"crypto/x509"
10+
"errors"
811
"net/http"
12+
"os"
913
"time"
1014

1115
grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry"
1216
"github.com/tus/tusd/v2/pkg/hooks"
1317
pb "github.com/tus/tusd/v2/pkg/hooks/grpc/proto"
1418
"google.golang.org/grpc"
19+
"google.golang.org/grpc/credentials"
1520
"google.golang.org/grpc/credentials/insecure"
1621
)
1722

1823
type GrpcHook struct {
19-
Endpoint string
20-
MaxRetries int
21-
Backoff time.Duration
22-
Client pb.HookHandlerClient
24+
Endpoint string
25+
MaxRetries int
26+
Backoff time.Duration
27+
Client pb.HookHandlerClient
28+
Secure bool
29+
ServerTLSCertificateFilePath string
30+
ClientTLSCertificateFilePath string
31+
ClientTLSCertificateKeyFilePath string
2332
}
2433

2534
func (g *GrpcHook) Setup() error {
35+
grpcOpts := []grpc.DialOption{}
36+
37+
if g.Secure {
38+
if g.ServerTLSCertificateFilePath == "" {
39+
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")
40+
}
41+
42+
// Load the server's TLS certificate if provided
43+
serverCert, err := os.ReadFile(g.ServerTLSCertificateFilePath)
44+
if err != nil {
45+
return err
46+
}
47+
48+
// Create a certificate pool and add the server's certificate
49+
certPool := x509.NewCertPool()
50+
certPool.AppendCertsFromPEM(serverCert)
51+
52+
// Create TLS configuration with the server's CA certificate
53+
tlsConfig := &tls.Config{
54+
RootCAs: certPool,
55+
}
56+
57+
// If client's TLS certificate and key file paths are provided, use mutual TLS
58+
if g.ClientTLSCertificateFilePath != "" && g.ClientTLSCertificateKeyFilePath != "" {
59+
// Load the client's TLS certificate and private key
60+
clientCert, err := tls.LoadX509KeyPair(g.ClientTLSCertificateFilePath, g.ClientTLSCertificateKeyFilePath)
61+
if err != nil {
62+
return err
63+
}
64+
65+
// Append client certificate to the TLS configuration
66+
tlsConfig.Certificates = append(tlsConfig.Certificates, clientCert)
67+
}
68+
69+
grpcOpts = append(grpcOpts, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)))
70+
} else {
71+
grpcOpts = append(grpcOpts, grpc.WithTransportCredentials(insecure.NewCredentials()))
72+
}
73+
2674
opts := []grpc_retry.CallOption{
2775
grpc_retry.WithBackoff(grpc_retry.BackoffLinear(g.Backoff)),
2876
grpc_retry.WithMax(uint(g.MaxRetries)),
2977
}
30-
grpcOpts := []grpc.DialOption{
31-
grpc.WithTransportCredentials(insecure.NewCredentials()),
32-
grpc.WithUnaryInterceptor(grpc_retry.UnaryClientInterceptor(opts...)),
33-
}
78+
grpcOpts = append(grpcOpts, grpc.WithUnaryInterceptor(grpc_retry.UnaryClientInterceptor(opts...)))
79+
3480
conn, err := grpc.Dial(g.Endpoint, grpcOpts...)
3581
if err != nil {
3682
return err

0 commit comments

Comments
 (0)