now paying respect to same zone subdomains
This commit is contained in:
86
ts/cert.classes.cert.helpers.ts
Normal file
86
ts/cert.classes.cert.helpers.ts
Normal file
@ -0,0 +1,86 @@
|
||||
import {Cert} from "./index.ts";
|
||||
import * as plugins from "./cert.plugins";
|
||||
import * as paths from "./cert.paths";
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* schedule a retry of certificate request
|
||||
*/
|
||||
export let scheduleRetry = (domainArg:string,certClassArg:Cert) => {
|
||||
let done = plugins.q.defer();
|
||||
setTimeout(() => {
|
||||
certClassArg.getDomainCert(domainArg)
|
||||
.then(done.resolve);
|
||||
},20000);
|
||||
return done.promise;
|
||||
};
|
||||
|
||||
/**
|
||||
* check if a given domainCert is still valid
|
||||
*/
|
||||
export let checkDomainsStillValid = (domainNameArg: string, sslDirArg: string): boolean => {
|
||||
let domainConfigPath = plugins.path.join(sslDirArg, domainNameArg, "config.json");
|
||||
if (plugins.smartfile.fs.fileExistsSync(domainConfigPath)) {
|
||||
let domainConfig = plugins.smartfile.fs.toObjectSync(
|
||||
domainConfigPath,
|
||||
"json"
|
||||
);
|
||||
if (Date.now() >= ((domainConfig.expires - 604800) * 1000)) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export interface certConfig {
|
||||
domainName: string;
|
||||
created: number;
|
||||
expires: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* update a ssl directory
|
||||
*/
|
||||
export let updateSslDirSync = (sslDirArg: string, domainNameArg: string) => {
|
||||
plugins.smartfile.fs.ensureDirSync(sslDirArg);
|
||||
let domainCertFolder = plugins.path.join(paths.certDir, domainNameArg)
|
||||
if (plugins.smartfile.fs.listFoldersSync(paths.certDir).indexOf(domainNameArg) != -1) {
|
||||
plugins.smartfile.fs.copySync(
|
||||
plugins.path.join(domainCertFolder, "fullchain.pem"),
|
||||
plugins.path.join(sslDirArg, domainNameArg, "fullchain.pem")
|
||||
);
|
||||
plugins.smartfile.fs.copySync(
|
||||
plugins.path.join(domainCertFolder, "privkey.pem"),
|
||||
plugins.path.join(sslDirArg, domainNameArg, "privkey.pem")
|
||||
);
|
||||
// create cert config
|
||||
let certRegex = /.*\-([0-9]*)\.pem/;
|
||||
let certFileNameWithTime: string = plugins.smartfile.fs.listFilesSync(domainCertFolder, certRegex)[0];
|
||||
let certTime = parseInt(certRegex.exec(certFileNameWithTime)[1]);
|
||||
let certConfig: certConfig = {
|
||||
domainName: domainNameArg,
|
||||
created: certTime,
|
||||
expires: certTime + 7776000
|
||||
};
|
||||
plugins.smartfile.memory.toFsSync(
|
||||
JSON.stringify(certConfig),
|
||||
plugins.path.join(sslDirArg, domainNameArg, "config.json")
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
const enum gitSyncDirection {
|
||||
toOrigin,
|
||||
fromOrigin
|
||||
}
|
||||
|
||||
let updateGitOrigin = (syncDirectionArg: gitSyncDirection) => {
|
||||
|
||||
};
|
||||
|
||||
updateGitOrigin(gitSyncDirection.toOrigin);
|
160
ts/cert.classes.cert.ts
Normal file
160
ts/cert.classes.cert.ts
Normal file
@ -0,0 +1,160 @@
|
||||
import * as plugins from "./cert.plugins";
|
||||
import * as paths from "./cert.paths";
|
||||
import * as helpers from "./cert.classes.cert.helpers"
|
||||
|
||||
export interface ICertConstructorOptions {
|
||||
cfEmail: string,
|
||||
cfKey: string,
|
||||
sslDir?: string,
|
||||
gitOriginRepo?: string,
|
||||
testMode?: boolean
|
||||
};
|
||||
|
||||
export class Cert {
|
||||
private _cfEmail: string;
|
||||
private _cfKey: string;
|
||||
private _sslDir: string;
|
||||
private _gitOriginRepo: string;
|
||||
private _testMode: boolean;
|
||||
domainCertRequestMap: plugins.lik.Stringmap = new plugins.lik.Stringmap();
|
||||
certificatesPresent: Certificate[];
|
||||
certificatesValid: Certificate[];
|
||||
|
||||
/**
|
||||
* Constructor for Cert object
|
||||
*/
|
||||
constructor(optionsArg: ICertConstructorOptions) {
|
||||
this._cfEmail = optionsArg.cfEmail;
|
||||
this._cfKey = optionsArg.cfKey;
|
||||
this._sslDir = optionsArg.sslDir;
|
||||
this._gitOriginRepo = optionsArg.gitOriginRepo;
|
||||
this._testMode = optionsArg.testMode;
|
||||
// write hook config
|
||||
let config = {
|
||||
cfEmail: this._cfEmail,
|
||||
cfKey: this._cfKey
|
||||
}
|
||||
plugins.smartfile.memory.toFsSync(
|
||||
JSON.stringify(config),
|
||||
plugins.path.join(__dirname, "assets/config.json")
|
||||
);
|
||||
// setup sslDir
|
||||
if (!this._sslDir) this._sslDir = paths.defaultSslDir;
|
||||
// setup Git
|
||||
if (this._gitOriginRepo) {
|
||||
plugins.smartgit.init(this._sslDir);
|
||||
plugins.smartgit.remote.add(this._sslDir, "origin", this._gitOriginRepo);
|
||||
this.sslGitOriginPull();
|
||||
}
|
||||
// setup leSh config;
|
||||
let leShConfigString;
|
||||
if (this._testMode) {
|
||||
leShConfigString = `CA="https://acme-staging.api.letsencrypt.org/directory"\n`;
|
||||
} else {
|
||||
leShConfigString = " ";
|
||||
};
|
||||
plugins.smartfile.memory.toFsSync(
|
||||
leShConfigString,
|
||||
paths.leShConfig
|
||||
);
|
||||
plugins.shelljs.exec("chmod 700 " + paths.letsencryptSh);
|
||||
plugins.shelljs.exec("chmod 700 " + paths.certHook);
|
||||
plugins.shelljs.exec(
|
||||
`bash -c "${paths.letsencryptSh} -c --no-lock -f ${paths.leShConfig} -d notthere.notthere -t dns-01 -k ${paths.certHook} -o ${paths.certDir}"`,
|
||||
{
|
||||
silent: true
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Pulls already requested certificates from git origin
|
||||
*/
|
||||
sslGitOriginPull = () => {
|
||||
if (this._gitOriginRepo) {
|
||||
plugins.smartgit.pull(this._sslDir, "origin", "master");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Pushes all new requested certificates to git origin
|
||||
*/
|
||||
sslGitOriginAddCommitPush = () => {
|
||||
if (this._gitOriginRepo) {
|
||||
plugins.smartgit.add.addAll(this._sslDir);
|
||||
plugins.smartgit.commit(this._sslDir, "added new SSL certificates and deleted obsolete ones.");
|
||||
plugins.smartgit.push(this._sslDir, "origin", "master");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* gets a ssl cert for a given domain
|
||||
*/
|
||||
getDomainCert(domainNameArg: string, optionsArg: { force: boolean } = { force: false }) {
|
||||
let done = plugins.q.defer();
|
||||
let domainStringData = new plugins.smartstring.Domain(domainNameArg);
|
||||
let sameZoneRequesting: boolean = this.domainCertRequestMap.checkMinimatch("*" + domainStringData.zoneName)
|
||||
// make sure no one else requires the same domain at the same time
|
||||
if (!this.domainCertRequestMap.checkString(domainNameArg)) {
|
||||
this.domainCertRequestMap.addString(domainNameArg);
|
||||
if (!helpers.checkDomainsStillValid(domainNameArg, this._sslDir) || optionsArg.force) {
|
||||
if (!sameZoneRequesting) {
|
||||
plugins.smartfile.fs.ensureDir(paths.certDir);
|
||||
plugins.beautylog.info(`getting cert for ${domainNameArg}`);
|
||||
plugins.shelljs.exec(
|
||||
`bash -c "${paths.letsencryptSh} -c --no-lock -f ${paths.leShConfig} -d ${domainNameArg} -t dns-01 -k ${paths.certHook} -o ${paths.certDir}"`,
|
||||
{
|
||||
silent: true
|
||||
},
|
||||
(codeArg, stdoutArg) => {
|
||||
if (codeArg == 0) {
|
||||
console.log(stdoutArg);
|
||||
let fetchedCertsArray: string[] = plugins.smartfile.fs.listFoldersSync(paths.certDir);
|
||||
if (fetchedCertsArray.indexOf(domainNameArg) != -1) {
|
||||
helpers.updateSslDirSync(this._sslDir, domainNameArg);
|
||||
plugins.smartfile.fs.removeSync(plugins.path.join(paths.certDir, domainNameArg));
|
||||
};
|
||||
done.resolve();
|
||||
} else {
|
||||
plugins.beautylog.warn(`${domainNameArg} scheduled for retry`);
|
||||
helpers.scheduleRetry(domainNameArg, this).then(done.resolve);
|
||||
}
|
||||
this.domainCertRequestMap.removeString(domainNameArg);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
plugins.beautylog.info(`${domainNameArg} is waiting for domains names of same zone to finish`);
|
||||
this.domainCertRequestMap.removeString(domainNameArg);
|
||||
this.domainCertRequestMap.registerUntilTrue(
|
||||
() => {
|
||||
return !this.domainCertRequestMap.checkMinimatch("*" + domainStringData.zoneName);
|
||||
},
|
||||
() => {
|
||||
this.getDomainCert(domainNameArg).then(done.resolve);
|
||||
}
|
||||
);
|
||||
}
|
||||
} else {
|
||||
plugins.beautylog.info("certificate for " + domainNameArg + " is still valid! Not fetching new one!");
|
||||
this.domainCertRequestMap.removeString(domainNameArg);
|
||||
done.resolve();
|
||||
};
|
||||
} else {
|
||||
plugins.beautylog.warn(`${domainNameArg} is already requesting`);
|
||||
};
|
||||
|
||||
return done.promise;
|
||||
};
|
||||
cleanOldCertificates() {
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
export class Certificate {
|
||||
domainName: string;
|
||||
creationDate: Date;
|
||||
expiryDate: Date;
|
||||
constructor() {
|
||||
|
||||
};
|
||||
};
|
||||
|
@ -1,25 +0,0 @@
|
||||
import {Cert} from "./index.ts";
|
||||
import * as plugins from "./cert.plugins";
|
||||
import * as paths from "./cert.paths";
|
||||
|
||||
let firstCall = true;
|
||||
let enoughTime = false;
|
||||
export let accountsKeyPresent = () => {
|
||||
let done = plugins.q.defer();
|
||||
if (firstCall) {
|
||||
done.resolve();
|
||||
firstCall = false;
|
||||
} else {
|
||||
setTimeout(done.resolve,5000);
|
||||
};
|
||||
return done.promise;
|
||||
};
|
||||
|
||||
export let scheduleRetry = (domainArg:string,certClassArg:Cert) => {
|
||||
let done = plugins.q.defer();
|
||||
setTimeout(() => {
|
||||
certClassArg.getDomainCert(domainArg)
|
||||
.then(done.resolve);
|
||||
},20000);
|
||||
return done.promise;
|
||||
};
|
203
ts/index.ts
203
ts/index.ts
@ -1,205 +1,4 @@
|
||||
import * as plugins from "./cert.plugins";
|
||||
import * as paths from "./cert.paths";
|
||||
import * as helpers from "./cert.helpers"
|
||||
|
||||
export interface ICertConstructorOptions {
|
||||
cfEmail: string,
|
||||
cfKey: string,
|
||||
sslDir?: string,
|
||||
gitOriginRepo?: string,
|
||||
testMode?: boolean
|
||||
};
|
||||
|
||||
export class Cert {
|
||||
private _cfEmail: string;
|
||||
private _cfKey: string;
|
||||
private _sslDir: string;
|
||||
private _gitOriginRepo: string;
|
||||
private _testMode: boolean;
|
||||
domainsCurrentlyRequesting: plugins.lik.Stringmap = new plugins.lik.Stringmap();
|
||||
certificatesPresent: Certificate[];
|
||||
certificatesValid: Certificate[];
|
||||
|
||||
/**
|
||||
* Constructor for Cert object
|
||||
*/
|
||||
constructor(optionsArg: ICertConstructorOptions) {
|
||||
this._cfEmail = optionsArg.cfEmail;
|
||||
this._cfKey = optionsArg.cfKey;
|
||||
this._sslDir = optionsArg.sslDir;
|
||||
this._gitOriginRepo = optionsArg.gitOriginRepo;
|
||||
this._testMode = optionsArg.testMode;
|
||||
// write hook config
|
||||
let config = {
|
||||
cfEmail: this._cfEmail,
|
||||
cfKey: this._cfKey
|
||||
}
|
||||
plugins.smartfile.memory.toFsSync(
|
||||
JSON.stringify(config),
|
||||
plugins.path.join(__dirname, "assets/config.json")
|
||||
);
|
||||
// setup sslDir
|
||||
if (!this._sslDir) this._sslDir = paths.defaultSslDir;
|
||||
// setup Git
|
||||
if (this._gitOriginRepo) {
|
||||
plugins.smartgit.init(this._sslDir);
|
||||
plugins.smartgit.remote.add(this._sslDir, "origin", this._gitOriginRepo);
|
||||
this.sslGitOriginPull();
|
||||
}
|
||||
// setup leSh config;
|
||||
let leShConfigString;
|
||||
if (this._testMode) {
|
||||
leShConfigString = `CA="https://acme-staging.api.letsencrypt.org/directory"\n`;
|
||||
} else {
|
||||
leShConfigString = " ";
|
||||
};
|
||||
plugins.smartfile.memory.toFsSync(
|
||||
leShConfigString,
|
||||
paths.leShConfig
|
||||
);
|
||||
plugins.shelljs.exec("chmod 700 " + paths.letsencryptSh);
|
||||
plugins.shelljs.exec("chmod 700 " + paths.certHook);
|
||||
};
|
||||
|
||||
/**
|
||||
* Pulls already requested certificates from git origin
|
||||
*/
|
||||
sslGitOriginPull = () => {
|
||||
if (this._gitOriginRepo) {
|
||||
plugins.smartgit.pull(this._sslDir, "origin", "master");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Pushes all new requested certificates to git origin
|
||||
*/
|
||||
sslGitOriginAddCommitPush = () => {
|
||||
if (this._gitOriginRepo) {
|
||||
plugins.smartgit.add.addAll(this._sslDir);
|
||||
plugins.smartgit.commit(this._sslDir, "added new SSL certificates and deleted obsolete ones.");
|
||||
plugins.smartgit.push(this._sslDir, "origin", "master");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* gets a ssl cert for a given domain
|
||||
*/
|
||||
getDomainCert(domainNameArg: string, optionsArg: { force: boolean } = { force: false }) {
|
||||
let done = plugins.q.defer();
|
||||
// make sure no one else requires the same domain at the same time
|
||||
helpers.accountsKeyPresent().then(() => {
|
||||
if (!this.domainsCurrentlyRequesting.checkString(domainNameArg)) {
|
||||
this.domainsCurrentlyRequesting.addString(domainNameArg);
|
||||
if (!checkDomainsStillValid(domainNameArg, this._sslDir) || optionsArg.force) {
|
||||
plugins.smartfile.fs.ensureDir(paths.certDir);
|
||||
plugins.beautylog.info(`getting cert for ${domainNameArg}`);
|
||||
plugins.shelljs.exec(
|
||||
`bash -c "${paths.letsencryptSh} -c --no-lock -f ${paths.leShConfig} -d ${domainNameArg} -t dns-01 -k ${paths.certHook} -o ${paths.certDir}"`,
|
||||
{
|
||||
silent: true
|
||||
},
|
||||
(codeArg, stdoutArg) => {
|
||||
if (codeArg == 0) {
|
||||
console.log(stdoutArg);
|
||||
let fetchedCertsArray: string[] = plugins.smartfile.fs.listFoldersSync(paths.certDir);
|
||||
if (fetchedCertsArray.indexOf(domainNameArg) != -1) {
|
||||
updateSslDirSync(this._sslDir, domainNameArg);
|
||||
plugins.smartfile.fs.removeSync(plugins.path.join(paths.certDir, domainNameArg));
|
||||
}
|
||||
this.domainsCurrentlyRequesting.removeString(domainNameArg);
|
||||
done.resolve();
|
||||
} else {
|
||||
this.domainsCurrentlyRequesting.removeString(domainNameArg);
|
||||
plugins.beautylog.warn(`${domainNameArg} scheduled for retry`);
|
||||
helpers.scheduleRetry(domainNameArg,this).then(done.resolve);
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
plugins.beautylog.info("certificate for " + domainNameArg + " is still valid! Not fetching new one!");
|
||||
this.domainsCurrentlyRequesting.removeString(domainNameArg);
|
||||
done.resolve();
|
||||
};
|
||||
} else {
|
||||
plugins.beautylog.warn(`${domainNameArg} is already requesting`);
|
||||
};
|
||||
});
|
||||
|
||||
return done.promise;
|
||||
};
|
||||
cleanOldCertificates() {
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
export class Certificate {
|
||||
domainName: string;
|
||||
creationDate: Date;
|
||||
expiryDate: Date;
|
||||
constructor() {
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
interface certConfig {
|
||||
domainName: string;
|
||||
created: number;
|
||||
expires: number;
|
||||
}
|
||||
|
||||
let checkDomainsStillValid = (domainNameArg: string, sslDirArg: string): boolean => {
|
||||
let domainConfigPath = plugins.path.join(sslDirArg, domainNameArg, "config.json");
|
||||
if (plugins.smartfile.fs.fileExistsSync(domainConfigPath)) {
|
||||
let domainConfig = plugins.smartfile.fs.toObjectSync(
|
||||
domainConfigPath,
|
||||
"json"
|
||||
);
|
||||
if (Date.now() >= ((domainConfig.expires - 604800) * 1000)) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let updateSslDirSync = (sslDirArg: string, domainNameArg: string) => {
|
||||
plugins.smartfile.fs.ensureDirSync(sslDirArg);
|
||||
let domainCertFolder = plugins.path.join(paths.certDir, domainNameArg)
|
||||
if (plugins.smartfile.fs.listFoldersSync(paths.certDir).indexOf(domainNameArg) != -1) {
|
||||
plugins.smartfile.fs.copySync(
|
||||
plugins.path.join(domainCertFolder, "fullchain.pem"),
|
||||
plugins.path.join(sslDirArg, domainNameArg, "fullchain.pem")
|
||||
);
|
||||
plugins.smartfile.fs.copySync(
|
||||
plugins.path.join(domainCertFolder, "privkey.pem"),
|
||||
plugins.path.join(sslDirArg, domainNameArg, "privkey.pem")
|
||||
);
|
||||
// create cert config
|
||||
let certRegex = /.*\-([0-9]*)\.pem/;
|
||||
let certFileNameWithTime: string = plugins.smartfile.fs.listFilesSync(domainCertFolder, certRegex)[0];
|
||||
let certTime = parseInt(certRegex.exec(certFileNameWithTime)[1]);
|
||||
let certConfig: certConfig = {
|
||||
domainName: domainNameArg,
|
||||
created: certTime,
|
||||
expires: certTime + 7776000
|
||||
};
|
||||
plugins.smartfile.memory.toFsSync(
|
||||
JSON.stringify(certConfig),
|
||||
plugins.path.join(sslDirArg, domainNameArg, "config.json")
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
const enum gitSyncDirection {
|
||||
toOrigin,
|
||||
fromOrigin
|
||||
}
|
||||
|
||||
let updateGitOrigin = (syncDirectionArg: gitSyncDirection) => {
|
||||
|
||||
};
|
||||
|
||||
updateGitOrigin(gitSyncDirection.toOrigin);
|
||||
export * from "./cert.classes.cert";
|
Reference in New Issue
Block a user