Custom root CA
In a cluster, services often use certificates issued by a private certificate authority, for example by
cert-manager, so that they can talk HTTP/2 and TLS 1.2 or 1.3 to each other. The nodes and the container images
do not trust that authority. You can add the root certificate to the image when you build it; if you prefer to
keep the image independent of the cluster, store the root certificate in a Kubernetes secret, mount it as a
volume, and tell your HTTP clients to trust it. Arc4u reads the root certificates from the configuration and
configures HttpClient instances to validate server certificates against them. For the place of these calls
in an Arc4u application, see Concepts.
Configuration
AddCustomRootCA(configuration) reads the CustomRootCA section (parameter sectionName). Each entry is a
named CARootOption with exactly one source:
{
"CustomRootCA": {
"Cluster": { "CaFilePath": "/app/ca/ca.crt" }
}
}
| Key | Type | Description |
|---|---|---|
CustomRootCA:<name>:CaFilePath |
string | Path of a PEM file holding the root certificate, for example a mounted secret. |
CustomRootCA:<name>:CaPem |
string | The PEM text of the root certificate. |
CustomRootCA:<name>:Store |
object | A certificate of the operating system store: Name, FindType (default FindBySubjectName), Location (default LocalMachine), StoreName (default My). |
An entry with no source or with more than one source throws ConfigurationException at startup. When the
section is missing, nothing is registered, so the feature can be enabled per environment.
Code
using Arc4u.OAuth2.Extensions;
using Arc4u.Security.Cryptography;
using Arc4u.Security.CustomRootCA;
builder.Services.AddSingleton<IX509CertificateLoader, X509CertificateLoader>();
builder.Services.AddCustomRootCA(builder.Configuration);
builder.Services.AddHttpClient("Orders", client => client.BaseAddress = new Uri("https://orders.my-namespace.svc"))
.ConfigureLocalCaCertificate("Cluster");
ConfigureLocalCaCertificate(name) sets the primary handler of the named client. Without a name, it trusts all
the registered root certificates. It needs IX509CertificateLoader and the Arc4u logger
(AddApplicationContext(), see the overview).
For gRPC clients, ConfigureLocalCaCertificateForGrpc of Arc4u.AspNetCore.gRpc does the same with a
SocketsHttpHandler; see gRPC and API versioning.
When a server certificate fails the standard validation, the handler builds the chain again with the custom certificates as the only trusted roots, without revocation check. A certificate that passes the standard validation is accepted as usual.
Caution
Known issue: when the standard validation fails, only the chain is checked. A certificate issued by your custom authority for another host name is accepted (the gRPC variant behaves the same way). Issue the certificates of your private authority only to services you trust.
Trust the identity provider
The JWT bearer and OpenID Connect handlers download the metadata and the signing keys with their own
Backchannel HTTP client, which ConfigureLocalCaCertificate does not change. When the identity provider uses
a certificate of your private authority, give the handler a client configured with it (general ASP.NET Core
configuration, verified with AddJwtAuthentication):
using Arc4u.OAuth2.Extensions;
using Arc4u.Security.Cryptography;
using Arc4u.Security.CustomRootCA;
using Microsoft.AspNetCore.Authentication.JwtBearer;
builder.Services.AddSingleton<IX509CertificateLoader, X509CertificateLoader>();
builder.Services.AddCustomRootCA(builder.Configuration);
builder.Services.AddHttpClient("IdentityProvider")
.ConfigureLocalCaCertificate("Cluster");
builder.Services.AddOptions<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme)
.Configure<IHttpClientFactory>((options, factory) =>
options.Backchannel = factory.CreateClient("IdentityProvider"));
For OpenID Connect, configure OpenIdConnectOptions of the OpenIdConnect scheme the same way: the token
refresh uses its Backchannel too. Without this, the API answers 401 because the metadata cannot be downloaded.
Kubernetes example
With cert-manager, the secret of a certificate contains ca.crt. Mount it and point CaFilePath to it
(general guidance, adapt the names to your cluster):
spec:
containers:
- name: my-api
volumeMounts:
- name: root-ca
mountPath: /app/ca
readOnly: true
volumes:
- name: root-ca
secret:
secretName: my-api-tls
items:
- key: ca.crt
path: ca.crt
See also
- Server authentication
- gRPC and API versioning
- CustomRootCaExtensions and CustomRootCaExtension in the API reference