summaryrefslogtreecommitdiffstats
path: root/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/IPTablesEditor.java
blob: d34b41e3ae5eeade1ec3b8f3e7c1d1f07955713e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
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;

/**
 * Edit the iptables for docker containers.
 */
class IPTablesEditor {

    private final PrefixLogger log;
    private final DockerOperations dockerOperations;
    private final ContainerName containerName;
    private final Consumer<String> testInterceptor;

    public IPTablesEditor(DockerOperations dockerOperations, ContainerName containerName) {
        this(dockerOperations, containerName, (result) -> {});
    }

    IPTablesEditor(DockerOperations dockerOperations, ContainerName containerName, Consumer<String> testInterceptor) {
        this.dockerOperations = dockerOperations;
        this.containerName = containerName;
        this.testInterceptor = testInterceptor;
        this.log = PrefixLogger.getNodeAgentLogger(AclMaintainer.class, containerName);
    }

    public static boolean editFlushOnError(DockerOperations dockerOperations, ContainerName containerName, IPVersion ipVersion, String table, LineEditor lineEditor) {
        return new IPTablesEditor(dockerOperations, containerName).edit(table, ipVersion, lineEditor, true);
    }

    public static boolean editLogOnError(DockerOperations dockerOperations, ContainerName containerName, IPVersion ipVersion, String table, LineEditor lineEditor) {
        return new IPTablesEditor(dockerOperations, containerName).edit(table, ipVersion, lineEditor, false);
    }

    public boolean edit(String table, IPVersion ipVersion, LineEditor lineEditor, boolean flush) {
        String editId = ipVersion.iptablesCmd() + "-" + table;
        Editor editor = new Editor(editId, listTable(table, ipVersion), restoreTable(table, ipVersion, flush), lineEditor);
        return editor.edit(log::info);
    }

    private Supplier<List<String>> listTable(String table, IPVersion ipVersion) {
        return () -> {
            ProcessResult currentRulesResult =
                    dockerOperations.executeCommandInNetworkNamespace(containerName, ipVersion.iptablesCmd(), "-S", "-t", table);
            return Arrays.stream(currentRulesResult.getOutput().split("\n"))
                    .map(String::trim)
                    .collect(Collectors.toList());
        };
    }

    private Consumer<List<String>> restoreTable(String table, IPVersion ipVersion, boolean flush) {
        return list -> {
            File file = null;
            try {
                String rules = String.join("\n", list);
                String fileContent = "*" + table + "\n" + rules + "\nCOMMIT\n";
                file = writeTempFile(table, fileContent);
                dockerOperations.executeCommandInNetworkNamespace(containerName, ipVersion.iptablesRestore(), file.getAbsolutePath());
                testInterceptor.accept(fileContent);
            } 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 File writeTempFile(String table, String content) {
        try {
            Path path = Files.createTempFile("iptables-restore", "." + table);
            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);
        }
    }
}