Hello,

Yes, with saml2 or cas, sogo still need a user source as the sso will only be 
used to validate and fetch the mail of the user but for all the rest, like the 
cn, it needs a ldap or sql user source.

So, everything is working now, or you still have issues? May I ask how you 
configured your imap server?

Quentin

-----Original Message-----
From: [email protected] <[email protected]> On Behalf Of Anton Hvornum
Sent: samedi 10 août 2024 20:38
To: [email protected]
Subject: Re: [SOGo] SOGo with SAML2 against KeyCloak 23+ causes "Tried to add 
nil value for key 'login' to dictionary INFO"

On 8/10/24 18:20, Anton Hvornum wrote:
> On 8/10/24 12:09, Anton Hvornum wrote:
>> On 8/10/24 02:27, Anton Hvornum wrote:
>>> On 8/9/24 22:59, Anton Hvornum ([email protected]) wrote:
>>>> I've attempted to get SAML login working using the following guide: 
>>>> https://bluntlab.space/posts/sogo-saml-keycloak/
>>>> Currently I'm running apache, memcached, sogo and postgresql in a 
>>>> docker compose environment while keycloak, postfix and dovecot are 
>>>> running externally.
>>>>
>>>> This is an excerpt from sogo.conf:
>>>>
>>>> SOGoCacheCleanupInterval = 3600;
>>>> SOGoAuthenticationType = saml2;
>>>> NGImap4AuthMechanism = SAML;
>>>> SOGoSAML2IdpMetadataLocation = "/etc/sogo/idp-metadata.xml"; 
>>>> SOGoSAML2PrivateKeyLocation = "/etc/sogo/saml.privkey.pem"; 
>>>> SOGoSAML2CertificateLocation = "/etc/sogo/saml.cert.pem"; // 
>>>> SOGoSAML2IdpPublicKeyLocation = "/etc/sogo/idp.key"; // 
>>>> SOGoSAML2IdpCertificateLocation = "/etc/sogo/idp.crt"; 
>>>> SOGoSAML2LoginAttribute = "username"; SOGoSAML2LogoutEnabled = YES; 
>>>> SOGoSAML2LogoutURL = "https://sogo.domain.com";;
>>>>
>>>> When visiting https://sogo.domain.com/SOGo i get redirected to the 
>>>> keycloak realm SSO prompt, credentials are accepted and it 
>>>> redirects me back to what I configured in KeyCloak to be "Assertion 
>>>> Consumer Service POST Binding URL":
>>>> https://sogo.domain.com:443/SOGo/saml2-signon-post
>>>>
>>>> But once there, I keep hitting:
>>>>
>>>> ```
>>>> sogod [11]: 192.168.0.10 "GET /SOGo HTTP/1.1" 302 0/0 0.002 - - 0 - 
>>>> 11 sogod [11]: |SOGo| starting method 'POST' on uri 
>>>> '/SOGo/saml2-signon-post'
>>>> sogod [11]: |SOGo| traverse(acquire): SOGo => saml2-signon-post 
>>>> sogod [11]: |SOGo|   do traverse name: 'SOGo'
>>>> sogod [11]: |SOGo|   do traverse name: 'saml2-signon-post'
>>>> sogod [11]: |SOGo| set clientObject: <SOGo[0x0x5a13bcaa3e80]: 
>>>> name=SOGo>
>>>> sogod[11:11] EXCEPTION: <NSException: 0x5a13bcc7dd10> 
>>>> NAME:NSInvalidArgumentException REASON:Tried to add nil value for 
>>>> key 'login' to dictionary INFO:{}
>>>>
>>>> ```
>>>>
>>>> Any idea why SOGo (or is it a library like lasso) would generate 
>>>> "Tried to add nil value for key 'login' to dictionary INFO:{}"?
>>>>
>>>> //Anton
>>>
>>> From my limited ability to debug Objective-C, it appears that the 
>>> error is caused by:
>>> https://github.com/Alinto/sogo/blob/b602b2b188ce6c331875450c6b1dbe48
>>> 240f4ff7/UI/MainUI/SOGoSAML2Actions.m#L176
>>>
>>> ```
>>> newSession = [SOGoSAML2Session SAML2SessionInContext: context]; 
>>> [newSession processAuthnResponse: [rq formValueForKey:
>>> @"SAMLResponse"]];
>>> login = [newSession login];
>>> ```
>>>
>>> Where `[newSession login]` is `nil`?
>>> My next obsession is going to be guessing what could be missing in 
>>> the SAML response from keycloak. Here's the post-back data from
>>> keycloak: https://0x0.st/XWZs.txt
>>>
>>> //Anton
>>>
>> I also keep getting:
>> ```
>> (process:907): Lasso-CRITICAL **: 11:28:20.078: 2024-08-10 11:28:20
>> (profile.c/:913) Trying to unref a non GObject pointer
>> file=profile.c:913 pointerbybname=profile->identity
>> pointer=0x5f5b99c00e40 (process:907): Lasso-CRITICAL **: 
>> 11:28:20.078: 2024-08-10 11:28:20 (profile.c/:916) Trying to unref a 
>> non GObject pointer file=profile.c:916 
>> pointerbybname=profile->session pointer=0x5f5b99cb6ae0 ```
>>
>> And in a production container I get coredump:ed:
>> ```
>> 2024-08-10 12:05:10.867 sogod[34:34] PG0x0x5e19c98edae0 SQL: COMMIT 
>> TRANSACTION
>>
>> (process:34): Lasso-CRITICAL **: 12:05:10.886: 2024-08-10 12:05:10
>> (profile.c/:913) Trying to unref a non GObject pointer
>> file=profile.c:913 pointerbybname=profile->identity
>> pointer=0x5e19ca0f5d90
>> 2024-08-10 12:05:11.077 sogod[28:28] INFO(-[NGActiveSocket isAlive])
>> poll(): fd=7 revents=0x0011)
>> Aug 10 12:05:11 sogod [28]: <0x0x5e19c9ccd250[WOWatchDogChild]> child
>> 34 exited
>> Aug 10 12:05:11 sogod [28]: <0x0x5e19c9ccd250[WOWatchDogChild]>
>> (terminated due to signal 11, coredump) ```
>>
>> Which according to some bug reports should already be handled:
>> - https://bugs.sogo.nu//view.php?id=5153
>> - https://bugs.sogo.nu/view.php?id=5270
>> - https://bugs.sogo.nu/view.php?id=5153
>>
>> The exception appears to happen only to the two keys in `lassoLogin` 
>> that has been "dumped" or loaded by a string:
>> https://github.com/Alinto/sogo/blob/b602b2b188ce6c331875450c6b1dbe482
>> 40f4ff7/SoObjects/SOGo/SOGoSAML2Session.m#L354-L362
>>
>> And while debugging, with my limited knowledge, it looks like I get a 
>> segfault right after the response is being sent.
>>
>> ```
>> gdb \
>>   -ex 'set breakpoint pending on' \
>>   -ex 'break SOGoSAML2Actions.m:174' \
>>   -ex 'run' \
>>   --args /usr/bin/sogod -WOUseWatchDog NO -SOGoDebugRequests YES 
>> -WONoDetach YES -WOPort 0.0.0.0:20000 -WOWorkersCount 1 -WOLogFile - 
>> -WOPidFile /tmp/sogo.pid ```
>> Generates:
>> ```
>> Aug 10 11:57:30 sogod [271]: |SOGo| WOHttpAdaptor listening on 
>> address 0.0.0.0:20000 Aug 10 11:57:31 sogod [271]: |SOGo| starting 
>> method 'POST' on uri '/SOGo/saml2-signon-post'
>> Aug 10 11:57:31 sogod [271]: <0x0x582dab9c4650[SOGoCache]> Cache 
>> cleanup interval set every 3600.000000 seconds Aug 10 11:57:31 sogod 
>> [271]: <0x0x582dab9c4650[SOGoCache]> Using
>> host(s) 'memcached:11211' as server(s) Aug 10 11:57:31 sogod [271]: 
>> |SOGo| traverse(acquire): SOGo => saml2-signon-post Aug 10 11:57:31 
>> sogod [271]: |SOGo|   do traverse name: 'SOGo'
>> Aug 10 11:57:31 sogod [271]: |SOGo|   do traverse name: 
>> 'saml2-signon-post'
>> Aug 10 11:57:31 sogod [271]: |SOGo| set clientObject: 
>> <SOGo[0x0x582dab9cb550]: name=SOGo>
>>
>> Breakpoint 1, -[SOGoSAML2Actions saml2SignOnPOSTAction] 
>> (self=0x582dabbea890, _cmd=0x582dabb12e90) at
>> /usr/src/debug/sogo/SOGo-5.10.0/UI/MainUI/SOGoSAML2Actions.m:174
>> 174          newSession = [SOGoSAML2Session SAML2SessionInContext: 
>> context];
>> (gdb) next
>> 175          [newSession processAuthnResponse: [rq formValueForKey: 
>> @"SAMLResponse"]];
>> 176          login = [newSession login];
>> 178          application = [SoApplication application];
>> 179          auth = [application authenticatorInContext: context];
>> 182                                      inContext: context];
>> 181                                    andPassword: [newSession 
>> identifier]
>> 182                                      inContext: context];
>> 2024-08-10 11:57:42.261 sogod[271:271] PostgreSQL72 connection
>> established: <0x0x582dabcc6000[PGConnection]: 
>> connection=0x0x582dabcb4c30>
>> 2024-08-10 11:57:42.261 sogod[271:271] PostgreSQL72 channel
>> 0x0x582dabcb8840 opened (connection=<0x0x582dabcc6000[PGConnection]: 
>> connection=0x0x582dabcb4c30>, count=2)
>> 2024-08-10 11:57:42.262 sogod[271:271] PG0x0x582dabcb8840 SQL: BEGIN 
>> TRANSACTION
>> 2024-08-10 11:57:42.262 sogod[271:271] PG0x0x582dabcb8840 SQL: SELECT 
>> t1.c_creationdate, t1.c_id, t1.c_lastseen, t1.c_value FROM 
>> sogo_sessions_folder t1 WHERE t1.c_id='Gq...+62v'
>> 2024-08-10 11:57:42.263 sogod[271:271] PG0x0x582dabcb8840 SQL: 
>> ROLLBACK TRANSACTION
>> 2024-08-10 11:57:42.263 sogod[271:271] PG0x0x582dab5d4ae0 SQL: BEGIN 
>> TRANSACTION
>> 2024-08-10 11:57:42.263 sogod[271:271] PG0x0x582dab5d4ae0 SQL: INSERT 
>> INTO sogo_sessions_folder (c_lastseen, c_creationdate, c_value, c_id) 
>> VALUES (1723283862, 1723283862, '91R...EmI', 'Gq...+62v')
>> 2024-08-10 11:57:42.264 sogod[271:271] PG0x0x582dab5d4ae0 SQL: COMMIT 
>> TRANSACTION
>> 185          creds = [auth parseCredentials: [authCookie value]];
>> 187                                          value: [[SOGoSession
>> valueForSessionKey: [creds lastObject]] asSHA1String]];
>> 188          [xsrfCookie setPath: [NSString stringWithFormat: 
>> @"/%@/", [[context request] applicationName]]];
>> 189          [response addCookie: xsrfCookie];
>> 191          oldLocation = [[context clientObject] baseURLInContext: 
>> context];
>> 193                                  oldLocation, [login 
>> stringByEscapingURL]];
>> 195          [response setStatus: 302];
>> 196          [response setHeader: newLocation forKey: @"location"];
>> 197          [response addCookie: authCookie];
>> 205      return response;
>> 206    }
>> -[SoActionInvocation
>> callOnObject:withPositionalParametersWhenNotNil:inContext:]
>> (self=<optimized out>, _cmd=<optimized out>, _client=<optimized out>, 
>> _positionalArgs=0x0, _ctx=0x582dab9d1b90)
>>     at SoObjects/SoActionInvocation.m:310
>> 310      result = [result retain];
>> 311      [method release]; method = nil;
>> 312      return [result autorelease];
>> 0x00007e26c476bb40 in ?? () from /usr/lib/libgnustep-base.so.1.29 
>> Cannot find bounds of current function Cannot find bounds of current 
>> function Cannot find bounds of current function
>> (gdb) c
>> Continuing.
>>
>> (process:271): Lasso-CRITICAL **: 11:57:50.259: 2024-08-10 11:57:50
>> (profile.c/:913) Trying to unref a non GObject pointer
>> file=profile.c:913 pointerbybname=profile->identity
>> pointer=0x582dabcb4de0
>>
>> Program received signal SIGSEGV, Segmentation fault.
>> 0x00007e26c3fb1f81 in g_type_check_instance_is_fundamentally_a () 
>> from /usr/lib/libgobject-2.0.so.0 ```
>>
>> Any guidance or assistance here would be greatly appreciated as I'm 
>> way out in deep water.
>> My two main concerns are:
>>
>> - What's missing from KeyCloak or what fields/data are wrong in the
>> SAML2 response that could cause this
>> - How do I patch the lassoLogin data to not cause coredump/critical 
>> errors
>>
>> //Anton
>>
> Continuing on to debug this, it appears that:
> On a good run (before auth is done), de-allocation in lasso looks like:
>
> ```
> 912        lasso_mem_debug("LassoProfile", "Identity",
> profile->identity);
> (gdb) print profile->identity
> $2 = (LassoIdentity *) 0x0
>   freeing LassoProfile/Identity (at (nil))
> 913        lasso_release_gobject(profile->identity);
> ```
>
> On a bad run, it looks like this:
>
> ```
> 912        lasso_mem_debug("LassoProfile", "Identity",
> profile->identity);
> (gdb) print profile->identity
> $4 = (LassoIdentity *) 0x614290dbbc10
>   freeing LassoProfile/Identity (at 0x614290dbbc10)
> 913        lasso_release_gobject(profile->identity);
>
> Program received signal SIGSEGV, Segmentation fault.
> ```
>
> Either way, it appears to hit after the request is sent to the user so 
> it shouldn't matter for debugging what's missing between SOGo and 
> KeyCloak SAML2 response.
> But it appears that SOGo treats my SAML2 user as anonymous or
> non-existant:
> ```
> if (!user || [[user login] isEqualToString: @"anonymous"]) ```
>
> However when adding in authentication using SQL: 
> https://www.sogo.nu/files/docs/SOGoInstallationGuide.html#Authenticati
> on-using-SQL I got a bit further but had to manually manipulate the 
> url from https://sogo.domain.com//anton to 
> https://sogo.domain.com/so/anton
>
> As if it didn't really get that it should redirect to `/so/anton`.
> Which got me thinking, that something was wrong with an attribute 
> somewhere.
>
> It looks like changing mapper type from "User Attribute" to "User 
> Property" for email and username gets me further.
> So I tried removing SQL Auth source, which caused the user to not be 
> able to login again.
>
> But adding back in SQL authentication causes the `/so/<user>` URL to 
> present a HTTP status code causing a plain HTTP login window:
>
> ```
> SOGoUserSources =
> (
>     {
>         type = sql;
>         id = vmail_mailbox;
>         viewURL = "postgresql://sogo:sogo@sogo_db:5432/sogo/users";
>         canAuthenticate = YES;
>         isAddressBook = YES;
>         userPasswordAlgorithm = md5;
>         prependPasswordScheme = YES;
>         displayName = "Global Address Book";
>     }
> );
> ```
>
> psql -U sogo sogo -c "CREATE TABLE IF NOT EXISTS users (c_uid 
> VARCHAR(255), c_name VARCHAR(255), c_password VARCHAR(255), c_cn 
> VARCHAR(255), mail VARCHAR(255))"
> psql -U sogo sogo -c "INSERT INTO users (c_uid, c_name, c_password, 
> c_cn, mail) VALUES ('anton', 'anton', 
> '098f6bcd4621d373cade4e832627b4f6', 'anton', '[email protected]')"
>
> Resetting everything to make sure I didn't do more mistakes while 
> debugging, I now hit: LASSO_DS_ERROR_CA_CERT_CHAIN_LOAD_FAILED
>
> Sorry for the noise but the SAML2 docs are slightly limited and I have 
> no idea how to properly interpret these debug messages.
>
> //Anton
>
So the LASSO_DS_ERROR_CA_CERT_CHAIN_LOAD_FAILED appears to happen because the 
extracted certificate and public key from KeyCloak does not
contain:
-----BEGIN PRIVATE KEY-----
and not
-----BEGIN PUBLIC KEY-----

And the HTTP authentication appears to be a faulty apache configuration taken 
from the example conf.

But SOGo will not function without a SOGoUserSources alongside the SAML2 login 
for (to me) unknown reasons.
Apologies for the noise, but debugging the SAML stuff was not easy and I was 
hoping someone else had bumped into similar issues.

Thanks for Jeroen in a previous mail thread: 
https://www.mail-archive.com/[email protected]/msg29861.html
It really helped out with the mappers as well as where to get the RSA keys from.

//Anton

Reply via email to