### Packages impacted by this PR
`@azure-tools/test-recorder` - Adding the central sanitizers

### Issues associated with this PR
**References:**
- https://github.com/Azure/azure-sdk-for-java/pull/39700
- https://github.com/Azure/azure-sdk-for-python/pull/35196
- And the patterns found

### Describe the problem that is addressed by this PR
- Introducing fallback sanitizers into the test recorder to handle
potential secret leaks.
- The new sanitizers are designed to work in conjunction with the
existing `handleEnvSetup` mechanism and the fake secrets.
  - The sanitizers include:
- `BodyKeySanitizers` that redact sensitive information in the JSON body
of the requests.
- `FindReplaceSanitizers` that redact sensitive information based on
provided regular expressions.
- `HeaderSanitizers` that redact sensitive information in the headers of
the requests.

## Tests
I've ran the tests for the following and they work fine
- [x] recorder
- [x] template
- [x] notification-hubs (needed to make a few fixes for browser tests in
notification hubs which do feel like unrelated to this PR, but fixing
them here anyway.)

___Currently only these three packages depend on recorder v4.___

## Future work (future PRs)
- Once this PR is merged, cherrypick the commit and release a hotfix 3.x
version
- Add more tests at some point
This commit is contained in:
Harsha Nalluru 2024-04-16 16:36:59 -07:00 коммит произвёл GitHub
Родитель 3ca26eea7a
Коммит b047b37941
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
7 изменённых файлов: 171 добавлений и 3 удалений

Просмотреть файл

@ -2,5 +2,5 @@
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "js",
"TagPrefix": "js/notificationhubs/notification-hubs",
"Tag": "js/notificationhubs/notification-hubs_44f522d99a"
"Tag": "js/notificationhubs/notification-hubs_0718648c50"
}

Просмотреть файл

@ -1,5 +1,5 @@
# Used in most samples. Retrieve these values from a Azure Notification Hub in the Azure Portal.
NOTIFICATIONHUBS_CONNECTION_STRING="<connection string>"
NOTIFICATION_HUB_CONNECTION_STRING="<connection string>"
# Used in most samples. Provide the name of a Notification Hub within a namespace.
NOTIFICATION_HUB_NAME="<hub name>"

Просмотреть файл

@ -3,6 +3,7 @@
import { Recorder, RecorderStartOptions, env } from "@azure-tools/test-recorder";
import { NotificationHubsClientContext, createClientContext } from "../../../src/api/index.js";
import { isBrowser } from "@azure/core-util";
const replaceableVariables: { [k: string]: string } = {
// Used in record and playback modes
@ -30,6 +31,24 @@ export async function createRecordedClientContext(
recorder: Recorder,
): Promise<NotificationHubsClientContext> {
await recorder.start(recorderOptions);
if (isBrowser) {
// there are timestamps in the body, so do not match body
await recorder.setMatcher("BodilessMatcher");
await recorder.addSanitizers(
{
// looks like the registration id is dynamic, redacting it instead
generalSanitizers: [
{
regex: true,
target: "registrations/(?<secret>.*?)?api-version=",
value: "registration-id-redacted",
groupForReplace: "secret",
},
],
},
["record", "playback"],
);
}
if (!env.NOTIFICATION_HUB_CONNECTION_STRING || !env.NOTIFICATION_HUB_NAME) {
throw new Error(

Просмотреть файл

@ -1,5 +1,16 @@
# Release History
## 4.1.0
### Features Added
- Introduced fallback sanitizers into the test recorder to handle potential secret leaks.
- The new sanitizers are designed to work in conjunction with the existing `handleEnvSetup` mechanism and the fake secrets.
- The sanitizers include:
- `BodyKeySanitizers` that redact sensitive information in the JSON body of the requests.
- `FindReplaceSanitizers` that redact sensitive information based on provided regular expressions.
- `HeaderSanitizers` that redact sensitive information in the headers of the requests.
## 4.0.0 (2024-04-09)
### Features Added

Просмотреть файл

@ -35,6 +35,7 @@ import { decodeBase64 } from "./utils/encoding.js";
import { AdditionalPolicyConfig } from "@azure/core-client";
import { isVitestTestContext, TestInfo, VitestSuite } from "./testInfo.js";
import { env } from "./utils/env.js";
import { fallbackSanitizers } from "./utils/fallbackSanitizers.js";
/**
* Caculates session file path and JSON assets path from test context
@ -355,6 +356,9 @@ export class Recorder {
options.envSetupForPlayback,
);
// Fallback sanitizers to be added in both record/playback modes
await fallbackSanitizers(this.httpClient, Recorder.url, this.recordingId);
// Sanitizers to be added only in record mode
if (isRecordMode() && options.sanitizerOptions) {
// Makes a call to the proxy-tool to add the sanitizers for the current recording id

Просмотреть файл

@ -0,0 +1,134 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import { HttpClient } from "@azure/core-rest-pipeline";
import { addSanitizers } from "../sanitizer.js";
import { BodyKeySanitizer, FindReplaceSanitizer, HeaderSanitizer } from "./utils.js";
const JSON_BODY_KEYS_TO_REDACT = [
"authHeader",
"accountKey",
"accessToken",
"accountName",
"applicationId",
"apiKey",
"client_secret",
"connectionString",
"url",
"host",
"password",
"userName",
"applicationSecret",
"aliasSecondaryConnectionString",
"aliasPrimaryConnectionString",
"primaryKey",
"secondaryKey",
"adminPassword.value",
"administratorLoginPassword",
"runAsPassword",
"adminPassword",
"accessSAS",
"WEBSITE_AUTH_ENCRYPTION_KEY",
"decryptionKey",
"primaryMasterKey",
"primaryReadonlyMasterKey",
"secondaryMasterKey",
"secondaryReadonlyMasterKey",
"certificatePassword",
"clientSecret",
"keyVaultClientSecret",
"authHeader",
"httpHeader",
"encryptedCredential",
"appkey",
"functionKey",
"atlasKafkaPrimaryEndpoint",
"atlasKafkaSecondaryEndpoint",
"certificatePassword",
"storageAccountPrimaryKey",
"privateKey",
"fencingClientPassword",
"acrToken",
"scriptUrlSasToken",
"azureBlobSource.containerUrl",
"properties.DOCKER_REGISTRY_SEVER_PASSWORD",
];
const BODY_REGEXES_TO_REDACT = [
"(?:(Password|User ID)=)(?<secret>.*)(?:;)",
"client_secret=(?<secret>[^&]+)",
"<PrimaryKey>(?<secret>.*?)</PrimaryKey>",
"<SecondaryKey>(?<secret>.*?)</SecondaryKey>",
"<UserDelegationKey>.*?<SignedOid>(?<secret>.*?)</SignedOid>.*?</UserDelegationKey>",
"<UserDelegationKey>.*?<SignedTid>(?<secret>.*?)</SignedTid>.*?</UserDelegationKey>",
"<UserDelegationKey>.*?<Value>(?<secret>.*?)</Value>.*?</UserDelegationKey>",
'SharedAccessKey=(?<secret>[^;\\"]+)',
'AccountKey=(?<secret>[^;\\"]+)',
'accesskey=(?<secret>[^;\\"]+)',
'AccessKey=(?<secret>[^;\\"]+)',
'Secret=(?<secret>[^;\\"]+)',
"access_token=(?<secret>.*?)(?=&|$)",
"refresh_token=(?<secret>.*?)(?=&|$)",
'(?:(sv|sig|se|srt|ss|sp)=)(?<secret>[^&\\"]*)',
];
const URL_REGEX = "(?<=http://|https://)([^/?]+)";
const HEADER_KEYS_TO_REDACT = [
"Ocp-Apim-Subscription-Key",
"api-key",
"x-api-key",
"subscription-key",
"x-ms-encryption-key",
"sshPassword",
];
export async function fallbackSanitizers(
httpClient: HttpClient,
url: string,
recordingId: string,
): Promise<void> {
const bodyKeySanitizers: BodyKeySanitizer[] = JSON_BODY_KEYS_TO_REDACT.map((prop) => ({
jsonPath: `$..${prop}`, // Handles the request body
value: "REDACTED",
}));
const generalSanitizers: FindReplaceSanitizer[] = BODY_REGEXES_TO_REDACT.map((regex) => ({
value: "REDACTED",
regex: true,
groupForReplace: "secret",
target: regex,
}));
const headerSanitizers: HeaderSanitizer[] = [
{
key: "Operation-location",
groupForReplace: "secret",
regex: true,
target: URL_REGEX,
value: "REDACTED",
},
{
key: "ServiceBusDlqSupplementaryAuthorization",
groupForReplace: "secret",
regex: true,
target: '(?:(sv|sig|se|srt|ss|sp)=)(?<secret>[^&\\"]+)',
value: "REDACTED",
},
{
key: "ServiceBusSupplementaryAuthorization",
groupForReplace: "secret",
regex: true,
target: '(?:(sv|sig|se|srt|ss|sp)=)(?<secret>[^&\\"]+)',
value: "REDACTED",
},
];
const headersForRemoval: string[] = HEADER_KEYS_TO_REDACT;
await addSanitizers(httpClient, url, recordingId, {
bodyKeySanitizers,
generalSanitizers,
removeHeaderSanitizer: { headersForRemoval },
headerSanitizers,
});
}

Просмотреть файл

@ -133,7 +133,7 @@ export function isStringSanitizer(sanitizer: FindReplaceSanitizer): sanitizer is
*
* If the body is NOT a JSON object, this sanitizer will NOT be applied.
*/
type BodyKeySanitizer = {
export type BodyKeySanitizer = {
regex?: string;
value?: string;