作者:Ash Narkar
微服务通过将应用程序分解为更小的、独立的部分来提高单个开发团队的生产力。然而,仅使用微服务并不能解决诸如服务发现、身份验证和授权等古老的分布式系统问题。事实上,由于微服务环境的异构性和短暂性,这些问题往往更为严重。
随着越来越多的组织采用微服务体系结构,对分离的身份验证和授权的需求变得越来越明显。本文将深入探讨如何利用Envoy、SPIFFE/SPIRE和Open Policy Agent(OPA)在微服务环境中执行重要的安全政策。
背景
Envoy是为大型现代面向服务架构设计的L7代理和通信总线。Envoy(v1.7.0+)支持外部授权过滤器(External Authorization filter),它调用授权服务来检查传入的请求是否被授权。该特性使将授权决策委托给外部服务成为可能,并使请求上下文对服务可用。请求上下文包含诸如网络活动的源、网络活动的目标、网络请求(例如http请求)。所有这些信息都可以被外部事务处用来对Envoy收到的传入请求的命运作出知情的决定。
SPIFFE(Secure Production Identity Framework for Everyone)是一组开放源码标准,用于在动态和异构环境中安全地标识软件系统。采用SPIFFE的系统无论在何处运行,都可以轻松可靠地进行相互身份验证。SPIRE(SPIFFE Runtime Environment,SPIFFE运行时环境)是一个工具链,用于在各种平台上的工作负载之间建立信任。
OPA(Open Policy Agent,开放政策代理)是一个开放源码的通用政策引擎,支持跨整个堆栈的统一的、上下文感知的政策执行。OPA的高级声明性语言Rego允许创建细粒度的安全政策,用于对结构化文档中表示的信息进行推理。
OPA作为外部授权服务
我们将演练一个使用Envoy的外部授权过滤器和OPA作为授权服务的示例。
Envoy-OPA外部授权
该示例由三个服务(web、后端和db)组成,它们与正在运行的Envoy服务进行协作。每个服务使用外部授权过滤器调用各自的OPA实例,检查是否允许传入请求。
web服务接收来自部署在不同子网中的api-server-1和api-server-2的所有入站请求。请求被转发到后端服务,后端服务随后调用db服务。
web、后端和db服务之间的安全通信,通过在每个容器中配置Envoy代理来建立彼此之间的mTLS连接来实现的。Envoy从实现Envoy SDS的SPIRE代理,获得用于mTLS通信的客户机和服务器TLS证书和可信CA根。代理依次从SPIRE服务器获取此信息,并将其提供给已标识的工作负载。在下面的示例中,SPIRE以嵌入到TLS证书中的SPIFFE ID的形式,为每个工作负载提供一个身份,以方便mTLS通信。然后,OPA可以使用每个工作负载的SPIFFE ID来构建授权政策。更多关于塔尖的信息可以在这里找到。
https://spiffe.io/spire/overview/
运行这个例子
第一步:安装Docker
确保安装了docker和docker-compose的最新版本。
第二步:克隆repo并启动容器
克隆OPA-Envoy-SPIRE repo:
git clone git@github.com:ashutosh-narkar/opa-envoy-spire-ext-authz.git
$ cd opa-envoy-spire-ext-authz
$ docker-compose up --build -d
$ docker-compose ps
下列容器应正常运作:
Name Command State Ports
----------------------------------------------------------------------------------------------------------------------
opa-envoy-spiffe-ext-authz_api-server-1_1 flask run --host=0.0.0.0 Up 0.0.0.0:5000->5000/tcp
opa-envoy-spiffe-ext-authz_api-server-2_1 flask run --host=0.0.0.0 Up 0.0.0.0:5001->5000/tcp, 5001/tcp
opa-envoy-spiffe-ext-authz_backend_1 /bin/sh -c /usr/local/bin/ ... Up 10000/tcp
opa-envoy-spiffe-ext-authz_db_1 /bin/sh -c /usr/local/bin/ ... Up 10000/tcp
opa-envoy-spiffe-ext-authz_opa_be_1 ./opa_istio_linux_amd64 -- ... Up 0.0.0.0:9192->9192/tcp
opa-envoy-spiffe-ext-authz_opa_db_1 ./opa_istio_linux_amd64 -- ... Up 0.0.0.0:9193->9193/tcp
opa-envoy-spiffe-ext-authz_opa_web_1 ./opa_istio_linux_amd64 -- ... Up 0.0.0.0:9191->9191/tcp
opa-envoy-spiffe-ext-authz_spire-server_1 /usr/bin/dumb-init /opt/sp ... Up
opa-envoy-spiffe-ext-authz_web_1 /bin/sh -c /usr/local/bin/ ... Up 10000/tcp, 0.0.0.0:8001->8001/tcp
第三步:启动SPIRE基础设施
启动SPIRE代理并在SPIRE服务器上注册web、后端和db服务器。更多关于注册过程的信息可以在这里找到。
$ ./configure-spire.sh
第四步:实行进入政策
进入政策声明web服务只能从子网172.28.0.0/16访问。
检查api-server-1是否可以访问web服务。
$ curl -i localhost:5000/hello
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 29
Server: Werkzeug/0.15.2 Python/2.7.15
Date: Thu, 02 May 2019 21:21:48 GMT
Hello from the web service !
检查api-server-2不能访问web服务。
$ curl -i localhost:5001/hello
HTTP/1.0 403 FORBIDDEN
Content-Type: text/html; charset=utf-8
Content-Length: 40
Server: Werkzeug/0.15.2 Python/2.7.15
Date: Thu, 02 May 2019 21:22:12 GMT
Access to the Web service is forbidden.
第五步:实施服务对服务政策
服务到服务政策声明请求可以从web流到后端到db服务。
检查这个流是否被接受。
$ curl -i localhost:5000/the/good/path
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 35
Server: Werkzeug/0.15.2 Python/2.7.15
Date: Thu, 02 May 2019 21:22:50 GMT
Allowed path: WEB -> BACKEND -> DB
检查web服务是否不允许直接调用db服务。
$ curl -i localhost:5000/the/bad/path
HTTP/1.0 403 FORBIDDEN
Content-Type: text/html; charset=utf-8
Content-Length: 26
Server: Werkzeug/0.15.2 Python/2.7.15
Date: Thu, 02 May 2019 21:23:22 GMT
Forbidden path: WEB -> DB
OPA政策例子
每个服务为一个决策调用其各自的OPA实例,并将其所需的政策加载到OPA中。要查看服务加载的OPA政策,请查看repo中的docker目录。
政策-1示例
上面示例中使用的以下OPA政策被加载到web服务调用的OPA中。
import input.attributes.request.http as http_request
import input.attributes.source.address as source_address
default allow = false
allowed_paths = {"/hello", "/the/good/path", "/the/bad/path"}
# allow access to the Web service from the subnet 172.28.0.0/16 for the allowed paths
allow {
allowed_paths[http_request.path]
http_request.method == "GET"
net.cidr_contains("172.28.0.0/16", source_address.Address.SocketAddress.address)
}
OPA-Envoy进入政策
在Rego游乐场尝试OPA-Envoy进入政策!
政策-2示例
本例中使用的另一项政策规定:
下面是一个政策片段,它被加载到db服务调用的OPA中。该政策只允许从后端服务请求db服务。
package envoy.authz
import input.attributes.request.http as http_request
import input.attributes.source.address as source_address
default allow = false
# allow Backend service to access DB service
allow {
http_request.path == "/good/db"
http_request.method == "GET"
svc_spiffe_id == "spiffe://domain.test/backend-server"
}
svc_spiffe_id = client_id {
[_, _, uri_type_san] := split(http_request.headers["x-forwarded-client-cert"], ";")
[_, client_id] := split(uri_type_san, "=")
}
OPA-Envoy服务政策
在Rego游乐场尝试OPA-Envoy服务对服务政策!
X-Forward-Client-Cert头由发起服务的Envoy代理注入,并由目标服务的Envoy代理验证。将Envoy配置为转发客户机证书中的URI字段。为了标识发出请求的服务,该政策使用X-Forward-Client-Cert头的URI字段,在本例中,该头是后端服务器的SPIFFE ID。
X-Forward-Client-Cert(XFCC)是一个代理标头,它指示请求在从客户机到服务器的过程中流经的部分,或全部客户机或代理的证书信息。有关标题及其支持的键的更多信息可以在这里找到。
https://www.envoyproxy.io/docs/envoy/latest/configuration/http_conn_man/headers#x-forwarded-client-cert
Envoy配置例子
下面是一个Envoy代理的示例配置,该代理侦听端口80上的HTTP客户机连接,然后调用OPA的gRPC服务器,该服务器实现了Envoy外部授权API。
https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/ext_authz_filter
static_resources:
listeners:
- address:
socket_address:
address: 0.0.0.0
port_value: 80
use_original_dst: true
filter_chains:
- filters:
- name: envoy.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager
codec_type: auto
stat_prefix: ingress_http
access_log:
- name: envoy.file_access_log
config:
path: "/dev/stdout"
route_config:
name: local_route
virtual_hosts:
- name: backend
domains:
- "*"
routes:
- match:
prefix: "/hello"
route:
cluster: web-service
- match:
prefix: "/the/good/path"
route:
cluster: web-service
- match:
prefix: "/the/bad/path"
route:
cluster: web-service
http_filters:
- name: envoy.ext_authz
config:
failure_mode_allow: false
grpc_service:
google_grpc:
target_uri: opa:9191
stat_prefix: ext_authz
timeout: 0.5s
- name: envoy.router
config: {}
clusters:
- name: web-service
connect_timeout: 0.25s
type: strict_dns
lb_policy: round_robin
http2_protocol_options: {}
load_assignment:
cluster_name: web-service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: web-service
port_value: 80
admin:
access_log_path: "/dev/null"
address:
socket_address:
address: 0.0.0.0
port_value: 8001
这就是了!
这就是如何使用OPA作为外部授权服务,使用Envoy的外部授权过滤器强制执行进入和服务到服务的安全政策。OPA利用SPIFFE/SPIRE提供的身份验证框架,通过将Envoy配置为转发客户端证书细节,OPA能够基于客户端X.509证书的URI SAN中包含的SPIFFE ID做出授权决策。
源代码
示例代码可以在这里找到:https://github.com/ashutosh-narkar/opa-envoy-spi-ext-authz。