@@ -17,13 +17,14 @@ use holo_utils::auth::Users;
1717use holo_utils:: task:: Task ;
1818use holo_yang:: { YANG_CTX , YANG_FEATURES } ;
1919use nix:: sys:: stat:: { Mode , fchmod} ;
20+ use nix:: unistd:: { Uid , User } ;
2021use tokio:: net:: UnixListener ;
2122use tokio:: sync:: mpsc:: Sender ;
2223use tokio:: sync:: { mpsc, oneshot, watch} ;
2324use tokio_stream:: wrappers:: { UnboundedReceiverStream , UnixListenerStream } ;
2425use tonic:: metadata:: MetadataMap ;
2526use tonic:: service:: interceptor:: InterceptedService ;
26- use tonic:: transport:: server:: Router ;
27+ use tonic:: transport:: server:: { Router , UdsConnectInfo } ;
2728use tonic:: transport:: { Identity , Server , ServerTlsConfig } ;
2829use tonic:: { Request , Response , Status } ;
2930use tracing:: { trace, trace_span} ;
@@ -53,6 +54,10 @@ pub(crate) struct Authenticator {
5354 unix : bool ,
5455}
5556
57+ // User name a request was authenticated with.
58+ #[ derive( Clone , Debug ) ]
59+ struct AuthenticatedUser ( String ) ;
60+
5661// Where a server accepts connections.
5762#[ derive( Debug ) ]
5863pub ( crate ) enum Listener {
@@ -298,6 +303,7 @@ impl proto::Northbound for NorthboundService {
298303 & self ,
299304 grpc_request : Request < proto:: CommitRequest > ,
300305 ) -> Result < Response < proto:: CommitResponse > , Status > {
306+ let author = request_author ( & grpc_request) ;
301307 let grpc_request = grpc_request. into_inner ( ) ;
302308 trace_span ! ( "northbound" ) . in_scope ( || {
303309 trace_span ! ( "client" , name = "grpc" ) . in_scope ( || {
@@ -335,6 +341,7 @@ impl proto::Northbound for NorthboundService {
335341 let nb_request =
336342 api:: client:: Request :: Commit ( api:: client:: CommitRequest {
337343 config,
344+ author,
338345 comment : grpc_request. comment ,
339346 confirmed_timeout : grpc_request. confirmed_timeout ,
340347 responder : responder_tx,
@@ -426,8 +433,9 @@ impl proto::Northbound for NorthboundService {
426433 nb_response. transactions . into_iter ( ) . map ( |transaction| {
427434 Ok ( proto:: ListTransactionsResponse {
428435 id : transaction. id ,
429- comment : transaction. comment ,
430436 date : transaction. date . to_string ( ) ,
437+ author : transaction. author ,
438+ comment : transaction. comment ,
431439 } )
432440 } ) ;
433441
@@ -534,19 +542,24 @@ impl Authenticator {
534542 // user.
535543 pub ( crate ) fn intercept (
536544 & self ,
537- request : Request < ( ) > ,
545+ mut request : Request < ( ) > ,
538546 ) -> Result < Request < ( ) > , Status > {
539- self . authenticate ( request. metadata ( ) ) ?;
547+ if let Some ( user) = self . authenticate ( request. metadata ( ) ) ? {
548+ request. extensions_mut ( ) . insert ( AuthenticatedUser ( user) ) ;
549+ }
540550
541551 Ok ( request)
542552 }
543553
544- fn authenticate ( & self , metadata : & MetadataMap ) -> Result < ( ) , Status > {
554+ fn authenticate (
555+ & self ,
556+ metadata : & MetadataMap ,
557+ ) -> Result < Option < String > , Status > {
545558 // The socket's file permissions already decide who may connect, and
546559 // the peer's identity comes from the kernel, so no password is asked
547560 // for.
548561 if self . unix {
549- return Ok ( ( ) ) ;
562+ return Ok ( None ) ;
550563 }
551564
552565 let Some ( ( username, password) ) = credentials ( metadata) else {
@@ -563,7 +576,7 @@ impl Authenticator {
563576 return Err ( Status :: unauthenticated ( "invalid credentials" ) ) ;
564577 }
565578
566- Ok ( ( ) )
579+ Ok ( Some ( username . to_owned ( ) ) )
567580 }
568581}
569582
@@ -812,6 +825,40 @@ fn unix_listener(path: &FsPath) -> std::io::Result<UnixListenerStream> {
812825
813826// ===== global functions =====
814827
828+ // Identifies who issued a request.
829+ //
830+ // A Unix socket carries the peer's credentials, so its user comes from the
831+ // kernel and is prefixed with "unix:". A remote request is attributed to the
832+ // user it authenticated as, qualified by the address it came from.
833+ pub ( crate ) fn request_author < T > ( request : & Request < T > ) -> String {
834+ const UNKNOWN : & str = "unknown" ;
835+
836+ if let Some ( info) = request. extensions ( ) . get :: < UdsConnectInfo > ( ) {
837+ let user = match info. peer_cred {
838+ Some ( cred) => {
839+ let uid = Uid :: from_raw ( cred. uid ( ) ) ;
840+ User :: from_uid ( uid)
841+ . ok ( )
842+ . flatten ( )
843+ . map ( |user| user. name )
844+ . unwrap_or_else ( || format ! ( "uid:{uid}" ) )
845+ }
846+ None => UNKNOWN . to_owned ( ) ,
847+ } ;
848+ return format ! ( "unix:{user}" ) ;
849+ }
850+
851+ let user = request
852+ . extensions ( )
853+ . get :: < AuthenticatedUser > ( )
854+ . map ( |user| user. 0 . as_str ( ) )
855+ . unwrap_or ( UNKNOWN ) ;
856+ match request. remote_addr ( ) {
857+ Some ( address) => format ! ( "{user}@{}" , address. ip( ) ) ,
858+ None => user. to_owned ( ) ,
859+ }
860+ }
861+
815862// Sets up the listener and the server for the given address.
816863//
817864// An address starting with a slash is taken as the path of a Unix socket,
0 commit comments