Merge commit from fork

* Add DenyLists parsing

* Remove unneeded returned error

* Plug global denylist into Command

* app creation: apply denylist to backchannel logout URI

* Inject denylist to backchannel logout worker

* webhook config: validate against blocked URLs

* Add notificationsWebhook denylist target

* command: Add SMTP endpoint validation against blocklist

* command: Add SMS endpoint validation against blocklist

* Validate webhook endpoint against denylist on channel notification

* Remove unused tests

* handle deprecated denylists

* remove unintended denylist entry in deprecated list

* use single http client

* fix tests

* update comments

* fixes

* cleanup

* address comments

* fix merge

---------

Co-authored-by: Livio Spring <[email protected]>
This commit is contained in:
Marco A.
2026-06-15 15:36:14 +02:00
committed by GitHub
co-authored by Livio Spring
parent d184e976fc
commit 8e82ec1cb9
97 changed files with 1834 additions and 285 deletions
+35 -10
View File
@@ -598,12 +598,9 @@ Executions:
MaxTtl: 5m # ZITADEL_EXECUTIONS_MAXTTL
# List of domains and IPs that are not valid execution target's endpoints
# Wildcard sub domains are currently unsupported
# Deprecated: use HTTPClient.DenyList instead. If both are set, this list will be merged into the HTTPClient.DenyList.
DenyList: # ZITADEL_EXECUTIONS_DENYLIST (comma separated list)
- localhost
- "127.0.0.0/8"
- "::1"
- "0.0.0.0"
- "::"
Auth:
# See Projections.BulkLimit
@@ -1033,12 +1030,8 @@ SystemDefaults:
Actions:
HTTP:
# Wildcard sub domains are currently unsupported
# Deprecated: use HTTPClient.DenyList instead. If both are set, this list will be merged into the HTTPClient.DenyList.
DenyList: # ZITADEL_ACTIONS_HTTP_DENYLIST (comma separated list)
- localhost
- "127.0.0.0/8"
- "::1"
- "0.0.0.0"
- "::"
LogStore:
Access:
@@ -1072,6 +1065,38 @@ Eventstore:
# Maximum amount of push retries in case of primary key violation on the sequence
MaxRetries: 5 #ZITADEL_EVENTSTORE_MAXRETRIES
HTTPClient:
# Sets the limit of the response body size in bytes, which will be processed by the HTTP client.
# If the response body exceeds this limit, the client will return an error.
# The default value is 20MB.
MaxBodySize: 20971520 # ZITADEL_HTTPCLIENT_MAXBODYSIZE
# Sets the timeout for the HTTP client.
Timeout: 30s # ZITADEL_HTTPCLIENT_TIMEOUT
# Sets the number of allowed redirects for the HTTP client.
MaxRedirects: 5 # ZITADEL_HTTPCLIENT_MAXREDIRECTS
# Specifies if the HTTP client should follow redirects from HTTPS to HTTP.
AllowHTTPSDowngrade: false # ZITADEL_HTTPCLIENT_ALLOWHTTPSDOWNGRADE
# Specifies the list of IP addresses or CIDR ranges that are not allowed to be called by the HTTP client.
# Note that hosts / domains are matched best-case and wildcards are not supported.
# CRITICAL SECURITY NOTE: Domain rules (like "localhost") protect application-layer string matches.
# Underneath, the network dialer relies exclusively on the CIDR ranges below to block resolved IPs and prevent DNS Rebinding.
# Do not remove the CIDR subnets even if a domain mapping exists.
DenyList: # ZITADEL_HTTPCLIENT_DENYLIST (comma separated list)
- localhost
- "0.0.0.0/8" # Unspecified IPv4 address range / Local network routing trick protection
- "10.0.0.0/8" # Class A private networks
- "100.64.0.0/10" # Carrier-Grade NAT space
- "127.0.0.0/8" # Loopback addresses (IPv4)
- "169.254.0.0/16" # Link-local subnets (Covers Cloud Metadata Endpoints like AWS/GCP 169.254.169.254)
- "172.16.0.0/12" # Class B private networks
- "192.168.0.0/16" # Class C private networks
- "198.18.0.0/15" # Benchmark/inter-network communication testing
- "::/128" # Unspecified IPv6 address
- "::1/128" # IPv6 loopback address
- "::ffff:0:0/96" # IPv4-mapped IPv6 addresses (Prevents IPv6 encapsulation bypasses)
- "fc00::/7" # Unique local addresses / IPv6 equivalent to private IPv4 subnets
- "fe80::/10" # Link-local addresses / IPv6 equivalent to cloud metadata subnets
# The DefaultInstance section defines the default values for each new virtual instance that is created.
# Check out https://zitadel.com/docs/concepts/structure/instance#multiple-virtual-instances for more information about virtual instances.
# For the initial setup, the default values are used to create the first instance.
+32
View File
@@ -211,3 +211,35 @@ FirstInstance:
Log:
Level: info
HTTPClient:
# Sets the limit of the response body size in bytes, which will be processed by the HTTP client.
# If the response body exceeds this limit, the client will return an error.
# The default value is 20MB.
MaxBodySize: 20971520 # ZITADEL_HTTPCLIENT_MAXBODYSIZE
# Sets the timeout for the HTTP client.
Timeout: 30s # ZITADEL_HTTPCLIENT_TIMEOUT
# Sets the number of allowed redirects for the HTTP client.
MaxRedirects: 5 # ZITADEL_HTTPCLIENT_MAXREDIRECTS
# Specifies if the HTTP client should follow redirects from HTTPS to HTTP.
AllowHTTPSDowngrade: false # ZITADEL_HTTPCLIENT_ALLOWHTTPSDOWNGRADE
# Specifies the list of IP addresses or CIDR ranges that are not allowed to be called by the HTTP client.
# Note that hosts / domains are matched best-case and wildcards are not supported.
# CRITICAL SECURITY NOTE: Domain rules (like "localhost") protect application-layer string matches.
# Underneath, the network dialer relies exclusively on the CIDR ranges below to block resolved IPs and prevent DNS Rebinding.
# Do not remove the CIDR subnets even if a domain mapping exists.
DenyList: # ZITADEL_HTTPCLIENT_DENYLIST (comma separated list)
- localhost
- "0.0.0.0/8" # Unspecified IPv4 address range / Local network routing trick protection
- "10.0.0.0/8" # Class A private networks
- "100.64.0.0/10" # Carrier-Grade NAT space
- "127.0.0.0/8" # Loopback addresses (IPv4)
- "169.254.0.0/16" # Link-local subnets (Covers Cloud Metadata Endpoints like AWS/GCP 169.254.169.254)
- "172.16.0.0/12" # Class B private networks
- "192.168.0.0/16" # Class C private networks
- "198.18.0.0/15" # Benchmark/inter-network communication testing
- "::/128" # Unspecified IPv6 address
- "::1/128" # IPv6 loopback address
- "::ffff:0:0/96" # IPv4-mapped IPv6 addresses (Prevents IPv6 encapsulation bypasses)
- "fc00::/7" # Unique local addresses / IPv6 equivalent to private IPv4 subnets
- "fe80::/10" # Link-local addresses / IPv6 equivalent to cloud metadata subnets
+8 -4
View File
@@ -5,7 +5,6 @@ import (
"database/sql"
"errors"
"fmt"
"net/http"
"sync"
"time"
@@ -22,6 +21,7 @@ import (
admin_handler "github.com/zitadel/zitadel/internal/admin/repository/eventsourcing/handler"
admin_view "github.com/zitadel/zitadel/internal/admin/repository/eventsourcing/view"
internal_authz "github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/api/oidc"
"github.com/zitadel/zitadel/internal/api/ui/login"
auth_es "github.com/zitadel/zitadel/internal/auth/repository/eventsourcing"
@@ -34,7 +34,6 @@ import (
"github.com/zitadel/zitadel/internal/config/systemdefaults"
crypto_db "github.com/zitadel/zitadel/internal/crypto/database"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/denylist"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
old_es "github.com/zitadel/zitadel/internal/eventstore/repository/sql"
@@ -109,6 +108,7 @@ type ProjectionsConfig struct {
WebAuthNName string
DefaultInstance command.InstanceSetup
AssetStorage static_config.AssetStorageConfig
HTTPClient http.ClientConfig
}
func migrateProjectionsFlags(cmd *cobra.Command) {
@@ -187,6 +187,9 @@ func projections(
DisplayName: config.WebAuthNName,
ExternalSecure: config.ExternalSecure,
}
httpClient := config.HTTPClient.NewClient()
commands, err := command.StartCommands(ctx,
es,
cacheConnectors,
@@ -206,7 +209,7 @@ func projections(
keys.SAML,
keys.Target,
keys.OIDC,
&http.Client{},
httpClient,
func(ctx context.Context, permission, orgID, resourceID string) (err error) {
return internal_authz.CheckPermission(ctx, authZRepo, config.SystemAuthZ.RolePermissionMappings, config.InternalAuthZ.RolePermissionMappings, permission, orgID, resourceID)
},
@@ -216,7 +219,7 @@ func projections(
config.OIDC.DefaultRefreshTokenIdleExpiration,
config.DefaultInstance.SecretGenerators,
config.Login.DefaultPaths,
[]denylist.AddressChecker{},
config.HTTPClient.DenyList,
)
logging.OnError(ctx, err).Fatal("unable to start commands")
@@ -246,6 +249,7 @@ func projections(
keys.SMTP,
keys.SMS,
nil,
httpClient,
)
config.Auth.Spooler.Client = client
+5 -2
View File
@@ -3,6 +3,7 @@ package setup
import (
"context"
"fmt"
"net/http"
"os"
"strings"
@@ -47,6 +48,8 @@ type FirstInstance struct {
externalPort uint16
domain string
defaultPaths *login.DefaultPaths
httpClient *http.Client
denylist []denylist.AddressChecker
}
func (mig *FirstInstance) Execute(ctx context.Context, _ eventstore.Event) error {
@@ -89,7 +92,7 @@ func (mig *FirstInstance) Execute(ctx context.Context, _ eventstore.Event) error
nil,
nil,
oidcEncryption,
nil,
mig.httpClient,
nil,
nil,
0,
@@ -97,7 +100,7 @@ func (mig *FirstInstance) Execute(ctx context.Context, _ eventstore.Event) error
0,
nil,
mig.defaultPaths,
[]denylist.AddressChecker{},
mig.denylist,
)
if err != nil {
return err
+4
View File
@@ -19,6 +19,7 @@ import (
"github.com/zitadel/zitadel/cmd/hooks"
"github.com/zitadel/zitadel/internal/actions"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/api/oidc"
"github.com/zitadel/zitadel/internal/api/ui/login"
"github.com/zitadel/zitadel/internal/cache/connector"
@@ -64,6 +65,7 @@ type Config struct {
WebAuthNName string
Telemetry *handlers.TelemetryPusherConfig
SystemAPIUsers map[string]*authz.SystemAPIUser
HTTPClient http.ClientConfig
}
type InitProjections struct {
@@ -114,6 +116,8 @@ func NewConfig(cmd *cobra.Command, v *viper.Viper) (*Config, instrumentation.Shu
id.Configure(config.Machine)
config.HTTPClient.MergeDeprecatedDenylists(nil, config.Executions.DenyList)
// Copy the global role permissions mappings to the instance until we allow instance-level configuration over the API.
config.DefaultInstance.RolePermissionMappings = config.InternalAuthZ.RolePermissionMappings
+1 -2
View File
@@ -7,7 +7,6 @@ import (
"github.com/zitadel/zitadel/internal/cache/connector"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/config/systemdefaults"
"github.com/zitadel/zitadel/internal/denylist"
"github.com/zitadel/zitadel/internal/eventstore"
)
@@ -61,7 +60,7 @@ func (mig *externalConfigChange) Execute(ctx context.Context, _ eventstore.Event
0,
nil,
&login.DefaultPaths{},
[]denylist.AddressChecker{},
nil,
)
if err != nil {
+9 -3
View File
@@ -170,6 +170,8 @@ func Setup(ctx context.Context, config *Config, steps *Steps, masterKey string)
MaxRetries: config.Eventstore.MaxRetries,
}))
httpClient := config.HTTPClient.NewClient()
steps.s1ProjectionTable = &ProjectionTable{dbClient: dbClient.DB}
steps.s2AssetsTable = &AssetTable{dbClient: dbClient.DB}
@@ -187,6 +189,8 @@ func Setup(ctx context.Context, config *Config, steps *Steps, masterKey string)
steps.FirstInstance.externalSecure = config.ExternalSecure
steps.FirstInstance.externalPort = config.ExternalPort
steps.FirstInstance.defaultPaths = config.Login.DefaultPaths
steps.FirstInstance.httpClient = httpClient
steps.FirstInstance.denylist = config.HTTPClient.DenyList
steps.s5LastFailed = &LastFailed{dbClient: dbClient.DB}
steps.s6OwnerRemoveColumns = &OwnerRemoveColumns{dbClient: dbClient.DB}
@@ -315,7 +319,7 @@ func Setup(ctx context.Context, config *Config, steps *Steps, masterKey string)
}
}
commands, _, _, _ := startCommandsQueries(ctx, eventstoreClient, eventstoreV4, dbClient, masterKey, config)
commands, _, _, _ := startCommandsQueries(ctx, eventstoreClient, eventstoreV4, dbClient, masterKey, config, httpClient)
steps.s59SetupWebkeys = &SetupWebkeys{eventstore: eventstoreClient, commands: commands}
repeatableSteps := []migration.RepeatableMigration{
@@ -454,6 +458,7 @@ func startCommandsQueries(
dbClient *database.DB,
masterKey string,
config *Config,
httpClient *http.Client,
) (
*command.Commands,
*query.Queries,
@@ -572,7 +577,7 @@ func startCommandsQueries(
keys.SAML,
keys.Target,
keys.OIDC,
&http.Client{},
httpClient,
permissionCheck,
sessionTokenVerifier,
config.OIDC.DefaultAccessTokenLifetime,
@@ -580,7 +585,7 @@ func startCommandsQueries(
config.OIDC.DefaultRefreshTokenIdleExpiration,
config.DefaultInstance.SecretGenerators,
config.Login.DefaultPaths,
config.Executions.DenyList,
config.HTTPClient.DenyList,
)
logging.OnError(ctx, err).Fatal("unable to start commands")
@@ -610,6 +615,7 @@ func startCommandsQueries(
keys.SMTP,
keys.SMS,
q,
httpClient,
)
return commands, queries, adminView, authView
+6 -1
View File
@@ -17,6 +17,7 @@ import (
"github.com/zitadel/zitadel/internal/actions"
admin_es "github.com/zitadel/zitadel/internal/admin/repository/eventsourcing"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/api/http/middleware"
"github.com/zitadel/zitadel/internal/api/oidc"
"github.com/zitadel/zitadel/internal/api/saml"
@@ -87,6 +88,7 @@ type Config struct {
Quotas *QuotasConfig
Telemetry *handlers.TelemetryPusherConfig
ServicePing *serviceping.Config
HTTPClient *http.ClientConfig
}
type QuotasConfig struct {
@@ -121,9 +123,12 @@ func NewConfig(cmd *cobra.Command, v *viper.Viper) (*Config, instrumentation.Shu
}
id.Configure(config.Machine)
var actionsDenylist []denylist.AddressChecker
if config.Actions != nil {
actions.SetHTTPConfig(&config.Actions.HTTP)
actionsDenylist = config.Actions.HTTP.DenyList
}
config.HTTPClient.MergeDeprecatedDenylists(actionsDenylist, config.Executions.DenyList)
err = config.SystemDefaults.Validate()
if err != nil {
+27 -4
View File
@@ -272,6 +272,8 @@ func startZitadel(ctx context.Context, config *Config, masterKey string, server
new_domain.SetWebAuthNConfig(webAuthNConfig)
httpClient := config.HTTPClient.NewClient()
commands, err := command.StartCommands(ctx,
eventstoreClient,
cacheConnectors,
@@ -291,7 +293,7 @@ func startZitadel(ctx context.Context, config *Config, masterKey string, server
keys.SAML,
keys.Target,
keys.OIDC,
&http.Client{},
httpClient,
permissionCheck,
sessionTokenVerifier,
config.OIDC.DefaultAccessTokenLifetime,
@@ -299,7 +301,7 @@ func startZitadel(ctx context.Context, config *Config, masterKey string, server
config.OIDC.DefaultRefreshTokenIdleExpiration,
config.DefaultInstance.SecretGenerators,
config.Login.DefaultPaths,
config.Executions.DenyList,
config.HTTPClient.DenyList,
)
if err != nil {
return fmt.Errorf("cannot start commands: %w", err)
@@ -345,12 +347,14 @@ func startZitadel(ctx context.Context, config *Config, masterKey string, server
keys.SMTP,
keys.SMS,
q,
httpClient,
)
notification.Start(ctx)
execution.Register(
ctx,
config.Executions,
httpClient,
q,
keys.Target,
queries.GetActiveSigningWebKey,
@@ -390,6 +394,7 @@ func startZitadel(ctx context.Context, config *Config, masterKey string, server
keys,
permissionCheck,
cacheConnectors,
httpClient,
)
if err != nil {
return err
@@ -435,6 +440,7 @@ func startAPIs(
keys *encryption.EncryptionKeys,
permissionCheck domain.PermissionCheck,
cacheConnectors connector.Connectors,
httpClient *http.Client,
) (*api.API, error) {
repo := struct {
authz_repo.Repository
@@ -492,7 +498,7 @@ func startAPIs(
keys.Target,
translator,
config.Instrumentation.Trace.TrustRemoteSpans,
config.Executions.DenyList,
httpClient,
)
if err != nil {
return nil, fmt.Errorf("error creating api %w", err)
@@ -675,13 +681,29 @@ func startAPIs(
config.Log.Slog(),
config.SystemDefaults.SecretHasher,
federatedLogoutsCache,
httpClient,
)
if err != nil {
return nil, fmt.Errorf("unable to start oidc provider: %w", err)
}
apis.RegisterHandlerPrefixes(oidcServer, oidcPrefixes...)
samlProvider, err := saml.NewProvider(config.SAML, config.ExternalSecure, commands, queries, authRepo, keys.OIDC, keys.SAML, keys.Target, eventstore, dbClient, instanceInterceptor.Handler, userAgentInterceptor, limitingAccessInterceptor)
samlProvider, err := saml.NewProvider(
config.SAML,
config.ExternalSecure,
commands,
queries,
authRepo,
keys.OIDC,
keys.SAML,
keys.Target,
eventstore,
dbClient,
instanceInterceptor.Handler,
userAgentInterceptor,
limitingAccessInterceptor,
httpClient,
)
if err != nil {
return nil, fmt.Errorf("unable to start saml provider: %w", err)
}
@@ -726,6 +748,7 @@ func startAPIs(
keys.CSRFCookieKey,
cacheConnectors,
federatedLogoutsCache,
httpClient,
)
if err != nil {
return nil, fmt.Errorf("unable to start login: %w", err)