77import time
88
99import click
10+ import requests
1011from flask import current_app
1112from flask .cli import with_appcontext
1213from flask_login import login_user
2930from api .lib .perm .acl .resource import ResourceTypeCRUD
3031from api .lib .perm .acl .role import RoleCRUD
3132from 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
3236from api .models .acl import App
3337from api .models .acl import ResourceType
3438from 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 ()
0 commit comments