Feature: Allow multiple Subject Alternative Name (SAN) extensions#52
Feature: Allow multiple Subject Alternative Name (SAN) extensions#52zetlen merged 11 commits intodavewasmer:masterfrom
Conversation
src/constants.ts
Outdated
|
|
||
| export const domainsDir = configPath('domains'); | ||
| export const pathForDomain: (domain: string, ...pathSegments: string[]) => string = path.join.bind(path, domainsDir) | ||
| export const pathForDomain: (domain: string | string[], ...pathSegments: string[]) => string = path.join.bind(path, domainsDir) |
There was a problem hiding this comment.
I don't think this can be string[] here, since path.join() itself only accepts an arg array of string types.
In this context ,path.join.bind(path, domainsDir) basically becomes
(domain, ...pathSegments) => path.join(domainsDir, domain, ...pathSegments)This is what I get in a node shell:
> configDir = '/test'
'/test'
> configPath = path.join.bind(path, configDir)
[Function: bound join]
> domainDir = configPath('domains')
'\\test\\domains'
> pathForDomain = path.join.bind(path, domainDir)
[Function: bound join]
> pathForDomain(['localhost', 'test.localhost'], 'private-key.key')
Thrown:
TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received type object
at validateString (internal/validators.js:125:11)
at Object.join (path.js:427:7)There was a problem hiding this comment.
Personally, I think that using path.join.bind() like this is kind of an obscure usage, and can lead to confusion. I know it took me a moment when I first looked at it, and this is the only place I've ever seen it used.
@zetlen how would you feel about these changing to simple wrapper functions, instead of bound path.join functions?
There was a problem hiding this comment.
Oops - I had figured that out while testing, forgot to push - the fixed version is now here.
zetlen
left a comment
There was a problem hiding this comment.
There's a subtle issue with this. It treats the first domain in the list as the unique key for the whole list of domains. This means that if I run:
devcert.certificateFor(['friends.local'])And then, later, I run this:
devcert.certificateFor(['friends.local', 'rachel.friends.local', 'joey.friends.local'])This code will return the original friends.local certificate it generated first, which won't include any of those alternate names.
There's a workaround for this, which is to reorder the domain names, but that's confusing and non-obvious behavior.
I'd rather that we always sort the domain list by level (number of dots) and then lexically. THEN we use domains[0] as the unique key / filename. Then lastly, if a cert already exists, make sure that it contains all requested SANs--and if not, regenerate the cert.
This is a little more work, but the alternative is pretty surprising behavior.
Thanks again!
|
HI @zetlen that sounds fine - I was aware of this behavior and it can lead to confusion. One alternative and simple solution I had thought of was to instead create a numeric hash for the domain using a simple hash algorithm like: const numericHash = (str) => {
let hash = 5381;
let i = str.length;
while (i) {
hash = hash * 33 ^ str.charCodeAt(--i);
}
return hash >>> 0;
};And then passing the array into there like: // user passes in this list
const domains = [
'localhost',
'local.api.example.com',
'local.example.com',
'local.auth.example.com'
].sort().join('');
// if domains.length = 1, we can just use first domain
const stableDomain = domains.length === 1 ? domains[0] : 'san-'+ numericHash(domains);
// later on
pathForDomain(stableDomain);So the arrays of domains > 1 would beget a name like
|
|
@camsjams That would work too; the worst part about it would be that we would never detect when the requested domains are a strict subset. If I went: devcert.certificateFor(['addams.family', 'pugsley.addams.family'])And then later: devcert.certificateFor(['pugsley.addams.family'])It would generate two certificates, when it could easily return the first cert. Fewer certificates is better for a number of reasons, but given the likely patterns of use, this might not be a big deal. |
|
I should clarify, @camsjams: can you think of a simple way to avoid the issue I mentioned above? If not, then you can make the changes you suggested and I'll merge them, then open the redundancy issue as another ticket. |
|
As part of the process of converting to a stable hash, it could filter out subset domains. So a list like: const domains = [
'eddie.munster',
'addams.family',
'pugsley.addams.family',
'gomez.addams.family'
];could be filtered down to : const domains = [
'eddie.munster',
'addams.family'
];Just for the sake of the hashing of names for folder use. |
|
That's a great plan. Let's do that. |
|
Sounds great - working on it and should have by end of week as part of my work stream. Thanks! |
|
OK @zetlen I've updated to add the features we've discussed. Locally I wrote some unit tests but this repo doesn't have any place for it, so I didn't commit them. |
|
@zetlen Is this still a feature that is desired? The code is complete and ready for review. I do see that a recent update has created a merge conflict - I'd be happy to merge master into my code and fix conflict if this feature branch is something that will get used. |
|
Hi! I was recently looking at using devcert but unfortunately couldn't because I needed multiple SAN. If this is included I'd definitely use it |
|
I had updated the PR per the requested changes, and then master had some updates that created the merge conflict. We've just been using my fork of this for our team: I am able to remerge this back in if this is still a desired feature. |
|
@camsjams we would appreciate it greatly if you could find the time to remerge it back. |
|
@amiryonatan Yes I can merge, test, and update this week |
|
@amiryonatan @zetlen @ranyitz This is remerged - sorry had some personal life things come up, so had to put aside for a bit. I've remerged and tested, and adjusted my code to work with the updated validation that caused the original merge conflicts. |
|
If at all useful BTW, you can temporarily clone my repo and
const fs = require('fs');
const devcert = require('./dist');
function main(mode = 0) {
console.log('mode:', mode);
switch (mode) {
case 0:
console.log('generating');
return devcert.certificateFor([
'localhost',
'local.api.example.com',
'local.cms.example.com',
'local.example.com'
])
.then(({key, cert}) => {
fs.writeFileSync('./tls.key', key);
fs.writeFileSync('./tls.cert', cert);
return;
})
.catch((error) => {
console.log('error', error);
});
case 1:
console.log('listing');
console.log(devcert.configuredDomains());
break;
case 2:
console.log('removing');
return devcert.removeDomain([
'localhost',
'local.api.example.com',
'local.cms.example.com',
'local.example.com'
]);
}
}
// update the argument to run each command to confirm that this works
main(0); |
|
@camsjams looks like it works as expected, thank you very much! |
|
@Js-Brecht are you able to assist here plz? |
|
I would love to; this PR looks good. Unfortunately, I don’t have write access. @zetlen is the one to ask. I guess he must be pretty busy if he hasn’t noticed the recent pings yet. |
|
+1. I'd really love to see this ship! |
|
@Js-Brecht are you able to assist please? |
|
@amiryonatan look up a couple comments ☝️ |
Revision of #34 and fixes #33
Changes: