Supports login cross multiple RAGFlow servers (#13322)

### What problem does this PR solve?

1. Use redis to store the secret key.
2. During startup API server will read the secret from redis. If no such
secret key, generate one and store it into redis, atomically.

### Type of change

- [x] New Feature (non-breaking change which adds functionality)

---------

Signed-off-by: Jin Hai <haijin.chn@gmail.com>
This commit is contained in:
Jin Hai
2026-03-04 13:07:45 +08:00
committed by GitHub
parent 5f8966608d
commit b9ad014f63
3 changed files with 49 additions and 12 deletions

View File

@@ -142,7 +142,7 @@ jobs:
RUNNER_WORKSPACE_PREFIX=${RUNNER_WORKSPACE_PREFIX:-${HOME}}
RAGFLOW_IMAGE=infiniflow/ragflow:${GITHUB_RUN_ID}
echo "RAGFLOW_IMAGE=${RAGFLOW_IMAGE}" >> ${GITHUB_ENV}
sudo docker pull ubuntu:22.04
sudo docker pull ubuntu:24.04
sudo DOCKER_BUILDKIT=1 docker build --build-arg NEED_MIRROR=1 --build-arg HTTPS_PROXY=${HTTPS_PROXY} --build-arg HTTP_PROXY=${HTTP_PROXY} -f Dockerfile -t ${RAGFLOW_IMAGE} .
if [[ ${GITHUB_EVENT_NAME} == "schedule" ]]; then
export HTTP_API_TEST_LEVEL=p3

View File

@@ -16,7 +16,6 @@
import os
import json
import secrets
from datetime import date
import logging
from common.constants import RAG_FLOW_SERVICE_NAME
from common.file_utils import get_project_base_directory
@@ -34,6 +33,7 @@ from rag.utils.azure_spn_conn import RAGFlowAzureSpnBlob
from rag.utils.gcs_conn import RAGFlowGCS
from rag.utils.minio_conn import RAGFlowMinio
from rag.utils.opendal_conn import OpenDALStorage
from rag.utils.redis_conn import REDIS_CONN
from rag.utils.s3_conn import RAGFlowS3
from rag.utils.oss_conn import RAGFlowOSS
@@ -138,21 +138,22 @@ def get_svr_queue_names():
return [get_svr_queue_name(priority) for priority in [1, 0]]
def _get_or_create_secret_key():
secret_key = os.environ.get("RAGFLOW_SECRET_KEY")
if secret_key and len(secret_key) >= 32:
return secret_key
# Check if there's a configured secret key
configured_key = get_base_config(RAG_FLOW_SERVICE_NAME, {}).get("secret_key")
if configured_key and configured_key != str(date.today()) and len(configured_key) >= 32:
return configured_key
# secret_key = os.environ.get("RAGFLOW_SECRET_KEY")
# if secret_key and len(secret_key) >= 32:
# return secret_key
#
# # Check if there's a configured secret key
# configured_key = get_base_config(RAG_FLOW_SERVICE_NAME, {}).get("secret_key")
# if configured_key and configured_key != str(date.today()) and len(configured_key) >= 32:
# return configured_key
# Generate a new secure key and warn about it
import logging
new_key = secrets.token_hex(32)
generated_key = secrets.token_hex(32)
secret_key = REDIS_CONN.get_or_create_secret_key("ragflow:system:secret_key", generated_key)
logging.warning("SECURITY WARNING: Using auto-generated SECRET_KEY.")
return new_key
return secret_key
class StorageFactory:
storage_mapping = {

View File

@@ -334,6 +334,42 @@ class RedisDB:
self.__open__()
return -1
def get_or_create_secret_key(self, key_name: str, new_value: str) -> str:
"""
Atomically get an existing key or create a new one.
This method guarantees that across multiple concurrent calls, only one
key will be created and all callers will receive the same key.
Returns:
The secret key string
Raises:
redis.RedisError: If Redis operations fail
"""
# First, try to get the existing key
existing_value = self.REDIS.get(key_name)
if existing_value is not None:
logging.debug("Retrieved existing key from Redis")
return existing_value
# Use SETNX to atomically set the key only if it doesn't exist
# SETNX returns True if the key was set, False if it already existed
if self.REDIS.setnx(key_name, new_value):
logging.info("Successfully created new secret key in Redis")
return new_value
# SETNX failed, meaning another process created the key concurrently
# Retrieve and return that key
final_key = self.REDIS.get(key_name)
if final_key is None:
# This should rarely happen, but retry if it does
logging.warning("Key disappeared during concurrent access, retrying...")
return self.get_or_create_secret_key(key_name, new_value)
logging.debug("Retrieved key created by another process")
return final_key
def transaction(self, key, value, exp=3600):
try:
pipeline = self.REDIS.pipeline(transaction=True)