diff --git a/mantle/kola/harness.go b/mantle/kola/harness.go index c2a0aeff8c..b882439a24 100644 --- a/mantle/kola/harness.go +++ b/mantle/kola/harness.go @@ -767,6 +767,7 @@ type externalTestMeta struct { TimeoutMin int `json:"timeoutMin"` Conflicts []string `json:"conflicts"` AllowConfigWarnings bool `json:"allowConfigWarnings"` + NoInstanceCreds bool `json:"noInstanceCreds"` } // metadataFromTestBinary extracts JSON-in-comment like: @@ -990,6 +991,9 @@ ExecStart=%s } else { t.Distros = strings.Fields(targetMeta.Distros) } + if targetMeta.NoInstanceCreds { + t.Flags = append(t.Flags, register.NoInstanceCreds) + } t.Tags = append(t.Tags, strings.Fields(targetMeta.Tags)...) // TODO validate tags here t.RequiredTag = targetMeta.RequiredTag @@ -1250,6 +1254,9 @@ func makeNonExclusiveTest(bucket int, tests []*register.Test, flight platform.Fl if test.HasFlag(register.NoSSHKeyInMetadata) || test.HasFlag(register.NoSSHKeyInUserData) { plog.Fatalf("Non-exclusive test %v cannot have NoSSHKeyIn* flag", test.Name) } + if test.HasFlag(register.NoInstanceCreds) { + plog.Fatalf("Non-exclusive test %v cannot have NoInstanceCreds flag", test.Name) + } if test.HasFlag(register.AllowConfigWarnings) { plog.Fatalf("Non-exclusive test %v cannot have AllowConfigWarnings flag", test.Name) } @@ -1336,6 +1343,7 @@ func runTest(h *harness.H, t *register.Test, pltfrm string, flight platform.Flig OutputDir: h.OutputDir(), NoSSHKeyInUserData: t.HasFlag(register.NoSSHKeyInUserData), NoSSHKeyInMetadata: t.HasFlag(register.NoSSHKeyInMetadata), + NoInstanceCreds: t.HasFlag(register.NoInstanceCreds), WarningsAction: conf.FailWarnings, InternetAccess: testRequiresInternet(t), } diff --git a/mantle/kola/register/register.go b/mantle/kola/register/register.go index a6d3e61dcd..dca61830ef 100644 --- a/mantle/kola/register/register.go +++ b/mantle/kola/register/register.go @@ -27,6 +27,7 @@ type Flag int const ( NoSSHKeyInUserData Flag = iota // don't inject SSH key into Ignition/cloud-config NoSSHKeyInMetadata // don't add SSH key to platform metadata + NoInstanceCreds // don't grant credentials (AWS instance profile, GCP service account) to the instance NoEmergencyShellCheck // don't check console output for emergency shell invocation RequiresInternetAccess // run the test only if the platform supports Internet access AllowConfigWarnings // ignore Ignition and Butane warnings instead of failing diff --git a/mantle/platform/api/aws/ec2.go b/mantle/platform/api/aws/ec2.go index 8bacd5ec41..9d171c63b4 100644 --- a/mantle/platform/api/aws/ec2.go +++ b/mantle/platform/api/aws/ec2.go @@ -82,7 +82,7 @@ func (a *API) DeleteKey(name string) error { } // CreateInstances creates EC2 instances with a given name tag, optional ssh key name, user data. The image ID, instance type, and security group set in the API will be used. CreateInstances will block until all instances are running and have an IP address. -func (a *API) CreateInstances(name, keyname, userdata string, count uint64, minDiskSize int64) ([]*ec2.Instance, error) { +func (a *API) CreateInstances(name, keyname, userdata string, count uint64, minDiskSize int64, useInstanceProfile bool) ([]*ec2.Instance, error) { cnt := int64(count) var ud *string @@ -91,9 +91,11 @@ func (a *API) CreateInstances(name, keyname, userdata string, count uint64, minD ud = &tud } - err := a.ensureInstanceProfile(a.opts.IAMInstanceProfile) - if err != nil { - return nil, fmt.Errorf("error verifying IAM instance profile: %v", err) + if useInstanceProfile { + err := a.ensureInstanceProfile(a.opts.IAMInstanceProfile) + if err != nil { + return nil, fmt.Errorf("error verifying IAM instance profile: %v", err) + } } sgId, err := a.getSecurityGroupID(a.opts.SecurityGroup) @@ -131,17 +133,14 @@ func (a *API) CreateInstances(name, keyname, userdata string, count uint64, minD }) } inst := ec2.RunInstancesInput{ - ImageId: &a.opts.AMI, - MinCount: &cnt, - MaxCount: &cnt, - KeyName: key, - InstanceType: &a.opts.InstanceType, - SecurityGroupIds: []*string{&sgId}, - SubnetId: &subnetId, - UserData: ud, - IamInstanceProfile: &ec2.IamInstanceProfileSpecification{ - Name: &a.opts.IAMInstanceProfile, - }, + ImageId: &a.opts.AMI, + MinCount: &cnt, + MaxCount: &cnt, + KeyName: key, + InstanceType: &a.opts.InstanceType, + SecurityGroupIds: []*string{&sgId}, + SubnetId: &subnetId, + UserData: ud, BlockDeviceMappings: rootBlockDev, TagSpecifications: []*ec2.TagSpecification{ &ec2.TagSpecification{ @@ -159,6 +158,11 @@ func (a *API) CreateInstances(name, keyname, userdata string, count uint64, minD }, }, } + if useInstanceProfile { + inst.IamInstanceProfile = &ec2.IamInstanceProfileSpecification{ + Name: &a.opts.IAMInstanceProfile, + } + } var reservations *ec2.Reservation err = util.RetryConditional(5, 5*time.Second, func(err error) bool { diff --git a/mantle/platform/api/gcloud/compute.go b/mantle/platform/api/gcloud/compute.go index 6439e9f14c..6c78d2d042 100644 --- a/mantle/platform/api/gcloud/compute.go +++ b/mantle/platform/api/gcloud/compute.go @@ -34,7 +34,7 @@ func (a *API) vmname() string { } // Taken from: https://github.com/golang/build/blob/master/buildlet/gce.go -func (a *API) mkinstance(userdata, name string, keys []*agent.Key) *compute.Instance { +func (a *API) mkinstance(userdata, name string, keys []*agent.Key, useServiceAcct bool) *compute.Instance { mantle := "mantle" metadataItems := []*compute.MetadataItems{ &compute.MetadataItems{ @@ -94,13 +94,15 @@ func (a *API) mkinstance(userdata, name string, keys []*agent.Key) *compute.Inst Network: instancePrefix + "/global/networks/" + a.options.Network, }, }, + } + if useServiceAcct { // allow the instance to perform authenticated GCS fetches - ServiceAccounts: []*compute.ServiceAccount{ + instance.ServiceAccounts = []*compute.ServiceAccount{ &compute.ServiceAccount{ Email: a.options.ServiceAcct, Scopes: []string{"https://www.googleapis.com/auth/devstorage.read_only"}, }, - }, + } } // add cloud config if userdata != "" { @@ -115,9 +117,9 @@ func (a *API) mkinstance(userdata, name string, keys []*agent.Key) *compute.Inst } // CreateInstance creates a Google Compute Engine instance. -func (a *API) CreateInstance(userdata string, keys []*agent.Key) (*compute.Instance, error) { +func (a *API) CreateInstance(userdata string, keys []*agent.Key, useServiceAcct bool) (*compute.Instance, error) { name := a.vmname() - inst := a.mkinstance(userdata, name, keys) + inst := a.mkinstance(userdata, name, keys, useServiceAcct) plog.Debugf("Creating instance %q", name) diff --git a/mantle/platform/machine/aws/cluster.go b/mantle/platform/machine/aws/cluster.go index b3dbce1442..700fe62915 100644 --- a/mantle/platform/machine/aws/cluster.go +++ b/mantle/platform/machine/aws/cluster.go @@ -83,7 +83,7 @@ func (ac *cluster) NewMachineWithOptions(userdata *conf.UserData, options platfo fmt.Printf("WARNING: compressed userdata exceeds expected limit of %d\n", MaxUserDataSize) } } - instances, err := ac.flight.api.CreateInstances(ac.Name(), keyname, ud, 1, int64(options.MinDiskSize)) + instances, err := ac.flight.api.CreateInstances(ac.Name(), keyname, ud, 1, int64(options.MinDiskSize), !ac.RuntimeConf().NoInstanceCreds) if err != nil { return nil, err } diff --git a/mantle/platform/machine/gcloud/cluster.go b/mantle/platform/machine/gcloud/cluster.go index bdabd886c5..ff0ab8befc 100644 --- a/mantle/platform/machine/gcloud/cluster.go +++ b/mantle/platform/machine/gcloud/cluster.go @@ -69,7 +69,7 @@ func (gc *cluster) NewMachineWithOptions(userdata *conf.UserData, options platfo } } - instance, err := gc.flight.api.CreateInstance(conf.String(), keys) + instance, err := gc.flight.api.CreateInstance(conf.String(), keys, !gc.RuntimeConf().NoInstanceCreds) if err != nil { return nil, err } diff --git a/mantle/platform/platform.go b/mantle/platform/platform.go index a2336d12d0..f4570957fd 100644 --- a/mantle/platform/platform.go +++ b/mantle/platform/platform.go @@ -199,6 +199,7 @@ type RuntimeConfig struct { NoSSHKeyInUserData bool // don't inject SSH key into Ignition/cloud-config NoSSHKeyInMetadata bool // don't add SSH key to platform metadata + NoInstanceCreds bool // don't grant credentials (AWS instance profile, GCP service account) to the instance AllowFailedUnits bool // don't fail CheckMachine if a systemd unit has failed WarningsAction conf.WarningsAction // what to do on Ignition or Butane validation warnings