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
- Register a
K8SCluster cert store with ServerUsername/ServerPassword (ServiceAccount token) and no kubeconfig.
- Run an Inventory job.
- 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.
Summary
K8SClusterinventory always returns 0 entries and logs "was not found in namespace ''" for every secret when the store is configured withServerUsername/ServerPasswordcredentials (no kubeconfig). The same credentials work correctly for all other K8S store types (K8SJKS,K8SPKCS12,K8SNS, etc.).Root Cause
ClusterSecretHandler.GetInventoryEntriescallsKubeClient.DiscoverSecrets("all")which internally builds a location string per secret:clusterNameis resolved byGetClusterName() ?? GetHost(). When no kubeconfig is present (username/password auth),ConfigObjis null and it falls back toGetHost():This produces location strings like:
ClusterSecretHandler.ProcessSecretEntrythen splits this by/:parts[1]is the empty string betweenhttps:and the hostname, so every subsequent secret read is:All 52 secrets fail this way; the inventory result is empty.
When It Works vs. Breaks
GetClusterName()returnsparts[1]"local"(fromConfigObj.Clusters[0].Name)local/cert-manager/secrets/name"cert-manager"✅null→ falls back toGetHost()→"https://10.43.0.1/"https://10.43.0.1//cert-manager/secrets/name""❌Other store types (
K8SJKS,K8SPKCS12,K8SNS) are not affected because they pass the namespace explicitly and never callProcessSecretEntry.Reproduction
K8SClustercert store withServerUsername/ServerPassword(ServiceAccount token) and no kubeconfig.Cluster has 14
kubernetes.io/tlssecrets and 2Opaquesecrets containingtls.crt— all should be inventoried.Proposed Fix
In
ProcessSecretEntry, parse the namespace robustly regardless of whetherclusterNameis a simple name or a full URL.Option A — parse from the right (most defensive):
Option B — sanitize
clusterNameinKubeClientbefore building the location string:Option B is cleaner — fix it once at the source rather than in every consumer of
DiscoverSecrets.Related
ClusterSecretHandler.csline ~224 documents the expected format ascluster/namespace/secrets/secretname— this assumption only holds when kubeconfig is present.ServerUsername/ServerPasswordcredentials work correctly, confirming the issue is specific toClusterSecretHandler's path parsing.