safer_redis 1.0.0 → 1.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d0c31676a608f111b89e5de3b5ec1ba3cc46b276afb362eee6aebd1a43120cfb
4
- data.tar.gz: 0bc66dde14999d62732ade01b0cf36f645cff45911eff4c818ab0f696bbe9bee
3
+ metadata.gz: 790845a432c5b85fa5261a8cbca3170058adcc00352f79ae3a1d211e5027b7dc
4
+ data.tar.gz: 9bacdc397e449211800e19ccd0fb6e99089215db5fbd52593d452ec8b73f44d8
5
5
  SHA512:
6
- metadata.gz: a854dda2a09c6dd52330afc9de66895eba48447767da9e0cf53bc7a9a7c3dd7e64ca2c1ea4c590f15be29d4bfc53bf7751b9335a09150b85e34d932586f42de6
7
- data.tar.gz: 11d94efde9334fd9e242ebd2ec7dcd6ff1a320731cda4f1ec537ca333f642d933b9e87b2cbf0cc16171ecb1c34294570897fa191a60bda823de0c107d3622e5f
6
+ metadata.gz: f0fa1a49e44253388abaa4483ad9ceeae25b65386c1ed5ecff2820075905245d45684aabd0dfb475041739ec1b938ddcf125e3546d52b22d1380e30793b823f6
7
+ data.tar.gz: ee4c6847111eb19a6e2d836e557826a3cf9a4a14219dfcb9734e26b9728d9f8649960aa0071c2a2de68f83b421c339713e92291d52b796f8926e3e15cc118b54
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.2.0] - 2023-11-14
4
+
5
+ - Allow `DEL` (`@slow`) if `lazyfree-lazy-user-del` server option is enabled.
6
+
7
+ ## [1.1.0] - 2023-01-07
8
+
9
+ - Allow `@slow` if it's only `O(1)` complexity, e.g. the `SET` (string) command.
10
+
3
11
  ## [1.0.0] - 2022-12-02
4
12
 
5
13
  - Initial release
data/Gemfile.lock CHANGED
@@ -1,37 +1,39 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- safer_redis (0.1.0)
4
+ safer_redis (1.1.0)
5
5
  redis (>= 4.6.0)
6
6
  zeitwerk
7
7
 
8
8
  GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
- connection_pool (2.3.0)
11
+ connection_pool (2.4.1)
12
12
  diff-lcs (1.5.0)
13
- rake (13.0.6)
14
- redis (5.0.5)
15
- redis-client (>= 0.9.0)
16
- redis-client (0.11.2)
13
+ rake (13.1.0)
14
+ redis (5.0.8)
15
+ redis-client (>= 0.17.0)
16
+ redis-client (0.18.0)
17
17
  connection_pool
18
18
  rspec (3.12.0)
19
19
  rspec-core (~> 3.12.0)
20
20
  rspec-expectations (~> 3.12.0)
21
21
  rspec-mocks (~> 3.12.0)
22
- rspec-core (3.12.0)
22
+ rspec-core (3.12.2)
23
23
  rspec-support (~> 3.12.0)
24
- rspec-expectations (3.12.0)
24
+ rspec-expectations (3.12.3)
25
25
  diff-lcs (>= 1.2.0, < 2.0)
26
26
  rspec-support (~> 3.12.0)
27
- rspec-mocks (3.12.0)
27
+ rspec-mocks (3.12.6)
28
28
  diff-lcs (>= 1.2.0, < 2.0)
29
29
  rspec-support (~> 3.12.0)
30
- rspec-support (3.12.0)
31
- zeitwerk (2.6.6)
30
+ rspec-support (3.12.1)
31
+ zeitwerk (2.6.12)
32
32
 
33
33
  PLATFORMS
34
34
  x86_64-darwin-21
35
+ x86_64-darwin-22
36
+ x86_64-darwin-23
35
37
 
36
38
  DEPENDENCIES
37
39
  rake (~> 13.0)
data/README.md CHANGED
@@ -4,9 +4,16 @@ SaferRedis wraps a Redis connection, and warns you before letting through comman
4
4
 
5
5
  Inspiration is taken from https://github.com/ankane/strong_migrations which provides similar guard-rails for potentially dangerous database migrations.
6
6
 
7
+ ## Special cases
8
+
9
+ Commands tagged as `@slow` but with only O(1) complexity are not considered dangerous. This includes
10
+ e.g. the extremely commonly used `SET` command.
11
+
12
+ The [`DEL`](https://redis.io/commands/del/) command (`@slow`) is treated as [`UNLINK`](https://redis.io/commands/unlink/) (`@fast`) when the `lazyfree-lazy-user-del` server option is set.
13
+
7
14
  ## Limitations
8
15
 
9
- Currently SaferRedis works with the [`redis` gem](https://rubygems.org/gems/redis) from https://github.com/redis/redis-rb (regardless of which connection adapter is being used, e.g. [`hiredis`](https://rubygems.org/gems/hiredis)) by hooking into the private `#send_command` method which was introduced in v4.6.0. This isn't a stable API, so other interception strategies will be considered and may be added in future. Some Redis connectors/drivers suport middleware, but others don't.
16
+ Currently SaferRedis works with the [`redis` gem](https://rubygems.org/gems/redis) from https://github.com/redis/redis-rb (regardless of which connection adapter is being used, e.g. [`hiredis`](https://rubygems.org/gems/hiredis)) by hooking into the private `#send_command` method which was introduced in v4.6.0. This isn't a stable API, so other interception strategies will be considered and may be added in future. Some Redis connectors/drivers support middleware, but others don't.
10
17
 
11
18
  Some commands are documented at the subcommand level as multiple words (e.g. `CLIENT LIST`). SaferRedis currently only recognises commands documented at their top-level single word (e.g. `DEL`). Support for multi-word commands will be considered and may be added in future.
12
19
 
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module SaferRedis
6
+ class Assessor
7
+ def self.assess!(doc, redis:)
8
+ # DEL becomes UNLINK if lazyfree-lazy-user-del is set
9
+ # https://github.com/redis/redis/pull/6243
10
+ # https://redis.io/docs/management/config-file/
11
+ if doc.name == "DEL" && config_get("lazyfree-lazy-user-del", redis:) == "yes"
12
+ doc = SaferRedis::CommandDoc.new("UNLINK")
13
+ end
14
+
15
+ if doc.dangerous?
16
+ # Anything tagged @dangerous is… dangerous
17
+ raise SaferRedis::Danger.new(doc)
18
+
19
+ elsif doc.slow? && doc.complexity != "O(1)"
20
+ # Anything tagged @slow might be dangerous, but we'll let through O(1)
21
+ # complexity commands e.g. SET
22
+ raise SaferRedis::Danger.new(doc)
23
+ end
24
+ end
25
+
26
+ def self.config_get(option, redis:)
27
+ SaferRedis.really { redis.config(:get, option)[option] }
28
+ end
29
+ end
30
+ end
@@ -52,6 +52,10 @@ module SaferRedis
52
52
  command.fetch("complexity", nil)
53
53
  end
54
54
 
55
+ def suggestion
56
+ Suggestion.for_command(name)
57
+ end
58
+
55
59
  private
56
60
 
57
61
  def command
@@ -15,6 +15,13 @@ module SaferRedis
15
15
  If you're sure this is okay, you can try again within `SaferRedis.really { ... }`
16
16
  MESSAGE
17
17
 
18
+ if doc.suggestion
19
+ message = <<~MESSAGE
20
+ #{message}
21
+ Suggestion: #{doc.suggestion.description}
22
+ MESSAGE
23
+ end
24
+
18
25
  super(message)
19
26
  end
20
27
  end
@@ -4,7 +4,10 @@ module SaferRedis
4
4
  module Interceptor
5
5
  def send_command(command, &block)
6
6
  if SaferRedis.active?
7
- SaferRedis.assess!(SaferRedis::CommandDoc.from_command_array(command))
7
+ SaferRedis::Assessor.assess!(
8
+ SaferRedis::CommandDoc.from_command_array(command),
9
+ redis: self,
10
+ )
8
11
  end
9
12
 
10
13
  super
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SaferRedis
4
+ class Suggestion
5
+ attr_reader :command, :description
6
+
7
+ def initialize(command, description)
8
+ @command = command
9
+ @description = description
10
+ end
11
+
12
+ def self.for_command(command)
13
+ case command
14
+ when "DEL"
15
+ new(command, <<~END)
16
+ Consider using the non-blocking UNLINK command instead to delete the key on a \
17
+ background thread, or set the lazyfree-lazy-user-del server option
18
+ END
19
+ else
20
+ nil
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SaferRedis
4
- VERSION = "1.0.0"
4
+ VERSION = "1.2.0"
5
5
  end
data/lib/safer_redis.rb CHANGED
@@ -26,10 +26,4 @@ module SaferRedis
26
26
  ensure
27
27
  @active = was
28
28
  end
29
-
30
- def self.assess!(doc)
31
- if doc.dangerous? || doc.slow?
32
- raise SaferRedis::Danger.new(doc)
33
- end
34
- end
35
29
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: safer_redis
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul Annesley
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-12-02 00:00:00.000000000 Z
11
+ date: 2023-11-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -56,11 +56,12 @@ files:
56
56
  - Rakefile
57
57
  - data/redis-doc/commands.json
58
58
  - lib/safer_redis.rb
59
+ - lib/safer_redis/assessor.rb
59
60
  - lib/safer_redis/command_doc.rb
60
61
  - lib/safer_redis/danger.rb
61
62
  - lib/safer_redis/interceptor.rb
63
+ - lib/safer_redis/suggestion.rb
62
64
  - lib/safer_redis/version.rb
63
- - safer_redis.gemspec
64
65
  - sig/safer_redis.rbs
65
66
  homepage: https://github.com/buildkite/safer_redis
66
67
  licenses:
@@ -77,14 +78,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
77
78
  requirements:
78
79
  - - ">="
79
80
  - !ruby/object:Gem::Version
80
- version: 2.6.0
81
+ version: 3.2.0
81
82
  required_rubygems_version: !ruby/object:Gem::Requirement
82
83
  requirements:
83
84
  - - ">="
84
85
  - !ruby/object:Gem::Version
85
86
  version: '0'
86
87
  requirements: []
87
- rubygems_version: 3.1.6
88
+ rubygems_version: 3.4.22
88
89
  signing_key:
89
90
  specification_version: 4
90
91
  summary: Catch unsafe Redis commands in production
data/safer_redis.gemspec DELETED
@@ -1,35 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "lib/safer_redis/version"
4
-
5
- Gem::Specification.new do |spec|
6
- spec.name = "safer_redis"
7
- spec.version = SaferRedis::VERSION
8
- spec.authors = ["Paul Annesley"]
9
- spec.email = ["paul@annesley.cc"]
10
-
11
- spec.summary = "Catch unsafe Redis commands in production"
12
- spec.description = "SaferRedis warns you before letting through commands that could impact production availability by being marked `@slow` or `@dangerous` in the Redis documentation"
13
- spec.homepage = "https://github.com/buildkite/safer_redis"
14
- spec.license = "MIT"
15
- spec.required_ruby_version = ">= 2.6.0"
16
-
17
- spec.metadata["homepage_uri"] = spec.homepage
18
- spec.metadata["source_code_uri"] = "https://github.com/buildkite/safer_redis"
19
- spec.metadata["changelog_uri"] = "https://github.com/buildkite/safer_redis/blob/main/CHANGELOG.md"
20
-
21
- # Specify which files should be added to the gem when it is released.
22
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
- spec.files = Dir.chdir(__dir__) do
24
- `git ls-files -z`.split("\x0").reject do |f|
25
- (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
26
- end
27
- end
28
- spec.require_paths = ["lib"]
29
-
30
- # The only strategy implemented so far is intercepting the private Redis#send_command method,
31
- # which was introduced in v4.6.0 via https://github.com/redis/redis-rb/pull/1058 and remains
32
- # in the latest release (v5.0.5) at time of writing.
33
- spec.add_dependency "redis", ">= 4.6.0"
34
- spec.add_dependency "zeitwerk"
35
- end