public interface DetectorConfig {

  /**
   * Returns the name of the detector.
   */
  String getDetectorName();

  /**
   * Returns the {@link ResourceType} checked by the detector.
   * <p>
   * Used when the detector operates on a singular {@link ResourceType}.
   */
  ResourceType getAssetFilter();

  /**
   * Returns an array of {@link ResourceType}s checked by the detector.
   * <p>
   * Used when the detector operates on multiple {@link ResourceType}s.
   */
  default ResourceType[] getAssetFilters() {
    return new ResourceType[]{getAssetFilter()};
  }

  /**
   * Returns the {@link MisconfigurationLevel} of the detector.
   */
  MisconfigurationLevel getMisconfigurationLevel();

  /**
   * Returns the {@link Severity} of the vulnerability detected by the detector.
   */
  Severity getSeverity();

  /**
   * Returns an explanation for why the findings created by the Detector constitute a security
   * risk.
   */
  String getExplanationText();

  /**
   * Returns a recommendation to the user for remediating the vulnerability detected by the
   * detector.
   */
  String getRecommendationText();

  /**
   * Returns a list of useful URLs that users can visit to learn about vulnerabilities detected by
   * the detector.
   */
  List<String> getUsefulURLs();

  /**
   * Returns True if the given {@link Asset} has the vulnerability the detector is searching for.
   * This method should not be called directly, use {@link DetectorConfig#isAssetVulnerable(Asset)}
   * instead!
   */
  Boolean isVulnerable(Asset asset);

  /**
   * Returns False if the given {@link Asset} is null or does not have the vulnerability the
   * detector is searching for, True otherwise.
   */
  default Boolean isAssetVulnerable(Asset asset) {
    if (isNull(asset)) {
      return false;
    }
    return isVulnerable(asset);
  }

  /**
   * Returns a {@link Logger.Builder} representation of the detector.
   */
  default Logger.Builder getReport(boolean quiet) {
    Builder builder = new Logger.Builder().addMessage("Detector", getDetectorName());
    if (!quiet) {
      builder.addSubMessage("Explanation", getExplanationText())
          .addSubMessage("Remediation", getRecommendationText())
          .addSubMessage("Useful links", getUsefulURLs().toString())
          .addSubMessage("Level", getMisconfigurationLevel().name())
          .addSubMessage("Severity", getSeverity().name(),
              getSeverity().equals(Severity.MEDIUM) || getSeverity().equals(Severity.LOW)
                  ? Color.DEFAULT : Color.RED);
    }
    return builder;
  }

} public class NodeAffinity extends NodeIsolationDetectorConfig {

  @Override
  public String getDetectorName() {
    return "NODE_AFFINITY";
  }

  /**
   * A detector auditing the requiredDuringSchedulingIgnoredDuringExecution node affinity that has
   * to be met for a pod to be scheduled onto a node. Checks if the pod was rejected from the node
   * based on node affinity selectors.
   * <p>
   * If you specify multiple nodeSelectorTerms associated with nodeAffinity types, then the pod can
   * be scheduled onto a node if one of the nodeSelectorTerms can be satisfied.
   * <p>
   * If you specify multiple matchExpressions associated with nodeSelectorTerms, then the pod can be
   * scheduled onto a node only if all matchExpressions is satisfied.
   * @param asset asset to audit
   * @return true if asset is vulnerable, false otherwise
   */
  @Override
  public Boolean isVulnerable(Asset asset) {
    NodePodBinding nodePodBinding = getNodePodBinding(asset);
    if (nodePodBinding == null
        || nodePodBinding.getNode() == null
        || nodePodBinding.getPod() == null) {
      return false;
    }

    Map<String, String> labels = nodePodBinding.getNode().getLabels();
    List<V1NodeSelectorTerm> nodeSelectorTerms = nodePodBinding.getPod()
        .getNodeAffinitySelectorTerms();

    List<LabelSelector> missingLabelSelectors = new ArrayList<>();
    // Any nodeSelectorTerm must be satisfied.
    for (V1NodeSelectorTerm term : nodeSelectorTerms) {
      if (term.getMatchExpressions() == null) {
        continue;
      }

      boolean nodeSelectorTermMatches = true;
      // All matchExpressions must be satisfied.
      for (V1NodeSelectorRequirement matchExpression : term.getMatchExpressions()) {
        String value = labels.getOrDefault(matchExpression.getKey(), null);
        if (!matches(value, matchExpression.getOperator(), matchExpression.getValues())) {
          missingLabelSelectors.add(new LabelSelector(matchExpression.getKey(),
              matchExpression.getValues() == null ? "[]" : matchExpression.getValues().toString(),
              "Operator",
              matchExpression.getOperator()));
          nodeSelectorTermMatches = false;
        }
      }

      if (nodeSelectorTermMatches) {
        // Found a matching nodeSelectorTerm.
        return false;
      }
    }

    nodePodBinding.addMissingLabelSelectors(missingLabelSelectors);
    return missingLabelSelectors.size() > 0;
  }

  /**
   * Checks if the given value matches the given selector operator and values.
   * @param value            value to check
   * @param selectorOperator represents the relationship to a set of values
   * @param selectorValues   array of string values
   * @return true if the value matches, false otherwise
   */
  private boolean matches(String value, String selectorOperator, List<String> selectorValues) {
    if (selectorValues == null) {
      return false;
    }

    switch (selectorOperator) {
      case "In": {
        return value != null && selectorValues.contains(value);
      }
      case "NotIn": {
        return value != null && !selectorValues.contains(value);
      }
      case "Exists": {
        return value != null;
      }
      case "DoesNotExist": {
        return value == null;
      }
      case "Gt": {
        return StringUtils.isNumeric(value)
            && selectorValues.stream().allMatch(selector -> StringUtils.isNumeric(selector)
            && Integer.parseInt(value) > Integer.parseInt(selector));
      }
      case "Lt": {
        return StringUtils.isNumeric(value)
            && selectorValues.stream().allMatch(selector -> StringUtils.isNumeric(selector)
            && Integer.parseInt(value) < Integer.parseInt(selector));
      }
      default: {
        return false;
      }
    }
  }

  @Override
  public String getExplanationText() {
    return "Node affinity allows you to constrain which nodes your pod is eligible to be scheduled "
        + "on, based on labels on the node. It is conceptually similar to nodeSelector, but greatly "
        + "expands types of constraints you can express.";
  }

  @Override
  public List<String> getUsefulURLs() {
    return List
        .of("https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity");
  }

} public class NodeSelector extends NodeIsolationDetectorConfig {

  @Override
  public String getDetectorName() {
    return "NODE_SELECTOR";
  }

  /**
   * A detector auditing the nodeSelectors required for a pod to be scheduled onto a node. Checks if
   * the pod was rejected from the node based on the nodeSelectors.
   * <p>
   * For the pod to be eligible to run on a node, the node must have each of the indicated key-value
   * pairs as labels (it can have additional labels as well). The most common usage is one key-value
   * pair.
   * @param asset asset to audit
   * @return true if asset is vulnerable, false otherwise
   */
  @Override
  public Boolean isVulnerable(Asset asset) {
    NodePodBinding nodePodBinding = getNodePodBinding(asset);
    if (nodePodBinding == null
        || nodePodBinding.getNode() == null
        || nodePodBinding.getPod() == null) {
      return false;
    }

    Map<String, String> labels = nodePodBinding.getNode().getLabels();
    Map<String, String> nodeSelectors = nodePodBinding.getPod().getNodeSelectors();

    List<LabelSelector> missingLabelSelectors = new ArrayList<>();
    for (Entry<String, String> entry : nodeSelectors.entrySet()) {
      if (!entry.getValue().equals(labels.getOrDefault(entry.getKey(), null))) {
        missingLabelSelectors.add(new LabelSelector(entry.getKey(), entry.getValue()));
      }
    }

    nodePodBinding.addMissingLabelSelectors(missingLabelSelectors);
    return missingLabelSelectors.size() > 0;
  }

  @Override
  public String getExplanationText() {
    return "nodeSelector is the simplest recommended form of node selection constraint. "
        + "nodeSelector specifies a map of key-value pairs. For the pod to be eligible to run on "
        + "a node, the node must have each of the indicated key-value pairs as labels (it can have "
        + "additional labels as well). The most common usage is one key-value pair.";
  }

  @Override
  public List<String> getUsefulURLs() {
    return List
        .of("https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector");
  }

} public class NodeTaint extends NodeIsolationDetectorConfig {

  @Override
  public String getDetectorName() {
    return "NODE_TAINT";
  }

  /**
   * Checks if the pod was rejected from the node based on node taints.
   * <p>
   * Taints and tolerations work together to ensure that pods are not scheduled onto inappropriate
   * nodes. One or more taints are applied to a node; this marks that the node should not accept any
   * pods that do not tolerate the taints.
   * @param asset asset to audit
   * @return true if asset is vulnerable, false otherwise
   */
  @Override
  public Boolean isVulnerable(Asset asset) {
    NodePodBinding nodePodBinding = getNodePodBinding(asset);
    if (nodePodBinding == null
        || nodePodBinding.getNode() == null
        || nodePodBinding.getPod() == null) {
      return false;
    }

    List<V1Toleration> tolerations = nodePodBinding.getPod().getTolerations();
    List<V1Taint> taints = nodePodBinding.getNode().getTaints();
    if (tolerations == null || taints == null) {
      return false;
    }

    List<LabelSelector> missingLabelSelectors = new ArrayList<>();
    for (V1Taint taint : taints) {
      boolean hasMatchingToleration = false;
      for (V1Toleration toleration : tolerations) {
        if (areMatching(toleration, taint)) {
          hasMatchingToleration = true;
          break;
        }
      }

      if (!hasMatchingToleration) {
        missingLabelSelectors
            .add(new LabelSelector(taint.getKey(), taint.getValue(), "Effect", taint.getEffect()));
      }
    }

    nodePodBinding.addMissingLabelSelectors(missingLabelSelectors);
    return missingLabelSelectors.size() > 0;
  }

  /**
   * Checks if the pod toleration matches the node taint.
   * <p>
   * A toleration "matches" a taint if the keys are the same and the effects are the same, and: the
   * operator is Exists (in which case no value should be specified), or the operator is Equal and
   * the values are equal.
   * <p>
   * There are two special cases: an empty key with operator Exists matches all keys, values and
   * effects which means this will tolerate everything. An empty effect matches all effects with key
   * key.
   * @param toleration pod toleration
   * @param taint      node taint
   * @return true if the toleration and taint match, false otherwise
   */
  private boolean areMatching(V1Toleration toleration, V1Taint taint) {
    if (toleration.getKey() == null || toleration.getEffect() == null) {
      return false;
    }

    boolean tolerationMatchesTaint = toleration.getKey().equals(taint.getKey())
        && areMatchingOnEffect(toleration, taint)
        && ("Exists".equals(toleration.getOperator())
        || "Equal".equals(toleration.getOperator()) && toleration.getValue() != null
        && toleration.getValue().equals(taint.getValue()));

    return tolerationMatchesTaint || isTolerationKeyEmpty(toleration);
  }

  /**
   * Checks if the toleration key is empty.
   * @param toleration toleration to check
   * @return true if the key is empty, false otherwise
   */
  private boolean isTolerationKeyEmpty(V1Toleration toleration) {
    return toleration.getKey() != null && toleration.getKey().isEmpty();
  }

  /**
   * Returns true if the toleration and taint effects match. The effects either have to be the same,
   * or the toleration effect is empty and matches all effects.
   * @param toleration toleration
   * @param taint      taint
   * @return true if the toleration and taint match, false otherwise
   */
  private boolean areMatchingOnEffect(V1Toleration toleration, V1Taint taint) {
    return toleration.getEffect() != null
        && (toleration.getEffect().isEmpty() || toleration.getEffect().equals(taint.getEffect()));
  }


  @Override
  public String getExplanationText() {
    return "Taints allow a node to repel a set of pods. Tolerations are applied to pods, and allow "
        + "(but do not require) the pods to schedule onto nodes with matching taints. Taints and "
        + "tolerations work together to ensure that pods are not scheduled onto inappropriate nodes. "
        + "One or more taints are applied to a node; this marks that the node should not accept any "
        + "pods that do not tolerate the taints.";
  }

  @Override
  public List<String> getUsefulURLs() {
    return List
        .of("https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/");
  }

} public class AddedCapabilities extends PodSecurityPolicyDetectorConfig {

  @Override
  public String getDetectorName() {
    return "CONTAINERS_ADDED_CAPABILITIES";
  }

  @Override
  public Boolean isVulnerable(Asset asset) {
    PodSecurityPolicy psp = getPodSecurityPolicy(asset);
    List<String> allowedCapabilities = psp.getAllowedCapabilities();
    if (allowedCapabilities != null && !allowedCapabilities.isEmpty()) {
      psp.addMisconfiguration("Allowed capabilities", allowedCapabilities.toString());
      return true;
    }

    return false;
  }

  @Override
  public String getExplanationText() {
    return "Containers run with a default set of capabilities as assigned by the Container Runtime."
        + "Capabilities outside this set can be added to containers which could expose them to "
        + "risks of container breakout attacks. There should be at least one PodSecurityPolicy (PSP) "
        + "defined which prevents containers with capabilities beyond the default set from launching.";
  }

  @Override
  public List<String> getUsefulURLs() {
    return List
        .of(getPSPDocumentationLink(),
            "https://kubernetes.io/docs/concepts/policy/pod-security-policy/#capabilities");
  }

  @Override
  public String getRecommendationText() {
    return getRecommendationText("additional capabilities")
        + "Ensure that allowedCapabilities is not present in PSPs for the cluster unless it is set to an empty array.";
  }

} public class AllowPrivilegeEscalation extends PodSecurityPolicyDetectorConfig {

  @Override
  public String getDetectorName() {
    return "CONTAINER_ALLOW_PRIVILEGE_ESCALATION";
  }

  @Override
  public Boolean isVulnerable(Asset asset) {
    PodSecurityPolicy psp = getPodSecurityPolicy(asset);
    boolean allowPrivilegeEscalation = psp.getAllowPrivilegeEscalation();
    if (allowPrivilegeEscalation) {
      psp.addMisconfiguration("AllowPrivilegeEscalation", "true");
      return true;
    }
    return false;
  }

  @Override
  public String getExplanationText() {
    return "A container running with the allowPrivilegeEscalation flag set to true may have "
        + "processes that can gain more privileges than their parent. There should be at least one "
        + "PodSecurityPolicy (PSP) defined which does not permit containers to allow privilege "
        + "escalation. The option exists (and is defaulted to true) to permit setuid binaries to run.";
  }

  @Override
  public List<String> getUsefulURLs() {
    return List
        .of(getPSPDocumentationLink(),
            "https://kubernetes.io/docs/concepts/policy/pod-security-policy/#privilege-escalation");
  }

  @Override
  public String getRecommendationText() {
    return getRecommendationText("setuid binaries or require privilege escalation")
        + getRemediationText(".spec.allowPrivilegeEscalation", "false");
  }

} public class CapabilitiesAssigned extends PodSecurityPolicyDetectorConfig {

  @Override
  public String getDetectorName() {
    return "CONTAINERS_CAPABILITIES_ASSIGNED";
  }

  @Override
  public Boolean isVulnerable(Asset asset) {
    PodSecurityPolicy psp = getPodSecurityPolicy(asset);
    List<String> requiredDropCapabilities = psp.getRequiredDropCapabilities();
    if (requiredDropCapabilities != null && requiredDropCapabilities.contains("ALL")) {
      return false;
    }

    psp.addMisconfiguration("Required Drop Capabilities",
        requiredDropCapabilities == null ? "[]" : requiredDropCapabilities.toString());
    return true;
  }

  @Override
  public String getExplanationText() {
    return "Containers run with a default set of capabilities as assigned by the Container "
        + "Runtime. Capabilities are parts of the rights generally granted on a Linux system to "
        + "the root user. In many cases applications running in containers do not require any "
        + "capabilities to operate, so from the perspective of the principal of least privilege use "
        + "of capabilities should be minimized.";
  }

  @Override
  public List<String> getUsefulURLs() {
    return List
        .of(getPSPDocumentationLink(),
            "https://kubernetes.io/docs/concepts/policy/pod-security-policy/#capabilities");
  }

  @Override
  public String getRecommendationText() {
    return "Review the use of capabilities in applications running on your cluster. Where a "
        + "namespace contains applications which do not require any Linux capabilities to operate "
        + "consider adding a PSP which forbids the admission of containers which do not drop all "
        + "capabilities.";
  }

} public class NetRawCapabilities extends PodSecurityPolicyDetectorConfig {

  @Override
  public String getDetectorName() {
    return "CONTAINERS_NET_RAW_CAPABILITIES";
  }

  @Override
  public Boolean isVulnerable(Asset asset) {
    PodSecurityPolicy psp = getPodSecurityPolicy(asset);
    List<String> requiredDropCapabilities = psp.getRequiredDropCapabilities();
    if (requiredDropCapabilities != null
        && !requiredDropCapabilities.contains("ALL")
        && !requiredDropCapabilities.contains("NET_RAW")) {
      psp.addMisconfiguration("Required Drop Capabilities", requiredDropCapabilities.toString());
      return true;
    }
    return false;
  }

  @Override
  public String getExplanationText() {
    return "Containers run with a default set of capabilities as assigned by the Container "
        + "Runtime. By default, this can include potentially dangerous capabilities. With Docker as "
        + "the container runtime the NET_RAW capability is enabled which may be misused by malicious "
        + "containers. Ideally, all containers should drop this capability. There should be at least "
        + "one PodSecurityPolicy (PSP) defined which prevents containers with the NET_RAW capability "
        + "from launching.";
  }

  @Override
  public List<String> getUsefulURLs() {
    return List
        .of(getPSPDocumentationLink(),
            "https://kubernetes.io/docs/concepts/policy/pod-security-policy/#capabilities");
  }

  @Override
  public String getRecommendationText() {
    return getRecommendationText("NET_RAW capability") + getRemediationText(
        ".spec.requiredDropCapabilities", "either NET_RAW or ALL");
  }

} public class PrivilegedContainers extends PodSecurityPolicyDetectorConfig {

  @Override
  public String getDetectorName() {
    return "PRIVILEGED_CONTAINERS";
  }

  @Override
  public Boolean isVulnerable(Asset asset) {
    PodSecurityPolicy psp = getPodSecurityPolicy(asset);
    boolean privileged = psp.getPrivileged();
    if (privileged) {
      psp.addMisconfiguration("Privileged", "true");
      return true;
    }
    return false;
  }

  @Override
  public String getExplanationText() {
    return "Privileged containers have access to all Linux Kernel capabilities and devices. "
        + "A container running with full privileges can do almost everything that the host can do. "
        + "This flag exists to allow special use-cases, like manipulating the network stack and "
        + "accessing devices. There should be at least one PodSecurityPolicy (PSP) defined which "
        + "does not permit privileged containers.";
  }

  @Override
  public List<String> getUsefulURLs() {
    return List
        .of(getPSPDocumentationLink(),
            "https://kubernetes.io/docs/concepts/policy/pod-security-policy/#privileged",
            "https://www.nccgroup.com/uk/our-research/abusing-privileged-and-unprivileged-linux-containers/");
  }

  @Override
  public String getRecommendationText() {
    return getRecommendationText("privileges") + getRemediationText(".spec.privileged", "false");
  }

} public class RootContainersAdmission extends PodSecurityPolicyDetectorConfig {

  @Override
  public String getDetectorName() {
    return "ROOT_CONTAINERS_ADMISSION";
  }

  /**
   * Verifies that there is at least one PSP which returns MustRunAsNonRoot or MustRunAs with the
   * range of UIDs not including 0.
   * @param asset asset to audit
   * @return true if asset is vulnerable, false otherwise
   */
  @Override
  public Boolean isVulnerable(Asset asset) {
    PodSecurityPolicy psp = getPodSecurityPolicy(asset);
    PolicyV1beta1RunAsUserStrategyOptions policy = psp.getRunAsUser();
    if (policy != null
        && !(policy.getRule().equals("MustRunAsNonRoot")
        || policy.getRule().equals("MustRunAs") && validateMustRunAs(policy))) {
      psp.addRunAsUserMisconfigurationToRepresentation();
      return true;
    }
    return false;
  }

  /**
   * Validated the {@link PolicyV1beta1RunAsUserStrategyOptions} MustRunAs policy to ensure
   * containers are not permitted to be run as root.
   * <p>
   * The policy must not include 0 in its range of UIDs.
   * @param policy policy to check
   * @return true if the policy does not allow containers to be run as root, false otherwise
   */
  private boolean validateMustRunAs(PolicyV1beta1RunAsUserStrategyOptions policy) {
    if (policy.getRanges() != null) {
      for (PolicyV1beta1IDRange range : policy.getRanges()) {
        if (range.getMin() == 0) {
          return false;
        }
      }
    }
    return true;
  }

  @Override
  public String getExplanationText() {
    return "Containers may run as any Linux user. Containers which run as the root user, whilst "
        + "constrained by Container Runtime security features still have an escalated likelihood of "
        + "container breakout. Ideally, all containers should run as a defined non-UID 0 user. "
        + "There should be at least one PodSecurityPolicy (PSP) defined which does not permit root "
        + "users in a container.";
  }

  @Override
  public List<String> getUsefulURLs() {
    return List
        .of(getPSPDocumentationLink(),
            "https://kubernetes.io/docs/concepts/policy/pod-security-policy/#volumes-and-file-systems",
            "https://kubernetes.io/docs/concepts/policy/pod-security-policy/#users-and-groups");
  }

  @Override
  public String getRecommendationText() {
    return getRecommendationText("root containers") + getRemediationText("spec.runAsUser.rule",
        "either MustRunAsNonRoot or MustRunAs with the range of UIDs not including 0");
  }

} public class SharingHostIPC extends PodSecurityPolicyDetectorConfig {

  @Override
  public String getDetectorName() {
    return "CONTAINERS_SHARING_HOST_IPC_NAMESPACE";
  }

  @Override
  public Boolean isVulnerable(Asset asset) {
    PodSecurityPolicy psp = getPodSecurityPolicy(asset);
    boolean hostIPC = psp.getHostIPC();
    if (hostIPC) {
      psp.addMisconfiguration("HostIPC", "true");
      return true;
    }
    return false;
  }

  @Override
  public String getExplanationText() {
    return "A container running in the host's IPC namespace can use IPC to interact with processes "
        + "outside the container. There should be at least one PodSecurityPolicy (PSP) defined which "
        + "does not permit containers to share the host IPC namespace.";
  }

  @Override
  public List<String> getUsefulURLs() {
    return List
        .of(getPSPDocumentationLink(),
            "https://kubernetes.io/docs/concepts/policy/pod-security-policy/#host-namespaces");
  }

  @Override
  public String getRecommendationText() {
    return getRecommendationText("hostIPC") + getRemediationText("spec.hostIPC", "false");
  }

} public class SharingHostNetworkNamespace extends PodSecurityPolicyDetectorConfig {

  @Override
  public String getDetectorName() {
    return "CONTAINER_SHARING_HOST_NETWORK_NAMESPACE";
  }

  @Override
  public Boolean isVulnerable(Asset asset) {
    PodSecurityPolicy psp = getPodSecurityPolicy(asset);
    boolean hostNetwork = psp.getHostNetwork();
    if (hostNetwork) {
      psp.addMisconfiguration("HostNetwork", "true");
      return true;
    }
    return false;
  }

  @Override
  public String getExplanationText() {
    return "A container running in the host's network namespace could access the local loopback "
        + "device, and could access network traffic to and from other pods. There should be at least "
        + "one PodSecurityPolicy (PSP) defined which does not permit containers to share the host "
        + "network namespace.";
  }

  @Override
  public List<String> getUsefulURLs() {
    return List
        .of(getPSPDocumentationLink(),
            "https://kubernetes.io/docs/concepts/policy/pod-security-policy/#host-namespaces");
  }

  @Override
  public String getRecommendationText() {
    return getRecommendationText("hostNetwork") + getRemediationText("spec.hostNetwork", "false");
  }

} public class SharingHostPIDNamespace extends PodSecurityPolicyDetectorConfig {

  @Override
  public String getDetectorName() {
    return "CONTAINERS_SHARING_HOST_PROCESS_ID_NAMESPACE";
  }

  @Override
  public Boolean isVulnerable(Asset asset) {
    PodSecurityPolicy psp = getPodSecurityPolicy(asset);
    boolean hostPID = psp.getHostPID();
    if (hostPID) {
      psp.addMisconfiguration("HostPID", "true");
      return true;
    }
    return false;
  }

  @Override
  public String getExplanationText() {
    return "A container running in the host's PID namespace can inspect processes running outside "
        + "the container. If the container also has access to ptrace capabilities this can be used to "
        + "escalate privileges outside of the container. There should be at least one PodSecurityPolicy "
        + "(PSP) defined which does not permit containers to share the host PID namespace.";
  }

  @Override
  public List<String> getUsefulURLs() {
    return List
        .of(getPSPDocumentationLink(),
            "https://kubernetes.io/docs/concepts/policy/pod-security-policy/#host-namespaces");
  }

  @Override
  public String getRecommendationText() {
    return getRecommendationText("hostPID") + getRemediationText(".spec.hostPID", "false");
  }

} public class AccessToSecrets extends KubernetesDetectorConfig {

  /**
   * Set of escalating verbs.
   */
  private static final HashSet<String> ESCALATING_VERBS = new HashSet<>(
      List.of("get", "list", "watch"));
  /**
   * The secrets resource.
   */
  private static final String SECRET = "secrets";

  @Override
  public String getDetectorName() {
    return "ACCESS_TO_SECRETS";
  }

  @Override
  public ResourceType[] getAssetFilters() {
    return new ResourceType[]{ResourceType.CLUSTER_ROLE, ResourceType.ROLE};
  }

  @Override
  public Boolean isVulnerable(Asset asset) {
    KubernetesRole role = getKubernetesRole(asset);
    List<V1beta1PolicyRule> rules = role.getRules();

    List<V1beta1PolicyRule> vulnerableRules = new ArrayList<>();
    for (V1beta1PolicyRule rule : rules) {
      if (hasSecrets(rule.getResources()) && hasEscalatingVerbs(rule.getVerbs())) {
        vulnerableRules.add(rule);
      }
    }
    role.addVulnerableRules(vulnerableRules);
    return !vulnerableRules.isEmpty();
  }

  /**
   * Checks if the given list of resources contains "secrets" resource.
   * @param resources list of resources to check
   * @return true if the given list contains "secrets", false otherwise
   */
  private boolean hasSecrets(List<String> resources) {
    return resources != null && resources.contains(SECRET);
  }

  /**
   * Checks if the given list of verbs contains escalating verbs.
   * @param verbs list of verbs to check
   * @return true if the given list contains any escalating verbs, false otherwise
   */
  private boolean hasEscalatingVerbs(List<String> verbs) {
    return verbs != null && !Collections.disjoint(verbs, ESCALATING_VERBS);
  }

  @Override
  public String getExplanationText() {
    return "The Kubernetes API stores secrets, which may be service account tokens for the "
        + "Kubernetes API or credentials used by workloads in the cluster. Access to these secrets "
        + "should be restricted to the smallest possible group of users to reduce the risk of "
        + "privilege escalation. Inappropriate access to secrets stored within the Kubernetes "
        + "cluster can allow for an attacker to gain additional access to the Kubernetes cluster "
        + "or external resources whose credentials are stored as secrets.";
  }

  @Override
  public String getRecommendationText() {
    return "Review the users who have get, list or watch access to secrets objects in the "
        + "Kubernetes API. Where possible, remove access. Care should be taken not to remove "
        + "access to secrets to system components which require this for their operation.";
  }

  @Override
  public List<String> getUsefulURLs() {
    return List.of("https://kubernetes.io/docs/concepts/configuration/secret/");
  }

  /**
   * Returns the asset as a {@link KubernetesRole}, or null if such conversion is not possible.
   * @param asset asset to convert
   * @return asset as a {@link KubernetesRole}
   */
  private KubernetesRole getKubernetesRole(Asset asset) {
    if (asset instanceof KubernetesRole) {
      return (KubernetesRole) asset;
    }
    return null;
  }

} public final class AutomountServiceAccountTokenEnabled extends KubernetesDetectorConfig {

  @Override
  public String getDetectorName() {
    return "AUTOMOUNT_SERVICE_ACCOUNT_TOKENS_ENABLED";
  }

  @Override
  public ResourceType getAssetFilter() {
    return ResourceType.SERVICE_ACCOUNT;
  }

  @Override
  public Boolean isVulnerable(Asset asset) {
    ServiceAccount serviceAccount = getServiceAccount(asset);
    return serviceAccount.getAutomountServiceAccountToken();
  }

  @Override
  public String getExplanationText() {
    return "Service accounts tokens should not be mounted in pods except where the workload "
        + "running in the pod explicitly needs to communicate with the API server. Mounting "
        + "service account tokens inside pods can provide an avenue for privilege escalation attacks "
        + "where an attacker is able to compromise a single pod in the cluster. Avoiding mounting "
        + "these tokens removes this attack avenue.";
  }

  @Override
  public List<String> getUsefulURLs() {
    return List
        .of("https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/");
  }

  @Override
  public String getRecommendationText() {
    return "Modify the definition of pods and service accounts which do not need to "
        + "mount service account tokens to disable it.";
  }

  /**
   * Returns the asset as a {@link ServiceAccount}, or null if such conversion is not possible.
   * @param asset asset to convert
   * @return asset as a {@link ServiceAccount}
   */
  private ServiceAccount getServiceAccount(Asset asset) {
    if (asset instanceof ServiceAccount) {
      return (ServiceAccount) asset;
    }
    return null;
  }

} public class ClusterAdminRoleUsed extends KubernetesDetectorConfig {

  /**
   * Role name for cluster admin.
   */
  private static final String CLUSTER_ADMIN = "cluster-admin";

  @Override
  public String getDetectorName() {
    return "CLUSTER_ADMIN_ROLE_USED";
  }

  @Override
  public ResourceType getAssetFilter() {
    return ResourceType.CLUSTER_ROLE_BINDING;
  }

  @Override
  public Boolean isVulnerable(Asset asset) {
    ClusterRoleBinding clusterRoleBinding = getClusterRoleBinding(asset);
    return CLUSTER_ADMIN.equals(clusterRoleBinding.getRoleRefName());
  }

  @Override
  public String getExplanationText() {
    return "Kubernetes provides a set of default roles where RBAC is used. Some of these roles "
        + "such as cluster-admin provide wide-ranging privileges which should only be applied where "
        + "absolutely necessary. Roles such as cluster-admin allow super-user access to perform any "
        + "action on any resource. When used in a ClusterRoleBinding, it gives full control over "
        + "every resource in the cluster and in all namespaces. When used in a RoleBinding, it gives "
        + "full control over every resource in the RoleBinding's namespace, including the namespace "
        + "itself.";
  }

  @Override
  public List<String> getUsefulURLs() {
    return List.of("https://kubernetes.io/docs/admin/authorization/rbac/#user-facing-roles");
  }

  @Override
  public String getRecommendationText() {
    return "Identify all ClusterRoleBindings to the cluster-admin role. Check if they are used and "
        + "if they need this role or if they could use a role with fewer privileges. Where possible, "
        + "first bind users to a lower privileged role and then remove the clusterrolebinding to the "
        + "cluster-admin role. Care should be taken before removing any clusterrolebindings from "
        + "the environment to ensure they were not required for operation of the cluster. Specifically, "
        + "modifications should not be made to clusterrolebindings with the system: prefix as they "
        + "are required for the operation of system components.";
  }

  /**
   * Returns the asset as a {@link ClusterRoleBinding}, or null if such conversion is not possible.
   * @param asset asset to convert
   * @return asset as a {@link ClusterRoleBinding}
   */
  private ClusterRoleBinding getClusterRoleBinding(Asset asset) {
    if (asset instanceof ClusterRoleBinding) {
      return (ClusterRoleBinding) asset;
    }
    return null;
  }

} public class CreatePodsAllowed extends KubernetesDetectorConfig {

  /**
   * Create verb.
   */
  private static final String CREATE = "create";
  /**
   * Pod resource name.
   */
  private static final String PODS = "pods";

  @Override
  public String getDetectorName() {
    return "CREATE_PODS_ALLOWED";
  }

  @Override
  public ResourceType[] getAssetFilters() {
    return new ResourceType[]{ResourceType.CLUSTER_ROLE, ResourceType.ROLE};
  }

  @Override
  public Boolean isVulnerable(Asset asset) {
    KubernetesRole role = getKubernetesRole(asset);

    List<V1beta1PolicyRule> vulnerableRules = new ArrayList<>();
    for (V1beta1PolicyRule rule : role.getRules()) {
      if (hasPodResource(rule.getResources()) && hasCreateAccess(rule.getVerbs())) {
        vulnerableRules.add(rule);
      }
    }
    role.addVulnerableRules(vulnerableRules);
    return !vulnerableRules.isEmpty();
  }

  /**
   * Checks if the given list of verbs contains the "pods" resource.
   * @param resources list of resources to check
   * @return true if the given list contains pods, false otherwise
   */
  private boolean hasPodResource(List<String> resources) {
    return resources != null && resources.contains(PODS);
  }

  /**
   * Checks if the given list of verbs contains the "create" verb.
   * @param verbs list of verbs to check
   * @return true if the given list contains "create", false otherwise
   */
  private boolean hasCreateAccess(List<String> verbs) {
    return verbs != null && verbs.contains(CREATE);
  }

  @Override
  public String getExplanationText() {
    return "The ability to create pods in a namespace can provide a number of opportunities for "
        + "privilege escalation, such as assigning privileged service accounts to these pods or "
        + "mounting hostPaths with access to sensitive data (unless Pod Security Policies are "
        + "implemented to restrict this access. As such, access to create new pods should be "
        + "restricted to the smallest possible group of users. The ability to create pods in a "
        + "cluster opens up possibilities for privilege escalation and should be restricted, "
        + "where possible.";
  }

  @Override
  public List<String> getUsefulURLs() {
    return List.of("https://kubernetes.io/docs/admin/authorization/rbac");
  }

  @Override
  public String getRecommendationText() {
    return "Review the users who have create access to pod objects in the Kubernetes API. "
        + "Where possible, remove create access to pod objects in the cluster. "
        + "Care should be taken not to remove access to pods to system components "
        + "which require this for their operation.";
  }

  /**
   * Returns the asset as a {@link KubernetesRole}, or null if such conversion is not possible.
   * @param asset asset to convert
   * @return asset as a {@link KubernetesRole}
   */
  private KubernetesRole getKubernetesRole(Asset asset) {
    if (asset instanceof KubernetesRole) {
      return (KubernetesRole) asset;
    }
    return null;
  }

} public class EscalatingResourcesDependencyReport extends KubernetesDetectorConfig {

  @Override
  public String getDetectorName() {
    return "ESCALATING_RESOURCES_DEPENDENCY_REPORT";
  }

  @Override
  public ResourceType getAssetFilter() {
    return ResourceType.DEPENDENCY_REPORT;
  }

  @Override
  public Boolean isVulnerable(Asset asset) {
    Dependency dependency = getDependency(asset);
    List<V1beta1PolicyRule> escalatingRules = new ArrayList<>();
    if (dependency != null && dependency.getRole() != null && dependency.getRole().getRules() != null) {
      for (V1beta1PolicyRule rule : dependency.getRole().getRules()) {
        if (isEscalatingRule(rule)) {
          escalatingRules.add(rule);
        }
      }
      dependency.setEscalatingRules(escalatingRules);
    }
    return !escalatingRules.isEmpty();
  }

  /**
   * Checks if the given {@link V1beta1PolicyRule} is escalating.
   * @param rule rule to check if escalating
   * @return true if rule is escalating, false otherwise
   */
  private boolean isEscalatingRule(V1beta1PolicyRule rule) {
    List<String> apiGroupResources = DetectorUtil.getAPIGroupResources(rule);
    List<String> verbs = rule.getVerbs() == null ? Collections.emptyList() : rule.getVerbs();
    return hasEscalatingResources(apiGroupResources) ||
        hasEscalatingPrivilegesOnWrite(verbs, apiGroupResources) ||
        hasEscalatingSubresources(verbs, apiGroupResources);
  }

  /**
   * Checks if the given list of resources contains any escalating resources.
   * @param resources list of resources to check
   * @return true if the given list contains escalating resources, false otherwise
   */
  private boolean hasEscalatingResources(List<String> resources) {
    return !Collections.disjoint(resources, EscalatingResources.ESCALATING_RESOURCES);
  }

  /**
   * Checks if the given list of resources contains any escalating privileges on write.
   * @param verbs     list of verbs to check if escalating
   * @param resources list of resources to check if escalating
   * @return true if the given list contains escalating privileges on write, false otherwise
   */
  private boolean hasEscalatingPrivilegesOnWrite(List<String> verbs, List<String> resources) {
    if (Collections.disjoint(verbs, EscalatingResources.ESCALATING_VERBS)) {
      return false;
    }

    return !Collections.disjoint(resources,
        EscalatingResources.ESCALATING_RESOURCES_ON_WRITE);
  }

  /**
   * Checks if the given list of resources contains any escalating sub-resources.
   * @param verbs     list of verbs to check if escalating
   * @param resources list of resources to check if escalating
   * @return true if the given list contains escalating privileges on write, false otherwise
   */
  private boolean hasEscalatingSubresources(List<String> verbs, List<String> resources) {
    if (Collections.disjoint(verbs, EscalatingResources.ESCALATING_VERBS)) {
      return false;
    }

    for (String apiResource : resources) {
      int idx = apiResource.lastIndexOf("/");
      if (idx == -1) {
        continue;
      }
      String subResource = apiResource.substring(idx + 1);
      if (EscalatingResources.ESCALATING_SUBRESOURCES.contains(subResource)) {
        return true;
      }
    }
    return false;
  }

  @Override
  public String getExplanationText() {
    return "The dependency report searches for a path from a Node to a Service Account with "
        + "permissions on the Node and its resources (Pods, Containers, Volume Mounts). "
        + "There can be security implications if the Service Account is over permissive.";
  }

  @Override
  public List<String> getUsefulURLs() {
    return List
        .of("https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/");
  }

  @Override
  public String getRecommendationText() {
    return "Review on which resources the Service Account has permissions and remove the ones "
        + "that are not completely necessary.";
  }

  /**
   * Returns the asset as a {@link Dependency}, or null if such conversion is not possible.
   * @param asset asset to convert
   * @return asset as a {@link Dependency }
   */
  private Dependency getDependency(Asset asset) {
    if (asset instanceof Dependency) {
      return (Dependency) asset;
    }
    return null;
  }

} public class WildcardUsed extends KubernetesDetectorConfig {

  /**
   * Wildcard resource.
   */
  private final static String WILDCARD = "*";

  @Override
  public String getDetectorName() {
    return "WILDCARD_USED";
  }

  @Override
  public ResourceType[] getAssetFilters() {
    return new ResourceType[]{ResourceType.ROLE, ResourceType.CLUSTER_ROLE};
  }

  @Override
  public Boolean isVulnerable(Asset asset) {
    KubernetesRole role = getKubernetesRole(asset);

    List<V1beta1PolicyRule> vulnerableRules = new ArrayList<>();
    for (V1beta1PolicyRule rule : role.getRules()) {
      if (checkForWildcard(rule.getApiGroups())
          || checkForWildcard(rule.getResources())
          || checkForWildcard(rule.getVerbs())
          || checkForWildcard(rule.getNonResourceURLs())
          || checkForWildcard(rule.getResourceNames())) {
        vulnerableRules.add(rule);
      }
    }
    role.addVulnerableRules(vulnerableRules);
    return !vulnerableRules.isEmpty();
  }

  /**
   * Checks if the given list of verbs contains the wildcard (*) resource.
   * @param resources list of resources to check
   * @return true if the given list contains wildcards, false otherwise
   */
  private boolean checkForWildcard(List<String> resources) {
    return resources != null && resources.contains(WILDCARD);
  }

  @Override
  public List<String> getUsefulURLs() {
    return List.of("https://kubernetes.io/docs/admin/authorization/rbac");
  }

  @Override
  public String getExplanationText() {
    return "Kubernetes Roles and ClusterRoles provide access to resources based on sets of objects "
        + "and actions that can be taken on those objects. It is possible to set either of these to be "
        + "the wildcard \"*\" which matches all items. Use of wildcards is not optimal from a security "
        + "perspective as it may allow for inadvertent access to be granted when new resources are "
        + "added to the Kubernetes API either as CRDs or in later versions of the product. The "
        + "principle of least privilege recommends that users are provided only the access required "
        + "for their role and nothing more. The use of wildcard rights grants is likely to provide "
        + "excessive rights to the Kubernetes API.";
  }

  @Override
  public String getRecommendationText() {
    return "Where possible replace any use of wildcards in clusterroles and roles with specific objects or actions.";
  }

  /**
   * Returns the asset as a {@link KubernetesRole}, or null if such conversion is not possible.
   * @param asset asset to convert
   * @return asset as a {@link KubernetesRole}
   */
  private KubernetesRole getKubernetesRole(Asset asset) {
    if (asset instanceof KubernetesRole) {
      return (KubernetesRole) asset;
    }
    return null;
  }

}
import com.google.gke.auditor.configs.DetectorConfig;
import com.google.gke.auditor.configs.isolation.NodeAffinity;
import com.google.gke.auditor.configs.isolation.NodeSelector;
import com.google.gke.auditor.configs.isolation.NodeTaint;
import com.google.gke.auditor.configs.psp.AddedCapabilities;
import com.google.gke.auditor.configs.psp.AllowPrivilegeEscalation;
import com.google.gke.auditor.configs.psp.CapabilitiesAssigned;
import com.google.gke.auditor.configs.psp.NetRawCapabilities;
import com.google.gke.auditor.configs.psp.PrivilegedContainers;
import com.google.gke.auditor.configs.psp.RootContainersAdmission;
import com.google.gke.auditor.configs.psp.SharingHostIPC;
import com.google.gke.auditor.configs.psp.SharingHostNetworkNamespace;
import com.google.gke.auditor.configs.psp.SharingHostPIDNamespace;
import com.google.gke.auditor.configs.rbac.AccessToSecrets;
import com.google.gke.auditor.configs.rbac.AutomountServiceAccountTokenEnabled;
import com.google.gke.auditor.configs.rbac.ClusterAdminRoleUsed;
import com.google.gke.auditor.configs.rbac.CreatePodsAllowed;
import com.google.gke.auditor.configs.rbac.EscalatingResourcesDependencyReport;
import com.google.gke.auditor.configs.rbac.WildcardUsed;
import java.util.Arrays;
import java.util.List;
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.gke.auditor.system;


/**
 * Utility class for storing grouped collections of implemented detectors.
 */
public class Detectors {

  /**
   * All RBAC detectors supported by the auditor.
   */
  public static final List<DetectorConfig> RBAC_DETECTORS = Arrays.asList(
      new ClusterAdminRoleUsed(),
      new AccessToSecrets(),
      new WildcardUsed(),
      new CreatePodsAllowed(),
      new AutomountServiceAccountTokenEnabled(),
      new EscalatingResourcesDependencyReport()
  );

  /**
   * All node isolation detectors supported by the auditor.
   */
  public static final List<DetectorConfig> ISOLATION_DETECTORS = Arrays.asList(
      new NodeSelector(),
      new NodeAffinity(),
      new NodeTaint()
  );

  /**
   * All PSP detectors supported by the auditor.
   */
  public static final List<DetectorConfig> PSP_DETECTORS = Arrays.asList(
      new PrivilegedContainers(),
      new SharingHostPIDNamespace(),
      new SharingHostIPC(),
      new SharingHostNetworkNamespace(),
      new AllowPrivilegeEscalation(),
      new RootContainersAdmission(),
      new NetRawCapabilities(),
      new AddedCapabilities(),