Skip to content

Commit

Permalink
Merge branch 'main' into bobertzh/apigwv2-validation
Browse files Browse the repository at this point in the history
  • Loading branch information
HBobertz authored Jan 24, 2025
2 parents 0d29662 + cc1988a commit cf63184
Show file tree
Hide file tree
Showing 19 changed files with 125 additions and 95 deletions.
14 changes: 13 additions & 1 deletion packages/aws-cdk-lib/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@ baseConfig.rules['import/no-extraneous-dependencies'] = [

// no-throw-default-error
const enableNoThrowDefaultErrorIn = [
'aws-apigatewayv2-integrations',
'aws-apigatewayv2-authorizers',
'aws-apigatewayv2-integrations',
'aws-elasticloadbalancing',
'aws-elasticloadbalancingv2',
'aws-elasticloadbalancingv2-actions',
'aws-elasticloadbalancingv2-targets',
'aws-lambda',
'aws-rds',
'aws-s3',
Expand All @@ -37,6 +41,14 @@ const enableNoThrowDefaultErrorIn = [
'aws-route53recoverycontrol',
'aws-route53recoveryreadiness',
'aws-route53resolver',
'aws-sns',
'aws-sqs',
'aws-ssm',
'aws-ssmcontacts',
'aws-ssmincidents',
'aws-ssmquicksetup',
'aws-synthetics',
'aws-s3',
'aws-s3-assets',
'aws-s3-deployment',
'aws-s3-notifications',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
SecurityGroup, SelectedSubnets, SubnetSelection, SubnetType,
} from '../../aws-ec2';
import { Duration, Lazy, Resource } from '../../core';
import { ValidationError } from '../../core/lib/errors';

/**
* Construction properties for a LoadBalancer
Expand Down Expand Up @@ -289,9 +290,9 @@ export class LoadBalancer extends Resource implements IConnectable {
*/
public addListener(listener: LoadBalancerListener): ListenerPort {
if (listener.sslCertificateArn && listener.sslCertificateId) {
throw new Error('"sslCertificateId" is deprecated, please use "sslCertificateArn" only.');
throw new ValidationError('"sslCertificateId" is deprecated, please use "sslCertificateArn" only.', this);
}
const protocol = ifUndefinedLazy(listener.externalProtocol, () => wellKnownProtocol(listener.externalPort));
const protocol = ifUndefinedLazy(listener.externalProtocol, () => wellKnownProtocol(this, listener.externalPort));
const instancePort = listener.internalPort || listener.externalPort;
const sslCertificateArn = listener.sslCertificateArn || listener.sslCertificateId;
const instanceProtocol = ifUndefined(listener.internalProtocol,
Expand Down Expand Up @@ -449,10 +450,10 @@ export class ListenerPort implements IConnectable {
}
}

function wellKnownProtocol(port: number): LoadBalancingProtocol {
function wellKnownProtocol(scope: Construct, port: number): LoadBalancingProtocol {
const proto = tryWellKnownProtocol(port);
if (!proto) {
throw new Error(`Please supply protocol to go with port ${port}`);
throw new ValidationError(`Please supply protocol to go with port ${port}`, scope);
}
return proto;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { IApplicationListener } from './application-listener';
import { IApplicationTargetGroup } from './application-target-group';
import { Port } from '../../../aws-ec2';
import { Duration, SecretValue, Token, Tokenization } from '../../../core';
import { UnscopedValidationError } from '../../../core/lib/errors';
import { CfnListener, CfnListenerRule } from '../elasticloadbalancingv2.generated';
import { IListenerAction } from '../shared/listener-action';

Expand Down Expand Up @@ -39,7 +40,7 @@ export class ListenerAction implements IListenerAction {
*/
public static forward(targetGroups: IApplicationTargetGroup[], options: ForwardOptions = {}): ListenerAction {
if (targetGroups.length === 0) {
throw new Error('Need at least one targetGroup in a ListenerAction.forward()');
throw new UnscopedValidationError('Need at least one targetGroup in a ListenerAction.forward()');
}
if (targetGroups.length === 1 && options.stickinessDuration === undefined) {
// Render a "simple" action for backwards compatibility with old templates
Expand All @@ -59,7 +60,7 @@ export class ListenerAction implements IListenerAction {
*/
public static weightedForward(targetGroups: WeightedTargetGroup[], options: ForwardOptions = {}): ListenerAction {
if (targetGroups.length === 0) {
throw new Error('Need at least one targetGroup in a ListenerAction.weightedForward()');
throw new UnscopedValidationError('Need at least one targetGroup in a ListenerAction.weightedForward()');
}

return new TargetGroupListenerAction(targetGroups.map(g => g.targetGroup), {
Expand Down Expand Up @@ -113,10 +114,10 @@ export class ListenerAction implements IListenerAction {
*/
public static redirect(options: RedirectOptions): ListenerAction {
if ([options.host, options.path, options.port, options.protocol, options.query].findIndex(x => x !== undefined) === -1) {
throw new Error('To prevent redirect loops, set at least one of \'protocol\', \'host\', \'port\', \'path\', or \'query\'.');
throw new UnscopedValidationError('To prevent redirect loops, set at least one of \'protocol\', \'host\', \'port\', \'path\', or \'query\'.');
}
if (options.path && !Token.isUnresolved(options.path) && !options.path.startsWith('/')) {
throw new Error(`Redirect path must start with a \'/\', got: ${options.path}`);
throw new UnscopedValidationError(`Redirect path must start with a \'/\', got: ${options.path}`);
}

return new ListenerAction({
Expand Down Expand Up @@ -200,7 +201,7 @@ export class ListenerAction implements IListenerAction {
*/
protected addRuleAction(actionJson: CfnListenerRule.ActionProperty) {
if (this._actionJson) {
throw new Error('rule action is already set');
throw new UnscopedValidationError('rule action is already set');
}
this._actionJson = actionJson;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Construct } from 'constructs';
import { IApplicationListener } from './application-listener';
import { ValidationError } from '../../../core/lib/errors';
import { CfnListenerCertificate } from '../elasticloadbalancingv2.generated';
import { IListenerCertificate } from '../shared/listener-certificate';

Expand Down Expand Up @@ -40,7 +41,7 @@ export class ApplicationListenerCertificate extends Construct {
super(scope, id);

if (!props.certificateArns && !props.certificates) {
throw new Error('At least one of \'certificateArns\' or \'certificates\' is required');
throw new ValidationError('At least one of \'certificateArns\' or \'certificates\' is required', this);
}

const certificates = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ListenerAction } from './application-listener-action';
import { IApplicationTargetGroup } from './application-target-group';
import { ListenerCondition } from './conditions';
import * as cdk from '../../../core';
import { UnscopedValidationError, ValidationError } from '../../../core/lib/errors';
import { CfnListenerRule } from '../elasticloadbalancingv2.generated';
import { IListenerAction } from '../shared/listener-action';

Expand Down Expand Up @@ -216,17 +217,17 @@ export class ApplicationListenerRule extends Construct {

const hasPathPatterns = props.pathPatterns || props.pathPattern;
if (this.conditions.length === 0 && !props.hostHeader && !hasPathPatterns) {
throw new Error('At least one of \'conditions\', \'hostHeader\', \'pathPattern\' or \'pathPatterns\' is required when defining a load balancing rule.');
throw new ValidationError('At least one of \'conditions\', \'hostHeader\', \'pathPattern\' or \'pathPatterns\' is required when defining a load balancing rule.', this);
}

const possibleActions: Array<keyof ApplicationListenerRuleProps> = ['action', 'targetGroups', 'fixedResponse', 'redirectResponse'];
const providedActions = possibleActions.filter(action => props[action] !== undefined);
if (providedActions.length > 1) {
throw new Error(`'${providedActions}' specified together, specify only one`);
throw new ValidationError(`'${providedActions}' specified together, specify only one`, this);
}

if (!cdk.Token.isUnresolved(props.priority) && props.priority <= 0) {
throw new Error('Priority must have value greater than or equal to 1');
throw new ValidationError('Priority must have value greater than or equal to 1', this);
}

this.listener = props.listener;
Expand All @@ -244,7 +245,7 @@ export class ApplicationListenerRule extends Construct {

if (hasPathPatterns) {
if (props.pathPattern && props.pathPatterns) {
throw new Error('Both `pathPatterns` and `pathPattern` are specified, specify only one');
throw new ValidationError('Both `pathPatterns` and `pathPattern` are specified, specify only one', this);
}
const pathPattern = props.pathPattern ? [props.pathPattern] : props.pathPatterns;
this.setCondition('path-pattern', pathPattern);
Expand Down Expand Up @@ -393,11 +394,11 @@ export class ApplicationListenerRule extends Construct {
*/
function validateFixedResponse(fixedResponse: FixedResponse) {
if (fixedResponse.statusCode && !/^(2|4|5)\d\d$/.test(fixedResponse.statusCode)) {
throw new Error('`statusCode` must be 2XX, 4XX or 5XX.');
throw new UnscopedValidationError('`statusCode` must be 2XX, 4XX or 5XX.');
}

if (fixedResponse.messageBody && fixedResponse.messageBody.length > 1024) {
throw new Error('`messageBody` cannot have more than 1024 characters.');
throw new UnscopedValidationError('`messageBody` cannot have more than 1024 characters.');
}
}

Expand All @@ -408,10 +409,10 @@ function validateFixedResponse(fixedResponse: FixedResponse) {
*/
function validateRedirectResponse(redirectResponse: RedirectResponse) {
if (redirectResponse.protocol && !/^(HTTPS?|#\{protocol\})$/i.test(redirectResponse.protocol)) {
throw new Error('`protocol` must be HTTP, HTTPS, or #{protocol}.');
throw new UnscopedValidationError('`protocol` must be HTTP, HTTPS, or #{protocol}.');
}

if (!redirectResponse.statusCode || !/^HTTP_30[12]$/.test(redirectResponse.statusCode)) {
throw new Error('`statusCode` must be HTTP_301 or HTTP_302.');
throw new UnscopedValidationError('`statusCode` must be HTTP_301 or HTTP_302.');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ITrustStore } from './trust-store';
import * as ec2 from '../../../aws-ec2';
import * as cxschema from '../../../cloud-assembly-schema';
import { Duration, FeatureFlags, Lazy, Resource, Token } from '../../../core';
import { ValidationError } from '../../../core/lib/errors';
import * as cxapi from '../../../cx-api';
import { BaseListener, BaseListenerLookupOptions, IListener } from '../shared/base-listener';
import { HealthCheck } from '../shared/base-target-group';
Expand Down Expand Up @@ -197,7 +198,7 @@ export class ApplicationListener extends BaseListener implements IApplicationLis
*/
public static fromLookup(scope: Construct, id: string, options: ApplicationListenerLookupOptions): IApplicationListener {
if (Token.isUnresolved(options.listenerArn)) {
throw new Error('All arguments to look up a load balancer listener must be concrete (no Tokens)');
throw new ValidationError('All arguments to look up a load balancer listener must be concrete (no Tokens)', scope);
}

let listenerProtocol: cxschema.LoadBalancerListenerProtocol | undefined;
Expand Down Expand Up @@ -251,10 +252,10 @@ export class ApplicationListener extends BaseListener implements IApplicationLis
constructor(scope: Construct, id: string, props: ApplicationListenerProps) {
const [protocol, port] = determineProtocolAndPort(props.protocol, props.port);
if (protocol === undefined || port === undefined) {
throw new Error('At least one of \'port\' or \'protocol\' is required');
throw new ValidationError('At least one of \'port\' or \'protocol\' is required', scope);
}

validateMutualAuthentication(props.mutualAuthentication);
validateMutualAuthentication(scope, props.mutualAuthentication);

super(scope, id, {
loadBalancerArn: props.loadBalancer.loadBalancerArn,
Expand Down Expand Up @@ -290,7 +291,7 @@ export class ApplicationListener extends BaseListener implements IApplicationLis
});

if (props.defaultAction && props.defaultTargetGroups) {
throw new Error('Specify at most one of \'defaultAction\' and \'defaultTargetGroups\'');
throw new ValidationError('Specify at most one of \'defaultAction\' and \'defaultTargetGroups\'', this);
}

if (props.defaultAction) {
Expand Down Expand Up @@ -361,7 +362,7 @@ export class ApplicationListener extends BaseListener implements IApplicationLis
* default Action).
*/
public addAction(id: string, props: AddApplicationActionProps): void {
checkAddRuleProps(props);
checkAddRuleProps(this, props);

if (props.priority !== undefined) {
// New rule
Expand Down Expand Up @@ -389,7 +390,7 @@ export class ApplicationListener extends BaseListener implements IApplicationLis
* become the default Action for this listener).
*/
public addTargetGroups(id: string, props: AddApplicationTargetGroupsProps): void {
checkAddRuleProps(props);
checkAddRuleProps(this, props);

if (props.priority !== undefined) {
// New rule
Expand Down Expand Up @@ -423,7 +424,7 @@ export class ApplicationListener extends BaseListener implements IApplicationLis
public addTargets(id: string, props: AddApplicationTargetsProps): ApplicationTargetGroup {
if (!this.loadBalancer.vpc) {
// eslint-disable-next-line max-len
throw new Error('Can only call addTargets() when using a constructed Load Balancer or an imported Load Balancer with specified vpc; construct a new TargetGroup and use addTargetGroup');
throw new ValidationError('Can only call addTargets() when using a constructed Load Balancer or an imported Load Balancer with specified vpc; construct a new TargetGroup and use addTargetGroup', this);
}

const group = new ApplicationTargetGroup(this, id + 'Group', {
Expand All @@ -445,7 +446,7 @@ export class ApplicationListener extends BaseListener implements IApplicationLis
* @deprecated Use `addAction()` instead
*/
public addFixedResponse(id: string, props: AddFixedResponseProps) {
checkAddRuleProps(props);
checkAddRuleProps(this, props);

const fixedResponse: FixedResponse = {
statusCode: props.statusCode,
Expand All @@ -459,11 +460,11 @@ export class ApplicationListener extends BaseListener implements IApplicationLis
* Inlining the duplication functionality in v2 only (for now).
*/
if (fixedResponse.statusCode && !/^(2|4|5)\d\d$/.test(fixedResponse.statusCode)) {
throw new Error('`statusCode` must be 2XX, 4XX or 5XX.');
throw new ValidationError('`statusCode` must be 2XX, 4XX or 5XX.', this);
}

if (fixedResponse.messageBody && fixedResponse.messageBody.length > 1024) {
throw new Error('`messageBody` cannot have more than 1024 characters.');
throw new ValidationError('`messageBody` cannot have more than 1024 characters.', this);
}

if (props.priority) {
Expand All @@ -487,7 +488,7 @@ export class ApplicationListener extends BaseListener implements IApplicationLis
* @deprecated Use `addAction()` instead
*/
public addRedirectResponse(id: string, props: AddRedirectResponseProps) {
checkAddRuleProps(props);
checkAddRuleProps(this, props);
const redirectResponse = {
host: props.host,
path: props.path,
Expand All @@ -503,11 +504,11 @@ export class ApplicationListener extends BaseListener implements IApplicationLis
* Inlining the duplication functionality in v2 only (for now).
*/
if (redirectResponse.protocol && !/^(HTTPS?|#\{protocol\})$/i.test(redirectResponse.protocol)) {
throw new Error('`protocol` must be HTTP, HTTPS, or #{protocol}.');
throw new ValidationError('`protocol` must be HTTP, HTTPS, or #{protocol}.', this);
}

if (!redirectResponse.statusCode || !/^HTTP_30[12]$/.test(redirectResponse.statusCode)) {
throw new Error('`statusCode` must be HTTP_301 or HTTP_302.');
throw new ValidationError('`statusCode` must be HTTP_301 or HTTP_302.', this);
}

if (props.priority) {
Expand Down Expand Up @@ -698,7 +699,7 @@ abstract class ExternalApplicationListener extends Resource implements IApplicat
* At least one TargetGroup must be added without conditions.
*/
public addTargetGroups(id: string, props: AddApplicationTargetGroupsProps): void {
checkAddRuleProps(props);
checkAddRuleProps(this, props);

if (props.priority !== undefined) {
// New rule
Expand All @@ -708,7 +709,7 @@ abstract class ExternalApplicationListener extends Resource implements IApplicat
...props,
});
} else {
throw new Error('Cannot add default Target Groups to imported ApplicationListener');
throw new ValidationError('Cannot add default Target Groups to imported ApplicationListener', this);
}
}

Expand All @@ -725,7 +726,7 @@ abstract class ExternalApplicationListener extends Resource implements IApplicat
*/
public addTargets(_id: string, _props: AddApplicationTargetsProps): ApplicationTargetGroup {
// eslint-disable-next-line max-len
throw new Error('Can only call addTargets() when using a constructed ApplicationListener; construct a new TargetGroup and use addTargetGroup.');
throw new ValidationError('Can only call addTargets() when using a constructed ApplicationListener; construct a new TargetGroup and use addTargetGroup.', this);
}

/**
Expand All @@ -747,7 +748,7 @@ abstract class ExternalApplicationListener extends Resource implements IApplicat
* property here to avoid having CloudFormation attempt to replace your resource.
*/
public addAction(id: string, props: AddApplicationActionProps): void {
checkAddRuleProps(props);
checkAddRuleProps(this, props);

if (props.priority !== undefined) {
const ruleId = props.removeSuffix ? id : id + 'Rule';
Expand All @@ -760,7 +761,7 @@ abstract class ExternalApplicationListener extends Resource implements IApplicat
...props,
});
} else {
throw new Error('priority must be set for actions added to an imported listener');
throw new ValidationError('priority must be set for actions added to an imported listener', this);
}
}
}
Expand Down Expand Up @@ -1036,17 +1037,17 @@ export interface AddFixedResponseProps extends AddRuleProps, FixedResponse {
export interface AddRedirectResponseProps extends AddRuleProps, RedirectResponse {
}

function checkAddRuleProps(props: AddRuleProps) {
function checkAddRuleProps(scope: Construct, props: AddRuleProps) {
const conditionsCount = props.conditions?.length || 0;
const hasAnyConditions = conditionsCount !== 0 ||
props.hostHeader !== undefined || props.pathPattern !== undefined || props.pathPatterns !== undefined;
const hasPriority = props.priority !== undefined;
if (hasAnyConditions !== hasPriority) {
throw new Error('Setting \'conditions\', \'pathPattern\' or \'hostHeader\' also requires \'priority\', and vice versa');
throw new ValidationError('Setting \'conditions\', \'pathPattern\' or \'hostHeader\' also requires \'priority\', and vice versa', scope);
}
}

function validateMutualAuthentication(mutualAuthentication?: MutualAuthentication): void {
function validateMutualAuthentication(scope: Construct, mutualAuthentication?: MutualAuthentication): void {
if (!mutualAuthentication) {
return;
}
Expand All @@ -1055,17 +1056,17 @@ function validateMutualAuthentication(mutualAuthentication?: MutualAuthenticatio

if (currentMode === MutualAuthenticationMode.VERIFY) {
if (!mutualAuthentication.trustStore) {
throw new Error(`You must set 'trustStore' when 'mode' is '${MutualAuthenticationMode.VERIFY}'`);
throw new ValidationError(`You must set 'trustStore' when 'mode' is '${MutualAuthenticationMode.VERIFY}'`, scope);
}
}

if (currentMode === MutualAuthenticationMode.OFF || currentMode === MutualAuthenticationMode.PASS_THROUGH) {
if (mutualAuthentication.trustStore) {
throw new Error(`You cannot set 'trustStore' when 'mode' is '${MutualAuthenticationMode.OFF}' or '${MutualAuthenticationMode.PASS_THROUGH}'`);
throw new ValidationError(`You cannot set 'trustStore' when 'mode' is '${MutualAuthenticationMode.OFF}' or '${MutualAuthenticationMode.PASS_THROUGH}'`, scope);
}

if (mutualAuthentication.ignoreClientCertificateExpiry !== undefined) {
throw new Error(`You cannot set 'ignoreClientCertificateExpiry' when 'mode' is '${MutualAuthenticationMode.OFF}' or '${MutualAuthenticationMode.PASS_THROUGH}'`);
throw new ValidationError(`You cannot set 'ignoreClientCertificateExpiry' when 'mode' is '${MutualAuthenticationMode.OFF}' or '${MutualAuthenticationMode.PASS_THROUGH}'`, scope);
}
}
}
Loading

0 comments on commit cf63184

Please sign in to comment.