-
Notifications
You must be signed in to change notification settings - Fork 52
Make server-ca optional in Cloud Logging credentials #432
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
25a2205
afd87de
d264cbb
e7e4123
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| package com.sap.hcf.cf.logging.opentelemetry.agent.ext.tls; | ||
|
|
||
| import java.io.ByteArrayInputStream; | ||
| import java.security.cert.CertificateException; | ||
| import java.security.cert.CertificateFactory; | ||
| import java.security.cert.X509Certificate; | ||
| import java.util.logging.Level; | ||
| import java.util.logging.Logger; | ||
| import java.util.stream.Stream; | ||
|
|
||
| /** | ||
| * Supplies the X.509 certificate carried by a Cloud Foundry service binding's | ||
| * {@code server-ca} field (or an equivalent field name for other bindings). | ||
| * | ||
| * <p>The input is expected to be the raw PEM bytes as they arrive in the binding. | ||
| * A {@code null} or empty input yields an empty stream, so the source can be used | ||
| * unconditionally with {@link TrustedCertificatesJoiner}.</p> | ||
| */ | ||
| public class BindingServerCertificateSource implements X509CertificateSource { | ||
|
|
||
| private static final Logger LOG = Logger.getLogger(BindingServerCertificateSource.class.getName()); | ||
|
|
||
| private final byte[] pemBytes; | ||
|
|
||
| public BindingServerCertificateSource(byte[] pemBytes) { | ||
| this.pemBytes = pemBytes; | ||
| } | ||
|
|
||
| @Override | ||
| public Stream<X509Certificate> stream() { | ||
| if (pemBytes == null || pemBytes.length == 0) { | ||
| return Stream.empty(); | ||
| } | ||
| try { | ||
| CertificateFactory factory = CertificateFactory.getInstance("X.509"); | ||
| X509Certificate certificate = (X509Certificate) factory.generateCertificate(new ByteArrayInputStream(pemBytes)); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please use |
||
| return Stream.of(certificate); | ||
| } catch (CertificateException e) { | ||
| LOG.log(Level.WARNING, e, () -> "Failed to parse server-ca from service binding; it will be omitted from the trust anchors."); | ||
| return Stream.empty(); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| package com.sap.hcf.cf.logging.opentelemetry.agent.ext.tls; | ||
|
|
||
| import java.io.ByteArrayInputStream; | ||
| import java.nio.charset.StandardCharsets; | ||
| import java.security.cert.CertificateException; | ||
| import java.security.cert.CertificateFactory; | ||
| import java.security.cert.X509Certificate; | ||
| import java.util.logging.Level; | ||
| import java.util.logging.Logger; | ||
| import java.util.stream.Stream; | ||
|
|
||
| /** | ||
| * Supplies the X.509 leaf certificate returned by {@link ServerCertificateDownloader} | ||
| * for a fixed OTLP endpoint URL. | ||
| * | ||
| * <p>This is the fallback path used when a service binding does not carry a | ||
| * {@code server-ca} field. It connects to the endpoint with TLS (validation disabled) | ||
| * and reads back the leaf certificate the server presents.</p> | ||
| */ | ||
| public class DownloadedServerCertificateSource implements X509CertificateSource { | ||
|
|
||
| private static final Logger LOG = Logger.getLogger(DownloadedServerCertificateSource.class.getName()); | ||
|
|
||
| private final ServerCertificateDownloader downloader; | ||
| private final String endpointUrl; | ||
|
|
||
| public DownloadedServerCertificateSource(ServerCertificateDownloader downloader, String endpointUrl) { | ||
| this.downloader = downloader; | ||
| this.endpointUrl = endpointUrl; | ||
| } | ||
|
|
||
| @Override | ||
| public Stream<X509Certificate> stream() { | ||
| String pem = downloader.download(endpointUrl); | ||
| if (pem == null || pem.isEmpty()) { | ||
| return Stream.empty(); | ||
| } | ||
| try { | ||
| CertificateFactory factory = CertificateFactory.getInstance("X.509"); | ||
| X509Certificate certificate = (X509Certificate) factory | ||
| .generateCertificate(new ByteArrayInputStream(pem.getBytes(StandardCharsets.UTF_8))); | ||
| return Stream.of(certificate); | ||
| } catch (CertificateException e) { | ||
| LOG.log(Level.WARNING, e, () -> "Failed to parse server certificate downloaded from " + endpointUrl | ||
| + "; it will be omitted from the trust anchors."); | ||
| return Stream.empty(); | ||
| } | ||
|
Comment on lines
+38
to
+47
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is largely duplicate code to the BindingServerCertificateSource. |
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| package com.sap.hcf.cf.logging.opentelemetry.agent.ext.tls; | ||
|
|
||
| import java.nio.charset.StandardCharsets; | ||
| import java.security.cert.CertificateEncodingException; | ||
| import java.security.cert.X509Certificate; | ||
| import java.util.Base64; | ||
|
|
||
| /** | ||
| * Encodes X.509 certificates as PEM strings. | ||
| * | ||
| * <p>Line separator is {@code "\n"} and Base64 body is wrapped at 64 characters, | ||
| * matching the format expected by OpenTelemetry's {@code setTrustedCertificates(byte[])}.</p> | ||
| */ | ||
| final class PemEncoder { | ||
|
|
||
| private static final String LINE_SEPARATOR = "\n"; | ||
| private static final Base64.Encoder BASE64_ENCODER = | ||
| Base64.getMimeEncoder(64, LINE_SEPARATOR.getBytes(StandardCharsets.UTF_8)); | ||
|
|
||
| private PemEncoder() { | ||
| } | ||
|
|
||
| /** | ||
| * Returns the PEM encoding of the given certificate, including the | ||
| * {@code BEGIN CERTIFICATE} / {@code END CERTIFICATE} armor and a trailing newline. | ||
| */ | ||
| static String encode(X509Certificate certificate) throws CertificateEncodingException { | ||
| return "-----BEGIN CERTIFICATE-----" + LINE_SEPARATOR | ||
| + BASE64_ENCODER.encodeToString(certificate.getEncoded()) + LINE_SEPARATOR | ||
| + "-----END CERTIFICATE-----" + LINE_SEPARATOR; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| package com.sap.hcf.cf.logging.opentelemetry.agent.ext.tls; | ||
|
|
||
| import javax.net.ssl.TrustManagerFactory; | ||
| import javax.net.ssl.X509TrustManager; | ||
| import java.security.KeyStore; | ||
| import java.security.KeyStoreException; | ||
| import java.security.NoSuchAlgorithmException; | ||
| import java.security.cert.X509Certificate; | ||
| import java.util.Arrays; | ||
| import java.util.logging.Level; | ||
| import java.util.logging.Logger; | ||
| import java.util.stream.Stream; | ||
|
|
||
| /** | ||
| * Supplies the X.509 trust anchors known to the JVM's default trust store | ||
| * (typically {@code $JAVA_HOME/lib/security/cacerts}). | ||
| * | ||
| * <p>This is the same set the platform uses to validate ordinary HTTPS connections, | ||
| * so any endpoint whose server certificate chains to a public root is trusted without | ||
| * additional configuration.</p> | ||
| * | ||
| * <p>The stream aggregates the accepted issuers of <em>every</em> | ||
| * {@link X509TrustManager} returned by the default {@link TrustManagerFactory}, so a | ||
| * runtime with multiple configured trust managers contributes all of them.</p> | ||
| */ | ||
| public class SystemTrustAnchorSource implements X509CertificateSource { | ||
|
|
||
| private static final Logger LOG = Logger.getLogger(SystemTrustAnchorSource.class.getName()); | ||
|
|
||
| @Override | ||
| public Stream<X509Certificate> stream() { | ||
| try { | ||
| TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); | ||
| tmf.init((KeyStore) null); | ||
| return Arrays.stream(tmf.getTrustManagers()) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can |
||
| .filter(X509TrustManager.class::isInstance) | ||
| .map(X509TrustManager.class::cast) | ||
| .flatMap(tm -> Arrays.stream(tm.getAcceptedIssuers())); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can |
||
| } catch (NoSuchAlgorithmException | KeyStoreException e) { | ||
| LOG.log(Level.WARNING, e, () -> "Failed to enumerate JVM default trust anchors; system trust anchors will be omitted."); | ||
| return Stream.empty(); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| package com.sap.hcf.cf.logging.opentelemetry.agent.ext.tls; | ||
|
|
||
| import java.nio.charset.StandardCharsets; | ||
| import java.security.cert.CertificateEncodingException; | ||
| import java.security.cert.X509Certificate; | ||
| import java.util.logging.Level; | ||
| import java.util.logging.Logger; | ||
|
|
||
| /** | ||
| * Concatenates the PEM encodings of the certificates from one or more | ||
| * {@link X509CertificateSource} instances into a single byte array suitable for | ||
| * {@code OtlpGrpc*ExporterBuilder.setTrustedCertificates(byte[])}. | ||
| * | ||
| * <p>Sources are consumed in the order given; certificates that fail to encode | ||
| * are logged and skipped so a single bad certificate does not break the exporter.</p> | ||
| */ | ||
| public final class TrustedCertificatesJoiner { | ||
|
|
||
| private static final Logger LOG = Logger.getLogger(TrustedCertificatesJoiner.class.getName()); | ||
|
|
||
| private TrustedCertificatesJoiner() { | ||
| } | ||
|
|
||
| /** | ||
| * Encodes every certificate produced by the given sources as PEM and returns the | ||
| * concatenated bytes (UTF-8). Empty sources contribute nothing; the returned array | ||
| * is empty if no source yields a certificate. | ||
| */ | ||
| public static byte[] toPemBytes(X509CertificateSource... sources) { | ||
| StringBuilder pem = new StringBuilder(); | ||
| for (X509CertificateSource source : sources) { | ||
| source.stream().forEach(cert -> appendPem(pem, cert)); | ||
| } | ||
| return pem.toString().getBytes(StandardCharsets.UTF_8); | ||
| } | ||
|
|
||
| private static void appendPem(StringBuilder pem, X509Certificate cert) { | ||
| try { | ||
| pem.append(PemEncoder.encode(cert)); | ||
| } catch (CertificateEncodingException e) { | ||
| LOG.log(Level.WARNING, e, () -> "Failed to PEM-encode a trust anchor; it will be omitted."); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would actually expect, that an explicit certificate takes precedence over the system trust store. Furthermore, if the server-ca is provided explicitly, why would you still trust the default certs?