feat: run on a single port (#3163)

* start v2

* start

* run

* some cleanup

* remove v2 pkg again

* simplify

* webauthn

* remove unused config

* fix login path in Dockerfile

* fix asset_generator.go

* health handler

* fix grpc web

* refactor

* merge

* build new main.go

* run new main.go

* update logging pkg

* fix error msg

* update logging

* cleanup

* cleanup

* go mod tidy

* change localDevMode

* fix customEndpoints

* update logging

* comments

* change local flag to external configs

* fix location generated go code

* fix

Co-authored-by: fforootd <florian@caos.ch>
This commit is contained in:
Livio Amstutz
2022-02-14 17:22:30 +01:00
committed by GitHub
parent 2f3a482ade
commit 389eb4a27a
306 changed files with 1708 additions and 1567 deletions

View File

@@ -1,23 +1,306 @@
package start
import (
"context"
"database/sql"
_ "embed"
"errors"
"fmt"
"net"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/caos/logging"
"github.com/gorilla/mux"
"github.com/mitchellh/mapstructure"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
admin_es "github.com/caos/zitadel/internal/admin/repository/eventsourcing"
"github.com/caos/zitadel/internal/api"
"github.com/caos/zitadel/internal/api/assets"
internal_authz "github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/api/grpc/admin"
"github.com/caos/zitadel/internal/api/grpc/auth"
"github.com/caos/zitadel/internal/api/grpc/management"
http_util "github.com/caos/zitadel/internal/api/http"
"github.com/caos/zitadel/internal/api/http/middleware"
"github.com/caos/zitadel/internal/api/oidc"
"github.com/caos/zitadel/internal/api/ui/console"
"github.com/caos/zitadel/internal/api/ui/login"
auth_es "github.com/caos/zitadel/internal/auth/repository/eventsourcing"
"github.com/caos/zitadel/internal/authz"
authz_repo "github.com/caos/zitadel/internal/authz/repository"
"github.com/caos/zitadel/internal/command"
"github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/database"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/id"
"github.com/caos/zitadel/internal/notification"
"github.com/caos/zitadel/internal/query"
"github.com/caos/zitadel/internal/query/projection"
"github.com/caos/zitadel/internal/static"
static_config "github.com/caos/zitadel/internal/static/config"
"github.com/caos/zitadel/internal/webauthn"
"github.com/caos/zitadel/openapi"
)
func New() *cobra.Command {
return &cobra.Command{
start := &cobra.Command{
Use: "start",
Short: "starts ZITADEL instance",
Long: `starts ZITADEL.
Requirements:
- cockroachdb`,
RunE: func(cmd *cobra.Command, args []string) error {
logging.Info("hello world")
logging.WithFields("field", 1).Info("hello world")
return nil
config := new(startConfig)
err := viper.Unmarshal(config, viper.DecodeHook(mapstructure.ComposeDecodeHookFunc(
mapstructure.StringToTimeDurationHookFunc(),
mapstructure.StringToSliceHookFunc(":"),
)))
if err != nil {
return err
}
err = config.Log.SetLogger()
if err != nil {
return err
}
return startZitadel(config)
},
}
bindUint16Flag(start, "port", "port to run ZITADEL on")
bindStringFlag(start, "externalDomain", "domain ZITADEL will be exposed on")
bindStringFlag(start, "externalPort", "port ZITADEL will be exposed on")
bindBoolFlag(start, "externalSecure", "if ZITADEL will be served on HTTPS")
return start
}
func bindStringFlag(cmd *cobra.Command, name, description string) {
cmd.PersistentFlags().String(name, viper.GetString(name), description)
viper.BindPFlag(name, cmd.PersistentFlags().Lookup(name))
}
func bindUint16Flag(cmd *cobra.Command, name, description string) {
cmd.PersistentFlags().Uint16(name, uint16(viper.GetUint(name)), description)
viper.BindPFlag(name, cmd.PersistentFlags().Lookup(name))
}
func bindBoolFlag(cmd *cobra.Command, name, description string) {
cmd.PersistentFlags().Bool(name, viper.GetBool(name), description)
viper.BindPFlag(name, cmd.PersistentFlags().Lookup(name))
}
type startConfig struct {
Log *logging.Config
Port uint16
ExternalPort uint16
ExternalDomain string
ExternalSecure bool
Database database.Config
Projections projectionConfig
AuthZ authz.Config
Auth auth_es.Config
Admin admin_es.Config
UserAgentCookie *middleware.UserAgentCookieConfig
OIDC oidc.Config
Login login.Config
Console console.Config
Notification notification.Config
AssetStorage static_config.AssetStorageConfig
InternalAuthZ internal_authz.Config
SystemDefaults systemdefaults.SystemDefaults
}
type projectionConfig struct {
projection.Config
KeyConfig *crypto.KeyConfig
}
func startZitadel(config *startConfig) error {
ctx := context.Background()
keyChan := make(chan interface{})
dbClient, err := database.Connect(config.Database)
if err != nil {
return fmt.Errorf("cannot start client for projection: %w", err)
}
var storage static.Storage
//TODO: enable when storage is implemented again
//if *assetsEnabled {
//storage, err = config.AssetStorage.Config.NewStorage()
//logging.Log("MAIN-Bfhe2").OnError(err).Fatal("Unable to start asset storage")
//}
eventstoreClient, err := eventstore.Start(dbClient)
if err != nil {
return fmt.Errorf("cannot start eventstore for queries: %w", err)
}
queries, err := query.StartQueries(ctx, eventstoreClient, dbClient, config.Projections.Config, config.SystemDefaults, config.Projections.KeyConfig, keyChan, config.InternalAuthZ.RolePermissionMappings)
if err != nil {
return fmt.Errorf("cannot start queries: %w", err)
}
authZRepo, err := authz.Start(config.AuthZ, config.SystemDefaults, queries, dbClient, config.OIDC.KeyConfig)
if err != nil {
return fmt.Errorf("error starting authz repo: %w", err)
}
webAuthNConfig := webauthn.Config{
ID: config.ExternalDomain,
Origin: http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure),
DisplayName: "ZITADEL",
}
commands, err := command.StartCommands(eventstoreClient, config.SystemDefaults, config.InternalAuthZ, storage, authZRepo, config.OIDC.KeyConfig, webAuthNConfig)
if err != nil {
return fmt.Errorf("cannot start commands: %w", err)
}
notification.Start(config.Notification, config.SystemDefaults, commands, queries, dbClient, assets.HandlerPrefix)
router := mux.NewRouter()
err = startAPIs(ctx, router, commands, queries, eventstoreClient, dbClient, keyChan, config, storage, authZRepo)
if err != nil {
return err
}
return listen(ctx, router, config.Port)
}
func startAPIs(ctx context.Context, router *mux.Router, commands *command.Commands, queries *query.Queries, eventstore *eventstore.Eventstore, dbClient *sql.DB, keyChan chan interface{}, config *startConfig, store static.Storage, authZRepo authz_repo.Repository) error {
repo := struct {
authz_repo.Repository
*query.Queries
}{
authZRepo,
queries,
}
verifier := internal_authz.Start(repo)
apis := api.New(config.Port, router, &repo, config.InternalAuthZ, config.SystemDefaults, config.ExternalSecure)
authRepo, err := auth_es.Start(config.Auth, config.SystemDefaults, commands, queries, dbClient, config.OIDC.KeyConfig, assets.HandlerPrefix)
if err != nil {
return fmt.Errorf("error starting auth repo: %w", err)
}
adminRepo, err := admin_es.Start(config.Admin, store, dbClient, login.HandlerPrefix)
if err != nil {
return fmt.Errorf("error starting admin repo: %w", err)
}
if err := apis.RegisterServer(ctx, admin.CreateServer(commands, queries, adminRepo, config.SystemDefaults.Domain, assets.HandlerPrefix)); err != nil {
return err
}
if err := apis.RegisterServer(ctx, management.CreateServer(commands, queries, config.SystemDefaults, assets.HandlerPrefix)); err != nil {
return err
}
if err := apis.RegisterServer(ctx, auth.CreateServer(commands, queries, authRepo, config.SystemDefaults, assets.HandlerPrefix)); err != nil {
return err
}
apis.RegisterHandler(assets.HandlerPrefix, assets.NewHandler(commands, verifier, config.InternalAuthZ, id.SonyFlakeGenerator, store, queries))
userAgentInterceptor, err := middleware.NewUserAgentHandler(config.UserAgentCookie, config.ExternalDomain, id.SonyFlakeGenerator, config.ExternalSecure)
if err != nil {
return err
}
issuer := oidc.Issuer(config.ExternalDomain, config.ExternalPort, config.ExternalSecure)
oidcProvider, err := oidc.NewProvider(ctx, config.OIDC, issuer, login.DefaultLoggedOutPath, commands, queries, authRepo, config.SystemDefaults.KeyConfig, eventstore, dbClient, keyChan, userAgentInterceptor)
if err != nil {
return fmt.Errorf("unable to start oidc provider: %w", err)
}
apis.RegisterHandler(oidc.HandlerPrefix, oidcProvider.HttpHandler())
openAPIHandler, err := openapi.Start()
if err != nil {
return fmt.Errorf("unable to start openapi handler: %w", err)
}
apis.RegisterHandler(openapi.HandlerPrefix, openAPIHandler)
consoleID, err := consoleClientID(ctx, queries)
if err != nil {
return fmt.Errorf("unable to get client_id for console: %w", err)
}
c, err := console.Start(config.Console, config.ExternalDomain, http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure), issuer, consoleID)
if err != nil {
return fmt.Errorf("unable to start console: %w", err)
}
apis.RegisterHandler(console.HandlerPrefix, c)
l, err := login.CreateLogin(config.Login, commands, queries, authRepo, store, config.SystemDefaults, console.HandlerPrefix, config.ExternalDomain, oidc.AuthCallback, config.ExternalSecure, userAgentInterceptor)
if err != nil {
return fmt.Errorf("unable to start login: %w", err)
}
apis.RegisterHandler(login.HandlerPrefix, l.Handler())
return nil
}
func listen(ctx context.Context, router *mux.Router, port uint16) error {
http2Server := &http2.Server{}
http1Server := &http.Server{Handler: h2c.NewHandler(router, http2Server)}
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
if err != nil {
return fmt.Errorf("tcp listener on %d failed: %w", port, err)
}
errCh := make(chan error)
go func() {
logging.Infof("server is listening on %s", lis.Addr().String())
errCh <- http1Server.Serve(lis)
}()
shutdown := make(chan os.Signal, 1)
signal.Notify(shutdown, os.Interrupt, syscall.SIGTERM)
select {
case err := <-errCh:
return fmt.Errorf("error starting server: %w", err)
case <-shutdown:
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
return shutdownServer(ctx, http1Server)
case <-ctx.Done():
return shutdownServer(ctx, http1Server)
}
}
func shutdownServer(ctx context.Context, server *http.Server) error {
err := server.Shutdown(ctx)
if err != nil {
return fmt.Errorf("could not shutdown gracefully: %w", err)
}
logging.New().Info("server shutdown gracefully")
return nil
}
//TODO:!!??!!
func consoleClientID(ctx context.Context, queries *query.Queries) (string, error) {
iam, err := queries.IAMByID(ctx, domain.IAMID)
if err != nil {
return "", err
}
projectID, err := query.NewAppProjectIDSearchQuery(iam.IAMProjectID)
if err != nil {
return "", err
}
name, err := query.NewAppNameSearchQuery(query.TextContainsIgnoreCase, "console") //TODO:!!??!!
if err != nil {
return "", err
}
apps, err := queries.SearchApps(ctx, &query.AppSearchQueries{
Queries: []query.SearchQuery{projectID, name},
})
if err != nil {
return "", err
}
if len(apps.Apps) != 1 || apps.Apps[0].OIDCConfig == nil {
return "", errors.New("invalid app")
}
return apps.Apps[0].OIDCConfig.ClientID, nil
}

View File

@@ -3,18 +3,278 @@ Log:
Formatter:
Format: text
database:
host: localhost
port: 26257
user: zitadel
database: zitadel
password:
maxOpenConns: 3
ssl:
mode: disable
rootCert:
cert:
key:
options:
# MaxConnLifetime: 30m
# MaxConnIdleTime: 30m
Port: 8080
ExternalPort: 8080
ExternalDomain: localhost
ExternalSecure: true
Database:
Host: localhost
Port: 26257
User: zitadel
Database: zitadel
Password: ""
MaxOpenConns: 20
MaxConnLifetime: 30m
MaxConnIdleTime: 30m
Options: ""
SSL:
Mode: diabled
RootCert: ""
Cert: ""
Key: ""
Projections:
Config:
RequeueEvery: 10s
RetryFailedAfter: 1s
MaxFailureCount: 5
BulkLimit: 200
MaxIterators: 1
Customizations:
projects:
BulkLimit: 2000
KeyConfig:
# We don't need an EncryptionKey but DecryptionKeys (and load them via env)
DecryptionKeyIDs:
Path: ""
AuthZ:
Repository:
Spooler:
ConcurrentWorkers: 1
BulkLimit: 10000
FailureCountUntilSkip: 5
Auth:
SearchLimit: 1000
Spooler:
ConcurrentWorkers: 1
BulkLimit: 10000
FailureCountUntilSkip: 5
Admin:
SearchLimit: 1000
Spooler:
ConcurrentWorkers: 1
BulkLimit: 10000
FailureCountUntilSkip: 5
UserAgentCookie:
Name: zitadel.useragent
Key:
EncryptionKeyID:
MaxAge: 8760h #365*24h (1 year)
OIDC:
CodeMethodS256: true
AuthMethodPost: true
AuthMethodPrivateKeyJWT: true
GrantTypeRefreshToken: true
RequestObjectSupported: true
SigningKeyAlgorithm: RS256
DefaultAccessTokenLifetime: 12h
DefaultIdTokenLifetime: 12h
DefaultRefreshTokenIdleExpiration: 720h #30d
DefaultRefreshTokenExpiration: 2160h #90d
Cache:
MaxAge: 12h
SharedMaxAge: 168h #7d
KeyConfig:
EncryptionKeyID: ""
DecryptionKeyIDs:
Path: ""
CustomEndpoints:
Login:
LanguageCookieName: zitadel.login.lang
CSRF:
CookieName: zitadel.login.csrf
Development: true
Key:
EncryptionKeyID:
Cache:
MaxAge: 12h
SharedMaxAge: 168h #7d
Console:
ConsoleOverwriteDir: ""
ShortCache:
MaxAge: 5m
SharedMaxAge: 15m
LongCache:
MaxAge: 12h
SharedMaxAge: 168h
Notification:
Repository:
Spooler:
ConcurrentWorkers: 1
BulkLimit: 10000
FailureCountUntilSkip: 5
Handlers:
#TODO: configure as soon as possible
#AssetStorage:
# Type: $ZITADEL_ASSET_STORAGE_TYPE
# Config:
# Endpoint: $ZITADEL_ASSET_STORAGE_ENDPOINT
# AccessKeyID: $ZITADEL_ASSET_STORAGE_ACCESS_KEY_ID
# SecretAccessKey: $ZITADEL_ASSET_STORAGE_SECRET_ACCESS_KEY
# SSL: $ZITADEL_ASSET_STORAGE_SSL
# Location: $ZITADEL_ASSET_STORAGE_LOCATION
# BucketPrefix: $ZITADEL_ASSET_STORAGE_BUCKET_PREFIX
# MultiDelete: $ZITADEL_ASSET_STORAGE_MULTI_DELETE
#TODO: remove as soon as possible
SystemDefaults:
# DefaultLanguage: 'en'
Domain: $ZITADEL_DEFAULT_DOMAIN
ZitadelDocs:
Issuer: $ZITADEL_ISSUER
DiscoveryEndpoint: '$ZITADEL_ISSUER/.well-known/openid-configuration'
UserVerificationKey:
EncryptionKeyID: $ZITADEL_USER_VERIFICATION_KEY
IDPConfigVerificationKey:
EncryptionKeyID: $ZITADEL_IDP_CONFIG_VERIFICATION_KEY
SecretGenerators:
PasswordSaltCost: 14
ClientSecretGenerator:
Length: 64
IncludeLowerLetters: true
IncludeUpperLetters: true
IncludeDigits: true
IncludeSymbols: false
InitializeUserCode:
Length: 6
Expiry: '72h'
IncludeLowerLetters: false
IncludeUpperLetters: true
IncludeDigits: true
IncludeSymbols: false
EmailVerificationCode:
Length: 6
Expiry: '1h'
IncludeLowerLetters: false
IncludeUpperLetters: true
IncludeDigits: true
IncludeSymbols: false
PhoneVerificationCode:
Length: 6
Expiry: '1h'
IncludeLowerLetters: false
IncludeUpperLetters: true
IncludeDigits: true
IncludeSymbols: false
PasswordVerificationCode:
Length: 6
Expiry: '1h'
IncludeLowerLetters: false
IncludeUpperLetters: true
IncludeDigits: true
IncludeSymbols: false
PasswordlessInitCode:
Length: 12
Expiry: '1h'
IncludeLowerLetters: true
IncludeUpperLetters: true
IncludeDigits: true
IncludeSymbols: false
MachineKeySize: 2048
ApplicationKeySize: 2048
Multifactors:
OTP:
Issuer: 'ZITADEL'
VerificationKey:
EncryptionKeyID: $ZITADEL_OTP_VERIFICATION_KEY
VerificationLifetimes:
PasswordCheck: 240h #10d
ExternalLoginCheck: 240h #10d
MFAInitSkip: 720h #30d
SecondFactorCheck: 18h
MultiFactorCheck: 12h
IamID: 'IAM'
DomainVerification:
VerificationKey:
EncryptionKeyID: $ZITADEL_DOMAIN_VERIFICATION_KEY
VerificationGenerator:
Length: 32
IncludeLowerLetters: true
IncludeUpperLetters: true
IncludeDigits: true
IncludeSymbols: false
Notifications:
# DebugMode: $DEBUG_MODE
Endpoints:
InitCode: '$ZITADEL_ACCOUNTS/user/init?userID={{.UserID}}&code={{.Code}}&passwordset={{.PasswordSet}}'
PasswordReset: '$ZITADEL_ACCOUNTS/password/init?userID={{.UserID}}&code={{.Code}}'
VerifyEmail: '$ZITADEL_ACCOUNTS/mail/verification?userID={{.UserID}}&code={{.Code}}'
DomainClaimed: '$ZITADEL_ACCOUNTS/login'
PasswordlessRegistration: '$ZITADEL_ACCOUNTS/login/passwordless/init'
Providers:
Email:
SMTP:
Host: $SMTP_HOST
User: $SMTP_USER
Password: $SMTP_PASSWORD
From: $EMAIL_SENDER_ADDRESS
FromName: $EMAIL_SENDER_NAME
# Tls: $SMTP_TLS
Twilio:
SID: $TWILIO_SERVICE_SID
Token: $TWILIO_TOKEN
From: $TWILIO_SENDER_NAME
FileSystem:
# Enabled: $FS_NOTIFICATIONS_ENABLED
Path: $FS_NOTIFICATIONS_PATH
# Compact: $FS_NOTIFICATIONS_COMPACT
Log:
# Enabled: $LOG_NOTIFICATIONS_ENABLED
# Compact: $LOG_NOTIFICATIONS_COMPACT
Chat:
# Enabled: $CHAT_ENABLED
Url: $CHAT_URL
# Compact: $CHAT_COMPACT
SplitCount: 4000
TemplateData:
InitCode:
Title: 'InitCode.Title'
PreHeader: 'InitCode.PreHeader'
Subject: 'InitCode.Subject'
Greeting: 'InitCode.Greeting'
Text: 'InitCode.Text'
ButtonText: 'InitCode.ButtonText'
PasswordReset:
Title: 'PasswordReset.Title'
PreHeader: 'PasswordReset.PreHeader'
Subject: 'PasswordReset.Subject'
Greeting: 'PasswordReset.Greeting'
Text: 'PasswordReset.Text'
ButtonText: 'PasswordReset.ButtonText'
VerifyEmail:
Title: 'VerifyEmail.Title'
PreHeader: 'VerifyEmail.PreHeader'
Subject: 'VerifyEmail.Subject'
Greeting: 'VerifyEmail.Greeting'
Text: 'VerifyEmail.Text'
ButtonText: 'VerifyEmail.ButtonText'
VerifyPhone:
Title: 'VerifyPhone.Title'
PreHeader: 'VerifyPhone.PreHeader'
Subject: 'VerifyPhone.Subject'
Greeting: 'VerifyPhone.Greeting'
Text: 'VerifyPhone.Text'
ButtonText: 'VerifyPhone.ButtonText'
DomainClaimed:
Title: 'DomainClaimed.Title'
PreHeader: 'DomainClaimed.PreHeader'
Subject: 'DomainClaimed.Subject'
Greeting: 'DomainClaimed.Greeting'
Text: 'DomainClaimed.Text'
ButtonText: 'DomainClaimed.ButtonText'
KeyConfig:
Size: 2048
PrivateKeyLifetime: 6h
PublicKeyLifetime: 30h
SigningKeyRotationCheck: 10s
SigningKeyGracefulPeriod: 10m

View File

@@ -4,11 +4,13 @@ import (
"bytes"
_ "embed"
"io"
"strings"
"github.com/caos/logging"
"github.com/caos/zitadel/cmd/admin"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/caos/zitadel/cmd/admin"
)
var (
@@ -29,9 +31,11 @@ func New(out io.Writer, in io.Reader, args []string) *cobra.Command {
}
viper.AutomaticEnv()
viper.SetEnvPrefix("ZITADEL")
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.SetConfigType("yaml")
err := viper.ReadConfig(bytes.NewBuffer(defaultConfig))
logging.New().OnError(err).Fatal("unable to read default config")
logging.OnError(err).Fatal("unable to read default config")
cobra.OnInitialize(initConfig)
cmd.PersistentFlags().StringArrayVar(&configFiles, "config", nil, "path to config file to overwrite system defaults")