diff --git a/pkg/cloudprovider/provider/digitalocean/provider.go b/pkg/cloudprovider/provider/digitalocean/provider.go index fce0dfe01..e5fe92352 100644 --- a/pkg/cloudprovider/provider/digitalocean/provider.go +++ b/pkg/cloudprovider/provider/digitalocean/provider.go @@ -2,12 +2,13 @@ package digitalocean import ( "context" + "crypto/rand" + "crypto/rsa" "encoding/json" "errors" "fmt" "net/http" "strconv" - "sync" "time" "github.com/digitalocean/godo" @@ -15,27 +16,26 @@ import ( "golang.org/x/crypto/ssh" "golang.org/x/oauth2" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apimachinery/pkg/util/wait" - "github.com/kubermatic/machine-controller/pkg/cloudprovider/cloud" cloudprovidererrors "github.com/kubermatic/machine-controller/pkg/cloudprovider/errors" "github.com/kubermatic/machine-controller/pkg/cloudprovider/instance" "github.com/kubermatic/machine-controller/pkg/machines/v1alpha1" "github.com/kubermatic/machine-controller/pkg/providerconfig" - machinessh "github.com/kubermatic/machine-controller/pkg/ssh" + "github.com/pborman/uuid" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/wait" ) +const privateRSAKeyBitSize = 4096 + type provider struct { - // TODO(p0lyn0mial): remove privateKey - privateKey *machinessh.PrivateKey configVarResolver *providerconfig.ConfigVarResolver } // New returns a digitalocean provider func New(configVarResolver *providerconfig.ConfigVarResolver) cloud.Provider { - return &provider{privateKey: nil, configVarResolver: configVarResolver} + return &provider{configVarResolver: configVarResolver} } type RawConfig struct { @@ -66,9 +66,6 @@ const ( createCheckFailedWaitPeriod = 10 * time.Second ) -// Protects creation of public key -var publicKeyCreationLock = &sync.Mutex{} - type TokenSource struct { AccessToken string } @@ -230,33 +227,29 @@ func (p *provider) Validate(spec v1alpha1.MachineSpec) error { return nil } -func ensureSSHKeysExist(ctx context.Context, service godo.KeysService, key *machinessh.PrivateKey) (string, error) { - publicKeyCreationLock.Lock() - defer publicKeyCreationLock.Unlock() - - publicKey := key.PublicKey() - pk, err := ssh.NewPublicKey(&publicKey) +// uploadSSHPublicKey uploads public part of the key to digital ocean +// this method returns an error if the key already exists +func uploadSSHPublicKey(ctx context.Context, service godo.KeysService, key *rsa.PublicKey) (string, error) { + pk, err := ssh.NewPublicKey(key) if err != nil { return "", fmt.Errorf("failed to parse publickey: %v", err) } fingerprint := ssh.FingerprintLegacyMD5(pk) - dokey, res, err := service.GetByFingerprint(ctx, fingerprint) + existingkey, res, err := service.GetByFingerprint(ctx, fingerprint) + if err == nil && existingkey != nil && res.StatusCode >= http.StatusOK && res.StatusCode <= http.StatusAccepted { + return "", fmt.Errorf("failed to create ssh public key, the key already exists") + } + + newDoKey, _, err := service.Create(ctx, &godo.KeyCreateRequest{ + PublicKey: string(ssh.MarshalAuthorizedKey(pk)), + Name: string(uuid.NewUUID()), + }) if err != nil { - if res != nil && res.StatusCode == http.StatusNotFound { - dokey, _, err = service.Create(ctx, &godo.KeyCreateRequest{ - PublicKey: string(ssh.MarshalAuthorizedKey(pk)), - Name: key.Name(), - }) - if err != nil { - return "", fmt.Errorf("failed to create ssh public key on digitalocean: %v", err) - } - return dokey.Fingerprint, nil - } - return "", fmt.Errorf("failed to get key from digitalocean: %v", err) + return "", fmt.Errorf("failed to create ssh public key on digitalocean: %v", err) } - return dokey.Fingerprint, nil + return newDoKey.Fingerprint, nil } func (p *provider) Create(machine *v1alpha1.Machine, userdata string) (instance.Instance, error) { @@ -268,19 +261,24 @@ func (p *provider) Create(machine *v1alpha1.Machine, userdata string) (instance. ctx := context.TODO() client := getClient(c.Token) - // TODO(p0lyn0mial): an ssh-key should be created only if the CoreOS was requested - // TODO(p0lyn0mial): an ssh-key must be temporal - deleted after an instance has been created. - if p.privateKey == nil { - var err error - p.privateKey, err = machinessh.NewPrivateKey("machine-controller") - if err != nil { - return nil, err - } + tmpRSAKeyPair, err := rsa.GenerateKey(rand.Reader, privateRSAKeyBitSize) + if err != nil { + return nil, fmt.Errorf("failed to create private RSA key: %v", err) + } + + if err := tmpRSAKeyPair.Validate(); err != nil { + return nil, fmt.Errorf("failed to validate private RSA key: %v", err) } - fingerprint, err := ensureSSHKeysExist(ctx, client.Keys, p.privateKey) + fingerprint, err := uploadSSHPublicKey(ctx, client.Keys, &tmpRSAKeyPair.PublicKey) if err != nil { - return nil, fmt.Errorf("failed ensure that the ssh key '%s' exists: %v", p.privateKey.Name(), err) + return nil, fmt.Errorf("failed upload the ssh key, error = %v", err) } + defer func() { + _, err := client.Keys.DeleteByFingerprint(ctx, fingerprint) + if err != nil { + glog.Errorf("failed to remove a temporary ssh key with fingerprint = %v, due to = %v", fingerprint, err) + } + }() slug, err := getSlugForOS(pc.OperatingSystem) if err != nil { diff --git a/pkg/controller/machine.go b/pkg/controller/machine.go index a3ce71465..133d579e0 100644 --- a/pkg/controller/machine.go +++ b/pkg/controller/machine.go @@ -37,7 +37,6 @@ import ( "github.com/kubermatic/machine-controller/pkg/containerruntime/docker" machinev1alpha1 "github.com/kubermatic/machine-controller/pkg/machines/v1alpha1" "github.com/kubermatic/machine-controller/pkg/providerconfig" - "github.com/kubermatic/machine-controller/pkg/ssh" "github.com/kubermatic/machine-controller/pkg/userdata" corev1 "k8s.io/api/core/v1" @@ -78,7 +77,6 @@ type Controller struct { workqueue workqueue.RateLimitingInterface - sshPrivateKey *ssh.PrivateKey clusterDNSIPs []net.IP metrics MetricsCollection kubeconfigProvider KubeconfigProvider diff --git a/pkg/ssh/key.go b/pkg/ssh/key.go deleted file mode 100644 index 3d4c24797..000000000 --- a/pkg/ssh/key.go +++ /dev/null @@ -1,36 +0,0 @@ -package ssh - -import ( - "crypto/rand" - "crypto/rsa" - "fmt" -) - -const privateKeyBitSize = 2048 - -type PrivateKey struct { - key *rsa.PrivateKey - name string -} - -func (p *PrivateKey) Name() string { - return p.name -} - -func (p *PrivateKey) PublicKey() rsa.PublicKey { - return p.key.PublicKey -} - -// NewPrivateKey generates a new PrivateKey -func NewPrivateKey(name string) (key *PrivateKey, err error) { - priv, err := rsa.GenerateKey(rand.Reader, privateKeyBitSize) - if err != nil { - return nil, fmt.Errorf("failed to create private key: %v", err) - } - - if err := priv.Validate(); err != nil { - return nil, fmt.Errorf("failed to validate private key: %v", err) - } - - return &PrivateKey{key: priv, name: name}, nil -}