Skip to content

Commit

Permalink
Add support for HTTP and WebSocket Domain Names
Browse files Browse the repository at this point in the history
  • Loading branch information
Jason Venable authored and tehnrd committed Mar 27, 2020
1 parent 633a506 commit 626965f
Show file tree
Hide file tree
Showing 9 changed files with 1,055 additions and 461 deletions.
98 changes: 98 additions & 0 deletions DomainConfig.ts
@@ -0,0 +1,98 @@
/**
* Wrapper class for Custom Domain information
*/

import Globals from "./Globals";
import DomainInfo = require("./DomainInfo");

class DomainConfig {

public givenDomainName: string;
public basePath: string | undefined;
public stage: string | undefined;
public certificateName: string | undefined;
public certificateArn: string | undefined;
public createRoute53Record: boolean | undefined;
public endpointType: string | undefined;
public apiType: string | undefined;
public hostedZoneId: string | undefined;
public hostedZonePrivate: boolean | undefined;
public enabled: boolean | string | undefined;
public securityPolicy: string | undefined;

public apiId: string | undefined;
public domainInfo: DomainInfo | undefined;
public currentBasePath: string | undefined;
public apiMappingId: string | undefined;

constructor(config: any) {

this.enabled = this.evaluateEnabled(config.enabled);
this.givenDomainName = config.domainName;
this.hostedZonePrivate = config.hostedZonePrivate;
this.certificateArn = config.certificateArn;
this.certificateName = config.certificateName;
this.createRoute53Record = config.createRoute53Record;
this.hostedZoneId = config.hostedZoneId;
this.hostedZonePrivate = config.hostedZonePrivate;

let basePath = config.basePath;
if (basePath == null || basePath.trim() === "") {
basePath = "(none)";
}
this.basePath = basePath;

let stage = config.stage;
if (typeof stage === "undefined") {
stage = Globals.options.stage || Globals.serverless.service.provider.stage;
}
this.stage = stage;

const endpointTypeWithDefault = config.endpointType || Globals.endpointTypes.edge;
const endpointTypeToUse = Globals.endpointTypes[endpointTypeWithDefault.toLowerCase()];
if (!endpointTypeToUse) {
throw new Error(`${endpointTypeWithDefault} is not supported endpointType, use edge or regional.`);
}
this.endpointType = endpointTypeToUse;

const apiTypeWithDefault = config.apiType || Globals.apiTypes.rest;
const apiTypeToUse = Globals.apiTypes[apiTypeWithDefault.toLowerCase()];
if (!apiTypeToUse) {
throw new Error(`${apiTypeWithDefault} is not supported api type, use REST, HTTP or WEBSOCKET.`);
}
this.apiType = apiTypeToUse;

const securityPolicyDefault = config.securityPolicy || Globals.tlsVersions.tls_1_2;
const tlsVersionToUse = Globals.tlsVersions[securityPolicyDefault.toLowerCase()];
if (!tlsVersionToUse) {
throw new Error(`${securityPolicyDefault} is not a supported securityPolicy, use tls_1_0 or tls_1_2.`);
}
this.securityPolicy = tlsVersionToUse;
}

/**
* Determines whether this plug-in is enabled.
*
* This method reads the customDomain property "enabled" to see if this plug-in should be enabled.
* If the property's value is undefined, a default value of true is assumed (for backwards
* compatibility).
* If the property's value is provided, this should be boolean, otherwise an exception is thrown.
* If no customDomain object exists, an exception is thrown.
*/
public evaluateEnabled(enabled: any): boolean {
// const enabled = this.serverless.service.custom.customDomain.enabled;
if (enabled === undefined) {
return true;
}
if (typeof enabled === "boolean") {
return enabled;
} else if (typeof enabled === "string" && enabled === "true") {
return true;
} else if (typeof enabled === "string" && enabled === "false") {
return false;
}
throw new Error(`serverless-domain-manager: Ambiguous enablement boolean: "${enabled}"`);
}
}

export = DomainConfig;
18 changes: 13 additions & 5 deletions DomainInfo.ts
Expand Up @@ -18,11 +18,19 @@ class DomainInfo {
private defaultSecurityPolicy: string = "TLS_1_2";

constructor(data: any) {
this.domainName = data.distributionDomainName || data.regionalDomainName;
this.hostedZoneId = data.distributionHostedZoneId ||
data.regionalHostedZoneId ||
this.defaultHostedZoneId;
this.securityPolicy = data.securityPolicy || this.defaultSecurityPolicy;
this.domainName = data.distributionDomainName
|| data.regionalDomainName
|| data.DomainNameConfigurations && data.DomainNameConfigurations[0].ApiGatewayDomainName
|| data.DomainName;

this.hostedZoneId = data.distributionHostedZoneId
|| data.regionalHostedZoneId
|| data.DomainNameConfigurations && data.DomainNameConfigurations[0].HostedZoneId
|| this.defaultHostedZoneId;

this.securityPolicy = data.securityPolicy
|| data.DomainNameConfigurations && data.DomainNameConfigurations[0].SecurityPolicy
|| this.defaultSecurityPolicy;
}
}

Expand Down
23 changes: 23 additions & 0 deletions Globals.ts
@@ -0,0 +1,23 @@
import { ServerlessInstance, ServerlessOptions } from "./types";

export default class Globals {

public static serverless: ServerlessInstance;
public static options: ServerlessOptions;

public static endpointTypes = {
edge: "EDGE",
regional: "REGIONAL",
};

public static apiTypes = {
http: "HTTP",
rest: "REST",
websocket: "WEBSOCKET",
};

public static tlsVersions = {
tls_1_0: "TLS_1_0",
tls_1_2: "TLS_1_2",
};
}
35 changes: 34 additions & 1 deletion README.md
Expand Up @@ -58,7 +58,7 @@ plugins:
- serverless-domain-manager
```

Add the plugin configuration (example for `serverless.foo.com/api`).
Add the plugin configuration (example for `serverless.foo.com/api`). For a single domain and API type the following sturcture can be used.

```yaml
custom:
Expand All @@ -70,6 +70,38 @@ custom:
createRoute53Record: true
endpointType: 'regional'
securityPolicy: tls_1_2
apiType: rest
```

Multiple API types mapped to different domains can also be supported with the follow structure. The key is the API Gateway API type.

```yaml
custom:
customDomain:
rest:
domainName: rest.serverless.foo.com
stage: ci
basePath: api
certificateName: '*.foo.com'
createRoute53Record: true
endpointType: 'regional'
securityPolicy: tls_1_2
http:
domainName: http.serverless.foo.com
stage: ci
basePath: api
certificateName: '*.foo.com'
createRoute53Record: true
endpointType: 'regional'
securityPolicy: tls_1_2
websocket:
domainName: ws.serverless.foo.com
stage: ci
basePath: api
certificateName: '*.foo.com'
createRoute53Record: true
endpointType: 'regional'
securityPolicy: tls_1_2
```

| Parameter Name | Default Value | Description |
Expand All @@ -81,6 +113,7 @@ custom:
| certificateArn | `(none)` | The arn of a specific certificate from Certificate Manager to use with this API. |
| createRoute53Record | `true` | Toggles whether or not the plugin will create an A Alias and AAAA Alias records in Route53 mapping the `domainName` to the generated distribution domain name. If false, does not create a record. |
| endpointType | edge | Defines the endpoint type, accepts `regional` or `edge`. |
| apiType | rest | Defines the api type, accepts `rest`, `http` or `websocket`. |
| hostedZoneId | | If hostedZoneId is set the route53 record set will be created in the matching zone, otherwise the hosted zone will be figured out from the domainName (hosted zone with matching domain). |
| hostedZonePrivate | | If hostedZonePrivate is set to `true` then only private hosted zones will be used for route 53 records. If it is set to `false` then only public hosted zones will be used for route53 records. Setting this parameter is specially useful if you have multiple hosted zones with the same domain name (e.g. a public and a private one) |
| enabled | true | Sometimes there are stages for which is not desired to have custom domain names. This flag allows the developer to disable the plugin for such cases. Accepts either `boolean` or `string` values and defaults to `true` for backwards compatibility. |
Expand Down

0 comments on commit 626965f

Please sign in to comment.