feat: Vault adds Amazon Secrets Manager (#14515)

* feat: Vault adds Amazon Secrets Manager

* perf: optimizing the code

---------

Co-authored-by: jiangweidong <1053570670@qq.com>
pull/14555/head
fit2bot 2024-11-29 17:51:28 +08:00 committed by GitHub
parent 801edc7cc9
commit fa61688c28
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 288 additions and 228 deletions

View File

@ -0,0 +1 @@
from .main import *

View File

@ -0,0 +1,16 @@
from .service import AmazonSecretsManagerClient
from ..base.vault import BaseVault
from ..utils.mixins import GeneralVaultMixin
from ...const import VaultTypeChoices
class Vault(GeneralVaultMixin, BaseVault):
type = VaultTypeChoices.aws
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.client = AmazonSecretsManagerClient(
region_name=kwargs.get('VAULT_AWS_REGION_NAME'),
access_key_id=kwargs.get('VAULT_AWS_ACCESS_KEY_ID'),
secret_key=kwargs.get('VAULT_AWS_ACCESS_SECRET_KEY'),
)

View File

@ -0,0 +1,57 @@
import boto3
from common.utils import get_logger, random_string
logger = get_logger(__name__)
__all__ = ['AmazonSecretsManagerClient']
class AmazonSecretsManagerClient(object):
def __init__(self, region_name, access_key_id, secret_key):
self.client = boto3.client(
'secretsmanager', region_name=region_name,
aws_access_key_id=access_key_id, aws_secret_access_key=secret_key,
)
self.empty_secret = '#{empty}#'
def is_active(self):
try:
secret_id = f'jumpserver/test-{random_string(12)}'
self.create(secret_id, 'secret')
self.get(secret_id)
self.update(secret_id, 'secret')
self.delete(secret_id)
except Exception as e:
return False, f'Vault is not reachable: {e}'
else:
return True, ''
def get(self, name, version=''):
params = {'SecretId': name}
if version:
params['VersionStage'] = version
try:
secret = self.client.get_secret_value(**params)['SecretString']
return secret if secret != self.empty_secret else ''
except Exception as e:
logger.error(f"Error retrieving secret: {e}")
return ''
def create(self, name, secret):
self.client.create_secret(Name=name, SecretString=secret or self.empty_secret)
def update(self, name, secret):
self.client.update_secret(SecretId=name, SecretString=secret or self.empty_secret)
def delete(self, name):
self.client.delete_secret(SecretId=name)
def update_metadata(self, name, metadata: dict):
tags = [{'Key': k, 'Value': v} for k, v in metadata.items()]
try:
self.client.tag_resource(SecretId=name, Tags=tags)
except Exception as e:
logger.error(f'update_metadata: {name} {str(e)}')

View File

@ -1,41 +1,13 @@
import sys
from abc import ABC
from common.db.utils import Encryptor
from common.utils import lazyproperty
current_module = sys.modules[__name__]
__all__ = ['build_entry']
from ..base.entries import BaseEntry
class BaseEntry(ABC):
def __init__(self, instance):
self.instance = instance
@lazyproperty
class AzureBaseEntry(BaseEntry):
@property
def full_path(self):
return self.path_spec
@property
def path_spec(self):
raise NotImplementedError
def to_internal_data(self):
secret = getattr(self.instance, '_secret', None)
if secret is not None:
secret = Encryptor(secret).encrypt()
return secret
@staticmethod
def to_external_data(secret):
if secret is not None:
secret = Encryptor(secret).decrypt()
return secret
class AccountEntry(BaseEntry):
class AccountEntry(AzureBaseEntry):
@property
def path_spec(self):
@ -45,7 +17,7 @@ class AccountEntry(BaseEntry):
return path
class AccountTemplateEntry(BaseEntry):
class AccountTemplateEntry(AzureBaseEntry):
@property
def path_spec(self):
@ -53,18 +25,9 @@ class AccountTemplateEntry(BaseEntry):
return path
class HistoricalAccountEntry(BaseEntry):
class HistoricalAccountEntry(AzureBaseEntry):
@property
def path_spec(self):
path = f'accounts-{self.instance.instance.id}-histories-{self.instance.history_id}'
return path
def build_entry(instance) -> BaseEntry:
class_name = instance.__class__.__name__
entry_class_name = f'{class_name}Entry'
entry_class = getattr(current_module, entry_class_name, None)
if not entry_class:
raise Exception(f'Entry class {entry_class_name} is not found')
return entry_class(instance)

View File

@ -1,16 +1,10 @@
from common.db.utils import get_logger
from .entries import build_entry
from .service import AZUREVaultClient
from ..base import BaseVault
from ..base.vault import BaseVault
from ..utils.mixins import GeneralVaultMixin
from ...const import VaultTypeChoices
logger = get_logger(__name__)
__all__ = ['Vault']
class Vault(BaseVault):
class Vault(GeneralVaultMixin, BaseVault):
type = VaultTypeChoices.azure
def __init__(self, *args, **kwargs):
@ -21,37 +15,3 @@ class Vault(BaseVault):
client_id=kwargs.get('VAULT_AZURE_CLIENT_ID'),
client_secret=kwargs.get('VAULT_AZURE_CLIENT_SECRET')
)
def is_active(self):
return self.client.is_active()
def _get(self, instance):
entry = build_entry(instance)
secret = self.client.get(name=entry.full_path)
secret = entry.to_external_data(secret)
return secret
def _create(self, instance):
entry = build_entry(instance)
secret = entry.to_internal_data()
self.client.create(name=entry.full_path, secret=secret)
def _update(self, instance):
entry = build_entry(instance)
secret = entry.to_internal_data()
self.client.update(name=entry.full_path, secret=secret)
def _delete(self, instance):
entry = build_entry(instance)
self.client.delete(name=entry.full_path)
def _clean_db_secret(self, instance):
instance.is_sync_metadata = False
instance.mark_secret_save_to_vault()
def _save_metadata(self, instance, metadata):
try:
entry = build_entry(instance)
self.client.update_metadata(name=entry.full_path, metadata=metadata)
except Exception as e:
logger.error(f'save metadata error: {e}')

View File

@ -49,7 +49,10 @@ class AZUREVaultClient(object):
self.client.set_secret(name, secret)
def delete(self, name):
self.client.begin_delete_secret(name)
try:
self.client.begin_delete_secret(name)
except ResourceNotFoundError as e:
logger.warning(f'Delete {name} failed: {str(e)}')
def update_metadata(self, name, metadata: dict):
try:

View File

@ -1,76 +0,0 @@
from abc import ABC, abstractmethod
from django.forms.models import model_to_dict
__all__ = ['BaseVault']
class BaseVault(ABC):
def __init__(self, *args, **kwargs):
self.enabled = kwargs.get('VAULT_ENABLED')
@property
@abstractmethod
def type(self):
raise NotImplementedError
def get(self, instance):
""" 返回 secret 值 """
return self._get(instance)
def create(self, instance):
if not instance.secret_has_save_to_vault:
self._create(instance)
self._clean_db_secret(instance)
self.save_metadata(instance)
def update(self, instance):
if not instance.secret_has_save_to_vault:
self._update(instance)
self._clean_db_secret(instance)
self.save_metadata(instance)
if instance.is_sync_metadata:
self.save_metadata(instance)
def delete(self, instance):
self._delete(instance)
def save_metadata(self, instance):
metadata = model_to_dict(instance, fields=[
'name', 'username', 'secret_type',
'connectivity', 'su_from', 'privileged'
])
metadata = {k: str(v)[:500] for k, v in metadata.items() if v}
return self._save_metadata(instance, metadata)
# -------- abstractmethod -------- #
@abstractmethod
def _get(self, instance):
raise NotImplementedError
@abstractmethod
def _create(self, instance):
raise NotImplementedError
@abstractmethod
def _update(self, instance):
raise NotImplementedError
@abstractmethod
def _delete(self, instance):
raise NotImplementedError
@abstractmethod
def _clean_db_secret(self, instance):
raise NotImplementedError
@abstractmethod
def _save_metadata(self, instance, metadata):
raise NotImplementedError
@abstractmethod
def is_active(self, *args, **kwargs) -> (bool, str):
raise NotImplementedError

View File

View File

@ -1,19 +1,18 @@
import sys
from abc import ABC
from common.db.utils import Encryptor
from common.utils import lazyproperty
current_module = sys.modules[__name__]
__all__ = ['build_entry']
class BaseEntry(ABC):
def __init__(self, instance):
self.instance = instance
@property
def path_base(self):
path = f'orgs/{self.instance.org_id}'
return path
@lazyproperty
def full_path(self):
path_base = self.path_base
@ -21,32 +20,24 @@ class BaseEntry(ABC):
path = f'{path_base}/{path_spec}'
return path
@property
def path_base(self):
path = f'orgs/{self.instance.org_id}'
return path
@property
def path_spec(self):
raise NotImplementedError
def to_internal_data(self):
def get_encrypt_secret(self):
secret = getattr(self.instance, '_secret', None)
if secret is not None:
secret = Encryptor(secret).encrypt()
data = {'secret': secret}
return data
return secret
@staticmethod
def to_external_data(data):
secret = data.pop('secret', None)
def get_decrypt_secret(secret):
if secret is not None:
secret = Encryptor(secret).decrypt()
return secret
class AccountEntry(BaseEntry):
@property
def path_spec(self):
path = f'assets/{self.instance.asset_id}/accounts/{self.instance.id}'
@ -54,7 +45,6 @@ class AccountEntry(BaseEntry):
class AccountTemplateEntry(BaseEntry):
@property
def path_spec(self):
path = f'account-templates/{self.instance.id}'
@ -62,23 +52,12 @@ class AccountTemplateEntry(BaseEntry):
class HistoricalAccountEntry(BaseEntry):
@property
def path_base(self):
account = self.instance.instance
path = f'accounts/{account.id}/'
path = f'accounts/{self.instance.instance.id}'
return path
@property
def path_spec(self):
path = f'histories/{self.instance.history_id}'
return path
def build_entry(instance) -> BaseEntry:
class_name = instance.__class__.__name__
entry_class_name = f'{class_name}Entry'
entry_class = getattr(current_module, entry_class_name, None)
if not entry_class:
raise Exception(f'Entry class {entry_class_name} is not found')
return entry_class(instance)

View File

@ -0,0 +1,109 @@
import importlib
import inspect
from abc import ABC, abstractmethod
from django.forms.models import model_to_dict
from .entries import BaseEntry
from ...const import VaultTypeChoices
class BaseVault(ABC):
def __init__(self, *args, **kwargs):
self.enabled = kwargs.get('VAULT_ENABLED')
self._entry_classes = {}
self._load_entries()
def _load_entries_import_module(self, module_name):
module = importlib.import_module(module_name)
for name, obj in inspect.getmembers(module, inspect.isclass):
self._entry_classes.setdefault(name, obj)
def _load_entries(self):
if self.type == VaultTypeChoices.local:
return
module_name = f'accounts.backends.{self.type}.entries'
if importlib.util.find_spec(module_name): # noqa
self._load_entries_import_module(module_name)
base_module = 'accounts.backends.base.entries'
self._load_entries_import_module(base_module)
@property
@abstractmethod
def type(self):
raise NotImplementedError
def get(self, instance):
""" 返回 secret 值 """
return self._get(self.build_entry(instance))
def create(self, instance):
if not instance.secret_has_save_to_vault:
entry = self.build_entry(instance)
self._create(entry)
self._clean_db_secret(instance)
self.save_metadata(entry)
def update(self, instance):
entry = self.build_entry(instance)
if not instance.secret_has_save_to_vault:
self._update(entry)
self._clean_db_secret(instance)
self.save_metadata(entry)
if instance.is_sync_metadata:
self.save_metadata(entry)
def delete(self, instance):
entry = self.build_entry(instance)
self._delete(entry)
def save_metadata(self, entry):
metadata = model_to_dict(entry.instance, fields=[
'name', 'username', 'secret_type',
'connectivity', 'su_from', 'privileged'
])
metadata = {k: str(v)[:500] for k, v in metadata.items() if v}
return self._save_metadata(entry, metadata)
def build_entry(self, instance):
if self.type == VaultTypeChoices.local:
return BaseEntry(instance)
entry_class_name = f'{instance.__class__.__name__}Entry'
entry_class = self._entry_classes.get(entry_class_name)
if not entry_class:
raise Exception(f'Entry class {entry_class_name} is not found')
return entry_class(instance)
def _clean_db_secret(self, instance):
instance.is_sync_metadata = False
instance.mark_secret_save_to_vault()
# -------- abstractmethod -------- #
@abstractmethod
def _get(self, instance):
raise NotImplementedError
@abstractmethod
def _create(self, entry):
raise NotImplementedError
@abstractmethod
def _update(self, entry):
raise NotImplementedError
@abstractmethod
def _delete(self, entry):
raise NotImplementedError
@abstractmethod
def _save_metadata(self, instance, metadata):
raise NotImplementedError
@abstractmethod
def is_active(self, *args, **kwargs) -> (bool, str):
raise NotImplementedError

View File

@ -1,10 +1,10 @@
from common.db.utils import get_logger
from .entries import build_entry
from .service import VaultKVClient
from ..base import BaseVault
from ..base.vault import BaseVault
from ...const import VaultTypeChoices
logger = get_logger(__name__)
__all__ = ['Vault']
@ -24,34 +24,25 @@ class Vault(BaseVault):
def is_active(self):
return self.client.is_active()
def _get(self, instance):
entry = build_entry(instance)
def _get(self, entry):
# TODO: get data 是不是层数太多了
data = self.client.get(path=entry.full_path).get('data', {})
data = entry.to_external_data(data)
data = entry.get_decrypt_secret(data.get('secret'))
return data
def _create(self, instance):
entry = build_entry(instance)
data = entry.to_internal_data()
def _create(self, entry):
data = {'secret': entry.get_encrypt_secret()}
self.client.create(path=entry.full_path, data=data)
def _update(self, instance):
entry = build_entry(instance)
data = entry.to_internal_data()
def _update(self, entry):
data = {'secret': entry.get_encrypt_secret()}
self.client.patch(path=entry.full_path, data=data)
def _delete(self, instance):
entry = build_entry(instance)
def _delete(self, entry):
self.client.delete(path=entry.full_path)
def _clean_db_secret(self, instance):
instance.is_sync_metadata = False
instance.mark_secret_save_to_vault()
def _save_metadata(self, instance, metadata):
def _save_metadata(self, entry, metadata):
try:
entry = build_entry(instance)
self.client.update_metadata(path=entry.full_path, metadata=metadata)
except Exception as e:
logger.error(f'save metadata error: {e}')

View File

@ -1,5 +1,5 @@
from common.utils import get_logger
from ..base import BaseVault
from ..base.vault import BaseVault
from ...const import VaultTypeChoices
logger = get_logger(__name__)
@ -13,23 +13,23 @@ class Vault(BaseVault):
def is_active(self):
return True, ''
def _get(self, instance):
secret = getattr(instance, '_secret', None)
def _get(self, entry):
secret = getattr(entry.instance, '_secret', None)
return secret
def _create(self, instance):
def _create(self, entry):
""" Ignore """
pass
def _update(self, instance):
def _update(self, entry):
""" Ignore """
pass
def _delete(self, instance):
def _delete(self, entry):
""" Ignore """
pass
def _save_metadata(self, instance, metadata):
def _save_metadata(self, entry, metadata):
""" Ignore """
pass

View File

View File

@ -0,0 +1,32 @@
from common.utils import get_logger
logger = get_logger(__name__)
class GeneralVaultMixin(object):
client = None
def is_active(self):
return self.client.is_active()
def _get(self, entry):
secret = self.client.get(name=entry.full_path)
return entry.get_decrypt_secret(secret)
def _create(self, entry):
secret = entry.get_encrypt_secret()
self.client.create(name=entry.full_path, secret=secret)
def _update(self, entry):
secret = entry.get_encrypt_secret()
self.client.update(name=entry.full_path, secret=secret)
def _delete(self, entry):
self.client.delete(name=entry.full_path)
def _save_metadata(self, entry, metadata):
try:
self.client.update_metadata(name=entry.full_path, metadata=metadata)
except Exception as e:
logger.error(f'save metadata error: {e}')

View File

@ -8,3 +8,4 @@ class VaultTypeChoices(models.TextChoices):
local = 'local', _('Database')
hcp = 'hcp', _('HCP Vault')
azure = 'azure', _('Azure Key Vault')
aws = 'aws', _('Amazon Secrets Manager')

View File

@ -1365,7 +1365,7 @@
"VariableHelpText": "You can use {{ key }} to read built-in variables in commands",
"VariableName": "Variable name",
"VaultHCPMountPoint": "The mount point of the Vault server, default is jumpserver",
"VaultHelpText": "1. for security reasons, vault storage must be enabled in the configuration file.<br>2. after enabled, fill in other configurations, and perform tests.<br>3. carry out data synchronization, which is one-way, only syncing from the local database to the distant vault, once synchronization is completed, the local database will no longer store passwords, please back up your data.<br>4. after modifying vault configuration the second time, you need to restart the service.",
"VaultHelpText": "1. for security reasons, add the parameters VAULT_ENABLED=true and VAULT_BACKEND=hcp/azure/aws in the configuration file to enable Vault storage.<br>2. after enabled, fill in other configurations, and perform tests.<br>3. carry out data synchronization, which is one-way, only syncing from the local database to the distant vault, once synchronization is completed, the local database will no longer store passwords, please back up your data.<br>4. after modifying vault configuration the second time, you need to restart the service.",
"VerificationCodeSent": "Verification code has been sent",
"VerifySignTmpl": "Sms template",
"Version": "Version",
@ -1419,4 +1419,4 @@
"ExtraArgsFormatError": "Format error, please enter according to the requirements",
"MFAOnlyAdminUsers": "Globally: Only admin",
"MFAAllUsers": "Globally: All users"
}
}

View File

@ -1412,7 +1412,7 @@
"VariableHelpText": "コマンド中で {{ key }} を使用して内蔵変数を読み取ることができます",
"VariableName": "変数名",
"VaultHCPMountPoint": "Vault サーバのマウントポイント、デフォルトはjumpserver",
"VaultHelpText": "1. セキュリティ上の理由により、設定ファイルで Vault ストレージをオンにする必要があります。<br>2. オンにした後、他の設定を入力してテストを行います。<br>3. データ同期を行います。同期は一方向です。ローカルデータベースからリモートの Vault にのみ同期します。同期が終了すればローカルデータベースはパスワードを保管していませんので、データのバックアップをお願いします。<br>4. Vault の設定を二度変更した後はサービスを再起動する必要があります。",
"VaultHelpText": "1. セキュリティ上の理由から、設定ファイルに VAULT_ENABLED=true および VAULT_BACKEND=hcp/azure/aws のパラメータを追加して、Vault ストレージを有効にします。<br>2. オンにした後、他の設定を入力してテストを行います。<br>3. データ同期を行います。同期は一方向です。ローカルデータベースからリモートの Vault にのみ同期します。同期が終了すればローカルデータベースはパスワードを保管していませんので、データのバックアップをお願いします。<br>4. Vault の設定を二度変更した後はサービスを再起動する必要があります。",
"VerificationCodeSent": "認証コードが送信されました",
"VerifySignTmpl": "認証コードのSMSテンプレート",
"Version": "バージョン",
@ -1460,4 +1460,4 @@
"forceEnableMFAHelpText": "強制的に有効化すると、ユーザーは自分で無効化することができません。",
"removeWarningMsg": "削除してもよろしいですか",
"setVariable": "パラメータ設定"
}
}

View File

@ -1370,7 +1370,7 @@
"VariableHelpText": "您可以在命令中使用 {{ key }} 读取内置变量",
"VariableName": "变量名",
"VaultHCPMountPoint": "Vault 服务器的挂载点,默认为 jumpserver",
"VaultHelpText": "1. 由于安全原因,需要配置文件中开启 Vault 存储。<br>2. 开启后,填写其他配置,进行测试。<br>3. 进行数据同步,同步是单向的,只会从本地数据库同步到远端 Vault同步完成本地数据库不再存储密码请备份好数据。<br>4. 二次修改 Vault 配置后需重启服务。",
"VaultHelpText": "1. 由于安全原因,需要配置文件中增加参数 VAULT_ENABLED=true 和 VAULT_BACKEND=hcp/azure/aws 以开启 Vault 存储。<br>2. 开启后,填写其他配置,进行测试。<br>3. 进行数据同步,同步是单向的,只会从本地数据库同步到远端 Vault同步完成本地数据库不再存储密码请备份好数据。<br>4. 二次修改 Vault 配置后需重启服务。",
"VerificationCodeSent": "验证码已发送",
"VerifySignTmpl": "验证码短信模板",
"Version": "版本",
@ -1424,4 +1424,4 @@
"ExtraArgsFormatError": "格式错误,请按要求输入",
"MFAOnlyAdminUsers": "全局启用: 仅管理员",
"MFAAllUsers": "全局启用: 所有用户"
}
}

View File

@ -1806,7 +1806,7 @@
"VariableName": "變數名",
"Vault": "密碼匣子",
"VaultHCPMountPoint": "重新嘗試所選",
"VaultHelpText": "1. 由於安全原因,需要配置文件中開啟 Vault 存。<br>2. 開啟後,填寫其他配置,進行測試。<br>3. 進行數據同步,同步是單向的,只會從本地資料庫同步到遠端 Vault同步完成本地資料庫不再儲存密碼請備份好數據。<br>4. 二次修改 Vault 配置後需重啟服務。",
"VaultHelpText": "1. 由於安全原因,需要配置文件中增加參數 VAULT_ENABLED=true 和 VAULT_BACKEND=hcp/azure/aws 以開啟 Vault 存。<br>2. 開啟後,填寫其他配置,進行測試。<br>3. 進行數據同步,同步是單向的,只會從本地資料庫同步到遠端 Vault同步完成本地資料庫不再儲存密碼請備份好數據。<br>4. 二次修改 Vault 配置後需重啟服務。",
"Vendor": "製造商",
"VerificationCodeSent": "驗證碼已發送",
"VerifySignTmpl": "驗證碼簡訊模板",
@ -2298,4 +2298,4 @@
"week": "周",
"weekOf": "周的星期",
"wildcardsAllowed": "允許的通配符"
}
}

View File

@ -270,6 +270,10 @@ class Config(dict):
'VAULT_AZURE_CLIENT_SECRET': '',
'VAULT_AZURE_TENANT_ID': '',
'VAULT_AWS_REGION_NAME': '',
'VAULT_AWS_ACCESS_KEY_ID': '',
'VAULT_AWS_ACCESS_SECRET_KEY': '',
'HISTORY_ACCOUNT_CLEAN_LIMIT': 999,
# Cache login password

View File

@ -246,6 +246,10 @@ VAULT_AZURE_CLIENT_ID = CONFIG.VAULT_AZURE_CLIENT_ID
VAULT_AZURE_CLIENT_SECRET = CONFIG.VAULT_AZURE_CLIENT_SECRET
VAULT_AZURE_TENANT_ID = CONFIG.VAULT_AZURE_TENANT_ID
VAULT_AWS_REGION_NAME = CONFIG.VAULT_AWS_REGION_NAME
VAULT_AWS_ACCESS_KEY_ID = CONFIG.VAULT_AWS_ACCESS_KEY_ID
VAULT_AWS_ACCESS_SECRET_KEY = CONFIG.VAULT_AWS_ACCESS_SECRET_KEY
HISTORY_ACCOUNT_CLEAN_LIMIT = CONFIG.HISTORY_ACCOUNT_CLEAN_LIMIT
# Other setting

View File

@ -62,6 +62,7 @@ class SettingsApi(generics.RetrieveUpdateAPIView):
'custom': serializers.CustomSMSSettingSerializer,
'vault': serializers.VaultSettingSerializer,
'azure_kv': serializers.AzureKVSerializer,
'aws_sm': serializers.AmazonSMSerializer,
'hcp': serializers.HashicorpKVSerializer,
'chat': serializers.ChatAISettingSerializer,
'announcement': serializers.AnnouncementSettingSerializer,

View File

@ -14,6 +14,7 @@ from .. import serializers
class VaultTestingAPI(GenericAPIView):
backends_serializer = {
'azure': serializers.AzureKVSerializer,
'aws': serializers.AmazonSMSerializer,
'hcp': serializers.HashicorpKVSerializer
}
rbac_perms = {

View File

@ -11,7 +11,7 @@ from common.utils import date_expired_default
__all__ = [
'AnnouncementSettingSerializer', 'OpsSettingSerializer', 'VaultSettingSerializer',
'HashicorpKVSerializer', 'AzureKVSerializer', 'TicketSettingSerializer',
'ChatAISettingSerializer', 'VirtualAppSerializer',
'ChatAISettingSerializer', 'VirtualAppSerializer', 'AmazonSMSerializer',
]
@ -103,6 +103,20 @@ class AzureKVSerializer(BaseVaultSettingSerializer, serializers.Serializer):
)
class AmazonSMSerializer(serializers.Serializer):
PREFIX_TITLE = _('Amazon Secrets Manager')
VAULT_AWS_REGION_NAME = serializers.CharField(
max_length=256, required=True, label=_('Region')
)
VAULT_AWS_ACCESS_KEY_ID = serializers.CharField(
max_length=1024, required=True, label=_('Access key ID')
)
VAULT_AWS_ACCESS_SECRET_KEY = EncryptedField(
max_length=1024, required=False, allow_blank=True,
label=_('Access key secret'), allow_null=True,
)
class ChatAISettingSerializer(serializers.Serializer):
PREFIX_TITLE = _('Chat AI')
API_MODEL = Protocol.gpt_protocols()[Protocol.chatgpt]['setting']['api_mode']