From d1c05fe05dac6eea8ea42e704e0602307962abd5 Mon Sep 17 00:00:00 2001 From: Kent Peacock Date: Aug 24 2022 18:33:43 +0000 Subject: Initial checkin of integrated Meta patches, and specfile changes --- diff --git a/fbpatches/fb87_070_logging_reverse_port_forward.patch b/fbpatches/fb87_070_logging_reverse_port_forward.patch new file mode 100644 index 0000000..339fd35 --- /dev/null +++ b/fbpatches/fb87_070_logging_reverse_port_forward.patch @@ -0,0 +1,48 @@ +Index: b/channels.c +=================================================================== +--- b.orig/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; + } + + /* +@@ -4593,7 +4603,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/fbpatches/fb87_080_logging_certificates.patch b/fbpatches/fb87_080_logging_certificates.patch new file mode 100644 index 0000000..46cc276 --- /dev/null +++ b/fbpatches/fb87_080_logging_certificates.patch @@ -0,0 +1,182 @@ +Index: b/auth2-pubkey.c +=================================================================== +--- b.orig/auth2-pubkey.c ++++ b/auth2-pubkey.c +@@ -389,6 +389,10 @@ check_principals_line(struct ssh *ssh, c + continue; + debug3("%s: matched principal \"%.100s\"", + loc, cert->principals[i]); ++ verbose("Matched principal \"%.100s\" from %s against \"%.100s\" " ++ "from cert", ++ cp, loc, cert->principals[i]); ++ + found = 1; + slog_set_principal(cp); + } +@@ -432,6 +436,8 @@ process_principals(struct ssh *ssh, FILE + found_principal = 1; + } + free(line); ++ if (!found_principal) ++ verbose("Did not match any principals from auth_principals_* files"); + return found_principal; + } + +@@ -710,7 +716,7 @@ check_authkey_line(struct ssh *ssh, stru + &reason) != 0) + goto fail_reason; + +- verbose("Accepted certificate ID \"%s\" (serial %llu) " ++ verbose("Accepted cert ID \"%s\" (serial %llu) " + "signed by CA %s %s found at %s", + key->cert->key_id, + (unsigned long long)key->cert->serial, +@@ -780,7 +786,7 @@ static int + user_cert_trusted_ca(struct ssh *ssh, struct passwd *pw, struct sshkey *key, + struct sshauthopt **authoptsp) + { +- char *ca_fp, *principals_file = NULL; ++ char *ca_fp, *key_fp, *principals_file = NULL; + const char *reason; + struct sshauthopt *principals_opts = NULL, *cert_opts = NULL; + struct sshauthopt *final_opts = NULL; +@@ -796,11 +802,16 @@ user_cert_trusted_ca(struct ssh *ssh, st + options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL) + return 0; + ++ key_fp = sshkey_fingerprint(key, options.fingerprint_hash, SSH_FP_DEFAULT); ++ + 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; + } + /* +@@ -851,6 +862,11 @@ user_cert_trusted_ca(struct ssh *ssh, st + 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, key_fp, ++ sshkey_type(key->cert->signature_key), ca_fp, ++ options.trusted_user_ca_keys); + error("%s", reason); + auth_debug_add("%s", reason); + goto out; +@@ -858,9 +874,10 @@ user_cert_trusted_ca(struct ssh *ssh, st + } + + /* Success */ +- verbose("Accepted certificate ID \"%s\" (serial %llu) signed by " +- "%s CA %s via %s", key->cert->key_id, +- (unsigned long long)key->cert->serial, ++ verbose("Accepted cert ID \"%s\" (serial %llu) with signature %s " ++ "signed by %s CA %s via %s", ++ key->cert->key_id, ++ (unsigned long long)key->cert->serial, key_fp, + sshkey_type(key->cert->signature_key), ca_fp, + options.trusted_user_ca_keys); + if (authoptsp != NULL) { +@@ -875,6 +892,7 @@ user_cert_trusted_ca(struct ssh *ssh, st + sshauthopt_free(final_opts); + free(principals_file); + free(ca_fp); ++ free(key_fp); + return ret; + } + +Index: b/regress/cert-logging.sh +=================================================================== +--- /dev/null ++++ b/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/fbpatches/fb87_090_logging_shell_cmd_pty.patch b/fbpatches/fb87_090_logging_shell_cmd_pty.patch new file mode 100644 index 0000000..6017884 --- /dev/null +++ b/fbpatches/fb87_090_logging_shell_cmd_pty.patch @@ -0,0 +1,73 @@ +Index: b/session.c +=================================================================== +--- b.orig/session.c ++++ b/session.c +@@ -2049,6 +2049,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); + +@@ -2148,6 +2150,7 @@ session_shell_req(struct ssh *ssh, Sessi + + if ((r = sshpkt_get_end(ssh)) != 0) + sshpkt_fatal(ssh, r, "%s: parse packet", __func__); ++ verbose("Shell Request for user %s", s->pw->pw_name); + return do_exec(ssh, s, NULL) == 0; + } + +@@ -2163,6 +2166,7 @@ session_exec_req(struct ssh *ssh, Sessio + sshpkt_fatal(ssh, r, "%s: parse packet", __func__); + + 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: b/regress/session-req.sh +=================================================================== +--- /dev/null ++++ b/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/fbpatches/fb87_810_increase_ssh_cert_max_principals.patch b/fbpatches/fb87_810_increase_ssh_cert_max_principals.patch new file mode 100644 index 0000000..2abe0e6 --- /dev/null +++ b/fbpatches/fb87_810_increase_ssh_cert_max_principals.patch @@ -0,0 +1,13 @@ +Index: b/sshkey.h +=================================================================== +--- b.orig/sshkey.h ++++ b/sshkey.h +@@ -106,7 +106,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/fbpatches/fb87_log_accept_env.patch b/fbpatches/fb87_log_accept_env.patch new file mode 100644 index 0000000..24ba1fc --- /dev/null +++ b/fbpatches/fb87_log_accept_env.patch @@ -0,0 +1,22 @@ +diff --git a/session.c b/session.c +--- a/session.c ++++ b/session.c +@@ -2106,16 +2106,16 @@ + + 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; + s->env[s->num_env].val = val; + s->num_env++; + 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/fbpatches/fb87_log_auth_info.patch b/fbpatches/fb87_log_auth_info.patch new file mode 100644 index 0000000..45f320a --- /dev/null +++ b/fbpatches/fb87_log_auth_info.patch @@ -0,0 +1,216 @@ +Index: b/regress/slog.sh +=================================================================== +--- b.orig/regress/slog.sh ++++ b/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: b/auth.c +=================================================================== +--- b.orig/auth.c ++++ b/auth.c +@@ -351,6 +351,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: b/auth2-pubkey.c +=================================================================== +--- b.orig/auth2-pubkey.c ++++ b/auth2-pubkey.c +@@ -722,7 +722,7 @@ check_authkey_line(struct ssh *ssh, stru + (unsigned long long)key->cert->serial, + sshkey_type(found), fp, loc); + +- slog_set_cert_serial(key->cert->serial); ++ slog_set_cert_serial(key->cert->serial); + success: + if (finalopts == NULL) + fatal_f("internal error: missing options"); +@@ -885,6 +885,8 @@ user_cert_trusted_ca(struct ssh *ssh, st + final_opts = NULL; + } + slog_set_cert_id(key->cert->key_id); ++ slog_set_cert_serial(key->cert->serial); ++ + ret = 1; + out: + sshauthopt_free(principals_opts); diff --git a/fbpatches/fb87_log_port_forwards.patch b/fbpatches/fb87_log_port_forwards.patch new file mode 100644 index 0000000..8a7b030 --- /dev/null +++ b/fbpatches/fb87_log_port_forwards.patch @@ -0,0 +1,24 @@ +Index: b/serverloop.c +=================================================================== +--- b.orig/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/fbpatches/fb87_log_session_id.patch b/fbpatches/fb87_log_session_id.patch new file mode 100644 index 0000000..ac9d6a3 --- /dev/null +++ b/fbpatches/fb87_log_session_id.patch @@ -0,0 +1,141 @@ +Index: b/sshd.c +=================================================================== +--- b.orig/sshd.c ++++ b/sshd.c +@@ -1420,6 +1420,8 @@ server_accept_loop(struct ssh *ssh, int + options.log_level, + options.log_facility, + log_stderr); ++ ++ set_log_session_id(); // Set log session ID for this session + if (rexec_flag) + close(config_s[0]); + else { +Index: b/auth-pam.c +=================================================================== +--- b.orig/auth-pam.c ++++ b/auth-pam.c +@@ -766,7 +766,20 @@ sshpam_init(struct ssh *ssh, Authctxt *a + return (-1); + } + #endif +- return (0); ++ debug("PAM: setting PAM LOG_SESSION_ID to \"%s\"", get_log_session_id()); ++ { ++ char log_session_id_env[HOST_NAME_MAX + 50]; ++ snprintf(log_session_id_env, sizeof(log_session_id_env), ++ "LOG_SESSION_ID=%s", get_log_session_id()); ++ sshpam_err = pam_putenv(sshpam_handle, log_session_id_env); ++ if (sshpam_err != PAM_SUCCESS) { ++ pam_end(sshpam_handle, sshpam_err); ++ sshpam_handle = NULL; ++ return (-1); ++ } ++ } ++ ++ return (0); + } + + static void +Index: b/log.c +=================================================================== +--- b.orig/log.c ++++ b/log.c +@@ -410,17 +410,17 @@ 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\r\n", +- (int)sizeof msgbuf - 3, fmtbuf); ++ snprintf(msgbuf, sizeof msgbuf, "%.*s session=%s\r\n", ++ (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(argv0 ? argv0 : __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(argv0 ? argv0 : __progname, LOG_PID, log_facility); +- syslog(pri, "%.500s", fmtbuf); ++ syslog(pri, "%.500s session=%s", fmtbuf, get_log_session_id()); + closelog(); + #endif + } +@@ -452,6 +452,33 @@ sshlogdie(const char *file, const char * + } + + 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 + sshsigdie(const char *file, const char *func, int line, int showfunc, + LogLevel level, const char *suffix, const char *fmt, ...) + { +Index: b/regress/session-id.sh +=================================================================== +--- /dev/null ++++ b/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: b/log.h +=================================================================== +--- b.orig/log.h ++++ b/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))); diff --git a/fbpatches/fb87_pass_principals_to_child.patch b/fbpatches/fb87_pass_principals_to_child.patch new file mode 100644 index 0000000..27bb7ca --- /dev/null +++ b/fbpatches/fb87_pass_principals_to_child.patch @@ -0,0 +1,224 @@ +diff --git a/session.c b/session.c +--- 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 +@@ -990,11 +991,18 @@ + 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 +@@ -1188,9 +1196,57 @@ + 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); ++ } ++ } ++ } + + if (debug_flag) { + /* dump the environment */ +diff --git a/regress/cert-princ-env.sh b/regress/cert-princ-env.sh +new file mode 100644 +--- /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/fbpatches/fb87_slog.patch b/fbpatches/fb87_slog.patch new file mode 100644 index 0000000..427cdda --- /dev/null +++ b/fbpatches/fb87_slog.patch @@ -0,0 +1,1148 @@ +Index: b/slog.c +=================================================================== +--- /dev/null ++++ b/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: b/servconf.c +=================================================================== +--- b.orig/servconf.c ++++ b/servconf.c +@@ -204,6 +204,9 @@ initialize_server_options(ServerOptions + options->disable_forwarding = -1; + options->expose_userauth_info = -1; + options->rsa_min_size = -1; ++ options->log_format_prefix = NULL; ++ options->num_log_format_keys = 0; ++ options->log_format_json = -1; + } + + /* Returns 1 if a string option is unset or set to "none" or 0 otherwise. */ +@@ -473,6 +476,8 @@ fill_default_server_options(ServerOption + options->sk_provider = xstrdup("internal"); + if (options->rsa_min_size == -1) + options->rsa_min_size = SSH_RSA_MINIMUM_MODULUS_SIZE; ++ if (options->log_format_json == -1) ++ options->log_format_json = 0; + + assemble_algorithms(options); + +@@ -553,6 +558,10 @@ typedef enum { + sAllowStreamLocalForwarding, sFingerprintHash, sDisableForwarding, + sExposeAuthInfo, sRDomain, sPubkeyAuthOptions, sSecurityKeyProvider, + sRSAMinSize, ++ /* Structured Logging options. Unless sLogFormatKeys is set, ++ structured logging is disabled */ ++ sLogFormatPrefix, sLogFormatKeys, sLogFormatJson, ++ + sDeprecated, sIgnore, sUnsupported + } ServerOpCodes; + +@@ -730,6 +739,9 @@ static struct { + { "casignaturealgorithms", sCASignatureAlgorithms, SSHCFG_ALL }, + { "securitykeyprovider", sSecurityKeyProvider, SSHCFG_GLOBAL }, + { "rsaminsize", sRSAMinSize, SSHCFG_ALL }, ++ { "logformatprefix", sLogFormatPrefix, SSHCFG_GLOBAL }, ++ { "logformatkeys", sLogFormatKeys, SSHCFG_GLOBAL }, ++ { "logformatjson", sLogFormatJson, SSHCFG_GLOBAL }, + { NULL, sBadOption, 0 } + }; + +@@ -2367,6 +2379,30 @@ process_server_config_line_depth(ServerO + } + break; + ++ case sLogFormatPrefix: ++ arg = strdelim(&str); ++ 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 = strdelim(&str)) && *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') +@@ -3024,6 +3060,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); + + /* string arguments */ + dump_cfg_string(sPidFile, o->pid_file); +@@ -3054,6 +3091,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); + + /* string arguments requiring a lookup */ + dump_cfg_string(sLogLevel, log_level_name(o->log_level)); +@@ -3076,6 +3114,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); + + /* other arguments */ + for (i = 0; i < o->num_subsystems; i++) +Index: b/auth2-pubkey.c +=================================================================== +--- b.orig/auth2-pubkey.c ++++ b/auth2-pubkey.c +@@ -66,6 +66,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? */ +@@ -389,6 +390,7 @@ check_principals_line(struct ssh *ssh, c + debug3("%s: matched principal \"%.100s\"", + loc, cert->principals[i]); + found = 1; ++ slog_set_principal(cp); + } + if (found && authoptsp != NULL) { + *authoptsp = opts; +@@ -714,6 +716,7 @@ check_authkey_line(struct ssh *ssh, stru + (unsigned long long)key->cert->serial, + sshkey_type(found), fp, loc); + ++ slog_set_cert_serial(key->cert->serial); + success: + if (finalopts == NULL) + fatal_f("internal error: missing options"); +@@ -864,6 +867,7 @@ user_cert_trusted_ca(struct ssh *ssh, st + *authoptsp = final_opts; + final_opts = NULL; + } ++ slog_set_cert_id(key->cert->key_id); + ret = 1; + out: + sshauthopt_free(principals_opts); +Index: b/regress/test-exec.sh +=================================================================== +--- b.orig/regress/test-exec.sh ++++ b/regress/test-exec.sh +@@ -689,7 +689,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: b/session.c +=================================================================== +--- b.orig/session.c ++++ b/session.c +@@ -96,6 +96,8 @@ + #include "monitor_wrap.h" + #include "sftp.h" + #include "atomicio.h" ++#include "slog.h" ++ + + #if defined(KRB5) && defined(USE_AFS) + #include +@@ -753,6 +755,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) +@@ -1482,7 +1485,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. + */ +@@ -2159,6 +2162,7 @@ session_exec_req(struct ssh *ssh, Sessio + (r = sshpkt_get_end(ssh)) != 0) + sshpkt_fatal(ssh, r, "%s: parse packet", __func__); + ++ slog_set_command(command); + success = do_exec(ssh, s, command) == 0; + free(command); + return success; +@@ -2869,4 +2873,3 @@ session_get_remote_name_or_ip(struct ssh + remote = ssh_remote_ipaddr(ssh); + return remote; + } +- +Index: b/log.h +=================================================================== +--- b.orig/log.h ++++ b/log.h +@@ -133,4 +133,6 @@ void sshlogdirect(LogLevel, int, const + #define logdie_fr(r, ...) sshlogdie(__FILE__, __func__, __LINE__, 1, SYSLOG_LEVEL_ERROR, ssh_err(r), __VA_ARGS__) + #define sigdie_fr(r, ...) sshsigdie(__FILE__, __func__, __LINE__, 1, SYSLOG_LEVEL_ERROR, ssh_err(r), __VA_ARGS__) + ++void do_log_slog_payload(const char *); ++ + #endif +Index: b/log.c +=================================================================== +--- b.orig/log.c ++++ b/log.c +@@ -529,3 +529,39 @@ sshlogdirect(LogLevel level, int forced, + do_log(level, forced, NULL, fmt, args); + 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: b/slog.h +=================================================================== +--- /dev/null ++++ b/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: b/sshd.c +=================================================================== +--- b.orig/sshd.c ++++ b/sshd.c +@@ -132,6 +132,8 @@ + #include "sk-api.h" + #include "srclimit.h" + #include "dh.h" ++#include "slog.h" ++#include + + /* Re-exec fds */ + #define REEXEC_DEVCRYPTO_RESERVED_FD (STDERR_FILENO + 1) +@@ -2149,6 +2151,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"); +@@ -2315,8 +2318,15 @@ main(int ac, char **av) + rdomain == NULL ? "" : " rdomain \"", + rdomain == NULL ? "" : rdomain, + rdomain == NULL ? "" : "\""); +- free(laddr); + ++ ++ slog_set_connection(remote_ip, ++ remote_port, ++ laddr, ++ ssh_local_port(ssh), ++ get_log_session_id()); ++ ++ free(laddr); + /* + * We don't want to listen forever unless the other side + * successfully authenticates itself. So we set up an alarm which is +@@ -2638,6 +2648,7 @@ cleanup_exit(int i) + if (in_cleanup) + _exit(i); + in_cleanup = 1; ++ slog_exit_handler(); + if (the_active_state != NULL && the_authctxt != NULL) { + do_cleanup(the_active_state, the_authctxt); + if (use_privsep && privsep_is_preauth && +Index: b/sshd_config +=================================================================== +--- b.orig/sshd_config ++++ b/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: b/auth-pam.c +=================================================================== +--- b.orig/auth-pam.c ++++ b/auth-pam.c +@@ -94,6 +94,7 @@ extern char *__progname; + #include "auth-pam.h" + #include "canohost.h" + #include "log.h" ++#include "slog.h" + #include "msg.h" + #include "packet.h" + #include "misc.h" +@@ -1223,9 +1224,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: b/servconf.h +=================================================================== +--- b.orig/servconf.h ++++ b/servconf.h +@@ -22,6 +22,8 @@ + + #define MAX_SUBSYSTEMS 256 /* Max # subsystems. */ + ++#define MAX_LOGFORMAT_KEYS 256 /* Max # LogFormatKeys */ ++ + /* permit_root_login */ + #define PERMIT_NOT_SET -1 + #define PERMIT_NO 0 +@@ -239,6 +241,12 @@ typedef struct { + u_int64_t timing_secret; + char *sk_provider; + int rsa_min_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 */ ++ + } ServerOptions; + + /* Information about the incoming connection as used by Match */ +Index: b/regress/slog.sh +=================================================================== +--- /dev/null ++++ b/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: b/Makefile.in +=================================================================== +--- b.orig/Makefile.in ++++ b/Makefile.in +@@ -129,7 +129,7 @@ SSHDOBJS=sshd.o auth-rhosts.o auth-passw + monitor.o monitor_wrap.o auth-krb5.o \ + auth2-gss.o gss-serv.o gss-serv-krb5.o kexgsss.o \ + loginrec.o auth-pam.o auth-shadow.o auth-sia.o md5crypt.o \ +- srclimit.o sftp-server.o sftp-common.o \ ++ srclimit.o sftp-server.o sftp-common.o sftp-realpath.o slog.o \ + sandbox-null.o sandbox-rlimit.o sandbox-systrace.o sandbox-darwin.o \ + sandbox-seccomp-filter.o sandbox-capsicum.o sandbox-pledge.o \ + sandbox-solaris.o uidswap.o $(SKOBJS) +@@ -313,7 +313,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: b/auth.c +=================================================================== +--- b.orig/auth.c ++++ b/auth.c +@@ -76,6 +76,7 @@ + #include "ssherr.h" + #include "compat.h" + #include "channels.h" ++#include "slog.h" + + /* import */ + extern ServerOptions options; +@@ -351,6 +352,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)) { +@@ -564,6 +566,7 @@ auth_openprincipals(const char *file, st + struct passwd * + getpwnamallow(struct ssh *ssh, const char *user) + { ++ slog_set_user(user); + #ifdef HAVE_LOGIN_CAP + extern login_cap_t *lc; + #ifdef BSD_AUTH diff --git a/fbpatches/series b/fbpatches/series new file mode 100644 index 0000000..8fe0e6e --- /dev/null +++ b/fbpatches/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 diff --git a/openssh.spec b/openssh.spec index 8a66c22..2d1b915 100644 --- a/openssh.spec +++ b/openssh.spec @@ -1,3 +1,6 @@ +%global facebook 1 +%global facebook_dev 0 + # Do we want SELinux & Audit %if 0%{?!noselinux:1} %global WITH_SELINUX 1 @@ -53,13 +56,18 @@ %global openssh_ver 8.7p1 %global openssh_rel 19 %global hyperscale_rel 2 +%global facebook_rel fb1 %global pam_ssh_agent_ver 0.10.4 %global pam_ssh_agent_rel 5 Summary: An open source implementation of SSH protocol version 2 Name: openssh Version: %{openssh_ver} +%if %{facebook} +Release: %{openssh_rel}.%{facebook_rel}%{?dist} +%else Release: %{openssh_rel}.%{hyperscale_rel}%{?dist} +%endif URL: http://www.openssh.com/portable.html #URL1: https://github.com/jbeverly/pam_ssh_agent_auth/ Source0: ftp://ftp.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-%{version}.tar.gz @@ -257,6 +265,19 @@ Patch1006: openssh-8.7p1-negotiate-supported-algs.patch # c9s specific logic factored out of openssh-7.7p1-fips.patch Patch2000: openssh-7.7p1-fips-warning.patch +%if %{facebook} && !%{facebook_dev} +Patch2010: fbpatches/fb87_log_session_id.patch +Patch2011: fbpatches/fb87_slog.patch +Patch2012: fbpatches/fb87_log_port_forwards.patch +Patch2013: fbpatches/fb87_070_logging_reverse_port_forward.patch +Patch2014: fbpatches/fb87_810_increase_ssh_cert_max_principals.patch +Patch2015: fbpatches/fb87_090_logging_shell_cmd_pty.patch +Patch2016: fbpatches/fb87_080_logging_certificates.patch +Patch2017: fbpatches/fb87_log_accept_env.patch +Patch2018: fbpatches/fb87_pass_principals_to_child.patch +Patch2019: fbpatches/fb87_log_auth_info.patch +%endif + License: BSD Requires: /sbin/nologin @@ -462,6 +483,24 @@ popd %patch100 -p1 -b .coverity +%if %{facebook} && !%{facebook_dev} +%patch2010 -p1 -b log_session_id +%patch2011 -p1 -b slog +%patch2012 -p1 -b log_port_forwards +%patch2013 -p1 -b logging_reverse_port_forward +%patch2014 -p1 -b increase_ssh_cert_max_principals +%patch2015 -p1 -b logging_shell_cmd_pty +%patch2016 -p1 -b logging_certificates +%patch2017 -p1 -b log_accept_env +%patch2018 -p1 -b pass_principals_to_child +%patch2019 -p1 -b log_auth_info +%endif + +%if %{facebook} && %{facebook_dev} +ln -sf ../../fbpatches patches +quilt push -a +%endif + autoreconf pushd pam_ssh_agent_auth-pam_ssh_agent_auth-%{pam_ssh_agent_ver} autoreconf