-
Notifications
You must be signed in to change notification settings - Fork 7.7k
Route directive adapter development guide
Warning: Mixer route directives are released as an alpha feature in istio 1.1 and may change in the future
This guide should take less than 45 minutes to follow.
Route directives enable Mixer adapters to modify traffic metadata using operation templates on the request and response headers. In this guide, we develop a simple functional out-of-process check adapter that produces extra output in addition to the check result. This adapter matches a key in the request headers against an external table of users and keys, and outputs the username on successful authorization checks.
You should be familiar with the core Istio concepts such as handlers, rules, and gateways, before attempting this guide. You should also have a development environment available, with an access to a docker registry.
- Deploy ingress sample
- Define a template
- Implement the adapter
- Deploy to the cluster
- Connect to Mixer
- Try request header operations
- Try response header operations
- What's next?
In this guide, we are going to use httpbin
sample application. Follow the ingress task to install Istio into istio-system
namespace, and deploy httpbin
application into the default namespace.
Make sure you have /headers
exposed in the virtual service routes. You should have the following networking configuration applied:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: httpbin
spec:
gateways:
- httpbin-gateway
hosts:
- httpbin.example.com
http:
- match:
- uri:
prefix: /status
- uri:
prefix: /delay
- uri:
prefix: /headers
route:
- destination:
host: httpbin
port:
number: 8000
---
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: httpbin-gateway
spec:
selector:
istio: ingressgateway
servers:
- hosts:
- httpbin.example.com
port:
name: http
number: 80
protocol: HTTP
You can validate that the installation is successful with the following command:
curl -H Host:httpbin.example.com -v https://${INGRESS}/headers
where ${INGRESS}
is the address of the ingressgateway
service (see the ingress traffic management task). The command should return both the request headers as seen by httpbin
application in the cluster, as well as the response headers as seen by curl
client.
You also need a development environment set-up with Go and Kubernetes per developer Guide.
Download and build a local copy of Istio:
mkdir -p $GOPATH/src/istio.io/
cd $GOPATH/src/istio.io/
git clone https://github.com/istio/istio
cd istio
go build ./...
Note: check out release-1.1 branch before 1.1 is released using git checkout release-1.1
Install protoc (version 3.5.1 or higher) from https://github.com/google/protobuf/releases and make it available as an executable from $PATH
.
First, let us define a new template that takes an instance with a request key and a request path, and produces an instance with the user name. Save the following template proto definition as a file userkey/template.proto
under Istio root directory:
syntax = "proto3";
package userkey;
import "mixer/adapter/model/v1beta1/extensions.proto";
option (istio.mixer.adapter.model.v1beta1.template_variety) = TEMPLATE_VARIETY_CHECK_WITH_OUTPUT;
message Template {
string key = 1;
string path = 2;
}
message OutputTemplate {
string user = 1;
}
Run the following command under Istio root to generate the code stubs for the new template:
bin/mixer_codegen.sh -t userkey/template.proto
To implement an adapter for the new template, we first need to define its configuration. Create file userkey/config/config.proto
with the following content:
syntax = "proto3";
import "google/protobuf/duration.proto";
import "gogoproto/gogo.proto";
package config;
message Params {
google.protobuf.Duration valid_duration = 1 [(gogoproto.nullable)=false, (gogoproto.stdduration) = true];
}
This adapter config consists of a single parameter that defines the validity duration of the successful check results.
Run the following command to generate the adapter definition:
bin/mixer_codegen.sh -a userkey/config/config.proto -x "-s=false -n userkey -t userkey"
This command produces a session-less adapter called userkey
that implements the template userkey
.
We are now ready to implement the adapter. Save the following implementation as userkey/userkey.go
:
package userkey
import (
"github.com/gogo/googleapis/google/rpc"
"golang.org/x/net/context"
"istio.io/api/mixer/adapter/model/v1beta1"
"istio.io/istio/userkey/config"
)
type Userkey struct{}
var externalTable = map[string]string{"foobar": "jason"}
func (Userkey) HandleUserkey(_ context.Context, req *HandleUserkeyRequest) (*HandleUserkeyResponse, error) {
config := &config.Params{}
if err := config.Unmarshal(req.AdapterConfig.Value); err != nil {
return nil, err
}
user, ok := externalTable[req.Instance.Key]
if ok {
return &HandleUserkeyResponse{
Result: &v1beta1.CheckResult{ValidDuration: config.ValidDuration},
Output: &OutputMsg{User: user},
}, nil
}
return &HandleUserkeyResponse{
Result: &v1beta1.CheckResult{
Status: rpc.Status{Code: int32(rpc.PERMISSION_DENIED)},
},
}, nil
}
The code above takes a key from the input instance and queries a table of users. If a match is found, it returns a successful check result together with the username from the table using the configured validity duration. Otherwise, it returns a permission denied error code.
We also need to define a main file for the adapter. Save the following main function as userkey/main/main.go
:
package main
import (
"net"
"google.golang.org/grpc"
"istio.io/istio/userkey"
)
func main() {
listener, err := net.Listen("tcp", ":9070")
if err != nil {
panic(err)
}
server := grpc.NewServer()
userkey.RegisterHandleUserkeyServiceServer(server, userkey.Userkey{})
server.Serve(listener)
}
Check that the adapter runs successfully locally first:
go run userkey/main/main.go
If all is good, you should see no errors and a local adapter server listening on port 9070.
We are now ready to deploy our new adapter to the remote istio installation. First, we need to package it as a docker container. For this, you need to have write access to a docker registry. Export the image name ($HUB:$TAG
combination) as ${DOCKER_IMAGE}
environment variable.
- Run the following command to generate a Linux static binary:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o userkey/userkey ./userkey/main/main.go
- Create a docker image:
cat <<EOF > userkey/Dockerfile
FROM scratch
ADD userkey /userkey
EXPOSE 9070
ENTRYPOINT ["/userkey"]
EOF
docker build -t ${DOCKER_IMAGE} userkey/
docker push ${DOCKER_IMAGE}
- Run the adapter container image in the cluster:
kubectl run userkey --image=${DOCKER_IMAGE} --namespace istio-system --port 9070 --expose
Note: you can use the image gcr.io/istio-testing/userkey
in place of DOCKER_IMAGE
in the last step if you encounter issues in the previous steps.
The last command creates a new pod running the adapter in istio-system
namespace and a service definition for it exposing the adapter interface on port 9070.
- Deploy template and adapter definitions to Mixer.
Run the following commands to publish template and adapter configurations:
kubectl apply -f userkey/template.yaml
kubectl apply -f userkey/config/userkey.yaml
- Let us instantiate the adapter by adding a handler to Mixer:
cat <<EOF | kubectl create -f -
apiVersion: config.istio.io/v1alpha2
kind: handler
metadata:
name: h1
namespace: istio-system
spec:
adapter: userkey
connection:
address: userkey:9070
params:
valid_duration: 1s
EOF
Note the address is the service userkey
created in the previous step.
- Next, we provide an input instance that uses a request header named
key
to fill in the value:
cat <<EOF | kubectl create -f -
apiVersion: config.istio.io/v1alpha2
kind: instance
metadata:
name: i1
namespace: istio-system
spec:
template: userkey
params:
key: request.headers["key"] | "unknown"
EOF
- Finally, we add a rule to invoke the handler on the instance:
cat <<EOF | kubectl create -f -
apiVersion: config.istio.io/v1alpha2
kind: rule
metadata:
name: r1
namespace: istio-system
spec:
actions:
- handler: h1.istio-system
instances: ["i1"]
EOF
To make sure everything is working correctly, let us access the application through the gateway. First, make a request without the key header:
curl -H Host:httpbin.example.com -v https://${INGRESS}/headers
You should see the following response:
PERMISSION_DENIED:h1.handler.istio-system
Now make the same request but with the header supplied:
curl -H Host:httpbin.example.com -H key:foobar -v https://${INGRESS}/headers
You should see a successful response:
> GET /headers HTTP/1.1
...
< HTTP/1.1 200 OK
...
{
"headers": {
"Accept": "*/*",
"Content-Length": "0",
"Host": "httpbin.example.com",
"Key": "foobar",
"User-Agent": "curl/7.58.0",
...
}
}
Let us modify the rule to send a request header to the back-end application. Using kubectl edit rule/r1 -n istio-system
change it to be as follows:
apiVersion: config.istio.io/v1alpha2
kind: rule
metadata:
name: r1
namespace: istio-system
spec:
# restrict the rule to the ingress gateway proxy workload only
match: context.reporter.kind == "outbound" && source.labels["istio"] == "ingressgateway"
actions:
- handler: h1.istio-system
instances: ["i1"]
# assign a name to the action
name: a1
requestHeaderOperations:
# set "user" header to the output value of action "a1" in the request to httpbin
- name: user
values:
- a1.output.user
# remove "key" header from the request
- name: key
operation: REMOVE
- Try sending a request without the key header:
curl -H Host:httpbin.example.com -v https://${INGRESS}/headers
You should see the same response as before, since the adapter issues an error code. Request and response operations are only applied to requests that pass checks successfully:
< HTTP/1.1 403 Forbidden
...
PERMISSION_DENIED:h1.handler.istio-system
- Try sending a request with the header
key
set tofoobar
:
curl -H Host:httpbin.example.com -H key:foobar -v https://${INGRESS}/headers
The requests successfully reaches the back-end application this time:
> GET /headers HTTP/1.1
> Host:httpbin.example.com
>
< HTTP/1.1 200 OK
< server: istio-envoy
<
{
"headers": {
"Accept": "*/*",
"Content-Length": "0",
"Host": "httpbin.example.com",
"User": "jason",
"User-Agent": "curl/7.58.0",
...
}
}
Note the absence of the header Key
and addition of the header User
with the value jason
in the request received by the back-end application. The request operations are applied at the gateway before the request reaches httpbin
.
Congratulations, you have successfully completed the first part of the guide, by implementing an adapter that modifies headers at the gateway!
We can also apply header operations on the response from the back-end application. Using kubectl
, change the rule to the following snippet:
apiVersion: config.istio.io/v1alpha2
kind: rule
metadata:
name: r1
namespace: istio-system
spec:
# restrict the rule to the ingress gateway proxy workload only
match: context.reporter.kind == "outbound" && source.labels["istio"] == "ingressgateway"
actions:
- handler: h1.istio-system
instances: ["i1"]
# assign a name to the action
name: a1
responseHeaderOperations:
# set a response header
- name: user
values:
- a1.output.user
- Try sending a request without the key header:
curl -H Host:httpbin.example.com -v https://${INGRESS}/headers
Observe no changes to the behavior. Requests are denied, and no operations are applied:
PERMISSION_DENIED:h1.handler.istio-system
- Try sending a request with the header
key
set tofoobar
:
curl -H Host:httpbin.example.com -H key:foobar -v https://${INGRESS}/headers
You should receive the following response:
> GET /headers HTTP/1.1
> Host:httpbin.example.com
> User-Agent: curl/7.58.0
> Accept: */*
> key:foobar
>
< HTTP/1.1 200 OK
< server: istio-envoy
< date: Wed, 21 Nov 2018 00:38:33 GMT
< content-type: application/json
< content-length: 339
< access-control-allow-origin: *
< access-control-allow-credentials: true
< x-envoy-upstream-service-time: 8
< user: jason
<
{
"headers": {
"Accept": "*/*",
"Content-Length": "0",
"Host": "httpbin.example.com",
"Key": "foobar",
"User-Agent": "curl/7.58.0",
...
}
}
Note that the response header user
is returned by the gateway, but no changes are made to the headers as received by the back-end application. The response header operations are applied at the gateway after the back-end application processed the request.
You can follow the rest of the adapter development guide to set up an integration test, or to package and distribute your custom adapter.
To clean up, delete the directory istio/userkey
, and remove the configuration resources:
kubectl delete rule/r1 handler/h1 instance/i1 adapter/userkey template/userkey -n istio-system
Then, remove the adapter pod from the cluster:
kubectl delete deployment/userkey service/userkey -n istio-system
Visit istio.io to learn how to use Istio.
- Preparing for Development Mac
- Preparing for Development Linux
- Troubleshooting Development Environment
- Repository Map
- GitHub Workflow
- Github Gmail Filters
- Using the Code Base
- Developing with Minikube
- Remote Debugging
- Verify your Docker Environment
- Istio Test Framework
- Working with Prow
- Test Grid
- Code Coverage FAQ
- Writing Good Integration Tests
- Test Flakes
- Release Manager Expectations
- Preparing Istio Releases
- 1.5 Release Information
- 1.6 Release Information
- 1.7 Release Information
- 1.8 Release Information
- 1.9 Release Information
- 1.10 Release Information
- 1.11 Release Information
- 1.12 Release Information
- 1.13 Release Information
- 1.14 Release Information
- 1.15 Release Information
- 1.16 Release Information
- 1.17 Release Information
- 1.18 Release Information
- 1.19 Release Information
- 1.20 Release Information
- 1.21 Release Information
- 1.22 Release Information
- 1.23 Release Information
- 1.24 Release Information
- Collecting Logs and Debug Info
- Dependency FAQ
- Working with discuss.istio.io
- Developing with and hosting upon OpenShift
- Adapter Dev Guide
- Adapter Walkthrough
- Attribute Generating Adapter Walkthrough
- Route Directive Adapter Development Guide
- Out of Tree Adapter Walkthrough
- Running a Local Instance
- Template Dev Guide
- Using a Custom Adapter
- Publishing Adapters and Templates to istio.io
- Enabling Envoy Authorization Service and gRPC Access Log Service With Mixer