| // Copyright (C) 2009 The Android Open Source Project |
| // |
| // 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 com.google.gerrit.pgm.init; |
| |
| import static com.google.gerrit.common.FileUtil.chmod; |
| import static com.google.gerrit.pgm.init.api.InitUtil.die; |
| import static com.google.gerrit.pgm.init.api.InitUtil.domainOf; |
| import static com.google.gerrit.pgm.init.api.InitUtil.isAnyAddress; |
| import static com.google.gerrit.pgm.init.api.InitUtil.toURI; |
| |
| import com.google.gerrit.pgm.init.api.ConsoleUI; |
| import com.google.gerrit.pgm.init.api.InitFlags; |
| import com.google.gerrit.pgm.init.api.InitStep; |
| import com.google.gerrit.pgm.init.api.Section; |
| import com.google.gerrit.server.config.SitePaths; |
| import com.google.gerrit.server.mail.SignedToken; |
| import com.google.inject.Inject; |
| import com.google.inject.Singleton; |
| import java.io.IOException; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| |
| /** Initialize the {@code httpd} configuration section. */ |
| @Singleton |
| class InitHttpd implements InitStep { |
| private final ConsoleUI ui; |
| private final SitePaths site; |
| private final InitFlags flags; |
| private final Section httpd; |
| private final Section gerrit; |
| |
| @Inject |
| InitHttpd( |
| final ConsoleUI ui, |
| final SitePaths site, |
| final InitFlags flags, |
| final Section.Factory sections) { |
| this.ui = ui; |
| this.site = site; |
| this.flags = flags; |
| this.httpd = sections.get("httpd", null); |
| this.gerrit = sections.get("gerrit", null); |
| } |
| |
| @Override |
| public void run() throws IOException, InterruptedException { |
| ui.header("HTTP Daemon"); |
| |
| boolean anySsl = false; |
| // If any listenUrls are present, validate whether it can be parsed as URL. |
| String[] listenUrls = httpd.getList("listenUrl"); |
| for (String listenUrl : listenUrls) { |
| if (listenUrl != null && !listenUrl.isEmpty()) { |
| try { |
| final URI u = toURI(listenUrl); |
| if (u.getScheme().startsWith("https")) { |
| anySsl = true; |
| } |
| } catch (URISyntaxException e) { |
| System.err.println( |
| String.format( |
| "warning: invalid httpd.listenUrl entry: '%s'. Gerrit may not be able to start.", |
| listenUrl)); |
| } |
| } |
| } |
| |
| if (listenUrls.length > 1) { |
| // Because we will return early here, we will warn about steps otherwise being covered. |
| if (!ui.isBatch()) { |
| System.err.println( |
| "Interactive configuration is not supported with multiple entries of " |
| + "httpd.listenUrl."); |
| } |
| if (anySsl) { |
| System.err.println( |
| "Generating self-signed SSL certificates is not supported with multiple " |
| + "entries of httpd.listenUrl."); |
| } |
| String canonicalWebUrlDefaultString = ""; |
| if (listenUrls[0] != null && !listenUrls[0].isEmpty()) { |
| try { |
| canonicalWebUrlDefaultString = new URI(listenUrls[0]).toString(); |
| } catch (URISyntaxException e) { |
| // Should not happen, but log it anyway |
| System.err.println( |
| String.format("warning: invalid httpd.listenUrl entry: '%s'", listenUrls[0])); |
| } |
| } |
| gerrit.string("Canonical URL", "canonicalWebUrl", canonicalWebUrlDefaultString); |
| return; |
| } |
| |
| boolean proxy = false; |
| boolean ssl = false; |
| String address = "*"; |
| int port = -1; |
| String context = "/"; |
| |
| if (listenUrls.length > 0 && listenUrls[0] != null && !listenUrls[0].isEmpty()) { |
| try { |
| final URI uri = toURI(listenUrls[0]); |
| proxy = uri.getScheme().startsWith("proxy-"); |
| ssl = uri.getScheme().endsWith("https"); |
| address = isAnyAddress(new URI(listenUrls[0])) ? "*" : uri.getHost(); |
| port = uri.getPort(); |
| context = uri.getPath(); |
| } catch (URISyntaxException e) { |
| System.err.println("warning: invalid httpd.listenUrl " + listenUrls[0]); |
| } |
| } |
| |
| proxy = ui.yesno(proxy, "Behind reverse proxy"); |
| |
| if (proxy) { |
| ssl = ui.yesno(ssl, "Proxy uses SSL (https://)"); |
| context = ui.readString(context, "Subdirectory on proxy server"); |
| } else { |
| ssl = ui.yesno(ssl, "Use SSL (https://)"); |
| context = "/"; |
| } |
| |
| address = ui.readString(address, "Listen on address"); |
| |
| if (port < 0) { |
| if (proxy) { |
| port = 8081; |
| } else if (ssl) { |
| port = 8443; |
| } else { |
| port = 8080; |
| } |
| } |
| port = ui.readInt(port, "Listen on port"); |
| |
| final StringBuilder urlbuf = new StringBuilder(); |
| urlbuf.append(proxy ? "proxy-" : ""); |
| urlbuf.append(ssl ? "https" : "http"); |
| urlbuf.append("://"); |
| urlbuf.append(address); |
| if (0 <= port) { |
| urlbuf.append(":"); |
| urlbuf.append(port); |
| } |
| urlbuf.append(context); |
| |
| URI uri; |
| try { |
| uri = toURI(urlbuf.toString()); |
| if (uri.getScheme().startsWith("proxy-")) { |
| // If its a proxy URL, assume the reverse proxy is on our system |
| // at the protocol standard ports (so omit the ports from the URL). |
| // |
| String s = uri.getScheme().substring("proxy-".length()); |
| uri = new URI(s + "://" + uri.getHost() + uri.getPath()); |
| } |
| } catch (URISyntaxException e) { |
| throw die("invalid httpd.listenUrl", e); |
| } |
| httpd.set("listenUrl", urlbuf.toString()); |
| gerrit.string("Canonical URL", "canonicalWebUrl", uri.toString()); |
| generateSslCertificate(); |
| } |
| |
| private void generateSslCertificate() throws IOException, InterruptedException { |
| final String listenUrl = httpd.get("listenUrl"); |
| |
| if (!listenUrl.startsWith("https://")) { |
| // We aren't responsible for SSL processing. |
| // |
| return; |
| } |
| |
| String hostname; |
| try { |
| String url = gerrit.get("canonicalWebUrl"); |
| if (url == null || url.isEmpty()) { |
| url = listenUrl; |
| } |
| hostname = toURI(url).getHost(); |
| } catch (URISyntaxException e) { |
| System.err.println("Invalid httpd.listenUrl, not checking certificate"); |
| return; |
| } |
| |
| Path store = site.ssl_keystore; |
| if (!ui.yesno(!Files.exists(store), "Create new self-signed SSL certificate")) { |
| return; |
| } |
| |
| String ssl_pass = flags.sec.get("http", null, "sslKeyPassword"); |
| if (ssl_pass == null || ssl_pass.isEmpty()) { |
| ssl_pass = SignedToken.generateRandomKey(); |
| flags.sec.set("httpd", null, "sslKeyPassword", ssl_pass); |
| } |
| |
| hostname = ui.readString(hostname, "Certificate server name"); |
| final String validity = ui.readString("365", "Certificate expires in (days)"); |
| |
| final String dname = "CN=" + hostname + ",OU=Gerrit Code Review,O=" + domainOf(hostname); |
| |
| Path tmpdir = site.etc_dir.resolve("tmp.sslcertgen"); |
| try { |
| Files.createDirectory(tmpdir); |
| } catch (IOException e) { |
| throw die("Cannot create directory " + tmpdir, e); |
| } |
| chmod(0600, tmpdir); |
| |
| Path tmpstore = tmpdir.resolve("keystore"); |
| Runtime.getRuntime() |
| .exec( |
| new String[] { |
| "keytool", // |
| "-keystore", |
| tmpstore.toAbsolutePath().toString(), // |
| "-storepass", |
| ssl_pass, // |
| "-genkeypair", // |
| "-alias", |
| hostname, // |
| "-keyalg", |
| "RSA", // |
| "-validity", |
| validity, // |
| "-dname", |
| dname, // |
| "-keypass", |
| ssl_pass, // |
| }) |
| .waitFor(); |
| chmod(0600, tmpstore); |
| |
| try { |
| Files.move(tmpstore, store); |
| } catch (IOException e) { |
| throw die("Cannot rename " + tmpstore + " to " + store, e); |
| } |
| try { |
| Files.delete(tmpdir); |
| } catch (IOException e) { |
| throw die("Cannot delete " + tmpdir, e); |
| } |
| } |
| } |