Skip to content

feat(snapshot): add kosli snapshot gke using Cloud Asset Inventory (no Kubernetes API access) #1231

Description

@mbevc1

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

  • kosli snapshot gke ENV --project P reports Running and Failed Pods from all GKE clusters in P to a K8S environment.
  • The payload for a given Pod is identical to what snapshot k8s produces for the same Pod, via the shared processPods / NewPodData path.
  • Cluster, location, and namespace filters behave like their snapshot k8s / snapshot cloud-run counterparts, including regex variants and mutual exclusivity.
  • --auto-environment creates a K8S environment.
  • --dry-run prints the payload without sending it.
  • Unit tests use fixture Asset Inventory responses; no live GCP calls in unit tests.
  • Docs: command reference, least-privilege IAM setup, required API, freshness caveat, and a tutorial for running it as a Cloud Run Job (mirroring the Cloud Run reporter tutorial).

References

No activity

Activity on this issue will appear here.

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions