some time ago, as a part of my Voron activities, i decided to add a camera to monitor the print. it was an interesting quest to find nice, and easy to use options, while minimizing external dependencies. finally i've settled for MJPEG + reverse proxy on nginx… but that's for another blog entry. :)
here, the interesting part was client side. since i used nginx
it was very natural to just do HTTPS + basic auth for auth and encryption.
ffmpeg has a built-in support for both MJPEG and basic auth, so it was supposed to be a smooth sailing… however there was a small catch – basic auth is passed as a part of the URI, e.g.:
https://user:mysecret@camera.address/some_stream
combining this with ffmpeg
was straight forward:
ffmpeg \ -f mjpeg -framerate 25 -r 25 -i https://user:mysecret@camera.address/some_stream \ -vcodec copy -an \ output.avi
…and guess what happens when you run ps
command? yes – the password is in clear text. it turns out that ffmpeg
does not sanitize the input secret. it also does not have any option
the next, natural option was to use curl:
curl https://user:mysecret@camera.address/some_stream | ffmpeg -i - ...
then ps
… and still the same – secret is there. :/
interestingly enough – there was no way to read a series of links from a file in curl
. i guess the assumption was that one can always do a for
loop in shell, or similar. it however does not cover my case of hiding basic auth details.
the next natural take was:
curl -O - https://user:mysecret@camera.address/some_stream | ffmpeg -i - ...
as you probably already expect – password was still visible in process list. this time however, wget
was an ace upon its sleeve – it can read links from file!
it's now enough to prepare a file:
touch ~/.my_camera.cfg chmod $_ vim $_
and put our magic string here:
https://user:mysecret@camera.address/some_stream
now to run it just use the input file, instead of a direct link:
wget -O - -i ~/.my_camera.cfg | ffmpeg -i - ...
and voila! stream is being recorded, while password is ~secure in a config file.
to make thins complete we add 2 more things on top:
#!/bin/bash set -eu -o pipefail app=$(basename "$0") crt="$HOME/.config/camera/mycam.crt" link="$HOME/.config/camera/mycam.link" # << secret password lives here outdir_base="$HOME/recordings" fps=25 outdir="$outdir_base/$(date +%Y-%m-%d_%H%M%d)" mkdir -p "$outdir" echo "$app: recording to $outdir" wget_cmd=(wget) wget_cmd+=(--quiet) wget_cmd+=(--timeout=5) wget_cmd+=(--ca-certificate "$crt") wget_cmd+=(-O -) wget_cmd+=(-i "$link") ffmpeg_cmd=(ffmpeg) ffmpeg_cmd+=(-f mjpeg) ffmpeg_cmd+=(-framerate "$fps") # input frame rate - apparently this is what source provides atm... ffmpeg_cmd+=(-i -) ffmpeg_cmd+=(-r "$fps") # output frame rate # compress input ffmpeg_cmd+=(-vcodec h264 -preset fast -crf 23) # disable audio (if present) ffmpeg_cmd+=(-an) # segment output ffmpeg_cmd+=(-f segment) ffmpeg_cmd+=(-segment_time 600) # seconds ffmpeg_cmd+=(-reset_timestamps 1) ffmpeg_cmd+=("$outdir/%06d.avi") "${wget_cmd[@]}" | "${ffmpeg_cmd[@]}"
while the need to combine ffmpeg
with downloader was anyway a must have, due to custom HTTPS certificates used for the local connection, the lack of easy basic auth covering up was a bit of a surprise to me. while it's kinda ok for app not overwriting password in its argv
(it can give a false sense of security, as there's still a race when starting application – password is briefly visible then), not being able to pass it via some env or a secrets file was a bit of a head-scratcher for me.
the good news is that FLOSS and the UNIX way of combining things with pipes once again shined and saved the day! :)