Merge lp:~thumper/juju-core/system-ssh-key into lp:~go-bot/juju-core/trunk

Proposed by Tim Penhey
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
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.

https://codereview.appspot.com/43730044/

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.

https://codereview.appspot.com/43730044/

To post a comment you must log in.
Revision history for this message
Tim Penhey (thumper) wrote :

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):
   A [revision details]
   M cloudinit/sshinit/configure_test.go
   A doc/system-ssh-key.txt
   M environs/cloudinit.go
   M environs/cloudinit/cloudinit.go
   M environs/cloudinit/cloudinit_test.go
   M environs/manual/bootstrap.go
   M provider/common/bootstrap.go
   M provider/common/bootstrap_test.go
   M provider/ec2/local_test.go
   M provider/local/environ.go
   A utils/ssh/generate.go
   A utils/ssh/generate_test.go
   A utils/ssh/systemidentity.go
   A utils/ssh/systemidentity_test.go

Revision history for this message
Andrew Wilkins (axwalk) wrote :

https://codereview.appspot.com/43730044/diff/1/environs/cloudinit/cloudinit.go
File environs/cloudinit/cloudinit.go (right):

https://codereview.appspot.com/43730044/diff/1/environs/cloudinit/cloudinit.go#newcode262
environs/cloudinit/cloudinit.go:262: identityFile :=
ssh.SystemIdentityFilename(cfg.DataDir)
See comment on ssh.SystemIdentityFilename: the path here should be
obtained with path.Join, not filepath.Join. If you expose
ssh.SystemIdentity, you can just use cfg.dataFile(ssh.SystemIdentity)
here.

https://codereview.appspot.com/43730044/diff/1/provider/common/bootstrap.go
File provider/common/bootstrap.go (right):

https://codereview.appspot.com/43730044/diff/1/provider/common/bootstrap.go#newcode90
provider/common/bootstrap.go:90: func GenerateSystemSSHKey(env
environs.Environ) (privateKey string, err error) {
Doc comment please. Please mention that the function records the public
key in the environment config.

https://codereview.appspot.com/43730044/diff/1/provider/common/bootstrap.go#newcode90
provider/common/bootstrap.go:90: func GenerateSystemSSHKey(env
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://codereview.appspot.com/43730044/diff/1/provider/common/bootstrap.go#newcode95
provider/common/bootstrap.go:95: return "", fmt.Errorf("failed to create
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://codereview.appspot.com/43730044/diff/1/utils/ssh/generate.go
File utils/ssh/generate.go (right):

https://codereview.appspot.com/43730044/diff/1/utils/ssh/generate.go#newcode19
utils/ssh/generate.go:19: func GenerateKey(comment string) (private,
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://codereview.appspot.com/43730044/diff/1/utils/ssh/systemidentity.go
File utils/ssh/systemidentity.go (right):

https://codereview.appspot.com/43730044/diff/1/utils/ssh/systemidentity.go#newcode15
utils/ssh/systemidentity.go:15: func WriteSystemIdentity(dataDir string,
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://codereview.appspot.com/43730044/diff/1/utils/ssh/systemidentity.go#newcode28
utils/ssh/systemidentity.go:28: return filepath.Join(dataDir,
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.

https://codereview.appspot.com/43730044/

Revision history for this message
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://codereview.appspot.com/43730044/diff/1/doc/system-ssh-key.txt
File doc/system-ssh-key.txt (right):

https://codereview.appspot.com/43730044/diff/1/doc/system-ssh-key.txt#newcode2
doc/system-ssh-key.txt:2: potential use-cases.
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://codereview.appspot.com/43730044/diff/1/doc/system-ssh-key.txt#newcode5
doc/system-ssh-key.txt:5: as part of the clout-init machine creation
process. The public key part is
s/clout/cloud/

https://codereview.appspot.com/43730044/diff/1/utils/ssh/systemidentity.go
File utils/ssh/systemidentity.go (right):

https://codereview.appspot.com/43730044/diff/1/utils/ssh/systemidentity.go#newcode15
utils/ssh/systemidentity.go:15: func WriteSystemIdentity(dataDir string,
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?

https://codereview.appspot.com/43730044/

Revision history for this message
Tim Penhey (thumper) wrote :
Download full text (4.0 KiB)

Please take a look.

https://codereview.appspot.com/43730044/diff/1/doc/system-ssh-key.txt
File doc/system-ssh-key.txt (right):

https://codereview.appspot.com/43730044/diff/1/doc/system-ssh-key.txt#newcode2
doc/system-ssh-key.txt:2: potential use-cases.
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://codereview.appspot.com/43730044/diff/1/doc/system-ssh-key.txt#newcode5
doc/system-ssh-key.txt:5: as part of the clout-init machine creation
process. The public key part is
On 2013/12/19 11:59:17, rog wrote:
> s/clout/cloud/

Done.

https://codereview.appspot.com/43730044/diff/1/environs/cloudinit/cloudinit.go
File environs/cloudinit/cloudinit.go (right):

https://codereview.appspot.com/43730044/diff/1/environs/cloudinit/cloudinit.go#newcode262
environs/cloudinit/cloudinit.go:262: identityFile :=
ssh.SystemIdentityFilename(cfg.DataDir)
On 2013/12/19 01:50:05, axw wrote:
> See comment on ssh.SystemIdentityFilename: the path here should be
obtained with
> path.Join, not filepath.Join. If you expose ssh.SystemIdentity, you
can just use
> cfg.dataFile(ssh.SystemIdentity) here.

Done.

https://codereview.appspot.com/43730044/diff/1/provider/common/bootstrap.go
File provider/common/bootstrap.go (right):

https://codereview.appspot.com/43730044/diff/1/provider/common/bootstrap.go#newcode90
provider/common/bootstrap.go:90: func GenerateSystemSSHKey(env
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://codereview.appspot.com/43730044/diff/1/provider/common/bootstrap.go#newcode95
provider/common/bootstrap.go:95: return "", fmt.Errorf("failed to 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://codereview.appspot.com/43730044/diff/1/utils/ssh/generate.go
File utils/ssh/generate.go (right):

https://codereview.appspot.com/43730044/diff/1/utils/ssh/generate.go#newcode19
utils/ssh/generate.go:19: func GenerateKey(comment string) (private,
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://codereview.appspot.com/43730044/diff/1/utils/ssh/systemidentity.go
File utils/ssh/systemidentity.go (right):

https://codereview.appspot.com/43730044/diff/1/utils/ssh/systemidentity.go#newcode15
utils/ssh/systemidentity.go:15: func WriteSystemIdentity(dataDir string,
privateKey string) error {
On 2013/12/19 01:50:05, axw wrote:
> This seems a bit too high-level for the utils/s...

Read more...

Revision history for this message
Andrew Wilkins (axwalk) wrote :
Download full text (4.2 KiB)

On 2013/12/19 22:16:10, thumper wrote:
> Please take a look.

> https://codereview.appspot.com/43730044/diff/1/doc/system-ssh-key.txt
> File doc/system-ssh-key.txt (right):

https://codereview.appspot.com/43730044/diff/1/doc/system-ssh-key.txt#newcode2
> doc/system-ssh-key.txt:2: potential use-cases.
> 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://codereview.appspot.com/43730044/diff/1/doc/system-ssh-key.txt#newcode5
> doc/system-ssh-key.txt:5: as part of the clout-init machine creation
process.
> The public key part is
> On 2013/12/19 11:59:17, rog wrote:
> > s/clout/cloud/

> Done.

https://codereview.appspot.com/43730044/diff/1/environs/cloudinit/cloudinit.go
> File environs/cloudinit/cloudinit.go (right):

https://codereview.appspot.com/43730044/diff/1/environs/cloudinit/cloudinit.go#newcode262
> environs/cloudinit/cloudinit.go:262: identityFile :=
> ssh.SystemIdentityFilename(cfg.DataDir)
> On 2013/12/19 01:50:05, axw wrote:
> > See comment on ssh.SystemIdentityFilename: the path here should be
obtained
> with
> > path.Join, not filepath.Join. If you expose ssh.SystemIdentity, you
can just
> use
> > cfg.dataFile(ssh.SystemIdentity) here.

> Done.

https://codereview.appspot.com/43730044/diff/1/provider/common/bootstrap.go
> File provider/common/bootstrap.go (right):

https://codereview.appspot.com/43730044/diff/1/provider/common/bootstrap.go#newcode90
> provider/common/bootstrap.go:90: func GenerateSystemSSHKey(env
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://codereview.appspot.com/43730044/diff/1/provider/common/bootstrap.go#newcode95
> provider/common/bootstrap.go:95: return "", fmt.Errorf("failed to
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://codereview.appspot.com/43730044/diff/1/utils/ssh/generate.go
> File utils/ssh/generate.go (right):

https://codereview.appspot.com/43730044/diff/1/utils/ssh/generate.go#newcode19
> utils/ssh/generate.go:19: func GenerateKey(comment string) (private,
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://codereview.appspot.com/43730044/diff/1/utils/ssh/systemidentity.go
> File utils/ssh/systemidentity.go (right):

https://codereview.appspot.com/43730044/diff/1/utils/ssh/systemidentity.go#newcode15
> utils/ssh/systemidentity.g...

Read more...

Revision history for this message
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.net/juju-core/utils/ssh
utils/ssh/generate.go:38: undefined: ssh.ParsePrivateKey

Revision history for this message
Go Bot (go-bot) wrote :

Attempt to merge into lp:juju-core failed due to conflicts:

text conflict in provider/common/bootstrap_test.go

Revision history for this message
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.net/juju-core/utils/ssh
utils/ssh/generate.go:38: undefined: ssh.ParsePrivateKey

Revision history for this message
Roger Peppe (rogpeppe) wrote :

https://codereview.appspot.com/43730044/diff/1/provider/common/bootstrap.go
File provider/common/bootstrap.go (right):

https://codereview.appspot.com/43730044/diff/1/provider/common/bootstrap.go#newcode95
provider/common/bootstrap.go:95: return "", fmt.Errorf("failed to create
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://codereview.appspot.com/43730044/diff/20001/doc/system-ssh-key.txt
File doc/system-ssh-key.txt (right):

https://codereview.appspot.com/43730044/diff/20001/doc/system-ssh-key.txt#newcode10
doc/system-ssh-key.txt:10: Juju already creates a private key to connect
to the mongo database. It was an
Strictly speaking it creates a private key for *serving* the mongo
database and the API server.

https://codereview.appspot.com/43730044/diff/20001/doc/system-ssh-key.txt#newcode12
doc/system-ssh-key.txt:12: different purposes just seems like a more
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://codereview.appspot.com/43730044/diff/20001/environs/ssh/systemidentity.go
File environs/ssh/systemidentity.go (right):

https://codereview.appspot.com/43730044/diff/20001/environs/ssh/systemidentity.go#newcode18
environs/ssh/systemidentity.go:18: func WriteSystemIdentity(filename
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 SystemIdentityFilename in environs/cloudinit
and call WriteFile directly inside provider/local, the only caller.

https://codereview.appspot.com/43730044/diff/20001/environs/ssh/systemidentity.go#newcode21
environs/ssh/systemidentity.go:21: logger.Errorf("failed writing system
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.

https://codereview.appspot.com/43730044/

Revision history for this message
Tim Penhey (thumper) wrote :

Please take a look.

https://codereview.appspot.com/43730044/diff/20001/doc/system-ssh-key.txt
File doc/system-ssh-key.txt (right):

https://codereview.appspot.com/43730044/diff/20001/doc/system-ssh-key.txt#newcode12
doc/system-ssh-key.txt:12: different purposes just seems like a more
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://codereview.appspot.com/43730044/diff/20001/environs/ssh/systemidentity.go
File environs/ssh/systemidentity.go (right):

https://codereview.appspot.com/43730044/diff/20001/environs/ssh/systemidentity.go#newcode18
environs/ssh/systemidentity.go:18: func WriteSystemIdentity(filename
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 SystemIdentityFilename in environs/cloudinit
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.

https://codereview.appspot.com/43730044/

Revision history for this message
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.net/juju-core/utils/ssh
utils/ssh/generate.go:38: undefined: ssh.ParsePrivateKey

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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+}

Subscribers

People subscribed via source and target branches

to status/vote changes: