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  }