Skip to content

Fix ggshield retrieval with proxy #80

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

Merged
merged 6 commits into from
Apr 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ repos:
stages: [commit-msg]
additional_dependencies: [ggcommitizen]

- repo: https://github.com/pre-commit/mirrors-prettier # to format JSON, YAML and markdown files among others
rev: v2.6.2 # Keep synchronize with .gitlab-ci.yml
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v3.1.0
hooks:
- id: prettier

Expand Down
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,6 @@
"@types/node": "20.2.5",
"@types/simple-mock": "^0.8.6",
"@types/tar": "^6.1.13",
"@types/unzipper": "^0.10.10",
"@types/vscode": "^1.81.0",
"@typescript-eslint/eslint-plugin": "^5.59.8",
"@typescript-eslint/parser": "^5.59.8",
Expand All @@ -169,7 +168,7 @@
},
"dependencies": {
"adm-zip": "^0.5.16",
"sync-request": "^6.1.0",
"axios": "^1.9.0",
"tar": "^7.4.3"
}
}
4 changes: 2 additions & 2 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ function registerOpenViewsCommands(
);
}

export function activate(context: ExtensionContext) {
export async function activate(context: ExtensionContext) {
const outputChannel = window.createOutputChannel("GitGuardian");
let configuration = getConfiguration(context, outputChannel);
let configuration = await getConfiguration(context, outputChannel);

const ggshieldResolver = new GGShieldResolver(
outputChannel,
Expand Down
13 changes: 7 additions & 6 deletions src/lib/ggshield-configuration-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,27 @@ import { getGGShield } from "./ggshield-resolver-utils";
* Retrieve configuration from settings
* @returns {GGShieldConfiguration} from the extension settings
*/
export function getConfiguration(
export async function getConfiguration(
context: ExtensionContext,
outputChannel: OutputChannel
): GGShieldConfiguration {
outputChannel: OutputChannel,
): Promise<GGShieldConfiguration> {
const config = workspace.getConfiguration("gitguardian");

const ggshieldPath: string | undefined = config.get("GGShieldPath");
const apiUrl: string | undefined = config.get("apiUrl");
const allowSelfSigned: boolean = config.get("allowSelfSigned", false);

const pathToGGShield: string = getGGShield(
const pathToGGShield: string = await getGGShield(
os.platform(),
os.arch(),
context,
outputChannel
outputChannel,
allowSelfSigned,
);

return new GGShieldConfiguration(
pathToGGShield,
apiUrl,
allowSelfSigned || false
allowSelfSigned || false,
);
}
109 changes: 68 additions & 41 deletions src/lib/ggshield-resolver-utils.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,28 @@
import * as path from "path";
import * as fs from "fs";
import * as tar from "tar";
import axios, { AxiosRequestConfig } from "axios";
import { Agent } from "https";

const AdmZip = require("adm-zip");

Check warning on line 7 in src/lib/ggshield-resolver-utils.ts

View workflow job for this annotation

GitHub Actions / build-and-test (macos-latest)

Variable name `AdmZip` must match one of the following formats: camelCase, UPPER_CASE

Check warning on line 7 in src/lib/ggshield-resolver-utils.ts

View workflow job for this annotation

GitHub Actions / build-and-test (windows-latest)

Variable name `AdmZip` must match one of the following formats: camelCase, UPPER_CASE

Check warning on line 7 in src/lib/ggshield-resolver-utils.ts

View workflow job for this annotation

GitHub Actions / build-and-test (ubuntu-latest)

Variable name `AdmZip` must match one of the following formats: camelCase, UPPER_CASE
import { ExtensionContext, OutputChannel } from "vscode";

const defaultRequestConfig = {
headers: { "User-Agent": "GitGuardian-VSCode-Extension" },

Check warning on line 11 in src/lib/ggshield-resolver-utils.ts

View workflow job for this annotation

GitHub Actions / build-and-test (macos-latest)

Object Literal Property name `User-Agent` must match one of the following formats: camelCase

Check warning on line 11 in src/lib/ggshield-resolver-utils.ts

View workflow job for this annotation

GitHub Actions / build-and-test (windows-latest)

Object Literal Property name `User-Agent` must match one of the following formats: camelCase

Check warning on line 11 in src/lib/ggshield-resolver-utils.ts

View workflow job for this annotation

GitHub Actions / build-and-test (ubuntu-latest)

Object Literal Property name `User-Agent` must match one of the following formats: camelCase
timeout: 30_000,
} satisfies AxiosRequestConfig;

/**
* Get the version of GGShield
* @param context The extension context
* @returns The version of GGShield
*/
export function getGGShieldVersion(context: ExtensionContext): string {
return fs
.readFileSync(path.join(context.extensionPath, "ggshield_version"), "utf8")
.trim();
}

/**
* Get the absolute path to GGShield binary. If it doesn't exist, it will be installed.
* @param platform The platform of the user
Expand All @@ -12,31 +31,30 @@
* @param outputChannel The output channel to use
* @returns The absolute path to the GGShield binary
*/
export function getGGShield(
export async function getGGShield(
platform: NodeJS.Platform,
arch: string,
context: ExtensionContext,
outputChannel: OutputChannel
): string {
const version = fs
.readFileSync(path.join(context.extensionPath, "ggshield_version"), "utf8")
.trim();
outputChannel: OutputChannel,
allowSelfSigned: boolean,
): Promise<string> {
const version = getGGShieldVersion(context);
console.log(`Latest GGShield version: ${version}`);
const ggshieldFolder: string = path.join(
context.extensionPath,
"ggshield-internal"
"ggshield-internal",
);
const ggshieldBinaryPath: string = computeGGShieldPath(
platform,
arch,
ggshieldFolder,
version
version,
);

// if exists, return the path
if (fs.existsSync(ggshieldBinaryPath)) {
outputChannel.appendLine(
`Using GGShield v${version}. Checkout https://github.com/GitGuardian/ggshield for more info.`
`Using GGShield v${version}. Checkout https://github.com/GitGuardian/ggshield for more info.`,
);
console.log(`GGShield already exists at ${ggshieldBinaryPath}`);
return ggshieldBinaryPath;
Expand All @@ -47,28 +65,20 @@
}
fs.mkdirSync(ggshieldFolder);
// install GGShield
installGGShield(platform, arch, ggshieldFolder, version);
await installGGShield(
platform,
arch,
ggshieldFolder,
version,
allowSelfSigned,
);
outputChannel.appendLine(
`Updated to GGShield v${version}. Checkout https://github.com/GitGuardian/ggshield for more info.`
`Updated to GGShield v${version}. Checkout https://github.com/GitGuardian/ggshield for more info.`,
);
console.log(`GGShield binary installed at ${ggshieldBinaryPath}`);
return ggshieldBinaryPath;
}

/**
* Get the latest version of GGShield
* @returns The latest version of GGShield
*/
export function getGGShieldLatestVersion(): string {
const response = require("sync-request")(
"GET",
"https://api.github.com/repos/GitGuardian/ggshield/releases/latest",
{ headers: { "User-Agent": "GitGuardian-VSCode-Extension" } }
);
const data = JSON.parse(response.getBody("utf8"));
return data.tag_name?.replace(/^v/, "");
}

/**
* Compute the folder name of the GGShield binary
* @param platform The platform of the user
Expand All @@ -79,7 +89,7 @@
export function computeGGShieldFolderName(
platform: NodeJS.Platform,
arch: string,
version: string
version: string,
): string {
let archString: string = "";
let platformString: string = "";
Expand Down Expand Up @@ -119,12 +129,13 @@
* @param ggshieldFolder The folder of the GGShield binary
* @param version The version of GGShield
*/
export function installGGShield(
export async function installGGShield(
platform: NodeJS.Platform,
arch: string,
ggshieldFolder: string,
version: string
): void {
version: string,
allowSelfSigned: boolean,
): Promise<void> {
let extension: string = "";
switch (platform) {
case "win32":
Expand All @@ -141,10 +152,15 @@
const fileName: string = `${computeGGShieldFolderName(
platform,
arch,
version
version,
)}.${extension}`;
const downloadUrl: string = `https://github.com/GitGuardian/ggshield/releases/download/v${version}/${fileName}`;
downloadGGShieldFromGitHub(fileName, downloadUrl, ggshieldFolder);
await downloadGGShieldFromGitHub(
fileName,
downloadUrl,
ggshieldFolder,
allowSelfSigned,
);
extractGGShieldBinary(path.join(ggshieldFolder, fileName), ggshieldFolder);
}

Expand All @@ -155,7 +171,7 @@
*/
export function extractGGShieldBinary(
filePath: string,
ggshieldFolder: string
ggshieldFolder: string,
): void {
if (filePath.endsWith(".tar.gz")) {
tar.x({
Expand All @@ -177,18 +193,29 @@
* @param downloadUrl The URL of the GGShield binary
* @param ggshieldFolder The folder of the GGShield binary
*/
function downloadGGShieldFromGitHub(
async function downloadGGShieldFromGitHub(
fileName: string,
downloadUrl: string,
ggshieldFolder: string
): void {
ggshieldFolder: string,
allowSelfSigned: boolean,
): Promise<void> {
console.log(`Downloading GGShield from ${downloadUrl}`);
const response = require("sync-request")("GET", downloadUrl, {
headers: { "User-Agent": "GitGuardian-VSCode-Extension" },

const instance = allowSelfSigned
? new Agent({
rejectUnauthorized: false,
})
: undefined;

const { data } = await axios.get(downloadUrl, {
...defaultRequestConfig,
responseType: "arraybuffer",
httpsAgent: instance,
});
fs.writeFileSync(path.join(ggshieldFolder, fileName), response.getBody());

fs.writeFileSync(path.join(ggshieldFolder, fileName), data);
console.log(
`GGShield archive downloaded to ${path.join(ggshieldFolder, fileName)}`
`GGShield archive downloaded to ${path.join(ggshieldFolder, fileName)}`,
);
}

Expand All @@ -203,7 +230,7 @@
platform: NodeJS.Platform,
arch: string,
ggshieldFolder: string,
version: string
version: string,
): string {
console.log(`Platform: ${platform}; Arch: ${arch}`);
let executable: string = "";
Expand All @@ -222,6 +249,6 @@
return path.join(
ggshieldFolder,
computeGGShieldFolderName(platform, arch, version),
executable
executable,
);
}
10 changes: 6 additions & 4 deletions src/test/suite/extension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import * as assert from "assert";
import { extensions, commands } from "vscode";

suite("activate", () => {
test("Should activate extension successfully", async () => {
test("Should activate extension successfully", async function () {
this.timeout(10000);

const ext = extensions.getExtension(
"gitguardian-secret-security.gitguardian"
"gitguardian-secret-security.gitguardian",
);
await ext?.activate();
assert.ok(ext?.isActive, "Extension should be active");
Expand All @@ -25,13 +27,13 @@ suite("activate", () => {

const registered = await commands.getCommands(true);
const gitguardianCommands = registered.filter((command) =>
command.startsWith("gitguardian")
command.startsWith("gitguardian"),
);

for (const command of commandIds) {
assert.ok(
gitguardianCommands.includes(command),
`Command ${command} should be registered`
`Command ${command} should be registered`,
);
}
});
Expand Down
4 changes: 2 additions & 2 deletions src/test/suite/lib/ggshield-configuration-utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ suite("getConfiguration", () => {
simple.restore();
});

test("Vscode settings are correctly read", () => {
test("Vscode settings are correctly read", async () => {
const context = {} as ExtensionContext;
const outputChannel = window.createOutputChannel("GitGuardian");
simple.mock(context, "asAbsolutePath").returnWith("");
Expand All @@ -42,7 +42,7 @@ suite("getConfiguration", () => {
}
},
});
const configuration = getConfiguration(context, outputChannel);
const configuration = await getConfiguration(context, outputChannel);

// Assert both workspace.getConfiguration and GGShieldConfiguration constructor were called
assert(
Expand Down
Loading