0%

Django-SSL证书管理

需求

  • 将域名的SSL证书保存到Django开发的系统中
  • 上传SSL证书文件,系统自动获取证书生效时间、过期时间、颁发机构
  • 校验SSL证书文件格式
  • 统一规范证书名称

思路

  • 使用Django数据库存储SSL证书文件信息,证书文件保存到Django服务器
  • 使用Django 信号(signals)监听model对象
  • 使用OpenSSL解析证书

代码

models.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
from django.db import models
from base.models import BaseModel
from .validators import validate_certificate_file_extension, validate_private_key_file_extension

class Certificate(BaseModel):
"""
SSL证书
"""
name = models.CharField(max_length=60, unique=True, verbose_name="名称")
certificate = models.TextField(
verbose_name='certificate', blank=True, null=True)

certificate_file = models.FileField(
verbose_name='certificate_file', upload_to='ssl/', validators=[validate_certificate_file_extension])
private_key = models.TextField(
verbose_name='private_key', blank=True, null=True)

private_key_file = models.FileField(
verbose_name='private_key_file', upload_to='ssl/', validators=[validate_private_key_file_extension])

before_time = models.CharField(
max_length=60, null=True, blank=True, verbose_name="生效时间")
after_time = models.CharField(
max_length=60, null=True, blank=True, verbose_name="过期时间")
organization = models.CharField(
max_length=20, null=True, blank=True, verbose_name="组织")
domain = models.CharField(
max_length=60, null=True, blank=True, verbose_name="域名")

class Meta:
verbose_name = "SSL证书"
verbose_name_plural = verbose_name
ordering = ['id']

def __str__(self):
return self.name

admin.py

1
2
3
4
5
6
7
8
9
10
11
12
from django.contrib import admin
from . import models
# Register your models here.

@admin.register(models.Certificate)
class CertificateAdmin(admin.ModelAdmin):
readonly_fields = ('domain', 'organization', 'before_time',
'after_time', 'private_key', 'certificate')
list_display = (
"id",
"name",
)

validators.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import os
from django.core.exceptions import ValidationError

def validate_certificate_file_extension(value):
ext = os.path.splitext(value.name)[1]
valid_extensions = ['.pem', '.crt']
if not ext.lower() in valid_extensions:
raise ValidationError('Unsupported file extension.')


def validate_private_key_file_extension(value):
ext = os.path.splitext(value.name)[1]
valid_extensions = ['.key']
if not ext.lower() in valid_extensions:
raise ValidationError('Unsupported file extension.')

signals.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import os
import random
import string

from django.db.models.signals import post_save
from django.dispatch import receiver

from .models import Certificate


@receiver(post_save, sender=Certificate)
def post_save_func(sender, **kwargs):
instance = kwargs['instance']
if kwargs['created']:
# 1.改名字,规范证书名字。
# 2.查询证书的生效时间、过期时间
c_file = instance.certificate_file.path
p_file = instance.private_key_file.path

with open(c_file, "r") as f:
cert_buf = f.read()

with open(c_file, "r") as f:
priv_buf = f.read()

instance.certificate = cert_buf
instance.private_key = priv_buf

from datetime import datetime

from OpenSSL import crypto
cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert_buf)

domain = cert.get_subject().get_components()[
0][1].decode(encoding='utf-8')

if domain:
instance.domain = domain

date_format, encoding = "%Y%m%d%H%M%SZ", "ascii"
not_before = datetime.strptime(
cert.get_notBefore().decode(encoding), date_format)
not_after = datetime.strptime(
cert.get_notAfter().decode(encoding), date_format)

instance.before_time = not_before
instance.after_time = not_after

for todo in cert.get_issuer().get_components():
if todo[0] == b'O':
organization = todo[1].decode(encoding='utf-8')
instance.organization = organization
dirname, c_filename = os.path.split(c_file)
dirname, p_filename = os.path.split(p_file)
file_extension = c_filename.split('.')[-1]
random_str = ''.join(random.sample(
string.ascii_letters + string.digits, 8))
new_c_filename = "{}_{}.{}".format(domain, random_str, file_extension)
new_c_file = os.path.join(dirname, c_file)
# os.rename(c_file, new_c_file)
from django.core.files.base import File
instance.certificate_file.save(
new_c_filename, File(open(new_c_file)))
file_extension = p_filename.split('.')[-1]
new_p_filename = "{}_{}.{}".format(domain, random_str, file_extension)
new_p_file = os.path.join(dirname, new_p_filename)
# os.rename(p_file, new_p_file)
instance.private_key_file.save(
new_p_filename, File(open(p_file)))
os.remove(p_file)
os.remove(c_file)

instance.save()

apps.py

1
2
3
4
5
6
7
8
9
from django.apps import AppConfig


class CmdbConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'apps.cmdb'

def ready(self):
from .signals import post_save_func

效果

新建证书时校验证书文件的后缀

文件上传后自动解析证书