wild card UI done

This commit is contained in:
2024-08-06 17:52:00 -04:00
parent 84db245f4a
commit 41919db8f8
6 changed files with 149 additions and 69 deletions
+38 -35
View File
@@ -30,6 +30,7 @@ class Host extends Table{
'is_wildcard': {default: false, isRequired: false, type: 'boolean',}, 'is_wildcard': {default: false, isRequired: false, type: 'boolean',},
'wildcard_status': {isRequired: false, type: 'string', min: 3, max: 500, default: 'Requesting'}, 'wildcard_status': {isRequired: false, type: 'string', min: 3, max: 500, default: 'Requesting'},
'wildcard_parent': {isRequired: false, type: 'string', min: 3, max: 500}, 'wildcard_parent': {isRequired: false, type: 'string', min: 3, max: 500},
'wildcard_expires': {isRequired: false, type: 'number'},
} }
static lookUpObj = {}; static lookUpObj = {};
@@ -62,7 +63,7 @@ class Host extends Table{
parent: parentOBJ.host parent: parentOBJ.host
}); });
}catch(error){ }catch(error){
console.error('add cahce error', {...parentOBJ, host, is_cache: true}, error) console.error('add cache error', {...parentOBJ, host, is_cache: true}, error)
throw error; throw error;
} }
} }
@@ -103,16 +104,18 @@ class Host extends Table{
try{ try{
let host = this; let host = this;
await host.update({
wildcard_status: 'Requesting',
});
let cert = await letsEncrypt.dnsWildcard(this.host, { let cert = await letsEncrypt.dnsWildcard(this.host, {
challengeCreateFn: async (authz, challenge, keyAuthorization) => { challengeCreateFn: async (authz, challenge, keyAuthorization) => {
host.update({ await host.update({
wildcard_status: `Adding record for ${authz.identifier.value}` wildcard_status: `Adding record`
}); });
try{ try{
let parts = tldExtract(authz.identifier.value); let parts = tldExtract(authz.identifier.value);
console.log('adding DNS record for', `_acme-challenge${parts.sub ? `.${parts.sub}` : ''}`)
let res = await porkBun.createRecordForce( let res = await porkBun.createRecordForce(
parts.domain, parts.domain,
{ {
@@ -121,56 +124,55 @@ class Host extends Table{
content: `${keyAuthorization}` content: `${keyAuthorization}`
} }
); );
console.log('porkbun res', res)
}catch(error){ }catch(error){
console.log('model Host challengeCreateFn error:', error) console.log('model Host challengeCreateFn error:', error)
host.update({ await host.update({
wildcard_status: `Add DNS record failed` wildcard_status: `Add DNS record failed`
}); });
} }
}, },
onDnsCheck: async(authz, checkCount)=>{ onDnsCheck: async(authz, checkCount)=>{
host.update({ await host.update({
wildcard_status: `${checkCount} Checking DNS for ${authz.identifier.value}` wildcard_status: `${checkCount} Checking DNS`
}); });
}, },
onDnsCheckFail: async(authz, error)=>{ onDnsCheckFail: async(authz, error)=>{
host.update({ await host.update({
wildcard_status: `DNS check failed for ${authz.identifier.value}` wildcard_status: `DNS check failed for ${authz.identifier.value}`
}); });
}, },
onDnsCheckFound: async(authz)=>{ onDnsCheckFound: async(authz)=>{
host.update({ await host.update({
wildcard_status: `DNS check found for ${authz.identifier.value}` wildcard_status: `DNS check found`
}); });
}, },
onDnsCheckSuccess: async(authz)=>{ onDnsCheckSuccess: async(authz)=>{
host.update({ await host.update({
wildcard_status: `DNS check success for ${authz.identifier.value}` wildcard_status: `DNS check success`
}) })
}, },
onDnsCheckRemove: async(authz)=>{ onDnsCheckRemove: async(authz)=>{
host.update({ await host.update({
wildcard_status: `DNS remove record for ${authz.identifier.value}` wildcard_status: `DNS remove record`
}) })
}, },
challengeRemoveFn: async (authz, challenge, keyAuthorization)=>{ challengeRemoveFn: async (authz, challenge, keyAuthorization)=>{
host.update({ await host.update({
wildcard_status: `DNS remove record for ${authz.identifier.value}` wildcard_status: `DNS remove record`
}) })
try{ try{
// let parts = tldExtract(authz.identifier.value); let parts = tldExtract(authz.identifier.value);
// await porkBun.deleteRecords( await porkBun.deleteRecords(
// parts.domain, parts.domain,
// { {
// type:'TXT', type:'TXT',
// name: `_acme-challenge${parts.sub ? `.${parts.sub}` : '' name: `_acme-challenge${parts.sub ? `.${parts.sub}` : ''
// }`, }`,
// content: `${keyAuthorization}`} content: `${keyAuthorization}`}
// ); );
}catch(error){ }catch(error){
host.update({ await host.update({
wildcard_status: `DNS remove record failed for ${authz.identifier.value}` wildcard_status: `DNS remove record failed for ${authz.identifier.value}`
}) })
} }
@@ -188,8 +190,9 @@ class Host extends Table{
await this.constructor.redisClient.SET(`${this.host}:latest`, JSON.stringify(toAdd)); await this.constructor.redisClient.SET(`${this.host}:latest`, JSON.stringify(toAdd));
this.update({ await this.update({
wildcard_status: `Done` wildcard_status: `Done`,
wildcard_expires: toAdd.real_expiry*1000,
}); });
return this; return this;
@@ -204,8 +207,8 @@ class Host extends Table{
async update(...args){ async update(...args){
try{ try{
let out = await super.update(...args) let out = await super.update(...args)
await this.bustCache(this.host) await this.bustCache(this.host);
await Host.buildLookUpObj() await Host.buildLookUpObj();
return out; return out;
} catch(error){ } catch(error){
@@ -215,9 +218,9 @@ class Host extends Table{
async remove(...args){ async remove(...args){
try{ try{
let out = await super.remove(...args) let out = await super.remove(...args);
await Host.buildLookUpObj() await Host.buildLookUpObj();
await this.bustCache(this.host) await this.bustCache(this.host);
return out; return out;
} catch(error){ } catch(error){
+8 -1
View File
@@ -26,10 +26,17 @@ app.host = (function(app){
function remove(args, callack){ function remove(args, callack){
app.api.delete('host/'+ args.host, function(error, data){ app.api.delete('host/'+ args.host, function(error, data){
callack(error, data); callack(error, data);
}) });
}
function getCert(args, callack){
app.api.get('cert/'+args.host, function(error, data){
callack(error, data);
});
} }
return { return {
getCert,
list: list, list: list,
get: get, get: get,
add: add, add: add,
+16
View File
@@ -311,7 +311,23 @@ app.util = (function(app){
return obj; return obj;
}; };
function downloadFile(filename, text){
// https://stackoverflow.com/a/18197341
var element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
element.setAttribute('download', filename);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
return { return {
downloadFile: downloadFile,
getUrlParameter: getUrlParameter, getUrlParameter: getUrlParameter,
actionMessage: actionMessage actionMessage: actionMessage
} }
+22 -9
View File
@@ -29,7 +29,19 @@ router.post('/', async function(req, res, next){
} }
}); });
router.get('/:item(*)', async function(req, res, next){ router.get('/lookup/:item', async function(req, res, next){
try{
return res.json({
string: req.params.item,
results: await Model.lookUp(req.params.item),
});
}catch(error){
return next(error);
}
});
router.get('/:item', async function(req, res, next){
try{ try{
return res.json({ return res.json({
@@ -41,7 +53,7 @@ router.get('/:item(*)', async function(req, res, next){
} }
}); });
router.put('/:item(*)', async function(req, res, next){ router.put('/:item', async function(req, res, next){
try{ try{
req.body.updated_by = req.user.username; req.body.updated_by = req.user.username;
let item = await Model.get(req.params.item); let item = await Model.get(req.params.item);
@@ -59,7 +71,7 @@ router.put('/:item(*)', async function(req, res, next){
} }
}); });
router.delete('/:item(*)', async function(req, res, next){ router.delete('/:item', async function(req, res, next){
try{ try{
let item = await Model.get(req.params.item); let item = await Model.get(req.params.item);
let count = await item.remove(); let count = await item.remove();
@@ -74,15 +86,16 @@ router.delete('/:item(*)', async function(req, res, next){
} }
}); });
router.get('/lookup/:item(*)', async function(req, res, next){ router.put('/:item/renew', async function(req, res, next){
try{ try{
return res.json({ let item = await Model.get(req.params.item);
string: req.params.item, item.createWildcardCert();
results: await Model.lookUp(req.params.item),
});
return res.json({
message: `Requesting wildcard cert for ${req.params.item}`,
})
}catch(error){ }catch(error){
return next(error); next(error);
} }
}); });
+5 -14
View File
@@ -7,10 +7,6 @@ const sleep = require('./sleep');
// https://dns.google/resolve?name=${name}&type=TXT // https://dns.google/resolve?name=${name}&type=TXT
AcmeClient.setLogger((message) => {
console.log('ACME:', message);
});
class LetsEncrypt{ class LetsEncrypt{
static AcmeClient = AcmeClient; static AcmeClient = AcmeClient;
@@ -64,15 +60,12 @@ class LetsEncrypt{
skipChallengeVerification: true, skipChallengeVerification: true,
challengeCreateFn: async (authz, challenge, keyAuthorization) => { challengeCreateFn: async (authz, challenge, keyAuthorization) => {
try{ try{
console.log('challenge', challenge) dnsToAdd++;
console.log(`start TXT record key=_acme-challenge.${authz.identifier.value} value=${keyAuthorization} challenge=${challenge} googleDNS=https://dns.google/resolve?name=_acme-challenge.${authz.identifier.value}&type=TXT`)
dnsToAdd++
let resCheck = await axios.get(`https://dns.google/resolve?name=_acme-challenge.${authz.identifier.value}&type=TXT`); let resCheck = await axios.get(`https://dns.google/resolve?name=_acme-challenge.${authz.identifier.value}&type=TXT`);
if(resCheck.data.Answer && resCheck.data.Answer.some(record => record.data === keyAuthorization)){ if(resCheck.data.Answer && resCheck.data.Answer.some(record => record.data === keyAuthorization)){
await sleep(1000); dnsFound++;
dnsFound++
if(dnsFound === dnsToAdd){ if(dnsFound === dnsToAdd){
options.onDnsCheckFound(authz, dnsFound) options.onDnsCheckFound(authz, dnsFound);
} }
return; return;
} }
@@ -83,13 +76,11 @@ class LetsEncrypt{
while(true){ while(true){
options.onDnsCheck(authz, checkCount); options.onDnsCheck(authz, checkCount);
let res = await axios.get(`https://dns.google/resolve?name=_acme-challenge.${authz.identifier.value}&type=TXT`); let res = await axios.get(`https://dns.google/resolve?name=_acme-challenge.${authz.identifier.value}&type=TXT`);
// console.log(keyAuthorization, res.data);
if(res.data.Answer && res.data.Answer.some(record => record.data === keyAuthorization)){ if(res.data.Answer && res.data.Answer.some(record => record.data === keyAuthorization)){
dnsFound++ dnsFound++;
if(dnsFound === dnsToAdd){ if(dnsFound === dnsToAdd){
options.onDnsCheckFound(authz, dnsFound) options.onDnsCheckFound(authz, dnsFound);
} }
// console.log(`found record for key=_acme-challenge.${authz.identifier.value} value=${keyAuthorization}`)
await sleep(10000); await sleep(10000);
break; break;
} }
+57 -7
View File
@@ -43,9 +43,21 @@
function parseHostRow(host) { function parseHostRow(host) {
host['updated_on_text'] = moment(host['updated_on'], "x").fromNow(); host['updated_on_text'] = moment(host['updated_on'], "x").fromNow();
host['wildcard_expires_text'] = moment(host['wildcard_expires'], "x").fromNow();
host['targetssl_text'] = host['targetssl'] ? 'https://' : 'http://'; host['targetssl_text'] = host['targetssl'] ? 'https://' : 'http://';
host['forcessl_text'] = host['forcessl'] ? 'https://' : 'http://'; host['forcessl_text'] = host['forcessl'] ? 'https://' : 'http://';
host['wildcard_text'] = host['is_wildcard'] ? host['wildcard_status'] : 'Auto'; host['wildcard_text'] = host['is_wildcard'] ? host['wildcard_status'] : 'Auto';
host['wildcard_text_bg'] = 'warning';
if(!host['is_wildcard']){
host['wildcard_text_bg'] = 'success';
}
if(host['wildcard_status'] == 'Done'){
host['wildcard_text_bg'] = 'success';
host['wildcard_text'] = undefined;
}
if(host['wildcard_status'].includes('failed')){
host['wildcard_text_bg'] = 'danger';
}
return host; return host;
} }
@@ -94,6 +106,13 @@
}); });
} }
function downloadCert(host, type){
app.host.getCert({host}, function(error, data){
if(error) app.util.actionMessage(error.message, $.scope.hosts.$this, 'danger');
app.util.downloadFile(`${host}-${type}.crt`, data[type])
});
}
$(document).ready(function(){ $(document).ready(function(){
$editHostForm = $('#addHost').clone(); $editHostForm = $('#addHost').clone();
$editHostForm.find('hr.buttonBreak').nextAll().remove(); $editHostForm.find('hr.buttonBreak').nextAll().remove();
@@ -121,9 +140,9 @@
$el.slideUp(); $el.slideUp();
}); });
app.subscribe(/^model:Host/, function(data, topic){ // app.subscribe(/^model:Host/, function(data, topic){
console.log(topic, data); // console.log(topic, data);
}); // });
app.subscribe(/^model:Host:create/, function(data, topic){ app.subscribe(/^model:Host:create/, function(data, topic){
let [a,b, action, host] = topic.split(':'); let [a,b, action, host] = topic.split(':');
@@ -350,7 +369,7 @@
<thead> <thead>
<th> <th>
SSL SSL Expire
</th> </th>
<th> <th>
Host Name Host Name
@@ -365,10 +384,14 @@
Actions Actions
</th> </th>
</thead> </thead>
<tbody id="hostsTable"> <tbody id="hostsTable">
<tr action="api" jq-repeat="hosts" jq-repeat-index='host' style="display:none"> <tr action="api" jq-repeat="hosts" jq-repeat-index='host' style="display:none">
<td> <td class="table-{{wildcard_text_bg}}">
{{{ wildcard_text }}} {{{ wildcard_text }}}
{{#wildcard_expires}}
<span class="momentFromNow" data-date="{{ wildcard_expires }}" >{{wildcard_expires_text}}</span>
{{/wildcard_expires}}
</td> </td>
<td> <td>
<a target="_blank" href="{{ forcessl_text }}{{ host }}"> <a target="_blank" href="{{ forcessl_text }}{{ host }}">
@@ -383,10 +406,37 @@
</td> </td>
<td> <td>
<div class="btn-group"> <div class="btn-group">
<!-- <button type="button" class="btn btn-info">
<div class="btn-group" role="group">
<button type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fa-brands fa-expeditedssl"></i> <i class="fa-brands fa-expeditedssl"></i>
Certs
</button>
<ul class="dropdown-menu">
<li>
<button type="button" class="dropdown-item" onclick="downloadCert('{{host}}', 'cert_pem')">
<i class="fa-solid fa-certificate"></i>
Cert Cert
</button> --> <i class="fa-solid fa-file-arrow-down float-end"></i>
</button>
</li>
<li>
<button type="button" class="dropdown-item" onclick="downloadCert('{{host}}', 'fullchain_pem')">
<i class="fa-solid fa-link"></i>
Full Chain
<i class="fa-solid fa-file-arrow-down float-end"></i>
</button>
</li>
<li>
<button type="button" class="dropdown-item" onclick="downloadCert('{{host}}', 'privkey_pem')">
<i class="fa-solid fa-key"></i>
Private key
<i class="fa-solid fa-file-arrow-down float-end"></i>
</button>
</li>
</ul>
</div>
<button type="button" onclick="editHost(this, '{{ host }}');" class="btn btn-sm btn-warning"> <button type="button" onclick="editHost(this, '{{ host }}');" class="btn btn-sm btn-warning">
<i class="fa-solid fa-pencil"></i> <i class="fa-solid fa-pencil"></i>
Edit Edit