Explorar o código

gcp service account creation

Alexander Belanger %!s(int64=5) %!d(string=hai) anos
pai
achega
d61936433b

+ 24 - 0
cli/cmd/generate.go

@@ -5,6 +5,9 @@ import (
 	"path/filepath"
 
 	"github.com/porter-dev/porter/internal/kubernetes/local"
+	"github.com/porter-dev/porter/internal/utils"
+
+	gcpLocal "github.com/porter-dev/porter/internal/providers/gcp/local"
 
 	"k8s.io/client-go/tools/clientcmd"
 	"k8s.io/client-go/util/homedir"
@@ -96,3 +99,24 @@ func generate(kubeconfigPath string, output string, print bool, contexts []strin
 
 	return nil
 }
+
+// TODO -- error handling, stop hard-coding, ask for permissions
+func gcpHelper() {
+	agent, _ := gcpLocal.NewDefaultAgent()
+
+	agent.ProjectID = "PROJECT_ID"
+	name := "porter-dashboard-" + utils.StringWithCharset(6, "abcdefghijklmnopqrstuvwxyz1234567890")
+	resp, err := agent.CreateServiceAccount(name)
+
+	if err != nil {
+		fmt.Printf("Error was %v\n", err)
+		return
+	}
+
+	err = agent.SetServiceAccountIAMPolicy(resp)
+
+	if err != nil {
+		fmt.Printf("Error was %v\n", err)
+		return
+	}
+}

+ 4 - 2
go.mod

@@ -3,6 +3,7 @@ module github.com/porter-dev/porter
 go 1.14
 
 require (
+	cloud.google.com/go v0.65.0
 	github.com/Azure/go-autorest/autorest v0.11.1 // indirect
 	github.com/DATA-DOG/go-sqlmock v1.5.0
 	github.com/Masterminds/semver v1.5.0 // indirect
@@ -38,10 +39,11 @@ require (
 	github.com/spf13/cobra v1.0.0
 	github.com/stretchr/testify v1.6.1
 	golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
-	golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 // indirect
+	golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43
 	golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6
 	golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect
-	google.golang.org/genproto v0.0.0-20201014134559-03b6142f0dc9 // indirect
+	google.golang.org/api v0.30.0
+	google.golang.org/genproto v0.0.0-20201014134559-03b6142f0dc9
 	google.golang.org/grpc v1.33.0 // indirect
 	gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
 	gopkg.in/go-playground/validator.v9 v9.31.0

+ 5 - 0
go.sum

@@ -14,6 +14,7 @@ cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bP
 cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
 cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
 cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
+cloud.google.com/go v0.65.0 h1:Dg9iHVQfrhq82rUNu9ZxUDrJLaxFUe/HlCVaLyRruq8=
 cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
 cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
 cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
@@ -361,6 +362,7 @@ github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4er
 github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
 github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
@@ -418,6 +420,7 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
 github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
+github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
 github.com/googleapis/gnostic v0.1.0 h1:rVsPeBmXbYv4If/cumu1AzZPwV58q433hvONV1UEZoI=
@@ -875,6 +878,7 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto=
 go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
@@ -1157,6 +1161,7 @@ google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/
 google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
 google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
 google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
+google.golang.org/api v0.30.0 h1:yfrXXP61wVuLb0vBcG6qaOoIoqYEzOQS8jum51jkv2w=
 google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=

+ 74 - 0
internal/providers/gcp/agent.go

@@ -0,0 +1,74 @@
+package gcp
+
+import (
+	"context"
+
+	admin "cloud.google.com/go/iam/admin/apiv1"
+	adminpb "google.golang.org/genproto/googleapis/iam/admin/v1"
+
+	crm "google.golang.org/api/cloudresourcemanager/v1"
+)
+
+type Agent struct {
+	Ctx       context.Context
+	ProjectID string
+
+	IAMClient                   *admin.IamClient
+	CloudResourceManagerService *crm.Service
+}
+
+func (a *Agent) CreateServiceAccount(name string) (*adminpb.ServiceAccount, error) {
+	req := &adminpb.CreateServiceAccountRequest{
+		Name:      "projects/" + a.ProjectID,
+		AccountId: name,
+		ServiceAccount: &adminpb.ServiceAccount{
+			DisplayName: name,
+		},
+	}
+
+	return a.IAMClient.CreateServiceAccount(a.Ctx, req)
+}
+
+func (a *Agent) SetServiceAccountIAMPolicy(sa *adminpb.ServiceAccount) error {
+	projectSvc := a.CloudResourceManagerService.Projects
+
+	policy, err := projectSvc.GetIamPolicy(
+		a.ProjectID,
+		&crm.GetIamPolicyRequest{},
+	).Do()
+
+	if err != nil {
+		return err
+	}
+
+	doesExist := false
+
+	// find a container.developer binding if it exists
+	for _, binding := range policy.Bindings {
+		if binding.Role == "roles/container.developer" {
+			doesExist = true
+			binding.Members = append(binding.Members, "serviceAccount:"+sa.Email)
+			break
+		}
+	}
+
+	if !doesExist {
+		policy.Bindings = append(policy.Bindings, &crm.Binding{
+			Members: []string{"serviceAccount:" + sa.Email},
+			Role:    "roles/container.developer",
+		})
+	}
+
+	policy, err = projectSvc.SetIamPolicy(
+		a.ProjectID,
+		&crm.SetIamPolicyRequest{
+			Policy: policy,
+		},
+	).Do()
+
+	if err != nil {
+		return err
+	}
+
+	return nil
+}

+ 93 - 0
internal/providers/gcp/local/config.go

@@ -0,0 +1,93 @@
+// +build cli
+
+package local
+
+import (
+	"context"
+	"fmt"
+	"os/exec"
+	"time"
+
+	"github.com/porter-dev/porter/internal/providers/gcp"
+	"google.golang.org/api/cloudresourcemanager/v1"
+
+	admin "cloud.google.com/go/iam/admin/apiv1"
+
+	oauth2 "golang.org/x/oauth2/google"
+)
+
+// NewDefaultAgent returns an agent using Application Default Credentials. If these are not
+// set and the gcloud utility is installed on the machine, this will spawn a setup process
+// to link these credentials.
+func NewDefaultAgent() (*gcp.Agent, error) {
+	ctx := context.Background()
+	creds, err := setupDefaultCredentials(ctx)
+
+	if err != nil {
+		return nil, err
+	}
+
+	c, err := getDefaultIAMClient(ctx)
+
+	if err != nil {
+		return nil, err
+	}
+
+	cloudresourcemanagerService, err := cloudresourcemanager.NewService(ctx)
+
+	if err != nil {
+		return nil, err
+	}
+
+	return &gcp.Agent{
+		Ctx:                         ctx,
+		ProjectID:                   creds.ProjectID,
+		IAMClient:                   c,
+		CloudResourceManagerService: cloudresourcemanagerService,
+	}, nil
+}
+
+func setupDefaultCredentials(ctx context.Context) (*oauth2.Credentials, error) {
+	// determine if local Application Default Credentials Exist
+	creds, _ := oauth2.FindDefaultCredentials(ctx)
+
+	// if they don't exist, attempt gcloud login
+	if creds == nil {
+		if !commandExists("gcloud") {
+			return nil, fmt.Errorf("gcloud cli command does not exist")
+		}
+
+		// create Application Default Credentials that use the local user creds
+		cmd := exec.Command("gcloud", "auth", "application-default", "login")
+		err := cmd.Run()
+
+		if err != nil {
+			return nil, err
+		}
+
+		for i := 0; i < 5; i++ {
+			creds, err := oauth2.FindDefaultCredentials(ctx)
+
+			if creds != nil {
+				return creds, err
+			}
+
+			if i == 4 {
+				return nil, err
+			}
+
+			time.Sleep(time.Second)
+		}
+	}
+
+	return creds, nil
+}
+
+func getDefaultIAMClient(ctx context.Context) (*admin.IamClient, error) {
+	return admin.NewIamClient(ctx)
+}
+
+func commandExists(cmd string) bool {
+	_, err := exec.LookPath(cmd)
+	return err == nil
+}

+ 24 - 0
internal/utils/random_string.go

@@ -0,0 +1,24 @@
+package utils
+
+import (
+	"math/rand"
+	"time"
+)
+
+const charset = "abcdefghijklmnopqrstuvwxyz" +
+	"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
+
+var seededRand *rand.Rand = rand.New(
+	rand.NewSource(time.Now().UnixNano()))
+
+func StringWithCharset(length int, charset string) string {
+	b := make([]byte, length)
+	for i := range b {
+		b[i] = charset[seededRand.Intn(len(charset))]
+	}
+	return string(b)
+}
+
+func String(length int) string {
+	return StringWithCharset(length, charset)
+}