Skip to content

Commit

Permalink
trying to fix peer based auth, not working yet
Browse files Browse the repository at this point in the history
  • Loading branch information
witlox committed Nov 1, 2024
1 parent 44aa7b9 commit 2b3a791
Show file tree
Hide file tree
Showing 10 changed files with 103 additions and 271 deletions.
2 changes: 0 additions & 2 deletions .env.production
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,5 @@ REDIS_URL=redis://redis:6379/0
OLTP_INSECURE=True#change-me
OLTP_COLLECTOR_URL=http://collector:4317
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS=".*session.*,set-cookie"
PEER_KEY=c3daXP0DDO#change-me
PEER_SECRET=4xST4WqrLF6LjE55vyWeaP2w#change-me
PEER_SALT=98fc47e49cb666023f438cbd6582af65#change-me
PEERS=server1,server2,server3#change-me
24 changes: 10 additions & 14 deletions horao/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
import horao.api
import horao.api.synchronization
import horao.auth
from horao.auth.basic_auth import BasicAuthBackend
from horao.auth.basic import BasicAuthBackend
from horao.auth.peer import PeerAuthBackend

LoggingInstrumentor().instrument(set_logging_format=True)
Expand Down Expand Up @@ -121,7 +121,7 @@ async def docs(request):
return HTMLResponse(html)


def init_api() -> Starlette:
def init() -> Starlette:
"""
Initialize the API
:return: app instance
Expand Down Expand Up @@ -154,17 +154,13 @@ def init_api() -> Starlette:
middleware.append(
Middleware(AuthenticationMiddleware, backend=BasicAuthBackend())
)
app = Starlette(
routes=routes,
middleware=middleware,
debug=False if os.getenv("DEBUG", "False") == "False" else True,
)
if os.getenv("TELEMETRY", "ON") == "OFF":
logger.warning("Telemetry is turned off")
return Starlette(
routes=routes,
middleware=middleware,
debug=False if os.getenv("DEBUG", "False") == "False" else True,
)
return StarletteInstrumentor.instrument_app(
Starlette(
routes=routes,
middleware=middleware,
debug=False if os.getenv("DEBUG", "False") == "False" else True,
)
)
else:
StarletteInstrumentor().instrument_app(app)
return app
23 changes: 16 additions & 7 deletions horao/api/synchronization.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,25 @@
from horao.persistance import HoraoDecoder, init_session


@requires("peer")
@requires("authenticated_peer")
async def synchronize(request: Request) -> JSONResponse:
"""
responses:
200:
description: synchronize logical infrastructures.
examples:
{"LogicalInfrastructure": b"ASc3dGJhcg=="}
{ "LogicalInfrastructure": {
"type": "LogicalInfrastructure",
"infrastructure": {},
"constraints": {},
"claims": {},
}}
400:
description: Error parsing request
403:
description: Unauthorized
500:
description: Error synchronizing
"""
logging.debug(f"Calling Synchronize ({request})")
try:
Expand All @@ -31,17 +40,17 @@ async def synchronize(request: Request) -> JSONResponse:
logical_infrastructure = json.loads(data, cls=HoraoDecoder)
session = init_session()
for k, v in logical_infrastructure.infrastructure.items():
local_dc = session.load(k.name)
local_dc = session.load(k.id)
if not local_dc:
session.save(k.name, k)
session.save(k.id, k)
else:
local_dc.merge(k)
local_dc_content = session.load(f"{k.name}.content")
local_dc_content = session.load(f"{k.id}.content")
if not local_dc_content:
session.save(f"{k.name}.content", v)
session.save(f"{k.id}.content", v)
else:
local_dc_content.merge(v)
except Exception as e:
logging.error(f"Error synchronizing: {e}")
return JSONResponse(status_code=400, content={"error": "Error synchronizing"})
return JSONResponse(status_code=500, content={"error": "Error synchronizing"})
return JSONResponse(status_code=200, content={"status": "is alive"})
3 changes: 2 additions & 1 deletion horao/auth/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@
only contains authorization. The RBAC module is used to define the roles and permissions of the users. There are
various implementations that can be used, but some are only meant for development purpose.
"""
from horao.auth.basic_auth import BasicAuthBackend
from horao.auth.basic import BasicAuthBackend
from horao.auth.peer import PeerAuthBackend, Peer
File renamed without changes.
134 changes: 47 additions & 87 deletions horao/auth/peer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,123 +3,83 @@
Digest authentication using pre-shared key.
"""
import base64
import binascii
import logging
import os
from typing import Tuple
from typing import Tuple, Union

from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
import jwt
from starlette.authentication import (
AuthCredentials,
AuthenticationBackend,
AuthenticationError,
SimpleUser,
BaseUser,
)


def encode(text: str, secret: str, salt: bytes) -> bytes:
"""
This function returns a digest auth token for the given text and secret
:param text: text
:param secret: secret
:param salt: salt
:return: token
"""
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterations=390000,
)
key = base64.urlsafe_b64encode(kdf.derive(secret.encode("utf-8")))
fernet = Fernet(key)
enc_text = fernet.encrypt(text.encode("utf-8"))
return enc_text
class Peer(BaseUser):
def __init__(self, id: str, token: str, payload, origin: str) -> None:
self.id = id
self.token = token
self.payload = payload
self.origin = origin

@property
def is_authenticated(self) -> bool:
return True

def decode(token: bytes, secret: str, salt: bytes) -> str:
"""
This function returns a digest auth token for the given text and secret
:param token: token
:param secret: secret
:param salt: salt
:return: text
"""
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterations=390000,
)
key = base64.urlsafe_b64encode(kdf.derive(secret.encode("utf-8")))
fernet = Fernet(key)
return fernet.decrypt(token).decode("utf-8")
@property
def display_name(self) -> str:
return self.origin

@property
def identity(self) -> str:
return self.id

def generate_digest(username: str, password: str, secret: str, salt: bytes) -> bytes:
"""
This function returns a digest auth token for the given username, password, secret and salt
:param username: username
:param password: password
:param secret: secret
:param salt: salt
:return: bytes
"""
return encode(f"{username}{password}", secret, salt)
def is_true(self) -> bool:
return self.id == self.origin


def extract_username_password(
token: bytes, secret: str, salt: bytes
) -> Tuple[str, str]:
"""
This function returns the username and password from the token
:param token: token
:param secret: secret
:param salt: salt
:return: typle of username and password
"""
result = decode(token, secret, salt)
return (
result[0 : len(result) - len(os.getenv("PEER_KEY"))], # type: ignore
result[len(result) - len(os.getenv("PEER_KEY")) :], # type: ignore
)
def __str__(self) -> str:
return f"{self.origin} -> {self.id}"


class PeerAuthBackend(AuthenticationBackend):
logger = logging.getLogger(__name__)

async def authenticate(self, conn):
async def authenticate(self, conn) -> Union[None, Tuple[AuthCredentials, BaseUser]]:
if "Authorization" not in conn.headers:
return
return None
if "PEERS" in os.environ:
return None
if "PEER_SECRET" not in os.environ:
return None

auth = conn.headers["Authorization"]
try:
scheme, credentials = auth.split()
if scheme.lower() != "digest":
return
scheme, token = auth.split()
if scheme.lower() != "jwt":
return None

peer_match_source = False
for peer in os.getenv("PEERS").split(","):
for peer in os.getenv("PEERS").split(","): # type: ignore
if peer in conn.client.host:
self.logger.debug(f"Peer {peer} is trying to authenticate")
peer_match_source = True
if not peer_match_source and os.getenv("PEER_STRICT", "True") == "True":
raise AuthenticationError(f"access not allowed for {conn.client.host}")
username, password = extract_username_password(
base64.b64decode(credentials),
os.getenv("PEER_SECRET"),
bytes.fromhex(os.getenv("PEER_SALT")),
payload = jwt.decode(token, os.getenv("PEER_SECRET"), algorithms=["HS256"]) # type: ignore
self.logger.debug(f"valid token for {payload['peer']}")
return AuthCredentials(["authenticated_peer"]), Peer(
id=payload["peer"],
token=token,
payload=payload,
origin=conn.client.host,
)
for peer in os.getenv("PEERS").split(","):
if peer == username and password == os.getenv("PEER_KEY"):
if not peer_match_source:
self.logger.warning(
f"Peer {peer} authenticated from different source {conn.client.host}"
)
return AuthCredentials(["peer"]), SimpleUser(peer)
except (ValueError, UnicodeDecodeError, binascii.Error) as exc:
self.logger.error(f"Invalid digest credentials for peer ({exc})")
except (
ValueError,
UnicodeDecodeError,
jwt.InvalidTokenError,
binascii.Error,
) as exc:
self.logger.error(f"Invalid token for peer ({exc})")
raise AuthenticationError(f"access not allowed for {conn.client.host}")
raise AuthenticationError("access not allowed")
2 changes: 1 addition & 1 deletion horao/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def main():
os.environ["DEBUG"] = "True"
os.environ["UI"] = "True"
uvicorn.run(
"horao:init_api",
"horao:init",
host="127.0.0.1",
port=8081,
log_level="debug",
Expand Down
Loading

0 comments on commit 2b3a791

Please sign in to comment.