Optional PII logging (#11322)

* Start with logger

* allow user to enable PII logging

* prefix the log
This commit is contained in:
Amir Omidi
2020-07-13 13:05:00 -07:00
committed by GitHub
parent 347c193455
commit ff979f90d1
7 changed files with 86 additions and 25 deletions

View File

@@ -92,6 +92,11 @@
"default": true, "default": true,
"description": "%config.noSystemKeychain%", "description": "%config.noSystemKeychain%",
"when": "isLinux || isWeb" "when": "isLinux || isWeb"
},
"azure.piiLogging": {
"type": "boolean",
"default": false,
"description": "%config.piiLogging%"
} }
} }
} }

View File

@@ -28,5 +28,6 @@
"config.azureCodeGrantMethod": "Code Grant Method", "config.azureCodeGrantMethod": "Code Grant Method",
"config.azureDeviceCodeMethod": "Device Code Method", "config.azureDeviceCodeMethod": "Device Code Method",
"config.noSystemKeychain": "Disable system keychain integration. Credentials will be stored in a flat file in the user's home directory.", "config.noSystemKeychain": "Disable system keychain integration. Credentials will be stored in a flat file in the user's home directory.",
"config.enableArcFeatures": "Should features related to Azure Arc be enabled (preview)" "config.enableArcFeatures": "Should features related to Azure Arc be enabled (preview)",
"config.piiLogging": "Should Personally Identifiable Information (PII) be logged in the console view locally"
} }

View File

@@ -22,6 +22,7 @@ import {
import { SimpleTokenCache } from '../simpleTokenCache'; import { SimpleTokenCache } from '../simpleTokenCache';
import { MemoryDatabase } from '../utils/memoryDatabase'; import { MemoryDatabase } from '../utils/memoryDatabase';
import { Logger } from '../../utils/Logger';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
export interface AccountKey { export interface AccountKey {
@@ -175,7 +176,7 @@ export abstract class AzureAuth implements vscode.Disposable {
if (ex.message) { if (ex.message) {
await vscode.window.showErrorMessage(ex.message); await vscode.window.showErrorMessage(ex.message);
} }
console.log(ex); Logger.error(ex);
} }
return oldAccount; return oldAccount;
} }
@@ -183,7 +184,7 @@ export abstract class AzureAuth implements vscode.Disposable {
public async getSecurityToken(account: azdata.Account, azureResource: azdata.AzureResource): Promise<TokenResponse | undefined> { public async getSecurityToken(account: azdata.Account, azureResource: azdata.AzureResource): Promise<TokenResponse | undefined> {
if (account.isStale === true) { if (account.isStale === true) {
console.log('Account was stale, no tokens being fetched'); Logger.log('Account was stale, no tokens being fetched');
return undefined; return undefined;
} }
@@ -212,7 +213,7 @@ export abstract class AzureAuth implements vscode.Disposable {
} else { } else {
// No expiration date, assume expired. // No expiration date, assume expired.
cachedTokens = undefined; cachedTokens = undefined;
console.info('Assuming expired token due to no expiration date - this is expected on first launch.'); Logger.log('Assuming expired token due to no expiration date - this is expected on first launch.');
} }
} }
@@ -223,22 +224,22 @@ export abstract class AzureAuth implements vscode.Disposable {
const baseToken = await this.getCachedToken(account.key); const baseToken = await this.getCachedToken(account.key);
if (!baseToken) { if (!baseToken) {
account.isStale = true; account.isStale = true;
console.log('Base token was empty, account is stale.'); Logger.log('Base token was empty, account is stale.');
return undefined; return undefined;
} }
try { try {
await this.refreshAccessToken(account.key, baseToken.refreshToken, tenant, resource); await this.refreshAccessToken(account.key, baseToken.refreshToken, tenant, resource);
} catch (ex) { } catch (ex) {
console.log(`Could not refresh access token for ${JSON.stringify(tenant)} - silently removing the tenant from the user's account.`); Logger.log(`Could not refresh access token for ${JSON.stringify(tenant)} - silently removing the tenant from the user's account.`);
console.log(`Actual error: ${JSON.stringify(ex?.response?.data ?? ex.message ?? ex, undefined, 2)}`); Logger.error(`Actual error: ${JSON.stringify(ex?.response?.data ?? ex.message ?? ex, undefined, 2)}`);
azureAccount.properties.tenants = azureAccount.properties.tenants.filter(t => t.id !== tenant.id); azureAccount.properties.tenants = azureAccount.properties.tenants.filter(t => t.id !== tenant.id);
continue; continue;
} }
cachedTokens = await this.getCachedToken(account.key, resource.id, tenant.id); cachedTokens = await this.getCachedToken(account.key, resource.id, tenant.id);
if (!cachedTokens) { if (!cachedTokens) {
console.log('Refresh access tokens didn not set cache'); Logger.log('Refresh access tokens didn not set cache');
return undefined; return undefined;
} }
} }
@@ -270,7 +271,7 @@ export abstract class AzureAuth implements vscode.Disposable {
} catch (ex) { } catch (ex) {
const msg = localize('azure.cacheErrrorRemove', "Error when removing your account from the cache."); const msg = localize('azure.cacheErrrorRemove', "Error when removing your account from the cache.");
vscode.window.showErrorMessage(msg); vscode.window.showErrorMessage(msg);
console.error('Error when removing tokens.', ex); Logger.error('Error when removing tokens.', ex);
} }
} }
@@ -292,7 +293,7 @@ export abstract class AzureAuth implements vscode.Disposable {
return await axios.post(uri, qs.stringify(postData), config); return await axios.post(uri, qs.stringify(postData), config);
} catch (ex) { } catch (ex) {
console.log('Unexpected error making Azure auth request', 'azureCore.postRequest', JSON.stringify(ex?.response?.data, undefined, 2)); Logger.log('Unexpected error making Azure auth request', 'azureCore.postRequest', JSON.stringify(ex?.response?.data, undefined, 2));
throw ex; throw ex;
} }
} }
@@ -309,7 +310,7 @@ export abstract class AzureAuth implements vscode.Disposable {
return await axios.get(uri, config); return await axios.get(uri, config);
} catch (ex) { } catch (ex) {
// Intercept and print error // Intercept and print error
console.log('Unexpected error making Azure auth request', 'azureCore.getRequest', JSON.stringify(ex?.response?.data, undefined, 2)); Logger.log('Unexpected error making Azure auth request', 'azureCore.getRequest', JSON.stringify(ex?.response?.data, undefined, 2));
// rethrow error // rethrow error
throw ex; throw ex;
} }
@@ -326,6 +327,7 @@ export abstract class AzureAuth implements vscode.Disposable {
const tenantUri = url.resolve(this.metadata.settings.armResource.endpoint, 'tenants?api-version=2019-11-01'); const tenantUri = url.resolve(this.metadata.settings.armResource.endpoint, 'tenants?api-version=2019-11-01');
try { try {
const tenantResponse = await this.makeGetRequest(token.token, tenantUri); const tenantResponse = await this.makeGetRequest(token.token, tenantUri);
Logger.pii('getTenants', tenantResponse.data);
const tenants: Tenant[] = tenantResponse.data.value.map((tenantInfo: TenantResponse) => { const tenants: Tenant[] = tenantResponse.data.value.map((tenantInfo: TenantResponse) => {
return { return {
id: tenantInfo.tenantId, id: tenantInfo.tenantId,
@@ -343,7 +345,7 @@ export abstract class AzureAuth implements vscode.Disposable {
return tenants; return tenants;
} catch (ex) { } catch (ex) {
console.log(ex); Logger.log(ex);
throw new Error('Error retrieving tenant information'); throw new Error('Error retrieving tenant information');
} }
} }
@@ -357,7 +359,7 @@ export abstract class AzureAuth implements vscode.Disposable {
const allSubs: Subscription[] = []; const allSubs: Subscription[] = [];
const tokens = await this.getSecurityToken(account, azdata.AzureResource.ResourceManagement); const tokens = await this.getSecurityToken(account, azdata.AzureResource.ResourceManagement);
if (!tokens) { if (!tokens) {
console.log('There were no resource management tokens to retrieve subscriptions from. Account is stale.'); Logger.log('There were no resource management tokens to retrieve subscriptions from. Account is stale.');
account.isStale = true; account.isStale = true;
} }
@@ -366,6 +368,7 @@ export abstract class AzureAuth implements vscode.Disposable {
const subscriptionUri = url.resolve(this.metadata.settings.armResource.endpoint, 'subscriptions?api-version=2019-11-01'); const subscriptionUri = url.resolve(this.metadata.settings.armResource.endpoint, 'subscriptions?api-version=2019-11-01');
try { try {
const subscriptionResponse = await this.makeGetRequest(token.token, subscriptionUri); const subscriptionResponse = await this.makeGetRequest(token.token, subscriptionUri);
Logger.pii('getSubscriptions', subscriptionResponse.data);
const subscriptions: Subscription[] = subscriptionResponse.data.value.map((subscriptionInfo: SubscriptionResponse) => { const subscriptions: Subscription[] = subscriptionResponse.data.value.map((subscriptionInfo: SubscriptionResponse) => {
return { return {
id: subscriptionInfo.subscriptionId, id: subscriptionInfo.subscriptionId,
@@ -375,7 +378,7 @@ export abstract class AzureAuth implements vscode.Disposable {
}); });
allSubs.push(...subscriptions); allSubs.push(...subscriptions);
} catch (ex) { } catch (ex) {
console.log(ex); Logger.error(ex);
throw new Error('Error retrieving subscription information'); throw new Error('Error retrieving subscription information');
} }
} }
@@ -389,6 +392,7 @@ export abstract class AzureAuth implements vscode.Disposable {
try { try {
const tokenUrl = `${this.loginEndpointUrl}${tenant}/oauth2/token`; const tokenUrl = `${this.loginEndpointUrl}${tenant}/oauth2/token`;
const tokenResponse = await this.makePostRequest(tokenUrl, postData); const tokenResponse = await this.makePostRequest(tokenUrl, postData);
Logger.pii(JSON.stringify(tokenResponse.data));
const tokenClaims = this.getTokenClaims(tokenResponse.data.access_token); const tokenClaims = this.getTokenClaims(tokenResponse.data.access_token);
const accessToken: AccessToken = { const accessToken: AccessToken = {
@@ -404,6 +408,7 @@ export abstract class AzureAuth implements vscode.Disposable {
refreshResponse = { accessToken, refreshToken, tokenClaims, expiresOn }; refreshResponse = { accessToken, refreshToken, tokenClaims, expiresOn };
} catch (ex) { } catch (ex) {
Logger.pii(JSON.stringify(ex?.response?.data));
if (ex?.response?.data?.error === 'interaction_required') { if (ex?.response?.data?.error === 'interaction_required') {
const shouldOpenLink = await this.openConsentDialog(tenant, resourceId); const shouldOpenLink = await this.openConsentDialog(tenant, resourceId);
if (shouldOpenLink === true) { if (shouldOpenLink === true) {
@@ -476,7 +481,7 @@ export abstract class AzureAuth implements vscode.Disposable {
const refreshToken = getTokenResponse?.refreshToken; const refreshToken = getTokenResponse?.refreshToken;
if (!accessToken || !refreshToken) { if (!accessToken || !refreshToken) {
console.log('Access or refresh token were undefined'); Logger.log('Access or refresh token were undefined');
const msg = localize('azure.refreshTokenError', "Error when refreshing your account."); const msg = localize('azure.refreshTokenError', "Error when refreshing your account.");
throw new Error(msg); throw new Error(msg);
} }
@@ -499,7 +504,7 @@ export abstract class AzureAuth implements vscode.Disposable {
await this.tokenCache.saveCredential(`${account.accountId}_access_${resourceId}_${tenantId}`, JSON.stringify(accessToken)); await this.tokenCache.saveCredential(`${account.accountId}_access_${resourceId}_${tenantId}`, JSON.stringify(accessToken));
await this.tokenCache.saveCredential(`${account.accountId}_refresh_${resourceId}_${tenantId}`, JSON.stringify(refreshToken)); await this.tokenCache.saveCredential(`${account.accountId}_refresh_${resourceId}_${tenantId}`, JSON.stringify(refreshToken));
} catch (ex) { } catch (ex) {
console.error('Error when storing tokens.', ex); Logger.error('Error when storing tokens.', ex);
throw new Error(msg); throw new Error(msg);
} }
} }

View File

@@ -28,6 +28,7 @@ import {
import { SimpleWebServer } from '../utils/simpleWebServer'; import { SimpleWebServer } from '../utils/simpleWebServer';
import { SimpleTokenCache } from '../simpleTokenCache'; import { SimpleTokenCache } from '../simpleTokenCache';
import { Logger } from '../../utils/Logger';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
function parseQuery(uri: vscode.Uri) { function parseQuery(uri: vscode.Uri) {
@@ -82,7 +83,7 @@ export class AzureAuthCodeGrant extends AzureAuth {
if (ex.msg) { if (ex.msg) {
vscode.window.showErrorMessage(ex.msg); vscode.window.showErrorMessage(ex.msg);
} }
console.log(ex); Logger.error(ex);
} }
if (!accessToken) { if (!accessToken) {
@@ -111,7 +112,7 @@ export class AzureAuthCodeGrant extends AzureAuth {
} catch (err) { } catch (err) {
const msg = localize('azure.serverCouldNotStart', 'Server could not start. This could be a permissions error or an incompatibility on your system. You can try enabling device code authentication from settings.'); const msg = localize('azure.serverCouldNotStart', 'Server could not start. This could be a permissions error or an incompatibility on your system. You can try enabling device code authentication from settings.');
vscode.window.showErrorMessage(msg); vscode.window.showErrorMessage(msg);
console.dir(err); Logger.error(JSON.stringify(err));
return undefined; return undefined;
} }
@@ -208,7 +209,7 @@ export class AzureAuthCodeGrant extends AzureAuth {
try { try {
await this.setCachedToken({ accountId: accessToken.key, providerId: this.metadata.id }, accessToken, refreshToken); await this.setCachedToken({ accountId: accessToken.key, providerId: this.metadata.id }, accessToken, refreshToken);
} catch (ex) { } catch (ex) {
console.log(ex); Logger.error(ex);
if (ex.msg) { if (ex.msg) {
vscode.window.showErrorMessage(ex.msg); vscode.window.showErrorMessage(ex.msg);
authCompleteDeferred.reject(ex); authCompleteDeferred.reject(ex);
@@ -237,7 +238,7 @@ export class AzureAuthCodeGrant extends AzureAuth {
try { try {
fileContents = await fs.readFile(filePath); fileContents = await fs.readFile(filePath);
} catch (ex) { } catch (ex) {
console.error(ex); Logger.error(ex);
res.writeHead(400); res.writeHead(400);
res.end(); res.end();
return; return;
@@ -252,11 +253,11 @@ export class AzureAuthCodeGrant extends AzureAuth {
}; };
server.on('/landing.css', (req, reqUrl, res) => { server.on('/landing.css', (req, reqUrl, res) => {
sendFile(res, path.join(mediaPath, 'landing.css'), 'text/css; charset=utf-8').catch(console.error); sendFile(res, path.join(mediaPath, 'landing.css'), 'text/css; charset=utf-8').catch(Logger.error);
}); });
server.on('/SignIn.svg', (req, reqUrl, res) => { server.on('/SignIn.svg', (req, reqUrl, res) => {
sendFile(res, path.join(mediaPath, 'SignIn.svg'), 'image/svg+xml').catch(console.error); sendFile(res, path.join(mediaPath, 'SignIn.svg'), 'image/svg+xml').catch(Logger.error);
}); });
server.on('/signin', (req, reqUrl, res) => { server.on('/signin', (req, reqUrl, res) => {
@@ -267,7 +268,7 @@ export class AzureAuthCodeGrant extends AzureAuth {
res.writeHead(400, { 'content-type': 'text/html' }); res.writeHead(400, { 'content-type': 'text/html' });
res.write(localize('azureAuth.nonceError', "Authentication failed due to a nonce mismatch, please close Azure Data Studio and try again.")); res.write(localize('azureAuth.nonceError', "Authentication failed due to a nonce mismatch, please close Azure Data Studio and try again."));
res.end(); res.end();
console.error('nonce no match', receivedNonce, nonce); Logger.error('nonce no match', receivedNonce, nonce);
return; return;
} }
res.writeHead(302, { Location: loginUrl }); res.writeHead(302, { Location: loginUrl });

View File

@@ -17,6 +17,7 @@ import { SimpleTokenCache } from './simpleTokenCache';
import { AzureAuth, TokenResponse } from './auths/azureAuth'; import { AzureAuth, TokenResponse } from './auths/azureAuth';
import { AzureAuthCodeGrant } from './auths/azureAuthCodeGrant'; import { AzureAuthCodeGrant } from './auths/azureAuthCodeGrant';
import { AzureDeviceCode } from './auths/azureDeviceCode'; import { AzureDeviceCode } from './auths/azureDeviceCode';
import { Logger } from '../utils/Logger';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
@@ -127,7 +128,7 @@ export class AzureAccountProvider implements azdata.AccountProvider, vscode.Disp
} }
if (this.authMappings.size === 0) { if (this.authMappings.size === 0) {
console.log('No auth method was enabled.'); Logger.log('No auth method was enabled.');
vscode.window.showErrorMessage(noAuthAvailable); vscode.window.showErrorMessage(noAuthAvailable);
return { canceled: true }; return { canceled: true };
} }
@@ -144,7 +145,7 @@ export class AzureAccountProvider implements azdata.AccountProvider, vscode.Disp
const pick = await vscode.window.showQuickPick(options, { canPickMany: false }); const pick = await vscode.window.showQuickPick(options, { canPickMany: false });
if (!pick) { if (!pick) {
console.log('No auth method was selected.'); Logger.log('No auth method was selected.');
vscode.window.showErrorMessage(noAuthSelected); vscode.window.showErrorMessage(noAuthSelected);
return { canceled: true }; return { canceled: true };
} }

View File

@@ -42,6 +42,7 @@ import * as azureResourceUtils from './azureResource/utils';
import * as utils from './utils'; import * as utils from './utils';
import * as loc from './localizedConstants'; import * as loc from './localizedConstants';
import { AzureResourceGroupService } from './azureResource/providers/resourceGroup/resourceGroupService'; import { AzureResourceGroupService } from './azureResource/providers/resourceGroup/resourceGroupService';
import { Logger } from './utils/Logger';
let extensionContext: vscode.ExtensionContext; let extensionContext: vscode.ExtensionContext;
@@ -76,6 +77,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<azurec
if (!storagePath) { if (!storagePath) {
return undefined; return undefined;
} }
updatePiiLoggingLevel(apiWrapper);
// Create the provider service and activate // Create the provider service and activate
initAzureAccountProvider(extensionContext, storagePath).catch((err) => console.log(err)); initAzureAccountProvider(extensionContext, storagePath).catch((err) => console.log(err));
@@ -164,5 +166,15 @@ async function onDidChangeConfiguration(e: vscode.ConfigurationChangeEvent, apiW
if (response === loc.reload) { if (response === loc.reload) {
await apiWrapper.executeCommand('workbench.action.reloadWindow'); await apiWrapper.executeCommand('workbench.action.reloadWindow');
} }
return;
}
if (e.affectsConfiguration('azure.piiLogging')) {
updatePiiLoggingLevel(apiWrapper);
} }
} }
function updatePiiLoggingLevel(apiWrapper: ApiWrapper) {
const piiLogging: boolean = apiWrapper.getExtensionConfiguration().get('piiLogging');
Logger.piiLogging = piiLogging;
}

View File

@@ -0,0 +1,36 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export class Logger {
private static _piiLogging: boolean = false;
static log(msg: any, ...vals: any[]) {
if (vals && vals.length > 0) {
return console.log(msg, vals);
}
console.log(msg);
}
static error(msg: any, ...vals: any[]) {
if (vals && vals.length > 0) {
return console.error(msg, vals);
}
console.error(msg);
}
static pii(msg: any, ...vals: any[]) {
if (this.piiLogging) {
Logger.log(msg, vals);
}
}
public static set piiLogging(val: boolean) {
this._piiLogging = val;
}
public static get piiLogging(): boolean {
return this._piiLogging;
}
}