Skip to content

Fix "Empty input" when loading SSL_CERT_FILE/SSL_CERT_DIR cert bundles.#1706

Open
PavolMisudikAta wants to merge 2 commits into
minio:masterfrom
PavolMisudikAta:fix-setting-certificates-entries
Open

Fix "Empty input" when loading SSL_CERT_FILE/SSL_CERT_DIR cert bundles.#1706
PavolMisudikAta wants to merge 2 commits into
minio:masterfrom
PavolMisudikAta:fix-setting-certificates-entries

Conversation

@PavolMisudikAta

@PavolMisudikAta PavolMisudikAta commented Jun 9, 2026

Copy link
Copy Markdown

Problem description

Constructing a MinioClient fails at startup when SSL_CERT_FILE (or
SSL_CERT_DIR) points at a normal CA bundle:

io.minio.errors.MinioException: java.security.cert.CertificateException:
Could not parse certificate: java.io.IOException: Empty input
    at io.minio.Http.setCertificateEntry(Http.java:455)
    at io.minio.Http.getTrustManagerFromFile(Http.java:467)
    at io.minio.Http.getCompositeTrustManager(Http.java:529)
    at io.minio.Http.enableExternalCertificates(Http.java:620)
    at io.minio.Http.enableExternalCertificatesFromEnv(Http.java:641)
    at io.minio.Http.newDefaultClient(Http.java:828)

This reproduces on 9.0.0 and 9.0.1. It bites any base image that exports
SSL_CERT_FILE/SSL_CERT_DIR (Chainguard/Wolfi, Alpine, Debian, …), which is
increasingly common.

Root cause

Http#setCertificateEntry reads the bundle with:

while (in.available() > 0) {
  X509Certificate cert = (X509Certificate) cf.generateCertificate(in);
  ks.setCertificateEntry(namePrefix + (index++), cert);
}

After the final -----END CERTIFICATE-----, any trailing bytes (a trailing
blank line, or trailing whitespace — both present in real CA bundles) leave
available() > 0, so the loop calls generateCertificate once more on the
remaining whitespace and X509Factory throws IOException: Empty input.
InputStream.available() is also not a reliable end-of-stream test, so the
exact failure is environment-dependent.

Fix

Read the whole bundle with CertificateFactory#generateCertificates(InputStream),
which parses all certificates in one pass, stops at EOF, and ignores trailing
whitespace. One-line change in setCertificateEntry; covers both the
SSL_CERT_FILE and SSL_CERT_DIR paths (both call it).

Summary by CodeRabbit

  • Bug Fixes

    • Fixed external TLS certificate loading so PEM bundles with or without trailing newlines/whitespace are handled reliably.
  • Tests

    • Added parameterized tests validating external certificate bundle loading across multiple PEM format variants to prevent regressions.

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d896010d-d7ae-4ef2-a4c1-7b5145f455d1

📥 Commits

Reviewing files that changed from the base of the PR and between d3f1fc6 and 1257116.

📒 Files selected for processing (1)
  • api/src/test/java/io/minio/HttpExternalCertificatesTest.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • api/src/test/java/io/minio/HttpExternalCertificatesTest.java

📝 Walkthrough

Walkthrough

This PR changes Http.setCertificateEntry() to parse all PEM certificates at once via CertificateFactory.generateCertificates(), adds the missing Certificate import, and adds a parameterized JUnit test that validates loading bundles with different trailing newline/whitespace variants.

Changes

Certificate Bundle Loading

Layer / File(s) Summary
Certificate loading implementation
api/src/main/java/io/minio/Http.java
Adds java.security.cert.Certificate import and refactors setCertificateEntry() to call CertificateFactory.generateCertificates(in) and iterate the returned certificates to populate the KeyStore.
Certificate bundle test coverage
api/src/test/java/io/minio/HttpExternalCertificatesTest.java
Adds a parameterized JUnit test with four PEM bundle variants (single newline, no newline, blank line, trailing whitespace); each variant is written to a temp .pem file and passed to Http.enableExternalCertificates(...), asserting a non-null OkHttpClient.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I nibble PEM leaves by moonlight bright,
Bundles trimmed or trailing space—alright!
One call to gather each certificate true,
Tests hop through endings, old and new,
Keys stored snugly, the TLS spring is light.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and specifically describes the main fix: resolving the 'Empty input' error that occurs when loading SSL certificate bundles from standard paths, which is the core issue addressed by the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (2)
api/src/test/java/io/minio/HttpExternalCertificatesTest.java (2)

55-63: ⚡ Quick win

Add a SSL_CERT_DIR-path test for parity with the production fix scope.

Current coverage exercises only the file-path flow. Add a case that writes the PEM into a temp directory and calls Http.enableExternalCertificates(baseClient, null, dirPath) to validate the directory code path too.

Also applies to: 73-77

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/src/test/java/io/minio/HttpExternalCertificatesTest.java` around lines 55
- 63, Update HttpExternalCertificatesTest.bundles() to include a new
parameterized case that exercises the SSL_CERT_DIR directory path: create a
temporary directory, write the PEM content into a file inside it, and add a test
case name like "ssl_cert_dir" that will be used by the test harness; then in the
corresponding test execution branch call
Http.enableExternalCertificates(baseClient, null, dirPath) (instead of the file
path) so the directory code path in Http.enableExternalCertificates is
validated; ensure you add the same directory-based case for the other
parameterized list at lines 73-77 as well.

73-77: ⚡ Quick win

Strengthen the assertion to prove certificates were actually applied.

Assert.assertNotNull(client) can pass even when no external certs are loaded (the method may return the original client). Assert that a new client instance is returned.

Suggested test tightening
   `@Test`
   public void loadsExternalCertificateBundle() throws Exception {
     String path = writeBundle(bundle);
-    OkHttpClient client = Http.enableExternalCertificates(new OkHttpClient(), path, null);
+    OkHttpClient baseClient = new OkHttpClient();
+    OkHttpClient client = Http.enableExternalCertificates(baseClient, path, null);
     Assert.assertNotNull(client);
+    Assert.assertNotSame(baseClient, client);
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/src/test/java/io/minio/HttpExternalCertificatesTest.java` around lines 73
- 77, The test currently only asserts the returned client is non-null; instead
create a named original OkHttpClient (e.g., OkHttpClient original = new
OkHttpClient()), call Http.enableExternalCertificates(original, path, null) into
client, then assert the method returned a new instance
(Assert.assertNotSame(original, client)) and additionally assert that the
returned client's SSL configuration changed (for example verify
client.sslSocketFactory() differs from original.sslSocketFactory() or that the
client's SSL/TrustManager references the loaded certificate) to prove the bundle
was actually applied. Use the existing test method
loadsExternalCertificateBundle and the Http.enableExternalCertificates call to
locate where to update assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@api/src/test/java/io/minio/HttpExternalCertificatesTest.java`:
- Around line 55-63: Update HttpExternalCertificatesTest.bundles() to include a
new parameterized case that exercises the SSL_CERT_DIR directory path: create a
temporary directory, write the PEM content into a file inside it, and add a test
case name like "ssl_cert_dir" that will be used by the test harness; then in the
corresponding test execution branch call
Http.enableExternalCertificates(baseClient, null, dirPath) (instead of the file
path) so the directory code path in Http.enableExternalCertificates is
validated; ensure you add the same directory-based case for the other
parameterized list at lines 73-77 as well.
- Around line 73-77: The test currently only asserts the returned client is
non-null; instead create a named original OkHttpClient (e.g., OkHttpClient
original = new OkHttpClient()), call Http.enableExternalCertificates(original,
path, null) into client, then assert the method returned a new instance
(Assert.assertNotSame(original, client)) and additionally assert that the
returned client's SSL configuration changed (for example verify
client.sslSocketFactory() differs from original.sslSocketFactory() or that the
client's SSL/TrustManager references the loaded certificate) to prove the bundle
was actually applied. Use the existing test method
loadsExternalCertificateBundle and the Http.enableExternalCertificates call to
locate where to update assertions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 39cf8c2f-fb3a-46ab-b275-692568504bfc

📥 Commits

Reviewing files that changed from the base of the PR and between 5a41af5 and d3f1fc6.

📒 Files selected for processing (2)
  • api/src/main/java/io/minio/Http.java
  • api/src/test/java/io/minio/HttpExternalCertificatesTest.java

@balamurugana balamurugana left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why are those env var set to empty?

@PavolMisudikAta

PavolMisudikAta commented Jun 9, 2026

Copy link
Copy Markdown
Author

Hi @balamurugana , sorry, not sure, what you mean :/

I encountered the issue, when using the chainguard image, which exports sets
SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt

The crt file was not empty, but the MinIO still failed to start ( exception in the description ).
After the investigation, the problem seems to be the incorrect parsing of the file.

balamurugana
balamurugana previously approved these changes Jun 12, 2026
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.

2 participants