nonnative 3.17.0 → 3.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.circleci/config.yml +2 -2
- data/.claude/skills +1 -0
- data/AGENTS.md +12 -1
- data/CLAUDE.md +4 -0
- data/Gemfile.lock +34 -9
- data/Makefile +1 -0
- data/README.md +34 -0
- data/lib/nonnative/cucumber.rb +1 -0
- data/lib/nonnative/ed25519_key.rb +30 -0
- data/lib/nonnative/fault_injection_proxy.rb +11 -0
- data/lib/nonnative/jwt_token.rb +46 -0
- data/lib/nonnative/paseto_token.rb +61 -0
- data/lib/nonnative/reset_peer_socket_pair.rb +29 -0
- data/lib/nonnative/socket_pair_factory.rb +5 -1
- data/lib/nonnative/ssh_token.rb +58 -0
- data/lib/nonnative/token.rb +64 -0
- data/lib/nonnative/version.rb +1 -1
- data/lib/nonnative.rb +32 -0
- data/nonnative.gemspec +6 -0
- metadata +129 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 011e40916b32666c33c2a0374ee97c3e96d563cf950f5c68e12b8afa7c18e986
|
|
4
|
+
data.tar.gz: 7b21a33ad8fc14077751e679b66a14523c79c0783004934f55fa61ea5608e477
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0e24fea055ed0e972507bfcb8ebbdf2c604d41e1a6908333a1863f1ba44dd356ea518936d3d6076f73fb37d0c3a9f593b6321ef1392d51d6d1cdf4c145b5ad6d
|
|
7
|
+
data.tar.gz: ec70302587120b093bb506d5d324821e880b10e406974822f44e1efa7c3fdee29465847eb8f71466ab3806ddd0eb328f2e0788bca28196066c6b3636ce0edb83
|
data/.circleci/config.yml
CHANGED
|
@@ -14,10 +14,10 @@ commands:
|
|
|
14
14
|
executors:
|
|
15
15
|
ruby:
|
|
16
16
|
docker:
|
|
17
|
-
- image: alexfalkowski/ruby:3.
|
|
17
|
+
- image: alexfalkowski/ruby:3.20
|
|
18
18
|
release:
|
|
19
19
|
docker:
|
|
20
|
-
- image: alexfalkowski/release:8.
|
|
20
|
+
- image: alexfalkowski/release:8.22
|
|
21
21
|
alpine:
|
|
22
22
|
docker:
|
|
23
23
|
- image: alpine:latest
|
data/.claude/skills
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
../bin/skills
|
data/AGENTS.md
CHANGED
|
@@ -111,7 +111,7 @@ of dependencies.
|
|
|
111
111
|
Public entry point: `lib/nonnative.rb`.
|
|
112
112
|
|
|
113
113
|
Main API: `configure`, `start`, `stop`, `clear`, `reset`, `pool`,
|
|
114
|
-
`go_argv`, `go_command`.
|
|
114
|
+
`go_argv`, `go_command`, `token`.
|
|
115
115
|
|
|
116
116
|
Configuration is `Nonnative::Configuration`, built with
|
|
117
117
|
`config.process`, `config.server`, `config.service`, or
|
|
@@ -127,6 +127,16 @@ Runners:
|
|
|
127
127
|
reverse. Readiness and shutdown checks are TCP-only via
|
|
128
128
|
`Nonnative::Port#open?` and `#closed?`.
|
|
129
129
|
|
|
130
|
+
Token generation: `Nonnative.token(kind:, issuer:, key:, private_key:, expiration:)`
|
|
131
|
+
returns a `Nonnative::Token` whose `generate(aud:, sub:)` produces a signed token for
|
|
132
|
+
authenticating against services under test; it feeds `Nonnative::Header.auth_bearer`.
|
|
133
|
+
Kinds are `jwt` (EdDSA, `kid` header), `paseto` (v4.public, `kid` footer), and `ssh`
|
|
134
|
+
(go-service style raw-Ed25519 `base64(claims).base64(signature)`). All Ed25519 and
|
|
135
|
+
generation-only. `jwt`/`paseto` take a PKCS#8 PEM key; `ssh` takes an OpenSSH-format
|
|
136
|
+
key. PASETO needs system libsodium (via `rbnacl`), required lazily so `require
|
|
137
|
+
'nonnative'` works without it until a PASETO token is generated.
|
|
138
|
+
`Nonnative::Token.http_audience` / `grpc_audience` build the endpoint-scoped `aud`.
|
|
139
|
+
|
|
130
140
|
## Cucumber Surface
|
|
131
141
|
|
|
132
142
|
`lib/nonnative/cucumber.rb` is public compatibility surface. Do not remove or
|
|
@@ -192,6 +202,7 @@ Limitations:
|
|
|
192
202
|
- Readiness/timeouts: `lib/nonnative/port.rb`, `lib/nonnative/timeout.rb`
|
|
193
203
|
- Process lifecycle: `lib/nonnative/process.rb`
|
|
194
204
|
- Go executable command/argv building: `lib/nonnative/go_executable.rb`
|
|
205
|
+
- Token generation: `lib/nonnative/token.rb`, `lib/nonnative/jwt_token.rb`, `lib/nonnative/paseto_token.rb`, `lib/nonnative/ssh_token.rb`, `lib/nonnative/ed25519_key.rb`
|
|
195
206
|
- Proxies: `lib/nonnative/fault_injection_proxy.rb`, `lib/nonnative/socket_pair_factory.rb`
|
|
196
207
|
- Cucumber: `lib/nonnative/cucumber.rb`, `lib/nonnative/startup.rb`, `features/support/env.rb`
|
|
197
208
|
- Config loading: `lib/nonnative/configuration.rb`, `lib/nonnative/configuration_file.rb`, `lib/nonnative/configuration_runner.rb`, `lib/nonnative/configuration_proxy.rb`
|
data/CLAUDE.md
ADDED
data/Gemfile.lock
CHANGED
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
nonnative (3.
|
|
4
|
+
nonnative (3.19.0)
|
|
5
5
|
concurrent-ruby (>= 1, < 2)
|
|
6
6
|
config (>= 5, < 6)
|
|
7
7
|
cucumber (>= 7, < 12)
|
|
8
8
|
cucumber-cucumber-expressions (< 19)
|
|
9
|
+
ed25519 (>= 1, < 2)
|
|
9
10
|
get_process_mem (>= 1, < 2)
|
|
10
11
|
grpc (>= 1, < 2)
|
|
12
|
+
jwt (>= 3, < 4)
|
|
13
|
+
jwt-eddsa (>= 0, < 1)
|
|
11
14
|
puma (>= 7, < 8)
|
|
15
|
+
rbnacl (>= 7, < 8)
|
|
12
16
|
rest-client (>= 2, < 3)
|
|
13
17
|
retriable (>= 3, < 4)
|
|
14
18
|
rspec-benchmark (>= 0, < 1)
|
|
15
19
|
rspec-expectations (>= 3, < 4)
|
|
16
20
|
rspec-wait (>= 1, < 2)
|
|
21
|
+
ruby-paseto (>= 0, < 1)
|
|
17
22
|
sinatra (>= 4, < 5)
|
|
23
|
+
ssh_data (>= 1, < 2)
|
|
18
24
|
|
|
19
25
|
GEM
|
|
20
26
|
remote: https://rubygems.org/
|
|
@@ -30,7 +36,7 @@ GEM
|
|
|
30
36
|
config (5.6.1)
|
|
31
37
|
deep_merge (~> 1.2, >= 1.2.1)
|
|
32
38
|
ostruct
|
|
33
|
-
cucumber (11.1.
|
|
39
|
+
cucumber (11.1.1)
|
|
34
40
|
base64 (~> 0.2)
|
|
35
41
|
builder (~> 3.2)
|
|
36
42
|
cucumber-ci-environment (> 9, < 12)
|
|
@@ -59,6 +65,7 @@ GEM
|
|
|
59
65
|
diff-lcs (1.6.2)
|
|
60
66
|
docile (1.4.1)
|
|
61
67
|
domain_name (0.6.20240107)
|
|
68
|
+
ed25519 (1.4.0)
|
|
62
69
|
ffi (1.17.4-x86_64-darwin)
|
|
63
70
|
ffi (1.17.4-x86_64-linux-gnu)
|
|
64
71
|
get_process_mem (1.0.0)
|
|
@@ -72,29 +79,37 @@ GEM
|
|
|
72
79
|
rake (~> 13.3)
|
|
73
80
|
googleapis-common-protos-types (1.23.0)
|
|
74
81
|
google-protobuf (~> 4.26)
|
|
75
|
-
grpc (1.
|
|
82
|
+
grpc (1.82.0-x86_64-darwin)
|
|
76
83
|
google-protobuf (>= 3.25, < 5.0)
|
|
77
84
|
googleapis-common-protos-types (~> 1.0)
|
|
78
|
-
grpc (1.
|
|
85
|
+
grpc (1.82.0-x86_64-linux-gnu)
|
|
79
86
|
google-protobuf (>= 3.25, < 5.0)
|
|
80
87
|
googleapis-common-protos-types (~> 1.0)
|
|
81
88
|
http-accept (1.7.0)
|
|
82
89
|
http-cookie (1.1.6)
|
|
83
90
|
domain_name (~> 0.5)
|
|
84
91
|
json (2.20.0)
|
|
85
|
-
|
|
92
|
+
jwt (3.2.0)
|
|
93
|
+
base64
|
|
94
|
+
jwt-eddsa (0.9.0)
|
|
95
|
+
base64
|
|
96
|
+
ed25519
|
|
97
|
+
jwt (>= 2.9.0)
|
|
98
|
+
language_server-protocol (3.17.0.6)
|
|
86
99
|
lint_roller (1.1.0)
|
|
87
100
|
logger (1.7.0)
|
|
88
101
|
memoist3 (1.0.0)
|
|
89
102
|
mime-types (3.7.0)
|
|
90
103
|
logger
|
|
91
104
|
mime-types-data (~> 3.2025, >= 3.2025.0507)
|
|
92
|
-
mime-types-data (3.2026.
|
|
105
|
+
mime-types-data (3.2026.0701)
|
|
93
106
|
mini_mime (1.1.5)
|
|
107
|
+
multi_json (1.15.0)
|
|
94
108
|
multi_test (1.1.0)
|
|
95
109
|
mustermann (3.1.1)
|
|
96
110
|
netrc (0.11.0)
|
|
97
111
|
nio4r (2.7.5)
|
|
112
|
+
openssl (3.3.3)
|
|
98
113
|
ostruct (0.6.3)
|
|
99
114
|
parallel (2.1.0)
|
|
100
115
|
parser (3.3.11.1)
|
|
@@ -114,6 +129,8 @@ GEM
|
|
|
114
129
|
rack (>= 3.0.0)
|
|
115
130
|
rainbow (3.1.1)
|
|
116
131
|
rake (13.4.2)
|
|
132
|
+
rbnacl (7.1.2)
|
|
133
|
+
ffi (~> 1)
|
|
117
134
|
rbs (4.0.3)
|
|
118
135
|
logger
|
|
119
136
|
prism (>= 1.6.0)
|
|
@@ -146,7 +163,7 @@ GEM
|
|
|
146
163
|
rspec-support (3.13.7)
|
|
147
164
|
rspec-wait (1.0.2)
|
|
148
165
|
rspec (>= 3.4)
|
|
149
|
-
rubocop (1.88.
|
|
166
|
+
rubocop (1.88.1)
|
|
150
167
|
json (~> 2.3)
|
|
151
168
|
language_server-protocol (~> 3.17.0.2)
|
|
152
169
|
lint_roller (~> 1.1.0)
|
|
@@ -157,13 +174,18 @@ GEM
|
|
|
157
174
|
rubocop-ast (>= 1.49.0, < 2.0)
|
|
158
175
|
ruby-progressbar (~> 1.7)
|
|
159
176
|
unicode-display_width (>= 2.4.0, < 4.0)
|
|
160
|
-
rubocop-ast (1.
|
|
177
|
+
rubocop-ast (1.50.0)
|
|
161
178
|
parser (>= 3.3.7.2)
|
|
162
179
|
prism (~> 1.7)
|
|
163
180
|
ruby-lsp (0.26.9)
|
|
164
181
|
language_server-protocol (~> 3.17.0)
|
|
165
182
|
prism (>= 1.2, < 2.0)
|
|
166
183
|
rbs (>= 3, < 5)
|
|
184
|
+
ruby-paseto (0.2.0)
|
|
185
|
+
multi_json (~> 1.15.0)
|
|
186
|
+
openssl (~> 3.3)
|
|
187
|
+
sorbet-runtime
|
|
188
|
+
zeitwerk
|
|
167
189
|
ruby-progressbar (1.13.0)
|
|
168
190
|
simplecov (0.22.0)
|
|
169
191
|
docile (~> 1.1)
|
|
@@ -181,14 +203,17 @@ GEM
|
|
|
181
203
|
rack-protection (= 4.2.1)
|
|
182
204
|
rack-session (>= 2.0.0, < 3)
|
|
183
205
|
tilt (~> 2.0)
|
|
206
|
+
sorbet-runtime (0.6.13323)
|
|
207
|
+
ssh_data (1.3.0)
|
|
184
208
|
sys-uname (1.5.1)
|
|
185
209
|
ffi (~> 1.1)
|
|
186
210
|
memoist3 (~> 1.0.0)
|
|
187
|
-
tilt (2.
|
|
211
|
+
tilt (2.8.0)
|
|
188
212
|
tsort (0.2.0)
|
|
189
213
|
unicode-display_width (3.2.0)
|
|
190
214
|
unicode-emoji (~> 4.1)
|
|
191
215
|
unicode-emoji (4.2.0)
|
|
216
|
+
zeitwerk (2.8.2)
|
|
192
217
|
|
|
193
218
|
PLATFORMS
|
|
194
219
|
x86_64-darwin-21
|
data/Makefile
CHANGED
data/README.md
CHANGED
|
@@ -180,6 +180,37 @@ health = Nonnative.grpc_health(
|
|
|
180
180
|
expect(health.serving?).to eq(true)
|
|
181
181
|
```
|
|
182
182
|
|
|
183
|
+
### 🔑 Tokens
|
|
184
|
+
|
|
185
|
+
`Nonnative.token` builds a signer for authenticating requests against a service under test. You pass the signing parameters directly (parsed from your own configuration); it is not coupled to any service's config format. The generated string is ready for `Nonnative::Header.auth_bearer`.
|
|
186
|
+
|
|
187
|
+
```ruby
|
|
188
|
+
token = Nonnative.token(
|
|
189
|
+
kind: 'jwt',
|
|
190
|
+
issuer: 'iss',
|
|
191
|
+
key: 'key-1',
|
|
192
|
+
private_key: 'config/ed25519.pem',
|
|
193
|
+
expiration: 3600
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
headers = Nonnative::Header.auth_bearer(
|
|
197
|
+
token.generate(aud: Nonnative::Token.http_audience('GET', '/v1/things'), sub: 'user-1')
|
|
198
|
+
)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Supported `kind` values (all Ed25519, generation only):
|
|
202
|
+
|
|
203
|
+
- `jwt`: EdDSA JWT with the key id in the `kid` header. `private_key` is a PKCS#8 PEM file.
|
|
204
|
+
- `paseto`: PASETO v4.public with the key id in a `{"kid":"..."}` footer. `private_key` is a PKCS#8 PEM file. Requires system **libsodium** (via `rbnacl`); it loads lazily, so `require 'nonnative'` works without libsodium until you generate a PASETO token.
|
|
205
|
+
- `ssh`: go-service style `base64(claims).base64(signature)` with a raw Ed25519 signature over `v1` claims. `private_key` is an **OpenSSH-format** key. `issuer` and `sub` are ignored (the subject is the key id).
|
|
206
|
+
|
|
207
|
+
The audience is endpoint-scoped; build it with the helpers:
|
|
208
|
+
|
|
209
|
+
```ruby
|
|
210
|
+
Nonnative::Token.http_audience('GET', '/v1/things') # => "GET /v1/things"
|
|
211
|
+
Nonnative::Token.grpc_audience('/health.v1.Health/Check') # => "/health.v1.Health/Check"
|
|
212
|
+
```
|
|
213
|
+
|
|
183
214
|
### 🔁 Lifecycle strategies (Cucumber integration)
|
|
184
215
|
|
|
185
216
|
Nonnative ships Cucumber hooks (when loaded) that support these tags/strategies:
|
|
@@ -757,6 +788,7 @@ The `fault_injection` proxy allows you to simulate failures by injecting them. W
|
|
|
757
788
|
Clients connect to the service `host`/`port`, while the proxy forwards traffic to nested `proxy.host`/`proxy.port`.
|
|
758
789
|
|
|
759
790
|
- `close_all` - Closes the socket as soon as it connects.
|
|
791
|
+
- `reset_peer` - Resets the socket as soon as it connects, so clients observe a TCP reset (`Errno::ECONNRESET`) rather than the graceful close performed by `close_all`.
|
|
760
792
|
- `delay` - Delays traffic on the connection. Defaults to 2 seconds and can be configured through options.
|
|
761
793
|
- `timeout` - Accepts the connection and stalls traffic until reset or stop closes the connection, so clients exercise their own read timeout behavior.
|
|
762
794
|
- `invalid_data` - Forwards client requests unchanged, then corrupts upstream responses before they reach the client.
|
|
@@ -770,6 +802,7 @@ name = 'name of service in configuration'
|
|
|
770
802
|
service = Nonnative.pool.service_by_name(name)
|
|
771
803
|
|
|
772
804
|
service.proxy.close_all # To use close_all.
|
|
805
|
+
service.proxy.reset_peer # To reset (RST) client connections.
|
|
773
806
|
service.proxy.timeout # To stall traffic until reset or stop.
|
|
774
807
|
service.proxy.reset # To reset it back to a good state.
|
|
775
808
|
```
|
|
@@ -778,6 +811,7 @@ Use the Cucumber proxy steps:
|
|
|
778
811
|
|
|
779
812
|
```cucumber
|
|
780
813
|
Given I set the proxy for service 'service_1' to 'close_all'
|
|
814
|
+
Given I set the proxy for service 'service_1' to 'reset_peer'
|
|
781
815
|
Given I set the proxy for service 'service_1' to 'timeout'
|
|
782
816
|
Then I should reset the proxy for service 'service_1'
|
|
783
817
|
```
|
data/lib/nonnative/cucumber.rb
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nonnative
|
|
4
|
+
# Loads an Ed25519 private key from a PEM file for signing tokens.
|
|
5
|
+
#
|
|
6
|
+
# Verifiers such as go-service use Ed25519 keys encoded as PKCS#8 PEM. This reads the PEM once and
|
|
7
|
+
# exposes it in the shapes the token backends need: the raw PEM for {Nonnative::PasetoToken} and the
|
|
8
|
+
# 32-byte seed for {Nonnative::JwtToken}.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# key = Nonnative::Ed25519Key.new('config/ed25519.pem')
|
|
12
|
+
# key.pem # => "-----BEGIN PRIVATE KEY-----\n..."
|
|
13
|
+
# key.seed # => 32-byte binary String
|
|
14
|
+
class Ed25519Key
|
|
15
|
+
# @param path [String] path to a PKCS#8 Ed25519 private key PEM file
|
|
16
|
+
def initialize(path)
|
|
17
|
+
@pem = File.read(path)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @return [String] the PEM contents (PKCS#8 Ed25519 private key)
|
|
21
|
+
attr_reader :pem
|
|
22
|
+
|
|
23
|
+
# Extracts the raw 32-byte Ed25519 seed from the PEM.
|
|
24
|
+
#
|
|
25
|
+
# @return [String] the raw 32-byte Ed25519 seed
|
|
26
|
+
def seed
|
|
27
|
+
OpenSSL::PKey.read(@pem).raw_private_key
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -10,6 +10,7 @@ module Nonnative
|
|
|
10
10
|
# This class exposes a small public control surface for tests:
|
|
11
11
|
#
|
|
12
12
|
# - {#close_all}: close connections immediately on accept
|
|
13
|
+
# - {#reset_peer}: reset connections immediately on accept (TCP RST rather than a graceful close)
|
|
13
14
|
# - {#delay}: delay reads by a configured duration (default: 2 seconds)
|
|
14
15
|
# - {#timeout}: accept connections and keep them silent until clients time out
|
|
15
16
|
# - {#invalid_data}: forward requests unchanged and mutate upstream responses before they reach clients
|
|
@@ -99,6 +100,16 @@ module Nonnative
|
|
|
99
100
|
apply_state :close_all
|
|
100
101
|
end
|
|
101
102
|
|
|
103
|
+
# Forces new connections to be reset immediately.
|
|
104
|
+
#
|
|
105
|
+
# Unlike {#close_all}, which closes the socket gracefully (FIN), this closes the accepted socket
|
|
106
|
+
# with a zero linger timeout so clients observe a TCP reset (`Errno::ECONNRESET`).
|
|
107
|
+
#
|
|
108
|
+
# @return [void]
|
|
109
|
+
def reset_peer
|
|
110
|
+
apply_state :reset_peer
|
|
111
|
+
end
|
|
112
|
+
|
|
102
113
|
# Delays reads before forwarding.
|
|
103
114
|
#
|
|
104
115
|
# The delay duration is controlled by `service.proxy.options[:delay]` and defaults to 2 seconds.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nonnative
|
|
4
|
+
# Generates Ed25519 (EdDSA) JWT tokens for authenticating against services under test.
|
|
5
|
+
#
|
|
6
|
+
# The key id is set in the JWT `kid` header, and the claims match the common
|
|
7
|
+
# iss/aud/sub/iat/nbf/exp/jti set expected by verifiers such as go-service.
|
|
8
|
+
#
|
|
9
|
+
# @example
|
|
10
|
+
# jwt = Nonnative::JwtToken.new(issuer: 'iss', key: 'key-1', private_key: 'config/ed25519.pem', expiration: 3600)
|
|
11
|
+
# jwt.generate(aud: 'GET /v1/things', sub: 'user-1')
|
|
12
|
+
#
|
|
13
|
+
# @see https://github.com/jwt/ruby-jwt-eddsa jwt-eddsa
|
|
14
|
+
class JwtToken
|
|
15
|
+
# @param issuer [String] the `iss` claim
|
|
16
|
+
# @param key [String] the key id set in the JWT `kid` header
|
|
17
|
+
# @param private_key [String] path to a PKCS#8 Ed25519 private key PEM file
|
|
18
|
+
# @param expiration [Integer] token lifetime in seconds (drives `exp`)
|
|
19
|
+
def initialize(issuer:, key:, private_key:, expiration:)
|
|
20
|
+
@issuer = issuer
|
|
21
|
+
@key = key
|
|
22
|
+
@ed25519 = Nonnative::Ed25519Key.new(private_key)
|
|
23
|
+
@expiration = expiration
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Generates a signed EdDSA JWT.
|
|
27
|
+
#
|
|
28
|
+
# @param aud [String] the `aud` claim (for example `"GET /v1/things"` or a gRPC full method)
|
|
29
|
+
# @param sub [String] the `sub` claim
|
|
30
|
+
# @return [String] the signed JWT
|
|
31
|
+
def generate(aud:, sub:)
|
|
32
|
+
now = Time.now.to_i
|
|
33
|
+
payload = {
|
|
34
|
+
iss: @issuer,
|
|
35
|
+
aud: aud,
|
|
36
|
+
sub: sub,
|
|
37
|
+
iat: now,
|
|
38
|
+
nbf: now,
|
|
39
|
+
exp: now + @expiration,
|
|
40
|
+
jti: SecureRandom.uuid
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
JWT.encode(payload, Ed25519::SigningKey.new(@ed25519.seed), 'EdDSA', { kid: @key })
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nonnative
|
|
4
|
+
# Generates Ed25519 PASETO v4.public tokens for authenticating against services under test.
|
|
5
|
+
#
|
|
6
|
+
# The key id is carried in a JSON footer (`{"kid":"..."}`), and the claims match the common
|
|
7
|
+
# iss/aud/sub/iat/nbf/exp/jti set expected by verifiers such as go-service. The signing library is
|
|
8
|
+
# required lazily so that requiring `nonnative` does not depend on `rbnacl`/libsodium being present.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# paseto = Nonnative::PasetoToken.new(issuer: 'iss', key: 'key-1', private_key: 'config/ed25519.pem', expiration: 3600)
|
|
12
|
+
# paseto.generate(aud: 'GET /v1/things', sub: 'user-1')
|
|
13
|
+
#
|
|
14
|
+
# @see https://github.com/bannable/paseto ruby-paseto
|
|
15
|
+
class PasetoToken
|
|
16
|
+
# @param issuer [String] the `iss` claim
|
|
17
|
+
# @param key [String] the key id carried in the `kid` footer
|
|
18
|
+
# @param private_key [String] path to a PKCS#8 Ed25519 private key PEM file
|
|
19
|
+
# @param expiration [Integer] token lifetime in seconds (drives `exp`)
|
|
20
|
+
def initialize(issuer:, key:, private_key:, expiration:)
|
|
21
|
+
@issuer = issuer
|
|
22
|
+
@key = key
|
|
23
|
+
@ed25519 = Nonnative::Ed25519Key.new(private_key)
|
|
24
|
+
@expiration = expiration
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Generates a signed PASETO v4.public token.
|
|
28
|
+
#
|
|
29
|
+
# @param aud [String] the `aud` claim (for example `"GET /v1/things"` or a gRPC full method)
|
|
30
|
+
# @param sub [String] the `sub` claim
|
|
31
|
+
# @return [String] the signed PASETO token
|
|
32
|
+
def generate(aud:, sub:)
|
|
33
|
+
load_dependencies!
|
|
34
|
+
|
|
35
|
+
now = Time.now.utc
|
|
36
|
+
claims = {
|
|
37
|
+
'iss' => @issuer,
|
|
38
|
+
'aud' => aud,
|
|
39
|
+
'sub' => sub,
|
|
40
|
+
'iat' => now.iso8601,
|
|
41
|
+
'nbf' => now.iso8601,
|
|
42
|
+
'exp' => (now + @expiration).iso8601,
|
|
43
|
+
'jti' => SecureRandom.uuid
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
Paseto::V4::Public.new(@ed25519.pem).encode!(claims, footer: { 'kid' => @key }.to_json)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
# Loads the PASETO signing libraries lazily and raises a clear error if they are unavailable.
|
|
52
|
+
# rbnacl loads system libsodium at runtime, which the gemspec cannot guarantee, so requiring it
|
|
53
|
+
# here keeps `require 'nonnative'` working without libsodium for consumers that do not use PASETO.
|
|
54
|
+
def load_dependencies!
|
|
55
|
+
require 'rbnacl'
|
|
56
|
+
require 'paseto'
|
|
57
|
+
rescue LoadError => e
|
|
58
|
+
raise Nonnative::Error, "PASETO token generation requires libsodium and the rbnacl and ruby-paseto gems (#{e.message})"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nonnative
|
|
4
|
+
# Socket-pair variant used by the fault-injection proxy to simulate an abrupt connection reset.
|
|
5
|
+
#
|
|
6
|
+
# When active, the proxy accepts a TCP connection and closes it with a zero linger timeout, so the
|
|
7
|
+
# kernel sends a TCP RST instead of a graceful FIN. Clients observe a reset ("connection reset by
|
|
8
|
+
# peer", `Errno::ECONNRESET`) rather than a clean end-of-stream, which is distinct from the graceful
|
|
9
|
+
# close performed by {Nonnative::CloseAllSocketPair}.
|
|
10
|
+
#
|
|
11
|
+
# This behavior is enabled by calling {Nonnative::FaultInjectionProxy#reset_peer}.
|
|
12
|
+
#
|
|
13
|
+
# @see Nonnative::FaultInjectionProxy
|
|
14
|
+
# @see Nonnative::SocketPairFactory
|
|
15
|
+
# @see Nonnative::SocketPair
|
|
16
|
+
# @see Nonnative::CloseAllSocketPair
|
|
17
|
+
class ResetPeerSocketPair < SocketPair
|
|
18
|
+
# Resets the accepted socket by closing it with a zero linger timeout, forcing a TCP RST.
|
|
19
|
+
#
|
|
20
|
+
# @param local_socket [TCPSocket] the accepted client socket
|
|
21
|
+
# @return [void]
|
|
22
|
+
def connect(local_socket)
|
|
23
|
+
Nonnative.logger.info "resetting socket '#{local_socket.inspect}' for 'reset_peer' pair"
|
|
24
|
+
|
|
25
|
+
local_socket.setsockopt(Socket::Option.linger(true, 0))
|
|
26
|
+
local_socket.close
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -9,6 +9,7 @@ module Nonnative
|
|
|
9
9
|
# Proxy states are mapped as follows:
|
|
10
10
|
# - `:none` (or any unknown value) -> {Nonnative::SocketPair} (pass-through)
|
|
11
11
|
# - `:close_all` -> {Nonnative::CloseAllSocketPair}
|
|
12
|
+
# - `:reset_peer` -> {Nonnative::ResetPeerSocketPair}
|
|
12
13
|
# - `:delay` -> {Nonnative::DelaySocketPair}
|
|
13
14
|
# - `:timeout` -> {Nonnative::TimeoutSocketPair}
|
|
14
15
|
# - `:invalid_data` -> {Nonnative::InvalidDataSocketPair}
|
|
@@ -16,6 +17,7 @@ module Nonnative
|
|
|
16
17
|
# @see Nonnative::FaultInjectionProxy
|
|
17
18
|
# @see Nonnative::SocketPair
|
|
18
19
|
# @see Nonnative::CloseAllSocketPair
|
|
20
|
+
# @see Nonnative::ResetPeerSocketPair
|
|
19
21
|
# @see Nonnative::DelaySocketPair
|
|
20
22
|
# @see Nonnative::TimeoutSocketPair
|
|
21
23
|
# @see Nonnative::InvalidDataSocketPair
|
|
@@ -23,13 +25,15 @@ module Nonnative
|
|
|
23
25
|
class << self
|
|
24
26
|
# Creates a socket-pair instance for the given proxy state.
|
|
25
27
|
#
|
|
26
|
-
# @param kind [Symbol] proxy state (e.g. `:none`, `:close_all`, `:delay`, `:timeout`, `:invalid_data`)
|
|
28
|
+
# @param kind [Symbol] proxy state (e.g. `:none`, `:close_all`, `:reset_peer`, `:delay`, `:timeout`, `:invalid_data`)
|
|
27
29
|
# @param proxy [Nonnative::ConfigurationProxy] proxy configuration (host/port/options)
|
|
28
30
|
# @return [Nonnative::SocketPair] a socket-pair implementation instance
|
|
29
31
|
def create(kind, proxy)
|
|
30
32
|
pair = case kind
|
|
31
33
|
when :close_all
|
|
32
34
|
CloseAllSocketPair
|
|
35
|
+
when :reset_peer
|
|
36
|
+
ResetPeerSocketPair
|
|
33
37
|
when :delay
|
|
34
38
|
DelaySocketPair
|
|
35
39
|
when :timeout
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nonnative
|
|
4
|
+
# Generates go-service style SSH tokens for authenticating against services under test.
|
|
5
|
+
#
|
|
6
|
+
# Despite the name this is not an SSH protocol signature: the key is loaded from an OpenSSH-format
|
|
7
|
+
# Ed25519 private key and used as a raw Ed25519 key, matching go-service's SSH token scheme. The token
|
|
8
|
+
# is `<base64(claims)>.<base64(signature)>`, where the signature is a raw Ed25519 signature over the
|
|
9
|
+
# claims JSON. Claims are ver/kid/sub/aud/iat/exp, with `sub == kid == key` and `iat`/`exp` in Unix
|
|
10
|
+
# nanoseconds. `issuer` and `sub` are accepted for a uniform interface with the other kinds but unused:
|
|
11
|
+
# SSH tokens have no issuer, and the subject is always the key id.
|
|
12
|
+
#
|
|
13
|
+
# @example
|
|
14
|
+
# ssh = Nonnative::SshToken.new(key: 'key-1', private_key: 'config/id_ed25519', expiration: 3600)
|
|
15
|
+
# ssh.generate(aud: 'GET /v1/things', sub: 'user-1')
|
|
16
|
+
#
|
|
17
|
+
# @see https://github.com/github/ssh_data ssh_data
|
|
18
|
+
class SshToken
|
|
19
|
+
# The go-service SSH token format version.
|
|
20
|
+
TOKEN_VERSION = 'v1'
|
|
21
|
+
|
|
22
|
+
# @param key [String] the key id; also used as the subject
|
|
23
|
+
# @param private_key [String] path to an OpenSSH-format Ed25519 private key file
|
|
24
|
+
# @param expiration [Integer] token lifetime in seconds (drives `exp`)
|
|
25
|
+
def initialize(key:, private_key:, expiration:, **)
|
|
26
|
+
@key = key
|
|
27
|
+
@private_key = private_key
|
|
28
|
+
@expiration = expiration
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Generates a signed SSH token.
|
|
32
|
+
#
|
|
33
|
+
# @param aud [String] the `aud` claim (for example `"GET /v1/things"` or a gRPC full method)
|
|
34
|
+
# @return [String] the token, `"<base64(claims)>.<base64(signature)>"`
|
|
35
|
+
def generate(aud:, **)
|
|
36
|
+
now = Time.now
|
|
37
|
+
issued_at = (now.to_i * 1_000_000_000) + now.nsec
|
|
38
|
+
claims = {
|
|
39
|
+
ver: TOKEN_VERSION,
|
|
40
|
+
kid: @key,
|
|
41
|
+
sub: @key,
|
|
42
|
+
aud: aud,
|
|
43
|
+
iat: issued_at,
|
|
44
|
+
exp: issued_at + (@expiration * 1_000_000_000)
|
|
45
|
+
}.to_json
|
|
46
|
+
|
|
47
|
+
signature = Ed25519::SigningKey.new(seed).sign(claims)
|
|
48
|
+
|
|
49
|
+
"#{Base64.strict_encode64(claims)}.#{Base64.strict_encode64(signature)}"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def seed
|
|
55
|
+
SSHData::PrivateKey.parse_openssh(File.read(@private_key)).first.sk[0, 32]
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nonnative
|
|
4
|
+
# Builds signed tokens (JWT, PASETO, or SSH) for authenticating against services under test.
|
|
5
|
+
#
|
|
6
|
+
# The consumer passes in the signing parameters they parsed from their own configuration; this class
|
|
7
|
+
# is not coupled to any particular service's configuration format. The generated token string is
|
|
8
|
+
# ready to pass to {Nonnative::Header.auth_bearer}.
|
|
9
|
+
#
|
|
10
|
+
# @example JWT
|
|
11
|
+
# token = Nonnative::Token.new(kind: 'jwt', issuer: 'iss', key: 'key-1',
|
|
12
|
+
# private_key: 'config/ed25519.pem', expiration: 3600)
|
|
13
|
+
# header = Nonnative::Header.auth_bearer(token.generate(aud: 'GET /v1/things', sub: 'user-1'))
|
|
14
|
+
#
|
|
15
|
+
# @example PASETO
|
|
16
|
+
# token = Nonnative::Token.new(kind: 'paseto', issuer: 'iss', key: 'key-1',
|
|
17
|
+
# private_key: 'config/ed25519.pem', expiration: 3600)
|
|
18
|
+
# token.generate(aud: Nonnative::Token.grpc_audience('/health.v1.Health/Check'), sub: 'user-1')
|
|
19
|
+
#
|
|
20
|
+
# @see Nonnative::Header.auth_bearer
|
|
21
|
+
class Token
|
|
22
|
+
# Supported token kinds mapped to their implementation.
|
|
23
|
+
KINDS = { 'jwt' => Nonnative::JwtToken, 'paseto' => Nonnative::PasetoToken, 'ssh' => Nonnative::SshToken }.freeze
|
|
24
|
+
|
|
25
|
+
class << self
|
|
26
|
+
# Builds the audience string for an HTTP endpoint.
|
|
27
|
+
#
|
|
28
|
+
# @param method [String] HTTP method (for example `"GET"`)
|
|
29
|
+
# @param path [String] request path (for example `"/v1/things"`)
|
|
30
|
+
# @return [String] the audience string (for example `"GET /v1/things"`)
|
|
31
|
+
def http_audience(method, path)
|
|
32
|
+
"#{method} #{path}"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Builds the audience string for a gRPC endpoint.
|
|
36
|
+
#
|
|
37
|
+
# @param full_method [String] the gRPC full method (for example `"/health.v1.Health/Check"`)
|
|
38
|
+
# @return [String] the audience string
|
|
39
|
+
def grpc_audience(full_method)
|
|
40
|
+
full_method
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @param kind [String] token kind, one of `"jwt"`, `"paseto"`, or `"ssh"`
|
|
45
|
+
# @param issuer [String] the `iss` claim (unused by the `ssh` kind)
|
|
46
|
+
# @param key [String] the key id (JWT `kid` header, PASETO `kid` footer, or SSH `kid` claim)
|
|
47
|
+
# @param private_key [String] path to the Ed25519 private key file (PKCS#8 PEM for `jwt`/`paseto`, OpenSSH format for `ssh`)
|
|
48
|
+
# @param expiration [Integer] token lifetime in seconds (drives `exp`)
|
|
49
|
+
# @raise [ArgumentError] if the kind is not supported
|
|
50
|
+
def initialize(kind:, issuer:, key:, private_key:, expiration:)
|
|
51
|
+
klass = KINDS.fetch(kind) { raise ArgumentError, "Unsupported token kind '#{kind}'" }
|
|
52
|
+
@token = klass.new(issuer: issuer, key: key, private_key: private_key, expiration: expiration)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Generates a signed token.
|
|
56
|
+
#
|
|
57
|
+
# @param aud [String] the `aud` claim
|
|
58
|
+
# @param sub [String] the `sub` claim
|
|
59
|
+
# @return [String] the signed token
|
|
60
|
+
def generate(aud:, sub:)
|
|
61
|
+
@token.generate(aud: aud, sub: sub)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
data/lib/nonnative/version.rb
CHANGED
data/lib/nonnative.rb
CHANGED
|
@@ -49,6 +49,10 @@ require 'open3'
|
|
|
49
49
|
require 'securerandom'
|
|
50
50
|
require 'shellwords'
|
|
51
51
|
require 'uri'
|
|
52
|
+
require 'openssl'
|
|
53
|
+
require 'json'
|
|
54
|
+
require 'time'
|
|
55
|
+
require 'singleton' # ruby-paseto depends on this stdlib, which Ruby no longer auto-loads
|
|
52
56
|
|
|
53
57
|
require 'grpc'
|
|
54
58
|
require 'grpc/health/v1/health_services_pb'
|
|
@@ -64,6 +68,11 @@ require 'rspec/wait'
|
|
|
64
68
|
require 'puma'
|
|
65
69
|
require 'puma/server'
|
|
66
70
|
|
|
71
|
+
# jwt-eddsa (with the ed25519 gem) and ssh_data are pure Ruby and need no system library, so they load
|
|
72
|
+
# here. PASETO's rbnacl needs system libsodium, so Nonnative::PasetoToken requires it lazily instead.
|
|
73
|
+
require 'jwt/eddsa'
|
|
74
|
+
require 'ssh_data'
|
|
75
|
+
|
|
67
76
|
require 'nonnative/version'
|
|
68
77
|
require 'nonnative/error'
|
|
69
78
|
require 'nonnative/start_error'
|
|
@@ -102,6 +111,7 @@ require 'nonnative/no_proxy'
|
|
|
102
111
|
require 'nonnative/fault_injection_proxy'
|
|
103
112
|
require 'nonnative/socket_pair'
|
|
104
113
|
require 'nonnative/close_all_socket_pair'
|
|
114
|
+
require 'nonnative/reset_peer_socket_pair'
|
|
105
115
|
require 'nonnative/delay_socket_pair'
|
|
106
116
|
require 'nonnative/timeout_socket_pair'
|
|
107
117
|
require 'nonnative/invalid_data_socket_pair'
|
|
@@ -109,6 +119,11 @@ require 'nonnative/socket_pair_factory'
|
|
|
109
119
|
require 'nonnative/go_executable'
|
|
110
120
|
require 'nonnative/cucumber'
|
|
111
121
|
require 'nonnative/header'
|
|
122
|
+
require 'nonnative/ed25519_key'
|
|
123
|
+
require 'nonnative/jwt_token'
|
|
124
|
+
require 'nonnative/paseto_token'
|
|
125
|
+
require 'nonnative/ssh_token'
|
|
126
|
+
require 'nonnative/token'
|
|
112
127
|
|
|
113
128
|
# The main namespace for the gem.
|
|
114
129
|
#
|
|
@@ -194,6 +209,23 @@ module Nonnative
|
|
|
194
209
|
Nonnative::GoExecutable.new(tools, exec, output).argv(cmd, *params)
|
|
195
210
|
end
|
|
196
211
|
|
|
212
|
+
# Builds a token generator for authenticating against a service under test.
|
|
213
|
+
#
|
|
214
|
+
# The signing parameters are passed in directly; this is not coupled to any service's
|
|
215
|
+
# configuration format. The generated token string is ready for {Nonnative::Header.auth_bearer}.
|
|
216
|
+
#
|
|
217
|
+
# @param kind [String] token kind, one of `"jwt"`, `"paseto"`, or `"ssh"`
|
|
218
|
+
# @param issuer [String] the `iss` claim (unused by the `ssh` kind)
|
|
219
|
+
# @param key [String] the key id (JWT `kid` header, PASETO `kid` footer, or SSH `kid` claim)
|
|
220
|
+
# @param private_key [String] path to the Ed25519 private key file (PKCS#8 PEM for `jwt`/`paseto`, OpenSSH format for `ssh`)
|
|
221
|
+
# @param expiration [Integer] token lifetime in seconds (drives `exp`)
|
|
222
|
+
# @return [Nonnative::Token]
|
|
223
|
+
#
|
|
224
|
+
# @example
|
|
225
|
+
# token = Nonnative.token(kind: 'jwt', issuer: 'iss', key: 'key-1', private_key: 'config/ed25519.pem', expiration: 3600)
|
|
226
|
+
# Nonnative::Header.auth_bearer(token.generate(aud: 'GET /v1/things', sub: 'user-1'))
|
|
227
|
+
def token(kind:, issuer:, key:, private_key:, expiration:) = Nonnative::Token.new(kind:, issuer:, key:, private_key:, expiration:)
|
|
228
|
+
|
|
197
229
|
# Returns an HTTP client for common health/readiness endpoints.
|
|
198
230
|
#
|
|
199
231
|
# @return [Nonnative::Observability]
|
data/nonnative.gemspec
CHANGED
|
@@ -28,13 +28,19 @@ Gem::Specification.new do |spec|
|
|
|
28
28
|
spec.add_dependency 'config', '>= 5', '< 6'
|
|
29
29
|
spec.add_dependency 'cucumber', '>= 7', '< 12'
|
|
30
30
|
spec.add_dependency 'cucumber-cucumber-expressions', '< 19'
|
|
31
|
+
spec.add_dependency 'ed25519', '>= 1', '< 2'
|
|
31
32
|
spec.add_dependency 'get_process_mem', '>= 1', '< 2'
|
|
32
33
|
spec.add_dependency 'grpc', '>= 1', '< 2'
|
|
34
|
+
spec.add_dependency 'jwt', '>= 3', '< 4'
|
|
35
|
+
spec.add_dependency 'jwt-eddsa', '>= 0', '< 1'
|
|
33
36
|
spec.add_dependency 'puma', '>= 7', '< 8'
|
|
37
|
+
spec.add_dependency 'rbnacl', '>= 7', '< 8'
|
|
34
38
|
spec.add_dependency 'rest-client', '>= 2', '< 3'
|
|
35
39
|
spec.add_dependency 'retriable', '>= 3', '< 4'
|
|
36
40
|
spec.add_dependency 'rspec-benchmark', '>= 0', '< 1'
|
|
37
41
|
spec.add_dependency 'rspec-expectations', '>= 3', '< 4'
|
|
38
42
|
spec.add_dependency 'rspec-wait', '>= 1', '< 2'
|
|
43
|
+
spec.add_dependency 'ruby-paseto', '>= 0', '< 1'
|
|
39
44
|
spec.add_dependency 'sinatra', '>= 4', '< 5'
|
|
45
|
+
spec.add_dependency 'ssh_data', '>= 1', '< 2'
|
|
40
46
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: nonnative
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.19.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alejandro Falkowski
|
|
@@ -83,6 +83,26 @@ dependencies:
|
|
|
83
83
|
- - "<"
|
|
84
84
|
- !ruby/object:Gem::Version
|
|
85
85
|
version: '19'
|
|
86
|
+
- !ruby/object:Gem::Dependency
|
|
87
|
+
name: ed25519
|
|
88
|
+
requirement: !ruby/object:Gem::Requirement
|
|
89
|
+
requirements:
|
|
90
|
+
- - ">="
|
|
91
|
+
- !ruby/object:Gem::Version
|
|
92
|
+
version: '1'
|
|
93
|
+
- - "<"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '2'
|
|
96
|
+
type: :runtime
|
|
97
|
+
prerelease: false
|
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - ">="
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '1'
|
|
103
|
+
- - "<"
|
|
104
|
+
- !ruby/object:Gem::Version
|
|
105
|
+
version: '2'
|
|
86
106
|
- !ruby/object:Gem::Dependency
|
|
87
107
|
name: get_process_mem
|
|
88
108
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -123,6 +143,46 @@ dependencies:
|
|
|
123
143
|
- - "<"
|
|
124
144
|
- !ruby/object:Gem::Version
|
|
125
145
|
version: '2'
|
|
146
|
+
- !ruby/object:Gem::Dependency
|
|
147
|
+
name: jwt
|
|
148
|
+
requirement: !ruby/object:Gem::Requirement
|
|
149
|
+
requirements:
|
|
150
|
+
- - ">="
|
|
151
|
+
- !ruby/object:Gem::Version
|
|
152
|
+
version: '3'
|
|
153
|
+
- - "<"
|
|
154
|
+
- !ruby/object:Gem::Version
|
|
155
|
+
version: '4'
|
|
156
|
+
type: :runtime
|
|
157
|
+
prerelease: false
|
|
158
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
159
|
+
requirements:
|
|
160
|
+
- - ">="
|
|
161
|
+
- !ruby/object:Gem::Version
|
|
162
|
+
version: '3'
|
|
163
|
+
- - "<"
|
|
164
|
+
- !ruby/object:Gem::Version
|
|
165
|
+
version: '4'
|
|
166
|
+
- !ruby/object:Gem::Dependency
|
|
167
|
+
name: jwt-eddsa
|
|
168
|
+
requirement: !ruby/object:Gem::Requirement
|
|
169
|
+
requirements:
|
|
170
|
+
- - ">="
|
|
171
|
+
- !ruby/object:Gem::Version
|
|
172
|
+
version: '0'
|
|
173
|
+
- - "<"
|
|
174
|
+
- !ruby/object:Gem::Version
|
|
175
|
+
version: '1'
|
|
176
|
+
type: :runtime
|
|
177
|
+
prerelease: false
|
|
178
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
179
|
+
requirements:
|
|
180
|
+
- - ">="
|
|
181
|
+
- !ruby/object:Gem::Version
|
|
182
|
+
version: '0'
|
|
183
|
+
- - "<"
|
|
184
|
+
- !ruby/object:Gem::Version
|
|
185
|
+
version: '1'
|
|
126
186
|
- !ruby/object:Gem::Dependency
|
|
127
187
|
name: puma
|
|
128
188
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -143,6 +203,26 @@ dependencies:
|
|
|
143
203
|
- - "<"
|
|
144
204
|
- !ruby/object:Gem::Version
|
|
145
205
|
version: '8'
|
|
206
|
+
- !ruby/object:Gem::Dependency
|
|
207
|
+
name: rbnacl
|
|
208
|
+
requirement: !ruby/object:Gem::Requirement
|
|
209
|
+
requirements:
|
|
210
|
+
- - ">="
|
|
211
|
+
- !ruby/object:Gem::Version
|
|
212
|
+
version: '7'
|
|
213
|
+
- - "<"
|
|
214
|
+
- !ruby/object:Gem::Version
|
|
215
|
+
version: '8'
|
|
216
|
+
type: :runtime
|
|
217
|
+
prerelease: false
|
|
218
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
219
|
+
requirements:
|
|
220
|
+
- - ">="
|
|
221
|
+
- !ruby/object:Gem::Version
|
|
222
|
+
version: '7'
|
|
223
|
+
- - "<"
|
|
224
|
+
- !ruby/object:Gem::Version
|
|
225
|
+
version: '8'
|
|
146
226
|
- !ruby/object:Gem::Dependency
|
|
147
227
|
name: rest-client
|
|
148
228
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -243,6 +323,26 @@ dependencies:
|
|
|
243
323
|
- - "<"
|
|
244
324
|
- !ruby/object:Gem::Version
|
|
245
325
|
version: '2'
|
|
326
|
+
- !ruby/object:Gem::Dependency
|
|
327
|
+
name: ruby-paseto
|
|
328
|
+
requirement: !ruby/object:Gem::Requirement
|
|
329
|
+
requirements:
|
|
330
|
+
- - ">="
|
|
331
|
+
- !ruby/object:Gem::Version
|
|
332
|
+
version: '0'
|
|
333
|
+
- - "<"
|
|
334
|
+
- !ruby/object:Gem::Version
|
|
335
|
+
version: '1'
|
|
336
|
+
type: :runtime
|
|
337
|
+
prerelease: false
|
|
338
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
339
|
+
requirements:
|
|
340
|
+
- - ">="
|
|
341
|
+
- !ruby/object:Gem::Version
|
|
342
|
+
version: '0'
|
|
343
|
+
- - "<"
|
|
344
|
+
- !ruby/object:Gem::Version
|
|
345
|
+
version: '1'
|
|
246
346
|
- !ruby/object:Gem::Dependency
|
|
247
347
|
name: sinatra
|
|
248
348
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -263,6 +363,26 @@ dependencies:
|
|
|
263
363
|
- - "<"
|
|
264
364
|
- !ruby/object:Gem::Version
|
|
265
365
|
version: '5'
|
|
366
|
+
- !ruby/object:Gem::Dependency
|
|
367
|
+
name: ssh_data
|
|
368
|
+
requirement: !ruby/object:Gem::Requirement
|
|
369
|
+
requirements:
|
|
370
|
+
- - ">="
|
|
371
|
+
- !ruby/object:Gem::Version
|
|
372
|
+
version: '1'
|
|
373
|
+
- - "<"
|
|
374
|
+
- !ruby/object:Gem::Version
|
|
375
|
+
version: '2'
|
|
376
|
+
type: :runtime
|
|
377
|
+
prerelease: false
|
|
378
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
379
|
+
requirements:
|
|
380
|
+
- - ">="
|
|
381
|
+
- !ruby/object:Gem::Version
|
|
382
|
+
version: '1'
|
|
383
|
+
- - "<"
|
|
384
|
+
- !ruby/object:Gem::Version
|
|
385
|
+
version: '2'
|
|
266
386
|
description: Starts OS processes, in-process Ruby servers, and proxy-only services
|
|
267
387
|
with TCP readiness checks and fault-injection proxies.
|
|
268
388
|
email:
|
|
@@ -272,6 +392,7 @@ extensions: []
|
|
|
272
392
|
extra_rdoc_files: []
|
|
273
393
|
files:
|
|
274
394
|
- ".circleci/config.yml"
|
|
395
|
+
- ".claude/skills"
|
|
275
396
|
- ".codecov.yml"
|
|
276
397
|
- ".config/cucumber.yml"
|
|
277
398
|
- ".editorconfig"
|
|
@@ -281,6 +402,7 @@ files:
|
|
|
281
402
|
- ".goreleaser.yml"
|
|
282
403
|
- ".rubocop.yml"
|
|
283
404
|
- AGENTS.md
|
|
405
|
+
- CLAUDE.md
|
|
284
406
|
- Gemfile
|
|
285
407
|
- Gemfile.lock
|
|
286
408
|
- LICENSE
|
|
@@ -299,6 +421,7 @@ files:
|
|
|
299
421
|
- lib/nonnative/configuration_service_readiness.rb
|
|
300
422
|
- lib/nonnative/cucumber.rb
|
|
301
423
|
- lib/nonnative/delay_socket_pair.rb
|
|
424
|
+
- lib/nonnative/ed25519_key.rb
|
|
302
425
|
- lib/nonnative/error.rb
|
|
303
426
|
- lib/nonnative/fault_injection_proxy.rb
|
|
304
427
|
- lib/nonnative/go_executable.rb
|
|
@@ -312,26 +435,31 @@ files:
|
|
|
312
435
|
- lib/nonnative/http_server.rb
|
|
313
436
|
- lib/nonnative/http_service.rb
|
|
314
437
|
- lib/nonnative/invalid_data_socket_pair.rb
|
|
438
|
+
- lib/nonnative/jwt_token.rb
|
|
315
439
|
- lib/nonnative/no_proxy.rb
|
|
316
440
|
- lib/nonnative/not_found_error.rb
|
|
317
441
|
- lib/nonnative/observability.rb
|
|
442
|
+
- lib/nonnative/paseto_token.rb
|
|
318
443
|
- lib/nonnative/pool.rb
|
|
319
444
|
- lib/nonnative/port.rb
|
|
320
445
|
- lib/nonnative/ports.rb
|
|
321
446
|
- lib/nonnative/process.rb
|
|
322
447
|
- lib/nonnative/proxy.rb
|
|
323
448
|
- lib/nonnative/proxy_factory.rb
|
|
449
|
+
- lib/nonnative/reset_peer_socket_pair.rb
|
|
324
450
|
- lib/nonnative/runner.rb
|
|
325
451
|
- lib/nonnative/server.rb
|
|
326
452
|
- lib/nonnative/service.rb
|
|
327
453
|
- lib/nonnative/socket_pair.rb
|
|
328
454
|
- lib/nonnative/socket_pair_factory.rb
|
|
455
|
+
- lib/nonnative/ssh_token.rb
|
|
329
456
|
- lib/nonnative/start_error.rb
|
|
330
457
|
- lib/nonnative/startup.rb
|
|
331
458
|
- lib/nonnative/stop_error.rb
|
|
332
459
|
- lib/nonnative/tcp_probe.rb
|
|
333
460
|
- lib/nonnative/timeout.rb
|
|
334
461
|
- lib/nonnative/timeout_socket_pair.rb
|
|
462
|
+
- lib/nonnative/token.rb
|
|
335
463
|
- lib/nonnative/version.rb
|
|
336
464
|
- nonnative.gemspec
|
|
337
465
|
homepage: https://github.com/alexfalkowski/nonnative
|