@@ -64,6 +64,22 @@ sub check_existing {
64
64
return 0;
65
65
}
66
66
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
+
67
83
die (" usage: unpack_slsa_provenance <provenance.json> <dir>\n " ) unless @ARGV == 2;
68
84
my ($provenance_file , $dir ) = @ARGV ;
69
85
@@ -77,86 +93,150 @@ if ($provenance->{'payload'}) {
77
93
$provenance = MIME::Base64::decode_base64($provenance -> {' payload' });
78
94
$provenance = Build::SimpleJSON::parse($provenance );
79
95
}
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
+
80
100
my $predicate = $provenance -> {' predicate' };
81
101
die (" no predicate in provenance?\n " ) unless ref ($predicate ) eq ' HASH' ;
82
102
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
+
84
131
die (" no materials in predicate?\n " ) unless ref ($materials ) eq ' ARRAY' ;
85
132
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 ;
88
154
89
- my $configsource = $invocation -> {' configSource' };
90
- die (" no configSource in invocation?\n " ) unless ref ($configsource ) eq ' HASH' ;
155
+ $_ -> {' intent' } = $intent ;
156
+ $_ -> {' filename' } = $filename ;
157
+ }
91
158
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 ;
94
162
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' }};
96
169
97
170
$| = 1;
98
171
99
172
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 );
115
174
116
175
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' );
133
191
}
134
192
135
193
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
+ }
154
232
233
+ for (qw{ preinstall vminstall runscripts installonly noinstall} ) {
234
+ push @rpmlist , " $_ : @{$flags {$_ }}" if $flags {$_ };
235
+ }
155
236
writestr(" $dir /.build.rpmlist" , undef , join (" \n " , @rpmlist )." \n " );
156
237
157
238
my @params ;
158
- if (ref ($invocation -> {' parameters' }) eq ' HASH' ) {
159
- my $parameters = $invocation -> {' parameters' };
239
+ if ($parameters ) {
160
240
for my $k (sort keys %$parameters ) {
161
241
next unless defined $parameters -> {$k } && !ref ($parameters -> {$k });
162
242
push @params , ' release' , $parameters -> {$k } if $k eq ' release' ;
0 commit comments