Skip to content

K8SCluster inventory returns 0 entries when configured with username/password auth (no kubeconfig) #87

Description

@spbsoluble

Summary

K8SCluster inventory always returns 0 entries and logs "was not found in namespace ''" for every secret when the store is configured with ServerUsername/ServerPassword credentials (no kubeconfig). The same credentials work correctly for all other K8S store types (K8SJKS, K8SPKCS12, K8SNS, etc.).

Root Cause

ClusterSecretHandler.GetInventoryEntries calls KubeClient.DiscoverSecrets("all") which internally builds a location string per secret:

// KubeClient.cs line ~820
var secretLocation = $"{clusterName}/{namespaceName}/secrets/{secret.Metadata.Name}";

clusterName is resolved by GetClusterName() ?? GetHost(). When no kubeconfig is present (username/password auth), ConfigObj is null and it falls back to GetHost():

public string GetHost()
{
    var host = Client.BaseUri.ToString();  // returns e.g. "https://10.43.0.1/"
    return host;
}

This produces location strings like:

https://10.43.0.1//cert-manager/secrets/lab-root-ca-secret

ClusterSecretHandler.ProcessSecretEntry then splits this by /:

// ClusterSecretHandler.cs line ~225
// secretPath format from DiscoverSecrets: cluster/namespace/secrets/secretname
var parts = secretPath.Split('/');
var ns = parts[1];  // expects namespace — actually gets "" (empty string between "https:" and host)
var name = parts[^1];

parts[1] is the empty string between https: and the hostname, so every subsequent secret read is:

Getting secret lab-root-ca-secret from namespace ''
→ Kubernetes TLS secret 'lab-root-ca-secret' was not found in namespace ''

All 52 secrets fail this way; the inventory result is empty.

When It Works vs. Breaks

Auth method GetClusterName() returns Location string parts[1] Result
kubeconfig "local" (from ConfigObj.Clusters[0].Name) local/cert-manager/secrets/name "cert-manager" works
username/password null → falls back to GetHost()"https://10.43.0.1/" https://10.43.0.1//cert-manager/secrets/name "" all secrets fail

Other store types (K8SJKS, K8SPKCS12, K8SNS) are not affected because they pass the namespace explicitly and never call ProcessSecretEntry.

Reproduction

  1. Register a K8SCluster cert store with ServerUsername/ServerPassword (ServiceAccount token) and no kubeconfig.
  2. Run an Inventory job.
  3. Observe 0 certificates inventoried and UO logs with errors like:
    Errors processing 52 secrets: https://10.43.0.1//cert-manager/secrets/lab-root-ca-secret: Kubernetes TLS secret 'lab-root-ca-secret' was not found in namespace ''.
    

Cluster has 14 kubernetes.io/tls secrets and 2 Opaque secrets containing tls.crt — all should be inventoried.

Proposed Fix

In ProcessSecretEntry, parse the namespace robustly regardless of whether clusterName is a simple name or a full URL.

Option A — parse from the right (most defensive):

// secretPath format: {clusterName}/{namespace}/secrets/{name}
// clusterName may be a URL like "https://10.43.0.1/" — find /secrets/ as the anchor
var secretsIdx = secretPath.IndexOf("/secrets/", StringComparison.Ordinal);
if (secretsIdx < 0) return;
var name = secretPath[(secretsIdx + "/secrets/".Length)..];
// namespace is everything between the first '/' after the scheme and "/secrets/"
var afterScheme = secretPath.Contains("://")
    ? secretPath[(secretPath.IndexOf("://", StringComparison.Ordinal) + 3)..]
    : secretPath;
var slashAfterHost = afterScheme.IndexOf('/');
var nsAndRest = slashAfterHost >= 0 ? afterScheme[(slashAfterHost + 1)..] : afterScheme;
var ns = nsAndRest[..nsAndRest.IndexOf("/secrets/", StringComparison.Ordinal)];

Option B — sanitize clusterName in KubeClient before building the location string:

// In KubeClient, extract just the hostname when clusterName is a URL
private static string SanitizeClusterName(string clusterName)
{
    if (Uri.TryCreate(clusterName, UriKind.Absolute, out var uri))
        return uri.Host;
    return clusterName;
}

// Then in ProcessSecret:
var sanitized = SanitizeClusterName(clusterName);
var secretLocation = $"{sanitized}/{namespaceName}/secrets/{secret.Metadata.Name}";

Option B is cleaner — fix it once at the source rather than in every consumer of DiscoverSecrets.

Related

  • Existing comment in ClusterSecretHandler.cs line ~224 documents the expected format as cluster/namespace/secrets/secretname — this assumption only holds when kubeconfig is present.
  • All other K8S store types using the same ServerUsername/ServerPassword credentials work correctly, confirming the issue is specific to ClusterSecretHandler's path parsing.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions