feat: action v2 signing (#8779)

# Which Problems Are Solved

The action v2 messages were didn't contain anything providing security
for the sent content.

# How the Problems Are Solved

Each Target now has a SigningKey, which can also be newly generated
through the API and returned at creation and through the Get-Endpoints.
There is now a HTTP header "Zitadel-Signature", which is generated with
the SigningKey and Payload, and also contains a timestamp to check with
a tolerance if the message took to long to sent.

# Additional Changes

The functionality to create and check the signature is provided in the
pkg/actions package, and can be reused in the SDK.

# Additional Context

Closes #7924

---------

Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
Stefan Benz
2024-11-28 11:06:52 +01:00
committed by GitHub
parent 8537805ea5
commit 7caa43ab23
37 changed files with 745 additions and 122 deletions

View File

@@ -9,6 +9,7 @@ import (
sq "github.com/Masterminds/squirrel"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query/projection"
"github.com/zitadel/zitadel/internal/zerrors"
@@ -59,6 +60,10 @@ var (
name: projection.TargetInterruptOnErrorCol,
table: targetTable,
}
TargetColumnSigningKey = Column{
name: projection.TargetSigningKey,
table: targetTable,
}
)
type Targets struct {
@@ -78,6 +83,20 @@ type Target struct {
Endpoint string
Timeout time.Duration
InterruptOnError bool
signingKey *crypto.CryptoValue
SigningKey string
}
func (t *Target) decryptSigningKey(alg crypto.EncryptionAlgorithm) error {
if t.signingKey == nil {
return nil
}
keyValue, err := crypto.DecryptString(t.signingKey, alg)
if err != nil {
return zerrors.ThrowInternal(err, "QUERY-bxevy3YXwy", "Errors.Internal")
}
t.SigningKey = keyValue
return nil
}
type TargetSearchQueries struct {
@@ -93,21 +112,37 @@ func (q *TargetSearchQueries) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
return query
}
func (q *Queries) SearchTargets(ctx context.Context, queries *TargetSearchQueries) (targets *Targets, err error) {
func (q *Queries) SearchTargets(ctx context.Context, queries *TargetSearchQueries) (*Targets, error) {
eq := sq.Eq{
TargetColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
}
query, scan := prepareTargetsQuery(ctx, q.client)
return genericRowsQueryWithState[*Targets](ctx, q.client, targetTable, combineToWhereStmt(query, queries.toQuery, eq), scan)
targets, err := genericRowsQueryWithState[*Targets](ctx, q.client, targetTable, combineToWhereStmt(query, queries.toQuery, eq), scan)
if err != nil {
return nil, err
}
for i := range targets.Targets {
if err := targets.Targets[i].decryptSigningKey(q.targetEncryptionAlgorithm); err != nil {
return nil, err
}
}
return targets, nil
}
func (q *Queries) GetTargetByID(ctx context.Context, id string) (target *Target, err error) {
func (q *Queries) GetTargetByID(ctx context.Context, id string) (*Target, error) {
eq := sq.Eq{
TargetColumnID.identifier(): id,
TargetColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
}
query, scan := prepareTargetQuery(ctx, q.client)
return genericRowQuery[*Target](ctx, q.client, query.Where(eq), scan)
target, err := genericRowQuery[*Target](ctx, q.client, query.Where(eq), scan)
if err != nil {
return nil, err
}
if err := target.decryptSigningKey(q.targetEncryptionAlgorithm); err != nil {
return nil, err
}
return target, nil
}
func NewTargetNameSearchQuery(method TextComparison, value string) (SearchQuery, error) {
@@ -129,6 +164,7 @@ func prepareTargetsQuery(context.Context, prepareDatabase) (sq.SelectBuilder, fu
TargetColumnTimeout.identifier(),
TargetColumnURL.identifier(),
TargetColumnInterruptOnError.identifier(),
TargetColumnSigningKey.identifier(),
countColumn.identifier(),
).From(targetTable.identifier()).
PlaceholderFormat(sq.Dollar),
@@ -147,6 +183,7 @@ func prepareTargetsQuery(context.Context, prepareDatabase) (sq.SelectBuilder, fu
&target.Timeout,
&target.Endpoint,
&target.InterruptOnError,
&target.signingKey,
&count,
)
if err != nil {
@@ -179,6 +216,7 @@ func prepareTargetQuery(context.Context, prepareDatabase) (sq.SelectBuilder, fun
TargetColumnTimeout.identifier(),
TargetColumnURL.identifier(),
TargetColumnInterruptOnError.identifier(),
TargetColumnSigningKey.identifier(),
).From(targetTable.identifier()).
PlaceholderFormat(sq.Dollar),
func(row *sql.Row) (*Target, error) {
@@ -193,6 +231,7 @@ func prepareTargetQuery(context.Context, prepareDatabase) (sq.SelectBuilder, fun
&target.Timeout,
&target.Endpoint,
&target.InterruptOnError,
&target.signingKey,
)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {