8000 Support for Let's encrypt IP address certs by dominicletz · Pull Request #4 · dominicletz/certmagex · GitHub
[go: up one dir, main page]

Skip to content

Support for Let's encrypt IP address certs - #4

Merged
dominicletz merged 6 commits into
masterfrom
ipcert
Mar 19, 2026
Merged

dominicletz merged 6 commits into
masterfrom
ipcert

Conversation

@dominicletz
Copy link
Copy Markdown
Owner

No description provided.

@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces the capability to issue SSL/TLS certificates for IP addresses (both IPv4 and IPv6) using the Let's Encrypt ACME provider. This feature extends the existing certificate management system to handle non-domain identifiers, providing greater flexibility for securing services directly accessible via IP. The changes involve modifications to certificate generation logic, SNI handling, and certificate parsing to correctly identify and process IP-based requests.

Highlights

  • IP Address Certificate Support: Enabled the issuance of SSL/TLS certificates for IP addresses (IPv4 and IPv6) through the Let's Encrypt ACME provider.
  • SNI and SSL Options Enhancement: Introduced a new ssl_opts/1 function for retrieving SSL options specifically for IP-based certificates and updated sni_fun/1 to conditionally process IP addresses, ensuring compatibility only with Let's Encrypt providers.
  • Certificate Signing Request (CSR) Generation: Modified the CSR generation process to include IP Subject Alternative Names (SANs) when an IP address is detected, aligning with ACME standards for IP certificates.
  • Certificate Parsing: Enhanced the internal Erlang domains function to correctly extract IP addresses from certificate SAN extensions.
  • Documentation and Testing: Added comprehensive documentation for the new IP address certificate functionality and included new tests for IP address detection, CSR generation, and provider validation.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

@dominicletz
dominicletz merged commit a5400c7 into master Mar 19, 2026
2 checks passed
@dominicletz
dominicletz deleted the ipcert branch March 19, 2026 22:58
@gemini-code-assist gemini-code-assist Bot left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request adds support for issuing Let's Encrypt certificates for IP addresses. The changes are well-structured, touching the main library, the ACMEv2 implementation, and Erlang helper functions. New tests have been added to cover the IP address functionality. My review includes a few suggestions to improve code clarity, reduce duplication, and follow language idioms. I've also noted a small documentation typo.

Comment thread lib/certmagex.ex

@doc """
Returns the SSL options for the given domain. This is useful for IP based SSL certificates.
Info: https://letsencrypt.org/2026/01/15/6day-and-ip-general-availability
8000 Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

There's a typo in the year in the Let's Encrypt URL. It should be 2024, not 2026.

  Info: https://letsencrypt.org/2024/01/15/6day-and-ip-general-availability

Comment thread lib/certmagex/acmev2.ex
Comment on lines 656 to +674
csr =
X509.CSR.new(key, "CN=#{domain}")
|> X509.CSR.to_der()
|> benc()
if CertMagex.ip?(domain) do
ip_binary = ip_string_to_binary!(domain)
# OTP's public_key uses the GeneralName choice type `iPAddress` (capital P).
# X509's typespec expects `{atom(), charlist()}` for tuple SAN entries; octets as a
# charlist satisfy Dialyzer and encode the same as a binary iPAddress.
san =
X509.Certificate.Extension.subject_alt_name([
{:iPAddress, :binary.bin_to_list(ip_binary)}
])

X509.CSR.new(key, "CN=", extension_request: [san])
|> X509.CSR.to_der()
|> benc()
else
X509.CSR.new(key, "CN=#{domain}")
|> X509.CSR.to_der()
|> benc()
end
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The if/else block has some code duplication (|> X509.CSR.to_der() |> benc()). You can refactor this to make it more DRY by moving the common operations outside the conditional.

    csr =
      (if CertMagex.ip?(domain) do
         ip_binary = ip_string_to_binary!(domain)
         # OTP's public_key uses the GeneralName choice type `iPAddress` (capital P).
         # X509's typespec expects `{atom(), charlist()}` for tuple SAN entries; octets as a
         # charlist satisfy Dialyzer and encode the same as a binary iPAddress.
         san =
           X509.Certificate.Extension.subject_alt_name([
             {:iPAddress, :binary.bin_to_list(ip_binary)}
           ])

         X509.CSR.new(key, "CN=", extension_request: [san])
       else
         X509.CSR.new(key, "CN=#{domain}")
       end)
      |> X509.CSR.to_der()
      |> benc()

Comment thread lib/certmagex/acmev2.ex
Comment on lines +679 to +690
defp ip_string_to_binary!(ip_string) do
case :inet.parse_address(String.to_charlist(ip_string)) do
{:ok, {a, b, c, d}} ->
<<a, b, c, d>>

{:ok, {a, b, c, d, e, f, g, h}} ->
<<a::16, b::16, c::16, d::16, e::16, f::16, g::16, h::16>>

_ ->
raise "Invalid IP address: #{inspect(ip_string)}"
end
end
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

This helper function ip_string_to_binary!/1 is a duplicate of the one defined in test/certmagex_test.exs. Duplicating code can lead to maintenance issues where one is updated but the other is forgotten. While common for testing private functions, consider if this utility could be made public in a suitable module to be shared, or if a shared test helper module would be appropriate.

Comment thread src/certmagex.erl
Comment on lines +14 to +16
IPStrings =
[ip_binary_to_string(Bin) || {ipAddress, Bin} <- Names] ++
[ip_binary_to_string(Bin) || {iPAddress, Bin} <- Names],
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

These two list comprehensions can be combined into one for slightly better performance and conciseness by checking for both ipAddress and iPAddress tags in a single pass.

    IPStrings = [ip_binary_to_string(Bin) || {Tag, Bin} <- Names, (Tag =:= ipAddress orelse Tag =:= iPAddress)],

Comment thread test/certmagex_test.exs
Comment on lines +6 to +19
test "returns true for IPv4 addresses" do
assert CertMagex.ip?("192.168.1.1") == true
assert CertMagex.ip?("0.0.0.0") == true
end

test "returns true for IPv6 addresses" do
assert CertMagex.ip?("::1") == true
assert CertMagex.ip?("2001:db8::1") == true
end

test "returns false for domain names" do
assert CertMagex.ip?("example.com") == false
assert CertMagex.ip?("sub.example.com") == false
end
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

For better readability and more idiomatic Elixir code, you can use assert/1 for truthiness checks and refute/1 for falsiness checks, instead of comparing with == true or == false.

    test "returns true for IPv4 addresses" do
      assert CertMagex.ip?("192.168.1.1")
      assert CertMagex.ip?("0.0.0.0")
    end

    test "returns true for IPv6 addresses" do
      assert CertMagex.ip?("::1")
      assert CertMagex.ip?("2001:db8::1")
    end

    test "returns false for domain names" do
      refute CertMagex.ip?("example.com")
      refute CertMagex.ip?("sub.example.com")
    end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

0