385 lines
12 KiB
JavaScript
385 lines
12 KiB
JavaScript
'use strict';
|
|
|
|
var Flags = module.exports;
|
|
|
|
var path = require('path');
|
|
//var pkgpath = path.join(__dirname, '..', 'package.json');
|
|
var pkgpath = path.join(process.cwd(), 'package.json');
|
|
var GreenlockRc = require('./greenlockrc.js');
|
|
|
|
// These are ALL options
|
|
// The individual CLI files each select a subset of them
|
|
Flags.flags = function(mconf, myOpts) {
|
|
// Current Manager Config
|
|
if (!mconf) {
|
|
mconf = {};
|
|
}
|
|
|
|
// Extra Override Options
|
|
if (!myOpts) {
|
|
myOpts = {};
|
|
}
|
|
|
|
return {
|
|
subject: [
|
|
false,
|
|
'the "subject" (primary domain) of the certificate',
|
|
'string'
|
|
],
|
|
altnames: [
|
|
false,
|
|
'the "subject alternative names" (additional domains) on the certificate, the first of which MUST be the subject',
|
|
'string'
|
|
],
|
|
servername: [
|
|
false,
|
|
'a name that matches a subject or altname',
|
|
'string'
|
|
],
|
|
servernames: [
|
|
false,
|
|
'a list of names that matches a subject or altname',
|
|
'string'
|
|
],
|
|
cluster: [false, 'initialize with cluster mode on', 'boolean', false],
|
|
'renew-offset': [
|
|
false,
|
|
"time to wait until renewing the cert such as '45d' (45 days after being issued) or '-3w' (3 weeks before expiration date)",
|
|
'string',
|
|
mconf.renewOffset
|
|
],
|
|
'customer-email': [
|
|
false,
|
|
"the email address of the owner of the domain or site (not necessarily the Let's Encrypt or ACME subscriber)",
|
|
'string'
|
|
],
|
|
'subscriber-email': [
|
|
false,
|
|
"the email address of the Let's Encrypt or ACME Account subscriber (not necessarily the domain owner)",
|
|
'string'
|
|
],
|
|
'maintainer-email': [
|
|
false,
|
|
'the maintainance contact for security and critical bug notices',
|
|
'string'
|
|
],
|
|
'account-key-type': [
|
|
false,
|
|
"either 'P-256' (ECDSA) or 'RSA-2048' - although other values are technically supported, they don't make sense and won't work with many services (More bits != More security)",
|
|
'string',
|
|
mconf.accountKeyType
|
|
],
|
|
'server-key-type': [
|
|
false,
|
|
"either 'RSA-2048' or 'P-256' (ECDSA) - although other values are technically supported, they don't make sense and won't work with many services (More bits != More security)",
|
|
'string',
|
|
mconf.serverKeyType
|
|
],
|
|
store: [
|
|
false,
|
|
'the module name or file path of the store module to use',
|
|
'string'
|
|
//mconf.store.module
|
|
],
|
|
'store-xxxx': [
|
|
false,
|
|
'an option for the chosen store module, such as --store-apikey or --store-bucket',
|
|
'bag'
|
|
],
|
|
manager: [
|
|
false,
|
|
'the module name or file path of the manager module to use',
|
|
'string',
|
|
'greenlock-manager-fs'
|
|
],
|
|
'manager-xxxx': [
|
|
false,
|
|
'an option for the chosen manager module, such as --manager-apikey or --manager-dburl',
|
|
'bag'
|
|
],
|
|
challenge: [
|
|
false,
|
|
'the module name or file path of the HTTP-01, DNS-01, or TLS-ALPN-01 challenge module to use',
|
|
'string',
|
|
''
|
|
/*
|
|
Object.keys(mconf.challenges)
|
|
.map(function(typ) {
|
|
return mconf.challenges[typ].module;
|
|
})
|
|
.join(',')
|
|
*/
|
|
],
|
|
'challenge-xxxx': [
|
|
false,
|
|
'an option for the chosen challenge module, such as --challenge-apikey or --challenge-bucket',
|
|
'bag'
|
|
],
|
|
'challenge-json': [
|
|
false,
|
|
'a JSON string containing all option for the chosen challenge module (instead of --challenge-xxxx)',
|
|
'json',
|
|
'{}'
|
|
],
|
|
'challenge-http-01': [
|
|
false,
|
|
'the module name or file path of the HTTP-01 to add',
|
|
'string'
|
|
//(mconf.challenges['http-01'] || {}).module
|
|
],
|
|
'challenge-http-01-xxxx': [
|
|
false,
|
|
'an option for the chosen challenge module, such as --challenge-http-01-apikey or --challenge-http-01-bucket',
|
|
'bag'
|
|
],
|
|
'challenge-dns-01': [
|
|
false,
|
|
'the module name or file path of the DNS-01 to add',
|
|
'string'
|
|
//(mconf.challenges['dns-01'] || {}).module
|
|
],
|
|
'challenge-dns-01-xxxx': [
|
|
false,
|
|
'an option for the chosen challenge module, such as --challenge-dns-01-apikey or --challenge-dns-01-bucket',
|
|
'bag'
|
|
],
|
|
'challenge-tls-alpn-01': [
|
|
false,
|
|
'the module name or file path of the DNS-01 to add',
|
|
'string'
|
|
//(mconf.challenges['tls-alpn-01'] || {}).module
|
|
],
|
|
'challenge-tls-alpn-01-xxxx': [
|
|
false,
|
|
'an option for the chosen challenge module, such as --challenge-tls-alpn-01-apikey or --challenge-tls-alpn-01-bucket',
|
|
'bag'
|
|
],
|
|
'force-save': [
|
|
false,
|
|
"save all options for this site, even if it's the same as the defaults",
|
|
'boolean',
|
|
myOpts.forceSave || false
|
|
]
|
|
};
|
|
};
|
|
|
|
Flags.init = function(myOpts) {
|
|
return GreenlockRc(pkgpath).then(async function(rc) {
|
|
rc._bin_mode = true;
|
|
var Greenlock = require('../../');
|
|
// this is a copy, so it's safe to modify
|
|
var greenlock = Greenlock.create(rc);
|
|
var mconf = await greenlock.manager.defaults();
|
|
var flagOptions = Flags.flags(mconf, myOpts);
|
|
return {
|
|
flagOptions,
|
|
rc,
|
|
greenlock,
|
|
mconf
|
|
};
|
|
});
|
|
};
|
|
|
|
Flags.mangleFlags = function(flags, mconf, sconf, extras) {
|
|
if (extras) {
|
|
if (extras.forceSave) {
|
|
flags.forceSave = true;
|
|
}
|
|
}
|
|
//console.log('debug a:', flags);
|
|
|
|
if ('altnames' in flags) {
|
|
flags.altnames = (flags.altnames || '').split(/[,\s]+/).filter(Boolean);
|
|
}
|
|
if ('servernames' in flags) {
|
|
flags.servernames = (flags.servernames || '')
|
|
.split(/[,\s]+/)
|
|
.filter(Boolean);
|
|
}
|
|
|
|
var store;
|
|
if (flags.store) {
|
|
store = flags.storeOpts;
|
|
store.module = flags.store;
|
|
flags.store = store;
|
|
} else {
|
|
delete flags.store;
|
|
}
|
|
delete flags.storeOpts;
|
|
|
|
// If this is additive, make an object to hold all values
|
|
var isAdditive = [
|
|
['http-01', 'Http01'],
|
|
['dns-01', 'Dns01'],
|
|
['tls-alpn-01', 'TlsAlpn01']
|
|
].some(function(types) {
|
|
var typCamel = types[1];
|
|
var modname = 'challenge' + typCamel;
|
|
if (flags[modname]) {
|
|
if (!flags.challenges) {
|
|
flags.challenges = {};
|
|
}
|
|
return true;
|
|
}
|
|
});
|
|
if (isAdditive && sconf) {
|
|
// copy over the old
|
|
var schallenges = sconf.challenges || {};
|
|
Object.keys(schallenges).forEach(function(k) {
|
|
if (!flags.challenges[k]) {
|
|
flags.challenges[k] = schallenges[k];
|
|
}
|
|
});
|
|
}
|
|
|
|
var typ;
|
|
var challenge;
|
|
if (flags.challenge) {
|
|
// this varient of the flag is exclusive
|
|
flags.challenges = {};
|
|
isAdditive = false;
|
|
|
|
if (/http-01/.test(flags.challenge)) {
|
|
typ = 'http-01';
|
|
} else if (/dns-01/.test(flags.challenge)) {
|
|
typ = 'dns-01';
|
|
} else if (/tls-alpn-01/.test(flags.challenge)) {
|
|
typ = 'tls-alpn-01';
|
|
}
|
|
|
|
var modname = 'challenge';
|
|
var optsname = 'challengeOpts';
|
|
challenge = flags[optsname];
|
|
// JSON may already have module name
|
|
if (challenge.module) {
|
|
if (flags[modname] && challenge.module !== flags[modname]) {
|
|
console.error(
|
|
'module names do not match:',
|
|
JSON.stringify(challenge.module),
|
|
JSON.stringify(flags[modname])
|
|
);
|
|
process.exit(1);
|
|
}
|
|
} else {
|
|
challenge.module = flags[modname];
|
|
}
|
|
flags.challenges[typ] = challenge;
|
|
|
|
var chall = mconf.challenges[typ];
|
|
if (chall && challenge.module === chall.module) {
|
|
var keys = Object.keys(challenge);
|
|
var same =
|
|
!keys.length ||
|
|
keys.every(function(k) {
|
|
return chall[k] === challenge[k];
|
|
});
|
|
if (same && !flags.forceSave) {
|
|
delete flags.challenges;
|
|
}
|
|
}
|
|
}
|
|
delete flags.challenge;
|
|
delete flags.challengeOpts;
|
|
|
|
// Add each of the values, including the existing
|
|
[
|
|
['http-01', 'Http01'],
|
|
['dns-01', 'Dns01'],
|
|
['tls-alpn-01', 'TlsAlpn01']
|
|
].forEach(function(types) {
|
|
var typ = types[0];
|
|
var typCamel = types[1];
|
|
var modname = 'challenge' + typCamel;
|
|
var optsname = 'challenge' + typCamel + 'Opts';
|
|
var chall = mconf.challenges[typ];
|
|
var challenge = flags[optsname];
|
|
|
|
// this variant of the flag is additive
|
|
if (isAdditive && chall && flags.forceSave) {
|
|
if (flags.challenges && !flags.challenges[typ]) {
|
|
flags.challenges[typ] = chall;
|
|
}
|
|
}
|
|
|
|
if (!flags[modname]) {
|
|
delete flags[modname];
|
|
delete flags[optsname];
|
|
return;
|
|
}
|
|
|
|
// JSON may already have module name
|
|
if (challenge.module) {
|
|
if (flags[modname] && challenge.module !== flags[modname]) {
|
|
console.error(
|
|
'module names do not match:',
|
|
JSON.stringify(challenge.module),
|
|
JSON.stringify(flags[modname])
|
|
);
|
|
process.exit(1);
|
|
}
|
|
} else {
|
|
challenge.module = flags[modname];
|
|
}
|
|
if (flags[modname]) {
|
|
if (!flags.challenges) {
|
|
flags.challenges = {};
|
|
}
|
|
flags.challenges[typ] = challenge;
|
|
}
|
|
|
|
// Check to see if this is already what's set in the defaults
|
|
if (chall && challenge.module === chall.module) {
|
|
var keys = Object.keys(challenge);
|
|
// Check if all of the options are also the same
|
|
var same =
|
|
!keys.length ||
|
|
keys.every(function(k) {
|
|
return chall[k] === challenge[k];
|
|
});
|
|
if (same && !flags.forceSave) {
|
|
// If it's already the global, don't make it the per-site
|
|
delete flags[modname];
|
|
delete flags[optsname];
|
|
}
|
|
}
|
|
|
|
delete flags[modname];
|
|
delete flags[optsname];
|
|
});
|
|
|
|
[
|
|
['accountKeyType', [/256/, /384/, /EC/], 'EC-P256'],
|
|
['serverKeyType', [/RSA/], 'RSA-2048']
|
|
].forEach(function(k) {
|
|
var key = k[0];
|
|
var vals = k[1];
|
|
var val = flags[key];
|
|
if (val) {
|
|
if (
|
|
!vals.some(function(v) {
|
|
return v.test(val);
|
|
})
|
|
) {
|
|
flags[key] = k[2];
|
|
console.warn(
|
|
key,
|
|
"does not allow the value '",
|
|
val,
|
|
"' using the default '",
|
|
k[2],
|
|
"' instead."
|
|
);
|
|
}
|
|
}
|
|
});
|
|
|
|
Object.keys(flags).forEach(function(k) {
|
|
if (flags[k] === mconf[k] && !flags.forceSave) {
|
|
delete flags[k];
|
|
}
|
|
});
|
|
|
|
//console.log('debug z:', flags);
|
|
delete flags.forceSave;
|
|
};
|