tls: add db_* commands for tls_mgm provisioning - #166
Open
dariusstefan wants to merge 7 commits into
Open
Conversation
The tls module could only write certificates to files, leaving the
tls_mgm table to be provisioned by hand. Add CRUD commands over it:
tls db_add <domain> [type] column=value ...
tls db_update <domain> [type] column=value ...
tls db_show <domain> [type]
tls db_delete <domain> [type]
tls db_list
A domain is identified by its name and its type (server or client),
matching the UNIQUE (domain, type) constraint of the table. Every
settable column is passed as 'column=value', so the whole schema is
reachable, not just the certificate and the key. The columns holding
PEM content take the path of the file holding it, which is read and
stored as a BLOB; every other column is stored as given.
Unknown columns, unreadable files and files that do not hold PEM are
rejected before the database is touched. After every change the
tls_reload MI command is issued, so a running OpenSIPS picks up the
domains without a restart.
dariusstefan
force-pushed
the
tls-db-provisioning
branch
from
September 9, 2026 12:50
78bb1d2 to
a45a669
Compare
A tls_mgm row is identified by (domain, type), so defaulting the type when it is not given lets these two commands change a different domain than the intended one. Require it for the commands that modify an existing row; db_add and db_show keep falling back to tls_db_type. Also guard against read_param() returning None when there is no terminal to prompt on, which made the type default path raise an AttributeError instead of reporting the missing value.
The type is part of the identity of a tls_mgm row, not an environment setting, so it does not belong in opensips-cli.cfg next to the database URL. Ask for it instead, defaulting to 'server' on empty input; the prompt already shows that default. db_update and db_delete keep requiring it as an argument.
db_update and db_delete errored out when the type was missing, while the domain right next to it was asked for, which is confusing when running the commands interactively. Ask for the type as well, and drop the default for these two commands instead: read_param() keeps asking until a type is given, so they still cannot pick a different row than the intended one.
db_list addresses no domain at all, so it never asks for a type.
'db_add a.example.org type=client' reported "unknown tls_mgm column 'type'", which is not true: the column exists, it just identifies the row and is passed as an argument. Name the three identity columns and say so. Parse the columns before resolving the domain as well, so that such a command is rejected right away rather than after asking for the domain and its type.
Rejecting 'domain=' and 'type=' meant the identity of a row was the one thing that could not be written the way every other column is, which is hard to justify to someone who just read the column list. Take everything as 'column=value', keep the first two arguments as a shorthand for the domain and its type, and refuse only the combination of the two, which is the single ambiguous case. Whatever is left out is then asked for, in one place instead of one per spelling. This folds tls_db_domain() into tls_db_params(), so db_show and db_delete go through the same parser and now report the columns they do not take. 'id' stays refused: the database generates it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
The
tlsmodule could only write certificates and keys to files. Provisioning thetls_mgmtable — wherecertificate,private_keyandca_listare stored as BLOBs rather than paths — had to be done by hand in SQL.What this adds
Everything is given as
column=value, so the whole schema is reachable —method,cipher_list,verify_cert,match_ip_addressand the rest, not just the certificate and the key:The four columns holding PEM content (
certificate,private_key,ca_list,dh_params) take the path of the file holding it; the file is read and its content stored. Every other column is stored as the value it is given, paths included — soca_listreads its file whileca_dirandcrl_dirkeep the directory as such, which is whattls_mgmexpects of them. The distinction follows the schema: those four are theDB_BLOBcolumns, the rest areDB_STRING.db_updatechanges only the columns it is given, which makes certificate renewal a single command rather than a delete followed by an add.Columns left unset keep their schema default, so a server domain that relies on the module's global
certificate/private_keymodparams stays expressible —tls_mgmfalls back to them on its own.Identifying a domain
A row is identified by
(domain, type), matching the table'sUNIQUE (domain, type)constraint. Both are ordinary columns, so both can be written ascolumn=value:The first two bare arguments are a shorthand for the same two columns, in that order, which keeps the common case short:
The two spellings can be mixed freely; supplying the same column both ways is the one ambiguous case and is refused, as is a third bare argument.
idis refused outright — the database generates it.Whatever is left out is then asked for, in one place rather than one per spelling.
db_addanddb_showdefault the type toserver;db_updateanddb_deletehave no default and keep asking until one is given, so the two commands that change an existing row cannot pick a different one than intended.db_listaddresses no domain and asks for nothing.There is deliberately no configuration setting for the type: it identifies a row rather than describing the environment, so it does not belong in
opensips-cli.cfgnext to the database URL.Notes
column=valuefollows the convention already used by themimodule, by-o/--optionand by the interactivesetcommand, and is split withsplit('=', 1)like all of them.db_showanddb_deletenow report the columns they do not take instead of ignoring them.tls_reloadMI command is issued, so a running OpenSIPS picks up the domains without a restart. If OpenSIPS cannot be reached, a warning is logged and the domains load at the next restart.__complete__offers the column names in interactive mode.rootCA,userCERT) is untouched.Testing
Exercised by hand against MySQL 8.0 and a running OpenSIPS 4.0 with
tls_mgmin DB mode: provisioning a certificate and key from PEM files in both the named and the positional form, the scalar columns round-tripping, unset columns coming back as schema NULLs,db_showrendering every column with the private key hidden, rejection of a duplicate(domain, type), of a column given both ways, of a surplus argument, ofid=and of an unknown column,db_updatepatching a single column, anddb_deleteprompting for the type and removing the row. Thetls_reloadMI command reached OpenSIPS over the FIFO in each case.No automated tests are included.