Skip to content

Commit

Permalink
Merge pull request #22 from mikkeloscar/assume-role
Browse files Browse the repository at this point in the history
Add option to define assume role at startup
  • Loading branch information
mikkeloscar authored Jun 5, 2019
2 parents 28c8aab + f313164 commit 2c39d42
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 5 deletions.
174 changes: 174 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,180 @@ It is currently moving a lot of effort on to the users defining the pod specs.
A future idea is to make the controller act as an admission controller which
can inject the required configuration automatically.

### Setting up AWS IAM roles

The controller does not take care of AWS IAM role provisioning and assumes that
the user provisions AWS IAM roles manually, for instance via
[CloudFormation](https://aws.amazon.com/cloudformation/) or
[Terraform](https://www.terraform.io/).

Here is an example of an AWS IAM role defined via CloudFormation:

```yaml
Parameters:
AssumeRoleARN:
Description: "Role ARN of the role used by kube-aws-iam-controller"
Type: String
Metadata:
StackName: "aws-iam-example"
AWSTemplateFormatVersion: "2010-09-09"
Description: "Example IAM Role"
Resources:
IAMRole:
Type: AWS::IAM::Role
Properties:
RoleName: "aws-iam-example"
Path: /
AssumeRolePolicyDocument:
Statement:
- Action: sts:AssumeRole
Effect: Allow
Principal:
AWS: !Ref "AssumeRoleARN"
Version: '2012-10-17'
Policies:
- PolicyName: "policy"
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- "ec2:Describe*"
Resource: "*"
```

The role could be created via:

```sh
# $ASSUME_ROLE_ARN is the ARN of the role used by the kube-aws-iam-controller deployment
$ aws cloudformation create-stack --stack-name aws-iam-example \
--parameters "ParameterKey=AssumeRoleARN,ParameterValue=$ASSUME_ROLE_ARN" \
--template-body=file://iam-role.yaml --capabilities CAPABILITY_NAMED_IAM
```

The important part is the `AssumeRolePolicyDocument`:

```yaml
AssumeRolePolicyDocument:
Statement:
- Action: sts:AssumeRole
Effect: Allow
Principal:
AWS: !Ref "AssumeRoleARN"
Version: '2012-10-17'
```

This allows the `kube-aws-iam-controller` to assume the role and provide
credentials on behalf of the application requesting credentials via an
`AWSIAMRole` resource in the cluster.

The `AssumeRoleARN` is the ARN of the role which the `kube-aws-iam-controller`
is running with. Usually this would be the instance role of the EC2 instance
were the controller is running.

#### Using custom Assume role

Sometimes it's desirable to let the controller assume roles with a specific
role dedicated for that task i.e. a role different from the instance role. The
controller allows specifying such a role via the
`--assume-role=<controller-role>` flag providing the following setup:

```
+-------------+
| |
+--> | <app-role1> |
+-----------------+ +-------------------+ | | |
| | | | | +-------------+
| <instance-role> | -- assumes --> | <controller-role> | -- assumes --+
| | | | | +-------------+
+-----------------+ +-------------------+ | | |
+--> | <app-role2> |
| |
+-------------+
```

In this case the `<instance-role>` will only be used for the initial assuming
of the `<controller-role>` and all `<app-role>s` are assumed by the
`<controller-role>`. This makes it possible to have many different
`<instance-role>s` while the `<app-role>s` only have to trust the single static
`<controller-role>`. If you don't specify `--assume-role` then the
`<instance-role>` would have to assume the `<app-role>s`.

Here is an example of the AWS IAM roles defined for this set-up to work:

```yaml
Metadata:
StackName: "aws-iam-assume-role-example"
AWSTemplateFormatVersion: "2010-09-09"
Description: "Example AWS IAM Assume Role"
Resources:
InstanceIAMRole:
Type: AWS::IAM::Role
Properties:
RoleName: "instance-role"
Path: /
AssumeRolePolicyDocument:
Statement:
- Action: sts:AssumeRole
Effect: Allow
Principal:
Service: ec2.amazonaws.com
Version: '2012-10-17'
Policies:
- PolicyName: "policy"
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- "sts:AssumeRole"
Resource: "*"
KubeAWSIAMControllerIAMRole:
Type: AWS::IAM::Role
Properties:
RoleName: "kube-aws-iam-controller"
Path: /
AssumeRolePolicyDocument:
Statement:
- Action: sts:AssumeRole
Effect: Allow
Principal:
AWS: !Sub 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/${InstanceIAMRole}'
Version: '2012-10-17'
Policies:
- PolicyName: "policy"
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- "sts:AssumeRole"
Resource: "*"
APPIAMRole:
Type: AWS::IAM::Role
Properties:
RoleName: "app-role"
Path: /
AssumeRolePolicyDocument:
Statement:
- Action: sts:AssumeRole
Effect: Allow
Principal:
AWS: !Sub 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/${KubeAWSIAMControllerIAMRole}'
Version: '2012-10-17'
Policies:
- PolicyName: "policy"
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- "ec2:Describe*"
Resource: "*"
```

## Setup

The `kube-aws-iam-controller` can be run as a deployment in the cluster.
Expand Down
4 changes: 2 additions & 2 deletions credentials_getter.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ type STSCredentialsGetter struct {
}

// NewSTSCredentialsGetter initializes a new STS based credentials fetcher.
func NewSTSCredentialsGetter(sess *session.Session, baseRoleARN string) *STSCredentialsGetter {
func NewSTSCredentialsGetter(sess *session.Session, baseRoleARN string, configs ...*aws.Config) *STSCredentialsGetter {
return &STSCredentialsGetter{
svc: sts.New(sess),
svc: sts.New(sess, configs...),
baseRoleARN: baseRoleARN,
}
}
Expand Down
22 changes: 19 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import (
"net/url"
"os"
"os/signal"
"strings"
"syscall"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/mikkeloscar/kube-aws-iam-controller/pkg/clientset"
log "github.com/sirupsen/logrus"
Expand All @@ -31,6 +34,7 @@ var (
BaseRoleARN string
APIServer *url.URL
Namespace string
AssumeRole string
}
)

Expand All @@ -44,6 +48,8 @@ func main() {
Default(defaultEventQueueSize).IntVar(&config.EventQueueSize)
kingpin.Flag("base-role-arn", "Base Role ARN. If not defined it will be autodiscovered from EC2 Metadata.").
StringVar(&config.BaseRoleARN)
kingpin.Flag("assume-role", "Assume Role can be specified to assume a role at start-up which is used for further assuming other roles managed by the controller.").
StringVar(&config.AssumeRole)
kingpin.Flag("namespace", "Limit the controller to a certain namespace.").
Default(v1.NamespaceAll).StringVar(&config.Namespace)
kingpin.Flag("apiserver", "API server url.").URLVar(&config.APIServer)
Expand All @@ -56,7 +62,7 @@ func main() {
ctx, cancel := context.WithCancel(context.Background())
kubeConfig, err := clientset.ConfigureKubeConfig(config.APIServer, defaultClientGOTimeout, ctx.Done())
if err != nil {
log.Fatalf("Failed to setup Kubernetes config: %v", err)
log.Fatalf("Failed to set up Kubernetes config: %v", err)
}

client, err := clientset.NewForConfig(kubeConfig)
Expand All @@ -66,7 +72,7 @@ func main() {

awsSess, err := session.NewSession()
if err != nil {
log.Fatalf("Failed to setup Kubernetes client: %v", err)
log.Fatalf("Failed to set up AWS session: %v", err)
}

if config.BaseRoleARN == "" {
Expand All @@ -78,7 +84,17 @@ func main() {
log.Infof("Autodiscovered Base Role ARN: %s", config.BaseRoleARN)
}

credsGetter := NewSTSCredentialsGetter(awsSess, config.BaseRoleARN)
awsConfigs := make([]*aws.Config, 0, 1)
if config.AssumeRole != "" {
if !strings.HasPrefix(config.AssumeRole, arnPrefix) {
config.AssumeRole = config.BaseRoleARN + config.AssumeRole
}
log.Infof("Using custom Assume Role: %s", config.AssumeRole)
creds := stscreds.NewCredentials(awsSess, config.AssumeRole)
awsConfigs = append(awsConfigs, &aws.Config{Credentials: creds})
}

credsGetter := NewSTSCredentialsGetter(awsSess, config.BaseRoleARN, awsConfigs...)

podsEventCh := make(chan *PodEvent, config.EventQueueSize)

Expand Down

0 comments on commit 2c39d42

Please sign in to comment.