Not a question, but something i could not find online. Maybe it saves someone a lot of time. Took me 2 days to solve.
Case: We have a centralized redis cache in AWS we needed to connect to from Outsystems. This had to be done from a .Net extension (kind of plugin). We installed a local Redis to develop with and had it up and running with Stackexchange.Redis (v 2.6.104) within an hour (mostly circumventing our network setup presenting certificate issues slowing down the install of Redis). With a quick qonsole application we were able to connect to Redis. We then transferred to and our Outsystems environment (outsystems 11, .net 4.7.2) and the central Redis that had In transit encryption enabled and required:
We were given a username and password (ACL?) to connect. The symptom: Timeouts!
The errormessage would read: Last Connection Exception: ProtocolFailure...
We could however connect from the Outsystems server (where the code was runnning, also AWS instance, security groups correctly configured) through telnet to the Redis server on the specific port. SO there had to be some other problem. Spent two days hunting SSL/TLS issues, turned out to be the user:
We did not have ECHO permission (INFO turned out not to be a big problem). WHen they added thos it suddenyl worked.
To be complete (maybe for other Outsystems developers), the code (server certificate validation fails so we took the lazy route for now just ignoring that):
The redis connector:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using StackExchange.Redis;
namespace OutSystems.NssRedisConnector
{
internal class RedisServer
{
private static string LocalHostUrl { get; set; }
private static int LocalPort { get; set; }
private static Boolean SslOn { get; set; }
private static string UserName { get; set; }
private static string Password { get; set; }
public class RedisStore
{
private static readonly Lazy<ConnectionMultiplexer> LazyConnection;
static RedisStore()
{
var configurationOptions = ConfigurationOptions.Parse(LocalHostUrl + ":" + LocalPort);
configurationOptions.AbortOnConnectFail = false;
configurationOptions.AsyncTimeout = 10000;
configurationOptions.SyncTimeout = 10000;
configurationOptions.ConnectTimeout = 10000;
if (SslOn)
{
configurationOptions.User = UserName;
configurationOptions.Password = Password;
configurationOptions.Ssl = SslOn;
configurationOptions.SslProtocols = SslProtocols.Tls12;
configurationOptions.CertificateValidation += ValidateServerCertificate;
}
LazyConnection = new Lazy<ConnectionMultiplexer>(() => ConnectionMultiplexer.Connect(configurationOptions));
}
public static ConnectionMultiplexer Connection => LazyConnection.Value;
public static IDatabase RedisCache => Connection.GetDatabase(0);
public static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
return true;
return true;
}
}
public void InitConnection(string localHostUrl, int localPort, Boolean sslOn, string userName, string password)
{
LocalHostUrl = localHostUrl;
LocalPort = localPort;
SslOn = sslOn;
UserName = userName;
Password = password;
}
public string ReadKey(string keyName, out bool exists)
{
exists = false;
try
{
var redis = RedisStore.RedisCache;
string returnValue = "";
var key = redis.StringGet(keyName);
exists = (!key.IsNull && key.Length()>0);
if (exists) {
returnValue = key.ToString();
}
else
{
returnValue = "key not found";
}
return returnValue;
}
catch (Exception e)
{
return "Error found. Message:" + e.Message + "\nStack:" + e.StackTrace ;
}
}
}
}
The Outsystems connector:
using System;
using System.Collections;
using System.Data;
using OutSystems.HubEdition.RuntimePlatform;
using OutSystems.RuntimePublic.Db;
namespace OutSystems.NssRedisConnector {
public class CssRedisConnector: IssRedisConnector {
/// <summary>
/// Get string value from redis cache
/// </summary>
/// <param name="ssRedisConnectionSettings">Connection settings</param>
/// <param name="ssRedisKey">Key to look for</param>
/// <param name="ssStringValue">The string vlaue for the key</param>
/// <param name="ssResult">True = OK</param>
/// <param name="ssErrorMessage">ErrorMessage logged</param>
public void MssGetStringValue(RCRedisConnectionSettingsRecord ssRedisConnectionSettings, string ssRedisKey, out string ssStringValue, out bool ssResult, out string ssErrorMessage) {
ssStringValue = "";
ssResult = false;
ssErrorMessage = "";
ssStringValue = "";
try
{
var redisServer = new RedisServer();
redisServer.InitConnection(ssRedisConnectionSettings.ssSTRedisConnectionSettings.ssHost, ssRedisConnectionSettings.ssSTRedisConnectionSettings.ssPort, ssRedisConnectionSettings.ssSTRedisConnectionSettings.ssSslOn, ssRedisConnectionSettings.ssSTRedisConnectionSettings.ssUsername, ssRedisConnectionSettings.ssSTRedisConnectionSettings.ssPassword);
ssStringValue = redisServer.ReadKey(ssRedisKey, out ssResult);
ssErrorMessage = ssResult ? "" : "Key not found";
}
catch (Exception e)
{
ssErrorMessage = "Error found. Message:" + e.Message + "\nStack:" + e.StackTrace;
ssResult = false;
}
} // MssGetStringValue
} // CssRedisConnector
} // OutSystems.NssRedisConnector
Still have to polish the code a bit, but wanted to share this for anyone having trouble connecting to AWS over SSL.
We also, in the process tried NRedisSTack and ServiceStack.Redis. In the end they all gave the same problem, so we reverted back to Stackexchange.Redis