Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Zero trust security mechanism in mesh #14002

Open
wants to merge 62 commits into
base: 3.3-dev-xds
Choose a base branch
from

Conversation

namelessssssssssss
Copy link
Contributor

@namelessssssssssss namelessssssssssss commented Mar 27, 2024

What is the purpose of the change

This is a draft commit of xDS security support. Mainly including:

  • Role based Authorization framework
  • mTLS support in istio mesh

Proposal (Chinese)

背景

随着微服务架构的广泛应用,服务间的安全通信与访问控制变得至关重要。Dubbo作为业界领先的RPC框架,其安全性同样不容忽视。本提案旨在构建一套全面、细致的Dubbo零信任安全体系,通过mTLS认证和鉴权机制,确保服务间通信的机密性、完整性和可访问性,从而提升整个微服务架构的安全性。

img

目标:为Dubbo提供一套零信任安全实现,包含数据面和可选的控制面。

  • 数据面:使Dubbo框架实现安全相关xDS协议(SDS、LDS、CDS等),接收控制面下发的安全资源并进行解析,通过解析的规则配置安全性能力。
  • 控制面(可选):提供一套Dubbo定制的控制面,使其更贴合Dubbo自身特性。也可直接选择使用现有的控制面作为xDS server(如istio)

实现概览

未命名文件

概述

mesh下的零信任安全体系,关注于两个维度的安全性

  • 验证(Authentication,Authn),关注于保证通信对端服务的信息可信且未被篡改。
  • 鉴权(Authorization,Authz),关注于对可信服务进行权限验证,确认其是否有访问目标资源的权限

在实践中,鉴权和验证通常在L4(传输层)和L7(应用层)进行,代表协议为TCP协议与HTTP协议。

1、传输层下的验证模型

传输层通常通过TLS/SSL加密实现验证。对端通过证书表示自身身份,被调用方通过查找信任存储库、验证信任链来确定证书是否可信。相比常见的单向TLS(一方提供证书,另一方验证证书),mutual(双向)TLS要求通信双方均提供证书并进行验证。

证书由集群内的证书签发服务签发。通常由服务发起一个包含了新生成证书的CSR请求,证书签发服务为证书签名,并确保它可以被信任链中的根证书或某个中间证书验证

信任存储由集群内的证书管理服务提供,可能与证书签发服务相同。服务通过它获取信任存储,通常是根证书和其签发的部分中间证书的集合。

当对端提供证书的签名可以被信任存储中的任一证书的公钥验证,则认为对端证书可信。

1.1 Istio的mTLS验证模型

id-prov

1、istio下服务部署时,其istio agent(和envoy一样在服务pod部署)会首先生成一个证书,并向istiod:15012/IstioCertificateService 发送一条包含证书的CSR请求,为证书签名。

2、此处服务需要通过其pod下的k8s(第一方)或 istio(第三方)ServiceAccount JWT Token 向 istiod 表明身份,以建立最初的信任关系。

3、签名完成后,istio agent 作为SDS服务端,以SDS协议将证书提供给envoy,enovy 再通过该证书向 istiod:15012/AdsService 安全端口建立连接,并进行Xds协议的后续调用。

在该场景下,istio agent永远作为SDS服务端向envoy提供服务,自身则直接与istiod通信,完成证书签名、轮换等工作。

4、在与控制面的Ads服务建立安全连接后,后续服务间通过envoy进行通信时均使用该证书建立mTLS连接。

5、证书的轮换由agent周期性发送CSR请求实现。服务的证书不会在其严格到期时再轮换,而是在其快到期时就尝试签名新证书。IstioCertificateService会将最新的信任链作为CSR请求响应返回,服务则可以使用该信任链更新信任存储。

image

1.2 X.509 SPIFFE Identity

SPIFFE是 Secure Production Identity Framework for Everyone(适用于所有人的安全生产身份框架)的缩写。它以一串spiffe://开头的URL为服务提供身份,通常以X.509证书JWT中附加字段的形式出现。这些spiffe的载体称为SVID

spiffe://<trust-domain>/<namespace>/<service-account>

满足spiffe标准的X.509证书示例 (X.509 SVID):

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            04:10:df:a2:3b:45:3b:75:ec:fd:fa:41:1b:84:cb:70:d9
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN=SPIFFE CA
        Validity
            Not Before: Mar 10 12:00:00 2021 GMT
            Not After : Mar 10 12:00:00 2022 GMT
        Subject: CN=spiffe://example.org/foo/service1/workload1 	<--- SPIFFE URL
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage: 
                TLS Web Server Authentication, TLS Web Client Authentication
            X509v3 Subject Alternative Name: 
                URI: spiffe://example.org/foo/service1/workload1	<--- SPIFFE URL
    Signature Algorithm: sha256WithRSAEncryption
         5c:ca:ba:8e:92:...:00:00:00:00:00:00:00:00:00:00:00:00

其中主题拓展字段均包含了一个SPIFFE url,表示该证书来源于信任域为example.org、命名空间为foo ,service1服务的工作负载workload1

Istio签发的证书符合SPIFFE标准。Istio agent在生成证书时,会根据pod的环境变量创建一个SPIFFE URL并将其写入待签名的X.509证书, 并携带k8s SA Token或其它身份证明验证自身身份。

Istiod收到CSR请求时,会通过Token和注册条目校验代理为证书写入的SPIFFE信息是否合规。

通过满足SPIFFE标准的X.509证书,上游服务就可以确定下游服务的具体信息(位于哪个信任域、服务名称及工作负载),这些信息可用于后续的访问权限验证流程。

1.2.1 SPIRE

SPIRE是SPIFFE的运行时环境,它是CNCF提供的一个SPIFFE实现。Istio提供了自己的SPIRE实现(Istio CA/Citadel/Istiod +Istio pilot),并支持和CNCF的实现进行集成。

SPIRE主要由Server代理组成。Server 作为中心化的服务,为客户端(服务实例)提供证书签发、管理服务,而代理则负责生成证书、管理证书并请求Server为证书签名、续签,为Node内工作负载提供有效证书。

![image-20240411120322335](/Users/nameles/Library/Application Support/typora-user-images/image-20240411120322335.png)

1.2.2 注册条目

注册条目描述了哪些工作负载可以和哪些SPIFFE身份关联起来,为工作负载关联身份提供了准入条件。这些条目存储在SPIRE Server上,由SPIRE Server管理。

注册条目由选择器SPIFFE ID联邦关系等字段组成。

  • 选择器用于确定哪些工作负载可以授予对应的SPIFFE ID,如基于UNIX用户ID、UNIX组、K8s服务账户、pod、环境变量、进程属性进行选择
  • SPIFFE ID为已成功注册的ID
  • 联邦关系则指定了哪些其它信任域可接收某个SPIFFE ID,跨集群安全通信依赖该配置

1.3 istio的mTLS配置

istio的mTLS配置由 PeerAuthentication 定义,用于配置是否启用mTLS及对应策略

apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: "example-peer-policy"
  namespace: "foo"
spec:
  selector:
    matchLabels:
      app: reviews
  mtls:
    mode: STRICT

该策略包含三种类型

STRICT:严格模式,经由该服务的流量必须由mTLS加密,否则会被拒绝。这要求对端服务必须支持TLS且可以提供有效证书,一般同为istio mesh下且部署有sidecar的应用。

PERMISSIVE:宽容模式,服务接受通过mTLS或普通TCP连接的流量。此配置适用于集群中同时存在非istio服务的迁移环境。

NONE/不设置:关闭模式,服务不主动配置证书和密钥,也不会主动建立mTLS连接。适用于少数高性能、不能承受TLS带来的额外开销的场景。

mTLS策略有两种解析方式:

  • CDS协议的Endpoint配置中显式获取TLS配置

  • LDS协议隐式获取,通过解析 virtualInbound 监听器链中的协议配置实现:若 TransportProtocol仅包含TLS,则认为使用STRICT模式;若同时包含其它协议,则认为使用PERMISSIVE模式;若不包含TLS协议则认为不启用。

2、应用层下的验证模型

应用层下的验证一般指上游服务对对端服务身份进行验证,确保其包含的身份证明不被伪造、身份信息可信。

身份证明通常由JWT携带,并通过HTTP Header传递,通常包含在 Authorizaiton / Cookie 头部中,但也可以是用户的某个自定义Header、表单中的某个字段,并不强制。

根据服务调用者的不同,其JWT来源也可能不同,主要可分为:网格内服务通信时的服务账户JWT与网格外消费者提供的JWT。不管是哪种来源的JWT,payload及签名都必须包含可被上游服务识别,且符合当前配置鉴权及验证策略的标识。

2.1 网格内服务身份证明

在istio mesh内部,istio默认使用k8s ServiceAccount JWT作为服务的身份证明。

认证服务需要具备根据服务信息的JWT的签发能力,并支持其自动更新。对于默认的K8s SA Token,约定存储于服务pod下的 /var/run/secrets/kubernetes.io/token , 并由k8s负责其自动更新及轮换。相比证书,Token的过期时间更短,通常为几个小时。

2.2 网格外消费者身份证明

非网格或istio体系外的请求者也可以使用其它外部认证服务,如OAuth2、OIDC、或某些云服务提供商的认证服务。Istio也支持与这些服务进行集成,使用其他来源的JWT而非Sa JWT。

2.3 身份证明的验证

istio内,用户通过配置 RequestAuthentication 和 AuthorizationPolicy 来指定服务如何验证对端JWT并进行鉴权。其中,RequestAuthentication更注重于验证JWT的有效性,而AuthorizationPolicy则注重于对有效JWT进行更细粒度的RBAC鉴权

![image-20240411202502036](/Users/nameles/Library/Application Support/typora-user-images/image-20240411202502036.png)

RequestAuthentication示例:

apiVersion: "security.istio.io/v1beta1"
kind: "RequestAuthentication"
metadata:
  name: "jwt-auth"
  namespace: istio-system
spec:
  selector:
    matchLabels:
      app: myapp
  jwtRules:
  - issuer: "https://example.com"
    jwksUri: "https://example.com/.well-known/jwks.json"

JWT的有效性主要由其是否能被JWKS验证决定。

JWKS(JSON Web Key Set)是一组JWK集合,每个JWT包含了用于验证JWT签名的公钥信息。当一个JWT的签名可以被某个JWK的公钥验证时,认为它是可信的。

RequestAuthentication中指定的jwksUri是一个外部的JWKS服务地址。除了配置一个独立的JWKS服务,它也可以配置为静态的JWK串。

Istio 将 RequestAuthentication 中的 JWKS 、issuer等验证属性由LDS协议下发到envoy sidecar。LDS中,它会被转换为 envoy.filters.http.jwt_authn 过滤器配置。http_authn过滤器会在下游请求入站时提取JWT,并按照定义的策略进行验证。

jwt_authn过滤器配置示例:

http_filters:
- name: envoy.filters.http.jwt_authn
  config:
    providers:
      example_provider:
        issuer: "https://example.com"
        audiences:
        - "example_service"
        local_jwks:
          inline_string: "{ \"keys\": [{ \"kty\": \"RSA\", \"kid\": \"abc\", ... }] }"
    rules:
    - match:
        prefix: "/"
      requires:
        provider_name: "example_provider"

这个过滤器使用了静态JWKS。

3、应用层下的鉴权模型

istio下,基于角色的权限控制(RBAC)由AuthorizationPolicy定义。用户通过配置AuthorizationPolicy来为服务进行细粒度的权限控制,包括端口、请求PATH、方法、Header等属性。

![image-20240411204459268](/Users/nameles/Library/Application Support/typora-user-images/image-20240411204459268.png)

AuthorizaitonPolicy示例:

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: example-policy
  namespace: default
spec:
  selector:
    matchLabels:
      app: my-app
  action: ALLOW
  rules:
  - to:
    - operation:
        paths: ["/private"]
    when:
    - key: request.auth.claims[iss]
      values: ["https://example.com"]
    - key: request.auth.principal
      values: ["user@example.com"]

RBAC过滤器本身不和JWT绑定,它可以仅通过一些通用的连接属性进行权限校验。但对端服务的JWT可以为校验提供更多的元数据,以支持基于更多属性的权限控制。

在Envoy中,jwt_authn过滤器在校验JWT同时也会将各种声明提取到校验上下文(HTTP header等)中,RBAC过滤器后续可以从上下文中获取这些属性用于校验。

以上AuthorizationPolicy转换为rbac_filter的配置:

http_filters:
 - name: envoy.filters.http.rbac
   config:
    rules:
      action: ALLOW
      policies:
        "policy-1":
          permissions:
            - header:
                name: ":path"
                exact_match: "/private"
          principals:
            - authenticated:
                principal_name:
                  exact: "user@example.com"

实现

数据流总览:
datastream

传输层验证实现

如前文所述,mesh下传输层安全性主要由mTLS提供,包括

1、证书签名前服务身份证明的获取。身份证明来源多样(k8s SA token、外部OAUTH服务,等),格式统一为 JWT。

2、通过服务运行环境生成证书、使用CSR请求为证书签名并缓存证书

3、从信任存储源拉取信任存储(证书链)

4、LDS 监听Listener配置,按port获取对端安全策略(PERMISSIVE、STRICT、NONE)

5、连接建立时提供证书,并要求对端提供证书,使用信任存储验证

Istio + mTLS保证了:

  • 传输信息可靠性(不被篡改)
  • 双端服务身份可信性(SPIFFE+信任链)

接口及实现:

  • ServiceIdentitySource 服务身份源,提供JWT形式的服务身份证明

    @SPI(value = "default", scope = ExtensionScope.APPLICATION)
    public interface ServiceIdentitySource {
    
        @Adaptive(value = {"serviceIdentity"})
        String getJwt(URL url);
    }

    默认实现 KubeServiceJwtIdentitySource 使用本地k8s服务账户Token

  • CertSource 证书源,提供有效的X.509证书并负责维护

    @SPI(scope = ExtensionScope.FRAMEWORK)
    public interface CertSource {
    
        @Adaptive(value = {"mesh", "cert_source"})
        CertPair getCert(URL url);
    }
  • TrustSource 信任源,提供信任证书链(信任存储)并负责维护

    @SPI(scope = ExtensionScope.FRAMEWORK)
    public interface TrustSource {
        @Adaptive(value = {"mesh","trust"})
        X509CertChains getTrustCerts(URL url);
    }

    CertSource和TrustSource的默认实现 IstioCitadelCertificateSigner,通过本地服务身份生成证书并请求Istio签名,并缓存CSR请求返回的信任链作为信任存储。

  • LdsListener LDS资源监听器

    public interface LdsListener extends XdsResourceListener<Listener> {}
    

    传输层鉴权下 TlsModeListener 负责解析Listener中各入站port的协议配置(是否支持TLS/SSL),将配置写入TlsModeRepo

  • MeshCredentialProvider

    现有CertProvider接口的实现,从CertSourceTrustSource获取证书及信任存储,并通过TlsModeRepo判断对端的安全策略。作为客户端时,其要求服务端提供证书;作为服务端时,其要求客户端提供证书。

应用层验证&鉴权实现

应用层验证、鉴权操作集中在Provider链路:

1、请求入站时从请求中解析出消费者请求凭据,包括所有可用于进行访问控制的字段:

  • 通用连接字段,对端/代理ip:端口、目标ip:端口
  • 应用层协议字段:HTTP method/path/header/form字段
  • 服务身份字段:对端pod id、服务名称、cluster、namespace、ServiceAccount、SPIFFE ID、......
  • 服务身份载体字段(JWT):(iss)签发者、(aud)受众、(iat)签发时间、(exp)过期时间、......
  • Dubbo特有标识字段:服务接口名、目标方法名、版本、......

其中应用层协议字段协议相关,提供 CredentialFactory 从请求中解析协议相关字段

@SPI(scope = ExtensionScope.APPLICATION)
public interface CredentialFactory {

    @Adaptive("protocol")
    RequestCredential getRequestCredential(URL url, Invocation invocation);
}
  • 默认实现TripleMeshRequestCredentialFactory,主要提供基于HTTP的协议字段解析

2、验证、鉴权规则的获取,包括:

  • 凭据验证规则
  • 凭据鉴权规则

若对端是具有SPIFFE身份的服务(TLS中已提供证书),则凭据的验证是可选的,支持不提供JWT/不对JWT的签名进行校验。

首先,根据当前连接的上下文信息(URL、Invocaition、请求凭据)

1、RequestAuthorizer(实现RoleBasedAuthorizer)先获取适用于当前请求的规则源RuleSource

2、使用对应的规则工厂(RuleFactory)生成规则树

3、通过 CredentialFactory 获取请求凭据,创建 AuthorizationRequestContext,并使用规则树评估请求,包括所有AND规则和OR规则。

为对端生成怎样的规则树由RuleFactory判断。生成的规则树可能包含两颗子树:验证树和鉴权树,分别使用请求凭据中的不同字段进行校验;也可能仅包含两颗树中的一个,取决于为对端使用怎样的验证策略。

@Activate
public class RoleBasedAuthorizer implements RequestAuthorizer {

    private final RuleProvider<?> ruleProvider;

    private final CredentialFactory credentialFactory;

    private final RuleFactory ruleFactory;

    private final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(RoleBasedAuthorizer.class);

    /**
     * TODO
     * Cached rules
     * Connection Identity -> Authorization Rules
     */
    private final Map<String, List<RuleRoot>> rules = new ConcurrentHashMap<>();

    public RoleBasedAuthorizer(ApplicationModel applicationModel) {
        this.ruleProvider = applicationModel.getAdaptiveExtension(RuleProvider.class);
        this.credentialFactory = applicationModel.getAdaptiveExtension(CredentialFactory.class);
        this.ruleFactory = applicationModel.getAdaptiveExtension(RuleFactory.class);
    }

    @Override
    @SuppressWarnings({"unchecked", "rawtypes"})
    public void validate(Invocation invocation) throws AuthorizationException {

        List rulesSources = ruleProvider.getSource(invocation.getInvoker().getUrl(), invocation);
        List<RuleRoot> roots = ruleFactory.getRules(invocation.getInvoker().getUrl(),rulesSources);

        List<RuleRoot> andRules = roots.stream()
                .filter(root -> Relation.AND.equals(root.getRelation()))
                .collect(Collectors.toList());
        List<RuleRoot> orRules = roots.stream()
                .filter(root -> Relation.OR.equals(root.getRelation()))
                .collect(Collectors.toList());

        RequestCredential requestCredential =
                credentialFactory.getRequestCredential(invocation.getInvoker().getUrl(), invocation);

        AuthorizationRequestContext context = new AuthorizationRequestContext(invocation, requestCredential);

        boolean andRes = true;
        for (RuleRoot rule : andRules) {
            try {
                if (!rule.evaluate(context) && rule.getAction().boolVal()) {
                    andRes = false;
                    break;
                }
            } catch (Exception e) {
                logger.error(
                        "",
                        "",
                        "",
                        "Request authorization failed, source:" + invocation.getServiceName() + // TODO get source
                                ", target URL:" + invocation.getInvoker().getUrl(),
                        e.getCause());
                if (e instanceof AuthorizationException) {
                    throw (AuthorizationException) e;
                }
                throw new AuthorizationException(e);
            }
        }

        boolean orRes = false;
        for (RuleRoot rule : orRules) {
            try {
                orRes = rule.evaluate(context) && rule.getAction().boolVal();
                if (orRes) {
                    break;
                }
            } catch (Exception e) {
                logger.error(
                        "",
                        "",
                        "",
                        "Request authorization failed, source:" + invocation.getServiceName() + // TODO source
                                ", target URL:" + invocation.getInvoker().getUrl(),
                        e.getCause());
                if (e instanceof AuthorizationException) {
                    throw (AuthorizationException) e;
                }
                throw new AuthorizationException(e);
            }
        }
        if (CollectionUtils.isEmpty(orRules)) {
            orRes = true;
        }
        if (andRes && orRes) {
            return;
        }
        throw new AuthorizationException("Request authorization failed: request credential doesn't meet rules.");
    }
}
/**
 * Provides rules for role-based authorization & authentication
 */
@SPI(value = "default", scope = ExtensionScope.APPLICATION)
public interface RuleProvider<T> {

    @Adaptive(value = {"mesh", "authz_rule"})
    List<T> getSource(URL url, Invocation invocation);
}
@SPI
public interface RuleFactory<T> {

    @Adaptive({"mesh", "authz_rule"})
    List<RuleRoot> getRules(URL url,List<T> ruleSource);
}
@SPI(scope = ExtensionScope.APPLICATION)
public interface CredentialFactory {

    @Adaptive("protocol")
    RequestCredential getRequestCredential(URL url, Invocation invocation);
}

规则树

规则树定义了规则属性之间的关系。如:

  • 同名的多个属性值,匹配其中一个
  • 同级的多个属性,必须匹配其中一个
  • 同级的多个属性,必须匹配其中所有
  • 具有多个属性结点的同级父属性结点,他们的子属性必须全部匹配/匹配其中一个

无论是Isitio的AuthorizationPolicy,又或是envoy的RBAC filter配置,都可以使用规则树表示完整的规则结构。

目前提供两套构建规则树的实现:

1、以KubeApiClient拉取AuthorizationPolicy配置,以Map构建规则树

2、以LDS监听器获取RBAC、authn过滤器配置,以HTTP filter构建规则树

public interface RuleNode {

    /**
     * evaluate if the request can match rules in this node and its children
     */
    boolean evaluate(AuthorizationRequestContext context);

    String getNodeName();

    enum Relation {
        AND,
        OR,
        NOT
    }
}
public class RuleRoot extends CompositeRuleNode {

    /**
     * Relations between rule tree roots.
     * All roots that has Relation=AND will do AND, and all roots has Relation=OR will do OR.
     */
    private Action action;

    public RuleRoot(Relation relation, Action action, String name) {
        super(name, relation);
        this.action = action;
    }

    public RuleRoot(Relation relation, Action action) {
        super("", relation);
        this.action = action;
    }

    public Action getAction() {
        return action;
    }

    @Override
    public boolean evaluate(AuthorizationRequestContext context) {
        boolean result;
        if (relation == Relation.AND) {
            result = children.values().stream()
                    .allMatch(childList -> childList.stream().allMatch(ch -> ch.evaluate(context)));
        } else {
            // Relation == OR
            result = children.values().stream()
                    .anyMatch(childList -> childList.stream().anyMatch(ch -> ch.evaluate(context)));
        }
        return result;
    }

    /**
     * The action of authorization policy
     */
    public enum Action {

        /**
         * The request must map this policy
         */
        ALLOW("ALLOW", true),

        /**
         * The request must not map this policy
         */
        DENY("DENY", false);

        private final String name;

        private boolean boolVal;

        Action(String name, boolean boolValue) {
            this.name = name;
            this.boolVal = boolValue;
        }

        public static Action map(String name) {
            name = name.toUpperCase();
            switch (name) {
                case "ALLOW":
                    return ALLOW;
                case "DENY":
                    return DENY;
                default:
                    return null;
            }
        }

        public boolean boolVal() {
            return boolVal;
        }
    }
}
@SuppressWarnings("unchecked,rawtypes")
public class LeafRuleNode implements RuleNode {

    /**
     * e.g rules.from.source.principles
     */
    private String rulePropName;

    /**
     * patterns that matches required values
     */
    private List<Matcher> matchers;

    public LeafRuleNode(List<? extends Matcher> expectedConditions, String name) {
        this.matchers = (List<Matcher>) expectedConditions;
        this.rulePropName = name;
    }

    public LeafRuleNode(Matcher matcher, String name) {
        this.matchers = Collections.singletonList(matcher);
        this.rulePropName = name;
    }

    @Override
    public boolean evaluate(AuthorizationRequestContext context) {
        // If we have multiple values to validate, then every value must match at list one matcher
        for (Matcher matcher : matchers) {

            Object toValidate = context.getRequestCredential().getRequestProperty(matcher.propType());

            if (!matcher.match(toValidate)) {
                return false;
            }
        }

        return true;
    }

    @Override
    public String getNodeName() {
        return rulePropName;
    }
}

@namelessssssssssss namelessssssssssss changed the title xDS security init commit Zero trust security mechanism in mesh Apr 5, 2024
@namelessssssssssss namelessssssssssss marked this pull request as ready for review April 5, 2024 09:57
Copy link

sonarcloud bot commented May 15, 2024

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants