Summary
Add a kosli snapshot gke ENVIRONMENT-NAME command that reports GKE Pods to a K8S Kosli environment by reading Google Cloud Asset Inventory (cloudasset.googleapis.com) instead of the Kubernetes API server.
The reporter needs only a Google Cloud identity with Asset Inventory list permissions. It needs no kubeconfig, no Kubernetes RBAC, and no network path to any cluster control plane.
Motivation
kosli snapshot k8s connects to the Kubernetes API server through a kubeconfig or in-cluster config. For GKE, that means either running the reporter inside each cluster or opening network access to each control plane, plus managing RBAC per cluster. Many teams restrict control plane access (private clusters, authorized networks); EKS users face the same problem.
Cloud Asset Inventory indexes GKE workload objects, including k8s.io/Pod, as regular Google API resources. A prototype confirmed that ListAssets with ContentType: RESOURCE returns the full Pod object, including status.containerStatuses[].imageID (REPO@sha256:...). That is the same field internal/kube already reduces to the Kosli fingerprint, so digests match by construction.
Benefits:
- No control plane access: traffic goes only to
cloudasset.googleapis.com.
- IAM instead of RBAC: one role grant replaces per-cluster RBAC setup.
- Fleet-wide scope: one call covers every cluster in a project, folder, or organization.
- Simple deployment: runs as a Cloud Run Job next to the existing
kosli snapshot cloud-run reporter.
Proposed CLI
kosli snapshot gke ENVIRONMENT-NAME \
--project my-project \ # or --folder / --organization (mutually exclusive)
--cluster autopilot-cluster-1 \ # optional, repeatable; default: all clusters in scope
--location europe-west1 \ # optional
--namespaces payments # same namespace flags as snapshot k8s
Flags:
- Scope:
--project, --folder, --organization (exactly one), mapped to the parent of ListAssets.
- Cluster filter:
--cluster / --cluster-regex, --location.
- Namespace filter:
--namespaces, --namespaces-regex, --exclude-namespaces, --exclude-namespaces-regex, with the same semantics and mutual exclusivity as snapshot k8s.
- Inherited:
--dry-run, --auto-environment, and the global flags.
Open question: one Kosli environment per invocation spanning several clusters, or a --config-file mapping clusters and namespaces to environments, as snapshot k8s supports. Suggest starting with one environment per invocation.
Implementation notes
Data source:
cloud.google.com/go/asset/apiv1, ListAssets with AssetTypes: ["k8s.io/Pod"], ContentType: RESOURCE, PageSize: 1000 (the API maximum; default 100).
- Convert
asset.Resource.Data (structpb.Struct) to JSON with protojson, then unmarshal into corev1.Pod.
- Parse cluster and location from the asset name:
//container.googleapis.com/projects/P/locations/L/clusters/C/k8s/namespaces/NS/pods/NAME.
ListAssetsRequest has no content filter field, so filter clusters and namespaces client-side. QueryAssets (SQL) could move filtering server-side later for large organizations; not yet tested.
Reuse:
- From
internal/kube: NewPodData(*corev1.Pod, logger) is already exported and produces the K8S payload (PodData: name, namespace, digests, creation timestamp, owners). processPods holds the phase filter (Running or Failed) but is unexported and takes a *corev1.PodList; export it or change it to take []corev1.Pod so both commands share it. filterNamespaces lists namespaces through the Kubernetes API, so snapshot gke should instead apply filters.CompiledResourceFilter.ShouldInclude to pod.Namespace.
- From
internal/cloudrun: Application Default Credentials setup and the name/regex filter conventions. Do not reuse the registry resolver for fingerprints: isGCPRegistryHost limits it to Artifact Registry and gcr.io, and a tag lookup returns the tag's current digest, which can differ from the running one.
- Environment type: call
ensureEnvironment(envName, "K8S"), as snapshotK8S.go does.
Suggested layout: cmd/kosli/snapshotGKE.go plus internal/gke (or a shared internal/gcp package if the cloud-run client setup can move there).
Edge cases / considerations
- Pod phase and containers: match
snapshot k8s exactly (Running and Failed Pods; status.containerStatuses only, no init or ephemeral containers) by reusing processPods and NewPodData.
- Missing digests:
NewPodData already warns and skips containers without a usable imageID, and skips Pods with none. Keep that behavior.
- Freshness: Asset Inventory is eventually consistent.
k8s.io/Pod has no per-type freshness note.
- Least privilege: document a custom role with
cloudasset.assets.listContainerPod and serviceusage.services.use (required for every Asset Inventory call). roles/cloudasset.viewer also works but grants listResource for every asset type, including k8s.io/Secret, which is available through list and export.
- API enablement: fail with a clear message when
cloudasset.googleapis.com is disabled in the calling project or the caller lacks the list permission.
- Pagination and quotas: respect Asset Inventory rate limits for organization-wide scans.
- Multi-arch digests (to verify): confirm that
imageID for multi-arch images carries the index digest, matching the fingerprint Kosli records at build time.
- Field coverage (to verify): compare Asset Inventory Pod data with
kubectl get pod -o yaml for the fields NewPodData reads (metadata.creationTimestamp, metadata.ownerReferences, spec.containers[].image, status.phase, status.containerStatuses[].image and .imageID).
Acceptance criteria
References
Summary
Add a
kosli snapshot gke ENVIRONMENT-NAMEcommand that reports GKE Pods to a K8S Kosli environment by reading Google Cloud Asset Inventory (cloudasset.googleapis.com) instead of the Kubernetes API server.The reporter needs only a Google Cloud identity with Asset Inventory list permissions. It needs no kubeconfig, no Kubernetes RBAC, and no network path to any cluster control plane.
Motivation
kosli snapshot k8sconnects to the Kubernetes API server through a kubeconfig or in-cluster config. For GKE, that means either running the reporter inside each cluster or opening network access to each control plane, plus managing RBAC per cluster. Many teams restrict control plane access (private clusters, authorized networks); EKS users face the same problem.Cloud Asset Inventory indexes GKE workload objects, including
k8s.io/Pod, as regular Google API resources. A prototype confirmed thatListAssetswithContentType: RESOURCEreturns the full Pod object, includingstatus.containerStatuses[].imageID(REPO@sha256:...). That is the same fieldinternal/kubealready reduces to the Kosli fingerprint, so digests match by construction.Benefits:
cloudasset.googleapis.com.kosli snapshot cloud-runreporter.Proposed CLI
Flags:
--project,--folder,--organization(exactly one), mapped to theparentofListAssets.--cluster/--cluster-regex,--location.--namespaces,--namespaces-regex,--exclude-namespaces,--exclude-namespaces-regex, with the same semantics and mutual exclusivity assnapshot k8s.--dry-run,--auto-environment, and the global flags.Open question: one Kosli environment per invocation spanning several clusters, or a
--config-filemapping clusters and namespaces to environments, assnapshot k8ssupports. Suggest starting with one environment per invocation.Implementation notes
Data source:
cloud.google.com/go/asset/apiv1,ListAssetswithAssetTypes: ["k8s.io/Pod"],ContentType: RESOURCE,PageSize: 1000(the API maximum; default 100).asset.Resource.Data(structpb.Struct) to JSON withprotojson, then unmarshal intocorev1.Pod.//container.googleapis.com/projects/P/locations/L/clusters/C/k8s/namespaces/NS/pods/NAME.ListAssetsRequesthas no content filter field, so filter clusters and namespaces client-side.QueryAssets(SQL) could move filtering server-side later for large organizations; not yet tested.Reuse:
internal/kube:NewPodData(*corev1.Pod, logger)is already exported and produces the K8S payload (PodData: name, namespace, digests, creation timestamp, owners).processPodsholds the phase filter (Running or Failed) but is unexported and takes a*corev1.PodList; export it or change it to take[]corev1.Podso both commands share it.filterNamespaceslists namespaces through the Kubernetes API, sosnapshot gkeshould instead applyfilters.CompiledResourceFilter.ShouldIncludetopod.Namespace.internal/cloudrun: Application Default Credentials setup and the name/regex filter conventions. Do not reuse the registry resolver for fingerprints:isGCPRegistryHostlimits it to Artifact Registry andgcr.io, and a tag lookup returns the tag's current digest, which can differ from the running one.ensureEnvironment(envName, "K8S"), assnapshotK8S.godoes.Suggested layout:
cmd/kosli/snapshotGKE.goplusinternal/gke(or a sharedinternal/gcppackage if the cloud-run client setup can move there).Edge cases / considerations
snapshot k8sexactly (Running and Failed Pods;status.containerStatusesonly, no init or ephemeral containers) by reusingprocessPodsandNewPodData.NewPodDataalready warns and skips containers without a usableimageID, and skips Pods with none. Keep that behavior.k8s.io/Podhas no per-type freshness note.cloudasset.assets.listContainerPodandserviceusage.services.use(required for every Asset Inventory call).roles/cloudasset.vieweralso works but grantslistResourcefor every asset type, includingk8s.io/Secret, which is available through list and export.cloudasset.googleapis.comis disabled in the calling project or the caller lacks the list permission.imageIDfor multi-arch images carries the index digest, matching the fingerprint Kosli records at build time.kubectl get pod -o yamlfor the fieldsNewPodDatareads (metadata.creationTimestamp,metadata.ownerReferences,spec.containers[].image,status.phase,status.containerStatuses[].imageand.imageID).Acceptance criteria
kosli snapshot gke ENV --project Preports Running and Failed Pods from all GKE clusters in P to a K8S environment.snapshot k8sproduces for the same Pod, via the sharedprocessPods/NewPodDatapath.snapshot k8s/snapshot cloud-runcounterparts, including regex variants and mutual exclusivity.--auto-environmentcreates a K8S environment.--dry-runprints the payload without sending it.References
cloudasset.assets.listContainerPod): https://docs.cloud.google.com/iam/docs/roles-permissions/cloudassetinternal/kube/kube.go(NewPodData,processPods,imageFingerprint),internal/cloudrun/,cmd/kosli/snapshotAutoEnvironment.goListAssets(available on request).