├── helm ├── charts │ ├── jaeger │ │ ├── values.yaml │ │ ├── templates │ │ │ ├── namespace.yml │ │ │ ├── cassandra.yml │ │ │ ├── ui.yml │ │ │ ├── collector.yml │ │ │ ├── agent.yml │ │ │ └── kubernetes.yml │ │ ├── .helmignore │ │ └── Chart.yaml │ ├── grafana │ │ ├── Chart.yaml │ │ ├── templates │ │ │ ├── dashboards-configmap.yaml │ │ │ ├── configmap.yaml │ │ │ ├── secret.yaml │ │ │ ├── _helpers.tpl │ │ │ ├── ingress.yaml │ │ │ ├── pvc.yaml │ │ │ ├── svc.yaml │ │ │ ├── job.yaml │ │ │ ├── NOTES.txt │ │ │ └── deployment.yaml │ │ └── README.md │ └── influxdb │ │ ├── Chart.yaml │ │ ├── .helmignore │ │ ├── templates │ │ ├── _helpers.tpl │ │ ├── secret.yaml │ │ ├── pvc.yaml │ │ ├── NOTES.txt │ │ ├── service.yaml │ │ ├── post-install-set-auth.yaml │ │ └── deployment.yaml │ │ └── README.md ├── templates │ ├── serviceAccount.yaml │ ├── service.yaml │ ├── hpa.yaml │ ├── clusterRoleBinding.yaml │ ├── clusterRole.yaml │ ├── config.yaml │ ├── deployment.yaml │ ├── decryptSecret.yaml │ └── tlsSecret.yaml ├── values.yaml ├── requirements.yaml └── Chart.yaml ├── .dockerignore ├── logo ├── logo.png ├── name_black.png ├── name_white.png ├── logo_with_name.png ├── name_black.svg └── name_white.svg ├── assets ├── grafana.png ├── jaeger1.png └── jaeger2.png ├── scripts ├── updateLicenses.sh ├── install.sh ├── helm-permissions.sh ├── cover.sh └── updateLicense.py ├── .gitignore ├── docs ├── docs.md ├── apikeybinding.md └── apikey.md ├── travis ├── upload-to-docker.sh └── build-docker-images.sh ├── glide.yaml ├── examples ├── exampleOne.yaml ├── exampleTwo.yaml ├── exampleThree.yaml ├── exampleSix.yaml ├── exampleEight.yaml ├── exampleNine.yaml └── exampleSeven.yaml ├── config.toml ├── cmd ├── start_test.go ├── root.go ├── version_test.go └── version.go ├── spec ├── endpoints.go └── store.go ├── Dockerfile ├── config ├── process.go ├── tracing.go ├── plugins.go ├── tls.go ├── server.go ├── flag.go ├── flag_test.go ├── analytics.go └── proxy.go ├── utils ├── errors_test.go ├── errors.go ├── utils.go └── utils_test.go ├── main.go ├── flow ├── step.go ├── flow.go └── flow_test.go ├── server ├── server_test.go └── udp_test.go ├── metrics ├── metrics.go └── metrics_test.go ├── handlers ├── logger.go ├── logger_test.go └── incoming.go ├── tracer ├── jaeger_test.go ├── jaeger.go └── tags.go ├── steps ├── pluginsonrequest_test.go ├── pluginsonresponse_test.go ├── writeresponse.go ├── writeresponse_test.go ├── mockplugins_test.go ├── pluginsonrequest.go ├── pluginsonresponse.go ├── validateproxy.go ├── mockservice.go └── validateproxy_test.go ├── Makefile ├── .travis.yml ├── controller ├── controller.go ├── tpr.go └── tpr_test.go └── plugins └── plugin.go /helm/charts/jaeger/values.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | ca-certificates.crt 2 | kanali 3 | vendor/ 4 | .idea/ -------------------------------------------------------------------------------- /logo/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/northwesternmutual/kanali/HEAD/logo/logo.png -------------------------------------------------------------------------------- /assets/grafana.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/northwesternmutual/kanali/HEAD/assets/grafana.png -------------------------------------------------------------------------------- /assets/jaeger1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/northwesternmutual/kanali/HEAD/assets/jaeger1.png -------------------------------------------------------------------------------- /assets/jaeger2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/northwesternmutual/kanali/HEAD/assets/jaeger2.png -------------------------------------------------------------------------------- /logo/name_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/northwesternmutual/kanali/HEAD/logo/name_black.png -------------------------------------------------------------------------------- /logo/name_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/northwesternmutual/kanali/HEAD/logo/name_white.png -------------------------------------------------------------------------------- /logo/logo_with_name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/northwesternmutual/kanali/HEAD/logo/logo_with_name.png -------------------------------------------------------------------------------- /scripts/updateLicenses.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | python scripts/updateLicense.py $(git ls-files "*\.go" | grep -v thrift-gen | grep -v tracetest) 6 | -------------------------------------------------------------------------------- /helm/charts/jaeger/templates/namespace.yml: -------------------------------------------------------------------------------- 1 | # --- 2 | # 3 | # apiVersion: v1 4 | # kind: Namespace 5 | # metadata: 6 | # name: tracing 7 | # labels: 8 | # name: tracing -------------------------------------------------------------------------------- /helm/templates/serviceAccount.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | apiVersion: v1 4 | kind: ServiceAccount 5 | metadata: 6 | labels: 7 | app: kanali 8 | name: kanali 9 | namespace: default -------------------------------------------------------------------------------- /helm/charts/jaeger/.helmignore: -------------------------------------------------------------------------------- 1 | templates/agent.yml 2 | templates/cassandra.yml 3 | templates/collector.yml 4 | templates/jaeger.yml 5 | templates/namespace.yml 6 | templates/ui.yml 7 | templates/.DS_Store -------------------------------------------------------------------------------- /helm/values.yaml: -------------------------------------------------------------------------------- 1 | 2 | imageRegistry: northwesternmutual 3 | 4 | dockerImageTag: v1.2.3 5 | 6 | pullPolicy: Always 7 | 8 | decryptKeySecretName: kanali-key-decription 9 | 10 | kanaliConfigName: kanali-config 11 | 12 | tlsSecretName: kanali -------------------------------------------------------------------------------- /helm/templates/service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | apiVersion: v1 4 | kind: Service 5 | metadata: 6 | name: kanali 7 | namespace: default 8 | spec: 9 | selector: 10 | k8s-app: kanali 11 | ports: 12 | - name: app-port 13 | port: 8443 14 | type: NodePort -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ca-certificates.crt 2 | kanali 3 | vendor/ 4 | .idea/ 5 | *.out 6 | git-hack.sh 7 | *.crt 8 | *.key 9 | *.csr 10 | *.srl 11 | *.test 12 | debug 13 | launch.json 14 | settings.json 15 | kanali.wiki/ 16 | *.so 17 | *.pem 18 | *.log 19 | LICENSE_* 20 | .cover/ 21 | *.html -------------------------------------------------------------------------------- /helm/charts/jaeger/Chart.yaml: -------------------------------------------------------------------------------- 1 | description: simple helm chart for Jaeger 2 | engine: gotpl 3 | home: http://jaeger.readthedocs.io/en/latest/ 4 | keywords: 5 | - jaeger 6 | - uber 7 | - opentracing 8 | maintainers: 9 | - email: frankgreco@northwesternmutual.com 10 | name: Frank B Greco Jr 11 | name: jaeger 12 | version: 1.0.0 13 | -------------------------------------------------------------------------------- /helm/templates/hpa.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | apiVersion: autoscaling/v1 4 | kind: HorizontalPodAutoscaler 5 | metadata: 6 | name: kanali 7 | namespace: default 8 | spec: 9 | maxReplicas: 8 10 | minReplicas: 2 11 | scaleTargetRef: 12 | apiVersion: v1 13 | kind: Deployment 14 | name: kanali 15 | targetCPUUtilizationPercentage: 500 -------------------------------------------------------------------------------- /docs/docs.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | ## `ApiProxy` 4 | 5 | Find detailed documentation for an `ApiProxy` [here](./apiproxy.md#apiproxy). 6 | 7 | ## `ApiKey` 8 | 9 | Find detailed documentation for an `ApiKey` [here](./apikey.md#apikey). 10 | 11 | ## `ApiKeyBinding` 12 | 13 | Find detailed documentation for an `ApiKeyBinding` [here](./apikeybinding.md#apikeybinding). -------------------------------------------------------------------------------- /helm/requirements.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | dependencies: 4 | - name: influxdb 5 | version: 0.4.1 6 | repository: https://github.com/kubernetes/charts/tree/master/stable/influxdb 7 | - name: grafana 8 | version: 0.3.6 9 | repository: https://github.com/kubernetes/charts/tree/master/stable/grafana 10 | - name: jaeger 11 | version: 1.0.0 12 | repository: file://./charts/jaeger -------------------------------------------------------------------------------- /helm/templates/clusterRoleBinding.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | apiVersion: rbac.authorization.k8s.io/v1beta1 4 | kind: ClusterRoleBinding 5 | metadata: 6 | name: kanali 7 | labels: 8 | app: kanali 9 | roleRef: 10 | apiGroup: rbac.authorization.k8s.io 11 | kind: ClusterRole 12 | name: kanali 13 | subjects: 14 | - kind: ServiceAccount 15 | name: kanali 16 | namespace: default -------------------------------------------------------------------------------- /travis/upload-to-docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if [[ "$DOCKER_REPO" == "" ]]; then 6 | echo "skip docker upload, DOCKER_REPO=$DOCKER_REPO" 7 | exit 0 8 | fi 9 | 10 | if [[ "$DOCKER_TAG" == "" ]]; then 11 | echo "skip docker upload, DOCKER_TAG=$DOCKER_TAG" 12 | exit 0 13 | fi 14 | 15 | docker login -u $DOCKER_USER -p $DOCKER_PASS 16 | docker push $DOCKER_REPO:$DOCKER_TAG 17 | -------------------------------------------------------------------------------- /helm/charts/grafana/Chart.yaml: -------------------------------------------------------------------------------- 1 | description: The leading tool for querying and visualizing time series and metrics. 2 | engine: gotpl 3 | home: https://grafana.net 4 | icon: https://raw.githubusercontent.com/grafana/grafana/master/public/img/logo_transparent_400x.png 5 | maintainers: 6 | - email: zanhsieh@gmail.com 7 | name: Ming Hsieh 8 | name: grafana 9 | sources: 10 | - https://github.com/grafana/grafana 11 | version: 0.3.6 12 | -------------------------------------------------------------------------------- /helm/charts/influxdb/Chart.yaml: -------------------------------------------------------------------------------- 1 | description: Scalable datastore for metrics, events, and real-time analytics. 2 | engine: gotpl 3 | home: https://www.influxdata.com/time-series-platform/influxdb/ 4 | keywords: 5 | - influxdb 6 | - database 7 | - timeseries 8 | maintainers: 9 | - email: jack@influxdb.com 10 | name: Jack Zampolin 11 | name: influxdb 12 | sources: 13 | - https://github.com/influxdata/influxdb 14 | version: 0.4.1 15 | -------------------------------------------------------------------------------- /helm/charts/influxdb/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | -------------------------------------------------------------------------------- /helm/charts/grafana/templates/dashboards-configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | labels: 5 | app: {{ template "grafana.fullname" . }} 6 | chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" 7 | component: "{{ .Values.server.name }}" 8 | heritage: "{{ .Release.Service }}" 9 | release: "{{ .Release.Name }}" 10 | name: {{ template "grafana.server.fullname" . }}-dashs 11 | data: 12 | {{ toYaml .Values.serverDashboardFiles | indent 2 }} 13 | -------------------------------------------------------------------------------- /helm/Chart.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: helm 4 | version: 1.0.0 5 | description: helm chart to deploy kanali 6 | keywords: 7 | - ingress 8 | - kubernetes 9 | - api 10 | - management 11 | home: "https://github.com/northwesternmutual/kanali" 12 | sources: 13 | - "https://github.com/northwesternmutual/kanali" 14 | maintainers: 15 | - name: Frank B Greco Jr 16 | email: frankgreco@northwesternmutual.com 17 | engine: gotpl 18 | appVersion: v1.0.0 19 | deprecated: false 20 | -------------------------------------------------------------------------------- /helm/templates/clusterRole.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | kind: ClusterRole 4 | apiVersion: rbac.authorization.k8s.io/v1beta1 5 | metadata: 6 | name: kanali 7 | rules: 8 | - apiGroups: ["kanali.io"] 9 | resources: ["apikeies", "apiproxies", "apikeybindings"] 10 | verbs: ["watch"] 11 | - apiGroups: ["extensions"] 12 | resources: ["thirdpartyresources"] 13 | verbs: ["create"] 14 | - apiGroups: [""] 15 | resources: ["services", "secrets", "endpoints", "configmaps"] 16 | verbs: ["watch"] -------------------------------------------------------------------------------- /helm/charts/grafana/templates/configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | labels: 5 | app: {{ template "grafana.fullname" . }} 6 | chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" 7 | component: "{{ .Values.server.name }}" 8 | heritage: "{{ .Release.Service }}" 9 | release: "{{ .Release.Name }}" 10 | name: {{ template "grafana.server.fullname" . }}-config 11 | data: 12 | {{- if .Values.server.installPlugins }} 13 | grafana-install-plugins: {{ .Values.server.installPlugins | quote }} 14 | {{- end }} 15 | {{ toYaml .Values.serverConfigFile | indent 2 }} 16 | -------------------------------------------------------------------------------- /helm/charts/influxdb/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | */}} 13 | {{- define "fullname" -}} 14 | {{- $name := default .Chart.Name .Values.nameOverride -}} 15 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 16 | {{- end -}} 17 | -------------------------------------------------------------------------------- /helm/charts/grafana/templates/secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | labels: 5 | app: {{ template "grafana.fullname" . }} 6 | chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" 7 | heritage: "{{ .Release.Service }}" 8 | release: "{{ .Release.Name }}" 9 | name: {{ template "grafana.server.fullname" . }} 10 | type: Opaque 11 | data: 12 | {{- if .Values.server.adminPassword }} 13 | grafana-admin-password: {{ .Values.server.adminPassword | b64enc | quote }} 14 | {{- else }} 15 | grafana-admin-password: {{ randAlphaNum 10 | b64enc | quote }} 16 | {{- end }} 17 | grafana-admin-user: {{ .Values.server.adminUser | b64enc | quote }} 18 | -------------------------------------------------------------------------------- /helm/charts/influxdb/templates/secret.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.setDefaultUser.enabled -}} 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | labels: 6 | app: {{ template "fullname" . }} 7 | chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" 8 | heritage: "{{ .Release.Service }}" 9 | release: "{{ .Release.Name }}" 10 | name: {{ template "fullname" . }}-auth 11 | data: 12 | {{- if .Values.setDefaultUser.user.password }} 13 | influxdb-password: {{ .Values.setDefaultUser.user.password | b64enc | quote }} 14 | {{- else }} 15 | influxdb-password: {{ randAscii 10 | b64enc | quote }} 16 | {{- end }} 17 | influxdb-user: {{ .Values.setDefaultUser.user.username | b64enc | quote }} 18 | {{- end -}} 19 | -------------------------------------------------------------------------------- /scripts/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | LIGHT_BLUE='\033[1;34m' 4 | LIGHT_GREEN='\033[1;32m' 5 | NC='\033[0m' 6 | 7 | echo -e "${LIGHT_BLUE}Step 1: Verify that Helm is installed${NC}" 8 | which helm > /dev/null || $(curl https://raw.githubusercontent.com/kubernetes/helm/master/scripts/get > get_helm.sh | bash) 9 | 10 | echo -e "${LIGHT_BLUE}Step 2: Deploy Helm" 11 | helm init > /dev/null 12 | 13 | echo -e "${LIGHT_BLUE}Step 3: Patch RBAC for Helm${NC}" 14 | ./scripts/helm-permissions.sh > /dev/null 15 | 16 | echo -e "${LIGHT_BLUE}Step 4: Install Kanali, Grafana, InfluxDB, and Jaeger (may take a few minutes)${NC}" 17 | 18 | while sleep 1 19 | do 20 | helm install ./helm --name kanali &>/dev/null && break || continue 21 | done 22 | 23 | echo -e "${LIGHT_GREEN}Deployment Complete!${NC}" 24 | -------------------------------------------------------------------------------- /glide.yaml: -------------------------------------------------------------------------------- 1 | package: github.com/northwesternmutual/kanali 2 | import: 3 | - package: github.com/Sirupsen/logrus 4 | version: 0.11.5 5 | - package: github.com/spf13/pflag 6 | - package: github.com/spf13/cobra 7 | - package: github.com/spf13/viper 8 | version: v1.0.0 9 | - package: github.com/armon/go-proxyproto 10 | - package: github.com/influxdata/influxdb/client/v2 11 | - package: k8s.io/kubernetes 12 | version: v1.5.7 13 | subpackages: 14 | - pkg/api 15 | - pkg/api/errors 16 | - pkg/api/unversioned 17 | - pkg/apis/extensions 18 | - pkg/client/clientset_generated/internalclientset 19 | - pkg/client/restclient 20 | - pkg/kubectl/cmd/util 21 | - package: github.com/opentracing/opentracing-go 22 | version: 1.0.2 23 | - package: github.com/uber/jaeger-client-go 24 | version: 2.9.0 25 | testImport: 26 | - package: github.com/stretchr/testify 27 | version: v1.1.4 28 | subpackages: 29 | - assert -------------------------------------------------------------------------------- /helm/charts/grafana/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "grafana.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | */}} 13 | {{- define "grafana.fullname" -}} 14 | {{- $name := default "grafana" .Values.nameOverride -}} 15 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 16 | {{- end -}} 17 | 18 | {{/* 19 | Create a fully qualified server name. 20 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 21 | */}} 22 | {{- define "grafana.server.fullname" -}} 23 | {{- printf "%s-%s" .Release.Name "grafana" | trunc 63 | trimSuffix "-" -}} 24 | {{- end -}} 25 | -------------------------------------------------------------------------------- /scripts/helm-permissions.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "{\"apiVersion\":\"v1\",\"kind\":\"ServiceAccount\",\"metadata\":{\"name\":\"tiller\",\"namespace\":\"kube-system\"}}" | kubectl apply -f - 4 | echo "{\"apiVersion\":\"rbac.authorization.k8s.io/v1beta1\",\"kind\":\"ClusterRoleBinding\",\"metadata\":{\"name\":\"tiller\"},\"roleRef\":{\"apiGroup\":\"rbac.authorization.k8s.io\",\"kind\":\"ClusterRole\",\"name\":\"cluster-admin\"},\"subjects\":[{\"kind\":\"ServiceAccount\",\"name\":\"tiller\",\"namespace\":\"kube-system\"}]}" | kubectl apply -f - 5 | kubectl -n kube-system patch deploy/tiller-deploy -p '{"spec": {"template": {"spec": {"serviceAccountName": "tiller"}}}}' 6 | echo "{\"apiVersion\":\"rbac.authorization.k8s.io/v1beta1\",\"kind\":\"ClusterRole\",\"metadata\":{\"annotations\":{\"rbac.authorization.kubernetes.io/autoupdate\":\"true\"},\"labels\":{\"kubernetes.io/bootstrapping\":\"rbac-defaults\"},\"name\":\"cluster-admin\"},\"rules\":[{\"apiGroups\":[\"*\"],\"resources\":[\"*\"],\"verbs\":[\"*\"]},{\"nonResourceURLs\":[\"*\"],\"verbs\":[\"*\"]}]}" | kubectl apply -f - -------------------------------------------------------------------------------- /helm/charts/influxdb/templates/pvc.yaml: -------------------------------------------------------------------------------- 1 | {{- if and (.Values.persistence.enabled) (not .Values.persistence.useExisting) }} 2 | kind: PersistentVolumeClaim 3 | apiVersion: v1 4 | metadata: 5 | name: "{{- if not (empty .Values.persistence.name) }}{{ .Values.persistence.name }}{{- else }}{{ template "fullname" . }}{{- end }}" 6 | labels: 7 | app: "{{- if not (empty .Values.persistence.name) }}{{ .Values.persistence.name }}{{- else }}{{ template "fullname" . }}{{- end }}" 8 | chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" 9 | release: "{{ .Release.Name }}" 10 | heritage: "{{ .Release.Service }}" 11 | annotations: 12 | {{- if .Values.persistence.storageClass }} 13 | volume.beta.kubernetes.io/storage-class: {{ .Values.persistence.storageClass | quote }} 14 | {{- else }} 15 | volume.alpha.kubernetes.io/storage-class: default 16 | {{- end }} 17 | spec: 18 | accessModes: 19 | - {{ .Values.persistence.accessMode | quote }} 20 | resources: 21 | requests: 22 | storage: {{ .Values.persistence.size | quote }} 23 | {{- end }} 24 | -------------------------------------------------------------------------------- /travis/build-docker-images.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if [[ "$TRAVIS_SECURE_ENV_VARS" == "false" ]]; then 6 | echo "skip docker upload, TRAVIS_SECURE_ENV_VARS=$TRAVIS_SECURE_ENV_VARS" 7 | exit 0 8 | fi 9 | 10 | export DOCKER_REPO="northwesternmutual/kanali" 11 | 12 | if [[ $TRAVIS_TAG =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then 13 | echo "uploading docker image TAG=$TRAVIS_TAG" 14 | export DOCKER_TAG=$TRAVIS_TAG 15 | docker build --build-arg VERSION=$DOCKER_TAG -t $DOCKER_REPO:$DOCKER_TAG . 16 | bash ./travis/upload-to-docker.sh 17 | elif [ $TRAVIS_PULL_REQUEST == "false" ] && [ $TRAVIS_BRANCH == "master" ]; then 18 | echo "uploading docker image BRANCH=$TRAVIS_BRANCH" 19 | for component in latest ${TRAVIS_COMMIT::8} 20 | do 21 | export DOCKER_TAG=${component} 22 | docker build --build-arg VERSION=$DOCKER_TAG -t $DOCKER_REPO:$DOCKER_TAG . 23 | bash ./travis/upload-to-docker.sh 24 | done 25 | else 26 | echo "skip docker upload - neither master branch nor tag" 27 | exit 0 28 | fi 29 | 30 | echo "docker upload successfull" 31 | exit 0 -------------------------------------------------------------------------------- /helm/charts/grafana/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.server.ingress.enabled -}} 2 | {{- $releaseName := .Release.Name -}} 3 | {{- $servicePort := .Values.server.httpPort -}} 4 | apiVersion: extensions/v1beta1 5 | kind: Ingress 6 | metadata: 7 | annotations: 8 | {{- range $key, $value := .Values.server.ingress.annotations }} 9 | {{ $key }}: {{ $value | quote }} 10 | {{- end }} 11 | labels: 12 | app: {{ template "grafana.fullname" . }} 13 | chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" 14 | component: "{{ .Values.server.name }}" 15 | heritage: "{{ .Release.Service }}" 16 | release: "{{ .Release.Name }}" 17 | name: {{ template "grafana.server.fullname" . }} 18 | spec: 19 | rules: 20 | {{- range .Values.server.ingress.hosts }} 21 | - host: {{ . }} 22 | http: 23 | paths: 24 | - path: / 25 | backend: 26 | serviceName: {{ printf "%s-%s" $releaseName "grafana" | trunc 63 }} 27 | servicePort: {{ $servicePort }} 28 | {{- end -}} 29 | {{- if .Values.server.ingress.tls }} 30 | tls: 31 | {{ toYaml .Values.server.ingress.tls | indent 4 }} 32 | {{- end -}} 33 | {{- end -}} 34 | -------------------------------------------------------------------------------- /examples/exampleOne.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | apiVersion: v1 4 | kind: Namespace 5 | metadata: 6 | name: application 7 | labels: 8 | name: application 9 | 10 | --- 11 | 12 | apiVersion: extensions/v1beta1 13 | kind: Deployment 14 | metadata: 15 | name: example-one 16 | namespace: application 17 | spec: 18 | replicas: 1 19 | selector: 20 | matchLabels: 21 | k8s-app: example-one 22 | template: 23 | metadata: 24 | labels: 25 | k8s-app: example-one 26 | spec: 27 | containers: 28 | - name: example-one 29 | imagePullPolicy: Always 30 | image: fbgrecojr/helloworld:node-opentracing 31 | ports: 32 | - containerPort: 8080 33 | 34 | --- 35 | 36 | apiVersion: v1 37 | kind: Service 38 | metadata: 39 | name: example-one 40 | namespace: application 41 | spec: 42 | selector: 43 | k8s-app: example-one 44 | ports: 45 | - name: http 46 | port: 8080 47 | type: ClusterIP 48 | 49 | --- 50 | 51 | apiVersion: kanali.io/v1 52 | kind: ApiProxy 53 | metadata: 54 | name: example-one 55 | namespace: application 56 | spec: 57 | path: /api/v1/example-one 58 | target: / 59 | service: 60 | port: 8080 61 | name: example-one 62 | -------------------------------------------------------------------------------- /helm/charts/grafana/templates/pvc.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.server.persistentVolume.enabled -}} 2 | {{- if not .Values.server.persistentVolume.existingClaim -}} 3 | apiVersion: v1 4 | kind: PersistentVolumeClaim 5 | metadata: 6 | annotations: 7 | {{- if .Values.server.persistentVolume.storageClass }} 8 | volume.beta.kubernetes.io/storage-class: {{ .Values.server.persistentVolume.storageClass | quote }} 9 | {{- else }} 10 | volume.alpha.kubernetes.io/storage-class: default 11 | {{- end }} 12 | {{- if .Values.server.persistentVolume.annotations }} 13 | {{ toYaml .Values.server.persistentVolume.annotations | indent 4 }} 14 | {{- end }} 15 | labels: 16 | app: {{ template "grafana.fullname" . }} 17 | chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" 18 | component: "{{ .Values.server.name }}" 19 | heritage: "{{ .Release.Service }}" 20 | release: "{{ .Release.Name }}" 21 | name: {{ template "grafana.server.fullname" . }} 22 | spec: 23 | accessModes: 24 | {{- range .Values.server.persistentVolume.accessModes }} 25 | - {{ . | quote }} 26 | {{- end }} 27 | resources: 28 | requests: 29 | storage: {{ .Values.server.persistentVolume.size | quote }} 30 | {{- end -}} 31 | {{- end -}} 32 | -------------------------------------------------------------------------------- /config.toml: -------------------------------------------------------------------------------- 1 | # This is an example configuration file for Kanali. 2 | # Note that all of the following options can be specified via cli flags. 3 | # In addtion, JSON and YAML formats can also be used. 4 | 5 | [tracing] 6 | jaeger_server_url = "jaeger-agent.kube-system.svc.cluster.local" 7 | jaeger_agent_url = "jaeger-agent.kube-system.svc.cluster.local" 8 | 9 | [analytics] 10 | influx_addr = "http://kanali-influxdb.default.svc.cluster.local:8086" 11 | influx_db = "kanali" 12 | influx_username = "" 13 | influx_password = "" 14 | 15 | [plugins] 16 | location = "/" 17 | 18 | [plugins.apiKey] 19 | decryption_key_file = "/etc/kanali/key.pem" 20 | header_key = "apikey" 21 | 22 | [tls] 23 | cert_file = "/etc/pki/tls.crt" 24 | key_file = "/etc/pki/tls.key" 25 | ca_file = "" 26 | 27 | [server] 28 | port = 8443 29 | bind_address = "0.0.0.0" 30 | peer_udp_port = 10001 31 | proxy_protocol = false 32 | 33 | [process] 34 | log_level = "info" 35 | 36 | [proxy] 37 | enable_cluster_ip = true 38 | enable_mock_responses = true 39 | upstream_timeout = "0h0m10s" 40 | header_mask_value = "ommitted" 41 | tls_common_name_validation = false 42 | mask_header_keys = [ 43 | "apikey" 44 | ] 45 | 46 | [proxy.default_header_values] 47 | x-canary-deployment = "stable" -------------------------------------------------------------------------------- /helm/charts/influxdb/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | InfluxDB can be accessed via port {{ .Values.config.http.bind_address }} on the following DNS name from within your cluster: 2 | 3 | - http://{{ template "fullname" . }}.{{ .Release.Namespace }}:{{ .Values.config.http.bind_address }} 4 | 5 | You can easily connect to the remote instance with your local influx cli. To forward the API port to localhost:8086 run the following: 6 | 7 | - kubectl port-forward --namespace {{ .Release.Namespace }} $(kubectl get pods --namespace {{ .Release.Namespace }} -l app={{ template "fullname" . }} -o jsonpath='{ .items[0].metadata.name }') 8086:{{ .Values.config.http.bind_address }} 8 | 9 | You can also connect to the influx cli from inside the container. To open a shell session in the InfluxDB pod run the following: 10 | 11 | - kubectl exec -i -t --namespace {{ .Release.Namespace }} $(kubectl get pods --namespace {{ .Release.Namespace }} -l app={{ template "fullname" . }} -o jsonpath='{.items[0].metadata.name}') /bin/sh 12 | 13 | To tail the logs for the InfluxDB pod run the following: 14 | 15 | - kubectl logs -f --namespace {{ .Release.Namespace }} $(kubectl get pods --namespace {{ .Release.Namespace }} -l app={{ template "fullname" . }} -o jsonpath='{ .items[0].metadata.name }') 16 | -------------------------------------------------------------------------------- /helm/charts/grafana/templates/svc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | app: {{ template "grafana.fullname" . }} 6 | chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" 7 | component: "{{ .Values.server.name }}" 8 | heritage: "{{ .Release.Service }}" 9 | release: "{{ .Release.Name }}" 10 | name: {{ template "grafana.server.fullname" . }} 11 | spec: 12 | ports: 13 | - name: {{ default "http" .Values.server.httpPortName | quote }} 14 | port: {{ .Values.server.httpPort }} 15 | protocol: TCP 16 | targetPort: 3000 17 | {{- if contains "NodePort" .Values.server.serviceType }} 18 | {{- if .Values.server.nodePort }} 19 | nodePort: {{ .Values.server.nodePort }} 20 | {{- end }} 21 | {{- end }} 22 | selector: 23 | app: {{ template "grafana.fullname" . }} 24 | component: "{{ .Values.server.name }}" 25 | type: "{{ .Values.server.serviceType }}" 26 | {{- if contains "LoadBalancer" .Values.server.serviceType }} 27 | {{- if .Values.server.loadBalancerIP }} 28 | loadBalancerIP: {{ .Values.server.loadBalancerIP }} 29 | {{- end -}} 30 | {{- if .Values.server.loadBalancerSourceRanges}} 31 | loadBalancerSourceRanges: 32 | {{- range .Values.server.loadBalancerSourceRanges }} 33 | - {{ . }} 34 | {{- end }} 35 | {{- end -}} 36 | {{- end -}} 37 | -------------------------------------------------------------------------------- /examples/exampleTwo.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | apiVersion: v1 4 | kind: Namespace 5 | metadata: 6 | name: application 7 | labels: 8 | name: application 9 | 10 | --- 11 | 12 | apiVersion: extensions/v1beta1 13 | kind: Deployment 14 | metadata: 15 | name: example-two 16 | namespace: application 17 | spec: 18 | replicas: 1 19 | selector: 20 | matchLabels: 21 | k8s-app: example-two 22 | template: 23 | metadata: 24 | labels: 25 | k8s-app: example-two 26 | spec: 27 | containers: 28 | - name: example-two 29 | imagePullPolicy: Always 30 | image: fbgrecojr/helloworld:golang 31 | ports: 32 | - containerPort: 8080 33 | 34 | --- 35 | 36 | apiVersion: v1 37 | kind: Service 38 | metadata: 39 | name: example-two 40 | namespace: application 41 | labels: 42 | app: example-two 43 | release: production 44 | spec: 45 | selector: 46 | k8s-app: example-two 47 | ports: 48 | - name: http 49 | port: 8080 50 | type: ClusterIP 51 | 52 | --- 53 | 54 | apiVersion: "kanali.io/v1" 55 | kind: ApiProxy 56 | metadata: 57 | name: example-two 58 | namespace: application 59 | spec: 60 | path: /api/v1/example-two 61 | target: / 62 | service: 63 | port: 8080 64 | labels: 65 | - name: app 66 | value: example-two 67 | - name: release 68 | header: deployment 69 | -------------------------------------------------------------------------------- /helm/templates/config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | kind: ConfigMap 4 | apiVersion: v1 5 | metadata: 6 | name: kanali-config 7 | namespace: default 8 | data: 9 | config.toml: |- 10 | [tracing] 11 | jaeger_server_url = "jaeger-agent.kube-system.svc.cluster.local" 12 | jaeger_agent_url = "jaeger-agent.kube-system.svc.cluster.local" 13 | 14 | [analytics] 15 | influx_addr = "http://kanali-influxdb.default.svc.cluster.local:8086" 16 | influx_db = "kanali" 17 | influx_username = "" 18 | influx_password = "" 19 | influx_buffer_size = 5 20 | 21 | [plugins] 22 | location = "/" 23 | 24 | [plugins.apiKey] 25 | decryption_key_file = "/etc/pki/key.pem" 26 | header_key = "apikey" 27 | 28 | [tls] 29 | cert_file = "/etc/pki/tls.crt" 30 | key_file = "/etc/pki/tls.key" 31 | ca_file = "" 32 | 33 | [server] 34 | port = 8443 35 | bind_address = "0.0.0.0" 36 | peer_udp_port = 10001 37 | proxy_protocol = false 38 | 39 | [process] 40 | log_level = "info" 41 | 42 | [proxy] 43 | enable_cluster_ip = true 44 | enable_mock_responses = true 45 | upstream_timeout = "0h0m10s" 46 | header_mask_value = "ommitted" 47 | tls_common_name_validation = false 48 | mask_header_keys = [ 49 | "apikey" 50 | ] 51 | 52 | [proxy.default_header_values] 53 | deployment = "production" -------------------------------------------------------------------------------- /scripts/cover.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | COVER=.cover 6 | ROOT_PKG=github.com/northwesternmutual/kanali/ 7 | 8 | if [[ -d "$COVER" ]]; then 9 | rm -rf "$COVER" 10 | fi 11 | mkdir -p "$COVER" 12 | 13 | # If a package directory has a .nocover file, don't count it when calculating 14 | # coverage. 15 | filter="" 16 | for pkg in "$@"; do 17 | if [[ -f "$GOPATH/src/$pkg/.nocover" ]]; then 18 | if [[ -n "$filter" ]]; then 19 | filter="$filter, " 20 | fi 21 | filter="\"$pkg\": true" 22 | fi 23 | done 24 | 25 | 26 | i=0 27 | for pkg in "$@"; do 28 | i=$((i + 1)) 29 | 30 | extracoverpkg="" 31 | if [[ -f "$GOPATH/src/$pkg/.extra-coverpkg" ]]; then 32 | extracoverpkg=$( \ 33 | sed -e "s|^|$pkg/|g" < "$GOPATH/src/$pkg/.extra-coverpkg" \ 34 | | tr '\n' ',') 35 | fi 36 | 37 | coverpkg=$(go list -json "$pkg" | jq -r ' 38 | .Deps 39 | | . + ["'"$pkg"'"] 40 | | map 41 | ( select(startswith("'"$ROOT_PKG"'")) 42 | | select(contains("/vendor/") | not) 43 | | select(in({'"$filter"'}) | not) 44 | ) 45 | | join(",") 46 | ') 47 | if [[ -n "$extracoverpkg" ]]; then 48 | coverpkg="$extracoverpkg$coverpkg" 49 | fi 50 | 51 | args="" 52 | if [[ -n "$coverpkg" ]]; then 53 | args="-coverprofile $COVER/cover.${i}.out" # -coverpkg $coverpkg" 54 | fi 55 | 56 | echo go test -v -race "$pkg" 57 | go test $args -v -race "$pkg" 58 | done 59 | 60 | gocovmerge "$COVER"/*.out > cover.out 61 | -------------------------------------------------------------------------------- /helm/charts/jaeger/templates/cassandra.yml: -------------------------------------------------------------------------------- 1 | # --- 2 | # 3 | # apiVersion: extensions/v1beta1 4 | # kind: Deployment 5 | # metadata: 6 | # name: jaeger-cassandra 7 | # namespace: tracing 8 | # spec: 9 | # replicas: 1 10 | # selector: 11 | # name: jaeger-cassandra 12 | # selector: 13 | # matchLabels: 14 | # name: jaeger-cassandra 15 | # template: 16 | # metadata: 17 | # labels: 18 | # name: jaeger-cassandra 19 | # spec: 20 | # containers: 21 | # - name: cassandra 22 | # image: registry.nmlv.nml.com/epitropos/jaegertracing/cassandra:latest 23 | # imagePullPolicy: Always 24 | # ports: 25 | # - containerPort: 9042 26 | # protocol: TCP 27 | # - name: status 28 | # image: registry.nmlv.nml.com/epitropos/jaegertracing/cassandra-status:latest 29 | # imagePullPolicy: Always 30 | # ports: 31 | # - containerPort: 8080 32 | # protocol: TCP 33 | # 34 | # --- 35 | # 36 | # apiVersion: v1 37 | # kind: Service 38 | # metadata: 39 | # name: jaeger-cassandra 40 | # namespace: tracing 41 | # spec: 42 | # ports: 43 | # - name: cassandra-port 44 | # port: 9042 45 | # protocol: TCP 46 | # targetPort: 9042 47 | # - name: health-port 48 | # port: 8080 49 | # protocol: TCP 50 | # targetPort: 8080 51 | # selector: 52 | # name: jaeger-cassandra 53 | # type: ClusterIP 54 | -------------------------------------------------------------------------------- /cmd/start_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package cmd 22 | 23 | import ( 24 | "testing" 25 | 26 | "github.com/stretchr/testify/assert" 27 | ) 28 | 29 | func TestStartCmdInit(t *testing.T) { 30 | assert.Equal(t, len(RootCmd.Commands()), 2) 31 | assert.Equal(t, RootCmd.Commands()[0], startCmd) 32 | } 33 | -------------------------------------------------------------------------------- /spec/endpoints.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package spec 22 | 23 | import "k8s.io/kubernetes/pkg/api" 24 | 25 | // KanaliEndpoints represents the endpoints of all running instances of Kanali 26 | var KanaliEndpoints *api.Endpoints 27 | 28 | func init() { 29 | KanaliEndpoints = &api.Endpoints{} 30 | } 31 | -------------------------------------------------------------------------------- /helm/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | apiVersion: extensions/v1beta1 4 | kind: Deployment 5 | metadata: 6 | name: kanali 7 | namespace: default 8 | spec: 9 | selector: 10 | matchLabels: 11 | k8s-app: kanali 12 | template: 13 | metadata: 14 | labels: 15 | k8s-app: kanali 16 | spec: 17 | serviceAccountName: kanali 18 | containers: 19 | - name: kanali 20 | imagePullPolicy: {{default "IfNotPresent" .Values.pullPolicy}} 21 | image: {{.Values.imageRegistry}}/kanali:{{.Values.dockerImageTag}} 22 | command: 23 | - /kanali 24 | - start 25 | env: 26 | - name: POD_IP 27 | valueFrom: 28 | fieldRef: 29 | fieldPath: status.podIP 30 | ports: 31 | - containerPort: 8443 32 | volumeMounts: 33 | - name: pki 34 | mountPath: /etc/pki 35 | - name: config 36 | mountPath: /etc/kanali 37 | volumes: 38 | - name: pki 39 | projected: 40 | sources: 41 | - secret: 42 | name: {{.Values.tlsSecretName}} 43 | items: 44 | - key: tls.crt 45 | path: tls.crt 46 | - key: tls.key 47 | path: tls.key 48 | - secret: 49 | name: {{.Values.decryptKeySecretName}} 50 | items: 51 | - key: key.pem 52 | path: key.pem 53 | - name: config 54 | configMap: 55 | name: {{.Values.kanaliConfigName}} -------------------------------------------------------------------------------- /spec/store.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package spec 22 | 23 | // Store is an interface that provides a common set of operations for 24 | // a data structure 25 | type Store interface { 26 | Set(obj interface{}) error 27 | Update(obj interface{}) error 28 | Get(params ...interface{}) (interface{}, error) 29 | Delete(obj interface{}) (interface{}, error) 30 | Clear() 31 | IsEmpty() bool 32 | } 33 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG GO_VERSION=1.8.3 2 | ARG CENTOS_VERSION=7 3 | 4 | FROM golang:${GO_VERSION} AS BUILD 5 | LABEL maintainer="frankgreco@northwesternmutual.com" 6 | LABEL version="${VERSION}" 7 | ARG VERSION="" 8 | ARG GLIDE_VERSION=0.12.3 9 | WORKDIR /go/src/github.com/northwesternmutual/kanali/ 10 | RUN wget "https://github.com/Masterminds/glide/releases/download/v${GLIDE_VERSION}/glide-v${GLIDE_VERSION}-`go env GOHOSTOS`-`go env GOHOSTARCH`.tar.gz" -O /tmp/glide.tar.gz \ 11 | && mkdir /tmp/glide \ 12 | && tar --directory=/tmp/glide -xvf /tmp/glide.tar.gz \ 13 | && rm -rf /tmp/glide.tar.gz \ 14 | && export PATH=$PATH:/tmp/glide/`go env GOHOSTOS`-`go env GOHOSTARCH` 15 | COPY glide.lock glide.yaml Makefile /go/src/github.com/northwesternmutual/kanali/ 16 | RUN make install 17 | COPY ./ /go/src/github.com/northwesternmutual/kanali/ 18 | RUN sed -ie "s/changeme/`echo ${VERSION}`/g" /go/src/github.com/northwesternmutual/kanali/cmd/version.go 19 | RUN curl -O https://raw.githubusercontent.com/northwesternmutual/kanali-plugin-apikey/v1.2.0/plugin.go 20 | RUN GOOS=`go env GOHOSTOS` GOARCH=`go env GOHOSTARCH` go build -buildmode=plugin -o apiKey_v1.2.0.so plugin.go 21 | RUN GOOS=`go env GOHOSTOS` GOARCH=`go env GOHOSTARCH` go build -o kanali 22 | 23 | FROM centos:${CENTOS_VERSION} 24 | LABEL maintainer="frankgreco@northwesternmutual.com" 25 | LABEL version="${VERSION}" 26 | COPY --from=BUILD /go/src/github.com/northwesternmutual/kanali/apiKey_v1.2.0.so /go/src/github.com/northwesternmutual/kanali/kanali / 27 | RUN cp /apiKey_v1.2.0.so /apiKey.so 28 | ENTRYPOINT ["/kanali"] -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package cmd 22 | 23 | import ( 24 | "github.com/spf13/cobra" 25 | ) 26 | 27 | // RootCmd is the base command of this project 28 | var RootCmd = &cobra.Command{ 29 | Use: "kanali", 30 | Short: "layer 7 load balancer for Kubernetes with API management", 31 | Long: "kanali provides a dynamic layer 7 load balancer for a Kubernetes cluster coupled tightly with an API management solution", 32 | } 33 | -------------------------------------------------------------------------------- /config/process.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package config 22 | 23 | func init() { 24 | Flags.Add( 25 | FlagProcessLogLevel, 26 | ) 27 | } 28 | 29 | var ( 30 | // FlagProcessLogLevel sets the logging level. Choose between 'debug', 'info', 'warn', 'error', 'fatal' 31 | FlagProcessLogLevel = Flag{ 32 | Long: "process.log_level", 33 | Short: "l", 34 | Value: "info", 35 | Usage: "Sets the logging level. Choose between 'debug', 'info', 'warn', 'error', 'fatal'.", 36 | } 37 | ) 38 | -------------------------------------------------------------------------------- /helm/charts/influxdb/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ template "fullname" . }} 5 | labels: 6 | app: {{ template "fullname" . }} 7 | chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" 8 | release: "{{ .Release.Name }}" 9 | heritage: "{{ .Release.Service }}" 10 | spec: 11 | type: {{ .Values.service.type }} 12 | ports: 13 | {{- if .Values.config.http.enabled }} 14 | - name: api 15 | port: {{ .Values.config.http.bind_address }} 16 | targetPort: {{ .Values.config.http.bind_address }} 17 | {{- end }} 18 | {{- if .Values.config.admin.enabled }} 19 | - name: admin 20 | port: {{ .Values.config.admin.bind_address }} 21 | targetPort: {{ .Values.config.admin.bind_address }} 22 | {{- end }} 23 | {{- if .Values.config.graphite.enabled }} 24 | - name: graphite 25 | port: {{ .Values.config.graphite.bind_address }} 26 | targetPort: {{ .Values.config.graphite.bind_address }} 27 | {{- end }} 28 | {{- if .Values.config.collectd.enabled }} 29 | - name: collectd 30 | port: {{ .Values.config.collectd.bind_address }} 31 | targetPort: {{ .Values.config.collectd.bind_address }} 32 | {{- end }} 33 | {{- if .Values.config.udp.enabled }} 34 | - name: udp 35 | port: {{ .Values.config.udp.bind_address }} 36 | targetPort: {{ .Values.config.udp.bind_address }} 37 | {{- end }} 38 | {{- if .Values.config.opentsdb.enabled }} 39 | - name: opentsdb 40 | port: {{ .Values.config.opentsdb.bind_address }} 41 | targetPort: {{ .Values.config.opentsdb.bind_address }} 42 | {{- end }} 43 | selector: 44 | app: {{ template "fullname" . }} 45 | -------------------------------------------------------------------------------- /utils/errors_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package utils 22 | 23 | import ( 24 | "errors" 25 | "testing" 26 | 27 | "github.com/stretchr/testify/assert" 28 | ) 29 | 30 | func TestStatus(t *testing.T) { 31 | se := StatusError{ 32 | Code: 400, 33 | Err: errors.New("new error"), 34 | } 35 | assert.Equal(t, "new error", se.Error()) 36 | } 37 | 38 | func TestError(t *testing.T) { 39 | se := StatusError{ 40 | Code: 400, 41 | Err: errors.New("new error"), 42 | } 43 | assert.Equal(t, 400, se.Status()) 44 | } 45 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package main 22 | 23 | import ( 24 | "os" 25 | "runtime" 26 | 27 | "github.com/Sirupsen/logrus" 28 | "github.com/northwesternmutual/kanali/cmd" 29 | ) 30 | 31 | func init() { 32 | 33 | logrus.SetFormatter(&logrus.JSONFormatter{}) 34 | logrus.SetOutput(os.Stdout) 35 | 36 | } 37 | 38 | func main() { 39 | runtime.GOMAXPROCS(runtime.NumCPU()) 40 | if err := cmd.RootCmd.Execute(); err != nil { 41 | logrus.Fatalf("error executing root cobra command: %s\n", err) 42 | os.Exit(1) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /helm/charts/influxdb/templates/post-install-set-auth.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.setDefaultUser.enabled -}} 2 | apiVersion: batch/v1 3 | kind: Job 4 | metadata: 5 | labels: 6 | app: {{ template "fullname" . }} 7 | chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" 8 | release: "{{ .Release.Name }}" 9 | heritage: "{{ .Release.Service }}" 10 | name: {{ template "fullname" . }}-set-auth 11 | annotations: 12 | "helm.sh/hook": post-install 13 | spec: 14 | activeDeadlineSeconds: {{ default 300 .Values.setDefaultUser.activeDeadlineSeconds }} 15 | template: 16 | metadata: 17 | labels: 18 | app: {{ template "fullname" . }} 19 | release: "{{ .Release.Name }}" 20 | spec: 21 | containers: 22 | - name: {{ template "fullname" . }}-set-auth 23 | image: "{{ .Values.setDefaultUser.image }}" 24 | env: 25 | - name: INFLUXDB_USER 26 | valueFrom: 27 | secretKeyRef: 28 | name: {{ template "fullname" . }}-auth 29 | key: influxdb-user 30 | - name: INFLUXDB_PASSWORD 31 | valueFrom: 32 | secretKeyRef: 33 | name: {{ template "fullname" . }}-auth 34 | key: influxdb-password 35 | args: 36 | - "/bin/sh" 37 | - "-c" 38 | - | 39 | curl -X POST http://{{ template "fullname" . }}:{{ .Values.config.http.bind_address }}/query \ 40 | --data-urlencode \ 41 | "q=CREATE USER \"${INFLUXDB_USER}\" WITH PASSWORD '${INFLUXDB_PASSWORD}' {{ .Values.setDefaultUser.user.privileges }}" 42 | restartPolicy: {{ .Values.setDefaultUser.restartPolicy }} 43 | {{- end -}} 44 | -------------------------------------------------------------------------------- /cmd/version_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package cmd 22 | 23 | import ( 24 | "bytes" 25 | "testing" 26 | 27 | "github.com/stretchr/testify/assert" 28 | ) 29 | 30 | func TestVersionCmdInit(t *testing.T) { 31 | assert.Equal(t, len(RootCmd.Commands()), 2) 32 | assert.Equal(t, RootCmd.Commands()[1], versionCmd) 33 | } 34 | 35 | func TestVersionCmdRun(t *testing.T) { 36 | org := out 37 | out = new(bytes.Buffer) 38 | defer func() { out = org }() 39 | 40 | versionCmdRun(nil, nil) 41 | assert.Equal(t, out.(*bytes.Buffer).String(), "changeme\n") 42 | } 43 | -------------------------------------------------------------------------------- /flow/step.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package flow 22 | 23 | import ( 24 | "context" 25 | "net/http" 26 | 27 | "github.com/northwesternmutual/kanali/metrics" 28 | "github.com/northwesternmutual/kanali/spec" 29 | opentracing "github.com/opentracing/opentracing-go" 30 | ) 31 | 32 | // Step is an interface that represents a step to be used in a flow 33 | type Step interface { 34 | GetName() string 35 | Do(ctx context.Context, proxy *spec.APIProxy, metrics *metrics.Metrics, w http.ResponseWriter, r *http.Request, resp *http.Response, trace opentracing.Span) error 36 | } 37 | -------------------------------------------------------------------------------- /cmd/version.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package cmd 22 | 23 | import ( 24 | "fmt" 25 | "io" 26 | "os" 27 | 28 | "github.com/spf13/cobra" 29 | ) 30 | 31 | func init() { 32 | RootCmd.AddCommand(versionCmd) 33 | } 34 | 35 | var out io.Writer = os.Stdout 36 | 37 | var versionCmd = &cobra.Command{ 38 | Use: `version`, 39 | Short: `version`, 40 | Long: `view the current version of this software`, 41 | Run: versionCmdRun, 42 | } 43 | 44 | var versionCmdRun = func(cmd *cobra.Command, args []string) { 45 | fmt.Fprint(out, "changeme") 46 | fmt.Fprint(out, "\n") 47 | } 48 | -------------------------------------------------------------------------------- /server/server_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package server 22 | 23 | import ( 24 | "testing" 25 | 26 | "github.com/northwesternmutual/kanali/config" 27 | "github.com/spf13/viper" 28 | "github.com/stretchr/testify/assert" 29 | ) 30 | 31 | func TestGetKanaliPort(t *testing.T) { 32 | assert.Equal(t, getKanaliPort(), 80) 33 | 34 | viper.Set(config.FlagServerPort.GetLong(), 12345) 35 | assert.Equal(t, getKanaliPort(), 12345) 36 | 37 | viper.Set(config.FlagServerPort.GetLong(), 0) 38 | viper.Set(config.FlagTLSCertFile.GetLong(), "hi") 39 | viper.Set(config.FlagTLSKeyFile.GetLong(), "bye") 40 | assert.Equal(t, getKanaliPort(), 443) 41 | } 42 | -------------------------------------------------------------------------------- /metrics/metrics.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package metrics 22 | 23 | // Metric represent a single request metric 24 | type Metric struct { 25 | Name string 26 | Value interface{} 27 | Index bool 28 | } 29 | 30 | // Metrics represents a list of Metrics associated with a request 31 | type Metrics []Metric 32 | 33 | // Add appends a metric to the list of metrics 34 | func (m *Metrics) Add(metrics ...Metric) { 35 | for _, metric := range metrics { 36 | *m = append(*m, metric) 37 | } 38 | } 39 | 40 | // Get retrives a specific metric by name 41 | func (m *Metrics) Get(name string) *Metric { 42 | for _, metric := range *m { 43 | if metric.Name == name { 44 | return &metric 45 | } 46 | } 47 | return nil 48 | } 49 | -------------------------------------------------------------------------------- /config/tracing.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package config 22 | 23 | func init() { 24 | Flags.Add( 25 | FlagTracingJaegerServerURL, 26 | FlagTracingJaegerAgentURL, 27 | ) 28 | } 29 | 30 | var ( 31 | // FlagTracingJaegerServerURL specifies the endpoint to the Jaeger server 32 | FlagTracingJaegerServerURL = Flag{ 33 | Long: "tracing.jaeger_server_url", 34 | Short: "", 35 | Value: "jaeger-all-in-one-agent.default.svc.cluster.local", 36 | Usage: "Endpoint to the Jaeger server", 37 | } 38 | // FlagTracingJaegerAgentURL specifies the endpoint to the Jaeger agent 39 | FlagTracingJaegerAgentURL = Flag{ 40 | Long: "tracing.jaeger_agent_url", 41 | Short: "", 42 | Value: "jaeger-all-in-one-agent.default.svc.cluster.local", 43 | Usage: "Endpoint to the Jaeger agent", 44 | } 45 | ) 46 | -------------------------------------------------------------------------------- /examples/exampleThree.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | apiVersion: v1 4 | kind: Namespace 5 | metadata: 6 | name: application 7 | labels: 8 | name: application 9 | 10 | --- 11 | 12 | kind: ConfigMap 13 | apiVersion: v1 14 | metadata: 15 | name: example-three 16 | namespace: application 17 | data: 18 | response: |- 19 | [{ 20 | "route": "/health", 21 | "code": 200, 22 | "method": "GET", 23 | "body": { 24 | "msg": "all systems are up and running" 25 | } 26 | }, 27 | { 28 | "route": "/accounts", 29 | "code": 200, 30 | "method": "GET", 31 | "body": [{ 32 | "id": "1", 33 | "balance": "$500.00" 34 | }, 35 | { 36 | "id": "2", 37 | "balance": "$1000.00" 38 | }] 39 | }] 40 | 41 | --- 42 | 43 | apiVersion: extensions/v1beta1 44 | kind: Deployment 45 | metadata: 46 | labels: 47 | app: example-three 48 | name: example-three 49 | namespace: application 50 | spec: 51 | replicas: 1 52 | selector: 53 | matchLabels: 54 | k8s-app: example-three 55 | template: 56 | metadata: 57 | labels: 58 | k8s-app: example-three 59 | spec: 60 | containers: 61 | - name: example-three 62 | imagePullPolicy: Always 63 | image: fbgrecojr/helloworld:golang 64 | ports: 65 | - containerPort: 8080 66 | 67 | --- 68 | 69 | apiVersion: v1 70 | kind: Service 71 | metadata: 72 | name: example-three 73 | namespace: application 74 | spec: 75 | selector: 76 | k8s-app: example-three 77 | ports: 78 | - name: http 79 | port: 8080 80 | type: ClusterIP 81 | 82 | --- 83 | 84 | apiVersion: "kanali.io/v1" 85 | kind: ApiProxy 86 | metadata: 87 | name: example-three 88 | namespace: application 89 | spec: 90 | path: /api/v1/example-three 91 | target: / 92 | mock: 93 | configMapName: example-three 94 | service: 95 | port: 8080 96 | name: example-three 97 | -------------------------------------------------------------------------------- /handlers/logger.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package handlers 22 | 23 | import ( 24 | "net/http" 25 | "strings" 26 | 27 | "github.com/Sirupsen/logrus" 28 | "github.com/northwesternmutual/kanali/utils" 29 | ) 30 | 31 | // Logger creates a custom http.Handler that logs details around a request 32 | // along with creating contextual request metrics. When the request is complete 33 | // these metrics will be writtin to Influxdb 34 | func Logger(inner Handler) http.Handler { 35 | 36 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 37 | 38 | inner.serveHTTP(w, r) 39 | 40 | logrus.WithFields(logrus.Fields{ 41 | "client ip": strings.Split(r.RemoteAddr, ":")[0], 42 | "method": r.Method, 43 | "uri": utils.ComputeURLPath(r.URL), 44 | }).Info("request details") 45 | 46 | }) 47 | 48 | } 49 | -------------------------------------------------------------------------------- /config/plugins.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package config 22 | 23 | func init() { 24 | Flags.Add( 25 | FlagPluginsLocation, 26 | FlagPluginsAPIKeyDecriptionKeyFile, 27 | ) 28 | } 29 | 30 | var ( 31 | // FlagPluginsLocation sets the location of custom plugins shared object (.so) files 32 | FlagPluginsLocation = Flag{ 33 | Long: "plugins.location", 34 | Short: "", 35 | Value: "/", 36 | Usage: "Location of custom plugins shared object (.so) files.", 37 | } 38 | // FlagPluginsAPIKeyDecriptionKeyFile set the location of the decryption RSA key file to be used to decrypt incoming API keys. 39 | FlagPluginsAPIKeyDecriptionKeyFile = Flag{ 40 | Long: "plugins.apiKey.decryption_key_file", 41 | Short: "", 42 | Value: "", 43 | Usage: "Path to valid PEM-encoded private key that matches the public key used to encrypt API keys.", 44 | } 45 | ) 46 | -------------------------------------------------------------------------------- /tracer/jaeger_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package tracer 22 | 23 | import ( 24 | "bytes" 25 | "testing" 26 | 27 | "github.com/Sirupsen/logrus" 28 | "github.com/stretchr/testify/assert" 29 | ) 30 | 31 | func TestCustomLoggerError(t *testing.T) { 32 | logger := customLogger{} 33 | 34 | writerOne := new(bytes.Buffer) 35 | writerTwo := new(bytes.Buffer) 36 | logrus.SetOutput(writerOne) 37 | logger.Error("custom error message") 38 | logrus.SetOutput(writerTwo) 39 | logrus.Error("custom error message") 40 | assert.Equal(t, writerOne.String(), writerTwo.String()) 41 | 42 | writerOne = new(bytes.Buffer) 43 | writerTwo = new(bytes.Buffer) 44 | logrus.SetOutput(writerOne) 45 | logger.Infof("custom %s message", "info") 46 | logrus.SetOutput(writerTwo) 47 | logrus.Infof("custom %s message", "info") 48 | assert.Equal(t, writerOne.String(), writerTwo.String()) 49 | } 50 | -------------------------------------------------------------------------------- /utils/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package utils 22 | 23 | // Error is an interface that is used to intuitively handle HTTP errors. 24 | // It expands on the native error interface that the language provides 25 | type Error interface { 26 | error 27 | Status() int 28 | } 29 | 30 | // StatusError implements the Error interface and is used to handle HTTP specific errors 31 | type StatusError struct { 32 | Code int 33 | Err error 34 | } 35 | 36 | // JSONErr is used to assist in marshalling HTTP errors 37 | type JSONErr struct { 38 | Code int `json:"code"` 39 | Msg string `json:"msg"` 40 | } 41 | 42 | // Error will return the error message associated with the error 43 | func (se StatusError) Error() string { 44 | return se.Err.Error() 45 | } 46 | 47 | // Status returns the HTTP error code associated with the error 48 | func (se StatusError) Status() int { 49 | return se.Code 50 | } 51 | -------------------------------------------------------------------------------- /helm/charts/grafana/templates/job.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.server.setDatasource.enabled -}} 2 | apiVersion: batch/v1 3 | kind: Job 4 | metadata: 5 | labels: 6 | app: {{ template "grafana.fullname" . }} 7 | chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" 8 | component: "{{ .Values.server.name }}" 9 | heritage: "{{ .Release.Service }}" 10 | release: "{{ .Release.Name }}" 11 | name: {{ template "grafana.server.fullname" . }}-set-datasource 12 | spec: 13 | activeDeadlineSeconds: {{ default 300 .Values.server.setDatasource.activeDeadlineSeconds }} 14 | template: 15 | metadata: 16 | labels: 17 | app: {{ template "grafana.fullname" . }} 18 | component: "{{ .Values.server.name }}" 19 | release: "{{ .Release.Name }}" 20 | spec: 21 | containers: 22 | - name: {{ template "grafana.server.fullname" . }}-set-datasource 23 | image: "{{ .Values.server.setDatasource.image }}" 24 | env: 25 | - name: ADMIN_USER 26 | valueFrom: 27 | secretKeyRef: 28 | name: {{ template "grafana.server.fullname" . }} 29 | key: grafana-admin-user 30 | - name: ADMIN_PASSWORD 31 | valueFrom: 32 | secretKeyRef: 33 | name: {{ template "grafana.server.fullname" . }} 34 | key: grafana-admin-password 35 | args: 36 | - "http://$(ADMIN_USER):$(ADMIN_PASSWORD)@{{ template "grafana.fullname" . }}:{{ .Values.server.httpPort }}/api/datasources" 37 | - "-X" 38 | - POST 39 | - "-H" 40 | - "Content-Type: application/json;charset=UTF-8" 41 | - "--data-binary" 42 | {{- with .Values.server.setDatasource.datasource }} 43 | - "{\"name\":\"{{ .name }}\",\"type\":\"{{ .type }}\",\"url\":\"{{ .url }}\",\"database\":\"{{ .database }}\",\"jsonData\":{ {{ .jsonData }} },\"access\":\"{{ .access }}\",\"isDefault\":{{ .isDefault }}}" 44 | {{- end }} 45 | restartPolicy: {{ .Values.server.setDatasource.restartPolicy }} 46 | {{- end -}} 47 | -------------------------------------------------------------------------------- /config/tls.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package config 22 | 23 | func init() { 24 | Flags.Add( 25 | FlagTLSCertFile, 26 | FlagTLSKeyFile, 27 | FlagTLSCaFile, 28 | ) 29 | } 30 | 31 | var ( 32 | // FlagTLSCertFile specifies the path to x509 certificate for HTTPS servers. 33 | FlagTLSCertFile = Flag{ 34 | Long: "tls.cert_file", 35 | Short: "c", 36 | Value: "", 37 | Usage: "Path to x509 certificate for HTTPS servers.", 38 | } 39 | // FlagTLSKeyFile pecifies the path to x509 private key matching --tls-cert-file 40 | FlagTLSKeyFile = Flag{ 41 | Long: "tls.key_file", 42 | Short: "k", 43 | Value: "", 44 | Usage: "Path to x509 private key matching --tls.cert_file.", 45 | } 46 | // FlagTLSCaFile specifies the path to x509 certificate authority bundle for mutual TLS 47 | FlagTLSCaFile = Flag{ 48 | Long: "tls.ca_file", 49 | Short: "", 50 | Value: "", 51 | Usage: "Path to x509 certificate authority bundle for mutual TLS.", 52 | } 53 | ) 54 | -------------------------------------------------------------------------------- /steps/pluginsonrequest_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package steps 22 | 23 | import ( 24 | "context" 25 | "testing" 26 | 27 | "github.com/northwesternmutual/kanali/spec" 28 | opentracing "github.com/opentracing/opentracing-go" 29 | "github.com/stretchr/testify/assert" 30 | ) 31 | 32 | func TestPluginsOnRequestGetName(t *testing.T) { 33 | assert := assert.New(t) 34 | step := PluginsOnRequestStep{} 35 | assert.Equal(step.GetName(), "Plugin OnRequest", "step name is incorrect") 36 | } 37 | 38 | func TestDoOnRequest(t *testing.T) { 39 | assert.Equal(t, doOnRequest(context.Background(), nil, "name", spec.APIProxy{}, nil, opentracing.StartSpan("test span"), fakePanicPlugin{}).Error(), "OnRequest paniced") 40 | assert.Equal(t, doOnRequest(context.Background(), nil, "name", spec.APIProxy{}, nil, opentracing.StartSpan("test span"), fakeErrorPlugin{}).Error(), "error") 41 | assert.Nil(t, doOnRequest(context.Background(), nil, "name", spec.APIProxy{}, nil, opentracing.StartSpan("test span"), fakeSuccessPlugin{})) 42 | } 43 | -------------------------------------------------------------------------------- /steps/pluginsonresponse_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package steps 22 | 23 | import ( 24 | "context" 25 | "testing" 26 | 27 | "github.com/northwesternmutual/kanali/spec" 28 | opentracing "github.com/opentracing/opentracing-go" 29 | "github.com/stretchr/testify/assert" 30 | ) 31 | 32 | func TestPluginsOnResponseGetName(t *testing.T) { 33 | step := PluginsOnResponseStep{} 34 | assert.Equal(t, step.GetName(), "Plugin OnResponse", "step name is incorrect") 35 | } 36 | 37 | func TestDoOnResponse(t *testing.T) { 38 | assert.Equal(t, doOnResponse(context.Background(), nil, "name", spec.APIProxy{}, nil, nil, opentracing.StartSpan("test span"), fakePanicPlugin{}).Error(), "OnResponse paniced") 39 | assert.Equal(t, doOnResponse(context.Background(), nil, "name", spec.APIProxy{}, nil, nil, opentracing.StartSpan("test span"), fakeErrorPlugin{}).Error(), "error") 40 | assert.Nil(t, doOnResponse(context.Background(), nil, "name", spec.APIProxy{}, nil, nil, opentracing.StartSpan("test span"), fakeSuccessPlugin{})) 41 | } 42 | -------------------------------------------------------------------------------- /helm/charts/jaeger/templates/ui.yml: -------------------------------------------------------------------------------- 1 | # --- 2 | # 3 | # apiVersion: extensions/v1beta1 4 | # kind: Deployment 5 | # metadata: 6 | # name: jaeger-query 7 | # namespace: tracing 8 | # spec: 9 | # replicas: 1 10 | # selector: 11 | # name: jaeger-query 12 | # strategy: 13 | # type: Recreate 14 | # selector: 15 | # matchLabels: 16 | # name: jaeger-query 17 | # template: 18 | # metadata: 19 | # labels: 20 | # name: jaeger-query 21 | # jaeger-infra: query 22 | # spec: 23 | # containers: 24 | # - name: jaeger-query 25 | # image: registry.nmlv.nml.com/epitropos/jaegertracing/jaeger-query:latest 26 | # imagePullPolicy: Always 27 | # command: 28 | # - /go/bin/query-linux 29 | # args: 30 | # - -cassandra.connections-per-host=2 31 | # - -cassandra.keyspace=jaeger_v1_test 32 | # - -cassandra.max-retry-attempts=3 33 | # - -cassandra.port=9042 34 | # - -cassandra.proto-version=4 35 | # - -cassandra.servers=jaeger-cassandra.tracing.svc.cluster.local 36 | # - -cassandra.socket-keep-alive=0h0m0s 37 | # - -cassandra.timeout=0h1m0s 38 | # - -dependency-storage.data-frequency=24h0m0s 39 | # - -dependency-storage.type=cassandra 40 | # - -log-level=info 41 | # - -query.port=16686 42 | # - -query.prefix=api 43 | # - -query.static-files=jaeger-ui-build/build/ 44 | # - -runtime-metrics-frequency=0h0m1s 45 | # - -span-storage.type=cassandra 46 | # ports: 47 | # - containerPort: 16686 48 | # protocol: TCP 49 | # resources: {} 50 | # readinessProbe: 51 | # httpGet: 52 | # path: / 53 | # port: 16686 54 | # initialDelaySeconds: 5 55 | # 56 | # --- 57 | # 58 | # apiVersion: v1 59 | # kind: Service 60 | # metadata: 61 | # name: jaeger-query 62 | # namespace: tracing 63 | # labels: 64 | # jaeger-infra: query 65 | # spec: 66 | # ports: 67 | # - name: query-https 68 | # port: 80 69 | # protocol: TCP 70 | # targetPort: 16686 71 | # selector: 72 | # name: jaeger-query 73 | # type: NodePort 74 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ALL_SRC := $(shell find . -name "*.go" | grep -v -e vendor \ 2 | -e ".*/\..*" \ 3 | -e ".*/_.*" \ 4 | -e ".*/mocks.*") 5 | 6 | BINARY=$(shell echo $${PWD\#\#*/}) 7 | FILES = $(shell go list ./... | grep -v /vendor/) 8 | PACKAGES := $(shell glide novendor) 9 | 10 | RACE=-race 11 | GOTEST=go test -v $(RACE) 12 | GOLINT=golint 13 | GOVET=go vet 14 | GOBUILD=go build $(RACE) 15 | GOFMT=gofmt 16 | ERRCHECK=errcheck -ignoretests 17 | FMT_LOG=fmt.log 18 | LINT_LOG=lint.log 19 | 20 | PASS=$(shell printf "\033[32mPASS\033[0m") 21 | FAIL=$(shell printf "\033[31mFAIL\033[0m") 22 | COLORIZE=sed ''/PASS/s//$(PASS)/'' | sed ''/FAIL/s//$(FAIL)/'' 23 | 24 | .DEFAULT_GOAL: $(BINARY) 25 | 26 | $(BINARY): $(ALL_SRC) test fmt lint 27 | 28 | .PHONY: install 29 | install: 30 | glide --version || go get github.com/Masterminds/glide 31 | glide install 32 | 33 | .PHONY: build 34 | build: 35 | GOOS=`go env GOHOSTOS` GOARCH=`go env GOHOSTARCH` $(GOBUILD) -o $(BINARY) 36 | 37 | .PHONY: fmt 38 | fmt: 39 | $(GOFMT) -e -s -l -w $(ALL_SRC) 40 | ./scripts/updateLicenses.sh 41 | 42 | .PHONY: cover 43 | cover: 44 | ./scripts/cover.sh $(shell go list $(PACKAGES)) 45 | go tool cover -html=cover.out -o cover.html 46 | 47 | .PHONY: test 48 | test: 49 | bash -c "set -e; set -o pipefail; $(GOTEST) $(PACKAGES) | $(COLORIZE)" 50 | 51 | .PHONY: lint 52 | lint: 53 | @$(GOVET) $(PACKAGES) 54 | @$(ERRCHECK) $(PACKAGES) 55 | @cat /dev/null > $(LINT_LOG) 56 | @$(foreach pkg, $(PACKAGES), $(GOLINT) $(pkg) >> $(LINT_LOG) || true;) 57 | @[ ! -s "$(LINT_LOG)" ] || (echo "Lint Failures" | cat - $(LINT_LOG) && false) 58 | @$(GOFMT) -e -s -l $(ALL_SRC) > $(FMT_LOG) 59 | @[ ! -s "$(FMT_LOG)" ] || (echo "Go Fmt Failures, run 'make fmt'" | cat - $(FMT_LOG) && false) 60 | 61 | .PHONY: install_ci 62 | install_ci: install 63 | go get github.com/wadey/gocovmerge 64 | go get github.com/mattn/goveralls 65 | go get golang.org/x/tools/cmd/cover 66 | go get github.com/golang/lint/golint 67 | go get github.com/kisielk/errcheck 68 | 69 | .PHONY: test_ci 70 | test_ci: 71 | @./scripts/cover.sh $(shell go list $(PACKAGES)) 72 | make lint 73 | 74 | .PHONY: clean 75 | clean: 76 | if [ -f ${BINARY} ] ; then rm ${BINARY} ; fi -------------------------------------------------------------------------------- /examples/exampleSix.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | apiVersion: v1 4 | kind: Namespace 5 | metadata: 6 | name: application 7 | labels: 8 | name: application 9 | 10 | --- 11 | 12 | apiVersion: extensions/v1beta1 13 | kind: Deployment 14 | metadata: 15 | name: example-six 16 | namespace: application 17 | spec: 18 | replicas: 1 19 | selector: 20 | matchLabels: 21 | k8s-app: example-six 22 | template: 23 | metadata: 24 | labels: 25 | k8s-app: example-six 26 | spec: 27 | containers: 28 | - name: example-six 29 | imagePullPolicy: Always 30 | image: fbgrecojr/helloworld:golang 31 | ports: 32 | - containerPort: 8080 33 | 34 | --- 35 | 36 | apiVersion: v1 37 | kind: Service 38 | metadata: 39 | name: example-six 40 | namespace: application 41 | spec: 42 | selector: 43 | k8s-app: example-six 44 | ports: 45 | - name: http 46 | port: 8080 47 | type: ClusterIP 48 | 49 | --- 50 | 51 | apiVersion: kanali.io/v1 52 | kind: ApiProxy 53 | metadata: 54 | name: example-six 55 | namespace: application 56 | spec: 57 | path: /api/v1/example-six 58 | target: / 59 | service: 60 | port: 8080 61 | name: example-six 62 | plugins: 63 | - name: apiKey 64 | version: v1.2.0 65 | 66 | --- 67 | 68 | apiVersion: kanali.io/v1 69 | kind: ApiKey 70 | metadata: 71 | name: janes-apikey 72 | namespace: application 73 | spec: 74 | data: 2b06207cce17aeeee93dba801dcfc8055638fe6eb00b0e644c48161bdcd66da42f6951ebc3cca02302f90c0ed2f42ecf28673fe1873af704c023367e3a7113919b8b85bd62384f5261d08fe4af51174141ae2836ab628d1ed58f030fca12c4fe53e8f1c8f836c9026a635ca6d419fb873fdeb621b40cfe336bbbf0c5fc2c352d044ae0f8a59b489a62e468a5b7e090f42127a8ad7a796cb2f67dfd81756d232b19f2522cf911809747c61fbe7f051219fe763e173ab074ae332f25c63bc2d6a5e190ed413f7bf830c006789677a69b4855ce54f06fd2a68d38a5267cec571b0de59198b537212d422cfc188366ce54e5f81a3485f5ead872688efdcdb0a3549b 75 | 76 | --- 77 | 78 | apiVersion: kanali.io/v1 79 | kind: ApiKeyBinding 80 | metadata: 81 | name: example-six 82 | namespace: application 83 | spec: 84 | proxy: example-six 85 | keys: 86 | - name: janes-apikey 87 | defaultRule: 88 | granular: 89 | verbs: 90 | - GET -------------------------------------------------------------------------------- /examples/exampleEight.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | apiVersion: v1 4 | kind: Namespace 5 | metadata: 6 | name: application 7 | labels: 8 | name: application 9 | 10 | --- 11 | 12 | apiVersion: extensions/v1beta1 13 | kind: Deployment 14 | metadata: 15 | name: example-eight 16 | namespace: application 17 | spec: 18 | replicas: 1 19 | selector: 20 | matchLabels: 21 | k8s-app: example-eight 22 | template: 23 | metadata: 24 | labels: 25 | k8s-app: example-eight 26 | spec: 27 | containers: 28 | - name: example-eight 29 | imagePullPolicy: Always 30 | image: fbgrecojr/helloworld:golang 31 | ports: 32 | - containerPort: 8080 33 | 34 | --- 35 | 36 | apiVersion: v1 37 | kind: Service 38 | metadata: 39 | name: example-eight 40 | namespace: application 41 | spec: 42 | selector: 43 | k8s-app: example-eight 44 | ports: 45 | - name: http 46 | port: 8080 47 | type: ClusterIP 48 | 49 | --- 50 | 51 | apiVersion: kanali.io/v1 52 | kind: ApiProxy 53 | metadata: 54 | name: example-eight 55 | namespace: application 56 | spec: 57 | path: /api/v1/example-eight 58 | target: / 59 | service: 60 | port: 8080 61 | name: example-eight 62 | plugins: 63 | - name: apiKey 64 | version: v1.2.0 65 | 66 | --- 67 | 68 | apiVersion: kanali.io/v1 69 | kind: ApiKey 70 | metadata: 71 | name: lonzos-apikey 72 | namespace: application 73 | spec: 74 | data: 2b06207cce17aeeee93dba801dcfc8055638fe6eb00b0e644c48161bdcd66da42f6951ebc3cca02302f90c0ed2f42ecf28673fe1873af704c023367e3a7113919b8b85bd62384f5261d08fe4af51174141ae2836ab628d1ed58f030fca12c4fe53e8f1c8f836c9026a635ca6d419fb873fdeb621b40cfe336bbbf0c5fc2c352d044ae0f8a59b489a62e468a5b7e090f42127a8ad7a796cb2f67dfd81756d232b19f2522cf911809747c61fbe7f051219fe763e173ab074ae332f25c63bc2d6a5e190ed413f7bf830c006789677a69b4855ce54f06fd2a68d38a5267cec571b0de59198b537212d422cfc188366ce54e5f81a3485f5ead872688efdcdb0a3549b 75 | 76 | --- 77 | 78 | apiVersion: kanali.io/v1 79 | kind: ApiKeyBinding 80 | metadata: 81 | name: example-eight 82 | namespace: application 83 | spec: 84 | proxy: example-eight 85 | keys: 86 | - name: lonzos-apikey 87 | quota: 4 88 | defaultRule: 89 | global: true 90 | -------------------------------------------------------------------------------- /examples/exampleNine.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | apiVersion: v1 4 | kind: Namespace 5 | metadata: 6 | name: application 7 | labels: 8 | name: application 9 | 10 | --- 11 | 12 | apiVersion: extensions/v1beta1 13 | kind: Deployment 14 | metadata: 15 | name: example-nine 16 | namespace: application 17 | spec: 18 | replicas: 1 19 | selector: 20 | matchLabels: 21 | k8s-app: example-nine 22 | template: 23 | metadata: 24 | labels: 25 | k8s-app: example-nine 26 | spec: 27 | containers: 28 | - name: example-nine 29 | imagePullPolicy: Always 30 | image: fbgrecojr/helloworld:golang 31 | ports: 32 | - containerPort: 8080 33 | 34 | --- 35 | 36 | apiVersion: v1 37 | kind: Service 38 | metadata: 39 | name: example-nine 40 | namespace: application 41 | spec: 42 | selector: 43 | k8s-app: example-nine 44 | ports: 45 | - name: http 46 | port: 8080 47 | type: ClusterIP 48 | 49 | --- 50 | 51 | apiVersion: kanali.io/v1 52 | kind: ApiProxy 53 | metadata: 54 | name: example-nine 55 | namespace: application 56 | spec: 57 | path: /api/v1/example-nine 58 | target: / 59 | service: 60 | port: 8080 61 | name: example-nine 62 | plugins: 63 | - name: apiKey 64 | version: v1.2.0 65 | 66 | --- 67 | 68 | apiVersion: kanali.io/v1 69 | kind: ApiKey 70 | metadata: 71 | name: johns-apikey 72 | namespace: application 73 | spec: 74 | data: 2b06207cce17aeeee93dba801dcfc8055638fe6eb00b0e644c48161bdcd66da42f6951ebc3cca02302f90c0ed2f42ecf28673fe1873af704c023367e3a7113919b8b85bd62384f5261d08fe4af51174141ae2836ab628d1ed58f030fca12c4fe53e8f1c8f836c9026a635ca6d419fb873fdeb621b40cfe336bbbf0c5fc2c352d044ae0f8a59b489a62e468a5b7e090f42127a8ad7a796cb2f67dfd81756d232b19f2522cf911809747c61fbe7f051219fe763e173ab074ae332f25c63bc2d6a5e190ed413f7bf830c006789677a69b4855ce54f06fd2a68d38a5267cec571b0de59198b537212d422cfc188366ce54e5f81a3485f5ead872688efdcdb0a3549b 75 | 76 | --- 77 | 78 | apiVersion: kanali.io/v1 79 | kind: ApiKeyBinding 80 | metadata: 81 | name: example-nine 82 | namespace: application 83 | spec: 84 | proxy: example-nine 85 | keys: 86 | - name: johns-apikey 87 | rate: 88 | amount: 5 89 | unit: second 90 | defaultRule: 91 | global: true 92 | -------------------------------------------------------------------------------- /examples/exampleSeven.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | apiVersion: v1 4 | kind: Namespace 5 | metadata: 6 | name: application 7 | labels: 8 | name: application 9 | 10 | --- 11 | 12 | apiVersion: extensions/v1beta1 13 | kind: Deployment 14 | metadata: 15 | name: example-seven 16 | namespace: application 17 | spec: 18 | replicas: 1 19 | selector: 20 | matchLabels: 21 | k8s-app: example-seven 22 | template: 23 | metadata: 24 | labels: 25 | k8s-app: example-seven 26 | spec: 27 | containers: 28 | - name: example-seven 29 | imagePullPolicy: Always 30 | image: fbgrecojr/helloworld:golang 31 | ports: 32 | - containerPort: 8080 33 | 34 | --- 35 | 36 | apiVersion: v1 37 | kind: Service 38 | metadata: 39 | name: example-seven 40 | namespace: application 41 | spec: 42 | selector: 43 | k8s-app: example-seven 44 | ports: 45 | - name: http 46 | port: 8080 47 | type: ClusterIP 48 | 49 | --- 50 | 51 | apiVersion: kanali.io/v1 52 | kind: ApiProxy 53 | metadata: 54 | name: example-seven 55 | namespace: application 56 | spec: 57 | path: /api/v1/example-seven 58 | target: / 59 | service: 60 | port: 8080 61 | name: example-seven 62 | plugins: 63 | - name: apiKey 64 | version: v1.2.0 65 | 66 | --- 67 | 68 | apiVersion: kanali.io/v1 69 | kind: ApiKey 70 | metadata: 71 | name: bobs-apikey 72 | namespace: application 73 | spec: 74 | data: 2b06207cce17aeeee93dba801dcfc8055638fe6eb00b0e644c48161bdcd66da42f6951ebc3cca02302f90c0ed2f42ecf28673fe1873af704c023367e3a7113919b8b85bd62384f5261d08fe4af51174141ae2836ab628d1ed58f030fca12c4fe53e8f1c8f836c9026a635ca6d419fb873fdeb621b40cfe336bbbf0c5fc2c352d044ae0f8a59b489a62e468a5b7e090f42127a8ad7a796cb2f67dfd81756d232b19f2522cf911809747c61fbe7f051219fe763e173ab074ae332f25c63bc2d6a5e190ed413f7bf830c006789677a69b4855ce54f06fd2a68d38a5267cec571b0de59198b537212d422cfc188366ce54e5f81a3485f5ead872688efdcdb0a3549b 75 | 76 | --- 77 | 78 | apiVersion: kanali.io/v1 79 | kind: ApiKeyBinding 80 | metadata: 81 | name: example-seven 82 | namespace: application 83 | spec: 84 | proxy: example-seven 85 | keys: 86 | - name: bobs-apikey 87 | subpaths: 88 | - path: /foo 89 | rule: 90 | granular: 91 | verbs: 92 | - GET 93 | -------------------------------------------------------------------------------- /helm/charts/jaeger/templates/collector.yml: -------------------------------------------------------------------------------- 1 | # --- 2 | # 3 | # apiVersion: extensions/v1beta1 4 | # kind: Deployment 5 | # metadata: 6 | # name: jaeger-collector 7 | # namespace: tracing 8 | # spec: 9 | # replicas: 1 10 | # selector: 11 | # name: jaeger-collector 12 | # strategy: 13 | # type: Recreate 14 | # selector: 15 | # matchLabels: 16 | # name: jaeger-collector 17 | # template: 18 | # metadata: 19 | # labels: 20 | # name: jaeger-collector 21 | # jaeger-infra: collector 22 | # spec: 23 | # containers: 24 | # - name: jaeger-collector 25 | # image: registry.nmlv.nml.com/epitropos/jaegertracing/jaeger-collector:latest 26 | # command: 27 | # - /go/bin/collector-linux 28 | # args: 29 | # - -cassandra.connections-per-host=2 30 | # - -cassandra.keyspace=jaeger_v1_test 31 | # - -cassandra.max-retry-attempts=5 32 | # - -cassandra.port=9042 33 | # - -cassandra.proto-version=4 34 | # - -cassandra.servers=jaeger-cassandra.tracing.svc.cluster.local 35 | # - -cassandra.socket-keep-alive=0h0m0s 36 | # - -cassandra.timeout=0h1m0s 37 | # - -collector.http-port=14268 38 | # - -collector.num-workers=50 39 | # - -collector.port=14267 40 | # - -collector.queue-size=2000 41 | # - -collector.write-cache-ttl=12h0m0s 42 | # - -dependency-storage.data-frequency=24h0m0s 43 | # - -dependency-storage.type=cassandra 44 | # - -log-level=debug 45 | # - -runtime-metrics-frequency=0h0m1s 46 | # - -span-storage.type=cassandra 47 | # ports: 48 | # - containerPort: 14267 49 | # protocol: UDP 50 | # - containerPort: 14268 51 | # protocol: TCP 52 | # imagePullPolicy: Always 53 | # 54 | # --- 55 | # 56 | # apiVersion: v1 57 | # kind: Service 58 | # metadata: 59 | # name: jaeger-collector 60 | # namespace: tracing 61 | # labels: 62 | # jaeger-infra: collector 63 | # spec: 64 | # ports: 65 | # - name: port-one 66 | # port: 14267 67 | # protocol: TCP 68 | # targetPort: 14267 69 | # - name: port-two 70 | # port: 14268 71 | # protocol: TCP 72 | # targetPort: 14268 73 | # selector: 74 | # name: jaeger-collector 75 | # type: ClusterIP 76 | -------------------------------------------------------------------------------- /flow/flow.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package flow 22 | 23 | import ( 24 | "context" 25 | "net/http" 26 | 27 | "github.com/Sirupsen/logrus" 28 | "github.com/northwesternmutual/kanali/metrics" 29 | "github.com/northwesternmutual/kanali/spec" 30 | "github.com/northwesternmutual/kanali/tracer" 31 | "github.com/opentracing/opentracing-go" 32 | ) 33 | 34 | // Flow is a list of steps 35 | type Flow []Step 36 | 37 | // Add appends a step to a flow 38 | func (f *Flow) Add(steps ...Step) { 39 | for _, step := range steps { 40 | *f = append(*f, step) 41 | } 42 | } 43 | 44 | // Play executes all step in a flow in the order they were added. 45 | func (f *Flow) Play(ctx context.Context, proxy *spec.APIProxy, metrics *metrics.Metrics, w http.ResponseWriter, r *http.Request, resp *http.Response, trace opentracing.Span) error { 46 | logrus.Debugf("flow with %d step about to play", len(*f)) 47 | for _, step := range *f { 48 | logrus.Debugf("playing step %s", step.GetName()) 49 | if err := step.Do(ctx, proxy, metrics, w, r, resp, trace); err != nil { 50 | trace.SetTag(tracer.Error, true) 51 | trace.LogKV( 52 | "event", "error", 53 | "error.message", err.Error(), 54 | ) 55 | return err 56 | } 57 | } 58 | return nil 59 | } 60 | -------------------------------------------------------------------------------- /helm/templates/decryptSecret.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | apiVersion: v1 4 | kind: Secret 5 | metadata: 6 | name: kanali-key-decription 7 | namespace: default 8 | type: Opaque 9 | data: 10 | key.pem: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQ0KTUlJRW93SUJBQUtDQVFFQXFZZG5YMGplT1gwelp1VEcwekRKK3QxcXpBNjNNTXhZbGx3Y05kU0l1REN2VDZSbg0Kd1NnMG54K1BTV1lRUXFYQ043cThDU3NCZ3A2UU5uZUN4TDNBLzFKelY3dy9mTXlXTEl1dVNPbjdHaThJeis3RQ0KTWI5ZGJQemVqSGJ4NDRURHpqSW0rK3h3cHlSNTZlNlpxaThoK1hGZk5UeFExSVdzaVVRSnNFdk5PdXM5a205Mg0KZ1RaOWhKTlg4R2dmQ3Z1UDBCakRzWEdqUVZoVXU3dExPNGVjY1h2WmpuTExZck9NOXF0a2tFZjhlRC8xZGg1Kw0KbnZtZ0NsMlFJOVlIcStPdmxDTHRBYzJtMXR4UHh0dkN1cjUxUmpvUFVYa21DZ2xqWmRxYWRTS2NVUnUvRFhNRQ0KY0xGMTNzbVFsNkpxNGdHelFZOTE5UEMxRWpraEdLWkEvRUZ0VXdJREFRQUJBb0lCQURONnI1UktyMWlyd1Rraw0KalkvQ0NBT0t5d3h1QjRqazlKMnNHTkRyMmh4OGhDL2VEN2VpK3QrN0dLckVPSG5VbGZhUVdOczcyUGlPSitLeQ0KUmQ1eWRMSFRtcnp3cUNMQWlYVzdjTkFwWlJ2ZFhvS3QwWnY5cldRVUlZeHI3aVlWd2RQU2ZPNFJMV0JEL2xWZw0KSS85KzBvVkp2UXlRWlVjejFHSFdiRTdCcGUrVzB2a0RlRlh4bENQMzlVV216ZkNDaGh6Q0ZYVFRndmwyRUhkeA0KUU1Qbm4zZGhqZit1QnRaWlhVcGpvNmxGTklyV2xxblNnRTdrckpVZnpENVRWZ0cxcThBZkY1QmRZbUF0RW9JYQ0KbldRcm41MSsrc2VKUWNDaDBnNGJSV2Jqazc5UXAydWFub2VWWnBaZFJRU2FndVVuSmJPTFhrU0pBaXFBY3pvRA0KTVdxWFNXa0NnWUVBMlNwSk1NZE4zMHlqVFplSFBMYjcyd3VtN2wrWlo2V3J2OHp0NkFXUEEzVDdpNmRCUlNLaQ0KRDZ5Y0NMazZWNlNXT0VnbTJNcnVZeHd2ZDNsWTNYSGpKd21mcFdxUWpZcktwMXU2dytCOE5LaGNkQXgybGNuMw0KVXY3cnYxQS9FdDdRMWFiekw5ZS92dGVQMG81c1lEdmZER3hNRnQ5amd1NDNTL3JwSE9XVGUxMENnWUVBeDloYQ0KaUhIY1BmcHlzWlI1a1gwZU9UOHBYcE9HNlVmckV3Mzh5WnFJeUNHejRmeVpLUStidHRnMFdxSE1wV2UxbXc5UA0KcFlCcTJQdHpVdXlKVkdmdDl4VFA3T3Yxb2E3NDFjWUZwQmJZdFc0ZTgxQ1dQQmo0Y1pOQnpxMVkwZGc0SHcwcw0KYUdZQlE5TDg3a29NMGVsZmpObzIvSEpmVFZjOE9XVTYwRVdMc0c4Q2dZQmcyQlN2cGhHNkpRa21UdzdHS3F3Qw0KTVI0T2E1K1RzelAyWXNNdGwxMEJvNmVSemRLenJCQXRnVUpNT1o0ays0YnFMbkwwZHZyOFE5Ti9LaVJSRExySg0KNithLzg5Zm01eUFjcGpHUnJJaDNTeVYvc3hjbkVWdzBMTzZnOEg1UVFnRkxaaHBKR2FPdXpaNmJ2VnZqUm8vZg0Ka0dRV1J5U3ZmT0E0Qi9yeElnZzFHUUtCZ0NNL1ZxQndMSjlGMkFyWUhDVDhBMk9uYnoxK0diSjFlOUd0aXVObg0KL1M0SE83bmxHb0p5ZlUxZmpzUlplMFhGSi9QRVhKRGRPSHN5eG1GZTFNM3RVcnhja0Z2Q05sMmhCY1IybTdJWQ0KVVhxV2hLRDNtcmZZMDZEOGp3UEw4VGw1d0ZSQnQ0NW1SMXpXRHNSY2pTeE0xQXg4eEd2OEpERDQ3T2RXb212dg0KaURiREFvR0JBSy9XcHpvTjU5dnNhdytQY3hTOTNXZ0JBbkdlOXErbVB4TlBMajB4YWFraU5GbjdGQ2RpR1Y4eg0KNFduMHN1YTQ4djdRY0piS2NMMFpiWGt5NjFFWlYzSHFBeXpoV1o2alNTUW9NMzdTL25QcHE1RVBUZnM0RHZubA0KQkE3ZFdleUxGbk43ZVBWU2tMMVNFUzU4TXBNTWl1bnJVby9DaTZPamlPeU43eW5uVTZ0RQ0KLS0tLS1FTkQgUlNBIFBSSVZBVEUgS0VZLS0tLS0= -------------------------------------------------------------------------------- /metrics/metrics_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package metrics 22 | 23 | import ( 24 | "testing" 25 | 26 | "github.com/stretchr/testify/assert" 27 | ) 28 | 29 | func TestAdd(t *testing.T) { 30 | f := &Metrics{} 31 | f.Add(Metric{"nameOne", "valueOne", false}) 32 | f.Add(Metric{"nameTwo", "valueTwo", true}, Metric{"nameThree", "valueThree", false}) 33 | assert.Equal(t, len(*f), 3) 34 | assert.False(t, (*f)[0].Index) 35 | assert.True(t, (*f)[1].Index) 36 | assert.False(t, (*f)[2].Index) 37 | assert.Equal(t, (*f)[0].Name, "nameOne") 38 | assert.Equal(t, (*f)[1].Name, "nameTwo") 39 | assert.Equal(t, (*f)[2].Name, "nameThree") 40 | assert.Equal(t, (*f)[0].Value, "valueOne") 41 | assert.Equal(t, (*f)[1].Value, "valueTwo") 42 | assert.Equal(t, (*f)[2].Value, "valueThree") 43 | } 44 | 45 | func TestGet(t *testing.T) { 46 | f := &Metrics{} 47 | f.Add(Metric{"nameOne", "valueOne", false}) 48 | f.Add(Metric{"nameTwo", "valueTwo", true}, Metric{"nameThree", "valueThree", false}) 49 | assert.Nil(t, f.Get("nameFour")) 50 | assert.Equal(t, f.Get("nameOne"), &Metric{"nameOne", "valueOne", false}) 51 | assert.Equal(t, f.Get("nameTwo"), &Metric{"nameTwo", "valueTwo", true}) 52 | assert.Equal(t, f.Get("nameThree"), &Metric{"nameThree", "valueThree", false}) 53 | } 54 | -------------------------------------------------------------------------------- /server/udp_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package server 22 | 23 | import ( 24 | "net" 25 | "testing" 26 | "time" 27 | 28 | "github.com/northwesternmutual/kanali/config" 29 | "github.com/northwesternmutual/kanali/spec" 30 | "github.com/spf13/viper" 31 | "github.com/stretchr/testify/assert" 32 | ) 33 | 34 | func TestStartUDPServer(t *testing.T) { 35 | 36 | assert := assert.New(t) 37 | 38 | viper.SetDefault(config.FlagServerPeerUDPPort.GetLong(), 10001) 39 | defer viper.Reset() 40 | 41 | go func() { 42 | if err := StartUDPServer(); err != nil { 43 | assert.Fail("upd server threw an error: ", err.Error()) 44 | } 45 | }() 46 | 47 | time.Sleep(time.Millisecond * 100) 48 | 49 | serverAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:10001") 50 | if err != nil { 51 | assert.Fail("there was an error: ", err.Error()) 52 | } 53 | 54 | conn, err := net.DialUDP("udp", nil, serverAddr) 55 | if err != nil { 56 | assert.Fail("there was an error: ", err.Error()) 57 | } 58 | 59 | defer conn.Close() 60 | 61 | _, err = conn.Write([]byte("namespace-one,proxy-one,key-one")) 62 | if err != nil { 63 | assert.Fail("there was an error: ", err.Error()) 64 | } 65 | 66 | time.Sleep(time.Millisecond * 100) 67 | 68 | assert.False(spec.TrafficStore.IsEmpty()) 69 | 70 | } 71 | -------------------------------------------------------------------------------- /tracer/jaeger.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package tracer 22 | 23 | import ( 24 | "fmt" 25 | "io" 26 | "time" 27 | 28 | "github.com/Sirupsen/logrus" 29 | "github.com/northwesternmutual/kanali/config" 30 | "github.com/opentracing/opentracing-go" 31 | "github.com/spf13/viper" 32 | jaegerConfig "github.com/uber/jaeger-client-go/config" 33 | ) 34 | 35 | type customLogger struct{} 36 | 37 | func (l customLogger) Error(msg string) { 38 | logrus.Error(msg) 39 | } 40 | 41 | func (l customLogger) Infof(msg string, args ...interface{}) { 42 | logrus.Info(fmt.Sprintf(msg, args...)) 43 | } 44 | 45 | // Jaeger creates a new opentracing compatible tracer 46 | func Jaeger() (opentracing.Tracer, io.Closer, error) { 47 | 48 | cfg := jaegerConfig.Configuration{ 49 | Sampler: &jaegerConfig.SamplerConfig{ 50 | Type: "const", 51 | SamplingServerURL: viper.GetString(config.FlagTracingJaegerServerURL.GetLong()), 52 | Param: 1, 53 | }, 54 | Reporter: &jaegerConfig.ReporterConfig{ 55 | LogSpans: true, 56 | BufferFlushInterval: 1 * time.Second, 57 | LocalAgentHostPort: fmt.Sprintf("%s:5775", viper.GetString(config.FlagTracingJaegerAgentURL.GetLong())), 58 | }, 59 | } 60 | 61 | return cfg.New("kanali", jaegerConfig.Logger(customLogger{})) 62 | 63 | } 64 | -------------------------------------------------------------------------------- /helm/charts/grafana/README.md: -------------------------------------------------------------------------------- 1 | # Grafana Helm Chart 2 | 3 | * Installs the web dashboarding system [Grafana](http://grafana.org/) 4 | 5 | ## TL;DR; 6 | 7 | ```console 8 | $ helm install stable/grafana 9 | ``` 10 | 11 | ## Installing the Chart 12 | 13 | To install the chart with the release name `my-release`: 14 | 15 | ```console 16 | $ helm install --name my-release stable/grafana 17 | ``` 18 | 19 | ## Uninstalling the Chart 20 | 21 | To uninstall/delete the my-release deployment: 22 | 23 | ```console 24 | $ helm delete my-release 25 | ``` 26 | 27 | The command removes all the Kubernetes components associated with the chart and deletes the release. 28 | 29 | 30 | ## Configuration 31 | 32 | | Parameter | Description | Default | 33 | |----------------------------------------|-------------------------------------|---------------------------------------------------| 34 | | `server.image` | Container image to run | grafana/grafana:latest | 35 | | `server.adminUser` | Admin user username | admin | 36 | | `server.adminPassword` | Admin user password | admin | 37 | | `server.persistentVolume.enabled` | Create a volume to store data | true | 38 | | `server.persistentVolume.size` | Size of persistent volume claim | 1Gi RW | 39 | | `server.persistentVolume.storageClass` | Type of persistent volume claim | `nil` (uses alpha storage class annotation) | 40 | | `server.persistentVolume.accessMode` | ReadWriteOnce or ReadOnly | [ReadWriteOnce] | 41 | | `server.persistentVolume.existingClaim`| Existing persistent volume claim | null | 42 | | `server.persistentVolume.subPath` | Subdirectory of pvc to mount | null | 43 | | `server.resources` | Server resource requests and limits | requests: {cpu: 100m, memory: 100Mi} | 44 | | `server.serviceType` | ClusterIP, NodePort, or LoadBalancer| ClusterIP | 45 | | `server.setDatasource.enabled` | Creates grafana datasource with job | false | 46 | -------------------------------------------------------------------------------- /steps/writeresponse.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package steps 22 | 23 | import ( 24 | "context" 25 | "io" 26 | "net/http" 27 | "strconv" 28 | 29 | "github.com/Sirupsen/logrus" 30 | "github.com/northwesternmutual/kanali/metrics" 31 | "github.com/northwesternmutual/kanali/spec" 32 | "github.com/northwesternmutual/kanali/tracer" 33 | "github.com/opentracing/opentracing-go" 34 | ) 35 | 36 | // WriteResponseStep is factory that defines a step responsible for writing 37 | // an HTTP response 38 | type WriteResponseStep struct{} 39 | 40 | // GetName retruns the name of the WriteResponseStep step 41 | func (step WriteResponseStep) GetName() string { 42 | return "Write Response" 43 | } 44 | 45 | // Do executes the logic of the WriteResponseStep step 46 | func (step WriteResponseStep) Do(ctx context.Context, proxy *spec.APIProxy, m *metrics.Metrics, w http.ResponseWriter, r *http.Request, resp *http.Response, span opentracing.Span) error { 47 | 48 | for k, v := range resp.Header { 49 | for _, value := range v { 50 | w.Header().Set(k, value) 51 | } 52 | } 53 | 54 | m.Add(metrics.Metric{Name: "http_response_code", Value: strconv.Itoa(resp.StatusCode), Index: true}) 55 | 56 | tracer.HydrateSpanFromResponse(resp, span) 57 | 58 | w.WriteHeader(resp.StatusCode) 59 | 60 | if _, err := io.Copy(w, resp.Body); err != nil { 61 | logrus.Warnf("error copying data to http response: %s", err.Error()) 62 | } 63 | 64 | return nil 65 | } 66 | -------------------------------------------------------------------------------- /handlers/logger_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package handlers 22 | 23 | import ( 24 | "bytes" 25 | "fmt" 26 | "io/ioutil" 27 | "net" 28 | "net/http" 29 | "os" 30 | "strings" 31 | "testing" 32 | 33 | "github.com/Sirupsen/logrus" 34 | "github.com/stretchr/testify/assert" 35 | ) 36 | 37 | func TestLogger(t *testing.T) { 38 | server := &http.Server{Addr: "127.0.0.1:40123", Handler: Logger(Handler{InfluxController: nil, H: IncomingRequest})} 39 | listener, _ := net.Listen("tcp4", "127.0.0.1:40123") 40 | go server.Serve(listener) 41 | defer server.Close() 42 | 43 | writer := new(bytes.Buffer) 44 | logrus.SetOutput(writer) 45 | resp, err := http.Get("http://127.0.0.1:40123/") 46 | logrus.SetOutput(os.Stdout) 47 | assert.Nil(t, err) 48 | assert.Equal(t, resp.Header.Get("Content-Type"), "application/json") 49 | 50 | body, _ := ioutil.ReadAll(resp.Body) 51 | assert.Equal(t, string(body), fmt.Sprintf("%s\n", `{"code":404,"msg":"proxy not found"}`)) 52 | assert.Equal(t, resp.StatusCode, 404) 53 | 54 | logOutput := writer.String() 55 | assert.True(t, strings.Contains(logOutput, `msg="proxy not found"`)) 56 | assert.True(t, strings.Contains(logOutput, `msg="request details"`)) 57 | assert.True(t, strings.Contains(logOutput, `level=error`)) 58 | assert.True(t, strings.Contains(logOutput, `client ip=127.0.0.1`)) 59 | assert.True(t, strings.Contains(logOutput, `method=GET`)) 60 | assert.True(t, strings.Contains(logOutput, `uri="/"`)) 61 | } 62 | -------------------------------------------------------------------------------- /helm/charts/grafana/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get your '{{ .Values.server.adminUser }}' user password by running: 2 | 3 | kubectl get secret --namespace {{ .Release.Namespace }} {{ template "grafana.server.fullname" . }} -o jsonpath="{.data.grafana-admin-password}" | base64 --decode ; echo 4 | 5 | 2. The Grafana server can be accessed via port {{ .Values.server.httpPort }} on the following DNS name from within your cluster: 6 | 7 | {{ template "grafana.server.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local 8 | {{ if .Values.server.ingress.enabled }} 9 | From outside the cluster, the server URL(s) are: 10 | {{- range .Values.server.ingress.hosts }} 11 | http://{{ . }} 12 | {{- end }} 13 | {{ else }} 14 | Get the Grafana URL to visit by running these commands in the same shell: 15 | {{ if contains "NodePort" .Values.server.serviceType -}} 16 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "grafana.server.fullname" . }}) 17 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 18 | echo http://$NODE_IP:$NODE_PORT 19 | {{ else if contains "LoadBalancer" .Values.server.serviceType -}} 20 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 21 | You can watch the status of by running 'kubectl get svc --namespace {{ .Release.Namespace }} -w {{ template "grafana.server.fullname" . }}' 22 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "grafana.server.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') 23 | echo http://$SERVICE_IP:{{ .Values.server.httpPort -}} 24 | {{ else if contains "ClusterIP" .Values.server.serviceType }} 25 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "grafana.fullname" . }},component={{ .Values.server.name }}" -o jsonpath="{.items[0].metadata.name}") 26 | kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 3000 27 | {{- end }} 28 | {{- end }} 29 | 30 | 3. Login with the password from step 1 and the username: {{ .Values.server.adminUser }} 31 | 32 | {{- if .Values.server.persistentVolume.enabled }} 33 | {{- else }} 34 | ################################################################################# 35 | ###### WARNING: Persistence is disabled!!! You will lose your data when ##### 36 | ###### the Grafana pod is terminated. ##### 37 | ################################################################################# 38 | {{- end }} 39 | -------------------------------------------------------------------------------- /config/server.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package config 22 | 23 | func init() { 24 | Flags.Add( 25 | FlagServerPort, 26 | FlagServerBindAddress, 27 | FlagServerPeerUDPPort, 28 | FlagServerProxyProtocol, 29 | ) 30 | } 31 | 32 | var ( 33 | // FlagServerPort sets the port that Kanali will listen on for incoming requests 34 | FlagServerPort = Flag{ 35 | Long: "server.port", 36 | Short: "p", 37 | Value: 0, 38 | Usage: "Sets the port that Kanali will listen on for incoming requests.", 39 | } 40 | // FlagServerBindAddress specifies the network address that Kanali will listen on for incoming requests 41 | FlagServerBindAddress = Flag{ 42 | Long: "server.bind_address", 43 | Short: "b", 44 | Value: "0.0.0.0", 45 | Usage: "Network address that Kanali will listen on for incoming requests.", 46 | } 47 | // FlagServerPeerUDPPort sets the port that all Kanali instances will communicate to each other over 48 | FlagServerPeerUDPPort = Flag{ 49 | Long: "server.peer_udp_port", 50 | Short: "", 51 | Value: 10001, 52 | Usage: "Sets the port that all Kanali instances will communicate to each other over.", 53 | } 54 | // FlagServerProxyProtocol maintains the integrity of the remote client IP address when incoming traffic to Kanali includes the Proxy Protocol header 55 | FlagServerProxyProtocol = Flag{ 56 | Long: "server.proxy_protocol", 57 | Short: "", 58 | Value: false, 59 | Usage: "Maintain the integrity of the remote client IP address when incoming traffic to Kanali includes the Proxy Protocol header.", 60 | } 61 | ) 62 | -------------------------------------------------------------------------------- /steps/writeresponse_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package steps 22 | 23 | import ( 24 | "bytes" 25 | "context" 26 | "io/ioutil" 27 | "net/http" 28 | "net/http/httptest" 29 | "testing" 30 | 31 | "github.com/northwesternmutual/kanali/metrics" 32 | opentracing "github.com/opentracing/opentracing-go" 33 | "github.com/stretchr/testify/assert" 34 | ) 35 | 36 | func TestWriteResponseGetName(t *testing.T) { 37 | step := WriteResponseStep{} 38 | assert.Equal(t, step.GetName(), "Write Response", "step name is incorrect") 39 | } 40 | 41 | func TestWriteResponseDo(t *testing.T) { 42 | step := WriteResponseStep{} 43 | writer := httptest.NewRecorder() 44 | response := &httptest.ResponseRecorder{ 45 | Code: 200, 46 | HeaderMap: http.Header{ 47 | "One": []string{"two"}, 48 | "Three": []string{"four"}, 49 | }, 50 | Body: bytes.NewBuffer([]byte("this is my mock response body")), 51 | } 52 | err := step.Do(context.Background(), nil, &metrics.Metrics{}, writer, nil, response.Result(), opentracing.StartSpan("test span")) 53 | defer writer.Result().Body.Close() 54 | assert.Nil(t, err) 55 | assert.Equal(t, writer.Result().StatusCode, 200) 56 | assert.Equal(t, writer.Result().Status, "OK") 57 | assert.Equal(t, len(writer.Result().Header), 2) 58 | assert.Equal(t, writer.Result().Header.Get("one"), "two") 59 | assert.Equal(t, writer.Result().Header.Get("three"), "four") 60 | bodyBytes, err := ioutil.ReadAll(writer.Result().Body) 61 | assert.Nil(t, err) 62 | assert.Equal(t, string(bodyBytes), "this is my mock response body") 63 | } 64 | -------------------------------------------------------------------------------- /helm/charts/jaeger/templates/agent.yml: -------------------------------------------------------------------------------- 1 | # --- 2 | # 3 | # apiVersion: extensions/v1beta1 4 | # kind: Deployment 5 | # metadata: 6 | # name: jaeger-agent 7 | # namespace: tracing 8 | # spec: 9 | # replicas: 1 10 | # selector: 11 | # name: jaeger-agent 12 | # strategy: 13 | # type: Recreate 14 | # selector: 15 | # matchLabels: 16 | # name: jaeger-agent 17 | # template: 18 | # metadata: 19 | # labels: 20 | # name: jaeger-agent 21 | # jaeger-infra: agent 22 | # spec: 23 | # containers: 24 | # - name: agent 25 | # image: registry.nmlv.nml.com/epitropos/jaegertracing/jaeger-agent:latest 26 | # command: 27 | # - /go/bin/agent-linux 28 | # args: 29 | # - -collector.host-port=jaeger-collector.tracing.svc.cluster.local:14267 30 | # - -discovery.min-peers=3 31 | # - -http-server.host-port=:5778 32 | # - -processor.jaeger-binary.server-host-port=:6832 33 | # - -processor.jaeger-binary.server-max-packet-size=65000 34 | # - -processor.jaeger-binary.server-queue-size=1000 35 | # - -processor.jaeger-binary.workers=10 36 | # - -processor.jaeger-compact.server-host-port=:6831 37 | # - -processor.jaeger-compact.server-max-packet-size=65000 38 | # - -processor.jaeger-compact.server-queue-size=1000 39 | # - -processor.jaeger-compact.workers=10 40 | # - -processor.zipkin-compact.server-host-port=:5775 41 | # - -processor.zipkin-compact.server-max-packet-size=65000 42 | # - -processor.zipkin-compact.server-queue-size=1000 43 | # - -processor.zipkin-compact.workers=10 44 | # ports: 45 | # - containerPort: 5775 46 | # protocol: UDP 47 | # - containerPort: 6831 48 | # protocol: UDP 49 | # - containerPort: 6832 50 | # protocol: UDP 51 | # - containerPort: 5778 52 | # protocol: TCP 53 | # resources: {} 54 | # imagePullPolicy: Always 55 | # securityContext: {} 56 | # 57 | # --- 58 | # 59 | # apiVersion: v1 60 | # kind: Service 61 | # metadata: 62 | # name: jaeger-agent 63 | # namespace: tracing 64 | # labels: 65 | # jaeger-infra: agent 66 | # spec: 67 | # ports: 68 | # - name: agent-zipkin-thrift 69 | # port: 5775 70 | # protocol: UDP 71 | # targetPort: 5775 72 | # - name: agent-compact 73 | # port: 6831 74 | # protocol: UDP 75 | # targetPort: 6831 76 | # - name: agent-binary 77 | # port: 6832 78 | # protocol: UDP 79 | # targetPort: 6832 80 | # - name: agent-sampling 81 | # port: 5778 82 | # protocol: TCP 83 | # targetPort: 5778 84 | # selector: 85 | # name: jaeger-agent 86 | # type: ClusterIP 87 | -------------------------------------------------------------------------------- /scripts/updateLicense.py: -------------------------------------------------------------------------------- 1 | from __future__ import ( 2 | absolute_import, print_function, division, unicode_literals 3 | ) 4 | 5 | import re 6 | import sys 7 | from datetime import datetime 8 | 9 | CURRENT_YEAR = datetime.today().year 10 | 11 | LICENSE_BLOB = """Copyright (c) %d Northwestern Mutual. 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in 21 | all copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 29 | THE SOFTWARE.""" % CURRENT_YEAR 30 | 31 | LICENSE_BLOB_LINES_GO = [ 32 | ('// ' + l).strip() + '\n' for l in LICENSE_BLOB.split('\n') 33 | ] 34 | 35 | COPYRIGHT_RE = re.compile(r'Copyright \(c\) (\d+)', re.I) 36 | 37 | 38 | def update_go_license(name): 39 | with open(name) as f: 40 | orig_lines = list(f) 41 | lines = list(orig_lines) 42 | 43 | found = False 44 | changed = False 45 | for i, line in enumerate(lines[:5]): 46 | m = COPYRIGHT_RE.search(line) 47 | if not m: 48 | continue 49 | 50 | found = True 51 | break 52 | 53 | if not found: 54 | if 'Code generated by' in lines[0]: 55 | lines[1:1] = ['\n'] + LICENSE_BLOB_LINES_GO 56 | else: 57 | lines[0:0] = LICENSE_BLOB_LINES_GO + ['\n'] 58 | changed = True 59 | 60 | if changed: 61 | with open(name, 'w') as f: 62 | for line in lines: 63 | f.write(line) 64 | 65 | 66 | def main(): 67 | if len(sys.argv) == 1: 68 | print('USAGE: %s FILE ...' % sys.argv[0]) 69 | sys.exit(1) 70 | 71 | for name in sys.argv[1:]: 72 | if name.endswith('.go'): 73 | update_go_license(name) 74 | else: 75 | raise NotImplementedError('Unsupported file type: %s' % name) 76 | 77 | 78 | if __name__ == "__main__": 79 | main() 80 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | 3 | language: go 4 | 5 | go: 6 | - 1.8.3 7 | 8 | env: 9 | global: 10 | - secure: kqPYySZHukKk79buA26c9staHoX7roMpp13aalARyDKoz8qdfgcCLMfBkAjI3OpIYRpqZHQlla67YBCrBgINA6ahoUm4AQF2nK7P7ll3ZM/VHwjxY6DY+wN7sSlMotBwu6qSkoCs34U8Vd1c6uCjNmDlGGgeTAY2WlLAnHNAe8FVNlYKX3fb2MOolKeLuZLh36VPbrF1409WyUFU79C20CMfN5X0sq13xOqwWJHjOaYF+wnEoOsFGt62lqGmBWXEwTOfFRKwVrYleDhR+LU0onW7k12KHydrAcI4j41mDfi0GUmLYPgCdyoffcyPNbdu6ZjmDEhF3ZSl9QXVoaSaEDIOinnbi9WJvmTRa1pE8rvB2VVz+pGB1V24aYMKZreSdkuCtFfJ1UxklW88312NcItPBVJbInY4Kqn/gMA9BwFMtzCD+pBpR06r83P3iOFs7zvzxTnuuBQ+TJUMw8PQBaRlgrDyrFY/9ORHgSGCSrquPg6nVMcxvHQoU4ugkC6fbSqnivq7TEYHTfKcUEVpLqqK+pdfB1qqCo2nAtfsViRPV2c5BXg5JyJT0uBIjukPnt7k/sJ3BOex1PAow0lgsURyy3JnRf8qAg4h28cZBBhTZCj3ovyC9TH9pcI/46EqmP7Yqs1L0UTReOG8+0cHT+d8wxECnWI8PjqN20rGWw8= 11 | - secure: BUFDeBBeZZ4yDuKFes7Myn1p78uYnQXnwNrgzQ48X7NBMSf5oaDL6UUH9efdiQwotF+IX8uPLU4mBabDFzZNoeb/nKEKnDMmXETjw/2x1zT1qMl3JjJbxcOM6ca3sbnOBFK4GYqgQjOR75NczVJWdZ/QsYghiPkTL+dn6nyLs7RbKXovUFu4RmM36h03R+/0LdkPPcYOa6BbAjs43BIHqsqmWDcQYs6VLVamaHQZjdVjUISP9ymK4NncXyfqDF2uRJPzitTNBwgDnkNwxBylBezrE+xIYtbGfZemVvwU6UIlQ5qHAZQgnvW8k3txmMgWg5tPxahSm0/TUUxtT32YO1aZwpjdhZmqAt0xkTQo3jJCxQS45QuTkgjaCTFXp5dZWegY+ftGWKRPTPnAVStTRephTjyBjxrPRIqNlNBOTGfMql1CE0wr/6kH3yrlx6jZbUbBEITODNG7CZ0VWxBVYRPy/EkhlaGhoAbM4HvrVpFh34ezlfK0RiWKLi4ycYAq3B78HO61UlwcZ8npMxPUkTVd4IYDPBQuGPuMEAHEYULHNn6lmKmrp2xKaBqsE2jPJH0VQqidYsH0R0osWZbNhyyxD044Yy5q+QIvLS9v1uBobyxK4hb1jkOKcWxPZX+rOPaYT8NardW3LykDPtO9IxJb/r47NtREgamuq02RzRk= 12 | 13 | before_install: 14 | - curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - 15 | - sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu 16 | $(lsb_release -cs) stable" 17 | - sudo apt-get update 18 | - sudo apt-get -y install docker-ce 19 | - docker --version 20 | 21 | install: 22 | - make install_ci 23 | 24 | script: 25 | - make test_ci 26 | - travis_retry goveralls -coverprofile=cover.out -service=travis-ci || true 27 | 28 | after_success: 29 | - bash ./travis/build-docker-images.sh 30 | 31 | notifications: 32 | slack: 33 | rooms: 34 | secure: Qkh1Ucluku5Q3mimju0VtMLaIr2ugzIYLf/wlyqIO60IZd1zYMnegl0FY7PwrbOAJfTxTADbaP5egPzDbKt71EAQnuu1s5vHzxac1kdvMtEFpUyJVxfJ/EqumUDdVOgb0QGKRbu252Y2HzPDT0de0xFvVuZR15mo1ntTI5D1UyCIzXA2ypO8RN4xS4pEppTMWx19QoDi8L8JuOKA5gpybaKk+aIv81eSLQMEheTln9pFza02ZoOY99b0Gv06CcUAyuXTXjPONM+M8HnlyeNxcdghBt6ovAl59YfpvLruDq30+zYTkUTHeaMfKoT1gyGuukZsU3sv1SeR4NCJ3l11H5wezREp4W56XJpZjert0x2nABNQui/JfRfm0Ofs7LJ/HDYXkIcAd0lyPMjjySq+27LMxpaUTO6EHYDoDbZq6egmjx02zJYhSil/14Y6b2PS1zfa3ZEe0qSHP5fIArfll5NP5bdLWZgzObBaqhiLPQXOoEg4BKkwOGfa0/MlqlxjqV7ObGwD8g8Tj2UBKmzX0j7iBOwo9McLaz2pkCJYLFbdZ6mlyQxbAQbCGr7fVwUMlAUNvZttQdZ6kisj8WDw1UqI3XlYMy0ca3IRclk7S28AtkqJaqv5dooqFCeHMz3CubQNb8TwHqDo5bb5QLX+u6ly1eNY2zokwokic5WOl6k= 35 | -------------------------------------------------------------------------------- /steps/mockplugins_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | // package 22 | package steps 23 | 24 | import ( 25 | "context" 26 | "errors" 27 | "net/http" 28 | 29 | "github.com/northwesternmutual/kanali/metrics" 30 | "github.com/northwesternmutual/kanali/spec" 31 | "github.com/opentracing/opentracing-go" 32 | ) 33 | 34 | type fakePanicPlugin struct{} 35 | 36 | func (plugin fakePanicPlugin) OnRequest(ctx context.Context, m *metrics.Metrics, p spec.APIProxy, r *http.Request, span opentracing.Span) error { 37 | panic("intentional") 38 | } 39 | 40 | func (plugin fakePanicPlugin) OnResponse(ctx context.Context, m *metrics.Metrics, p spec.APIProxy, r *http.Request, resp *http.Response, span opentracing.Span) error { 41 | panic("intentional") 42 | } 43 | 44 | type fakeSuccessPlugin struct{} 45 | 46 | func (plugin fakeSuccessPlugin) OnRequest(ctx context.Context, m *metrics.Metrics, p spec.APIProxy, r *http.Request, span opentracing.Span) error { 47 | return nil 48 | } 49 | 50 | func (plugin fakeSuccessPlugin) OnResponse(ctx context.Context, m *metrics.Metrics, p spec.APIProxy, r *http.Request, resp *http.Response, span opentracing.Span) error { 51 | return nil 52 | } 53 | 54 | type fakeErrorPlugin struct{} 55 | 56 | func (plugin fakeErrorPlugin) OnRequest(ctx context.Context, m *metrics.Metrics, p spec.APIProxy, r *http.Request, span opentracing.Span) error { 57 | return errors.New("error") 58 | } 59 | 60 | func (plugin fakeErrorPlugin) OnResponse(ctx context.Context, m *metrics.Metrics, p spec.APIProxy, r *http.Request, resp *http.Response, span opentracing.Span) error { 61 | return errors.New("error") 62 | } 63 | -------------------------------------------------------------------------------- /helm/charts/influxdb/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Deployment 3 | metadata: 4 | name: {{ template "fullname" . }} 5 | labels: 6 | app: {{ template "fullname" . }} 7 | chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" 8 | release: "{{ .Release.Name }}" 9 | heritage: "{{ .Release.Service }}" 10 | spec: 11 | replicas: 1 12 | template: 13 | metadata: 14 | labels: 15 | app: {{ template "fullname" . }} 16 | spec: 17 | containers: 18 | - name: {{ template "fullname" . }} 19 | image: "{{ .Values.image.repo }}:{{ .Values.image.tag }}" 20 | imagePullPolicy: {{ .Values.image.pullPolicy | quote }} 21 | resources: 22 | {{ toYaml .Values.resources | indent 10 }} 23 | ports: 24 | - name: api 25 | containerPort: {{ .Values.config.http.bind_address }} 26 | {{- if .Values.config.admin.enabled -}} 27 | - name: admin 28 | containerPort: {{ .Values.config.admin.bind_address }} 29 | {{- end }} 30 | {{- if .Values.config.graphite.enabled -}} 31 | - name: graphite 32 | containerPort: {{ .Values.config.graphite.bind_address }} 33 | {{- end }} 34 | {{- if .Values.config.collectd.enabled -}} 35 | - name: collectd 36 | containerPort: {{ .Values.config.collectd.bind_address }} 37 | {{- end }} 38 | {{- if .Values.config.udp.enabled -}} 39 | - name: udp 40 | containerPort: {{ .Values.config.udp.bind_address }} 41 | {{- end }} 42 | {{- if .Values.config.opentsdb.enabled -}} 43 | - name: opentsdb 44 | containerPort: {{ .Values.config.opentsdb.bind_address }} 45 | {{- end }} 46 | livenessProbe: 47 | httpGet: 48 | path: /ping 49 | port: api 50 | initialDelaySeconds: 30 51 | timeoutSeconds: 5 52 | readinessProbe: 53 | httpGet: 54 | path: /ping 55 | port: api 56 | initialDelaySeconds: 5 57 | timeoutSeconds: 1 58 | volumeMounts: 59 | - name: data 60 | mountPath: {{ .Values.config.storage_directory }} 61 | - name: config 62 | mountPath: /etc/influxdb 63 | volumes: 64 | - name: data 65 | {{- if .Values.persistence.enabled }} 66 | {{- if not (empty .Values.persistence.name) }} 67 | persistentVolumeClaim: 68 | claimName: {{ .Values.persistence.name }} 69 | {{- else }} 70 | persistentVolumeClaim: 71 | claimName: {{ template "fullname" . }} 72 | {{- end }} 73 | {{- else }} 74 | emptyDir: {} 75 | {{- end }} 76 | - name: config 77 | configMap: 78 | name: {{ template "fullname" . }} 79 | -------------------------------------------------------------------------------- /flow/flow_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package flow 22 | 23 | import ( 24 | "context" 25 | "errors" 26 | "net/http" 27 | "testing" 28 | 29 | "github.com/northwesternmutual/kanali/metrics" 30 | "github.com/northwesternmutual/kanali/spec" 31 | opentracing "github.com/opentracing/opentracing-go" 32 | "github.com/stretchr/testify/assert" 33 | ) 34 | 35 | type mockStep struct{} 36 | 37 | func (s mockStep) GetName() string { 38 | return "mock step" 39 | } 40 | 41 | func (s mockStep) Do(ctx context.Context, proxy *spec.APIProxy, m *metrics.Metrics, w http.ResponseWriter, r *http.Request, resp *http.Response, trace opentracing.Span) error { 42 | return nil 43 | } 44 | 45 | type mockErrorStep struct{} 46 | 47 | func (s mockErrorStep) GetName() string { 48 | return "mock error step" 49 | } 50 | 51 | func (s mockErrorStep) Do(ctx context.Context, proxy *spec.APIProxy, m *metrics.Metrics, w http.ResponseWriter, r *http.Request, resp *http.Response, trace opentracing.Span) error { 52 | return errors.New("forced error") 53 | } 54 | 55 | func TestAdd(t *testing.T) { 56 | f := &Flow{} 57 | f.Add(mockStep{}, mockStep{}) 58 | f.Add(mockStep{}) 59 | assert.Equal(t, len(*f), 3) 60 | for _, step := range *f { 61 | assert.Equal(t, step.GetName(), "mock step") 62 | assert.Nil(t, step.Do(context.Background(), nil, nil, nil, nil, nil, opentracing.StartSpan("test span"))) 63 | } 64 | } 65 | 66 | func TestPlay(t *testing.T) { 67 | f := &Flow{} 68 | f.Add(mockStep{}) 69 | assert.Nil(t, f.Play(context.Background(), nil, nil, nil, nil, nil, opentracing.StartSpan("test span"))) 70 | f.Add(mockErrorStep{}) 71 | assert.Error(t, f.Play(context.Background(), nil, nil, nil, nil, nil, opentracing.StartSpan("test span"))) 72 | } 73 | -------------------------------------------------------------------------------- /docs/apikeybinding.md: -------------------------------------------------------------------------------- 1 | # ApiKeyBinding 2 | 3 | | Field | Required | Description | 4 | | ----- | -------- | ----------- | 5 | | apiVersion
*string* | `true` | APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. | 6 | | kind
*string* | `true` | Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. | 7 | | metadata
*[ObjectMeta](https://kubernetes.io/docs/api-reference/v1.6/#objectmeta-v1-meta)* | `true` | Standard object's metadata. | 8 | | spec
*[ApiKeyBindingSpec](#apikeybindingspec)* | `true` | Defines an ApiKeyBinding | 9 | 10 | # ApiKeyBindingSpec 11 | 12 | | Field | Required | Description | 13 | | ----- | -------- | ----------- | 14 | | proxy
*string* | `true` | The name of the `ApiProxy` that this binding applies to. | 15 | | keys
*[Key](#key) array* | `true` | List of `ApiKey`s that belong to this binding. | 16 | 17 | # Key 18 | 19 | | Field | Required | Description | 20 | | ----- | -------- | ----------- | 21 | | name
*string* | `true` | Name of the `ApiKey` | 22 | | quota
*integer* | `false` | Number of requests that this `ApiKey` is granted. | 23 | | rate
*[Rate](#rate)* | `false` | The rate limiting policy for this `ApiKey` | 24 | | defaultRule
*[Rule](#rule)* | `false` | The default rule this `ApiKey` has for fine grained access. Default is `false` | 25 | | subpaths
*[Path](#path) array* | `false` | Defines find grained authorization based on subpath. If not defined, falls back to the `defaultRule` for any subpath | 26 | 27 | # Rate 28 | 29 | | Field | Required | Description | 30 | | ----- | -------- | ----------- | 31 | | amount
*integer* | `true` | Scalar value for the defined `unit` | 32 | | unit
*string* | `true` | Unit of rate limit. Valid values are `second`, `minute`, `hour` | 33 | 34 | # Rule 35 | 36 | | Field | Required | Description | 37 | | ----- | -------- | ----------- | 38 | | global
*boolean* | If undefined, *granular* must be defined. | If true, access to all HTTP methods is granted. | 39 | | granular
*[Granular](#granular)* | If undefined, *global* must be defined. | Defines granular rules | 40 | 41 | # Granular 42 | 43 | | Field | Required | Description | 44 | | ----- | -------- | ----------- | 45 | | verbs
*string array* | `true` | List of http verbs that this `ApiKeyBinding` has access to. | 46 | 47 | # Path 48 | 49 | | Field | Required | Description | 50 | | ----- | -------- | ----------- | 51 | | path
*string* | `true` | The subpath | 52 | | rule
*[Rule](#rule)* | `true` | The rules defined for this subpath | -------------------------------------------------------------------------------- /controller/controller.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package controller 22 | 23 | import ( 24 | "errors" 25 | 26 | "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" 27 | "k8s.io/kubernetes/pkg/client/restclient" 28 | "k8s.io/kubernetes/pkg/kubectl/cmd/util" 29 | ) 30 | 31 | // Controller is an exported struct 32 | // that holds all of the information 33 | // we will need throughout our program 34 | // to talk to the kubernetes api server. 35 | // this includes a rest client and well 36 | // a clientset that gives us access to the libs 37 | type Controller struct { 38 | RestClient *restclient.RESTClient 39 | ClientSet internalclientset.Interface 40 | MasterHost string 41 | } 42 | 43 | // New creates a new kubernetes controller 44 | // the controller is secure and uses the 45 | // cluster's kubeconfig file to construct 46 | // permissions 47 | func New() (*Controller, error) { 48 | 49 | f := util.NewFactory(nil) 50 | 51 | // ClientSet gives you back an internal, generated clientset 52 | clientSet, err := f.ClientSet() 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | // Returns a RESTClient for accessing Kubernetes resources or an error. 58 | restClient, err := f.RESTClient() 59 | if err != nil { 60 | return nil, err 61 | } 62 | 63 | controller := &Controller{ 64 | RestClient: restClient, 65 | ClientSet: clientSet, 66 | } 67 | 68 | // Returns a client.Config for accessing the Kubernetes server. 69 | k8sConfig, err := f.ClientConfig() 70 | if err != nil { 71 | return nil, err 72 | } else if k8sConfig == nil { 73 | return nil, errors.New("received nil k8sConfig, please check if k8s cluster is available") 74 | } else { 75 | controller.MasterHost = k8sConfig.Host 76 | } 77 | 78 | // return our newly created controller 79 | return controller, nil 80 | } 81 | -------------------------------------------------------------------------------- /handlers/incoming.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package handlers 22 | 23 | import ( 24 | "context" 25 | "net/http" 26 | 27 | "github.com/northwesternmutual/kanali/config" 28 | "github.com/northwesternmutual/kanali/flow" 29 | "github.com/northwesternmutual/kanali/metrics" 30 | "github.com/northwesternmutual/kanali/spec" 31 | "github.com/northwesternmutual/kanali/steps" 32 | "github.com/northwesternmutual/kanali/utils" 33 | "github.com/opentracing/opentracing-go" 34 | "github.com/spf13/viper" 35 | ) 36 | 37 | // IncomingRequest orchestrates the logic that occurs for every incoming request 38 | func IncomingRequest(ctx context.Context, proxy *spec.APIProxy, m *metrics.Metrics, w http.ResponseWriter, r *http.Request, trace opentracing.Span) error { 39 | 40 | // this is a handler to our future proxy pass response 41 | // maybe there's a better way to do this... seems misplaced 42 | futureResponse := &http.Response{} 43 | 44 | f := &flow.Flow{} 45 | 46 | f.Add( 47 | steps.ValidateProxyStep{}, 48 | steps.PluginsOnRequestStep{}, 49 | ) 50 | if viper.GetBool(config.FlagProxyEnableMockResponses.GetLong()) && mockIsDefined(utils.ComputeURLPath(r.URL)) { 51 | f.Add(steps.MockServiceStep{}) 52 | } else { 53 | f.Add(steps.ProxyPassStep{}) 54 | } 55 | 56 | f.Add( 57 | steps.PluginsOnResponseStep{}, 58 | steps.WriteResponseStep{}, 59 | ) 60 | 61 | err := f.Play(ctx, proxy, m, w, r, futureResponse, trace) 62 | 63 | return err 64 | 65 | } 66 | 67 | func mockIsDefined(path string) bool { 68 | 69 | untypedProxy, err := spec.ProxyStore.Get(path) 70 | if err != nil || untypedProxy == nil { 71 | return false 72 | } 73 | 74 | proxy, _ := untypedProxy.(spec.APIProxy) 75 | 76 | if proxy.Spec.Mock != nil { 77 | return proxy.Spec.Mock.ConfigMapName != "" 78 | } 79 | 80 | return false 81 | 82 | } 83 | -------------------------------------------------------------------------------- /steps/pluginsonrequest.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package steps 22 | 23 | import ( 24 | "context" 25 | "errors" 26 | "fmt" 27 | "net/http" 28 | 29 | "github.com/Sirupsen/logrus" 30 | "github.com/northwesternmutual/kanali/metrics" 31 | "github.com/northwesternmutual/kanali/plugins" 32 | "github.com/northwesternmutual/kanali/spec" 33 | "github.com/opentracing/opentracing-go" 34 | ) 35 | 36 | // PluginsOnRequestStep is factory that defines a step responsible for 37 | // executing the on request lifecycle hook for all the defined plugins 38 | type PluginsOnRequestStep struct{} 39 | 40 | // GetName retruns the name of the PluginsOnRequestStep step 41 | func (step PluginsOnRequestStep) GetName() string { 42 | return "Plugin OnRequest" 43 | } 44 | 45 | // Do executes the logic of the PluginsOnRequestStep step 46 | func (step PluginsOnRequestStep) Do(ctx context.Context, proxy *spec.APIProxy, m *metrics.Metrics, w http.ResponseWriter, r *http.Request, resp *http.Response, trace opentracing.Span) error { 47 | 48 | for _, plugin := range proxy.Spec.Plugins { 49 | p, err := plugins.GetPlugin(plugin) 50 | if err != nil { 51 | return err 52 | } 53 | if err := doOnRequest(ctx, m, plugin.Name, *proxy, r, trace, *p); err != nil { 54 | return err 55 | } 56 | } 57 | return nil 58 | } 59 | 60 | func doOnRequest(ctx context.Context, m *metrics.Metrics, name string, proxy spec.APIProxy, req *http.Request, span opentracing.Span, p plugins.Plugin) (e error) { 61 | defer func() { 62 | if r := recover(); r != nil { 63 | logrus.Errorf("OnRequest paniced: %v", r) 64 | e = errors.New("OnRequest paniced") 65 | } 66 | }() 67 | 68 | sp := opentracing.StartSpan(fmt.Sprintf("PLUGIN: ON_REQUEST: %s", name), opentracing.ChildOf(span.Context())) 69 | defer sp.Finish() 70 | 71 | return p.OnRequest(ctx, m, proxy, req, sp) 72 | } 73 | -------------------------------------------------------------------------------- /docs/apikey.md: -------------------------------------------------------------------------------- 1 | The `ApiKey` spec represents an rsa encrypted API key. 2 | 3 | *NOTE:* while you can generate this spec yourself, it is recommended that you generate an `ApiKey` spec using the [`kanalictl`](https://github.com/northwesternmutual/kanalictl) tool. See example below for usage details. 4 | 5 | Here are some useful commands to generate an rsa key pair: 6 | 7 | ```sh 8 | # generate private key - using 4096 bit long modulus 9 | $ openssl genrsa -out private.pem 4096 10 | # calculate public key 11 | $ openssl rsa -in private.pem -pubout -out public.pem 12 | # use the kanalictl tool to generate ApiKey spec 13 | $ kanalictl generate apikey -k public.pem -o apikey.yml -n my-test-api-key 14 | Here is your api key (you will only see this once): ksAR0xqSKjh9UGSBvhP2IxDDC9Ckou0S 15 | Corresponding Kubernetes config written to apikey.yml 16 | $ cat apikey.yml 17 | apiVersion: kanali.io/v1 18 | kind: ApiKey 19 | metadata: 20 | creationTimestamp: null 21 | name: my-test-api-key 22 | namespace: default 23 | spec: 24 | data: 2778ac7127f97212dbc27cb9a8b7fb5a51f49bcefc8a23eb26fd9e3b8673faa9dc3e98597dcc62d1dcc01a5c054b28268c30b206c5e5296e058fded2458905382b15ba30eef7596ae46248b0958442e03e38ec1097a96a9fe6420fb671a06ed7782deadd0bb35f9ef1debb5693a34d20108647364834939a12f8a9959864c52f3df4d0cebbae60a27facf0b75bbae5e91077c2e013179810a7cdca77bca6c8d1a48acc3e6b3af72119f4886cc9c483063b5e42f660095d4e3f69c35a6511c9ecbe59c5893eb176c208c6d00c0eda2315416e856fd264ab886ee18527f6cc0c5311953a79ad2c1695780d322bb5d6cacac61d808bfbca531614084d7caac6a11f310127adb319ba53fcd91835d0bcf318f85242563ec555e2d3c0cefbff31585ec6a631f893a5dd57725002b4e9ac5d68ac4ba849d9f3314968ea63d1f8520060cf800fcded379a1353b6f018f431e5206018b9a5c81d52c13069a7621ca6b02de302ad830279f9963c957ef73a6e170f17883eefac405bae03796fcbb02e07b7ff1b691bd320a8a72a35203898664206ac386f730787160f94739459d11ab3b0019648414c6a4b9bcc7121a17a42aa8bd2e3e7a64234f9e78503833dd208c8a4a948b51491a0a4fec15f17a213c0ae4a5d87d002b8047f9aa235c9f32b052301e499d64d7650a1cb3a201f7342028d6b5f50e0f4ab7d3d3b3c4bbb410aa81e04 25 | ``` 26 | 27 | # ApiKey 28 | 29 | | Field | Required | Description | 30 | | ----- | -------- | ----------- | 31 | | apiVersion
*string* | `true` | APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. | 32 | | kind
*string* | `true` | Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. | 33 | | metadata
*[ObjectMeta](https://kubernetes.io/docs/api-reference/v1.6/#objectmeta-v1-meta)* | `true` | Standard object's metadata. | 34 | | spec
*[ApiKeySpec](#apikeyspec)* | `true` | Defines an ApiKey | 35 | 36 | # ApiKeySpec 37 | 38 | | Field | Required | Description | 39 | | ----- | -------- | ----------- | 40 | | data
*string* | `true` | encrypted api key | -------------------------------------------------------------------------------- /logo/name_black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 11 | 16 | 20 | 25 | 27 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /config/flag.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package config 22 | 23 | import ( 24 | "time" 25 | 26 | "github.com/spf13/cobra" 27 | "github.com/spf13/viper" 28 | ) 29 | 30 | // Flag is a simplified representation of a configuration item. 31 | type Flag struct { 32 | Long string 33 | Short string 34 | Value interface{} 35 | Usage string 36 | } 37 | 38 | type flags []Flag 39 | 40 | // Flags is the aggregate set of flags that Kanali has available to configure. 41 | var Flags = &flags{} 42 | 43 | func (f *flags) Add(a ...Flag) { 44 | for _, curr := range a { 45 | *f = append(*f, curr) 46 | } 47 | } 48 | 49 | // GetLong returns the name of the flag 50 | func (f Flag) GetLong() string { 51 | return f.Long 52 | } 53 | 54 | // GetShort returns the Short name of the flag 55 | func (f Flag) GetShort() string { 56 | return f.Short 57 | } 58 | 59 | // GetUsage returns the flag's description. 60 | func (f Flag) GetUsage() string { 61 | return f.Usage 62 | } 63 | 64 | func (f flags) AddAll(cmd *cobra.Command) error { 65 | for _, currFlag := range f { 66 | switch v := currFlag.Value.(type) { 67 | case int: 68 | cmd.Flags().IntP(currFlag.Long, currFlag.Short, v, currFlag.Usage) 69 | case bool: 70 | cmd.Flags().BoolP(currFlag.Long, currFlag.Short, v, currFlag.Usage) 71 | case string: 72 | cmd.Flags().StringP(currFlag.Long, currFlag.Short, v, currFlag.Usage) 73 | case time.Duration: 74 | cmd.Flags().DurationP(currFlag.Long, currFlag.Short, v, currFlag.Usage) 75 | case []string: 76 | cmd.Flags().StringSliceP(currFlag.Long, currFlag.Short, v, currFlag.Usage) 77 | default: 78 | viper.SetDefault(currFlag.Long, currFlag.Value) 79 | continue 80 | } 81 | viper.SetDefault(currFlag.Long, currFlag.Value) 82 | if err := viper.BindPFlag(currFlag.Long, cmd.Flags().Lookup(currFlag.Long)); err != nil { 83 | return err 84 | } 85 | } 86 | 87 | return nil 88 | 89 | } 90 | -------------------------------------------------------------------------------- /steps/pluginsonresponse.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package steps 22 | 23 | import ( 24 | "context" 25 | "errors" 26 | "fmt" 27 | "net/http" 28 | 29 | "github.com/Sirupsen/logrus" 30 | "github.com/northwesternmutual/kanali/metrics" 31 | "github.com/northwesternmutual/kanali/plugins" 32 | "github.com/northwesternmutual/kanali/spec" 33 | "github.com/opentracing/opentracing-go" 34 | ) 35 | 36 | // PluginsOnResponseStep is factory that defines a step responsible for 37 | // executing the on response lifecycle hook for all the defined plugins 38 | type PluginsOnResponseStep struct{} 39 | 40 | // GetName retruns the name of the PluginsOnResponseStep step 41 | func (step PluginsOnResponseStep) GetName() string { 42 | return "Plugin OnResponse" 43 | } 44 | 45 | // Do executes the logic of the PluginsOnResponseStep step 46 | func (step PluginsOnResponseStep) Do(ctx context.Context, proxy *spec.APIProxy, m *metrics.Metrics, w http.ResponseWriter, r *http.Request, resp *http.Response, trace opentracing.Span) error { 47 | 48 | for _, plugin := range proxy.Spec.Plugins { 49 | p, err := plugins.GetPlugin(plugin) 50 | if err != nil { 51 | return err 52 | } 53 | if err := doOnResponse(ctx, m, plugin.Name, *proxy, r, resp, trace, *p); err != nil { 54 | return err 55 | } 56 | } 57 | 58 | return nil 59 | } 60 | 61 | func doOnResponse(ctx context.Context, m *metrics.Metrics, name string, proxy spec.APIProxy, req *http.Request, resp *http.Response, span opentracing.Span, p plugins.Plugin) (e error) { 62 | defer func() { 63 | if r := recover(); r != nil { 64 | logrus.Errorf("OnResponse paniced: %v", r) 65 | e = errors.New("OnResponse paniced") 66 | } 67 | }() 68 | 69 | sp := opentracing.StartSpan(fmt.Sprintf("PLUGIN: ON_RESPONSE: %s", name), opentracing.ChildOf(span.Context())) 70 | defer sp.Finish() 71 | 72 | return p.OnResponse(ctx, m, proxy, req, resp, sp) 73 | } 74 | -------------------------------------------------------------------------------- /config/flag_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package config 22 | 23 | import ( 24 | "testing" 25 | "time" 26 | 27 | "github.com/spf13/cobra" 28 | "github.com/spf13/viper" 29 | "github.com/stretchr/testify/assert" 30 | ) 31 | 32 | func TestGetters(t *testing.T) { 33 | f := Flag{ 34 | Long: "test", 35 | Short: "t", 36 | Value: "hello world", 37 | Usage: "for testing", 38 | } 39 | assert.Equal(t, f.GetLong(), "test") 40 | assert.Equal(t, f.GetShort(), "t") 41 | assert.Equal(t, f.GetUsage(), "for testing") 42 | } 43 | 44 | func TestAddAll(t *testing.T) { 45 | cmd := &cobra.Command{} 46 | d, _ := time.ParseDuration("0h0m0s") 47 | f := flags{ 48 | Flag{ 49 | Long: "int", 50 | Short: "i", 51 | Value: 1, 52 | Usage: "for testing", 53 | }, 54 | Flag{ 55 | Long: "bool", 56 | Short: "b", 57 | Value: true, 58 | Usage: "for testing", 59 | }, 60 | Flag{ 61 | Long: "string", 62 | Short: "s", 63 | Value: "hello world", 64 | Usage: "for testing", 65 | }, 66 | Flag{ 67 | Long: "duration", 68 | Short: "d", 69 | Value: d, 70 | Usage: "for testing", 71 | }, 72 | Flag{ 73 | Long: "slice", 74 | Short: "p", 75 | Value: []string{"foo"}, 76 | Usage: "for testing", 77 | }, 78 | } 79 | assert.Nil(t, f.AddAll(cmd)) 80 | cobraValOne, _ := cmd.Flags().GetInt("int") 81 | cobraValTwo, _ := cmd.Flags().GetBool("bool") 82 | cobraValThree, _ := cmd.Flags().GetString("string") 83 | cobraValFour, _ := cmd.Flags().GetDuration("duration") 84 | cobraValFive, _ := cmd.Flags().GetStringSlice("slice") 85 | assert.Equal(t, viper.GetString("string"), "hello world") 86 | assert.Equal(t, viper.GetInt("int"), 1) 87 | assert.True(t, viper.GetBool("bool")) 88 | assert.Equal(t, viper.GetDuration("duration"), d) 89 | assert.Equal(t, cobraValOne, 1) 90 | assert.True(t, cobraValTwo) 91 | assert.Equal(t, cobraValThree, "hello world") 92 | assert.Equal(t, cobraValFour, d) 93 | assert.Equal(t, cobraValFive, []string{"foo"}) 94 | } 95 | -------------------------------------------------------------------------------- /logo/name_white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 9 | 10 | 14 | 19 | 23 | 28 | 30 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /tracer/tags.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package tracer 22 | 23 | const ( 24 | // Error is the opentracing tag name that represents an error 25 | Error = "error" 26 | 27 | // KanaliProxyName is the opentracing tag name that represents an APIProxy name 28 | KanaliProxyName = "kanali.proxy.name" 29 | // KanaliProxyNamespace is the opentracing tag name that represents an APIProxy namespace 30 | KanaliProxyNamespace = "kanali.proxy.namespace" 31 | 32 | // HTTPRequest is the opentracing tag name that represents the existence on an HTTP request 33 | HTTPRequest = "http.request" 34 | 35 | // HTTPRequestMethod is the opentracing tag name that represents an HTTP request method 36 | HTTPRequestMethod = "http.request.method" 37 | // HTTPRequestBody is the opentracing tag name that represents an HTTP request body 38 | HTTPRequestBody = "http.request.body" 39 | // HTTPRequestHeaders is the opentracing tag name that represents an HTTP request headers 40 | HTTPRequestHeaders = "http.request.headers" 41 | 42 | // HTTPRequestURLPath is the opentracing tag name that represents an HTTP URL path 43 | HTTPRequestURLPath = "http.request.url.path" 44 | // HTTPRequestURLHost is the opentracing tag name that represents an HTTP URL host 45 | HTTPRequestURLHost = "http.request.url.host" 46 | // HTTPRequestURLQuery is the opentracing tag name that represents an HTTP URL query 47 | HTTPRequestURLQuery = "http.request.url.query" 48 | 49 | // HTTPResponse is the opentracing tag name that represents the existence on an HTTP response 50 | HTTPResponse = "http.response" 51 | 52 | // HTTPResponseBody is the opentracing tag name that represents an HTTP response body 53 | HTTPResponseBody = "http.response.body" 54 | // HTTPResponseHeaders is the opentracing tag name that represents an HTTP response headers 55 | HTTPResponseHeaders = "http.response.headers" 56 | 57 | // HTTPResponseStatusCode is the opentracing tag name that represents an HTTP response status code 58 | HTTPResponseStatusCode = "http.response.status.code" 59 | ) 60 | -------------------------------------------------------------------------------- /steps/validateproxy.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package steps 22 | 23 | import ( 24 | "context" 25 | "errors" 26 | "net/http" 27 | 28 | "github.com/Sirupsen/logrus" 29 | "github.com/northwesternmutual/kanali/metrics" 30 | "github.com/northwesternmutual/kanali/spec" 31 | "github.com/northwesternmutual/kanali/tracer" 32 | "github.com/northwesternmutual/kanali/utils" 33 | "github.com/opentracing/opentracing-go" 34 | ) 35 | 36 | // ValidateProxyStep is factory that defines a step responsible for 37 | // validating that an incoming request matches a proxy that Kanali 38 | // has stored in memory 39 | type ValidateProxyStep struct{} 40 | 41 | // GetName retruns the name of the ValidateProxyStep step 42 | func (step ValidateProxyStep) GetName() string { 43 | return "Validate Proxy" 44 | } 45 | 46 | // Do executes the logic of the ValidateProxyStep step 47 | func (step ValidateProxyStep) Do(ctx context.Context, proxy *spec.APIProxy, m *metrics.Metrics, w http.ResponseWriter, r *http.Request, resp *http.Response, trace opentracing.Span) error { 48 | 49 | untypedProxy, err := spec.ProxyStore.Get(utils.ComputeURLPath(r.URL)) 50 | if err != nil || untypedProxy == nil { 51 | if err != nil { 52 | logrus.Error(err.Error()) 53 | } 54 | 55 | trace.SetTag(tracer.KanaliProxyName, "unknown") 56 | trace.SetTag(tracer.KanaliProxyNamespace, "unknown") 57 | 58 | m.Add( 59 | metrics.Metric{Name: "proxy_name", Value: "unknown", Index: true}, 60 | metrics.Metric{Name: "proxy_namespace", Value: "unknown", Index: true}, 61 | ) 62 | 63 | return utils.StatusError{Code: http.StatusNotFound, Err: errors.New("proxy not found")} 64 | } 65 | 66 | typedProxy, _ := untypedProxy.(spec.APIProxy) 67 | 68 | *proxy = typedProxy 69 | 70 | trace.SetTag(tracer.KanaliProxyName, proxy.ObjectMeta.Name) 71 | trace.SetTag(tracer.KanaliProxyNamespace, proxy.ObjectMeta.Namespace) 72 | 73 | m.Add( 74 | metrics.Metric{Name: "proxy_name", Value: proxy.ObjectMeta.Name, Index: true}, 75 | metrics.Metric{Name: "proxy_namespace", Value: proxy.ObjectMeta.Namespace, Index: true}, 76 | ) 77 | 78 | return nil 79 | 80 | } 81 | -------------------------------------------------------------------------------- /helm/charts/jaeger/templates/kubernetes.yml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2017 The Jaeger Authors 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | # in compliance with the License. You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License 10 | # is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | # or implied. See the License for the specific language governing permissions and limitations under 12 | # the License. 13 | # 14 | 15 | # This template uses Jaeger with in-memory storage with limited functionality 16 | # Do not use this in production environment! 17 | # 18 | # kubectl create -f jaeger-all-in-one-template.yml 19 | # kubectl delete pod,service,deployment -l jaeger-infra 20 | apiVersion: v1 21 | kind: List 22 | items: 23 | - apiVersion: extensions/v1beta1 24 | kind: Deployment 25 | metadata: 26 | name: jaeger-all-in-one 27 | namespace: kube-system 28 | spec: 29 | replicas: 1 30 | selector: 31 | name: jaeger-all-in-one 32 | strategy: 33 | type: Recreate 34 | selector: 35 | matchLabels: 36 | name: jaeger-all-in-one 37 | template: 38 | metadata: 39 | labels: 40 | name: jaeger-all-in-one 41 | jaeger-infra: all-in-one 42 | spec: 43 | containers: 44 | - name: jaeger-all-in-one 45 | image: jaegertracing/all-in-one 46 | ports: 47 | - containerPort: 5775 48 | protocol: UDP 49 | - containerPort: 6831 50 | protocol: UDP 51 | - containerPort: 6832 52 | protocol: UDP 53 | - containerPort: 16686 54 | protocol: TCP 55 | resources: {} 56 | imagePullPolicy: Always 57 | readinessProbe: 58 | httpGet: 59 | path: "/" 60 | port: 16686 61 | initialDelaySeconds: 5 62 | securityContext: {} 63 | - apiVersion: v1 64 | kind: Service 65 | metadata: 66 | name: jaeger-all-in-one 67 | namespace: kube-system 68 | labels: 69 | jaeger-infra: all-in-one 70 | spec: 71 | ports: 72 | - name: query-http 73 | port: 80 74 | protocol: TCP 75 | targetPort: 16686 76 | selector: 77 | name: jaeger-all-in-one 78 | type: NodePort 79 | - apiVersion: v1 80 | kind: Service 81 | metadata: 82 | name: jaeger-agent 83 | namespace: kube-system 84 | labels: 85 | jaeger-infra: all-in-one 86 | spec: 87 | ports: 88 | - name: agent-zipkin-thrift 89 | port: 5775 90 | protocol: UDP 91 | targetPort: 5775 92 | - name: agent-compact 93 | port: 6831 94 | protocol: UDP 95 | targetPort: 6831 96 | - name: agent-binary 97 | port: 6832 98 | protocol: UDP 99 | targetPort: 6832 100 | selector: 101 | name: jaeger-all-in-one 102 | type: ClusterIP -------------------------------------------------------------------------------- /helm/charts/influxdb/README.md: -------------------------------------------------------------------------------- 1 | # InfluxDB 2 | 3 | ## An Open-Source Time Series Database 4 | 5 | [InfluxDB](https://github.com/influxdata/influxdb) is an open source time series database built by the folks over at [InfluxData](https://influxdata.com) with no external dependencies. It's useful for recording metrics, events, and performing analytics. 6 | 7 | ## QuickStart 8 | 9 | ```bash 10 | $ helm install stable/influxdb --name foo --namespace bar 11 | ``` 12 | 13 | ## Introduction 14 | 15 | This chart bootstraps an InfluxDB deployment and service on a Kubernetes cluster using the Helm Package manager. 16 | 17 | ## Prerequisites 18 | 19 | - Kubernetes 1.4+ 20 | - PV provisioner support in the underlying infrastructure (optional) 21 | 22 | ## Installing the Chart 23 | 24 | To install the chart with the release name `my-release`: 25 | 26 | ```bash 27 | $ helm install --name my-release stable/influxdb 28 | ``` 29 | 30 | The command deploys InfluxDB on the Kubernetes cluster in the default configuration. The [configuration](#configuration) section lists the parameters that can be configured during installation. 31 | 32 | > **Tip**: List all releases using `helm list` 33 | 34 | ## Uninstalling the Chart 35 | 36 | To uninstall/delete the `my-release` deployment: 37 | 38 | ```bash 39 | $ helm delete my-release --purge 40 | ``` 41 | 42 | The command removes all the Kubernetes components associated with the chart and deletes the release. 43 | 44 | ## Configuration 45 | 46 | The default configuration values for this chart are listed in `values.yaml`. 47 | 48 | The [full image documentation](https://hub.docker.com/_/influxdb/) contains more information about running InfluxDB in docker. 49 | 50 | Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example, 51 | 52 | ```bash 53 | $ helm install --name my-release \ 54 | --set persistence.enabled=true,persistence.size=200Gi \ 55 | stable/influxdb 56 | ``` 57 | 58 | The above command enables persistence and changes the size of the requested data volume to 200GB. 59 | 60 | Alternatively, a YAML file that specifies the values for the parameters can be provided while installing the chart. For example, 61 | 62 | ```bash 63 | $ helm install --name my-release -f values.yaml stable/influxdb 64 | ``` 65 | 66 | > **Tip**: You can use the default [values.yaml](values.yaml) 67 | 68 | ## Persistence 69 | 70 | The [InfluxDB](https://hub.docker.com/_/influxdb/) image stores data in the `/var/lib/influxdb` directory in the container. 71 | 72 | The chart mounts a [Persistent Volume](kubernetes.io/docs/user-guide/persistent-volumes/) volume at this location. The volume is created using dynamic volume provisioning. 73 | 74 | ## Starting with authentication 75 | 76 | In `values.yaml` change `.Values.config.http.auth_enabled` to `true`. 77 | 78 | Influxdb requires also a user to be set in order for authentication to be enforced. See more details [here](https://docs.influxdata.com/influxdb/v1.2/query_language/authentication_and_authorization/#set-up-authentication). 79 | 80 | To handle this setup on startup, a job can be enabled in `values.yaml` by setting `.Values.setDefaultUser.enabled` to `true`. 81 | 82 | Make sure to uncomment or configure the job settings after enabling it. If a password is not set, a random password will be generated. 83 | -------------------------------------------------------------------------------- /plugins/plugin.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package plugins 22 | 23 | import ( 24 | "context" 25 | "fmt" 26 | "net/http" 27 | pluginPkg "plugin" 28 | 29 | "github.com/northwesternmutual/kanali/config" 30 | "github.com/northwesternmutual/kanali/metrics" 31 | "github.com/northwesternmutual/kanali/spec" 32 | "github.com/northwesternmutual/kanali/utils" 33 | "github.com/opentracing/opentracing-go" 34 | "github.com/spf13/viper" 35 | ) 36 | 37 | const pluginSymbolName = "Plugin" 38 | 39 | // Plugin is an interface that is used for every Plugin used by Kanali. 40 | // If external plugins are developed, they also must conform to this interface. 41 | type Plugin interface { 42 | OnRequest(ctx context.Context, m *metrics.Metrics, proxy spec.APIProxy, req *http.Request, span opentracing.Span) error 43 | OnResponse(ctx context.Context, m *metrics.Metrics, proxy spec.APIProxy, req *http.Request, resp *http.Response, span opentracing.Span) error 44 | } 45 | 46 | // GetPlugin will use the Go plugin package and extract 47 | // the plugin 48 | func GetPlugin(plugin spec.Plugin) (*Plugin, error) { 49 | path, err := utils.GetAbsPath(viper.GetString(config.FlagPluginsLocation.GetLong())) 50 | if err != nil { 51 | return nil, utils.StatusError{Code: http.StatusInternalServerError, Err: fmt.Errorf("file path %s could not be found", viper.GetString(config.FlagPluginsLocation.GetLong()))} 52 | } 53 | 54 | plug, err := pluginPkg.Open(fmt.Sprintf("%s/%s.so", 55 | path, 56 | plugin.GetFileName(), 57 | )) 58 | if err != nil { 59 | return nil, utils.StatusError{ 60 | Code: http.StatusInternalServerError, 61 | Err: fmt.Errorf("could not open plugin %s: %s", plugin.Name, err.Error()), 62 | } 63 | } 64 | 65 | symPlug, err := plug.Lookup(pluginSymbolName) 66 | if err != nil { 67 | return nil, utils.StatusError{ 68 | Code: http.StatusInternalServerError, 69 | Err: err, 70 | } 71 | } 72 | 73 | var p Plugin 74 | p, ok := symPlug.(Plugin) 75 | if !ok { 76 | return nil, utils.StatusError{ 77 | Code: http.StatusInternalServerError, 78 | Err: fmt.Errorf("plugin %s must implement the Plugin interface", plugin.Name), 79 | } 80 | } 81 | 82 | return &p, nil 83 | } 84 | -------------------------------------------------------------------------------- /config/analytics.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package config 22 | 23 | func init() { 24 | Flags.Add( 25 | FlagAnalyticsInfluxAddr, 26 | FlagAnalyticsInfluxDb, 27 | FlagAnalyticsInfluxUsername, 28 | FlagAnalyticsInfluxPassword, 29 | FlagAnalyticsInfluxBufferSize, 30 | FlagAnalyticsInfluxMeasurement, 31 | ) 32 | } 33 | 34 | var ( 35 | // FlagAnalyticsInfluxAddr specifies the Influxdb address. Address should be of the form 'http://host:port' or 'http://[ipv6-host%zone]:port' 36 | FlagAnalyticsInfluxAddr = Flag{ 37 | Long: "analytics.influx_addr", 38 | Short: "", 39 | Value: "http://monitoring-influxdb.kube-system.svc.cluster.local:8086", 40 | Usage: "InfluxDB address. Address should be of the form 'http://host:port' or 'http://[ipv6-host%zone]:port'.", 41 | } 42 | // FlagAnalyticsInfluxDb specifies the name of the InfluxDB database 43 | FlagAnalyticsInfluxDb = Flag{ 44 | Long: "analytics.influx_db", 45 | Short: "", 46 | Value: "k8s", 47 | Usage: "InfluxDB database name", 48 | } 49 | // FlagAnalyticsInfluxUsername specifies the InfluxDB username 50 | FlagAnalyticsInfluxUsername = Flag{ 51 | Long: "analytics.influx_username", 52 | Short: "", 53 | Value: "", 54 | Usage: "InfluxDB username", 55 | } 56 | // FlagAnalyticsInfluxPassword specifies the InfluxDB password 57 | FlagAnalyticsInfluxPassword = Flag{ 58 | Long: "analytics.influx_password", 59 | Short: "", 60 | Value: "", 61 | Usage: "InfluxDB password", 62 | } 63 | // FlagAnalyticsInfluxBufferSize specifies the InfluxDB buffer size. 64 | // Request metrics will be written to InfluxDB when this buffer is full. 65 | FlagAnalyticsInfluxBufferSize = Flag{ 66 | Long: "analytics.influx_buffer_size", 67 | Short: "", 68 | Value: 10, 69 | Usage: "InfluxDB buffer size. Request metrics will be written to InfluxDB when this buffer is full.", 70 | } 71 | // FlagAnalyticsInfluxMeasurement specifies the InfluxDB measurement to be used for Kanali request metrics. 72 | FlagAnalyticsInfluxMeasurement = Flag{ 73 | Long: "analytics.influx_measurement", 74 | Short: "", 75 | Value: "request_details", 76 | Usage: " InfluxDB measurement to be used for Kanali request metrics.", 77 | } 78 | ) 79 | -------------------------------------------------------------------------------- /controller/tpr.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package controller 22 | 23 | import ( 24 | "fmt" 25 | "net/http" 26 | 27 | "github.com/Sirupsen/logrus" 28 | "k8s.io/kubernetes/pkg/api" 29 | "k8s.io/kubernetes/pkg/api/errors" 30 | "k8s.io/kubernetes/pkg/api/unversioned" 31 | "k8s.io/kubernetes/pkg/apis/extensions" 32 | ) 33 | 34 | // tpr is an internal struct that stores 35 | // information about a Kubernetes ThirdPartyResource 36 | type tpr struct { 37 | Name string 38 | Version string 39 | Description string 40 | } 41 | 42 | // CreateTPRs will create the two tprs that kanali uses 43 | // ApiProxy an ApiKey 44 | func (c *Controller) CreateTPRs() error { 45 | 46 | logrus.Debug("creating TPRs") 47 | 48 | if err := c.doCreateTPRs(&tpr{ 49 | Name: "api-proxy.kanali.io", 50 | Version: "v1", 51 | Description: "api proxy TPR", 52 | }, &tpr{ 53 | Name: "api-key.kanali.io", 54 | Version: "v1", 55 | Description: "api key TPR", 56 | }, &tpr{ 57 | Name: "api-key-binding.kanali.io", 58 | Version: "v1", 59 | Description: "api key binding TPR", 60 | }); err != nil { 61 | if !isKubernetesResourceAlreadyExistError(err) { 62 | return fmt.Errorf("Fail to create TPR: %v", err) 63 | } 64 | } 65 | 66 | return nil 67 | 68 | } 69 | 70 | // doCreateTPRs is a helper function that takes a 71 | // list of tprs and adds each of them to our cluster 72 | func (c *Controller) doCreateTPRs(tprs ...*tpr) error { 73 | 74 | for _, tpr := range tprs { 75 | if _, err := c.ClientSet.Extensions().ThirdPartyResources().Create(&extensions.ThirdPartyResource{ 76 | ObjectMeta: api.ObjectMeta{ 77 | Name: tpr.Name, 78 | }, 79 | Versions: []extensions.APIVersion{ 80 | { 81 | Name: tpr.Version, 82 | }, 83 | }, 84 | Description: tpr.Description, 85 | }); err != nil { 86 | return err 87 | } 88 | 89 | } 90 | 91 | return nil 92 | 93 | } 94 | 95 | func isKubernetesResourceAlreadyExistError(err error) bool { 96 | se, ok := err.(*errors.StatusError) 97 | if !ok { 98 | return false 99 | } else if se.Status().Code == http.StatusConflict && se.Status().Reason == unversioned.StatusReasonAlreadyExists { 100 | return true 101 | } else { 102 | return false 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /controller/tpr_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package controller 22 | 23 | import ( 24 | "errors" 25 | "net/http" 26 | "testing" 27 | 28 | "github.com/stretchr/testify/assert" 29 | "k8s.io/kubernetes/pkg/api" 30 | e "k8s.io/kubernetes/pkg/api/errors" 31 | "k8s.io/kubernetes/pkg/api/unversioned" 32 | "k8s.io/kubernetes/pkg/apis/extensions" 33 | "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" 34 | ) 35 | 36 | func TestCreateTPRs(t *testing.T) { 37 | ctlr := Controller{ 38 | RestClient: nil, 39 | ClientSet: fake.NewSimpleClientset(), 40 | MasterHost: "foo.bar.com", 41 | } 42 | 43 | err := ctlr.CreateTPRs() 44 | assert.Nil(t, err) 45 | err = ctlr.CreateTPRs() 46 | assert.Nil(t, err) 47 | resource, _ := ctlr.ClientSet.Extensions().ThirdPartyResources().Get("api-proxy.kanali.io") 48 | assert.Equal(t, resource, &extensions.ThirdPartyResource{ 49 | ObjectMeta: api.ObjectMeta{ 50 | Name: "api-proxy.kanali.io", 51 | }, 52 | Versions: []extensions.APIVersion{ 53 | { 54 | Name: "v1", 55 | }, 56 | }, 57 | Description: "api proxy TPR", 58 | }) 59 | resource, _ = ctlr.ClientSet.Extensions().ThirdPartyResources().Get("api-key.kanali.io") 60 | assert.Equal(t, resource, &extensions.ThirdPartyResource{ 61 | ObjectMeta: api.ObjectMeta{ 62 | Name: "api-key.kanali.io", 63 | }, 64 | Versions: []extensions.APIVersion{ 65 | { 66 | Name: "v1", 67 | }, 68 | }, 69 | Description: "api key TPR", 70 | }) 71 | resource, _ = ctlr.ClientSet.Extensions().ThirdPartyResources().Get("api-key-binding.kanali.io") 72 | assert.Equal(t, resource, &extensions.ThirdPartyResource{ 73 | ObjectMeta: api.ObjectMeta{ 74 | Name: "api-key-binding.kanali.io", 75 | }, 76 | Versions: []extensions.APIVersion{ 77 | { 78 | Name: "v1", 79 | }, 80 | }, 81 | Description: "api key binding TPR", 82 | }) 83 | } 84 | 85 | func TestIsKubernetesResourceAlreadyExistError(t *testing.T) { 86 | assert.False(t, isKubernetesResourceAlreadyExistError(errors.New("test error"))) 87 | se := e.StatusError{ 88 | ErrStatus: unversioned.Status{ 89 | Code: http.StatusConflict, 90 | Reason: unversioned.StatusReasonAlreadyExists, 91 | }, 92 | } 93 | assert.True(t, isKubernetesResourceAlreadyExistError(&se)) 94 | se.ErrStatus.Code = http.StatusNotFound 95 | assert.False(t, isKubernetesResourceAlreadyExistError(&se)) 96 | } 97 | -------------------------------------------------------------------------------- /helm/charts/grafana/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app: {{ template "grafana.fullname" . }} 6 | chart: "{{.Chart.Name}}-{{.Chart.Version}}" 7 | component: "{{ .Values.server.name }}" 8 | heritage: "{{ .Release.Service }}" 9 | release: "{{ .Release.Name }}" 10 | name: {{ template "grafana.server.fullname" . }} 11 | spec: 12 | replicas: 1 13 | template: 14 | metadata: 15 | annotations: 16 | {{- range $key, $value := .Values.server.annotations }} 17 | {{ $key }}: {{ $value }} 18 | {{- end }} 19 | labels: 20 | app: {{ template "grafana.fullname" . }} 21 | component: "{{ .Values.server.name }}" 22 | release: "{{ .Release.Name }}" 23 | spec: 24 | nodeSelector: 25 | {{ toYaml .Values.server.nodeSelector | indent 12 }} 26 | containers: 27 | - name: {{ template "grafana.name" . }} 28 | image: "{{ .Values.server.image }}" 29 | imagePullPolicy: {{ default "Always" .Values.server.imagePullPolicy }} 30 | env: 31 | - name: GF_SECURITY_ADMIN_USER 32 | valueFrom: 33 | secretKeyRef: 34 | name: {{ template "grafana.server.fullname" . }} 35 | key: grafana-admin-user 36 | - name: GF_SECURITY_ADMIN_PASSWORD 37 | valueFrom: 38 | secretKeyRef: 39 | name: {{ template "grafana.server.fullname" . }} 40 | key: grafana-admin-password 41 | {{- if .Values.server.installPlugins }} 42 | - name: GF_INSTALL_PLUGINS 43 | valueFrom: 44 | configMapKeyRef: 45 | name: {{ template "grafana.server.fullname" . }}-config 46 | key: grafana-install-plugins 47 | {{- end }} 48 | ports: 49 | - containerPort: 3000 50 | readinessProbe: 51 | httpGet: 52 | path: /login 53 | port: 3000 54 | initialDelaySeconds: 30 55 | timeoutSeconds: 30 56 | resources: 57 | {{ toYaml .Values.server.resources | indent 12 }} 58 | volumeMounts: 59 | - name: config-volume 60 | mountPath: {{ default "/etc/grafana" .Values.server.configLocalPath | quote }} 61 | - name: dashboard-volume 62 | mountPath: {{ default "/var/lib/grafana/dashboards" .Values.server.dashboardLocalPath | quote }} 63 | - name: storage-volume 64 | mountPath: {{ default "/var/lib/grafana/data" .Values.server.storageLocalPath | quote }} 65 | subPath: "{{ .Values.server.persistentVolume.subPath }}" 66 | terminationGracePeriodSeconds: {{ default 300 .Values.server.terminationGracePeriodSeconds }} 67 | volumes: 68 | - name: config-volume 69 | configMap: 70 | name: {{ template "grafana.server.fullname" . }}-config 71 | - name: dashboard-volume 72 | configMap: 73 | name: {{ template "grafana.server.fullname" . }}-dashs 74 | - name: storage-volume 75 | {{- if .Values.server.persistentVolume.enabled }} 76 | persistentVolumeClaim: 77 | claimName: {{ if .Values.server.persistentVolume.existingClaim }}{{ .Values.server.persistentVolume.existingClaim }}{{- else }}{{ template "grafana.server.fullname" . }}{{- end }} 78 | {{- else }} 79 | emptyDir: {} 80 | {{- end -}} 81 | -------------------------------------------------------------------------------- /utils/utils.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package utils 22 | 23 | import ( 24 | "bytes" 25 | "net/url" 26 | "path/filepath" 27 | "regexp" 28 | "strings" 29 | 30 | "k8s.io/kubernetes/pkg/api" 31 | ) 32 | 33 | // ComputeTargetPath calcuates the target or destination path based on the incoming path, 34 | // desired target path prefix and the assicated proxy 35 | func ComputeTargetPath(proxyPath, proxyTarget, requestPath string) string { 36 | 37 | proxyPath = NormalizeURLPath(proxyPath) 38 | proxyTarget = NormalizeURLPath(proxyTarget) 39 | requestPath = NormalizeURLPath(requestPath) 40 | 41 | var buffer bytes.Buffer 42 | 43 | if len(strings.SplitAfter(requestPath, proxyPath)) == 0 { 44 | buffer.WriteString("/") 45 | } else if proxyTarget != "/" { 46 | buffer.WriteString(proxyTarget) 47 | } 48 | 49 | buffer.WriteString(strings.SplitAfter(requestPath, proxyPath)[1]) 50 | 51 | if len(buffer.Bytes()) == 0 { 52 | return "/" 53 | } 54 | 55 | return buffer.String() 56 | } 57 | 58 | // GetAbsPath returns the absolute path given any path 59 | // the returned path is in a form that Kanali prefers 60 | func GetAbsPath(path string) (string, error) { 61 | 62 | p, err := filepath.Abs(path) 63 | if err != nil { 64 | return "", err 65 | } 66 | 67 | if p[len(p)-1] == '/' { 68 | if len(p) < 2 { 69 | return "", nil 70 | } 71 | return p[:len(p)-2], nil 72 | } 73 | 74 | return p, nil 75 | 76 | } 77 | 78 | // CompareObjectMeta will loosly determine whether two ObjectMeta objects are equal. 79 | // It does this by comparing the name and namespace 80 | func CompareObjectMeta(c1, c2 api.ObjectMeta) bool { 81 | return c1.Namespace == c2.Namespace && c1.Name == c2.Name 82 | } 83 | 84 | // ComputeURLPath will correct a URL path that might be valid but not ideally formatted 85 | func ComputeURLPath(u *url.URL) string { 86 | return NormalizeURLPath(u.EscapedPath()) 87 | } 88 | 89 | // NormalizeURLPath will normalize any string treating it like a URL path. 90 | func NormalizeURLPath(path string) string { 91 | if len(path) < 1 { 92 | return "/" 93 | } 94 | 95 | path = regexp.MustCompile(`/{2,}`).ReplaceAllString(path, "/") 96 | 97 | if strings.HasSuffix(path, "/") { 98 | path = path[:len(path)-1] 99 | } 100 | 101 | if len(path) < 1 { 102 | return "/" 103 | } 104 | 105 | if path[0] != '/' { 106 | path = "/" + path 107 | } 108 | 109 | return path 110 | } 111 | -------------------------------------------------------------------------------- /config/proxy.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package config 22 | 23 | func init() { 24 | Flags.Add( 25 | FlagProxyEnableClusterIP, 26 | FlagProxyHeaderMaskValue, 27 | FlagProxyEnableMockResponses, 28 | FlagProxyUpstreamTimeout, 29 | FlagProxyMaskHeaderKeys, 30 | FlagProxyTLSCommonNameValidation, 31 | FlagProxyDefaultHeaderValues, 32 | ) 33 | } 34 | 35 | var ( 36 | // FlagProxyEnableClusterIP enables to use of cluster ip as opposed to Kubernetes DNS for upstream routing 37 | FlagProxyEnableClusterIP = Flag{ 38 | Long: "proxy.enable_cluster_ip", 39 | Short: "", 40 | Value: false, 41 | Usage: "Enables to use of cluster ip as opposed to Kubernetes DNS for upstream routing.", 42 | } 43 | // FlagProxyHeaderMaskValue sets the Value to be used when omitting header Values 44 | FlagProxyHeaderMaskValue = Flag{ 45 | Long: "proxy.header_mask_Value", 46 | Short: "", 47 | Value: "omitted", 48 | Usage: "Sets the Value to be used when omitting header Values.", 49 | } 50 | // FlagProxyEnableMockResponses enables Kanali's mock responses feature. Read the documentation for more information 51 | FlagProxyEnableMockResponses = Flag{ 52 | Long: "proxy.enable_mock_responses", 53 | Short: "", 54 | Value: false, 55 | Usage: "Enables Kanali's mock responses feature. Read the documentation for more information.", 56 | } 57 | // FlagProxyUpstreamTimeout sets the length of upstream timeout 58 | FlagProxyUpstreamTimeout = Flag{ 59 | Long: "proxy.upstream_timeout", 60 | Short: "", 61 | Value: "0h0m10s", 62 | Usage: "Set length of upstream timeout. Defaults to none", 63 | } 64 | // FlagProxyMaskHeaderKeys specifies which headers to mask. 65 | FlagProxyMaskHeaderKeys = Flag{ 66 | Long: "proxy.mask_header_keys", 67 | Short: "", 68 | Value: []string{}, 69 | Usage: "Specify which headers to mask", 70 | } 71 | // FlagProxyTLSCommonNameValidation determins whether common name validation occurs as part of an SSL handshake 72 | FlagProxyTLSCommonNameValidation = Flag{ 73 | Long: "proxy.tls_common_name_validation", 74 | Short: "", 75 | Value: true, 76 | Usage: "Should common name validate as part of an SSL handshake.", 77 | } 78 | // FlagProxyDefaultHeaderValues specifies the default values for HTTP headers to be used in dynamic service discovery 79 | FlagProxyDefaultHeaderValues = Flag{ 80 | Long: "proxy.default_header_values", 81 | Short: "", 82 | Value: map[string]string{}, 83 | Usage: "Specifies the default values for HTTP headers to be used in dynamic service discovery.", 84 | } 85 | ) 86 | -------------------------------------------------------------------------------- /helm/templates/tlsSecret.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | apiVersion: v1 4 | kind: Secret 5 | metadata: 6 | name: kanali 7 | namespace: default 8 | type: kubernetes.io/tls 9 | data: 10 | tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tDQpNSUlDempDQ0FiYWdBd0lCQWdJSkFLWXh2bGVwaFZyVU1BMEdDU3FHU0liM0RRRUJDd1VBTUJZeEZEQVNCZ05WDQpCQU1NQzJWNFlXMXdiR1V1WTI5dE1CNFhEVEUzTURRek1ESXlOREUxT1ZvWERURTVNRFF6TURJeU5ERTFPVm93DQpGakVVTUJJR0ExVUVBd3dMWlhoaGJYQnNaUzVqYjIwd2dnRWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUJEd0F3DQpnZ0VLQW9JQkFRRDVuM1NwUTI5T1dkNUEvbXJFeHh5OGN5ek42WTRTcEFrVjZ4SmsyKzRMRFg5RnZxbGo3NVhqDQpjd3VnY0U1bmhHOHZaWWJMSkRwTFd1cWFUUUtHMGpmN3lNK3pSQ0FzR0dNQ1NPQUczNmZIYThmMU5ZUzh2elVHDQpmVUtLRzJ5OFpWYU9aaHJOOERld1RkOGJIRFJCakllcTVQMjhaZHdqdDJDSSsvMHdBbTlHT1RQa25JMnpWUDdSDQpROVBEcnYxTU55Z0RtRnUyUmx6dFdtbTAxM0x3Z0dEVXRWOXJmd0FnbVpFc29iMUVsK3VWeFlRREZZMGx6a3BRDQpVdGNpR0tMV283TUNUV3ZmY1ZHc0hKelZWL29ydDZERlN3TlJOY0swZWh1ZzduaHlOYUttTzFlRE1QKzgzR285DQpFeXArMlVJYXovYjJtQ3QrT3E4c1ZLN0pZZWttV05hakFnTUJBQUdqSHpBZE1Cc0dBMVVkRVFRVU1CS0hCTUNvDQpZMlNIQk1Db1kyV0hCTUNvWTJZd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFLdEtBMDJVK1BEQVQ0TnNGMjlXDQpOazYzTFkzM2RmSFdPSUs1QjFDMzhpTEdJQU9YT1A5cWlBZHJ5NHFIN3RDOUJtb3FYcVdSZnJ2S2NFdTA3bDc4DQp5N1lGK1RQTUtNY0ZnaXRDbmQydVFTSk52VUM2aDBSaVg4NjI3SWZDaUlVNG5oWXVvOEk3TkZaOWlCdXNac2xyDQo1S2dEODdyTUxvL3UrQitobkpEZjRUSStpNnhuMjd0Z3dqeWlrbGJRUFlXZ1VFK3FNYVVaZGVEVTBmT1lEN09xDQpoay8zdkJaQlkxcGhHeS9GckZLYnJEazA1ellNT05JSDlvUjJIOUpRdnF6VzVOQ2Z6M3dpZGpqZFZ4aUloMzlWDQp5eW5rQ2VpN1VCa0MvZnRPcDd1dTRjdXoxN1I5aFc3ZjhDZHNtcWZidkZhcVkzVHVzTnlzL0lFRmdyN3p5RHc4DQo0Y289DQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0t 11 | tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQ0KTUlJRW93SUJBQUtDQVFFQStaOTBxVU52VGxuZVFQNXF4TWNjdkhNc3plbU9FcVFKRmVzU1pOdnVDdzEvUmI2cA0KWSsrVjQzTUxvSEJPWjRSdkwyV0d5eVE2UzFycW1rMENodEkzKzhqUHMwUWdMQmhqQWtqZ0J0K254MnZIOVRXRQ0Kdkw4MUJuMUNpaHRzdkdWV2ptWWF6ZkEzc0UzZkd4dzBRWXlIcXVUOXZHWGNJN2RnaVB2OU1BSnZSamt6NUp5Tg0KczFUKzBVUFR3Njc5VERjb0E1aGJ0a1pjN1ZwcHROZHk4SUJnMUxWZmEzOEFJSm1STEtHOVJKZnJsY1dFQXhXTg0KSmM1S1VGTFhJaGlpMXFPekFrMXIzM0ZSckJ5YzFWZjZLN2VneFVzRFVUWEN0SG9ib081NGNqV2lwanRYZ3pELw0Kdk54cVBSTXFmdGxDR3MvMjlwZ3JmanF2TEZTdXlXSHBKbGpXb3dJREFRQUJBb0lCQUVOQTQ5U0t0NTFiZHhiNQ0KdE5ocGNPT1JBRnhGOWFJdUVjaVcrZkMwbEhEajVRdHNjQVRkMHZ0aHpwc2VSdkY2NjkzUU03M2RkOXMvbG4rVw0KQ2YwNi9CeFpJU1NDVVV5d0VWVFhQNHg2aTZDZCtGU25ZNmphdHpXVlgrMEhzSWNkb25GaEx3MlhEOW52VVJIag0KeW14eXFVYXd4WEFSM1hxOStFTlA2UW9iVGRHVUphQlE1MlZJN1B0dm8wV3hmM0dyUWs0Z3ZpZ2FHM0JDNUI3OQ0KTW1TYnhLNXdOOURycm1meGZ1MjFSVU00Y005ZG1iU2ZvNTEzVU9Vdm1Ya3d3c3F3M01GVTRLd0dUVi8xdXN1Uw0KbkJLZTJkcWhKMThOd1dNaGpuWlJWMEcrTTRLSkttcnV3cXRlVWFENDl5ZmZSUXpLRDRhazBDK2tqZnpNRWRTQg0KdXZSSTJ0RUNnWUVBL2ZBb2RrbnhFN2l0WmVNMnoxeVJKWE5CVnNVcFJ5NjY0QWxaNXBRSjRSQjhmZ1NLa0Y4NQ0KZUw5VmZ5enFSVldQQkxCaVhRdG9EMTVxL2JGcDZxUlNDWXd0b0M0R3J3VEtFUERvUmNyVEorelVCeXpMZTYzSg0KV0w0ZE1SUG5JcUZCL3E3SkVGc3dMbmNzbDd6R1RtaHdrclJkaEoyeE51djd2Z3RHNEpaRU84c0NnWUVBKzZaVA0KOEI0MS8ycnVHeG1nT09pNDliM1Y1RHhJMVdVYXpHaUF0Qk5ORmxEZm8zWnE0bkdtU05XZ1NuQW5COGo0cFU1Tg0KU2ZzK09VRit5Si84MmlqMVBmZDhNYzZ3ZTd4ZmJMZFhFNmpHOGp3S1ZVcGUwVGFGbHpRdm1RSWFvcUdUdHBOWg0KUVAzaE9iMHhaZTVjVytXSlJmSldZOVJWUUdJdm83dVRJZUNCcFlrQ2dZRUF4TlVWbC9MaWtlWFJTaXVmdllYRA0KNENLQlgrKzllamFIbGNiSno0ZXFUTEVKdm1ob3UxV0VaOHJ2UzMrV0s1NFJHSkpiL0VFdUxOT0QzUmRhd1EwVA0KcGVEcE1NTGNYV2M1OVgyMm5QcUZSK296dzBmK2hlU0VNR3hVbGtrV0hPcWdDL2lSVTBOTGlvakhvT29yVUhWMQ0KNU5FM3QrYS9pWkhMZFZpcVhNVTlLSmNDZ1lBWjZENjkrcTQrZEdpOCszOW1QSGRHUFZ2MjJrbjVSaVpqSXVNVg0KSnVPSng1dXVmWE4xaXBPKzdkZEpzcEFpR2d1WElSK04zVUxEckQxOE5CUlk5VnlDRzZkNmpUZllGVVdSc0xKVA0KUU0zeWhFSGdFLzc4OU9yOTdRNTFaeVVNMXl1WTRVU1FEMU1QbWEyck84WGdaQm9rekZVZWcrNmU2VHpVVTJ4TA0KVVl5bldRS0JnQWg0UnN3dXBqMzVsakUyY0dLa2ZpODBCTHR4b3ZvMzZVbmJtTWY1eFZQaTJLRmtxSWZUelZRbw0KQ3hEWHQ4R1ZWOTViTTZQOUNDODhNWks3MERkZXFZdWtxaG9DRDRIUzFVaUloaitEcEhzMU16MmxJVUZ0QWhTQg0KMGFwc2JDejloTGdMakNEbkFORzFsVGJSR0FVblQvb1o4azJSUytwMldxak9SS2t0ZXhiMA0KLS0tLS1FTkQgUlNBIFBSSVZBVEUgS0VZLS0tLS0= -------------------------------------------------------------------------------- /steps/mockservice.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package steps 22 | 23 | import ( 24 | "fmt" 25 | "bytes" 26 | "context" 27 | "encoding/json" 28 | "errors" 29 | "strconv" 30 | "net/http" 31 | "net/http/httptest" 32 | 33 | "github.com/northwesternmutual/kanali/metrics" 34 | "github.com/northwesternmutual/kanali/spec" 35 | "github.com/northwesternmutual/kanali/utils" 36 | "github.com/opentracing/opentracing-go" 37 | ) 38 | 39 | // MockServiceStep is factory that defines a step responsible for 40 | // discovering a mock response for the incoming request 41 | type MockServiceStep struct{} 42 | 43 | // GetName retruns the name of the MockServiceStep step 44 | func (step MockServiceStep) GetName() string { 45 | return "Mock Service" 46 | } 47 | 48 | // Do executes the logic of the MockServiceStep step 49 | func (step MockServiceStep) Do(ctx context.Context, proxy *spec.APIProxy, m *metrics.Metrics, w http.ResponseWriter, r *http.Request, resp *http.Response, trace opentracing.Span) error { 50 | 51 | targetPath := utils.ComputeTargetPath(proxy.Spec.Path, proxy.Spec.Target, utils.ComputeURLPath(r.URL)) 52 | 53 | untypedMr, err := spec.MockResponseStore.Get(proxy.ObjectMeta.Namespace, proxy.Spec.Mock.ConfigMapName, targetPath, r.Method) 54 | if err != nil { 55 | return &utils.StatusError{Code: http.StatusInternalServerError, Err: fmt.Errorf("error retrieving mock response: %s", err.Error())} 56 | } 57 | if untypedMr == nil { 58 | return &utils.StatusError{Code: http.StatusNotFound, Err: errors.New("no mock response found")} 59 | } 60 | mr, ok := untypedMr.(spec.Route) 61 | if !ok { 62 | return &utils.StatusError{Code: http.StatusNotFound, Err: errors.New("no mock response found")} 63 | } 64 | 65 | mockBodyData, err := json.Marshal(mr.Body) 66 | if err != nil { 67 | return &utils.StatusError{Code: http.StatusInternalServerError, Err: fmt.Errorf("the configmap %s in the namespace %s is not formated correctly. while data was found for the incoming route, it was not valid json", 68 | proxy.Spec.Mock.ConfigMapName, 69 | proxy.ObjectMeta.Namespace, 70 | )} 71 | } 72 | 73 | // create new upstream header object 74 | upstreamHeaders := http.Header{} 75 | 76 | // currently, we are enforcing a json response 77 | upstreamHeaders.Add("Content-Type", "application/json") 78 | 79 | // create a fake response 80 | responseRecorder := &httptest.ResponseRecorder{ 81 | Code: mr.Code, 82 | Body: bytes.NewBuffer(mockBodyData), 83 | HeaderMap: upstreamHeaders, 84 | } 85 | 86 | m.Add(metrics.Metric{Name: "http_response_code", Value: strconv.Itoa(mr.Code), Index: true}) 87 | 88 | *resp = *responseRecorder.Result() 89 | 90 | return nil 91 | 92 | } 93 | -------------------------------------------------------------------------------- /utils/utils_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package utils 22 | 23 | import ( 24 | "testing" 25 | 26 | "github.com/stretchr/testify/assert" 27 | "k8s.io/kubernetes/pkg/api" 28 | ) 29 | 30 | func TestComputeTargetPath(t *testing.T) { 31 | assert.Equal(t, "/", NormalizeURLPath(ComputeTargetPath("/foo/bar", "", "/foo/bar"))) 32 | assert.Equal(t, "/", NormalizeURLPath(ComputeTargetPath("/foo/bar", "/", "/foo/bar"))) 33 | assert.Equal(t, "/foo", NormalizeURLPath(ComputeTargetPath("/foo/bar", "/foo", "/foo/bar"))) 34 | assert.Equal(t, "/foo/bar", NormalizeURLPath(ComputeTargetPath("/foo/bar", "/foo", "/foo/bar/bar"))) 35 | assert.Equal(t, "/bar", NormalizeURLPath(ComputeTargetPath("/foo/bar", "", "/foo/bar/bar"))) 36 | assert.Equal(t, "/accounts", NormalizeURLPath(ComputeTargetPath("/api/v1/example-two", "/", "/api/v1/example-two/accounts"))) 37 | assert.Equal(t, "/accounts", NormalizeURLPath(ComputeTargetPath("/api/v1/example-two", "/", "/api/v1/example-two/accounts/"))) 38 | assert.Equal(t, "/accounts", NormalizeURLPath(ComputeTargetPath("/api/v1/example-two", "", "/api/v1/example-two/accounts/"))) 39 | assert.Equal(t, "/accounts", NormalizeURLPath(ComputeTargetPath("/api/v1/example-two/", "/", "/api/v1/example-two/accounts/"))) 40 | assert.Equal(t, "/accounts", NormalizeURLPath(ComputeTargetPath("/api/v1/example-two/", "", "/api/v1/example-two/accounts/"))) 41 | assert.Equal(t, "/accounts", NormalizeURLPath(ComputeTargetPath("/api/v1/example-two/", "", "/api/v1/example-two/accounts"))) 42 | assert.Equal(t, "/", NormalizeURLPath(ComputeTargetPath("/", "", "/"))) 43 | assert.Equal(t, "/", NormalizeURLPath(ComputeTargetPath("/", "/", "/"))) 44 | } 45 | 46 | func TestAbsPath(t *testing.T) { 47 | p, _ := GetAbsPath("/") 48 | assert.Equal(t, "", p) 49 | p, _ = GetAbsPath("/foo/") 50 | assert.Equal(t, "/foo", p) 51 | p, _ = GetAbsPath("//") 52 | assert.Equal(t, "", p) 53 | } 54 | 55 | func TestCompareObjectMeta(t *testing.T) { 56 | c1 := api.ObjectMeta{ 57 | Name: "foo", 58 | Namespace: "bar", 59 | } 60 | c2 := api.ObjectMeta{ 61 | Name: "bar", 62 | Namespace: "foo", 63 | } 64 | c3 := api.ObjectMeta{ 65 | Name: "foo", 66 | Namespace: "car", 67 | } 68 | c4 := api.ObjectMeta{ 69 | Name: "bar", 70 | Namespace: "car", 71 | } 72 | 73 | assert.True(t, CompareObjectMeta(c1, c1)) 74 | assert.False(t, CompareObjectMeta(c1, c2)) 75 | assert.False(t, CompareObjectMeta(c1, c3)) 76 | assert.False(t, CompareObjectMeta(c3, c4)) 77 | } 78 | 79 | func TestNormalizeURLPath(t *testing.T) { 80 | assert.Equal(t, "/foo/bar", NormalizeURLPath("foo////bar")) 81 | assert.Equal(t, "/foo", NormalizeURLPath("foo")) 82 | assert.Equal(t, "/foo", NormalizeURLPath("foo////")) 83 | assert.Equal(t, "/foo/bar", NormalizeURLPath("///foo////bar//")) 84 | assert.Equal(t, "/", NormalizeURLPath("")) 85 | assert.Equal(t, "/", NormalizeURLPath("////")) 86 | assert.Equal(t, "/https%3A%2F%2Fgoogle.com", NormalizeURLPath("/////https%3A%2F%2Fgoogle.com")) 87 | } 88 | -------------------------------------------------------------------------------- /steps/validateproxy_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Northwestern Mutual. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package steps 22 | 23 | import ( 24 | "context" 25 | "errors" 26 | "net/http" 27 | "net/url" 28 | "testing" 29 | 30 | "github.com/northwesternmutual/kanali/metrics" 31 | "github.com/northwesternmutual/kanali/spec" 32 | "github.com/northwesternmutual/kanali/utils" 33 | "github.com/opentracing/opentracing-go" 34 | "github.com/stretchr/testify/assert" 35 | "k8s.io/kubernetes/pkg/api" 36 | "k8s.io/kubernetes/pkg/api/unversioned" 37 | ) 38 | 39 | func TestValidateProxyGetName(t *testing.T) { 40 | assert := assert.New(t) 41 | step := ValidateProxyStep{} 42 | assert.Equal(step.GetName(), "Validate Proxy", "step name is incorrect") 43 | } 44 | 45 | func TestValidateProxy(t *testing.T) { 46 | assert := assert.New(t) 47 | step := ValidateProxyStep{} 48 | 49 | proxyStore := spec.ProxyStore 50 | proxyList := getTestAPIProxyListForValidateProxy() 51 | proxyStore.Clear() 52 | proxyStore.Set(proxyList.Proxies[0]) 53 | proxyStore.Set(proxyList.Proxies[1]) 54 | 55 | urlOne, _ := url.Parse("https://www.foo.bar.com/api/v1/accounts/one/two") 56 | urlTwo, _ := url.Parse("https://www.foo.bar.com/api/v1/field/one/two") 57 | urlThree, _ := url.Parse("https://www.foo.bar.com/") 58 | urlFour, _ := url.Parse("https://www.foo.bar.com/foo/bar") 59 | 60 | proxy := &spec.APIProxy{} 61 | 62 | assert.Nil(step.Do(context.Background(), proxy, &metrics.Metrics{}, nil, &http.Request{URL: urlOne}, nil, opentracing.StartSpan("test span")), "expected proxy to be found") 63 | assert.Equal(*proxy, proxyList.Proxies[0]) 64 | assert.Nil(step.Do(context.Background(), proxy, &metrics.Metrics{}, nil, &http.Request{URL: urlTwo}, nil, opentracing.StartSpan("test span")), "expected proxy to be found") 65 | assert.Equal(*proxy, proxyList.Proxies[1]) 66 | assert.Equal(utils.StatusError{Code: http.StatusNotFound, Err: errors.New("proxy not found")}, step.Do(context.Background(), nil, &metrics.Metrics{}, nil, &http.Request{URL: urlThree}, nil, opentracing.StartSpan("test span")), "expected proxy to not exist") 67 | assert.Equal(utils.StatusError{Code: http.StatusNotFound, Err: errors.New("proxy not found")}, step.Do(context.Background(), nil, &metrics.Metrics{}, nil, &http.Request{URL: urlFour}, nil, opentracing.StartSpan("test span")), "expected proxy to not exist") 68 | } 69 | 70 | func getTestAPIProxyListForValidateProxy() *spec.APIProxyList { 71 | 72 | return &spec.APIProxyList{ 73 | TypeMeta: unversioned.TypeMeta{}, 74 | ListMeta: unversioned.ListMeta{}, 75 | Proxies: []spec.APIProxy{ 76 | { 77 | TypeMeta: unversioned.TypeMeta{}, 78 | ObjectMeta: api.ObjectMeta{ 79 | Name: "exampleAPIProxyOne", 80 | Namespace: "foo", 81 | }, 82 | Spec: spec.APIProxySpec{ 83 | Path: "/api/v1/accounts", 84 | Target: "/", 85 | Service: spec.Service{ 86 | Namespace: "foo", 87 | }, 88 | }, 89 | }, 90 | { 91 | TypeMeta: unversioned.TypeMeta{}, 92 | ObjectMeta: api.ObjectMeta{ 93 | Name: "exampleAPIProxyTwo", 94 | Namespace: "foo", 95 | }, 96 | Spec: spec.APIProxySpec{ 97 | Path: "/api/v1/field", 98 | Target: "/", 99 | Service: spec.Service{ 100 | Namespace: "foo", 101 | }, 102 | }, 103 | }, 104 | }, 105 | } 106 | 107 | } 108 | --------------------------------------------------------------------------------