summaryrefslogtreecommitdiffstats
path: root/container-disc
diff options
context:
space:
mode:
authorMorten Tokle <mortent@yahooinc.com>2023-05-25 14:24:52 +0200
committerMorten Tokle <mortent@yahooinc.com>2023-05-25 14:24:52 +0200
commit17680e5bd51252b282e011e4f9929653f78be016 (patch)
tree3e29c910059bf34e30ac1f47b9ac1a95581c8592 /container-disc
parenta89a6d9269fcdb23d907c92bb348c7a3655f9cae (diff)
Configure data plane proxy
Diffstat (limited to 'container-disc')
-rw-r--r--container-disc/src/main/java/com/yahoo/container/jdisc/DataplaneProxyConfigurator.java23
-rw-r--r--container-disc/src/main/java/com/yahoo/container/jdisc/DataplaneProxyService.java173
2 files changed, 196 insertions, 0 deletions
diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/DataplaneProxyConfigurator.java b/container-disc/src/main/java/com/yahoo/container/jdisc/DataplaneProxyConfigurator.java
new file mode 100644
index 00000000000..4c637b5798d
--- /dev/null
+++ b/container-disc/src/main/java/com/yahoo/container/jdisc/DataplaneProxyConfigurator.java
@@ -0,0 +1,23 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.jdisc;
+
+import com.yahoo.cloud.config.DataplaneProxyConfig;
+import com.yahoo.component.AbstractComponent;
+import com.yahoo.jdisc.http.ConnectorConfig;
+
+/**
+ * Reconfigurable component for supporting data plane proxy. Configures the {@code DataplaneProxyService} by calling {@code DataplaneProxyService#init}
+ *
+ * @author mortent
+ */
+public class DataplaneProxyConfigurator extends AbstractComponent {
+
+ public DataplaneProxyConfigurator(DataplaneProxyConfig config, DataplaneProxyService dataplaneProxyService) {
+ dataplaneProxyService.reconfigure(config);
+ }
+
+ @Override
+ public void deconstruct() {
+ super.deconstruct();
+ }
+}
diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/DataplaneProxyService.java b/container-disc/src/main/java/com/yahoo/container/jdisc/DataplaneProxyService.java
new file mode 100644
index 00000000000..5b0cdfaf630
--- /dev/null
+++ b/container-disc/src/main/java/com/yahoo/container/jdisc/DataplaneProxyService.java
@@ -0,0 +1,173 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.jdisc;
+
+import com.yahoo.cloud.config.DataplaneProxyConfig;
+import com.yahoo.component.AbstractComponent;
+
+import javax.inject.Inject;
+import java.io.IOException;
+import java.net.URI;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+
+/**
+ * Configures a data plane proxy. Currently using Nginx.
+ *
+ * @author mortent
+ */
+public class DataplaneProxyService extends AbstractComponent {
+
+ private static final String PREFIX = "/opt/vespa";
+ private static final Path CONFIG_TEMPLATE = Paths.get(PREFIX, "conf/nginx/nginx.conf.template");
+
+ private static final Path clientCertificateFile = Paths.get(PREFIX, "conf/nginx/client_cert.pem");
+ private static final Path clientKeyFile = Paths.get(PREFIX, "conf/nginx/client_key.pem");
+ private static final Path serverCertificateFile = Paths.get(PREFIX, "conf/nginx/server_cert.pem");
+ private static final Path serverKeyFile = Paths.get(PREFIX, "conf/nginx/server_key.pem");
+
+ private static final Path nginxConf = Paths.get(PREFIX, "conf/nginx/nginx.conf");
+
+ private boolean started;
+
+ @Inject
+ public DataplaneProxyService() {
+ this.started = false;
+ }
+
+ public void reconfigure(DataplaneProxyConfig config) {
+ try {
+ String serverCert = config.serverCertificate();
+ String serverKey = config.serverKey();
+ String clientCert = config.clientCertificate();
+ String clientKey = config.clientKey();
+
+ boolean configChanged = false;
+ configChanged |= writeFile(clientCertificateFile, clientCert);
+ configChanged |= writeFile(clientKeyFile, clientKey);
+ configChanged |= writeFile(serverCertificateFile, serverCert);
+ configChanged |= writeFile(serverKeyFile, serverKey);
+ configChanged |= writeFile(nginxConf,
+ nginxConfig(
+ clientCertificateFile,
+ clientKeyFile,
+ serverCertificateFile,
+ serverKeyFile,
+ URI.create(config.mTlsEndpoint()),
+ URI.create(config.tokenEndpoint()),
+ config.port(),
+ PREFIX
+ ));
+ if (!started) {
+ startNginx();
+ started = true;
+ } else if (configChanged){
+ reloadNginx();
+ }
+ } catch (IOException e) {
+ throw new RuntimeException("Error reconfiguring data plane proxy", e);
+ }
+ }
+
+ private void startNginx() {
+ try {
+ Process startCommand = new ProcessBuilder().command(
+ "nginx",
+ "-c", nginxConf.toString()
+ ).start();
+ int exitCode = startCommand.waitFor();
+ if (exitCode != 0) {
+ throw new RuntimeException("Non-zero exitcode from nginx: %d".formatted(exitCode));
+ }
+ } catch (IOException | InterruptedException e) {
+ throw new RuntimeException("Could not start nginx", e);
+ }
+ }
+
+ private void reloadNginx() {
+ try {
+ Process reloadCommand = new ProcessBuilder().command(
+ "nginx",
+ "-s", "reload"
+ ).start();
+ int exitCode = reloadCommand.waitFor();
+ if (exitCode != 0) {
+ throw new RuntimeException("Non-zero exitcode from nginx: %d".formatted(exitCode));
+ }
+ } catch (IOException | InterruptedException e) {
+ throw new RuntimeException("Could not start nginx", e);
+ }
+ }
+
+ private void stopNginx() {
+ try {
+ Process stopCommand = new ProcessBuilder().command(
+ "nginx",
+ "-s", "reload"
+ ).start();
+ int exitCode = stopCommand.waitFor();
+ if (exitCode != 0) {
+ throw new RuntimeException("Non-zero exitcode from nginx: %d".formatted(exitCode));
+ }
+ } catch (IOException | InterruptedException e) {
+ throw new RuntimeException("Could not start nginx", e);
+ }
+ }
+
+ @Override
+ public void deconstruct() {
+ super.deconstruct();
+ stopNginx();
+ }
+
+ /*
+ * Writes a file to disk
+ * return true if file was changed, false if no changes
+ */
+ private boolean writeFile(Path file, String contents) throws IOException {
+ Path tempPath = Paths.get(file.toFile().getAbsolutePath() + ".new");
+ Files.createDirectories(tempPath.getParent());
+ Files.writeString(tempPath, contents);
+
+ if (!Files.exists(file) || Files.mismatch(tempPath, file) > 0) {
+ Files.move(tempPath, file, StandardCopyOption.REPLACE_EXISTING);
+ return true;
+ } else {
+ Files.delete(tempPath);
+ return false;
+ }
+ }
+
+ static String nginxConfig(
+ Path clientCert,
+ Path clientKey,
+ Path serverCert,
+ Path serverKey,
+ URI mTlsEndpoint,
+ URI tokenEndpoint,
+ int vespaPort,
+ String prefix) {
+
+ try {
+ String nginxTemplate = Files.readString(CONFIG_TEMPLATE);
+ nginxTemplate = replace(nginxTemplate, "client_cert", clientCert.toString());
+ nginxTemplate = replace(nginxTemplate, "client_key", clientKey.toString());
+ nginxTemplate = replace(nginxTemplate, "server_cert", serverCert.toString());
+ nginxTemplate = replace(nginxTemplate, "server_key", serverKey.toString());
+ nginxTemplate = replace(nginxTemplate, "mtls_endpoint", mTlsEndpoint.getHost());
+ nginxTemplate = replace(nginxTemplate, "token_endpoint", tokenEndpoint.getHost());
+ nginxTemplate = replace(nginxTemplate, "vespa_port", Integer.toString(vespaPort));
+ nginxTemplate = replace(nginxTemplate, "prefix", prefix);
+
+ // TODO: verify that all template vars have been expanded
+ return nginxTemplate;
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Could not create data plane proxy configuration", e);
+ }
+ }
+
+ private static String replace(String template, String key, String value) {
+ return template.replaceAll("\\$\\{%s\\}".formatted(key), value);
+ }
+}