@@ -1,29 +1,55 @@
|
||||
package gcp
|
||||
package cloud
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/oauth2/google"
|
||||
"google.golang.org/api/compute/v1"
|
||||
"google.golang.org/api/oslogin/v1"
|
||||
)
|
||||
|
||||
// ComputeClient wraps the original *compute.Service
|
||||
type ComputeClient struct {
|
||||
*compute.Service
|
||||
Project string
|
||||
type GCPClient struct {
|
||||
gce *compute.Service
|
||||
oslogin *oslogin.Service
|
||||
client_email string
|
||||
Project string
|
||||
}
|
||||
|
||||
// NewComputeClient will initialize a GCP compute API client
|
||||
func NewComputeClient(project string) (*ComputeClient, error) {
|
||||
func NewGCPClient(project string) (*GCPClient, error) {
|
||||
var err error
|
||||
ctx := context.Background()
|
||||
credentials, err := google.FindDefaultCredentials(ctx, compute.ComputeScope)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("couldn't fetch default client credentials: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s, err := compute.NewService(ctx)
|
||||
var jsonCreds map[string]interface{}
|
||||
err = json.Unmarshal(credentials.JSON, &jsonCreds)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("couldn't decode default client credentials json: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gce, err := compute.NewService(ctx)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("couldn't initialize GCP client: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
computeService := &ComputeClient{s, project}
|
||||
|
||||
return computeService, nil
|
||||
osc, err := oslogin.NewService(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c := &GCPClient{
|
||||
gce: gce,
|
||||
oslogin: osc,
|
||||
client_email: jsonCreds["client_email"].(string),
|
||||
Project: project,
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package gcp
|
||||
package cloud
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -6,24 +6,26 @@ import (
|
||||
"google.golang.org/api/compute/v1"
|
||||
)
|
||||
|
||||
// GetIPAddresses returns a list of external IP addresses used for the SHH connection
|
||||
func (c *ComputeClient) GetIPAddresses(instances []*compute.Instance) []string {
|
||||
addresses := []string{}
|
||||
for _, instance := range instances {
|
||||
addresses = append(addresses, instance.NetworkInterfaces[0].AccessConfigs[0].NatIP+":22")
|
||||
}
|
||||
return addresses
|
||||
}
|
||||
|
||||
// GetInstances returns a list of external IP addresses used for the SHH connection
|
||||
func (c *ComputeClient) GetInstances(filter string) ([]*compute.Instance, error) {
|
||||
listCall := c.Instances.AggregatedList(c.Project)
|
||||
func (c *GCPClient) GetInstances(filter string, usePrivateIP bool) ([]Instance, error) {
|
||||
instances := []Instance{}
|
||||
listCall := c.gce.Instances.AggregatedList(c.Project).Fields("nextPageToken", "items(Name,NetworkInterfaces)")
|
||||
var ctx context.Context
|
||||
instances := []*compute.Instance{}
|
||||
|
||||
listCall.Filter(filter).Pages(ctx, func(list *compute.InstanceAggregatedList) error {
|
||||
for _, item := range list.Items {
|
||||
instances = append(instances, item.Instances...)
|
||||
for _, instance := range item.Instances {
|
||||
i := &Instance{
|
||||
Name: instance.Name,
|
||||
}
|
||||
if usePrivateIP {
|
||||
i.Address = instance.NetworkInterfaces[0].NetworkIP
|
||||
} else {
|
||||
i.Address = instance.NetworkInterfaces[0].AccessConfigs[0].NatIP
|
||||
}
|
||||
|
||||
instances = append(instances, *i)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
@@ -1,30 +1,22 @@
|
||||
package gcp
|
||||
package cloud
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/user"
|
||||
"sort"
|
||||
"speedrun/key"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"google.golang.org/api/compute/v1"
|
||||
)
|
||||
|
||||
// AddKeyToMetadataP updates SSH key entires in the project metadata
|
||||
func (c *ComputeClient) AddKeyToMetadataP(pubKey ssh.PublicKey) error {
|
||||
getProject := c.Projects.Get(c.Project)
|
||||
projectData, err := getProject.Do()
|
||||
// addKeyToMetadataP updates SSH key entires in the project metadata
|
||||
func (c *GCPClient) AddKeyToMetadata(key *key.Key) error {
|
||||
projectData, err := c.gce.Projects.Get(c.Project).Do()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authorizedKey, err := formatPubKey(pubKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
item, err := createMetadataItem(authorizedKey)
|
||||
item, err := formatSSHKey(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -45,7 +37,7 @@ func (c *ComputeClient) AddKeyToMetadataP(pubKey ssh.PublicKey) error {
|
||||
Items: items,
|
||||
}
|
||||
|
||||
setMetadata := c.Projects.SetCommonInstanceMetadata(c.Project, &metadata)
|
||||
setMetadata := c.gce.Projects.SetCommonInstanceMetadata(c.Project, &metadata)
|
||||
_, err = setMetadata.Do()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -54,59 +46,14 @@ func (c *ComputeClient) AddKeyToMetadataP(pubKey ssh.PublicKey) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddKeyToMetadata adds ssh public key to the intsance metadata
|
||||
func (c *ComputeClient) AddKeyToMetadata(instance *compute.Instance, pubKey ssh.PublicKey) error {
|
||||
authorizedKey, err := formatPubKey(pubKey)
|
||||
// removeKeyFromMetadata removes user's ssh public key from the project metadata
|
||||
func (c *GCPClient) RemoveKeyFromMetadata(key *key.Key) error {
|
||||
projectData, err := c.gce.Projects.Get(c.Project).Do()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
item, err := createMetadataItem(authorizedKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hasKey, same, i := hasItem(instance.Metadata, item)
|
||||
var items []*compute.MetadataItems
|
||||
|
||||
if hasKey && same {
|
||||
return nil
|
||||
} else if hasKey && !same {
|
||||
items = updateMetadata(instance.Metadata, item, i)
|
||||
} else if !hasKey {
|
||||
items = appendToMetadata(instance.Metadata, item)
|
||||
}
|
||||
|
||||
metadata := compute.Metadata{
|
||||
Fingerprint: instance.Metadata.Fingerprint,
|
||||
Items: items,
|
||||
}
|
||||
|
||||
instance.Metadata = &metadata
|
||||
s := strings.Split(instance.Zone, "/")
|
||||
zone := s[len(s)-1]
|
||||
call := c.Instances.Update(c.Project, zone, instance.Name, instance)
|
||||
_, err = call.Do()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s failed to update metadata: ", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveKeyFromMetadataP removes user's ssh public key from the project metadata
|
||||
func (c *ComputeClient) RemoveKeyFromMetadataP(pubKey ssh.PublicKey) error {
|
||||
getProject := c.Projects.Get(c.Project)
|
||||
projectData, err := getProject.Do()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authorizedKey, err := formatPubKey(pubKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
item, err := createMetadataItem(authorizedKey)
|
||||
item, err := formatSSHKey(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -125,7 +72,7 @@ func (c *ComputeClient) RemoveKeyFromMetadataP(pubKey ssh.PublicKey) error {
|
||||
Items: items,
|
||||
}
|
||||
|
||||
setMetadata := c.Projects.SetCommonInstanceMetadata(c.Project, &metadata)
|
||||
setMetadata := c.gce.Projects.SetCommonInstanceMetadata(c.Project, &metadata)
|
||||
_, err = setMetadata.Do()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -134,58 +81,6 @@ func (c *ComputeClient) RemoveKeyFromMetadataP(pubKey ssh.PublicKey) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveKeyFromMetadata removes user's ssh public key from the intsance metadata
|
||||
func (c *ComputeClient) RemoveKeyFromMetadata(instance *compute.Instance, pubKey ssh.PublicKey) error {
|
||||
authorizedKey, err := formatPubKey(pubKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
item, err := createMetadataItem(authorizedKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hasKey, same, i := hasItem(instance.Metadata, item)
|
||||
var items []*compute.MetadataItems
|
||||
|
||||
if hasKey && same {
|
||||
removeFromMetadata(instance.Metadata, item, i)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
metadata := compute.Metadata{
|
||||
Fingerprint: instance.Metadata.Fingerprint,
|
||||
Items: items,
|
||||
}
|
||||
|
||||
instance.Metadata = &metadata
|
||||
s := strings.Split(instance.Zone, "/")
|
||||
zone := s[len(s)-1]
|
||||
call := c.Instances.Update(c.Project, zone, instance.Name, instance)
|
||||
_, err = call.Do()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s failed to update metadata: ", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isBlocking(i *compute.Instance) bool {
|
||||
for _, m := range i.Metadata.Items {
|
||||
if m.Key == "sshKeys" || m.Key == "block-project-ssh-keys" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func formatPubKey(pubKey ssh.PublicKey) (string, error) {
|
||||
authorizedKey := ssh.MarshalAuthorizedKey(pubKey)
|
||||
tk := strings.TrimSuffix(string(authorizedKey), "\n")
|
||||
return tk, nil
|
||||
}
|
||||
|
||||
// Extracts username, algorithm and comment from a metadata SSH key item
|
||||
func parseMetadataitem(key string) (string, string, string) {
|
||||
t := strings.Split(key, " ")
|
||||
@@ -217,19 +112,14 @@ func hasItem(md *compute.Metadata, x string) (bool, bool, int) {
|
||||
return false, false, -1
|
||||
}
|
||||
|
||||
// createMetadataItem formats public key item according to GCP guidelines
|
||||
func createMetadataItem(pubKey string) (string, error) {
|
||||
user, err := user.Current()
|
||||
// formatSSHKey formats public key item according to GCP guidelines
|
||||
func formatSSHKey(key *key.Key) (string, error) {
|
||||
authorizedKey, err := key.MarshalAuthorizedKey()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
v := fmt.Sprintf("%s:%s %s", user.Username, pubKey, hostname)
|
||||
v := fmt.Sprintf("%s:%s %s", key.User, authorizedKey, key.Comment)
|
||||
return v, nil
|
||||
}
|
||||
|
||||
|
||||
62
cloud/google_oslogin.go
Normal file
62
cloud/google_oslogin.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package cloud
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"speedrun/key"
|
||||
|
||||
"github.com/apex/log"
|
||||
"google.golang.org/api/oslogin/v1"
|
||||
)
|
||||
|
||||
func (c *GCPClient) AddUserKey(key *key.Key) error {
|
||||
parent := fmt.Sprintf("users/%s", c.client_email)
|
||||
|
||||
authorizedKey, err := key.MarshalAuthorizedKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sshPublicKey := &oslogin.SshPublicKey{
|
||||
Key: string(authorizedKey),
|
||||
}
|
||||
|
||||
_, err = c.oslogin.Users.ImportSshPublicKey(parent, sshPublicKey).Do()
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *GCPClient) RemoveUserKey(key *key.Key) error {
|
||||
authorizedKey, err := key.MarshalAuthorizedKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
name := fmt.Sprintf("users/%s/sshPublicKeys/%x", c.client_email, sha256.Sum256([]byte(authorizedKey)))
|
||||
_, err = c.oslogin.Users.SshPublicKeys.Delete(name).Do()
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *GCPClient) ListUserKeys() error {
|
||||
parent := fmt.Sprintf("users/%s", c.client_email)
|
||||
|
||||
profile, err := c.oslogin.Users.GetLoginProfile(parent).Do()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, k := range profile.SshPublicKeys {
|
||||
log.Info(k.Key)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *GCPClient) GetSAUsername() (string, error) {
|
||||
parent := fmt.Sprintf("users/%s", c.client_email)
|
||||
|
||||
profile, err := c.oslogin.Users.GetLoginProfile(parent).Do()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return profile.PosixAccounts[0].Username, nil
|
||||
}
|
||||
6
cloud/instance.go
Normal file
6
cloud/instance.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package cloud
|
||||
|
||||
type Instance struct {
|
||||
Address string
|
||||
Name string
|
||||
}
|
||||
Reference in New Issue
Block a user