[PATCH v2] erofs-utils: lib: oci: support reading credentials from docker config
ChengyuZhu6
hudson at cyzhu.com
Tue Feb 10 20:37:26 AEDT 2026
From: Chengyu Zhu <hudsonzhu at tencent.com>
This patch adds support for reading authentication credentials from
Docker's `config.json` (typically in `~/.docker/config.json` or via
`DOCKER_CONFIG`).
If no username/password is provided via command-line arguments, the
implementation will attempt to look up the registry in the docker config
file and use the stored credentials if found.
Signed-off-by: Chengyu Zhu <hudsonzhu at tencent.com>
---
include/erofs/internal.h | 10 ++
lib/Makefile.am | 2 +-
lib/liberofs_dockerconfig.h | 30 +++++
lib/remotes/docker_config.c | 240 ++++++++++++++++++++++++++++++++++++
lib/remotes/oci.c | 18 ++-
5 files changed, 295 insertions(+), 5 deletions(-)
create mode 100644 lib/liberofs_dockerconfig.h
create mode 100644 lib/remotes/docker_config.c
diff --git a/include/erofs/internal.h b/include/erofs/internal.h
index ef019a5..3f1e4ff 100644
--- a/include/erofs/internal.h
+++ b/include/erofs/internal.h
@@ -25,6 +25,8 @@ typedef unsigned short umode_t;
#ifdef HAVE_PTHREAD_H
#include <pthread.h>
#endif
+#include <stdlib.h>
+#include <string.h>
#include "atomic.h"
#include "io.h"
@@ -542,6 +544,14 @@ static inline int erofs_blk_read(struct erofs_sb_info *sbi, int device_id,
erofs_pos(sbi, nblocks));
}
+static inline void erofs_free_sensitive(void *ptr, size_t len)
+{
+ if (!ptr)
+ return;
+ memset(ptr, 0, len);
+ free(ptr);
+}
+
/* vmdk.c */
int erofs_dump_vmdk_desc(FILE *f, struct erofs_sb_info *sbi);
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 817f69a..7a65b2c 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -95,7 +95,7 @@ liberofs_la_LDFLAGS += ${liburing_LIBS}
liberofs_la_SOURCES += backends/ublk.c
endif
endif
-liberofs_la_SOURCES += remotes/oci.c
+liberofs_la_SOURCES += remotes/oci.c remotes/docker_config.c
liberofs_la_CFLAGS += ${json_c_CFLAGS}
liberofs_la_LDFLAGS += ${json_c_LIBS}
liberofs_la_SOURCES += gzran.c
diff --git a/lib/liberofs_dockerconfig.h b/lib/liberofs_dockerconfig.h
new file mode 100644
index 0000000..1580e1c
--- /dev/null
+++ b/lib/liberofs_dockerconfig.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */
+/*
+ * Copyright (C) 2026 Tencent, Inc.
+ * http://www.tencent.com/
+ */
+#ifndef __EROFS_DOCKER_CONFIG_H
+#define __EROFS_DOCKER_CONFIG_H
+
+#include "erofs/internal.h"
+
+#define DOCKER_REGISTRY "docker.io"
+#define DOCKER_API_REGISTRY "registry-1.docker.io"
+#define DOCKER_HUB_AUTH_KEY "https://index.docker.io/v1/"
+
+struct erofs_docker_credential {
+ char *username;
+ char *password;
+};
+
+/**
+ * erofs_docker_config_lookup - look up registry credentials from Docker config
+ * @registry: the registry hostname (e.g. "index.docker.io")
+ * @cred: output credential structure
+ */
+int erofs_docker_config_lookup(const char *registry,
+ struct erofs_docker_credential *cred);
+
+void erofs_docker_credential_free(struct erofs_docker_credential *cred);
+
+#endif
diff --git a/lib/remotes/docker_config.c b/lib/remotes/docker_config.c
new file mode 100644
index 0000000..89fb826
--- /dev/null
+++ b/lib/remotes/docker_config.c
@@ -0,0 +1,240 @@
+// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0
+/*
+ * Copyright (C) 2026 Tencent, Inc.
+ * http://www.tencent.com/
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#ifdef HAVE_JSON_C_JSON_H
+#include <json-c/json.h>
+#endif
+
+#include "erofs/defs.h"
+#include "erofs/print.h"
+#include "liberofs_base64.h"
+#include "liberofs_dockerconfig.h"
+
+#ifndef HAVE_JSON_C_JSON_H
+
+int erofs_docker_config_lookup(const char *registry,
+ struct erofs_docker_credential *cred)
+{
+ (void)registry;
+ (void)cred;
+ return -EOPNOTSUPP;
+}
+
+void erofs_docker_credential_free(struct erofs_docker_credential *cred)
+{
+ (void)cred;
+}
+
+#else /* HAVE_JSON_C_JSON_H */
+
+static char *docker_config_path(void)
+{
+ const char *dir;
+ char *path = NULL;
+
+ dir = getenv("DOCKER_CONFIG");
+ if (dir) {
+ if (!*dir)
+ return NULL;
+ if (asprintf(&path, "%s/config.json", dir) < 0)
+ return NULL;
+ return path;
+ }
+
+ dir = getenv("HOME");
+ if (!dir || !*dir) {
+ erofs_dbg("HOME is not set, cannot locate docker config");
+ return NULL;
+ }
+
+ if (asprintf(&path, "%s/.docker/config.json", dir) < 0)
+ return NULL;
+ return path;
+}
+
+static char *read_file_to_string(const char *path)
+{
+ FILE *fp;
+ struct stat st;
+ char *buf;
+ size_t nread;
+
+ if (stat(path, &st) < 0)
+ return NULL;
+
+ if (st.st_size <= 0 || st.st_size > (1 << 22))
+ return NULL;
+
+ fp = fopen(path, "r");
+ if (!fp)
+ return NULL;
+
+ buf = malloc(st.st_size + 1);
+ if (!buf) {
+ fclose(fp);
+ return NULL;
+ }
+
+ nread = fread(buf, 1, st.st_size, fp);
+ fclose(fp);
+
+ if ((off_t)nread != st.st_size) {
+ free(buf);
+ return NULL;
+ }
+ buf[nread] = '\0';
+ return buf;
+}
+
+/*
+ * Check if @key (an auths entry key) matches @registry.
+ *
+ * For Docker Hub: @registry is docker.io or registry-1.docker.io.
+ * The auths key in config.json is always "https://index.docker.io/v1/".
+ * For other registries: the auths key is an exact match against @registry.
+ */
+static bool registry_match(const char *key, const char *registry)
+{
+ if (!key || !registry)
+ return false;
+
+ if (!strcasecmp(registry, DOCKER_REGISTRY) ||
+ !strcasecmp(registry, DOCKER_API_REGISTRY))
+ return !strcmp(key, DOCKER_HUB_AUTH_KEY);
+
+ return !strcasecmp(key, registry);
+}
+
+static int decode_auth_field(const char *b64, char **out_user, char **out_pass)
+{
+ int b64_len = strlen(b64);
+ int decoded_max = b64_len;
+ u8 *decoded;
+ int decoded_len;
+ char *colon;
+
+ decoded = malloc(decoded_max + 1);
+ if (!decoded)
+ return -ENOMEM;
+
+ decoded_len = erofs_base64_decode(b64, b64_len, decoded);
+ if (decoded_len <= 0) {
+ free(decoded);
+ return -EINVAL;
+ }
+ decoded[decoded_len] = '\0';
+
+ colon = strchr((char *)decoded, ':');
+ if (!colon) {
+ erofs_free_sensitive(decoded, decoded_len);
+ return -EINVAL;
+ }
+
+ *colon = '\0';
+ *out_user = strdup((char *)decoded);
+ *out_pass = strdup(colon + 1);
+
+ erofs_free_sensitive(decoded, decoded_len);
+
+ if (!*out_user || !*out_pass) {
+ free(*out_user);
+ free(*out_pass);
+ *out_user = NULL;
+ *out_pass = NULL;
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+int erofs_docker_config_lookup(const char *registry,
+ struct erofs_docker_credential *cred)
+{
+ char *path = NULL;
+ char *content = NULL;
+ struct json_object *root = NULL, *auths_obj = NULL;
+ int ret = -ENOENT;
+
+ memset(cred, 0, sizeof(*cred));
+
+ path = docker_config_path();
+ if (!path)
+ return -ENOENT;
+
+ content = read_file_to_string(path);
+ if (!content) {
+ erofs_dbg("cannot read docker config: %s", path);
+ free(path);
+ return -ENOENT;
+ }
+ free(path);
+
+ root = json_tokener_parse(content);
+ erofs_free_sensitive(content, strlen(content));
+
+ if (!root) {
+ erofs_warn("failed to parse docker config.json");
+ return -EINVAL;
+ }
+
+ if (!json_object_object_get_ex(root, "auths", &auths_obj)) {
+ erofs_dbg("no \"auths\" in docker config.json");
+ json_object_put(root);
+ return -ENOENT;
+ }
+
+ struct json_object_iterator it = json_object_iter_begin(auths_obj);
+ struct json_object_iterator end = json_object_iter_end(auths_obj);
+
+ while (!json_object_iter_equal(&it, &end)) {
+ const char *key = json_object_iter_peek_name(&it);
+ struct json_object *entry, *auth_field;
+ const char *b64;
+
+ if (!registry_match(key, registry)) {
+ json_object_iter_next(&it);
+ continue;
+ }
+
+ entry = json_object_iter_peek_value(&it);
+ if (json_object_object_get_ex(entry, "auth", &auth_field)) {
+ b64 = json_object_get_string(auth_field);
+ if (b64 && *b64) {
+ ret = decode_auth_field(b64, &cred->username,
+ &cred->password);
+ if (!ret)
+ erofs_dbg("found docker credentials for %s",
+ registry);
+ }
+ }
+ break;
+ }
+
+ json_object_put(root);
+ return ret;
+}
+
+void erofs_docker_credential_free(struct erofs_docker_credential *cred)
+{
+ if (cred->username) {
+ erofs_free_sensitive(cred->username, strlen(cred->username));
+ cred->username = NULL;
+ }
+ if (cred->password) {
+ erofs_free_sensitive(cred->password, strlen(cred->password));
+ cred->password = NULL;
+ }
+}
+
+#endif /* HAVE_JSON_C_JSON_H */
diff --git a/lib/remotes/oci.c b/lib/remotes/oci.c
index fe5df29..48bf37f 100644
--- a/lib/remotes/oci.c
+++ b/lib/remotes/oci.c
@@ -30,6 +30,7 @@
#include "erofs/tar.h"
#include "liberofs_base64.h"
#include "liberofs_oci.h"
+#include "liberofs_dockerconfig.h"
#include "liberofs_private.h"
#include "liberofs_gzran.h"
@@ -39,9 +40,6 @@
#ifdef OCIEROFS_ENABLED
-#define DOCKER_REGISTRY "docker.io"
-#define DOCKER_API_REGISTRY "registry-1.docker.io"
-
#define DOCKER_MEDIATYPE_MANIFEST_V2 \
"application/vnd.docker.distribution.manifest.v2+json"
#define DOCKER_MEDIATYPE_MANIFEST_V1 \
@@ -985,9 +983,21 @@ static int ocierofs_find_layer_by_digest(struct ocierofs_ctx *ctx, const char *d
static int ocierofs_prepare_layers(struct ocierofs_ctx *ctx,
const struct ocierofs_config *config)
{
+ struct erofs_docker_credential dcred = { NULL, NULL };
+ const char *username = config->username;
+ const char *password = config->password;
int ret;
- ret = ocierofs_prepare_auth(ctx, config->username, config->password);
+ /* Fallback to Docker config.json if no CLI credentials provided */
+ if ((!username || !*username) && (!password || !*password)) {
+ if (!erofs_docker_config_lookup(ctx->registry, &dcred)) {
+ username = dcred.username;
+ password = dcred.password;
+ }
+ }
+
+ ret = ocierofs_prepare_auth(ctx, username, password);
+ erofs_docker_credential_free(&dcred);
if (ret)
return ret;
--
2.51.0
More information about the Linux-erofs
mailing list