aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/AclMaintainer.java98
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/IPTablesEditor.java87
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/IPTablesRestore.java82
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/Editor.java44
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/LineEdit.java2
5 files changed, 210 insertions, 103 deletions
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/AclMaintainer.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/AclMaintainer.java
index 42bbe7e67e0..18ce6395ed3 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/AclMaintainer.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/AclMaintainer.java
@@ -7,11 +7,17 @@ import com.yahoo.vespa.hosted.node.admin.component.Environment;
import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.Acl;
import com.yahoo.vespa.hosted.node.admin.docker.DockerOperations;
import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeRepository;
+import com.yahoo.vespa.hosted.node.admin.task.util.file.LineEdit;
+import com.yahoo.vespa.hosted.node.admin.task.util.file.LineEditor;
import com.yahoo.vespa.hosted.node.admin.task.util.network.IPAddresses;
import com.yahoo.vespa.hosted.node.admin.task.util.network.IPVersion;
import com.yahoo.vespa.hosted.node.admin.util.PrefixLogger;
import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@@ -51,21 +57,14 @@ public class AclMaintainer implements Runnable {
private void applyRedirect(Container container, InetAddress address) {
IPVersion ipVersion = IPVersion.get(address);
-
- String redirectStatements = String.join("\n"
- , "-P PREROUTING ACCEPT"
- , "-P INPUT ACCEPT"
- , "-P OUTPUT ACCEPT"
- , "-P POSTROUTING ACCEPT"
- , "-A OUTPUT -d " + InetAddresses.toAddrString(address) + ipVersion.singleHostCidr() + " -j REDIRECT");
-
- IPTablesRestore.syncTableLogOnError(dockerOperations, container.name, ipVersion, "nat", redirectStatements);
+ String redirectRule = "-A OUTPUT -d " + InetAddresses.toAddrString(address) + ipVersion.singleHostCidr() + " -j REDIRECT";
+ IPTablesEditor.editLogOnError(dockerOperations, container.name, ipVersion, "nat", NatTableLineEditor.from(redirectRule));
}
private void apply(Container container, Acl acl) {
// Apply acl to the filter table
- IPTablesRestore.syncTableFlushOnError(dockerOperations, container.name, IPVersion.IPv6, "filter", acl.toRules(IPVersion.IPv6));
- IPTablesRestore.syncTableFlushOnError(dockerOperations, container.name, IPVersion.IPv4, "filter", acl.toRules(IPVersion.IPv4));
+ IPTablesEditor.editFlushOnError(dockerOperations, container.name, IPVersion.IPv6, "filter", FilterTableLineEditor.from(acl, IPVersion.IPv6));
+ IPTablesEditor.editFlushOnError(dockerOperations, container.name, IPVersion.IPv4, "filter", FilterTableLineEditor.from(acl, IPVersion.IPv4));
// Apply redirect to the nat table
if (this.environment.getCloud().equals("AWS")) {
@@ -93,4 +92,81 @@ public class AclMaintainer implements Runnable {
log.error("Failed to configure ACLs", t);
}
}
+
+ /**
+ * An editor that assumes all lines are exactly as the the wanted rules
+ */
+ private static class FilterTableLineEditor implements LineEditor {
+
+ private final List<String> wantedRules;
+ private boolean removeRemaining = false;
+
+ FilterTableLineEditor(List<String> wantedRules) {
+ this.wantedRules = new ArrayList<>(wantedRules);
+ }
+
+ static FilterTableLineEditor from(Acl acl, IPVersion ipVersion) {
+ List<String> rules = Arrays.asList(acl.toRules(ipVersion).split("\n"));
+ return new FilterTableLineEditor(rules);
+ }
+
+ @Override
+ public LineEdit edit(String line) {
+ if (removeRemaining) {
+ return LineEdit.remove();
+ }
+ if (wantedRules.indexOf(line) == 0) {
+ wantedRules.remove(line);
+ return LineEdit.none();
+ } else {
+ removeRemaining = true;
+ return LineEdit.remove();
+ }
+ }
+
+ @Override
+ public List<String> onComplete() {
+ return this.wantedRules;
+ }
+ }
+
+ /**
+ * An editor that only cares about the REDIRECT statement
+ */
+ private static class NatTableLineEditor implements LineEditor {
+
+ private final String redirectRule;
+ private boolean redirectExists;
+
+ NatTableLineEditor(String redirectRule) {
+ this.redirectRule = redirectRule;
+ }
+
+ static NatTableLineEditor from(String redirectRule) {
+ return new NatTableLineEditor(redirectRule);
+ }
+
+ @Override
+ public LineEdit edit(String line) {
+ if (line.endsWith("REDIRECT")) {
+ if (redirectExists) {
+ // Only allow one redirect rule
+ return LineEdit.remove();
+ } else {
+ redirectExists = true;
+ if (line.equals(redirectRule)) {
+ return LineEdit.none();
+ } else {
+ return LineEdit.replaceWith(redirectRule);
+ }
+ }
+ } else return LineEdit.none();
+ }
+
+ @Override
+ public List<String> onComplete() {
+ if (redirectExists) return new ArrayList<>();
+ return Collections.singletonList(redirectRule);
+ }
+ }
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/IPTablesEditor.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/IPTablesEditor.java
new file mode 100644
index 00000000000..454f0a83ebe
--- /dev/null
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/IPTablesEditor.java
@@ -0,0 +1,87 @@
+package com.yahoo.vespa.hosted.node.admin.maintenance.acl;
+
+import com.yahoo.vespa.hosted.dockerapi.ContainerName;
+import com.yahoo.vespa.hosted.dockerapi.ProcessResult;
+import com.yahoo.vespa.hosted.node.admin.docker.DockerOperations;
+import com.yahoo.vespa.hosted.node.admin.task.util.file.Editor;
+import com.yahoo.vespa.hosted.node.admin.task.util.file.LineEditor;
+import com.yahoo.vespa.hosted.node.admin.task.util.network.IPVersion;
+import com.yahoo.vespa.hosted.node.admin.util.PrefixLogger;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+public class IPTablesEditor {
+
+ private static final PrefixLogger log = PrefixLogger.getNodeAdminLogger(AclMaintainer.class);
+
+ public static boolean editFlushOnError(DockerOperations dockerOperations, ContainerName containerName, IPVersion ipVersion, String table, LineEditor lineEditor) {
+ return edit(dockerOperations, containerName, ipVersion, table, lineEditor, true);
+ }
+
+ public static boolean editLogOnError(DockerOperations dockerOperations, ContainerName containerName, IPVersion ipVersion, String table, LineEditor lineEditor) {
+ return edit(dockerOperations, containerName, ipVersion, table, lineEditor, false);
+ }
+
+ private static boolean edit(DockerOperations dockerOperations, ContainerName containerName, IPVersion ipVersion, String table, LineEditor lineEditor, boolean flush) {
+ Editor editor = new Editor(ipVersion.iptablesCmd() + " table: " + table,
+ listTable(dockerOperations, containerName, ipVersion, table),
+ restoreTable(dockerOperations, containerName, ipVersion, table, flush), lineEditor);
+ return editor.edit(log::info);
+ }
+
+ private static Supplier<List<String>> listTable(DockerOperations dockerOperations, ContainerName containerName, IPVersion ipVersion, String table) {
+ return () -> {
+ ProcessResult currentRulesResult =
+ dockerOperations.executeCommandInNetworkNamespace(containerName, ipVersion.iptablesCmd(), "-S", "-t", table);
+ return Arrays.stream(currentRulesResult.getOutput().split("\n"))
+ .collect(Collectors.toList());
+ };
+ }
+
+ private static Consumer<List<String>> restoreTable(DockerOperations dockerOperations, ContainerName containerName, IPVersion ipVersion, String table, boolean flush) {
+ return list -> {
+ File file = null;
+ try {
+ String rules = String.join("\n", list);
+ file = writeTempFile(ipVersion.name(), "*" + table + "\n" + rules + "\nCOMMIT\n");
+ dockerOperations.executeCommandInNetworkNamespace(containerName, ipVersion.iptablesRestore(), file.getAbsolutePath());
+ } catch (Exception e) {
+ if (flush) {
+ log.error("Exception occurred while syncing iptable " + table + " for " + containerName.asString() + ", attempting rollback", e);
+ try {
+ dockerOperations.executeCommandInNetworkNamespace(containerName, ipVersion.iptablesCmd(), "-F", "-t", table);
+ } catch (Exception ne) {
+ log.error("Rollback of table " + table + " for " + containerName.asString() + " failed, giving up", ne);
+ }
+ } else {
+ log.warning("Unable to sync iptables for " + table, e);
+ }
+ } finally {
+ if (file != null) {
+ file.delete();
+ }
+ }
+ };
+ }
+
+ private static File writeTempFile(String postfix, String content) {
+ try {
+ Path path = Files.createTempFile("iptables-restore", "." + postfix);
+ File file = path.toFile();
+ Files.write(path, content.getBytes(StandardCharsets.UTF_8));
+ file.deleteOnExit();
+ return file;
+ } catch (IOException e) {
+ throw new RuntimeException("Unable to write restore file for iptables.", e);
+ }
+ }
+}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/IPTablesRestore.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/IPTablesRestore.java
deleted file mode 100644
index 20bd50d0892..00000000000
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/IPTablesRestore.java
+++ /dev/null
@@ -1,82 +0,0 @@
-package com.yahoo.vespa.hosted.node.admin.maintenance.acl;
-
-import com.yahoo.vespa.hosted.dockerapi.ContainerName;
-import com.yahoo.vespa.hosted.dockerapi.ProcessResult;
-import com.yahoo.vespa.hosted.node.admin.docker.DockerOperations;
-import com.yahoo.vespa.hosted.node.admin.task.util.network.IPVersion;
-import com.yahoo.vespa.hosted.node.admin.util.PrefixLogger;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-
-/**
- * Utility class to sync rules for a given iptables table in a container.
- *
- * @author smorgrav
- */
-public class IPTablesRestore {
-
- private static final PrefixLogger log = PrefixLogger.getNodeAdminLogger(AclMaintainer.class);
-
- public static void syncTableFlushOnError(DockerOperations dockerOperations, ContainerName containerName, IPVersion ipVersion, String table, String rules) {
- syncTable(dockerOperations, containerName, ipVersion, table, rules, true);
- }
-
- public static void syncTableLogOnError(DockerOperations dockerOperations, ContainerName containerName, IPVersion ipVersion, String table, String rules) {
- syncTable(dockerOperations, containerName, ipVersion, table, rules, false);
- }
-
- private static void syncTable(DockerOperations dockerOperations, ContainerName containerName, IPVersion ipVersion, String table, String rules, boolean flush) {
- File file = null;
- try {
- // Get current rules for table
- ProcessResult currentRulesResult =
- dockerOperations.executeCommandInNetworkNamespace(containerName, ipVersion.iptablesCmd(), "-S", "-t", table);
- String currentRules = currentRulesResult.getOutput();
-
- // Compare and apply wanted if different
- if (!equalsWhenIgnoreSpaceAndCase(rules, currentRules)) {
- log.info(ipVersion.iptablesCmd() + " table: " + table + " differs. Wanted:\n" + rules + "\nGot\n" + currentRules);
- file = writeTempFile(ipVersion.name(), "*" + table + "\n" + rules + "\nCOMMIT\n");
- dockerOperations.executeCommandInNetworkNamespace(containerName, ipVersion.iptablesRestore(), file.getAbsolutePath());
- }
- } catch (Exception e) {
- if (flush) {
- log.error("Exception occurred while syncing iptable " + table + " for " + containerName.asString() + ", attempting rollback", e);
- try {
- dockerOperations.executeCommandInNetworkNamespace(containerName, ipVersion.iptablesCmd(), "-F", "-t", table);
- } catch (Exception ne) {
- log.error("Rollback of table " + table + " for " + containerName.asString() + " failed, giving up", ne);
- }
- } else {
- log.warning("Unable to sync iptables for " + table, e);
- }
- } finally {
- if (file != null) {
- file.delete();
- }
- }
- }
-
- private static File writeTempFile(String postfix, String content) {
- try {
- Path path = Files.createTempFile("iptables-restore", "." + postfix);
- File file = path.toFile();
- Files.write(path, content.getBytes(StandardCharsets.UTF_8));
- file.deleteOnExit();
- return file;
- } catch (IOException e) {
- throw new RuntimeException("Unable to write restore file for iptables.", e);
- }
- }
-
- /**
- * to be agnostic to potential variances in output (and simplify test cases)
- */
- private static boolean equalsWhenIgnoreSpaceAndCase(String a, String b) {
- return a.trim().replaceAll("\\s+", " ").equalsIgnoreCase(b.trim().replaceAll("\\s+", " "));
- }
-}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/Editor.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/Editor.java
index 83954551905..3d4dbcca0e6 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/Editor.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/Editor.java
@@ -9,6 +9,8 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
import java.util.logging.Logger;
import static com.yahoo.vespa.hosted.node.admin.task.util.file.IOExceptionUtil.uncheck;
@@ -24,20 +26,36 @@ public class Editor {
private static int maxLength = 300;
- private final Path path;
+ private final Supplier<List<String>> supplier;
+ private final Consumer<List<String>> consumer;
+ private final String name;
private final LineEditor editor;
public Editor(Path path, LineEditor editor) {
- this.path = path;
- this.editor = editor;
+ this(path.toString(),
+ () -> uncheck(() -> Files.readAllLines(path, ENCODING)),
+ (newLines) -> uncheck(() -> Files.write(path, newLines, ENCODING)),
+ editor);
}
/**
- * Read the file which must be encoded in UTF-8, use the LineEditor to edit it,
- * and any modifications were done write it back and return true.
+ * @param name The name of what is being edited - used in logging
+ * @param supplier Supplies the editor with a list of lines to edit
+ * @param consumer Consumes the lines to presist if any changes is detected
+ * @param editor The line operations to execute on the lines supplied
*/
- public boolean converge(TaskContext context) {
- List<String> lines = uncheck(() -> Files.readAllLines(path, ENCODING));
+ public Editor(String name,
+ Supplier<List<String>> supplier,
+ Consumer<List<String>> consumer,
+ LineEditor editor) {
+ this.supplier = supplier;
+ this.consumer = consumer;
+ this.name = name;
+ this.editor = editor;
+ }
+
+ public boolean edit(Consumer<String> logConsumer) {
+ List<String> lines = supplier.get();
List<String> newLines = new ArrayList<>();
StringBuilder diff = new StringBuilder();
boolean modified = false;
@@ -80,11 +98,19 @@ public class Editor {
}
String diffDescription = diffTooLarge(diff) ? "" : ":\n" + diff.toString();
- context.recordSystemModification(logger, "Patching file " + path + diffDescription);
- uncheck(() -> Files.write(path, newLines, ENCODING));
+ logConsumer.accept("Patching " + name + diffDescription);
+ consumer.accept(newLines);
return true;
}
+ /**
+ * Read the file which must be encoded in UTF-8, use the LineEditor to edit it,
+ * and any modifications were done write it back and return true.
+ */
+ public boolean converge(TaskContext context) {
+ return this.edit(line -> context.recordSystemModification(logger, line));
+ }
+
private static void maybeAdd(StringBuilder diff, List<String> lines) {
for (String line : lines) {
if (!diffTooLarge(diff)) {
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/LineEdit.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/LineEdit.java
index 78e7a3e71b6..1745e63be56 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/LineEdit.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/LineEdit.java
@@ -16,7 +16,7 @@ import static com.yahoo.vespa.hosted.node.admin.task.util.file.LineEdit.Type.NON
*/
@Immutable
public class LineEdit {
- enum Type { NONE, REPLACE }
+ public enum Type { NONE, REPLACE }
public static LineEdit none() { return insert(Collections.emptyList(), Collections.emptyList()); }
public static LineEdit remove() { return replaceWith(Collections.emptyList()); }