API-Docker
view release on metacpan or search on metacpan
lib/API/Docker/Role/HTTP.pm view on Meta::CPAN
is => 'lazy',
clearer => '_clear_socket',
);
# The connect timeout and the endpoint it belongs to, on their way to
# _build__socket. It is a lazy builder and so cannot be handed an argument,
# and the value is per request rather than per client -- hence rw, with
# _reconnect as the only writer, setting it immediately before the build and
# clearing it immediately after. Unset is the whole of the old behaviour: no
# Timeout on any constructor, and the plain croak on a failure.
has _pending_connect => (
is => 'rw',
init_arg => undef,
);
sub _build__socket {
my ($self) = @_;
my $host = $self->host;
my $pending = $self->_pending_connect;
my $timeout = $pending ? $pending->{timeout} : undef;
if ($host =~ m{^unix://(.+)$}) {
my $path = $1;
$log->debugf("Connecting to Unix socket: %s", $path);
my $sock = IO::Socket::UNIX->new(
Peer => $path,
Type => SOCK_STREAM,
$timeout ? (Timeout => $timeout) : (),
);
unless ($sock) {
# Asked before anything else can touch $@ or $!, which is the whole of
# the evidence; see _connect_expired.
$self->_croak_connect_timeout($pending, 'unix://' . $path)
if $self->_connect_expired($timeout);
croak "Cannot connect to Unix socket $path: $!";
}
return $sock;
}
elsif ($host =~ m{^tcp://([^:]+):(\d+)$}) {
my ($addr, $port) = ($1, $2);
unless ($self->tls) {
$log->debugf("Connecting to TCP %s:%s", $addr, $port);
my $sock = IO::Socket::INET->new(
PeerAddr => $addr,
PeerPort => $port,
Proto => 'tcp',
$timeout ? (Timeout => $timeout) : (),
);
unless ($sock) {
$self->_croak_connect_timeout($pending, $addr . ':' . $port)
if $self->_connect_expired($timeout);
croak "Cannot connect to $addr:$port: $!";
}
return $sock;
}
# Built before the connection is opened: a cert_path that names nothing,
# or half a client certificate, is a configuration mistake and the caller
# should hear about it as one rather than as a handshake failure.
my %ssl = $self->_ssl_options($addr);
$log->debugf("Connecting to TCP %s:%s over TLS (verification %s)",
$addr, $port, $self->tls_insecure ? 'off' : 'on');
my $sock = IO::Socket::SSL->new(
PeerAddr => $addr,
PeerPort => $port,
Proto => 'tcp',
$timeout ? (Timeout => $timeout) : (),
%ssl,
);
unless ($sock) {
$self->_croak_connect_timeout($pending, $addr . ':' . $port . ' (TLS)')
if $self->_connect_expired($timeout);
# $SSL_ERROR carries the handshake failure -- an untrusted certificate,
# a name that does not match -- and $! the plain connect failure. Both
# are named because either can be the one that happened. The pragma is
# for the package variable of a module that is not loaded at compile
# time, which perl would otherwise report as a probable typo.
no warnings 'once';
croak 'Cannot connect to ' . $addr . ':' . $port . ' over TLS: '
. ($IO::Socket::SSL::SSL_ERROR || $! || 'unknown error');
}
return $sock;
}
else {
croak "Unsupported host format: $host (expected unix:// or tcp://)";
}
}
# Loaded here rather than with the other modules at the top of the file.
# IO::Socket::SSL pulls in Net::SSLeay, which is XS compiled against libssl,
# and the unix:// transport -- local Docker, rootless Podman, the default and
# the only one most installations use -- never needs a byte of it. A hard
# dependency would make this client unbuildable on a machine with no OpenSSL
# headers for the sake of a transport it is not using, so it is a recommended
# one and this is the point where its absence becomes an error.
sub _load_ssl {
my ($self) = @_;
return 1 if eval { require IO::Socket::SSL; 1 };
my $why = $@ || 'unknown error';
$why =~ s/\s+\z//;
croak __PACKAGE__ . ': tls => 1 needs IO::Socket::SSL, which failed to '
. 'load (' . $why . '). It is a recommended rather than a required '
. 'dependency because the unix:// transport never uses it -- install it '
. 'with `cpanm IO::Socket::SSL` (or `cpanm --with-recommends '
. 'API::Docker`)';
}
# The IO::Socket::SSL arguments for this client, as a plain hash, so the
# policy can be read off without opening a connection.
sub _ssl_options {
my ($self, $addr) = @_;
$self->_load_ssl;
# SNI, sent whether or not the certificate is checked: a terminator serving
# several names needs it to pick the right one, and that is true of an
# unverified connection too.
my %ssl = ( SSL_hostname => $addr );
if ($self->tls_insecure) {
# Everything below is off deliberately, and the attribute that got us here
# says so in its name. Encryption without verification stops a passive
# listener and nothing else: whoever answers the connection chooses the
# certificate, so anyone able to redirect it reads and rewrites the
# traffic -- credentials, image contents, container commands.
$ssl{SSL_verify_mode} = IO::Socket::SSL::SSL_VERIFY_NONE();
$ssl{SSL_verifycn_scheme} = undef;
}
else {
$ssl{SSL_verify_mode} = IO::Socket::SSL::SSL_VERIFY_PEER();
# The name is checked against the certificate as well as the chain: a
# valid certificate for some other host is not this host.
$ssl{SSL_verifycn_scheme} = 'http';
$ssl{SSL_verifycn_name} = $addr;
}
return (%ssl, $self->_ssl_certificates);
}
# cert.pem, key.pem and ca.pem in one directory -- the layout the docker CLI
# writes and the one cert_path has always pointed at, whether or not anything
# read it.
sub _ssl_certificates {
my ($self) = @_;
my $dir = $self->cert_path;
return () unless defined $dir && length $dir;
# cert_path defaults from DOCKER_CERT_PATH, so it can arrive from a machine's
# environment rather than from this caller -- but it is only looked at once
# TLS was asked for, and at that point a path naming nothing is a mistake
# worth stopping on rather than quietly connecting without the certificates
# the caller believes are in use.
my $path = path($dir);
croak __PACKAGE__ . ": cert_path $dir is not a directory. TLS expects the "
. 'layout the docker CLI writes -- ca.pem, cert.pem and key.pem in one '
. 'directory -- and this names nothing that could hold it'
unless $path->is_dir;
my %ssl;
# No ca.pem is not an error: verifying a daemon behind a terminator with a
# publicly trusted certificate needs no private trust anchor, and the
# default store is then the right one. See L</"TLS on a tcp:// connection">.
my $ca = $path->child('ca.pem');
$ssl{SSL_ca_file} = "$ca" if $ca->exists;
my $cert = $path->child('cert.pem');
my $key = $path->child('key.pem');
my @half = grep { !$_->[1]->exists }
( [ 'cert.pem', $cert ], [ 'key.pem', $key ] );
# One of the two is never a mode, only ever an accident: a key with no
# certificate proves nothing and a certificate with no key cannot be used.
croak __PACKAGE__ . ': cert_path ' . $dir . ' has ' . $half[0][0]
. ' missing while the other half of the client certificate is there. '
. 'Both cert.pem and key.pem are needed, or neither'
if @half == 1;
if (!@half) {
$ssl{SSL_cert_file} = "$cert";
$ssl{SSL_key_file} = "$key";
}
return %ssl;
}
sub _reconnect {
my ($self, $pending) = @_;
$self->_clear_socket;
# Cleared on the way out whichever way the build went, so a later _socket
# built by anything but a request -- a test subclass, a caller reaching for
# it directly -- never picks up the last request's bound.
$self->_pending_connect($pending);
my $sock;
my $ok = eval { $sock = $self->_socket; 1 };
my $err = $@;
$self->_pending_connect(undef);
die $err unless $ok;
return $sock;
}
# Whether the connect that has just failed failed because the bound fired.
# Asked with nothing in between, because $@ and $! are the whole of the
# evidence and both are global.
#
# $@ rather than errno: IO::Socket writes 'connect: timeout' there, and only
# there, when its own select() ran out -- measured, against a host that drops
# SYNs, where $! is ETIMEDOUT, which the kernel also produces on its own after
# two minutes with no Timeout set at all.
#
# EAGAIN is the second shape and belongs to unix:// alone. Measured against a
# listener whose backlog is full: with no Timeout the connect blocks
# indefinitely (still blocked after 8s), and with one it fails at once with
# EAGAIN, because IO::Socket does the timed connect non-blocking and an
# AF_UNIX connect has no in-progress state to wait on. So on that transport
# the option does not wait, it refuses -- but a connect that failed with
# EAGAIN is still one the bound ended, and reporting it as anything else would
# name a cause the caller cannot act on.
sub _connect_expired {
my ($self, $timeout) = @_;
return 0 unless $timeout;
return 1 if defined $@ && $@ =~ /connect: timeout\z/;
return 1 if $! == EAGAIN || $! == EWOULDBLOCK;
return 0;
}
sub _croak_connect_timeout {
my ($self, $pending, $where) = @_;
my $endpoint = $pending->{endpoint};
# See _croak_timeout for why the object goes into a variable first and why
# the location is captured by hand.
my $error = API::Docker::Error::Timeout->new(
message => 'Docker API connect timeout'
. (defined $endpoint && length $endpoint ? ' (' . $endpoint . ')' : '')
. ': ' . $where . ' did not accept within ' . $pending->{timeout} . 's',
location => shortmess(''),
endpoint => defined $endpoint ? $endpoint : '',
timeout => $pending->{timeout},
phase => 'connect',
);
lib/API/Docker/Role/HTTP.pm view on Meta::CPAN
}
# ---------------------------------------------------------------------------
# Reading, in one buffer regime
#
# Every byte of a response is taken off the handle by _pull and by nothing
# else, and every reader below is served out of the buffer _pull fills. That
# is not an optimisation, it is the only shape that works (karr k60).
#
# What forced it: perl's read() is fread-shaped. It loops until it has the
# LENGTH it was asked for or the stream ends -- it does not return what has
# arrived. Measured on an AF_UNIX socketpair whose peer writes 6 bytes, waits
# half a second, writes 6 more and closes: read($sock, $buf, 65536) came back
# with 12 after 0.90s, having waited for the close, while sysread came back
# with 6 in 0.00s. On the endpoints with neither a Content-Length nor chunked
# encoding -- attach, logs(follow), exec/start, all
# application/vnd.docker.raw-stream -- the reader asks for $READ_SIZE, so
# read() delivered nothing to an on_frame/on_chunk callback until 64K had
# piled up or the daemon hung up. On a stream that never ends it would deliver
# nothing at all. The POD promised those callbacks the bytes as they arrive,
# and that promise was not kept.
#
# Why it could not be fixed at the one site that had the bug: _read_head read
# the status line and the headers with <$sock>, and PerlIO reads ahead. The
# bytes past the header block were sitting in a buffer this code cannot reach
# -- there is no supported way to take them back out; ungetc is layer-
# dependent, seek does not work on a socket, and select/MSG_PEEK see the
# kernel's buffer rather than PerlIO's. So switching only the body reads to
# sysread would have silently dropped the start of every body. Either all read
# sites move together or none do.
#
# The buffer lives on the handle rather than on the client or in the context:
# it is the unconsumed bytes of *that* handle, its lifetime is the handle's,
# and a client that opens a socket per request therefore has nothing to reset.
# ${*$sock}{...} is the IO::Socket idiom for exactly this and was measured to
# work on a real socket, a lexical filehandle, a bareword glob and a tied
# handle alike.
my $RBUF = __PACKAGE__ . '/rbuf';
sub _read_buffer {
my ($self, $sock) = @_;
${*$sock}{$RBUF} = '' unless defined ${*$sock}{$RBUF};
return \${*$sock}{$RBUF};
}
# The one physical read. It answers with what happened rather than with a
# count, because a count makes every reader re-derive the same distinction and
# an undef quietly becoming an end of stream at any one of them is the silent
# truncation this transport must not have:
#
# 'data' something was appended to the buffer -- however little
# 'eof' the stream ended
# 'timeout' the read_timeout expired with nothing to show for it
#
# What TLS does here, since it is not obvious from the call. IO::Socket::SSL
# ties the glob, so the builtin sysread reaches the same place -- SSL_HANDLE's
# READ delegates to the object's own sysread -- and the method form is written
# out only so that the dispatch is visible rather than accidental. What does
# matter is sysread rather than read: IO::Socket::SSL's sysread is a single
# Net::SSLeay::read, one record, while its read is ssl_read_all on a blocking
# socket, which is the same fill semantics this is here to get away from.
#
# A short positive read is never an end of stream and never an expiry. Over
# TLS it is the normal case, one plaintext record at a time; over a plain
# socket it is whatever the kernel had. Both are data.
#
# SSL_WANT_READ and SSL_WANT_WRITE are deliberately not retried. On a blocking
# socket they arrive as EWOULDBLOCK (IO::Socket::SSL's _skip_rw_error does
# `$! ||= EWOULDBLOCK`) and mean the underlying receive would have blocked --
# which, with SO_RCVTIMEO in force, is the bound firing and nothing else.
# Retrying would be a busy loop on WANT_READ and could not make progress on
# WANT_WRITE in any case, so they are reported as the timeout they are.
sub _pull {
my ($self, $sock, $ctx) = @_;
my $buf = $self->_read_buffer($sock);
while (1) {
# errno immediately before, errno immediately after, nothing in between:
# it is only meaningful after a failure, and any operation at all would
# overwrite it. See _timed_out.
$! = 0;
my $n = $sock->sysread(my $got, $READ_SIZE);
my $errno = 0 + $!;
if (defined $n) {
return 'eof' unless $n;
$$buf .= $got;
return 'data';
}
# A signal is not an answer. perl's read() retried here of its own accord
# (PerlIOUnix_read loops while errno is EINTR), so retrying keeps the
# behaviour this replaces rather than introducing one.
next if $errno == EINTR;
return 'timeout' if $self->_timed_out($ctx, $errno);
# Anything else -- a reset connection, a handle that cannot be read at
# all -- ends the response, which is what it did before this too: every
# reader answered a failed read with `last unless $n`. Whether the
# response was complete when it ended is a question about its structure,
# and is asked by the readers that know the structure.
return 'eof';
}
}
# The two reads every reader below is built out of. Both serve from the buffer
# and pull only when it is empty, so both hand back what has arrived rather
# than waiting for what was asked for.
sub _read_line {
my ($self, $sock, $ctx) = @_;
$ctx ||= {};
my $buf = $self->_read_buffer($sock);
my $idx = index($$buf, "\n");
while ($idx < 0) {
my $kind = $self->_pull($sock, $ctx);
# The part of a line already in the buffer is dropped, exactly as the
lib/API/Docker/Role/HTTP.pm view on Meta::CPAN
=over
=item * F<ca.pem> - the trust anchor the daemon's certificate is checked
against
=item * F<cert.pem> and F<key.pem> - this client's certificate and private
key, sent when the daemon asks the client to identify itself
=back
The two halves of the client certificate go together: one of them present
without the other is a croak, because a key with no certificate proves nothing
and a certificate with no key cannot be used. A directory holding only
F<ca.pem> is fine -- that is a daemon this client verifies but does not
authenticate to. A C<cert_path> that names nothing is a croak: it is read only
once TLS was asked for, and at that point a path pointing nowhere means the
caller believes certificates are in use that are not.
C<cert_path> defaults from C<DOCKER_CERT_PATH>, so on a machine that also runs
the C<docker> CLI it arrives set. Without C<< tls => 1 >> nothing reads it, so
that costs nothing; with it, pass C<< cert_path => undef >> to use the system
trust store instead of the CLI's private one.
=head3 TLS with no certificates at all
It means B<encrypt and verify against the system trust store>, not an error.
C<tls> asks for a connection that is encrypted and whose far end is
authenticated. It does not ask to authenticate this client, which is what the
files on disk are for, and treating the absence of a client certificate as a
missing precondition would conflate the two. The deployment with no
certificate files is real, and is the one this role's documentation used to
recommend before there was any TLS here: a terminator -- nginx, stunnel,
Traefik -- in front of the daemon, holding a publicly trusted certificate.
There is nothing for a C<cert_path> to point at in that setup.
It is also the safe reading rather than the lax one: verification stays on
either way, so the mode reached by configuring nothing is the verifying mode.
A stock C<dockerd --tlsverify> uses a private CA that the system store does
not have, and such a connection fails with a verification error naming exactly
that -- which is the intended outcome, not a silent downgrade. Point
C<cert_path> at the directory holding its F<ca.pem> and it verifies.
=head3 Turning verification off
C<< tls_insecure => 1 >>, and the name is the whole of the warning. It sets
C<SSL_VERIFY_NONE> and switches the hostname check off, which leaves a
connection that is encrypted against a passive listener and against nothing
else: whoever answers chooses the certificate, so anyone able to redirect the
connection reads and rewrites everything on it -- registry credentials,
image contents, the commands containers are started with.
It exists for a self-signed daemon certificate whose CA is genuinely not to
hand. The better answer to that is nearly always F<ca.pem>: a self-signed
certificate is its own CA and can be used as the anchor directly.
=head3 The dependency
L<IO::Socket::SSL> is a B<recommended>, not a required, dependency, and it is
loaded at the moment the first TLS connection is opened. It brings in
L<Net::SSLeay>, which is XS compiled against libssl, and the C<unix://>
transport -- local Docker, rootless Podman, the default -- never needs any of
it; requiring it would make this client unbuildable on a machine with no
OpenSSL headers for the sake of a transport it is not using. Without it,
C<< tls => 1 >> croaks naming the module and how to install it, at the same
point every other connection failure is reported.
=head2 read_timeout
Seconds of silence after which a request gives up and croaks with an
L<API::Docker::Error::Timeout>. C<undef> -- the default, and what every
existing caller gets -- means no timeout at all and is the behaviour this
distribution has always had. C<0> means the same and is the way to say it
explicitly, so a client carrying a default can be opted out of per request.
my $docker = API::Docker->new(read_timeout => 30);
$docker->system->using(read_timeout => 0)->events; # this one may wait
Per request it is an option of L</get>, L</post>, L</put>, L</delete_request>
and L</head>. A resource class carries it through
L<API::Docker::Role::Using/using>, which clones the class rather than taking
it per method -- up for a slow endpoint, down for a stream that should not
stall, off with C<0>.
See L</"Bounding a request that never ends"> for what it does and does not
cover, and L<API::Docker/"What a timeout covers"> for the same question
asked of both bounds at once.
=head2 connect_timeout
Seconds after which opening the connection gives up and croaks with an
L<API::Docker::Error::Timeout> whose C<< ->phase >> is C<'connect'>. C<undef>
-- the default, and what every existing caller gets -- means no bound and is
the behaviour this distribution has always had; C<0> means the same and is the
way to say it explicitly.
my $docker = API::Docker->new(connect_timeout => 5);
$docker->system->using(connect_timeout => 0)->version; # may wait
Separate from L</read_timeout> rather than folded into it, because the two
bound different things and want different numbers: a connect is either
immediate or broken, while a read is waiting on work the daemon has to do.
Per request it is an option of L</get>, L</post>, L</put>, L</delete_request>
and L</head>. A resource class carries it through
L<API::Docker::Role::Using/using>. See L</"Bounding the connection itself">
for what it does on each transport, which is not the same thing on all
three.
=head2 get
my $data = $client->get($path, %opts);
Perform HTTP GET request. Returns decoded JSON or raw response body.
Options:
=over
=item * C<params> - HashRef of query parameters; a HashRef value is JSON-encoded
( run in 0.462 second using v1.01-cache-2.11-cpan-ad19def0cd9 )