API-Docker

 view release on metacpan or  search on metacpan

LICENSE  view on Meta::CPAN


Source code for a work means the preferred form of the work for making
modifications to it.  For an executable file, complete source code means
all the source code for all modules it contains; but, as a special
exception, it need not include source code for modules which are standard
libraries that accompany the operating system on which the executable
file runs, or for standard header files or definitions files that
accompany that operating system.

  4. You may not copy, modify, sublicense, distribute or transfer the
Program except as expressly provided under this General Public License.
Any attempt otherwise to copy, modify, sublicense, distribute or transfer
the Program is void, and will automatically terminate your rights to use
the Program under this License.  However, parties who have received
copies, or rights to use copies, from you under this General Public
License will not have their licenses terminated so long as such parties
remain in full compliance.

  5. By copying, distributing or modifying the Program (or any work based
on the Program) you indicate your acceptance of this license to do so,
and all its terms and conditions.

cpanfile  view on Meta::CPAN

requires 'namespace::clean';
requires 'overload';
requires 'Package::Stash';
requires 'Path::Tiny';
requires 'Scalar::Util';
requires 'Socket';
requires 'Types::Standard';

# Only the tcp:// transport with tls => 1 loads this, and it is loaded at the
# moment that connection is opened. It brings in Net::SSLeay, which is XS
# compiled against libssl; requiring it would make this client unbuildable
# where there are no OpenSSL headers, for the sake of a transport that the
# unix:// default -- local Docker, rootless Podman -- never uses.
recommends 'IO::Socket::SSL';

on test => sub {
    requires 'Test::More';
    requires 'Path::Tiny';
    requires 'Exporter';
};

lib/API/Docker/API/Containers.pm  view on Meta::CPAN


=over

=item * C<stream> - Subscribe to what the container writes from the time of
the request onwards. Default B<0>, which is the engine's own default.
C<< stream => 1 >> on a container that is not running never returns; see
L</"The defaults follow the engine">

=item * C<logs> - Replay what the container has already written. Default
B<1>, so the call returns something without subscribing; combined with
C<< stream => 1 >> the replay comes first and then transitions seamlessly
into the live output. C<< logs => 0 >> without C<< stream => 1 >> is the
combination the engine refuses (400 on Podman)

=item * C<stdout> - Attach stdout. Default 1 (engine default: false)

=item * C<stderr> - Attach stderr. Default 1 (engine default: false)

=item * C<stdin> - Attach stdin. Sent as asked, but nothing can be written to
it here; see above

lib/API/Docker/Role/HTTP.pm  view on Meta::CPAN

        $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';

lib/API/Docker/Role/HTTP.pm  view on Meta::CPAN

        . ($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);

lib/API/Docker/Role/HTTP.pm  view on Meta::CPAN

#
#   '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.

lib/API/Docker/Role/HTTP.pm  view on Meta::CPAN

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

lib/API/Docker/Type/SwarmSpec/CAConfig/ExternalCA.pm  view on Meta::CPAN

package API::Docker::Type::SwarmSpec::CAConfig::ExternalCA;
# ABSTRACT: One entry of C<SwarmSpec.CAConfig.ExternalCAs>
our $VERSION = '0.004';
use API::Docker::Type;
use namespace::clean;


docker protocol => Str, enum => [qw( cfssl )];


docker url => Str, wire => 'URL';


docker options => { Str, Str };


docker ca_cert => Str, wire => 'CACert';

lib/API/Docker/Type/SwarmSpec/CAConfig/ExternalCA.pm  view on Meta::CPAN

version 0.004

=head1 DESCRIPTION

Generated from the inline C<items> schema of
C<SwarmSpec.CAConfig.ExternalCAs> in C<spec/v1.51.yaml>, which the swagger
leaves undescribed.

=head2 protocol

Protocol for communication with the external CA (currently only C<cfssl> is
supported). The daemon defaults it to cfssl.

=head2 url

URL where certificate signing requests should be sent. Serialised as C<URL>
-- spelled out, because deriving it from the Perl name would produce C<Url>.

=head2 options

An object with key/value pairs that are interpreted as protocol-specific
options for the external CA driver. B<The keys are the caller's data> and

t/tls.t  view on Meta::CPAN

  like $source, qr/\$self->tls\b/, 'the socket builder asks whether TLS is wanted';
  like $source, qr/\$self->cert_path\b/, 'and reads the certificate directory';
  like $source, qr/IO::Socket::SSL->new/, 'the tcp:// branch can be an SSL socket';
  like $source, qr/IO::Socket::INET->new/, 'and is still a plain one without TLS';
};

SKIP: {
  skip 'IO::Socket::SSL is not installed', 4 unless $HAVE_SSL;

  subtest 'verification is the default' => sub {
    my %ssl = client(tls => 1)->_ssl_options('dockerhost');

    is $ssl{SSL_verify_mode}, IO::Socket::SSL::SSL_VERIFY_PEER(),
      'the certificate chain is checked';
    is $ssl{SSL_verifycn_scheme}, 'http',
      'and so is the name on it: a valid certificate for another host is not '
      . 'this host';
    is $ssl{SSL_verifycn_name}, 'dockerhost', 'checked against the host asked for';
    is $ssl{SSL_hostname}, 'dockerhost', 'which is also sent as SNI';

    ok !exists $ssl{SSL_ca_file}, 'no ca file without a cert_path';
    ok !exists $ssl{SSL_cert_file}, 'and no client certificate';
  };

  subtest 'tls_insecure turns verification off, and only that' => sub {
    my %ssl = client(tls => 1, tls_insecure => 1)->_ssl_options('dockerhost');

    is $ssl{SSL_verify_mode}, IO::Socket::SSL::SSL_VERIFY_NONE(),
      'the chain is not checked';
    is $ssl{SSL_verifycn_scheme}, undef, 'nor the name';
    is $ssl{SSL_hostname}, 'dockerhost',
      'SNI is still sent: a terminator serving several names needs it either way';
  };

  subtest 'the cert.pem / key.pem / ca.pem layout' => sub {
    my $dir = Path::Tiny->tempdir;
    $dir->child($_)->spew('') for qw( ca.pem cert.pem key.pem );

    my %ssl = client(tls => 1, cert_path => "$dir")->_ssl_options('dockerhost');
    is $ssl{SSL_ca_file}, $dir->child('ca.pem') . '', 'ca.pem is the trust anchor';
    is $ssl{SSL_cert_file}, $dir->child('cert.pem') . '', 'cert.pem is sent';
    is $ssl{SSL_key_file}, $dir->child('key.pem') . '', 'with key.pem';
    is $ssl{SSL_verify_mode}, IO::Socket::SSL::SSL_VERIFY_PEER(),
      'and having certificates does not change the verification policy';

    my $ca_only = Path::Tiny->tempdir;
    $ca_only->child('ca.pem')->spew('');
    my %anchor = client(tls => 1, cert_path => "$ca_only")->_ssl_options('dockerhost');
    is $anchor{SSL_ca_file}, $ca_only->child('ca.pem') . '', 'ca.pem alone is used';
    ok !exists $anchor{SSL_cert_file},
      'and is a daemon this client verifies without authenticating to it';

    my $client_only = Path::Tiny->tempdir;
    $client_only->child($_)->spew('') for qw( cert.pem key.pem );
    my %pair = client(tls => 1, cert_path => "$client_only")->_ssl_options('dockerhost');
    ok !exists $pair{SSL_ca_file},
      'no ca.pem falls back to the system trust store rather than croaking';
    is $pair{SSL_cert_file}, $client_only->child('cert.pem') . '',
      'while the client certificate is still sent';
  };

  subtest 'the layouts that are mistakes' => sub {
    for my $half (['cert.pem', 'key.pem'], ['key.pem', 'cert.pem']) {
      my ($present, $missing) = @$half;
      my $dir = Path::Tiny->tempdir;
      $dir->child($present)->spew('');
      my $err = do {
        local $@;
        eval { client(tls => 1, cert_path => "$dir")->_ssl_options('dockerhost') };
        $@;
      };
      like $err, qr/\Q$missing\E missing/,
        "$present without $missing croaks, naming the half that is gone";
      like $err, qr/Both cert\.pem and key\.pem are needed, or neither/,
        'and says what a complete client certificate is';
    }

    my $err = do {
      local $@;
      eval { client(tls => 1, cert_path => '/no/such/certificate/directory')
        ->_ssl_options('dockerhost') };
      $@;
    };
    like $err, qr{cert_path /no/such/certificate/directory is not a directory},
      'a cert_path naming nothing croaks rather than connecting without the '
      . 'certificates the caller believes are in use';
  };
}

subtest 'IO::Socket::SSL is a recommended dependency, and says so when absent' => sub {
  # It is required at the moment the first TLS connection is opened, not at

t/tls.t  view on Meta::CPAN

  # hiding it from require rather than by uninstalling it.
  my $err = do {
    local %INC = %INC;
    delete $INC{'IO/Socket/SSL.pm'};
    local @INC = (sub {
      my (undef, $filename) = @_;
      die "Can't locate $filename in \@INC\n" if $filename eq 'IO/Socket/SSL.pm';
      return;
    }, @INC);
    local $@;
    eval { client(tls => 1)->_load_ssl };
    $@;
  };

  like $err, qr/needs IO::Socket::SSL/, 'the croak names the module';
  like $err, qr/cpanm IO::Socket::SSL/, 'and how to install it';
  like $err, qr/recommended rather than a required/,
    'and why it was not there already';
};

# ===========================================================================

t/tls_read.t  view on Meta::CPAN

#   three writes 0.15s apart      -> 3 on_chunk calls, spaced apart
#   100000 bytes over many records -> every byte arrives, none lost
#   delivered, then silent, read_timeout 1 -> Error::Timeout after ~1s,
#     phase 'read', summary delivered=1, the bytes already with the callback
#
# It matters here specifically because TLS is the one transport where a
# short read is the normal case -- one plaintext record (<= 16384 bytes,
# RFC 8446 5.1) per sysread -- so the "a short read is not an end of
# stream" rule this role depends on (see _pull in
# API::Docker::Role::HTTP) has no margin to be wrong in. And
# IO::Socket::SSL's own read/sysread split -- ssl_read_all on a blocking
# socket for read(), a single Net::SSLeay::read for sysread() -- is exactly
# the distinction karr k60 is built on: get the wrong one and either every
# read blocks until the daemon closes, or a stall never times out.
#
# Certificate: generated fresh per run rather than checked in under t/, the
# same choice t/tls.t already made for the same reason -- a stored
# certificate is deterministic but expires, and CERT_create costs single-
# digit milliseconds. Gated on IO::Socket::SSL the same way t/tls.t gates
# it: a recommended, not a required, dependency, so its absence is
# skip_all rather than a failure.



( run in 0.929 second using v1.01-cache-2.11-cpan-ad19def0cd9 )