diff --git a/k8s/3scale-apicast/apicast-dep.yaml b/k8s/3scale-apicast/apicast-dep.yaml new file mode 100644 index 00000000..9fab4967 --- /dev/null +++ b/k8s/3scale-apicast/apicast-dep.yaml @@ -0,0 +1,52 @@ +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: openresty-instance-1-dep +spec: + replicas: 1 + template: + metadata: + labels: + app: openresty-instance-1-dep + spec: + terminationGracePeriodSeconds: 10 + containers: + - name: nginx-openresty + image: bigchaindb/nginx_3scale:unstable + imagePullPolicy: Always + env: + - name: THREESCALE_PORTAL_ENDPOINT + valueFrom: + secretKeyRef: + name: apicast-secret + key: portal-endpoint + - name: RESOLVER + valueFrom: + configMapKeyRef: + name: vars + key: cluster-dns-server-ip + - name: APICAST_LOG_LEVEL + valueFrom: + configMapKeyRef: + name: apicast-config + key: api-log-level + - name: APICAST_MANAGEMENT_API + valueFrom: + configMapKeyRef: + name: apicast-config + key: mgmt-api-mode + - name: BIGCHAINDB_BACKEND_HOST + valueFrom: + configMapKeyRef: + name: vars + key: bdb-instance-name + - name: BIGCHAINDB_API_PORT + valueFrom: + configMapKeyRef: + name: vars + key: bigchaindb-api-port + ports: + - containerPort: 8080 + protocol: TCP + - containerPort: 8090 + protocol: TCP diff --git a/k8s/3scale-apicast/apicast-svc.yaml b/k8s/3scale-apicast/apicast-svc.yaml new file mode 100644 index 00000000..d8259151 --- /dev/null +++ b/k8s/3scale-apicast/apicast-svc.yaml @@ -0,0 +1,25 @@ +apiVersion: v1 +kind: Service +metadata: + name: openresty-instance-1 + namespace: default + labels: + name: openresty-instance-1 + annotations: + # NOTE: the following annotation is a beta feature and + # only available in GCE/GKE and Azure as of now + # Ref: https://kubernetes.io/docs/tutorials/services/source-ip/ + service.beta.kubernetes.io/external-traffic: OnlyLocal +spec: + selector: + app: openresty-instance-1-dep + ports: + - port: 8080 + targetPort: 8080 + name: apicast-svc + - port: 8090 + targetPort: 8090 + name: apicast-mgmt + protocol: TCP + type: ClusterIP + clusterIP: None diff --git a/k8s/3scale-apicast/container/Dockerfile b/k8s/3scale-apicast/container/Dockerfile new file mode 100644 index 00000000..f06d23fa --- /dev/null +++ b/k8s/3scale-apicast/container/Dockerfile @@ -0,0 +1,16 @@ +FROM openresty/openresty:xenial +LABEL maintainer "dev@bigchaindb.com" +WORKDIR /opt/apicast +RUN apt-get update \ + && apt-get -y upgrade \ + && apt-get autoremove \ + && apt-get clean \ + && apt-get install wget +COPY nginx_openresty_entrypoint.bash / + +# The following ports are the values we use to run the NGINX+3scale container. +# 80 for http, 8080 for the 3scale api, 8888 for health-check, 27017 for +# MongoDB +EXPOSE 8080 8090 8888 + +ENTRYPOINT ["/nginx_openresty_entrypoint.bash"] \ No newline at end of file diff --git a/k8s/3scale-apicast/container/README.md b/k8s/3scale-apicast/container/README.md new file mode 100644 index 00000000..47b37103 --- /dev/null +++ b/k8s/3scale-apicast/container/README.md @@ -0,0 +1,60 @@ +# nginx_3scale agent +nginx_3scale agent is a module that is responsible for providing authentication, +authorization and metering of BigchainDB API users, by communicating with 3scale. +We use the openresty for this, which is nginx bundled with lua libraries. +More information at their [website](openresty.org/en) + +It validates the tokens sent by users in HTTP headers. +The user tokens map directly to the Application Plan specified in 3scale. + +## Build and Push the Latest Container +Use the `docker_build_and_push.bash` script to build the latest docker image +and upload it to Docker Hub. +Ensure that the image tag is updated to a new version number to properly +reflect any changes made to the container. + + +## Working + +* We define a [lua module](./nginx.lua.template) and + custom hooks (lua functions to be executed at certain phases of the nginx + request processing lifecycle) to authenticate an API request. + +* Download the template available from 3scale which pre-defines all the + rules defined using the 3scale UI for monitoring, and the basic nginx + configuration. + +* We heavily modify these templates to add our custom functionality. + +* The nginx_3scale image reads the environment variables and accordingly + creates the nginx.conf and nginx.lua files from the templates. + +* Every request calls the `_M.access()` function. This function extracts the + `app_id` and `app_key` from the HTTP request headers and forwards it to + 3scale to see if a request is allowed to be forwarded to the BigchainDB + backend. The request also contains the + various parameters that one would like to set access policies on. If the + `app_id` and `app_key` is successful, the access rules for the parameters + passed with the request are checked to see if the request can pass through. + For example, we can send a parameter, say `request_body_size`, to the 3scale + auth API. If we have defined a rule in the 3scale dashboard to drop + `request_body_size` above a certain threshold, the authorization will fail + even if the `app_id` and `app_key` are valid. + +* A successful response from the auth API causes the request to be proxied to + the backend. After a backend response, the `_M.post_action_content` hook is + called. We calculate details about all the metrics we are interested in and + form a payload for the 3scale reporting API. This ensures that we update + parameters of every metric defined in the 3scale UI after every request. + +* Note: We do not cache the keys in nginx so that we can validate every request + with 3scale and apply plan rules immediately. We can add auth caching to + improve performance, and in case we move to a fully post-paid billing model. + +* Refer to the references made in the [lua module](./nginx.lua.template) for + more details about how nginx+lua+3scale works + +* For HTTPS support, we also need to add the signed certificate and the + corresponding private key to the folder + `/usr/local/openresty/nginx/conf/ssl/`. Name the pem-encoded certificate as + `cert.pem` and the private key as `cert.key`. diff --git a/k8s/3scale-apicast/container/docker_build_and_push.bash b/k8s/3scale-apicast/container/docker_build_and_push.bash new file mode 100755 index 00000000..31dbb5e9 --- /dev/null +++ b/k8s/3scale-apicast/container/docker_build_and_push.bash @@ -0,0 +1,5 @@ +#!/bin/bash + +docker build -t bigchaindb/nginx_3scale:unstable . + +docker push bigchaindb/nginx_3scale:unstable diff --git a/k8s/3scale-apicast/container/nginx_openresty_entrypoint.bash b/k8s/3scale-apicast/container/nginx_openresty_entrypoint.bash new file mode 100755 index 00000000..1511b63c --- /dev/null +++ b/k8s/3scale-apicast/container/nginx_openresty_entrypoint.bash @@ -0,0 +1,37 @@ +#!/bin/bash +set -euo pipefail + +BASE_DIR=$(pwd) +APICAST_RELEASE="3.1.0" +BASE_GIT_URL="https://github.com/3scale/apicast/archive" + +# Set Default config +export APICAST_CONFIGURATION_LOADER="boot" # Overriding apicast default lazy config loader +export APICAST_MANAGEMENT_API="debug" # Overriding apicast default fo 'status' mode to be + # able to update bigchaindb backen service endpoint + +# Sanity Check +if [[ -z "${THREESCALE_PORTAL_ENDPOINT:?THREESCALE_PORTAL_ENDPOINT not specified. Exiting!}" || \ + -z "${BIGCHAINDB_BACKEND_HOST:?BIGCHAINDB_BACKEND_HOST not specified. Exiting!}" || \ + -z "${BIGCHAINDB_API_PORT:?BIGCHAINDB_API_PORT not specified. Exiting!}" ]]; then + exit 1 +fi + +export THREESCALE_PORTAL_ENDPOINT=`printenv THREESCALE_PORTAL_ENDPOINT` + +# Print Current Configs +echo "Apicast Release: ${APICAST_RELEASE}" +echo "Apicast Download URL: ${BASE_GIT_URL}" +echo "APICAST_CONFIGURATION_LOADER: ${APICAST_CONFIGURATION_LOADER}" +echo "BIGCHAINDB_BACKEND_HOST: ${BIGCHAINDB_BACKEND_HOST}" +echo "BIGCHAINDB_API_PORT: ${BIGCHAINDB_API_PORT}" + +# Download and Install Apicast +wget "${BASE_GIT_URL}/v${APICAST_RELEASE}.tar.gz" +tar -xvzf "v${APICAST_RELEASE}.tar.gz" + +eval luarocks make apicast-${APICAST_RELEASE}/apicast/*.rockspec --tree /usr/local/openresty/luajit + +# Start nginx +echo "INFO: starting nginx..." +exec apicast-${APICAST_RELEASE}/apicast/bin/apicast -b -e production -v -v -v \ No newline at end of file