diff --git a/.gitignore b/.gitignore index 16a958b3d..708a13976 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,9 @@ dist *.pyc *.egg-info .venv* +.DS_Store build +.devspace/ +mkchain-devspace.yaml +/*_chain.yaml +/*_chain_invite.yaml diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 0e5a616c9..1834643cb 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -18,3 +18,26 @@ Build all containers: devspace build --skip-push --tag=dev ``` +# Develop with devspace +(This is all still being worked on! Things should become more clear with the introduction of Helm.) + +## Deploy a chain and have devspace watch only k8s manifest: +- Generate constants (with or without Zerotier flags) +- `devspace dev --var=CHAIN_NAME="$CHAIN_NAME"` + +## Deploy chain with Zerotier and also watch the ZT docker file: +- Generate constants with ZT flags +- Use zerotier profile: `devspace dev --var=CHAIN_NAME="$CHAIN_NAME" -p zerotier` +- You may leave out the profile flag if you don't want devspace to watch for zerotier file changes. + +## RPC Auth backend +I've experimented with the idea of giving each service its own devspace.yaml. Inside RPC auth's devspace.yaml, it defines mkchain as a dependency which by running RPC auth devspace.yaml as the entrypoint, will also spin up mkchain (with zerotier if you generated it). Devspace is currently limited in that it will not reload dependencies if their files change. Therefore right now, the recommended way if you would like to view logs and/or have dependencies auto reload, is to run `devspace dev/logs` in multiple shells. + +### Deploy RPC auth backend and watch its files: +- `cd ./docker/rpc-auth` +- `devspace dev --var=CHAIN_NAME="$CHAIN_NAME"` + +## Misc +- Devspace runs a hook to increase `fs.inotify.max_user_watches` to 1048576 in minikube. This is to avoid a "no space left on device" error. See [here](https://serverfault.com/questions/963529/minikube-k8s-kubectl-failed-to-watch-file-no-space-left-on-device) for more. +- If you would like to avoid passing `--var=CHAIN_NAME="$CHAIN_NAME"` to devspace, you can execute in your shell `export CHAIN_NAME=my_chain`. +- You can pass mkchain args like so: `devspace dev --var=CHAIN_NAME="$CHAIN_NAME" --var=MKCHAIN_ARGS="--number-of-nodes 2"` diff --git a/MULTICLUSTER.md b/MULTICLUSTER.md index c23a7bcd8..b77812832 100644 --- a/MULTICLUSTER.md +++ b/MULTICLUSTER.md @@ -14,7 +14,7 @@ peer-to-peer network. This tutorial assumes the use of Minikube. * a ZeroTier network and api access token * jq -## Installing prerequisites +## Installing prerequisites This section varies depending on OS. @@ -34,7 +34,7 @@ brew install jq brew install minikube ``` -### Arch Linux +### Arch Linux ```shell pacman -Syu && pacman -S python3 jq minikube kubectl kubectx linux @@ -162,3 +162,26 @@ Check that the nodes have matching heads by comparing their hashes: ``` shell kubectl get pod -n tqtezos -l appType=tezos -o name | while read line; do kubectl -n tqtezos exec $line -c tezos-node -- /usr/local/bin/tezos-client rpc get /chains/main/blocks/head/hash; done ``` + +## RPC Authentication +You can optionally spin up an RPC authentication server allowing clients with your given permission to make RPC calls: + +```shell +mkchain create $CHAIN_NAME --rpc-auth ... +``` + +### Current authentication flow +The client authenticates themselves and will receive a secret url that allows them to make RPC calls. +- You provide a trusted client with your cluster ip/address and your private tezos chain id. +- To see your chain id: + ```shell + kubectl exec -it -n tqtezos deployment/tezos-bootstrap-node -c tezos-node -- tezos-client rpc get /chains/main/chain_id + ``` + Or use a tool like like [Lens](https://k8slens.dev/) or run `kubectl logs -n tqtezos deployment/tezos-bootstrap-node -c tezos-node` to see the tezos node logs at the top. +- client runs: `scripts/rpc-auth-client.sh --cluster-address $CLUSTER_IP --tz-alias $TZ_ALIAS --chain-id $CHAIN_ID` +- TZ_ALIAS is the alias of a client's tz address secret key. The client's secret key is used to sign some data for the server to then verify. +- If the client is authenticated, the response should contain a secret url such as `http://192.168.64.51/tezos-node-rpc/ffff3eb3d7dd4f6bbff3f2fd096722ae/` +- Client can then make RPC requests: + - `curl http://192.168.64.51/tezos-node-rpc/ffff3eb3d7dd4f6bbff3f2fd096722ae/chains/main/chain_id` + - Bug in tezos client v8, so as of version `tezos/tezos:master_08d3405e_20201113152010`: + - `tezos-client --endpoint http://192.168.64.51/tezos-node-rpc/ffff3eb3d7dd4f6bbff3f2fd096722ae/ rpc get chains/main/chain_id` diff --git a/README.md b/README.md index 60b037589..8c7aed5c5 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ You can modify these parameters by: | timestamp | --timestamp | timestamp for the chain to join | | | protocol_hash | --protocol-hash | Desired Tezos protocol hash | PsDELPH1Kxsxt8f9eWbxQeRxkjfbxoqM52jvs5Y5fBxWWh4ifpo | | baker_command | --baker-command | The baker command to use, including protocol | tezos-baker-007-PsDELPH1 | +| rpc_auth | --rpc-auth | Include an RPC authentication server | | ## private chain diff --git a/devspace.yaml b/devspace.yaml index 21b5b9b85..b137d86e9 100755 --- a/devspace.yaml +++ b/devspace.yaml @@ -5,31 +5,55 @@ deployments: kubectl: manifests: - ./mkchain-devspace.yaml -images: - zerotier: - image: tezos-zerotier - dockerfile: ./docker/zerotier/Dockerfile - context: ./docker/zerotier + dev: - sync: - - imageName: zerotier - namespace: tqtezos - labelSelector: - app: tezos-bootstrap-node + logs: + disabled: true + autoReload: + deployments: + - chain + paths: + - ./tqchain/deployment/* + hooks: - command: sh args: - -c - - "mkchain ${ACTION} ${CHAIN_NAME} > mkchain-devspace.yaml" + - minikube ssh "sudo sysctl fs.inotify.max_user_watches=1048576" when: before: deployments: all + - command: sh + args: + - -c + - "mkchain ${ACTION} ${CHAIN_NAME} ${CHAIN_ARGS} > mkchain-devspace.yaml" + when: + before: + deployments: all + vars: -- name: CHAIN_NAME - question: Name of the chain as passed to generate-constants - default: "devspace" - source: env -- name: ACTION - question: Mkchain action (create or invite) - default: "create" - source: env + - name: ACTION + question: Mkchain action (create or invite) + default: "create" + source: env + - name: CHAIN_NAME + question: Name of the chain as passed to generate-constants + default: "devspace" + source: env + - name: CHAIN_ARGS + default: "" + source: env + +profiles: + - name: zerotier + replace: + images: + zerotier: + image: tezos-zerotier + dockerfile: ./docker/zerotier/Dockerfile + context: ./docker/zerotier + patches: + - op: add + path: dev.autoReload.images + value: + - zerotier diff --git a/docker/rpc-auth/Dockerfile b/docker/rpc-auth/Dockerfile new file mode 100644 index 000000000..1216a7dbd --- /dev/null +++ b/docker/rpc-auth/Dockerfile @@ -0,0 +1,66 @@ +FROM python:3.9-slim as builder + +RUN mkdir -p /var/rpc-auth/ + +WORKDIR /var/rpc-auth/ + +# Installing pytezos deps +RUN apt-get update -y \ + && apt-get install --no-install-recommends -y \ + automake \ + build-essential \ + libffi-dev \ + libgmp-dev \ + libsecp256k1-dev \ + libsodium-dev \ + libtool \ + pkg-config \ + && echo + +COPY requirements.txt . +RUN mkdir wheels \ + && pip wheel -r requirements.txt \ + --wheel-dir ./wheels --no-cache-dir + +FROM python:3.9-slim AS src + +WORKDIR /var/rpc-auth/ + +# Installing pytezos deps +RUN apt-get update -y \ + && apt-get install --no-install-recommends -y \ + libffi-dev \ + libgmp-dev \ + libsecp256k1-dev \ + libsodium-dev \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Installing python dependencies +COPY --from=builder /var/rpc-auth/wheels wheels +COPY requirements.txt . +RUN pip install -r requirements.txt \ + --no-index --find-links ./wheels \ + && rm -rf ./wheels ./requirements.txt + +RUN groupadd -g 999 appuser && \ + useradd -r -u 999 -g appuser appuser + +COPY --chown=appuser:appuser ./server/index.py . + +ARG FLASK_ENV=production +ENV FLASK_ENV=$FLASK_ENV +ENV PYTHONUNBUFFERED=x + +EXPOSE 8080 + +USER appuser + +CMD uwsgi \ + --http-socket 0.0.0.0:8080 \ + --callable app \ + --threads 100 \ + --processes 1 \ + --wsgi-file index.py \ + --worker-reload-mercy 0 \ + $(if [ "${FLASK_ENV}" = "development" ] ; then echo "--touch-reload index.py" ; else : ; fi) diff --git a/docker/rpc-auth/devspace.yaml b/docker/rpc-auth/devspace.yaml new file mode 100644 index 000000000..4295ad36f --- /dev/null +++ b/docker/rpc-auth/devspace.yaml @@ -0,0 +1,54 @@ +version: v1beta9 +deployments: + - name: rpc-auth + namespace: tqtezos + kubectl: + manifests: + - ../../tqchain/deployment/rpc-auth.yaml + +images: + rpc-auth: + image: tezos-rpc-auth + dockerfile: ./Dockerfile + context: ./ + build: + docker: + options: + buildArgs: + FLASK_ENV: ${FLASK_ENV} +dev: + logs: + images: + - rpc-auth + sync: + - imageName: rpc-auth + namespace: tqtezos + labelSelector: + app: rpc-auth + localSubPath: ./server + autoReload: + images: + - rpc-auth + deployments: + - rpc-auth + +hooks: + - command: minikube + args: + - addons + - enable + - ingress + when: + before: + deployments: rpc-auth + +vars: + - name: FLASK_ENV # development | production + question: Deployment env to run RPC authentication + default: development + source: env + +dependencies: + - name: chain + source: + path: ../../ diff --git a/docker/rpc-auth/requirements.txt b/docker/rpc-auth/requirements.txt new file mode 100644 index 000000000..ebd6aa72d --- /dev/null +++ b/docker/rpc-auth/requirements.txt @@ -0,0 +1,35 @@ +base58==1.0.3 +bson==0.5.10 +certifi==2020.6.20 +cffi==1.14.3 +chardet==3.0.4 +click==7.1.2 +fastecdsa==1.7.5 +fire==0.3.1 +Flask==1.1.2 +idna==2.10 +itsdangerous==1.1.0 +Jinja2==2.11.2 +loguru==0.5.3 +MarkupSafe==1.1.1 +mnemonic==0.19 +netstruct==1.1.2 +pendulum==2.1.2 +ply==3.11 +pyblake2==1.1.2 +pycparser==2.20 +pysodium==0.7.5 +pytezos==2.5.11 +python-dateutil==2.8.1 +pytzdata==2020.1 +PyYAML==5.3.1 +redis==3.5.3 +requests==2.24.0 +secp256k1==0.13.2 +simplejson==3.17.2 +six==1.15.0 +termcolor==1.1.0 +tqdm==4.51.0 +urllib3==1.25.11 +uWSGI==2.0.19.1 +Werkzeug==1.0.1 diff --git a/docker/rpc-auth/server/index.py b/docker/rpc-auth/server/index.py new file mode 100755 index 000000000..d4e16af67 --- /dev/null +++ b/docker/rpc-auth/server/index.py @@ -0,0 +1,167 @@ +import os +import re +import time +from functools import cache +from urllib.parse import urljoin +from uuid import uuid4 + +import requests +from flask import Flask, abort, request +from pytezos.crypto import Key +from redis import StrictRedis, WatchError + +TEZOS_RPC_SERVICE_URL = ( + f"http://{os.getenv('TEZOS_RPC_SERVICE')}:{os.getenv('TEZOS_RPC_SERVICE_PORT')}" +) + +app = Flask(__name__) +redis = StrictRedis(host=os.getenv("REDIS_HOST"), port=os.getenv("REDIS_PORT")) + +## ROUTES + + +@app.route("/vending-machine/") +def get_nonce(chain_id): + try: + is_correct_chain_id = verify_chain_id(chain_id) + if not is_correct_chain_id: + abort(401) + except requests.exceptions.RequestException as e: + print("Failed to verify chain id.", e) + abort(500) + + # Tezos client requires the data to be signed in hex format + nonce = uuid4().hex + redis.set(nonce, "", ex=3) + return nonce + + +@app.route("/vending-machine", methods=["POST"]) +def generate_tezos_rpc_url(): + try: + nonce, signature, public_key = [ + request.values[k] for k in ("nonce", "signature", "public_key") + ] + except KeyError as e: + print("Request data:", request.values) + print(e) + abort(400) + + if not is_valid_nonce(nonce): + abort(401) + + tezos_key_object = get_tezos_key_object(public_key) + if not is_valid_signature(tezos_key_object, signature, nonce): + abort(401) + + access_token = uuid4().hex + secret_url = create_secret_url(access_token) + save_access_token(tezos_key_object.public_key_hash(), access_token) + return secret_url + + +@app.route("/auth") +def rpc_auth(): + access_token = extract_access_token(request.headers) + if not is_valid_access_token(access_token): + abort(401) + return "OK", 200 + + +## HELPER FUNCTIONS + + +def verify_chain_id(chain_id): + if chain_id != get_tezos_chain_id(): + return False + return True + + +@cache +def get_tezos_chain_id(): + tezos_chain_id = os.getenv("TEZOS_CHAIN_ID") + if tezos_chain_id: + return tezos_chain_id + chain_id_response = requests.get( + urljoin(TEZOS_RPC_SERVICE_URL, "chains/main/chain_id") + ) + return chain_id_response.text.strip('\n"') + + +def is_valid_nonce(nonce): + with redis.pipeline() as pipeline: + try: + pipeline.watch(nonce) + redis_nonce = pipeline.get(nonce) + pipeline.multi() + pipeline.delete(nonce) + pipeline.execute() + if redis_nonce != None: + return True + except WatchError: + print("Nonce was already validated.") + + return False + + +def get_tezos_key_object(public_key): + try: + return Key.from_encoded_key(public_key) + except ValueError as e: + print("Something is wrong with the public_key provided:", e) + abort(401) + + +def is_valid_signature(key_object, signature, nonce): + try: + bytes_prefix = "0x05" + key_object.verify(signature, bytes_prefix + nonce) + return True + except ValueError as e: + print("Error verifying signature:", e) + return False + + +def create_secret_url(access_token): + return urljoin(request.url_root, f"tezos-node-rpc/{access_token}") + + +def create_redis_access_token_key(access_token, hash=False): + return f"access_token{':hash' if hash else ''}:{access_token}" + + +def save_access_token(tz_address, access_token): + access_token_key = create_redis_access_token_key(access_token) + with redis.pipeline() as pipeline: + # Create redis hash of access token with timestamp and tz address + pipeline.hset( + access_token_key, mapping={"timestamp": time.time(), "address": tz_address} + ) + # Add access token to list of this tz address's tokens + pipeline.sadd(tz_address, access_token_key) + pipeline.execute() + + +def extract_access_token(headers): + original_url = headers.get("X-Original-Url") + regex_obj = re.search(r"tezos-node-rpc/(.*?)/", original_url) + if regex_obj: + return regex_obj.group(1) + + +def is_valid_access_token(access_token): + if ( + access_token + and len(access_token) == 32 # Should be 32 char hex string + and redis.exists(create_redis_access_token_key(access_token)) == 1 + ): + return True + return False + + +if __name__ == "__main__": + app.run( + host="0.0.0.0", + port=8080, + debug=(True if os.getenv("FLASK_ENV") == "development" else False), + ) diff --git a/docker/zerotier/entrypoint.sh b/docker/zerotier/entrypoint.sh index 0e7d7d837..dd05ab9a3 100755 --- a/docker/zerotier/entrypoint.sh +++ b/docker/zerotier/entrypoint.sh @@ -12,7 +12,7 @@ if [[ $(hostname) == *"bootstrap-node"* ]]; then zerotier_name="${CHAIN_NAME}_bootstrap" zerotier_description="Bootstrap node for chain ${CHAIN_NAME}" else - zerotier_name="${CHAIN_NAME}_node" + zerotier_name="${CHAIN_NAME}_node" zerotier_description="P2p node of chain ${CHAIN_NAME}" fi @@ -45,7 +45,7 @@ if [[ $(hostname) == *"bootstrap-node"* ]]; then zerotier_name="${CHAIN_NAME}_bootstrap" zerotier_description="Bootstrap node for chain ${CHAIN_NAME}" else - zerotier_name="${CHAIN_NAME}_node" + zerotier_name="${CHAIN_NAME}_node" zerotier_description="P2p node of chain ${CHAIN_NAME}" fi curl -s -XPOST \ diff --git a/lint.sh b/scripts/lint.sh similarity index 100% rename from lint.sh rename to scripts/lint.sh diff --git a/scripts/rpc-auth-client.sh b/scripts/rpc-auth-client.sh new file mode 100755 index 000000000..160164d73 --- /dev/null +++ b/scripts/rpc-auth-client.sh @@ -0,0 +1,104 @@ +#!/bin/bash + +while test $# -gt 0; do + case "$1" in + -h | --help) + echo "options:" + echo "-h, --help show brief help" + echo "--cluster-address specify ip or url for requesting nonce" + echo "--chain-id specify chain id of permission chain" + echo "--tz-alias specify tz alias of your key" + exit 0 + ;; + --cluster-address) + shift + if test $# -gt 0; then + export CLUSTER_ADDRESS=$1 + else + echo "no address specified" + exit 1 + fi + shift + ;; + --chain-id) + shift + if test $# -gt 0; then + export CHAIN_ID=$1 + else + echo "no chain id specified" + exit 1 + fi + shift + ;; + --tz-alias) + shift + if test $# -gt 0; then + export TZ_ALIAS=$1 + else + echo "no tz alias specified" + exit 1 + fi + shift + ;; + *) + break + ;; + esac +done + +if [ -z "$CLUSTER_ADDRESS" ]; then + echo "--cluster-address flag is required" + exit 1 +elif [ -z "$CHAIN_ID" ]; then + echo "--chain-id flag is required" + exit 1 +elif [ -z "$TZ_ALIAS" ]; then + echo "--tz-alias flag is required" + exit 1 +fi + +if ! tezos-client show address $TZ_ALIAS >/dev/null 2>&1; then + echo "no public key hash alias named $TZ_ALIAS" + exit 1 +fi + +get_response_body() { + echo $1 | sed -e 's/ HTTPSTATUS\:.*//g' +} +get_response_status() { + printf '%q' $1 | sed -e 's/.*HTTPSTATUS://' +} + +echo "Requesting data to sign..." +nonce_res=$(curl -s -X GET http://$CLUSTER_ADDRESS/vending-machine/$CHAIN_ID -w " HTTPSTATUS:%{http_code}") +NONCE=$(get_response_body "$nonce_res") +nonce_res_status=$(get_response_status "$nonce_res") + +if [ "$nonce_res_status" != "200" ]; then + echo "Failed to get nonce. [HTTP status: $nonce_res_status]" + echo "$nonce_res" + exit 1 +fi + +# echo NONCE: "$NONCE" + +echo "Signing data..." +SIGNATURE=$(tezos-client -p PsCARTHAGazK sign bytes 0x05${NONCE} for ${TZ_ALIAS} | cut -f 2 -d " ") +PUBLIC_KEY=$(tezos-client show address ${TZ_ALIAS} 2>/dev/null | grep "Public Key: " | awk '{print $3}') + +# echo SIGNATURE: "$SIGNATURE" +# echo PUBLIC_KEY: "$PUBLIC_KEY" + +echo "Sending request for RPC url..." +secret_url_res=$(curl -s -X POST -d "nonce=${NONCE}" -d "signature=${SIGNATURE}" -d "public_key=${PUBLIC_KEY}" http://$CLUSTER_ADDRESS/vending-machine -w " HTTPSTATUS:%{http_code}") +SECRET_URL=$(get_response_body "$secret_url_res") +secret_url_status=$(get_response_status "$secret_url_res") + +if [ "$secret_url_status" != "200" ]; then + echo "Failed to get secret url. [HTTP status: $secret_url_status]" + echo "$secret_url_res" + exit 1 +fi + +echo "Your secret tezos node RPC url: $SECRET_URL" + diff --git a/tqchain/deployment/bootstrap-node.yaml b/tqchain/deployment/bootstrap-node.yaml index 0ce9eebf8..70dd6a07d 100644 --- a/tqchain/deployment/bootstrap-node.yaml +++ b/tqchain/deployment/bootstrap-node.yaml @@ -120,6 +120,11 @@ spec: envFrom: - configMapRef: name: tezos-config + env: + - name: MY_POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP volumeMounts: - name: config-volume mountPath: /etc/tezos diff --git a/tqchain/deployment/node.yaml b/tqchain/deployment/node.yaml index 00c1a93a6..16e8fce16 100644 --- a/tqchain/deployment/node.yaml +++ b/tqchain/deployment/node.yaml @@ -55,6 +55,11 @@ spec: envFrom: - configMapRef: name: tezos-config + env: + - name: MY_POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP volumeMounts: - name: config-volume mountPath: /etc/tezos diff --git a/tqchain/deployment/rpc-auth.yaml b/tqchain/deployment/rpc-auth.yaml new file mode 100644 index 000000000..152e7bf49 --- /dev/null +++ b/tqchain/deployment/rpc-auth.yaml @@ -0,0 +1,194 @@ +apiVersion: v1 +kind: Service +metadata: + name: redis-service + namespace: tqtezos + labels: + app: redis +spec: + ports: + - name: redis + port: 6379 + selector: + app: redis +--- +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: redis-pv-claim + namespace: tqtezos + labels: + app: redis +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 5Gi +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: redis-config + namespace: tqtezos +data: + redis.conf: | + appendonly yes + appendfsync always +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis + namespace: tqtezos + labels: + app: redis +spec: + selector: + matchLabels: + app: redis + template: + metadata: + labels: + app: redis + spec: + containers: + - name: master + image: redis + command: + - redis-server + - "/redis/redis.conf" + readinessProbe: + exec: + command: + - sh + - -c + - "redis-cli -h $(hostname) ping" + initialDelaySeconds: 5 + timeoutSeconds: 5 + livenessProbe: + exec: + command: + - sh + - -c + - "redis-cli -h $(hostname) ping" + initialDelaySeconds: 5 + periodSeconds: 3 + # resources: + # requests: + # cpu: 100m + # memory: 100Mi + ports: + - containerPort: 6379 + volumeMounts: + - mountPath: /data + name: redis-data + - mountPath: /redis + name: redis-config + + volumes: + - name: redis-data + persistentVolumeClaim: + claimName: redis-pv-claim + - name: redis-config + configMap: + name: redis-config +--- +apiVersion: v1 +kind: Service +metadata: + name: rpc-auth + namespace: tqtezos +spec: + selector: + app: rpc-auth + ports: + - port: 8080 + targetPort: 8080 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: rpc-auth + namespace: tqtezos +spec: + selector: + matchLabels: + app: rpc-auth + template: + metadata: + labels: + app: rpc-auth + appType: rpc-auth + spec: + containers: + - name: rpc-auth + image: tezos-rpc-auth:dev + imagePullPolicy: Never + ports: + - containerPort: 8080 + env: + - name: REDIS_HOST + value: redis-service + - name: REDIS_PORT + value: "6379" + - name: TEZOS_RPC_SERVICE + value: tezos-bootstrap-node-rpc + - name: TEZOS_RPC_SERVICE_PORT + value: "8732" +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: rpc-vending-machine-ingress + namespace: tqtezos + annotations: + nginx.ingress.kubernetes.io/use-regex: "true" +spec: + rules: + - http: + paths: + # Client provides chain id and gets back a nonce + - path: /vending-machine/(.*) + pathType: Exact + backend: + service: + name: rpc-auth + port: + number: 8080 + # Client provides signed data and gets back a secret url + - path: /vending-machine + pathType: Exact + backend: + service: + name: rpc-auth + port: + number: 8080 +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: tezos-rpc-ingress + namespace: tqtezos + annotations: + nginx.ingress.kubernetes.io/use-regex: "true" + nginx.ingress.kubernetes.io/rewrite-target: /$2 + # https://github.com/kubernetes/ingress-nginx/blob/master/docs/user-guide/nginx-configuration/annotations.md#external-authentication + nginx.ingress.kubernetes.io/auth-url: http://$service_name.$namespace.svc.cluster.local:8080/auth + nginx.ingress.kubernetes.io/auth-snippet: | + # Bug with nginx setting auth-url annotation port with a variable. + # Service name and namespace variables work. + # https://github.com/kubernetes/ingress-nginx/issues/6427 + set $service_name "rpc-auth"; +spec: + rules: + - http: + paths: + # Client uses secret url to access the RPC endpoint + - path: /tezos-node-rpc/(.*?)/(.*) + pathType: Exact + backend: + service: + name: tezos-bootstrap-node-rpc + port: + number: 8732 diff --git a/tqchain/mkchain.py b/tqchain/mkchain.py index 6f0b5e28b..b4ef76ab5 100644 --- a/tqchain/mkchain.py +++ b/tqchain/mkchain.py @@ -202,6 +202,25 @@ def get_zerotier_container(): } +def get_rpc_auth_container(): + return { + "name": "rpc-auth", + "image": ( + "tezos-rpc-auth:dev" + if "-" in __version__ or "+" in __version__ + else "tqtezos/tezos-k8s-rpc-auth:%s" % __version__ + ), + "imagePullPolicy": "IfNotPresent", + "ports": [{"containerPort": 8080}], + "env": [ + {"name": "TEZOS_RPC_SERVICE", "value": "tezos-bootstrap-node-rpc"}, + {"name": "TEZOS_RPC_SERVICE_PORT", "value": "8732"}, + {"name": "REDIS_HOST", "value": "redis-service"}, + {"name": "REDIS_PORT", "value": "6379"}, + ], + } + + def get_genesis_vanity_chain_id(seed_len=16): seed = "".join( random.choice(string.ascii_uppercase + string.digits) for _ in range(seed_len) @@ -258,6 +277,11 @@ def get_genesis_vanity_chain_id(seed_len=16): "help": "The baker command to use, including protocol", "default": "tezos-baker-007-PsDELPH1", }, + "rpc_auth": { + "help": "Should spin up an RPC authentication server", + "action": "store_true", + "default": False, + }, } @@ -326,7 +350,7 @@ def main(): .isoformat(), } for k in CHAIN_CONSTANTS.keys(): - if vars(args)[k]: + if vars(args)[k] is not None: base_constants[k] = vars(args)[k] secret_keys = {} public_keys = {} @@ -336,7 +360,7 @@ def main(): public_keys[f"{account}_public_key"] = keys["public_key"] creation_constants = {**base_constants, **secret_keys} - invitation_constants = {**base_constants, **public_keys} + invitation_constants = {**base_constants, "rpc_auth": False, **public_keys} with open(f"{args.chain_name}_chain.yaml", "w") as yaml_file: yaml.dump(creation_constants, yaml_file) @@ -351,6 +375,9 @@ def main(): for k in yaml_constants.keys() } + if c.get("rpc_auth"): + k8s_templates.append("rpc-auth.yaml") + if c["number_of_nodes"] < 1: print( f"Invalid argument --number-of-nodes {c['number_of_nodes']}, must be 1 or more" @@ -517,6 +544,15 @@ def main(): k["data"]["ZTAUTHTOKEN"] = c["zerotier_token"] k["data"]["CHAIN_NAME"] = args.chain_name + if ( + safeget(k, "metadata", "name") == "rpc-auth" + and safeget("kind") == "Deployment" + ): + print(k) + k["spec"]["template"]["spec"]["containers"][ + 0 + ] = get_rpc_auth_container() + k8s_objects.append(k) yaml.dump_all(k8s_objects, sys.stdout) diff --git a/tqchain/utils/generateTezosConfig.py b/tqchain/utils/generateTezosConfig.py index ae75de9d2..9bcbe4464 100644 --- a/tqchain/utils/generateTezosConfig.py +++ b/tqchain/utils/generateTezosConfig.py @@ -123,7 +123,11 @@ def generate_node_config(node_argv): global_parser.add_argument("--data-dir", default="/var/tezos/node") rpc_parser = subparsers.add_parser("rpc") - rpc_parser.add_argument("--listen-addrs", action="append", default=["0.0.0.0:8732"]) + rpc_parser.add_argument( + "--listen-addrs", + action="append", + default=[f"{os.getenv('MY_POD_IP')}:8732", "127.0.0.1:8732"], + ) p2p_parser = subparsers.add_parser("p2p") p2p_parser.add_argument("--bootstrap-peers", action="append", default=[])