├── .gitignore ├── Dockerfile ├── README.md ├── debug-webhook.yml ├── generate-keys.sh ├── requirements.txt ├── sidecar └── Dockerfile ├── test_webhook.py ├── webhook.py └── webhook.yml /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *~ 3 | *.iml 4 | keys -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.6-alpine 2 | 3 | RUN apk update && apk add gcc libc-dev libffi-dev openssl openssl-dev 4 | 5 | COPY requirements.txt requirements.txt 6 | 7 | RUN pip3 install -r requirements.txt 8 | 9 | COPY webhook.py webhook.py 10 | 11 | COPY keys/* ./ 12 | 13 | ENTRYPOINT ["python", "webhook.py"] 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Auto Tracing Webhook 2 | 3 | A kubernetes webhook to automatically instrument any java application with open tracing abilities (without changing a line of code). 4 | 5 | The webhook simply modifies the tagged deployment by adding the [java-specialagent](https://github.com/opentracing-contrib/java-specialagent) and setting up the environment correctly. 6 | 7 | ## Deploy 8 | 9 | Ensure jaeger is installed: 10 | 11 | ```kubectl apply -f https://raw.githubusercontent.com/jaegertracing/jaeger-kubernetes/master/all-in-one/jaeger-all-in-one-template.yml``` 12 | 13 | Apply the webhook to kubernetes 14 | 15 | ```kubectl apply -f webhook.yml``` 16 | 17 | Tag your namespace with the ```autotrace``` label: 18 | 19 | ```kubectl label namespace default autotrace=enabled``` 20 | 21 | Tag your deployment's pod template with the autotrace label: 22 | 23 | ``` 24 | spec: 25 | replicas: 1 26 | selector: 27 | matchLabels: 28 | app: service-a 29 | template: 30 | metadata: 31 | name: service-a 32 | labels: 33 | app: service-a 34 | autotrace: enabled 35 | ``` 36 | 37 | Now when you deploy your java app it should automatically instrument and begin tracing. 38 | 39 | ## Generate Keys 40 | 41 | ``` 42 | mkdir keys 43 | ./generate-keys.sh keys 44 | ``` 45 | 46 | ## Check Certificate Expiry 47 | 48 | ``` 49 | openssl x509 -subject -enddate -noout -in keys/ca.crt 50 | openssl x509 -subject -enddate -noout -in keys/webhook-server-tls.crt 51 | ``` 52 | 53 | ## Generate caBundle for webhook.yml 54 | 55 | ``` 56 | cat keys/ca.crt | base64 | tr -d "\n" 57 | ``` 58 | 59 | Add to webhook.yml -------------------------------------------------------------------------------- /debug-webhook.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: admissionregistration.k8s.io/v1beta1 3 | kind: MutatingWebhookConfiguration 4 | metadata: 5 | name: explore-webhook 6 | labels: 7 | app: explore-webhook 8 | webhooks: 9 | - name: explore-webhook.default.svc.cluster.local 10 | clientConfig: 11 | url: "https://2c17bca6.ngrok.io/decorate" 12 | rules: 13 | - operations: ["CREATE"] 14 | apiGroups: [""] 15 | apiVersions: ["v1"] 16 | resources: ["pods"] 17 | failurePolicy: Fail 18 | namespaceSelector: 19 | matchLabels: 20 | explore: enabled -------------------------------------------------------------------------------- /generate-keys.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright (c) 2019 StackRox Inc. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # generate-keys.sh 18 | # 19 | # Generate a (self-signed) CA certificate and a certificate and private key to be used by the webhook demo server. 20 | # The certificate will be issued for the Common Name (CN) of `webhook-server.webhook-demo.svc`, which is the 21 | # cluster-internal DNS name for the service. 22 | # 23 | # NOTE: THIS SCRIPT EXISTS FOR DEMO PURPOSES ONLY. DO NOT USE IT FOR YOUR PRODUCTION WORKLOADS. 24 | 25 | : ${1?'missing key directory'} 26 | 27 | key_dir="$1" 28 | 29 | chmod 0700 "$key_dir" 30 | cd "$key_dir" 31 | 32 | # Generate the CA cert and private key 33 | openssl req -nodes -new -x509 -keyout ca.key -out ca.crt -subj "/CN=Admission Controller Webhook Demo CA" -days 10000 34 | # Generate the private key for the webhook server 35 | openssl genrsa -out webhook-server-tls.key 2048 36 | # Generate a Certificate Signing Request (CSR) for the private key, and sign it with the private key of the CA. 37 | openssl req -new -key webhook-server-tls.key -subj "/CN=auto-tracing-mutating-webhook.default.svc" \ 38 | | openssl x509 -req -CA ca.crt -CAkey ca.key -CAcreateserial -out webhook-server-tls.crt -days 10000 39 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | flask==1.1.1 2 | jsonpatch==1.24 3 | pyOpenSSL==19.0.0 4 | -------------------------------------------------------------------------------- /sidecar/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3 2 | 3 | RUN wget -O opentracing-specialagent-1.5.0.jar "http://central.maven.org/maven2/io/opentracing/contrib/specialagent/opentracing-specialagent/1.5.0/opentracing-specialagent-1.5.0.jar" 4 | 5 | RUN mkdir -p /mnt/shared 6 | 7 | VOLUME /mnt/shared 8 | 9 | ENTRYPOINT ["cp", "-v", "opentracing-specialagent-1.5.0.jar", "/mnt/shared"] 10 | -------------------------------------------------------------------------------- /test_webhook.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notmattlucas/auto-tracing-webhook/450ab8df099d38e5a994ad1bcb98b65c107c69ba/test_webhook.py -------------------------------------------------------------------------------- /webhook.py: -------------------------------------------------------------------------------- 1 | import jsonpatch 2 | from flask import Flask, request, jsonify 3 | import copy 4 | import base64 5 | import os 6 | 7 | JAEGER_ENV = { 8 | 'JAEGER_AGENT_PORT': os.environ['JAEGER_AGENT_PORT'], 9 | 'JAEGER_AGENT_HOST': os.environ['JAEGER_AGENT_HOST'], 10 | 'JAEGER_SAMPLER_TYPE': 'const', 11 | 'JAEGER_SAMPLER_PARAM': '1' 12 | } 13 | 14 | JAVA_AGENT = ' -javaagent:/mnt/auto-trace/opentracing-specialagent-1.5.0.jar -Dsa.tracer=jaeger -Dsa.log.level=FINE' 15 | 16 | app = Flask(__name__) 17 | 18 | @app.route('/decorate', methods=['POST']) 19 | def decorate(): 20 | payload = request.get_json() 21 | req = payload['request'] 22 | source = req['object'] 23 | target = copy.deepcopy(source) 24 | 25 | add_volume(target) 26 | add_init_container(target) 27 | tweak_containers(target) 28 | 29 | patch = jsonpatch.JsonPatch.from_diff(source, target) 30 | print(patch) 31 | 32 | return jsonify({ 33 | 'response': { 34 | 'uid': req['uid'], 35 | 'allowed': True, 36 | 'patchType': 'JSONPatch', 37 | 'patch': base64.b64encode(str(patch).encode()).decode(), 38 | 39 | } 40 | }) 41 | 42 | 43 | def tweak_containers(target): 44 | containers = target['spec'].get('containers', []) 45 | for container in containers: 46 | add_mount(container) 47 | edit_env(container) 48 | 49 | 50 | def edit_env(container): 51 | env = container.get('env', []) 52 | for key, val in JAEGER_ENV.items(): 53 | env.append({ 54 | 'name': key, 55 | 'value': val 56 | }) 57 | env.append({ 58 | 'name': 'JAEGER_SERVICE_NAME', 59 | 'value': container['name'] 60 | }) 61 | 62 | add_java_agent(env) 63 | 64 | container['env'] = env 65 | 66 | 67 | def add_java_agent(env): 68 | existing = [e for e in env if e['name'] == 'JAVA_TOOL_OPTIONS'] 69 | if existing: 70 | existing = existing[0] 71 | existing['value'] = existing['value'] + JAVA_AGENT 72 | else: 73 | env.append({ 74 | 'name': 'JAVA_TOOL_OPTIONS', 75 | 'value': JAVA_AGENT 76 | }) 77 | 78 | 79 | def add_mount(container): 80 | mounts = container.get('volumeMounts', []) 81 | mounts.append({ 82 | 'mountPath': '/mnt/auto-trace', 83 | 'name': 'auto-trace-mount' 84 | }) 85 | container['volumeMounts'] = mounts 86 | 87 | 88 | def add_init_container(target): 89 | inits = target['spec'].get('initContainers', []) 90 | inits.append({ 91 | 'name': 'autotrace-additions', 92 | 'image': 'k0diak/autotracing-sidecar:1.5.0', 93 | 'volumeMounts': [{ 94 | 'mountPath': '/mnt/shared', 95 | 'name': 'auto-trace-mount' 96 | }] 97 | }) 98 | target['spec']['initContainers'] = inits 99 | 100 | 101 | def add_volume(target): 102 | volumes = target['spec'].get('volumes', []) 103 | volumes.append({ 104 | 'name': 'auto-trace-mount', 105 | 'emptyDir': {} 106 | }) 107 | target['spec']['volumes'] = volumes 108 | 109 | 110 | if __name__ == "__main__": 111 | app.run('0.0.0.0', debug=False, ssl_context=('webhook-server-tls.crt', 'webhook-server-tls.key')) 112 | -------------------------------------------------------------------------------- /webhook.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: auto-tracing-mutating-webhook 6 | labels: 7 | app: auto-tracing-mutating-webhook 8 | spec: 9 | publishNotReadyAddresses: true 10 | ports: 11 | - port: 443 12 | targetPort: 5000 13 | selector: 14 | app: auto-tracing-mutating-webhook 15 | 16 | --- 17 | apiVersion: apps/v1 18 | kind: Deployment 19 | metadata: 20 | name: auto-tracing-mutating-webhook 21 | labels: 22 | app: auto-tracing-mutating-webhook 23 | spec: 24 | replicas: 1 25 | selector: 26 | matchLabels: 27 | app: auto-tracing-mutating-webhook 28 | template: 29 | metadata: 30 | name: auto-tracing-mutating-webhook 31 | labels: 32 | app: auto-tracing-mutating-webhook 33 | spec: 34 | containers: 35 | - name: auto-tracing-mutating-webhook 36 | image: k0diak/auto-tracing-webhook:0.0.3 37 | imagePullPolicy: Always 38 | env: 39 | - name: JAEGER_AGENT_PORT 40 | value: "5775" 41 | - name: JAEGER_AGENT_HOST 42 | value: "jaeger-agent.default.svc" 43 | resources: 44 | limits: 45 | cpu: 500m 46 | memory: 128Mi 47 | requests: 48 | cpu: 250m 49 | memory: 64Mi 50 | imagePullSecrets: 51 | - name: regcred 52 | --- 53 | apiVersion: admissionregistration.k8s.io/v1beta1 54 | kind: MutatingWebhookConfiguration 55 | metadata: 56 | name: auto-tracing-mutating-webhook 57 | labels: 58 | app: auto-tracing-mutating-webhook 59 | webhooks: 60 | - name: auto-tracing-mutating-webhook.default.svc.cluster.local 61 | clientConfig: 62 | caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURQekNDQWllZ0F3SUJBZ0lVREREWnBONjIyV3AxWXJFWVFkc2VPRjk2b293d0RRWUpLb1pJaHZjTkFRRUwKQlFBd0x6RXRNQ3NHQTFVRUF3d2tRV1J0YVhOemFXOXVJRU52Ym5SeWIyeHNaWElnVjJWaWFHOXZheUJFWlcxdgpJRU5CTUI0WERUSXdNRGt4TmpFME1qazFORm9YRFRRNE1ESXdNakUwTWprMU5Gb3dMekV0TUNzR0ExVUVBd3drClFXUnRhWE56YVc5dUlFTnZiblJ5YjJ4c1pYSWdWMlZpYUc5dmF5QkVaVzF2SUVOQk1JSUJJakFOQmdrcWhraUcKOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXdaNXhka2c4MFdTZ1NxMVhCekhVTmlxdm5NVnNXUEROV1EwcgpSRGZBU1VvS09ZMmJ4Q1ZGTENLZTl3akQ1VVFJdnREaGxOQ1VVYUVaSERYZjdHaUt3R2dsdTlNSzk3YzZaNC90CnVwWGJ3bi96MVRLZFdCZGlzN1pYTmdlRk44N3V0a2pIbmdMY0k1SzFHV2FxUGM3eWJXVUp5MDlyODBqUzE1QWUKNmc4ZW1vd3FJT3ZSVU90cTY0MHlncUM3aEVXWGswTVVoMCtJZ0dRQ2FzOE5saHQ4QVJreXpOT05yQVFjUS9ldAorVWZ3aDl2R0x6WGh6VjNCTkcvMElxRVRQa2tOVENDOFh5ZUpMRUpQZEJDSVk1aHp5RXBOSjFoeVdDSGkvSWJMClQxVTkzT3ZVVEJ2TzE4VGp2V1JmUzUwMWp6K0tLMFo2eDVENVBtV2liOTJrT1ZuY3NRSURBUUFCbzFNd1VUQWQKQmdOVkhRNEVGZ1FVZ0hoQi90Y0JvekRKUDBkTU9tQ1h1MlNyeWc4d0h3WURWUjBqQkJnd0ZvQVVnSGhCL3RjQgpvekRKUDBkTU9tQ1h1MlNyeWc4d0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBTkJna3Foa2lHOXcwQkFRc0ZBQU9DCkFRRUFTeGFielp3MDZpRUZMQkJvcjdGaGQrRnRPczN5bXJHazhtdEhyNi9qUnJWVFJ4ZUdiY2d5SmpYdEI0NHIKbk1ON1M0Z29SMkRjZ3VwWnhzVGl6clMvV2dKZG9neWtiTllMZ2h6cjZjUjVOcFlKaEZqR0Q5cXhkdCtxaUtMTApHMUZYNVdJUWl4YURqL1RHajAwVDNEaFowWGdiN3BuVGlmSmtTeHdMT0duNHQrcVRMNURFWnVEZzJYL2ZQYlE1Ci9rcFdDTGRvem5RRkY1QjRXREJjYWZPYmpxcXZSMzhFV08yWitoaFRhK2s2L1M2QUIyWkw3eDV4WCtxQ2pCUjIKaGg1eThuN0luL0IvcUZzcXVtWTBsV0lqeTR3OGl2cTdzOGsyajhrTGxHNkxVSitKRWRoT1I3bFNXdFhBTTVnMQp2UkF0QVFLeC9WTU1zYWRvQkxUWjNNRUtGQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K 63 | service: 64 | name: auto-tracing-mutating-webhook 65 | namespace: default 66 | path: "/decorate" 67 | rules: 68 | - operations: ["CREATE"] 69 | apiGroups: [""] 70 | apiVersions: ["v1"] 71 | resources: ["pods"] 72 | failurePolicy: Fail 73 | namespaceSelector: 74 | matchLabels: 75 | autotrace: enabled 76 | --------------------------------------------------------------------------------