pkp / pkp-lib

The library used by PKP's applications OJS, OMP and OPS, open source software for scholarly publishing.
https://pkp.sfu.ca
GNU General Public License v3.0
306 stars 445 forks source link

Running instance behind proxy with ssl #1504

Closed ndege closed 2 years ago

ndege commented 8 years ago

... currently we run an OJS 3.0 instance behind a proxy with ssl. Getting in trouble with browser warnings if the OJS includes css and javascript not correctly with https but http. I'm not sure if I detect the correct position at ./lib/pkp/classes/core/PKPRequest.inc.php -> getProtocol() for building the baseUrl with whom scripts will be included, but there seems no hint to assess for example HTTP_X_FORWARDED_PROTO to get the proper protocol behind a proxy?

Don't believe that this would be not an issue so far and like to ask if there exists already a solution? If not I would like to ask for a feature request?

Best, Frank

asmecher commented 8 years ago

Hi @ndege -- we did run into this a while back, particularly in CSS. See for example this issue: https://github.com/pkp/pkp-lib/issues/1391

What URLs are showing up as mixed for you? Javascript, CSS, CDN usage, or something else (font assets)?

ndege commented 8 years ago

Hi Alec, thanks for your answer! We solved this a bit quick and dirty at first to give the local server a self-signed certificate. So now it works but of course it isn't an elegant way.

Javascript and CSS files will be included by http protocol. I try various settings but with code inspecting I found no proper solution that ojs knows that the proxy speak https. For me it's a question which lies a bit between the force_ssl and enable_cdn settings. The first to use ssl consequently, the second for delivering relative paths. But I'm not so into the code for now considering the best starting point to solve it.

Best, Frank

HighwayStar commented 7 years ago

I have same issue trying to run ojs from current master behinnd ssl nginx.

Got following console log in chromium on main page

Mixed Content: The page at 'https://journal.local/index.php/jstp' was loaded over HTTPS, but requested an insecure stylesheet 'http://journal.local/index.php/jstp/$$$call$$$/page/page/css?name=stylesheet'. This request has been blocked; the content must be served over HTTPS.
Mixed Content: The page at 'https://journal.local/index.php/jstp' was loaded over HTTPS, but requested an insecure stylesheet 'http://journal.local/plugins/generic/orcidProfile/css/orcidProfile.css'. This request has been blocked; the content must be served over HTTPS.
Mixed Content: The page at 'https://journal.local/index.php/jstp' was loaded over a secure connection, but contains a form which targets an insecure endpoint 'http://journal.local/index.php/jstp/search/search'. This endpoint should be made available over a secure connection.
Mixed Content: The page at 'https://journal.local/index.php/jstp' was loaded over HTTPS, but requested an insecure image 'http://journal.local/plugins/generic/webFeed/templates/images/atom10_logo.gif'. This content should also be served over HTTPS.
Mixed Content: The page at 'https://journal.local/index.php/jstp' was loaded over HTTPS, but requested an insecure image 'http://journal.local/plugins/generic/webFeed/templates/images/rss20_logo.gif'. This content should also be served over HTTPS.
Mixed Content: The page at 'https://journal.local/index.php/jstp' was loaded over HTTPS, but requested an insecure image 'http://journal.local/plugins/generic/webFeed/templates/images/rss10_logo.gif'. This content should also be served over HTTPS.
Mixed Content: The page at 'https://journal.local/index.php/jstp' was loaded over HTTPS, but requested an insecure image 'http://journal.local/templates/images/ojs_brand.png'. This content should also be served over HTTPS.
Mixed Content: The page at 'https://journal.local/index.php/jstp' was loaded over HTTPS, but requested an insecure image 'http://journal.local/lib/pkp/templates/images/pkp_brand.png'. This content should also be served over HTTPS.
Mixed Content: The page at 'https://journal.local/index.php/jstp' was loaded over HTTPS, but requested an insecure script 'http://journal.local/lib/pkp/lib/components/jquery/jquery.js'. This request has been blocked; the content must be served over HTTPS.
Mixed Content: The page at 'https://journal.local/index.php/jstp' was loaded over HTTPS, but requested an insecure script 'http://journal.local/lib/pkp/lib/components/jquery-ui/jquery-ui.js'. This request has been blocked; the content must be served over HTTPS.
Mixed Content: The page at 'https://journal.local/index.php/jstp' was loaded over HTTPS, but requested an insecure script 'http://journal.local/lib/pkp/js/lib/jquery/plugins/jquery.tag-it.js'. This request has been blocked; the content must be served over HTTPS.
Mixed Content: The page at 'https://journal.local/index.php/jstp' was loaded over HTTPS, but requested an insecure script 'http://journal.local/plugins/themes/default/js/main.js'. This request has been blocked; the content must be served over HTTPS.

I have following frontend nginx config

upstream ojs-ssl {
  server 10.0.5.29:8086; 
}

server {
  listen *:443 ssl;         # e.g., listen 192.168.1.1:80; In most cases *:80 is a good idea
  server_name journal.local;     # e.g., server_name source.example.com;

    ssl_certificate /etc/letsencrypt/live/journal.local/fullchain.pem;                                                         
    ssl_certificate_key /etc/letsencrypt/live/journal.local/privkey.pem;                                                       

        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;                                                                                         
        ssl_prefer_server_ciphers on;                                                                                                
        ssl_dhparam /etc/nginx/ssl/dhparam.pem;                                                                                      
        ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';                                                             
        ssl_session_timeout 1d;                                                                                                      
        ssl_session_cache shared:SSL:10m;                                                                                            
        ssl_stapling on;                                                                                                             
        ssl_stapling_verify on;                                                                                                      
        add_header Strict-Transport-Security max-age=15768000;                                                                       

  server_tokens off;     # don't show the version number, a security best practice                                                   
#wosign options                                                                                                                      
  # individual nginx logs for this redmine vhost                                                                                     
  access_log  /var/log/nginx/ojs_access.log;                                                                                         
  error_log   /var/log/nginx/ojs_error.log;                                                                                          
  client_max_body_size 50m;                                                                                                          

  # if a file, which is not found in the root folder is requested,                                                                   
  # then the proxy pass the request to the upsteam (gitlab unicorn)                                                                  
  location / {
    proxy_read_timeout 300; # https://github.com/gitlabhq/gitlabhq/issues/694
    proxy_connect_timeout 300; # https://github.com/gitlabhq/gitlabhq/issues/694
    proxy_redirect     off;

    proxy_set_header   X-Forwarded-Proto $scheme;
    proxy_set_header   Host              $http_host;
    proxy_set_header   X-Real-IP         $remote_addr;
    proxy_set_header    X-Forwarded-For $remote_addr;
    proxy_set_header    X-Forwarded-Protocol  $scheme;
    proxy_set_header    X-Forwarded-Ssl    on;
    proxy_set_header    X-Url-Scheme       $scheme;

    proxy_pass http://ojs-ssl;
  }

}

and following backend (non-ssl) nginx

server {
    listen       80 default;
    server_name  ojs.local;

    access_log /var/log/nginx/scripts.log scripts;
    root /srv/ojs;
    index index.php
    client_max_body_size  100M;

  location ~ /\.ht {
    deny  all;
  }

  location ~ ^/cache(.*)$ {                                                                                                          
    deny all;                                                                                                                        
  }                                                                                                                                  
  location ~ \.php {                                                                                                                 
    fastcgi_split_path_info ^(.*?\.php)(/.*)$;                                                                                       
    fastcgi_pass   127.0.0.1:9000;                                                                                                   
    include fastcgi_params;                                                                                                          
    fastcgi_index  index.php;                                                                                                        
    fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;                                                              
    fastcgi_param  PATH_INFO $fastcgi_path_info;                                                                                     
  }                                                                                                                                  

  location ~* ^.+\.(css|js|xml|jpg|jpeg|gif|png|ico|swf|pdf|doc|xls|tiff|tif|txt|shtml|cgi|bat|pl|dll|asp|exe|class|htm|html|md)$ {  
    access_log      off;                                                                                                             
    expires         30d;                                                                                                             
    try_files $uri =404;                                                                                                             
  }                                                                                                                                  

  location ~* ^.+\.(avi|mpg|mpeg|mov|wmv|mp3|mp4|m4a|flv|wav|midi|zip|gz|rar)$ {                                                     
    expires         30d;                                                                                                             
    try_files $uri =404;                                                                                                             
  }                                                                                                                                  

}

ojs config:

base_url = "https://journal.local"
enable_cdn = Off
HighwayStar commented 7 years ago

I found that problem caused by lofic inside getBaseUrl() function

        function getBaseUrl($allowProtocolRelative = false) {
                $_this =& PKPRequest::_checkThis();

                $serverHost = $_this->getServerHost(false);
                if ($serverHost !== false) {
                        // Auto-detection worked.
                        if ($allowProtocolRelative) {
                                $baseUrl = '//' . $_this->getServerHost() . $_this->getBasePath();
                        } else {
                                $baseUrl = $_this->getProtocol() . '://' . $_this->getServerHost() . $_this->getBasePath();
                        }
                } else {
                        // Auto-detection didn't work (e.g. this is a command-line call); use configuration param
                        $baseUrl = Config::getVar('general', 'base_url');
                }
                HookRegistry::call('Request::getBaseUrl', array(&$baseUrl));
                return $baseUrl;
        }

allowProtocolRelative now always false, (I cant find use of this function in ojs with parameter spicified),.

If set default parameter $allowProtocolRelative = false ssl works as expected. Are there any side effects?

asmecher commented 7 years ago

The $allowProcotolRelative variable is called with a true value in the Less compilation code (PKPTemplateManager::compileLess). This is necessary because the same cached CSS is served up regardless of whether the site is accessed from HTTP or HTTPS.

arturluizbr commented 5 years ago

This could be solved with a config variable, even the name "allowProtocolRelative" sounds like a config variable.

Btw, i had this problem today and we are in a dilemma, create a function in OJS or change PKP-LIB code...

OJS - Request.inc.php

[...]
function getBaseUrl(){
    return parent::getBaseUrl(true);
}
[...]

We tried to create a Hook in "Request::getBaseUrl" scope, but it seems that plugin hook can't reach early calls to this method.

asmecher commented 5 years ago

@arturluizbr, can you describe where you're having trouble with mixed http/https URLs? IMO using protocol--relative as widely as possible is a good solution.

arturluizbr commented 5 years ago

@arturluizbr, can you describe where you're having trouble with mixed http/https URLs? IMO using protocol--relative as widely as possible is a good solution.

The link between our proxy and our OJS instance is made using http, and the proxy serves it as https, since OJS rewrite url to use http, so assets can't be loaded in browser. When i tried to set OJS to use force_ssl, an infinite loop happens.

Probably if we had more time to dig in proxy settings we could find something to fix this issue, but i think the application should have a better way to handle protocols, and since this funcionality (allowProtocolRelative) is already implemented, it should be available in config.

asmecher commented 5 years ago

@arturluizbr, the infinite-loop behavior with force_ssl makes sense when using a proxy to transform http to https, as OJS will detect a http URL in your scenario and attempt to redirect to https.

Have you tried setting the base_url[...] parameters in config.inc.php? Those can be used to force OJS to generate specific URLs, rather than auto-detecting from the CGI parameters. You'll need one for each journal, plus one for base_url[index] (the site-wide context).

arturluizbr commented 5 years ago

@asmecher i tried that too, it worked, but it would cause an additional operational work when creating new journals in our IT department (we don't have nearly half as needed team to operate all of our university systems).

Related to base_url, i think it should be available in each journal settings not in a config file, like the Wordpress Multisite does.

That way we could pass the responsibility to administrate the system to end user department and restrict our operation to tech problems only.

asmecher commented 5 years ago

Moving config options to the database is suggested here: https://github.com/pkp/pkp-lib/issues/4687 However, one impediment to doing that with URL generation settings is that it would be very easy to change settings in a way that made the settings forms inaccessible, so I'm not sure the base_url[...] settings are good candidates for this.

arturluizbr commented 5 years ago

However, one impediment to doing that with URL generation settings is that it would be very easy to change settings in a way that made the settings forms inaccessible, so I'm not sure the base_url[...] settings are good candidates for this.

That's true. =/

Maybe sensitive config could be isolated from each journal settings page to a global admin's only settings page.

peterstadler commented 4 years ago

Since we've been bitten by that same issue I wanted to share our remedy. We fixed it by adding SetEnvIf X-Forwarded-Proto "^https$" HTTPS=on to the apache config (running OJS) which will properly inject that HTTPS key to the $_SERVER needed at https://github.com/pkp/pkp-lib/blob/c5f40d7d390d0aedb9a4f9063914652aa6e3dc6c/classes/core/PKPRequest.inc.php#L351

Yet, I think the 'proper' way would be to check that X-Forwarded-Proto Header directly as @ndege already suggested in the initial issue description:

/lib/pkp/classes/core/PKPRequest.inc.php -> getProtocol() for building the baseUrl with whom scripts will be included, but there seems no hint to assess for example HTTP_X_FORWARDED_PROTO to get the proper protocol behind a proxy?

alexm commented 3 years ago

Since we've been bitten by that same issue I wanted to share our remedy. We fixed it by adding SetEnvIf X-Forwarded-Proto "^https$" HTTPS=on to the apache config (running OJS) which will properly inject that HTTPS key to the $_SERVER

Tried that, even SetEnv HTTPS on, to no avail. $_SERVER['HTTPS'] is NULL in both cases :thinking:

NateWr commented 2 years ago

I'm going to close this as there doesn't seem to be a clear bug report for us to investigate. However, it seems to be a useful resource for people struggling with proxy configs over the years.

Feel free to continue posting suggestions. But please file a new issue if you encounter a bug or want to make a concrete proposal about how OJS/OMP/OPS should change.