Skip to content
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

Feat/apigateway integration #726

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
129 changes: 129 additions & 0 deletions adapters/apigateway-api-key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package adapters

import (
"context"
"github.com/aws/aws-sdk-go-v2/service/apigateway"
"github.com/aws/aws-sdk-go-v2/service/apigateway/types"
"github.com/overmindtech/aws-source/adapterhelpers"
"github.com/overmindtech/sdp-go"
"strings"
)

// convertGetApiKeyOutputToApiKey converts a GetApiKeyOutput to an ApiKey
func convertGetApiKeyOutputToApiKey(output *apigateway.GetApiKeyOutput) *types.ApiKey {
return &types.ApiKey{
Id: output.Id,
Name: output.Name,
Enabled: output.Enabled,
CreatedDate: output.CreatedDate,
LastUpdatedDate: output.LastUpdatedDate,
StageKeys: output.StageKeys,
Tags: output.Tags,
}
}

func apiKeyListFunc(ctx context.Context, client *apigateway.Client, _ string) ([]*types.ApiKey, error) {
out, err := client.GetApiKeys(ctx, &apigateway.GetApiKeysInput{})
if err != nil {
return nil, err
}

var items []*types.ApiKey
for _, apiKey := range out.Items {
items = append(items, &apiKey)
}

return items, nil
}

func apiKeyOutputMapper(scope string, awsItem *types.ApiKey) (*sdp.Item, error) {
attributes, err := adapterhelpers.ToAttributesWithExclude(awsItem, "tags")
if err != nil {
return nil, err
}

item := sdp.Item{
Type: "apigateway-api-key",
UniqueAttribute: "Id",
Attributes: attributes,
Scope: scope,
Tags: awsItem.Tags,
}

for _, key := range awsItem.StageKeys {
// {restApiId}/{stage}
restAPIID := strings.Split(key, "/")[0]
if restAPIID != "" {
item.LinkedItemQueries = append(item.LinkedItemQueries, &sdp.LinkedItemQuery{
Query: &sdp.Query{
Type: "apigateway-rest-api",
Method: sdp.QueryMethod_GET,
Query: restAPIID,
Scope: scope,
},
BlastPropagation: &sdp.BlastPropagation{
// They are tightly coupled, so we need to propagate both ways
In: true,
Out: true,
},
})
}
}

return &item, nil
}

func NewAPIGatewayApiKeyAdapter(client *apigateway.Client, accountID string, region string) *adapterhelpers.GetListAdapter[*types.ApiKey, *apigateway.Client, *apigateway.Options] {
return &adapterhelpers.GetListAdapter[*types.ApiKey, *apigateway.Client, *apigateway.Options]{
ItemType: "apigateway-api-key",
Client: client,
AccountID: accountID,
Region: region,
AdapterMetadata: apiKeyAdapterMetadata,
GetFunc: func(ctx context.Context, client *apigateway.Client, scope, query string) (*types.ApiKey, error) {
out, err := client.GetApiKey(ctx, &apigateway.GetApiKeyInput{
ApiKey: &query,
})
if err != nil {
return nil, err
}
return convertGetApiKeyOutputToApiKey(out), nil
},
ListFunc: apiKeyListFunc,
SearchFunc: func(ctx context.Context, client *apigateway.Client, scope string, query string) ([]*types.ApiKey, error) {
out, err := client.GetApiKeys(ctx, &apigateway.GetApiKeysInput{
NameQuery: &query,
})
if err != nil {
return nil, err
}

var items []*types.ApiKey
for _, apiKey := range out.Items {
items = append(items, &apiKey)
}

return items, nil
},
ItemMapper: func(_, scope string, awsItem *types.ApiKey) (*sdp.Item, error) {
return apiKeyOutputMapper(scope, awsItem)
},
}
}

var apiKeyAdapterMetadata = Metadata.Register(&sdp.AdapterMetadata{
Type: "apigateway-api-key",
DescriptiveName: "API Key",
Category: sdp.AdapterCategory_ADAPTER_CATEGORY_SECURITY,
SupportedQueryMethods: &sdp.AdapterSupportedQueryMethods{
Get: true,
List: true,
Search: true,
GetDescription: "Get an API Key by ID",
ListDescription: "List all API Keys",
SearchDescription: "Search for API Keys by their name",
},
TerraformMappings: []*sdp.TerraformMapping{
{TerraformQueryMap: "aws_api_gateway_api_key.id"},
},
})
60 changes: 60 additions & 0 deletions adapters/apigateway-api-key_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package adapters

import (
"testing"
"time"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/apigateway"
"github.com/aws/aws-sdk-go-v2/service/apigateway/types"
"github.com/overmindtech/aws-source/adapterhelpers"
"github.com/overmindtech/sdp-go"
)

func TestApiKeyOutputMapper(t *testing.T) {
awsItem := &types.ApiKey{
Id: aws.String("api-key-id"),
Name: aws.String("api-key-name"),
Enabled: true,
CreatedDate: aws.Time(time.Now()),
LastUpdatedDate: aws.Time(time.Now()),
StageKeys: []string{"rest-api-id/stage"},
Tags: map[string]string{"key": "value"},
}

item, err := apiKeyOutputMapper("scope", awsItem)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

if err := item.Validate(); err != nil {
t.Error(err)
}

tests := adapterhelpers.QueryTests{
{
ExpectedType: "apigateway-rest-api",
ExpectedMethod: sdp.QueryMethod_GET,
ExpectedQuery: "rest-api-id",
ExpectedScope: "scope",
},
}

tests.Execute(t, item)
}

func TestNewAPIGatewayApiKeyAdapter(t *testing.T) {
config, account, region := adapterhelpers.GetAutoConfig(t)

client := apigateway.NewFromConfig(config)

adapter := NewAPIGatewayApiKeyAdapter(client, account, region)

test := adapterhelpers.E2ETest{
Adapter: adapter,
Timeout: 10 * time.Second,
SkipList: true,
}

test.Run(t)
}
143 changes: 143 additions & 0 deletions adapters/apigateway-authorizer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package adapters

import (
"context"
"fmt"
"strings"

"github.com/aws/aws-sdk-go-v2/service/apigateway"
"github.com/aws/aws-sdk-go-v2/service/apigateway/types"
"github.com/overmindtech/aws-source/adapterhelpers"
"github.com/overmindtech/sdp-go"
)

// convertGetAuthorizerOutputToAuthorizer converts a GetAuthorizerOutput to an Authorizer
func convertGetAuthorizerOutputToAuthorizer(output *apigateway.GetAuthorizerOutput) *types.Authorizer {
return &types.Authorizer{
Id: output.Id,
Name: output.Name,
Type: output.Type,
ProviderARNs: output.ProviderARNs,
AuthType: output.AuthType,
AuthorizerUri: output.AuthorizerUri,
AuthorizerCredentials: output.AuthorizerCredentials,
IdentitySource: output.IdentitySource,
IdentityValidationExpression: output.IdentityValidationExpression,
AuthorizerResultTtlInSeconds: output.AuthorizerResultTtlInSeconds,
}
}

func authorizerOutputMapper(query, scope string, awsItem *types.Authorizer) (*sdp.Item, error) {
attributes, err := adapterhelpers.ToAttributesWithExclude(awsItem, "tags")
if err != nil {
return nil, err
}

item := sdp.Item{
Type: "apigateway-authorizer",
UniqueAttribute: "Id",
Attributes: attributes,
Scope: scope,
}

item.LinkedItemQueries = append(item.LinkedItemQueries, &sdp.LinkedItemQuery{
Query: &sdp.Query{
Type: "apigateway-rest-api",
Method: sdp.QueryMethod_GET,
Query: strings.Split(query, "/")[0],
Scope: scope,
},
BlastPropagation: &sdp.BlastPropagation{
// They are tightly coupled, so we need to propagate the blast to the linked item
In: true,
Out: true,
},
})

return &item, nil
}

func NewAPIGatewayAuthorizerAdapter(client *apigateway.Client, accountID string, region string) *adapterhelpers.GetListAdapter[*types.Authorizer, *apigateway.Client, *apigateway.Options] {
return &adapterhelpers.GetListAdapter[*types.Authorizer, *apigateway.Client, *apigateway.Options]{
ItemType: "apigateway-authorizer",
Client: client,
AccountID: accountID,
Region: region,
AdapterMetadata: authorizerAdapterMetadata,
GetFunc: func(ctx context.Context, client *apigateway.Client, scope, query string) (*types.Authorizer, error) {
f := strings.Split(query, "/")
if len(f) != 2 {
return nil, &sdp.QueryError{
ErrorType: sdp.QueryError_NOTFOUND,
ErrorString: fmt.Sprintf("query must be in the format of: the rest-api-id/authorizer-id, but found: %s", query),
}
}
out, err := client.GetAuthorizer(ctx, &apigateway.GetAuthorizerInput{
RestApiId: &f[0],
AuthorizerId: &f[1],
})
if err != nil {
return nil, err
}
return convertGetAuthorizerOutputToAuthorizer(out), nil
},
DisableList: true,
SearchFunc: func(ctx context.Context, client *apigateway.Client, scope string, query string) ([]*types.Authorizer, error) {
f := strings.Split(query, "/")
var restAPIID string
var name string

switch len(f) {
case 1:
restAPIID = f[0]
case 2:
restAPIID = f[0]
name = f[1]
default:
return nil, &sdp.QueryError{
ErrorType: sdp.QueryError_NOTFOUND,
ErrorString: fmt.Sprintf(
"query must be in the format of: the rest-api-id/authorizer-id or rest-api-id, but found: %s",
query,
),
}
}

out, err := client.GetAuthorizers(ctx, &apigateway.GetAuthorizersInput{
RestApiId: &restAPIID,
})
if err != nil {
return nil, err
}

var items []*types.Authorizer
for _, authorizer := range out.Items {
if name != "" && strings.Contains(*authorizer.Name, name) {
items = append(items, &authorizer)
} else {
items = append(items, &authorizer)
}
}

return items, nil
},
ItemMapper: func(query, scope string, awsItem *types.Authorizer) (*sdp.Item, error) {
return authorizerOutputMapper(query, scope, awsItem)
},
}
}

var authorizerAdapterMetadata = Metadata.Register(&sdp.AdapterMetadata{
Type: "apigateway-authorizer",
DescriptiveName: "API Gateway Authorizer",
Category: sdp.AdapterCategory_ADAPTER_CATEGORY_SECURITY,
SupportedQueryMethods: &sdp.AdapterSupportedQueryMethods{
Get: true,
Search: true,
GetDescription: "Get an API Gateway Authorizer by its rest API ID and ID: rest-api-id/authorizer-id",
SearchDescription: "Search for API Gateway Authorizers by their rest API ID or with rest API ID and their name: rest-api-id/authorizer-name",
},
TerraformMappings: []*sdp.TerraformMapping{
{TerraformQueryMap: "aws_api_gateway_authorizer.id"},
},
})
63 changes: 63 additions & 0 deletions adapters/apigateway-authorizer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package adapters

import (
"github.com/overmindtech/sdp-go"
"testing"
"time"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/apigateway"
"github.com/aws/aws-sdk-go-v2/service/apigateway/types"
"github.com/overmindtech/aws-source/adapterhelpers"
)

func TestAuthorizerOutputMapper(t *testing.T) {
awsItem := &types.Authorizer{
Id: aws.String("authorizer-id"),
Name: aws.String("authorizer-name"),
Type: types.AuthorizerTypeRequest,
ProviderARNs: []string{"arn:aws:iam::123456789012:role/service-role"},
AuthType: aws.String("custom"),
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"),
AuthorizerCredentials: aws.String("arn:aws:iam::123456789012:role/service-role"),
IdentitySource: aws.String("method.request.header.Authorization"),
IdentityValidationExpression: aws.String(".*"),
AuthorizerResultTtlInSeconds: aws.Int32(300),
}

item, err := authorizerOutputMapper("rest-api-id", "scope", awsItem)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

if err := item.Validate(); err != nil {
t.Error(err)
}

tests := adapterhelpers.QueryTests{
{
ExpectedType: "apigateway-rest-api",
ExpectedMethod: sdp.QueryMethod_GET,
ExpectedQuery: "rest-api-id",
ExpectedScope: "scope",
},
}

tests.Execute(t, item)
}

func TestNewAPIGatewayAuthorizerAdapter(t *testing.T) {
config, account, region := adapterhelpers.GetAutoConfig(t)

client := apigateway.NewFromConfig(config)

adapter := NewAPIGatewayAuthorizerAdapter(client, account, region)

test := adapterhelpers.E2ETest{
Adapter: adapter,
Timeout: 10 * time.Second,
SkipList: true,
}

test.Run(t)
}
Loading
Loading