Skip to content

Commit

Permalink
feat: support service account impersonation
Browse files Browse the repository at this point in the history
  • Loading branch information
ekristen committed May 22, 2024
1 parent eff538e commit 51fa697
Show file tree
Hide file tree
Showing 31 changed files with 151 additions and 39 deletions.
24 changes: 18 additions & 6 deletions pkg/commands/run/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func execute(c *cli.Context) error {
ctx, cancel := context.WithCancel(c.Context)
defer cancel()

gcp, err := gcputil.New(ctx, c.String("project-id"))
gcp, err := gcputil.New(ctx, c.String("project-id"), c.String("impersonate-service-account"))
if err != nil {
return err
}
Expand Down Expand Up @@ -67,6 +67,10 @@ func execute(c *cli.Context) error {
n := libnuke.New(params, filters, parsedConfig.Settings)

n.SetRunSleep(5 * time.Second)
n.RegisterVersion(fmt.Sprintf("> %s", common.AppVersion.String()))

p := &nuke.Prompt{Parameters: params, GCP: gcp}
n.RegisterPrompt(p.Prompt)

projectResourceTypes := types.ResolveResourceTypes(
registry.GetNamesForScope(nuke.Project),
Expand Down Expand Up @@ -121,9 +125,10 @@ func execute(c *cli.Context) error {
// Register the scanners for each region that is defined in the configuration.
for _, regionName := range parsedConfig.Regions {
if err := n.RegisterScanner(nuke.Project, scanner.New(regionName, projectResourceTypes, &nuke.ListerOpts{
Project: ptr.String(projectID),
Region: ptr.String(regionName),
Zones: gcp.GetZones(regionName),
Project: ptr.String(projectID),
Region: ptr.String(regionName),
Zones: gcp.GetZones(regionName),
ClientOptions: gcp.GetClientOptions(),
})); err != nil {
return err
}
Expand Down Expand Up @@ -168,14 +173,21 @@ func init() {
Value: 10,
},
&cli.StringSliceFlag{
Name: "feature-flag",
Usage: "enable experimental behaviors that may not be fully tested or supported",
Name: "feature-flag",
Usage: "enable experimental behaviors that may not be fully tested or supported",
EnvVars: []string{"GCP_NUKE_FEATURE_FLAGS"},
},
&cli.StringFlag{
Name: "project-id",
Usage: "which GCP project should be nuked",
EnvVars: []string{"GCP_NUKE_PROJECT_ID"},
Required: true,
},
&cli.StringFlag{
Name: "impersonate-service-account",
Usage: "impersonate a service account for all API calls",
EnvVars: []string{"GCP_NUKE_IMPERSONATE_SERVICE_ACCOUNT"},
},
}

cmd := &cli.Command{
Expand Down
6 changes: 6 additions & 0 deletions pkg/common/version.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package common

import "fmt"

// NAME of the App
var NAME = "gcp-nuke"

Expand All @@ -22,6 +24,10 @@ type AppVersionInfo struct {
Commit string
}

func (a *AppVersionInfo) String() string {
return fmt.Sprintf("%s - %s - %s", a.Name, a.Summary, a.Commit)
}

func init() {
AppVersion = AppVersionInfo{
Name: NAME,
Expand Down
63 changes: 60 additions & 3 deletions pkg/gcputil/gcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,20 @@ package gcputil
import (
compute "cloud.google.com/go/compute/apiv1"
"cloud.google.com/go/compute/apiv1/computepb"
credentials "cloud.google.com/go/iam/credentials/apiv1"
"cloud.google.com/go/iam/credentials/apiv1/credentialspb"
"context"
"errors"
"fmt"
"github.com/sirupsen/logrus"
"golang.org/x/oauth2"
"google.golang.org/api/cloudresourcemanager/v3"
"google.golang.org/api/iterator"
"google.golang.org/api/option"
"google.golang.org/protobuf/types/known/durationpb"
"log"
"strings"
"time"
)

type Organization struct {
Expand All @@ -35,7 +42,12 @@ type GCP struct {
Projects []*Project
Regions []string

ProjectID string

zones map[string][]string

tokenSource oauth2.TokenSource
clientOptions []option.ClientOption
}

func (g *GCP) HasOrganizations() bool {
Expand All @@ -56,15 +68,60 @@ func (g *GCP) GetZones(region string) []string {
return g.zones[region]
}

func New(ctx context.Context, projectID string) (*GCP, error) {
func (g *GCP) ImpersonateServiceAccount(ctx context.Context, targetServiceAccount string) error {
credsClient, err := credentials.NewIamCredentialsClient(ctx)
if err != nil {
return err
}
defer credsClient.Close()

req := &credentialspb.GenerateAccessTokenRequest{
Name: fmt.Sprintf("projects/-/serviceAccounts/%s", targetServiceAccount),
Scope: []string{"https://www.googleapis.com/auth/cloud-platform"},
Lifetime: &durationpb.Duration{
Seconds: int64(time.Hour.Seconds()), // 1 hour
},
}
resp, err := credsClient.GenerateAccessToken(ctx, req)
if err != nil {
return err
}

// Create a new authenticated client using the impersonated access token
g.tokenSource = oauth2.StaticTokenSource(&oauth2.Token{
AccessToken: resp.AccessToken,
})

g.clientOptions = append(g.clientOptions, option.WithTokenSource(g.tokenSource))

return nil
}

func (g *GCP) GetClientOptions() []option.ClientOption {
return g.clientOptions
}

func (g *GCP) ID() string {
return g.ProjectID
}

func New(ctx context.Context, projectID, impersonateServiceAccount string) (*GCP, error) {
gcp := &GCP{
Organizations: make([]*Organization, 0),
Projects: make([]*Project, 0),
Regions: []string{"global"},
ProjectID: projectID,
zones: make(map[string][]string),
clientOptions: make([]option.ClientOption, 0),
}

if impersonateServiceAccount != "" {
if err := gcp.ImpersonateServiceAccount(ctx, impersonateServiceAccount); err != nil {
return nil, err
}
}

service, err := cloudresourcemanager.NewService(ctx)
service, err := cloudresourcemanager.NewService(ctx, gcp.GetClientOptions()...)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -110,7 +167,7 @@ func New(ctx context.Context, projectID string) (*GCP, error) {
return nil, err
}

c, err := compute.NewRegionsRESTClient(ctx)
c, err := compute.NewRegionsRESTClient(ctx, gcp.GetClientOptions()...)
if err != nil {
log.Fatalf("Failed to create client: %v", err)
}
Expand Down
35 changes: 35 additions & 0 deletions pkg/nuke/prompt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package nuke

import (
"fmt"
"time"

libnuke "github.com/ekristen/libnuke/pkg/nuke"
"github.com/ekristen/libnuke/pkg/utils"

"github.com/ekristen/gcp-nuke/pkg/gcputil"
)

type Prompt struct {
Parameters *libnuke.Parameters
GCP *gcputil.GCP
}

// Prompt is the actual function called by the libnuke process during it's run
func (p *Prompt) Prompt() error {
promptDelay := time.Duration(p.Parameters.ForceSleep) * time.Second

fmt.Printf("Do you really want to nuke the project with "+
"the ID '%s'?\n", p.GCP.ID())
if p.Parameters.Force {
fmt.Printf("Waiting %v before continuing.\n", promptDelay)
time.Sleep(promptDelay)
} else {
fmt.Printf("Do you want to continue? Enter project ID to continue.\n")
if err := utils.Prompt(p.GCP.ID()); err != nil {
return err
}
}

return nil
}
9 changes: 6 additions & 3 deletions pkg/nuke/resource.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package nuke

import (
"google.golang.org/api/option"

"github.com/ekristen/libnuke/pkg/registry"
)

Expand All @@ -11,7 +13,8 @@ const (
)

type ListerOpts struct {
Project *string
Region *string
Zones []string
Project *string
Region *string
Zones []string
ClientOptions []option.ClientOption
}
2 changes: 1 addition & 1 deletion resources/cloud-function.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func (l *CloudFunctionLister) List(ctx context.Context, o interface{}) ([]resour

if l.svc == nil {
var err error
l.svc, err = functions.NewCloudFunctionsRESTClient(ctx)
l.svc, err = functions.NewCloudFunctionsRESTClient(ctx, opts.ClientOptions...)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion resources/cloud-function2.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func (l *CloudFunction2Lister) List(ctx context.Context, o interface{}) ([]resou

if l.svc == nil {
var err error
l.svc, err = functions.NewFunctionRESTClient(ctx)
l.svc, err = functions.NewFunctionRESTClient(ctx, opts.ClientOptions...)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion resources/cloud-run.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func (l *CloudRunLister) List(ctx context.Context, o interface{}) ([]resource.Re

if l.svc == nil {
var err error
l.svc, err = run.NewServicesClient(ctx)
l.svc, err = run.NewServicesClient(ctx, opts.ClientOptions...)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion resources/cloud-sql-instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func (l *CloudSQLInstanceLister) List(ctx context.Context, o interface{}) ([]res

if l.svc == nil {
var err error
l.svc, err = sqladmin.NewService(ctx)
l.svc, err = sqladmin.NewService(ctx, opts.ClientOptions...)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion resources/compute-common-instance-metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func (l *ComputeCommonInstanceMetadataLister) List(ctx context.Context, o interf

if l.svc == nil {
var err error
l.svc, err = compute.NewProjectsRESTClient(ctx)
l.svc, err = compute.NewProjectsRESTClient(ctx, opts.ClientOptions...)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion resources/compute-disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func (l *ComputeDiskLister) List(ctx context.Context, o interface{}) ([]resource

if l.svc == nil {
var err error
l.svc, err = compute.NewDisksRESTClient(ctx)
l.svc, err = compute.NewDisksRESTClient(ctx, opts.ClientOptions...)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion resources/compute-firewall.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func (l *ComputeFirewallLister) List(ctx context.Context, o interface{}) ([]reso

if l.svc == nil {
var err error
l.svc, err = compute.NewFirewallsRESTClient(ctx)
l.svc, err = compute.NewFirewallsRESTClient(ctx, opts.ClientOptions...)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion resources/compute-instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func (l *ComputeInstanceLister) List(ctx context.Context, o interface{}) ([]reso

if l.svc == nil {
var err error
l.svc, err = compute.NewInstancesRESTClient(ctx)
l.svc, err = compute.NewInstancesRESTClient(ctx, opts.ClientOptions...)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion resources/firebase-realtime-database.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func (l *FirebaseRealtimeDatabaseLister) List(ctx context.Context, o interface{}

if l.svc == nil {
var err error
l.svc, err = gcputil.NewFirebaseDatabaseService(ctx)
l.svc, err = gcputil.NewFirebaseDatabaseService(ctx, opts.ClientOptions...)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion resources/firebase-web-app.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func (l *FirebaseWebAppLister) List(ctx context.Context, o interface{}) ([]resou

if l.svc == nil {
var err error
l.svc, err = firebase.NewService(ctx)
l.svc, err = firebase.NewService(ctx, opts.ClientOptions...)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion resources/gke-cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func (l *GKEClusterLister) List(ctx context.Context, o interface{}) ([]resource.

if l.svc == nil {
var err error
l.svc, err = container.NewClusterManagerClient(ctx)
l.svc, err = container.NewClusterManagerClient(ctx, opts.ClientOptions...)
if err != nil {
return nil, err
}
Expand Down
3 changes: 1 addition & 2 deletions resources/iam-policy-binding.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func (l *IAMPolicyBindingLister) List(ctx context.Context, o interface{}) ([]res

if l.svc == nil {
var err error
l.svc, err = cloudresourcemanager.NewService(ctx)
l.svc, err = cloudresourcemanager.NewService(ctx, opts.ClientOptions...)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -135,7 +135,6 @@ func (r *IAMPolicyBinding) Remove(ctx context.Context) error {
}
}

// Set the updated IAM policy with an update mask
_, err = r.svc.Projects.SetIamPolicy(fmt.Sprintf("projects/%s", *r.project), &cloudresourcemanager.SetIamPolicyRequest{
Policy: policy,
}).Context(ctx).Do()
Expand Down
2 changes: 1 addition & 1 deletion resources/iam-role.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func (l *IAMRoleLister) List(ctx context.Context, o interface{}) ([]resource.Res

if l.svc == nil {
var err error
l.svc, err = iamadmin.NewIamClient(ctx)
l.svc, err = iamadmin.NewIamClient(ctx, opts.ClientOptions...)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion resources/iam-service-account.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func (l *IAMServiceAccountLister) List(ctx context.Context, o interface{}) ([]re

if l.svc == nil {
var err error
l.svc, err = iamadmin.NewIamClient(ctx)
l.svc, err = iamadmin.NewIamClient(ctx, opts.ClientOptions...)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion resources/iam-workload-identity-pool-provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func (l *IAMWorkloadIdentityPoolProviderLister) List(ctx context.Context, o inte

if l.svc == nil {
var err error
l.svc, err = iam.NewService(ctx)
l.svc, err = iam.NewService(ctx, opts.ClientOptions...)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion resources/iam-workload-identity-pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type IAMWorkloadIdentityPoolLister struct {
func (l *IAMWorkloadIdentityPoolLister) ListPools(ctx context.Context, opts *nuke.ListerOpts) ([]*iam.WorkloadIdentityPool, error) {
if l.svc == nil {
var err error
l.svc, err = iam.NewService(ctx)
l.svc, err = iam.NewService(ctx, opts.ClientOptions...)
if err != nil {
return nil, err
}
Expand Down
Loading

0 comments on commit 51fa697

Please sign in to comment.