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

Backport 1.7.x: Fixes roleset bindings for BigQuery datasets (#130) #134

Merged
merged 1 commit into from
Jan 20, 2022
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
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
## Next

## v0.11.1
### Unreleased

BUG FIXES:

* Fixes role bindings for BigQuery dataset resources [[GH-130](https://github.com/hashicorp/vault-plugin-secrets-gcp/pull/130)]

## v0.10.3
### Unreleased

BUG FIXES:

* Fixes role bindings for BigQuery dataset resources [[GH-130](https://github.com/hashicorp/vault-plugin-secrets-gcp/pull/130)]

## 0.8.1
### December 14, 2020

Expand Down
42 changes: 39 additions & 3 deletions plugin/iamutil/dataset_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,18 @@ func datasetAsPolicy(ds *Dataset) *Policy {
for _, accessBinding := range ds.Access {
var iamMember string

// Role mapping must be applied for datasets in order to properly
// detect when to change bindings (via RemoveBindings()) after a
// modification or deletion occurs. This is due to BigQuery
// access roles accepting both legacy (e.g., OWNER) and current
// (e.g., roles/bigquery.dataOwner) role references. The API will
// only return the legacy format, so this mapping allows us to properly
// diff the current and desired roles to set the access policy.
//
// See the access[].role description in the following document for details
// https://cloud.google.com/bigquery/docs/reference/rest/v2/datasets#Dataset
role := mapLegacyRoles(accessBinding.Role)

//NOTE: Can either have GroupByEmail or UserByEmail but not both
if accessBinding.GroupByEmail != "" {
iamMember = fmt.Sprintf("group:%s", accessBinding.GroupByEmail)
Expand All @@ -126,11 +138,11 @@ func datasetAsPolicy(ds *Dataset) *Policy {
} else {
iamMember = fmt.Sprintf("user:%s", accessBinding.UserByEmail)
}
if binding, ok := bindingMap[accessBinding.Role]; ok {
if binding, ok := bindingMap[role]; ok {
binding.Members = append(binding.Members, iamMember)
} else {
bindingMap[accessBinding.Role] = &Binding{
Role: accessBinding.Role,
bindingMap[role] = &Binding{
Role: role,
Members: []string{iamMember},
}
}
Expand All @@ -140,3 +152,27 @@ func datasetAsPolicy(ds *Dataset) *Policy {
}
return policy
}

// mapLegacyRoles returns a current role name given a legacy role name.
//
// The following role mappings will be applied:
// - OWNER -> roles/bigquery.dataOwner
// - WRITER -> roles/bigquery.dataEditor
// - READER -> roles/bigquery.dataViewer
//
// See the access[].role description in the following document for details
// https://cloud.google.com/bigquery/docs/reference/rest/v2/datasets#Dataset
//
// Returns the given role if no mapping applies.
func mapLegacyRoles(role string) string {
switch role {
case "OWNER":
return "roles/bigquery.dataOwner"
case "WRITER":
return "roles/bigquery.dataEditor"
case "READER":
return "roles/bigquery.dataViewer"
default:
return role
}
}
4 changes: 2 additions & 2 deletions plugin/iamutil/dataset_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,8 +210,8 @@ func getTestFixtures() (*Policy, *Dataset) {
func testResource() *DatasetResource {
return &DatasetResource{
relativeId: &gcputil.RelativeResourceName{
Name: "dataset",
TypeKey: "projects/dataset",
Name: "datasets",
TypeKey: "projects/datasets",
IdTuples: map[string]string{
"projects": "project",
"datasets": "dataset",
Expand Down
2 changes: 1 addition & 1 deletion plugin/iamutil/resource_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ func (apis GeneratedResources) Parse(rawName string) (Resource, error) {
return nil, err
}
switch cfg.TypeKey {
case "projects/dataset":
case "projects/datasets":
return &DatasetResource{relativeId: relName, config: cfg}, nil
default:
return &IamResource{relativeId: relName, config: cfg}, nil
Expand Down
18 changes: 9 additions & 9 deletions plugin/iamutil/resource_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (

var letters = "ABCDEFGHIJKLMNOP"

func TestEnabledIamResources_RelativeName(t *testing.T) {
func TestEnabledResources_RelativeName(t *testing.T) {
enabledApis := GetEnabledResources()

for resourceType, services := range generatedResources {
Expand All @@ -39,7 +39,7 @@ func TestEnabledIamResources_RelativeName(t *testing.T) {
}

if resource != nil {
if err = verifyResource(resourceType, resource.(*IamResource)); err != nil {
if err = verifyResource(resourceType, resource); err != nil {
t.Errorf("could not verify resource for relative resource name %q: %sv", testRelName, err)
}
}
Expand All @@ -50,7 +50,7 @@ func TestEnabledIamResources_RelativeName(t *testing.T) {
}
}

func TestEnabledIamResources_FullName(t *testing.T) {
func TestEnabledResources_FullName(t *testing.T) {
enabledApis := GetEnabledResources()

for resourceType, services := range generatedResources {
Expand All @@ -67,7 +67,7 @@ func TestEnabledIamResources_FullName(t *testing.T) {
t.Errorf("failed to get resource for full resource name %s (type: %s): %v", testFullName, resourceType, err)
continue
}
if err = verifyResource(resourceType, resource.(*IamResource)); err != nil {
if err = verifyResource(resourceType, resource); err != nil {
t.Errorf("could not verify resource for relative resource name %s: %v", testFullName, err)
continue
}
Expand Down Expand Up @@ -226,21 +226,21 @@ func getFakeId(resourceType string) string {
return strings.Trim(fakeId, "/")
}

func verifyResource(rType string, resource *IamResource) (err error) {
func verifyResource(rType string, resource Resource) (err error) {
var req *http.Request
if resource.relativeId.TypeKey != rType {
return fmt.Errorf("expected resource type %s, actual resource has different type %s", rType, resource.relativeId.TypeKey)
if resource.GetRelativeId().TypeKey != rType {
return fmt.Errorf("expected resource type %s, actual resource has different type %s", rType, resource.GetRelativeId().TypeKey)
}

req, err = constructRequest(resource, &resource.config.GetMethod, nil)
req, err = constructRequest(resource, &resource.GetConfig().GetMethod, nil)
if err != nil {
return errwrap.Wrapf("unable to construct GetIamPolicyRequest: {{err}}", err)
}
if err := verifyConstructRequest(req, rType); err != nil {
return err
}

req, err = constructRequest(resource, &resource.config.SetMethod, strings.NewReader("{}"))
req, err = constructRequest(resource, &resource.GetConfig().SetMethod, strings.NewReader("{}"))
if err != nil {
return errwrap.Wrapf("unable to construct SetIamPolicyRequest: {{err}}", err)
}
Expand Down