feat: TLS support (#3862)

* feat: TLS support

* add comment

* fix comment
This commit is contained in:
Livio Spring 2022-06-24 14:38:22 +02:00 committed by GitHub
parent 70a108deeb
commit ed5721d39e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 186 additions and 19 deletions

View File

@ -9,6 +9,7 @@ import (
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/cmd/admin/key"
"github.com/zitadel/zitadel/cmd/admin/tls"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/migration"
@ -28,6 +29,9 @@ func New() *cobra.Command {
Requirements:
- cockroachdb`,
Run: func(cmd *cobra.Command, args []string) {
err := tls.ModeFromFlag(cmd)
logging.OnError(err).Fatal("invalid tlsMode")
config := MustNewConfig(viper.GetViper())
steps := MustNewSteps(viper.New())
@ -46,6 +50,7 @@ Requirements:
func Flags(cmd *cobra.Command) {
cmd.PersistentFlags().StringArrayVar(&stepFiles, "steps", nil, "paths to step files to overwrite default steps")
key.AddMasterKeyFlag(cmd)
tls.AddTLSModeFlag(cmd)
}
func Setup(config *Config, steps *Steps, masterKey string) {

View File

@ -16,6 +16,7 @@ import (
auth_es "github.com/zitadel/zitadel/internal/auth/repository/eventsourcing"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/config/hook"
"github.com/zitadel/zitadel/internal/config/network"
"github.com/zitadel/zitadel/internal/config/systemdefaults"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/database"
@ -31,6 +32,7 @@ type Config struct {
ExternalPort uint16
ExternalDomain string
ExternalSecure bool
TLS network.TLS
HTTP2HostHeader string
HTTP1HostHeader string
WebAuthNName string

View File

@ -5,14 +5,17 @@ import (
"github.com/spf13/viper"
"github.com/zitadel/zitadel/cmd/admin/key"
"github.com/zitadel/zitadel/cmd/admin/tls"
)
var tlsMode *string
func startFlags(cmd *cobra.Command) {
bindUint16Flag(cmd, "port", "port to run ZITADEL on")
bindStringFlag(cmd, "externalDomain", "domain ZITADEL will be exposed on")
bindStringFlag(cmd, "externalPort", "port ZITADEL will be exposed on")
bindBoolFlag(cmd, "externalSecure", "if ZITADEL will be served on HTTPS")
tls.AddTLSModeFlag(cmd)
key.AddMasterKeyFlag(cmd)
}

View File

@ -2,6 +2,7 @@ package start
import (
"context"
"crypto/tls"
"database/sql"
_ "embed"
"fmt"
@ -21,6 +22,7 @@ import (
"golang.org/x/net/http2/h2c"
"github.com/zitadel/zitadel/cmd/admin/key"
cmd_tls "github.com/zitadel/zitadel/cmd/admin/tls"
admin_es "github.com/zitadel/zitadel/internal/admin/repository/eventsourcing"
"github.com/zitadel/zitadel/internal/api"
"github.com/zitadel/zitadel/internal/api/assets"
@ -57,6 +59,10 @@ func New() *cobra.Command {
Requirements:
- cockroachdb`,
RunE: func(cmd *cobra.Command, args []string) error {
err := cmd_tls.ModeFromFlag(cmd)
if err != nil {
return err
}
config := MustNewConfig(viper.GetViper())
masterKey, err := key.MasterKey(cmd)
if err != nil {
@ -136,14 +142,18 @@ func startZitadel(config *Config, masterKey string) error {
notification.Start(config.Notification, config.ExternalPort, config.ExternalSecure, commands, queries, dbClient, assets.HandlerPrefix, config.SystemDefaults.Notifications.FileSystemPath, keys.User, keys.SMTP, keys.SMS)
router := mux.NewRouter()
err = startAPIs(ctx, router, commands, queries, eventstoreClient, dbClient, config, storage, authZRepo, keys, config.SystemAPIUsers)
tlsConfig, err := config.TLS.Config()
if err != nil {
return err
}
return listen(ctx, router, config.Port)
err = startAPIs(ctx, router, commands, queries, eventstoreClient, dbClient, config, storage, authZRepo, keys)
if err != nil {
return err
}
return listen(ctx, router, config.Port, tlsConfig)
}
func startAPIs(ctx context.Context, router *mux.Router, commands *command.Commands, queries *query.Queries, eventstore *eventstore.Eventstore, dbClient *sql.DB, config *Config, store static.Storage, authZRepo authz_repo.Repository, keys *encryptionKeys, systemAPIKeys map[string]*internal_authz.SystemAPIUser) error {
func startAPIs(ctx context.Context, router *mux.Router, commands *command.Commands, queries *query.Queries, eventstore *eventstore.Eventstore, dbClient *sql.DB, config *Config, store static.Storage, authZRepo authz_repo.Repository, keys *encryptionKeys) error {
repo := struct {
authz_repo.Repository
*query.Queries
@ -151,9 +161,12 @@ func startAPIs(ctx context.Context, router *mux.Router, commands *command.Comman
authZRepo,
queries,
}
verifier := internal_authz.Start(repo, http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure), systemAPIKeys)
apis := api.New(config.Port, router, queries, verifier, config.InternalAuthZ, config.ExternalSecure, config.HTTP2HostHeader, config.HTTP1HostHeader)
verifier := internal_authz.Start(repo, http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure), config.SystemAPIUsers)
tlsConfig, err := config.TLS.Config()
if err != nil {
return err
}
apis := api.New(config.Port, router, queries, verifier, config.InternalAuthZ, config.ExternalSecure, tlsConfig, config.HTTP2HostHeader, config.HTTP1HostHeader)
authRepo, err := auth_es.Start(config.Auth, config.SystemDefaults, commands, queries, dbClient, keys.OIDC, keys.User)
if err != nil {
return fmt.Errorf("error starting auth repo: %w", err)
@ -215,9 +228,9 @@ func startAPIs(ctx context.Context, router *mux.Router, commands *command.Comman
return nil
}
func listen(ctx context.Context, router *mux.Router, port uint16) error {
func listen(ctx context.Context, router *mux.Router, port uint16, tlsConfig *tls.Config) error {
http2Server := &http2.Server{}
http1Server := &http.Server{Handler: h2c.NewHandler(router, http2Server)}
http1Server := &http.Server{Handler: h2c.NewHandler(router, http2Server), TLSConfig: tlsConfig}
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
if err != nil {
return fmt.Errorf("tcp listener on %d failed: %w", port, err)
@ -227,7 +240,12 @@ func listen(ctx context.Context, router *mux.Router, port uint16) error {
go func() {
logging.Infof("server is listening on %s", lis.Addr().String())
if tlsConfig != nil {
//we don't need to pass the files here, because we already initialized the TLS config on the server
errCh <- http1Server.ServeTLS(lis, "", "")
} else {
errCh <- http1Server.Serve(lis)
}
}()
shutdown := make(chan os.Signal, 1)

View File

@ -8,6 +8,7 @@ import (
"github.com/zitadel/zitadel/cmd/admin/initialise"
"github.com/zitadel/zitadel/cmd/admin/key"
"github.com/zitadel/zitadel/cmd/admin/setup"
"github.com/zitadel/zitadel/cmd/admin/tls"
)
func NewStartFromInit() *cobra.Command {
@ -22,6 +23,9 @@ Last ZITADEL starts.
Requirements:
- cockroachdb`,
Run: func(cmd *cobra.Command, args []string) {
err := tls.ModeFromFlag(cmd)
logging.OnError(err).Fatal("invalid tlsMode")
masterKey, err := key.MasterKey(cmd)
logging.OnError(err).Panic("No master key provided")

46
cmd/admin/tls/tls.go Normal file
View File

@ -0,0 +1,46 @@
package tls
import (
"errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
const (
flagTLSMode = "tlsMode"
)
var (
ErrValidValue = errors.New("value must either be `enabled`, `external` or `disabled`")
)
func AddTLSModeFlag(cmd *cobra.Command) {
if cmd.PersistentFlags().Lookup(flagTLSMode) != nil {
return
}
cmd.PersistentFlags().String(flagTLSMode, "", "start ZITADEL with (enabled), without (disabled) TLS or external component e.g. reverse proxy (external) terminating TLS, this flag will overwrite `externalSecure` and `tls.enabled` in configs files")
}
func ModeFromFlag(cmd *cobra.Command) error {
tlsMode, _ := cmd.Flags().GetString(flagTLSMode)
var tlsEnabled, externalSecure bool
switch tlsMode {
case "enabled":
tlsEnabled = true
externalSecure = true
case "external":
tlsEnabled = false
externalSecure = true
case "disabled":
tlsEnabled = false
externalSecure = false
case "":
return nil
default:
return ErrValidValue
}
viper.Set("tls.enabled", tlsEnabled)
viper.Set("externalSecure", externalSecure)
return nil
}

View File

@ -3,11 +3,38 @@ Log:
Formatter:
Format: text
# Port ZITADEL will listen on
Port: 8080
# Port ZITADEL is exposed on, it can differ from port e.g. if you proxy the traffic
# !!! Changing this after initial setup breaks your system !!!
ExternalPort: 8080
# Domain / hostname ZITADEL is exposed externally
# !!! Changing this after initial setup breaks your system !!!
ExternalDomain: localhost
# specifies if ZITADEL is exposed externally through TLS
# this must be set to true even if TLS is not enabled on ZITADEL itself
# but TLS traffic is terminated on a reverse proxy
# !!! Changing this after initial setup breaks your system !!!
ExternalSecure: true
TLS:
# if enabled, ZITADEL will serve all traffic over TLS (HTTPS and gRPC)
# you must then also provide a private key and certificate to be used for the connection
# either directly or by a path to the corresponding file
Enabled: true
# Path to the private key of the TLS certificate, it will be loaded into the Key
# and overwrite any exising value
KeyPath: #/path/to/key/file.pem
# Private key of the TLS certificate (KeyPath will this overwrite, if specified)
Key: #<bas64 encoded content of a pem file>
# Path to the certificate for the TLS connection, it will be loaded into the Cert
# and overwrite any exising value
CertPath: #/path/to/cert/file.pem
# Certificate for the TLS connection (CertPath will this overwrite, if specified)
Cert: #<bas64 encoded content of a pem file>
# Header name of HTTP2 (incl. gRPC) calls from which the instance will be matched
HTTP2HostHeader: ":authority"
# Header name of HTTP1 calls from which the instance will be matched
HTTP1HostHeader: "host"
WebAuthNName: ZITADEL

View File

@ -14,7 +14,6 @@ cockroach start-single-node --insecure --background --http-addr :9090
Configure your environment and generate a master encryption key
```bash
export ZITADEL_EXTERNALSECURE=false
export ZITADEL_EXTERNALDOMAIN=localhost
export ZITADEL_DEFAULTINSTANCE_CUSTOMDOMAIN=localhost
```
@ -28,5 +27,5 @@ curl -s https://api.github.com/repos/zitadel/zitadel/releases/tags/v2.0.0-v2-alp
Run the database and application containers
```bash
zitadel admin start-from-init --masterkey "MasterkeyNeedsToHave32Characters"
zitadel admin start-from-init --tlsMode disabled --masterkey "MasterkeyNeedsToHave32Characters"
```

View File

@ -14,7 +14,6 @@ cockroach start-single-node --insecure --background --http-addr :9090
Configure your environment and generate a master encryption key
```bash
export ZITADEL_EXTERNALSECURE=false
export ZITADEL_EXTERNALDOMAIN=localhost
export ZITADEL_DEFAULTINSTANCE_CUSTOMDOMAIN=localhost
export MY_ARCHITECTURE="arm64 or amd64 depeding on your mac"
@ -37,5 +36,5 @@ curl -s https://api.github.com/repos/zitadel/zitadel/releases/tags/v2.0.0-v2-alp
Run ZITADEL
```bash
zitadel admin start-from-init --masterkey "MasterkeyNeedsToHave32Characters"
zitadel admin start-from-init --tlsMode disabled --masterkey "MasterkeyNeedsToHave32Characters"
```

View File

@ -2,6 +2,7 @@ package api
import (
"context"
"crypto/tls"
"net/http"
"strings"
@ -34,7 +35,7 @@ type health interface {
Instance(ctx context.Context, shouldTriggerBulk bool) (*query.Instance, error)
}
func New(port uint16, router *mux.Router, queries *query.Queries, verifier *internal_authz.TokenVerifier, authZ internal_authz.Config, externalSecure bool, http2HostName, http1HostName string) *API {
func New(port uint16, router *mux.Router, queries *query.Queries, verifier *internal_authz.TokenVerifier, authZ internal_authz.Config, externalSecure bool, tlsConfig *tls.Config, http2HostName, http1HostName string) *API {
api := &API{
port: port,
verifier: verifier,
@ -43,7 +44,7 @@ func New(port uint16, router *mux.Router, queries *query.Queries, verifier *inte
externalSecure: externalSecure,
http1HostName: http1HostName,
}
api.grpcServer = server.CreateServer(api.verifier, authZ, queries, http2HostName)
api.grpcServer = server.CreateServer(api.verifier, authZ, queries, http2HostName, tlsConfig)
api.routeGRPC()
api.RegisterHandler("/debug", api.healthHandler())

View File

@ -1,8 +1,11 @@
package server
import (
"crypto/tls"
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"github.com/zitadel/zitadel/internal/api/authz"
grpc_api "github.com/zitadel/zitadel/internal/api/grpc"
@ -20,9 +23,9 @@ type Server interface {
AuthMethods() authz.MethodMapping
}
func CreateServer(verifier *authz.TokenVerifier, authConfig authz.Config, queries *query.Queries, hostHeaderName string) *grpc.Server {
func CreateServer(verifier *authz.TokenVerifier, authConfig authz.Config, queries *query.Queries, hostHeaderName string, tlsConfig *tls.Config) *grpc.Server {
metricTypes := []metrics.MetricType{metrics.MetricTypeTotalCount, metrics.MetricTypeRequestCount, metrics.MetricTypeStatusCode}
return grpc.NewServer(
serverOptions := []grpc.ServerOption{
grpc.UnaryInterceptor(
grpc_middleware.ChainUnaryServer(
middleware.DefaultTracingServer(),
@ -37,5 +40,9 @@ func CreateServer(verifier *authz.TokenVerifier, authConfig authz.Config, querie
middleware.ServiceHandler(),
),
),
)
}
if tlsConfig != nil {
serverOptions = append(serverOptions, grpc.Creds(credentials.NewTLS(tlsConfig)))
}
return grpc.NewServer(serverOptions...)
}

View File

@ -0,0 +1,56 @@
package network
import (
"crypto/tls"
"errors"
"os"
)
var (
ErrMissingConfig = errors.New("")
)
type TLS struct {
//If enabled, ZITADEL will serve all traffic over TLS (HTTPS and gRPC)
//you must then also provide a private key and certificate to be used for the connection
//either directly or by a path to the corresponding file
Enabled bool
//Path to the private key of the TLS certificate, it will be loaded into the Key
//and overwrite any exising value
KeyPath string
//Path to the certificate for the TLS connection, it will be loaded into the Cert
//and overwrite any exising value
CertPath string
//Private key of the TLS certificate (KeyPath will this overwrite, if specified)
Key []byte
//Certificate for the TLS connection (CertPath will this overwrite, if specified)
Cert []byte
}
func (t *TLS) Config() (_ *tls.Config, err error) {
if !t.Enabled {
return nil, nil
}
if t.KeyPath != "" {
t.Key, err = os.ReadFile(t.KeyPath)
if err != nil {
return nil, err
}
}
if t.CertPath != "" {
t.Cert, err = os.ReadFile(t.CertPath)
if err != nil {
return nil, err
}
}
if t.Key == nil || t.Cert == nil {
return nil, ErrMissingConfig
}
tlsCert, err := tls.X509KeyPair(t.Cert, t.Key)
if err != nil {
return nil, err
}
return &tls.Config{
Certificates: []tls.Certificate{tlsCert},
}, nil
}