github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/upgrades/systemsshkey.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package upgrades
     5  
     6  import (
     7  	"io/ioutil"
     8  	"os"
     9  
    10  	"github.com/juju/errors"
    11  	"gopkg.in/mgo.v2/txn"
    12  
    13  	"github.com/juju/juju/api/keymanager"
    14  	"github.com/juju/juju/environs/config"
    15  	"github.com/juju/juju/state"
    16  	"github.com/juju/juju/utils/ssh"
    17  )
    18  
    19  func ensureSystemSSHKey(context Context) error {
    20  	privateKey, publicKey, err := readOrMakeSystemIdentity(context)
    21  	if err != nil {
    22  		return errors.Trace(err)
    23  	}
    24  	if publicKey == "" {
    25  		// privateKey was read from disk, so it exists.
    26  		return nil
    27  	}
    28  	if err := updateAuthorizedKeys(context, publicKey); err != nil {
    29  		return errors.Trace(err)
    30  	}
    31  	if err := writeSystemIdentity(context, privateKey); err != nil {
    32  		return errors.Trace(err)
    33  	}
    34  	return nil
    35  }
    36  
    37  // Somewhere in the 1.20 cycle the system-identity was added to the
    38  // state serving info collection in the database, however no migration
    39  // step was added to take the identity file from disk and put it into the
    40  // new value in the database.
    41  func ensureSystemSSHKeyRedux(context Context) error {
    42  	// If there is a system-identity in the database already, we don't need to
    43  	// do anything.
    44  	stateInfo, err := context.State().StateServingInfo()
    45  	if err != nil {
    46  		logger.Errorf("failed to read state serving info: %v", err)
    47  		return errors.Trace(err)
    48  	}
    49  	if stateInfo.SystemIdentity != "" {
    50  		logger.Infof("state serving info has a system identity already, all good")
    51  		// We are good. One exists already.
    52  		// Make sure that the agent thinks that it is the same.
    53  		return updateSystemIdentityInAgentConfig(context, stateInfo.SystemIdentity)
    54  	}
    55  
    56  	privateKey, publicKey, err := readOrMakeSystemIdentity(context)
    57  	if err != nil {
    58  		logger.Errorf("failed to read or make system identity: %v", err)
    59  		return errors.Trace(err)
    60  	}
    61  
    62  	if err := state.SetSystemIdentity(context.State(), privateKey); err != nil {
    63  		if errors.Cause(err) == txn.ErrAborted {
    64  			logger.Errorf("someone else has set system identity already")
    65  			// Another state server upgrading concurrently has updated
    66  			// the system identity so it is no longer empty. So discard
    67  			// anything that was created, reread the system info and write
    68  			// out the file.  We also assume that the other upgrade has
    69  			// updated the authorized keys already.
    70  			stateInfo, err := context.State().StateServingInfo()
    71  			if err != nil {
    72  				logger.Errorf("failed to read state serving info: %v", err)
    73  				return errors.Trace(err)
    74  			}
    75  			if stateInfo.SystemIdentity == "" {
    76  				logger.Errorf("but the transaction said it would be there...")
    77  				return errors.New("system identity is not set")
    78  			}
    79  			if err := writeSystemIdentity(context, stateInfo.SystemIdentity); err != nil {
    80  				logger.Errorf("failed to write the system identity file: %v", err)
    81  				return errors.Trace(err)
    82  			}
    83  			return updateSystemIdentityInAgentConfig(context, stateInfo.SystemIdentity)
    84  		}
    85  
    86  		logger.Errorf("failed to set system identity: %v", err)
    87  		return errors.Annotate(err, "cannot set state serving info")
    88  	}
    89  
    90  	if publicKey != "" {
    91  		if err := writeSystemIdentity(context, privateKey); err != nil {
    92  			return errors.Trace(err)
    93  		}
    94  	}
    95  	return updateSystemIdentityInAgentConfig(context, privateKey)
    96  }
    97  
    98  // updateAuthorizedKeysForSystemIdentity makes sure that the authorized keys
    99  // list is up to date with the system identity.  Due to changes in the way
   100  // upgrades are done in 1.22, this part, which uses the API had to be split
   101  // from the first part which used the state connection.
   102  func updateAuthorizedKeysForSystemIdentity(context Context) error {
   103  	agentInfo, ok := context.AgentConfig().StateServingInfo()
   104  	if !ok {
   105  		return errors.New("missing state serving info for the agent")
   106  	}
   107  	publicKey, err := ssh.PublicKey([]byte(agentInfo.SystemIdentity), config.JujuSystemKey)
   108  	if err != nil {
   109  		return errors.Trace(err)
   110  	}
   111  	return errors.Trace(updateAuthorizedKeys(context, publicKey))
   112  }
   113  
   114  func updateAuthorizedKeys(context Context, publicKey string) error {
   115  	// Look for an existing authorized key.
   116  	logger.Infof("setting new authorized key for %q", publicKey)
   117  	keyManager := keymanager.NewClient(context.APIState())
   118  
   119  	result, err := keyManager.ListKeys(ssh.FullKeys, config.JujuSystemKey)
   120  	if err != nil {
   121  		return errors.Trace(err)
   122  	}
   123  	if result[0].Error != nil {
   124  		return errors.Trace(result[0].Error)
   125  	}
   126  	keys := result[0].Result
   127  
   128  	// Loop through the keys. If we find a key that matches the publicKey
   129  	// then we are good, and done.  If the comment on the key is for the system identity
   130  	// but it is not the same, remove it.
   131  	var keysToRemove []string
   132  	for _, key := range keys {
   133  		// The list of keys returned don't have carriage returns, but the
   134  		// publicKey does, so add one one before testing for equality.
   135  		if (key + "\n") == publicKey {
   136  			logger.Infof("system identity key already in authorized list")
   137  			return nil
   138  		}
   139  
   140  		fingerprint, comment, err := ssh.KeyFingerprint(key)
   141  		if err != nil {
   142  			// Log the error, but it doesn't stop us doing what we need to do.
   143  			logger.Errorf("bad key in authorized keys: %v", err)
   144  		} else if comment == config.JujuSystemKey {
   145  			keysToRemove = append(keysToRemove, fingerprint)
   146  		}
   147  	}
   148  	if keysToRemove != nil {
   149  		logger.Infof("removing %d keys", len(keysToRemove))
   150  		results, err := keyManager.DeleteKeys(config.JujuSystemKey, keysToRemove...)
   151  		if err != nil {
   152  			// Log the error but continue.
   153  			logger.Errorf("failed to remove keys: %v", err)
   154  		} else {
   155  			for _, err := range results {
   156  				if err.Error != nil {
   157  					// Log the error but continue.
   158  					logger.Errorf("failed to remove key: %v", err.Error)
   159  				}
   160  			}
   161  		}
   162  	}
   163  
   164  	errResults, err := keyManager.AddKeys(config.JujuSystemKey, publicKey)
   165  	if err != nil {
   166  		return errors.Annotate(err, "failed to update authorised keys with new system key")
   167  	}
   168  	if err := errResults[0].Error; err != nil {
   169  		return errors.Annotate(err, "failed to update authorised keys with new system key")
   170  	}
   171  	return nil
   172  }
   173  
   174  func updateSystemIdentityInAgentConfig(context Context, systemIdentity string) error {
   175  	agentInfo, ok := context.AgentConfig().StateServingInfo()
   176  	if !ok {
   177  		return errors.New("missing state serving info for the agent")
   178  	}
   179  	if agentInfo.SystemIdentity != systemIdentity {
   180  		agentInfo.SystemIdentity = systemIdentity
   181  		context.AgentConfig().SetStateServingInfo(agentInfo)
   182  	}
   183  	return nil
   184  }
   185  
   186  func readOrMakeSystemIdentity(context Context) (privateKey, publicKey string, err error) {
   187  	identityFile := context.AgentConfig().SystemIdentityPath()
   188  	// Don't generate a key unless we have to.
   189  	keyExists, err := systemKeyExists(identityFile)
   190  	if err != nil {
   191  		return "", "", errors.Annotate(err, "failed to check system key exists")
   192  	}
   193  	if keyExists {
   194  		logger.Infof("key exists, reading contents")
   195  
   196  		// Read the contents.
   197  		contents, err := ioutil.ReadFile(identityFile)
   198  		if err != nil {
   199  			return "", "", errors.Trace(err)
   200  		}
   201  		// If we are just reading the private key,
   202  		return string(contents), "", nil
   203  	}
   204  
   205  	logger.Infof("generating new key")
   206  	privateKey, publicKey, err = ssh.GenerateKey(config.JujuSystemKey)
   207  	if err != nil {
   208  		return "", "", errors.Annotate(err, "failed to create system key")
   209  	}
   210  	return privateKey, publicKey, nil
   211  }
   212  
   213  func writeSystemIdentity(context Context, privateKey string) error {
   214  	identityFile := context.AgentConfig().SystemIdentityPath()
   215  	logger.Infof("writing system identity to %q", identityFile)
   216  	if err := ioutil.WriteFile(identityFile, []byte(privateKey), 0600); err != nil {
   217  		return errors.Annotate(err, "failed to write identity file")
   218  	}
   219  	return nil
   220  }
   221  
   222  func systemKeyExists(identityFile string) (bool, error) {
   223  	_, err := os.Stat(identityFile)
   224  	if err == nil {
   225  		logger.Infof("identity file %q exists", identityFile)
   226  		return true, nil
   227  	}
   228  	if !os.IsNotExist(err) {
   229  		logger.Infof("error looking for identity file %q: %v", identityFile, err)
   230  		return false, err
   231  	}
   232  	logger.Infof("identity file %q does not exist", identityFile)
   233  	return false, nil
   234  }