mirror of
https://github.com/zitadel/zitadel.git
synced 2025-02-28 19:57:22 +00:00
test(session): load tests for session api (#9212)
# Which Problems Are Solved We currently are not able to benchmark the performance of the session api # How the Problems Are Solved Load tests were added to - use sessions in oidc tokens analog https://zitadel.com/docs/guides/integrate/login-ui/oidc-standard # Additional Context - Closes https://github.com/zitadel/zitadel/issues/7847
This commit is contained in:
parent
679ab58fa1
commit
b10428fb56
@ -481,7 +481,7 @@ func (q *Queries) SearchApps(ctx context.Context, queries *AppSearchQueries, wit
|
|||||||
return err
|
return err
|
||||||
}, stmt, args...)
|
}, stmt, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, zerrors.ThrowInternal(err, "QUERY-aJnZL", "Errors.Internal")
|
return nil, zerrors.ThrowInternal(err, "QUERY-h9TeF", "Errors.Internal")
|
||||||
}
|
}
|
||||||
apps.State, err = q.latestState(ctx, appsTable)
|
apps.State, err = q.latestState(ctx, appsTable)
|
||||||
return apps, err
|
return apps, err
|
||||||
|
@ -207,7 +207,7 @@ func (q *Queries) SearchSMSConfigs(ctx context.Context, queries *SMSConfigsSearc
|
|||||||
return err
|
return err
|
||||||
}, stmt, args...)
|
}, stmt, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, zerrors.ThrowInternal(err, "QUERY-aJnZL", "Errors.Internal")
|
return nil, zerrors.ThrowInternal(err, "QUERY-l4bxm", "Errors.Internal")
|
||||||
}
|
}
|
||||||
configs.State, err = q.latestState(ctx, smsConfigsTable)
|
configs.State, err = q.latestState(ctx, smsConfigsTable)
|
||||||
return configs, err
|
return configs, err
|
||||||
|
@ -33,9 +33,21 @@ introspect: ensure_modules bundle
|
|||||||
cd ../../xk6-modules && xk6 build --with xk6-zitadel=.
|
cd ../../xk6-modules && xk6 build --with xk6-zitadel=.
|
||||||
${K6} run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/introspection.js --vus ${VUS} --duration ${DURATION} --out csv=output/introspect_${DATE}.csv
|
${K6} run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/introspection.js --vus ${VUS} --duration ${DURATION} --out csv=output/introspect_${DATE}.csv
|
||||||
|
|
||||||
|
.PHONY: oidc_session
|
||||||
|
oidc_session: ensure_key_pair ensure_modules bundle
|
||||||
|
${K6} run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/oidc_session.js --vus ${VUS} --duration ${DURATION} --out csv=output/oidc_session_${DATE}.csv
|
||||||
|
|
||||||
|
.PHONY: otp_session
|
||||||
|
otp_session: ensure_key_pair ensure_modules bundle
|
||||||
|
${K6} run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/otp_session.js --vus ${VUS} --duration ${DURATION} --out csv=output/otp_session_${DATE}.csv
|
||||||
|
|
||||||
|
.PHONY: password_session
|
||||||
|
password_session: ensure_key_pair ensure_modules bundle
|
||||||
|
${K6} run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/password_session.js --vus ${VUS} --duration ${DURATION} --out csv=output/otp_session_${DATE}.csv
|
||||||
|
|
||||||
.PHONY: add_session
|
.PHONY: add_session
|
||||||
add_session: bundle
|
add_session: ensure_modules bundle
|
||||||
${K6} run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/session.js --vus ${VUS} --duration ${DURATION} --out csv=output/add_session_${DATE}.csv
|
${K6} run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/add_session.js --vus ${VUS} --duration ${DURATION} --out csv=output/add_session_${DATE}.csv
|
||||||
|
|
||||||
.PHONY: machine_jwt_profile_grant
|
.PHONY: machine_jwt_profile_grant
|
||||||
machine_jwt_profile_grant: ensure_modules ensure_key_pair bundle
|
machine_jwt_profile_grant: ensure_modules ensure_key_pair bundle
|
||||||
|
@ -20,6 +20,8 @@ The use cases under tests are defined in `src/use_cases`. The implementation of
|
|||||||
- `VUS`: Amount of parallel processes execute the test (default is 20)
|
- `VUS`: Amount of parallel processes execute the test (default is 20)
|
||||||
- `DURATION`: Defines how long the tests are executed (default is `200s`)
|
- `DURATION`: Defines how long the tests are executed (default is `200s`)
|
||||||
- `ZITADEL_HOST`: URL of ZITADEL (default is `http://localhost:8080`)
|
- `ZITADEL_HOST`: URL of ZITADEL (default is `http://localhost:8080`)
|
||||||
|
- `ADMIN_LOGIN_NAME`: Loginanme of a human user with `IAM_OWNER`-role
|
||||||
|
- `ADMIN_PASSWORD`: password of the human user
|
||||||
|
|
||||||
To setup the tests we use the credentials of console and log in using an admin. The user must be able to create organizations and all resources inside organizations.
|
To setup the tests we use the credentials of console and log in using an admin. The user must be able to create organizations and all resources inside organizations.
|
||||||
|
|
||||||
@ -50,6 +52,15 @@ Before you run the tests you need an initialized user. The tests don't implement
|
|||||||
* `make add_session`
|
* `make add_session`
|
||||||
setup: creates human users
|
setup: creates human users
|
||||||
test: creates new sessions with user id check
|
test: creates new sessions with user id check
|
||||||
|
* `make oidc_session`
|
||||||
|
setup: creates a machine user to create the auth request and session.
|
||||||
|
test: creates an auth request, a session and links the session to the auth request. Implementation of [this flow](https://zitadel.com/docs/guides/integrate/login-ui/oidc-standard).
|
||||||
|
* `make otp_session`
|
||||||
|
setup: creates 1 human user for each VU and adds email OTP to it
|
||||||
|
test: creates a session based on the login name of the user, sets the email OTP challenge to the session and afterwards checks the OTP code
|
||||||
|
* `make password_session`
|
||||||
|
setup: creates 1 human user for each VU and adds email OTP to it
|
||||||
|
test: creates a session based on the login name of the user and checks for the password on a second step
|
||||||
* `make machine_jwt_profile_grant`
|
* `make machine_jwt_profile_grant`
|
||||||
setup: generates private/public key, creates machine users, adds a key
|
setup: generates private/public key, creates machine users, adds a key
|
||||||
test: creates a token and calls user info
|
test: creates a token and calls user info
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"bundle": "webpack",
|
"bundle": "webpack",
|
||||||
"lint": "prettier --check src",
|
"lint": "prettier --check src/**",
|
||||||
"lint:fix": "prettier --write src"
|
"lint:fix": "prettier --write src"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,10 +21,27 @@ export function loginByUsernamePassword(user: User) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const initLoginTrend = new Trend('login_ui_init_login_duration', true);
|
const initLoginTrend = new Trend('login_ui_init_login_duration', true);
|
||||||
function initLogin(): Response {
|
export function initLogin(clientId?: string): Response {
|
||||||
const response = http.get(url('/oauth/v2/authorize', { searchParams: Client() }));
|
let params = {};
|
||||||
|
let expectedStatus = 200;
|
||||||
|
if (clientId) {
|
||||||
|
params = {
|
||||||
|
headers: {
|
||||||
|
'x-zitadel-login-client': clientId,
|
||||||
|
},
|
||||||
|
redirects: 0,
|
||||||
|
};
|
||||||
|
expectedStatus = 302;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = http.get(
|
||||||
|
url('/oauth/v2/authorize', {
|
||||||
|
searchParams: Client(),
|
||||||
|
}),
|
||||||
|
params,
|
||||||
|
);
|
||||||
check(response, {
|
check(response, {
|
||||||
'authorize status ok': (r) => r.status == 200 || fail(`init login failed: ${r}`),
|
'authorize status ok': (r) => r.status == expectedStatus || fail(`init login failed: ${JSON.stringify(r)}`),
|
||||||
});
|
});
|
||||||
initLoginTrend.add(response.timings.duration);
|
initLoginTrend.add(response.timings.duration);
|
||||||
return response;
|
return response;
|
||||||
|
25
load-test/src/membership.ts
Normal file
25
load-test/src/membership.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import http from 'k6/http';
|
||||||
|
import { Trend } from 'k6/metrics';
|
||||||
|
import url from './url';
|
||||||
|
import { check, fail } from 'k6';
|
||||||
|
|
||||||
|
const addIAMMemberTrend = new Trend('membership_iam_member', true);
|
||||||
|
export async function addIAMMember(userId: string, roles: string[], accessToken: string): Promise<void> {
|
||||||
|
const res = await http.post(
|
||||||
|
url('/admin/v1/members'),
|
||||||
|
JSON.stringify({
|
||||||
|
userId: userId,
|
||||||
|
roles: roles,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${accessToken}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
check(res, {
|
||||||
|
'member added successful': (r) => r.status == 200 || fail(`unable add member: ${JSON.stringify(res)}`),
|
||||||
|
});
|
||||||
|
addIAMMemberTrend.add(res.timings.duration);
|
||||||
|
}
|
@ -1,9 +1,9 @@
|
|||||||
import { JSONObject, check, fail } from 'k6';
|
import { JSONObject, check, fail } from 'k6';
|
||||||
import encoding from 'k6/encoding';
|
import encoding from 'k6/encoding';
|
||||||
import http, { RequestBody } from 'k6/http';
|
import http, { RequestBody, Response } from 'k6/http';
|
||||||
import { Trend } from 'k6/metrics';
|
import { Trend } from 'k6/metrics';
|
||||||
import url from './url';
|
import url from './url';
|
||||||
import { Config } from './config';
|
import { Client, Config } from './config';
|
||||||
// @ts-ignore Import module
|
// @ts-ignore Import module
|
||||||
import zitadel from 'k6/x/zitadel';
|
import zitadel from 'k6/x/zitadel';
|
||||||
|
|
||||||
@ -79,9 +79,11 @@ export function introspect(jwt: string, token: string) {
|
|||||||
const clientCredentialsTrend = new Trend('oidc_client_credentials_duration', true);
|
const clientCredentialsTrend = new Trend('oidc_client_credentials_duration', true);
|
||||||
export function clientCredentials(clientId: string, clientSecret: string): Promise<Tokens> {
|
export function clientCredentials(clientId: string, clientSecret: string): Promise<Tokens> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const response = http.asyncRequest('POST', configuration().token_endpoint,
|
const response = http.asyncRequest(
|
||||||
|
'POST',
|
||||||
|
configuration().token_endpoint,
|
||||||
{
|
{
|
||||||
grant_type: "client_credentials",
|
grant_type: 'client_credentials',
|
||||||
scope: 'openid profile urn:zitadel:iam:org:project:id:zitadel:aud',
|
scope: 'openid profile urn:zitadel:iam:org:project:id:zitadel:aud',
|
||||||
client_id: clientId,
|
client_id: clientId,
|
||||||
client_secret: clientSecret,
|
client_secret: clientSecret,
|
||||||
@ -91,26 +93,26 @@ export function clientCredentials(clientId: string, clientSecret: string): Promi
|
|||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
response.then((res) => {
|
response.then((res) => {
|
||||||
check(res, {
|
check(res, {
|
||||||
'client credentials status ok': (r) => r.status === 200,
|
'client credentials status ok': (r) => r.status === 200,
|
||||||
}) || reject(`client credentials request failed (client id: ${clientId}) status: ${res.status} body: ${res.body}`);
|
}) || reject(`client credentials request failed (client id: ${clientId}) status: ${res.status} body: ${res.body}`);
|
||||||
|
|
||||||
clientCredentialsTrend.add(res.timings.duration);
|
clientCredentialsTrend.add(res.timings.duration);
|
||||||
const tokens = new Tokens(res.json() as JSONObject)
|
const tokens = new Tokens(res.json() as JSONObject);
|
||||||
check(tokens, {
|
check(tokens, {
|
||||||
'client credentials token ok': (t) => t.accessToken !== undefined,
|
'client credentials token ok': (t) => t.accessToken !== undefined,
|
||||||
}) || reject(`client credentials access token missing (client id: ${clientId}`);
|
}) || reject(`client credentials access token missing (client id: ${clientId}`);
|
||||||
|
|
||||||
resolve(tokens)
|
resolve(tokens);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TokenRequest {
|
export interface TokenRequest {
|
||||||
payload(): RequestBody;
|
payload(): RequestBody;
|
||||||
headers(): { [name: string]: string; };
|
headers(): { [name: string]: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
const privateKey = open('../.keys/key.pem');
|
const privateKey = open('../.keys/key.pem');
|
||||||
@ -126,46 +128,83 @@ export class JWTProfileRequest implements TokenRequest {
|
|||||||
this.keyPayload = {
|
this.keyPayload = {
|
||||||
userId: userId,
|
userId: userId,
|
||||||
// 1 minute
|
// 1 minute
|
||||||
expiration: 60*1_000_000_000,
|
expiration: 60 * 1_000_000_000,
|
||||||
keyId: keyId,
|
keyId: keyId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
payload(): RequestBody{
|
payload(): RequestBody {
|
||||||
const assertion = zitadel.signJWTProfileAssertion(
|
const assertion = zitadel.signJWTProfileAssertion(this.keyPayload.userId, this.keyPayload.keyId, {
|
||||||
this.keyPayload.userId,
|
audience: [Config.host],
|
||||||
this.keyPayload.keyId,
|
expiration: this.keyPayload.expiration,
|
||||||
{
|
key: privateKey,
|
||||||
audience: [Config.host],
|
});
|
||||||
expiration: this.keyPayload.expiration,
|
|
||||||
key: privateKey
|
|
||||||
});
|
|
||||||
return {
|
return {
|
||||||
'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
||||||
scope: 'openid',
|
scope: 'openid urn:zitadel:iam:org:project:id:zitadel:aud',
|
||||||
assertion: `${assertion}`
|
assertion: `${assertion}`,
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
public headers(): { [name: string]: string; } {
|
public headers(): { [name: string]: string } {
|
||||||
return {
|
return {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded'
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const tokenDurationTrend = new Trend('oidc_token_duration', true);
|
const tokenDurationTrend = new Trend('oidc_token_duration', true);
|
||||||
export async function token(request: TokenRequest): Promise<Tokens> {
|
export async function token(request: TokenRequest): Promise<Tokens> {
|
||||||
return http.asyncRequest('POST', configuration().token_endpoint,
|
return http
|
||||||
request.payload(),
|
.asyncRequest('POST', configuration().token_endpoint, request.payload(), {
|
||||||
{
|
|
||||||
headers: request.headers(),
|
headers: request.headers(),
|
||||||
},
|
})
|
||||||
).then((res) => {
|
.then((res) => {
|
||||||
tokenDurationTrend.add(res.timings.duration);
|
tokenDurationTrend.add(res.timings.duration);
|
||||||
check(res, {
|
check(res, {
|
||||||
'token status ok': (r) => r.status === 200,
|
'token status ok': (r) => r.status === 200,
|
||||||
'access token returned': (r) => r.json('access_token')! != undefined && r.json('access_token')! != '',
|
'access token returned': (r) => r.json('access_token')! != undefined && r.json('access_token')! != '',
|
||||||
|
});
|
||||||
|
return new Tokens(res.json() as JSONObject);
|
||||||
});
|
});
|
||||||
return new Tokens(res.json() as JSONObject);
|
}
|
||||||
|
|
||||||
|
const authRequestBiIDTrend = new Trend('oidc_auth_request_by_id_duration', true);
|
||||||
|
export async function authRequestByID(id: string, tokens: any): Promise<Response> {
|
||||||
|
const response = http.get(url(`/v2/oidc/auth_requests/${id}`), {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${tokens.accessToken}`,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
check(response, {
|
||||||
|
'authorize status ok': (r) => r.status == 200 || fail(`auth request by failed: ${JSON.stringify(r)}`),
|
||||||
|
});
|
||||||
|
authRequestBiIDTrend.add(response.timings.duration);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
const finalizeAuthRequestTrend = new Trend('oidc_auth_requst_by_id_duration', true);
|
||||||
|
export async function finalizeAuthRequest(id: string, session: any, tokens: any): Promise<Response> {
|
||||||
|
const res = await http.post(
|
||||||
|
url(`/v2/oidc/auth_requests/${id}`),
|
||||||
|
JSON.stringify({
|
||||||
|
session: {
|
||||||
|
sessionId: session.sessionId,
|
||||||
|
sessionToken: session.sessionToken,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${tokens.accessToken}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
// 'Accept': 'application/json',
|
||||||
|
'x-zitadel-login-client': tokens.info.client_id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
check(res, {
|
||||||
|
'finalize auth request status ok': (r) => r.status == 200 || fail(`finalize auth request failed: ${JSON.stringify(r)}`),
|
||||||
|
});
|
||||||
|
finalizeAuthRequestTrend.add(res.timings.duration);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
@ -3,33 +3,23 @@ import { Org } from './org';
|
|||||||
import http from 'k6/http';
|
import http from 'k6/http';
|
||||||
import url from './url';
|
import url from './url';
|
||||||
import { check } from 'k6';
|
import { check } from 'k6';
|
||||||
import { User } from './user';
|
|
||||||
|
|
||||||
export type Session = {
|
export type Session = {
|
||||||
|
challenges: any;
|
||||||
id: string;
|
id: string;
|
||||||
|
token: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const addSessionTrend = new Trend('session_add_session_duration', true);
|
const addSessionTrend = new Trend('session_add_session_duration', true);
|
||||||
export function createSession(user: User, org: Org, accessToken: string): Promise<Session> {
|
export function createSession(org: Org, accessToken: string, checks?: any): Promise<Session> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let response = http.asyncRequest(
|
let response = http.asyncRequest('POST', url('/v2/sessions'), checks ? JSON.stringify({ checks: checks }) : null, {
|
||||||
'POST',
|
headers: {
|
||||||
url('/v2beta/sessions'),
|
authorization: `Bearer ${accessToken}`,
|
||||||
JSON.stringify({
|
'Content-Type': 'application/json',
|
||||||
checks: {
|
'x-zitadel-orgid': org.organizationId,
|
||||||
user: {
|
|
||||||
userId: user.userId,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
authorization: `Bearer ${accessToken}`,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'x-zitadel-orgid': org.organizationId,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
});
|
||||||
response.then((res) => {
|
response.then((res) => {
|
||||||
check(res, {
|
check(res, {
|
||||||
'add Session status ok': (r) => r.status === 201,
|
'add Session status ok': (r) => r.status === 201,
|
||||||
@ -40,3 +30,29 @@ export function createSession(user: User, org: Org, accessToken: string): Promis
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const setSessionTrend = new Trend('session_set_session_duration', true);
|
||||||
|
export function setSession(id: string, session: any, accessToken: string, challenges?: any, checks?: any): Promise<Session> {
|
||||||
|
const body = {
|
||||||
|
sessionToken: session.sessionToken,
|
||||||
|
checks: checks ? checks : null,
|
||||||
|
challenges: challenges ? challenges : null,
|
||||||
|
};
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let response = http.asyncRequest('PATCH', url(`/v2/sessions/${id}`), JSON.stringify(body), {
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${accessToken}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
// 'x-zitadel-orgid': org.organizationId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
response.then((res) => {
|
||||||
|
check(res, {
|
||||||
|
'set Session status ok': (r) => r.status === 200,
|
||||||
|
}) || reject(`unable to set Session status: ${res.status} body: ${res.body}`);
|
||||||
|
|
||||||
|
setSessionTrend.add(res.timings.duration);
|
||||||
|
resolve(res.json() as Session);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { loginByUsernamePassword } from '../login_ui';
|
import { loginByUsernamePassword } from '../login_ui';
|
||||||
import { createOrg, removeOrg } from '../org';
|
import { createOrg, removeOrg } from '../org';
|
||||||
import {createMachine, User, addMachineSecret} from '../user';
|
import { createMachine, User, addMachineSecret } from '../user';
|
||||||
import {clientCredentials, userinfo} from '../oidc';
|
import { clientCredentials, userinfo } from '../oidc';
|
||||||
import { Config, MaxVUs } from '../config';
|
import { Config, MaxVUs } from '../config';
|
||||||
|
|
||||||
export async function setup() {
|
export async function setup() {
|
||||||
@ -37,10 +37,9 @@ export async function setup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function (data: any) {
|
export default function (data: any) {
|
||||||
clientCredentials(data.machines[__VU - 1].loginName, data.machines[__VU - 1].password)
|
clientCredentials(data.machines[__VU - 1].loginName, data.machines[__VU - 1].password).then((token) => {
|
||||||
.then((token) => {
|
userinfo(token.accessToken!);
|
||||||
userinfo(token.accessToken!)
|
});
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function teardown(data: any) {
|
export function teardown(data: any) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { loginByUsernamePassword } from '../login_ui';
|
import { loginByUsernamePassword } from '../login_ui';
|
||||||
import { createOrg, removeOrg } from '../org';
|
import { createOrg, removeOrg } from '../org';
|
||||||
import {createMachine, User, addMachineKey} from '../user';
|
import { createMachine, User, addMachineKey } from '../user';
|
||||||
import {JWTProfileRequest, token, userinfo} from '../oidc';
|
import { JWTProfileRequest, token, userinfo } from '../oidc';
|
||||||
import { Config, MaxVUs } from '../config';
|
import { Config, MaxVUs } from '../config';
|
||||||
import encoding from 'k6/encoding';
|
import encoding from 'k6/encoding';
|
||||||
|
|
||||||
@ -10,10 +10,10 @@ const publicKey = encoding.b64encode(open('../.keys/key.pem.pub'));
|
|||||||
export async function setup() {
|
export async function setup() {
|
||||||
const tokens = loginByUsernamePassword(Config.admin as User);
|
const tokens = loginByUsernamePassword(Config.admin as User);
|
||||||
console.info('setup: admin signed in');
|
console.info('setup: admin signed in');
|
||||||
|
|
||||||
const org = await createOrg(tokens.accessToken!);
|
const org = await createOrg(tokens.accessToken!);
|
||||||
console.info(`setup: org (${org.organizationId}) created`);
|
console.info(`setup: org (${org.organizationId}) created`);
|
||||||
|
|
||||||
let machines = (
|
let machines = (
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
Array.from({ length: MaxVUs() }, (_, i) => {
|
Array.from({ length: MaxVUs() }, (_, i) => {
|
||||||
@ -24,16 +24,11 @@ export async function setup() {
|
|||||||
return { userId: machine.userId, loginName: machine.loginNames[0] };
|
return { userId: machine.userId, loginName: machine.loginNames[0] };
|
||||||
});
|
});
|
||||||
console.info(`setup: ${machines.length} machines created`);
|
console.info(`setup: ${machines.length} machines created`);
|
||||||
|
|
||||||
let keys = (
|
let keys = (
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
machines.map((machine) => {
|
machines.map((machine) => {
|
||||||
return addMachineKey(
|
return addMachineKey(machine.userId, org, tokens.accessToken!, publicKey);
|
||||||
machine.userId,
|
|
||||||
org,
|
|
||||||
tokens.accessToken!,
|
|
||||||
publicKey,
|
|
||||||
);
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
).map((key, i) => {
|
).map((key, i) => {
|
||||||
@ -45,7 +40,7 @@ export async function setup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function (data: any) {
|
export default function (data: any) {
|
||||||
token(new JWTProfileRequest(data.machines[__VU - 1].userId, data.machines[__VU - 1].keyId))
|
token(new JWTProfileRequest(data.machines[__VU - 1].userId, data.machines[__VU - 1].keyId));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function teardown(data: any) {
|
export function teardown(data: any) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { loginByUsernamePassword } from '../login_ui';
|
import { loginByUsernamePassword } from '../login_ui';
|
||||||
import { createOrg, removeOrg } from '../org';
|
import { createOrg, removeOrg } from '../org';
|
||||||
import {createMachine, User, addMachineKey} from '../user';
|
import { createMachine, User, addMachineKey } from '../user';
|
||||||
import {JWTProfileRequest, token, userinfo} from '../oidc';
|
import { JWTProfileRequest, token, userinfo } from '../oidc';
|
||||||
import { Config } from '../config';
|
import { Config } from '../config';
|
||||||
import encoding from 'k6/encoding';
|
import encoding from 'k6/encoding';
|
||||||
|
|
||||||
@ -10,20 +10,20 @@ const publicKey = encoding.b64encode(open('../.keys/key.pem.pub'));
|
|||||||
export async function setup() {
|
export async function setup() {
|
||||||
const tokens = loginByUsernamePassword(Config.admin as User);
|
const tokens = loginByUsernamePassword(Config.admin as User);
|
||||||
console.info('setup: admin signed in');
|
console.info('setup: admin signed in');
|
||||||
|
|
||||||
const org = await createOrg(tokens.accessToken!);
|
const org = await createOrg(tokens.accessToken!);
|
||||||
console.info(`setup: org (${org.organizationId}) created`);
|
console.info(`setup: org (${org.organizationId}) created`);
|
||||||
|
|
||||||
const machine = await createMachine(`zitachine`, org, tokens.accessToken!);
|
const machine = await createMachine(`zitachine`, org, tokens.accessToken!);
|
||||||
console.info(`setup: machine ${machine.userId} created`);
|
console.info(`setup: machine ${machine.userId} created`);
|
||||||
const key = await addMachineKey(machine.userId, org, tokens.accessToken!, publicKey);
|
const key = await addMachineKey(machine.userId, org, tokens.accessToken!, publicKey);
|
||||||
console.info(`setup: key ${key.keyId} added`);
|
console.info(`setup: key ${key.keyId} added`);
|
||||||
|
|
||||||
return { tokens, machine: {userId: machine.userId, keyId: key.keyId}, org };
|
return { tokens, machine: { userId: machine.userId, keyId: key.keyId }, org };
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function (data: any) {
|
export default function (data: any) {
|
||||||
token(new JWTProfileRequest(data.machine.userId, data.machine.keyId))
|
token(new JWTProfileRequest(data.machine.userId, data.machine.keyId));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function teardown(data: any) {
|
export function teardown(data: any) {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { loginByUsernamePassword } from '../login_ui';
|
import { loginByUsernamePassword } from '../../login_ui';
|
||||||
import { createOrg, removeOrg } from '../org';
|
import { createOrg, removeOrg } from '../../org';
|
||||||
import { User, createHuman } from '../user';
|
import { User, createHuman } from '../../user';
|
||||||
import { Trend } from 'k6/metrics';
|
import { Trend } from 'k6/metrics';
|
||||||
import { Config, MaxVUs } from '../config';
|
import { Config, MaxVUs } from '../../config';
|
||||||
import { createSession } from '../session';
|
import { createSession } from '../../session';
|
||||||
import { check } from 'k6';
|
import { check } from 'k6';
|
||||||
|
|
||||||
export async function setup() {
|
export async function setup() {
|
||||||
@ -27,12 +27,16 @@ export async function setup() {
|
|||||||
const addSessionTrend = new Trend('add_session_duration', true);
|
const addSessionTrend = new Trend('add_session_duration', true);
|
||||||
export default async function (data: any) {
|
export default async function (data: any) {
|
||||||
const start = new Date();
|
const start = new Date();
|
||||||
const session = await createSession(data.users[__VU - 1], data.org, data.tokens.accessToken);
|
const session = await createSession(data.org, data.tokens.accessToken, {
|
||||||
|
user: {
|
||||||
|
userId: data.users[__VU - 1].userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
check(session, {
|
check(session, {
|
||||||
'add session is status ok': (s) => s.id !== "",
|
'add session is status ok': (s) => s.id !== '',
|
||||||
});
|
});
|
||||||
|
|
||||||
addSessionTrend.add(new Date().getTime() - start.getTime());
|
addSessionTrend.add(new Date().getTime() - start.getTime());
|
||||||
}
|
}
|
||||||
|
|
53
load-test/src/use_cases/session/oidc_session.ts
Normal file
53
load-test/src/use_cases/session/oidc_session.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { loginByUsernamePassword, initLogin } from '../../login_ui';
|
||||||
|
import { createOrg, removeOrg } from '../../org';
|
||||||
|
import { User, addMachineKey, createMachine } from '../../user';
|
||||||
|
import { Trend } from 'k6/metrics';
|
||||||
|
import { Config } from '../../config';
|
||||||
|
import { check } from 'k6';
|
||||||
|
import { finalizeAuthRequest, JWTProfileRequest, token } from '../../oidc';
|
||||||
|
import { createSession } from '../../session';
|
||||||
|
import encoding from 'k6/encoding';
|
||||||
|
import { addIAMMember } from '../../membership';
|
||||||
|
|
||||||
|
const publicKey = encoding.b64encode(open('../.keys/key.pem.pub'));
|
||||||
|
|
||||||
|
export async function setup() {
|
||||||
|
const adminTokens = loginByUsernamePassword(Config.admin as User);
|
||||||
|
console.log('setup: admin signed in');
|
||||||
|
|
||||||
|
const org = await createOrg(adminTokens.accessToken!);
|
||||||
|
console.log(`setup: org (${org.organizationId}) created`);
|
||||||
|
|
||||||
|
const loginUser = await createMachine('load-test', org, adminTokens.accessToken!);
|
||||||
|
const loginUserKey = await addMachineKey(loginUser.userId, org, adminTokens.accessToken!, publicKey);
|
||||||
|
await addIAMMember(loginUser.userId, ['IAM_LOGIN_CLIENT'], adminTokens.accessToken!);
|
||||||
|
const tokens = await token(new JWTProfileRequest(loginUser.userId, loginUserKey.keyId));
|
||||||
|
|
||||||
|
return { tokens, user: loginUser, key: loginUserKey, org, adminTokens };
|
||||||
|
}
|
||||||
|
|
||||||
|
// implements the flow described in
|
||||||
|
// https://zitadel.com/docs/guides/integrate/login-ui/oidc-standard
|
||||||
|
const addSessionTrend = new Trend('oidc_session_duration', true);
|
||||||
|
export default async function (data: any) {
|
||||||
|
const start = new Date();
|
||||||
|
const authorizeResponse = initLogin(data.tokens.info.client_id);
|
||||||
|
|
||||||
|
const authRequestId = new URLSearchParams(authorizeResponse.headers['Location']).values().next().value;
|
||||||
|
check(authRequestId, {
|
||||||
|
'auth request id returned': (s) => s !== '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const session = await createSession(data.org, data.tokens.accessToken, {
|
||||||
|
user: {
|
||||||
|
userId: data.user.userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await finalizeAuthRequest(authRequestId!, session, data.tokens!);
|
||||||
|
|
||||||
|
addSessionTrend.add(new Date().getTime() - start.getTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function teardown(data: any) {
|
||||||
|
removeOrg(data.org, data.adminTokens.accessToken);
|
||||||
|
}
|
57
load-test/src/use_cases/session/otp_session.ts
Normal file
57
load-test/src/use_cases/session/otp_session.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import { loginByUsernamePassword } from '../../login_ui';
|
||||||
|
import { createOrg, removeOrg } from '../../org';
|
||||||
|
import { createHuman, setEmailOTPOnHuman, User } from '../../user';
|
||||||
|
import { Trend } from 'k6/metrics';
|
||||||
|
import { Config, MaxVUs } from '../../config';
|
||||||
|
import { createSession, setSession } from '../../session';
|
||||||
|
|
||||||
|
export async function setup() {
|
||||||
|
const tokens = loginByUsernamePassword(Config.admin as User);
|
||||||
|
console.log('setup: admin signed in');
|
||||||
|
|
||||||
|
const org = await createOrg(tokens.accessToken!);
|
||||||
|
console.log(`setup: org (${org.organizationId}) created`);
|
||||||
|
|
||||||
|
const humanPromises = Array.from({ length: MaxVUs() }, (_, i) => {
|
||||||
|
return createHuman(`zitizen-${i}`, org, tokens.accessToken!);
|
||||||
|
});
|
||||||
|
|
||||||
|
const humans = (await Promise.all(humanPromises)).map((user) => {
|
||||||
|
setEmailOTPOnHuman(user, org, tokens.accessToken!);
|
||||||
|
return { userId: user.userId, loginName: user.loginNames[0], password: 'Password1!' };
|
||||||
|
});
|
||||||
|
console.log(`setup: ${humans.length} users created`);
|
||||||
|
|
||||||
|
return { tokens, org, users: humans };
|
||||||
|
}
|
||||||
|
|
||||||
|
// implements the flow described in
|
||||||
|
// https://zitadel.com/docs/guides/integrate/login-ui/oidc-standard
|
||||||
|
const otpSessionTrend = new Trend('otp_session_duration', true);
|
||||||
|
export default async function (data: any) {
|
||||||
|
const start = new Date();
|
||||||
|
let session = await createSession(data.org, data.tokens.accessToken, {
|
||||||
|
user: {
|
||||||
|
loginName: data.users[__VU - 1].loginName,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const sessionId = (session as any).sessionId;
|
||||||
|
|
||||||
|
session = await setSession(sessionId, session, data.tokens.accessToken, {
|
||||||
|
otpEmail: {
|
||||||
|
return_code: {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
session = await setSession(sessionId, session, data.tokens.accessToken, null, {
|
||||||
|
otpEmail: {
|
||||||
|
code: session.challenges.otpEmail,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
otpSessionTrend.add(new Date().getTime() - start.getTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function teardown(data: any) {
|
||||||
|
removeOrg(data.org, data.tokens.accessToken);
|
||||||
|
}
|
49
load-test/src/use_cases/session/password_session.ts
Normal file
49
load-test/src/use_cases/session/password_session.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { loginByUsernamePassword } from '../../login_ui';
|
||||||
|
import { createOrg, removeOrg } from '../../org';
|
||||||
|
import { createHuman, setEmailOTPOnHuman, User } from '../../user';
|
||||||
|
import { Trend } from 'k6/metrics';
|
||||||
|
import { Config, MaxVUs } from '../../config';
|
||||||
|
import { createSession, setSession } from '../../session';
|
||||||
|
|
||||||
|
export async function setup() {
|
||||||
|
const tokens = loginByUsernamePassword(Config.admin as User);
|
||||||
|
console.log('setup: admin signed in');
|
||||||
|
|
||||||
|
const org = await createOrg(tokens.accessToken!);
|
||||||
|
console.log(`setup: org (${org.organizationId}) created`);
|
||||||
|
|
||||||
|
const humanPromises = Array.from({ length: MaxVUs() }, (_, i) => {
|
||||||
|
return createHuman(`zitizen-${i}`, org, tokens.accessToken!);
|
||||||
|
});
|
||||||
|
|
||||||
|
const humans = (await Promise.all(humanPromises)).map((user) => {
|
||||||
|
setEmailOTPOnHuman(user, org, tokens.accessToken!);
|
||||||
|
return { userId: user.userId, loginName: user.loginNames[0], password: 'Password1!' };
|
||||||
|
});
|
||||||
|
console.log(`setup: ${humans.length} users created`);
|
||||||
|
|
||||||
|
return { tokens, org, users: humans };
|
||||||
|
}
|
||||||
|
|
||||||
|
const passwordSessionTrend = new Trend('password_session_duration', true);
|
||||||
|
export default async function (data: any) {
|
||||||
|
const start = new Date();
|
||||||
|
let session = await createSession(data.org, data.tokens.accessToken, {
|
||||||
|
user: {
|
||||||
|
loginName: data.users[__VU - 1].loginName,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const sessionId = (session as any).sessionId;
|
||||||
|
|
||||||
|
session = await setSession(sessionId, session, data.tokens.accessToken, null, {
|
||||||
|
password: {
|
||||||
|
password: data.users[__VU - 1].password,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
passwordSessionTrend.add(new Date().getTime() - start.getTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function teardown(data: any) {
|
||||||
|
removeOrg(data.org, data.tokens.accessToken);
|
||||||
|
}
|
@ -19,7 +19,7 @@ export function createHuman(username: string, org: Org, accessToken: string): Pr
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let response = http.asyncRequest(
|
let response = http.asyncRequest(
|
||||||
'POST',
|
'POST',
|
||||||
url('/v2beta/users/human'),
|
url('/v2/users/human'),
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
username: username,
|
username: username,
|
||||||
organization: {
|
organization: {
|
||||||
@ -69,6 +69,23 @@ export function createHuman(username: string, org: Org, accessToken: string): Pr
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const setEmailOTPOnHumanTrend = new Trend('set_human_email_otp_duration', true);
|
||||||
|
export async function setEmailOTPOnHuman(user: User, org: Org, accessToken: string): Promise<void> {
|
||||||
|
const response = await http.asyncRequest('POST', url(`/v2/users/${user.userId}/otp_email`), null, {
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${accessToken}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'x-zitadel-orgid': org.organizationId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
check(response, {
|
||||||
|
'set email otp status ok': (r) => r.status === 200,
|
||||||
|
});
|
||||||
|
setEmailOTPOnHumanTrend.add(response.timings.duration);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const updateHumanTrend = new Trend('update_human_duration', true);
|
const updateHumanTrend = new Trend('update_human_duration', true);
|
||||||
export function updateHuman(
|
export function updateHuman(
|
||||||
payload: any = {},
|
payload: any = {},
|
||||||
@ -172,8 +189,8 @@ export function addMachinePat(userId: string, org: Org, accessToken: string): Pr
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type MachineSecret = {
|
export type MachineSecret = {
|
||||||
clientId: string;
|
clientId: string;
|
||||||
clientSecret: string;
|
clientSecret: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const addMachineSecretTrend = new Trend('user_add_machine_secret_duration', true);
|
const addMachineSecretTrend = new Trend('user_add_machine_secret_duration', true);
|
||||||
@ -204,20 +221,23 @@ export type MachineKey = {
|
|||||||
const addMachineKeyTrend = new Trend('user_add_machine_key_duration', true);
|
const addMachineKeyTrend = new Trend('user_add_machine_key_duration', true);
|
||||||
export function addMachineKey(userId: string, org: Org, accessToken: string, publicKey?: string): Promise<MachineKey> {
|
export function addMachineKey(userId: string, org: Org, accessToken: string, publicKey?: string): Promise<MachineKey> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let response = http.asyncRequest('POST', url(`/management/v1/users/${userId}/keys`),
|
let response = http.asyncRequest(
|
||||||
JSON.stringify({
|
'POST',
|
||||||
type: 'KEY_TYPE_JSON',
|
url(`/management/v1/users/${userId}/keys`),
|
||||||
userId: userId,
|
JSON.stringify({
|
||||||
// base64 encoded public key
|
type: 'KEY_TYPE_JSON',
|
||||||
publicKey: publicKey
|
userId: userId,
|
||||||
}),
|
// base64 encoded public key
|
||||||
{
|
publicKey: publicKey,
|
||||||
headers: {
|
}),
|
||||||
authorization: `Bearer ${accessToken}`,
|
{
|
||||||
'Content-Type': 'application/json',
|
headers: {
|
||||||
'x-zitadel-orgid': org.organizationId,
|
authorization: `Bearer ${accessToken}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'x-zitadel-orgid': org.organizationId,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
);
|
||||||
response.then((res) => {
|
response.then((res) => {
|
||||||
check(res, {
|
check(res, {
|
||||||
'generate machine key status ok': (r) => r.status === 200,
|
'generate machine key status ok': (r) => r.status === 200,
|
||||||
|
@ -5,7 +5,7 @@ const GlobEntries = require('webpack-glob-entries');
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
mode: 'production',
|
mode: 'production',
|
||||||
entry: GlobEntries('./src/use_cases/*.ts'), // Generates multiple entry for each test
|
entry: GlobEntries('./src/use_cases/**/*.ts'), // Generates multiple entry for each test
|
||||||
output: {
|
output: {
|
||||||
path: path.join(__dirname, 'dist'),
|
path: path.join(__dirname, 'dist'),
|
||||||
libraryTarget: 'commonjs',
|
libraryTarget: 'commonjs',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user