Repository: linkerd/linkerd2
Pull request #8421 (2022-05-04 03:19:54)

build(deps): bump serde_yaml from 0.8.23 to 0.8.24
Bumps [serde_yaml](https://github.com/dtolnay/serde-yaml) from 0.8.23 to 0.8.24.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/dtolnay/serde-yaml/releases">serde_yaml's releases</a>.</em></p>
<blockquote>
<h2>0.8.24</h2>
<ul>
<li>Work around <code>indexmap</code>/<code>autocfg</code> not always properly detecting whether a <code>std</code> sysroot crate is available (<a href="https://github-redirect.dependabot.com/dtolnay/serde-yaml/issues/243">#243</a>, thanks <a href="https://github.com/cuviper"><code>@​cuviper</code></a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/dtolnay/serde-yaml/commit/915d0134019cca544db86e8a4e8f13bdeacb80e8"><code>915d013</code></a> Release 0.8.24</li>
<li><a href="https://github.com/dtolnay/serde-yaml/commit/74211ec8c7d147383694e51944233365e6106ba2"><code>74211ec</code></a> Merge pull request <a href="https://github-redirect.dependabot.com/dtolnay/serde-yaml/issues/243">#243</a> from cuviper/indexmap-std</li>
<li><a href="https://github.com/dtolnay/serde-yaml/commit/80bd0a494e05e9c0b48bc1953099993ae48ea80e"><code>80bd0a4</code></a> Explicitly enable indexmap/std</li>
<li><a href="https://github.com/dtolnay/serde-yaml/commit/748976003e153969fd8a209f77ba7f2d907eebdd"><code>7489760</code></a> Resolve type_repetition_in_bounds clippy lint</li>
<li><a href="https://github.com/dtolnay/serde-yaml/commit/85baedfebb4f0d95eff57697857b5350f0fe9f1d"><code>85baedf</code></a> Add a miri test job in CI</li>
<li><a href="https://github.com/dtolnay/serde-yaml/commit/8040788ac14750ab9f4e958173ccb361c385ca71"><code>8040788</code></a> Drop unneeded quoting from env variable in workflows yaml</li>
<li><a href="https://github.com/dtolnay/serde-yaml/commit/13306e044de9fe2587420dc510273be59ea80169"><code>13306e0</code></a> Update workflows to actions/checkout@v3</li>
<li><a href="https://github.com/dtolnay/serde-yaml/commit/8155852f3b9bd17526cbd7422c0faac3d4a52a28"><code>8155852</code></a> Merge pull request <a href="https://github-redirect.dependabot.com/dtolnay/serde-yaml/issues/239">#239</a> from uta8a/fix-readme-example</li>
<li><a href="https://github.com/dtolnay/serde-yaml/commit/45affad0cef7061ac57bc751acc2814bd8fab15d"><code>45affad</code></a> Fix README example</li>
<li><a href="https://github.com/dtolnay/serde-yaml/commit/a14e026560881764e828681ec6397e8bcde1e77a"><code>a14e026</code></a> Merge pull request <a href="https://github-redirect.dependabot.com/dtolnay/serde-yaml/issues/237">#237</a> from dtolnay/description</li>
<li>Additional commits viewable in <a href="https://github.com/dtolnay/serde-yaml/compare/0.8.23...0.8.24">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=serde_yaml&package-manager=cargo&previous-version=0.8.23&new-version=0.8.24)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 74638807e..9187b9a0b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1683,9 +1683,9 @@ dependencies = [
 
 [[package]]
 name = "serde_yaml"
-version = "0.8.23"
+version = "0.8.24"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4a521f2940385c165a24ee286aa8599633d162077a54bdcae2a6fd5a7bfa7a0"
+checksum = "707d15895415db6628332b737c838b88c598522e4dc70647e59b72312924aebc"
 dependencies = [
  "indexmap",
  "ryu",
diff --git a/charts/linkerd2-cni/templates/cni-plugin.yaml b/charts/linkerd2-cni/templates/cni-plugin.yaml
index 17756fd30..8d8d62c21 100644
--- a/charts/linkerd2-cni/templates/cni-plugin.yaml
+++ b/charts/linkerd2-cni/templates/cni-plugin.yaml
@@ -241,7 +241,7 @@ spec:
               command:
               - /bin/sh
               - -c
-              - kill -15 1
+              - kill -15 1; sleep 15s
         volumeMounts:
         {{- if ne .Values.destCNIBinDir .Values.destCNINetDir }}
         - mountPath: /host{{.Values.destCNIBinDir}}
diff --git a/cni-plugin/Dockerfile b/cni-plugin/Dockerfile
index 8bb048ef7..4f01531c4 100644
--- a/cni-plugin/Dockerfile
+++ b/cni-plugin/Dockerfile
@@ -22,6 +22,8 @@ FROM debian:bullseye-slim
 WORKDIR /linkerd
 RUN apt-get update && apt-get install -y --no-install-recommends \
     iptables \
+    inotify-tools \
+    procps \
     jq && \
     rm -rf /var/lib/apt/lists/*
 
diff --git a/cni-plugin/deployment/scripts/install-cni.sh b/cni-plugin/deployment/scripts/install-cni.sh
index d89f9fd68..a3a06a4a1 100755
--- a/cni-plugin/deployment/scripts/install-cni.sh
+++ b/cni-plugin/deployment/scripts/install-cni.sh
@@ -1,4 +1,4 @@
-#!/usr/bin/env sh
+#!/usr/bin/env bash
 # Copyright (c) 2018 Tigera, Inc. All rights reserved.
 # Copyright 2018 Istio Authors
 # Modifications copyright (c) Linkerd authors
@@ -51,98 +51,132 @@ CONTAINER_MOUNT_PREFIX=${CONTAINER_MOUNT_PREFIX:-/host}
 # overridden by setting CONTAINER_CNI_BIN_DIR. The binary in this directory
 # will be copied over to the host DEST_CNI_BIN_DIR through the mount point.
 CONTAINER_CNI_BIN_DIR=${CONTAINER_CNI_BIN_DIR:-/opt/cni/bin}
-
-# Default to the first file following a find | sort since the Kubernetes CNI runtime is going
-# to look for the lexicographically first file. If the directory is empty, then use a name
-# of our choosing.
-CNI_CONF_PATH=${CNI_CONF_PATH:-$(find "${CONTAINER_MOUNT_PREFIX}${DEST_CNI_NET_DIR}" -maxdepth 1 -type f \( -iname '*conflist' -o -iname '*conf' \) | sort | head -n 1)}
-CNI_CONF_PATH=${CNI_CONF_PATH:-"${CONTAINER_MOUNT_PREFIX}${DEST_CNI_NET_DIR}/01-linkerd-cni.conf"}
-
+# Directory path where CNI configuration should live on the host
+HOST_CNI_NET="${CONTAINER_MOUNT_PREFIX}${DEST_CNI_NET_DIR}"
+# Default path for when linkerd runs as a standalone CNI plugin
+DEFAULT_CNI_CONF_PATH="${HOST_CNI_NET}/01-linkerd-cni.conf"
 KUBECONFIG_FILE_NAME=${KUBECONFIG_FILE_NAME:-ZZZ-linkerd-cni-kubeconfig}
 
+############################
+### Function definitions ###
+############################
+
+# Cleanup will remove any installed configuration from the host If there are any
+# *conflist files, then linkerd-cni configuration parameters will be removed
+# from them; otherwise, if linkerd-cni is the only plugin, the configuration
+# file will be removed.
 cleanup() {
+  # First, kill 'inotifywait' so we don't process any DELETE/CREATE events
+  if [ -n "$(pgrep inotifywait)" ]; then
+    echo "Sending SIGKILL to inotifywait"
+    kill -s KILL "$(pgrep inotifywait)"
+  fi
+
   echo 'Removing linkerd-cni artifacts.'
 
-  if [ -e "${CNI_CONF_PATH}" ]; then
-    echo "Removing linkerd-cni config: ${CNI_CONF_PATH}"
-    CNI_CONF_DATA=$(jq 'del( .plugins[]? | select( .type == "linkerd-cni" ))' "${CNI_CONF_PATH}")
-    echo "${CNI_CONF_DATA}" > "${CNI_CONF_PATH}"
+  # Find all conflist files and print them out using a NULL separator instead of
+  # writing each file in a new line. We will subsequently read each string and
+  # attempt to rm linkerd config from it using jq helper.
+  local cni_data=""
+  find "${HOST_CNI_NET}" -maxdepth 1 -type f \( -iname '*conflist' \) -print0 |
+    while read -r -d $'\0' file; do
+      echo "Removing linkerd-cni config from $file"
+      cni_data=$(jq 'del( .plugins[]? | select( .type == "linkerd-cni" ))' "$file")
+      # TODO (matei): we should write this out to a temp file and then do a `mv`
+      # to be atomic. 
+      echo "$cni_data" > "$file"
+    done
 
-    if [ "${CNI_CONF_PATH}" = "${CONTAINER_MOUNT_PREFIX}${DEST_CNI_NET_DIR}/01-linkerd-cni.conf" ]; then
-      rm -f "${CNI_CONF_PATH}"
-    fi
+  # Check whether configuration file has been created by our own cni plugin
+  # and if so, rm it.
+  if [ -e "${DEFAULT_CNI_CONF_PATH}" ]; then
+    echo "Cleaning up ${DEFAULT_CNI_CONF_PATH}"
+    rm -f "${DEFAULT_CNI_CONF_PATH}"
   fi
-  if [ -e "${CONTAINER_MOUNT_PREFIX}${DEST_CNI_NET_DIR}/${KUBECONFIG_FILE_NAME}" ]; then
-    echo "Removing linkerd-cni kubeconfig: ${CONTAINER_MOUNT_PREFIX}${DEST_CNI_NET_DIR}/${KUBECONFIG_FILE_NAME}"
-    rm -f "${CONTAINER_MOUNT_PREFIX}${DEST_CNI_NET_DIR}/${KUBECONFIG_FILE_NAME}"
+
+  # Remove binary and kubeconfig file
+  if [ -e "${HOST_CNI_NET}/${KUBECONFIG_FILE_NAME}" ]; then
+    echo "Removing linkerd-cni kubeconfig: ${HOST_CNI_NET}/${KUBECONFIG_FILE_NAME}"
+    rm -f "${HOST_CNI_NET}/${KUBECONFIG_FILE_NAME}"
   fi
   if [ -e "${CONTAINER_MOUNT_PREFIX}${DEST_CNI_BIN_DIR}"/linkerd-cni ]; then
     echo "Removing linkerd-cni binary: ${CONTAINER_MOUNT_PREFIX}${DEST_CNI_BIN_DIR}/linkerd-cni"
     rm -f "${CONTAINER_MOUNT_PREFIX}${DEST_CNI_BIN_DIR}/linkerd-cni"
   fi
+
   echo 'Exiting.'
+  exit 0
 }
 
 # Capture the usual signals and exit from the script
-trap cleanup EXIT
 trap 'echo "SIGINT received, simply exiting..."; cleanup' INT
 trap 'echo "SIGTERM received, simply exiting..."; cleanup' TERM
 trap 'echo "SIGHUP received, simply exiting..."; cleanup' HUP
 
-# Place the new binaries if the mounted directory is writeable.
-dir="${CONTAINER_MOUNT_PREFIX}${DEST_CNI_BIN_DIR}"
-if [ ! -w "${dir}" ]; then
-  exit_with_error "${dir} is non-writeable, failure"
-fi
-for path in "${CONTAINER_CNI_BIN_DIR}"/*; do
-  cp "${path}" "${dir}"/ || exit_with_error "Failed to copy ${path} to ${dir}."
-done
+# Install CNI bin will copy the linkerd-cni binary on the host's filesystem
+install_cni_bin() {
+  # Place the new binaries if the mounted directory is writeable.
+  dir="${CONTAINER_MOUNT_PREFIX}${DEST_CNI_BIN_DIR}"
+  if [ ! -w "${dir}" ]; then
+    exit_with_error "${dir} is non-writeable, failure"
+  fi
+  for path in "${CONTAINER_CNI_BIN_DIR}"/*; do
+    cp "${path}" "${dir}"/ || exit_with_error "Failed to copy ${path} to ${dir}."
+  done
+
+  echo "Wrote linkerd CNI binaries to ${dir}"
+}
+
+create_cni_conf() {
+  # Create temp configuration and kubeconfig files
+  #
+  TMP_CONF='/tmp/linkerd-cni.conf.default'
+  # If specified, overwrite the network configuration file.
+  CNI_NETWORK_CONFIG_FILE="${CNI_NETWORK_CONFIG_FILE:-}"
+  CNI_NETWORK_CONFIG="${CNI_NETWORK_CONFIG:-}"
 
-echo "Wrote linkerd CNI binaries to ${dir}"
-
-TMP_CONF='/tmp/linkerd-cni.conf.default'
-# If specified, overwrite the network configuration file.
-: "${CNI_NETWORK_CONFIG_FILE:=}"
-: "${CNI_NETWORK_CONFIG:=}"
-if [ -e "${CNI_NETWORK_CONFIG_FILE}" ]; then
-  echo "Using CNI config template from ${CNI_NETWORK_CONFIG_FILE}."
-  cp "${CNI_NETWORK_CONFIG_FILE}" "${TMP_CONF}"
-elif [ "${CNI_NETWORK_CONFIG}" != "" ]; then
-  echo 'Using CNI config template from CNI_NETWORK_CONFIG environment variable.'
-  cat >"${TMP_CONF}" <<EOF
+  # If the CNI Network Config has been overwritten, then use template from file
+  if [ -e "${CNI_NETWORK_CONFIG_FILE}" ]; then
+    echo "Using CNI config template from ${CNI_NETWORK_CONFIG_FILE}."
+    cp "${CNI_NETWORK_CONFIG_FILE}" "${TMP_CONF}"
+  elif [ "${CNI_NETWORK_CONFIG}" != "" ]; then
+    echo 'Using CNI config template from CNI_NETWORK_CONFIG environment variable.'
+    cat >"${TMP_CONF}" <<EOF
 ${CNI_NETWORK_CONFIG}
 EOF
-fi
-
-SERVICE_ACCOUNT_PATH=/var/run/secrets/kubernetes.io/serviceaccount
-KUBE_CA_FILE=${KUBE_CA_FILE:-${SERVICE_ACCOUNT_PATH}/ca.crt}
-SKIP_TLS_VERIFY=${SKIP_TLS_VERIFY:-false}
-# Pull out service account token.
-SERVICEACCOUNT_TOKEN=$(cat ${SERVICE_ACCOUNT_PATH}/token)
-
-# Check if we're running as a k8s pod.
-if [ -f "${SERVICE_ACCOUNT_PATH}/token" ]; then
-  # We're running as a k8d pod - expect some variables.
-  if [ -z "${KUBERNETES_SERVICE_HOST}" ]; then
-    echo 'KUBERNETES_SERVICE_HOST not set'; exit 1;
-  fi
-  if [ -z "${KUBERNETES_SERVICE_PORT}" ]; then
-    echo 'KUBERNETES_SERVICE_PORT not set'; exit 1;
   fi
 
-  if [ "${SKIP_TLS_VERIFY}" = 'true' ]; then
-    TLS_CFG='insecure-skip-tls-verify: true'
-  elif [ -f "${KUBE_CA_FILE}" ]; then
-    TLS_CFG="certificate-authority-data: $(base64 "${KUBE_CA_FILE}" | tr -d '\n')"
-  fi
+  SERVICE_ACCOUNT_PATH=/var/run/secrets/kubernetes.io/serviceaccount
+  KUBE_CA_FILE=${KUBE_CA_FILE:-${SERVICE_ACCOUNT_PATH}/ca.crt}
+  SKIP_TLS_VERIFY=${SKIP_TLS_VERIFY:-false}
+  # Pull out service account token.
+  SERVICEACCOUNT_TOKEN=$(cat ${SERVICE_ACCOUNT_PATH}/token)
 
-  # Write a kubeconfig file for the CNI plugin. Do this
-  # to skip TLS verification for now. We should eventually support
-  # writing more complete kubeconfig files. This is only used
-  # if the provided CNI network config references it.
-  touch "${CONTAINER_MOUNT_PREFIX}${DEST_CNI_NET_DIR}/${KUBECONFIG_FILE_NAME}"
-  chmod "${KUBECONFIG_MODE:-600}" "${CONTAINER_MOUNT_PREFIX}${DEST_CNI_NET_DIR}/${KUBECONFIG_FILE_NAME}"
-  cat > "${CONTAINER_MOUNT_PREFIX}${DEST_CNI_NET_DIR}/${KUBECONFIG_FILE_NAME}" <<EOF
+  # Check if we're running as a k8s pod.
+  # The check will assert whether token exists and is a regular file
+  if [ -f "${SERVICE_ACCOUNT_PATH}/token" ]; then
+    # We're running as a k8d pod - expect some variables.
+    # If the variables are null, exit
+    if [ -z "${KUBERNETES_SERVICE_HOST}" ]; then
+      echo 'KUBERNETES_SERVICE_HOST not set'; exit 1;
+    fi
+    if [ -z "${KUBERNETES_SERVICE_PORT}" ]; then
+      echo 'KUBERNETES_SERVICE_PORT not set'; exit 1;
+    fi
+
+    if [ "${SKIP_TLS_VERIFY}" = 'true' ]; then
+      TLS_CFG='insecure-skip-tls-verify: true'
+    elif [ -f "${KUBE_CA_FILE}" ]; then
+      TLS_CFG="certificate-authority-data: $(base64 "${KUBE_CA_FILE}" | tr -d '\n')"
+    fi
+
+    # Write a kubeconfig file for the CNI plugin. Do this
+    # to skip TLS verification for now. We should eventually support
+    # writing more complete kubeconfig files. This is only used
+    # if the provided CNI network config references it.
+    touch "${CONTAINER_MOUNT_PREFIX}${DEST_CNI_NET_DIR}/${KUBECONFIG_FILE_NAME}"
+    chmod "${KUBECONFIG_MODE:-600}" "${CONTAINER_MOUNT_PREFIX}${DEST_CNI_NET_DIR}/${KUBECONFIG_FILE_NAME}"
+    cat > "${CONTAINER_MOUNT_PREFIX}${DEST_CNI_NET_DIR}/${KUBECONFIG_FILE_NAME}" <<EOF
 # Kubeconfig file for linkerd CNI plugin.
 apiVersion: v1
 kind: Config
@@ -163,58 +197,163 @@ contexts:
 current-context: linkerd-cni-context
 EOF
 
-fi
+  fi
 
-# Insert any of the supported "auto" parameters.
-grep '__KUBERNETES_SERVICE_HOST__' ${TMP_CONF} && sed -i s/__KUBERNETES_SERVICE_HOST__/"${KUBERNETES_SERVICE_HOST}"/g ${TMP_CONF}
-grep '__KUBERNETES_SERVICE_PORT__' ${TMP_CONF} && sed -i s/__KUBERNETES_SERVICE_PORT__/"${KUBERNETES_SERVICE_PORT}"/g ${TMP_CONF}
-sed -i s/__KUBERNETES_NODE_NAME__/"${KUBERNETES_NODE_NAME:-$(hostname)}"/g ${TMP_CONF}
-sed -i s/__KUBECONFIG_FILENAME__/"${KUBECONFIG_FILE_NAME}"/g ${TMP_CONF}
-sed -i s/__CNI_MTU__/"${CNI_MTU:-1500}"/g ${TMP_CONF}
+  # Insert any of the supported "auto" parameters.
+  grep '__KUBERNETES_SERVICE_HOST__' ${TMP_CONF} && sed -i s/__KUBERNETES_SERVICE_HOST__/"${KUBERNETES_SERVICE_HOST}"/g ${TMP_CONF}
+  grep '__KUBERNETES_SERVICE_PORT__' ${TMP_CONF} && sed -i s/__KUBERNETES_SERVICE_PORT__/"${KUBERNETES_SERVICE_PORT}"/g ${TMP_CONF}
+  # Check in container
+  sed -i s/__KUBERNETES_NODE_NAME__/"${KUBERNETES_NODE_NAME:-$(hostname)}"/g ${TMP_CONF}
+  sed -i s/__KUBECONFIG_FILENAME__/"${KUBECONFIG_FILE_NAME}"/g ${TMP_CONF}
+  sed -i s/__CNI_MTU__/"${CNI_MTU:-1500}"/g ${TMP_CONF}
 
-# Use alternative command character "~", since these include a "/".
-sed -i s~__KUBECONFIG_FILEPATH__~"${DEST_CNI_NET_DIR}/${KUBECONFIG_FILE_NAME}"~g ${TMP_CONF}
+  # Use alternative command character "~", since these include a "/".
+  sed -i s~__KUBECONFIG_FILEPATH__~"${DEST_CNI_NET_DIR}/${KUBECONFIG_FILE_NAME}"~g ${TMP_CONF}
 
-CNI_OLD_CONF_PATH="${CNI_OLD_CONF_PATH:-${CNI_CONF_PATH}}"
 
-# Log the config file before inserting service account token.
-# This way auth token is not visible in the logs.
-echo "CNI config: $(cat ${TMP_CONF})"
+  # Log the config file before inserting service account token.
+  # This way auth token is not visible in the logs.
+  echo "CNI config: $(cat ${TMP_CONF})"
 
-sed -i s/__SERVICEACCOUNT_TOKEN__/"${SERVICEACCOUNT_TOKEN:-}"/g ${TMP_CONF}
+  sed -i s/__SERVICEACCOUNT_TOKEN__/"${SERVICEACCOUNT_TOKEN:-}"/g ${TMP_CONF}
+}
 
-CNI_CONF_FILE="${CNI_CONF_PATH}"
-if [ -e "${CNI_CONF_FILE}" ]; then
-  # Add the linkerd-cni plugin to the existing list
-  CNI_TMP_CONF_DATA=$(cat "${TMP_CONF}")
-  CNI_CONF_DATA=$(jq --argjson CNI_TMP_CONF_DATA "$CNI_TMP_CONF_DATA" -f /linkerd/filter.jq "${CNI_CONF_FILE}")
-  echo "${CNI_CONF_DATA}" > ${TMP_CONF}
-fi
+install_cni_conf() {
+  local cni_conf_path="$1"
+ 
+  create_cni_conf
+  local tmp_data=""
+  local conf_data=""
+  if [ -e "${cni_conf_path}" ]; then
+   # Add the linkerd-cni plugin to the existing list
+   tmp_data=$(cat "${TMP_CONF}")
+   conf_data=$(jq --argjson CNI_TMP_CONF_DATA "${tmp_data}" -f /linkerd/filter.jq "${cni_conf_path}")
+   echo "${conf_data}" > ${TMP_CONF}
+  fi
 
-# If the old config filename ends with .conf, rename it to .conflist, because it has changed to be a list
-filename=${CNI_CONF_PATH##*/}
-extension="${filename##*.}"
-if [ "${filename}" != '01-linkerd-cni.conf' ] && [ "${extension}" = 'conf' ]; then
-  echo "Renaming ${CNI_CONF_PATH} extension to .conflist"
-  CNI_CONF_PATH="${CNI_CONF_PATH}list"
-fi
+  # If the old config filename ends with .conf, rename it to .conflist, because it has changed to be a list
+  filename=${cni_conf_path##*/}
+  extension="${filename##*.}"
+  if [ "${filename}" != '01-linkerd-cni.conf' ] && [ "${extension}" = 'conf' ]; then
+   echo "Renaming ${cni_conf_path} extension to .conflist"
+   cni_conf_path="${cni_conf_path}list"
+  fi
 
-# Delete old CNI config files for upgrades.
-if [ "${CNI_CONF_PATH}" != "${CNI_OLD_CONF_PATH}" ]; then
-  echo "Removing CNI_OLD_CONF_PATH: ${CNI_OLD_CONF_PATH}"
-  rm -f "${CNI_OLD_CONF_PATH}"
-fi
+  if [ -e "${DEFAULT_CNI_CONF_PATH}" ] && [ "$cni_conf_path" != "${DEFAULT_CNI_CONF_PATH}" ]; then
+   echo "Removing Linkerd's configuration file: ${DEFAULT_CNI_CONF_PATH}"
+   rm -f "${DEFAULT_CNI_CONF_PATH}"
+  fi
+
+  # Move the temporary CNI config into place.
+  mv "${TMP_CONF}" "${cni_conf_path}" || exit_with_error 'Failed to mv files.'
+
+  echo "Created CNI config ${cni_conf_path}"
+}
+
+# Sync() is responsible for reacting to file system changes. It is used in
+# conjunction with inotify events; sync() is called with the name of the file that
+# has changed, the event type (which can be either 'CREATE' or 'DELETE'), and
+# the previously observed SHA of the configuration file.
+#
+# Based on the changed file and event type, sync() might re-install the CNI
+# plugin's configuration file.
+sync() {
+  local filename="$1"
+  local ev="$2"
+  local filepath="${HOST_CNI_NET}/$filename"
+
+  local prev_sha="$3"
+
+  local config_file_count
+  local new_sha
+  if [ "$ev" = "DELETE" ]; then
+    # When the event type is 'DELETE', we check to see if there are any `*conf` or `*conflist`
+    # files on the host's filesystem. If none are present, we install in
+    # 'interface' mode, using our own CNI config file.
+    config_file_count=$(find "${HOST_CNI_NET}" -maxdepth 1 -type f \( -iname '*conflist' -o -iname '*conf' \) | sort | wc -l)
+    if [ "$config_file_count" -eq 0 ]; then
+      echo "No active CNI configuration file found after $ev event; re-installing in \"interface\" mode"
+      install_cni_conf "${DEFAULT_CNI_CONF_PATH}"
+    fi
+  elif [ "$ev" = "CREATE" ]; then
+    # When the event type is 'CREATE', we check the previously observed SHA (updated
+    # with each file watch) and compare it against the new file's SHA. If they
+    # differ, it means something has changed.
+    new_sha=$(sha256sum "${filepath}" | awk '{print $1}')
+    if [ "$new_sha" != "$prev_sha" ]; then
+      # Create but don't rm old one since we don't know if this will be configured
+      # to run as _the_ cni plugin.
+      echo "New file [$filename] detected; re-installing in \"chained\" mode"
+      install_cni_conf "$filepath"
+    else
+      # If the SHA hasn't changed or we get an unrecognised event, ignore it.
+      # When the SHA is the same, we can get into infinite loops whereby a file has
+      # been created and after re-install the watch keeps triggering CREATE events
+      # that never end.
+      echo "Ignoring event: $ev $filepath; no real changes detected"
+    fi
+  fi
+}
+
+# Monitor will start a watch on host's CNI config directory. Although files are
+# mostly `mv'd`, because they are moved from the container's filesystem, the
+# events logged will typically be a DELETED followed by a CREATE. When we are on
+# the same system partition, `mv` simply renames, however, that won't be the
+# case so we don't watch any "moved_to" or "moved_from" events.
+monitor() {
+  inotifywait -m "${HOST_CNI_NET}" -e create,delete |
+    while read -r directory action filename; do
+      if [[ "$filename" =~ .*.(conflist|conf)$ ]]; then 
+        echo "Detected change in $directory: $action $filename"
+        sync "$filename" "$action" "$cni_conf_sha"
+        # When file exists (i.e we didn't deal with a DELETE ev)
+        # then calculate its sha to be used the next turn.
+        if [ -e "$directory/$filename" ]; then
+          cni_conf_sha="$(sha256sum "$directory/$filename" | awk '{print $1}')"
+        fi
+      fi
+    done
+}
 
-# Move the temporary CNI config into place.
-mv "${TMP_CONF}" "${CNI_CONF_PATH}" || exit_with_error 'Failed to mv files.'
+################################
+### CNI Plugin Install Logic ###
+################################
+
+install_cni_bin
+
+# Install CNI configuration. If we have an existing CNI configuration file (*.conflist or *.conf) that is not linkerd's,
+# then append our configuration to that file. Otherwise, if no CNI config files
+# are present, install our stand-alone config file.
+config_file_count=$(find "${HOST_CNI_NET}" -maxdepth 1 -type f \( -iname '*conflist' -o -iname '*conf' \) | grep -v "linkerd" | sort | wc -l)
+if [ "$config_file_count" -eq 0 ]; then
+  echo "No active CNI configuration files found; installing in \"interface\" mode in ${DEFAULT_CNI_CONF_PATH}"
+  install_cni_conf "${DEFAULT_CNI_CONF_PATH}"
+else
+  find "${HOST_CNI_NET}" -maxdepth 1 -type f \( -iname '*conflist' -o -iname '*conf' \) -print0 |
+    while read -r -d $'\0' file; do
+      echo "Installing CNI configuration in \"chained\" mode for $file"
+      install_cni_conf "$file"
+    done
+fi
 
-echo "Created CNI config ${CNI_CONF_PATH}"
+# Compute SHA for first config file found; this will be updated after every iteration.
+# First config file is likely to be chosen as the de facto CNI config by the
+# host.
+cni_conf_sha="$(sha256sum "$(find "${HOST_CNI_NET}" -maxdepth 1 -type f \( -iname '*conflist' -o -iname '*conf' \) | sort | head -n 1)" | awk '{print $1}')"
 
-# Unless told otherwise, sleep forever.
-# This prevents Kubernetes from restarting the pod repeatedly.
-should_sleep=${SLEEP:-"true"}
-echo "Done configuring CNI. Sleep=$should_sleep"
-while [ "${should_sleep}" = 'true'  ]; do
+# Watch in bg so we can receive interrupt signals through 'trap'. From 'man
+# bash': 
+# "If  bash  is  waiting  for a command to complete and receives a signal
+# for which a trap has been set, the trap will not be executed until the command
+# completes. When bash is waiting for an asynchronous command via the wait
+# builtin, the reception of a signal for which a trap has been set will cause
+# the wait builtin to return immediately with an exit status greater than 128,
+# immediately after which the trap is executed."
+monitor &
+while true; do
+  # sleep so script never finishes
+  # we start sleep in bg so we can trap signals
   sleep infinity &
+  # block
   wait $!
 done


Test patch:
diff --git a/cli/cmd/testdata/install-cni-plugin_default.golden b/cli/cmd/testdata/install-cni-plugin_default.golden
index 5048ca232..046dc01e7 100644
--- a/cli/cmd/testdata/install-cni-plugin_default.golden
+++ b/cli/cmd/testdata/install-cni-plugin_default.golden
@@ -140,7 +140,7 @@ spec:
               command:
               - /bin/sh
               - -c
-              - kill -15 1
+              - kill -15 1; sleep 15s
         volumeMounts:
         - mountPath: /host/opt/cni/bin
           name: cni-bin-dir
diff --git a/cli/cmd/testdata/install-cni-plugin_fully_configured.golden b/cli/cmd/testdata/install-cni-plugin_fully_configured.golden
index eae7c4c94..c42322e89 100644
--- a/cli/cmd/testdata/install-cni-plugin_fully_configured.golden
+++ b/cli/cmd/testdata/install-cni-plugin_fully_configured.golden
@@ -141,7 +141,7 @@ spec:
               command:
               - /bin/sh
               - -c
-              - kill -15 1
+              - kill -15 1; sleep 15s
         volumeMounts:
         - mountPath: /host/opt/my-cni/bin
           name: cni-bin-dir
diff --git a/cli/cmd/testdata/install-cni-plugin_fully_configured_equal_dsts.golden b/cli/cmd/testdata/install-cni-plugin_fully_configured_equal_dsts.golden
index 7b6cdafb2..eb6f49718 100644
--- a/cli/cmd/testdata/install-cni-plugin_fully_configured_equal_dsts.golden
+++ b/cli/cmd/testdata/install-cni-plugin_fully_configured_equal_dsts.golden
@@ -141,7 +141,7 @@ spec:
               command:
               - /bin/sh
               - -c
-              - kill -15 1
+              - kill -15 1; sleep 15s
         volumeMounts:
         - mountPath: /host/etc/kubernetes/cni/net.d
           name: cni-net-dir
diff --git a/cli/cmd/testdata/install-cni-plugin_fully_configured_no_namespace.golden b/cli/cmd/testdata/install-cni-plugin_fully_configured_no_namespace.golden
index eae7c4c94..c42322e89 100644
--- a/cli/cmd/testdata/install-cni-plugin_fully_configured_no_namespace.golden
+++ b/cli/cmd/testdata/install-cni-plugin_fully_configured_no_namespace.golden
@@ -141,7 +141,7 @@ spec:
               command:
               - /bin/sh
               - -c
-              - kill -15 1
+              - kill -15 1; sleep 15s
         volumeMounts:
         - mountPath: /host/opt/my-cni/bin
           name: cni-bin-dir
diff --git a/cli/cmd/testdata/install-cni-plugin_skip_ports.golden b/cli/cmd/testdata/install-cni-plugin_skip_ports.golden
index 550f9b801..0f0e4dc70 100644
--- a/cli/cmd/testdata/install-cni-plugin_skip_ports.golden
+++ b/cli/cmd/testdata/install-cni-plugin_skip_ports.golden
@@ -141,7 +141,7 @@ spec:
               command:
               - /bin/sh
               - -c
-              - kill -15 1
+              - kill -15 1; sleep 15s
         volumeMounts:
         - mountPath: /host/opt/cni/bin
           name: cni-bin-dir
diff --git a/cli/cmd/testdata/install_cni_helm_default_output.golden b/cli/cmd/testdata/install_cni_helm_default_output.golden
index 7050f90db..e6fb19dd8 100644
--- a/cli/cmd/testdata/install_cni_helm_default_output.golden
+++ b/cli/cmd/testdata/install_cni_helm_default_output.golden
@@ -134,7 +134,7 @@ spec:
               command:
               - /bin/sh
               - -c
-              - kill -15 1
+              - kill -15 1; sleep 15s
         volumeMounts:
         - mountPath: /host/opt/cni/bin
           name: cni-bin-dir
diff --git a/cli/cmd/testdata/install_cni_helm_override_output.golden b/cli/cmd/testdata/install_cni_helm_override_output.golden
index f369b7185..c642058fc 100644
--- a/cli/cmd/testdata/install_cni_helm_override_output.golden
+++ b/cli/cmd/testdata/install_cni_helm_override_output.golden
@@ -135,7 +135,7 @@ spec:
               command:
               - /bin/sh
               - -c
-              - kill -15 1
+              - kill -15 1; sleep 15s
         volumeMounts:
         - mountPath: /host/opt/cni/bin-test
           name: cni-bin-dir