github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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  	"os"
     8  	"strings"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/utils/ssh"
    12  
    13  	"github.com/juju/juju/network"
    14  	"github.com/juju/juju/network/iptables"
    15  )
    16  
    17  const (
    18  	iptablesComment = "managed by juju"
    19  )
    20  
    21  // Implementations of this interface should provide a way to configure external
    22  // IP allocation and add firewall functionality.
    23  type InstanceConfigurator interface {
    24  
    25  	// Close all ports.
    26  	DropAllPorts(exceptPorts []int, addr string) error
    27  
    28  	// Add network interface and allocate external IP address.
    29  	// Implementations should also configure this interface and initialise  ports state.
    30  	ConfigureExternalIpAddress(apiPort int) error
    31  
    32  	// Open or close ports.
    33  	ChangeIngressRules(ipAddress string, insert bool, rules []network.IngressRule) error
    34  
    35  	// List all ingress rules.
    36  	FindIngressRules() ([]network.IngressRule, error)
    37  }
    38  
    39  type sshInstanceConfigurator struct {
    40  	client  ssh.Client
    41  	host    string
    42  	options *ssh.Options
    43  }
    44  
    45  // NewSshInstanceConfigurator creates new sshInstanceConfigurator.
    46  func NewSshInstanceConfigurator(host string) InstanceConfigurator {
    47  	options := ssh.Options{}
    48  	options.SetIdentities("/var/lib/juju/system-identity")
    49  
    50  	// Disable host key checking. We're not sending any sensitive data
    51  	// across, and we don't have access to the host's keys from here.
    52  	//
    53  	// TODO(axw) 2017-12-07 #1732665
    54  	// Stop using SSH, instead manage iptables on the machine
    55  	// itself. This will also provide firewalling for MAAS and
    56  	// LXD machines.
    57  	options.SetStrictHostKeyChecking(ssh.StrictHostChecksNo)
    58  	options.SetKnownHostsFile(os.DevNull)
    59  
    60  	return &sshInstanceConfigurator{
    61  		client:  ssh.DefaultClient,
    62  		host:    "ubuntu@" + host,
    63  		options: &options,
    64  	}
    65  }
    66  
    67  func (c *sshInstanceConfigurator) runCommand(cmd string) (string, error) {
    68  	command := c.client.Command(c.host, []string{"/bin/bash"}, c.options)
    69  	command.Stdin = strings.NewReader(cmd)
    70  	output, err := command.CombinedOutput()
    71  	if err != nil {
    72  		return "", errors.Trace(err)
    73  	}
    74  	return string(output), nil
    75  }
    76  
    77  // DropAllPorts implements InstanceConfigurator interface.
    78  func (c *sshInstanceConfigurator) DropAllPorts(exceptPorts []int, addr string) error {
    79  	cmds := []string{
    80  		iptables.DropCommand{DestinationAddress: addr}.Render(),
    81  	}
    82  	for _, port := range exceptPorts {
    83  		cmds = append(cmds, iptables.AcceptInternalCommand{
    84  			Protocol:           "tcp",
    85  			DestinationAddress: addr,
    86  			DestinationPort:    port,
    87  		}.Render())
    88  	}
    89  
    90  	output, err := c.runCommand(strings.Join(cmds, "\n"))
    91  	if err != nil {
    92  		return errors.Errorf("failed to drop all ports: %s", output)
    93  	}
    94  	logger.Tracef("drop all ports output: %s", output)
    95  	return nil
    96  }
    97  
    98  // ConfigureExternalIpAddressCommands returns the commands to run to configure
    99  // the external IP address
   100  func ConfigureExternalIpAddressCommands(apiPort int) []string {
   101  	commands := []string{
   102  		`printf 'auto eth1\niface eth1 inet dhcp' | sudo tee -a /etc/network/interfaces.d/eth1.cfg`,
   103  		"sudo ifup eth1",
   104  		iptables.DropCommand{Interface: "eth1"}.Render(),
   105  	}
   106  	if apiPort > 0 {
   107  		commands = append(commands, iptables.AcceptInternalCommand{
   108  			Protocol:        "tcp",
   109  			DestinationPort: apiPort,
   110  		}.Render())
   111  	}
   112  	return commands
   113  }
   114  
   115  // ConfigureExternalIpAddress implements InstanceConfigurator interface.
   116  func (c *sshInstanceConfigurator) ConfigureExternalIpAddress(apiPort int) error {
   117  	cmds := ConfigureExternalIpAddressCommands(apiPort)
   118  	output, err := c.runCommand(strings.Join(cmds, "\n"))
   119  	if err != nil {
   120  		return errors.Errorf("failed to drop all ports: %s", output)
   121  	}
   122  	if err != nil {
   123  		return errors.Errorf("failed to allocate external IP address: %s", output)
   124  	}
   125  	logger.Tracef("configure external ip address output: %s", output)
   126  	return nil
   127  }
   128  
   129  // ChangeIngressRules implements InstanceConfigurator interface.
   130  func (c *sshInstanceConfigurator) ChangeIngressRules(ipAddress string, insert bool, rules []network.IngressRule) error {
   131  	var cmds []string
   132  	for _, rule := range rules {
   133  		cmds = append(cmds, iptables.IngressRuleCommand{
   134  			Rule:               rule,
   135  			DestinationAddress: ipAddress,
   136  			Delete:             !insert,
   137  		}.Render())
   138  	}
   139  
   140  	output, err := c.runCommand(strings.Join(cmds, "\n"))
   141  	if err != nil {
   142  		return errors.Errorf("failed to configure ports on external network: %s", output)
   143  	}
   144  	logger.Tracef("change ports output: %s", output)
   145  	return nil
   146  }
   147  
   148  // FindIngressRules implements InstanceConfigurator interface.
   149  func (c *sshInstanceConfigurator) FindIngressRules() ([]network.IngressRule, error) {
   150  	output, err := c.runCommand("sudo iptables -L INPUT -n")
   151  	if err != nil {
   152  		return nil, errors.Errorf("failed to list open ports: %s", output)
   153  	}
   154  	logger.Tracef("find open ports output: %s", output)
   155  	return iptables.ParseIngressRules(strings.NewReader(output))
   156  }