Skip to content

Commit

Permalink
Introduce TLS Settings for go-micro based grpc services and clients
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
rhafer committed Nov 2, 2022
1 parent b7482e5 commit 9d8b4a1
Show file tree
Hide file tree
Showing 39 changed files with 427 additions and 68 deletions.
5 changes: 3 additions & 2 deletions changelog/unreleased/grpc-tls.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
Enhancement: Allow to setup TLS for the reva grpc services
Enhancement: Allow to setup TLS for grpc services

We added config options to allow enabling TLS encrption for all reva backed
We added config options to allow enabling TLS encrption for all reva and go-micro backed
grpc services.

https://github.com/owncloud/ocis/pull/4798
https://github.com/owncloud/ocis/pull/4901
8 changes: 5 additions & 3 deletions ocis-pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
MicroGRPCClient *shared.MicroGRPCClient `yaml:"micro_grpc_client"`
MicroGRPCService *shared.MicroGRPCService `yaml:"micro_grpc_service"`

Mode Mode // DEPRECATED
File string
Expand Down
15 changes: 15 additions & 0 deletions ocis-pkg/config/parser/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ func EnsureDefaults(cfg *config.Config) {
if cfg.CacheStore == nil {
cfg.CacheStore = &shared.CacheStore{}
}
if cfg.MicroGRPCClient == nil {
cfg.MicroGRPCClient = &shared.MicroGRPCClient{}
}
if cfg.MicroGRPCService == nil {
cfg.MicroGRPCService = &shared.MicroGRPCService{}
}

}

// EnsureCommons copies applicable parts of the oCIS config into the commons part
Expand Down Expand Up @@ -94,6 +101,14 @@ func EnsureCommons(cfg *config.Config) {
cfg.Commons.CacheStore = &shared.CacheStore{}
}

if cfg.MicroGRPCClient != nil {
cfg.Commons.MicroGRPCClient = cfg.MicroGRPCClient
}

if cfg.MicroGRPCService != nil {
cfg.Commons.MicroGRPCService = cfg.MicroGRPCService
}

// copy token manager to the commons part if set
if cfg.TokenManager != nil {
cfg.Commons.TokenManager = cfg.TokenManager
Expand Down
101 changes: 101 additions & 0 deletions ocis-pkg/service/grpc/client.go
Original file line number Diff line number Diff line change
@@ -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(mc *shared.MicroGRPCClient) []ClientOption {
opts := []ClientOption{
WithTLSMode(mc.TLSMode),
WithTLSCACert(mc.TLSCACert),
}
return opts
}
32 changes: 25 additions & 7 deletions ocis-pkg/service/grpc/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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) {
Expand Down
69 changes: 40 additions & 29 deletions ocis-pkg/service/grpc/service.go
Original file line number Diff line number Diff line change
@@ -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("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("No server certificate configured. Generating a temporary self-signed certificate")

cert, err = mtls.Certificate(subj...)
if err != nil {
return Service{}, fmt.Errorf("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),
Expand All @@ -65,5 +76,5 @@ func NewService(opts ...Option) Service {
micro.WrapSubscriber(opencensus.NewSubscriberWrapper()),
}

return Service{micro.NewService(mopts...)}
return Service{micro.NewService(mopts...)}, nil
}
35 changes: 24 additions & 11 deletions ocis-pkg/shared/shared_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,17 @@ type Reva struct {
TLSCACert string `yaml:"tls_cacert" env:"REVA_GATEWAY_TLS_CACERT" desc:"The root CA certificate used to validate the gateway's TLS certificate."`
}

type MicroGRPCClient struct {
TLSMode string `yaml:"tls_mode" env:"OCIS_MICRO_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."`
TLSCACert string `yaml:"tls_cacert env:"OCIS_MICRO_GRPC_CLIENT_TLS_CACERT" desc:"The root CA certificate used to validate TLS server certificates of the go-micro based grpc services."`
}

type MicroGRPCService struct {
TLSEnabled bool `yaml:"tls_enabled" env:"OCIS_MICRO_GRPC_TLS_ENABLED"`
TLSCert string `yaml:"tls_cert" env:"OCIS_MICRO_GRPC_TLS_CERTIFICATE" desc:"Path/File name of the TLS server certificate (in PEM format) for the go-micro based grpc services."`
TLSKey string `yaml:"tls_key" env:"OCIS_MICRO_GRPC_TLS_KEY" desc:"Path/File name for the TLS certificate key (in PEM format) for the server certificate to use for the go-micro based grpc services."`
}

type CacheStore struct {
Type string `yaml:"type" env:"OCIS_CACHE_STORE_TYPE" desc:"The type of the cache store. Valid options are \"noop\", \"ocmem\", \"etcd\" and \"memory\""`
Address string `yaml:"address" env:"OCIS_CACHE_STORE_ADDRESS" desc:"A comma-separated list of addresses to connect to. Only valid if the above setting is set to \"etcd\""`
Expand All @@ -45,15 +56,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"`
MicroGRPCClient *MicroGRPCClient `yaml:"micro_grpc_client"`
MicroGRPCService *MicroGRPCService `yaml:"micro_grpc_service"`
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."`
}
5 changes: 5 additions & 0 deletions ocis/pkg/command/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"github.com/owncloud/ocis/v2/ocis-pkg/config/configlog"
"github.com/owncloud/ocis/v2/ocis-pkg/config/parser"
"github.com/owncloud/ocis/v2/ocis-pkg/registry"
"github.com/owncloud/ocis/v2/ocis-pkg/service/grpc"
"github.com/owncloud/ocis/v2/ocis/pkg/register"
"github.com/owncloud/ocis/v2/ocis/pkg/runtime"
"github.com/urfave/cli/v2"
Expand All @@ -22,6 +23,10 @@ func Server(cfg *config.Config) *cli.Command {
Action: func(c *cli.Context) error {
// Prefer the in-memory registry as the default when running in single-binary mode
registry.Configure("memory")
err := grpc.Configure(grpc.GetClientOptions(cfg.MicroGRPCClient)...)
if err != nil {
return err
}
r := runtime.New(cfg)
return r.Start()
},
Expand Down
5 changes: 5 additions & 0 deletions services/graph/pkg/command/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/oklog/run"
"github.com/owncloud/ocis/v2/ocis-pkg/config/configlog"
ogrpc "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc"
"github.com/owncloud/ocis/v2/ocis-pkg/version"
"github.com/owncloud/ocis/v2/services/graph/pkg/config"
"github.com/owncloud/ocis/v2/services/graph/pkg/config/parser"
Expand All @@ -32,6 +33,10 @@ func Server(cfg *config.Config) *cli.Command {
if err != nil {
return err
}
err = ogrpc.Configure(ogrpc.GetClientOptions(cfg.MicroGRPCClient)...)
if err != nil {
return err
}

gr := run.Group{}
ctx, cancel := func() (context.Context, context.CancelFunc) {
Expand Down
5 changes: 3 additions & 2 deletions services/graph/pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ type Config struct {

HTTP HTTP `yaml:"http"`

Reva *shared.Reva `yaml:"reva"`
TokenManager *TokenManager `yaml:"token_manager"`
Reva *shared.Reva `yaml:"reva"`
TokenManager *TokenManager `yaml:"token_manager"`
MicroGRPCClient *shared.MicroGRPCClient `yaml:"micro_grpc_client"`

Spaces Spaces `yaml:"spaces"`
Identity Identity `yaml:"identity"`
Expand Down
Loading

0 comments on commit 9d8b4a1

Please sign in to comment.