API-Docker

 view release on metacpan or  search on metacpan

t/tls.t  view on Meta::CPAN

    'verification is still on: the CLI sets InsecureSkipVerify = !TLSVerify, '
    . 'so non-empty is encrypt AND verify';
};

subtest 'tls => 1 on a unix:// host croaks' => sub {
  # A Unix socket is a file, not a wire. Accepting the option would answer a
  # request for an encrypted transport with an unencrypted one, which is the
  # exact failure this ticket was raised about.
  my $err = do {
    local $@;
    eval { API::Docker->new(host => 'unix:///var/run/docker.sock', tls => 1) };
    $@;
  };
  like $err, qr/only meaningful for a tcp:\/\/ host/, 'it says which half is wrong';
  like $err, qr/unix:\/\/\/var\/run\/docker\.sock/, 'and names the host it got';
  like $err, qr/ at \S+ line \d+/, 'croaked, so the caller\'s line is named';
};

subtest 'tls_insecure without tls croaks' => sub {
  # Only reachable while tls is off, and the default is read from the
  # environment now, so the environment is what this subtest pins first.
  delete local $ENV{DOCKER_TLS_VERIFY};

  my $err = do {
    local $@;
    eval { client(tls_insecure => 1) };
    $@;
  };
  like $err, qr/tls_insecure => 1 without tls => 1/,
    'an option that could not do anything is refused, not accepted quietly';

  is client()->tls_insecure, 0, 'the default is off';
  is client(tls => 1, tls_insecure => 1)->tls_insecure, 1,
    'and it is settable alongside tls';
};

subtest 'cert_path on its own still transmits nothing' => sub {
  # Kept from the file this replaces, and still true: cert_path defaults from
  # DOCKER_CERT_PATH, which machines running the docker CLI export, so it must
  # not change anything for a client that never asked for TLS.
  delete local $ENV{DOCKER_TLS_VERIFY};
  my $docker = eval { client(cert_path => '/etc/docker/certs') };
  is $@, '', 'no croak, even though the path does not exist';
  is $docker->cert_path, '/etc/docker/certs', 'the value is kept';
  is $docker->tls, 0, 'and TLS is still off, so nothing reads it';

  local $ENV{DOCKER_CERT_PATH} = '/from/env';
  my $from_env = eval { API::Docker->new(api_version => '1.41') };
  is $@, '', 'a machine with DOCKER_CERT_PATH set still constructs';
  is $from_env->cert_path, '/from/env', 'defaulted from the environment';
};

# ===========================================================================
# The transport now reads both attributes
# ===========================================================================

subtest 'the transport consults tls and cert_path' => sub {
  # The inverse of the claim this replaces, which asserted that
  # API::Docker::Role::HTTP never mentions either attribute and that its
  # tcp:// branch is unconditionally a plain socket.
  my $source = path($INC{'API/Docker/Role/HTTP.pm'})->slurp_utf8;

  ok length($source) > 1000, 'the transport source was actually read';
  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') . '',



( run in 1.490 second using v1.01-cache-2.11-cpan-364913b4093 )