Version 1.9.16 of sudo will feature a new option for logging: json_compact. Why is this important? This new format can easily be read and parsed by a log management software, like syslog-ng.

Note that in this blog I am showing you a sudo feature which has not yet been released officially. You have to compile sudo yourself. By all means, if you have any other application writing JSON-formatted log messages, you can apply most of what you read here with slight modifications.

Before you begin

You need JSON-formatted log messages. This blog is about working with the json_compact logs from sudo. You need version 1.9.16 or later for this, or you can also compile sudo yourself from git sources. It is described in my latest sudo blog at https://www.sudo.ws/posts/2024/04/when-it-comes-to-sudo-logging-pretty-is-not-always-better/ .

Naturally, you can also use any other JSON-formatted logs, as long as each message takes a single line.

Configuring sudo

You can enable the new json_compact logging format in sudo for log files (JSON formatting for syslog always uses a variant of the compact format) by adding these two lines to your sudoers file using visudo:

Defaults log_format=json_compact
Defaults logfile=/var/log/czpsudo2

Certainly, the name of the file could be anything. I use my initials in file names to make sure they are unique and do not collide with existing names on the system.

Once you have saved the sudoers file, you should test sudo. Run something using sudo and check the content of the file you just specified in the sudoers file. Instead ofreadable, multi-line messages, you should see single-line JSON-formatted messages like this:

{"accept":{"uuid":"8cdfaea5c1-df69-44b6-180a-7fb1378d94","server_time":{"seconds":1712645078,"nanoseconds":588541414,"iso8601":"20240409064438Z","localtime":"Apr  9 08:44:38"},"submit_time":{"seconds":1712645078,"nanoseconds":587941154,"iso8601":"20240409064438Z","localtime":"Apr  9 08:44:38"},"submituser":"czanik","command":"/usr/bin/ls","runuser":"root","runcwd":"/home/czanik","source":"/etc/sudoers:66:23","ttyname":"/dev/pts/0","submithost":"leap154b","submitcwd":"/home/czanik","runuid":0,"columns":118,"lines":60,"runargv":["ls","/root/"],"runenv":["LANG=en_US.UTF-8","COLORTERM=1","TERM=xterm-256color","MAIL=/var/mail/root","PATH=/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/bin:/usr/local/sbin","LOGNAME=root","USER=root","HOME=/root","SHELL=/bin/bash","SUDO_COMMAND=/usr/bin/ls /root/","SUDO_USER=czanik","SUDO_UID=1000","SUDO_GID=100","SUDO_HOME=/home/czanik"],"submitenv":["LS_COLORS=no=00:fi=00:di=01;34:ln=00;36:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=41;33;01:ex=00;32:*.cmd=00;32:*.exe=01;32:*.com=01;32:*.bat=01;32:*.btm=01;32:*.dll=01;32:*.tar=00;31:*.tbz=00;31:*.tgz=00;31:*.rpm=00;31:*.deb=00;31:*.arj=00;31:*.taz=00;31:*.lzh=00;31:*.lzma=00;31:*.zip=00;31:*.zoo=00;31:*.z=00;31:*.Z=00;31:*.gz=00;31:*.bz2=00;31:*.tb2=00;31:*.tz2=00;31:*.tbz2=00;31:*.xz=00;31:*.avi=01;35:*.bmp=01;35:*.dl=01;35:*.fli=01;35:*.gif=01;35:*.gl=01;35:*.jpg=01;35:*.jpeg=01;35:*.mkv=01;35:*.mng=01;35:*.mov=01;35:*.mp4=01;35:*.mpg=01;35:*.pcx=01;35:*.pbm=01;35:*.pgm=01;35:*.png=01;35:*.ppm=01;35:*.svg=01;35:*.tga=01;35:*.tif=01;35:*.webm=01;35:*.webp=01;35:*.wmv=01;35:*.xbm=01;35:*.xcf=01;35:*.xpm=01;35:*.aiff=00;32:*.ape=00;32:*.au=00;32:*.flac=00;32:*.m4a=00;32:*.mid=00;32:*.mp3=00;32:*.mpc=00;32:*.ogg=00;32:*.voc=00;32:*.wav=00;32:*.wma=00;32:*.wv=00;32:","HOSTTYPE=x86_64","LESSCLOSE=lessclose.sh %s %s","XKEYSYMDB=/usr/X11R6/lib/X11/XKeysymDB","LANG=en_US.UTF-8","MALLOC_PERTURB_=69","WINDOWMANAGER=/usr/bin/startxfce4","LESS=-M -I -R","HOSTNAME=leap154b","CONFIG_SITE=/usr/share/site/x86_64-unknown-linux-gnu","CSHEDIT=emacs","GPG_TTY=/dev/pts/0","AUDIODRIVER=pulseaudio","LESS_ADVANCED_PREPROCESSOR=no","COLORTERM=1","MACHTYPE=x86_64-suse-linux","QEMU_AUDIO_DRV=pa","MINICOM=-c on","OSTYPE=linux","USER=czanik","PAGER=less","MORE=-sl","PWD=/home/czanik","HOME=/home/czanik","HOST=leap154b","XNLSPATH=/usr/share/X11/nls","XDG_DATA_DIRS=/usr/share","PROFILEREAD=true","FROM_HEADER=","MAIL=/var/spool/mail/czanik","LESSKEY=/etc/lesskey.bin","TERM=xterm-256color","SHELL=/bin/bash","LS_OPTIONS=-N --color=tty -T 0","PYTHONSTARTUP=/etc/pythonstart","SHLVL=1","G_FILENAME_ENCODING=@locale,UTF-8,ISO-8859-15,CP1252","MANPATH=/usr/local/man:/usr/share/man","LOGNAME=czanik","XDG_CONFIG_DIRS=/etc/xdg","PATH=/home/czanik/bin:/usr/local/bin:/usr/bin:/bin","G_BROKEN_FILENAMES=1","HISTSIZE=1000","CPU=x86_64","LESSOPEN=lessopen.sh %s","BASH_FUNC_mc%%=() {  . /usr/share/mc/mc-wrapper.sh\n}","_=/usr/local/bin/sudo"]}}

This is not easy to read. However, syslog-ng and most other log management software can read single-line log messages out of the box. You just have to point them at the file name.

Configuring and testing syslog-ng

Here is my initial syslog-ng configuration: append it to syslog-ng.conf or create a new configuration snippet under /etc/syslog-ng/conf.d/ with a .conf extension.

source s_sudojson {
  file("/var/log/czpsudo2" flags(no-parse));
};
parser p_json {
    json-parser();  
};
destination d_sudo {
  file("/var/log/sudowelf"
    template("$(format-welf --scope nv_pairs --exclude MESSAGE --exclude accept.submitenv)\n\n")
  );
};
log {
  source(s_sudojson);
  parser(p_json);
  destination(d_sudo);
};

This configuration does not do much. It reads the JSON-formatted file line by line. The no-parse flag means that syslog-ng does not parse the message as an RFC3164-formatted syslog message, as it is normally done by default. Instead, syslog-ng uses a JSON parser to turn the message into name-value pairs.I usually use JSON formatting to save name-value pairs into a text file, but as the initial format is JSON, I use WELF formatting here.

If you take a look at that file, you should see similar messages to these:

FILE_NAME=/var/log/czpsudo2 HOST=leap154b HOST_FROM=leap154b MSGFORMAT=raw SOURCE=s_sudojson TRANSPORT=local+file accept.columns=118 accept.command=/usr/bin/ls accept.lines=60 accept.runargv=ls accept.runcwd=/home/czanik accept.runenv=LANG=en_US.UTF-8,COLORTERM=1,TERM=xterm-256color,MAIL=/var/mail/root,PATH=/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/bin:/usr/local/sbin,LOGNAME=root,USER=root,HOME=/root,SHELL=/bin/bash,SUDO_COMMAND=/usr/bin/ls,SUDO_USER=czanik,SUDO_UID=1000,SUDO_GID=100,SUDO_HOME=/home/czanik accept.runuid=0 accept.runuser=root accept.server_time.iso8601=20240410103849Z accept.server_time.localtime="Apr 10 12:38:49" accept.server_time.nanoseconds=65573888 accept.server_time.seconds=1712745529 accept.source=/etc/sudoers:66:23 accept.submit_time.iso8601=20240410103849Z accept.submit_time.localtime="Apr 10 12:38:49" accept.submit_time.nanoseconds=65060010 accept.submit_time.seconds=1712745529 accept.submitcwd=/home/czanik accept.submithost=leap154b accept.submituser=czanik accept.ttyname=/dev/pts/0 accept.uuid=f3da17758b-8819-419b-22d1-ba77ef1902

FILE_NAME=/var/log/czpsudo2 HOST=leap154b HOST_FROM=leap154b MSGFORMAT=raw SOURCE=s_sudojson TRANSPORT=local+file accept.columns=118 accept.command=/usr/bin/ls accept.lines=60 accept.runargv=ls accept.runcwd=/etc/syslog-ng/conf.d accept.runenv=LANG=en_US.UTF-8,COLORTERM=1,TERM=xterm-256color,MAIL=/var/mail/root,PATH=/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/bin:/usr/local/sbin,LOGNAME=root,USER=root,HOME=/root,SHELL=/bin/bash,SUDO_COMMAND=/usr/bin/ls,SUDO_USER=root,SUDO_UID=0,SUDO_GID=0,SUDO_HOME=/root accept.runuid=0 accept.runuser=root accept.server_time.iso8601=20240410103853Z accept.server_time.localtime="Apr 10 12:38:53" accept.server_time.nanoseconds=894832114 accept.server_time.seconds=1712745533 accept.source=/etc/sudoers:75:23 accept.submit_time.iso8601=20240410103853Z accept.submit_time.localtime="Apr 10 12:38:53" accept.submit_time.nanoseconds=894214742 accept.submit_time.seconds=1712745533 accept.submitcwd=/etc/syslog-ng/conf.d accept.submithost=leap154b accept.submituser=root accept.ttyname=/dev/pts/0 accept.uuid=fbb68d939e-94d4-4398-ac13-06379c29d3

Even if you do not need logs in this format in the long run, this step is useful in multiple ways. First of all, you can see that message parsing works. You can also see all (well, most) of the name-value pairs created by syslog-ng from the log message. You can use this log file while refining the syslog-ng configuration and delete / comment it out from the configuration later.

Here is a bit more fun of configuration, building on the previous one. This one adds two more destinations:

source s_sudojson {
  file("/var/log/czpsudo2" flags(no-parse));
};
parser p_json {
    json-parser();  
};
destination d_sudo {
  file("/var/log/sudowelf"
    template("$(format-welf --scope nv_pairs --exclude MESSAGE --exclude accept.submitenv)\n\n")
  );
  file("/var/log/sudofreetext"
    template("${DATE} user ${accept.submituser} ran ${accept.command} on host ${HOST} using sudo\n")
  );
};
log {
  source(s_sudojson);
  parser(p_json);
  destination(d_sudo);
  if (match("root" value("accept.submituser"))) {
      destination { file("/var/log/sudoroot" template("Oops, why did root use sudo to run ${accept.command}\n")); };
  };
};

The file sudofreetext uses name-value pairs parsed by syslog-ng from the JSON-formatted logs to create new log messages.

The other log file is only written to if user root executes a command using sudo. Of course, there can also be some legitimate uses, but I just come across the user becoming root way too often while still using sudo to run commands. Copy & paste from blogs and documentation :-)

The log files will look something similar to these:

leap154b:/var/log # cat sudofreetext
Apr 10 12:38:49 user czanik ran /usr/bin/ls on host leap154b using sudo
Apr 10 12:38:54 user root ran /usr/bin/ls on host leap154b using sudo
leap154b:/var/log # cat sudoroot
Oops, why did root use sudo to run /usr/bin/ls

What is next?

From this blog, you could learn how to work with JSON-formatted log files. The sample configurations showed you how to get started when developing a configuration. I hope I was not the only one having fun while working with these configurations. Surely, in a production environment, you will use different message formats or use other name-value pairs. However, you can use the examples with minor modifications to achieve those.

-

If you have questions or comments related to syslog-ng, do not hesitate to contact us. You can reach us by email or even chat with us. For a list of possibilities, check our GitHub page under the “Community” section at https://github.com/syslog-ng/syslog-ng. On Twitter, I am available as @PCzanik, on Mastodon as @Pczanik@fosstodon.org.

Related Content