Skip to content

Commit 6335d86

Browse files
committed
Add --choria-mcollective-certname flag for non-root cert identity
The choria-mcorpc-support library hardcodes the MCollective certname as <username>.mcollective for non-root users. This prevents non-root users from authenticating with a certificate that has a different CN (e.g. the host's own Puppet certificate). The only existing override is the MCOLLECTIVE_CERTNAME environment variable. Add a --choria-mcollective-certname CLI flag (and corresponding mcollective-certname transport option) that sets this env var internally. This keeps all Choria configuration in the same CLI flag layer and allows callers like smart_proxy_openbolt to pass the certname as a regular transport option rather than managing process environment. Signed-off-by: nmburgan <13688219+nmburgan@users.noreply.github.com>
1 parent dee4899 commit 6335d86

8 files changed

Lines changed: 129 additions & 10 deletions

File tree

developer-docs/choria/choria-transport-testing.md

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,16 @@ MCollective looks for client config files in this order (first readable wins):
7979
3. `/etc/choria/client.conf`
8080
4. `/etc/puppetlabs/mcollective/client.cfg`
8181

82-
### Generate a client certificate
82+
### Client certificate
8383

84-
The user running OpenBolt needs a certificate signed by the Puppet CA. For non-root
85-
users, MCollective resolves the certname as `<username>.mcollective` by default.
86-
Generate a matching certificate on the primary server:
84+
The user running OpenBolt needs a certificate signed by the Puppet CA.
85+
There are two approaches:
86+
87+
**Option A: Generate a `.mcollective` certificate (traditional)**
88+
89+
For non-root users, MCollective resolves the certname as
90+
`<username>.mcollective` by default. Generate a matching certificate on
91+
the primary server:
8792

8893
```bash
8994
sudo puppetserver ca generate --certname <username>.mcollective
@@ -103,6 +108,29 @@ sudo chown -R $(whoami) ~/.puppetlabs
103108
chmod 600 ~/.puppetlabs/etc/puppet/ssl/private_keys/*.pem
104109
```
105110

111+
**Option B: Reuse the host's Puppet certificate**
112+
113+
If the non-root user can read the host's Puppet SSL files (e.g. via
114+
group membership in the `puppet` group), you can skip certificate
115+
generation and use the host's own cert with `--choria-mcollective-certname`
116+
to override the automatic `<username>.mcollective` certname:
117+
118+
```bash
119+
bolt task run facts --targets node1.example.com \
120+
--transport choria \
121+
--choria-ssl-cert /etc/puppetlabs/puppet/ssl/certs/$(hostname -f).pem \
122+
--choria-ssl-key /etc/puppetlabs/puppet/ssl/private_keys/$(hostname -f).pem \
123+
--choria-ssl-ca /etc/puppetlabs/puppet/ssl/certs/ca.pem \
124+
--choria-mcollective-certname $(hostname -f)
125+
```
126+
127+
This requires a permissive `certname_whitelist` on the Choria servers
128+
(e.g. `/.*/`) since the host's FQDN does not match the default
129+
`\.mcollective$` pattern. In production, use a more restrictive pattern
130+
such as `/.*\.example\.com$/` to limit which certnames are accepted. See
131+
[choria-transport.md](../../documentation/choria-transport.md#non-root-certname)
132+
for details.
133+
106134
### Set up `~/.choriarc`
107135

108136
Create `~/.choriarc` with the NATS broker address and cert paths. Replace
@@ -270,7 +298,8 @@ choria::server_config:
270298
plugin.choria.middleware_hosts: "primary.example.com:4222"
271299
plugin.choria.use_srv: false
272300
273-
# Allow all callers for testing. Restrict in production.
301+
# Allow all callers for testing. In production, restrict callers to specific
302+
# certnames or a domain pattern like /.*\.example\.com$/.
274303
mcollective::site_policies:
275304
- action: "allow"
276305
callers: "/.*/"
@@ -279,6 +308,8 @@ mcollective::site_policies:
279308
classes: "*"
280309
281310
mcollective::client: true
311+
# Allow all certnames for testing. In production, replace /.*/ with a pattern
312+
# matching your environment, such as /.*\.example\.com$/.
282313
mcollective_choria::config:
283314
security.certname_whitelist: "/\\.mcollective$/, /.*/"
284315

documentation/choria-transport.md

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ targets:
9595
| `config-file` | `--choria-config-file` | String | (auto-detected) | Path to a Choria/MCollective client config file. |
9696
| `host` | | String | (from URI) | Target's Choria identity (FQDN). Overrides the hostname from the URI. |
9797
| `interpreters` | | Hash | (none) | File extension to interpreter mapping (e.g., `{".rb": "/usr/bin/ruby"}`). |
98+
| `mcollective-certname` | `--choria-mcollective-certname` | String | (auto) | Override the MCollective certname for Choria client identity. See [Non-root certname](#non-root-certname) below. |
9899
| `nats-connection-timeout` | `--nats-connection-timeout` | Integer | `30` | Seconds to wait for the TCP connection to the NATS broker. |
99100
| `nats-servers` | `--nats-servers` | String or Array | (from config file) | NATS broker addresses in `nats://host:port` format (comma-separated for multiple). Multiple servers provide failover if a broker is unavailable. Overrides the config file. |
100101
| `puppet-environment` | `--choria-puppet-environment` | String | `production` | Puppet environment for bolt_tasks file URIs. |
@@ -130,6 +131,47 @@ the Choria config file.
130131
you must provide all three. Partial SSL configurations are rejected during
131132
validation.
132133

134+
### Non-root certname
135+
136+
The `choria-mcorpc-support` library derives the MCollective certname as
137+
`<username>.mcollective` for non-root users. This certname is embedded
138+
in signed messages and validated against the SSL certificate's CN during
139+
`check_ssl_setup`. When running as a non-root user (e.g. `foreman-proxy`)
140+
with the host's own Puppet certificate, the automatic certname
141+
(`foreman-proxy.mcollective`) does not match the certificate's CN
142+
(the host's FQDN), causing authentication failures.
143+
144+
The `mcollective-certname` option overrides this automatic derivation.
145+
Set it to the CN of the certificate you are authenticating with:
146+
147+
```bash
148+
bolt task run facts --targets node1.example.com \
149+
--transport choria \
150+
--choria-ssl-cert /etc/puppetlabs/puppet/ssl/certs/primary.example.com.pem \
151+
--choria-ssl-key /etc/puppetlabs/puppet/ssl/private_keys/primary.example.com.pem \
152+
--choria-ssl-ca /etc/puppetlabs/puppet/ssl/certs/ca.pem \
153+
--choria-mcollective-certname primary.example.com
154+
```
155+
156+
Or in the inventory file:
157+
158+
```yaml
159+
config:
160+
transport: choria
161+
choria:
162+
mcollective-certname: primary.example.com
163+
ssl-cert: /etc/puppetlabs/puppet/ssl/certs/primary.example.com.pem
164+
ssl-key: /etc/puppetlabs/puppet/ssl/private_keys/primary.example.com.pem
165+
ssl-ca: /etc/puppetlabs/puppet/ssl/certs/ca.pem
166+
```
167+
168+
This is not needed when running as root (the library uses the configured
169+
identity directly) or when using a certificate that matches the
170+
`<username>.mcollective` naming convention.
171+
172+
When using OpenBolt through `smart_proxy_openbolt`, the proxy sets this
173+
automatically from its SSL certificate.
174+
133175
## Operations
134176

135177
### run_command
@@ -386,6 +428,7 @@ local modulepath matters.
386428
long-running infrastructure.
387429

388430
11. **MCollective client library refuses to run as root.** Use a non-root
389-
user with a Puppet CA-signed certificate. See the
390-
[testing guide](choria-transport-testing.md#running-bolt-as-a-non-root-user)
391-
for setup instructions.
431+
user with a Puppet CA-signed certificate. When using a certificate
432+
whose CN does not match `<username>.mcollective`, set the
433+
`mcollective-certname` option to the certificate's CN. See
434+
[Non-root certname](#non-root-certname) above.

lib/bolt/bolt_option_parser.rb

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ class BoltOptionParser < OptionParser
1313
run_context: %w[concurrency inventoryfile save-rerun cleanup puppetdb],
1414
global_config_setters: PROJECT_PATHS + %w[modulepath],
1515
transports: %w[transport connect-timeout tty native-ssh ssh-command copy-command],
16-
choria: %w[choria-config-file choria-ssl-ca choria-ssl-cert choria-ssl-key
16+
choria: %w[choria-config-file choria-mcollective-certname
17+
choria-ssl-ca choria-ssl-cert choria-ssl-key
1718
choria-collective choria-puppet-environment choria-rpc-timeout
1819
choria-task-timeout choria-command-timeout nats-servers
1920
nats-connection-timeout],
@@ -1108,6 +1109,14 @@ def initialize(options)
11081109
'Path to a Choria/MCollective client configuration file.') do |path|
11091110
@options[:'config-file'] = path
11101111
end
1112+
define('--choria-mcollective-certname NAME',
1113+
'Override the MCollective certname for Choria client identity.',
1114+
'The choria-mcorpc-support library identifies non-root clients',
1115+
"as '<username>.mcollective', which fails when authenticating",
1116+
"with a certificate that has a different CN (e.g. the host's",
1117+
'Puppet cert). Set this to the CN of the certificate being used.') do |name|
1118+
@options[:'mcollective-certname'] = name
1119+
end
11111120
define('--choria-ssl-ca PATH',
11121121
'CA certificate path for Choria TLS authentication.') do |path|
11131122
@options[:'ssl-ca'] = path

lib/bolt/config/transport/choria.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class Choria < Base
1414
config-file
1515
host
1616
interpreters
17+
mcollective-certname
1718
nats-connection-timeout
1819
nats-servers
1920
puppet-environment

lib/bolt/config/transport/options.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,15 @@ module Options
275275
_plugin: true,
276276
_example: %w[defaults hmac-md5]
277277
},
278+
"mcollective-certname" => {
279+
type: String,
280+
description: "Override the MCollective certname used for Choria client identity. " \
281+
"The choria-mcorpc-support library identifies non-root clients as " \
282+
"'<username>.mcollective'. Set this when authenticating with a certificate " \
283+
"whose CN differs from that default (e.g. the host's Puppet cert).",
284+
_plugin: true,
285+
_example: "primary.example.com"
286+
},
278287
"nats-servers" => {
279288
type: [String, Array],
280289
description: "One or more NATS server addresses for the Choria transport. Overrides the middleware " \

lib/bolt/transport/choria/client.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ def configure_client(target)
6868
logger.debug { "Loaded Choria client config from #{config_file}" }
6969
end
7070

71+
if opts['mcollective-certname']
72+
ENV['MCOLLECTIVE_CERTNAME'] = opts['mcollective-certname']
73+
logger.debug { "MCOLLECTIVE_CERTNAME set to #{opts['mcollective-certname']}" }
74+
end
75+
7176
if opts['nats-servers']
7277
servers = [opts['nats-servers']].flatten
7378
config.pluginconf['choria.middleware_hosts'] = servers.join(',')

spec/unit/config/transport/choria_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
context 'validating' do
2323
include_examples 'interpreters'
2424

25-
%w[task-agent config-file collective host puppet-environment ssl-ca ssl-cert ssl-key tmpdir].each do |opt|
25+
%w[task-agent config-file collective host mcollective-certname puppet-environment ssl-ca ssl-cert ssl-key tmpdir].each do |opt|
2626
it "#{opt} rejects non-string value" do
2727
data[opt] = 100
2828
expect { transport.new(data) }.to raise_error(Bolt::ValidationError)

spec/unit/transport/choria_spec.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,27 @@
99
include_context 'choria task'
1010
include BoltSpec::Sensitive
1111

12+
describe '#configure_client mcollective-certname' do
13+
before(:each) do
14+
ENV.delete('MCOLLECTIVE_CERTNAME')
15+
end
16+
17+
after(:each) do
18+
ENV.delete('MCOLLECTIVE_CERTNAME')
19+
end
20+
21+
it 'sets MCOLLECTIVE_CERTNAME when mcollective-certname option is provided' do
22+
inventory.set_config(target, %w[choria mcollective-certname], 'primary.example.com')
23+
transport.configure_client(target)
24+
expect(ENV['MCOLLECTIVE_CERTNAME']).to eq('primary.example.com')
25+
end
26+
27+
it 'does not set MCOLLECTIVE_CERTNAME when mcollective-certname is not provided' do
28+
transport.configure_client(target)
29+
expect(ENV.key?('MCOLLECTIVE_CERTNAME')).to be false
30+
end
31+
end
32+
1233
describe '#provided_features' do
1334
it 'includes shell and powershell' do
1435
expect(transport.provided_features).to eq(%w[shell powershell])

0 commit comments

Comments
 (0)