Skip to content

Commit

Permalink
fix skipSSLValidation: use dispatcher instead of agent (#190)
Browse files Browse the repository at this point in the history
  • Loading branch information
phryneas authored Sep 4, 2024
1 parent 09dbc57 commit ec1e992
Show file tree
Hide file tree
Showing 12 changed files with 153 additions and 38 deletions.
5 changes: 5 additions & 0 deletions .changeset/healthy-planets-live.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"vscode-apollo": patch
---

Fix a bug that prevented `skipSSLValidation` from working.
13 changes: 10 additions & 3 deletions .gitleaks.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# This file exists primarily to influence scheduled scans that Apollo runs of all repos in Apollo-managed orgs.
# This file exists primarily to influence scheduled scans that Apollo runs of all repos in Apollo-managed orgs.
# This is an Apollo-Internal link, but more information about these scans is available here:
# https://apollographql.atlassian.net/wiki/spaces/SecOps/pages/81330213/Everything+Static+Application+Security+Testing#Scheduled-Scans.1
#
#
# Apollo is using Gitleaks (https://github.com/gitleaks/gitleaks) to run these scans.
# However, this file is not something that Gitleaks natively consumes. This file is an
# Apollo-convention. Prior to scanning a repo, Apollo merges
# our standard Gitleaks configuration (which is largely just the Gitleaks-default config) with
# this file if it exists in a repo. The combined config is then used to scan a repo.
#
#
# We did this because the natively-supported allowlisting functionality in Gitleaks didn't do everything we wanted
# or wasn't as robust as we needed. For example, one of the allowlisting options offered by Gitleaks depends on the line number
# on which a false positive secret exists to allowlist it. (https://github.com/gitleaks/gitleaks#gitleaksignore).
Expand All @@ -24,3 +24,10 @@
# See https://github.com/apollographql/vscode-graphql/blob/a905280c143991b3fd675f8b4c3a7da277ccf095/packages/apollo-language-server/src/engine/index.ts#L86
"a905280c143991b3fd675f8b4c3a7da277ccf095"
]

[[ rules ]]
id = "private-key"
[ rules.allowlist ]
paths = [
'''sampleWorkspace/httpSchema/self-signed.key$''',
]
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"lz-string": "1.5.0",
"minimatch": "10.0.1",
"moment": "2.30.1",
"undici": "6.19.8",
"vscode-languageclient": "9.0.1",
"vscode-languageserver": "9.0.1",
"vscode-languageserver-textdocument": "1.0.12",
Expand Down
2 changes: 2 additions & 0 deletions sampleWorkspace/httpSchema/apollo.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ export default {
service: {
name: "httpSchema",
url: "http://localhost:7096/graphql",
// url: "https://localhost:7097/graphql",
// skipSSLValidation: true,
},
},
};
22 changes: 22 additions & 0 deletions sampleWorkspace/httpSchema/self-signed.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDkzCCAnugAwIBAgIUVNlDGdat5znvwWhOEFQLq7BWzNwwDQYJKoZIhvcNAQEL
BQAwWTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MB4X
DTI0MDkwMjA4MTgwNloXDTM0MDgzMTA4MTgwNlowWTELMAkGA1UEBhMCQVUxEzAR
BgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5
IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAvHLw9Ey0WoRLxjVIajxVAT4za1pUQw3E9mtl57DPHDcdGWR56S9w
KPjmND5lrtALZ5K+EKqslJdPf6Uxzxpf6phVnwpFwq5hPeY/Gpm77HxpPiJ61Q9r
fsYnLtGXiZta0kbdrisALB+3QykEHOerDUF3wGiVYVcpDu7WF/WcLaF+zUlgf1gQ
RTa5B3HpdCk34LiKPm9IZpWRpgLC90ro+HP+nBo7FoLYwu+WiPxg49qWEUY8fk+d
TuJVdH7lf8GxcfM2oCzhBGpT5O/t6lqYBkgZvvY3YAERmAxg/OSeuUa6ChOMLK2T
+2MRLy7eLaaeTmPMFjjrzFODCA2/ekfGVwIDAQABo1MwUTAdBgNVHQ4EFgQUpEu/
mbpTwcOGdPZ8NhTl1e16G84wHwYDVR0jBBgwFoAUpEu/mbpTwcOGdPZ8NhTl1e16
G84wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAhfSAl9VFS+Fk
Wkmsl1ndoXvLWVDy36YepbNfXUeB+iwebIgdyZhNu6VdpcHsPNBuUV4wr4mgZ7PK
Ksp3kKdqTDTAfBzHNBiOK7QgGyrDQJa0/Nn8ifmS+TYYCOs4FnkOXCUFipXCCMaS
KzFYc9Ls4jtAxiSN58NmwxW9fqRHqwHW4o2Z/aNx4EnCEat0x4QcAqq/qfEodmjH
jI7/AKb4UE+yEcJnZSlUDdpM4zPM3FcjmY7JVyfd/CziywR7rHGbLz7XQcCkYyDv
5xqz0Lvk0ZtOC73cFWS41qfh8lrt34CNPoG7EaPFf+tMwhvjNooDHMQCb8y1A0Y3
2yaDZNbCbQ==
-----END CERTIFICATE-----
28 changes: 28 additions & 0 deletions sampleWorkspace/httpSchema/self-signed.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC8cvD0TLRahEvG
NUhqPFUBPjNrWlRDDcT2a2XnsM8cNx0ZZHnpL3Ao+OY0PmWu0Atnkr4QqqyUl09/
pTHPGl/qmFWfCkXCrmE95j8ambvsfGk+InrVD2t+xicu0ZeJm1rSRt2uKwAsH7dD
KQQc56sNQXfAaJVhVykO7tYX9ZwtoX7NSWB/WBBFNrkHcel0KTfguIo+b0hmlZGm
AsL3Suj4c/6cGjsWgtjC75aI/GDj2pYRRjx+T51O4lV0fuV/wbFx8zagLOEEalPk
7+3qWpgGSBm+9jdgARGYDGD85J65RroKE4wsrZP7YxEvLt4tpp5OY8wWOOvMU4MI
Db96R8ZXAgMBAAECggEAGRrrnHLZi2jY2sDUyF5dlAr6zlWcKHYITtceSNWmXyO9
Ky8BChPen9QPgFdDCZ0VX94jQaooeqqGa0K71ijf2ADPq0ky46LX4+dYHHhC762K
rGiV2kDceROh5bFYvFAniHWWE8gOalJsAjT6eMqo4DJgEeXSPMO1UxQguSlofdrX
9PIkRmsmQVmVh17V4RJhhW/qg8r75OwpM1uFTknikaXwd9Rw5/HZhW4hXP5EeM2x
rcaYtXudhUyG/AWCrRPpHdGGNpHPmpwKUw3uADYAb2Hicjswx34kmWAbcwVJMs8d
3QR/4hyrJnVSgYAB8/5oiNsnvaF/sO6/9KkDFpF03QKBgQDxrVJznfA0xgeVTGxt
sLRhbsUyVn5tHteEdbvbTGLfl80Dayzlht9rTrjYXgQUevw40+chQW8LPEA8gmyM
oCyAMouk+DJ5rl75jQnQh1/pob+ReyFi7Jl4D6Ro8J6gP6nds+wZ/BN5WLNtd/KI
BMwi4fEyKjT7nKTVFNQlrZEG6wKBgQDHng2nFYbSEKv1lb5HabSxJ1bbTHld0lzI
tn5zEmZ9PW6jBM0UJMEmkPRAvzhGGnzbRM0hYhiZR1FBR9T6BCJb+1N0HfT0Xo2X
MTOuz8auLRtH73SCRbRoxVbz+TFmLVQuwAXdwIT+p4AgEqoJ+QyMKwuwr70AGZmm
SkL08Bp7RQKBgQDYZfOgJtmAx5jerFGiXkkFvSPBkQUfPDCKIMmW8WzO/KPL3dmT
pBLFiPWmd3h7xiu1zrf0ZRzDGK4EAFymBn4SRDAaBUtc/S95kDoriCvvjK912qTo
aSZ6BLeYZ2wB3T+CjqpoEfh1/WCcMnzuIi2PRnSsEHLkoTxOt5nGKwXjBQKBgDve
o6mhQzZt2aVmrBMvGQqpCdvsK9p/5WQtl+9bbXHSowQxxHBuNaAjiZ6Bu5cLCreZ
Aw0oJsiSI0S5Dp+N7eA4mOcStQ017rGSCDY+CxDiZnRE1WTdEyb5SQMTkkVbAwyi
ex/vRfQ6uKrl7infkGvZ3T+49a65/uNpEnv0J30hAoGBAJwhlCGf3BhMPrXjXGsp
qxtCAbnaARErq0cI/nP6EKv8U0zOte09j/KKqFhckzsN3Df+P7dSuciIbFEDFY8Y
aWVRMDF/owMo7qrb/Hyvt72gRtRAeypN1Zf7Uy7JtP7PMqdvMynK0xSdrGAl4UCV
6AOSsKQotsgA2R1PKV89Rn2R
-----END PRIVATE KEY-----
48 changes: 37 additions & 11 deletions src/__e2e__/mockServer.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,43 @@
// @ts-check
const http = require("http");
const {
parseRequestParams,
createHandler,
} = require("graphql-http/lib/use/http");
const { buildSchema } = require("graphql");
const { Trie } = require("@wry/trie");
const { readFileSync } = require("fs");
const { join } = require("path");

function runMockServer(
async function runMockServer(
/** @type {number} */ port,
onStart = (/** @type {number} */ port) => {},
useSelfSignedCert = false,
onStart = (/** @type {string} */ baseUri) => {},
) {
const mocks = new Trie(false);

const server = http.createServer(async (req, res) => {
/**
*
* @param {import('node:http').RequestListener} listener
* @returns
*/
function createServer(listener) {
if (useSelfSignedCert) {
return require("node:https").createServer(
{
key: readFileSync(
join(__dirname, "../../sampleWorkspace/httpSchema/self-signed.key"),
),
cert: readFileSync(
join(__dirname, "../../sampleWorkspace/httpSchema/self-signed.crt"),
),
},
listener,
);
}
return require("node:http").createServer(listener);
}

const server = createServer(async (req, res) => {
if (req.url === "/apollo") {
if (req.method === "POST") {
await handleApolloPost(req, res);
Expand All @@ -33,8 +57,9 @@ function runMockServer(

console.log("Starting server...");
server.listen(port);
onStart(port);
console.log(`Server ready at: http://localhost:${port}`);
const baseUri = `${useSelfSignedCert ? "https" : "http"}://localhost:${port}`;
await onStart(baseUri);
console.log(`Server ready at: ${baseUri}`);
return {
[Symbol.dispose]() {
console.log("Closing server...");
Expand All @@ -45,8 +70,8 @@ function runMockServer(

/**
* Mock GraphQL Endpoint Handler
* @param {http.IncomingMessage} req
* @param {http.ServerResponse} res
* @param {import('node:http').IncomingMessage} req
* @param {import('node:http').ServerResponse} res
*/
async function handleApolloPost(req, res) {
const { operationName, variables } =
Expand Down Expand Up @@ -79,8 +104,8 @@ function runMockServer(

/**
* Handler to accept new GraphQL Mocks
* @param {http.IncomingMessage} req
* @param {http.ServerResponse} res
* @param {import('node:http').IncomingMessage} req
* @param {import('node:http').ServerResponse} res
*/
async function handleApolloPut(req, res) {
const body = await new Promise((resolve) => {
Expand Down Expand Up @@ -111,7 +136,8 @@ const schemaHandler = createHandler({
});

if (require.main === module) {
runMockServer(7096, require("./mocks.js").loadDefaultMocks);
runMockServer(7096, false, require("./mocks.js").loadDefaultMocks);
runMockServer(7097, true, require("./mocks.js").loadDefaultMocks);
}

module.exports.runMockServer = runMockServer;
18 changes: 11 additions & 7 deletions src/__e2e__/mocks.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
// @ts-check

async function loadDefaultMocks(/** @type {number} */ port) {
await sendMock(port, FrontendUrlRoot);
await sendMock(port, SchemaTagsAndFieldStats);
await sendMock(port, GetSchemaByTag);
async function loadDefaultMocks(/** @type {string} */ baseUri) {
await sendMock(baseUri, FrontendUrlRoot);
await sendMock(baseUri, SchemaTagsAndFieldStats);
await sendMock(baseUri, GetSchemaByTag);
}

function sendMock(
/** @type {number} */ port,
/** @type {string} */ baseUri,
/** @type { { operationName: string, variables: Record<string, string>, response: unknown }} */ {
operationName,
variables,
response,
},
) {
return fetch(`http://localhost:${port}/apollo`, {
return require("undici").fetch(`${baseUri}/apollo`, {
method: "PUT",
body: JSON.stringify({
operationName,
variables,
response,
}),
dispatcher: new (require("undici").Agent)({
connect: {
rejectUnauthorized: false,
},
}),
});
}

Expand Down
14 changes: 8 additions & 6 deletions src/__e2e__/runTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const { runMockServer } = require("./mockServer.js");
const { loadDefaultMocks } = require("./mocks.js");

async function main() {
let disposable;
const disposables = /**{@type Disposable[]}*/ [];
try {
// The folder containing the Extension Manifest package.json
// Passed to `--extensionDevelopmentPath`
Expand All @@ -18,8 +18,12 @@ async function main() {
const TEST_PORT = 7096;
process.env.APOLLO_ENGINE_ENDPOINT = "http://localhost:7096/apollo";
process.env.MOCK_SERVER_PORT = String(TEST_PORT);
disposable = runMockServer(TEST_PORT);
await loadDefaultMocks(TEST_PORT);
disposables.push(
...(await Promise.all([
runMockServer(TEST_PORT, false, loadDefaultMocks),
runMockServer(TEST_PORT + 1, true, loadDefaultMocks),
])),
);
// Download VS Code, unzip it and run the integration test
const exitCode = await runTests({
extensionDevelopmentPath,
Expand All @@ -35,9 +39,7 @@ async function main() {
console.error("Failed to run tests");
process.exit(1);
} finally {
if (disposable) {
disposable[Symbol.dispose]();
}
disposables.forEach((d) => d[Symbol.dispose]());
}
}

Expand Down
7 changes: 4 additions & 3 deletions src/language-server/__e2e__/studioGraph.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,10 @@ Get detailed profile information about the current user (including the current u
});

test("wrong token", async () => {
const baseUri = `http://localhost:${mockPort}`;
try {
await mocks.sendMock(mockPort, mocks.GetSchemaByTag_WRONG_TOKEN);
await mocks.sendMock(mockPort, mocks.SchemaTagsAndFieldStats_WRONG_TOKEN);
await mocks.sendMock(baseUri, mocks.GetSchemaByTag_WRONG_TOKEN);
await mocks.sendMock(baseUri, mocks.SchemaTagsAndFieldStats_WRONG_TOKEN);

const ext = getExtension();
ext.outputChannel.clear();
Expand All @@ -59,7 +60,7 @@ Invalid credentials provided
at new ApolloError`.trim(),
);
} finally {
await mocks.loadDefaultMocks(mockPort);
await mocks.loadDefaultMocks(baseUri);
await reloadService();
}
});
23 changes: 15 additions & 8 deletions src/language-server/providers/schema/endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,16 @@ import { RemoteServiceConfig } from "../../config";
import { GraphQLSchemaProvider, SchemaChangeUnsubscribeHandler } from "./base";
import { Debug } from "../../utilities";
import { isString } from "util";

import { fetch as undiciFetch, Agent } from "undici";

const skipSSLValidationFetchOptions = {
// see https://github.com/nodejs/undici/issues/1489#issuecomment-1543856261
dispatcher: new Agent({
connect: {
rejectUnauthorized: false,
},
}),
} satisfies import("undici").RequestInit;
export class EndpointSchemaProvider implements GraphQLSchemaProvider {
private schema?: GraphQLSchema;
private federatedServiceSDL?: string;
Expand All @@ -28,11 +37,11 @@ export class EndpointSchemaProvider implements GraphQLSchemaProvider {
const { skipSSLValidation, url, headers } = this.config;
const options: HttpOptions = {
uri: url,
fetch: undiciFetch as typeof fetch,
};

if (url.startsWith("https:") && skipSSLValidation) {
options.fetchOptions = {
agent: new HTTPSAgent({ rejectUnauthorized: false }),
};
options.fetchOptions = skipSSLValidationFetchOptions;
}

const { data, errors } = (await toPromise(
Expand Down Expand Up @@ -92,12 +101,10 @@ export class EndpointSchemaProvider implements GraphQLSchemaProvider {
const { skipSSLValidation, url, headers } = this.config;
const options: HttpOptions = {
uri: url,
fetch,
fetch: undiciFetch as typeof fetch,
};
if (url.startsWith("https:") && skipSSLValidation) {
options.fetchOptions = {
agent: new HTTPSAgent({ rejectUnauthorized: false }),
};
options.fetchOptions = skipSSLValidationFetchOptions;
}

const getFederationInfoQuery = `
Expand Down

0 comments on commit ec1e992

Please sign in to comment.