github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/provider/common/instance_configurator.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package common 5 6 import ( 7 "fmt" 8 "strconv" 9 "strings" 10 11 "github.com/juju/errors" 12 "github.com/juju/utils/ssh" 13 14 "github.com/juju/juju/network" 15 ) 16 17 // Implementations of this interface should provide a way to configure external 18 // IP allocation and add firewall functionality. 19 type InstanceConfigurator interface { 20 21 // Close all ports. 22 DropAllPorts(exceptPorts []int, addr string) error 23 24 // Add network interface and allocate external IP address. 25 // Implementations should also configure this interface and initialise ports state. 26 ConfigureExternalIpAddress(apiPort int) error 27 28 // Open or close ports. 29 ChangePorts(ipAddress string, insert bool, ports []network.PortRange) error 30 31 // List all opened ports. 32 FindOpenPorts() ([]network.PortRange, error) 33 34 // Add Ip address. 35 AddIpAddress(nic string, addr string) error 36 37 // Release Ip address. 38 ReleaseIpAddress(addr string) error 39 } 40 41 type sshInstanceConfigurator struct { 42 client ssh.Client 43 host string 44 options *ssh.Options 45 } 46 47 // NewSshInstanceConfigurator creates new sshInstanceConfigurator. 48 func NewSshInstanceConfigurator(host string) InstanceConfigurator { 49 options := ssh.Options{} 50 options.SetIdentities("/var/lib/juju/system-identity") 51 return &sshInstanceConfigurator{ 52 client: ssh.DefaultClient, 53 host: "ubuntu@" + host, 54 options: &options, 55 } 56 } 57 58 // DropAllPorts implements InstanceConfigurator interface. 59 func (c *sshInstanceConfigurator) DropAllPorts(exceptPorts []int, addr string) error { 60 cmd := fmt.Sprintf("sudo iptables -d %s -I INPUT -m state --state NEW -j DROP", addr) 61 62 for _, port := range exceptPorts { 63 cmd += fmt.Sprintf("\nsudo iptables -I INPUT -p tcp --dport %d -j ACCEPT", port) 64 } 65 66 command := c.client.Command(c.host, []string{"/bin/bash"}, c.options) 67 command.Stdin = strings.NewReader(cmd) 68 output, err := command.CombinedOutput() 69 if err != nil { 70 return errors.Errorf("failed to drop all ports: %s", output) 71 } 72 logger.Tracef("drop all ports output: %s", output) 73 return nil 74 } 75 76 // ConfigureExternalIpAddress implements InstanceConfigurator interface. 77 func (c *sshInstanceConfigurator) ConfigureExternalIpAddress(apiPort int) error { 78 cmd := `printf 'auto eth1\niface eth1 inet dhcp' | sudo tee -a /etc/network/interfaces.d/eth1.cfg 79 sudo ifup eth1 80 sudo iptables -i eth1 -I INPUT -m state --state NEW -j DROP` 81 82 if apiPort > 0 { 83 cmd += fmt.Sprintf("\nsudo iptables -I INPUT -p tcp --dport %d -j ACCEPT", apiPort) 84 } 85 86 command := c.client.Command(c.host, []string{"/bin/bash"}, c.options) 87 command.Stdin = strings.NewReader(cmd) 88 output, err := command.CombinedOutput() 89 if err != nil { 90 return errors.Errorf("failed to allocate external IP address: %s", output) 91 } 92 logger.Tracef("configure external ip address output: %s", output) 93 return nil 94 } 95 96 // ChangePorts implements InstanceConfigurator interface. 97 func (c *sshInstanceConfigurator) ChangePorts(ipAddress string, insert bool, ports []network.PortRange) error { 98 cmd := "" 99 insertArg := "-I" 100 if !insert { 101 insertArg = "-D" 102 } 103 for _, port := range ports { 104 if port.ToPort-port.FromPort > 0 { 105 cmd += fmt.Sprintf("sudo iptables -d %s %s INPUT -p %s --match multiport --dports %d:%d -j ACCEPT\n", ipAddress, insertArg, port.Protocol, port.FromPort, port.ToPort) 106 } else { 107 108 cmd += fmt.Sprintf("sudo iptables -d %s %s INPUT -p %s --dport %d -j ACCEPT\n", ipAddress, insertArg, port.Protocol, port.FromPort) 109 } 110 } 111 cmd += "sudo /etc/init.d/iptables-persistent save\n" 112 command := c.client.Command(c.host, []string{"/bin/bash"}, c.options) 113 command.Stdin = strings.NewReader(cmd) 114 output, err := command.CombinedOutput() 115 if err != nil { 116 return errors.Errorf("failed to configure ports on external network: %s", output) 117 } 118 logger.Tracef("change ports output: %s", output) 119 return nil 120 } 121 122 // FindOpenPorts implements InstanceConfigurator interface. 123 func (c *sshInstanceConfigurator) FindOpenPorts() ([]network.PortRange, error) { 124 cmd := "sudo iptables -L INPUT -n" 125 command := c.client.Command(c.host, []string{"/bin/bash"}, c.options) 126 command.Stdin = strings.NewReader(cmd) 127 output, err := command.CombinedOutput() 128 if err != nil { 129 return nil, errors.Errorf("failed to list open ports: %s", output) 130 } 131 logger.Tracef("find open ports output: %s", output) 132 133 //the output have the following format, we will skip all other rules 134 //Chain INPUT (policy ACCEPT) 135 //target prot opt source destination 136 //ACCEPT tcp -- 0.0.0.0/0 192.168.0.1 multiport dports 3456:3458 137 //ACCEPT tcp -- 0.0.0.0/0 192.168.0.2 tcp dpt:12345 138 139 res := make([]network.PortRange, 0) 140 var addSinglePortRange = func(items []string) { 141 ports := strings.Split(items[6], ":") 142 if len(ports) != 2 { 143 return 144 } 145 to, err := strconv.ParseInt(ports[1], 10, 32) 146 if err != nil { 147 return 148 } 149 150 res = append(res, network.PortRange{ 151 Protocol: items[1], 152 FromPort: int(to), 153 ToPort: int(to), 154 }) 155 } 156 var addMultiplePortRange = func(items []string) { 157 ports := strings.Split(items[7], ":") 158 if len(ports) != 2 { 159 return 160 } 161 from, err := strconv.ParseInt(ports[0], 10, 32) 162 if err != nil { 163 return 164 } 165 to, err := strconv.ParseInt(ports[1], 10, 32) 166 if err != nil { 167 return 168 } 169 170 res = append(res, network.PortRange{ 171 Protocol: items[1], 172 FromPort: int(from), 173 ToPort: int(to), 174 }) 175 } 176 177 for i, line := range strings.Split(string(output), "\n") { 178 if i == 1 || i == 0 { 179 continue 180 } 181 items := strings.Split(line, " ") 182 if len(items) == 7 && items[0] == "ACCEPT" && items[3] == "0.0.0.0/0" { 183 addSinglePortRange(items) 184 } 185 if len(items) == 8 && items[0] == "ACCEPT" && items[3] == "0.0.0.0/0" && items[5] != "multiport" && items[6] != "dports" { 186 addMultiplePortRange(items) 187 } 188 } 189 return res, nil 190 } 191 192 // AddIpAddress implements InstanceConfigurator interface. 193 func (c *sshInstanceConfigurator) AddIpAddress(nic string, addr string) error { 194 cmd := fmt.Sprintf("ls /etc/network/interfaces.d | grep %s: | sed 's/%s://' | sed 's/.cfg//' | tail -1", nic, nic) 195 command := c.client.Command(c.host, []string{"/bin/bash"}, c.options) 196 command.Stdin = strings.NewReader(cmd) 197 lastIndStr, err := command.CombinedOutput() 198 if err != nil { 199 return errors.Errorf("failed to obtain last device index: %s", lastIndStr) 200 } 201 lastInd := 0 202 if ind, err := strconv.ParseInt(string(lastIndStr), 10, 64); err != nil { 203 lastInd = int(ind) + 1 204 } 205 nic = fmt.Sprintf("%s:%d", nic, lastInd) 206 cmd = fmt.Sprintf("printf 'auto %s\\niface %s inet static\\naddress %s' | sudo tee -a /etc/network/interfaces.d/%s.cfg\nsudo ifup %s", nic, nic, addr, nic, nic) 207 208 command = c.client.Command(c.host, []string{"/bin/bash"}, c.options) 209 command.Stdin = strings.NewReader(cmd) 210 output, err := command.CombinedOutput() 211 if err != nil { 212 return errors.Errorf("failed to add IP address: %s", output) 213 } 214 logger.Tracef("add ip address output: %s", output) 215 return nil 216 } 217 218 // ReleaseIpAddress implements InstanceConfigurator interface. 219 func (c *sshInstanceConfigurator) ReleaseIpAddress(addr string) error { 220 cmd := fmt.Sprintf("ip addr show | grep %s | awk '{print $7}'", addr) 221 command := c.client.Command(c.host, []string{"/bin/bash"}, c.options) 222 command.Stdin = strings.NewReader(cmd) 223 nic, err := command.CombinedOutput() 224 if err != nil { 225 return errors.Errorf("faild to get nic by ip address: %s", nic) 226 } 227 228 cmd = fmt.Sprintf("sudo rm %s.cfg \nsudo ifdown %s", nic, nic) 229 command = c.client.Command(c.host, []string{"/bin/bash"}, c.options) 230 command.Stdin = strings.NewReader(cmd) 231 output, err := command.CombinedOutput() 232 if err != nil { 233 return errors.Errorf("failed to release IP address: %s", output) 234 } 235 logger.Tracef("release ip address output: %s", output) 236 return nil 237 }