Skip to content

Commit

Permalink
Merge branch 'main' into reconcile
Browse files Browse the repository at this point in the history
  • Loading branch information
JamieSlome committed Feb 9, 2024
2 parents 9903517 + 245f5b4 commit 5ffdb80
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 61 deletions.
68 changes: 19 additions & 49 deletions package-lock.json

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

4 changes: 2 additions & 2 deletions src/proxy/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const proxyApp = require('express')();
const bodyParser = require('body-parser');
const routes = require('./routes').router;
const router = require('./routes').router;
const { GIT_PROXY_SERVER_PORT: proxyHttpPort } = require('../config/env').Vars;

const options = {
Expand All @@ -11,7 +11,7 @@ const options = {

// Setup the proxy middleware
proxyApp.use(bodyParser.raw(options));
proxyApp.use('/', routes);
proxyApp.use('/', router);

const start = async () => {
proxyApp.listen(proxyHttpPort, () => {
Expand Down
68 changes: 60 additions & 8 deletions src/proxy/routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,61 @@ if (config.getAllowSelfSignedCert()) {
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0;
}

/**
* For a given Git HTTP request destined for a GitHub repo,
* remove the GitHub specific components of the URL.
* @param {string} url URL path of the request
* @return {string} Modified path which removes the {owner}/{repo} parts
*/
const stripGitHubFromGitPath = (url) => {
const parts = url.split('/');
// url = '/{owner}/{repo}.git/{git-path}'
// url.split('/') = ['', '{owner}', '{repo}.git', '{git-path}']
if (parts.length !== 4 && parts.length !== 5) {
console.error('unexpected url received: ', url);
return undefined;
}
parts.splice(1, 2); // remove the {owner} and {repo} from the array
return parts.join('/');
};

/**
* Check whether an HTTP request has the expected properties of a
* Git HTTP request. The URL is expected to be "sanitized", stripped of
* specific paths such as the GitHub {owner}/{repo}.git parts.
* @param {string} url Sanitized URL which only includes the path specific to git
* @param {*} headers Request headers (TODO: Fix JSDoc linting and refer to node:http.IncomingHttpHeaders)
* @return {boolean} If true, this is a valid and expected git request. Otherwise, false.
*/
const validGitRequest = (url, headers) => {
const { 'user-agent': agent, accept } = headers;
if (['/info/refs?service=git-upload-pack', '/info/refs?service=git-receive-pack'].includes(url)) {
// https://www.git-scm.com/docs/http-protocol#_discovering_references
// We can only filter based on User-Agent since the Accept header is not
// sent in this request
return agent.startsWith('git/');
}
if (['/git-upload-pack', '/git-receive-pack'].includes(url)) {
// https://www.git-scm.com/docs/http-protocol#_uploading_data
return agent.startsWith('git/') && accept.startsWith('application/x-git-');
}
return false;
};

router.use(
'/',
proxy(config.getProxyUrl(), {
preserveHostHdr: false,
filter: async function (req, res) {
try {
console.log('received');
console.log(req.hostname);
console.log('request url: ', req.url);
console.log('host: ', req.headers.host);
console.log('user-agent: ', req.headers['user-agent']);
const gitPath = stripGitHubFromGitPath(req.url);
if (gitPath === undefined || !validGitRequest(gitPath, req.headers)) {
res.status(400).send('Invalid request received');
return false;
}
if (req.body && req.body.length) {
req.rawBody = req.body.toString('utf8');
}
Expand Down Expand Up @@ -43,7 +90,7 @@ router.use(
message = action.blockedMessage;
}

packetMessage = handleMessage(message);
const packetMessage = handleMessage(message);

console.log(req.headers);

Expand Down Expand Up @@ -81,13 +128,18 @@ router.use(
}),
);

const handleMessage = async(message) => {
const handleMessage = async (message) => {
const errorMessage = `ERR\t${message}`;
const len = 6 + new TextEncoder().encode(errorMessage).length;

const prefix = len.toString(16);
const packetMessage = `${prefix.padStart(4, '0')}\x02${errorMessage}\n0000`;
return packetMessage
}

module.exports = {router, handleMessage};
return packetMessage;
};

module.exports = {
router,
handleMessage,
validGitRequest,
stripGitHubFromGitPath,
};
2 changes: 0 additions & 2 deletions test/testProxyRoute.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@

const handleMessage = require('../src/proxy/routes').handleMessage;
const chai = require('chai');

const expect = chai.expect;

// Use this test as a template
describe('proxy error messages', async () => {

it('should handle short messages', async function () {
const res = await handleMessage('one');
expect(res).to.contain('one');
Expand Down
88 changes: 88 additions & 0 deletions test/testRouteFilter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/* eslint-disable max-len */
const chai = require('chai');
const validGitRequest = require('../src/proxy/routes').validGitRequest;
const stripGitHubFromGitPath =
require('../src/proxy/routes').stripGitHubFromGitPath;

chai.should();

const expect = chai.expect;

describe('url filters for proxying ', function () {
it('stripGitHubFromGitPath should return the sanitized URL with owner & repo removed', function () {
expect(
stripGitHubFromGitPath(
'/octocat/hello-world.git/info/refs?service=git-upload-pack',
),
).eq('/info/refs?service=git-upload-pack');
});

it('stripGitHubFromGitPath should return undefined if the url', function () {
expect(stripGitHubFromGitPath('/octocat/hello-world')).undefined;
});

it('validGitRequest should return true for safe requests on expected URLs', function () {
[
'/info/refs?service=git-upload-pack',
'/info/refs?service=git-receive-pack',
'/git-upload-pack',
'/git-receive-pack',
].forEach((url) => {
expect(
validGitRequest(url, {
'user-agent': 'git/2.30.0',
accept: 'application/x-git-upload-pack-request',
}),
).true;
});
});

it('validGitRequest should return false for unsafe URLs', function () {
['/', '/foo'].forEach((url) => {
expect(
validGitRequest(url, {
'user-agent': 'git/2.30.0',
accept: 'application/x-git-upload-pack-request',
}),
).false;
});
});

it('validGitRequest should return false for a browser request', function () {
expect(
validGitRequest('/', {
'user-agent': 'Mozilla/5.0',
accept: '*/*',
}),
).false;
});

it('validGitRequest should return false for unexpected combinations of headers & URLs', function () {
// expected Accept=application/x-git-upload-pack
expect(
validGitRequest('/git-upload-pack', {
'user-agent': 'git/2.30.0',
accept: '*/*',
}),
).false;

// expected User-Agent=git/*
expect(
validGitRequest('/info/refs?service=git-upload-pack', {
'user-agent': 'Mozilla/5.0',
accept: '*/*',
}),
).false;
});

it('validGitRequest should return false for unexpected content-type on certain URLs', function () {
['application/json', 'text/html', '*/*'].map((accept) => {
expect(
validGitRequest('/git-upload-pack', {
'user-agent': 'git/2.30.0',
accept: accept,
}),
).false;
});
});
});

0 comments on commit 5ffdb80

Please sign in to comment.