Skip to content

security/acme-client: fix IPv6 support#5281

Open
pgerber wants to merge 3 commits intoopnsense:masterfrom
pgerber:ipv6
Open

security/acme-client: fix IPv6 support#5281
pgerber wants to merge 3 commits intoopnsense:masterfrom
pgerber:ipv6

Conversation

@pgerber
Copy link

@pgerber pgerber commented Mar 2, 2026

Important notices
Before you submit a pull request, we ask you kindly to acknowledge the following:

If AI was used, please disclose:

n/a


Related issue
Fixes #5228


Describe the problem

Issuing certificates for IPv6-only hosts failed.


Describe the proposed solution

See commit messages.



// Check if IPv6 support is enabled
if (isset($configObj->system->ipv6allow) && ($configObj->system->ipv6allow == '1')) {
if (!isset($configObj->system->ipv6allow) || ($configObj->system->ipv6allow == '1')) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does not exist in this configuration spot anymore

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand. Are you saying a later commit removes this? Yes, that intentional to ensure commit is consistent in itself. This way you can revert one of the later commits and don't end up in a broken state where this change is missing. Even if the revert no longer applies cleanly you should still see that you need to touch this file.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. I guess I never actually tested with IPv6 disabled, who would in 2026. Let me see if I can setup IPv4-based access to my test OPNsense instance to actually test it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I missed that a plugin might use it. The flip of the meaning was intended to address your first point here. I think you just need to chase these new locations. The inversion of the logic is already a good step :)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've now switched to the new property / auxiliary function and actually tested things with IPv6 disabled.

Thanks for pointing out my mistake.

@fraenki fraenki self-assigned this Mar 3, 2026
@fraenki fraenki changed the title acme: fix IPv6 support security/acme-client: fix IPv6 support Mar 3, 2026
$ipv6_redirect_addr = null;
if ($_ipv6_enabled == true) {
$backend = new \OPNsense\Core\Backend();
$interface = "wan";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you hardcoding wan here? The interface is specified in $this->config->tlsalpn_acme_interface. It's not always wan, the IP might be configured on a different interface.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redirecting to ::1 isn't possible, as described in the commit message. This is simply an attempt find any address in the right scope. Any address really, on any interface, that belongs to this host. I could use tlsalpn_acme_interface but, to my knowledge, there is no guarantee that this interface is set.

# bind to port
server.bind = "127.0.0.1"
server.port = {{OPNsense.AcmeClient.settings.challengePort}}
$SERVER["socket"] == "127.0.0.1:{{OPNsense.AcmeClient.settings.challengePort}}" { }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you removing this? It's there for a reason.

Copy link
Author

@pgerber pgerber Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is that this was added to have the web server only bind to localhost (::1 and 127.0.0.1). However, as stated in the commit message, we cannot do this for IPv6 because ::1 is a scope that doesn't allow traffic coming from another interface. Removing this reverts binding to the default behavior, namely, binding to all interfaces.

I could try to only bind to specific addresses but this seems a bit pointless. It would require to generate this config for every Challenge Type. Addresses my differ between them. I also looked at some other services running on OPNsense and binding to all interfaces and then using the firewall to restrict access seems the norm:

# netstat -an -f inet6
Active Internet connections (including servers)
Proto Recv-Q Send-Q Local Address          Foreign Address        (state)    
...
tcp6       0      0 *.53                   *.*                    LISTEN     
tcp6       0      0 *.53                   *.*                    LISTEN     
tcp6       0      0 *.53                   *.*                    LISTEN     
tcp6       0      0 *.53                   *.*                    LISTEN     
tcp6       0      0 *.22                   *.*                    LISTEN     
tcp6       0      0 *.443                  *.*                    LISTEN     
tcp6       0      0 *.43580                *.*                    LISTEN     
...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually found some issues with this while testing with IPv6 disabled and I ended up readding explicit binding for IPv4 and IPv6.

@pgerber
Copy link
Author

pgerber commented Mar 4, 2026

One thing I should probably point out is that IP Auto-Discovery still doesn't work with IPv6. Maybe I'll look at it later, not sure.

pgerber and others added 3 commits March 4, 2026 00:40
`ipv6allow` was renamed to `disableipv6`. It is now always set
and its meaning was flipped.
…6 address

Note that this will still not include virtual IPs but only the
main IPs.
…nges

Underlying issue:

IPv6 features multiple scopes restricting where the IP address
is valid [1]. ::1 belongs to link-local scope which is not
allowed to be routed. As result FreeBSD will reject the
connection after rewriting, as it will come from global
scope (the internet) and going to ::1 [2]. This isn't allowed.

The fix / workaround:

Instead of redirecting to ::1, the following is tried:

* If there is a WAN interface with an IPv6 address defined,
  redirect to this address.

  I expect most setup with IPv6 to have a WAN interface with
  a suitable (=allowed scope) IPv6 address.

* Else, only redirect the port and leave the address unchanged.

  This will only work if we are issuing a certificate for
  ourselves (rather than a host behind the firewall).

A better solution would be to pick an arbitrary IPv6 address
of the host with a suitable scope. However, I believe this
would be considerably more complex to implement and test.
I propose we use this simplified approach, at least for now,
which should already work for the vast majority of users.

[1]: https://en.wikipedia.org/wiki/IPv6_address#Address_scopes
[2]: https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=193568
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

security/acme-client: unable to get ipv6 certificate

3 participants