Skip to content

Commit

Permalink
πŸ—“ Jun 18, 2022 7:19:59 PM
Browse files Browse the repository at this point in the history
✨ gcp curl builder
🎨 move to persistant flags for gcp rest
🎨 split gcp enumerate rest api into request builder function
  • Loading branch information
securisec committed Jun 18, 2022
1 parent b1f9913 commit f596955
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 42 deletions.
94 changes: 90 additions & 4 deletions cli/cmd/gcp_curl_builder.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
package cmd

import "github.com/spf13/cobra"
import (
"context"
"fmt"

"github.com/securisec/cliam/gcp"
"github.com/securisec/cliam/gcp/rest"
"github.com/securisec/cliam/gcp/scanner"
"github.com/securisec/cliam/logger"
"github.com/spf13/cobra"
"moul.io/http2curl/v2"
)

// TODO: implement this
var gcpRestCurlBuilderCmd = &cobra.Command{
Use: "curl-builder",
Short: "Build the curl command to test a gcp policy.",
Expand All @@ -14,12 +23,89 @@ func init() {
gcpRestCmd.AddCommand(gcpRestCurlBuilderCmd)
gcpRestCurlBuilderCmd.Flags().StringP("policy", "p", "", "The policy to build")
gcpRestCurlBuilderCmd.Flags().StringP("operation", "o", "", "The operation to build.")
gcpRestCurlBuilderCmd.Flags().StringSliceP("values", "n", []string{}, "The values to use for known values.")
// gcpRestCurlBuilderCmd.Flags().StringSliceP("values", "n", []string{}, "The values to use for known values.")

gcpRestCurlBuilderCmd.RegisterFlagCompletionFunc("policy", func(cmd *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return gcp.GetGCPResources(), cobra.ShellCompDirectiveNoFileComp
})
gcpRestCurlBuilderCmd.RegisterFlagCompletionFunc("operation", func(cmd *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
hold := []string{}
pCli, _ := cmd.Flags().GetString("policy")
if pCli == "" {
return hold, cobra.ShellCompDirectiveNoSpace
}
policies, ok := rest.RestApiCalls[pCli]
if !ok {
return hold, cobra.ShellCompDirectiveNoFileComp
}
for k := range policies {
hold = append(hold, k)
}
return hold, cobra.ShellCompDirectiveNoFileComp
})
// gcpRestCurlBuilderCmd.RegisterFlagCompletionFunc("values", func(cmd *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
// hold := []string{}
// pCli, _ := cmd.Flags().GetString("policy")
// oCli, _ := cmd.Flags().GetString("operation")

// p := gcpGetSpecificOperation(pCli, oCli)
// matches := shared.AzureTemplatePropertyRegex.FindAllStringSubmatch(p.URL, -1)
// for _, m := range matches {
// hold = append(hold, m[1]+"=")
// }
// return hold, cobra.ShellCompDirectiveNoSpace
// })

gcpRestCurlBuilderCmd.MarkFlagRequired("policy")
gcpRestCurlBuilderCmd.MarkFlagRequired("operation")
}

func gcpRestCurlBuilderFunc(cmd *cobra.Command, args []string) {
// TODO
var err error
ctx := context.Background()
sa, _, region, zone := getSaAndRegion()

pCli, _ := cmd.Flags().GetString("policy")
oCli, _ := cmd.Flags().GetString("operation")
// values, _ := cmd.Flags().GetStringSlice("values")

accessToken := gcpAccessToken
if accessToken == "" {
accessToken, err = gcp.GetAccessToken(ctx, sa)
if err != nil {
logger.LoggerStdErr.Fatal().Err(err).Msg("Failed to get access token")
}
}

p := gcpGetSpecificOperation(pCli, oCli)
parentType, parentID := processParent(gcpRestParent)
p.ParentType = parentType
p.ParentID = parentID
p.ResourceID = gcpRestResourceID
p.Zone = zone
p.Region = region
p.ReqBody = gcpRestBody
req, err := scanner.RequestBuilder(ctx, p, accessToken)
if err != nil {
logger.LoggerStdErr.Fatal().Str("url", p.URL).Err(err).Msg("Failed to build request")
}

c, err := http2curl.GetCurlCommand(req)
if err != nil {
logger.LoggerStdErr.Fatal().Err(err).Msg("Failed to get curl command")
}
fmt.Println(c.String())
}

func gcpGetSpecificOperation(policy, operationId string) rest.RestCall {
policies, ok := rest.RestApiCalls[policy]
if !ok {
logger.LoggerStdErr.Fatal().Msgf("Policy %s not found", policy)
}
o, ok := policies[operationId]
if !ok {
logger.LoggerStdErr.Fatal().Msgf("Operation %s not found", operationId)
}

return o
}
12 changes: 12 additions & 0 deletions cli/cmd/gcp_rest.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ import (
"github.com/spf13/cobra"
)

var (
gcpRestParent map[string]string
gcpRestBody map[string]string
gcpRestResourceID string
)

var gcpRestCmd = &cobra.Command{
Use: "rest [--parent project=someproject] [resource...]",
Short: "GCP permissions using the REST API",
Expand All @@ -20,4 +26,10 @@ var gcpRestCmd = &cobra.Command{

func init() {
gcpCmd.AddCommand(gcpRestCmd)
gcpRestCmd.PersistentFlags().StringToStringVar(&gcpRestParent, "parent", map[string]string{}, "Specify the parent. i.e. project=my-project or organization=my-org. Valid keys are project, origanization, folder, billingAccount")
gcpRestCmd.PersistentFlags().StringToStringVar(&gcpRestBody, "body", map[string]string{}, "Rest API body to use when not a GET request")
gcpRestCmd.PersistentFlags().StringVar(&gcpRestResourceID, "resource-id", "", `Resource ID to use when not a GET request. Resource id is quite
versitile and is used as a generic term for any resource. For example, if you are
enumerating pubsub, resource-id could be the subscription name; but also if you are
enumerating compute, resource-id could be the instance name.`)
}
24 changes: 5 additions & 19 deletions cli/cmd/gcp_rest_enumerate.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,34 +35,20 @@ var gcpRestEnumerateCmd = &cobra.Command{

func init() {
gcpRestCmd.AddCommand(gcpRestEnumerateCmd)
gcpRestEnumerateCmd.Flags().StringToString("parent", nil, "Specify the parent. i.e. project=my-project or organization=my-org. Valid keys are project, origanization, folder, billingAccount")
gcpRestEnumerateCmd.Flags().StringToString("body", map[string]string{}, "Rest API body to use when not a GET request")
gcpRestEnumerateCmd.Flags().String("resource-id", "", `Resource ID to use when not a GET request. Resource id is quite
versitile and is used as a generic term for any resource. For example, if you are
enumerating pubsub, resource-id could be the subscription name; but also if you are
enumerating compute, resource-id could be the instance name.`)
// gcpRestEnumerateCmd.MarkFlagRequired("parent")
}

func gcpRestEnumerateCmdFunc(cmd *cobra.Command, args []string) {
var err error
accessToken := gcpAccessToken

parent, err := cmd.Flags().GetStringToString("parent")
body, err := cmd.Flags().GetStringToString("body")
resourceID, err := cmd.Flags().GetString("resource-id")

if err != nil {
logger.LoggerStdErr.Fatal().Err(err).Msg("Invalid parent")
os.Exit(1)
}

sa, projectID, region, zone := getSaAndRegion()
cliGcpLogRegion(region, zone)
if projectID != "" {
parent["project"] = projectID
gcpRestParent["project"] = projectID
}

parentType, parentID := processParent(parent)
parentType, parentID := processParent(gcpRestParent)

if parentType == "" || parentID == "" {
logger.LoggerStdErr.Fatal().Msg("parent must be specified as project=my-project or organization=my-org")
Expand All @@ -85,10 +71,10 @@ func gcpRestEnumerateCmdFunc(cmd *cobra.Command, args []string) {
for _, p := range r {
p.ParentType = parentType
p.ParentID = parentID
p.ResourceID = resourceID
p.ResourceID = gcpRestResourceID
p.Zone = zone
p.Region = region
p.ReqBody = body
p.ReqBody = gcpRestBody
permissions = append(permissions, p)
}
}
Expand Down
6 changes: 3 additions & 3 deletions gcp/rest/rest.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ type RestCall struct {

func (r RestCall) GetURL() (string, error) {
t := template.Must(template.New("").Parse("{{.URL}}"))
t.Option("missingkey=error")
t = t.Option("missingkey=error")
b := bytes.Buffer{}

if err := t.Execute(&b, r); err != nil {
Expand All @@ -43,7 +43,7 @@ func (r RestCall) GetURL() (string, error) {

bb := bytes.Buffer{}
tt := template.Must(template.New("").Parse(b.String()))
tt.Option("missingkey=error")
tt = tt.Option("missingkey=error")

if err := tt.Execute(&bb, r); err != nil {
return "", err
Expand All @@ -52,7 +52,7 @@ func (r RestCall) GetURL() (string, error) {
url := bb.String()
if strings.Contains(strings.Replace(url, "https://", "", -1), "//") {
// one or more of the template variables is empty
return "", errors.New("invalid url.")
return "", errors.New("invalid url. Needed parameters are missing")
}
return url, nil
}
Expand Down
38 changes: 22 additions & 16 deletions gcp/scanner/scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,29 @@ func EnumerateRestApiRequest(
accessToken string,
r rest.RestCall,
) (rest.RestCall, []byte, error) {
req, err := RequestBuilder(ctx, r, accessToken)

res, err := http.DefaultClient.Do(req)
if err != nil {
return r, nil, err
}

body, err := ioutil.ReadAll(res.Body)
if err != nil {
return r, nil, err
}

r.Response = res
res.Body.Close()
return r, body, nil
}

func RequestBuilder(ctx context.Context, r rest.RestCall, accessToken string) (*http.Request, error) {
var req *http.Request

url, err := r.GetURL()
if err != nil {
return r, nil, err
return nil, err
}

isGet := r.ReqMethod == "GET"
Expand All @@ -151,12 +169,12 @@ func EnumerateRestApiRequest(
} else {
o, err := json.Marshal(r.ReqBody)
if err != nil {
return r, nil, err
return nil, err
}
req, err = http.NewRequestWithContext(ctx, r.ReqMethod, url, bytes.NewBuffer(o))
}
if err != nil {
return r, nil, err
return nil, err
}
if !isGet {
req.Header.Add("Content-Type", "application/json")
Expand All @@ -166,17 +184,5 @@ func EnumerateRestApiRequest(
req.Header.Add("Authorization", "Bearer "+accessToken)
req.Header.Add("user-agent", "google-cloud-sdk gcloud/379.0.0")

res, err := http.DefaultClient.Do(req)
if err != nil {
return r, nil, err
}

body, err := ioutil.ReadAll(res.Body)
if err != nil {
return r, nil, err
}

r.Response = res
res.Body.Close()
return r, body, nil
return req, nil
}

0 comments on commit f596955

Please sign in to comment.