From: Chengyu Zhu <[email protected]> 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 <[email protected]> --- 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
