mirror of
https://github.com/facebook/sapling.git
synced 2024-10-10 00:45:18 +03:00
mononoke: add an option to check service identity in hgcli
Reviewed By: krallin Differential Revision: D24786082 fbshipit-source-id: 7cd709d19490defb39fcebad08f95c00af54216c
This commit is contained in:
parent
0dce99888f
commit
7b3990c080
@ -7,6 +7,7 @@ license = "GPLv2+"
|
||||
include = ["src/**/*.rs"]
|
||||
|
||||
[dependencies]
|
||||
permission_checker = { path = "../permission_checker" }
|
||||
scuba_ext = { path = "../common/scuba_ext" }
|
||||
session_id = { path = "../server/session_id" }
|
||||
sshrelay = { path = "../sshrelay" }
|
||||
|
@ -8,7 +8,7 @@
|
||||
#![deny(warnings)]
|
||||
|
||||
use anyhow::Error;
|
||||
use clap::{App, Arg, SubCommand};
|
||||
use clap::{App, Arg, ArgGroup, SubCommand};
|
||||
use fbinit::FacebookInit;
|
||||
use std::time::Duration;
|
||||
|
||||
@ -60,9 +60,6 @@ fn main(fb: FacebookInit) {
|
||||
.arg(Arg::from_usage(
|
||||
"--private-key [KEY] 'path to the private key'",
|
||||
))
|
||||
.arg(Arg::from_usage(
|
||||
"--common-name [CN] 'expected SSL common name of the server see https://www.ssl.com/faqs/common-name/'",
|
||||
))
|
||||
.arg(Arg::from_usage("--insecure 'run hgcli without verifying peer certificate'"))
|
||||
.arg(Arg::from_usage("--stdio 'for remote clients'"))
|
||||
.arg(
|
||||
@ -74,7 +71,26 @@ fn main(fb: FacebookInit) {
|
||||
))
|
||||
.arg(Arg::from_usage(
|
||||
"--client-debug 'tell mononoke to send debug information to the client'",
|
||||
)),
|
||||
))
|
||||
.arg(
|
||||
Arg::with_name(serve::ARG_COMMON_NAME)
|
||||
.long(serve::ARG_COMMON_NAME)
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.help("expected SSL common name of the server see https://www.ssl.com/faqs/common-name/"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(serve::ARG_SERVER_CERT_IDENTITY)
|
||||
.long(serve::ARG_SERVER_CERT_IDENTITY)
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.help("expected identity of the server"),
|
||||
)
|
||||
.group(
|
||||
ArgGroup::with_name("idents")
|
||||
.args(&[serve::ARG_COMMON_NAME, serve::ARG_SERVER_CERT_IDENTITY])
|
||||
.required(true)
|
||||
),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
|
@ -31,11 +31,13 @@ use openssl::{
|
||||
ssl::{SslConnector, SslMethod, SslVerifyMode},
|
||||
x509::{X509StoreContextRef, X509VerifyResult},
|
||||
};
|
||||
use permission_checker::MononokeIdentity;
|
||||
use scuba_ext::{ScubaSampleBuilder, ScubaSampleBuilderExt};
|
||||
use secure_utils::{build_identity, read_x509};
|
||||
use session_id::generate_session_id;
|
||||
use slog::{debug, error, o, Drain, Logger};
|
||||
use sshrelay::{Preamble, Priority, SshDecoder, SshEncoder, SshEnvVars, SshMsg, SshStream};
|
||||
use std::str::FromStr;
|
||||
use tokio::io::{self, BufReader, Stderr, Stdin, Stdout};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::time::timeout;
|
||||
@ -46,6 +48,8 @@ use users::get_current_username;
|
||||
const X509_R_CERT_ALREADY_IN_HASH_TABLE: c_ulong = 185057381;
|
||||
const BUFSZ: usize = 8192;
|
||||
const NUMBUFS: usize = 50000; // This seems high
|
||||
pub const ARG_COMMON_NAME: &str = "common-name";
|
||||
pub const ARG_SERVER_CERT_IDENTITY: &str = "server-cert-identity";
|
||||
|
||||
// Wait for up to 1sec to let Scuba flush its data to the server.
|
||||
const SCUBA_TIMEOUT: Duration = Duration::from_millis(1000);
|
||||
@ -75,9 +79,7 @@ pub async fn cmd(
|
||||
let ca_pem = sub
|
||||
.value_of("ca-pem")
|
||||
.expect("Cental authority pem file is not specified");
|
||||
let common_name = sub
|
||||
.value_of("common-name")
|
||||
.expect("expected SSL common name of the Mononoke server");
|
||||
let expected_server_identity = ExpectedServerIdentity::new(sub)?;
|
||||
let insecure = sub.is_present("insecure");
|
||||
let is_remote_proxy = main.is_present("remote-proxy");
|
||||
let scuba_table = main.value_of("scuba-table");
|
||||
@ -102,7 +104,7 @@ pub async fn cmd(
|
||||
cert,
|
||||
private_key,
|
||||
ca_pem,
|
||||
ssl_common_name: common_name,
|
||||
expected_server_identity,
|
||||
insecure,
|
||||
is_remote_proxy,
|
||||
scuba_logger,
|
||||
@ -120,6 +122,43 @@ pub async fn cmd(
|
||||
return Err(format_err!("Only stdio server is supported"));
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum ExpectedServerIdentity {
|
||||
CommonName(String),
|
||||
Identity(MononokeIdentity),
|
||||
}
|
||||
|
||||
impl ExpectedServerIdentity {
|
||||
fn new(matches: &ArgMatches<'_>) -> Result<Self, Error> {
|
||||
let maybe_common_name = matches.value_of(ARG_COMMON_NAME);
|
||||
let maybe_server_cert_identity = matches.value_of(ARG_SERVER_CERT_IDENTITY);
|
||||
|
||||
match (maybe_common_name, maybe_server_cert_identity) {
|
||||
(Some(common_name), None) => {
|
||||
Ok(ExpectedServerIdentity::CommonName(common_name.to_string()))
|
||||
}
|
||||
(None, Some(server_cert_identity)) => Ok(ExpectedServerIdentity::Identity(
|
||||
MononokeIdentity::from_str(server_cert_identity)?,
|
||||
)),
|
||||
(Some(_), Some(_)) => Err(format_err!(
|
||||
"both common-name and server-cert-identity are specified"
|
||||
)),
|
||||
(None, None) => Err(format_err!(
|
||||
"neither common-name nor server-cert-identity are specified"
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn maybe_get_common_name(&self) -> Option<&str> {
|
||||
use ExpectedServerIdentity::*;
|
||||
|
||||
match self {
|
||||
CommonName(common_name) => Some(&common_name),
|
||||
Identity(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct StdioRelay<'a> {
|
||||
path: &'a str,
|
||||
repo: &'a str,
|
||||
@ -127,7 +166,7 @@ struct StdioRelay<'a> {
|
||||
cert: &'a str,
|
||||
private_key: &'a str,
|
||||
ca_pem: &'a str,
|
||||
ssl_common_name: &'a str,
|
||||
expected_server_identity: ExpectedServerIdentity,
|
||||
insecure: bool,
|
||||
is_remote_proxy: bool,
|
||||
scuba_logger: ScubaSampleBuilder,
|
||||
@ -224,7 +263,7 @@ impl<'a> StdioRelay<'a> {
|
||||
|
||||
async fn establish_connection(&self) -> Result<SslStream<TcpStream>, Error> {
|
||||
let path = self.path.to_owned();
|
||||
let ssl_common_name = self.ssl_common_name.to_owned();
|
||||
let expected_server_identity = self.expected_server_identity.clone();
|
||||
let client_logger = self.client_logger.clone();
|
||||
let scuba_logger = self.scuba_logger.clone();
|
||||
|
||||
@ -245,8 +284,27 @@ impl<'a> StdioRelay<'a> {
|
||||
return preverify_ok;
|
||||
}
|
||||
|
||||
let verification_result =
|
||||
verify_common_name(&ssl_common_name, x509_ctx_ref);
|
||||
|
||||
let verification_result = match expected_server_identity {
|
||||
ExpectedServerIdentity::CommonName(ref common_name) => {
|
||||
verify_common_name(&common_name, x509_ctx_ref)
|
||||
}
|
||||
ExpectedServerIdentity::Identity(ref identity) => {
|
||||
#[cfg(fbcode_build)]
|
||||
{
|
||||
verify_service_identity::verify_service_identity(
|
||||
identity,
|
||||
x509_ctx_ref,
|
||||
)
|
||||
}
|
||||
#[cfg(not(fbcode_build))]
|
||||
{
|
||||
let _ = identity;
|
||||
Err("verifying service identity is not supported".to_string())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
match verification_result {
|
||||
Ok(()) => true,
|
||||
Err(err_msg) => {
|
||||
@ -304,7 +362,14 @@ impl<'a> StdioRelay<'a> {
|
||||
// Don't verify the hostname since we have a callback that verifies the
|
||||
// common name
|
||||
configured_connector.set_verify_hostname(false);
|
||||
tokio_openssl::connect(configured_connector, &self.ssl_common_name, sock)
|
||||
let common_name = match self.expected_server_identity.maybe_get_common_name() {
|
||||
Some(common_name) => common_name,
|
||||
None => {
|
||||
configured_connector.set_use_server_name_indication(false);
|
||||
""
|
||||
}
|
||||
};
|
||||
tokio_openssl::connect(configured_connector, &common_name, sock)
|
||||
.await
|
||||
.with_context(|| format!("tls failed: talking to '{}'", path))
|
||||
}
|
||||
@ -427,3 +492,37 @@ fn verify_common_name(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(fbcode_build)]
|
||||
mod verify_service_identity {
|
||||
use super::*;
|
||||
use identity::Identity;
|
||||
use identity_ext::x509::get_identities;
|
||||
|
||||
pub fn verify_service_identity(
|
||||
expected_identity: &MononokeIdentity,
|
||||
x509_ctx_ref: &mut X509StoreContextRef,
|
||||
) -> Result<(), String> {
|
||||
let cert = match x509_ctx_ref.current_cert() {
|
||||
Some(cert) => cert,
|
||||
None => {
|
||||
let err_msg = "certificate to verify not found";
|
||||
return Err(err_msg.to_string());
|
||||
}
|
||||
};
|
||||
|
||||
let identities = get_identities(cert)
|
||||
.map_err(|err| format!("failed get identities from certificate: {:#}", err))?;
|
||||
|
||||
let expected_identity = Identity::from(expected_identity);
|
||||
if identities.contains(&expected_identity) {
|
||||
Ok(())
|
||||
} else {
|
||||
let err_msg = format!(
|
||||
"couldn't find identity {} in certificate that has identities {:?}",
|
||||
expected_identity, identities
|
||||
);
|
||||
Err(err_msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user