github.com/in4it/ecs-deploy@v0.0.42-0.20240508120354-ed77ff16df25/provider/ecs/alb.go (about) 1 package ecs 2 3 import ( 4 "fmt" 5 6 "github.com/aws/aws-sdk-go/aws" 7 "github.com/aws/aws-sdk-go/aws/awserr" 8 "github.com/aws/aws-sdk-go/aws/session" 9 "github.com/aws/aws-sdk-go/service/acm" 10 "github.com/aws/aws-sdk-go/service/elbv2" 11 "github.com/in4it/ecs-deploy/service" 12 "github.com/in4it/ecs-deploy/util" 13 "github.com/juju/loggo" 14 15 "errors" 16 "strconv" 17 "strings" 18 ) 19 20 // logging 21 var albLogger = loggo.GetLogger("alb") 22 23 // ALB struct 24 type ALB struct { 25 loadBalancerName string 26 loadBalancerArn string 27 VpcId string 28 Listeners []*elbv2.Listener 29 Domain string 30 Rules map[string][]*elbv2.Rule 31 DnsName string 32 } 33 34 func NewALB(loadBalancerName string) (*ALB, error) { 35 a := ALB{} 36 a.loadBalancerName = loadBalancerName 37 // retrieve vpcId and loadBalancerArn 38 svc := elbv2.New(session.New()) 39 input := &elbv2.DescribeLoadBalancersInput{ 40 Names: []*string{ 41 aws.String(loadBalancerName), 42 }, 43 } 44 45 result, err := svc.DescribeLoadBalancers(input) 46 if err != nil { 47 if aerr, ok := err.(awserr.Error); ok { 48 switch aerr.Code() { 49 case elbv2.ErrCodeLoadBalancerNotFoundException: 50 albLogger.Errorf(elbv2.ErrCodeLoadBalancerNotFoundException+": %v", aerr.Error()) 51 default: 52 albLogger.Errorf(aerr.Error()) 53 } 54 } else { 55 // Print the error, cast err to awserr.Error to get the Code and 56 // Message from an error. 57 albLogger.Errorf(err.Error()) 58 } 59 return nil, errors.New("Could not describe loadbalancer") 60 } else if len(result.LoadBalancers) == 0 { 61 return nil, errors.New("Could not describe loadbalancer (no elements returned)") 62 } 63 a.loadBalancerArn = *result.LoadBalancers[0].LoadBalancerArn 64 a.loadBalancerName = *result.LoadBalancers[0].LoadBalancerName 65 a.VpcId = *result.LoadBalancers[0].VpcId 66 67 // get listeners 68 err = a.GetListeners() 69 if err != nil { 70 return nil, err 71 } else if len(result.LoadBalancers) == 0 { 72 return nil, errors.New("Could not get listeners for loadbalancer (no elements returned)") 73 } 74 // get domain (if SSL cert is attached) 75 err = a.GetDomainUsingCertificate() 76 if err != nil { 77 return nil, err 78 } 79 80 return &a, nil 81 } 82 83 // get the listeners for the loadbalancer 84 func NewALBAndCreate(loadBalancerName, ipAddressType string, scheme string, securityGroups []string, subnets []string, lbType string) (*ALB, error) { 85 a := ALB{} 86 svc := elbv2.New(session.New()) 87 input := &elbv2.CreateLoadBalancerInput{ 88 IpAddressType: aws.String(ipAddressType), 89 Name: aws.String(loadBalancerName), 90 Scheme: aws.String(scheme), 91 SecurityGroups: aws.StringSlice(securityGroups), 92 Subnets: aws.StringSlice(subnets), 93 Type: aws.String(lbType), 94 } 95 96 result, err := svc.CreateLoadBalancer(input) 97 if err != nil { 98 if aerr, ok := err.(awserr.Error); ok { 99 albLogger.Errorf(aerr.Error()) 100 return nil, aerr 101 } 102 albLogger.Errorf(err.Error()) 103 return nil, err 104 } 105 if len(result.LoadBalancers) == 0 { 106 return nil, errors.New("No loadbalancers returned") 107 } 108 a.loadBalancerArn = aws.StringValue(result.LoadBalancers[0].LoadBalancerArn) 109 a.DnsName = aws.StringValue(result.LoadBalancers[0].DNSName) 110 a.VpcId = aws.StringValue(result.LoadBalancers[0].VpcId) 111 return &a, nil 112 } 113 114 func (a *ALB) DeleteLoadBalancer() error { 115 svc := elbv2.New(session.New()) 116 input := &elbv2.DeleteLoadBalancerInput{ 117 LoadBalancerArn: aws.String(a.loadBalancerArn), 118 } 119 _, err := svc.DeleteLoadBalancer(input) 120 if err != nil { 121 if aerr, ok := err.(awserr.Error); ok { 122 albLogger.Errorf(aerr.Error()) 123 return aerr 124 } 125 albLogger.Errorf(err.Error()) 126 return err 127 } 128 return nil 129 } 130 131 func (a *ALB) CreateListener(protocol string, port int64, targetGroupArn string) error { 132 // only HTTP is supported for now 133 svc := elbv2.New(session.New()) 134 input := &elbv2.CreateListenerInput{ 135 LoadBalancerArn: aws.String(a.loadBalancerArn), 136 Port: aws.Int64(port), 137 Protocol: aws.String(protocol), 138 DefaultActions: []*elbv2.Action{ 139 {Type: aws.String("forward"), TargetGroupArn: aws.String(targetGroupArn)}, 140 }, 141 } 142 143 result, err := svc.CreateListener(input) 144 if err != nil { 145 if aerr, ok := err.(awserr.Error); ok { 146 albLogger.Errorf(aerr.Error()) 147 } else { 148 albLogger.Errorf(err.Error()) 149 } 150 return err 151 } 152 if len(result.Listeners) == 0 { 153 return errors.New("No listeners returned") 154 } 155 a.Listeners = append(a.Listeners, result.Listeners[0]) 156 return nil 157 } 158 func (a *ALB) DeleteListener(listenerArn string) error { 159 svc := elbv2.New(session.New()) 160 input := &elbv2.DeleteListenerInput{ 161 ListenerArn: aws.String(listenerArn), 162 } 163 164 _, err := svc.DeleteListener(input) 165 if err != nil { 166 if aerr, ok := err.(awserr.Error); ok { 167 albLogger.Errorf(aerr.Error()) 168 } else { 169 albLogger.Errorf(err.Error()) 170 } 171 return err 172 } 173 return nil 174 } 175 176 // get the listeners for the loadbalancer 177 func (a *ALB) GetListeners() error { 178 svc := elbv2.New(session.New()) 179 input := &elbv2.DescribeListenersInput{LoadBalancerArn: aws.String(a.loadBalancerArn)} 180 181 result, err := svc.DescribeListeners(input) 182 if err != nil { 183 if aerr, ok := err.(awserr.Error); ok { 184 switch aerr.Code() { 185 case elbv2.ErrCodeListenerNotFoundException: 186 albLogger.Errorf(elbv2.ErrCodeListenerNotFoundException+": %v", aerr.Error()) 187 case elbv2.ErrCodeLoadBalancerNotFoundException: 188 albLogger.Errorf(elbv2.ErrCodeLoadBalancerNotFoundException+": %v", aerr.Error()) 189 default: 190 albLogger.Errorf(aerr.Error()) 191 } 192 } else { 193 albLogger.Errorf(err.Error()) 194 } 195 return errors.New("Could not get Listeners for loadbalancer") 196 } 197 for _, l := range result.Listeners { 198 a.Listeners = append(a.Listeners, l) 199 } 200 return nil 201 } 202 203 // get the domain using certificates 204 func (a *ALB) GetDomainUsingCertificate() error { 205 svc := acm.New(session.New()) 206 for _, l := range a.Listeners { 207 for _, c := range l.Certificates { 208 albLogger.Debugf("ALB Certificate found with arn: %v", *c.CertificateArn) 209 input := &acm.DescribeCertificateInput{ 210 CertificateArn: c.CertificateArn, 211 } 212 213 result, err := svc.DescribeCertificate(input) 214 if err != nil { 215 if aerr, ok := err.(awserr.Error); ok { 216 switch aerr.Code() { 217 case acm.ErrCodeResourceNotFoundException: 218 albLogger.Errorf(acm.ErrCodeResourceNotFoundException+": %v", aerr.Error()) 219 case acm.ErrCodeInvalidArnException: 220 albLogger.Errorf(acm.ErrCodeInvalidArnException+": %v", aerr.Error()) 221 default: 222 albLogger.Errorf(aerr.Error()) 223 } 224 } else { 225 albLogger.Errorf(err.Error()) 226 } 227 return errors.New("Could not describe certificate") 228 } 229 albLogger.Debugf("Domain found through ALB certificate: %v", *result.Certificate.DomainName) 230 s := strings.Split(*result.Certificate.DomainName, ".") 231 if len(s) >= 2 { 232 a.Domain = s[len(s)-2] + "." + s[len(s)-1] 233 } 234 return nil 235 } 236 } 237 return nil 238 } 239 240 func (a *ALB) CreateTargetGroup(serviceName string, d service.Deploy) (*string, error) { 241 svc := elbv2.New(session.New()) 242 input := &elbv2.CreateTargetGroupInput{ 243 Name: aws.String(util.TruncateString(serviceName, 32)), 244 VpcId: aws.String(a.VpcId), 245 Port: aws.Int64(d.ServicePort), 246 Protocol: aws.String(d.ServiceProtocol), 247 } 248 if d.HealthCheck.HealthyThreshold != 0 { 249 input.SetHealthyThresholdCount(d.HealthCheck.HealthyThreshold) 250 } 251 if d.HealthCheck.UnhealthyThreshold != 0 { 252 input.SetUnhealthyThresholdCount(d.HealthCheck.UnhealthyThreshold) 253 } 254 if d.HealthCheck.Path != "" { 255 input.SetHealthCheckPath(d.HealthCheck.Path) 256 } 257 if d.HealthCheck.Port != "" { 258 input.SetHealthCheckPort(d.HealthCheck.Port) 259 } 260 if d.HealthCheck.Protocol != "" { 261 input.SetHealthCheckProtocol(d.HealthCheck.Protocol) 262 } 263 if d.HealthCheck.Interval != 0 { 264 input.SetHealthCheckIntervalSeconds(d.HealthCheck.Interval) 265 } 266 if d.HealthCheck.Matcher != "" { 267 input.SetMatcher(&elbv2.Matcher{HttpCode: aws.String(d.HealthCheck.Matcher)}) 268 } 269 if d.HealthCheck.Timeout > 0 { 270 input.SetHealthCheckTimeoutSeconds(d.HealthCheck.Timeout) 271 } 272 if d.NetworkMode == "awsvpc" && len(d.NetworkConfiguration.Subnets) > 0 { 273 input.SetTargetType("ip") 274 } 275 276 result, err := svc.CreateTargetGroup(input) 277 if err != nil { 278 if aerr, ok := err.(awserr.Error); ok { 279 switch aerr.Code() { 280 case elbv2.ErrCodeDuplicateTargetGroupNameException: 281 albLogger.Errorf(elbv2.ErrCodeDuplicateTargetGroupNameException+": %v", aerr.Error()) 282 case elbv2.ErrCodeTooManyTargetGroupsException: 283 albLogger.Errorf(elbv2.ErrCodeTooManyTargetGroupsException+": %v", aerr.Error()) 284 case elbv2.ErrCodeInvalidConfigurationRequestException: 285 albLogger.Errorf(elbv2.ErrCodeInvalidConfigurationRequestException+": %v", aerr.Error()) 286 default: 287 albLogger.Errorf(aerr.Error()) 288 } 289 } else { 290 // Print the error, cast err to awserr.Error to get the Code and 291 // Message from an error. 292 albLogger.Errorf(err.Error()) 293 } 294 return nil, errors.New("Could not create target group") 295 } else if len(result.TargetGroups) == 0 { 296 return nil, errors.New("Could not create target group (target group list is empty)") 297 } 298 return result.TargetGroups[0].TargetGroupArn, nil 299 } 300 func (a *ALB) DeleteTargetGroup(targetGroupArn string) error { 301 svc := elbv2.New(session.New()) 302 input := &elbv2.DeleteTargetGroupInput{ 303 TargetGroupArn: aws.String(targetGroupArn), 304 } 305 _, err := svc.DeleteTargetGroup(input) 306 if err != nil { 307 if aerr, ok := err.(awserr.Error); ok { 308 albLogger.Errorf(aerr.Error()) 309 } else { 310 albLogger.Errorf(err.Error()) 311 } 312 return err 313 } 314 return nil 315 } 316 317 func (a *ALB) GetHighestRule() (int64, error) { 318 var highest int64 319 svc := elbv2.New(session.New()) 320 321 for _, listener := range a.Listeners { 322 input := &elbv2.DescribeRulesInput{ListenerArn: listener.ListenerArn} 323 324 c := true // parse more pages if c is true 325 result, err := svc.DescribeRules(input) 326 for c { 327 if err != nil { 328 if aerr, ok := err.(awserr.Error); ok { 329 switch aerr.Code() { 330 case elbv2.ErrCodeListenerNotFoundException: 331 albLogger.Errorf(elbv2.ErrCodeListenerNotFoundException+": %v", aerr.Error()) 332 case elbv2.ErrCodeRuleNotFoundException: 333 albLogger.Errorf(elbv2.ErrCodeRuleNotFoundException+": %v", aerr.Error()) 334 default: 335 albLogger.Errorf(aerr.Error()) 336 } 337 } else { 338 // Print the error, cast err to awserr.Error to get the Code and 339 // Message from an error. 340 albLogger.Errorf(err.Error()) 341 } 342 return 0, errors.New("Could not describe alb listener rules") 343 } 344 345 albLogger.Tracef("Looping rules: %+v", result.Rules) 346 for _, rule := range result.Rules { 347 if i, _ := strconv.ParseInt(*rule.Priority, 10, 64); i > highest { 348 albLogger.Debugf("Found rule with priority: %d", i) 349 highest = i 350 } 351 } 352 if result.NextMarker == nil || len(*result.NextMarker) == 0 { 353 c = false 354 } else { 355 input.SetMarker(*result.NextMarker) 356 result, err = svc.DescribeRules(input) 357 } 358 } 359 } 360 361 albLogger.Debugf("Higest rule: %d", highest) 362 363 return highest, nil 364 } 365 366 func (a *ALB) CreateRuleForAllListeners(ruleType string, targetGroupArn string, rules []string, priority int64) ([]string, error) { 367 var listeners []string 368 for _, l := range a.Listeners { 369 err := a.CreateRule(ruleType, *l.ListenerArn, targetGroupArn, rules, priority, service.DeployRuleConditionsCognitoAuth{}) 370 if err != nil { 371 return nil, err 372 } 373 listeners = append(listeners, *l.ListenerArn) 374 } 375 return listeners, nil 376 } 377 378 func (a *ALB) CreateRuleForListeners(ruleType string, listeners []string, targetGroupArn string, rules []string, priority int64, cognitoAuth service.DeployRuleConditionsCognitoAuth) ([]string, error) { 379 retListeners := a.getListenersArnForProtocol(listeners) 380 for proto, listener := range retListeners { 381 var err error 382 // if cognito is set, a redirect is needed instead (cognito doesn't work with http) 383 if proto == "http" && cognitoAuth.ClientName != "" { 384 err = a.CreateHTTPSRedirectRule(ruleType, listener, targetGroupArn, rules, priority) 385 } else { 386 err = a.CreateRule(ruleType, listener, targetGroupArn, rules, priority, cognitoAuth) 387 } 388 if err != nil { 389 return nil, err 390 } 391 } 392 listenerArns := []string{} 393 for _, v := range retListeners { 394 listenerArns = append(listenerArns, v) 395 } 396 return listenerArns, nil 397 } 398 399 func (a *ALB) getListenersArnForProtocol(listeners []string) map[string]string { 400 listenersArn := make(map[string]string) 401 for _, l := range a.Listeners { 402 for _, l2 := range listeners { 403 if l.Protocol != nil && strings.ToLower(aws.StringValue(l.Protocol)) == strings.ToLower(l2) { 404 listenersArn[strings.ToLower(aws.StringValue(l.Protocol))] = aws.StringValue(l.ListenerArn) 405 } 406 } 407 } 408 for k, v := range listenersArn { 409 albLogger.Debugf("getListenersArnForProtocol: resolved %s to %s", k, v) 410 } 411 412 return listenersArn 413 } 414 415 /* 416 * Gets listeners ARN based on http / https string 417 */ 418 func (a *ALB) GetListenerArnForProtocol(listener string) string { 419 listeners := a.getListenersArnForProtocol([]string{listener}) 420 if val, ok := listeners[listener]; ok { 421 return val 422 } 423 return "" 424 } 425 426 /* 427 * modify an existing rule to a https redirect 428 */ 429 func (a *ALB) UpdateRuleToHTTPSRedirect(targetGroupArn, ruleArn string, ruleType string, rules []string) error { 430 svc := elbv2.New(session.New()) 431 input := &elbv2.ModifyRuleInput{ 432 Actions: []*elbv2.Action{ 433 { 434 RedirectConfig: &elbv2.RedirectActionConfig{ 435 Protocol: aws.String("HTTPS"), 436 StatusCode: aws.String("HTTP_301"), 437 Port: aws.String("443"), 438 }, 439 Type: aws.String("redirect"), 440 }, 441 }, 442 RuleArn: aws.String(ruleArn), 443 } 444 conditions, err := a.getRuleConditions(ruleType, rules) 445 if err != nil { 446 return err 447 } 448 input.SetConditions(conditions) 449 450 _, err = svc.ModifyRule(input) 451 if err != nil { 452 if aerr, ok := err.(awserr.Error); ok { 453 albLogger.Errorf(aerr.Error()) 454 } else { 455 albLogger.Errorf(err.Error()) 456 } 457 return errors.New("Could not modify alb rule") 458 } 459 return nil 460 } 461 462 func (a *ALB) UpdateRule(targetGroupArn, ruleArn string, ruleType string, rules []string, cognitoAuth service.DeployRuleConditionsCognitoAuth) error { 463 svc := elbv2.New(session.New()) 464 input := &elbv2.ModifyRuleInput{ 465 Actions: []*elbv2.Action{ 466 { 467 TargetGroupArn: aws.String(targetGroupArn), 468 Type: aws.String("forward"), 469 }, 470 }, 471 RuleArn: aws.String(ruleArn), 472 } 473 conditions, err := a.getRuleConditions(ruleType, rules) 474 if err != nil { 475 return err 476 } 477 input.SetConditions(conditions) 478 479 // cognito 480 if cognitoAuth.UserPoolName != "" && cognitoAuth.ClientName != "" { 481 cognitoAction, err := a.getCognitoAction(targetGroupArn, cognitoAuth) 482 if err != nil { 483 return err 484 } 485 input.SetActions(cognitoAction) 486 } 487 _, err = svc.ModifyRule(input) 488 if err != nil { 489 if aerr, ok := err.(awserr.Error); ok { 490 albLogger.Errorf(aerr.Error()) 491 } else { 492 albLogger.Errorf(err.Error()) 493 } 494 return errors.New("Could not modify alb rule") 495 } 496 return nil 497 } 498 499 func (a *ALB) getRuleConditions(ruleType string, rules []string) ([]*elbv2.RuleCondition, error) { 500 if ruleType == "pathPattern" { 501 if len(rules) != 1 { 502 return nil, errors.New("Wrong number of rules (expected 1, got " + strconv.Itoa(len(rules)) + ")") 503 } 504 return []*elbv2.RuleCondition{ 505 { 506 Field: aws.String("path-pattern"), 507 Values: []*string{aws.String(rules[0])}, 508 }, 509 }, nil 510 } else if ruleType == "hostname" { 511 if len(rules) != 1 { 512 return nil, errors.New("Wrong number of rules (expected 1, got " + strconv.Itoa(len(rules)) + ")") 513 } 514 hostname := rules[0] 515 return []*elbv2.RuleCondition{ 516 { 517 Field: aws.String("host-header"), 518 Values: []*string{aws.String(hostname)}, 519 }, 520 }, nil 521 } else if ruleType == "combined" { 522 if len(rules) != 2 { 523 return nil, errors.New("Wrong number of rules (expected 2, got " + strconv.Itoa(len(rules)) + ")") 524 } 525 hostname := rules[1] 526 return []*elbv2.RuleCondition{ 527 { 528 Field: aws.String("path-pattern"), 529 Values: []*string{aws.String(rules[0])}, 530 }, 531 { 532 Field: aws.String("host-header"), 533 Values: []*string{aws.String(hostname)}, 534 }, 535 }, nil 536 537 } else { 538 return nil, errors.New("ruleType not recognized: " + ruleType) 539 } 540 } 541 542 func (a *ALB) CreateHTTPSRedirectRule(ruleType string, listenerArn string, targetGroupArn string, rules []string, priority int64) error { 543 svc := elbv2.New(session.New()) 544 input := &elbv2.CreateRuleInput{ 545 Actions: []*elbv2.Action{ 546 { 547 RedirectConfig: &elbv2.RedirectActionConfig{ 548 Protocol: aws.String("HTTPS"), 549 StatusCode: aws.String("HTTP_301"), 550 Port: aws.String("443"), 551 }, 552 Type: aws.String("redirect"), 553 }, 554 }, 555 ListenerArn: aws.String(listenerArn), 556 Priority: aws.Int64(priority), 557 } 558 conditions, err := a.getRuleConditions(ruleType, rules) 559 if err != nil { 560 return err 561 } 562 input.SetConditions(conditions) 563 564 _, err = svc.CreateRule(input) 565 if err != nil { 566 albLogger.Errorf(err.Error()) 567 return fmt.Errorf("Could not create alb rule: %+v", input) 568 } 569 return nil 570 } 571 572 func (a *ALB) CreateRule(ruleType string, listenerArn string, targetGroupArn string, rules []string, priority int64, cognitoAuth service.DeployRuleConditionsCognitoAuth) error { 573 svc := elbv2.New(session.New()) 574 input := &elbv2.CreateRuleInput{ 575 Actions: []*elbv2.Action{ 576 { 577 TargetGroupArn: aws.String(targetGroupArn), 578 Type: aws.String("forward"), 579 }, 580 }, 581 ListenerArn: aws.String(listenerArn), 582 Priority: aws.Int64(priority), 583 } 584 conditions, err := a.getRuleConditions(ruleType, rules) 585 if err != nil { 586 return err 587 } 588 input.SetConditions(conditions) 589 590 // cognito 591 if cognitoAuth.UserPoolName != "" && cognitoAuth.ClientName != "" { 592 cognitoAction, err := a.getCognitoAction(targetGroupArn, cognitoAuth) 593 if err != nil { 594 return err 595 } 596 input.SetActions(cognitoAction) 597 } 598 599 _, err = svc.CreateRule(input) 600 if err != nil { 601 albLogger.Errorf(err.Error()) 602 return errors.New("Could not create alb rule") 603 } 604 return nil 605 } 606 607 // get rules by listener 608 func (a *ALB) GetRulesForAllListeners() error { 609 a.Rules = make(map[string][]*elbv2.Rule) 610 svc := elbv2.New(session.New()) 611 612 for _, l := range a.Listeners { 613 input := &elbv2.DescribeRulesInput{ListenerArn: aws.String(*l.ListenerArn)} 614 615 result, err := svc.DescribeRules(input) 616 if err != nil { 617 if aerr, ok := err.(awserr.Error); ok { 618 switch aerr.Code() { 619 case elbv2.ErrCodeListenerNotFoundException: 620 albLogger.Errorf(elbv2.ErrCodeListenerNotFoundException+": %v", aerr.Error()) 621 case elbv2.ErrCodeRuleNotFoundException: 622 albLogger.Errorf(elbv2.ErrCodeRuleNotFoundException+": %v", aerr.Error()) 623 default: 624 albLogger.Errorf(aerr.Error()) 625 } 626 } else { 627 albLogger.Errorf(err.Error()) 628 } 629 return errors.New("Could not get Listeners for loadbalancer") 630 } 631 for _, r := range result.Rules { 632 a.Rules[*l.ListenerArn] = append(a.Rules[*l.ListenerArn], r) 633 if len(r.Conditions) != 0 && len(r.Conditions[0].Values) != 0 { 634 albLogger.Debugf("Importing rule: %+v (prio: %v)", *r.Conditions[0].Values[0], *r.Priority) 635 } 636 } 637 } 638 return nil 639 } 640 func (a *ALB) GetRulesByTargetGroupArn(targetGroupArn string) []string { 641 var result []string 642 for _, rules := range a.Rules { 643 for _, rule := range rules { 644 for _, ruleAction := range rule.Actions { 645 if aws.StringValue(ruleAction.TargetGroupArn) == targetGroupArn { 646 result = append(result, aws.StringValue(rule.RuleArn)) 647 } 648 } 649 } 650 } 651 return result 652 } 653 func (a *ALB) GetRuleByTargetGroupArnWithAuth(targetGroupArn string) []string { 654 var result []string 655 for _, rules := range a.Rules { 656 for _, rule := range rules { 657 foundAuthType := false 658 for _, ruleAction := range rule.Actions { 659 if aws.StringValue(ruleAction.Type) == "authenticate-cognito" { 660 foundAuthType = true 661 } 662 } 663 if foundAuthType { 664 for _, ruleAction := range rule.Actions { 665 if aws.StringValue(ruleAction.TargetGroupArn) == targetGroupArn { 666 result = append(result, aws.StringValue(rule.RuleArn)) 667 } 668 } 669 } 670 } 671 } 672 return result 673 } 674 func (a *ALB) GetConditionsForRule(ruleArn string) ([]string, []string) { 675 conditionFields := []string{} 676 conditionValues := []string{} 677 for _, rules := range a.Rules { 678 for _, rule := range rules { 679 if aws.StringValue(rule.RuleArn) == ruleArn { 680 for _, condition := range rule.Conditions { 681 if aws.StringValue(condition.Field) == "path-pattern" || aws.StringValue(condition.Field) == "host-header" { 682 conditionFields = append(conditionFields, aws.StringValue(condition.Field)) 683 if len(condition.Values) >= 1 { 684 conditionValues = append(conditionValues, aws.StringValue(condition.Values[0])) 685 } 686 } 687 } 688 } 689 } 690 } 691 return conditionFields, conditionValues 692 } 693 694 func (a *ALB) GetTargetGroupArn(serviceName string) (*string, error) { 695 svc := elbv2.New(session.New()) 696 input := &elbv2.DescribeTargetGroupsInput{ 697 Names: []*string{aws.String(util.TruncateString(serviceName, 32))}, 698 } 699 700 result, err := svc.DescribeTargetGroups(input) 701 if err != nil { 702 if aerr, ok := err.(awserr.Error); ok { 703 switch aerr.Code() { 704 case elbv2.ErrCodeLoadBalancerNotFoundException: 705 albLogger.Errorf(elbv2.ErrCodeLoadBalancerNotFoundException+": %v", aerr.Error()) 706 case elbv2.ErrCodeTargetGroupNotFoundException: 707 albLogger.Errorf(elbv2.ErrCodeTargetGroupNotFoundException+": %v", aerr.Error()) 708 default: 709 albLogger.Errorf(aerr.Error()) 710 } 711 } else { 712 albLogger.Errorf(err.Error()) 713 } 714 return nil, err 715 } 716 if len(result.TargetGroups) == 1 { 717 return result.TargetGroups[0].TargetGroupArn, nil 718 } else { 719 if len(result.TargetGroups) == 0 { 720 return nil, errors.New("No ALB target group found for service: " + serviceName) 721 } else { 722 return nil, errors.New("Multiple target groups found for service: " + serviceName + " (" + fmt.Sprintf("%d", len(result.TargetGroups)) + ")") 723 } 724 } 725 } 726 func (a *ALB) GetDomain() string { 727 return util.GetEnv("LOADBALANCER_DOMAIN", a.Domain) 728 } 729 730 /* 731 * FindRule tries to find a matching rule in the Rules map 732 */ 733 func (a *ALB) FindRule(listener string, targetGroupArn string, conditionField []string, conditionValue []string) (*string, *string, error) { 734 albLogger.Debugf("Find Rule: listener %s, targetGroupArn %s, conditionField %s, conditionValue %s", listener, targetGroupArn, strings.Join(conditionField, ","), strings.Join(conditionValue, ",")) 735 736 if len(conditionField) != len(conditionValue) { 737 return nil, nil, errors.New("conditionField length not equal to conditionValue length") 738 } 739 // examine rules 740 if rules, ok := a.Rules[listener]; ok { 741 for _, r := range rules { 742 for _, a := range r.Actions { 743 if (aws.StringValue(a.Type) == "forward" && aws.StringValue(a.TargetGroupArn) == targetGroupArn) || aws.StringValue(a.Type) == "redirect" { 744 // possible action match found, checking conditions 745 matchingConditions := []bool{} 746 for _, c := range r.Conditions { 747 match := false 748 for i := range conditionField { 749 if aws.StringValue(c.Field) == conditionField[i] && len(c.Values) > 0 && aws.StringValue(c.Values[0]) == conditionValue[i] { 750 match = true 751 } 752 } 753 matchingConditions = append(matchingConditions, match) 754 } 755 if len(matchingConditions) == len(conditionField) && util.IsBoolArrayTrue(matchingConditions) { 756 return r.RuleArn, r.Priority, nil 757 } 758 } 759 } 760 } 761 } else { 762 return nil, nil, errors.New("Listener not found in rule list") 763 } 764 return nil, nil, errors.New("Priority not found for rule: listener " + listener + ", targetGroupArn: " + targetGroupArn + ", Field: " + strings.Join(conditionField, ",") + ", Value: " + strings.Join(conditionValue, ",")) 765 } 766 767 func (a *ALB) UpdateHealthCheck(targetGroupArn string, healthCheck service.DeployHealthCheck) error { 768 svc := elbv2.New(session.New()) 769 input := &elbv2.ModifyTargetGroupInput{ 770 TargetGroupArn: aws.String(targetGroupArn), 771 } 772 if healthCheck.HealthyThreshold != 0 { 773 input.SetHealthyThresholdCount(healthCheck.HealthyThreshold) 774 } 775 if healthCheck.UnhealthyThreshold != 0 { 776 input.SetUnhealthyThresholdCount(healthCheck.UnhealthyThreshold) 777 } 778 if healthCheck.Path != "" { 779 input.SetHealthCheckPath(healthCheck.Path) 780 } 781 if healthCheck.Port != "" { 782 input.SetHealthCheckPort(healthCheck.Port) 783 } 784 if healthCheck.Protocol != "" { 785 input.SetHealthCheckProtocol(healthCheck.Protocol) 786 } 787 if healthCheck.Interval != 0 { 788 input.SetHealthCheckIntervalSeconds(healthCheck.Interval) 789 } 790 if healthCheck.Matcher != "" { 791 input.SetMatcher(&elbv2.Matcher{HttpCode: aws.String(healthCheck.Matcher)}) 792 } 793 if healthCheck.Timeout > 0 { 794 input.SetHealthCheckTimeoutSeconds(healthCheck.Timeout) 795 } 796 _, err := svc.ModifyTargetGroup(input) 797 if err != nil { 798 if aerr, ok := err.(awserr.Error); ok { 799 albLogger.Errorf(aerr.Error()) 800 return aerr 801 } 802 albLogger.Errorf(err.Error()) 803 return err 804 } 805 return nil 806 } 807 808 func (a *ALB) ModifyTargetGroupAttributes(targetGroupArn string, d service.Deploy) error { 809 svc := elbv2.New(session.New()) 810 input := &elbv2.ModifyTargetGroupAttributesInput{ 811 TargetGroupArn: aws.String(targetGroupArn), 812 Attributes: []*elbv2.TargetGroupAttribute{}, 813 } 814 815 if d.DeregistrationDelay != -1 { 816 delay := strconv.FormatInt(d.DeregistrationDelay, 10) 817 input.Attributes = append(input.Attributes, &elbv2.TargetGroupAttribute{Key: aws.String("deregistration_delay.timeout_seconds"), Value: aws.String(delay)}) 818 } 819 820 if d.Stickiness.Enabled { 821 input.Attributes = append(input.Attributes, &elbv2.TargetGroupAttribute{Key: aws.String("stickiness.enabled"), Value: aws.String("true")}) 822 input.Attributes = append(input.Attributes, &elbv2.TargetGroupAttribute{Key: aws.String("stickiness.type"), Value: aws.String("lb_cookie")}) 823 if d.Stickiness.Duration != -1 { 824 sd := strconv.FormatInt(d.Stickiness.Duration, 10) 825 input.Attributes = append(input.Attributes, &elbv2.TargetGroupAttribute{Key: aws.String("stickiness.lb_cookie.duration_seconds"), Value: aws.String(sd)}) 826 } 827 } 828 829 if len(input.Attributes) == 0 { 830 albLogger.Errorf("Tried to modify target group, but no attributes were passed") 831 return nil 832 } 833 834 _, err := svc.ModifyTargetGroupAttributes(input) 835 if err != nil { 836 if aerr, ok := err.(awserr.Error); ok { 837 albLogger.Errorf(aerr.Error()) 838 return aerr 839 } 840 albLogger.Errorf(err.Error()) 841 return err 842 } 843 return nil 844 } 845 func (a *ALB) DeleteRule(ruleArn string) error { 846 svc := elbv2.New(session.New()) 847 input := &elbv2.DeleteRuleInput{ 848 RuleArn: aws.String(ruleArn), 849 } 850 851 albLogger.Debugf("Deleting ALB Rule: %v", ruleArn) 852 _, err := svc.DeleteRule(input) 853 if err != nil { 854 if aerr, ok := err.(awserr.Error); ok { 855 ecsLogger.Errorf("%v", aerr.Error()) 856 } else { 857 ecsLogger.Errorf("%v", err.Error()) 858 } 859 return err 860 } 861 return nil 862 } 863 func (a *ALB) getCognitoAction(targetGroupArn string, cognitoAuth service.DeployRuleConditionsCognitoAuth) ([]*elbv2.Action, error) { 864 // get cognito user pool info 865 cognito := CognitoIdp{} 866 userPoolArn, userPoolClientID, userPoolDomain, err := cognito.getUserPoolInfo(cognitoAuth.UserPoolName, cognitoAuth.ClientName) 867 if err != nil { 868 return nil, err 869 } 870 return []*elbv2.Action{ 871 { 872 AuthenticateCognitoConfig: &elbv2.AuthenticateCognitoActionConfig{ 873 OnUnauthenticatedRequest: aws.String("deny"), 874 UserPoolArn: aws.String(userPoolArn), 875 UserPoolClientId: aws.String(userPoolClientID), 876 UserPoolDomain: aws.String(userPoolDomain), 877 }, 878 Type: aws.String("authenticate-cognito"), 879 Order: aws.Int64(1), 880 }, 881 { 882 TargetGroupArn: aws.String(targetGroupArn), 883 Type: aws.String("forward"), 884 Order: aws.Int64(2), 885 }, 886 }, nil 887 }