diff --git a/apps/accounts/backends/aws/__init__.py b/apps/accounts/backends/aws/__init__.py
new file mode 100644
index 000000000..15b6a64ba
--- /dev/null
+++ b/apps/accounts/backends/aws/__init__.py
@@ -0,0 +1 @@
+from .main import *
diff --git a/apps/accounts/backends/aws/main.py b/apps/accounts/backends/aws/main.py
new file mode 100644
index 000000000..9ebaa65b0
--- /dev/null
+++ b/apps/accounts/backends/aws/main.py
@@ -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'),
+ )
diff --git a/apps/accounts/backends/aws/service.py b/apps/accounts/backends/aws/service.py
new file mode 100644
index 000000000..d4236403e
--- /dev/null
+++ b/apps/accounts/backends/aws/service.py
@@ -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)}')
diff --git a/apps/accounts/backends/azure/entries.py b/apps/accounts/backends/azure/entries.py
index 8c607a54a..8df60f7b8 100644
--- a/apps/accounts/backends/azure/entries.py
+++ b/apps/accounts/backends/azure/entries.py
@@ -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)
diff --git a/apps/accounts/backends/azure/main.py b/apps/accounts/backends/azure/main.py
index ab5b9bfcd..41ab59665 100644
--- a/apps/accounts/backends/azure/main.py
+++ b/apps/accounts/backends/azure/main.py
@@ -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}')
diff --git a/apps/accounts/backends/azure/service.py b/apps/accounts/backends/azure/service.py
index 800687ee6..8c748115e 100644
--- a/apps/accounts/backends/azure/service.py
+++ b/apps/accounts/backends/azure/service.py
@@ -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:
diff --git a/apps/accounts/backends/base.py b/apps/accounts/backends/base.py
deleted file mode 100644
index c6f12a808..000000000
--- a/apps/accounts/backends/base.py
+++ /dev/null
@@ -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
diff --git a/apps/accounts/backends/base/__init__.py b/apps/accounts/backends/base/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/apps/accounts/backends/hcp/entries.py b/apps/accounts/backends/base/entries.py
similarity index 67%
rename from apps/accounts/backends/hcp/entries.py
rename to apps/accounts/backends/base/entries.py
index ff73ef98d..f15bebcef 100644
--- a/apps/accounts/backends/hcp/entries.py
+++ b/apps/accounts/backends/base/entries.py
@@ -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)
diff --git a/apps/accounts/backends/base/vault.py b/apps/accounts/backends/base/vault.py
new file mode 100644
index 000000000..1eb7a20be
--- /dev/null
+++ b/apps/accounts/backends/base/vault.py
@@ -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
diff --git a/apps/accounts/backends/hcp/main.py b/apps/accounts/backends/hcp/main.py
index 708c1c228..2e5e277ba 100644
--- a/apps/accounts/backends/hcp/main.py
+++ b/apps/accounts/backends/hcp/main.py
@@ -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}')
diff --git a/apps/accounts/backends/local/main.py b/apps/accounts/backends/local/main.py
index d1f774f5b..87e9cd49e 100644
--- a/apps/accounts/backends/local/main.py
+++ b/apps/accounts/backends/local/main.py
@@ -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
diff --git a/apps/accounts/backends/utils/__init__.py b/apps/accounts/backends/utils/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/apps/accounts/backends/utils/mixins.py b/apps/accounts/backends/utils/mixins.py
new file mode 100644
index 000000000..a0fbc8192
--- /dev/null
+++ b/apps/accounts/backends/utils/mixins.py
@@ -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}')
diff --git a/apps/accounts/const/vault.py b/apps/accounts/const/vault.py
index f832b028e..460950427 100644
--- a/apps/accounts/const/vault.py
+++ b/apps/accounts/const/vault.py
@@ -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')
diff --git a/apps/i18n/lina/en.json b/apps/i18n/lina/en.json
index 8059b7dae..ecd49a906 100644
--- a/apps/i18n/lina/en.json
+++ b/apps/i18n/lina/en.json
@@ -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.
2. after enabled, fill in other configurations, and perform tests.
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.
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.
2. after enabled, fill in other configurations, and perform tests.
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.
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"
-}
\ No newline at end of file
+}
diff --git a/apps/i18n/lina/ja.json b/apps/i18n/lina/ja.json
index b4bd08b0b..7f1d21ecc 100644
--- a/apps/i18n/lina/ja.json
+++ b/apps/i18n/lina/ja.json
@@ -1412,7 +1412,7 @@
"VariableHelpText": "コマンド中で {{ key }} を使用して内蔵変数を読み取ることができます",
"VariableName": "変数名",
"VaultHCPMountPoint": "Vault サーバのマウントポイント、デフォルトはjumpserver",
- "VaultHelpText": "1. セキュリティ上の理由により、設定ファイルで Vault ストレージをオンにする必要があります。
2. オンにした後、他の設定を入力してテストを行います。
3. データ同期を行います。同期は一方向です。ローカルデータベースからリモートの Vault にのみ同期します。同期が終了すればローカルデータベースはパスワードを保管していませんので、データのバックアップをお願いします。
4. Vault の設定を二度変更した後はサービスを再起動する必要があります。",
+ "VaultHelpText": "1. セキュリティ上の理由から、設定ファイルに VAULT_ENABLED=true および VAULT_BACKEND=hcp/azure/aws のパラメータを追加して、Vault ストレージを有効にします。
2. オンにした後、他の設定を入力してテストを行います。
3. データ同期を行います。同期は一方向です。ローカルデータベースからリモートの Vault にのみ同期します。同期が終了すればローカルデータベースはパスワードを保管していませんので、データのバックアップをお願いします。
4. Vault の設定を二度変更した後はサービスを再起動する必要があります。",
"VerificationCodeSent": "認証コードが送信されました",
"VerifySignTmpl": "認証コードのSMSテンプレート",
"Version": "バージョン",
@@ -1460,4 +1460,4 @@
"forceEnableMFAHelpText": "強制的に有効化すると、ユーザーは自分で無効化することができません。",
"removeWarningMsg": "削除してもよろしいですか",
"setVariable": "パラメータ設定"
-}
\ No newline at end of file
+}
diff --git a/apps/i18n/lina/zh.json b/apps/i18n/lina/zh.json
index f84e5c540..4dd50d858 100644
--- a/apps/i18n/lina/zh.json
+++ b/apps/i18n/lina/zh.json
@@ -1370,7 +1370,7 @@
"VariableHelpText": "您可以在命令中使用 {{ key }} 读取内置变量",
"VariableName": "变量名",
"VaultHCPMountPoint": "Vault 服务器的挂载点,默认为 jumpserver",
- "VaultHelpText": "1. 由于安全原因,需要配置文件中开启 Vault 存储。
2. 开启后,填写其他配置,进行测试。
3. 进行数据同步,同步是单向的,只会从本地数据库同步到远端 Vault,同步完成本地数据库不再存储密码,请备份好数据。
4. 二次修改 Vault 配置后需重启服务。",
+ "VaultHelpText": "1. 由于安全原因,需要配置文件中增加参数 VAULT_ENABLED=true 和 VAULT_BACKEND=hcp/azure/aws 以开启 Vault 存储。
2. 开启后,填写其他配置,进行测试。
3. 进行数据同步,同步是单向的,只会从本地数据库同步到远端 Vault,同步完成本地数据库不再存储密码,请备份好数据。
4. 二次修改 Vault 配置后需重启服务。",
"VerificationCodeSent": "验证码已发送",
"VerifySignTmpl": "验证码短信模板",
"Version": "版本",
@@ -1424,4 +1424,4 @@
"ExtraArgsFormatError": "格式错误,请按要求输入",
"MFAOnlyAdminUsers": "全局启用: 仅管理员",
"MFAAllUsers": "全局启用: 所有用户"
-}
\ No newline at end of file
+}
diff --git a/apps/i18n/lina/zh_hant.json b/apps/i18n/lina/zh_hant.json
index 1216087ee..cf0a48163 100644
--- a/apps/i18n/lina/zh_hant.json
+++ b/apps/i18n/lina/zh_hant.json
@@ -1806,7 +1806,7 @@
"VariableName": "變數名",
"Vault": "密碼匣子",
"VaultHCPMountPoint": "重新嘗試所選",
- "VaultHelpText": "1. 由於安全原因,需要配置文件中開啟 Vault 儲存。
2. 開啟後,填寫其他配置,進行測試。
3. 進行數據同步,同步是單向的,只會從本地資料庫同步到遠端 Vault,同步完成本地資料庫不再儲存密碼,請備份好數據。
4. 二次修改 Vault 配置後需重啟服務。",
+ "VaultHelpText": "1. 由於安全原因,需要在配置文件中增加參數 VAULT_ENABLED=true 和 VAULT_BACKEND=hcp/azure/aws 以開啟 Vault 存儲。
2. 開啟後,填寫其他配置,進行測試。
3. 進行數據同步,同步是單向的,只會從本地資料庫同步到遠端 Vault,同步完成本地資料庫不再儲存密碼,請備份好數據。
4. 二次修改 Vault 配置後需重啟服務。",
"Vendor": "製造商",
"VerificationCodeSent": "驗證碼已發送",
"VerifySignTmpl": "驗證碼簡訊模板",
@@ -2298,4 +2298,4 @@
"week": "周",
"weekOf": "周的星期",
"wildcardsAllowed": "允許的通配符"
-}
\ No newline at end of file
+}
diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py
index da55cd88a..6491b13ce 100644
--- a/apps/jumpserver/conf.py
+++ b/apps/jumpserver/conf.py
@@ -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
diff --git a/apps/jumpserver/settings/auth.py b/apps/jumpserver/settings/auth.py
index f2cec42b6..e69471dd4 100644
--- a/apps/jumpserver/settings/auth.py
+++ b/apps/jumpserver/settings/auth.py
@@ -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
diff --git a/apps/settings/api/settings.py b/apps/settings/api/settings.py
index 3d35e3432..00e6bd873 100644
--- a/apps/settings/api/settings.py
+++ b/apps/settings/api/settings.py
@@ -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,
diff --git a/apps/settings/api/vault.py b/apps/settings/api/vault.py
index 82df54b18..b4e0c71a3 100644
--- a/apps/settings/api/vault.py
+++ b/apps/settings/api/vault.py
@@ -14,6 +14,7 @@ from .. import serializers
class VaultTestingAPI(GenericAPIView):
backends_serializer = {
'azure': serializers.AzureKVSerializer,
+ 'aws': serializers.AmazonSMSerializer,
'hcp': serializers.HashicorpKVSerializer
}
rbac_perms = {
diff --git a/apps/settings/serializers/feature.py b/apps/settings/serializers/feature.py
index b6dcacc08..dfe3d5ad6 100644
--- a/apps/settings/serializers/feature.py
+++ b/apps/settings/serializers/feature.py
@@ -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']