feat(crypto): use passwap for machine and app secrets (#7657)

* feat(crypto): use passwap for machine and app secrets

* fix command package tests

* add hash generator command test

* naming convention, fix query tests

* rename PasswordHasher and cleanup start commands

* add reducer tests

* fix intergration tests, cleanup old config

* add app secret unit tests

* solve setup panics

* fix push of updated events

* add missing event translations

* update documentation

* solve linter errors

* remove nolint:SA1019 as it doesn't seem to help anyway

* add nolint to deprecated filter usage

* update users migration version

* remove unused ClientSecret from APIConfigChangedEvent

---------

Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
Tim Möhlmann
2024-04-05 12:35:49 +03:00
committed by GitHub
parent 5931fb8f28
commit 2089992d75
135 changed files with 2407 additions and 1779 deletions

View File

@@ -2,9 +2,13 @@ package command
import (
"context"
"io"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zitadel/passwap"
"github.com/zitadel/passwap/bcrypt"
"github.com/zitadel/zitadel/internal/command/preparation"
"github.com/zitadel/zitadel/internal/crypto"
@@ -114,7 +118,7 @@ func TestAddAPIConfig(t *testing.T) {
project.NewAPIConfigAddedEvent(ctx, &agg.Aggregate,
"appID",
"clientID@project",
nil,
"",
domain.APIAuthMethodTypePrivateKeyJWT,
),
},
@@ -137,7 +141,6 @@ func TestAddAPIConfig(t *testing.T) {
},
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
},
nil,
), tt.args.filter, tt.want)
})
}
@@ -145,14 +148,13 @@ func TestAddAPIConfig(t *testing.T) {
func TestCommandSide_AddAPIApplication(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(t *testing.T) *eventstore.Eventstore
idGenerator id.Generator
}
type args struct {
ctx context.Context
apiApp *domain.APIApp
resourceOwner string
secretGenerator crypto.Generator
ctx context.Context
apiApp *domain.APIApp
resourceOwner string
}
type res struct {
want *domain.APIApp
@@ -167,9 +169,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
{
name: "no aggregate id, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@@ -183,8 +183,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
{
name: "project not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(),
),
},
@@ -206,8 +205,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
{
name: "invalid app, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(),
@@ -236,8 +234,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
{
name: "create api app basic, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(),
@@ -256,12 +253,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"client1@project",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
"secret",
domain.APIAuthMethodTypeBasic),
),
),
@@ -276,8 +268,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
AppName: "app",
AuthMethodType: domain.APIAuthMethodTypeBasic,
},
resourceOwner: "org1",
secretGenerator: GetMockSecretGenerator(t),
resourceOwner: "org1",
},
res: res{
want: &domain.APIApp{
@@ -288,7 +279,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
AppID: "app1",
AppName: "app",
ClientID: "client1@project",
ClientSecretString: "a",
ClientSecretString: "secret",
AuthMethodType: domain.APIAuthMethodTypeBasic,
State: domain.AppStateActive,
},
@@ -297,8 +288,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
{
name: "create api app jwt, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(),
@@ -317,7 +307,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"client1@project",
nil,
"",
domain.APIAuthMethodTypePrivateKeyJWT),
),
),
@@ -352,10 +342,14 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
idGenerator: tt.fields.idGenerator,
eventstore: tt.fields.eventstore(t),
idGenerator: tt.fields.idGenerator,
newHashedSecret: mockHashedSecret("secret"),
defaultSecretGenerators: &SecretGenerators{
ClientSecret: emptyConfig,
},
}
got, err := r.AddAPIApplication(tt.args.ctx, tt.args.apiApp, tt.args.resourceOwner, tt.args.secretGenerator)
got, err := r.AddAPIApplication(tt.args.ctx, tt.args.apiApp, tt.args.resourceOwner)
if tt.res.err == nil {
assert.NoError(t, err)
}
@@ -371,7 +365,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
func TestCommandSide_ChangeAPIApplication(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(t *testing.T) *eventstore.Eventstore
}
type args struct {
ctx context.Context
@@ -391,9 +385,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) {
{
name: "missing appid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@@ -414,9 +406,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) {
{
name: "missing aggregateid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@@ -437,8 +427,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) {
{
name: "app not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(),
),
},
@@ -460,8 +449,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) {
{
name: "no changes, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewApplicationAddedEvent(context.Background(),
@@ -475,7 +463,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) {
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"client1@project",
nil,
"",
domain.APIAuthMethodTypePrivateKeyJWT),
),
),
@@ -500,8 +488,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) {
{
name: "change api app, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewApplicationAddedEvent(context.Background(),
@@ -515,12 +502,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) {
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"client1@project",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
"secret",
domain.APIAuthMethodTypeBasic),
),
),
@@ -563,7 +545,11 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
newHashedSecret: mockHashedSecret("secret"),
defaultSecretGenerators: &SecretGenerators{
ClientSecret: emptyConfig,
},
}
got, err := r.ChangeAPIApplication(tt.args.ctx, tt.args.apiApp, tt.args.resourceOwner)
if tt.res.err == nil {
@@ -581,14 +567,13 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) {
func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
}
type args struct {
ctx context.Context
appID string
projectID string
resourceOwner string
secretGenerator crypto.Generator
ctx context.Context
appID string
projectID string
resourceOwner string
}
type res struct {
want *domain.APIApp
@@ -603,9 +588,7 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) {
{
name: "no projectid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@@ -619,9 +602,7 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) {
{
name: "no appid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@@ -636,8 +617,7 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) {
{
name: "app not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(),
),
},
@@ -654,8 +634,7 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) {
{
name: "change secret, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewApplicationAddedEvent(context.Background(),
@@ -669,12 +648,7 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) {
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"client1@project",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
"secret",
domain.APIAuthMethodTypeBasic),
),
),
@@ -682,22 +656,16 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) {
project.NewAPIConfigSecretChangedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
"secret",
),
),
),
},
args: args{
ctx: context.Background(),
projectID: "project1",
appID: "app1",
resourceOwner: "org1",
secretGenerator: GetMockSecretGenerator(t),
ctx: context.Background(),
projectID: "project1",
appID: "app1",
resourceOwner: "org1",
},
res: res{
want: &domain.APIApp{
@@ -708,7 +676,7 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) {
AppID: "app1",
AppName: "app",
ClientID: "client1@project",
ClientSecretString: "a",
ClientSecretString: "secret",
AuthMethodType: domain.APIAuthMethodTypeBasic,
State: domain.AppStateActive,
},
@@ -718,9 +686,13 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
newHashedSecret: mockHashedSecret("secret"),
defaultSecretGenerators: &SecretGenerators{
ClientSecret: emptyConfig,
},
}
got, err := r.ChangeAPIApplicationSecret(tt.args.ctx, tt.args.projectID, tt.args.appID, tt.args.resourceOwner, tt.args.secretGenerator)
got, err := r.ChangeAPIApplicationSecret(tt.args.ctx, tt.args.projectID, tt.args.appID, tt.args.resourceOwner)
if tt.res.err == nil {
assert.NoError(t, err)
}
@@ -745,3 +717,105 @@ func newAPIAppChangedEvent(ctx context.Context, appID, projectID, resourceOwner
)
return event
}
func TestCommands_VerifyAPIClientSecret(t *testing.T) {
hasher := &crypto.Hasher{
Swapper: passwap.NewSwapper(bcrypt.New(bcrypt.MinCost)),
}
hashedSecret, err := hasher.Hash("secret")
require.NoError(t, err)
agg := project.NewAggregate("projectID", "orgID")
tests := []struct {
name string
secret string
eventstore func(*testing.T) *eventstore.Eventstore
wantErr error
}{
{
name: "filter error",
eventstore: expectEventstore(
expectFilterError(io.ErrClosedPipe),
),
wantErr: io.ErrClosedPipe,
},
{
name: "app not exists",
eventstore: expectEventstore(
expectFilter(),
),
wantErr: zerrors.ThrowPreconditionFailed(nil, "COMMAND-DFnbf", "Errors.Project.App.NotExisting"),
},
{
name: "wrong app type",
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewApplicationAddedEvent(context.Background(), &agg.Aggregate, "appID", "appName"),
),
),
),
wantErr: zerrors.ThrowInvalidArgument(nil, "COMMAND-Bf3fw", "Errors.Project.App.IsNotAPI"),
},
{
name: "no secret set",
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewApplicationAddedEvent(context.Background(), &agg.Aggregate, "appID", "appName"),
),
eventFromEventPusher(
project.NewAPIConfigAddedEvent(context.Background(), &agg.Aggregate, "appID", "clientID", "", domain.APIAuthMethodTypePrivateKeyJWT),
),
),
),
wantErr: zerrors.ThrowPreconditionFailed(nil, "COMMAND-D3t5g", "Errors.Project.App.APIConfigInvalid"),
},
{
name: "check succeeded",
secret: "secret",
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewApplicationAddedEvent(context.Background(), &agg.Aggregate, "appID", "appName"),
),
eventFromEventPusher(
project.NewAPIConfigAddedEvent(context.Background(), &agg.Aggregate, "appID", "clientID", hashedSecret, domain.APIAuthMethodTypePrivateKeyJWT),
),
),
expectPush(
project.NewAPIConfigSecretCheckSucceededEvent(context.Background(), &agg.Aggregate, "appID"),
),
),
},
{
name: "check failed",
secret: "wrong!",
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewApplicationAddedEvent(context.Background(), &agg.Aggregate, "appID", "appName"),
),
eventFromEventPusher(
project.NewAPIConfigAddedEvent(context.Background(), &agg.Aggregate, "appID", "clientID", hashedSecret, domain.APIAuthMethodTypePrivateKeyJWT),
),
),
expectPush(
project.NewAPIConfigSecretCheckFailedEvent(context.Background(), &agg.Aggregate, "appID"),
),
),
wantErr: zerrors.ThrowInvalidArgument(err, "COMMAND-SADfg", "Errors.Project.App.ClientSecretInvalid"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.eventstore(t),
secretHasher: hasher,
}
err := c.VerifyAPIClientSecret(context.Background(), "projectID", "appID", tt.secret)
c.jobs.Wait()
require.ErrorIs(t, err, tt.wantErr)
})
}
}