Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move ssh key functions to pkg/pki #4653

Merged
merged 1 commit into from
Mar 11, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pkg/model/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ go_library(
"//pkg/model/components:go_default_library",
"//pkg/model/iam:go_default_library",
"//pkg/model/resources:go_default_library",
"//pkg/pki:go_default_library",
"//pkg/tokens:go_default_library",
"//upup/pkg/fi:go_default_library",
"//upup/pkg/fi/cloudup/awstasks:go_default_library",
Expand Down
4 changes: 3 additions & 1 deletion pkg/model/names.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ import (
"fmt"

"github.com/golang/glog"

"k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/pkg/pki"
"k8s.io/kops/upup/pkg/fi/cloudup/awstasks"
)

Expand Down Expand Up @@ -126,7 +128,7 @@ func (c *KopsModelContext) SSHKeyName() (string, error) {
return name, nil
}

fingerprint, err := awstasks.ComputeOpenSSHKeyFingerprint(string(c.SSHPublicKeys[0]))
fingerprint, err := pki.ComputeOpenSSHKeyFingerprint(string(c.SSHPublicKeys[0]))
if err != nil {
return "", err
}
Expand Down
8 changes: 7 additions & 1 deletion pkg/pki/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,23 @@ go_library(
"certificate.go",
"csr.go",
"privatekey.go",
"sshkey.go",
],
importpath = "k8s.io/kops/pkg/pki",
visibility = ["//visibility:public"],
deps = ["//vendor/github.com/golang/glog:go_default_library"],
deps = [
"//upup/pkg/fi/utils:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/golang.org/x/crypto/ssh:go_default_library",
],
)

go_test(
name = "go_default_test",
srcs = [
"certificate_test.go",
"privatekey_test.go",
"sshkey_test.go",
],
embed = [":go_default_library"],
)
127 changes: 127 additions & 0 deletions pkg/pki/sshkey.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
Copyright 2016 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package pki

import (
"bytes"
"crypto"
"crypto/md5"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"fmt"
"reflect"
"strings"

"golang.org/x/crypto/ssh"

"k8s.io/kops/upup/pkg/fi/utils"
)

// parseSSHPublicKey parses the SSH public key string
func parseSSHPublicKey(publicKey string) (ssh.PublicKey, error) {
tokens := strings.Fields(publicKey)
if len(tokens) < 2 {
return nil, fmt.Errorf("error parsing SSH public key: %q", publicKey)
}

sshPublicKeyBytes, err := base64.StdEncoding.DecodeString(tokens[1])
if err != nil {
return nil, fmt.Errorf("error decoding SSH public key: %q err: %s", publicKey, err)
}
if len(tokens) < 2 {
return nil, fmt.Errorf("error decoding SSH public key: %q", publicKey)
}

sshPublicKey, err := ssh.ParsePublicKey(sshPublicKeyBytes)
if err != nil {
return nil, fmt.Errorf("error parsing SSH public key: %v", err)
}
return sshPublicKey, nil
}

// colonSeparatedHex formats the byte slice SSH-fingerprint style: hex bytes separated by colons
func colonSeparatedHex(data []byte) string {
sshKeyFingerprint := fmt.Sprintf("%x", data)
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()
}

// ComputeAWSKeyFingerprint computes the AWS-specific fingerprint of the SSH public key
func ComputeAWSKeyFingerprint(publicKey string) (string, error) {
sshPublicKey, err := parseSSHPublicKey(publicKey)
if err != nil {
return "", err
}

der, err := toDER(sshPublicKey)
if err != nil {
return "", fmt.Errorf("error computing fingerprint for SSH public key: %v", err)
}
h := md5.Sum(der)

return colonSeparatedHex(h[:]), nil
}

// ComputeOpenSSHKeyFingerprint computes the OpenSSH fingerprint of the SSH public key
func ComputeOpenSSHKeyFingerprint(publicKey string) (string, error) {
sshPublicKey, err := parseSSHPublicKey(publicKey)
if err != nil {
return "", err
}

h := md5.Sum(sshPublicKey.Marshal())
return colonSeparatedHex(h[:]), 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("Unexpected type of SSH key (%q); AWS can only import RSA keys", typeName)
}

der, err := x509.MarshalPKIXPublicKey(cryptoKey)
if err != nil {
return nil, fmt.Errorf("error marshalling SSH public key: %v", err)
}
return der, nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

package awstasks
package pki

import (
"fmt"
Expand All @@ -23,7 +23,7 @@ import (
)

func checkAWSFingerprintEqual(t *testing.T, publicKey string, fingerprint string) {
actual, err := computeAWSKeyFingerprint(publicKey)
actual, err := ComputeAWSKeyFingerprint(publicKey)
if err != nil {
t.Fatalf("Unexpected error computing AWS key fingerprint: %v", err)
}
Expand All @@ -33,7 +33,7 @@ func checkAWSFingerprintEqual(t *testing.T, publicKey string, fingerprint string
}

func checkAWSFingerprintError(t *testing.T, publicKey string, message string) {
_, err := computeAWSKeyFingerprint(publicKey)
_, err := ComputeAWSKeyFingerprint(publicKey)
if err == nil {
t.Fatalf("Expected error %q computing AWS key fingerprint", message)
}
Expand Down
3 changes: 1 addition & 2 deletions upup/pkg/fi/cloudup/awstasks/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ go_library(
deps = [
"//pkg/diff:go_default_library",
"//pkg/featureflag:go_default_library",
"//pkg/pki:go_default_library",
"//upup/pkg/fi:go_default_library",
"//upup/pkg/fi/cloudup/awsup:go_default_library",
"//upup/pkg/fi/cloudup/cloudformation:go_default_library",
Expand All @@ -80,7 +81,6 @@ go_library(
"//vendor/github.com/aws/aws-sdk-go/service/iam:go_default_library",
"//vendor/github.com/aws/aws-sdk-go/service/route53:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/golang.org/x/crypto/ssh:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
],
Expand All @@ -94,7 +94,6 @@ go_test(
"elastic_ip_test.go",
"internetgateway_test.go",
"securitygroup_test.go",
"sshkey_test.go",
"subnet_test.go",
"vpc_test.go",
],
Expand Down
107 changes: 3 additions & 104 deletions upup/pkg/fi/cloudup/awstasks/sshkey.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,18 @@ limitations under the License.
package awstasks

import (
"bytes"
"crypto"
"crypto/md5"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"fmt"
"reflect"
"strings"

"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/kops/pkg/pki"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
"k8s.io/kops/upup/pkg/fi/cloudup/cloudformation"
"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
"k8s.io/kops/upup/pkg/fi/utils"
)

//go:generate fitask -type=SSHKey
Expand Down Expand Up @@ -102,108 +95,14 @@ func (e *SSHKey) find(cloud awsup.AWSCloud) (*SSHKey, error) {
return actual, nil
}

// parseSSHPublicKey parses the SSH public key string
func parseSSHPublicKey(publicKey string) (ssh.PublicKey, error) {
tokens := strings.Fields(publicKey)
if len(tokens) < 2 {
return nil, fmt.Errorf("error parsing SSH public key: %q", publicKey)
}

sshPublicKeyBytes, err := base64.StdEncoding.DecodeString(tokens[1])
if err != nil {
return nil, fmt.Errorf("error decoding SSH public key: %q err: %s", publicKey, err)
}
if len(tokens) < 2 {
return nil, fmt.Errorf("error decoding SSH public key: %q", publicKey)
}

sshPublicKey, err := ssh.ParsePublicKey(sshPublicKeyBytes)
if err != nil {
return nil, fmt.Errorf("error parsing SSH public key: %v", err)
}
return sshPublicKey, nil
}

// colonSeparatedHex formats the byte slice SSH-fingerprint style: hex bytes separated by colons
func colonSeparatedHex(data []byte) string {
sshKeyFingerprint := fmt.Sprintf("%x", data)
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()
}

// computeAWSKeyFingerprint computes the AWS-specific fingerprint of the SSH public key
func computeAWSKeyFingerprint(publicKey string) (string, error) {
sshPublicKey, err := parseSSHPublicKey(publicKey)
if err != nil {
return "", err
}

der, err := toDER(sshPublicKey)
if err != nil {
return "", fmt.Errorf("error computing fingerprint for SSH public key: %v", err)
}
h := md5.Sum(der)

return colonSeparatedHex(h[:]), nil
}

// ComputeOpenSSHKeyFingerprint computes the OpenSSH fingerprint of the SSH public key
func ComputeOpenSSHKeyFingerprint(publicKey string) (string, error) {
sshPublicKey, err := parseSSHPublicKey(publicKey)
if err != nil {
return "", err
}

h := md5.Sum(sshPublicKey.Marshal())
return colonSeparatedHex(h[:]), 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("Unexpected type of SSH key (%q); AWS can only import RSA keys", 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 {
publicKey, err := e.PublicKey.AsString()
if err != nil {
return fmt.Errorf("error reading SSH public key: %v", err)
}

keyFingerprint, err := computeAWSKeyFingerprint(publicKey)
keyFingerprint, err := pki.ComputeAWSKeyFingerprint(publicKey)
if err != nil {
return fmt.Errorf("error computing key fingerprint for SSH key: %v", err)
}
Expand Down