From 7bfe729b8ff9c730e0e254a2034238a534483b5e Mon Sep 17 00:00:00 2001 From: Vishal Mishra Date: Nov 27 2024 11:22:36 +0000 Subject: Apply fb patches and update the spec --- diff --git a/.gitignore b/.gitignore index 7004cb5..6ccd229 100644 --- a/.gitignore +++ b/.gitignore @@ -64,3 +64,6 @@ pam_ssh_agent_auth-0.9.2.tar.bz2 /openssh-9.8p1.tar.gz.asc /openssh-9.9p1.tar.gz /openssh-9.9p1.tar.gz.asc +.pc +BUILD +patches diff --git a/fb87_070_logging_reverse_port_forward.patch b/fb87_070_logging_reverse_port_forward.patch new file mode 100644 index 0000000..a255577 --- /dev/null +++ b/fb87_070_logging_reverse_port_forward.patch @@ -0,0 +1,46 @@ +--- a/channels.c ++++ b/channels.c +@@ -3774,6 +3774,7 @@ int + channel_setup_remote_fwd_listener(struct ssh *ssh, struct Forward *fwd, + int *allocated_listen_port, struct ForwardOptions *fwd_opts) + { ++ int success = 0; + if (!check_rfwd_permission(ssh, fwd)) { + ssh_packet_send_debug(ssh, "port forwarding refused"); + if (fwd->listen_path != NULL) +@@ -3795,14 +3796,23 @@ channel_setup_remote_fwd_listener(struct + ssh_remote_ipaddr(ssh), ssh_remote_port(ssh)); + return 0; + } +- if (fwd->listen_path != NULL) { +- return channel_setup_fwd_listener_streamlocal(ssh, ++ if (fwd->listen_path != NULL) { ++ success = channel_setup_fwd_listener_streamlocal(ssh, + SSH_CHANNEL_RUNIX_LISTENER, fwd, fwd_opts); + } else { +- return channel_setup_fwd_listener_tcpip(ssh, ++ success = channel_setup_fwd_listener_tcpip(ssh, + SSH_CHANNEL_RPORT_LISTENER, fwd, allocated_listen_port, + fwd_opts); + } ++ logit("Remote forward request %s: listen=%s:%d connect=%s:%d" ++ " uid=%d", ++ success ? "succeeded" : "failed", ++ fwd->listen_host, ++ fwd->listen_port, ++ ssh_remote_ipaddr(ssh), ++ ssh_remote_port(ssh), ++ getuid()); ++ return success; + } + + /* +@@ -4591,7 +4601,7 @@ x11_create_display_inet(struct ssh *ssh, + if ((errno != EINVAL) && (errno != EAFNOSUPPORT) + #ifdef EPFNOSUPPORT + && (errno != EPFNOSUPPORT) +-#endif ++#endif + ) { + error("socket: %.100s", strerror(errno)); + freeaddrinfo(aitop); diff --git a/fb87_080_logging_certificates.patch b/fb87_080_logging_certificates.patch new file mode 100644 index 0000000..2462170 --- /dev/null +++ b/fb87_080_logging_certificates.patch @@ -0,0 +1,149 @@ +Index: openssh-9.9p1/auth2-pubkey.c +=================================================================== +--- openssh-9.9p1.orig/auth2-pubkey.c ++++ openssh-9.9p1/auth2-pubkey.c +@@ -524,11 +524,16 @@ user_cert_trusted_ca(struct passwd *pw, + options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL) + return 0; + ++ ++ + if ((r = sshkey_in_file(key->cert->signature_key, + options.trusted_user_ca_keys, 1, 0)) != 0) { + debug2_fr(r, "CA %s %s is not listed in %s", + sshkey_type(key->cert->signature_key), ca_fp, + options.trusted_user_ca_keys); ++ verbose("CA %s %s is not listed in %s", ++ sshkey_type(key->cert->signature_key), ca_fp, ++ options.trusted_user_ca_keys); + goto out; + } + /* +@@ -540,6 +545,9 @@ user_cert_trusted_ca(struct passwd *pw, + if (match_principals_file(pw, principals_file, + key->cert, &principals_opts)) + found_principal = 1; ++ else { ++ verbose("Did not match any principals from auth_principals_* files"); ++ } + } + /* Try querying command if specified */ + if (!found_principal && match_principals_command(pw, key, +@@ -580,6 +588,11 @@ user_cert_trusted_ca(struct passwd *pw, + if ((final_opts = sshauthopt_merge(principals_opts, + cert_opts, &reason)) == NULL) { + fail_reason: ++ verbose("Rejected cert ID \"%s\" with signature " ++ "%s signed by %s CA %s via %s", ++ key->cert->key_id, ca_fp, ++ sshkey_type(key->cert->signature_key), ca_fp, ++ options.trusted_user_ca_keys); + error("%s", reason); + auth_debug_add("%s", reason); + goto out; +@@ -587,7 +600,7 @@ user_cert_trusted_ca(struct passwd *pw, + } + slog_set_cert_serial((unsigned long long)key->cert->serial); + /* Success */ +- verbose("Accepted certificate ID \"%s\" (serial %llu) signed by " ++ verbose("Accepted cert ID \"%s\" (serial %llu) signed by " + "%s CA %s via %s", key->cert->key_id, + (unsigned long long)key->cert->serial, + sshkey_type(key->cert->signature_key), ca_fp, +@@ -604,6 +617,7 @@ user_cert_trusted_ca(struct passwd *pw, + sshauthopt_free(final_opts); + free(principals_file); + free(ca_fp); ++ + return ret; + } + +Index: openssh-9.9p1/regress/cert-logging.sh +=================================================================== +--- /dev/null ++++ openssh-9.9p1/regress/cert-logging.sh +@@ -0,0 +1,84 @@ ++tid="cert logging" ++ ++CERT_ID="cert_id" ++PRINCIPAL=$USER ++SERIAL=0 ++ ++log_grep() { ++ if [ "$(grep -c -G "$1" "$TEST_SSHD_LOGFILE")" == "0" ]; then ++ return 1; ++ else ++ return 0; ++ fi ++} ++ ++cat << EOF >> $OBJ/sshd_config ++TrustedUserCAKeys $OBJ/ssh-rsa.pub ++Protocol 2 ++PubkeyAuthentication yes ++AuthenticationMethods publickey ++AuthorizedPrincipalsFile $OBJ/auth_principals ++EOF ++ ++if [ ! -f $OBJ/trusted_rsa ]; then ++ ${SSHKEYGEN} -q -t rsa -C '' -N '' -f $OBJ/trusted_rsa ++fi ++if [ ! -f $OBJ/untrusted_rsa ]; then ++ ${SSHKEYGEN} -q -t rsa -C '' -N '' -f $OBJ/untrusted_rsa ++fi ++ ++${SSHKEYGEN} -q -s $OBJ/ssh-rsa -I $CERT_ID -n $PRINCIPAL -z $SERIAL $OBJ/trusted_rsa.pub || ++ fatal "Could not create trusted SSH cert" ++ ++${SSHKEYGEN} -q -s $OBJ/untrusted_rsa -I $CERT_ID -n $PRINCIPAL -z $SERIAL $OBJ/untrusted_rsa.pub || ++ fatal "Could not create untrusted SSH cert" ++ ++CA_FP="$(${SSHKEYGEN} -l -E sha256 -f ssh-rsa | cut -d' ' -f2)" ++KEY_FP="$(${SSHKEYGEN} -l -E sha256 -f trusted_rsa | cut -d' ' -f2)" ++UNTRUSTED_CA_FP="$(${SSHKEYGEN} -l -E sha256 -f untrusted_rsa | cut -d' ' -f2)" ++ ++start_sshd ++ ++ ++test_no_principals() { ++ echo > $OBJ/auth_principals ++ ${SSH} -F $OBJ/ssh_config -i $OBJ/trusted_rsa-cert.pub somehost true || ++ fatal "SSH failed" ++ ++ if ! log_grep 'Did not match any principals from auth_principals_\* files'; then ++ fail "No 'Did not match any principals' message" ++ fi ++ ++ if ! log_grep "Rejected cert ID \"$CERT_ID\" with signature $KEY_FP signed by RSA CA $CA_FP via $OBJ/ssh-rsa.pub"; then ++ fail "No 'Rejected cert ID' message" ++ fi ++} ++ ++ ++test_with_principals() { ++ echo $USER > $OBJ/auth_principals ++ ${SSH} -F $OBJ/ssh_config -i $OBJ/trusted_rsa-cert.pub somehost true || ++ fatal "SSH failed" ++ ++ if ! log_grep "Matched principal \"$PRINCIPAL\" from $OBJ/auth_principals:1 against \"$PRINCIPAL\" from cert"; then ++ fail "No 'Matched principal' message" ++ fi ++ if ! log_grep "Accepted cert ID \"$CERT_ID\" (serial $SERIAL) with signature $KEY_FP signed by RSA CA $CA_FP via $OBJ/ssh-rsa.pub"; then ++ fail "No 'Accepted cert ID' message" ++ fi ++} ++ ++ ++test_untrusted_cert() { ++ ${SSH} -F $OBJ/ssh_config -i $OBJ/untrusted_rsa-cert.pub somehost true || ++ fatal "SSH failed" ++ ++ if ! log_grep "CA RSA $UNTRUSTED_CA_FP is not listed in $OBJ/ssh-rsa.pub"; then ++ fail "No 'CA is not listed' message" ++ fi ++} ++ ++ ++test_no_principals ++test_with_principals ++test_untrusted_cert diff --git a/fb87_090_logging_shell_cmd_pty.patch b/fb87_090_logging_shell_cmd_pty.patch new file mode 100644 index 0000000..af7436f --- /dev/null +++ b/fb87_090_logging_shell_cmd_pty.patch @@ -0,0 +1,74 @@ +Index: openssh-9.9p1/session.c +=================================================================== +--- openssh-9.9p1.orig/session.c ++++ openssh-9.9p1/session.c +@@ -2037,6 +2037,8 @@ session_pty_req(struct ssh *ssh, Session + return 0; + } + debug("session_pty_req: session %d alloc %s", s->self, s->tty); ++ verbose("Allocated pty %s for user %s session %d", ++ s->tty, s->pw->pw_name, s->self); + + ssh_tty_parse_modes(ssh, s->ttyfd); + +@@ -2139,7 +2141,7 @@ session_shell_req(struct ssh *ssh, Sessi + sshpkt_fatal(ssh, r, "%s: parse packet", __func__); + + channel_set_xtype(ssh, s->chanid, "session:shell"); +- ++ verbose("Shell Request for user %s", s->pw->pw_name); + return do_exec(ssh, s, NULL) == 0; + } + +@@ -2156,6 +2158,7 @@ session_exec_req(struct ssh *ssh, Sessio + + channel_set_xtype(ssh, s->chanid, "session:command"); + slog_set_command(command); ++ verbose("Exec Request for user %s with command %s", s->pw->pw_name, command); + success = do_exec(ssh, s, command) == 0; + free(command); + return success; +Index: openssh-9.9p1/regress/session-req.sh +=================================================================== +--- /dev/null ++++ openssh-9.9p1/regress/session-req.sh +@@ -0,0 +1,39 @@ ++tid="session req" ++ ++start_sshd ++ ++test_user_shell_exec_req() { ++ session_shell_req_expected="Exec Request for user $USER with command true" ++ cnt=$(grep -c "$session_shell_req_expected" "$TEST_SSHD_LOGFILE") ++ if [ $cnt == "0" ]; then ++ fail "No exec request for user log lines found" ++ fi ++} ++ ++test_user_pty() { ++ session_pty_req_expected="Allocated pty .* for user $USER session .*" ++ line_count=$(grep -c "$session_req_expected" "$TEST_SSHD_LOGFILE") ++ if [ $line_count == "0" ]; then ++ fail "No Allocated pty for user session found in log lines" ++ fi ++} ++ ++test_user_shell_req() { ++ exit | ${SSH} -F $OBJ/ssh_config somehost ++ if [ $? -ne 0 ]; then ++ fail "ssh connect with failed" ++ fi ++ session_shell_req_expected="Shell Request for user $USER" ++ line_count=$(grep -c "$session_shell_req_expected" "$TEST_SSHD_LOGFILE") ++ if [ $line_count == "0" ]; then ++ fail "No session request for user log lines found" ++ fi ++} ++ ++${SSH} -F $OBJ/ssh_config somehost true ++if [ $? -ne 0 ]; then ++ fail "ssh connect with failed" ++fi ++test_user_shell_exec_req ++test_user_pty ++test_user_shell_req diff --git a/fb87_810_increase_ssh_cert_max_principals.patch b/fb87_810_increase_ssh_cert_max_principals.patch new file mode 100644 index 0000000..82919c2 --- /dev/null +++ b/fb87_810_increase_ssh_cert_max_principals.patch @@ -0,0 +1,11 @@ +--- a/sshkey.h ++++ b/sshkey.h +@@ -109,7 +109,7 @@ enum sshkey_private_format { + /* key is stored in external hardware */ + #define SSHKEY_FLAG_EXT 0x0001 + +-#define SSHKEY_CERT_MAX_PRINCIPALS 256 ++#define SSHKEY_CERT_MAX_PRINCIPALS 1024 + /* XXX opaquify? */ + struct sshkey_cert { + struct sshbuf *certblob; /* Kept around for use on wire */ diff --git a/fb87_log_accept_env.patch b/fb87_log_accept_env.patch new file mode 100644 index 0000000..11bd761 --- /dev/null +++ b/fb87_log_accept_env.patch @@ -0,0 +1,20 @@ +--- a/session.c ++++ b/session.c +@@ -2210,7 +2210,7 @@ session_env_req(struct ssh *ssh, Session + + for (i = 0; i < options.num_accept_env; i++) { + if (match_pattern(name, options.accept_env[i])) { +- debug2("Setting env %d: %s=%s", s->num_env, name, val); ++ verbose("Setting env %d: %s=%s user=%s", s->num_env, name, val, s->pw->pw_name); + s->env = xrecallocarray(s->env, s->num_env, + s->num_env + 1, sizeof(*s->env)); + s->env[s->num_env].name = name; +@@ -2219,7 +2219,7 @@ session_env_req(struct ssh *ssh, Session + return (1); + } + } +- debug2("Ignoring env request %s: disallowed name", name); ++ verbose("Ignoring env request %s user=%s : disallowed name", name, s->pw->pw_name); + + fail: + free(name); diff --git a/fb87_log_auth_info.patch b/fb87_log_auth_info.patch new file mode 100644 index 0000000..f317618 --- /dev/null +++ b/fb87_log_auth_info.patch @@ -0,0 +1,207 @@ +Index: openssh-9.9p1/regress/slog.sh +=================================================================== +--- openssh-9.9p1.orig/regress/slog.sh ++++ openssh-9.9p1/regress/slog.sh +@@ -1,41 +1,60 @@ + tid='structured log' + +-port="4242" + log_prefix="sshd_auth_msg:" +-log_keys="server_ip server_port remote_ip remote_port pid session_id method cert_id cert_serial principal user session_state auth_successful _time command end_time duration auth_info client_version" ++log_keys="server_ip server_port remote_ip remote_port pid session_id method cert_id cert_serial principal user session_state auth_successful command end_time duration auth_info client_version" + do_log_json="yes" +-test_config="$OBJ/sshd2_config" +-old_config="$OBJ/sshd_config" +-PIDFILE=$OBJ/pidfile +- +-cat << EOF > $test_config +- #*: +- StrictModes no +- Port $port +- AddressFamily inet +- ListenAddress 127.0.0.1 +- #ListenAddress ::1 +- PidFile $PIDFILE +- AuthorizedKeysFile $OBJ/authorized_keys_%u +- LogLevel ERROR +- AcceptEnv _XXX_TEST_* +- AcceptEnv _XXX_TEST +- HostKey $OBJ/host.ssh-ed25519 +- LogFormatPrefix $log_prefix +- LogFormatJson $do_log_json +- LogFormatKeys $log_keys ++ ++AUTH_PRINC_FILE="$OBJ/auth_principals" ++CA_FILE="$OBJ/ca-rsa" ++IDENTITY_FILE="$OBJ/$USER-rsa" ++CERT_ID=$USER ++ ++cat << EOF >> $OBJ/sshd_config ++TrustedUserCAKeys $CA_FILE.pub ++PubkeyAuthentication yes ++AuthenticationMethods publickey ++AuthorizedPrincipalsFile $AUTH_PRINC_FILE ++LogFormatPrefix $log_prefix ++LogFormatJson $do_log_json ++LogFormatKeys $log_keys + EOF + ++sed -i 's/DEBUG3/VERBOSE/g' $OBJ/sshd_config + +-cp $test_config $old_config +-start_sshd ++cleanup() { ++ rm -f $CA_FILE{.pub,} ++ rm -f $IDENTITY_FILE{-cert.pub,.pub,} ++ rm -f $AUTH_PRINC_FILE ++ rm -f $TEST_SSHD_LOGFILE ++} ++ ++make_keys() { ++ local keytype=$1 ++ ++ rm -f $IDENTITY_FILE{.pub,} ++ ${SSHKEYGEN} -q -t $keytype -C '' -N '' -f $IDENTITY_FILE || ++ fatal 'Could not create keypair' ++ ++ cat $IDENTITY_FILE.pub > authorized_keys_$USER ++ ${SSHKEYGEN} -lf $IDENTITY_FILE ++} + +-${SSH} -F $OBJ/ssh_config somehost true +-if [ $? -ne 0 ]; then +- fail "ssh connect with failed" +-fi ++make_cert() { ++ local princs=$1 ++ local certtype=$2 ++ local serial=$3 + +-test_log_counts() { ++ rm -f $CA_FILE ++ rm -f "$IDENTITY_FILE-cert.pub" ++ ++ ${SSHKEYGEN} -q -t $certtype -C '' -N '' -f $CA_FILE || ++ fatal 'Could not create CA key' ++ ++ ${SSHKEYGEN} -q -s $CA_FILE -I $CERT_ID -n "$princs" -z $serial "$IDENTITY_FILE.pub" || ++ fatal "Could not create SSH cert" ++} ++ ++do_test_log_counts() { + cnt=$(grep -c "$log_prefix" "$TEST_SSHD_LOGFILE") + if [ $cnt -ne 2 ]; then + fail "expected 2 structured logging lines, got $cnt" +@@ -43,7 +62,10 @@ test_log_counts() { + } + + test_json_valid() { +- which python &>/dev/null || echo 'python not found in path, skipping tests' ++ if ! $(which python &>/dev/null) ; then ++ echo 'python not found in path, skipping JSON tests' ++ return 1 ++ fi + + loglines=$(cat "$TEST_SSHD_LOGFILE" | grep "$log_prefix") + first=$(echo "$loglines" | head -n1) +@@ -55,5 +77,72 @@ test_json_valid() { + || fail "invalid json structure $last" + } + +-test_log_counts +-test_json_valid ++# todo: first/last line ++extract_key() { ++ local key=$1 ++ loglines=$(cat "$TEST_SSHD_LOGFILE" | grep "$log_prefix") ++ last=$(echo "$loglines" | tail -n1) ++ json=${last:$(expr length $log_prefix)} ++ ++ val=$(echo $json | python -c "import sys, json; print(json.load(sys.stdin)[\"$key\"])") || ++ fail "error extracting $key from $json" ++ echo "$val" ++} ++ ++test_basic_logging() { ++ ${SSH} -F $OBJ/ssh_config -v -i "$IDENTITY_FILE" somehost true || ++ fatal "SSH failed" ++ ++ do_test_log_counts ++ test_json_valid || return 1 ++} ++ ++extract_hash() { ++ local source=$1 ++ echo $source | sed "s/.*\(SHA256:[[:print:]]\{43\}\).*$/\1/" ++} ++ ++test_auth_info() { ++ local keyfp=$1 ++ local keytype=$2 ++ local princ=$3 ++ local serial=$4 ++ ++ ${SSH} -F $OBJ/ssh_config -v -i "$IDENTITY_FILE" somehost true || ++ fatal "SSH failed" ++ ++ auth_info=$(extract_key 'auth_info') ++ digest=$(extract_hash "$keyfp") ++ ++ [ -z "$keyfp" ] || echo "$auth_info" | grep -q "$digest" || ++ echo "hash digest not found" ++ [ -z "$keytype" ] || echo "$auth_info" | grep -q "$keytype" || ++ echo "keytype not found" ++ [ -z "$princ" ] || echo "$auth_info" | grep -q "$princ" || ++ echo "princ not found" ++ [ -z "$serial" ] || echo "$auth_info" | grep -q "$serial" || ++ echo "serial not found" ++} ++ ++test_cert_serial() { ++ local serial=$1 ++ logged_serial=$(extract_key 'cert_serial') ++ [ $serial = $logged_serial ] || fail 'cert serial mismatch' ++} ++ ++start_sshd ++ ++keytype="RSA" ++keyfp=$(make_keys $keytype) ++test_basic_logging || return ++test_auth_info "$keyfp" "$keytype" ++ ++rm authorized_keys_$USER # force cert auth ++ ++princ="$USER" ++echo $princ > $AUTH_PRINC_FILE ++ ++serial='42' ++make_cert "$princ" "$keytype" "$serial" ++test_auth_info "$keyfp" "$keytype" "$princ" "$serial" ++test_cert_serial "$serial" +Index: openssh-9.9p1/auth.c +=================================================================== +--- openssh-9.9p1.orig/auth.c ++++ openssh-9.9p1/auth.c +@@ -305,6 +305,8 @@ auth_log(struct ssh *ssh, int authentica + extra != NULL ? ": " : "", + extra != NULL ? extra : ""); + ++ if (extra != NULL) ++ slog_set_auth_info(extra); + free(extra); + slog_set_auth_data(authenticated, method, authctxt->user); + +Index: openssh-9.9p1/auth2-pubkey.c +=================================================================== +--- openssh-9.9p1.orig/auth2-pubkey.c ++++ openssh-9.9p1/auth2-pubkey.c +@@ -612,6 +612,8 @@ user_cert_trusted_ca(struct passwd *pw, + final_opts = NULL; + } + slog_set_cert_id(key->cert->key_id); ++ slog_set_cert_serial((unsigned long long) key->cert->serial); ++ + ret = 1; + out: + sshauthopt_free(principals_opts); diff --git a/fb87_log_port_forwards.patch b/fb87_log_port_forwards.patch new file mode 100644 index 0000000..d43b857 --- /dev/null +++ b/fb87_log_port_forwards.patch @@ -0,0 +1,22 @@ +--- a/serverloop.c ++++ b/serverloop.c +@@ -433,6 +433,7 @@ server_request_direct_tcpip(struct ssh * + char *target = NULL, *originator = NULL; + u_int target_port = 0, originator_port = 0; + int r; ++ uid_t user; + + if ((r = sshpkt_get_cstring(ssh, &target, NULL)) != 0 || + (r = sshpkt_get_u32(ssh, &target_port)) != 0 || +@@ -451,6 +452,11 @@ server_request_direct_tcpip(struct ssh * + goto out; + } + ++ user = getuid(); ++ logit("Tunnel: %s:%d -> %s:%d UID(%d) user %s", ++ originator, originator_port, target, target_port, user, ++ getpwuid(user)->pw_name); ++ + debug_f("originator %s port %u, target %s port %u", + originator, originator_port, target, target_port); + diff --git a/fb87_log_session_id.patch b/fb87_log_session_id.patch new file mode 100644 index 0000000..cb120d0 --- /dev/null +++ b/fb87_log_session_id.patch @@ -0,0 +1,142 @@ +Index: openssh-9.9p1/sshd.c +=================================================================== +--- openssh-9.9p1.orig/sshd.c ++++ openssh-9.9p1/sshd.c +@@ -1768,6 +1768,9 @@ main(int ac, char **av) + /* Accept a connection and return in a forked child */ + server_accept_loop(&sock_in, &sock_out, + &newsock, config_s, log_stderr); ++ ++ set_log_session_id(); // Set log session ID for this session ++ + } + + /* This is the child processing a new connection. */ +@@ -1818,3 +1821,4 @@ cleanup_exit(int i) + { + _exit(i); + } ++ +Index: openssh-9.9p1/log.c +=================================================================== +--- openssh-9.9p1.orig/log.c ++++ openssh-9.9p1/log.c +@@ -414,25 +414,52 @@ do_log(LogLevel level, int force, const + tmp_handler(level, force, fmtbuf, log_handler_ctx); + log_handler = tmp_handler; + } else if (log_on_stderr) { +- snprintf(msgbuf, sizeof msgbuf, "%s%s%.*s\r\n", ++ snprintf(msgbuf, sizeof msgbuf, "%s%s%.*s session=%s\r\n", + (log_on_stderr > 1) ? progname : "", + (log_on_stderr > 1) ? ": " : "", +- (int)sizeof msgbuf - 3, fmtbuf); ++ (int)sizeof msgbuf - 3, fmtbuf, get_log_session_id()); + (void)write(log_stderr_fd, msgbuf, strlen(msgbuf)); + } else { + #if defined(HAVE_OPENLOG_R) && defined(SYSLOG_DATA_INIT) + openlog_r(progname, LOG_PID, log_facility, &sdata); +- syslog_r(pri, &sdata, "%.500s", fmtbuf); ++ syslog_r(pri, &sdata, "%.500s session=%s", fmtbuf, get_log_session_id()); + closelog_r(&sdata); + #else + openlog(progname, LOG_PID, log_facility); +- syslog(pri, "%.500s", fmtbuf); ++ syslog(pri, "%.500s session=%s", fmtbuf, get_log_session_id()); + closelog(); + #endif + } + errno = saved_errno; + } + ++void ++set_log_session_id() ++{ ++ struct timeval tv; ++ char hostname[HOST_NAME_MAX + 1]; ++ char session_id[HOST_NAME_MAX + 20]; ++ char *s; ++ if (gethostname(hostname, sizeof(hostname)) != 0) { ++ *hostname = '\0'; ++ } ++ gettimeofday(&tv, NULL); ++ snprintf(session_id, sizeof(session_id), "%s:%x.%x", ++ hostname, tv.tv_sec, tv.tv_usec); ++ setenv("LOG_SESSION_ID", session_id, 1); ++} ++ ++const char * ++get_log_session_id() ++{ ++ const char *id = getenv("LOG_SESSION_ID"); ++ if (!id) { ++ set_log_session_id(); ++ id = getenv("LOG_SESSION_ID"); ++ } ++ return id; ++} ++ + void + sshlog(const char *file, const char *func, int line, int showfunc, + LogLevel level, const char *suffix, const char *fmt, ...) +@@ -519,3 +546,4 @@ sshlogdirect(LogLevel level, int forced, + do_log(level, forced, NULL, fmt, args); + va_end(args); + } ++ +Index: openssh-9.9p1/regress/session-id.sh +=================================================================== +--- /dev/null ++++ openssh-9.9p1/regress/session-id.sh +@@ -0,0 +1,23 @@ ++tid="session id" ++ ++start_sshd ++ ++${SSH} -F $OBJ/ssh_config somehost true ++if [ $? -ne 0 ]; then ++ fail "ssh connect with failed" ++fi ++ ++expected="session=$(hostname)" ++ ++# grab the first session ID which will be stable across session ++sessionid=$(grep -m1 $expected $TEST_SSHD_LOGFILE | sed -E 's/.*(session=.*)/\1/') ++ ++line_count=$(grep -c $expected $TEST_SSHD_LOGFILE) ++if [ $line_count == "0" ]; then ++ fail "No session ID lines found" ++fi ++ ++stable_id_count=$(grep -c $sessionid $TEST_SSHD_LOGFILE) ++if [ $line_count != $stable_id_count ]; then ++ fail 'Mismatching session ids found' ++fi +Index: openssh-9.9p1/log.h +=================================================================== +--- openssh-9.9p1.orig/log.h ++++ openssh-9.9p1/log.h +@@ -68,6 +68,9 @@ const char * log_level_name(LogLevel); + void set_log_handler(log_handler_fn *, void *); + void cleanup_exit(int) __attribute__((noreturn)); + ++void set_log_session_id(); ++const char * get_log_session_id(); ++ + void sshlog(const char *, const char *, int, int, + LogLevel, const char *, const char *, ...) + __attribute__((format(printf, 7, 8))); +Index: openssh-9.9p1/session.c +=================================================================== +--- openssh-9.9p1.orig/session.c ++++ openssh-9.9p1/session.c +@@ -1242,6 +1242,10 @@ do_setup_env(struct ssh *ssh, Session *s + child_set_env(&env, &envsize, "SSH_ORIGINAL_COMMAND", + original_command); + ++ /* set LOG_SESSION_ID for child */ ++ child_set_env(&env, &envsize, "LOG_SESSION_ID", get_log_session_id()); ++ debug("set LOG_SESION_ID to: %s", get_log_session_id()); ++ + if (debug_flag) { + /* dump the environment */ + fprintf(stderr, "Environment:\n"); diff --git a/fb87_pass_principals_to_child.patch b/fb87_pass_principals_to_child.patch new file mode 100644 index 0000000..49de966 --- /dev/null +++ b/fb87_pass_principals_to_child.patch @@ -0,0 +1,221 @@ +--- a/session.c ++++ b/session.c +@@ -98,6 +98,7 @@ + #include "atomicio.h" + #include "slog.h" + ++#define SSH_MAX_PUBKEY_BYTES 16384 + + #if defined(KRB5) && defined(USE_AFS) + #include +@@ -1054,11 +1055,18 @@ copy_environment(char **source, char *** + static char ** + do_setup_env(struct ssh *ssh, Session *s, const char *shell) + { +- char buf[256]; ++ char buf[SSH_MAX_PUBKEY_BYTES]; ++ char *pbuf = &buf[0]; + size_t n; + u_int i, envsize; + char *ocp, *cp, *value, **env, *laddr; + struct passwd *pw = s->pw; ++ Authctxt *authctxt = s->authctxt; ++ struct sshkey *key; ++ size_t len = 0; ++ ssize_t total = 0; ++ struct sshkey_cert *cert; ++ + #if !defined (HAVE_LOGIN_CAP) && !defined (HAVE_CYGWIN) + char *path = NULL; + #endif +@@ -1255,9 +1263,57 @@ do_setup_env(struct ssh *ssh, Session *s + child_set_env(&env, &envsize, "SSH_USER_AUTH", auth_info_file); + if (s->ttyfd != -1) + child_set_env(&env, &envsize, "SSH_TTY", s->tty); +- if (original_command) ++ if (original_command) { + child_set_env(&env, &envsize, "SSH_ORIGINAL_COMMAND", + original_command); ++ /* ++ * Set SSH_CERT_PRINCIPALS to be the principals on the ssh certificate. ++ * Only do so when a force command is present to prevent the client ++ * from changing the value of SSH_CERT_PRINCIPALS. For example, when a ++ * client is given shell access, the client can easily change the ++ * value of an environment variable by running, e.g., ++ * ssh user@host.address 'SSH_CERT_PRINCIPALS=attacker env' ++ */ ++ ++ if (authctxt->nprev_keys > 0) { ++ key = authctxt->prev_keys[authctxt->nprev_keys-1]; ++ /* If a user was authorized by a certificate, set SSH_CERT_PRINCIPALS */ ++ if (sshkey_is_cert(key)) { ++ cert = key->cert; ++ ++ for (i = 0; i < cert->nprincipals - 1; ++i) { ++ /* ++ * total: bytes written to buf so far ++ * 2: one for comma and one for '\0' to be added by snprintf ++ * We stop at the first principal overflowing buf. ++ */ ++ if (total + strlen(cert->principals[i]) + 2 > SSH_MAX_PUBKEY_BYTES) ++ break; ++ ++ len = snprintf(pbuf, SSH_MAX_PUBKEY_BYTES-total, "%s,", ++ cert->principals[i]); ++ /* pbuf advances by len, the '\0' at the end will be overwritten */ ++ pbuf += len; ++ total += len; ++ } ++ ++ if (total + strlen(cert->principals[i]) + 1 <= SSH_MAX_PUBKEY_BYTES) { ++ len = snprintf(pbuf, SSH_MAX_PUBKEY_BYTES-total, "%s", ++ cert->principals[i]); ++ total += len; ++ } else if (total > 0) ++ /* ++ * If we hit the overflow condition, remove the trailing comma. ++ * We only do so if the overflowing principal is not the first one on the ++ * certificate so that there is at least one principal in buf ++ */ ++ buf[total-1] = '\0'; ++ ++ if (total > 0) ++ child_set_env(&env, &envsize, "SSH_CERT_PRINCIPALS", buf); ++ } ++ } ++ } + + /* set LOG_SESSION_ID for child */ + child_set_env(&env, &envsize, "LOG_SESSION_ID", get_log_session_id()); +--- /dev/null ++++ b/regress/cert-princ-env.sh +@@ -0,0 +1,129 @@ ++tid="cert principal env" ++ ++# change to ecdsa ++CERT_ID="$USER" ++AUTH_PRINC_FILE="$OBJ/auth_principals" ++CA_FILE="$OBJ/ca-rsa" ++IDENTITY_FILE="$OBJ/$USER-rsa" ++SSH_MAX_PUBKEY_BYTES=16384 ++ ++cat << EOF >> $OBJ/sshd_config ++TrustedUserCAKeys $CA_FILE.pub ++Protocol 2 ++PubkeyAuthentication yes ++AuthenticationMethods publickey ++AuthorizedPrincipalsFile $AUTH_PRINC_FILE ++ForceCommand=/bin/env ++EOF ++ ++cleanup() { ++ rm -f $CA_FILE{.pub,} ++ rm -f $IDENTITY_FILE{-cert.pub,.pub,} ++ rm -f $AUTH_PRINC_FILE ++} ++ ++make_keys_and_certs() { ++ rm -f $CA_FILE{.pub,} ++ rm -f $IDENTITY_FILE{-cert.pub,.pub,} ++ ++ local princs=$1 ++ ++ ${SSHKEYGEN} -q -t rsa -C '' -N '' -f $CA_FILE || ++ fatal 'Could not create CA key' ++ ++ ${SSHKEYGEN} -q -t rsa -C '' -N '' -f $IDENTITY_FILE || ++ fatal 'Could not create keypair' ++ ++ ${SSHKEYGEN} -q -s $CA_FILE -I $CERT_ID -n "$princs" -z "42" "$IDENTITY_FILE.pub" || ++ fatal "Could not create SSH cert" ++} ++ ++test_with_expected_principals() { ++ local princs=$1 ++ ++ out=$(${SSH} -E thlog -F $OBJ/ssh_config -i "$IDENTITY_FILE" somehost false) || ++ fatal "SSH failed" ++ ++ echo "$out" | grep -q "SSH_CERT_PRINCIPALS=$princs$" || ++ fatal "SSH_CERT_PRINCIPALS has incorrect value" ++} ++ ++test_with_no_expected_principals() { ++ local princs=$1 ++ ++ out=$(${SSH} -E thlog -F $OBJ/ssh_config -i "$IDENTITY_FILE" somehost false) || ++ fatal "SSH failed" ++ ++ echo "$out" | grep -vq "SSH_CERT_PRINCIPALS" || ++ fatal "SSH_CERT_PRINCIPALS env should not be set" ++ ++ echo "$out" | grep -vq "SSH_CERT_PRINCIPALS=$princs" || ++ fatal "SSH_CERT_PRINCIPALS has incorrect value" ++} ++ ++ ++echo 'a' > $AUTH_PRINC_FILE ++start_sshd ++ ++principals="a,b,c,d" ++make_keys_and_certs "$principals" ++test_with_expected_principals "$principals" ++ ++big_princ=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 16381 | head -n 1) ++make_keys_and_certs "a,$big_princ" ++test_with_expected_principals "a,$big_princ" ++ ++# No room for two principals ++big_princ=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 16382 | head -n 1) ++make_keys_and_certs "a,$big_princ" ++test_with_expected_principals "a" ++ ++make_keys_and_certs "$big_princ,a" ++test_with_expected_principals "$big_princ" ++ ++big_princ=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 16384 | head -n 1) ++make_keys_and_certs "a,$big_princ" ++test_with_expected_principals "a" ++ ++# principal too big for buffer ++big_princ=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w $SSH_MAX_PUBKEY_BYTES | head -n 1) ++make_keys_and_certs "$big_princ" ++test_with_no_expected_principals "$big_princ" ++ ++# no matching principals in certificate and auth princ file ++principals="b,c,d" ++make_keys_and_certs "$principals" ++test_with_no_expected_principals "$principals" ++ ++stop_sshd ++ ++cat << EOF >> $OBJ/sshd_config ++TrustedUserCAKeys $CA_FILE.pub ++Protocol 2 ++PubkeyAuthentication yes ++AuthenticationMethods publickey ++AuthorizedPrincipalsFile $AUTH_PRINC_FILE ++EOF ++ ++start_sshd ++ ++# no force command, no princpals ++principals="a,b,c,d" ++make_keys_and_certs "$principals" ++test_with_no_expected_principals "$principals" ++ ++stop_sshd ++ ++cat << EOF >> $OBJ/sshd_config ++Protocol 2 ++PubkeyAuthentication yes ++AuthenticationMethods publickey ++AuthorizedPrincipalsFile $AUTH_PRINC_FILE ++EOF ++ ++start_sshd ++ ++# No TrustedUserCAKeys causes pubkey auth, no principals ++principals="a,b,c,d" ++make_keys_and_certs "$principals" ++test_with_no_expected_principals "$principals" diff --git a/fb87_slog.patch b/fb87_slog.patch new file mode 100644 index 0000000..01f68b0 --- /dev/null +++ b/fb87_slog.patch @@ -0,0 +1,1167 @@ +Index: openssh-9.9p1/slog.c +=================================================================== +--- /dev/null ++++ openssh-9.9p1/slog.c +@@ -0,0 +1,619 @@ ++/* ++ * Copyright 2004-present Facebook. All Rights Reserved. ++ */ ++ ++ /* When using slogctxt in any module perform a NULL pointer check */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "includes.h" ++#include "slog.h" ++#include "log.h" ++#include "misc.h" ++#include "servconf.h" ++#include "xmalloc.h" ++ ++typedef struct Structuredlogctxt Structuredlogctxt; ++ ++struct Structuredlogctxt { /* items we care about logging */ ++ char server_ip[SLOG_SHORT_STRING_LEN]; // server_ip ++ int server_port; // server_port ++ char remote_ip[SLOG_SHORT_STRING_LEN]; // remote_ip ++ int remote_port; // remote_port ++ pid_t pam_process_pid; // pam_pid ++ char session_id[SLOG_STRING_LEN]; // session_id ++ char method[SLOG_STRING_LEN]; // method ++ char cert_id[SLOG_STRING_LEN]; // cert_id ++ unsigned long long cert_serial; // cert_serial ++ char principal[SLOG_STRING_LEN]; // principal ++ char user[SLOG_STRING_LEN]; // user ++ char command[SLOG_LONG_STRING_LEN]; // command ++ SLOG_SESSION_STATE session_state; // session_state ++ SLOG_AUTHENTICATED auth_successful; // auth_successful ++ time_t start_time; // start_time ++ time_t end_time; // end_time ++ int duration; // duration ++ pid_t main_pid; // main_pid ++ char auth_info[SLOG_MEDIUM_STRING_LEN]; // auth_info ++ char client_version[SLOG_STRING_LEN]; // client_version ++}; ++ ++Structuredlogctxt *slogctxt; ++extern ServerOptions options; ++ ++// Define token constants and default syntax format ++static const char *server_ip_token = "server_ip"; ++static const char *server_port_token = "server_port"; ++static const char *remote_ip_token = "remote_ip"; ++static const char *remote_port_token = "remote_port"; ++static const char *pam_pid_token = "pam_pid"; ++static const char *process_pid_token = "pid"; ++static const char *session_id_token = "session_id"; ++static const char *method_token = "method"; ++static const char *cert_id_token = "cert_id"; ++static const char *cert_serial_token = "cert_serial"; ++static const char *principal_token = "principal"; ++static const char *user_token = "user"; ++static const char *command_token = "command"; ++static const char *session_state_token = "session_state"; ++static const char *auth_successful_token = "auth_successful"; ++static const char *start_time_token = "start_time"; ++static const char *end_time_token = "end_time"; ++static const char *duration_token = "duration"; ++static const char *main_pid_token = "main_pid"; ++static const char *auth_info_token = "auth_info"; ++static const char *client_version = "client_version"; ++ ++/* Example log format sshd_config ++ * LogFormatPrefix sshd_auth_msg: ++ * LogFormatKeys server_ip server_port remote_ip remote_port pid session_id / ++ method cert_id cert_serial principal user session_state auth_successful / ++ start_time command # NO LINE BREAKS ++ * LogFormatJson yes # no makes this output an json array vs json object ++ */ ++ ++// Set a arbitrary large size so we can feed a potentially ++// large json object to the logger ++#define SLOG_BUF_SIZE 8192 ++#define SLOG_TRUNCATED_MESSAGE_JSON ", \"incomplete\": \"true\"}" ++#define SLOG_TRUNCATED_MESSAGE_ARRAY ", \"incomplete\"]" ++#define SLOG_TRUNCATED_SIZE 25 ++/* size of format for JSON for quotes_colon_space around key and comma_space ++ after value or closure_null */ ++#define SLOG_JSON_FORMAT_SIZE 6 ++#define SLOG_BUF_CALC_SIZE SLOG_BUF_SIZE - SLOG_TRUNCATED_SIZE ++ ++/* private declarations */ ++static void slog_log(void); ++static void slog_cleanup(void); ++static void slog_generate_auth_payload(char *); ++static void slog_escape_value(char *, char *, size_t); ++static void slog_get_safe_from_token(char *, const char *); ++static const char* slog_get_state_text(void); ++ ++/* public functions */ ++ ++void ++slog_init(void) ++{ ++ /* initialize only if we have log_format_keys */ ++ if (options.num_log_format_keys != 0) { ++ slogctxt = xcalloc(1, sizeof(Structuredlogctxt)); ++ if (slogctxt != NULL) ++ slog_cleanup(); ++ } ++} ++ ++void ++slog_pam_session_opened(void) ++{ ++ if (slogctxt != NULL) { ++ slogctxt->session_state = SLOG_SESSION_OPEN; ++ slogctxt->pam_process_pid = getpid(); ++ } ++} ++ ++void ++slog_set_auth_data(int authenticated, const char *method, const char *user) ++{ ++ if (slogctxt != NULL) { ++ slogctxt->auth_successful = ++ authenticated ? SLOG_AUTHORIZED : SLOG_UNAUTHORIZED; ++ strlcpy(slogctxt->method, method, SLOG_SHORT_STRING_LEN); ++ strlcpy(slogctxt->user, user, SLOG_STRING_LEN); ++ } ++} ++ ++void ++slog_set_cert_id(const char *id) ++{ ++ if (slogctxt != NULL) ++ strlcpy(slogctxt->cert_id, id, SLOG_STRING_LEN); ++} ++ ++ ++void ++slog_set_cert_serial(unsigned long long serial) ++{ ++ if (slogctxt != NULL) ++ slogctxt->cert_serial = serial; ++} ++ ++void ++slog_set_connection(const char *remote_ip, int remote_port, ++ const char *server_ip, int server_port, const char *session) ++{ ++ if (slogctxt != NULL) { ++ strlcpy(slogctxt->remote_ip, remote_ip, SLOG_SHORT_STRING_LEN); ++ slogctxt->remote_port = remote_port; ++ strlcpy(slogctxt->server_ip, server_ip, SLOG_SHORT_STRING_LEN); ++ slogctxt->server_port = server_port; ++ strlcpy(slogctxt->session_id, session, SLOG_STRING_LEN); ++ slogctxt->start_time = time(NULL); ++ slogctxt->main_pid = getpid(); ++ } ++} ++ ++void ++slog_set_client_version(const char *version) ++{ ++ if (slogctxt != NULL) { ++ if (strlen(version) < SLOG_STRING_LEN) ++ strlcpy(slogctxt->client_version, version, SLOG_STRING_LEN); ++ else { ++ // version can be up to 256 bytes, truncate to 95 and add ' ...' ++ // which will fit in SLOG_STRING_LEN ++ snprintf(slogctxt->client_version, SLOG_STRING_LEN, "%.95s ...", version); ++ } ++ } ++} ++ ++void ++slog_set_command(const char *command) ++{ ++ if (slogctxt != NULL) { ++ if (strlen(command) < SLOG_LONG_STRING_LEN) ++ strlcpy(slogctxt->command, command, SLOG_LONG_STRING_LEN); ++ else { ++ // If command is longer than allowed we truncate it to ++ // 1995 (SLOG_LONG_STRING_LEN - 5) characters and add ' ...\0' to ++ // the end of the command. ++ snprintf(slogctxt->command, SLOG_LONG_STRING_LEN, "%.1995s ...", command); ++ } ++ } ++} ++ ++void ++slog_set_principal(const char *principal) ++{ ++ if (slogctxt != NULL) ++ strlcpy(slogctxt->principal, principal, SLOG_STRING_LEN); ++} ++ ++void ++slog_set_user(const char *user) ++{ ++ if (slogctxt != NULL) ++ strlcpy(slogctxt->user, user, SLOG_STRING_LEN); ++} ++ ++void ++slog_set_auth_info(const char *auth_info) ++{ ++ if (slogctxt != NULL) ++ strlcpy(slogctxt->auth_info, auth_info, SLOG_MEDIUM_STRING_LEN); ++} ++ ++void ++slog_exit_handler(void) ++{ ++ /* to prevent duplicate logging we only log based on the pid set */ ++ if (slogctxt != NULL) { ++ if (slogctxt->server_ip[0] == 0) ++ return; // not initialized ++ if (slogctxt->main_pid != getpid()) ++ return; // not main process ++ if (slogctxt->session_state == SLOG_SESSION_INIT) ++ slog_log(); ++ else { ++ slogctxt->session_state = SLOG_SESSION_CLOSED; ++ slogctxt->end_time = time(NULL); ++ slogctxt->duration = slogctxt->end_time - slogctxt->start_time; ++ slog_log(); ++ slog_cleanup(); ++ } ++ } ++} ++ ++void ++slog_log_session(void) ++{ ++ if (slogctxt != NULL) { ++ slogctxt->session_state = SLOG_SESSION_OPEN; ++ slog_log(); ++ } ++} ++ ++/* private function scope begin */ ++ ++static void ++slog_log(void) ++{ ++ char *buffer = xmalloc(SLOG_BUF_SIZE); ++ ++ if (buffer == NULL) ++ return; ++ ++ memset(buffer, 0, SLOG_BUF_SIZE); ++ ++ if (options.num_log_format_keys > 0 ++ && slogctxt != NULL ++ && slogctxt->server_ip[0] != 0 ++ && slogctxt->user[0] != 0) { ++ slog_generate_auth_payload(buffer); ++ do_log_slog_payload(buffer); ++ } ++ ++ free(buffer); ++} ++ ++static void ++slog_cleanup(void) ++{ ++ // Reset the log context values ++ if (slogctxt != NULL) { ++ memset(slogctxt, 0, sizeof(Structuredlogctxt)); ++ slogctxt->session_state = SLOG_SESSION_INIT; ++ slogctxt->auth_successful = SLOG_UNAUTHORIZED; ++ } ++} ++ ++/* We use debug3 since the debug is very noisy */ ++static void ++slog_generate_auth_payload(char *buf) ++{ ++ if (buf == NULL) ++ return; ++ ++ // Create large buffer so don't risk overflow ++ char *safe = xmalloc(SLOG_BUF_SIZE); ++ memset(safe, 0, SLOG_BUF_SIZE); ++ ++ if (safe == NULL) ++ return; ++ ++ int i; ++ size_t remaining; ++ int json = options.log_format_json; ++ int keys = options.num_log_format_keys; ++ int truncated = 0; ++ char *tmpbuf = buf; ++ char *key; ++ ++ debug3("JSON format is %d with %d tokens.", json, keys); ++ ++ if (options.log_format_prefix != NULL ++ && strlen(options.log_format_prefix) < SLOG_BUF_CALC_SIZE-1) { ++ tmpbuf += snprintf(tmpbuf, SLOG_BUF_CALC_SIZE, ++ "%s ", options.log_format_prefix); ++ } ++ *tmpbuf++ = (json) ? '{' : '['; ++ debug3("current buffer after prefix: %s", buf); ++ ++ // Loop through the keys filling out the output string ++ for (i = 0; i < keys; i++) { ++ safe[0] = 0; // clear safe string ++ key = options.log_format_keys[i]; ++ remaining = SLOG_BUF_CALC_SIZE - (tmpbuf - buf); ++ ++ if (key == NULL) ++ continue; // Shouldn't happen but if null go to next key ++ ++ slog_get_safe_from_token(safe, key); ++ debug3("token: %s, value: %s", key, safe); ++ ++ if (json) { ++ if (*safe == '\0') ++ continue; // No value since we are using key pairs skip ++ if (remaining <= SLOG_JSON_FORMAT_SIZE + strlen(key) + strlen(safe)) { ++ debug("Log would exceed buffer size %u, %zu, %zu at key: %s", ++ (unsigned int)remaining, strlen(key), strlen(safe), key); ++ truncated = 1; ++ break; ++ } ++ tmpbuf += snprintf(tmpbuf, remaining, "%s\"%s\": %s", ++ i > 0 ? ", " : "", key, safe); ++ } else { ++ if (*safe == '\0') ++ strlcpy(safe, "\"\"", SLOG_SHORT_STRING_LEN); ++ if (remaining < SLOG_JSON_FORMAT_SIZE + strlen(safe)) { ++ debug("Log would exceed remaining buffer size %d, %zu, at key: %s", ++ (unsigned int)remaining, strlen(safe), key); ++ truncated = 1; ++ break; ++ } ++ tmpbuf += snprintf(tmpbuf, remaining, "%s%s", i > 0 ? ", " : "", safe); ++ } ++ debug3("current buffer after token: %s", buf); ++ debug3("end of loop key: %s, %d out of %d keys", key, i + 1, keys); ++ } ++ ++ // Close the log string. If truncated set truncated message and close string ++ if (truncated == 1) ++ strlcpy(tmpbuf, json ? SLOG_TRUNCATED_MESSAGE_JSON : ++ SLOG_TRUNCATED_MESSAGE_ARRAY, SLOG_TRUNCATED_SIZE); ++ else { ++ *tmpbuf++ = (json) ? '}' : ']'; ++ } ++ ++ free(safe); ++} ++ ++// buffer size is input string * 2 +1 ++static void ++slog_escape_value(char *output, char *input, size_t buffer_size) ++{ ++ int i; ++ buffer_size -= 2; ++ if (input != NULL) { ++ int input_size = strlen(input); ++ char *temp = output; ++ *temp++ = '"'; ++ buffer_size--; ++ for (i = 0; i < input_size && buffer_size > 0; i++) { ++ switch(input[i]) { ++ // characters escaped are the same as folly::json::escapeString ++ case 27: // ascii control character ++ if (buffer_size > 6) { ++ *temp++ = '\\'; ++ *temp++ = 'u'; ++ *temp++ = '0'; ++ *temp++ = '0'; ++ *temp++ = '1'; ++ *temp++ = 'b'; ++ buffer_size -= 6; ++ } ++ case '\b': ++ if (buffer_size > 1) { ++ *temp++ = '\\'; ++ *temp++ = 'b'; ++ buffer_size -= 2; ++ } ++ break; ++ case '\f': ++ if (buffer_size > 1) { ++ *temp++ = '\\'; ++ *temp++ = 'f'; ++ buffer_size -= 2; ++ } ++ break; ++ case '\n': ++ if (buffer_size > 1) { ++ *temp++ = '\\'; ++ *temp++ = 'n'; ++ buffer_size -= 2; ++ } ++ break; ++ case '\r': ++ if (buffer_size > 1) { ++ *temp++ = '\\'; ++ *temp++ = 'r'; ++ buffer_size -= 2; ++ } ++ break; ++ case '\t': ++ if (buffer_size > 1) { ++ *temp++ = '\\'; ++ *temp++ = 't'; ++ buffer_size -= 2; ++ } ++ break; ++ case '\"': ++ case '\\': ++ if (buffer_size > 1) { ++ *temp++ = '\\'; ++ buffer_size--; ++ } ++ default: // Non-escape char ++ *temp++ = input[i]; ++ buffer_size--; ++ } ++ } ++ *temp++ = '"'; ++ *temp++ = '\0'; ++ } ++} ++ ++static void ++slog_get_safe_from_token(char *output, const char *token) ++{ ++ if (output == NULL || token == NULL || slogctxt == NULL) ++ return; ++ ++ if (strcmp(token, server_ip_token) == 0) { ++ if (slogctxt->server_ip[0] != 0) { ++ snprintf(output, SLOG_SHORT_STRING_LEN, "\"%s\"", ++ slogctxt->server_ip); ++ } ++ return; ++ } ++ ++ if (strcmp(token, server_port_token) == 0) { ++ if (slogctxt->server_port > 0) { ++ snprintf(output, SLOG_SHORT_STRING_LEN, "\"%d\"", ++ slogctxt->server_port); ++ } ++ return; ++ } ++ ++ if (strcmp(token, remote_ip_token) == 0) { ++ if (slogctxt->remote_ip[0] != 0) { ++ snprintf(output, SLOG_SHORT_STRING_LEN, "\"%s\"", ++ slogctxt->remote_ip); ++ } ++ return; ++ } ++ ++ if (strcmp(token, remote_port_token) == 0) { ++ if (slogctxt->remote_port > 0) { ++ snprintf(output, SLOG_SHORT_STRING_LEN, "\"%d\"", ++ slogctxt->remote_port); ++ } ++ return; ++ } ++ ++ if (strcmp(token, pam_pid_token) == 0) { ++ if (slogctxt->pam_process_pid > 0) { ++ snprintf(output, SLOG_SHORT_STRING_LEN, "\"%ld\"", ++ (long)slogctxt->pam_process_pid); ++ } ++ return; ++ } ++ ++ if (strcmp(token, process_pid_token) == 0) { ++ snprintf(output, SLOG_SHORT_STRING_LEN, "\"%ld\"", (long)getpid()); ++ return; ++ } ++ ++ if (strcmp(token, session_id_token) == 0) { ++ if (slogctxt->session_id[0] != 0) { ++ snprintf(output, SLOG_STRING_LEN, "\"%s\"", ++ slogctxt->session_id); ++ } ++ return; ++ } ++ ++ if (strcmp(token, method_token) == 0) { ++ if (slogctxt->method[0] != 0) { ++ snprintf(output, SLOG_STRING_LEN, "\"%s\"", ++ slogctxt->method); ++ } ++ return; ++ } ++ ++ // Arbitrary input ++ if (strcmp(token, cert_id_token) == 0) { ++ if (slogctxt->cert_id[0] != 0 && ++ strcmp(slogctxt->method, "publickey") == 0) { ++ slog_escape_value(output, slogctxt->cert_id, ++ SLOG_STRING_LEN * 2 + 1); ++ } ++ return; ++ } ++ ++ if (strcmp(token, cert_serial_token) == 0) { ++ if (slogctxt->cert_serial > 0 && ++ strcmp(slogctxt->method, "publickey") == 0) { ++ snprintf(output, SLOG_SHORT_STRING_LEN, "\"%llu\"", ++ slogctxt->cert_serial); ++ } ++ return; ++ } ++ ++ // Arbitrary input ++ if (strcmp(token, principal_token) == 0) { ++ if (slogctxt->principal[0] != 0) { ++ slog_escape_value(output, slogctxt->principal, ++ SLOG_STRING_LEN * 2 + 1); ++ } ++ return; ++ } ++ ++ // Arbitrary input ++ if (strcmp(token, user_token) == 0) { ++ if (slogctxt->user[0] != 0) { ++ slog_escape_value(output, slogctxt->user, ++ SLOG_STRING_LEN * 2 + 1); ++ } ++ return; ++ } ++ ++ // Arbitrary input ++ if (strcmp(token, auth_info_token) == 0) { ++ if (slogctxt->auth_info[0] != 0) { ++ slog_escape_value(output, slogctxt->auth_info, ++ SLOG_MEDIUM_STRING_LEN * 2 + 1); ++ } ++ return; ++ } ++ ++ // Arbitrary input ++ if (strcmp(token, command_token) == 0) { ++ if (slogctxt->command[0] != 0) { ++ slog_escape_value(output, slogctxt->command, ++ SLOG_LONG_STRING_LEN * 2 + 1); ++ } ++ return; ++ } ++ ++ if (strcmp(token, auth_successful_token) == 0) { ++ snprintf(output, SLOG_SHORT_STRING_LEN, "\"%s\"", ++ slogctxt->auth_successful == SLOG_AUTHORIZED ? "true" : "false"); ++ return; ++ } ++ ++ if (strcmp(token, session_state_token) == 0) { ++ snprintf(output, SLOG_SHORT_STRING_LEN, "\"%s\"", ++ slog_get_state_text()); ++ return; ++ } ++ ++ if (strcmp(token, start_time_token) == 0) { ++ snprintf(output, SLOG_SHORT_STRING_LEN, "\"%d\"", ++ (int)slogctxt->start_time); ++ return; ++ } ++ ++ if (strcmp(token, end_time_token) == 0 && slogctxt->end_time > 0) { ++ snprintf(output, SLOG_SHORT_STRING_LEN, "\"%d\"", ++ (int)slogctxt->end_time); ++ return; ++ } ++ ++ if (strcmp(token, duration_token) == 0 && slogctxt->end_time > 0) { ++ snprintf(output, SLOG_SHORT_STRING_LEN, "\"%d\"", slogctxt->duration); ++ return; ++ } ++ ++ if (strcmp(token, main_pid_token) == 0) { ++ if (slogctxt->main_pid > 0) { ++ snprintf(output, SLOG_SHORT_STRING_LEN, "\"%ld\"", ++ (long)slogctxt->main_pid); ++ } ++ return; ++ } ++ ++ // Arbitrary input ++ if (strncmp(token, client_version, strlen(client_version)) == 0) { ++ if (slogctxt->client_version[0] != 0) { ++ slog_escape_value(output, slogctxt->client_version, ++ SLOG_STRING_LEN + 2); ++ } ++ return; ++ } ++} ++ ++static const char * ++slog_get_state_text(void) ++{ ++ if (slogctxt == NULL) ++ return ""; ++ ++ switch (slogctxt->session_state) { ++ case SLOG_SESSION_INIT: ++ return "Session failed"; ++ case SLOG_SESSION_OPEN: ++ return "Session opened"; ++ case SLOG_SESSION_CLOSED: ++ return "Session closed"; ++ default: ++ return "Unknown session state"; // Should never happen ++ } ++} +Index: openssh-9.9p1/servconf.c +=================================================================== +--- openssh-9.9p1.orig/servconf.c ++++ openssh-9.9p1/servconf.c +@@ -219,6 +219,9 @@ initialize_server_options(ServerOptions + options->disable_forwarding = -1; + options->expose_userauth_info = -1; + options->required_rsa_size = -1; ++ options->log_format_prefix = NULL; ++ options->num_log_format_keys = 0; ++ options->log_format_json = -1; + options->channel_timeouts = NULL; + options->num_channel_timeouts = 0; + options->unused_connection_timeout = -1; +@@ -519,6 +522,8 @@ fill_default_server_options(ServerOption + options->sk_provider = xstrdup("internal"); + if (options->required_rsa_size == -1) + options->required_rsa_size = SSH_RSA_MINIMUM_MODULUS_SIZE; ++ if (options->log_format_json == -1) ++ options->log_format_json = 0; + if (options->unused_connection_timeout == -1) + options->unused_connection_timeout = 0; + if (options->sshd_session_path == NULL) +@@ -609,6 +614,9 @@ typedef enum { + sExposeAuthInfo, sRDomain, sPubkeyAuthOptions, sSecurityKeyProvider, + sRequiredRSASize, sChannelTimeout, sUnusedConnectionTimeout, + sSshdSessionPath, sRefuseConnection, ++ /* Structured Logging options. Unless sLogFormatKeys is set, ++ structured logging is disabled */ ++ sLogFormatPrefix, sLogFormatKeys, sLogFormatJson, + sDeprecated, sIgnore, sUnsupported + } ServerOpCodes; + +@@ -791,6 +799,9 @@ static struct { + { "securitykeyprovider", sSecurityKeyProvider, SSHCFG_GLOBAL }, + { "requiredrsasize", sRequiredRSASize, SSHCFG_ALL }, + { "rsaminsize", sRequiredRSASize, SSHCFG_ALL }, /* alias */ ++ { "logformatprefix", sLogFormatPrefix, SSHCFG_GLOBAL }, ++ { "logformatkeys", sLogFormatKeys, SSHCFG_GLOBAL }, ++ { "logformatjson", sLogFormatJson, SSHCFG_GLOBAL }, + { "channeltimeout", sChannelTimeout, SSHCFG_ALL }, + { "unusedconnectiontimeout", sUnusedConnectionTimeout, SSHCFG_ALL }, + { "sshdsessionpath", sSshdSessionPath, SSHCFG_GLOBAL }, +@@ -2552,6 +2563,30 @@ process_server_config_line_depth(ServerO + } + break; + ++ case sLogFormatPrefix: ++ arg = argv_next(&ac, &av); ++ if (!arg || *arg == '\0') { ++ fatal("%.200s line %d: invalid log format prefix", ++ filename, linenum); ++ } ++ options->log_format_prefix = xstrdup(arg); ++ break; ++ ++ case sLogFormatKeys: ++ while ((arg = argv_next(&ac, &av)) && *arg != '\0') { ++ if (options->num_log_format_keys >= MAX_LOGFORMAT_KEYS) ++ fatal("%s line %d: too long format keys.", ++ filename, linenum); ++ if (!*activep) ++ continue; ++ options->log_format_keys[options->num_log_format_keys++] = xstrdup(arg); ++ } ++ break; ++ ++ case sLogFormatJson: ++ intptr = &options->log_format_json; ++ goto parse_flag; ++ + case sIPQoS: + arg = argv_next(&ac, &av); + if (!arg || *arg == '\0') +@@ -3326,6 +3361,7 @@ dump_config(ServerOptions *o) + dump_cfg_fmtint(sStreamLocalBindUnlink, o->fwd_opts.streamlocal_bind_unlink); + dump_cfg_fmtint(sFingerprintHash, o->fingerprint_hash); + dump_cfg_fmtint(sExposeAuthInfo, o->expose_userauth_info); ++ dump_cfg_fmtint(sLogFormatJson, o->log_format_json); + dump_cfg_fmtint(sRefuseConnection, o->refuse_connection); + + /* string arguments */ +@@ -3357,6 +3393,7 @@ dump_config(ServerOptions *o) + #if defined(__OpenBSD__) || defined(HAVE_SYS_SET_PROCESS_RDOMAIN) + dump_cfg_string(sRDomain, o->routing_domain); + #endif ++ dump_cfg_string(sLogFormatPrefix, o->log_format_prefix); + dump_cfg_string(sSshdSessionPath, o->sshd_session_path); + dump_cfg_string(sPerSourcePenaltyExemptList, o->per_source_penalty_exempt); + +@@ -3381,6 +3418,7 @@ dump_config(ServerOptions *o) + o->num_auth_methods, o->auth_methods); + dump_cfg_strarray_oneline(sLogVerbose, + o->num_log_verbose, o->log_verbose); ++ dump_cfg_strarray(sLogFormatKeys, o->num_log_format_keys, o->log_format_keys); + dump_cfg_strarray_oneline(sChannelTimeout, + o->num_channel_timeouts, o->channel_timeouts); + +Index: openssh-9.9p1/auth2-pubkey.c +=================================================================== +--- openssh-9.9p1.orig/auth2-pubkey.c ++++ openssh-9.9p1/auth2-pubkey.c +@@ -65,6 +65,7 @@ + #include "monitor_wrap.h" + #include "authfile.h" + #include "match.h" ++#include "slog.h" + #include "ssherr.h" + #include "channels.h" /* XXX for session.h */ + #include "session.h" /* XXX for child_set_env(); refactor? */ +@@ -584,7 +585,7 @@ user_cert_trusted_ca(struct passwd *pw, + goto out; + } + } +- ++ slog_set_cert_serial((unsigned long long)key->cert->serial); + /* Success */ + verbose("Accepted certificate ID \"%s\" (serial %llu) signed by " + "%s CA %s via %s", key->cert->key_id, +@@ -595,6 +596,7 @@ user_cert_trusted_ca(struct passwd *pw, + *authoptsp = final_opts; + final_opts = NULL; + } ++ slog_set_cert_id(key->cert->key_id); + ret = 1; + out: + sshauthopt_free(principals_opts); +Index: openssh-9.9p1/regress/test-exec.sh +=================================================================== +--- openssh-9.9p1.orig/regress/test-exec.sh ++++ openssh-9.9p1/regress/test-exec.sh +@@ -894,7 +894,7 @@ start_sshd () + + trace "wait for sshd" + i=0; +- while [ ! -f $PIDFILE -a $i -lt 10 ]; do ++ while [ ! -f $PIDFILE -a $i -lt 3 ]; do + i=`expr $i + 1` + sleep $i + done +Index: openssh-9.9p1/session.c +=================================================================== +--- openssh-9.9p1.orig/session.c ++++ openssh-9.9p1/session.c +@@ -94,6 +94,8 @@ + #include "monitor_wrap.h" + #include "sftp.h" + #include "atomicio.h" ++#include "slog.h" ++ + + #if defined(KRB5) && defined(USE_AFS) + #include +@@ -762,6 +764,7 @@ do_exec(struct ssh *ssh, Session *s, con + ssh_remote_ipaddr(ssh), + ssh_remote_port(ssh), + s->self); ++ slog_log_session(); + + #ifdef SSH_AUDIT_EVENTS + if (s->command != NULL || s->command_handle != -1) +@@ -1472,7 +1475,7 @@ do_setusercontext(struct passwd *pw) + perror("unable to set user context (setuser)"); + exit(1); + } +- /* ++ /* + * FreeBSD's setusercontext() will not apply the user's + * own umask setting unless running with the user's UID. + */ +@@ -2152,7 +2155,7 @@ session_exec_req(struct ssh *ssh, Sessio + sshpkt_fatal(ssh, r, "%s: parse packet", __func__); + + channel_set_xtype(ssh, s->chanid, "session:command"); +- ++ slog_set_command(command); + success = do_exec(ssh, s, command) == 0; + free(command); + return success; +@@ -2872,4 +2875,3 @@ session_get_remote_name_or_ip(struct ssh + remote = ssh_remote_ipaddr(ssh); + return remote; + } +- +Index: openssh-9.9p1/log.h +=================================================================== +--- openssh-9.9p1.orig/log.h ++++ openssh-9.9p1/log.h +@@ -86,6 +86,8 @@ void sshfatal(const char *, const char + void sshlogdirect(LogLevel, int, const char *, ...) + __attribute__((format(printf, 3, 4))); + ++void do_log_slog_payload(const char *); ++ + #define do_log2(level, ...) sshlog(__FILE__, __func__, __LINE__, 0, level, NULL, __VA_ARGS__) + #define debug3(...) sshlog(__FILE__, __func__, __LINE__, 0, SYSLOG_LEVEL_DEBUG3, NULL, __VA_ARGS__) + #define debug2(...) sshlog(__FILE__, __func__, __LINE__, 0, SYSLOG_LEVEL_DEBUG2, NULL, __VA_ARGS__) +Index: openssh-9.9p1/log.c +=================================================================== +--- openssh-9.9p1.orig/log.c ++++ openssh-9.9p1/log.c +@@ -547,3 +547,39 @@ sshlogdirect(LogLevel level, int forced, + va_end(args); + } + ++ ++/* Custom function to log to syslog, to handle the session logging code ++*/ ++void ++do_log_slog_payload(const char *payload) ++{ ++#if defined(HAVE_OPENLOG_R) && defined(SYSLOG_DATA_INIT) ++ struct syslog_data sdata = SYSLOG_DATA_INIT; ++#endif ++ int pri = LOG_INFO; ++ int saved_errno = errno; ++ LogLevel level = SYSLOG_LEVEL_INFO; ++ log_handler_fn *tmp_handler; ++ ++ if (log_handler != NULL) { ++ /* Avoid recursion */ ++ tmp_handler = log_handler; ++ log_handler = NULL; ++ tmp_handler(level, 0, payload, log_handler_ctx); ++ log_handler = tmp_handler; ++ } else if (log_on_stderr) { ++ (void)write(log_stderr_fd, payload, strlen(payload)); ++ (void)write(log_stderr_fd, "\r\n", 2); ++ } else { ++#if defined(HAVE_OPENLOG_R) && defined(SYSLOG_DATA_INIT) ++ openlog_r(argv0 ? argv0 : __progname, LOG_PID, log_facility, &sdata); ++ syslog_r(pri, &sdata, "%s", payload); ++ closelog_r(&sdata); ++#else ++ openlog(argv0 ? argv0 : __progname, LOG_PID, log_facility); ++ syslog(pri, "%s", payload); ++ closelog(); ++#endif ++ } ++ errno = saved_errno; ++} +Index: openssh-9.9p1/slog.h +=================================================================== +--- /dev/null ++++ openssh-9.9p1/slog.h +@@ -0,0 +1,41 @@ ++/* ++ * Copyright 2004-present Facebook. All Rights Reserved. ++ */ ++#ifndef USE_SLOG ++#define USE_SLOG ++ ++#define SLOG_STRING_LEN 100 ++#define SLOG_MEDIUM_STRING_LEN 1000 ++#define SLOG_SHORT_STRING_LEN 50 ++#define SLOG_LONG_STRING_LEN 2000 ++ ++typedef enum { ++ SLOG_SESSION_INIT, ++ SLOG_SESSION_OPEN, ++ SLOG_SESSION_CLOSED, ++} SLOG_SESSION_STATE; ++ ++typedef enum { ++ SLOG_UNAUTHORIZED = 0, ++ SLOG_AUTHORIZED = 1 ++} SLOG_AUTHENTICATED; ++ ++void slog_init(void); ++ ++// setters ++void slog_pam_session_opened(void); ++void slog_set_auth_data(int , const char *, const char *); ++void slog_set_cert_id(const char *); ++void slog_set_cert_serial(unsigned long long ); ++void slog_set_connection(const char *, int, const char *, int, const char *); ++void slog_set_command(const char *); ++void slog_set_principal(const char *); ++void slog_set_user(const char *); ++void slog_set_auth_info(const char *); ++void slog_set_client_version(const char *); ++ ++// loggers ++void slog_exit_handler(void); ++void slog_log_session(void); ++ ++#endif +Index: openssh-9.9p1/sshd.c +=================================================================== +--- openssh-9.9p1.orig/sshd.c ++++ openssh-9.9p1/sshd.c +@@ -93,6 +93,8 @@ + #include "sk-api.h" + #include "addr.h" + #include "srclimit.h" ++#include "slog.h" ++#include + + /* Re-exec fds */ + #define REEXEC_DEVCRYPTO_RESERVED_FD (STDERR_FILENO + 1) +@@ -275,6 +277,12 @@ child_register(int pipefd, int sockfd) + raddr = get_peer_ipaddr(sockfd); + xasprintf(&child->id, "connection from %s to %s", raddr, laddr); + } ++ slog_set_connection(raddr, ++ rport, ++ laddr, ++ lport, ++ get_log_session_id()); ++ + free(laddr); + free(raddr); + if (++children_active > options.max_startups) +@@ -1713,6 +1721,7 @@ main(int ac, char **av) + } + /* Reinitialize the log (because of the fork above). */ + log_init(__progname, options.log_level, options.log_facility, log_stderr); ++ slog_init(); + + if (FIPS_mode()) { + debug("FIPS mode initialized"); +@@ -1819,6 +1828,7 @@ main(int ac, char **av) + void + cleanup_exit(int i) + { ++ slog_exit_handler(); + _exit(i); + } + +Index: openssh-9.9p1/sshd_config +=================================================================== +--- openssh-9.9p1.orig/sshd_config ++++ openssh-9.9p1/sshd_config +@@ -33,6 +33,15 @@ Include /etc/ssh/sshd_config.d/*.conf + # Logging + #SyslogFacility AUTH + #LogLevel INFO ++# Structured logging ++# The default is no structured logging, to enable structured logging at least ++# one LogFormatKeys must be set. LogFormatJson defaults to no (array) to keep ++# the log line size smaller, if keys are desired set LogFormatJson to yes ++LogFormatPrefix sshd_auth_msg: ++LogFormatKeys server_ip server_port remote_ip remote_port pid session_id method ++LogFormatKeys cert_id cert_serial principal user session_state auth_successful ++LogFormatKeys start_time command ++LogFormatJson yes + + # Authentication: + +Index: openssh-9.9p1/auth-pam.c +=================================================================== +--- openssh-9.9p1.orig/auth-pam.c ++++ openssh-9.9p1/auth-pam.c +@@ -89,6 +89,7 @@ + #include "auth-pam.h" + #include "canohost.h" + #include "log.h" ++#include "slog.h" + #include "msg.h" + #include "packet.h" + #include "misc.h" +@@ -1222,9 +1223,12 @@ do_pam_session(struct ssh *ssh) + if (sshpam_err != PAM_SUCCESS) + fatal("PAM: failed to set PAM_CONV: %s", + pam_strerror(sshpam_handle, sshpam_err)); ++ + sshpam_err = pam_open_session(sshpam_handle, 0); +- if (sshpam_err == PAM_SUCCESS) ++ if (sshpam_err == PAM_SUCCESS) { ++ slog_pam_session_opened(); + sshpam_session_open = 1; ++ } + else { + sshpam_session_open = 0; + auth_restrict_session(ssh); +Index: openssh-9.9p1/servconf.h +=================================================================== +--- openssh-9.9p1.orig/servconf.h ++++ openssh-9.9p1/servconf.h +@@ -20,6 +20,8 @@ + + #define MAX_PORTS 256 /* Max # ports. */ + ++#define MAX_LOGFORMAT_KEYS 256 /* Max # LogFormatKeys */ ++ + /* permit_root_login */ + #define PERMIT_NOT_SET -1 + #define PERMIT_NO 0 +@@ -251,6 +253,11 @@ typedef struct { + u_int64_t timing_secret; + char *sk_provider; + int required_rsa_size; /* minimum size of RSA keys */ ++ ++ char *log_format_prefix; ++ u_int num_log_format_keys; ++ char *log_format_keys[MAX_LOGFORMAT_KEYS]; ++ int log_format_json; /* 1 to return "token": "token_val" in log format */ + + char **channel_timeouts; /* inactivity timeout by channel type */ + u_int num_channel_timeouts; +Index: openssh-9.9p1/regress/slog.sh +=================================================================== +--- /dev/null ++++ openssh-9.9p1/regress/slog.sh +@@ -0,0 +1,59 @@ ++tid='structured log' ++ ++port="4242" ++log_prefix="sshd_auth_msg:" ++log_keys="server_ip server_port remote_ip remote_port pid session_id method cert_id cert_serial principal user session_state auth_successful _time command end_time duration auth_info client_version" ++do_log_json="yes" ++test_config="$OBJ/sshd2_config" ++old_config="$OBJ/sshd_config" ++PIDFILE=$OBJ/pidfile ++ ++cat << EOF > $test_config ++ #*: ++ StrictModes no ++ Port $port ++ AddressFamily inet ++ ListenAddress 127.0.0.1 ++ #ListenAddress ::1 ++ PidFile $PIDFILE ++ AuthorizedKeysFile $OBJ/authorized_keys_%u ++ LogLevel ERROR ++ AcceptEnv _XXX_TEST_* ++ AcceptEnv _XXX_TEST ++ HostKey $OBJ/host.ssh-ed25519 ++ LogFormatPrefix $log_prefix ++ LogFormatJson $do_log_json ++ LogFormatKeys $log_keys ++EOF ++ ++ ++cp $test_config $old_config ++start_sshd ++ ++${SSH} -F $OBJ/ssh_config somehost true ++if [ $? -ne 0 ]; then ++ fail "ssh connect with failed" ++fi ++ ++test_log_counts() { ++ cnt=$(grep -c "$log_prefix" "$TEST_SSHD_LOGFILE") ++ if [ $cnt -ne 2 ]; then ++ fail "expected 2 structured logging lines, got $cnt" ++ fi ++} ++ ++test_json_valid() { ++ which python &>/dev/null || echo 'python not found in path, skipping tests' ++ ++ loglines=$(cat "$TEST_SSHD_LOGFILE" | grep "$log_prefix") ++ first=$(echo "$loglines" | head -n1) ++ last=$(echo "$loglines" | tail -n1) ++ ++ echo ${first:$(expr length $log_prefix)} | python -m json.tool &>/dev/null \ ++ || fail "invalid json structure $first" ++ echo ${last:$(expr length $log_prefix)} | python -m json.tool &>/dev/null \ ++ || fail "invalid json structure $last" ++} ++ ++test_log_counts ++test_json_valid +Index: openssh-9.9p1/Makefile.in +=================================================================== +--- openssh-9.9p1.orig/Makefile.in ++++ openssh-9.9p1/Makefile.in +@@ -125,7 +125,7 @@ SSHOBJS= ssh.o readconf.o clientloop.o s + + SSHDOBJS=sshd.o \ + platform-listen.o \ +- servconf.o sshpty.o srclimit.o groupaccess.o auth2-methods.o \ ++ servconf.o sshpty.o srclimit.o slog.o groupaccess.o auth2-methods.o \ + dns.o fatal.o compat.o utf8.o authfd.o canohost.o \ + $(SKOBJS) + +@@ -135,7 +135,7 @@ SSHD_SESSION_OBJS=sshd-session.o auth-rh + auth.o auth2.o auth2-methods.o auth-options.o session.o \ + auth2-chall.o groupaccess.o \ + auth-bsdauth.o auth2-hostbased.o auth2-kbdint.o \ +- auth2-none.o auth2-passwd.o auth2-pubkey.o auth2-pubkeyfile.o \ ++ auth2-none.o auth2-passwd.o slog.o auth2-pubkey.o auth2-pubkeyfile.o \ + monitor.o monitor_wrap.o auth-krb5.o kexgsss.o \ + auth2-gss.o gss-serv.o gss-serv-krb5.o \ + loginrec.o auth-pam.o auth-shadow.o auth-sia.o \ +@@ -321,7 +321,7 @@ distclean: regressclean + rm -f *.o *.a $(TARGETS) logintest config.cache config.log + rm -f *.out core opensshd.init openssh.xml + rm -f Makefile buildpkg.sh config.h config.status +- rm -f survey.sh openbsd-compat/regress/Makefile *~ ++ rm -f survey.sh openbsd-compat/regress/Makefile *~ + rm -rf autom4te.cache + rm -f regress/check-perm + rm -f regress/mkdtemp +Index: openssh-9.9p1/auth.c +=================================================================== +--- openssh-9.9p1.orig/auth.c ++++ openssh-9.9p1/auth.c +@@ -75,6 +75,7 @@ + #include "monitor_wrap.h" + #include "ssherr.h" + #include "channels.h" ++#include "slog.h" + + /* import */ + extern ServerOptions options; +@@ -305,6 +306,7 @@ auth_log(struct ssh *ssh, int authentica + extra != NULL ? extra : ""); + + free(extra); ++ slog_set_auth_data(authenticated, method, authctxt->user); + + #if defined(CUSTOM_FAILED_LOGIN) || defined(SSH_AUDIT_EVENTS) + if (authenticated == 0 && !(authctxt->postponed || partial)) { +@@ -462,6 +464,7 @@ check_key_in_hostfiles(struct passwd *pw + struct passwd * + getpwnamallow(struct ssh *ssh, const char *user) + { ++ slog_set_user(user); + #ifdef HAVE_LOGIN_CAP + extern login_cap_t *lc; + #ifdef HAVE_AUTH_HOSTOK +Index: openssh-9.9p1/auth2-pubkeyfile.c +=================================================================== +--- openssh-9.9p1.orig/auth2-pubkeyfile.c ++++ openssh-9.9p1/auth2-pubkeyfile.c +@@ -50,6 +50,7 @@ + #include "authfile.h" + #include "match.h" + #include "ssherr.h" ++#include "slog.h" + + int + auth_authorise_keyopts(struct passwd *pw, struct sshauthopt *opts, +@@ -249,6 +250,7 @@ auth_process_principals(FILE *f, const c + snprintf(loc, sizeof(loc), "%.200s:%lu", file, linenum); + if (auth_check_principals_line(cp, cert, loc, authoptsp) == 0) + found_principal = 1; ++ slog_set_principal(cp); + } + debug2_f("%s: processed %lu/%lu lines", file, nonblank, linenum); + free(line); diff --git a/openssh.spec b/openssh.spec index 1f88c66..1900eaf 100644 --- a/openssh.spec +++ b/openssh.spec @@ -5,6 +5,10 @@ %global WITH_SELINUX 0 %endif +# Useful development mode for porting patches from +# a different release +%global use_quilt 0 + %global _hardened_build 1 # Do we want to disable building of gnome-askpass? (1=yes 0=no) @@ -40,6 +44,8 @@ %global openssh_ver 9.9p1 %global openssh_rel 4 +%global hyperscale_rel 1 + Summary: An open source implementation of SSH protocol version 2 Name: openssh @@ -65,6 +71,10 @@ Source20: ssh-host-keys-migration.sh Source21: ssh-host-keys-migration.service Source22: parallel_test.sh Source23: parallel_test.Makefile +Source24: series +%if 0%{?facebook} && 0%{?use_quilt} +BuildRequires: quilt +%endif #https://bugzilla.mindrot.org/show_bug.cgi?id=2581 Patch100: openssh-6.7p1-coverity.patch @@ -200,6 +210,33 @@ Patch1018: openssh-8.7p1-openssl-log.patch # upstream 0c3927c45f8a57b511c874c4d51a8c89414f74ef Patch1019: openssh-9.9p1-mlkembe.patch +# Add a unique log session identifier to output messages for +# each sshd process and its children. +Patch2010: fb87_log_session_id.patch +# Add structured logging +Patch2011: fb87_slog.patch +# Add a log entry when a session is started over a local forward port. +Patch2012: fb87_log_port_forwards.patch +# Add a log line when a session is started over a reverse port forward. +Patch2013: fb87_070_logging_reverse_port_forward.patch +# Increase ssh cert max principals from 256 to 1024. +Patch2014: fb87_810_increase_ssh_cert_max_principals.patch +# Output a line in the logs showing the command run, or shell request +# and the user. +Patch2015: fb87_090_logging_shell_cmd_pty.patch +# Output a line in the logs showing which principal was matched when +# certificate authentication was used. +Patch2016: fb87_080_logging_certificates.patch +# Add verbose logging for setting env variables. +Patch2017: fb87_log_accept_env.patch +# Set an environment variable SSH_CERT_PRINCIPALS in the child process +# to be the full principal list of a user's SSH certificate when forced +# command is present and the user is authenticated by the certificate. +Patch2018: fb87_pass_principals_to_child.patch +# Log extra authentication information to the auth_info structured +# logging field, and add tests for pubkey and cert auth. +Patch2019: fb87_log_auth_info.patch + License: BSD-3-Clause AND BSD-2-Clause AND ISC AND SSH-OpenSSH AND ssh-keyscan AND sprintf AND LicenseRef-Fedora-Public-Domain AND X11-distribute-modifications-variant Requires: /sbin/nologin @@ -386,6 +423,24 @@ gpgv2 --quiet --keyring %{SOURCE3} %{SOURCE1} %{SOURCE0} %patch -P 100 -p1 -b .coverity +%if 0%{?facebook} && !0%{?use_quilt} +%patch -P 2010 -p1 -b .log_session_id +%patch -P 2011 -p1 -b .slog +%patch -P 2012 -p1 -b .log_port_forwards +%patch -P 2013 -p1 -b .logging_reverse_port_forward +%patch -P 2014 -p1 -b .increase_ssh_cert_max_principals +%patch -P 2015 -p1 -b .logging_shell_cmd_pty +%patch -P 2016 -p1 -b .logging_certificates +%patch -P 2017 -p1 -b .log_accept_env +%patch -P 2018 -p1 -b .pass_principals_to_child +%patch -P 2019 -p1 -b .log_auth_info +%endif + +%if 0%{?facebook} && 0%{?use_quilt} +ln -sf %{_sourcedir} patches +quilt push -a +%endif + autoreconf %build @@ -662,6 +717,9 @@ test -f %{sysconfig_anaconda} && \ %attr(0755,root,root) %{_libdir}/sshtest/sk-dummy.so %changelog +* Fri Nov 22 2024 Vishal Mishra 9.9p1-4.1 +- Merge fb patches to c10s to create c10s-sig-hyperscale + * Tue Oct 29 2024 Troy Dawson - 9.9p1-4.1 - Bump release for October 2024 mass rebuild: Resolves: RHEL-64018 diff --git a/series b/series new file mode 100644 index 0000000..8fe0e6e --- /dev/null +++ b/series @@ -0,0 +1,10 @@ +fb87_log_session_id.patch +fb87_slog.patch +fb87_log_port_forwards.patch +fb87_070_logging_reverse_port_forward.patch +fb87_810_increase_ssh_cert_max_principals.patch +fb87_090_logging_shell_cmd_pty.patch +fb87_080_logging_certificates.patch +fb87_log_accept_env.patch +fb87_pass_principals_to_child.patch +fb87_log_auth_info.patch