Skip to content

Commit

Permalink
add role assumption option (#263)
Browse files Browse the repository at this point in the history
* add role assumption option

* use new creds

* name session

* readme
  • Loading branch information
anvddriesch authored Oct 16, 2024
1 parent 2f807ca commit d2fb96b
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 7 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Add optional `--role-arn` flag to specify the role ARN to assume when interacting with Route53.

## [0.9.2] - 2024-08-26

### Added
Expand Down
24 changes: 22 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,26 @@
# dns-operator-route53

`dns-operator-route53` is a controller, which runs once per management Cluster. It's responsible for creating the following DNS Records per `cluster`:
`dns-operator-route53` is a controller, which runs once per management Cluster and watches for `Cluster` resources. It creates DNS records in Route53 for each `Cluster` in the management Cluster.

## credentials

`dns-operator-route53` has to communicate with AWS Route53 API.
Therefore it needs to be configured with a set of AWS IAM credentials.

```yaml
aws:
accessKeyID: accesskey # AWS Access Key ID for the IAM user
secretAccessKey: secretkey # AWS Secret Access Key for the IAM user
roleARN: "" # AWS Role ARN for the IAM role to assume - optional but recommended
```
The `roleARN` is optional but recommended.
If it is set, the operator will assume the role before interacting with Route53 using a set of temporary credentials.
In this case, the IAM user is only used to assume the role and does not need to have any permissions to interact with Route53.

## dns records

`dns-operator-route53` is responsible for creating the following DNS Records per `cluster`:

* `A`: `api.<clustername>.test.gigantic.io` (points to the kubernetes API IP of a `cluster`)
* `A`: `bastion1.<clustername>.test.gigantic.io` (points to the bastion Host IP of a `cluster` - only on `OpenStack` yet)
Expand Down Expand Up @@ -121,4 +141,4 @@ By using the `aws` cli it's sometimes helpful to get the current created Route53
}
]
}
```
```
2 changes: 2 additions & 0 deletions controllers/cluster_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ type ClusterReconciler struct {

BaseDomain string
ManagementCluster string
RoleArn string
StaticBastionIP string
}

Expand Down Expand Up @@ -92,6 +93,7 @@ func (r *ClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
Cluster: cluster,
InfrastructureCluster: infraCluster,
ManagementCluster: r.ManagementCluster,
RoleArn: r.RoleArn,
StaticBastionIP: r.StaticBastionIP,
})
if err != nil {
Expand Down
3 changes: 3 additions & 0 deletions helm/dns-operator-route53/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ spec:
- --enable-leader-election
- --base-domain={{ .Values.baseDomain }}
- --management-cluster={{ .Values.managementCluster }}
{{ if .Values.aws.roleARN -}}
- --role-arn={{ .Values.aws.roleARN }}
{{- end }}
{{ if .Values.staticBastionIP -}}
- --static-bastion-ip={{ .Values.staticBastionIP }}
{{- end }}
Expand Down
1 change: 1 addition & 0 deletions helm/dns-operator-route53/values.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
aws:
accessKeyID: accesskey
secretAccessKey: secretkey
roleARN: ""

project:
branch: "[[ .Branch ]]"
Expand Down
3 changes: 3 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ func main() {
enableLeaderElection bool
managementCluster string
metricsAddr string
roleArn string
staticBastionIP string
)

Expand All @@ -62,6 +63,7 @@ func main() {

flag.StringVar(&baseDomain, "base-domain", "", "Domain for which to create the DNS entries, e.g. customer.gigantic.io.")
flag.StringVar(&managementCluster, "management-cluster", "", "Name of the management cluster.")
flag.StringVar(&roleArn, "role-arn", "", "ARN of the role to assume for the AWS API calls.")
flag.StringVar(&staticBastionIP, "static-bastion-ip", "", "IP address of static bastion machine for all clusters.")

flag.Parse()
Expand Down Expand Up @@ -93,6 +95,7 @@ func main() {
Client: mgr.GetClient(),
BaseDomain: baseDomain,
ManagementCluster: managementCluster,
RoleArn: roleArn,
StaticBastionIP: staticBastionIP,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Cluster")
Expand Down
4 changes: 1 addition & 3 deletions pkg/cloud/scope/clients.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package scope

import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/service/route53"
"k8s.io/apimachinery/pkg/runtime"
Expand All @@ -21,7 +19,7 @@ type AWSClients struct {

// NewRoute53Client creates a new Route53 API client for a given session
func NewRoute53Client(session cloud.Session, target runtime.Object) *route53.Route53 {
Route53Client := route53.New(session.Session(), &aws.Config{Credentials: credentials.NewSharedCredentials("", "")})
Route53Client := route53.New(session.Session(), nil)
Route53Client.Handlers.Build.PushFrontNamed(getUserAgentHandler())
Route53Client.Handlers.CompleteAttempt.PushFront(awsmetrics.CaptureRequestMetrics("dns-operator-route53"))
Route53Client.Handlers.Complete.PushBack(recordAWSPermissionsIssue(target))
Expand Down
35 changes: 33 additions & 2 deletions pkg/cloud/scope/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ import (
"context"
"fmt"

"github.com/aws/aws-sdk-go/aws"
awsclient "github.com/aws/aws-sdk-go/aws/client"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/sts"
corev1 "k8s.io/api/core/v1"
"k8s.io/client-go/rest"
capi "sigs.k8s.io/cluster-api/api/v1beta1"
Expand All @@ -31,6 +34,7 @@ type ClusterScopeParams struct {
Cluster *capi.Cluster
InfrastructureCluster *unstructured.Unstructured
ManagementCluster string
RoleArn string
StaticBastionIP string
}

Expand All @@ -50,9 +54,36 @@ func NewClusterScope(ctx context.Context, params ClusterScopeParams) (*ClusterSc
return nil, microerror.Mask(err)
}

return &ClusterScope{
session: awsSession,
if params.RoleArn != "" {
// Assume Role
stsSvc := sts.New(awsSession)

assumeRoleOutput, err := stsSvc.AssumeRole(&sts.AssumeRoleInput{
RoleArn: aws.String(params.RoleArn),
RoleSessionName: aws.String(fmt.Sprintf("dns-operator-route53-%s-%s", params.ManagementCluster, params.Cluster.GetName())),
})
if err != nil {
return nil, microerror.Mask(err)
}

// Use the temporary credentials from the AssumeRole response
creds := credentials.NewStaticCredentials(
*assumeRoleOutput.Credentials.AccessKeyId,
*assumeRoleOutput.Credentials.SecretAccessKey,
*assumeRoleOutput.Credentials.SessionToken,
)

// Create a new session with the assumed role credentials
awsSession, err = session.NewSession(&aws.Config{
Credentials: creds,
})
if err != nil {
return nil, microerror.Mask(err)
}
}

return &ClusterScope{
session: awsSession,
baseDomain: params.BaseDomain,
cluster: params.Cluster,
infraCluster: params.InfrastructureCluster,
Expand Down

0 comments on commit d2fb96b

Please sign in to comment.