start
This commit is contained in:
commit
de116796d4
1
.gear/rules
Normal file
1
.gear/rules
Normal file
@ -0,0 +1 @@
|
||||
tar: .
|
3
.gear/upstream/remotes
Normal file
3
.gear/upstream/remotes
Normal file
@ -0,0 +1,3 @@
|
||||
[remote "upstream"]
|
||||
url = git://git.proxmox.com/git/pve-http-server.git
|
||||
fetch = +refs/heads/*:refs/remotes/upstream/*
|
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
build/
|
||||
*.deb
|
||||
*.buildinfo
|
||||
*.changes
|
50
Makefile
Normal file
50
Makefile
Normal file
@ -0,0 +1,50 @@
|
||||
include /usr/share/dpkg/pkg-info.mk
|
||||
|
||||
PACKAGE=libpve-http-server-perl
|
||||
|
||||
GITVERSION:=$(shell git rev-parse HEAD)
|
||||
BUILDDIR ?= $(PACKAGE)-$(DEB_VERSION)
|
||||
|
||||
DSC=$(PACKAGE)_$(DEB_VERSION).dsc
|
||||
DEB=$(PACKAGE)_$(DEB_VERSION)_all.deb
|
||||
|
||||
all:
|
||||
|
||||
$(BUILDDIR): src debian
|
||||
rm -rf $@ $@.tmp
|
||||
cp -a src $@.tmp
|
||||
cp -a debian $@.tmp/
|
||||
echo "git clone git://git.proxmox.com/git/pve-http-server\\ngit checkout $(GITVERSION)" > $@.tmp/debian/SOURCE
|
||||
mv $@.tmp $@
|
||||
|
||||
.PHONY: deb
|
||||
deb: $(DEB)
|
||||
$(DEB): $(BUILDDIR)
|
||||
cd $(BUILDDIR); dpkg-buildpackage -b -us -uc
|
||||
lintian $(DEB)
|
||||
|
||||
.PHONY: dsc
|
||||
dsc: $(DSC)
|
||||
$(DSC): $(BUILDDIR)
|
||||
cd $(BUILDDIR); dpkg-buildpackage -S -us -uc
|
||||
lintian $(DSC)
|
||||
|
||||
sbuild: $(DSC)
|
||||
sbuild $(DSC)
|
||||
|
||||
.PHONY: upload
|
||||
upload: UPLOAD_DIST ?= $(DEB_DISTRIBUTION)
|
||||
upload: $(DEB)
|
||||
tar cf - $(DEB) | ssh -X repoman@repo.proxmox.com -- upload --product pve,pmg --dist $(UPLOAD_DIST)
|
||||
|
||||
.PHONY: clean distclean
|
||||
distclean: clean
|
||||
$(MAKE) -C src $@
|
||||
|
||||
clean:
|
||||
$(MAKE) -C src $@
|
||||
rm -rf $(PACKAGE)-*/ *.deb *.dsc *.tar.* *.changes *.build *.buildinfo examples/simple-demo.lck
|
||||
|
||||
.PHONY: dinstall
|
||||
dinstall: $(DEB)
|
||||
dpkg -i $(DEB)
|
426
debian/changelog
vendored
Normal file
426
debian/changelog
vendored
Normal file
@ -0,0 +1,426 @@
|
||||
libpve-http-server-perl (5.1.2) bookworm; urgency=medium
|
||||
|
||||
* fix #5391: proxy request: avoid "HTTP 599 Too many redirections" error
|
||||
that could occur due to long-running requests and bad timing during
|
||||
connection reuse. Disable connection reuse for all but GET requests that
|
||||
are proxied between different nodes, and allow one retry in this case.
|
||||
|
||||
This can add a tiny bit of overhead if many PUT requests that are proxied
|
||||
to other nodes are issued with only a small delay between each other.
|
||||
However, such a high-frequency PUT request pattern is considered an edge
|
||||
case, and benchmarks show that the slowdown is about 2ms on average, which
|
||||
is often negligible compared to the actual time required to process the
|
||||
request.
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Fri, 04 Oct 2024 14:02:39 +0200
|
||||
|
||||
libpve-http-server-perl (5.1.1) bookworm; urgency=medium
|
||||
|
||||
* handler: only allow downloads for annotated endpoints and remove support
|
||||
for directly returned download info
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Mon, 23 Sep 2024 11:07:22 +0200
|
||||
|
||||
libpve-http-server-perl (5.1.0) bookworm; urgency=medium
|
||||
|
||||
* http: support the deflate compression content encoding
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Mon, 22 Apr 2024 13:14:26 +0200
|
||||
|
||||
libpve-http-server-perl (5.0.6) bookworm; urgency=medium
|
||||
|
||||
* access control: avoid "uninitialized value" warning if using IP
|
||||
ranges
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Tue, 26 Mar 2024 09:16:48 +0100
|
||||
|
||||
libpve-http-server-perl (5.0.5) bookworm; urgency=medium
|
||||
|
||||
* fix #4859: properly configure TLSv1.3 only mode
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Fri, 03 Nov 2023 12:06:31 +0100
|
||||
|
||||
libpve-http-server-perl (5.0.4) bookworm; urgency=medium
|
||||
|
||||
* fix #4802: reduce CA lookups while proxying with OpenSSL as packaged in
|
||||
Debian 12 Bookworm.
|
||||
|
||||
* avoid AnyEvent::AIO to fix CPU spinning if the pure-perl implementation
|
||||
libanyevent-aio-perl is installed, for example on development machines
|
||||
when trying to use the perl language server.
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Mon, 03 Jul 2023 09:38:56 +0200
|
||||
|
||||
libpve-http-server-perl (5.0.3) bookworm; urgency=medium
|
||||
|
||||
* proxy request: handle missing content-type header
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Fri, 09 Jun 2023 18:58:05 +0200
|
||||
|
||||
libpve-http-server-perl (5.0.2) bookworm; urgency=medium
|
||||
|
||||
* formatter/bootstrap: set SameSite attr of auth cookie to 'strict'
|
||||
|
||||
* when proxying requests, preserve json formatting instead of converting to
|
||||
x-www-form-urlencoded
|
||||
|
||||
* support actual arrays for array parameters, as a replacement for '-list' and
|
||||
'-alist' formats
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Wed, 07 Jun 2023 13:21:19 +0200
|
||||
|
||||
libpve-http-server-perl (5.0.1) bookworm; urgency=medium
|
||||
|
||||
* fix regression in the html (bootstrap) based API debug explorer, which
|
||||
came in through a more strict pattern checking in a newer version of the
|
||||
used URL encoding library
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Sat, 03 Jun 2023 15:15:47 +0200
|
||||
|
||||
libpve-http-server-perl (5.0.0) bookworm; urgency=medium
|
||||
|
||||
* switch over to native versioning
|
||||
|
||||
* various small code and packaging clean ups
|
||||
|
||||
* re-build for Debian 12 Bookworm based releases
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Wed, 17 May 2023 07:26:11 +0200
|
||||
|
||||
libpve-http-server-perl (4.2-3) bullseye; urgency=medium
|
||||
|
||||
* file upload: don't always calculate MD5 for syslog message, rather log the
|
||||
file name instead,
|
||||
|
||||
* explicitly disallow tmpfilename parameter in query URL
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Fri, 14 Apr 2023 16:27:07 +0200
|
||||
|
||||
libpve-http-server-perl (4.2-2) bullseye; urgency=medium
|
||||
|
||||
* multipart upload: properly parse file parts without Content-Type
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Tue, 11 Apr 2023 14:44:03 +0200
|
||||
|
||||
libpve-http-server-perl (4.2-1) bullseye; urgency=medium
|
||||
|
||||
* fix #4494: redirect incoming HTTP requests to HTTPS to avoid common
|
||||
pitfall when opening the Proxmox VE or Proxmox Mail Gateway web-interface
|
||||
for the first time
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Thu, 16 Mar 2023 16:57:59 +0100
|
||||
|
||||
libpve-http-server-perl (4.1-6) bullseye; urgency=medium
|
||||
|
||||
* multipart upload: fix upload of files starting with newlines
|
||||
|
||||
* multipart upload: don't fail on presebce of additional headers
|
||||
|
||||
* multipart upload: loosen trailing-newline requirement from spec, as some
|
||||
more popular clients (e.g., postman) violate that rule.
|
||||
|
||||
* fix #4344: http-server: fix regression that required the 'Content-Type' to
|
||||
be always present for multipart headers, while it wasn't used at all.
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Mon, 06 Mar 2023 13:39:57 +0100
|
||||
|
||||
libpve-http-server-perl (4.1-5) bullseye; urgency=medium
|
||||
|
||||
* upload: re-allow having white-space in filenames
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Mon, 07 Nov 2022 16:43:31 +0100
|
||||
|
||||
libpve-http-server-perl (4.1-4) bullseye; urgency=medium
|
||||
|
||||
* acknowledge content-disposition header
|
||||
|
||||
* request: add missing early return to future proof error check
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Thu, 29 Sep 2022 14:37:05 +0200
|
||||
|
||||
libpve-http-server-perl (4.1-3) bullseye; urgency=medium
|
||||
|
||||
* response: forbid linefeeds in response status message
|
||||
|
||||
* proxy request: assert that API url starts with a slash
|
||||
|
||||
* pass through streaming: only allow from privileged local pvedaemon as
|
||||
safety net
|
||||
|
||||
* requests: assert that there is no @ in the URLs authority
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Sat, 02 Jul 2022 09:16:21 +0200
|
||||
|
||||
libpve-http-server-perl (4.1-2) bullseye; urgency=medium
|
||||
|
||||
* tls: log failure to apply TLS 1.3 ciphers
|
||||
|
||||
* html formatter: encode href attributes for API debug viewer
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Tue, 17 May 2022 16:40:12 +0200
|
||||
|
||||
libpve-http-server-perl (4.1-1) bullseye; urgency=medium
|
||||
|
||||
* web socket: guard disconnect block check properly
|
||||
|
||||
* avoid warning if request params does not exist
|
||||
|
||||
* fix #3807: don't attempt response on closed handle
|
||||
|
||||
* fix #3790: allow setting TLS 1.3 cipher suites
|
||||
|
||||
* fix #3745: allow overriding TLS key location
|
||||
|
||||
* fix #3789: allow disabling TLS v1.2/v1.3
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Thu, 13 Jan 2022 13:32:43 +0100
|
||||
|
||||
libpve-http-server-perl (4.0-4) bullseye; urgency=medium
|
||||
|
||||
* webproxy: handle unflushed write buffer
|
||||
|
||||
* fix #3724: disable TLS renegotiation
|
||||
|
||||
* download-stream: allow the api call to set the content-encoding
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Wed, 24 Nov 2021 18:14:53 +0100
|
||||
|
||||
libpve-http-server-perl (4.0-3) bullseye; urgency=medium
|
||||
|
||||
* anyevent: move unlink from http-server to endpoint
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Mon, 04 Oct 2021 10:18:12 +0200
|
||||
|
||||
libpve-http-server-perl (4.0-2) pve pmg; urgency=medium
|
||||
|
||||
* AnyEvent/websocket_proxy: remove 'base64' handling
|
||||
|
||||
* AnyEvent/websocket_proxy: drop handling of websocket subprotocols
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Tue, 18 May 2021 10:19:00 +0200
|
||||
|
||||
libpve-http-server-perl (4.0-1) bullseye; urgency=medium
|
||||
|
||||
* rebuild for Debian 11 Bullseye based releases
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Fri, 14 May 2021 16:37:34 +0200
|
||||
|
||||
libpve-http-server-perl (3.2-2) pve pmg; urgency=medium
|
||||
|
||||
* access control: correctly match v4-mapped-v6 addresses
|
||||
|
||||
* access control: also match any IPv6 in 'ALL'
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Fri, 07 May 2021 17:49:34 +0200
|
||||
|
||||
libpve-http-server-perl (3.2-1) pve pmg; urgency=medium
|
||||
|
||||
* allow 'download' to be passed from API handler
|
||||
|
||||
* utils: add LISTEN_IP option in proxy configuration
|
||||
|
||||
* support streaming data form a file handle to a client
|
||||
|
||||
* allow stream download from path and over short-cutted pvedaemon-proxy
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Fri, 23 Apr 2021 13:54:04 +0200
|
||||
|
||||
libpve-http-server-perl (3.1-1) pve pmg; urgency=medium
|
||||
|
||||
* accept connection phase: fix connection count leak
|
||||
|
||||
* accept connection phase: immediately close socket on early error
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Fri, 11 Dec 2020 08:39:36 +0100
|
||||
|
||||
libpve-http-server-perl (3.0-6) pve pmg; urgency=medium
|
||||
|
||||
* fix #2766: allow application/json as content-type for post/put requests
|
||||
|
||||
* increase maximal accepted header count to 64. Modern browsers and proxy
|
||||
combinations can exceed the old limit of 30. The maximal accumulated total
|
||||
header size of 8 KiB stays untouched.
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Thu, 02 Jul 2020 09:42:39 +0200
|
||||
|
||||
libpve-http-server-perl (3.0-5) pve pmg; urgency=medium
|
||||
|
||||
* partially fix #2618: use new unified spice port range helper from
|
||||
pve-common, increases maximum proxy port for spice to 61999
|
||||
|
||||
* Websocket: implement ping/pong from RFC
|
||||
|
||||
* Websocket: performance improvements
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Mon, 09 Mar 2020 16:12:45 +0100
|
||||
|
||||
libpve-http-server-perl (3.0-4) pve pmg; urgency=medium
|
||||
|
||||
* allow ticket in 'Authorization' header as fallback
|
||||
|
||||
* api-server: extract, set and handle API token header
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Wed, 29 Jan 2020 09:32:04 +0100
|
||||
|
||||
libpve-http-server-perl (3.0-3) pve pmg; urgency=medium
|
||||
|
||||
* send_file_start: allow to pass a open fh and content-type
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Fri, 11 Oct 2019 11:25:12 +0200
|
||||
|
||||
libpve-http-server-perl (3.0-2) pve pmg; urgency=medium
|
||||
|
||||
* decode_urlencoded: cope with undefined values
|
||||
|
||||
* anyevent: rpcenv is optional and from our child instance
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Thu, 11 Jul 2019 19:30:23 +0200
|
||||
|
||||
libpve-http-server-perl (3.0-1) pve pmg; urgency=medium
|
||||
|
||||
* rebuild for Debian Buster / PVE 6.0
|
||||
|
||||
* update jQuery to 3.4.1
|
||||
|
||||
* update Bootstrap to 3.4.1
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Tue, 21 May 2019 21:35:00 +0200
|
||||
|
||||
libpve-http-server-perl (2.0-13) unstable; urgency=medium
|
||||
|
||||
* tls: make dh to openssl 1.1 compatible
|
||||
|
||||
* store Host header in rpc environment
|
||||
|
||||
* forward Host header in proxy_request
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Wed, 03 Apr 2019 13:55:44 +0200
|
||||
|
||||
libpve-http-server-perl (2.0-12) unstable; urgency=medium
|
||||
|
||||
* Allow one to specify 'honor_cipher_order' and 'compression' parameters
|
||||
|
||||
* move read_proxy_conf from PVE::API2Tools to new PVE::ApiServer::Utils module
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Tue, 26 Feb 2019 07:07:31 +0100
|
||||
|
||||
libpve-http-server-perl (2.0-11) unstable; urgency=medium
|
||||
|
||||
* fix #1935: spice proxy: read empty line after 200 OK
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Fri, 28 Sep 2018 10:41:22 +0200
|
||||
|
||||
libpve-http-server-perl (2.0-10) unstable; urgency=medium
|
||||
|
||||
* fix #1869: send correct http response in spice proxy
|
||||
|
||||
* websocket: set $max_payload_size = 128*1024; (131072)
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Fri, 17 Aug 2018 08:29:53 +0200
|
||||
|
||||
libpve-http-server-perl (2.0-9) unstable; urgency=medium
|
||||
|
||||
* Fix #1684 WebSocket proxy behind a buffered proxy
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Mon, 28 May 2018 10:33:41 +0200
|
||||
|
||||
libpve-http-server-perl (2.0-8) unstable; urgency=medium
|
||||
|
||||
* auth_handler: handle exceptions correctly instead of always returning 401
|
||||
|
||||
* add 'map' filetype to http-server
|
||||
|
||||
* do not send websocket status code to port
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Mon, 11 Dec 2017 15:35:34 +0100
|
||||
|
||||
libpve-http-server-perl (2.0-7) unstable; urgency=medium
|
||||
|
||||
* add content type application/x-compressed-tar
|
||||
|
||||
* allow API calls to download file contents
|
||||
|
||||
* build: reformat debian/control
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Tue, 14 Nov 2017 08:05:17 +0100
|
||||
|
||||
libpve-http-server-perl (2.0-6) unstable; urgency=medium
|
||||
|
||||
* pass $format to rest_handler()
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Thu, 10 Aug 2017 12:05:42 +0200
|
||||
|
||||
libpve-http-server-perl (2.0-5) unstable; urgency=medium
|
||||
|
||||
* add json/mp3/oga/svg MIME types for the new novnc
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Fri, 02 Jun 2017 12:49:02 +0200
|
||||
|
||||
libpve-http-server-perl (2.0-4) unstable; urgency=medium
|
||||
|
||||
* assume all parameters are utf8 encoded
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Tue, 02 May 2017 11:55:21 +0200
|
||||
|
||||
libpve-http-server-perl (2.0-3) unstable; urgency=medium
|
||||
|
||||
* avoid locale specific time stamps
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Mon, 24 Apr 2017 07:43:29 +0200
|
||||
|
||||
libpve-http-server-perl (2.0-2) unstable; urgency=medium
|
||||
|
||||
* fix #1332: allow ECDHE with all supported curves
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Mon, 03 Apr 2017 15:11:38 +0200
|
||||
|
||||
libpve-http-server-perl (2.0-1) unstable; urgency=medium
|
||||
|
||||
* bump version for debian stretch
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Fri, 10 Mar 2017 08:50:55 +0100
|
||||
|
||||
libpve-http-server-perl (1.0-4) unstable; urgency=medium
|
||||
|
||||
* add debian triggers file
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Sat, 21 Jan 2017 16:36:47 +0100
|
||||
|
||||
libpve-http-server-perl (1.0-3) unstable; urgency=medium
|
||||
|
||||
* console-demo.pl: add a more complex demo
|
||||
|
||||
* call Net::SSLeay::ERR_clear_error after all handlers
|
||||
|
||||
* avoid warnings when clients disconnects early
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Sat, 21 Jan 2017 16:19:20 +0100
|
||||
|
||||
libpve-http-server-perl (1.0-2) unstable; urgency=medium
|
||||
|
||||
* simple-demo.pl: simple demo server for testing
|
||||
|
||||
* extract_auth_cookie: always call uri_unescape($ticket)
|
||||
|
||||
* use canonical flag for json format
|
||||
|
||||
* remove base_handler_class from required arguments
|
||||
|
||||
* remove all references to rpcenv
|
||||
|
||||
* include jquery and bootstrap
|
||||
|
||||
* new helper add_dirs
|
||||
|
||||
* add new hook function to generate CSRF token
|
||||
|
||||
* add generic formatter framework
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Mon, 16 Jan 2017 18:39:21 +0100
|
||||
|
||||
libpve-http-server-perl (1.0-1) unstable; urgency=medium
|
||||
|
||||
* first try
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Fri, 13 Jan 2017 12:47:07 +0100
|
||||
|
31
debian/control
vendored
Normal file
31
debian/control
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
Source: libpve-http-server-perl
|
||||
Section: perl
|
||||
Priority: optional
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Build-Depends: debhelper-compat (= 13), perl,
|
||||
Standards-Version: 4.6.2
|
||||
Homepage: https://www.proxmox.com
|
||||
|
||||
Package: libpve-http-server-perl
|
||||
Architecture: all
|
||||
Depends: libanyevent-http-perl,
|
||||
libanyevent-perl (>= 7.140-3),
|
||||
libcrypt-ssleay-perl,
|
||||
libhtml-parser-perl,
|
||||
libhttp-date-perl,
|
||||
libhttp-message-perl,
|
||||
libio-socket-ssl-perl,
|
||||
libjs-bootstrap,
|
||||
libjs-jquery,
|
||||
libjson-perl,
|
||||
libnet-ip-perl,
|
||||
libpve-common-perl (>= 8.0.2),
|
||||
liburi-perl,
|
||||
${misc:Depends},
|
||||
${perl:Depends},
|
||||
Breaks: libpve-storage-perl (<< 8.2.5),
|
||||
pmg-api (<< 8.1.4),
|
||||
pve-manager (<< 8.2.7),
|
||||
Description: Proxmox Asynchrounous HTTP Server Implementation
|
||||
This package is used as base to implement the REST API in all perl based
|
||||
Proxmox projects.
|
16
debian/copyright
vendored
Normal file
16
debian/copyright
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
Copyright (C) 2010-2021 Proxmox Server Solutions GmbH
|
||||
|
||||
This software is written by Proxmox Server Solutions GmbH <support@proxmox.com>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
1
debian/docs
vendored
Normal file
1
debian/docs
vendored
Normal file
@ -0,0 +1 @@
|
||||
debian/SOURCE
|
9
debian/rules
vendored
Executable file
9
debian/rules
vendored
Executable file
@ -0,0 +1,9 @@
|
||||
#!/usr/bin/make -f
|
||||
# See debhelper(7) (uncomment to enable)
|
||||
# output every command that modifies files on the build system.
|
||||
#DH_VERBOSE = 1
|
||||
|
||||
|
||||
%:
|
||||
dh $@
|
||||
|
1
debian/source/format
vendored
Normal file
1
debian/source/format
vendored
Normal file
@ -0,0 +1 @@
|
||||
3.0 (native)
|
1
debian/triggers
vendored
Normal file
1
debian/triggers
vendored
Normal file
@ -0,0 +1 @@
|
||||
activate-noawait pve-api-updates
|
95
pve-http-server.spec
Normal file
95
pve-http-server.spec
Normal file
@ -0,0 +1,95 @@
|
||||
%define _unpackaged_files_terminate_build 1
|
||||
|
||||
Name: pve-http-server
|
||||
Summary: Proxmox Asynchrounous HTTP Server Implementation
|
||||
Version: 5.1.2
|
||||
Release: alt2
|
||||
License: AGPL-3.0+
|
||||
Group: Development/Perl
|
||||
Url: https://www.proxmox.com
|
||||
Vcs: git://git.proxmox.com/git/pve-http-server.git
|
||||
Source: %name-%version.tar
|
||||
|
||||
ExclusiveArch: x86_64 aarch64
|
||||
|
||||
Provides: perl-%name = %EVR
|
||||
# from debian/control
|
||||
Provides: libpve-http-server-perl = %EVR
|
||||
Conflicts: pve-storage < 8.2.5
|
||||
Conflicts: pmg-api < 8.1.4
|
||||
Conflicts: pve-manager < 8.2.7
|
||||
|
||||
Requires: fonts-font-awesome javascript-jquery javascript-bootstrap
|
||||
|
||||
BuildRequires: perl(AnyEvent/HTTP.pm) perl(AnyEvent/Handle.pm) perl(AnyEvent/IO.pm) perl(AnyEvent/Socket.pm) perl(AnyEvent/TLS.pm) perl(AnyEvent/Util.pm)
|
||||
BuildRequires: perl(Compress/Zlib.pm)
|
||||
BuildRequires: perl(Digest/MD5.pm) perl(Digest/SHA.pm) perl(Encode.pm)
|
||||
BuildRequires: perl(Net/SSLeay.pm)
|
||||
BuildRequires: perl(Time/HiRes.pm)
|
||||
BuildRequires: perl(HTTP/Date.pm) perl(HTTP/Headers.pm) perl(HTTP/Request.pm) perl(HTTP/Response.pm) perl(HTTP/Status.pm) perl(HTML/Entities.pm)
|
||||
BuildRequires: perl(JSON.pm)
|
||||
BuildRequires: perl(Net/IP.pm)
|
||||
BuildRequires: perl(URI/Escape.pm) perl(URI.pm)
|
||||
BuildRequires: perl(PVE/INotify.pm) perl(PVE/SafeSyslog.pm) perl(PVE/Tools.pm) perl(PVE/JSONSchema.pm)
|
||||
|
||||
%description
|
||||
%summary.
|
||||
This package is used as base to implement the REST API in all perl based
|
||||
|
||||
%prep
|
||||
%setup
|
||||
|
||||
%install
|
||||
%makeinstall_std -C src
|
||||
|
||||
%files
|
||||
%doc debian/copyright
|
||||
%perl_vendor_privlib/PVE/*
|
||||
|
||||
%changelog
|
||||
* Thu Dec 05 2024 Alexey Shabalin <shaba@altlinux.org> 5.1.2-alt2
|
||||
- Revert "fix UTF-8 presentation"
|
||||
|
||||
* Thu Nov 28 2024 Alexey Shabalin <shaba@altlinux.org> 5.1.2-alt1
|
||||
- unbootstrap
|
||||
- fix UTF-8 presentation
|
||||
|
||||
* Sun Oct 20 2024 Alexey Shabalin <shaba@altlinux.org> 5.1.2-alt0.1
|
||||
- 5.1.2
|
||||
- bootstrap, build without conflicts
|
||||
|
||||
* Thu Aug 29 2024 Andrew A. Vasilyev <andy@altlinux.org> 5.1.0-alt1
|
||||
- 5.1.0
|
||||
|
||||
* Fri Mar 29 2024 Andrew A. Vasilyev <andy@altlinux.org> 5.0.6-alt1
|
||||
- 5.0.6
|
||||
|
||||
* Wed Feb 28 2024 Andrew A. Vasilyev <andy@altlinux.org> 5.0.5-alt1
|
||||
- 5.0.5
|
||||
|
||||
* Thu May 25 2023 Andrew A. Vasilyev <andy@altlinux.org> 4.2.3-alt1
|
||||
- 4.2-3
|
||||
- add copyright file
|
||||
|
||||
* Mon Mar 20 2023 Andrew A. Vasilyev <andy@altlinux.org> 4.2.1-alt1
|
||||
- 4.2-1
|
||||
|
||||
* Sat Mar 11 2023 Andrew A. Vasilyev <andy@altlinux.org> 4.1.6-alt1
|
||||
- 4.1-6
|
||||
|
||||
* Mon Nov 14 2022 Alexey Shabalin <shaba@altlinux.org> 4.1.5-alt1
|
||||
- 4.1-5
|
||||
|
||||
* Fri Oct 07 2022 Andrew A. Vasilyev <andy@altlinux.org> 4.1.4-alt2
|
||||
- fix CPU eating loop
|
||||
|
||||
* Mon Oct 03 2022 Alexey Shabalin <shaba@altlinux.org> 4.1.4-alt1
|
||||
- 4.1-4
|
||||
|
||||
* Thu Jul 07 2022 Andrew A. Vasilyev <andy@altlinux.org> 4.1.3-alt1
|
||||
- 4.1-3
|
||||
|
||||
* Thu Feb 17 2022 Alexey Shabalin <shaba@altlinux.org> 4.1.1-alt1
|
||||
- 4.1-1
|
||||
- build as separate package
|
||||
|
23
src/Makefile
Normal file
23
src/Makefile
Normal file
@ -0,0 +1,23 @@
|
||||
DESTDIR=
|
||||
PERL5DIR=${DESTDIR}/usr/share/perl5
|
||||
DOCDIR=${DESTDIR}/usr/share/doc/${PACKAGE}
|
||||
|
||||
all:
|
||||
|
||||
install: PVE
|
||||
install -d -m 755 ${PERL5DIR}/PVE/APIServer
|
||||
install -m 0644 PVE/APIServer/AnyEvent.pm ${PERL5DIR}/PVE/APIServer
|
||||
install -m 0644 PVE/APIServer/Formatter.pm ${PERL5DIR}/PVE/APIServer
|
||||
install -m 0644 PVE/APIServer/Utils.pm ${PERL5DIR}/PVE/APIServer
|
||||
install -d -m 755 ${PERL5DIR}/PVE/APIServer/Formatter
|
||||
install -m 0644 PVE/APIServer/Formatter/Standard.pm ${PERL5DIR}/PVE/APIServer/Formatter
|
||||
install -m 0644 PVE/APIServer/Formatter/Bootstrap.pm ${PERL5DIR}/PVE/APIServer/Formatter
|
||||
install -m 0644 PVE/APIServer/Formatter/HTML.pm ${PERL5DIR}/PVE/APIServer/Formatter
|
||||
|
||||
.PHONY: clean distclean
|
||||
distclean: clean
|
||||
rm -f examples/simple-demo.pem
|
||||
|
||||
clean:
|
||||
rm -rf examples/simple-demo.lck
|
||||
find . -name '*~' -exec rm {} ';'
|
2206
src/PVE/APIServer/AnyEvent.pm
Normal file
2206
src/PVE/APIServer/AnyEvent.pm
Normal file
File diff suppressed because it is too large
Load Diff
107
src/PVE/APIServer/Formatter.pm
Normal file
107
src/PVE/APIServer/Formatter.pm
Normal file
@ -0,0 +1,107 @@
|
||||
package PVE::APIServer::Formatter;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use URI::Escape;
|
||||
|
||||
# generic formatter support
|
||||
# PVE::APIServer::Formatter::* classes should register themselves here
|
||||
|
||||
my $formatter_hash = {};
|
||||
my $page_formatter_hash = {};
|
||||
|
||||
sub register_formatter {
|
||||
my ($format, $code) = @_;
|
||||
|
||||
die "formatter '$format' already defined"
|
||||
if defined($formatter_hash->{$format});
|
||||
|
||||
$formatter_hash->{$format} = $code;
|
||||
}
|
||||
|
||||
sub register_page_formatter {
|
||||
my (%config) = @_;
|
||||
|
||||
my $format = $config{format} ||
|
||||
die "missing format";
|
||||
|
||||
my $path = $config{path} ||
|
||||
die "missing path";
|
||||
|
||||
my $method = $config{method} ||
|
||||
die "missing method";
|
||||
|
||||
my $code = $config{code} ||
|
||||
die "missing formatter code";
|
||||
|
||||
die "duplicate page formatter for '$method: $path'"
|
||||
if defined($page_formatter_hash->{$format}->{$method}->{$path});
|
||||
|
||||
$page_formatter_hash->{$format}->{$method}->{$path} = $code;
|
||||
}
|
||||
|
||||
sub get_formatter {
|
||||
my ($format, $method, $path) = @_;
|
||||
|
||||
return undef if !defined($format);
|
||||
|
||||
if (defined($method) && defined($path)) {
|
||||
my $code = $page_formatter_hash->{$format}->{$method}->{$path};
|
||||
return $code if defined($code);
|
||||
}
|
||||
|
||||
return $formatter_hash->{$format};
|
||||
}
|
||||
|
||||
my $login_formatter_hash = {};
|
||||
|
||||
sub register_login_formatter {
|
||||
my ($format, $code) = @_;
|
||||
|
||||
die "login formatter '$format' already defined"
|
||||
if defined($login_formatter_hash->{$format});
|
||||
|
||||
$login_formatter_hash->{$format} = $code;
|
||||
}
|
||||
|
||||
sub get_login_formatter {
|
||||
my ($format) = @_;
|
||||
|
||||
return undef if !defined($format);
|
||||
|
||||
return $login_formatter_hash->{$format};
|
||||
}
|
||||
|
||||
# some helper functions
|
||||
|
||||
sub extract_auth_value {
|
||||
my ($header, $key) = @_;
|
||||
|
||||
return undef if !$header;
|
||||
|
||||
my $value = ($header =~ /(?:^|\s)\Q$key\E(?:=| )([^;]*)/)[0];
|
||||
|
||||
$value = uri_unescape($value) if $value;
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
sub create_auth_cookie {
|
||||
my ($ticket, $cookie_name) = @_;
|
||||
|
||||
my $encticket = uri_escape($ticket);
|
||||
|
||||
return "${cookie_name}=$encticket; path=/; secure; SameSite=Lax;";
|
||||
}
|
||||
|
||||
sub create_auth_header {
|
||||
my ($value, $key) = @_;
|
||||
|
||||
return undef if !$key;
|
||||
|
||||
my $encoded = uri_escape($value);
|
||||
return "${key} ${encoded}";
|
||||
}
|
||||
|
||||
1;
|
237
src/PVE/APIServer/Formatter/Bootstrap.pm
Normal file
237
src/PVE/APIServer/Formatter/Bootstrap.pm
Normal file
@ -0,0 +1,237 @@
|
||||
package PVE::APIServer::Formatter::Bootstrap;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use HTML::Entities;
|
||||
use JSON;
|
||||
use URI::Escape;
|
||||
|
||||
# FIXME: remove console code??
|
||||
|
||||
# Helpers to generate simple html pages using Bootstrap markup.
|
||||
|
||||
my $jssrc = <<_EOJS;
|
||||
PVE.open_vm_console = function(node, vmid) {
|
||||
console.log("open vm " + vmid + " on node " + node);
|
||||
|
||||
var downloadWithName = function(uri, name) {
|
||||
var link = jQuery('#pve_console_anchor');
|
||||
link.attr("href", uri);
|
||||
|
||||
// Note: we need to tell android the correct file name extension
|
||||
// but we do not set 'download' tag for other environments, because
|
||||
// It can have strange side effects (additional user prompt on firefox)
|
||||
var andriod = navigator.userAgent.match(/Android/i) ? true : false;
|
||||
if (andriod) {
|
||||
link.attr("download", name);
|
||||
}
|
||||
|
||||
if (document.createEvent) {
|
||||
var evt = document.createEvent("MouseEvents");
|
||||
evt.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
|
||||
link.get(0).dispatchEvent(evt);
|
||||
} else {
|
||||
link.get(0).fireEvent('onclick');
|
||||
}
|
||||
};
|
||||
|
||||
jQuery.ajax("/api2/json/console", {
|
||||
data: { vmid: vmid, node: node },
|
||||
headers: { CSRFPreventionToken: PVE.CSRFPreventionToken },
|
||||
dataType: 'json',
|
||||
type: 'POST',
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
// fixme: howto view JS errors ?
|
||||
console.log("ERROR " + textStatus + ": " + errorThrown);
|
||||
},
|
||||
success: function(data) {
|
||||
var raw = "[virt-viewer]\\n";
|
||||
jQuery.each(data.data, function(k, v) {
|
||||
raw += k + "=" + v + "\\n";
|
||||
});
|
||||
var url = 'data:application/x-virt-viewer;charset=UTF-8,' +
|
||||
encodeURIComponent(raw);
|
||||
|
||||
downloadWithName(url, "pve-spice.vv");
|
||||
}
|
||||
});
|
||||
};
|
||||
_EOJS
|
||||
|
||||
sub new {
|
||||
my ($class, $res, $url, $auth, $config) = @_;
|
||||
|
||||
my $self = bless {
|
||||
url => $url,
|
||||
title => $config->{title},
|
||||
cookie_name => $config->{cookie_name},
|
||||
apitoken_name => $config->{apitoken_name},
|
||||
js => '',
|
||||
}, $class;
|
||||
|
||||
if (my $username = $auth->{userid}) {
|
||||
$self->{csrftoken} = $config->{csrfgen_func}->($username);
|
||||
}
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub body {
|
||||
my ($self, $html) = @_;
|
||||
|
||||
my $jssetup = "PVE = {};\n\n"; # create namespace
|
||||
|
||||
if ($self->{csrftoken}) {
|
||||
$jssetup .= "PVE.CSRFPreventionToken = '$self->{csrftoken}';\n";
|
||||
}
|
||||
|
||||
$jssetup .= "PVE.delete_auth_cookie = function() {\n";
|
||||
|
||||
if ($self->{cookie_name}) {
|
||||
$jssetup .= " document.cookie = \"$self->{cookie_name}=; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/; secure; SameSite=Lax;\";\n";
|
||||
};
|
||||
$jssetup .= "};\n";
|
||||
|
||||
return <<_EOD;
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>$self->{title}</title>
|
||||
|
||||
<!-- Bootstrap -->
|
||||
<link href="/js/bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||
|
||||
<script type="text/javascript">
|
||||
$jssetup
|
||||
$jssrc
|
||||
</script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
padding-top: 70px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
|
||||
<script src="/js/jquery/jquery.min.js"></script>
|
||||
<!-- Include all compiled plugins (below), or include individual files as needed -->
|
||||
<script src="/js/bootstrap/js/bootstrap.min.js"></script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<a class="hidden" id="pve_console_anchor"></a>
|
||||
$html
|
||||
<script type="text/javascript">
|
||||
$self->{js}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
_EOD
|
||||
}
|
||||
|
||||
my $comp_id_counter = 0;
|
||||
|
||||
sub el {
|
||||
my ($self, %param) = @_;
|
||||
|
||||
$param{tag} = 'div' if !$param{tag};
|
||||
|
||||
my $id;
|
||||
|
||||
my $html = "<$param{tag}";
|
||||
|
||||
if (wantarray) {
|
||||
$comp_id_counter++;
|
||||
$id = "pveid$comp_id_counter";
|
||||
$html .= " id=$id";
|
||||
}
|
||||
|
||||
my $skip = {
|
||||
tag => 1,
|
||||
cn => 1,
|
||||
html => 1,
|
||||
text => 1,
|
||||
};
|
||||
|
||||
my $boolattr = {
|
||||
required => 1,
|
||||
autofocus => 1,
|
||||
};
|
||||
|
||||
my $noescape = {
|
||||
placeholder => 1,
|
||||
};
|
||||
|
||||
foreach my $attr (keys %param) {
|
||||
next if $skip->{$attr};
|
||||
my $v = $noescape->{$attr} ? $param{$attr} : uri_escape_utf8($param{$attr}, "^\/\ A-Za-z0-9\-\._~");
|
||||
next if !defined($v);
|
||||
if ($boolattr->{$attr}) {
|
||||
$html .= " $attr" if $v;
|
||||
} else {
|
||||
$html .= " $attr=\"$v\"";
|
||||
}
|
||||
}
|
||||
|
||||
$html .= ">";
|
||||
|
||||
|
||||
if (my $cn = $param{cn}) {
|
||||
if(ref($cn) eq 'ARRAY'){
|
||||
foreach my $rec (@$cn) {
|
||||
$html .= $self->el(%$rec);
|
||||
}
|
||||
} else {
|
||||
$html .= $self->el(%$cn);
|
||||
}
|
||||
} elsif ($param{html}) {
|
||||
$html .= $param{html};
|
||||
} elsif ($param{text}) {
|
||||
$html .= encode_entities($param{text});
|
||||
}
|
||||
|
||||
$html .= "</$param{tag}>";
|
||||
|
||||
return wantarray ? ($html, $id) : $html;
|
||||
}
|
||||
|
||||
sub alert {
|
||||
my ($self, %param) = @_;
|
||||
|
||||
return $self->el(class => "alert alert-danger", %param);
|
||||
}
|
||||
|
||||
sub add_js {
|
||||
my ($self, $js) = @_;
|
||||
|
||||
$self->{js} .= $js . "\n";
|
||||
}
|
||||
|
||||
my $format_event_callback = sub {
|
||||
my ($info) = @_;
|
||||
|
||||
my $pstr = encode_json($info->{param});
|
||||
return "function(e){$info->{fn}.apply(e, $pstr);}";
|
||||
};
|
||||
|
||||
sub button {
|
||||
my ($self, %param) = @_;
|
||||
|
||||
$param{tag} = 'button';
|
||||
$param{class} = "btn btn-default btn-xs";
|
||||
|
||||
if (my $click = delete $param{click}) {
|
||||
my ($html, $id) = $self->el(%param);
|
||||
my $cb = &$format_event_callback($click);
|
||||
$self->add_js("jQuery('#$id').on('click', $cb);");
|
||||
return $html;
|
||||
} else {
|
||||
return $self->el(%param);
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
296
src/PVE/APIServer/Formatter/HTML.pm
Normal file
296
src/PVE/APIServer/Formatter/HTML.pm
Normal file
@ -0,0 +1,296 @@
|
||||
package PVE::APIServer::Formatter::HTML;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use PVE::APIServer::Formatter;
|
||||
use HTTP::Status;
|
||||
use JSON;
|
||||
use HTML::Entities;
|
||||
use PVE::JSONSchema;
|
||||
use PVE::APIServer::Formatter::Bootstrap;
|
||||
use PVE::APIServer::Formatter::Standard;
|
||||
|
||||
my $portal_format = 'html';
|
||||
my $portal_ct = 'text/html;charset=UTF-8';
|
||||
|
||||
my $get_portal_base_url = sub {
|
||||
my ($config) = @_;
|
||||
return "$config->{base_uri}/$portal_format";
|
||||
};
|
||||
|
||||
my $get_portal_login_url = sub {
|
||||
my ($config) = @_;
|
||||
return "$config->{base_uri}/$portal_format/access/ticket";
|
||||
};
|
||||
|
||||
sub render_page {
|
||||
my ($doc, $html, $config) = @_;
|
||||
|
||||
my $items = [];
|
||||
|
||||
push @$items, {
|
||||
tag => 'li',
|
||||
cn => {
|
||||
tag => 'a',
|
||||
href => $get_portal_login_url->($config),
|
||||
onClick => "PVE.delete_auth_cookie();",
|
||||
text => "Logout",
|
||||
}};
|
||||
|
||||
my $base_url = $get_portal_base_url->($config);
|
||||
|
||||
my $nav = $doc->el(
|
||||
class => "navbar navbar-inverse navbar-fixed-top",
|
||||
role => "navigation", cn => {
|
||||
class => "container", cn => [
|
||||
{
|
||||
class => "navbar-header", cn => [
|
||||
{
|
||||
tag => 'button',
|
||||
type => 'button',
|
||||
class => "navbar-toggle",
|
||||
'data-toggle' => "collapse",
|
||||
'data-target' => ".navbar-collapse",
|
||||
cn => [
|
||||
{ tag => 'span', class => 'sr-only', text => "Toggle navigation" },
|
||||
{ tag => 'span', class => 'icon-bar' },
|
||||
{ tag => 'span', class => 'icon-bar' },
|
||||
{ tag => 'span', class => 'icon-bar' },
|
||||
],
|
||||
},
|
||||
{
|
||||
tag => 'a',
|
||||
class => "navbar-brand",
|
||||
href => $base_url,
|
||||
text => $config->{title},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
class => "collapse navbar-collapse",
|
||||
cn => {
|
||||
tag => 'ul',
|
||||
class => "nav navbar-nav",
|
||||
cn => $items,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
$items = [];
|
||||
my @pcomp = split('/', $doc->{url});
|
||||
shift @pcomp; # empty
|
||||
shift @pcomp; # api2
|
||||
shift @pcomp; # $format
|
||||
|
||||
my $href = $base_url;
|
||||
push @$items, { tag => 'li', cn => {
|
||||
tag => 'a',
|
||||
href => $href,
|
||||
text => 'Home'}};
|
||||
|
||||
foreach my $comp (@pcomp) {
|
||||
$href .= "/".encode_entities($comp);
|
||||
push @$items, { tag => 'li', cn => {
|
||||
tag => 'a',
|
||||
href => $href,
|
||||
text => $comp}};
|
||||
}
|
||||
|
||||
my $breadcrumbs = $doc->el(tag => 'ol', class => 'breadcrumb container', cn => $items);
|
||||
|
||||
return $doc->body($nav . $breadcrumbs . $html);
|
||||
}
|
||||
|
||||
my $login_form = sub {
|
||||
my ($config, $doc, $param, $errmsg) = @_;
|
||||
|
||||
$param = {} if !$param;
|
||||
|
||||
my $username = $param->{username} || '';
|
||||
my $password = $param->{password} || '';
|
||||
|
||||
my $items = [
|
||||
{
|
||||
tag => 'label',
|
||||
text => "Please sign in",
|
||||
},
|
||||
{
|
||||
tag => 'input',
|
||||
type => 'text',
|
||||
class => 'form-control',
|
||||
name => 'username',
|
||||
value => $username,
|
||||
placeholder => "Enter user name",
|
||||
required => 1,
|
||||
autofocus => 1,
|
||||
},
|
||||
{
|
||||
tag => 'input',
|
||||
type => 'password',
|
||||
class => 'form-control',
|
||||
name => 'password',
|
||||
value => $password,
|
||||
placeholder => 'Password',
|
||||
required => 1,
|
||||
},
|
||||
];
|
||||
|
||||
my $html = '';
|
||||
|
||||
$html .= $doc->alert(text => $errmsg) if ($errmsg);
|
||||
|
||||
$html .= $doc->el(
|
||||
class => 'container',
|
||||
cn => {
|
||||
tag => 'form',
|
||||
role => 'form',
|
||||
method => 'POST',
|
||||
action => $get_portal_login_url->($config),
|
||||
cn => [
|
||||
{
|
||||
class => 'form-group',
|
||||
cn => $items,
|
||||
},
|
||||
{
|
||||
tag => 'button',
|
||||
type => 'submit',
|
||||
class => 'btn btn-lg btn-primary btn-block',
|
||||
text => "Sign in",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return $html;
|
||||
};
|
||||
|
||||
PVE::APIServer::Formatter::register_login_formatter($portal_format, sub {
|
||||
my ($path, $auth, $config) = @_;
|
||||
|
||||
my $headers = HTTP::Headers->new(Location => $get_portal_login_url->($config));
|
||||
return HTTP::Response->new(301, "Moved", $headers);
|
||||
});
|
||||
|
||||
PVE::APIServer::Formatter::register_formatter($portal_format, sub {
|
||||
my ($res, $data, $param, $path, $auth, $config) = @_;
|
||||
|
||||
# fixme: clumsy!
|
||||
PVE::APIServer::Formatter::Standard::prepare_response_data($portal_format, $res);
|
||||
$data = $res->{data};
|
||||
|
||||
my $html = '';
|
||||
my $doc = PVE::APIServer::Formatter::Bootstrap->new($res, $path, $auth, $config);
|
||||
|
||||
if (!HTTP::Status::is_success($res->{status})) {
|
||||
$html .= $doc->alert(text => "Error $res->{status}: $res->{message}");
|
||||
}
|
||||
|
||||
my $lnk;
|
||||
|
||||
if (my $info = $res->{info}) {
|
||||
$html .= $doc->el(tag => 'h3', text => 'Description');
|
||||
$html .= $doc->el(tag => 'p', text => $info->{description});
|
||||
|
||||
$lnk = PVE::JSONSchema::method_get_child_link($info);
|
||||
}
|
||||
|
||||
if ($lnk && $data && $data->{data} && HTTP::Status::is_success($res->{status})) {
|
||||
|
||||
my $href = $lnk->{href};
|
||||
if ($href =~ m/^\{(\S+)\}$/) {
|
||||
|
||||
my $items = [];
|
||||
|
||||
my $prop = $1;
|
||||
$path =~ s/\/+$//; # remove trailing slash
|
||||
|
||||
foreach my $elem (sort {$a->{$prop} cmp $b->{$prop}} @{$data->{data}}) {
|
||||
next if !ref($elem);
|
||||
|
||||
if (defined(my $value = $elem->{$prop})) {
|
||||
my $tv = to_json($elem, {pretty => 1, allow_nonref => 1, canonical => 1});
|
||||
|
||||
push @$items, {
|
||||
tag => 'a',
|
||||
class => 'list-group-item',
|
||||
href => "$path/".encode_entities($value),
|
||||
cn => [
|
||||
{
|
||||
tag => 'h4',
|
||||
class => 'list-group-item-heading',
|
||||
text => $value,
|
||||
},
|
||||
{
|
||||
tag => 'pre',
|
||||
class => 'list-group-item',
|
||||
text => $tv,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
$html .= $doc->el(class => 'list-group', cn => $items);
|
||||
|
||||
} else {
|
||||
|
||||
my $json = to_json($data, {allow_nonref => 1, pretty => 1, canonical => 1});
|
||||
$html .= $doc->el(tag => 'pre', text => $json);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
my $json = to_json($data, {allow_nonref => 1, pretty => 1, canonical => 1});
|
||||
$html .= $doc->el(tag => 'pre', text => $json);
|
||||
}
|
||||
|
||||
$html = $doc->el(class => 'container', html => $html);
|
||||
|
||||
my $raw = render_page($doc, $html, $config);
|
||||
return ($raw, $portal_ct);
|
||||
});
|
||||
|
||||
PVE::APIServer::Formatter::register_page_formatter(
|
||||
'format' => $portal_format,
|
||||
method => 'GET',
|
||||
path => "/access/ticket",
|
||||
code => sub {
|
||||
my ($res, $data, $param, $path, $auth, $config) = @_;
|
||||
|
||||
my $doc = PVE::APIServer::Formatter::Bootstrap->new($res, $path, $auth, $config);
|
||||
|
||||
my $html = $login_form->($config, $doc);
|
||||
|
||||
my $raw = render_page($doc, $html, $config);
|
||||
return ($raw, $portal_ct);
|
||||
});
|
||||
|
||||
PVE::APIServer::Formatter::register_page_formatter(
|
||||
'format' => $portal_format,
|
||||
method => 'POST',
|
||||
path => "/access/ticket",
|
||||
code => sub {
|
||||
my ($res, $data, $param, $path, $auth, $config) = @_;
|
||||
|
||||
if (HTTP::Status::is_success($res->{status})) {
|
||||
my $cookie = PVE::APIServer::Formatter::create_auth_cookie(
|
||||
$data->{ticket}, $config->{cookie_name});
|
||||
|
||||
my $headers = HTTP::Headers->new(Location => $get_portal_base_url->($config),
|
||||
'Set-Cookie' => $cookie);
|
||||
return HTTP::Response->new(301, "Moved", $headers);
|
||||
}
|
||||
|
||||
# Note: HTTP server redirects to 'GET /access/ticket', so below
|
||||
# output is not really visible.
|
||||
|
||||
my $doc = PVE::APIServer::Formatter::Bootstrap->new($res, $path, $auth, $config);
|
||||
|
||||
my $html = $login_form->($config, $doc);
|
||||
|
||||
my $raw = render_page($doc, $html, $config);
|
||||
return ($raw, $portal_ct);
|
||||
});
|
||||
|
||||
1;
|
141
src/PVE/APIServer/Formatter/Standard.pm
Normal file
141
src/PVE/APIServer/Formatter/Standard.pm
Normal file
@ -0,0 +1,141 @@
|
||||
package PVE::APIServer::Formatter::Standard;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use PVE::APIServer::Formatter;
|
||||
use HTTP::Status;
|
||||
use JSON;
|
||||
use HTML::Entities;
|
||||
use PVE::JSONSchema;
|
||||
|
||||
# register result formatters
|
||||
|
||||
sub prepare_response_data {
|
||||
my ($format, $res) = @_;
|
||||
|
||||
my $success = 1;
|
||||
my $new = {
|
||||
data => $res->{data},
|
||||
};
|
||||
if (scalar(keys %{$res->{errors}})) {
|
||||
$success = 0;
|
||||
$new->{errors} = $res->{errors};
|
||||
}
|
||||
|
||||
if ($format eq 'extjs' || $format eq 'htmljs') {
|
||||
# HACK: extjs wants 'success' property instead of useful HTTP status codes
|
||||
if (HTTP::Status::is_error($res->{status})) {
|
||||
$success = 0;
|
||||
$new->{message} = $res->{message} || status_message($res->{status});
|
||||
$new->{status} = $res->{status} || 200;
|
||||
$res->{message} = undef;
|
||||
$res->{status} = 200;
|
||||
}
|
||||
$new->{success} = $success;
|
||||
}
|
||||
|
||||
if ($success && $res->{total}) {
|
||||
$new->{total} = $res->{total};
|
||||
}
|
||||
|
||||
if ($success && $res->{changes}) {
|
||||
$new->{changes} = $res->{changes};
|
||||
}
|
||||
|
||||
$res->{data} = $new;
|
||||
}
|
||||
|
||||
PVE::APIServer::Formatter::register_formatter('json', sub {
|
||||
my ($res, $data, $param, $path, $auth, $config) = @_;
|
||||
|
||||
my $nocomp = 0;
|
||||
|
||||
my $ct = 'application/json;charset=UTF-8';
|
||||
|
||||
prepare_response_data('json', $res);
|
||||
|
||||
my $raw = to_json($res->{data}, {utf8 => 1, allow_nonref => 1});
|
||||
|
||||
return ($raw, $ct, $nocomp);
|
||||
});
|
||||
|
||||
|
||||
PVE::APIServer::Formatter::register_formatter('extjs', sub {
|
||||
my ($res, $data, $param, $path, $auth, $config) = @_;
|
||||
|
||||
my $nocomp = 0;
|
||||
|
||||
my $ct = 'application/json;charset=UTF-8';
|
||||
|
||||
prepare_response_data('extjs', $res);
|
||||
|
||||
my $raw = to_json($res->{data}, {utf8 => 1, allow_nonref => 1});
|
||||
|
||||
return ($raw, $ct, $nocomp);
|
||||
});
|
||||
|
||||
PVE::APIServer::Formatter::register_formatter('htmljs', sub {
|
||||
my ($res, $data, $param, $path, $auth, $config) = @_;
|
||||
|
||||
my $nocomp = 0;
|
||||
|
||||
# we use this for extjs file upload forms
|
||||
|
||||
my $ct = 'text/html;charset=UTF-8';
|
||||
|
||||
prepare_response_data('htmljs', $res);
|
||||
|
||||
my $raw = encode_entities(to_json($res->{data}, {allow_nonref => 1}));
|
||||
|
||||
return ($raw, $ct, $nocomp);
|
||||
});
|
||||
|
||||
|
||||
PVE::APIServer::Formatter::register_formatter('spiceconfig', sub {
|
||||
my ($res, $data, $param, $path, $auth, $config) = @_;
|
||||
|
||||
my $nocomp = 0;
|
||||
|
||||
my $ct = 'application/x-virt-viewer;charset=UTF-8';
|
||||
|
||||
prepare_response_data('spiceconfig', $res);
|
||||
|
||||
$data = $res->{data};
|
||||
|
||||
my $raw;
|
||||
|
||||
if ($data && ref($data) && ref($data->{data})) {
|
||||
$raw = "[virt-viewer]\n";
|
||||
while (my ($key, $value) = each %{$data->{data}}) {
|
||||
$raw .= "$key=$value\n" if defined($value);
|
||||
}
|
||||
}
|
||||
|
||||
return ($raw, $ct, $nocomp);
|
||||
});
|
||||
|
||||
PVE::APIServer::Formatter::register_formatter('png', sub {
|
||||
my ($res, $data, $param, $path, $auth, $config) = @_;
|
||||
|
||||
my $nocomp = 1;
|
||||
|
||||
my $ct = 'image/png';
|
||||
|
||||
prepare_response_data('png', $res);
|
||||
|
||||
$data = $res->{data};
|
||||
|
||||
# fixme: better to revove that whole png thing ?
|
||||
|
||||
my $filename;
|
||||
my $raw = '';
|
||||
|
||||
if ($data && ref($data) && ref($data->{data}) &&
|
||||
$data->{data}->{filename} && defined($data->{data}->{image})) {
|
||||
$filename = $data->{data}->{filename};
|
||||
$raw = $data->{data}->{image};
|
||||
}
|
||||
|
||||
return ($raw, $ct, $nocomp);
|
||||
});
|
90
src/PVE/APIServer/Utils.pm
Normal file
90
src/PVE/APIServer/Utils.pm
Normal file
@ -0,0 +1,90 @@
|
||||
package PVE::APIServer::Utils;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Net::IP;
|
||||
|
||||
# all settings are used for pveproxy and pmgproxy
|
||||
# the ALLOW/DENY/POLICY is also used by spiceproxy
|
||||
sub read_proxy_config {
|
||||
my ($proxy_name) = @_;
|
||||
|
||||
my $conffile = "/etc/default/$proxy_name";
|
||||
|
||||
# Note: evaluate with bash
|
||||
my $shcmd = ". $conffile;\n";
|
||||
$shcmd .= 'echo \"LISTEN_IP:\$LISTEN_IP\";';
|
||||
$shcmd .= 'echo \"ALLOW_FROM:\$ALLOW_FROM\";';
|
||||
$shcmd .= 'echo \"DENY_FROM:\$DENY_FROM\";';
|
||||
$shcmd .= 'echo \"POLICY:\$POLICY\";';
|
||||
$shcmd .= 'echo \"CIPHERS:\$CIPHERS\";';
|
||||
$shcmd .= 'echo \"CIPHERSUITES:\$CIPHERSUITES\";';
|
||||
$shcmd .= 'echo \"DHPARAMS:\$DHPARAMS\";';
|
||||
$shcmd .= 'echo \"TLS_KEY_FILE:\$TLS_KEY_FILE\";';
|
||||
$shcmd .= 'echo \"HONOR_CIPHER_ORDER:\$HONOR_CIPHER_ORDER\";';
|
||||
$shcmd .= 'echo \"COMPRESSION:\$COMPRESSION\";';
|
||||
$shcmd .= 'echo \"DISABLE_TLS_1_2:\$DISABLE_TLS_1_2\";';
|
||||
$shcmd .= 'echo \"DISABLE_TLS_1_3:\$DISABLE_TLS_1_3\";';
|
||||
|
||||
my $data = -f $conffile ? `bash -c "$shcmd"` : '';
|
||||
|
||||
my $res = {};
|
||||
|
||||
my $boolean_options = [
|
||||
'HONOR_CIPHER_ORDER',
|
||||
'COMPRESSION',
|
||||
'DISABLE_TLS_1_2',
|
||||
'DISABLE_TLS_1_3',
|
||||
];
|
||||
|
||||
while ($data =~ s/^(.*)\n//) {
|
||||
my ($key, $value) = split(/:/, $1, 2);
|
||||
next if !defined($value) || $value eq '';
|
||||
if ($key eq 'ALLOW_FROM' || $key eq 'DENY_FROM') {
|
||||
my $ips = [];
|
||||
foreach my $ip (split(/,/, $value)) {
|
||||
if ($ip eq 'all') {
|
||||
push @$ips, Net::IP->new('0/0') || die Net::IP::Error() . "\n";
|
||||
push @$ips, Net::IP->new('::/0') || die Net::IP::Error() . "\n";
|
||||
next;
|
||||
}
|
||||
push @$ips, Net::IP->new(normalize_v4_in_v6($ip)) || die Net::IP::Error() . "\n";
|
||||
}
|
||||
$res->{$key} = $ips;
|
||||
} elsif ($key eq 'LISTEN_IP') {
|
||||
$res->{$key} = $value;
|
||||
} elsif ($key eq 'POLICY') {
|
||||
die "unknown policy '$value'\n" if $value !~ m/^(allow|deny)$/;
|
||||
$res->{$key} = $value;
|
||||
} elsif ($key eq 'CIPHERS') {
|
||||
$res->{$key} = $value;
|
||||
} elsif ($key eq 'CIPHERSUITES') {
|
||||
$res->{$key} = $value;
|
||||
} elsif ($key eq 'DHPARAMS') {
|
||||
$res->{$key} = $value;
|
||||
} elsif ($key eq 'TLS_KEY_FILE') {
|
||||
$res->{$key} = $value;
|
||||
} elsif (grep { $key eq $_ } @$boolean_options) {
|
||||
die "unknown value '$value' - use 0 or 1\n" if $value !~ m/^(0|1)$/;
|
||||
$res->{$key} = $value;
|
||||
} else {
|
||||
# silently skip everythin else?
|
||||
}
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
sub normalize_v4_in_v6 {
|
||||
my ($ip_text) = @_;
|
||||
|
||||
my $ip = Net::IP->new($ip_text) || die Net::IP::Error() . "\n";
|
||||
my $v4_mapped_v6_prefix = Net::IP->new('::ffff:0:0/96');
|
||||
if ($v4_mapped_v6_prefix->overlaps($ip)) {
|
||||
return Net::IP::ip_get_embedded_ipv4($ip_text);
|
||||
}
|
||||
return $ip_text;
|
||||
}
|
||||
|
||||
1;
|
552
src/examples/console-demo.pl
Executable file
552
src/examples/console-demo.pl
Executable file
@ -0,0 +1,552 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
# This demo requires some other packages: novnc-pve and
|
||||
# pve-manager (for PVE::NoVncIndex)
|
||||
|
||||
|
||||
# First, we need some helpers to create authentication Tickets
|
||||
|
||||
package Ticket;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Net::SSLeay;
|
||||
|
||||
use PVE::Ticket;
|
||||
|
||||
use Crypt::OpenSSL::RSA;
|
||||
|
||||
my $min_ticket_lifetime = -60*5; # allow 5 minutes time drift
|
||||
my $max_ticket_lifetime = 60*60*2; # 2 hours
|
||||
|
||||
my $rsa = Crypt::OpenSSL::RSA->generate_key(2048);
|
||||
|
||||
sub create_ticket {
|
||||
my ($username) = @_;
|
||||
|
||||
return PVE::Ticket::assemble_rsa_ticket($rsa, 'DEMO', $username);
|
||||
}
|
||||
|
||||
sub verify_ticket {
|
||||
my ($ticket, $noerr) = @_;
|
||||
|
||||
return PVE::Ticket::verify_rsa_ticket(
|
||||
$rsa, 'DEMO', $ticket, undef,
|
||||
$min_ticket_lifetime, $max_ticket_lifetime, $noerr);
|
||||
}
|
||||
|
||||
# VNC tickets
|
||||
# - they do not contain the username in plain text
|
||||
# - they are restricted to a specific resource path (example: '/vms/100')
|
||||
sub assemble_vnc_ticket {
|
||||
my ($username, $path) = @_;
|
||||
|
||||
my $secret_data = "$username:$path";
|
||||
|
||||
return PVE::Ticket::assemble_rsa_ticket(
|
||||
$rsa, 'DEMOVNC', undef, $secret_data);
|
||||
}
|
||||
|
||||
sub verify_vnc_ticket {
|
||||
my ($ticket, $username, $path, $noerr) = @_;
|
||||
|
||||
my $secret_data = "$username:$path";
|
||||
|
||||
return PVE::Ticket::verify_rsa_ticket(
|
||||
$rsa, 'DEMOVNC', $ticket, $secret_data, -20, 40, $noerr);
|
||||
}
|
||||
|
||||
# We stack several PVE::RESTHandler classes to create
|
||||
# the API for the novnc-pve console.
|
||||
|
||||
package NodeInfoAPI;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use PVE::RESTHandler;
|
||||
use PVE::JSONSchema qw(get_standard_option);
|
||||
use PVE::RESTEnvironment;
|
||||
use PVE::SafeSyslog;
|
||||
|
||||
use base qw(PVE::RESTHandler);
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
name => 'index',
|
||||
path => '',
|
||||
method => 'GET',
|
||||
permissions => { user => 'all' },
|
||||
description => "Node index.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {},
|
||||
},
|
||||
links => [ { rel => 'child', href => "{name}" } ],
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
|
||||
my $result = [
|
||||
{ name => 'vncshell' },
|
||||
];
|
||||
|
||||
return $result;
|
||||
}});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
name => 'vncshell',
|
||||
path => 'vncshell',
|
||||
method => 'POST',
|
||||
description => "Creates a VNC Shell proxy.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
websocket => {
|
||||
optional => 1,
|
||||
type => 'boolean',
|
||||
description => "use websocket instead of standard vnc.",
|
||||
default => 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
user => { type => 'string' },
|
||||
ticket => { type => 'string' },
|
||||
port => { type => 'integer' },
|
||||
upid => { type => 'string' },
|
||||
},
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
|
||||
my $node = $param->{node};
|
||||
|
||||
# we only implement the websocket based VNC here
|
||||
my $websocket = $param->{websocket} // 1;
|
||||
die "standard VNC not implemented" if !$websocket;
|
||||
|
||||
my $authpath = "/nodes/$node";
|
||||
|
||||
my $restenv = PVE::RESTEnvironment->get();
|
||||
my $user = $restenv->get_user();
|
||||
|
||||
my $ticket = Ticket::assemble_vnc_ticket($user, $authpath);
|
||||
|
||||
my $family = PVE::Tools::get_host_address_family($node);
|
||||
my $port = PVE::Tools::next_vnc_port($family);
|
||||
|
||||
my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
|
||||
'-timeout', 10, '-notls', '-listen', 'localhost',
|
||||
'-c', '/usr/bin/top'];
|
||||
|
||||
my $realcmd = sub {
|
||||
my $upid = shift;
|
||||
|
||||
syslog ('info', "starting vnc proxy $upid\n");
|
||||
|
||||
my $cmdstr = join (' ', @$cmd);
|
||||
syslog ('info', "launch command: $cmdstr");
|
||||
|
||||
eval {
|
||||
foreach my $k (keys %ENV) {
|
||||
next if $k eq 'PATH' || $k eq 'TERM' || $k eq 'USER' || $k eq 'HOME';
|
||||
delete $ENV{$k};
|
||||
}
|
||||
$ENV{PWD} = '/';
|
||||
|
||||
$ENV{PVE_VNC_TICKET} = $ticket; # pass ticket to vncterm
|
||||
|
||||
PVE::Tools::run_command($cmd, errmsg => "vncterm failed");
|
||||
};
|
||||
if (my $err = $@) {
|
||||
syslog('err', $err);
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
my $upid = $restenv->fork_worker('vncshell', "", $user, $realcmd);
|
||||
|
||||
PVE::Tools::wait_for_vnc_port($port);
|
||||
|
||||
return {
|
||||
user => $user,
|
||||
ticket => $ticket,
|
||||
port => $port,
|
||||
upid => $upid,
|
||||
};
|
||||
}});
|
||||
|
||||
__PACKAGE__->register_method({
|
||||
name => 'vncwebsocket',
|
||||
path => 'vncwebsocket',
|
||||
method => 'GET',
|
||||
description => "Opens a weksocket for VNC traffic.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
vncticket => {
|
||||
description => "Ticket from previous call to vncproxy.",
|
||||
type => 'string',
|
||||
maxLength => 512,
|
||||
},
|
||||
port => {
|
||||
description => "Port number returned by previous vncproxy call.",
|
||||
type => 'integer',
|
||||
minimum => 5900,
|
||||
maximum => 5999,
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => "object",
|
||||
properties => {
|
||||
port => { type => 'string' },
|
||||
},
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
|
||||
my $authpath = "/nodes/$param->{node}";
|
||||
|
||||
my $restenv = PVE::RESTEnvironment->get();
|
||||
my $user = $restenv->get_user();
|
||||
|
||||
Ticket::verify_vnc_ticket($param->{vncticket}, $user, $authpath);
|
||||
|
||||
my $port = $param->{port};
|
||||
|
||||
return { port => $port };
|
||||
}});
|
||||
|
||||
|
||||
package NodeAPI;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use PVE::RESTHandler;
|
||||
use PVE::JSONSchema qw(get_standard_option);
|
||||
|
||||
use base qw(PVE::RESTHandler);
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
subclass => "NodeInfoAPI",
|
||||
path => '{node}',
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
name => 'index',
|
||||
path => '',
|
||||
method => 'GET',
|
||||
permissions => { user => 'all' },
|
||||
description => "Cluster node index.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {},
|
||||
},
|
||||
links => [ { rel => 'child', href => "{node}" } ],
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
|
||||
my $res = [
|
||||
{ node => 'elsa' },
|
||||
];
|
||||
|
||||
return $res;
|
||||
}});
|
||||
|
||||
|
||||
package YourAPI;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use PVE::RESTHandler;
|
||||
use PVE::JSONSchema;
|
||||
|
||||
use base qw(PVE::RESTHandler);
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
subclass => "NodeAPI",
|
||||
path => 'nodes',
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
name => 'index',
|
||||
path => '',
|
||||
method => 'GET',
|
||||
permissions => { user => 'all' },
|
||||
description => "Directory index.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
subdir => { type => 'string' },
|
||||
},
|
||||
},
|
||||
links => [ { rel => 'child', href => "{subdir}" } ],
|
||||
},
|
||||
code => sub {
|
||||
my ($resp, $param) = @_;
|
||||
|
||||
my $res = [ { subdir => 'nodes' } ];
|
||||
|
||||
return $res;
|
||||
}});
|
||||
|
||||
|
||||
# This is the REST/HTTPS Server
|
||||
package DemoServer;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use HTTP::Status qw(:constants);
|
||||
use URI::Escape;
|
||||
|
||||
use PVE::APIServer::AnyEvent;
|
||||
use PVE::Exception qw(raise_param_exc);
|
||||
use PVE::RESTEnvironment;
|
||||
|
||||
use base('PVE::APIServer::AnyEvent');
|
||||
|
||||
sub new {
|
||||
my ($this, %args) = @_;
|
||||
|
||||
my $class = ref($this) || $this;
|
||||
|
||||
my $self = $class->SUPER::new(%args);
|
||||
|
||||
PVE::RESTEnvironment->init('pub');
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub auth_handler {
|
||||
my ($self, $method, $rel_uri, $ticket, $token, $peer_host) = @_;
|
||||
|
||||
my $restenv = PVE::RESTEnvironment::get();
|
||||
$restenv->set_user(undef);
|
||||
|
||||
# explicitly allow some calls without authentication
|
||||
if ($rel_uri eq '/access/ticket' &&
|
||||
($method eq 'POST' || $method eq 'GET')) {
|
||||
return; # allow call to create ticket
|
||||
}
|
||||
|
||||
my $userid = Ticket::verify_ticket($ticket);
|
||||
$restenv->set_user($userid);
|
||||
|
||||
return {
|
||||
ticket => $ticket,
|
||||
userid => $userid,
|
||||
};
|
||||
}
|
||||
|
||||
sub rest_handler {
|
||||
my ($self, $clientip, $method, $rel_uri, $auth, $params) = @_;
|
||||
|
||||
my $resp = {
|
||||
status => HTTP_NOT_IMPLEMENTED,
|
||||
message => "Method '$method $rel_uri' not implemented",
|
||||
};
|
||||
|
||||
if ($rel_uri eq '/access/ticket') {
|
||||
if ($method eq 'POST') {
|
||||
if ($params->{username} && $params->{username} eq 'demo' &&
|
||||
$params->{password} && $params->{password} eq 'demo') {
|
||||
return {
|
||||
status => HTTP_OK,
|
||||
data => {
|
||||
ticket => Ticket::create_ticket($params->{username}),
|
||||
},
|
||||
};
|
||||
}
|
||||
return $resp;
|
||||
} elsif ($method eq 'GET') {
|
||||
# this is allowed to display the login form
|
||||
return { status => HTTP_OK, data => {} };
|
||||
} else {
|
||||
return $resp;
|
||||
}
|
||||
}
|
||||
|
||||
my ($handler, $info);
|
||||
|
||||
eval {
|
||||
my $uri_param = {};
|
||||
($handler, $info) = YourAPI->find_handler($method, $rel_uri, $uri_param);
|
||||
return if !$handler || !$info;
|
||||
|
||||
foreach my $p (keys %{$params}) {
|
||||
if (defined($uri_param->{$p})) {
|
||||
raise_param_exc({$p => "duplicate parameter (already defined in URI)"});
|
||||
}
|
||||
$uri_param->{$p} = $params->{$p};
|
||||
}
|
||||
|
||||
$resp = {
|
||||
data => $handler->handle($info, $uri_param),
|
||||
info => $info, # useful to format output
|
||||
status => HTTP_OK,
|
||||
};
|
||||
};
|
||||
if (my $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;
|
||||
}
|
||||
}
|
||||
|
||||
return $resp;
|
||||
}
|
||||
|
||||
|
||||
# The main package creates the socket and runs the server
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Socket qw(IPPROTO_TCP TCP_NODELAY SOMAXCONN);
|
||||
use IO::Socket::IP;
|
||||
use HTTP::Headers;
|
||||
use HTTP::Response;
|
||||
use Data::Dumper;
|
||||
|
||||
use PVE::Tools qw(run_command);
|
||||
use PVE::INotify;
|
||||
use PVE::APIServer::Formatter::Standard;
|
||||
use PVE::APIServer::Formatter::HTML;
|
||||
use PVE::NoVncIndex;
|
||||
|
||||
my $nodename = PVE::INotify::nodename();
|
||||
my $port = 9999;
|
||||
|
||||
my $cert_file = "simple-demo.pem";
|
||||
|
||||
if (! -f $cert_file) {
|
||||
print "generating demo server certificate\n";
|
||||
my $cmd = ['openssl', 'req', '-batch', '-x509', '-newkey', 'rsa:4096',
|
||||
'-nodes', '-keyout', $cert_file, '-out', $cert_file,
|
||||
'-subj', "/CN=$nodename/",
|
||||
'-days', '3650'];
|
||||
run_command($cmd);
|
||||
}
|
||||
|
||||
my $socket = IO::Socket::IP->new(
|
||||
LocalAddr => $nodename,
|
||||
LocalPort => $port,
|
||||
Listen => SOMAXCONN,
|
||||
Proto => 'tcp',
|
||||
GetAddrInfoFlags => 0,
|
||||
ReuseAddr => 1) ||
|
||||
die "unable to create socket - $@\n";
|
||||
|
||||
# we often observe delays when using Nagle algorithm,
|
||||
# so we disable that to maximize performance
|
||||
setsockopt($socket, IPPROTO_TCP, TCP_NODELAY, 1);
|
||||
|
||||
my $accept_lock_fn = "simple-demo.lck";
|
||||
my $lockfh = IO::File->new(">>${accept_lock_fn}") ||
|
||||
die "unable to open lock file '${accept_lock_fn}' - $!\n";
|
||||
|
||||
my $dirs = {};
|
||||
PVE::APIServer::AnyEvent::add_dirs(
|
||||
$dirs, '/novnc/' => '/usr/share/novnc-pve/');
|
||||
|
||||
my $server = DemoServer->new(
|
||||
debug => 1,
|
||||
socket => $socket,
|
||||
lockfile => $accept_lock_fn,
|
||||
lockfh => $lockfh,
|
||||
title => 'Simple Demo API',
|
||||
cookie_name => 'DEMO',
|
||||
logfh => \*STDOUT,
|
||||
tls_ctx => { verify => 0, cert_file => $cert_file },
|
||||
dirs => $dirs,
|
||||
pages => {
|
||||
'/' => sub { get_index($nodename, @_) },
|
||||
},
|
||||
);
|
||||
|
||||
# NOTE: Requests to non-API pages are not authenticated
|
||||
# so you must be very careful here
|
||||
|
||||
my $root_page = <<__EOD__;
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<title>Simple Demo Server</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Simple Demo Server ($nodename)</h1>
|
||||
|
||||
<p>You can browse the API <a href='/api2/html' >here</a>. Please sign
|
||||
in with usrename <b>demo</b> and passwort <b>demo</b>.</p>
|
||||
|
||||
<p>Server console is here: <a href="?console=shell&novnc=1&node=$nodename">Console</a>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
__EOD__
|
||||
|
||||
sub get_index {
|
||||
my ($nodename, $server, $r, $args) = @_;
|
||||
|
||||
my $token = '';
|
||||
|
||||
my ($ticket, $userid);
|
||||
if (my $cookie = $r->header('Cookie')) {
|
||||
#$ticket = PVE::APIServer::Formatter::extract_auth_cookie($cookie, $server->{cookie_name});
|
||||
# $userid = Ticket::verify_ticket($ticket, 1);
|
||||
}
|
||||
|
||||
my $page = $root_page;
|
||||
|
||||
if (defined($args->{console}) && $args->{novnc}) {
|
||||
$page = PVE::NoVncIndex::get_index('en', $userid, $token,
|
||||
$args->{console}, $nodename);
|
||||
}
|
||||
|
||||
my $headers = HTTP::Headers->new(Content_Type => "text/html; charset=utf-8");
|
||||
my $resp = HTTP::Response->new(200, "OK", $headers, $page);
|
||||
|
||||
return $resp;
|
||||
}
|
||||
|
||||
print "demo server listens at: https://$nodename:$port/\n";
|
||||
|
||||
$server->run();
|
195
src/examples/simple-demo.pl
Executable file
195
src/examples/simple-demo.pl
Executable file
@ -0,0 +1,195 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
package DemoServer;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use HTTP::Status qw(:constants);
|
||||
use URI::Escape;
|
||||
|
||||
use PVE::APIServer::AnyEvent;
|
||||
use PVE::Exception qw(raise_param_exc);
|
||||
|
||||
use base('PVE::APIServer::AnyEvent');
|
||||
|
||||
use Digest::MD5;
|
||||
|
||||
my $secret = Digest::MD5::md5_base64($$ . time());
|
||||
|
||||
sub create_ticket {
|
||||
my ($username) = @_;
|
||||
|
||||
my $salt = sprintf("%08x", time());
|
||||
my $data = "$username:$salt";
|
||||
my $sig = Digest::MD5::md5_base64("$data:$secret");
|
||||
return "$username:$salt:$sig";
|
||||
}
|
||||
|
||||
sub verify_ticket {
|
||||
my ($ticket) = @_;
|
||||
|
||||
die "no ticket" if !defined($ticket);
|
||||
my ($userid, $salt, $rest) = split(/:/, $ticket, 3);
|
||||
|
||||
die "invalid ticket" if !defined($salt) || !defined($rest);
|
||||
|
||||
die "invalid unsername" if $userid ne 'demo';
|
||||
|
||||
my $sig = Digest::MD5::md5_base64("$userid:$salt:$secret");
|
||||
|
||||
die "invalid ticket" if $rest ne $sig;
|
||||
|
||||
return $userid;
|
||||
}
|
||||
|
||||
sub auth_handler {
|
||||
my ($self, $method, $rel_uri, $ticket, $token, $peer_host) = @_;
|
||||
|
||||
# explicitly allow some calls without authentication
|
||||
if ($rel_uri eq '/access/ticket' &&
|
||||
($method eq 'POST' || $method eq 'GET')) {
|
||||
return; # allow call to create ticket
|
||||
}
|
||||
|
||||
my $userid = verify_ticket($ticket);
|
||||
|
||||
return {
|
||||
ticket => $ticket,
|
||||
userid => $userid,
|
||||
};
|
||||
}
|
||||
|
||||
sub rest_handler {
|
||||
my ($self, $clientip, $method, $rel_uri, $auth, $params) = @_;
|
||||
|
||||
my $resp = {
|
||||
status => HTTP_NOT_IMPLEMENTED,
|
||||
message => "Method '$method $rel_uri' not implemented",
|
||||
};
|
||||
if ($rel_uri eq '/access/ticket') {
|
||||
if ($method eq 'POST') {
|
||||
if ($params->{username} && $params->{username} eq 'demo' &&
|
||||
$params->{password} && $params->{password} eq 'demo') {
|
||||
return {
|
||||
status => HTTP_OK,
|
||||
data => {
|
||||
ticket => create_ticket($params->{username}),
|
||||
},
|
||||
};
|
||||
}
|
||||
return $resp;
|
||||
} elsif ($method eq 'GET') {
|
||||
# this is allowed to display the login form
|
||||
return { status => HTTP_OK, data => {} };
|
||||
} else {
|
||||
return $resp;
|
||||
}
|
||||
}
|
||||
|
||||
$resp = {
|
||||
data => {
|
||||
method => $method,
|
||||
clientip => $clientip,
|
||||
rel_uri => $rel_uri,
|
||||
auth => $auth,
|
||||
params => $params,
|
||||
},
|
||||
info => { description => "You called API method '$method $rel_uri'" },
|
||||
status => HTTP_OK,
|
||||
};
|
||||
|
||||
return $resp;
|
||||
}
|
||||
|
||||
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Socket qw(IPPROTO_TCP TCP_NODELAY SOMAXCONN);
|
||||
use IO::Socket::IP;
|
||||
use HTTP::Headers;
|
||||
use HTTP::Response;
|
||||
|
||||
use PVE::Tools qw(run_command);
|
||||
use PVE::INotify;
|
||||
use PVE::APIServer::Formatter::Standard;
|
||||
use PVE::APIServer::Formatter::HTML;
|
||||
|
||||
my $nodename = PVE::INotify::nodename();
|
||||
my $port = 9999;
|
||||
|
||||
my $cert_file = "simple-demo.pem";
|
||||
|
||||
if (! -f $cert_file) {
|
||||
print "generating demo server certificate\n";
|
||||
my $cmd = ['openssl', 'req', '-batch', '-x509', '-newkey', 'rsa:4096',
|
||||
'-nodes', '-keyout', $cert_file, '-out', $cert_file,
|
||||
'-subj', "/CN=$nodename/",
|
||||
'-days', '3650'];
|
||||
run_command($cmd);
|
||||
}
|
||||
|
||||
my $socket = IO::Socket::IP->new(
|
||||
LocalAddr => $nodename,
|
||||
LocalPort => $port,
|
||||
Listen => SOMAXCONN,
|
||||
Proto => 'tcp',
|
||||
GetAddrInfoFlags => 0,
|
||||
ReuseAddr => 1) ||
|
||||
die "unable to create socket - $@\n";
|
||||
|
||||
# we often observe delays when using Nagle algorithm,
|
||||
# so we disable that to maximize performance
|
||||
setsockopt($socket, IPPROTO_TCP, TCP_NODELAY, 1);
|
||||
|
||||
my $accept_lock_fn = "simple-demo.lck";
|
||||
my $lockfh = IO::File->new(">>${accept_lock_fn}") ||
|
||||
die "unable to open lock file '${accept_lock_fn}' - $!\n";
|
||||
|
||||
my $server = DemoServer->new(
|
||||
socket => $socket,
|
||||
lockfile => $accept_lock_fn,
|
||||
lockfh => $lockfh,
|
||||
title => 'Simple Demo API',
|
||||
logfh => \*STDOUT,
|
||||
tls_ctx => { verify => 0, cert_file => $cert_file },
|
||||
pages => {
|
||||
'/' => sub { get_index($nodename, @_) },
|
||||
},
|
||||
);
|
||||
|
||||
# NOTE: Requests to non-API pages are not authenticated
|
||||
# so you must be very careful here
|
||||
|
||||
my $root_page = <<__EOD__;
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<title>Simple Demo Server</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Simple Demo Server ($nodename)</h1>
|
||||
|
||||
You can browse the API <a href='/api2/html' >here</a>. Please sign
|
||||
in with usrename <b>demo</b> and passwort <b>demo</b>.
|
||||
|
||||
</body>
|
||||
</html>
|
||||
__EOD__
|
||||
|
||||
sub get_index {
|
||||
my ($nodename, $server, $r, $args) = @_;
|
||||
|
||||
my $headers = HTTP::Headers->new(Content_Type => "text/html; charset=utf-8");
|
||||
my $resp = HTTP::Response->new(200, "OK", $headers, $root_page);
|
||||
|
||||
}
|
||||
|
||||
print "demo server listens at: https://$nodename:$port/\n";
|
||||
|
||||
$server->run();
|
Loading…
Reference in New Issue
Block a user