Monday, April 18, 2016

Kerberos with groups on Tomcat 7

To provide Single-SignOn (SSO) to your web service on a linux server in a windows environment (Active Directory) you can add a login filter to tomcat to accept Kerberos tokens. It's really quite simple, if you set it up correctly, which is not simple to figure out.

Ack: Compiled from http://portlandlanguagecraft.com/ and https://pixabay.com/en/chain-gold-power-connection-rights-307886/

I did it using a custom SPNEGO filter to also extract AD groups from the kerberos tokens.

Mostly follow https://tomcat.apache.org/tomcat-7.0-doc/windows-auth-howto.html but with many additional tweaks.

You will need to create/edit the following files (On Ubuntu):

/etc/tomcat7/web.xml
/etc/tomcat7/login.conf
/etc/tomcat7/krb5.conf
/etc/tomcat7/mykeytab.keytab
/usr/share/tomcat7/libs/spnego-r7.jar

(These files are also found in /var/lib/tomcat7/conf).

Step-by-step (First section just for login and second section for getting groups too):

AUTHENTICATION (Login):


Download the Spnego HTTP filter:
Available from https://sourceforge.net/projects/spnego/. Put the file under /usr/share/tomcat7/libs/spnego-r7.jar to make tomcat load it on startup.

On the Active Directory (AD) / Kerberos Key Distribution Center (KDC) / Windows server:

Add service user:
Add a service user to let you linux server "log in" and validate kerberos tokens.

Link SPN to service user:
SPN (Service Principal Names) are identifiers for users or hosts. We need to add the ones representing our server. NOTE: The SPN is case sensitive and you must use the same case everywhere. The command is on the form:
setspn.exe -A HTTP/<HOSTNAME> DOMAIN\<SERVICE USER>

So on your windows/AD server enter the following

setspn.exe -A HTTP/myserver DOMAIN\myserviceuser
setspn.exe -A HTTP/myserver.domain.local DOMAIN\myserviceuser

to link the SPN HTTP/myserver to the user myserviceuser.

Generate keytab:
The keytab is file which stores SPNs/usernames and password for them.

According to the apache tutorial you should do this on your windos/AD server using the ktpass tool. I found it was better to use the ktab.exe that comes with java on windows:
ktab -a HTTP/<HOSTNAME> <SERVICE USER PASSWORD> -k <OUTPUT FILE> -n 0
e.g.
ktab -a HTTP/myserver.domain.local myservicepassword -k mykeytab.keytab -n 0

Note the n 0 flag which sets key version number, it needs to be 0 for tomcat/Spnego to find the key.

Put the mykeytab.keytab file under /etc/tomcat7/mykeytab.keytab on your linux server.


We also need to make the computers in the windows network trust our server, to do this we can use Group Policy on the AD server. But we'll get back to that later.


On the linux server (as sudo):

Add a filter to /etc/tomcat7/web.xml:

<!-- ======================== SPNEGO filter ==============================-->
  <filter>
    <filter-name>SpnegoHttpFilter</filter-name>
    <filter-class>net.sourceforge.spnego.SpnegoHttpFilter</filter-class>

    <init-param>
        <param-name>spnego.allow.basic</param-name>
        <param-value>true</param-value>
    </init-param>
  
    <init-param>
        <param-name>spnego.allow.localhost</param-name>
        <param-value>false</param-value>
    </init-param>
  
    <init-param>
        <param-name>spnego.allow.unsecure.basic</param-name>
        <param-value>true</param-value>
    </init-param>
  
    <init-param>
        <param-name>spnego.login.client.module</param-name>
        <param-value>com.sun.security.jgss.krb5.initiate</param-value>
    </init-param>
  
    <init-param>
        <param-name>spnego.krb5.conf</param-name>
        <param-value>conf/krb5.conf</param-value>
    </init-param>
  
    <init-param>
        <param-name>spnego.login.conf</param-name>
        <param-value>conf/login.conf</param-value>
    </init-param>
  
    <init-param>
        <param-name>spnego.preauth.username</param-name>
        <param-value>SERVICE_USER_USERNAME</param-value>
    </init-param>
  
    <init-param>
        <param-name>spnego.preauth.password</param-name>
        <param-value>SERVICE_USER_PASSWORD</param-value>
    </init-param>
  
    <init-param>
        <param-name>spnego.login.server.module</param-name>
        <param-value>com.sun.security.jgss.krb5.accept</param-value>
    </init-param>
  
    <init-param>
        <param-name>spnego.prompt.ntlm</param-name>
        <param-value>true</param-value>
    </init-param>
  
    <init-param>
        <param-name>spnego.logger.level</param-name>
        <param-value>1</param-value>
    </init-param>
</filter>


You need to replace SERVICE_USER_NAME and SERVICE_USER_PASSWORD with the ones you use to create your keytab. spnego.allow.basic, spnego.prompt.ntlm are true to let users who haven't logged into windows to log in (WARNING: username and password are sent in cleartext/base64 to the linux server!). spnego.allow.unsecure.basic needs to be true if you don't use https, which you should do.

  <filter-mapping>
    <filter-name>SpnegoHttpFilter</filter-name>
    <url-pattern>*</url-pattern>
  </filter-mapping>


To apply filter to all files.


Create/edit the login.conf:

com.sun.security.jgss.krb5.initiate {
    com.sun.security.auth.module.Krb5LoginModule required;
};

com.sun.security.jgss.krb5.accept {
    com.sun.security.auth.module.Krb5LoginModule required
    doNotPrompt=true
    useKeyTab=true
    principal="HTTP/myserver.domain.local@DOMAIN.LOCAL"
    keyTab="/var/lib/tomcat7/conf/mykeytab.keytab"
    storeKey=true
    isInitiator=false;
};


Differently from the apache tutorial, the initate object should only contain the module, otherwise tomcat will throw a parse error on startup.


Create/edit the krb5.conf:

[libdefaults]
default_realm = DOMAIN.LOCAL
default_keytab_name = FILE:/etc/tomcat7/mykeytab.keytab
default_tkt_enctypes = rc4-hmac,aes256-cts-hmac-sha1-96,aes128-cts-hmac-sha1-96
default_tgs_enctypes = rc4-hmac,aes256-cts-hmac-sha1-96,aes128-cts-hmac-sha1-96
forwardable = true

[realms]
DOMAIN.LOCAL = {
        kdc = 192.168.1.5:88
}

[domain_realm]
domain.local = DOMAIN.LOCAL
.domain.local = DOMAIN.LOCAL

[login]
        krb4_convert = true
        krb4_get_tickets = false


The kdc parameter should support the hostname of the KDC/AD server e.g. kdc.domain.local, but mine had trouble with DNS lookup for it, luckily IP works fine.

NOTE: Make sure time is within a few minutes of the AD server, consider installing a NTP client to keep in sync.

AUTHORIZATION (roles/groups):


You will need to create/edit the following files (On Ubuntu):

/var/lib/tomcat7/webapps/ROOT/WEB-INF/web.xml
/usr/share/tomcat7/libs/bcprov-jdk15on-147.jar
/usr/share/tomcat7/libs/spnego-pac.jar

Active Directory adds a blob to their kerberos tokens called PAC (Privilege Attribute Certificate), which includes a users roles. We can extract these roles from our ticket so we don't have to do an additional LDAP request (which is the normal way).

To do this we need a custom build of the spnego library by Ricardo Martín Camarero (rickyepoderi) (see http://blogs.nologin.es/rickyepoderi/index.php?/archives/73-SPNEGOKerberos-in-JavaEE-PAC.html) which utilizes JaasLounge and Bouncy Castle ASN1 to extract the PAC roles.
I've added support for fetching a users kerberos token when using Basic Auth as well as adding compressed PAC from another library.

The spnego-pac source code is available from github (https://github.com/asmund1/spnego-pac), and the final binaries used in this project from https://github.com/asmund1/spnego-pac/blob/master/jars/spnego-pac.jar and https://github.com/asmund1/spnego-pac/blob/master/jars/bcprov-jdk15on-147.jar (additional library needed).

On the linux server (as sudo):

Copy spnego-pac.jar and bcprov-jdk15on-147.jar to /usr/share/tomcat7/libs/ so that tomcat loads it on startup. NOTE: Remove the original spnego jar if you have it there already.

The PAC contains only the numerical representation for each role for the user, you can use this directly in your servlets, but I added some aliases for my roles. I did this in the webapp web.xml, but it should work in the global web.xml too (/etc/tomcat7/web.xml):

    <context-param>
        <param-name>myserver_write_role</param-name>
        <param-value>S-1-5-21-123456789-1234567890-1234567890-1234</param-value>
        <description>Alias for write access role</description>
    </context-param>


To get the value for your roles, check your tomcat log (/var/lib/tomcat7/logs/catalina.out) after login using kerberos, the library prints the SIDs found for a user.
You might have to change the log level since they are printed at FINER level. Do this by appending
net.sourceforge.spnego.SpnegoAuthenticator = FINER
to the bottom of /etc/tomcat/logging.properties file and restarting tomcat.

GET USER AND ROLES (CODE):


The username/SPN of the logged in user and his/her roles are added to the request object, so to fetch them in Java servlets / JSP use the following lines of code:

For Java Servlet:
import javax.servlet.http.HttpServletRequest;
import javax.servlet.ServletContext;

public class MyServlet extends HttpServlet {

    @Override
    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {

        // Fetches username of logged in user
        req.getRemoteUser()

         // Check if user has write access
        ServletContext context = req.getServletContext();
        if (!req.isUserInRole(context.getInitParameter("myserver_write_role"))) {
            resp.sendError(resp.SC_FORBIDDEN);
            return;
        }
    }
}


For JSP:
<%= request.getRemoteUser() %>
and
<%= request.isUserInRole(request.getServletContext().getInitParameter("myserver_write_role")) %>

TRUSTED SERVER:


To get the kerberos token from windows you need to be on the trusted server list. Chrome and IE use a common list while firefox and other browsers have their own.

To add you server as trusted in IE (and Chrome) open Internet Options -> Security -> Local intranet -> Sites button -> Advanced button
Enter the url for your server and press the Add button.

To do the same for firefox do the following:
1. Open Firefox, and type "about:config" in the Address Bar.
2. In the Search field, type "negotiate".
3. Set the following fields:
      network.negotiate-auth.trusted-uris  myserver.domain.local
      network.negotiate-auth.delegation-uris myserver.domain.local

(https://bugzilla.mozilla.org/show_bug.cgi?id=520668)

To add  your server for IE and Chrome for all windows machines in the intranet, you can use Group Policy: https://www.serverknowledge.net/group-policy/adding-trusted-sites-internet-explorer-using-group-policy-gpo/

TEST:


On the linux server (as sudo):
Start/Restart tomcat server to load changes in config and load jars:
service tomcat7 restart

Then tail the log output for any errors:
tail -f /var/lib/tomcat7/logs/catalina.out

Then navigate to your server in IE/Chrome. You should not be prompted for username or password. If you go to the server from a non-windows logged on computer (e.g. mobile phone) you should get a popup asking you to enter username and password. If you enter the incorrect password you should get a white page, with correct credentials you should see your content.

Sources:

http://spnego.sourceforge.net/spnego_tomcat.html
http://spnego.sourceforge.net/pre_flight.html
http://spnego.sourceforge.net/reference_docs.html
http://spnego.sourceforge.net/client_keytab.html
http://spnego.sourceforge.net/ExampleSpnegoAuthenticatorValve.java
http://spnego.sourceforge.net/HelloKeytab.java
https://sourceforge.net/p/spnego/discussion/1003769/thread/98e5ea01/

http://jaaslounge.sourceforge.net/howto/SSO_Tomcat_Howto.pdf
http://www.oracle.com/technetwork/articles/idm/weblogic-sso-kerberos-1619890.html
http://stackoverflow.com/questions/20152000/get-ad-groups-with-kerberos-ticket-in-java
https://tomcat.apache.org/tomcat-7.0-doc/realm-howto.html#JNDIRealm
http://docs.oracle.com/javase/7/docs/technotes/guides/security/jgss/lab/part6.html
http://kerberos.996246.n3.nabble.com/kinit-Cannot-contact-any-KDC-for-realm-EXAMPLE-COM-while-getting-initial-credentials-td19145.html
http://serverfault.com/questions/166768/kinit-wont-connect-to-a-domain-server-realm-not-local-to-kdc-while-getting-in
http://stackoverflow.com/questions/31877027/kerberos-cannot-find-key-of-appropriate-type-to-decrypt-ap-rep-rc4-with-hmac

https://docs.google.com/document/d/1G7WAaYEKMzj16PTHT_cIYuKXJG6bBcrQ7QQBQ6ihOcQ/edit#heading=h.yh8m8tkjdx9h
http://stackoverflow.com/questions/3568635/android-authenticating-with-kerberos

http://stackoverflow.com/questions/2518256/override-intranet-compatibility-mode-ie8

Friday, April 15, 2016

Share one file

Frequently I have two computers next to each other and I want to copy a file or directory from one to the other. What do you do? Mail it to yourself, sync it via some cloud service, set up samba? No just use woof (http://www.home.unix-ag.org/simon/woof.html).

 

A tiny python server that serves one file and then shuts down as soon as the file has been downloaded. (You can also share a directory as a tar archive).

Just do:
python woof.py 'file to share'

Enter the resulting URL e.g.:
http://10.0.0.94:8080/IMG_0017.jpg
in a web browser on the other machine
and voila.

NOTE: the computers need to be on the same network. + you need to be running linux/unix with python 2.7.x.

Wednesday, April 6, 2016

Framebuffer screen dumps to PNG and back

I've got a nice card with an Atmel AVR32 processor and a nice big 320x240 (monochrome!) screen. :)
I can put data on the screen from buildroot linux by writing directly to the framebuffer, /dev/fb0. However the data there is in raw format. Here is how to dump from screen. Then convert, using ffmpeg, the raw data to a PNG and then back to raw data after editing:

  1. Get a screenshot of the framebuffer using cat /dev/fb0 > screendump.bin.
  2. Convert to PNG using ffmpeg: ffmpeg   -vcodec rawvideo   -f rawvideo   -pix_fmt monow   -s 320x240   -i screendump.bin     -f image2   -vcodec png screendump.png
    This will read raw video from the screendump.bin, handle it as pixel monochrome data from a 320x240 screen and save as sreendump PNG file.
  3. Next, open in Gimp or similar to edit. Save as PNG afterwards.
  4. Convert to rawvideo binary again using ffmpeg: ffmpeg -vcodec png -i savedimage.png -vcodec rawvideo -f rawvideo -pix_fmt monow savedimage.bin
  5. Put the data on the screen: cat savedimage.bin > /dev/fb0
Use ffmpeg -pix_fmts to see other raw video types to convert from/to.

Sources: Check out these great sites for more:
http://www.catswhocode.com/blog/19-ffmpeg-commands-for-all-needs
http://forum.videohelp.com/threads/334333-Help-with-lossless-ffmpeg-command-%5Bvideo-png-back-to-video%5D
http://stackoverflow.com/questions/3781549/how-to-convert-16-bit-rgb-frame-buffer-to-a-viewable-format
https://community.freescale.com/docs/DOC-100347

Remote Desktop over the Internet using UltraVNC on Windows

Here is a HowTo for setting up a UltraVNC server on your computer, automatically open needed ports, and creating a configured UltraVNC single-click client to let you see someones desktop where all they have to do is start the client you send to them. No installations!
(A great alternative is TeamViewer which is free for personal use, but expensive for commercial use)

Here is how:

- Download UltraVNC and install:
See bottom of: http://www.uvnc.com/downloads/ultravnc.html

- Download UltraVNC SingleClick custom.zip package from:
http://www.uvnc.com/pchelpware/sc/create.html

- Extract helpdesk.txt file.

- Edit to look like you want to.
Example helpdesk.txt:

[TITLE]
UltraVnc SC

[HOST]
My RDC connection
-connect 158.158.158.158:5900 -noregistry

[TEXTTOP]
Double click to open connection

[TEXTMIDDLE]

[TEXTBOTTOM]


[TEXTRBOTTOM]


[TEXTRMIDDLE]


[TEXTRTOP]


[TEXTBUTTON]
UltraVNC web

[WEBPAGE]
http://www.ultravnc.net

[TEXTCLOSEBUTTON]
Cancel


[BALLOON1TITLE]
Establishing connection ...

[BALLOON1A]
5 min connection attempt

[BALLOON1B]
If it fails, the software will close.

[BALLOON1C]


[BALLOON2TITLE]
Connection active.

[BALLOON2A]
Warning, your desktop is visible remotely

[BALLOON2B]
You can break the connection any time

[BALLOON2C]
by using the close button



(remember to replace 158.158.158.158 with your external IP, e.g. from https://www.whatismyip.com/)

- Create a ZIP file with only helpdesk.txt in it.

- Go to:
http://support1.uvnc.com/cgi-bin/upload4.pl

- Enter user: foo
- Enter password: foobar
- Select Zip file containing helpdesk.txt
- Click Upload
- Wait until you a link to download exe file appears. Then download the exe file.
- Start it to see what it looks like.


- Download UPnP PortMapper to open the needed port on your computer (if you havent done so manually on your router).
https://sourceforge.net/projects/upnp-portmapper/files/latest/download
https://github.com/kaklakariada/portmapper

- Open PortMapper jar file (should need only double-click if you have Java installed).
- Under the section "Port Mapping presets", select Create button.
- Enter a description, e.g. UltraVNC.
- Then click Add button.
- Change port 1 to 5900 in both External Port and Internal Port columns.
- Not sure if you also need UDP port open, but might be useful so click Add again and select UDP with port 5900 internal and external.
- Click save.

- Click connect to connect to your router.
When successfully connected (you might get an error, but stuff seems to work regardless).
- Select your UltraVNC entry in the list and press "Use".
- Wait for ports to show up in the list above.

Now you have ports open. You need to start VNC listener:
- Open command line prompt (cmd in windows start menu) and navigate to where you installed UltraVNC and run it with flag -listen 5900 to tell it to listen on port 5900:

cd "C:\Program Files\uvnc bvba\UltraVnc"
vncviewer.exe -listen 5900


- Finally, send the exe file you generated from helpdesk.txt (not the zip file) to the person you want to connect to. Get the person to open the file and double click on the top option and you should get a message on your computer asking you to accept the connection.


Source:
http://www.uvnc.com/docs/uvnc-sc/76-how-to-setup-and-configure-a-custom-Ultra%20VNC-sc.html

Sites to read about AngularJS and Ionic

Top 10 angularjs mistakes:
https://www.airpair.com/angularjs/posts/top-10-mistakes-angularjs-developers-make#9-manual-testing


Best practice dir structure angularjs/ionic:
https://scotch.io/tutorials/angularjs-best-practices-directory-structure
I've tried both. Unsure which one I think is best. I think the most important point is to create a directory structure to group your files according to how they are used. So that other people can understand how you program is structured more quickly.


Tips for ionic:
http://www.betsmartmedia.com/what-i-learned-building-an-app-with-ionic-framework (https://web.archive.org/web/20150925034548/http://www.betsmartmedia.com/what-i-learned-building-an-app-with-ionic-framework)


Intro and how to use jasmine unit test framework:
http://jasmine.github.io/edge/introduction.html
Great test tool!

Promises in AngularJS

 

Introduction to AngularJS promises (as a cartoon):
http://andyshora.com/promises-angularjs-explained-as-cartoon.html

Promise anti-patterns
Flatten chaining, clean up, and broken chains:
http://taoofcode.net/promise-anti-patterns/

HTTP promise not like Q promise, use deferred:
http://weblog.west-wind.com/posts/2014/Oct/24/AngularJs-and-Promises-with-the-http-Service

AngularJS Q:
https://docs.angularjs.org/api/ng/service/$q

Nice features to put on headless linux box

 

Unattended-upgrades:

Automatically download and install stable updates.

Install using:
sudo apt-get install unattended-upgrades
Configure using:
sudo dpkg-reconfigure -plow unattended-upgrades

Source:
http://raspberrypi.stackexchange.com/questions/4698/how-can-i-keep-my-raspbian-wheezy-up-to-date
https://help.ubuntu.com/community/AutomaticSecurityUpdates


Fail2ban:

http://www.fail2ban.org/wiki/index.php/Main_Page
https://en.wikipedia.org/wiki/Fail2ban





Imapgrab:

To automatically backup email:


http://www.linux-magazine.com/Online/Blogs/Productivity-Sauce/Back-up-Email-with-a-Single-Command
https://sourceforge.net/projects/imapgrab/ 

Android LayerDrawable in HTML using CSS

So a neat feature for Android is the LayerDrawable which lets you make an image compiled from other images. You define an array of images that shuld be drawn on top of each other and receive the resulting image.
This feature exists in CSS too. It's called background-image and lets you define one or more images to be displayed as a background of a container.

background-image: url(front.png), url(behind.png);





Remember to make the container a block element and if you don't have any content in it you should set the container height and width or a padding-bottom to give it size (Otherwise you will be shown approximately 0px of your background). See http://stackoverflow.com/questions/1495407/css-a-way-to-maintain-aspect-ratio-when-resizing-a-div

.container-with-background-image {
    width: 100%;
    padding-bottom: 75%;
}


Source:
http://www.css3.info/preview/multiple-backgrounds/
http://stackoverflow.com/questions/5846637/why-an-inline-background-image-style-doesnt-work-in-chrome-10-and-internet-ex