-
Notifications
You must be signed in to change notification settings - Fork 26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Aryeh/rpc-auth #36
Aryeh/rpc-auth #36
Changes from 47 commits
6fee28f
dec7345
20bc5ea
1b022e3
e438a41
f65b18f
fb83b1b
8d2632c
f0d2f3b
e8687d3
f1da427
8a604b2
14b7034
c31ff23
8c3b8e1
806870a
95ca56e
f7279aa
8fe6e8c
f9ce69f
853cbd7
f4ed012
224dc90
1734ba1
de28627
a1534f2
9f528dc
be1272e
8a457be
0a8addc
4b2dfff
cffc113
cd78851
6e82a3c
07a6340
074e7b5
b416cb3
7edf679
9698c6a
d509484
ec6cbd8
9ea611d
a5b8c4c
d500c4a
5357394
32c098f
635b952
64e2de2
7fca9ae
eec82f1
22c25d6
3a0a70f
e1361e8
c9a2ef8
38dde33
97d618a
3ca2c86
bf55629
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,4 +4,9 @@ dist | |
*.pyc | ||
*.egg-info | ||
.venv* | ||
.DS_Store | ||
build | ||
.devspace/ | ||
mkchain-devspace.yaml | ||
/*_chain.yaml | ||
/*_chain_invite.yaml |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,17 +5,44 @@ deployments: | |
kubectl: | ||
manifests: | ||
- ./mkchain-devspace.yaml | ||
- name: rpc-auth | ||
namespace: tqtezos | ||
kubectl: | ||
manifests: | ||
- ./tqchain/deployment/rpc-auth.yaml | ||
images: | ||
zerotier: | ||
image: tezos-zerotier | ||
dockerfile: ./docker/zerotier/Dockerfile | ||
context: ./docker/zerotier | ||
rpc-auth: | ||
image: rpc-auth | ||
dockerfile: ./docker/rpc-auth/Dockerfile | ||
context: ./docker/rpc-auth | ||
build: | ||
docker: | ||
options: | ||
buildArgs: | ||
FLASK_ENV: ${FLASK_ENV} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this variable also needs to be declared in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @nicolasochem I just tried with no prompt: ❯ echo $FLASK_ENV
❯ devspace build --skip-push -b
[done] √ Done building image tezos-rpc-auth:qchX3Uf (rpc-auth)
[done] √ Done building image tezos-zerotier:KkAWwY3 (zerotier)
[done] √ Successfully built 2 images How can I reproduce? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @nicolasochem yes, removing .devspace prompted me. thanks |
||
dev: | ||
logs: | ||
images: | ||
- rpc-auth | ||
sync: | ||
- imageName: zerotier | ||
- imageName: rpc-auth | ||
namespace: tqtezos | ||
labelSelector: | ||
app: tezos-bootstrap-node | ||
app: rpc-auth | ||
localSubPath: ./docker/rpc-auth/server | ||
autoReload: | ||
images: | ||
- rpc-auth | ||
- zerotier | ||
deployments: | ||
- rpc-auth | ||
- chain | ||
paths: | ||
- ./tqchain/deployment/* | ||
hooks: | ||
- command: sh | ||
args: | ||
|
@@ -24,6 +51,14 @@ hooks: | |
when: | ||
before: | ||
deployments: all | ||
- command: minikube | ||
args: | ||
- addons | ||
- enable | ||
- ingress | ||
when: | ||
before: | ||
deployments: rpc-auth | ||
vars: | ||
- name: CHAIN_NAME | ||
question: Name of the chain as passed to generate-constants | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is that useful considering that devspace will rebuild and restart the container when something changes? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @nicolasochem looks like devspace is set up to sync files for rpc-auth rather than rebuild image, so this makes sense There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is true |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
import os | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. putting rpc-auth under There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a container centric view of the environment. mkchain does not belong here because it's not part of the cluster. It's a helper script to generate helm chart values. See also: https://github.com/midl-dev/tezos-on-gke/tree/master/docker One of the things I am planning is to migrate Perhaps mkchain could move to the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. mkchain is a software component, so is rpc-auth. They have their own packaging, dependencies, documentation, possible usage and deployment scenarios. I would really like to see component based structure here, perhaps even force it via putting them in separate repos (monorepo can work too, but not like this). Containers flow from that, not the other way around. |
||
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/<chain_id>") | ||
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(): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it is extremely unlikely that a chain is restarted (i guess from scratch), and so the chain id would change, right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think there's a meaningful scenario where chain id would change but this service would not be updated/restarted |
||
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), | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please also add a paragraph in DEVELOPMENT.md explaining how to spin up rpc-auth on minikube with devspace.