Add a custom ca-cert to a java truststore in a snap

Hi there I’m trying to make a snap that can accept a custom CA in the java truststore. The JVM expects the truststore to be in the file

usr/lib/jvm/default-java/lib/security/cacerts

which is typically a symlink to

/etc/ssl/certs/java/cacerts

This can be installed into the snap using the ca-certs-java apt package. Great. However it’s installed onto a readonly filesystem. I need the cacerts file to be on the $SNAP_COMMON filesystem and then symlinked to the location that the JVM expects, noted above and on the readonly filesystem.

How to do this? I’ve tried using the install hook script, but obviously it’s too late in the lifecycle to create the symlink at that stage, since the $SNAP filesystem is already readonly. Within snapcraft.yaml I can’t reference $SNAP_COMMON, as I only get the directory of snapcraft itself.

Any help appreciated.

1 Like

You want to copy the content from $SNAP to $SNAP_DATA using the install hook and then use a layout to re-map the dir from $SNAP_DATA to the respective place in /etc…

tried that, got

layout:
  /etc/ssl/certs/java/cacerts:
    bind: $SNAP_COMMON/etc/ssl/certs/java/cacerts

but i get this error:

error: cannot perform the following tasks:
- Run install hook of "mysnap" snap if present (run hook "install": 
-----
cannot update snap namespace: cannot write to "/etc/ssl/certs/java/cacerts" because it would affect the host in "/etc/ssl"
snap-update-ns failed with code 1
-----)

how does your hook look like ?

(indeed you do not want to write to /etc from the hook, but only to SNAP_DATA (or _COMMON))

hooks/install contains this

mkdir -p ${SNAP_COMMON}/etc/ssl/certs/java
cp ${SNAP}/cacerts ${SNAP_COMMON}/etc/ssl/certs/java

hmm, you might want to use cp -a ... here to make sure it copies recursively, preserves ownership and symlinks and such …

i’d also try to set the layout one level up (to the java dir) and try if a symlink layout might perhaps work better

hum so I’ve adapted it to this:

#hook
mkdir -p ${SNAP_COMMON}/etc/ssl/certs/java
cp -a ${SNAP}/cacerts $SNAP_COMMON/etc/ssl/certs/java/cacerts
# snapcraft.yaml
...
layout:
  /usr/lib/jvm/java-11-openjdk-amd64/lib/security/cacerts:
    bind-file: $SNAP_COMMON/etc/ssl/certs/java/cacerts
...
   override-prime: |
        snapcraftctl prime
        cp usr/lib/jvm/java-11-openjdk-*/lib/security/cacerts .

it installs ok but there’s no cacert in the target directory

ll /snap/mysnap/x1/usr/lib/jvm/java-1.11.0-openjdk-amd64/lib/security/
total 0
drwxr-xr-x 2 root root  88 Feb 15 13:27 ./
drwxr-xr-x 6 root root 962 Feb 15 13:27 ../
lrwxrwxrwx 1 root root  60 Feb 14 17:13 blocked.certs -> ../../../../../../etc/java-11-openjdk/security/blocked.certs
lrwxrwxrwx 1 root root  61 Feb 14 17:13 default.policy -> ../../../../../../etc/java-11-openjdk/security/default.policy
lrwxrwxrwx 1 root root  69 Feb 14 17:13 public_suffix_list.dat -> ../../../../../../etc/java-11-openjdk/security/public_suffix_list.dat

but the file is there in $SNAP_COMMON

ll /var/snap/mysnap/common/etc/ssl/certs/java/
total 164
drwxrwxr-x 2 root root   4096 Feb 15 15:55 ./
drwxrwxr-x 3 root root   4096 Feb 15 15:55 ../
-rw-r--r-- 1 root root 156996 Feb 15 13:27 cacerts

I don’t get it

checking on my local ubuntu install, cacerts is actually an empty symlink shipped inside the package, but only populated from the dpkg postinst script through a dpkg-trigger call:

$ grep cert /var/lib/dpkg/info/openjdk-8-jre-headless\:amd64.postinst 
    # call update-ca-certificates-java
    dpkg-trigger update-ca-certificates-java

(this is openjdk-8 but i doubt newer ones are different)

so what you likely need to do during your build is to add your jdk package as build-package and then pull in the actual /etc/ssl/certs/java from the host into your build … for stage-packages the postinst scripts are explicitly disabled, so you wouldn’t get the properly populated dir from that …

nya but the file is there, I’m able to copy it to $SNAP/cacerts and I can verify that it is in there, also i’ve got this already in snapcraft.yaml:

    build-packages:
        - ca-certificates
        - ca-certificates-java
        - openjdk-11-jre-headless
        - wget
    stage-packages:
        - openjdk-11-jre-headless
        - ca-certificates-java

But for some reason the layout section doesn’t want to work, the symlink from $SNAP_COMMON/etc/ssl/certs/java/cacerts to $SNAP/usr/lib/jvm/java-11-openjdk-amd64/lib/security/cacerts just doesn’t seem to get made. Thanks for your help thus far,

Err, why would you want that ? What you want is a symlink from /etc/ssl/certs/java pointing to $SNAP_COMMON/etc/ssl/certs/java and populate the latter from the install hook

well that would be great but then I’m back to this error

cannot update snap namespace: cannot write to "/etc/ssl/certs/java/cacerts" because it would affect the host in "/etc/ssl"
snap-update-ns failed with code 1

have you tried to use the higher level dir like i suggested above ?

layout:
  /etc/ssl/certs/java:
    symlink: $SNAP_COMMON/etc/ssl/certs/java

I had previously tried like you suggested but with bind. Tried now with symlink but the same install time error. meh

Hmm, i wonder if /etc/ssl as a whole is blocked then… i could understand why we would prevent the top level to be protected, but not a subdirectory like the java one… @zyga do you happen to know more?

Any further thoughts here? I’m still blocked by this

I don’t remember off the top of my head. The problem with /etc is that it’s a literal bind-mount from real host /etc. Making things writable there is tricky, creating new entries is tricky. We do some “creative” things to work around this, but it’s not guaranteed.

Having said that, /etc/ssl/certs/java exists on my host so it should be fine to bind-mount to the directory. Does the reporter @grob also have that directory around?

One more note on the error message, it’s not from apparmor, it’s actually from our own system that is designed to not mutate the host unless we really explicitly want to, and the part about “because it would affect the host” is precisely from that piece.

1 Like

Having said that, /etc/ssl/certs/java exists on my host so it should be fine to bind-mount to the directory. Does the reporter @grob also have that directory around?

I’ll need to check as I was using a VM to build on, which I’ve since misplaced - let me dig about a bit and see if I can find it. But honestly I’m having trouble understanding why this is an issue - I thought the whole point of an overlay is that it offers an isolated view of the filesystem that only affects the snap and not the host?

Thanks for your insight and help