ZoneMinder / zoneminder

ZoneMinder is a free, open source Closed-circuit television software application developed for Linux which supports IP, USB and Analog cameras.
http://www.zoneminder.com/
GNU General Public License v2.0
5.11k stars 1.22k forks source link

ZM_BASE_URL doesn't actually set base url for most assets #2127

Closed thoraxe closed 6 years ago

thoraxe commented 6 years ago

ZoneMinder Version (zmaudit.pl -v): 1.31.44

Linux Distribution and Version (cat /etc/os-release or cat /etc/redhat-release):

root@zm:/usr/share/zoneminder/www# cat /etc/os-release 
NAME="Ubuntu"
VERSION="16.04.4 LTS (Xenial Xerus)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 16.04.4 LTS"
VERSION_ID="16.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
VERSION_CODENAME=xenial
UBUNTU_CODENAME=xenial
root@zm:/usr/share/zoneminder/www# grep BASE index.php 
define( 'ZM_BASE_PROTOCOL', $protocol );
// define( "ZM_BASE_URL", $protocol.'://'.$_SERVER['HTTP_HOST'] );
define( 'ZM_BASE_URL', '/zm/' );
define( 'ZM_BASE_PATH', dirname( $_SERVER['REQUEST_URI'] ) );

segment of index rendered:

curl http://localhost:8080/zm/

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <title>ZM - Console</title>

  <link rel="icon" type="image/ico" href="graphics/favicon.ico"/>
  <link rel="shortcut icon" href="graphics/favicon.ico"/>
  <link rel="stylesheet" href="css/reset.css" type="text/css"/>
  <link rel="stylesheet" href="css/overlay.css" type="text/css"/>
  <link rel="stylesheet" href="css/bootstrap.min.css" type="text/css"/>

<link rel="stylesheet" href="skins/classic/css/base/skin.css" type="text/css"/>
<link rel="stylesheet" href="skins/classic/css/classic/skin.css" type="text/css"/>
<link rel="stylesheet" href="skins/classic/css/base/views/console.css" type="text/css"/>
<link rel="stylesheet" href="skins/classic/css/classic/views/console.css" type="text/css"/>
<link rel="stylesheet" href="skins/classic//js/dateTimePicker/jquery-ui-timepicker-addon.css" type="text/css"/>
<link rel="stylesheet" href="skins/classic//js/jquery-ui-1.12.1/jquery-ui.structure.min.css" type="text/css"/>
<link rel="stylesheet" href="skins/classic//css/classic/jquery-ui-theme.css" type="text/css"/>  <!--Chosen can't be cache-busted because it loads sprites by relative path-->
<link rel="stylesheet" href="skins/classic/js/chosen/chosen.min.css" type="text/css"/>

  <script src="tools/mootools/mootools-core.js"></script>
  <script src="tools/mootools/mootools-more.js"></script>
  <script src="js/mootools.ext.js"></script>
  <script src="skins/classic/js/jquery.js"></script>
  <script src="skins/classic/js/jquery-ui-1.12.1/jquery-ui.js"></script>
  <script src="skins/classic/js/bootstrap.min.js"></script>
  <script src="skins/classic/js/chosen/chosen.jquery.min.js"></script>
  <script src="skins/classic/js/dateTimePicker/jquery-ui-timepicker-addon.js"></script>

If ZM_BASE_URL is supposed to do something here, it definitely doesn't.

I'm using https://hub.docker.com/r/quantumobject/docker-zoneminder/ but I will try with the "official" image shortly.

thoraxe commented 6 years ago

Actually, I took a look and found:

https://github.com/ZoneMinder/zoneminder/blob/master/web/skins/classic/includes/functions.php#L113-L120

Since none of these are prefixed with ZM_BASE_URL, these assets would never be loaded relative to it.

connortechnology commented 6 years ago

I'm not sure what the issue is. When specifying a url as skins/classic/js/jquery.js, the browser will auto-append the http://servername/ part. These defines are generated by the url in the query parameter. As such they should work out the same.

I gather you are actually seeing failed requests, but so far havn't given enough info for us to figure out why.

thoraxe commented 6 years ago

@connortechnology can you show me in the code where ZM_BASE_URL ends up as part of the servername and/or part of the asset URL?

When I set ZM_BASE_URL to /zm/ I still get relative URLs (eg: /skins/...)

connortechnology commented 6 years ago

There should not be a / in front of skins (I think). I think I see what is wrong. I'll get back to you.

ZM_BASE_URL is really not used

thoraxe commented 6 years ago

ZM_BASE_URL is the only trivial way to make ZM work behind a reverse proxy because of the default Apache configuration of /zm/ with an alias.

The other option is to change the default Apache configuration to work a little better behind things like reverse proxies.

connortechnology commented 6 years ago

I use ZM behind a reverse proxy. It works fine. The /zm is not required, it's just the default config. You can do without it, or make it something else.

There is some magic in skins/classic/includes/functions.php that uses links to create a unique url for each asset, so that on upgrades, old versions will not be cached. So the resulting url for any given asset should become cache/skins_classic_css_something_something-timestamp.css . The lack of a / in the url so should append the url to whatever the current url is. So if the console url is https://hostname/zm/index.php then the result would be https://hostname/zm/cache/skins_classic_css_soemthing_something-timestamp.css

There needs to be an alias in your apache config pointing either /zm/cache or /cache (depending on which is appropriate) to /var/cache/zoneminder/cache (or wherever the actual cache dir is on your distro).

thoraxe commented 6 years ago

Yeah that's definitely not the behavior I observed when I set ZM_BASE_URL. I'll have to spin it up again later to show you.

Is ZM_BASE_URL supposed to be a full URL (https://hostname/foo/bar)?

While /zm is not required, it's the default. Having to start mucking with the Apache config just to get reverse proxy to work is... not fun. Can you perhaps share your reverse proxy config (I know it's separate from this issue which I still think is valid)?

connortechnology commented 6 years ago

So on my external web server:

    ServerName     pseudo.connortechnology.com
    ServerAlias    pseudo
    ServerAlias    pseudo.home.connortechnology.com

  SSLProxyEngine On
  #RequestHeader set Front-End-Https "On"
  ProxyPass  /   https://pseudo.connortechnology.com/   timeout=36000
  ProxyPassReverse   /   https://pseudo.connortechnology.com/

  SSLCertificateFile /etc/letsencrypt/live/pseudo.connortechnology.com/fullchain.pem
  SSLCertificateKeyFile /etc/letsencrypt/live/pseudo.connortechnology.com/privkey.pem
  Include /etc/letsencrypt/options-ssl-apache.conf

And on my internal web server:

DocumentRoot    /usr/share/zoneminder/www
  ServerName      pseudo.connortechnology.com
ErrorLog        /var/log/apache2/error.log

 # Order matters. This alias must come first.
    Alias /zm/cache "/var/cache/zoneminder/cache"
    Alias /cache "/var/cache/zoneminder/cache"
    <Directory "/var/cache/zoneminder/cache">
        Options -Indexes +FollowSymLinks
        AllowOverride None
        <IfModule mod_authz_core.c>
           # Apache 2.4
           Require all granted
        </IfModule>
        <IfModule !mod_authz_core.c>
            # Apache 2.2
            Order deny,allow
            Allow from all
        </IfModule>
    </Directory>
ScriptAlias /zm/cgi-bin/ /usr/lib/zoneminder/cgi-bin/
ScriptAlias /cgi-bin/ /usr/lib/zoneminder/cgi-bin/
<Directory "/usr/lib/zoneminder/cgi-bin">
  AllowOverride None
  Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
  Require all granted
  Satisfy Any
  Order allow,deny
  Allow from all
</Directory>

Alias /zm /usr/share/zoneminder/www

<Directory /usr/share/zoneminder/www>
  php_flag register_globals off
  Options +Indexes +FollowSymLinks
  AllowOverride All
  <IfModule mod_dir.c>
    DirectoryIndex index.php
  </IfModule>
  Require all granted
  Satisfy Any
  Order allow,deny
  Allow from all

</Directory>

 # For better visibility, the following directives have been migrated from the
    # default .htaccess files included with the CakePHP project.
    # Parameters not set here are inherited from the parent directive above.
    <Directory "/usr/share/zoneminder/www/api">
       RewriteEngine on
       RewriteRule ^$ app/webroot/ [L]
       RewriteRule (.*) app/webroot/$1 [L]
       RewriteBase /zm/api
    </Directory>

    <Directory "/usr/share/zoneminder/www/api/app">
       RewriteEngine on
       RewriteRule ^$ webroot/ [L]
       RewriteRule (.*) webroot/$1 [L]
       RewriteBase /zm/api
    </Directory>

    <Directory "/usr/share/zoneminder/www/api/app/webroot">
        RewriteEngine On
        RewriteCond %{REQUEST_FILENAME} !-d
        RewriteCond %{REQUEST_FILENAME} !-f
        RewriteRule ^ index.php [L]
        RewriteBase /zm/api
    </Directory>

SSLCertificateFile /etc/letsencrypt/live/pseudo.connortechnology.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/pseudo.connortechnology.com/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf

Now I don't use the /zm, but my config responds to /zm just as easily as / mostly to simplify api config.

knight-of-ni commented 6 years ago

ZM_BASE_URL is a leftover from the days when every url generated by zoneminder was absolute. So yes, this variable was designed to be a full url, which isn't what you want if you are doing reverse proxy.

If you are trying to change the url suffix from /zm to something else, then writing your own apache config file is the way to do it.

knight-of-ni commented 6 years ago

Last night it didn't immediately click why you were going down this path. You are running zoneminder inside docker but you want to access it from your host server, using a normal url, right?

Accomplishing this does not require any changes to zoneminder's apache configuration, the script alias, nor changes to ZM_BASE_URL or anything else. Just run ZoneMinder as it is in docker, then properly set up the reverse proxy on the server that will do the proxy-ing. That's it.

You are going to configure a proxypass and proxypassreverse directive. Configure it to redirect /somearbitraryurl -> http://{docker ip}:{docker port}/zm

somearbitraryurl can just be / if that is what you want. Notice, that does not affect the suffix /zm in the target url.

thoraxe commented 6 years ago

@knnniggett slightly different.

When using podman (alternative to the docker daemon) and when a container is not using host networking, you get a local system IP address but it's not available off the host. To reduce attack vector (why I'm thinking about this on my home fileserver...) you can run a simple nginx proxy that is connected to the host network, and then use that to proxy various services that run on that host. Plus you can do stuff like automatic letsencrypt with ssl re-encrypt and blah blah.

When you just have nginx proxy /zm to the container running ZM with the default Apache configuration running at /zm, you end up with the wrong paths to the assets. The HTML that comes back is saying /skins/... and not /zm/skins and so nginx basically doesn't know what to do with that and you get 404s on the assets.

I probably won't get a chance to create a reproducer until this weekend, but we can close this ticket if ZM_BASE_URL is working as intended. I will just have to change the Apache config.

stale[bot] commented 6 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

undigo commented 5 years ago

debian OS pi@server: $ lsb_release -a No LSB modules are available. Distributor ID: Raspbian Description: Raspbian GNU/Linux 10 (buster) Release: 10 Codename: buster

to correct API access on website to see version with page http://localhost/zm/api/host/getVersion.json

After installing zoneminder 1.32.3 from debian repo on last Raspbian "Buster" (authentification activate) The apache conf in /etc/apache2/conf-enabled/zonminder.conf have only directive for api folder :

<Directory "/usr/share/zoneminder/www/api"> AllowOverride All with 404 error to see .../zm/api/host/getVersion.json

TODO : Edit /etc/apache2/conf-enabled/zonminder.conf and add these lines , thanks @connortechnology 👍

<Directory "/usr/share/zoneminder/www/api"> RewriteEngine on RewriteRule ^$ app/webroot/ [L] RewriteRule (.*) app/webroot/$1 [L] RewriteBase /zm/api

<Directory "/usr/share/zoneminder/www/api/app">
   RewriteEngine on
   RewriteRule ^$ webroot/ [L]
   RewriteRule (.*) webroot/$1 [L]
   RewriteBase /zm/api
</Directory>

<Directory "/usr/share/zoneminder/www/api/app/webroot">
    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^ index.php [L]
    RewriteBase /zm/api
</Directory>

restart apache service apache2 restart

and refresh page to see API version

{ "version": "1.32.3", "apiversion": "1.0" }