blob: b9fe874ad841672d76ede43032bc4bafd9112611 [file] [log] [blame]
// 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);
}
}
}