Skip to content

Commit

Permalink
Support deployment behind nginx reverse proxy
Browse files Browse the repository at this point in the history
  • Loading branch information
Luca Bassi committed Apr 4, 2024
1 parent bb5aa0a commit 5c05b0f
Show file tree
Hide file tree
Showing 9 changed files with 264 additions and 8 deletions.
61 changes: 61 additions & 0 deletions doc/nginx-reverse-proxy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Use nginx as a reverse proxy

It is possible to deploy StoRM WebDAV using nginx as a reverse proxy.

The main pros of this type of deployment are:

- use nginx to manage TLS termination
- delegate VOMS proxy authentication to [ngx_http_voms_module](https://baltig.infn.it/cnafsd/ngx_http_voms_module)
- improve performance of downloads by using nginx to handle GET requests

## How to deploy StoRM WebDAV using nginx

Install nginx and [ngx_http_voms_module](https://baltig.infn.it/cnafsd/ngx_http_voms_module) on your server.

Change the configuration of nginx to:

- enable the client certificates
- set the correct headers for VOMS authentication
- add an internal endpoint to which to redirect GET requests

In your `application.yml` configuration set `storm.nginx-reverse-proxy` to `true`.

Example nginx configuration:

```
server {
location /internal-get {
internal;
alias /;
sendfile on;
tcp_nopush on;
keepalive_timeout 65;
tcp_nodelay on;
}
location / {
proxy_pass http://127.0.0.1:8086;
proxy_set_header X-VOMS-voms_user $voms_user;
proxy_set_header X-VOMS-ssl_client_ee_s_dn $ssl_client_ee_s_dn;
proxy_set_header X-VOMS-voms_user_ca $voms_user_ca;
proxy_set_header X-VOMS-ssl_client_ee_i_dn $ssl_client_ee_i_dn;
proxy_set_header X-VOMS-voms_fqans $voms_fqans;
proxy_set_header X-VOMS-voms_server $voms_server;
proxy_set_header X-VOMS-voms_server_ca $voms_server_ca;
proxy_set_header X-VOMS-voms_vo $voms_vo;
proxy_set_header X-VOMS-voms_server_uri $voms_server_uri;
proxy_set_header X-VOMS-voms_not_before $voms_not_before;
proxy_set_header X-VOMS-voms_not_after $voms_not_after;
proxy_set_header X-VOMS-voms_generic_attributes $voms_generic_attributes;
proxy_set_header X-VOMS-voms_serial $voms_serial;
}
listen [::]:8443 ssl http2;
listen 8443 ssl http2;
ssl_certificate /etc/grid-security/hostcert.pem;
ssl_certificate_key /etc/grid-security/hostkey.pem;
ssl_client_certificate /etc/pki/ca-trust/extracted/pem/tls-ca-bundle-all.pem;
ssl_verify_client optional;
ssl_verify_depth 10;
client_max_body_size 0;
error_page 497 https://$host:8443$request_uri;
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* Copyright (c) Istituto Nazionale di Fisica Nucleare, 2014-2023.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.italiangrid.storm.webdav.authz;

public interface VOMSConstants {

String VOMS_USER_HEADER = "X-VOMS-voms_user";
String SSL_CLIENT_EE_S_DN_HEADER = "X-VOMS-ssl_client_ee_s_dn";
String VOMS_USER_CA_HEADER = "X-VOMS-voms_user_ca";
String SSL_CLIENT_EE_I_DN_HEADER = "X-VOMS-ssl_client_ee_i_dn";
String VOMS_FQANS_HEADER = "X-VOMS-voms_fqans";
String VOMS_VO_HEADER = "X-VOMS-voms_vo";
String VOMS_SERVER_URI_HEADER = "X-VOMS-voms_server_uri";
String VOMS_NOT_BEFORE_HEADER = "X-VOMS-voms_not_before";
String VOMS_NOT_AFTER_HEADER = "X-VOMS-voms_not_after";
String VOMS_GENERIC_ATTRIBUTES_HEADER = "X-VOMS-voms_generic_attributes";
String VOMS_SERIAL_HEADER = "X-VOMS-voms_serial";

String VOMS_DATE_FORMAT = "yyyyMMddHHmmss'Z'";

String VOMS_GENERIC_ATTRIBUTES_REGEX = "n=(\\S*) v=(\\S*) q=(\\S*)";

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* Copyright (c) Istituto Nazionale di Fisica Nucleare, 2014-2023.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.italiangrid.storm.webdav.authz;

import javax.security.auth.x500.X500Principal;
import javax.servlet.http.HttpServletRequest;

import org.springframework.security.authentication.AuthenticationManager;

public class VOMSNginxFilter extends VOMSAuthenticationFilter implements VOMSConstants {

public VOMSNginxFilter(AuthenticationManager mgr) {
super(mgr);
}

@Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
if (request.getHeader(VOMS_USER_HEADER) != null
&& request.getHeader(SSL_CLIENT_EE_S_DN_HEADER) != null) {
return new X500Principal(request.getHeader(SSL_CLIENT_EE_S_DN_HEADER)).getName();
}
return null;
}

@Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
return new Object();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,38 +15,50 @@
*/
package org.italiangrid.storm.webdav.authz;

import java.math.BigInteger;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.text.ParseException;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.regex.Matcher;

import javax.security.auth.x500.X500Principal;
import javax.servlet.http.HttpServletRequest;

import org.italiangrid.storm.webdav.authz.vomap.VOMapDetailsService;
import org.italiangrid.voms.VOMSAttribute;
import org.italiangrid.voms.VOMSGenericAttribute;
import org.italiangrid.voms.ac.VOMSACValidator;
import org.italiangrid.voms.ac.impl.VOMSAttributesImpl;
import org.italiangrid.voms.ac.impl.VOMSGenericAttributeImpl;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.core.GrantedAuthority;

import com.google.common.collect.Sets;

import eu.emi.security.authn.x509.proxy.ProxyUtils;

public class VOMSPreAuthDetailsSource
implements AuthenticationDetailsSource<HttpServletRequest, VOMSAuthenticationDetails> {
public class VOMSPreAuthDetailsSource implements
AuthenticationDetailsSource<HttpServletRequest, VOMSAuthenticationDetails>, VOMSConstants {

private final AuthorizationPolicyService policyService;
private final VOMSACValidator validator;
private final VOMapDetailsService voMapDetailsService;
private final boolean nginxReverseProxy;

public VOMSPreAuthDetailsSource(VOMSACValidator vomsValidator,
AuthorizationPolicyService policyService, VOMapDetailsService voMapDetailsService) {
AuthorizationPolicyService policyService, VOMapDetailsService voMapDetailsService,
boolean nginxReverseProxy) {
this.policyService = policyService;
this.validator = vomsValidator;
this.voMapDetailsService = voMapDetailsService;
this.nginxReverseProxy = nginxReverseProxy;
}

@Override
Expand Down Expand Up @@ -116,6 +128,64 @@ protected Optional<X509SubjectAuthority> getSubjectAuthority(HttpServletRequest
}

protected List<VOMSAttribute> getAttributes(HttpServletRequest request) {
if (nginxReverseProxy) {
VOMSAttributesImpl attrs = new VOMSAttributesImpl();
// voms_user and voms_user_ca are present only when a VOMS proxy is used
// After checking that they are present, use ssl_client_ee_*_dn that are formatted according
// to RFC 2253
if (request.getHeader(VOMS_USER_HEADER) != null
&& request.getHeader(SSL_CLIENT_EE_S_DN_HEADER) != null) {
attrs.setHolder(new X500Principal(request.getHeader(SSL_CLIENT_EE_S_DN_HEADER)));
}
if (request.getHeader(VOMS_USER_CA_HEADER) != null
&& request.getHeader(SSL_CLIENT_EE_I_DN_HEADER) != null) {
attrs.setIssuer(new X500Principal(request.getHeader(SSL_CLIENT_EE_I_DN_HEADER)));
}
if (request.getHeader(VOMS_FQANS_HEADER) != null) {
attrs.setFQANs(Arrays.asList(request.getHeader(VOMS_FQANS_HEADER).split(",")));
}
if (request.getHeader(VOMS_VO_HEADER) != null) {
attrs.setVO(request.getHeader(VOMS_VO_HEADER));
}
if (request.getHeader(VOMS_SERVER_URI_HEADER) != null) {
String[] splittedServerUri = request.getHeader(VOMS_SERVER_URI_HEADER).split(":");
attrs.setHost(splittedServerUri[0]);
attrs.setPort(Integer.parseInt(splittedServerUri[1]));
}
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(VOMS_DATE_FORMAT);
if (request.getHeader(VOMS_NOT_BEFORE_HEADER) != null) {
try {
attrs.setNotBefore(simpleDateFormat.parse(request.getHeader(VOMS_NOT_BEFORE_HEADER)));
} catch (ParseException e) {
}
}
if (request.getHeader(VOMS_NOT_AFTER_HEADER) != null) {
try {
attrs.setNotAfter(simpleDateFormat.parse(request.getHeader(VOMS_NOT_AFTER_HEADER)));
} catch (ParseException e) {
}
}
if (request.getHeader(VOMS_GENERIC_ATTRIBUTES_HEADER) != null) {
List<VOMSGenericAttribute> generic_attrs = Collections.emptyList();
Pattern pattern = Pattern.compile(VOMS_GENERIC_ATTRIBUTES_REGEX);
for (String genericAttribute : request.getHeader(VOMS_GENERIC_ATTRIBUTES_HEADER)
.split(",")) {
Matcher matcher = pattern.matcher(genericAttribute);
if (matcher.find()) {
VOMSGenericAttributeImpl generic_attr = new VOMSGenericAttributeImpl();
generic_attr.setName(matcher.group(1));
generic_attr.setValue(matcher.group(2));
generic_attr.setContext(matcher.group(3));
generic_attrs.add(generic_attr);
}
}
attrs.setGenericAttributes(generic_attrs);
}
if (request.getHeader(VOMS_SERIAL_HEADER) != null) {
attrs.setHolderSerialNumber(new BigInteger(request.getHeader(VOMS_SERIAL_HEADER), 16));
}
return List.of(attrs);
}

Optional<X509Certificate[]> chain = Utils.getCertificateChainFromRequest(request);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,8 @@ public void setTrustStore(VOMSTrustStoreProperties trustStore) {

private ChecksumStrategy checksumStrategy = ChecksumStrategy.EARLY;

private boolean nginxReverseProxy = false;

private BufferProperties buffer;

private RedirectorProperties redirector;
Expand Down Expand Up @@ -850,6 +852,13 @@ public void setChecksumStrategy(ChecksumStrategy checksumStrategy) {
this.checksumStrategy = checksumStrategy;
}

public boolean getNginxReverseProxy() {
return nginxReverseProxy;
}

public void setNginxReverseProxy(boolean nginxReverseProxy) {
this.nginxReverseProxy = nginxReverseProxy;
}

@Override
public boolean useConscrypt() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ public Resource getResource(String pathInContext) {

}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
resourceService.doGet(request, response, pathResolver, serviceConfig);
}

@Override
protected void doHead(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@
*/
package org.italiangrid.storm.webdav.server.servlet.resource;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
Expand All @@ -26,6 +28,9 @@
import org.eclipse.jetty.server.ResourceService;
import org.eclipse.jetty.util.URIUtil;

import org.italiangrid.storm.webdav.config.ServiceConfigurationProperties;
import org.italiangrid.storm.webdav.server.PathResolver;

public class StormResourceService extends ResourceService {

private String pathInContext(HttpServletRequest request) {
Expand Down Expand Up @@ -73,4 +78,20 @@ public boolean doHead(HttpServletRequest request, HttpServletResponse response)
return true;
}

public boolean doGet(HttpServletRequest request, HttpServletResponse response,
PathResolver resolver, ServiceConfigurationProperties serviceConfig)
throws ServletException, IOException {

if (serviceConfig.getNginxReverseProxy()) {
final String pathInContext = pathInContext(request);
String resolvedPath = resolver.resolvePath(pathInContext);
File f = new File(resolvedPath);
if (f.isFile()) {
response.setHeader("X-Accel-Redirect", "/internal-get" + resolvedPath);
return response.isCommitted();
}
}
return super.doGet(request, response);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.italiangrid.storm.webdav.authz.AuthorizationPolicyService;
import org.italiangrid.storm.webdav.authz.VOMSAuthenticationFilter;
import org.italiangrid.storm.webdav.authz.VOMSAuthenticationProvider;
import org.italiangrid.storm.webdav.authz.VOMSNginxFilter;
import org.italiangrid.storm.webdav.authz.VOMSPreAuthDetailsSource;
import org.italiangrid.storm.webdav.authz.vomap.VOMapDetailServiceBuilder;
import org.italiangrid.storm.webdav.config.ServiceConfigurationProperties;
Expand All @@ -31,6 +32,7 @@
import org.italiangrid.voms.store.VOMSTrustStores;
import org.italiangrid.voms.util.CachingCertificateValidator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand Down Expand Up @@ -71,13 +73,20 @@ VOMSAuthenticationProvider vomsAuthenticationProvider() {

@Bean
VOMSPreAuthDetailsSource vomsDetailsSource(VOMSACValidator validator,
AuthorizationPolicyService ps, VOMapDetailServiceBuilder builder) {
return new VOMSPreAuthDetailsSource(validator, ps, builder.build());
AuthorizationPolicyService ps, VOMapDetailServiceBuilder builder,
@Value("${storm.nginx-reverse-proxy}") boolean nginxReverseProxy) {
return new VOMSPreAuthDetailsSource(validator, ps, builder.build(), nginxReverseProxy);
}

@Bean
VOMSAuthenticationFilter vomsAuthenticationFilter(VOMSPreAuthDetailsSource ds) {
VOMSAuthenticationFilter filter = new VOMSAuthenticationFilter(vomsAuthenticationProvider());
VOMSAuthenticationFilter vomsAuthenticationFilter(VOMSPreAuthDetailsSource ds,
@Value("${storm.nginx-reverse-proxy}") boolean nginxReverseProxy) {
VOMSAuthenticationFilter filter;
if (nginxReverseProxy) {
filter = new VOMSNginxFilter(vomsAuthenticationProvider());
} else {
filter = new VOMSAuthenticationFilter(vomsAuthenticationProvider());
}
filter.setAuthenticationDetailsSource(ds);
return filter;
}
Expand Down
3 changes: 2 additions & 1 deletion src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ oauth:
issuers:

storm:
nginx-reverse-proxy: false
checksum-strategy: early

redirector:
Expand Down Expand Up @@ -165,4 +166,4 @@ storm:

tape:
well-known:
source: ${STORM_WEBDAV_TAPE_WELLKNOWN_SOURCE:/etc/storm/webdav/wlcg-tape-rest-api.json}
source: ${STORM_WEBDAV_TAPE_WELLKNOWN_SOURCE:/etc/storm/webdav/wlcg-tape-rest-api.json}

0 comments on commit 5c05b0f

Please sign in to comment.