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