App-FuguVM
view release on metacpan or search on metacpan
t/fuguvm/mirror.t view on Meta::CPAN
close $fh;
my $mirror = _mirror(tempdir(CLEANUP => 1), keys_dir => $keys_dir);
is($mirror->key_path, "$keys_dir/openbsd-78-base.pub",
'key_path maps 7.8 to openbsd-78-base.pub in keys_dir');
# The share tree of this checkout also holds the 7.8 key, so the
# answer above proves that keys_dir wins over it.
ok(-f "$RealBin/../../share/fuguvm/signify/openbsd-78-base.pub",
'the share tree holds the shipped key');
}
# The shipped key of the default release resolves from the share tree
{
my $mirror = _mirror(tempdir(CLEANUP => 1));
like($mirror->key_path, qr{share/fuguvm/signify/openbsd-78-base\.pub$},
'key_path falls back to the share tree');
}
# An absent key is a diagnosed miss that names the file and each
# directory. Version 9.9 has no key in any directory.
{
my $keys_dir = tempdir(CLEANUP => 1);
my $cache_dir = tempdir(CLEANUP => 1);
my $mirror = _mirror($cache_dir,
version => '9.9', keys_dir => $keys_dir);
is($mirror->key_path, undef, 'key_path returns undef without a key');
like($mirror->error, qr/openbsd-99-base\.pub/, 'error names the file');
like($mirror->error, qr/\Q$keys_dir\E/, 'error names keys_dir');
like($mirror->error, qr{share/fuguvm/signify},
'error names the share directory');
# manifest stops on the key, before any fetch
is($mirror->manifest('release'), undef,
'manifest returns undef when the key does not resolve');
like($mirror->error, qr/openbsd-99-base\.pub/,
'and the reason is the key');
is(scalar @{ _cache($cache_dir)->list }, 0, 'and it fetched nothing');
}
# ensure returns the cached path with no fetch when the cache holds
# the file. The verification runs on the way into the cache, not on
# every read.
{
my $cache_dir = tempdir(CLEANUP => 1);
my $seeded = _seed($cache_dir, '7.8/arm64/base78.tgz', 'set bytes');
my $mirror = _mirror($cache_dir);
is($mirror->ensure('release', 'base78.tgz'), $seeded,
'ensure returns the cached path with no fetch');
}
# With verify set to 0, ensure stores the file and logs one warning,
# and the proof methods refuse: a method must not report a proof that
# it did not make.
{
my $cache_dir = tempdir(CLEANUP => 1);
my $mirror = _mirror($cache_dir, verify => 0);
my $warned = _capture_stderr(sub {
no warnings 'redefine';
local *App::FuguVM::Mirror::fetch =
sub ($, $) { _temp_file('unproven bytes') };
is($mirror->ensure('release', 'base78.tgz'),
_cache($cache_dir)->cache_path(
'https://cdn.openbsd.org/pub/OpenBSD/7.8/arm64/base78.tgz'),
'ensure with verify 0 stores the file');
});
like($warned, qr/unproven/i, 'and it logs one warning');
is($mirror->verify_file('release', 'base78.tgz', '/tmp/x'), undef,
'verify_file with verify 0 returns undef');
like($mirror->error, qr/verification is off/, 'with a reason');
is($mirror->manifest('release'), undef,
'manifest with verify 0 returns undef');
like($mirror->error, qr/verification is off/, 'with the same reason');
}
# The signify(1) subtests. Each one generates its own key pair, writes
# a manifest in the sha256(1) line form, signs it, and seeds the
# cache at the mirror paths. The manifests are then local, so the
# module fetches nothing.
my $SIGNIFY = _find_signify();
subtest 'the release manifest and the file proofs' => sub {
plan skip_all => 'signify(1) not available' if !defined $SIGNIFY;
my ($cache_dir, $keys_dir) = _signed_fixture(
release => { 'base78.tgz' => 'good bytes' });
my $mirror = _mirror($cache_dir, keys_dir => $keys_dir);
my $manifest = $mirror->manifest('release');
ok(defined $manifest, 'manifest returns the path for a good signature');
is($manifest, _cache($cache_dir)->cache_path(
'https://cdn.openbsd.org/pub/OpenBSD/7.8/arm64/SHA256'),
'and the path is the cached manifest');
is_deeply($mirror->manifest_names('release'), ['base78.tgz'],
'manifest_names lists the named files');
my $good = _write_file($cache_dir, 'good.local', 'good bytes');
is($mirror->verify_file('release', 'base78.tgz', $good), 1,
'verify_file returns 1 for a matching digest');
my $bad = _write_file($cache_dir, 'bad.local', 'other bytes');
is($mirror->verify_file('release', 'base78.tgz', $bad), undef,
'verify_file returns undef for a digest that does not match');
like($mirror->error, qr/base78\.tgz/, 'and the reason names the file');
is($mirror->verify_file('release', 'absent.tgz', $good), undef,
'verify_file returns undef for a name the manifest does not hold');
};
subtest 'a repeated identical manifest line changes nothing' => sub {
plan skip_all => 'signify(1) not available' if !defined $SIGNIFY;
# The real SHA256 of the OpenBSD 7.8 amd64 directory repeats the
# install image lines. The digest check must accept a duplicate
# whose digest is identical, and it must refuse a duplicate name
# with two digests.
my $work = tempdir(CLEANUP => 1);
t/fuguvm/mirror.t view on Meta::CPAN
sub _signed_fixture
{
my (%scopes) = @_;
my $cache_dir = tempdir(CLEANUP => 1);
my $keys_dir = tempdir(CLEANUP => 1);
my $work = tempdir(CLEANUP => 1);
# signify -G wants one basename for the pair, so the pair lands
# in the work directory and the public half moves to the release
# name.
system($SIGNIFY, '-G', '-n',
'-p', "$work/key.pub", '-s', "$work/key.sec") == 0
or die "signify -G failed\n";
require File::Copy;
File::Copy::copy("$work/key.pub", "$keys_dir/openbsd-78-base.pub")
or die "cannot copy the public key: $!\n";
for my $scope (sort keys %scopes) {
my $files = $scopes{$scope};
my $dir = $scope eq 'release' ? '7.8/arm64' : '7.8';
my $manifest = '';
for my $name (sort keys %$files) {
my $digest = Digest::SHA->new(256);
$digest->add($files->{$name});
$manifest .= sprintf "SHA256 (%s) = %s\n",
$name, $digest->hexdigest;
}
my $local = "$work/SHA256.$scope";
open my $fh, '>', $local or die $!;
print $fh $manifest;
close $fh;
system($SIGNIFY, '-S', '-s', "$work/key.sec",
'-m', $local, '-x', "$local.sig") == 0
or die "signify -S failed\n";
_seed($cache_dir, "$dir/SHA256", $manifest);
_seed($cache_dir, "$dir/SHA256.sig", _slurp("$local.sig"));
}
return ($cache_dir, $keys_dir);
}
# _slurp($path):
# The whole file as bytes.
sub _slurp
{
my ($path) = @_;
open my $fh, '<', $path or die "read $path: $!";
local $/;
my $bytes = <$fh>;
close $fh;
return $bytes;
}
# _capture_stderr($code):
# Run the code with the process default logger on standard
# error, capture the stream, and put the quiet default back.
sub _capture_stderr
{
my ($code) = @_;
my $dir = tempdir(CLEANUP => 1);
Fugu::TestLog->stderr;
open my $saved, '>&', \*STDERR or die $!;
open STDERR, '>', "$dir/err" or die $!;
$code->();
open STDERR, '>&', $saved or die $!;
close $saved;
Fugu::TestLog->quiet;
return _slurp("$dir/err");
}
( run in 1.355 second using v1.01-cache-2.11-cpan-5c0b1e786e0 )