Make server-ca optional in Cloud Logging credentials - #432
Conversation
Signed-off-by: Jannik Brand <jannik.brand@sap.com>
There was a problem hiding this comment.
Please comment whether you tested this with a real application and the Java agent. Up to now, the agent would not create a connection without an explicit server certificate to trust. The agent would just not connect and create errors. This change can only work if the implementation of the agent changed.
Note, that other exporters circumvent this restriction by downloading the server certificate if it was not provided.
|
|
||
| byte[] serverCert = credentials.getServerCert(); | ||
| if (serverCert != null && serverCert.length > 0) { | ||
| builder.setTrustedCertificates(serverCert); |
There was a problem hiding this comment.
Doing this conditionally is known to break the Java agent. It would not establish the connection. You may want to change the TLS setup using builder.setSslContext() instead. This would also be a way to exchange client key and certificate without restart in future.
Signed-off-by: Jannik Brand <jannik.brand@sap.com>
KarstenSchnitter
left a comment
There was a problem hiding this comment.
The original issue is unresolved. When no server-ca is given this implementation will crash.
|
Quite honestly, I would like to see a new interface being introduced based on |
KarstenSchnitter
left a comment
There was a problem hiding this comment.
Please look for other places in this extension, where the PemEncoder could be used.
| .setClientTls(credentials.getClientKey(), credentials.getClientCert()) | ||
| .setTrustedCertificates(credentials.getServerCert()).setRetryPolicy(RetryPolicy.getDefault()); | ||
| .setTrustedCertificates(TrustedCertificatesJoiner.toPemBytes( | ||
| new SystemTrustAnchorSource(), |
There was a problem hiding this comment.
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?
- Inject Function<CloudLoggingCredentials, byte[]> into all three exporter providers so the joiner can be replaced in tests. - Rename X509CertificateSource#get() to stream(). - SystemTrustAnchorSource: merge accepted issuers from every X509TrustManager returned by the default TrustManagerFactory. - PemEncoder: use UTF-8 for the base64 line separator. - Tests: share the loaded certificate as a constant in PemEncoderTest; use Assumptions.assumeThat for the resource-not-null precondition in the tls tests; fold the join-order assertion into the concatenation test.
KarstenSchnitter
left a comment
There was a problem hiding this comment.
There is still some code duplication. But the major question is why to use the system trust store when an explicit server-ca was provided.
| credentials -> TrustedCertificatesJoiner.toPemBytes(new SystemTrustAnchorSource(), | ||
| new BindingServerCertificateSource(credentials.getServerCert()))); |
There was a problem hiding this comment.
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?
| try { | ||
| TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); | ||
| tmf.init((KeyStore) null); | ||
| return Arrays.stream(tmf.getTrustManagers()) |
There was a problem hiding this comment.
Can tmf.getTrustManagers be null?
| return Arrays.stream(tmf.getTrustManagers()) | ||
| .filter(X509TrustManager.class::isInstance) | ||
| .map(X509TrustManager.class::cast) | ||
| .flatMap(tm -> Arrays.stream(tm.getAcceptedIssuers())); |
There was a problem hiding this comment.
Can tm.get.getAcceptedIssuers be null?
| 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(); | ||
| } |
There was a problem hiding this comment.
This is largely duplicate code to the BindingServerCertificateSource.
| } | ||
| try { | ||
| CertificateFactory factory = CertificateFactory.getInstance("X.509"); | ||
| X509Certificate certificate = (X509Certificate) factory.generateCertificate(new ByteArrayInputStream(pemBytes)); |
There was a problem hiding this comment.
Please use generateCertificates (in plural) to get the full certificate chain that might be provided within the pemBytes.
Optimally, I would agree and wanted a replacing instead of additive mechanism here. (This is similar to the contribution in opentelemetry-exporter-for-sap-cloud-logging-for-nodejs)
Happy to get more feedback on this. |
What
Make server-ca optional in the auto-configured OTLP exporters, and when it is present, add it to the default Node.js trust store (rather than replacing the trust store with it).
Why
SAP Cloud Logging's OTLP ingest endpoint presents a server certificate that chains to a public root (Let's Encrypt ISRG Root X1). Java's (e.g. Sapmachine)
cacertsmay already trusts ISRG X1 and X2. The server-ca field in the binding response is a convenience for runtimes that don't already trust public roots.Under the previous "replace" semantics:
server-cavalue from the binding became the sole trust anchor. If the endpoint's certificate ever chained to a different public root than the one pinned in the binding it would fail.We add server-ca rather than replace the system trust store with it because bindings today always ship server-ca. So a replacing it would never exercise the system trust store and would break on CA rotations.
Testing
mvn -pl cf-java-logging-support-opentelemetry-agent-extension test— 274 tests pass, 0 failures (SapMachine 17.0.20.1).Companion changes