mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 04:37:31 +00:00
test(load): machine jwt profile grant (#8482)
# Which Problems Are Solved Currently there was no load test present for machine jwt profile grant. This test is now added # How the Problems Are Solved K6 test implemented. # Additional Context - part of https://github.com/zitadel/zitadel/issues/8352
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
import { JSONObject, check, fail } from 'k6';
|
||||
import encoding from 'k6/encoding';
|
||||
import http from 'k6/http';
|
||||
import http, { RequestBody } from 'k6/http';
|
||||
import { Trend } from 'k6/metrics';
|
||||
import url from './url';
|
||||
import { Config } from './config';
|
||||
// @ts-ignore Import module
|
||||
import zitadel from 'k6/x/zitadel';
|
||||
|
||||
export class Tokens {
|
||||
idToken?: string;
|
||||
@@ -103,4 +106,66 @@ export function clientCredentials(clientId: string, clientSecret: string): Promi
|
||||
resolve(tokens)
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export interface TokenRequest {
|
||||
payload(): RequestBody;
|
||||
headers(): { [name: string]: string; };
|
||||
}
|
||||
|
||||
const privateKey = open('../.keys/key.pem');
|
||||
|
||||
export class JWTProfileRequest implements TokenRequest {
|
||||
keyPayload!: {
|
||||
userId: string;
|
||||
expiration: number;
|
||||
keyId: string;
|
||||
};
|
||||
|
||||
constructor(userId: string, keyId: string) {
|
||||
this.keyPayload = {
|
||||
userId: userId,
|
||||
// 1 minute
|
||||
expiration: 60*1_000_000_000,
|
||||
keyId: keyId,
|
||||
};
|
||||
}
|
||||
|
||||
payload(): RequestBody{
|
||||
const assertion = zitadel.signJWTProfileAssertion(
|
||||
this.keyPayload.userId,
|
||||
this.keyPayload.keyId,
|
||||
{
|
||||
audience: [Config.host],
|
||||
expiration: this.keyPayload.expiration,
|
||||
key: privateKey
|
||||
});
|
||||
return {
|
||||
'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
||||
scope: 'openid',
|
||||
assertion: `${assertion}`
|
||||
};
|
||||
};
|
||||
public headers(): { [name: string]: string; } {
|
||||
return {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const tokenDurationTrend = new Trend('oidc_token_duration', true);
|
||||
export async function token(request: TokenRequest): Promise<Tokens> {
|
||||
return http.asyncRequest('POST', configuration().token_endpoint,
|
||||
request.payload(),
|
||||
{
|
||||
headers: request.headers(),
|
||||
},
|
||||
).then((res) => {
|
||||
tokenDurationTrend.add(res.timings.duration);
|
||||
check(res, {
|
||||
'token status ok': (r) => r.status === 200,
|
||||
'access token returned': (r) => r.json('access_token')! != undefined && r.json('access_token')! != '',
|
||||
});
|
||||
return new Tokens(res.json() as JSONObject);
|
||||
});
|
||||
};
|
57
load-test/src/use_cases/machine_jwt_profile_grant.ts
Normal file
57
load-test/src/use_cases/machine_jwt_profile_grant.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { loginByUsernamePassword } from '../login_ui';
|
||||
import { createOrg, removeOrg } from '../org';
|
||||
import {createMachine, User, addMachineKey} from '../user';
|
||||
import {JWTProfileRequest, token, userinfo} from '../oidc';
|
||||
import { Config, MaxVUs } from '../config';
|
||||
import encoding from 'k6/encoding';
|
||||
|
||||
const publicKey = encoding.b64encode(open('../.keys/key.pem.pub'));
|
||||
|
||||
export async function setup() {
|
||||
const tokens = loginByUsernamePassword(Config.admin as User);
|
||||
console.info('setup: admin signed in');
|
||||
|
||||
const org = await createOrg(tokens.accessToken!);
|
||||
console.info(`setup: org (${org.organizationId}) created`);
|
||||
|
||||
let machines = (
|
||||
await Promise.all(
|
||||
Array.from({ length: MaxVUs() }, (_, i) => {
|
||||
return createMachine(`zitachine-${i}`, org, tokens.accessToken!);
|
||||
}),
|
||||
)
|
||||
).map((machine) => {
|
||||
return { userId: machine.userId, loginName: machine.loginNames[0] };
|
||||
});
|
||||
console.info(`setup: ${machines.length} machines created`);
|
||||
|
||||
let keys = (
|
||||
await Promise.all(
|
||||
machines.map((machine) => {
|
||||
return addMachineKey(
|
||||
machine.userId,
|
||||
org,
|
||||
tokens.accessToken!,
|
||||
publicKey,
|
||||
);
|
||||
}),
|
||||
)
|
||||
).map((key, i) => {
|
||||
return { userId: machines[i].userId, keyId: key.keyId };
|
||||
});
|
||||
console.info(`setup: ${keys.length} keys added`);
|
||||
|
||||
return { tokens, machines: keys, org };
|
||||
}
|
||||
|
||||
export default function (data: any) {
|
||||
token(new JWTProfileRequest(data.machines[__VU - 1].userId, data.machines[__VU - 1].keyId))
|
||||
.then((token) => {
|
||||
userinfo(token.accessToken!)
|
||||
})
|
||||
}
|
||||
|
||||
export function teardown(data: any) {
|
||||
removeOrg(data.org, data.tokens.accessToken);
|
||||
console.info('teardown: org removed');
|
||||
}
|
@@ -197,6 +197,38 @@ export function addMachineSecret(userId: string, org: Org, accessToken: string):
|
||||
});
|
||||
}
|
||||
|
||||
export type MachineKey = {
|
||||
keyId: string;
|
||||
};
|
||||
|
||||
const addMachineKeyTrend = new Trend('user_add_machine_key_duration', true);
|
||||
export function addMachineKey(userId: string, org: Org, accessToken: string, publicKey?: string): Promise<MachineKey> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let response = http.asyncRequest('POST', url(`/management/v1/users/${userId}/keys`),
|
||||
JSON.stringify({
|
||||
type: 'KEY_TYPE_JSON',
|
||||
userId: userId,
|
||||
// base64 encoded public key
|
||||
publicKey: publicKey
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
authorization: `Bearer ${accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
'x-zitadel-orgid': org.organizationId,
|
||||
},
|
||||
});
|
||||
response.then((res) => {
|
||||
check(res, {
|
||||
'generate machine key status ok': (r) => r.status === 200,
|
||||
}) || reject(`unable to generate machine Key (user id: ${userId}) status: ${res.status} body: ${res.body}`);
|
||||
|
||||
addMachineKeyTrend.add(res.timings.duration);
|
||||
resolve(res.json()! as MachineKey);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const lockUserTrend = new Trend('lock_user_duration', true);
|
||||
export function lockUser(userId: string, org: Org, accessToken: string): Promise<RefinedResponse<any>> {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
Reference in New Issue
Block a user