From 3d8f118dd71d02502678edb53bca05aae939fc68 Mon Sep 17 00:00:00 2001 From: savagebidoof Date: Wed, 26 Apr 2023 02:08:15 +0200 Subject: [PATCH 01/25] HTTP to HTTPS documented. --- .../README.md | 208 ++++++++++++++++++ .../gateway.yaml | 25 +++ 2 files changed, 233 insertions(+) create mode 100644 Istio/02-Traffic_management/12-HTTP-to-HTTPS-traffic-redirect/README.md create mode 100755 Istio/02-Traffic_management/12-HTTP-to-HTTPS-traffic-redirect/gateway.yaml diff --git a/Istio/02-Traffic_management/12-HTTP-to-HTTPS-traffic-redirect/README.md b/Istio/02-Traffic_management/12-HTTP-to-HTTPS-traffic-redirect/README.md new file mode 100644 index 0000000..631359d --- /dev/null +++ b/Istio/02-Traffic_management/12-HTTP-to-HTTPS-traffic-redirect/README.md @@ -0,0 +1,208 @@ +--- +gitea: none +include_toc: true +--- + +# Based on + +- [07-HTTPS-Gateway-Simple-TLS](../07-HTTPS-Gateway-Simple-TLS) + +# Description + +This example adds `HTTP` to `HTTPS` redirect at the port `80` of the gateway, which listens for HTTP traffic. + +Also contains a listens for `HTTPS` traffic at the port 443, with a self-signed certificate. This will be used to ensure that the redirect is working correctly. + + +> **NOTE:**\ +> This example is kept at minimal, without the need of containing a `Virtual Service`, a `Service` nor a `Deployment/Pod`. + +# Configuration applied + +## Gateway + +The port `80` listens for any host, expecting `HTTP` traffic.\ +As `tls.httpsRedirect` is set to `true`, the incoming traffic will be redirected to `HTTPS`, effectively enabling the `HTTP` to `HTTPS` redirect. + + +The port `443` is expecting traffic that use the `HTTPS` protocol, without being limited to specific hosts.\ +The TLS configuration is set to simple, and the credentials (the object that contains the certificates/TLS configuration) is set to `my-tls-cert-secret`. + +```yaml +apiVersion: networking.istio.io/v1alpha3 +kind: Gateway +metadata: + name: helloworld-gateway +spec: + selector: + istio: ingressgateway + servers: + - port: + number: 80 + name: http + protocol: HTTP + hosts: + - "*" + tls: + httpsRedirect: true + - port: + number: 443 + name: https-web + protocol: HTTPS + hosts: + - "*" + tls: + mode: SIMPLE + credentialName: my-tls-cert-secret +``` + +> **Note:**\ +> The credentials resource is created further bellow through the [Walkthrough](#walkthrough) steps. + +> **Note:**\ +> For more information regarding the TLS mode configuration, refer to the following [Istio documentation regarding the TLS mode field](https://istio.io/latest/docs/reference/config/networking/gateway/#ServerTLSSettings-TLSmode). + +# Walkthrough + +## Generate client and server certificate and key files + +First step will be to generate the certificate and key files to be able to set them to the Gateway resource. + +### Create a folder to store files. + +Create the folder to contain the files that will be generated. + +```shell +mkdir certfolder +``` + +### Create a certificate and a private key. + +```shell +openssl req -x509 -sha256 -nodes -days 365 -subj '/O=Internet of things/CN=lb.net' -newkey rsa:2048 -keyout certfolder/istio.cert.key -out certfolder/istio.cert.crt +``` + +The files generated are the following: + +```yaml +private-key: certfolder/istio.cert.key +root-certificate: certfolder/istio.cert.crt +``` + +The information set to the certificate generated is the following: + +```yaml +Organization-name: Internet of things +CN: lb.net +``` + +### Create a TLS secret + +At this step we create the tls secret `my-tls-cert-secret` on the namespace `istio-system`. + +```shell +kubectl create -n istio-system secret tls my-tls-cert-secret \ + --key=certfolder/istio.cert.key \ + --cert=certfolder/istio.cert.crt +``` +```text +secret/my-tls-cert-secret created +``` +```text +service/helloworld created +deployment.apps/helloworld-nginx created +gateway.networking.istio.io/helloworld-gateway created +virtualservice.networking.istio.io/helloworld-vs created +``` + +> **Note:**\ +> It's Important that the secret is located in the same namespace as the Load Balancer used. In my case is the `istio-system`, but it will vary based on the environment. + + +## Deploy resources + +```shell +kubectl apply -f ./ +``` +```text +gateway.networking.istio.io/helloworld-gateway created +``` + +## Test the service + +### Curl --HEAD + +We receive the status message `301 Moved Permanently`. + +This points that we are being redirected somewhere. By default, `curl` doesn't follow the redirects. + +To confirm the redirect is being performed towards the `HTTPS` service, we allow `curl` to follow redirects. + +```shell +curl http://192.168.1.50 -I +``` +```text +HTTP/1.1 301 Moved Permanently +location: https://192.168.1.50/ +date: Tue, 25 Apr 2023 23:59:34 GMT +server: istio-envoy +transfer-encoding: chunked +``` + +### Curl --HEAD follow redirects + +Allowing `curl` to follow redirects, we notice the following output: + +- First we receive the same message from before, as we connected to the same service. + +- Afterwards we are met with a status code `404`, which is expected as we don't have any service configured behind this gateway. + +> **NOTE:**\ +> Due using a self-signed certificate, had to allow accessing "insecure" `HTTPS` destinations. + +```shell +curl http://192.168.1.50 -I -L -k +``` +```text +HTTP/1.1 301 Moved Permanently +location: https://192.168.1.50/ +date: Wed, 26 Apr 2023 00:05:24 GMT +server: istio-envoy +transfer-encoding: chunked + +HTTP/2 404 +date: Wed, 26 Apr 2023 00:05:24 GMT +server: istio-envoy +``` + +## Cleanup + +```shell +kubectl delete -n istio-system secret my-tls-cert-secret +``` + +```text +secret "my-tls-cert-secret" deleted +``` + +```shell +kubectl delete -f ./ +``` +```text +gateway.networking.istio.io "helloworld-gateway" deleted +``` + +```shell +rm -rv certfolder/ +``` + +```text +removed 'certfolder/istio.cert.key' +removed 'certfolder/istio.cert.crt' +removed directory 'certfolder/' +``` + +# Links of Interest + + +- https://istio.io/latest/docs/reference/config/networking/gateway/#ServerTLSSettings \ No newline at end of file diff --git a/Istio/02-Traffic_management/12-HTTP-to-HTTPS-traffic-redirect/gateway.yaml b/Istio/02-Traffic_management/12-HTTP-to-HTTPS-traffic-redirect/gateway.yaml new file mode 100755 index 0000000..1e60549 --- /dev/null +++ b/Istio/02-Traffic_management/12-HTTP-to-HTTPS-traffic-redirect/gateway.yaml @@ -0,0 +1,25 @@ +apiVersion: networking.istio.io/v1alpha3 +kind: Gateway +metadata: + name: helloworld-gateway +spec: + selector: + istio: ingressgateway + servers: + - port: + number: 80 + name: http + protocol: HTTP + hosts: + - "*" + tls: + httpsRedirect: true + - port: + number: 443 + name: https-web + protocol: HTTPS + hosts: + - "*" + tls: + mode: SIMPLE + credentialName: my-tls-cert-secret \ No newline at end of file -- 2.47.2 From 585a76de61022752575aa5e6b33609fb021cefb8 Mon Sep 17 00:00:00 2001 From: savagebidoof Date: Wed, 26 Apr 2023 02:20:29 +0200 Subject: [PATCH 02/25] updated contents --- Istio/02-Traffic_management/README.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Istio/02-Traffic_management/README.md b/Istio/02-Traffic_management/README.md index 50b20e1..c57649a 100644 --- a/Istio/02-Traffic_management/README.md +++ b/Istio/02-Traffic_management/README.md @@ -1,6 +1,6 @@ # Examples -ALL NEEDS DOCUMENTATION +(almost) ALL NEEDS DOCUMENTATION / REVIEW - 01-2_deployments_method - 02-DirectResponse-HTTP-Body @@ -9,4 +9,15 @@ ALL NEEDS DOCUMENTATION - 05a-FaultInjection-delay - 05b-FaultInjection-abort - 06-mTLS (would need some documentation review, mainly go over the differences respective to the template/prior configuration used) -- 07-HTTPS-Gateway-Simple-TLS <- Doesn't respect the changelog format. \ No newline at end of file +- 07-HTTPS-Gateway-Simple-TLS <- Doesn't respect the changelog format. +- 08a-HTTPS-min-TLS-version +- 08b-HTTPS-max-TLS-version +- 09-HTTPS-backend +- 10-TCP-FORWARDING +- 11-TLS-PASSTHROUGH +- 12-HTTP-to-HTTPS-traffic-redirect -> Documented. + + + +This will need some reorganization. + -- 2.47.2 From 7ab9735f1534a76666fe5caa11321a0599942707 Mon Sep 17 00:00:00 2001 From: savagebidoof Date: Wed, 26 Apr 2023 02:21:59 +0200 Subject: [PATCH 03/25] renamed folder --- .../{01-namespaces => 01-target-namespaces}/01-namespace.yaml | 0 .../{01-namespaces => 01-target-namespaces}/README.md | 0 .../{01-namespaces => 01-target-namespaces}/authentication.yaml | 0 .../{01-namespaces => 01-target-namespaces}/deployment.yaml | 0 .../{01-namespaces => 01-target-namespaces}/deployment_2.yaml | 0 .../{01-namespaces => 01-target-namespaces}/gateway.yaml | 0 .../02-target-service-accounts/README.md | 2 +- 7 files changed, 1 insertion(+), 1 deletion(-) rename Istio/06-Internal-Authentication/{01-namespaces => 01-target-namespaces}/01-namespace.yaml (100%) rename Istio/06-Internal-Authentication/{01-namespaces => 01-target-namespaces}/README.md (100%) rename Istio/06-Internal-Authentication/{01-namespaces => 01-target-namespaces}/authentication.yaml (100%) rename Istio/06-Internal-Authentication/{01-namespaces => 01-target-namespaces}/deployment.yaml (100%) rename Istio/06-Internal-Authentication/{01-namespaces => 01-target-namespaces}/deployment_2.yaml (100%) rename Istio/06-Internal-Authentication/{01-namespaces => 01-target-namespaces}/gateway.yaml (100%) diff --git a/Istio/06-Internal-Authentication/01-namespaces/01-namespace.yaml b/Istio/06-Internal-Authentication/01-target-namespaces/01-namespace.yaml similarity index 100% rename from Istio/06-Internal-Authentication/01-namespaces/01-namespace.yaml rename to Istio/06-Internal-Authentication/01-target-namespaces/01-namespace.yaml diff --git a/Istio/06-Internal-Authentication/01-namespaces/README.md b/Istio/06-Internal-Authentication/01-target-namespaces/README.md similarity index 100% rename from Istio/06-Internal-Authentication/01-namespaces/README.md rename to Istio/06-Internal-Authentication/01-target-namespaces/README.md diff --git a/Istio/06-Internal-Authentication/01-namespaces/authentication.yaml b/Istio/06-Internal-Authentication/01-target-namespaces/authentication.yaml similarity index 100% rename from Istio/06-Internal-Authentication/01-namespaces/authentication.yaml rename to Istio/06-Internal-Authentication/01-target-namespaces/authentication.yaml diff --git a/Istio/06-Internal-Authentication/01-namespaces/deployment.yaml b/Istio/06-Internal-Authentication/01-target-namespaces/deployment.yaml similarity index 100% rename from Istio/06-Internal-Authentication/01-namespaces/deployment.yaml rename to Istio/06-Internal-Authentication/01-target-namespaces/deployment.yaml diff --git a/Istio/06-Internal-Authentication/01-namespaces/deployment_2.yaml b/Istio/06-Internal-Authentication/01-target-namespaces/deployment_2.yaml similarity index 100% rename from Istio/06-Internal-Authentication/01-namespaces/deployment_2.yaml rename to Istio/06-Internal-Authentication/01-target-namespaces/deployment_2.yaml diff --git a/Istio/06-Internal-Authentication/01-namespaces/gateway.yaml b/Istio/06-Internal-Authentication/01-target-namespaces/gateway.yaml similarity index 100% rename from Istio/06-Internal-Authentication/01-namespaces/gateway.yaml rename to Istio/06-Internal-Authentication/01-target-namespaces/gateway.yaml diff --git a/Istio/06-Internal-Authentication/02-target-service-accounts/README.md b/Istio/06-Internal-Authentication/02-target-service-accounts/README.md index 8825737..dcc2fa3 100755 --- a/Istio/06-Internal-Authentication/02-target-service-accounts/README.md +++ b/Istio/06-Internal-Authentication/02-target-service-accounts/README.md @@ -6,7 +6,7 @@ include_toc: true # Continues from [//]: # (- [01-hello_world_1_service_1_deployment](../../01-simple/01-hello_world_1_service_1_deployment)) -- [01-namespaces](../01-namespaces) +- [01-namespaces](../01-target-namespaces) > **Note:**\ > On this example there is minimal changes to the configuration to involve targeting service accounts. -- 2.47.2 From 39a7c1245038aac0fc09186cd557ada6f9fe0b32 Mon Sep 17 00:00:00 2001 From: savagebidoof Date: Wed, 26 Apr 2023 02:36:23 +0200 Subject: [PATCH 04/25] Directory renaming --- .../09-HTTPS-backend/README.md | 2 +- .../01-target-namespaces/01-namespace.yaml | 0 .../01-target-namespaces/README.md | 0 .../01-target-namespaces/authentication.yaml | 0 .../01-target-namespaces/deployment.yaml | 0 .../01-target-namespaces/deployment_2.yaml | 0 .../01-target-namespaces/gateway.yaml | 0 .../01-namespace.yaml | 0 .../01-service-accounts.yaml | 0 .../02-target-service-accounts/README.md | 0 .../authentication.yaml | 0 .../deployment.yaml | 0 .../deployment_2.yaml | 0 .../02-target-service-accounts/gateway.yaml | 0 .../06-AuthorizationPolicy/04-audit/README.md | 17 + .../04-audit/authentication.yaml | 45 +++ .../04-audit/deployment.yaml | 48 +++ .../04-audit/gateway.yaml} | 29 +- .../05-disable-mTLS}/authentication.yaml | 0 .../05-disable-mTLS}/deployment.yaml | 0 .../05-disable-mTLS}/gateway.yaml | 0 .../README.md | 12 +- Istio/__bookshelf/README.md | 1 - Istio/__bookshelf/bookinfo.yaml | 343 ------------------ 24 files changed, 128 insertions(+), 369 deletions(-) rename Istio/{06-Internal-Authentication => 06-AuthorizationPolicy}/01-target-namespaces/01-namespace.yaml (100%) rename Istio/{06-Internal-Authentication => 06-AuthorizationPolicy}/01-target-namespaces/README.md (100%) rename Istio/{06-Internal-Authentication => 06-AuthorizationPolicy}/01-target-namespaces/authentication.yaml (100%) rename Istio/{06-Internal-Authentication => 06-AuthorizationPolicy}/01-target-namespaces/deployment.yaml (100%) rename Istio/{06-Internal-Authentication => 06-AuthorizationPolicy}/01-target-namespaces/deployment_2.yaml (100%) rename Istio/{06-Internal-Authentication => 06-AuthorizationPolicy}/01-target-namespaces/gateway.yaml (100%) rename Istio/{06-Internal-Authentication => 06-AuthorizationPolicy}/02-target-service-accounts/01-namespace.yaml (100%) rename Istio/{06-Internal-Authentication => 06-AuthorizationPolicy}/02-target-service-accounts/01-service-accounts.yaml (100%) rename Istio/{06-Internal-Authentication => 06-AuthorizationPolicy}/02-target-service-accounts/README.md (100%) rename Istio/{06-Internal-Authentication => 06-AuthorizationPolicy}/02-target-service-accounts/authentication.yaml (100%) rename Istio/{06-Internal-Authentication => 06-AuthorizationPolicy}/02-target-service-accounts/deployment.yaml (100%) rename Istio/{06-Internal-Authentication => 06-AuthorizationPolicy}/02-target-service-accounts/deployment_2.yaml (100%) rename Istio/{06-Internal-Authentication => 06-AuthorizationPolicy}/02-target-service-accounts/gateway.yaml (100%) create mode 100755 Istio/06-AuthorizationPolicy/04-audit/README.md create mode 100644 Istio/06-AuthorizationPolicy/04-audit/authentication.yaml create mode 100755 Istio/06-AuthorizationPolicy/04-audit/deployment.yaml rename Istio/{__bookshelf/bookinfo-gateway.yaml => 06-AuthorizationPolicy/04-audit/gateway.yaml} (50%) rename Istio/{06-Internal-Authentication/03-disable-mTLS => 06-AuthorizationPolicy/05-disable-mTLS}/authentication.yaml (100%) rename Istio/{06-Internal-Authentication/03-disable-mTLS => 06-AuthorizationPolicy/05-disable-mTLS}/deployment.yaml (100%) rename Istio/{06-Internal-Authentication/03-disable-mTLS => 06-AuthorizationPolicy/05-disable-mTLS}/gateway.yaml (100%) rename Istio/{06-Internal-Authentication => 06-AuthorizationPolicy}/README.md (67%) delete mode 100755 Istio/__bookshelf/README.md delete mode 100755 Istio/__bookshelf/bookinfo.yaml diff --git a/Istio/02-Traffic_management/09-HTTPS-backend/README.md b/Istio/02-Traffic_management/09-HTTPS-backend/README.md index 76d945b..c236283 100644 --- a/Istio/02-Traffic_management/09-HTTPS-backend/README.md +++ b/Istio/02-Traffic_management/09-HTTPS-backend/README.md @@ -197,7 +197,7 @@ spec: ``` > **Note**:\ -> As this configuration is very board, and targets the whole namespace, I would strongly recommend referring to the following example [06-Internal-Authentication/02-target-service-accounts](../../06-Internal-Authentication/02-target-service-accounts), which shows how to target service accounts set to resources, limiting the scope of this rule set. +> As this configuration is very board, and targets the whole namespace, I would strongly recommend referring to the following example [06-Internal-Authentication/02-target-service-accounts](../../06-AuthorizationPolicy/02-target-service-accounts), which shows how to target service accounts set to resources, limiting the scope of this rule set. # Walkthrough diff --git a/Istio/06-Internal-Authentication/01-target-namespaces/01-namespace.yaml b/Istio/06-AuthorizationPolicy/01-target-namespaces/01-namespace.yaml similarity index 100% rename from Istio/06-Internal-Authentication/01-target-namespaces/01-namespace.yaml rename to Istio/06-AuthorizationPolicy/01-target-namespaces/01-namespace.yaml diff --git a/Istio/06-Internal-Authentication/01-target-namespaces/README.md b/Istio/06-AuthorizationPolicy/01-target-namespaces/README.md similarity index 100% rename from Istio/06-Internal-Authentication/01-target-namespaces/README.md rename to Istio/06-AuthorizationPolicy/01-target-namespaces/README.md diff --git a/Istio/06-Internal-Authentication/01-target-namespaces/authentication.yaml b/Istio/06-AuthorizationPolicy/01-target-namespaces/authentication.yaml similarity index 100% rename from Istio/06-Internal-Authentication/01-target-namespaces/authentication.yaml rename to Istio/06-AuthorizationPolicy/01-target-namespaces/authentication.yaml diff --git a/Istio/06-Internal-Authentication/01-target-namespaces/deployment.yaml b/Istio/06-AuthorizationPolicy/01-target-namespaces/deployment.yaml similarity index 100% rename from Istio/06-Internal-Authentication/01-target-namespaces/deployment.yaml rename to Istio/06-AuthorizationPolicy/01-target-namespaces/deployment.yaml diff --git a/Istio/06-Internal-Authentication/01-target-namespaces/deployment_2.yaml b/Istio/06-AuthorizationPolicy/01-target-namespaces/deployment_2.yaml similarity index 100% rename from Istio/06-Internal-Authentication/01-target-namespaces/deployment_2.yaml rename to Istio/06-AuthorizationPolicy/01-target-namespaces/deployment_2.yaml diff --git a/Istio/06-Internal-Authentication/01-target-namespaces/gateway.yaml b/Istio/06-AuthorizationPolicy/01-target-namespaces/gateway.yaml similarity index 100% rename from Istio/06-Internal-Authentication/01-target-namespaces/gateway.yaml rename to Istio/06-AuthorizationPolicy/01-target-namespaces/gateway.yaml diff --git a/Istio/06-Internal-Authentication/02-target-service-accounts/01-namespace.yaml b/Istio/06-AuthorizationPolicy/02-target-service-accounts/01-namespace.yaml similarity index 100% rename from Istio/06-Internal-Authentication/02-target-service-accounts/01-namespace.yaml rename to Istio/06-AuthorizationPolicy/02-target-service-accounts/01-namespace.yaml diff --git a/Istio/06-Internal-Authentication/02-target-service-accounts/01-service-accounts.yaml b/Istio/06-AuthorizationPolicy/02-target-service-accounts/01-service-accounts.yaml similarity index 100% rename from Istio/06-Internal-Authentication/02-target-service-accounts/01-service-accounts.yaml rename to Istio/06-AuthorizationPolicy/02-target-service-accounts/01-service-accounts.yaml diff --git a/Istio/06-Internal-Authentication/02-target-service-accounts/README.md b/Istio/06-AuthorizationPolicy/02-target-service-accounts/README.md similarity index 100% rename from Istio/06-Internal-Authentication/02-target-service-accounts/README.md rename to Istio/06-AuthorizationPolicy/02-target-service-accounts/README.md diff --git a/Istio/06-Internal-Authentication/02-target-service-accounts/authentication.yaml b/Istio/06-AuthorizationPolicy/02-target-service-accounts/authentication.yaml similarity index 100% rename from Istio/06-Internal-Authentication/02-target-service-accounts/authentication.yaml rename to Istio/06-AuthorizationPolicy/02-target-service-accounts/authentication.yaml diff --git a/Istio/06-Internal-Authentication/02-target-service-accounts/deployment.yaml b/Istio/06-AuthorizationPolicy/02-target-service-accounts/deployment.yaml similarity index 100% rename from Istio/06-Internal-Authentication/02-target-service-accounts/deployment.yaml rename to Istio/06-AuthorizationPolicy/02-target-service-accounts/deployment.yaml diff --git a/Istio/06-Internal-Authentication/02-target-service-accounts/deployment_2.yaml b/Istio/06-AuthorizationPolicy/02-target-service-accounts/deployment_2.yaml similarity index 100% rename from Istio/06-Internal-Authentication/02-target-service-accounts/deployment_2.yaml rename to Istio/06-AuthorizationPolicy/02-target-service-accounts/deployment_2.yaml diff --git a/Istio/06-Internal-Authentication/02-target-service-accounts/gateway.yaml b/Istio/06-AuthorizationPolicy/02-target-service-accounts/gateway.yaml similarity index 100% rename from Istio/06-Internal-Authentication/02-target-service-accounts/gateway.yaml rename to Istio/06-AuthorizationPolicy/02-target-service-accounts/gateway.yaml diff --git a/Istio/06-AuthorizationPolicy/04-audit/README.md b/Istio/06-AuthorizationPolicy/04-audit/README.md new file mode 100755 index 0000000..a0296bc --- /dev/null +++ b/Istio/06-AuthorizationPolicy/04-audit/README.md @@ -0,0 +1,17 @@ + +# Based on + +Resources: + +- [01-Simple/01-hello_world_1_service_1_deployment](../../01-Simple/01-hello_world_1_service_1_deployment) + +AuthorizationPolicies: + +- [01-target-namespaces](../01-target-namespaces) + +# Description + +This example aims to trigger Audit rules, and where to see these events. + + +https://istio.io/latest/docs/reference/config/security/authorization-policy/ \ No newline at end of file diff --git a/Istio/06-AuthorizationPolicy/04-audit/authentication.yaml b/Istio/06-AuthorizationPolicy/04-audit/authentication.yaml new file mode 100644 index 0000000..4f5c20a --- /dev/null +++ b/Istio/06-AuthorizationPolicy/04-audit/authentication.yaml @@ -0,0 +1,45 @@ +# Deny all requests to namespace foo +apiVersion: security.istio.io/v1beta1 +kind: AuthorizationPolicy +metadata: + name: allow-nothing + namespace: foo +spec: + {} +--- +# Deny all requests to namespace default +apiVersion: security.istio.io/v1beta1 +kind: AuthorizationPolicy +metadata: + name: allow-nothing + namespace: default +spec: + {} +--- +apiVersion: security.istio.io/v1beta1 +kind: AuthorizationPolicy +metadata: + name: allow-from-istio-system + namespace: foo +spec: + action: ALLOW + rules: + - from: + - source: + namespaces: ["istio-system"] +--- +apiVersion: security.istio.io/v1beta1 +kind: AuthorizationPolicy +metadata: + name: allow-head-from-default + namespace: foo +spec: + action: ALLOW + rules: + - from: + - source: + namespaces: ["default"] + to: + - operation: + methods: ["HEAD"] + notPaths: ["/secret*"] \ No newline at end of file diff --git a/Istio/06-AuthorizationPolicy/04-audit/deployment.yaml b/Istio/06-AuthorizationPolicy/04-audit/deployment.yaml new file mode 100755 index 0000000..36e6b76 --- /dev/null +++ b/Istio/06-AuthorizationPolicy/04-audit/deployment.yaml @@ -0,0 +1,48 @@ +# https://github.com/istio/istio/blob/master/samples/helloworld/helloworld.yaml +apiVersion: v1 +kind: Service +metadata: + name: helloworld + labels: + app: helloworld + service: helloworld +spec: + ports: + - port: 80 + name: http + selector: + app: helloworld +--- +#apiVersion: v1 +#kind: ServiceAccount +#metadata: +# name: istio-helloworld +# labels: +# account: +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: helloworld-nginx + labels: + app: helloworld +spec: + replicas: 1 + selector: + matchLabels: + app: helloworld + template: + metadata: + labels: + app: helloworld + spec: +# serviceAccountName: istio-helloworld + containers: + - name: helloworld + image: nginx + resources: + requests: + cpu: "100m" + imagePullPolicy: IfNotPresent #Always + ports: + - containerPort: 80 diff --git a/Istio/__bookshelf/bookinfo-gateway.yaml b/Istio/06-AuthorizationPolicy/04-audit/gateway.yaml similarity index 50% rename from Istio/__bookshelf/bookinfo-gateway.yaml rename to Istio/06-AuthorizationPolicy/04-audit/gateway.yaml index 57fb37b..252a01e 100755 --- a/Istio/__bookshelf/bookinfo-gateway.yaml +++ b/Istio/06-AuthorizationPolicy/04-audit/gateway.yaml @@ -1,8 +1,7 @@ - apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: - name: bookinfo-gateway + name: helloworld-gateway spec: selector: istio: ingressgateway # use istio default controller @@ -17,36 +16,20 @@ spec: apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: - name: bookinfo + name: helloworld-vs spec: hosts: - "*" gateways: - - bookinfo-gateway + - helloworld-gateway http: - - match: - - uri: - exact: /productpage - - uri: - prefix: /static - - uri: - exact: /login - - uri: - exact: /logout - - uri: - prefix: /api/v1/products - route: - - destination: - host: productpage - port: - number: 9080 - match: - uri: exact: /helloworld route: - destination: - host: productpage + host: helloworld port: - number: 9080 + number: 80 rewrite: - uri: "/productpage" + uri: "/" \ No newline at end of file diff --git a/Istio/06-Internal-Authentication/03-disable-mTLS/authentication.yaml b/Istio/06-AuthorizationPolicy/05-disable-mTLS/authentication.yaml similarity index 100% rename from Istio/06-Internal-Authentication/03-disable-mTLS/authentication.yaml rename to Istio/06-AuthorizationPolicy/05-disable-mTLS/authentication.yaml diff --git a/Istio/06-Internal-Authentication/03-disable-mTLS/deployment.yaml b/Istio/06-AuthorizationPolicy/05-disable-mTLS/deployment.yaml similarity index 100% rename from Istio/06-Internal-Authentication/03-disable-mTLS/deployment.yaml rename to Istio/06-AuthorizationPolicy/05-disable-mTLS/deployment.yaml diff --git a/Istio/06-Internal-Authentication/03-disable-mTLS/gateway.yaml b/Istio/06-AuthorizationPolicy/05-disable-mTLS/gateway.yaml similarity index 100% rename from Istio/06-Internal-Authentication/03-disable-mTLS/gateway.yaml rename to Istio/06-AuthorizationPolicy/05-disable-mTLS/gateway.yaml diff --git a/Istio/06-Internal-Authentication/README.md b/Istio/06-AuthorizationPolicy/README.md similarity index 67% rename from Istio/06-Internal-Authentication/README.md rename to Istio/06-AuthorizationPolicy/README.md index dc7c773..b8fa14d 100644 --- a/Istio/06-Internal-Authentication/README.md +++ b/Istio/06-AuthorizationPolicy/README.md @@ -10,7 +10,17 @@ - Audit / logs (should be the 3th) +- disable mTLS (4th) JWT seems important, refer to source.requestPrincipals -https://istio.io/latest/docs/tasks/security/authentication/ \ No newline at end of file +https://istio.io/latest/docs/tasks/security/authentication/ + + + +Per deployment: +```yaml + selector: + matchLabels: + app: myapi +``` \ No newline at end of file diff --git a/Istio/__bookshelf/README.md b/Istio/__bookshelf/README.md deleted file mode 100755 index 4ce3894..0000000 --- a/Istio/__bookshelf/README.md +++ /dev/null @@ -1 +0,0 @@ -# Example from istio, storing it for testing purposes \ No newline at end of file diff --git a/Istio/__bookshelf/bookinfo.yaml b/Istio/__bookshelf/bookinfo.yaml deleted file mode 100755 index 4de3a21..0000000 --- a/Istio/__bookshelf/bookinfo.yaml +++ /dev/null @@ -1,343 +0,0 @@ -# Copyright Istio Authors -# -# 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. - -################################################################################################## -# This file defines the services, service accounts, and deployments for the Bookinfo sample. -# -# To apply all 4 Bookinfo services, their corresponding service accounts, and deployments: -# -# kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml -# -# Alternatively, you can deploy any resource separately: -# -# kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml -l service=reviews # reviews Service -# kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml -l account=reviews # reviews ServiceAccount -# kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml -l app=reviews,version=v3 # reviews-v3 Deployment -################################################################################################## - -################################################################################################## -# Details service -################################################################################################## -apiVersion: v1 -kind: Service -metadata: - name: details - labels: - app: details - service: details -spec: - ports: - - port: 9080 - name: http - selector: - app: details ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: bookinfo-details - labels: - account: details ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: details-v1 - labels: - app: details - version: v1 -spec: - replicas: 1 - selector: - matchLabels: - app: details - version: v1 - template: - metadata: - labels: - app: details - version: v1 - spec: - serviceAccountName: bookinfo-details - containers: - - name: details - image: docker.io/istio/examples-bookinfo-details-v1:1.17.0 - imagePullPolicy: IfNotPresent - ports: - - containerPort: 9080 - securityContext: - runAsUser: 1000 ---- -################################################################################################## -# Ratings service -################################################################################################## -apiVersion: v1 -kind: Service -metadata: - name: ratings - labels: - app: ratings - service: ratings -spec: - ports: - - port: 9080 - name: http - selector: - app: ratings ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: bookinfo-ratings - labels: - account: ratings ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: ratings-v1 - labels: - app: ratings - version: v1 -spec: - replicas: 1 - selector: - matchLabels: - app: ratings - version: v1 - template: - metadata: - labels: - app: ratings - version: v1 - spec: - serviceAccountName: bookinfo-ratings - containers: - - name: ratings - image: docker.io/istio/examples-bookinfo-ratings-v1:1.17.0 - imagePullPolicy: IfNotPresent - ports: - - containerPort: 9080 - securityContext: - runAsUser: 1000 ---- -################################################################################################## -# Reviews service -################################################################################################## -apiVersion: v1 -kind: Service -metadata: - name: reviews - labels: - app: reviews - service: reviews -spec: - ports: - - port: 9080 - name: http - selector: - app: reviews ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: bookinfo-reviews - labels: - account: reviews ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: reviews-v1 - labels: - app: reviews - version: v1 -spec: - replicas: 1 - selector: - matchLabels: - app: reviews - version: v1 - template: - metadata: - labels: - app: reviews - version: v1 - spec: - serviceAccountName: bookinfo-reviews - containers: - - name: reviews - image: docker.io/istio/examples-bookinfo-reviews-v1:1.17.0 - imagePullPolicy: IfNotPresent - env: - - name: LOG_DIR - value: "/tmp/logs" - ports: - - containerPort: 9080 - volumeMounts: - - name: tmp - mountPath: /tmp - - name: wlp-output - mountPath: /opt/ibm/wlp/output - securityContext: - runAsUser: 1000 - volumes: - - name: wlp-output - emptyDir: {} - - name: tmp - emptyDir: {} ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: reviews-v2 - labels: - app: reviews - version: v2 -spec: - replicas: 1 - selector: - matchLabels: - app: reviews - version: v2 - template: - metadata: - labels: - app: reviews - version: v2 - spec: - serviceAccountName: bookinfo-reviews - containers: - - name: reviews - image: docker.io/istio/examples-bookinfo-reviews-v2:1.17.0 - imagePullPolicy: IfNotPresent - env: - - name: LOG_DIR - value: "/tmp/logs" - ports: - - containerPort: 9080 - volumeMounts: - - name: tmp - mountPath: /tmp - - name: wlp-output - mountPath: /opt/ibm/wlp/output - securityContext: - runAsUser: 1000 - volumes: - - name: wlp-output - emptyDir: {} - - name: tmp - emptyDir: {} ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: reviews-v3 - labels: - app: reviews - version: v3 -spec: - replicas: 1 - selector: - matchLabels: - app: reviews - version: v3 - template: - metadata: - labels: - app: reviews - version: v3 - spec: - serviceAccountName: bookinfo-reviews - containers: - - name: reviews - image: docker.io/istio/examples-bookinfo-reviews-v3:1.17.0 - imagePullPolicy: IfNotPresent - env: - - name: LOG_DIR - value: "/tmp/logs" - ports: - - containerPort: 9080 - volumeMounts: - - name: tmp - mountPath: /tmp - - name: wlp-output - mountPath: /opt/ibm/wlp/output - securityContext: - runAsUser: 1000 - volumes: - - name: wlp-output - emptyDir: {} - - name: tmp - emptyDir: {} ---- -################################################################################################## -# Productpage services -################################################################################################## -apiVersion: v1 -kind: Service -metadata: - name: productpage - labels: - app: productpage - service: productpage -spec: - ports: - - port: 9080 - name: http - selector: - app: productpage ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: bookinfo-productpage - labels: - account: productpage ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: productpage-v1 - labels: - app: productpage - version: v1 -spec: - replicas: 1 - selector: - matchLabels: - app: productpage - version: v1 - template: - metadata: - labels: - app: productpage - version: v1 - spec: - serviceAccountName: bookinfo-productpage - containers: - - name: productpage - image: docker.io/istio/examples-bookinfo-productpage-v1:1.17.0 - imagePullPolicy: IfNotPresent - ports: - - containerPort: 9080 - volumeMounts: - - name: tmp - mountPath: /tmp - securityContext: - runAsUser: 1000 - volumes: - - name: tmp - emptyDir: {} ---- -- 2.47.2 From d7d39094997a043178828c48753d058b75a20610 Mon Sep 17 00:00:00 2001 From: savagebidoof Date: Wed, 26 Apr 2023 02:36:34 +0200 Subject: [PATCH 05/25] Directory renaming --- .../05-disable-mTLS}/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename Istio/{06-Internal-Authentication/03-disable-mTLS => 06-AuthorizationPolicy/05-disable-mTLS}/README.md (71%) diff --git a/Istio/06-Internal-Authentication/03-disable-mTLS/README.md b/Istio/06-AuthorizationPolicy/05-disable-mTLS/README.md similarity index 71% rename from Istio/06-Internal-Authentication/03-disable-mTLS/README.md rename to Istio/06-AuthorizationPolicy/05-disable-mTLS/README.md index 1aa4192..d7004d0 100644 --- a/Istio/06-Internal-Authentication/03-disable-mTLS/README.md +++ b/Istio/06-AuthorizationPolicy/05-disable-mTLS/README.md @@ -1,6 +1,6 @@ # Based on -- [02-Traffic_management/09-HTTPS-backend (pending document)](../../02-Traffic_management/09-HTTPS-backend%20(pending%20document)) +- [02-Traffic_management/09-HTTPS-backend (pending document)](../../02-Traffic_management/09-HTTPS-backend) On the previous example only uses a HTTPS backend, here boards both HTTP and HTTPS backends. -- 2.47.2 From ab5b775534b6f294e258a22df73c3a474236877c Mon Sep 17 00:00:00 2001 From: savagebidoof Date: Thu, 27 Apr 2023 03:24:52 +0200 Subject: [PATCH 06/25] Deleted audit, from my understanding I need some sort of stackdriver and dont' wanna bother with it rn --- Istio/06-AuthorizationPolicy/04-audit/README.md | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100755 Istio/06-AuthorizationPolicy/04-audit/README.md diff --git a/Istio/06-AuthorizationPolicy/04-audit/README.md b/Istio/06-AuthorizationPolicy/04-audit/README.md deleted file mode 100755 index a0296bc..0000000 --- a/Istio/06-AuthorizationPolicy/04-audit/README.md +++ /dev/null @@ -1,17 +0,0 @@ - -# Based on - -Resources: - -- [01-Simple/01-hello_world_1_service_1_deployment](../../01-Simple/01-hello_world_1_service_1_deployment) - -AuthorizationPolicies: - -- [01-target-namespaces](../01-target-namespaces) - -# Description - -This example aims to trigger Audit rules, and where to see these events. - - -https://istio.io/latest/docs/reference/config/security/authorization-policy/ \ No newline at end of file -- 2.47.2 From 2be63317fa4d8e33410322874a77e6e0b153607c Mon Sep 17 00:00:00 2001 From: savagebidoof Date: Thu, 27 Apr 2023 03:25:22 +0200 Subject: [PATCH 07/25] fixed spelling mistake --- .../06-AuthorizationPolicy/02-target-service-accounts/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Istio/06-AuthorizationPolicy/02-target-service-accounts/README.md b/Istio/06-AuthorizationPolicy/02-target-service-accounts/README.md index dcc2fa3..0018395 100755 --- a/Istio/06-AuthorizationPolicy/02-target-service-accounts/README.md +++ b/Istio/06-AuthorizationPolicy/02-target-service-accounts/README.md @@ -6,7 +6,7 @@ include_toc: true # Continues from [//]: # (- [01-hello_world_1_service_1_deployment](../../01-simple/01-hello_world_1_service_1_deployment)) -- [01-namespaces](../01-target-namespaces) +- [01-target-namespaces](../01-target-namespaces) > **Note:**\ > On this example there is minimal changes to the configuration to involve targeting service accounts. -- 2.47.2 From 9ee02f77136186b6d6175eb6641fe8a008b97150 Mon Sep 17 00:00:00 2001 From: savagebidoof Date: Thu, 27 Apr 2023 03:26:32 +0200 Subject: [PATCH 08/25] Added example about targeting deployments through the use of selectors (targeting labels) --- .../03-target-deployments/01-namespace.yaml | 7 + .../03-target-deployments/README.md | 382 ++++++++++++++++++ .../authentication.yaml | 22 +- .../deployment.yaml | 14 +- .../gateway.yaml | 14 +- 5 files changed, 414 insertions(+), 25 deletions(-) create mode 100755 Istio/06-AuthorizationPolicy/03-target-deployments/01-namespace.yaml create mode 100755 Istio/06-AuthorizationPolicy/03-target-deployments/README.md rename Istio/06-AuthorizationPolicy/{04-audit => 03-target-deployments}/authentication.yaml (70%) rename Istio/06-AuthorizationPolicy/{04-audit => 03-target-deployments}/deployment.yaml (67%) rename Istio/06-AuthorizationPolicy/{04-audit => 03-target-deployments}/gateway.yaml (66%) diff --git a/Istio/06-AuthorizationPolicy/03-target-deployments/01-namespace.yaml b/Istio/06-AuthorizationPolicy/03-target-deployments/01-namespace.yaml new file mode 100755 index 0000000..e7a45bc --- /dev/null +++ b/Istio/06-AuthorizationPolicy/03-target-deployments/01-namespace.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: foo + labels: + istio-injection: "enabled" +--- \ No newline at end of file diff --git a/Istio/06-AuthorizationPolicy/03-target-deployments/README.md b/Istio/06-AuthorizationPolicy/03-target-deployments/README.md new file mode 100755 index 0000000..972ae00 --- /dev/null +++ b/Istio/06-AuthorizationPolicy/03-target-deployments/README.md @@ -0,0 +1,382 @@ +--- +gitea: none +include_toc: true +--- + +# Continues from + +- [01-target-namespaces](../01-target-namespaces) + +> **Note:**\ +> On this example there is minimal changes to the configuration to involve targeting the deployment resources through label filtering. + +## Description + +Bla bla bla + +In this example we will be targeting the labels set to the deployments, while keeping part of the previous AuthorizationPolicy configuration to maintain its behavior. + +[//]: # (For such, it's important to check the labels set in the Istio ingress that we will be using.) + +[//]: # () +[//]: # (On my case, in the gateway I will be targeting/using the Istio ingress `ingressgateway`.) + +[//]: # () +[//]: # (I would **strongly** recommend checking yours through the following command, as to proceed we should be aware of which are our possible labels options.) + +[//]: # () +[//]: # (```shell) + +[//]: # (kubectl get deployments -A -l istio=ingressgateway -o jsonpath='{.items[].spec.template.metadata.labels}'| jq) + +[//]: # (```) + +[//]: # (```json) + +[//]: # ({) + +[//]: # ( "app": "istio-ingressgateway",) + +[//]: # ( "chart": "gateways",) + +[//]: # ( "heritage": "Tiller",) + +[//]: # ( "install.operator.istio.io/owning-resource": "unknown",) + +[//]: # ( "istio": "ingressgateway",) + +[//]: # ( "istio.io/rev": "default",) + +[//]: # ( "operator.istio.io/component": "IngressGateways",) + +[//]: # ( "release": "istio",) + +[//]: # ( "service.istio.io/canonical-name": "istio-ingressgateway",) + +[//]: # ( "service.istio.io/canonical-revision": "latest",) + +[//]: # ( "sidecar.istio.io/inject": "false") + +[//]: # (}) + +[//]: # (```) + +[//]: # () +[//]: # (Based on the list displayed, I would suggest focusing on the following options:) + +[//]: # () +[//]: # (```json) + +[//]: # ({) + +[//]: # ("istio": "ingressgateway",) + +[//]: # ("operator.istio.io/component": "IngressGateways",) + +[//]: # ("app": "istio-ingressgateway",) + +[//]: # (}) + +[//]: # (```) + +[//]: # () +[//]: # (The label `"service.istio.io/canonical-revision": "latest"` could be reasonable to use, in very specific situations, as depending on the implementation/environment or procedures that we might use in the future, it's something to keep in mind in case of being configured.) + +[//]: # () +[//]: # () + + +# Configuration + +## AuthorizationPolicy +### Allow nothing (deny all not matched) + +#### default namespace + +If the action is not specified, it will deploy the rule as "ALLOW". + +Here we are deploying a rule that allows the traffic that it matches, yet as it has no conditions, it will never match. + +```yaml +# Deny all requests to namespace default +apiVersion: security.istio.io/v1beta1 +kind: AuthorizationPolicy +metadata: + name: allow-nothing + namespace: default +``` + +Citing the [Authorization Policy documentation from Istio](https://istio.io/latest/docs/reference/config/security/authorization-policy), regarding the evaluation behavior of these rules: + + 1. If there are any CUSTOM policies that match the request, evaluate and deny the request if the evaluation result is deny. + 2. If there are any DENY policies that match the request, deny the request. + 3. If there are no ALLOW policies for the workload, allow the request. + 4. If any of the ALLOW policies match the request, allow the request. + 5. Deny the request. + +On this scenario, as we don't have any DENY or CUSTOM rule, we skip right into the 3rd scenario. + +This rule is being applied to the workload (due being a rule that affects the whole namespace), and for such the 3rd scenario is not being applied either. + +On the 4rth, scenario, as the rule deployed, even if it's on ALLOW mode, has no conditions, it won't allow the traffic either. + +And finally, as any of the above scenarios allowed the traffic of the request, it ends getting denied. + +For such, the creation of this "empty" rule, has set the authorization mode on the not explicitly allowed request to "DENY ALL". + +#### foo namespace + +Same behavior as above, this time applied to the namespace `foo` + +```yaml +# Deny all requests to namespace foo +apiVersion: security.istio.io/v1beta1 +kind: AuthorizationPolicy +metadata: + name: allow-nothing + namespace: foo +spec: + {} +``` + +## ALLOW + + +#### byeworld-allow-from-istio-system + +As we have a service deployed, and the traffic will come through the Istio Load Balancer (at least on my environment). I have set a rule that will allow all the traffic coming from a resource located in the namespace `istio-system`. + +This rule will be applied to the deployments that have set the following label `app: byeworld`, and deployed in the namespace `istio-system`. + +> **Note:**\ +> As this rule will be deployed in the root namespace `istio-system` (it's my root namespace in **MY** environment, review your Istio configuration to ensure which is **YOUR** root namespace).\ +> By deploying the rule in the root namespace, it gets applied to all namespaces, I have set this to ensure that there are minor differences on the configuration in comparison on which example this is based on. As well this will allow us to confirm tha the labels are being applied correctly. + +```yaml +apiVersion: security.istio.io/v1beta1 +kind: AuthorizationPolicy +metadata: + name: byeworld-allow-from-istio-system + namespace: istio-system +spec: + selector: + matchLabels: + app: byeworld + action: ALLOW + rules: + - from: + - source: + namespaces: ["istio-system"] +``` + +#### byeworld-allow-head-from-default + +I have set a new rule, that will allow the traffic coming from the namespace `default`, as long the method used is `HEAD` and is not targeting the path `/secret`. + +This rule will be applied to the deployments that have set the following label `app: byeworld`, and deployed in the namespace `istio-system`. + +> **Note:**\ +> This will be deployed in the root namespace `istio-system` (it's my root namespace in **MY** environment, review your Istio configuration to ensure which is **YOUR** root namespace).\ +> By deploying the rule in the root namespace, it gets applied to all namespaces, I have set this to ensure that there are minor differences on the configuration in comparison on which example this is based on. As well this will allow us to confirm tha the labels are being applied correctly. + +```yaml +apiVersion: security.istio.io/v1beta1 +kind: AuthorizationPolicy +metadata: + name: byeworld-allow-head-from-default + namespace: istio-system +spec: + action: ALLOW + selector: + matchLabels: + app: byeworld + rules: + - from: + - source: + namespaces: ["default"] + to: + - operation: + methods: ["HEAD"] + notPaths: ["/secret*"] +``` + + +# Walkthrough + +## Deploy the resources + +```shell +kubectl apply -f ./ +``` +```text +namespace/foo created +authorizationpolicy.security.istio.io/allow-nothing created +authorizationpolicy.security.istio.io/byeworld-allow-from-istio-system created +authorizationpolicy.security.istio.io/byeworld-allow-head-from-default created +service/helloworld created +deployment.apps/helloworld-nginx created +service/byeworld created +deployment.apps/byeworld-nginx created +gateway.networking.istio.io/helloworld-gateway created +virtualservice.networking.istio.io/helloworld-vs create +``` + +## Test resources + +### Curl / LB requests / requests from external traffic + +#### Get LB IP + +```shell +kubectl get svc istio-ingressgateway -n istio-system +``` +```text +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +istio-ingressgateway LoadBalancer 10.97.47.216 192.168.1.50 15021:31316/TCP,80:32012/TCP,443:32486/TCP 39h +``` + +#### helloworld + +Due to the rule `allow-nothing` created on the namespace `istio-system`, which is being applied to all the namespaces, we are not hitting any rule that explicitly allows us, and for such, the traffic is being denied. + +For such we receive the status code `403` (**Forbidden**) + +```shell +curl 192.168.1.50/helloworld -I +``` +```text +HTTP/1.1 403 Forbidden +content-length: 19 +content-type: text/plain +date: Thu, 27 Apr 2023 01:20:06 GMT +server: istio-envoy +x-envoy-upstream-service-time: 108 +``` + +#### byeworld + +As we created the rule `byeworld-allow-from-istio-system` created in the namespace `foo`, which allows all the traffic coming from a resource located in the namespace `istio-system`, and the load balancer used is located in the namespace `istio-system`, the traffic is allowed. + +For such we receive the code `200`. + +```shell +curl 192.168.1.50/byeworld --head +``` +```text +HTTP/1.1 200 OK +server: istio-envoy +date: Thu, 27 Apr 2023 01:20:49 GMT +content-type: text/html +content-length: 615 +last-modified: Tue, 28 Mar 2023 15:01:54 GMT +etag: "64230162-267" +accept-ranges: bytes +x-envoy-upstream-service-time: 104 +``` + +### Connectivity between the deployments + +> **NOTE:**\ +> The command `curl`, when uses the flag `--head` or `-I`, the request sent will be a `HEAD` request. +> +> It's important to be aware of that due the rule configured, where one of the targets was the method used, specifically targeted the method `HEAD`. +> +> On this example, all request will be done with the method `HEAD` unless specified otherwise. + +#### helloworld towards byeworld + +It works. + +Due to the rule `byeworld-allow-head-from-default` deployed on the namespace `foo`, which allowed the traffic coming from the namespace `default` as long it used the method `HEAD` and wasn't targeting the path `/secret`, the request is allowed. + + +```shell +kubectl exec -i -t "$(kubectl get pod -l app=helloworld | tail -n 1 | awk '{print $1}')" -- curl http://byeworld.foo.svc.cluster.local:9090 --head +``` +```text +HTTP/1.1 200 OK +server: envoy +date: Thu, 27 Apr 2023 01:20:58 GMT +content-type: text/html +content-length: 615 +last-modified: Tue, 28 Mar 2023 15:01:54 GMT +etag: "64230162-267" +accept-ranges: bytes +x-envoy-upstream-service-time: 86 +``` + +#### helloworld towards byeworld (GET REQUEST) + +This example is made on base on the last comand executed, where the request sent uses the `HEAD` method. + +On this example the flag `--head` is removed, which causes the command `curl` to send a request of method `GET`. + +As the rule created required the method to be `HEAD`, it causes the request to not be allowed, and finally as there are no rules that allow this request, it results in failure. + +```shell +kubectl exec -i -t "$(kubectl get pod -l app=helloworld | tail -n 1 | awk '{print $1}')" -- curl http://byeworld.foo.svc.cluster.local:9090 +``` +```text +RBAC: access denied% +``` + +#### byeworld towards helloworld + +It fails. + +As expected, like when accessing through the Load Balancer, we receive the status code `403` (**Forbidden**). + +The `HEAD` request is irrelevant on this scenario, yet using it as I like this output more. + +```shell +kubectl exec -i -n foo -t "$(kubectl get pod -n foo -l app=byeworld | tail -n 1 | awk '{print $1}')" -- curl http://helloworld.default.svc.cluster.local:8080 --head +``` +```text +HTTP/1.1 403 Forbidden +content-length: 19 +content-type: text/plain +date: Thu, 27 Apr 2023 01:21:10 GMT +server: envoy +x-envoy-upstream-service-time: 96 +``` + +#### helloworld towards byeworld/secret + +Due to the configuration set on the rule `byeworld-allow-head-from-default`, one of the conditions for it to allow the traffic, was to not access the path/match the prefix expression `/secret*`. + +This causes the traffic to not be allowed. + +```shell +kubectl exec -i -t "$(kubectl get pod -l app=helloworld | tail -n 1 | awk '{print $1}')" -- curl http://byeworld.foo.svc.cluster.local:9090/secret --head +``` +```text +HTTP/1.1 403 Forbidden +content-length: 19 +content-type: text/plain +date: Thu, 27 Apr 2023 01:21:18 GMT +server: envoy +x-envoy-upstream-service-time: 3 +``` + + +#### helloworld towards byeworld/not-found + +On this example, we can notice how even if the request was allowed due meeting all the requirements, it still results in the error code `404` (Not Found). + +This 404 error is raised by the destination service, yet before being able to handle such request, firstly the traffic required to be allowed, meaning that even if we target as a destination path a non-existent resource, we will need to match the requirements for the traffic to be allowed. + +```shell +kubectl exec -i -t "$(kubectl get pod -l app=helloworld | tail -n 1 | awk '{print $1}')" -- curl http://byeworld.foo.svc.cluster.local:9090/not-found --head +``` +```text +HTTP/1.1 404 Not Found +server: envoy +date: Thu, 27 Apr 2023 01:21:31 GMT +content-type: text/html +content-length: 153 +x-envoy-upstream-service-time: 56 +``` + +# Links of interest + +- https://istio.io/latest/docs/reference/config/security/authorization-policy/ \ No newline at end of file diff --git a/Istio/06-AuthorizationPolicy/04-audit/authentication.yaml b/Istio/06-AuthorizationPolicy/03-target-deployments/authentication.yaml similarity index 70% rename from Istio/06-AuthorizationPolicy/04-audit/authentication.yaml rename to Istio/06-AuthorizationPolicy/03-target-deployments/authentication.yaml index 4f5c20a..02963e4 100644 --- a/Istio/06-AuthorizationPolicy/04-audit/authentication.yaml +++ b/Istio/06-AuthorizationPolicy/03-target-deployments/authentication.yaml @@ -1,11 +1,3 @@ -# Deny all requests to namespace foo -apiVersion: security.istio.io/v1beta1 -kind: AuthorizationPolicy -metadata: - name: allow-nothing - namespace: foo -spec: - {} --- # Deny all requests to namespace default apiVersion: security.istio.io/v1beta1 @@ -19,9 +11,12 @@ spec: apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: - name: allow-from-istio-system - namespace: foo + name: byeworld-allow-from-istio-system + namespace: istio-system spec: + selector: + matchLabels: + app: byeworld action: ALLOW rules: - from: @@ -31,10 +26,13 @@ spec: apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: - name: allow-head-from-default - namespace: foo + name: byeworld-allow-head-from-default + namespace: istio-system spec: action: ALLOW + selector: + matchLabels: + app: byeworld rules: - from: - source: diff --git a/Istio/06-AuthorizationPolicy/04-audit/deployment.yaml b/Istio/06-AuthorizationPolicy/03-target-deployments/deployment.yaml similarity index 67% rename from Istio/06-AuthorizationPolicy/04-audit/deployment.yaml rename to Istio/06-AuthorizationPolicy/03-target-deployments/deployment.yaml index 36e6b76..0fb81b3 100755 --- a/Istio/06-AuthorizationPolicy/04-audit/deployment.yaml +++ b/Istio/06-AuthorizationPolicy/03-target-deployments/deployment.yaml @@ -1,4 +1,3 @@ -# https://github.com/istio/istio/blob/master/samples/helloworld/helloworld.yaml apiVersion: v1 kind: Service metadata: @@ -8,18 +7,12 @@ metadata: service: helloworld spec: ports: - - port: 80 + - port: 8080 name: http + targetPort: 80 selector: app: helloworld --- -#apiVersion: v1 -#kind: ServiceAccount -#metadata: -# name: istio-helloworld -# labels: -# account: ---- apiVersion: apps/v1 kind: Deployment metadata: @@ -36,13 +29,12 @@ spec: labels: app: helloworld spec: -# serviceAccountName: istio-helloworld containers: - name: helloworld image: nginx resources: requests: cpu: "100m" - imagePullPolicy: IfNotPresent #Always + imagePullPolicy: IfNotPresent ports: - containerPort: 80 diff --git a/Istio/06-AuthorizationPolicy/04-audit/gateway.yaml b/Istio/06-AuthorizationPolicy/03-target-deployments/gateway.yaml similarity index 66% rename from Istio/06-AuthorizationPolicy/04-audit/gateway.yaml rename to Istio/06-AuthorizationPolicy/03-target-deployments/gateway.yaml index 252a01e..a481920 100755 --- a/Istio/06-AuthorizationPolicy/04-audit/gateway.yaml +++ b/Istio/06-AuthorizationPolicy/03-target-deployments/gateway.yaml @@ -28,8 +28,18 @@ spec: exact: /helloworld route: - destination: - host: helloworld + host: helloworld.default.svc.cluster.local port: - number: 80 + number: 8080 + rewrite: + uri: "/" + - match: + - uri: + exact: /byeworld + route: + - destination: + host: byeworld.foo.svc.cluster.local + port: + number: 9090 rewrite: uri: "/" \ No newline at end of file -- 2.47.2 From 20d69656fd6bb46b27d5ca54463c747b0fa59b30 Mon Sep 17 00:00:00 2001 From: savagebidoof Date: Thu, 27 Apr 2023 03:26:39 +0200 Subject: [PATCH 09/25] Added example about targeting deployments through the use of selectors (targeting labels) --- .../03-target-deployments/deployment_2.yaml | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100755 Istio/06-AuthorizationPolicy/03-target-deployments/deployment_2.yaml diff --git a/Istio/06-AuthorizationPolicy/03-target-deployments/deployment_2.yaml b/Istio/06-AuthorizationPolicy/03-target-deployments/deployment_2.yaml new file mode 100755 index 0000000..69a8412 --- /dev/null +++ b/Istio/06-AuthorizationPolicy/03-target-deployments/deployment_2.yaml @@ -0,0 +1,42 @@ +apiVersion: v1 +kind: Service +metadata: + name: byeworld + labels: + app: byeworld + service: byeworld + namespace: foo +spec: + ports: + - port: 9090 + name: http + targetPort: 80 + selector: + app: byeworld +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: byeworld-nginx + labels: + app: byeworld + namespace: foo +spec: + replicas: 1 + selector: + matchLabels: + app: byeworld + template: + metadata: + labels: + app: byeworld + spec: + containers: + - name: byeworld + image: nginx + resources: + requests: + cpu: "100m" + imagePullPolicy: IfNotPresent + ports: + - containerPort: 80 -- 2.47.2 From 1d10285d3d5e9c69046219cda1832bc6c801656c Mon Sep 17 00:00:00 2001 From: savagebidoof Date: Thu, 27 Apr 2023 03:26:58 +0200 Subject: [PATCH 10/25] added "GET LB IP" step --- .../12-HTTP-to-HTTPS-traffic-redirect/README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Istio/02-Traffic_management/12-HTTP-to-HTTPS-traffic-redirect/README.md b/Istio/02-Traffic_management/12-HTTP-to-HTTPS-traffic-redirect/README.md index 631359d..fc7e371 100644 --- a/Istio/02-Traffic_management/12-HTTP-to-HTTPS-traffic-redirect/README.md +++ b/Istio/02-Traffic_management/12-HTTP-to-HTTPS-traffic-redirect/README.md @@ -130,6 +130,16 @@ gateway.networking.istio.io/helloworld-gateway created ## Test the service +### Get LB IP + +```shell +kubectl get svc -l istio=ingressgateway -A +``` +```text +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +istio-ingressgateway LoadBalancer 10.97.47.216 192.168.1.50 15021:31316/TCP,80:32012/TCP,443:32486/TCP 39h +``` + ### Curl --HEAD We receive the status message `301 Moved Permanently`. -- 2.47.2 From b9521a27d157727d38019568dc7cd3371fe2e9d2 Mon Sep 17 00:00:00 2001 From: savagebidoof Date: Thu, 27 Apr 2023 03:27:12 +0200 Subject: [PATCH 11/25] Replaced "changelog" for "configuration" --- .../01-target-namespaces/README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Istio/06-AuthorizationPolicy/01-target-namespaces/README.md b/Istio/06-AuthorizationPolicy/01-target-namespaces/README.md index df76098..54bebf0 100755 --- a/Istio/06-AuthorizationPolicy/01-target-namespaces/README.md +++ b/Istio/06-AuthorizationPolicy/01-target-namespaces/README.md @@ -5,7 +5,6 @@ include_toc: true # Continues from -[//]: # (- [01-hello_world_1_service_1_deployment](../../01-simple/01-hello_world_1_service_1_deployment)) - [06-mTLS](../../02-Traffic_management/06-mTLS) ## Description @@ -14,7 +13,7 @@ Bla bla bla Configuration targeting namespaces -# Changelog +# Configuration ## Authentication configuration deployed @@ -123,7 +122,7 @@ namespace/foo created authorizationpolicy.security.istio.io/allow-nothing created authorizationpolicy.security.istio.io/allow-nothing created authorizationpolicy.security.istio.io/allow-from-istio-system created -authorizationpolicy.security.istio.io/allow-get-from-default created +authorizationpolicy.security.istio.io/allow-head-from-default created service/helloworld created deployment.apps/helloworld-nginx created service/byeworld created @@ -198,7 +197,7 @@ x-envoy-upstream-service-time: 91 It works. -Due to the rule `allow-get-from-default` deployed on the namespace `foo`, which allowed the traffic coming from the namespace `default` as long it used the method `HEAD` and wasn't targeting the path `/secret`, the request is allowed. +Due to the rule `allow-head-from-default` deployed on the namespace `foo`, which allowed the traffic coming from the namespace `default` as long it used the method `HEAD` and wasn't targeting the path `/secret`, the request is allowed. @@ -254,7 +253,7 @@ x-envoy-upstream-service-time: 65 #### helloworld towards byeworld/secret -Due to the configuration set on the rule `allow-get-from-default`, one of the conditions for it to allow the traffic, was to not access the path/match the prefix expression `/secret*`. +Due to the configuration set on the rule `allow-head-from-default`, one of the conditions for it to allow the traffic, was to not access the path/match the prefix expression `/secret*`. This causes the traffic to not be allowed. -- 2.47.2 From 6121d9c0c81421d8ddc07d67a5d9aac0188f65f4 Mon Sep 17 00:00:00 2001 From: savagebidoof Date: Thu, 27 Apr 2023 03:47:41 +0200 Subject: [PATCH 12/25] added some text --- Istio/README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Istio/README.md b/Istio/README.md index 53d112f..2a475b8 100755 --- a/Istio/README.md +++ b/Istio/README.md @@ -34,3 +34,24 @@ Internal and external authentication should be set together. https://istio.io/latest/docs/ops/diagnostic-tools/proxy-cmd/ +## Services port names + +Istio allows to specify which protocol will run through a port. + +It requires the name of the port to be set to a specific format `name: (-)`. + +Starting from Kubernetes 1.18, it also can be specified through the `appProtocol` field in the port, resulting in `appProtocol: `. + +This means that port names should respect this format to avoid issues, and for such be cautious when setting up the name of the ports. + +This applies to multiple Istio elements, but as well to `kind: Services` from default Kubernetes. + +For more information about this behavior, refer to: + +https://istio.io/latest/docs/ops/configuration/traffic-management/protocol-selection/#explicit-protocol-selection + + +# Links of interest + +- https://istiobyexample.dev/ + -- 2.47.2 From 2498a559e993780e17499fec5e3945e1f1600cb2 Mon Sep 17 00:00:00 2001 From: savagebidoof Date: Thu, 27 Apr 2023 04:27:25 +0200 Subject: [PATCH 13/25] renamed folder --- .../README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Istio/{07-External-Authentication => 07-External-Authrorization}/README.md (100%) diff --git a/Istio/07-External-Authentication/README.md b/Istio/07-External-Authrorization/README.md similarity index 100% rename from Istio/07-External-Authentication/README.md rename to Istio/07-External-Authrorization/README.md -- 2.47.2 From d9c3fa6c4cd48443e549edc4c96149eccf6b4280 Mon Sep 17 00:00:00 2001 From: savagebidoof Date: Thu, 27 Apr 2023 07:33:20 +0200 Subject: [PATCH 14/25] Disable mTLS set. --- .../10-TCP-FORWARDING/README.md | 2 +- .../11-TLS-PASSTHROUGH/README.md | 1 + .../05-disable-mTLS/README.md | 637 +++++++++++++++++- .../05-disable-mTLS/Service.yaml | 22 + .../05-disable-mTLS/authentication.yaml | 44 +- .../05-disable-mTLS/deployment.yaml | 52 +- .../05-disable-mTLS/gateway.yaml | 82 ++- .../01-Create-Istio-LoadBalancer/README.md | 4 +- Istio/README.md | 5 + 9 files changed, 780 insertions(+), 69 deletions(-) create mode 100644 Istio/06-AuthorizationPolicy/05-disable-mTLS/Service.yaml diff --git a/Istio/02-Traffic_management/10-TCP-FORWARDING/README.md b/Istio/02-Traffic_management/10-TCP-FORWARDING/README.md index 7ca8464..9b250eb 100644 --- a/Istio/02-Traffic_management/10-TCP-FORWARDING/README.md +++ b/Istio/02-Traffic_management/10-TCP-FORWARDING/README.md @@ -84,7 +84,7 @@ spec: ## Service -The service will forward incoming traffic from the service port 8443, that will be forwarded towards the port 443 from the deployment. +The service will forward incoming traffic from the service port `8443`, that will be forwarded towards the port `443` from the deployment. ```yaml diff --git a/Istio/02-Traffic_management/11-TLS-PASSTHROUGH/README.md b/Istio/02-Traffic_management/11-TLS-PASSTHROUGH/README.md index 2437b2f..c7b16d4 100644 --- a/Istio/02-Traffic_management/11-TLS-PASSTHROUGH/README.md +++ b/Istio/02-Traffic_management/11-TLS-PASSTHROUGH/README.md @@ -160,6 +160,7 @@ kubectl get svc -l istio=ingressgateway -A NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE istio-ingressgateway LoadBalancer 10.97.47.216 192.168.1.50 15021:31316/TCP,80:32012/TCP,443:32486/TCP 39h ``` + ### curl HTTPS Well, it just works. diff --git a/Istio/06-AuthorizationPolicy/05-disable-mTLS/README.md b/Istio/06-AuthorizationPolicy/05-disable-mTLS/README.md index d7004d0..9d45257 100644 --- a/Istio/06-AuthorizationPolicy/05-disable-mTLS/README.md +++ b/Istio/06-AuthorizationPolicy/05-disable-mTLS/README.md @@ -1,6 +1,637 @@ -# Based on +--- +gitea: none +include_toc: true +--- -- [02-Traffic_management/09-HTTPS-backend (pending document)](../../02-Traffic_management/09-HTTPS-backend) +# Description -On the previous example only uses a HTTPS backend, here boards both HTTP and HTTPS backends. +On this example we disable the mTLS for the service deployed, and observe which is the behavior, and one possible environment where it might be required to disable mTLS. +This example uses the `selector` field to target labels set to the deployments. + +Also explores the behavior of accessing an `HTTPS` backend using the tls `STRICT` mode, when using `mTLS` and when `mTLS` is disabled. + +To explore the different behaviors, [2 deployments](#deployments) where used, both under the same [Service](#service), and the traffic will be distributed through subsets in the [Destination Rule](#destination-rule) set. + +> **Note:**\ +> For more information about the image used refer to [here](https://hub.docker.com/r/oriolfilter/https-nginx-demo) + +# Configuration + +## Gateway + +Listens for `HTTP` traffic without limiting any host. + +```yaml +apiVersion: networking.istio.io/v1alpha3 +kind: Gateway +metadata: + name: helloworld-gateway +spec: + selector: + istio: ingressgateway + servers: + - port: + number: 80 + name: http + protocol: HTTP + hosts: + - "*" +``` + +## Virtual Service + +Without limiting to any host, listens for traffic at port 80, and only has a very specific URL paths available to match. + +- /http-mTLS +- /https-mTLS +- /http-no-mTLS +- /https-no-mTLS + +Depending on the path used, the traffic will be distributed between 2 subsets from the same service: + +- mtls +- nomtls + +```yaml +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: helloworld-vs +spec: + hosts: + - "*" + gateways: + - helloworld-gateway + http: + - name: http-mTLS + match: + - port: 80 + uri: + exact: "/http-mTLS" + route: + - destination: + host: helloworld.default.svc.cluster.local + port: + number: 8080 + subset: mtls + rewrite: + uri: "/" + - name: https-mTLS + match: + - port: 80 + uri: + exact: "/https-mTLS" + route: + - destination: + host: helloworld.default.svc.cluster.local + port: + number: 8443 + subset: mtls + rewrite: + uri: "/" + - name: http-no-mTLS + match: + - port: 80 + uri: + exact: "/http-no-mTLS" + route: + - destination: + host: helloworld.default.svc.cluster.local + port: + number: 8080 + subset: nomtls + rewrite: + uri: "/" + - name: https-no-mTLS + match: + - port: 80 + uri: + exact: "/https-no-mTLS" + route: + - destination: + host: helloworld.default.svc.cluster.local + port: + number: 8443 + subset: nomtls + rewrite: + uri: "/" +``` + +## Destination Rule + +Interfering with the service URL `helloworld.default.svc.cluster.local`, it specifies 2 subsets: + +- mtls +- nomtls + +Additionally, specifies that the traffic with port destination 8443, will attempt to proceed with TLS termination, as it is required to connect with an `HTTPS` backend. + +```yaml +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: helloworld.default.svc.cluster.local +spec: + host: helloworld.default.svc.cluster.local + subsets: + - name: mtls + labels: + mtls: "true" + + - name: nomtls + labels: + mtls: "false" + + trafficPolicy: + portLevelSettings: + - port: + number: 8443 + tls: + mode: SIMPLE # Required for https backend +``` + +## Service + +The service will forward incoming traffic from the service port `8443`, that will be forwarded towards the port `443` from the deployment, which contains an `HTTPS` service. + +Also listens for `HTTP` traffic at the port `8080`, and will be forwarded to the deployment port `80`. + +```yaml +apiVersion: v1 +kind: Service +metadata: + name: helloworld + labels: + app: helloworld + service: helloworld +spec: + ports: + - port: 8080 + name: http + targetPort: 80 + protocol: TCP + appProtocol: http + + - port: 8443 + name: https + targetPort: 443 + protocol: TCP + appProtocol: https + selector: + app: helloworld +``` + +## Deployments + +There's been configured 2 deployments with the same service and settings, besides the label `mtls`, which will contain `true` or `false` based on the deployment. + +This label is used for the [Destination Rule](#destination-rule) to distribute the traffic between the 2 deployments under the same service. + +> **Note:**\ +> For more information about the image used refer to [here](https://hub.docker.com/r/oriolfilter/https-nginx-demo) + +### helloworld-mtls + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: helloworld-mtls + labels: + app: helloworld + mtls: "true" +spec: + replicas: 1 + selector: + matchLabels: + app: helloworld + mtls: "true" + template: + metadata: + labels: + app: helloworld + mtls: "true" + spec: + containers: + - name: helloworld + image: oriolfilter/https-nginx-demo + resources: + requests: + cpu: "100m" + imagePullPolicy: IfNotPresent + ports: + - containerPort: 80 + - containerPort: 443 +``` + +### helloworld-nomtls + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: helloworld-nomtls + labels: + app: helloworld + mtls: "false" +spec: + replicas: 1 + selector: + matchLabels: + app: helloworld + mtls: "false" + template: + metadata: + labels: + app: helloworld + mtls: "false" + spec: + containers: + - name: helloworld-nomtls + image: oriolfilter/https-nginx-demo + resources: + requests: + cpu: "100m" + imagePullPolicy: IfNotPresent + ports: + - containerPort: 80 + - containerPort: 443 +``` + +## PeerAuthentications + +Deployed 2 Peer Authentication rules, which use the `selector` field to target the deployments. + +Both point to the same application, yet also specify the `mtls` label set in the deployments above, allowing the rules to target each deployment individually. + +These rules are deployed in the `default` namespace. + +### disable-mtls + +This rule will disable `mTLS` for that deployment. + +```yaml +apiVersion: security.istio.io/v1beta1 +kind: PeerAuthentication +metadata: + name: disable-mtls + namespace: default +spec: + selector: + matchLabels: + app: helloworld + mtls: "false" + mtls: + mode: DISABLE +``` + +### force-mtls + +This rule forces the deployment to communicate exclusively through `mTLS`, in case this rule is not endorsed, the traffic won't be allowed to proceed further. + +```yaml +apiVersion: security.istio.io/v1beta1 +kind: PeerAuthentication +metadata: + name: force-mtls + namespace: default +spec: + selector: + matchLabels: + app: helloworld + mtls: "true" + mtls: + mode: STRICT +``` + +# Walkthrough + +## Deploy resources + +```shell +kubectl apply -f ./ +``` +```text +service/helloworld created +peerauthentication.security.istio.io/disable-mtls created +peerauthentication.security.istio.io/force-mtls created +deployment.apps/helloworld-mtls created +deployment.apps/helloworld-nomtls created +gateway.networking.istio.io/helloworld-gateway created +virtualservice.networking.istio.io/helloworld-vs created +destinationrule.networking.istio.io/helloworld.default.svc.cluster.local created +``` + +## Get LB IP + +```shell +kubectl get svc -l istio=ingressgateway -A +``` +```text +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +istio-ingressgateway LoadBalancer 10.97.47.216 192.168.1.50 15021:31316/TCP,80:32012/TCP,443:32486/TCP 39h +``` + + + +## Analyze the different behaviours + +> **DISCLAIMER**:\ +> For some reason, during the packet captures, I required to execute the curl 2 times in order for the output to be updated.\ +> During the tests, feel free to perform the curl twice in a row. + +This steps will be structured on 3 parts: + +- Starting the packet capture. +- Using `curl` to send a request to the destination. This step can also be performed through a web browser. +- Observing the information captured in the packet capture. + +All this steps will be performed for each one of the environments, each environment being formed by 2 backend destinations. + +Environments: + +- mTLS disabled +- mTLS enabled + +Backend destinations in each one of the environments: +- HTTP +- HTTPS + + +### mTLS disabled + + +#### HTTP + +##### Start the packet capture for the port 80 + +Start the packet capture and proceed with another shell or browser to send traffic requests to the right destination. + +```shell +PORT=80 && MTLS="false" && kubectl exec -n default "$(kubectl get pod -n default -l app=helloworld -l mtls=${MTLS} -o jsonpath={.items..metadata.name})" -c istio-proxy -- sudo tcpdump dst port ${PORT} -A +``` +```text +tcpdump: verbose output suppressed, use -v[v]... for full protocol decode +listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes +``` + +##### Curl + +Nothing to higlight so far, we can access the service. + +```shell +curl 192.168.1.50/http-no-mTLS +``` +```text +

Howdy

+``` + +##### Reviewing pcap output + +Due to having the mTLS disabled, the traffic is not encrypted, and for such we can see its context in plain text. + +This scenario should be avoided unless it is required due the application being used, as mTLS allows an extra layer of security. + +```text +04:25:47.757900 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.60966 > helloworld-nomtls-66d8499c5c-298vw.http: Flags [P.], seq 3134140617:3134142280, ack 2649160847, win 501, options [nop,nop,TS val 1425864700 ecr 2534833629], length 1663: HTTP: GET / HTTP/1.1 +E....t@.?.....yX..yx.&.P..0.........Q...... +T.....}.GET / HTTP/1.1 +host: 192.168.1.50 +user-agent: curl/8.0.1 +accept: */* +x-forwarded-for: 192.168.1.10 +x-forwarded-proto: http +x-envoy-internal: true +x-request-id: 65b60be7-da98-48f3-9ed6-13112cdd14f0 +x-envoy-decorator-operation: helloworld.default.svc.cluster.local:8080/http-no-mTLS +x-envoy-peer-metadata: ChQKDkFQUF9DT05UQUlORVJTEgIaAAoaCgpDTFVTVEVSX0lEEgwaCkt1YmVybmV0ZXMKHwoMSU5TVEFOQ0VfSVBTEg8aDTE3Mi4xNy4xMjEuODgKGQoNSVNUSU9fVkVSU0lPThIIGgYxLjE3LjIKnAMKBkxBQkVMUxKRAyqOAwodCgNhcHASFhoUaXN0aW8taW5ncmVzc2dhdGV3YXkKEwoFY2hhcnQSChoIZ2F0ZXdheXMKFAoIaGVyaXRhZ2USCBoGVGlsbGVyCjYKKWluc3RhbGwub3BlcmF0b3IuaXN0aW8uaW8vb3duaW5nLXJlc291cmNlEgkaB3Vua25vd24KGQoFaXN0aW8SEBoOaW5ncmVzc2dhdGV3YXkKGQoMaXN0aW8uaW8vcmV2EgkaB2RlZmF1bHQKMAobb3BlcmF0b3IuaXN0aW8uaW8vY29tcG9uZW50EhEaD0luZ3Jlc3NHYXRld2F5cwoSCgdyZWxlYXNlEgcaBWlzdGlvCjkKH3NlcnZpY2UuaXN0aW8uaW8vY2Fub25pY2FsLW5hbWUSFhoUaXN0aW8taW5ncmVzc2dhdGV3YXkKLwojc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtcmV2aXNpb24SCBoGbGF0ZXN0CiIKF3NpZGVjYXIuaXN0aW8uaW8vaW5qZWN0EgcaBWZhbHNlChoKB01FU0hfSUQSDxoNY2x1c3Rlci5sb2NhbAovCgROQU1FEicaJWlzdGlvLWluZ3Jlc3NnYXRld2F5LTg2NGRiOTZjNDctZjZscWQKGwoJTkFNRVNQQUNFEg4aDGlzdGlvLXN5c3RlbQpdCgVPV05FUhJUGlJrdWJlcm5ldGVzOi8vYXBpcy9hcHBzL3YxL25hbWVzcGFjZXMvaXN0aW8tc3lzdGVtL2RlcGxveW1lbnRzL2lzdGlvLWluZ3Jlc3NnYXRld2F5ChcKEVBMQVRGT1JNX01FVEFEQVRBEgIqAAonCg1XT1JLTE9BRF9OQU1FEhYaFGlzdGlvLWluZ3Jlc3NnYXRld2F5 +x-envoy-peer-metadata-id: router~172.17.121.88~istio-ingressgateway-864db96c47-f6lqd.istio-system~istio-system.svc.cluster.local +x-envoy-attempt-count: 1 +x-envoy-original-path: /http-no-mTLS +x-b3-traceid: 36e7d48757f2ce26eaa6e1959f3b1221 +x-b3-spanid: eaa6e1959f3b1221 +x-b3-sampled: 0 +``` + +#### HTTPS + +##### Start the packet capture for the port 443 + +```shell +PORT=443 && MTLS="false" && kubectl exec -n default "$(kubectl get pod -n default -l app=helloworld -l mtls=${MTLS} -o jsonpath={.items..metadata.name})" -c istio-proxy -- sudo tcpdump dst port ${PORT} -A +``` +```text +tcpdump: verbose output suppressed, use -v[v]... for full protocol decode +listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes +``` + + +##### Curl + +So good so far. + +```shell +curl 192.168.1.50/https-no-mTLS +``` +```text +

Howdy

+``` + +##### Reviewing pcap output + +Due to the configuration set in the [Destination Rule](#destination-rule), where we set the `tls.mode` setting to `SIMPLE`, the traffic will be TLS terminated with the backend. + +For such, the traffic captured is encrypted, even tho we displayed the `mTLS` configuration for this deployment. + +Yet, there are still a couple readable lines, where we can see that the request was initialized by the host `stio-ingressgateway.istio-system.svc.cluster.local`, through the egress port `39884`, using as destination `helloworld-nomtls-66d8499c5c-298vw`, and the port defined with name `https`, which, if we reviewed the configuration from the [Service](#service), we would observe that it was the port `8443`. + +```text +k 496, win 505, options [nop,nop,TS val 1425943341 ecr 2534945802], length 0 +E..4..@.?.....yX..yx.......;........K...... +T.+-..4 + +04:27:06.400101 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.39884 > helloworld-nomtls-66d8499c5c-298vw.https: Flags [.], ack 809, win 503, options [nop,nop,TS val 1425943342 ecr 2534945803], length 0 +E..4..@.?.....yX..yx.......;........K...... +T.+...4. +04:27:13.439290 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.39826 > helloworld-nomtls-66d8499c5c-298vw.https: Flags [P.], seq 1997:3684, ack 2200, win 501, options [nop,nop,TS val 1425950381 ecr 2534942622], length 1687 +E.....@.?..+..yX..yx....pI.+,U+.....Q...... +T.F...'......,.SuD..a....`..]....j..v[tF$y.<......&..m.E.p.Y.-....w..V..T.....g..a~$.Q'.c#......qj..8.|......M.J.....\".>...R...M..k|..f^,.....E.....+.Y.6..].u.g.m....Z..~o.IL.......D.]h.G.... .....F/..V......}.v.^N.P.C.G.......1..T.....w....?..]:........D...;q?...W..cI.).O......3..X14P..B.).',.N...B.../q..)\.. GW.".... .`.....[9.IS......1y.J]...d..}...B.n...C.........e6..B..[w.\.3.l.HU....5%......p.irW.@s..!1\u./.~..[.g..W.........'W..,m};._../S2\..c.9..8..rg"f..35a.A.;..T....>`..Zv.L.8....hZ".*r...0..*.%K.?.. .P]DKve/E.J.....\....t.e.9#-..3.$).....Q.Z.....m].". q. *.OW...f.=l...K.o:.D.......+.a..h?{h.?..T.....7\N.....M.`..Ob1`.....3d.aq..0...q.r.*j....KE./.O...T%..r.......'..9.W1J^^TU8.$...Y."~..~ZH.......G..?......Q4..=|.{.d/..^_....`.pjJ+p.........R."..Y-.`1....{....k...]ib.+m.....6..k...U.P.T........wU...}......`.z..#..[1.@9.z+R.3pAW).......m...Px4..9^ X..ux.EVO.o.%./+.....|4..!s......g.1...9%.... B.....{.6..].-?.../..n..y...2..sLc..|x. +,.t..'...7.............|...........?..&}........@...=.|#.+...........u.3....m.X..... QrW?............u`-k....Q.o^{........$..h.....R.#...k...o.7~.*.tE.C...I<"......k..czN.DJ.y...R.....hx.he.r}0.82....6.J...)..3.f.G=Ky|f.L.).=.hlN!..D..J..g.V.?.......#...fQ..d.......9.9.-....j..O...Pd..E.da/..b} .}.Qx.......I..[+....>.5....p.9....K2M s(.a..K6.]..m.?...%.. helloworld-nomtls-66d8499c5c-298vw.https: Flags [.], ack 2513, win 501, options [nop,nop,TS val 1425950382 ecr 2534952843], length 0 +E..4..@.?.....yX..yx....pI..,U,.....K...... +T.F...O. +04:27:20.932653 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.40126 > helloworld-nomtls-66d8499c5c-298vw.https: Flags [S], seq 3645561416, win 64800, options [mss 1440,sackOK,TS val 1425957874 ecr 0,nop,wscale 7], length 0 +E..<..@.?.f>..yX..yx.....J.H....... K"......... +T.c......... +04:27:20.933038 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.40126 > helloworld-nomtls-66d8499c5c-298vw.https: Flags [.], ack 840930767, win 507, options [nop,nop,TS val 1425957875 ecr 2534960336], length 0 +E..4..@.?.fE..yX..yx.....J.I2.......K...... +T.c...l. +04:27:20.933916 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.40126 > helloworld-nomtls-66d8499c5c-298vw.https: Flags [P.], seq 0:517, ack 1, win 507, options [nop,nop,TS val 1425957876 ecr 2534960336], length 517 +E..9..@.?.d?..yX..yx.....J.I2.......M...... +T.c...l..............#.."H..\..\A*...5.../m.....wV. ;.......>..`..k.t.b.O.U +e(?.X...........+.../...,.0.............. +...............#..... ...istio-http/1.1.istio.http/1.1.........................3.&.$... J7.y............. +..<.Ma.v}.*3LI.-.....+........................)......./.....`.............3.. .[....N.,......i.9;.9V9A..1..J.......W.....o.%.%.#ev..f.....! ........FHc..r...6...e.'J.&..T.p +04:27:20.937464 IP 172-17-1 +``` + +### mTLS enabled + +#### HTTP + +##### Start the packet capture for the port 80 + +```shell +PORT=80 && MTLS="true" && kubectl exec -n default "$(kubectl get pod -n default -l app=helloworld -l mtls=${MTLS} -o jsonpath={.items..metadata.name})" -c istio-proxy -- sudo tcpdump dst port ${PORT} -A +``` +```text +tcpdump: verbose output suppressed, use -v[v]... for full protocol decode +listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes +``` + +##### Curl + +We can access the service. + +```shell +curl 192.168.1.50/http-mTLS +``` +```text +

Howdy

+``` +##### Reviewing pcap output + +Due to mTLS being enabled, the traffic captured is encrypted, and for such we cannot explore the contents of such. + +We can notice the following lines `outbound_.8080_.mtls_.helloworld.default.svc.cluster.local`, and further deep in the sea of text `1.1.istio.http/1.1` referring that `mTLS` termination was performed through the HTTP version `HTTP1.1`. + +```text +04:21:48.543118 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.40224 > helloworld-mtls-7998d9646b-sv7hp.http: Flags [S], seq 4217286528, win 64800, options [mss 1440,sackOK,TS val 1478647369 ecr 0,nop,wscale 7], length 0 +E..<..@.>..x..yX...,. .P.^......... ........... +X"^I........ +04:21:48.544529 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.40224 > helloworld-mtls-7998d9646b-sv7hp.http: Flags [.], ack 3797925086, win 507, options [nop,nop,TS val 1478647370 ecr 861329182], length 0 +E..4..@.>.....yX...,. .P.^..._............. +X"^J3V.. +04:21:48.545045 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.40224 > helloworld-mtls-7998d9646b-sv7hp.http: Flags [P.], seq 0:2216, ack 1, win 507, options [nop,nop,TS val 1478647371 ecr 861329182], length 2216: HTTP +E.....@.>.....yX...,. .P.^..._.......v..... +X"^K3V................~5pO...T`.|..{. .........Q..e .}..,....q...n....=...'.a7....=r.........+.../...,.0...D...?.=..:outbound_.8080_.mtls_.helloworld.default.svc.cluster.local.......... +...............#..... ...istio-http/1.1.istio.http/1.1.........................3.&.$... #.g....m............l....`.KE.>J.-.....+........).k.F.@O..)z.l.....8b......}.2....77.?.......J..T0....]..\....R.W.]....x..W....;.[....x...."Wy.Q.{.c.Fo..W%7.... +. .].m..>......2\V._(&........;.....&K.b..};R.._A.$)s....2.gC.....d..>Q.x{.uw...s....<|O.:T...d.........j..O...d2..;...S.&. s.l..v..G........B..|g..!....@6fdG..]....=e..>.2..*}%*..>..u..y.B....vq99:....IT..)I5........`......BG...[5m.../7.. +v........R.1...l.S2W{M.7.._w..D....j.,.....O-;6.q....<..P....s +..0..:.....Oq..cX..=.k`Q.X.x.E.E`T.<...Y..tPG.:..z.#p.)$..)@...W..g]Q..W......I..:....~..... .....;Y.YG....+.o.,.....8t...l.q.&.........1..w.{.[.U..B...]a up..8:\....:5......../o.5..[.,+xA(......... +...`.M...>...mor.o........`x\.1......:..s.h..r....Mm*..w.Q. ..d..W..&..0[bi.u.F}4...SP=....j\.H._1....6..f....=.\.$.. pD1.6@.>..4YT.D..e".}=.c..,O..M.eC*?...w..R..LZ..f.._.q..bR.t.-I..=,....%"...*...].m..d5..W...3.k...k.s...[ANc._.....V ...z._.b{I...(r.)..v......H.?......*|./h A|.l.(2..&-..} ...V....D..........g.vA.P/@...._`...M...}..}kF..g.,.rs7...^.0.:W"....8.(.Rr.O{..#I.d.CL....(.D.....L..4..)I3.F.l..kD..`.x<8Z.`..a'.u. +_.^RMn.w.. ..?y...R.T.P.c...9...Q.....w.._.T..;...... .l...?..w!.T.._.,...p +..I.zG....x.^p.........X......7v.'.pp..u....ab^Q_ +pS.........B...6....s;...... +. ..Q....nRw.\HG.H]......l.....G._..4% .{.<...a..p.5\...0......86..Al........&..;....\.V....d.U.......-.Y.....B..v.9...k...]S.)....V]C8.<....0..V..fP.oe..{........ .....8:.{tU..`]...@_.H.t.a...9.}......eE...F..6........!S=....)..W4.;...?..C.... ....t.D..IU....RU.X'V.....t...M.j.'-...^p..1...S.9. ...o.J..8v..C2..%..d.T..GU..-.?...F"`....z.../........s.N....$I..F'0....#........B.4..S.M...#..)...Sx.....E....f.).....m.k.G1..I..$=.Q..^n.[..tn..y4.g.}.7...&..}..JXRk..<...S%r..."]#.......O..;.Rt......2.v..~e.E.{t.F.b...4..-:......6..CrE#.....^]~.k5.@..*.^.K..G.k..(dc.#L..z...L..8..._........d..gXl.......! |r?....%Z&!]n....C7...c.([6u.... +04:21:48.550098 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.40224 > helloworld-mtls-7998d9646b-sv7hp.http: Flags [.], ack 219, win 506, options [nop,nop,TS val 1478647376 ecr 861329188], length 0 +E..4..@.>..|..yX...,. .P.^.)._............. +X"^P3V.$ +04:21:48.551427 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.40224 > helloworld-mtls-7998d9646b-sv7hp.http: Flags [P.], seq 2216:2280, ack 219, win 506, options [nop,nop,TS val 1478647378 ecr 861329188], length 64: HTTP +E..t..@.>..;..yX...,. .P.^.)._.......7..... +X"^R3V.$..........5k)...o...^D......3..........WC.|...@...zwS...z.@yA.c +04:21:48.551870 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.40224 > helloworld-mtls-7998d9646b-sv7hp.http: Flags [P.], seq 2280:3959, ack 219, win 506, options [nop,nop,TS val 1478647378 ecr 861329188], length 1679: HTTP +E.....@.>.....yX...,. .P.^.i._.......]..... +X"^R3V.$......zb5...o.....x.....a..-....B^4...K.m. +..Z..z..(.f3aG......r...$9 +``` + + + +#### HTTPS + +##### Start the packet capture for the port 443 + +```shell +PORT=443 && MTLS="true" && kubectl exec -n default "$(kubectl get pod -n default -l app=helloworld -l mtls=${MTLS} -o jsonpath={.items..metadata.name})" -c istio-proxy -- sudo tcpdump dst port ${PORT} -A +``` +```text +tcpdump: verbose output suppressed, use -v[v]... for full protocol decode +listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes +``` + +##### Curl + +On this scenario, we met a fatal error, not allowing us to access the service, unlike the previous attempts. + +From my understanding, not only from this interaction, but from investigating through Istio forums (yet I don't have the link handy, so take this words with some grains of salt), **the traffic cannot be double terminated**, for such if we have an `HTTPS` backend, we might require to disable `mTLS` in order to communicate with it. We also would need to set a [Destination Rule like we did further above](#destination-rule), to specify that the traffic must be terminated with the backend (`tls.mode: STRICT`). + +Yet this depends on which would be our architecture, due also being able to set up [TLS Passthrough](../../02-Traffic_management/11-TLS-PASSTHROUGH), or use a [TCP Forwarding](../../02-Traffic_management/10-TCP-FORWARDING). + +```shell +curl 192.168.1.50/https-mTLS +``` +```text +upstream connect error or disconnect/reset before headers. reset reason: connection termination +``` + +##### Reviewing pcap output + + +Not much to highlight as there isn't much available text for us to be able to read. + +```text +04:22:15.813163 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.50576 > helloworld-mtls-7998d9646b-sv7hp.https: Flags [S], seq 693161527, win 64800, options [mss 1440,sackOK,TS val 1478674639 ecr 0,nop,wscale 7], length 0 +E..<..@.>.}Z..yX...,....)P.7....... ........... +X".......... +04:22:15.814619 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.50576 > helloworld-mtls-7998d9646b-sv7hp.https: Flags [.], ack 609580424, win 507, options [nop,nop,TS val 1478674641 ecr 861356452], length 0 +E..4..@.>.}a..yX...,....)P.8$Uu......x..... +X"..3WA. +04:22:15.815126 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.50576 > helloworld-mtls-7998d9646b-sv7hp.https: Flags [P.], seq 0:246, ack 1, win 507, options [nop,nop,TS val 1478674641 ecr 861356452], length 246 +E..*..@.>.|j..yX...,....)P.8$Uu............ +X"..3WA.............#j..S..(.j....4\v.h_ N......S.O e....U.....oM.j.....l...t......T.........+.../...,.0.............. +...............#..... ...istio-http/1.1.istio.http/1.1.........................3.&.$... ..t.i.=..1...[i.. +FQF.....8d..}..-.....+....... +04:22:15.831747 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.50576 > helloworld-mtls-7998d9646b-sv7hp.https: Flags [.], ack 2165, win 499, options [nop,nop,TS val 1478674658 ecr 861356470], length 0 +E..4..@.>.}_..yX...,....)P..$U}............ +X"..3WA. +04:22:15.834886 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.50576 > helloworld-mtls-7998d9646b-sv7hp.https: Flags [P.], seq 246:318, ack 2165, win 501, options [nop,nop,TS val 1478674661 ecr 861356470], length 72 +E..|..@.>.}...yX...,....)P..$U}.....+...... +X"..3WA...........=d..Vv.s..."..Dc.p...T...s...3........i.'-Sc..0p|)...!. T~...) +04:22:15.835307 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.50576 > helloworld-mtls-7998d9646b-sv7hp.https: Flags [P.], seq 318:1999, ack 2165, win 501, options [nop,nop,TS val 1478674661 ecr 861356470], length 1681 +E.....@.>.v...yX...,....)P.v$U}......_..... +X"..3WA.......7..t...*.U.....,...]...l..=.x....jH..*......[..._...._..l..+......T..9.}$CO.[...b.Fx0...X.2.......V.U.%.^.%.}?... ...$..G.\0G..=.9.X....jA...ks^r.H.*H.....2H........Im +...........@..D!O0...a..G.i/1..W-.....A..yd..`...h.'Y...&.Po..T.4..B........$..t...M.....D..Y..6z.....8I-....e.3.....4.$Y.C_R...'V......C...&.\....."...U.[T....nW..}......!......L..j..ov...~.....r.. +B..B.gRp +R..xLTm..af_.X.2.......|.,.Wi.....F@.0.'... +.>.8.'t.....r.Xi....#*..l.bO.V.......G.[:....7.2.(U....R,#.>!..<.o..w..R|.T..:_..i.. iJs.-.>...B..~.mOH0+N.....-.b...5.._.9%....u&..y.S...8A...*.=....MJS.m........u..Ic....s}Y....{.8d.....<..P-;V[......\.....+..S.8k..r&...dT..K..y].t..3..BU,.<......:IH......-..\j.g...\:..[........(.S......"..0|-.p"Z..:..>6..b..x.....M..;K2AT|Ah.....3z.+..><.&........)E.C ..4....X1.p} .@...@n.........\..R...H........5...+h-...q.|.(....]o.. jw..(....=.. +..+(nY{......6..@..c.^.........o..:.V|..0.... N*..e*...G.,{...wb...-y..g.k7...,tI.|..........H....4E.2..!b........K..&q1..0.us|z...he/.T+6b.}.L........q...F....nTs.Vp!.........W.F..j...X +./.gIv..6G(Ze.h`.......<..w...........@!E..N.>..^.[..IO$T.]6.D..K%m.....LD @. + .......f!O....5 ...K...Y..}.I.o.]q0`..H&...d.aZ.1...P.......R.. C.jfM....;9........y.h.E)...r.....B....#.\......Q..fX..~....ixh ..t.q.y....BkR.nr.k5.`@.8..Z5_Gl.l. +...'t....q...... ....t8......_`.....:4>.H....S.e/=!.V.. .6...X.o..K.H...S@.3...a.....].j-.$Q.6..{..kr.....=a.....-.......-2....D.....&......:..y.DJQ.0....E....,Uc......H..6.`.u.....).f..R.xp.H....(.c.9..a.*.P$d..KD..;.x.$,....L.......`..x...p.[..d...z.,jV.[0....j.r."\..._....[......].o..5.Q*.Y.....b0.......-..B...^..)9....S.l...Ek?..~9......`....^...../G{Q14......7......SVV.A.>8..]. +04:22:15.835726 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.50576 > helloworld-mtls-7998d9646b-sv7hp.https: Flags [.], ack 2189, win 501, options [nop,nop,TS val 1478674662 ecr 861356474], length 0 +E..4..@.>.}[..yX...,....)P..$U~............ +X"..3WA. +04:22:15.835912 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.505 +``` + + +## Cleanup + +```shell +kubectl delete -f ./ +``` + +```text +service "helloworld" deleted +peerauthentication.security.istio.io "disable-mtls" deleted +peerauthentication.security.istio.io "force-mtls" deleted +deployment.apps "helloworld-mtls" deleted +deployment.apps "helloworld-nomtls" deleted +gateway.networking.istio.io "helloworld-gateway" deleted +virtualservice.networking.istio.io "helloworld-vs" deleted +destinationrule.networking.istio.io "helloworld.default.svc.cluster.local" deleted +``` + + +# Links of Interest + +- https://istio.io/latest/docs/reference/config/security/peer_authentication/#PeerAuthentication-MutualTLS-Mode + +- https://istio.io/latest/docs/tasks/security/authentication/mtls-migration/ + +- https://istio.io/latest/docs/concepts/security/#mutual-tls-authentication + +- https://istio.io/latest/docs/reference/config/security/peer_authentication/ diff --git a/Istio/06-AuthorizationPolicy/05-disable-mTLS/Service.yaml b/Istio/06-AuthorizationPolicy/05-disable-mTLS/Service.yaml new file mode 100644 index 0000000..23e43df --- /dev/null +++ b/Istio/06-AuthorizationPolicy/05-disable-mTLS/Service.yaml @@ -0,0 +1,22 @@ +apiVersion: v1 +kind: Service +metadata: + name: helloworld + labels: + app: helloworld + service: helloworld +spec: + ports: + - port: 8080 + name: http + targetPort: 80 + protocol: TCP + appProtocol: http + - port: 8443 + name: https + targetPort: 443 + protocol: TCP + appProtocol: https + selector: + app: helloworld +--- \ No newline at end of file diff --git a/Istio/06-AuthorizationPolicy/05-disable-mTLS/authentication.yaml b/Istio/06-AuthorizationPolicy/05-disable-mTLS/authentication.yaml index 221a86d..94cb780 100644 --- a/Istio/06-AuthorizationPolicy/05-disable-mTLS/authentication.yaml +++ b/Istio/06-AuthorizationPolicy/05-disable-mTLS/authentication.yaml @@ -1,8 +1,50 @@ +#apiVersion: security.istio.io/v1beta1 +#kind: PeerAuthentication +#metadata: +# name: enable-mtls +# namespace: default +#spec: +# selector: +# matchLabels: +# app: helloworld +# mtls: "true" +# mtls: +# mode: STRICT +#--- apiVersion: security.istio.io/v1beta1 kind: PeerAuthentication metadata: - name: default-mtls + name: disable-mtls namespace: default spec: + selector: + matchLabels: + app: helloworld + mtls: "false" mtls: mode: DISABLE +--- +apiVersion: security.istio.io/v1beta1 +kind: PeerAuthentication +metadata: + name: force-mtls + namespace: default +spec: + selector: + matchLabels: + app: helloworld + mtls: "true" + mtls: + mode: STRICT +# portLevelMtls: +# 443: +# mode: STRICT +#--- +#apiVersion: security.istio.io/v1beta1 +#kind: PeerAuthentication +#metadata: +# name: default-mtls +# namespace: default +#spec: +# mtls: +# mode: DISABLE diff --git a/Istio/06-AuthorizationPolicy/05-disable-mTLS/deployment.yaml b/Istio/06-AuthorizationPolicy/05-disable-mTLS/deployment.yaml index 5b2d589..3741061 100755 --- a/Istio/06-AuthorizationPolicy/05-disable-mTLS/deployment.yaml +++ b/Istio/06-AuthorizationPolicy/05-disable-mTLS/deployment.yaml @@ -1,42 +1,21 @@ -apiVersion: v1 -kind: Service -metadata: - name: helloworld - labels: - app: helloworld - service: helloworld -spec: - ports: - - port: 8080 - name: http - targetPort: 80 - protocol: TCP - appProtocol: http - - - port: 8443 - name: https - targetPort: 443 - protocol: TCP - appProtocol: https - selector: - app: helloworld ---- apiVersion: apps/v1 kind: Deployment metadata: - name: helloworld-nginx + name: helloworld-mtls labels: app: helloworld + mtls: "true" spec: replicas: 1 selector: matchLabels: app: helloworld + mtls: "true" template: metadata: labels: app: helloworld - sidecar.istio.io/inject: "true" + mtls: "true" spec: containers: - name: helloworld @@ -44,7 +23,7 @@ spec: resources: requests: cpu: "100m" - imagePullPolicy: Always #Always + imagePullPolicy: IfNotPresent ports: - containerPort: 80 - containerPort: 443 @@ -52,28 +31,29 @@ spec: apiVersion: apps/v1 kind: Deployment metadata: - name: nginx + name: helloworld-nomtls labels: - app: nginx - version: v1 + app: helloworld + mtls: "false" spec: replicas: 1 selector: matchLabels: - app: nginx - version: v1 + app: helloworld + mtls: "false" template: metadata: labels: - app: nginx - version: v1 + app: helloworld + mtls: "false" spec: containers: - - name: nginx - image: nginx + - name: helloworld-nomtls + image: oriolfilter/https-nginx-demo resources: requests: cpu: "100m" imagePullPolicy: IfNotPresent ports: - - containerPort: 80 \ No newline at end of file + - containerPort: 80 + - containerPort: 443 \ No newline at end of file diff --git a/Istio/06-AuthorizationPolicy/05-disable-mTLS/gateway.yaml b/Istio/06-AuthorizationPolicy/05-disable-mTLS/gateway.yaml index f88d191..a476db4 100755 --- a/Istio/06-AuthorizationPolicy/05-disable-mTLS/gateway.yaml +++ b/Istio/06-AuthorizationPolicy/05-disable-mTLS/gateway.yaml @@ -12,16 +12,6 @@ spec: protocol: HTTP hosts: - "*" - - port: - number: 443 - name: https - protocol: HTTPS - hosts: - - "*" - tls: - credentialName: my-tls-cert-secret - minProtocolVersion: TLSV1_2 - mode: SIMPLE --- apiVersion: networking.istio.io/v1alpha3 kind: VirtualService @@ -33,38 +23,78 @@ spec: gateways: - helloworld-gateway http: - - name: http-vs + - name: http-mTLS match: - port: 80 + uri: + exact: "/http-mTLS" route: - destination: host: helloworld.default.svc.cluster.local port: number: 8080 - - name: https-vs + subset: mtls + rewrite: + uri: "/" + - name: https-mTLS match: - - port: 443 + - port: 80 + uri: + exact: "/https-mTLS" route: - destination: host: helloworld.default.svc.cluster.local port: number: 8443 + subset: mtls + rewrite: + uri: "/" + - name: http-no-mTLS + match: + - port: 80 + uri: + exact: "/http-no-mTLS" + route: + - destination: + host: helloworld.default.svc.cluster.local + port: + number: 8080 + subset: nomtls + rewrite: + uri: "/" + - name: https-no-mTLS + match: + - port: 80 + uri: + exact: "/https-no-mTLS" + route: + - destination: + host: helloworld.default.svc.cluster.local + port: + number: 8443 + subset: nomtls + rewrite: + uri: "/" --- apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: - name: helloworld - namespace: default + name: helloworld.default.svc.cluster.local spec: - host: helloworld.default.svc.cluster.local - trafficPolicy: - portLevelSettings: - - port: - number: 8080 - tls: - mode: SIMPLE + host: helloworld.default.svc.cluster.local + subsets: + - name: mtls + labels: + mtls: "true" - - port: - number: 8443 - tls: - mode: SIMPLE + - name: nomtls + labels: + mtls: "false" + + trafficPolicy: + portLevelSettings: + - port: + number: 8443 + tls: + mode: SIMPLE # Required for https backend +--- diff --git a/Istio/09-Ingress/01-Create-Istio-LoadBalancer/README.md b/Istio/09-Ingress/01-Create-Istio-LoadBalancer/README.md index f81c507..382f17e 100644 --- a/Istio/09-Ingress/01-Create-Istio-LoadBalancer/README.md +++ b/Istio/09-Ingress/01-Create-Istio-LoadBalancer/README.md @@ -119,7 +119,7 @@ virtualservice.networking.istio.io/helloworld-vs created ### Deploy deployment ```shell -kubectl apply -f deployment.yaml +kubectl apply -f deployment-nomtls.yaml ``` ```text service/helloworld created @@ -161,7 +161,7 @@ x-envoy-upstream-service-time: 15 [Yeah no idea, gl with that.](https://stackoverflow.com/a/55731730) ```shell -kubectl delete -f ./deployment.yaml +kubectl delete -f ./deployment-nomtls.yaml kubectl delete -f ./gateway.yaml ``` ```text diff --git a/Istio/README.md b/Istio/README.md index 2a475b8..150371b 100755 --- a/Istio/README.md +++ b/Istio/README.md @@ -51,6 +51,11 @@ For more information about this behavior, refer to: https://istio.io/latest/docs/ops/configuration/traffic-management/protocol-selection/#explicit-protocol-selection + +# Workload selector is cool + +- https://istio.io/latest/docs/reference/config/type/workload-selector/#WorkloadSelector + # Links of interest - https://istiobyexample.dev/ -- 2.47.2 From 828c3beb1126ab371b4b972d8f982c514a2b3464 Mon Sep 17 00:00:00 2001 From: savagebidoof Date: Thu, 27 Apr 2023 19:03:03 +0200 Subject: [PATCH 15/25] temporal backup + moved a folder. --- Istio/06-AuthorizationPolicy/README.md | 2 - .../01-disable-mTLS}/README.md | 0 .../01-disable-mTLS}/Service.yaml | 0 .../01-disable-mTLS}/authentication.yaml | 0 .../01-disable-mTLS}/deployment.yaml | 0 .../01-disable-mTLS}/gateway.yaml | 0 .../02-portLevelMtls/README.md | 480 ++++++++++++++++++ .../02-portLevelMtls/Service.yaml | 22 + .../02-portLevelMtls/authentication.yaml | 15 + .../02-portLevelMtls/deployment.yaml | 26 + .../02-portLevelMtls/gateway.yaml | 62 +++ 11 files changed, 605 insertions(+), 2 deletions(-) rename Istio/{06-AuthorizationPolicy/05-disable-mTLS => 10-PeerAuthentication/01-disable-mTLS}/README.md (100%) rename Istio/{06-AuthorizationPolicy/05-disable-mTLS => 10-PeerAuthentication/01-disable-mTLS}/Service.yaml (100%) rename Istio/{06-AuthorizationPolicy/05-disable-mTLS => 10-PeerAuthentication/01-disable-mTLS}/authentication.yaml (100%) rename Istio/{06-AuthorizationPolicy/05-disable-mTLS => 10-PeerAuthentication/01-disable-mTLS}/deployment.yaml (100%) rename Istio/{06-AuthorizationPolicy/05-disable-mTLS => 10-PeerAuthentication/01-disable-mTLS}/gateway.yaml (100%) create mode 100644 Istio/10-PeerAuthentication/02-portLevelMtls/README.md create mode 100644 Istio/10-PeerAuthentication/02-portLevelMtls/Service.yaml create mode 100644 Istio/10-PeerAuthentication/02-portLevelMtls/authentication.yaml create mode 100755 Istio/10-PeerAuthentication/02-portLevelMtls/deployment.yaml create mode 100755 Istio/10-PeerAuthentication/02-portLevelMtls/gateway.yaml diff --git a/Istio/06-AuthorizationPolicy/README.md b/Istio/06-AuthorizationPolicy/README.md index b8fa14d..c722a3c 100644 --- a/Istio/06-AuthorizationPolicy/README.md +++ b/Istio/06-AuthorizationPolicy/README.md @@ -10,8 +10,6 @@ - Audit / logs (should be the 3th) -- disable mTLS (4th) - JWT seems important, refer to source.requestPrincipals https://istio.io/latest/docs/tasks/security/authentication/ diff --git a/Istio/06-AuthorizationPolicy/05-disable-mTLS/README.md b/Istio/10-PeerAuthentication/01-disable-mTLS/README.md similarity index 100% rename from Istio/06-AuthorizationPolicy/05-disable-mTLS/README.md rename to Istio/10-PeerAuthentication/01-disable-mTLS/README.md diff --git a/Istio/06-AuthorizationPolicy/05-disable-mTLS/Service.yaml b/Istio/10-PeerAuthentication/01-disable-mTLS/Service.yaml similarity index 100% rename from Istio/06-AuthorizationPolicy/05-disable-mTLS/Service.yaml rename to Istio/10-PeerAuthentication/01-disable-mTLS/Service.yaml diff --git a/Istio/06-AuthorizationPolicy/05-disable-mTLS/authentication.yaml b/Istio/10-PeerAuthentication/01-disable-mTLS/authentication.yaml similarity index 100% rename from Istio/06-AuthorizationPolicy/05-disable-mTLS/authentication.yaml rename to Istio/10-PeerAuthentication/01-disable-mTLS/authentication.yaml diff --git a/Istio/06-AuthorizationPolicy/05-disable-mTLS/deployment.yaml b/Istio/10-PeerAuthentication/01-disable-mTLS/deployment.yaml similarity index 100% rename from Istio/06-AuthorizationPolicy/05-disable-mTLS/deployment.yaml rename to Istio/10-PeerAuthentication/01-disable-mTLS/deployment.yaml diff --git a/Istio/06-AuthorizationPolicy/05-disable-mTLS/gateway.yaml b/Istio/10-PeerAuthentication/01-disable-mTLS/gateway.yaml similarity index 100% rename from Istio/06-AuthorizationPolicy/05-disable-mTLS/gateway.yaml rename to Istio/10-PeerAuthentication/01-disable-mTLS/gateway.yaml diff --git a/Istio/10-PeerAuthentication/02-portLevelMtls/README.md b/Istio/10-PeerAuthentication/02-portLevelMtls/README.md new file mode 100644 index 0000000..bb8355d --- /dev/null +++ b/Istio/10-PeerAuthentication/02-portLevelMtls/README.md @@ -0,0 +1,480 @@ +--- +gitea: none +include_toc: true +--- + +# Based on + +- [01-disable-mTLS](../01-disable-mTLS) + +# Description + +Based on the previous example that disabled mTLS, and explored how it affected the behavior of the services, on `HTTP` and `HTTPS` backends, this example aims to, through the usage of `portLevelMtls`, configure the `mTLS` behavior based on the destination port. + +Through this, we can apply multiple `mTLS` behaviors under a single deployment, unlike the [previous example](../01-disable-mTLS) that required to create 2 different deployments under a single service, and as well implement `Destination Rules` as well of `subsets` to route the traffic between the 2 deployments. + +> **Note:**\ +> For more information about the image used refer to [here](https://hub.docker.com/r/oriolfilter/https-nginx-demo) + +# Configuration + +## Gateway + +Listens for `HTTP` traffic without limiting any host. + +```yaml +apiVersion: networking.istio.io/v1alpha3 +kind: Gateway +metadata: + name: helloworld-gateway +spec: + selector: + istio: ingressgateway + servers: + - port: + number: 80 + name: http + protocol: HTTP + hosts: + - "*" +``` + +## Virtual Service + +Without limiting to any host, listens for traffic at port 80, and only has a very specific URL paths available to match. + +- /http-mTLS +- /https-mTLS +- /http-no-mTLS +- /https-no-mTLS + +Depending on the path used, the traffic will be distributed between 2 subsets from the same service: + +- mtls +- nomtls + +```yaml +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: helloworld-vs +spec: + hosts: + - "*" + gateways: + - helloworld-gateway + http: + - name: http-mTLS + match: + - port: 80 + uri: + exact: "/http-mTLS" + route: + - destination: + host: helloworld.default.svc.cluster.local + port: + number: 8080 + subset: mtls + rewrite: + uri: "/" + - name: https-mTLS + match: + - port: 80 + uri: + exact: "/https-mTLS" + route: + - destination: + host: helloworld.default.svc.cluster.local + port: + number: 8443 + subset: mtls + rewrite: + uri: "/" + - name: http-no-mTLS + match: + - port: 80 + uri: + exact: "/http-no-mTLS" + route: + - destination: + host: helloworld.default.svc.cluster.local + port: + number: 8080 + subset: nomtls + rewrite: + uri: "/" + - name: https-no-mTLS + match: + - port: 80 + uri: + exact: "/https-no-mTLS" + route: + - destination: + host: helloworld.default.svc.cluster.local + port: + number: 8443 + subset: nomtls + rewrite: + uri: "/" +``` + +## Destination Rule + +Interfering with the service URL `helloworld.default.svc.cluster.local`, it specifies 2 subsets: + +- mtls +- nomtls + +Additionally, specifies that the traffic with port destination 8443, will attempt to proceed with TLS termination, as it is required to connect with an `HTTPS` backend. + +```yaml +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: helloworld.default.svc.cluster.local +spec: + host: helloworld.default.svc.cluster.local + subsets: + - name: mtls + labels: + mtls: "true" + + - name: nomtls + labels: + mtls: "false" + + trafficPolicy: + portLevelSettings: + - port: + number: 8443 + tls: + mode: SIMPLE # Required for https backend +``` + +## Service + +The service will forward incoming traffic from the service port `8443`, that will be forwarded towards the port `443` from the deployment, which contains an `HTTPS` service. + +Also listens for `HTTP` traffic at the port `8080`, and will be forwarded to the deployment port `80`. + +```yaml +apiVersion: v1 +kind: Service +metadata: + name: helloworld + labels: + app: helloworld + service: helloworld +spec: + ports: + - port: 8080 + name: http + targetPort: 80 + protocol: TCP + appProtocol: http + + - port: 8443 + name: https + targetPort: 443 + protocol: TCP + appProtocol: https + selector: + app: helloworld +``` + +## Deployments + +There's been configured 2 deployments with the same service and settings, besides the label `mtls`, which will contain `true` or `false` based on the deployment. + +This label is used for the [Destination Rule](#destination-rule) to distribute the traffic between the 2 deployments under the same service. + +> **Note:**\ +> For more information about the image used refer to [here](https://hub.docker.com/r/oriolfilter/https-nginx-demo) + +### helloworld-mtls + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: helloworld-mtls + labels: + app: helloworld + mtls: "true" +spec: + replicas: 1 + selector: + matchLabels: + app: helloworld + mtls: "true" + template: + metadata: + labels: + app: helloworld + mtls: "true" + spec: + containers: + - name: helloworld + image: oriolfilter/https-nginx-demo + resources: + requests: + cpu: "100m" + imagePullPolicy: IfNotPresent + ports: + - containerPort: 80 + - containerPort: 443 +``` + +### helloworld-nomtls + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: helloworld-nomtls + labels: + app: helloworld + mtls: "false" +spec: + replicas: 1 + selector: + matchLabels: + app: helloworld + mtls: "false" + template: + metadata: + labels: + app: helloworld + mtls: "false" + spec: + containers: + - name: helloworld-nomtls + image: oriolfilter/https-nginx-demo + resources: + requests: + cpu: "100m" + imagePullPolicy: IfNotPresent + ports: + - containerPort: 80 + - containerPort: 443 +``` + +## PeerAuthentications + +Deployed 2 Peer Authentication rules, which use the `selector` field to target the deployments. + +Both point to the same application, yet also specify the `mtls` label set in the deployments above, allowing the rules to target each deployment individually. + +These rules are deployed in the `default` namespace. + +### disable-mtls + +This rule will disable `mTLS` for that deployment. + +```yaml +apiVersion: security.istio.io/v1beta1 +kind: PeerAuthentication +metadata: + name: disable-mtls + namespace: default +spec: + selector: + matchLabels: + app: helloworld + mtls: "false" + mtls: + mode: DISABLE +``` + +### force-mtls + +This rule forces the deployment to communicate exclusively through `mTLS`, in case this rule is not endorsed, the traffic won't be allowed to proceed further. + +```yaml +apiVersion: security.istio.io/v1beta1 +kind: PeerAuthentication +metadata: + name: force-mtls + namespace: default +spec: + selector: + matchLabels: + app: helloworld + mtls: "true" + mtls: + mode: STRICT +``` + +# Walkthrough + +## Deploy resources + +```shell +kubectl apply -f ./ +``` +```text +service/helloworld created +peerauthentication.security.istio.io/helloworld-mtls created +deployment.apps/helloworld created +gateway.networking.istio.io/helloworld-gateway created +virtualservice.networking.istio.io/helloworld-vs created +destinationrule.networking.istio.io/helloworld.default.svc.cluster.local created +``` + +## Get LB IP + +```shell +kubectl get svc -l istio=ingressgateway -A +``` +```text +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +istio-ingressgateway LoadBalancer 10.97.47.216 192.168.1.50 15021:31316/TCP,80:32012/TCP,443:32486/TCP 39h +``` + +## Test resources and analyze behaviors + +> **DISCLAIMER**:\ +> For some reason, during the packet captures, I required to execute the curl 2 times in order for the output to be updated.\ +> During the tests, feel free to perform the curl twice in a row. + +### HTTP + +#### Start the packet capture for the port 80 + +Start the packet capture and proceed with another shell or browser to send traffic requests to the right destination. + +```shell +PORT=80 && MTLS="false" && kubectl exec -n default "$(kubectl get pod -n default -l app=helloworld -l mtls=${MTLS} -o jsonpath={.items..metadata.name})" -c istio-proxy -- sudo tcpdump dst port ${PORT} -A +``` +```text +tcpdump: verbose output suppressed, use -v[v]... for full protocol decode +listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes +``` + +##### Curl + +Nothing to higlight so far, we can access the service. + +```shell +curl 192.168.1.50/http-no-mTLS +``` +```text +

Howdy

+``` + +##### Reviewing pcap output + +Due to having the mTLS disabled, the traffic is not encrypted, and for such we can see its context in plain text. + +This scenario should be avoided unless it is required due the application being used, as mTLS allows an extra layer of security. + +```text +04:25:47.757900 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.60966 > helloworld-nomtls-66d8499c5c-298vw.http: Flags [P.], seq 3134140617:3134142280, ack 2649160847, win 501, options [nop,nop,TS val 1425864700 ecr 2534833629], length 1663: HTTP: GET / HTTP/1.1 +E....t@.?.....yX..yx.&.P..0.........Q...... +T.....}.GET / HTTP/1.1 +host: 192.168.1.50 +user-agent: curl/8.0.1 +accept: */* +x-forwarded-for: 192.168.1.10 +x-forwarded-proto: http +x-envoy-internal: true +x-request-id: 65b60be7-da98-48f3-9ed6-13112cdd14f0 +x-envoy-decorator-operation: helloworld.default.svc.cluster.local:8080/http-no-mTLS +x-envoy-peer-metadata: ChQKDkFQUF9DT05UQUlORVJTEgIaAAoaCgpDTFVTVEVSX0lEEgwaCkt1YmVybmV0ZXMKHwoMSU5TVEFOQ0VfSVBTEg8aDTE3Mi4xNy4xMjEuODgKGQoNSVNUSU9fVkVSU0lPThIIGgYxLjE3LjIKnAMKBkxBQkVMUxKRAyqOAwodCgNhcHASFhoUaXN0aW8taW5ncmVzc2dhdGV3YXkKEwoFY2hhcnQSChoIZ2F0ZXdheXMKFAoIaGVyaXRhZ2USCBoGVGlsbGVyCjYKKWluc3RhbGwub3BlcmF0b3IuaXN0aW8uaW8vb3duaW5nLXJlc291cmNlEgkaB3Vua25vd24KGQoFaXN0aW8SEBoOaW5ncmVzc2dhdGV3YXkKGQoMaXN0aW8uaW8vcmV2EgkaB2RlZmF1bHQKMAobb3BlcmF0b3IuaXN0aW8uaW8vY29tcG9uZW50EhEaD0luZ3Jlc3NHYXRld2F5cwoSCgdyZWxlYXNlEgcaBWlzdGlvCjkKH3NlcnZpY2UuaXN0aW8uaW8vY2Fub25pY2FsLW5hbWUSFhoUaXN0aW8taW5ncmVzc2dhdGV3YXkKLwojc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtcmV2aXNpb24SCBoGbGF0ZXN0CiIKF3NpZGVjYXIuaXN0aW8uaW8vaW5qZWN0EgcaBWZhbHNlChoKB01FU0hfSUQSDxoNY2x1c3Rlci5sb2NhbAovCgROQU1FEicaJWlzdGlvLWluZ3Jlc3NnYXRld2F5LTg2NGRiOTZjNDctZjZscWQKGwoJTkFNRVNQQUNFEg4aDGlzdGlvLXN5c3RlbQpdCgVPV05FUhJUGlJrdWJlcm5ldGVzOi8vYXBpcy9hcHBzL3YxL25hbWVzcGFjZXMvaXN0aW8tc3lzdGVtL2RlcGxveW1lbnRzL2lzdGlvLWluZ3Jlc3NnYXRld2F5ChcKEVBMQVRGT1JNX01FVEFEQVRBEgIqAAonCg1XT1JLTE9BRF9OQU1FEhYaFGlzdGlvLWluZ3Jlc3NnYXRld2F5 +x-envoy-peer-metadata-id: router~172.17.121.88~istio-ingressgateway-864db96c47-f6lqd.istio-system~istio-system.svc.cluster.local +x-envoy-attempt-count: 1 +x-envoy-original-path: /http-no-mTLS +x-b3-traceid: 36e7d48757f2ce26eaa6e1959f3b1221 +x-b3-spanid: eaa6e1959f3b1221 +x-b3-sampled: 0 +``` + +#### HTTPS + +##### Start the packet capture for the port 443 + +```shell +PORT=443 && MTLS="false" && kubectl exec -n default "$(kubectl get pod -n default -l app=helloworld -l mtls=${MTLS} -o jsonpath={.items..metadata.name})" -c istio-proxy -- sudo tcpdump dst port ${PORT} -A +``` +```text +tcpdump: verbose output suppressed, use -v[v]... for full protocol decode +listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes +``` + + +##### Curl + +So good so far. + +```shell +curl 192.168.1.50/https-no-mTLS +``` +```text +

Howdy

+``` + +##### Reviewing pcap output + +Due to the configuration set in the [Destination Rule](#destination-rule), where we set the `tls.mode` setting to `SIMPLE`, the traffic will be TLS terminated with the backend. + +For such, the traffic captured is encrypted, even tho we displayed the `mTLS` configuration for this deployment. + +Yet, there are still a couple readable lines, where we can see that the request was initialized by the host `stio-ingressgateway.istio-system.svc.cluster.local`, through the egress port `39884`, using as destination `helloworld-nomtls-66d8499c5c-298vw`, and the port defined with name `https`, which, if we reviewed the configuration from the [Service](#service), we would observe that it was the port `8443`. + +```text +k 496, win 505, options [nop,nop,TS val 1425943341 ecr 2534945802], length 0 +E..4..@.?.....yX..yx.......;........K...... +T.+-..4 + +04:27:06.400101 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.39884 > helloworld-nomtls-66d8499c5c-298vw.https: Flags [.], ack 809, win 503, options [nop,nop,TS val 1425943342 ecr 2534945803], length 0 +E..4..@.?.....yX..yx.......;........K...... +T.+...4. +04:27:13.439290 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.39826 > helloworld-nomtls-66d8499c5c-298vw.https: Flags [P.], seq 1997:3684, ack 2200, win 501, options [nop,nop,TS val 1425950381 ecr 2534942622], length 1687 +E.....@.?..+..yX..yx....pI.+,U+.....Q...... +T.F...'......,.SuD..a....`..]....j..v[tF$y.<......&..m.E.p.Y.-....w..V..T.....g..a~$.Q'.c#......qj..8.|......M.J.....\".>...R...M..k|..f^,.....E.....+.Y.6..].u.g.m....Z..~o.IL.......D.]h.G.... .....F/..V......}.v.^N.P.C.G.......1..T.....w....?..]:........D...;q?...W..cI.).O......3..X14P..B.).',.N...B.../q..)\.. GW.".... .`.....[9.IS......1y.J]...d..}...B.n...C.........e6..B..[w.\.3.l.HU....5%......p.irW.@s..!1\u./.~..[.g..W.........'W..,m};._../S2\..c.9..8..rg"f..35a.A.;..T....>`..Zv.L.8....hZ".*r...0..*.%K.?.. .P]DKve/E.J.....\....t.e.9#-..3.$).....Q.Z.....m].". q. *.OW...f.=l...K.o:.D.......+.a..h?{h.?..T.....7\N.....M.`..Ob1`.....3d.aq..0...q.r.*j....KE./.O...T%..r.......'..9.W1J^^TU8.$...Y."~..~ZH.......G..?......Q4..=|.{.d/..^_....`.pjJ+p.........R."..Y-.`1....{....k...]ib.+m.....6..k...U.P.T........wU...}......`.z..#..[1.@9.z+R.3pAW).......m...Px4..9^ X..ux.EVO.o.%./+.....|4..!s......g.1...9%.... B.....{.6..].-?.../..n..y...2..sLc..|x. +,.t..'...7.............|...........?..&}........@...=.|#.+...........u.3....m.X..... QrW?............u`-k....Q.o^{........$..h.....R.#...k...o.7~.*.tE.C...I<"......k..czN.DJ.y...R.....hx.he.r}0.82....6.J...)..3.f.G=Ky|f.L.).=.hlN!..D..J..g.V.?.......#...fQ..d.......9.9.-....j..O...Pd..E.da/..b} .}.Qx.......I..[+....>.5....p.9....K2M s(.a..K6.]..m.?...%.. helloworld-nomtls-66d8499c5c-298vw.https: Flags [.], ack 2513, win 501, options [nop,nop,TS val 1425950382 ecr 2534952843], length 0 +E..4..@.?.....yX..yx....pI..,U,.....K...... +T.F...O. +04:27:20.932653 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.40126 > helloworld-nomtls-66d8499c5c-298vw.https: Flags [S], seq 3645561416, win 64800, options [mss 1440,sackOK,TS val 1425957874 ecr 0,nop,wscale 7], length 0 +E..<..@.?.f>..yX..yx.....J.H....... K"......... +T.c......... +04:27:20.933038 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.40126 > helloworld-nomtls-66d8499c5c-298vw.https: Flags [.], ack 840930767, win 507, options [nop,nop,TS val 1425957875 ecr 2534960336], length 0 +E..4..@.?.fE..yX..yx.....J.I2.......K...... +T.c...l. +04:27:20.933916 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.40126 > helloworld-nomtls-66d8499c5c-298vw.https: Flags [P.], seq 0:517, ack 1, win 507, options [nop,nop,TS val 1425957876 ecr 2534960336], length 517 +E..9..@.?.d?..yX..yx.....J.I2.......M...... +T.c...l..............#.."H..\..\A*...5.../m.....wV. ;.......>..`..k.t.b.O.U +e(?.X...........+.../...,.0.............. +...............#..... ...istio-http/1.1.istio.http/1.1.........................3.&.$... J7.y............. +..<.Ma.v}.*3LI.-.....+........................)......./.....`.............3.. .[....N.,......i.9;.9V9A..1..J.......W.....o.%.%.#ev..f.....! ........FHc..r...6...e.'J.&..T.p +04:27:20.937464 IP 172-17-1 +``` + + +## Cleanup + +```shell +kubectl delete -f ./ +``` + +```text +service "helloworld" deleted +peerauthentication.security.istio.io "helloworld-mtls" deleted +deployment.apps "helloworld" deleted +gateway.networking.istio.io "helloworld-gateway" deleted +virtualservice.networking.istio.io "helloworld-vs" deleted +destinationrule.networking.istio.io "helloworld.default.svc.cluster.local" deleted +``` + + +# Links of Interest + +- https://istio.io/latest/docs/reference/config/security/peer_authentication/#PeerAuthentication-MutualTLS-Mode + +- https://istio.io/latest/docs/tasks/security/authentication/mtls-migration/ + +- https://istio.io/latest/docs/concepts/security/#mutual-tls-authentication + +- https://istio.io/latest/docs/reference/config/security/peer_authentication/ diff --git a/Istio/10-PeerAuthentication/02-portLevelMtls/Service.yaml b/Istio/10-PeerAuthentication/02-portLevelMtls/Service.yaml new file mode 100644 index 0000000..23e43df --- /dev/null +++ b/Istio/10-PeerAuthentication/02-portLevelMtls/Service.yaml @@ -0,0 +1,22 @@ +apiVersion: v1 +kind: Service +metadata: + name: helloworld + labels: + app: helloworld + service: helloworld +spec: + ports: + - port: 8080 + name: http + targetPort: 80 + protocol: TCP + appProtocol: http + - port: 8443 + name: https + targetPort: 443 + protocol: TCP + appProtocol: https + selector: + app: helloworld +--- \ No newline at end of file diff --git a/Istio/10-PeerAuthentication/02-portLevelMtls/authentication.yaml b/Istio/10-PeerAuthentication/02-portLevelMtls/authentication.yaml new file mode 100644 index 0000000..8fe7cc9 --- /dev/null +++ b/Istio/10-PeerAuthentication/02-portLevelMtls/authentication.yaml @@ -0,0 +1,15 @@ +apiVersion: security.istio.io/v1beta1 +kind: PeerAuthentication +metadata: + name: helloworld-mtls + namespace: default +spec: + selector: + matchLabels: + app: helloworld + mtls: "false" + mtls: + mode: STRICT + portLevelMtls: + 443: + mode: DISABLE \ No newline at end of file diff --git a/Istio/10-PeerAuthentication/02-portLevelMtls/deployment.yaml b/Istio/10-PeerAuthentication/02-portLevelMtls/deployment.yaml new file mode 100755 index 0000000..4a802ba --- /dev/null +++ b/Istio/10-PeerAuthentication/02-portLevelMtls/deployment.yaml @@ -0,0 +1,26 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: helloworld + labels: + app: helloworld +spec: + replicas: 1 + selector: + matchLabels: + app: helloworld + template: + metadata: + labels: + app: helloworld + spec: + containers: + - name: helloworld + image: oriolfilter/https-nginx-demo + resources: + requests: + cpu: "100m" + imagePullPolicy: IfNotPresent + ports: + - containerPort: 80 + - containerPort: 443 diff --git a/Istio/10-PeerAuthentication/02-portLevelMtls/gateway.yaml b/Istio/10-PeerAuthentication/02-portLevelMtls/gateway.yaml new file mode 100755 index 0000000..c824983 --- /dev/null +++ b/Istio/10-PeerAuthentication/02-portLevelMtls/gateway.yaml @@ -0,0 +1,62 @@ +apiVersion: networking.istio.io/v1alpha3 +kind: Gateway +metadata: + name: helloworld-gateway +spec: + selector: + istio: ingressgateway + servers: + - port: + number: 80 + name: http + protocol: HTTP + hosts: + - "*" +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: helloworld-vs +spec: + hosts: + - "*" + gateways: + - helloworld-gateway + http: + - name: http-mTLS + match: + - port: 80 + uri: + exact: "/http" + route: + - destination: + host: helloworld.default.svc.cluster.local + port: + number: 8080 + rewrite: + uri: "/" + - name: https-mTLS + match: + - port: 80 + uri: + exact: "/https" + route: + - destination: + host: helloworld.default.svc.cluster.local + port: + number: 8443 + rewrite: + uri: "/" +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: helloworld.default.svc.cluster.local +spec: + host: helloworld.default.svc.cluster.local + trafficPolicy: + portLevelSettings: + - port: + number: 8443 + tls: + mode: SIMPLE # Required for https backend \ No newline at end of file -- 2.47.2 From a5c1039583d6c98b9f6fdc44d4ec02cf43455dba Mon Sep 17 00:00:00 2001 From: savagebidoof Date: Fri, 28 Apr 2023 04:11:49 +0200 Subject: [PATCH 16/25] Documented the example --- .../02-portLevelMtls/README.md | 283 ++++++------------ .../02-portLevelMtls/authentication.yaml | 1 - 2 files changed, 94 insertions(+), 190 deletions(-) diff --git a/Istio/10-PeerAuthentication/02-portLevelMtls/README.md b/Istio/10-PeerAuthentication/02-portLevelMtls/README.md index bb8355d..255cfc0 100644 --- a/Istio/10-PeerAuthentication/02-portLevelMtls/README.md +++ b/Istio/10-PeerAuthentication/02-portLevelMtls/README.md @@ -20,7 +20,7 @@ Through this, we can apply multiple `mTLS` behaviors under a single deployment, ## Gateway -Listens for `HTTP` traffic without limiting any host. +Listens for `HTTP` traffic without limiting to any host. ```yaml apiVersion: networking.istio.io/v1alpha3 @@ -43,15 +43,10 @@ spec: Without limiting to any host, listens for traffic at port 80, and only has a very specific URL paths available to match. -- /http-mTLS -- /https-mTLS -- /http-no-mTLS -- /https-no-mTLS +The path `/http` will be routed to the `HTTP` service set in our backend. -Depending on the path used, the traffic will be distributed between 2 subsets from the same service: +The path `/http` will be routed to the `HTTPS` service set in our backend. -- mtls -- nomtls ```yaml apiVersion: networking.istio.io/v1alpha3 @@ -68,64 +63,31 @@ spec: match: - port: 80 uri: - exact: "/http-mTLS" + exact: "/http" route: - destination: host: helloworld.default.svc.cluster.local port: number: 8080 - subset: mtls rewrite: uri: "/" - name: https-mTLS match: - port: 80 uri: - exact: "/https-mTLS" + exact: "/https" route: - destination: host: helloworld.default.svc.cluster.local port: number: 8443 - subset: mtls - rewrite: - uri: "/" - - name: http-no-mTLS - match: - - port: 80 - uri: - exact: "/http-no-mTLS" - route: - - destination: - host: helloworld.default.svc.cluster.local - port: - number: 8080 - subset: nomtls - rewrite: - uri: "/" - - name: https-no-mTLS - match: - - port: 80 - uri: - exact: "/https-no-mTLS" - route: - - destination: - host: helloworld.default.svc.cluster.local - port: - number: 8443 - subset: nomtls rewrite: uri: "/" ``` ## Destination Rule -Interfering with the service URL `helloworld.default.svc.cluster.local`, it specifies 2 subsets: - -- mtls -- nomtls - -Additionally, specifies that the traffic with port destination 8443, will attempt to proceed with TLS termination, as it is required to connect with an `HTTPS` backend. +Interfering with the service URL `helloworld.default.svc.cluster.local`, the traffic with port destination `8443`, will attempt to proceed with TLS termination, as it is required to connect with an `HTTPS` backend. ```yaml apiVersion: networking.istio.io/v1alpha3 @@ -134,15 +96,6 @@ metadata: name: helloworld.default.svc.cluster.local spec: host: helloworld.default.svc.cluster.local - subsets: - - name: mtls - labels: - mtls: "true" - - - name: nomtls - labels: - mtls: "false" - trafficPolicy: portLevelSettings: - port: @@ -182,36 +135,29 @@ spec: app: helloworld ``` -## Deployments +## Deployment -There's been configured 2 deployments with the same service and settings, besides the label `mtls`, which will contain `true` or `false` based on the deployment. - -This label is used for the [Destination Rule](#destination-rule) to distribute the traffic between the 2 deployments under the same service. +The deployment listen to the port `80` and `443`, hosting an `HTTP` and `HTTPS` service respectively to the aforementioned ports. > **Note:**\ > For more information about the image used refer to [here](https://hub.docker.com/r/oriolfilter/https-nginx-demo) -### helloworld-mtls - ```yaml apiVersion: apps/v1 kind: Deployment metadata: - name: helloworld-mtls + name: helloworld labels: app: helloworld - mtls: "true" spec: replicas: 1 selector: matchLabels: app: helloworld - mtls: "true" template: metadata: labels: app: helloworld - mtls: "true" spec: containers: - name: helloworld @@ -225,84 +171,34 @@ spec: - containerPort: 443 ``` -### helloworld-nomtls +## PeerAuthentication -```yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: helloworld-nomtls - labels: - app: helloworld - mtls: "false" -spec: - replicas: 1 - selector: - matchLabels: - app: helloworld - mtls: "false" - template: - metadata: - labels: - app: helloworld - mtls: "false" - spec: - containers: - - name: helloworld-nomtls - image: oriolfilter/https-nginx-demo - resources: - requests: - cpu: "100m" - imagePullPolicy: IfNotPresent - ports: - - containerPort: 80 - - containerPort: 443 -``` -## PeerAuthentications +Deployed a rule that sets a "global" mTLS mode to `STRICT`, meaning that the traffic require mTLS termination in order to proceed further with the request. -Deployed 2 Peer Authentication rules, which use the `selector` field to target the deployments. +Also, at a specific port configuration, the port `443` has the mTLS mode disabled, as the deployment contains an `HTTPS` service we required to disable it in order of the request to be successful. -Both point to the same application, yet also specify the `mtls` label set in the deployments above, allowing the rules to target each deployment individually. +Through the use of the `selector.matchLabels` field, we targeted our deployment pods, limiting the target of this rule. -These rules are deployed in the `default` namespace. - -### disable-mtls - -This rule will disable `mTLS` for that deployment. +> **Note**:\ +> In order to use the `portLevelMtls` field, the selector field is required, otherwise it won't take effect.\ +> For more information regarding this behavior, refer to the [official Istio documentation regarding PeerAuthentication](https://istio.io/latest/docs/reference/config/security/peer_authentication/#PeerAuthentication) ```yaml apiVersion: security.istio.io/v1beta1 kind: PeerAuthentication metadata: - name: disable-mtls + name: helloworld-mtls namespace: default spec: selector: matchLabels: app: helloworld - mtls: "false" - mtls: - mode: DISABLE -``` - -### force-mtls - -This rule forces the deployment to communicate exclusively through `mTLS`, in case this rule is not endorsed, the traffic won't be allowed to proceed further. - -```yaml -apiVersion: security.istio.io/v1beta1 -kind: PeerAuthentication -metadata: - name: force-mtls - namespace: default -spec: - selector: - matchLabels: - app: helloworld - mtls: "true" mtls: mode: STRICT + portLevelMtls: + 443: + mode: DISABLE ``` # Walkthrough @@ -333,9 +229,13 @@ istio-ingressgateway LoadBalancer 10.97.47.216 192.168.1.50 15021:31316/ ## Test resources and analyze behaviors -> **DISCLAIMER**:\ -> For some reason, during the packet captures, I required to execute the curl 2 times in order for the output to be updated.\ -> During the tests, feel free to perform the curl twice in a row. + + +[//]: # (> **DISCLAIMER**:\) + +[//]: # (> For some reason, during the packet captures, I required to execute the curl 2 times in order for the output to be updated.\) + +[//]: # (> During the tests, feel free to perform the curl twice in a row.) ### HTTP @@ -344,7 +244,7 @@ istio-ingressgateway LoadBalancer 10.97.47.216 192.168.1.50 15021:31316/ Start the packet capture and proceed with another shell or browser to send traffic requests to the right destination. ```shell -PORT=80 && MTLS="false" && kubectl exec -n default "$(kubectl get pod -n default -l app=helloworld -l mtls=${MTLS} -o jsonpath={.items..metadata.name})" -c istio-proxy -- sudo tcpdump dst port ${PORT} -A +PORT=80 && kubectl exec -n default "$(kubectl get pod -n default -l app=helloworld -o jsonpath={.items..metadata.name})" -c istio-proxy -- sudo tcpdump dst port ${PORT} -A ``` ```text tcpdump: verbose output suppressed, use -v[v]... for full protocol decode @@ -356,7 +256,7 @@ listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes Nothing to higlight so far, we can access the service. ```shell -curl 192.168.1.50/http-no-mTLS +curl 192.168.1.50/http ``` ```text

Howdy

@@ -364,29 +264,37 @@ curl 192.168.1.50/http-no-mTLS ##### Reviewing pcap output -Due to having the mTLS disabled, the traffic is not encrypted, and for such we can see its context in plain text. - -This scenario should be avoided unless it is required due the application being used, as mTLS allows an extra layer of security. +As we can observe, the traffic is encrypted, proving that the mTLS is taking effect terminating the connection with the `HTTP` backend. ```text -04:25:47.757900 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.60966 > helloworld-nomtls-66d8499c5c-298vw.http: Flags [P.], seq 3134140617:3134142280, ack 2649160847, win 501, options [nop,nop,TS val 1425864700 ecr 2534833629], length 1663: HTTP: GET / HTTP/1.1 -E....t@.?.....yX..yx.&.P..0.........Q...... -T.....}.GET / HTTP/1.1 -host: 192.168.1.50 -user-agent: curl/8.0.1 -accept: */* -x-forwarded-for: 192.168.1.10 -x-forwarded-proto: http -x-envoy-internal: true -x-request-id: 65b60be7-da98-48f3-9ed6-13112cdd14f0 -x-envoy-decorator-operation: helloworld.default.svc.cluster.local:8080/http-no-mTLS -x-envoy-peer-metadata: ChQKDkFQUF9DT05UQUlORVJTEgIaAAoaCgpDTFVTVEVSX0lEEgwaCkt1YmVybmV0ZXMKHwoMSU5TVEFOQ0VfSVBTEg8aDTE3Mi4xNy4xMjEuODgKGQoNSVNUSU9fVkVSU0lPThIIGgYxLjE3LjIKnAMKBkxBQkVMUxKRAyqOAwodCgNhcHASFhoUaXN0aW8taW5ncmVzc2dhdGV3YXkKEwoFY2hhcnQSChoIZ2F0ZXdheXMKFAoIaGVyaXRhZ2USCBoGVGlsbGVyCjYKKWluc3RhbGwub3BlcmF0b3IuaXN0aW8uaW8vb3duaW5nLXJlc291cmNlEgkaB3Vua25vd24KGQoFaXN0aW8SEBoOaW5ncmVzc2dhdGV3YXkKGQoMaXN0aW8uaW8vcmV2EgkaB2RlZmF1bHQKMAobb3BlcmF0b3IuaXN0aW8uaW8vY29tcG9uZW50EhEaD0luZ3Jlc3NHYXRld2F5cwoSCgdyZWxlYXNlEgcaBWlzdGlvCjkKH3NlcnZpY2UuaXN0aW8uaW8vY2Fub25pY2FsLW5hbWUSFhoUaXN0aW8taW5ncmVzc2dhdGV3YXkKLwojc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtcmV2aXNpb24SCBoGbGF0ZXN0CiIKF3NpZGVjYXIuaXN0aW8uaW8vaW5qZWN0EgcaBWZhbHNlChoKB01FU0hfSUQSDxoNY2x1c3Rlci5sb2NhbAovCgROQU1FEicaJWlzdGlvLWluZ3Jlc3NnYXRld2F5LTg2NGRiOTZjNDctZjZscWQKGwoJTkFNRVNQQUNFEg4aDGlzdGlvLXN5c3RlbQpdCgVPV05FUhJUGlJrdWJlcm5ldGVzOi8vYXBpcy9hcHBzL3YxL25hbWVzcGFjZXMvaXN0aW8tc3lzdGVtL2RlcGxveW1lbnRzL2lzdGlvLWluZ3Jlc3NnYXRld2F5ChcKEVBMQVRGT1JNX01FVEFEQVRBEgIqAAonCg1XT1JLTE9BRF9OQU1FEhYaFGlzdGlvLWluZ3Jlc3NnYXRld2F5 -x-envoy-peer-metadata-id: router~172.17.121.88~istio-ingressgateway-864db96c47-f6lqd.istio-system~istio-system.svc.cluster.local -x-envoy-attempt-count: 1 -x-envoy-original-path: /http-no-mTLS -x-b3-traceid: 36e7d48757f2ce26eaa6e1959f3b1221 -x-b3-spanid: eaa6e1959f3b1221 -x-b3-sampled: 0 +02:00:10.511593 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.54396 > helloworld-6798765f88-76r6c.http: Flags [S], seq 3999274711, win 64800, options [mss 1440,sackOK,TS val 2646430461 ecr 0,nop,wscale 7], length 0 +E..<..@.>..I..yX...=.|.P.`......... .z......... +..R......... +02:00:10.512773 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.54396 > helloworld-6798765f88-76r6c.http: Flags [.], ack 134781521, win 507, options [nop,nop,TS val 2646430462 ecr 2887117842], length 0 +E..4..@.>..P..yX...=.|.P.`.....Q........... +..R..... +02:00:10.512988 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.54396 > helloworld-6798765f88-76r6c.http: Flags [P.], seq 0:517, ack 1, win 507, options [nop,nop,TS val 2646430462 ecr 2887117842], length 517: HTTP +E..9..@.>..J..yX...=.|.P.`.....Q........... +..R.................a7.i..v{ +.Nr.0.Yex..C7..k.6...d .......z._ikW3.C.H.....5..Yk.&.c.........+.../...,.0.......;.9..6outbound_.8080_._.helloworld.default.svc.cluster.local.......... +...............#..... ...istio-http/1.1.istio.http/1.1.........................3.&.$... .....M4...^9V........d_..+J."..Z.-.....+....................................................................................................................................................................................................................... +02:00:10.530088 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.54396 > helloworld-6798765f88-76r6c.http: Flags [.], ack 2164, win 499, options [nop,nop,TS val 2646430479 ecr 2887117859], length 0 +E..4..@.>..N..yX...=.|.P.`...........3..... +..S....# +02:00:10.551166 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.54396 > helloworld-6798765f88-76r6c.http: Flags [P.], seq 517:2501, ack 2164, win 501, options [nop,nop,TS val 2646430500 ecr 2887117859], length 1984: HTTP +E.....@.>.....yX...=.|.P.`................. +..S$...#...........D.W_....+..v{..Q.3....^..m~..aU.+~t..%b....O.X|.).....=.w.z...'....`.2._...7.N..9.y..V.y.&..*vBx..z)B.g.D...1...x....V.J.*!....5.......#.......9.....V.Y..kes.&:+..j;.X5C.I...h+.SO0V....A..b,?.d.@YOy.`x.......o.EcTf}n.0....!N..Qh?.uK#?.Nx..q.&..9|?.)".qpg.]..O2.;O.x...J...$0.......I......1.X2.......2..=.UG.h'pA.CKX........ +. . 7W.v...q..?IW.M:..'d....!2..Y......I.P..).Y..~..>.:k..y..Z?....w.D.Y..M.B.MXH.HDa...(.].B......k...{..c&.0...S3..]..2.a\.......?.#..........]3...~...Q|w...l."Z;.4..!.1..,X.>YE..3Yw..9.....#|.....[`...qq..@v.m..1.|V.j$t.C..&.Ww...5e....?.|Q."..obR.a...^...D8;...=.1.....S[.90...ss....-.@..q.JI........$.8..)skW.....G....3:.qb..#/....#...'/n.~F...(Y[.k..EEz}...cgR..6...P..)'.X..e..z....Tv0>....l.t.O=D.vc..}.a.ct.....E/.*..]-`.....O.hY..j..u...."(QZ.^.......f.1.LZ.O.L.9}..m.1_sC....x.*`D..ny.......):.V.."n..t.0....T.S..u[._v%...q`._.....W.w_.q...........O.:J.....[S.a$...l.[. ..cP..zF..~..+..|.....l.. [.l.."/ +.....D6f....9:..i............N........o.....;...%v.0@...n^..."OSN.o*.:ap.C#C.Hc..r..MD. +.-..2.... +..`...."..I...Wh9.L...r:.4M...b+q +...8f...*.^.K.k.?7:.\..O... ..cD..c........jM....;......k.... +02:00:10.551752 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.54396 > helloworld-6798765f88-76r6c.http: Flags [P.], seq 2501:4170, ack 2164, wi ``` #### HTTPS @@ -394,7 +302,7 @@ x-b3-sampled: 0 ##### Start the packet capture for the port 443 ```shell -PORT=443 && MTLS="false" && kubectl exec -n default "$(kubectl get pod -n default -l app=helloworld -l mtls=${MTLS} -o jsonpath={.items..metadata.name})" -c istio-proxy -- sudo tcpdump dst port ${PORT} -A +PORT=443 && kubectl exec -n default "$(kubectl get pod -n default -l app=helloworld -o jsonpath={.items..metadata.name})" -c istio-proxy -- sudo tcpdump dst port ${PORT} -A ``` ```text tcpdump: verbose output suppressed, use -v[v]... for full protocol decode @@ -404,10 +312,10 @@ listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes ##### Curl -So good so far. +Even tho, we have set in the [PeerAuthentication configuration](#peerauthentication) mode to `STRICT`, unlike in the [previous example](../01-disable-mTLS/#https-1), where the mode was also set to `STRICT`, in this example we configured the `portLevelMtls` field for the port `443`, successfully disabling `mTLS` for this port, and allowing to proceed with the request towards the `HTTPS` backend; which was performed without the need of disabling `mTLS` for the whole deployment. ```shell -curl 192.168.1.50/https-no-mTLS +curl 192.168.1.50/https ``` ```text

Howdy

@@ -422,34 +330,38 @@ For such, the traffic captured is encrypted, even tho we displayed the `mTLS` co Yet, there are still a couple readable lines, where we can see that the request was initialized by the host `stio-ingressgateway.istio-system.svc.cluster.local`, through the egress port `39884`, using as destination `helloworld-nomtls-66d8499c5c-298vw`, and the port defined with name `https`, which, if we reviewed the configuration from the [Service](#service), we would observe that it was the port `8443`. ```text -k 496, win 505, options [nop,nop,TS val 1425943341 ecr 2534945802], length 0 -E..4..@.?.....yX..yx.......;........K...... -T.+-..4 - -04:27:06.400101 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.39884 > helloworld-nomtls-66d8499c5c-298vw.https: Flags [.], ack 809, win 503, options [nop,nop,TS val 1425943342 ecr 2534945803], length 0 -E..4..@.?.....yX..yx.......;........K...... -T.+...4. -04:27:13.439290 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.39826 > helloworld-nomtls-66d8499c5c-298vw.https: Flags [P.], seq 1997:3684, ack 2200, win 501, options [nop,nop,TS val 1425950381 ecr 2534942622], length 1687 -E.....@.?..+..yX..yx....pI.+,U+.....Q...... -T.F...'......,.SuD..a....`..]....j..v[tF$y.<......&..m.E.p.Y.-....w..V..T.....g..a~$.Q'.c#......qj..8.|......M.J.....\".>...R...M..k|..f^,.....E.....+.Y.6..].u.g.m....Z..~o.IL.......D.]h.G.... .....F/..V......}.v.^N.P.C.G.......1..T.....w....?..]:........D...;q?...W..cI.).O......3..X14P..B.).',.N...B.../q..)\.. GW.".... .`.....[9.IS......1y.J]...d..}...B.n...C.........e6..B..[w.\.3.l.HU....5%......p.irW.@s..!1\u./.~..[.g..W.........'W..,m};._../S2\..c.9..8..rg"f..35a.A.;..T....>`..Zv.L.8....hZ".*r...0..*.%K.?.. .P]DKve/E.J.....\....t.e.9#-..3.$).....Q.Z.....m].". q. *.OW...f.=l...K.o:.D.......+.a..h?{h.?..T.....7\N.....M.`..Ob1`.....3d.aq..0...q.r.*j....KE./.O...T%..r.......'..9.W1J^^TU8.$...Y."~..~ZH.......G..?......Q4..=|.{.d/..^_....`.pjJ+p.........R."..Y-.`1....{....k...]ib.+m.....6..k...U.P.T........wU...}......`.z..#..[1.@9.z+R.3pAW).......m...Px4..9^ X..ux.EVO.o.%./+.....|4..!s......g.1...9%.... B.....{.6..].-?.../..n..y...2..sLc..|x. -,.t..'...7.............|...........?..&}........@...=.|#.+...........u.3....m.X..... QrW?............u`-k....Q.o^{........$..h.....R.#...k...o.7~.*.tE.C...I<"......k..czN.DJ.y...R.....hx.he.r}0.82....6.J...)..3.f.G=Ky|f.L.).=.hlN!..D..J..g.V.?.......#...fQ..d.......9.9.-....j..O...Pd..E.da/..b} .}.Qx.......I..[+....>.5....p.9....K2M s(.a..K6.]..m.?...%.. helloworld-nomtls-66d8499c5c-298vw.https: Flags [.], ack 2513, win 501, options [nop,nop,TS val 1425950382 ecr 2534952843], length 0 -E..4..@.?.....yX..yx....pI..,U,.....K...... -T.F...O. -04:27:20.932653 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.40126 > helloworld-nomtls-66d8499c5c-298vw.https: Flags [S], seq 3645561416, win 64800, options [mss 1440,sackOK,TS val 1425957874 ecr 0,nop,wscale 7], length 0 -E..<..@.?.f>..yX..yx.....J.H....... K"......... -T.c......... -04:27:20.933038 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.40126 > helloworld-nomtls-66d8499c5c-298vw.https: Flags [.], ack 840930767, win 507, options [nop,nop,TS val 1425957875 ecr 2534960336], length 0 -E..4..@.?.fE..yX..yx.....J.I2.......K...... -T.c...l. -04:27:20.933916 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.40126 > helloworld-nomtls-66d8499c5c-298vw.https: Flags [P.], seq 0:517, ack 1, win 507, options [nop,nop,TS val 1425957876 ecr 2534960336], length 517 -E..9..@.?.d?..yX..yx.....J.I2.......M...... -T.c...l..............#.."H..\..\A*...5.../m.....wV. ;.......>..`..k.t.b.O.U -e(?.X...........+.../...,.0.............. -...............#..... ...istio-http/1.1.istio.http/1.1.........................3.&.$... J7.y............. -..<.Ma.v}.*3LI.-.....+........................)......./.....`.............3.. .[....N.,......i.9;.9V9A..1..J.......W.....o.%.%.#ev..f.....! ........FHc..r...6...e.'J.&..T.p -04:27:20.937464 IP 172-17-1 +02:02:41.616839 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.58752 > helloworld-6798765f88-76r6c.https: Flags [S], seq 1052122243, win 64800, options [mss 1440,sackOK,TS val 2646581565 ecr 0,nop,wscale 7], length 0 +E..<.y@.>.....yX...=....>.......... ........... +...=........ +02:02:41.618256 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.58752 > helloworld-6798765f88-76r6c.https: Flags [.], ack 1254443190, win 507, options [nop,nop,TS val 2646581567 ecr 2887268947], length 0 +E..4.z@.>.....yX...=....>...J.H............ +...?..:S +02:02:41.618902 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.58752 > helloworld-6798765f88-76r6c.https: Flags [P.], seq 0:246, ack 1, win 507, options [nop,nop,TS val 2646581568 ecr 2887268947], length 246 +E..*.{@.>.....yX...=....>...J.H.....T...... +...@..:S............ +.B.L(....I....`O.#.$-..f..y.'. :.&.....1oX.i.J.W.CD.-.l.|...y...........+.../...,.0.............. +...............#..... ...istio-http/1.1.istio.http/1.1.........................3.&.$... .vw..q|H[6.HQp.zn[. m...M0yL..]g.-.....+....... +02:02:41.637813 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.58752 > helloworld-6798765f88-76r6c.https: Flags [.], ack 1377, win 501, options [nop,nop,TS val 2646581587 ecr 2887268967], length 0 +E..4.|@.>.....yX...=....>..zJ.N......4..... +...S..:g +02:02:41.641084 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.58752 > helloworld-6798765f88-76r6c.https: Flags [P.], seq 246:310, ack 1377, win 501, options [nop,nop,TS val 2646581590 ecr 2887268967], length 64 +E..t.}@.>..M..yX...=....>..zJ.N............ +...V..:g..........5D\..yfI.....]iyu.:........m!Ev.....*..-..`.'*.......g +02:02:41.642627 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.58752 > helloworld-6798765f88-76r6c.https: Flags [.], ack 1632, win 501, options [nop,nop,TS val 2646581592 ecr 2887268972], length 0 +E..4.~@.>.....yX...=....>...J.O............ +...X..:l +02:02:41.642884 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.58752 > helloworld-6798765f88-76r6c.https: Flags [.], ack 1887, win 501, options [nop,nop,TS val 2646581592 ecr 2887268972], length 0 +E..4..@.>.....yX...=....>...J.P............ +...X..:l +02:02:41.643146 IP 172-17-121-88.istio-ingressgateway.istio-system.svc.cluster.local.58752 > helloworld-6798765f88-76r6c.https: Flags [P.], seq 310:1981, ack 1887, win 501, options [nop,nop,TS val 2646581592 ecr 2887268972], length 1671 +E.....@.>.....yX...=....>...J.P......f..... +...X..:l...............*t\o.....z^=.=\....cq..../.9eKL..`.C....."....q{...*..0;^n7.o:,...a..-.W8:.1..c........Z..b.......i...4....B.. .-2...+3$i.!.......7..._.T..G`...Ar.D.a.....U..^....^.Q.h.._.p.H..9.*O.5)-T_....7}8.>."...j..)e.^..-.'.L....Y9.6d...Z...<.....hygo.z\H.11...q{.*T....V.>K.9\HJ...7.....m.r:.(...s.'5|_...F..X&..>#(..]...H.6.V....(.4z..3,...e.P.r..H..A.[...[....S.YNp......C..LN.....z.r.....6.J..".H...%=T.f.O...84........(..r@O#.3C...9.G..m.D.J...a.w....).GuC?.,.].9a..4...1....MoG8l..u..hV.h.6....Z`....+..9.aAW.]..,_7.@...y..._{.....buwy).q.\L.L....E2..~....',.J............Z.._...G......4,....o.w2 +...`....qp.. .g..iP.Vdw...W9.B...q..<...F...j..-G.!\..3r+\.T....{d$....Ys..4.J....D.["..- +(E.l..H7.iw.....?....?p..cI#qu...mK.T...qp.[g..%.2...|....7O...u.K..........?....s.......J.#%...;._.....>..Z......7DA...P.fg.......N..Oz..+....3........y..+...r..*.....[...xT...J...}..n...n...V ..P...<..y..U.^.....90.......4..'..p.E..F2.....~.GBG.....@v<....;m dd..z~..>\..T$.i..Da...M.!xR......x6.h...l...m.I.Zl ..t +.g..c..w...EEtq.s.......8...x.E.|..%e..n..b.FA'..w.. +.. +.H.d... ...H>K.......O..#'.`....q..0.K>...".c.~.\.......N..$. ``` @@ -468,13 +380,6 @@ virtualservice.networking.istio.io "helloworld-vs" deleted destinationrule.networking.istio.io "helloworld.default.svc.cluster.local" deleted ``` - # Links of Interest -- https://istio.io/latest/docs/reference/config/security/peer_authentication/#PeerAuthentication-MutualTLS-Mode - -- https://istio.io/latest/docs/tasks/security/authentication/mtls-migration/ - -- https://istio.io/latest/docs/concepts/security/#mutual-tls-authentication - -- https://istio.io/latest/docs/reference/config/security/peer_authentication/ +- https://istio.io/latest/docs/reference/config/security/peer_authentication/#PeerAuthentication diff --git a/Istio/10-PeerAuthentication/02-portLevelMtls/authentication.yaml b/Istio/10-PeerAuthentication/02-portLevelMtls/authentication.yaml index 8fe7cc9..69c6834 100644 --- a/Istio/10-PeerAuthentication/02-portLevelMtls/authentication.yaml +++ b/Istio/10-PeerAuthentication/02-portLevelMtls/authentication.yaml @@ -7,7 +7,6 @@ spec: selector: matchLabels: app: helloworld - mtls: "false" mtls: mode: STRICT portLevelMtls: -- 2.47.2 From b506b32229f9b0b266e2aeffc7c03c7e80eff9bd Mon Sep 17 00:00:00 2001 From: savagebidoof Date: Fri, 28 Apr 2023 04:11:54 +0200 Subject: [PATCH 17/25] cleanup --- .../01-disable-mTLS/authentication.yaml | 27 +------------------ 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/Istio/10-PeerAuthentication/01-disable-mTLS/authentication.yaml b/Istio/10-PeerAuthentication/01-disable-mTLS/authentication.yaml index 94cb780..fa47f34 100644 --- a/Istio/10-PeerAuthentication/01-disable-mTLS/authentication.yaml +++ b/Istio/10-PeerAuthentication/01-disable-mTLS/authentication.yaml @@ -1,16 +1,3 @@ -#apiVersion: security.istio.io/v1beta1 -#kind: PeerAuthentication -#metadata: -# name: enable-mtls -# namespace: default -#spec: -# selector: -# matchLabels: -# app: helloworld -# mtls: "true" -# mtls: -# mode: STRICT -#--- apiVersion: security.istio.io/v1beta1 kind: PeerAuthentication metadata: @@ -35,16 +22,4 @@ spec: app: helloworld mtls: "true" mtls: - mode: STRICT -# portLevelMtls: -# 443: -# mode: STRICT -#--- -#apiVersion: security.istio.io/v1beta1 -#kind: PeerAuthentication -#metadata: -# name: default-mtls -# namespace: default -#spec: -# mtls: -# mode: DISABLE + mode: STRICT \ No newline at end of file -- 2.47.2 From 26d947610acae3a75a197584483e7e704be549f7 Mon Sep 17 00:00:00 2001 From: savagebidoof Date: Fri, 28 Apr 2023 05:23:58 +0200 Subject: [PATCH 18/25] Set and documented. --- .../05-hello_world_1_Service_Entry/README.md | 188 +++++++++++++++++- .../ServiceEntry.yaml | 13 ++ .../deployment.yaml | 57 ------ .../gateway.yaml | 34 ++-- 4 files changed, 214 insertions(+), 78 deletions(-) create mode 100644 Istio/01-Simple/05-hello_world_1_Service_Entry/ServiceEntry.yaml delete mode 100755 Istio/01-Simple/05-hello_world_1_Service_Entry/deployment.yaml diff --git a/Istio/01-Simple/05-hello_world_1_Service_Entry/README.md b/Istio/01-Simple/05-hello_world_1_Service_Entry/README.md index adf6551..324c3fd 100755 --- a/Istio/01-Simple/05-hello_world_1_Service_Entry/README.md +++ b/Istio/01-Simple/05-hello_world_1_Service_Entry/README.md @@ -1,11 +1,189 @@ -# Continues from +# Description -- 01-hello_world_1_service_1_deployment +This example uses a resource `ServiceEntry` to "integrate" external resources into our `Istio Service Mesh`. + +It also explores the different behaviors between specifying the destination URL on the headers or not. + +The following page has been used for testing purposes: + +- info.cern.ch + +> **Quick disclaimer**:\ +> I have no relation with that page. + +# Configuration + +## ServiceEntry + +This `ServiceEntry` resource, defines as a destination the URL `info.cern.ch`. + +Note that location is set to `MESH_EXTERNAL` and that the resolution is set to `DNS`, this means that the resource is external to ou `Istio Service Mesh`, and the URL will be resolved through `DNS` + +Bear in mind that when Istio is communicating with resources externals to the mesh, `mTLS` is disabled. + +Also, policy enforcement is performed in the client side instead of the server side. + +> **Note:**/ +> For more information regarding the `resolution` field or the `location` field, refer to the following official Istio documentations: +> [ServiceEntry.Location](https://istio.io/latest/docs/reference/config/networking/service-entry/#ServiceEntry-Location) +> [ServiceEntry.Resolution](https://istio.io/latest/docs/reference/config/networking/service-entry/#ServiceEntry-Resolution) + +```yaml +apiVersion: networking.istio.io/v1alpha3 +kind: ServiceEntry +metadata: + name: external-cern-service +spec: + hosts: + - info.cern.ch + ports: + - number: 80 + name: http + protocol: HTTP + resolution: DNS + location: MESH_EXTERNAL +``` + +## Gateway + +Listens for `HTTP` traffic at the port `80` without limiting to any host. + +```yaml +apiVersion: networking.istio.io/v1alpha3 +kind: Gateway +metadata: + name: helloworld-gateway +spec: + selector: + istio: ingressgateway # use istio default controller + servers: + - port: + number: 80 + name: http + protocol: HTTP + hosts: + - "*" +``` -https://github.com/istio/istio/issues/29463 +## VirtualService +There has been configured 2 paths: +- "/external" +- "/external-noh" + +Both routes will forward the request towards the destination URL `info.cern.ch`. + +Highlight that the destination is `info.cern.ch`, which is the same as the contents set on the field `host` from the [ServiceEntry resource configured above](#serviceentry). + +The difference between `/external` and `/external-noh` is that the first path will contain a header named `HOST`, with the contents set to `info.cern.ch`, it being the URL from the external service. + +On the [Walkthrough](#walkthrough) section we will observe the different behaviors of these paths, being the only difference the header attributed. + +```yaml +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: helloworld-vs +spec: + hosts: + - "*" + gateways: + - helloworld-gateway + http: + - name: https-external-service + timeout: 3s + match: + - uri: + exact: "/external" + route: + - destination: + host: info.cern.ch + port: + number: 80 + rewrite: + uri: "/" + headers: + request: + set: + HOST: "info.cern.ch" + + - name: https-external-service-without-headers + timeout: 3s + match: + - uri: + exact: "/external-noh" + route: + - destination: + host: info.cern.ch + port: + number: 80 + rewrite: + uri: "/" +``` + +# Walkthrough + +## Deploy the resources + +```shell +kubectl apply -f ./ +``` +```text +serviceentry.networking.istio.io/external-cern-service created +gateway.networking.istio.io/helloworld-gateway created +virtualservice.networking.istio.io/helloworld-vs created +``` + +## Test the service + +### Get LB IP + +```shell +$ kubectl get svc -l istio=ingressgateway -A +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +istio-ingressgateway LoadBalancer 10.97.47.216 192.168.1.50 15021:31316/TCP,80:32012/TCP,443:32486/TCP 39h +``` + +### /external + +We can visualize the page contents without issues, nothing to highlight. + +```shell +curl 192.168.1.50/external +``` +```text +
+http://info.cern.ch +
+ +

http://info.cern.ch - home of the first website

+

From here you can:

+ + +``` + +### /external-noh + +We don't receive any output. + +This could be due, even if we resolve the destination IP for the URL `info.cern.ch`, the destination might have a Reverse Proxy or any other ingress resource that could condition handling this request. + +Due to the `HOST` field not being modified after we set the request, it might not be able to pass the filtering set, weather it is security wise, for example, requiring such field to allow the request; or it being a routing condition, which due not having this field specified, it's not able to route the request towards the destination desired. + +```shell +curl 192.168.1.50/external +``` +```text +``` + +# Links of interest: + +- https://istio.io/latest/docs/reference/config/networking/service-entry/#ServiceEntry-Location -Funny example I guess. -Q \ No newline at end of file diff --git a/Istio/01-Simple/05-hello_world_1_Service_Entry/ServiceEntry.yaml b/Istio/01-Simple/05-hello_world_1_Service_Entry/ServiceEntry.yaml new file mode 100644 index 0000000..db20e4d --- /dev/null +++ b/Istio/01-Simple/05-hello_world_1_Service_Entry/ServiceEntry.yaml @@ -0,0 +1,13 @@ +apiVersion: networking.istio.io/v1alpha3 +kind: ServiceEntry +metadata: + name: external-cern-service +spec: + hosts: + - info.cern.ch + ports: + - number: 80 + name: http + protocol: HTTP + resolution: DNS + location: MESH_EXTERNAL diff --git a/Istio/01-Simple/05-hello_world_1_Service_Entry/deployment.yaml b/Istio/01-Simple/05-hello_world_1_Service_Entry/deployment.yaml deleted file mode 100755 index 7bee5e1..0000000 --- a/Istio/01-Simple/05-hello_world_1_Service_Entry/deployment.yaml +++ /dev/null @@ -1,57 +0,0 @@ -# https://github.com/istio/istio/blob/master/samples/helloworld/helloworld.yaml -apiVersion: v1 -kind: Service -metadata: - name: helloworld - labels: - app: helloworld - service: helloworld -spec: - ports: - - port: 80 - name: http - selector: - app: helloworld ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: helloworld-nginx - labels: - app: helloworld -spec: - replicas: 1 - selector: - matchLabels: - app: helloworld - template: - metadata: - labels: - app: helloworld - spec: - containers: - - name: helloworld - image: nginx - resources: - requests: - cpu: "100m" - imagePullPolicy: IfNotPresent #Always - ports: - - containerPort: 80 ---- -apiVersion: networking.istio.io/v1alpha3 -kind: ServiceEntry -metadata: - name: external-svc -spec: - hosts: - - help.websiteos.com - # /websiteos/example_of_a_simple_html_page.htm -# - http://help.websiteos.com/websiteos/example_of_a_simple_html_page.htm - ports: - - number: 80 - name: http - protocol: HTTP - resolution: DNS - location: MESH_EXTERNAL ---- \ No newline at end of file diff --git a/Istio/01-Simple/05-hello_world_1_Service_Entry/gateway.yaml b/Istio/01-Simple/05-hello_world_1_Service_Entry/gateway.yaml index 7e96565..acb9481 100755 --- a/Istio/01-Simple/05-hello_world_1_Service_Entry/gateway.yaml +++ b/Istio/01-Simple/05-hello_world_1_Service_Entry/gateway.yaml @@ -1,4 +1,3 @@ -# https://github.com/istio/istio/blob/master/samples/helloworld/helloworld-gateway.yaml apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: @@ -24,29 +23,32 @@ spec: gateways: - helloworld-gateway http: - - match: - - uri: - exact: /helloworld - route: - - destination: - host: helloworld - port: - number: 80 - rewrite: - uri: "/" - - - timeout: 3s + - name: https-external-service + timeout: 3s match: - uri: exact: "/external" route: - destination: - host: help.websiteos.com + host: info.cern.ch port: number: 80 rewrite: - uri: "/websiteos/example_of_a_simple_html_page.htm" + uri: "/" headers: request: set: - HOST: "help.websiteos.com" \ No newline at end of file + HOST: "info.cern.ch" + + - name: https-external-service-without-headers + timeout: 3s + match: + - uri: + exact: "/external-noh" + route: + - destination: + host: info.cern.ch + port: + number: 80 + rewrite: + uri: "/" \ No newline at end of file -- 2.47.2 From 135d7afaf1ee3cffa031017082fdf20701100422 Mon Sep 17 00:00:00 2001 From: savagebidoof Date: Fri, 28 Apr 2023 05:25:41 +0200 Subject: [PATCH 19/25] quality improvements --- Istio/10-PeerAuthentication/02-portLevelMtls/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Istio/10-PeerAuthentication/02-portLevelMtls/README.md b/Istio/10-PeerAuthentication/02-portLevelMtls/README.md index 255cfc0..0dfb971 100644 --- a/Istio/10-PeerAuthentication/02-portLevelMtls/README.md +++ b/Istio/10-PeerAuthentication/02-portLevelMtls/README.md @@ -20,7 +20,7 @@ Through this, we can apply multiple `mTLS` behaviors under a single deployment, ## Gateway -Listens for `HTTP` traffic without limiting to any host. +Listens for `HTTP` traffic at the port `80` without limiting to any host. ```yaml apiVersion: networking.istio.io/v1alpha3 -- 2.47.2 From a5228033244e24e57f9ecb0ba251530b5d6ad1fc Mon Sep 17 00:00:00 2001 From: savagebidoof Date: Fri, 28 Apr 2023 05:29:00 +0200 Subject: [PATCH 20/25] Added cleanup field --- .../05-hello_world_1_Service_Entry/README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Istio/01-Simple/05-hello_world_1_Service_Entry/README.md b/Istio/01-Simple/05-hello_world_1_Service_Entry/README.md index 324c3fd..7de3242 100755 --- a/Istio/01-Simple/05-hello_world_1_Service_Entry/README.md +++ b/Istio/01-Simple/05-hello_world_1_Service_Entry/README.md @@ -183,6 +183,17 @@ curl 192.168.1.50/external ```text ``` +## Cleanup + +```shell +kubectl delete -f ./ +``` +```text +serviceentry.networking.istio.io "external-cern-service" deleted +gateway.networking.istio.io "helloworld-gateway" deleted +virtualservice.networking.istio.io "helloworld-vs" deleted +``` + # Links of interest: - https://istio.io/latest/docs/reference/config/networking/service-entry/#ServiceEntry-Location -- 2.47.2 From 79f819272a34a4d70cf3993f5d2fb4e77f9f4e7a Mon Sep 17 00:00:00 2001 From: savagebidoof Date: Fri, 28 Apr 2023 05:30:58 +0200 Subject: [PATCH 21/25] fixed spelling mistake --- Istio/01-Simple/05-hello_world_1_Service_Entry/README.md | 2 +- Istio/01-Simple/05-hello_world_1_Service_Entry/gateway.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Istio/01-Simple/05-hello_world_1_Service_Entry/README.md b/Istio/01-Simple/05-hello_world_1_Service_Entry/README.md index 7de3242..b895b79 100755 --- a/Istio/01-Simple/05-hello_world_1_Service_Entry/README.md +++ b/Istio/01-Simple/05-hello_world_1_Service_Entry/README.md @@ -92,7 +92,7 @@ spec: gateways: - helloworld-gateway http: - - name: https-external-service + - name: http-external-service timeout: 3s match: - uri: diff --git a/Istio/01-Simple/05-hello_world_1_Service_Entry/gateway.yaml b/Istio/01-Simple/05-hello_world_1_Service_Entry/gateway.yaml index acb9481..8d3c198 100755 --- a/Istio/01-Simple/05-hello_world_1_Service_Entry/gateway.yaml +++ b/Istio/01-Simple/05-hello_world_1_Service_Entry/gateway.yaml @@ -23,7 +23,7 @@ spec: gateways: - helloworld-gateway http: - - name: https-external-service + - name: http-external-service timeout: 3s match: - uri: -- 2.47.2 From 953943925e780d9d4ab9cf88c154631a35b34da9 Mon Sep 17 00:00:00 2001 From: savagebidoof Date: Fri, 28 Apr 2023 05:41:10 +0200 Subject: [PATCH 22/25] quality improvement. --- Istio/01-Simple/05-hello_world_1_Service_Entry/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Istio/01-Simple/05-hello_world_1_Service_Entry/README.md b/Istio/01-Simple/05-hello_world_1_Service_Entry/README.md index b895b79..f26c020 100755 --- a/Istio/01-Simple/05-hello_world_1_Service_Entry/README.md +++ b/Istio/01-Simple/05-hello_world_1_Service_Entry/README.md @@ -81,6 +81,8 @@ The difference between `/external` and `/external-noh` is that the first path wi On the [Walkthrough](#walkthrough) section we will observe the different behaviors of these paths, being the only difference the header attributed. +Also, we have set a timeout of 3 seconds towards the external services. + ```yaml apiVersion: networking.istio.io/v1alpha3 kind: VirtualService -- 2.47.2 From 2d61bd861710cb9ce79d257b31156914c88d81a2 Mon Sep 17 00:00:00 2001 From: savagebidoof Date: Fri, 28 Apr 2023 06:03:44 +0200 Subject: [PATCH 23/25] quality improvement. --- Istio/01-Simple/05-hello_world_1_Service_Entry/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Istio/01-Simple/05-hello_world_1_Service_Entry/README.md b/Istio/01-Simple/05-hello_world_1_Service_Entry/README.md index f26c020..cadf166 100755 --- a/Istio/01-Simple/05-hello_world_1_Service_Entry/README.md +++ b/Istio/01-Simple/05-hello_world_1_Service_Entry/README.md @@ -144,6 +144,8 @@ virtualservice.networking.istio.io/helloworld-vs created ```shell $ kubectl get svc -l istio=ingressgateway -A +``` +```text NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE istio-ingressgateway LoadBalancer 10.97.47.216 192.168.1.50 15021:31316/TCP,80:32012/TCP,443:32486/TCP 39h ``` @@ -180,7 +182,7 @@ This could be due, even if we resolve the destination IP for the URL `info.cern. Due to the `HOST` field not being modified after we set the request, it might not be able to pass the filtering set, weather it is security wise, for example, requiring such field to allow the request; or it being a routing condition, which due not having this field specified, it's not able to route the request towards the destination desired. ```shell -curl 192.168.1.50/external +curl 192.168.1.50/external-noh ``` ```text ``` -- 2.47.2 From e1749edab4f909b95da4e079d9d86d2ed5a017ea Mon Sep 17 00:00:00 2001 From: savagebidoof Date: Fri, 28 Apr 2023 06:06:32 +0200 Subject: [PATCH 24/25] Example set and documented. --- .../README.md | 188 ++++++++++++++++++ .../ServiceEntry.yaml | 14 ++ .../gateway.yaml | 55 +++++ .../src/github-screenshot.png | Bin 0 -> 115654 bytes 4 files changed, 257 insertions(+) create mode 100755 Istio/01-Simple/06-hello_world_1_HTTPS-Service_Entry/README.md create mode 100644 Istio/01-Simple/06-hello_world_1_HTTPS-Service_Entry/ServiceEntry.yaml create mode 100755 Istio/01-Simple/06-hello_world_1_HTTPS-Service_Entry/gateway.yaml create mode 100644 Istio/01-Simple/06-hello_world_1_HTTPS-Service_Entry/src/github-screenshot.png diff --git a/Istio/01-Simple/06-hello_world_1_HTTPS-Service_Entry/README.md b/Istio/01-Simple/06-hello_world_1_HTTPS-Service_Entry/README.md new file mode 100755 index 0000000..69813e6 --- /dev/null +++ b/Istio/01-Simple/06-hello_world_1_HTTPS-Service_Entry/README.md @@ -0,0 +1,188 @@ +# Description + +This example configures an `ServiceEntry` service pointing to a URL external to our `Istio Service Mesh`. + +The main difference with the previous example, is that on this example the resource selected requires `HTTPS` communication. + +The page used as a destination is my own [GitHub page](https://github.com/). + +# Based on + +- [05-hello_world_1_Service_Entry](../05-hello_world_1_Service_Entry) + +# Configuration + +## ServiceEntry + +This `ServiceEntry` resource, defines as a destination the URL `github.com`. + +Note that location is set to `MESH_EXTERNAL` and that the resolution is set to `DNS`, this means that the resource is external to ou `Istio Service Mesh`, and the URL will be resolved through `DNS` + +This resource listens for the port `8443`, and will connect to its destination with the port `443`, intending to handle `HTTPS` protocol traffic. + +Bear in mind that when Istio is communicating with resources externals to the mesh, `mTLS` is disabled. + +Also, policy enforcement is performed in the client side instead of the server side. + +> **Note:**/ +> For more information regarding the `resolution` field or the `location` field, refer to the following official Istio documentations: +> [ServiceEntry.Location](https://istio.io/latest/docs/reference/config/networking/service-entry/#ServiceEntry-Location) +> [ServiceEntry.Resolution](https://istio.io/latest/docs/reference/config/networking/service-entry/#ServiceEntry-Resolution) + +```yaml +apiVersion: networking.istio.io/v1alpha3 +kind: ServiceEntry +metadata: + name: external-github-service +spec: + hosts: + - github.com + ports: + - number: 8443 + name: https + protocol: HTTPS + targetPort: 443 + resolution: DNS + location: MESH_EXTERNAL +``` + +## Gateway + +Listens for `HTTP` traffic at the port `80` without limiting to any host. + +```yaml +apiVersion: networking.istio.io/v1alpha3 +kind: Gateway +metadata: + name: helloworld-gateway +spec: + selector: + istio: ingressgateway # use istio default controller + servers: + - port: + number: 80 + name: http + protocol: HTTP + hosts: + - "*" +``` + + +## VirtualService + +The path `/external` will forward the request towards the destination URL `github.com`, and path `/OriolFilter`. + + +Highlight that the destination is `github.com`, which is the same as the contents set on the field `host` from the [ServiceEntry resource configured above](#serviceentry). + +As seen [in the previous example, where the host that didn't have the `HOST` header wasn't able to receive a response by the destination](../05-hello_world_1_Service_Entry/#external-noh), we configured the `HOST` header to match the URL from the external service. + +```yaml +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: helloworld-vs +spec: + hosts: + - "*" + gateways: + - helloworld-gateway + http: + - name: https-external-service + timeout: 3s + match: + - uri: + exact: "/external" + route: + - destination: + host: "github.com" + port: + number: 8443 + rewrite: + uri: "/OriolFilter/" + headers: + request: + set: + HOST: "github.com" +``` + +## DestinationRule + +As seen in the example [02-Traffic_management/09-HTTPS-backend](../../02-Traffic_management/09-HTTPS-backend), where we configure Istio to use an `HTTPS` backend, the same configuration is applied on this case (yes, I am aware that a `ServiceEntry` is also a backend). + +For such, we deploy a `DestinationRule` setting to expect to terminate the TLS traffic, for the traffic with resource destination `github.com`, and port `8443`, which matches the settings set in our [ServiceEntry](#serviceentry) deployed. + +```yaml +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: github.com + namespace: default +spec: + host: github.com + trafficPolicy: + portLevelSettings: + - port: + number: 8443 + tls: + mode: SIMPLE +``` + +# Walkthrough + +## Deploy the resources + +```shell +kubectl apply -f ./ +``` +```text +serviceentry.networking.istio.io/external-github-service created +gateway.networking.istio.io/helloworld-gateway created +virtualservice.networking.istio.io/helloworld-vs created +destinationrule.networking.istio.io/github.com created +``` + +## Test the service + +### Get LB IP + +```shell +$ kubectl get svc -l istio=ingressgateway -A +``` +```text +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +istio-ingressgateway LoadBalancer 10.97.47.216 192.168.1.50 15021:31316/TCP,80:32012/TCP,443:32486/TCP 39h +``` + +### /external + +We can visualize the page contents without issues, nothing to highlight. + +```shell +curl 192.168.1.50/external +``` +```text +... +I mean, we can use curl but it's certainly quite an ugly output, it works tho. +... +``` + +As performing the test through `curl` is ugly, here is a screenshot of the setting working correctly. + +![github-screenshot.png](./src/github-screenshot.png) + +## Cleanup + +```shell +kubectl delete -f ./ +``` +```text +serviceentry.networking.istio.io "external-github-service" deleted +gateway.networking.istio.io "helloworld-gateway" deleted +virtualservice.networking.istio.io "helloworld-vs" deleted +destinationrule.networking.istio.io "github.com" deleted +``` + +# Links of interest: + +- https://istio.io/latest/docs/reference/config/networking/service-entry/#ServiceEntry-Location \ No newline at end of file diff --git a/Istio/01-Simple/06-hello_world_1_HTTPS-Service_Entry/ServiceEntry.yaml b/Istio/01-Simple/06-hello_world_1_HTTPS-Service_Entry/ServiceEntry.yaml new file mode 100644 index 0000000..5e19a86 --- /dev/null +++ b/Istio/01-Simple/06-hello_world_1_HTTPS-Service_Entry/ServiceEntry.yaml @@ -0,0 +1,14 @@ +apiVersion: networking.istio.io/v1alpha3 +kind: ServiceEntry +metadata: + name: external-github-service +spec: + hosts: + - github.com + ports: + - number: 8443 + name: https + protocol: HTTPS + targetPort: 443 + resolution: DNS + location: MESH_EXTERNAL \ No newline at end of file diff --git a/Istio/01-Simple/06-hello_world_1_HTTPS-Service_Entry/gateway.yaml b/Istio/01-Simple/06-hello_world_1_HTTPS-Service_Entry/gateway.yaml new file mode 100755 index 0000000..387584a --- /dev/null +++ b/Istio/01-Simple/06-hello_world_1_HTTPS-Service_Entry/gateway.yaml @@ -0,0 +1,55 @@ +apiVersion: networking.istio.io/v1alpha3 +kind: Gateway +metadata: + name: helloworld-gateway +spec: + selector: + istio: ingressgateway # use istio default controller + servers: + - port: + number: 80 + name: http + protocol: HTTP + hosts: + - "*" +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: helloworld-vs +spec: + hosts: + - "*" + gateways: + - helloworld-gateway + http: + - name: https-external-service + timeout: 3s + match: + - uri: + exact: "/external" + route: + - destination: + host: "github.com" + port: + number: 8443 + rewrite: + uri: "/OriolFilter/" + headers: + request: + set: + HOST: "github.com" +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: github.com + namespace: default +spec: + host: github.com + trafficPolicy: + portLevelSettings: + - port: + number: 8443 + tls: + mode: SIMPLE \ No newline at end of file diff --git a/Istio/01-Simple/06-hello_world_1_HTTPS-Service_Entry/src/github-screenshot.png b/Istio/01-Simple/06-hello_world_1_HTTPS-Service_Entry/src/github-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..ebf30e63b54c6e0975b0be161a70b5a79fdf88dd GIT binary patch literal 115654 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 z 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 z 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 literal 0 HcmV?d00001 -- 2.47.2 From 54538ebfaca0f720dc37edddc890c5e85e7443fb Mon Sep 17 00:00:00 2001 From: savagebidoof Date: Sun, 30 Apr 2023 22:32:06 +0200 Subject: [PATCH 25/25] Circuit Breaking uploaded --- Istio/08-monitoring/README.md | 2 + Istio/README.md | 1 + Istio/XX-CirtcuitBreaking/Deployment.yaml | 26 ++ .../XX-CirtcuitBreaking/DestinationRule.yaml | 18 + Istio/XX-CirtcuitBreaking/Fortio.yaml | 50 +++ Istio/XX-CirtcuitBreaking/README.md | 382 ++++++++++++++++++ Istio/XX-CirtcuitBreaking/Service.yaml | 17 + 7 files changed, 496 insertions(+) create mode 100755 Istio/XX-CirtcuitBreaking/Deployment.yaml create mode 100755 Istio/XX-CirtcuitBreaking/DestinationRule.yaml create mode 100644 Istio/XX-CirtcuitBreaking/Fortio.yaml create mode 100644 Istio/XX-CirtcuitBreaking/README.md create mode 100644 Istio/XX-CirtcuitBreaking/Service.yaml diff --git a/Istio/08-monitoring/README.md b/Istio/08-monitoring/README.md index e69de29..70a91fc 100644 --- a/Istio/08-monitoring/README.md +++ b/Istio/08-monitoring/README.md @@ -0,0 +1,2 @@ +https://raw.githubusercontent.com/istio/istio/release-1.17/samples/httpbin/sample-client/fortio-deploy.yaml + diff --git a/Istio/README.md b/Istio/README.md index 150371b..514fef1 100755 --- a/Istio/README.md +++ b/Istio/README.md @@ -33,6 +33,7 @@ Internal and external authentication should be set together. https://istio.io/latest/docs/ops/diagnostic-tools/proxy-cmd/ +https://istio.io/latest/docs/ops/deployment/deployment-models/ ## Services port names diff --git a/Istio/XX-CirtcuitBreaking/Deployment.yaml b/Istio/XX-CirtcuitBreaking/Deployment.yaml new file mode 100755 index 0000000..4a802ba --- /dev/null +++ b/Istio/XX-CirtcuitBreaking/Deployment.yaml @@ -0,0 +1,26 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: helloworld + labels: + app: helloworld +spec: + replicas: 1 + selector: + matchLabels: + app: helloworld + template: + metadata: + labels: + app: helloworld + spec: + containers: + - name: helloworld + image: oriolfilter/https-nginx-demo + resources: + requests: + cpu: "100m" + imagePullPolicy: IfNotPresent + ports: + - containerPort: 80 + - containerPort: 443 diff --git a/Istio/XX-CirtcuitBreaking/DestinationRule.yaml b/Istio/XX-CirtcuitBreaking/DestinationRule.yaml new file mode 100755 index 0000000..eb53015 --- /dev/null +++ b/Istio/XX-CirtcuitBreaking/DestinationRule.yaml @@ -0,0 +1,18 @@ +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: helloworld.default.svc.cluster.local +spec: + host: helloworld.default.svc.cluster.local + trafficPolicy: + connectionPool: + tcp: + maxConnections: 1 + http: + http1MaxPendingRequests: 1 + maxRequestsPerConnection: 1 + outlierDetection: + consecutive5xxErrors: 1 + interval: 1s + baseEjectionTime: 3m + maxEjectionPercent: 100 \ No newline at end of file diff --git a/Istio/XX-CirtcuitBreaking/Fortio.yaml b/Istio/XX-CirtcuitBreaking/Fortio.yaml new file mode 100644 index 0000000..1d0eae3 --- /dev/null +++ b/Istio/XX-CirtcuitBreaking/Fortio.yaml @@ -0,0 +1,50 @@ +# https://raw.githubusercontent.com/istio/istio/release-1.17/samples/httpbin/sample-client/fortio-deploy.yaml +apiVersion: v1 +kind: Service +metadata: + name: fortio + labels: + app: fortio + service: fortio +spec: + ports: + - port: 8080 + name: http + selector: + app: fortio +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: fortio-deploy +spec: + replicas: 1 + selector: + matchLabels: + app: fortio + template: + metadata: + annotations: + # This annotation causes Envoy to serve cluster.outbound statistics via 15000/stats + # in addition to the stats normally served by Istio. The Circuit Breaking example task + # gives an example of inspecting Envoy stats via proxy config. + proxy.istio.io/config: |- + proxyStatsMatcher: + inclusionPrefixes: + - "cluster.outbound" + - "cluster_manager" + - "listener_manager" + - "server" + - "cluster.xds-grpc" + labels: + app: fortio + spec: + containers: + - name: fortio + image: fortio/fortio + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8080 + name: http-fortio + - containerPort: 8079 + name: grpc-ping diff --git a/Istio/XX-CirtcuitBreaking/README.md b/Istio/XX-CirtcuitBreaking/README.md new file mode 100644 index 0000000..9d347ba --- /dev/null +++ b/Istio/XX-CirtcuitBreaking/README.md @@ -0,0 +1,382 @@ +--- +gitea: none +include_toc: true +--- + + +## Description + +This example displays how to configure a `circuit breaking` in Istio, as well on how to test it and trigger the limits sets. + +## Based on + +This example is based on the [ **OFFICIAL** Istio documentation example regarding circuit breaking](https://istio.io/latest/docs/tasks/traffic-management/circuit-breaking/). + +## Fortio + +aka. `Fortio` allows you to send traffic requests meanwhile being allowed to have some degree of control over them. + +This is useful as we will try to reach/trigger the limits set in the DestinationRule configuration. + +# Configuration + +## Service + +The service will forward incoming traffic from the service port `8080`, that will be forwarded towards the port `80` from the deployment, which contains an `HTTP` service. + + +```yaml +apiVersion: v1 +kind: Service +metadata: + name: helloworld + labels: + app: helloworld + service: helloworld +spec: + ports: + - port: 8080 + name: http + targetPort: 80 + protocol: TCP + appProtocol: http + selector: + app: helloworld +``` + +## Deployment + +The deployment listen to the port `80` and `443`, hosting an `HTTP` and `HTTPS` service respectively to the aforementioned ports. + +> **Note:**\ +> For more information about the image used refer to [here](https://hub.docker.com/r/oriolfilter/https-nginx-demo) + + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: helloworld + labels: + app: helloworld +spec: + replicas: 1 + selector: + matchLabels: + app: helloworld + template: + metadata: + labels: + app: helloworld + spec: + containers: + - name: helloworld + image: oriolfilter/https-nginx-demo + resources: + requests: + cpu: "100m" + imagePullPolicy: IfNotPresent + ports: + - containerPort: 80 + - containerPort: 443 +``` + +## Destination rule + +This destination rule configures and sets limits to the traffic with host destination `helloworld.default.svc.cluster.local`. + +- `connectionPool.tcp.maxConnections` being set to 1, limits the amount of simultaneous maximum number of connections to 1. + +- `connectionPool.http.http1MaxPendingRequests`: Number of queued requests. + +- `connectionPool.http.maxRequestsPerConnection`: Limits the amount of connections to the backend by source of the request, if 1 is set in this field (which is our scenario), it disables the keep alive configuration. + +- `outlierDetection.consecutive5xxErrors`: Number of status codes `5XX` required before a host is ejected from the connection pool. + +- `outlierDetection.interval`: Time between each analysis. + +- `outlierDetection.baseEjectionTime`: Minimum of time that a host is ejected from the connection pool. + +- `outlierDetection.maxEjectionPercent`: Maximum of hosts available to be ejected, as we set it to 100%, and as well we have only 1 deployment, whenever this rule is required to be triggered, it will allow the trigger to proceed to remove the host from the connection pool, finally resulting in all the hosts to be ejected. + +> **Note:**/ +> For more information regarding `DestinationRules` and their configuration fields, reffer to the following [official Istio documentation regarding `DestinationRules`](https://istio.io/latest/docs/reference/config/networking/destination-rule/). + +```yaml +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: helloworld.default.svc.cluster.local +spec: + host: helloworld.default.svc.cluster.local + trafficPolicy: + connectionPool: + tcp: + maxConnections: 1 + http: + http1MaxPendingRequests: 1 + maxRequestsPerConnection: 1 + outlierDetection: + consecutive5xxErrors: 1 + interval: 1s + baseEjectionTime: 3m + maxEjectionPercent: 100 +``` + + +# Walkthrough + +## Deploy resources + +```shell +kubectl apply -f ./ +``` +```text +service/helloworld created +deployment.apps/helloworld created +service/fortio created +deployment.apps/fortio-deploy created +destinationrule.networking.istio.io/helloworld.default.svc.cluster.local created +``` + +## Test deployments +### helloworld.default.svc.cluster.local +#### Check connectivity from `fortio` to `helloworld` + +We will use the package `/usr/bin/fortio` to send a `curl` request towards the `helloworld` service deployment. + +If it doesn't work, ensure that the URL And IP are the right ones, as well if there isn't a `AuthorizationPolicy` that limits the traffic (also ensure that the deployments are ready `kubectl get deployments -w -n default -owide`). + +```shell +kubectl exec -n default "$(kubectl get pod -n default -l app=fortio -o jsonpath={.items..metadata.name})" -- /usr/bin/fortio curl -quiet helloworld.default.svc.cluster.local:8080 +``` +```text +HTTP/1.1 200 OK +server: envoy +date: Sat, 29 Apr 2023 04:12:37 GMT +content-type: text/html +content-length: 15 +last-modified: Tue, 25 Apr 2023 00:47:17 GMT +etag: "64472315-f" +strict-transport-security: max-age=7200 +accept-ranges: bytes +x-envoy-upstream-service-time: 100 + +

Howdy

+``` + +#### Perform a stress test of the resources deployed + +Through the `Fortio` container, we will execute the following command `fortio load -c 2 -qps 0 -n 20`: + +- `fortio`: The package used. +- `load`: It's used to gather statistics. +- `-c 2`: Number of simultaneous connections. +- `-qps 0`: Queries per second, if it's set to `0`, means that there is no limit and will try to send it as fast/maximum as possible. +- `-n 20`: Send 20 queries. + +> **Note:**\ +> For more information regarding the available possible command configurations, refer to the respective [Fortio documentation on Github](https://github.com/fortio/fortio#command-line-arguments) + +```shell +kubectl exec -n default "$(kubectl get pod -n default -l app=fortio -o jsonpath={.items..metadata.name})" -c fortio -- /usr/bin/fortio load -c 2 -qps 0 -n 20 -loglevel Warning helloworld.default.svc.cluster.local:8080 +``` + +```text +04:16:28 I logger.go:183> Log level is now 3 Warning (was 2 Info) +Fortio 1.54.2 running at 0 queries per second, 8->8 procs, for 20 calls: helloworld.default.svc.cluster.local:8080 +04:16:28 W http_client.go:170> Assuming http:// on missing scheme for 'helloworld.default.svc.cluster.local:8080' +Starting at max qps with 2 thread(s) [gomax 8] for exactly 20 calls (10 per thread + 0) +04:16:28 W http_client.go:1058> [0] Non ok http code 503 (HTTP/1.1 503) +04:16:28 W http_client.go:1058> [0] Non ok http code 503 (HTTP/1.1 503) +04:16:28 W http_client.go:1058> [0] Non ok http code 503 (HTTP/1.1 503) +04:16:28 W http_client.go:1058> [1] Non ok http code 503 (HTTP/1.1 503) +04:16:28 W http_client.go:1058> [0] Non ok http code 503 (HTTP/1.1 503) +04:16:28 W http_client.go:1058> [1] Non ok http code 503 (HTTP/1.1 503) +04:16:28 W http_client.go:1058> [0] Non ok http code 503 (HTTP/1.1 503) +04:16:28 W http_client.go:1058> [1] Non ok http code 503 (HTTP/1.1 503) +Ended after 259.87612ms : 20 calls. qps=76.96 +Aggregated Function Time : count 20 avg 0.02460594 +/- 0.02677 min 0.0010981 max 0.090700113 sum 0.492118798 +# range, mid point, percentile, count +>= 0.0010981 <= 0.002 , 0.00154905 , 15.00, 3 +> 0.002 <= 0.003 , 0.0025 , 20.00, 1 +> 0.003 <= 0.004 , 0.0035 , 25.00, 1 +> 0.005 <= 0.006 , 0.0055 , 30.00, 1 +> 0.007 <= 0.008 , 0.0075 , 40.00, 2 +> 0.011 <= 0.012 , 0.0115 , 45.00, 1 +> 0.012 <= 0.014 , 0.013 , 55.00, 2 +> 0.016 <= 0.018 , 0.017 , 60.00, 1 +> 0.018 <= 0.02 , 0.019 , 65.00, 1 +> 0.025 <= 0.03 , 0.0275 , 75.00, 2 +> 0.03 <= 0.035 , 0.0325 , 80.00, 1 +> 0.05 <= 0.06 , 0.055 , 85.00, 1 +> 0.07 <= 0.08 , 0.075 , 95.00, 2 +> 0.09 <= 0.0907001 , 0.0903501 , 100.00, 1 +# target 50% 0.013 +# target 75% 0.03 +# target 90% 0.075 +# target 99% 0.0905601 +# target 99.9% 0.0906861 +Error cases : count 8 avg 0.012249498 +/- 0.01797 min 0.0010981 max 0.054279663 sum 0.097995986 +# range, mid point, percentile, count +>= 0.0010981 <= 0.002 , 0.00154905 , 37.50, 3 +> 0.002 <= 0.003 , 0.0025 , 50.00, 1 +> 0.003 <= 0.004 , 0.0035 , 62.50, 1 +> 0.005 <= 0.006 , 0.0055 , 75.00, 1 +> 0.025 <= 0.03 , 0.0275 , 87.50, 1 +> 0.05 <= 0.0542797 , 0.0521398 , 100.00, 1 +# target 50% 0.003 +# target 75% 0.006 +# target 90% 0.0508559 +# target 99% 0.0539373 +# target 99.9% 0.0542454 +# Socket and IP used for each connection: +[0] 6 socket used, resolved to 10.99.49.188:8080, connection timing : count 6 avg 0.00037803967 +/- 0.0001574 min 0.000231869 max 0.00069415 sum 0.002268238 +[1] 4 socket used, resolved to 10.99.49.188:8080, connection timing : count 4 avg 0.00045579175 +/- 9.155e-05 min 0.0003847 max 0.000612777 sum 0.001823167 +Connection time (s) : count 10 avg 0.0004091405 +/- 0.0001403 min 0.000231869 max 0.00069415 sum 0.004091405 +Sockets used: 10 (for perfect keepalive, would be 2) +Uniform: false, Jitter: false, Catchup allowed: true +IP addresses distribution: +10.99.49.188:8080: 10 +Code 200 : 12 (60.0 %) +Code 503 : 8 (40.0 %) +Response Header Sizes : count 20 avg 167.7 +/- 136.9 min 0 max 280 sum 3354 +Response Body/Total Sizes : count 20 avg 273.1 +/- 26.21 min 241 max 295 sum 5462 +All done 20 calls (plus 0 warmup) 24.606 ms avg, 77.0 qps +``` + +#### Information to highlight + +From the output received, I would like to focus in the following entry, which states that 60% of the traffic was successful (returning a status code `200`), meanwhile 40% failed (returning status code `503`). + +```text +Code 200 : 12 (60.0 %) +Code 503 : 8 (40.0 %) +``` + +### Check Fortio istio-proxy logs (`pilot-agent request GET stats`) + +Check (Fortio's app) istio-proxy logs + +```shell +kubectl exec "$(kubectl get pod -n default -l app=fortio -o jsonpath={.items..metadata.name})" -c istio-proxy -- pilot-agent request GET stats | grep helloworld | grep pending +``` +```text +cluster.outbound|8080||helloworld.default.svc.cluster.local.circuit_breakers.default.remaining_pending: 1 +cluster.outbound|8080||helloworld.default.svc.cluster.local.circuit_breakers.default.rq_pending_open: 0 +cluster.outbound|8080||helloworld.default.svc.cluster.local.circuit_breakers.high.rq_pending_open: 0 +cluster.outbound|8080||helloworld.default.svc.cluster.local.upstream_rq_pending_active: 0 +cluster.outbound|8080||helloworld.default.svc.cluster.local.upstream_rq_pending_failure_eject: 0 +cluster.outbound|8080||helloworld.default.svc.cluster.local.upstream_rq_pending_overflow: 3 +cluster.outbound|8080||helloworld.default.svc.cluster.local.upstream_rq_pending_total: 18 +``` + +If we review the field `upstream_rq_pending_overflow`, where it states that the value is set to `3`, it means that 3 entries where flagged for circuit breaking. + +## helloworld.default + +### Test destination URL `helloworld.default` + +Same procedure as the step [Perform a stress test of the resources deployed](#perform-a-stress-test-of-the-resources-deployed), but instead of using the destination URL `helloworld.default.svc.cluster.local`, we will be using the URL `helloworld.default` to confirm if the destination rule is still being applied even if the full URL doesn't match. + +```shell +kubectl exec -n default "$(kubectl get pod -n default -l app=fortio -o jsonpath={.items..metadata.name})" -c fortio -- /usr/bin/fortio load -c 2 -qps 0 -n 20 -loglevel Warning helloworld.default:8080 +``` + +```text +20:01:10 I logger.go:183> Log level is now 3 Warning (was 2 Info) +Fortio 1.54.2 running at 0 queries per second, 8->8 procs, for 20 calls: helloworld.default:8080 +20:01:10 W http_client.go:170> Assuming http:// on missing scheme for 'helloworld.default:8080' +Starting at max qps with 2 thread(s) [gomax 8] for exactly 20 calls (10 per thread + 0) +20:01:10 W http_client.go:1058> [0] Non ok http code 503 (HTTP/1.1 503) +20:01:10 W http_client.go:1058> [0] Non ok http code 503 (HTTP/1.1 503) +20:01:10 W http_client.go:1058> [1] Non ok http code 503 (HTTP/1.1 503) +20:01:10 W http_client.go:1058> [0] Non ok http code 503 (HTTP/1.1 503) +20:01:10 W http_client.go:1058> [1] Non ok http code 503 (HTTP/1.1 503) +20:01:10 W http_client.go:1058> [1] Non ok http code 503 (HTTP/1.1 503) +20:01:10 W http_client.go:1058> [0] Non ok http code 503 (HTTP/1.1 503) +20:01:10 W http_client.go:1058> [1] Non ok http code 503 (HTTP/1.1 503) +20:01:10 W http_client.go:1058> [1] Non ok http code 503 (HTTP/1.1 503) +20:01:10 W http_client.go:1058> [1] Non ok http code 503 (HTTP/1.1 503) +20:01:10 W http_client.go:1058> [0] Non ok http code 503 (HTTP/1.1 503) +Ended after 162.18683ms : 20 calls. qps=123.31 +Aggregated Function Time : count 20 avg 0.014704745 +/- 0.01561 min 0.001096933 max 0.044131073 sum 0.294094897 +# range, mid point, percentile, count +>= 0.00109693 <= 0.002 , 0.00154847 , 30.00, 6 +> 0.003 <= 0.004 , 0.0035 , 35.00, 1 +> 0.004 <= 0.005 , 0.0045 , 45.00, 2 +> 0.005 <= 0.006 , 0.0055 , 50.00, 1 +> 0.006 <= 0.007 , 0.0065 , 55.00, 1 +> 0.011 <= 0.012 , 0.0115 , 60.00, 1 +> 0.014 <= 0.016 , 0.015 , 65.00, 1 +> 0.016 <= 0.018 , 0.017 , 70.00, 1 +> 0.018 <= 0.02 , 0.019 , 75.00, 1 +> 0.035 <= 0.04 , 0.0375 , 90.00, 3 +> 0.04 <= 0.0441311 , 0.0420655 , 100.00, 2 +# target 50% 0.006 +# target 75% 0.02 +# target 90% 0.04 +# target 99% 0.043718 +# target 99.9% 0.0440898 +Error cases : count 11 avg 0.0029070851 +/- 0.001827 min 0.001096933 max 0.006039404 sum 0.031977936 +# range, mid point, percentile, count +>= 0.00109693 <= 0.002 , 0.00154847 , 54.55, 6 +> 0.003 <= 0.004 , 0.0035 , 63.64, 1 +> 0.004 <= 0.005 , 0.0045 , 81.82, 2 +> 0.005 <= 0.006 , 0.0055 , 90.91, 1 +> 0.006 <= 0.0060394 , 0.0060197 , 100.00, 1 +# target 50% 0.00190969 +# target 75% 0.004625 +# target 90% 0.0059 +# target 99% 0.00603507 +# target 99.9% 0.00603897 +# Socket and IP used for each connection: +[0] 6 socket used, resolved to 10.98.152.137:8080, connection timing : count 6 avg 0.00047558633 +/- 0.0001364 min 0.000294869 max 0.000739941 sum 0.002853518 +[1] 7 socket used, resolved to 10.98.152.137:8080, connection timing : count 7 avg 0.000457311 +/- 0.0001076 min 0.000320826 max 0.000596445 sum 0.003201177 +Connection time (s) : count 13 avg 0.00046574577 +/- 0.0001221 min 0.000294869 max 0.000739941 sum 0.006054695 +Sockets used: 13 (for perfect keepalive, would be 2) +Uniform: false, Jitter: false, Catchup allowed: true +IP addresses distribution: +10.98.152.137:8080: 13 +Code 200 : 9 (45.0 %) +Code 503 : 11 (55.0 %) +Response Header Sizes : count 20 avg 125.9 +/- 139.2 min 0 max 280 sum 2518 +Response Body/Total Sizes : count 20 avg 265.2 +/- 26.76 min 241 max 295 sum 5304 +All done 20 calls (plus 0 warmup) 14.705 ms avg, 123.3 qps +``` + +As we can see, the rules are still being applied, this time resulting in a 45% of the traffic receiving a successful status code (`200`), meanwhile a 55% received a failure status code (`503`). + +This confirms that, even if the full URL is not the same as the configured in the [DestinationRule](#destination-rule), the DestinationRule is still being enforced. + + + +## Cleanup + +```shell +kubectl delete -f ./ +``` + +```text +deployment.apps "helloworld" deleted +destinationrule.networking.istio.io "helloworld.default.svc.cluster.local" deleted +service "fortio" deleted +deployment.apps "fortio-deploy" deleted +service "helloworld" deleted +``` + +## Links of interest + +- https://raw.githubusercontent.com/istio/istio/release-1.17/samples/httpbin/sample-client/fortio-deploy.yaml + +- https://istio.io/latest/docs/tasks/traffic-management/circuit-breaking/#adding-a-client + +- https://github.com/fortio/fortio + +- https://github.com/fortio/fortio#command-line-arguments + +- https://istio.io/latest/docs/reference/config/networking/destination-rule/ + +- https://istio.io/latest/docs/reference/config/networking/destination-rule/#ConnectionPoolSettings-TCPSettings + +- https://istio.io/latest/docs/reference/config/networking/destination-rule/#ConnectionPoolSettings-HTTPSettings \ No newline at end of file diff --git a/Istio/XX-CirtcuitBreaking/Service.yaml b/Istio/XX-CirtcuitBreaking/Service.yaml new file mode 100644 index 0000000..e761dd0 --- /dev/null +++ b/Istio/XX-CirtcuitBreaking/Service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: helloworld + labels: + app: helloworld + service: helloworld +spec: + ports: + - port: 8080 + name: http + targetPort: 80 + protocol: TCP + appProtocol: http + selector: + app: helloworld +--- -- 2.47.2