Skip to content

Commit 180928d

Browse files
committed
Support slsa v1 in unpack_slsa_provenance
1 parent b65e6f3 commit 180928d

File tree

2 files changed

+145
-60
lines changed

2 files changed

+145
-60
lines changed

build

+6-1
Original file line numberDiff line numberDiff line change
@@ -1581,13 +1581,18 @@ for RECIPEPATH in "${RECIPEFILES[@]}" ; do
15811581
recipe=*) RECIPEFILE="${k#*=}" ;;
15821582
release=*) test -z "$RELEASE" && RELEASE="${k#*=}" ;;
15831583
debuginfo=*) BUILD_DEBUG=1 ;;
1584+
disturl=*) test -z "$DISTURL" && DISTURL="${k#*=}" ;;
1585+
vcs=*) test -z "$BUILD_VCSURL" && BUILD_VCSURL="${k#*=}" ;;
1586+
buildflavor=*) test -z "$BUILD_FLAVOR" && BUILD_FLAVOR="${k#*=}" ;;
15841587
esac
15851588
done < .build.params
15861589
test -z "$RECIPEFILE" && cleanup_and_exit 1 "recipe not set in build parameters"
15871590
RECIPEPATH="$SRCDIR/$RECIPEFILE"
15881591
RPMLIST="--rpmlist $MYSRCDIR/.build.rpmlist"
15891592
BUILD_RPMS=
1590-
BUILD_DIST="$SRCDIR/.build.config"
1593+
rm -f "$BUILD_ROOT/.build.config"
1594+
mv "$SRCDIR/.build.config" "$BUILD_ROOT/.build.config"
1595+
BUILD_DIST="$BUILD_ROOT/.build.config"
15911596
repos=()
15921597
fi
15931598

unpack_slsa_provenance

+139-59
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,22 @@ sub check_existing {
6464
return 0;
6565
}
6666

67+
sub download_materials {
68+
my ($materials, $dir, $subdir) = @_;
69+
if ($subdir) {
70+
mkdir("$dir/$subdir") || die("mkdir $dir/$subdir: $!\n") unless -d "$dir/$subdir";
71+
$subdir .= "/";
72+
}
73+
$subdir ||= '';
74+
for my $material (@{$materials || []}) {
75+
my $fn = $material->{'filename'};
76+
$fn = '.build.config' if $material->{'intent'} eq 'buildconfig';
77+
my $digest = material2digest($material);
78+
next if check_existing("$dir/$subdir$fn", $digest);
79+
Build::Download::download($material->{'uri'}, "$dir/$subdir$fn", undef, 'digest' => $digest);
80+
}
81+
}
82+
6783
die("usage: unpack_slsa_provenance <provenance.json> <dir>\n") unless @ARGV == 2;
6884
my ($provenance_file, $dir) = @ARGV;
6985

@@ -77,86 +93,150 @@ if ($provenance->{'payload'}) {
7793
$provenance = MIME::Base64::decode_base64($provenance->{'payload'});
7894
$provenance = Build::SimpleJSON::parse($provenance);
7995
}
96+
my $predicate_type = $provenance->{'predicateType'};
97+
die("no predicateType in provenance?\n") unless $predicate_type;
98+
die("unsupported predicate type '$predicate_type'\n") unless $predicate_type eq 'https://slsa.dev/provenance/v0.1' || $predicate_type eq 'https://slsa.dev/provenance/v0.2' || $predicate_type eq 'https://slsa.dev/provenance/v1';
99+
80100
my $predicate = $provenance->{'predicate'};
81101
die("no predicate in provenance?\n") unless ref($predicate) eq 'HASH';
82102

83-
my $materials = $predicate->{'materials'};
103+
my ($materials, $recipefile, $parameters);
104+
105+
if ($predicate_type eq 'https://slsa.dev/provenance/v1') {
106+
my $build_definition = $predicate->{'buildDefinition'};
107+
die("no buildDefinition in predicate?\n") unless ref($build_definition) eq 'HASH';
108+
my $build_type = $build_definition->{'buildType'} || '';
109+
die("Unsupported buildType '$build_type'\n") unless $build_type eq 'https://open-build-service.org/worker';
110+
my $external_parameters = $build_definition->{'externalParameters'};
111+
die("no externalParameters in buildDefinition?\n") unless ref($build_definition) eq 'HASH';
112+
$recipefile = $external_parameters->{'recipeFile'};
113+
die("no recipeFile in externalParameters?\n") unless defined($recipefile) && ref($recipefile) eq '';
114+
$materials = $build_definition->{'resolvedDependencies'};
115+
$parameters = $external_parameters;
116+
} else {
117+
my $build_type = $predicate->{'buildType'} || '';
118+
die("Unsupported buildType '$build_type'\n") unless $build_type eq 'https://open-build-service.org/worker';
119+
$materials = $predicate->{'materials'};
120+
my $invocation = $predicate->{'invocation'};
121+
die("no invocation in predicate?\n") unless ref($invocation) eq 'HASH';
122+
my $configsource = $invocation->{'configSource'};
123+
die("no configSource in invocation?\n") unless ref($configsource) eq 'HASH';
124+
$recipefile = $configsource->{'entryPoint'};
125+
die("no entryPoint in configSource?\n") unless defined($recipefile) && ref($recipefile) eq '';
126+
$parameters = $invocation->{'parameters'};
127+
die("bad parameters in invocation?\n") unless !$parameters || ref($parameters) eq 'HASH';
128+
}
129+
130+
84131
die("no materials in predicate?\n") unless ref($materials) eq 'ARRAY';
85132

86-
my $invocation = $predicate->{'invocation'};
87-
die("no invocation in predicate?\n") unless ref($invocation) eq 'HASH';
133+
# add name/intent to all materials
134+
for (@$materials) {
135+
my $intent = $_->{'intent'};
136+
$intent = $_->{'annotations'}->{'intent'} if $_->{'annotations'};
137+
if (!$intent && $_->{'uri'}) {
138+
# autodetect sources/buildconfig
139+
$intent = 'source' unless $_->{'uri'} =~ /\/_slsa\//;
140+
$intent = 'buildconfig' if $_->{'uri'} =~ /\/_slsa\// && $_->{'uri'} =~ /\/_config\/[^\/]+$/
141+
}
142+
$intent ||= 'buildenv';
143+
die("unknown intent in material: '$intent'\n") unless $intent eq 'buildenv' || $intent eq 'buildconfig' || $intent eq 'source' || $intent eq 'repos' || $intent eq 'containers';
144+
145+
my $filename = $_->{'name'};
146+
if (!$filename && $_->{'uri'}) {
147+
$filename = $_->{'uri'};
148+
$filename =~ s/%([a-fA-F0-9]{2})/chr(hex($1))/sge;
149+
$filename =~ s/\/[^\/]+$// if $filename =~ /\/_slsa\//;
150+
$filename =~ s/.*\///;
151+
}
152+
die("cannot determine file name for material\n") unless defined $filename;
153+
die("bad file name $filename\n") if $filename eq '.' || $filename eq '..' || $filename eq '' || $filename =~ /[\000-\037\/]/ || $filename =~ /^\.build\./s;
88154

89-
my $configsource = $invocation->{'configSource'};
90-
die("no configSource in invocation?\n") unless ref($configsource) eq 'HASH';
155+
$_->{'intent'} = $intent;
156+
$_->{'filename'} = $filename;
157+
}
91158

92-
my $recipefile = $configsource->{'entryPoint'};
93-
die("no entryPoint in configSource?\n") unless defined($recipefile) && ref($recipefile) eq '';
159+
# classify materials by intent
160+
my %materials;
161+
push @{$materials{$_->{'intent'}}}, $_ for @$materials;
94162

95-
my @rpmlist;
163+
# check for missing materials
164+
for my $needed_intent ('source', 'buildconfig', 'buildenv') {
165+
die("missing materials for '$needed_intent'\n") unless $materials{$needed_intent};
166+
}
167+
die("more than one buildconfig material\n") if @{$materials{'buildconfig'}} != 1;
168+
die("recipefile $recipefile is missing from source\n") unless grep {$_->{'filename'} eq $recipefile} @{$materials{'source'}};
96169

97170
$| = 1;
98171

99172
print "fetching sources\n";
100-
my $recipe_found;
101-
for my $material (@$materials) {
102-
my $uri = $material->{'uri'};
103-
next if $uri =~ /\/_slsa\//;
104-
my $digest = material2digest($material);
105-
my $fn = $uri;
106-
$fn =~ s/%([a-fA-F0-9]{2})/chr(hex($1))/sge;
107-
$fn =~ s/.*\///;
108-
die("bad file name $fn\n") if $fn eq '.' || $fn eq '..' || $fn eq '';
109-
die("bad file name $fn\n") if $fn =~ /^\.build\./;
110-
$recipe_found = 1 if $fn eq $recipefile;
111-
next if check_existing("$dir/$fn", $digest);
112-
Build::Download::download($uri, "$dir/$fn", undef, 'digest' => $digest);
113-
}
114-
die("recipefile $recipefile is missing from source\n") unless $recipe_found;
173+
download_materials($materials{'source'}, $dir);
115174

116175
print "fetching build environment\n";
117-
mkdir("$dir/.build.binaries") || die("mkdir $dir/.build.binaries: $!\n") unless -d "$dir/.build.binaries";
118-
for my $material (@$materials) {
119-
my $uri = $material->{'uri'};
120-
next unless $uri =~ /\/_slsa\//;
121-
next if $uri =~ /\/_config\/[^\/]+$/;
122-
my $digest = material2digest($material);
123-
my $fn = $uri;
124-
$fn =~ s/%([a-fA-F0-9]{2})/chr(hex($1))/sge;
125-
$fn =~ s/\/[^\/]+$//;
126-
$fn =~ s/.*\///;
127-
die("bad file name $fn\n") if $fn eq '.' || $fn eq '..' || $fn eq '';
128-
if ($fn =~ /^(.*)\.rpm$/) {
129-
push @rpmlist, "$1 $dir/.build.binaries/$fn";
130-
}
131-
next if check_existing("$dir/.build.binaries/$fn", $digest);
132-
Build::Download::download($uri, "$dir/.build.binaries/$fn", undef, 'digest' => $digest);
176+
download_materials($materials{'buildenv'}, $dir, '.build.binaries');
177+
178+
if ($materials{'sysroot'}) {
179+
print "fetching sysroot binaries\n";
180+
download_materials($materials{'buildenv'}, $dir, '.sysroot.binaries');
181+
}
182+
183+
if ($materials{'repos'}) {
184+
print "fetching repository binaries\n";
185+
download_materials($materials{'repos'}, $dir, 'repos');
186+
}
187+
188+
if ($materials{'containers'}) {
189+
print "fetching containers\n";
190+
download_materials($materials{'containers'}, $dir, 'containers');
133191
}
134192

135193
print "fetching build config\n";
136-
for my $material (@$materials) {
137-
my $uri = $material->{'uri'};
138-
next unless $uri =~ /\/_slsa\//;
139-
next unless $uri =~ /\/_config\/[^\/]+$/;
140-
my $digest = material2digest($material);
141-
next if check_existing("$dir/.build.config", $digest);
142-
Build::Download::download($uri, "$dir/.build.config", undef, 'digest' => $digest);
143-
}
144-
145-
# parse the config to get preinstall/vminstall/runscripts information
146-
my $bconf = Build::read_config('noarch', "$dir/.build.config");
147-
die("cannot expand preinstalls\n") if $bconf->{'expandflags:preinstallexpand'};
148-
my @preinstalls = Build::get_preinstalls($bconf);
149-
my @vminstalls = Build::get_vminstalls($bconf);
150-
my @runscripts = Build::get_runscripts($bconf);
151-
push @rpmlist, "preinstall: @preinstalls";
152-
push @rpmlist, "vminstall: @vminstalls";
153-
push @rpmlist, "runscripts: @runscripts";
194+
download_materials($materials{'buildconfig'}, $dir);
195+
196+
my %flags;
197+
if ($predicate_type eq 'https://slsa.dev/provenance/v1') {
198+
# get preinstall/vminstall/runscripts information from the annotations
199+
for my $material (@{$materials{'buildenv'}}) {
200+
my $flags = ($material->{'annotations'} || {})->{'flags'};
201+
next unless $flags;
202+
my $fn = $material->{'filename'};
203+
my $n;
204+
$n = $1 if $fn =~ /^(.*)\.(?:rpm|deb|pkg\.tar\.gz|pkg\.tar\.xz|pkg\.tar.zst)$/;
205+
next unless $n;
206+
push @{$flags{$_}}, $n for split(',', $flags);
207+
}
208+
} else {
209+
# parse the config to get preinstall/vminstall/runscripts information
210+
my $bconf = Build::read_config('noarch', "$dir/.build.config");
211+
die("cannot expand preinstalls\n") if $bconf->{'expandflags:preinstallexpand'};
212+
$flags{'preinstall'} = [ Build::get_preinstalls($bconf) ];
213+
$flags{'vminstall'} = [ Build::get_vminstalls($bconf) ];
214+
$flags{'runscripts'} = [ Build::get_runscripts($bconf) ];
215+
}
216+
217+
# add all buildenv materials to the rpmlist
218+
my @rpmlist;
219+
for my $material (@{$materials{'buildenv'} || []}) {
220+
my $fn = $material->{'filename'};
221+
my $n;
222+
$n = $1 if $fn =~ /^(.*)\.(?:rpm|deb|pkg\.tar\.gz|pkg\.tar\.xz|pkg\.tar.zst)$/;
223+
push @rpmlist, "$n $dir/.build.binaries/$fn" if $n;
224+
}
225+
for my $material (@{$materials{'sysroot'} || []}) {
226+
my $fn = $material->{'filename'};
227+
my $n;
228+
$n = $1 if $fn =~ /^(.*)\.(?:rpm|deb|pkg\.tar\.gz|pkg\.tar\.xz|pkg\.tar.zst)$/;
229+
next unless $n;
230+
push @rpmlist, "sysroot: $n $dir/.sysroot.binaries/$fn" if $n;
231+
}
154232

233+
for (qw{preinstall vminstall runscripts installonly noinstall}) {
234+
push @rpmlist, "$_: @{$flags{$_}}" if $flags{$_};
235+
}
155236
writestr("$dir/.build.rpmlist", undef, join("\n", @rpmlist)."\n");
156237

157238
my @params;
158-
if (ref($invocation->{'parameters'}) eq 'HASH') {
159-
my $parameters = $invocation->{'parameters'};
239+
if ($parameters) {
160240
for my $k (sort keys %$parameters) {
161241
next unless defined $parameters->{$k} && !ref($parameters->{$k});
162242
push @params, 'release', $parameters->{$k} if $k eq 'release';

0 commit comments

Comments
 (0)