mirror of
https://github.com/storytold/irsa-manager.git
synced 2026-10-17 00:23:52 +00:00
irsa create/update/delete role and attach policy
This commit is contained in:
@@ -25,6 +25,11 @@ type AwsClientFactory struct {
|
||||
type AwsIamAPI interface {
|
||||
CreateOpenIDConnectProvider(ctx context.Context, params *iam.CreateOpenIDConnectProviderInput, optFns ...func(*iam.Options)) (*iam.CreateOpenIDConnectProviderOutput, error)
|
||||
DeleteOpenIDConnectProvider(ctx context.Context, params *iam.DeleteOpenIDConnectProviderInput, optFns ...func(*iam.Options)) (*iam.DeleteOpenIDConnectProviderOutput, error)
|
||||
CreateRole(ctx context.Context, params *iam.CreateRoleInput, optFns ...func(*iam.Options)) (*iam.CreateRoleOutput, error)
|
||||
UpdateAssumeRolePolicy(ctx context.Context, params *iam.UpdateAssumeRolePolicyInput, optFns ...func(*iam.Options)) (*iam.UpdateAssumeRolePolicyOutput, error)
|
||||
AttachRolePolicy(ctx context.Context, params *iam.AttachRolePolicyInput, optFns ...func(*iam.Options)) (*iam.AttachRolePolicyOutput, error)
|
||||
DeleteRole(ctx context.Context, params *iam.DeleteRoleInput, optFns ...func(*iam.Options)) (*iam.DeleteRoleOutput, error)
|
||||
DetachRolePolicy(ctx context.Context, params *iam.DetachRolePolicyInput, optFns ...func(*iam.Options)) (*iam.DetachRolePolicyOutput, error)
|
||||
}
|
||||
|
||||
type AwsStsAPI interface {
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
package aws
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/iam"
|
||||
"github.com/aws/smithy-go"
|
||||
)
|
||||
|
||||
// RoleManager represents the details needed to manage IAM roles
|
||||
type RoleManager struct {
|
||||
// RoleName represents the name of the IAM role
|
||||
RoleName string
|
||||
// Namespaces represents the list of namespaces associated with the role
|
||||
Namespaces []string
|
||||
// Policies represents the list of policies to be attached to the role
|
||||
Policies []string
|
||||
}
|
||||
|
||||
func (r *RoleManager) PolicyArn(policy string) *string {
|
||||
prefix := "arn:aws:iam::"
|
||||
if strings.HasPrefix(policy, prefix) {
|
||||
return aws.String(policy)
|
||||
}
|
||||
return aws.String(fmt.Sprintf("%saws:policy/%s", prefix, policy))
|
||||
}
|
||||
|
||||
// DeleteIRSARole detaches specified policies from the IAM role and deletes the IAM role
|
||||
func (a *AwsIamClient) DeleteIRSARole(ctx context.Context, r RoleManager) error {
|
||||
for _, policy := range r.Policies {
|
||||
detachRolePolicyInput := &iam.DetachRolePolicyInput{
|
||||
RoleName: aws.String(r.RoleName),
|
||||
PolicyArn: r.PolicyArn(policy),
|
||||
}
|
||||
_, err := a.Client.DetachRolePolicy(ctx, detachRolePolicyInput)
|
||||
// Ignore error if the policy is already detached or the role does not exist
|
||||
if errorHandle(err, []string{"NoSuchEntity"}) != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("Policy %s detached from role %s successfully", policy, r.RoleName)
|
||||
|
||||
}
|
||||
input := &iam.DeleteRoleInput{RoleName: aws.String(r.RoleName)}
|
||||
_, err := a.Client.DeleteRole(ctx, input)
|
||||
// Ignore error if the role does not exist or there are other policies that this controller does not manage
|
||||
if errorHandle(err, []string{"DeleteConflict", "NoSuchEntity"}) != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("Role %s deleted successfully", r.RoleName)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateIRSARole creates an IAM role with the specified trust policy and attaches specified policies to it
|
||||
func (a *AwsIamClient) CreateIRSARole(ctx context.Context, accountId, issuerHostPath string, r RoleManager) error {
|
||||
providerArn := fmt.Sprintf("arn:aws:iam::%s:oidc-provider/%s", accountId, issuerHostPath)
|
||||
statement := make([]map[string]interface{}, len(r.Namespaces))
|
||||
for i, ns := range r.Namespaces {
|
||||
statement[i] = map[string]interface{}{
|
||||
"Effect": "Allow",
|
||||
"Principal": map[string]interface{}{
|
||||
"Federated": providerArn,
|
||||
},
|
||||
"Action": "sts:AssumeRoleWithWebIdentity",
|
||||
"Condition": map[string]interface{}{
|
||||
"StringEquals": map[string]interface{}{
|
||||
fmt.Sprintf("%s:sub", issuerHostPath): fmt.Sprintf("system:serviceaccount:%s:%s", ns, r.RoleName),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
trustPolicy := map[string]interface{}{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": statement,
|
||||
}
|
||||
trustPolicyJSON, err := json.Marshal(trustPolicy)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal trust policy: %w", err)
|
||||
}
|
||||
createRoleInput := &iam.CreateRoleInput{
|
||||
RoleName: aws.String(r.RoleName),
|
||||
AssumeRolePolicyDocument: aws.String(string(trustPolicyJSON)),
|
||||
}
|
||||
|
||||
_, err = a.Client.CreateRole(context.TODO(), createRoleInput)
|
||||
if errorHandle(err, []string{"EntityAlreadyExists"}) != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("Role %s created successfully", r.RoleName)
|
||||
|
||||
updateRoleInput := &iam.UpdateAssumeRolePolicyInput{
|
||||
RoleName: aws.String(r.RoleName),
|
||||
PolicyDocument: aws.String(string(trustPolicyJSON)),
|
||||
}
|
||||
|
||||
_, err = a.Client.UpdateAssumeRolePolicy(context.TODO(), updateRoleInput)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update assume role policy for role %s: %w", r.RoleName, err)
|
||||
}
|
||||
log.Printf("Assume role policy for %s updated successfully", r.RoleName)
|
||||
|
||||
for _, policy := range r.Policies {
|
||||
attachRolePolicyInput := &iam.AttachRolePolicyInput{
|
||||
RoleName: aws.String(r.RoleName),
|
||||
PolicyArn: r.PolicyArn(policy),
|
||||
}
|
||||
|
||||
_, err = a.Client.AttachRolePolicy(context.TODO(), attachRolePolicyInput)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("Policy %s attached to role %s successfully", policy, r.RoleName)
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// errorHandle handles specific errors by checking the error code against a list of codes to ignore
|
||||
func errorHandle(err error, errorCodes []string) error {
|
||||
if err != nil {
|
||||
var ae smithy.APIError
|
||||
if errors.As(err, &ae) && slices.Contains(errorCodes, ae.ErrorCode()) {
|
||||
fmt.Printf("Skipped error: %s \n", err.Error())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user