jc

Activity, over time

Daily observations from the public event archive.

Attention

Daily stars & forks

Loading attention chart

Star accumulation

Cumulative stars

Loading star accumulation chart

Pull requests

Daily opens & merges

Loading pull requests chart

Conversations

Daily issues & comments

Loading conversations chart

Community

Daily accounts & reviews

Loading community chart

Releases

Daily releases

Loading releases chart

Mentions

5posts · 30d
Nov 40 posts
  1. Reddit · · r/linux · u/kellyjonbrazil

    Auditing every SSH key sshd accepts, including keys outside ~/.ssh If you audit SSH keys by checking ~/.ssh/authorized_keys, you can miss keys sshd will happily accept. AuthorizedKeysFile can point anywhere: lots of shops use something like /etc/ssh/auth_keys/%u so users can't manage their own keys, and it can list several paths. It can also be overridden per user or group in Match blocks. Even the default includes ~/.ssh/authorized_keys2, which is easy to forget. If you already run osquery or a compliance platform, you may have some of this covered. This is a lightweight approach for ad hoc audits that uses only jc (a tool I maintain that converts command output and config files to JSON), jq, and sshd's own view of its config. 1. Ask sshd where it looks for keys sshd -T prints the effective config, with includes and defaults resolved. Adding -C evaluates Match blocks for a specific connection, so you get the answer for a particular user: $ sudo sshd -T -C user=alice,host=localhost,addr=127.0.0.1 | jc --sshd-conf | jq -r '.authorizedkeysfile[]' /etc/ssh/auth_keys/%u .ssh/authorized_keys 2. Expand the paths for every account and parse each file #!/usr/bin/env bash getent passwd | jc --passwd | jq -r '.[] | "(.username) (.uid) (.home)"' | while read -r user uid home; do # Ask sshd for this user's effective AuthorizedKeysFile (honors Match User/Group) paths=$(sshd -T -C user="$user",host=localhost,addr=127.0.0.1 | jc --sshd-conf | jq -r '.authorizedkeysfile[]?') for p in $paths; do [[ $p == none ]] && continue f=${p//%%/$'\001'} # protect literal %% f=${f//%u/$user}; f=${f//%U/$uid}; f=${f//%h/$home} f=${f//$'\001'/%} [[ $f != /* ]] && f="$home/$f" # relative paths are relative to $HOME [[ -r $f ]] || continue jc --authorized-keys < "$f" | jq -c --arg u "$user" --arg f "$f" '.[] | . + {user: $u, file: $f}' done done > keys.jsonl Run it as root so sshd -T works and every file is readable. Using getent means LDAP/SSSD accounts are included, not just local ones in /etc/passwd. The script expands all four tokens AuthorizedKeysFile supports: %u, %U, %h, and %%. Each line of keys.jsonl is one key, tagged with its owner and the file it came from: {"options":[],"type":"ssh-ed25519","key":"AAAA...","comment":"kelly@laptop","user":"alice","file":"/home/alice/.ssh/authorized_keys"} {"options":[],"type":"ssh-rsa","key":"AAAA...","comment":"backup@nas","user":"bob","file":"/etc/ssh/auth_keys/1001"} 3. Ask questions Keys with no from= or command= restriction: jq -r 'select(any(.options[]; startswith("from=") or startswith("command=")) | not) | "(.user) (.type) (.comment // "(no comment)") (.file)"' keys.jsonl Users who still have keys in ~/.ssh when policy says they should only be in /etc/ssh/auth_keys: jq -r 'select(.file | test("/\.ssh/")) | "(.user) (.file)"' keys.jsonl | sort -u The same key authorized for more than one account: jq -s -r 'group_by(.key)[] | select(length > 1) | "(.[0].comment // "(no comment)"): (map(.user) | unique | join(", "))"' keys.jsonl Legacy RSA/DSA keys: jq -r 'select(.type == "ssh-rsa" or .type == "ssh-dss") | "(.user) (.comment) (.file)"' keys.jsonl Why not awk? authorized_keys lines can start with an options field containing quoted values with spaces (command="/usr/bin/rsync --server ..."), so field positions shift from line to line. awk '{print $1}' returns an option string instead of a key type, and grep -v command= also drops keys whose comment happens to mention it. The parser splits options into an array and keeps quoted values intact. Limitations: - The audit evaluates Match blocks as a local connection (host=localhost,addr=127.0.0.1). If you have Match Address or Match Host rules that change AuthorizedKeysFile for remote sources, run it again with those values. - AuthorizedKeysCommand setups, where keys come from LDAP or an API instead of files, aren't covered. That command's output is in authorized_keys format, though, so you should be able to pipe it to jc --authorized-keys the same way. - None of this tells you whether a user's private key has a passphrase, and nothing on the server can. A commenter on r/bash made this point. If it matters to you, SSH certificates with short validity windows are the better fix. The same sshd -T output shows trustedusercakeys and authorizedprincipalsfile, so you can audit that setup too. More jc examples (calendars, headerless CSVs, etc.): JC Tips and Tricks – Part 3

    8 comments
  2. Reddit · · r/CLI · u/kellyjonbrazil

    Querying .ics files, SSH authorized_keys, and more with jq I maintain jc, a tool that converts the output of CLI commands and file types to JSON so you can query them with jq. A few recent parsers cover files I used to fight with grep and awk, so I wrote up some examples. Here are a few highlights: What's on the calendar today? cat work.ics | jc --ics | jq -r '.[] | select(.dtstart | startswith("2026-10-19")) | .summary' Timestamps are normalized to ISO 8601, mailto: prefixes are stripped, and attendees come out as an array with name, role, and RSVP status. Which SSH keys have no from= or command= restriction? cat ~/.ssh/authorized_keys | jc --authorized-keys | jq -r '.[] | select(any(.options[]; startswith("from=") or startswith("command=")) | not) | .comment // "(no comment)"' Options are split into an array, and quoted values like command="/usr/bin/rsync --server ..." stay intact. CSV with no header row? cat access.csv | jc --csv-ih | jq -r '.[] | select((.c2 | tonumber) >= 500) | .c4' Columns are named c0, c1, and so on. The post includes a jq one-liner to swap in your own names. There are also --tsv, --tsv-ih, and streaming versions (-s) that output JSON Lines for huge files. Full write-up with more examples: JC Tips and Tricks – Part 3 I'd love to hear what file formats you still parse by hand. That's usually where the next parser comes from.

    3 points
  3. Reddit · · r/bash · u/kellyjonbrazil

    Auditing every user’s authorized_keys in a Bash loop with jc + jq I maintain jc, which converts command output and common file types to JSON so you can query them with jq instead of grep/awk/cut. A couple of newer parsers turned out to be handy in Bash scripts, so I wrote up some examples. Why not just awk? authorized_keys looks like a whitespace-delimited file, but it isn't. Lines can start with an optional, comma-separated options field, and option values can be quoted strings with spaces in them. So the field positions shift from line to line. Here's a sample file: ssh-ed25519 AAAAC3Nza... kelly@laptop command="/usr/bin/rsync --server --sender -logDtpre.iLsfxC . /srv/backup",no-port-forwarding,no-pty ssh-rsa AAAAB3Nza... backup@nas from="10.0.8.0/24",no-agent-forwarding ecdsa-sha2-nistp256 AAAAE2Vj... ci-runner@build01 ssh-ed25519 AAAAC3Nza... The obvious awk one-liner to print the key type and comment: $ awk '{print $1, $3}' ~/.ssh/authorized_keys ssh-ed25519 kelly@laptop command="/usr/bin/rsync --sender from="10.0.8.0/24",no-agent-forwarding AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlz... ssh-ed25519 Only the first line is right. Looking for RSA keys with awk '$1=="ssh-rsa"' returns nothing, because the one RSA key has options in front of it. grep has the opposite problem: it matches text anywhere on the line. grep -v 'command=' to find unrestricted keys also drops a key whose comment says "no command= needed". And grep ssh-rsa matches a restricted ed25519 key whose forced command happens to contain the string ssh-rsa. With jc --authorized-keys, every line comes out with the same fields, so there's no guessing: $ jc --authorized-keys < ~/.ssh/authorized_keys | jq -r '.[] | "(.type) (.comment // "(no comment)")"' ssh-ed25519 kelly@laptop ssh-rsa backup@nas ecdsa-sha2-nistp256 ci-runner@build01 ssh-ed25519 (no comment) options is always an array, quoted values like command="echo ssh-rsa disabled; exit" stay intact, and multi-word comments are kept whole. Tag every key with its owner, then query the whole box A loop adds a user field to each key and emits JSON Lines: for f in /home/*/.ssh/authorized_keys; do u=$(echo "$f" | cut -d/ -f3) jc --authorized-keys < "$f" | jq -c --arg u "$u" '.[] | . + {user: $u}' done > keys.jsonl Now each question is just a different jq filter. Which keys have no from= or command= restriction? jq -r 'select(any(.options[]; startswith("from=") or startswith("command=")) | not) | "(.user) (.type) (.comment // "(no comment)")"' keys.jsonl Because this checks the options array instead of the whole line, comments and key data can't cause false matches. Is the same key authorized for more than one user? jq -s -r 'group_by(.key)[] | select(length > 1) | "(.[0].comment // "(no comment)"): (map(.user) | join(", "))"' keys.jsonl Which legacy RSA or DSA keys are still around? jq -r 'select(.type == "ssh-rsa" or .type == "ssh-dss") | "(.user) (.comment)"' keys.jsonl (Run the loop with sudo if you need to read other users' files.) Bonus: a change-freeze guard from a shared calendar .ics files are worse for line tools: long lines get folded onto the next line, and parameters are jammed into property names (DTSTART;TZID=America/Los_Angeles:20261019T090000). The --ics parser unfolds all of that and normalizes timestamps to ISO 8601. That means jq's fromdateiso8601 and now can check whether you're inside a freeze window, and jq -e turns the result into an exit code: if jc --ics < freeze.ics | jq -e ' [.[] | select(.categories[]? == "Freeze") | select((.dtstart | fromdateiso8601) <= now and now < (.dtend | fromdateiso8601))] | length > 0' > /dev/null then echo "Change freeze in effect. Aborting deploy." >&2 exit 1 fi (This works with UTC timestamps, the ones ending in Z. Events with a TZID come through as local time with a separate dtstart_tzid field.) Full write-up, which also covers headerless CSV/TSV files: JC Tips and Tricks – Part 3 What file formats are you still parsing by hand in your scripts?

    21 points · 8 comments
  4. Reddit · · r/commandline · u/kellyjonbrazil

    Querying .ics files, SSH authorized_keys, and more with jq I maintain jc, a tool that converts the output of CLI commands and file types to JSON so you can query them with jq. A few recent parsers cover files I used to fight with grep and awk, so I wrote up some examples. Here are a few highlights: What's on the calendar today? cat work.ics | jc --ics | jq -r '.[] | select(.dtstart | startswith("2026-10-19")) | .summary' Timestamps are normalized to ISO 8601, mailto: prefixes are stripped, and attendees come out as an array with name, role, and RSVP status. Which SSH keys have no from= or command= restriction? cat ~/.ssh/authorized_keys | jc --authorized-keys | jq -r '.[] | select(any(.options[]; startswith("from=") or startswith("command=")) | not) | .comment // "(no comment)"' Options are split into an array, and quoted values like command="/usr/bin/rsync --server ..." stay intact. CSV with no header row? cat access.csv | jc --csv-ih | jq -r '.[] | select((.c2 | tonumber) >= 500) | .c4' Columns are named c0, c1, and so on. The post includes a jq one-liner to swap in your own names. There are also --tsv, --tsv-ih, and streaming versions (-s) that output JSON Lines for huge files. Full write-up with more examples: JC Tips and Tricks – Part 3 I'd love to hear what file formats you still parse by hand. That's usually where the next parser comes from.

    1 point · 1 comment
  5. 📅 jc v1.26.1 now parses .ics calendar files! Turn iCalendar events into clean JSON and query them with jq: cat events.ics | jc --ics | jq Also new: apachectl, conntrack, nft, nfsiostat parsers, 11 fixes, and slimmer Python 3.14 binaries. kellyjonbrazil/jc

Latest stargazers

The last 10 distinct accounts we observed this year.

Most active participants

PR, issue, comment & review events in the past year.

Packages

No packages found.

README badges

Embed a badge in your README or website.

  • kellyjonbrazil/jc reached #3 in daily stars on gitwal · Dec 6, 2021
Live mentions · last 30 days
kellyjonbrazil/jc mentions in the last 30 days