在开始之前,确定已经安装了Docker、Kubernetes和Go语言环境,并设置GOPATH环境变量。下载Mixer代码仓库的本地副本,命令如下:
mkdir -p $GOPATH/src/istio.io/ && \ cd $GOPATH/src/istio.io/ && \ git clone https://github.com/istio/istio
从https://github.com/google/protobuf/releases安装protoc(版本3.5.1或更高版本)并将其添加到环境变量PATH中。
将MIXER_REPO变量设置为Mixer代码存储库在本地计算机上的路径。另外,设置环境变量$ISTIO,使之指向$GOPATH/src/istio.io。设置如下环境变量:
export MIXER_REPO=$GOPATH/src/istio.io/istio/mixer export ISTIO=$GOPATH/src/istio.io
运行如下命令,如果成功运行则表明环境准备通过:
pushd $ISTIO/istio && make mixs
接下来,开发一个简单的定制化的Out of Process(进程外)授权适配器,我们将其用于处理授权检查。Istio中已经自带了基本的身份验证和授权能力,但用户可以将自定义授权作为一种定制化的策略适配器直接注入Mixer中。实现的基本思路是设置一个独立于Mixer的外部服务,该服务接受来自入站请求的头信息,然后做出是否允许请求通过或不通过的决定。
此外,可以将定制的外部适配器作为单独的Kubernetes服务运行,也可以将其完全运行在集群外部。对于任何一种情况,授权服务器都应验证入站请求并保护其端点(此示例仅使用明文未加密的gRPC作为演示)。
开发一个定制化的适配器包括以下几个步骤:
·步骤1:编写基本适配器框架代码
·步骤2:编写适配器配置
·步骤3:链接适配器代码与配置并添加业务逻辑
·步骤4:编写示例运维配置
·步骤5:启动Mixer并验证适配器
步骤1:编写基本适配器框架代码
创建mygrpcadapter目录并导航到它,如下所示:
cd $MIXER_REPO/adapter mkdir mygrpcadapter cd mygrpcadapter
创建名为mygrpcadapter.go的文件。在该文件中,将适配器定义为实现授权模板的服务接口的gRPC服务。到目前为止,代码并没有添加任何关于验证授权逻辑的详细功能,它是在后面的步骤中完成的。文件的内容如下所示:
package mygrpcadapter
import (
"context"
"fmt"
"net"
"google.golang.org/grpc"
"istio.io/api/mixer/adapter/model/v1beta1"
"istio.io/istio/mixer/template/authorization"
)
type (
// Server is basic server interface
Server interface {
Addr() string
Close() error
Run(shutdown chan error)
}
// MyGrpcAdapter supports metric template.
MyGrpcAdapter struct {
listener net.Listener
server *grpc.Server
}
)
var _ authorization.HandleAuthorizationServiceServer = &MyGrpcAdapter{}
func (s *MyGrpcAdapter) HandleAuthorization(ctx context.Context, r *authorization.
HandleAuthorizationRequest) (*v1beta1.CheckResult, error) {
return nil, nil
}
// Addr returns the listening address of the server
func (s *MyGrpcAdapter) Addr() string {
return s.listener.Addr().String()
}
// Run starts the server run
func (s *MyGrpcAdapter) Run(shutdown chan error) {
shutdown <- s.server.Serve(s.listener)
}
// Close gracefully shuts down the server; used for testing
func (s *MyGrpcAdapter) Close() error {
if s.server != nil {
s.server.GracefulStop()
}
if s.listener != nil {
_ = s.listener.Close()
}
return nil
}
// NewMyGrpcAdapter creates a new IBP adapter that listens at provided port.
func NewMyGrpcAdapter(addr string) (Server, error) {
if addr == "" {
addr = "0"
}
listener, err := net.Listen("tcp", fmt.Sprintf(":%s", addr))
if err != nil {
return nil, fmt.Errorf("unable to listen on socket: %v", err)
}
s := &MyGrpcAdapter{
listener: listener,
}
fmt.Printf("listening on \"%v\"\n", s.Addr())
s.server = grpc.NewServer()
authorization.RegisterHandleAuthorizationServiceServer(s.server, s)
return s, nil
}
为了确保上述代码准确无误,通过以下命令构建代码:
go build ./...
现在我们有了一个适配器的基本框架,其中包含授权模板接口的空的实现。后面的步骤将添加此适配器的核心代码。
步骤2:编写适配器配置
由于此适配器只是对从Mixer接收的数据进行校验,因此适配器配置将该文件的路径作为配置字段。
在conf ig目录下创建配置proto文件,如下所示:
mkdir config touch config.proto
使用以下内容在conf ig目录中创建新的conf ig.proto文件:
syntax = "proto3";
// config for mygrpcadapter
package adapter.mygrpcadapter.config;
import "gogoproto/gogo.proto";
option go_package="config";
// config for mygrpcadapter
message Params {
// header name to check for custom token
string auth_key = 1;
}
文件conf ig.proto可以用来生成相应的go文件以及包含适配器信息(配置描述符和名称)的适配器资源。为此,请在适配器代码中添加以下go generate注释:
// nolint:lll // Generates the mygrpcadapter adapter's resource yaml. It contains the adapter's configuration, name, // supported template names (metric in this case), and whether it is session or no-session based. //go:generate $GOPATH/src/istio.io/istio/bin/mixer_codegen.sh -a mixer/adapter/mygrpcadapter/config/config.proto -x "-s=false -n mygrpcadapter -t authorization" package mygrpcadapter import ( "context" "fmt" "net" "google.golang.org/grpc" "istio.io/api/mixer/adapter/model/v1beta1" "istio.io/istio/mixer/template/authorization" ) .... ....
go generate过程需要拉取镜像gcr.io/istio-testing/protoc:2018-06-12,由于各种原因不能正常拉取的话,可以将文件$ISTIO/istio/bin/protoc.sh中的代码gen_img=gcr.io/istio-testing/protoc:2018-06-12替换为:
gen_img=osswangxining/gcr.io-istio-testing-protoc:latest
为了确保上述代码准确无误,通过以下命令构建代码:
go generate ./... go build ./...
第一次运行应该会出现如下类似的结果:
Unable to find image 'osswangxining/gcr.io-istio-testing-protoc:latest' locally latest: Pulling from osswangxining/gcr.io-istio-testing-protoc ff3a5c916c92: Already exists dc6fb857355d: Pulling fs layer f198fee4ce54: Pulling fs layer faa0691ae748: Pulling fs layer faa0691ae748: Verifying Checksum faa0691ae748: Download complete dc6fb857355d: Verifying Checksum dc6fb857355d: Download complete dc6fb857355d: Pull complete f198fee4ce54: Verifying Checksum f198fee4ce54: Download complete f198fee4ce54: Pull complete faa0691ae748: Pull complete Digest: sha256:1c492efce28b962c49bb3f2eedcece3c2ce0fd7f7e2752a7568d71f5cdeceec5 Status: Downloaded newer image for osswangxining/gcr.io-istio-testing-protoc:latest
在conf ig目录下会生成如下文件:
config ├── adapter.mygrpcadapter.config.pb.html ├── config.pb.go ├── config.proto ├── config.proto_descriptor └── mygrpcadapter.yaml
如果在生成过程中没有任何反应,请确保已安装protoc程序,并且将其设置到执行路径中。
conf ig/mygrpcadapter.yaml是适配器的资源文件,提供给Mixer以便让它了解此适配器。在这种情况下,处理程序配置中使用资源名称mygrpcadapter来引用该适配器。文件内容如下所示:
# this config is created through command # mixgen adapter -c $GOPATH/src/istio.io/istio/mixer/adapter/mygrpcadapter/config/config.proto_descriptor -o $GOPATH/src/istio.io/istio/mixer/adapter/mygrpcadapter/config -s=false -n mygrpcadapter -t authorization apiVersion: "config.istio.io/v1alpha2" kind: adapter metadata: name: mygrpcadapter namespace: istio-system spec: description: session_based: false templates: - authorization config: .......
Conf ig.pb.go是配置适配器的生成的go文件。mysampleadapter.conf ig.pb.html是生成的适配器文档。Conf ig.proto_descriptor是适配器代码中不直接使用的中间文件。
步骤3:链接适配器代码与配置并添加业务逻辑
修改适配器代码(mygrpcadapter.go)以使用特定于适配器的配置(具体就是指在文件mygrpcadapter/conf ig/conf ig.proto中定义的配置),HandleAuthorization函数包含了具体的新代码的更改,如下所示:
// nolint:lll
// Generates the mygrpcadapter adapter's resource yaml. It contains the adapter's configuration, name, supported template
// names (metric in this case), and whether it is session or no-session based.
//go:generate $GOPATH/src/istio.io/istio/bin/mixer_codegen.sh -a mixer/adapter/mygrpcadapter/config/config.proto -x "-s=false -n mygrpcadapter -t authorization"
package mygrpcadapter
import (
"context"
"fmt"
"net"
"google.golang.org/grpc"
"istio.io/api/mixer/adapter/model/v1beta1"
policy "istio.io/api/policy/v1beta1"
"istio.io/istio/mixer/adapter/mygrpcadapter/config"
"istio.io/istio/mixer/pkg/status"
"istio.io/istio/mixer/template/authorization"
"istio.io/istio/pkg/log"
)
type (
// Server is basic server interface
Server interface {
Addr() string
Close() error
Run(shutdown chan error)
}
// MyGrpcAdapter supports metric template.
MyGrpcAdapter struct {
listener net.Listener
server *grpc.Server
}
)
var _ authorization.HandleAuthorizationServiceServer = &MyGrpcAdapter{}
// HandleMetric records metric entries
func (s *MyGrpcAdapter) HandleAuthorization(ctx context.Context, r *authorization.HandleAuthorizationRequest) (*v1beta1.CheckResult, error) {
**log.Infof("received request %v\n", *r)
cfg := &config.Params{}
if r.AdapterConfig != nil {
if err := cfg.Unmarshal(r.AdapterConfig.Value); err != nil {
log.Errorf("error unmarshalling adapter config: %v", err)
return nil, err
}
}
decodeValue := func(in interface{}) interface{} {
switch t := in.(type) {
case *policy.Value_StringValue:
return t.StringValue
case *policy.Value_Int64Value:
return t.Int64Value
case *policy.Value_DoubleValue:
return t.DoubleValue
default:
return fmt.Sprintf("%v", in)
}
}
decodeValueMap := func(in map[string]*policy.Value) map[string]interface{} {
out := make(map[string]interface{}, len(in))
for k, v := range in {
out[k] = decodeValue(v.GetValue())
}
return out
}
log.Infof(cfg.AuthKey)
props := decodeValueMap(r.Instance.Subject.Properties)
log.Infof("%v", props)
for k, v := range props {
fmt.Println("k:", k, "v:", v)
if (k == "custom_token_header") && v == cfg.AuthKey {
log.Infof("success!!")
return &v1beta1.CheckResult{
Status: status.OK,
}, nil
}
}
log.Infof("failure; header not provided")
return &v1beta1.CheckResult{
Status: status.WithPermissionDenied("Unauthorized..."),
}, nil**
}
// Addr returns the listening address of the server
func (s *MyGrpcAdapter) Addr() string {
return s.listener.Addr().String()
}
// Run starts the server run
func (s *MyGrpcAdapter) Run(shutdown chan error) {
shutdown <- s.server.Serve(s.listener)
}
// Close gracefully shuts down the server; used for testing
func (s *MyGrpcAdapter) Close() error {
if s.server != nil {
s.server.GracefulStop()
}
if s.listener != nil {
_ = s.listener.Close()
}
return nil
}
// NewMyGrpcAdapter creates a new IBP adapter that listens at provided port.
func NewMyGrpcAdapter(addr string) (Server, error) {
if addr == "" {
addr = "0"
}
listener, err := net.Listen("tcp", fmt.Sprintf(":%s", addr))
if err != nil {
return nil, fmt.Errorf("unable to listen on socket: %v", err)
}
s := &MyGrpcAdapter{
listener: listener,
}
fmt.Printf("listening on \"%v\"\n", s.Addr())
s.server = grpc.NewServer()
authorization.RegisterHandleAuthorizationServiceServer(s.server, s)
return s, nil
}
为了确保上述代码准确无误,通过以下命令构建代码:
go build ./...
接下来还要编写main程序以用于启动gRPC服务。创建cmd/main.go文件,包含以下内容:
// Copyright 2018 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.
package main
import (
"fmt"
"os"
mygrpcadapter "istio.io/istio/mixer/adapter/mygrpcadapter"
)
func main() {
addr := ""
if len(os.Args) > 1 {
addr = os.Args[1]
}
s, err := mygrpcadapter.NewMyGrpcAdapter(addr)
if err != nil {
fmt.Printf("unable to start server: %v", err)
os.Exit(-1)
}
shutdown := make(chan error, 1)
go func() {
s.Run(shutdown)
}()
_ = <-shutdown
}
这样就完成了适配器代码的实现部分。接下来的步骤演示如何将适配器插入到Mixer的构建中并验证代码逻辑。
步骤4:编写示例配置
为了验证适配器是否正确运行,我们需要一个示例配置。这个简单的配置文件将传递给Mixer,并将数据分发到该示例适配器。我们需要将实例、处理程序和规则配置传递给Mixer配置服务器。
在$MIXER_REPO/adapter/mygrpcadapter目录中创建一个命名为sample_operator_cfg.yaml的示例配置文件,其中包含以下内容:
# handler for adapter mygrpcadapter
apiVersion: "config.istio.io/v1alpha2"
kind: handler
metadata:
name: myhandler4auth
namespace: istio-system
spec:
adapter: mygrpcadapter
connection:
address: "{ADDRESS}"
#address: "mygrpcadapterservice:46990"
params:
auth_key: "abc"
---
apiVersion: "config.istio.io/v1alpha2"
kind: instance
metadata:
name: mycheck
namespace: istio-system
spec:
template: authorization
params:
subject:
properties:
custom_token_header: request.headers["x-custom-token"]
---
# rule to dispatch to handler myhandler
apiVersion: "config.istio.io/v1alpha2"
kind: rule
metadata:
name: myrule
namespace: istio-system
spec:
actions:
- handler: myhandler4auth.istio-system
instances:
- mycheck
---
步骤5:启动Mixer并验证适配器
首先将与mygrpcadapter相关的配置复制到一个单独的目录中,Mixer可以一起读取这些重要的示例资源,包括属性词汇表等,命令如下所示:
mkdir -p $MIXER_REPO/adapter/mygrpcadapter/testdata cp $MIXER_REPO/adapter/mygrpcadapter/config/mygrpcadapter.yaml $MIXER_REPO/adapter/mygrpcadapter/testdata cp $MIXER_REPO/testdata/config/attributes.yaml $MIXER_REPO/adapter/mygrpcadapter/testdata cp $MIXER_REPO/template/authorization/template.yaml $MIXER_REPO/adapter/mygrpcadapter/testdata cp $MIXER_REPO/adapter/mygrpcadapter/sample_operator_cfg.yaml $MIXER_REPO/adapter/mygrpcadapter/testdata
在testdata目录下会生成如下文件:
tree testdata testdata ├── attributes.yaml ├── mygrpcadapter.yaml ├── sample_operator_cfg.yaml └── template.yaml 0 directories, 4 files
现在让我们启动gRPC适配器。在新的终端窗口中运行如下命令:
export ISTIO=$GOPATH/src/istio.io export MIXER_REPO=$GOPATH/src/istio.io/istio/mixer cd $MIXER_REPO/adapter/mygrpcadapter go run cmd/main.go
请注意控制台上打印的监听地址。对于下面的输出,侦听器地址是[::]:53814:
listening on "[::]:53814"
打开一个新终端窗口,并在其中设置ADDRESS变量指向上述的监听器地址。
export ISTIO=$GOPATH/src/istio.io export MIXER_REPO=$GOPATH/src/istio.io/istio/mixer export ADDRESS=[::]:53814
更改处理程序的连接参数以指向适配器地址$ADDRESS。将testdata/sample_operator_cfg.yaml中的{ADDRESS}字符串替换为$ADDRESS的值,如下所示:
sed -i -e "s/{ADDRESS}/${ADDRESS}/g" $MIXER_REPO/adapter/mygrpcadapter/testdata/
sample_operator_cfg.yaml
启动Mixer,将其指向testdata配置,如下所示:
pushd $ISTIO/istio && make mixs //找到mixs二进制文件,如果是linux操作系统,路径为$GOPATH/out/linux_amd64/release/mixs, // 如果是mac os操作系统,路径则为$GOPATH/out/darwin_amd64/release/mixs // 根据你的操作系统选择以下命令: $GOPATH/out/darwin_amd64/release/mixs server --configStoreURL=fs://$(pwd)/mixer/adapter/mygrpcadapter/testdata --log_output_level=attributes:debug
终端将具有以下输出,并将被阻塞以等待服务请求,可能会存在与其他无关配置相关的错误,我们现在可以忽略它们:
Mixer started with
MaxMessageSize: 1048576
MaxConcurrentStreams: 1024
APIWorkerPoolSize: 1024
AdapterWorkerPoolSize: 1024
APIPort: 9091
APIAddress:
MonitoringPort: 9093
EnableProfiling: true
SingleThreaded: false
NumCheckCacheEntries: 1500000
ConfigStoreURL: fs:///Users/wangxn/Documents/mygoworkspace/src/istio.io/istio/mixer/adapter/mygrpcadapter/testdata
CertificateFile: /etc/certs/cert-chain.pem
KeyFile: /etc/certs/key.pem
CACertificateFile: /etc/certs/root-cert.pem
ConfigDefaultNamespace: istio-system
LoggingOptions: log.Options{OutputPaths:[]string{"stdout"}, ErrorOutputPaths:[]string{"stderr"}, RotateOutputPath:"", RotationMaxSize:104857600, RotationMaxAge:30, RotationMaxBackups:1000, JSONEncoding:false, LogGrpc:true, outputLevels:"attributes:debug", logCallers:"", stackTraceLevels:"default:none"}
TracingOptions: tracing.Options{ZipkinURL:"", JaegerURL:"", LogTraceSpans:false, SamplingRate:0}
IntrospectionOptions: ctrlz.Options{Port:0x2694, Address:"127.0.0.1"}
LoadSheddingOptions: loadshedding.Options{Mode:0, AverageLatencyThreshold:0, SamplesPerSecond:1.7976931348623157e+308, SampleHalfLife:1000000000, MaxRequestsPerSecond:0, BurstSize:0}
2019-02-17T05:06:49.026405Z info Awaiting for config store sync...
2019-02-17T05:06:49.026439Z info Starting runtime config watch...
2019-02-17T05:06:49.026515Z info Built new config.Snapshot: id='0'
2019-02-17T05:06:49.026568Z info Cleaning up handler table, with config ID:-1
2019-02-17T05:06:49.041592Z info Built new config.Snapshot: id='1'
2019-02-17T05:06:49.043032Z info parsed scheme: ""
2019-02-17T05:06:49.043051Z info scheme "" not registered, fallback to default scheme
2019-02-17T05:06:49.043070Z info grpcAdapter Connected to: [::]:53814
2019-02-17T05:06:49.043107Z info Cleaning up handler table, with config ID:0
2019-02-17T05:06:49.043182Z info Starting monitor server...
2019-02-17T05:06:49.043181Z info ccResolverWrapper: sending new addresses to cc: [{[::]:53814 0 <nil>}]
2019-02-17T05:06:49.043204Z info ClientConn switching balancer to "pick_first"
2019-02-17T05:06:49.043276Z info pickfirstBalancer: HandleSubConnStateChange: 0xc000bf00b0, CONNECTING
2019-02-17T05:06:49.043745Z info pickfirstBalancer: HandleSubConnStateChange: 0xc000bf00b0, READY
Istio Mixer: wangxn@ali-1c36bbed0b91.local-docker.io/istio-d3b598579ec4d395e6c26ac6b468a3ea5315efbc-dirty-d3b598579ec4d395e6c26ac6b468a3ea5315efbc-dirty-Modified
Starting gRPC server on port 9091
2019-02-17T05:06:49.045417Z info ControlZ available at 192.168.1.9:9876
...
现在让我们使用Mixer客户端调用该适配器。此步骤中调用示例适配器的应该是Mixer服务器使用规则配置构造的实例对象。
打开一个新终端窗口,并设置$ISTIO和$MIXER_REPO变量,如下所示:
export ISTIO=$GOPATH/src/istio.io export MIXER_REPO=$GOPATH/src/istio.io/istio/mixer
然后运行以下命令:
pushd $ISTIO/istio && make mixc //找到mixc二进制文件,如果是linux操作系统,路径为$GOPATH/out/linux_amd64/release/mixs, // 如果是mac os操作系统,路径则为$GOPATH/out/darwin_amd64/release/mixs // 根据你的操作系统选择以下命令: $GOPATH/out/darwin_amd64/release/mixc check -s destination.service="svc.cluster.local" --stringmap_attributes "request.headers=x-custom-token:abc"
在mixs运行的终端窗口中,可以看到类似于如下的内容:
2019-02-17T05:07:11.237915Z debug attributes Creating bag with attributes: destination.service : svc.cluster.local request.headers : stringmap[x-custom-token:abc]
而在mixc运行的终端窗口中,可以看到类似于如下的内容:
Check RPC completed successfully. Check status was OK
Valid use count: 10000, valid duration: 1m0s
Referenced Attributes
context.reporter.kind ABSENCE
destination.namespace ABSENCE
request.headers::x-custom-token EXACT
现在不发送请求头或不正确的请求头值,如下所示:
pushd $ISTIO/istio && make mixc // 找到mixc二进制文件,如果是linux操作系统,路径为$GOPATH/out/linux_amd64/release/mixs, // 如果是mac os操作系统,路径则为$GOPATH/out/darwin_amd64/release/mixs // 根据你的操作系统选择以下命令: $GOPATH/out/darwin_amd64/release/mixc check -s destination.service="svc.cluster.local" --stringmap_attributes "request.headers=x-custom-token:wrongvalue"
在mixc运行的终端窗口中,可以看到检查状态为PERMISSION_DENIED的如下内容:
Check RPC completed successfully. Check status was PERMISSION_DENIED (myhandler4auth.handler.istio-system:Unauthorized...)
Valid use count: 0, valid duration: 0s
Referenced Attributes
context.reporter.kind ABSENCE
destination.namespace ABSENCE
request.headers::x-custom-token EXACT
到此为止,如果上述步骤全部执行成功并得到预期的结果,那么说明我们已成功创建Mixer适配器。接下来将其部署到Kubernetes集群中以便在Istio中使用这个适配器的功能。