1 package services
2
3 import (
4
5 "context"
6 "crypto/tls"
7 "encoding/json"
8 "fmt"
9 "net/http"
10 "strconv"
11 "strings"
12
13
14 "google.golang.org/genproto/googleapis/rpc/code"
15 "google.golang.org/genproto/googleapis/rpc/status"
16 "google.golang.org/grpc"
17 "google.golang.org/protobuf/types/known/wrapperspb"
18
19
20 core "github.com/datawire/ambassador/v2/pkg/api/envoy/api/v2/core"
21 pb "github.com/datawire/ambassador/v2/pkg/api/envoy/service/auth/v2"
22 envoy_type "github.com/datawire/ambassador/v2/pkg/api/envoy/type"
23
24
25 "github.com/datawire/dlib/dgroup"
26 "github.com/datawire/dlib/dhttp"
27 "github.com/datawire/dlib/dlog"
28 )
29
30
31 type GRPCAUTH struct {
32 Port int16
33 Backend string
34 SecurePort int16
35 SecureBackend string
36 Cert string
37 Key string
38 ProtocolVersion string
39 }
40
41
42 func (g *GRPCAUTH) Start(ctx context.Context) <-chan bool {
43 dlog.Printf(ctx, "GRPCAUTH: %s listening on %d/%d", g.Backend, g.Port, g.SecurePort)
44
45 grpcHandler := grpc.NewServer()
46 dlog.Printf(ctx, "registering v2 service")
47 pb.RegisterAuthorizationServer(grpcHandler, g)
48
49 cer, err := tls.LoadX509KeyPair(g.Cert, g.Key)
50 if err != nil {
51 dlog.Error(ctx, err)
52 panic(err)
53 }
54
55 sc := &dhttp.ServerConfig{
56 Handler: grpcHandler,
57 TLSConfig: &tls.Config{
58 Certificates: []tls.Certificate{cer},
59 },
60 }
61
62 grp := dgroup.NewGroup(ctx, dgroup.GroupConfig{})
63 grp.Go("cleartext", func(ctx context.Context) error {
64 return sc.ListenAndServe(ctx, fmt.Sprintf(":%v", g.Port))
65 })
66 grp.Go("tls", func(ctx context.Context) error {
67 return sc.ListenAndServeTLS(ctx, fmt.Sprintf(":%v", g.SecurePort), "", "")
68 })
69
70 dlog.Print(ctx, "starting gRPC authorization service")
71
72 exited := make(chan bool)
73 go func() {
74 if err := grp.Wait(); err != nil {
75 dlog.Error(ctx, err)
76 panic(err)
77 }
78 close(exited)
79 }()
80 return exited
81 }
82
83
84 func (g *GRPCAUTH) Check(ctx context.Context, r *pb.CheckRequest) (*pb.CheckResponse, error) {
85 rs := &Response{}
86
87 rheader := r.GetAttributes().GetRequest().GetHttp().GetHeaders()
88 rbody := r.GetAttributes().GetRequest().GetHttp().GetBody()
89 if len(rbody) > 0 {
90 rheader["body"] = rbody
91 }
92
93 rContextExtensions := r.GetAttributes().GetContextExtensions()
94 if rContextExtensions != nil {
95 val, err := json.Marshal(rContextExtensions)
96 if err != nil {
97 val = []byte(fmt.Sprintf("Error: %v", err))
98 }
99
100 rs.AddHeader(false, "x-request-context-extensions", string(val))
101 }
102
103
104 rs.SetStatus(ctx, rheader["requested-status"])
105
106 rs.AddHeader(false, "x-grpc-service-protocol-version", g.ProtocolVersion)
107
108
109 for _, key := range strings.Split(rheader["requested-header"], ",") {
110 if val := rheader[key]; len(val) > 0 {
111 rs.AddHeader(false, key, val)
112 }
113 }
114
115
116 for _, token := range strings.Split(rheader["x-grpc-auth-append"], ";") {
117 header := strings.Split(strings.TrimSpace(token), "=")
118 if len(header) > 1 {
119 dlog.Printf(ctx, "appending header %s : %s", header[0], header[1])
120 rs.AddHeader(true, header[0], header[1])
121 }
122 }
123
124
125 for _, v := range strings.Split(rheader["requested-cookie"], ",") {
126 val := strings.Trim(v, " ")
127 rs.AddHeader(false, "Set-Cookie", fmt.Sprintf("%s=%s", val, val))
128 }
129
130
131 if len(rheader["requested-location"]) > 0 {
132 rs.AddHeader(false, "Location", rheader["requested-location"])
133 }
134
135
136 headers := make(map[string]interface{})
137 for k, v := range rheader {
138 headers[k] = strings.Split(v, ",")
139 }
140
141
142 url := make(map[string]interface{})
143 url["fragment"] = r.GetAttributes().GetRequest().GetHttp().GetFragment()
144 url["host"] = r.GetAttributes().GetRequest().GetHttp().GetHost()
145 url["path"] = r.GetAttributes().GetRequest().GetHttp().GetPath()
146 url["query"] = r.GetAttributes().GetRequest().GetHttp().GetQuery()
147 url["scheme"] = r.GetAttributes().GetRequest().GetHttp().GetScheme()
148
149
150 tls := make(map[string]interface{})
151 tls["enabled"] = false
152
153
154 request := make(map[string]interface{})
155 request["url"] = url
156 request["method"] = r.GetAttributes().GetRequest().GetHttp().GetMethod()
157 request["headers"] = headers
158 request["host"] = r.GetAttributes().GetRequest().GetHttp().GetHost()
159 request["tls"] = tls
160
161
162 results := make(map[string]interface{})
163 results["backend"] = g.Backend
164 results["status"] = rs.GetStatus()
165 if len(request) > 0 {
166 results["request"] = request
167 }
168 if rs.GetHTTPHeaderMap() != nil {
169 results["headers"] = *rs.GetHTTPHeaderMap()
170 }
171 body, err := json.MarshalIndent(results, "", " ")
172 if err != nil {
173 body = []byte(fmt.Sprintf("Error: %v", err))
174 }
175
176
177 dlog.Printf(ctx, "setting response body: %s", string(body))
178 rs.SetBody(string(body))
179
180 return rs.GetResponse(), nil
181 }
182
183
184 type Response struct {
185 headers []*core.HeaderValueOption
186 body string
187 status uint32
188 }
189
190
191
192 func (r *Response) AddHeader(a bool, k, v string) {
193 val := &core.HeaderValueOption{
194 Header: &core.HeaderValue{
195 Key: k,
196 Value: v,
197 },
198 Append: &wrapperspb.BoolValue{Value: a},
199 }
200 r.headers = append(r.headers, val)
201 }
202
203
204 func (r *Response) GetHTTPHeaderMap() *http.Header {
205 h := &http.Header{}
206 for _, v := range r.headers {
207 h.Add(v.Header.Key, v.Header.Value)
208 }
209 return h
210 }
211
212
213 func (r *Response) SetBody(s string) {
214 r.body = s
215 }
216
217
218 func (r *Response) SetStatus(ctx context.Context, s string) {
219 if len(s) == 0 {
220 s = "200"
221 }
222 if val, err := strconv.Atoi(s); err == nil {
223 r.status = uint32(val)
224 r.AddHeader(false, "status", s)
225 dlog.Printf(ctx, "setting HTTP status %v", r.status)
226 } else {
227 r.status = uint32(500)
228 r.AddHeader(false, "status", "500")
229 dlog.Printf(ctx, "error setting HTTP status. Cannot parse string %s: %v.", s, err)
230 }
231 }
232
233
234 func (r *Response) GetStatus() uint32 {
235 return r.status
236 }
237
238
239 func (r *Response) GetResponse() *pb.CheckResponse {
240 rs := &pb.CheckResponse{}
241 switch {
242
243 case r.status == http.StatusOK || r.status == 0:
244 rs.Status = &status.Status{Code: int32(code.Code_OK)}
245 rs.HttpResponse = &pb.CheckResponse_OkResponse{
246 OkResponse: &pb.OkHttpResponse{
247 Headers: r.headers,
248 },
249 }
250
251
252 default:
253 rs.Status = &status.Status{Code: int32(code.Code_UNAUTHENTICATED)}
254 rs.HttpResponse = &pb.CheckResponse_DeniedResponse{
255 DeniedResponse: &pb.DeniedHttpResponse{
256 Status: &envoy_type.HttpStatus{
257 Code: envoy_type.StatusCode(r.status),
258 },
259 Headers: r.headers,
260 Body: r.body,
261 },
262 }
263 }
264
265 return rs
266 }
267
View as plain text