/
apiKey.go
137 lines (126 loc) · 4.36 KB
/
apiKey.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// # CF Api Key
//
// To bind an Api of the API Gateway to a custom Cloud Front distribution requires the use of a API Key to
// protect the native end point of the API and to make sure that the API is available only via the Cloud Front
// distribution.
// Unfortunately, the out-of-the-box resource `AWS::ApiGateway::ApiKey` does not allow to extract the key secret.
// This resource creates api keys and export the key secret.
//
// ## Syntax
// To create a new api key, add the following resource to your template
//
// ```yaml
// MyCfApiKey:
// Type: Custom::ApiKey
// Properties:
// ServiceToken: !Import ForgeResources-ApiKey
// Ordinal: <number>
// ```
//
// ## Properties
//
// `Ordinal`
//
// > The name of the API keys is created automatically from the Stack Name and the Ordinal. By making the Ordinal
// > a parameter of the stack, one can easily rotate the keys. You can automate the key rotation with the
// > AWS Lambda `RotateCfApiKey`.
//
// > _Type_: Number
// >
// > _Required: Yes
// >
// > _Update Requires_: Replacement
package main
import (
"context"
"github.com/aws/aws-lambda-go/cfn"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/aws/awserr"
"github.com/aws/aws-sdk-go-v2/aws/external"
"github.com/aws/aws-sdk-go-v2/service/apigateway"
"github.com/aws/aws-sdk-go-v2/service/cloudformation"
"github.com/codesmith-gmbh/cgc/cgccf"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
)
// The lambda is started using the AWS lambda go sdk. The handler function
// does the actual work of creating the apikey. Cloudformation sends an
// event to signify that a resources must be created, updated or deleted.
func main() {
p := newProc()
cgccf.StartEventProcessor(p)
}
type proc struct {
apg *apigateway.Client
cf *cloudformation.Client
}
func newProc() cgccf.EventProcessor {
cfg, err := external.LoadDefaultAWSConfig()
if err != nil {
return &cgccf.ConstantErrorEventProcessor{Error: err}
}
return newProcFromConfig(cfg)
}
func newProcFromConfig(cfg aws.Config) *proc {
return &proc{apg: apigateway.New(cfg), cf: cloudformation.New(cfg)}
}
type Properties struct {
Ordinal string
}
func validateProperties(input map[string]interface{}) (Properties, error) {
var properties Properties
err := mapstructure.Decode(input, &properties)
if err != nil {
return properties, err
}
if properties.Ordinal == "" {
return properties, errors.Wrap(err, "Ordinal is obligatory")
}
return properties, nil
}
// It is not possible to update the resource, if the ordinal changes, a new resource is allocated.
func (p *proc) ProcessEvent(ctx context.Context, event cfn.Event) (string, map[string]interface{}, error) {
properties, err := validateProperties(event.ResourceProperties)
if err != nil {
return "", nil, err
}
switch event.RequestType {
case cfn.RequestDelete:
return p.deleteApiKey(ctx, event.PhysicalResourceID)
case cfn.RequestUpdate, cfn.RequestCreate:
return p.createApiKey(ctx, event, properties)
default:
return event.PhysicalResourceID, nil, errors.Errorf("unknown request type %s", event.RequestType)
}
}
// To create the Api Key, we first retrieve the name of the stack and concatenate with the LogicalResourceId and Ordinal to create
// The Api Key Name.
func (p *proc) createApiKey(ctx context.Context, event cfn.Event, properties Properties) (string, map[string]interface{}, error) {
stack, err := p.cf.DescribeStacksRequest(&cloudformation.DescribeStacksInput{
StackName: &event.StackID,
}).Send(ctx)
if err != nil {
return "", nil, errors.Wrapf(err, "Cannot retrieve the stack name for %s", event.StackID)
}
name := *stack.Stacks[0].StackName + "-" + event.LogicalResourceID + "-" + properties.Ordinal
key, err := p.apg.CreateApiKeyRequest(&apigateway.CreateApiKeyInput{
Name: aws.String(name),
Enabled: aws.Bool(true),
}).Send(ctx)
if err != nil {
return "", nil, errors.Wrapf(err, "Cannot create the key with name %s", name)
}
return *key.Id, map[string]interface{}{"Secret": key.Value}, nil
}
func (p *proc) deleteApiKey(ctx context.Context, keyId string) (string, map[string]interface{}, error) {
_, err := p.apg.DeleteApiKeyRequest(&apigateway.DeleteApiKeyInput{
ApiKey: &keyId,
}).Send(ctx)
if err != nil {
awsErr, ok := err.(awserr.RequestFailure)
if !ok || awsErr.StatusCode() != 404 {
return keyId, nil, errors.Wrapf(err, "could not delete the api key %s", keyId)
}
}
return keyId, nil, nil
}