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/emissary-ingress/emissary/v3/pkg/api/envoy/api/v2/core"
21 pb "github.com/emissary-ingress/emissary/v3/pkg/api/envoy/service/auth/v2"
22 envoy_type "github.com/emissary-ingress/emissary/v3/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 GRPCAuthV2 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 *GRPCAuthV2) Start(ctx context.Context) <-chan bool {
43 dlog.Printf(ctx, "GRPCAuthV2: %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 *GRPCAuthV2) Check(ctx context.Context, r *pb.CheckRequest) (*pb.CheckResponse, error) {
85 rs := &ResponseV2{}
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, "kat-resp-extauth-context-extensions", string(val))
101 }
102
103
104 rs.SetStatus(ctx, rheader["kat-req-extauth-requested-status"])
105
106 rs.AddHeader(false, "kat-resp-extauth-protocol-version", g.ProtocolVersion)
107
108
109
110 if rs.status != http.StatusOK && rs.status != 0 {
111 for _, key := range strings.Split(strings.ToLower(rheader["kat-req-extauth-requested-header"]), ",") {
112 if val := rheader[key]; val != "" {
113 rs.AddHeader(false, key, val)
114 }
115 }
116 }
117
118
119 for _, token := range strings.Split(rheader["kat-req-extauth-append"], ";") {
120 header := strings.Split(strings.TrimSpace(token), "=")
121 if len(header) > 1 {
122 dlog.Printf(ctx, "appending header %s : %s", header[0], header[1])
123 rs.AddHeader(true, header[0], header[1])
124 }
125 }
126
127
128 for _, v := range strings.Split(rheader["kat-req-extauth-requested-cookie"], ",") {
129 val := strings.Trim(v, " ")
130 rs.AddHeader(false, "Set-Cookie", fmt.Sprintf("%s=%s", val, val))
131 }
132
133
134 if loc := rheader["kat-req-extauth-requested-location"]; loc != "" {
135 rs.AddHeader(false, "Location", loc)
136 }
137
138
139 headers := make(map[string]interface{})
140 for k, v := range rheader {
141 headers[k] = strings.Split(v, ",")
142 }
143
144
145 url := make(map[string]interface{})
146 url["fragment"] = r.GetAttributes().GetRequest().GetHttp().GetFragment()
147 url["host"] = r.GetAttributes().GetRequest().GetHttp().GetHost()
148 url["path"] = r.GetAttributes().GetRequest().GetHttp().GetPath()
149 url["query"] = r.GetAttributes().GetRequest().GetHttp().GetQuery()
150 url["scheme"] = r.GetAttributes().GetRequest().GetHttp().GetScheme()
151
152
153 tls := make(map[string]interface{})
154 tls["enabled"] = false
155
156
157 request := make(map[string]interface{})
158 request["url"] = url
159 request["method"] = r.GetAttributes().GetRequest().GetHttp().GetMethod()
160 request["headers"] = headers
161 request["host"] = r.GetAttributes().GetRequest().GetHttp().GetHost()
162 request["tls"] = tls
163
164
165 results := make(map[string]interface{})
166 results["backend"] = g.Backend
167 results["status"] = rs.GetStatus()
168 if len(request) > 0 {
169 results["request"] = request
170 }
171 if rs.GetHTTPHeaderMap() != nil {
172 results["headers"] = *rs.GetHTTPHeaderMap()
173 }
174 body, err := json.MarshalIndent(results, "", " ")
175 if err != nil {
176 body = []byte(fmt.Sprintf("Error: %v", err))
177 }
178
179
180 dlog.Printf(ctx, "setting response body: %s", string(body))
181 rs.SetBody(string(body))
182
183 return rs.GetResponse(), nil
184 }
185
186
187 type ResponseV2 struct {
188 headers []*core.HeaderValueOption
189 body string
190 status uint32
191 }
192
193
194
195 func (r *ResponseV2) AddHeader(a bool, k, v string) {
196 val := &core.HeaderValueOption{
197 Header: &core.HeaderValue{
198 Key: k,
199 Value: v,
200 },
201 Append: &wrapperspb.BoolValue{Value: a},
202 }
203 r.headers = append(r.headers, val)
204 }
205
206
207 func (r *ResponseV2) GetHTTPHeaderMap() *http.Header {
208 h := &http.Header{}
209 for _, v := range r.headers {
210 h.Add(v.Header.Key, v.Header.Value)
211 }
212 return h
213 }
214
215
216 func (r *ResponseV2) SetBody(s string) {
217 r.body = s
218 }
219
220
221 func (r *ResponseV2) SetStatus(ctx context.Context, s string) {
222 if len(s) == 0 {
223 s = "200"
224 }
225 if val, err := strconv.Atoi(s); err == nil {
226 r.status = uint32(val)
227 r.AddHeader(false, "status", s)
228 dlog.Printf(ctx, "setting HTTP status %v", r.status)
229 } else {
230 r.status = uint32(500)
231 r.AddHeader(false, "status", "500")
232 dlog.Printf(ctx, "error setting HTTP status. Cannot parse string %s: %v.", s, err)
233 }
234 }
235
236
237 func (r *ResponseV2) GetStatus() uint32 {
238 return r.status
239 }
240
241
242 func (r *ResponseV2) GetResponse() *pb.CheckResponse {
243 rs := &pb.CheckResponse{}
244 switch {
245
246 case r.status == http.StatusOK || r.status == 0:
247 rs.Status = &status.Status{Code: int32(code.Code_OK)}
248 rs.HttpResponse = &pb.CheckResponse_OkResponse{
249 OkResponse: &pb.OkHttpResponse{
250 Headers: r.headers,
251 },
252 }
253
254
255 default:
256 rs.Status = &status.Status{Code: int32(code.Code_UNAUTHENTICATED)}
257 rs.HttpResponse = &pb.CheckResponse_DeniedResponse{
258 DeniedResponse: &pb.DeniedHttpResponse{
259 Status: &envoy_type.HttpStatus{
260 Code: envoy_type.StatusCode(r.status),
261 },
262 Headers: r.headers,
263 Body: r.body,
264 },
265 }
266 }
267
268 return rs
269 }
270
View as plain text