Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GCE: Set up permissions for cross-project configurations #3712

Merged
merged 1 commit into from
Oct 28, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pkg/model/gcemodel/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ go_library(
"external_access.go",
"firewall.go",
"network.go",
"storageacl.go",
],
visibility = ["//visibility:public"],
deps = [
Expand All @@ -21,6 +22,7 @@ go_library(
"//upup/pkg/fi/cloudup/gce:go_default_library",
"//upup/pkg/fi/cloudup/gcetasks:go_default_library",
"//upup/pkg/fi/fitasks:go_default_library",
"//util/pkg/vfs:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
],
)
63 changes: 63 additions & 0 deletions pkg/model/gcemodel/storageacl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
Copyright 2016 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package gcemodel

import (
"fmt"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/gce"
"k8s.io/kops/upup/pkg/fi/cloudup/gcetasks"
"k8s.io/kops/util/pkg/vfs"
)

// StorageAclBuilder configures storage acls
type StorageAclBuilder struct {
*GCEModelContext
Cloud gce.GCECloud
Lifecycle *fi.Lifecycle
}

var _ fi.ModelBuilder = &NetworkModelBuilder{}

// Build creates the tasks that set up storage acls
func (b *StorageAclBuilder) Build(c *fi.ModelBuilderContext) error {
clusterPath := b.Cluster.Spec.ConfigBase
p, err := vfs.Context.BuildVfsPath(clusterPath)
if err != nil {
return fmt.Errorf("cannot parse cluster path %q: %v", clusterPath, err)
}

serviceAccount, err := b.Cloud.ServiceAccount()
if err != nil {
return fmt.Errorf("error fetching ServiceAccount: %v", err)
}

switch p := p.(type) {
case *vfs.GSPath:
// It's not ideal that we have to do this at the bucket level,
// but GCS doesn't seem to have a way to do subtrees (like AWS IAM does)
c.AddTask(&gcetasks.StorageBucketIam{
Name: s("serviceaccount-statestore-read"),
Lifecycle: b.Lifecycle,
Bucket: s(p.Bucket()),
Entity: s("serviceAccount:" + serviceAccount),
Role: s("roles/storage.objectViewer"),
})
}

return nil
}
1 change: 1 addition & 0 deletions upup/pkg/fi/cloudup/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ go_library(
"//pkg/model:go_default_library",
"//pkg/model/awsmodel:go_default_library",
"//pkg/model/components:go_default_library",
"//pkg/model/domodel:go_default_library",
"//pkg/model/gcemodel:go_default_library",
"//pkg/model/vspheremodel:go_default_library",
"//pkg/resources/digitalocean:go_default_library",
Expand Down
1 change: 1 addition & 0 deletions upup/pkg/fi/cloudup/apply_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,7 @@ func (c *ApplyClusterCmd) Run() error {
&gcemodel.ExternalAccessModelBuilder{GCEModelContext: gceModelContext, Lifecycle: securityLifecycle},
&gcemodel.FirewallModelBuilder{GCEModelContext: gceModelContext, Lifecycle: securityLifecycle},
&gcemodel.NetworkModelBuilder{GCEModelContext: gceModelContext, Lifecycle: networkLifecycle},
&gcemodel.StorageAclBuilder{GCEModelContext: gceModelContext, Cloud: cloud.(gce.GCECloud), Lifecycle: securityLifecycle},
)

case kops.CloudProviderVSphere:
Expand Down
2 changes: 2 additions & 0 deletions upup/pkg/fi/cloudup/gce/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ go_library(
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/golang.org/x/net/context:go_default_library",
"//vendor/golang.org/x/oauth2/google:go_default_library",
"//vendor/google.golang.org/api/cloudresourcemanager/v1:go_default_library",
"//vendor/google.golang.org/api/compute/v0.beta:go_default_library",
"//vendor/google.golang.org/api/googleapi:go_default_library",
"//vendor/google.golang.org/api/iam/v1:go_default_library",
"//vendor/google.golang.org/api/storage/v1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
Expand Down
81 changes: 78 additions & 3 deletions upup/pkg/fi/cloudup/gce/gce_cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,22 @@ import (
"github.com/golang/glog"
"golang.org/x/net/context"
"golang.org/x/oauth2/google"
"google.golang.org/api/cloudresourcemanager/v1"
compute "google.golang.org/api/compute/v0.beta"
"google.golang.org/api/iam/v1"
"google.golang.org/api/storage/v1"
"k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kubernetes/federation/pkg/dnsprovider"
"k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns"
"strconv"
)

type GCECloud interface {
fi.Cloud
Compute() *compute.Service
Storage() *storage.Service
IAM() *iam.Service

Region() string
Project() string
Expand All @@ -46,15 +50,26 @@ type GCECloud interface {
FindClusterStatus(cluster *kops.Cluster) (*kops.ClusterStatus, error)

Zones() ([]string, error)

// ServiceAccount returns the email for the service account that the instances will run under
ServiceAccount() (string, error)
}

type gceCloudImplementation struct {
compute *compute.Service
storage *storage.Service
compute *compute.Service
storage *storage.Service
iam *iam.Service
cloudResourceManager *cloudresourcemanager.Service

region string
project string

// projectInfo caches the project info from the cloud resource manager
projectInfo *cloudresourcemanager.Project

// serviceAccount caches the service account from IAM
serviceAccount *iam.ServiceAccount

labels map[string]string
}

Expand Down Expand Up @@ -92,6 +107,18 @@ func NewGCECloud(region string, project string, labels map[string]string) (GCECl
}
c.storage = storageService

iamService, err := iam.New(client)
if err != nil {
return nil, fmt.Errorf("error building IAM API client: %v", err)
}
c.iam = iamService

cloudResourceManagerService, err := cloudresourcemanager.New(client)
if err != nil {
return nil, fmt.Errorf("error building Cloud Resource Manager API client: %v", err)
}
c.cloudResourceManager = cloudResourceManagerService

gceCloudInstances[region+"::"+project] = c

return c.WithLabels(labels), nil
Expand Down Expand Up @@ -121,6 +148,11 @@ func (c *gceCloudImplementation) Storage() *storage.Service {
return c.storage
}

// IAM returns the IAM client
func (c *gceCloudImplementation) IAM() *iam.Service {
return c.iam
}

// Region returns private struct element region.
func (c *gceCloudImplementation) Region() string {
return c.region
Expand All @@ -131,6 +163,50 @@ func (c *gceCloudImplementation) Project() string {
return c.project
}

// ServiceAccount returns the email address for the service account that the instances will run under.
func (c *gceCloudImplementation) ServiceAccount() (string, error) {
if c.projectInfo == nil {
// Find the project info, so we can find the ProjectNumber
p, err := c.cloudResourceManager.Projects.Get(c.project).Do()
if err != nil {
return "", fmt.Errorf("error fetching info for project %q: %v", c.project, err)
}

c.projectInfo = p
}

if c.serviceAccount == nil {
// This is the format of the default GCE compute IAM service account
expectedName := strconv.FormatInt(c.projectInfo.ProjectNumber, 10) + "-compute@developer.gserviceaccount.com"

var matches []*iam.ServiceAccount
ctx := context.TODO()
err := c.iam.Projects.ServiceAccounts.List("projects/"+c.project).Pages(ctx, func(page *iam.ListServiceAccountsResponse) error {
for _, a := range page.Accounts {
if a.Email == expectedName {
matches = append(matches, a)
}
}
return nil
})
if err != nil {
return "", fmt.Errorf("error listing service accounts: %v", err)
}

if len(matches) == 0 {
return "", fmt.Errorf("could not find expected service account with name %q", expectedName)
}

if len(matches) > 1 {
return "", fmt.Errorf("found multiple service accounts with name %q", expectedName)
}

c.serviceAccount = matches[0]
}

return c.serviceAccount.Email, nil
}

func (c *gceCloudImplementation) DNS() (dnsprovider.Interface, error) {
provider, err := clouddns.CreateInterface(c.project, nil)
if err != nil {
Expand Down Expand Up @@ -158,7 +234,6 @@ func (c *gceCloudImplementation) Labels() map[string]string {

// Zones returns the zones in a region
func (c *gceCloudImplementation) Zones() ([]string, error) {

var zones []string
// TODO: Only zones in api.Cluster object, if we have one?
gceZones, err := c.Compute().Zones.List(c.Project()).Do()
Expand Down
12 changes: 12 additions & 0 deletions upup/pkg/fi/cloudup/gce/mock_gce_cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (

"github.com/golang/glog"
compute "google.golang.org/api/compute/v0.beta"
"google.golang.org/api/iam/v1"
"google.golang.org/api/storage/v1"
"k8s.io/api/core/v1"
"k8s.io/kops/pkg/apis/kops"
Expand Down Expand Up @@ -98,6 +99,12 @@ func (c *mockGCECloud) Storage() *storage.Service {
return nil
}

// IAM returns the IAM client
func (c *mockGCECloud) IAM() *iam.Service {
glog.Fatalf("mockGCECloud::IAM not implemented")
return nil
}

// WaitForOp implements GCECloud::WaitForOp
func (c *mockGCECloud) WaitForOp(op *compute.Operation) error {
return fmt.Errorf("mockGCECloud::WaitForOp not implemented")
Expand All @@ -123,6 +130,11 @@ func (c *mockGCECloud) Project() string {
return c.region
}

// ServiceAccount implements GCECloud::ServiceAccount
func (c *mockGCECloud) ServiceAccount() (string, error) {
return "12345678-compute@developer.gserviceaccount.com", nil
}

// Labels implements GCECloud::Labels
func (c *mockGCECloud) Labels() map[string]string {
return c.labels
Expand Down
3 changes: 3 additions & 0 deletions upup/pkg/fi/cloudup/gcetasks/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ go_library(
"instancetemplate_fitask.go",
"network.go",
"network_fitask.go",
"storagebucketiam.go",
"storagebucketiam_fitask.go",
"subnet.go",
"subnet_fitask.go",
"targetpool.go",
Expand All @@ -33,5 +35,6 @@ go_library(
"//upup/pkg/fi/cloudup/terraform:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/google.golang.org/api/compute/v0.beta:go_default_library",
"//vendor/google.golang.org/api/storage/v1:go_default_library",
],
)
Loading