caddyserver / certmagic

Automatic HTTPS for any Go program: fully-managed TLS certificate issuance and renewal
https://pkg.go.dev/github.com/caddyserver/certmagic?tab=doc
Apache License 2.0
4.89k stars 278 forks source link

Question: How to issue wildcard certificates rather than exact subject name in OnDemand? #280

Closed hazycora closed 3 weeks ago

hazycora commented 3 months ago

What is your question?

I'm wanting to use OnDemand, and be able to handle unknown domains for which I may not have DNS access, but also manage subdomains multiple levels deep on another domain I do have access to. On the domain I do have access to, I'd like to use wildcard subdomains any chance I can, so that I get certificates which can be used elsewhere while I'm at it. This is particularly useful in environments where domains might be of the format [commit/branch].[repo].[username].example.com - issuing more certificates constantly would hit ratelimits often if used by many users, if this can at all be optimised with wildcards that would be preferable. Is this possible currently?

Example

...etc.

What have you already tried?

Looking at the code for certmagic, it seems like I could do something like this if I could just edit getNameFromClientHello, but if there's a better way to do this I'd rather do that than having to edit this codebase. Alternatively I could use my own OnDemand implementation but I'd rather have the stability of using what's been made here than needing to maintain my own implementation.

I've been looking around for a while to find a way to do this at all without a Ton of extra work, and haven't found much yet, so currently I've yet to actually try anything.

mholt commented 3 months ago

I think I had this on a TODO list a while ago. I'll try to revisit it soon.

hazycora commented 3 months ago

I'm using a bodge, adding a ManipulateClientHelloName function for this. here's an example of how it could be used:

magic.ManipulateClientHelloName = func(name string) string {
    if canUseWildcard(name) {
        name = strings.TrimSuffix(name, "." + PAGES_DOMAIN)
        parts := strings.Split(name, ".")
        parts[0] = "*"
        name = strings.Join(parts, ".") + "." + PAGES_DOMAIN
    }
    return name
}

I'm not recommending this be used here by any means but as a stopgap it's working for me for now, though longterm I'd prefer this project be able to accommodate this situation

mholt commented 2 months ago

@hazycora I've just committed b29d2a0 which adds a SubjectTransformer field to the certmagic.Config struct. In my tests I used it similarly to you:

magic.SubjectTransformer = func(ctx context.Context, domain string) string {
    if !strings.HasPrefix(domain, "*.") {
        parts := strings.Split(domain, ".")
        parts[0] = "*"
        return strings.Join(parts, ".")
    }
    return domain
}

Want to give that a shot and see if that does what you need?

Note that my implementation affects all (I think all?) code paths that manage certificates, including NON-on-demand configs, but it can, of course, be used with on-demand configs as well. Let me know what you think!

hazycora commented 3 weeks ago

Sorry for the wait- This works, been using it for the past two months without a problem. I think this serves this use-case just fine so I'll close. Thanks!

mholt commented 3 weeks ago

Excellent! Thanks for the update.