Ubuntu Kiosk

This post is a work in progress. I’ll update it as I tweak the solution.

Last Wednesday I was helping a friend build a Kiosk. We tried to follow https://thepcspy.com/read/building-a-kiosk-computer-ubuntu-1404-chrome/ but it didn’t work. It turns out between using the wrong version of ubuntu (16.04 instead of 14.04) and doing it in a virtual machine, we were all messed up.

There has to be a better way.

There is a secret to debian/ubuntu packages. If you aren’t trying to get them included in debian/ubuntu, you can break most of the rules and get them to do whatever you want. I figured I should be able to use this and make creating a kiosk as easy as apt install kiosk

TL;DR: you can try this by running these two commands on a new ubuntu-server installation:

add-apt-repository ppa:evarlast/kiosk
apt install --no-install-recommends kioskme

The rest of the post describes how I did this.

First, I’m going to create a new PPA on launchpad just for this, so that a user can `add-apt-repository ppa:evarlast/kiosk`

I visit https://launchpad.net/~/+activate-ppa and fill in the fields with kiosk and click activate.

Next, I start a new deb. I may as well build it from source. There might be a better way, but I’ve gotten to know dh (debhelper) a bit, so I’m going to use it.

$ mkdir kioskme ; cd kioskme
$ cat > Makefile
build:
<tab>echo noop
install:
<tab>install -d 755 ${DESTDIR}/usr/bin
<tab>install -m 755 kioskme ${DESTDIR}/usr/bin/kioskme
^D
$ cat > kioskme
#!/bin/bash
xset -dpms
xset s off 
openbox-session & 
start-pulseaudio-x11 
while true; do 
  rm -rf ~/.{config,cache}/chromium/ 
  chromium-browser --kiosk --no-first-run 'http://duckduckgo.com' 
done
^D

Now debianize this script directory using dh_make:

dh_make -p kioskme_0.0.0 --createorig -s

Now customize the deb with a service, preinst for user creation and some dependencies:

$ cat > debian/service
[Unit]
Description=kioskme

[Service]
Type=simple
Restart=on-failure
User=kioskme
Group=kioskme
ExecStart=/usr/bin/startx /etc/X11/Xsession /usr/bin/kioskme
^D
$ cat > debian/preinst
#!/bin/sh

set -e

. /usr/share/debconf/confmodule

case "$1" in
 install|upgrade)
 if ! getent group kioskme >/dev/null; then
 addgroup --system kioskme >/dev/null
 fi
 if ! getent passwd kioskme >/dev/null; then
 adduser \
 --system \
 --disabled-login \
 --ingroup kioskme \
 --gecos kioskme \
 --shell /bin/false \
 kioskme >/dev/null
 fi
 mkdir -p /var/log/kioskme
 chown kioskme:kioskme /var/log/kioskme
 setfacl -m u:kioskme:rw /dev/tty0 /dev/tty7
 ;;

 abort-upgrade)
 ;;

 *)
 echo "preinst called with unknown argument \`$1'" >&2
 exit 1
 ;;
esac

# dh_installdeb will replace this with shell code automatically
# generated by other debhelper scripts.

#DEBHELPER#

exit 0

Alright, maybe that preinst is a bit big. I copy it around and fill it out like a template for services I put into debs.

Now edit the debian/control file to add dependencies, change the section to utils, fill in whatever else you want, set Depends to look like this:

Depends: ${shlibs:Depends}, ${misc:Depends}, Xorg, openbox, chromium-browser, pulseaudio

Now create the deb:

fakeroot debian/rules clean build binary

To test the deb, I copy it to a fresh ubuntu server install and dpkg -i to install it. I get a bunch of errors because dpkg -i doesn’t resolve dependencies, but I run apt install -f and the dependencies are installed.

Once I tested and tweaked and got things working, I updated the tarball `tar -Jcf ../kioskme_0.0.0-0.orig.tar.xz -C ..  –exclude=’debian’ kioskme` and I used dpkg-buildpackage -S to build a source package and then I used dput ppa:evarlast/kiosk ../kioskme_0.0.0-1_source.changes to upload to PPA.

Now, this still does not work in a VM. Ubuntu desktop installer must do some magic to make X work in a virtual machine with a driver which works with VMWare, VirtualBox, or Parallels.

 

Some LXD containers on a hidden net, others on your lan

Back in November I wrote about Converting eth0 to br0 and getting all your LXC or LXD onto your LAN

It works, but you might not want ALL of your LXD on your LAN.

You’ll still need your LAN interface to be a br0 instead of a device that isn’t a bridge. Go follow the Bridge your interface section of that post to convert your eth0 to br0.

I’ve fully converted to using LXD. I don’t even remember if LXC supports profiles. I think it does, so I think the same idea could be applied to LXC, but I’m only showing this for LXD.

First, copy the default profile:

lxc profile copy default lanbridge

Second, edit the new profile to use br0 instead of lxdbr0:

lxc profile device set lanbridge eth0 parent br0

Third and finally, start instances with that profile:

lxc launch ubuntu-xenial -p lanbridge

In my case, this instance is on my local lan AND on public ipv6 space (thanks Comcast).

heritable-gale    | RUNNING | 192.168.15.172 (eth0) | 2601:400:8000:5ab3:216:3eff:fe73:d242 (eth0)

 

Cloud-config with LXD

A year ago I wrote http://jrwren.wrenfam.com/blog/2015/05/26/ubuntu-cloud-image-based-containers-with-lxc/

Since then, LXD became the best way to use LXC.

By default, LXD already uses ubuntu-cloudimg images.

The lesser know feature is using cloud-config with LXD. It turns out it is very easy to pass user-data to an LXD instance when you start it, just like you would on any cloud provider.

LXD even has the -e option to make your LXD instance ephemeral. It will be deleted automatically when you stop it.

Just like in that previous blog post, I create a file named one.yaml. The name can be anything. Then i start it:

lxc launch ubuntu:14.04 crisp-Hadley -c user.user-data="$(cat one.yaml)"

That is all there is to it.

Here is an example of config similar to what I used recently to QA a build configuration:

#cloud-config
output:
 all: "|tee -a /tmp/cloud.out"
#hostname: {{ hostname }}
bootcmd:
 - rm -f /etc/dpkg/dpkg.cfg.d/multiarch
apt_sources:
 - source: ppa:yellow/ppa
ssh_import_id: [evarlast] # use -S option
packages:
 - make
final_message: "The system is finally up, after $UPTIME seconds"
runcmd:
 - cd /home/ubuntu
 - git clone https://www.github.com/jrwren/myproject
 - cd myproject
 - make deps run

Inverse Multimatch Source Address Blocking With iptables and ipset

I wanted to block all traffic on port 22 except for a few hosts that I use.

I was tried of seeing lots of stupid worm attack traffic on my EC2 host.

Jun  9 19:02:45 ip-172-30-4-108 sshd[5004]: Invalid user cisco from 218.85.133.73
Jun  9 19:02:45 ip-172-30-4-108 sshd[5004]: input_userauth_request: invalid user cisco [preauth]
Jun  9 19:02:46 ip-172-30-4-108 sshd[5004]: Connection closed by 218.85.133.73 port 9224 [preauth]

Yes, I could use security groups, but then I’d have to use security groups.

iptables ! -s with,more,than,one,address fails

iptables v1.6.0: ! not allowed with multiple source or destination IP addresses

The alternative is to use ipset. Its not hard!

ipset create ssh-ok hash:ip
ipset add ssh-ok mine.example.com
ipset list  # is this thing working, just checking.
ipset add ssh-ok myfriend.example.com
ipset add ssh-ok mywork.example.com
ipset list  # still working, ok looks good.

iptables -A INPUT -m set \! --match-set ssh-ok src -p tcp --dport 22 -j DROP

Thanks for the help:

http://daemonkeeper.net/781/mass-blocking-ip-addresses-with-ipset/

http://unix.stackexchange.com/questions/70917/iptables-multiple-exclusions-on-port-forwarding

jq Is the grep, sed and awk for json

The only problem with jq is that its not installed by default in ubuntu or ubuntu-server. Its not in the default ubuntu-cloudimg. One must apt-get install jq.

https://stedolan.github.io/jq/manual/ says, “jq is a lightweight and flexible command-line JSON processor.

In working with juju, we work with json formatted cookies in a ~/.go-cookies file. Sometimes we need to investigate these cookies to develop, verify, and debug our services.

An unexpired cookie value might be as good as a password or authentication token and so for the purpose of our debugging sometimes everything but the value is good enough. The jq filter ‘.[]|del(.Value)‘ strips all of the .Value properties from every object in the input array. This results in:

{
“Name”: “macaroon-a40e7abc65a78faf130dc652d45052c1c8b5b4aeff8181f44a15175b6525558f”,
“Domain”: “api.staging.example.com”,
“Path”: “/identity/”,
“Secure”: false,
“HttpOnly”: false,
“Persistent”: true,
“HostOnly”: true,
“Expires”: “2016-05-09T19:52:21Z”,
“Creation”: “2016-04-11T15:52:21.466266522-04:00”,
“LastAccess”: “2016-04-11T15:52:21.928768825-04:00”,
“Updated”: “2016-04-11T15:52:21.928768825-04:00”,
“CanonicalHost”: “api.staging.example.com”
}
{
“Name”: “macaroon-a605d07b7a95ba7e57a267ed507f673bce1188d0de7f544074f1c33ec4a8ff2a”,
“Domain”: “www.example.org”,
“Path”: “/identity/”,
“Secure”: false,
“HttpOnly”: false,
“Persistent”: true,
“HostOnly”: true,
“Expires”: “2016-05-03T21:46:35Z”,
“Creation”: “2016-04-05T17:46:36.351842179-04:00”,
“LastAccess”: “2016-05-02T15:11:10.525298848-04:00”,
“Updated”: “2016-04-05T17:46:36.351842179-04:00”,
“CanonicalHost”: “www.example.org”
}
{
“Name”: “macaroon-authn”,
“Domain”: “www.example.org”,
“Path”: “/NEENR/”,
“Secure”: false,
“HttpOnly”: false,
“Persistent”: true,
“HostOnly”: true,
“Expires”: “2016-05-03T19:11:09.794240373Z”,
“Creation”: “2016-05-02T15:11:10.592852105-04:00”,
“LastAccess”: “2016-05-02T15:23:26.813664654-04:00”,
“Updated”: “2016-05-02T15:11:10.592852105-04:00”,
“CanonicalHost”: “www.example.org”
}

Now lets say you want to remove the cookie with the Path value “/NEENR/”.

The jq filter: ‘.[] | select(.Path!=”/NEENR/”)’ does that job.

These examples show filter and map, but what about reduce?

Min, max, min_by and max_by are nice default reducers.

  • min_by(.Expires) shows the next expiring cookie.
  • max_by(.Created) shows the most recently created cookie.
  • [.[]|.Expires]|max if you don’t care about the rest of the cookie and just want the max date.
  • [.[]|.Expires]|min if you just want the min date.

See the Array Construction section of the manual for the details on the syntax. I like to think of it as the .[]|.NAME returns elements and if I want them in an array I wrap it in [] for array construction.

jq is a sweet tool that I’m glad to have in my toolbox.

Version from debian/changelog

Almost two years ago I did some scripting of updating debian/changelog and building a package to enable a CI environment for some software. I wanted to parse the changelog correctly and so I copied and changed some perl from the source of dpkg-buildpackage. This turned out to be the wrong solution.

There is a nice tool called dpkg-parsechangelog. You can get just the version for use in scripts with this simple awk:

dpkg-parsechangelog | awk ‘/Version/ { print $2 }’

I didn’t even think to write about it until I ran across someone else who’d written some perl to do exactly the same thing. Dear world, we need to stop reinventing this wheel.

Ubuntu Xenial 16.04 Has All The Good Stuff

A couple of days ago, Ubuntu Xenial was released. There is a press release with some good stuff in it.

I’ve been looking forward to this release for the following reasons:

  • Postgresql 9.5
  • systemd
  • haproxy 1.6.3
  • uwsgi 2.0.12
  • nginx 1.9.15

I know, it doesn’t look that exciting until you recall that the last LTS release of Ubuntu, Trusty, 14.04, was missing fabulous features OOTB in each of these components.

Postgresql 9.3 did not have the the awesome JSONB improvements of 9.4 and 9.5

haproxy 1.4 didn’t have ssl support.

uwsgi… well latest uwsgi is just always great to have.

nginx 1.9.15 has http2 support, out of the box!

Finally, while I loved upstart, systemd is nice and has been rock solid.

This is the greatest Ubuntu ever. I’ve not even mentioned how awesome lxd is on it. That is covered elsewhere. This is just my personal little list. Thanks Ubuntu.

MacGyvering Windows 8.1 Remote Assistance

My Mother called me up rather frazzled this evening.

This isn’t too surprising. Since her stroke 16 years ago she can sometimes become confused or forget simple things, things she once knew.

Tonight, the cause of her frazzled state was her computer.

After listening to her rant and ramble about her computer, I quickly realized that she had some web browser pop-up phishing telling her she had a virus. Partly because of who she is, and partly because of brain damage from stroke, she called the phone number that the pop-up displayed. When they told her they can fix it for $199 and if she took it to Best Buy, they would charge her $350-$400, this fueled her worry.

After some calming I finally had her start the Windows Remote Assistance application, but unfortunately she has forgotten what saving files actually means and she has no email configured. So she is unable to save the remote assist file and she can’t use Windows Remote Assistance to automatically email the request to me. It was at this point that I suggested she mail the laptop to me. I also may have said, “never again!” when I agree to support a laptop that someone else gifted her.

But, I couldn’t let it go. This was a challenge and I love a challenge.

I searched around a bit and tried my hand at the msra.exe command line. After a bit of trial and error, I realized I can have her open a powershell and type

msra /saveasfile helpme 12345678

Yes, I’m ok with the 12345678 password in this case. Trying some other password over the phone and having her type it was error prone.

“Did you say bee?”

“No I said pee, like Paul.”

“Bee like ball?”

“No…”

I still needed a way to get a file to me. I’ve had an aversion to PowerShell ever since it launched, despite tech reviewing a very fine PowerShell book. I knew it was probably my best bet at getting a file to me. After a bit of poking I found the invoke-webrequest helper, thingy. I don’t know PowerShell terminology. It looks like a function to me.

I have my home server on the internet. Its running Ubuntu Linux  and I’ve had 4 line php upload scripts with html forms that let people send me files for years. Could I use this?

The shoelace was there. The paperclip was there. Did I also have some bubble gum?

All I really needed as an index.php in a /mom/ directory that looked like this:

<?php
file_put_contents('err.out', file_get_contents('php://input'));
?>

Wow that is some trivial stuff. Bland bubble gum, I guess.

Why an index.php and a /mom/? Well, because that will be easy for me to relay over the telephone.

I did some testing and found invoke-webrequest works nicely coupled with this http request body dumping php.

invoke-webrequest -uri jrwren.xmtp/mom/ -infile .\helpme
.msrcincident -method post

I was able to call my mom back, tell her, to press windows key-r, reminding her that windows key is usually between the ctrl and alt on the keyboard, and to type powershell and press enter.

“Powershell, P-O-W-E-R-S-H-E-L-L- no spaces?”

“Yup”

On first try, I tried to have her use the password 1234, but msra.exe complained that it was too short. Working through this mistake, I tried to have her use the up arrow to edit the previously executed command line in powershell.

“What is the up arrow?”

This honestly dumbfounded me and I had absolutely no idea what to do for a minute or so.

“The up arrow on my keyboard is on the right. There is an inverted tee of arrows, left right up down to the left of my left control key.”

Whew, I got lucky and she found it.

Once we had the msra.exe create the helpme file, I had her type out the invoke-webrequest command, prompting her to press tab after typing helpme to autocomplete the file extension.

The multiline color output of running the command shocked and surprised her. It maybe even scared her a little bit, but as she was reading it aloud, I heard her say, “200 OK”

“200 OK is great”, I said.

I checked my server and there was an err.out file along side the index.php. The only two files in the mom directory.

My home server always has samba setup. I used Windows Explorer to navigate to H:\public_html\mom and I renamed err.out to helpme.msrcincident. I double clicked it.

Mom said, “Oh what is this? jrwren wants to share your computer.”

I rejoiced inside.

The hard part being done, I was able to connect and control her computer. Microsoft has done a very nice job with Windows Remote Assist, ever since Windows 7. I’m impressed that my Windows 7 can connect flawlessly to her Windows 8.1. I’m thankful that PowerShell is out of the box in all versions of windows. I do not think I’d have been able to walk her through this over the phone with this few keystrokes without PowerShell.

To the evil con artists who extort money from poor little old disabled ladies who work two jobs: please stop.