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:
Miguel Cabrerizo 2024-03-13 19:21:19 +01:00 committed by GitHub
parent 30a1f4b39e
commit dff5984f7d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 53 additions and 34 deletions

View File

@ -61,7 +61,7 @@
<ng-container matColumnDef="expirationDate">
<th mat-header-cell *matHeaderCellDef>{{ 'USER.MACHINE.EXPIRATIONDATE' | translate }}</th>
<td mat-cell *matCellDef="let key">
{{ key.expirationDate | timestampToDate | localizedDate: 'fromNow' }}
{{ key.expirationDate | timestampToDate | localizedDate: 'EEE dd. MMM YYYY, HH:mm' }}
</td>
</ng-container>

View File

@ -17,6 +17,13 @@
</p>
</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>
</ng-container>
</div>

View File

@ -11,6 +11,7 @@ import { InfoSectionType } from '../info-section/info-section.component';
})
export class ShowKeyDialogComponent {
public keyResponse!: AddMachineKeyResponse.AsObject | AddAppKeyResponse.AsObject;
public expirationDate: string = '';
public InfoSectionType: any = InfoSectionType;
constructor(
@ -18,6 +19,10 @@ export class ShowKeyDialogComponent {
@Inject(MAT_DIALOG_DATA) public data: any,
) {
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 {

View File

@ -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).
## Create a Service User
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
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)
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
{
"type":"serviceaccount",
"keyId":"100509901696068329",
"key":"-----BEGIN RSA PRIVATE KEY----- [...] -----END RSA PRIVATE KEY-----\n",
"userId":"100507859606888466"
"type": "serviceaccount",
"keyId": "100509901696068329",
"key": "-----BEGIN RSA PRIVATE KEY----- [...] -----END RSA PRIVATE KEY-----\n",
"userId": "100507859606888466"
}
```
@ -55,8 +60,8 @@ Header
```json
{
"alg": "RS256",
"kid":"100509901696068329"
"alg": "RS256",
"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.
* `sub` represents the application. Set the value also to the value of `userId`
* `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
* `exp` is the unix timestamp of expiry of this assertion
- `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`
- `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
- `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.
@ -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.
* `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`
* `assertion` is the encoded value of the JWT that was signed with your private key from the prior step
- `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`
- `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
HTTP/1.1 200 OK
@ -142,11 +147,11 @@ Content-Type: application/json
## Summary
* 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
* After successful authorization you can use an access token like for human users
- 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
- After successful authorization you can use an access token like for human users
Where to go from here:
* Management API
* Securing backend API
- Management API
- Securing backend API

View File

@ -56,7 +56,7 @@ func (key *MachineKey) Detail() ([]byte, error) {
return nil, zerrors.ThrowPreconditionFailed(nil, "KEY-sp2l2m", "Errors.Internal")
}
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")
}

View File

@ -42,7 +42,7 @@ func (key *MachineKey) Detail() ([]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
@ -59,16 +59,18 @@ func (f MachineKeyState) Valid() bool {
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 {
Type string `json:"type"`
KeyID string `json:"keyId"`
Key string `json:"key"`
UserID string `json:"userId"`
Type string `json:"type"`
KeyID string `json:"keyId"`
Key string `json:"key"`
ExpirationDate time.Time `json:"expirationDate"`
UserID string `json:"userId"`
}{
Type: "serviceaccount",
KeyID: keyID,
Key: string(privateKey),
UserID: userID,
Type: "serviceaccount",
KeyID: keyID,
Key: string(privateKey),
ExpirationDate: expirationDate,
UserID: userID,
})
}