Skip to content

Commit a4dc4af

Browse files
committed
Add server side private IP blocking for data source endpoints validation
Signed-off-by: Kristen Tian <tyarong@amazon.com>
1 parent 86d42bc commit a4dc4af

File tree

3 files changed

+95
-11
lines changed

3 files changed

+95
-11
lines changed

src/plugins/data_source/server/saved_objects/data_source_saved_objects_client_wrapper.ts

+5-11
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
UsernamePasswordTypedContent,
2525
} from '../../common/data_sources';
2626
import { EncryptionContext, CryptographyServiceSetup } from '../cryptography_service';
27+
import { isValidURL } from '../util';
2728

2829
/**
2930
* Describes the Credential Saved Objects Client Wrapper class,
@@ -138,15 +139,6 @@ export class DataSourceSavedObjectsClientWrapper {
138139
};
139140
};
140141

141-
private isValidUrl(endpoint: string) {
142-
try {
143-
const url = new URL(endpoint);
144-
return Boolean(url) && (url.protocol === 'http:' || url.protocol === 'https:');
145-
} catch (e) {
146-
return false;
147-
}
148-
}
149-
150142
private async validateAndEncryptAttributes<T = unknown>(attributes: T) {
151143
this.validateAttributes(attributes);
152144

@@ -254,8 +246,10 @@ export class DataSourceSavedObjectsClientWrapper {
254246
);
255247
}
256248

257-
if (!this.isValidUrl(endpoint)) {
258-
throw SavedObjectsErrorHelpers.createBadRequestError('"endpoint" attribute is not valid');
249+
if (!isValidURL(endpoint)) {
250+
throw SavedObjectsErrorHelpers.createBadRequestError(
251+
'"endpoint" attribute is not valid or allowed'
252+
);
259253
}
260254

261255
if (!auth) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import dns from 'dns-sync';
7+
import IPCIDR from 'ip-cidr';
8+
9+
const BLOCK_LIST = [
10+
'127.0.0.0/8',
11+
'::1/128',
12+
'169.254.0.0/16',
13+
'fe80::/10',
14+
'10.0.0.0/8',
15+
'172.16.0.0/12',
16+
'192.168.0.0/16',
17+
'fc00::/7',
18+
'0.0.0.0/8',
19+
'100.64.0.0/10',
20+
'192.0.0.0/24',
21+
'192.0.2.0/24',
22+
'198.18.0.0/15',
23+
'192.88.99.0/24',
24+
'198.51.100.0/24',
25+
'203.0.113.0/24',
26+
'224.0.0.0/4',
27+
'240.0.0.0/4',
28+
'255.255.255.255/32',
29+
'::/128',
30+
'2001:db8::/32',
31+
'ff00::/8',
32+
];
33+
34+
export function isValidURL(endpoint: string) {
35+
// Check the format of URL, URL has be in the format as
36+
// scheme://server/path/resource otherwise an TypeError
37+
// would be thrown.
38+
let url;
39+
try {
40+
url = new URL(endpoint);
41+
} catch (err) {
42+
return false;
43+
}
44+
45+
if (!(Boolean(url) && (url.protocol === 'http:' || url.protocol === 'https:'))) {
46+
return false;
47+
}
48+
49+
const ip = getIpAddress(url);
50+
if (!ip) {
51+
return false;
52+
}
53+
54+
// IP CIDR check if a specific IP address fall in the
55+
// range of an IP address block
56+
for (const bl of BLOCK_LIST) {
57+
const cidr = new IPCIDR(bl);
58+
if (cidr.contains(ip)) {
59+
return false;
60+
}
61+
}
62+
return true;
63+
}
64+
65+
/**
66+
* Resolve hostname to IP address
67+
* @param {object} urlObject
68+
* @returns {string} configuredIP
69+
* or null if it cannot be resolve
70+
* According to RFC, all IPv6 IP address needs to be in []
71+
* such as [::1].
72+
* So if we detect a IPv6 address, we remove brackets.
73+
*/
74+
function getIpAddress(urlObject: URL) {
75+
const hostname = urlObject.hostname;
76+
const configuredIP = dns.resolve(hostname);
77+
if (configuredIP) {
78+
return configuredIP;
79+
}
80+
if (hostname.startsWith('[') && hostname.endsWith(']')) {
81+
return hostname.substr(1).slice(0, -1);
82+
}
83+
return null;
84+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
export { isValidURL } from './endpoint_validator';

0 commit comments

Comments
 (0)