...

Source file src/github.com/datawire/ambassador/v2/cmd/kat-server/services/grpc-auth.go

Documentation: github.com/datawire/ambassador/v2/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/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  	// first party
    25  	"github.com/datawire/dlib/dgroup"
    26  	"github.com/datawire/dlib/dhttp"
    27  	"github.com/datawire/dlib/dlog"
    28  )
    29  
    30  // GRPCAUTH server object (all fields are required).
    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  // Start initializes the HTTP server.
    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) // 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 *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  	// Sets requested HTTP status.
   104  	rs.SetStatus(ctx, rheader["requested-status"])
   105  
   106  	rs.AddHeader(false, "x-grpc-service-protocol-version", g.ProtocolVersion)
   107  
   108  	// Sets requested headers.
   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  	// Append requested headers.
   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  	// Sets requested Cookies.
   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  	// Sets requested location.
   131  	if len(rheader["requested-location"]) > 0 {
   132  		rs.AddHeader(false, "Location", rheader["requested-location"])
   133  	}
   134  
   135  	// Parses request headers.
   136  	headers := make(map[string]interface{})
   137  	for k, v := range rheader {
   138  		headers[k] = strings.Split(v, ",")
   139  	}
   140  
   141  	// Parses request URL.
   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  	// Parses TLS info.
   150  	tls := make(map[string]interface{})
   151  	tls["enabled"] = false
   152  
   153  	// Sets request portion of the results body.
   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  	// Sets results body.
   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  	// Sets response body.
   177  	dlog.Printf(ctx, "setting response body: %s", string(body))
   178  	rs.SetBody(string(body))
   179  
   180  	return rs.GetResponse(), nil
   181  }
   182  
   183  // Response constructs an authorization response object.
   184  type Response struct {
   185  	headers []*core.HeaderValueOption
   186  	body    string
   187  	status  uint32
   188  }
   189  
   190  // AddHeader adds a header to the response. When append param is true, Envoy will
   191  // append the value to an existent request header instead of overriding it.
   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  // GetHTTPHeaderMap returns HTTP header mapping of the response header-options.
   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  // SetBody sets the authorization response message body.
   213  func (r *Response) SetBody(s string) {
   214  	r.body = s
   215  }
   216  
   217  // SetStatus sets the authorization response HTTP status code.
   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  // GetStatus returns the authorization response HTTP status code.
   234  func (r *Response) GetStatus() uint32 {
   235  	return r.status
   236  }
   237  
   238  // GetResponse returns the gRPC authorization response object.
   239  func (r *Response) GetResponse() *pb.CheckResponse {
   240  	rs := &pb.CheckResponse{}
   241  	switch {
   242  	// Ok respose.
   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  	// Denied response.
   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