...

Source file src/github.com/emissary-ingress/emissary/v3/cmd/kat-server/services/grpc-auth-v2.go

Documentation: github.com/emissary-ingress/emissary/v3/cmd/kat-server/services

     1  package services
     2  
     3  import (
     4  	// stdlib
     5  	"context"
     6  	"crypto/tls"
     7  	"encoding/json"
     8  	"fmt"
     9  	"net/http"
    10  	"strconv"
    11  	"strings"
    12  
    13  	// third party
    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  	// first party (protobuf)
    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  	// first party
    25  	"github.com/datawire/dlib/dgroup"
    26  	"github.com/datawire/dlib/dhttp"
    27  	"github.com/datawire/dlib/dlog"
    28  )
    29  
    30  // GRPCAuthV2 server object (all fields are required).
    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  // Start initializes the HTTP server.
    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) // TODO: do something better
    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) // TODO: do something better
    77  		}
    78  		close(exited)
    79  	}()
    80  	return exited
    81  }
    82  
    83  // Check checks the request object.
    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  	// Sets requested HTTP status.
   104  	rs.SetStatus(ctx, rheader["kat-req-extauth-requested-status"])
   105  
   106  	rs.AddHeader(false, "kat-resp-extauth-protocol-version", g.ProtocolVersion)
   107  
   108  	// Sets requested headers.
   109  	// Don't bother if we'll be returning a pb.CheckResponse_OkResponse; it'd be a no-op in that case.
   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  	// Append requested headers.
   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  	// Sets requested Cookies.
   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  	// Sets requested location.
   134  	if loc := rheader["kat-req-extauth-requested-location"]; loc != "" {
   135  		rs.AddHeader(false, "Location", loc)
   136  	}
   137  
   138  	// Parses request headers.
   139  	headers := make(map[string]interface{})
   140  	for k, v := range rheader {
   141  		headers[k] = strings.Split(v, ",")
   142  	}
   143  
   144  	// Parses request URL.
   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  	// Parses TLS info.
   153  	tls := make(map[string]interface{})
   154  	tls["enabled"] = false
   155  
   156  	// Sets request portion of the results body.
   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  	// Sets results body.
   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  	// Sets response body.
   180  	dlog.Printf(ctx, "setting response body: %s", string(body))
   181  	rs.SetBody(string(body))
   182  
   183  	return rs.GetResponse(), nil
   184  }
   185  
   186  // ResponseV2 constructs an authorization response object.
   187  type ResponseV2 struct {
   188  	headers []*core.HeaderValueOption
   189  	body    string
   190  	status  uint32
   191  }
   192  
   193  // AddHeader adds a header to the response. When append param is true, Envoy will
   194  // append the value to an existent request header instead of overriding it.
   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  // GetHTTPHeaderMap returns HTTP header mapping of the response header-options.
   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  // SetBody sets the authorization response message body.
   216  func (r *ResponseV2) SetBody(s string) {
   217  	r.body = s
   218  }
   219  
   220  // SetStatus sets the authorization response HTTP status code.
   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  // GetStatus returns the authorization response HTTP status code.
   237  func (r *ResponseV2) GetStatus() uint32 {
   238  	return r.status
   239  }
   240  
   241  // GetResponse returns the gRPC authorization response object.
   242  func (r *ResponseV2) GetResponse() *pb.CheckResponse {
   243  	rs := &pb.CheckResponse{}
   244  	switch {
   245  	// Ok respose.
   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  	// Denied response.
   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