本文节选自 ServiceMesher 社区出品的开源电子书《Istio Handbook——Istio 服务网格进阶实践》,阅读地址:https://www.servicemesher.com/istio-handbook/

授权功能是 Istio 中安全体系的一个重要组成部分,它用来实现访问控制的功能,即判断一个请求是否允许通过,这个请求可以是从外部进入 Istio 内部的请求,也可以是在 Istio 内部从服务 A 到服务 B 的请求。可以把授权功能近似地认为是一种四层到七层的“防火墙”,它会像传统防火墙一样,对数据流进行分析和匹配,然后执行相应的动作。

本节所有概念和操作都基于 Istio 1.6 版本。

授权功能是通过授权策略 (AuthorizationPolicy) 来进行配置和使用的,下面是一个完整的授权策略示例:

                apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:name: httpbin-policynamespace: foo
spec:selector:matchLabels:app: httpbinaction: ALLOWrules:- from:- source:principals: ["cluster.local/ns/default/sa/sleep"]to:- operation:methods: ["GET"]paths: ["/info*"]when:- key: request.auth.claims[iss]values: ["https://foo.com"]

这个授权策略的含义是:筛选出 foo 这个 namespace 中含有 app: httpbin label 的 pod,对发送到这些 pod 的请求进行匹配,如果匹配成功,则放行当前请求,匹配规则如下:发起请求的 pod 的 Service Account 需要是 cluster.local/ns/default/sa/sleep ,请求使用 HTTP 协议,请求的具体方法类型是 GET ,请求的 URL 为 /info* ,并且请求中需要包含由 https://foo.com 签发的有效的 JWT Token。

从这个例子中可以看出一个授权策略主要包含以下几个部分:

name:授权策略的名称,仅用于标识授权策略本身,不会影响规则的匹配和执行。•namespace:当前授权策略对象所在的 namespace ,可以使用这个字段配置不同作用范围的授权策略,详见授权策略的作用范围[1]。•selector:使用 label 来选择当前授权策略作用于哪些 pod 上。注意,这里设置的是服务端的 pod ,因为最终这些规则会转换成 Envoy 规则由服务端的 Envoy Proxy 来具体执行。例如有 client 和 server 两个 service ,它们的 pod 对应的 label 分别为 app: client 和 app: server ,为了针对从 client 到 server 的请求进行配置授权策略,这里的 selector 应该设置为 app: server。•action:可以为 ALLOW(默认值)或者 DENY。•rules:匹配规则,如果匹配成功,就会执行对应的 action ,详见授权策略的规则详解[2]

授权策略的作用范围

授权策略可以按照作用域的大小分成三个不同的类型:全局策略、某个 namespace 内的局部策略和具有明确 match label 的授权策略。下面分别进行说明。

全局策略

授权策略位于 istio 的 root namespace 中(例如 istio-system),且匹配所有的 pod。这种规则会作用于整个集群中的所有 pod。

下面的例子中有3个全局策略,第一个是全局 ALLOW ,第二个和第三个是全局 DENY ,后面这两个作用类似,但又有重要的区别,详见授权策略的匹配算法。

                kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:name: global-allownamespace: istio-system
spec:action: ALLOWrules:- {}
EOFkubectl apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:name: global-denynamespace: istio-system
spec:action: DENYrules:- {}
EOFkubectl apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:name: global-denynamespace: istio-system
spec:{}
EOF

某个 namespace 内的局部策略

授权策略位于除了 root namespace 之外的任何一个 namespace 中,且匹配所有的 pod ,这种情况下,这个策略会作用于当前 namespace 中的所有 pod。

下面的例子中是3个 namespace 级别的策略,第一个是 ALLOW ,第二个和第三个是 DENY ,像全局策略一样,后面这两个作用类似,但又有重要的区别,详见授权策略的匹配算法。

                kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:name: foo-namespace-allownamespace: foo
spec:action: ALLOWrules:- {}
EOFkubectl apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:name: foo-namespace-denynamespace: foo
spec:action: DENYrules:- {}
EOFkubectl apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:name: foo-namespace-denynamespace: foo
spec:{}
EOF

具有明确 match label 的授权策略

这种授权策略仅作用于当前 namespace 下使用 selector 字段匹配到的 pod。

                kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:name: httpbin-allownamespace: foo
spec:selector:matchLabels:app: httpbinaction: ALLOWrules:- {}
EOF

授权策略的匹配算法

针对某一个请求,会按照一定的匹配算法来执行相应的授权策略:

1.如果有任何一条 DENY 授权策略匹配当前请求,则拒绝当前请求。2.针对当前 pod,如果没有任何 ALLOW 授权策略,则放行当前请求。3.如果有任何一条 ALLOW 授权策略匹配当前请求,则放行当前请求。4.拒绝当前请求。

也就意味着,如果同时有 ALLOW 和 DENY 策略作用于同一个 pod 上,则 DENY 策略会优先执行,其它的 ALLOW 规则就会被忽略。

注意这个顺序非常重要,有时又会比较隐晦,因此在配置比较复杂策略的时候需要多加小心。

在上文授权策略的作用范围中提到授权策略在配置时,有一些细节上的差异,现结合授权策略的匹配算法进行一些分析。

                spec:{}

这是一个 DENY 策略,作用于全局策略或者 namespace 级别(取决于策略所在 namespace 是否为 root namespace)。但是它并没有对当前请求进行匹配,也就意味着按照授权策略的匹配算法在匹配的时候并不会优先匹配到这条规则,因此可以将其作为一个“后备”策略,即全局或者 namespace 级别的一个默认策略。

                spec:action: DENYrules:- {}

这条规则会真正地匹配当前的请求,又由于它是 DENY 规则,按照授权策略的匹配算法,它会首先得到执行,也就意味着如果配置了一条这种全局或者 namespace 级别的规则,那么所有的其它 ALLOW 规则都不会得到执行。因此这条规则在实际中并没有什么价值。

                spec:action: ALLOWrules:- {}

这条规则和上一条规则类似,但是它是 ALLOW 规则,因此按照授权策略的匹配算法,它的优先级会低一些,因此也可以像第一条规则一样作为一个全局或者 namespace 级别的默认策略。

授权策略的规则详解

授权策略中最重要的是其中的 rule 字段,它指定了如何针对当前的请求进行匹配。如果一个授权策略中指定了多条 rule 规则,则它们之间是的关系,即只要其中任意一条规则匹配成功,那么整个授权策略匹配成功,就会执行相应的 action ,下面是概述中提到的一个授权策略的例子:

                apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:name: httpbin-policynamespace: foo
spec:selector:matchLabels:app: httpbinaction: ALLOWrules:- from:- source:principals: ["cluster.local/ns/default/sa/sleep"]to:- operation:methods: ["GET"]paths: ["/info*"]when:- key: request.auth.claims[iss]values: ["https://foo.com"]

这里的 rules 是一个 rule 的列表。每一条 rule 规则包括三部分:from 、 to 和 when 。类似于防火墙规则,from 和 to 匹配当前请求从哪里来、到哪里去,when 会增加一些额外的检测,当这些条件都满足时,就会认为当前规则匹配成功。如果其中某一部分未进行配置,则认为其可以匹配成功。

在 rule 中进行配置时,所有的字符串类型都支持类似于通配符的匹配模式,例如 abc* 匹配 "abc" 和 "abcd" 等,*xyz 匹配 "xyz" 和 "axyz" 等,单独的 * 匹配非空的字符串。

下面针对具体的字段详细进行说明。

from。针对请求的发送方进行匹配,主要包括 principals 、 requestPrincipals 、 namespaces 和 ipBlocks 四个部分。•principals。匹配发送方的身份,在 Kubernetes 中可以认为是 pod 的 Service Account。使用这个字段时,首先需要开启 mTLS 功能,关于这部分内容可参见对等认证。例如,当前请求是从 default namespace 中的 pod 中发出,且 pod 使用的 Service Account 名为 sleep,针对这个请求进行匹配,可将 principals 配置为[cluster.local/ns/default/sa/sleep]。•requestPrincipals。匹配请求中的 JWT Token 的 <issuer>/<subject> 字段组合。•namespaces。匹配发送方 pod 所在的 namespace。•ipBlocks。匹配请求的源 IP 地址段。•to。针对请求的接收方进行匹配。除了请求接收方,还会对请求本身进行匹配。包括以下字段:•hosts。目的 host。•ports。目的 port。•methods。是指当前请求执行的 HTTP Method。针对 gRPC 服务,这个字段需要设置为 POST。注意这个字段必须在 HTTP 协议时才进行匹配,如果请求不是 HTTP 协议,则认为匹配失败。•paths。当前请求执行的 HTTP URL Path。针对 gRPC 服务,需要配置为 /package.service/method 格式。•when。这是一个 key/value 格式的 list 。这个字段会针对请求进行一些额外的检测,当这些检测全部匹配时才会认证当前规则匹配成功。例如 key: request.headers[User-Agent] 可以匹配 HTTP Header 中的 User-Agent 字段。所有可配置项可参见 Istio 官网上的 Authorization Policy Conditions[3] 说明。

针对以上字段,还有对应的反向匹配操作,即“取反”匹配,包括 notPrincipals、notNamespaces 等。例如 notNamespaces: ["bar"] 表示当发送请求的 pod 不位于 "bar" 这个 namespace 中的时候匹配成功。

另外,在 rule 中会有非常多针对 JWT Token 进行匹配的字段,关于这部分可以查看 JWT 授权里的详细分析。

下面针对上文列出来的授权策略给出一些实际的例子,一方面可以在实际环境中是如何使用这些策略的,另一方面也可以验证前文所述的各种匹配字段、授权策略的匹配算法和授权策略的作用域。

操作示例

创建应用

首先,创建客户端和服务器端的 service 和对应的 pod,使用的例子位于 istio 源代码的 samples[4] 目录中:

                $ kubectl create ns foo
$ kubectl apply -f <(istioctl kube-inject -f samples/httpbin/httpbin.yaml) -n foo
$ kubectl apply -f <(istioctl kube-inject -f samples/sleep/sleep.yaml) -n foo

确认 pod 已正常运行:

                $ kubectl get pod -n foo --show-labels
NAME                       READY   STATUS    RESTARTS   AGE   LABELS
httpbin-5d5df46d48-jndgh   2/2     Running   0          57s   app=httpbin,istio.io/rev=,pod-template-hash=5d5df46d48,security.istio.io/tlsMode=isti
o,version=v1
sleep-545684d78b-29x74     2/2     Running   0          56s   app=sleep,istio.io/rev=,pod-template-hash=545684d78b,security.istio.io/tlsMode=istio
$

确认可以正常访问,且启用了 mTLS 功能,关于 mTLS 功能的开启和验证请参见对等认证:

                $ kubectl exec $(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name}) -c sleep -n foo -- curl "http://httpbin.foo:8000/ip" -s -o /dev/null -w "sleep.foo to httpbin.foo: %{http_code}\n"
sleep.foo to httpbin.foo: 200
$
$ kubectl exec $(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name}) -c sleep -n foo -- curl http://httpbin.foo:8000/headers -s | grep X-Forwarded-Client-Cert"X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/foo/sa/httpbin;Hash=e0f2132eb6ae920cec4b2ea16b9baa33ca388b719a2648636f7a75542852ff0e;Subject=\"\";URI=spiffe://cluster.local/ns/foo/sa/sleep"

全局策略测试

接下来创建一个全局默认的拒绝策略:

                $ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:name: global-denynamespace: istio-system
spec:{}
EOF

这时再进行验证连通性:

                $ kubectl exec $(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name}) -c sleep -n foo -- curl "http://httpbin.foo:8000/ip" -s -o /dev/null -w "sleep.foo to httpbin.foo: %{http_code}\n"
sleep.foo to httpbin.foo: 403

服务拒绝,表明全局拒绝策略生效。

接下来创建一个 httpbin pod 的 ALLOW 策略:

                kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:name: httpbin-allow-policynamespace: foo
spec:selector:matchLabels:app: httpbinaction: ALLOWrules:- to:- operation:methods: ["GET"]
EOF

按照授权策略的匹配算法,应该可以匹配到第3条规则,因此会执行 ALLOW 动作,运行下面的命令进行验证:

                $ kubectl exec $(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name}) -c sleep -n foo -- curl "http://httpbin.foo:8000/ip" -s -o /dev/null -w "sleep.foo to httpbin.foo: %{http_code}\n"
sleep.foo to httpbin.foo: 200

测试 Rule 中的字段

下面我们以 Service Account 为例来进行说明。首先来检查 sleep pod 所使用的的 Service Account:

                $ kubectl get pod -l app=sleep -n foo -o jsonpath={.items...serviceAccountName}
sleep

根据 namespace 和 Service Account 构造出 principals 字段,更新授权策略:

                $ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:name: httpbin-allow-policynamespace: foo
spec:selector:matchLabels:app: httpbinaction: ALLOWrules:- from:- source:principals: ["cluster.local/ns/foo/sa/sleep"]to:- operation:methods: ["GET"]
EOF

进行验证:

                $ kubectl exec $(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name}) -c sleep -n foo -- curl "http://httpbin.foo:8000/ip" -s -o /dev/null -w "sleep.foo to httpbin.foo: %{http_code}\n"
sleep.foo to httpbin.foo: 200

访问仍然是放行状态,说明刚才的授权策略是生效的。

将授权策略中的 Service Account 改为一个其它值:

                $ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:name: httpbin-allow-policynamespace: foo
spec:selector:matchLabels:app: httpbinaction: ALLOWrules:- from:- source:principals: ["cluster.local/ns/foo/sa/other-sa"]to:- operation:methods: ["GET"]
EOF
                $ kubectl exec $(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name}) -c sleep -n foo -- curl "http://httpbin.foo:8000/ip" -s -o /dev/null -w "sleep.foo to httpbin.foo: %{http_code}\n"
sleep.foo to httpbin.foo: 403

访问失败,因为授权策略中配置 Service Account 字段与实际的 Service Account 不匹配。

同样地,可以配置 From、To 和 When 中的其它字段进行测试。

授权策略的匹配算法测试

首先,删除之前创建的名为 httpbin-allow-policy 的授权策略,目前系统中仅存在一个全局的默认 DENY 策略。

                kubectl delete authorizationpolicies httpbin-allow-policy -n foo

接下来创建一个匹配 "GET" 方法的 ALLOW 策略,名为 httpbin-allow-get:

                $ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:name: httpbin-allow-getnamespace: foo
spec:selector:matchLabels:app: httpbinaction: ALLOWrules:- to:- operation:methods: ["GET"]
EOF

这时使用 "GET /ip" 请求进行测试,由于可以和 httpbin-allow-get 策略匹配,因此按照授权策略的匹配算法,可以匹配到第3条规则,因此可以正常访问:

                $ kubectl exec $(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name}) -c sleep -n foo -- curl "http://httpbin.foo:8000/ip" -s -o /dev/null -w "sleep.foo to httpbin.foo: %{http_code}\n"
sleep.foo to httpbin.foo: 200

使用 "POST /ip" 请求进行测试,与 httpbin-allow-get 策略不能匹配,因此会执默认的全局 DENY 策略:

                $ kubectl exec $(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name}) -c sleep -n foo -- curl -X POST "http://httpbin.foo:8000/ip" -s -o /dev/null -w "sleep.foo to httpbin.foo: %{http_code}\n"
sleep.foo to httpbin.foo: 403

再创建一个 "/ip" 的 DENY 策略,名为 httpbin-deny-ip-url:

                $ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:name: httpbin-deny-ip-urlnamespace: foo
spec:selector:matchLabels:app: httpbinaction: DENYrules:- to:- operation:paths: ["/ip"]
EOF

这时使用 "GET /ip" 请求进行测试:

                $ kubectl exec $(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name}) -c sleep -n foo -- curl "http://httpbin.foo:8000/ip" -s -o /dev/null -w "sleep.foo to httpbin.foo: %{http_code}\n"
sleep.foo to httpbin.foo: 403

可以看出执行失败。失败的原因是 "GET /ip" 请求与我们刚才创建的 httpbin-allow-get 和 httpbin-deny-ip-url 两个授权策略都会匹配,但是授权策略的匹配算法执行到第1条规则时,会发现匹配 httpbin-deny-ip-url 授权策略,然后就会直接拒绝当前的请求。另一条授权策略 httpbin-allow-get 便无法得到执行。

清理

执行以下操作来清理我们创建过的各种资源:

                $ kubectl delete authorizationpolicies httpbin-deny-ip-url -n foo
$ kubectl delete authorizationpolicies httpbin-allow-get -n foo
$ kubectl delete authorizationpolicies httpbin-allow-policy -n foo
$ kubectl delete authorizationpolicies global-deny -n istio-system
$ kubectl delete -f <(istioctl kube-inject -f samples/httpbin/httpbin.yaml) -n foo
$ kubectl delete -f <(istioctl kube-inject -f samples/sleep/sleep.yaml) -n foo
$ kubectl delete ns foo

总结

本节我们详细分析了授权策略的概念和用法,也可以看出利用这些规则可以组合出非常复杂的场景,因此在使用复杂的授权策略时需要非常小心。另外,在授权策略中会涉及到很多针对请求中的 JWT Token 进行匹配的规则,相关的概念和用法会在下一节进行详细阐述。

参考

•Authorization Policy[5]•Authorization Policy Conditions[6]•Authorization[7]

引用链接

[1] 授权策略的作用范围: #授权策略的作用范围
[2] 授权策略的规则详解: #授权策略的规则详解
[3] Authorization Policy Conditions: https://istio.io/latest/docs/reference/config/security/conditions/
[4] samples: https://github.com/istio/istio/tree/master/samples
[5] Authorization Policy: https://istio.io/latest/docs/reference/config/security/authorization-policy/
[6] Authorization Policy Conditions: https://istio.io/latest/docs/reference/config/security/conditions/
[7] Authorization: https://istio.io/latest/docs/tasks/security/authorization/

活动预告

Service Mesh Webinar #2

7 月 22 日(周三)晚 8 点,B 站直播《基于 MOSN 和 Istio Service Mesh 的服务治理实践》,直播间 https://live.bilibili.com/21954520,点击阅读原文查看详细信息。

Go 夜读

Go 夜读第 97 期我们可以从 MOSN 和相关的项目中学习到什么

点击 阅读原文 查看更多

Istio 中的授权策略详解相关推荐

  1. MySQL权限授权认证详解

    MySQL权限授权认证详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.MySQL权限系统介绍 1>.权限系统的作用是授予来自某个主机的某个用户可以查询.插入.修改.删 ...

  2. Spring Data JPA 从入门到精通~Naming命名策略详解及其实践

    Naming 命名策略详解及其实践 用 JPA 离不开 @Entity 实体,我都知道实体里面有字段映射,而字段映射的方法有两种: 显式命名:在映射配置时,设置的数据库表名.列名等,就是进行显式命名, ...

  3. timm 视觉库中的 create_model 函数详解

    timm 视觉库中的 create_model 函数详解 最近一年 Vision Transformer 及其相关改进的工作层出不穷,在他们开源的代码中,大部分都用到了这样一个库:timm.各位炼丹师 ...

  4. CentOS7下的软件安装方法及策略详解

    CentOS7下的软件安装方法及策略详解 互联网   01-24 15:15:09   作者:佚名   我要评论 今天小编为大家带来的是CentOS7下的软件安装方法及策略详解:希望对大家安装Cent ...

  5. Linux系统中UID和GID详解

    Linux系统中UID和GID详解 在Linux系统中,每个用户和组都有一个唯一的数字标识符,分别称为UID(用户ID)和GID(组ID).这些标识符用于在系统级别上对用户和组进行身份验证和授权,并且 ...

  6. Ehcache 中ehcache.xml 配置详解和示例

    EhCache 是一个纯Java的进程内缓存框架,具有快速.精干等特点,是Hibernate中默认的CacheProvider. Ehcache是一种广泛使用的开源Java分布式缓存.主要面向通用缓存 ...

  7. [转]Flash socket通讯中的安全策略问题详解

    Flash socket通讯中的安全策略问题详解 昨天做测试的时候遇到一个问题,做好的SWF在Flash AS3中调试通过,但是发布到html中之后就无法得到数据了.查了一些资料之后找到了解决办法.这 ...

  8. Firewalld防火墙策略详解

    Firewalld防火墙策略详解 一.Firewalld firewalld防火墙是Centos7系统默认的防火墙管理工具,取代了之前的iptables防火墙,也是工作在网络层,属于包过滤防火墙. f ...

  9. Iptables防火墙策略详解

    Iptables防火墙策略详解 一.iptables Linux 系统的防火墙--netfilter/iptables IP信息包过滤系统,它实际上由两个组件netfilter 和 iptables组 ...

最新文章

  1. 从自定义TagLayout看自定义布局的一般步骤[手动加精]
  2. CloudCC: 智能CRM究竟能否成为下一个行业风口?
  3. JSTL(JSP 标准标签库)和EL表达式联合使用时,进行字符的比较
  4. 因此,甲骨文杀死了java.net
  5. 关于360举报恶意软件有数字签名的回复
  6. flink 三种时间机制_Flink时间系列:Event Time下如何处理迟到数据
  7. ASP.NET高级配置Web.config和Machine.Config
  8. 【Java开发规范】禁止在 foreach 循环里进行元素的 remove/add 操作
  9. Juniper srx系列防火墙端口限速
  10. 2019 年的 Linux 会如何?
  11. oracle 11g 静默安装
  12. 层次分析法详细讲解(小白必看电脑查看)
  13. RS码编译matlab仿真2
  14. qj71c24n通讯实例_三菱Q系列串行通信模块QJ71C24N概述与特点
  15. python常用颜色表示_OpenCV+Python常用颜色空间
  16. Maccms8.x 命令执行漏洞分析
  17. 为何苹果赚取智能手机市场大部分利润?
  18. 美国弗吉尼亚大学计算机科学,弗吉尼亚大学计算机科学专业排名第30(2020年USNEWS美国排名)...
  19. C++中的同名二义性和路径二义性
  20. 从力的角度重新认识迭代次数

热门文章

  1. Git 常用命令(二)
  2. js浏览器对象模型(BOM)
  3. Educational Codeforces Round 1
  4. NOIP2012模拟试题【奶牛晒衣服】
  5. [方法“Boolean Contains(System.Guid)”不支持转换为 SQL]的解决办法
  6. php v9 上传_phpcms v9 表单添加文件上传字段
  7. 网格自适应_ANSYS 非线性自适应(NLAD)网格划分及应用举例
  8. VS2017调试闪退之Chrome
  9. CentOS 6.9通过RPM安装EPEL源(http://dl.fedoraproject.org)
  10. openssh登陆时提示服务器拒绝了密码