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 kubernetes_token_request resource #2024

Merged
merged 24 commits into from
Mar 20, 2023
Merged
Show file tree
Hide file tree
Changes from 21 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 .changelog/2024.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-resource
`kubernetes/resource_kubernetes_tokenrequest.go`: Add `kubernetes_token_request_v1` resource
```
3 changes: 3 additions & 0 deletions kubernetes/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,9 @@ func Provider() *schema.Provider {
// provider helper resources
"kubernetes_labels": resourceKubernetesLabels(),
"kubernetes_annotations": resourceKubernetesAnnotations(),

// authentication
"kubernetes_token_request_v1": resourceKubernetesTokenRequestV1(),
},
}

Expand Down
90 changes: 90 additions & 0 deletions kubernetes/resource_kubernetes_tokenrequest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package kubernetes

import (
"context"
"log"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
apiv1 "k8s.io/api/authentication/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func resourceKubernetesTokenRequestV1() *schema.Resource {
return &schema.Resource{
CreateContext: resourceKubernetesTokenRequestCreateV1,
ReadContext: resourceKubernetesTokenRequestReadV1,
UpdateContext: resourceKubernetesTokenRequestUpdateV1,
DeleteContext: resourceKubernetesTokenDeleteV1,

Schema: map[string]*schema.Schema{
"metadata": namespacedMetadataSchema("token request", true),
"spec": {
BBBmau marked this conversation as resolved.
Show resolved Hide resolved
Type: schema.TypeList,
Description: apiv1.TokenRequest{}.Spec.SwaggerDoc()["spec"],
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: tokenRequestSpecFields(),
},
},
"token": {
Type: schema.TypeString,
Description: "Token is the opaque bearer token.",
Computed: true,
BBBmau marked this conversation as resolved.
Show resolved Hide resolved
Sensitive: true,
},
},
}
}

func resourceKubernetesTokenRequestCreateV1(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
conn, err := meta.(KubeClientsets).MainClientset()
if err != nil {
return diag.FromErr(err)
}

metadata := expandMetadata(d.Get("metadata").([]interface{}))
spec := expandTokenRequestSpec(d.Get("spec").([]interface{}))
saName := d.Get("metadata.0.name").(string)

request := apiv1.TokenRequest{
ObjectMeta: metadata,
Spec: *spec,
}

log.Printf("[INFO] Creating new TokenRequest: %#v", request)
out, err := conn.CoreV1().ServiceAccounts(metadata.Namespace).CreateToken(ctx, saName, &request, metav1.CreateOptions{})
if err != nil {
return diag.FromErr(err)
}
d.Set("token", out.Status.Token)
s, err := flattenTokenRequestSpec(out.Spec, d, meta)
if err != nil {
return diag.FromErr(err)
}
d.Set("spec", s)

BBBmau marked this conversation as resolved.
Show resolved Hide resolved
log.Printf("[INFO] Submitted new TokenRequest: %#v", out)
d.SetId(buildId(out.ObjectMeta))

return resourceKubernetesTokenRequestReadV1(ctx, d, meta)
}

func resourceKubernetesTokenRequestReadV1(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {

return nil
}

func resourceKubernetesTokenRequestUpdateV1(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {

return resourceKubernetesRoleRead(ctx, d, meta)
}

func resourceKubernetesTokenDeleteV1(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {

return nil
}
63 changes: 63 additions & 0 deletions kubernetes/resource_kubernetes_tokenrequest_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package kubernetes

import (
"fmt"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
api "k8s.io/api/core/v1"
)

func TestAccKubernetesTokenRequest_basic(t *testing.T) {
var conf api.ServiceAccount
resourceName := "kubernetes_service_account_v1.tokentest"
tokenName := "kubernetes_token_request_v1.test"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
IDRefreshName: resourceName,
IDRefreshIgnore: []string{"metadata.0.resource_version"},
ProviderFactories: testAccProviderFactories,
CheckDestroy: testAccCheckKubernetesServiceAccountDestroy,
Steps: []resource.TestStep{
{
Config: testAccKubernetesTokenRequestConfig_basic(),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckKubernetesServiceAccountExists(resourceName, &conf),
resource.TestCheckResourceAttr(resourceName, "metadata.0.name", "tokentest"),
resource.TestCheckResourceAttr(tokenName, "metadata.0.name", "tokentest"),
resource.TestCheckResourceAttr(tokenName, "spec.0.audiences.0", "api"),
resource.TestCheckResourceAttr(tokenName, "spec.0.audiences.1", "vault"),
resource.TestCheckResourceAttr(tokenName, "spec.0.audiences.2", "factors"),
BBBmau marked this conversation as resolved.
Show resolved Hide resolved
resource.TestCheckResourceAttrSet(tokenName, "token"),
),
},
},
})
}

func testAccKubernetesTokenRequestConfig_basic() string {
return fmt.Sprintf(`resource "kubernetes_service_account_v1" "tokentest" {
metadata {
name = "tokentest"
}
}

resource "kubernetes_token_request_v1" "test" {
metadata {
name = kubernetes_service_account_v1.tokentest.metadata.0.name
}
spec {
audiences = [
"api",
"vault",
"factors"
]
}
}


`)
}
69 changes: 69 additions & 0 deletions kubernetes/schema_token_request.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package kubernetes

import (
"errors"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
apiv1 "k8s.io/api/authentication/v1"
)

func tokenRequestSpecFields() map[string]*schema.Schema {
s := map[string]*schema.Schema{
"audiences": {
BBBmau marked this conversation as resolved.
Show resolved Hide resolved
Type: schema.TypeList,
Optional: true,
ForceNew: true,
Description: "Audiences are the intendend audiences of the token. A recipient of a token must identify themself with an identifier in the list of audiences of the token, and otherwise should reject the token. A token issued for multiple audiences may be used to authenticate against any of the audiences listed but implies a high degree of trust between the target audiences.",
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"bound_object_ref": {
Type: schema.TypeList,
Optional: true,
ForceNew: true,
MaxItems: 1,
Description: apiv1.TokenRequest{}.Spec.SwaggerDoc()["boundObjectRef"],
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"api_version": {
Type: schema.TypeString,
Optional: true,
Description: "API version of the referent.",
},
"kind": {
Type: schema.TypeString,
Optional: true,
Description: "Kind of the referent. Valid kinds are 'Pod' and 'Secret'.",
},
"name": {
Type: schema.TypeString,
Optional: true,
Description: "Name of the referent.",
},
"uid": {
Type: schema.TypeString,
Optional: true,
Description: "UID of the referent.",
},
},
},
},
"expiration_seconds": {
Type: schema.TypeInt,
Optional: true,
Description: "expiration_seconds is the requested duration of validity of the request. The token issuer may return a token with a different validity duration so a client needs to check the 'expiration' field in a response. The expiration can't be less than 10 minutes.",
ValidateFunc: func(value interface{}, key string) ([]string, []error) {
v := value.(int)
if v < 600 || v > 4294967296 {
return nil, []error{errors.New("must be between 600 and 4294967296 ")}
}
return nil, nil
},
},
BBBmau marked this conversation as resolved.
Show resolved Hide resolved
}
return s
}
98 changes: 98 additions & 0 deletions kubernetes/structures_tokenrequest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package kubernetes

import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
v1 "k8s.io/api/authentication/v1"
"k8s.io/apimachinery/pkg/types"
)

// Flatteners

func flattenTokenRequestSpec(in v1.TokenRequestSpec, d *schema.ResourceData, meta interface{}) ([]interface{}, error) {
att := make(map[string]interface{})

att["audiences"] = in.Audiences

if in.BoundObjectRef != nil {
bndObjRef, err := flattenBoundObjectReference(*in.BoundObjectRef, d, meta)
if err != nil {
return nil, err
}
att["bound_object_ref"] = bndObjRef
}

if in.ExpirationSeconds != nil {
att["expirationseconds"] = int(*in.ExpirationSeconds)
BBBmau marked this conversation as resolved.
Show resolved Hide resolved
}

return []interface{}{att}, nil
}

func flattenBoundObjectReference(in v1.BoundObjectReference, d *schema.ResourceData, meta interface{}) ([]interface{}, error) {
att := make(map[string]interface{})

att["api_version"] = in.APIVersion

att["kind"] = in.Kind

att["name"] = in.Name

att["uid"] = in.UID

return []interface{}{att}, nil
}

// Expanders

func expandTokenRequestSpec(p []interface{}) *v1.TokenRequestSpec {
obj := &v1.TokenRequestSpec{}
if len(p) == 0 || p[0] == nil {
return obj
}
in := p[0].(map[string]interface{})

if v, ok := in["audiences"].([]interface{}); ok && len(v) > 0 {
obj.Audiences = expandStringSlice(v)
}

bdObjRef, err := expandBoundObjectReference(in["bound_object_ref"].([]interface{}))
if err != nil {
return obj
}
obj.BoundObjectRef = bdObjRef

if v, ok := in["expiration_seconds"].(int); v != 0 && ok {
obj.ExpirationSeconds = ptrToInt64(int64(v))
}

return obj
}

func expandBoundObjectReference(p []interface{}) (*v1.BoundObjectReference, error) {
obj := &v1.BoundObjectReference{}
if len(p) == 0 || p[0] == nil {
return nil, nil
}
in := p[0].(map[string]interface{})

if v, ok := in["api_version"]; ok {
obj.APIVersion = v.(string)
}

if v, ok := in["kind"]; ok {
obj.Kind = v.(string)
}

if v, ok := in["name"]; ok {
obj.Name = v.(string)
}

if v, ok := in["uid"]; ok {
obj.UID = types.UID(v.(string))
}

return obj, nil
}
Loading