blob: bbe81aa8c6b133bb5fb7b4d86536392b96b75d93 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.brooklyn.entity.proxy.nginx;
import static java.lang.String.format;
import java.util.Collection;
import org.apache.brooklyn.entity.proxy.ProxySslConfig;
import org.apache.brooklyn.util.text.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
/**
* Generates the {@code server.conf} configuration file using sensors on an {@link NginxController}.
*/
public class NginxDefaultConfigGenerator implements NginxConfigFileGenerator {
private static final Logger LOG = LoggerFactory.getLogger(NginxDefaultConfigGenerator.class);
public NginxDefaultConfigGenerator() { }
@Override
public String generateConfigFile(NginxDriver driver, NginxController nginx) {
StringBuilder config = new StringBuilder();
config.append("\n");
config.append(format("pid %s;\n", driver.getPidFile()));
config.append("events {\n");
config.append(" worker_connections 8196;\n");
config.append("}\n");
config.append("http {\n");
ProxySslConfig globalSslConfig = nginx.getSslConfig();
if (nginx.isSsl()) {
verifyConfig(globalSslConfig);
appendSslConfig("global", config, " ", globalSslConfig, true, true);
}
// If no servers, then defaults to returning 404
// TODO Give nicer page back
if (nginx.getDomain()!=null || nginx.getServerPoolAddresses() == null || nginx.getServerPoolAddresses().isEmpty()) {
config.append(" server {\n");
config.append(getCodeForServerConfig());
config.append(" listen "+nginx.getPort()+";\n");
config.append(getCodeFor404());
config.append(" }\n");
}
// For basic round-robin across the server-pool
if (nginx.getServerPoolAddresses() != null && nginx.getServerPoolAddresses().size() > 0) {
config.append(format(" upstream "+nginx.getId()+" {\n"));
if (nginx.isSticky()){
config.append(" sticky;\n");
}
for (String address : nginx.getServerPoolAddresses()) {
config.append(" server "+address+";\n");
}
config.append(" }\n");
config.append(" server {\n");
config.append(getCodeForServerConfig());
appendCodeForProxySSLConfig(nginx.getId(), config, " ", globalSslConfig);
config.append(" listen "+nginx.getPort()+";\n");
if (nginx.getDomain()!=null)
config.append(" server_name "+nginx.getDomain()+";\n");
config.append(" location / {\n");
config.append(" proxy_pass "+(globalSslConfig != null && globalSslConfig.getTargetIsSsl() ? "https" : "http")+"://"+nginx.getId()+";\n");
config.append(" }\n");
config.append(" }\n");
}
// For mapping by URL
Iterable<UrlMapping> mappings = nginx.getUrlMappings();
Multimap<String, UrlMapping> mappingsByDomain = LinkedHashMultimap.create();
for (UrlMapping mapping : mappings) {
Collection<String> addrs = mapping.getAttribute(UrlMapping.TARGET_ADDRESSES);
if (addrs != null && addrs.size() > 0) {
mappingsByDomain.put(mapping.getDomain(), mapping);
}
}
for (UrlMapping um : mappings) {
Collection<String> addrs = um.getAttribute(UrlMapping.TARGET_ADDRESSES);
if (addrs != null && addrs.size() > 0) {
config.append(format(" upstream "+um.getUniqueLabel()+" {\n"));
if (nginx.isSticky()){
config.append(" sticky;\n");
}
for (String address: addrs) {
config.append(" server "+address+";\n");
}
config.append(" }\n");
}
}
for (String domain : mappingsByDomain.keySet()) {
config.append(" server {\n");
config.append(getCodeForServerConfig());
config.append(" listen "+nginx.getPort()+";\n");
config.append(" server_name "+domain+";\n");
boolean hasRoot = false;
// set up SSL
ProxySslConfig localSslConfig = null;
for (UrlMapping mappingInDomain : mappingsByDomain.get(domain)) {
ProxySslConfig sslConfig = mappingInDomain.getConfig(UrlMapping.SSL_CONFIG);
if (sslConfig!=null) {
verifyConfig(sslConfig);
if (localSslConfig!=null) {
if (localSslConfig.equals(sslConfig)) {
//ignore identical config specified on multiple mappings
} else {
LOG.warn("{} mapping {} provides SSL config for {} when a different config had already been provided by another mapping, ignoring this one",
new Object[] {this, mappingInDomain, domain});
}
} else if (globalSslConfig!=null) {
if (globalSslConfig.equals(sslConfig)) {
//ignore identical config specified on multiple mappings
} else {
LOG.warn("{} mapping {} provides SSL config for {} when a different config had been provided at root nginx scope, ignoring this one",
new Object[] {this, mappingInDomain, domain});
}
} else {
//new config, is okay
localSslConfig = sslConfig;
}
}
}
if (localSslConfig != null) {
appendSslConfig(domain, config, " ", localSslConfig, true, true);
appendCodeForProxySSLConfig(domain, config, " ", localSslConfig);
}
for (UrlMapping mappingInDomain : mappingsByDomain.get(domain)) {
// TODO Currently only supports "~" for regex. Could add support for other options,
// such as "~*", "^~", literals, etc.
boolean isRoot = mappingInDomain.getPath()==null || mappingInDomain.getPath().length()==0 || mappingInDomain.getPath().equals("/");
if (isRoot && hasRoot) {
LOG.warn(""+this+" mapping "+mappingInDomain+" provides a duplicate / proxy, ignoring");
} else {
hasRoot |= isRoot;
String location = isRoot ? "/" : "~ " + mappingInDomain.getPath();
config.append(" location "+location+" {\n");
Collection<UrlRewriteRule> rewrites = mappingInDomain.getConfig(UrlMapping.REWRITES);
if (rewrites != null && rewrites.size() > 0) {
for (UrlRewriteRule rule: rewrites) {
config.append(" rewrite \"^"+rule.getFrom()+"$\" \""+rule.getTo()+"\"");
if (rule.isBreak()) config.append(" break");
config.append(" ;\n");
}
}
config.append(" proxy_pass "+
(localSslConfig != null && localSslConfig.getTargetIsSsl() ? "https" :
(localSslConfig == null && globalSslConfig != null && globalSslConfig.getTargetIsSsl()) ? "https" :
"http")+
"://"+mappingInDomain.getUniqueLabel()+" ;\n");
config.append(" }\n");
}
}
if (!hasRoot) {
//provide a root block giving 404 if there isn't one for this server
config.append(" location / { \n"+getCodeFor404()+" }\n");
}
config.append(" }\n");
}
config.append("}\n");
return config.toString();
}
protected String getCodeForServerConfig() {
// See http://wiki.nginx.org/HttpProxyModule
return "" +
// this prevents nginx from reporting version number on error pages
" server_tokens off;\n"+
// this prevents nginx from using the internal proxy_pass codename as Host header passed upstream.
// Not using $host, as that causes integration test to fail with a "connection refused" testing
// url-mappings, at URL "http://localhost:${port}/atC0" (with a trailing slash it does work).
" proxy_set_header Host $http_host;\n"+
// following added, as recommended for wordpress in:
// http://zeroturnaround.com/labs/wordpress-protips-go-with-a-clustered-approach/#!/
" proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n"+
" proxy_set_header X-Real-IP $remote_addr;\n";
}
protected void appendCodeForProxySSLConfig(String id, StringBuilder out, String prefix, ProxySslConfig ssl) {
if (ssl.getTargetIsSsl() && ssl.getVerifyClient()) {
// Send 403 if not verified, otherwise pass on the client certificate we received
out.append(prefix).append("if ($ssl_client_verify != SUCCESS) { return 403; }\n");
out.append(prefix).append("proxy_set_header ssl.client_cert $ssl_client_cert;\n");
// Use the configured SSL certificate and key for the proxied server
String cert;
if (Strings.isEmpty(ssl.getCertificateDestination())) {
cert = "" + id + ".crt";
} else {
cert = ssl.getCertificateDestination();
}
out.append(prefix);
out.append("proxy_ssl_certificate " + cert + ";\n");
String key;
if (!Strings.isEmpty(ssl.getKeyDestination())) {
key = ssl.getKeyDestination();
} else if (!Strings.isEmpty(ssl.getKeySourceUrl())) {
key = "" + id + ".key";
} else {
key = null;
}
if (key != null) {
out.append(prefix);
out.append("proxy_ssl_certificate_key " + key + ";\n");
}
}
}
protected String getCodeFor404() {
return " return 404;\n";
}
protected void verifyConfig(ProxySslConfig proxySslConfig) {
if(Strings.isEmpty(proxySslConfig.getCertificateDestination()) && Strings.isEmpty(proxySslConfig.getCertificateSourceUrl())){
throw new IllegalStateException("ProxySslConfig can't have a null certificateDestination and null certificateSourceUrl. One or both need to be set");
}
}
protected boolean appendSslConfig(String id, StringBuilder out, String prefix, ProxySslConfig ssl,
boolean sslBlock, boolean certificateBlock) {
if (ssl == null) return false;
if (sslBlock) {
out.append(prefix);
out.append("ssl on;\n");
}
if (ssl.getReuseSessions()) {
out.append(prefix);
out.append("");
}
if (certificateBlock) {
String cert;
if (Strings.isEmpty(ssl.getCertificateDestination())) {
cert = "" + id + ".crt";
} else {
cert = ssl.getCertificateDestination();
}
out.append(prefix);
out.append("ssl_certificate " + cert + ";\n");
String key;
if (!Strings.isEmpty(ssl.getKeyDestination())) {
key = ssl.getKeyDestination();
} else if (!Strings.isEmpty(ssl.getKeySourceUrl())) {
key = "" + id + ".key";
} else {
key = null;
}
if (key != null) {
out.append(prefix);
out.append("ssl_certificate_key " + key + ";\n");
}
if (ssl.getVerifyClient()) {
out.append("ssl_verify_client on;\n");
String client;
if (Strings.isEmpty(ssl.getClientCertificateDestination())) {
client = "" + id + ".cli";
} else {
client = ssl.getClientCertificateDestination();
}
if (client != null) {
out.append(prefix);
out.append("ssl_client_certificate " + client + ";\n");
}
}
out.append("ssl_protocols TLSv1 TLSv1.1 TLSv1.2;\n");
}
return true;
}
@Override
public String toString(){
return getClass().getName();
}
}