mirror of
https://github.com/zitadel/zitadel.git
synced 2025-02-28 16:37:24 +00:00
feat: TLS support (#3862)
* feat: TLS support * add comment * fix comment
This commit is contained in:
parent
70a108deeb
commit
ed5721d39e
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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())
|
||||
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)
|
||||
|
@ -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
46
cmd/admin/tls/tls.go
Normal 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
|
||||
}
|
@ -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
|
||||
|
@ -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"
|
||||
```
|
||||
|
@ -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"
|
||||
```
|
||||
|
@ -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())
|
||||
|
@ -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...)
|
||||
}
|
||||
|
56
internal/config/network/config.go
Normal file
56
internal/config/network/config.go
Normal 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
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user