2022-07-07 20:11:50 +00:00
|
|
|
// Package grpcutil implements various utilities to simplify common gRPC APIs.
|
|
|
|
package grpcutil
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"crypto/tls"
|
|
|
|
"crypto/x509"
|
2024-02-24 00:34:55 +00:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io/fs"
|
|
|
|
"os"
|
2022-07-07 20:11:50 +00:00
|
|
|
|
|
|
|
"github.com/certifi/gocertifi"
|
|
|
|
"google.golang.org/grpc"
|
|
|
|
"google.golang.org/grpc/credentials"
|
|
|
|
)
|
|
|
|
|
2024-02-24 00:34:55 +00:00
|
|
|
type verification int
|
|
|
|
|
2022-07-07 20:11:50 +00:00
|
|
|
const (
|
2024-02-24 00:34:55 +00:00
|
|
|
// SkipVerifyCA is a constant that improves the readability of functions
|
|
|
|
// with the insecureSkipVerify parameter.
|
|
|
|
SkipVerifyCA verification = iota
|
2022-07-07 20:11:50 +00:00
|
|
|
|
2024-02-24 00:34:55 +00:00
|
|
|
// VerifyCA is a constant that improves the readability of functions
|
|
|
|
// with the insecureSkipVerify parameter.
|
|
|
|
VerifyCA
|
2022-07-07 20:11:50 +00:00
|
|
|
)
|
|
|
|
|
2024-02-24 00:34:55 +00:00
|
|
|
func (v verification) asInsecureSkipVerify() bool {
|
|
|
|
switch v {
|
|
|
|
case SkipVerifyCA:
|
|
|
|
return true
|
|
|
|
case VerifyCA:
|
|
|
|
return false
|
|
|
|
default:
|
|
|
|
panic("unknown verification")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithSystemCerts returns a grpc.DialOption that uses the system-provided
|
|
|
|
// certificate authority chain to verify the connection.
|
2022-07-07 20:11:50 +00:00
|
|
|
//
|
2024-02-24 00:34:55 +00:00
|
|
|
// If one cannot be found, this falls back to using a vendored version of
|
|
|
|
// Mozilla's collection of root certificate authorities.
|
|
|
|
func WithSystemCerts(v verification) (grpc.DialOption, error) {
|
2022-07-07 20:11:50 +00:00
|
|
|
certPool, err := x509.SystemCertPool()
|
|
|
|
if err != nil {
|
|
|
|
// Fall back to Mozilla collection of root CAs.
|
|
|
|
certPool, err = gocertifi.CACerts()
|
|
|
|
if err != nil {
|
|
|
|
// This library promises that this should never occur.
|
2024-02-24 00:34:55 +00:00
|
|
|
return nil, fmt.Errorf("gocertifi returned an error: %w", err)
|
2022-07-07 20:11:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
|
|
|
|
RootCAs: certPool,
|
2024-02-24 00:34:55 +00:00
|
|
|
InsecureSkipVerify: v.asInsecureSkipVerify(), // nolint
|
|
|
|
})), nil
|
2022-07-07 20:11:50 +00:00
|
|
|
}
|
|
|
|
|
2024-02-24 00:34:55 +00:00
|
|
|
func forEachFileContents(dirPath string, fn func(contents []byte)) error {
|
|
|
|
dirFS := os.DirFS(dirPath)
|
|
|
|
return fs.WalkDir(dirFS, ".", func(path string, d fs.DirEntry, err error) error {
|
|
|
|
if !d.IsDir() {
|
|
|
|
contents, err := fs.ReadFile(dirFS, d.Name())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
fn(contents)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithCustomCerts returns a grpc.DialOption for requiring TLS that is
|
|
|
|
// authenticated using a certificate authority chain provided as a path on disk.
|
2022-07-07 20:11:50 +00:00
|
|
|
//
|
2024-02-24 00:34:55 +00:00
|
|
|
// If the path is a directory, all files are loaded.
|
|
|
|
func WithCustomCerts(v verification, certPaths ...string) (grpc.DialOption, error) {
|
|
|
|
var caFiles [][]byte
|
|
|
|
for _, certPath := range certPaths {
|
|
|
|
fi, err := os.Stat(certPath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to find certificate: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if fi.IsDir() {
|
|
|
|
if err = forEachFileContents(certPath, func(contents []byte) {
|
|
|
|
caFiles = append(caFiles, contents)
|
|
|
|
}); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
contents, err := os.ReadFile(certPath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
caFiles = append(caFiles, contents)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return WithCustomCertBytes(v, caFiles...)
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithCustomCertBytes returns a grpc.DialOption for requiring TLS that is
|
|
|
|
// authenticated using a certificate authority chain provided in bytes.
|
|
|
|
func WithCustomCertBytes(v verification, certsContents ...[]byte) (grpc.DialOption, error) {
|
|
|
|
certPool := x509.NewCertPool()
|
|
|
|
for _, certContents := range certsContents {
|
|
|
|
if ok := certPool.AppendCertsFromPEM(certContents); !ok {
|
|
|
|
return nil, errors.New("failed to append certs from CA PEM")
|
|
|
|
}
|
2022-07-07 20:11:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
|
|
|
|
RootCAs: certPool,
|
2024-02-24 00:34:55 +00:00
|
|
|
InsecureSkipVerify: v.asInsecureSkipVerify(), // nolint:gosec
|
|
|
|
})), nil
|
2022-07-07 20:11:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type secureMetadataCreds map[string]string
|
|
|
|
|
|
|
|
func (c secureMetadataCreds) RequireTransportSecurity() bool { return true }
|
|
|
|
func (c secureMetadataCreds) GetRequestMetadata(context.Context, ...string) (map[string]string, error) {
|
|
|
|
return c, nil
|
|
|
|
}
|
|
|
|
|
2024-02-24 00:34:55 +00:00
|
|
|
// WithBearerToken returns a grpc.DialOption that adds a standard HTTP Bearer
|
|
|
|
// token to all requests sent from a client.
|
2022-07-07 20:11:50 +00:00
|
|
|
func WithBearerToken(token string) grpc.DialOption {
|
|
|
|
return grpc.WithPerRPCCredentials(secureMetadataCreds{"authorization": "Bearer " + token})
|
|
|
|
}
|
|
|
|
|
|
|
|
type insecureMetadataCreds map[string]string
|
|
|
|
|
|
|
|
func (c insecureMetadataCreds) RequireTransportSecurity() bool { return false }
|
2024-02-24 00:34:55 +00:00
|
|
|
func (c insecureMetadataCreds) GetRequestMetadata(_ context.Context, _ ...string) (map[string]string, error) {
|
2022-07-07 20:11:50 +00:00
|
|
|
return c, nil
|
|
|
|
}
|
|
|
|
|
2024-02-24 00:34:55 +00:00
|
|
|
// WithInsecureBearerToken returns a grpc.DialOption that adds a standard HTTP
|
|
|
|
// Bearer token to all requests sent from an insecure client.
|
2022-07-07 20:11:50 +00:00
|
|
|
//
|
2024-02-24 00:34:55 +00:00
|
|
|
// Must be used in conjunction with `insecure.NewCredentials()`.
|
2022-07-07 20:11:50 +00:00
|
|
|
func WithInsecureBearerToken(token string) grpc.DialOption {
|
|
|
|
return grpc.WithPerRPCCredentials(insecureMetadataCreds{"authorization": "Bearer " + token})
|
|
|
|
}
|