Skip to content

Instantly share code, notes, and snippets.

@prabhakhar
Created November 9, 2025 20:45
Show Gist options
  • Select an option

  • Save prabhakhar/de062ac88c16ea0e69c7a52999197d9d to your computer and use it in GitHub Desktop.

Select an option

Save prabhakhar/de062ac88c16ea0e69c7a52999197d9d to your computer and use it in GitHub Desktop.
package main
import (
"context"
"encoding/json"
"fmt"
"strings"
ackv1alpha1 "github.com/aws-controllers-k8s/iam-controller/apis/v1alpha1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
)
// ==================================================================
// 1. NEW CUSTOM RESOURCE DEFINITIONS (The Inputs)
// ==================================================================
// In a real project, these would be in api/v1alpha1/xxx_types.go
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
type EKSWorkloadIAM struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
// This spec matches ActorConfig from the previous script
Spec ActorConfig `json:"spec,omitempty"`
}
// +kubebuilder:object:root=true
type EKSWorkloadIAMList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []EKSWorkloadIAM `json:"items"`
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
type AuroraAccessIAM struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
// This spec matches TargetConfig from the previous script
Spec TargetConfig `json:"spec,omitempty"`
}
// +kubebuilder:object:root=true
type AuroraAccessIAMList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []AuroraAccessIAM `json:"items"`
}
// Re-using the config structs as Kubernetes Specs
type ActorConfig struct {
// AccountID is the AWS Account ID where this role should live
AccountID string `json:"accountId"`
// RoleName is the desired IAM Role name
RoleName string `json:"roleName"`
// --- Trust Method Selection ---
// Option A: EKS Pod Identity (PIA)
// e.g., "pods.eks.amazonaws.com"
TrustPrincipal string `json:"trustPrincipal,omitempty"`
// Option B: IRSA (OIDC)
// OIDCProvider is the OIDC issuer URL without https://
// e.g., "oidc.eks.us-west-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE"
OIDCProvider string `json:"oidcProvider,omitempty"`
// ServiceAccountName for IRSA condition
ServiceAccountName string `json:"serviceAccountName,omitempty"`
// ServiceAccountNamespace for IRSA condition
ServiceAccountNamespace string `json:"serviceAccountNamespace,omitempty"`
// ------------------------------
// TargetRoleARNs is a list of DB roles this pod can assume
TargetRoleARNs []string `json:"targetRoleArns,omitempty"`
}
type TargetConfig struct {
AccountID string `json:"accountId"`
RoleName string `json:"roleName"`
Region string `json:"region"`
DBClusterID string `json:"dbClusterId"`
DBUser string `json:"dbUser"`
TrustedRoleARNs []string `json:"trustedRoleArns,omitempty"`
}
// ==================================================================
// 2. RECONCILERS (The Controller Logic)
// ==================================================================
// EKSWorkloadIAMReconciler watches EKSWorkloadIAM inputs and creates ACK Roles
type EKSWorkloadIAMReconciler struct {
client.Client
Scheme *runtime.Scheme
}
func (r *EKSWorkloadIAMReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
l := log.FromContext(ctx)
// 1. Fetch Input (EKSWorkloadIAM)
var input EKSWorkloadIAM
if err := r.Get(ctx, req.NamespacedName, &input); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// 2. Generate the desired ACK Role
desiredACKRole, err := r.generateACKRole(&input)
if err != nil {
l.Error(err, "failed to generate ACK role definition")
// Don't requeue if it's a configuration error in the CR
if strings.Contains(err.Error(), "invalid configuration") {
return ctrl.Result{}, nil
}
return ctrl.Result{}, err // Retry for other errors
}
// 3. Create or Update the ACK Role on the cluster
var currentACKRole ackv1alpha1.Role
err = r.Get(ctx, types.NamespacedName{Name: desiredACKRole.Name, Namespace: desiredACKRole.Namespace}, &currentACKRole)
if errors.IsNotFound(err) {
l.Info("Creating new ACK Role", "role", desiredACKRole.Name)
if err := r.Create(ctx, desiredACKRole); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
} else if err != nil {
return ctrl.Result{}, err
}
// Simple update logic: overwrite spec if it exists
// (In prod, you might compare specs to avoid unnecessary API calls)
currentACKRole.Spec = desiredACKRole.Spec
l.Info("Updating existing ACK Role", "role", currentACKRole.Name)
if err := r.Update(ctx, &currentACKRole); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
// generateACKRole adapts the old 'GenerateActorRole' function for K8s
func (r *EKSWorkloadIAMReconciler) generateACKRole(input *EKSWorkloadIAM) (*ackv1alpha1.Role, error) {
var trustPolicy IAMPolicy
// --- Trust Policy Generation Logic ---
if input.Spec.OIDCProvider != "" {
// === OPTION B: IRSA ===
if input.Spec.ServiceAccountName == "" || input.Spec.ServiceAccountNamespace == "" {
return nil, fmt.Errorf("invalid configuration: OIDCProvider requires ServiceAccountName and ServiceAccountNamespace")
}
oidcARN := fmt.Sprintf("arn:aws:iam::%s:oidc-provider/%s", input.Spec.AccountID, input.Spec.OIDCProvider)
subCondition := fmt.Sprintf("system:serviceaccount:%s:%s", input.Spec.ServiceAccountNamespace, input.Spec.ServiceAccountName)
trustPolicy = IAMPolicy{
Version: "2012-10-17",
Statement: []IAMStatement{{
Sid: "TrustIRSA",
Effect: "Allow",
Principal: map[string]string{
"Federated": oidcARN,
},
Action: "sts:AssumeRoleWithWebIdentity",
Condition: map[string]interface{}{
"StringEquals": map[string]string{
// "oidc.eks.REGION.amazonaws.com/id/XXX:sub": "system:serviceaccount:ns:sa"
fmt.Sprintf("%s:sub", input.Spec.OIDCProvider): subCondition,
// "oidc.eks.REGION.amazonaws.com/id/XXX:aud": "sts.amazonaws.com"
fmt.Sprintf("%s:aud", input.Spec.OIDCProvider): "sts.amazonaws.com",
},
},
}},
}
} else if input.Spec.TrustPrincipal != "" {
// === OPTION A: EKS Pod Identity ===
trustPolicy = IAMPolicy{
Version: "2012-10-17",
Statement: []IAMStatement{{
Sid: "TrustEKSPodIdentity",
Effect: "Allow",
Principal: map[string]string{
"Service": input.Spec.TrustPrincipal,
},
Action: []string{"sts:AssumeRole", "sts:TagSession"},
}},
}
} else {
return nil, fmt.Errorf("invalid configuration: must specify either trustPrincipal (PIA) or oidcProvider (IRSA)")
}
trustJSON, _ := json.Marshal(trustPolicy)
// --- Permissions Policy Generation Logic ---
permPolicy := IAMPolicy{
Version: "2012-10-17",
Statement: []IAMStatement{{
Sid: "AllowCrossAccountAssume",
Effect: "Allow",
Action: "sts:AssumeRole",
Resource: input.Spec.TargetRoleARNs,
}},
}
permJSON, _ := json.Marshal(permPolicy)
// ------------------------------------------------
ackRole := &ackv1alpha1.Role{
ObjectMeta: metav1.ObjectMeta{
Name: "ack-" + strings.ToLower(input.Spec.RoleName),
Namespace: input.Namespace,
},
Spec: ackv1alpha1.RoleSpec{
Name: &input.Spec.RoleName,
AssumeRolePolicyDocument: string(trustJSON),
InlinePolicies: map[string]*string{
"CrossAccountHops": awsString(string(permJSON)),
},
},
}
// CRITICAL: Set OwnerReference so deleting the Input deletes the ACK Role
if err := ctrl.SetControllerReference(input, ackRole, r.Scheme); err != nil {
return nil, err
}
return ackRole, nil
}
// AuroraAccessIAMReconciler watches AuroraAccessIAM inputs and creates ACK Roles
type AuroraAccessIAMReconciler struct {
client.Client
Scheme *runtime.Scheme
}
func (r *AuroraAccessIAMReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
l := log.FromContext(ctx)
var input AuroraAccessIAM
if err := r.Get(ctx, req.NamespacedName, &input); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
desiredACKRole, err := r.generateACKRole(&input)
if err != nil {
l.Error(err, "failed to generate ACK role definition")
return ctrl.Result{}, err
}
var currentACKRole ackv1alpha1.Role
err = r.Get(ctx, types.NamespacedName{Name: desiredACKRole.Name, Namespace: desiredACKRole.Namespace}, &currentACKRole)
if errors.IsNotFound(err) {
l.Info("Creating new Aurora Target ACK Role", "role", desiredACKRole.Name)
if err := r.Create(ctx, desiredACKRole); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
} else if err != nil {
return ctrl.Result{}, err
}
currentACKRole.Spec = desiredACKRole.Spec
l.Info("Updating existing Aurora Target ACK Role", "role", currentACKRole.Name)
if err := r.Update(ctx, &currentACKRole); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
func (r *AuroraAccessIAMReconciler) generateACKRole(input *AuroraAccessIAM) (*ackv1alpha1.Role, error) {
// --- Re-using IAM generation logic ---
trustPolicy := IAMPolicy{
Version: "2012-10-17",
Statement: []IAMStatement{{
Sid: "TrustExternalPods",
Effect: "Allow",
Principal: map[string][]string{"AWS": input.Spec.TrustedRoleARNs},
Action: "sts:AssumeRole",
}},
}
trustJSON, _ := json.Marshal(trustPolicy)
dbResourceARN := fmt.Sprintf("arn:aws:rds-db:%s:%s:dbuser:%s/%s",
input.Spec.Region, input.Spec.AccountID, input.Spec.DBClusterID, input.Spec.DBUser)
permPolicy := IAMPolicy{
Version: "2012-10-17",
Statement: []IAMStatement{{
Sid: "AllowDBConnect",
Effect: "Allow",
Action: "rds-db:connect",
Resource: dbResourceARN,
}},
}
permJSON, _ := json.Marshal(permPolicy)
// ------------------------------------
ackRole := &ackv1alpha1.Role{
ObjectMeta: metav1.ObjectMeta{
Name: "ack-" + strings.ToLower(input.Spec.RoleName),
Namespace: input.Namespace,
},
Spec: ackv1alpha1.RoleSpec{
Name: &input.Spec.RoleName,
AssumeRolePolicyDocument: string(trustJSON),
InlinePolicies: map[string]*string{
"DBConnectPermissions": awsString(string(permJSON)),
},
},
}
if err := ctrl.SetControllerReference(input, ackRole, r.Scheme); err != nil {
return nil, err
}
return ackRole, nil
}
// ==================================================================
// 3. HELPER STRUCTS (from original script)
// ==================================================================
type IAMPolicy struct {
Version string `json:"Version"`
Statement []IAMStatement `json:"Statement"`
}
type IAMStatement struct {
Sid string `json:"Sid,omitempty"`
Effect string `json:"Effect"`
Principal interface{} `json:"Principal,omitempty"`
Action interface{} `json:"Action"`
Resource interface{} `json:"Resource,omitempty"`
Condition map[string]interface{} `json:"Condition,omitempty"`
}
func awsString(v string) *string { return &v }
// ==================================================================
// 4. MAIN (Setup)
// ==================================================================
// In a real operator, this would be in main.go
func Setup(mgr ctrl.Manager) error {
// Register our 2 new controllers with the manager
if err := (&EKSWorkloadIAMReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
return err
}
if err := (&AuroraAccessIAMReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
return err
}
return nil
}
// Boilerplate to attach reconciler to manager
func (r *EKSWorkloadIAMReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&EKSWorkloadIAM{}).
Owns(&ackv1alpha1.Role{}). // Watch ACK roles we own
Complete(r)
}
func (r *AuroraAccessIAMReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&AuroraAccessIAM{}).
Owns(&ackv1alpha1.Role{}).
Complete(r)
}
@prabhakhar
Copy link
Author

// In your AccessSubscription controller's Reconcile loop...

func (r *AccessSubscriptionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    // 1. Fetch AccessSubscription
    var sub AccessSubscription
    if err := r.Get(ctx, req.NamespacedName, &sub); err != nil { return ctrl.Result{}, client.IgnoreNotFound(err) }

    // 2. CALCULATE CONFIG (Mocked here - this comes from your ApplicationInstance CRD)
    // Assume you found the app runs in 2 accounts.
    actorConfigs := []iamgen.ActorConfig{
        {
            Name: sub.Name + "-actor-acct1", Namespace: sub.Namespace,
            AccountID: "111111111111", RoleName: "my-app-role",
            Type: iamgen.ActorTypeIRSA,
            IRSA: &iamgen.IRSAConfig{OIDCProvider: "oidc...", ServiceAccount: "sa", ServiceNamespace: "ns"},
             // We know the target ARN ahead of time deterministically
            TargetRoleARNs: []string{fmt.Sprintf("arn:aws:iam::%s:role/%s", "333333333333", "target-db-role")},
        },
        {
             Name: sub.Name + "-actor-acct2", Namespace: sub.Namespace,
             AccountID: "222222222222", RoleName: "my-app-role",
             // ... similar config for second account ...
             TargetRoleARNs: []string{fmt.Sprintf("arn:aws:iam::%s:role/%s", "333333333333", "target-db-role")},
        },
    }

    targetConfig := iamgen.TargetConfig{
        Name: sub.Name + "-target", Namespace: sub.Namespace,
        AccountID: "333333333333", RoleName: "target-db-role",
        Type: iamgen.TargetTypeDatabase,
        Database: &iamgen.DatabaseConfig{Region: "us-west-2", DBClusterID: "mydb", DBUser: "appuser"},
        // Trust the known ARNs of the actors
        TrustedRoleARNs: []string{
             "arn:aws:iam::111111111111:role/my-app-role",
             "arn:aws:iam::222222222222:role/my-app-role",
        },
    }

    // 3. RECONCILE ACTORS
    allActorsSynced := true
    for _, cfg := range actorConfigs {
        desiredRole, _ := iamgen.GenerateActorRole(cfg)
        // Set owner ref so deleting Subscription deletes Roles
        ctrl.SetControllerReference(&sub, desiredRole, r.Scheme)
        
        synced, err := r.applyAndCheckStatus(ctx, desiredRole)
        if err != nil { return ctrl.Result{}, err }
        if !synced { allActorsSynced = false }
    }

    // 4. RECONCILE TARGET
    desiredTarget, _ := iamgen.GenerateTargetRole(targetConfig)
    ctrl.SetControllerReference(&sub, desiredTarget, r.Scheme)
    targetSynced, err := r.applyAndCheckStatus(ctx, desiredTarget)
    if err != nil { return ctrl.Result{}, err }

    // 5. AGGREGATE STATUS
    newStatus := AccessSubscriptionStatus{
        IAMReady: allActorsSynced && targetSynced,
        // ... other status fields ...
    }
    
    // Update status if changed
    if sub.Status.IAMReady != newStatus.IAMReady {
        sub.Status = newStatus
        r.Status().Update(ctx, &sub)
    }

    return ctrl.Result{}, nil
}

// Helper to Apply ACK Role and check its 'ResourceSynced' condition
func (r *AccessSubscriptionReconciler) applyAndCheckStatus(ctx context.Context, desired *ackv1alpha1.Role) (bool, error) {
    var current ackv1alpha1.Role
    err := r.Get(ctx, types.NamespacedName{Name: desired.Name, Namespace: desired.Namespace}, &current)
    
    if errors.IsNotFound(err) {
        if err := r.Create(ctx, desired); err != nil { return false, err }
        return false, nil // Created, not yet synced
    } else if err != nil { return false, err }

    // Update if spec changed
    // Note: In prod, do a deep comparison of Spec before updating to avoid noise
    current.Spec = desired.Spec
    if err := r.Update(ctx, &current); err != nil { return false, err }

    // CHECK ACK STATUS
    for _, cond := range current.Status.Conditions {
        // ACK standard condition for "AWS resource is created and matches spec"
        if cond.Type == ackv1alpha1.ConditionTypeResourceSynced && cond.Status == corev1.ConditionTrue {
            return true, nil
        }
    }
    return false, nil
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment