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

add support for managing ssh keys #47

Merged
merged 1 commit into from
Mar 15, 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 scaleway/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func Provider() terraform.ResourceProvider {

ResourcesMap: map[string]*schema.Resource{
"scaleway_server": resourceScalewayServer(),
"scaleway_ssh_key": resourceScalewaySSHKey(),
"scaleway_ip": resourceScalewayIP(),
"scaleway_security_group": resourceScalewaySecurityGroup(),
"scaleway_security_group_rule": resourceScalewaySecurityGroupRule(),
Expand Down
135 changes: 135 additions & 0 deletions scaleway/resource_ssh_key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package scaleway

import (
"fmt"
"strings"

"github.com/hashicorp/terraform/helper/schema"
api "github.com/nicolai86/scaleway-sdk"
"golang.org/x/crypto/ssh"
)

func resourceScalewaySSHKey() *schema.Resource {
return &schema.Resource{
Create: resourceScalewaySSHKeyCreate,
Read: resourceScalewaySSHKeyRead,
Delete: resourceScalewaySSHKeyDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"key": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "The ssh key",
},
},
}
}

func getSSHKeyFingerprint(key []byte) (string, error) {
pubkey, _, _, _, err := ssh.ParseAuthorizedKey(key)
if err != nil {
return "", err
}
return ssh.FingerprintLegacyMD5(pubkey), nil
}

func resourceScalewaySSHKeyCreate(d *schema.ResourceData, m interface{}) error {
scaleway := m.(*Client).scaleway

mu.Lock()
defer mu.Unlock()
fingerprint, err := getSSHKeyFingerprint([]byte(d.Get("key").(string)))
if err != nil {
return err
}

user, err := scaleway.GetUser()
if err != nil {
return err
}

keys := []api.KeyDefinition{}
exists := false
for _, key := range user.SSHPublicKeys {
exists = exists || key.Key == d.Get("key").(string)
keys = append(keys, api.KeyDefinition{
Key: key.Key,
})
}

// remote already contains the key, nothing to do
if exists {
d.SetId(fingerprint)
return nil
}

user, err = scaleway.PatchUserSSHKey(user.ID, api.UserPatchSSHKeyDefinition{
SSHPublicKeys: append(keys, api.KeyDefinition{
Key: d.Get("key").(string),
}),
})

if err != nil {
return err
}

d.SetId(fingerprint)
return nil
}

func resourceScalewaySSHKeyRead(d *schema.ResourceData, m interface{}) error {
scaleway := m.(*Client).scaleway

user, err := scaleway.GetUser()
if err != nil {
return err
}

exists := false
for _, key := range user.SSHPublicKeys {
exists = exists || strings.Contains(key.Fingerprint, d.Id())
if exists {
d.Set("key", key.Key)
break
}
}
if !exists {
return fmt.Errorf("ssh key does not exist anymore")
}

return nil
}

func resourceScalewaySSHKeyDelete(d *schema.ResourceData, m interface{}) error {
scaleway := m.(*Client).scaleway

mu.Lock()
defer mu.Unlock()

user, err := scaleway.GetUser()
if err != nil {
return err
}

keys := []api.KeyDefinition{}
for _, key := range user.SSHPublicKeys {
if !strings.Contains(key.Fingerprint, d.Id()) {
keys = append(keys, api.KeyDefinition{
Key: key.Key,
})
}
}
user, err = scaleway.PatchUserSSHKey(user.ID, api.UserPatchSSHKeyDefinition{
SSHPublicKeys: keys,
})

if err != nil {
return err
}
d.SetId("")
return nil
}
94 changes: 94 additions & 0 deletions scaleway/resource_ssh_key_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package scaleway

import (
"errors"
"strings"
"testing"

"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)

func TestGetSSHKeyFingerprint(t *testing.T) {
key := []byte("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDYpDmIzRs5c+xs0jmljMbNYVcgV8fRruMCRDA4HKjGN2lqLTZhngGDXsdt/2kTNQQPAq2sR4N8mfX5wMRT/+jNb+8esPyY5WlElni0zmD7oLoPW4lYRES6f7EeAv6NttLfkDO42r15OtMnglcgWk1u4o3lOXuLbhzJT1qdicpDja22X3uR/xUy1AYhKBOoiSlQbkb7NhL0lA1xQNwerdaJJS8tFB+wViVDyP0f1HaIRxViFlTGuTbTuIJNR/7VJ9VBBuTnYXaRkPxz64sUXrtdVK8U0+4KsisyXwmgQKnvZBDj91wxz12OOzFSQ52iFprIj1JbkzuBmNWXUGKYzXJZ nicolai86@test")
fingerprint, err := getSSHKeyFingerprint(key)

if err != nil {
t.Errorf("Expected no error, but got %v", err.Error())
}
if fingerprint != "d1:4c:45:59:a8:ee:e6:41:10:fb:3c:3e:54:98:5b:6f" {
t.Errorf("Expected fingerprint of %q, but got %q", "d1:4c:45:59:a8:ee:e6:41:10:fb:3c:3e:54:98:5b:6f", fingerprint)
}
}

func TestAccScalewaySSHKey_Basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckScalewaySSHKeyDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCheckScalewaySSHKeyConfig,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
"scaleway_ssh_key.test", "id", "d1:4c:45:59:a8:ee:e6:41:10:fb:3c:3e:54:98:5b:6f"),
),
},
resource.TestStep{
Config: testAccCheckScalewaySSHKeysConfig,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
"scaleway_ssh_key.test", "id", "d1:4c:45:59:a8:ee:e6:41:10:fb:3c:3e:54:98:5b:6f"),
resource.TestCheckResourceAttr(
"scaleway_ssh_key.test2", "id", "71:a9:e9:ec:5a:43:bc:49:0c:59:1d:74:0d:bb:a4:24"),
),
},
resource.TestStep{
Config: testAccCheckScalewaySSHKeyConfig,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
"scaleway_ssh_key.test", "id", "d1:4c:45:59:a8:ee:e6:41:10:fb:3c:3e:54:98:5b:6f"),
),
},
},
})
}

func testAccCheckScalewaySSHKeyDestroy(s *terraform.State) error {
client := testAccProvider.Meta().(*Client).scaleway

for _, rs := range s.RootModule().Resources {
if rs.Type != "scaleway" {
continue
}

user, err := client.GetUser()
if err != nil {
return err
}
for _, key := range user.SSHPublicKeys {
if strings.Contains(key.Fingerprint, rs.Primary.ID) {
return errors.New("key still exists.")
}
}
return nil
}

return nil
}

var testAccCheckScalewaySSHKeyConfig = `
resource "scaleway_ssh_key" "test" {
key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDYpDmIzRs5c+xs0jmljMbNYVcgV8fRruMCRDA4HKjGN2lqLTZhngGDXsdt/2kTNQQPAq2sR4N8mfX5wMRT/+jNb+8esPyY5WlElni0zmD7oLoPW4lYRES6f7EeAv6NttLfkDO42r15OtMnglcgWk1u4o3lOXuLbhzJT1qdicpDja22X3uR/xUy1AYhKBOoiSlQbkb7NhL0lA1xQNwerdaJJS8tFB+wViVDyP0f1HaIRxViFlTGuTbTuIJNR/7VJ9VBBuTnYXaRkPxz64sUXrtdVK8U0+4KsisyXwmgQKnvZBDj91wxz12OOzFSQ52iFprIj1JbkzuBmNWXUGKYzXJZ test"
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given the comments above, it might be worth a test adding multiple keys?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added testcases for multiple keys including removal.

`

var testAccCheckScalewaySSHKeysConfig = `
resource "scaleway_ssh_key" "test" {
key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDYpDmIzRs5c+xs0jmljMbNYVcgV8fRruMCRDA4HKjGN2lqLTZhngGDXsdt/2kTNQQPAq2sR4N8mfX5wMRT/+jNb+8esPyY5WlElni0zmD7oLoPW4lYRES6f7EeAv6NttLfkDO42r15OtMnglcgWk1u4o3lOXuLbhzJT1qdicpDja22X3uR/xUy1AYhKBOoiSlQbkb7NhL0lA1xQNwerdaJJS8tFB+wViVDyP0f1HaIRxViFlTGuTbTuIJNR/7VJ9VBBuTnYXaRkPxz64sUXrtdVK8U0+4KsisyXwmgQKnvZBDj91wxz12OOzFSQ52iFprIj1JbkzuBmNWXUGKYzXJZ test"
}

resource "scaleway_ssh_key" "test2" {
key = "ssh-dss AAAAB3NzaC1kc3MAAACBAOU48/Wjx5JYNdcxdbb0mfJ3vtRU5wzPXmJcaa5CbpWeG6x3wD2+1aasxgO54YdSfEJwVXcoqPwx5gQfpAEgZmYi3M7Yurv+iwAJaSk+CFHOdhxUNPdwWKxsuIA1vk+edhqTKPC5fMFPpMQU/QDr5XegLhCUq11oRjnpfhzmi96/AAAAFQCTRSG8CPxOGVfYbZF/NjRGRgDWMQAAAIArEya6WPd7Bz19rn6u0KC0LeBHmxjoe0M9hblrFHjL4sLpxW1qipUKN+zwXKR9lv4Y/voyzirc7a8DPrEIyMy6SjPu/CiTwx7zDv08nE4qx20V8X0FrusvPbm5jzQJBweUvUZFZcM7Ybvk7RwawaLCGBGZ6Mg/P2YWTfor88NnjwAAAIBUfM9wfQvn0bHso8bsxFFtdME0eZbyeIRlU8JPjOatei4/eyFMHYvfqeGxiGgox1/E7/qX+3rXuypQFTa8DlhyteZCproysFa8NRh8PJZ7uchWrgPZHuXISW+UwlJ/5cJpFxn3ijzdOzEj5EiDM+LBtFYtA5/obIq68eqK6tqM0w== test2"
}
`
8 changes: 8 additions & 0 deletions vendor/golang.org/x/crypto/curve25519/const_amd64.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions vendor/golang.org/x/crypto/curve25519/const_amd64.s

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

65 changes: 65 additions & 0 deletions vendor/golang.org/x/crypto/curve25519/cswap_amd64.s

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading