Skip to content

feat(destination): apply client certificates to DestinationHttpClient - #278

Open
jplbrun wants to merge 3 commits into
mainfrom
feat/apply-client-certs-to-dest-client
Open

feat(destination): apply client certificates to DestinationHttpClient#278
jplbrun wants to merge 3 commits into
mainfrom
feat/apply-client-certs-to-dest-client

Conversation

@jplbrun

@jplbrun jplbrun commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Disclaimer: Do not include SAP-internal or customer-specific information in this PR (e.g. internal system URLs, customer names, tenant IDs, or confidential configurations). This is a public repository.

Description

DestinationHttpClient did not configure client certificates for destinations using ClientCertificateAuthentication, even when the destination carried the certificate material — outbound mTLS calls failed with HTTP 401.

This PR makes DestinationHttpClient apply the client certificate automatically. When a destination's Authentication is ClientCertificateAuthentication, it builds a verifying SSL context from the destination's certificate list and mounts it on the session's https:// adapter. No caller code changes are required.

New _cert_loader.pybuild_client_cert_context(destination) returns a verifying ssl.SSLContext, or None when the destination does not use client-cert auth. Certificate material is written to a short-lived, owner-only (0o600) temp file, loaded via load_cert_chain, then deleted immediately.

_destination_http_client.py — a _ClientCertAdapter (requests HTTPAdapter subclass) injects the context into the pool and proxy managers.

New public DestinationCertificateError.

Supported formats: PEM (.pem) and PKCS12 (.p12/.pfx), with KeyStorePassword for encrypted keys and KeyStoreLocation to select a specific certificate.

DestinationHttpClient also gains context-manager support (with ... as http:), which closes the underlying session on exit.

Related Issue

Closes #254

Type of Change

Please check the relevant option:

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Code refactoring
  • Dependency update

How to Test

Step 1 — Generate test certificates locally:

# Encrypted PEM (combined private key + certificate)
openssl req -x509 -newkey rsa:2048 -keyout key_enc.pem -out cert_enc.pem -days 365 \
  -passout pass:certpassword -subj "/CN=test-mtls-encrypted"
cat key_enc.pem cert_enc.pem > combined_enc.pem

# PKCS12 keystores
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes \
  -subj "/CN=test-mtls"
openssl pkcs12 -export -out client.p12 -inkey key.pem -in cert.pem -passout pass:testpassword
cp client.p12 client.pfx

Step 2 — Upload certificates to BTP Cockpit (Connectivity → Destination Certificates → Create → Import):

File BTP certificate name
combined_enc.pem combined_enc.pem
client.p12 client.p12
client.pfx client.pfx

Step 3 — Create destinations on BTP (Connectivity → Destinations → Import):

destination-pem.json:

{
  "destination": {
    "Authentication": "ClientCertificateAuthentication",
    "KeyStore.Source": "DestinationService",
    "KeyStoreLocation": "combined_enc.pem",
    "KeyStorePassword": "certpassword",
    "Name": "TEST_MTLS_PEM",
    "ProxyType": "Internet",
    "Type": "HTTP",
    "URL": "https://google.com"
  }
}

destination-p12.json:

{
  "destination": {
    "Authentication": "ClientCertificateAuthentication",
    "KeyStore.Source": "DestinationService",
    "KeyStoreLocation": "client.p12",
    "KeyStorePassword": "testpassword",
    "Name": "TEST_MTLS_P12",
    "ProxyType": "Internet",
    "Type": "HTTP",
    "URL": "https://google.com"
  }
}

destination-pfx.json:

{
  "destination": {
    "Authentication": "ClientCertificateAuthentication",
    "KeyStore.Source": "DestinationService",
    "KeyStoreLocation": "client.pfx",
    "KeyStorePassword": "testpassword",
    "Name": "TEST_MTLS_PFX",
    "ProxyType": "Internet",
    "Type": "HTTP",
    "URL": "https://google.com"
  }
}

Step 4 — Fetch each destination via the v2 API and create a DestinationHttpClient from it. Confirm that a request to the destination URL succeeds.

Checklist

Before submitting your PR, please review and check the following:

  • I have read the Contributing Guidelines
  • I have verified that my changes solve the issue
  • I have added/updated automated tests to cover my changes
  • All tests pass locally
  • I have verified that my code follows the Code Guidelines
  • I have updated documentation (if applicable)
  • I have added type hints for all public APIs
  • My code does not contain sensitive information (credentials, tokens, etc.)
  • I have followed Conventional Commits for commit messages

Breaking Changes

None. The constructor and request() signatures of DestinationHttpClient are unchanged. mTLS wiring is additive and only triggers for ClientCertificateAuthentication destinations.

Additional Notes

N/A

@jplbrun
jplbrun marked this pull request as ready for review August 20, 2026 16:47
@jplbrun
jplbrun requested a review from a team as a code owner August 20, 2026 16:47
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.

DestinationHttpClient does not apply client certificates

1 participant