-
-
Notifications
You must be signed in to change notification settings - Fork 56
/
letsencrypt.js
236 lines (210 loc) · 8.84 KB
/
letsencrypt.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
'use strict';
const { tools } = require('@iobroker/js-controller-common');
const fs = require('fs-extra');
/**
* This method initializes a web server with certificates if needed and returns a promise
* @param app
* @param settings
* @param certificates
* @param leSettings
* @param log
* @param adapter
* @returns {Promise<unknown>}
*/
async function createServerAsync(app, settings, certificates, leSettings, log, adapter) {
log.warn(
`Information for Developer: Using the direct "Let's encrypt" module import is deprecated and will be removed in the next js-controller version, use @iobroker/webserver instead`
);
if (!settings.secure) {
log.debug('Using http createServer');
return require('http').createServer(app);
} else if (!settings.leEnabled) {
log.debug('Using https createServer');
return require('https').createServer(certificates, app);
}
// prepare domains
if (leSettings && typeof leSettings.domains === 'string') {
leSettings.domains = leSettings.domains
.split(',')
.map(d => d.trim())
.filter(d => d);
}
if (!leSettings || !leSettings.email || !Array.isArray(leSettings.domains) || !leSettings.domains.length) {
log.error(
"Please specify the email address and domains to use Let's Encrypt certificates! We reuse old certificates"
);
return require('https').createServer(certificates, app);
}
let leDir;
let configPath = tools.getConfigFileName().replace(/\\/g, '/');
const parts = configPath.split('/');
parts.pop();
configPath = parts.join('/');
if (leSettings.path && (leSettings.path[0] === '/' || leSettings.path.match(/^[A-Za-z]:/))) {
leDir = leSettings.path;
} else {
leDir = `${configPath}/${leSettings.path || 'letsencrypt'}`;
}
try {
if (!fs.existsSync(leDir)) {
fs.mkdirSync(leDir);
}
} catch (err) {
log.error(
`Lets encrypt Directory does not exist and can not be created as ${leDir}: ${err.message}. We reuse old certificates`
);
return require('https').createServer(certificates, app);
}
// TODO? Tried adding sites in the gl create but that didn't work so is below with gl.add.
const pkg = require(`${tools.getControllerDir()}/package.json`);
const glOpts = {
packageRoot: tools.getControllerDir(),
configDir: leDir,
maintainerEmail: leSettings.email,
cluster: false,
packageAgent: `${pkg.name}/${pkg.version}`,
renew: settings.leUpdate,
notify: (ev, params) => {
switch (ev) {
case 'warning':
log.warn(`[LE] ${typeof params === 'string' ? params : JSON.stringify(params)}`);
break;
case 'error':
log.error(`[LE] ${typeof params === 'string' ? params : JSON.stringify(params)}`);
break;
default:
// For all other event types, always debug
log.debug(`[LE] ${ev}: ${typeof params === 'string' ? params : JSON.stringify(params)}`);
// Special cases for certificate issue/renewal notices
// TODO: possibly check for ev === 'cert_renewal' / 'cert_issue'
if (params.status === 'valid' && params.altname) {
log.info(`Received new certificate for ${params.altname} via ${params.type}`);
} else if (params.renewAt) {
log.info(
`Certificate for ${params.subject} is valid till ${new Date(params.renewAt).toISOString()}`
);
adapter &&
adapter.sendToHost(adapter.host, 'certsUpdated', {
validTill: params.renewAt,
domain: params.subject,
instance: adapter.namespace
});
}
break;
}
}
};
if (settings.leStaging) {
log.debug('Greenlock staging enabled');
glOpts.staging = true;
}
const gl = require('greenlock').create(glOpts);
let addedDomains = 0;
// Add sites.
// Allowed in leSettings.domains:
// * "String" with comma separated list of single domain names (parsing above)
// * each will be registered as own cert
// * "Array" with eighter
// * "String" that is the domain name and each will be registered as own cert
// * "Array of String where the first entry is the main domain name and others
// alternative names (alternative names also contain main domain for greenlock)
for (const domainEntry of leSettings.domains) {
let altnames;
let domain;
if (Array.isArray(domainEntry)) {
altnames = domainEntry;
domain = altnames[0];
} else if (typeof domainEntry === 'string') {
altnames = [domainEntry];
domain = domainEntry;
}
if (!domain || !altnames) {
continue;
}
log.debug(`Adding to Greenlock: ${domain} with alternative names ${altnames.join(',')}`);
try {
await gl.add({ subject: domain, altnames });
addedDomains++;
} catch (err) {
log.warn(`Failed to add "${domain}" into configuration: ${err.message}`);
}
}
if (!addedDomains) {
log.error(`No domains available for Let's Encrypt, reuse old certificates`);
return require('https').createServer(certificates, app);
}
await gl.manager.defaults({
agreeToTerms: true,
subscriberEmail: leSettings.email
});
return new Promise((resolve, reject) => {
require('greenlock-express')
.init({
greenlock: gl
})
.ready(glx => {
// The below is from greenlock-express examples/https/server.js
if (settings.leUpdate) {
// Start the challenge server. Wait for it before resolving (pass back https).
settings.lePort = parseInt(settings.lePort, 10) || 80;
const bind = !settings.bind || settings.bind === '0.0.0.0' ? undefined : settings.bind || undefined;
// Check port not in use, because catching EADDRINUSE from httpServer.listen not possible.
adapter.getPort(settings.lePort, bind, port => {
if (port !== settings.lePort) {
reject(new Error(`Challenge server port ${settings.lePort} already in use`));
} else {
const httpServer = glx.httpServer();
httpServer.listen(settings.lePort, bind, () => {
log.info(`Challenge server listening on port ${settings.lePort}`);
log.info(
`If something is not working and your adapter webpage is not reachable anymore you can turn off HTTPS with executing "iobroker set ${
adapter ? adapter.namespace : 'admin'
} --secure false" in your shell.`
);
resolve(glx.httpsServer(null, app));
});
}
});
} else {
// Just resolve with https.
// Don't call listen here (or above) because caller will do that
resolve(glx.httpsServer(null, app));
}
});
});
}
/**
* This method initializes a web server with certificates if needed. No Let's encrypt support anymore
* @param app
* @param settings
* @param certificates
* @param leSettings
* @param log
* @deprecated
* @returns {object}
*/
function createServer(app, settings, certificates, leSettings, log) {
log.warn(
`Information for Developer: Using the direct "Let's encrypt" module import is deprecated and will be removed in the next js-controller version, use @iobroker/webserver instead`
);
let server;
if (settings.secure) {
if (leSettings && settings.leEnabled) {
log.info(
'Please update this adapter to the latest version to allow again to update your certificates. Using the current certificates'
);
}
try {
server = require('https').createServer(certificates, app);
} catch (err) {
log.error(`HTTPS server could not be started: ${err.message}`);
}
} else {
server = require('http').createServer(app);
}
return server;
}
module.exports = {
createServerAsync,
createServer
};