Permissions parser from GCP official documentation written in Go
Features:
- WaitGroups implementation (default: groups of 10 goroutines)
- returns permissions in a slice of strings
Optional features:
- write permissions to file
- read permissions from file
Code architecture and high-level documentation:
Function perm_url_func parses https://cloud.google.com/iam/docs/roles-permissions/
and collects all the roles and permissions URLs, such as https://cloud.google.com/iam/docs/roles-permissions/accessapproval
.
Function role_parsing_func is optional
and parses all the available roles from each roles and permissions URLs.
Function perm_collection_func collects all permissions from a given roles and permissions URL and returns these in a slice of strings.
Function all_permissions_func orchestrates the collection of permissions from roles and permissions URLs and returns a slice of strings with all permissions.
Source Code
package main
import (
"fmt"
"net/http"
"net/url"
"strings"
"sync"
"golang.org/x/net/html"
)
// Google cloud base URL
var gc_base_url string = "https://cloud.google.com"
// Google cloud permissions URI
var gc_permissions_uri string = "/iam/docs/roles-permissions/"
// helper function to search for specific class in HTML
func hasClass(attrs []html.Attribute, className string) bool {
for _, attr := range attrs {
if attr.Key == "class" {
// Split class attribute by spaces and look for match
classes := strings.Fields(attr.Val)
for _, c := range classes {
if c == className {
return true
}
}
}
}
return false
}
// returns a slice of URLs (strings)
// for example: https://cloud.google.com/iam/docs/roles-permissions/accessapproval
func perm_url_func() []string {
gc_permissions_url := gc_base_url + gc_permissions_uri
req, err := http.NewRequest("GET", gc_permissions_url, nil)
if err != nil {
fmt.Println("[-] http.NewRequest error in perm_url_func")
}
req.Header.Set("User-Agent", " Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36")
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Println("[-] http.DefaultClient.Do error in perm_url_func")
}
defer resp.Body.Close()
doc, err := html.Parse(resp.Body)
if err != nil {
fmt.Println("[-] html.Parse error in perm_url_func")
}
var roles_and_permissions_urls []string
var href_parser_func func(*html.Node)
href_parser_func = func(n *html.Node) {
if n.Type == html.ElementNode && n.Data == "a" {
for _, attr := range n.Attr {
if attr.Key == "href" && strings.HasPrefix(attr.Val, gc_permissions_uri) {
roles_and_permissions_urls = append(roles_and_permissions_urls, gc_base_url+attr.Val)
}
}
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
href_parser_func(c)
}
}
href_parser_func(doc)
return roles_and_permissions_urls
}
// function parsing roles from HTML
func role_parsing_func(doc *html.Node, pattern_search string) []string {
var roles []string
var href_parser_func func(*html.Node)
href_parser_func = func(n *html.Node) {
if n.Type == html.ElementNode && n.Data == "a" {
for _, attr := range n.Attr {
if attr.Key == "href" && strings.HasPrefix(attr.Val, pattern_search) {
parts := strings.SplitN(attr.Val, "#", 2)
if len(parts) == 2 {
// check if element is already in
roles = append(roles, parts[1])
}
}
}
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
href_parser_func(c)
}
}
href_parser_func(doc)
// remove duplicate roles
uniqueMap := make(map[string]bool)
var dedup_roles []string
for _, item := range roles {
if !uniqueMap[item] {
uniqueMap[item] = true
dedup_roles = append(dedup_roles, item)
}
}
return dedup_roles
}
// returns a slice of permissions (strings)
func perm_collection_func(perm_url string) []string {
_, err := url.Parse(perm_url)
if err != nil {
fmt.Println("[-] url.Parse error in perm_collection_func")
}
//pattern_search := parsedURL.Path + "#"
//fmt.Printf("[debug] pattern_search: %s\n", pattern_search)
resp, err := http.Get(perm_url)
if err != nil {
fmt.Println("[-] http.Get error in perm_collection_func")
}
defer resp.Body.Close()
doc, err := html.Parse(resp.Body)
if err != nil {
fmt.Println("[-] html.Parse error in perm_collection_func")
}
// parse roles
//role_parsing_func(doc, pattern_search)
var permissions []string
var findDataText func(*html.Node)
findDataText = func(n *html.Node) {
if n.Type == html.ElementNode && n.Data == "h4" && hasClass(n.Attr, "permission-name") {
for _, attr := range n.Attr {
if attr.Key == "data-text" {
permissions = append(permissions, attr.Val)
}
}
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
findDataText(c)
}
}
findDataText(doc)
return permissions
}
func worker_func(id int, jobs <-chan string, results chan<- []string, wg *sync.WaitGroup) {
defer wg.Done()
for pemr_url := range jobs {
perms := perm_collection_func(pemr_url)
results <- perms
}
}
func all_permissions_func(perm_urls []string) []string {
const cWorkers = 10
jobs := make(chan string, len(perm_urls))
results := make(chan []string, len(perm_urls))
var wg sync.WaitGroup
for i := 0; i < cWorkers; i++ {
wg.Add(1)
go worker_func(i, jobs, results, &wg)
}
fmt.Println("[+] Collecting permissions from GCP documentation...")
for _, url := range perm_urls {
jobs <- url
}
close(jobs)
wg.Wait()
close(results)
// create slice of strings
var permissions []string
for result := range results {
permissions = append(permissions, result...)
}
return permissions
}
func main() {
// collect permissions URLs
fmt.Println("[+] Collecting permissions URLs from GCP documentation...")
perm_urls := perm_url_func()
permissions := all_permissions_func(perm_urls)
for _, permission := range permissions {
fmt.Println(permission)
}
fmt.Printf("[*] %d\n", len(permissions))
/*
// write output to file
content := strings.Join(permissions, "\n")
// Write to a file
err := os.WriteFile("all_permissions.txt", []byte(content), 0644)
if err != nil {
fmt.Println("Error writing file:", err)
return
}
*/
/*
// read permissions from file
file, err := os.Open("all_permissions.txt")
if err != nil {
fmt.Println("Error opening file:", err)
return
}
defer file.Close()
scanner := bufio.NewScanner(file)
var lines []string
for scanner.Scan() {
line := scanner.Text()
fmt.Println("Read line:", line)
// Store lines in a slice
lines = append(lines, scanner.Text())
}
*/
}
tags:#GCP - Google Cloud Platform