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 }