Add a hashed_access_tokens config option (env RS_HASHED_ACCESS_TOKENS) that hashes the presented bearer token and looks up the SHA256 digest first, falling back to the plaintext key during the transition. When disabled (the default), the lookup is unchanged, so deployments not using hashed tokens pay no extra Redis round-trips. Enable the flag here first, then migrate akkounts to re-key the Redis authorizations (kosmos/akkounts#259); the plaintext fallback keeps existing tokens working throughout. Refs kosmos/akkounts#259
This commit is contained in:
1 parent
0cf5926b65
commit
007e12d12e
4 files changed
+71
-1
No files matched your search
@@ -50,6 +50,19 @@ Running the app:
|
||||
|
||||
_TODO document options_
|
||||
|
||||
### Hashed access tokens
|
||||
|
||||
By default Liquor Cabinet looks up remoteStorage authorizations by the plaintext
|
||||
bearer token (`authorizations:<user>:<token>`). Set `RS_HASHED_ACCESS_TOKENS=true`
|
||||
to look up the SHA256 digest first, falling back to the plaintext key when no
|
||||
digest matches.
|
||||
|
||||
This is a coordinated rollout with akkounts, which owns the Redis keys: enable
|
||||
the flag here first (the plaintext fallback keeps existing tokens working), then
|
||||
migrate akkounts so it re-keys the authorizations to digests. Keep the fallback
|
||||
enabled until the re-key has been verified; only then can the plaintext lookup
|
||||
be retired.
|
||||
|
||||
## Contributing
|
||||
|
||||
We love pull requests. If you want to submit a patch:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
development: &defaults
|
||||
maintenance: false
|
||||
hashed_access_tokens: <%= ENV["RS_HASHED_ACCESS_TOKENS"] == "true" %>
|
||||
redis:
|
||||
host: <%= ENV["REDIS_HOST"] || "localhost" %>
|
||||
port: <%= ENV["REDIS_PORT"] || 6379 %>
|
||||
|
||||
@@ -5,6 +5,7 @@ require "active_support/core_ext/time/conversions"
|
||||
require "active_support/core_ext/numeric/time"
|
||||
require "active_support/core_ext/hash"
|
||||
require "redis"
|
||||
require "digest"
|
||||
require "digest/md5"
|
||||
|
||||
module RemoteStorage
|
||||
@@ -26,7 +27,7 @@ module RemoteStorage
|
||||
|
||||
server.halt 401, "Unauthorized" if token.nil? || token.empty?
|
||||
|
||||
authorizations = redis.smembers("authorizations:#{user}:#{token}")
|
||||
authorizations = authorizations_for(user, token)
|
||||
permission = directory_permission(authorizations, directory)
|
||||
|
||||
server.halt 401, "Unauthorized" unless permission
|
||||
@@ -233,6 +234,15 @@ module RemoteStorage
|
||||
end
|
||||
end
|
||||
|
||||
def authorizations_for(user, token)
|
||||
if settings.respond_to?(:hashed_access_tokens) && settings.hashed_access_tokens
|
||||
digest = Digest::SHA256.hexdigest(token)
|
||||
permissions = redis.smembers("authorizations:#{user}:#{digest}")
|
||||
return permissions unless permissions.empty?
|
||||
end
|
||||
redis.smembers("authorizations:#{user}:#{token}")
|
||||
end
|
||||
|
||||
def directory_permission(authorizations, directory)
|
||||
authorizations = authorizations.map do |auth|
|
||||
auth.index(":") ? auth.split(":") : [auth, "rw"]
|
||||
|
||||
@@ -704,4 +704,50 @@ JFIFddDuckyA␍⎺␉␊␍
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "authorization with hashed access tokens" do
|
||||
before do
|
||||
redis.del "authorizations:phil:#{Digest::SHA256.hexdigest("amarillo")}"
|
||||
redis.del "authorizations:phil:amarillo"
|
||||
purge_redis
|
||||
header "Authorization", "Bearer amarillo"
|
||||
end
|
||||
|
||||
after do
|
||||
app.set :hashed_access_tokens, false
|
||||
end
|
||||
|
||||
context "when hashed access tokens are enabled" do
|
||||
before do
|
||||
app.set :hashed_access_tokens, true
|
||||
end
|
||||
|
||||
it "authorizes using the digest key" do
|
||||
redis.sadd "authorizations:phil:#{Digest::SHA256.hexdigest("amarillo")}", [":rw"]
|
||||
|
||||
put "/phil/food/aguacate", "si"
|
||||
|
||||
_(last_response.status).must_equal 201
|
||||
end
|
||||
|
||||
it "falls back to the plaintext key during the rollout" do
|
||||
redis.sadd "authorizations:phil:amarillo", [":rw"]
|
||||
|
||||
put "/phil/food/aguacate", "si"
|
||||
|
||||
_(last_response.status).must_equal 201
|
||||
end
|
||||
end
|
||||
|
||||
context "when hashed access tokens are disabled" do
|
||||
it "does not authorize a digest-only key" do
|
||||
redis.sadd "authorizations:phil:#{Digest::SHA256.hexdigest("amarillo")}", [":rw"]
|
||||
|
||||
put "/phil/food/aguacate", "si"
|
||||
|
||||
_(last_response.status).must_equal 401
|
||||
_(last_response.body).must_equal "Unauthorized"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in new issue
Block a user