Gcp oslogin issue 10170 (#10360)

This commit is contained in:
Gareth Rees 2020-12-09 14:01:51 +00:00 committed by GitHub
parent f8e85f94fd
commit 355b93730b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 80 additions and 3 deletions

View File

@ -70,6 +70,9 @@ type Driver interface {
// DeleteOSLoginSSHKey deletes the SSH public key for OSLogin with the given key. // DeleteOSLoginSSHKey deletes the SSH public key for OSLogin with the given key.
DeleteOSLoginSSHKey(user, fingerprint string) error DeleteOSLoginSSHKey(user, fingerprint string) error
// If packer is running on a GCE, derives the user from it for use with OSLogin.
GetOSLoginUserFromGCE() string
// Add to the instance metadata for the existing instance // Add to the instance metadata for the existing instance
AddToInstanceMetadata(zone string, name string, metadata map[string]string) error AddToInstanceMetadata(zone string, name string, metadata map[string]string) error
} }

View File

@ -10,9 +10,11 @@ import (
"errors" "errors"
"fmt" "fmt"
"log" "log"
"net/http"
"strings" "strings"
"time" "time"
metadata "cloud.google.com/go/compute/metadata"
compute "google.golang.org/api/compute/v1" compute "google.golang.org/api/compute/v1"
"google.golang.org/api/option" "google.golang.org/api/option"
oslogin "google.golang.org/api/oslogin/v1" oslogin "google.golang.org/api/oslogin/v1"
@ -32,6 +34,7 @@ import (
type driverGCE struct { type driverGCE struct {
projectId string projectId string
service *compute.Service service *compute.Service
thisGCEUser string
osLoginService *oslogin.Service osLoginService *oslogin.Service
ui packersdk.Ui ui packersdk.Ui
} }
@ -119,6 +122,9 @@ func NewClientOptionGoogle(account *ServiceAccount, vaultOauth string, impersona
// 4. On Google Compute Engine and Google App Engine Managed VMs, it fetches // 4. On Google Compute Engine and Google App Engine Managed VMs, it fetches
// credentials from the metadata server. // credentials from the metadata server.
// (In this final case any provided scopes are ignored.) // (In this final case any provided scopes are ignored.)
//
// Note: (4) is not usable with OSLogin on Google Compute Engine (GCE).
// The GCE service account is derived separately and used instead.
} }
if err != nil { if err != nil {
@ -129,6 +135,9 @@ func NewClientOptionGoogle(account *ServiceAccount, vaultOauth string, impersona
} }
func NewDriverGCE(config GCEDriverConfig) (Driver, error) { func NewDriverGCE(config GCEDriverConfig) (Driver, error) {
var thisGCEUser string
opts, err := NewClientOptionGoogle(config.Account, config.VaultOauthEngineName, config.ImpersonateServiceAccountName) opts, err := NewClientOptionGoogle(config.Account, config.VaultOauthEngineName, config.ImpersonateServiceAccountName)
if err != nil { if err != nil {
return nil, err return nil, err
@ -140,6 +149,15 @@ func NewDriverGCE(config GCEDriverConfig) (Driver, error) {
return nil, err return nil, err
} }
if metadata.OnGCE() {
log.Printf("[INFO] On GCE, capture service account for OSLogin...")
thisGCEUser, err = metadata.NewClient(&http.Client{}).Email("")
if err != nil {
return nil, err
}
}
log.Printf("[INFO] Instantiating OS Login client...") log.Printf("[INFO] Instantiating OS Login client...")
osLoginService, err := oslogin.NewService(context.TODO(), opts) osLoginService, err := oslogin.NewService(context.TODO(), opts)
if err != nil { if err != nil {
@ -152,6 +170,7 @@ func NewDriverGCE(config GCEDriverConfig) (Driver, error) {
return &driverGCE{ return &driverGCE{
projectId: config.ProjectId, projectId: config.ProjectId,
service: service, service: service,
thisGCEUser: thisGCEUser,
osLoginService: osLoginService, osLoginService: osLoginService,
ui: config.Ui, ui: config.Ui,
}, nil }, nil
@ -625,8 +644,13 @@ func (d *driverGCE) getPasswordResponses(zone, instance string) ([]windowsPasswo
return passwordResponses, nil return passwordResponses, nil
} }
func (d *driverGCE) GetOSLoginUserFromGCE() string {
return d.thisGCEUser
}
func (d *driverGCE) ImportOSLoginSSHKey(user, sshPublicKey string) (*oslogin.LoginProfile, error) { func (d *driverGCE) ImportOSLoginSSHKey(user, sshPublicKey string) (*oslogin.LoginProfile, error) {
parent := fmt.Sprintf("users/%s", user) parent := fmt.Sprintf("users/%s", user)
resp, err := d.osLoginService.Users.ImportSshPublicKey(parent, &oslogin.SshPublicKey{ resp, err := d.osLoginService.Users.ImportSshPublicKey(parent, &oslogin.SshPublicKey{
Key: sshPublicKey, Key: sshPublicKey,
}).Do() }).Do()

View File

@ -94,6 +94,8 @@ type DriverMock struct {
AddToInstanceMetadataKVPairs map[string]string AddToInstanceMetadataKVPairs map[string]string
AddToInstanceMetadataErrCh <-chan error AddToInstanceMetadataErrCh <-chan error
AddToInstanceMetadataErr error AddToInstanceMetadataErr error
OSLoginUserFromGCE string
} }
func (d *DriverMock) CreateImage(name, description, family, zone, disk string, image_labels map[string]string, image_licenses []string, image_encryption_key *compute.CustomerEncryptionKey, imageStorageLocations []string) (<-chan *Image, <-chan error) { func (d *DriverMock) CreateImage(name, description, family, zone, disk string, image_labels map[string]string, image_licenses []string, image_encryption_key *compute.CustomerEncryptionKey, imageStorageLocations []string) (<-chan *Image, <-chan error) {
@ -308,3 +310,7 @@ func (d *DriverMock) AddToInstanceMetadata(zone string, name string, metadata ma
return nil return nil
} }
func (d *DriverMock) GetOSLoginUserFromGCE() string {
return d.OSLoginUserFromGCE
}

View File

@ -36,7 +36,10 @@ func (s *StepImportOSLoginSSHKey) Run(ctx context.Context, state multistep.State
return multistep.ActionContinue return multistep.ActionContinue
} }
if s.TokeninfoFunc == nil { // Are we running packer on a GCE ?
s.accountEmail = driver.GetOSLoginUserFromGCE()
if s.TokeninfoFunc == nil && s.accountEmail == "" {
s.TokeninfoFunc = tokeninfo s.TokeninfoFunc = tokeninfo
} }
@ -46,7 +49,7 @@ func (s *StepImportOSLoginSSHKey) Run(ctx context.Context, state multistep.State
sha256sum := sha256.Sum256(config.Comm.SSHPublicKey) sha256sum := sha256.Sum256(config.Comm.SSHPublicKey)
state.Put("ssh_key_public_sha256", hex.EncodeToString(sha256sum[:])) state.Put("ssh_key_public_sha256", hex.EncodeToString(sha256sum[:]))
if config.account != nil { if config.account != nil && s.accountEmail == "" {
s.accountEmail = config.account.jwt.Email s.accountEmail = config.account.jwt.Email
} }
@ -62,6 +65,13 @@ func (s *StepImportOSLoginSSHKey) Run(ctx context.Context, state multistep.State
s.accountEmail = info.Email s.accountEmail = info.Email
} }
if s.accountEmail == "" {
err := fmt.Errorf("All options for deriving the OSLogin user have been exhausted")
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
loginProfile, err := driver.ImportOSLoginSSHKey(s.accountEmail, string(config.Comm.SSHPublicKey)) loginProfile, err := driver.ImportOSLoginSSHKey(s.accountEmail, string(config.Comm.SSHPublicKey))
if err != nil { if err != nil {
err := fmt.Errorf("Error importing SSH public key for OSLogin: %s", err) err := fmt.Errorf("Error importing SSH public key for OSLogin: %s", err)

View File

@ -150,3 +150,37 @@ func TestStepImportOSLoginSSHKey_withPrivateSSHKey(t *testing.T) {
t.Errorf("expected to not see a public key when using a dedicated private key, but got %q", pubKey) t.Errorf("expected to not see a public key when using a dedicated private key, but got %q", pubKey)
} }
} }
func TestStepImportOSLoginSSHKey_onGCE(t *testing.T) {
state := testState(t)
d := state.Get("driver").(*DriverMock)
step := new(StepImportOSLoginSSHKey)
defer step.Cleanup(state)
fakeAccountEmail := "testing@packer.io"
config := state.Get("config").(*Config)
config.UseOSLogin = true
config.Comm.SSHPublicKey = []byte{'k', 'e', 'y'}
d.OSLoginUserFromGCE = fakeAccountEmail
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
if step.accountEmail != fakeAccountEmail {
t.Fatalf("expected accountEmail to be %q but got %q", fakeAccountEmail, step.accountEmail)
}
pubKey, ok := state.GetOk("ssh_key_public_sha256")
if !ok {
t.Fatal("expected to see a public key")
}
sha256sum := sha256.Sum256(config.Comm.SSHPublicKey)
if pubKey != hex.EncodeToString(sha256sum[:]) {
t.Errorf("expected to see a matching public key, but got %q", pubKey)
}
}

2
go.mod
View File

@ -1,7 +1,7 @@
module github.com/hashicorp/packer module github.com/hashicorp/packer
require ( require (
cloud.google.com/go v0.66.0 // indirect cloud.google.com/go v0.66.0
github.com/1and1/oneandone-cloudserver-sdk-go v1.0.1 github.com/1and1/oneandone-cloudserver-sdk-go v1.0.1
github.com/Azure/azure-sdk-for-go v40.5.0+incompatible github.com/Azure/azure-sdk-for-go v40.5.0+incompatible
github.com/Azure/go-autorest/autorest v0.10.0 github.com/Azure/go-autorest/autorest v0.10.0