Skip to content

Commit d0fb08c

Browse files
jomi-seJose Arroyo Rodriguez
and
Jose Arroyo Rodriguez
authored
(Feature) Get team audit logs through secure tunnel (#274)
Replace old endpoints with new ones --------- Signed-off-by: Jose Arroyo <jose.m.arroyo.se@gmail.com> Co-authored-by: Jose Arroyo Rodriguez <jose.arroyo@dashlane.com>
1 parent f0f559c commit d0fb08c

File tree

9 files changed

+92
-121
lines changed

9 files changed

+92
-121
lines changed

documentation/pages/business/audit-logs.mdx

+1-3
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,11 @@ The logs are output in JSON format, each line is a new log entry.
3333

3434
## Filtering the logs
3535

36-
With the following options you can filter the logs by start and end date, log type and category.
36+
With the following options you can filter the logs by start and end date.
3737

3838
```sh
3939
--start <start> start timestamp in ms (default: "0")
4040
--end <end> end timestamp in ms (default: "now")
41-
--type <type> log type
42-
--category <category> log category
4341
```
4442

4543
### Filtering by date

src/command-handlers/teamLogs.ts

+7-51
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,17 @@
1-
import { StartAuditLogsQueryParams, startAuditLogsQuery, getAuditLogQueryResults } from '../endpoints/index.js';
1+
import { getAuditLogs } from '../endpoints/index.js';
22
import { getTeamDeviceCredentials, jsonToCsv, epochTimestampToIso } from '../utils/index.js';
3-
import { GenericLog } from '../types/logs.js';
43
import { logger } from '../logger.js';
54

6-
export const runTeamLogs = async (options: {
7-
start: string;
8-
end: string;
9-
type: string;
10-
category: string;
11-
csv: boolean;
12-
humanReadable: boolean;
13-
}) => {
5+
export const runTeamLogs = async (options: { start: string; end: string; csv: boolean; humanReadable: boolean }) => {
146
const teamDeviceCredentials = getTeamDeviceCredentials();
15-
16-
const { start, end, type, category } = options;
7+
const { start, end } = options;
178

189
let logs = await getAuditLogs({
1910
teamDeviceCredentials,
20-
startDateRangeUnix: parseInt(start),
21-
endDateRangeUnix: parseInt(end),
22-
logType: type,
23-
category,
11+
queryParams: {
12+
startDateRangeUnixMs: parseInt(start),
13+
endDateRangeUnixMs: parseInt(end),
14+
},
2415
});
2516

2617
if (options.humanReadable) {
@@ -39,38 +30,3 @@ export const runTeamLogs = async (options: {
3930

4031
logs.forEach((log) => logger.content(JSON.stringify(log)));
4132
};
42-
43-
const MAX_RESULT = 1000;
44-
45-
export const getAuditLogs = async (params: StartAuditLogsQueryParams): Promise<GenericLog[]> => {
46-
const { teamDeviceCredentials } = params;
47-
48-
const { queryExecutionId } = await startAuditLogsQuery(params);
49-
50-
let result = await getAuditLogQueryResults({ teamDeviceCredentials, queryExecutionId, maxResults: MAX_RESULT });
51-
logger.debug(`Query state: ${result.state}`);
52-
53-
while (['QUEUED', 'RUNNING'].includes(result.state)) {
54-
await new Promise((resolve) => setTimeout(resolve, 2000));
55-
result = await getAuditLogQueryResults({ teamDeviceCredentials, queryExecutionId, maxResults: MAX_RESULT });
56-
logger.debug(`Query state: ${result.state}`);
57-
}
58-
59-
if (result.state !== 'SUCCEEDED') {
60-
throw new Error(`Query execution did not succeed: ${result.state}`);
61-
}
62-
63-
let logs = result.results;
64-
while (result.nextToken) {
65-
result = await getAuditLogQueryResults({
66-
teamDeviceCredentials,
67-
queryExecutionId,
68-
maxResults: MAX_RESULT,
69-
nextToken: result.nextToken,
70-
});
71-
logger.debug(`Query state: ${result.state}`);
72-
logs = logs.concat(result.results);
73-
}
74-
75-
return logs.map((log) => JSON.parse(log) as GenericLog);
76-
};

src/commands/team/index.ts

-2
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,6 @@ export const teamCommands = (params: { program: Command }) => {
4949
customParseTimestampMilliseconds,
5050
Date.now()
5151
)
52-
.option('--type <type>', 'log type')
53-
.option('--category <category>', 'log category')
5452
.option('--csv', 'Output in CSV format')
5553
.option('--human-readable', 'Output dates in human readable format')
5654
.action(runTeamLogs);

src/endpoints/getAuditLogs.ts

+63-55
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,17 @@
1-
import { requestTeamApi } from '../requestApi.js';
1+
import { apiConnect } from '../modules/tunnel-api-connect/apiconnect.js';
2+
import { logger } from '../logger.js';
23
import { TeamDeviceCredentials } from '../types.js';
4+
import { GenericLog } from '../types/logs.js';
35

46
export interface StartAuditLogsQueryParams {
5-
teamDeviceCredentials: TeamDeviceCredentials;
6-
77
/**
88
* The start of the date range to query audit logs by. The format is unix timestamp in seconds. Only the date is used, not the time.
99
*/
10-
startDateRangeUnix: number;
10+
startDateRangeUnixMs: number;
1111
/**
1212
* The end of the date range of to query audit logs by. The format is unix timestamp in seconds. Only the date is used, not the time.
1313
*/
14-
endDateRangeUnix: number;
15-
/**
16-
* The user ID of the author of the audit log.
17-
*/
18-
authorUserId?: number;
19-
/**
20-
* The ID of the user targeted by the audit log action.
21-
*/
22-
targetUserId?: number;
23-
/**
24-
* The ID of the sharing group targeted by the audit log action.
25-
*/
26-
sharingGroupId?: number;
27-
/**
28-
* The types of audit logs to filter by.
29-
*/
30-
logType?: string;
31-
/**
32-
* The categories audit logs to filter by.
33-
*/
34-
category?: string;
35-
/**
36-
* Additional properties to filter by. Refer to the specific audit log schema for property details.
37-
*/
38-
properties?: {
39-
propName: string;
40-
value: string;
41-
}[];
14+
endDateRangeUnixMs: number;
4215
}
4316

4417
export interface StartAuditLogsQueryOutput {
@@ -48,22 +21,13 @@ export interface StartAuditLogsQueryOutput {
4821
queryExecutionId: string;
4922
}
5023

51-
export const startAuditLogsQuery = (params: StartAuditLogsQueryParams) => {
52-
const { teamDeviceCredentials, ...payload } = params;
53-
return requestTeamApi<StartAuditLogsQueryOutput>({
54-
path: 'auditlogs-teamdevice/StartAuditLogsQuery',
55-
teamUuid: teamDeviceCredentials.uuid,
56-
teamDeviceKeys: {
57-
accessKey: teamDeviceCredentials.accessKey,
58-
secretKey: teamDeviceCredentials.secretKey,
59-
},
60-
payload,
61-
});
62-
};
24+
export interface StartAuditLogsQueryRequest {
25+
path: 'logs-teamdevice/StartAuditLogsQuery';
26+
input: StartAuditLogsQueryParams;
27+
output: StartAuditLogsQueryOutput;
28+
}
6329

6430
export interface GetAuditLogQueryResultsParams {
65-
teamDeviceCredentials: TeamDeviceCredentials;
66-
6731
/**
6832
* The ID associated with the query executed by the RequestAuditLogs endpoint.
6933
*/
@@ -93,15 +57,59 @@ export interface GetAuditLogQueryResultsOutput {
9357
nextToken?: string;
9458
}
9559

96-
export const getAuditLogQueryResults = (params: GetAuditLogQueryResultsParams) => {
97-
const { teamDeviceCredentials, ...payload } = params;
98-
return requestTeamApi<GetAuditLogQueryResultsOutput>({
99-
path: 'auditlogs-teamdevice/GetAuditLogQueryResults',
100-
teamUuid: teamDeviceCredentials.uuid,
101-
teamDeviceKeys: {
102-
accessKey: teamDeviceCredentials.accessKey,
103-
secretKey: teamDeviceCredentials.secretKey,
60+
export interface GetAuditLogQueryResultsRequest {
61+
path: 'logs-teamdevice/GetAuditLogQueryResults';
62+
input: GetAuditLogQueryResultsParams;
63+
output: GetAuditLogQueryResultsOutput;
64+
}
65+
66+
const MAX_RESULT = 1000;
67+
68+
export const getAuditLogs = async (params: {
69+
queryParams: StartAuditLogsQueryParams;
70+
teamDeviceCredentials: TeamDeviceCredentials;
71+
}): Promise<GenericLog[]> => {
72+
const { teamDeviceCredentials, queryParams } = params;
73+
74+
const api = await apiConnect({
75+
useProductionCertificate: true,
76+
});
77+
78+
const { queryExecutionId } = await api.sendSecureContent<StartAuditLogsQueryRequest>({
79+
...api,
80+
path: 'logs-teamdevice/StartAuditLogsQuery',
81+
payload: queryParams,
82+
authentication: {
83+
type: 'teamDevice',
84+
teamDeviceKeys: teamDeviceCredentials,
85+
teamUuid: teamDeviceCredentials.uuid,
10486
},
105-
payload,
10687
});
88+
89+
let result: GetAuditLogQueryResultsOutput | undefined;
90+
let logs: string[] = [];
91+
92+
do {
93+
await new Promise((resolve) => setTimeout(resolve, 2000));
94+
result = await api.sendSecureContent<GetAuditLogQueryResultsRequest>({
95+
...api,
96+
path: 'logs-teamdevice/GetAuditLogQueryResults',
97+
payload: { queryExecutionId, maxResults: MAX_RESULT, nextToken: result?.nextToken },
98+
authentication: {
99+
type: 'teamDevice',
100+
teamDeviceKeys: teamDeviceCredentials,
101+
teamUuid: teamDeviceCredentials.uuid,
102+
},
103+
});
104+
logger.debug(`Query state: ${result.state}`);
105+
if (result.state === 'SUCCEEDED') {
106+
logs = logs.concat(result.results);
107+
} else if (['QUEUED', 'RUNNING'].includes(result.state)) {
108+
await new Promise((resolve) => setTimeout(resolve, 2000));
109+
} else {
110+
throw new Error(`Query execution did not succeed: ${result.state}`);
111+
}
112+
} while (result.state !== 'SUCCEEDED' || result.nextToken);
113+
114+
return logs.map((log) => JSON.parse(log) as GenericLog);
107115
};

src/modules/auth/confidential-sso/index.ts

+1-5
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,7 @@ interface ConfidentialSSOParams {
1010

1111
export const doConfidentialSSOVerification = async ({ requestedLogin }: ConfidentialSSOParams) => {
1212
const api = await apiConnect({
13-
isProduction: true,
14-
enclavePcrList: [
15-
[3, 'dfb6428f132530b8c021bea8cbdba2c87c96308ba7e81c7aff0655ec71228122a9297fd31fe5db7927a7322e396e4c16'],
16-
[8, '4dbb92401207e019e132d86677857081d8e4d21f946f3561b264b7389c6982d3a86bcf9560cef4a2327eac5c5c6ab820'],
17-
],
13+
useProductionCertificate: true,
1814
});
1915
const requestLoginResponse = await api.sendSecureContent<RequestLogin2Request>({
2016
...api,

src/modules/tunnel-api-connect/apiconnect.ts

+15
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import sodium from 'libsodium-wrappers';
2+
import { EnclavePcr } from '@dashlane/nsm-attestation';
23
import { clientHello, terminateHello, SendSecureContentParams, sendSecureContent } from './steps/index.js';
34
import { ApiConnectParams, ApiConnect, ApiData, ApiRequestsDefault } from './types.js';
45
import { makeClientKeyPair, makeOrRefreshSession } from './utils/index.js';
@@ -15,13 +16,27 @@ const hasFullApiData = (data: Partial<ApiData>): data is ApiData => {
1516
return false;
1617
};
1718

19+
const getEnclavePcrList = (): EnclavePcr<string>[] => {
20+
if (process.env.DCLI_STAGING_HOST) {
21+
return [
22+
[3, '90528150e0f0537fa9e96b067137f6494d525f2fcfd15b478ce28ab2cfaf38dd4e24ad73f9d9d6f238a7f39f2d1956b7'],
23+
];
24+
}
25+
26+
return [
27+
[3, 'dfb6428f132530b8c021bea8cbdba2c87c96308ba7e81c7aff0655ec71228122a9297fd31fe5db7927a7322e396e4c16'],
28+
[8, '4dbb92401207e019e132d86677857081d8e4d21f946f3561b264b7389c6982d3a86bcf9560cef4a2327eac5c5c6ab820'],
29+
];
30+
};
31+
1832
/** Return an object that can be used to send secure content through the tunnel
1933
*/
2034
export const apiConnect = async (apiParametersIn: ApiConnectParams): Promise<ApiConnect> => {
2135
await sodium.ready;
2236

2337
const apiParameters = {
2438
...apiParametersIn,
39+
enclavePcrList: getEnclavePcrList(),
2540
...{ clientKeyPair: apiParametersIn.clientKeyPair ?? makeClientKeyPair() },
2641
};
2742

src/modules/tunnel-api-connect/steps/terminateHello.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ export const terminateHello = async (
1717
throw new SecureTunnelNotInitialized();
1818
}
1919

20-
const { clientKeyPair, attestation, isProduction, enclavePcrList } = params;
20+
const { clientKeyPair, attestation, useProductionCertificate, enclavePcrList } = params;
2121
const { tunnelUuid } = apiData.clientHello;
2222

2323
const { userData } = await verifyAttestation({
2424
attestation,
25-
useProductionCertificate: isProduction,
25+
useProductionCertificate,
2626
pcrs: enclavePcrList,
2727
});
2828

src/modules/tunnel-api-connect/steps/types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type sodium from 'libsodium-wrappers';
22
import { ApiRequestsDefault } from '../types.js';
3+
import { EnclavePcr } from '@dashlane/nsm-attestation';
34

45
export interface ApiEndpointResponse<T> {
56
requestId: string;
@@ -56,6 +57,7 @@ export interface SendSecureContentParams<R extends ApiRequestsDefault> {
5657

5758
export interface TerminateHelloParams {
5859
attestation: Buffer;
60+
enclavePcrList: EnclavePcr<string>[];
5961
}
6062

6163
export interface TerminateHelloResponse {

src/modules/tunnel-api-connect/types.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { EnclavePcr } from '@dashlane/nsm-attestation';
21
import type sodium from 'libsodium-wrappers';
32
import type { ClientHelloParsedResponse, SendSecureContentParams, TerminateHelloResponse } from './steps/index.js';
43

@@ -23,9 +22,8 @@ export interface ApiData {
2322
}
2423

2524
export interface ApiConnectParams {
26-
isProduction: boolean;
25+
useProductionCertificate: boolean;
2726
clientKeyPair?: sodium.KeyPair;
28-
enclavePcrList: EnclavePcr<string>[];
2927
}
3028

3129
export interface ApiConnectInternalParams extends ApiConnectParams {

0 commit comments

Comments
 (0)