mirror of https://github.com/kubernetes/kops.git
Import SSH public key into the keystore
This means it only needs to be specified during `kops create`. We remove the option from `kops update` for consistency. This will shortly be manageable using the secrets functionality. Fix #221
This commit is contained in:
parent
c2dc9fd992
commit
bd3ab166b7
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
"github.com/golang/glog"
|
||||
"github.com/spf13/cobra"
|
||||
"io/ioutil"
|
||||
"k8s.io/kops/upup/pkg/api"
|
||||
"k8s.io/kops/upup/pkg/fi"
|
||||
"k8s.io/kops/upup/pkg/fi/cloudup"
|
||||
|
|
@ -112,6 +113,17 @@ func (c *CreateClusterCmd) Run(args []string) error {
|
|||
c.OutDir = "out"
|
||||
}
|
||||
|
||||
var sshPublicKeyData []byte
|
||||
if c.SSHPublicKey != "" {
|
||||
sshPublicKey := c.SSHPublicKey
|
||||
sshPublicKey = utils.ExpandPath(sshPublicKey)
|
||||
authorized, err := ioutil.ReadFile(sshPublicKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading SSH key file %q: %v", sshPublicKey, err)
|
||||
}
|
||||
sshPublicKeyData = authorized
|
||||
}
|
||||
|
||||
clusterRegistry, err := rootCommand.ClusterRegistry()
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -297,10 +309,6 @@ func (c *CreateClusterCmd) Run(args []string) error {
|
|||
}
|
||||
}
|
||||
|
||||
if c.SSHPublicKey != "" {
|
||||
c.SSHPublicKey = utils.ExpandPath(c.SSHPublicKey)
|
||||
}
|
||||
|
||||
if c.AdminAccess != "" {
|
||||
cluster.Spec.AdminAccess = []string{c.AdminAccess}
|
||||
}
|
||||
|
|
@ -345,6 +353,14 @@ func (c *CreateClusterCmd) Run(args []string) error {
|
|||
return fmt.Errorf("error writing updated configuration: %v", err)
|
||||
}
|
||||
|
||||
if sshPublicKeyData != nil {
|
||||
keystore := clusterRegistry.KeyStore(clusterName)
|
||||
err := keystore.AddSSHPublicKey("admin", sshPublicKeyData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error storing SSH public key: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
err = clusterRegistry.WriteCompletedConfig(fullCluster)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error writing completed cluster spec: %v", err)
|
||||
|
|
@ -360,7 +376,6 @@ func (c *CreateClusterCmd) Run(args []string) error {
|
|||
Models: strings.Split(c.Models, ","),
|
||||
ClusterRegistry: clusterRegistry,
|
||||
Target: c.Target,
|
||||
SSHPublicKey: c.SSHPublicKey,
|
||||
OutDir: c.OutDir,
|
||||
DryRun: isDryrun,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"crypto/x509"
|
||||
"github.com/golang/glog"
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/kops/upup/pkg/fi"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
|
@ -62,9 +63,13 @@ func (cmd *CreateSecretsCommand) Run() error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, created, err := secretStore.GetOrCreateSecret(cmd.Id)
|
||||
secret, err := fi.CreateSecret()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating secrets %v", err)
|
||||
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")
|
||||
|
|
|
|||
|
|
@ -6,17 +6,15 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
"k8s.io/kops/upup/pkg/api"
|
||||
"k8s.io/kops/upup/pkg/fi/cloudup"
|
||||
"k8s.io/kops/upup/pkg/fi/utils"
|
||||
"k8s.io/kops/upup/pkg/kutil"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type UpdateClusterCmd struct {
|
||||
Yes bool
|
||||
Target string
|
||||
Models string
|
||||
OutDir string
|
||||
SSHPublicKey string
|
||||
Yes bool
|
||||
Target string
|
||||
Models string
|
||||
OutDir string
|
||||
}
|
||||
|
||||
var updateCluster UpdateClusterCmd
|
||||
|
|
@ -39,7 +37,6 @@ func init() {
|
|||
cmd.Flags().BoolVar(&updateCluster.Yes, "yes", false, "Actually create cloud resources")
|
||||
cmd.Flags().StringVar(&updateCluster.Target, "target", "direct", "Target - direct, terraform")
|
||||
cmd.Flags().StringVar(&updateCluster.Models, "model", "config,proto,cloudup", "Models to apply (separate multiple models with commas)")
|
||||
cmd.Flags().StringVar(&updateCluster.SSHPublicKey, "ssh-public-key", "~/.ssh/id_rsa.pub", "SSH public key to use")
|
||||
cmd.Flags().StringVar(&updateCluster.OutDir, "out", "", "Path to write any local output")
|
||||
}
|
||||
|
||||
|
|
@ -86,10 +83,6 @@ func (c *UpdateClusterCmd) Run(args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if c.SSHPublicKey != "" {
|
||||
c.SSHPublicKey = utils.ExpandPath(c.SSHPublicKey)
|
||||
}
|
||||
|
||||
strict := false
|
||||
err = api.DeepValidate(cluster, fullInstanceGroups, strict)
|
||||
if err != nil {
|
||||
|
|
@ -102,7 +95,6 @@ func (c *UpdateClusterCmd) Run(args []string) error {
|
|||
Models: strings.Split(c.Models, ","),
|
||||
ClusterRegistry: clusterRegistry,
|
||||
Target: c.Target,
|
||||
SSHPublicKey: c.SSHPublicKey,
|
||||
OutDir: c.OutDir,
|
||||
DryRun: isDryrun,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
# Detailed description of arguments
|
||||
|
||||
# create
|
||||
|
||||
## ssh-public-key
|
||||
|
||||
`--ssh-public-key` is the path to the SSH public key to set up on the cluster. (The SSH username is admin)
|
||||
|
||||
This will automatically create an SSH public-key in the keystore (`kubectl get secret`) with a name of `admin`.
|
||||
|
||||
## admin-access
|
||||
|
||||
`admin-access` controls the CIDR which can access the admin endpoints (SSH to each node, HTTPS to the master).
|
||||
|
|
|
|||
|
|
@ -28,6 +28,13 @@ type Certificate struct {
|
|||
PublicKey crypto.PublicKey
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
@ -87,6 +94,12 @@ type CAStore interface {
|
|||
|
||||
// AddCert adds an alternative certificate to the pool (primarily useful for CAs)
|
||||
AddCert(id string, cert *Certificate) error
|
||||
|
||||
// AddSSHPublicKey adds an SSH public key
|
||||
AddSSHPublicKey(id string, data []byte) error
|
||||
|
||||
// FindSSHPublicKeys retrieves the SSH public keys with the specific name
|
||||
FindSSHPublicKeys(name string) ([]*KeystoreItem, error)
|
||||
}
|
||||
|
||||
func (c *Certificate) AsString() (string, error) {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package cloudup
|
|||
import (
|
||||
"fmt"
|
||||
"github.com/golang/glog"
|
||||
"io/ioutil"
|
||||
"k8s.io/kops/upup/pkg/api"
|
||||
"k8s.io/kops/upup/pkg/fi"
|
||||
"k8s.io/kops/upup/pkg/fi/cloudup/awstasks"
|
||||
|
|
@ -35,8 +34,7 @@ type ApplyClusterCmd struct {
|
|||
Target string
|
||||
//// The node model to use
|
||||
//NodeModel string
|
||||
// The SSH public key (file) to use
|
||||
SSHPublicKey string
|
||||
|
||||
// OutDir is a local directory in which we place output, can cache files etc
|
||||
OutDir string
|
||||
|
||||
|
|
@ -235,11 +233,23 @@ func (c *ApplyClusterCmd) Run() error {
|
|||
"dnsZone": &awstasks.DNSZone{},
|
||||
})
|
||||
|
||||
if c.SSHPublicKey == "" {
|
||||
return fmt.Errorf("SSH public key must be specified when running with AWS")
|
||||
l.TemplateFunctions["MachineTypeInfo"] = awsup.GetMachineTypeInfo
|
||||
|
||||
{
|
||||
sshPublicKeys, err := keyStore.FindSSHPublicKeys("admin")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading SSH public key %q: %v", "admin", err)
|
||||
}
|
||||
if len(sshPublicKeys) == 0 {
|
||||
return fmt.Errorf("Must specify SSH public key when running with AWS")
|
||||
}
|
||||
if len(sshPublicKeys) != 1 {
|
||||
glog.Warningf("Found multiple SSH public keys - choosing arbitrarily")
|
||||
}
|
||||
sshPublicKey := sshPublicKeys[0]
|
||||
l.Resources["ssh-public-key"] = fi.NewStringResource(string(sshPublicKey.Data))
|
||||
}
|
||||
|
||||
l.TemplateFunctions["MachineTypeInfo"] = awsup.GetMachineTypeInfo
|
||||
}
|
||||
|
||||
default:
|
||||
|
|
@ -328,15 +338,6 @@ func (c *ApplyClusterCmd) Run() error {
|
|||
|
||||
tf.AddTo(l.TemplateFunctions)
|
||||
|
||||
if c.SSHPublicKey != "" {
|
||||
authorized, err := ioutil.ReadFile(c.SSHPublicKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading SSH key file %q: %v", c.SSHPublicKey, err)
|
||||
}
|
||||
|
||||
l.Resources["ssh-public-key"] = fi.NewStringResource(string(authorized))
|
||||
}
|
||||
|
||||
taskMap, err := l.BuildTasks(modelStore, c.Models)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error building tasks: %v", err)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -25,6 +27,10 @@ type VFSCAStore struct {
|
|||
cacheCaPrivateKeys *privateKeys
|
||||
}
|
||||
|
||||
const (
|
||||
SecretTypeSSHPublicKey = "SSHPublicKey"
|
||||
)
|
||||
|
||||
var _ CAStore = &VFSCAStore{}
|
||||
|
||||
func NewVFSCAStore(basedir vfs.Path) CAStore {
|
||||
|
|
@ -572,3 +578,93 @@ func buildSerial(timestamp int64) *big.Int {
|
|||
|
||||
return serial
|
||||
}
|
||||
|
||||
// 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 = fmt.Sprintf("%x", h.Sum(nil))
|
||||
}
|
||||
|
||||
p := c.buildSSHPublicKeyPath(name, id)
|
||||
return c.storeData(pubkey, p)
|
||||
}
|
||||
|
||||
func (c *VFSCAStore) buildSSHPublicKeyPath(name string, id string) vfs.Path {
|
||||
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 {
|
||||
item.Type = SecretTypeSSHPublicKey
|
||||
item.Name = name
|
||||
}
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
Loading…
Reference in New Issue