Skip to content

Commit

Permalink
Merge pull request #32
Browse files Browse the repository at this point in the history
* Add support for Externally Managed Policies
  • Loading branch information
Moulick authored Sep 12, 2023
1 parent c36129b commit 6390f37
Show file tree
Hide file tree
Showing 13 changed files with 162 additions and 99 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ dist/
*.so
*.dylib
bin
aws-iam-operator

# Test binary, build with `go test -c`
*.test
Expand All @@ -24,4 +25,4 @@ bin
*.swo
*~

**/.DS_Store
**/.DS_Store
1 change: 1 addition & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ builds:
- windows
- darwin
ldflags:
- -s -w # strip symbols, reduces binary size
- -X main.operatorversion={{.Version}}
- -X main.operatorbuilddate={{.Date}}
archives:
Expand Down
2 changes: 1 addition & 1 deletion api/v1beta1/group_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ type GroupStatus struct {
// +kubebuilder:printcolumn:name="Message",type=string,JSONPath=`.status.message`
// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.state`
// +kubebuilder:printcolumn:name="Last Sync",type=string,JSONPath=`.status.lastSyncAttempt`
//

// Group is the Schema for the roles API
type Group struct {
metav1.TypeMeta `json:",inline"`
Expand Down
24 changes: 20 additions & 4 deletions api/v1beta1/policyattachment_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// ResourceReference refrences the Policy resource to attach to another resource
// +kubebuilder:validation:Optional
// +optional
type ResourceReference struct {

// +kubebuilder:validation:Required
Expand All @@ -29,6 +32,15 @@ type ResourceReference struct {
Namespace string `json:"namespace,omitempty"`
}

// ExternalResource is a reference to a policy ARN that is not created by the controller
// +kubebuilder:validation:Optional
// +optional
type ExternalResource struct {

// +kubebuilder:validation:Required
ARN string `json:"arn,omitempty"`
}

type TargetType string

const (
Expand All @@ -54,14 +66,18 @@ type TargetReference struct {
// PolicyAttachmentSpec defines the desired state of PolicyAttachment
type PolicyAttachmentSpec struct {

// +kubebuilder:validation:Required
//
// PolicyReference refrences the Policy resource to attach to another resource
// +kubebuilder:validation:Optional
// +optional
PolicyReference ResourceReference `json:"policy,omitempty"`

// +kubebuilder:validation:Required
//
// ExternalPolicy is a reference to a resource that is not created by the controller
// +kubebuilder:validation:Optional
// +optional
ExternalPolicy ExternalResource `json:"externalPolicy,omitempty"`

// Attachments holds all defined attachments
// +kubebuilder:validation:Required
TargetReference TargetReference `json:"target,omitempty"`
}

Expand Down
16 changes: 16 additions & 0 deletions api/v1beta1/zz_generated.deepcopy.go

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

7 changes: 7 additions & 0 deletions config/crd/bases/aws-iam.redradrat.xyz_policyattachments.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ spec:
spec:
description: PolicyAttachmentSpec defines the desired state of PolicyAttachment
properties:
externalPolicy:
description: ExternalPolicy is a reference to a resource that is not
created by the controller
properties:
arn:
type: string
type: object
policy:
description: PolicyReference refrences the Policy resource to attach
to another resource
Expand Down
4 changes: 0 additions & 4 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,8 @@ rules:
resources:
- policyattachments
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- aws-iam.redradrat.xyz
Expand Down
21 changes: 11 additions & 10 deletions controllers/group_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package controllers
import (
"context"
"fmt"

"github.com/go-logr/logr"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
Expand Down Expand Up @@ -59,7 +60,7 @@ func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
// Get our actual IAM Service to communicate with AWS; we don't need to continue without it
iamsvc, err := IAMService(r.Region)
if err != nil {
return ctrl.Result{}, errWithStatus(&group, err, r.Status(), ctx)
return ctrl.Result{}, errWithStatus(ctx, &group, err, r.Status())
}

// new group instance
Expand All @@ -68,7 +69,7 @@ func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
if group.Status.ARN != "" {
parsedArn, err := aws.ARNify(group.Status.ARN)
if err != nil {
return ctrl.Result{}, errWithStatus(&group, fmt.Errorf("ARN in Group status is not valid/parsable"), r.Status(), ctx)
return ctrl.Result{}, errWithStatus(ctx, &group, fmt.Errorf("ARN in Group status is not valid/parsable"), r.Status())
}
ins = iam.NewExistingGroupInstance(groupName, parsedArn[len(parsedArn)-1])
} else {
Expand Down Expand Up @@ -104,7 +105,7 @@ func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
// delete the actual AWS Object and pass the cleanup function
statusUpdater, err := DeleteAWSObject(iamsvc, ins, cleanupFunc)
// we got a StatusUpdater function returned... let's execute it
statusUpdater(ins, &group, ctx, r.Status(), log)
statusUpdater(ctx, ins, &group, r.Status(), log)
if err != nil {
// we had an error during AWS Object deletion... so we return here to retry
log.Error(err, "unable to delete Group")
Expand All @@ -130,7 +131,7 @@ func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
if group.Status.ARN != "" {
// Delete the actual AWS Object and pass the cleanup function
statusWriter, err := DeleteAWSObject(iamsvc, ins, cleanupFunc)
statusWriter(ins, &group, ctx, r.Status(), log)
statusWriter(ctx, ins, &group, r.Status(), log)
if err != nil {
// we had an error during AWS Object deletion... so we return here to retry
log.Error(err, "error while deleting Group during reconciliation")
Expand All @@ -139,7 +140,7 @@ func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
}

statusWriter, err := CreateAWSObject(iamsvc, ins, DoNothingPreFunc)
statusWriter(ins, &group, ctx, r.Status(), log)
statusWriter(ctx, ins, &group, r.Status(), log)
if err != nil {
log.Error(err, "error while creating Group during reconciliation")
return ctrl.Result{}, err
Expand All @@ -151,23 +152,23 @@ func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
userObj := iamv1beta1.User{}
r.Client.Get(ctx, client.ObjectKey{Name: user.Name, Namespace: user.Namespace}, &userObj)
if err != nil {
return ctrl.Result{}, errWithStatus(&group, err, r.Status(), ctx)
return ctrl.Result{}, errWithStatus(ctx, &group, err, r.Status())
}

// Err if ARN is not available in the user obj
if userObj.Status.ARN == "" {
return ctrl.Result{}, errWithStatus(&group, fmt.Errorf("referenced user resource '%s/%s' has not yet been created", user.Namespace, user.Name), r.Status(), ctx)
return ctrl.Result{}, errWithStatus(ctx, &group, fmt.Errorf("referenced user resource '%s/%s' has not yet been created", user.Namespace, user.Name), r.Status())
}

// parse the user arn
parsedArn, err := aws.ARNify(userObj.Status.ARN)
if err != nil {
return ctrl.Result{}, errWithStatus(&group, fmt.Errorf("ARN in referenced User status is not valid/parsable"), r.Status(), ctx)
return ctrl.Result{}, errWithStatus(ctx, &group, fmt.Errorf("ARN in referenced User status is not valid/parsable"), r.Status())
}

// Now add the user to our Group Instance
if err = ins.AddUser(iamsvc, parsedArn[len(parsedArn)-1]); err != nil {
return ctrl.Result{}, errWithStatus(&group, err, r.Status(), ctx)
return ctrl.Result{}, errWithStatus(ctx, &group, err, r.Status())
}
}

Expand Down Expand Up @@ -195,7 +196,7 @@ func groupCleanup(r *GroupReconciler, ctx context.Context, group iamv1beta1.Grou
for _, att := range attachments.Items {
if att.Spec.TargetReference.Type == iamv1beta1.GroupTargetType {
if att.Spec.TargetReference.Name == group.Name && att.Spec.TargetReference.Namespace == group.Namespace {
err := fmt.Errorf(fmt.Sprintf("cannot delete Group due to existing PolicyAttachment '%s/%s'", att.Name, att.Namespace))
err := fmt.Errorf("cannot delete Group due to existing PolicyAttachment '%s/%s'", att.Name, att.Namespace)
return err
}
}
Expand Down
10 changes: 5 additions & 5 deletions controllers/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func ignoreDoesNotExistError(err error) error {

func DoNothingPreFunc() error { return nil }

func errWithStatus(obj AWSObjectStatusResource, err error, sw client.StatusWriter, ctx context.Context) error {
func errWithStatus(ctx context.Context, obj AWSObjectStatusResource, err error, sw client.StatusWriter) error {
origerr := err
obj.GetStatus().Message = origerr.Error()
obj.GetStatus().State = iamv1beta1.ErrorSyncState
Expand All @@ -115,10 +115,10 @@ func IAMService(region string) (*awsiam.IAM, error) {
return iam.Client(session), nil
}

type StatusUpdater func(ins aws.Instance, obj AWSObjectStatusResource, ctx context.Context, sw client.StatusWriter, log logr.Logger)
type StatusUpdater func(ctx context.Context, ins aws.Instance, obj AWSObjectStatusResource, sw client.StatusWriter, log logr.Logger)

func SuccessStatusUpdater() StatusUpdater {
return func(ins aws.Instance, obj AWSObjectStatusResource, ctx context.Context, sw client.StatusWriter, log logr.Logger) {
return func(ctx context.Context, ins aws.Instance, obj AWSObjectStatusResource, sw client.StatusWriter, log logr.Logger) {
obj.GetStatus().ARN = ins.ARN().String()
obj.GetStatus().Message = "Succesfully reconciled"
obj.GetStatus().State = iamv1beta1.OkSyncState
Expand All @@ -132,7 +132,7 @@ func SuccessStatusUpdater() StatusUpdater {
}

func ErrorStatusUpdater(reason string) StatusUpdater {
return func(ins aws.Instance, obj AWSObjectStatusResource, ctx context.Context, sw client.StatusWriter, log logr.Logger) {
return func(ctx context.Context, ins aws.Instance, obj AWSObjectStatusResource, sw client.StatusWriter, log logr.Logger) {
obj.GetStatus().Message = reason
obj.GetStatus().State = iamv1beta1.ErrorSyncState
obj.GetStatus().LastSyncAttempt = time.Now().Format(time.RFC822Z)
Expand All @@ -144,5 +144,5 @@ func ErrorStatusUpdater(reason string) StatusUpdater {
}
}

func DoNothingStatusUpdater(ins aws.Instance, obj AWSObjectStatusResource, ctx context.Context, sw client.StatusWriter, log logr.Logger) {
func DoNothingStatusUpdater(ctx context.Context, ins aws.Instance, obj AWSObjectStatusResource, sw client.StatusWriter, log logr.Logger) {
}
10 changes: 5 additions & 5 deletions controllers/policy_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func (r *PolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
policy.ObjectMeta.Finalizers = append(policy.ObjectMeta.Finalizers, policiesFinalizer)
if err := r.Update(context.Background(), &policy); err != nil {
log.Error(err, "unable to register finalizer for Policy")
return ctrl.Result{}, errWithStatus(&policy, err, r.Status(), ctx)
return ctrl.Result{}, errWithStatus(ctx, &policy, err, r.Status())
}
}
} else {
Expand All @@ -105,7 +105,7 @@ func (r *PolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr

// delete the actual AWS Object and pass the cleanup function
statusWriter, err := DeleteAWSObject(iamsvc, ins, cleanupFunc)
statusWriter(ins, &policy, ctx, r.Status(), log)
statusWriter(ctx, ins, &policy, r.Status(), log)
if err != nil {
// we had an error during AWS Object deletion... so we return here to retry
log.Error(err, "unable to delete Policy")
Expand All @@ -128,14 +128,14 @@ func (r *PolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr

// if there is already an ARN in our status, then we update the object
statusWriter, err := CreateAWSObject(iamsvc, ins, DoNothingPreFunc)
statusWriter(ins, &policy, ctx, r.Status(), log)
statusWriter(ctx, ins, &policy, r.Status(), log)
if err != nil {
// If already exists, we just update the status
if aerr, ok := err.(awserr.Error); ok {
if aerr.Code() == awsiam.ErrCodeEntityAlreadyExistsException {
// Update the actual AWS Object and pass the DoNothing function
statusWriter, err := UpdateAWSObject(iamsvc, ins, DoNothingPreFunc)
statusWriter(ins, &policy, ctx, r.Status(), log)
statusWriter(ctx, ins, &policy, r.Status(), log)
if err != nil {
// we had an error during AWS Object update... so we return here to retry
log.Error(err, "error while updating Policy during reconciliation")
Expand Down Expand Up @@ -172,7 +172,7 @@ func policyCleanup(r *PolicyReconciler, ctx context.Context, policy *iamv1beta1.
}
for _, att := range attachments.Items {
if att.Spec.PolicyReference.Name == policy.Name && att.Spec.PolicyReference.Namespace == policy.Namespace {
err := fmt.Errorf(fmt.Sprintf("cannot delete policy due to existing PolicyAttachment '%s/%s'", att.Name, att.Namespace))
err := fmt.Errorf("cannot delete policy due to existing PolicyAttachment '%s/%s'", att.Name, att.Namespace)
return err
}
}
Expand Down
Loading

0 comments on commit 6390f37

Please sign in to comment.