diff --git a/README.md b/README.md index 86db3b3..298bb01 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,15 @@ curl \ To open SPYT UI, just paste service domain in your browser. If you have correct cookies in your `baseDomain`, task proxy will use it to auth your browser request. Auth cookie name is specified in Helm chart values in `auth.cookieName` parameter. +You can also use header `x-yt-taskproxy-id` with left part of domain as value (`645236d8` in our example) as alternative way of routing instead of domain. It is suitable in situations +then using domain is not possible, i.e. when your infrastructure doesn't support wildcard domains: +```sh +curl \ + -H "Authorization: Bearer ${IAM_TOKEN}" \ + -H "x-yt-taskproxy-id: 645236d8" \ + "https://task-proxy.my-cluster.ytsaurus.example.net" +``` + ## Extended task proxy annotation format Annotation `task_proxy={enabled=%true}` is minimal required format. Using it, each service given port indexes in order of task appearance and default service names `port_${PORT_INDEX}`, as well as `HTTP` protocol. If you want to specify service names, which port index they use and protocol, you can use extended annotation format. diff --git a/server/pkg/auth.go b/server/pkg/auth.go index 4afc6d0..b3411a2 100644 --- a/server/pkg/auth.go +++ b/server/pkg/auth.go @@ -42,18 +42,21 @@ func (s *authServer) Check(ctx context.Context, req *authv3.CheckRequest) (*auth path := httpAttrs.GetPath() headers := httpAttrs.GetHeaders() - host := httpAttrs.Host - if host == "" { - s.logger.Warnf("authority (host) header is missing in request") + var hash string + if routerHeaderValue, ok := httpAttrs.Headers[routerHeaderName]; ok { + hash = routerHeaderValue + } else if host := httpAttrs.Host; host != "" { + hash = strings.Split(host, ".")[0] + } else { + s.logger.Warnf("authority (host) or %s headers are missing in request", routerHeaderName) return deniedResponse, nil } - s.logger.Debugf("checking auth for host %q, path %q", host, path) + s.logger.Debugf("checking auth for hash %q, path %q", hash, path) - hash := strings.Split(host, ".")[0] task, ok := s.getHashToTasks()[hash] if !ok { - s.logger.Warnf("no entry for host %q in tasks registry", host) + s.logger.Warnf("no entry for hash %q in tasks registry", hash) return deniedResponse, nil } @@ -63,7 +66,7 @@ func (s *authServer) Check(ctx context.Context, req *authv3.CheckRequest) (*auth return okResponse, nil } - s.logger.Debugf("auth for host %q, path %q, task %v", host, path, task) + s.logger.Debugf("auth for hash %q, path %q, task %v", hash, path, task) allowed, err := s.checkOperationPermission(ctx, task.operationID, headers) if err != nil { diff --git a/server/pkg/xds.go b/server/pkg/xds.go index 304561e..24d8740 100644 --- a/server/pkg/xds.go +++ b/server/pkg/xds.go @@ -28,13 +28,17 @@ import ( clustergrpc "github.com/envoyproxy/go-control-plane/envoy/service/cluster/v3" discoverygrpc "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3" listenergrpc "github.com/envoyproxy/go-control-plane/envoy/service/listener/v3" + matcherv3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3" cachetypes "github.com/envoyproxy/go-control-plane/pkg/cache/types" cachev3 "github.com/envoyproxy/go-control-plane/pkg/cache/v3" resourcev3 "github.com/envoyproxy/go-control-plane/pkg/resource/v3" serverv3 "github.com/envoyproxy/go-control-plane/pkg/server/v3" ) -const extAuthClusterName = "extAuthz" +const ( + extAuthClusterName = "extAuthz" + routerHeaderName = "x-yt-taskproxy-id" +) func ServeGRPC(s serverv3.Server, authServer *authServer) error { lis, err := net.Listen("tcp", fmt.Sprintf(":%d", serverPort)) @@ -57,6 +61,9 @@ func ServeGRPC(s serverv3.Server, authServer *authServer) error { func makeSnapshot(hashToTask map[string]Task, version string, baseDomain string, tls bool, authEnabled bool) (*cachev3.Snapshot, error) { var clusters []cachetypes.Resource var vhosts []*routev3.VirtualHost + + var defaultVhostRoutes []*routev3.Route + for hash, task := range hashToTask { grpc := task.protocol == "grpc" vhostName := fmt.Sprintf("%s-%s-%s", task.operationID, task.taskName, task.service) @@ -70,39 +77,60 @@ func makeSnapshot(hashToTask map[string]Task, version string, baseDomain string, Weight: &wrapperspb.UInt32Value{Value: 1}, }) } - + action := &routev3.Route_Route{ + Route: &routev3.RouteAction{ + ClusterSpecifier: &routev3.RouteAction_WeightedClusters{ + WeightedClusters: &routev3.WeightedCluster{ + Clusters: vhostClusters, + }, + }, + }, + } + // route either by domain vhosts = append(vhosts, &routev3.VirtualHost{ Name: vhostName, Domains: []string{getTaskDomain(hash, baseDomain)}, Routes: []*routev3.Route{{ - Match: &routev3.RouteMatch{PathSpecifier: &routev3.RouteMatch_Prefix{Prefix: "/"}}, - Action: &routev3.Route_Route{ - Route: &routev3.RouteAction{ - ClusterSpecifier: &routev3.RouteAction_WeightedClusters{ - WeightedClusters: &routev3.WeightedCluster{ - Clusters: vhostClusters, + Match: &routev3.RouteMatch{PathSpecifier: &routev3.RouteMatch_Prefix{Prefix: "/"}}, + Action: action, + }}, + }) + // ... or by custom header + defaultVhostRoutes = append(defaultVhostRoutes, &routev3.Route{ + Match: &routev3.RouteMatch{ + PathSpecifier: &routev3.RouteMatch_Prefix{Prefix: "/"}, + Headers: []*routev3.HeaderMatcher{ + { + Name: routerHeaderName, + HeaderMatchSpecifier: &routev3.HeaderMatcher_StringMatch{ + StringMatch: &matcherv3.StringMatcher{ + MatchPattern: &matcherv3.StringMatcher_Exact{ + Exact: hash, + }, }, }, }, }, - }}, + }, + Action: action, }) } + defaultVhostRoutes = append(defaultVhostRoutes, &routev3.Route{ + Match: &routev3.RouteMatch{PathSpecifier: &routev3.RouteMatch_Prefix{Prefix: "/"}}, + Action: &routev3.Route_DirectResponse{ + DirectResponse: &routev3.DirectResponseAction{ + Status: 404, + Body: &corev3.DataSource{ + Specifier: &corev3.DataSource_InlineString{InlineString: "no such task"}, + }, + }, + }, + }) vhosts = append(vhosts, &routev3.VirtualHost{ Name: "vhost_default", Domains: []string{"*"}, - Routes: []*routev3.Route{{ - Match: &routev3.RouteMatch{PathSpecifier: &routev3.RouteMatch_Prefix{Prefix: "/"}}, - Action: &routev3.Route_DirectResponse{ - DirectResponse: &routev3.DirectResponseAction{ - Status: 404, - Body: &corev3.DataSource{ - Specifier: &corev3.DataSource_InlineString{InlineString: "no such task"}, - }, - }, - }, - }}, + Routes: defaultVhostRoutes, }) if authEnabled {