Class: Protobuf::Nats::Config

Inherits:
Object
  • Object
show all
Defined in:
lib/protobuf/nats/config.rb

Constant Summary collapse

CONFIG_MUTEX =
::Mutex.new
DEFAULTS =
{
  :connect_timeout => nil,
  # Per-server reconnect attempt cap. -1 means reconnect forever
  # (nats-pure treats a negative value as infinite). When exhausted on
  # every server, nats-pure fires on_close and the connection is
  # terminally dead.
  :max_reconnect_attempts => 60_000,
  # Failover tuning; nil falls through to the nats-pure defaults
  # (reconnect_time_wait: 2s, ping_interval: 120s, max_outstanding_pings: 2).
  # A node that dies silently (partition, hard host failure) is only
  # detected after ping_interval * max_outstanding_pings, so lower these
  # for faster failover to a healthy node.
  :reconnect_time_wait => nil,
  :ping_interval => nil,
  :max_outstanding_pings => nil,
  :servers => nil,
  :connection_name => nil,
  :tls_client_cert => nil,
  :tls_client_key => nil,
  :tls_ca_cert => nil,
  :uses_tls => false,
  :server_subscription_key_do_not_subscribe_to_when_includes_any_of => [],
  :server_subscription_key_only_subscribe_to_when_includes_any_of => [],
  :subscription_key_replacements => [],
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeConfig

Returns a new instance of Config.



43
44
45
46
47
# File 'lib/protobuf/nats/config.rb', line 43

def initialize
  DEFAULTS.each_pair do |key, value|
    __send__("#{key}=", value)
  end
end

Instance Attribute Details

#connect_timeoutObject

Returns the value of attribute connect_timeout.



9
10
11
# File 'lib/protobuf/nats/config.rb', line 9

def connect_timeout
  @connect_timeout
end

#connection_nameObject

Returns the value of attribute connection_name.



9
10
11
# File 'lib/protobuf/nats/config.rb', line 9

def connection_name
  @connection_name
end

#max_outstanding_pingsObject

Returns the value of attribute max_outstanding_pings.



10
11
12
# File 'lib/protobuf/nats/config.rb', line 10

def max_outstanding_pings
  @max_outstanding_pings
end

#max_reconnect_attemptsObject

Returns the value of attribute max_reconnect_attempts.



9
10
11
# File 'lib/protobuf/nats/config.rb', line 9

def max_reconnect_attempts
  @max_reconnect_attempts
end

#ping_intervalObject

Returns the value of attribute ping_interval.



10
11
12
# File 'lib/protobuf/nats/config.rb', line 10

def ping_interval
  @ping_interval
end

#reconnect_time_waitObject

Returns the value of attribute reconnect_time_wait.



10
11
12
# File 'lib/protobuf/nats/config.rb', line 10

def reconnect_time_wait
  @reconnect_time_wait
end

#server_subscription_key_do_not_subscribe_to_when_includes_any_ofObject

Returns the value of attribute server_subscription_key_do_not_subscribe_to_when_includes_any_of.



11
12
13
# File 'lib/protobuf/nats/config.rb', line 11

def server_subscription_key_do_not_subscribe_to_when_includes_any_of
  @server_subscription_key_do_not_subscribe_to_when_includes_any_of
end

#server_subscription_key_only_subscribe_to_when_includes_any_ofObject

Returns the value of attribute server_subscription_key_only_subscribe_to_when_includes_any_of.



11
12
13
# File 'lib/protobuf/nats/config.rb', line 11

def server_subscription_key_only_subscribe_to_when_includes_any_of
  @server_subscription_key_only_subscribe_to_when_includes_any_of
end

#serversObject

Returns the value of attribute servers.



9
10
11
# File 'lib/protobuf/nats/config.rb', line 9

def servers
  @servers
end

#subscription_key_replacementsObject

Returns the value of attribute subscription_key_replacements.



11
12
13
# File 'lib/protobuf/nats/config.rb', line 11

def subscription_key_replacements
  @subscription_key_replacements
end

#tls_ca_certObject

Returns the value of attribute tls_ca_cert.



9
10
11
# File 'lib/protobuf/nats/config.rb', line 9

def tls_ca_cert
  @tls_ca_cert
end

#tls_client_certObject

Returns the value of attribute tls_client_cert.



9
10
11
# File 'lib/protobuf/nats/config.rb', line 9

def tls_client_cert
  @tls_client_cert
end

#tls_client_keyObject

Returns the value of attribute tls_client_key.



9
10
11
# File 'lib/protobuf/nats/config.rb', line 9

def tls_client_key
  @tls_client_key
end

#uses_tlsObject

Returns the value of attribute uses_tls.



9
10
11
# File 'lib/protobuf/nats/config.rb', line 9

def uses_tls
  @uses_tls
end

Instance Method Details

#connection_options(reload = false) ⇒ Object

Only the keys nats-pure's connect actually consumes. App-level settings (uses_tls, tls_client_cert, tls_client_key, tls_ca_cert, server_subscription_key_*, subscription_key_replacements) are read directly via their accessors elsewhere and must NOT be forwarded to nats-pure (it ignores unknown keys today, but that is brittle). The TLS cert/key/CA are folded into the :tls context by #new_tls_context.



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/protobuf/nats/config.rb', line 89

def connection_options(reload = false)
  @connection_options = false if reload
  @connection_options ||= begin
    options = {
      servers: servers,
      max_reconnect_attempts: max_reconnect_attempts,
      connect_timeout: connect_timeout,
      # nil values are safe to forward: nats-pure nil-fills each of these
      # with its own default during connect.
      reconnect_time_wait: reconnect_time_wait,
      ping_interval: ping_interval,
      max_outstanding_pings: max_outstanding_pings,
      # A friendly connection name surfaces in NATS server monitoring,
      # error reporting, and debugging (highly recommended by the NATS
      # docs). Shared by both the client and server connections since both
      # build from this hash.
      name: resolved_connection_name,
    }
    options[:tls] = {:context => new_tls_context} if uses_tls
    options
  end
end

#load_from_yml(reload = false) ⇒ Object



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/protobuf/nats/config.rb', line 49

def load_from_yml(reload = false)
  CONFIG_MUTEX.synchronize do
    @load_from_yml = nil if reload
    @load_from_yml ||= begin
      env = ENV["RAILS_ENV"] || ENV["RACK_ENV"] || ENV["APP_ENV"] || "development"

      yaml_config = {}
      config_path = ENV["PROTOBUF_NATS_CONFIG_PATH"] || ::File.join("config", "protobuf_nats.yml")
      absolute_config_path = ::File.expand_path(config_path)
      if ::File.exist?(absolute_config_path)
        yaml_string = ::ERB.new(::File.read(absolute_config_path)).result
        # safe_load (no arbitrary object deserialization) with aliases
        # enabled so the common `&defaults` / `<<: *defaults` pattern works.
        parsed = ::YAML.safe_load(yaml_string, :aliases => true)

        # An empty file parses to nil/false, and a file without a section
        # for the current env yields nil on lookup -- guard both so we
        # don't blow up with NoMethodError below.
        yaml_config = (parsed && parsed[env]) || {}
      end

      DEFAULTS.each_pair do |key, value|
        setting = yaml_config[key.to_s]
        __send__("#{key}=", setting) if setting
      end

      # Reload the connection options hash
      connection_options(true)

      true
    end
  end
end

#make_subscription_key_replacements(subscription_key) ⇒ Object



166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/protobuf/nats/config.rb', line 166

def make_subscription_key_replacements(subscription_key)
  subscription_key_replacements.each do |replacement|
    match = replacement.keys.first
    replacement = replacement[match]

    if subscription_key.include?(match)
      return subscription_key.gsub(match, replacement)
    end
  end

  subscription_key
end

#new_tls_contextObject



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/protobuf/nats/config.rb', line 119

def new_tls_context
  tls_context = ::OpenSSL::SSL::SSLContext.new
  # Floor at TLS 1.2, ceiling at TLS 1.3 (replaces the deprecated
  # ssl_version=:TLSv1_2 hard pin). The client offers 1.2 and 1.3 and
  # negotiates the highest the server also supports, so a TLS-1.2-only
  # transport still connects (verified on JRuby 9.4 and 10.0).
  #
  # An OpenSSL build without TLS 1.3 support does not define
  # TLS1_3_VERSION (#7); degrade to a 1.2-only ceiling there instead of
  # raising NameError at connect time.
  tls_context.min_version = ::OpenSSL::SSL::TLS1_2_VERSION
  tls_context.max_version = if defined?(::OpenSSL::SSL::TLS1_3_VERSION)
    ::OpenSSL::SSL::TLS1_3_VERSION
  else
    ::OpenSSL::SSL::TLS1_2_VERSION
  end
  tls_context.cert = ::OpenSSL::X509::Certificate.new(::File.read(tls_client_cert)) if tls_client_cert
  # PKey.read handles any key type (RSA, EC, Ed25519...); the previous
  # PKey::RSA.new rejected non-RSA client keys.
  tls_context.key = ::OpenSSL::PKey.read(::File.read(tls_client_key)) if tls_client_key

  # Verify the NATS server's certificate chain. This context is handed to
  # nats-pure as :tls => {:context => ...}; nats-pure uses a supplied
  # context verbatim and does NOT call #set_params, so verification has to
  # be configured here. Without this the OpenSSL default (VERIFY_NONE)
  # stood and any certificate -- including an attacker's -- was accepted.
  tls_context.verify_mode = ::OpenSSL::SSL::VERIFY_PEER
  cert_store = ::OpenSSL::X509::Store.new
  if tls_ca_cert
    # Trust the configured CA bundle (the private-CA deployment case).
    cert_store.add_file(tls_ca_cert)
  else
    # No CA configured: fall back to the system trust store.
    cert_store.set_default_paths
  end
  tls_context.cert_store = cert_store

  # NOTE: hostname (SAN/CN) verification is NOT enabled here. nats-pure only
  # sets the SSLSocket hostname from @tls[:hostname], which it populates
  # itself only when it builds the context; for a supplied context it stays
  # nil, and a single static hostname would be wrong for a multi-server
  # cluster that reconnects across hosts. Chain verification above still
  # ensures the cert is signed by the trusted CA. Plumbing per-connection
  # hostname verification is tracked separately.
  tls_context
end

#resolved_connection_nameObject

Precedence: PB_NATS_CONNECTION_NAME env var > yaml/DEFAULT connection_name

hostname. Env wins so ops can set a per-pod/per-host name without a config file; the hostname fallback ensures the name is never blank.



115
116
117
# File 'lib/protobuf/nats/config.rb', line 115

def resolved_connection_name
  ::ENV["PB_NATS_CONNECTION_NAME"] || connection_name || ::Socket.gethostname
end