feat(backend-common): added ports range

Signed-off-by: Antonio Musolino <antoniomusolino007@gmail.com>
This commit is contained in:
Antonio Musolino
2022-08-22 15:12:11 +02:00
parent f6ac8bf63c
commit e3b1993788
3 changed files with 110 additions and 4 deletions
+11
View File
@@ -0,0 +1,11 @@
---
'@backstage/backend-common': patch
---
Added port ranges in allowed hosts:
```yaml
reading:
allow:
- host: *.examples.org:900-1000
```
@@ -74,9 +74,14 @@ describe('FetchUrlReader', () => {
reading: {
allow: [
{ host: 'example.com' },
{ host: 'example.com:100-200' },
{ host: 'example.com:700' },
{ host: '*.examples.org' },
{ host: '*.examples.org:700' },
{ host: '*.examples.org:900-1000' },
{ host: '*.examples.org:900-1000' },
{ host: 'https.org:443' },
{ host: 'http.org:80' },
{
host: 'foobar.org',
paths: ['/dir1/'],
@@ -113,6 +118,55 @@ describe('FetchUrlReader', () => {
expect(predicate(new URL('https://foobar.org/dir1/subpath'))).toBe(true);
expect(predicate(new URL('https://foobar.org/dir12'))).toBe(false);
expect(predicate(new URL('https://foobar.org/'))).toBe(false);
expect(predicate(new URL('https://a.examples.org:900/test'))).toBe(true);
expect(predicate(new URL('https://a.examples.org:1000/test'))).toBe(true);
expect(predicate(new URL('https://a.examples.org:950/test'))).toBe(true);
expect(predicate(new URL('https://a.examples.org:1050/test'))).toBe(false);
expect(predicate(new URL('https://example.com:150/test'))).toBe(true);
expect(predicate(new URL('https://example.com:4000/test'))).toBe(false);
expect(predicate(new URL('https://https.org'))).toBe(true);
expect(predicate(new URL('http://https.org'))).toBe(false);
expect(predicate(new URL('http://http.org'))).toBe(true);
expect(predicate(new URL('https://http.org'))).toBe(false);
});
it('factory should throw for malformed uri', async () => {
const buildFactory = (hosts: string[]) => {
return FetchUrlReader.factory({
config: new ConfigReader({
backend: {
reading: {
allow: hosts.map(host => ({ host })),
},
},
}),
logger: getVoidLogger(),
treeResponseFactory: DefaultReadTreeResponseFactory.create({
config: new ConfigReader({}),
}),
});
};
expect(() =>
buildFactory(['example.com:100-200', 'example.com:100-']),
).toThrow();
expect(() =>
buildFactory(['example.com:100-200', 'example.com:-']),
).toThrow();
expect(() =>
buildFactory(['example.com:100-200', 'example.com:500-']),
).toThrow();
expect(() =>
buildFactory(['example.com:100-200', 'example.com:100-50']),
).toThrow();
expect(() =>
buildFactory(['example.com:100-200', 'example.com:-330-']),
).toThrow();
expect(() =>
buildFactory(['example.com:100-200', 'example.com:-100-300']),
).not.toThrow();
expect(() =>
buildFactory(['example.com:100-200', 'example.com:nb-300']),
).toThrow();
});
describe('read', () => {
@@ -27,6 +27,41 @@ import {
import path from 'path';
import { ReadUrlResponseFactory } from './ReadUrlResponseFactory';
const isInRange = (num: number, [start, end]: [number, number]) => {
return num >= start && num <= end;
};
const parsePortRange = (port: string): [number, number] => {
const isRange = port.includes('-');
if (isRange) {
const range = port
.split('-')
.map(v => parseInt(v, 10))
.filter(Boolean) as [number, number];
if (range.length !== 2) throw new Error(`Port range is not valid: ${port}`);
const [start, end] = range;
if (start <= 0 || end <= 0 || start > end)
throw new Error(`Port range is not valid: [${start}, ${end}]`);
return range;
}
const parsedPort = parseInt(port, 10);
return [parsedPort, parsedPort];
};
const parsePortPredicate = (port: string | undefined) => {
if (port) {
const range = parsePortRange(port);
return (url: URL) => {
if (url.port) return isInRange(parseInt(url.port, 10), range);
if (url.protocol === 'http:') return isInRange(80, range);
if (url.protocol === 'https:') return isInRange(443, range);
return false;
};
}
return (url: URL) => !url.port;
};
/**
* A {@link UrlReader} that does a plain fetch of the URL.
*
@@ -60,11 +95,17 @@ export class FetchUrlReader implements UrlReader {
}
: (_url: URL) => true;
const host = allowConfig.getString('host');
if (host.startsWith('*.')) {
const suffix = host.slice(1);
return (url: URL) => url.host.endsWith(suffix) && checkPath(url);
const [hostname, port] = host.split(':');
const checkPort = parsePortPredicate(port);
if (hostname.startsWith('*.')) {
const suffix = hostname.slice(1);
return (url: URL) =>
url.hostname.endsWith(suffix) && checkPath(url) && checkPort(url);
}
return (url: URL) => url.host === host && checkPath(url);
return (url: URL) =>
url.hostname === hostname && checkPath(url) && checkPort(url);
}) ?? [];
const reader = new FetchUrlReader();