Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow basic auth for Redis clients #1

Open
trevorturk opened this issue Apr 11, 2024 · 9 comments
Open

Allow basic auth for Redis clients #1

trevorturk opened this issue Apr 11, 2024 · 9 comments

Comments

@trevorturk
Copy link

trevorturk commented Apr 11, 2024

I ran into an issue that seems similar to socketry/async-redis#35

I have a URL like so:

rediss://:PASSWORD@redis-10954.c246.us-east-1-4.ec2.cloud.redislabs.com:10954

...but in lib/async/job/backend/redis.rb I can't include protocol as in the example.

My config is a bit convoluted, but I believe otherwise working:

    # used for Sidekiq
    config.redis_ssl_params = {
      cert_store: OpenSSL::X509::Store.new.tap { |store|
        store.add_file(Rails.root.join("config/redis.pem").to_s)
      },
      cert: OpenSSL::X509::Certificate.new(File.read(
        Rails.root.join("config/redis.crt")
      )),
      key: OpenSSL::PKey::RSA.new(
        Rails.application.credentials.services.redis.private_key
      ),
      verify_mode: OpenSSL::SSL::VERIFY_PEER
    }

    # building for use in async-job
    config.redis_ssl_context = OpenSSL::SSL::SSLContext.new.tap do |context|
      context.cert_store = config.redis_ssl_params[:cert_store]
      context.cert = config.redis_ssl_params[:cert]
      context.key = config.redis_ssl_params[:key]
      context.verify_mode = config.redis_ssl_params[:verify_mode]
    end

  config.async_job.backend_for "redis_remote" do
		uri = URI(ENV["REDIS_ASYNC_JOB_URL"])
    queue Async::Job::Backend::Redis, endpoint: Async::IO::SSLEndpoint.new(Async::IO::Endpoint.tcp(uri.hostname, uri.port), ssl_context: Rails.configuration.redis_ssl_context)
	end

Perhaps there's an easier way to configure, or maybe we can figure out how to get it working and provide an integration guide, but in any case I'd need to be able to get the password passed through somehow.

@ioquatix
Copy link
Member

Ahh, I see, yeah, we need to provide more flexibility to configure the connection.

@ioquatix
Copy link
Member

We should probably include a default wrapper for setting auth, database, etc, in async-redis.

@trevorturk
Copy link
Author

Yeah I'll give that client_class fix a try and report back, but I think we can probably eventually improve the ergonomics a bit for what I imagine is a fairly common setup like mine with Redislabs.

@trevorturk
Copy link
Author

Apologies for my confusion here, but I'm not seeing how to use the client_class option to fix this issue. If you see this example: https://github.com/socketry/async-redis/blob/main/examples/auth/protocol.rb#L27, I need to pass in the protocol, so I think I'd need to pass in the whole client, not just the class?

@ioquatix
Copy link
Member

Ah got it, I think I misunderstood, let me update the PR.

@ioquatix
Copy link
Member

The latest commit lets you override the redis client protocol - I think this should be what you want.

@trevorturk
Copy link
Author

trevorturk commented Apr 15, 2024

Confirmed allowing overriding the protocol works for me! So, I think #2 is good to merge.

It's worth noting this requires a fair amount of setup and perhaps we can improve the developer experience.

Full working example below:

Rails.application.configure do
  redis_ssl_context = OpenSSL::SSL::SSLContext.new.tap do |context|
    context.cert_store = OpenSSL::X509::Store.new.tap { |store|
      store.add_file(Rails.root.join("config/redis.pem").to_s)
    }
    context.cert = OpenSSL::X509::Certificate.new(File.read(
      Rails.root.join("config/redis.crt")
    ))
    context.key = OpenSSL::PKey::RSA.new(
      Rails.application.credentials.services.redis.private_key
    )
    context.verify_mode = OpenSSL::SSL::VERIFY_PEER
  end

  config.async_job.backend_for "redis_remote" do
    uri = URI(ENV["REDIS_ASYNC_JOB_URL"])
    tcp_endpoint = Async::IO::Endpoint.tcp(uri.hostname, uri.port)
    ssl_endpoint = Async::IO::SSLEndpoint.new(tcp_endpoint, ssl_context: redis_ssl_context)
    protocol = AuthenticatedRESP2.new([uri.password])
    queue Async::Job::Backend::Redis, endpoint: ssl_endpoint, protocol: protocol
  end
end

class AuthenticatedRESP2
  def initialize(credentials, protocol: Async::Redis::Protocol::RESP2)
    @credentials = credentials
    @protocol = protocol
  end

  def client(stream)
    client = @protocol.client(stream)

    client.write_request(["AUTH", *@credentials])
    client.read_response

    return client
  end
end

One quick thing might be adding a note to https://github.com/socketry/async-redis/blob/main/examples/auth/protocol.rb showing that if you only have a password (and not a username) as in my example above, you just need to pass in the password, for example:

client = Async::Redis::Client.new(endpoint, protocol: AuthenticatedRESP2.new(["password"]))

Let me know if you'd like any help with the documentation etc, but tldr things are working for me now, thank you!

@ioquatix
Copy link
Member

What about adding AuthenticatedRESP2 to the async-redis gem?

@trevorturk
Copy link
Author

trevorturk commented Apr 15, 2024

Yeah, I'm not sure where to draw the line, but I suppose I'm accustomed to being able to pass in a URL and just have it work, as per: https://github.com/redis/redis-rb?tab=readme-ov-file#getting-started

redis = Redis.new(url: "redis://:p4ssw0rd@10.0.1.1:6380/15")

...but it seems like we're comfortable with being a bit lower level here. Honestly if there's documentation I suppose it's fine for now -- perhaps down the road we can make it all easier?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants