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

Add alias types for IAM and GCE logins #89

Merged
merged 5 commits into from
Apr 24, 2020
Merged
Show file tree
Hide file tree
Changes from 3 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
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,8 @@ fmtcheck:
fmt:
gofmt -w $(GOFMT_FILES)

mocks:
mockgen -destination ${CURDIR}/plugin/mocks_test.go -package gcpauth github.com/hashicorp/vault/sdk/logical SystemView,Storage


.PHONY: bin default generate test vet bootstrap fmt fmtcheck
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ module github.com/hashicorp/vault-plugin-auth-gcp
go 1.12

require (
github.com/golang/mock v1.4.3
github.com/hashicorp/errwrap v1.0.0
github.com/hashicorp/go-cleanhttp v0.5.1
github.com/hashicorp/go-gcp-common v0.6.0
github.com/hashicorp/go-hclog v0.12.0
github.com/hashicorp/go-uuid v1.0.2
github.com/hashicorp/vault/api v1.0.5-0.20200215224050-f6547fa8e820
github.com/hashicorp/vault/sdk v0.1.14-0.20200215224050-f6547fa8e820
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect
Expand Down
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekf
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
Expand Down Expand Up @@ -215,6 +218,7 @@ golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e h1:nFYrTHrdrAOpShe27kaFHjsqY
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be h1:QAcqgptGM8IQBC9K/RC4o+O9YmqEm0diQn9QmZw/0mU=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db h1:6/JqlYfC1CCaLnGceQTI+sDGhC9UBSPAsBqI0Gun6kU=
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
Expand All @@ -227,6 +231,8 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135 h1:5Beo0mZN8dRzgrMMkDp0jc8YXQKx9DiJ2k1dkvGsn5A=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
google.golang.org/api v0.0.0-20181220000619-583d854617af/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.2.0/go.mod h1:IfRCZScioGtypHNTlz3gFk67J8uePVW7uDTBzXuIkhU=
Expand Down Expand Up @@ -262,3 +268,7 @@ honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
97 changes: 97 additions & 0 deletions plugin/aliasing.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package gcpauth

import (
"fmt"
"sort"
"strconv"
"strings"

"google.golang.org/api/compute/v1"
"google.golang.org/api/iam/v1"
)

type aliasData struct {
tyrannosaurus-becks marked this conversation as resolved.
Show resolved Hide resolved
role *gcpRole
svcAccount *iam.ServiceAccount
instance *compute.Instance
}

type iamAliaser func(role *gcpRole, svcAccount *iam.ServiceAccount) (alias string)
type gceAliaser func(role *gcpRole, instance *compute.Instance) (alias string)

const (
defaultIAMAlias = "unique_id"
defaultGCEAlias = "instance_id"
)

var (
allowedIAMAliases = map[string]iamAliaser{
defaultIAMAlias: getIAMSvcAccountUniqueID,
"": getIAMSvcAccountUniqueID, // For backwards compatibility

"role_id": getIAMRoleID,
}
allowedGCEAliases = map[string]gceAliaser{
defaultGCEAlias: getGCEInstanceID,
"": getGCEInstanceID, // For backwards compatibility

"role_id": getGCERoleID,
}

allowedIAMAliasesSlice = iamMapKeyToSlice(allowedIAMAliases)
allowedGCEAliasesSlice = gceMapKeyToSlice(allowedGCEAliases)
)

func iamMapKeyToSlice(m map[string]iamAliaser) (s []string) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These could probably be reduced into a single function that accepts an interface and is type switched. Same with many of the get.. functions below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately it wouldn't really help here. The function would have the same code in it (including both versions of the for loop), but wrapped up into a single function rather than two. There isn't a generic map type that can be used as a variable type that would allow the type coercion needed here. Additionally it's generally frowned upon to pass the empty interface around unless necessary.

Copy link
Contributor

@tyrannosaurus-becks tyrannosaurus-becks Apr 23, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have generally found the same thing as well - when I have tried that approach it read awkwardly, but just in a different way. I don't think there's any "perfect" answer here, but I do like the more direct approach taken here because type conversions can be expensive.

Thank you for your review, by the way, broamski!

for key := range m {
if key == "" {
continue
}
s = append(s, key)
}
sort.Strings(s)
return s
}

func gceMapKeyToSlice(m map[string]gceAliaser) (s []string) {
for key := range m {
if key == "" {
continue
}
s = append(s, key)
}
sort.Strings(s)
return s
}

func getIAMSvcAccountUniqueID(_ *gcpRole, svcAccount *iam.ServiceAccount) (alias string) {
return svcAccount.UniqueId
}

func getIAMRoleID(role *gcpRole, _ *iam.ServiceAccount) (alias string) {
return role.RoleID
}

func getGCEInstanceID(_ *gcpRole, instance *compute.Instance) (alias string) {
return fmt.Sprintf("gce-%s", strconv.FormatUint(instance.Id, 10))
}

func getGCERoleID(role *gcpRole, _ *compute.Instance) (alias string) {
return role.RoleID
}

func getIAMAlias(role *gcpRole, svcAccount *iam.ServiceAccount) (alias string, err error) {
aliaser, exists := allowedIAMAliases[role.IAMAliasType]
if !exists {
return "", fmt.Errorf("invalid IAM alias type: must be one of: %s", strings.Join(allowedIAMAliasesSlice, ", "))
}
return aliaser(role, svcAccount), nil
}

func getGCEAlias(role *gcpRole, instance *compute.Instance) (alias string, err error) {
aliaser, exists := allowedGCEAliases[role.GCEAliasType]
if !exists {
return "", fmt.Errorf("invalid GCE alias type: must be one of: %s", strings.Join(allowedIAMAliasesSlice, ", "))
}
return aliaser(role, instance), nil
}
150 changes: 150 additions & 0 deletions plugin/aliasing_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package gcpauth

import (
"testing"

"google.golang.org/api/compute/v1"
"google.golang.org/api/iam/v1"
)

func TestGetIAMAlias(t *testing.T) {
type testCase struct {
role *gcpRole
svcAccount *iam.ServiceAccount
expectedAlias string
expectErr bool
}

tests := map[string]testCase{
"invalid type": {
role: &gcpRole{
IAMAliasType: "bogus",
RoleID: "testRoleID",
},
svcAccount: &iam.ServiceAccount{
UniqueId: "iamUniqueID",
},
expectedAlias: "",
expectErr: true,
},
"empty type goes to default": {
role: &gcpRole{
IAMAliasType: "",
RoleID: "testRoleID",
},
svcAccount: &iam.ServiceAccount{
UniqueId: "iamUniqueID",
},
expectedAlias: "iamUniqueID",
expectErr: false,
},
"default type": {
role: &gcpRole{
IAMAliasType: defaultIAMAlias,
RoleID: "testRoleID",
},
svcAccount: &iam.ServiceAccount{
UniqueId: "iamUniqueID",
},
expectedAlias: "iamUniqueID",
expectErr: false,
},
"role_id": {
role: &gcpRole{
IAMAliasType: "role_id",
RoleID: "testRoleID",
},
svcAccount: &iam.ServiceAccount{
UniqueId: "iamUniqueID",
},
expectedAlias: "testRoleID",
expectErr: false,
},
}

for name, test := range tests {
t.Run(name, func(t *testing.T) {
actualAlias, err := getIAMAlias(test.role, test.svcAccount)
if test.expectErr && err == nil {
t.Fatalf("err expected, got nil")
}
if !test.expectErr && err != nil {
t.Fatalf("no error expected, got: %s", err)
}
if actualAlias != test.expectedAlias {
t.Fatalf("Actual alias: %s Expected Alias: %s", actualAlias, test.expectedAlias)
}
})
}
}

func TestGetGCEAlias(t *testing.T) {
type testCase struct {
role *gcpRole
instance *compute.Instance
expectedAlias string
expectErr bool
}

tests := map[string]testCase{
"invalid type": {
role: &gcpRole{
GCEAliasType: "bogus",
RoleID: "testRoleID",
},
instance: &compute.Instance{
Id: 123,
},
expectedAlias: "",
expectErr: true,
},
"empty type goes to default": {
role: &gcpRole{
GCEAliasType: "",
RoleID: "testRoleID",
},
instance: &compute.Instance{
Id: 123,
},
expectedAlias: "gce-123",
expectErr: false,
},
"default type": {
role: &gcpRole{
GCEAliasType: defaultGCEAlias,
RoleID: "testRoleID",
},
instance: &compute.Instance{
Id: 123,
},
expectedAlias: "gce-123",
expectErr: false,
},
"role_id": {
role: &gcpRole{
GCEAliasType: "role_id",
RoleID: "testRoleID",
},
instance: &compute.Instance{
Id: 123,
},
expectedAlias: "testRoleID",
expectErr: false,
},
}

for name, test := range tests {
t.Run(name, func(t *testing.T) {
actualAlias, err := getGCEAlias(test.role, test.instance)
if test.expectErr && err == nil {
t.Fatalf("err expected, got nil")
}
if !test.expectErr && err != nil {
t.Fatalf("no error expected, got: %s", err)
}
if actualAlias != test.expectedAlias {
t.Fatalf("Actual alias: %s Expected Alias: %s", actualAlias, test.expectedAlias)
}
})
}
}
Loading