|
| 1 | +package lb |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + "fmt" |
| 6 | + "strconv" |
| 7 | + |
| 8 | + "github.com/aws/aws-sdk-go/aws" |
| 9 | + "github.com/aws/aws-sdk-go/service/elbv2" |
| 10 | + "github.com/aws/aws-sdk-go/service/elbv2/elbv2iface" |
| 11 | + "github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/albctx" |
| 12 | + "github.com/kubernetes-sigs/aws-alb-ingress-controller/pkg/util/log" |
| 13 | + api "k8s.io/api/core/v1" |
| 14 | +) |
| 15 | + |
| 16 | +const ( |
| 17 | + DeletionProtectionEnabledKey = "deletion_protection.enabled" |
| 18 | + AccessLogsS3EnabledKey = "access_logs.s3.enabled" |
| 19 | + AccessLogsS3BucketKey = "access_logs.s3.bucket" |
| 20 | + AccessLogsS3PrefixKey = "access_logs.s3.prefix" |
| 21 | + IdleTimeoutTimeoutSecondsKey = "idle_timeout.timeout_seconds" |
| 22 | + RoutingHTTP2EnabledKey = "routing.http2.enabled" |
| 23 | + |
| 24 | + DeletionProtectionEnabled = false |
| 25 | + AccessLogsS3Enabled = false |
| 26 | + AccessLogsS3Bucket = "" |
| 27 | + AccessLogsS3Prefix = "" |
| 28 | + IdleTimeoutTimeoutSeconds = 60 |
| 29 | + RoutingHTTP2Enabled = true |
| 30 | +) |
| 31 | + |
| 32 | +// Attributes represents the desired state of attributes for a load balancer. |
| 33 | +type Attributes struct { |
| 34 | + // LbArn is the ARN of the load balancer |
| 35 | + LbArn string |
| 36 | + |
| 37 | + // DeletionProtectionEnabled: deletion_protection.enabled - Indicates whether deletion protection |
| 38 | + // is enabled. The value is true or false. The default is false. |
| 39 | + DeletionProtectionEnabled bool |
| 40 | + |
| 41 | + // AccessLogsS3Enabled: access_logs.s3.enabled - Indicates whether access logs are enabled. |
| 42 | + // The value is true or false. The default is false. |
| 43 | + AccessLogsS3Enabled bool |
| 44 | + |
| 45 | + // AccessLogsS3Bucket: access_logs.s3.bucket - The name of the S3 bucket for the access logs. |
| 46 | + // This attribute is required if access logs are enabled. The bucket must |
| 47 | + // exist in the same region as the load balancer and have a bucket policy |
| 48 | + // that grants Elastic Load Balancing permissions to write to the bucket. |
| 49 | + AccessLogsS3Bucket string |
| 50 | + |
| 51 | + // AccessLogsS3Prefix: access_logs.s3.prefix - The prefix for the location in the S3 bucket |
| 52 | + // for the access logs. |
| 53 | + AccessLogsS3Prefix string |
| 54 | + |
| 55 | + // IdleTimeoutTimeoutSeconds: idle_timeout.timeout_seconds - The idle timeout value, in seconds. The |
| 56 | + // valid range is 1-4000 seconds. The default is 60 seconds. |
| 57 | + IdleTimeoutTimeoutSeconds int64 |
| 58 | + |
| 59 | + // RoutingHTTP2Enabled: routing.http2.enabled - Indicates whether HTTP/2 is enabled. The value |
| 60 | + // is true or false. The default is true. |
| 61 | + RoutingHTTP2Enabled bool |
| 62 | +} |
| 63 | + |
| 64 | +func NewAttributes(attrs []*elbv2.LoadBalancerAttribute) (a *Attributes, err error) { |
| 65 | + a = &Attributes{ |
| 66 | + DeletionProtectionEnabled: DeletionProtectionEnabled, |
| 67 | + AccessLogsS3Enabled: AccessLogsS3Enabled, |
| 68 | + AccessLogsS3Bucket: AccessLogsS3Bucket, |
| 69 | + AccessLogsS3Prefix: AccessLogsS3Prefix, |
| 70 | + IdleTimeoutTimeoutSeconds: IdleTimeoutTimeoutSeconds, |
| 71 | + RoutingHTTP2Enabled: RoutingHTTP2Enabled, |
| 72 | + } |
| 73 | + var e error |
| 74 | + for _, attr := range attrs { |
| 75 | + attrValue := aws.StringValue(attr.Value) |
| 76 | + switch attrKey := aws.StringValue(attr.Key); attrKey { |
| 77 | + case DeletionProtectionEnabledKey: |
| 78 | + a.DeletionProtectionEnabled, err = strconv.ParseBool(attrValue) |
| 79 | + if err != nil { |
| 80 | + return a, fmt.Errorf("invalid load balancer attribute value %s=%s", attrKey, attrValue) |
| 81 | + } |
| 82 | + case AccessLogsS3EnabledKey: |
| 83 | + a.AccessLogsS3Enabled, err = strconv.ParseBool(attrValue) |
| 84 | + if err != nil { |
| 85 | + return a, fmt.Errorf("invalid load balancer attribute value %s=%s", attrKey, attrValue) |
| 86 | + } |
| 87 | + case AccessLogsS3BucketKey: |
| 88 | + a.AccessLogsS3Bucket = attrValue |
| 89 | + case AccessLogsS3PrefixKey: |
| 90 | + a.AccessLogsS3Prefix = attrValue |
| 91 | + case IdleTimeoutTimeoutSecondsKey: |
| 92 | + a.IdleTimeoutTimeoutSeconds, err = strconv.ParseInt(attrValue, 10, 64) |
| 93 | + if err != nil { |
| 94 | + return a, fmt.Errorf("invalid load balancer attribute value %s=%s", attrKey, attrValue) |
| 95 | + } |
| 96 | + if a.IdleTimeoutTimeoutSeconds < 1 || a.IdleTimeoutTimeoutSeconds > 4000 { |
| 97 | + return a, fmt.Errorf("%s must be within 1-4000 seconds", attrKey) |
| 98 | + } |
| 99 | + case RoutingHTTP2EnabledKey: |
| 100 | + a.RoutingHTTP2Enabled, err = strconv.ParseBool(attrValue) |
| 101 | + if err != nil { |
| 102 | + return a, fmt.Errorf("invalid load balancer attribute value %s=%s", attrKey, attrValue) |
| 103 | + } |
| 104 | + default: |
| 105 | + e = NewInvalidAttribute(attrKey) |
| 106 | + } |
| 107 | + } |
| 108 | + return a, e |
| 109 | +} |
| 110 | + |
| 111 | +// AttributesController provides functionality to manage Attributes |
| 112 | +type AttributesController interface { |
| 113 | + // Reconcile ensures the load balancer attributes in AWS matches the state specified by the ingress configuration. |
| 114 | + Reconcile(context.Context, *Attributes) error |
| 115 | +} |
| 116 | + |
| 117 | +// NewAttributesController constructs a new attributes controller |
| 118 | +func NewAttributesController(elbv2 elbv2iface.ELBV2API) AttributesController { |
| 119 | + return &attributesController{ |
| 120 | + elbv2: elbv2, |
| 121 | + } |
| 122 | +} |
| 123 | + |
| 124 | +type attributesController struct { |
| 125 | + elbv2 elbv2iface.ELBV2API |
| 126 | +} |
| 127 | + |
| 128 | +func (c *attributesController) Reconcile(ctx context.Context, desired *Attributes) error { |
| 129 | + raw, err := c.elbv2.DescribeLoadBalancerAttributes(&elbv2.DescribeLoadBalancerAttributesInput{ |
| 130 | + LoadBalancerArn: aws.String(desired.LbArn), |
| 131 | + }) |
| 132 | + |
| 133 | + if err != nil { |
| 134 | + return fmt.Errorf("failed to retrieve attributes from ELBV2 in AWS: %s", err.Error()) |
| 135 | + } |
| 136 | + |
| 137 | + current, err := NewAttributes(raw.Attributes) |
| 138 | + if err != nil && !IsInvalidAttribute(err) { |
| 139 | + return fmt.Errorf("failed parsing attributes: %v", err) |
| 140 | + } |
| 141 | + |
| 142 | + changeSet := attributesChangeSet(current, desired) |
| 143 | + if len(changeSet) > 0 { |
| 144 | + albctx.GetLogger(ctx).Infof("Modifying ELBV2 attributes to %v.", log.Prettify(changeSet)) |
| 145 | + _, err = c.elbv2.ModifyLoadBalancerAttributes(&elbv2.ModifyLoadBalancerAttributesInput{ |
| 146 | + LoadBalancerArn: aws.String(desired.LbArn), |
| 147 | + Attributes: changeSet, |
| 148 | + }) |
| 149 | + if err != nil { |
| 150 | + eventf, ok := albctx.GetEventf(ctx) |
| 151 | + if ok { |
| 152 | + eventf(api.EventTypeWarning, "ERROR", "%s attributes modification failed: %s", desired.LbArn, err.Error()) |
| 153 | + } |
| 154 | + return fmt.Errorf("failed modifying attributes: %s", err) |
| 155 | + } |
| 156 | + |
| 157 | + } |
| 158 | + return nil |
| 159 | +} |
| 160 | + |
| 161 | +// attributesChangeSet returns a list of elbv2.LoadBalancerAttribute required to change a into b |
| 162 | +func attributesChangeSet(a, b *Attributes) (changeSet []*elbv2.LoadBalancerAttribute) { |
| 163 | + if a.DeletionProtectionEnabled != b.DeletionProtectionEnabled { |
| 164 | + changeSet = append(changeSet, lbAttribute(DeletionProtectionEnabledKey, fmt.Sprintf("%v", b.DeletionProtectionEnabled))) |
| 165 | + } |
| 166 | + |
| 167 | + if a.AccessLogsS3Enabled != b.AccessLogsS3Enabled { |
| 168 | + changeSet = append(changeSet, lbAttribute(AccessLogsS3EnabledKey, fmt.Sprintf("%v", b.AccessLogsS3Enabled))) |
| 169 | + } |
| 170 | + |
| 171 | + if a.AccessLogsS3Bucket != b.AccessLogsS3Bucket { |
| 172 | + changeSet = append(changeSet, lbAttribute(AccessLogsS3BucketKey, b.AccessLogsS3Bucket)) |
| 173 | + } |
| 174 | + |
| 175 | + if a.AccessLogsS3Prefix != b.AccessLogsS3Prefix { |
| 176 | + changeSet = append(changeSet, lbAttribute(AccessLogsS3PrefixKey, b.AccessLogsS3Prefix)) |
| 177 | + } |
| 178 | + |
| 179 | + if a.IdleTimeoutTimeoutSeconds != b.IdleTimeoutTimeoutSeconds { |
| 180 | + changeSet = append(changeSet, lbAttribute(IdleTimeoutTimeoutSecondsKey, fmt.Sprintf("%v", b.IdleTimeoutTimeoutSeconds))) |
| 181 | + } |
| 182 | + |
| 183 | + if a.RoutingHTTP2Enabled != b.RoutingHTTP2Enabled { |
| 184 | + changeSet = append(changeSet, lbAttribute(RoutingHTTP2EnabledKey, fmt.Sprintf("%v", b.RoutingHTTP2Enabled))) |
| 185 | + } |
| 186 | + |
| 187 | + return |
| 188 | +} |
| 189 | + |
| 190 | +func lbAttribute(k, v string) *elbv2.LoadBalancerAttribute { |
| 191 | + return &elbv2.LoadBalancerAttribute{Key: aws.String(k), Value: aws.String(v)} |
| 192 | +} |
| 193 | + |
| 194 | +// NewInvalidAttribute returns a new InvalidAttribute error |
| 195 | +func NewInvalidAttribute(name string) error { |
| 196 | + return InvalidAttribute{ |
| 197 | + Name: fmt.Sprintf("the load balancer attribute %v is not valid", name), |
| 198 | + } |
| 199 | +} |
| 200 | + |
| 201 | +// InvalidAttribute error |
| 202 | +type InvalidAttribute struct { |
| 203 | + Name string |
| 204 | +} |
| 205 | + |
| 206 | +func (e InvalidAttribute) Error() string { |
| 207 | + return e.Name |
| 208 | +} |
| 209 | + |
| 210 | +// IsInvalidAttribute checks if the err is from an invalid attribute |
| 211 | +func IsInvalidAttribute(e error) bool { |
| 212 | + _, ok := e.(InvalidAttribute) |
| 213 | + return ok |
| 214 | +} |
0 commit comments