InPanel 1.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- inpanel/__init__.py +29 -0
- inpanel/acme.py +289 -0
- inpanel/aliyuncs.py +269 -0
- inpanel/app.py +514 -0
- inpanel/apt.py +223 -0
- inpanel/base.py +200 -0
- inpanel/config.py +139 -0
- inpanel/data/config.ini +29 -0
- inpanel/data/history +5 -0
- inpanel/data/runlogs.ini +12 -0
- inpanel/data/update_info.ini +4 -0
- inpanel/dnf.py +225 -0
- inpanel/lib/__init__.py +9 -0
- inpanel/lib/async_process.py +98 -0
- inpanel/lib/filelock.py +82 -0
- inpanel/lib/magic.py +233 -0
- inpanel/lib/pxssh.py +310 -0
- inpanel/lib/pyDes.py +867 -0
- inpanel/mod/__init__.py +0 -0
- inpanel/mod/apt.py +316 -0
- inpanel/mod/certificate.py +268 -0
- inpanel/mod/config.py +372 -0
- inpanel/mod/cron.py +315 -0
- inpanel/mod/disk.py +322 -0
- inpanel/mod/dnf.py +239 -0
- inpanel/mod/file.py +722 -0
- inpanel/mod/firewall.py +199 -0
- inpanel/mod/firewall_base.py +137 -0
- inpanel/mod/firewall_firewalld.py +170 -0
- inpanel/mod/firewall_iptables.py +176 -0
- inpanel/mod/firewall_macos.py +213 -0
- inpanel/mod/firewall_ufw.py +128 -0
- inpanel/mod/ftp.py +231 -0
- inpanel/mod/httpd.py +1205 -0
- inpanel/mod/lighttpd.py +26 -0
- inpanel/mod/login.py +98 -0
- inpanel/mod/mysql.py +577 -0
- inpanel/mod/named.py +26 -0
- inpanel/mod/nginx.py +2327 -0
- inpanel/mod/package.py +33 -0
- inpanel/mod/package_apt.py +62 -0
- inpanel/mod/package_base.py +77 -0
- inpanel/mod/package_dnf.py +62 -0
- inpanel/mod/package_map.py +366 -0
- inpanel/mod/package_yum.py +64 -0
- inpanel/mod/php.py +289 -0
- inpanel/mod/process.py +291 -0
- inpanel/mod/proftpd.py +26 -0
- inpanel/mod/pureftpd.py +26 -0
- inpanel/mod/query.py +108 -0
- inpanel/mod/server.py +1319 -0
- inpanel/mod/service.py +262 -0
- inpanel/mod/setting.py +228 -0
- inpanel/mod/shell.py +78 -0
- inpanel/mod/ssh.py +280 -0
- inpanel/mod/system.py +220 -0
- inpanel/mod/task.py +1337 -0
- inpanel/mod/ufw.py +14 -0
- inpanel/mod/user.py +306 -0
- inpanel/mod/vsftpd.py +67 -0
- inpanel/mod/yum.py +243 -0
- inpanel/plugins/__init__.py +10 -0
- inpanel/plugins/__pycache__/__init__.cpython-313.pyc +0 -0
- inpanel/plugins/acme/static/index.html +10 -0
- inpanel/plugins/acme/static/index.js +13 -0
- inpanel/plugins/shadowsocks-libev/__pycache__/info.cpython-313.pyc +0 -0
- inpanel/plugins/shadowsocks-libev/info.py +73 -0
- inpanel/plugins/shadowsocks-libev/static/index.html +70 -0
- inpanel/plugins/shadowsocks-libev/static/index.js +18 -0
- inpanel/public/css/global.css +138 -0
- inpanel/public/favicon.ico +0 -0
- inpanel/public/images/alipay.jpg +0 -0
- inpanel/public/images/loading.gif +0 -0
- inpanel/public/images/weixin.jpg +0 -0
- inpanel/public/index.html +94 -0
- inpanel/public/js/controllers/controllers.js +566 -0
- inpanel/public/js/controllers/database.js +590 -0
- inpanel/public/js/controllers/ecs.js +624 -0
- inpanel/public/js/controllers/file.js +940 -0
- inpanel/public/js/controllers/plugins.js +105 -0
- inpanel/public/js/controllers/service.js +1460 -0
- inpanel/public/js/controllers/site.js +1241 -0
- inpanel/public/js/controllers/utils.js +2319 -0
- inpanel/public/js/core.js +221 -0
- inpanel/public/js/directives.js +784 -0
- inpanel/public/js/filters.js +395 -0
- inpanel/public/js/services.js +257 -0
- inpanel/public/lib/angular/1.0.2/angular-resource.min.js +10 -0
- inpanel/public/lib/angular/1.0.2/angular.min.js +158 -0
- inpanel/public/lib/bootstrap/css/bootstrap-theme.css +587 -0
- inpanel/public/lib/bootstrap/css/bootstrap-theme.css.map +1 -0
- inpanel/public/lib/bootstrap/css/bootstrap-theme.min.css +6 -0
- inpanel/public/lib/bootstrap/css/bootstrap-theme.min.css.map +1 -0
- inpanel/public/lib/bootstrap/css/bootstrap.css +6757 -0
- inpanel/public/lib/bootstrap/css/bootstrap.css.map +1 -0
- inpanel/public/lib/bootstrap/css/bootstrap.min.css +6 -0
- inpanel/public/lib/bootstrap/css/bootstrap.min.css.map +1 -0
- inpanel/public/lib/bootstrap/fonts/glyphicons-halflings-regular.eot +0 -0
- inpanel/public/lib/bootstrap/fonts/glyphicons-halflings-regular.svg +288 -0
- inpanel/public/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf +0 -0
- inpanel/public/lib/bootstrap/fonts/glyphicons-halflings-regular.woff +0 -0
- inpanel/public/lib/bootstrap/fonts/glyphicons-halflings-regular.woff2 +0 -0
- inpanel/public/lib/bootstrap/img/glyphicons-halflings-white.png +0 -0
- inpanel/public/lib/bootstrap/img/glyphicons-halflings.png +0 -0
- inpanel/public/lib/bootstrap/js/bootstrap.js +2377 -0
- inpanel/public/lib/bootstrap/js/bootstrap.min.js +7 -0
- inpanel/public/lib/bootstrap/js/npm.js +13 -0
- inpanel/public/lib/codemirror/codemirror-compressed.js +1 -0
- inpanel/public/lib/codemirror/codemirror.css +173 -0
- inpanel/public/lib/jquery/jquery-1.12.4.min.js +5 -0
- inpanel/public/lib/md5/md5.js +379 -0
- inpanel/public/lib/md5/md5.min.js +1 -0
- inpanel/public/partials/database/index.html +172 -0
- inpanel/public/partials/database/mysql/dbedit.html +462 -0
- inpanel/public/partials/database/mysql/dbnew.html +348 -0
- inpanel/public/partials/database/mysql/useredit.html +310 -0
- inpanel/public/partials/database/mysql/usernew.html +97 -0
- inpanel/public/partials/directives/message.html +20 -0
- inpanel/public/partials/directives/navbar.html +25 -0
- inpanel/public/partials/directives/selector.html +57 -0
- inpanel/public/partials/directives/srvbase.html +72 -0
- inpanel/public/partials/directives/srvext.html +34 -0
- inpanel/public/partials/directives/srvfile.html +23 -0
- inpanel/public/partials/directives/srvinstall.html +34 -0
- inpanel/public/partials/directives/srvlog.html +23 -0
- inpanel/public/partials/directives/srvminiop.html +28 -0
- inpanel/public/partials/directives/srvuninstall.html +38 -0
- inpanel/public/partials/directives/srvupdate.html +30 -0
- inpanel/public/partials/ecs/account.html +156 -0
- inpanel/public/partials/ecs/ecs.html +198 -0
- inpanel/public/partials/ecs/index.html +40 -0
- inpanel/public/partials/ecs/setings.html +459 -0
- inpanel/public/partials/file/file.html +501 -0
- inpanel/public/partials/file/trash.html +95 -0
- inpanel/public/partials/ftp.html +36 -0
- inpanel/public/partials/log.html +83 -0
- inpanel/public/partials/login.html +79 -0
- inpanel/public/partials/logout.html +22 -0
- inpanel/public/partials/main.html +534 -0
- inpanel/public/partials/plugins/index.html +55 -0
- inpanel/public/partials/plugins/plugins.html +13 -0
- inpanel/public/partials/secure.html +13 -0
- inpanel/public/partials/service/apache.html +147 -0
- inpanel/public/partials/service/cron.html +71 -0
- inpanel/public/partials/service/index.html +293 -0
- inpanel/public/partials/service/iptables.html +40 -0
- inpanel/public/partials/service/lighttpd.html +63 -0
- inpanel/public/partials/service/mariadb.html +113 -0
- inpanel/public/partials/service/memcache.html +36 -0
- inpanel/public/partials/service/minio.html +44 -0
- inpanel/public/partials/service/mongodb.html +44 -0
- inpanel/public/partials/service/mysql.html +113 -0
- inpanel/public/partials/service/named.html +61 -0
- inpanel/public/partials/service/nginx.html +296 -0
- inpanel/public/partials/service/ntp.html +40 -0
- inpanel/public/partials/service/php.html +250 -0
- inpanel/public/partials/service/proftpd.html +59 -0
- inpanel/public/partials/service/pureftpd.html +59 -0
- inpanel/public/partials/service/redis.html +40 -0
- inpanel/public/partials/service/samba.html +58 -0
- inpanel/public/partials/service/sendmail.html +40 -0
- inpanel/public/partials/service/ssh.html +186 -0
- inpanel/public/partials/service/tomcat.html +65 -0
- inpanel/public/partials/service/vsftpd.html +195 -0
- inpanel/public/partials/setting.html +299 -0
- inpanel/public/partials/site/apache/site.html +277 -0
- inpanel/public/partials/site/index.html +344 -0
- inpanel/public/partials/site/nginx/site.html +558 -0
- inpanel/public/partials/sorry.html +19 -0
- inpanel/public/partials/storage/autofm.html +103 -0
- inpanel/public/partials/storage/backup.html +42 -0
- inpanel/public/partials/storage/index.html +360 -0
- inpanel/public/partials/storage/migrate.html +93 -0
- inpanel/public/partials/storage/movedata.html +98 -0
- inpanel/public/partials/storage/remote.html +77 -0
- inpanel/public/partials/utils/cron.html +486 -0
- inpanel/public/partials/utils/firewall.html +266 -0
- inpanel/public/partials/utils/index.html +193 -0
- inpanel/public/partials/utils/network.html +118 -0
- inpanel/public/partials/utils/process.html +239 -0
- inpanel/public/partials/utils/repository.html +373 -0
- inpanel/public/partials/utils/shell.html +28 -0
- inpanel/public/partials/utils/ssl.html +319 -0
- inpanel/public/partials/utils/time.html +109 -0
- inpanel/public/partials/utils/user.html +359 -0
- inpanel/public/robots.txt +2 -0
- inpanel/remote.py +109 -0
- inpanel/templates/file/preview.html +34 -0
- inpanel/templates/index.html +93 -0
- inpanel/utils.py +282 -0
- inpanel/web.py +1811 -0
- inpanel/yum.py +756 -0
- inpanel-1.2.1.dist-info/METADATA +399 -0
- inpanel-1.2.1.dist-info/RECORD +198 -0
- inpanel-1.2.1.dist-info/WHEEL +5 -0
- inpanel-1.2.1.dist-info/entry_points.txt +2 -0
- inpanel-1.2.1.dist-info/licenses/LICENSE +30 -0
- inpanel-1.2.1.dist-info/top_level.txt +1 -0
inpanel/__init__.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2017-2026 Jackson Dou
|
|
4
|
+
# All rights reserved.
|
|
5
|
+
#
|
|
6
|
+
# InPanel is distributed under the terms of the (new) BSD License.
|
|
7
|
+
# The full license can be found in 'LICENSE'.
|
|
8
|
+
|
|
9
|
+
from .base import (
|
|
10
|
+
__version__,
|
|
11
|
+
__author__,
|
|
12
|
+
__license__,
|
|
13
|
+
app_name,
|
|
14
|
+
version_info,
|
|
15
|
+
server_info,
|
|
16
|
+
DEBUG,
|
|
17
|
+
run_type,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
'__version__',
|
|
22
|
+
'__author__',
|
|
23
|
+
'__license__',
|
|
24
|
+
'app_name',
|
|
25
|
+
'version_info',
|
|
26
|
+
'server_info',
|
|
27
|
+
'DEBUG',
|
|
28
|
+
'run_type',
|
|
29
|
+
]
|
inpanel/acme.py
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2017-2026 Jackson Dou
|
|
4
|
+
# All rights reserved.
|
|
5
|
+
#
|
|
6
|
+
# InPanel is distributed under the terms of the New BSD License.
|
|
7
|
+
# The full license can be found in 'LICENSE'.
|
|
8
|
+
|
|
9
|
+
'''Module for getting a signed TLS certificate by ACME protocol from Let's Encrypt.'''
|
|
10
|
+
|
|
11
|
+
import re
|
|
12
|
+
from base64 import urlsafe_b64encode
|
|
13
|
+
from binascii import unhexlify
|
|
14
|
+
from hashlib import sha256
|
|
15
|
+
from json import dumps, loads
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
import os
|
|
18
|
+
from subprocess import PIPE, STDOUT, Popen
|
|
19
|
+
from time import sleep
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
from urllib.request import urlopen, Request # Python 3
|
|
23
|
+
except ImportError:
|
|
24
|
+
from urllib2 import urlopen, Request # Python 2
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ACME():
|
|
28
|
+
|
|
29
|
+
# REF: https://github.com/diafygi/acme-tiny, under MIT license, author: Daniel Roesler
|
|
30
|
+
def __init__(self, account_key, csr, acme_check_dir, contact=None):
|
|
31
|
+
self.account_key = account_key
|
|
32
|
+
self.csr = csr
|
|
33
|
+
self.acme_check_dir = acme_check_dir
|
|
34
|
+
self.directory = None
|
|
35
|
+
self.acct_headers = None
|
|
36
|
+
self.alg = 'RS256'
|
|
37
|
+
self.jwk = None
|
|
38
|
+
self.thumbprint = None
|
|
39
|
+
self.certificate = None
|
|
40
|
+
|
|
41
|
+
# Contact details (e.g. mailto:aaa@bbb.com) for your account-key
|
|
42
|
+
self.contact = contact # 'a client of the InPanel'
|
|
43
|
+
# self.ca = "https://acme-v02.api.letsencrypt.org"
|
|
44
|
+
# self.ca_directory = = "https://acme-v02.api.letsencrypt.org/directory"
|
|
45
|
+
# dev
|
|
46
|
+
self.ca = "https://acme-staging-v02.api.letsencrypt.org"
|
|
47
|
+
# certificate authority directory url, default is Let's Encrypt
|
|
48
|
+
self.ca_directory = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
|
49
|
+
|
|
50
|
+
self.init_api_url()
|
|
51
|
+
self.init_account()
|
|
52
|
+
self.registe_account()
|
|
53
|
+
self.parse_csr(order=True)
|
|
54
|
+
# self.create_new_order()
|
|
55
|
+
|
|
56
|
+
def _cmd(self, cmd_list, stdin=None, cmd_input=None, err_msg="Command Line Error"):
|
|
57
|
+
'''run external commands'''
|
|
58
|
+
proc = Popen(cmd_list, stdin=stdin, stdout=PIPE, stderr=PIPE)
|
|
59
|
+
out, err = proc.communicate(cmd_input)
|
|
60
|
+
if proc.returncode != 0:
|
|
61
|
+
raise IOError("{0}\n{1}".format(err_msg, err))
|
|
62
|
+
return out
|
|
63
|
+
|
|
64
|
+
def _b64(self, b):
|
|
65
|
+
'''base64 encode for jose spec'''
|
|
66
|
+
return urlsafe_b64encode(b).decode('utf8').replace('=', '')
|
|
67
|
+
|
|
68
|
+
def _request(self, url, data=None, err_msg='Error', depth=0):
|
|
69
|
+
'''make request and automatically parse json response'''
|
|
70
|
+
try:
|
|
71
|
+
hd = {
|
|
72
|
+
"Content-Type": "application/jose+json",
|
|
73
|
+
"User-Agent": "inpanel"
|
|
74
|
+
}
|
|
75
|
+
res = urlopen(Request(url, data=data, headers=hd))
|
|
76
|
+
res_data = res.read().decode('utf8')
|
|
77
|
+
code = res.getcode()
|
|
78
|
+
headers = res.headers
|
|
79
|
+
except IOError as e:
|
|
80
|
+
res_data = e.read().decode('utf8') if hasattr(e, 'read') else str(e)
|
|
81
|
+
code, headers = getattr(e, 'code', None), {}
|
|
82
|
+
try:
|
|
83
|
+
res_data = loads(res_data) # try to parse json results
|
|
84
|
+
except ValueError:
|
|
85
|
+
pass # ignore json parsing errors
|
|
86
|
+
if depth < 100 and code == 400 and res_data['type'] == 'urn:ietf:params:acme:error:badNonce':
|
|
87
|
+
raise IndexError(res_data) # allow 100 retrys for bad nonces
|
|
88
|
+
if code not in [200, 201, 204]:
|
|
89
|
+
raise ValueError("{0}:\nUrl: {1}\nData: {2}\nResponse Code: {3}\nResponse: {4}".format(
|
|
90
|
+
err_msg, url, data, code, res_data))
|
|
91
|
+
return res_data, code, headers
|
|
92
|
+
|
|
93
|
+
def _s_request(self, url, payload, err_msg, depth=0):
|
|
94
|
+
'''make signed requests'''
|
|
95
|
+
payload64 = self._b64(dumps(payload).encode('utf8'))
|
|
96
|
+
new_nonce = self._request(self.ca_new_nonce)[2]['Replay-Nonce']
|
|
97
|
+
protected = {'url': url, 'alg': self.alg, "nonce": new_nonce}
|
|
98
|
+
protected.update({"jwk": self.jwk} if self.acct_headers is None else {"kid": self.acct_headers['Location']})
|
|
99
|
+
protected64 = self._b64(dumps(protected).encode('utf8'))
|
|
100
|
+
protected_input = "{0}.{1}".format(protected64, payload64).encode('utf8')
|
|
101
|
+
cmd = ['openssl', 'dgst', '-sha256', '-sign', self.account_key]
|
|
102
|
+
out = self._cmd(cmd, stdin=PIPE, cmd_input=protected_input, err_msg='OpenSSL Error')
|
|
103
|
+
data = dumps({
|
|
104
|
+
'protected': protected64,
|
|
105
|
+
'payload': payload64,
|
|
106
|
+
'signature': self._b64(out)
|
|
107
|
+
})
|
|
108
|
+
try:
|
|
109
|
+
return self._request(url, data=data.encode('utf8'), err_msg=err_msg, depth=depth)
|
|
110
|
+
except IndexError: # retry bad nonces (they raise IndexError)
|
|
111
|
+
return self._s_request(url, payload, err_msg, depth=(depth + 1))
|
|
112
|
+
|
|
113
|
+
# helper function - poll until complete
|
|
114
|
+
def _poll_until_not(self, url, pending_statuses, err_msg):
|
|
115
|
+
while True:
|
|
116
|
+
result, _, _ = self._request(url, err_msg=err_msg)
|
|
117
|
+
if result['status'] in pending_statuses:
|
|
118
|
+
sleep(2)
|
|
119
|
+
continue
|
|
120
|
+
return result
|
|
121
|
+
|
|
122
|
+
def init_api_url(self, ca_directory=None):
|
|
123
|
+
# get the ACME directory of urls
|
|
124
|
+
print('API directory getting...')
|
|
125
|
+
ca = ca_directory
|
|
126
|
+
if ca is None:
|
|
127
|
+
ca = self.ca_directory
|
|
128
|
+
ca_dir, _, _ = self._request(ca, err_msg='get directory error')
|
|
129
|
+
self.ca_new_account = ca_dir['newAccount']
|
|
130
|
+
self.ca_new_nonce = ca_dir['newNonce']
|
|
131
|
+
self.ca_new_order = ca_dir['newOrder']
|
|
132
|
+
self.ca_revoke_cert = ca_dir['revokeCert']
|
|
133
|
+
self.ca_key_change = ca_dir['keyChange']
|
|
134
|
+
self.directory = ca_dir
|
|
135
|
+
print('API directory ready!')
|
|
136
|
+
|
|
137
|
+
def init_account(self, account_key=None):
|
|
138
|
+
# parse account key to get public key
|
|
139
|
+
print('Account key parsing...')
|
|
140
|
+
acc_key = account_key
|
|
141
|
+
if acc_key is None:
|
|
142
|
+
acc_key = self.account_key
|
|
143
|
+
acc_key_path = Path(acc_key)
|
|
144
|
+
if not acc_key_path.exists() or not acc_key_path.is_file():
|
|
145
|
+
return None
|
|
146
|
+
cmd = ['openssl', 'rsa', '-in', acc_key, '-noout', '-text']
|
|
147
|
+
out = self._cmd(cmd, err_msg='openssl error')
|
|
148
|
+
pub_pattern = r"modulus:\n\s+00:([a-f0-9\:\s]+?)\npublicExponent: ([0-9]+)"
|
|
149
|
+
pub_hex, pub_exp = re.search(pub_pattern, out.decode('utf8'), re.MULTILINE | re.DOTALL).groups()
|
|
150
|
+
pub_exp = "{0:x}".format(int(pub_exp))
|
|
151
|
+
pub_exp = "0{0}".format(pub_exp) if len(pub_exp) % 2 else pub_exp
|
|
152
|
+
self.jwk = {
|
|
153
|
+
'e': self._b64(unhexlify(pub_exp.encode('utf-8'))),
|
|
154
|
+
'kty': 'RSA',
|
|
155
|
+
'n': self._b64(unhexlify(re.sub(r"(\s|:)", '', pub_hex).encode('utf-8'))),
|
|
156
|
+
}
|
|
157
|
+
acc_key_json = dumps(self.jwk, sort_keys=True, separators=(',', ':'))
|
|
158
|
+
self.thumbprint = self._b64(sha256(acc_key_json.encode('utf8')).digest())
|
|
159
|
+
# print('thumbprint', self.thumbprint)
|
|
160
|
+
print('Account key ready...')
|
|
161
|
+
|
|
162
|
+
def registe_account(self, contact=None):
|
|
163
|
+
# create account, update contact details (if any), and set the global key identifier
|
|
164
|
+
print('Account registration...')
|
|
165
|
+
reg_payload = {'termsOfServiceAgreed': True}
|
|
166
|
+
account, code, self.acct_headers = self._s_request(
|
|
167
|
+
self.ca_new_account, reg_payload, 'Error registering')
|
|
168
|
+
if code == 201:
|
|
169
|
+
print('Account registration is successful !')
|
|
170
|
+
else:
|
|
171
|
+
print('Account is already registered!')
|
|
172
|
+
# print(self.acct_headers)
|
|
173
|
+
if contact is None:
|
|
174
|
+
contact = self.contact
|
|
175
|
+
if contact is not None:
|
|
176
|
+
url = self.acct_headers['Location']
|
|
177
|
+
payload = {'contact': contact}
|
|
178
|
+
account, _, _ = self._s_request(url, payload, 'Error updating contact details')
|
|
179
|
+
print(account)
|
|
180
|
+
print("Updated contact details:\n{0}".format("\n".join(account['contact'])))
|
|
181
|
+
|
|
182
|
+
def parse_csr(self, order=False):
|
|
183
|
+
# find domains
|
|
184
|
+
print('Domains CSR parsing...')
|
|
185
|
+
cmd = ['openssl', 'req', '-in', self.csr, '-noout', '-text']
|
|
186
|
+
out = self._cmd(cmd, err_msg="Error loading {0}".format(self.csr))
|
|
187
|
+
domains = set([])
|
|
188
|
+
common_name = re.search(r"Subject:.*? CN\s?=\s?([^\s,;/]+)", out.decode('utf8'))
|
|
189
|
+
if common_name is not None:
|
|
190
|
+
domains.add(common_name.group(1))
|
|
191
|
+
subject_alt_names = re.search(
|
|
192
|
+
r"X509v3 Subject Alternative Name: \n +([^\n]+)\n", out.decode('utf8'), re.MULTILINE | re.DOTALL)
|
|
193
|
+
if subject_alt_names is not None:
|
|
194
|
+
for san in subject_alt_names.group(1).split(", "):
|
|
195
|
+
if san.startswith("DNS:"):
|
|
196
|
+
domains.add(san[4:])
|
|
197
|
+
print("Found domains: {0}".format(", ".join(domains)))
|
|
198
|
+
# print('domains', domains)
|
|
199
|
+
if order == True:
|
|
200
|
+
self.create_new_order(domains)
|
|
201
|
+
|
|
202
|
+
def create_new_order(self, domains, disable_check=False):
|
|
203
|
+
'''create a new order
|
|
204
|
+
disable_check: disable checking if the challenge file is hosted correctly before telling the CA
|
|
205
|
+
'''
|
|
206
|
+
print("Creating new order...")
|
|
207
|
+
order_payload = {"identifiers": [
|
|
208
|
+
{"type": "dns", "value": d} for d in domains]}
|
|
209
|
+
order, _, order_headers = self._s_request(
|
|
210
|
+
self.ca_new_order, order_payload, "Error creating new order")
|
|
211
|
+
print("Order created!")
|
|
212
|
+
|
|
213
|
+
# get the authorizations that need to be completed
|
|
214
|
+
for auth_url in order['authorizations']:
|
|
215
|
+
authorization, _, _ = self._request(auth_url, err_msg='Error getting challenges')
|
|
216
|
+
domain = authorization['identifier']['value']
|
|
217
|
+
print("Domain {0} Verifying...".format(domain))
|
|
218
|
+
|
|
219
|
+
# find the http-01 challenge and write the challenge file
|
|
220
|
+
challenge = [c for c in authorization['challenges'] if c['type'] == "http-01"][0]
|
|
221
|
+
token = re.sub(r"[^A-Za-z0-9_\-]", "_", challenge['token'])
|
|
222
|
+
key_auth = "{0}.{1}".format(token, self.thumbprint)
|
|
223
|
+
wellknown_path = str(Path(self.acme_check_dir) / token)
|
|
224
|
+
with open(wellknown_path, 'w', encoding='utf-8') as f:
|
|
225
|
+
f.write(key_auth)
|
|
226
|
+
|
|
227
|
+
# check that the wellknown_file is in specified place
|
|
228
|
+
try:
|
|
229
|
+
wellknown_url = "http://{0}/.well-known/acme-challenge/{1}".format(domain, token)
|
|
230
|
+
assert(disable_check or self._request(wellknown_url)[0] == key_auth)
|
|
231
|
+
except (AssertionError, ValueError) as e:
|
|
232
|
+
os.remove(wellknown_path)
|
|
233
|
+
raise ValueError("Wrote file to {0}, but couldn't download {1}: {2}".format(
|
|
234
|
+
wellknown_path, wellknown_url, e))
|
|
235
|
+
|
|
236
|
+
# say the challenge is done
|
|
237
|
+
self._s_request(
|
|
238
|
+
challenge['url'], {}, "Error submitting challenges: {0}".format(domain))
|
|
239
|
+
authorization = self._poll_until_not(auth_url, ["pending"], "Error checking challenge status for {0}".format(domain))
|
|
240
|
+
if authorization['status'] != "valid":
|
|
241
|
+
raise ValueError("Challenge did not pass for {0}: {1}".format(domain, authorization))
|
|
242
|
+
print("Domain {0} verified!".format(domain))
|
|
243
|
+
|
|
244
|
+
# finalize the order with the csr
|
|
245
|
+
print('Certificate signing...')
|
|
246
|
+
csr_der = self._cmd(['openssl', 'req', '-in', self.csr, '-outform', 'DER'], err_msg='DER Export Error')
|
|
247
|
+
self._s_request(order['finalize'], {'csr': self._b64(csr_der)}, 'Error finalizing order')
|
|
248
|
+
# poll the order to monitor when it's done
|
|
249
|
+
order = self._poll_until_not(order_headers['Location'], ['pending', 'processing'], 'Error checking order status')
|
|
250
|
+
if order['status'] != 'valid':
|
|
251
|
+
raise ValueError("Order failed: {0}".format(order))
|
|
252
|
+
self.certificate = order['certificate']
|
|
253
|
+
|
|
254
|
+
def get_certificate(self, certificate=None):
|
|
255
|
+
# download the certificate
|
|
256
|
+
crt_url = self.certificate if certificate is None else certificate
|
|
257
|
+
if crt_url is None:
|
|
258
|
+
return None
|
|
259
|
+
certificate_pem, _, _ = self._request(crt_url, err_msg='Certificate download failed')
|
|
260
|
+
print('Certificate signed!')
|
|
261
|
+
return certificate_pem
|
|
262
|
+
|
|
263
|
+
def ertificate_revoke(self, crt):
|
|
264
|
+
'''revoke certificate'''
|
|
265
|
+
print(crt)
|
|
266
|
+
print('Certificate revoked!')
|
|
267
|
+
|
|
268
|
+
def certificate_renew(self, crt):
|
|
269
|
+
'''renew certificate'''
|
|
270
|
+
print(crt)
|
|
271
|
+
print('Certificate updated!')
|
|
272
|
+
|
|
273
|
+
# if __name__ == "__main__":
|
|
274
|
+
# # import certificate
|
|
275
|
+
# # cert = certificate.Certificate()
|
|
276
|
+
# # # # cert.create_domain_key('test.com')
|
|
277
|
+
# # cert.generate_domain_csr(['test.com', 'aaa.com'], forced=True)
|
|
278
|
+
# account_key = '/Users/douzhenjiang/Projects/inpanel/data/certificate/client.key'
|
|
279
|
+
# csr = '/Users/douzhenjiang/Projects/inpanel/data/certificate/csr/test.com.csr'
|
|
280
|
+
# acme_check_dir = '/Users/douzhenjiang/Projects/inpanel/data'
|
|
281
|
+
# # aaa = ACME(account_key, csr, acme_check_dir)
|
|
282
|
+
|
|
283
|
+
# key = '/Users/douzhenjiang/Projects/inpanel/test/example_com.key'
|
|
284
|
+
# csr = '/Users/douzhenjiang/Projects/inpanel/test/example_com.csr'
|
|
285
|
+
# aaa = ACME(key, csr, acme_check_dir)
|
|
286
|
+
|
|
287
|
+
# # C=CN, ST=Beijing, L=Beijing, O=Example Inc, OU=Network Dept,
|
|
288
|
+
# # CN=example.com/subjectAltName=DNS.1=sub1.example.com,DNS.2=sub2.example.com,DNS.3=sub.another-example.com
|
|
289
|
+
# # CN=test.com/subjectAltName=DNS:test.com,DNS:aaa.com
|
inpanel/aliyuncs.py
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2017-2026 Jackson Dou
|
|
4
|
+
# All rights reserved.
|
|
5
|
+
#
|
|
6
|
+
# ECSMate is distributed under the terms of the (new) BSD License.
|
|
7
|
+
# The full license can be found in 'LICENSE'.
|
|
8
|
+
|
|
9
|
+
'''ECS SDK'''
|
|
10
|
+
|
|
11
|
+
import time
|
|
12
|
+
import hmac
|
|
13
|
+
import base64
|
|
14
|
+
import hashlib
|
|
15
|
+
import urllib
|
|
16
|
+
import json
|
|
17
|
+
import inspect
|
|
18
|
+
from random import random
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ECS(object):
|
|
22
|
+
|
|
23
|
+
def __init__(self, AccessKeyID, AccessKeySecret, gateway='https://ecs.aliyuncs.com'):
|
|
24
|
+
self.AccessKeyID = AccessKeyID
|
|
25
|
+
self.AccessKeySecret = AccessKeySecret
|
|
26
|
+
self.gateway = gateway
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
def _urlencode(self, string):
|
|
30
|
+
return urllib.quote(string, '~')
|
|
31
|
+
|
|
32
|
+
def _sign(self, params):
|
|
33
|
+
paramstrings = []
|
|
34
|
+
for k, v in sorted(params.items()):
|
|
35
|
+
paramstrings.append('%s=%s' %
|
|
36
|
+
(ECS._urlencode(k), ECS._urlencode(v)))
|
|
37
|
+
datastrings = [
|
|
38
|
+
ECS._urlencode('GET'),
|
|
39
|
+
ECS._urlencode('/'),
|
|
40
|
+
ECS._urlencode('&'.join(paramstrings)),
|
|
41
|
+
]
|
|
42
|
+
datastring = '&'.join(datastrings)
|
|
43
|
+
signature = hmac.new(self.AccessKeySecret+'&',
|
|
44
|
+
datastring, hashlib.sha1).digest()
|
|
45
|
+
return base64.b64encode(signature)
|
|
46
|
+
|
|
47
|
+
def _http_get(self, params):
|
|
48
|
+
url = self.gateway + '/?'
|
|
49
|
+
|
|
50
|
+
sysparams = {
|
|
51
|
+
'Format': 'JSON',
|
|
52
|
+
'Version': '2012-09-13',
|
|
53
|
+
'AccessKeyID': self.AccessKeyID,
|
|
54
|
+
'SignatureMethod': 'HMAC-SHA1',
|
|
55
|
+
'Timestamp': time.strftime('%Y-%m-%dT%XZ'),
|
|
56
|
+
'SignatureVersion': '1.0',
|
|
57
|
+
'SignatureNonce': str(random()).replace('0.', ''),
|
|
58
|
+
}
|
|
59
|
+
params.update(sysparams)
|
|
60
|
+
params['Signature'] = self._sign(params)
|
|
61
|
+
params = urllib.urlencode(params)
|
|
62
|
+
|
|
63
|
+
url += params
|
|
64
|
+
f = urllib.urlopen(url)
|
|
65
|
+
data = f.read()
|
|
66
|
+
f.close()
|
|
67
|
+
|
|
68
|
+
return json.loads(data)
|
|
69
|
+
|
|
70
|
+
def _parse_response(self, apiname, response):
|
|
71
|
+
if 'Error' in response:
|
|
72
|
+
respdata = response['Error']
|
|
73
|
+
reqid = respdata['RequestID']
|
|
74
|
+
del respdata['RequestID']
|
|
75
|
+
return [False, respdata, reqid]
|
|
76
|
+
else:
|
|
77
|
+
respdata = response[apiname+'Response']
|
|
78
|
+
return [True, respdata[apiname+'Result'], respdata['ResponseMetadata']['RequestID']]
|
|
79
|
+
|
|
80
|
+
def _make_params(self, params):
|
|
81
|
+
params = dict((k, str(v))
|
|
82
|
+
for k, v in params.items() if k != 'self' and v != None)
|
|
83
|
+
params['Action'] = inspect.stack()[1][3]
|
|
84
|
+
return params
|
|
85
|
+
|
|
86
|
+
def _execute(self, params):
|
|
87
|
+
response = self._http_get(params)
|
|
88
|
+
return self._parse_response(params['Action'], response)
|
|
89
|
+
|
|
90
|
+
def CreateInstance(self, RegionCode, DiskSize, InstanceType, GroupCode, ImageCode,
|
|
91
|
+
MaxBandwidthIn=None, MaxBandwidthOut=None, InstanceName=None, HostName=None,
|
|
92
|
+
Password=None, ZoneCode=None):
|
|
93
|
+
params = self._make_params(locals())
|
|
94
|
+
return self._execute(params)
|
|
95
|
+
|
|
96
|
+
def StartInstance(self, InstanceName):
|
|
97
|
+
params = self._make_params(locals())
|
|
98
|
+
return self._execute(params)
|
|
99
|
+
|
|
100
|
+
def StopInstance(self, InstanceName, ForceStop=None):
|
|
101
|
+
params = self._make_params(locals())
|
|
102
|
+
return self._execute(params)
|
|
103
|
+
|
|
104
|
+
def RebootInstance(self, InstanceName, ForceStop=None):
|
|
105
|
+
params = self._make_params(locals())
|
|
106
|
+
return self._execute(params)
|
|
107
|
+
|
|
108
|
+
def ResetInstance(self, InstanceName, ImageCode=None, DiskType=None):
|
|
109
|
+
params = self._make_params(locals())
|
|
110
|
+
return self._execute(params)
|
|
111
|
+
|
|
112
|
+
def ResetPassword(self, InstanceName, NewPassword=None):
|
|
113
|
+
params = self._make_params(locals())
|
|
114
|
+
return self._execute(params)
|
|
115
|
+
|
|
116
|
+
def DeleteInstance(self, InstanceName):
|
|
117
|
+
params = self._make_params(locals())
|
|
118
|
+
return self._execute(params)
|
|
119
|
+
|
|
120
|
+
def DescribeInstanceStatus(self, RegionCode=None, ZoneCode=None, PageNumber=None, PageSize=None):
|
|
121
|
+
params = self._make_params(locals())
|
|
122
|
+
return self._execute(params)
|
|
123
|
+
|
|
124
|
+
def DescribeInstanceAttribute(self, InstanceName):
|
|
125
|
+
params = self._make_params(locals())
|
|
126
|
+
return self._execute(params)
|
|
127
|
+
|
|
128
|
+
def ModifyInstanceAttribute(self, InstanceName, InstanceType):
|
|
129
|
+
params = self._make_params(locals())
|
|
130
|
+
return self._execute(params)
|
|
131
|
+
|
|
132
|
+
def ModifyBandwidth(self, InstanceName, MaxBandwidthOut, MaxBandwidthIn):
|
|
133
|
+
params = self._make_params(locals())
|
|
134
|
+
return self._execute(params)
|
|
135
|
+
|
|
136
|
+
def ModifyHostName(self, InstanceName, HostName):
|
|
137
|
+
params = self._make_params(locals())
|
|
138
|
+
return self._execute(params)
|
|
139
|
+
|
|
140
|
+
def CreateDisk(self, InstanceName, Size, SnapshotCode=None):
|
|
141
|
+
params = self._make_params(locals())
|
|
142
|
+
return self._execute(params)
|
|
143
|
+
|
|
144
|
+
def DeleteDisk(self, InstanceName, DiskCode):
|
|
145
|
+
params = self._make_params(locals())
|
|
146
|
+
return self._execute(params)
|
|
147
|
+
|
|
148
|
+
def DescribeDisks(self, InstanceName):
|
|
149
|
+
params = self._make_params(locals())
|
|
150
|
+
return self._execute(params)
|
|
151
|
+
|
|
152
|
+
def DescribeImages(self, RegionCode=None, PageNumber=None, PageSize=None):
|
|
153
|
+
params = self._make_params(locals())
|
|
154
|
+
return self._execute(params)
|
|
155
|
+
|
|
156
|
+
def AllocateAddress(self, InstanceName):
|
|
157
|
+
params = self._make_params(locals())
|
|
158
|
+
return self._execute(params)
|
|
159
|
+
|
|
160
|
+
def ReleaseAddress(self, PublicIpAddress):
|
|
161
|
+
params = self._make_params(locals())
|
|
162
|
+
return self._execute(params)
|
|
163
|
+
|
|
164
|
+
def CreateSecurityGroup(self, GroupCode, RegionCode, Description):
|
|
165
|
+
params = self._make_params(locals())
|
|
166
|
+
return self._execute(params)
|
|
167
|
+
|
|
168
|
+
def AuthorizeSecurityGroup(self, GroupCode, RegionCode, IpProtocol, PortRange,
|
|
169
|
+
SourceGroupCode=None, SourceCidrIp=None, Policy=None, NicType=None, Priority=None):
|
|
170
|
+
params = self._make_params(locals())
|
|
171
|
+
return self._execute(params)
|
|
172
|
+
|
|
173
|
+
def DescribeSecurityGroupAttribute(self, GroupCode, RegionCode, NicType=None):
|
|
174
|
+
params = self._make_params(locals())
|
|
175
|
+
return self._execute(params)
|
|
176
|
+
|
|
177
|
+
def DescribeSecurityGroups(self, RegionCode, PageNumber=None, PageSize=None):
|
|
178
|
+
params = self._make_params(locals())
|
|
179
|
+
return self._execute(params)
|
|
180
|
+
|
|
181
|
+
def ModifySecurityGroupAttribute(self, RegionCode, GroupCode, Adjust):
|
|
182
|
+
params = self._make_params(locals())
|
|
183
|
+
return self._execute(params)
|
|
184
|
+
|
|
185
|
+
def RevokeSecurityGroup(self, GroupCode, RegionCode, IpProtocol, PortRange,
|
|
186
|
+
SourceGroupCode=None, SourceCidrIp=None, Policy=None, NicType=None):
|
|
187
|
+
params = self._make_params(locals())
|
|
188
|
+
return self._execute(params)
|
|
189
|
+
|
|
190
|
+
def DeleteSecurityGroup(self, GroupCode, RegionCode):
|
|
191
|
+
params = self._make_params(locals())
|
|
192
|
+
return self._execute(params)
|
|
193
|
+
|
|
194
|
+
def CreateSnapshot(self, InstanceName, DiskCode):
|
|
195
|
+
params = self._make_params(locals())
|
|
196
|
+
return self._execute(params)
|
|
197
|
+
|
|
198
|
+
def DeleteSnapshot(self, DiskCode, InstanceName, SnapshotCode):
|
|
199
|
+
params = self._make_params(locals())
|
|
200
|
+
return self._execute(params)
|
|
201
|
+
|
|
202
|
+
def CancelSnapshotRequest(self, InstanceName, SnapshotCode):
|
|
203
|
+
params = self._make_params(locals())
|
|
204
|
+
return self._execute(params)
|
|
205
|
+
|
|
206
|
+
def DescribeSnapshots(self, InstanceName, DiskCode):
|
|
207
|
+
params = self._make_params(locals())
|
|
208
|
+
return self._execute(params)
|
|
209
|
+
|
|
210
|
+
def DescribeSnapshotAttribute(self, RegionCode, SnapshotCode):
|
|
211
|
+
params = self._make_params(locals())
|
|
212
|
+
return self._execute(params)
|
|
213
|
+
|
|
214
|
+
def RollbackSnapshot(self, InstanceName, DiskCode, SnapshotCode):
|
|
215
|
+
params = self._make_params(locals())
|
|
216
|
+
return self._execute(params)
|
|
217
|
+
|
|
218
|
+
def DescribeRegions(self):
|
|
219
|
+
params = self._make_params(locals())
|
|
220
|
+
return self._execute(params)
|
|
221
|
+
|
|
222
|
+
def DescribeZones(self, RegionCode):
|
|
223
|
+
params = self._make_params(locals())
|
|
224
|
+
return self._execute(params)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
if __name__ == '__main__':
|
|
228
|
+
import pprint
|
|
229
|
+
pp = pprint.PrettyPrinter(indent=4)
|
|
230
|
+
|
|
231
|
+
AccessKeyID = ''
|
|
232
|
+
AccessKeySecret = ''
|
|
233
|
+
|
|
234
|
+
ecs = ECS(AccessKeyID, AccessKeySecret)
|
|
235
|
+
|
|
236
|
+
if 0:
|
|
237
|
+
print('## Regions\n')
|
|
238
|
+
regions = ecs.DescribeRegions()[1]
|
|
239
|
+
pp.pprint(regions)
|
|
240
|
+
print()
|
|
241
|
+
|
|
242
|
+
for region in regions['Regions']:
|
|
243
|
+
print('## Zones in %s\n' % region['RegionCode'])
|
|
244
|
+
zones = ecs.DescribeZones(region['RegionCode'])
|
|
245
|
+
if not zones[0]:
|
|
246
|
+
pp.pprint(zones)
|
|
247
|
+
continue
|
|
248
|
+
zones = zones[1]
|
|
249
|
+
pp.pprint(zones)
|
|
250
|
+
print()
|
|
251
|
+
|
|
252
|
+
for zone in zones['Zones']:
|
|
253
|
+
print('## Instances in %s\n' % zone['ZoneCode'])
|
|
254
|
+
instances = ecs.DescribeInstanceStatus(
|
|
255
|
+
region['RegionCode'], zone['ZoneCode'])[1]
|
|
256
|
+
pp.pprint(instances)
|
|
257
|
+
print()
|
|
258
|
+
|
|
259
|
+
print()
|
|
260
|
+
|
|
261
|
+
#pp.pprint(ecs.DescribeInstanceStatus(PageSize=10, PageNumber=1))
|
|
262
|
+
#pp.pprint(ecs.DescribeInstanceStatus('cn-hangzhou-dg-a01', 'cn-hangzhou-dg101-a'))
|
|
263
|
+
# pp.pprint(ecs.StartInstance('AY1209220917063704221'))
|
|
264
|
+
# pp.pprint(ecs.StopInstance('AY1209220917063704221'))
|
|
265
|
+
# pp.pprint(ecs.RebootInstance('AY1209220917063704221'))
|
|
266
|
+
# pp.pprint(ecs.DescribeInstanceAttribute('AY1209220917063704221'))
|
|
267
|
+
#pp.pprint(ecs.DescribeImages(PageSize=10, PageNumber=9))
|
|
268
|
+
# pp.pprint(ecs.DescribeDisks('AY1209220917063704221'))
|
|
269
|
+
#pp.pprint(ecs.DescribeSnapshots('AY1209220917063704221', '1006-60002839'))
|