-
Notifications
You must be signed in to change notification settings - Fork 14
/
callback.go
212 lines (186 loc) · 11.2 KB
/
callback.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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
package core
import (
"context"
"crypto/tls"
"fmt"
"net/http"
"github.com/anz-bank/sysl-go/log"
"github.com/anz-bank/sysl-go/common"
"github.com/anz-bank/sysl-go/config"
"github.com/anz-bank/sysl-go/core/authrules"
"github.com/anz-bank/sysl-go/jwtauth"
"github.com/go-chi/chi"
"google.golang.org/grpc"
)
// RestGenCallback is used by `sysl-go` to call hand-crafted code.
type RestGenCallback interface {
// AddMiddleware allows hand-crafted code to add middleware to the router
AddMiddleware(ctx context.Context, r chi.Router)
// BasePath allows hand-crafted code to set the base path for the Router
BasePath() string
// Config returns a structure representing the server config
// This is returned from the status endpoint
Config() interface{}
// MapError maps an error to an HTTPError in instances where custom error mapping is required. Return nil to perform default error mapping; defined as:
// 1. CustomError.HTTPError if the original error is a CustomError, otherwise
// 2. common.MapError
MapError(ctx context.Context, err error) *common.HTTPError
// DownstreamTimeoutContext add the desired timeout duration to the context for downstreams
// A separate service timeout (usually greater than the downstream) should also be in
// place to automatically respond to callers
DownstreamTimeoutContext(ctx context.Context) (context.Context, context.CancelFunc)
}
// GrpcGenCallback is currently a subset of RestGenCallback so is defined separately for convenience.
type GrpcGenCallback interface {
DownstreamTimeoutContext(ctx context.Context) (context.Context, context.CancelFunc)
}
// Hooks can be used to customise the behaviour of an autogenerated sysl-go service.
type Hooks struct {
// Logger returns the common.Logger instance to set use within Sysl-go.
// By default, if this Logger hook is not set then an instance of the pkg logger is used.
// This hook can also be used to define a custom logger.
// For more information about logging see log/README.md within this project.
// Note: The returned logger is guaranteed to have the log level from the external configuration
// file (library: log: level) set against it.
Logger func() log.Logger
// MapError maps an error to an HTTPError in instances where custom error mapping is required.
// Return nil to perform default error mapping; defined as:
// 1. CustomError.HTTPError if the original error is a CustomError, otherwise
// 2. common.MapError
// By default, if this MapError hook is not customised, the default error mapping will be used.
MapError func(ctx context.Context, err error) *common.HTTPError
// AdditionalGrpcDialOptions can be used to append to the default grpc.DialOption configuration used by
// an autogenerated service when it calls grpc.Dial when using a grpc.Client to connect to a gRPC server.
// If given, AdditionalGrpcDialOptions will be appended to the list of default options created by
// DefaultGrpcDialOptions(CommonGRPCDownstreamData).
//
// Use AdditionalGrpcDialOptions if you need both default and custom options. Be careful that you do
// not specify any options that clash with the default options.
//
// If you need to completely override the default options, use OverrideGrpcDialOptions.
// It is an error to set both AdditionalGrpcDialOptions and OverrideGrpcDialOptions.
AdditionalGrpcDialOptions []grpc.DialOption
// OverrideGrpcDialOptions can be used to override the default grpc.DialOption configuration used by an
// an autogenerated service when it calls grpc.Dial when using a grpc.Client to connect to a gRPC server.
//
// The serviceName parameter will be filled with the name of the target service that we
// are about to call grpc.Dial to connect to -- a function implementing this hook can use the
// serviceName to customise different dial options for different targets.
//
// Prefer to use AdditionalGrpcDialOptions instead of OverrideGrpcDialOptions if you only need
// to append to the default grpc.DialOption configuration instead of overriding it completely.
//
// It is an error to set both AdditionalGrpcDialOptions and OverrideGrpcDialOptions.
OverrideGrpcDialOptions func(serviceName string, cfg *config.CommonGRPCDownstreamData) ([]grpc.DialOption, error)
// AdditionalGrpcServerOptions can be used to append to the default grpc.ServerOption configuration used by
// an autogenerated service when it creates a gRPC server. If given, AdditionalGrpcServerOptions will be
// appended to the list of default options created by DefaultGrpcServerOptions(context.Context, CommonServerConfig).
//
// Use AdditionalGrpcServerOptions if you need both default and custom options. Be careful that you do
// not specify any options that clash with the default options.
//
// If you need to completely override the default options, use OverrideGrpcServerOptions.
// It is an error to set both AdditionalGrpcServerOptions and OverrideGrpcServerOptions.
AdditionalGrpcServerOptions []grpc.ServerOption
// OverrideGrpcServerOptions can be used to override the default grpc.ServerOption configuration used by an
// autogenerated service when it creates a gRPC server.
//
// Prefer to use AdditionalGrpcServerOptions instead of OverrideGrpcServerOptions if you only need
// to append to the default grpc.ServerOption configuration instead of overriding it completely.
//
// It is an error to set both AdditionalGrpcServerOptions and OverrideGrpcServerOptions.
OverrideGrpcServerOptions func(ctx context.Context, grpcPublicServerConfig *config.GRPCServerConfig) ([]grpc.ServerOption, error)
// OverrideMakeJWTClaimsBasedAuthorizationRule can be used to customise how authorization rule
// expressions are evaluated and used to decide if JWT claims are authorised. By default, if this
// hook is nil, then authrules.MakeDefaultJWTClaimsBasedAuthorizationRule is used.
OverrideMakeJWTClaimsBasedAuthorizationRule func(authorizationRuleExpression string) (authrules.JWTClaimsBasedAuthorizationRule, error)
// AddHTTPMiddleware can be used to install additional HTTP middleware into the chi.Router
// used to serve all (non-admin) HTTP endpoints. By default, sysl-go installs a number of
// HTTP middleware -- refer to prepareMiddleware inside sysl-go/core. This hook can only
// be used to add middleware, not override any of the default middleware.
AddHTTPMiddleware func(ctx context.Context, r chi.Router)
// AddAdminHTTPMiddleware can be used to install additional HTTP middleware into the chi.Router
// used to serve the admin HTTP endpoints. See AddHTTPMiddleware for further details.
AddAdminHTTPMiddleware func(ctx context.Context, r chi.Router)
// DownstreamRoundTripper can be used to install additional HTTP RoundTrippers to the downstream clients
DownstreamRoundTripper func(serviceName string, serviceURL string, original http.RoundTripper) http.RoundTripper
// ValidateConfig can be used to validate (or override) values in the config.
ValidateConfig func(ctx context.Context, cfg *config.DefaultConfig) error
// HTTPClientBuilder can be used to add a function which will be used to create the downstream HTTP clients
// instead of the normal generator. This can be used to create custom test HTTP clients.
HTTPClientBuilder func(serviceName string) (client *http.Client, serviceURL string, err error)
// StoppableServerBuilder can be used to add a function which will be used to create the public listener
// instead of the normal httpServer. This can be used to create custom test HTTP listeners.
StoppableServerBuilder func(ctx context.Context, rootRouter http.Handler, tlsConfig *tls.Config, httpConfig config.CommonHTTPServerConfig, name string) StoppableServer
}
func ResolveGrpcDialOptions(ctx context.Context, serviceName string, h *Hooks, grpcDownstreamConfig *config.CommonGRPCDownstreamData) ([]grpc.DialOption, error) {
switch {
case len(h.AdditionalGrpcDialOptions) > 0 && h.OverrideGrpcDialOptions != nil:
return nil, fmt.Errorf("Hooks.AdditionalGrpcDialOptions and Hooks.OverrideGrpcDialOptions cannot both be set")
case h.OverrideGrpcDialOptions != nil:
return h.OverrideGrpcDialOptions(serviceName, grpcDownstreamConfig)
default:
opts, err := config.DefaultGrpcDialOptions(ctx, grpcDownstreamConfig)
if err != nil {
return nil, err
}
opts = append(opts, h.AdditionalGrpcDialOptions...)
return opts, nil
}
}
func ResolveGrpcServerOptions(ctx context.Context, h *Hooks, grpcPublicServerConfig *config.GRPCServerConfig) ([]grpc.ServerOption, error) {
switch {
case len(h.AdditionalGrpcServerOptions) > 0 && h.OverrideGrpcServerOptions != nil:
return nil, fmt.Errorf("Hooks.AdditionalGrpcServerOptions and Hooks.OverrideGrpcServerOptions cannot both be set")
case h.OverrideGrpcServerOptions != nil:
return h.OverrideGrpcServerOptions(ctx, grpcPublicServerConfig)
default:
opts, err := DefaultGrpcServerOptions(ctx, grpcPublicServerConfig)
if err != nil {
return nil, err
}
opts = append(opts, h.AdditionalGrpcServerOptions...)
return opts, nil
}
}
func ResolveGRPCAuthorizationRule(ctx context.Context, h *Hooks, endpointName string, authRuleExpression string) (authrules.Rule, error) {
return resolveAuthorizationRule(ctx, h, endpointName, authRuleExpression, authrules.MakeGRPCJWTAuthorizationRule)
}
func ResolveRESTAuthorizationRule(ctx context.Context, h *Hooks, endpointName string, authRuleExpression string) (authrules.Rule, error) {
return resolveAuthorizationRule(ctx, h, endpointName, authRuleExpression, authrules.MakeRESTJWTAuthorizationRule)
}
func resolveAuthorizationRule(ctx context.Context, h *Hooks, endpointName string, authRuleExpression string, ruleFactory func(authRule authrules.JWTClaimsBasedAuthorizationRule, authenticator jwtauth.Authenticator) (authrules.Rule, error)) (authrules.Rule, error) {
cfg := config.GetDefaultConfig(ctx)
if cfg.Development != nil && cfg.Development.DisableAllAuthorizationRules {
log.Info(ctx, "warning: development.disableAllAuthorizationRules is set, all authorization rules are disabled, this is insecure and should not be used in production.")
return authrules.InsecureAlwaysGrantAccess, nil
}
var claimsBasedAuthRuleFactory func(authorizationRuleExpression string) (authrules.JWTClaimsBasedAuthorizationRule, error)
switch {
case h.OverrideMakeJWTClaimsBasedAuthorizationRule != nil:
claimsBasedAuthRuleFactory = h.OverrideMakeJWTClaimsBasedAuthorizationRule
default:
claimsBasedAuthRuleFactory = authrules.MakeDefaultJWTClaimsBasedAuthorizationRule
}
claimsBasedAuthRule, err := claimsBasedAuthRuleFactory(authRuleExpression)
if err != nil {
return nil, err
}
// TODO(fletcher) inject custom http client instrumented with monitoring
httpClient, err := config.DefaultHTTPClient(ctx, nil)
if err != nil {
return nil, err
}
httpClientFactory := func(_ string) *http.Client {
return httpClient
}
// Note: this will start a new jwtauth.Authenticator with its own cache & threads running for each of our service's endpoints, we usually want a shared one.
if cfg == nil || cfg.Library.Authentication == nil || cfg.Library.Authentication.JWTAuth == nil {
return nil, fmt.Errorf("method/endpoint %s requires a JWT-based authorization rule, but there is no config for library.authentication.jwtauth", endpointName)
}
authenticator, err := jwtauth.AuthFromConfig(ctx, cfg.Library.Authentication.JWTAuth, httpClientFactory)
if err != nil {
return nil, err
}
return ruleFactory(claimsBasedAuthRule, authenticator)
}