@cloud-cli/px 3.4.1 → 3.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/proxy.d.ts +14 -10
- package/dist/proxy.js +63 -64
- package/package.json +1 -1
package/dist/proxy.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
|
|
2
|
+
/// <reference types="node" />
|
|
3
|
+
/// <reference types="node" />
|
|
4
|
+
/// <reference types="node" />
|
|
5
|
+
import { IncomingMessage, ClientRequest, ServerResponse } from 'http';
|
|
6
|
+
import { ServerOptions as HttpsServerOptions } from 'https';
|
|
7
|
+
import { SecureContext } from 'tls';
|
|
3
8
|
import { Resource } from '@cloud-cli/store';
|
|
4
9
|
export declare class ProxyEntry extends Resource {
|
|
5
10
|
id: number;
|
|
@@ -10,12 +15,8 @@ export declare class ProxyEntry extends Resource {
|
|
|
10
15
|
cors: boolean;
|
|
11
16
|
constructor(p: Partial<ProxyEntry>);
|
|
12
17
|
}
|
|
13
|
-
interface Certificates {
|
|
14
|
-
key: string;
|
|
15
|
-
cert: string;
|
|
16
|
-
}
|
|
17
18
|
export declare class ProxyServer {
|
|
18
|
-
protected certs: Record<string,
|
|
19
|
+
protected certs: Record<string, SecureContext>;
|
|
19
20
|
protected targets: Record<string, ProxyEntry>;
|
|
20
21
|
protected servers: any;
|
|
21
22
|
start(): void;
|
|
@@ -23,8 +24,11 @@ export declare class ProxyServer {
|
|
|
23
24
|
protected loadCertificates(): Promise<void>;
|
|
24
25
|
protected loadTargets(): Promise<void>;
|
|
25
26
|
protected serveRequest(req: IncomingMessage, res: ServerResponse, insecure?: boolean): void;
|
|
26
|
-
protected getSslOptions():
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
protected getSslOptions(): HttpsServerOptions;
|
|
28
|
+
protected findRootDomain(domain: string): SecureContext;
|
|
29
|
+
protected setHeaders(from: IncomingMessage, to: ServerResponse | ClientRequest): void;
|
|
30
|
+
protected getOrigin(req: IncomingMessage): URL;
|
|
31
|
+
protected getHost(string: string): string;
|
|
32
|
+
protected setCorsHeaders(req: IncomingMessage, res: ServerResponse): void;
|
|
33
|
+
protected handleError(error: any, res: ServerResponse): void;
|
|
29
34
|
}
|
|
30
|
-
export {};
|
package/dist/proxy.js
CHANGED
|
@@ -4,12 +4,11 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { createServer as createHttpServer, request as httpRequest } from 'http';
|
|
7
|
+
import { createServer as createHttpServer, request as httpRequest, } from 'http';
|
|
8
8
|
import { createServer as createHttpsServer, request as httpsRequest } from 'https';
|
|
9
9
|
import { createSecureContext } from 'tls';
|
|
10
10
|
import { Model, Primary, Property, Query, Resource } from '@cloud-cli/store';
|
|
11
|
-
import { readdir, readFile
|
|
12
|
-
import { existsSync } from 'fs';
|
|
11
|
+
import { readdir, readFile } from 'fs/promises';
|
|
13
12
|
import { join } from 'path';
|
|
14
13
|
const certificatesFolder = process.env.PX_CERTS_FOLDER || '/etc/letsencrypt/live';
|
|
15
14
|
const certificateFile = 'fullchain.pem';
|
|
@@ -62,12 +61,9 @@ export class ProxyServer {
|
|
|
62
61
|
}
|
|
63
62
|
async loadCertificates() {
|
|
64
63
|
const certs = (this.certs = {});
|
|
65
|
-
const localCerts = await readdir(certificatesFolder);
|
|
66
|
-
const folders =
|
|
67
|
-
|
|
68
|
-
return status.isDirectory() ? dir : '';
|
|
69
|
-
}));
|
|
70
|
-
folders.filter(Boolean).forEach(async (rootDomain) => {
|
|
64
|
+
const localCerts = await readdir(certificatesFolder, { withFileTypes: true });
|
|
65
|
+
const folders = localCerts.filter((entry) => entry.isDirectory()).map((dir) => dir.name);
|
|
66
|
+
for (const rootDomain of folders) {
|
|
71
67
|
if (process.env.DEBUG) {
|
|
72
68
|
console.log(`Loading certificate for ${rootDomain}`);
|
|
73
69
|
}
|
|
@@ -75,7 +71,7 @@ export class ProxyServer {
|
|
|
75
71
|
cert: await readFile(join(certificatesFolder, rootDomain, certificateFile), 'utf8'),
|
|
76
72
|
key: await readFile(join(certificatesFolder, rootDomain, keyFile), 'utf8'),
|
|
77
73
|
});
|
|
78
|
-
}
|
|
74
|
+
}
|
|
79
75
|
}
|
|
80
76
|
async loadTargets() {
|
|
81
77
|
const targets = await Resource.find(ProxyEntry, new Query());
|
|
@@ -83,7 +79,7 @@ export class ProxyServer {
|
|
|
83
79
|
targets.forEach((entry) => (this.targets[entry.domain] = entry));
|
|
84
80
|
}
|
|
85
81
|
serveRequest(req, res, insecure = false) {
|
|
86
|
-
const origin = getOrigin(req);
|
|
82
|
+
const origin = this.getOrigin(req);
|
|
87
83
|
const proxyEntry = this.targets[origin?.hostname];
|
|
88
84
|
if (!origin.hostname || !proxyEntry) {
|
|
89
85
|
res.writeHead(404, 'Not found');
|
|
@@ -107,24 +103,25 @@ export class ProxyServer {
|
|
|
107
103
|
const url = new URL(req.url, target);
|
|
108
104
|
const isCorsPreflight = req.method === 'OPTIONS' && proxyEntry.cors;
|
|
109
105
|
if (isCorsPreflight) {
|
|
110
|
-
setCorsHeaders(req, res);
|
|
106
|
+
this.setCorsHeaders(req, res);
|
|
111
107
|
res.writeHead(204, { 'Content-Length': '0' });
|
|
112
108
|
res.end();
|
|
113
109
|
return;
|
|
114
110
|
}
|
|
115
111
|
const proxyRequest = (url.protocol === 'https:' ? httpsRequest : httpRequest)(url, { method: req.method });
|
|
116
|
-
setHeaders(req, proxyRequest);
|
|
117
|
-
proxyRequest.setHeader('host', getHost(target));
|
|
112
|
+
this.setHeaders(req, proxyRequest);
|
|
113
|
+
proxyRequest.setHeader('host', this.getHost(target));
|
|
118
114
|
proxyRequest.setHeader('x-forwarded-for', req.headers.host);
|
|
119
|
-
proxyRequest.setHeader('x-forwarded-proto', insecure ? 'http
|
|
115
|
+
proxyRequest.setHeader('x-forwarded-proto', insecure ? 'http' : 'https');
|
|
116
|
+
proxyRequest.setHeader('forwarded', 'host=' + req.headers.host + ';proto=' + insecure ? 'http' : 'https');
|
|
120
117
|
req.on('data', (chunk) => proxyRequest.write(chunk));
|
|
121
118
|
req.on('end', () => proxyRequest.end());
|
|
122
|
-
proxyRequest.on('error', (error) => handleError(error, res));
|
|
119
|
+
proxyRequest.on('error', (error) => this.handleError(error, res));
|
|
123
120
|
proxyRequest.on('response', (proxyRes) => {
|
|
124
|
-
setHeaders(proxyRes, res);
|
|
121
|
+
this.setHeaders(proxyRes, res);
|
|
125
122
|
const isCorsSimple = req.method !== 'OPTIONS' && proxyEntry.cors && req.headers.origin;
|
|
126
123
|
if (isCorsSimple) {
|
|
127
|
-
setCorsHeaders(req, res);
|
|
124
|
+
this.setCorsHeaders(req, res);
|
|
128
125
|
}
|
|
129
126
|
res.writeHead(proxyRes.statusCode, proxyRes.statusMessage);
|
|
130
127
|
proxyRes.on('data', (chunk) => res.write(chunk));
|
|
@@ -135,59 +132,61 @@ export class ProxyServer {
|
|
|
135
132
|
const server = this;
|
|
136
133
|
return {
|
|
137
134
|
SNICallback(domain, cb) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
return cb(null, server.certs[rootDomain]);
|
|
135
|
+
const rootDomain = server.findRootDomain(domain);
|
|
136
|
+
if (rootDomain) {
|
|
137
|
+
return cb(null, rootDomain);
|
|
142
138
|
}
|
|
143
|
-
|
|
139
|
+
cb(new Error('Not found', { cause: 404 }), null);
|
|
144
140
|
},
|
|
145
141
|
};
|
|
146
142
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
143
|
+
findRootDomain(domain) {
|
|
144
|
+
const parts = domain.split('/')[0].split('.');
|
|
145
|
+
const certs = this.certs;
|
|
146
|
+
while (parts.length) {
|
|
147
|
+
const rootDomain = parts.join('.');
|
|
148
|
+
if (certs[rootDomain]) {
|
|
149
|
+
return certs[rootDomain];
|
|
150
|
+
}
|
|
151
|
+
parts.shift();
|
|
155
152
|
}
|
|
156
|
-
|
|
153
|
+
return null;
|
|
157
154
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
to.setHeader(header[0], header[1]);
|
|
155
|
+
setHeaders(from, to) {
|
|
156
|
+
const headers = Object.entries(from.headers);
|
|
157
|
+
for (const header of headers) {
|
|
158
|
+
to.setHeader(header[0], header[1]);
|
|
159
|
+
}
|
|
164
160
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
if (error.code === 'ECONNREFUSED') {
|
|
169
|
-
res.writeHead(502);
|
|
170
|
-
res.end();
|
|
171
|
-
return;
|
|
161
|
+
getOrigin(req) {
|
|
162
|
+
const host = req.headers['x-forwarded-for'] || req.headers.host || '';
|
|
163
|
+
return (host && new URL('http://' + host)) || null;
|
|
172
164
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
165
|
+
getHost(string) {
|
|
166
|
+
const url = new URL(string);
|
|
167
|
+
return url.hostname + (url.port ? ':' + url.port : '');
|
|
168
|
+
}
|
|
169
|
+
setCorsHeaders(req, res) {
|
|
170
|
+
const headers = req.headers;
|
|
171
|
+
const corsOrigin = new URL(req.headers.origin).origin;
|
|
172
|
+
const allowedMethod = headers['access-control-request-method'] || 'GET,HEAD,PUT,PATCH,POST,DELETE';
|
|
173
|
+
const allowedHeaders = headers['access-control-request-headers'] || '*';
|
|
174
|
+
res.setHeader('Vary', 'Origin');
|
|
175
|
+
res.setHeader('Access-Control-Allow-Origin', corsOrigin);
|
|
176
|
+
res.setHeader('Access-Control-Allow-Headers', allowedHeaders);
|
|
177
|
+
res.setHeader('Access-Control-Allow-Methods', allowedMethod);
|
|
178
|
+
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
|
179
|
+
}
|
|
180
|
+
handleError(error, res) {
|
|
181
|
+
console.error(error);
|
|
182
|
+
if (error.code === 'ECONNREFUSED') {
|
|
183
|
+
res.writeHead(502);
|
|
184
|
+
res.end();
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (!res.headersSent) {
|
|
188
|
+
res.writeHead(500);
|
|
189
|
+
res.end();
|
|
190
|
+
}
|
|
176
191
|
}
|
|
177
|
-
}
|
|
178
|
-
function getOrigin(req) {
|
|
179
|
-
const host = req.headers['x-forwarded-host'] || req.headers.host || '';
|
|
180
|
-
return (host && new URL('http://' + host)) || null;
|
|
181
|
-
}
|
|
182
|
-
function getHost(string) {
|
|
183
|
-
const url = new URL(string);
|
|
184
|
-
return url.hostname + (url.port ? ':' + url.port : '');
|
|
185
|
-
}
|
|
186
|
-
function setCorsHeaders(req, res) {
|
|
187
|
-
const corsOrigin = new URL(req.headers.origin).origin;
|
|
188
|
-
res.setHeader('Vary', 'Origin');
|
|
189
|
-
res.setHeader('Access-Control-Allow-Origin', corsOrigin);
|
|
190
|
-
res.setHeader('Access-Control-Allow-Headers', '*');
|
|
191
|
-
res.setHeader('Access-Control-Allow-Methods', 'GET,HEAD,PUT,PATCH,POST,DELETE');
|
|
192
|
-
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
|
193
192
|
}
|