tobybatch / kimai2

Docker containers for the kimai2 web application including docker-compose and kubernetes/helm deployment.
MIT License
183 stars 97 forks source link

KimaiV2 as Docker container with LDAP #25

Closed tobybatch closed 5 years ago

tobybatch commented 5 years ago

In an email i received:

Hi Toby,

thanks for your work on Kimai with Docker.

I've started a successfull new Kimai setup. It's all working OK. Next step would be the integration of my LDAP server.

I've found this KB article: https://www.kimai.org/documentation/ldap.html

Unforunately I am not sure, Kimai as Docker container is working fine with the ZendFramework LDAP library.

Is there a example setup for Kimai as Docker with LDAP?

Many thanks and greetings from Germany, Michael

tobybatch commented 5 years ago

I'm waiting for a stable release from Kevin of Kimai before I build the new Docker.  If you really need it then you can use the dev docker image as that builds against master. 

If you have problems then let me know.

bkraul commented 5 years ago

I too am interested on this. I am not sure exactly how I would use the docker image to add this functionality. I am guessing I would add the ldap component through composer, then include the yaml data, and add a few env variables?

tobybatch commented 5 years ago

@bkraul It's available, https://cloud.docker.com/u/kimai/repository/docker/kimai/kimai2

Just mount the local.yml into the container something like this:

docker run -v $(pwd)/local.yml:/opt/docker/config/package/local.yml kimai/kimai2:1.0

I'll write this up better for the readme.

bkraul commented 5 years ago

Added the mount as specified with local.yml containing AD information for my organization, however, I do not see it being called, and LDAP authentication is not working, as described in the Kimai LDAP referenced (accounts get synced on first login). Is the /opt/docker/config path correct? I have tried with tag 1.0 and with tag latest, to no avail. I am also unable to see any log information pointing me to any configuration issues.

Would I need to drop the database and recreate from scratch?

kevinpapst commented 5 years ago

Not sure about the docker container, but if you run in prod, you normally have to clear the cache in order for new configs to take effect.

tobybatch commented 5 years ago

Can you please include your local.yml and docker command or docker-compose file please?

bkraul commented 5 years ago

Docker-compose:

version: '3.7'
services:
  kimai:
    image: kimai/kimai2:latest
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /root/docker-persist/kimai/local.yml:/opt/docker/config/package/local.yml
    environment:
      - VIRTUAL_HOST=kimai.mydomain.com
      - VIRTUAL_PORT=8001
      - APP_ENV=prod
      - DATABASE_URL=mysql://kimai:mypass@mydbserver/kimai
      - MAILER_FROM=kimai@mydomain.com
      - MAILER_URL=smtp://myemailserver:25/?encryption=null&auth_mode=null
      - APP_SECRET=myappsecret
    networks:
      - reverse-proxy

networks:
  reverse-proxy:
    external:
      name: reverse-proxy

local.yml:

kimai:
    ldap:
        connection:
            host: dc.mydomain.com
            username: serviceaccount@mydomain.com
            password: mypass
            accountDomainName: mydomain.com
            accountDomainNameShort: MYDOMAIN
            accountFilterFormat: (&(objectClass=Person)(sAMAccountName=%s))
        user:
            baseDn: ou=Users,dc=mydomain,dc=com
            filter: (&(!(userAccountControl:1.2.840.113556.1.4.803:=2))(objectClass=person))
            usernameAttribute: samaccountname
            attributesFilter: (objectClass=Person)
            attributes:
                - { ldap_attr: mail, user_method: setEmail }
                - { ldap_attr: displayname, user_method: setAlias }
                - { ldap_attr: samaccountname,  user_method: setUsername }
        role:
            baseDn: dc=ad,dc=example,dc=com
            filter: (&(objectClass=group))
            groups:
                - { ldap_value: Leads, role: ROLE_TEAMLEAD }
                - { ldap_value: Sysadmins, role: ROLE_SUPER_ADMIN }
                - { ldap_value: Domain Admins, role: ROLE_SUPER_ADMIN }
                - { ldap_value: Users, role: ROLE_USER }

What I am confused about is, why does the file need to be mounted to /opt/docker/config, when the kimai folder for the docker image has /opt/kimai/config as the folder for the configurations? @kevinpapst, I also tried clearing the prod cache.

tobybatch commented 5 years ago

@bkraul You're right thta's a type in the docs. it should be mounted as per these instructions: https://github.com/tobybatch/kimai2#docker-compose

I only updated those docs this morning.

As per @kevinpapst instruction clearing the cache will need a docker exec command:

docker-compose exec kimai /opt/kimai/bin/console cache:clear --env=prod
bkraul commented 5 years ago

Followed everything to no avail. Documentation says an LDAP user will be imported on first login. However, as I try to do this with a user I know exists on the directory, I only get an Invaid credentials message.

Is there any way I can turn on logging to see any kind of errors that may be happening? Right now, my docker container's STDOUT only shows the container's initialization messages and HTTP requests, but nothing else.

bkraul commented 5 years ago

Update: It seems that in addition to the cache clear, I have to restart the container itself. I was noticing that some of the other options I was setting in local.yaml were not taking place.

I was able to get those with a docker-compose down && docker-compose up -d. There is a configuration problem likely with my LDAP setup, so I still wish I could see more debugging information.

bkraul commented 5 years ago

At a loss. This is what I am getting:

image

But no errors or any feedback on container logs.

kevinpapst commented 5 years ago

Hm, that looks more like a misconfiguration. We are currently trying to figure out how to stream the Kimai logs to the container output. Right now, Kimai logs into var/log/prod.log, so I guess it should be /opt/kimai/var/log/prod.log. Please have a look there. If you can't find helpful logs, you should change your APP_ENV to dev to activate more logs.

bkraul commented 5 years ago

Set APP_ENV=dev on my docker-compose. Container startup still shows this:

 // Clearing the cache for the prod environment with debug                      
 // false                                                                       
 [OK] Cache for the "prod" environment (debug=false) was successfully cleared.  
 // Warming up the cache for the prod environment with debug                    
 // false                                                                       
 [OK] Cache for the "prod" environment (debug=false) was successfully warmed.   

Checked /opt/kimai/var/log/dev.log, and the only log output gets logged on startup. Among the things to note:

[2019-07-04 23:22:25] php.INFO: User Deprecated: The "FOS\UserBundle\Model\UserInterface" interface extends "Symfony\Component\Security\Core\User\AdvancedUserInterface" that is deprecated since Symfony 4.1. {"exception":"[object] (ErrorException(code: 0): User Deprecated: The \"FOS\\UserBundle\\Model\\UserInterface\" interface extends \"Symfony\\Component\\Security\\Core\\User\\AdvancedUserInterface\" that is deprecated since Symfony 4.1. at /opt/kimai/vendor/symfony/debug/DebugClassLoader.php:199)"} []

I am tailing the log, and when I try log in with an ActiveDirectory user (both user and password are correct), there is no output logged.

kevinpapst commented 5 years ago

First thing to do: create a local user (either via command line or login/registration form) and see if you can login with that one. It must work, if not something else is broken.

Then check https://xxxxx/en/about/debug and verify that is says: Environment: APP_ENV / dev

And please tail var/log/*.log (just in case you are still running in prod)

kevinpapst commented 5 years ago

Oh and btw: do you have this in your local.yaml? Its not mentioned in your post above.

bkraul commented 5 years ago

I have a local user called master. Per the docker-compose instructions, I set my APP_ENV=dev under environment. Destroyed the container. Started it again. Cleared and warmed up cache as per instructions.

Signed in as the local user.

This is the debug page: image

And yes, I added that section after @tobybatch updated the readme.

image

kevinpapst commented 5 years ago

Okay, that must be answered by @tobybatch as I am not fmailiar with the docker setup.

But in this case, are there no logs in var/log/prod.log? The LDAP component logs explicit search and bind commands. If they are logged in prod (which I am not sure about) you will see them...

Edit: do you have the security settings in local.yaml?

bkraul commented 5 years ago

It is weird. The only log I see is dev.log (and again there is no information logged when attempting to log in) but you saw, my app says it is in prod mode.

This is my current local.yaml (sensitive info edited). This is for connecting to a Microsoft ActiveDirectory base.

kimai:
    ldap:
        connection:
            host: dc.mydomain.com
            username: cn=serviceaccount,dc=mydomain,dc=com
            password: MySuperSecurePass
            accountDomainName: mydomain.com
            accountDomainNameShort: MYDOMAIN
            accountFilterFormat: (&(objectClass=Person)(sAMAccountName=%s))
        user:
            baseDn: ou=Users,dc=mydomain,dc=com
            filter: (&(objectClass=person))
            usernameAttribute: samaccountname
            attributesFilter: (&(objectClass=Person))
            attributes:
                - { ldap_attr: mail, user_method: setEmail }
                - { ldap_attr: displayname, user_method: setAlias }
                - { ldap_attr: samaccountname,  user_method: setUsername }
        role:
            baseDn: dc=mydomain,dc=com
            filter: (&(objectClass=group))
            groups:
                - { ldap_value: Leads, role: ROLE_TEAMLEAD }
                - { ldap_value: Sysadmins, role: ROLE_SUPER_ADMIN }
                - { ldap_value: Users, role: ROLE_USER }

    user:
        registration: false
        password_reset: false

security:
    providers:
        chain_provider:
            chain:
                providers: [kimai_ldap]
    firewalls:
        secured_area:
            kimai_ldap: ~
bkraul commented 5 years ago

If I can't get the docker one to work I guess I'll have to set me up a LAMP container and try the composer install. Hopefully I can get more debug info then.

kevinpapst commented 5 years ago

Maybe thats faster, setting up Kimai locally on Mac or Linux is a matter of 5 minutes. Then find the working LDAP/AD config and afterwards @tobybatch might jump in and help with the docker setup.

tobybatch commented 5 years ago

@bkraul Can you connect to the docker using docker-compose exec NAME bash and check the contents of the /etc/apache2/sites-available/000-default file.

The prod docker uses the settings from their when running in apache.

You shouldn't need to restart the container to load the new settings but if you want to be super sure use:

```docker-compose up --build -d && docker-compose logs -f```
bkraul commented 5 years ago

I am pretty sure I need to restart. local.yaml did not get picked up until then, in spite of cache clearing and warmup. Here is the contents of the conf file:

<VirtualHost *:8001>
        ServerAdmin webmaster@localhost
        DocumentRoot /opt/kimai/public

        SetEnv DATABASE_PREFIX
        SetEnv MAILER_FROM email@mydomain.com
        SetEnv APP_ENV prod
        SetEnv APP_SECRET Superdupersecret
        SetEnv DATABASE_URL mysql://validmysqlurl
        SetEnv MAILER_URL smtp://validsmtpurl

        <Directory "/opt/kimai/public">
            Require all granted
                DirectoryIndex index.php
                AllowOverride All
        </Directory>

        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

# vim: syntax=apache ts=4 sw=4 sts=4 sr noet

As you can see, from my previous posts, my docker env var clearly states dev. But the container initialization always says prod.

tobybatch commented 5 years ago

Updates in the container, I guess you are using vim. https://github.com/moby/moby/issues/15793#issuecomment-135411504

If you look at the inode number of the file you are editing:

ls -i local.yaml

then edit it and repeat the ls then the inode will be different. So to the OS the file is different.

It bugs the hell out of me too. If you find a fix (other than mounting the folder) please let me know.

bkraul commented 5 years ago

@kevinpapst you were right. Had the LAMP env (using webdevops/apache) going in no time. Set my app env to dev, worked immediately. Added local.yaml (as posted here), cleared cache and did warmup, changes immediately picked up. Getting tons of debugging information on dev.log:

[2019-07-05 11:59:10] security.DEBUG: Checking for guard authentication credentials. {"firewall_key":"secured_area","authenticators":1} []
[2019-07-05 11:59:10] security.DEBUG: Checking support on guard authenticator. {"firewall_key":"secured_area","authenticator":"App\\Security\\TokenAuthenticator"} []
[2019-07-05 11:59:10] security.DEBUG: Guard authenticator does not support the request. {"firewall_key":"secured_area","authenticator":"App\\Security\\TokenAuthenticator"} []
[2019-07-05 11:59:11] doctrine.DEBUG: SELECT t0.username AS username_1, t0.username_canonical AS username_canonical_2, t0.email AS email_3, t0.email_canonical AS email_canonical_4, t0.enabled AS enabled_5, t0.salt AS salt_6, t0.password AS password_7, t0.last_login AS last_login_8, t0.confirmation_token AS confirmation_token_9, t0.password_requested_at AS password_requested_at_10, t0.roles AS roles_11, t0.id AS id_12, t0.alias AS alias_13, t0.registration_date AS registration_date_14, t0.title AS title_15, t0.avatar AS avatar_16, t0.api_token AS api_token_17 FROM kimai2_users t0 WHERE t0.username_canonical = ? LIMIT 1 ["bkraul"] []
[2019-07-05 11:59:12] app.DEBUG: ldap_search(ou=Users,dc=mydomain,dc=com, (&(samaccountname=bkraul)), [array]) {"action":"ldap_search","base_dn":"ou=Users,dc=mydomain,dc=com","filter":"(&(samaccountname=bkraul))","attributes":["+","*"]} []
[2019-07-05 11:59:12] app.DEBUG: Zend\Ldap\Exception\LdapException: 0x31 (Invalid credentials; 80090308: LdapErr: DSID-0C09042F, comment: AcceptSecurityContext error, data 52e, v2580): cn=serviceaccount,dc=mydomain,dc=com in /app/vendor/zendframework/zend-ldap/src/Ldap.php:991 Stack trace: #0 /app/src/Ldap/LdapDriver.php(64): Zend\Ldap\Ldap->bind() #1 /app/src/Ldap/LdapManager.php(66): App\Ldap\LdapDriver->search('ou=Users,dc=...', '(&(samaccountna...') #2 /app/src/Ldap/LdapManager.php(55): App\Ldap\LdapManager->findUserBy(Array) #3 /app/src/Ldap/LdapUserProvider.php(47): App\Ldap\LdapManager->findUserByUsername('bkraul') #4 /app/vendor/symfony/security/Core/User/ChainUserProvider.php(56): App\Ldap\LdapUserProvider->loadUserByUsername('bkraul') #5 /app/src/Ldap/LdapAuthenticationProvider.php(62): Symfony\Component\Security\Core\User\ChainUserProvider->loadUserByUsername('bkraul') #6 /app/vendor/symfony/security/Core/Authentication/Provider/UserAuthenticationProvider.php(64): App\Ldap\LdapAuthenticationProvider->retrieveUser('bkraul', Object(Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken)) #7 /app/vendor/symfony/security/Core/Authentication/AuthenticationProviderManager.php(76): Symfony\Component\Security\Core\Authentication\Provider\UserAuthenticationProvider->authenticate(Object(Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken)) #8 /app/vendor/symfony/security/Http/Firewall/UsernamePasswordFormAuthenticationListener.php(100): Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager->authenticate(Object(Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken)) #9 /app/vendor/symfony/security/Http/Firewall/AbstractAuthenticationListener.php(128): Symfony\Component\Security\Http\Firewall\UsernamePasswordFormAuthenticationListener->attemptAuthentication(Object(Symfony\Component\HttpFoundation\Request)) #10 /app/vendor/symfony/security-bundle/Debug/WrappedListener.php(46): Symfony\Component\Security\Http\Firewall\AbstractAuthenticationListener->handle(Object(Symfony\Component\HttpKernel\Event\GetResponseEvent)) #11 /app/vendor/symfony/security-bundle/Debug/TraceableFirewallListener.php(35): Symfony\Bundle\SecurityBundle\Debug\WrappedListener->handle(Object(Symfony\Component\HttpKernel\Event\GetResponseEvent)) #12 /app/vendor/symfony/security/Http/Firewall.php(90): Symfony\Bundle\SecurityBundle\Debug\TraceableFirewallListener->handleRequest(Object(Symfony\Component\HttpKernel\Event\GetResponseEvent), Object(Generator)) #13 /app/vendor/symfony/security-bundle/EventListener/FirewallListener.php(48): Symfony\Component\Security\Http\Firewall->onKernelRequest(Object(Symfony\Component\HttpKernel\Event\GetResponseEvent)) #14 /app/vendor/symfony/event-dispatcher/Debug/WrappedListener.php(115): Symfony\Bundle\SecurityBundle\EventListener\FirewallListener->onKernelRequest(Object(Symfony\Component\HttpKernel\Event\GetResponseEvent), 'kernel.request', Object(Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher)) #15 /app/vendor/symfony/event-dispatcher/EventDispatcher.php(212): Symfony\Component\EventDispatcher\Debug\WrappedListener->__invoke(Object(Symfony\Component\HttpKernel\Event\GetResponseEvent), 'kernel.request', Object(Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher)) #16 /app/vendor/symfony/event-dispatcher/EventDispatcher.php(44): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'kernel.request', Object(Symfony\Component\HttpKernel\Event\GetResponseEvent)) #17 /app/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php(145): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('kernel.request', Object(Symfony\Component\HttpKernel\Event\GetResponseEvent)) #18 /app/vendor/symfony/http-kernel/HttpKernel.php(126): Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher->dispatch('kernel.request', Object(Symfony\Component\HttpKernel\Event\GetResponseEvent)) #19 /app/vendor/symfony/http-kernel/HttpKernel.php(67): Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object(Symfony\Component\HttpFoundation\Request), 1) #20 /app/vendor/symfony/http-kernel/Kernel.php(198): Symfony\Component\HttpKernel\HttpKernel->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true) #21 /app/public/index.php(37): Symfony\Component\HttpKernel\Kernel->handle(Object(Symfony\Component\HttpFoundation\Request)) #22 {main} {"exception":"[object] (Zend\\Ldap\\Exception\\LdapException(code: 49): 0x31 (Invalid credentials; 80090308: LdapErr: DSID-0C09042F, comment: AcceptSecurityContext error, data 52e, v2580): cn=serviceaccount,dc=mydomain,dc=com at /app/vendor/zendframework/zend-ldap/src/Ldap.php:991)"} []
[2019-07-05 11:59:12] doctrine.DEBUG: SELECT t0.username AS username_1, t0.username_canonical AS username_canonical_2, t0.email AS email_3, t0.email_canonical AS email_canonical_4, t0.enabled AS enabled_5, t0.salt AS salt_6, t0.password AS password_7, t0.last_login AS last_login_8, t0.confirmation_token AS confirmation_token_9, t0.password_requested_at AS password_requested_at_10, t0.roles AS roles_11, t0.id AS id_12, t0.alias AS alias_13, t0.registration_date AS registration_date_14, t0.title AS title_15, t0.avatar AS avatar_16, t0.api_token AS api_token_17 FROM kimai2_users t0 WHERE t0.username_canonical = ? LIMIT 1 ["bkraul"] []
[2019-07-05 11:59:12] app.DEBUG: ldap_search(ou=Users,dc=mydomain,dc=com, (&(samaccountname=bkraul)), [array]) {"action":"ldap_search","base_dn":"ou=Users,dc=mydomain,dc=com","filter":"(&(samaccountname=bkraul))","attributes":["+","*"]} []
[2019-07-05 11:59:14] app.DEBUG: Zend\Ldap\Exception\LdapException: 0x31 (Invalid credentials; 80090308: LdapErr: DSID-0C09042F, comment: AcceptSecurityContext error, data 52e, v2580): cn=serviceaccount,dc=mydomain,dc=com in /app/vendor/zendframework/zend-ldap/src/Ldap.php:991 Stack trace: #0 /app/src/Ldap/LdapDriver.php(64): Zend\Ldap\Ldap->bind() #1 /app/src/Ldap/LdapManager.php(66): App\Ldap\LdapDriver->search('ou=Users,dc=...', '(&(samaccountna...') #2 /app/src/Ldap/LdapManager.php(55): App\Ldap\LdapManager->findUserBy(Array) #3 /app/src/Ldap/LdapUserProvider.php(47): App\Ldap\LdapManager->findUserByUsername('bkraul') #4 /app/vendor/symfony/security/Core/User/ChainUserProvider.php(56): App\Ldap\LdapUserProvider->loadUserByUsername('bkraul') #5 /app/vendor/symfony/security/Core/Authentication/Provider/DaoAuthenticationProvider.php(74): Symfony\Component\Security\Core\User\ChainUserProvider->loadUserByUsername('bkraul') #6 /app/vendor/symfony/security/Core/Authentication/Provider/UserAuthenticationProvider.php(64): Symfony\Component\Security\Core\Authentication\Provider\DaoAuthenticationProvider->retrieveUser('bkraul', Object(Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken)) #7 /app/vendor/symfony/security/Core/Authentication/AuthenticationProviderManager.php(76): Symfony\Component\Security\Core\Authentication\Provider\UserAuthenticationProvider->authenticate(Object(Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken)) #8 /app/vendor/symfony/security/Http/Firewall/UsernamePasswordFormAuthenticationListener.php(100): Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager->authenticate(Object(Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken)) #9 /app/vendor/symfony/security/Http/Firewall/AbstractAuthenticationListener.php(128): Symfony\Component\Security\Http\Firewall\UsernamePasswordFormAuthenticationListener->attemptAuthentication(Object(Symfony\Component\HttpFoundation\Request)) #10 /app/vendor/symfony/security-bundle/Debug/WrappedListener.php(46): Symfony\Component\Security\Http\Firewall\AbstractAuthenticationListener->handle(Object(Symfony\Component\HttpKernel\Event\GetResponseEvent)) #11 /app/vendor/symfony/security-bundle/Debug/TraceableFirewallListener.php(35): Symfony\Bundle\SecurityBundle\Debug\WrappedListener->handle(Object(Symfony\Component\HttpKernel\Event\GetResponseEvent)) #12 /app/vendor/symfony/security/Http/Firewall.php(90): Symfony\Bundle\SecurityBundle\Debug\TraceableFirewallListener->handleRequest(Object(Symfony\Component\HttpKernel\Event\GetResponseEvent), Object(Generator)) #13 /app/vendor/symfony/security-bundle/EventListener/FirewallListener.php(48): Symfony\Component\Security\Http\Firewall->onKernelRequest(Object(Symfony\Component\HttpKernel\Event\GetResponseEvent)) #14 /app/vendor/symfony/event-dispatcher/Debug/WrappedListener.php(115): Symfony\Bundle\SecurityBundle\EventListener\FirewallListener->onKernelRequest(Object(Symfony\Component\HttpKernel\Event\GetResponseEvent), 'kernel.request', Object(Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher)) #15 /app/vendor/symfony/event-dispatcher/EventDispatcher.php(212): Symfony\Component\EventDispatcher\Debug\WrappedListener->__invoke(Object(Symfony\Component\HttpKernel\Event\GetResponseEvent), 'kernel.request', Object(Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher)) #16 /app/vendor/symfony/event-dispatcher/EventDispatcher.php(44): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'kernel.request', Object(Symfony\Component\HttpKernel\Event\GetResponseEvent)) #17 /app/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php(145): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('kernel.request', Object(Symfony\Component\HttpKernel\Event\GetResponseEvent)) #18 /app/vendor/symfony/http-kernel/HttpKernel.php(126): Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher->dispatch('kernel.request', Object(Symfony\Component\HttpKernel\Event\GetResponseEvent)) #19 /app/vendor/symfony/http-kernel/HttpKernel.php(67): Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object(Symfony\Component\HttpFoundation\Request), 1) #20 /app/vendor/symfony/http-kernel/Kernel.php(198): Symfony\Component\HttpKernel\HttpKernel->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true) #21 /app/public/index.php(37): Symfony\Component\HttpKernel\Kernel->handle(Object(Symfony\Component\HttpFoundation\Request)) #22 {main} {"exception":"[object] (Zend\\Ldap\\Exception\\LdapException(code: 49): 0x31 (Invalid credentials; 80090308: LdapErr: DSID-0C09042F, comment: AcceptSecurityContext error, data 52e, v2580): cn=serviceaccount,dc=mydomain,dc=com at /app/vendor/zendframework/zend-ldap/src/Ldap.php:991)"} []
[2019-07-05 11:59:14] security.INFO: Authentication request failed. {"exception":"[object] (Symfony\\Component\\Security\\Core\\Exception\\AuthenticationServiceException(code: 0): An error occurred with the search operation. at /app/vendor/symfony/security/Core/Authentication/Provider/DaoAuthenticationProvider.php:85, App\\Ldap\\LdapDriverException(code: 0): An error occurred with the search operation. at /app/src/Ldap/LdapDriver.php:72)"} []
[2019-07-05 11:59:14] security.DEBUG: Authentication failure, redirect triggered. {"failure_path":"fos_user_security_login"} []
[2019-07-05 11:59:20] request.INFO: Matched route "fos_user_security_login". {"route":"fos_user_security_login","route_parameters":{"_route":"fos_user_security_login","_controller":"fos_user.security.controller:loginAction","_locale":"en"},"request_uri":"http://dev-kimai.mydomain.com/en/login","method":"GET"} []
[2019-07-05 11:59:20] security.DEBUG: Checking for guard authentication credentials. {"firewall_key":"secured_area","authenticators":1} []
[2019-07-05 11:59:20] security.DEBUG: Checking support on guard authenticator. {"firewall_key":"secured_area","authenticator":"App\\Security\\TokenAuthenticator"} []
[2019-07-05 11:59:20] security.DEBUG: Guard authenticator does not support the request. {"firewall_key":"secured_area","authenticator":"App\\Security\\TokenAuthenticator"} []
[2019-07-05 11:59:20] security.INFO: Populated the TokenStorage with an anonymous Token. [] []
[2019-07-05 11:59:24] doctrine.DEBUG: SELECT k0_.id AS id_0, k0_.name AS name_1, k0_.value AS value_2 FROM kimai2_configuration k0_ WHERE k0_.name LIKE ? ["theme.%"] []
[2019-07-05 11:59:29] request.INFO: Matched route "_wdt". {"route":"_wdt","route_parameters":{"_route":"_wdt","_controller":"web_profiler.controller.profiler::toolbarAction","token":"32d37d"},"request_uri":"http://dev-kimai.mydomain.com/_wdt/32d37d","method":"GET"} []
tobybatch commented 5 years ago

Your env is really weird. It's effectively the same as mine but you seem to run in dev. Can we check the hash on your docker image please?

tobias@dockerhost:kimai_00 $ docker images kimai/kimai2:1.0 --no-trunc
REPOSITORY          TAG                 IMAGE ID                                                                  CREATED             SIZE
kimai/kimai2        1.0                 sha256:fcaef567f2a56dd02679f553ad8077066f327ccb3bb5f66fed12bb5ef8bc50f5   47 hours ago        1.09GB
bkraul commented 5 years ago
REPOSITORY          TAG                 IMAGE ID                                                                  CREATED             SIZE
kimai/kimai2        1.0                 sha256:6d3faa6a5d2db2938e5278fa1d5966e80e6db97f1765d565ff1d7e9582be93bd   23 hours ago        1.09GB
tobybatch commented 5 years ago

Ha, yours is newer than my prod env. I'll pull and update mine, hang on a sec.

tobybatch commented 5 years ago

OK, mine is still running in prod.

Can you check the contents of the //opt/kimai/.env file please. @kevinpapst Other then apache conf and .env is there anywhere else symfony can get the env from?

kevinpapst commented 5 years ago

Well, it takes it from the environment if found (Apache or regular bash env for bin/console). Fallback is the .env file.

But isn't the problem, that it is still running in prod? @bkraul switched the docker APP_ENV to dev but the apache config still has APP_ENV=prod, so the changed config is not taken into account. Is the Apache config regenerated?

Besides: maybe its a bad idea in the first place, to use the docker-prod image to switch to dev mode?

bkraul commented 5 years ago

I have used both 1.0 and latest from that image (not sure if they are different). To the best of my knowledge, there should be no need to put the env on the apache conf, because the docker container picks up the vars from the environment and sets them as env vars in the container.

@tobybatch my .env file has:

DATABASE_PREFIX=
MAILER_FROM=kimai@mydomain.com
APP_SECRET=MySuperDuperSecret
DATABASE_URL=mysql://validmyslurl
MAILER_URL=smtp://validsmtpurl

There seems to be no mention of the APP_ENV var.

tobybatch commented 5 years ago

The apache env gets re-written when the startup.sh is called, so on a re-build I guess.

https://github.com/tobybatch/kimai2/blob/master/tags/1.0/startup.sh#L47

The console commands will pick up the env from OS.

@bkraul Do we currently have several issues?

  1. Switch environments
  2. LDAP not working
  3. No log files

Is this right?

bkraul commented 5 years ago

@tobybatch that is correct, but only with the docker image. LAMP install from official Kimai through composer works as intended. (Minus the LDAP auth which I am still trying to iron out, of course)

bkraul commented 5 years ago

@kevinpapst what does this mean?

[2019-07-05 13:14:45] security.DEBUG: Checking for guard authentication credentials. {"firewall_key":"secured_area","authenticators":1} []
[2019-07-05 13:14:45] security.DEBUG: Checking support on guard authenticator. {"firewall_key":"secured_area","authenticator":"App\\Security\\TokenAuthenticator"} []
[2019-07-05 13:14:45] security.DEBUG: Guard authenticator does not support the request. {"firewall_key":"secured_area","authenticator":"App\\Security\\TokenAuthenticator"} []
kevinpapst commented 5 years ago

Nothing relevant to your issue. The guard authenticator kicks in when calling the Json-API.

bkraul commented 5 years ago

For some reason, the bind security context is not getting created, even though I am positive the login information for the bind account is correct:

[2019-07-05 14:26:42] app.DEBUG: Zend\Ldap\Exception\LdapException: 0x31 (Invalid credentials; 80090308: LdapErr: DSID-0C09042F, comment: AcceptSecurityContext error, data 52e, v2580): cn=ldapserviceaccount,dc=mydomain,dc=com...
    ldap:
        connection:
            host: dc.mydomain.com
            username: cn=ldapserviceaccount,dc=mydomain,dc=com
            password: correctPassword
            accountDomainName: mydomain.com
            accountDomainNameShort: MYDOMAIN
            accountFilterFormat: (&(objectClass=Person)(sAMAccountName=%s))

What am I missing here? By the way, I believe the Kimai documentation on LDAP for AD is incorrect. You cannot use a ldapuser@domain.com notation, you get an error saying it must use the cn=user,dn=domain,dn=tld notation.

kevinpapst commented 5 years ago

I will update the example about the username, after you found a working config.

This here https://www.infrasightlabs.com/common-error-codes-for-active-directory-authentication says :

data 52e = Invalid credentials = Returned when a valid username is supplied but an invalid password/credential is supplied. If this error is received, it will prevent most other errors from being displayed.

bkraul commented 5 years ago

Yeah, I ended up in that post as well. I can't understand how the credentials are being interpreted as incorrect when I know for sure it is the right username and password. Question, would the APP_SECRET value have any bearing on how passwords are encoded before sending an LDAP request?

kevinpapst commented 5 years ago

No, Kimai does not encrypt LDAP passwords. You can add some debug infos here and see what is passed: https://github.com/kevinpapst/kimai2/blob/master/src/Ldap/LdapDriver.php#L87

bkraul commented 5 years ago

@kevinpapst, Has anyone ever reported a successful connection to Microsoft ActiveDirectory using Kimai2?

kevinpapst commented 5 years ago

Yes, search the closed issues in the Kimai repo.

bkraul commented 5 years ago

So I was looking at the function you suggested I track:

    public function bind(UserInterface $user, string $password): bool
    {
        $bindDn = $user->getUsername();
        try {
            $this->logDebug('{action}({bindDn}, ****)', [
                'action' => 'ldap_bind',
                'bindDn' => $bindDn,
            ]);
            $bind = $this->driver->bind($bindDn, $password);
            return $bind instanceof Ldap;
        } catch (LdapException $exception) {
            $this->ldapExceptionHandler($exception, $password);
        }
        return false;
    }

From this, when debugging is turned on, I should see an app.DEBUG message on the log showing a ldap_bind action. However, the only message I see before the error is a ldap_search debug event, never the bind. But the errors are clearly telling me there is a security context error. I'm so confused. I have been checking the issues on the kimai2 repo. Will keep trying.

kevinpapst commented 5 years ago

Maybe I am wrong and it is not the "user bind", but the service-account bind: https://github.com/kevinpapst/kimai2/blob/master/src/Ldap/LdapDriver.php#L64

bkraul commented 5 years ago

The mystery has now been solved. The full DN string I was using for my ldap service account user apparently was incomplete AND incorrect (because of my sysadmin's OU requirements). When I put the full DN in, authentication and search worked, my user was imported and everything is operating.

@kevinpapst thank you for pointing me to the right debugging spots. I was able to add $this->driver->getOptions() to the debug output in the search function in order to see all that was being sent to the server.

I am torn between using the LAMP stack or using the docker container because of the APP_ENV situation, (which probably could use a separate issue) and because I am currenly using a php-nginx setup, which I prefer. I might create me a Dockerfile to suit my specific needs. @tobybatch , thank you for your prompt responses in spite of this VERY long thread.

This particular LDAP issue can now be closed if desired.

tobybatch commented 5 years ago

@bkraul I'm really happy to work with you to produce an nginx solution. my original version of the docker used nginx but i had some issues with setting up the php.

I'd love to offer apache and nginx versions.

bkraul commented 5 years ago

@tobybatch ok, so this is what I have got. This yields a fully operational Kimai2 set up on nginx. I have talked to @kevinpapst about some ways of making the initialization more graceful, but until then, this seems to work great for me. As mentioned, I prefer to use the webdevops/php-nginx image as my base, as it is well-updated, has extensive documentation and is well-organized (includes everything that is needed).

I have 2 versions, one based on ubuntu and based on alpine. I have not put this on a GitHub repo, this is my self-hosted one. Feel free to check it out. This implementation does require that the database be created beforehand (through like phpMyAdmin and such).

Attached is a usual docker-compose (using jwilder/nginx-proxy):

version: '3.7'
services:
  kimai:
    image: bkraul/kimai2:nginx-alpine
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ./local.yaml:/app/config/packages/local.yaml
    environment:
      # nginx-proxy
      - VIRTUAL_HOST=kimai.mydomain.com
      - PHP_DATE_TIMEZONE=America/Chicago
      # kimai2
      - APP_ENV=prod
      - DATABASE_URL=mysql://validsqlurl
      - MAILER_FROM=kimai@mydomain.com
      - MAILER_URL=smtp://validsmtpurl
      - APP_SECRET=Superdupersecret
      # initial admin user
      - APP_ADMIN_USER=master
      - APP_ADMIN_EMAIL=master@mydomain.com
      - APP_ADMIN_PASS=masterPassword
    networks:
      - reverse-proxy

networks:
  reverse-proxy:
    external:
      name: reverse-proxy
tobybatch commented 5 years ago

@bkraul Thanks, I'll take a look at those. I will try and offer an nginx version soon.