Skip to content

Commit 24add76

Browse files
phlogistonjohnmergify[bot]
authored andcommitted
commands/satellite: add commands for satellite servers & keybridge
Instead of constantly having to define new dedicated subcommands for things that don't fit neatly under the `samba-container` umbrella, create a new concept for things provided by sambacc that "orbit" the samba & smbd processes but are very optional in most scenarios. Establish the keybridge server as our first satellite. The keybridge server can be set up via the sambacc config or configured/customized by command line values. In particular, the TLS credentials needed for KMIP can be specified on the command line while the other params are in the file because of ceph-shaped reasons. Signed-off-by: John Mulligan <[email protected]>
1 parent 4b94f0b commit 24add76

File tree

3 files changed

+324
-0
lines changed

3 files changed

+324
-0
lines changed

sambacc/commands/satellite/__init__.py

Whitespace-only changes.
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#
2+
# sambacc: a samba container configuration tool
3+
# Copyright (C) 2025 John Mulligan
4+
#
5+
# This program is free software: you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License as published by
7+
# the Free Software Foundation, either version 3 of the License, or
8+
# (at your option) any later version.
9+
#
10+
# This program is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU General Public License
16+
# along with this program. If not, see <http://www.gnu.org/licenses/>
17+
#
18+
19+
import sys
20+
import typing
21+
22+
23+
from .. import skips
24+
from ..cli import Context, Fail, commands
25+
from ..common import (
26+
CommandContext,
27+
enable_logging,
28+
env_to_cli,
29+
global_args,
30+
pre_action,
31+
)
32+
33+
34+
def _default(ctx: Context) -> None:
35+
sys.stdout.write(f"{sys.argv[0]} requires a subcommand.\n")
36+
sys.exit(1)
37+
38+
39+
def main(args: typing.Optional[typing.Sequence[str]] = None) -> None:
40+
pkg = "sambacc.commands.satellite"
41+
commands.include(".keybridge", package=pkg)
42+
43+
cli = commands.assemble(arg_func=global_args).parse_args(args)
44+
env_to_cli(cli)
45+
enable_logging(cli)
46+
if not cli.identity:
47+
raise Fail("missing container identity")
48+
49+
pre_action(cli)
50+
ctx = CommandContext(cli)
51+
skip = skips.test(ctx)
52+
if skip:
53+
print(f"Command Skipped: {skip}")
54+
return
55+
cfunc = getattr(cli, "cfunc", _default)
56+
cfunc(ctx)
57+
return
58+
59+
60+
if __name__ == "__main__":
61+
main()
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
#
2+
# sambacc: a samba container configuration tool
3+
# Copyright (C) 2025 John Mulligan
4+
#
5+
# This program is free software: you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License as published by
7+
# the Free Software Foundation, either version 3 of the License, or
8+
# (at your option) any later version.
9+
#
10+
# This program is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU General Public License
16+
# along with this program. If not, see <http://www.gnu.org/licenses/>
17+
#
18+
19+
import argparse
20+
import logging
21+
import signal
22+
import sys
23+
import typing
24+
25+
import sambacc.config
26+
27+
from ..cli import Context, Fail, commands
28+
29+
30+
if typing.TYPE_CHECKING:
31+
import sambacc.varlink.keybridge
32+
33+
Scope = sambacc.varlink.keybridge.KeyBridgeScope
34+
else:
35+
Scope = typing.Any
36+
37+
38+
_logger = logging.getLogger(__name__)
39+
40+
41+
def _serve_args(parser: argparse.ArgumentParser) -> None:
42+
parser.add_argument(
43+
"--config-section",
44+
default="",
45+
help="Select a keybridge configuration from the sambacc config",
46+
)
47+
parser.add_argument(
48+
"--verify-peer",
49+
action="store_true",
50+
help="Enable peer process verification",
51+
)
52+
parser.add_argument(
53+
"--peer-check-pid",
54+
type=_pcheck,
55+
help=(
56+
"Verify peers with given PIDs. Supply ints, ranges (<n0>-<nX>),"
57+
" or comma separated lists (<n0>,<n1>,<n2>...)"
58+
),
59+
)
60+
parser.add_argument(
61+
"--peer-check-uid",
62+
type=_pcheck,
63+
help=(
64+
"Verify peers with given UIDs. Supply ints, ranges (<n0>-<nX>),"
65+
" or comma separated lists (<n0>,<n1>,<n2>...)"
66+
),
67+
)
68+
parser.add_argument(
69+
"--peer-check-gid",
70+
type=_pcheck,
71+
help=(
72+
"Verify peers with given GIDs. Supply ints, ranges (<n0>-<nX>),"
73+
" or comma separated lists (<n0>,<n1>,<n2>...)"
74+
),
75+
)
76+
parser.add_argument(
77+
"--mem-scope",
78+
action="store_true",
79+
help="Enable keybridge in-memory scope (FOR TESTING ONLY)",
80+
)
81+
parser.add_argument(
82+
"--kmip-scope",
83+
action="store_true",
84+
help="Enable KMIP proxy scope",
85+
)
86+
parser.add_argument(
87+
"--kmip-port",
88+
type=int,
89+
help="KMIP server port",
90+
)
91+
parser.add_argument(
92+
"--kmip-host",
93+
dest="kmip_hosts",
94+
action="append",
95+
help="KMIP server address",
96+
)
97+
parser.add_argument(
98+
"--kmip-tls-cert",
99+
help="KMIP TLS server certificate",
100+
)
101+
parser.add_argument(
102+
"--kmip-tls-key",
103+
help="KMIP TLS server key",
104+
)
105+
parser.add_argument(
106+
"--kmip-tls-ca-cert",
107+
help="KMIP TLS CA certificate",
108+
)
109+
parser.add_argument(
110+
"address",
111+
type=_varlink_addr,
112+
help="Specify a unix:{path} value to bind to.",
113+
)
114+
115+
116+
def _varlink_addr(value: str) -> str:
117+
if value.startswith("unix:"):
118+
return value
119+
raise argparse.ArgumentTypeError(
120+
"socket name must be prefixed with 'unix:'"
121+
)
122+
123+
124+
class Restart(Exception):
125+
pass
126+
127+
128+
@commands.command(name="keybridge", arg_func=_serve_args)
129+
def serve_keybridge(ctx: Context) -> None:
130+
"""Start a keybridge varlink RPC server."""
131+
132+
def _handler(*args: typing.Any) -> None:
133+
raise Restart()
134+
135+
signal.signal(signal.SIGHUP, _handler)
136+
while True:
137+
try:
138+
_serve(ctx)
139+
return
140+
except KeyboardInterrupt:
141+
_logger.info("Exiting")
142+
sys.exit(0)
143+
except Restart:
144+
_logger.info("Re-starting server")
145+
continue
146+
147+
148+
def _serve(ctx: Context) -> None:
149+
import sambacc.varlink.keybridge
150+
import sambacc.varlink.server
151+
152+
cfg = ctx.instance_config.keybridge_config(name=ctx.cli.config_section)
153+
if not cfg:
154+
cfg = sambacc.config.KeyBridgeConfig({})
155+
if ctx.cli.mem_scope:
156+
cfg.update_mem_scope()
157+
if (
158+
ctx.cli.kmip_scope
159+
or ctx.cli.kmip_tls_cert
160+
or ctx.cli.kmip_tls_key
161+
or ctx.cli.kmip_tls_ca_cert
162+
):
163+
try:
164+
cfg.update_kmip_scope(
165+
ctx.cli.kmip_hosts,
166+
ctx.cli.kmip_port,
167+
ctx.cli.kmip_tls_cert,
168+
ctx.cli.kmip_tls_key,
169+
ctx.cli.kmip_tls_ca_cert,
170+
)
171+
except ValueError as err:
172+
raise Fail(str(err))
173+
174+
scopes = [_new_scope(ctx, s) for s in cfg.scopes()]
175+
if ctx.cli.verify_peer or (vcfg := cfg.verify()):
176+
scopes = [_verify_peer(ctx, vcfg, s) for s in scopes]
177+
if not scopes:
178+
raise Fail("no keybridge scopes defined")
179+
180+
sambacc.varlink.server.patch_varlink_encoder()
181+
opts = sambacc.varlink.server.VarlinkServerOptions(ctx.cli.address)
182+
srv = sambacc.varlink.server.VarlinkServer(opts)
183+
srv.add_endpoint(sambacc.varlink.keybridge.endpoint(scopes))
184+
with srv.serve():
185+
signal.pause()
186+
187+
188+
def _new_scope(
189+
ctx: Context, scope_cfg: sambacc.config.KeyBridgeScopeConfig
190+
) -> Scope:
191+
192+
if scope_cfg.type_name == "mem":
193+
return sambacc.varlink.keybridge.MemScope()
194+
elif scope_cfg.type_name == "kmip":
195+
return _kmip_scope(ctx, scope_cfg)
196+
else:
197+
raise ValueError(f"invalid scope type name: {scope_cfg.type_name!r}")
198+
199+
200+
def _kmip_scope(
201+
ctx: Context, scope_cfg: sambacc.config.KeyBridgeScopeConfig
202+
) -> Scope:
203+
import sambacc.kmip.scope
204+
205+
if not scope_cfg.hostnames:
206+
raise Fail("KMIP Store requires at least one KMIP host")
207+
if not scope_cfg.port or scope_cfg.port < 0:
208+
raise Fail("KMIP Store requires a KMIP port")
209+
tls = scope_cfg.tls_paths
210+
if not tls or not all(tls[k] for k in ("cert", "key", "ca_cert")):
211+
raise Fail(
212+
"KMIP Store requires TLS certificate, key, and CA certificate"
213+
)
214+
215+
return sambacc.kmip.scope.KMIPScope(
216+
scope_cfg.subname,
217+
hostnames=scope_cfg.hostnames,
218+
port=scope_cfg.port,
219+
tls_paths=sambacc.kmip.scope.TLSPaths(**tls),
220+
)
221+
222+
223+
def _verify_peer(
224+
ctx: Context,
225+
vcfg: typing.Optional[sambacc.config.KeyBridgeVerifyConfig],
226+
scope: Scope,
227+
) -> Scope:
228+
import sambacc.varlink.keybridge
229+
230+
if ctx.cli.peer_check_pid:
231+
check_pid = ctx.cli.peer_check_pid
232+
elif vcfg and vcfg.check_pid:
233+
check_pid = vcfg.check_pid
234+
else:
235+
check_pid = None
236+
if ctx.cli.peer_check_uid:
237+
check_uid = ctx.cli.peer_check_uid
238+
elif vcfg and vcfg.check_uid:
239+
check_uid = vcfg.check_uid
240+
else:
241+
check_uid = None
242+
if ctx.cli.peer_check_gid:
243+
check_gid = ctx.cli.peer_check_gid
244+
elif vcfg and vcfg.check_gid:
245+
check_gid = vcfg.check_gid
246+
else:
247+
check_gid = None
248+
249+
return sambacc.varlink.keybridge.VerifyPeerScopeWrapper(
250+
scope,
251+
check_pid=check_pid,
252+
check_uid=check_uid,
253+
check_gid=check_gid,
254+
)
255+
256+
257+
def _pcheck(
258+
value: typing.Optional[str],
259+
) -> typing.Union[None, range, typing.Collection[int]]:
260+
try:
261+
return sambacc.config.KeyBridgeVerifyConfig.parameter(value)
262+
except ValueError as err:
263+
raise argparse.ArgumentTypeError(str(err)) from err

0 commit comments

Comments
 (0)