feat(action v2): add JWT and JWE payload type options (#11196)

# Which Problems Are Solved

The payload in actions V2 is currently sent as JSON to the target
endpoint. It might get exposed to intermediary infrastructure or logging
systems.
For these scenarios there needs to be an application-layer encryption,
where the provider of the endpoint can define an key to be used for the
encryption.

# How the Problems Are Solved

- Added an additional option to the target to specify the payload type:
`JSON` (current and default), `JWT`, `JWE` (api and console)
- added endpoints to upload and manage public keys (to be used for
encryption) for a target
- updated all action v2 executions (interceptors, oidc, saml, ...) to
provide the `GetActiveSigningWebKey` from queries
- implemented jwt and jwe in the exections incl. refactoring of code and
tests
- changes to the authn_keys table:
  - added a `fingerprint` column
  - dropped not null constraint on expiration
- moved the `GetSignerOnce` into its own package to prevent circular
dependencies

# Additional Changes

None

# Additional Context

closes #11061

---------

Co-authored-by: Marco A. <[email protected]>
Co-authored-by: conblem <[email protected]>
Co-authored-by: Silvan <[email protected]>
This commit is contained in:
Livio Spring
2025-12-29 13:35:53 +00:00
committed by GitHub
co-authored by Marco A. conblem Silvan
parent 72c8f0192c
commit 791d0587aa
124 changed files with 5040 additions and 782 deletions
+53 -6
View File
@@ -84,6 +84,10 @@ var (
name: projection.AuthNKeyEnabledCol,
table: authNKeyTable,
}
AuthNKeyColumnFingerprint = Column{
name: projection.AuthNKeyFingerprintCol,
table: authNKeyTable,
}
)
type AuthNKeys struct {
@@ -100,8 +104,11 @@ type AuthNKey struct {
Sequence uint64
ApplicationID string
Expiration time.Time
Type domain.AuthNKeyType
Expiration time.Time
Type domain.AuthNKeyType
Enabled bool
PublicKey []byte
Fingerprint string
}
type AuthNKeysData struct {
@@ -141,6 +148,7 @@ const (
JoinFilterUnspecified JoinFilter = iota
JoinFilterApp
JoinFilterUserMachine
JoinFilterTarget
)
// SearchAuthNKeys returns machine or app keys, depending on the join filter.
@@ -173,11 +181,17 @@ func (q *Queries) searchAuthNKeys(ctx context.Context, queries *AuthNKeySearchQu
case JoinFilterUserMachine:
query = query.Join(join(MachineUserIDCol, AuthNKeyColumnIdentifier))
query = userPermissionCheckV2WithCustomColumns(ctx, query, permissionCheckV2, queries.Queries, AuthNKeyColumnResourceOwner, AuthNKeyColumnIdentifier)
case JoinFilterTarget:
query = query.Join(join(TargetColumnID, AuthNKeyColumnObjectID))
}
eq := sq.Eq{
AuthNKeyColumnEnabled.identifier(): true,
AuthNKeyColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
}
// Until now all disabled keys were just filtered out, but target keys can be explicitly disabled and
// the state needs to be returned rather than filtering them out.
if joinFilter != JoinFilterTarget {
eq[AuthNKeyColumnEnabled.identifier()] = true
}
stmt, args, err := query.Where(eq).ToSql()
if err != nil {
@@ -283,6 +297,14 @@ func NewAuthNKeyIDQuery(id string) (SearchQuery, error) {
return NewTextQuery(AuthNKeyColumnID, id, TextEquals)
}
func NewAuthNKeyInIDsSearchQuery(ids []string) (SearchQuery, error) {
return NewInTextQuery(AuthNKeyColumnID, ids)
}
func NewAuthNKeyEnabledSearchQuery(enabled bool) (SearchQuery, error) {
return NewBoolQuery(AuthNKeyColumnEnabled, enabled)
}
func NewAuthNKeyIdentifyerQuery(id string) (SearchQuery, error) {
return NewTextQuery(AuthNKeyColumnIdentifier, id, TextEquals)
}
@@ -291,8 +313,19 @@ func NewAuthNKeyCreationDateQuery(ts time.Time, compare TimestampComparison) (Se
return NewTimestampQuery(AuthNKeyColumnCreationDate, ts, compare)
}
func NewAuthNKeyExpirationDateDateQuery(ts time.Time, compare TimestampComparison) (SearchQuery, error) {
return NewTimestampQuery(AuthNKeyColumnExpiration, ts, compare)
func NewAuthNKeyExpirationDateQuery(ts time.Time, compare TimestampComparison) (SearchQuery, error) {
query, err := NewTimestampQuery(AuthNKeyColumnExpiration, ts, compare)
if err != nil {
return nil, err
}
if compare != TimestampGreater && compare != TimestampGreaterOrEquals {
return query, nil
}
noExpiration, err := NewIsNullQuery(AuthNKeyColumnExpiration)
if err != nil {
return nil, err
}
return NewOrQuery(query, noExpiration)
}
//go:embed authn_key_user.sql
@@ -344,6 +377,9 @@ func prepareAuthNKeysQuery() (sq.SelectBuilder, func(rows *sql.Rows) (*AuthNKeys
AuthNKeyColumnExpiration.identifier(),
AuthNKeyColumnType.identifier(),
AuthNKeyColumnObjectID.identifier(),
AuthNKeyColumnEnabled.identifier(),
AuthNKeyColumnPublicKey.identifier(),
AuthNKeyColumnFingerprint.identifier(),
countColumn.identifier(),
).From(authNKeyTable.identifier()).
PlaceholderFormat(sq.Dollar)
@@ -352,6 +388,8 @@ func prepareAuthNKeysQuery() (sq.SelectBuilder, func(rows *sql.Rows) (*AuthNKeys
authNKeys := make([]*AuthNKey, 0)
var count uint64
for rows.Next() {
var expiration sql.NullTime
var fingerprint sql.NullString
authNKey := new(AuthNKey)
err := rows.Scan(
&authNKey.ID,
@@ -360,14 +398,23 @@ func prepareAuthNKeysQuery() (sq.SelectBuilder, func(rows *sql.Rows) (*AuthNKeys
&authNKey.ChangeDate,
&authNKey.ResourceOwner,
&authNKey.Sequence,
&authNKey.Expiration,
&expiration,
&authNKey.Type,
&authNKey.ApplicationID,
&authNKey.Enabled,
&authNKey.PublicKey,
&fingerprint,
&count,
)
if err != nil {
return nil, err
}
if expiration.Valid {
authNKey.Expiration = expiration.Time
}
if fingerprint.Valid {
authNKey.Fingerprint = fingerprint.String
}
authNKeys = append(authNKeys, authNKey)
}
+27 -2
View File
@@ -7,6 +7,7 @@ import (
"fmt"
"regexp"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -27,6 +28,9 @@ var (
` projections.authn_keys2.expiration,` +
` projections.authn_keys2.type,` +
` projections.authn_keys2.object_id,` +
` projections.authn_keys2.enabled,` +
` projections.authn_keys2.public_key,` +
` projections.authn_keys2.fingerprint,` +
` COUNT(*) OVER ()` +
` FROM projections.authn_keys2`
prepareAuthNKeysCols = []string{
@@ -39,6 +43,9 @@ var (
"expiration",
"type",
"object_id",
"enabled",
"public_key",
"fingerprint",
"count",
}
@@ -132,6 +139,9 @@ func Test_AuthNKeyPrepares(t *testing.T) {
testNow,
1,
"app1",
true,
[]byte("public"),
"fingerprint",
},
},
),
@@ -151,6 +161,9 @@ func Test_AuthNKeyPrepares(t *testing.T) {
Expiration: testNow,
Type: domain.AuthNKeyTypeJSON,
ApplicationID: "app1",
Enabled: true,
PublicKey: []byte("public"),
Fingerprint: "fingerprint",
},
},
},
@@ -173,6 +186,9 @@ func Test_AuthNKeyPrepares(t *testing.T) {
testNow,
1,
"app1",
true,
[]byte("public1"),
"fingerprint1",
},
{
"id-2",
@@ -181,9 +197,12 @@ func Test_AuthNKeyPrepares(t *testing.T) {
testNow,
"ro",
uint64(20211109),
testNow,
nil,
1,
"app1",
true,
[]byte("public2"),
nil,
},
},
),
@@ -203,6 +222,9 @@ func Test_AuthNKeyPrepares(t *testing.T) {
Expiration: testNow,
Type: domain.AuthNKeyTypeJSON,
ApplicationID: "app1",
Enabled: true,
PublicKey: []byte("public1"),
Fingerprint: "fingerprint1",
},
{
ID: "id-2",
@@ -211,9 +233,12 @@ func Test_AuthNKeyPrepares(t *testing.T) {
ChangeDate: testNow,
ResourceOwner: "ro",
Sequence: 20211109,
Expiration: testNow,
Expiration: time.Time{},
Type: domain.AuthNKeyTypeJSON,
ApplicationID: "app1",
Enabled: true,
PublicKey: []byte("public2"),
Fingerprint: "",
},
},
},
+9 -1
View File
@@ -35,7 +35,10 @@ with domain as (
'endpoint', t.endpoint,
'timeout', t.timeout,
'interrupt_on_error', t.interrupt_on_error,
'signing_key', t.signing_key
'signing_key', t.signing_key,
'payload_type', t.payload_type,
'encryption_key', encode(k.public_key, 'base64'),
'encryption_key_id', k.id
) as execution_targets
from domain d
join projections.executions1 e
@@ -46,6 +49,11 @@ with domain as (
join projections.targets2 t
on et.instance_id = t.instance_id
and et.target_id = t.id
left join projections.authn_keys2 k
on k.instance_id = et.instance_id
and k.object_id = t.id
and k.enabled = true
and (k.expiration IS NULL or k.expiration > now())
order by et.position asc
) as x
group by instance_id
+9 -1
View File
@@ -26,7 +26,10 @@ with features as (
'endpoint', t.endpoint,
'timeout', t.timeout,
'interrupt_on_error', t.interrupt_on_error,
'signing_key', t.signing_key
'signing_key', t.signing_key,
'payload_type', t.payload_type,
'encryption_key', encode(k.public_key, 'base64'),
'encryption_key_id', k.id
) as execution_targets
from projections.executions1 e
join projections.executions1_targets et
@@ -35,6 +38,11 @@ with features as (
join projections.targets2 t
on et.instance_id = t.instance_id
and et.target_id = t.id
left join projections.authn_keys2 k
on k.instance_id = et.instance_id
and k.object_id = t.id
and k.enabled = true
and (k.expiration IS NULL or k.expiration > now())
where e.instance_id = $1
order by et.position asc
) as x
+109 -9
View File
@@ -2,6 +2,7 @@ package projection
import (
"context"
"database/sql"
"time"
"github.com/zitadel/zitadel/internal/domain"
@@ -11,6 +12,7 @@ import (
"github.com/zitadel/zitadel/internal/repository/instance"
"github.com/zitadel/zitadel/internal/repository/org"
"github.com/zitadel/zitadel/internal/repository/project"
"github.com/zitadel/zitadel/internal/repository/target"
"github.com/zitadel/zitadel/internal/repository/user"
"github.com/zitadel/zitadel/internal/zerrors"
)
@@ -30,6 +32,7 @@ const (
AuthNKeyPublicKeyCol = "public_key"
AuthNKeyTypeCol = "type"
AuthNKeyEnabledCol = "enabled"
AuthNKeyFingerprintCol = "fingerprint"
)
type authNKeyProjection struct{}
@@ -53,11 +56,12 @@ func (*authNKeyProjection) Init() *old_handler.Check {
handler.NewColumn(AuthNKeyAggregateIDCol, handler.ColumnTypeText),
handler.NewColumn(AuthNKeySequenceCol, handler.ColumnTypeInt64),
handler.NewColumn(AuthNKeyObjectIDCol, handler.ColumnTypeText),
handler.NewColumn(AuthNKeyExpirationCol, handler.ColumnTypeTimestamp),
handler.NewColumn(AuthNKeyExpirationCol, handler.ColumnTypeTimestamp, handler.Nullable()),
handler.NewColumn(AuthNKeyIdentifierCol, handler.ColumnTypeText),
handler.NewColumn(AuthNKeyPublicKeyCol, handler.ColumnTypeBytes),
handler.NewColumn(AuthNKeyEnabledCol, handler.ColumnTypeBool, handler.Default(true)),
handler.NewColumn(AuthNKeyTypeCol, handler.ColumnTypeEnum, handler.Default(0)),
handler.NewColumn(AuthNKeyFingerprintCol, handler.ColumnTypeText, handler.Nullable()),
},
handler.NewPrimaryKey(AuthNKeyInstanceIDCol, AuthNKeyIDCol),
handler.WithIndex(handler.NewIndex("enabled", []string{AuthNKeyEnabledCol})),
@@ -117,6 +121,31 @@ func (p *authNKeyProjection) Reducers() []handler.AggregateReducer {
},
},
},
{
Aggregate: target.AggregateType,
EventReducers: []handler.EventReducer{
{
Event: target.KeyAddedEventType,
Reduce: p.reduceAuthNKeyAdded,
},
{
Event: target.KeyActivatedEventType,
Reduce: p.reduceTargetKeyActivated,
},
{
Event: target.KeyDeactivatedEventType,
Reduce: p.reduceTargetKeyDeactivated,
},
{
Event: target.KeyRemovedEventType,
Reduce: p.reduceAuthNKeyRemoved,
},
{
Event: target.RemovedEventType,
Reduce: p.reduceAuthNKeyRemoved,
},
},
},
{
Aggregate: org.AggregateType,
EventReducers: []handler.EventReducer{
@@ -141,30 +170,43 @@ func (p *authNKeyProjection) Reducers() []handler.AggregateReducer {
func (p *authNKeyProjection) reduceAuthNKeyAdded(event eventstore.Event) (*handler.Statement, error) {
var authNKeyEvent struct {
eventstore.BaseEvent
keyID string
objectID string
expiration time.Time
identifier string
publicKey []byte
keyType domain.AuthNKeyType
keyID string
objectID string
expiration sql.NullTime
identifier string
publicKey []byte
keyType domain.AuthNKeyType
fingerprint sql.NullString
enabled bool
}
switch e := event.(type) {
case *project.ApplicationKeyAddedEvent:
authNKeyEvent.BaseEvent = e.BaseEvent
authNKeyEvent.keyID = e.KeyID
authNKeyEvent.objectID = e.AppID
authNKeyEvent.expiration = e.ExpirationDate
authNKeyEvent.expiration = sql.NullTime{Time: e.ExpirationDate, Valid: true}
authNKeyEvent.identifier = e.ClientID
authNKeyEvent.publicKey = e.PublicKey
authNKeyEvent.keyType = e.KeyType
authNKeyEvent.enabled = true
case *user.MachineKeyAddedEvent:
authNKeyEvent.BaseEvent = e.BaseEvent
authNKeyEvent.keyID = e.KeyID
authNKeyEvent.objectID = e.Aggregate().ID
authNKeyEvent.expiration = e.ExpirationDate
authNKeyEvent.expiration = sql.NullTime{Time: e.ExpirationDate, Valid: true}
authNKeyEvent.identifier = e.Aggregate().ID
authNKeyEvent.publicKey = e.PublicKey
authNKeyEvent.keyType = e.KeyType
authNKeyEvent.enabled = true
case *target.KeyAddedEvent:
authNKeyEvent.BaseEvent = e.BaseEvent
authNKeyEvent.keyID = e.KeyID
authNKeyEvent.objectID = e.Aggregate().ID
authNKeyEvent.expiration = sql.NullTime{Time: e.ExpirationDate, Valid: !e.ExpirationDate.IsZero()}
authNKeyEvent.identifier = e.Aggregate().ID
authNKeyEvent.publicKey = e.PublicKey
authNKeyEvent.fingerprint = sql.NullString{String: e.Fingerprint, Valid: true}
authNKeyEvent.enabled = false
default:
return nil, zerrors.ThrowInvalidArgumentf(nil, "PROJE-Dgb32", "reduce.wrong.event.type %v", []eventstore.EventType{project.ApplicationKeyAddedEventType, user.MachineKeyAddedEventType})
}
@@ -183,6 +225,8 @@ func (p *authNKeyProjection) reduceAuthNKeyAdded(event eventstore.Event) (*handl
handler.NewCol(AuthNKeyIdentifierCol, authNKeyEvent.identifier),
handler.NewCol(AuthNKeyPublicKeyCol, authNKeyEvent.publicKey),
handler.NewCol(AuthNKeyTypeCol, authNKeyEvent.keyType),
handler.NewCol(AuthNKeyFingerprintCol, authNKeyEvent.fingerprint),
handler.NewCol(AuthNKeyEnabledCol, authNKeyEvent.enabled),
},
), nil
}
@@ -239,6 +283,10 @@ func (p *authNKeyProjection) reduceAuthNKeyRemoved(event eventstore.Event) (*han
condition = handler.NewCond(AuthNKeyIDCol, e.KeyID)
case *user.UserRemovedEvent:
condition = handler.NewCond(AuthNKeyAggregateIDCol, e.Aggregate().ID)
case *target.KeyRemovedEvent:
condition = handler.NewCond(AuthNKeyIDCol, e.KeyID)
case *target.RemovedEvent:
condition = handler.NewCond(AuthNKeyAggregateIDCol, e.Aggregate().ID)
default:
return nil, zerrors.ThrowInvalidArgumentf(nil, "PROJE-BGge42", "reduce.wrong.event.type %v", []eventstore.EventType{project.ApplicationKeyRemovedEventType, project.ApplicationRemovedType, project.ProjectRemovedType, user.MachineKeyRemovedEventType, user.UserRemovedType})
}
@@ -265,3 +313,55 @@ func (p *authNKeyProjection) reduceOwnerRemoved(event eventstore.Event) (*handle
},
), nil
}
func (p *authNKeyProjection) reduceTargetKeyActivated(event eventstore.Event) (*handler.Statement, error) {
e, err := assertEvent[*target.KeyActivatedEvent](event)
if err != nil {
return nil, err
}
return handler.NewMultiStatement(
e,
handler.AddUpdateStatement(
[]handler.Column{
handler.NewCol(AuthNKeyEnabledCol, false),
handler.NewCol(AuthNKeyChangeDateCol, e.CreationDate()),
handler.NewCol(AuthNKeySequenceCol, e.Sequence()),
},
[]handler.Condition{
handler.NewCond(AuthNKeyInstanceIDCol, e.Aggregate().InstanceID),
handler.NewCond(AuthNKeyAggregateIDCol, e.Aggregate().ID),
handler.NewCond(AuthNKeyEnabledCol, true),
},
),
handler.AddUpdateStatement(
[]handler.Column{
handler.NewCol(AuthNKeyEnabledCol, true),
handler.NewCol(AuthNKeyChangeDateCol, e.CreationDate()),
handler.NewCol(AuthNKeySequenceCol, e.Sequence()),
},
[]handler.Condition{
handler.NewCond(AuthNKeyIDCol, e.KeyID),
handler.NewCond(AuthNKeyInstanceIDCol, e.Aggregate().InstanceID),
},
),
), nil
}
func (p *authNKeyProjection) reduceTargetKeyDeactivated(event eventstore.Event) (*handler.Statement, error) {
e, err := assertEvent[*target.KeyDeactivatedEvent](event)
if err != nil {
return nil, err
}
return handler.NewUpdateStatement(
e,
[]handler.Column{
handler.NewCol(AuthNKeyEnabledCol, false),
handler.NewCol(AuthNKeyChangeDateCol, e.CreationDate()),
handler.NewCol(AuthNKeySequenceCol, e.Sequence()),
},
[]handler.Condition{
handler.NewCond(AuthNKeyIDCol, e.KeyID),
handler.NewCond(AuthNKeyInstanceIDCol, e.Aggregate().InstanceID),
},
), nil
}
+172 -2
View File
@@ -1,6 +1,7 @@
package projection
import (
"database/sql"
"testing"
"github.com/zitadel/zitadel/internal/domain"
@@ -9,6 +10,7 @@ import (
"github.com/zitadel/zitadel/internal/repository/instance"
"github.com/zitadel/zitadel/internal/repository/org"
"github.com/zitadel/zitadel/internal/repository/project"
"github.com/zitadel/zitadel/internal/repository/target"
"github.com/zitadel/zitadel/internal/repository/user"
"github.com/zitadel/zitadel/internal/zerrors"
)
@@ -40,7 +42,7 @@ func TestAuthNKeyProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO projections.authn_keys2 (id, creation_date, change_date, resource_owner, instance_id, aggregate_id, sequence, object_id, expiration, identifier, public_key, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)",
expectedStmt: "INSERT INTO projections.authn_keys2 (id, creation_date, change_date, resource_owner, instance_id, aggregate_id, sequence, object_id, expiration, identifier, public_key, type, fingerprint, enabled) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)",
expectedArgs: []interface{}{
"keyId",
anyArg{},
@@ -54,6 +56,8 @@ func TestAuthNKeyProjection_reduces(t *testing.T) {
"clientId",
[]byte("publicKey"),
domain.AuthNKeyTypeJSON,
sql.NullString{}, // fingerprint is not set for apps
true, // apps keys are enabled by default
},
},
},
@@ -77,7 +81,7 @@ func TestAuthNKeyProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO projections.authn_keys2 (id, creation_date, change_date, resource_owner, instance_id, aggregate_id, sequence, object_id, expiration, identifier, public_key, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)",
expectedStmt: "INSERT INTO projections.authn_keys2 (id, creation_date, change_date, resource_owner, instance_id, aggregate_id, sequence, object_id, expiration, identifier, public_key, type, fingerprint, enabled) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)",
expectedArgs: []interface{}{
"keyId",
anyArg{},
@@ -91,6 +95,47 @@ func TestAuthNKeyProjection_reduces(t *testing.T) {
"agg-id",
[]byte("publicKey"),
domain.AuthNKeyTypeJSON,
sql.NullString{}, // fingerprint is not set for users
true, // user keys are enabled by default
},
},
},
},
},
},
{
name: "reduceAuthNKeyAdded target",
args: args{
event: getEvent(
testEvent(
target.KeyAddedEventType,
target.AggregateType,
[]byte(`{"keyId": "keyId", "expirationDate": "2021-11-30T15:00:00Z", "publicKey": "cHVibGljS2V5", "fingerprint": "fingerprint"}`),
), eventstore.GenericEventMapper[target.KeyAddedEvent]),
},
reduce: (&authNKeyProjection{}).reduceAuthNKeyAdded,
want: wantReduce{
aggregateType: eventstore.AggregateType("target"),
sequence: 15,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO projections.authn_keys2 (id, creation_date, change_date, resource_owner, instance_id, aggregate_id, sequence, object_id, expiration, identifier, public_key, type, fingerprint, enabled) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)",
expectedArgs: []interface{}{
"keyId",
anyArg{},
anyArg{},
"ro-id",
"instance-id",
"agg-id",
uint64(15),
"agg-id",
anyArg{},
"agg-id",
[]byte("publicKey"),
domain.AuthNKeyTypeNONE,
sql.NullString{String: "fingerprint", Valid: true},
false, // target keys are disabled by default
},
},
},
@@ -470,6 +515,60 @@ func TestAuthNKeyProjection_reduces(t *testing.T) {
},
},
},
{
name: "reduceAuthNKeyRemoved target key removed",
args: args{
event: getEvent(
testEvent(
target.KeyRemovedEventType,
target.AggregateType,
[]byte(`{"keyId": "keyId"}`),
), eventstore.GenericEventMapper[target.KeyRemovedEvent]),
},
reduce: (&authNKeyProjection{}).reduceAuthNKeyRemoved,
want: wantReduce{
aggregateType: eventstore.AggregateType("target"),
sequence: 15,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "DELETE FROM projections.authn_keys2 WHERE (id = $1) AND (instance_id = $2)",
expectedArgs: []interface{}{
"keyId",
"instance-id",
},
},
},
},
},
},
{
name: "reduceAuthNKeyRemoved target removed",
args: args{
event: getEvent(
testEvent(
target.RemovedEventType,
target.AggregateType,
[]byte(`{"keyId": "keyId"}`),
), eventstore.GenericEventMapper[target.RemovedEvent]),
},
reduce: (&authNKeyProjection{}).reduceAuthNKeyRemoved,
want: wantReduce{
aggregateType: eventstore.AggregateType("target"),
sequence: 15,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "DELETE FROM projections.authn_keys2 WHERE (aggregate_id = $1) AND (instance_id = $2)",
expectedArgs: []interface{}{
"agg-id",
"instance-id",
},
},
},
},
},
},
{
name: "reduceOwnerRemoved",
args: args{
@@ -497,6 +596,77 @@ func TestAuthNKeyProjection_reduces(t *testing.T) {
},
},
},
{
name: "reduceTargetKeyActivated",
args: args{
event: getEvent(
testEvent(
target.KeyActivatedEventType,
target.AggregateType,
[]byte(`{"keyId": "keyId"}`),
), eventstore.GenericEventMapper[target.KeyActivatedEvent]),
},
reduce: (&authNKeyProjection{}).reduceTargetKeyActivated,
want: wantReduce{
aggregateType: eventstore.AggregateType("target"),
sequence: 15,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.authn_keys2 SET (enabled, change_date, sequence) = ($1, $2, $3) WHERE (instance_id = $4) AND (aggregate_id = $5) AND (enabled = $6)",
expectedArgs: []interface{}{
false,
anyArg{},
uint64(15),
"instance-id",
"agg-id",
true,
},
},
{
expectedStmt: "UPDATE projections.authn_keys2 SET (enabled, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedArgs: []interface{}{
true,
anyArg{},
uint64(15),
"keyId",
"instance-id",
},
},
},
},
},
},
{
name: "reduceTargetKeyDeactivated",
args: args{
event: getEvent(
testEvent(
target.KeyDeactivatedEventType,
target.AggregateType,
[]byte(`{"keyId": "keyId"}`),
), eventstore.GenericEventMapper[target.KeyDeactivatedEvent]),
},
reduce: (&authNKeyProjection{}).reduceTargetKeyDeactivated,
want: wantReduce{
aggregateType: eventstore.AggregateType("target"),
sequence: 15,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.authn_keys2 SET (enabled, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedArgs: []interface{}{
false,
anyArg{},
uint64(15),
"keyId",
"instance-id",
},
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
+7
View File
@@ -6,6 +6,7 @@ import (
"github.com/zitadel/zitadel/internal/eventstore"
old_handler "github.com/zitadel/zitadel/internal/eventstore/handler"
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
target_domain "github.com/zitadel/zitadel/internal/execution/target"
"github.com/zitadel/zitadel/internal/repository/instance"
"github.com/zitadel/zitadel/internal/repository/target"
)
@@ -24,6 +25,7 @@ const (
TargetTimeoutCol = "timeout"
TargetInterruptOnErrorCol = "interrupt_on_error"
TargetSigningKey = "signing_key"
TargetPayloadType = "payload_type"
)
type targetProjection struct{}
@@ -51,6 +53,7 @@ func (*targetProjection) Init() *old_handler.Check {
handler.NewColumn(TargetTimeoutCol, handler.ColumnTypeInt64),
handler.NewColumn(TargetInterruptOnErrorCol, handler.ColumnTypeBool),
handler.NewColumn(TargetSigningKey, handler.ColumnTypeJSONB, handler.Nullable()),
handler.NewColumn(TargetPayloadType, handler.ColumnTypeEnum, handler.Default(target_domain.PayloadTypeUnspecified)),
},
handler.NewPrimaryKey(TargetInstanceIDCol, TargetIDCol),
),
@@ -108,6 +111,7 @@ func (p *targetProjection) reduceTargetAdded(event eventstore.Event) (*handler.S
handler.NewCol(TargetTimeoutCol, e.Timeout),
handler.NewCol(TargetInterruptOnErrorCol, e.InterruptOnError),
handler.NewCol(TargetSigningKey, e.SigningKey),
handler.NewCol(TargetPayloadType, e.PayloadType),
},
), nil
}
@@ -140,6 +144,9 @@ func (p *targetProjection) reduceTargetChanged(event eventstore.Event) (*handler
if e.SigningKey != nil {
values = append(values, handler.NewCol(TargetSigningKey, e.SigningKey))
}
if e.PayloadType != target_domain.PayloadTypeUnspecified {
values = append(values, handler.NewCol(TargetPayloadType, e.PayloadType))
}
return handler.NewUpdateStatement(
e,
values,
+6 -4
View File
@@ -29,7 +29,7 @@ func TestTargetProjection_reduces(t *testing.T) {
testEvent(
target.AddedEventType,
target.AggregateType,
[]byte(`{"name": "name", "targetType":0, "endpoint":"https://example.com", "timeout": 3000000000, "async": true, "interruptOnError": true, "signingKey": { "cryptoType": 0, "algorithm": "RSA-265", "keyId": "key-id" }}`),
[]byte(`{"name": "name", "targetType":0, "endpoint":"https://example.com", "timeout": 3000000000, "async": true, "interruptOnError": true, "signingKey": { "cryptoType": 0, "algorithm": "RSA-265", "keyId": "key-id" }, "payloadType": 1}`),
),
eventstore.GenericEventMapper[target.AddedEvent],
),
@@ -41,7 +41,7 @@ func TestTargetProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO projections.targets2 (instance_id, resource_owner, id, creation_date, change_date, sequence, name, endpoint, target_type, timeout, interrupt_on_error, signing_key) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)",
expectedStmt: "INSERT INTO projections.targets2 (instance_id, resource_owner, id, creation_date, change_date, sequence, name, endpoint, target_type, timeout, interrupt_on_error, signing_key, payload_type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)",
expectedArgs: []interface{}{
"instance-id",
"ro-id",
@@ -55,6 +55,7 @@ func TestTargetProjection_reduces(t *testing.T) {
3 * time.Second,
true,
anyArg{},
target_domain.PayloadTypeJSON,
},
},
},
@@ -68,7 +69,7 @@ func TestTargetProjection_reduces(t *testing.T) {
testEvent(
target.ChangedEventType,
target.AggregateType,
[]byte(`{"name": "name2", "targetType":0, "endpoint":"https://example.com", "timeout": 3000000000, "async": true, "interruptOnError": true, "signingKey": { "cryptoType": 0, "algorithm": "RSA-265", "keyId": "key-id" }}`),
[]byte(`{"name": "name2", "targetType":0, "endpoint":"https://example.com", "timeout": 3000000000, "async": true, "interruptOnError": true, "signingKey": { "cryptoType": 0, "algorithm": "RSA-265", "keyId": "key-id" }, "payloadType": 1}`),
),
eventstore.GenericEventMapper[target.ChangedEvent],
),
@@ -80,7 +81,7 @@ func TestTargetProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.targets2 SET (change_date, sequence, resource_owner, name, target_type, endpoint, timeout, interrupt_on_error, signing_key) = ($1, $2, $3, $4, $5, $6, $7, $8, $9) WHERE (instance_id = $10) AND (id = $11)",
expectedStmt: "UPDATE projections.targets2 SET (change_date, sequence, resource_owner, name, target_type, endpoint, timeout, interrupt_on_error, signing_key, payload_type) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) WHERE (instance_id = $11) AND (id = $12)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
@@ -91,6 +92,7 @@ func TestTargetProjection_reduces(t *testing.T) {
3 * time.Second,
true,
anyArg{},
target_domain.PayloadTypeJSON,
"instance-id",
"agg-id",
},
+9
View File
@@ -65,6 +65,10 @@ var (
name: projection.TargetSigningKey,
table: targetTable,
}
TargetColumnPayloadType = Column{
name: projection.TargetPayloadType,
table: targetTable,
}
)
type Targets struct {
@@ -86,6 +90,7 @@ type Target struct {
InterruptOnError bool
signingKey *crypto.CryptoValue
SigningKey string
PayloadType target_domain.PayloadType
}
func (t *Target) decryptSigningKey(alg crypto.EncryptionAlgorithm) error {
@@ -166,6 +171,7 @@ func prepareTargetsQuery() (sq.SelectBuilder, func(rows *sql.Rows) (*Targets, er
TargetColumnURL.identifier(),
TargetColumnInterruptOnError.identifier(),
TargetColumnSigningKey.identifier(),
TargetColumnPayloadType.identifier(),
countColumn.identifier(),
).From(targetTable.identifier()).
PlaceholderFormat(sq.Dollar),
@@ -185,6 +191,7 @@ func prepareTargetsQuery() (sq.SelectBuilder, func(rows *sql.Rows) (*Targets, er
&target.Endpoint,
&target.InterruptOnError,
&target.signingKey,
&target.PayloadType,
&count,
)
if err != nil {
@@ -218,6 +225,7 @@ func prepareTargetQuery() (sq.SelectBuilder, func(row *sql.Row) (*Target, error)
TargetColumnURL.identifier(),
TargetColumnInterruptOnError.identifier(),
TargetColumnSigningKey.identifier(),
TargetColumnPayloadType.identifier(),
).From(targetTable.identifier()).
PlaceholderFormat(sq.Dollar),
func(row *sql.Row) (*Target, error) {
@@ -233,6 +241,7 @@ func prepareTargetQuery() (sq.SelectBuilder, func(row *sql.Row) (*Target, error)
&target.Endpoint,
&target.InterruptOnError,
&target.signingKey,
&target.PayloadType,
)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
+15 -1
View File
@@ -26,6 +26,7 @@ var (
` projections.targets2.endpoint,` +
` projections.targets2.interrupt_on_error,` +
` projections.targets2.signing_key,` +
` projections.targets2.payload_type,` +
` COUNT(*) OVER ()` +
` FROM projections.targets2`
prepareTargetsCols = []string{
@@ -39,6 +40,7 @@ var (
"endpoint",
"interrupt_on_error",
"signing_key",
"payload_type",
"count",
}
@@ -51,7 +53,8 @@ var (
` projections.targets2.timeout,` +
` projections.targets2.endpoint,` +
` projections.targets2.interrupt_on_error,` +
` projections.targets2.signing_key` +
` projections.targets2.signing_key,` +
` projections.targets2.payload_type` +
` FROM projections.targets2`
prepareTargetCols = []string{
"id",
@@ -64,6 +67,7 @@ var (
"endpoint",
"interrupt_on_error",
"signing_key",
"payload_type",
}
)
@@ -114,6 +118,7 @@ func Test_TargetPrepares(t *testing.T) {
KeyID: "encKey",
Crypted: []byte("crypted"),
},
target_domain.PayloadTypeJSON,
},
},
),
@@ -141,6 +146,7 @@ func Test_TargetPrepares(t *testing.T) {
KeyID: "encKey",
Crypted: []byte("crypted"),
},
PayloadType: target_domain.PayloadTypeJSON,
},
},
},
@@ -169,6 +175,7 @@ func Test_TargetPrepares(t *testing.T) {
KeyID: "encKey",
Crypted: []byte("crypted"),
},
target_domain.PayloadTypeJSON,
},
{
"id-2",
@@ -186,6 +193,7 @@ func Test_TargetPrepares(t *testing.T) {
KeyID: "encKey",
Crypted: []byte("crypted"),
},
target_domain.PayloadTypeJWT,
},
{
"id-3",
@@ -203,6 +211,7 @@ func Test_TargetPrepares(t *testing.T) {
KeyID: "encKey",
Crypted: []byte("crypted"),
},
target_domain.PayloadTypeJWE,
},
},
),
@@ -230,6 +239,7 @@ func Test_TargetPrepares(t *testing.T) {
KeyID: "encKey",
Crypted: []byte("crypted"),
},
PayloadType: target_domain.PayloadTypeJSON,
},
{
ObjectDetails: domain.ObjectDetails{
@@ -249,6 +259,7 @@ func Test_TargetPrepares(t *testing.T) {
KeyID: "encKey",
Crypted: []byte("crypted"),
},
PayloadType: target_domain.PayloadTypeJWT,
},
{
ObjectDetails: domain.ObjectDetails{
@@ -268,6 +279,7 @@ func Test_TargetPrepares(t *testing.T) {
KeyID: "encKey",
Crypted: []byte("crypted"),
},
PayloadType: target_domain.PayloadTypeJWE,
},
},
},
@@ -330,6 +342,7 @@ func Test_TargetPrepares(t *testing.T) {
KeyID: "encKey",
Crypted: []byte("crypted"),
},
target_domain.PayloadTypeJSON,
},
),
},
@@ -351,6 +364,7 @@ func Test_TargetPrepares(t *testing.T) {
KeyID: "encKey",
Crypted: []byte("crypted"),
},
PayloadType: target_domain.PayloadTypeJSON,
},
},
{