Skip to content

Commit ca3862d

Browse files
committed
add apigateway authorizer adapter
1 parent a5b9e53 commit ca3862d

File tree

7 files changed

+315
-0
lines changed

7 files changed

+315
-0
lines changed

adapters/apigateway-authorizer.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package adapters
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/aws/aws-sdk-go-v2/service/apigateway"
9+
"github.com/aws/aws-sdk-go-v2/service/apigateway/types"
10+
"github.com/overmindtech/aws-source/adapterhelpers"
11+
"github.com/overmindtech/sdp-go"
12+
)
13+
14+
// convertGetAuthorizerOutputToAuthorizer converts a GetAuthorizerOutput to an Authorizer
15+
func convertGetAuthorizerOutputToAuthorizer(output *apigateway.GetAuthorizerOutput) *types.Authorizer {
16+
return &types.Authorizer{
17+
Id: output.Id,
18+
Name: output.Name,
19+
Type: output.Type,
20+
ProviderARNs: output.ProviderARNs,
21+
AuthType: output.AuthType,
22+
AuthorizerUri: output.AuthorizerUri,
23+
AuthorizerCredentials: output.AuthorizerCredentials,
24+
IdentitySource: output.IdentitySource,
25+
IdentityValidationExpression: output.IdentityValidationExpression,
26+
AuthorizerResultTtlInSeconds: output.AuthorizerResultTtlInSeconds,
27+
}
28+
}
29+
30+
func authorizerOutputMapper(scope string, awsItem *types.Authorizer) (*sdp.Item, error) {
31+
attributes, err := adapterhelpers.ToAttributesWithExclude(awsItem, "tags")
32+
if err != nil {
33+
return nil, err
34+
}
35+
36+
item := sdp.Item{
37+
Type: "apigateway-authorizer",
38+
UniqueAttribute: "Id",
39+
Attributes: attributes,
40+
Scope: scope,
41+
}
42+
43+
return &item, nil
44+
}
45+
46+
func NewAPIGatewayAuthorizerAdapter(client *apigateway.Client, accountID string, region string) *adapterhelpers.GetListAdapter[*types.Authorizer, *apigateway.Client, *apigateway.Options] {
47+
return &adapterhelpers.GetListAdapter[*types.Authorizer, *apigateway.Client, *apigateway.Options]{
48+
ItemType: "apigateway-authorizer",
49+
Client: client,
50+
AccountID: accountID,
51+
Region: region,
52+
AdapterMetadata: authorizerAdapterMetadata,
53+
GetFunc: func(ctx context.Context, client *apigateway.Client, scope, query string) (*types.Authorizer, error) {
54+
f := strings.Split(query, "/")
55+
if len(f) != 2 {
56+
return nil, &sdp.QueryError{
57+
ErrorType: sdp.QueryError_NOTFOUND,
58+
ErrorString: fmt.Sprintf("query must be in the format of: the rest-api-id/authorizer-id, but found: %s", query),
59+
}
60+
}
61+
out, err := client.GetAuthorizer(ctx, &apigateway.GetAuthorizerInput{
62+
RestApiId: &f[0],
63+
AuthorizerId: &f[1],
64+
})
65+
if err != nil {
66+
return nil, err
67+
}
68+
return convertGetAuthorizerOutputToAuthorizer(out), nil
69+
},
70+
DisableList: true,
71+
SearchFunc: func(ctx context.Context, client *apigateway.Client, scope string, query string) ([]*types.Authorizer, error) {
72+
f := strings.Split(query, "/")
73+
var restAPIID string
74+
var name string
75+
76+
switch len(f) {
77+
case 1:
78+
restAPIID = f[0]
79+
case 2:
80+
restAPIID = f[0]
81+
name = f[1]
82+
default:
83+
return nil, &sdp.QueryError{
84+
ErrorType: sdp.QueryError_NOTFOUND,
85+
ErrorString: fmt.Sprintf(
86+
"query must be in the format of: the rest-api-id/authorizer-id or rest-api-id, but found: %s",
87+
query,
88+
),
89+
}
90+
}
91+
92+
out, err := client.GetAuthorizers(ctx, &apigateway.GetAuthorizersInput{
93+
RestApiId: &restAPIID,
94+
})
95+
if err != nil {
96+
return nil, err
97+
}
98+
99+
var items []*types.Authorizer
100+
for _, authorizer := range out.Items {
101+
if name != "" && strings.Contains(*authorizer.Name, name) {
102+
items = append(items, &authorizer)
103+
} else {
104+
items = append(items, &authorizer)
105+
}
106+
}
107+
108+
return items, nil
109+
},
110+
ItemMapper: func(_, scope string, awsItem *types.Authorizer) (*sdp.Item, error) {
111+
return authorizerOutputMapper(scope, awsItem)
112+
},
113+
}
114+
}
115+
116+
var authorizerAdapterMetadata = Metadata.Register(&sdp.AdapterMetadata{
117+
Type: "apigateway-authorizer",
118+
DescriptiveName: "API Gateway Authorizer",
119+
Category: sdp.AdapterCategory_ADAPTER_CATEGORY_SECURITY,
120+
SupportedQueryMethods: &sdp.AdapterSupportedQueryMethods{
121+
Get: true,
122+
List: true,
123+
Search: true,
124+
GetDescription: "Get an API Gateway Authorizer by its rest API ID and ID: rest-api-id/authorizer-id",
125+
ListDescription: "List all API Gateway Authorizers",
126+
SearchDescription: "Search for API Gateway Authorizers by their rest API ID or with rest API ID and their name: rest-api-id/authorizer-name",
127+
},
128+
})
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package adapters
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/aws/aws-sdk-go-v2/aws"
8+
"github.com/aws/aws-sdk-go-v2/service/apigateway"
9+
"github.com/aws/aws-sdk-go-v2/service/apigateway/types"
10+
"github.com/overmindtech/aws-source/adapterhelpers"
11+
)
12+
13+
func TestAuthorizerOutputMapper(t *testing.T) {
14+
awsItem := &types.Authorizer{
15+
Id: aws.String("authorizer-id"),
16+
Name: aws.String("authorizer-name"),
17+
Type: types.AuthorizerTypeRequest,
18+
ProviderARNs: []string{"arn:aws:iam::123456789012:role/service-role"},
19+
AuthType: aws.String("custom"),
20+
AuthorizerUri: aws.String("arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:123456789012:function:my-function/invocations"),
21+
AuthorizerCredentials: aws.String("arn:aws:iam::123456789012:role/service-role"),
22+
IdentitySource: aws.String("method.request.header.Authorization"),
23+
IdentityValidationExpression: aws.String(".*"),
24+
AuthorizerResultTtlInSeconds: aws.Int32(300),
25+
}
26+
27+
item, err := authorizerOutputMapper("scope", awsItem)
28+
if err != nil {
29+
t.Fatalf("unexpected error: %v", err)
30+
}
31+
32+
if err := item.Validate(); err != nil {
33+
t.Error(err)
34+
}
35+
}
36+
37+
func TestNewAPIGatewayAuthorizerAdapter(t *testing.T) {
38+
config, account, region := adapterhelpers.GetAutoConfig(t)
39+
40+
client := apigateway.NewFromConfig(config)
41+
42+
adapter := NewAPIGatewayAuthorizerAdapter(client, account, region)
43+
44+
test := adapterhelpers.E2ETest{
45+
Adapter: adapter,
46+
Timeout: 10 * time.Second,
47+
SkipList: true,
48+
}
49+
50+
test.Run(t)
51+
}

adapters/integration/apigateway/apigateway_test.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,13 @@ func APIGateway(t *testing.T) {
7171
t.Fatalf("failed to validate APIGateway API key adapter: %v", err)
7272
}
7373

74+
authorizerSource := adapters.NewAPIGatewayAuthorizerAdapter(testClient, accountID, testAWSConfig.Region)
75+
76+
err = authorizerSource.Validate()
77+
if err != nil {
78+
t.Fatalf("failed to validate APIGateway authorizer adapter: %v", err)
79+
}
80+
7481
scope := adapterhelpers.FormatScope(accountID, testAWSConfig.Region)
7582

7683
// List restApis
@@ -301,5 +308,73 @@ func APIGateway(t *testing.T) {
301308
t.Fatalf("expected API key ID %s, got %s", apiKeyID, apiKeyIDFromSearch)
302309
}
303310

311+
// Search authorizers by restApiID
312+
authorizers, err := authorizerSource.Search(ctx, scope, restApiID, true)
313+
if err != nil {
314+
t.Fatalf("failed to search APIGateway authorizers: %v", err)
315+
}
316+
317+
authorizerUniqueAttribute := authorizers[0].GetUniqueAttribute()
318+
319+
authorizerTestName := integration.ResourceName(integration.APIGateway, authorizerSrc, integration.TestID())
320+
authorizerID, err := integration.GetUniqueAttributeValueBySignificantAttribute(
321+
authorizerUniqueAttribute,
322+
"Name",
323+
authorizerTestName,
324+
authorizers,
325+
true,
326+
)
327+
if err != nil {
328+
t.Fatalf("failed to get authorizer ID: %v", err)
329+
}
330+
331+
// Get authorizer
332+
query := fmt.Sprintf("%s/%s", restApiID, authorizerID)
333+
authorizer, err := authorizerSource.Get(ctx, scope, query, true)
334+
if err != nil {
335+
t.Fatalf("failed to get APIGateway authorizer: %v", err)
336+
}
337+
338+
authorizerIDFromGet, err := integration.GetUniqueAttributeValueBySignificantAttribute(
339+
authorizerUniqueAttribute,
340+
"Name",
341+
authorizerTestName,
342+
[]*sdp.Item{authorizer},
343+
true,
344+
)
345+
if err != nil {
346+
t.Fatalf("failed to get authorizer ID from get: %v", err)
347+
}
348+
349+
if authorizerID != authorizerIDFromGet {
350+
t.Fatalf("expected authorizer ID %s, got %s", authorizerID, authorizerIDFromGet)
351+
}
352+
353+
// Search authorizer by restApiID/name
354+
query = fmt.Sprintf("%s/%s", restApiID, authorizerTestName)
355+
authorizersFromSearch, err := authorizerSource.Search(ctx, scope, query, true)
356+
if err != nil {
357+
t.Fatalf("failed to search APIGateway authorizers: %v", err)
358+
}
359+
360+
if len(authorizersFromSearch) == 0 {
361+
t.Fatalf("no authorizers found")
362+
}
363+
364+
authorizerIDFromSearch, err := integration.GetUniqueAttributeValueBySignificantAttribute(
365+
authorizerUniqueAttribute,
366+
"Name",
367+
authorizerTestName,
368+
authorizersFromSearch,
369+
true,
370+
)
371+
if err != nil {
372+
t.Fatalf("failed to get authorizer ID from search: %v", err)
373+
}
374+
375+
if authorizerID != authorizerIDFromSearch {
376+
t.Fatalf("expected authorizer ID %s, got %s", authorizerID, authorizerIDFromSearch)
377+
}
378+
304379
t.Log("APIGateway integration test completed")
305380
}

adapters/integration/apigateway/create.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package apigateway
33
import (
44
"context"
55
"errors"
6+
"github.com/aws/aws-sdk-go-v2/service/apigateway/types"
67
"log/slog"
78
"strings"
89

@@ -196,3 +197,34 @@ func createAPIKey(ctx context.Context, logger *slog.Logger, client *apigateway.C
196197

197198
return nil
198199
}
200+
201+
func createAuthorizer(ctx context.Context, logger *slog.Logger, client *apigateway.Client, restAPIID, testID string) error {
202+
// check if an authorizer with the same name already exists
203+
id, err := findAuthorizerByName(ctx, client, restAPIID, integration.ResourceName(integration.APIGateway, authorizerSrc, testID))
204+
if err != nil {
205+
if errors.As(err, new(integration.NotFoundError)) {
206+
logger.InfoContext(ctx, "Creating authorizer")
207+
} else {
208+
return err
209+
}
210+
}
211+
212+
if id != nil {
213+
logger.InfoContext(ctx, "Authorizer already exists")
214+
return nil
215+
}
216+
217+
identitySource := "method.request.header.Authorization"
218+
_, err = client.CreateAuthorizer(ctx, &apigateway.CreateAuthorizerInput{
219+
RestApiId: &restAPIID,
220+
Name: adapterhelpers.PtrString(integration.ResourceName(integration.APIGateway, authorizerSrc, testID)),
221+
Type: types.AuthorizerTypeToken,
222+
IdentitySource: &identitySource,
223+
AuthorizerUri: adapterhelpers.PtrString("arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:123456789012:function:auth-function/invocations"),
224+
})
225+
if err != nil {
226+
return err
227+
}
228+
229+
return nil
230+
}

adapters/integration/apigateway/find.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,3 +131,24 @@ func findAPIKeyByName(ctx context.Context, client *apigateway.Client, name strin
131131

132132
return nil, integration.NewNotFoundError(integration.ResourceName(integration.APIGateway, apiKeySrc, name))
133133
}
134+
135+
func findAuthorizerByName(ctx context.Context, client *apigateway.Client, restAPIID, name string) (*string, error) {
136+
result, err := client.GetAuthorizers(ctx, &apigateway.GetAuthorizersInput{
137+
RestApiId: &restAPIID,
138+
})
139+
if err != nil {
140+
return nil, err
141+
}
142+
143+
if len(result.Items) == 0 {
144+
return nil, integration.NewNotFoundError(integration.ResourceName(integration.APIGateway, authorizerSrc, name))
145+
}
146+
147+
for _, authorizer := range result.Items {
148+
if *authorizer.Name == name {
149+
return authorizer.Id, nil
150+
}
151+
}
152+
153+
return nil, integration.NewNotFoundError(integration.ResourceName(integration.APIGateway, authorizerSrc, name))
154+
}

adapters/integration/apigateway/setup.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const (
1515
methodResponseSrc = "method-response"
1616
integrationSrc = "integration"
1717
apiKeySrc = "api-key"
18+
authorizerSrc = "authorizer"
1819
)
1920

2021
func setup(ctx context.Context, logger *slog.Logger, client *apigateway.Client) error {
@@ -62,5 +63,11 @@ func setup(ctx context.Context, logger *slog.Logger, client *apigateway.Client)
6263
return err
6364
}
6465

66+
// Create Authorizer
67+
err = createAuthorizer(ctx, logger, client, *restApiID, testID)
68+
if err != nil {
69+
return err
70+
}
71+
6572
return nil
6673
}

proc/proc.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,7 @@ func InitializeAwsSourceEngine(ctx context.Context, ec *discovery.EngineConfig,
484484
adapters.NewAPIGatewayIntegrationAdapter(apigatewayClient, *callerID.Account, cfg.Region),
485485
adapters.NewAPIGatewayVpcLinkAdapter(apigatewayClient, *callerID.Account, cfg.Region),
486486
adapters.NewAPIGatewayApiKeyAdapter(apigatewayClient, *callerID.Account, cfg.Region),
487+
adapters.NewAPIGatewayAuthorizerAdapter(apigatewayClient, *callerID.Account, cfg.Region),
487488

488489
// SSM
489490
adapters.NewSSMParameterAdapter(ssmClient, *callerID.Account, cfg.Region),

0 commit comments

Comments
 (0)