- Isolate OSLogin service account derivation from google metadata server to OSLogin step only

This commit is contained in:
Gareth Rees 2021-01-12 21:38:41 +00:00
parent 707d2d8236
commit 2dd2ef3c49
5 changed files with 30 additions and 80 deletions

View File

@ -70,9 +70,6 @@ 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,11 +10,9 @@ 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"
@ -34,7 +32,6 @@ 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
} }
@ -147,8 +144,6 @@ func NewDriverGCE(config GCEDriverConfig) (Driver, error) {
return nil, err return nil, err
} }
thisGCEUser := getGCEUser()
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 {
@ -161,7 +156,6 @@ 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
@ -635,10 +629,6 @@ 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)
@ -790,29 +780,3 @@ func (d *driverGCE) AddToInstanceMetadata(zone string, name string, metadata map
return nil return nil
} }
// getGCEUser determines if we're running packer on a GCE, and if we are, gets the associated service account email for subsequent use with OSLogin.
// There are cases where we are running on a GCE, but the GCP metadata server isn't accessible. GitLab docker-engine runners are an edge case example of this.
// It makes little sense to run packer on GCP in this way, however, we defensively timeout in those cases, rather than abort.
func getGCEUser() string {
metadataCheckTimeout := 5 * time.Second
metadataCheckChl := make(chan string, 1)
go func() {
if metadata.OnGCE() {
log.Printf("[INFO] Attempt to capture the GCE service account for use with OSLogin...")
GCEUser, _ := metadata.NewClient(&http.Client{}).Email("")
metadataCheckChl <- GCEUser
}
}()
select {
case thisGCEUser := <-metadataCheckChl:
log.Printf("[INFO] GCE service account %s will be used for OSLogin", thisGCEUser)
return thisGCEUser
case <-time.After(metadataCheckTimeout):
log.Printf("[INFO] Timeout after %s whilst waiting for google metadata server.", metadataCheckTimeout)
return ""
}
}

View File

@ -94,8 +94,6 @@ 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) {
@ -310,7 +308,3 @@ func (d *DriverMock) AddToInstanceMetadata(zone string, name string, metadata ma
return nil return nil
} }
func (d *DriverMock) GetOSLoginUserFromGCE() string {
return d.OSLoginUserFromGCE
}

View File

@ -5,7 +5,11 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"log"
"net/http"
"time"
metadata "cloud.google.com/go/compute/metadata"
"github.com/hashicorp/packer-plugin-sdk/multistep" "github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer" packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"google.golang.org/api/oauth2/v2" "google.golang.org/api/oauth2/v2"
@ -37,7 +41,7 @@ func (s *StepImportOSLoginSSHKey) Run(ctx context.Context, state multistep.State
} }
// Are we running packer on a GCE ? // Are we running packer on a GCE ?
s.accountEmail = driver.GetOSLoginUserFromGCE() s.accountEmail = getGCEUser()
if s.TokeninfoFunc == nil && s.accountEmail == "" { if s.TokeninfoFunc == nil && s.accountEmail == "" {
s.TokeninfoFunc = tokeninfo s.TokeninfoFunc = tokeninfo
@ -140,3 +144,28 @@ func tokeninfo(ctx context.Context) (*oauth2.Tokeninfo, error) {
return svc.Tokeninfo().Context(ctx).Do() return svc.Tokeninfo().Context(ctx).Do()
} }
// getGCEUser determines if we're running packer on a GCE, and if we are, gets the associated service account email for subsequent use with OSLogin.
// There are cases where we are running on a GCE, but the GCP metadata server isn't accessible. GitLab docker-engine runners are an edge case example of this.
// It makes little sense to run packer on GCP in this way, however, we defensively timeout in those cases, rather than abort.
func getGCEUser() string {
metadataCheckTimeout := 5 * time.Second
metadataCheckChl := make(chan string, 1)
go func() {
if metadata.OnGCE() {
GCEUser, _ := metadata.NewClient(&http.Client{}).Email("")
metadataCheckChl <- GCEUser
}
}()
select {
case thisGCEUser := <-metadataCheckChl:
log.Printf("[INFO] OSLogin: GCE service account %s will be used for identity", thisGCEUser)
return thisGCEUser
case <-time.After(metadataCheckTimeout):
log.Printf("[INFO] OSLogin: Could not derive a GCE service account from google metadata server after %s", metadataCheckTimeout)
return ""
}
}

View File

@ -150,37 +150,3 @@ 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)
}
}