Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for certificates #627

Merged
merged 9 commits into from
Nov 17, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 20 additions & 7 deletions dockerExtension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@
let loadStartTime = Date.now();

import * as assert from 'assert';
import * as fse from 'fs-extra';
import * as https from 'https';
import * as path from 'path';
import { CoreOptions } from 'request';
import * as request from 'request-promise-native';
import * as vscode from 'vscode';
import { AzureUserInput, callWithTelemetryAndErrorHandling, createTelemetryReporter, IActionContext, registerCommand as uiRegisterCommand, registerUIExtensionVariables, TelemetryProperties, UserCancelledError } from 'vscode-azureextensionui';
import { AzureUserInput, callWithTelemetryAndErrorHandling, callWithTelemetryAndErrorHandlingSync, createTelemetryReporter, IActionContext, registerCommand as uiRegisterCommand, registerUIExtensionVariables, TelemetryProperties, UserCancelledError } from 'vscode-azureextensionui';
import { ConfigurationParams, DidChangeConfigurationNotification, DocumentSelector, LanguageClient, LanguageClientOptions, Middleware, ServerOptions, TransportKind } from 'vscode-languageclient/lib/main';
import { viewACRLogs } from "./commands/azureCommands/acr-logs";
import { LogContentProvider } from "./commands/azureCommands/acr-logs-utils/logFileManager";
Expand Down Expand Up @@ -61,9 +64,12 @@ import { RootNode } from './explorer/models/rootNode';
import { browseAzurePortal } from './explorer/utils/browseAzurePortal';
import { browseDockerHub, dockerHubLogout } from './explorer/utils/dockerHubUtils';
import { ext } from './extensionVariables';
import { globAsync } from './helpers/async';
import { isLinux, isMac, isWindows } from './helpers/osVersion';
import { initializeTelemetryReporter, reporter } from './telemetry/telemetry';
import { addUserAgent } from './utils/addUserAgent';
import { AzureUtilityManager } from './utils/azureUtilityManager';
import { getTrustedCertificates } from './utils/getTrustedCertificates';
import { Keytar } from './utils/keytar';

export const FROM_DIRECTIVE_PATTERN = /^\s*FROM\s*([\w-\/:]*)(\s*AS\s*[a-z][a-z0-9-_\\.]*)?$/i;
Expand Down Expand Up @@ -109,12 +115,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
let activateStartTime = Date.now();

initializeExtensionVariables(ctx);

// Set up the user agent for all direct 'request' calls in the extension (must use ext.request)
let defaultRequestOptions = {};
addUserAgent(defaultRequestOptions);
ext.request = request.defaults(defaultRequestOptions);

await setRequestDefaults();
await callWithTelemetryAndErrorHandling('docker.activate', async function (this: IActionContext): Promise<void> {
this.properties.isActivationEvent = 'true';
this.measurements.mainFileLoad = (loadEndTime - loadStartTime) / 1000;
Expand Down Expand Up @@ -189,6 +190,16 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
});
}

async function setRequestDefaults(): Promise<void> {
// Set up the user agent for all direct 'request' calls in the extension (as long as they use ext.request)
// ... Trusted root certificate authorities
let caList = await getTrustedCertificates();
let defaultRequestOptions: CoreOptions = { agentOptions: { ca: caList } };
// ... User agent
addUserAgent(defaultRequestOptions);
ext.request = request.defaults(defaultRequestOptions);
}

async function createWebApp(context?: AzureImageTagNode | DockerHubImageTagNode): Promise<void> {
assert(!!context, "Should not be available through command palette");

Expand Down Expand Up @@ -324,6 +335,8 @@ namespace Configuration {
// Update endpoint and refresh explorer if needed
if (e.affectsConfiguration('docker')) {
docker.refreshEndpoint();
// tslint:disable-next-line: no-floating-promises
setRequestDefaults();
vscode.commands.executeCommand('vscode-docker.explorer.refresh');
}
}
Expand Down
16 changes: 13 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,14 @@
"default": "",
"description": "Host to connect to (same as setting the DOCKER_HOST environment variable)"
},
"docker.certificates": {
"type": "array",
"items": {
"type": "string"
},
"description": "Overrides the default certificate file and folder paths. On Mac and Windows, any certificates pointed to here will be added to those from the system.",
"default": null
},
"docker.languageserver.diagnostics.deprecatedMaintainer": {
"scope": "resource",
"type": "string",
Expand Down Expand Up @@ -851,21 +859,23 @@
"azure-arm-containerregistry": "^3.0.0",
"azure-arm-resource": "^2.0.0-preview",
"azure-arm-website": "^1.0.0-preview",
"deep-equal": "^1.0.1",
"dockerfile-language-server-nodejs": "^0.0.19",
"azure-storage": "^2.8.1",
"clipboardy": "^1.2.3",
"deep-equal": "^1.0.1",
"dockerfile-language-server-nodejs": "^0.0.19",
"dockerode": "^2.5.1",
"fs-extra": "^6.0.1",
"glob": "7.1.2",
"gradle-to-js": "^1.0.1",
"mac-ca": "^1.0.4",
"moment": "^2.19.3",
"opn": "^5.2.0",
"pom-parser": "^1.1.1",
"request-promise-native": "^1.0.5",
"semver": "^5.5.1",
"tar": "^4.4.6",
"vscode-azureextensionui": "^0.19.0",
"vscode-languageclient": "^4.4.0"
"vscode-languageclient": "^4.4.0",
"win-ca": "^2.2.0"
}
}
64 changes: 64 additions & 0 deletions thirdpartynotices.txt
Original file line number Diff line number Diff line change
Expand Up @@ -447,3 +447,67 @@ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

14. win-ca (https://github.com/ukoloff/win-ca)

BSD 3-Clause License

Copyright (c) 2016, Stas Ukolov
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

15. mac-ca (https://github.com/jfromaniello/mac-ca)

BSD 3-Clause License

Copyright (c) 2018, José F. Romaniello
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
107 changes: 107 additions & 0 deletions utils/getTrustedCertificates.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import * as fse from 'fs-extra';
import * as https from 'https';
import * as path from 'path';
import * as vscode from 'vscode';
import { callWithTelemetryAndErrorHandling, IActionContext } from "vscode-azureextensionui";
import { ext } from '../extensionVariables';
import { globAsync } from '../helpers/async';
import { isLinux, isMac, isWindows } from '../helpers/osVersion';

let _systemCertificates: (string | Buffer)[] | undefined;

export async function getTrustedCertificates(): Promise<(string | Buffer)[]> {
// tslint:disable-next-line:no-function-expression
return callWithTelemetryAndErrorHandling('docker.certificates', async function (this: IActionContext): Promise<(string | Buffer)[]> {
this.suppressTelemetry = true;

let systemCerts = getCertificatesFromSystem();

let certificatePaths = vscode.workspace.getConfiguration('docker').get<string[] | undefined>('certificates');
ejizba marked this conversation as resolved.
Show resolved Hide resolved
if (certificatePaths === undefined) {
if (isLinux()) {
certificatePaths = ['/etc/ssl/certs/ca-certificates', '/etc/openssl/certs', '/etc/pki/tls/certs', '/usr/local/share/certs'];
ejizba marked this conversation as resolved.
Show resolved Hide resolved
} else {
certificatePaths = [];
}
}
let folderCerts = await getCertificatesFromPaths(certificatePaths);

this.properties.fromSystem = String(systemCerts.length);
ejizba marked this conversation as resolved.
Show resolved Hide resolved
this.properties.fromPaths = String(folderCerts.length);

let certificates = systemCerts;
certificates.push(...folderCerts);

return certificates;
});
}

async function getCertificatesFromPaths(paths: string[]): Promise<string[]> {
let certs: string[] = [];
for (let certPath of paths) {
if (!path.isAbsolute(certPath)) {
// tslint:disable-next-line: no-floating-promises
ext.ui.showWarningMessage(`Certificate path "${certPath}" is not an absolute path, ignored.`);
} else {
let isFile = false;
let isFolder = false;
try {
if (await fse.pathExists(certPath)) {
let stat = await fse.stat(certPath);
isFolder = stat.isDirectory();
isFile = stat.isFile();
}
} catch {
// Ignore (could be permission issues, for instance)
}

if (isFolder) {
let files = await globAsync('**', { absolute: true, nodir: true, cwd: certPath });
certs.push(...files);
} else if (isFile) {
certs.push(certPath);
} else {
console.log(`Could not find certificate path "${certPath}.`);
}
}
}

return certs;
}

function getCertificatesFromSystem(): (string | Buffer)[] {
if (!_systemCertificates) {
// {win,mac}-ca automatically read trusted certificate authorities from the system and place them into the global
// Node agent. We don't want them in the global agent because that will affect all other extensions
// loaded in the same process, which will make them behave inconsistently depending on whether we're loaded.
let previousCertificateAuthorities = https.globalAgent.options.ca;
let certificates: string | Buffer | (string | Buffer)[] = [];

try {
if (isWindows()) {
require('win-ca');
} else if (isMac()) {
require('mac-ca');
} else if (isLinux()) {
}
} finally {
certificates = https.globalAgent.options.ca;
https.globalAgent.options.ca = previousCertificateAuthorities;
}

if (!certificates) {
certificates = [];
} else if (!Array.isArray(certificates)) {
certificates = [certificates];
}

_systemCertificates = certificates;
}

return _systemCertificates;
}