istio: Sidecar injection fails when using PSP with allowPrivilegeEscalation=false and istio-init container privleged=false

Describe the bug Failed to deploy automatically injected sidecar container, with the istio-sidecar-injector configmap is configured to use securityContext: privileged: false for proxy-init, when using a PodSecurityPolicy with privilegeEscalationAllowed: false and CAP_NET_ADMIN enabled, yet it works for manual sidecar injection using the same PSPs/ClusterRole/ClusterRoleBinding.

  • With automatic sidecar injection enabled kubectl label namespace default istio-injection=enabled kubectl get deployment httpbin
      message: 'pods "httpbin-7cf66f94c4-hzhhd" is forbidden: unable to validate against
        any pod security policy: [spec.initContainers[0].securityContext.capabilities.add:
        Invalid value: "NET_ADMIN": capability may not be added]'
      reason: FailedCreate
  • With manual sidecar injection and namespace label removed kubectl label namespace default istio-injection- istioctl kube-inject -f httpbin.yaml | kubectl create -f -
kubectl get pods
NAME                      READY     STATUS            RESTARTS   AGE
httpbin-986b585bf-z5xkb   0/2       PodInitializing   0          3s
  • sidecar-injector configmap kubectl -n istio-system get cm istio-sidecar-injector -o yaml
apiVersion: v1
data:
  config: "policy: enabled\ntemplate: |-\n  initContainers:\n  - name: istio-init\n
    \   image: \"istio/proxy_init:1.0.3\"\n    args:\n    -
    \"-p\"\n    - [[ .MeshConfig.ProxyListenPort ]]\n    - \"-u\"\n    - 1337\n    -
    \"-m\"\n    - [[ annotation .ObjectMeta `sidecar.istio.io/interceptionMode` .ProxyConfig.InterceptionMode
    ]]\n    - \"-i\"\n    - \"[[ annotation .ObjectMeta `traffic.sidecar.istio.io/includeOutboundIPRanges`
    \ \"100.64.0.0/13\"  ]]\"\n    - \"-x\"\n    - \"[[ annotation .ObjectMeta `traffic.sidecar.istio.io/excludeOutboundIPRanges`
    \ \"\"  ]]\"\n    - \"-b\"\n    - \"[[ annotation .ObjectMeta `traffic.sidecar.istio.io/includeInboundPorts`
    (includeInboundPorts .Spec.Containers) ]]\"\n    - \"-d\"\n    - \"[[ excludeInboundPort
    (annotation .ObjectMeta `status.sidecar.istio.io/port`  0 ) (annotation .ObjectMeta
    `traffic.sidecar.istio.io/excludeInboundPorts`  \"\" ) ]]\"\n    imagePullPolicy:
    IfNotPresent\n    securityContext:\n      capabilities:\n        add:\n        -
    NET_ADMIN\n      privileged: false \n    restartPolicy: Always\n  containers:\n
    \ - name: istio-proxy\n    image: [[ annotation .ObjectMeta `sidecar.istio.io/proxyImage`
    \ \"istio/proxyv2:1.0.3\"  ]]\n\n    ports:\n    - containerPort:
    15090\n      protocol: TCP\n      name: http-envoy-prom\n\n    args:\n    - proxy\n
    \   - sidecar\n    - --configPath\n    - [[ .ProxyConfig.ConfigPath ]]\n    -
    --binaryPath\n    - [[ .ProxyConfig.BinaryPath ]]\n    - --serviceCluster\n    [[
    if ne \"\" (index .ObjectMeta.Labels \"app\") -]]\n    - [[ index .ObjectMeta.Labels
    \"app\" ]]\n    [[ else -]]\n    - \"istio-proxy\"\n    [[ end -]]\n    - --drainDuration\n
    \   - [[ formatDuration .ProxyConfig.DrainDuration ]]\n    - --parentShutdownDuration\n
    \   - [[ formatDuration .ProxyConfig.ParentShutdownDuration ]]\n    - --discoveryAddress\n
    \   - [[ .ProxyConfig.DiscoveryAddress ]]\n    - --discoveryRefreshDelay\n    -
    [[ formatDuration .ProxyConfig.DiscoveryRefreshDelay ]]\n    - --zipkinAddress\n
    \   - [[ .ProxyConfig.ZipkinAddress ]]\n    - --connectTimeout\n    - [[ formatDuration
    .ProxyConfig.ConnectTimeout ]]\n    - --proxyAdminPort\n    - [[ .ProxyConfig.ProxyAdminPort
    ]]\n    [[ if gt .ProxyConfig.Concurrency 0 -]]\n    - --concurrency\n    - [[
    .ProxyConfig.Concurrency ]]\n    [[ end -]]\n    - --controlPlaneAuthPolicy\n
    \   - [[ annotation .ObjectMeta `sidecar.istio.io/controlPlaneAuthPolicy` .ProxyConfig.ControlPlaneAuthPolicy
    ]]\n  [[- if (ne (annotation .ObjectMeta `status.sidecar.istio.io/port`  0 ) \"0\")
    ]]\n    - --statusPort\n    - [[ annotation .ObjectMeta `status.sidecar.istio.io/port`
    \ 0  ]]\n    - --applicationPorts\n    - \"[[ annotation .ObjectMeta `readiness.status.sidecar.istio.io/applicationPorts`
    (applicationPorts .Spec.Containers) ]]\"\n  [[- end ]]\n    env:\n    - name:
    POD_NAME\n      valueFrom:\n        fieldRef:\n          fieldPath: metadata.name\n
    \   - name: POD_NAMESPACE\n      valueFrom:\n        fieldRef:\n          fieldPath:
    metadata.namespace\n    - name: INSTANCE_IP\n      valueFrom:\n        fieldRef:\n
    \         fieldPath: status.podIP\n    - name: ISTIO_META_POD_NAME\n      valueFrom:\n
    \       fieldRef:\n          fieldPath: metadata.name\n    - name: ISTIO_META_INTERCEPTION_MODE\n
    \     value: [[ or (index .ObjectMeta.Annotations \"sidecar.istio.io/interceptionMode\")
    .ProxyConfig.InterceptionMode.String ]]\n    [[ if .ObjectMeta.Annotations ]]\n
    \   - name: ISTIO_METAJSON_ANNOTATIONS\n      value: |\n             [[ toJson
    .ObjectMeta.Annotations ]]\n    [[ end ]]\n    [[ range $k,$v := .ObjectMeta.Labels
    ]]\n    - name: ISTIO_META_[[ $k ]]\n      value: \"[[ $v ]]\"\n    [[ end ]]\n
    \   imagePullPolicy: IfNotPresent\n    [[ if (ne (annotation .ObjectMeta `status.sidecar.istio.io/port`
    \ 0 ) \"0\") ]]\n    readinessProbe:\n      httpGet:\n        path: /healthz/ready\n
    \       port: [[ annotation .ObjectMeta `status.sidecar.istio.io/port`  0  ]]\n
    \     initialDelaySeconds: [[ annotation .ObjectMeta `readiness.status.sidecar.istio.io/initialDelaySeconds`
    \ 1  ]]\n      periodSeconds: [[ annotation .ObjectMeta `readiness.status.sidecar.istio.io/periodSeconds`
    \ 2  ]]\n      failureThreshold: [[ annotation .ObjectMeta `readiness.status.sidecar.istio.io/failureThreshold`
    \ 30  ]]\n    [[ end -]]securityContext:\n      \n      readOnlyRootFilesystem:
    true\n      [[ if eq (annotation .ObjectMeta `sidecar.istio.io/interceptionMode`
    .ProxyConfig.InterceptionMode) \"TPROXY\" -]]\n      capabilities:\n        add:\n
    \       - NET_ADMIN\n      runAsGroup: 1337\n      [[ else -]]\n      runAsUser:
    1337\n      [[ end -]]\n    restartPolicy: Always\n    resources:\n      [[ if
    (isset .ObjectMeta.Annotations `sidecar.istio.io/proxyCPU`) -]]\n      requests:\n
    \       cpu: \"[[ index .ObjectMeta.Annotations `sidecar.istio.io/proxyCPU` ]]\"\n
    \       memory: \"[[ index .ObjectMeta.Annotations `sidecar.istio.io/proxyMemory`
    ]]\"\n    [[ else -]]\n      requests:\n        cpu: 10m\n      \n    [[ end -]]\n
    \   volumeMounts:\n    - mountPath: /etc/istio/proxy\n      name: istio-envoy\n
    \   - mountPath: /etc/certs/\n      name: istio-certs\n      readOnly: true\n
    \ volumes:\n  - emptyDir:\n      medium: Memory\n    name: istio-envoy\n  - name:
    istio-certs\n    secret:\n      optional: true\n      [[ if eq .Spec.ServiceAccountName
    \"\" -]]\n      secretName: istio.default\n      [[ else -]]\n      secretName:
    [[ printf \"istio.%s\" .Spec.ServiceAccountName ]]\n      [[ end -]]"
kind: ConfigMap

PSP, ClusterRole and ClusterRoleBinding shown in following sections

Expected behavior With NONE of the inject container’ssecurityContext demanding privileged: true, a PSP with privilegeEscalationAllowed: false and CAP_NET_ADMIN enabled should satisty the requirements to successfully deploy a pod with the sidecar injected.

I have proved this by removing the namespace label istio-injecion (to disable automatic sidecar injection) and using manual sidecar injection using istioctl kube-inject -f httpbin.yaml to generate and apply the modified spec. In this case the deployment succeeds with the aforemention PSP.

Steps to reproduce the bug With the istio-sidecar-injector configmap using the value privilege: false being used in the initContainer `securityContext, enable automatic injection in default namespace by labeling with istio-injection=enabled, as follows,

kubectl label namespace default istio-injection=enabled

Create a PSP, ClusterRole and ClusterRoleBinding as follows in the default namespace,

apiVersion: extensions/v1beta1
kind: PodSecurityPolicy
metadata:
  name: istio-psp
spec:
  allowPrivilegeEscalation: false
  allowedCapabilities:
  - NET_ADMIN
  fsGroup:
    rule: RunAsAny
  runAsUser:
    rule: RunAsAny
  seLinux:
    rule: RunAsAny
  supplementalGroups:
    rule: RunAsAny
  volumes:
  - configMap
  - emptyDir
  - projected
  - secret
  - downwardAPI
  - persistentVolumeClaim
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: psp-user
rules:
- apiGroups:
  - extensions
  resourceNames:
  - istio-psp
  resources:
  - podsecuritypolicies
  verbs:
  - use
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: psp-user-binding
  namespace: default
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: psp-user
subjects:
- apiGroup: ""
  kind: ServiceAccount
  name: default
  namespace: default

, and apply.

Now deploy sample/httpbin/httpbin.yaml to the default namespace.

`kubectl create -f sample/httpbin/httpbin.yaml

Version kubectl version

Client Version: version.Info{Major:"1", Minor:"10", GitVersion:"v1.10.5", GitCommit:"32ac1c9073b132b8ba18aa830f46b77dcceb0723", GitTreeState:"clean", BuildDate:"2018-06-21T11:46:00Z", GoVersion:"go1.9.3", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"12", GitVersion:"v1.12.3", GitCommit:"435f92c719f279a3a67808c80521ea17d5715c66", GitTreeState:"clean", BuildDate:"2018-11-26T12:46:57Z", GoVersion:"go1.10.4", Compiler:"gc", Platform:"linux/amd64"}

istioctl version

Version: 1.0.3
GitRevision: a44d4c8bcb427db16ca4a439adfbd8d9361b8ed3
User: root@0ead81bba27d
Hub: docker.io/istio
GolangVersion: go1.10.4
BuildStatus: Clean

Installation Istio was installed using helm template with --set global.proxy.privileged=false among other settings.

Environment Self managed kubernetes cluster running on AWS.

Cluster state Can provide as needed.

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 32 (17 by maintainers)

Most upvoted comments

I’m also encountering this issue, even when using istio-cni, which avoids needing proxy_init and thus the NET_ADMIN privilege, and with proxy.privileged: false in the Helm chart values. This is surprising to me because the securityContext doesn’t seem to need privilege escalation:

    securityContext:
      procMount: Default
      readOnlyRootFilesystem: true
      runAsUser: 1337

I found issue https://github.com/kubernetes/kubernetes/issues/65716, which suggests that this could be solved if the sidecar injector added allowPrivilegeEscalation: false to istio-proxy’s securityContext. I tested this out by adding that to the istio-sidecar-injector ConfigMap and restarting the sidecar injector, and it fixed this issue. Also, I added

          capabilities:
            drop:
            - ALL

to the security context, which allowed me to include requiredDropCapabilities: ["ALL"] in my PSP. Could we consider adding these to the security context in the helm chart?

Thanks for bringing this up @tbarrella, I’ve created https://github.com/istio/istio/issues/12231 to track it.

Yeah, I’ve since removed the change, but it should be around this line in 1.1.0