github.com/jiasir/deis@v1.12.2/builder/sshd/sshd.go (about)

     1  package sshd
     2  
     3  import (
     4  	"bufio"
     5  	"crypto/md5"
     6  	"crypto/subtle"
     7  	"encoding/hex"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"os"
    11  	"os/exec"
    12  	"strings"
    13  
    14  	"golang.org/x/crypto/ssh"
    15  
    16  	"github.com/Masterminds/cookoo"
    17  	"github.com/Masterminds/cookoo/log"
    18  )
    19  
    20  // ParseAuthorizedKeys reads and process an authorized_keys file.
    21  //
    22  // The file is merely parsed into lines, which are then returned in an array.
    23  //
    24  // Params:
    25  // 	- path (string): The path to the authorized_keys file.
    26  //
    27  // Returns:
    28  //  []string of keys.
    29  //
    30  func ParseAuthorizedKeys(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {
    31  	path := p.Get("path", "~/.ssh/authorized_keys").(string)
    32  
    33  	file, err := os.Open(path)
    34  	if err != nil {
    35  		return []string{}, err
    36  	}
    37  	defer file.Close()
    38  
    39  	reader := bufio.NewScanner(file)
    40  	buf := []string{}
    41  
    42  	for reader.Scan() {
    43  		data := reader.Text()
    44  		if len(data) > 0 {
    45  			log.Infof(c, "Adding key '%s'", data)
    46  			buf = append(buf, strings.TrimSpace(data))
    47  		}
    48  	}
    49  
    50  	return buf, nil
    51  
    52  }
    53  
    54  // ParseHostKeys parses the host key files.
    55  //
    56  // By default it looks in /etc/ssh for host keys of the patterh ssh_host_{{TYPE}}_key.
    57  //
    58  // Params:
    59  // 	- keytypes ([]string): Key types to parse. Defaults to []string{rsa, dsa, ecdsa}
    60  // 	- enableV1 (bool): Allow V1 keys. By default this is disabled.
    61  // 	- path (string): Override the lookup pattern. If %s, it will be replaced with the keytype.
    62  //
    63  // Returns:
    64  // 	[]ssh.Signer
    65  func ParseHostKeys(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {
    66  	hostKeyTypes := p.Get("keytypes", []string{"rsa", "dsa", "ecdsa"}).([]string)
    67  	pathTpl := p.Get("path", "/etc/ssh/ssh_host_%s_key").(string)
    68  	hostKeys := make([]ssh.Signer, 0, len(hostKeyTypes))
    69  	for _, t := range hostKeyTypes {
    70  		path := fmt.Sprintf(pathTpl, t)
    71  
    72  		if key, err := ioutil.ReadFile(path); err == nil {
    73  			if hk, err := ssh.ParsePrivateKey(key); err == nil {
    74  				log.Infof(c, "Parsed host key %s.", path)
    75  				hostKeys = append(hostKeys, hk)
    76  			} else {
    77  				log.Errf(c, "Failed to parse host key %s (skipping): %s", path, err)
    78  			}
    79  		}
    80  	}
    81  	if c.Get("enableV1", false).(bool) {
    82  		path := "/etc/ssh/ssh_host_key"
    83  		if key, err := ioutil.ReadFile(path); err != nil {
    84  			log.Errf(c, "Failed to read ssh_host_key")
    85  		} else if hk, err := ssh.ParsePrivateKey(key); err == nil {
    86  			log.Infof(c, "Parsed host key %s.", path)
    87  			hostKeys = append(hostKeys, hk)
    88  		} else {
    89  			log.Errf(c, "Failed to parse host key %s: %s", path, err)
    90  		}
    91  	}
    92  	return hostKeys, nil
    93  }
    94  
    95  // AuthKey authenticates based on a public key.
    96  //
    97  // Params:
    98  // 	- metadata (ssh.ConnMetadata)
    99  // 	- key (ssh.PublicKey)
   100  // 	- authorizedKeys ([]string): List of lines from an authorized keys file.
   101  //
   102  // Returns:
   103  // 	*ssh.Permissions
   104  //
   105  func AuthKey(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {
   106  	meta := p.Get("metadata", nil).(ssh.ConnMetadata)
   107  	key := p.Get("key", nil).(ssh.PublicKey)
   108  	authorized := p.Get("authorizedKeys", []string{}).([]string)
   109  
   110  	auth := new(ssh.CertChecker)
   111  	auth.UserKeyFallback = func(meta ssh.ConnMetadata, pk ssh.PublicKey) (*ssh.Permissions, error) {
   112  
   113  		// This gives us a string in the form "ssh-rsa LONG_KEY"
   114  		suppliedType := key.Type()
   115  		supplied := key.Marshal()
   116  
   117  		for _, allowedKey := range authorized {
   118  			allowed, _, _, _, err := ssh.ParseAuthorizedKey([]byte(allowedKey))
   119  			if err != nil {
   120  				log.Infof(c, "Could not parse authorized key '%q': %s", allowedKey, err)
   121  				continue
   122  			}
   123  
   124  			// We use a contstant time compare more as a precaution than anything
   125  			// else. A timing attack here would be very difficult, but... better
   126  			// safe than sorry.
   127  			if allowed.Type() == suppliedType && subtle.ConstantTimeCompare(allowed.Marshal(), supplied) == 1 {
   128  				log.Infof(c, "Key accepted for user %s.", meta.User())
   129  				perm := &ssh.Permissions{
   130  					Extensions: map[string]string{
   131  						"user": meta.User(),
   132  					},
   133  				}
   134  				return perm, nil
   135  			}
   136  		}
   137  
   138  		return nil, fmt.Errorf("No matching keys found.")
   139  	}
   140  
   141  	return auth.Authenticate(meta, key)
   142  }
   143  
   144  // compareKeys compares to key files and returns true of they match.
   145  func compareKeys(a, b ssh.PublicKey) bool {
   146  	if a.Type() != b.Type() {
   147  		return false
   148  	}
   149  	// The best way to compare just the key seems to be to marshal both and
   150  	// then compare the output byte sequence.
   151  	return subtle.ConstantTimeCompare(a.Marshal(), b.Marshal()) == 1
   152  }
   153  
   154  // Start starts an instance of /usr/sbin/sshd.
   155  func Start(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {
   156  	dargs := []string{"-e", "-D"}
   157  
   158  	sshd := exec.Command("/usr/sbin/sshd", dargs...)
   159  	sshd.Stdout = os.Stdout
   160  	sshd.Stderr = os.Stderr
   161  
   162  	if err := sshd.Start(); err != nil {
   163  		return 0, err
   164  	}
   165  
   166  	return sshd.Process.Pid, nil
   167  }
   168  
   169  // Configure creates a new SSH configuration object.
   170  //
   171  // Config sets a PublicKeyCallback handler that forwards public key auth
   172  // requests to the route named "pubkeyAuth".
   173  //
   174  // This assumes certain details about our environment, like the location of the
   175  // host keys. It also provides only key-based authentication.
   176  // ConfigureServerSshConfig
   177  //
   178  // Returns:
   179  //  An *ssh.ServerConfig
   180  func Configure(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {
   181  	router := c.Get("cookoo.Router", nil).(*cookoo.Router)
   182  
   183  	cfg := &ssh.ServerConfig{
   184  		PublicKeyCallback: func(m ssh.ConnMetadata, k ssh.PublicKey) (*ssh.Permissions, error) {
   185  			c.Put("metadata", m)
   186  			c.Put("key", k)
   187  
   188  			pubkeyAuth := c.Get("route.sshd.pubkeyAuth", "pubkeyAuth").(string)
   189  			err := router.HandleRequest(pubkeyAuth, c, true)
   190  			return c.Get("pubkeyAuth", &ssh.Permissions{}).(*ssh.Permissions), err
   191  		},
   192  	}
   193  
   194  	return cfg, nil
   195  }
   196  
   197  // FingerprintKey fingerprints a key and returns the colon-formatted version
   198  //
   199  // Params:
   200  // 	- key (ssh.PublicKey): The key to fingerprint.
   201  //
   202  // Returns:
   203  // 	- A string representation of the key fingerprint.
   204  func FingerprintKey(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {
   205  	key := p.Get("key", nil).(ssh.PublicKey)
   206  	return Fingerprint(key), nil
   207  }
   208  
   209  // Fingerprint generates a colon-separated fingerprint string from a public key.
   210  func Fingerprint(key ssh.PublicKey) string {
   211  	hash := md5.Sum(key.Marshal())
   212  	buf := make([]byte, hex.EncodedLen(len(hash)))
   213  	hex.Encode(buf, hash[:])
   214  	// We need this in colon notation:
   215  	fp := make([]byte, len(buf)+15)
   216  
   217  	i, j := 0, 0
   218  	for ; i < len(buf); i++ {
   219  		if i > 0 && i%2 == 0 {
   220  			fp[j] = ':'
   221  			j++
   222  		}
   223  		fp[j] = buf[i]
   224  		j++
   225  	}
   226  
   227  	return string(fp)
   228  }