[[appendices]] = Appendices :figures: servlet/authentication/kerberos :numbered!: [appendix] == Material Used in this Document Dummy UserDetailsService used in samples because we don't have a real user source. [source,java,indent=0] ---- include::example$kerberos/DummyUserDetailsService.java[tags=snippetA] ---- [appendix] == Crash Course to Kerberos In any authentication process there are usually a three parties involved. image::{figures}/drawio-kerb-cc1.png[] First is a `client` which sometimes is a client computer but in most of the scenarios it is the actual user sitting on a computer and trying to access resources. Then there is the `resource` user is trying to access. In this example it is a web server. Then there is a `Key Distribution Center` or `KDC`. In a case of Windows environment this would be a `Domain Controller`. `KDC` is the one which really brings everything together and thus is the most critical component in your environment. Because of this it is also considered as a single point of failure. Initially when `Kerberos` environment is setup and domain user principals created into a database, encryption keys are also created. These encryption keys are based on shared secrets(i.e. user password) and actual passwords are never kept in a clear text. Effectively `KDC` has its own key and other keys for domain users. Interestingly there is no communication between a `resource` and a `KDC` during the authentication process. image::{figures}/drawio-kerb-cc2.png[] When client wants to authenticate itself with a `resource` it first needs to communicate with a `KDC`. `Client` will craft a special package which contains encrypted and unencrypted parts. Unencrypted part contains i.e. information about a user and encrypted part other information which is part of a protocol. `Client` will encrypt package data with its own key. When `KDC` receives this authentication package from a client it checks who this `client` claims to be from an unencrypted part and based on that information it uses `client` decryption key it already have in its database. If this decryption is succesfull `KDC` knows that this `client` is the one it claims to be. What KDC returns to a client is a ticket called `Ticket Granting Ticket` which is signed by a KDC's own private key. Later when `client` sends back this ticket it can try to decrypt it and if that operation is succesfull it knows that it was a ticket it itself originally signed and gave to a `client`. image::{figures}/drawio-kerb-cc3.png[] When client wants to get a ticket which it can use to authenticate with a service, `TGT` is sent to `KDC` which then signs a service ticket with service's own key. This a moment when a trust between `client` and `service` is created. This service ticket contains data which only `service` itself is able to decrypt. image::{figures}/drawio-kerb-cc4.png[] When `client` is authenticating with a service it sends previously received service ticket to a service which then thinks that I don't know anything about this guy but he gave me an authentication ticket. What `service` can do next is try to decrypt that ticket and if that operation is succesfull it knows that only other party who knows my credentials is the `KDC` and because I trust him I can also trust that this client is a one he claims to be. [appendix] == Setup Kerberos Environments Doing a production setup of Kerberos environment is out of scope of this document but this appendix provides some help to get you started for setting up needed components for development. [[setupmitkerberos]] === Setup MIT Kerberos First action is to setup a new realm and a database. [source,text,indent=0] ---- # kdb5_util create -s -r EXAMPLE.ORG Loading random data Initializing database '/var/lib/krb5kdc/principal' for realm 'EXAMPLE.ORG', master key name 'K/M@EXAMPLE.ORG' You will be prompted for the database Master Password. It is important that you NOT FORGET this password. Enter KDC database master key: Re-enter KDC database master key to verify: ---- `kadmin` command can be used to administer Kerberos environment but you can't yet use it because there are no admin users in a database. [source,text,indent=0] ---- root@neo:/etc/krb5kdc# kadmin Authenticating as principal root/admin@EXAMPLE.ORG with password. kadmin: Client not found in Kerberos database while initializing kadmin interface ---- Lets use `kadmin.local` command to create one. [source,text,indent=0] ---- root@neo:/etc/krb5kdc# kadmin.local Authenticating as principal root/admin@EXAMPLE.ORG with password. kadmin.local: listprincs K/M@EXAMPLE.ORG kadmin/admin@EXAMPLE.ORG kadmin/changepw@EXAMPLE.ORG kadmin/cypher@EXAMPLE.ORG krbtgt/EXAMPLE.ORG@EXAMPLE.ORG kadmin.local: addprinc root/admin@EXAMPLE.ORG WARNING: no policy specified for root/admin@EXAMPLE.ORG; defaulting to no policy Enter password for principal "root/admin@EXAMPLE.ORG": Re-enter password for principal "root/admin@EXAMPLE.ORG": Principal "root/admin@EXAMPLE.ORG" created. ---- Then enable admins by modifying `kadm5.acl` file and restart Kerberos services. [source,text,indent=0] ---- # cat /etc/krb5kdc/kadm5.acl # This file Is the access control list for krb5 administration. */admin * ---- Now you can use `kadmin` with previously created `root/admin` principal. Lets create our first user `user1`. [source,text,indent=0] ---- kadmin: addprinc user1 WARNING: no policy specified for user1@EXAMPLE.ORG; defaulting to no policy Enter password for principal "user1@EXAMPLE.ORG": Re-enter password for principal "user1@EXAMPLE.ORG": Principal "user1@EXAMPLE.ORG" created. ---- Lets create our second user `user2` and export a keytab file. [source,text,indent=0] ---- kadmin: addprinc user2 WARNING: no policy specified for user2@EXAMPLE.ORG; defaulting to no policy Enter password for principal "user2@EXAMPLE.ORG": Re-enter password for principal "user2@EXAMPLE.ORG": Principal "user2@EXAMPLE.ORG" created. kadmin: ktadd -k /tmp/user2.keytab user2@EXAMPLE.ORG Entry for principal user2@EXAMPLE.ORG with kvno 2, encryption type aes256-cts-hmac-sha1-96 added to keytab WRFILE:/tmp/user2.keytab. Entry for principal user2@EXAMPLE.ORG with kvno 2, encryption type arcfour-hmac added to keytab WRFILE:/tmp/user2.keytab. Entry for principal user2@EXAMPLE.ORG with kvno 2, encryption type des3-cbc-sha1 added to keytab WRFILE:/tmp/user2.keytab. Entry for principal user2@EXAMPLE.ORG with kvno 2, encryption type des-cbc-crc added to keytab WRFILE:/tmp/user2.keytab. ---- Lets create a service ticket for tomcat and export credentials to a keytab file named `tomcat.keytab`. [source,text,indent=0] ---- kadmin: addprinc -randkey HTTP/neo.example.org@EXAMPLE.ORG WARNING: no policy specified for HTTP/neo.example.org@EXAMPLE.ORG; defaulting to no policy Principal "HTTP/neo.example.org@EXAMPLE.ORG" created. kadmin: ktadd -k /tmp/tomcat.keytab HTTP/neo.example.org@EXAMPLE.ORG Entry for principal HTTP/neo.example.org@EXAMPLE.ORG with kvno 2, encryption type aes256-cts-hmac-sha1-96 added to keytab WRFILE:/tmp/tomcat2.keytab. Entry for principal HTTP/neo.example.org@EXAMPLE.ORG with kvno 2, encryption type arcfour-hmac added to keytab WRFILE:/tmp/tomcat2.keytab. Entry for principal HTTP/neo.example.org@EXAMPLE.ORG with kvno 2, encryption type des3-cbc-sha1 added to keytab WRFILE:/tmp/tomcat2.keytab. Entry for principal HTTP/neo.example.org@EXAMPLE.ORG with kvno 2, encryption type des-cbc-crc added to keytab WRFILE:/tmp/tomcat2.keytab. ---- [[setupwinkerberos]] === Setup Windows Domain Controller This was tested using `Windows Server 2012 R2` [TIP] ==== Internet is full of good articles and videos how to setup Windows AD but these two are quite usefull http://www.rackspace.com/knowledge_center/article/installing-active-directory-on-windows-server-2012[Rackspace] and http://social.technet.microsoft.com/wiki/contents/articles/12370.windows-server-2012-set-up-your-first-domain-controller-step-by-step.aspx[Microsoft Technet]. ==== - Normal domain controller and active directory setup was done. - Used dns domain `example.org` and windows domain `EXAMPLE`. - I created various domain users like `user1`, `user2`, `user3`, `tomcat` and set passwords to `Password#`. I eventually also added all ip's of my vm's to AD's dns server for that not to cause any trouble. [source,text] ---- Name: WIN-EKBO0EQ7TS7.example.org Address: 172.16.101.135 Name: win8vm.example.org Address: 172.16.101.136 Name: neo.example.org Address: 172.16.101.1 ---- Service Principal Name(SPN) needs to be setup with `HTTP` and a server name `neo.example.org` where tomcat servlet container is run. This is used with `tomcat` domain user and its `keytab` is then used as a service credential. [source,text] ---- PS C:\> setspn -A HTTP/neo.example.org tomcat ---- I exported keytab file which is copied to linux server running tomcat. [source,text] ---- PS C:\> ktpass /out c:\tomcat.keytab /mapuser tomcat@EXAMPLE.ORG /princ HTTP/neo.example.org@EXAMPLE.ORG /pass Password# /ptype KRB5_NT_PRINCIPAL /crypto All Targeting domain controller: WIN-EKBO0EQ7TS7.example.org Using legacy password setting method Successfully mapped HTTP/neo.example.org to tomcat. ---- [appendix] == Troubleshooting This appendix provides generic information about troubleshooting errors and problems. [IMPORTANT] ==== If you think environment and configuration is correctly setup, do double check and ask other person to check possible obvious mistakes or typos. Kerberos setup is generally very brittle and it is not always very easy to debug where the problem lies. ==== .Cannot find key of appropriate type to decrypt [source,text] ---- GSSException: Failure unspecified at GSS-API level (Mechanism level: Invalid argument (400) - Cannot find key of appropriate type to decrypt AP REP - RC4 with HMAC) ---- If you see abore error indicating missing key type, this will happen with two different use cases. Firstly your JVM may not support appropriate encryption type or it is disabled in your `krb5.conf` file. [source,text] ---- default_tkt_enctypes = rc4-hmac default_tgs_enctypes = rc4-hmac ---- Second case is less obvious and hard to track because it will lead into same error. This specific `GSSException` is throws also if you simply don't have a required encryption key which then may be caused by a misconfiguration in your kerberos server or a simply typo in your principal. .Using wrong kerberos configuration {zwsp} + In most system all commands and libraries will search kerberos configuration either from a default locations or special locations like JDKs. It's easy to get mixed up especially if working from unix systems, which already may have default settings to work with MIT kerberos, towards Windows domains. This is a specific example what happens with `ldapsearch` trying to query Windows AD using kerberos authentication. [source,text] ---- $ ldapsearch -H ldap://WIN-EKBO0EQ7TS7.example.org -b "dc=example,dc=org" SASL/GSSAPI authentication started ldap_sasl_interactive_bind_s: Local error (-2) additional info: SASL(-1): generic failure: GSSAPI Error: Unspecified GSS failure. Minor code may provide more information (No Kerberos credentials available) ---- Well that doesn't look good and is a simple indication that I don't have a valid kerberos tickets as shown below. [source,text] ---- $ klist klist: Credentials cache file '/tmp/krb5cc_1000' not found ---- We already have a keytab file we exported from Windows AD to be used with tomcat running on Linux. Lets try to use that to authenticate with Windows AD. You can have a dedicated config file which usually can be used with native Linux commands and JVMs via system propertys. [source,text] ---- $ cat krb5.ini [libdefaults] default_realm = EXAMPLE.ORG default_keytab_name = /tmp/tomcat.keytab forwardable=true [realms] EXAMPLE.ORG = { kdc = WIN-EKBO0EQ7TS7.example.org:88 } [domain_realm] example.org=EXAMPLE.ORG .example.org=EXAMPLE.ORG ---- Lets use that config and a keytab to get initial credentials. [source,text] ---- $ env KRB5_CONFIG=/path/to/krb5.ini kinit -kt tomcat.keytab HTTP/neo.example.org@EXAMPLE.ORG $ klist Ticket cache: FILE:/tmp/krb5cc_1000 Default principal: HTTP/neo.example.org@EXAMPLE.ORG Valid starting Expires Service principal 26/03/15 09:04:37 26/03/15 19:04:37 krbtgt/EXAMPLE.ORG@EXAMPLE.ORG renew until 27/03/15 09:04:37 ---- Lets see what happens if we now try to do a simple query against Windows AD. [source,text] ---- $ ldapsearch -H ldap://WIN-EKBO0EQ7TS7.example.org -b "dc=example,dc=org" SASL/GSSAPI authentication started ldap_sasl_interactive_bind_s: Local error (-2) additional info: SASL(-1): generic failure: GSSAPI Error: Unspecified GSS failure. Minor code may provide more information (KDC returned error string: PROCESS_TGS) ---- This may be simply because `ldapsearch` is getting confused and simply using wrong configuration. You can tell `ldapsearch` to use a different configuration via `KRB5_CONFIG` env variable just like we did with `kinit`. You can also use `KRB5_TRACE=/dev/stderr` to get more verbose output of what native libraries are doing. [source,text] ---- $ env KRB5_CONFIG=/path/to/krb5.ini ldapsearch -H ldap://WIN-EKBO0EQ7TS7.example.org -b "dc=example,dc=org" $ klist Ticket cache: FILE:/tmp/krb5cc_1000 Default principal: HTTP/neo.example.org@EXAMPLE.ORG Valid starting Expires Service principal 26/03/15 09:11:03 26/03/15 19:11:03 krbtgt/EXAMPLE.ORG@EXAMPLE.ORG renew until 27/03/15 09:11:03 26/03/15 09:11:44 26/03/15 19:11:03 ldap/win-ekbo0eq7ts7.example.org@EXAMPLE.ORG renew until 27/03/15 09:11:03 ---- Above you can see what happened if query was successful by looking kerberos tickets. Now you can experiment with further query commands i.e. if you working with `KerberosLdapContextSource`. [source,text] ---- $ ldapsearch -H ldap://WIN-EKBO0EQ7TS7.example.org \ -b "dc=example,dc=org" \ "(| (userPrincipalName=user2@EXAMPLE.ORG) (sAMAccountName=user2@EXAMPLE.ORG))" \ dn ... # test user, example.org dn: CN=test user,DC=example,DC=org ---- [appendix] [[browserspnegoconfig]] == Configure Browsers for Spnego Negotiation === Firefox Complete following steps to ensure that your Firefox browser is enabled to perform Spnego authentication. - Open Firefox. - At address field, type *about:config*. - In filter/search, type *negotiate*. - Parameter *network.negotiate-auth.trusted-uris* may be set to default *https://* which doesn't work for you. Generally speaking this parameter has to replaced with the server address if Kerberos delegation is required. - It is recommended to use `https` for all communication. === Chrome With Google Chrome you generally need to set command-line parameters order to white list servers with Chrome will negotiate. - on Windows machines (clients): Chrome shares the configuration with Internet Explorer so if all changes were applied to IE (as described in E.3), nothing has to be passed via command-line parameters. - on Linux/Mac OS machines (clients): the command-line parameter `--auth-negotiate-delegate-whitelist` should only used if Kerberos delegation is required (otherwise do not set this parameter). - It is recommended to use `https` for all communication. [source,text] ---- --auth-server-whitelist="*.example.com" --auth-negotiate-delegate-whitelist="*.example.com" ---- You can see which policies are enable by typing *chrome://policy/* into Chrome's address bar. With Linux Chrome will also read policy files from `/etc/opt/chrome/policies/managed` directory. .mypolicy.json [source,json] ---- { "AuthServerWhitelist" : "*.example.org", "AuthNegotiateDelegateWhitelist" : "*.example.org", "DisableAuthNegotiateCnameLookup" : true, "EnableAuthNegotiatePort" : true } ---- === Internet Explorer Complete following steps to ensure that your Internet Explorer browser is enabled to perform Spnego authentication. - Open Internet Explorer. - Click *Tools > Intenet Options > Security* tab. - In *Local intranet* section make sure your server is trusted by i.e. adding it into a list.