Skip to content

Commit

Permalink
[backend] implement cosign signature creation for external registries
Browse files Browse the repository at this point in the history
  • Loading branch information
mlschroe committed Jan 19, 2022
1 parent a2140d8 commit ff76b74
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 5 deletions.
21 changes: 18 additions & 3 deletions src/backend/BSPublisher/Container.pm
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ sub upload_all_containers {
for my $joinp (sort keys %todo) {
my @tags = @{$todo{$joinp}};
my @containerinfos = map {$containers->{$_}} @{$todo_p{$joinp}};
my ($digest, @refs) = upload_to_registry($registry, \@containerinfos, $repository, \@tags);
my ($digest, @refs) = upload_to_registry($registry, \@containerinfos, $repository, \@tags, $projid, $signargs, $pubkey);
add_notary_upload($notary_uploads, $registry, $repository, $digest, \@tags);
$containerdigests .= $digest;
push @{$allrefs{$_}}, @refs for @{$todo_p{$joinp}};
Expand Down Expand Up @@ -347,15 +347,14 @@ sub reconstruct_container {
containerinfos - array of containers to upload (more than one for multiarch)
repository - registry repository name
tags - array of tags to upload to
notary_uploads - hash to store notary information
Returns:
containerdigests + public references to uploaded containers
=cut

sub upload_to_registry {
my ($registry, $containerinfos, $repository, $tags) = @_;
my ($registry, $containerinfos, $repository, $tags, $projid, $signargs, $pubkey) = @_;

return unless @{$containerinfos || []} && @{$tags || []};

Expand Down Expand Up @@ -401,6 +400,22 @@ sub upload_to_registry {
my @opts = map {('-t', $_)} @$tags;
push @opts, '-m' if @uploadfiles > 1; # create multi arch container
push @opts, '-B', $blobdir if $blobdir;
my $cosign = $registry->{'cosign'};
$cosign = $cosign->($repository, $projid) if $cosign && ref($cosign) eq 'CODE';
if (defined($pubkey) && $cosign) {
my $gun = $registry->{'notary_gunprefix'} || $registry->{'server'};
$gun =~ s/^https?:\/\///;
$gun .= "/$repository";
my @signargs;
push @signargs, '--project', $projid if $BSConfig::sign_project;
push @signargs, @{$signargs || []};
my $pubkeyfile = "$uploaddir/publisher.$$.pubkey";
push @tempfiles, $pubkeyfile;
mkdir_p($uploaddir);
unlink($pubkeyfile);
writestr($pubkeyfile, undef, $pubkey);
push @opts, '--cosign', '-p', $pubkeyfile, '-G', $gun, @signargs;
}
my @cmd = ("$INC[0]/bs_regpush", '--dest-creds', '-', @opts, '-F', $containerdigestfile, $registryserver, $repository, @uploadfiles);
print "Uploading to registry: @cmd\n";
my $result = BSPublisher::Util::qsystem('echo', "$registry->{user}:$registry->{password}\n", 'stdout', '', @cmd);
Expand Down
93 changes: 91 additions & 2 deletions src/backend/bs_regpush
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ my @tags;
my $blobdir;
my $oci;

my $cosign;
my $gun;
my @signcmd;
my $pubkeyfile;

my $rekorserver;

my $registryserver;
my $repository;
my @tarfiles;
Expand Down Expand Up @@ -129,6 +136,11 @@ sub blob_upload {
return $blobid;
}

sub blob_upload_content {
my ($blobid, $content) = @_;
return blob_upload($blobid, { 'data' => $content, 'size' => length($content) });
}

sub blob_download {
my ($blobid, $filename) = @_;
my $stdout_receiver = sub {
Expand Down Expand Up @@ -355,13 +367,15 @@ sub list_tag {
}

sub tags_from_digestfile {
my ($add_sig_tags) = @_;
return () unless $digestfile;
my @ret;
local *DIG;
open(DIG, '<', $digestfile) || die("$digestfile: $!\n");
while (<DIG>) {
chomp;
next if /^#/ || /^\s*$/;
push @ret, "$1-$2.sig" if $add_sig_tags && /^([a-z0-9]+):([a-f0-9]+) (\d+)/;
next if /^([a-z0-9]+):([a-f0-9]+) (\d+)\s*$/; # ignore anonymous images
die("bad line in digest file\n") unless /^([a-z0-9]+):([a-f0-9]+) (\d+) (.+?)\s*$/;
push @ret, $4;
Expand Down Expand Up @@ -468,6 +482,21 @@ while (@ARGV) {
} elsif ($ARGV[0] eq '--oci') {
$oci = 1;
shift @ARGV;
} elsif ($ARGV[0] eq '--cosign') {
$cosign = 1;
shift @ARGV;
} elsif ($ARGV[0] eq '--rekor') {
(undef, $rekorserver) = splice(@ARGV, 0, 2);
} elsif ($ARGV[0] eq '-G') {
(undef, $gun) = splice(@ARGV, 0, 2);
} elsif ($ARGV[0] eq '-p') {
(undef, $pubkeyfile) = splice(@ARGV, 0, 2);
} elsif ($ARGV[0] eq '--dest-creds') {
$dest_creds = BSBearer::get_credentials($ARGV[1]);
splice(@ARGV, 0, 2);
} elsif ($ARGV[0] eq '-P' || $ARGV[0] eq '--project' || $ARGV[0] eq '-u' || $ARGV[0] eq '--signtype' || $ARGV[0] eq '-h') {
my @signopts = splice(@ARGV, 0, 2);
push @signcmd, @signopts unless $signopts[0] eq '-h';
} else {
last;
}
Expand Down Expand Up @@ -511,7 +540,7 @@ if ($delete_mode || $delete_except_mode) {
die("cannot do both delete and delete-except\n") if $delete_mode && $delete_except_mode;
my %tags;
$tags{$_} = 1 for @tags;
$tags{$_} = 1 for tags_from_digestfile();
$tags{$_} = 1 for tags_from_digestfile($delete_except_mode ? 1 : 0);

if ($delete_mode) {
for my $tag (sort keys %tags) {
Expand All @@ -525,6 +554,19 @@ if ($delete_mode || $delete_except_mode) {
exit;
}

if ($cosign) {
require BSConfiguration;
require BSConSign;
require BSPGP;
require BSX509 if $rekorserver;
require BSRekor if $rekorserver;
die("need a pubkey for cosign signature creation\n") unless $pubkeyfile;
die("need a gun for cosign signature creation\n") unless $gun;
die("sign program is not configured!\n") unless $BSConfig::sign;
unshift @signcmd, $BSConfig::sign;
}


die("No tar file to upload?\n") if !@tarfiles;
die("more than one tar file specified\n") if @tarfiles > 1 && !$multiarch;

Expand All @@ -551,6 +593,7 @@ if ($use_image_tags && @tarfiles > 1) {
# use oci types if we have a helm chart
$oci = 1 if grep {/\.helminfo$/} @tarfiles;

my %digests_to_sign;
my @multimanifests;
my %multiplatforms;
for my $tarfile (@tarfiles) {
Expand Down Expand Up @@ -649,13 +692,15 @@ for my $tarfile (@tarfiles) {
'layers' => \@layer_data,
};
my $mani_json = BSContar::create_dist_manifest($mani);
my $mani_id = 'sha256:'.Digest::SHA::sha256_hex($mani_json);
$digests_to_sign{$mani_id} = 1;

if ($multiarch) {
manifest_upload_tags($mani_json, undef, $mediaType); # upload anonymous image
my $multimani = {
'mediaType' => $mediaType,
'size' => length($mani_json),
'digest' => 'sha256:'.Digest::SHA::sha256_hex($mani_json),
'digest' => $mani_id,
'platform' => {'architecture' => $goarch, 'os' => $goos},
};
$multimani->{'platform'}->{'variant'} = $govariant if $govariant;
Expand All @@ -673,6 +718,50 @@ if ($multiarch) {
'manifests' => \@multimanifests,
};
my $mani_json = BSContar::create_dist_manifest_list($mani);
my $mani_id = 'sha256:'.Digest::SHA::sha256_hex($mani_json);
$digests_to_sign{$mani_id} = 1;
manifest_upload_tags($mani_json, \@tags, $mediaType);
}

if ($cosign && %digests_to_sign) {
my $creator = 'OBS';
my $gpgpubkey = BSPGP::unarmor(readstr($pubkeyfile));
my $pubkeyid = BSPGP::pk2fingerprint($gpgpubkey);
my $cosigncookie = Digest::SHA::sha256_hex("$creator/$pubkeyid/$gun");
for my $digest (sort keys %digests_to_sign) {
my $sig_tag = "$digest.sig";
$sig_tag =~ s/:/-/;
my ($sig_mani, $sig_maniid, $sig_mani_json) = get_manifest_for_tag($sig_tag);
if ($sig_mani && @{$sig_mani->{'layers'} || []} == 1 && $BSConSign::mt_cosign && $sig_mani->{'layers'}->[0]->{'mediaType'} eq $BSConSign::mt_cosign) {
my $annotations = $sig_mani->{'layers'}->[0]->{'annotations'} || {};
next if ($annotations->{'org.open-build-service.cosign.cookie'} || '') eq $cosigncookie;
}
print "creating cosign signature for $gun $digest\n";
my $signfunc = sub { BSUtil::xsystem($_[0], @signcmd, '-O', '-h', 'sha256') };
my $annotations = { 'org.open-build-service.cosign.cookie' => $cosigncookie };
my ($config, $payload_layer, $payload, $sig) = BSConSign::createcosign($signfunc, $digest, $gun, $creator, undef, $annotations);
my $config_blobid = blob_upload_content('sha256:'.Digest::SHA::sha256_hex($config), $config);
my $payload_blobid = blob_upload_content('sha256:'.Digest::SHA::sha256_hex($payload), $payload);
die unless $payload_blobid eq $payload_layer->{'digest'};
my $config_data = {
'mediaType' => $oci ? $BSContar::mt_oci_config : $BSContar::mt_docker_config,
'size' => length($config),
'digest' => $config_blobid,
};
my $mediaType = $oci ? $BSContar::mt_oci_manifest : $BSContar::mt_docker_manifest;
my $mani = {
'schemaVersion' => 2,
'mediaType' => $mediaType,
'config' => $config_data,
'layers' => [ $payload_layer ],
};
my $mani_json = BSContar::create_dist_manifest_list($mani);
manifest_upload($mani_json, $sig_tag, $mediaType);
if ($rekorserver) {
print "uploading cosign signature to $rekorserver\n";
my $sslpubkey = BSX509::keydata2pubkey(BSPGP::pk2keydata($gpgpubkey));
BSRekor::upload_hashedrekord($rekorserver, $payload_layer->{'digest'}, $sslpubkey, $sig);
}
}
}

0 comments on commit ff76b74

Please sign in to comment.