diff --git a/cmdb-api/api/app.py b/cmdb-api/api/app.py index 348a6b5..c1c8d06 100644 --- a/cmdb-api/api/app.py +++ b/cmdb-api/api/app.py @@ -21,6 +21,7 @@ from api.extensions import (bcrypt, cache, celery, cors, db, es, login_manager, from api.extensions import inner_secrets from api.flask_cas import CAS from api.models.acl import User +from api.lib.secrets.secrets import InnerKVManger HERE = os.path.abspath(os.path.dirname(__file__)) PROJECT_ROOT = os.path.join(HERE, os.pardir) @@ -126,7 +127,7 @@ def register_extensions(app): app.config.update(app.config.get("CELERY")) celery.conf.update(app.config) - inner_secrets.init_app(app) + inner_secrets.init_app(app, InnerKVManger()) def register_blueprints(app): diff --git a/cmdb-api/api/commands/click_cmdb.py b/cmdb-api/api/commands/click_cmdb.py index 08c84e8..6c9b774 100644 --- a/cmdb-api/api/commands/click_cmdb.py +++ b/cmdb-api/api/commands/click_cmdb.py @@ -7,6 +7,7 @@ import json import time import click +import requests from flask import current_app from flask.cli import with_appcontext from flask_login import login_user @@ -318,28 +319,61 @@ def cmdb_index_table_upgrade(): @click.command() +@click.option( + '-a', + '--address', + help='inner cmdb api, http://127.0.0.1:8000', +) @with_appcontext -def cmdb_inner_secrets_init(): +def cmdb_inner_secrets_init(address): """ init inner secrets for password feature """ KeyManage(backend=InnerKVManger).init() + if address and address.startswith("http") and current_app.config.get("INNER_TRIGGER_TOKEN", "") != "": + resp = requests.post("{}/api/v0.1/secrets/auto_seal".format(address.strip("/")), + headers={"Inner-Token": current_app.config.get("INNER_TRIGGER_TOKEN", "")}) + if resp.status_code == 200: + KeyManage.print_response(resp.json()) + else: + KeyManage.print_response({"message": resp.text, "status": "failed"}) + @click.command() +@click.option( + '-a', + '--address', + help='inner cmdb api, http://127.0.0.1:8000', + required=True, +) @with_appcontext -def cmdb_inner_secrets_unseal(): +def cmdb_inner_secrets_unseal(address): """ unseal the secrets feature """ + address = "{}/api/v0.1/secrets/unseal".format(address.strip("/")) + if not address.startswith("http"): + KeyManage.print_response({"message": "invalid address, should start with http", "status": "failed"}) + return for i in range(global_key_threshold): - token = click.prompt(f'Enter token {i + 1}', hide_input=True, confirmation_prompt=False) + token = click.prompt(f'Enter unseal token {i + 1}', hide_input=True, confirmation_prompt=False) assert token is not None - res = KeyManage(backend=InnerKVManger).unseal(token) - KeyManage.print_response(res) + resp = requests.post(address, headers={"Unseal-Token": token}) + if resp.status_code == 200: + KeyManage.print_response(resp.json()) + else: + KeyManage.print_response({"message": resp.text, "status": "failed"}) + return @click.command() +@click.option( + '-a', + '--address', + help='inner cmdb api, http://127.0.0.1:8000', + required=True, +) @click.option( '-k', '--token', @@ -348,23 +382,21 @@ def cmdb_inner_secrets_unseal(): hide_input=True, ) @with_appcontext -def cmdb_inner_secrets_seal(token): +def cmdb_inner_secrets_seal(address, token): """ seal the secrets feature """ + assert address is not None assert token is not None - res = KeyManage(backend=InnerKVManger()).seal(token) - KeyManage.print_response(res) - - -@click.command() -@with_appcontext -def cmdb_inner_secrets_auto_seal(): - """ - auto seal the secrets feature - """ - res = KeyManage(current_app.config.get("INNER_TRIGGER_TOKEN"), backend=InnerKVManger()).auto_unseal() - KeyManage.print_response(res) + if address.startswith("http"): + address = "{}/api/v0.1/secrets/seal".format(address.strip("/")) + resp = requests.post(address, headers={ + "Inner-Token": token, + }) + if resp.status_code == 200: + KeyManage.print_response(resp.json()) + else: + KeyManage.print_response({"message": resp.text, "status": "failed"}) @click.command() diff --git a/cmdb-api/api/extensions.py b/cmdb-api/api/extensions.py index 0dd13ad..cf68700 100644 --- a/cmdb-api/api/extensions.py +++ b/cmdb-api/api/extensions.py @@ -14,6 +14,7 @@ from api.lib.utils import RedisHandler from api.lib.secrets.inner import KeyManage + bcrypt = Bcrypt() login_manager = LoginManager() db = SQLAlchemy(session_options={"autoflush": False}) diff --git a/cmdb-api/api/lib/secrets/inner.py b/cmdb-api/api/lib/secrets/inner.py index 6394373..9ce8857 100644 --- a/cmdb-api/api/lib/secrets/inner.py +++ b/cmdb-api/api/lib/secrets/inner.py @@ -60,8 +60,13 @@ class KeyManage: if backend: self.backend = Backend(backend) - def init_app(self, app): - self.auto_unseal() + def init_app(self, app, backend=None): + self.trigger = app.config.get("INNER_TRIGGER_TOKEN") + if not self.trigger: + return + self.backend = backend + # resp = self.auto_unseal() + # self.print_response(resp) def hash_root_key(self, value): algorithm = hashes.SHA256() @@ -133,15 +138,17 @@ class KeyManage: "message": "encrypt key is empty", "status": "failed" } - secrets_encrypt_key = InnerCrypt.aes_decrypt(string_to_bytes(root_key), encrypt_key_aes) - setattr(current_app, 'secrets_encrypt_key', secrets_encrypt_key) - setattr(current_app, 'secrets_root_key', root_key) - setattr(current_app, 'secrets_shares', []) - - return { - "message": success, - "status": success - } + secrets_encrypt_key, ok = InnerCrypt.aes_decrypt(string_to_bytes(root_key), encrypt_key_aes) + if ok: + current_app.config["secrets_encrypt_key"] = secrets_encrypt_key + current_app.config["secrets_root_key"] = root_key + current_app.config["secrets_shares"] = [] + return {"message": success, "status": success} + else: + return { + "message": secrets_encrypt_key, + "status": "failed" + } def unseal(self, key): if not self.is_seal(): @@ -152,10 +159,14 @@ class KeyManage: try: t = [i for i in b64decode(key)] v = (int("".join([chr(i) for i in t[-2:]])), bytes(t[:-2])) - shares = getattr(current_app, "secrets_shares", []) + print("............") + # shares = getattr(current_app.config, "secrets_shares", []) + shares = current_app.config.get("secrets_shares", []) + print("222222222222") if v not in shares: shares.append(v) - setattr(current_app, "secrets_shares", shares) + current_app.config["secrets_shares"] = shares + print("shares:", shares) if len(shares) >= global_key_threshold: recovered_secret = Shamir.combine(shares[:global_key_threshold]) return self.auth_root_secret(b64encode(recovered_secret)) @@ -209,10 +220,10 @@ class KeyManage: msg, ok = self.backend.add(backend_encrypt_key_name, encrypt_key_aes) if not ok: return {"message": msg}, False - # - setattr(current_app, 'secrets_root_key', root_key) - setattr(current_app, 'secrets_encrypt_key', encrypt_key) + current_app.config["secrets_root_key"] = root_key + current_app.config["secrets_encrypt_key"] = encrypt_key + print(".....", current_app.config["secrets_root_key"], current_app.config["secrets_encrypt_key"]) self.print_token(shares, root_token=root_key) return {"message": "OK", @@ -271,8 +282,8 @@ class KeyManage: "status": "failed" } else: - setattr(current_app, 'secrets_root_key', '') - setattr(current_app, 'secrets_encrypt_key', '') + current_app.config["secrets_root_key"] = '' + current_app.config["secrets_encrypt_key"] = '' return { "message": success, @@ -284,7 +295,7 @@ class KeyManage: If there is no initialization or the root key is inconsistent, it is considered to be in a sealed state. :return: """ - secrets_root_key = getattr(current_app, 'secrets_root_key') + secrets_root_key = current_app.config.get("secrets_root_key") root_key = self.backend.get(backend_root_key_name) if root_key == "" or root_key != secrets_root_key: return "invalid root key", True @@ -326,7 +337,8 @@ class KeyManage: class InnerCrypt: def __init__(self): - secrets_encrypt_key = getattr(current_app, 'secrets_encrypt_key', '') + secrets_encrypt_key = current_app.config.get("secrets_encrypt_key", "") + print("secrets_encrypt_key:", secrets_encrypt_key) self.encrypt_key = b64decode(secrets_encrypt_key.encode("utf-8")) def encrypt(self, plaintext): diff --git a/cmdb-api/api/resource.py b/cmdb-api/api/resource.py index 271d4b0..1fc79f5 100644 --- a/cmdb-api/api/resource.py +++ b/cmdb-api/api/resource.py @@ -46,5 +46,4 @@ def register_resources(resource_path, rest_api): resource_cls.url_prefix = ("",) if isinstance(resource_cls.url_prefix, six.string_types): resource_cls.url_prefix = (resource_cls.url_prefix,) - rest_api.add_resource(resource_cls, *resource_cls.url_prefix) diff --git a/cmdb-api/api/views/cmdb/inner_secrets.py b/cmdb-api/api/views/cmdb/inner_secrets.py new file mode 100644 index 0000000..512f86d --- /dev/null +++ b/cmdb-api/api/views/cmdb/inner_secrets.py @@ -0,0 +1,38 @@ +from api.resource import APIView +from api.lib.secrets.inner import KeyManage +from api.lib.secrets.secrets import InnerKVManger + +from flask import request, abort + + +class InnerSecretUnSealView(APIView): + url_prefix = "/secrets/unseal" + + def post(self): + unseal_key = request.headers.get("Unseal-Token") + res = KeyManage(backend=InnerKVManger()).unseal(unseal_key) + # if res.get("status") == "failed": + # return abort(400, res.get("message")) + return self.jsonify(**res) + + +class InnerSecretSealView(APIView): + url_prefix = "/secrets/seal" + + def post(self): + unseal_key = request.headers.get("Inner-Token") + res = KeyManage(backend=InnerKVManger()).seal(unseal_key) + # if res.get("status") == "failed": + # return abort(400, res.get("message")) + return self.jsonify(**res) + + +class InnerSecretAutoSealView(APIView): + url_prefix = "/secrets/auto_seal" + + def post(self): + unseal_key = request.headers.get("Inner-Token") + res = KeyManage(backend=InnerKVManger()).seal(unseal_key) + # if res.get("status") == "failed": + # return abort(400, res.get("message")) + return self.jsonify(**res) diff --git a/cmdb-api/api/views/cmdb/secrets.py b/cmdb-api/api/views/cmdb/secrets.py deleted file mode 100644 index b98215f..0000000 --- a/cmdb-api/api/views/cmdb/secrets.py +++ /dev/null @@ -1,27 +0,0 @@ -from api.resource import APIView -from api.models.cmdb import InnerKV -from api.lib.secrets.inner import KeyManage - -from flask import request, abort - - -class InnerSecretUnSealView(APIView): - url_prefix = "/secrets/unseal" - - def post(self): - unseal_key = request.headers.get("Inner-Token") - res = KeyManage(InnerKV()).unseal(unseal_key) - if res.get("status") == "failed": - return abort(400, res.get("message")) - return self.jsonify(**res) - - -class InnerSecretSealView(APIView): - url_prefix = "/secrets/seal" - - def post(self): - unseal_key = request.headers.get("Inner-Token") - res = KeyManage(InnerKV()).seal(unseal_key) - if res.get("status") == "failed": - return abort(400, res.get("message")) - return self.jsonify(**res)