mirror of https://github.com/kubernetes/kops.git
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
bc26eecf49
|
@ -7,8 +7,8 @@ import (
|
||||||
// createCmd represents the create command
|
// createCmd represents the create command
|
||||||
var createCmd = &cobra.Command{
|
var createCmd = &cobra.Command{
|
||||||
Use: "create",
|
Use: "create",
|
||||||
Short: "create clusters",
|
Short: "create resources",
|
||||||
Long: `Create clusters`,
|
Long: `Create resources`,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CreateSecretCommand struct {
|
||||||
|
cobraCommand *cobra.Command
|
||||||
|
}
|
||||||
|
|
||||||
|
var createSecretCmd = CreateSecretCommand{
|
||||||
|
cobraCommand: &cobra.Command{
|
||||||
|
Use: "secret",
|
||||||
|
Short: "Create secrets",
|
||||||
|
Long: `Create secrets.`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cmd := createSecretCmd.cobraCommand
|
||||||
|
|
||||||
|
createCmd.AddCommand(cmd)
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"io/ioutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CreateSecretPublickeyCommand struct {
|
||||||
|
cobraCommand *cobra.Command
|
||||||
|
|
||||||
|
Pubkey string
|
||||||
|
}
|
||||||
|
|
||||||
|
var createSecretPublickeyCommand = CreateSecretPublickeyCommand{
|
||||||
|
cobraCommand: &cobra.Command{
|
||||||
|
Use: "sshpublickey",
|
||||||
|
Short: "Create SSH publickey",
|
||||||
|
Long: `Create SSH publickey.`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cmd := createSecretPublickeyCommand.cobraCommand
|
||||||
|
|
||||||
|
cmd.Run = func(cmd *cobra.Command, args []string) {
|
||||||
|
err := createSecretPublickeyCommand.Run(args)
|
||||||
|
if err != nil {
|
||||||
|
exitWithError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Flags().StringVarP(&createSecretPublickeyCommand.Pubkey, "pubkey", "i", "", "Path to SSH public key")
|
||||||
|
|
||||||
|
createSecretCmd.cobraCommand.AddCommand(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd *CreateSecretPublickeyCommand) Run(args []string) error {
|
||||||
|
if len(args) == 0 {
|
||||||
|
return fmt.Errorf("syntax: NAME -i <PublicKeyPath>")
|
||||||
|
}
|
||||||
|
if len(args) != 1 {
|
||||||
|
return fmt.Errorf("syntax: NAME -i <PublicKeyPath>")
|
||||||
|
}
|
||||||
|
name := args[0]
|
||||||
|
|
||||||
|
if cmd.Pubkey == "" {
|
||||||
|
return fmt.Errorf("pubkey path is required (use -i)")
|
||||||
|
}
|
||||||
|
|
||||||
|
caStore, err := rootCommand.KeyStore()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := ioutil.ReadFile(cmd.Pubkey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error reading SSH public key %v: %v", cmd.Pubkey, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = caStore.AddSSHPublicKey(name, data)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error adding SSH public key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,153 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
//
|
||||||
|
//import (
|
||||||
|
// "fmt"
|
||||||
|
//
|
||||||
|
// "crypto/x509"
|
||||||
|
// "github.com/golang/glog"
|
||||||
|
// "github.com/spf13/cobra"
|
||||||
|
// "k8s.io/kops/upup/pkg/fi"
|
||||||
|
// "net"
|
||||||
|
// "strings"
|
||||||
|
//)
|
||||||
|
//
|
||||||
|
//type CreateSecretsCommand struct {
|
||||||
|
// Id string
|
||||||
|
// Type string
|
||||||
|
//
|
||||||
|
// Usage string
|
||||||
|
// Subject string
|
||||||
|
// AlternateNames []string
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//var createSecretsCommand CreateSecretsCommand
|
||||||
|
//
|
||||||
|
//func init() {
|
||||||
|
// cmd := &cobra.Command{
|
||||||
|
// Use: "secret",
|
||||||
|
// Short: "Create secrets",
|
||||||
|
// Long: `Create secrets.`,
|
||||||
|
// Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
// err := createSecretsCommand.Run()
|
||||||
|
// if err != nil {
|
||||||
|
// exitWithError(err)
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// createCmd.AddCommand(cmd)
|
||||||
|
//
|
||||||
|
// cmd.Flags().StringVarP(&createSecretsCommand.Type, "type", "", "", "Type of secret to create")
|
||||||
|
// cmd.Flags().StringVarP(&createSecretsCommand.Id, "id", "", "", "Id of secret to create")
|
||||||
|
// cmd.Flags().StringVarP(&createSecretsCommand.Usage, "usage", "", "", "Usage of secret (for SSL certificate)")
|
||||||
|
// cmd.Flags().StringVarP(&createSecretsCommand.Subject, "subject", "", "", "Subject (for SSL certificate)")
|
||||||
|
// cmd.Flags().StringSliceVarP(&createSecretsCommand.AlternateNames, "san", "", nil, "Alternate name (for SSL certificate)")
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func (cmd *CreateSecretsCommand) Run() error {
|
||||||
|
// if cmd.Id == "" {
|
||||||
|
// return fmt.Errorf("id is required")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if cmd.Type == "" {
|
||||||
|
// return fmt.Errorf("type is required")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // TODO: Prompt before replacing?
|
||||||
|
// // TODO: Keep history?
|
||||||
|
//
|
||||||
|
// if strings.ToLower(cmd.Type) == strings.ToLower(fi.SecretTypeSecret) {
|
||||||
|
// return fmt.Errorf("creating secrets of type %q not (currently) supported", cmd.Type)
|
||||||
|
// //{
|
||||||
|
// // secretStore, err := rootCommand.SecretStore()
|
||||||
|
// // if err != nil {
|
||||||
|
// // return err
|
||||||
|
// // }
|
||||||
|
// // secret, err := fi.CreateSecret()
|
||||||
|
// // if err != nil {
|
||||||
|
// // return fmt.Errorf("error creating secret: %v", err)
|
||||||
|
// // }
|
||||||
|
// // _, created, err := secretStore.GetOrCreateSecret(cmd.Id, secret)
|
||||||
|
// // if err != nil {
|
||||||
|
// // return fmt.Errorf("error creating secret: %v", err)
|
||||||
|
// // }
|
||||||
|
// // if !created {
|
||||||
|
// // return fmt.Errorf("secret already exists")
|
||||||
|
// // }
|
||||||
|
// // return nil
|
||||||
|
// //}
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if strings.ToLower(cmd.Type) == strings.ToLower(fi.SecretTypeKeypair) {
|
||||||
|
// return fmt.Errorf("creating secrets of type %q not (currently) supported", cmd.Type)
|
||||||
|
// //
|
||||||
|
// //// TODO: Create a rotate command which keeps the same values?
|
||||||
|
// //// Or just do it here a "replace" action - existing=fail, replace or rotate
|
||||||
|
// //// TODO: Create a CreateKeypair class, move to fi (this is duplicated code)
|
||||||
|
// //{
|
||||||
|
// // if cmd.Subject == "" {
|
||||||
|
// // return fmt.Errorf("subject is required")
|
||||||
|
// // }
|
||||||
|
// //
|
||||||
|
// // subject, err := parsePkixName(cmd.Subject)
|
||||||
|
// // if err != nil {
|
||||||
|
// // return fmt.Errorf("Error parsing subject: %v", err)
|
||||||
|
// // }
|
||||||
|
// // template := &x509.Certificate{
|
||||||
|
// // Subject: *subject,
|
||||||
|
// // BasicConstraintsValid: true,
|
||||||
|
// // IsCA: false,
|
||||||
|
// // }
|
||||||
|
// //
|
||||||
|
// // if len(template.Subject.ToRDNSequence()) == 0 {
|
||||||
|
// // return fmt.Errorf("Subject name was empty")
|
||||||
|
// // }
|
||||||
|
// //
|
||||||
|
// // switch cmd.Usage {
|
||||||
|
// // case "client":
|
||||||
|
// // template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}
|
||||||
|
// // template.KeyUsage = x509.KeyUsageDigitalSignature
|
||||||
|
// // break
|
||||||
|
// //
|
||||||
|
// // case "server":
|
||||||
|
// // template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
|
||||||
|
// // template.KeyUsage = x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment
|
||||||
|
// // break
|
||||||
|
// //
|
||||||
|
// // default:
|
||||||
|
// // return fmt.Errorf("unknown usage: %q", cmd.Usage)
|
||||||
|
// // }
|
||||||
|
// //
|
||||||
|
// // for _, san := range cmd.AlternateNames {
|
||||||
|
// // san = strings.TrimSpace(san)
|
||||||
|
// // if san == "" {
|
||||||
|
// // continue
|
||||||
|
// // }
|
||||||
|
// // if ip := net.ParseIP(san); ip != nil {
|
||||||
|
// // template.IPAddresses = append(template.IPAddresses, ip)
|
||||||
|
// // } else {
|
||||||
|
// // template.DNSNames = append(template.DNSNames, san)
|
||||||
|
// // }
|
||||||
|
// // }
|
||||||
|
// //
|
||||||
|
// // caStore, err := rootCommand.KeyStore()
|
||||||
|
// // if err != nil {
|
||||||
|
// // return err
|
||||||
|
// // }
|
||||||
|
// //
|
||||||
|
// // // TODO: Allow resigning of the existing private key?
|
||||||
|
// //
|
||||||
|
// // _, _, err = caStore.CreateKeypair(cmd.Id, template)
|
||||||
|
// // if err != nil {
|
||||||
|
// // return fmt.Errorf("error creating keypair %v", err)
|
||||||
|
// // }
|
||||||
|
// // return nil
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if strings.ToLower(cmd.Type) == strings.ToLower(fi.SecretTypeSSHPublicKey) {
|
||||||
|
// return fmt.Errorf("creating secrets of type %q not (currently) supported", cmd.Type)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return fmt.Errorf("secret type not known: %q", cmd.Type)
|
||||||
|
//}
|
|
@ -0,0 +1,79 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"k8s.io/kops/upup/pkg/fi"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DeleteSecretCmd struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
var deleteSecretCmd DeleteSecretCmd
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "secret",
|
||||||
|
Short: "Delete secret",
|
||||||
|
Long: `Delete a secret.`,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
err := deleteSecretCmd.Run(args)
|
||||||
|
if err != nil {
|
||||||
|
exitWithError(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteCmd.AddCommand(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DeleteSecretCmd) Run(args []string) error {
|
||||||
|
if len(args) != 2 && len(args) != 3 {
|
||||||
|
return fmt.Errorf("Syntax: <type> <name> [<id>]")
|
||||||
|
}
|
||||||
|
|
||||||
|
secretType := args[0]
|
||||||
|
secretName := args[1]
|
||||||
|
|
||||||
|
secretID := ""
|
||||||
|
if len(args) == 3 {
|
||||||
|
secretID = args[2]
|
||||||
|
}
|
||||||
|
|
||||||
|
secrets, err := listSecrets(secretType, []string{secretName})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if secretID != "" {
|
||||||
|
var matches []*fi.KeystoreItem
|
||||||
|
for _, s := range secrets {
|
||||||
|
if s.Id == secretID {
|
||||||
|
matches = append(matches, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
secrets = matches
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(secrets) == 0 {
|
||||||
|
return fmt.Errorf("secret %q not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(secrets) != 1 {
|
||||||
|
// TODO: it would be friendly to print the matching keys
|
||||||
|
return fmt.Errorf("found multiple matching secrets; specify the id of the key")
|
||||||
|
}
|
||||||
|
|
||||||
|
keyStore, err := rootCommand.KeyStore()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = keyStore.DeleteSecret(secrets[0])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error deleting secret: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DescribeCmd represents the describe command
|
||||||
|
type DescribeCmd struct {
|
||||||
|
cobraCommand *cobra.Command
|
||||||
|
}
|
||||||
|
|
||||||
|
var describeCmd = DescribeCmd{
|
||||||
|
cobraCommand: &cobra.Command{
|
||||||
|
Use: "describe",
|
||||||
|
Short: "describe objects",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cmd := describeCmd.cobraCommand
|
||||||
|
|
||||||
|
rootCommand.AddCommand(cmd)
|
||||||
|
}
|
|
@ -0,0 +1,155 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"bytes"
|
||||||
|
"crypto/rsa"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"k8s.io/kops/upup/pkg/fi"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"text/tabwriter"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DescribeSecretsCommand struct {
|
||||||
|
Type string
|
||||||
|
}
|
||||||
|
|
||||||
|
var describeSecretsCommand DescribeSecretsCommand
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "secrets",
|
||||||
|
Aliases: []string{"secret"},
|
||||||
|
Short: "Describe secrets",
|
||||||
|
Long: `Describe secrets.`,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
err := describeSecretsCommand.Run(args)
|
||||||
|
if err != nil {
|
||||||
|
exitWithError(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
describeCmd.cobraCommand.AddCommand(cmd)
|
||||||
|
|
||||||
|
cmd.Flags().StringVarP(&describeSecretsCommand.Type, "type", "", "", "Filter by secret type")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DescribeSecretsCommand) Run(args []string) error {
|
||||||
|
items, err := listSecrets(c.Type, args)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(items) == 0 {
|
||||||
|
fmt.Fprintf(os.Stderr, "No secrets found\n")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
w := new(tabwriter.Writer)
|
||||||
|
var b bytes.Buffer
|
||||||
|
|
||||||
|
// Format in tab-separated columns with a tab stop of 8.
|
||||||
|
w.Init(os.Stdout, 0, 8, 0, '\t', tabwriter.StripEscape)
|
||||||
|
|
||||||
|
keyStore, err := rootCommand.KeyStore()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, i := range items {
|
||||||
|
fmt.Fprintf(w, "Name:\t%s\n", i.Name)
|
||||||
|
fmt.Fprintf(w, "Type:\t%s\n", i.Type)
|
||||||
|
fmt.Fprintf(w, "Id:\t%s\n", i.Id)
|
||||||
|
|
||||||
|
switch i.Type {
|
||||||
|
case fi.SecretTypeKeypair:
|
||||||
|
err = describeKeypair(keyStore, i, &b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
case fi.SecretTypeSSHPublicKey:
|
||||||
|
err = describeSSHPublicKey(i, &b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
case fi.SecretTypeSecret:
|
||||||
|
err = describeSecret(i, &b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.WriteString("\n")
|
||||||
|
_, err = w.Write(b.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error writing to output: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
return w.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func describeKeypair(keyStore fi.CAStore, item *fi.KeystoreItem, w *bytes.Buffer) error {
|
||||||
|
name := item.Name
|
||||||
|
|
||||||
|
cert, err := keyStore.FindCert(name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error retrieving cert %q: %v", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
key, err := keyStore.FindPrivateKey(name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error retrieving private key %q: %v", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var alternateNames []string
|
||||||
|
if cert != nil {
|
||||||
|
alternateNames = append(alternateNames, cert.Certificate.DNSNames...)
|
||||||
|
alternateNames = append(alternateNames, cert.Certificate.EmailAddresses...)
|
||||||
|
for _, ip := range cert.Certificate.IPAddresses {
|
||||||
|
alternateNames = append(alternateNames, ip.String())
|
||||||
|
}
|
||||||
|
sort.Strings(alternateNames)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cert != nil {
|
||||||
|
fmt.Fprintf(w, "Subject:\t%s\n", pkixNameToString(&cert.Certificate.Subject))
|
||||||
|
fmt.Fprintf(w, "Issuer:\t%s\n", pkixNameToString(&cert.Certificate.Issuer))
|
||||||
|
fmt.Fprintf(w, "AlternateNames:\t%s\n", strings.Join(alternateNames, ", "))
|
||||||
|
fmt.Fprintf(w, "CA:\t%v\n", cert.IsCA)
|
||||||
|
fmt.Fprintf(w, "NotAfter:\t%s\n", cert.Certificate.NotAfter)
|
||||||
|
fmt.Fprintf(w, "NotBefore:\t%s\n", cert.Certificate.NotBefore)
|
||||||
|
|
||||||
|
// PublicKeyAlgorithm doesn't have a String() function. Also, is this important information?
|
||||||
|
//fmt.Fprintf(w, "PublicKeyAlgorithm:\t%v\n", c.Certificate.PublicKeyAlgorithm)
|
||||||
|
//fmt.Fprintf(w, "SignatureAlgorithm:\t%v\n", c.Certificate.SignatureAlgorithm)
|
||||||
|
}
|
||||||
|
|
||||||
|
if key != nil {
|
||||||
|
if rsaPrivateKey, ok := key.Key.(*rsa.PrivateKey); ok {
|
||||||
|
fmt.Fprintf(w, "PrivateKeyType:\t%v\n", "rsa")
|
||||||
|
fmt.Fprintf(w, "KeyLength:\t%v\n", rsaPrivateKey.N.BitLen())
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(w, "PrivateKeyType:\tunknown (%T)\n", key.Key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func describeSecret(item *fi.KeystoreItem, w *bytes.Buffer) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func describeSSHPublicKey(item *fi.KeystoreItem, w *bytes.Buffer) error {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -129,7 +129,7 @@ func (t *Table) Render(items interface{}, out io.Writer, columnNames ...string)
|
||||||
w := new(tabwriter.Writer)
|
w := new(tabwriter.Writer)
|
||||||
|
|
||||||
// Format in tab-separated columns with a tab stop of 8.
|
// Format in tab-separated columns with a tab stop of 8.
|
||||||
w.Init(out, 0, 8, 0, '\t', tabwriter.StripEscape)
|
w.Init(out, 0, 8, 1, '\t', tabwriter.StripEscape)
|
||||||
|
|
||||||
writeHeader := true
|
writeHeader := true
|
||||||
if writeHeader {
|
if writeHeader {
|
||||||
|
|
|
@ -4,14 +4,31 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// getCmd represents the get command
|
// GetCmd represents the get command
|
||||||
var getCmd = &cobra.Command{
|
type GetCmd struct {
|
||||||
Use: "get",
|
output string
|
||||||
SuggestFor: []string{"list"},
|
|
||||||
Short: "list or get obejcts",
|
cobraCommand *cobra.Command
|
||||||
Long: `list or get obejcts`,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
var getCmd = GetCmd{
|
||||||
rootCommand.AddCommand(getCmd)
|
cobraCommand: &cobra.Command{
|
||||||
|
Use: "get",
|
||||||
|
SuggestFor: []string{"list"},
|
||||||
|
Short: "list or get objects",
|
||||||
|
Long: `list or get objects`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
OutputYaml = "yaml"
|
||||||
|
OutputTable = "table"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cmd := getCmd.cobraCommand
|
||||||
|
|
||||||
|
rootCommand.AddCommand(cmd)
|
||||||
|
|
||||||
|
cmd.PersistentFlags().StringVarP(&getCmd.output, "output", "o", OutputTable, "output format. One of: table, yaml")
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"fmt"
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"k8s.io/kops/upup/pkg/api"
|
"k8s.io/kops/upup/pkg/api"
|
||||||
|
@ -10,6 +11,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type GetClustersCmd struct {
|
type GetClustersCmd struct {
|
||||||
|
FullSpec bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var getClustersCmd GetClustersCmd
|
var getClustersCmd GetClustersCmd
|
||||||
|
@ -21,28 +23,34 @@ func init() {
|
||||||
Short: "get clusters",
|
Short: "get clusters",
|
||||||
Long: `List or get clusters.`,
|
Long: `List or get clusters.`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
err := getClustersCmd.Run()
|
err := getClustersCmd.Run(args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Exitf("%v", err)
|
glog.Exitf("%v", err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
getCmd.AddCommand(cmd)
|
getCmd.cobraCommand.AddCommand(cmd)
|
||||||
|
|
||||||
|
cmd.Flags().BoolVar(&getClustersCmd.FullSpec, "full", false, "Show fully populated configuration")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GetClustersCmd) Run() error {
|
func (c *GetClustersCmd) Run(args []string) error {
|
||||||
clusterRegistry, err := rootCommand.ClusterRegistry()
|
clusterRegistry, err := rootCommand.ClusterRegistry()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
clusterNames, err := clusterRegistry.List()
|
var clusters []*api.Cluster
|
||||||
if err != nil {
|
|
||||||
return err
|
clusterNames := args
|
||||||
|
if len(args) == 0 {
|
||||||
|
clusterNames, err = clusterRegistry.List()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var clusters []*api.Cluster
|
|
||||||
for _, clusterName := range clusterNames {
|
for _, clusterName := range clusterNames {
|
||||||
cluster, err := clusterRegistry.Find(clusterName)
|
cluster, err := clusterRegistry.Find(clusterName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -50,28 +58,59 @@ func (c *GetClustersCmd) Run() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if cluster == nil {
|
if cluster == nil {
|
||||||
glog.Warningf("cluster was listed, but then not found %q", clusterName)
|
return fmt.Errorf("cluster not found %q", clusterName)
|
||||||
}
|
}
|
||||||
|
|
||||||
clusters = append(clusters, cluster)
|
clusters = append(clusters, cluster)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(clusters) == 0 {
|
if len(clusters) == 0 {
|
||||||
|
fmt.Fprintf(os.Stderr, "No clusters found\n")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
t := &Table{}
|
output := getCmd.output
|
||||||
t.AddColumn("NAME", func(c *api.Cluster) string {
|
if output == OutputTable {
|
||||||
return c.Name
|
t := &Table{}
|
||||||
})
|
t.AddColumn("NAME", func(c *api.Cluster) string {
|
||||||
t.AddColumn("CLOUD", func(c *api.Cluster) string {
|
return c.Name
|
||||||
return c.Spec.CloudProvider
|
})
|
||||||
})
|
t.AddColumn("CLOUD", func(c *api.Cluster) string {
|
||||||
t.AddColumn("ZONES", func(c *api.Cluster) string {
|
return c.Spec.CloudProvider
|
||||||
var zoneNames []string
|
})
|
||||||
for _, z := range c.Spec.Zones {
|
t.AddColumn("ZONES", func(c *api.Cluster) string {
|
||||||
zoneNames = append(zoneNames, z.Name)
|
var zoneNames []string
|
||||||
|
for _, z := range c.Spec.Zones {
|
||||||
|
zoneNames = append(zoneNames, z.Name)
|
||||||
|
}
|
||||||
|
return strings.Join(zoneNames, ",")
|
||||||
|
})
|
||||||
|
return t.Render(clusters, os.Stdout, "NAME", "CLOUD", "ZONES")
|
||||||
|
} else if output == OutputYaml {
|
||||||
|
if c.FullSpec {
|
||||||
|
var fullSpecs []*api.Cluster
|
||||||
|
for _, cluster := range clusters {
|
||||||
|
spec, err := clusterRegistry.ReadCompletedConfig(cluster.Name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error reading full cluster spec for %q: %v", cluster.Name, err)
|
||||||
|
}
|
||||||
|
fullSpecs = append(fullSpecs, spec)
|
||||||
|
}
|
||||||
|
clusters = fullSpecs
|
||||||
}
|
}
|
||||||
return strings.Join(zoneNames, ",")
|
|
||||||
})
|
for _, cluster := range clusters {
|
||||||
return t.Render(clusters, os.Stdout, "NAME", "CLOUD", "ZONES")
|
y, err := api.ToYaml(cluster)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error marshaling yaml for %q: %v", cluster.Name, err)
|
||||||
|
}
|
||||||
|
_, err = os.Stdout.Write(y)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error writing to stdout: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("Unknown output format: %q", output)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"fmt"
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"k8s.io/kops/upup/pkg/api"
|
"k8s.io/kops/upup/pkg/api"
|
||||||
|
@ -22,17 +23,17 @@ func init() {
|
||||||
Short: "get instancegroups",
|
Short: "get instancegroups",
|
||||||
Long: `List or get InstanceGroups.`,
|
Long: `List or get InstanceGroups.`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
err := getInstanceGroupsCmd.Run()
|
err := getInstanceGroupsCmd.Run(args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Exitf("%v", err)
|
glog.Exitf("%v", err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
getCmd.AddCommand(cmd)
|
getCmd.cobraCommand.AddCommand(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GetInstanceGroupsCmd) Run() error {
|
func (c *GetInstanceGroupsCmd) Run(args []string) error {
|
||||||
registry, err := rootCommand.InstanceGroupRegistry()
|
registry, err := rootCommand.InstanceGroupRegistry()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -43,30 +44,64 @@ func (c *GetInstanceGroupsCmd) Run() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(args) != 0 {
|
||||||
|
m := make(map[string]*api.InstanceGroup)
|
||||||
|
for _, ig := range instancegroups {
|
||||||
|
m[ig.Name] = ig
|
||||||
|
}
|
||||||
|
instancegroups = make([]*api.InstanceGroup, 0, len(args))
|
||||||
|
for _, arg := range args {
|
||||||
|
ig := m[arg]
|
||||||
|
if ig == nil {
|
||||||
|
return fmt.Errorf("instancegroup not found %q", arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
instancegroups = append(instancegroups, ig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if len(instancegroups) == 0 {
|
if len(instancegroups) == 0 {
|
||||||
|
fmt.Fprintf(os.Stderr, "No InstanceGroup objects found\n")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
t := &Table{}
|
output := getCmd.output
|
||||||
t.AddColumn("NAME", func(c *api.InstanceGroup) string {
|
if output == OutputTable {
|
||||||
return c.Name
|
t := &Table{}
|
||||||
})
|
t.AddColumn("NAME", func(c *api.InstanceGroup) string {
|
||||||
t.AddColumn("ROLE", func(c *api.InstanceGroup) string {
|
return c.Name
|
||||||
return string(c.Spec.Role)
|
})
|
||||||
})
|
t.AddColumn("ROLE", func(c *api.InstanceGroup) string {
|
||||||
t.AddColumn("MACHINETYPE", func(c *api.InstanceGroup) string {
|
return string(c.Spec.Role)
|
||||||
return c.Spec.MachineType
|
})
|
||||||
})
|
t.AddColumn("MACHINETYPE", func(c *api.InstanceGroup) string {
|
||||||
t.AddColumn("ZONES", func(c *api.InstanceGroup) string {
|
return c.Spec.MachineType
|
||||||
return strings.Join(c.Spec.Zones, ",")
|
})
|
||||||
})
|
t.AddColumn("ZONES", func(c *api.InstanceGroup) string {
|
||||||
t.AddColumn("MIN", func(c *api.InstanceGroup) string {
|
return strings.Join(c.Spec.Zones, ",")
|
||||||
return intPointerToString(c.Spec.MinSize)
|
})
|
||||||
})
|
t.AddColumn("MIN", func(c *api.InstanceGroup) string {
|
||||||
t.AddColumn("MAX", func(c *api.InstanceGroup) string {
|
return intPointerToString(c.Spec.MinSize)
|
||||||
return intPointerToString(c.Spec.MinSize)
|
})
|
||||||
})
|
t.AddColumn("MAX", func(c *api.InstanceGroup) string {
|
||||||
return t.Render(instancegroups, os.Stdout, "NAME", "ROLE", "MACHINETYPE", "MIN", "MAX", "ZONES")
|
return intPointerToString(c.Spec.MinSize)
|
||||||
|
})
|
||||||
|
return t.Render(instancegroups, os.Stdout, "NAME", "ROLE", "MACHINETYPE", "MIN", "MAX", "ZONES")
|
||||||
|
} else if output == OutputYaml {
|
||||||
|
for _, ig := range instancegroups {
|
||||||
|
y, err := api.ToYaml(ig)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error marshaling yaml for %q: %v", ig.Name, err)
|
||||||
|
}
|
||||||
|
_, err = os.Stdout.Write(y)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error writing to stdout: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("Unknown output format: %q", output)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func intPointerToString(v *int) string {
|
func intPointerToString(v *int) string {
|
||||||
|
|
|
@ -0,0 +1,175 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"k8s.io/kops/upup/pkg/fi"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetSecretsCommand struct {
|
||||||
|
Type string
|
||||||
|
}
|
||||||
|
|
||||||
|
var getSecretsCommand GetSecretsCommand
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "secrets",
|
||||||
|
Aliases: []string{"secret"},
|
||||||
|
Short: "get secrets",
|
||||||
|
Long: `List or get secrets.`,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
err := getSecretsCommand.Run(args)
|
||||||
|
if err != nil {
|
||||||
|
exitWithError(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
getCmd.cobraCommand.AddCommand(cmd)
|
||||||
|
|
||||||
|
cmd.Flags().StringVarP(&getSecretsCommand.Type, "type", "", "", "Filter by secret type")
|
||||||
|
}
|
||||||
|
|
||||||
|
func listSecrets(secretType string, names []string) ([]*fi.KeystoreItem, error) {
|
||||||
|
var items []*fi.KeystoreItem
|
||||||
|
|
||||||
|
findType := strings.ToLower(secretType)
|
||||||
|
switch findType {
|
||||||
|
case "":
|
||||||
|
// OK
|
||||||
|
case "sshpublickey", "keypair", "secret":
|
||||||
|
// OK
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown secret type %q", secretType)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
caStore, err := rootCommand.KeyStore()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
l, err := caStore.List()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error listing CA store items %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, i := range l {
|
||||||
|
if findType != "" && findType != strings.ToLower(i.Type) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if findType == "" || findType == strings.ToLower(fi.SecretTypeSecret) {
|
||||||
|
secretStore, err := rootCommand.SecretStore()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
l, err := secretStore.ListSecrets()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error listing secrets %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, id := range l {
|
||||||
|
i := &fi.KeystoreItem{
|
||||||
|
Name: id,
|
||||||
|
Type: fi.SecretTypeSecret,
|
||||||
|
}
|
||||||
|
if findType != "" && findType != strings.ToLower(i.Type) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(names) != 0 {
|
||||||
|
var matches []*fi.KeystoreItem
|
||||||
|
for _, arg := range names {
|
||||||
|
var found []*fi.KeystoreItem
|
||||||
|
for _, i := range items {
|
||||||
|
// There may be multiple secrets with the same name (of different type)
|
||||||
|
if i.Name == arg {
|
||||||
|
found = append(found, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(found) == 0 {
|
||||||
|
return nil, fmt.Errorf("Secret not found: %q", arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
matches = append(matches, found...)
|
||||||
|
}
|
||||||
|
items = matches
|
||||||
|
}
|
||||||
|
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GetSecretsCommand) Run(args []string) error {
|
||||||
|
items, err := listSecrets(c.Type, args)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(items) == 0 {
|
||||||
|
fmt.Fprintf(os.Stderr, "No secrets found\n")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
output := getCmd.output
|
||||||
|
if output == OutputTable {
|
||||||
|
t := &Table{}
|
||||||
|
t.AddColumn("NAME", func(i *fi.KeystoreItem) string {
|
||||||
|
return i.Name
|
||||||
|
})
|
||||||
|
t.AddColumn("ID", func(i *fi.KeystoreItem) string {
|
||||||
|
return i.Id
|
||||||
|
})
|
||||||
|
t.AddColumn("TYPE", func(i *fi.KeystoreItem) string {
|
||||||
|
return i.Type
|
||||||
|
})
|
||||||
|
return t.Render(items, os.Stdout, "TYPE", "NAME", "ID")
|
||||||
|
} else if output == OutputYaml {
|
||||||
|
return fmt.Errorf("yaml output format is not (currently) supported for secrets")
|
||||||
|
} else if output == "plaintext" {
|
||||||
|
secretStore, err := rootCommand.SecretStore()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, i := range items {
|
||||||
|
var data string
|
||||||
|
switch i.Type {
|
||||||
|
case fi.SecretTypeSecret:
|
||||||
|
secret, err := secretStore.FindSecret(i.Name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error getting secret %q: %v", i.Name, err)
|
||||||
|
}
|
||||||
|
if secret == nil {
|
||||||
|
return fmt.Errorf("cannot find secret %q", i.Name)
|
||||||
|
}
|
||||||
|
data = string(secret.Data)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("secret type %v cannot (currently) be exported as plaintext", i.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := fmt.Fprintf(os.Stdout, "%s\n", data)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error writing output: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("Unknown output format: %q", output)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,5 +1,17 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
Execute()
|
Execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// exitWithError will terminate execution with an error result
|
||||||
|
// It prints the error to stderr and exits with a non-zero exit code
|
||||||
|
func exitWithError(err error) {
|
||||||
|
fmt.Fprintf(os.Stderr, "\n%v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
|
@ -2,141 +2,18 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"crypto/x509"
|
|
||||||
"github.com/golang/glog"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type CreateSecretsCommand struct {
|
|
||||||
Id string
|
|
||||||
Type string
|
|
||||||
|
|
||||||
Usage string
|
|
||||||
Subject string
|
|
||||||
AlternateNames []string
|
|
||||||
}
|
|
||||||
|
|
||||||
var createSecretsCommand CreateSecretsCommand
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "create",
|
Use: "create",
|
||||||
Short: "Create secrets",
|
Short: "Create secrets",
|
||||||
Long: `Create secrets.`,
|
Long: `Create secrets.`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
err := createSecretsCommand.Run()
|
exitWithError(fmt.Errorf("The 'secrets create' command has been replaced by 'create secrets'"))
|
||||||
if err != nil {
|
|
||||||
glog.Exitf("%v", err)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
secretsCmd.AddCommand(cmd)
|
secretsCmd.AddCommand(cmd)
|
||||||
|
|
||||||
cmd.Flags().StringVarP(&createSecretsCommand.Type, "type", "", "", "Type of secret to create")
|
|
||||||
cmd.Flags().StringVarP(&createSecretsCommand.Id, "id", "", "", "Id of secret to create")
|
|
||||||
cmd.Flags().StringVarP(&createSecretsCommand.Usage, "usage", "", "", "Usage of secret (for SSL certificate)")
|
|
||||||
cmd.Flags().StringVarP(&createSecretsCommand.Subject, "subject", "", "", "Subject (for SSL certificate)")
|
|
||||||
cmd.Flags().StringSliceVarP(&createSecretsCommand.AlternateNames, "san", "", nil, "Alternate name (for SSL certificate)")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cmd *CreateSecretsCommand) Run() error {
|
|
||||||
if cmd.Id == "" {
|
|
||||||
return fmt.Errorf("id is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cmd.Type == "" {
|
|
||||||
return fmt.Errorf("type is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Prompt before replacing?
|
|
||||||
// TODO: Keep history?
|
|
||||||
|
|
||||||
switch cmd.Type {
|
|
||||||
case "secret":
|
|
||||||
{
|
|
||||||
secretStore, err := rootCommand.SecretStore()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, created, err := secretStore.GetOrCreateSecret(cmd.Id)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error creating secrets %v", err)
|
|
||||||
}
|
|
||||||
if !created {
|
|
||||||
return fmt.Errorf("secret already exists")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
case "keypair":
|
|
||||||
// TODO: Create a rotate command which keeps the same values?
|
|
||||||
// Or just do it here a "replace" action - existing=fail, replace or rotate
|
|
||||||
// TODO: Create a CreateKeypair class, move to fi (this is duplicated code)
|
|
||||||
{
|
|
||||||
if cmd.Subject == "" {
|
|
||||||
return fmt.Errorf("subject is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
subject, err := parsePkixName(cmd.Subject)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Error parsing subject: %v", err)
|
|
||||||
}
|
|
||||||
template := &x509.Certificate{
|
|
||||||
Subject: *subject,
|
|
||||||
BasicConstraintsValid: true,
|
|
||||||
IsCA: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(template.Subject.ToRDNSequence()) == 0 {
|
|
||||||
return fmt.Errorf("Subject name was empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
switch cmd.Usage {
|
|
||||||
case "client":
|
|
||||||
template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}
|
|
||||||
template.KeyUsage = x509.KeyUsageDigitalSignature
|
|
||||||
break
|
|
||||||
|
|
||||||
case "server":
|
|
||||||
template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
|
|
||||||
template.KeyUsage = x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment
|
|
||||||
break
|
|
||||||
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unknown usage: %q", cmd.Usage)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, san := range cmd.AlternateNames {
|
|
||||||
san = strings.TrimSpace(san)
|
|
||||||
if san == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if ip := net.ParseIP(san); ip != nil {
|
|
||||||
template.IPAddresses = append(template.IPAddresses, ip)
|
|
||||||
} else {
|
|
||||||
template.DNSNames = append(template.DNSNames, san)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
caStore, err := rootCommand.KeyStore()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Allow resigning of the existing private key?
|
|
||||||
|
|
||||||
_, _, err = caStore.CreateKeypair(cmd.Id, template)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error creating keypair %v", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("secret type not known: %q", cmd.Type)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,178 +3,18 @@ package main
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"bytes"
|
|
||||||
"crypto/rsa"
|
|
||||||
"github.com/golang/glog"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"k8s.io/kops/upup/pkg/fi"
|
|
||||||
"os"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"text/tabwriter"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type DescribeSecretsCommand struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
var describeSecretsCommand DescribeSecretsCommand
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "describe",
|
Use: "describe",
|
||||||
Short: "Describe secrets",
|
Short: "Describe secrets",
|
||||||
Long: `Describe secrets.`,
|
Long: `Describe secrets.`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
err := describeSecretsCommand.Run()
|
exitWithError(fmt.Errorf("The 'secrets describe' command has been replaced by 'describe secrets'"))
|
||||||
if err != nil {
|
|
||||||
glog.Exitf("%v", err)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
secretsCmd.AddCommand(cmd)
|
secretsCmd.AddCommand(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *DescribeSecretsCommand) Run() error {
|
|
||||||
|
|
||||||
w := new(tabwriter.Writer)
|
|
||||||
var b bytes.Buffer
|
|
||||||
|
|
||||||
// Format in tab-separated columns with a tab stop of 8.
|
|
||||||
w.Init(os.Stdout, 0, 8, 0, '\t', tabwriter.StripEscape)
|
|
||||||
|
|
||||||
{
|
|
||||||
caStore, err := rootCommand.KeyStore()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ids, err := caStore.List()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error listing CA store items %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, id := range ids {
|
|
||||||
cert, err := caStore.FindCert(id)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error retrieving cert %q: %v", id, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
key, err := caStore.FindPrivateKey(id)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error retrieving private key %q: %v", id, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if key == nil && cert == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
err = describeKeypair(id, cert, key, &b)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
b.WriteString("\n")
|
|
||||||
|
|
||||||
_, err = w.Write(b.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error writing to output: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
secretStore, err := rootCommand.SecretStore()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ids, err := secretStore.ListSecrets()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error listing secrets %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, id := range ids {
|
|
||||||
secret, err := secretStore.FindSecret(id)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error retrieving secret %q: %v", id, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if secret == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
err = describeSecret(id, secret, &b)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
b.WriteString("\n")
|
|
||||||
|
|
||||||
_, err = w.Write(b.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error writing to output: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Reset()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return w.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
func describeKeypair(id string, c *fi.Certificate, k *fi.PrivateKey, w *bytes.Buffer) error {
|
|
||||||
var alternateNames []string
|
|
||||||
if c != nil {
|
|
||||||
alternateNames = append(alternateNames, c.Certificate.DNSNames...)
|
|
||||||
alternateNames = append(alternateNames, c.Certificate.EmailAddresses...)
|
|
||||||
for _, ip := range c.Certificate.IPAddresses {
|
|
||||||
alternateNames = append(alternateNames, ip.String())
|
|
||||||
}
|
|
||||||
sort.Strings(alternateNames)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "Id:\t%s\n", id)
|
|
||||||
if c != nil && k != nil {
|
|
||||||
fmt.Fprintf(w, "Type:\t%s\n", "keypair")
|
|
||||||
} else if c != nil && k == nil {
|
|
||||||
fmt.Fprintf(w, "Type:\t%s\n", "certificate")
|
|
||||||
} else if k != nil && c == nil {
|
|
||||||
// Unexpected!
|
|
||||||
fmt.Fprintf(w, "Type:\t%s\n", "privatekey")
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("expected either certificate or key to be set")
|
|
||||||
}
|
|
||||||
|
|
||||||
if c != nil {
|
|
||||||
fmt.Fprintf(w, "Subject:\t%s\n", pkixNameToString(&c.Certificate.Subject))
|
|
||||||
fmt.Fprintf(w, "Issuer:\t%s\n", pkixNameToString(&c.Certificate.Issuer))
|
|
||||||
fmt.Fprintf(w, "AlternateNames:\t%s\n", strings.Join(alternateNames, ", "))
|
|
||||||
fmt.Fprintf(w, "CA:\t%v\n", c.IsCA)
|
|
||||||
fmt.Fprintf(w, "NotAfter:\t%s\n", c.Certificate.NotAfter)
|
|
||||||
fmt.Fprintf(w, "NotBefore:\t%s\n", c.Certificate.NotBefore)
|
|
||||||
|
|
||||||
// PublicKeyAlgorithm doesn't have a String() function. Also, is this important information?
|
|
||||||
//fmt.Fprintf(w, "PublicKeyAlgorithm:\t%v\n", c.Certificate.PublicKeyAlgorithm)
|
|
||||||
//fmt.Fprintf(w, "SignatureAlgorithm:\t%v\n", c.Certificate.SignatureAlgorithm)
|
|
||||||
}
|
|
||||||
|
|
||||||
if k != nil {
|
|
||||||
if rsaPrivateKey, ok := k.Key.(*rsa.PrivateKey); ok {
|
|
||||||
fmt.Fprintf(w, "PrivateKeyType:\t%v\n", "rsa")
|
|
||||||
fmt.Fprintf(w, "KeyLength:\t%v\n", rsaPrivateKey.N.BitLen())
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(w, "PrivateKeyType:\tunknown (%T)\n", k.Key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func describeSecret(id string, s *fi.Secret, w *bytes.Buffer) error {
|
|
||||||
fmt.Fprintf(w, "Id:\t%s\n", id)
|
|
||||||
fmt.Fprintf(w, "Type:\t%s\n", "secret")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,108 +3,18 @@ package main
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"os"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ExposeSecretsCommand struct {
|
|
||||||
ID string
|
|
||||||
Type string
|
|
||||||
}
|
|
||||||
|
|
||||||
var exposeSecretsCommand ExposeSecretsCommand
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "expose",
|
Use: "expose",
|
||||||
Short: "Expose secrets",
|
Short: "Expose secrets",
|
||||||
Long: `Expose secrets.`,
|
Long: `Expose secrets.`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
err := exposeSecretsCommand.Run()
|
exitWithError(fmt.Errorf("The 'secrets export' command has been replaced by 'get secrets -oplaintext'"))
|
||||||
if err != nil {
|
|
||||||
glog.Exitf("%v", err)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
secretsCmd.AddCommand(cmd)
|
secretsCmd.AddCommand(cmd)
|
||||||
|
|
||||||
cmd.Flags().StringVarP(&exposeSecretsCommand.Type, "type", "", "", "Type of secret to create")
|
|
||||||
cmd.Flags().StringVarP(&exposeSecretsCommand.ID, "id", "", "", "Id of secret to create")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cmd *ExposeSecretsCommand) Run() error {
|
|
||||||
id := cmd.ID
|
|
||||||
if id == "" {
|
|
||||||
return fmt.Errorf("id is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cmd.Type == "" {
|
|
||||||
return fmt.Errorf("type is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
var value string
|
|
||||||
switch cmd.Type {
|
|
||||||
case "secret":
|
|
||||||
{
|
|
||||||
secretStore, err := rootCommand.SecretStore()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
secret, err := secretStore.FindSecret(id)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error finding secret %q: %v", id, err)
|
|
||||||
}
|
|
||||||
if secret == nil {
|
|
||||||
return fmt.Errorf("secret not found: %q", id)
|
|
||||||
}
|
|
||||||
value = string(secret.Data)
|
|
||||||
}
|
|
||||||
|
|
||||||
case "certificate", "privatekey":
|
|
||||||
{
|
|
||||||
caStore, err := rootCommand.KeyStore()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error building CA store: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if cmd.Type == "privatekey" {
|
|
||||||
k, err := caStore.FindPrivateKey(id)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error finding privatekey: %v", err)
|
|
||||||
}
|
|
||||||
if k == nil {
|
|
||||||
return fmt.Errorf("privatekey not found: %q", id)
|
|
||||||
}
|
|
||||||
value, err = k.AsString()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error encoding privatekey: %v", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
c, err := caStore.FindCert(id)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error finding certificate: %v", err)
|
|
||||||
}
|
|
||||||
if c == nil {
|
|
||||||
return fmt.Errorf("certificate not found: %q", id)
|
|
||||||
}
|
|
||||||
value, err = c.AsString()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error encoding certiifcate: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("secret type not known: %q", cmd.Type)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := fmt.Fprint(os.Stdout, value+"\n")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error writing to output: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,101 +2,18 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"bytes"
|
|
||||||
"github.com/golang/glog"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"os"
|
|
||||||
"text/tabwriter"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type GetSecretsCommand struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
var getSecretsCommand GetSecretsCommand
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "get",
|
Use: "get",
|
||||||
Short: "Get secrets",
|
Short: "Get secrets",
|
||||||
Long: `Get secrets.`,
|
Long: `Get secrets.`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
err := getSecretsCommand.Run()
|
exitWithError(fmt.Errorf("The 'secrets get' command has been replaced by 'get secret'"))
|
||||||
if err != nil {
|
|
||||||
glog.Exitf("%v", err)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
secretsCmd.AddCommand(cmd)
|
secretsCmd.AddCommand(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
type SecretInfo struct {
|
|
||||||
Id string
|
|
||||||
Type string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *GetSecretsCommand) Run() error {
|
|
||||||
var infos []*SecretInfo
|
|
||||||
{
|
|
||||||
caStore, err := rootCommand.KeyStore()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ids, err := caStore.List()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error listing CA store items %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, id := range ids {
|
|
||||||
info := &SecretInfo{
|
|
||||||
Id: id,
|
|
||||||
Type: "keypair",
|
|
||||||
}
|
|
||||||
infos = append(infos, info)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
secretStore, err := rootCommand.SecretStore()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ids, err := secretStore.ListSecrets()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error listing secrets %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, id := range ids {
|
|
||||||
info := &SecretInfo{
|
|
||||||
Id: id,
|
|
||||||
Type: "secret",
|
|
||||||
}
|
|
||||||
infos = append(infos, info)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var b bytes.Buffer
|
|
||||||
w := new(tabwriter.Writer)
|
|
||||||
|
|
||||||
// Format in tab-separated columns with a tab stop of 8.
|
|
||||||
w.Init(os.Stdout, 0, 8, 0, '\t', tabwriter.StripEscape)
|
|
||||||
for _, info := range infos {
|
|
||||||
b.WriteByte(tabwriter.Escape)
|
|
||||||
b.WriteString(info.Type)
|
|
||||||
b.WriteByte(tabwriter.Escape)
|
|
||||||
b.WriteByte('\t')
|
|
||||||
b.WriteByte(tabwriter.Escape)
|
|
||||||
b.WriteString(info.Id)
|
|
||||||
b.WriteByte(tabwriter.Escape)
|
|
||||||
b.WriteByte('\n')
|
|
||||||
|
|
||||||
_, err := w.Write(b.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error writing to output: %v", err)
|
|
||||||
}
|
|
||||||
b.Reset()
|
|
||||||
}
|
|
||||||
w.Flush()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ And then navigate to `https://<clustername>/ui`
|
||||||
The login credentials are:
|
The login credentials are:
|
||||||
|
|
||||||
* Username: `admin`
|
* Username: `admin`
|
||||||
* Password: get by running `kops secrets expose --id kube --type secret`
|
* Password: get by running `kops get secrets kube --type secret -oplaintext`
|
||||||
|
|
||||||
|
|
||||||
### Monitoring - Standalone
|
### Monitoring - Standalone
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
## Managing secrets
|
||||||
|
|
||||||
|
### get secrets
|
||||||
|
|
||||||
|
### get secret <name> -oplaintext
|
||||||
|
|
||||||
|
-oplaintext exposes the raw secret value.
|
||||||
|
|
||||||
|
### describe secret
|
||||||
|
|
||||||
|
`kops describe secret`
|
||||||
|
|
||||||
|
### create secret
|
||||||
|
|
||||||
|
`kops create secret publickey admin -i ~/.ssh/id_rsa.pub`
|
||||||
|
|
||||||
|
### delete secret
|
||||||
|
|
||||||
|
Syntax: `kops delete secret <type> <name>`
|
||||||
|
or `kops delete secret <type> <name> <id>`
|
||||||
|
|
||||||
|
The ID form can be used when there are multiple matching keys.
|
||||||
|
|
||||||
|
example:
|
||||||
|
`kops delete secret sshpublickey admin`
|
||||||
|
|
|
@ -28,6 +28,19 @@ type Certificate struct {
|
||||||
PublicKey crypto.PublicKey
|
PublicKey crypto.PublicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
SecretTypeSSHPublicKey = "SSHPublicKey"
|
||||||
|
SecretTypeKeypair = "Keypair"
|
||||||
|
SecretTypeSecret = "Secret"
|
||||||
|
)
|
||||||
|
|
||||||
|
type KeystoreItem struct {
|
||||||
|
Type string
|
||||||
|
Name string
|
||||||
|
Id string
|
||||||
|
Data []byte
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Certificate) UnmarshalJSON(b []byte) error {
|
func (c *Certificate) UnmarshalJSON(b []byte) error {
|
||||||
s := ""
|
s := ""
|
||||||
if err := json.Unmarshal(b, &s); err == nil {
|
if err := json.Unmarshal(b, &s); err == nil {
|
||||||
|
@ -67,26 +80,36 @@ func (c *Certificate) MarshalJSON() ([]byte, error) {
|
||||||
|
|
||||||
type CAStore interface {
|
type CAStore interface {
|
||||||
// Cert returns the primary specified certificate
|
// Cert returns the primary specified certificate
|
||||||
Cert(id string) (*Certificate, error)
|
Cert(name string) (*Certificate, error)
|
||||||
// CertificatePool returns all active certificates with the specified id
|
// CertificatePool returns all active certificates with the specified id
|
||||||
CertificatePool(id string) (*CertificatePool, error)
|
CertificatePool(name string) (*CertificatePool, error)
|
||||||
PrivateKey(id string) (*PrivateKey, error)
|
PrivateKey(name string) (*PrivateKey, error)
|
||||||
|
|
||||||
FindCert(id string) (*Certificate, error)
|
FindCert(name string) (*Certificate, error)
|
||||||
FindPrivateKey(id string) (*PrivateKey, error)
|
FindPrivateKey(name string) (*PrivateKey, error)
|
||||||
|
|
||||||
//IssueCert(id string, privateKey *PrivateKey, template *x509.Certificate) (*Certificate, error)
|
//IssueCert(id string, privateKey *PrivateKey, template *x509.Certificate) (*Certificate, error)
|
||||||
//CreatePrivateKey(id string) (*PrivateKey, error)
|
//CreatePrivateKey(id string) (*PrivateKey, error)
|
||||||
|
|
||||||
CreateKeypair(id string, template *x509.Certificate) (*Certificate, *PrivateKey, error)
|
CreateKeypair(name string, template *x509.Certificate) (*Certificate, *PrivateKey, error)
|
||||||
|
|
||||||
List() ([]string, error)
|
// List will list all the items, but will not fetch the data
|
||||||
|
List() ([]*KeystoreItem, error)
|
||||||
|
|
||||||
// VFSPath returns the path where the CAStore is stored
|
// VFSPath returns the path where the CAStore is stored
|
||||||
VFSPath() vfs.Path
|
VFSPath() vfs.Path
|
||||||
|
|
||||||
// AddCert adds an alternative certificate to the pool (primarily useful for CAs)
|
// AddCert adds an alternative certificate to the pool (primarily useful for CAs)
|
||||||
AddCert(id string, cert *Certificate) error
|
AddCert(name string, cert *Certificate) error
|
||||||
|
|
||||||
|
// AddSSHPublicKey adds an SSH public key
|
||||||
|
AddSSHPublicKey(name string, data []byte) error
|
||||||
|
|
||||||
|
// FindSSHPublicKeys retrieves the SSH public keys with the specific name
|
||||||
|
FindSSHPublicKeys(name string) ([]*KeystoreItem, error)
|
||||||
|
|
||||||
|
// DeleteSecret will delete the specified item
|
||||||
|
DeleteSecret(item *KeystoreItem) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Certificate) AsString() (string, error) {
|
func (c *Certificate) AsString() (string, error) {
|
||||||
|
|
|
@ -290,7 +290,7 @@ func (c *populateClusterSpec) run() error {
|
||||||
if cluster.Spec.DNSZone == "" {
|
if cluster.Spec.DNSZone == "" {
|
||||||
dnsZone, err := cloud.FindDNSHostedZone(cluster.Name)
|
dnsZone, err := cloud.FindDNSHostedZone(cluster.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error determining default DNS zone; please specify --zone-name: %v", err)
|
return fmt.Errorf("Error determining default DNS zone; please specify --dns-zone: %v", err)
|
||||||
}
|
}
|
||||||
glog.Infof("Defaulting DNS zone to: %s", dnsZone)
|
glog.Infof("Defaulting DNS zone to: %s", dnsZone)
|
||||||
cluster.Spec.DNSZone = dnsZone
|
cluster.Spec.DNSZone = dnsZone
|
||||||
|
|
|
@ -61,7 +61,12 @@ func (_ *Secret) Render(c *fi.Context, a, e, changes *Secret) error {
|
||||||
|
|
||||||
secrets := c.SecretStore
|
secrets := c.SecretStore
|
||||||
|
|
||||||
_, _, err := secrets.GetOrCreateSecret(name)
|
secret, err := fi.CreateSecret()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error creating secret %q: %v", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = secrets.GetOrCreateSecret(name, secret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error creating secret %q: %v", name, err)
|
return fmt.Errorf("error creating secret %q: %v", name, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ type SecretStore interface {
|
||||||
// Find a secret, if exists. Returns nil,nil if not found
|
// Find a secret, if exists. Returns nil,nil if not found
|
||||||
FindSecret(id string) (*Secret, error)
|
FindSecret(id string) (*Secret, error)
|
||||||
// Create or replace a secret
|
// Create or replace a secret
|
||||||
GetOrCreateSecret(id string) (secret *Secret, created bool, err error)
|
GetOrCreateSecret(id string, secret *Secret) (current *Secret, created bool, err error)
|
||||||
// Lists the ids of all known secrets
|
// Lists the ids of all known secrets
|
||||||
ListSecrets() ([]string, error)
|
ListSecrets() ([]string, error)
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,14 @@ package fi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/md5"
|
||||||
crypto_rand "crypto/rand"
|
crypto_rand "crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"crypto/x509/pkix"
|
"crypto/x509/pkix"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
"k8s.io/kops/upup/pkg/fi/vfs"
|
"k8s.io/kops/upup/pkg/fi/vfs"
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
|
@ -316,20 +318,66 @@ func (c *VFSCAStore) FindCertificatePool(id string) (*CertificatePool, error) {
|
||||||
return pool, nil
|
return pool, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *VFSCAStore) List() ([]string, error) {
|
func (c *VFSCAStore) List() ([]*KeystoreItem, error) {
|
||||||
var ids []string
|
var items []*KeystoreItem
|
||||||
|
|
||||||
issuedDir := c.basedir.Join("issued")
|
{
|
||||||
files, err := issuedDir.ReadDir()
|
baseDir := c.basedir.Join("issued")
|
||||||
if err != nil {
|
files, err := baseDir.ReadTree()
|
||||||
return nil, fmt.Errorf("error reading directory %q: %v", issuedDir, err)
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error reading directory %q: %v", baseDir, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range files {
|
||||||
|
relativePath, err := vfs.RelativePath(baseDir, f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens := strings.Split(relativePath, "/")
|
||||||
|
if len(tokens) != 2 {
|
||||||
|
glog.V(2).Infof("ignoring unexpected file in keystore: %q", f)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
item := &KeystoreItem{
|
||||||
|
Name: tokens[0],
|
||||||
|
Id: strings.TrimSuffix(tokens[1], ".crt"),
|
||||||
|
Type: SecretTypeKeypair,
|
||||||
|
}
|
||||||
|
items = append(items, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, f := range files {
|
{
|
||||||
name := f.Base()
|
baseDir := c.basedir.Join("ssh", "public")
|
||||||
ids = append(ids, name)
|
files, err := baseDir.ReadTree()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error reading directory %q: %v", baseDir, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range files {
|
||||||
|
relativePath, err := vfs.RelativePath(baseDir, f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens := strings.Split(relativePath, "/")
|
||||||
|
if len(tokens) != 2 {
|
||||||
|
glog.V(2).Infof("ignoring unexpected file in keystore: %q", f)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
item := &KeystoreItem{
|
||||||
|
Name: tokens[0],
|
||||||
|
Id: insertFingerprintColons(tokens[1]),
|
||||||
|
Type: SecretTypeSSHPublicKey,
|
||||||
|
}
|
||||||
|
items = append(items, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return ids, nil
|
|
||||||
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *VFSCAStore) IssueCert(id string, serial *big.Int, privateKey *PrivateKey, template *x509.Certificate) (*Certificate, error) {
|
func (c *VFSCAStore) IssueCert(id string, serial *big.Int, privateKey *PrivateKey, template *x509.Certificate) (*Certificate, error) {
|
||||||
|
@ -572,3 +620,143 @@ func buildSerial(timestamp int64) *big.Int {
|
||||||
|
|
||||||
return serial
|
return serial
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func formatFingerprint(data []byte) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
for i, b := range data {
|
||||||
|
s := fmt.Sprintf("%0.2x", b)
|
||||||
|
if i != 0 {
|
||||||
|
buf.WriteString(":")
|
||||||
|
}
|
||||||
|
buf.WriteString(s)
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func insertFingerprintColons(id string) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
for {
|
||||||
|
if id == "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if buf.Len() != 0 {
|
||||||
|
buf.WriteString(":")
|
||||||
|
}
|
||||||
|
if len(id) < 2 {
|
||||||
|
buf.WriteString(id)
|
||||||
|
} else {
|
||||||
|
buf.WriteString(id[0:2])
|
||||||
|
id = id[2:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddSSHPublicKey stores an SSH public key
|
||||||
|
func (c *VFSCAStore) AddSSHPublicKey(name string, pubkey []byte) error {
|
||||||
|
var id string
|
||||||
|
{
|
||||||
|
sshPublicKey, _, _, _, err := ssh.ParseAuthorizedKey(pubkey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error parsing public key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// compute fingerprint to serve as id
|
||||||
|
h := md5.New()
|
||||||
|
_, err = h.Write(sshPublicKey.Marshal())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
id = formatFingerprint(h.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
p := c.buildSSHPublicKeyPath(name, id)
|
||||||
|
return c.storeData(pubkey, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *VFSCAStore) buildSSHPublicKeyPath(name string, id string) vfs.Path {
|
||||||
|
// id is fingerprint with colons, but we store without colons
|
||||||
|
id = strings.Replace(id, ":", "", -1)
|
||||||
|
return c.basedir.Join("ssh", "public", name, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *VFSCAStore) storeData(data []byte, p vfs.Path) error {
|
||||||
|
return p.WriteFile(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *VFSCAStore) FindSSHPublicKeys(name string) ([]*KeystoreItem, error) {
|
||||||
|
p := c.basedir.Join("ssh", "public", name)
|
||||||
|
|
||||||
|
items, err := c.loadPath(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, item := range items {
|
||||||
|
// Fill in the missing fields
|
||||||
|
item.Type = SecretTypeSSHPublicKey
|
||||||
|
item.Name = name
|
||||||
|
|
||||||
|
item.Id = insertFingerprintColons(item.Id)
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *VFSCAStore) loadPath(p vfs.Path) ([]*KeystoreItem, error) {
|
||||||
|
files, err := p.ReadDir()
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var keystoreItems []*KeystoreItem
|
||||||
|
|
||||||
|
for _, f := range files {
|
||||||
|
data, err := f.ReadFile()
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
glog.V(2).Infof("Ignoring not-found issue reading %q", f)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("error loading keystore item %q: %v", f, err)
|
||||||
|
}
|
||||||
|
name := f.Base()
|
||||||
|
keystoreItem := &KeystoreItem{
|
||||||
|
Id: name,
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
keystoreItems = append(keystoreItems, keystoreItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
return keystoreItems, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *VFSCAStore) loadData(p vfs.Path) (*PrivateKey, error) {
|
||||||
|
data, err := p.ReadFile()
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
k, err := ParsePEMPrivateKey(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing private key from %q: %v", p, err)
|
||||||
|
}
|
||||||
|
return k, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *VFSCAStore) DeleteSecret(item *KeystoreItem) error {
|
||||||
|
switch item.Type {
|
||||||
|
case SecretTypeSSHPublicKey:
|
||||||
|
p := c.buildSSHPublicKeyPath(item.Name, item.Id)
|
||||||
|
return p.Remove()
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Primarily because we need to make sure users can recreate them!
|
||||||
|
return fmt.Errorf("deletion of keystore items of type %v not (yet) supported", item.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -62,7 +62,7 @@ func (c *VFSSecretStore) Secret(id string) (*Secret, error) {
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *VFSSecretStore) GetOrCreateSecret(id string) (*Secret, bool, error) {
|
func (c *VFSSecretStore) GetOrCreateSecret(id string, secret *Secret) (*Secret, bool, error) {
|
||||||
p := c.buildSecretPath(id)
|
p := c.buildSecretPath(id)
|
||||||
|
|
||||||
for i := 0; i < 2; i++ {
|
for i := 0; i < 2; i++ {
|
||||||
|
@ -75,12 +75,7 @@ func (c *VFSSecretStore) GetOrCreateSecret(id string) (*Secret, bool, error) {
|
||||||
return s, false, nil
|
return s, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
s, err = CreateSecret()
|
err = c.createSecret(secret, p)
|
||||||
if err != nil {
|
|
||||||
return nil, false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.createSecret(s, p)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsExist(err) && i == 0 {
|
if os.IsExist(err) && i == 0 {
|
||||||
glog.Infof("Got already-exists error when writing secret; likely due to concurrent creation. Will retry")
|
glog.Infof("Got already-exists error when writing secret; likely due to concurrent creation. Will retry")
|
||||||
|
|
Loading…
Reference in New Issue