1.1 Governance

Audit all Certificates, including status, user, and requested usages

Retrieve all CertificateSigningRequests in all namespaces. Group them by status (i.e., "Pending", "Approved" or "Denied"), and then for each, report (1) the status of the request, (2) group information about the requesting user, and (3) the requested usages for the certificate.

Query:

import {Client, transform} from "carbonql";
const certificates = transform.certificates;

const csrs =
  from csr in c.certificates.v1beta1.CertificateSigningRequest.list()
  let condition = certificates.v1beta1.certificateSigningRequest.getStatus(csr)
  group csr by condition.type into requests
  select {
      status: requests.key,
      requests: requests.ToArray(),
  };

csrs.forEach(csrs => {
  console.log(csrs.status);
  csrs.requests.forEach(({request}) => {
    const usages = request.spec.usages.sort().join(", ");
    const groups = request.spec.groups.sort().join(", ");
    console.log(`\t${request.spec.username}\t[${usages}]\t[${groups}]`);
  });
});

Output:

Denied
  minikube-user    [digital signature, key encipherment, server auth]    [system:authenticated, system:masters]
Pending
  minikube-user    [digital signature, key encipherment, server auth]    [system:authenticated, system:masters]
  minikube-user    [digital signature, key encipherment, server auth]    [system:authenticated, system:masters]

Distinct versions of mysql container in cluster

Search all running Kubernetes Pods for containers that have the string "mysql" in their image name. Report only distinct image names.

Query:

import {Client, query} from "carbonql";

const c = Client.fromFile(<string>process.env.KUBECONFIG);
const mySqlVersions =
  from pod in c.core.v1.Pod.list("default")
  from container in pod.spec.containers
  // Filter image names that don't include "mysql", return distinct.
  where container.image.includes("mysql")
  // Return just the container image.
  select container.image;

mySqlVersions.distinct().forEach(console.log);

Output:

mysql:5.7
mysql:8.0.4
mysql

List all Namespaces with no hard memory quota specified

Retrieve all Kubernetes Namespaces. Filter this down to a set of namespaces for which there is either (1) no ResourceQuota governing resource use of that Namespace; or (2) a ResourceQuota that does not specify a hard memory limit.

Query:

import {Client} from "carbonql";

const c = Client.fromFile(<string>process.env.KUBECONFIG);
const noQuotas =
  from ns in c.core.v1.Namespace.list()
  // Retrive all resource quotas with hard memory limits for given namespace.
  let quotas =
    (from rq in c.core.v1.ResourceQuota(ns.metadata.name)
    where rq.spec.hard["limits.memory"] != null
    select rq)
  // Return only namespaces where there are no such quotas.
  where quotas.Count() == 0
  select ns;

noQuotas.forEach(ns => console.log(ns.metadata.name));

Output:

kube-system
default
kube-public

Pods using the default ServiceAccount

Retrieve all Pods, filtering down to those that are using the "default" ServiceAccount.

Query:

import {Client, certificates} from "carbonql";

const c = Client.fromFile(<string>process.env.KUBECONFIG);
const noServiceAccounts =
  from pod in c.core.v1.Pod.list()
  where pod.spec.serviceAccountName == "default"
  select pod;

noServiceAccounts.forEach(pod => console.log(pod.metadata.name));

Output:

mysql-5-66f5b49b8f-5r48g
mysql-8-7d4f8d46d7-hrktb
mysql-859645bdb9-w29z7
nginx-56b8c64cb4-lcjv2
nginx-56b8c64cb4-n6prt
nginx-56b8c64cb4-v2qj2
nginx2-687c5bbccd-dmccm
nginx2-687c5bbccd-hrqdl
nginx2-687c5bbccd-rzjl5
kube-addon-manager-minikube
kube-dns-54cccfbdf8-hwgh8
kubernetes-dashboard-77d8b98585-gzjgb
storage-provisioner

Find Services publicly exposed to the Internet

Kubernetes Services can expose a Pod to Internet traffic by setting the .spec.type to "LoadBalancer" (see documentation for ServiceSpec). Other Service types (such as "ClusterIP") are accessible only from inside the cluster.

This query will find all Services whose type is "LoadBalancer", so they can be audited for access and cost (since a service with .spec.type set to "LoadBalancer" will typically cause the underlying cloud provider to boot up a dedicated load balancer).

Query:

import {Client} from "carbonql";

const c = Client.fromFile(<string>process.env.KUBECONFIG);
const loadBalancers =
  from service in c.core.v1.Service.list()
  where service.spec.type == "LoadBalancer"
  select service;

// Print.
loadBalancers.forEach(
  svc => console.log(`${svc.metadata.namespace}/${svc.metadata.name}`));

Output:

default/someSvc
default/otherSvc
prod/apiSvc
dev/apiSvc

Find users and ServiceAccounts with access to Secrets

Inspect every Kubernetes RBAC Role for rules that apply to Secrets. Using this, find every RBAC RoleBinding that references each of these ruels, and list users and ServiceAccounts that they bind to.

NOTE: This query does not query for [ClusterRoles][clusterroles], which means that cluster-level roles granting access to secrets are not taken into account in this query.

Query:

import {Client, transform} from "carbonql";
const rbac = transform.rbacAuthorization

const c = Client.fromFile(<string>process.env.KUBECONFIG);
const subjectsWithSecretAccess =
  from role in c.rbacAuthorization.v1beta1.Role.list()
  from binding in c.rbacAuthorization.v1beta1.RoleBinding.list()
  where rbac.v1beta1.roleBinding.referencesRole(binding, role.metadata.name)
  select binding.subjects;

subjectsWithSecretAccess.forEach(subj => console.log(`${subj.kind}\t${subj.name}`));

Output:

User    jane
User    frank
User    susan
User    bill

List Pods and their ServiceAccount (possibly a unique user) by Secrets they use

Obtain all Secrets. For each of these Secrets, obtain all Pods that use them.

Here we print (1) the name of the Secret, (2) the list of Pods that use it, and (3) the ServiceAccount that the Pod runs as (oftentimes this is allocated to a single user).

Query:

import {Client, query} from "carbonql";

const c = Client.fromFile(<string>process.env.KUBECONFIG);
const podsByClaim =
  from secret in c.core.v1.Secret.list()
  let podsWithSecretAccess =
    (from pod in c.core.v1.Pod.list()
    where
      // Filter down to pods where at least one volume
      // references `secret`.
      (from volume in pod.spec.volumes
      where volume.secret != null && volume.secret.secretName == secret.metadata.name
      select volume).Count() > 0
    // Return pods satisfying this condition.
    select pod)
  // Return object with `secret` and all pods that
  // access it.
  select new {secret: secret, pods: podsWithSecretAccess};

// Print.
podsByClaim.forEach(({secret, pods}) => {
  console.log(secret.metadata.name);
  pods.forEach(pod => console.log(`  ${pod.spec.serviceAccountName} ${pod.metadata.namespace}/${pod.metadata.name}`));
});

Input:

kubernetes-dashboard-key-holder
default-token-vq5hb
  default kube-system/kube-dns-54cccfbdf8-hwgh8
  default kube-system/kubernetes-dashboard-77d8b98585-gzjgb
  default kube-system/storage-provisioner
default-token-j2bmb
  alex default/mysql-5-66f5b49b8f-5r48g
  alex default/mysql-8-7d4f8d46d7-hrktb
  alex default/mysql-859645bdb9-w29z7
  alex default/nginx-56b8c64cb4-lcjv2
  alex default/nginx-56b8c64cb4-n6prt
  alex default/nginx-56b8c64cb4-v2qj2
  alex default/nginx2-687c5bbccd-dmccm
  alex default/nginx2-687c5bbccd-hrqdl
  alex default/nginx2-687c5bbccd-rzjl5
  alex default/task-pv-pod
default-token-n9vxk

List Pods grouped by PersistentVolumes they use

Obtain all "Bound" PersistentVolumes (PVs). Then, obtain all Pods that use those PVs. Finally, print a small report listing the PV and all Pods that reference it.

Query:

import {Client, query} from "carbonql";

const c = Client.fromFile(<string>process.env.KUBECONFIG);
const podsByClaim =
  from pv in c.core.v1.PersistentVolume.list()
  let podsWithPvAccess =
    (from pod in c.core.v1.Pod.list()
    where
      // Filter down to pods where at least one volume
      // references `pv`.
      (from volume in pod.spec.volumes
      where volume.persistentVolumeClaim != null &&
        volume.persistentVolumeClaim.claimName == pv.spec.claimRef.name
      select volume).Count() > 0
    // Return pods satisfying this condition.
    select pod)
  // Return object with `secret` and all pods that
  // access it.
  select new {secret: secret, pods: podsWithPvAccess};

// Print.
podsByClaim.forEach(({pv, pods}) => {
  console.log(pv.metadata.name);
  pods.forEach(pod => console.log(`  ${pod.metadata.namespace}/${pod.metadata.name}`));
});

Output:

devHtmlData
  dev/cgiGateway-1
  dev/cgiGateway-2
prodHtmlData
  prod/cgiGateway-1
  prod/cgiGateway-2

results matching ""

    No results matching ""