[full-ci] Introduce TLS Settings for go-micro based grpc services and clients (#4901)
* Introduce TLS Settings for go-micro based grpc services and clients TLS for the services can be configure by setting the OCIS_MICRO_GRPC_TLS_ENABLED" "OCIS_MICRO_GRPC_TLS_CERTIFICATE" and "OCIS_MICRO_GRPC_TLS_KEY" enviroment variables. TLS for the clients can configured by setting the "OCIS_MICRO_GRPC_CLIENT_TLS_MODE" and "OCIS_MICRO_GRPC_CLIENT_TLS_CACERT" variables. By default TLS is disabled. Co-authored-by: Martin <github@diemattels.at> * Unify TLS configuration for all grpc services All grpc service (whether they're based on reva) or go-micro use the same set of config vars now. TLS for the services can be configure by setting the OCIS_GRPC_TLS_ENABLED, OCIS_GRPC_TLS_CERTIFICATE and OCIS_GRPC_TLS_KEY enviroment variables. TLS for the clients can configured by setting the OCIS_GRPC_CLIENT_TLS_MODE and OCIS_MICRO_GRPC_CLIENT_TLS_CACERT variables. There are no individual per service config vars currently. If really needed, per service tls configurations can be specified via config file. Co-authored-by: Martin <github@diemattels.at> Co-authored-by: Martin <github@diemattels.at>
This commit is contained in:
@@ -56,9 +56,11 @@ type Runtime struct {
|
||||
type Config struct {
|
||||
*shared.Commons `yaml:"shared"`
|
||||
|
||||
Tracing *shared.Tracing `yaml:"tracing"`
|
||||
Log *shared.Log `yaml:"log"`
|
||||
CacheStore *shared.CacheStore `yaml:"cache_store"`
|
||||
Tracing *shared.Tracing `yaml:"tracing"`
|
||||
Log *shared.Log `yaml:"log"`
|
||||
CacheStore *shared.CacheStore `yaml:"cache_store"`
|
||||
GRPCClientTLS *shared.GRPCClientTLS `yaml:"grpc_client_tls"`
|
||||
GRPCServiceTLS *shared.GRPCServiceTLS `yaml:"grpc_service_tls"`
|
||||
|
||||
Mode Mode // DEPRECATED
|
||||
File string
|
||||
|
||||
@@ -51,6 +51,13 @@ func EnsureDefaults(cfg *config.Config) {
|
||||
if cfg.CacheStore == nil {
|
||||
cfg.CacheStore = &shared.CacheStore{}
|
||||
}
|
||||
if cfg.GRPCClientTLS == nil {
|
||||
cfg.GRPCClientTLS = &shared.GRPCClientTLS{}
|
||||
}
|
||||
if cfg.GRPCServiceTLS == nil {
|
||||
cfg.GRPCServiceTLS = &shared.GRPCServiceTLS{}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// EnsureCommons copies applicable parts of the oCIS config into the commons part
|
||||
@@ -94,6 +101,14 @@ func EnsureCommons(cfg *config.Config) {
|
||||
cfg.Commons.CacheStore = &shared.CacheStore{}
|
||||
}
|
||||
|
||||
if cfg.GRPCClientTLS != nil {
|
||||
cfg.Commons.GRPCClientTLS = cfg.GRPCClientTLS
|
||||
}
|
||||
|
||||
if cfg.GRPCServiceTLS != nil {
|
||||
cfg.Commons.GRPCServiceTLS = cfg.GRPCServiceTLS
|
||||
}
|
||||
|
||||
// copy token manager to the commons part if set
|
||||
if cfg.TokenManager != nil {
|
||||
cfg.Commons.TokenManager = cfg.TokenManager
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"sync"
|
||||
|
||||
mgrpcc "github.com/go-micro/plugins/v4/client/grpc"
|
||||
mbreaker "github.com/go-micro/plugins/v4/wrapper/breaker/gobreaker"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/registry"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/shared"
|
||||
"go-micro.dev/v4/client"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultClient client.Client
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
// ClientOptions represent options (e.g. tls settings) for the grpc clients
|
||||
type ClientOptions struct {
|
||||
tlsMode string
|
||||
caCert string
|
||||
}
|
||||
|
||||
// Option is used to pass client options
|
||||
type ClientOption func(opts *ClientOptions)
|
||||
|
||||
// WithTLSMode allows to set the TLSMode option for grpc clients
|
||||
func WithTLSMode(v string) ClientOption {
|
||||
return func(o *ClientOptions) {
|
||||
o.tlsMode = v
|
||||
}
|
||||
}
|
||||
|
||||
// WithTLSCACert allows to set the CA Certificate for grpc clients
|
||||
func WithTLSCACert(v string) ClientOption {
|
||||
return func(o *ClientOptions) {
|
||||
o.caCert = v
|
||||
}
|
||||
}
|
||||
|
||||
// Configure configures the default oOCIS grpc client (e.g. TLS settings)
|
||||
func Configure(opts ...ClientOption) error {
|
||||
var options ClientOptions
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
var outerr error
|
||||
once.Do(func() {
|
||||
reg := registry.GetRegistry()
|
||||
var tlsConfig *tls.Config
|
||||
cOpts := []client.Option{
|
||||
client.Registry(reg),
|
||||
client.Wrap(mbreaker.NewClientWrapper()),
|
||||
}
|
||||
switch options.tlsMode {
|
||||
case "insecure":
|
||||
tlsConfig = &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
cOpts = append(cOpts, mgrpcc.AuthTLS(tlsConfig))
|
||||
case "on":
|
||||
tlsConfig = &tls.Config{}
|
||||
// Note: If caCert is empty we use the system's default set of trusted CAs
|
||||
if options.caCert != "" {
|
||||
certs := x509.NewCertPool()
|
||||
pemData, err := ioutil.ReadFile(options.caCert)
|
||||
if err != nil {
|
||||
outerr = err
|
||||
return
|
||||
}
|
||||
if !certs.AppendCertsFromPEM(pemData) {
|
||||
outerr = errors.New("Error initializing LDAP Backend. Adding CA cert failed")
|
||||
return
|
||||
}
|
||||
tlsConfig.RootCAs = certs
|
||||
}
|
||||
cOpts = append(cOpts, mgrpcc.AuthTLS(tlsConfig))
|
||||
}
|
||||
|
||||
defaultClient = mgrpcc.NewClient(cOpts...)
|
||||
})
|
||||
return outerr
|
||||
}
|
||||
|
||||
// DefaultClient returns a custom oCIS grpc configured client.
|
||||
func DefaultClient() client.Client {
|
||||
return defaultClient
|
||||
}
|
||||
|
||||
func GetClientOptions(t *shared.GRPCClientTLS) []ClientOption {
|
||||
opts := []ClientOption{
|
||||
WithTLSMode(t.Mode),
|
||||
WithTLSCACert(t.CACert),
|
||||
}
|
||||
return opts
|
||||
}
|
||||
@@ -12,13 +12,16 @@ type Option func(o *Options)
|
||||
|
||||
// Options defines the available options for this package.
|
||||
type Options struct {
|
||||
Logger log.Logger
|
||||
Namespace string
|
||||
Name string
|
||||
Version string
|
||||
Address string
|
||||
Context context.Context
|
||||
Flags []cli.Flag
|
||||
Logger log.Logger
|
||||
Namespace string
|
||||
Name string
|
||||
Version string
|
||||
Address string
|
||||
TLSEnabled bool
|
||||
TLSCert string
|
||||
TLSKey string
|
||||
Context context.Context
|
||||
Flags []cli.Flag
|
||||
}
|
||||
|
||||
// newOptions initializes the available default options.
|
||||
@@ -69,6 +72,21 @@ func Address(a string) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// TLSEnabled provides a function to enable/disable TLS
|
||||
func TLSEnabled(v bool) Option {
|
||||
return func(o *Options) {
|
||||
o.TLSEnabled = v
|
||||
}
|
||||
}
|
||||
|
||||
// TLSCert provides a function to set the TLS server certificate and key
|
||||
func TLSCert(c string, k string) Option {
|
||||
return func(o *Options) {
|
||||
o.TLSCert = c
|
||||
o.TLSKey = k
|
||||
}
|
||||
}
|
||||
|
||||
// Context provides a function to set the context option.
|
||||
func Context(ctx context.Context) Option {
|
||||
return func(o *Options) {
|
||||
|
||||
@@ -1,54 +1,65 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
mgrpcc "github.com/go-micro/plugins/v4/client/grpc"
|
||||
mgrpcs "github.com/go-micro/plugins/v4/server/grpc"
|
||||
mbreaker "github.com/go-micro/plugins/v4/wrapper/breaker/gobreaker"
|
||||
"github.com/go-micro/plugins/v4/wrapper/monitoring/prometheus"
|
||||
"github.com/go-micro/plugins/v4/wrapper/trace/opencensus"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/registry"
|
||||
"go-micro.dev/v4"
|
||||
"go-micro.dev/v4/client"
|
||||
"go-micro.dev/v4/server"
|
||||
mtls "go-micro.dev/v4/util/tls"
|
||||
)
|
||||
|
||||
// DefaultClient is a custom oCIS grpc configured client.
|
||||
var (
|
||||
defaultClient client.Client
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
func DefaultClient() client.Client {
|
||||
return getDefaultGrpcClient()
|
||||
}
|
||||
|
||||
func getDefaultGrpcClient() client.Client {
|
||||
once.Do(func() {
|
||||
reg := registry.GetRegistry()
|
||||
|
||||
defaultClient = mgrpcc.NewClient(
|
||||
client.Registry(reg),
|
||||
client.Wrap(mbreaker.NewClientWrapper()),
|
||||
)
|
||||
})
|
||||
return defaultClient
|
||||
}
|
||||
|
||||
// Service simply wraps the go-micro grpc service.
|
||||
type Service struct {
|
||||
micro.Service
|
||||
}
|
||||
|
||||
// NewService initializes a new grpc service.
|
||||
func NewService(opts ...Option) Service {
|
||||
func NewService(opts ...Option) (Service, error) {
|
||||
var mServer server.Server
|
||||
sopts := newOptions(opts...)
|
||||
tlsConfig := &tls.Config{}
|
||||
if sopts.TLSEnabled {
|
||||
var cert tls.Certificate
|
||||
var err error
|
||||
if sopts.TLSCert != "" {
|
||||
cert, err = tls.LoadX509KeyPair(sopts.TLSCert, sopts.TLSKey)
|
||||
if err != nil {
|
||||
sopts.Logger.Error().Err(err).Str("cert", sopts.TLSCert).Str("key", sopts.TLSKey).Msg("error loading server certifcate and key")
|
||||
return Service{}, fmt.Errorf("grpc service error loading server certificate and key: %w", err)
|
||||
}
|
||||
} else {
|
||||
// Generate a self-signed server certificate on the fly. This requires the clients
|
||||
// to connect with InsecureSkipVerify.
|
||||
subj := []string{sopts.Address}
|
||||
if host, _, err := net.SplitHostPort(sopts.Address); err == nil && host != "" {
|
||||
subj = []string{host}
|
||||
}
|
||||
|
||||
sopts.Logger.Warn().Str("address", sopts.Address).
|
||||
Msg("GRPC: No server certificate configured. Generating a temporary self-signed certificate")
|
||||
|
||||
cert, err = mtls.Certificate(subj...)
|
||||
if err != nil {
|
||||
return Service{}, fmt.Errorf("grpc service error creating temporary self-signed certificate: %w", err)
|
||||
}
|
||||
}
|
||||
tlsConfig.Certificates = []tls.Certificate{cert}
|
||||
mServer = mgrpcs.NewServer(mgrpcs.AuthTLS(tlsConfig))
|
||||
} else {
|
||||
mServer = mgrpcs.NewServer()
|
||||
}
|
||||
|
||||
mopts := []micro.Option{
|
||||
// first add a server because it will reset any options
|
||||
micro.Server(mgrpcs.NewServer()),
|
||||
micro.Server(mServer),
|
||||
// also add a client that can be used after initializing the service
|
||||
micro.Client(DefaultClient()),
|
||||
micro.Address(sopts.Address),
|
||||
@@ -65,5 +76,5 @@ func NewService(opts ...Option) Service {
|
||||
micro.WrapSubscriber(opencensus.NewSubscriberWrapper()),
|
||||
}
|
||||
|
||||
return Service{micro.NewService(mopts...)}
|
||||
return Service{micro.NewService(mopts...)}, nil
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ func DefaultRevaConfig() *Reva {
|
||||
}
|
||||
|
||||
func (r *Reva) GetRevaOptions() []pool.Option {
|
||||
tm, _ := pool.StringToTLSMode(r.TLSMode)
|
||||
tm, _ := pool.StringToTLSMode(r.TLS.Mode)
|
||||
opts := []pool.Option{
|
||||
pool.WithTLSMode(tm),
|
||||
}
|
||||
@@ -22,7 +22,7 @@ func (r *Reva) GetRevaOptions() []pool.Option {
|
||||
|
||||
func (r *Reva) GetGRPCClientConfig() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"tls_mode": r.TLSMode,
|
||||
"tls_cacert": r.TLSCACert,
|
||||
"tls_mode": r.TLS.Mode,
|
||||
"tls_cacert": r.TLS.CACert,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,9 +31,19 @@ type TokenManager struct {
|
||||
|
||||
// Reva defines all available REVA client configuration.
|
||||
type Reva struct {
|
||||
Address string `yaml:"address" env:"REVA_GATEWAY" desc:"The CS3 gateway endpoint."`
|
||||
TLSMode string `yaml:"tls_mode" env:"REVA_GATEWAY_TLS_MODE" desc:"TLS mode for grpc connection to the CS3 gateway endpoint. Possible values are 'off', 'insecure' and 'on'. 'off': disables transport security for the clients. 'insecure' allows to use transport security, but disables certificate verification (to be used with the autogenerated self-signed certificates). 'on' enables transport security, including server ceritificate verification."`
|
||||
TLSCACert string `yaml:"tls_cacert" env:"REVA_GATEWAY_TLS_CACERT" desc:"The root CA certificate used to validate the gateway's TLS certificate."`
|
||||
Address string `yaml:"address" env:"REVA_GATEWAY" desc:"The CS3 gateway endpoint."`
|
||||
TLS GRPCClientTLS `yaml:"tls"`
|
||||
}
|
||||
|
||||
type GRPCClientTLS struct {
|
||||
Mode string `yaml:"mode" env:"OCIS_GRPC_CLIENT_TLS_MODE" desc:"TLS mode for grpc connection to the go-micro based grpc services. Possible values are 'off', 'insecure' and 'on'. 'off': disables transport security for the clients. 'insecure' allows to use transport security, but disables certificate verification (to be used with the autogenerated self-signed certificates). 'on' enables transport security, including server ceritificate verification."`
|
||||
CACert string `yaml:"cacert env:"OCIS_GRPC_CLIENT_TLS_CACERT" desc:"The root CA certificate used to validate TLS server certificates of the go-micro based grpc services."`
|
||||
}
|
||||
|
||||
type GRPCServiceTLS struct {
|
||||
Enabled bool `yaml:"enabled" env:"OCIS_GRPC_TLS_ENABLED" desc:"Activates TLS for the grpcs based services using the server certifcate and key configured via OCIS_GRPC_TLS_CERTIFICATE and OCIS_GRPC_TLS_KEY. If OCIS_GRPC_TLS_CERTIFICATE is not set a temporary server certificate is generated - to be used with OCIS_GRPC_CLIENT_TLS_MODE=insecure."`
|
||||
Cert string `yaml:"cert" env:"OCIS_GRPC_TLS_CERTIFICATE" desc:"Path/File name of the TLS server certificate (in PEM format) for the grpc services."`
|
||||
Key string `yaml:"key" env:"OCIS_GRPC_TLS_KEY" desc:"Path/File name for the TLS certificate key (in PEM format) for the server certificate to use for the grpc services."`
|
||||
}
|
||||
|
||||
type CacheStore struct {
|
||||
@@ -45,15 +55,17 @@ type CacheStore struct {
|
||||
// Commons holds configuration that are common to all extensions. Each extension can then decide whether
|
||||
// to overwrite its values.
|
||||
type Commons struct {
|
||||
Log *Log `yaml:"log"`
|
||||
Tracing *Tracing `yaml:"tracing"`
|
||||
CacheStore *CacheStore `yaml:"cache_store"`
|
||||
OcisURL string `yaml:"ocis_url" env:"OCIS_URL" desc:"URL, where oCIS is reachable for users."`
|
||||
TokenManager *TokenManager `mask:"struct" yaml:"token_manager"`
|
||||
Reva *Reva `yaml:"reva"`
|
||||
MachineAuthAPIKey string `mask:"password" yaml:"machine_auth_api_key" env:"OCIS_MACHINE_AUTH_API_KEY" desc:"Machine auth API key used to validate internal requests necessary for the access to resources from other services."`
|
||||
TransferSecret string `mask:"password" yaml:"transfer_secret,omitempty" env:"REVA_TRANSFER_SECRET"`
|
||||
SystemUserID string `yaml:"system_user_id" env:"OCIS_SYSTEM_USER_ID" desc:"ID of the oCIS storage-system system user. Admins need to set the ID for the storage-system system user in this config option which is then used to reference the user. Any reasonable long string is possible, preferably this would be an UUIDv4 format."`
|
||||
SystemUserAPIKey string `mask:"password" yaml:"system_user_api_key" env:"SYSTEM_USER_API_KEY"`
|
||||
AdminUserID string `yaml:"admin_user_id" env:"OCIS_ADMIN_USER_ID" desc:"ID of a user, that should receive admin privileges."`
|
||||
Log *Log `yaml:"log"`
|
||||
Tracing *Tracing `yaml:"tracing"`
|
||||
CacheStore *CacheStore `yaml:"cache_store"`
|
||||
GRPCClientTLS *GRPCClientTLS `yaml:"grpc_client_tls"`
|
||||
GRPCServiceTLS *GRPCServiceTLS `yaml:"grpc_service_tls"`
|
||||
OcisURL string `yaml:"ocis_url" env:"OCIS_URL" desc:"URL, where oCIS is reachable for users."`
|
||||
TokenManager *TokenManager `mask:"struct" yaml:"token_manager"`
|
||||
Reva *Reva `yaml:"reva"`
|
||||
MachineAuthAPIKey string `mask:"password" yaml:"machine_auth_api_key" env:"OCIS_MACHINE_AUTH_API_KEY" desc:"Machine auth API key used to validate internal requests necessary for the access to resources from other services."`
|
||||
TransferSecret string `mask:"password" yaml:"transfer_secret,omitempty" env:"REVA_TRANSFER_SECRET"`
|
||||
SystemUserID string `yaml:"system_user_id" env:"OCIS_SYSTEM_USER_ID" desc:"ID of the oCIS storage-system system user. Admins need to set the ID for the storage-system system user in this config option which is then used to reference the user. Any reasonable long string is possible, preferably this would be an UUIDv4 format."`
|
||||
SystemUserAPIKey string `mask:"password" yaml:"system_user_api_key" env:"SYSTEM_USER_API_KEY"`
|
||||
AdminUserID string `yaml:"admin_user_id" env:"OCIS_ADMIN_USER_ID" desc:"ID of a user, that should receive admin privileges."`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user