Generate JWT secret key if none is configured in the config file.

Change location of the database.
This commit is contained in:
grossmj committed 2020-12-16 18:24:21 +10:30
1 parent 509e762cda
commit bde706d19a
8 files changed
+130 -40

No files matched your search

+14 -2
View File
@@ -25,7 +25,12 @@ from uuid import UUID
from typing import List
from gns3server import schemas
from gns3server.controller.controller_error import ControllerBadRequestError, ControllerNotFoundError
from gns3server.controller.controller_error import (
ControllerBadRequestError,
ControllerNotFoundError,
ControllerUnauthorizedError
)
from gns3server.db.repositories.users import UsersRepository
from gns3server.services import auth_service
@@ -98,11 +103,18 @@ async def update_user(
@router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_user(user_id: UUID, user_repo: UsersRepository = Depends(get_repository(UsersRepository))):
async def delete_user(
user_id: UUID,
user_repo: UsersRepository = Depends(get_repository(UsersRepository)),
current_user: schemas.User = Depends(get_current_active_user)
) -> None:
"""
Delete an user.
"""
if current_user.is_superuser:
raise ControllerUnauthorizedError("The super user cannot be deleted")
success = await user_repo.delete_user(user_id)
if not success:
raise ControllerNotFoundError(f"User '{user_id}' not found")
+26 -2
View File
@@ -182,9 +182,21 @@ class Config:
controller_config_filename = "gns3_controller.conf"
return os.path.join(self.config_dir, controller_config_filename)
@property
def server_config(self):
if sys.platform.startswith("win"):
server_config_filename = "gns3_server.ini"
else:
server_config_filename = "gns3_server.conf"
return os.path.join(self.config_dir, server_config_filename)
def clear(self):
"""Restart with a clean config"""
self._config = configparser.RawConfigParser()
"""
Restart with a clean config
"""
self._config = configparser.ConfigParser(interpolation=None)
# Override config from command line even if we modify the config file and live reload it.
self._override_config = {}
@@ -231,6 +243,18 @@ class Config:
log.info("Load configuration file {}".format(file))
self._watched_files[file] = os.stat(file).st_mtime
def write_config(self):
"""
Write the server configuration file.
"""
try:
os.makedirs(os.path.dirname(self.server_config), exist_ok=True)
with open(self.server_config, 'w+') as fd:
self._config.write(fd)
except OSError as e:
log.error("Cannot write server configuration file '{}': {}".format(self.server_config, e))
def get_default_section(self):
"""
Get the default configuration section.
+1
View File
@@ -74,6 +74,7 @@ class BaseTable(Base):
def generate_uuid():
return str(uuid.uuid4())
class User(BaseTable):
__tablename__ = "users"
+5 -3
View File
@@ -22,6 +22,7 @@ from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext.asyncio import create_async_engine
from .models import Base
from gns3server.config import Config
import logging
log = logging.getLogger(__name__)
@@ -29,12 +30,13 @@ log = logging.getLogger(__name__)
async def connect_to_db(app: FastAPI) -> None:
db_url = os.environ.get("GNS3_DATABASE_URI", "sqlite:///./sql_app.db")
db_path = os.path.join(Config.instance().config_dir, "gns3_controller.db")
db_url = os.environ.get("GNS3_DATABASE_URI", f"sqlite:///{db_path}")
engine = create_async_engine(db_url, connect_args={"check_same_thread": False}, future=True)
try:
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
log.info("Successfully connected to the database")
log.info(f"Successfully connected to database '{db_url}'")
app.state._db_engine = engine
except SQLAlchemyError as e:
log.error(f"Error while connecting to the database: {e}")
log.error(f"Error while connecting to database '{db_url}: {e}")
+11 -3
View File
@@ -31,6 +31,7 @@ import asyncio
import signal
import functools
import uvicorn
import secrets
from gns3server.controller import Controller
from gns3server.compute.port_manager import PortManager
@@ -122,7 +123,7 @@ def parse_arguments(argv):
config = Config.instance().get_section_config("Server")
defaults = {
"host": config.get("host", "0.0.0.0"),
"port": config.get("port", 3080),
"port": config.getint("port", 3080),
"ssl": config.getboolean("ssl", False),
"certfile": config.get("certfile", ""),
"certkey": config.get("certkey", ""),
@@ -132,8 +133,8 @@ def parse_arguments(argv):
"quiet": config.getboolean("quiet", False),
"debug": config.getboolean("debug", False),
"logfile": config.getboolean("logfile", ""),
"logmaxsize": config.get("logmaxsize", 10000000), # default is 10MB
"logbackupcount": config.get("logbackupcount", 10),
"logmaxsize": config.getint("logmaxsize", 10000000), # default is 10MB
"logbackupcount": config.getint("logbackupcount", 10),
"logcompression": config.getboolean("logcompression", False)
}
@@ -145,6 +146,13 @@ def set_config(args):
config = Config.instance()
server_config = config.get_section_config("Server")
jwt_secret_key = server_config.get("jwt_secret_key", None)
if not jwt_secret_key:
log.info("No JWT secret key configured, generating one...")
if not config._config.has_section("Server"):
config._config.add_section("Server")
config._config.set("Server", "jwt_secret_key", secrets.token_hex(32))
config.write_config()
server_config["local"] = str(args.local)
server_config["allow_remote_console"] = str(args.allow)
server_config["host"] = args.host
+39 -11
View File
@@ -16,7 +16,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import bcrypt
from jose import JWTError, jwt
from datetime import datetime, timedelta
from passlib.context import CryptContext
@@ -24,19 +23,22 @@ from passlib.context import CryptContext
from typing import Optional
from fastapi import HTTPException, status
from gns3server.schemas.tokens import TokenData
from gns3server.controller.controller_error import ControllerError
from gns3server.config import Config
from pydantic import ValidationError
# FIXME: temporary variables to move to config
SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
import logging
log = logging.getLogger(__name__)
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
class AuthService:
def __init__(self):
self._server_config = Config.instance().get_section_config("Server")
def hash_password(self, password: str) -> str:
return pwd_context.hash(password)
@@ -45,19 +47,40 @@ class AuthService:
return pwd_context.verify(password, hashed_password)
def get_secret_key(self):
"""
Should only be used by tests.
"""
return self._server_config.get("jwt_secret_key", None)
def get_algorithm(self):
"""
Should only be used by tests.
"""
return self._server_config.get("jwt_algorithm", None)
def create_access_token(
self,
username,
secret_key: str = SECRET_KEY,
expires_in: int = ACCESS_TOKEN_EXPIRE_MINUTES
secret_key: str = None,
expires_in: int = 0
) -> str:
if not expires_in:
expires_in = self._server_config.getint("jwt_access_token_expire_minutes", 1440)
expire = datetime.utcnow() + timedelta(minutes=expires_in)
to_encode = {"sub": username, "exp": expire}
encoded_jwt = jwt.encode(to_encode, secret_key, algorithm=ALGORITHM)
if secret_key is None:
secret_key = self._server_config.get("jwt_secret_key", None)
if secret_key is None:
raise ControllerError("No JWT secret key has been configured")
algorithm = self._server_config.get("jwt_algorithm", "HS256")
encoded_jwt = jwt.encode(to_encode, secret_key, algorithm=algorithm)
return encoded_jwt
def get_username_from_token(self, token: str, secret_key: str = SECRET_KEY) -> Optional[str]:
def get_username_from_token(self, token: str, secret_key: str = None) -> Optional[str]:
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
@@ -65,7 +88,12 @@ class AuthService:
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, secret_key, algorithms=[ALGORITHM])
if secret_key is None:
secret_key = self._server_config.get("jwt_secret_key", None)
if secret_key is None:
raise ControllerError("No JWT secret key has been configured")
algorithm = self._server_config.get("jwt_algorithm", "HS256")
payload = jwt.decode(token, secret_key, algorithms=[algorithm])
username: str = payload.get("sub")
if username is None:
raise credentials_exception