istio: Incorrect RemoteIP when Authorization Policy is applied to Injected Istio Proxy
Bug description
When AuthorizationPolicy
is applied to injected istio proxy, remoteIpBlocks
does not work as expected when istio gateway is behind another reverse proxy (Azure Front Door). RemoteIP seems to set to the IP of the reverse-proxy deployed in front of istio gateway. (see diagram above: requests in green, configuration in blue)
kind: AuthorizationPolicy
metadata:
name: test-auth-policy
spec:
action: ALLOW
selector:
matchLabels:
app: test
rules:
- from:
- source:
remoteIpBlocks:
- <clientIp>
Request from <clientIp>
gets denied with the following entry in the logs:
2021-01-18T17:40:15.872025Z debug envoy rbac checking request: requestedServerName: outbound_.80_._.test-service.test-ns.svc.cluster.local, sourceIP: <IstioIngressGatewayIp>:37834, directRemoteIP: <IstioIngressGatewayIp>:37834, remoteIP: <AzureFrontDoorIP>:0,localAddress: 10.240.2.152:8080, ssl: uriSanPeerCertificate: spiffe://cluster.local/ns/istio-system/sa/istio-ingressgateway-public-service-account, dnsSanPeerCertificate: , subjectPeerCertificate: , headers: ':authority', 'test.example.com'
...
'x-forwarded-for', '<clientIp>,<AzureFrontDoorIP>'
...
'x-envoy-external-address', '<clientIp>'
...
'x-forwarded-client-cert', 'By=spiffe://cluster.local/ns/test-ns/sa/default;Hash=2a3d7357260f29961fd26f74bf7e9d637eaa9c23c4d735488f0541027b4c48ed;Subject="";URI=spiffe://cluster.local/ns/istio-system/sa/istio-ingressgateway-public-service-account'
, dynamicMetadata: filter_metadata {
key: "istio_authn"
value {
fields {
key: "request.auth.principal"
value {
string_value: "cluster.local/ns/istio-system/sa/istio-ingressgateway-public-service-account"
}
}
fields {
key: "source.namespace"
value {
string_value: "istio-system"
}
}
fields {
key: "source.principal"
value {
string_value: "cluster.local/ns/istio-system/sa/istio-ingressgateway-public-service-account"
}
}
fields {
key: "source.user"
value {
string_value: "cluster.local/ns/istio-system/sa/istio-ingressgateway-public-service-account"
}
}
}
}
2021-01-18T17:40:15.872065Z debug envoy rbac enforced denied, matched policy none
Affected product area (please put an X in all that apply)
[ ] Docs [ ] Installation [X] Networking [ ] Performance and Scalability [ ] Extensions and Telemetry [X] Security [ ] Test and Release [ ] User Experience [ ] Developer Infrastructure [ ] Upgrade
Affected features
[ ] Multi Cluster [ ] Virtual Machine [ ] Multi Control Plane
Expected behavior
AuthorizationPolicy
allows requests from <clientIp>
.
Steps to reproduce the bug
- Install istio using
istioctl
and configure with:ingressGateways: - enabled: false name: istio-ingressgateway k8s: replicaCount: 2 podAnnotations: proxy.istio.io/config: '{"gatewayTopology" : { "numTrustedProxies": 1 } }' service: externalTrafficPolicy: Local
- Create
AuthorizationPolicy
kind: AuthorizationPolicy metadata: name: test-auth-policy spec: action: ALLOW selector: matchLabels: app: test rules: - from: - source: remoteIpBlocks: - <clientIp>
- Deploy a dummy service with the labels matching istio
AuthorizationPolicy
- Make a request from
<clientIp>
through a reverse proxy (or simulate with XFF header) - Observe the request beeing blocked, but should be allowed
Version
istioctl version --remote
client version: 1.8.2 control plane version: 1.8.2 data plane version: 1.8.2 (7 proxies)
kubectl version --short
Client Version: v1.18.15 Server Version: v1.18.14
How was Istio installed?
istioctl install -f install.yaml
install.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
profile: default
tag: 1.8.2-distroless
meshConfig:
outboundTrafficPolicy:
mode: REGISTRY_ONLY
components:
cni:
enabled: true
ingressGateways:
- enabled: false
name: istio-ingressgateway
- enabled: true
name: istio-ingressgateway-public
k8s:
replicaCount: 2
podAnnotations:
proxy.istio.io/config: '{"gatewayTopology" : { "numTrustedProxies": 1 } }'
serviceAnnotations:
service.beta.kubernetes.io/azure-allowed-service-tags: AzureFrontDoor.Backend
service:
externalTrafficPolicy: Local
loadBalancerIP: 51.145.104.39
ports:
- name: http2
port: 80
targetPort: 8080
- name: https
port: 443
targetPort: 8443
hpaSpec:
maxReplicas: 5
minReplicas: 2
resources:
limits:
cpu: 650m
memory: 400Mi
requests:
cpu: 100m
memory: 400Mi
overlays:
- kind: HorizontalPodAutoscaler
name: istio-ingressgateway-public
patches:
- path: metadata.labels.app
value: istio-ingressgateway-public
- path: metadata.labels.istio
value: public-ingressgateway
- path: spec.scaleTargetRef.name
value: istio-ingressgateway-public
- kind: Deployment
name: istio-ingressgateway-public
patches:
- path: metadata.labels.app
value: istio-ingressgateway-public # Change the label to istio-ingressgateway-public
- path: metadata.labels.istio
value: public-ingressgateway
- path: spec.selector.matchLabels.app
value: istio-ingressgateway-public
- path: spec.selector.matchLabels.istio
value: public-ingressgateway
- path: spec.template.metadata.labels.app
value: istio-ingressgateway-public
- path: spec.template.metadata.labels.istio
value: public-ingressgateway
- path: spec.template.spec.affinity.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].labelSelector.matchExpressions[0].values[0]
value: istio-ingressgateway-public
- kind: Service
name: istio-ingressgateway-public
patches:
- path: metadata.labels.app
value: istio-ingressgateway-public
- path: metadata.labels.istio
value: public-ingressgateway
- path: spec.selector.app
value: istio-ingressgateway-public
- path: spec.selector.istio
value: public-ingressgateway
- kind: ServiceAccount
name: istio-ingressgateway-public-service-account
patches:
- path: metadata.labels.app
value: istio-ingressgateway-public
- path: metadata.labels.istio
value: public-ingressgateway
- kind: PodDisruptionBudget
name: istio-ingressgateway-public
patches:
- path: metadata.labels.app
value: istio-ingressgateway-public
- path: metadata.labels.istio
value: public-ingressgateway
- path: spec.selector.matchLabels.app
value: istio-ingressgateway-public
- path: spec.selector.matchLabels.istio
value: public-ingressgateway
pilot:
k8s:
replicaCount: 2
values:
global:
defaultNodeSelector:
beta.kubernetes.io/os: linux
proxy:
resources:
limits:
cpu: 500m
memory: 400Mi
requests:
cpu: 10m
memory: 80Mi
proxy_init:
resources:
limits:
cpu: 500m
memory: 400Mi
requests:
cpu: 10m
memory: 10Mi
gateways:
istio-ingressgateway:
podAntiAffinityLabelSelector:
- key: app
operator: In
values: istio-ingressgateway
topologyKey: kubernetes.io/hostname
cni:
excludeNamespaces:
- istio-system
- kube-system
- kube-public
- kube-node-lease
pilot:
autoscaleMin: 2
Environment where the bug was observed (cloud vendor, OS, etc)
- Cloud: Azure AKS
- Networking: Azure (advanced)
- OS:
Ubuntu 18.04.5 LTS
- Kernel:
5.4.0-1035-azure
- Container Runtime:
docker://19.3.14
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Reactions: 10
- Comments: 27 (13 by maintainers)
I’m not sure if/when that might be supported, but you can approximate it a couple ways:
I think an AuthorizationPolicy like this might work:
That just supports basic wildcard matching right now I think, but full regex matching is on the horizon. If you need a full regex, you could also use the VirtualService to filter the traffic with something like this:
@rblaine95 Below one worked for me
I gave a few things a try there but no joy unfortunately. It seems this stuff is perhaps codified to the gateways? I think the problem with selecting on the IGWs with an
AuthorizationPolicy
is multi-tenant platforms (like ours) tend to share gateways. Product engineering teams (the platform consumers) don’t get permission to put stuff in the namespace that the IGWs reside.Quick dump of things tried:
Firstly, @yangminzhu’s suggestion of hacking in the annotation at various levels (pod spec, deployment etc.) but didn’t have any luck.
Then I tried to get the annotations in place with our mesh deployment (which is
istioctl
generation rather than in-cluster operator). I think the more modern Istio incantation here is something like (please correct me if wrong):This unfortunately doesn’t get accepted by our 1.8
istiod
. It crash loops trying to parse the above from YAML to JSON. I tried a few variations but no luck. Perhaps someone else will have more joy.The
EnvoyFilter
approachThis got me the furthest. The
istioctl listeners
objective outlined by @kylebevans is met, but still seems to have no effect, and RBAC debug logging still shows the wrong IP, unlike at the Gateway. Maybe myEnvoyFilter
is not sliding in at the right spot?Inspired by this suggestion from @anannaya, I tried the following, which worked perfectly:
The only other required change was adding the following annotation to the pod spec of my
istio-ingressgateway
deployment, following the instructions from the documentation:proxy.istio.io/config: '{"gatewayTopology" : { "numTrustedProxies": 1 } }'
If
numTrustedProxies
is set correctly for your topology, thenX-Envoy-External-Address
will populate with the correct IP fromX-Forwarded-For
(which can potentially be appended to by each layer 7 proxy along the request path), eliminating the need for a wildcard match.numTrustedProxies
is set to 1 because:Prior to getting this to work, I tried every conceivable configuration of the
from
andto
fields with no success.Hope this helps someone.
@pokidov Below configuration at sidecar worked , I have some which routed via ALB So i preferred to apply at the app layer, If you have a all the apps goes via ALB then create Envoyfilter in istio-system namespace.
@jingslunt I have tried with 1 and 2 as well. Not worked for me.
Hi there, can this issue potentially be re-opened?
We’re experiencing a very similar issue (
Client -> AWS ALB -> IGW -> Sidecar -> Workload
) whereremoteIP
is the internal IP address of the AWS ALB, but not Client IP.Istio version:
1.13.1
Setting this Istio config:
Doesn’t add the
"xffNumTrustedHops": 1,
proxy config, even though the sidecar logs the correct config:Adding the annotation to the pods
proxy.istio.io/config: '{"gatewayTopology" : { "numTrustedProxies": 1 } }'
also doesn’t add thexffNumTrustedHops
config when doingistioctl proxy-config listeners -o json -n $NS $POD | grep xff
Example logs from sidecar:
After applying the EnvoyFilter detailed at https://github.com/istio/istio/issues/30166#issuecomment-796556188 that selects the workload pods, the
xffNumTrustedHops
config appears and theremoteIP
is the correct Client IP:Side note: Also tried setting
gatewayTopology.numTrustedProxies
to higher values like2
and5
with no luck.If my understanding of the documentation is correct, this appears like it may be a bug.
FYI, the X-Envoy-External-Address isn’t calculated at the sidecar, and it is not what is used internally by RBAC. It’s calculated at the ingress gateway, and then the sidecars just pass it through among themselves.
Thanks for the very detailed report, that’s a very clear description.
I haven’t looked deep into this but just a quick check, have you tried setting the
numTrustedProxies
to the sidecar as well?