2022-12-21 09:41:20 +00:00
import logging
2022-12-20 13:55:07 +00:00
from base64 import b64encode as b64enc
2024-06-13 18:18:18 +00:00
from calendar import timegm
2024-06-13 18:34:27 +00:00
from contextlib import asynccontextmanager
2024-06-13 18:18:18 +00:00
from datetime import datetime , timedelta
2022-12-16 12:51:14 +00:00
from hashlib import sha256
2024-06-13 18:18:18 +00:00
from json import loads as json_loads
2022-12-28 10:53:56 +00:00
from os import getenv as env
2024-06-13 18:18:18 +00:00
from os . path import join , dirname
from uuid import uuid4
2022-12-21 09:41:20 +00:00
2024-06-13 18:18:18 +00:00
from dateutil . relativedelta import relativedelta
2022-12-21 09:53:51 +00:00
from dotenv import load_dotenv
2023-01-04 09:04:52 +00:00
from fastapi import FastAPI
2022-12-16 12:51:14 +00:00
from fastapi . requests import Request
2023-01-04 09:04:52 +00:00
from jose import jws , jwk , jwt , JWTError
2022-12-16 12:51:14 +00:00
from jose . constants import ALGORITHMS
2022-12-22 11:57:06 +00:00
from sqlalchemy import create_engine
from sqlalchemy . orm import sessionmaker
2024-06-13 18:18:18 +00:00
from starlette . middleware . cors import CORSMiddleware
from starlette . responses import StreamingResponse , JSONResponse as JSONr , HTMLResponse as HTMLr , Response , RedirectResponse
2022-12-27 11:21:52 +00:00
2023-06-12 13:19:06 +00:00
from orm import init as db_init , migrate , Site , Instance , Origin , Lease
2022-12-22 11:57:06 +00:00
2024-06-13 18:34:45 +00:00
# Load variables
2022-12-21 10:06:09 +00:00
load_dotenv ( ' ../version.env ' )
2022-12-21 09:40:05 +00:00
2024-06-13 18:34:45 +00:00
# Get current timezone
2023-01-18 13:23:25 +00:00
TZ = datetime . now ( ) . astimezone ( ) . tzinfo
2024-06-13 18:34:45 +00:00
# Load basic variables
2022-12-28 10:53:56 +00:00
VERSION , COMMIT , DEBUG = env ( ' VERSION ' , ' unknown ' ) , env ( ' COMMIT ' , ' unknown ' ) , bool ( env ( ' DEBUG ' , False ) )
2022-12-21 09:40:05 +00:00
2024-06-13 18:34:45 +00:00
# Database connection
2022-12-28 10:53:56 +00:00
db = create_engine ( str ( env ( ' DATABASE ' , ' sqlite:///db.sqlite ' ) ) )
2022-12-29 08:40:36 +00:00
db_init ( db ) , migrate ( db )
2022-12-16 12:51:14 +00:00
2024-06-13 18:34:45 +00:00
# Load DLS variables (all prefixed with "INSTANCE_*" is used as "SERVICE_INSTANCE_*" or "SI_*" in official dls service)
2022-12-28 10:53:56 +00:00
DLS_URL = str ( env ( ' DLS_URL ' , ' localhost ' ) )
DLS_PORT = int ( env ( ' DLS_PORT ' , ' 443 ' ) )
2022-12-30 06:42:57 +00:00
CORS_ORIGINS = str ( env ( ' CORS_ORIGINS ' , ' ' ) ) . split ( ' , ' ) if ( env ( ' CORS_ORIGINS ' ) ) else [ f ' https:// { DLS_URL } ' ]
2022-12-21 09:41:20 +00:00
2023-06-12 13:19:06 +00:00
ALLOTMENT_REF = str ( env ( ' ALLOTMENT_REF ' , ' 20000000-0000-0000-0000-000000000001 ' ) ) # todo
2022-12-20 13:55:07 +00:00
2024-06-13 18:34:45 +00:00
2024-06-13 18:34:27 +00:00
# FastAPI
@asynccontextmanager
async def lifespan ( _ : FastAPI ) :
# on startup
2024-06-21 17:28:23 +00:00
default_instance = Instance . get_default_instance ( db )
lease_renewal_period = default_instance . lease_renewal_period
lease_renewal_delta = default_instance . get_lease_renewal_delta ( )
client_token_expire_delta = default_instance . get_client_token_expire_delta ( )
2024-06-13 18:34:27 +00:00
logger . info ( f '''
Using timezone : { str ( TZ ) } . Make sure this is correct and match your clients !
2024-06-21 17:28:23 +00:00
Your clients will renew their license every { str ( Lease . calculate_renewal ( lease_renewal_period , lease_renewal_delta ) ) } .
If the renewal fails , the license is valid for { str ( lease_renewal_delta ) } .
2024-06-13 18:34:27 +00:00
2024-06-21 17:28:23 +00:00
Your client - token file ( . tok ) is valid for { str ( client_token_expire_delta ) } .
2024-06-13 18:34:27 +00:00
''' )
logger . info ( f ' Debug is { " enabled " if DEBUG else " disabled " } . ' )
2024-06-21 17:28:23 +00:00
validate_settings ( )
2024-06-13 18:34:27 +00:00
yield
# on shutdown
logger . info ( f ' Shutting down ... ' )
2024-06-13 18:34:45 +00:00
config = dict ( openapi_url = None , docs_url = None , redoc_url = None ) # dict(openapi_url='/-/openapi.json', docs_url='/-/docs', redoc_url='/-/redoc')
app = FastAPI ( title = ' FastAPI-DLS ' , description = ' Minimal Delegated License Service (DLS). ' , version = VERSION , lifespan = lifespan , * * config )
2022-12-21 09:41:20 +00:00
app . debug = DEBUG
app . add_middleware (
CORSMiddleware ,
allow_origins = CORS_ORIGINS ,
allow_credentials = True ,
2022-12-29 19:40:42 +00:00
allow_methods = [ ' * ' ] ,
allow_headers = [ ' * ' ] ,
2022-12-21 09:41:20 +00:00
)
2024-06-13 18:34:45 +00:00
# Logging
2024-06-13 18:16:51 +00:00
LOG_LEVEL = logging . DEBUG if DEBUG else logging . INFO
2024-06-21 16:59:23 +00:00
logging . basicConfig ( format = ' [ {levelname:^7} ] [ {module:^15} ] {message} ' , style = ' { ' )
2023-01-18 13:23:25 +00:00
logger = logging . getLogger ( __name__ )
2024-06-13 18:16:51 +00:00
logger . setLevel ( LOG_LEVEL )
logging . getLogger ( ' util ' ) . setLevel ( LOG_LEVEL )
2024-06-21 17:01:33 +00:00
logging . getLogger ( ' NV ' ) . setLevel ( LOG_LEVEL )
2022-12-21 09:41:20 +00:00
2022-12-16 12:51:14 +00:00
2024-06-13 18:34:45 +00:00
# Helper
2022-12-29 19:42:40 +00:00
def __get_token ( request : Request ) - > dict :
2022-12-29 19:40:42 +00:00
authorization_header = request . headers . get ( ' authorization ' )
2022-12-20 17:06:32 +00:00
token = authorization_header . split ( ' ' ) [ 1 ]
2022-12-21 07:00:52 +00:00
return jwt . decode ( token = token , key = jwt_decode_key , algorithms = ALGORITHMS . RS256 , options = { ' verify_aud ' : False } )
2022-12-21 09:41:20 +00:00
2022-12-16 12:51:14 +00:00
2023-06-12 13:19:06 +00:00
def validate_settings ( ) :
session = sessionmaker ( bind = db ) ( )
lease_expire_delta_min , lease_expire_delta_max = 86_400 , 7_776_000
for instance in session . query ( Instance ) . all ( ) :
lease_expire_delta = instance . lease_expire_delta
if lease_expire_delta < 86_400 or lease_expire_delta > 7_776_000 :
logging . warning ( f ' > [ instance ]: { instance . instance_ref } : " lease_expire_delta " should be between { lease_expire_delta_min } and { lease_expire_delta_max } ' )
session . close ( )
2024-06-13 18:34:45 +00:00
# Endpoints
2022-12-20 17:06:32 +00:00
2022-12-29 19:41:02 +00:00
@app.get ( ' / ' , summary = ' Index ' )
2022-12-16 12:51:14 +00:00
async def index ( ) :
2022-12-29 09:31:25 +00:00
return RedirectResponse ( ' /-/readme ' )
2022-12-16 12:51:14 +00:00
2022-12-29 19:41:02 +00:00
@app.get ( ' /-/ ' , summary = ' * Index ' )
async def _index ( ) :
return RedirectResponse ( ' /-/readme ' )
2022-12-29 09:31:25 +00:00
@app.get ( ' /-/health ' , summary = ' * Health ' )
2023-03-20 09:06:21 +00:00
async def _health ( ) :
2023-01-04 09:04:52 +00:00
return JSONr ( { ' status ' : ' up ' } )
2023-01-02 18:14:25 +00:00
@app.get ( ' /-/config ' , summary = ' * Config ' , description = ' returns environment variables. ' )
async def _config ( ) :
2023-06-12 13:19:06 +00:00
default_site , default_instance = Site . get_default_site ( db ) , Instance . get_default_instance ( db )
2023-01-04 09:04:52 +00:00
return JSONr ( {
2023-01-02 18:23:23 +00:00
' VERSION ' : str ( VERSION ) ,
' COMMIT ' : str ( COMMIT ) ,
' DEBUG ' : str ( DEBUG ) ,
' DLS_URL ' : str ( DLS_URL ) ,
' DLS_PORT ' : str ( DLS_PORT ) ,
2023-06-12 13:19:06 +00:00
' SITE_KEY_XID ' : str ( default_site . site_key ) ,
' INSTANCE_REF ' : str ( default_instance . instance_ref ) ,
2023-01-04 09:04:52 +00:00
' ALLOTMENT_REF ' : [ str ( ALLOTMENT_REF ) ] ,
2023-06-12 13:19:06 +00:00
' TOKEN_EXPIRE_DELTA ' : str ( default_instance . get_token_expire_delta ( ) ) ,
' LEASE_EXPIRE_DELTA ' : str ( default_instance . get_lease_expire_delta ( ) ) ,
' LEASE_RENEWAL_PERIOD ' : str ( default_instance . lease_renewal_period ) ,
2023-01-02 18:23:23 +00:00
' CORS_ORIGINS ' : str ( CORS_ORIGINS ) ,
2023-01-18 13:23:25 +00:00
' TZ ' : str ( TZ ) ,
2023-01-02 18:14:25 +00:00
} )
2022-12-29 09:31:25 +00:00
@app.get ( ' /-/readme ' , summary = ' * Readme ' )
async def _readme ( ) :
from markdown import markdown
2023-06-12 10:40:10 +00:00
from util import load_file
2024-06-13 17:24:46 +00:00
content = load_file ( join ( dirname ( __file__ ) , ' ../README.md ' ) ) . decode ( ' utf-8 ' )
2023-01-04 09:04:52 +00:00
return HTMLr ( markdown ( text = content , extensions = [ ' tables ' , ' fenced_code ' , ' md_in_html ' , ' nl2br ' , ' toc ' ] ) )
2022-12-29 09:31:25 +00:00
@app.get ( ' /-/manage ' , summary = ' * Management UI ' )
2022-12-29 09:12:31 +00:00
async def _manage ( request : Request ) :
response = '''
< ! DOCTYPE html >
< html >
< head >
< title > FastAPI - DLS Management < / title >
< / head >
< body >
2023-01-04 17:12:59 +00:00
< button onclick = " deleteOrigins() " > delete ALL origins and their leases < / button >
2022-12-29 09:12:31 +00:00
< button onclick = " deleteLease() " > delete specific lease < / button >
< script >
function deleteOrigins ( ) {
2023-01-04 17:12:59 +00:00
const response = confirm ( ' Are you sure you want to delete all origins and their leases? ' ) ;
if ( response ) {
var xhr = new XMLHttpRequest ( ) ;
xhr . open ( " DELETE " , ' /-/origins ' , true ) ;
xhr . send ( ) ;
}
2022-12-29 09:12:31 +00:00
}
function deleteLease ( lease_ref ) {
if ( lease_ref == = undefined )
lease_ref = window . prompt ( " Please enter ' lease_ref ' which should be deleted " ) ;
if ( lease_ref == = null | | lease_ref == = " " )
return
var xhr = new XMLHttpRequest ( ) ;
xhr . open ( " DELETE " , ` / - / lease / $ { lease_ref } ` , true ) ;
xhr . send ( ) ;
}
< / script >
< / body >
< / html >
'''
2023-01-04 09:04:52 +00:00
return HTMLr ( response )
2022-12-29 09:12:31 +00:00
2022-12-29 09:35:15 +00:00
@app.get ( ' /-/origins ' , summary = ' * Origins ' )
2022-12-29 08:00:52 +00:00
async def _origins ( request : Request , leases : bool = False ) :
2022-12-23 12:21:52 +00:00
session = sessionmaker ( bind = db ) ( )
2022-12-29 08:00:52 +00:00
response = [ ]
for origin in session . query ( Origin ) . all ( ) :
x = origin . serialize ( )
if leases :
2023-06-12 13:19:06 +00:00
x [ ' leases ' ] = list ( map ( lambda _ : _ . serialize ( ) , Lease . find_by_origin_ref ( db , origin . origin_ref ) ) )
2022-12-29 08:00:52 +00:00
response . append ( x )
2022-12-22 11:57:06 +00:00
session . close ( )
2023-01-04 09:04:52 +00:00
return JSONr ( response )
2022-12-20 17:24:59 +00:00
2022-12-29 09:35:15 +00:00
@app.delete ( ' /-/origins ' , summary = ' * Origins ' )
2022-12-29 08:57:37 +00:00
async def _origins_delete ( request : Request ) :
Origin . delete ( db )
return Response ( status_code = 201 )
2022-12-29 09:35:15 +00:00
@app.get ( ' /-/leases ' , summary = ' * Leases ' )
2022-12-29 08:00:52 +00:00
async def _leases ( request : Request , origin : bool = False ) :
2022-12-23 12:21:52 +00:00
session = sessionmaker ( bind = db ) ( )
2022-12-29 08:00:52 +00:00
response = [ ]
for lease in session . query ( Lease ) . all ( ) :
2023-06-12 13:19:06 +00:00
x = lease . serialize ( )
2022-12-29 08:00:52 +00:00
if origin :
2023-01-17 10:18:07 +00:00
lease_origin = session . query ( Origin ) . filter ( Origin . origin_ref == lease . origin_ref ) . first ( )
if lease_origin is not None :
x [ ' origin ' ] = lease_origin . serialize ( )
2022-12-29 08:00:52 +00:00
response . append ( x )
2022-12-22 11:57:06 +00:00
session . close ( )
2023-01-04 09:04:52 +00:00
return JSONr ( response )
2022-12-20 17:24:59 +00:00
2023-06-12 08:48:00 +00:00
@app.delete ( ' /-/leases/expired ' , summary = ' * Leases ' )
async def _lease_delete_expired ( request : Request ) :
Lease . delete_expired ( db )
return Response ( status_code = 201 )
2022-12-29 09:35:15 +00:00
@app.delete ( ' /-/lease/ {lease_ref} ' , summary = ' * Lease ' )
2022-12-29 08:57:37 +00:00
async def _lease_delete ( request : Request , lease_ref : str ) :
if Lease . delete ( db , lease_ref ) == 1 :
return Response ( status_code = 201 )
2023-01-04 09:04:52 +00:00
return JSONr ( status_code = 404 , content = { ' status ' : 404 , ' detail ' : ' lease not found ' } )
2022-12-29 08:57:37 +00:00
2022-12-16 12:51:14 +00:00
# venv/lib/python3.9/site-packages/nls_core_service_instance/service_instance_token_manager.py
2022-12-29 19:33:50 +00:00
@app.get ( ' /-/client-token ' , summary = ' * Client-Token ' , description = ' creates a new messenger token for this service instance ' )
async def _client_token ( ) :
2022-12-16 12:51:14 +00:00
cur_time = datetime . utcnow ( )
2023-06-12 13:19:06 +00:00
default_instance = Instance . get_default_instance ( db )
public_key = default_instance . get_public_key ( )
# todo: implemented request parameter to support different instances
jwt_encode_key = default_instance . get_jwt_encode_key ( )
exp_time = cur_time + default_instance . get_client_token_expire_delta ( )
2022-12-19 14:51:49 +00:00
2022-12-16 12:51:14 +00:00
payload = {
" jti " : str ( uuid4 ( ) ) ,
" iss " : " NLS Service Instance " ,
" aud " : " NLS Licensed Client " ,
2022-12-19 12:52:16 +00:00
" iat " : timegm ( cur_time . timetuple ( ) ) ,
" nbf " : timegm ( cur_time . timetuple ( ) ) ,
" exp " : timegm ( exp_time . timetuple ( ) ) ,
2022-12-16 12:51:14 +00:00
" update_mode " : " ABSOLUTE " ,
2023-01-03 12:05:05 +00:00
" scope_ref_list " : [ ALLOTMENT_REF ] ,
2022-12-16 12:51:14 +00:00
" fulfillment_class_ref_list " : [ ] ,
" service_instance_configuration " : {
2023-06-12 13:19:06 +00:00
" nls_service_instance_ref " : default_instance . instance_ref ,
2022-12-16 12:51:14 +00:00
" svc_port_set_list " : [
{
" idx " : 0 ,
" d_name " : " DLS " ,
2022-12-20 17:06:32 +00:00
" svc_port_map " : [ { " service " : " auth " , " port " : DLS_PORT } , { " service " : " lease " , " port " : DLS_PORT } ]
2022-12-16 12:51:14 +00:00
}
] ,
2022-12-19 13:44:26 +00:00
" node_url_list " : [ { " idx " : 0 , " url " : DLS_URL , " url_qr " : DLS_URL , " svc_port_set_idx " : 0 } ]
2022-12-16 12:51:14 +00:00
} ,
2022-12-23 12:22:06 +00:00
" service_instance_public_key_configuration " : {
" service_instance_public_key_me " : {
2023-06-12 13:19:06 +00:00
" mod " : hex ( public_key . public_key ( ) . n ) [ 2 : ] ,
" exp " : int ( public_key . public_key ( ) . e ) ,
2022-12-23 12:22:06 +00:00
} ,
2023-06-12 13:19:06 +00:00
" service_instance_public_key_pem " : public_key . export_key ( ) . decode ( ' utf-8 ' ) ,
2022-12-23 12:22:06 +00:00
" key_retention_mode " : " LATEST_ONLY "
} ,
2022-12-16 12:51:14 +00:00
}
2022-12-21 07:00:52 +00:00
content = jws . sign ( payload , key = jwt_encode_key , headers = None , algorithm = ALGORITHMS . RS256 )
2022-12-16 12:51:14 +00:00
2022-12-20 17:06:32 +00:00
response = StreamingResponse ( iter ( [ content ] ) , media_type = " text/plain " )
2022-12-30 02:50:48 +00:00
filename = f ' client_configuration_token_ { datetime . now ( ) . strftime ( " %d - % m- % y- % H- % M- % S " ) } .tok '
2022-12-19 21:20:50 +00:00
response . headers [ " Content-Disposition " ] = f ' attachment; filename= { filename } '
2022-12-20 17:06:32 +00:00
2022-12-16 12:51:14 +00:00
return response
# venv/lib/python3.9/site-packages/nls_services_auth/test/test_origins_controller.py
2022-12-29 17:48:30 +00:00
@app.post ( ' /auth/v1/origin ' , description = ' find or create an origin ' )
2022-12-21 07:00:52 +00:00
async def auth_v1_origin ( request : Request ) :
2023-01-04 09:04:52 +00:00
j , cur_time = json_loads ( ( await request . body ( ) ) . decode ( ' utf-8 ' ) ) , datetime . utcnow ( )
2022-12-20 13:55:07 +00:00
2023-01-03 08:20:18 +00:00
origin_ref = j . get ( ' candidate_origin_ref ' )
2022-12-21 09:45:45 +00:00
logging . info ( f ' > [ origin ]: { origin_ref } : { j } ' )
2022-12-20 13:55:07 +00:00
2022-12-22 11:57:06 +00:00
data = Origin (
2022-12-20 17:06:32 +00:00
origin_ref = origin_ref ,
2023-01-03 08:20:18 +00:00
hostname = j . get ( ' environment ' ) . get ( ' hostname ' ) ,
guest_driver_version = j . get ( ' environment ' ) . get ( ' guest_driver_version ' ) ,
os_platform = j . get ( ' environment ' ) . get ( ' os_platform ' ) , os_version = j . get ( ' environment ' ) . get ( ' os_version ' ) ,
2022-12-20 13:55:07 +00:00
)
2022-12-20 17:06:32 +00:00
2022-12-22 11:57:06 +00:00
Origin . create_or_update ( db , data )
2022-12-16 12:51:14 +00:00
response = {
2022-12-20 17:06:32 +00:00
" origin_ref " : origin_ref ,
2023-01-03 08:20:18 +00:00
" environment " : j . get ( ' environment ' ) ,
2022-12-16 12:51:14 +00:00
" svc_port_set_list " : None ,
" node_url_list " : None ,
" node_query_order " : None ,
" prompts " : None ,
2022-12-20 05:47:40 +00:00
" sync_timestamp " : cur_time . isoformat ( )
2022-12-16 12:51:14 +00:00
}
2022-12-20 17:06:32 +00:00
2023-01-04 09:04:52 +00:00
return JSONr ( response )
2022-12-16 12:51:14 +00:00
2022-12-27 18:03:03 +00:00
# venv/lib/python3.9/site-packages/nls_services_auth/test/test_origins_controller.py
2022-12-29 17:48:30 +00:00
@app.post ( ' /auth/v1/origin/update ' , description = ' update an origin evidence ' )
2022-12-27 18:03:03 +00:00
async def auth_v1_origin_update ( request : Request ) :
2023-01-04 09:04:52 +00:00
j , cur_time = json_loads ( ( await request . body ( ) ) . decode ( ' utf-8 ' ) ) , datetime . utcnow ( )
2022-12-27 18:03:03 +00:00
2023-01-03 08:20:18 +00:00
origin_ref = j . get ( ' origin_ref ' )
2022-12-27 18:03:03 +00:00
logging . info ( f ' > [ update ]: { origin_ref } : { j } ' )
2022-12-27 18:05:41 +00:00
data = Origin (
2022-12-27 18:03:03 +00:00
origin_ref = origin_ref ,
2023-01-03 08:20:18 +00:00
hostname = j . get ( ' environment ' ) . get ( ' hostname ' ) ,
guest_driver_version = j . get ( ' environment ' ) . get ( ' guest_driver_version ' ) ,
os_platform = j . get ( ' environment ' ) . get ( ' os_platform ' ) , os_version = j . get ( ' environment ' ) . get ( ' os_version ' ) ,
2022-12-27 18:03:03 +00:00
)
2022-12-27 18:05:41 +00:00
Origin . create_or_update ( db , data )
2022-12-27 18:03:03 +00:00
response = {
2023-01-03 08:20:18 +00:00
" environment " : j . get ( ' environment ' ) ,
2022-12-27 18:03:03 +00:00
" prompts " : None ,
" sync_timestamp " : cur_time . isoformat ( )
}
2023-01-04 09:04:52 +00:00
return JSONr ( response )
2022-12-27 18:03:03 +00:00
2022-12-16 12:51:14 +00:00
# venv/lib/python3.9/site-packages/nls_services_auth/test/test_auth_controller.py
# venv/lib/python3.9/site-packages/nls_core_auth/auth.py - CodeResponse
2022-12-29 17:48:30 +00:00
@app.post ( ' /auth/v1/code ' , description = ' get an authorization code ' )
2022-12-21 07:00:52 +00:00
async def auth_v1_code ( request : Request ) :
2023-01-04 09:04:52 +00:00
j , cur_time = json_loads ( ( await request . body ( ) ) . decode ( ' utf-8 ' ) ) , datetime . utcnow ( )
2022-12-20 13:55:07 +00:00
2023-01-03 08:20:18 +00:00
origin_ref = j . get ( ' origin_ref ' )
2022-12-21 09:45:45 +00:00
logging . info ( f ' > [ code ]: { origin_ref } : { j } ' )
2022-12-16 12:51:14 +00:00
2022-12-20 17:06:32 +00:00
delta = relativedelta ( minutes = 15 )
expires = cur_time + delta
2022-12-16 12:51:14 +00:00
2023-06-12 13:19:06 +00:00
default_site = Site . get_default_site ( db )
jwt_encode_key = Instance . get_default_instance ( db ) . get_jwt_encode_key ( )
2022-12-16 12:51:14 +00:00
payload = {
' iat ' : timegm ( cur_time . timetuple ( ) ) ,
' exp ' : timegm ( expires . timetuple ( ) ) ,
2023-01-03 08:20:18 +00:00
' challenge ' : j . get ( ' code_challenge ' ) ,
' origin_ref ' : j . get ( ' origin_ref ' ) ,
2023-06-12 13:19:06 +00:00
' key_ref ' : default_site . site_key ,
' kid ' : default_site . site_key ,
2022-12-16 12:51:14 +00:00
}
2022-12-21 07:00:52 +00:00
auth_code = jws . sign ( payload , key = jwt_encode_key , headers = { ' kid ' : payload . get ( ' kid ' ) } , algorithm = ALGORITHMS . RS256 )
2022-12-16 12:51:14 +00:00
response = {
" auth_code " : auth_code ,
2022-12-20 05:47:40 +00:00
" sync_timestamp " : cur_time . isoformat ( ) ,
2022-12-16 12:51:14 +00:00
" prompts " : None
}
2022-12-20 17:06:32 +00:00
2023-01-04 09:04:52 +00:00
return JSONr ( response )
2022-12-16 12:51:14 +00:00
# venv/lib/python3.9/site-packages/nls_services_auth/test/test_auth_controller.py
# venv/lib/python3.9/site-packages/nls_core_auth/auth.py - TokenResponse
2022-12-29 17:48:30 +00:00
@app.post ( ' /auth/v1/token ' , description = ' exchange auth code and verifier for token ' )
2022-12-21 07:00:52 +00:00
async def auth_v1_token ( request : Request ) :
2023-01-04 09:04:52 +00:00
j , cur_time = json_loads ( ( await request . body ( ) ) . decode ( ' utf-8 ' ) ) , datetime . utcnow ( )
2023-06-12 13:19:06 +00:00
default_site , default_instance = Site . get_default_site ( db ) , Instance . get_default_instance ( db )
jwt_encode_key , jwt_decode_key = default_instance . get_jwt_encode_key ( ) , default_instance . get_jwt_decode_key ( )
2023-01-04 09:04:52 +00:00
try :
2023-06-12 13:19:06 +00:00
payload = jwt . decode ( token = j . get ( ' auth_code ' ) , key = jwt_decode_key , algorithms = [ ALGORITHMS . RS256 ] )
2023-01-04 09:04:52 +00:00
except JWTError as e :
return JSONr ( status_code = 400 , content = { ' status ' : 400 , ' title ' : ' invalid token ' , ' detail ' : str ( e ) } )
2022-12-16 12:51:14 +00:00
2023-01-03 08:20:18 +00:00
origin_ref = payload . get ( ' origin_ref ' )
2022-12-23 05:56:29 +00:00
logging . info ( f ' > [ auth ]: { origin_ref } : { j } ' )
2022-12-16 12:51:14 +00:00
# validate the code challenge
2023-01-04 09:04:52 +00:00
challenge = b64enc ( sha256 ( j . get ( ' code_verifier ' ) . encode ( ' utf-8 ' ) ) . digest ( ) ) . rstrip ( b ' = ' ) . decode ( ' utf-8 ' )
if payload . get ( ' challenge ' ) != challenge :
2023-01-04 09:14:00 +00:00
return JSONr ( status_code = 401 , content = { ' status ' : 401 , ' detail ' : ' expected challenge did not match verifier ' } )
2022-12-16 12:51:14 +00:00
2023-06-12 13:19:06 +00:00
access_expires_on = cur_time + default_instance . get_token_expire_delta ( )
2022-12-16 12:51:14 +00:00
new_payload = {
' iat ' : timegm ( cur_time . timetuple ( ) ) ,
' nbf ' : timegm ( cur_time . timetuple ( ) ) ,
' iss ' : ' https://cls.nvidia.org ' ,
' aud ' : ' https://cls.nvidia.org ' ,
' exp ' : timegm ( access_expires_on . timetuple ( ) ) ,
2022-12-23 05:56:29 +00:00
' origin_ref ' : origin_ref ,
2023-06-12 13:19:06 +00:00
' key_ref ' : default_site . site_key ,
' kid ' : default_site . site_key ,
2022-12-16 12:51:14 +00:00
}
2022-12-21 07:00:52 +00:00
auth_token = jwt . encode ( new_payload , key = jwt_encode_key , headers = { ' kid ' : payload . get ( ' kid ' ) } , algorithm = ALGORITHMS . RS256 )
2022-12-16 12:51:14 +00:00
response = {
2022-12-20 05:47:40 +00:00
" expires " : access_expires_on . isoformat ( ) ,
2022-12-16 12:51:14 +00:00
" auth_token " : auth_token ,
2022-12-20 05:47:40 +00:00
" sync_timestamp " : cur_time . isoformat ( ) ,
2022-12-16 12:51:14 +00:00
}
2023-01-04 09:04:52 +00:00
return JSONr ( response )
2022-12-16 12:51:14 +00:00
2023-01-02 17:10:11 +00:00
# venv/lib/python3.9/site-packages/nls_services_lease/test/test_lease_multi_controller.py
2022-12-29 17:48:30 +00:00
@app.post ( ' /leasing/v1/lessor ' , description = ' request multiple leases (borrow) for current origin ' )
2022-12-21 07:00:52 +00:00
async def leasing_v1_lessor ( request : Request ) :
2023-06-12 13:19:06 +00:00
j , cur_time = json_loads ( ( await request . body ( ) ) . decode ( ' utf-8 ' ) ) , datetime . utcnow ( )
default_instance = Instance . get_default_instance ( db )
jwt_decode_key = default_instance . get_jwt_decode_key ( )
2023-01-04 09:04:52 +00:00
try :
2023-06-12 13:19:06 +00:00
token = __get_token ( request , jwt_decode_key )
2023-01-04 09:04:52 +00:00
except JWTError :
return JSONr ( status_code = 401 , content = { ' status ' : 401 , ' detail ' : ' token is not valid ' } )
2022-12-20 13:55:07 +00:00
2022-12-29 17:59:26 +00:00
origin_ref = token . get ( ' origin_ref ' )
2023-01-03 08:20:18 +00:00
scope_ref_list = j . get ( ' scope_ref_list ' )
2022-12-23 05:56:29 +00:00
logging . info ( f ' > [ create ]: { origin_ref } : create leases for scope_ref_list { scope_ref_list } ' )
2022-12-16 12:51:14 +00:00
lease_result_list = [ ]
2022-12-20 13:55:07 +00:00
for scope_ref in scope_ref_list :
2023-01-03 13:09:19 +00:00
# if scope_ref not in [ALLOTMENT_REF]:
2023-01-04 09:14:00 +00:00
# return JSONr(status_code=500, detail=f'no service instances found for scopes: ["{scope_ref}"]')
2023-01-03 12:05:05 +00:00
lease_ref = str ( uuid4 ( ) )
2023-06-12 13:19:06 +00:00
expires = cur_time + default_instance . get_lease_expire_delta ( )
2022-12-16 12:51:14 +00:00
lease_result_list . append ( {
" ordinal " : 0 ,
2022-12-20 17:06:32 +00:00
# https://docs.nvidia.com/license-system/latest/nvidia-license-system-user-guide/index.html
2022-12-16 12:51:14 +00:00
" lease " : {
2023-01-03 12:05:05 +00:00
" ref " : lease_ref ,
2022-12-20 05:47:40 +00:00
" created " : cur_time . isoformat ( ) ,
2022-12-20 17:06:32 +00:00
" expires " : expires . isoformat ( ) ,
2023-06-12 13:19:06 +00:00
" recommended_lease_renewal " : default_instance . lease_renewal_period ,
2022-12-16 12:51:14 +00:00
" offline_lease " : " true " ,
" license_type " : " CONCURRENT_COUNTED_SINGLE "
}
} )
2022-12-20 17:06:32 +00:00
2023-06-12 13:19:06 +00:00
data = Lease ( instance_ref = default_instance . instance_ref , origin_ref = origin_ref , lease_ref = lease_ref , lease_created = cur_time , lease_expires = expires )
2022-12-22 11:57:06 +00:00
Lease . create_or_update ( db , data )
2022-12-16 12:51:14 +00:00
response = {
" lease_result_list " : lease_result_list ,
" result_code " : " SUCCESS " ,
2022-12-20 05:47:40 +00:00
" sync_timestamp " : cur_time . isoformat ( ) ,
2022-12-16 12:51:14 +00:00
" prompts " : None
}
2023-01-04 09:04:52 +00:00
return JSONr ( response )
2022-12-16 12:51:14 +00:00
# venv/lib/python3.9/site-packages/nls_services_lease/test/test_lease_multi_controller.py
2022-12-20 17:06:32 +00:00
# venv/lib/python3.9/site-packages/nls_dal_service_instance_dls/schema/service_instance/V1_0_21__product_mapping.sql
2022-12-29 17:48:30 +00:00
@app.get ( ' /leasing/v1/lessor/leases ' , description = ' get active leases for current origin ' )
2022-12-21 07:00:52 +00:00
async def leasing_v1_lessor_lease ( request : Request ) :
2023-06-12 13:19:06 +00:00
cur_time = datetime . utcnow ( )
jwt_decode_key = Instance . get_default_instance ( db ) . get_jwt_decode_key ( )
try :
token = __get_token ( request , jwt_decode_key )
except JWTError :
return JSONr ( status_code = 401 , content = { ' status ' : 401 , ' detail ' : ' token is not valid ' } )
2022-12-20 13:55:07 +00:00
2022-12-29 17:59:26 +00:00
origin_ref = token . get ( ' origin_ref ' )
2022-12-20 13:55:07 +00:00
2022-12-27 19:05:55 +00:00
active_lease_list = list ( map ( lambda x : x . lease_ref , Lease . find_by_origin_ref ( db , origin_ref ) ) )
2022-12-23 05:56:29 +00:00
logging . info ( f ' > [ leases ]: { origin_ref } : found { len ( active_lease_list ) } active leases ' )
2022-12-20 13:55:07 +00:00
2022-12-16 12:51:14 +00:00
response = {
2022-12-20 13:55:07 +00:00
" active_lease_list " : active_lease_list ,
2022-12-20 05:47:40 +00:00
" sync_timestamp " : cur_time . isoformat ( ) ,
2022-12-16 12:51:14 +00:00
" prompts " : None
}
2023-01-04 09:04:52 +00:00
return JSONr ( response )
2022-12-16 12:51:14 +00:00
2023-01-02 17:10:11 +00:00
# venv/lib/python3.9/site-packages/nls_services_lease/test/test_lease_single_controller.py
2022-12-19 12:15:19 +00:00
# venv/lib/python3.9/site-packages/nls_core_lease/lease_single.py
2022-12-29 17:48:30 +00:00
@app.put ( ' /leasing/v1/lease/ {lease_ref} ' , description = ' renew a lease ' )
2022-12-21 07:00:52 +00:00
async def leasing_v1_lease_renew ( request : Request , lease_ref : str ) :
2023-06-12 13:19:06 +00:00
cur_time = datetime . utcnow ( )
default_instance = Instance . get_default_instance ( db )
jwt_decode_key = default_instance . get_jwt_decode_key ( )
try :
token = __get_token ( request , jwt_decode_key )
except JWTError :
return JSONr ( status_code = 401 , content = { ' status ' : 401 , ' detail ' : ' token is not valid ' } )
2022-12-20 13:55:07 +00:00
2022-12-29 17:59:26 +00:00
origin_ref = token . get ( ' origin_ref ' )
2022-12-23 05:56:29 +00:00
logging . info ( f ' > [ renew ]: { origin_ref } : renew { lease_ref } ' )
2022-12-20 17:06:32 +00:00
2022-12-22 11:57:06 +00:00
entity = Lease . find_by_origin_ref_and_lease_ref ( db , origin_ref , lease_ref )
if entity is None :
2023-01-04 09:04:52 +00:00
return JSONr ( status_code = 404 , content = { ' status ' : 404 , ' detail ' : ' requested lease not available ' } )
2022-12-19 12:15:19 +00:00
2023-06-12 13:19:06 +00:00
expires = cur_time + default_instance . get_lease_expire_delta ( )
2022-12-19 12:15:19 +00:00
response = {
" lease_ref " : lease_ref ,
2022-12-20 13:55:07 +00:00
" expires " : expires . isoformat ( ) ,
2023-06-12 13:19:06 +00:00
" recommended_lease_renewal " : default_instance . lease_renewal_period ,
2022-12-19 12:15:19 +00:00
" offline_lease " : True ,
" prompts " : None ,
2022-12-20 05:47:40 +00:00
" sync_timestamp " : cur_time . isoformat ( ) ,
2022-12-19 12:15:19 +00:00
}
2022-12-22 11:57:06 +00:00
Lease . renew ( db , entity , expires , cur_time )
2022-12-20 13:55:07 +00:00
2023-01-04 09:04:52 +00:00
return JSONr ( response )
2022-12-19 12:15:19 +00:00
2023-01-02 17:10:11 +00:00
# venv/lib/python3.9/site-packages/nls_services_lease/test/test_lease_single_controller.py
2022-12-29 18:03:09 +00:00
@app.delete ( ' /leasing/v1/lease/ {lease_ref} ' , description = ' release (return) a lease ' )
async def leasing_v1_lease_delete ( request : Request , lease_ref : str ) :
2023-06-12 13:19:06 +00:00
cur_time = datetime . utcnow ( )
jwt_decode_key = Instance . get_default_instance ( db ) . get_jwt_decode_key ( )
try :
token = __get_token ( request , jwt_decode_key )
except JWTError :
return JSONr ( status_code = 401 , content = { ' status ' : 401 , ' detail ' : ' token is not valid ' } )
2022-12-29 18:03:09 +00:00
origin_ref = token . get ( ' origin_ref ' )
logging . info ( f ' > [ return ]: { origin_ref } : return { lease_ref } ' )
entity = Lease . find_by_lease_ref ( db , lease_ref )
if entity . origin_ref != origin_ref :
2023-01-04 09:04:52 +00:00
return JSONr ( status_code = 403 , content = { ' status ' : 403 , ' detail ' : ' access or operation forbidden ' } )
2022-12-29 18:03:09 +00:00
if entity is None :
2023-01-04 09:04:52 +00:00
return JSONr ( status_code = 404 , content = { ' status ' : 404 , ' detail ' : ' requested lease not available ' } )
2022-12-29 18:03:09 +00:00
if Lease . delete ( db , lease_ref ) == 0 :
2023-01-04 09:04:52 +00:00
return JSONr ( status_code = 404 , content = { ' status ' : 404 , ' detail ' : ' lease not found ' } )
2022-12-29 18:03:09 +00:00
response = {
" lease_ref " : lease_ref ,
" prompts " : None ,
" sync_timestamp " : cur_time . isoformat ( ) ,
}
2023-01-04 09:04:52 +00:00
return JSONr ( response )
2022-12-29 18:03:09 +00:00
2023-01-02 17:10:11 +00:00
# venv/lib/python3.9/site-packages/nls_services_lease/test/test_lease_multi_controller.py
2022-12-29 17:48:30 +00:00
@app.delete ( ' /leasing/v1/lessor/leases ' , description = ' release all leases ' )
2022-12-21 07:00:52 +00:00
async def leasing_v1_lessor_lease_remove ( request : Request ) :
2023-06-12 13:19:06 +00:00
cur_time = datetime . utcnow ( )
jwt_decode_key = Instance . get_default_instance ( db ) . get_jwt_decode_key ( )
try :
token = __get_token ( request , jwt_decode_key )
except JWTError :
return JSONr ( status_code = 401 , content = { ' status ' : 401 , ' detail ' : ' token is not valid ' } )
2022-12-20 13:55:07 +00:00
2022-12-29 17:59:26 +00:00
origin_ref = token . get ( ' origin_ref ' )
2022-12-22 11:57:06 +00:00
released_lease_list = list ( map ( lambda x : x . lease_ref , Lease . find_by_origin_ref ( db , origin_ref ) ) )
2022-12-27 19:10:18 +00:00
deletions = Lease . cleanup ( db , origin_ref )
2022-12-23 05:56:29 +00:00
logging . info ( f ' > [ remove ]: { origin_ref } : removed { deletions } leases ' )
2022-12-20 13:55:07 +00:00
2022-12-16 12:51:14 +00:00
response = {
2022-12-20 13:55:07 +00:00
" released_lease_list " : released_lease_list ,
2022-12-16 12:51:14 +00:00
" release_failure_list " : None ,
2022-12-20 05:47:40 +00:00
" sync_timestamp " : cur_time . isoformat ( ) ,
2022-12-16 12:51:14 +00:00
" prompts " : None
}
2022-12-23 12:31:23 +00:00
2023-01-04 09:04:52 +00:00
return JSONr ( response )
2022-12-16 12:51:14 +00:00
2023-01-02 18:42:23 +00:00
@app.post ( ' /leasing/v1/lessor/shutdown ' , description = ' shutdown all leases ' )
async def leasing_v1_lessor_shutdown ( request : Request ) :
2023-01-04 09:04:52 +00:00
j , cur_time = json_loads ( ( await request . body ( ) ) . decode ( ' utf-8 ' ) ) , datetime . utcnow ( )
2023-01-02 18:42:23 +00:00
2023-06-12 13:19:06 +00:00
jwt_decode_key = Instance . get_default_instance ( db ) . get_jwt_decode_key ( )
2023-01-03 08:20:18 +00:00
token = j . get ( ' token ' )
2023-01-02 18:42:23 +00:00
token = jwt . decode ( token = token , key = jwt_decode_key , algorithms = ALGORITHMS . RS256 , options = { ' verify_aud ' : False } )
origin_ref = token . get ( ' origin_ref ' )
released_lease_list = list ( map ( lambda x : x . lease_ref , Lease . find_by_origin_ref ( db , origin_ref ) ) )
deletions = Lease . cleanup ( db , origin_ref )
logging . info ( f ' > [ shutdown ]: { origin_ref } : removed { deletions } leases ' )
response = {
" released_lease_list " : released_lease_list ,
" release_failure_list " : None ,
" sync_timestamp " : cur_time . isoformat ( ) ,
" prompts " : None
}
2023-01-04 09:04:52 +00:00
return JSONr ( response )
2023-01-02 18:42:23 +00:00
2022-12-16 12:51:14 +00:00
if __name__ == ' __main__ ' :
import uvicorn
2022-12-19 12:35:03 +00:00
###
#
# Running `python app/main.py` assumes that the user created a keypair, e.g. with openssl.
#
# openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout app/cert/webserver.key -out app/cert/webserver.crt
#
###
2022-12-16 12:51:14 +00:00
2022-12-21 09:45:45 +00:00
logging . info ( f ' > Starting dev-server ... ' )
2022-12-19 12:35:03 +00:00
2022-12-19 13:27:10 +00:00
ssl_keyfile = join ( dirname ( __file__ ) , ' cert/webserver.key ' )
ssl_certfile = join ( dirname ( __file__ ) , ' cert/webserver.crt ' )
2022-12-19 12:35:03 +00:00
uvicorn . run ( ' main:app ' , host = ' 0.0.0.0 ' , port = 443 , ssl_keyfile = ssl_keyfile , ssl_certfile = ssl_certfile , reload = True )