Skip to content

Commit

Permalink
Merge pull request #13394 from tlm/aws-instance-profile
Browse files Browse the repository at this point in the history
#13394

This PR adds support to the controller for using AWS instance profiles attached to its machine. With this change we have introduced a new credential type and blocking to make sure the instance profile is attached to the controller's machine.

## Checklist

 - [x] Requires a [pylibjuju](https://github.com/juju/python-libjuju) change
 - [x] Added [integration tests](https://github.com/juju/juju/tree/develop/tests) for the PR
 - [x] Added or updated [doc.go](https://discourse.jujucharms.com/t/readme-in-packages/451) related to packages changed
 - [x] Comments answer the question of why design decisions were made

## QA steps

1. Create a new instance profile
```
aws iam create-instance-profile --instance-profile-name tlm-ip-test
{
 "InstanceProfile": {
 "Roles": [],
 "InstanceProfileName": "tlm-ip-test",
 "Path": "/",
 }
}
```
2. Add a role to the instance profile. THIS MUST BE DONE or the boostrap will hang in waiting for association.
```
aws iam add-role-to-instance-profile --instance-profile-name tlm-ip-test --role-name <role_name>
```
3. Do the bootstrap with:
```
juju bootstrap --bootstrap-constraints="instance-role=tlm-ip-test" aws/ap-southeast-2 test-ip-controller
```

NOTE: the auto keywork to instance-role will work in create an instance profile for the controller but the bootstrap will hang as we currently don't have the role sorted for the controller yet so we must create it our selves until the next PR.
  • Loading branch information
jujubot committed Oct 7, 2021
2 parents 526ee49 + f431d8f commit f970413
Show file tree
Hide file tree
Showing 14 changed files with 373 additions and 84 deletions.
5 changes: 5 additions & 0 deletions cloud/clouds.go
Expand Up @@ -46,6 +46,11 @@ const (
// AccessKeyAuthType is an authentication type using a key and secret.
AccessKeyAuthType AuthType = "access-key"

// InstanceRoleAuthType is an authentication type used by sourcing
// credentials from within the machine's context in a given cloud provider.
// You only get these credentials by running within that machine.
InstanceRoleAuthType AuthType = "instance-role"

// UserPassAuthType is an authentication type using a username and password.
UserPassAuthType AuthType = "userpass"

Expand Down
14 changes: 14 additions & 0 deletions environs/bootstrap/bootstrap.go
Expand Up @@ -621,6 +621,20 @@ func bootstrapIAAS(
if e, ok := environ.(environs.Environ); ok {
environVersion = e.Provider().Version()
}

if finalizer, ok := environ.(environs.BootstrapCredentialsFinalizer); ok {
cred, err := finalizer.FinalizeBootstrapCredential(
ctx,
bootstrapParams,
args.CloudCredential)

if err != nil {
return errors.Annotate(err, "finalizing bootstrap credential")
}

args.CloudCredential = cred
}

// Make sure we have the most recent environ config as the specified
// tools version has been updated there.
if err := finalizeInstanceBootstrapConfig(
Expand Down
9 changes: 9 additions & 0 deletions environs/interface.go
Expand Up @@ -140,6 +140,15 @@ type ProviderCredentials interface {
) (*cloud.Credential, error)
}

// BootstrapCredentialsFinalizer is an interface for environs to provide a
// method for finalizing bootstrap credentials before being passed to a
// newly bootstrapped controller.
type BootstrapCredentialsFinalizer interface {
// FinalizeBootstrapCredential finalizes credential as the last step of a
// bootstrap process.
FinalizeBootstrapCredential(BootstrapContext, BootstrapParams, *cloud.Credential) (*cloud.Credential, error)
}

// ProviderCredentialsRegister is an interface that an EnvironProvider
// implements in order to validate and automatically register credentials for
// clouds supported by the provider.
Expand Down
18 changes: 13 additions & 5 deletions provider/ec2/client.go
Expand Up @@ -11,10 +11,12 @@ import (
"github.com/aws/aws-sdk-go-v2/aws/retry"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/credentials/ec2rolecreds"
"github.com/aws/aws-sdk-go-v2/service/ec2"
"github.com/aws/smithy-go/logging"
"github.com/juju/errors"

"github.com/juju/juju/cloud"
"github.com/juju/juju/environs/cloudspec"
)

Expand Down Expand Up @@ -46,6 +48,7 @@ type ClientFunc = func(context.Context, cloudspec.CloudSpec, ...ClientOption) (C
// Client defines the subset of *ec2.Client methods that we currently use.
type Client interface {
AssociateIamInstanceProfile(context.Context, *ec2.AssociateIamInstanceProfileInput, ...func(*ec2.Options)) (*ec2.AssociateIamInstanceProfileOutput, error)
DescribeIamInstanceProfileAssociations(context.Context, *ec2.DescribeIamInstanceProfileAssociationsInput, ...func(*ec2.Options)) (*ec2.DescribeIamInstanceProfileAssociationsOutput, error)
DescribeInstances(context.Context, *ec2.DescribeInstancesInput, ...func(*ec2.Options)) (*ec2.DescribeInstancesOutput, error)
DescribeInstanceTypeOfferings(context.Context, *ec2.DescribeInstanceTypeOfferingsInput, ...func(*ec2.Options)) (*ec2.DescribeInstanceTypeOfferingsOutput, error)
DescribeInstanceTypes(context.Context, *ec2.DescribeInstanceTypesInput, ...func(*ec2.Options)) (*ec2.DescribeInstanceTypesOutput, error)
Expand Down Expand Up @@ -101,17 +104,22 @@ func configFromCloudSpec(
spec cloudspec.CloudSpec,
clientOptions ...ClientOption,
) (aws.Config, error) {
credentialAttrs := spec.Credential.Attributes()
accessKey := credentialAttrs["access-key"]
secretKey := credentialAttrs["secret-key"]
var credentialProvider aws.CredentialsProvider = ec2rolecreds.New()
if spec.Credential.AuthType() == cloud.AccessKeyAuthType {
credentialAttrs := spec.Credential.Attributes()
credentialProvider = credentials.NewStaticCredentialsProvider(
credentialAttrs["access-key"],
credentialAttrs["secret-key"],
"",
)
}

cfg, err := config.LoadDefaultConfig(ctx,
config.WithRegion(spec.Region),
config.WithRetryer(func() aws.Retryer {
return retry.NewStandard()
}),
config.WithCredentialsProvider(
credentials.NewStaticCredentialsProvider(accessKey, secretKey, "")),
config.WithCredentialsProvider(credentialProvider),
)
if err != nil {
return aws.Config{}, errors.Trace(err)
Expand Down
28 changes: 28 additions & 0 deletions provider/ec2/cloud.go
@@ -0,0 +1,28 @@
// Copyright 2021 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package ec2

import (
"github.com/juju/juju/cloud"
"github.com/juju/juju/environs"
)

type environProviderCloud struct{}

// FinalizeCloud implements environs.CloudFinalizer.FinalizeCloud
func (environProviderCloud) FinalizeCloud(
ctx environs.FinalizeCloudContext,
cld cloud.Cloud,
) (cloud.Cloud, error) {
// We want to make sure that the cloud at least has the instance role
// auth type in it's supported list as there may be alterations to a
// credential that forces this new type. Specifically this could happen
// during bootstrap. By adding it to the always supported list we are
// avoiding things blowing up. Added by tlm on 07/10/2021
if !cld.AuthTypes.Contains(cloud.InstanceRoleAuthType) {
cld.AuthTypes = append(cld.AuthTypes, cloud.InstanceRoleAuthType)
}

return cld, nil
}
34 changes: 34 additions & 0 deletions provider/ec2/cloud_test.go
@@ -0,0 +1,34 @@
// Copyright 2021 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package ec2

import (
"sort"

gc "gopkg.in/check.v1"

"github.com/juju/juju/cloud"
jc "github.com/juju/testing/checkers"
)

type cloudSuite struct {
}

var _ = gc.Suite(&cloudSuite{})

func (*cloudSuite) TestFinalizeCloudSetAuthTypes(c *gc.C) {
environCloud := environProviderCloud{}
r, err := environCloud.FinalizeCloud(nil, cloud.Cloud{})
c.Assert(err, jc.ErrorIsNil)
sort.Sort(r.AuthTypes)
c.Assert(r.AuthTypes, jc.DeepEquals, cloud.AuthTypes{"instance-role"})
}

func (*cloudSuite) TestFinalizeCloudSetAuthTypesAddition(c *gc.C) {
environCloud := environProviderCloud{}
r, err := environCloud.FinalizeCloud(nil, cloud.Cloud{AuthTypes: cloud.AuthTypes{"test"}})
c.Assert(err, jc.ErrorIsNil)
sort.Sort(r.AuthTypes)
c.Assert(r.AuthTypes, jc.DeepEquals, cloud.AuthTypes{"instance-role", "test"})
}
31 changes: 16 additions & 15 deletions provider/ec2/context_mock_test.go

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

19 changes: 19 additions & 0 deletions provider/ec2/credentials.go
Expand Up @@ -20,6 +20,17 @@ import (

type environProviderCredentials struct{}

// AuthTypes returns all of the AuthTypes supported by the ec2 environ
// credentials provider.
func (e environProviderCredentials) AuthTypes() cloud.AuthTypes {
credSchemas := e.CredentialSchemas()
at := make(cloud.AuthTypes, 0, len(credSchemas))
for k := range credSchemas {
at = append(at, k)
}
return at
}

// CredentialSchemas is part of the environs.ProviderCredentials interface.
func (environProviderCredentials) CredentialSchemas() map[cloud.AuthType]cloud.CredentialSchema {
return map[cloud.AuthType]cloud.CredentialSchema{
Expand All @@ -37,6 +48,14 @@ func (environProviderCredentials) CredentialSchemas() map[cloud.AuthType]cloud.C
},
},
},
cloud.InstanceRoleAuthType: {
{
"instance-profile-name",
cloud.CredentialAttr{
Description: "The AWS Instance Profile name",
},
},
},
}
}

Expand Down
2 changes: 1 addition & 1 deletion provider/ec2/credentials_test.go
Expand Up @@ -36,7 +36,7 @@ func (s *credentialsSuite) SetUpTest(c *gc.C) {
}

func (s *credentialsSuite) TestCredentialSchemas(c *gc.C) {
envtesting.AssertProviderAuthTypes(c, s.provider, "access-key")
envtesting.AssertProviderAuthTypes(c, s.provider, "access-key", "instance-role")
}

func (s *credentialsSuite) TestAccessKeyCredentialsValid(c *gc.C) {
Expand Down

0 comments on commit f970413

Please sign in to comment.