Initial IP address support: use san.SAN types internally#10468
Initial IP address support: use san.SAN types internally#10468ohemorange merged 29 commits intocertbot:mainfrom
Conversation
|
Took a look. What makes you say that combining and re-separating them will make the change more contained? It looks to me like this PR modifies each spot where domains might be in the first place, if only in the comments. From what I'm seeing, maybe just in Actually, I'm tempted to suggest spinning up a few new types -- |
On first pass, it seemed daunting. In particular the public type signature of And I didn't relish propagating that last big type everywhere. However, as you point out, using some custom types and adding/modifying/deprecating public functions can make this easier. I can take another pass and see how this looks. I'm thinking something like |
|
As I start this, I'm remembering the other thing that seemed daunting: updating all the mock setup in all the tests seems likely to be a pretty big change. I'll see how it goes. |
|
Alright, I added new types in I wound up drawing a boundary at the interface with installer plugins: they still provide / receive strings, for now. We'll likely want to change that, but that seemed like a good place to stop. I like the idea of making I'm marking this "ready for review" even though I still only have a single integration test for IP address certs. I'd like to get feedback on all the |
|
ohemorange asked me to take a high level look at this so we could all get on the same page before we got deep into the weeds of review here my biggest concerns are around API changes and trying to ensure that all references to things like "domains" are properly updated if the value can now be an IP address API changesfor API changes, everything in for this PR as it sits right now, all changes to files like crypto_util.py, display/ops.py, interfaces.py, and util.py should ideally be made in a backwards compatible way or with warnings about upcoming changes in behavior that we then implement in a new major version of certbot. i'm not sure the best way to handle this and many of the decisions will probably have to be made on a case by case basis one idea that unfortunately may be kind of frustrating to consider now is continuing to keep SANs as a list of strings. right now, many of the changes to type signature of public APIs just change the return value of a function from a) continue to use a list of strings which may (greatly?) simplify the transition, allow any code that can continue to work without changes, and let users who try to use IP addresses report bugs to us or 3rd parties as they come up i personally lean towards (a). i think the end result of (b) is cleaner, but it feels like significantly more work to me and i'm not sure it's worth it i think similar things could be done by not renaming i'm very curious what the two of you think though. if y'all both prefer (b), i'm not against it, i'm just not looking forward to the migration involved here updating variable nameswith all that said about maybe trying to avoid unnecessary changes, i do basically want us to never have a variable named something like domain or domains that actually now refers to an IP address in our own code. i think that's just a bug waiting to happen i think that currently happens in code like this where i think we should fix all these up as part of this IP address work. unfortunately the way i'd probably do it is looking through all references in our acme and certbot code for the string "domain", but one of y'all may have a cleverer idea @jsha, you probably already know this but just to make sure, i'd recommend waiting until the 3 of us are on the same page here before you do much more work implementing this |
Oop, yes, I got carried away and made incompatible changes to some functions in these files. Still, that was useful in terms of forcing errors when things called the old names / passed the old types. If we go forward with the re-names / applying types, my proposal for these "externally facing" items would be to keep the new item with the new name (since most have been renamed), and restore the old one with the old name, marking it as deprecated. I think that'll be better than trying to keep old function names and adding optional / defaulted parameters. Defaulted parameters can wind up creating a lot of complexity that's hard to reason about, and then more difficult cleanup after a deprecation period (if we do ever clean up).
No worries about being frustrating. That's basically the first version of this PR, and we can revert to that commit easily. I knew when I set out on this type journey that the work was speculative.
Yes, this is true. Although I was surprised about some of the places I stumbled on, like this sort function that sorts by second-level domain: certbot/certbot/src/certbot/display/ops.py Lines 129 to 137 in 4d5d5f7
I think we're in agreement that we should not break the public API without a deprecation period. That requires some further diffs to this PR but nothing complicated. For plugins and hooks, I've tried to keep the interfaces be strings containing domain names. The new
Based on my proposal above I'd re-add
The changes here to have a specific named type for "could be a domain name or an IP address" really helped a lot with this. I won't say I caught every instance (e.g. I missed that achall thing you pointed out), but I caught a lot more than I did with the first pass of this PR. IMO, this is an argument in favor of using the new |
i agree. this works for me!
that's helpful to know. after i found domain variables that now are now sometimes IPs that slipped thru, i just assumed it wasn't helping that much. i'm glad to hear that's wrong in that case, if you and/or ohemorange are still up for going the route of the bigger cleanup which will almost certainly be nicer for us in the end, this high level approach sounds good to me |
|
By the way, looking again at the example you pointed out, of |
|
i personally think it should be a separate PR if splitting it up isn't too difficult. i don't expect splitting it up to be too bad, but i could certainly be wrong i don't have a strong preference on the ordering though |
bmw
left a comment
There was a problem hiding this comment.
lgtm! let's get a 2nd reviewer in here
|
ohemorange, just in case it helps as it initially tripped me up a bit for whatever reason, i think our change to certbot/src/certbot/configuration.py is ok here because |
ohemorange
left a comment
There was a problem hiding this comment.
wow, appreciate you taking the time to do such thorough changes!
| _handle_unexpected_key_type_migration(config, lineage) | ||
| _ask_user_to_confirm_new_names(config, domains, certname, | ||
| lineage.names()) # raises if no | ||
| _ask_user_to_confirm_new_sans(config, sans, certname, |
There was a problem hiding this comment.
just noting that something you switch "names" to "sans" and sometimes you leave it as "names." I think it's fine either way, but noting it in case you had an opinion and didn't notice you missed some, or had a preference here. see _find_lineage_for_sans for more names-keeping.
There was a problem hiding this comment.
In _find_lineage_for_sans, you're talking about this line?
ident_names_cert, subset_names_cert = cert_manager.find_duplicative_certs(config, sans)
That makes sense; I mostly was updating function names that contained names to instead contain sans, when I updated those functions to have different parameter types or return values. Since these variables didn't have a type involving san.SAN I would have missed them.
I note that you say "more names-keeping", but I can't find the names-keeping on this line. Is there some?
There was a problem hiding this comment.
Yes, that's the line I meant, there were others as well -- for example find_duplicative_certs keeps variables with names in them. config.allow_subset_of_names is another big one.
No, that's a mistype, this line is just what made me think of it. It should have said "some names-keeping."
Anyway, sounds like you didn't intend to change every instance of names to sans and just missed some accidentally, in which case I'm find with using names and sans largely interchangeably.
There was a problem hiding this comment.
did you want to switch everything over to sans or nah? if not I'll go ahead and merge
There was a problem hiding this comment.
I'm gonna leave remaining places that say "names" as-is, at least for now. Thanks for checking.
if you dislike the general idea of this PR, feel free to just close it, but i'm scheduled to release the next version of certbot a week from today and i personally didn't like how [newsfragments](https://github.com/certbot/certbot/tree/main/newsfragments) is so empty despite us having done a lot of work on certbot lately this PR just adds a simple newsfragment highlighting/teasing the work jsha has been leading on support for IP address certificates which i imagine would be of interest to some people in the community ``` $ towncrier build --draft --version 5.2.0 Loading template... Finding news fragments... Rendering news fragments... Draft only -- nothing has been written. What is seen below is what would be written. ## 5.2.0 - 2025-11-25 ### Added - Support for Python 3.14 was added. ([#10477](#10477)) ### Changed - While nothing significant should have changed from the user's perspective, we've been doing a lot of internal refactoring in preparation for soon adding support for IP address certificates to Certbot. ([#10468](#10468), [#10478](#10478)) ```
see the thread at https://opensource.eff.org/eff-open-source/pl/f5yx4a4q4j8zjyqpmath494jge for details since it's only the `v5.2.0` github tag that's borked, we could in theory try and use like `v5.2.0-2` or something, but there are [places](https://github.com/certbot/certbot/blob/259dfadb43cc52cb06ba57883766497faaf72b59/.azure-pipelines/release.yml#L10) in the release pipeline that use the GH tag as input to the assets they build, so i think just skipping 5.2.0 altogether is simpler, easier, and safer with this change, here's the proposed changelog ``` $ towncrier build --draft --version 5.2.1 Loading template... Finding news fragments... Rendering news fragments... Draft only -- nothing has been written. What is seen below is what would be written. ## 5.2.1 - 2025-12-02 ### Added - Support for Python 3.14 was added. ([#10477](#10477)) ### Changed - While nothing significant should have changed from the user's perspective, we've been doing a lot of internal refactoring in preparation for soon adding support for IP address certificates to Certbot. ([#10468](#10468), [#10478](#10478)) ### Fixed - Removed `vhost_combined` and `vhost_common` log formats from included Apache configuration file. ([#9769](#9769)) - Due to a mistake on our end playing with GitHub's new [immutable releases](https://github.blog/changelog/2025-10-28-immutable-releases-are-now-generally-available/) feature that prevented our CI from uploading additional release assets, Certbot 5.2.0 was not and will not be uploaded to most platforms. Instead, that version number will be skipped and we'll go straight to 5.2.1. ([#10501](#10501))
This field is optional to maintain backwards compatibility. Note that `AnnotatedChallenge` inherits from `jose.ImmutableMap`, which has a [check in __init__](https://github.com/certbot/josepy/blob/4b747476703fe4fff1aaccd76ebe570698bbf4f0/src/josepy/util.py#L125-L131) that all slots are provided. That check would not allow us to do a backwards-compatible addition, so I implemented an `__init__` for each of these subclasses that fills the fields without calling the parent `__init__`, and so doesn't hit an error when `identifier` is absent. I chose to use `acme.messages.Identifier` rather than `certbot._internal.san.SAN` here because these are wrapped ACME types, so they should use the ACME representation. Also, `AnnotatedChallenge` is passed to plugins, so we need to pass a type that the plugins can understand. Additionally, `domain` is marked as deprecated. Part of #10346 /cc @bmw, who noticed the issue with `AnnotatedChallenge` [here](#10468 (comment)) and provided additional feedback [here](jsha#2 (comment)). Note that there's still some work to do to finish excising `domain` assumptions from this portion of the code. --------- Co-authored-by: ohemorange <ebportnoy@gmail.com>
Fixes #10506. When --webroot-path was specified multiple times, Certbot was erroring with `DNSName SAN compared to non-SAN`. That's because, in the _WebrootPathAction that builds `namespace.webroot_path`, we were passing `domain` (type `san.DNSName`) as the keys. The other code that modifies or accesses `namespace.webroot_path` expects the keys to be of type `str`. In particular `webroot.Authenticator._set_webroots` does: ```python for achall in achalls: self.conf("map").setdefault(achall.domain, webroot_path) ``` Where `achall.domain` is a `str`. Two existing unittests would have caught this: `test_multiwebroot` and `test_webroot_map_partial_without_perform`. However, they faked out the parsing of the `--domains` flag, and that faked out code was not updated in #10468. Since this bug is caused by an interaction between the types produced by the `--domains` flag and those produced by the `--webroot-path` flag, the tests failed to catch the problem. I've updated the tests and confirmed that they fail before the fix is applied.
Fixes #10506. When --webroot-path was specified multiple times, Certbot was erroring with `DNSName SAN compared to non-SAN`. That's because, in the _WebrootPathAction that builds `namespace.webroot_path`, we were passing `domain` (type `san.DNSName`) as the keys. The other code that modifies or accesses `namespace.webroot_path` expects the keys to be of type `str`. In particular `webroot.Authenticator._set_webroots` does: ```python for achall in achalls: self.conf("map").setdefault(achall.domain, webroot_path) ``` Where `achall.domain` is a `str`. Two existing unittests would have caught this: `test_multiwebroot` and `test_webroot_map_partial_without_perform`. However, they faked out the parsing of the `--domains` flag, and that faked out code was not updated in #10468. Since this bug is caused by an interaction between the types produced by the `--domains` flag and those produced by the `--webroot-path` flag, the tests failed to catch the problem. I've updated the tests and confirmed that they fail before the fix is applied.
In #10478 we added a
san.SANclass, with two subclassessan.DNSNameandsan.IPAddress, so we can carry type information about identifiers through the Certbot code. This PR plumbs through those types in most Certbot-internal code. Note that this does not change theacmemodule, which usesmessages.Identifier. It also tries to leave alone the code paths into plugins.This does not add a CLI flag to request an IP address certificate. That will be in a followup PR.
Part of #10346