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

Is there a way to use client certificates with SolrNet.Cloud? #591

Open
kostas-kelgeorgiadis opened this issue Mar 10, 2022 · 6 comments · May be fixed by #604
Open

Is there a way to use client certificates with SolrNet.Cloud? #591

kostas-kelgeorgiadis opened this issue Mar 10, 2022 · 6 comments · May be fixed by #604

Comments

@kostas-kelgeorgiadis
Copy link

kostas-kelgeorgiadis commented Mar 10, 2022

I was using client certificates with SolrNet before, and I am thinking of moving to SolrNet.Cloud now.
However, I see that there is no method for setting an ISolrConnection for the library.

SolrNet had this method:

var postConnection = new PostSolrConnection(connection, solrServerUrl);
Startup.Init<MyCollectionDocument>(postConnection);

...where you could customize the connection.

For SolrNet.Cloud there is only this one, that does not accept any ISolrConnection instance:

var cloudStateProvider = new SolrNet.Cloud.ZooKeeperClient.SolrCloudStateProvider(settings.SolrZookeeperUrl, settings.SolrTimeoutInSeconds);
await SolrNet.Cloud.Startup.InitAsync<MyCollectionDocument>(cloudStateProvider: cloudStateProvider, collectionName: CollectionName, isPostConnection: true);

Am I missing something?
Or this is a missing feature?

Update:
Hm... I see that SolrNet.Cloud depends on SolrNet, so perhaps the old method is used for cloud as well...
But calling both methods from above tries to register ISolrBasicOperations twice, resulting in error:

System.ApplicationException: Key 'SolrNet.ISolrBasicOperations`1[[Admincontrol.Common.FileService.SolrDocument, Admincontrol.Common.FileService, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]' already registered in container
   at SolrNet.Utils.Container.Register(String key, Type serviceType, Converter`2 factory)
   at SolrNet.Cloud.Startup.<InitAsync>d__6`1.MoveNext()
@kostas-kelgeorgiadis
Copy link
Author

I ended up overriding the ISolrOperationsProvider in my DI Container after the call to InitAsync().
Not the most beautiful thing, but seems to work...

@kostas-kelgeorgiadis
Copy link
Author

kostas-kelgeorgiadis commented Mar 15, 2022

Also, the docs here don't seem to be up-to-date:
https://github.com/SolrNet/SolrNet/blob/master/Documentation/Basic-usage-cloud.md
In the latest version of the lib, I see that SolrNet.Startup.Init<T>() does not accept a SolrCloudStateProvider and you apparently have to call SolrNet.Cloud.Startup.InitAsync() for initialization.

mausch added a commit that referenced this issue Jun 4, 2022
@mausch
Copy link
Member

mausch commented Jun 4, 2022

Fixed docs in e602ed2

@ancailliau
Copy link
Contributor

@kostas-kelgeorgiadis Could you share the override you did, I'm currently reviewing this part of the code and I might include a proper fix in a later PR.

@kostas-kelgeorgiadis
Copy link
Author

kostas-kelgeorgiadis commented Jul 11, 2022

Sure @ancailliau .
Here you go (there is some extra code that prevents deadlocks in web applications):

var cert = LoadCertificate(settings);

using (NoSynchronizationContextScope.Enter())
{
	var container = SolrNet.Cloud.Startup.Container;

	container.RemoveAll<SolrNet.Cloud.ISolrOperationsProvider>();
	var cloudStateProvider = new SolrNet.Cloud.ZooKeeperClient.SolrCloudStateProvider(settings.ZookeeperConnectionString, settings.TimeoutInSeconds);
	SolrNet.Cloud.Startup.InitAsync<T>(cloudStateProvider: cloudStateProvider, collectionName: settings.CollectionName, isPostConnection: true)
		.ConfigureAwait(false)
		.GetAwaiter()
		.GetResult();

	var requestFactory = cert is null
		? new HttpWebRequestFactory() as IHttpWebRequestFactory
		: new CertificateHttpWebRequestFactory(cert);
	container.RemoveAll<SolrNet.Cloud.ISolrOperationsProvider>();
	container.Register<SolrNet.Cloud.ISolrOperationsProvider>(c => new MySolrOperationsProvider(requestFactory, settings.TimeoutInSeconds));
}

public class MySolrOperationsProvider : SolrNet.Cloud.ISolrOperationsProvider
{
    private readonly IHttpWebRequestFactory _requestFactory;
    private readonly int _timeOutSeconds;

    public MySolrOperationsProvider(IHttpWebRequestFactory requestFactory, int timeOutSeconds)
    {
        _requestFactory = requestFactory;
        _timeOutSeconds = timeOutSeconds;
    }

    public ISolrBasicOperations<T> GetBasicOperations<T>(string url, bool isPostConnection = false)
    {
        var connection = new SolrNet.Impl.SolrConnection(url);
        connection.HttpWebRequestFactory = _requestFactory;
        connection.Timeout = (int)TimeSpan.FromSeconds(_timeOutSeconds).TotalMilliseconds;

        if (!isPostConnection)
        {
            return SolrNet.SolrNet.GetBasicServer<T>(connection);
        }
        else
        {
            var postConnection = new SolrNet.Impl.PostSolrConnection(connection, url);
            postConnection.HttpWebRequestFactory = _requestFactory;
            return SolrNet.SolrNet.GetBasicServer<T>(postConnection);
        }
    }

    public ISolrOperations<T> GetOperations<T>(string url, bool isPostConnection = false)
    {
        var connection = new SolrNet.Impl.SolrConnection(url);
        connection.HttpWebRequestFactory = _requestFactory;
        connection.Timeout = (int)TimeSpan.FromSeconds(_timeOutSeconds).TotalMilliseconds;

        if (!isPostConnection)
        {
            return SolrNet.SolrNet.GetServer<T>(connection);
        }
        else
        {
            var postConnection = new SolrNet.Impl.PostSolrConnection(connection, url);
            postConnection.HttpWebRequestFactory = _requestFactory;
            return SolrNet.SolrNet.GetServer<T>(postConnection);
        }
    }
}

public static class NoSynchronizationContextScope
{
    public static IDisposable Enter()
    {
        var context = SynchronizationContext.Current;
        SynchronizationContext.SetSynchronizationContext(null);
        return new Restorer(context);
    }

    private struct Restorer : IDisposable
    {
        private readonly SynchronizationContext _synchronizationContext;

        public Restorer(SynchronizationContext synchronizationContext)
        {
            _synchronizationContext = synchronizationContext;
        }

        public void Dispose()
        {
            SynchronizationContext.SetSynchronizationContext(_synchronizationContext);
        }
    }
}

public class CertificateHttpWebRequestFactory : IHttpWebRequestFactory
{
    private readonly X509Certificate _certificate;

    public CertificateHttpWebRequestFactory(X509Certificate certificate)
    {
        _certificate = certificate;
    }

    public IHttpWebRequest Create(Uri url)
    {
        var req = (HttpWebRequest)WebRequest.Create(url);
        req.ClientCertificates.Add(_certificate);
        return new HttpWebRequestAdapter(req);
    }

    public IHttpWebRequest Create(string url)
    {
        return Create(new Uri(url));
    }
}

However, I think I've found an extra problem, which I am not sure if it is a problem of my additions or a problem in the library.
It appears that registering and resolving more than one types of documents does not work as I was expecting.
So, if you call the 1st snippet of code for different types of <T>, it fails to register and resolve them both.
It only works with one document type.

I hope this helps.

@kostas-kelgeorgiadis
Copy link
Author

kostas-kelgeorgiadis commented Sep 16, 2022

Added a PR for this, with some extra improvements.

Usage:

        var cert = LoadCertificate(certificatePath, certificatePass);
        var requestFactory = cert is null
            ? new HttpWebRequestFactory() as IHttpWebRequestFactory
            : new ClientCertificateHttpWebRequestFactory(cert);

        var cloudStateProvider = new SolrNet.Cloud.ZooKeeperClient.SolrCloudStateProvider(zkConnectionString, zooKeeperTimeoutMs: timeoutSeconds * 1000);
        SolrNet.Cloud.Startup.InitAsync<T>(
            cloudStateProvider: cloudStateProvider,
            collectionName: collectionName,
            isPostConnection: true,
            solrOperationsProvider: new ClientCertificateSolrOperationsProvider(requestFactory, timeoutSeconds * 1000))
                .ConfigureAwait(false)
                .GetAwaiter()
                .GetResult();

Also added a new method for resetting the DI, in case the previous initialization has failed:

        SolrNet.Cloud.Startup.Reset();

I don't know if you feel like merging this PR, @mausch, if all the tests pass.

kostas-kelgeorgiadis added a commit to kostas-kelgeorgiadis/SolrNet that referenced this issue Sep 19, 2022
kostas-kelgeorgiadis added a commit to kostas-kelgeorgiadis/SolrNet that referenced this issue Sep 20, 2022
…dding ability to validate the remote server certificates by code.
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

Successfully merging a pull request may close this issue.

3 participants