mirror of
https://github.com/zitadel/zitadel.git
synced 2025-01-06 13:07:52 +00:00
feat: allow using a local RSA key for machine keys (#7671)
* Allow using a local RSA key for machine keys * Add check for key validity * Fix naming error * docs: provide translations of invalid key --------- Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
parent
df50c3835b
commit
e46dd121cd
@ -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,
|
||||
|
@ -237,6 +237,7 @@ func AddMachineKeyRequestToCommand(req *mgmt_pb.AddMachineKeyRequest, resourceOw
|
||||
},
|
||||
ExpirationDate: expDate,
|
||||
Type: authn.KeyTypeToDomain(req.Type),
|
||||
PublicKey: req.PublicKey,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -111,6 +111,7 @@ Errors:
|
||||
Key:
|
||||
NotFound: Машинният ключ не е намерен
|
||||
AlreadyExisting: Машинният ключ вече съществува
|
||||
Invalid: Публичният ключ не е валиден RSA публичен ключ във формат PKIX с PEM кодиране
|
||||
Secret:
|
||||
NotExisting: Тайната не съществува
|
||||
Invalid: Тайната е невалидна
|
||||
|
@ -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é
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -102,6 +102,7 @@ Errors:
|
||||
Key:
|
||||
NotFound: マシーンキーが見つかりません
|
||||
AlreadyExisting: すでに存在しているマシーンキーです
|
||||
Invalid: 公開キーは、PEM エンコードを使用した PKIX 形式の有効な RSA 公開キーではありません
|
||||
Secret:
|
||||
NotExisting: シークレットは存在しません
|
||||
Invalid: 無効なシークレットです
|
||||
|
@ -109,6 +109,7 @@ Errors:
|
||||
Key:
|
||||
NotFound: Machine key не е пронајден
|
||||
AlreadyExisting: Machine key веќе постои
|
||||
Invalid: Јавниот клуч не е валиден јавен клуч RSA во формат PKIX со PEM кодирање
|
||||
Secret:
|
||||
NotExisting: Тајната не постои
|
||||
Invalid: Тајната е невалидна
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -110,6 +110,7 @@ Errors:
|
||||
Key:
|
||||
NotFound: Машинный ключ не найден
|
||||
AlreadyExisting: Машинный ключ уже существует
|
||||
Invalid: Открытый ключ не является допустимым открытым ключом RSA в формате PKIX с кодировкой PEM
|
||||
Secret:
|
||||
NotExisting: Ключ не существует
|
||||
Invalid: Ключ недействителен
|
||||
|
@ -109,6 +109,7 @@ Errors:
|
||||
Key:
|
||||
NotFound: 未找到机器密钥
|
||||
AlreadyExisting: 已有的机器钥匙
|
||||
Invalid: 公钥不是采用 PEM 编码的 PKIX 格式的有效 RSA 公钥
|
||||
Secret:
|
||||
NotExisting: 秘密并不存在
|
||||
Invalid: 秘密是无效的
|
||||
|
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user