Merge lp:~thumper/juju-core/system-ssh-key into lp:~go-bot/juju-core/trunk
- system-ssh-key
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Tim Penhey |
Approved revision: | no longer in the source branch. |
Merged at revision: | 2182 |
Proposed branch: | lp:~thumper/juju-core/system-ssh-key |
Merge into: | lp:~go-bot/juju-core/trunk |
Diff against target: |
458 lines (+198/-22) 13 files modified
cloudinit/sshinit/configure_test.go (+1/-1) dependencies.tsv (+1/-1) doc/system-ssh-key.txt (+24/-0) environs/cloudinit.go (+2/-1) environs/cloudinit/cloudinit.go (+13/-0) environs/cloudinit/cloudinit_test.go (+17/-11) environs/manual/bootstrap.go (+7/-1) provider/common/bootstrap.go (+29/-1) provider/common/bootstrap_test.go (+12/-3) provider/ec2/local_test.go (+2/-1) provider/local/environ.go (+13/-2) utils/ssh/generate.go (+49/-0) utils/ssh/generate_test.go (+28/-0) |
To merge this branch: | bzr merge lp:~thumper/juju-core/system-ssh-key |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju Engineering | Pending | ||
Review via email: mp+199572@code.launchpad.net |
Commit message
Create a system ssh identity on bootstrap
When an environment is bootstrapped, a system ssh
identity is created and written to the data directory.
The public key part is added to the authorized keys.
Description of the change
Create a system ssh identity on bootstrap
When an environment is bootstrapped, a system ssh
identity is created and written to the data directory.
The public key part is added to the authorized keys.
Tim Penhey (thumper) wrote : | # |
Andrew Wilkins (axwalk) wrote : | # |
https:/
File environs/
https:/
environs/
ssh.SystemIdent
See comment on ssh.SystemIdent
obtained with path.Join, not filepath.Join. If you expose
ssh.SystemIdentity, you can just use cfg.dataFile(
here.
https:/
File provider/
https:/
provider/
environs.Environ) (privateKey string, err error) {
Doc comment please. Please mention that the function records the public
key in the environment config.
https:/
provider/
environs.Environ) (privateKey string, err error) {
Can you please add a test for this function in bootstrap_test.go, to
make sure the public key is added to the environment's config?
https:/
provider/
system key: %v", err)
I'm told the errors should be worded like "creating system key", so it
ends up as
error: creating system key: <thing>
(I've traditionally done it the same way as you have here, mind.)
https:/
File utils/ssh/
https:/
utils/ssh/
public string, err error) {
Doc comment please. What are the properties of the keys? It's RSA,
2048-bits, unencrypted (i.e. no passphrase), with the private key
encoded as PEM and the public key encoded in authorized_keys format. I
think it'd be good to spell all of that out.
https:/
File utils/ssh/
https:/
utils/ssh/
privateKey string) error {
This seems a bit too high-level for the utils/ssh package. It probably
ought to go in environs/ssh, as a "system identity" is a Juju-level
concept rather than an SSH-level one.
I'm probably being a bit too anal though, so I won't insist.
https:/
utils/ssh/
systemIdentity)
Using filepath.Join isn't correct in the case of bootstrapping from
Windows. I'd say just expose the systemIdentity constant, and use the
appropriate path/filepath.Join depending on the context.
Roger Peppe (rogpeppe) wrote : | # |
Looks reasonable if we actually want to do this, but I have my
reservations. I'd like more information on why we're doing this.
https:/
File doc/system-
https:/
doc/system-
I'd like to see a few of those use cases outlined here.
Also, we already have a system private key. Can't we just use that?
https:/
doc/system-
process. The public key part is
s/clout/cloud/
https:/
File utils/ssh/
https:/
utils/ssh/
privateKey string) error {
On 2013/12/19 01:50:05, axw wrote:
> This seems a bit too high-level for the utils/ssh package. It probably
ought to
> go in environs/ssh, as a "system identity" is a Juju-level concept
rather than
> an SSH-level one.
+1
This definitely feels like the wrong place to me. Doesn't
environs/cloudinit encapsulate most of this kind of knowledge?
Tim Penhey (thumper) wrote : | # |
Please take a look.
https:/
File doc/system-
https:/
doc/system-
On 2013/12/19 11:59:17, rog wrote:
> I'd like to see a few of those use cases outlined here.
> Also, we already have a system private key. Can't we just use that?
Added some. Also made the decision to use different keys for different
uses rather than overloading use.
https:/
doc/system-
process. The public key part is
On 2013/12/19 11:59:17, rog wrote:
> s/clout/cloud/
Done.
https:/
File environs/
https:/
environs/
ssh.SystemIdent
On 2013/12/19 01:50:05, axw wrote:
> See comment on ssh.SystemIdent
obtained with
> path.Join, not filepath.Join. If you expose ssh.SystemIdentity, you
can just use
> cfg.dataFile(
Done.
https:/
File provider/
https:/
provider/
environs.Environ) (privateKey string, err error) {
On 2013/12/19 01:50:05, axw wrote:
> Doc comment please. Please mention that the function records the
public key in
> the environment config.
Done.
https:/
provider/
system key: %v", err)
On 2013/12/19 01:50:05, axw wrote:
> I'm told the errors should be worded like "creating system key", so it
ends up
> as
> error: creating system key: <thing>
> (I've traditionally done it the same way as you have here, mind.)
I'd say that this is dumb.
https:/
File utils/ssh/
https:/
utils/ssh/
public string, err error) {
On 2013/12/19 01:50:05, axw wrote:
> Doc comment please. What are the properties of the keys? It's RSA,
2048-bits,
> unencrypted (i.e. no passphrase), with the private key encoded as PEM
and the
> public key encoded in authorized_keys format. I think it'd be good to
spell all
> of that out.
OK
https:/
File utils/ssh/
https:/
utils/ssh/
privateKey string) error {
On 2013/12/19 01:50:05, axw wrote:
> This seems a bit too high-level for the utils/s...
Andrew Wilkins (axwalk) wrote : | # |
On 2013/12/19 22:16:10, thumper wrote:
> Please take a look.
> https:/
> File doc/system-
https:/
> doc/system-
> On 2013/12/19 11:59:17, rog wrote:
> > I'd like to see a few of those use cases outlined here.
> >
> > Also, we already have a system private key. Can't we just use that?
> Added some. Also made the decision to use different keys for different
uses
> rather than overloading use.
https:/
> doc/system-
process.
> The public key part is
> On 2013/12/19 11:59:17, rog wrote:
> > s/clout/cloud/
> Done.
https:/
> File environs/
https:/
> environs/
> ssh.SystemIdent
> On 2013/12/19 01:50:05, axw wrote:
> > See comment on ssh.SystemIdent
obtained
> with
> > path.Join, not filepath.Join. If you expose ssh.SystemIdentity, you
can just
> use
> > cfg.dataFile(
> Done.
https:/
> File provider/
https:/
> provider/
environs.Environ)
> (privateKey string, err error) {
> On 2013/12/19 01:50:05, axw wrote:
> > Doc comment please. Please mention that the function records the
public key in
> > the environment config.
> Done.
https:/
> provider/
create system
> key: %v", err)
> On 2013/12/19 01:50:05, axw wrote:
> > I'm told the errors should be worded like "creating system key", so
it ends up
> > as
> > error: creating system key: <thing>
> >
> > (I've traditionally done it the same way as you have here, mind.)
> I'd say that this is dumb.
> https:/
> File utils/ssh/
https:/
> utils/ssh/
public
> string, err error) {
> On 2013/12/19 01:50:05, axw wrote:
> > Doc comment please. What are the properties of the keys? It's RSA,
2048-bits,
> > unencrypted (i.e. no passphrase), with the private key encoded as
PEM and the
> > public key encoded in authorized_keys format. I think it'd be good
to spell
> all
> > of that out.
> OK
https:/
> File utils/ssh/
https:/
> utils/ssh/
Go Bot (go-bot) wrote : | # |
The attempt to merge lp:~thumper/juju-core/system-ssh-key into lp:juju-core failed. Below is the output from the failed tests.
# launchpad.
utils/ssh/
Go Bot (go-bot) wrote : | # |
Attempt to merge into lp:juju-core failed due to conflicts:
text conflict in provider/
Go Bot (go-bot) wrote : | # |
The attempt to merge lp:~thumper/juju-core/system-ssh-key into lp:juju-core failed. Below is the output from the failed tests.
# launchpad.
utils/ssh/
Roger Peppe (rogpeppe) wrote : | # |
https:/
File provider/
https:/
provider/
system key: %v", err)
On 2013/12/19 22:16:11, thumper wrote:
> On 2013/12/19 01:50:05, axw wrote:
> > I'm told the errors should be worded like "creating system key", so
it ends up
> > as
> > error: creating system key: <thing>
> >
> > (I've traditionally done it the same way as you have here, mind.)
> I'd say that this is dumb.
I agree with both of you here. I think it's good if the error message
still makes sense even when the subsidiary error after the colon is
removed.
https:/
File doc/system-
https:/
doc/system-
to the mongo database. It was an
Strictly speaking it creates a private key for *serving* the mongo
database and the API server.
https:/
doc/system-
robust idea.
I'd like a less hand-wavy justification than this.
Every extra secret lying around is another potential system
vulnerability.
On the other hand, if there *is* a good reason for using different keys
for different purposes, perhaps we should consider using different keys
for serving the API server and for the mongo server.
https:/
File environs/
https:/
environs/
string, privateKey string) error {
We're creating an entire new package for a single constant and a
function that's semantically almost identical to ioutil.WriteFile?
Please let's just define SystemIdentityF
and call WriteFile directly inside provider/local, the only caller.
https:/
environs/
identity: %v", err)
If we've got an error return, please return the more informative error
rather than logging it. The error should be logged later at a higher
level.
Tim Penhey (thumper) wrote : | # |
Please take a look.
https:/
File doc/system-
https:/
doc/system-
robust idea.
On 2013/12/20 09:19:42, rog wrote:
> I'd like a less hand-wavy justification than this.
> Every extra secret lying around is another potential system
vulnerability.
> On the other hand, if there *is* a good reason for using different
keys for
> different purposes, perhaps we should consider using different keys
for serving
> the API server and for the mongo server.
When I originally wrote this, I wasn't really intending to commit it to
the tree, so the prose was somewhat fast and loose.
https:/
File environs/
https:/
environs/
string, privateKey string) error {
On 2013/12/20 09:19:42, rog wrote:
> We're creating an entire new package for a single constant and a
function that's
> semantically almost identical to ioutil.WriteFile?
> Please let's just define SystemIdentityF
and call
> WriteFile directly inside provider/local, the only caller.
Yeah, I have done this now.
Originally I was going to have this module abstract away more of the
information of the system identity file, but now as you can see it
doesn't do much. I have now removed this and just call write from the
local provider.
Go Bot (go-bot) wrote : | # |
The attempt to merge lp:~thumper/juju-core/system-ssh-key into lp:juju-core failed. Below is the output from the failed tests.
# launchpad.
utils/ssh/
Preview Diff
1 | === modified file 'cloudinit/sshinit/configure_test.go' |
2 | --- cloudinit/sshinit/configure_test.go 2013-12-10 14:43:21 +0000 |
3 | +++ cloudinit/sshinit/configure_test.go 2014-01-05 22:29:00 +0000 |
4 | @@ -53,7 +53,7 @@ |
5 | func (s *configureSuite) getCloudConfig(c *gc.C, stateServer bool, vers version.Binary) *cloudinit.Config { |
6 | var mcfg *envcloudinit.MachineConfig |
7 | if stateServer { |
8 | - mcfg = environs.NewBootstrapMachineConfig("http://whatever/dotcom") |
9 | + mcfg = environs.NewBootstrapMachineConfig("http://whatever/dotcom", "private-key") |
10 | } else { |
11 | mcfg = environs.NewMachineConfig("0", "ya", nil, nil) |
12 | } |
13 | |
14 | === modified file 'dependencies.tsv' |
15 | --- dependencies.tsv 2013-12-19 03:18:49 +0000 |
16 | +++ dependencies.tsv 2014-01-05 22:29:00 +0000 |
17 | @@ -1,4 +1,4 @@ |
18 | -code.google.com/p/go.crypto hg 1747226a2f43c3f146f77dede1adf7073461b616 141 |
19 | +code.google.com/p/go.crypto hg 6478cc9340cbbe6c04511280c5007722269108e9 184 |
20 | code.google.com/p/go.net hg 3591c18acabc99439c783463ef00e6dc277eee39 77 |
21 | labix.org/v2/mgo bzr gustavo@niemeyer.net-20130830194015-zk51jnp704tqmp7n 240 |
22 | launchpad.net/gnuflag bzr roger.peppe@canonical.com-20121003093437-zcyyw0lpvj2nifpk 12 |
23 | |
24 | === added file 'doc/system-ssh-key.txt' |
25 | --- doc/system-ssh-key.txt 1970-01-01 00:00:00 +0000 |
26 | +++ doc/system-ssh-key.txt 2014-01-05 22:29:00 +0000 |
27 | @@ -0,0 +1,24 @@ |
28 | +The idea of having a system SSH key is to support a number of real and |
29 | +potential use-cases. |
30 | + * The system ssh key could be used to monitor the bootstrap process, and this |
31 | + would benefit the new users that don't have an existing SSH key |
32 | + * Allows the api server machines to ssh to other machines in the environment |
33 | + * could be used to set up ssh tunnels through a single public facing IP |
34 | + address on the server |
35 | + * allows juju-run commands to be run on remote machiens |
36 | + |
37 | +Juju already creates a private key for serving the mongo database. It was an |
38 | +option to also use this key, but in the end, having different keys for |
39 | +different purposes just seems like a more robust idea. |
40 | + |
41 | +A system key is generated when the environment is bootstrapped, and uploaded |
42 | +as part of the cloud-init machine creation process. The public key part is |
43 | +added to the authorized keys list. |
44 | + |
45 | +This means that we need to generate an identity file and the authorized key |
46 | +line prior to creating the new machine. |
47 | + |
48 | +If subsequent state server machines are created, they also need to have the |
49 | +system identity file on them. Actually, it is most likely the API server jobs |
50 | +that we really care about. |
51 | + |
52 | |
53 | === modified file 'environs/cloudinit.go' |
54 | --- environs/cloudinit.go 2013-12-03 06:04:43 +0000 |
55 | +++ environs/cloudinit.go 2014-01-05 22:29:00 +0000 |
56 | @@ -42,12 +42,13 @@ |
57 | // bootstrap node. You'll still need to supply more information, but this |
58 | // takes care of the fixed entries and the ones that are always needed. |
59 | // stateInfoURL is the storage URL for the environment's state file. |
60 | -func NewBootstrapMachineConfig(stateInfoURL string) *cloudinit.MachineConfig { |
61 | +func NewBootstrapMachineConfig(stateInfoURL string, privateSystemSSHKey string) *cloudinit.MachineConfig { |
62 | // For a bootstrap instance, FinishMachineConfig will provide the |
63 | // state.Info and the api.Info. The machine id must *always* be "0". |
64 | mcfg := NewMachineConfig("0", state.BootstrapNonce, nil, nil) |
65 | mcfg.StateServer = true |
66 | mcfg.StateInfoURL = stateInfoURL |
67 | + mcfg.SystemPrivateSSHKey = privateSystemSSHKey |
68 | return mcfg |
69 | } |
70 | |
71 | |
72 | === modified file 'environs/cloudinit/cloudinit.go' |
73 | --- environs/cloudinit/cloudinit.go 2013-12-18 15:15:23 +0000 |
74 | +++ environs/cloudinit/cloudinit.go 2014-01-05 22:29:00 +0000 |
75 | @@ -33,6 +33,9 @@ |
76 | // is bootstrapping. |
77 | const BootstrapStateURLFile = "/tmp/provider-state-url" |
78 | |
79 | +// SystemIdentity is the name of the file where the environment SSH key is kept. |
80 | +const SystemIdentity = "system-identity" |
81 | + |
82 | // fileSchemePrefix is the prefix for file:// URLs. |
83 | const fileSchemePrefix = "file://" |
84 | |
85 | @@ -118,6 +121,11 @@ |
86 | // DisableSSLHostnameVerification can be set to true to tell cloud-init |
87 | // that it shouldn't verify SSL certificates |
88 | DisableSSLHostnameVerification bool |
89 | + |
90 | + // SystemPrivateSSHKey is created at bootstrap time and recorded on every |
91 | + // node that has an API server. At this stage, that is any machine where |
92 | + // StateServer (member above) is set to true. |
93 | + SystemPrivateSSHKey string |
94 | } |
95 | |
96 | func base64yaml(m *config.Config) string { |
97 | @@ -270,6 +278,8 @@ |
98 | cfg.MaybeAddCloudArchiveCloudTools(c) |
99 | |
100 | if cfg.StateServer { |
101 | + identityFile := cfg.dataFile(SystemIdentity) |
102 | + c.AddFile(identityFile, cfg.SystemPrivateSSHKey, 0600) |
103 | // Disable the default mongodb installed by the mongodb-server package. |
104 | // Only do this if the file doesn't exist already, so users can run |
105 | // their own mongodb server if they wish to. |
106 | @@ -607,6 +617,9 @@ |
107 | if cfg.APIPort == 0 { |
108 | return fmt.Errorf("missing API port") |
109 | } |
110 | + if cfg.SystemPrivateSSHKey == "" { |
111 | + return fmt.Errorf("missing system ssh identity") |
112 | + } |
113 | } else { |
114 | if len(cfg.StateInfo.Addrs) == 0 { |
115 | return fmt.Errorf("missing state hosts") |
116 | |
117 | === modified file 'environs/cloudinit/cloudinit_test.go' |
118 | --- environs/cloudinit/cloudinit_test.go 2013-12-17 12:13:50 +0000 |
119 | +++ environs/cloudinit/cloudinit_test.go 2014-01-05 22:29:00 +0000 |
120 | @@ -81,9 +81,10 @@ |
121 | Password: "bletch", |
122 | CACert: []byte("CA CERT\n" + testing.CACert), |
123 | }, |
124 | - Constraints: envConstraints, |
125 | - DataDir: environs.DataDir, |
126 | - StateInfoURL: "some-url", |
127 | + Constraints: envConstraints, |
128 | + DataDir: environs.DataDir, |
129 | + StateInfoURL: "some-url", |
130 | + SystemPrivateSSHKey: "private rsa key", |
131 | }, |
132 | setEnvConfig: true, |
133 | expectScripts: ` |
134 | @@ -112,6 +113,8 @@ |
135 | install -m 600 /dev/null '/var/lib/juju/agents/machine-0/agent\.conf' |
136 | printf '%s\\n' '.*' > '/var/lib/juju/agents/machine-0/agent\.conf' |
137 | ln -s /var/lib/juju/tools/machine-0/jujud /usr/local/bin/juju-run |
138 | +install -D -m 600 /dev/null '/var/lib/juju/system-identity' |
139 | +printf '%s\\n' '.*' > '/var/lib/juju/system-identity' |
140 | install -D -m 600 /dev/null '/var/lib/juju/server\.pem' |
141 | printf '%s\\n' 'SERVER CERT\\n[^']*SERVER KEY\\n[^']*' > '/var/lib/juju/server\.pem' |
142 | mkdir -p /var/lib/juju/db/journal |
143 | @@ -159,9 +162,10 @@ |
144 | Password: "bletch", |
145 | CACert: []byte("CA CERT\n" + testing.CACert), |
146 | }, |
147 | - Constraints: envConstraints, |
148 | - DataDir: environs.DataDir, |
149 | - StateInfoURL: "some-url", |
150 | + Constraints: envConstraints, |
151 | + DataDir: environs.DataDir, |
152 | + StateInfoURL: "some-url", |
153 | + SystemPrivateSSHKey: "private rsa key", |
154 | }, |
155 | setEnvConfig: true, |
156 | inexactMatch: true, |
157 | @@ -320,8 +324,9 @@ |
158 | Password: "bletch", |
159 | CACert: []byte("CA CERT\n" + testing.CACert), |
160 | }, |
161 | - DataDir: environs.DataDir, |
162 | - StateInfoURL: "some-url", |
163 | + DataDir: environs.DataDir, |
164 | + StateInfoURL: "some-url", |
165 | + SystemPrivateSSHKey: "private rsa key", |
166 | }, |
167 | setEnvConfig: true, |
168 | inexactMatch: true, |
169 | @@ -721,9 +726,10 @@ |
170 | Addrs: []string{"host:9999"}, |
171 | CACert: []byte(testing.CACert), |
172 | }, |
173 | - Config: minimalConfig(c), |
174 | - DataDir: environs.DataDir, |
175 | - MachineNonce: "FAKE_NONCE", |
176 | + Config: minimalConfig(c), |
177 | + DataDir: environs.DataDir, |
178 | + MachineNonce: "FAKE_NONCE", |
179 | + SystemPrivateSSHKey: "private rsa key", |
180 | } |
181 | // check that the base configuration does not give an error |
182 | ci := coreCloudinit.New() |
183 | |
184 | === modified file 'environs/manual/bootstrap.go' |
185 | --- environs/manual/bootstrap.go 2013-12-19 08:40:11 +0000 |
186 | +++ environs/manual/bootstrap.go 2014-01-05 22:29:00 +0000 |
187 | @@ -12,6 +12,7 @@ |
188 | "launchpad.net/juju-core/environs/bootstrap" |
189 | envtools "launchpad.net/juju-core/environs/tools" |
190 | "launchpad.net/juju-core/instance" |
191 | + "launchpad.net/juju-core/provider/common" |
192 | "launchpad.net/juju-core/tools" |
193 | "launchpad.net/juju-core/worker/localstorage" |
194 | ) |
195 | @@ -121,9 +122,14 @@ |
196 | return err |
197 | } |
198 | |
199 | + privateKey, err := common.GenerateSystemSSHKey(args.Environ) |
200 | + if err != nil { |
201 | + return err |
202 | + } |
203 | + |
204 | // Finally, provision the machine agent. |
205 | stateFileURL := fmt.Sprintf("file://%s/%s", storageDir, bootstrap.StateFile) |
206 | - mcfg := environs.NewBootstrapMachineConfig(stateFileURL) |
207 | + mcfg := environs.NewBootstrapMachineConfig(stateFileURL, privateKey) |
208 | if args.DataDir != "" { |
209 | mcfg.DataDir = args.DataDir |
210 | } |
211 | |
212 | === modified file 'provider/common/bootstrap.go' |
213 | --- provider/common/bootstrap.go 2013-12-20 02:38:56 +0000 |
214 | +++ provider/common/bootstrap.go 2014-01-05 22:29:00 +0000 |
215 | @@ -46,7 +46,12 @@ |
216 | if err != nil { |
217 | return err |
218 | } |
219 | - machineConfig := environs.NewBootstrapMachineConfig(stateFileURL) |
220 | + |
221 | + privateKey, err := GenerateSystemSSHKey(env) |
222 | + if err != nil { |
223 | + return err |
224 | + } |
225 | + machineConfig := environs.NewBootstrapMachineConfig(stateFileURL, privateKey) |
226 | |
227 | selectedTools, err := EnsureBootstrapTools(env, env.Config().DefaultSeries(), cons.Arch) |
228 | if err != nil { |
229 | @@ -76,6 +81,29 @@ |
230 | return FinishBootstrap(ctx, inst, machineConfig) |
231 | } |
232 | |
233 | +// GenerateSystemSSHKey creates a new key for the system identity. The |
234 | +// authorized_keys in the environment config is updated to include the public |
235 | +// key for the generated key. |
236 | +func GenerateSystemSSHKey(env environs.Environ) (privateKey string, err error) { |
237 | + logger.Debugf("generate a system ssh key") |
238 | + // Create a new system ssh key and add that to the authorized keys. |
239 | + privateKey, publicKey, err := ssh.GenerateKey("juju-system-key") |
240 | + if err != nil { |
241 | + return "", fmt.Errorf("failed to create system key: %v", err) |
242 | + } |
243 | + authorized_keys := env.Config().AuthorizedKeys() + publicKey |
244 | + newConfig, err := env.Config().Apply(map[string]interface{}{ |
245 | + "authorized-keys": authorized_keys, |
246 | + }) |
247 | + if err != nil { |
248 | + return "", fmt.Errorf("failed to create new config: %v", err) |
249 | + } |
250 | + if err = env.SetConfig(newConfig); err != nil { |
251 | + return "", fmt.Errorf("failed to set new config: %v", err) |
252 | + } |
253 | + return privateKey, nil |
254 | +} |
255 | + |
256 | // handelBootstrapError cleans up after a failed bootstrap. |
257 | func handleBootstrapError(err error, ctx environs.BootstrapContext, inst instance.Instance, env environs.Environ) { |
258 | if err == nil { |
259 | |
260 | === modified file 'provider/common/bootstrap_test.go' |
261 | --- provider/common/bootstrap_test.go 2013-12-20 09:25:57 +0000 |
262 | +++ provider/common/bootstrap_test.go 2014-01-05 22:29:00 +0000 |
263 | @@ -107,7 +107,7 @@ |
264 | instance.Instance, *instance.HardwareCharacteristics, error, |
265 | ) { |
266 | c.Assert(cons, gc.DeepEquals, checkCons) |
267 | - c.Assert(mcfg, gc.DeepEquals, environs.NewBootstrapMachineConfig(checkURL)) |
268 | + c.Assert(mcfg, gc.DeepEquals, environs.NewBootstrapMachineConfig(checkURL, mcfg.SystemPrivateSSHKey)) |
269 | return nil, nil, fmt.Errorf("meh, not started") |
270 | } |
271 | |
272 | @@ -209,11 +209,15 @@ |
273 | checkURL = mcfg.StateInfoURL |
274 | return &mockInstance{id: checkInstanceId}, &checkHardware, nil |
275 | } |
276 | - |
277 | + var mocksConfig = minimalConfig(c) |
278 | var getConfigCalled int |
279 | getConfig := func() *config.Config { |
280 | getConfigCalled++ |
281 | - return minimalConfig(c) |
282 | + return mocksConfig |
283 | + } |
284 | + setConfig := func(c *config.Config) error { |
285 | + mocksConfig = c |
286 | + return nil |
287 | } |
288 | |
289 | restore := envtesting.DisableFinishBootstrap() |
290 | @@ -223,7 +227,9 @@ |
291 | storage: stor, |
292 | startInstance: startInstance, |
293 | config: getConfig, |
294 | + setConfig: setConfig, |
295 | } |
296 | + originalAuthKeys := env.Config().AuthorizedKeys() |
297 | ctx, _ := bootstrapContext(c) |
298 | err := common.Bootstrap(ctx, env, constraints.Value{}) |
299 | c.Assert(err, gc.IsNil) |
300 | @@ -234,6 +240,9 @@ |
301 | StateInstances: []instance.Id{instance.Id(checkInstanceId)}, |
302 | Characteristics: []instance.HardwareCharacteristics{checkHardware}, |
303 | }) |
304 | + authKeys := env.Config().AuthorizedKeys() |
305 | + c.Assert(authKeys, gc.Not(gc.Equals), originalAuthKeys) |
306 | + c.Assert(authKeys, jc.HasSuffix, "juju-system-key\n") |
307 | } |
308 | |
309 | type neverRefreshes struct { |
310 | |
311 | === modified file 'provider/ec2/local_test.go' |
312 | --- provider/ec2/local_test.go 2013-12-20 02:38:56 +0000 |
313 | +++ provider/ec2/local_test.go 2014-01-05 22:29:00 +0000 |
314 | @@ -240,11 +240,12 @@ |
315 | var userDataMap map[interface{}]interface{} |
316 | err = goyaml.Unmarshal(userData, &userDataMap) |
317 | c.Assert(err, gc.IsNil) |
318 | + expectedAuthKeys := strings.TrimSpace(env.Config().AuthorizedKeys()) |
319 | c.Assert(userDataMap, gc.DeepEquals, map[interface{}]interface{}{ |
320 | "output": map[interface{}]interface{}{ |
321 | "all": "| tee -a /var/log/cloud-init-output.log", |
322 | }, |
323 | - "ssh_authorized_keys": []interface{}{"my-keys"}, |
324 | + "ssh_authorized_keys": []interface{}{expectedAuthKeys}, |
325 | "runcmd": []interface{}{ |
326 | "install -D -m 644 /dev/null '/var/lib/juju/nonce.txt'", |
327 | "printf '%s\\n' 'user-admin:bootstrap' > '/var/lib/juju/nonce.txt'", |
328 | |
329 | === modified file 'provider/local/environ.go' |
330 | --- provider/local/environ.go 2013-12-19 08:40:11 +0000 |
331 | +++ provider/local/environ.go 2014-01-05 22:29:00 +0000 |
332 | @@ -114,6 +114,10 @@ |
333 | } |
334 | |
335 | // TODO(thumper): check that the constraints don't include "container=lxc" for now. |
336 | + privateKey, err := common.GenerateSystemSSHKey(env) |
337 | + if err != nil { |
338 | + return err |
339 | + } |
340 | |
341 | cert, key, err := env.setupLocalMongoService() |
342 | if err != nil { |
343 | @@ -151,7 +155,7 @@ |
344 | return err |
345 | } |
346 | |
347 | - return env.setupLocalMachineAgent(cons, selectedTools) |
348 | + return env.setupLocalMachineAgent(cons, selectedTools, privateKey) |
349 | } |
350 | |
351 | // StateInfo is specified in the Environ interface. |
352 | @@ -459,11 +463,18 @@ |
353 | return cert, key, nil |
354 | } |
355 | |
356 | -func (env *localEnviron) setupLocalMachineAgent(cons constraints.Value, possibleTools tools.List) error { |
357 | +func (env *localEnviron) setupLocalMachineAgent(cons constraints.Value, possibleTools tools.List, privateKey string) error { |
358 | dataDir := env.config.rootDir() |
359 | // unpack the first tools into the agent dir. |
360 | agentTools := possibleTools[0] |
361 | logger.Debugf("tools: %#v", agentTools) |
362 | + // save the system identity file |
363 | + systemIdentityFilename := filepath.Join(dataDir, cloudinit.SystemIdentity) |
364 | + logger.Debugf("writing system identity to %s", systemIdentityFilename) |
365 | + if err := ioutil.WriteFile(systemIdentityFilename, []byte(privateKey), 0600); err != nil { |
366 | + return fmt.Errorf("failed to write system identity: %v", err) |
367 | + } |
368 | + |
369 | // brutally abuse our knowledge of storage to directly open the file |
370 | toolsUrl, err := url.Parse(agentTools.URL) |
371 | if err != nil { |
372 | |
373 | === added file 'utils/ssh/generate.go' |
374 | --- utils/ssh/generate.go 1970-01-01 00:00:00 +0000 |
375 | +++ utils/ssh/generate.go 2014-01-05 22:29:00 +0000 |
376 | @@ -0,0 +1,49 @@ |
377 | +// Copyright 2013 Canonical Ltd. |
378 | +// Licensed under the AGPLv3, see LICENCE file for details. |
379 | + |
380 | +package ssh |
381 | + |
382 | +import ( |
383 | + "crypto/rand" |
384 | + "crypto/rsa" |
385 | + "crypto/x509" |
386 | + "encoding/pem" |
387 | + "fmt" |
388 | + "strings" |
389 | + |
390 | + "code.google.com/p/go.crypto/ssh" |
391 | +) |
392 | + |
393 | +// KeyBits is used to determine the number of bits to use for the RSA keys |
394 | +// created using the GenerateKey function. |
395 | +var KeyBits = 2048 |
396 | + |
397 | +// GenerateKey makes a 2048 bit RSA no-passphrase SSH capable key. The bit |
398 | +// size is actually controlled by the KeyBits var. The private key returned is |
399 | +// encoded to ASCII using the PKCS1 encoding. The public key is suitable to |
400 | +// be added into an authorized_keys file, and has the comment passed in as the |
401 | +// comment part of the key. |
402 | +func GenerateKey(comment string) (private, public string, err error) { |
403 | + key, err := rsa.GenerateKey(rand.Reader, KeyBits) |
404 | + if err != nil { |
405 | + return "", "", err |
406 | + } |
407 | + |
408 | + identity := pem.EncodeToMemory( |
409 | + &pem.Block{ |
410 | + Type: "RSA PRIVATE KEY", |
411 | + Bytes: x509.MarshalPKCS1PrivateKey(key), |
412 | + }) |
413 | + |
414 | + signer, err := ssh.ParsePrivateKey(identity) |
415 | + if err != nil { |
416 | + return "", "", fmt.Errorf("failed to load key: %v", err) |
417 | + } |
418 | + |
419 | + auth_key := string(ssh.MarshalAuthorizedKey(signer.PublicKey())) |
420 | + // Strip off the trailing new line so we can add a comment. |
421 | + auth_key = strings.TrimSpace(auth_key) |
422 | + public = fmt.Sprintf("%s %s\n", auth_key, comment) |
423 | + |
424 | + return string(identity), public, nil |
425 | +} |
426 | |
427 | === added file 'utils/ssh/generate_test.go' |
428 | --- utils/ssh/generate_test.go 1970-01-01 00:00:00 +0000 |
429 | +++ utils/ssh/generate_test.go 2014-01-05 22:29:00 +0000 |
430 | @@ -0,0 +1,28 @@ |
431 | +// Copyright 2013 Canonical Ltd. |
432 | +// Licensed under the AGPLv3, see LICENCE file for details. |
433 | + |
434 | +package ssh_test |
435 | + |
436 | +import ( |
437 | + gc "launchpad.net/gocheck" |
438 | + |
439 | + jc "launchpad.net/juju-core/testing/checkers" |
440 | + "launchpad.net/juju-core/testing/testbase" |
441 | + "launchpad.net/juju-core/utils/ssh" |
442 | +) |
443 | + |
444 | +type GenerateSuite struct { |
445 | + testbase.LoggingSuite |
446 | +} |
447 | + |
448 | +var _ = gc.Suite(&GenerateSuite{}) |
449 | + |
450 | +func (s *GenerateSuite) TestGenerate(c *gc.C) { |
451 | + private, public, err := ssh.GenerateKey("some-comment") |
452 | + |
453 | + c.Check(err, gc.IsNil) |
454 | + c.Check(private, jc.HasPrefix, "-----BEGIN RSA PRIVATE KEY-----\n") |
455 | + c.Check(private, jc.HasSuffix, "-----END RSA PRIVATE KEY-----\n") |
456 | + c.Check(public, jc.HasPrefix, "ssh-rsa ") |
457 | + c.Check(public, jc.HasSuffix, " some-comment\n") |
458 | +} |
Reviewers: mp+199572_ code.launchpad. net,
Message:
Please take a look.
Description:
Create a system ssh identity on bootstrap
When an environment is bootstrapped, a system ssh
identity is created and written to the data directory.
The public key part is added to the authorized keys.
https:/ /code.launchpad .net/~thumper/ juju-core/ system- ssh-key/ +merge/ 199572
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/43730044/
Affected files (+228, -19 lines): sshinit/ configure_ test.go ssh-key. txt cloudinit. go cloudinit/ cloudinit. go cloudinit/ cloudinit_ test.go manual/ bootstrap. go common/ bootstrap. go common/ bootstrap_ test.go ec2/local_ test.go local/environ. go generate. go generate_ test.go systemidentity. go systemidentity_ test.go
A [revision details]
M cloudinit/
A doc/system-
M environs/
M environs/
M environs/
M environs/
M provider/
M provider/
M provider/
M provider/
A utils/ssh/
A utils/ssh/
A utils/ssh/
A utils/ssh/