~ubuntu-branches/ubuntu/trusty/juju-core/trusty-proposed

« back to all changes in this revision

Viewing changes to src/launchpad.net/juju-core/utils/ssh/clientkeys.go

  • Committer: Package Import Robot
  • Author(s): James Page
  • Date: 2014-01-29 11:40:20 UTC
  • mfrom: (23.1.1 trusty-proposed)
  • Revision ID: package-import@ubuntu.com-20140129114020-ejieitm8smtt5vln
Tags: 1.17.1-0ubuntu2
d/tests/local-provider: Don't fail tests if ~/.juju is present as its
created by the juju version command. 

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2014 Canonical Ltd.
 
2
// Licensed under the AGPLv3, see LICENCE file for details.
 
3
 
 
4
package ssh
 
5
 
 
6
import (
 
7
        "fmt"
 
8
        "io/ioutil"
 
9
        "os"
 
10
        "path/filepath"
 
11
        "strings"
 
12
        "sync"
 
13
 
 
14
        "code.google.com/p/go.crypto/ssh"
 
15
 
 
16
        "launchpad.net/juju-core/utils"
 
17
        "launchpad.net/juju-core/utils/set"
 
18
)
 
19
 
 
20
const clientKeyName = "juju_id_rsa"
 
21
 
 
22
// PublicKeySuffix is the file extension for public key files.
 
23
const PublicKeySuffix = ".pub"
 
24
 
 
25
var (
 
26
        clientKeysMutex sync.Mutex
 
27
 
 
28
        // clientKeys is a cached map of private key filenames
 
29
        // to ssh.Signers. The private keys are those loaded
 
30
        // from the client key directory, passed to LoadClientKeys.
 
31
        clientKeys map[string]ssh.Signer
 
32
)
 
33
 
 
34
// LoadClientKeys loads the client SSH keys from the
 
35
// specified directory, and caches them as a process-wide
 
36
// global. If the directory does not exist, it is created;
 
37
// if the directory did not exist, or contains no keys, it
 
38
// is populated with a new key pair.
 
39
//
 
40
// If the directory exists, then all pairs of files where one
 
41
// has the same name as the other + ".pub" will be loaded as
 
42
// private/public key pairs.
 
43
//
 
44
// Calls to LoadClientKeys will clear the previously loaded
 
45
// keys, and recompute the keys.
 
46
func LoadClientKeys(dir string) error {
 
47
        clientKeysMutex.Lock()
 
48
        defer clientKeysMutex.Unlock()
 
49
        dir, err := utils.NormalizePath(dir)
 
50
        if err != nil {
 
51
                return err
 
52
        }
 
53
        if _, err := os.Stat(dir); err == nil {
 
54
                keys, err := loadClientKeys(dir)
 
55
                if err != nil {
 
56
                        return err
 
57
                } else if len(keys) > 0 {
 
58
                        clientKeys = keys
 
59
                        return nil
 
60
                }
 
61
                // Directory exists but contains no keys;
 
62
                // fall through and create one.
 
63
        }
 
64
        if err := utils.MkdirAllForUser(dir, 0700); err != nil {
 
65
                return err
 
66
        }
 
67
        keyfile, key, err := generateClientKey(dir)
 
68
        if err != nil {
 
69
                os.RemoveAll(dir)
 
70
                return err
 
71
        }
 
72
        clientKeys = map[string]ssh.Signer{keyfile: key}
 
73
        return nil
 
74
}
 
75
 
 
76
// ClearClientKeys clears the client keys cached in memory.
 
77
func ClearClientKeys() {
 
78
        clientKeysMutex.Lock()
 
79
        defer clientKeysMutex.Unlock()
 
80
        clientKeys = nil
 
81
}
 
82
 
 
83
func generateClientKey(dir string) (keyfile string, key ssh.Signer, err error) {
 
84
        private, public, err := GenerateKey("juju-client-key")
 
85
        if err != nil {
 
86
                return "", nil, err
 
87
        }
 
88
        clientPrivateKey, err := ssh.ParsePrivateKey([]byte(private))
 
89
        if err != nil {
 
90
                return "", nil, err
 
91
        }
 
92
        privkeyFilename := filepath.Join(dir, clientKeyName)
 
93
        if err = ioutil.WriteFile(privkeyFilename, []byte(private), 0600); err != nil {
 
94
                return "", nil, err
 
95
        }
 
96
        if err := ioutil.WriteFile(privkeyFilename+PublicKeySuffix, []byte(public), 0600); err != nil {
 
97
                os.Remove(privkeyFilename)
 
98
                return "", nil, err
 
99
        }
 
100
        if err := utils.ChownToUser(privkeyFilename, privkeyFilename+PublicKeySuffix); err != nil {
 
101
                os.Remove(privkeyFilename)
 
102
                os.Remove(privkeyFilename + PublicKeySuffix)
 
103
                return "", nil, err
 
104
        }
 
105
        return privkeyFilename, clientPrivateKey, nil
 
106
}
 
107
 
 
108
func loadClientKeys(dir string) (map[string]ssh.Signer, error) {
 
109
        publicKeyFiles, err := publicKeyFiles(dir)
 
110
        if err != nil {
 
111
                return nil, err
 
112
        }
 
113
        keys := make(map[string]ssh.Signer, len(publicKeyFiles))
 
114
        for _, filename := range publicKeyFiles {
 
115
                filename = filename[:len(filename)-len(PublicKeySuffix)]
 
116
                data, err := ioutil.ReadFile(filename)
 
117
                if err != nil {
 
118
                        return nil, err
 
119
                }
 
120
                keys[filename], err = ssh.ParsePrivateKey(data)
 
121
                if err != nil {
 
122
                        return nil, fmt.Errorf("parsing key file %q: %v", filename, err)
 
123
                }
 
124
        }
 
125
        return keys, nil
 
126
}
 
127
 
 
128
// privateKeys returns the private keys loaded by LoadClientKeys.
 
129
func privateKeys() (signers []ssh.Signer) {
 
130
        clientKeysMutex.Lock()
 
131
        defer clientKeysMutex.Unlock()
 
132
        for _, key := range clientKeys {
 
133
                signers = append(signers, key)
 
134
        }
 
135
        return signers
 
136
}
 
137
 
 
138
// PrivateKeyFiles returns the filenames of private SSH keys loaded by
 
139
// LoadClientKeys.
 
140
func PrivateKeyFiles() []string {
 
141
        clientKeysMutex.Lock()
 
142
        defer clientKeysMutex.Unlock()
 
143
        keyfiles := make([]string, 0, len(clientKeys))
 
144
        for f := range clientKeys {
 
145
                keyfiles = append(keyfiles, f)
 
146
        }
 
147
        return keyfiles
 
148
}
 
149
 
 
150
// PublicKeyFiles returns the filenames of public SSH keys loaded by
 
151
// LoadClientKeys.
 
152
func PublicKeyFiles() []string {
 
153
        privkeys := PrivateKeyFiles()
 
154
        pubkeys := make([]string, len(privkeys))
 
155
        for i, priv := range privkeys {
 
156
                pubkeys[i] = priv + PublicKeySuffix
 
157
        }
 
158
        return pubkeys
 
159
}
 
160
 
 
161
// publicKeyFiles returns the filenames of public SSH keys
 
162
// in the specified directory (all the files ending with .pub).
 
163
func publicKeyFiles(clientKeysDir string) ([]string, error) {
 
164
        if clientKeysDir == "" {
 
165
                return nil, nil
 
166
        }
 
167
        var keys []string
 
168
        dir, err := os.Open(clientKeysDir)
 
169
        if err != nil {
 
170
                return nil, err
 
171
        }
 
172
        names, err := dir.Readdirnames(-1)
 
173
        dir.Close()
 
174
        if err != nil {
 
175
                return nil, err
 
176
        }
 
177
        candidates := set.NewStrings(names...)
 
178
        for _, name := range names {
 
179
                if !strings.HasSuffix(name, PublicKeySuffix) {
 
180
                        continue
 
181
                }
 
182
                // If the private key filename also exists, add the file.
 
183
                priv := name[:len(name)-len(PublicKeySuffix)]
 
184
                if candidates.Contains(priv) {
 
185
                        keys = append(keys, filepath.Join(dir.Name(), name))
 
186
                }
 
187
        }
 
188
        return keys, nil
 
189
}