Skip to content

Commit

Permalink
Adds support for instance profile credentials.
Browse files Browse the repository at this point in the history
This PR adds support to the controller for using AWS instance profiles attached to it's machine. With this change we have introduced a new credential type and blocking to make sure the instance profile is attached to the controllers machine.
  • Loading branch information
tlm committed Oct 7, 2021
1 parent e187f6c commit f431d8f
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading

0 comments on commit f431d8f

Please sign in to comment.