Skip to content

Commit 3a4f0b2

Browse files
pycookfxiang21mrrnb
authored
feat(cmdb-api): CI password data store (#242)
* add secrets,for test * feat: vault SDK (#238) * feat: vault SDK * docs: i18n * perf(vault): format code * feat(secrets): support vault * feat: add inner password storage * feat: secrets * feat: add inner password storage * feat: add secrets feature * perf(secrets): review --------- Co-authored-by: fxiang21 <fxiang21@126.com> Co-authored-by: Mimo <osatmnzn@gmail.com>
1 parent ffa3d7c commit 3a4f0b2

20 files changed

Lines changed: 950 additions & 30 deletions

File tree

cmdb-api/Pipfile

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,17 @@ Flask-Bcrypt = "==1.0.1"
2626
Flask-Cors = ">=3.0.8"
2727
ldap3 = "==2.9.1"
2828
pycryptodome = "==3.12.0"
29-
cryptography = "==41.0.2"
29+
cryptography = ">=41.0.2"
3030
# Caching
3131
Flask-Caching = ">=1.0.0"
3232
# Environment variable parsing
3333
environs = "==4.2.0"
3434
marshmallow = "==2.20.2"
3535
# async tasks
36-
celery = "==5.3.1"
36+
celery = ">=5.3.1"
3737
celery_once = "==3.0.1"
3838
more-itertools = "==5.0.0"
39-
kombu = "==5.3.1"
39+
kombu = ">=5.3.1"
4040
# common setting
4141
timeout-decorator = "==0.5.0"
4242
WTForms = "==3.0.0"
@@ -59,6 +59,9 @@ Jinja2 = "==3.1.2"
5959
jinja2schema = "==0.1.4"
6060
msgpack-python = "==0.5.6"
6161
alembic = "==1.7.7"
62+
hvac = "==2.0.0"
63+
colorama = ">=0.4.6"
64+
pycryptodomex = ">=3.19.0"
6265

6366
[dev-packages]
6467
# Testing
@@ -75,4 +78,3 @@ flake8-isort = "==2.7.0"
7578
isort = "==4.3.21"
7679
pep8-naming = "==0.8.2"
7780
pydocstyle = "==3.0.0"
78-

cmdb-api/api/app.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818

1919
import api.views.entry
2020
from api.extensions import (bcrypt, cache, celery, cors, db, es, login_manager, migrate, rd)
21+
from api.extensions import inner_secrets
2122
from api.flask_cas import CAS
23+
from api.lib.secrets.secrets import InnerKVManger
2224
from api.models.acl import User
2325

2426
HERE = os.path.abspath(os.path.dirname(__file__))
@@ -126,6 +128,10 @@ def register_extensions(app):
126128
app.config.update(app.config.get("CELERY"))
127129
celery.conf.update(app.config)
128130

131+
if app.config.get('SECRETS_ENGINE') == 'inner':
132+
with app.app_context():
133+
inner_secrets.init_app(app, InnerKVManger())
134+
129135

130136
def register_blueprints(app):
131137
for item in getmembers(api.views.entry):

cmdb-api/api/commands/click_cmdb.py

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import time
88

99
import click
10+
import requests
1011
from flask import current_app
1112
from flask.cli import with_appcontext
1213
from flask_login import login_user
@@ -29,6 +30,9 @@
2930
from api.lib.perm.acl.resource import ResourceTypeCRUD
3031
from api.lib.perm.acl.role import RoleCRUD
3132
from api.lib.perm.acl.user import UserCRUD
33+
from api.lib.secrets.inner import KeyManage
34+
from api.lib.secrets.inner import global_key_threshold
35+
from api.lib.secrets.secrets import InnerKVManger
3236
from api.models.acl import App
3337
from api.models.acl import ResourceType
3438
from api.models.cmdb import Attribute
@@ -53,6 +57,7 @@ def cmdb_init_cache():
5357
if relations:
5458
rd.create_or_update(relations, REDIS_PREFIX_CI_RELATION)
5559

60+
es = None
5661
if current_app.config.get("USE_ES"):
5762
from api.extensions import es
5863
from api.models.cmdb import Attribute
@@ -311,3 +316,128 @@ def cmdb_index_table_upgrade():
311316
CIIndexValueDateTime.create(ci_id=i.ci_id, attr_id=i.attr_id, value=i.value, commit=False)
312317
i.delete(commit=False)
313318
db.session.commit()
319+
320+
321+
@click.command()
322+
@click.option(
323+
'-a',
324+
'--address',
325+
help='inner cmdb api, http://127.0.0.1:8000',
326+
)
327+
@with_appcontext
328+
def cmdb_inner_secrets_init(address):
329+
"""
330+
init inner secrets for password feature
331+
"""
332+
KeyManage(backend=InnerKVManger).init()
333+
334+
if address and address.startswith("http") and current_app.config.get("INNER_TRIGGER_TOKEN", "") != "":
335+
resp = requests.post("{}/api/v0.1/secrets/auto_seal".format(address.strip("/")),
336+
headers={"Inner-Token": current_app.config.get("INNER_TRIGGER_TOKEN", "")})
337+
if resp.status_code == 200:
338+
KeyManage.print_response(resp.json())
339+
else:
340+
KeyManage.print_response({"message": resp.text, "status": "failed"})
341+
342+
343+
@click.command()
344+
@click.option(
345+
'-a',
346+
'--address',
347+
help='inner cmdb api, http://127.0.0.1:8000',
348+
required=True,
349+
)
350+
@with_appcontext
351+
def cmdb_inner_secrets_unseal(address):
352+
"""
353+
unseal the secrets feature
354+
"""
355+
address = "{}/api/v0.1/secrets/unseal".format(address.strip("/"))
356+
if not address.startswith("http"):
357+
KeyManage.print_response({"message": "invalid address, should start with http", "status": "failed"})
358+
return
359+
for i in range(global_key_threshold):
360+
token = click.prompt(f'Enter unseal token {i + 1}', hide_input=True, confirmation_prompt=False)
361+
assert token is not None
362+
resp = requests.post(address, headers={"Unseal-Token": token})
363+
if resp.status_code == 200:
364+
KeyManage.print_response(resp.json())
365+
else:
366+
KeyManage.print_response({"message": resp.text, "status": "failed"})
367+
return
368+
369+
370+
@click.command()
371+
@click.option(
372+
'-a',
373+
'--address',
374+
help='inner cmdb api, http://127.0.0.1:8000',
375+
required=True,
376+
)
377+
@click.option(
378+
'-k',
379+
'--token',
380+
help='root token',
381+
prompt=True,
382+
hide_input=True,
383+
)
384+
@with_appcontext
385+
def cmdb_inner_secrets_seal(address, token):
386+
"""
387+
seal the secrets feature
388+
"""
389+
assert address is not None
390+
assert token is not None
391+
if address.startswith("http"):
392+
address = "{}/api/v0.1/secrets/seal".format(address.strip("/"))
393+
resp = requests.post(address, headers={
394+
"Inner-Token": token,
395+
})
396+
if resp.status_code == 200:
397+
KeyManage.print_response(resp.json())
398+
else:
399+
KeyManage.print_response({"message": resp.text, "status": "failed"})
400+
401+
402+
@click.command()
403+
@with_appcontext
404+
def cmdb_password_data_migrate():
405+
"""
406+
Migrate CI password data, version >= v2.3.6
407+
"""
408+
from api.models.cmdb import CIIndexValueText
409+
from api.models.cmdb import CIValueText
410+
from api.lib.secrets.inner import InnerCrypt
411+
from api.lib.secrets.vault import VaultClient
412+
413+
attrs = Attribute.get_by(to_dict=False)
414+
for attr in attrs:
415+
if attr.is_password:
416+
417+
value_table = CIIndexValueText if attr.is_index else CIValueText
418+
419+
for i in value_table.get_by(attr_id=attr.id, to_dict=False):
420+
if current_app.config.get("SECRETS_ENGINE", 'inner') == 'inner':
421+
_, status = InnerCrypt().decrypt(i.value)
422+
if status:
423+
continue
424+
425+
encrypt_value, status = InnerCrypt().encrypt(i.value)
426+
if status:
427+
CIValueText.create(ci_id=i.ci_id, attr_id=attr.id, value=encrypt_value)
428+
else:
429+
continue
430+
elif current_app.config.get("SECRETS_ENGINE") == 'vault':
431+
if i.value == '******':
432+
continue
433+
434+
vault = VaultClient(current_app.config.get('VAULT_URL'), current_app.config.get('VAULT_TOKEN'))
435+
try:
436+
vault.update("/{}/{}".format(i.ci_id, i.attr_id), dict(v=i.value))
437+
except Exception as e:
438+
print('save password to vault failed: {}'.format(e))
439+
continue
440+
else:
441+
continue
442+
443+
i.delete()

cmdb-api/api/extensions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
from api.lib.utils import ESHandler
1313
from api.lib.utils import RedisHandler
1414

15+
from api.lib.secrets.inner import KeyManage
16+
17+
1518
bcrypt = Bcrypt()
1619
login_manager = LoginManager()
1720
db = SQLAlchemy(session_options={"autoflush": False})
@@ -21,3 +24,4 @@
2124
cors = CORS(supports_credentials=True)
2225
rd = RedisHandler()
2326
es = ESHandler()
27+
inner_secrets = KeyManage()

0 commit comments

Comments
 (0)