diff --git a/README.md b/README.md
index 34a096db..0bc65424 100644
--- a/README.md
+++ b/README.md
@@ -193,7 +193,7 @@ 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`
- `https` means sending an https request to `credentialConfig.path`, with optional client certificate authentication and CA pinning |
+| 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
- `uds` means sending the same http request as `http` mode, but over a Unix-domain socket at `credentialConfig.path` |
| 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. |
@@ -325,6 +325,30 @@ we write a sample http server in `test/simple_auth_server.cpp`
- `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.
+- mode **uds**
+
+ the `credentialConfig.path` should be the filesystem path of a Unix-domain socket. overlaybd dials the socket and speaks the same HTTP request/response format as the `http` mode.
+
+```json
+#### /etc/overlaybd/config.json ####
+{
+ "logLevel": 1,
+ "logPath": "/var/log/overlaybd.log",
+ ...
+ "credentialConfig": {
+ "mode": "uds",
+ "path": "/run/overlaybd/creds.sock"
+ },
+ ...
+}
+```
+ overlaybd will dial the socket and send a request like:
+> GET "http://localhost/auth?remote_url=https://hub.docker.com/v2/overlaybd/ubuntu/blobs/sha256:47e63559a8487efb55b2f1ccea9cfc04110a185c49785fdf1329d1ea462ce5f0"
+
+ The HTTP host (`localhost` here) is a placeholder — the request is always dialed over the configured socket. The server response format is identical to the `http` mode.
+
+ Security model: a UDS is not network-reachable and is gated by filesystem permissions on the socket file. Restrict access by setting the socket owner/group and a non-world-readable mode (e.g. `0600` or `0660`) on the helper side; overlaybd does not enforce this on the client.
+
## Usage
diff --git a/src/image_service.cpp b/src/image_service.cpp
index bb79f97c..e754770a 100644
--- a/src/image_service.cpp
+++ b/src/image_service.cpp
@@ -209,6 +209,46 @@ int load_cred_from_https(const std::string addr /* https server */, const std::s
return parse_auths(response.data().auths(), remote_path, username, password);
}
+int load_cred_from_uds(const std::string socket_path, const std::string &remote_path,
+ std::string &username, std::string &password, int timeout) {
+ if (socket_path.empty()) {
+ LOG_ERROR_RETURN(0, -1, "empty uds socket path");
+ }
+ if (::access(socket_path.c_str(), F_OK) != 0) {
+ LOG_ERRNO_RETURN(0, -1, "uds socket does not exist: `", socket_path);
+ }
+ if (::access(socket_path.c_str(), W_OK) != 0) {
+ LOG_ERRNO_RETURN(0, -1, "uds socket not writable: `", socket_path);
+ }
+
+ auto request = new photon::net::cURL();
+ DEFER({ delete request; });
+
+ request->setopt(CURLOPT_UNIX_SOCKET_PATH, socket_path.c_str());
+
+ // Host is a placeholder when CURLOPT_UNIX_SOCKET_PATH is set; libcurl
+ // dials the socket regardless of the URL host.
+ auto request_url = std::string("http://localhost/auth?remote_url=") + remote_path;
+ LOG_INFO("request url: ` (uds: `)", request_url, socket_path);
+ 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)) {
@@ -291,6 +331,10 @@ ImageService::reload_auth(const char *remote_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 if (mode == "uds") {
+ auto timeout = global_conf.credentialConfig().timeout();
+ res = load_cred_from_uds(path, std::string(remote_path),
+ username, password, timeout);
} else {
LOG_ERROR("invalid mode for authentication.");
return std::make_pair("","");
diff --git a/src/test/simple_credsrv_test.cpp b/src/test/simple_credsrv_test.cpp
index acc33934..169c47df 100644
--- a/src/test/simple_credsrv_test.cpp
+++ b/src/test/simple_credsrv_test.cpp
@@ -97,6 +97,37 @@ TEST(auth, http_server) {
photon::thread_sleep(60);
}
+TEST(auth, uds_server) {
+ const char *sock_path = "/tmp/obd-credsrv-uds-test.sock";
+ ::unlink(sock_path);
+
+ auto udsserver = photon::net::new_uds_server(true /* autoremove */);
+ udsserver->timeout(1000UL * 1000);
+ ASSERT_EQ(0, udsserver->bind(sock_path));
+ ASSERT_EQ(0, udsserver->listen());
+ DEFER(delete udsserver);
+
+ auto server = new_http_server();
+ DEFER(delete server);
+ SimpleAuthHandler h;
+ server->add_handler(&h, false, "/auth");
+ udsserver->set_handler(server->get_connection_handler());
+ udsserver->start_loop();
+ photon::thread_sleep(1);
+
+ std::string remote_path = "", user = "", passwd = "";
+ // The handler sleeps 1s before responding; a 1s timeout must trip.
+ auto ret = load_cred_from_uds(sock_path, remote_path, user, passwd, 1);
+ EXPECT_EQ(ret, -1);
+ ret = load_cred_from_uds(sock_path, remote_path, user, passwd, 3);
+ EXPECT_EQ(ret, 0);
+
+ // Missing socket file must surface as a clear error.
+ ret = load_cred_from_uds("/tmp/obd-credsrv-uds-test.does-not-exist.sock",
+ remote_path, user, passwd, 1);
+ EXPECT_EQ(ret, -1);
+}
+
int main(int argc, char** arg) {
photon::init();
DEFER(photon::fini());