Module: Docker::API::Auth
- Defined in:
- lib/docker/api/auth.rb
Overview
Registry credentials, resolved per call.
There is no credential store on this module and no way to set one
globally. Credentials are looked up for the registry a particular request
is talking to and travel on that request as X-Registry-Auth, so pulling
from two registries in one process needs no coordination.
Constant Summary collapse
- DOCKER_HUB_KEY =
Docker Hub's canonical auth key, which is not the hostname you pull from.
"https://index.docker.io/v1/"
Class Method Summary collapse
-
.encode(credentials) ⇒ String
Encode a credential hash the way the daemon expects it: base64url of a JSON document, in a header.
- .from_auths(config, key) ⇒ Hash?
-
.from_helper(config, key) ⇒ Hash?
A helper named for this specific registry wins over the catch-all store, which is how
credHelpersis specified to behave. - .read_config(path) ⇒ Hash?
-
.registry_key(registry) ⇒ String
Docker Hub is stored under a URL key rather than its hostname, and an empty registry means Hub.
-
.resolve(registry = nil, config_path: nil) ⇒ String?
Find credentials for a registry and encode them for the daemon.
-
.run_helper(helper, key) ⇒ Hash?
Invoke
docker-credential-<helper> get, which reads the registry on stdin and writes JSON on stdout.
Class Method Details
.encode(credentials) ⇒ String
Encode a credential hash the way the daemon expects it: base64url of a JSON document, in a header.
Encoded with pack rather than with the base64 library, deliberately.
base64 stopped being a default gem in Ruby 3.4, so requiring it makes
this a gem with a runtime dependency it does not declare -- which fails
at require time for anyone whose bundle does not happen to carry it
for another reason. pack("m0") is the same transform with no
newlines, and tr maps the standard alphabet onto the URL-safe one the
daemon expects.
56 57 58 |
# File 'lib/docker/api/auth.rb', line 56 def encode(credentials) [JSON.generate(credentials)].pack("m0").tr("+/", "-_") end |
.from_auths(config, key) ⇒ Hash?
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
# File 'lib/docker/api/auth.rb', line 86 def from_auths(config, key) entry = config.dig("auths", key) return nil if entry.nil? if entry["auth"] && !entry["auth"].empty? # unpack1("m") is Base64.decode64: lenient, ignoring newlines and # any character outside the alphabet, which is what these values # arrive as when a config.json has been hand-edited or line-wrapped. username, _, password = entry["auth"].unpack1("m").partition(":") return { username: username, password: password, serveraddress: key } end return nil if entry["username"].nil? { username: entry["username"], password: entry["password"], serveraddress: key } end |
.from_helper(config, key) ⇒ Hash?
A helper named for this specific registry wins over the catch-all
store, which is how credHelpers is specified to behave.
109 110 111 112 113 114 115 116 117 118 |
# File 'lib/docker/api/auth.rb', line 109 def from_helper(config, key) helper = config.dig("credHelpers", key) || config.dig("credHelpers", URI(key).host.to_s) || config["credsStore"] return nil if helper.nil? || helper.empty? run_helper(helper, key) rescue URI::InvalidURIError nil end |
.read_config(path) ⇒ Hash?
74 75 76 77 78 79 80 81 |
# File 'lib/docker/api/auth.rb', line 74 def read_config(path) path ||= File.join(Dir.home, ".docker", "config.json") return nil unless File.readable?(path) JSON.parse(File.read(path)) rescue JSON::ParserError, SystemCallError, ArgumentError nil end |
.registry_key(registry) ⇒ String
Docker Hub is stored under a URL key rather than its hostname, and an empty registry means Hub. Everything else is keyed by hostname.
65 66 67 68 69 70 |
# File 'lib/docker/api/auth.rb', line 65 def registry_key(registry) return DOCKER_HUB_KEY if registry.nil? || registry.empty? return DOCKER_HUB_KEY if ["docker.io", "index.docker.io", "registry-1.docker.io"].include?(registry) registry end |
.resolve(registry = nil, config_path: nil) ⇒ String?
Find credentials for a registry and encode them for the daemon.
Every failure mode here is soft. A missing config file, an unreadable
one, or a credential helper that is not installed all mean "no
credentials", because anonymous pulls of public images must keep
working on a machine that has never run docker login.
32 33 34 35 36 37 38 39 40 41 |
# File 'lib/docker/api/auth.rb', line 32 def resolve(registry = nil, config_path: nil) key = registry_key(registry) config = read_config(config_path) return nil if config.nil? credentials = from_helper(config, key) || from_auths(config, key) return nil if credentials.nil? encode(credentials) end |
.run_helper(helper, key) ⇒ Hash?
Invoke docker-credential-<helper> get, which reads the registry on
stdin and writes JSON on stdout.
The command is passed as an argument array, never a shell string, so a helper name from a config file cannot become a shell injection.
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/docker/api/auth.rb', line 129 def run_helper(helper, key) output = IO.popen(["docker-credential-#{helper}", "get"], "r+") do |io| io.write(key) io.close_write io.read end return nil unless $CHILD_STATUS.nil? || $CHILD_STATUS.success? parsed = JSON.parse(output.to_s) secret = parsed["Secret"] return nil if secret.nil? || secret.empty? # A username of <token> is the helper's way of saying the secret is an # identity token rather than a password. if parsed["Username"] == "<token>" { identitytoken: secret, serveraddress: parsed["ServerURL"] || key } else { username: parsed["Username"], password: secret, serveraddress: parsed["ServerURL"] || key } end rescue Errno::ENOENT, Errno::EACCES, JSON::ParserError, IOError nil end |