[Azure Arc] Update MIAA model to include encryption properties (#21702)

This commit is contained in:
Cheena Malhotra
2023-01-24 16:20:35 -08:00
committed by GitHub
parent 427a859c63
commit ef240a9a63
6 changed files with 107 additions and 12 deletions

View File

@@ -190,3 +190,7 @@ export namespace cssStyles {
}
export const iconSize = '20px';
export const encryptOption = 'encrypt';
export const trustServerCertificateOption = 'trustServerCertificate';
export const encryptReadMoreLink = 'https://learn.microsoft.com/sql/database-engine/configure-windows/enable-encrypted-connections-to-the-database-engine';

View File

@@ -142,12 +142,21 @@ export const controllerPassword = localize('arc.controllerPassword', "Controller
export const username = localize('arc.username', "Username");
export const password = localize('arc.password', "Password");
export const rememberPassword = localize('arc.rememberPassword', "Remember Password");
export const encrypt = localize('arc.encrypt', "Encrypt");
export const encryptDescription = localize('arc.encryptDescription', "When true, SQL Server uses SSL encryption for all data sent between the client and server if the server has a certificate installed.");
export const trustServerCertificate = localize('arc.trustServerCertificate', "Trust Server Certificate");
export const trustServerCertDescription = localize('arc.trustServerCertDescription', "When true (and encrypt=true), SQL Server uses SSL encryption for all data sent between the client and server without validating the server certificate.");
export const enableTrustServerCert = localize('arc.enableTrustServerCert', "Enable Trust Server Certificate");
export const msgPromptSSLCertificateValidationFailed = localize('arc.msgPromptSSLCertificateValidationFailed', 'Encryption was enabled on this connection, review your SSL and certificate configuration for the target SQL Server, or set \'Trust server certificate\' to \'true\' in the settings file. Note: A self-signed certificate offers only limited protection and is not a recommended practice for production environments. Do you want to enable \'Trust server certificate\' on this connection and retry?');
export const connect = localize('arc.connect', "Connect");
export const readMore = localize('arc.readMore', "Read more");
export const cancel = localize('arc.cancel', "Cancel");
export const apply = localize('arc.apply', "Apply");
export const ok = localize('arc.ok', "Ok");
export const on = localize('arc.on', "On");
export const off = localize('arc.off', "Off");
export const booleantrue = localize('arc.booleantrue', "True");
export const booleanfalse = localize('arc.booleanfalse', "False");
export const notConfigured = localize('arc.notConfigured', "Not Configured");
// Database States - see https://docs.microsoft.com/sql/relational-databases/databases/database-states

View File

@@ -219,7 +219,10 @@ export class MiaaModel extends ResourceModel {
saveProfile: true,
id: '',
groupId: undefined,
options: {}
options: {
encrypt: this._miaaInfo.encrypt || true,
trustServerCertificate: this._miaaInfo.trustServerCertificate || false
}
};
}
@@ -240,6 +243,8 @@ export class MiaaModel extends ResourceModel {
this._activeConnectionId = connectionProfile.id;
this.info.connectionId = connectionProfile.id;
this._miaaInfo.userName = connectionProfile.userName;
this._miaaInfo.encrypt = connectionProfile.options.encrypt;
this._miaaInfo.trustServerCertificate = connectionProfile.options.trustServerCertificate;
await this._treeDataProvider.saveControllers();
}
@@ -270,6 +275,5 @@ export class MiaaModel extends ResourceModel {
this._databaseTimeWindow.set(dbName, ['', '']);
}
}
}
}

View File

@@ -20,7 +20,9 @@ declare module 'arc' {
}
export type MiaaResourceInfo = ResourceInfo & {
userName?: string
userName?: string,
encrypt?: string,
trustServerCertificate?: boolean
};
export type PGResourceInfo = ResourceInfo & {

View File

@@ -8,11 +8,15 @@ import * as vscode from 'vscode';
import { Deferred } from '../../common/promise';
import * as loc from '../../localizedConstants';
import { createCredentialId } from '../../common/utils';
import { credentialNamespace } from '../../constants';
import * as constants from '../../constants';
import { InitializingComponent } from '../components/initializingComponent';
import { ResourceModel } from '../../models/resourceModel';
import { ControllerModel } from '../../models/controllerModel';
export interface IReconnectAction {
(profile: azdata.IConnectionProfile): Promise<boolean>;
}
export abstract class ConnectToSqlDialog extends InitializingComponent {
protected modelBuilder!: azdata.ModelBuilder;
@@ -20,6 +24,9 @@ export abstract class ConnectToSqlDialog extends InitializingComponent {
protected usernameInputBox!: azdata.InputBoxComponent;
protected passwordInputBox!: azdata.InputBoxComponent;
protected rememberPwCheckBox!: azdata.CheckBoxComponent;
protected encryptSelectBox!: azdata.DropDownComponent;
protected trustServerCertificateSelectBox!: azdata.DropDownComponent;
private options: { [name: string]: any } = {};
protected _completionPromise = new Deferred<azdata.IConnectionProfile | undefined>();
@@ -29,7 +36,11 @@ export abstract class ConnectToSqlDialog extends InitializingComponent {
}
public showDialog(dialogTitle: string, connectionProfile?: azdata.IConnectionProfile): azdata.window.Dialog {
const dialog = azdata.window.createModelViewDialog(dialogTitle);
const dialog = azdata.window.createModelViewDialog(dialogTitle, undefined, 'narrow');
const trueCategory: azdata.CategoryValue = { displayName: loc.booleantrue, name: 'true' }
const falseCategory: azdata.CategoryValue = { displayName: loc.booleanfalse, name: 'false' }
const booleanCategoryValues: azdata.CategoryValue[] = [trueCategory, falseCategory];
dialog.cancelButton.onClick(() => this.handleCancel());
dialog.registerContent(async view => {
this.modelBuilder = view.modelBuilder;
@@ -47,13 +58,22 @@ export abstract class ConnectToSqlDialog extends InitializingComponent {
.withProps({
inputType: 'password',
value: connectionProfile?.password
})
.component();
}).component();
this.rememberPwCheckBox = this.modelBuilder.checkBox()
.withProps({
label: loc.rememberPassword,
checked: connectionProfile?.savePassword
}).component();
this.encryptSelectBox = this.modelBuilder.dropDown()
.withProps({
values: booleanCategoryValues,
value: connectionProfile?.options[constants.encryptOption] ? trueCategory : falseCategory
}).component();
this.trustServerCertificateSelectBox = this.modelBuilder.dropDown()
.withProps({
values: booleanCategoryValues,
value: connectionProfile?.options[constants.trustServerCertificateOption] ? trueCategory : falseCategory
}).component();
let formModel = this.modelBuilder.formContainer()
.withFormItems([{
@@ -73,6 +93,18 @@ export abstract class ConnectToSqlDialog extends InitializingComponent {
}, {
component: this.rememberPwCheckBox,
title: ''
}, {
component: this.encryptSelectBox,
title: loc.encrypt,
layout: {
info: loc.encryptDescription,
}
}, {
component: this.trustServerCertificateSelectBox,
title: loc.trustServerCertificate,
layout: {
info: loc.trustServerCertDescription,
}
}
],
title: ''
@@ -94,6 +126,10 @@ export abstract class ConnectToSqlDialog extends InitializingComponent {
if (!this.serverNameInputBox.value || !this.usernameInputBox.value || !this.passwordInputBox.value) {
return false;
}
this.options.encrypt = this.encryptSelectBox.value;
this.options.trustServerCertificate = this.trustServerCertificateSelectBox.value;
const connectionProfile: azdata.IConnectionProfile = {
serverName: this.serverNameInputBox.value,
databaseName: '',
@@ -109,10 +145,15 @@ export abstract class ConnectToSqlDialog extends InitializingComponent {
groupId: undefined,
options: this.options
};
return await this.connect(connectionProfile);
}
private async connect(connectionProfile: azdata.IConnectionProfile): Promise<boolean> {
const result = await azdata.connection.connect(connectionProfile, false, false);
if (result.connected) {
connectionProfile.id = result.connectionId!;
const credentialProvider = await azdata.credentials.getProvider(credentialNamespace);
const credentialProvider = await azdata.credentials.getProvider(constants.credentialNamespace);
if (connectionProfile.savePassword) {
await credentialProvider.saveCredential(createCredentialId(this._controllerModel.info.id, this._model.info.resourceType, this._model.info.name), connectionProfile.password);
} else {
@@ -123,10 +164,40 @@ export abstract class ConnectToSqlDialog extends InitializingComponent {
}
else {
vscode.window.showErrorMessage(this.connectionFailedMessage(result.errorMessage));
return false;
// Show error with instructions for MSSQL Provider Encryption error code -2146893019 thrown by SqlClient when certificate validation fails.
if (result.errorCode === -2146893019) {
return this.showInstructionTextAsWarning(connectionProfile, async updatedConnection => {
return await this.connect(updatedConnection);
});
} else {
return false;
}
}
}
private async showInstructionTextAsWarning(profile: azdata.IConnectionProfile, reconnectAction: IReconnectAction): Promise<boolean> {
while (true) {
const selection = await vscode.window.showWarningMessage(
loc.msgPromptSSLCertificateValidationFailed,
{ modal: false },
...[
loc.enableTrustServerCert,
loc.readMore,
loc.cancel
]);
if (selection === loc.enableTrustServerCert) {
profile.options.encrypt = true;
profile.options.trustServerCertificate = true;
return await reconnectAction(profile);
} else if (selection === loc.readMore) {
vscode.env.openExternal(vscode.Uri.parse(constants.encryptReadMoreLink));
// Show the dialog again so the user can still pick yes or no after they've read the docs
continue;
} else {
return false;
}
}
}
protected abstract get providerName(): string;
protected abstract connectionFailedMessage(error: any): string;

View File

@@ -110,10 +110,15 @@ export class ControllerTreeNode extends TreeNode {
node = new PostgresTreeNode(postgresModel, this.model);
break;
case ResourceType.sqlManagedInstances:
// Fill in the username too if we already have it
(resourceInfo as MiaaResourceInfo).userName = (this.model.info.resources.find(info =>
// Fill in the username and connection properties too if we already have them
let miaaResourceInfo = this.model.info.resources.find(info =>
info.name === resourceInfo.name &&
info.resourceType === resourceInfo.resourceType) as MiaaResourceInfo)?.userName;
info.resourceType === resourceInfo.resourceType) as MiaaResourceInfo;
if (miaaResourceInfo) {
(resourceInfo as MiaaResourceInfo).userName = miaaResourceInfo.userName;
(resourceInfo as MiaaResourceInfo).encrypt = miaaResourceInfo.encrypt;
(resourceInfo as MiaaResourceInfo).trustServerCertificate = miaaResourceInfo.trustServerCertificate;
}
const miaaModel = new MiaaModel(this.model, resourceInfo, registration, this._treeDataProvider);
node = new MiaaTreeNode(miaaModel, this.model);
break;