mirror of https://github.com/jumpserver/jumpserver
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
parent
801edc7cc9
commit
fa61688c28
|
@ -0,0 +1 @@
|
|||
from .main import *
|
|
@ -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'),
|
||||
)
|
|
@ -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)}')
|
|
@ -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)
|
||||
|
|
|
@ -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}')
|
||||
|
|
|
@ -49,7 +49,10 @@ class AZUREVaultClient(object):
|
|||
self.client.set_secret(name, secret)
|
||||
|
||||
def delete(self, 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:
|
||||
|
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
|
@ -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}')
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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}')
|
|
@ -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')
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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": "バージョン",
|
||||
|
|
|
@ -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": "版本",
|
||||
|
|
|
@ -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": "驗證碼簡訊模板",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -14,6 +14,7 @@ from .. import serializers
|
|||
class VaultTestingAPI(GenericAPIView):
|
||||
backends_serializer = {
|
||||
'azure': serializers.AzureKVSerializer,
|
||||
'aws': serializers.AmazonSMSerializer,
|
||||
'hcp': serializers.HashicorpKVSerializer
|
||||
}
|
||||
rbac_perms = {
|
||||
|
|
|
@ -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']
|
||||
|
|
Loading…
Reference in New Issue