diff --git a/internal/api/grpc/management/user.go b/internal/api/grpc/management/user.go index eae4f40d35..6166bf4a5b 100644 --- a/internal/api/grpc/management/user.go +++ b/internal/api/grpc/management/user.go @@ -774,13 +774,22 @@ func (s *Server) ListMachineKeys(ctx context.Context, req *mgmt_pb.ListMachineKe func (s *Server) AddMachineKey(ctx context.Context, req *mgmt_pb.AddMachineKeyRequest) (*mgmt_pb.AddMachineKeyResponse, error) { machineKey := AddMachineKeyRequestToCommand(req, authz.GetCtxData(ctx).OrgID) + // If there is no pubkey supplied, then AddUserMachineKey will generate a new one + pubkeySupplied := len(machineKey.PublicKey) > 0 details, err := s.command.AddUserMachineKey(ctx, machineKey) if err != nil { return nil, err } - keyDetails, err := machineKey.Detail() - if err != nil { - return nil, err + + // Return key details only if the pubkey wasn't supplied, otherwise the user already has + // private key locally + var keyDetails []byte + if !pubkeySupplied { + var err error + keyDetails, err = machineKey.Detail() + if err != nil { + return nil, err + } } return &mgmt_pb.AddMachineKeyResponse{ KeyId: machineKey.KeyID, diff --git a/internal/api/grpc/management/user_converter.go b/internal/api/grpc/management/user_converter.go index 32496b431c..511e1a4f54 100644 --- a/internal/api/grpc/management/user_converter.go +++ b/internal/api/grpc/management/user_converter.go @@ -237,6 +237,7 @@ func AddMachineKeyRequestToCommand(req *mgmt_pb.AddMachineKeyRequest, resourceOw }, ExpirationDate: expDate, Type: authn.KeyTypeToDomain(req.Type), + PublicKey: req.PublicKey, } } diff --git a/internal/command/user_machine_key.go b/internal/command/user_machine_key.go index d092cc54ef..d6abdd9545 100644 --- a/internal/command/user_machine_key.go +++ b/internal/command/user_machine_key.go @@ -5,6 +5,7 @@ import ( "time" "github.com/zitadel/zitadel/internal/command/preparation" + "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore/v1/models" @@ -78,6 +79,12 @@ func (key *MachineKey) valid() (err error) { if err := key.content(); err != nil { return err } + // If a key is supplied, it should be a valid public key + if len(key.PublicKey) > 0 { + if _, err := crypto.BytesToPublicKey(key.PublicKey); err != nil { + return zerrors.ThrowInvalidArgument(nil, "COMMAND-5F3h1", "Errors.User.Machine.Key.Invalid") + } + } key.ExpirationDate, err = domain.ValidateExpirationDate(key.ExpirationDate) return err } diff --git a/internal/command/user_machine_key_test.go b/internal/command/user_machine_key_test.go index 7e4a0f069a..9e50f81e7c 100644 --- a/internal/command/user_machine_key_test.go +++ b/internal/command/user_machine_key_test.go @@ -18,6 +18,16 @@ import ( "github.com/zitadel/zitadel/internal/zerrors" ) +const fakePubkey = `-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp4qNBuUu/HekF2E5bOtA +oEL76zS0NQdZL3ByEJ3hZplJhE30ITPIOLW3+uaMMM+obl/LLapwG2vdhvutQtx/ +FOLJmXysbG3RL9zjXDBT5IE+nGFC7ctsi5FGbHQbAm45E3HHCSk7gfmTy9hxyk1K +GsyU8BDeOWasJO6aeXqpOnRM8vw/fY+6mHVC9CxcIroSfrIabFGe/mP6qpBGeFSn +APymBc/8lca4JaPv2/u/rBhnaAHZiUuCS1+MonWelOb+MSfq48VgtpiaYIVY9szI +esorA6EJ9pO17ROEUpX5wP5Oir+yGJU27jSvLCjvK6fOFX+OwUM9L8047JKoo+Nf +PwIDAQAB +-----END PUBLIC KEY-----` + func TestCommands_AddMachineKey(t *testing.T) { type fields struct { eventstore *eventstore.Eventstore @@ -145,7 +155,7 @@ func TestCommands_AddMachineKey(t *testing.T) { "key1", domain.AuthNKeyTypeJSON, time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC), - []byte("public"), + []byte(fakePubkey), ), ), ), @@ -161,14 +171,14 @@ func TestCommands_AddMachineKey(t *testing.T) { }, Type: domain.AuthNKeyTypeJSON, ExpirationDate: time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC), - PublicKey: []byte("public"), + PublicKey: []byte(fakePubkey), }, }, res{ want: &domain.ObjectDetails{ ResourceOwner: "org1", }, - key: true, + key: false, }, }, { @@ -194,7 +204,7 @@ func TestCommands_AddMachineKey(t *testing.T) { "key1", domain.AuthNKeyTypeJSON, time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC), - []byte("public"), + []byte(fakePubkey), ), ), ), @@ -210,14 +220,35 @@ func TestCommands_AddMachineKey(t *testing.T) { KeyID: "key1", Type: domain.AuthNKeyTypeJSON, ExpirationDate: time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC), - PublicKey: []byte("public"), + PublicKey: []byte(fakePubkey), }, }, res{ want: &domain.ObjectDetails{ ResourceOwner: "org1", }, - key: true, + key: false, + }, + }, + { + "key added with invalid public key", + fields{ + eventstore: eventstoreExpect(t), + }, + args{ + ctx: context.Background(), + key: &MachineKey{ + ObjectRoot: models.ObjectRoot{ + AggregateID: "user1", + ResourceOwner: "org1", + }, + KeyID: "key1", + Type: domain.AuthNKeyTypeJSON, + PublicKey: []byte("incorrect"), + }, + }, + res{ + err: zerrors.IsErrorInvalidArgument, }, }, } @@ -237,9 +268,8 @@ func TestCommands_AddMachineKey(t *testing.T) { } if tt.res.err == nil { assert.Equal(t, tt.res.want, got) - if tt.res.key { - assert.NotEqual(t, "", tt.args.key.PrivateKey) - } + receivedKey := len(tt.args.key.PrivateKey) > 0 + assert.Equal(t, tt.res.key, receivedKey) } }) } diff --git a/internal/static/i18n/bg.yaml b/internal/static/i18n/bg.yaml index 899fa9f0ce..5fac59fb9d 100644 --- a/internal/static/i18n/bg.yaml +++ b/internal/static/i18n/bg.yaml @@ -111,6 +111,7 @@ Errors: Key: NotFound: Машинният ключ не е намерен AlreadyExisting: Машинният ключ вече съществува + Invalid: Публичният ключ не е валиден RSA публичен ключ във формат PKIX с PEM кодиране Secret: NotExisting: Тайната не съществува Invalid: Тайната е невалидна diff --git a/internal/static/i18n/cs.yaml b/internal/static/i18n/cs.yaml index b1e83a6d18..d12a1b451e 100644 --- a/internal/static/i18n/cs.yaml +++ b/internal/static/i18n/cs.yaml @@ -109,6 +109,7 @@ Errors: Key: NotFound: Klíč stroje nenalezen AlreadyExisting: Klíč stroje již existuje + Invalid: Veřejný klíč není platný veřejný klíč RSA ve formátu PKIX s kódováním PEM Secret: NotExisting: Tajemství neexistuje Invalid: Tajemství je neplatné diff --git a/internal/static/i18n/de.yaml b/internal/static/i18n/de.yaml index 12bd70bc40..408a862b45 100644 --- a/internal/static/i18n/de.yaml +++ b/internal/static/i18n/de.yaml @@ -109,6 +109,7 @@ Errors: Key: NotFound: Maschinen Schlüssel nicht gefunden AlreadyExisting: Machine Schlüssel exisiert bereits + Invalid: Der öffentliche Schlüssel ist kein gültiger öffentlicher RSA-Schlüssel im PKIX-Format mit PEM-Kodierung Secret: NotExisting: Secret existiert nicht Invalid: Secret ist ungültig diff --git a/internal/static/i18n/en.yaml b/internal/static/i18n/en.yaml index 511248da6e..937820fdc9 100644 --- a/internal/static/i18n/en.yaml +++ b/internal/static/i18n/en.yaml @@ -109,6 +109,7 @@ Errors: Key: NotFound: Machine key not found AlreadyExisting: Machine key already existing + Invalid: Public key is not a valid RSA public key in PKIX format with PEM encoding Secret: NotExisting: Secret doesn't exist Invalid: Secret is invalid diff --git a/internal/static/i18n/es.yaml b/internal/static/i18n/es.yaml index 60115ca865..102d99f086 100644 --- a/internal/static/i18n/es.yaml +++ b/internal/static/i18n/es.yaml @@ -109,6 +109,7 @@ Errors: Key: NotFound: Clave de máquina no encontrada AlreadyExisting: La clave de máquina ya existe + Invalid: La clave pública no es una clave pública RSA válida en formato PKIX con codificación PEM Secret: NotExisting: El secreto no existe Invalid: El secret no es válido diff --git a/internal/static/i18n/fr.yaml b/internal/static/i18n/fr.yaml index 8855ce5c46..631d5be478 100644 --- a/internal/static/i18n/fr.yaml +++ b/internal/static/i18n/fr.yaml @@ -109,6 +109,7 @@ Errors: Key: NotFound: Clé de la machine non trouvée AlreadyExisting: Clé de la machine déjà existante + Invalid: La clé publique n'est pas une clé publique RSA valide au format PKIX avec encodage PEM Secret: NotExisting: Secret n'existe pas Invalid: Secret n'est pas valide diff --git a/internal/static/i18n/it.yaml b/internal/static/i18n/it.yaml index 7005eaf311..064f3929ef 100644 --- a/internal/static/i18n/it.yaml +++ b/internal/static/i18n/it.yaml @@ -109,6 +109,7 @@ Errors: Key: NotFound: Chiave macchina non trovato AlreadyExisting: Chiave macchina già esistente + Invalid: La chiave pubblica non è una chiave pubblica RSA valida in formato PKIX con codifica PEM Secret: NotExisting: Secret non esiste Invalid: Secret non è valido diff --git a/internal/static/i18n/ja.yaml b/internal/static/i18n/ja.yaml index 6eed3af3e7..4ef7b8f985 100644 --- a/internal/static/i18n/ja.yaml +++ b/internal/static/i18n/ja.yaml @@ -102,6 +102,7 @@ Errors: Key: NotFound: マシーンキーが見つかりません AlreadyExisting: すでに存在しているマシーンキーです + Invalid: 公開キーは、PEM エンコードを使用した PKIX 形式の有効な RSA 公開キーではありません Secret: NotExisting: シークレットは存在しません Invalid: 無効なシークレットです diff --git a/internal/static/i18n/mk.yaml b/internal/static/i18n/mk.yaml index f964ce2d71..deab6d2e37 100644 --- a/internal/static/i18n/mk.yaml +++ b/internal/static/i18n/mk.yaml @@ -109,6 +109,7 @@ Errors: Key: NotFound: Machine key не е пронајден AlreadyExisting: Machine key веќе постои + Invalid: Јавниот клуч не е валиден јавен клуч RSA во формат PKIX со PEM кодирање Secret: NotExisting: Тајната не постои Invalid: Тајната е невалидна diff --git a/internal/static/i18n/nl.yaml b/internal/static/i18n/nl.yaml index 42ca88acf9..a5ff77d710 100644 --- a/internal/static/i18n/nl.yaml +++ b/internal/static/i18n/nl.yaml @@ -108,6 +108,7 @@ Errors: Key: NotFound: Machine sleutel niet gevonden AlreadyExisting: Machine sleutel al bestaand + Invalid: De openbare sleutel is geen geldige openbare RSA-sleutel in PKIX-indeling met PEM-codering Secret: NotExisting: Geheim bestaat niet Invalid: Geheim is ongeldig diff --git a/internal/static/i18n/pl.yaml b/internal/static/i18n/pl.yaml index 33e7b77342..8040183629 100644 --- a/internal/static/i18n/pl.yaml +++ b/internal/static/i18n/pl.yaml @@ -109,6 +109,7 @@ Errors: Key: NotFound: Klucz maszyny nie znaleziony AlreadyExisting: Klucz maszyny już istnieje + Invalid: Klucz publiczny nie jest prawidłowym kluczem publicznym RSA w formacie PKIX z kodowaniem PEM Secret: NotExisting: Sekret nie istnieje Invalid: Sekret jest nieprawidłowy diff --git a/internal/static/i18n/pt.yaml b/internal/static/i18n/pt.yaml index 89dab225c9..3bcc981d31 100644 --- a/internal/static/i18n/pt.yaml +++ b/internal/static/i18n/pt.yaml @@ -109,6 +109,7 @@ Errors: Key: NotFound: Chave de máquina não encontrada AlreadyExisting: Chave de máquina já existe + Invalid: A chave pública não é uma chave pública RSA válida no formato PKIX com codificação PEM Secret: NotExisting: Segredo não existe Invalid: Segredo é inválido diff --git a/internal/static/i18n/ru.yaml b/internal/static/i18n/ru.yaml index 3bb3cd64dc..5d4182d253 100644 --- a/internal/static/i18n/ru.yaml +++ b/internal/static/i18n/ru.yaml @@ -110,6 +110,7 @@ Errors: Key: NotFound: Машинный ключ не найден AlreadyExisting: Машинный ключ уже существует + Invalid: Открытый ключ не является допустимым открытым ключом RSA в формате PKIX с кодировкой PEM Secret: NotExisting: Ключ не существует Invalid: Ключ недействителен diff --git a/internal/static/i18n/zh.yaml b/internal/static/i18n/zh.yaml index ee7bfec97c..4ec0e24cc1 100644 --- a/internal/static/i18n/zh.yaml +++ b/internal/static/i18n/zh.yaml @@ -109,6 +109,7 @@ Errors: Key: NotFound: 未找到机器密钥 AlreadyExisting: 已有的机器钥匙 + Invalid: 公钥不是采用 PEM 编码的 PKIX 格式的有效 RSA 公钥 Secret: NotExisting: 秘密并不存在 Invalid: 秘密是无效的 diff --git a/proto/zitadel/management.proto b/proto/zitadel/management.proto index 0b1a3b6a10..5a0f241ff3 100644 --- a/proto/zitadel/management.proto +++ b/proto/zitadel/management.proto @@ -1734,7 +1734,7 @@ service ManagementService { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { summary: "Create Key for machine user"; - description: "A new key is generated and will be returned in the response. Make sure to store the returned key. Machine keys are used to authenticate with jwt profile." + description: "If a public key is not supplied, a new key is generated and will be returned in the response. Make sure to store the returned key. If an RSA public key is supplied, the private key is omitted from the response. Machine keys are used to authenticate with jwt profile." tags: "Users"; tags: "User Machine"; responses: { @@ -8504,6 +8504,12 @@ message AddMachineKeyRequest { description: "The date the key will expire and no logins will be possible"; } ]; + bytes public_key = 4 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1...\""; + description: "Optionally provide a public key of your own generated RSA private key."; + } + ]; } message AddMachineKeyResponse {