Skip to content

Commit c5a7088

Browse files
vjeffreyclaude
andauthored
🐛 Fix remaining GCP permissions and add validation test (#7102)
* 🐛 Fix remaining GCP permissions and add validation test Regenerate gcp.permissions.json to apply the overrides from #7080 that were never reflected in the manifest. Also fix compute.firewallPolicies mapping (was networkFirewallPolicies), add org-level permission scoping, and add a test that validates the manifest against a known-good list verified with the GCP IAM queryTestablePermissions API. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * headers --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 937de0e commit c5a7088

File tree

3 files changed

+372
-208
lines changed

3 files changed

+372
-208
lines changed

providers-sdk/v1/util/permissions/permissions.go

Lines changed: 59 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,12 @@ import (
2121

2222
// PermissionManifest is the JSON output for a provider's permissions.
2323
type PermissionManifest struct {
24-
Provider string `json:"provider"`
25-
Version string `json:"version"`
26-
GeneratedAt string `json:"generated_at"`
27-
Permissions []string `json:"permissions"`
28-
Details []PermissionDetail `json:"details"`
24+
Provider string `json:"provider"`
25+
Version string `json:"version"`
26+
GeneratedAt string `json:"generated_at"`
27+
Permissions []string `json:"permissions"`
28+
Details []PermissionDetail `json:"details"`
29+
OrgLevelPermissions []string `json:"org_level_permissions,omitempty"`
2930
}
3031

3132
// PermissionDetail describes a single extracted API call and its mapped permission.
@@ -34,6 +35,7 @@ type PermissionDetail struct {
3435
Service string `json:"service"`
3536
Action string `json:"action"`
3637
SourceFile string `json:"source_file"`
38+
Scope string `json:"scope,omitempty"`
3739
}
3840

3941
func main() {
@@ -71,16 +73,35 @@ func main() {
7173
os.Exit(0)
7274
}
7375

74-
// Deduplicate and sort permissions
76+
// Mark org-level permissions (GCP only)
77+
if providerName == "gcp" {
78+
for i := range details {
79+
if gcpOrgLevelPermissions[details[i].Permission] {
80+
details[i].Scope = "org"
81+
}
82+
}
83+
}
84+
85+
// Deduplicate and sort permissions, separating org-level ones
7586
permSet := map[string]bool{}
87+
orgPermSet := map[string]bool{}
7688
for _, d := range details {
77-
permSet[d.Permission] = true
89+
if d.Scope == "org" {
90+
orgPermSet[d.Permission] = true
91+
} else {
92+
permSet[d.Permission] = true
93+
}
7894
}
7995
permissions := make([]string, 0, len(permSet))
8096
for p := range permSet {
8197
permissions = append(permissions, p)
8298
}
8399
sort.Strings(permissions)
100+
orgPermissions := make([]string, 0, len(orgPermSet))
101+
for p := range orgPermSet {
102+
orgPermissions = append(orgPermissions, p)
103+
}
104+
sort.Strings(orgPermissions)
84105

85106
// Sort details for stable output
86107
sort.Slice(details, func(i, j int) bool {
@@ -103,11 +124,12 @@ func main() {
103124
}
104125

105126
manifest := PermissionManifest{
106-
Provider: providerName,
107-
Version: version,
108-
GeneratedAt: deterministicTimestamp(),
109-
Permissions: permissions,
110-
Details: details,
127+
Provider: providerName,
128+
Version: version,
129+
GeneratedAt: deterministicTimestamp(),
130+
Permissions: permissions,
131+
Details: details,
132+
OrgLevelPermissions: orgPermissions,
111133
}
112134

113135
if outputPath == "" {
@@ -919,6 +941,10 @@ var gcpPermissionOverrides = map[string]map[string]string{
919941
"backupdr": {
920942
"ListDataSources": "backupdr.bvdataSources.list",
921943
},
944+
"compute": {
945+
"NetworkFirewallPolicies.Get": "compute.firewallPolicies.get",
946+
"NetworkFirewallPolicies.List": "compute.firewallPolicies.list",
947+
},
922948
"recommender": {
923949
// recommender.recommendations.list is not a real permission; the Recommender
924950
// API uses type-specific permissions (e.g., recommender.iamPolicyRecommendations.list).
@@ -927,6 +953,19 @@ var gcpPermissionOverrides = map[string]map[string]string{
927953
},
928954
}
929955

956+
// gcpOrgLevelPermissions are permissions that only apply at the organization
957+
// level, not the project level. They are placed in the org_level_permissions
958+
// section of the manifest instead of the main permissions list.
959+
var gcpOrgLevelPermissions = map[string]bool{
960+
"resourcemanager.folders.get": true,
961+
"resourcemanager.folders.list": true,
962+
"resourcemanager.folders.search": true,
963+
"resourcemanager.organizations.get": true,
964+
"resourcemanager.organizations.getIamPolicy": true,
965+
"resourcemanager.projects.list": true,
966+
"resourcemanager.projects.search": true,
967+
}
968+
930969
// gcpSkipMethods lists method names that match isGCPAPIMethod patterns but are
931970
// actually protobuf getter methods or internal helpers, not real API calls.
932971
var gcpSkipMethods = map[string]bool{
@@ -1009,6 +1048,14 @@ func gcpRESTToPermission(service, resource, method string) string {
10091048
if resource == "" {
10101049
return ""
10111050
}
1051+
1052+
// Check for explicit overrides using "Resource.Method" as the key
1053+
if overrides, ok := gcpPermissionOverrides[service]; ok {
1054+
if perm, ok := overrides[resource+"."+method]; ok {
1055+
return perm
1056+
}
1057+
}
1058+
10121059
verb := ""
10131060
switch method {
10141061
case "List", "AggregatedList", "Aggregated", "Pages", "Search":

0 commit comments

Comments
 (0)