mirror of
https://github.com/zitadel/zitadel.git
synced 2025-01-07 23:17:41 +00:00
fix: add expiration date information to service users keys (#7497)
* feat: add ExpirationDate to MachineKey JSON detail * fix: include time in expiration date column for machine keys table * fix: show expiration date in ShowKeyDialog if available * fix: add machine key expiration date note --------- Co-authored-by: Tim Möhlmann <tim+github@zitadel.com>
This commit is contained in:
parent
30a1f4b39e
commit
dff5984f7d
@ -61,7 +61,7 @@
|
|||||||
<ng-container matColumnDef="expirationDate">
|
<ng-container matColumnDef="expirationDate">
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'USER.MACHINE.EXPIRATIONDATE' | translate }}</th>
|
<th mat-header-cell *matHeaderCellDef>{{ 'USER.MACHINE.EXPIRATIONDATE' | translate }}</th>
|
||||||
<td mat-cell *matCellDef="let key">
|
<td mat-cell *matCellDef="let key">
|
||||||
{{ key.expirationDate | timestampToDate | localizedDate: 'fromNow' }}
|
{{ key.expirationDate | timestampToDate | localizedDate: 'EEE dd. MMM YYYY, HH:mm' }}
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
@ -17,6 +17,13 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="row" *ngIf="expirationDate">
|
||||||
|
<p class="left cnsl-secondary-text">{{ 'USER.MACHINE.EXPIRATIONDATE' | translate }}</p>
|
||||||
|
<p class="right">
|
||||||
|
{{ expirationDate | localizedDate: 'EEE dd. MMM YYYY, HH:mm' }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button class="download-button" mat-stroked-button color="primary" (click)="saveFile()">Download</button>
|
<button class="download-button" mat-stroked-button color="primary" (click)="saveFile()">Download</button>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
@ -11,6 +11,7 @@ import { InfoSectionType } from '../info-section/info-section.component';
|
|||||||
})
|
})
|
||||||
export class ShowKeyDialogComponent {
|
export class ShowKeyDialogComponent {
|
||||||
public keyResponse!: AddMachineKeyResponse.AsObject | AddAppKeyResponse.AsObject;
|
public keyResponse!: AddMachineKeyResponse.AsObject | AddAppKeyResponse.AsObject;
|
||||||
|
public expirationDate: string = '';
|
||||||
public InfoSectionType: any = InfoSectionType;
|
public InfoSectionType: any = InfoSectionType;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -18,6 +19,10 @@ export class ShowKeyDialogComponent {
|
|||||||
@Inject(MAT_DIALOG_DATA) public data: any,
|
@Inject(MAT_DIALOG_DATA) public data: any,
|
||||||
) {
|
) {
|
||||||
this.keyResponse = data.key;
|
this.keyResponse = data.key;
|
||||||
|
if (this.keyResponse.keyDetails) {
|
||||||
|
const keyDetails: { expirationDate: string } = JSON.parse(atob(this.keyResponse.keyDetails.toString()));
|
||||||
|
this.expirationDate = keyDetails.expirationDate;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public saveFile(): void {
|
public saveFile(): void {
|
||||||
|
@ -4,6 +4,7 @@ sidebar_label: Service Users
|
|||||||
---
|
---
|
||||||
|
|
||||||
This is a guide on how to create service users in ZITADEL. You can read more about users [here](/concepts/structure/users.md).
|
This is a guide on how to create service users in ZITADEL. You can read more about users [here](/concepts/structure/users.md).
|
||||||
|
|
||||||
## Create a Service User
|
## Create a Service User
|
||||||
|
|
||||||
1. Navigate to Service Users
|
1. Navigate to Service Users
|
||||||
@ -32,18 +33,22 @@ In this step we will authenticate a service user and receive an access_token to
|
|||||||
|
|
||||||
### 1. Generate a private-public key pair in ZITADEL
|
### 1. Generate a private-public key pair in ZITADEL
|
||||||
|
|
||||||
Select your service user and in the section KEYS click **New**. Enter an expiration date and click **Add**. Make sure to download the json by clicking **Download**.
|
Select your service user and in the section KEYS click **New**. Enter an optional expiration date and click **Add**. Make sure to download the json by clicking **Download**.
|
||||||
|
|
||||||
|
:::note
|
||||||
|
If you specify an expiration date, note that the key will expire at midnight that day
|
||||||
|
:::
|
||||||
|
|
||||||
![Create private key](/img/console_serviceusers_new_key.gif)
|
![Create private key](/img/console_serviceusers_new_key.gif)
|
||||||
|
|
||||||
The downloaded json should look something like outlined below. The value of `key` contains the *private* key for your service account. Please make sure to keep this key securely stored and handle with care. The public key is automatically stored in ZITADEL.
|
The downloaded json should look something like outlined below. The value of `key` contains the _private_ key for your service account. Please make sure to keep this key securely stored and handle with care. The public key is automatically stored in ZITADEL.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type":"serviceaccount",
|
"type": "serviceaccount",
|
||||||
"keyId":"100509901696068329",
|
"keyId": "100509901696068329",
|
||||||
"key":"-----BEGIN RSA PRIVATE KEY----- [...] -----END RSA PRIVATE KEY-----\n",
|
"key": "-----BEGIN RSA PRIVATE KEY----- [...] -----END RSA PRIVATE KEY-----\n",
|
||||||
"userId":"100507859606888466"
|
"userId": "100507859606888466"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -55,8 +60,8 @@ Header
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"alg": "RS256",
|
"alg": "RS256",
|
||||||
"kid":"100509901696068329"
|
"kid": "100509901696068329"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -74,11 +79,11 @@ Payload
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
* `iss` represents the requesting party, i.e. the owner of the private key. In this case the value of `userId` from the downloaded JSON.
|
- `iss` represents the requesting party, i.e. the owner of the private key. In this case the value of `userId` from the downloaded JSON.
|
||||||
* `sub` represents the application. Set the value also to the value of `userId`
|
- `sub` represents the application. Set the value also to the value of `userId`
|
||||||
* `aud` must be ZITADEL's issuing domain
|
- `aud` must be ZITADEL's issuing domain
|
||||||
* `iat` is a unix timestamp of the creation signing time of the JWT, e.g. now and must not be older than 1 hour ago
|
- `iat` is a unix timestamp of the creation signing time of the JWT, e.g. now and must not be older than 1 hour ago
|
||||||
* `exp` is the unix timestamp of expiry of this assertion
|
- `exp` is the unix timestamp of expiry of this assertion
|
||||||
|
|
||||||
Please refer to [JWT_with_Private_Key](/apis/openidoauth/authn-methods#jwt-with-private-key) in the documentation for further information.
|
Please refer to [JWT_with_Private_Key](/apis/openidoauth/authn-methods#jwt-with-private-key) in the documentation for further information.
|
||||||
|
|
||||||
@ -99,11 +104,11 @@ curl --request POST \
|
|||||||
|
|
||||||
If you want to access the ZITADEL API with this access token, you have to add `urn:zitadel:iam:org:project:id:zitadel:aud` to the list of scopes.
|
If you want to access the ZITADEL API with this access token, you have to add `urn:zitadel:iam:org:project:id:zitadel:aud` to the list of scopes.
|
||||||
|
|
||||||
* `grant_type` should be set to `urn:ietf:params:oauth:grant-type:jwt-bearer`
|
- `grant_type` should be set to `urn:ietf:params:oauth:grant-type:jwt-bearer`
|
||||||
* `scope` should contain any [Scopes](/apis/openidoauth/scopes) you want to include, but must include `openid`. For this example, please include `profile` and `email`
|
- `scope` should contain any [Scopes](/apis/openidoauth/scopes) you want to include, but must include `openid`. For this example, please include `profile` and `email`
|
||||||
* `assertion` is the encoded value of the JWT that was signed with your private key from the prior step
|
- `assertion` is the encoded value of the JWT that was signed with your private key from the prior step
|
||||||
|
|
||||||
You should receive a successful response with `access_token`, `token_type` and time to expiry in seconds as `expires_in`.
|
You should receive a successful response with `access_token`, `token_type` and time to expiry in seconds as `expires_in`.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
HTTP/1.1 200 OK
|
HTTP/1.1 200 OK
|
||||||
@ -142,11 +147,11 @@ Content-Type: application/json
|
|||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
* With service users you can secure machine-to-machine communication
|
- With service users you can secure machine-to-machine communication
|
||||||
* Because there is no interactive logon, you need to use a JWT signed with your private key to authorize the user
|
- Because there is no interactive logon, you need to use a JWT signed with your private key to authorize the user
|
||||||
* After successful authorization you can use an access token like for human users
|
- After successful authorization you can use an access token like for human users
|
||||||
|
|
||||||
Where to go from here:
|
Where to go from here:
|
||||||
|
|
||||||
* Management API
|
- Management API
|
||||||
* Securing backend API
|
- Securing backend API
|
||||||
|
@ -56,7 +56,7 @@ func (key *MachineKey) Detail() ([]byte, error) {
|
|||||||
return nil, zerrors.ThrowPreconditionFailed(nil, "KEY-sp2l2m", "Errors.Internal")
|
return nil, zerrors.ThrowPreconditionFailed(nil, "KEY-sp2l2m", "Errors.Internal")
|
||||||
}
|
}
|
||||||
if key.Type == domain.AuthNKeyTypeJSON {
|
if key.Type == domain.AuthNKeyTypeJSON {
|
||||||
return domain.MachineKeyMarshalJSON(key.KeyID, key.PrivateKey, key.AggregateID)
|
return domain.MachineKeyMarshalJSON(key.KeyID, key.PrivateKey, key.ExpirationDate, key.AggregateID)
|
||||||
}
|
}
|
||||||
return nil, zerrors.ThrowPreconditionFailed(nil, "KEY-dsg52", "Errors.Internal")
|
return nil, zerrors.ThrowPreconditionFailed(nil, "KEY-dsg52", "Errors.Internal")
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ func (key *MachineKey) Detail() ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (key *MachineKey) MarshalJSON() ([]byte, error) {
|
func (key *MachineKey) MarshalJSON() ([]byte, error) {
|
||||||
return MachineKeyMarshalJSON(key.KeyID, key.PrivateKey, key.AggregateID)
|
return MachineKeyMarshalJSON(key.KeyID, key.PrivateKey, key.ExpirationDate, key.AggregateID)
|
||||||
}
|
}
|
||||||
|
|
||||||
type MachineKeyState int32
|
type MachineKeyState int32
|
||||||
@ -59,16 +59,18 @@ func (f MachineKeyState) Valid() bool {
|
|||||||
return f >= 0 && f < machineKeyStateCount
|
return f >= 0 && f < machineKeyStateCount
|
||||||
}
|
}
|
||||||
|
|
||||||
func MachineKeyMarshalJSON(keyID string, privateKey []byte, userID string) ([]byte, error) {
|
func MachineKeyMarshalJSON(keyID string, privateKey []byte, expirationDate time.Time, userID string) ([]byte, error) {
|
||||||
return json.Marshal(struct {
|
return json.Marshal(struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
KeyID string `json:"keyId"`
|
KeyID string `json:"keyId"`
|
||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
UserID string `json:"userId"`
|
ExpirationDate time.Time `json:"expirationDate"`
|
||||||
|
UserID string `json:"userId"`
|
||||||
}{
|
}{
|
||||||
Type: "serviceaccount",
|
Type: "serviceaccount",
|
||||||
KeyID: keyID,
|
KeyID: keyID,
|
||||||
Key: string(privateKey),
|
Key: string(privateKey),
|
||||||
UserID: userID,
|
ExpirationDate: expirationDate,
|
||||||
|
UserID: userID,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user