Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. <br> - `file` means reading credential from `credentialConfig.path`. <br> - `http` means sending an http request to `credentialConfig.path` |
| credentialConfig.mode | Authentication mode for lazy-loading. <br> - `file` means reading credential from `credentialConfig.path`. <br> - `http` means sending an http request to `credentialConfig.path` <br> - `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. |
Expand Down Expand Up @@ -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

Expand Down
3 changes: 3 additions & 0 deletions src/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
50 changes: 50 additions & 0 deletions src/image_service.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down Expand Up @@ -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("","");
Expand Down
Loading