Skip to content

Commit

Permalink
Don't fail when unable to find default SA token secret (#1634)
Browse files Browse the repository at this point in the history
* Don't fail when unable to find default SA token secret. It may have been replaced.
  • Loading branch information
alexsomesan authored Mar 15, 2022
1 parent 735d656 commit c178cdc
Show file tree
Hide file tree
Showing 9 changed files with 112 additions and 22 deletions.
9 changes: 4 additions & 5 deletions kubernetes/data_source_kubernetes_service_account.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,7 @@ func dataSourceKubernetesServiceAccountRead(ctx context.Context, d *schema.Resou
return diag.Errorf("Unable to fetch service account from Kubernetes: %s", err)
}

defaultSecret, err := findDefaultServiceAccount(ctx, sa, conn)
if err != nil {
return diag.Errorf("Failed to discover the default service account token: %s", err)
}
defaultSecret, diagMsg := findDefaultServiceAccount(ctx, sa, conn)

err = d.Set("default_secret_name", defaultSecret)
if err != nil {
Expand All @@ -79,5 +76,7 @@ func dataSourceKubernetesServiceAccountRead(ctx context.Context, d *schema.Resou

d.SetId(buildId(sa.ObjectMeta))

return resourceKubernetesServiceAccountRead(ctx, d, meta)
diagMsg = append(diagMsg, resourceKubernetesServiceAccountRead(ctx, d, meta)...)

return diagMsg
}
70 changes: 70 additions & 0 deletions kubernetes/data_source_kubernetes_service_account_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,33 @@ func TestAccKubernetesDataSourceServiceAccount_basic(t *testing.T) {
})
}

func TestAccKubernetesDataSourceServiceAccount_default_secret(t *testing.T) {
name := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum))

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: testAccProviderFactories,
Steps: []resource.TestStep{
{
Config: testAccKubernetesServiceAccountConfig_default_secret(name),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("kubernetes_service_account.test", "metadata.0.name", name),
resource.TestCheckResourceAttr("kubernetes_service_account.test", "secret.#", "1"),
),
},
{
Config: testAccKubernetesServiceAccountConfig_default_secret(name) +
testAccKubernetesDataSourceServiceAccountConfig_default_secret_read(name),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("data.kubernetes_service_account.test", "metadata.0.name", name),
resource.TestCheckResourceAttr("data.kubernetes_service_account.test", "secret.#", "2"),
resource.TestCheckResourceAttr("data.kubernetes_service_account.test", "default_secret_name", ""),
),
},
},
})
}

func testAccKubernetesDataSourceServiceAccountConfig_basic(name string) string {
return fmt.Sprintf(`resource "kubernetes_service_account" "test" {
metadata {
Expand Down Expand Up @@ -89,3 +116,46 @@ func testAccKubernetesDataSourceServiceAccountConfig_read() string {
}
`)
}

func testAccKubernetesServiceAccountConfig_default_secret(name string) string {
return fmt.Sprintf(`
variable "token_name" {
default = "%s-token-test0"
}
resource "kubernetes_service_account" "test" {
metadata {
name = "%s"
}
secret {
name = var.token_name
}
}
resource "kubernetes_secret" "test" {
metadata {
name = var.token_name
annotations = {
"kubernetes.io/service-account.name" = "%s"
}
}
type = "kubernetes.io/service-account-token"
depends_on = [
kubernetes_service_account.test
]
}
`, name, name, name)
}

func testAccKubernetesDataSourceServiceAccountConfig_default_secret_read(name string) string {
return fmt.Sprintf(`
data "kubernetes_service_account" "test" {
metadata {
name = "%s"
}
depends_on = [
kubernetes_secret.test
]
}
`, name)
}
43 changes: 32 additions & 11 deletions kubernetes/resource_kubernetes_service_account.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ func getServiceAccountDefaultSecret(ctx context.Context, name string, config api
return &svcAccTokens[0], nil
}

func findDefaultServiceAccount(ctx context.Context, sa *api.ServiceAccount, conn *kubernetes.Clientset) (string, error) {
func findDefaultServiceAccount(ctx context.Context, sa *api.ServiceAccount, conn *kubernetes.Clientset) (string, diag.Diagnostics) {
/*
The default service account token secret would have:
- been created either at the same moment as the service account or _just_ after (Kubernetes controllers appears to work off a queue)
Expand All @@ -165,6 +165,8 @@ func findDefaultServiceAccount(ctx context.Context, sa *api.ServiceAccount, conn
See this for where the default token is created in Kubernetes
https://github.com/kubernetes/kubernetes/blob/release-1.13/pkg/controller/serviceaccount/tokens_controller.go#L384
*/
ds := make([]string, 0)

for _, saSecret := range sa.Secrets {
if !strings.HasPrefix(saSecret.Name, fmt.Sprintf("%s-token-", sa.Name)) {
log.Printf("[DEBUG] Skipping %s as it doesn't have the right name", saSecret.Name)
Expand All @@ -173,30 +175,48 @@ func findDefaultServiceAccount(ctx context.Context, sa *api.ServiceAccount, conn

secret, err := conn.CoreV1().Secrets(sa.Namespace).Get(ctx, saSecret.Name, metav1.GetOptions{})
if err != nil {
return "", fmt.Errorf("Unable to fetch secret %s/%s from Kubernetes: %s", sa.Namespace, saSecret.Name, err)
return "", diag.Errorf("Unable to fetch secret %s/%s from Kubernetes: %s", sa.Namespace, saSecret.Name, err)
}

if secret.Type != api.SecretTypeServiceAccountToken {
log.Printf("[DEBUG] Skipping %s as it is of the wrong type", saSecret.Name)
continue
}

if secret.CreationTimestamp.Before(&sa.CreationTimestamp) {
log.Printf("[DEBUG] Skipping %s as it existed before the service account", saSecret.Name)
if secret.Annotations[api.ServiceAccountNameKey] != sa.ObjectMeta.Name {
log.Printf("[DEBUG] Skipping %s as it has a different name than the service account", saSecret.Name)
continue
}

if secret.CreationTimestamp.Sub(sa.CreationTimestamp.Time) > (3 * time.Second) {
log.Printf("[DEBUG] Skipping %s as it wasn't created at the same time as the service account", saSecret.Name)
if secret.Annotations[api.ServiceAccountUIDKey] != string(sa.ObjectMeta.UID) {
log.Printf("[DEBUG] Skipping %s as it has a different UID than the service account", saSecret.Name)
continue
}

log.Printf("[DEBUG] Found %s as a candidate for the default service account token", saSecret.Name)
ds = append(ds, saSecret.Name)
}

if len(ds) == 0 {
return "", diag.Diagnostics{
diag.Diagnostic{
Severity: diag.Warning,
Summary: "Unable to find any service accounts tokens which could have been the default one.",
},
}
}

return saSecret.Name, nil
if len(ds) == 1 {
return ds[0], nil
}

return "", fmt.Errorf("Unable to find any service accounts tokens which could have been the default one")
return "", diag.Diagnostics{
diag.Diagnostic{
Severity: diag.Warning,
Summary: "Unable to discover default secret name.",
Detail: "There is more than one service account token associated to the service account.",
},
}
}

func diffObjectReferences(origOrs []api.ObjectReference, ors []api.ObjectReference) []api.ObjectReference {
Expand Down Expand Up @@ -385,9 +405,10 @@ func resourceKubernetesServiceAccountImportState(ctx context.Context, d *schema.
if err != nil {
return nil, fmt.Errorf("Unable to fetch service account from Kubernetes: %s", err)
}
defaultSecret, err := findDefaultServiceAccount(ctx, sa, conn)
if err != nil {
return nil, fmt.Errorf("Failed to discover the default service account token: %s", err)

defaultSecret, diagMsg := findDefaultServiceAccount(ctx, sa, conn)
if diagMsg.HasError() {
log.Print("[WARN] Failed to discover the default service account token")
}

err = d.Set("default_secret_name", defaultSecret)
Expand Down
2 changes: 1 addition & 1 deletion website/docs/d/service_account.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ The following arguments are supported:

* `image_pull_secret` - A list of image pull secrets associated with the service account.
* `secret` - A list of secrets associated with the service account.
* `default_secret_name` - Name of the default secret, containing service account token, created & managed by the service.
* `default_secret_name` - Name of the default secret, containing service account token, created & managed by the service. By default, the provider will try to find the secret containing the service account token that Kubernetes automatically created for the service account. Where there are multiple tokens and the provider cannot determine which was created by Kubernetes, this attribute will be empty. When only one token is associated with the service account, the provider will return this single token secret.

### `image_pull_secret`

Expand Down
2 changes: 1 addition & 1 deletion website/docs/d/service_account_v1.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ The following arguments are supported:

* `image_pull_secret` - A list of image pull secrets associated with the service account.
* `secret` - A list of secrets associated with the service account.
* `default_secret_name` - Name of the default secret, containing service account token, created & managed by the service.
* `default_secret_name` - Name of the default secret, containing service account token, created & managed by the service. By default, the provider will try to find the secret containing the service account token that Kubernetes automatically created for the service account. Where there are multiple tokens and the provider cannot determine which was created by Kubernetes, this attribute will be empty. When only one token is associated with the service account, the provider will return this single token secret.

### `image_pull_secret`

Expand Down
2 changes: 1 addition & 1 deletion website/docs/r/default_service_account.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ The following arguments are supported:
In addition to the arguments listed above, the following computed attributes are
exported:

* `default_secret_name` - Name of the default secret, containing service account token, created & managed by the service.
* `default_secret_name` - Name of the default secret, containing service account token, created & managed by the service. By default, the provider will try to find the secret containing the service account token that Kubernetes automatically created for the service account. Where there are multiple tokens and the provider cannot determine which was created by Kubernetes, this attribute will be empty. When only one token is associated with the service account, the provider will return this single token secret.

## Destroying

Expand Down
2 changes: 1 addition & 1 deletion website/docs/r/default_service_account_v1.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ The following arguments are supported:
In addition to the arguments listed above, the following computed attributes are
exported:

* `default_secret_name` - Name of the default secret, containing service account token, created & managed by the service.
* `default_secret_name` - Name of the default secret, containing service account token, created & managed by the service. By default, the provider will try to find the secret containing the service account token that Kubernetes automatically created for the service account. Where there are multiple tokens and the provider cannot determine which was created by Kubernetes, this attribute will be empty. When only one token is associated with the service account, the provider will return this single token secret.

## Destroying

Expand Down
2 changes: 1 addition & 1 deletion website/docs/r/service_account.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ The following arguments are supported:
In addition to the arguments listed above, the following computed attributes are
exported:

* `default_secret_name` - Name of the default secret, containing service account token, created & managed by the service.
* `default_secret_name` - Name of the default secret, containing service account token, created & managed by the service. By default, the provider will try to find the secret containing the service account token that Kubernetes automatically created for the service account. Where there are multiple tokens and the provider cannot determine which was created by Kubernetes, this attribute will be empty. When only one token is associated with the service account, the provider will return this single token secret.

## Import

Expand Down
2 changes: 1 addition & 1 deletion website/docs/r/service_account_v1.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ The following arguments are supported:
In addition to the arguments listed above, the following computed attributes are
exported:

* `default_secret_name` - Name of the default secret, containing service account token, created & managed by the service.
* `default_secret_name` - Name of the default secret, containing service account token, created & managed by the service. By default, the provider will try to find the secret containing the service account token that Kubernetes automatically created for the service account. Where there are multiple tokens and the provider cannot determine which was created by Kubernetes, this attribute will be empty. When only one token is associated with the service account, the provider will return this single token secret.

## Import

Expand Down

0 comments on commit c178cdc

Please sign in to comment.