# -*- coding: utf-8 -*- """ @File : issue_certificate_api.py @Date : 2023-07-23 """ import json import requests from flask import g, request from playhouse.shortcuts import model_to_dict, chunked from domain_admin.enums.role_enum import RoleEnum from domain_admin.model.dns_model import DnsModel from domain_admin.model.domain_model import DomainModel from domain_admin.model.host_model import HostModel from domain_admin.model.issue_certificate_model import IssueCertificateModel, ChallengeDeployTypeEnum, \ SSLDeployTypeEnum, DeployStatusEnum from domain_admin.service import issue_certificate_service, auth_service from domain_admin.utils import ip_util, domain_util, fabric_util, datetime_util, validate_util from domain_admin.utils.acme_util.challenge_type import ChallengeType from domain_admin.utils.acme_util.key_type_enum import KEY_TYPE_OPTIONS, KeyTypeEnum from domain_admin.utils.acme_util.directory_type_enum import DIRECTORY_URL_OPTIONS, DirectoryTypeEnum from domain_admin.utils.flask_ext.app_exception import AppException, DataNotFoundAppException from domain_admin.utils.open_api import aliyun_domain_api from domain_admin.utils.open_api.aliyun_domain_api import RecordTypeEnum @auth_service.permission(role=RoleEnum.USER) def issue_certificate(): """ 发起申请 :return: """ current_user_id = g.user_id domains = request.json['domains'] directory_type = request.json.get('directory_type') or DirectoryTypeEnum.LETS_ENCRYPT key_type = request.json.get('key_type') or KeyTypeEnum.RSA issue_certificate_id = issue_certificate_service.issue_certificate( domains=domains, user_id=current_user_id, directory_type=directory_type, key_type=key_type ) issue_certificate_row = IssueCertificateModel.get_by_id(issue_certificate_id) return issue_certificate_row.to_dict() @auth_service.permission(role=RoleEnum.USER) def verify_certificate(): """ 通知验证 :return: """ current_user_id = g.user_id issue_certificate_id = request.json['issue_certificate_id'] challenge_type = request.json['challenge_type'] # 验证文件部署信息 可选参数 verify_deploy_path = request.json.get('verify_deploy_path') host_id = request.json.get('host_id') dns_id = request.json.get('dns_id') # 验证成功后,自动添加到证书监控列表 issue_certificate_row = IssueCertificateModel.select().where( IssueCertificateModel.id == issue_certificate_id, IssueCertificateModel.user_id == current_user_id, ).first() if not issue_certificate_row: raise DataNotFoundAppException() issue_certificate_service.verify_certificate( issue_certificate_id=issue_certificate_id, challenge_type=challenge_type, verify_deploy_path=verify_deploy_path, host_id=host_id, dns_id=dns_id ) if host_id and verify_deploy_path: IssueCertificateModel.update( challenge_deploy_type_id=ChallengeDeployTypeEnum.SSH, challenge_deploy_id=host_id, deploy_verify_path=verify_deploy_path, challenge_deploy_status=DeployStatusEnum.SUCCESS ).where( IssueCertificateModel.id == issue_certificate_id ).execute() if dns_id: IssueCertificateModel.update( challenge_deploy_type_id=ChallengeDeployTypeEnum.DNS, challenge_deploy_id=dns_id, challenge_deploy_status=DeployStatusEnum.SUCCESS ).where( IssueCertificateModel.id == issue_certificate_id ).execute() issue_certificate_service.renew_certificate(issue_certificate_id) # fix: 过滤通配符的域名 lst = [ { 'domain': domain, 'root_domain': domain_util.get_root_domain(domain), 'port': 443, 'alias': '', 'user_id': current_user_id, 'group_id': 0, } for domain in issue_certificate_row.domains if validate_util.is_domain(domain) ] for batch in chunked(lst, 10): DomainModel.insert_many(batch).on_conflict_ignore().execute() @auth_service.permission(role=RoleEnum.USER) def get_certificate_challenges(): current_user_id = g.user_id issue_certificate_id = request.json['issue_certificate_id'] # data check issue_certificate_row = IssueCertificateModel.select().where( IssueCertificateModel.id == issue_certificate_id, IssueCertificateModel.user_id == current_user_id, ).first() if not issue_certificate_row: raise DataNotFoundAppException() lst = issue_certificate_service.get_certificate_challenges(issue_certificate_id) return { 'total': len(lst), 'list': lst } @auth_service.permission(role=RoleEnum.USER) def get_domain_host(): domain = request.json['domain'] host = ip_util.get_domain_ip(domain) return { 'domain': domain, 'host': host } @auth_service.permission(role=RoleEnum.USER) def deploy_verify_file(): """ 部署验证文件 :return: """ current_user_id = g.user_id issue_certificate_id = request.json['issue_certificate_id'] verify_deploy_path = request.json['verify_deploy_path'] challenges = request.json['challenges'] host_id = request.json['host_id'] if not verify_deploy_path.endswith("/"): raise AppException("verify_deploy_path must endswith '/'") # data check issue_certificate_row = IssueCertificateModel.select().where( IssueCertificateModel.id == issue_certificate_id, IssueCertificateModel.user_id == current_user_id, ).first() if not issue_certificate_row: raise DataNotFoundAppException() # deploy issue_certificate_service.deploy_verify_file( host_id=host_id, verify_deploy_path=verify_deploy_path, challenges=challenges ) IssueCertificateModel.update( challenge_deploy_type_id=ChallengeDeployTypeEnum.SSH, challenge_deploy_id=host_id, deploy_verify_path=verify_deploy_path, challenge_deploy_status=DeployStatusEnum.SUCCESS ).where( IssueCertificateModel.id == issue_certificate_id ).execute() @auth_service.permission(role=RoleEnum.USER) def deploy_certificate_file(): """ ssh方式部署证书文件 :return: """ current_user_id = g.user_id issue_certificate_id = request.json['issue_certificate_id'] host_id = request.json['host_id'] key_deploy_path = request.json['key_deploy_path'] pem_deploy_path = request.json['pem_deploy_path'] reload_cmd = request.json['reloadcmd'] # data check issue_certificate_row = IssueCertificateModel.select().where( IssueCertificateModel.id == issue_certificate_id, IssueCertificateModel.user_id == current_user_id, ).first() if not issue_certificate_row: raise DataNotFoundAppException() # host_row = HostModel.get_by_id(host_id) # data check host_row = HostModel.select().where( HostModel.id == host_id, HostModel.user_id == current_user_id, ).first() if not host_row: raise DataNotFoundAppException() host = host_row.host user = host_row.user password = host_row.password # issue_certificate_row = IssueCertificateModel.get_by_id(issue_certificate_id) if not issue_certificate_row.ssl_certificate: issue_certificate_service.renew_certificate(issue_certificate_id) # issue_certificate_row = IssueCertificateModel.get_by_id(issue_certificate_id) # deploy key issue_certificate_row = IssueCertificateModel.get_by_id(issue_certificate_id) issue_certificate_service.deploy_certificate_file( host_id=host_id, key_content=issue_certificate_row.ssl_certificate_key, pem_content=issue_certificate_row.ssl_certificate, key_deploy_path=key_deploy_path, pem_deploy_path=pem_deploy_path, reload_cmd=reload_cmd ) # update only support file verify # if issue_certificate_row.challenge_type == ChallengeType.HTTP01: # is_auto_renew = True # else: # is_auto_renew = False IssueCertificateModel.update( deploy_type_id=SSLDeployTypeEnum.SSH, deploy_host_id=host_id, deploy_key_file=key_deploy_path, deploy_fullchain_file=pem_deploy_path, deploy_reloadcmd=reload_cmd, ssl_deploy_status=DeployStatusEnum.SUCCESS, ).where( IssueCertificateModel.id == issue_certificate_id ).execute() # 验证成功后, check_auto_renew issue_certificate_service.check_auto_renew( issue_certificate_id=issue_certificate_id ) @auth_service.permission(role=RoleEnum.USER) def renew_certificate(): """ 发起申请 :return: """ current_user_id = g.user_id issue_certificate_id = request.json['issue_certificate_id'] # data check issue_certificate_row = IssueCertificateModel.select().where( IssueCertificateModel.id == issue_certificate_id, IssueCertificateModel.user_id == current_user_id, ).first() if not issue_certificate_row: raise DataNotFoundAppException() issue_certificate_service.renew_certificate(issue_certificate_id) issue_certificate_row = IssueCertificateModel.get_by_id(issue_certificate_id) if not issue_certificate_row: raise AppException('数据不存在') return issue_certificate_row.to_dict() @auth_service.permission(role=RoleEnum.USER) def get_issue_certificate_list(): """ 发起申请 :return: """ current_user_id = g.user_id page = request.json.get('page', 1) size = request.json.get('size', 10) keyword = request.json.get('keyword') query = IssueCertificateModel.select().where( IssueCertificateModel.user_id == current_user_id ) if keyword: query = query.where(IssueCertificateModel.domain_raw.contains(keyword)) total = query.count() rows = query.order_by( IssueCertificateModel.update_time.desc(), IssueCertificateModel.id.desc() ).paginate(page, size) lst = [] for row in rows: item = model_to_dict( row, extra_attrs=[ 'domains', 'create_time_label', 'update_time_label', 'start_date', 'expire_date', 'has_ssl_certificate', 'can_auto_renew', ], exclude=[ IssueCertificateModel.ssl_certificate, IssueCertificateModel.ssl_certificate_key ]) # 关联信息 item['challenge_deploy_name'] = '-' if row.challenge_deploy_type_id == ChallengeDeployTypeEnum.SSH: host = HostModel.get_or_none(HostModel.id == row.challenge_deploy_id) if host: item['challenge_deploy_name'] = host.host elif row.challenge_deploy_type_id == ChallengeDeployTypeEnum.DNS: dns = DnsModel.get_or_none(DnsModel.id == row.challenge_deploy_id) if dns: item['challenge_deploy_name'] = dns.name item['deploy_host_name'] = '-' if row.deploy_type_id == SSLDeployTypeEnum.SSH: host = HostModel.get_or_none(HostModel.id == row.deploy_host_id) if host: item['deploy_host_name'] = host.host elif row.deploy_type_id in [SSLDeployTypeEnum.OSS, SSLDeployTypeEnum.CDN, SSLDeployTypeEnum.DCDN]: dns = DnsModel.get_or_none(DnsModel.id == row.deploy_host_id) if dns: item['deploy_host_name'] = dns.name lst.append(item) return { 'list': lst, 'total': total, } @auth_service.permission(role=RoleEnum.USER) def get_issue_certificate_by_id(): """ 获取 :return: """ current_user_id = g.user_id issue_certificate_id = request.json['issue_certificate_id'] # check data issue_certificate_row = IssueCertificateModel.select().where( IssueCertificateModel.id == issue_certificate_id, IssueCertificateModel.user_id == current_user_id ).first() if not issue_certificate_row: raise DataNotFoundAppException() data = issue_certificate_row.to_dict() data['deploy_dns'] = None data['deploy_host'] = None data['cert_deploy_host'] = None data['cert_deploy_dns'] = None if issue_certificate_row.challenge_deploy_type_id == ChallengeDeployTypeEnum.SSH: host_row = HostModel.get_or_none(HostModel.id == issue_certificate_row.challenge_deploy_id) data['deploy_host'] = host_row if host_row and host_row.dns_id: data['deploy_dns'] = DnsModel.get_or_none(DnsModel.id == host_row.dns_id) elif issue_certificate_row.challenge_deploy_type_id == ChallengeDeployTypeEnum.DNS: data['deploy_dns'] = DnsModel.get_or_none(DnsModel.id == issue_certificate_row.challenge_deploy_id) if issue_certificate_row.deploy_type_id == SSLDeployTypeEnum.SSH: data['cert_deploy_host'] = HostModel.get_or_none(HostModel.id == issue_certificate_row.deploy_host_id) elif issue_certificate_row.deploy_type_id in [SSLDeployTypeEnum.OSS, SSLDeployTypeEnum.CDN, SSLDeployTypeEnum.DCDN]: data['cert_deploy_dns'] = DnsModel.get_or_none(DnsModel.id == issue_certificate_row.deploy_host_id) return data @auth_service.permission(role=RoleEnum.USER) def delete_issue_certificate_by_id(): """ 获取 :return: """ current_user_id = g.user_id issue_certificate_id = request.json['issue_certificate_id'] # check data issue_certificate_row = IssueCertificateModel.select().where( IssueCertificateModel.id == issue_certificate_id, IssueCertificateModel.user_id == current_user_id ).first() if not issue_certificate_row: raise DataNotFoundAppException() IssueCertificateModel.delete_by_id(issue_certificate_row.id) @auth_service.permission(role=RoleEnum.USER) def delete_certificate_by_batch(): """ 批量删除 @since v1.2.16 :return: """ current_user_id = g.user_id ids = request.json['ids'] IssueCertificateModel.delete().where( IssueCertificateModel.id.in_(ids), IssueCertificateModel.user_id == current_user_id ).execute() @auth_service.permission(role=RoleEnum.USER) def renew_issue_certificate_by_id(): """ 手动续期SSL证书 :return: """ current_user_id = g.user_id issue_certificate_id = request.json['issue_certificate_id'] # check data issue_certificate_row = IssueCertificateModel.select().where( IssueCertificateModel.id == issue_certificate_id, IssueCertificateModel.user_id == current_user_id ).first() if not issue_certificate_row: raise DataNotFoundAppException() issue_certificate_service.renew_certificate_row(issue_certificate_row) @auth_service.permission(role=RoleEnum.USER) def get_allow_commands(): """ 命令白名单 :return: """ return fabric_util.allow_commands @auth_service.permission(role=RoleEnum.USER) def notify_web_hook(): """ 用户调用webhook :return: """ current_user_id = g.user_id issue_certificate_id = request.json['issue_certificate_id'] url = request.json['url'] headers = request.json.get('headers') # data check issue_certificate_row = IssueCertificateModel.select().where( IssueCertificateModel.id == issue_certificate_id, IssueCertificateModel.user_id == current_user_id, ).first() if not issue_certificate_row: raise DataNotFoundAppException() ret = issue_certificate_service.deploy_ssl_by_web_hook( issue_certificate_id=issue_certificate_id, url=url, headers=headers, ) # 更新验证信息 IssueCertificateModel.update( deploy_type_id=SSLDeployTypeEnum.WEB_HOOK, deploy_url=url, deploy_header_raw=json.dumps(headers or {}), ssl_deploy_status=DeployStatusEnum.SUCCESS ).where( IssueCertificateModel.id == issue_certificate_id ).execute() # 验证成功后, check_auto_renew issue_certificate_service.check_auto_renew( issue_certificate_id=issue_certificate_id ) return ret @auth_service.permission(role=RoleEnum.USER) def deploy_cert_to_oss(): """ 部署证书到阿里云oss :return: """ current_user_id = g.user_id issue_certificate_id = request.json['issue_certificate_id'] dns_id = request.json['dns_id'] # data check issue_certificate_row = IssueCertificateModel.select().where( IssueCertificateModel.id == issue_certificate_id, IssueCertificateModel.user_id == current_user_id, ).first() if not issue_certificate_row: raise DataNotFoundAppException() ret = issue_certificate_service.deploy_cert_to_oss( issue_certificate_id=issue_certificate_id, dns_id=dns_id, ) # 更新验证信息 IssueCertificateModel.update( deploy_type_id=SSLDeployTypeEnum.OSS, deploy_host_id=dns_id, ssl_deploy_status=DeployStatusEnum.SUCCESS ).where( IssueCertificateModel.id == issue_certificate_id ).execute() # 验证成功后, check_auto_renew issue_certificate_service.check_auto_renew( issue_certificate_id=issue_certificate_id ) return ret @auth_service.permission(role=RoleEnum.USER) def deploy_cert_to_cdn(): """ 部署证书到阿里云cdn :return: """ current_user_id = g.user_id issue_certificate_id = request.json['issue_certificate_id'] dns_id = request.json['dns_id'] # data check issue_certificate_row = IssueCertificateModel.select().where( IssueCertificateModel.id == issue_certificate_id, IssueCertificateModel.user_id == current_user_id, ).first() if not issue_certificate_row: raise DataNotFoundAppException() ret = issue_certificate_service.deploy_cert_to_cdn( issue_certificate_id=issue_certificate_id, dns_id=dns_id, ) # 更新验证信息 IssueCertificateModel.update( deploy_type_id=SSLDeployTypeEnum.CDN, deploy_host_id=dns_id, ssl_deploy_status=DeployStatusEnum.SUCCESS ).where( IssueCertificateModel.id == issue_certificate_id ).execute() # 验证成功后, check_auto_renew issue_certificate_service.check_auto_renew( issue_certificate_id=issue_certificate_id ) return ret @auth_service.permission(role=RoleEnum.USER) def deploy_cert_to_dcdn(): """ 部署证书到阿里云dcdn :return: """ current_user_id = g.user_id issue_certificate_id = request.json['issue_certificate_id'] dns_id = request.json['dns_id'] # data check issue_certificate_row = IssueCertificateModel.select().where( IssueCertificateModel.id == issue_certificate_id, IssueCertificateModel.user_id == current_user_id, ).first() if not issue_certificate_row: raise DataNotFoundAppException() ret = issue_certificate_service.deploy_cert_to_dcdn( issue_certificate_id=issue_certificate_id, dns_id=dns_id, ) # 更新验证信息 IssueCertificateModel.update( deploy_type_id=SSLDeployTypeEnum.DCDN, deploy_host_id=dns_id, ssl_deploy_status=DeployStatusEnum.SUCCESS ).where( IssueCertificateModel.id == issue_certificate_id ).execute() # 验证成功后, check_auto_renew issue_certificate_service.check_auto_renew( issue_certificate_id=issue_certificate_id ) return ret @auth_service.permission(role=RoleEnum.USER) def add_dns_domain_record(): """ 添加dns记录 :return: """ current_user_id = g.user_id dns_id = request.json['dns_id'] issue_certificate_id = request.json['issue_certificate_id'] # data check issue_certificate_row = IssueCertificateModel.select().where( IssueCertificateModel.id == issue_certificate_id, IssueCertificateModel.user_id == current_user_id, ).first() if not issue_certificate_row: raise DataNotFoundAppException() challenges = issue_certificate_service.get_certificate_challenges(issue_certificate_id) # 添加txt记录 issue_certificate_service.add_dns_domain_record( dns_id=dns_id, challenges=challenges ) # 更新验证信息 IssueCertificateModel.update( challenge_deploy_type_id=ChallengeDeployTypeEnum.DNS, challenge_deploy_id=dns_id, challenge_deploy_status=DeployStatusEnum.SUCCESS ).where( IssueCertificateModel.id == issue_certificate_id ).execute() @auth_service.permission(role=RoleEnum.USER) def update_row_auto_renew(): """ 修改自动更新字段 :return: """ current_user_id = g.user_id issue_certificate_id = request.json['issue_certificate_id'] is_auto_renew = request.json['is_auto_renew'] # check data issue_certificate_row = IssueCertificateModel.select().where( IssueCertificateModel.id == issue_certificate_id, IssueCertificateModel.user_id == current_user_id ).first() if not issue_certificate_row: raise DataNotFoundAppException() if issue_certificate_row and issue_certificate_row.can_auto_renew: # 更新验证信息 IssueCertificateModel.update( is_auto_renew=is_auto_renew ).where( IssueCertificateModel.id == issue_certificate_id ).execute() else: raise AppException("不支持自动续期") @auth_service.permission(role=RoleEnum.USER) def get_issue_certificate_options(): """ 获取常量 :return: """ return { 'KEY_TYPE_OPTIONS': KEY_TYPE_OPTIONS, 'DIRECTORY_URL_OPTIONS': DIRECTORY_URL_OPTIONS }