<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Fei Y</title>
    <description>The latest articles on DEV Community by Fei Y (@yaofei).</description>
    <link>https://dev.to/yaofei</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F214448%2F3ec62bb8-e827-4eb1-89dd-196d1a3846ae.jpg</url>
      <title>DEV Community: Fei Y</title>
      <link>https://dev.to/yaofei</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yaofei"/>
    <language>en</language>
    <item>
      <title>Secure Microservice using Istio w/ JWT</title>
      <dc:creator>Fei Y</dc:creator>
      <pubDate>Fri, 22 Jul 2022 20:33:00 +0000</pubDate>
      <link>https://dev.to/yaofei/secure-microservice-using-istio-w-jwt-3co7</link>
      <guid>https://dev.to/yaofei/secure-microservice-using-istio-w-jwt-3co7</guid>
      <description>&lt;p&gt;Istio &lt;a href="https://istio.io/latest/docs/concepts/security/"&gt;security pillar&lt;/a&gt; provides a comprehensive solution to secure your workloads with strong identity (with optional integration with &lt;a href="https://spiffe.io/docs/latest/spire-about/"&gt;SPIRE&lt;/a&gt; since v1.14), authentication, and authorization (with optional integration with &lt;a href="https://www.openpolicyagent.org/docs/latest/envoy-tutorial-istio/"&gt;OPA&lt;/a&gt;). Istio sidecar (service proxy) and ingressgateway (edge proxy) serve as the &lt;a href="https://csrc.nist.gov/glossary/term/pep"&gt;PEP&lt;/a&gt; for the entire mesh.&lt;/p&gt;

&lt;p&gt;According to &lt;a href="https://csrc.nist.gov/publications/detail/sp/800-207/final"&gt;NIST SP 800-207&lt;/a&gt; that &lt;strong&gt;no resource is inherently trusted&lt;/strong&gt; that 1) every asset (service) MUST have its security posture evaluated via a PEP before a request is granted ... 2) and the evaluation should be continual for as long as the session lasts. Istio typically operates at L7. And in order to implement Defense-in-Depth strategy we also need to complement it with L3/4 enforcement through &lt;a href="https://kubernetes.io/docs/concepts/services-networking/network-policies/"&gt;NetworkPolicy&lt;/a&gt;. Please check out &lt;a href="https://istio.io/latest/docs/tasks/traffic-management/egress/egress-gateway/#additional-security-considerations"&gt;additional security considerations&lt;/a&gt;, and we won't talk about it. Instead, let me walk you through how to protect the workload using Istio with &lt;a href="https://jwt.io/"&gt;JWT&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can acquire a free JWT token through &lt;a href="https://auth0.com/"&gt;auth0&lt;/a&gt;. We'll use it for the &lt;a href="https://github.com/feiyao/istio_authn-authz_jwt#setup-auth0"&gt;demo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Obviously, we need to enforce mTLS using PeerAuthentication. By default, Istio &lt;code&gt;enableAutoMtls&lt;/code&gt; is set to &lt;code&gt;true&lt;/code&gt; but it was in &lt;code&gt;PERMISSIVE&lt;/code&gt; mode.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: istio-system
spec:
  mtls:
    mode: STRICT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need to make sure the request MUST be denied at the edge (istio-ingressgateway) either without JWT token or invalid JWT token with the combination of &lt;code&gt;RequestAuthentication&lt;/code&gt; and &lt;code&gt;AuthorizationPolicy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;RequesetAuthentication&lt;/code&gt; CRD will make sure JWT token adheres to the &lt;code&gt;jwtRules&lt;/code&gt; specified.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
  name: jwt-authn-gw
  namespace: istio-gateway
spec:
  selector:
    matchLabels:
      istio: ingressgateway
  jwtRules:
  - issuer: "https://dev-wl5b26zy.us.auth0.com/"
    jwksUri: "https://dev-wl5b26zy.us.auth0.com/.well-known/jwks.json"
    audiences:
    - "https://httpbin/api"
    forwardOriginalToken: true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;JWT Token typically uses RS256(RSA Signature with SHA-256) as the asymmetric signing algorithm. Istio will make sure the token is indeed valid and tamper-proof by verifying the digital signature through &lt;code&gt;jwksUri&lt;/code&gt;. When a request comes in it goes through various &lt;a href="https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/http/http_filters"&gt;HTTP filters&lt;/a&gt;, and one of them is &lt;code&gt;envoy.filters.http.jwt_authn&lt;/code&gt;. To be more efficient the JWKS will be "cached" in &lt;code&gt;localJwks&lt;/code&gt; to improve the performance instead of every time having outbound call to the &lt;code&gt;jwksUri&lt;/code&gt; endpoint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
{
    "name": "envoy.filters.http.jwt_authn",
    "typedConfig": {
        "@type": "type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication",
        "providers": {
            "origins-0": {
                "issuer": "https://dev-wl5b26zy.us.auth0.com/",
                "audiences": [
                    "https://httpbin/api"
                ],
                "localJwks": {
                    "inlineString": "..."
                },
                "forward": true,
                "payloadInMetadata": "https://dev-wl5b26zy.us.auth0.com/"
            }
        },
...
}
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;istio_authn&lt;/code&gt; filter is right after the &lt;code&gt;jwt_authn&lt;/code&gt; filter. And it tries to verify the issuer as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "name": "istio_authn",
    "typedConfig": {
        "@type": "type.googleapis.com/istio.envoy.config.filter.http.authn.v2alpha1.FilterConfig",
        "policy": {
            "origins": [
                {
                    "jwt": {
                        "issuer": "https://dev-wl5b26zy.us.auth0.com/"
                    }
                }
            ],
            "originIsOptional": true,
            "principalBinding": "USE_ORIGIN"
        },
        "skipValidateTrustDomain": true
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just with &lt;code&gt;RequesetAuthentication&lt;/code&gt; will not be enough since &lt;a href="https://istio.io/latest/docs/reference/config/security/request_authentication/"&gt;a request without any authentication credentials will be accepted but will not have any authenticated identity&lt;/a&gt;. That's why it's quintessential to have &lt;code&gt;AuthorizationPolicy&lt;/code&gt; in place to enforce.&lt;/p&gt;

&lt;p&gt;First, it stipulates that the request will be denied at istio-ingressgateway if it doesn't have the &lt;code&gt;RequestPrincipal&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: "security.istio.io/v1beta1"
kind: AuthorizationPolicy
metadata:
  name: deny-jwt-gw
  namespace: istio-gateway
spec:
  selector:
    matchLabels:
      istio: ingressgateway
  action: DENY
  rules:
  - from:
    - source:
        notRequestPrincipals: ["*"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Second, at the service level it has to satisfy the claims that the workload expects. In order to make it work it's important to have &lt;code&gt;forwardOriginalToken: true&lt;/code&gt; to allow JWT token to be forwarded from the istio-ingressgateway to the workload.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: require-jwt-httpbin
  namespace: default
spec:
  selector:
    matchLabels:
      app: httpbin
  action: ALLOW
  rules:
  - from:
    - source:
        requestPrincipals: ["https://dev-wl5b26zy.us.auth0.com//8qPXVf5npNa4yXmeyHhnGh5GDgrDK3B5@clients"]
    when:
    - key: request.auth.claims[scope]
      values: ["read:messages"]
    - key: request.auth.claims[aud]
      values: ["https://httpbin/api"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These enforcement have been "encoded" into workload's sidecar EnvoyProxy HTTP Filter called &lt;code&gt;envoy.filters.http.rbac&lt;/code&gt;. Any violation will cause &lt;code&gt;403 Forbidden&lt;/code&gt; with the error &lt;code&gt;RBAC: access denied&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "name": "envoy.filters.http.rbac",
    "typedConfig": {
        "@type": "type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC",
        "rules": {
            "policies": {
                "ns[default]-policy[require-jwt-httpbin]-rule[0]": {
                    "permissions": [
                        {
                            "andRules": {
                                "rules": [
                                    {
                                        "any": true
                                    }
                                ]
                            }
                        }
                    ],
                    "principals": [
                        {
                            "andIds": {
                                "ids": [
                                    {
                                        "orIds": {
                                            "ids": [
                                                {
                                                    "metadata": {
                                                        "filter": "istio_authn",
                                                        "path": [
                                                            {
                                                                "key": "request.auth.principal"
                                                            }
                                                        ],
                                                        "value": {
                                                            "stringMatch": {
                                                                "exact": "https://dev-wl5b26zy.us.auth0.com//8qPXVf5npNa4yXmeyHhnGh5GDgrDK3B5@clients"
                                                            }
                                                        }
                                                    }
                                                }
                                            ]
                                        }
                                    },
...
        },
        "shadowRulesStatPrefix": "istio_dry_run_allow_"
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thanks for reading, and learning with me. If you want to run it yourself here is the &lt;a href="https://github.com/feiyao/istio_authn-authz_jwt"&gt;source code&lt;/a&gt; and instructions.&lt;/p&gt;

&lt;p&gt;Voila!&lt;/p&gt;

</description>
      <category>istio</category>
      <category>zta</category>
      <category>auth0</category>
      <category>microservices</category>
    </item>
    <item>
      <title>Gatekeeper with Istio</title>
      <dc:creator>Fei Y</dc:creator>
      <pubDate>Tue, 19 Jul 2022 00:40:00 +0000</pubDate>
      <link>https://dev.to/yaofei/gatekeeper-with-istio-3f56</link>
      <guid>https://dev.to/yaofei/gatekeeper-with-istio-3f56</guid>
      <description>&lt;p&gt;For those who have been using &lt;a href="https://istio.io/"&gt;Istio&lt;/a&gt; to manage microservices you shouldn't be too surprised about its superpower. While working with various teams and organizations within the Enterprise I have to admit that Istio sometimes can be very complicated. Fortunately, Google, &lt;a href="https://www.solo.io"&gt;Solo&lt;/a&gt;, and many others continue to drive the innovation in this space so you can eventually benefit as the end user.&lt;/p&gt;

&lt;p&gt;According to &lt;a href="https://www.nist.gov/blogs/taking-measure/zero-trust-cybersecurity-never-trust-always-verify"&gt;ZTA&lt;/a&gt; we should &lt;strong&gt;Never Trust, Always Verify&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In this article I want to touch upon how to use PaC (aka. Policy-As-Code) to enforce the correct implementation of the Istio (to be clear that there is no absolute right or wrong, but by following the best practices you achieve the correctness for the time being), for example &lt;a href="https://istio.io/latest/docs/ops/configuration/traffic-management/protocol-selection/"&gt;Protocol Selection&lt;/a&gt;. By default, Istio can automatically detect HTTP(/2) traffic otherwise it will be treated as plain TCP traffic. As the &lt;a href="https://peps.python.org/pep-0020/"&gt;Zen of Python&lt;/a&gt; teaches us that &lt;strong&gt;Explicit is better than implicit&lt;/strong&gt;. We should always strive for code's readability and maintainability. So, let's enforce this rule at design-time and run-time.&lt;/p&gt;

&lt;p&gt;This article is not meant for a deep dive on &lt;a href="https://www.openpolicyagent.org/docs/latest/policy-language/"&gt;Rego&lt;/a&gt; which I will leave it to the readers.&lt;/p&gt;

&lt;p&gt;According to &lt;a href="https://istio.io/latest/docs/ops/configuration/traffic-management/protocol-selection/#explicit-protocol-selection"&gt;Istio Explicit Protocol Selection&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This can be configured in two ways:&lt;/p&gt;

&lt;p&gt;By the name of the port: &lt;code&gt;name: &amp;lt;protocol&amp;gt;[-&amp;lt;suffix&amp;gt;]&lt;/code&gt;.&lt;br&gt;
In Kubernetes 1.18+, by the appProtocol field: &lt;code&gt;appProtocol: &amp;lt;protocol&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's dissect that.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;port: name&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can definitely use the build-in function &lt;a href="https://www.openpolicyagent.org/docs/v0.12.2/language-reference/#regex"&gt;re_match&lt;/a&gt;, but it might complicate the Gatekeeper's ConstraintTemplate which we will talked about it in a moment. Instead, we can just &lt;code&gt;split&lt;/code&gt; the port_name by &lt;code&gt;-&lt;/code&gt;, and check its membership.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;protocol := split(port_name, "-")[0]
protocol in protocols
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;port: appProtocol&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The are a couple of ways to check the membership:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#1 Unification, which is different from `:=` and `==` in Rego.

protocol = protocols[_]

#2 `in`, which requires to `import future.keywords`

protocol in protocols

#3 Set

protocol_set := { p | p := input.parameters.protocols[_] }
protocol_set[protocol]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By following &lt;a href="https://www.styra.com/"&gt;Styra&lt;/a&gt;'s &lt;a href="https://github.com/StyraInc/rego-style-guide#use-in-to-check-for-membership"&gt;Rego Style Guide&lt;/a&gt; the option #2 will be the preferred way.&lt;/p&gt;

&lt;p&gt;There is one little subtlety which we haven't talked about yet. When &lt;a href="https://kubernetes.io/docs/concepts/services-networking/service/"&gt;Service&lt;/a&gt; has both port name and port appProtocol the latter takes precedence by Istio. So, how do we express that in Rego?&lt;/p&gt;

&lt;p&gt;In Rego within the Rule the &lt;code&gt;AND&lt;/code&gt; condition implies which means all Rule body needs to be True to make this Rule True. The &lt;code&gt;OR&lt;/code&gt; condition is achieved by multiple Rules with the same Rule name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# when port.appProtocl exists just use it and ignore port name altogether.
_is_valid(port, protocols) {
  port.appProtocol

  _match_app_protocol(port.appProtocol, protocols)
}

# when port.appProtocol doesn't exit port name has to exist and match the protocols we specified.
_is_valid(port, protocols) {
  not port.appProtocol
  port.name

  _match_port_name(port.name, protocols)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's put it all together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package istio.security.protocolselection

import future.keywords

violation[{"msg": msg}] {
  protocols := input.parameters.protocols

  some port in input.review.object.spec.ports
  not _is_valid(port, protocols)

  msg := sprintf("port: %v name or appProtocol is invalid", [port])
}

_is_valid(port, protocols) {
  port.appProtocol

  _match_app_protocol(port.appProtocol, protocols)
}

_is_valid(port, protocols) {
  not port.appProtocol
  port.name

  _match_port_name(port.name, protocols)
}

_match_app_protocol(protocol, protocols) {
  protocol in protocols
}

_match_port_name(port_name, protocols) {
  protocol := split(port_name, "-")[0]

  protocol in protocols
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we have the hardest part resolved and let's turn our attention to the &lt;a href="https://github.com/open-policy-agent/gatekeeper"&gt;OPA Gatekeeper&lt;/a&gt;. Gatekeeper uses the &lt;a href="https://github.com/open-policy-agent/frameworks/tree/master/constraint"&gt;OPA Constraint Framework&lt;/a&gt; to describe and enforce policy. Right now there are mainly 3 parts we should pay attention:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;ContraintTemplate: describe both Rego that enforces the constraint and the schema of the contraint.&lt;/li&gt;
&lt;li&gt;Constraint: describe what ContraintTemplate needs to be enforced, and how.&lt;/li&gt;
&lt;li&gt;Config: describe behaviors for certain processes.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  1: ContraintTemplate
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  annotations:
    description: Explicit protocol selection either by name or appProtocol
  name: istioexplicitprotocolselection
spec:
  crd:
    spec:
      names:
        kind: IstioExplicitProtocolSelection
      validation:
        openAPIV3Schema:
          type: object
          properties:
            prefixes:
              type: string
            protocols:
              type: array
              items:
                type: string
  targets:
  - target: admission.k8s.gatekeeper.sh
    rego: |-
      package istio.security.protocolselection

      import future.keywords

      violation[{"msg": msg}] {
        protocols := input.parameters.protocols

        some port in input.review.object.spec.ports
        not _is_valid(port, protocols)

        msg := sprintf("port: %v name or appProtocol is invalid", [port])
      }

      _is_valid(port, protocols) {
        port.appProtocol

        _match_app_protocol(port.appProtocol, protocols)
      }

      _is_valid(port, protocols) {
        not port.appProtocol
        port.name

        _match_port_name(port.name, protocols)
      }

      _match_app_protocol(protocol, protocols) {
        protocol in protocols
      }

      _match_port_name(port_name, protocols) {
        protocol := split(port_name, "-")[0]

        protocol in protocols
      }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2: Constraint
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: constraints.gatekeeper.sh/v1beta1
kind: IstioExplicitProtocolSelection
metadata:
  name: explicitprotocolselection
spec:
  enforcementAction: deny
  match:
    kinds:
    - apiGroups:
      - ""
      kinds:
      - Service
  parameters:
    protocols:
    - http
    - https
    - http2
    - grpc
    - grpc-web
    - tcp
    - tls
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3: Config
&lt;/h4&gt;

&lt;p&gt;We will use it to ignore all namespaces that we don't want to enforce the policy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: config.gatekeeper.sh/v1alpha1
kind: Config
metadata:
  name: config
  namespace: "gatekeeper-system"
spec:
  match:
    - excludedNamespaces: ["kube-*", "istio-*"]
      processes: ["*"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Voila!&lt;/p&gt;

&lt;p&gt;OPA Gatekeeper has more than I just showed you here. I'll leave it to you to explore: Gator, Mutation, Audit, Replication, etc.&lt;/p&gt;

&lt;p&gt;Ref: &lt;a href="https://github.com/feiyao/gatekeeper-istio"&gt;https://github.com/feiyao/gatekeeper-istio&lt;/a&gt;&lt;/p&gt;

</description>
      <category>gatekeeper</category>
      <category>istio</category>
      <category>opa</category>
    </item>
  </channel>
</rss>
