Skip to content

Commit

Permalink
Add alias types for IAM and GCE logins (#89) (#95)
Browse files Browse the repository at this point in the history
Resolves hashicorp/vault#8761

Allows users to specify an alias type field for IAM and GCE logins which
will then switch between the current behavior (a unique ID for IAM, and
the instance ID for GCE) and a newly created role_id field. The role_id
is a UUID generated when the role is created. All existing roles without
a role_id will have one generated and saved when the role is read or
written.
  • Loading branch information
pcman312 committed Apr 28, 2020
1 parent 91fa66d commit 43bcca9
Show file tree
Hide file tree
Showing 12 changed files with 1,635 additions and 422 deletions.
3 changes: 3 additions & 0 deletions Makefile
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
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.20200317185738-82f498082f02
github.com/hashicorp/vault/sdk v0.1.14-0.20200317185738-82f498082f02
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect
Expand Down
10 changes: 10 additions & 0 deletions go.sum
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=
91 changes: 91 additions & 0 deletions plugin/aliasing.go
@@ -0,0 +1,91 @@
package gcpauth

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

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

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) {
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
@@ -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)
}
})
}
}

0 comments on commit 43bcca9

Please sign in to comment.