diff --git a/README.md b/README.md
index a7a9d741..0927eef1 100644
--- a/README.md
+++ b/README.md
@@ -193,8 +193,11 @@ Default configure file `overlaybd.json` is installed to `/etc/overlaybd/`.
| gzipCacheConfig.cacheSizeGB | The max size of cache, in GB. |
| gzipCacheConfig.refillSize | The refill size from source, in byte. `262144` is default (256 KB). |
| credentialFilePath(legacy) | The credential used for fetching images on registry. `/opt/overlaybd/cred.json` is the default value. |
-| credentialConfig.mode | Authentication mode for lazy-loading.
- `file` means reading credential from `credentialConfig.path`.
- `http` means sending an http request to `credentialConfig.path` |
+| credentialConfig.mode | Authentication mode for lazy-loading.
- `file` means reading credential from `credentialConfig.path`.
- `http` means sending an http request to `credentialConfig.path`
- `https` means sending an https request to `credentialConfig.path`, with optional client certificate authentication and CA pinning |
| credentialConfig.path | credential file path or url which is determined by `mode` |
+| credentialConfig.client_cert_path | Optional. Path to the client certificate file (`https` mode). May contain the private key in the same PEM file. |
+| credentialConfig.client_key_path | Optional. Path to the client private key file (`https` mode). Only needed when the key is separate from the certificate. |
+| credentialConfig.server_ca_path | Optional. Path to the CA certificate used to verify the server (`https` mode). If omitted, the system CA bundle is used. When set, **only** this CA file is trusted. |
| download.enable | Whether background downloading is enabled or not. |
| download.delay | The seconds waiting to start downloading task after the overlaybd device launched. |
| download.delayExtra | A random extra delay is attached to delay, avoiding too many tasks started at the same time. |
@@ -293,6 +296,35 @@ Overlaybd supports serveral credential mode. Here are some example `credentialCo
```
we write a sample http server in `test/simple_auth_server.cpp`
+- mode **https**
+
+ the `credentialConfig.path` should be an HTTPS server listening address. Unlike `http` mode, the `https://` scheme prefix must be included in the path (e.g. `https://localhost:19876/auth`). The optional `client_cert_path`/`client_key_path` fields enable client certificate authentication, and `server_ca_path` pins trust to a specific CA. For a local auth server, providing all three fields secures communication exclusively with that server (mutual TLS).
+
+```json
+#### /etc/overlaybd/config.json ####
+{
+ "logLevel": 1,
+ "logPath": "/var/log/overlaybd.log",
+ ...
+ "credentialConfig": {
+ "mode": "https",
+ "path": "https://localhost:19876/auth",
+ "client_cert_path": "/etc/overlaybd/client.crt",
+ "client_key_path": "/etc/overlaybd/client.key",
+ "server_ca_path": "/etc/overlaybd/ca.crt"
+ },
+ ...
+}
+```
+ overlaybd will send an https request with mTLS to the server with `remote_url` like this:
+> GET "https://localhost:19876/auth?remote_url=https://hub.docker.com/v2/overlaybd/ubuntu/blobs/sha256:47e63559a8487efb55b2f1ccea9cfc04110a185c49785fdf1329d1ea462ce5f0"
+ the server response format is the same as the `http` mode.
+
+ All three TLS fields are optional and independently configured:
+ - `client_cert_path` sets the client certificate. If the PEM file also contains the private key, `client_key_path` can be omitted.
+ - `client_key_path` sets the client private key. Only needed when the key is in a separate file from the certificate.
+ - If `server_ca_path` is omitted, the system CA bundle is used to verify the server certificate. When `server_ca_path` is set, **only** the specified CA file is used — the system CA bundle is not consulted.
+
## Usage
diff --git a/src/config.h b/src/config.h
index d2640194..d89a210a 100644
--- a/src/config.h
+++ b/src/config.h
@@ -97,6 +97,9 @@ struct CredentialConfig : public ConfigUtils::Config {
APPCFG_PARA(mode, std::string, "");
APPCFG_PARA(path, std::string, "");
APPCFG_PARA(timeout, int, 1);
+ APPCFG_PARA(client_cert_path, std::string, "");
+ APPCFG_PARA(client_key_path, std::string, "");
+ APPCFG_PARA(server_ca_path, std::string, "");
};
struct CacheConfig : public ConfigUtils::Config {
diff --git a/src/image_service.cpp b/src/image_service.cpp
index 3d39c57a..bb79f97c 100644
--- a/src/image_service.cpp
+++ b/src/image_service.cpp
@@ -166,6 +166,49 @@ int load_cred_from_http(const std::string addr /* http server */, const std::str
return parse_auths(response.data().auths(), remote_path, username, password);
}
+int load_cred_from_https(const std::string addr /* https server */, const std::string &remote_path,
+ std::string &username, std::string &password, int timeout,
+ const std::string &client_cert_path, const std::string &client_key_path,
+ const std::string &server_ca_path) {
+
+ auto request = new photon::net::cURL();
+ DEFER({ delete request; });
+
+ // Configure mTLS: client certificate, client key, and server CA verification
+ if (!server_ca_path.empty()) {
+ request->set_cafile(server_ca_path.c_str());
+ request->setopt(CURLOPT_SSL_VERIFYPEER, 1L).setopt(CURLOPT_SSL_VERIFYHOST, 2L);
+ }
+ if (!client_cert_path.empty()) {
+ request->setopt(CURLOPT_SSLCERT, client_cert_path.c_str());
+ }
+ // When CURLOPT_SSLKEY is not set, libcurl expects the private key to be
+ // bundled in the same PEM file as the client certificate.
+ if (!client_key_path.empty()) {
+ request->setopt(CURLOPT_SSLKEY, client_key_path.c_str());
+ }
+
+ auto request_url = addr + "?remote_url=" + remote_path;
+ LOG_INFO("request url: `", request_url);
+ photon::net::StringWriter writer;
+ auto ret = request->GET(request_url.c_str(), &writer, (int64_t)timeout * 1000000);
+ if (ret != 200) {
+ LOG_ERRNO_RETURN(0, -1, "connect to auth component failed. http response code: `", ret);
+ }
+ LOG_DEBUG(writer.string);
+ ImageAuthResponse response;
+ LOG_DEBUG("response size: `", writer.string.size());
+ if (response.ParseJSONStream(writer.string) == false) {
+ LOG_ERRNO_RETURN(0, -1, "parse http response message failed: `", writer.string);
+ }
+ LOG_INFO("traceId: `, succ: `", response.traceId(), response.success());
+ if (response.success() == false) {
+ LOG_ERRNO_RETURN(0, -1, "http request failed.");
+ }
+ ImageConfigNS::AuthConfig cfg;
+ return parse_auths(response.data().auths(), remote_path, username, password);
+}
+
int ImageService::read_global_config_and_set() {
LOG_INFO("using config `", m_config_path);
if (!global_conf.ParseJSON(m_config_path)) {
@@ -241,6 +284,13 @@ ImageService::reload_auth(const char *remote_path) {
} else if (mode == "http") {
auto timeout = global_conf.credentialConfig().timeout();
res = load_cred_from_http(path, std::string(remote_path), username, password, timeout);
+ } else if (mode == "https") {
+ auto timeout = global_conf.credentialConfig().timeout();
+ auto client_cert = global_conf.credentialConfig().client_cert_path();
+ auto client_key = global_conf.credentialConfig().client_key_path();
+ auto server_ca = global_conf.credentialConfig().server_ca_path();
+ res = load_cred_from_https(path, std::string(remote_path), username, password,
+ timeout, client_cert, client_key, server_ca);
} else {
LOG_ERROR("invalid mode for authentication.");
return std::make_pair("","");