Skip to content

Commit d867878

Browse files
authored
feat(location): support L2 API Key Construct (#32733)
### Issue # (if applicable) Closes #30684. ### Reason for this change To support Amazon Location API Key. ### Description of changes Add `ApiKey` construct. ### Describe any new or updated permissions being added API key includes allowed actions for AWS managed resources. ### Description of how you validated changes Add unit tests and an integ test. ### Checklist - [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 1cac5a0 commit d867878

13 files changed

+1540
-0
lines changed

packages/@aws-cdk/aws-location-alpha/README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,32 @@ const tracker = new location.Tracker(this, 'Tracker', {
9797
tracker.addGeofenceCollections(geofenceCollectionForAdd);
9898
```
9999

100+
## API key
101+
102+
API keys are a key value that is associated with specific Amazon Location Service resources or API in your AWS account, and specific actions that you can perform on those resources.
103+
You can use an API key in your application to make unauthenticated calls to the Amazon Location APIs for those resources.
104+
105+
For more information, see [Use API keys to authenticate](https://docs.aws.amazon.com/location/latest/developerguide/using-apikeys.html).
106+
107+
To create an API key, define an `ApiKey`:
108+
109+
```ts
110+
new location.ApiKey(this, 'APIKeyAny', {
111+
// specify allowed actions
112+
allowMapsActions: [
113+
location.AllowMapsAction.GET_STATIC_MAP,
114+
],
115+
allowPlacesActions: [
116+
location.AllowPlacesAction.GET_PLACE,
117+
],
118+
allowRoutesActions: [
119+
location.AllowRoutesAction.CALCULATE_ISOLINES,
120+
],
121+
});
122+
```
123+
124+
> Note: `ApiKey` construct only supports [Enhanced Places, Routes, and Maps](https://aws.amazon.com/blogs/aws/announcing-new-apis-for-amazon-location-service-routes-places-and-maps/) This API key grants access to AWS-managed Places, Routes, and Maps.
125+
100126
## Legacy Resources
101127

102128
AWS has released new [Enhanced Places, Routes, and Maps](https://aws.amazon.com/about-aws/whats-new/2024/11/amazon-location-service-enhanced-places-routes-maps/?nc1=h_ls). Since these use AWS-managed resources, users no longer need to create Maps, Places, and Routes resources themselves.
Lines changed: 359 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,359 @@
1+
import { ArnFormat, Aws, IResource, Lazy, Resource, Stack, Token, UnscopedValidationError, ValidationError } from 'aws-cdk-lib/core';
2+
import { Construct } from 'constructs';
3+
import { CfnAPIKey } from 'aws-cdk-lib/aws-location';
4+
import { generateUniqueId } from './util';
5+
6+
/**
7+
* An API Key
8+
*/
9+
export interface IApiKey extends IResource {
10+
/**
11+
* The name of the api key
12+
*
13+
* @attribute
14+
*/
15+
readonly apiKeyName: string;
16+
17+
/**
18+
* The Amazon Resource Name (ARN) of the api key resource
19+
*
20+
* @attribute Arn, apiKeyArn
21+
*/
22+
readonly apiKeyArn: string;
23+
}
24+
25+
/**
26+
* Properties for an API Key
27+
*/
28+
export interface ApiKeyProps {
29+
/**
30+
* A name for the api key
31+
*
32+
* Must be between 1 and 100 characters and contain only alphanumeric characters,
33+
* hyphens, periods and underscores.
34+
*
35+
* Must be a unique API key name.
36+
*
37+
* @default - A name is automatically generated
38+
*/
39+
readonly apiKeyName?: string;
40+
41+
/**
42+
* A description for the api key
43+
*
44+
* @default - no description
45+
*/
46+
readonly description?: string;
47+
48+
/**
49+
* The optional timestamp for when the API key resource will expire
50+
*
51+
* `expireTime` must be set when `noExpiry` is false or undefined.
52+
* When `expireTime` is not set, `noExpiry` must be `true`.
53+
*
54+
* @default undefined - The API Key never expires
55+
*/
56+
readonly expireTime?: Date;
57+
58+
/**
59+
* `forceDelete` bypasses an API key's expiry conditions and deletes the key.
60+
* Set the parameter true to delete the key or to false to not preemptively delete the API key.
61+
*
62+
* @default undefined - not force delete
63+
*/
64+
readonly forceDelete?: boolean;
65+
66+
/**
67+
* The boolean flag to be included for updating ExpireTime or Restrictions details.
68+
* Must be set to true to update an API key resource that has been used in the past 7 days.
69+
* False if force update is not preferred.
70+
*
71+
* @default undefined - not force update
72+
*/
73+
readonly forceUpdate?: boolean;
74+
75+
/**
76+
* Whether the API key should expire.
77+
*
78+
* Set to `true` when `expireTime` is not set.
79+
* When you set `expireTime`, `noExpiry` must be `false` or `undefined`.
80+
*
81+
* @default undefined - The API Key expires at `expireTime`
82+
*/
83+
readonly noExpiry?: boolean;
84+
85+
/**
86+
* A list of allowed actions for Maps that an API key resource grants permissions to perform.
87+
*
88+
* @default - no actions for Maps are permitted
89+
*/
90+
readonly allowMapsActions?: AllowMapsAction[];
91+
92+
/**
93+
* A list of allowed actions for Places that an API key resource grants permissions to perform.
94+
*
95+
* @default - no actions for Places are permitted
96+
*/
97+
readonly allowPlacesActions?: AllowPlacesAction[];
98+
99+
/**
100+
* A list of allowed actions for Routes that an API key resource grants permissions to perform.
101+
*
102+
* @default - no actions for Routes are permitted
103+
*/
104+
readonly allowRoutesActions?: AllowRoutesAction[];
105+
106+
/**
107+
* An optional list of allowed HTTP referers for which requests must originate from.
108+
* Requests using this API key from other domains will not be allowed.
109+
*
110+
* @see https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-location-apikey-apikeyrestrictions.html#cfn-location-apikey-apikeyrestrictions-allowreferers
111+
* @default - no Referer
112+
*/
113+
readonly allowReferers?: string[];
114+
}
115+
116+
/**
117+
* Actions for Maps that an API key resource grants permissions to perform.
118+
*
119+
* @see https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazonlocationservicemaps.html
120+
*/
121+
export enum AllowMapsAction {
122+
/**
123+
* Allows getting static map images.
124+
*/
125+
GET_STATIC_MAP = 'geo-maps:GetStaticMap',
126+
127+
/**
128+
* Allows getting map tiles for rendering.
129+
*/
130+
GET_TILE = 'geo-maps:GetTile',
131+
132+
/**
133+
* Allows any maps actions.
134+
*/
135+
ANY = 'geo-maps:*',
136+
}
137+
138+
/**
139+
* Actions for Places that an API key resource grants permissions to perform.
140+
*
141+
* @see https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazonlocationserviceplaces.html
142+
*/
143+
export enum AllowPlacesAction {
144+
/**
145+
* Allows auto-completion of search text.
146+
*/
147+
AUTOCOMPLETE = 'geo-places:Autocomplete',
148+
149+
/**
150+
* Allows finding geo coordinates of a known place.
151+
*/
152+
GEOCODE = 'geo-places:Geocode',
153+
154+
/**
155+
* Allows getting details of a place.
156+
*/
157+
GET_PLACE = 'geo-places:GetPlace',
158+
159+
/**
160+
* Allows getting nearest address to geo coordinates.
161+
*/
162+
REVERSE_GEOCODE = 'geo-places:ReverseGeocode',
163+
164+
/**
165+
* Allows category based places search around geo coordinates.
166+
*/
167+
SEARCH_NEARBY = 'geo-places:SearchNearby',
168+
169+
/**
170+
* Allows place or address search based on free-form text.
171+
*/
172+
SEARCH_TEXT = 'geo-places:SearchText',
173+
174+
/**
175+
* Allows suggestions based on an incomplete or misspelled query.
176+
*/
177+
SUGGEST = 'geo-places:Suggest',
178+
179+
/**
180+
* Allows any places actions.
181+
*/
182+
ANY = 'geo-places:*',
183+
}
184+
185+
/**
186+
* Actions for Routes that an API key resource grants permissions to perform.
187+
*
188+
* @see https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazonlocationserviceroutes.html
189+
*/
190+
export enum AllowRoutesAction {
191+
/**
192+
* Allows isoline calculation.
193+
*/
194+
CALCULATE_ISOLINES = 'geo-routes:CalculateIsolines',
195+
196+
/**
197+
* Allows point to point routing.
198+
*/
199+
CALCULATE_ROUTES = 'geo-routes:CalculateRoutes',
200+
201+
/**
202+
* Allows matrix routing.
203+
*/
204+
CALCULATE_ROUTE_MATRIX = 'geo-routes:CalculateRouteMatrix',
205+
206+
/**
207+
* Allows computing the best sequence of waypoints.
208+
*/
209+
OPTIMIZE_WAYPOINTS = 'geo-routes:OptimizeWaypoints',
210+
211+
/**
212+
* Allows snapping GPS points to a likely route.
213+
*/
214+
SNAP_TO_ROADS = 'geo-routes:SnapToRoads',
215+
216+
/**
217+
* Allows any routes actions.
218+
*/
219+
ANY = 'geo-routes:*',
220+
}
221+
222+
/**
223+
* An API Key
224+
*
225+
* @see https://docs.aws.amazon.com/location/latest/developerguide/using-apikeys.html
226+
*/
227+
export class ApiKey extends Resource implements IApiKey {
228+
/**
229+
* Use an existing api key by name
230+
*/
231+
public static fromApiKeyName(scope: Construct, id: string, apiKeyName: string): IApiKey {
232+
const apiKeyArn = Stack.of(scope).formatArn({
233+
service: 'geo',
234+
resource: 'api-key',
235+
resourceName: apiKeyName,
236+
});
237+
238+
return ApiKey.fromApiKeyArn(scope, id, apiKeyArn);
239+
}
240+
241+
/**
242+
* Use an existing api key by ARN
243+
*/
244+
public static fromApiKeyArn(scope: Construct, id: string, apiKeyArn: string): IApiKey {
245+
const parsedArn = Stack.of(scope).splitArn(apiKeyArn, ArnFormat.SLASH_RESOURCE_NAME);
246+
247+
if (!parsedArn.resourceName) {
248+
throw new UnscopedValidationError(`API Key Arn ${apiKeyArn} does not have a resource name.`);
249+
}
250+
251+
class Import extends Resource implements IApiKey {
252+
public readonly apiKeyName = parsedArn.resourceName!;
253+
public readonly apiKeyArn = apiKeyArn;
254+
}
255+
256+
return new Import(scope, id, {
257+
account: parsedArn.account,
258+
region: parsedArn.region,
259+
});
260+
}
261+
262+
public readonly apiKeyName: string;
263+
264+
public readonly apiKeyArn: string;
265+
266+
/**
267+
* The timestamp for when the api key resource was created in ISO 8601 format
268+
*
269+
* @attribute
270+
*/
271+
public readonly apiKeyCreateTime: string;
272+
273+
/**
274+
* The timestamp for when the api key resource was last updated in ISO 8601 format
275+
*
276+
* @attribute
277+
*/
278+
public readonly apiKeyUpdateTime: string;
279+
280+
constructor(scope: Construct, id: string, props: ApiKeyProps = {}) {
281+
super(scope, id, {
282+
physicalName: props.apiKeyName ?? Lazy.string({ produce: () => generateUniqueId(this) }),
283+
});
284+
285+
if (props.description && !Token.isUnresolved(props.description) && props.description.length > 1000) {
286+
throw new ValidationError(`\`description\` must be between 0 and 1000 characters. Received: ${props.description.length} characters`, this);
287+
}
288+
289+
if (props.apiKeyName !== undefined && !Token.isUnresolved(props.apiKeyName)) {
290+
if (props.apiKeyName.length < 1 || props.apiKeyName.length > 100) {
291+
throw new ValidationError(`\`apiKeyName\` must be between 1 and 100 characters, got: ${props.apiKeyName.length} characters.`, this);
292+
}
293+
294+
if (!/^[-._\w]+$/.test(props.apiKeyName)) {
295+
throw new ValidationError(`\`apiKeyName\` must contain only alphanumeric characters, hyphens, periods and underscores, got: ${props.apiKeyName}.`, this);
296+
}
297+
}
298+
299+
if (props.expireTime !== undefined && props.noExpiry === true) {
300+
throw new ValidationError('`expireTime` must not be set when `noExpiry` has value true.', this);
301+
}
302+
303+
if (props.expireTime === undefined && props.noExpiry !== true) {
304+
throw new ValidationError('`expireTime` must be set when `noExpiry` is false or undefined.', this);
305+
}
306+
307+
if (!props.allowMapsActions && !props.allowPlacesActions && !props.allowRoutesActions) {
308+
throw new ValidationError('At least one of `allowMapsActions`, `allowPlacesActions`, or `allowRoutesActions` must be specified.', this);
309+
}
310+
311+
if (props.allowReferers !== undefined &&
312+
(props.allowReferers.length < 1 || props.allowReferers.length > 5)) {
313+
throw new ValidationError(`\`allowReferers\` must be between 1 and 5 elements, got: ${props.allowReferers.length} elements.`, this);
314+
}
315+
316+
const apiKey = new CfnAPIKey(this, 'Resource', {
317+
keyName: this.physicalName,
318+
description: props.description,
319+
expireTime: props.expireTime?.toISOString(),
320+
forceDelete: props.forceDelete,
321+
forceUpdate: props.forceUpdate,
322+
noExpiry: props.noExpiry,
323+
restrictions: this._renderRestrictions(props),
324+
});
325+
326+
this.apiKeyName = apiKey.ref;
327+
this.apiKeyArn = apiKey.attrArn;
328+
this.apiKeyCreateTime = apiKey.attrCreateTime;
329+
this.apiKeyUpdateTime = apiKey.attrUpdateTime;
330+
}
331+
332+
/**
333+
* Renders the restrictions property for API Keys
334+
*
335+
* NOTE: added allowResources are AWS managed resources.
336+
*/
337+
private _renderRestrictions(props: ApiKeyProps): CfnAPIKey.ApiKeyRestrictionsProperty {
338+
let allowResources = [];
339+
if (props.allowMapsActions) {
340+
allowResources.push(`arn:${Aws.PARTITION}:geo-maps:${Stack.of(this).region}::provider/default`);
341+
}
342+
if (props.allowPlacesActions) {
343+
allowResources.push(`arn:${Aws.PARTITION}:geo-places:${Stack.of(this).region}::provider/default`);
344+
}
345+
if (props.allowRoutesActions) {
346+
allowResources.push(`arn:${Aws.PARTITION}:geo-routes:${Stack.of(this).region}::provider/default`);
347+
}
348+
349+
return {
350+
allowActions: [
351+
...props.allowMapsActions ?? [],
352+
...props.allowPlacesActions ?? [],
353+
...props.allowRoutesActions ?? [],
354+
].map((action) => action.toString()),
355+
allowReferers: props.allowReferers,
356+
allowResources,
357+
};
358+
}
359+
}

packages/@aws-cdk/aws-location-alpha/lib/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export * from './api-key';
12
export * from './geofence-collection';
23
export * from './map';
34
export * from './place-index';

0 commit comments

Comments
 (0)