Skip to content

aws-ecs-patterns.ApplicationLoadBalancedFargateService: A listener already exists on this port for this load balancer when using redirect_http=True #34235

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
2 tasks
BwL1289 opened this issue Apr 23, 2025 · 4 comments
Labels
@aws-cdk/aws-ecs-patterns Related to ecs-patterns library bug This issue is a bug. p1

Comments

@BwL1289
Copy link

BwL1289 commented Apr 23, 2025

Describe the feature

Similar to #13759 and #15894, redirect_http=True should not unconditionally create an HTTP listener.

It should be possible to pass in an existing http listener.

Use Case

Allow passing in an existing http listener when using redirect_http=True. If you've already deployed your stack and try to use redirect_http=True, you'll get the following error:

Resource handler returned message: "A listener already exists on this port for this load balancer 'arn:aws:elasticloadbalancing:<redacted>' (Service: ElasticLoadBalancingV2, Status Code: 400, Request ID: 18a9f3bd-a9a2-425c-98ab-2840249dff81) (SDK Attempt Count: 1)" (RequestToken: 376b999e-d4a9-761e-408a-a9e551765032, HandlerErrorCode: AlreadyExists)

Proposed Solution

Allow passing in an existing http listener when using redirect_http=True

Other Information

No response

Acknowledgements

  • I may be able to implement this feature request
  • This feature might incur a breaking change

CDK version used

2.180.0

Environment details (OS name and version, etc.)

MacOS

@BwL1289 BwL1289 added feature-request A feature should be added or improved. needs-triage This issue or PR still needs to be triaged. labels Apr 23, 2025
@github-actions github-actions bot added the @aws-cdk/aws-ecs-patterns Related to ecs-patterns library label Apr 23, 2025
@ykethan
Copy link
Contributor

ykethan commented Apr 24, 2025

Hey @BwL1289, thank you for reporting this issue. I was able to reproduce this issue with redirectHTTP: true. When updating an existing stack to enable HTTP to HTTPS redirection, the deployment fails with the error:

CREATE_FAILED | AWS::ElasticLoadBalancingV2::Listener
Resource handler returned message: "A listener already exists on this port for this load balancer 'arn:aws:elasticloadbalancing:us-east-1:...'
(Service: ElasticLoadBalancingV2, Status Code: 400, Request ID: a4d73514-1cc8-4b6a-9fc7-f1da0eff1657)"

Reproduction Steps

  1. Create a CDK stack with ApplicationLoadBalancedFargateService without redirectHTTP: true:
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns';

export class MyProjectStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const vpc = new ec2.Vpc(this, 'MyVpc', {
      maxAzs: 2,
      natGateways: 1,
    });

    const cluster = new ecs.Cluster(this, 'MyCluster', {
      vpc: vpc,
    });

    const fargateService =
      new ecs_patterns.ApplicationLoadBalancedFargateService(
        this,
        'MyFargateService',
        {
          cluster: cluster,
          memoryLimitMiB: 512,
          cpu: 256,
          taskImageOptions: {
            image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
          },
        },
      );

    new cdk.CfnOutput(this, 'LoadBalancerDNS', {
      value: fargateService.loadBalancer.loadBalancerDnsName,
    });
  }
}
  1. Deploy this stack (this creates an HTTP listener on port 80)
  2. Update the stack to add redirectHTTP: true and a certificate:
const certificate = certificatemanager.Certificate.fromCertificateArn(
  this,
  'Certificate',
  'arn:aws:acm:region:account:certificate/certificate-id'
);

const fargateService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'MyFargateService', {
  cluster: cluster,
  memoryLimitMiB: 512,
  cpu: 256,
  taskImageOptions: {
    image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
  },
  certificate: certificate,
  protocol: ApplicationProtocol.HTTPS,
  redirectHTTP: true, // This causes the error
});
  1. Deploy the updated stack, which fails with the error above

The issue appears in the ApplicationLoadBalancedServiceBase class. When redirectHTTP: true is set, it unconditionally creates a new HTTP listener on port 80:

if (props.redirectHTTP) {
  this.redirectListener = loadBalancer.addListener('PublicRedirectListener', {
    protocol: ApplicationProtocol.HTTP,
    port: 80,
    open: props.openListener ?? true,
    defaultAction: ListenerAction.redirect({
      port: props.listenerPort?.toString() || '443',
      protocol: ApplicationProtocol.HTTPS,
      permanent: true,
    }),
  });
}

we may need to strategize this to

  1. Check if an HTTP listener already exists on port 80 before creating a new one
  2. If an existing listener is found, modify its default action to add the redirect action instead of creating a new listener.

Thank you for posting a workaround for this issue: #5583 (comment)

@ykethan ykethan added bug This issue is a bug. p2 and removed feature-request A feature should be added or improved. needs-triage This issue or PR still needs to be triaged. labels Apr 24, 2025
@BwL1289
Copy link
Author

BwL1289 commented Apr 24, 2025

@ykethan sure. Given that #13759 and #15894 are years old and p1, I think those should be given priority.

@pahud
Copy link
Contributor

pahud commented Apr 24, 2025

@BwL1289 Thank you for bringing this to our attention. Yes you are right, I guess we might need a PR to filter out all existing listeners and avoid potential duplicate. Off the top of my head, it should pretty much look like this:

// Inside ApplicationLoadBalancedServiceBase constructor, within the
// if (props.redirectHTTP) { ... } block:

if (props.redirectHTTP) {
  // Define the redirect action properties
  const redirectAction = elbv2.ListenerAction.redirect({
    protocol: elbv2.ApplicationProtocol.HTTPS,
    port: props.listenerPort?.toString() || '443', // Use the HTTPS listener port or default 443
    permanent: true,
  });

  // --- START REVISED FIX LOGIC  ---

  let existingHttpListener: elbv2.IApplicationListener | undefined = undefined;

  // Check if the load balancer was created by this construct (most reliable case)
  // `this._applicationLoadBalancer` holds the owned instance, if created.
  if (this._applicationLoadBalancer) {
      // Attempt to find an existing listener on port 80.
      // Ideally, the ALB construct would have a method like `tryFindListener`.
      // If not, iterating `listeners` is possible but requires careful token handling.
      existingHttpListener = this._applicationLoadBalancer.listeners.find(listener => {
          const cfnListener = listener.node.defaultChild as elbv2.CfnListener;
          // Comparing resolved Cfn properties can be brittle due to Tokens.
          // This comparison might need refinement or rely on intrinsic functions/Aspects
          // for robust token resolution during synthesis if needed.
          return cdk.Stack.of(this).resolve(cfnListener.port) === 80 &&
                 cdk.Stack.of(this).resolve(cfnListener.protocol) === 'HTTP';
      });

      if (existingHttpListener) {
        // If found, modify its default action.
        existingHttpListener.addAction('RedirectAction', { // Or modifyDefaultAction if more suitable
           action: redirectAction,
        });
        this.redirectListener = existingHttpListener;
      } else {
        // If not found (and we own the LB), create a new one.
        this.redirectListener = this._applicationLoadBalancer.addListener('PublicRedirectListener', {
          protocol: elbv2.ApplicationProtocol.HTTP,
          port: 80,
          open: props.openListener ?? true, // Use existing openListener prop
          defaultAction: redirectAction, // Set the default action to redirect
        });
      }
  } else {
      // If the load balancer was imported (props.loadBalancer was provided),
      // reliably finding the listener without context lookups is difficult.
      // Add a CDK warning annotation to guide the user.
      cdk.Annotations.of(this).addWarning(
          'Cannot automatically configure redirectHTTP for an imported Load Balancer. '+
          'The construct cannot reliably determine if an HTTP listener already exists. '+
          'Please configure the redirect manually on the port 80 listener or explicitly pass the listener reference.'
      );
      // In this case (imported LB), the construct does *not* attempt to add or modify listeners for redirect.
  }
  // --- END REVISED FIX LOGIC ---
}
  • Please note this is just a random idea for the fix, I didn't test this in my account.

We'll bring this up to the team for inputs and we encourage contributions! If you or someone else is interested in implementing the proper fix within the construct (following the revised logic outlined above), a Pull Request would be welcome. The contribution guidelines can be found here.

We'll keep this issue open to track progress on the fix.

@ykethan ykethan added p1 and removed p2 labels Apr 24, 2025
@BwL1289
Copy link
Author

BwL1289 commented Apr 24, 2025

@pahud sounds good, this LGTM. I'd make a PR but my TS skills are shaky at best.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
@aws-cdk/aws-ecs-patterns Related to ecs-patterns library bug This issue is a bug. p1
Projects
None yet
Development

No branches or pull requests

3 participants