Istio mTLS¶
Istio is an open source service mesh for managing the different microservices that make up a cloud-native application. Istio provides a mechanism to use a service as an external authorizer with the AuthorizationPolicy API.
The kyverno-envoy-plugin is a custom Envoy filter that is used to intercept the incoming request to the service and validate the request using the kyverno engine.
In this tutorial we will create a two simple microservices which are going to make external authorization to a single kyverno-envoy-plugin service as a separate pod in the mesh. With this tutorial we are going to understand how to use multiple microservices to make authorization decisions to a single ext-authz server.
To handle multiple different requests effectively, we leverage the match/exclude
declarations to route the specific authz-request to the appropriate validating policy within the Kyverno engine. This approach allows us to execute the right validating policy for each request, enabling efficient and targeted request processing.
Example Policy¶
The following policies will be executed by the kyverno-envoy-plugin to validate incoming requests made specifically to the testapp-1
service. By leveraging the match declarations, we ensure that these policies are executed only when the incoming request is destined for the testapp-1
service. This targeted approach allows us to apply the appropriate validation rules and policies based on the specific service being accessed.
apiVersion: json.kyverno.io/v1alpha1
kind: ValidatingPolicy
metadata:
name: test-policy
spec:
rules:
- name: deny-external-calls-testapp-1
match:
any:
- request:
http:
host: 'testapp-1.demo.svc.cluster.local:8080'
assert:
all:
- message: "The GET method is restricted to the /book path."
check:
request:
http:
method: 'GET'
path: '/book'
testapp-2
service we need to use the match
declarations. apiVersion: json.kyverno.io/v1alpha1
kind: ValidatingPolicy
metadata:
name: test-policy
spec:
rules:
- name: deny-external-calls-testapp-2
match:
any:
- request:
http:
host: 'testapp-2.demo.svc.cluster.local:8080'
assert:
all:
- message: "The GET method is restricted to the /movies path."
check:
request:
http:
method: 'GET'
path: '/movie'
{
"source": {
"address": {
"socketAddress": {
"address": "10.244.0.71",
"portValue": 33880
}
}
},
"destination": {
"address": {
"socketAddress": {
"address": "10.244.0.65",
"portValue": 8080
}
}
},
"request": {
"time": "2024-05-20T07:52:01.566887Z",
"http": {
"id": "5415544797791892902",
"method": "GET",
"headers": {
":authority": "testapp-2.demo.svc.cluster.local:8080",
":method": "GET",
":path": "/movie",
":scheme": "http",
"user-agent": "Wget",
"x-forwarded-proto": "http",
"x-request-id": "a3ad9f03-c9cd-4eab-97d1-83e90e0cee1b"
},
"path": "/movie",
"host": "testapp-2.demo.svc.cluster.local:8080",
"scheme": "http",
"protocol": "HTTP/1.1"
}
},
"metadataContext": {},
"routeMetadataContext": {}
}
To enhance security, we can implement Mutual TLS (mTLS) for peer authentication between test services and kyverno-envoy-plugin. Since we are currently using JSON request data to validate incoming requests, there is a potential risk of this data being tampered with during transit. Implementing mTLS would ensure that communication between services is encrypted and authenticated, mitigating the risk of unauthorized data modification.
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: mtls-demo
namespace: demo
spec:
mtls:
mode: STRICT
---
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: mtls-testapp-1
namespace: demo
spec:
selector:
matchLabels:
app: testapp-1
mtls:
mode: STRICT
portLevelMtls:
8080:
mode: PERMISSIVE
---
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: mtls-testapp-2
namespace: demo
spec:
selector:
matchLabels:
app: testapp-2
mtls:
mode: STRICT
portLevelMtls:
8080:
mode: PERMISSIVE
Demo instructions¶
Required tools¶
Create a local cluster and install Istio¶
The tutorial also requries istio v1.19.0 or later. To install istio, follow the instructions here or run the below script it will create a kind cluster and install istio
#!/bin/bash
KIND_IMAGE=kindest/node:v1.29.2
ISTIO_REPO=https://istio-release.storage.googleapis.com/charts
ISTIO_NS=istio-system
# Create Kind cluster
kind create cluster --image $KIND_IMAGE --wait 1m --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
kubeadmConfigPatches:
- |-
kind: InitConfiguration
nodeRegistration:
kubeletExtraArgs:
node-labels: "ingress-ready=true"
extraPortMappings:
- containerPort: 80
hostPort: 80
protocol: TCP
- containerPort: 443
hostPort: 443
protocol: TCP
- role: worker
EOF
# Install Istio components
helm upgrade --install istio-base --namespace $ISTIO_NS --create-namespace --wait --repo $ISTIO_REPO base
helm upgrade --install istiod --namespace $ISTIO_NS --create-namespace --wait --repo $ISTIO_REPO istiod
Sample applications¶
Manifests for the sample applications are available in test-application-1.yaml and test-application-2.yaml. The sample app testapp-1
provides information about books in a collection and exposes APIs to get, create and delete Book resources. The sample app testapp-2
provides information about movies in a collection and exposes APIs to get, create and delete Movie resources.
$ kubectl apply -f - <<EOF
apiVersion: v1
kind: Namespace
metadata:
name: demo
labels:
istio-injection: enabled
EOF
# test-application-1.yaml
# Deploy sample application testapp-1
$ kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: testapp-1
namespace: demo
spec:
replicas: 1
selector:
matchLabels:
app: testapp-1
template:
metadata:
labels:
app: testapp-1
spec:
containers:
- name: testapp-1
image: sanskardevops/test-application:0.0.1
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: testapp-1
namespace: demo
spec:
type: NodePort
selector:
app: testapp-1
ports:
- port: 8080
targetPort: 8080
EOF
# test-application-2.yaml
# Deploy sample application testapp-2
$ kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: testapp-2
namespace: demo
spec:
replicas: 1
selector:
matchLabels:
app: testapp-2
template:
metadata:
labels:
app: testapp-2
spec:
containers:
- name: testapp-2
image: sanskardevops/test-application-1:0.0.3
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: testapp-2
namespace: demo
spec:
type: ClusterIP
selector:
app: testapp-2
ports:
- port: 8080
targetPort: 8080
EOF
Calling the sample applications¶
We are going to call the sample applications using a pod in the cluster.
$ kubectl run test -it --rm --restart=Never --image=busybox -- wget -q --output-document - testapp-1.demo.svc.cluster.local:8080/book
[{"id":"1","bookname":"Harry Potter","author":"J.K. Rowling"},{"id":"2","bookname":"Animal Farm","author":"George Orwell"}]
pod "test" deleted
$ kubectl run test -it --rm --restart=Never --image=busybox -- wget -q --output-document - testapp-2.demo.svc.cluster.local:8080/movie
[{"id":"1","Moviename":"Inception","Actor":"Leonardo DiCaprio"},{"id":"2","Moviename":"Batman","Actor":"Jack Nicholson"}]
pod "test" deleted
Register authorization provider¶
Edit the mesh configmap to register authorization provider with the following command:
$ kubectl edit configmap istio -n istio-system
data:
mesh: |-
extensionProviders:
- name: "kyverno-ext-authz-grpc"
envoyExtAuthzGrpc:
service: "ext-authz.demo.svc.cluster.local"
port: "9000"
Authorization policy¶
Now we can deploy an istio AuthorizationPolicy
: AuthorizationPolicy to tell Istio to use kyverno-envoy-plugin as the Authz Server
$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: kyverno-ext-authz-grpc
namespace: demo
spec:
action: CUSTOM
provider:
# The provider name must match the extension provider defined in the mesh config.
name: kyverno-ext-authz-grpc
rules:
# The rules specify when to trigger the external authorizer.
- to:
- operation:
paths: ["/book","/movie"]
EOF
This policy configures an external service for authorization. Note that the service is not specified directly in the policy but using a provider.name field. The rules specify that requests to paths /book
and /movies
.
Authorization service deployment¶
The deployment manifest of the authorization service is available in ext-auth-server.yaml. This deployment require policy through configmap .
Apply the policy configmap with the following command.
$ kubectl apply -f - <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
name: policy-files
namespace: demo
data:
policy1.yaml: |
apiVersion: json.kyverno.io/v1alpha1
kind: ValidatingPolicy
metadata:
name: test-policy
spec:
rules:
- name: deny-external-calls-testapp-1
match:
any:
- request:
http:
host: 'testapp-1.demo.svc.cluster.local:8080'
assert:
all:
- message: "The GET method is restricted to the /book path."
check:
request:
http:
method: 'GET'
path: '/book'
policy2.yaml: |
apiVersion: json.kyverno.io/v1alpha1
kind: ValidatingPolicy
metadata:
name: test-policy
spec:
rules:
- name: deny-external-calls-testapp-2
match:
any:
- request:
http:
host: 'testapp-2.demo.svc.cluster.local:8080'
assert:
all:
- message: "The GET method is restricted to the /movies path."
check:
request:
http:
method: 'GET'
path: '/movie'
EOF
# ext-auth-server.yaml
# Deploy the kyverno external authorizer server
$ kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
name: ext-authz
labels:
app: ext-authz
namespace: demo
spec:
ports:
- name: http
port: 8000
targetPort: 8000
- name: grpc
port: 9000
targetPort: 9000
selector:
app: ext-authz
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: ext-authz
namespace: demo
spec:
replicas: 1
selector:
matchLabels:
app: ext-authz
template:
metadata:
labels:
app: ext-authz
spec:
containers:
- image: sanskardevops/plugin:0.0.29
imagePullPolicy: IfNotPresent
name: ext-authz
ports:
- containerPort: 8000
- containerPort: 9000
args:
- "serve"
- "--policy=/policies/policy1.yaml"
- "--policy=/policies/policy2.yaml"
volumeMounts:
- name: policy-files
mountPath: /policies
volumes:
- name: policy-files
configMap:
name: policy-files
EOF
Verify the sample external authorizer is up and running:
$ kubectl logs "$(kubectl get pod -l app=ext-authz -n demo -o jsonpath={.items..metadata.name})" -n demo -c ext-authz -f
Starting GRPC server on Port 9000
Starting HTTP server on Port 8000
Apply PeerAuthentication Policy¶
Apply the PeerAuthentication policy to enable mTLS for the sample applications and external authorizer.
$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: mtls-demo
namespace: demo
spec:
mtls:
mode: STRICT
---
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: mtls-testapp-1
namespace: demo
spec:
selector:
matchLabels:
app: testapp-1
mtls:
mode: STRICT
portLevelMtls:
8080:
mode: PERMISSIVE
---
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: mtls-testapp-2
namespace: demo
spec:
selector:
matchLabels:
app: testapp-2
mtls:
mode: STRICT
portLevelMtls:
8080:
mode: PERMISSIVE
EOF
Test the sample applications¶
Check on the logs of the sample applications to see that the requests are accepted and rejected
Check on GET
request on testapp-1
which is allowed according to policy deny-external-calls-testapp-1
$ kubectl run test -it --rm --restart=Never --image=busybox -- wget -q --output-document - testapp-1.demo.svc.cluster.local:8080/book
[{"id":"1","bookname":"Harry Potter","author":"J.K. Rowling"},{"id":"2","bookname":"Animal Farm","author":"George Orwell"}]
pod "test" deleted
Check on GET
request on testapp-2
which is allowed according to policy deny-external-calls-testapp-2
$ kubectl run test -it --rm --restart=Never --image=busybox -- wget -q --output-document - testapp-2.demo.svc.cluster.local:8080/movie
[{"id":"1","Moviename":"Inception","Actor":"Leonardo DiCaprio"},{"id":"2","Moviename":"Batman","Actor":"Jack Nicholson"}]
pod "test" deleted
Check logs of external authorizer to see that the requests are which policy was executed for a perticular request .
$ kubectl logs "$(kubectl get pod -l app=ext-authz -n demo -o jsonpath={.items..metadata.name})" -n demo -c ext-authz -f
Starting GRPC server on Port 9000
Starting HTTP server on Port 8000
2024/05/21 07:41:33 Request is initialized in kyvernojson engine .
2024/05/21 07:41:33 Request passed the deny-external-calls-testapp-1 policy rule.
2024/05/21 07:42:22 Request is initialized in kyvernojson engine .
2024/05/21 07:42:22 Request passed the deny-external-calls-testapp-2 policy rule.
deny-external-calls-testapp-1
and the second request was directed to testapp-2 which was allowed by the policy deny-external-calls-testapp-2
. Wrap Up¶
Congratulations on completing the tutorial!
This tutorial demonstrated how to configure Istio's AuthorizationPolicy to utilize the kyverno-envoy-plugin as an separate pod external authorization service. By leveraging the power of Kyverno's policy engine, you can enforce fine-grained authorization rules across your microservices within the Istio service mesh.
Additionally, the tutorial showcased the use of mTLS (Mutual TLS) to secure communication between services and the kyverno-envoy-plugin, ensuring end-to-end encryption and authentication.
The combination of Istio's AuthorizationPolicy and the kyverno-envoy-plugin provides a flexible and powerful solution for implementing custom authorization logic in your cloud-native applications. By following this tutorial, you've gained hands-on experience in configuring and deploying this solution, setting the stage for further exploration and customization to meet your specific requirements.
We hope this tutorial has been informative and has provided you with a solid foundation for integrating the kyverno-envoy-plugin into your Istio service mesh environment. Feel free to explore the project's documentation and community resources for further assistance and to stay updated with the latest developments.