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/logging"
"github.com/zitadel/zitadel/cmd/admin/key" "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/database"
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/migration" "github.com/zitadel/zitadel/internal/migration"
@ -28,6 +29,9 @@ func New() *cobra.Command {
Requirements: Requirements:
- cockroachdb`, - cockroachdb`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
err := tls.ModeFromFlag(cmd)
logging.OnError(err).Fatal("invalid tlsMode")
config := MustNewConfig(viper.GetViper()) config := MustNewConfig(viper.GetViper())
steps := MustNewSteps(viper.New()) steps := MustNewSteps(viper.New())
@ -46,6 +50,7 @@ Requirements:
func Flags(cmd *cobra.Command) { func Flags(cmd *cobra.Command) {
cmd.PersistentFlags().StringArrayVar(&stepFiles, "steps", nil, "paths to step files to overwrite default steps") cmd.PersistentFlags().StringArrayVar(&stepFiles, "steps", nil, "paths to step files to overwrite default steps")
key.AddMasterKeyFlag(cmd) key.AddMasterKeyFlag(cmd)
tls.AddTLSModeFlag(cmd)
} }
func Setup(config *Config, steps *Steps, masterKey string) { 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" auth_es "github.com/zitadel/zitadel/internal/auth/repository/eventsourcing"
"github.com/zitadel/zitadel/internal/command" "github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/config/hook" "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/config/systemdefaults"
"github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/database"
@ -31,6 +32,7 @@ type Config struct {
ExternalPort uint16 ExternalPort uint16
ExternalDomain string ExternalDomain string
ExternalSecure bool ExternalSecure bool
TLS network.TLS
HTTP2HostHeader string HTTP2HostHeader string
HTTP1HostHeader string HTTP1HostHeader string
WebAuthNName string WebAuthNName string

View File

@ -5,14 +5,17 @@ import (
"github.com/spf13/viper" "github.com/spf13/viper"
"github.com/zitadel/zitadel/cmd/admin/key" "github.com/zitadel/zitadel/cmd/admin/key"
"github.com/zitadel/zitadel/cmd/admin/tls"
) )
var tlsMode *string
func startFlags(cmd *cobra.Command) { func startFlags(cmd *cobra.Command) {
bindUint16Flag(cmd, "port", "port to run ZITADEL on") bindUint16Flag(cmd, "port", "port to run ZITADEL on")
bindStringFlag(cmd, "externalDomain", "domain ZITADEL will be exposed on") bindStringFlag(cmd, "externalDomain", "domain ZITADEL will be exposed on")
bindStringFlag(cmd, "externalPort", "port 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) key.AddMasterKeyFlag(cmd)
} }

View File

@ -2,6 +2,7 @@ package start
import ( import (
"context" "context"
"crypto/tls"
"database/sql" "database/sql"
_ "embed" _ "embed"
"fmt" "fmt"
@ -21,6 +22,7 @@ import (
"golang.org/x/net/http2/h2c" "golang.org/x/net/http2/h2c"
"github.com/zitadel/zitadel/cmd/admin/key" "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" admin_es "github.com/zitadel/zitadel/internal/admin/repository/eventsourcing"
"github.com/zitadel/zitadel/internal/api" "github.com/zitadel/zitadel/internal/api"
"github.com/zitadel/zitadel/internal/api/assets" "github.com/zitadel/zitadel/internal/api/assets"
@ -57,6 +59,10 @@ func New() *cobra.Command {
Requirements: Requirements:
- cockroachdb`, - cockroachdb`,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
err := cmd_tls.ModeFromFlag(cmd)
if err != nil {
return err
}
config := MustNewConfig(viper.GetViper()) config := MustNewConfig(viper.GetViper())
masterKey, err := key.MasterKey(cmd) masterKey, err := key.MasterKey(cmd)
if err != nil { 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) 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() 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 { if err != nil {
return err 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 { repo := struct {
authz_repo.Repository authz_repo.Repository
*query.Queries *query.Queries
@ -151,9 +161,12 @@ func startAPIs(ctx context.Context, router *mux.Router, commands *command.Comman
authZRepo, authZRepo,
queries, queries,
} }
verifier := internal_authz.Start(repo, http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure), systemAPIKeys) verifier := internal_authz.Start(repo, http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure), config.SystemAPIUsers)
tlsConfig, err := config.TLS.Config()
apis := api.New(config.Port, router, queries, verifier, config.InternalAuthZ, config.ExternalSecure, config.HTTP2HostHeader, config.HTTP1HostHeader) 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) authRepo, err := auth_es.Start(config.Auth, config.SystemDefaults, commands, queries, dbClient, keys.OIDC, keys.User)
if err != nil { if err != nil {
return fmt.Errorf("error starting auth repo: %w", err) 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 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{} 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)) lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
if err != nil { if err != nil {
return fmt.Errorf("tcp listener on %d failed: %w", port, err) 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() { go func() {
logging.Infof("server is listening on %s", lis.Addr().String()) logging.Infof("server is listening on %s", lis.Addr().String())
errCh <- http1Server.Serve(lis) 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) 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/initialise"
"github.com/zitadel/zitadel/cmd/admin/key" "github.com/zitadel/zitadel/cmd/admin/key"
"github.com/zitadel/zitadel/cmd/admin/setup" "github.com/zitadel/zitadel/cmd/admin/setup"
"github.com/zitadel/zitadel/cmd/admin/tls"
) )
func NewStartFromInit() *cobra.Command { func NewStartFromInit() *cobra.Command {
@ -22,6 +23,9 @@ Last ZITADEL starts.
Requirements: Requirements:
- cockroachdb`, - cockroachdb`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
err := tls.ModeFromFlag(cmd)
logging.OnError(err).Fatal("invalid tlsMode")
masterKey, err := key.MasterKey(cmd) masterKey, err := key.MasterKey(cmd)
logging.OnError(err).Panic("No master key provided") 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: Formatter:
Format: text Format: text
# Port ZITADEL will listen on
Port: 8080 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 ExternalPort: 8080
# Domain / hostname ZITADEL is exposed externally
# !!! Changing this after initial setup breaks your system !!!
ExternalDomain: localhost 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 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" HTTP2HostHeader: ":authority"
# Header name of HTTP1 calls from which the instance will be matched
HTTP1HostHeader: "host" HTTP1HostHeader: "host"
WebAuthNName: ZITADEL 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 Configure your environment and generate a master encryption key
```bash ```bash
export ZITADEL_EXTERNALSECURE=false
export ZITADEL_EXTERNALDOMAIN=localhost export ZITADEL_EXTERNALDOMAIN=localhost
export ZITADEL_DEFAULTINSTANCE_CUSTOMDOMAIN=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 Run the database and application containers
```bash ```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 Configure your environment and generate a master encryption key
```bash ```bash
export ZITADEL_EXTERNALSECURE=false
export ZITADEL_EXTERNALDOMAIN=localhost export ZITADEL_EXTERNALDOMAIN=localhost
export ZITADEL_DEFAULTINSTANCE_CUSTOMDOMAIN=localhost export ZITADEL_DEFAULTINSTANCE_CUSTOMDOMAIN=localhost
export MY_ARCHITECTURE="arm64 or amd64 depeding on your mac" 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 Run ZITADEL
```bash ```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 ( import (
"context" "context"
"crypto/tls"
"net/http" "net/http"
"strings" "strings"
@ -34,7 +35,7 @@ type health interface {
Instance(ctx context.Context, shouldTriggerBulk bool) (*query.Instance, error) 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{ api := &API{
port: port, port: port,
verifier: verifier, verifier: verifier,
@ -43,7 +44,7 @@ func New(port uint16, router *mux.Router, queries *query.Queries, verifier *inte
externalSecure: externalSecure, externalSecure: externalSecure,
http1HostName: http1HostName, http1HostName: http1HostName,
} }
api.grpcServer = server.CreateServer(api.verifier, authZ, queries, http2HostName) api.grpcServer = server.CreateServer(api.verifier, authZ, queries, http2HostName, tlsConfig)
api.routeGRPC() api.routeGRPC()
api.RegisterHandler("/debug", api.healthHandler()) api.RegisterHandler("/debug", api.healthHandler())

View File

@ -1,8 +1,11 @@
package server package server
import ( import (
"crypto/tls"
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/authz"
grpc_api "github.com/zitadel/zitadel/internal/api/grpc" grpc_api "github.com/zitadel/zitadel/internal/api/grpc"
@ -20,9 +23,9 @@ type Server interface {
AuthMethods() authz.MethodMapping 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} metricTypes := []metrics.MetricType{metrics.MetricTypeTotalCount, metrics.MetricTypeRequestCount, metrics.MetricTypeStatusCode}
return grpc.NewServer( serverOptions := []grpc.ServerOption{
grpc.UnaryInterceptor( grpc.UnaryInterceptor(
grpc_middleware.ChainUnaryServer( grpc_middleware.ChainUnaryServer(
middleware.DefaultTracingServer(), middleware.DefaultTracingServer(),
@ -37,5 +40,9 @@ func CreateServer(verifier *authz.TokenVerifier, authConfig authz.Config, querie
middleware.ServiceHandler(), 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
}