implement iam for project and make middleware generics

main
Martin Guibert 2021-10-01 11:02:46 +02:00
parent 7c653d0500
commit b94c41777a
18 changed files with 966 additions and 18 deletions

View File

@ -115,8 +115,8 @@ func (d DriftCTL) Run() (*analyser.Analysis, error) {
middlewares.NewAwsApiGatewayRestApiExpander(d.resourceFactory),
middlewares.NewAwsApiGatewayRestApiPolicyExpander(d.resourceFactory),
middlewares.NewGoogleStorageBucketIAMBindingTransformer(d.resourceFactory),
middlewares.NewGoogleStorageBucketIAMPolicyTransformer(d.resourceFactory),
middlewares.NewGoogleIAMBindingTransformer(d.resourceFactory),
middlewares.NewGoogleIAMPolicyTransformer(d.resourceFactory),
middlewares.NewGoogleLegacyBucketIAMMember(),
middlewares.NewAzurermRouteExpander(d.resourceFactory),

View File

@ -11,6 +11,256 @@ import (
"github.com/r3labs/diff/v2"
)
func TestGoogleProjectIAMBindingTransformer_Execute(t *testing.T) {
tests := []struct {
name string
resourcesFromState []*resource.Resource
expected []*resource.Resource
mock func(factory *terraform.MockResourceFactory)
}{
{
"Test that project bindings are transformed into member",
[]*resource.Resource{
{
Id: "fake",
Type: google.GoogleStorageBucketResourceType,
Attrs: &resource.Attributes{},
},
{
Id: "admin project",
Type: google.GoogleProjectIamMemberResourceType,
Attrs: &resource.Attributes{
"project": "coucou",
"role": "storage.admin",
"member": "user:elie@cloudskiff.com",
},
},
{
Id: "proj/admin",
Type: google.GoogleProjectIamBindingResourceType,
Attrs: &resource.Attributes{
"role": "storage.admin",
"project": "proj",
"members": []interface{}{
"user:elie@cloudskiff.com",
"user:william@cloudskiff.com",
},
},
},
{
Id: "proj/viewer",
Type: google.GoogleProjectIamBindingResourceType,
Attrs: &resource.Attributes{
"role": "storage.viewer",
"project": "proj",
"members": []interface{}{
"user:william@cloudskiff.com",
},
},
},
{
Id: "proj2/viewer",
Type: google.GoogleProjectIamBindingResourceType,
Attrs: &resource.Attributes{
"role": "storage.viewer",
"project": "proj2",
"members": []interface{}{
"user:william@cloudskiff.com",
},
},
},
},
[]*resource.Resource{
{
Id: "fake",
Type: google.GoogleStorageBucketResourceType,
Attrs: &resource.Attributes{},
},
{
Id: "admin project",
Type: google.GoogleProjectIamMemberResourceType,
Attrs: &resource.Attributes{
"project": "coucou",
"role": "storage.admin",
"member": "user:elie@cloudskiff.com",
},
},
{
Id: "proj/storage.admin/user:elie@cloudskiff.com",
Type: google.GoogleProjectIamMemberResourceType,
Attrs: &resource.Attributes{
"role": "storage.admin",
"project": "proj",
"member": "user:elie@cloudskiff.com",
},
},
{
Id: "proj/storage.admin/user:william@cloudskiff.com",
Type: google.GoogleProjectIamMemberResourceType,
Attrs: &resource.Attributes{
"role": "storage.admin",
"project": "proj",
"member": "user:william@cloudskiff.com",
},
},
{
Id: "proj/storage.viewer/user:william@cloudskiff.com",
Type: google.GoogleProjectIamMemberResourceType,
Attrs: &resource.Attributes{
"role": "storage.viewer",
"project": "proj",
"member": "user:william@cloudskiff.com",
},
},
{
Id: "proj2/storage.viewer/user:william@cloudskiff.com",
Type: google.GoogleProjectIamMemberResourceType,
Attrs: &resource.Attributes{
"role": "storage.viewer",
"project": "proj2",
"member": "user:william@cloudskiff.com",
},
},
},
func(factory *terraform.MockResourceFactory) {
factory.On(
"CreateAbstractResource", google.GoogleProjectIamMemberResourceType,
"proj/storage.admin/user:elie@cloudskiff.com",
map[string]interface{}{
"id": "proj/storage.admin/user:elie@cloudskiff.com",
"project": "proj",
"role": "storage.admin",
"member": "user:elie@cloudskiff.com",
}).Return(&resource.Resource{
Id: "proj/storage.admin/user:elie@cloudskiff.com",
Type: google.GoogleProjectIamMemberResourceType,
Attrs: &resource.Attributes{
"role": "storage.admin",
"project": "proj",
"member": "user:elie@cloudskiff.com",
},
}).Once()
factory.On(
"CreateAbstractResource", google.GoogleProjectIamMemberResourceType,
"proj/storage.admin/user:william@cloudskiff.com",
map[string]interface{}{
"id": "proj/storage.admin/user:william@cloudskiff.com",
"project": "proj",
"role": "storage.admin",
"member": "user:william@cloudskiff.com",
}).Return(&resource.Resource{
Id: "proj/storage.admin/user:william@cloudskiff.com",
Type: google.GoogleProjectIamMemberResourceType,
Attrs: &resource.Attributes{
"role": "storage.admin",
"project": "proj",
"member": "user:william@cloudskiff.com",
},
}).Once()
factory.On(
"CreateAbstractResource", google.GoogleProjectIamMemberResourceType,
"proj/storage.viewer/user:william@cloudskiff.com",
map[string]interface{}{
"id": "proj/storage.viewer/user:william@cloudskiff.com",
"project": "proj",
"role": "storage.viewer",
"member": "user:william@cloudskiff.com",
}).Return(&resource.Resource{
Id: "proj/storage.viewer/user:william@cloudskiff.com",
Type: google.GoogleProjectIamMemberResourceType,
Attrs: &resource.Attributes{
"role": "storage.viewer",
"project": "proj",
"member": "user:william@cloudskiff.com",
},
}).Once()
factory.On(
"CreateAbstractResource", google.GoogleProjectIamMemberResourceType,
"proj2/storage.viewer/user:william@cloudskiff.com",
map[string]interface{}{
"id": "proj2/storage.viewer/user:william@cloudskiff.com",
"project": "proj2",
"role": "storage.viewer",
"member": "user:william@cloudskiff.com",
}).Return(&resource.Resource{
Id: "proj2/storage.viewer/user:william@cloudskiff.com",
Type: google.GoogleProjectIamMemberResourceType,
Attrs: &resource.Attributes{
"role": "storage.viewer",
"project": "proj2",
"member": "user:william@cloudskiff.com",
},
}).Once()
},
},
{
"test that everything is fine when there is no bindings",
[]*resource.Resource{
{
Id: "fake",
Type: google.GoogleStorageBucketResourceType,
Attrs: &resource.Attributes{},
},
{
Id: "admin project",
Type: google.GoogleProjectIamMemberResourceType,
Attrs: &resource.Attributes{
"project": "coucou",
"role": "storage.admin",
"member": "user:elie@cloudskiff.com",
},
},
},
[]*resource.Resource{
{
Id: "fake",
Type: google.GoogleStorageBucketResourceType,
Attrs: &resource.Attributes{},
},
{
Id: "admin project",
Type: google.GoogleProjectIamMemberResourceType,
Attrs: &resource.Attributes{
"project": "coucou",
"role": "storage.admin",
"member": "user:elie@cloudskiff.com",
},
},
},
func(factory *terraform.MockResourceFactory) {
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
factory := &terraform.MockResourceFactory{}
if tt.mock != nil {
tt.mock(factory)
}
m := NewGoogleIAMBindingTransformer(factory)
err := m.Execute(&[]*resource.Resource{}, &tt.resourcesFromState)
if err != nil {
t.Fatal(err)
}
changelog, err := diff.Diff(tt.expected, tt.resourcesFromState)
if err != nil {
t.Fatal(err)
}
if len(changelog) > 0 {
for _, change := range changelog {
t.Errorf("%s got = %v, want %v", strings.Join(change.Path, "."), awsutil.Prettify(change.From), awsutil.Prettify(change.To))
}
}
})
}
}
func TestGoogleBucketIAMBindingTransformer_Execute(t *testing.T) {
tests := []struct {
name string
@ -243,7 +493,7 @@ func TestGoogleBucketIAMBindingTransformer_Execute(t *testing.T) {
tt.mock(factory)
}
m := NewGoogleStorageBucketIAMBindingTransformer(factory)
m := NewGoogleIAMBindingTransformer(factory)
err := m.Execute(&[]*resource.Resource{}, &tt.resourcesFromState)
if err != nil {
t.Fatal(err)

View File

@ -0,0 +1,65 @@
package middlewares
import (
"fmt"
"strings"
"github.com/cloudskiff/driftctl/pkg/resource"
"github.com/cloudskiff/driftctl/pkg/resource/google"
)
// GoogleIAMBindingTransformer Transforms Bucket IAM binding in bucket iam member to ease comparison.
type GoogleIAMBindingTransformer struct {
resourceFactory resource.ResourceFactory
resFieldByType map[string]string // map of the field to add to resource attribute for all supported type
}
func NewGoogleIAMBindingTransformer(resourceFactory resource.ResourceFactory) *GoogleIAMBindingTransformer {
return &GoogleIAMBindingTransformer{
resourceFactory,
map[string]string{
google.GoogleStorageBucketIamBindingResourceType: "bucket",
google.GoogleProjectIamBindingResourceType: "project",
},
}
}
func (m *GoogleIAMBindingTransformer) Execute(_, resourcesFromState *[]*resource.Resource) error {
resources := make([]*resource.Resource, 0)
for _, stateRes := range *resourcesFromState {
// Ignore all resources other than IamBinding
resType := stateRes.ResourceType()
resField, supported := m.resFieldByType[resType]
if !supported {
resources = append(resources, stateRes)
continue
}
resName := *stateRes.Attrs.GetString(resField)
roleName := *stateRes.Attrs.GetString("role")
members, _ := stateRes.Attrs.Get("members")
for _, member := range members.([]interface{}) {
id := fmt.Sprintf("%s/%s/%s", resName, roleName, member)
resources = append(
resources,
m.resourceFactory.CreateAbstractResource(
fmt.Sprintf("%s_member", strings.TrimSuffix(resType, "_binding")),
id,
map[string]interface{}{
"id": id,
resField: resName,
"role": roleName,
"member": member.(string),
},
),
)
}
}
*resourcesFromState = resources
return nil
}

View File

@ -11,6 +11,202 @@ import (
"github.com/r3labs/diff/v2"
)
func TestGoogleProjectIAMPolicyTransformer_Execute(t *testing.T) {
tests := []struct {
name string
resourcesFromState []*resource.Resource
expected []*resource.Resource
mock func(factory *terraform.MockResourceFactory)
}{
{
"Test that project policy are transformed into bindings",
[]*resource.Resource{
{
Id: "b/bucket-1",
Type: google.GoogleStorageBucketResourceType,
Attrs: &resource.Attributes{},
},
{
Id: "b/bucket-2",
Type: google.GoogleStorageBucketResourceType,
Attrs: &resource.Attributes{},
},
{
Id: "project-1",
Type: google.GoogleProjectIamPolicyResourceType,
Attrs: &resource.Attributes{
"project": "project-1",
"id": "project-1",
"policy_data": "{\"bindings\":[{\"members\":[\"serviceAccount:driftctl@cloudskiff-dev-martin.iam.gserviceaccount.com\"],\"role\":\"roles/storage.admin\"},{\"members\":[\"user:william.beuil@cloudskiff.com\"],\"role\":\"roles/storage.objectViewer\"}]}",
},
},
{
Id: "dctlgstorageprojectiambinding-2",
Type: google.GoogleProjectIamPolicyResourceType,
Attrs: &resource.Attributes{
"project": "project-2",
"etag": "CAU=",
"id": "project-2",
"policy_data": "{\"bindings\":[{\"members\":[\"serviceAccount:driftctl@cloudskiff-dev-martin.iam.gserviceaccount.com\"],\"role\":\"roles/storage.admin\"},{\"members\":[\"user:william.beuil@cloudskiff.com\"],\"role\":\"roles/storage.objectViewer\"}]}",
},
},
},
[]*resource.Resource{
{
Id: "b/bucket-1",
Type: google.GoogleStorageBucketResourceType,
Attrs: &resource.Attributes{},
},
{
Id: "b/bucket-2",
Type: google.GoogleStorageBucketResourceType,
Attrs: &resource.Attributes{},
},
{
Id: "project-1/roles/storage.admin/serviceAccount:driftctl@cloudskiff-dev-martin.iam.gserviceaccount.com",
Type: google.GoogleProjectIamMemberResourceType,
Attrs: &resource.Attributes{
"id": "project-1/roles/storage.admin/serviceAccount:driftctl@cloudskiff-dev-martin.iam.gserviceaccount.com",
"role": "roles/storage.admin",
"project": "project-1",
"member": "serviceAccount:driftctl@cloudskiff-dev-martin.iam.gserviceaccount.com",
},
},
{
Id: "project-1/roles/storage.objectViewer/user:william.beuil@cloudskiff.com",
Type: google.GoogleProjectIamMemberResourceType,
Attrs: &resource.Attributes{
"id": "project-1/roles/storage.objectViewer/user:william.beuil@cloudskiff.com",
"role": "roles/storage.objectViewer",
"project": "project-1",
"member": "user:william.beuil@cloudskiff.com",
},
},
{
Id: "project-2/roles/storage.admin/serviceAccount:driftctl@cloudskiff-dev-martin.iam.gserviceaccount.com",
Type: google.GoogleProjectIamMemberResourceType,
Attrs: &resource.Attributes{
"id": "project-2/roles/storage.admin/serviceAccount:driftctl@cloudskiff-dev-martin.iam.gserviceaccount.com",
"role": "roles/storage.admin",
"project": "project-2",
"member": "serviceAccount:driftctl@cloudskiff-dev-martin.iam.gserviceaccount.com",
},
},
{
Id: "project-2/roles/storage.objectViewer/user:william.beuil@cloudskiff.com",
Type: google.GoogleProjectIamMemberResourceType,
Attrs: &resource.Attributes{
"id": "project-2/roles/storage.objectViewer/user:william.beuil@cloudskiff.com",
"role": "roles/storage.objectViewer",
"project": "project-2",
"member": "user:william.beuil@cloudskiff.com",
},
},
},
func(factory *terraform.MockResourceFactory) {
factory.On(
"CreateAbstractResource", google.GoogleProjectIamMemberResourceType,
"project-1/roles/storage.admin/serviceAccount:driftctl@cloudskiff-dev-martin.iam.gserviceaccount.com",
map[string]interface{}{
"id": "project-1/roles/storage.admin/serviceAccount:driftctl@cloudskiff-dev-martin.iam.gserviceaccount.com",
"project": "project-1",
"role": "roles/storage.admin",
"member": "serviceAccount:driftctl@cloudskiff-dev-martin.iam.gserviceaccount.com",
}).Return(&resource.Resource{
Id: "project-1/roles/storage.admin/serviceAccount:driftctl@cloudskiff-dev-martin.iam.gserviceaccount.com",
Type: google.GoogleProjectIamMemberResourceType,
Attrs: &resource.Attributes{
"id": "project-1/roles/storage.admin/serviceAccount:driftctl@cloudskiff-dev-martin.iam.gserviceaccount.com",
"role": "roles/storage.admin",
"project": "project-1",
"member": "serviceAccount:driftctl@cloudskiff-dev-martin.iam.gserviceaccount.com",
},
}).Once()
factory.On(
"CreateAbstractResource", google.GoogleProjectIamMemberResourceType,
"project-1/roles/storage.objectViewer/user:william.beuil@cloudskiff.com",
map[string]interface{}{
"id": "project-1/roles/storage.objectViewer/user:william.beuil@cloudskiff.com",
"project": "project-1",
"role": "roles/storage.objectViewer",
"member": "user:william.beuil@cloudskiff.com",
}).Return(&resource.Resource{
Id: "project-1/roles/storage.objectViewer/user:william.beuil@cloudskiff.com",
Type: google.GoogleProjectIamMemberResourceType,
Attrs: &resource.Attributes{
"id": "project-1/roles/storage.objectViewer/user:william.beuil@cloudskiff.com",
"role": "roles/storage.objectViewer",
"project": "project-1",
"member": "user:william.beuil@cloudskiff.com",
},
}).Once()
factory.On(
"CreateAbstractResource", google.GoogleProjectIamMemberResourceType,
"project-2/roles/storage.admin/serviceAccount:driftctl@cloudskiff-dev-martin.iam.gserviceaccount.com",
map[string]interface{}{
"id": "project-2/roles/storage.admin/serviceAccount:driftctl@cloudskiff-dev-martin.iam.gserviceaccount.com",
"project": "project-2",
"role": "roles/storage.admin",
"member": "serviceAccount:driftctl@cloudskiff-dev-martin.iam.gserviceaccount.com",
}).Return(&resource.Resource{
Id: "project-2/roles/storage.admin/serviceAccount:driftctl@cloudskiff-dev-martin.iam.gserviceaccount.com",
Type: google.GoogleProjectIamMemberResourceType,
Attrs: &resource.Attributes{
"id": "project-2/roles/storage.admin/serviceAccount:driftctl@cloudskiff-dev-martin.iam.gserviceaccount.com",
"role": "roles/storage.admin",
"project": "project-2",
"member": "serviceAccount:driftctl@cloudskiff-dev-martin.iam.gserviceaccount.com",
},
}).Once()
factory.On(
"CreateAbstractResource", google.GoogleProjectIamMemberResourceType,
"project-2/roles/storage.objectViewer/user:william.beuil@cloudskiff.com",
map[string]interface{}{
"id": "project-2/roles/storage.objectViewer/user:william.beuil@cloudskiff.com",
"project": "project-2",
"role": "roles/storage.objectViewer",
"member": "user:william.beuil@cloudskiff.com",
}).Return(&resource.Resource{
Id: "project-2/roles/storage.objectViewer/user:william.beuil@cloudskiff.com",
Type: google.GoogleProjectIamMemberResourceType,
Attrs: &resource.Attributes{
"id": "project-2/roles/storage.objectViewer/user:william.beuil@cloudskiff.com",
"role": "roles/storage.objectViewer",
"project": "project-2",
"member": "user:william.beuil@cloudskiff.com",
},
}).Once()
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
factory := &terraform.MockResourceFactory{}
if tt.mock != nil {
tt.mock(factory)
}
m := NewGoogleIAMPolicyTransformer(factory)
err := m.Execute(&[]*resource.Resource{}, &tt.resourcesFromState)
if err != nil {
t.Fatal(err)
}
changelog, err := diff.Diff(tt.expected, tt.resourcesFromState)
if err != nil {
t.Fatal(err)
}
if len(changelog) > 0 {
for _, change := range changelog {
t.Errorf("%s got = %v, want %v", strings.Join(change.Path, "."), awsutil.Prettify(change.From), awsutil.Prettify(change.To))
}
}
})
}
}
func TestGoogleBucketIAMPolicyTransformer_Execute(t *testing.T) {
tests := []struct {
name string
@ -189,7 +385,7 @@ func TestGoogleBucketIAMPolicyTransformer_Execute(t *testing.T) {
tt.mock(factory)
}
m := NewGoogleStorageBucketIAMPolicyTransformer(factory)
m := NewGoogleIAMPolicyTransformer(factory)
err := m.Execute(&[]*resource.Resource{}, &tt.resourcesFromState)
if err != nil {
t.Fatal(err)

View File

@ -3,6 +3,7 @@ package middlewares
import (
"encoding/json"
"fmt"
"strings"
"github.com/cloudskiff/driftctl/pkg/resource"
"github.com/cloudskiff/driftctl/pkg/resource/google"
@ -11,10 +12,16 @@ import (
// GoogleStorageBucketIAMPolicyTransformer Transforms Bucket IAM policy in bucket iam binding to ease comparison.
type GoogleStorageBucketIAMPolicyTransformer struct {
resourceFactory resource.ResourceFactory
resFieldByType map[string]string // map of the field to add to resource attribute for all supported type
}
func NewGoogleStorageBucketIAMPolicyTransformer(resourceFactory resource.ResourceFactory) *GoogleStorageBucketIAMPolicyTransformer {
return &GoogleStorageBucketIAMPolicyTransformer{resourceFactory}
func NewGoogleIAMPolicyTransformer(resourceFactory resource.ResourceFactory) *GoogleStorageBucketIAMPolicyTransformer {
return &GoogleStorageBucketIAMPolicyTransformer{
resourceFactory,
map[string]string{
google.GoogleStorageBucketIamPolicyResourceType: "bucket",
google.GoogleProjectIamPolicyResourceType: "project",
}}
}
func (m *GoogleStorageBucketIAMPolicyTransformer) Execute(_, resourcesFromState *[]*resource.Resource) error {
@ -23,12 +30,14 @@ func (m *GoogleStorageBucketIAMPolicyTransformer) Execute(_, resourcesFromState
for _, stateRes := range *resourcesFromState {
// Ignore all resources other than BucketIamBinding
if stateRes.ResourceType() != google.GoogleStorageBucketIamPolicyResourceType {
resType := stateRes.ResourceType()
resField, supported := m.resFieldByType[resType]
if !supported {
resources = append(resources, stateRes)
continue
}
bucket := *stateRes.Attrs.GetString("bucket")
resName := *stateRes.Attrs.GetString(resField)
policyJSON := *stateRes.Attrs.GetString("policy_data")
policies := policyDataType{}
@ -38,20 +47,20 @@ func (m *GoogleStorageBucketIAMPolicyTransformer) Execute(_, resourcesFromState
}
for _, policy := range policies.Bindings {
roleName := policy.Role
members := policy.Members
roleName := policy["role"].(string)
members := policy["members"].([]interface{})
for _, member := range members {
id := fmt.Sprintf("%s/%s/%s", bucket, roleName, member)
id := fmt.Sprintf("%s/%s/%s", resName, roleName, member)
resources = append(
resources,
m.resourceFactory.CreateAbstractResource(
google.GoogleStorageBucketIamMemberResourceType,
fmt.Sprintf("%s_member", strings.TrimSuffix(resType, "_policy")),
id,
map[string]interface{}{
"id": id,
"bucket": bucket,
resField: resName,
"role": roleName,
"member": member,
"member": member.(string),
},
),
)
@ -65,8 +74,5 @@ func (m *GoogleStorageBucketIAMPolicyTransformer) Execute(_, resourcesFromState
}
type policyDataType struct {
Bindings []struct {
Members []string
Role string
}
Bindings []map[string]interface{}
}

View File

@ -0,0 +1,57 @@
package google
import (
"fmt"
remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error"
"github.com/cloudskiff/driftctl/pkg/remote/google/repository"
"github.com/cloudskiff/driftctl/pkg/resource"
"github.com/cloudskiff/driftctl/pkg/resource/google"
)
type GoogleProjectIamMemberEnumerator struct {
repository repository.CloudResourceManagerRepository
factory resource.ResourceFactory
}
func NewGoogleProjectIamMemberEnumerator(repo repository.CloudResourceManagerRepository, factory resource.ResourceFactory) *GoogleProjectIamMemberEnumerator {
return &GoogleProjectIamMemberEnumerator{
repository: repo,
factory: factory,
}
}
func (e *GoogleProjectIamMemberEnumerator) SupportedType() resource.ResourceType {
return google.GoogleProjectIamMemberResourceType
}
func (e *GoogleProjectIamMemberEnumerator) Enumerate() ([]*resource.Resource, error) {
results := make([]*resource.Resource, 0)
bindingsByProject, err := e.repository.ListProjectsBindings()
if err != nil {
return nil, remoteerror.NewResourceListingError(err, string(e.SupportedType()))
}
for project, bindings := range bindingsByProject {
for roleName, members := range bindings {
for _, member := range members {
id := fmt.Sprintf("%s/%s/%s", project, roleName, member)
results = append(
results,
e.factory.CreateAbstractResource(
string(e.SupportedType()),
id,
map[string]interface{}{
"id": id,
"project": project,
"role": roleName,
"member": member,
},
),
)
}
}
}
return results, err
}

View File

@ -13,6 +13,7 @@ import (
"github.com/cloudskiff/driftctl/pkg/resource"
"github.com/cloudskiff/driftctl/pkg/resource/google"
"github.com/cloudskiff/driftctl/pkg/terraform"
"google.golang.org/api/cloudresourcemanager/v1"
)
func Init(version string, alerter *alerter.Alerter,
@ -45,8 +46,14 @@ func Init(version string, alerter *alerter.Alerter,
return err
}
crmService, err := cloudresourcemanager.NewService(ctx)
if err != nil {
return err
}
assetRepository := repository.NewAssetRepository(assetClient, provider.GetConfig(), repositoryCache)
storageRepository := repository.NewStorageRepository(storageClient, repositoryCache)
iamRepository := repository.NewCloudResourceManagerRepository(crmService, provider.GetConfig(), repositoryCache)
providerLibrary.AddProvider(terraform.GOOGLE, provider)
deserializer := resource.NewDeserializer(factory)
@ -61,6 +68,9 @@ func Init(version string, alerter *alerter.Alerter,
remoteLibrary.AddEnumerator(NewGoogleComputeInstanceEnumerator(assetRepository, factory))
remoteLibrary.AddEnumerator(NewGoogleProjectIamMemberEnumerator(iamRepository, factory))
remoteLibrary.AddDetailsFetcher(google.GoogleProjectIamMemberResourceType, common.NewGenericDetailsFetcher(google.GoogleProjectIamMemberResourceType, provider, deserializer))
remoteLibrary.AddEnumerator(NewGoogleStorageBucketIamMemberEnumerator(assetRepository, storageRepository, factory))
remoteLibrary.AddDetailsFetcher(google.GoogleStorageBucketIamMemberResourceType, common.NewGenericDetailsFetcher(google.GoogleStorageBucketIamMemberResourceType, provider, deserializer))

View File

@ -0,0 +1,57 @@
package repository
import (
"sync"
"github.com/cloudskiff/driftctl/pkg/remote/cache"
"github.com/cloudskiff/driftctl/pkg/remote/google/config"
"google.golang.org/api/cloudresourcemanager/v1"
)
type CloudResourceManagerRepository interface {
ListProjectsBindings() (map[string]map[string][]string, error)
}
type cloudResourceManagerRepository struct {
service *cloudresourcemanager.Service
config config.GCPTerraformConfig
cache cache.Cache
lock sync.Locker
}
func NewCloudResourceManagerRepository(service *cloudresourcemanager.Service, config config.GCPTerraformConfig, cache cache.Cache) CloudResourceManagerRepository {
return &cloudResourceManagerRepository{
service: service,
config: config,
cache: cache,
lock: &sync.Mutex{},
}
}
func (s *cloudResourceManagerRepository) ListProjectsBindings() (map[string]map[string][]string, error) {
s.lock.Lock()
defer s.lock.Unlock()
if cachedResults := s.cache.Get("ListProjectsBindings"); cachedResults != nil {
return cachedResults.(map[string]map[string][]string), nil
}
request := new(cloudresourcemanager.GetIamPolicyRequest)
policy, err := s.service.Projects.GetIamPolicy(s.config.Project, request).Do()
if err != nil {
return nil, err
}
bindings := make(map[string][]string)
for _, binding := range policy.Bindings {
bindings[binding.Role] = binding.Members
}
bindingsByProject := make(map[string]map[string][]string)
bindingsByProject[s.config.Project] = bindings
s.cache.Put("ListProjectsBindings", bindingsByProject)
return bindingsByProject, nil
}

View File

@ -0,0 +1,33 @@
// Code generated by mockery v0.0.0-dev. DO NOT EDIT.
package repository
import mock "github.com/stretchr/testify/mock"
// MockCloudResourceManagerRepository is an autogenerated mock type for the CloudResourceManagerRepository type
type MockCloudResourceManagerRepository struct {
mock.Mock
}
// ListProjectsBindings provides a mock function with given fields:
func (_m *MockCloudResourceManagerRepository) ListProjectsBindings() (map[string]map[string][]string, error) {
ret := _m.Called()
var r0 map[string]map[string][]string
if rf, ok := ret.Get(0).(func() map[string]map[string][]string); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(map[string]map[string][]string)
}
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}

View File

@ -0,0 +1,138 @@
package remote
import (
"testing"
"github.com/cloudskiff/driftctl/mocks"
"github.com/cloudskiff/driftctl/pkg/filter"
"github.com/cloudskiff/driftctl/pkg/remote/alerts"
"github.com/cloudskiff/driftctl/pkg/remote/common"
remoteerr "github.com/cloudskiff/driftctl/pkg/remote/error"
"github.com/cloudskiff/driftctl/pkg/remote/google"
"github.com/cloudskiff/driftctl/pkg/remote/google/repository"
"github.com/cloudskiff/driftctl/pkg/resource"
googleresource "github.com/cloudskiff/driftctl/pkg/resource/google"
"github.com/cloudskiff/driftctl/pkg/terraform"
"github.com/cloudskiff/driftctl/test"
"github.com/cloudskiff/driftctl/test/goldenfile"
testresource "github.com/cloudskiff/driftctl/test/resource"
terraform2 "github.com/cloudskiff/driftctl/test/terraform"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
func TestGoogleProjectIAMMember(t *testing.T) {
cases := []struct {
test string
dirName string
repositoryMock func(repository *repository.MockCloudResourceManagerRepository)
responseErr error
setupAlerterMock func(alerter *mocks.AlerterInterface)
wantErr error
}{
{
test: "no bindings",
dirName: "google_project_member_empty",
repositoryMock: func(repository *repository.MockCloudResourceManagerRepository) {
repository.On("ListProjectsBindings").Return(map[string]map[string][]string{}, nil)
},
wantErr: nil,
},
{
test: "Cannot list bindings",
dirName: "google_project_member_listing_error",
repositoryMock: func(repository *repository.MockCloudResourceManagerRepository) {
repository.On("ListProjectsBindings").Return(
map[string]map[string][]string{},
errors.New("googleapi: Error 403: driftctl-acc-circle@driftctl-qa-1.iam.gserviceaccount.com does not have project.getIamPolicy access., forbidden"))
},
setupAlerterMock: func(alerter *mocks.AlerterInterface) {
alerter.On(
"SendAlert",
"google_project_iam_member",
alerts.NewRemoteAccessDeniedAlert(
common.RemoteGoogleTerraform,
remoteerr.NewResourceListingError(
errors.New("googleapi: Error 403: driftctl-acc-circle@driftctl-qa-1.iam.gserviceaccount.com does not have project.getIamPolicy access., forbidden"),
"google_project_iam_member",
),
alerts.EnumerationPhase,
),
).Once()
},
wantErr: nil,
},
{
test: "multiples storage buckets, multiple bindings",
dirName: "google_project_member_listing_multiple",
repositoryMock: func(repository *repository.MockCloudResourceManagerRepository) {
repository.On("ListProjectsBindings").Return(map[string]map[string][]string{
"": {
"roles/editor": {
"user:martin.guibert@cloudskiff.com",
"serviceAccount:drifctl-admin@cloudskiff-dev-martin.iam.gserviceaccount.com",
},
"roles/storage.admin": {"user:martin.guibert@cloudskiff.com"},
"roles/viewer": {"serviceAccount:driftctl@cloudskiff-dev-martin.iam.gserviceaccount.com"},
"roles/cloudasset.viewer": {"serviceAccount:driftctl@cloudskiff-dev-martin.iam.gserviceaccount.com"},
"roles/iam.securityReviewer": {"serviceAccount:driftctl@cloudskiff-dev-martin.iam.gserviceaccount.com"},
},
}, nil)
},
wantErr: nil,
},
}
providerVersion := "3.78.0"
resType := resource.ResourceType(googleresource.GoogleProjectIamMemberResourceType)
schemaRepository := testresource.InitFakeSchemaRepository("google", providerVersion)
googleresource.InitResourcesMetadata(schemaRepository)
factory := terraform.NewTerraformResourceFactory(schemaRepository)
deserializer := resource.NewDeserializer(factory)
for _, c := range cases {
t.Run(c.test, func(tt *testing.T) {
shouldUpdate := c.dirName == *goldenfile.Update
scanOptions := ScannerOptions{Deep: true}
providerLibrary := terraform.NewProviderLibrary()
remoteLibrary := common.NewRemoteLibrary()
// Initialize mocks
alerter := &mocks.AlerterInterface{}
if c.setupAlerterMock != nil {
c.setupAlerterMock(alerter)
}
realProvider, err := terraform2.InitTestGoogleProvider(providerLibrary, providerVersion)
if err != nil {
tt.Fatal(err)
}
provider := terraform2.NewFakeTerraformProvider(realProvider)
provider.WithResponse(c.dirName)
managerRepository := &repository.MockCloudResourceManagerRepository{}
if c.repositoryMock != nil {
c.repositoryMock(managerRepository)
}
remoteLibrary.AddEnumerator(google.NewGoogleProjectIamMemberEnumerator(managerRepository, factory))
testFilter := &filter.MockFilter{}
testFilter.On("IsTypeIgnored", mock.Anything).Return(false)
s := NewScanner(remoteLibrary, alerter, scanOptions, testFilter)
got, err := s.Resources()
assert.Equal(tt, c.wantErr, err)
if err != nil {
return
}
alerter.AssertExpectations(tt)
testFilter.AssertExpectations(tt)
test.TestAgainstGoldenFile(got, resType.String(), c.dirName, provider, deserializer, shouldUpdate, tt)
})
}
}

View File

@ -0,0 +1,50 @@
[
{
"condition": null,
"etag": null,
"id": "/roles/storage.admin/user:martin.guibert@cloudskiff.com",
"member": "user:martin.guibert@cloudskiff.com",
"project": "",
"role": "roles/storage.admin"
},
{
"condition": null,
"etag": null,
"id": "/roles/viewer/serviceAccount:driftctl@cloudskiff-dev-martin.iam.gserviceaccount.com",
"member": "serviceAccount:driftctl@cloudskiff-dev-martin.iam.gserviceaccount.com",
"project": "",
"role": "roles/viewer"
},
{
"condition": null,
"etag": null,
"id": "/roles/cloudasset.viewer/serviceAccount:driftctl@cloudskiff-dev-martin.iam.gserviceaccount.com",
"member": "serviceAccount:driftctl@cloudskiff-dev-martin.iam.gserviceaccount.com",
"project": "",
"role": "roles/cloudasset.viewer"
},
{
"condition": null,
"etag": null,
"id": "/roles/iam.securityReviewer/serviceAccount:driftctl@cloudskiff-dev-martin.iam.gserviceaccount.com",
"member": "serviceAccount:driftctl@cloudskiff-dev-martin.iam.gserviceaccount.com",
"project": "",
"role": "roles/iam.securityReviewer"
},
{
"condition": null,
"etag": null,
"id": "/roles/editor/serviceAccount:drifctl-admin@cloudskiff-dev-martin.iam.gserviceaccount.com",
"member": "serviceAccount:drifctl-admin@cloudskiff-dev-martin.iam.gserviceaccount.com",
"project": "",
"role": "roles/editor"
},
{
"condition": null,
"etag": null,
"id": "/roles/editor/user:martin.guibert@cloudskiff.com",
"member": "user:martin.guibert@cloudskiff.com",
"project": "",
"role": "roles/editor"
}
]

View File

@ -0,0 +1,3 @@
package google
const GoogleProjectIamBindingResourceType = "google_project_iam_binding"

View File

@ -0,0 +1,21 @@
package google
import "github.com/cloudskiff/driftctl/pkg/resource"
const GoogleProjectIamMemberResourceType = "google_project_iam_member"
func initGoogleProjectIAMMemberMetadata(resourceSchemaRepository resource.SchemaRepositoryInterface) {
resourceSchemaRepository.SetNormalizeFunc(GoogleProjectIamMemberResourceType, func(res *resource.Resource) {
res.Attributes().SafeDelete([]string{"force_destroy"})
res.Attributes().SafeDelete([]string{"etag"})
})
resourceSchemaRepository.SetResolveReadAttributesFunc(GoogleProjectIamMemberResourceType, func(res *resource.Resource) map[string]string {
return map[string]string{
"project": *res.Attrs.GetString("project"),
"role": *res.Attrs.GetString("role"),
"member": *res.Attrs.GetString("member"),
}
})
resourceSchemaRepository.SetFlags(GoogleProjectIamMemberResourceType, resource.FlagDeepMode)
}

View File

@ -0,0 +1,31 @@
package google_test
import (
"testing"
"github.com/cloudskiff/driftctl/test"
"github.com/cloudskiff/driftctl/test/acceptance"
)
func TestAcc_Google_ProjectIAMMember(t *testing.T) {
acceptance.Run(t, acceptance.AccTestCase{
TerraformVersion: "0.15.5",
Paths: []string{"./testdata/acc/google_project_iam_member"},
Args: []string{
"scan",
"--to", "gcp+tf",
"--filter", "Type=='google_project_iam_member'",
},
Checks: []acceptance.AccCheck{
{
Check: func(result *test.ScanResult, stdout string, err error) {
if err != nil {
t.Fatal(err)
}
result.AssertInfrastructureIsInSync()
result.AssertManagedCount(2)
},
},
},
})
}

View File

@ -0,0 +1,3 @@
package google
const GoogleProjectIamPolicyResourceType = "google_project_iam_policy"

View File

@ -10,4 +10,5 @@ func InitResourcesMetadata(resourceSchemaRepository resource.SchemaRepositoryInt
initGoogleStorageBucketIamBMemberMetadata(resourceSchemaRepository)
initGoogleComputeInstanceGroupMetadata(resourceSchemaRepository)
initGoogleBigqueryDatasetMetadata(resourceSchemaRepository)
initGoogleProjectIAMMemberMetadata(resourceSchemaRepository)
}

View File

@ -0,0 +1,20 @@
provider "google" {}
terraform {
required_version = "~> 0.15.0"
required_providers {
google = {
version = "3.78.0"
}
}
}
resource "google_project_iam_member" "elie1" {
role = "roles/editor"
member = "user:elie.charra@cloudskiff.com"
}
resource "google_project_iam_member" "will1" {
role = "roles/viewer"
member = "user:william.beuil@cloudskiff.com"
}

View File

@ -145,6 +145,13 @@ var supportedTypes = map[string]ResourceTypeMeta{
"google_dns_managed_zone": {},
"google_compute_instance_group": {},
"google_bigquery_dataset": {},
"google_project_iam_member": {},
"google_project_iam_binding": {children: []ResourceType{
"google_project_iam_member",
}},
"google_project_iam_policy": {children: []ResourceType{
"google_project_iam_member",
}},
"azurerm_storage_account": {},
"azurerm_storage_container": {},