Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Mykhailo Oleksiuk 2016-08-05 12:08:56 +03:00
commit bc26eecf49
25 changed files with 1105 additions and 545 deletions

View File

@ -7,8 +7,8 @@ import (
// createCmd represents the create command
var createCmd = &cobra.Command{
Use: "create",
Short: "create clusters",
Long: `Create clusters`,
Short: "create resources",
Long: `Create resources`,
}
func init() {

23
cmd/kops/create_secret.go Normal file
View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
//}

79
cmd/kops/delete_secret.go Normal file
View File

@ -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
}

23
cmd/kops/describe.go Normal file
View File

@ -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)
}

View File

@ -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
}

View File

@ -129,7 +129,7 @@ func (t *Table) Render(items interface{}, out io.Writer, columnNames ...string)
w := new(tabwriter.Writer)
// 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
if writeHeader {

View File

@ -4,14 +4,31 @@ import (
"github.com/spf13/cobra"
)
// getCmd represents the get command
var getCmd = &cobra.Command{
Use: "get",
SuggestFor: []string{"list"},
Short: "list or get obejcts",
Long: `list or get obejcts`,
// GetCmd represents the get command
type GetCmd struct {
output string
cobraCommand *cobra.Command
}
func init() {
rootCommand.AddCommand(getCmd)
var getCmd = 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")
}

View File

@ -3,6 +3,7 @@ package main
import (
"os"
"fmt"
"github.com/golang/glog"
"github.com/spf13/cobra"
"k8s.io/kops/upup/pkg/api"
@ -10,6 +11,7 @@ import (
)
type GetClustersCmd struct {
FullSpec bool
}
var getClustersCmd GetClustersCmd
@ -21,28 +23,34 @@ func init() {
Short: "get clusters",
Long: `List or get clusters.`,
Run: func(cmd *cobra.Command, args []string) {
err := getClustersCmd.Run()
err := getClustersCmd.Run(args)
if err != nil {
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()
if err != nil {
return err
}
clusterNames, err := clusterRegistry.List()
if err != nil {
return err
var clusters []*api.Cluster
clusterNames := args
if len(args) == 0 {
clusterNames, err = clusterRegistry.List()
if err != nil {
return err
}
}
var clusters []*api.Cluster
for _, clusterName := range clusterNames {
cluster, err := clusterRegistry.Find(clusterName)
if err != nil {
@ -50,28 +58,59 @@ func (c *GetClustersCmd) Run() error {
}
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)
}
if len(clusters) == 0 {
fmt.Fprintf(os.Stderr, "No clusters found\n")
return nil
}
t := &Table{}
t.AddColumn("NAME", func(c *api.Cluster) string {
return c.Name
})
t.AddColumn("CLOUD", func(c *api.Cluster) string {
return c.Spec.CloudProvider
})
t.AddColumn("ZONES", func(c *api.Cluster) string {
var zoneNames []string
for _, z := range c.Spec.Zones {
zoneNames = append(zoneNames, z.Name)
output := getCmd.output
if output == OutputTable {
t := &Table{}
t.AddColumn("NAME", func(c *api.Cluster) string {
return c.Name
})
t.AddColumn("CLOUD", func(c *api.Cluster) string {
return c.Spec.CloudProvider
})
t.AddColumn("ZONES", func(c *api.Cluster) string {
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, ",")
})
return t.Render(clusters, os.Stdout, "NAME", "CLOUD", "ZONES")
for _, cluster := range clusters {
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)
}
}

View File

@ -3,6 +3,7 @@ package main
import (
"os"
"fmt"
"github.com/golang/glog"
"github.com/spf13/cobra"
"k8s.io/kops/upup/pkg/api"
@ -22,17 +23,17 @@ func init() {
Short: "get instancegroups",
Long: `List or get InstanceGroups.`,
Run: func(cmd *cobra.Command, args []string) {
err := getInstanceGroupsCmd.Run()
err := getInstanceGroupsCmd.Run(args)
if err != nil {
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()
if err != nil {
return err
@ -43,30 +44,64 @@ func (c *GetInstanceGroupsCmd) Run() error {
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 {
fmt.Fprintf(os.Stderr, "No InstanceGroup objects found\n")
return nil
}
t := &Table{}
t.AddColumn("NAME", func(c *api.InstanceGroup) string {
return c.Name
})
t.AddColumn("ROLE", func(c *api.InstanceGroup) string {
return string(c.Spec.Role)
})
t.AddColumn("MACHINETYPE", func(c *api.InstanceGroup) string {
return c.Spec.MachineType
})
t.AddColumn("ZONES", func(c *api.InstanceGroup) string {
return strings.Join(c.Spec.Zones, ",")
})
t.AddColumn("MIN", func(c *api.InstanceGroup) string {
return intPointerToString(c.Spec.MinSize)
})
t.AddColumn("MAX", func(c *api.InstanceGroup) string {
return intPointerToString(c.Spec.MinSize)
})
return t.Render(instancegroups, os.Stdout, "NAME", "ROLE", "MACHINETYPE", "MIN", "MAX", "ZONES")
output := getCmd.output
if output == OutputTable {
t := &Table{}
t.AddColumn("NAME", func(c *api.InstanceGroup) string {
return c.Name
})
t.AddColumn("ROLE", func(c *api.InstanceGroup) string {
return string(c.Spec.Role)
})
t.AddColumn("MACHINETYPE", func(c *api.InstanceGroup) string {
return c.Spec.MachineType
})
t.AddColumn("ZONES", func(c *api.InstanceGroup) string {
return strings.Join(c.Spec.Zones, ",")
})
t.AddColumn("MIN", func(c *api.InstanceGroup) string {
return intPointerToString(c.Spec.MinSize)
})
t.AddColumn("MAX", func(c *api.InstanceGroup) string {
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 {

175
cmd/kops/get_secrets.go Normal file
View File

@ -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)
}
}

View File

@ -1,5 +1,17 @@
package main
import (
"fmt"
"os"
)
func main() {
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)
}

View File

@ -2,141 +2,18 @@ package main
import (
"fmt"
"crypto/x509"
"github.com/golang/glog"
"github.com/spf13/cobra"
"net"
"strings"
)
type CreateSecretsCommand struct {
Id string
Type string
Usage string
Subject string
AlternateNames []string
}
var createSecretsCommand CreateSecretsCommand
func init() {
cmd := &cobra.Command{
Use: "create",
Short: "Create secrets",
Long: `Create secrets.`,
Run: func(cmd *cobra.Command, args []string) {
err := createSecretsCommand.Run()
if err != nil {
glog.Exitf("%v", err)
}
exitWithError(fmt.Errorf("The 'secrets create' command has been replaced by 'create secrets'"))
},
}
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)
}
}

View File

@ -3,178 +3,18 @@ package main
import (
"fmt"
"bytes"
"crypto/rsa"
"github.com/golang/glog"
"github.com/spf13/cobra"
"k8s.io/kops/upup/pkg/fi"
"os"
"sort"
"strings"
"text/tabwriter"
)
type DescribeSecretsCommand struct {
}
var describeSecretsCommand DescribeSecretsCommand
func init() {
cmd := &cobra.Command{
Use: "describe",
Short: "Describe secrets",
Long: `Describe secrets.`,
Run: func(cmd *cobra.Command, args []string) {
err := describeSecretsCommand.Run()
if err != nil {
glog.Exitf("%v", err)
}
exitWithError(fmt.Errorf("The 'secrets describe' command has been replaced by 'describe secrets'"))
},
}
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
}

View File

@ -3,108 +3,18 @@ package main
import (
"fmt"
"github.com/golang/glog"
"github.com/spf13/cobra"
"os"
)
type ExposeSecretsCommand struct {
ID string
Type string
}
var exposeSecretsCommand ExposeSecretsCommand
func init() {
cmd := &cobra.Command{
Use: "expose",
Short: "Expose secrets",
Long: `Expose secrets.`,
Run: func(cmd *cobra.Command, args []string) {
err := exposeSecretsCommand.Run()
if err != nil {
glog.Exitf("%v", err)
}
exitWithError(fmt.Errorf("The 'secrets export' command has been replaced by 'get secrets -oplaintext'"))
},
}
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
}

View File

@ -2,101 +2,18 @@ package main
import (
"fmt"
"bytes"
"github.com/golang/glog"
"github.com/spf13/cobra"
"os"
"text/tabwriter"
)
type GetSecretsCommand struct {
}
var getSecretsCommand GetSecretsCommand
func init() {
cmd := &cobra.Command{
Use: "get",
Short: "Get secrets",
Long: `Get secrets.`,
Run: func(cmd *cobra.Command, args []string) {
err := getSecretsCommand.Run()
if err != nil {
glog.Exitf("%v", err)
}
exitWithError(fmt.Errorf("The 'secrets get' command has been replaced by 'get secret'"))
},
}
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
}

View File

@ -24,7 +24,7 @@ And then navigate to `https://<clustername>/ui`
The login credentials are:
* 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

26
docs/secrets.md Normal file
View File

@ -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`

View File

@ -28,6 +28,19 @@ type Certificate struct {
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 {
s := ""
if err := json.Unmarshal(b, &s); err == nil {
@ -67,26 +80,36 @@ func (c *Certificate) MarshalJSON() ([]byte, error) {
type CAStore interface {
// 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(id string) (*CertificatePool, error)
PrivateKey(id string) (*PrivateKey, error)
CertificatePool(name string) (*CertificatePool, error)
PrivateKey(name string) (*PrivateKey, error)
FindCert(id string) (*Certificate, error)
FindPrivateKey(id string) (*PrivateKey, error)
FindCert(name string) (*Certificate, error)
FindPrivateKey(name string) (*PrivateKey, error)
//IssueCert(id string, privateKey *PrivateKey, template *x509.Certificate) (*Certificate, 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() vfs.Path
// 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) {

View File

@ -290,7 +290,7 @@ func (c *populateClusterSpec) run() error {
if cluster.Spec.DNSZone == "" {
dnsZone, err := cloud.FindDNSHostedZone(cluster.Name)
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)
cluster.Spec.DNSZone = dnsZone

View File

@ -61,7 +61,12 @@ func (_ *Secret) Render(c *fi.Context, a, e, changes *Secret) error {
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 {
return fmt.Errorf("error creating secret %q: %v", name, err)
}

View File

@ -14,7 +14,7 @@ type SecretStore interface {
// Find a secret, if exists. Returns nil,nil if not found
FindSecret(id string) (*Secret, error)
// 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
ListSecrets() ([]string, error)

View File

@ -2,12 +2,14 @@ package fi
import (
"bytes"
"crypto/md5"
crypto_rand "crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"fmt"
"github.com/golang/glog"
"golang.org/x/crypto/ssh"
"k8s.io/kops/upup/pkg/fi/vfs"
"math/big"
"os"
@ -316,20 +318,66 @@ func (c *VFSCAStore) FindCertificatePool(id string) (*CertificatePool, error) {
return pool, nil
}
func (c *VFSCAStore) List() ([]string, error) {
var ids []string
func (c *VFSCAStore) List() ([]*KeystoreItem, error) {
var items []*KeystoreItem
issuedDir := c.basedir.Join("issued")
files, err := issuedDir.ReadDir()
if err != nil {
return nil, fmt.Errorf("error reading directory %q: %v", issuedDir, err)
{
baseDir := c.basedir.Join("issued")
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: strings.TrimSuffix(tokens[1], ".crt"),
Type: SecretTypeKeypair,
}
items = append(items, item)
}
}
for _, f := range files {
name := f.Base()
ids = append(ids, name)
{
baseDir := c.basedir.Join("ssh", "public")
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) {
@ -572,3 +620,143 @@ func buildSerial(timestamp int64) *big.Int {
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)
}
}

View File

@ -62,7 +62,7 @@ func (c *VFSSecretStore) Secret(id string) (*Secret, error) {
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)
for i := 0; i < 2; i++ {
@ -75,12 +75,7 @@ func (c *VFSSecretStore) GetOrCreateSecret(id string) (*Secret, bool, error) {
return s, false, nil
}
s, err = CreateSecret()
if err != nil {
return nil, false, err
}
err = c.createSecret(s, p)
err = c.createSecret(secret, p)
if err != nil {
if os.IsExist(err) && i == 0 {
glog.Infof("Got already-exists error when writing secret; likely due to concurrent creation. Will retry")