Skip to content
This repository has been archived by the owner on Jan 4, 2021. It is now read-only.

Commit

Permalink
(#634) Support JWT based anonymous broker tls
Browse files Browse the repository at this point in the history
Signed-off-by: R.I.Pienaar <rip@devco.net>
  • Loading branch information
ripienaar committed Sep 13, 2020
1 parent 7e1e43b commit ed7a9fa
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 19 deletions.
6 changes: 3 additions & 3 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ GEM
hiera (3.6.0)
hocon (1.3.0)
httpclient (2.8.3)
jgrep (1.5.1)
json (2.2.0)
jgrep (1.5.4)
json (2.3.1)
json-schema (2.8.1)
addressable (>= 2.4)
json-schema-rspec (0.0.4)
Expand Down Expand Up @@ -102,7 +102,7 @@ GEM
pry (0.12.2)
coderay (~> 1.1.0)
method_source (~> 0.9.0)
public_suffix (4.0.1)
public_suffix (4.0.6)
puppet (6.11.1)
concurrent-ruby (~> 1.0)
deep_merge (~> 1.0)
Expand Down
9 changes: 7 additions & 2 deletions lib/mcollective/connector/nats.rb
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ def make_target(agent, type, collective, identity=nil)

case type
when :reply
"%s.reply.%s.%d.%d" % [collective, identity, current_pid, Client.request_sequence]
"%s.reply.%s.%d.%d" % [collective, Digest::MD5.hexdigest(choria.callerid), current_pid, Client.request_sequence]

when :broadcast, :request
"%s.broadcast.agent.%s" % [collective, agent]
Expand Down Expand Up @@ -416,7 +416,12 @@ def decorate_servers_with_users(servers)
user = get_option("nats.user", environment["MCOLLECTIVE_NATS_USERNAME"])
pass = get_option("nats.pass", environment["MCOLLECTIVE_NATS_PASSWORD"])

if user && pass
if choria.anon_tls?
user = PluginManager["security_plugin"].request_signer.token
pass = nil
end

if user || pass
servers.each do |uri|
uri.user = user
uri.password = pass
Expand Down
3 changes: 3 additions & 0 deletions lib/mcollective/security/choria.rb
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,10 @@ def has_client_private_key?
# Caller ids are in the form `choria=certname`
#
# @return [String] callerid
# @raise [Exception] for invalid callerid or JWT token
def callerid
return request_signer.callerid if choria.anon_tls?

"choria=%s" % certname
end

Expand Down
25 changes: 25 additions & 0 deletions lib/mcollective/signer/choria.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,31 @@ def sign_secure_request!(secure_request)
end
end

# Determines the callerid for this client
#
# When a remote signer is enabled the caller is extracted from the JWT
# otherwise a choria=user style ID is generated
#
# @return [String]
# @raise [Exception] when the JWT is invalid
def callerid
if remote_signer?
parts = token.split(".")

raise("Invalid JWT token") unless parts.length == 3

claims = JSON.parse(Base64.decode64(parts[1]))

raise("Invalid JWT token") unless claims.include?("callerid")
raise("Invalid JWT token") unless claims["callerid"].is_a?(String)
raise("Invalid JWT token") if claims["callerid"].empty?

claims["callerid"]
else
"choria=%s" % choria.certname
end
end

# The body that would be submitted to the remote service
#
# @param secure_request [Hash] a v1 secure request
Expand Down
21 changes: 21 additions & 0 deletions lib/mcollective/util/choria.rb
Original file line number Diff line number Diff line change
Expand Up @@ -461,13 +461,22 @@ def parse_pubcert(pubcert, log=true)
nil
end

# The callerid for the current client
#
# @return [String]
# @raise [Exception] when remote JWT is invalid
def callerid
PluginManager["security_plugin"].callerid
end

# Checks all the required SSL files exist
#
# @param log [Boolean] log warnings when true
# @return [Boolean]
# @raise [StandardError] on failure
def check_ssl_setup(log=true)
return true if $choria_unsafe_disable_protocol_security # rubocop:disable Style/GlobalVars
return true if anon_tls?

if Process.uid == 0 && PluginManager["security_plugin"].initiated_by == :client
raise(UserError, "The Choria client cannot be run as root")
Expand Down Expand Up @@ -735,6 +744,11 @@ def ssl_context
context.ca_file = ca_path
context.ssl_version = :TLSv1_2

if anon_tls?
context.verify_mode = OpenSSL::SSL::VERIFY_NONE
return context
end

public_cert = File.read(client_public_cert)
private_key = File.read(client_private_key)

Expand Down Expand Up @@ -849,6 +863,13 @@ def ca_path
File.join(ssl_dir, "certs", "ca.pem")
end

# Determines if Choria is configured for anonymous TLS mode
#
# @return [Boolean]
def anon_tls?
remote_signer_configured? && Util.str_to_bool(get_option("security.client_anon_tls", "false"))
end

# Determines if the CA exist
#
# @return [Boolean]
Expand Down
29 changes: 15 additions & 14 deletions spec/unit/mcollective/connector/nats_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ module MCollective
choria.stubs(:ssl_dir).returns("/ssl")
choria.stubs(:certname).returns("rspec_identity")
choria.stubs(:check_ssl_setup)
choria.stubs(:callerid).returns("choria=rip")

msg.agent = "rspec_agent"
msg.collective = "mcollective"
Expand Down Expand Up @@ -144,7 +145,7 @@ module MCollective
connector.stubs(:connected_server).returns("nats1.example.net")
expect(connector.headers_for(msg)).to eq(
"mc_sender" => "rspec_identity",
"reply-to" => "mcollective.reply.rspec_identity.999999.0",
"reply-to" => "mcollective.reply.d92f5de06d3c84950d8040e4253f08bb.999999.0",
"seen-by" => [["rspec_identity", "nats1.example.net"]]
)
end
Expand Down Expand Up @@ -200,8 +201,8 @@ module MCollective
end

it "should support :reply" do
expect(connector.make_target("rspec_agent", :reply, "mcollective")).to eq("mcollective.reply.rspec_identity.999999.0")
expect(connector.make_target("rspec_agent", :reply, "mcollective", "rsi")).to eq("mcollective.reply.rsi.999999.0")
expect(connector.make_target("rspec_agent", :reply, "mcollective")).to eq("mcollective.reply.d92f5de06d3c84950d8040e4253f08bb.999999.0")
expect(connector.make_target("rspec_agent", :reply, "mcollective", "rsi")).to eq("mcollective.reply.d92f5de06d3c84950d8040e4253f08bb.999999.0")
end

it "should support :broadcast and :request" do
Expand Down Expand Up @@ -229,7 +230,7 @@ module MCollective
"data" => "rspec",
"headers" => {
"mc_sender" => "rspec_identity",
"reply-to" => "mcollective.reply.rspec_identity.999999.0",
"reply-to" => "mcollective.reply.d92f5de06d3c84950d8040e4253f08bb.999999.0",
"federation" => {
"req" => "rspec.req.id",
"target" => ["mcollective.broadcast.agent.rspec_agent"]
Expand All @@ -239,8 +240,8 @@ module MCollective

JSON.expects(:dump).with(msg1).returns("msg_1")

connection.expects(:publish).with("choria.federation.net_a.federation", "msg_1", "mcollective.reply.rspec_identity.999999.0")
connection.expects(:publish).with("choria.federation.net_b.federation", "msg_1", "mcollective.reply.rspec_identity.999999.0")
connection.expects(:publish).with("choria.federation.net_a.federation", "msg_1", "mcollective.reply.d92f5de06d3c84950d8040e4253f08bb.999999.0")
connection.expects(:publish).with("choria.federation.net_b.federation", "msg_1", "mcollective.reply.d92f5de06d3c84950d8040e4253f08bb.999999.0")

connector.publish_federated_broadcast(msg)
end
Expand All @@ -263,7 +264,7 @@ module MCollective
"req" => "rspec.req.id",
"target" => (0..199).to_a.map {|i| "mcollective.node.#{i}.example"}
},
"reply-to" => "mcollective.reply.rspec_identity.999999.0"
"reply-to" => "mcollective.reply.d92f5de06d3c84950d8040e4253f08bb.999999.0"
}
}

Expand All @@ -276,17 +277,17 @@ module MCollective
"req" => "rspec.req.id",
"target" => (200..300).to_a.map {|i| "mcollective.node.#{i}.example"}
},
"reply-to" => "mcollective.reply.rspec_identity.999999.0"
"reply-to" => "mcollective.reply.d92f5de06d3c84950d8040e4253f08bb.999999.0"
}
}

JSON.expects(:dump).with(msg1).returns("msg_1")
JSON.expects(:dump).with(msg2).returns("msg_2")

connection.expects(:publish).with("choria.federation.net_a.federation", "msg_1", "mcollective.reply.rspec_identity.999999.0")
connection.expects(:publish).with("choria.federation.net_a.federation", "msg_2", "mcollective.reply.rspec_identity.999999.0")
connection.expects(:publish).with("choria.federation.net_b.federation", "msg_1", "mcollective.reply.rspec_identity.999999.0")
connection.expects(:publish).with("choria.federation.net_b.federation", "msg_2", "mcollective.reply.rspec_identity.999999.0")
connection.expects(:publish).with("choria.federation.net_a.federation", "msg_1", "mcollective.reply.d92f5de06d3c84950d8040e4253f08bb.999999.0")
connection.expects(:publish).with("choria.federation.net_a.federation", "msg_2", "mcollective.reply.d92f5de06d3c84950d8040e4253f08bb.999999.0")
connection.expects(:publish).with("choria.federation.net_b.federation", "msg_1", "mcollective.reply.d92f5de06d3c84950d8040e4253f08bb.999999.0")
connection.expects(:publish).with("choria.federation.net_b.federation", "msg_2", "mcollective.reply.d92f5de06d3c84950d8040e4253f08bb.999999.0")

connector.publish_federated_directed(msg)
end
Expand Down Expand Up @@ -324,14 +325,14 @@ module MCollective
"data" => "rspec",
"headers" => {
"mc_sender" => "rspec_identity",
"reply-to" => "mcollective.reply.rspec_identity.999999.0",
"reply-to" => "mcollective.reply.d92f5de06d3c84950d8040e4253f08bb.999999.0",
"federation" => {
"reply-to" => "reply.example"
}
}
).returns("json_stub")

connector.connection.expects(:publish).with("mcollective.broadcast.agent.rspec_agent", "json_stub", "mcollective.reply.rspec_identity.999999.0")
connector.connection.expects(:publish).with("mcollective.broadcast.agent.rspec_agent", "json_stub", "mcollective.reply.d92f5de06d3c84950d8040e4253f08bb.999999.0")

connector.publish_connected_broadcast(msg)
end
Expand Down

0 comments on commit ed7a9fa

Please sign in to comment.