Os login (#50)

Add OS Login support + change key storage format
This commit is contained in:
2021-07-04 18:37:28 +02:00
committed by GitHub
parent ef268fd7b2
commit 91c6dbb2b7
11 changed files with 533 additions and 456 deletions

View File

@@ -1,22 +1,15 @@
package cmd
import (
"crypto/ed25519"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"fmt"
"io/ioutil"
"path/filepath"
gcp "speedrun/cloud"
"speedrun/cloud"
"speedrun/key"
"github.com/alitto/pond"
"github.com/apex/log"
homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"golang.org/x/crypto/ssh"
)
var keyCmd = &cobra.Command{
@@ -34,32 +27,43 @@ var newKeyCmd = &cobra.Command{
RunE: newKey,
}
var setKeyCmd = &cobra.Command{
Use: "set",
Short: "Set key in the project or instance metadata",
Example: " speedrun key set \n speedrun key set --filter \"labels.foo = bar AND labels.environment = staging\"",
var authorizeKeyCmd = &cobra.Command{
Use: "authorize",
Short: "Authorize key for ssh access",
Example: " speedrun key authorize",
PreRun: func(cmd *cobra.Command, args []string) {
initConfig()
},
RunE: setKey,
RunE: authorizeKey,
}
var removeKeyCmd = &cobra.Command{
Use: "remove",
Short: "Remove key from the project metadata or instance metadata",
Example: " speedrun key remove \n speedrun key remove --filter \"labels.foo = bar AND labels.environment = staging\"",
var revokeKeyCmd = &cobra.Command{
Use: "revoke",
Short: "Revoke ssh key",
Example: " speedrun key revoke",
PreRun: func(cmd *cobra.Command, args []string) {
initConfig()
},
RunE: removeKey,
RunE: revokeKey,
}
var listKeysCmd = &cobra.Command{
Use: "list",
Short: "List OS Login keys",
Example: " speedrun key list",
PreRun: func(cmd *cobra.Command, args []string) {
initConfig()
},
RunE: listKeys,
}
func init() {
setKeyCmd.Flags().String("filter", "", "Set the key only on matching instances")
removeKeyCmd.Flags().String("filter", "", "Set the key only on matching instances")
keyCmd.AddCommand(newKeyCmd)
keyCmd.AddCommand(setKeyCmd)
keyCmd.AddCommand(removeKeyCmd)
keyCmd.AddCommand(authorizeKeyCmd)
keyCmd.AddCommand(revokeKeyCmd)
keyCmd.AddCommand(listKeysCmd)
authorizeKeyCmd.Flags().Bool("use-oslogin", false, "Authorize the key via OS Login rather than metadata")
viper.BindPFlag("gcp.use-oslogin", authorizeKeyCmd.Flags().Lookup("use-oslogin"))
}
func determineKeyFilePath() (string, error) {
@@ -74,24 +78,17 @@ func determineKeyFilePath() (string, error) {
}
func newKey(cmd *cobra.Command, args []string) error {
log.Debug("Generating new private key")
_, privKey, err := ed25519.GenerateKey(rand.Reader)
k, err := key.New()
if err != nil {
return err
}
log.Debug("Converting private key to PKCS8 format")
pemBlock := &pem.Block{}
pemBlock.Type = "PRIVATE KEY"
pemBlock.Bytes, err = x509.MarshalPKCS8PrivateKey(privKey)
path, err := determineKeyFilePath()
if err != nil {
return err
}
log.Debug("Encoding the key to PEM format")
privateKey := pem.EncodeToMemory(pemBlock)
err = writeKeyFile(privateKey)
err = k.Write(path)
if err != nil {
return err
}
@@ -100,142 +97,84 @@ func newKey(cmd *cobra.Command, args []string) error {
return nil
}
func writeKeyFile(key []byte) error {
privateKeyPath, err := determineKeyFilePath()
func authorizeKey(cmd *cobra.Command, args []string) error {
project := viper.GetString("gcp.projectid")
useOSlogin := viper.GetBool("gcp.use-oslogin")
gcpClient, err := cloud.NewGCPClient(project)
if err != nil {
return err
}
log.Debugf("Writing priviate key to %s", privateKeyPath)
err = ioutil.WriteFile(privateKeyPath, key, 0600)
if err != nil {
return err
}
return nil
}
func loadKeyPair() (ssh.PublicKey, ssh.Signer, error) {
privateKeyPath, err := determineKeyFilePath()
if err != nil {
return nil, nil, err
}
file, err := readKeyFile(privateKeyPath)
if err != nil {
return nil, nil, fmt.Errorf("couldn't find private key. Use 'speedrun key new' to generate a new one")
}
signer, err := ssh.ParsePrivateKey(file)
if err != nil {
return nil, nil, err
}
pubKey := signer.PublicKey()
return pubKey, signer, nil
}
func readKeyFile(path string) ([]byte, error) {
cleanPath := filepath.Clean(path)
absPath, err := filepath.Abs(cleanPath)
if err != nil {
return nil, err
}
file, err := ioutil.ReadFile(absPath)
if err != nil {
return nil, err
}
return file, nil
}
func setKey(cmd *cobra.Command, args []string) error {
client, err := gcp.NewComputeClient(viper.GetString("gcp.projectid"))
path, err := determineKeyFilePath()
if err != nil {
return err
}
pubKey, _, err := loadKeyPair()
k, err := key.Read(path)
if err != nil {
return err
}
filter, err := cmd.Flags().GetString("filter")
if err != nil {
return err
}
if filter != "" {
log.Info("Setting public key in the instance metadata")
instances, err := client.GetInstances(filter)
if useOSlogin {
gcpClient.AddUserKey(k)
if err != nil {
return err
}
if len(instances) == 0 {
log.Warn("no instances found")
}
pool := pond.New(10, 0, pond.MinWorkers(10))
for i := 0; i < len(instances); i++ {
n := i
pool.Submit(func() {
client.AddKeyToMetadata(instances[n], pubKey)
})
}
pool.StopAndWait()
log.Info("Authorized key via OS Login")
} else {
log.Info("Setting public key in the project metadata")
err = client.AddKeyToMetadataP(pubKey)
gcpClient.AddKeyToMetadata(k)
if err != nil {
return err
}
log.Info("Authorized key in the project metadata")
}
return nil
}
func removeKey(cmd *cobra.Command, args []string) error {
client, err := gcp.NewComputeClient(viper.GetString("gcp.projectid"))
func revokeKey(cmd *cobra.Command, args []string) error {
project := viper.GetString("gcp.projectid")
gcpClient, err := cloud.NewGCPClient(project)
if err != nil {
return err
}
pubKey, _, err := loadKeyPair()
path, err := determineKeyFilePath()
if err != nil {
return err
}
filter, err := cmd.Flags().GetString("filter")
k, err := key.Read(path)
if err != nil {
return err
}
if filter != "" {
log.Info("Removing public from the instance metadata")
instances, err := client.GetInstances(filter)
if err != nil {
return err
}
log.Info("Revoking public key")
err = gcpClient.RemoveKeyFromMetadata(k)
if err != nil {
return err
}
if len(instances) == 0 {
log.Warn("no instances found")
}
pool := pond.New(10, 0, pond.MinWorkers(10))
for i := 0; i < len(instances); i++ {
n := i
pool.Submit(func() {
client.RemoveKeyFromMetadata(instances[n], pubKey)
})
}
pool.StopAndWait()
} else {
log.Info("Removing public key from the project metadata")
err = client.RemoveKeyFromMetadataP(pubKey)
if err != nil {
return err
}
err = gcpClient.RemoveUserKey(k)
if err != nil {
return err
}
return nil
}
func listKeys(cmd *cobra.Command, args []string) error {
project := viper.GetString("gcp.projectid")
gcpClient, err := cloud.NewGCPClient(project)
if err != nil {
return err
}
log.Info("Fetching OS Login keys")
err = gcpClient.ListUserKeys()
if err != nil {
return err
}
return nil