Skip to content

Commit

Permalink
feat: allow using a local RSA key for machine keys (#7671)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
aDogCalledSpot and livio-a committed Apr 23, 2024
1 parent df50c38 commit e46dd12
Show file tree
Hide file tree
Showing 19 changed files with 80 additions and 13 deletions.
15 changes: 12 additions & 3 deletions internal/api/grpc/management/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions internal/api/grpc/management/user_converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ func AddMachineKeyRequestToCommand(req *mgmt_pb.AddMachineKeyRequest, resourceOw
},
ExpirationDate: expDate,
Type: authn.KeyTypeToDomain(req.Type),
PublicKey: req.PublicKey,
}
}

Expand Down
7 changes: 7 additions & 0 deletions internal/command/user_machine_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}
Expand Down
48 changes: 39 additions & 9 deletions internal/command/user_machine_key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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),
),
),
),
Expand All @@ -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,
},
},
{
Expand All @@ -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),
),
),
),
Expand All @@ -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,
},
},
}
Expand All @@ -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)
}
})
}
Expand Down
1 change: 1 addition & 0 deletions internal/static/i18n/bg.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ Errors:
Key:
NotFound: Машинният ключ не е намерен
AlreadyExisting: Машинният ключ вече съществува
Invalid: Публичният ключ не е валиден RSA публичен ключ във формат PKIX с PEM кодиране
Secret:
NotExisting: Тайната не съществува
Invalid: Тайната е невалидна
Expand Down
1 change: 1 addition & 0 deletions internal/static/i18n/cs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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é
Expand Down
1 change: 1 addition & 0 deletions internal/static/i18n/de.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions internal/static/i18n/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions internal/static/i18n/es.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions internal/static/i18n/fr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions internal/static/i18n/it.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions internal/static/i18n/ja.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ Errors:
Key:
NotFound: マシーンキーが見つかりません
AlreadyExisting: すでに存在しているマシーンキーです
Invalid: 公開キーは、PEM エンコードを使用した PKIX 形式の有効な RSA 公開キーではありません
Secret:
NotExisting: シークレットは存在しません
Invalid: 無効なシークレットです
Expand Down
1 change: 1 addition & 0 deletions internal/static/i18n/mk.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ Errors:
Key:
NotFound: Machine key не е пронајден
AlreadyExisting: Machine key веќе постои
Invalid: Јавниот клуч не е валиден јавен клуч RSA во формат PKIX со PEM кодирање
Secret:
NotExisting: Тајната не постои
Invalid: Тајната е невалидна
Expand Down
1 change: 1 addition & 0 deletions internal/static/i18n/nl.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions internal/static/i18n/pl.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions internal/static/i18n/pt.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions internal/static/i18n/ru.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ Errors:
Key:
NotFound: Машинный ключ не найден
AlreadyExisting: Машинный ключ уже существует
Invalid: Открытый ключ не является допустимым открытым ключом RSA в формате PKIX с кодировкой PEM
Secret:
NotExisting: Ключ не существует
Invalid: Ключ недействителен
Expand Down
1 change: 1 addition & 0 deletions internal/static/i18n/zh.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ Errors:
Key:
NotFound: 未找到机器密钥
AlreadyExisting: 已有的机器钥匙
Invalid: 公钥不是采用 PEM 编码的 PKIX 格式的有效 RSA 公钥
Secret:
NotExisting: 秘密并不存在
Invalid: 秘密是无效的
Expand Down
8 changes: 7 additions & 1 deletion proto/zitadel/management.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit e46dd12

Please sign in to comment.