Skip to content

Commit

Permalink
feat: add token_default_audiences option to create role
Browse files Browse the repository at this point in the history
  • Loading branch information
thyton committed Mar 9, 2023
1 parent 0906f31 commit 5dbbd31
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 25 deletions.
2 changes: 1 addition & 1 deletion client.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ func (c *client) createToken(ctx context.Context, namespace, name string, ttl ti
intTTL := int64(ttl.Seconds())
resp, err := c.k8s.CoreV1().ServiceAccounts(namespace).CreateToken(ctx, name, &authenticationv1.TokenRequest{
Spec: authenticationv1.TokenRequestSpec{
Audiences: audiences,
ExpirationSeconds: &intTTL,
Audiences: audiences,
},
}, metav1.CreateOptions{})
if err != nil {
Expand Down
29 changes: 23 additions & 6 deletions integrationtest/creds_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,30 +145,42 @@ func TestCreds_audiences(t *testing.T) {
type testCase struct {
roleConfig map[string]interface{}
credsConfig map[string]interface{}
expectedAudiences []string
expectedAudiences []interface{}
}

tests := map[string]testCase{
"audiences set": {
"both set": {
roleConfig: map[string]interface{}{
"allowed_kubernetes_namespaces": []string{"*"},
"service_account_name": "sample-app",
"token_default_audiences": []string{"foo", "bar"},
},
credsConfig: map[string]interface{}{
"kubernetes_namespace": "test",
"audiences": "baz,qux",
},
expectedAudiences: []interface{}{"baz", "qux"},
},
"default to token_default_audiences": {
roleConfig: map[string]interface{}{
"allowed_kubernetes_namespaces": []string{"*"},
"service_account_name": "sample-app",
"token_default_audiences": []string{"foo", "bar"},
},
credsConfig: map[string]interface{}{
"kubernetes_namespace": "test",
"audiences": "foo,bar",
},
expectedAudiences: []string{"foo", "bar"},
expectedAudiences: []interface{}{"foo", "bar"},
},
"audiences not set": {
"default to audiences of k8s cluster setup if both not set": {
roleConfig: map[string]interface{}{
"allowed_kubernetes_namespaces": []string{"*"},
"service_account_name": "sample-app",
},
credsConfig: map[string]interface{}{
"kubernetes_namespace": "test",
},
expectedAudiences: []string{},
expectedAudiences: []interface{}{"https://kubernetes.default.svc.cluster.local"},
},
}
i := 0
Expand Down Expand Up @@ -227,6 +239,7 @@ func TestCreds_service_account_name(t *testing.T) {
"service_account_name": "sample-app",
"token_max_ttl": oneDay,
"token_default_ttl": oneHour,
"token_default_audiences": nil,
}, roleResponse.Data)

result1, err := client.Logical().Write(path+"/creds/testrole", map[string]interface{}{
Expand Down Expand Up @@ -304,6 +317,7 @@ func TestCreds_kubernetes_role_name(t *testing.T) {
"service_account_name": "",
"token_max_ttl": oneDay,
"token_default_ttl": oneHour,
"token_default_audiences": nil,
}
testRoleType(t, client, path, roleConfig, expectedRoleResponse)
})
Expand Down Expand Up @@ -338,6 +352,7 @@ func TestCreds_kubernetes_role_name(t *testing.T) {
"service_account_name": "",
"token_max_ttl": oneDay,
"token_default_ttl": oneHour,
"token_default_audiences": nil,
}
testClusterRoleType(t, client, path, roleConfig, expectedRoleResponse)
})
Expand Down Expand Up @@ -407,6 +422,7 @@ func TestCreds_generated_role_rules(t *testing.T) {
"service_account_name": "",
"token_max_ttl": oneDay,
"token_default_ttl": oneHour,
"token_default_audiences": nil,
}
testRoleType(t, client, path, roleConfig, expectedRoleResponse)
})
Expand Down Expand Up @@ -442,6 +458,7 @@ func TestCreds_generated_role_rules(t *testing.T) {
"service_account_name": "",
"token_max_ttl": oneDay,
"token_default_ttl": oneHour,
"token_default_audiences": nil,
}
testClusterRoleType(t, client, path, roleConfig, expectedRoleResponse)
})
Expand Down
7 changes: 4 additions & 3 deletions integrationtest/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -390,15 +390,16 @@ func testK8sTokenTTL(t *testing.T, expectedSec int, token string) {
assert.Equal(t, expectedSec, int(exp-iat))
}

func testK8sTokenAudiences(t *testing.T, expectedAudiences []string, token string) {
func testK8sTokenAudiences(t *testing.T, expectedAudiences []interface{}, token string) {
parsed, err := josejwt.ParseSigned(token)
require.NoError(t, err)
claims := map[string]interface{}{}
err = parsed.UnsafeClaimsWithoutVerification(&claims)
require.NoError(t, err)
aud := claims["aud"].([]interface{})
for _, audience := range expectedAudiences {
assert.Contains(t, aud, interface{}(audience))
assert.Equal(t, len(expectedAudiences), len(aud))
for _, expectedAudience := range expectedAudiences {
assert.Contains(t, aud, expectedAudience)
}
}

Expand Down
5 changes: 5 additions & 0 deletions integrationtest/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ func TestRole(t *testing.T) {
"generated_role_rules": sampleRules,
"token_default_ttl": "1h",
"token_max_ttl": "24h",
"token_default_audiences": []string{"foobar"},
})
assert.NoError(t, err)

Expand All @@ -180,6 +181,7 @@ func TestRole(t *testing.T) {
"service_account_name": "",
"token_max_ttl": oneDay,
"token_default_ttl": oneHour,
"token_default_audiences": []interface{}{"foobar"},
}, result.Data)

// update
Expand All @@ -188,6 +190,7 @@ func TestRole(t *testing.T) {
"extra_annotations": sampleExtraAnnotations,
"extra_labels": sampleExtraLabels,
"token_default_ttl": "30m",
"token_default_audiences": []string{"bar"},
})

result, err = client.Logical().Read(path + "/roles/testrole")
Expand All @@ -205,6 +208,7 @@ func TestRole(t *testing.T) {
"service_account_name": "",
"token_max_ttl": oneDay,
"token_default_ttl": thirtyMinutes,
"token_default_audiences": []interface{}{"bar"},
}, result.Data)

// update again
Expand All @@ -228,6 +232,7 @@ func TestRole(t *testing.T) {
"service_account_name": "",
"token_max_ttl": oneDay,
"token_default_ttl": thirtyMinutes,
"token_default_audiences": []interface{}{"bar"},
}, result.Data)

result, err = client.Logical().List(path + "/roles")
Expand Down
4 changes: 4 additions & 0 deletions integrationtest/wal_rollback_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ func TestCreds_wal_rollback(t *testing.T) {
"kubernetes_role_type": "RolE",
"token_default_ttl": "1h",
"token_max_ttl": "24h",
"token_default_audiences": []string{"foobar"},
}
expectedRoleResponse := map[string]interface{}{
"allowed_kubernetes_namespaces": []interface{}{"test"},
Expand All @@ -80,6 +81,7 @@ func TestCreds_wal_rollback(t *testing.T) {
"service_account_name": "",
"token_max_ttl": oneDay,
"token_default_ttl": oneHour,
"token_default_audiences": []interface{}{"foobar"},
}

_, err := client.Logical().Write(mountPath+"/roles/walrole", roleConfig)
Expand Down Expand Up @@ -138,6 +140,7 @@ func TestCreds_wal_rollback(t *testing.T) {
"kubernetes_role_type": "ClusterRole",
"token_default_ttl": "1h",
"token_max_ttl": "24h",
"token_default_audiences": []string{"foobar"},
}
expectedRoleResponse := map[string]interface{}{
"allowed_kubernetes_namespaces": interface{}(nil),
Expand All @@ -152,6 +155,7 @@ func TestCreds_wal_rollback(t *testing.T) {
"service_account_name": "",
"token_max_ttl": oneDay,
"token_default_ttl": oneHour,
"token_default_audiences": []interface{}{"foobar"},
}

_, err := client.Logical().Write(mountPath+"/roles/walrolebinding", roleConfig)
Expand Down
11 changes: 8 additions & 3 deletions path_creds.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,11 @@ func (b *backend) createCreds(ctx context.Context, req *logical.Request, role *r
theTTL = b.System().MaxLeaseTTL()
}

theAudiences := role.TokenDefaultAudiences
if len(reqPayload.Audiences) != 0 {
theAudiences = reqPayload.Audiences
}

// These are created items to save internally and/or return to the caller
token := ""
serviceAccountName := ""
Expand All @@ -228,7 +233,7 @@ func (b *backend) createCreds(ctx context.Context, req *logical.Request, role *r
switch {
case role.ServiceAccountName != "":
// Create token for existing service account
status, err := client.createToken(ctx, reqPayload.Namespace, role.ServiceAccountName, theTTL, reqPayload.Audiences)
status, err := client.createToken(ctx, reqPayload.Namespace, role.ServiceAccountName, theTTL, theAudiences)
if err != nil {
return nil, fmt.Errorf("failed to create a service account token for %s/%s: %s", reqPayload.Namespace, role.ServiceAccountName, err)
}
Expand All @@ -250,7 +255,7 @@ func (b *backend) createCreds(ctx context.Context, req *logical.Request, role *r
return nil, err
}

status, err := client.createToken(ctx, reqPayload.Namespace, genName, theTTL, reqPayload.Audiences)
status, err := client.createToken(ctx, reqPayload.Namespace, genName, theTTL, theAudiences)
if err != nil {
return nil, fmt.Errorf("failed to create a service account token for %s/%s: %s", reqPayload.Namespace, genName, err)
}
Expand All @@ -277,7 +282,7 @@ func (b *backend) createCreds(ctx context.Context, req *logical.Request, role *r
return nil, err
}

status, err := client.createToken(ctx, reqPayload.Namespace, genName, theTTL, reqPayload.Audiences)
status, err := client.createToken(ctx, reqPayload.Namespace, genName, theTTL, theAudiences)
if err != nil {
return nil, fmt.Errorf("failed to create a service account token for %s/%s: %s", reqPayload.Namespace, genName, err)
}
Expand Down
33 changes: 21 additions & 12 deletions path_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,19 @@ const (
)

type roleEntry struct {
Name string `json:"name" mapstructure:"name"`
K8sNamespaces []string `json:"allowed_kubernetes_namespaces" mapstructure:"allowed_kubernetes_namespaces"`
K8sNamespaceSelector string `json:"allowed_kubernetes_namespace_selector" mapstructure:"allowed_kubernetes_namespace_selector"`
TokenMaxTTL time.Duration `json:"token_max_ttl" mapstructure:"token_max_ttl"`
TokenDefaultTTL time.Duration `json:"token_default_ttl" mapstructure:"token_default_ttl"`
ServiceAccountName string `json:"service_account_name" mapstructure:"service_account_name"`
K8sRoleName string `json:"kubernetes_role_name" mapstructure:"kubernetes_role_name"`
K8sRoleType string `json:"kubernetes_role_type" mapstructure:"kubernetes_role_type"`
RoleRules string `json:"generated_role_rules" mapstructure:"generated_role_rules"`
NameTemplate string `json:"name_template" mapstructure:"name_template"`
ExtraLabels map[string]string `json:"extra_labels" mapstructure:"extra_labels"`
ExtraAnnotations map[string]string `json:"extra_annotations" mapstructure:"extra_annotations"`
Name string `json:"name" mapstructure:"name"`
K8sNamespaces []string `json:"allowed_kubernetes_namespaces" mapstructure:"allowed_kubernetes_namespaces"`
K8sNamespaceSelector string `json:"allowed_kubernetes_namespace_selector" mapstructure:"allowed_kubernetes_namespace_selector"`
TokenMaxTTL time.Duration `json:"token_max_ttl" mapstructure:"token_max_ttl"`
TokenDefaultTTL time.Duration `json:"token_default_ttl" mapstructure:"token_default_ttl"`
TokenDefaultAudiences []string `json:"token_default_audiences" mapstructure:"token_default_audiences"`
ServiceAccountName string `json:"service_account_name" mapstructure:"service_account_name"`
K8sRoleName string `json:"kubernetes_role_name" mapstructure:"kubernetes_role_name"`
K8sRoleType string `json:"kubernetes_role_type" mapstructure:"kubernetes_role_type"`
RoleRules string `json:"generated_role_rules" mapstructure:"generated_role_rules"`
NameTemplate string `json:"name_template" mapstructure:"name_template"`
ExtraLabels map[string]string `json:"extra_labels" mapstructure:"extra_labels"`
ExtraAnnotations map[string]string `json:"extra_annotations" mapstructure:"extra_annotations"`
}

func (r *roleEntry) toResponseData() (map[string]interface{}, error) {
Expand Down Expand Up @@ -78,6 +79,11 @@ func (b *backend) pathRoles() []*framework.Path {
Description: "The default ttl for generated Kubernetes service account tokens. If not set or set to 0, will use system default.",
Required: false,
},
"token_default_audiences": {
Type: framework.TypeCommaStringSlice,
Description: "The default audiences for generated Kubernetes service account tokens. If not set or set to \"\", will use k8s cluster setup.",
Required: false,
},
"service_account_name": {
Type: framework.TypeString,
Description: "The pre-existing service account to generate tokens for. Mutually exclusive with all role parameters. If set, only a Kubernetes service account token will be created.",
Expand Down Expand Up @@ -206,6 +212,9 @@ func (b *backend) pathRolesWrite(ctx context.Context, req *logical.Request, d *f
if tokenTTLRaw, ok := d.GetOk("token_default_ttl"); ok {
entry.TokenDefaultTTL = time.Duration(tokenTTLRaw.(int)) * time.Second
}
if tokenAudiencesRaw, ok := d.GetOk("token_default_audiences"); ok {
entry.TokenDefaultAudiences = strutil.RemoveDuplicates(tokenAudiencesRaw.([]string), true)
}
if svcAccount, ok := d.GetOk("service_account_name"); ok {
entry.ServiceAccountName = svcAccount.(string)
}
Expand Down

0 comments on commit 5dbbd31

Please sign in to comment.