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
26 changes: 25 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. <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.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 <br> - `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. |
Expand Down Expand Up @@ -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

Expand Down
44 changes: 44 additions & 0 deletions src/image_service.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Comment on lines +212 to +213
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)) {
Expand Down Expand Up @@ -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("","");
Expand Down
31 changes: 31 additions & 0 deletions src/test/simple_credsrv_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
Loading