2013-04-11 08:48:01 +04:00
package PVE::HTTPServer ;
use strict ;
use warnings ;
2013-04-12 14:51:28 +04:00
2017-01-16 12:59:31 +03:00
use PVE::SafeSyslog ;
use PVE::INotify ;
use PVE::Tools ;
use PVE::APIServer::AnyEvent ;
2020-01-21 15:54:22 +03:00
use PVE::Exception qw( raise_param_exc raise_perm_exc raise ) ;
2013-04-11 09:47:33 +04:00
2017-01-16 12:59:31 +03:00
use PVE::RPCEnvironment ;
use PVE::AccessControl ;
2019-11-11 13:28:33 +03:00
use PVE::CertCache ;
2017-01-16 12:59:31 +03:00
use PVE::Cluster ;
2017-05-29 08:49:17 +03:00
use PVE::API2Tools ;
2013-04-11 09:47:33 +04:00
2017-01-16 12:59:31 +03:00
use Data::Dumper ;
2013-04-11 08:48:01 +04:00
2017-01-16 12:59:31 +03:00
use base ( 'PVE::APIServer::AnyEvent' ) ;
2013-04-11 10:31:42 +04:00
2017-01-16 12:59:31 +03:00
use HTTP::Status qw( :constants ) ;
2013-04-11 10:31:42 +04:00
2013-04-11 08:48:01 +04:00
sub new {
my ( $ this , % args ) = @ _ ;
my $ class = ref ( $ this ) || $ this ;
2017-01-16 12:59:31 +03:00
my $ self = $ class - > SUPER:: new ( % args ) ;
2021-04-22 10:10:19 +03:00
2013-04-11 10:17:34 +04:00
$ self - > { rpcenv } = PVE::RPCEnvironment - > init (
2021-04-22 10:10:19 +03:00
$ self - > { trusted_env } ? 'priv' : 'pub' ,
atfork = > sub { $ self - > atfork_handler ( ) } ,
) ;
2013-04-11 10:17:34 +04:00
2017-01-16 12:59:31 +03:00
return $ self ;
}
2013-04-16 11:09:41 +04:00
2017-01-16 12:59:31 +03:00
sub verify_spice_connect_url {
my ( $ self , $ connect_str ) = @ _ ;
2013-04-11 08:48:01 +04:00
2017-01-16 12:59:35 +03:00
my $ rpcenv = $ self - > { rpcenv } ;
$ rpcenv - > init_request ( ) ;
2017-01-16 12:59:31 +03:00
my ( $ vmid , $ node , $ port ) = PVE::AccessControl:: verify_spice_connect_url ( $ connect_str ) ;
2013-04-11 08:48:01 +04:00
2017-01-16 12:59:31 +03:00
return ( $ vmid , $ node , $ port ) ;
}
2013-04-11 08:48:01 +04:00
2017-01-16 12:59:31 +03:00
sub generate_csrf_prevention_token {
my ( $ username ) = @ _ ;
return PVE::AccessControl:: assemble_csrf_prevention_token ( $ username ) ;
2013-04-11 08:48:01 +04:00
}
2017-01-10 19:05:59 +03:00
sub auth_handler {
2020-01-21 15:54:21 +03:00
my ( $ self , $ method , $ rel_uri , $ ticket , $ token , $ api_token , $ peer_host ) = @ _ ;
2017-01-10 19:05:59 +03:00
my $ rpcenv = $ self - > { rpcenv } ;
2017-01-16 12:59:35 +03:00
# set environment variables
$ rpcenv - > set_user ( undef ) ;
$ rpcenv - > set_language ( 'C' ) ;
$ rpcenv - > set_client_ip ( $ peer_host ) ;
2017-12-07 16:00:36 +03:00
eval { $ rpcenv - > init_request ( ) } ;
raise ( "RPCEnvironment init request failed: $@\n" ) if $@ ;
2017-01-16 12:59:36 +03:00
2017-01-10 19:05:59 +03:00
my $ require_auth = 1 ;
# explicitly allow some calls without auth
if ( ( $ rel_uri eq '/access/domains' && $ method eq 'GET' ) ||
2021-06-24 11:17:59 +03:00
( $ rel_uri eq '/access/ticket' && ( $ method eq 'GET' || $ method eq 'POST' ) ) ||
( $ rel_uri eq '/access/openid/login' && $ method eq 'POST' ) ||
( $ rel_uri eq '/access/openid/auth-url' && $ method eq 'POST' ) ) {
2017-01-10 19:05:59 +03:00
$ require_auth = 0 ;
}
my ( $ username , $ age ) ;
my $ isUpload = 0 ;
if ( $ require_auth ) {
2020-01-21 15:54:21 +03:00
if ( $ api_token ) {
2021-04-22 10:10:19 +03:00
# the token-ID `<user>@<realm>!<tokenname>` is the user for token based authentication
2020-01-21 15:54:21 +03:00
$ username = PVE::AccessControl:: verify_token ( $ api_token ) ;
} else {
die "No ticket\n" if ! $ ticket ;
( $ username , $ age , my $ tfa_info ) = PVE::AccessControl:: verify_ticket ( $ ticket ) ;
2020-03-12 17:09:21 +03:00
$ rpcenv - > check_user_enabled ( $ username ) ;
2020-01-21 15:54:21 +03:00
if ( defined ( $ tfa_info ) ) {
if ( defined ( my $ challenge = $ tfa_info - > { challenge } ) ) {
$ rpcenv - > set_u2f_challenge ( $ challenge ) ;
}
2021-04-22 10:10:19 +03:00
die "No ticket\n" if ( $ rel_uri ne '/access/tfa' || $ method ne 'POST' ) ;
2020-01-21 15:54:21 +03:00
}
2021-04-21 14:59:43 +03:00
}
2017-01-10 19:05:59 +03:00
2021-04-21 14:59:43 +03:00
$ rpcenv - > set_user ( $ username ) ;
2017-01-10 19:05:59 +03:00
2021-04-21 14:59:43 +03:00
if ( $ method eq 'POST' && $ rel_uri =~ m | ^ /nodes/ ( [ ^ /]+)/s torage /([^/ ] + ) / upload $| ) {
my ( $ node , $ storeid ) = ( $ 1 , $ 2 ) ;
2021-04-22 10:10:19 +03:00
# CSRF check are omitted if $isUpload is set, so check user upload permission here
2021-04-21 14:59:43 +03:00
my $ perm = { check = > [ 'perm' , "/storage/$storeid" , [ 'Datastore.AllocateTemplate' ] ] } ;
$ rpcenv - > check_api2_permissions ( $ perm , $ username , { } ) ;
$ isUpload = 1 ;
2017-01-10 19:05:59 +03:00
}
2021-04-21 14:59:43 +03:00
2021-04-22 12:10:48 +03:00
# Skip CSRF check for file upload (difficult to pass CSRF header with native html forms).
# Also skip the check with API tokens, as one of the design goals of API tokens was to
# provide stateless API access without requiring round-trips to get such CSRF tokens.
# CSRF-prevention also does not make much sense outside of the browser context.
if ( $ method ne 'GET' && ! ( $ api_token || $ isUpload ) ) {
2021-04-22 11:46:34 +03:00
my $ euid = $> ;
2021-04-22 12:10:48 +03:00
PVE::AccessControl:: verify_csrf_prevention_token ( $ username , $ token ) if $ euid != 0 ;
2021-04-22 11:46:34 +03:00
}
2017-01-10 19:05:59 +03:00
}
return {
ticket = > $ ticket ,
token = > $ token ,
userid = > $ username ,
age = > $ age ,
isUpload = > $ isUpload ,
2020-01-21 15:54:21 +03:00
api_token = > $ api_token ,
2017-01-10 19:05:59 +03:00
} ;
}
2017-01-10 19:06:01 +03:00
sub rest_handler {
my ( $ self , $ clientip , $ method , $ rel_uri , $ auth , $ params ) = @ _ ;
my $ rpcenv = $ self - > { rpcenv } ;
2017-01-16 12:59:38 +03:00
my $ resp = {
status = > HTTP_NOT_IMPLEMENTED ,
message = > "Method '$method $rel_uri' not implemented" ,
} ;
my ( $ handler , $ info ) ;
eval {
my $ uri_param = { } ;
( $ handler , $ info ) = PVE::API2 - > find_handler ( $ method , $ rel_uri , $ uri_param ) ;
return if ! $ handler || ! $ info ;
2017-01-10 19:06:01 +03:00
2021-04-22 10:10:19 +03:00
for my $ p ( sort keys % { $ params } ) {
2020-11-21 22:33:47 +03:00
if ( defined ( $ uri_param - > { $ p } ) && $ uri_param - > { $ p } ne $ params - > { $ p } ) {
raise_param_exc ( {
$ p = > "duplicate parameter (already defined in URI) with conflicting values!"
} ) ;
2017-01-16 12:59:38 +03:00
}
$ uri_param - > { $ p } = $ params - > { $ p } ;
2017-01-10 19:06:01 +03:00
}
2020-01-21 15:54:22 +03:00
raise_perm_exc ( "URI '$rel_uri' not available with API token, need proper ticket.\n" )
if $ auth - > { api_token } && ! $ info - > { allowtoken } ;
2017-01-16 12:59:38 +03:00
# check access permissions
$ rpcenv - > check_api2_permissions ( $ info - > { permissions } , $ auth - > { userid } , $ uri_param ) ;
2017-01-10 19:06:01 +03:00
2017-05-29 08:49:17 +03:00
if ( $ info - > { proxyto } || $ info - > { proxyto_callback } ) {
my $ node = PVE::API2Tools:: resolve_proxyto (
$ rpcenv , $ info - > { proxyto_callback } , $ info - > { proxyto } , $ uri_param ) ;
2017-01-10 19:06:01 +03:00
if ( $ node ne 'localhost' && $ node ne PVE::INotify:: nodename ( ) ) {
die "unable to proxy file uploads" if $ auth - > { isUpload } ;
2017-01-16 12:59:38 +03:00
my $ remip = $ self - > remote_node_ip ( $ node ) ;
$ resp = { proxy = > $ remip , proxynode = > $ node , proxy_params = > $ params } ;
return ;
2017-01-10 19:06:01 +03:00
}
}
2017-01-16 12:59:38 +03:00
my $ euid = $> ;
if ( $ info - > { protected } && ( $ euid != 0 ) ) {
$ resp = { proxy = > 'localhost' , proxy_params = > $ params } ;
return ;
}
2017-01-10 19:06:01 +03:00
2017-01-16 12:59:38 +03:00
$ resp = {
data = > $ handler - > handle ( $ info , $ uri_param ) ,
info = > $ info , # useful to format output
status = > HTTP_OK ,
} ;
2017-01-10 19:06:01 +03:00
if ( my $ count = $ rpcenv - > get_result_attrib ( 'total' ) ) {
$ resp - > { total } = $ count ;
}
2017-01-16 12:59:38 +03:00
2017-01-10 19:06:01 +03:00
if ( my $ diff = $ rpcenv - > get_result_attrib ( 'changes' ) ) {
$ resp - > { changes } = $ diff ;
}
} ;
2017-01-16 12:59:38 +03:00
my $ err = $@ ;
2017-01-10 19:06:01 +03:00
2017-01-16 12:59:35 +03:00
$ rpcenv - > set_user ( undef ) ; # clear after request
2017-01-16 12:59:38 +03:00
if ( $ err ) {
$ resp = { info = > $ info } ;
if ( ref ( $ err ) eq "PVE::Exception" ) {
$ resp - > { status } = $ err - > { code } || HTTP_INTERNAL_SERVER_ERROR ;
$ resp - > { errors } = $ err - > { errors } if $ err - > { errors } ;
$ resp - > { message } = $ err - > { msg } ;
} else {
$ resp - > { status } = HTTP_INTERNAL_SERVER_ERROR ;
$ resp - > { message } = $ err ;
}
}
2017-01-10 19:06:01 +03:00
return $ resp ;
}
2017-01-10 19:05:59 +03:00
2017-01-12 14:14:53 +03:00
sub check_cert_fingerprint {
my ( $ self , $ cert ) = @ _ ;
2019-11-11 13:28:33 +03:00
return PVE::CertCache:: check_cert_fingerprint ( $ cert ) ;
2017-01-12 14:14:53 +03:00
}
sub initialize_cert_cache {
my ( $ self , $ node ) = @ _ ;
2019-11-11 13:28:33 +03:00
PVE::CertCache:: initialize_cert_cache ( $ node ) ;
2017-01-12 14:14:53 +03:00
}
sub remote_node_ip {
my ( $ self , $ node ) = @ _ ;
my $ remip = PVE::Cluster:: remote_node_ip ( $ node ) ;
die "unable to get remote IP address for node '$node'\n" if ! $ remip ;
return $ remip ;
}
2013-04-11 08:48:01 +04:00
1 ;