anycable-core 1.4.0.rc.3 → 1.4.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 737d6a61d9703667767212bb560fe63974749f689b35d214ac7a83e19b22b3cf
4
- data.tar.gz: 10c05cf944bf608b8bdbb408a116688d29122511964502c0e88ab44256cdbaca
3
+ metadata.gz: fead71a088b7431fb65a3cfdd16ebee3760700496e9cc0d440f40b118952ac6a
4
+ data.tar.gz: 26c7f7d1fd4fa3aefbf8e43358b8eb959e3aa707ec36d2d7775f657748e1afc5
5
5
  SHA512:
6
- metadata.gz: ffcc587a711bec5bef6d7fb677b3e20d85365c19ae7ed9c2ad081cfce216c68c648e6792d43749708330a5cb69f8a1edea6466ddd5fad5b05fcf51686f922ed9
7
- data.tar.gz: 196436a8cece7e55a1dd7e44292ff9b3bef50dd06d8cb1d1e96dd9eab383bd4ee74a078d76687852e3136fc466aa46ae4edf92dbd93fabe045ae83caa7d2054b
6
+ metadata.gz: 887068bded37ce33ebdb0a5ae2ffce3afbb545053a544db83ad581008ac0ec603a45019df8a87281030b790db90f2aae3c812b3434b3c782ac69f29d04c535bd
7
+ data.tar.gz: c2e49c2fc7becb3c9b9076fd57f988abec053bd419e56dd0769d4e086704a66fd80dab0ffa485a9221053b9742a55de38c90b1463d1d5264b385c7b2f132d33d
data/CHANGELOG.md CHANGED
@@ -2,16 +2,14 @@
2
2
 
3
3
  ## master
4
4
 
5
- ## 1.4.0.rc.3 (2023-07-04)
5
+ ## 1.4.1 (2023-07-24)
6
6
 
7
- - Fix HTTP RPC protobuf-to-json encoding. ([@palkan][])
7
+ - Add TLS support for gRPC server. ([@Envek][])
8
8
 
9
- ## 1.4.0.rc.2 (2023-07-03)
9
+ ## 1.4.0 (2023-07-07)
10
10
 
11
11
  - Add HTTP RPC support. ([@palkan][])
12
12
 
13
- ## 1.4.0.rc.1 (2023-06-01)
14
-
15
13
  - Add `redisx` broadcast adapter. ([@palkan][])
16
14
 
17
15
  A next-gen (Broker-compatible) broadcasting adapter using Redis Streams.
@@ -57,6 +57,7 @@ module AnyCable
57
57
 
58
58
  def raw_broadcast(payload)
59
59
  ensure_thread_is_alive
60
+ AnyCable.logger.info "Enqueue: #{payload}"
60
61
  queue << payload
61
62
  end
62
63
 
@@ -95,6 +96,7 @@ module AnyCable
95
96
  build_http do |http|
96
97
  req = Net::HTTP::Post.new(url, {"Content-Type" => "application/json"}.merge(headers))
97
98
  req.body = payload
99
+ AnyCable.logger.info "Perform: #{payload}"
98
100
  http.request(req)
99
101
  end
100
102
  end
@@ -98,7 +98,7 @@ module AnyCable
98
98
  --http-health-path=path Endpoint to serve health checks, default: "/health"
99
99
 
100
100
  REDIS PUB/SUB
101
- --redis-url=url Redis URL for pub/sub, default: REDIS_URL or "redis://localhost:6379/5"
101
+ --redis-url=url Redis URL for pub/sub, default: REDIS_URL or "redis://localhost:6379"
102
102
  --redis-channel=name Redis channel for broadcasting, default: "__anycable__"
103
103
  --redis-sentinels=<...hosts> Redis Sentinel followers addresses (as a comma-separated list), default: nil
104
104
  --redis-tls-verify=yes|no Whether to perform server certificate check in case of rediss:// protocol. Default: yes
@@ -3,6 +3,8 @@
3
3
  AnyCable::Config.attr_config(
4
4
  ### gRPC options
5
5
  rpc_host: "127.0.0.1:50051",
6
+ rpc_tls_cert: nil,
7
+ rpc_tls_key: nil,
6
8
  rpc_pool_size: 30,
7
9
  rpc_max_waiting_requests: 20,
8
10
  rpc_poll_period: 1,
@@ -32,6 +34,7 @@ module AnyCable
32
34
  max_waiting_requests: rpc_max_waiting_requests,
33
35
  poll_period: rpc_poll_period,
34
36
  pool_keep_alive: rpc_pool_keep_alive,
37
+ tls_credentials: tls_credentials,
35
38
  server_args: enhance_grpc_server_args(normalized_grpc_server_args)
36
39
  }
37
40
  end
@@ -53,6 +56,17 @@ module AnyCable
53
56
  opts["grpc.max_connection_age_ms"] = rpc_max_connection_age.to_i * 1000
54
57
  opts
55
58
  end
59
+
60
+ def tls_credentials
61
+ cert_path_or_content = rpc_tls_cert # Assign to local variable to make steep happy
62
+ key_path_or_content = rpc_tls_key # Assign to local variable to make steep happy
63
+ return {} if cert_path_or_content.nil? || key_path_or_content.nil?
64
+
65
+ cert = File.exist?(cert_path_or_content) ? File.read(cert_path_or_content) : cert_path_or_content
66
+ pkey = File.exist?(key_path_or_content) ? File.read(key_path_or_content) : key_path_or_content
67
+
68
+ {cert: cert, pkey: pkey}
69
+ end
56
70
  end
57
71
  end
58
72
  end
@@ -62,6 +76,8 @@ AnyCable::Config.prepend AnyCable::GRPC::Config
62
76
  AnyCable::Config.usage <<~TXT
63
77
  GRPC OPTIONS
64
78
  --rpc-host=host Local address to run gRPC server on, default: "127.0.0.1:50051"
79
+ --rpc-tls-cert=path TLS certificate file path or contents in PEM format, default: <none> (TLS disabled)
80
+ --rpc-tls-key=path TLS private key file path or contents in PEM format, default: <none> (TLS disabled)
65
81
  --rpc-pool-size=size gRPC workers pool size, default: 30
66
82
  --rpc-max-waiting-requests=num Max waiting requests queue size, default: 20
67
83
  --rpc-poll-period=seconds Poll period (sec), default: 1
@@ -83,8 +83,9 @@ module AnyCable
83
83
  end
84
84
 
85
85
  def build_server(**options)
86
+ tls_credentials = options.delete(:tls_credentials)
86
87
  ::GRPC::RpcServer.new(**options).tap do |server|
87
- server.add_http2_port(host, :this_port_is_insecure)
88
+ server.add_http2_port(host, server_credentials(**tls_credentials))
88
89
  server.handle(AnyCable::GRPC::Handler)
89
90
  server.handle(build_health_checker)
90
91
  end
@@ -102,6 +103,12 @@ module AnyCable
102
103
  )
103
104
  health_checker
104
105
  end
106
+
107
+ def server_credentials(cert: nil, pkey: nil)
108
+ return :this_port_is_insecure if cert.nil? || pkey.nil?
109
+
110
+ ::GRPC::Core::ServerCredentials.new(nil, [{private_key: pkey, cert_chain: cert}], false)
111
+ end
105
112
  end
106
113
  end
107
114
  end
@@ -44,6 +44,7 @@ module AnyCable
44
44
  @hostname = host_parts[:hostname]
45
45
  @port = host_parts[:port].to_i
46
46
 
47
+ @tls_credentials = options.delete(:tls_credentials)
47
48
  @grpc_server = build_server(**options)
48
49
  end
49
50
 
@@ -56,7 +57,7 @@ module AnyCable
56
57
 
57
58
  logger.info "RPC server (grpc_kit) is starting..."
58
59
 
59
- @sock = TCPServer.new(hostname, port)
60
+ @sock = build_server_socket
60
61
 
61
62
  server = grpc_server
62
63
 
@@ -66,6 +67,12 @@ module AnyCable
66
67
  server.run(conn)
67
68
  rescue IOError
68
69
  # ignore broken connections
70
+ rescue OpenSSL::SSL::SSLError => ssl_error
71
+ if ssl_error.message.match?(/SSL_read: unexpected eof while reading/i)
72
+ # ignore broken connections
73
+ else
74
+ raise
75
+ end
69
76
  end
70
77
  end
71
78
 
@@ -81,13 +88,18 @@ module AnyCable
81
88
 
82
89
  loop do
83
90
  sock = TCPSocket.new(hostname, port, connect_timeout: 1)
91
+ if @tls_credentials&.any?
92
+ sock = OpenSSL::SSL::SSLSocket.new(sock, tls_context)
93
+ sock.sync_close = true
94
+ sock.connect
95
+ end
84
96
  stub = ::Grpc::Health::V1::Health::Stub.new(sock)
85
97
  stub.check(::Grpc::Health::V1::HealthCheckRequest.new)
86
98
  sock.close
87
99
  break
88
- rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, SocketError
100
+ rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, SocketError, OpenSSL::SSL::SSLError => e
89
101
  timeout -= 1
90
- raise "Server is not responding" if timeout.zero?
102
+ raise "Server is not responding: #{e}" if timeout.zero?
91
103
  end
92
104
  end
93
105
 
@@ -125,6 +137,20 @@ module AnyCable
125
137
  @logger ||= AnyCable.logger
126
138
  end
127
139
 
140
+ def build_server_socket
141
+ tcp_server = TCPServer.new(hostname, port)
142
+ return tcp_server unless @tls_credentials&.any?
143
+
144
+ OpenSSL::SSL::SSLServer.new(tcp_server, tls_context)
145
+ end
146
+
147
+ def tls_context
148
+ OpenSSL::SSL::SSLContext.new.tap do |tls_context|
149
+ tls_context.cert = OpenSSL::X509::Certificate.new(@tls_credentials.fetch(:cert))
150
+ tls_context.key = OpenSSL::PKey.read(@tls_credentials.fetch(:pkey))
151
+ end
152
+ end
153
+
128
154
  def build_server(**options)
129
155
  pool_size = options[:pool_size]
130
156
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AnyCable
4
- VERSION = "1.4.0.rc.3"
4
+ VERSION = "1.4.1"
5
5
  end
@@ -3,6 +3,10 @@ module AnyCable
3
3
  interface _Config
4
4
  def rpc_host: () -> String
5
5
  def rpc_host=: (String) -> void
6
+ def rpc_tls_cert: () -> String?
7
+ def rpc_tls_cert=: (String?) -> void
8
+ def rpc_tls_key: () -> String?
9
+ def rpc_tls_key=: (String?) -> void
6
10
  def rpc_pool_size: () -> Integer
7
11
  def rpc_pool_size=: (Integer) -> void
8
12
  def rpc_max_waiting_requests: () -> Integer
@@ -28,11 +32,13 @@ module AnyCable
28
32
  max_waiting_requests: Integer,
29
33
  poll_period: Numeric,
30
34
  pool_keep_alive: Numeric,
35
+ tls_credentials: Hash[Symbol, String],
31
36
  server_args: Hash[String, untyped]
32
37
  }
33
38
 
34
39
  def normalized_grpc_server_args: () -> Hash[String, untyped]
35
40
  def enhance_grpc_server_args: (Hash[String, untyped]) -> Hash[String, untyped]
41
+ def tls_credentials: () -> Hash[Symbol, String]
36
42
  end
37
43
  end
38
44
  end
@@ -15,6 +15,7 @@ module AnyCable
15
15
  def logger: () -> Logger
16
16
  def build_server: (**untyped options) -> untyped
17
17
  def build_health_checker: () -> untyped
18
+ def server_credentials: (?cert: String?, ?pkey: String?) -> (::GRPC::Core::ServerCredentials | :this_port_is_insecure)
18
19
  end
19
20
  end
20
21
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: anycable-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0.rc.3
4
+ version: 1.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - palkan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-07-05 00:00:00.000000000 Z
11
+ date: 2023-07-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: anyway_config
@@ -282,9 +282,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
282
282
  version: 2.7.0
283
283
  required_rubygems_version: !ruby/object:Gem::Requirement
284
284
  requirements:
285
- - - ">"
285
+ - - ">="
286
286
  - !ruby/object:Gem::Version
287
- version: 1.3.1
287
+ version: '0'
288
288
  requirements: []
289
289
  rubygems_version: 3.4.8
290
290
  signing_key: