2012-03-22 12:38:15 +01:00
package PVE::API2::Subscription ;
use strict ;
use warnings ;
2024-09-26 12:11:52 +02:00
2012-03-22 12:38:15 +01:00
use Digest::MD5 qw( md5_hex md5_base64 ) ;
use HTTP::Request ;
2017-10-03 12:54:29 +02:00
use JSON ;
2024-09-26 12:11:52 +02:00
use LWP::UserAgent ;
use MIME::Base64 ;
2012-03-22 12:38:15 +01:00
2022-06-30 14:06:16 +02:00
use Proxmox::RS::Subscription ;
2024-09-26 12:11:52 +02:00
use PVE::AccessControl ;
2012-03-22 12:38:15 +01:00
use PVE::Cluster qw (cfs_read_file cfs_write_file ) ;
2019-11-11 11:28:34 +01:00
use PVE::DataCenterConfig ;
2024-09-26 12:11:52 +02:00
use PVE::Exception qw( raise_param_exc ) ;
use PVE::INotify ;
2012-03-22 12:38:15 +01:00
use PVE::JSONSchema qw( get_standard_option ) ;
2024-09-26 12:11:52 +02:00
use PVE::ProcFSTools ;
2012-03-22 12:38:15 +01:00
use PVE::SafeSyslog ;
2024-09-26 12:11:52 +02:00
use PVE::Storage ;
use PVE::Tools ;
2012-03-22 12:38:15 +01:00
2024-10-01 15:53:12 +02:00
use PVE::Ceph::Releases ;
2012-03-22 12:38:15 +01:00
use PVE::API2Tools ;
use base qw( PVE::RESTHandler ) ;
2019-01-10 14:47:13 +01:00
my $ subscription_pattern = 'pve([1248])([cbsp])-[0-9a-f]{10}' ;
2022-06-30 14:06:16 +02:00
my $ filename = "/etc/subscription" ;
2012-03-22 12:38:15 +01:00
sub get_sockets {
2012-03-26 08:00:57 +02:00
my $ info = PVE::ProcFSTools:: read_cpuinfo ( ) ;
return $ info - > { sockets } ;
2012-03-22 12:38:15 +01:00
}
sub parse_key {
2017-10-03 12:54:29 +02:00
my ( $ key , $ noerr ) = @ _ ;
2012-03-22 12:38:15 +01:00
2017-10-03 12:54:29 +02:00
if ( $ key =~ m/^${subscription_pattern}$/ ) {
2012-03-26 10:26:26 +02:00
return wantarray ? ( $ 1 , $ 2 ) : $ 1 ; # number of sockets, level
2012-03-22 12:38:15 +01:00
}
2017-10-03 12:54:29 +02:00
return undef if $ noerr ;
die "Wrong subscription key format\n" ;
2012-03-22 12:38:15 +01:00
}
2017-10-03 12:54:29 +02:00
sub check_key {
my ( $ key , $ req_sockets ) = @ _ ;
2012-03-22 12:38:15 +01:00
2017-10-03 12:54:29 +02:00
my ( $ sockets , $ level ) = parse_key ( $ key ) ;
2012-03-22 12:38:15 +01:00
if ( $ sockets < $ req_sockets ) {
die "wrong number of sockets ($sockets < $req_sockets)\n" ;
}
2017-10-03 12:54:29 +02:00
return ( $ sockets , $ level ) ;
2012-03-22 12:38:15 +01:00
}
2022-06-30 14:06:16 +02:00
sub read_etc_subscription {
2017-10-03 12:54:29 +02:00
my $ req_sockets = get_sockets ( ) ;
my $ server_id = PVE::API2Tools:: get_hwaddress ( ) ;
2012-03-22 12:38:15 +01:00
2022-06-30 14:06:16 +02:00
my $ info = Proxmox::RS::Subscription:: read_subscription ( $ filename ) ;
2012-03-22 12:38:15 +01:00
2022-09-07 10:47:05 +02:00
return $ info if ! $ info || $ info - > { status } ne 'active' ;
2012-03-22 12:38:15 +01:00
2017-10-03 12:54:29 +02:00
my ( $ sockets , $ level ) ;
eval { ( $ sockets , $ level ) = check_key ( $ info - > { key } , $ req_sockets ) ; } ;
if ( my $ err = $@ ) {
chomp $ err ;
2022-06-30 14:06:16 +02:00
$ info - > { status } = 'invalid' ;
2017-10-03 12:54:29 +02:00
$ info - > { message } = $ err ;
} else {
2012-03-26 10:26:26 +02:00
$ info - > { level } = $ level ;
}
2012-03-22 12:38:15 +01:00
return $ info ;
}
2023-06-05 13:25:42 +02:00
my sub cache_is_valid {
my ( $ info ) = @ _ ;
return if ! $ info || $ info - > { status } ne 'active' ;
my $ checked_info = Proxmox::RS::Subscription:: check_age ( $ info , 1 ) ;
return $ checked_info - > { status } eq 'active'
}
2022-06-30 14:06:16 +02:00
sub write_etc_subscription {
my ( $ info ) = @ _ ;
2012-03-22 12:38:15 +01:00
2013-08-01 11:35:48 +02:00
my $ server_id = PVE::API2Tools:: get_hwaddress ( ) ;
2022-06-30 14:06:16 +02:00
mkdir "/etc/apt/auth.conf.d" ;
2023-06-05 13:25:17 +02:00
Proxmox::RS::Subscription:: write_subscription (
$ filename , "/etc/apt/auth.conf.d/pve.conf" , "enterprise.proxmox.com/debian/pve" , $ info ) ;
2023-06-05 13:07:48 +02:00
2023-10-25 15:34:35 +02:00
if ( ! ( defined ( $ info - > { key } ) && defined ( $ info - > { serverid } ) ) ) {
2024-10-01 15:51:06 +02:00
unlink "/etc/apt/auth.conf.d/ceph.conf" or $! { ENOENT } or die "failed to remove apt auth ceph.conf - $!" ;
2023-10-25 15:34:35 +02:00
} else {
2024-10-01 15:53:12 +02:00
my $ supported_ceph_releases = PVE::Ceph::Releases:: get_available_ceph_release_codenames ( 1 ) ;
2023-10-25 15:34:35 +02:00
my $ ceph_auth = '' ;
2024-10-01 15:53:12 +02:00
for my $ ceph_release ( $ supported_ceph_releases - > @ * ) {
2023-10-25 15:34:35 +02:00
$ ceph_auth . = "machine enterprise.proxmox.com/debian/ceph-${ceph_release}"
2023-06-05 13:07:48 +02:00
. " login $info->{key} password $info->{serverid}\n"
2023-10-25 15:34:35 +02:00
}
PVE::Tools:: file_set_contents ( "/etc/apt/auth.conf.d/ceph.conf" , $ ceph_auth ) ;
2023-06-05 13:07:48 +02:00
}
2012-03-22 12:38:15 +01:00
}
__PACKAGE__ - > register_method ( {
2017-10-03 12:54:29 +02:00
name = > 'get' ,
path = > '' ,
2012-03-22 12:38:15 +01:00
method = > 'GET' ,
description = > "Read subscription info." ,
proxyto = > 'node' ,
2012-04-12 11:16:04 +02:00
permissions = > { user = > 'all' } ,
2012-03-22 12:38:15 +01:00
parameters = > {
additionalProperties = > 0 ,
properties = > {
node = > get_standard_option ( 'pve-node' ) ,
} ,
} ,
2024-10-21 11:15:41 +02:00
returns = > {
type = > 'object' ,
additionalProperties = > 0 ,
properties = > {
status = > {
type = > 'string' ,
enum = > [ qw( new notfound active invalid expired suspended ) ] ,
description = > "The current subscription status." ,
} ,
checktime = > {
type = > 'integer' ,
description = > 'Timestamp of the last check done.' ,
optional = > 1 ,
} ,
key = > {
type = > 'string' ,
description = > 'The subscription key, if set and permitted to access.' ,
optional = > 1 ,
} ,
level = > {
type = > 'string' ,
description = > 'A short code for the subscription level.' ,
optional = > 1 ,
} ,
message = > {
type = > 'string' ,
description = > 'A more human readable status message.' ,
optional = > 1 ,
} ,
nextduedate = > {
type = > 'string' ,
description = > 'Next due date of the set subscription.' ,
optional = > 1 ,
} ,
productname = > {
type = > 'string' ,
description = > 'Human readable productname of the set subscription.' ,
optional = > 1 ,
} ,
regdate = > {
type = > 'string' ,
description = > 'Register date of the set subscription.' ,
optional = > 1 ,
} ,
serverid = > {
type = > 'string' ,
description = > 'The server ID, if permitted to access.' ,
optional = > 1 ,
} ,
signature = > {
type = > 'string' ,
description = > 'Signature for offline keys' ,
optional = > 1 ,
} ,
sockets = > {
type = > 'integer' ,
description = > 'The number of sockets for this host.' ,
optional = > 1 ,
} ,
url = > {
type = > 'string' ,
description = > 'URL to the web shop.' ,
optional = > 1 ,
} ,
} ,
} ,
2012-03-22 12:38:15 +01:00
code = > sub {
my ( $ param ) = @ _ ;
2019-01-31 10:14:24 +01:00
my $ node = $ param - > { node } ;
2012-04-05 08:04:06 +02:00
2018-10-30 11:36:14 +01:00
my $ rpcenv = PVE::RPCEnvironment:: get ( ) ;
my $ authuser = $ rpcenv - > get_user ( ) ;
2019-10-17 15:14:14 +02:00
my $ has_permission = $ rpcenv - > check ( $ authuser , "/nodes/$node" , [ 'Sys.Audit' ] , 1 ) ;
2018-10-30 11:36:14 +01:00
2019-01-31 10:54:50 +01:00
my $ server_id = PVE::API2Tools:: get_hwaddress ( ) ;
2023-08-11 12:46:54 +02:00
my $ url = "https://www.proxmox.com/en/proxmox-virtual-environment/pricing" ;
2019-01-31 10:54:50 +01:00
2022-06-30 14:06:16 +02:00
my $ info = read_etc_subscription ( ) ;
2012-03-22 12:38:15 +01:00
if ( ! $ info ) {
2018-10-30 11:36:14 +01:00
my $ no_subscription_info = {
2022-06-30 14:06:16 +02:00
status = > "notfound" ,
2012-03-22 12:38:15 +01:00
message = > "There is no subscription key" ,
2018-10-30 11:36:14 +01:00
url = > $ url ,
} ;
$ no_subscription_info - > { serverid } = $ server_id if $ has_permission ;
return $ no_subscription_info ;
}
if ( ! $ has_permission ) {
return {
status = > $ info - > { status } ,
message = > $ info - > { message } ,
2017-10-03 12:54:29 +02:00
url = > $ url ,
2012-03-22 12:38:15 +01:00
}
}
2012-04-05 08:04:06 +02:00
$ info - > { serverid } = $ server_id ;
$ info - > { sockets } = get_sockets ( ) ;
2017-10-03 12:54:29 +02:00
$ info - > { url } = $ url ;
2012-04-05 08:04:06 +02:00
2012-03-22 12:38:15 +01:00
return $ info
} } ) ;
__PACKAGE__ - > register_method ( {
2017-10-03 12:54:29 +02:00
name = > 'update' ,
path = > '' ,
2012-03-22 12:38:15 +01:00
method = > 'POST' ,
2017-11-30 08:20:34 +01:00
permissions = > {
check = > [ 'perm' , '/nodes/{node}' , [ 'Sys.Modify' ] ] ,
} ,
2012-03-22 12:38:15 +01:00
description = > "Update subscription info." ,
proxyto = > 'node' ,
protected = > 1 ,
parameters = > {
additionalProperties = > 0 ,
properties = > {
node = > get_standard_option ( 'pve-node' ) ,
force = > {
2023-06-05 13:25:17 +02:00
description = > "Always connect to server, even if local cache is still valid." ,
2012-03-22 12:38:15 +01:00
type = > 'boolean' ,
optional = > 1 ,
default = > 0
}
} ,
} ,
returns = > { type = > 'null' } ,
code = > sub {
my ( $ param ) = @ _ ;
2022-06-30 14:06:16 +02:00
my $ info = read_etc_subscription ( ) ;
2012-03-22 12:38:15 +01:00
return undef if ! $ info ;
2017-10-03 12:54:29 +02:00
my $ server_id = PVE::API2Tools:: get_hwaddress ( ) ;
my $ key = $ info - > { key } ;
2022-06-30 14:46:42 +02:00
die "Updating offline key not possible - please remove and re-add subscription key to switch to online key.\n"
if $ info - > { signature } ;
2023-06-05 13:25:42 +02:00
return undef if ! $ param - > { force } && cache_is_valid ( $ info ) ; # key has been recently checked
2012-03-22 12:38:15 +01:00
2017-10-03 12:54:29 +02:00
my $ req_sockets = get_sockets ( ) ;
check_key ( $ key , $ req_sockets ) ;
my $ dccfg = PVE::Cluster:: cfs_read_file ( 'datacenter.cfg' ) ;
my $ proxy = $ dccfg - > { http_proxy } ;
2022-06-30 14:06:16 +02:00
$ info = Proxmox::RS::Subscription:: check_subscription ( $ key , $ server_id , "" , "Proxmox VE" , $ proxy ) ;
2012-03-22 12:38:15 +01:00
2022-06-30 14:06:16 +02:00
write_etc_subscription ( $ info ) ;
2012-03-22 12:38:15 +01:00
return undef ;
} } ) ;
__PACKAGE__ - > register_method ( {
2017-10-03 12:54:29 +02:00
name = > 'set' ,
path = > '' ,
2012-03-22 12:38:15 +01:00
method = > 'PUT' ,
2017-11-30 08:20:34 +01:00
permissions = > {
check = > [ 'perm' , '/nodes/{node}' , [ 'Sys.Modify' ] ] ,
} ,
2012-03-22 12:38:15 +01:00
description = > "Set subscription key." ,
proxyto = > 'node' ,
protected = > 1 ,
parameters = > {
2020-05-27 13:39:13 +02:00
additionalProperties = > 0 ,
2012-03-22 12:38:15 +01:00
properties = > {
node = > get_standard_option ( 'pve-node' ) ,
key = > {
description = > "Proxmox VE subscription key" ,
type = > 'string' ,
2023-10-05 10:24:30 +02:00
pattern = > "\\s*${subscription_pattern}\\s*" ,
2017-10-03 12:54:29 +02:00
maxLength = > 32 ,
2012-03-22 12:38:15 +01:00
} ,
} ,
} ,
returns = > { type = > 'null' } ,
code = > sub {
my ( $ param ) = @ _ ;
2017-10-03 12:54:29 +02:00
my $ key = PVE::Tools:: trim ( $ param - > { key } ) ;
2014-06-13 06:32:07 +02:00
2023-06-05 13:25:17 +02:00
my $ new_info = {
2012-03-22 12:38:15 +01:00
status = > 'New' ,
2017-10-03 12:54:29 +02:00
key = > $ key ,
2012-03-22 12:38:15 +01:00
checktime = > time ( ) ,
} ;
my $ req_sockets = get_sockets ( ) ;
2013-08-01 11:35:48 +02:00
my $ server_id = PVE::API2Tools:: get_hwaddress ( ) ;
2012-03-22 12:38:15 +01:00
2017-10-03 12:54:29 +02:00
check_key ( $ key , $ req_sockets ) ;
2012-03-22 12:38:15 +01:00
2023-06-05 13:25:17 +02:00
write_etc_subscription ( $ new_info ) ;
2012-03-22 12:38:15 +01:00
2017-10-03 12:54:29 +02:00
my $ dccfg = PVE::Cluster:: cfs_read_file ( 'datacenter.cfg' ) ;
my $ proxy = $ dccfg - > { http_proxy } ;
2023-06-05 13:25:17 +02:00
my $ checked_info = Proxmox::RS::Subscription:: check_subscription (
$ key , $ server_id , "" , "Proxmox VE" , $ proxy ) ;
2012-03-22 12:38:15 +01:00
2023-06-05 13:25:17 +02:00
write_etc_subscription ( $ checked_info ) ;
2012-03-22 12:38:15 +01:00
return undef ;
} } ) ;
2020-05-26 21:39:45 +02:00
__PACKAGE__ - > register_method ( {
name = > 'delete' ,
path = > '' ,
method = > 'DELETE' ,
permissions = > {
check = > [ 'perm' , '/nodes/{node}' , [ 'Sys.Modify' ] ] ,
} ,
2020-05-27 14:11:16 +02:00
description = > "Delete subscription key of this node." ,
2020-05-26 21:39:45 +02:00
proxyto = > 'node' ,
protected = > 1 ,
parameters = > {
2020-05-27 13:39:13 +02:00
additionalProperties = > 0 ,
2020-05-26 21:39:45 +02:00
properties = > {
node = > get_standard_option ( 'pve-node' ) ,
} ,
} ,
returns = > { type = > 'null' } ,
code = > sub {
my $ subscription_file = '/etc/subscription' ;
2020-05-27 13:39:13 +02:00
return if ! - e $ subscription_file ;
2020-05-26 21:39:45 +02:00
unlink ( $ subscription_file ) or die "cannot delete subscription key: $!" ;
return undef ;
} } ) ;
2012-03-22 12:38:15 +01:00
1 ;