opensipscli 0.3.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.
- opensipscli/__init__.py +20 -0
- opensipscli/args.py +56 -0
- opensipscli/cli.py +472 -0
- opensipscli/comm.py +57 -0
- opensipscli/config.py +162 -0
- opensipscli/db.py +989 -0
- opensipscli/defaults.py +91 -0
- opensipscli/libs/__init__.py +20 -0
- opensipscli/libs/sqlalchemy_utils.py +244 -0
- opensipscli/logger.py +85 -0
- opensipscli/main.py +86 -0
- opensipscli/module.py +69 -0
- opensipscli/modules/__init__.py +24 -0
- opensipscli/modules/database.py +1062 -0
- opensipscli/modules/diagnose.py +1089 -0
- opensipscli/modules/instance.py +53 -0
- opensipscli/modules/mi.py +200 -0
- opensipscli/modules/tls.py +354 -0
- opensipscli/modules/trace.py +292 -0
- opensipscli/modules/trap.py +138 -0
- opensipscli/modules/user.py +281 -0
- opensipscli/version.py +22 -0
- opensipscli-0.3.1.data/scripts/opensips-cli +9 -0
- opensipscli-0.3.1.dist-info/LICENSE +674 -0
- opensipscli-0.3.1.dist-info/METADATA +225 -0
- opensipscli-0.3.1.dist-info/RECORD +28 -0
- opensipscli-0.3.1.dist-info/WHEEL +5 -0
- opensipscli-0.3.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
##
|
|
3
|
+
## This file is part of OpenSIPS CLI
|
|
4
|
+
## (see https://github.com/OpenSIPS/opensips-cli).
|
|
5
|
+
##
|
|
6
|
+
## This program is free software: you can redistribute it and/or modify
|
|
7
|
+
## it under the terms of the GNU General Public License as published by
|
|
8
|
+
## the Free Software Foundation, either version 3 of the License, or
|
|
9
|
+
## (at your option) any later version.
|
|
10
|
+
##
|
|
11
|
+
## This program is distributed in the hope that it will be useful,
|
|
12
|
+
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
## GNU General Public License for more details.
|
|
15
|
+
##
|
|
16
|
+
## You should have received a copy of the GNU General Public License
|
|
17
|
+
## along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
18
|
+
##
|
|
19
|
+
|
|
20
|
+
from opensipscli.config import cfg
|
|
21
|
+
from opensipscli.logger import logger
|
|
22
|
+
from opensipscli.module import Module
|
|
23
|
+
|
|
24
|
+
class instance(Module):
|
|
25
|
+
|
|
26
|
+
def get_instances(self):
|
|
27
|
+
l = cfg.config.sections()
|
|
28
|
+
default_section = cfg.get_default_instance()
|
|
29
|
+
if default_section not in l:
|
|
30
|
+
l.insert(0, default_section)
|
|
31
|
+
return l
|
|
32
|
+
|
|
33
|
+
def do_show(self, params, modifiers):
|
|
34
|
+
print(cfg.current_instance)
|
|
35
|
+
|
|
36
|
+
def do_list(self, params, modifiers):
|
|
37
|
+
for i in self.get_instances():
|
|
38
|
+
print(i)
|
|
39
|
+
|
|
40
|
+
def complete_switch(self, text, line, *ignore):
|
|
41
|
+
if len(line.split(' ')) > 3:
|
|
42
|
+
return []
|
|
43
|
+
return [ a for a in self.get_instances() if a.startswith(text)]
|
|
44
|
+
|
|
45
|
+
def do_switch(self, params, modifiers):
|
|
46
|
+
if len(params) == 0:
|
|
47
|
+
return
|
|
48
|
+
new_instance = params[0]
|
|
49
|
+
if cfg.has_instance(new_instance):
|
|
50
|
+
cfg.set_instance(new_instance)
|
|
51
|
+
else:
|
|
52
|
+
logger.error("cannot switch to instance '{}': instance not found!".format(new_instance))
|
|
53
|
+
return -1
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
##
|
|
3
|
+
## This file is part of OpenSIPS CLI
|
|
4
|
+
## (see https://github.com/OpenSIPS/opensips-cli).
|
|
5
|
+
##
|
|
6
|
+
## This program is free software: you can redistribute it and/or modify
|
|
7
|
+
## it under the terms of the GNU General Public License as published by
|
|
8
|
+
## the Free Software Foundation, either version 3 of the License, or
|
|
9
|
+
## (at your option) any later version.
|
|
10
|
+
##
|
|
11
|
+
## This program is distributed in the hope that it will be useful,
|
|
12
|
+
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
## GNU General Public License for more details.
|
|
15
|
+
##
|
|
16
|
+
## You should have received a copy of the GNU General Public License
|
|
17
|
+
## along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
18
|
+
##
|
|
19
|
+
|
|
20
|
+
import re
|
|
21
|
+
import json
|
|
22
|
+
import shlex
|
|
23
|
+
from collections import OrderedDict
|
|
24
|
+
from opensipscli.config import cfg
|
|
25
|
+
from opensipscli.logger import logger
|
|
26
|
+
from opensipscli.module import Module
|
|
27
|
+
from opensipscli import comm
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
import yaml
|
|
31
|
+
yaml_available = True
|
|
32
|
+
except ImportError:
|
|
33
|
+
yaml_available = False
|
|
34
|
+
|
|
35
|
+
# temporary special handling for commands that require array params
|
|
36
|
+
# format is: command: (idx, name)
|
|
37
|
+
MI_ARRAY_PARAMS_COMMANDS = {
|
|
38
|
+
"fs_subscribe": (1, "events"),
|
|
39
|
+
"fs_unsubscribe": (1, "events"),
|
|
40
|
+
"b2b_trigger_scenario": (3, "scenario_params"),
|
|
41
|
+
"dlg_push_var": (2, "DID"),
|
|
42
|
+
"get_statistics": (0, "statistics"),
|
|
43
|
+
"list_statistics": (0, "statistics"),
|
|
44
|
+
"reset_statistics": (0, "statistics"),
|
|
45
|
+
"trace_start": (0, "filter"),
|
|
46
|
+
"raise_event": (1, "params"),
|
|
47
|
+
"dfks_set_feature": (4, "values"),
|
|
48
|
+
"cluster_broadcast_mi": (2, "cmd_params"),
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
MI_MODIFIERS = [ "-j" ]
|
|
53
|
+
|
|
54
|
+
class mi(Module):
|
|
55
|
+
|
|
56
|
+
def print_pretty_print(self, result):
|
|
57
|
+
print(json.dumps(result, indent=4))
|
|
58
|
+
|
|
59
|
+
def print_dictionary(self, result):
|
|
60
|
+
print(str(result))
|
|
61
|
+
|
|
62
|
+
def print_lines(self, result, indent=0):
|
|
63
|
+
if type(result) in [OrderedDict, dict]:
|
|
64
|
+
for k, v in result.items():
|
|
65
|
+
if type(v) in [OrderedDict, list, dict]:
|
|
66
|
+
print(" " * indent + k + ":")
|
|
67
|
+
self.print_lines(v, indent + 4)
|
|
68
|
+
else:
|
|
69
|
+
print(" " * indent + "{}: {}". format(k, v))
|
|
70
|
+
elif type(result) == list:
|
|
71
|
+
for v in result:
|
|
72
|
+
self.print_lines(v, indent)
|
|
73
|
+
else:
|
|
74
|
+
print(" " * indent + str(result))
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
def print_yaml(self, result):
|
|
78
|
+
if not yaml_available:
|
|
79
|
+
logger.warning("yaml not available on your platform! "
|
|
80
|
+
"Please install `python-yaml` package or similar!")
|
|
81
|
+
else:
|
|
82
|
+
print(yaml.dump(result, default_flow_style=False).strip())
|
|
83
|
+
|
|
84
|
+
def get_params_set(self, cmds):
|
|
85
|
+
l = set()
|
|
86
|
+
for p in cmds:
|
|
87
|
+
m = re.match('([a-zA-Z\.\-_]+)=', p)
|
|
88
|
+
# if it's not a parameter name, skip
|
|
89
|
+
if m:
|
|
90
|
+
l.add(m.group(1))
|
|
91
|
+
else:
|
|
92
|
+
return None
|
|
93
|
+
return l
|
|
94
|
+
|
|
95
|
+
def get_params_names(self, line):
|
|
96
|
+
cmds = shlex.split(line)
|
|
97
|
+
# cmd[0] = module, cmd[1] = command
|
|
98
|
+
if len(cmds) < 2:
|
|
99
|
+
return None
|
|
100
|
+
return self.get_params_set(cmds[2:])
|
|
101
|
+
|
|
102
|
+
def parse_params(self, cmd, params, modifiers):
|
|
103
|
+
|
|
104
|
+
# first, we check to see if we have only named parameters
|
|
105
|
+
nparams = self.get_params_set(params)
|
|
106
|
+
if nparams is not None:
|
|
107
|
+
logger.debug("named parameters are used")
|
|
108
|
+
new_params = {}
|
|
109
|
+
for p in params:
|
|
110
|
+
s = p.split("=", 1)
|
|
111
|
+
value = "" if len(s) == 1 else s[1]
|
|
112
|
+
# check to see if we have to split them in array or not
|
|
113
|
+
if cmd in MI_ARRAY_PARAMS_COMMANDS and \
|
|
114
|
+
"-j" not in modifiers and \
|
|
115
|
+
MI_ARRAY_PARAMS_COMMANDS[cmd][1] == s[0]:
|
|
116
|
+
value = shlex.split(value)
|
|
117
|
+
new_params[s[0]] = value
|
|
118
|
+
else:
|
|
119
|
+
# old style positional parameters
|
|
120
|
+
logger.debug("positional parameters are used")
|
|
121
|
+
# if the command is not in MI_ARRAY_PARAMS_COMMANDS, return the
|
|
122
|
+
# parameters as they are
|
|
123
|
+
logger.debug("0. {}".format(params))
|
|
124
|
+
if "-j" in modifiers:
|
|
125
|
+
json_params = []
|
|
126
|
+
for x in params:
|
|
127
|
+
try:
|
|
128
|
+
x = json.loads(x)
|
|
129
|
+
except:
|
|
130
|
+
pass
|
|
131
|
+
json_params.append(x)
|
|
132
|
+
params = json_params
|
|
133
|
+
|
|
134
|
+
if not cmd in MI_ARRAY_PARAMS_COMMANDS or "-j" in modifiers:
|
|
135
|
+
return params
|
|
136
|
+
# build params based on their index
|
|
137
|
+
new_params = params[0:MI_ARRAY_PARAMS_COMMANDS[cmd][0]]
|
|
138
|
+
if params[MI_ARRAY_PARAMS_COMMANDS[cmd][0]:]:
|
|
139
|
+
new_params.append(params[MI_ARRAY_PARAMS_COMMANDS[cmd][0]:])
|
|
140
|
+
return new_params
|
|
141
|
+
|
|
142
|
+
def __invoke__(self, cmd, params=None, modifiers=None):
|
|
143
|
+
params = self.parse_params(cmd, params, modifiers)
|
|
144
|
+
# Mi Module works with JSON Communication
|
|
145
|
+
logger.debug("running command '{}' '{}'".format(cmd, params))
|
|
146
|
+
res = comm.execute(cmd, params)
|
|
147
|
+
if res is None:
|
|
148
|
+
return -1
|
|
149
|
+
output_type = cfg.get('output_type')
|
|
150
|
+
if output_type == "pretty-print":
|
|
151
|
+
self.print_pretty_print(res)
|
|
152
|
+
elif output_type == "dictionary":
|
|
153
|
+
self.print_dictionary(res)
|
|
154
|
+
elif output_type == "lines":
|
|
155
|
+
self.print_lines(res)
|
|
156
|
+
elif output_type == "yaml":
|
|
157
|
+
self.print_yaml(res)
|
|
158
|
+
elif output_type == "none":
|
|
159
|
+
pass # no one interested in the reply
|
|
160
|
+
else:
|
|
161
|
+
logger.error("unknown output_type='{}'! Dropping output!"
|
|
162
|
+
.format(output_type))
|
|
163
|
+
return 0
|
|
164
|
+
|
|
165
|
+
def __complete__(self, command, text, line, begidx, endidx):
|
|
166
|
+
# TODO: shall we cache this?
|
|
167
|
+
params_arr = comm.execute('which', {'command': command})
|
|
168
|
+
if len(text) == 0:
|
|
169
|
+
# if last character is an equal, it's probably a value, or it will
|
|
170
|
+
if line[-1] == "=":
|
|
171
|
+
return ['']
|
|
172
|
+
params = self.get_params_names(line)
|
|
173
|
+
if params is None:
|
|
174
|
+
flat_list = list([item for sublist in params_arr for item in sublist])
|
|
175
|
+
else:
|
|
176
|
+
# check in the line to see the parameters we've used
|
|
177
|
+
flat_list = set()
|
|
178
|
+
for p in params_arr:
|
|
179
|
+
sp = set(p)
|
|
180
|
+
if params.issubset(sp):
|
|
181
|
+
flat_list = flat_list.union(sp)
|
|
182
|
+
flat_list = flat_list - params
|
|
183
|
+
else:
|
|
184
|
+
flat_list = []
|
|
185
|
+
for l in params_arr:
|
|
186
|
+
p = [ x for x in l if x.startswith(text) ]
|
|
187
|
+
if len(p) != 0:
|
|
188
|
+
flat_list += p
|
|
189
|
+
l = [ x + "=" for x in list(dict.fromkeys(flat_list)) ]
|
|
190
|
+
return l if len(l) > 0 else ['']
|
|
191
|
+
|
|
192
|
+
def __exclude__(self):
|
|
193
|
+
vld = comm.valid()
|
|
194
|
+
return (not vld[0], vld[1])
|
|
195
|
+
|
|
196
|
+
def __get_methods__(self):
|
|
197
|
+
return comm.execute('which')
|
|
198
|
+
|
|
199
|
+
def __get_modifiers__(self):
|
|
200
|
+
return MI_MODIFIERS
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
##
|
|
3
|
+
## This file is part of OpenSIPS CLI
|
|
4
|
+
## (see https://github.com/OpenSIPS/opensips-cli).
|
|
5
|
+
##
|
|
6
|
+
## This program is free software: you can redistribute it and/or modify
|
|
7
|
+
## it under the terms of the GNU General Public License as published by
|
|
8
|
+
## the Free Software Foundation, either version 3 of the License, or
|
|
9
|
+
## (at your option) any later version.
|
|
10
|
+
##
|
|
11
|
+
## This program is distributed in the hope that it will be useful,
|
|
12
|
+
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
## GNU General Public License for more details.
|
|
15
|
+
##
|
|
16
|
+
## You should have received a copy of the GNU General Public License
|
|
17
|
+
## along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
18
|
+
##
|
|
19
|
+
|
|
20
|
+
from opensipscli.module import Module
|
|
21
|
+
from opensipscli.logger import logger
|
|
22
|
+
from socket import gethostname
|
|
23
|
+
from pprint import pprint
|
|
24
|
+
from time import gmtime, mktime
|
|
25
|
+
from os.path import exists, join, dirname
|
|
26
|
+
from os import makedirs
|
|
27
|
+
from opensipscli.config import cfg, OpenSIPSCLIConfig
|
|
28
|
+
from random import randrange
|
|
29
|
+
|
|
30
|
+
openssl_version = None
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
from cryptography import x509
|
|
34
|
+
from cryptography.hazmat.backends import default_backend
|
|
35
|
+
from cryptography.hazmat.primitives import hashes, serialization
|
|
36
|
+
from cryptography.hazmat.primitives.asymmetric import rsa
|
|
37
|
+
from cryptography.x509.oid import NameOID
|
|
38
|
+
import datetime
|
|
39
|
+
openssl_version = 'cryptography'
|
|
40
|
+
except ImportError:
|
|
41
|
+
logger.info("cryptography library not available!")
|
|
42
|
+
try:
|
|
43
|
+
from OpenSSL import crypto, SSL
|
|
44
|
+
openssl_version = 'openssl'
|
|
45
|
+
except (TypeError, ImportError):
|
|
46
|
+
logger.info("OpenSSL library not available!")
|
|
47
|
+
|
|
48
|
+
class tlsCert:
|
|
49
|
+
|
|
50
|
+
def __init__(self, prefix, cfg=None):
|
|
51
|
+
|
|
52
|
+
if not cfg:
|
|
53
|
+
self.load(prefix)
|
|
54
|
+
return
|
|
55
|
+
self.CN = cfg.read_param("tls_"+prefix+"_common_name", "Website address (CN)", "opensips.org")
|
|
56
|
+
self.C = cfg.read_param("tls_"+prefix+"_country", "Country (C)", "RO")
|
|
57
|
+
self.ST = cfg.read_param("tls_"+prefix+"_state", "State (ST)", "Bucharest")
|
|
58
|
+
self.L = cfg.read_param("tls_"+prefix+"_locality", "Locality (L)", "Bucharest")
|
|
59
|
+
self.O = cfg.read_param("tls_"+prefix+"_organisation", "Organization (O)", "OpenSIPS")
|
|
60
|
+
self.OU = cfg.read_param("tls_"+prefix+"_organisational_unit", "Organisational Unit (OU)", "Project")
|
|
61
|
+
self.notafter = int(cfg.read_param("tls_"+prefix+"_notafter", "Certificate validity (seconds)", 315360000))
|
|
62
|
+
self.md = cfg.read_param("tls_"+prefix+"_md", "Digest Algorithm", "SHA256")
|
|
63
|
+
|
|
64
|
+
class tlsKey:
|
|
65
|
+
|
|
66
|
+
def __init__(self, prefix, cfg=None):
|
|
67
|
+
|
|
68
|
+
if not cfg:
|
|
69
|
+
self.load(prefix)
|
|
70
|
+
return
|
|
71
|
+
self.key_size = int(cfg.read_param("tls_"+prefix+"_key_size", "RSA key size (bits)", 4096))
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class tlsOpenSSLCert(tlsCert):
|
|
75
|
+
|
|
76
|
+
def __init__(self, prefix, cfg=None):
|
|
77
|
+
super().__init__(prefix, cfg)
|
|
78
|
+
if not cfg:
|
|
79
|
+
return
|
|
80
|
+
cert = crypto.X509()
|
|
81
|
+
cert.set_version(2)
|
|
82
|
+
cert.get_subject().CN = self.CN
|
|
83
|
+
cert.get_subject().C = self.C
|
|
84
|
+
cert.get_subject().ST = self.ST
|
|
85
|
+
cert.get_subject().L = self.L
|
|
86
|
+
cert.get_subject().O = self.O
|
|
87
|
+
cert.get_subject().OU = self.OU
|
|
88
|
+
cert.set_serial_number(randrange(100000))
|
|
89
|
+
cert.gmtime_adj_notBefore(0)
|
|
90
|
+
cert.gmtime_adj_notAfter(self.notafter)
|
|
91
|
+
|
|
92
|
+
extensions = [
|
|
93
|
+
crypto.X509Extension(b'basicConstraints', False, b'CA:TRUE'),
|
|
94
|
+
crypto.X509Extension(b'extendedKeyUsage', False, b'clientAuth,serverAuth')
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
cert.add_extensions(extensions)
|
|
98
|
+
|
|
99
|
+
self.cert = cert
|
|
100
|
+
|
|
101
|
+
def load(self, cacert):
|
|
102
|
+
self.cert = crypto.load_certificate(crypto.FILETYPE_PEM, open(cacert, 'rt').read())
|
|
103
|
+
|
|
104
|
+
def sign(self, key):
|
|
105
|
+
self.cert.set_pubkey(key)
|
|
106
|
+
self.cert.sign(key.key, self.md)
|
|
107
|
+
|
|
108
|
+
def set_issuer(self, issuer):
|
|
109
|
+
self.cert.set_issuer(issuer)
|
|
110
|
+
|
|
111
|
+
def get_subject(self):
|
|
112
|
+
return self.cert.get_subject()
|
|
113
|
+
|
|
114
|
+
def dump(self):
|
|
115
|
+
return crypto.dump_certificate(crypto.FILETYPE_PEM, self.cert).decode('utf-8')
|
|
116
|
+
|
|
117
|
+
class tlsCryptographyCert(tlsCert):
|
|
118
|
+
|
|
119
|
+
def __init__(self, prefix, cfg=None):
|
|
120
|
+
super().__init__(prefix, cfg)
|
|
121
|
+
if not cfg:
|
|
122
|
+
return
|
|
123
|
+
builder = x509.CertificateBuilder()
|
|
124
|
+
builder = builder.subject_name(x509.Name([
|
|
125
|
+
x509.NameAttribute(NameOID.COMMON_NAME, self.CN),
|
|
126
|
+
x509.NameAttribute(NameOID.COUNTRY_NAME, self.C),
|
|
127
|
+
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, self.ST),
|
|
128
|
+
x509.NameAttribute(NameOID.LOCALITY_NAME, self.L),
|
|
129
|
+
x509.NameAttribute(NameOID.ORGANIZATION_NAME, self.O),
|
|
130
|
+
x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, self.OU),
|
|
131
|
+
]))
|
|
132
|
+
builder = builder.serial_number(x509.random_serial_number())
|
|
133
|
+
builder = builder.not_valid_before(datetime.datetime.today() -
|
|
134
|
+
datetime.timedelta(1))
|
|
135
|
+
builder = builder.not_valid_after(datetime.datetime.today() +
|
|
136
|
+
datetime.timedelta(0, self.notafter))
|
|
137
|
+
builder = builder.add_extension(
|
|
138
|
+
x509.BasicConstraints(ca=False, path_length=None),
|
|
139
|
+
critical=False
|
|
140
|
+
)
|
|
141
|
+
builder = builder.add_extension(
|
|
142
|
+
x509.ExtendedKeyUsage([
|
|
143
|
+
x509.ExtendedKeyUsageOID.CLIENT_AUTH,
|
|
144
|
+
x509.ExtendedKeyUsageOID.SERVER_AUTH]),
|
|
145
|
+
critical=False
|
|
146
|
+
)
|
|
147
|
+
self.builder = builder
|
|
148
|
+
self.cert = None
|
|
149
|
+
|
|
150
|
+
def load(self, cacert):
|
|
151
|
+
self.cert = x509.load_pem_x509_certificate(open(cacert, 'rb').read())
|
|
152
|
+
|
|
153
|
+
def sign(self, key):
|
|
154
|
+
self.builder = self.builder.public_key(key.key.public_key())
|
|
155
|
+
self.cert = self.builder.sign(private_key = key.key,
|
|
156
|
+
algorithm=getattr(hashes, self.md)(),
|
|
157
|
+
backend=default_backend())
|
|
158
|
+
|
|
159
|
+
def set_issuer(self, issuer):
|
|
160
|
+
self.builder = self.builder.issuer_name(issuer)
|
|
161
|
+
|
|
162
|
+
def get_subject(self):
|
|
163
|
+
if self.cert:
|
|
164
|
+
return self.cert.subject
|
|
165
|
+
return self.builder._subject_name
|
|
166
|
+
|
|
167
|
+
def dump(self):
|
|
168
|
+
return self.cert.public_bytes(encoding=serialization.Encoding.PEM).decode('utf-8')
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class tlsOpenSSLKey(tlsKey):
|
|
172
|
+
|
|
173
|
+
def __init__(self, prefix, cfg=None):
|
|
174
|
+
super().__init__(prefix, cfg)
|
|
175
|
+
if not cfg:
|
|
176
|
+
return
|
|
177
|
+
key = crypto.PKey()
|
|
178
|
+
key.generate_key(crypto.TYPE_RSA, self.key_size)
|
|
179
|
+
self.key = key
|
|
180
|
+
|
|
181
|
+
def dump(self):
|
|
182
|
+
return crypto.dump_privatekey(crypto.FILETYPE_PEM, self.key).decode('utf-8')
|
|
183
|
+
|
|
184
|
+
def load(self, key):
|
|
185
|
+
self.key = crypto.load_privatekey(crypto.FILETYPE_PEM, open(key, 'rt').read())
|
|
186
|
+
|
|
187
|
+
class tlsCryptographyKey(tlsKey):
|
|
188
|
+
|
|
189
|
+
def __init__(self, prefix, cfg=None):
|
|
190
|
+
super().__init__(prefix, cfg)
|
|
191
|
+
if not cfg:
|
|
192
|
+
return
|
|
193
|
+
self.key = rsa.generate_private_key(
|
|
194
|
+
key_size=self.key_size,
|
|
195
|
+
public_exponent=65537,
|
|
196
|
+
backend=default_backend()
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
def dump(self):
|
|
200
|
+
return self.key.private_bytes(encoding=serialization.Encoding.PEM,
|
|
201
|
+
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
|
202
|
+
encryption_algorithm=serialization.NoEncryption()
|
|
203
|
+
).decode('utf-8')
|
|
204
|
+
|
|
205
|
+
def load(self, key):
|
|
206
|
+
self.key = serialization.load_pem_private_key(open(key, 'rb').read(),
|
|
207
|
+
password=None)
|
|
208
|
+
|
|
209
|
+
class tls(Module):
|
|
210
|
+
def do_rootCA(self, params, modifiers=None):
|
|
211
|
+
global cfg
|
|
212
|
+
logger.info("Preparing to generate CA cert + key...")
|
|
213
|
+
|
|
214
|
+
# TODO
|
|
215
|
+
# separate cli.cfg files for TLS are fully deprecated, this if block is
|
|
216
|
+
# only kept for backwards-compatibility. Remove starting from v3.2! <3
|
|
217
|
+
if cfg.exists('tls_ca_config'):
|
|
218
|
+
tls_cfg = cfg.get('tls_ca_config')
|
|
219
|
+
cfg = OpenSIPSCLIConfig()
|
|
220
|
+
cfg.parse(tls_cfg)
|
|
221
|
+
|
|
222
|
+
ca_dir = cfg.read_param("tls_ca_dir", "Output directory", "/etc/opensips/tls/rootCA/")
|
|
223
|
+
cert_file = cfg.read_param("tls_ca_cert_file", "Output cert file", "cacert.pem")
|
|
224
|
+
key_file = cfg.read_param("tls_ca_key_file", "Output key file", "private/cakey.pem")
|
|
225
|
+
c_f = join(ca_dir, cert_file)
|
|
226
|
+
k_f = join(ca_dir, key_file)
|
|
227
|
+
|
|
228
|
+
if (exists(c_f) or exists(k_f)) and not cfg.read_param("tls_ca_overwrite",
|
|
229
|
+
"CA certificate or key already exists, overwrite?", "yes", True):
|
|
230
|
+
return
|
|
231
|
+
|
|
232
|
+
if openssl_version == 'openssl':
|
|
233
|
+
cert = tlsOpenSSLCert("ca", cfg)
|
|
234
|
+
key = tlsOpenSSLKey("ca", cfg)
|
|
235
|
+
else:
|
|
236
|
+
cert = tlsCryptographyCert("ca", cfg)
|
|
237
|
+
key = tlsCryptographyKey("ca", cfg)
|
|
238
|
+
|
|
239
|
+
cert.set_issuer(cert.get_subject())
|
|
240
|
+
cert.sign(key)
|
|
241
|
+
|
|
242
|
+
try:
|
|
243
|
+
if not exists(dirname(c_f)):
|
|
244
|
+
makedirs(dirname(c_f))
|
|
245
|
+
open(c_f, "wt").write(cert.dump())
|
|
246
|
+
except Exception as e:
|
|
247
|
+
logger.exception(e)
|
|
248
|
+
logger.error("Failed to write to %s", c_f)
|
|
249
|
+
return
|
|
250
|
+
|
|
251
|
+
try:
|
|
252
|
+
if not exists(dirname(k_f)):
|
|
253
|
+
makedirs(dirname(k_f))
|
|
254
|
+
open(k_f, "wt").write(key.dump())
|
|
255
|
+
except Exception as e:
|
|
256
|
+
logger.exception(e)
|
|
257
|
+
logger.error("Failed to write to %s", k_f)
|
|
258
|
+
return
|
|
259
|
+
|
|
260
|
+
logger.info("CA certificate created in " + c_f)
|
|
261
|
+
logger.info("CA private key created in " + k_f)
|
|
262
|
+
|
|
263
|
+
def do_userCERT(self, params, modifiers=None):
|
|
264
|
+
global cfg
|
|
265
|
+
logger.info("Preparing to generate user cert + key + CA list...")
|
|
266
|
+
|
|
267
|
+
# TODO
|
|
268
|
+
# separate cli.cfg files for TLS are fully deprecated, this if block is
|
|
269
|
+
# only kept for backwards-compatibility. Remove starting from v3.2! <3
|
|
270
|
+
if cfg.exists('tls_user_config'):
|
|
271
|
+
tls_cfg = cfg.get('tls_user_config')
|
|
272
|
+
cfg = OpenSIPSCLIConfig()
|
|
273
|
+
cfg.parse(tls_cfg)
|
|
274
|
+
|
|
275
|
+
user_dir = cfg.read_param("tls_user_dir", "Output directory", "/etc/opensips/tls/user/")
|
|
276
|
+
cert_file = cfg.read_param("tls_user_cert_file", "Output cert file", "user-cert.pem")
|
|
277
|
+
key_file = cfg.read_param("tls_user_key_file", "Output key file", "user-privkey.pem")
|
|
278
|
+
calist_file = cfg.read_param("tls_user_calist_file", "Output CA list file", "user-calist.pem")
|
|
279
|
+
|
|
280
|
+
c_f = join(user_dir, cert_file)
|
|
281
|
+
k_f = join(user_dir, key_file)
|
|
282
|
+
ca_f = join(user_dir, calist_file)
|
|
283
|
+
|
|
284
|
+
if (exists(c_f) or exists(k_f) or exists(ca_f)) and not cfg.read_param("tls_user_overwrite",
|
|
285
|
+
"User certificate, key or CA list file already exists, overwrite?", "yes", True):
|
|
286
|
+
return
|
|
287
|
+
|
|
288
|
+
cacert = cfg.read_param("tls_user_cacert", "CA cert file", "/etc/opensips/tls/rootCA/cacert.pem")
|
|
289
|
+
cakey = cfg.read_param("tls_user_cakey", "CA key file", "/etc/opensips/tls/rootCA/private/cakey.pem")
|
|
290
|
+
|
|
291
|
+
try:
|
|
292
|
+
if openssl_version == 'openssl':
|
|
293
|
+
ca_cert = tlsOpenSSLCert(cacert)
|
|
294
|
+
else:
|
|
295
|
+
ca_cert = tlsCryptographyCert(cacert)
|
|
296
|
+
except Exception as e:
|
|
297
|
+
logger.exception(e)
|
|
298
|
+
logger.error("Failed to load %s", cacert)
|
|
299
|
+
return
|
|
300
|
+
|
|
301
|
+
try:
|
|
302
|
+
if openssl_version == 'openssl':
|
|
303
|
+
ca_key = tlsOpenSSLLey(cakey)
|
|
304
|
+
else:
|
|
305
|
+
ca_key = tlsCryptographyKey(cakey)
|
|
306
|
+
except Exception as e:
|
|
307
|
+
logger.exception(e)
|
|
308
|
+
logger.error("Failed to load %s", cakey)
|
|
309
|
+
return
|
|
310
|
+
|
|
311
|
+
# create a self-signed cert
|
|
312
|
+
if openssl_version == 'openssl':
|
|
313
|
+
cert = tlsOpenSSLCert("user", cfg)
|
|
314
|
+
key = tlsOpenSSLKey("user", cfg)
|
|
315
|
+
else:
|
|
316
|
+
cert = tlsCryptographyCert("user", cfg)
|
|
317
|
+
key = tlsCryptographyKey("user", cfg)
|
|
318
|
+
|
|
319
|
+
cert.set_issuer(ca_cert.get_subject())
|
|
320
|
+
cert.sign(ca_key)
|
|
321
|
+
try:
|
|
322
|
+
if not exists(dirname(c_f)):
|
|
323
|
+
makedirs(dirname(c_f))
|
|
324
|
+
open(c_f, "wt").write(cert.dump())
|
|
325
|
+
except Exception as e:
|
|
326
|
+
logger.exception(e)
|
|
327
|
+
logger.error("Failed to write to %s", c_f)
|
|
328
|
+
return
|
|
329
|
+
|
|
330
|
+
try:
|
|
331
|
+
if not exists(dirname(k_f)):
|
|
332
|
+
makedirs(dirname(k_f))
|
|
333
|
+
open(k_f, "wt").write(key.dump())
|
|
334
|
+
except Exception as e:
|
|
335
|
+
logger.exception(e)
|
|
336
|
+
logger.error("Failed to write to %s", k_f)
|
|
337
|
+
return
|
|
338
|
+
|
|
339
|
+
try:
|
|
340
|
+
if not exists(dirname(ca_f)):
|
|
341
|
+
makedirs(dirname(ca_f))
|
|
342
|
+
open(ca_f, "wt").write(ca_cert.dump())
|
|
343
|
+
except Exception as e:
|
|
344
|
+
logger.exception(e)
|
|
345
|
+
logger.error("Failed to write to %s", ca_f)
|
|
346
|
+
return
|
|
347
|
+
|
|
348
|
+
logger.info("user certificate created in " + c_f)
|
|
349
|
+
logger.info("user private key created in " + k_f)
|
|
350
|
+
logger.info("user CA list (chain of trust) created in " + ca_f)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def __exclude__(self):
|
|
354
|
+
return (not openssl_version, None)
|