Skip to content

Commit 24508b8

Browse files
authored
Merge pull request #1164 from kriswest/1163-dont-forward-unknown-repos
feat: don't forward requests for unknown repos
2 parents 4bf2276 + 4051bcb commit 24508b8

File tree

6 files changed

+117
-72
lines changed

6 files changed

+117
-72
lines changed

src/proxy/chain.ts

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,24 +27,29 @@ const pullActionChain: ((req: any, action: Action) => Promise<Action>)[] = [
2727
proc.push.checkRepoInAuthorisedList,
2828
];
2929

30+
const defaultActionChain: ((req: any, action: Action) => Promise<Action>)[] = [
31+
proc.push.checkRepoInAuthorisedList,
32+
];
33+
3034
let pluginsInserted = false;
3135

3236
export const executeChain = async (req: any, res: any): Promise<Action> => {
3337
let action: Action = {} as Action;
38+
3439
try {
3540
action = await proc.pre.parseAction(req);
3641
const actionFns = await getChain(action);
3742

3843
for (const fn of actionFns) {
3944
action = await fn(req, action);
40-
if (!action.continue()) {
41-
return action;
42-
}
43-
44-
if (action.allowPush) {
45-
return action;
45+
if (!action.continue() || action.allowPush) {
46+
break;
4647
}
4748
}
49+
} catch (e) {
50+
action.error = true;
51+
action.errorMessage = `An error occurred when executing the chain: ${e}`;
52+
console.error(action.errorMessage);
4853
} finally {
4954
await proc.push.audit(req, action);
5055
if (action.autoApproved) {
@@ -89,9 +94,13 @@ export const getChain = async (
8994
// This is set to true so that we don't re-insert the plugins into the chain
9095
pluginsInserted = true;
9196
}
92-
if (action.type === 'pull') return pullActionChain;
93-
if (action.type === 'push') return pushActionChain;
94-
return [];
97+
if (action.type === 'pull') {
98+
return pullActionChain;
99+
} else if (action.type === 'push') {
100+
return pushActionChain;
101+
} else {
102+
return defaultActionChain;
103+
}
95104
};
96105

97106
export default {
@@ -110,6 +119,9 @@ export default {
110119
get pullActionChain() {
111120
return pullActionChain;
112121
},
122+
get defaultActionChain() {
123+
return defaultActionChain;
124+
},
113125
executeChain,
114126
getChain,
115127
};

src/proxy/processors/pre-processor/parseAction.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ const exec = async (req: {
1414
if (pathBreakdown) {
1515
if (pathBreakdown.gitPath.endsWith('git-upload-pack') && req.method === 'GET') {
1616
type = 'pull';
17-
}
18-
if (
17+
} else if (
1918
pathBreakdown.gitPath.includes('git-receive-pack') &&
2019
req.method === 'POST' &&
2120
req.headers['content-type'] === 'application/x-git-receive-pack-request'

src/proxy/routes/index.ts

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,19 @@ const logAction = (
2929
const proxyFilter: ProxyOptions['filter'] = async (req, res) => {
3030
try {
3131
const urlComponents = processUrlPath(req.url);
32-
3332
if (
3433
!urlComponents ||
3534
urlComponents.gitPath === undefined ||
3635
!validGitRequest(urlComponents.gitPath, req.headers)
3736
) {
38-
res.status(400).send('Invalid request received');
39-
console.log('action blocked');
37+
logAction(
38+
req.url,
39+
req.headers.host,
40+
req.headers['user-agent'],
41+
'Invalid request received',
42+
null,
43+
);
44+
res.status(400).send(handleMessage('Invalid request received'));
4045
return false;
4146
}
4247

@@ -51,17 +56,7 @@ const proxyFilter: ProxyOptions['filter'] = async (req, res) => {
5156
res.set('x-frame-options', 'DENY');
5257
res.set('connection', 'close');
5358

54-
let message = '';
55-
56-
if (action.error) {
57-
message = action.errorMessage!;
58-
console.error(message);
59-
}
60-
if (action.blocked) {
61-
message = action.blockedMessage!;
62-
}
63-
64-
const packetMessage = handleMessage(message);
59+
const packetMessage = handleMessage(action.errorMessage ?? action.blockedMessage ?? '');
6560

6661
logAction(
6762
req.url,
@@ -70,9 +65,7 @@ const proxyFilter: ProxyOptions['filter'] = async (req, res) => {
7065
action.errorMessage,
7166
action.blockedMessage,
7267
);
73-
74-
res.status(200).send(packetMessage);
75-
68+
res.status(403).send(packetMessage);
7669
return false;
7770
}
7871

@@ -84,16 +77,20 @@ const proxyFilter: ProxyOptions['filter'] = async (req, res) => {
8477
action.blockedMessage,
8578
);
8679

80+
// this is the only case where we do not respond directly, instead we return true to proxy the request
8781
return true;
8882
} catch (e) {
89-
console.error('Error occurred in proxy filter function ', e);
83+
const packetMessage = handleMessage(`Error occurred in proxy filter function ${e}`);
84+
9085
logAction(
9186
req.url,
9287
req.headers.host,
9388
req.headers['user-agent'],
9489
'Error occurred in proxy filter function: ' + ((e as Error).message ?? e),
9590
null,
9691
);
92+
93+
res.status(500).send(packetMessage);
9794
return false;
9895
}
9996
};
@@ -140,7 +137,9 @@ const isPackPost = (req: Request) =>
140137
/^(?:\/[^/]+)*\/[^/]+\.git\/(?:git-upload-pack|git-receive-pack)$/.test(req.url);
141138

142139
const teeAndValidate = async (req: Request, res: Response, next: NextFunction) => {
143-
if (!isPackPost(req)) return next();
140+
if (!isPackPost(req)) {
141+
return next();
142+
}
144143

145144
const proxyStream = new PassThrough();
146145
const pluginStream = new PassThrough();
@@ -152,17 +151,16 @@ const teeAndValidate = async (req: Request, res: Response, next: NextFunction) =
152151
const buf = await getRawBody(pluginStream, { limit: '1gb' });
153152
(req as any).body = buf;
154153
const verdict = await executeChain(req, res);
155-
console.log('action processed');
156154
if (verdict.error || verdict.blocked) {
157-
let msg = '';
155+
const msg = verdict.errorMessage ?? verdict.blockedMessage ?? '';
158156

159-
if (verdict.error) {
160-
msg = verdict.errorMessage!;
161-
console.error(msg);
162-
}
163-
if (verdict.blocked) {
164-
msg = verdict.blockedMessage!;
165-
}
157+
logAction(
158+
req.url,
159+
req.headers?.host,
160+
req.headers?.['user-agent'],
161+
verdict.errorMessage,
162+
verdict.blockedMessage,
163+
);
166164

167165
res
168166
.set({
@@ -174,7 +172,7 @@ const teeAndValidate = async (req: Request, res: Response, next: NextFunction) =
174172
'x-frame-options': 'DENY',
175173
connection: 'close',
176174
})
177-
.status(200)
175+
.status(403)
178176
.send(handleMessage(msg));
179177
return;
180178
}
@@ -233,8 +231,9 @@ const getRouter = async () => {
233231
console.log('proxy keys registered: ', JSON.stringify(proxyKeys));
234232

235233
router.use('/', (req, res, next) => {
236-
console.log(`processing request URL: '${req.url}'`);
237-
console.log('proxy keys registered: ', JSON.stringify(proxyKeys));
234+
console.log(
235+
`processing request URL: '${req.url}' against registered proxy keys: ${JSON.stringify(proxyKeys)}`,
236+
);
238237

239238
for (let i = 0; i < proxyKeys.length; i++) {
240239
if (req.url.startsWith(proxyKeys[i])) {

test/chain.test.js

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ let mockPushProcessors;
6464
const clearCache = (sandbox) => {
6565
delete require.cache[require.resolve('../src/proxy/processors')];
6666
delete require.cache[require.resolve('../src/proxy/chain')];
67-
sandbox.reset();
67+
sandbox.restore();
6868
};
6969

7070
describe('proxy chain', function () {
@@ -275,17 +275,15 @@ describe('proxy chain', function () {
275275
expect(mockPushProcessors.audit.called).to.be.true;
276276
});
277277

278-
it('executeChain should run no actions if not a push or pull', async function () {
278+
it('executeChain should always run at least checkRepoInAuthList', async function () {
279279
const req = {};
280280
const action = { type: 'foo', continue: () => true, allowPush: true };
281281

282-
processors.pre.parseAction.resolves(action);
283-
284-
const result = await chain.executeChain(req);
282+
mockPreProcessors.parseAction.resolves(action);
283+
mockPushProcessors.checkRepoInAuthorisedList.resolves(action);
285284

286-
expect(mockPushProcessors.checkRepoInAuthorisedList.called).to.be.false;
287-
expect(mockPushProcessors.parsePush.called).to.be.false;
288-
expect(result).to.deep.equal(action);
285+
await chain.executeChain(req);
286+
expect(mockPushProcessors.checkRepoInAuthorisedList.called).to.be.true;
289287
});
290288

291289
it('should approve push automatically and record in the database', async function () {

test/teeAndValidation.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ describe('teeAndValidate middleware', () => {
5656
expect(next.called).to.be.false;
5757

5858
expect(res.set.called).to.be.true;
59-
expect(res.status.calledWith(200)).to.be.true;
59+
expect(res.status.calledWith(403)).to.be.true;
6060
expect(res.send.calledWith(handleMessage('denied!'))).to.be.true;
6161
});
6262

0 commit comments

Comments
 (0)