-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add alias types for IAM and GCE logins
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
Showing
12 changed files
with
1,682 additions
and
430 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 { | ||
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) { | ||
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
}) | ||
} | ||
} |
Oops, something went wrong.