mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-13 13:13:38 +00:00
feat: enable iframe use (#4766)
* feat: enable iframe use * cleanup * fix mocks * fix linting * docs: add iframe usage to solution scenarios configurations * improve api * feat(console): security policy * description * remove unnecessary line * disable input button and urls when not enabled * add image to docs Co-authored-by: Max Peintner <max@caos.ch> Co-authored-by: Fabi <38692350+hifabienne@users.noreply.github.com>
This commit is contained in:
@@ -19,6 +19,7 @@ type Instance interface {
|
||||
RequestedHost() string
|
||||
DefaultLanguage() language.Tag
|
||||
DefaultOrganisationID() string
|
||||
SecurityPolicyAllowedOrigins() []string
|
||||
}
|
||||
|
||||
type InstanceVerifier interface {
|
||||
@@ -66,6 +67,10 @@ func (i *instance) DefaultOrganisationID() string {
|
||||
return i.orgID
|
||||
}
|
||||
|
||||
func (i *instance) SecurityPolicyAllowedOrigins() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetInstance(ctx context.Context) Instance {
|
||||
instance, ok := ctx.Value(instanceKey).(Instance)
|
||||
if !ok {
|
||||
|
@@ -99,3 +99,7 @@ func (m *mockInstance) RequestedDomain() string {
|
||||
func (m *mockInstance) RequestedHost() string {
|
||||
return "zitadel.cloud:443"
|
||||
}
|
||||
|
||||
func (m *mockInstance) SecurityPolicyAllowedOrigins() []string {
|
||||
return nil
|
||||
}
|
||||
|
@@ -106,3 +106,23 @@ func (s *Server) UpdateSMTPConfigPassword(ctx context.Context, req *admin_pb.Upd
|
||||
details.ResourceOwner),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) GetSecurityPolicy(ctx context.Context, req *admin_pb.GetSecurityPolicyRequest) (*admin_pb.GetSecurityPolicyResponse, error) {
|
||||
policy, err := s.query.SecurityPolicy(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &admin_pb.GetSecurityPolicyResponse{
|
||||
Policy: SecurityPolicyToPb(policy),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) SetSecurityPolicy(ctx context.Context, req *admin_pb.SetSecurityPolicyRequest) (*admin_pb.SetSecurityPolicyResponse, error) {
|
||||
details, err := s.command.SetSecurityPolicy(ctx, req.EnableIframeEmbedding, req.AllowedOrigins)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &admin_pb.SetSecurityPolicyResponse{
|
||||
Details: object.DomainToChangeDetailsPb(details),
|
||||
}, nil
|
||||
}
|
||||
|
@@ -149,3 +149,11 @@ func SMTPConfigToPb(smtp *query.SMTPConfig) *settings_pb.SMTPConfig {
|
||||
}
|
||||
return mapped
|
||||
}
|
||||
|
||||
func SecurityPolicyToPb(policy *query.SecurityPolicy) *settings_pb.SecurityPolicy {
|
||||
return &settings_pb.SecurityPolicy{
|
||||
Details: obj_grpc.ToViewDetailsPb(policy.Sequence, policy.CreationDate, policy.ChangeDate, policy.AggregateID),
|
||||
EnableIframeEmbedding: policy.Enabled,
|
||||
AllowedOrigins: policy.AllowedOrigins,
|
||||
}
|
||||
}
|
||||
|
@@ -193,3 +193,7 @@ func (m *mockInstance) RequestedDomain() string {
|
||||
func (m *mockInstance) RequestedHost() string {
|
||||
return "localhost:8080"
|
||||
}
|
||||
|
||||
func (m *mockInstance) SecurityPolicyAllowedOrigins() []string {
|
||||
return nil
|
||||
}
|
||||
|
@@ -6,36 +6,38 @@ import (
|
||||
)
|
||||
|
||||
type CSP struct {
|
||||
DefaultSrc CSPSourceOptions
|
||||
ScriptSrc CSPSourceOptions
|
||||
ObjectSrc CSPSourceOptions
|
||||
StyleSrc CSPSourceOptions
|
||||
ImgSrc CSPSourceOptions
|
||||
MediaSrc CSPSourceOptions
|
||||
FrameSrc CSPSourceOptions
|
||||
FontSrc CSPSourceOptions
|
||||
ManifestSrc CSPSourceOptions
|
||||
ConnectSrc CSPSourceOptions
|
||||
FormAction CSPSourceOptions
|
||||
DefaultSrc CSPSourceOptions
|
||||
ScriptSrc CSPSourceOptions
|
||||
ObjectSrc CSPSourceOptions
|
||||
StyleSrc CSPSourceOptions
|
||||
ImgSrc CSPSourceOptions
|
||||
MediaSrc CSPSourceOptions
|
||||
FrameSrc CSPSourceOptions
|
||||
FrameAncestors CSPSourceOptions
|
||||
FontSrc CSPSourceOptions
|
||||
ManifestSrc CSPSourceOptions
|
||||
ConnectSrc CSPSourceOptions
|
||||
FormAction CSPSourceOptions
|
||||
}
|
||||
|
||||
var (
|
||||
DefaultSCP = CSP{
|
||||
DefaultSrc: CSPSourceOptsNone(),
|
||||
ScriptSrc: CSPSourceOptsSelf(),
|
||||
ObjectSrc: CSPSourceOptsNone(),
|
||||
StyleSrc: CSPSourceOptsSelf(),
|
||||
ImgSrc: CSPSourceOptsSelf(),
|
||||
MediaSrc: CSPSourceOptsNone(),
|
||||
FrameSrc: CSPSourceOptsNone(),
|
||||
FontSrc: CSPSourceOptsSelf(),
|
||||
ManifestSrc: CSPSourceOptsSelf(),
|
||||
ConnectSrc: CSPSourceOptsSelf(),
|
||||
DefaultSrc: CSPSourceOptsNone(),
|
||||
ScriptSrc: CSPSourceOptsSelf(),
|
||||
ObjectSrc: CSPSourceOptsNone(),
|
||||
StyleSrc: CSPSourceOptsSelf(),
|
||||
ImgSrc: CSPSourceOptsSelf(),
|
||||
MediaSrc: CSPSourceOptsNone(),
|
||||
FrameSrc: CSPSourceOptsNone(),
|
||||
FrameAncestors: CSPSourceOptsNone(),
|
||||
FontSrc: CSPSourceOptsSelf(),
|
||||
ManifestSrc: CSPSourceOptsSelf(),
|
||||
ConnectSrc: CSPSourceOptsSelf(),
|
||||
}
|
||||
)
|
||||
|
||||
func (csp *CSP) Value(nonce string, host string) string {
|
||||
valuesMap := csp.asMap()
|
||||
func (csp *CSP) Value(nonce, host string, iframe []string) string {
|
||||
valuesMap := csp.asMap(iframe)
|
||||
|
||||
values := make([]string, 0, len(valuesMap))
|
||||
for k, v := range valuesMap {
|
||||
@@ -49,19 +51,24 @@ func (csp *CSP) Value(nonce string, host string) string {
|
||||
return strings.Join(values, ";")
|
||||
}
|
||||
|
||||
func (csp *CSP) asMap() map[string]CSPSourceOptions {
|
||||
func (csp *CSP) asMap(iframe []string) map[string]CSPSourceOptions {
|
||||
frameAncestors := csp.FrameAncestors
|
||||
if len(iframe) > 0 {
|
||||
frameAncestors = CSPSourceOpts().AddHost(iframe...)
|
||||
}
|
||||
return map[string]CSPSourceOptions{
|
||||
"default-src": csp.DefaultSrc,
|
||||
"script-src": csp.ScriptSrc,
|
||||
"object-src": csp.ObjectSrc,
|
||||
"style-src": csp.StyleSrc,
|
||||
"img-src": csp.ImgSrc,
|
||||
"media-src": csp.MediaSrc,
|
||||
"frame-src": csp.FrameSrc,
|
||||
"font-src": csp.FontSrc,
|
||||
"manifest-src": csp.ManifestSrc,
|
||||
"connect-src": csp.ConnectSrc,
|
||||
"form-action": csp.FormAction,
|
||||
"default-src": csp.DefaultSrc,
|
||||
"script-src": csp.ScriptSrc,
|
||||
"object-src": csp.ObjectSrc,
|
||||
"style-src": csp.StyleSrc,
|
||||
"img-src": csp.ImgSrc,
|
||||
"media-src": csp.MediaSrc,
|
||||
"frame-src": csp.FrameSrc,
|
||||
"frame-ancestors": frameAncestors,
|
||||
"font-src": csp.FontSrc,
|
||||
"manifest-src": csp.ManifestSrc,
|
||||
"connect-src": csp.ConnectSrc,
|
||||
"form-action": csp.FormAction,
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -277,3 +277,7 @@ func (m *mockInstance) RequestedDomain() string {
|
||||
func (m *mockInstance) RequestedHost() string {
|
||||
return "zitadel.cloud:443"
|
||||
}
|
||||
|
||||
func (m *mockInstance) SecurityPolicyAllowedOrigins() []string {
|
||||
return nil
|
||||
}
|
||||
|
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
http_utils "github.com/zitadel/zitadel/internal/api/http"
|
||||
)
|
||||
|
||||
@@ -62,11 +63,14 @@ func (h *headers) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
r = saveContext(r, nonceKey, nonce)
|
||||
}
|
||||
allowedHosts := authz.GetInstance(r.Context()).SecurityPolicyAllowedOrigins()
|
||||
headers := w.Header()
|
||||
headers.Set(http_utils.ContentSecurityPolicy, h.csp.Value(nonce, r.Host))
|
||||
headers.Set(http_utils.ContentSecurityPolicy, h.csp.Value(nonce, r.Host, allowedHosts))
|
||||
headers.Set(http_utils.XXSSProtection, "1; mode=block")
|
||||
headers.Set(http_utils.StrictTransportSecurity, "max-age=31536000; includeSubDomains")
|
||||
headers.Set(http_utils.XFrameOptions, "DENY")
|
||||
if len(allowedHosts) == 0 {
|
||||
headers.Set(http_utils.XFrameOptions, "DENY")
|
||||
}
|
||||
headers.Set(http_utils.XContentTypeOptions, "nosniff")
|
||||
headers.Set(http_utils.ReferrerPolicy, "same-origin")
|
||||
headers.Set(http_utils.FeaturePolicy, "payment 'none'")
|
||||
|
@@ -99,7 +99,7 @@ func Start(config Config, externalSecure bool, issuer op.IssuerFromRequest, inst
|
||||
|
||||
handler := mux.NewRouter()
|
||||
|
||||
handler.Use(security, instanceHandler)
|
||||
handler.Use(instanceHandler, security)
|
||||
handler.Handle(envRequestPath, middleware.TelemetryHandler()(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
url := http_util.BuildOrigin(r.Host, externalSecure)
|
||||
environmentJSON, err := createEnvironmentJSON(url, issuer(r), authz.GetInstance(r.Context()).ConsoleClientID(), customerPortal)
|
||||
|
59
internal/command/instance_policy_security.go
Normal file
59
internal/command/instance_policy_security.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/command/preparation"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||
)
|
||||
|
||||
func (c *Commands) SetSecurityPolicy(ctx context.Context, enabled bool, allowedOrigins []string) (*domain.ObjectDetails, error) {
|
||||
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
|
||||
validation := c.prepareSetSecurityPolicy(instanceAgg, enabled, allowedOrigins)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
events, err := c.eventstore.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &domain.ObjectDetails{
|
||||
Sequence: events[len(events)-1].Sequence(),
|
||||
EventDate: events[len(events)-1].CreationDate(),
|
||||
ResourceOwner: events[len(events)-1].Aggregate().InstanceID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Commands) prepareSetSecurityPolicy(a *instance.Aggregate, enabled bool, allowedOrigins []string) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
writeModel, err := c.getSecurityPolicyWriteModel(ctx, filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmd, err := writeModel.NewSetEvent(ctx, &a.Aggregate, enabled, allowedOrigins)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []eventstore.Command{cmd}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) getSecurityPolicyWriteModel(ctx context.Context, filter preparation.FilterToQueryReducer) (_ *InstanceSecurityPolicyWriteModel, err error) {
|
||||
writeModel := NewInstanceSecurityPolicyWriteModel(ctx)
|
||||
events, err := filter(ctx, writeModel.Query())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(events) == 0 {
|
||||
return writeModel, nil
|
||||
}
|
||||
writeModel.AppendEvents(events...)
|
||||
err = writeModel.Reduce()
|
||||
return writeModel, err
|
||||
}
|
73
internal/command/instance_policy_security_model.go
Normal file
73
internal/command/instance_policy_security_model.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||
)
|
||||
|
||||
type InstanceSecurityPolicyWriteModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
Enabled bool
|
||||
AllowedOrigins []string
|
||||
}
|
||||
|
||||
func NewInstanceSecurityPolicyWriteModel(ctx context.Context) *InstanceSecurityPolicyWriteModel {
|
||||
return &InstanceSecurityPolicyWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: authz.GetInstance(ctx).InstanceID(),
|
||||
ResourceOwner: authz.GetInstance(ctx).InstanceID(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *InstanceSecurityPolicyWriteModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
if e, ok := event.(*instance.SecurityPolicySetEvent); ok {
|
||||
if e.Enabled != nil {
|
||||
wm.Enabled = *e.Enabled
|
||||
}
|
||||
if e.AllowedOrigins != nil {
|
||||
wm.AllowedOrigins = *e.AllowedOrigins
|
||||
}
|
||||
}
|
||||
}
|
||||
return wm.WriteModel.Reduce()
|
||||
}
|
||||
|
||||
func (wm *InstanceSecurityPolicyWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
ResourceOwner(wm.ResourceOwner).
|
||||
AddQuery().
|
||||
AggregateTypes(instance.AggregateType).
|
||||
AggregateIDs(wm.AggregateID).
|
||||
EventTypes(
|
||||
instance.SecurityPolicySetEventType).
|
||||
Builder()
|
||||
}
|
||||
|
||||
func (wm *InstanceSecurityPolicyWriteModel) NewSetEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
enabled bool,
|
||||
allowedOrigins []string,
|
||||
) (*instance.SecurityPolicySetEvent, error) {
|
||||
changes := make([]instance.SecurityPolicyChanges, 0, 2)
|
||||
var err error
|
||||
|
||||
if wm.Enabled != enabled {
|
||||
changes = append(changes, instance.ChangeSecurityPolicyEnabled(enabled))
|
||||
}
|
||||
if enabled && !reflect.DeepEqual(wm.AllowedOrigins, allowedOrigins) {
|
||||
changes = append(changes, instance.ChangeSecurityPolicyAllowedOrigins(allowedOrigins))
|
||||
}
|
||||
changeEvent, err := instance.NewSecurityPolicySetEvent(ctx, aggregate, changes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return changeEvent, nil
|
||||
}
|
@@ -244,3 +244,7 @@ func (m *mockInstance) RequestedDomain() string {
|
||||
func (m *mockInstance) RequestedHost() string {
|
||||
return "zitadel.cloud:443"
|
||||
}
|
||||
|
||||
func (m *mockInstance) SecurityPolicyAllowedOrigins() []string {
|
||||
return nil
|
||||
}
|
||||
|
@@ -11,6 +11,7 @@ import (
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/database"
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/query/projection"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
@@ -81,6 +82,12 @@ type Instance struct {
|
||||
DefaultLang language.Tag
|
||||
Domains []*InstanceDomain
|
||||
host string
|
||||
csp csp
|
||||
}
|
||||
|
||||
type csp struct {
|
||||
enabled bool
|
||||
allowedOrigins database.StringArray
|
||||
}
|
||||
|
||||
type Instances struct {
|
||||
@@ -120,6 +127,13 @@ func (i *Instance) DefaultOrganisationID() string {
|
||||
return i.DefaultOrgID
|
||||
}
|
||||
|
||||
func (i *Instance) SecurityPolicyAllowedOrigins() []string {
|
||||
if !i.csp.enabled {
|
||||
return nil
|
||||
}
|
||||
return i.csp.allowedOrigins
|
||||
}
|
||||
|
||||
type InstanceSearchQueries struct {
|
||||
SearchRequest
|
||||
Queries []SearchQuery
|
||||
@@ -189,7 +203,7 @@ func (q *Queries) InstanceByHost(ctx context.Context, host string) (_ authz.Inst
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
stmt, scan := prepareInstanceDomainQuery(host)
|
||||
stmt, scan := prepareAuthzInstanceQuery(host)
|
||||
host = strings.Split(host, ":")[0] //remove possible port
|
||||
query, args, err := stmt.Where(sq.Eq{
|
||||
InstanceDomainDomainCol.identifier(): host,
|
||||
@@ -436,3 +450,92 @@ func prepareInstanceDomainQuery(host string) (sq.SelectBuilder, func(*sql.Rows)
|
||||
return instance, nil
|
||||
}
|
||||
}
|
||||
|
||||
func prepareAuthzInstanceQuery(host string) (sq.SelectBuilder, func(*sql.Rows) (*Instance, error)) {
|
||||
return sq.Select(
|
||||
InstanceColumnID.identifier(),
|
||||
InstanceColumnCreationDate.identifier(),
|
||||
InstanceColumnChangeDate.identifier(),
|
||||
InstanceColumnSequence.identifier(),
|
||||
InstanceColumnName.identifier(),
|
||||
InstanceColumnDefaultOrgID.identifier(),
|
||||
InstanceColumnProjectID.identifier(),
|
||||
InstanceColumnConsoleID.identifier(),
|
||||
InstanceColumnConsoleAppID.identifier(),
|
||||
InstanceColumnDefaultLanguage.identifier(),
|
||||
InstanceDomainDomainCol.identifier(),
|
||||
InstanceDomainIsPrimaryCol.identifier(),
|
||||
InstanceDomainIsGeneratedCol.identifier(),
|
||||
InstanceDomainCreationDateCol.identifier(),
|
||||
InstanceDomainChangeDateCol.identifier(),
|
||||
InstanceDomainSequenceCol.identifier(),
|
||||
SecurityPolicyColumnEnabled.identifier(),
|
||||
SecurityPolicyColumnAllowedOrigins.identifier(),
|
||||
).
|
||||
From(instanceTable.identifier()).
|
||||
LeftJoin(join(InstanceDomainInstanceIDCol, InstanceColumnID)).
|
||||
LeftJoin(join(SecurityPolicyColumnInstanceID, InstanceColumnID)).
|
||||
PlaceholderFormat(sq.Dollar),
|
||||
func(rows *sql.Rows) (*Instance, error) {
|
||||
instance := &Instance{
|
||||
host: host,
|
||||
Domains: make([]*InstanceDomain, 0),
|
||||
}
|
||||
lang := ""
|
||||
for rows.Next() {
|
||||
var (
|
||||
domain sql.NullString
|
||||
isPrimary sql.NullBool
|
||||
isGenerated sql.NullBool
|
||||
changeDate sql.NullTime
|
||||
creationDate sql.NullTime
|
||||
sequence sql.NullInt64
|
||||
securityPolicyEnabled sql.NullBool
|
||||
)
|
||||
err := rows.Scan(
|
||||
&instance.ID,
|
||||
&instance.CreationDate,
|
||||
&instance.ChangeDate,
|
||||
&instance.Sequence,
|
||||
&instance.Name,
|
||||
&instance.DefaultOrgID,
|
||||
&instance.IAMProjectID,
|
||||
&instance.ConsoleID,
|
||||
&instance.ConsoleAppID,
|
||||
&lang,
|
||||
&domain,
|
||||
&isPrimary,
|
||||
&isGenerated,
|
||||
&changeDate,
|
||||
&creationDate,
|
||||
&sequence,
|
||||
&securityPolicyEnabled,
|
||||
&instance.csp.allowedOrigins,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-d3fas", "Errors.Internal")
|
||||
}
|
||||
if !domain.Valid {
|
||||
continue
|
||||
}
|
||||
instance.Domains = append(instance.Domains, &InstanceDomain{
|
||||
CreationDate: creationDate.Time,
|
||||
ChangeDate: changeDate.Time,
|
||||
Sequence: uint64(sequence.Int64),
|
||||
Domain: domain.String,
|
||||
IsPrimary: isPrimary.Bool,
|
||||
IsGenerated: isGenerated.Bool,
|
||||
InstanceID: instance.ID,
|
||||
})
|
||||
instance.csp.enabled = securityPolicyEnabled.Bool
|
||||
}
|
||||
if instance.ID == "" {
|
||||
return nil, errors.ThrowNotFound(nil, "QUERY-n0wng", "Errors.IAM.NotFound")
|
||||
}
|
||||
instance.DefaultLang = language.Make(lang)
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-Dfbe2", "Errors.Query.CloseRows")
|
||||
}
|
||||
return instance, nil
|
||||
}
|
||||
}
|
||||
|
@@ -59,6 +59,7 @@ var (
|
||||
OIDCSettingsProjection *oidcSettingsProjection
|
||||
DebugNotificationProviderProjection *debugNotificationProviderProjection
|
||||
KeyProjection *keyProjection
|
||||
SecurityPolicyProjection *securityPolicyProjection
|
||||
NotificationsProjection interface{}
|
||||
)
|
||||
|
||||
@@ -131,6 +132,7 @@ func Create(ctx context.Context, sqlClient *sql.DB, es *eventstore.Eventstore, c
|
||||
OIDCSettingsProjection = newOIDCSettingsProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["oidc_settings"]))
|
||||
DebugNotificationProviderProjection = newDebugNotificationProviderProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["debug_notification_provider"]))
|
||||
KeyProjection = newKeyProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["keys"]), keyEncryptionAlgorithm, certEncryptionAlgorithm)
|
||||
SecurityPolicyProjection = newSecurityPolicyProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["security_policies"]))
|
||||
newProjectionsList()
|
||||
return nil
|
||||
}
|
||||
@@ -221,5 +223,6 @@ func newProjectionsList() {
|
||||
OIDCSettingsProjection,
|
||||
DebugNotificationProviderProjection,
|
||||
KeyProjection,
|
||||
SecurityPolicyProjection,
|
||||
}
|
||||
}
|
||||
|
89
internal/query/projection/security_policy.go
Normal file
89
internal/query/projection/security_policy.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package projection
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/handler"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/handler/crdb"
|
||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||
)
|
||||
|
||||
const (
|
||||
SecurityPolicyProjectionTable = "projections.security_policies"
|
||||
SecurityPolicyColumnInstanceID = "instance_id"
|
||||
SecurityPolicyColumnCreationDate = "creation_date"
|
||||
SecurityPolicyColumnChangeDate = "change_date"
|
||||
SecurityPolicyColumnSequence = "sequence"
|
||||
SecurityPolicyColumnEnabled = "enabled"
|
||||
SecurityPolicyColumnAllowedOrigins = "origins"
|
||||
)
|
||||
|
||||
type securityPolicyProjection struct {
|
||||
crdb.StatementHandler
|
||||
}
|
||||
|
||||
func newSecurityPolicyProjection(ctx context.Context, config crdb.StatementHandlerConfig) *securityPolicyProjection {
|
||||
p := new(securityPolicyProjection)
|
||||
config.ProjectionName = SecurityPolicyProjectionTable
|
||||
config.Reducers = p.reducers()
|
||||
config.InitCheck = crdb.NewTableCheck(
|
||||
crdb.NewTable([]*crdb.Column{
|
||||
crdb.NewColumn(SecurityPolicyColumnCreationDate, crdb.ColumnTypeTimestamp),
|
||||
crdb.NewColumn(SecurityPolicyColumnChangeDate, crdb.ColumnTypeTimestamp),
|
||||
crdb.NewColumn(SecurityPolicyColumnInstanceID, crdb.ColumnTypeText),
|
||||
crdb.NewColumn(SecurityPolicyColumnSequence, crdb.ColumnTypeInt64),
|
||||
crdb.NewColumn(SecurityPolicyColumnEnabled, crdb.ColumnTypeBool, crdb.Default(false)),
|
||||
crdb.NewColumn(SecurityPolicyColumnAllowedOrigins, crdb.ColumnTypeTextArray, crdb.Nullable()),
|
||||
},
|
||||
crdb.NewPrimaryKey(SecurityPolicyColumnInstanceID),
|
||||
),
|
||||
)
|
||||
p.StatementHandler = crdb.NewStatementHandler(ctx, config)
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *securityPolicyProjection) reducers() []handler.AggregateReducer {
|
||||
return []handler.AggregateReducer{
|
||||
{
|
||||
Aggregate: instance.AggregateType,
|
||||
EventRedusers: []handler.EventReducer{
|
||||
{
|
||||
Event: instance.SecurityPolicySetEventType,
|
||||
Reduce: p.reduceSecurityPolicySet,
|
||||
},
|
||||
{
|
||||
Event: instance.InstanceRemovedEventType,
|
||||
Reduce: reduceInstanceRemovedHelper(SecurityPolicyColumnInstanceID),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *securityPolicyProjection) reduceSecurityPolicySet(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, ok := event.(*instance.SecurityPolicySetEvent)
|
||||
if !ok {
|
||||
return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-D3g87", "reduce.wrong.event.type %s", instance.SecurityPolicySetEventType)
|
||||
}
|
||||
changes := []handler.Column{
|
||||
handler.NewCol(SecurityPolicyColumnCreationDate, e.CreationDate()),
|
||||
handler.NewCol(SecurityPolicyColumnChangeDate, e.CreationDate()),
|
||||
handler.NewCol(SecurityPolicyColumnInstanceID, e.Aggregate().InstanceID),
|
||||
handler.NewCol(SecurityPolicyColumnSequence, e.Sequence()),
|
||||
}
|
||||
if e.Enabled != nil {
|
||||
changes = append(changes, handler.NewCol(SecurityPolicyColumnEnabled, *e.Enabled))
|
||||
}
|
||||
if e.AllowedOrigins != nil {
|
||||
changes = append(changes, handler.NewCol(SecurityPolicyColumnAllowedOrigins, e.AllowedOrigins))
|
||||
}
|
||||
return crdb.NewUpsertStatement(
|
||||
e,
|
||||
[]handler.Column{
|
||||
handler.NewCol(SecurityPolicyColumnInstanceID, ""),
|
||||
},
|
||||
changes,
|
||||
), nil
|
||||
}
|
98
internal/query/security_policy.go
Normal file
98
internal/query/security_policy.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
errs "errors"
|
||||
"time"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/database"
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/query/projection"
|
||||
)
|
||||
|
||||
var (
|
||||
securityPolicyTable = table{
|
||||
name: projection.SecurityPolicyProjectionTable,
|
||||
instanceIDCol: projection.SecurityPolicyColumnInstanceID,
|
||||
}
|
||||
SecurityPolicyColumnCreationDate = Column{
|
||||
name: projection.SecurityPolicyColumnCreationDate,
|
||||
table: securityPolicyTable,
|
||||
}
|
||||
SecurityPolicyColumnChangeDate = Column{
|
||||
name: projection.SecurityPolicyColumnChangeDate,
|
||||
table: securityPolicyTable,
|
||||
}
|
||||
SecurityPolicyColumnInstanceID = Column{
|
||||
name: projection.SecurityPolicyColumnInstanceID,
|
||||
table: securityPolicyTable,
|
||||
}
|
||||
SecurityPolicyColumnSequence = Column{
|
||||
name: projection.SecurityPolicyColumnSequence,
|
||||
table: securityPolicyTable,
|
||||
}
|
||||
SecurityPolicyColumnEnabled = Column{
|
||||
name: projection.SecurityPolicyColumnEnabled,
|
||||
table: securityPolicyTable,
|
||||
}
|
||||
SecurityPolicyColumnAllowedOrigins = Column{
|
||||
name: projection.SecurityPolicyColumnAllowedOrigins,
|
||||
table: securityPolicyTable,
|
||||
}
|
||||
)
|
||||
|
||||
type SecurityPolicy struct {
|
||||
AggregateID string
|
||||
CreationDate time.Time
|
||||
ChangeDate time.Time
|
||||
ResourceOwner string
|
||||
Sequence uint64
|
||||
|
||||
Enabled bool
|
||||
AllowedOrigins database.StringArray
|
||||
}
|
||||
|
||||
func (q *Queries) SecurityPolicy(ctx context.Context) (*SecurityPolicy, error) {
|
||||
stmt, scan := prepareSecurityPolicyQuery()
|
||||
query, args, err := stmt.Where(sq.Eq{
|
||||
SecurityPolicyColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
|
||||
}).ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-Sf6d1", "Errors.Query.SQLStatment")
|
||||
}
|
||||
|
||||
row := q.client.QueryRowContext(ctx, query, args...)
|
||||
return scan(row)
|
||||
}
|
||||
|
||||
func prepareSecurityPolicyQuery() (sq.SelectBuilder, func(*sql.Row) (*SecurityPolicy, error)) {
|
||||
return sq.Select(
|
||||
SecurityPolicyColumnInstanceID.identifier(),
|
||||
SecurityPolicyColumnCreationDate.identifier(),
|
||||
SecurityPolicyColumnChangeDate.identifier(),
|
||||
SecurityPolicyColumnInstanceID.identifier(),
|
||||
SecurityPolicyColumnSequence.identifier(),
|
||||
SecurityPolicyColumnEnabled.identifier(),
|
||||
SecurityPolicyColumnAllowedOrigins.identifier()).
|
||||
From(securityPolicyTable.identifier()).PlaceholderFormat(sq.Dollar),
|
||||
func(row *sql.Row) (*SecurityPolicy, error) {
|
||||
securityPolicy := new(SecurityPolicy)
|
||||
err := row.Scan(
|
||||
&securityPolicy.AggregateID,
|
||||
&securityPolicy.CreationDate,
|
||||
&securityPolicy.ChangeDate,
|
||||
&securityPolicy.ResourceOwner,
|
||||
&securityPolicy.Sequence,
|
||||
&securityPolicy.Enabled,
|
||||
&securityPolicy.AllowedOrigins,
|
||||
)
|
||||
if err != nil && !errs.Is(err, sql.ErrNoRows) { // ignore not found errors
|
||||
return nil, errors.ThrowInternal(err, "QUERY-Dfrt2", "Errors.Internal")
|
||||
}
|
||||
return securityPolicy, nil
|
||||
}
|
||||
}
|
@@ -30,6 +30,7 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
|
||||
RegisterFilterEventMapper(DebugNotificationProviderLogRemovedEventType, DebugNotificationProviderLogRemovedEventMapper).
|
||||
RegisterFilterEventMapper(OIDCSettingsAddedEventType, OIDCSettingsAddedEventMapper).
|
||||
RegisterFilterEventMapper(OIDCSettingsChangedEventType, OIDCSettingsChangedEventMapper).
|
||||
RegisterFilterEventMapper(SecurityPolicySetEventType, SecurityPolicySetEventMapper).
|
||||
RegisterFilterEventMapper(LabelPolicyAddedEventType, LabelPolicyAddedEventMapper).
|
||||
RegisterFilterEventMapper(LabelPolicyChangedEventType, LabelPolicyChangedEventMapper).
|
||||
RegisterFilterEventMapper(LabelPolicyActivatedEventType, LabelPolicyActivatedEventMapper).
|
||||
|
80
internal/repository/instance/policy_security.go
Normal file
80
internal/repository/instance/policy_security.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package instance
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/repository"
|
||||
)
|
||||
|
||||
const (
|
||||
securityPolicyPrefix = "policy.security."
|
||||
SecurityPolicySetEventType = instanceEventTypePrefix + securityPolicyPrefix + "set"
|
||||
)
|
||||
|
||||
type SecurityPolicySetEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
|
||||
Enabled *bool `json:"enabled,omitempty"`
|
||||
AllowedOrigins *[]string `json:"allowedOrigins,omitempty"`
|
||||
}
|
||||
|
||||
func NewSecurityPolicySetEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
changes []SecurityPolicyChanges,
|
||||
) (*SecurityPolicySetEvent, error) {
|
||||
if len(changes) == 0 {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "POLICY-EWsf3", "Errors.NoChangesFound")
|
||||
}
|
||||
event := &SecurityPolicySetEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
SecurityPolicySetEventType,
|
||||
),
|
||||
}
|
||||
for _, change := range changes {
|
||||
change(event)
|
||||
}
|
||||
return event, nil
|
||||
}
|
||||
|
||||
type SecurityPolicyChanges func(event *SecurityPolicySetEvent)
|
||||
|
||||
func ChangeSecurityPolicyEnabled(enabled bool) func(event *SecurityPolicySetEvent) {
|
||||
return func(e *SecurityPolicySetEvent) {
|
||||
e.Enabled = &enabled
|
||||
}
|
||||
}
|
||||
|
||||
func ChangeSecurityPolicyAllowedOrigins(allowedOrigins []string) func(event *SecurityPolicySetEvent) {
|
||||
return func(e *SecurityPolicySetEvent) {
|
||||
if len(allowedOrigins) == 0 {
|
||||
allowedOrigins = []string{}
|
||||
}
|
||||
e.AllowedOrigins = &allowedOrigins
|
||||
}
|
||||
}
|
||||
|
||||
func (e *SecurityPolicySetEvent) Data() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *SecurityPolicySetEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func SecurityPolicySetEventMapper(event *repository.Event) (eventstore.Event, error) {
|
||||
securityPolicyAdded := &SecurityPolicySetEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
}
|
||||
err := json.Unmarshal(event.Data, securityPolicyAdded)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "IAM-soiwj", "unable to unmarshal oidc config added")
|
||||
}
|
||||
|
||||
return securityPolicyAdded, nil
|
||||
}
|
Reference in New Issue
Block a user