dash 3.2.1 → 4.0.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/lib/dash/cli/app/boot.rb +2 -2
- data/lib/dash/cli/app.rb +1 -1
- data/lib/dash/cli/base.rb +1 -1
- data/lib/dash/cli/doctor/endpoint_checks.rb +1 -1
- data/lib/dash/cli/doctor/host_checks.rb +4 -4
- data/lib/dash/cli/main.rb +5 -5
- data/lib/dash/cli/proxy/legacy_rename.rb +58 -0
- data/lib/dash/cli/proxy/loadbalancer_reboot.rb +1 -1
- data/lib/dash/cli/proxy/reboot.rb +8 -8
- data/lib/dash/cli/proxy.rb +14 -10
- data/lib/dash/commander.rb +1 -1
- data/lib/dash/commands/accessory/proxy.rb +1 -1
- data/lib/dash/commands/app/execution.rb +1 -1
- data/lib/dash/commands/app/proxy.rb +1 -1
- data/lib/dash/commands/app.rb +1 -1
- data/lib/dash/commands/docker.rb +35 -1
- data/lib/dash/commands/loadbalancer.rb +47 -17
- data/lib/dash/commands/lock.rb +1 -1
- data/lib/dash/commands/proxy/cert_transfer.rb +4 -4
- data/lib/dash/commands/proxy.rb +112 -13
- data/lib/dash/commands/prune.rb +1 -1
- data/lib/dash/configuration/accessory.rb +2 -2
- data/lib/dash/configuration/docs/accessory.yml +3 -3
- data/lib/dash/configuration/docs/configuration.yml +1 -1
- data/lib/dash/configuration/docs/proxy.yml +38 -38
- data/lib/dash/configuration/loadbalancer.rb +2 -2
- data/lib/dash/configuration/proxy/acme.rb +5 -5
- data/lib/dash/configuration/proxy/boot.rb +2 -4
- data/lib/dash/configuration/proxy/run.rb +10 -10
- data/lib/dash/configuration/proxy.rb +36 -14
- data/lib/dash/configuration/role.rb +2 -2
- data/lib/dash/configuration/validator/proxy.rb +23 -23
- data/lib/dash/configuration.rb +8 -8
- data/lib/dash/utils.rb +1 -1
- data/lib/dash/version.rb +1 -1
- metadata +2 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
class Dash::Configuration::Proxy::Run
|
|
2
|
-
MINIMUM_VERSION = "v1.0.0
|
|
2
|
+
MINIMUM_VERSION = "v1.1.0.0"
|
|
3
3
|
DEFAULT_HTTP_PORT = 80
|
|
4
4
|
DEFAULT_HTTPS_PORT = 443
|
|
5
5
|
DEFAULT_LOG_MAX_SIZE = "10m"
|
|
@@ -103,7 +103,7 @@ class Dash::Configuration::Proxy::Run
|
|
|
103
103
|
end
|
|
104
104
|
|
|
105
105
|
def container_name
|
|
106
|
-
"
|
|
106
|
+
"dash-proxy"
|
|
107
107
|
end
|
|
108
108
|
|
|
109
109
|
# Zero-downtime reboots: a minimal long-lived holder container owns the
|
|
@@ -114,14 +114,14 @@ class Dash::Configuration::Proxy::Run
|
|
|
114
114
|
end
|
|
115
115
|
|
|
116
116
|
def holder_container_name
|
|
117
|
-
"
|
|
117
|
+
"dash-proxy-net"
|
|
118
118
|
end
|
|
119
119
|
|
|
120
120
|
def network_args
|
|
121
121
|
if port_holder?
|
|
122
122
|
[ "--network", "container:#{holder_container_name}" ]
|
|
123
123
|
else
|
|
124
|
-
[ "--network", "
|
|
124
|
+
[ "--network", "dash" ]
|
|
125
125
|
end
|
|
126
126
|
end
|
|
127
127
|
|
|
@@ -136,7 +136,7 @@ class Dash::Configuration::Proxy::Run
|
|
|
136
136
|
end
|
|
137
137
|
|
|
138
138
|
def run_command
|
|
139
|
-
[ "
|
|
139
|
+
[ "dash-proxy", "run", *optionize(run_command_options), *acme.run_command_args ].join(" ")
|
|
140
140
|
end
|
|
141
141
|
|
|
142
142
|
def metrics_port
|
|
@@ -158,7 +158,7 @@ class Dash::Configuration::Proxy::Run
|
|
|
158
158
|
.compact.merge(cache_options).merge(server_options)
|
|
159
159
|
end
|
|
160
160
|
|
|
161
|
-
# Anything
|
|
161
|
+
# Anything dash-proxy accepts that has no key of its own yet. Values are
|
|
162
162
|
# optionized as written - `true` is a bare flag, anything else takes a value -
|
|
163
163
|
# because the point of an escape hatch is that the gem holds no opinion about
|
|
164
164
|
# the flag it is forwarding.
|
|
@@ -189,7 +189,7 @@ class Dash::Configuration::Proxy::Run
|
|
|
189
189
|
].compact
|
|
190
190
|
end
|
|
191
191
|
|
|
192
|
-
# The flag only tells
|
|
192
|
+
# The flag only tells dash-proxy where to look inside its own container, so
|
|
193
193
|
# without this mount the socket is not there and a sleeping service never
|
|
194
194
|
# wakes - which the operator sees as one hung request, not as a misconfiguration.
|
|
195
195
|
def docker_socket_args
|
|
@@ -224,7 +224,7 @@ class Dash::Configuration::Proxy::Run
|
|
|
224
224
|
end
|
|
225
225
|
|
|
226
226
|
def apps_container_directory
|
|
227
|
-
"/home/
|
|
227
|
+
"/home/dash-proxy/.apps-config"
|
|
228
228
|
end
|
|
229
229
|
|
|
230
230
|
def apps_volume
|
|
@@ -288,7 +288,7 @@ class Dash::Configuration::Proxy::Run
|
|
|
288
288
|
end
|
|
289
289
|
|
|
290
290
|
# A passthrough flag that a named key already emits would be optionized
|
|
291
|
-
# twice, and
|
|
291
|
+
# twice, and dash-proxy would silently take the last one. Refuse instead.
|
|
292
292
|
# Only Proxy::Run knows which flags the named keys produce, so the check
|
|
293
293
|
# cannot live in the validator with the rest.
|
|
294
294
|
def ensure_no_conflicting_flags
|
|
@@ -303,7 +303,7 @@ class Dash::Configuration::Proxy::Run
|
|
|
303
303
|
# than of any one service - the policy that fills the cache is per service,
|
|
304
304
|
# in proxy/cache. The store itself is deliberately absent: a store URL may
|
|
305
305
|
# embed credentials, so it travels as CACHE_STORE in the secrets env file
|
|
306
|
-
# (
|
|
306
|
+
# (dash-proxy reads it as the --cache-store default) rather than on a
|
|
307
307
|
# command line that lands in process listings and the audit log.
|
|
308
308
|
def cache_options
|
|
309
309
|
cache = run_config["cache"] || {}
|
|
@@ -2,11 +2,33 @@ class Dash::Configuration::Proxy
|
|
|
2
2
|
include Dash::Configuration::Validation
|
|
3
3
|
|
|
4
4
|
DEFAULT_LOG_REQUEST_HEADERS = [ "Cache-Control", "Last-Modified", "User-Agent" ]
|
|
5
|
-
CONTAINER_NAME = "
|
|
6
|
-
LOADBALANCER_CONTAINER_NAME = "
|
|
5
|
+
CONTAINER_NAME = "dash-proxy"
|
|
6
|
+
LOADBALANCER_CONTAINER_NAME = "dash-loadbalancer"
|
|
7
7
|
CLIENT_CA_FILENAME = "client-ca.pem"
|
|
8
8
|
|
|
9
|
-
#
|
|
9
|
+
# The image label the prune filters key on, set by dash-proxy's publish
|
|
10
|
+
# workflow.
|
|
11
|
+
IMAGE_TITLE = "dash-proxy"
|
|
12
|
+
LOADBALANCER_IMAGE_TITLE = "dash-loadbalancer"
|
|
13
|
+
|
|
14
|
+
NETWORK = "dash"
|
|
15
|
+
CONFIG_VOLUME = "dash-proxy-config"
|
|
16
|
+
LOADBALANCER_CONFIG_VOLUME = "dash-loadbalancer-config"
|
|
17
|
+
|
|
18
|
+
# Pre-rename names, kept only so the stage-3c migration can find what a host
|
|
19
|
+
# already has: the container to replace, the volume to copy, the network to
|
|
20
|
+
# bridge, and the image label to keep pruning by. Nothing constructs a
|
|
21
|
+
# resource with these — stage 3d deletes them.
|
|
22
|
+
LEGACY_CONTAINER_NAME = "kamal-proxy"
|
|
23
|
+
LEGACY_LOADBALANCER_CONTAINER_NAME = "kamal-loadbalancer"
|
|
24
|
+
LEGACY_HOLDER_CONTAINER_NAME = "kamal-proxy-net"
|
|
25
|
+
LEGACY_NETWORK = "kamal"
|
|
26
|
+
LEGACY_CONFIG_VOLUME = "kamal-proxy-config"
|
|
27
|
+
LEGACY_LOADBALANCER_CONFIG_VOLUME = "kamal-loadbalancer-config"
|
|
28
|
+
LEGACY_IMAGE_TITLE = "kamal-proxy"
|
|
29
|
+
LEGACY_LOADBALANCER_IMAGE_TITLE = "kamal-loadbalancer"
|
|
30
|
+
|
|
31
|
+
# What `compress: true` offers. dash-proxy has no "on" state without an
|
|
10
32
|
# explicit list - --compress *is* the list - so the shorthand has to pick.
|
|
11
33
|
# Best ratio first, matching the proxy's own default ordering; the client's
|
|
12
34
|
# Accept-Encoding q-values still outrank this preference.
|
|
@@ -14,7 +36,7 @@ class Dash::Configuration::Proxy
|
|
|
14
36
|
|
|
15
37
|
SUPPORTED_COMPRESSION_ENCODINGS = %w[ gzip br zstd ].freeze
|
|
16
38
|
|
|
17
|
-
#
|
|
39
|
+
# dash-proxy maps `brotli` onto the `br` token that travels in Content-Encoding.
|
|
18
40
|
COMPRESSION_ENCODING_ALIASES = { "brotli" => "br" }.freeze
|
|
19
41
|
|
|
20
42
|
# The layering contract. When the fork's load balancer fronts the per-host
|
|
@@ -32,7 +54,7 @@ class Dash::Configuration::Proxy
|
|
|
32
54
|
# Without load balancing the single proxy is every layer at once and the
|
|
33
55
|
# whole surface applies to it.
|
|
34
56
|
DEPLOY_OPTION_DISPOSITIONS = {
|
|
35
|
-
# --- Edge: TLS terminates where the handshake happens, and
|
|
57
|
+
# --- Edge: TLS terminates where the handshake happens, and dash-proxy
|
|
36
58
|
# gates TLSRedirect on TLSEnabled, so the whole family travels together.
|
|
37
59
|
host: :edge,
|
|
38
60
|
tls: :edge,
|
|
@@ -59,7 +81,7 @@ class Dash::Configuration::Proxy
|
|
|
59
81
|
"rate-limit-burst": :edge,
|
|
60
82
|
"rate-limit-exempt": :edge,
|
|
61
83
|
|
|
62
|
-
# --- Edge:
|
|
84
|
+
# --- Edge: dash-proxy deletes the Authorization header once a service
|
|
63
85
|
# enforces basic auth, so an inner proxy would 401 the credential-less
|
|
64
86
|
# request the load balancer forwards. Credentials belong at the edge only.
|
|
65
87
|
"basic-auth": :edge,
|
|
@@ -311,7 +333,7 @@ class Dash::Configuration::Proxy
|
|
|
311
333
|
proxy_config["path_prefixes"] || proxy_config["path_prefix"]&.split(",") || []
|
|
312
334
|
end
|
|
313
335
|
|
|
314
|
-
# Nil when unset: the default lives in
|
|
336
|
+
# Nil when unset: the default lives in dash-proxy, not here.
|
|
315
337
|
def healthcheck_path
|
|
316
338
|
proxy_config.dig("healthcheck", "path")
|
|
317
339
|
end
|
|
@@ -370,7 +392,7 @@ class Dash::Configuration::Proxy
|
|
|
370
392
|
optionize ({ target: "#{target}:#{app_port}" }).merge(deploy_options), with: "="
|
|
371
393
|
end
|
|
372
394
|
|
|
373
|
-
#
|
|
395
|
+
# dash-proxy rollout deploy only accepts the target and the timeouts - the service already
|
|
374
396
|
# exists, so it keeps the host, TLS, buffering and logging options of the live deploy.
|
|
375
397
|
def rollout_deploy_options
|
|
376
398
|
{
|
|
@@ -426,7 +448,7 @@ class Dash::Configuration::Proxy
|
|
|
426
448
|
primary_role.present? && primary_role.running_proxy? && Array(primary_role.hosts).size > 1
|
|
427
449
|
end
|
|
428
450
|
|
|
429
|
-
# Flags for
|
|
451
|
+
# Flags for dash-proxy's dynamic domain source (runtime TLS hostnames).
|
|
430
452
|
# TLS terminates wherever these flags land — :edge in the layering contract.
|
|
431
453
|
def ssl_domains_options
|
|
432
454
|
{
|
|
@@ -449,7 +471,7 @@ class Dash::Configuration::Proxy
|
|
|
449
471
|
# The connection pool between the proxy and this app's containers, and how
|
|
450
472
|
# hard the proxy tries to place a request on a healthy one.
|
|
451
473
|
#
|
|
452
|
-
# Every value is passed through exactly as written.
|
|
474
|
+
# Every value is passed through exactly as written. dash-proxy resolves its
|
|
453
475
|
# own defaults from a zero (target_pool.go), and it has to do that server
|
|
454
476
|
# side because restored state and older RPC clients never see the CLI — so
|
|
455
477
|
# substituting a default here would be both redundant and wrong.
|
|
@@ -517,7 +539,7 @@ class Dash::Configuration::Proxy
|
|
|
517
539
|
proxy_config["headers"] || {}
|
|
518
540
|
end
|
|
519
541
|
|
|
520
|
-
#
|
|
542
|
+
# dash-proxy cuts a rule at the first colon, so a value carrying colons of
|
|
521
543
|
# its own arrives intact.
|
|
522
544
|
def header_rules(direction, verb)
|
|
523
545
|
(headers_config.dig(direction, verb) || {}).map { |name, value| "#{name}: #{value}" }.presence
|
|
@@ -574,7 +596,7 @@ class Dash::Configuration::Proxy
|
|
|
574
596
|
|
|
575
597
|
# The cache policy, which is per service - the store it writes into is
|
|
576
598
|
# proxy-wide and lives in proxy/run/cache. Only --cache is derived from a
|
|
577
|
-
# truthy key; every other default stays in
|
|
599
|
+
# truthy key; every other default stays in dash-proxy, so an unset key emits
|
|
578
600
|
# nothing rather than restating a default the gem would then have to track.
|
|
579
601
|
def cache_options
|
|
580
602
|
cache = proxy_config["cache"] || {}
|
|
@@ -600,7 +622,7 @@ class Dash::Configuration::Proxy
|
|
|
600
622
|
File.join([ directory, role_name, filename ].compact)
|
|
601
623
|
end
|
|
602
624
|
|
|
603
|
-
#
|
|
625
|
+
# dash-proxy takes the credential as <username>:<password> and cuts at the
|
|
604
626
|
# first colon, so a password may contain colons but a username may not (the
|
|
605
627
|
# validator enforces that). The password is read from .dash/secrets when
|
|
606
628
|
# password_secret names it, so it need not live in deploy.yml.
|
|
@@ -628,7 +650,7 @@ class Dash::Configuration::Proxy
|
|
|
628
650
|
Dash::Utils.sensitive("#{basic_auth["username"]}:#{password}")
|
|
629
651
|
end
|
|
630
652
|
|
|
631
|
-
# Serves both --path-timeout and --path-request-timeout, which
|
|
653
|
+
# Serves both --path-timeout and --path-request-timeout, which dash-proxy
|
|
632
654
|
# reads with one parser. A duration may be a Go string ("5m") or plain
|
|
633
655
|
# seconds; 0 is a value, meaning no limit for that prefix.
|
|
634
656
|
def path_timeout_args(key)
|
|
@@ -148,7 +148,7 @@ class Dash::Configuration::Role
|
|
|
148
148
|
def readiness_description
|
|
149
149
|
case readiness_source
|
|
150
150
|
when :proxy
|
|
151
|
-
[ "
|
|
151
|
+
[ "dash-proxy health check", proxy.healthcheck_path ].compact.join(" ")
|
|
152
152
|
when :healthcheck
|
|
153
153
|
healthcheck.port ? "healthcheck #{healthcheck.path}:#{healthcheck.port}" : "healthcheck (custom cmd)"
|
|
154
154
|
when :healthcheck_exec
|
|
@@ -168,7 +168,7 @@ class Dash::Configuration::Role
|
|
|
168
168
|
end
|
|
169
169
|
|
|
170
170
|
def stop_args
|
|
171
|
-
# When deploying with the proxy,
|
|
171
|
+
# When deploying with the proxy, dash-proxy will drain request before returning so we don't need to wait.
|
|
172
172
|
timeout = stop_timeout || (running_proxy? ? nil : config.drain_timeout)
|
|
173
173
|
|
|
174
174
|
[ *argumentize("-t", timeout) ]
|
|
@@ -8,7 +8,7 @@ class Dash::Configuration::Validator::Proxy < Dash::Configuration::Validator
|
|
|
8
8
|
# provides the hostnames at runtime.
|
|
9
9
|
# On-demand TLS joins loadbalancer and ssl_domains as a source of hostnames
|
|
10
10
|
# that only exist at handshake time - demanding a static host here would
|
|
11
|
-
# reject the one shape
|
|
11
|
+
# reject the one shape dash-proxy requires for it.
|
|
12
12
|
if config["host"].blank? && config["hosts"].blank? && config["ssl"] && config["loadbalancer"].blank? &&
|
|
13
13
|
config.dig("ssl_domains", "source").blank? && ssl_config["on_demand_url"].blank?
|
|
14
14
|
error "Must set a host to enable automatic SSL"
|
|
@@ -113,7 +113,7 @@ class Dash::Configuration::Validator::Proxy < Dash::Configuration::Validator
|
|
|
113
113
|
end
|
|
114
114
|
end
|
|
115
115
|
|
|
116
|
-
# Same source shape as ssl_domains. The interval minimum is
|
|
116
|
+
# Same source shape as ssl_domains. The interval minimum is dash-proxy's
|
|
117
117
|
# own (MinRedirectsInterval, 10s) - it rejects a smaller one after the SSH
|
|
118
118
|
# round trip, so catch it here at config time.
|
|
119
119
|
def validate_redirects_source!(redirects_source)
|
|
@@ -132,7 +132,7 @@ class Dash::Configuration::Validator::Proxy < Dash::Configuration::Validator
|
|
|
132
132
|
end
|
|
133
133
|
end
|
|
134
134
|
|
|
135
|
-
#
|
|
135
|
+
# dash-proxy only logs a warning for a DNS provider it does not recognise
|
|
136
136
|
# and then carries on with no provider at all, so the symptom is a
|
|
137
137
|
# certificate that never issues rather than a failed boot. Catch it here,
|
|
138
138
|
# before any host is contacted.
|
|
@@ -185,7 +185,7 @@ class Dash::Configuration::Validator::Proxy < Dash::Configuration::Validator
|
|
|
185
185
|
end
|
|
186
186
|
|
|
187
187
|
# The whole TLS surface lives in the one `ssl` hash: certificate material,
|
|
188
|
-
# on-demand issuance and the mTLS client CA.
|
|
188
|
+
# on-demand issuance and the mTLS client CA. dash-proxy rejects the
|
|
189
189
|
# on-demand combinations outright rather than picking a winner
|
|
190
190
|
# (ServiceOptions.Validate), so there is no precedence to document - only
|
|
191
191
|
# a deploy that would fail after the SSH round-trip. Fail here.
|
|
@@ -231,13 +231,13 @@ class Dash::Configuration::Validator::Proxy < Dash::Configuration::Validator
|
|
|
231
231
|
|
|
232
232
|
# An RFC 9110 token, which is both a valid header field name and a valid
|
|
233
233
|
# cookie name. For a header, a space or a colon would also break the
|
|
234
|
-
# '<name>: <value>' encoding
|
|
234
|
+
# '<name>: <value>' encoding dash-proxy cuts at the first colon.
|
|
235
235
|
TOKEN = /\A[!#$%&'*+\-.^_`|~0-9A-Za-z]+\z/
|
|
236
236
|
|
|
237
237
|
POOL_KEYS = %w[ max_conns max_idle_conns idle_conn_timeout dial_timeout try_duration try_interval ].freeze
|
|
238
238
|
|
|
239
239
|
# A zero is meaningful for most of these, so only negatives are refused.
|
|
240
|
-
#
|
|
240
|
+
# dash-proxy clamps them rather than failing when it reads saved state, and
|
|
241
241
|
# rejects them from a client — this is the client.
|
|
242
242
|
def validate_tuning!
|
|
243
243
|
target = config["target"] || {}
|
|
@@ -258,13 +258,13 @@ class Dash::Configuration::Validator::Proxy < Dash::Configuration::Validator
|
|
|
258
258
|
with_context("request_timeout") { error "cannot be negative" }
|
|
259
259
|
end
|
|
260
260
|
|
|
261
|
-
# Sibling consistency:
|
|
261
|
+
# Sibling consistency: dash-proxy clamps a negative --target-timeout as
|
|
262
262
|
# silently as a negative --request-timeout.
|
|
263
263
|
if config["response_timeout"].to_f.negative?
|
|
264
264
|
with_context("response_timeout") { error "cannot be negative" }
|
|
265
265
|
end
|
|
266
266
|
|
|
267
|
-
# Both maps reach
|
|
267
|
+
# Both maps reach dash-proxy's one parsePathTimeouts, which refuses a
|
|
268
268
|
# negative duration.
|
|
269
269
|
%w[ path_response_timeouts path_request_timeouts ].each do |key|
|
|
270
270
|
with_context(key) do
|
|
@@ -312,7 +312,7 @@ class Dash::Configuration::Validator::Proxy < Dash::Configuration::Validator
|
|
|
312
312
|
end
|
|
313
313
|
end
|
|
314
314
|
|
|
315
|
-
#
|
|
315
|
+
# dash-proxy rejects canonical_host + on-demand TLS after the SSH round
|
|
316
316
|
# trip (canonical redirection needs a fixed host, on-demand TLS has none),
|
|
317
317
|
# and a canonical host outside the host list redirects every request to a
|
|
318
318
|
# hostname this service does not serve.
|
|
@@ -393,7 +393,7 @@ class Dash::Configuration::Validator::Proxy < Dash::Configuration::Validator
|
|
|
393
393
|
end
|
|
394
394
|
|
|
395
395
|
# Go carries the request host in Request.Host rather than the header map,
|
|
396
|
-
# so
|
|
396
|
+
# so dash-proxy refuses the rule instead of silently doing nothing.
|
|
397
397
|
if direction == "request" && name.to_s.downcase == "host"
|
|
398
398
|
return error "cannot rewrite the Host header"
|
|
399
399
|
end
|
|
@@ -408,7 +408,7 @@ class Dash::Configuration::Validator::Proxy < Dash::Configuration::Validator
|
|
|
408
408
|
|
|
409
409
|
# The pattern is deliberately not compiled: Go's RE2 and Ruby's Onigmo differ
|
|
410
410
|
# at the edges (Ruby rejects Go's `(?P<name>...)`), and refusing a pattern
|
|
411
|
-
#
|
|
411
|
+
# dash-proxy would have accepted is worse than finding out at deploy time.
|
|
412
412
|
def validate_path_rules!(key, redirect:)
|
|
413
413
|
Array(config[key]).each_with_index do |rule, index|
|
|
414
414
|
with_context(key) do
|
|
@@ -419,7 +419,7 @@ class Dash::Configuration::Validator::Proxy < Dash::Configuration::Validator
|
|
|
419
419
|
next error "needs both from and to"
|
|
420
420
|
end
|
|
421
421
|
|
|
422
|
-
# The wire format is '<from>=<to>' and
|
|
422
|
+
# The wire format is '<from>=<to>' and dash-proxy cuts at the
|
|
423
423
|
# FIRST '=', so an '=' inside the pattern silently builds a rule
|
|
424
424
|
# for a different path.
|
|
425
425
|
if rule["from"].to_s.include?("=")
|
|
@@ -443,7 +443,7 @@ class Dash::Configuration::Validator::Proxy < Dash::Configuration::Validator
|
|
|
443
443
|
end
|
|
444
444
|
end
|
|
445
445
|
|
|
446
|
-
#
|
|
446
|
+
# dash-proxy's --rate-limit is a Float64, so half a request per second is a
|
|
447
447
|
# legal rate. The docs example can only demonstrate one numeric type, and an
|
|
448
448
|
# Integer there would reject 0.5 — so the shape is checked by hand instead.
|
|
449
449
|
def validate_key_override!(key, value)
|
|
@@ -488,7 +488,7 @@ class Dash::Configuration::Validator::Proxy < Dash::Configuration::Validator
|
|
|
488
488
|
end
|
|
489
489
|
|
|
490
490
|
# Rate limiting and the IP allow list are only as correct as the address they
|
|
491
|
-
# key on, so
|
|
491
|
+
# key on, so dash-proxy ties the three settings together with rules that
|
|
492
492
|
# otherwise surface only once the deploy has reached a host.
|
|
493
493
|
def validate_access_control!
|
|
494
494
|
allow_ips = Array(config["allow_ips"])
|
|
@@ -531,7 +531,7 @@ class Dash::Configuration::Validator::Proxy < Dash::Configuration::Validator
|
|
|
531
531
|
error "trusted_proxies has no effect without allow_ips, deny_ips or rate_limit"
|
|
532
532
|
end
|
|
533
533
|
|
|
534
|
-
# Unconditional: even without allow_ips/rate_limit,
|
|
534
|
+
# Unconditional: even without allow_ips/rate_limit, dash-proxy rewrites
|
|
535
535
|
# the client address (and X-Forwarded-For) from this header, so honoring
|
|
536
536
|
# it from untrusted peers is a client-spoofable identity.
|
|
537
537
|
if client_ip["header"].present? && trusted_proxies.empty?
|
|
@@ -549,7 +549,7 @@ class Dash::Configuration::Validator::Proxy < Dash::Configuration::Validator
|
|
|
549
549
|
end
|
|
550
550
|
end
|
|
551
551
|
|
|
552
|
-
#
|
|
552
|
+
# dash-proxy serves the health check path without an address check and
|
|
553
553
|
# without a rate limit so deploys keep working, which makes '/' a hole
|
|
554
554
|
# straight through all three features.
|
|
555
555
|
if (allow_ips.any? || deny_ips.any? || rate_limited) && config.dig("healthcheck", "path") == "/"
|
|
@@ -618,7 +618,7 @@ class Dash::Configuration::Validator::Proxy < Dash::Configuration::Validator
|
|
|
618
618
|
|
|
619
619
|
Array(compress["content_types"]).each do |content_type|
|
|
620
620
|
# A main-type wildcard would match everything the proxy cannot see
|
|
621
|
-
# inside, so
|
|
621
|
+
# inside, so dash-proxy allows a wildcard only in the subtype.
|
|
622
622
|
unless content_type.to_s.match?(%r{\A[^/*\s]+/[^/\s]+\z})
|
|
623
623
|
error "content_types entry '#{content_type}' must be a media type such as text/html or text/*"
|
|
624
624
|
end
|
|
@@ -632,7 +632,7 @@ class Dash::Configuration::Validator::Proxy < Dash::Configuration::Validator
|
|
|
632
632
|
REFUSED_TLS_VERSIONS = %w[ 1.0 1.1 ].freeze
|
|
633
633
|
RUN_TIMEOUTS = %w[ read_header_timeout read_timeout write_timeout idle_timeout shutdown_timeout ].freeze
|
|
634
634
|
|
|
635
|
-
#
|
|
635
|
+
# dash-proxy validates these three in preRun, so a typo does not fail the
|
|
636
636
|
# deploy - it stops the proxy container from booting at all.
|
|
637
637
|
def validate_run_server_options!(run_config)
|
|
638
638
|
with_context("run") do
|
|
@@ -656,7 +656,7 @@ class Dash::Configuration::Validator::Proxy < Dash::Configuration::Validator
|
|
|
656
656
|
end
|
|
657
657
|
end
|
|
658
658
|
|
|
659
|
-
# Accepts the spellings
|
|
659
|
+
# Accepts the spellings dash-proxy's normalizeTLSVersion reduces - a `tls`
|
|
660
660
|
# or `tlsv` prefix, and `_` for `.` - so a config written for upstream is not
|
|
661
661
|
# rejected here and accepted there.
|
|
662
662
|
def validate_min_tls!(value)
|
|
@@ -673,7 +673,7 @@ class Dash::Configuration::Validator::Proxy < Dash::Configuration::Validator
|
|
|
673
673
|
end
|
|
674
674
|
end
|
|
675
675
|
|
|
676
|
-
#
|
|
676
|
+
# dash-proxy reads the cache policy only when --cache is set and ignores it
|
|
677
677
|
# otherwise, so a tuned block with no `enabled` is a cache that silently
|
|
678
678
|
# never caches - this issue's own predicted first support question.
|
|
679
679
|
def validate_cache!(cache)
|
|
@@ -682,12 +682,12 @@ class Dash::Configuration::Validator::Proxy < Dash::Configuration::Validator
|
|
|
682
682
|
with_context("cache") do
|
|
683
683
|
if (orphaned = cache.keys - [ "enabled" ]).any?
|
|
684
684
|
error "#{orphaned.first} has no effect without enabled: true - " \
|
|
685
|
-
"
|
|
685
|
+
"dash-proxy ignores the cache policy entirely when --cache is absent"
|
|
686
686
|
end
|
|
687
687
|
end
|
|
688
688
|
end
|
|
689
689
|
|
|
690
|
-
# An unsupported store is rejected in
|
|
690
|
+
# An unsupported store is rejected in dash-proxy's preRun, which means the
|
|
691
691
|
# proxy container exits at boot rather than a deploy failing. Catch it before
|
|
692
692
|
# the fleet loses its proxy.
|
|
693
693
|
def validate_cache_store!(cache)
|
|
@@ -708,7 +708,7 @@ class Dash::Configuration::Validator::Proxy < Dash::Configuration::Validator
|
|
|
708
708
|
if basic_auth["username"].blank?
|
|
709
709
|
error "Missing username setting (required when basic_auth is set)"
|
|
710
710
|
elsif basic_auth["username"].to_s.include?(":")
|
|
711
|
-
#
|
|
711
|
+
# dash-proxy cuts <username>:<password> at the first colon, so a
|
|
712
712
|
# colon in the username would silently truncate it.
|
|
713
713
|
error "Invalid username: cannot contain a colon"
|
|
714
714
|
end
|
data/lib/dash/configuration.rb
CHANGED
|
@@ -478,7 +478,7 @@ class Dash::Configuration
|
|
|
478
478
|
return true if offenders.empty?
|
|
479
479
|
|
|
480
480
|
warn "Host(s) #{offenders.sort.join(", ")}: proxy_protocol is enabled without proxy_protocol_allow_ips, " \
|
|
481
|
-
"so
|
|
481
|
+
"so dash-proxy honours a PROXY header from any peer that can reach the port. That header sets the client " \
|
|
482
482
|
"address `allow_ips`, `rate_limit` and the access log all use. Name the load balancers you actually run."
|
|
483
483
|
|
|
484
484
|
true
|
|
@@ -503,11 +503,11 @@ class Dash::Configuration
|
|
|
503
503
|
return true if offenders.empty?
|
|
504
504
|
|
|
505
505
|
raise Dash::ConfigurationError, "Role(s) #{offenders.map(&:name).join(", ")}: " \
|
|
506
|
-
"proxy/sleep requires proxy/run/docker_socket -
|
|
506
|
+
"proxy/sleep requires proxy/run/docker_socket - dash-proxy can only stop and start containers " \
|
|
507
507
|
"through the container runtime socket, and it is not mounted into the proxy without it"
|
|
508
508
|
end
|
|
509
509
|
|
|
510
|
-
#
|
|
510
|
+
# dash-proxy resolves a zero max_idle_conns to its default of 100
|
|
511
511
|
# (target_pool.go resolves defaults from zeros), so the one value an
|
|
512
512
|
# operator writes to mean "keep none" is the one value that cannot mean it.
|
|
513
513
|
# Legal, so warn rather than raise.
|
|
@@ -518,14 +518,14 @@ class Dash::Configuration
|
|
|
518
518
|
return true if offenders.empty?
|
|
519
519
|
|
|
520
520
|
warn "Role(s) #{offenders.map(&:name).join(", ")}: target/max_idle_conns: 0 means the proxy default of 100, " \
|
|
521
|
-
"not \"keep none\" -
|
|
521
|
+
"not \"keep none\" - dash-proxy resolves its defaults from zeros. Leave it unset for the default, " \
|
|
522
522
|
"or set 1 for the practical minimum."
|
|
523
523
|
|
|
524
524
|
true
|
|
525
525
|
end
|
|
526
526
|
|
|
527
527
|
# `intercept_errors` discards the app's error body and renders the proxy's own
|
|
528
|
-
# page instead. With no pages to render,
|
|
528
|
+
# page instead. With no pages to render, dash-proxy falls back to a bare
|
|
529
529
|
# plaintext status line — so the app's error page is thrown away and replaced
|
|
530
530
|
# by the words "Bad Gateway". Legal, and almost never the intent.
|
|
531
531
|
def ensure_intercepted_errors_have_pages
|
|
@@ -535,7 +535,7 @@ class Dash::Configuration
|
|
|
535
535
|
return true if offenders.empty?
|
|
536
536
|
|
|
537
537
|
warn "Role(s) #{offenders.map(&:name).join(", ")}: intercept_errors is set but no error_pages_path is " \
|
|
538
|
-
"configured, so
|
|
538
|
+
"configured, so dash-proxy replaces the app's error page with a bare plaintext status line. " \
|
|
539
539
|
"Set `error_pages_path:` to serve your own pages, or remove `intercept_errors` to let the app's through."
|
|
540
540
|
|
|
541
541
|
true
|
|
@@ -543,7 +543,7 @@ class Dash::Configuration
|
|
|
543
543
|
|
|
544
544
|
# Rate limiting and IP deny rules are only as correct as the address they key
|
|
545
545
|
# on. `forward_headers: true` says something sits in front of the proxy, and
|
|
546
|
-
# without `trusted_proxies`
|
|
546
|
+
# without `trusted_proxies` dash-proxy keys on that something's address
|
|
547
547
|
# rather than the client's — so the limiter throttles the whole world as one
|
|
548
548
|
# client (or nobody), and a deny list denies nobody it was written for. Warn
|
|
549
549
|
# rather than raise: the config is legal, just almost certainly not what was
|
|
@@ -568,7 +568,7 @@ class Dash::Configuration
|
|
|
568
568
|
|
|
569
569
|
offenders.each do |role_name, features|
|
|
570
570
|
warn "Role #{role_name}: #{features.join(" and ")} is set with forward_headers, " \
|
|
571
|
-
"but no proxy/client_ip/trusted_proxies.
|
|
571
|
+
"but no proxy/client_ip/trusted_proxies. dash-proxy will key on the address of " \
|
|
572
572
|
"whatever sits in front of it, not on the client's — so the rules apply to every " \
|
|
573
573
|
"visitor as one client, or to none of them. Declare the proxies in front with " \
|
|
574
574
|
"`client_ip: trusted_proxies:`."
|
data/lib/dash/utils.rb
CHANGED
|
@@ -45,7 +45,7 @@ module Dash::Utils
|
|
|
45
45
|
options.flatten.compact
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
-
#
|
|
48
|
+
# dash-proxy takes durations as Go duration strings; deploy.yml takes plain
|
|
49
49
|
# seconds. Zero is a real value (it disables the timeout), so only nil drops out.
|
|
50
50
|
#
|
|
51
51
|
# A value that already carries a unit is passed through untouched. Appending
|
data/lib/dash/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dash
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 4.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mikael Henriksson
|
|
@@ -203,6 +203,7 @@ files:
|
|
|
203
203
|
- lib/dash/cli/main/migrate.rb
|
|
204
204
|
- lib/dash/cli/proxy.rb
|
|
205
205
|
- lib/dash/cli/proxy/drift.rb
|
|
206
|
+
- lib/dash/cli/proxy/legacy_rename.rb
|
|
206
207
|
- lib/dash/cli/proxy/loadbalancer_claim.rb
|
|
207
208
|
- lib/dash/cli/proxy/loadbalancer_reboot.rb
|
|
208
209
|
- lib/dash/cli/proxy/reboot.rb
|