mirror of https://github.com/kubernetes/kops.git
200 lines
5.1 KiB
Go
200 lines
5.1 KiB
Go
package awstasks
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"bytes"
|
|
"crypto"
|
|
"crypto/dsa"
|
|
"crypto/md5"
|
|
"crypto/rsa"
|
|
"crypto/x509"
|
|
"encoding/base64"
|
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
|
"github.com/aws/aws-sdk-go/service/ec2"
|
|
"github.com/golang/glog"
|
|
"golang.org/x/crypto/ssh"
|
|
"k8s.io/kube-deploy/upup/pkg/fi"
|
|
"k8s.io/kube-deploy/upup/pkg/fi/cloudup/awsup"
|
|
"k8s.io/kube-deploy/upup/pkg/fi/utils"
|
|
"reflect"
|
|
"strings"
|
|
)
|
|
|
|
type SSHKey struct {
|
|
Name *string
|
|
PublicKey fi.Resource
|
|
|
|
KeyFingerprint *string
|
|
}
|
|
|
|
var _ fi.CompareWithID = &SecurityGroup{}
|
|
|
|
func (e *SSHKey) CompareWithID() *string {
|
|
return e.Name
|
|
}
|
|
|
|
func (e *SSHKey) String() string {
|
|
return fi.TaskAsString(e)
|
|
}
|
|
func (e *SSHKey) Find(c *fi.Context) (*SSHKey, error) {
|
|
cloud := c.Cloud.(*awsup.AWSCloud)
|
|
|
|
request := &ec2.DescribeKeyPairsInput{
|
|
KeyNames: []*string{e.Name},
|
|
}
|
|
|
|
response, err := cloud.EC2.DescribeKeyPairs(request)
|
|
if awsErr, ok := err.(awserr.Error); ok {
|
|
if awsErr.Code() == "InvalidKeyPair.NotFound" {
|
|
return nil, nil
|
|
}
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error listing SSHKeys: %v", err)
|
|
}
|
|
|
|
if response == nil || len(response.KeyPairs) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
if len(response.KeyPairs) != 1 {
|
|
return nil, fmt.Errorf("Found multiple SSHKeys with Name %q", *e.Name)
|
|
}
|
|
|
|
k := response.KeyPairs[0]
|
|
|
|
actual := &SSHKey{
|
|
Name: k.KeyName,
|
|
KeyFingerprint: k.KeyFingerprint,
|
|
}
|
|
|
|
// Avoid spurious changes
|
|
if fi.StringValue(actual.KeyFingerprint) == fi.StringValue(e.KeyFingerprint) {
|
|
glog.V(2).Infof("SSH key fingerprints match; assuming public keys match")
|
|
actual.PublicKey = e.PublicKey
|
|
} else {
|
|
glog.V(2).Infof("Computed SSH key fingerprint mismatch: %q %q", fi.StringValue(e.KeyFingerprint), fi.StringValue(actual.KeyFingerprint))
|
|
}
|
|
|
|
return actual, nil
|
|
}
|
|
|
|
func computeAwsKeyFingerprint(publicKey fi.Resource) (string, error) {
|
|
publicKeyString, err := fi.ResourceAsString(publicKey)
|
|
if err != nil {
|
|
return "", fmt.Errorf("error reading SSH public key: %v", err)
|
|
}
|
|
|
|
tokens := strings.Split(publicKeyString, " ")
|
|
if len(tokens) < 2 {
|
|
return "", fmt.Errorf("error parsing SSH public key: %s", publicKeyString)
|
|
}
|
|
|
|
sshPublicKeyBytes, err := base64.StdEncoding.DecodeString(tokens[1])
|
|
if len(tokens) < 2 {
|
|
return "", fmt.Errorf("error decoding SSH public key: %s", publicKeyString)
|
|
}
|
|
|
|
sshPublicKey, err := ssh.ParsePublicKey(sshPublicKeyBytes)
|
|
if err != nil {
|
|
return "", fmt.Errorf("error parsing SSH public key: %v", err)
|
|
}
|
|
|
|
der, err := toDER(sshPublicKey)
|
|
if err != nil {
|
|
return "", fmt.Errorf("error computing fingerprint for SSH public key: %v", err)
|
|
}
|
|
h := md5.Sum(der)
|
|
sshKeyFingerprint := fmt.Sprintf("%x", h)
|
|
|
|
var colonSeparated bytes.Buffer
|
|
for i := 0; i < len(sshKeyFingerprint); i++ {
|
|
if (i%2) == 0 && i != 0 {
|
|
colonSeparated.WriteByte(':')
|
|
}
|
|
colonSeparated.WriteByte(sshKeyFingerprint[i])
|
|
}
|
|
|
|
return colonSeparated.String(), nil
|
|
}
|
|
|
|
// toDER gets the DER encoding of the SSH public key
|
|
// Annoyingly, the ssh code wraps the actual crypto keys, so we have to use reflection tricks
|
|
func toDER(pubkey ssh.PublicKey) ([]byte, error) {
|
|
pubkeyValue := reflect.ValueOf(pubkey)
|
|
typeName := utils.BuildTypeName(pubkeyValue.Type())
|
|
|
|
var cryptoKey crypto.PublicKey
|
|
switch typeName {
|
|
case "*rsaPublicKey":
|
|
var rsaPublicKey *rsa.PublicKey
|
|
targetType := reflect.ValueOf(rsaPublicKey).Type()
|
|
rsaPublicKey = pubkeyValue.Convert(targetType).Interface().(*rsa.PublicKey)
|
|
cryptoKey = rsaPublicKey
|
|
|
|
case "*dsaPublicKey":
|
|
var dsaPublicKey *dsa.PublicKey
|
|
targetType := reflect.ValueOf(dsaPublicKey).Type()
|
|
dsaPublicKey = pubkeyValue.Convert(targetType).Interface().(*dsa.PublicKey)
|
|
cryptoKey = dsaPublicKey
|
|
|
|
default:
|
|
return nil, fmt.Errorf("Unknown type for SSH PublicKey; cannot compute fingerprint: %q", typeName)
|
|
}
|
|
|
|
der, err := x509.MarshalPKIXPublicKey(cryptoKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error marshalling SSH public key: %v", err)
|
|
}
|
|
return der, nil
|
|
}
|
|
|
|
func (e *SSHKey) Run(c *fi.Context) error {
|
|
if e.KeyFingerprint == nil && e.PublicKey != nil {
|
|
keyFingerprint, err := computeAwsKeyFingerprint(e.PublicKey)
|
|
if err != nil {
|
|
return fmt.Errorf("error computing key fingerpring for SSH key: %v", err)
|
|
}
|
|
glog.V(2).Infof("Computed SSH key fingerprint as %q", keyFingerprint)
|
|
e.KeyFingerprint = &keyFingerprint
|
|
}
|
|
return fi.DefaultDeltaRunMethod(e, c)
|
|
}
|
|
|
|
func (s *SSHKey) CheckChanges(a, e, changes *SSHKey) error {
|
|
if a != nil {
|
|
if changes.Name != nil {
|
|
return fi.CannotChangeField("Name")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (_ *SSHKey) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *SSHKey) error {
|
|
if a == nil {
|
|
glog.V(2).Infof("Creating SSHKey with Name:%q", *e.Name)
|
|
|
|
request := &ec2.ImportKeyPairInput{
|
|
KeyName: e.Name,
|
|
}
|
|
|
|
if e.PublicKey != nil {
|
|
d, err := fi.ResourceAsBytes(e.PublicKey)
|
|
if err != nil {
|
|
return fmt.Errorf("error rendering SSHKey PublicKey: %v", err)
|
|
}
|
|
request.PublicKeyMaterial = d
|
|
}
|
|
|
|
response, err := t.Cloud.EC2.ImportKeyPair(request)
|
|
if err != nil {
|
|
return fmt.Errorf("error creating SSHKey: %v", err)
|
|
}
|
|
|
|
e.KeyFingerprint = response.KeyFingerprint
|
|
}
|
|
|
|
return nil //return output.AddAWSTags(cloud.Tags(), v, "vpc")
|
|
}
|