From 5462300cff602ce8f210e6b0a90e16b6e60b148a Mon Sep 17 00:00:00 2001 From: Dan Fuhry Date: Fri, 14 Nov 2025 11:52:07 -0500 Subject: [PATCH] [grpc] support loading ACLs from ephs Supporting changes: - Rearranged grpc into internal/{client,server,common} packages to deal with circular dep between ephs and grpc - Rename `mtls.parseName` -> `mtls.ParseRemoteIdentity` and make the returned struct and its properties public - In ephs server ACLs, try to parse principal; if successful, set `name`, `class` and `domain` substitutions in the key - Fixed severely incorrect invocation of acl.Check in ephs server (not a security issue, legitimate requests were blocked but no illegitimate requests were allowed) - Add `utils/context` package - wrapper for `context` with shared `Interruptible` context that cancels on SIGTERM and SIGINT --- ephs/BUILD.bazel | 3 +- ephs/client.go | 5 +- ephs/servicer/acl.go | 11 ++- ephs/servicer/acl_test.go | 97 ++++++++++++++--------- ephs/servicer/servicer.go | 17 ++-- grpc/BUILD.bazel | 28 +------ grpc/imports.go | 40 ++++++++++ grpc/{ => internal}/acl/BUILD.bazel | 5 +- grpc/{ => internal}/acl/acl_yaml.go | 90 +++++++++++++++++++-- grpc/internal/client/BUILD.bazel | 20 +++++ grpc/{ => internal/client}/client.go | 10 ++- grpc/internal/common/BUILD.bazel | 24 ++++++ grpc/{ => internal/common}/conn_base.go | 2 +- grpc/{ => internal/common}/conn_quic.go | 2 +- grpc/{ => internal/common}/conn_tcp.go | 2 +- grpc/internal/server/BUILD.bazel | 29 +++++++ grpc/{ => internal/server}/context.go | 2 +- grpc/{ => internal/server}/healthcheck.go | 2 +- grpc/{ => internal/server}/server.go | 9 ++- mtls/verify_names.go | 40 +++++----- utils/context/BUILD.bazel | 11 +++ utils/context/imports.go | 24 ++++++ utils/context/interruptible.go | 23 ++++++ 23 files changed, 383 insertions(+), 113 deletions(-) create mode 100644 grpc/imports.go rename grpc/{ => internal}/acl/BUILD.bazel (67%) rename grpc/{ => internal}/acl/acl_yaml.go (60%) create mode 100644 grpc/internal/client/BUILD.bazel rename grpc/{ => internal/client}/client.go (91%) create mode 100644 grpc/internal/common/BUILD.bazel rename grpc/{ => internal/common}/conn_base.go (98%) rename grpc/{ => internal/common}/conn_quic.go (99%) rename grpc/{ => internal/common}/conn_tcp.go (98%) create mode 100644 grpc/internal/server/BUILD.bazel rename grpc/{ => internal/server}/context.go (98%) rename grpc/{ => internal/server}/healthcheck.go (99%) rename grpc/{ => internal/server}/server.go (96%) create mode 100644 utils/context/BUILD.bazel create mode 100644 utils/context/imports.go create mode 100644 utils/context/interruptible.go diff --git a/ephs/BUILD.bazel b/ephs/BUILD.bazel index d5ead53..242cfba 100644 --- a/ephs/BUILD.bazel +++ b/ephs/BUILD.bazel @@ -6,7 +6,8 @@ go_library( importpath = "go.fuhry.dev/runtime/ephs", visibility = ["//visibility:public"], deps = [ - "//grpc", + "//grpc/internal/client", + "//grpc/internal/common", "//mtls", "//proto/service/ephs", "//utils/log", diff --git a/ephs/client.go b/ephs/client.go index baba8bf..0ca364b 100644 --- a/ephs/client.go +++ b/ephs/client.go @@ -12,7 +12,8 @@ import ( "time" "github.com/quic-go/quic-go" - "go.fuhry.dev/runtime/grpc" + grpc "go.fuhry.dev/runtime/grpc/internal/client" + grpc_common "go.fuhry.dev/runtime/grpc/internal/common" "go.fuhry.dev/runtime/mtls" ephs_pb "go.fuhry.dev/runtime/proto/service/ephs" "go.fuhry.dev/runtime/utils/log" @@ -415,7 +416,7 @@ func (c *clientImpl) grpcClient() (ephs_pb.EphsClient, error) { if c.client == nil { c.client, err = grpc.NewGrpcClient(c.defaultCtx, serverId, c.id, - grpc.WithConnectionFactory(&grpc.QUICConnectionFactory{ + grpc.WithConnectionFactory(&grpc_common.QUICConnectionFactory{ QUICConfig: ephsQuicConfig.Clone(), })) if err != nil { diff --git a/ephs/servicer/acl.go b/ephs/servicer/acl.go index e9cf055..5591e7c 100644 --- a/ephs/servicer/acl.go +++ b/ephs/servicer/acl.go @@ -8,6 +8,7 @@ import ( "gopkg.in/yaml.v3" + "go.fuhry.dev/runtime/mtls" "go.fuhry.dev/runtime/utils/stringmatch" ) @@ -66,7 +67,15 @@ func (r *AclRule) Match(principal, key string) bool { return false } - keyMatcher = keyMatcher.Sub(map[string]string{"principal": principal}) + vars := map[string]string{"principal": principal} + + if identity, err := mtls.ParseRemoteIdentity(principal); err == nil { + vars["class"] = identity.Class.String() + vars["domain"] = identity.Domain + vars["name"] = identity.Principal + } + + keyMatcher = keyMatcher.Sub(vars) return keyMatcher.Match(key) } diff --git a/ephs/servicer/acl_test.go b/ephs/servicer/acl_test.go index d744c7a..6dba593 100644 --- a/ephs/servicer/acl_test.go +++ b/ephs/servicer/acl_test.go @@ -9,95 +9,116 @@ import ( const testRules = ` rules: - principal: - exact: bob + exact: spiffe://domain.example.com/user/bob key: - exact: /{{principal}}/can/write/this + exact: "{{name}}/can/write/this" - principal: any: true key: - exact: /writable/by/self/{{principal}} + exact: writable/by/self/{{name}} - principal: or: - - exact: bob - - exact: suzie + - exact: spiffe://domain.example.com/user/bob + - exact: spiffe://domain.example.com/user/suzie key: - prefix: /or/test/{{principal}}/ + prefix: or/test/{{name}}/ + - principal: + prefix: spiffe://domain.example.com/service/ + key: + exact: acl/{{name}}.yaml ` func TestAcl(t *testing.T) { + assert := assert.New(t) + type testCase struct { description, princ, key string expect bool } acl, err := LoadAclString(testRules) - assert.NoError(t, err) + assert.NoError(err) + if err != nil { + return + } testCases := []testCase{ { - "bob has access to /bob/can/write/this", - "bob", - "/bob/can/write/this", + "bob has access to bob/can/write/this", + "spiffe://domain.example.com/user/bob", + "bob/can/write/this", true, }, { - "bob does not have access to /suzie/can/write/this", - "bob", - "/suzie/can/write/this", + "bob does not have access to suzie/can/write/this", + "spiffe://domain.example.com/user/bob", + "suzie/can/write/this", false, }, { - "suzie does not have access to /suzie/can/write/this", - "suzie", - "/suzie/can/write/this", + "suzie does not have access to suzie/can/write/this", + "spiffe://domain.example.com/user/suzie", + "suzie/can/write/this", false, }, { - "bob has access to /writable/by/self/bob", - "bob", - "/writable/by/self/bob", + "bob has access to writable/by/self/bob", + "spiffe://domain.example.com/user/bob", + "writable/by/self/bob", true, }, { - "suzie has access to /writable/by/self/suzie", - "suzie", - "/writable/by/self/suzie", + "suzie has access to writable/by/self/suzie", + "spiffe://domain.example.com/user/suzie", + "writable/by/self/suzie", true, }, { - "bob has access to /writable/by/self/suzie", - "suzie", - "/writable/by/self/suzie", + "bob has access to writable/by/self/suzie", + "spiffe://domain.example.com/user/suzie", + "writable/by/self/suzie", true, }, { - "bob has access to /or/test/bob/foo", - "bob", - "/or/test/bob/foo", + "bob has access to or/test/bob/foo", + "spiffe://domain.example.com/user/bob", + "or/test/bob/foo", true, }, { - "suzie has access to /or/test/suzie/foo", - "suzie", - "/or/test/suzie/foo", + "suzie has access to or/test/suzie/foo", + "spiffe://domain.example.com/user/suzie", + "or/test/suzie/foo", true, }, { - "frank does not have access to /or/test/frank/foo", - "frank", - "/or/test/frank/foo", + "frank does not have access to or/test/frank/foo", + "spiffe://domain.example.com/user/frank", + "or/test/frank/foo", + false, + }, + { + "bob does not have access no/rule/for/this", + "spiffe://domain.example.com/user/bob", + "no/rule/for/this", false, }, { - "bob does not have access /no/rule/for/this", - "bob", - "/no/rule/for/this", + "bob has access to acl/bob.yaml", + "spiffe://domain.example.com/service/bob", + "acl/bob.yaml", + true, + }, + { + "bob has no access to acl/frank.yaml", + "spiffe://domain.example.com/service/bob", + "acl/frank.yaml", false, }, } for i, tc := range testCases { - assert.Equal(t, tc.expect, acl.Check(tc.princ, tc.key), + assert.Equal(tc.expect, acl.Check(tc.princ, tc.key), "test case %d: %s", i, tc.description) } } diff --git a/ephs/servicer/servicer.go b/ephs/servicer/servicer.go index a95d72a..d1f47c7 100644 --- a/ephs/servicer/servicer.go +++ b/ephs/servicer/servicer.go @@ -170,22 +170,23 @@ func (s *ephsServicer) checkPath(ctx context.Context, path string) (string, erro return "", err } - aclPath := strings.Join(parts[2:], "/") + aclPath := strings.Join(parts[3:], "/") + + if parts[2] == "local" { + parts[2] = cell + } if s.acl != nil { if !s.acl.Check(ident, aclPath) { return "", status.Errorf( codes.PermissionDenied, - "access to path %q denied for %q", - aclPath, - ident) + "access to path %q denied for %q (rule path: %q)", + strings.Join(parts, "/"), + ident, + aclPath) } } - if parts[2] == "local" { - parts[2] = cell - } - return strings.Join(parts, "/"), nil } diff --git a/grpc/BUILD.bazel b/grpc/BUILD.bazel index 9d13723..736056e 100644 --- a/grpc/BUILD.bazel +++ b/grpc/BUILD.bazel @@ -2,32 +2,12 @@ load("@rules_go//go:def.bzl", "go_library") go_library( name = "grpc", - srcs = [ - "client.go", - "conn_base.go", - "conn_quic.go", - "conn_tcp.go", - "context.go", - "healthcheck.go", - "server.go", - ], + srcs = ["imports.go"], importpath = "go.fuhry.dev/runtime/grpc", visibility = ["//visibility:public"], deps = [ - "//grpc/acl", - "//mtls", - "//mtls/certutil", - "//sd", - "//utils/hostname", - "//utils/log", - "@com_github_hashicorp_golang_lru_v2//:golang-lru", - "@com_github_quic_go_quic_go//:quic-go", - "@dev_fuhry_go_grpc_quic//:grpc-quic", - "@org_golang_google_grpc//:grpc", - "@org_golang_google_grpc//codes", - "@org_golang_google_grpc//credentials", - "@org_golang_google_grpc//health/grpc_health_v1", - "@org_golang_google_grpc//peer", - "@org_golang_google_grpc//status", + "//grpc/internal/client", + "//grpc/internal/common", + "//grpc/internal/server", ], ) diff --git a/grpc/imports.go b/grpc/imports.go new file mode 100644 index 0000000..6c2de64 --- /dev/null +++ b/grpc/imports.go @@ -0,0 +1,40 @@ +package grpc + +import ( + "go.fuhry.dev/runtime/grpc/internal/client" + "go.fuhry.dev/runtime/grpc/internal/common" + "go.fuhry.dev/runtime/grpc/internal/server" +) + +// type aliases: common +type ConnectionFactory = common.ConnectionFactory +type ContextDialer = common.ContextDialer +type QUICConnectionFactory = common.QUICConnectionFactory +type TCPConnectionFactory = common.TCPConnectionFactory + +// type aliases: client +type Client = client.Client +type ClientOption = client.ClientOption +type AddressProvider = client.AddressProvider +type ClientConn = client.ClientConn + +// type aliases: server +type Server = server.Server + +// function aliases: common +var NewDefaultConnectionFactory = common.NewDefaultConnectionFactory +var RegisterTransport = common.RegisterTransport + +// function aliases: client +var WithConnectionFactory = client.WithConnectionFactory +var WithAddressProvider = client.WithAddressProvider +var WithStaticAddress = client.WithStaticAddress + +var NewGrpcClient = client.NewGrpcClient + +// function aliases: server +var RandomPort = server.RandomPort +var NewGrpcServer = server.NewGrpcServer +var NewGrpcServerWithPort = server.NewGrpcServerWithPort +var PeerIdentity = server.PeerIdentity +var NewHealthCheckServicer = server.NewHealthCheckServicer diff --git a/grpc/acl/BUILD.bazel b/grpc/internal/acl/BUILD.bazel similarity index 67% rename from grpc/acl/BUILD.bazel rename to grpc/internal/acl/BUILD.bazel index d3e0896..f190af5 100644 --- a/grpc/acl/BUILD.bazel +++ b/grpc/internal/acl/BUILD.bazel @@ -3,11 +3,14 @@ load("@rules_go//go:def.bzl", "go_library") go_library( name = "acl", srcs = ["acl_yaml.go"], - importpath = "go.fuhry.dev/runtime/grpc/acl", + importpath = "go.fuhry.dev/runtime/grpc/internal/acl", visibility = ["//visibility:public"], deps = [ + "//config_watcher", "//constants", + "//ephs", "//mtls", + "//utils/context", "//utils/log", "@in_gopkg_yaml_v3//:yaml_v3", ], diff --git a/grpc/acl/acl_yaml.go b/grpc/internal/acl/acl_yaml.go similarity index 60% rename from grpc/acl/acl_yaml.go rename to grpc/internal/acl/acl_yaml.go index 648ef44..9fc0591 100644 --- a/grpc/acl/acl_yaml.go +++ b/grpc/internal/acl/acl_yaml.go @@ -2,13 +2,17 @@ package acl import ( "fmt" + "io" "net/url" "os" "path" "strings" + "go.fuhry.dev/runtime/config_watcher" "go.fuhry.dev/runtime/constants" + "go.fuhry.dev/runtime/ephs" "go.fuhry.dev/runtime/mtls" + "go.fuhry.dev/runtime/utils/context" "go.fuhry.dev/runtime/utils/log" "gopkg.in/yaml.v3" ) @@ -39,6 +43,8 @@ type aclEntry struct { // "principal" - the thing to allow type aclYaml struct { Services map[string][]*aclEntry `yaml:",inline"` + + logger log.Logger } var aclSearchPaths = []string{ @@ -48,15 +54,30 @@ var aclSearchPaths = []string{ func TryLoadAcl(serverId mtls.Identity) ACLChecker { logger := log.WithPrefix("ACLChecker") + var ( + fsErr, ephsErr error + ) for _, dir := range aclSearchPaths { path := path.Join(dir, serverId.Name()+"_acl.yaml") - if ay, err := loadAclFromPath(path); err == nil { - logger.V(1).Infof("loaded ACLs from %s", path) + if ay, err := loadAclFromPath(serverId, path); err == nil { + logger.V(1).Infof("loaded ACLs from filesystem @ %s", path) return ay + } else { + fsErr = err } } - logger.V(1).Infof("using default ACLs for server %s", serverId.Name()) + if ay, err := loadAclFromEphs(serverId); err == nil { + logger.V(1).Infof("loaded ACLs for service %q from ephs", serverId.Name()) + return ay + } else { + ephsErr = err + } + + logger.V(1).Infof( + "using default ACLs for server %s\n filesystem err: %v (paths: %+v)\n ephs err: %v", + serverId.Name(), fsErr, aclSearchPaths, ephsErr) + return &aclYaml{ Services: map[string][]*aclEntry{ "DEFAULT": { @@ -65,24 +86,83 @@ func TryLoadAcl(serverId mtls.Identity) ACLChecker { }, }, }, + + logger: log.Default().WithPrefix(fmt.Sprintf("aclYaml(%s, )", serverId.Name())), } } -func loadAclFromPath(path string) (*aclYaml, error) { +func loadAclFromPath(serverId mtls.Identity, path string) (*aclYaml, error) { contents, err := os.ReadFile(path) if err != nil { return nil, err } - ay := &aclYaml{} + ay := &aclYaml{ + logger: log.Default().WithPrefix(fmt.Sprintf("aclYaml(%s, %q)", serverId.Name(), path)), + } + err = yaml.Unmarshal(contents, ay) if err != nil { return nil, err } + ctx, _ := context.Interruptible() + if w, err := config_watcher.Watch(ctx, path); err == nil { + go ay.watch(w) + } + return ay, nil } +func loadAclFromEphs(serverId mtls.Identity) (*aclYaml, error) { + ephsClient, err := ephs.DefaultClient() + if err != nil { + return nil, err + } + + ephsPath := fmt.Sprintf("/ephs/local/acl/%s.yaml", serverId.Name()) + reader, err := ephsClient.Get(ephsPath) + if err != nil { + return nil, err + } + + contents, err := io.ReadAll(reader) + if err != nil { + return nil, err + } + + ay := &aclYaml{ + logger: log.Default().WithPrefix(fmt.Sprintf("aclYaml(%s, %q)", serverId.Name(), ephsPath)), + } + + err = yaml.Unmarshal(contents, ay) + if err != nil { + return nil, err + } + + ctx, _ := context.Interruptible() + if w, err := config_watcher.Watch(ctx, ephsPath); err == nil { + go ay.watch(w) + } + + return ay, nil +} + +func (ay *aclYaml) watch(cw config_watcher.ConfigWatcher) { + for { + update, err := cw.Next() + if err != nil { + return + } + + if err := yaml.Unmarshal(update.Contents, ay); err != nil { + ay.logger.Warningf("failed to parse reloaded ACLs: %v", err) + } else { + ay.logger.Notice("ACLs reloaded") + } + } +} + func (ay *aclYaml) Check(method string, spiffe *url.URL) error { logger := log.WithPrefix("ACLChecker") diff --git a/grpc/internal/client/BUILD.bazel b/grpc/internal/client/BUILD.bazel new file mode 100644 index 0000000..42d11ce --- /dev/null +++ b/grpc/internal/client/BUILD.bazel @@ -0,0 +1,20 @@ +load("@rules_go//go:def.bzl", "go_library") + +package( + default_visibility = [ + "//ephs:__subpackages__", + "//grpc:__subpackages__", + ], +) + +go_library( + name = "client", + srcs = ["client.go"], + importpath = "go.fuhry.dev/runtime/grpc/internal/client", + deps = [ + "//grpc/internal/common", + "//mtls", + "//sd", + "@org_golang_google_grpc//:grpc", + ], +) diff --git a/grpc/client.go b/grpc/internal/client/client.go similarity index 91% rename from grpc/client.go rename to grpc/internal/client/client.go index 93072f5..bc54369 100644 --- a/grpc/client.go +++ b/grpc/internal/client/client.go @@ -1,16 +1,18 @@ -package grpc +package client import ( "context" "fmt" "net" + "go.fuhry.dev/runtime/grpc/internal/common" "go.fuhry.dev/runtime/mtls" "go.fuhry.dev/runtime/sd" "google.golang.org/grpc" ) type ClientConn = grpc.ClientConn +type ConnectionFactory = common.ConnectionFactory type Client interface { Conn() (*grpc.ClientConn, error) @@ -37,7 +39,7 @@ type client struct { serverId mtls.Identity clientId mtls.Identity watcher AddressProvider - connFac ConnectionFactory + connFac common.ConnectionFactory } type staticAddressProvider struct { @@ -48,7 +50,7 @@ func (s *staticAddressProvider) GetAddrs(_ context.Context) ([]sd.ServiceAddress return s.addresses, nil } -func WithConnectionFactory(fac ConnectionFactory) ClientOption { +func WithConnectionFactory(fac common.ConnectionFactory) ClientOption { return &clientOption{ f: func(c *client) error { c.connFac = fac @@ -98,7 +100,7 @@ func NewGrpcClient(ctx context.Context, serverId, clientId mtls.Identity, opts . ctx: ctx, serverId: serverId, clientId: clientId, - connFac: NewDefaultConnectionFactory(), + connFac: common.NewDefaultConnectionFactory(), } for _, opt := range opts { diff --git a/grpc/internal/common/BUILD.bazel b/grpc/internal/common/BUILD.bazel new file mode 100644 index 0000000..024c911 --- /dev/null +++ b/grpc/internal/common/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_go//go:def.bzl", "go_library") + +package( + default_visibility = [ + "//ephs:__subpackages__", + "//grpc:__subpackages__", + ], +) + +go_library( + name = "common", + srcs = [ + "conn_base.go", + "conn_quic.go", + "conn_tcp.go", + ], + importpath = "go.fuhry.dev/runtime/grpc/internal/common", + deps = [ + "//utils/log", + "@com_github_quic_go_quic_go//:quic-go", + "@dev_fuhry_go_grpc_quic//:grpc-quic", + "@org_golang_google_grpc//credentials", + ], +) diff --git a/grpc/conn_base.go b/grpc/internal/common/conn_base.go similarity index 98% rename from grpc/conn_base.go rename to grpc/internal/common/conn_base.go index ef76db6..be0d682 100644 --- a/grpc/conn_base.go +++ b/grpc/internal/common/conn_base.go @@ -1,4 +1,4 @@ -package grpc +package common import ( "context" diff --git a/grpc/conn_quic.go b/grpc/internal/common/conn_quic.go similarity index 99% rename from grpc/conn_quic.go rename to grpc/internal/common/conn_quic.go index c949686..76eba44 100644 --- a/grpc/conn_quic.go +++ b/grpc/internal/common/conn_quic.go @@ -1,4 +1,4 @@ -package grpc +package common import ( "context" diff --git a/grpc/conn_tcp.go b/grpc/internal/common/conn_tcp.go similarity index 98% rename from grpc/conn_tcp.go rename to grpc/internal/common/conn_tcp.go index 048b26a..32d2456 100644 --- a/grpc/conn_tcp.go +++ b/grpc/internal/common/conn_tcp.go @@ -1,4 +1,4 @@ -package grpc +package common import ( "context" diff --git a/grpc/internal/server/BUILD.bazel b/grpc/internal/server/BUILD.bazel new file mode 100644 index 0000000..b7a1bb2 --- /dev/null +++ b/grpc/internal/server/BUILD.bazel @@ -0,0 +1,29 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "server", + srcs = [ + "context.go", + "healthcheck.go", + "server.go", + ], + importpath = "go.fuhry.dev/runtime/grpc/internal/server", + visibility = ["//grpc:__subpackages__"], + deps = [ + "//grpc/internal/acl", + "//grpc/internal/common", + "//mtls", + "//mtls/certutil", + "//sd", + "//utils/hostname", + "//utils/log", + "@com_github_hashicorp_golang_lru_v2//:golang-lru", + "@dev_fuhry_go_grpc_quic//:grpc-quic", + "@org_golang_google_grpc//:grpc", + "@org_golang_google_grpc//codes", + "@org_golang_google_grpc//credentials", + "@org_golang_google_grpc//health/grpc_health_v1", + "@org_golang_google_grpc//peer", + "@org_golang_google_grpc//status", + ], +) diff --git a/grpc/context.go b/grpc/internal/server/context.go similarity index 98% rename from grpc/context.go rename to grpc/internal/server/context.go index 897e345..4a92d58 100644 --- a/grpc/context.go +++ b/grpc/internal/server/context.go @@ -1,4 +1,4 @@ -package grpc +package server import ( "context" diff --git a/grpc/healthcheck.go b/grpc/internal/server/healthcheck.go similarity index 99% rename from grpc/healthcheck.go rename to grpc/internal/server/healthcheck.go index ce8b14b..c6e3d67 100644 --- a/grpc/healthcheck.go +++ b/grpc/internal/server/healthcheck.go @@ -1,4 +1,4 @@ -package grpc +package server import ( "context" diff --git a/grpc/server.go b/grpc/internal/server/server.go similarity index 96% rename from grpc/server.go rename to grpc/internal/server/server.go index 9e56c73..7ce5aef 100644 --- a/grpc/server.go +++ b/grpc/internal/server/server.go @@ -1,4 +1,4 @@ -package grpc +package server import ( "context" @@ -10,7 +10,8 @@ import ( lru "github.com/hashicorp/golang-lru/v2" grpc_quic "go.fuhry.dev/grpc-quic" - "go.fuhry.dev/runtime/grpc/acl" + "go.fuhry.dev/runtime/grpc/internal/acl" + "go.fuhry.dev/runtime/grpc/internal/common" "go.fuhry.dev/runtime/mtls" "go.fuhry.dev/runtime/mtls/certutil" "go.fuhry.dev/runtime/sd" @@ -33,7 +34,7 @@ type Server struct { acl acl.ACLChecker log log.Logger sessions *lru.Cache[string, *session] - connFac ConnectionFactory + connFac common.ConnectionFactory hc HealthCheckServicer } @@ -86,7 +87,7 @@ func NewGrpcServerWithPort(id mtls.Identity, port uint16) (*Server, error) { verifier: cv, log: log.WithPrefix(fmt.Sprintf("grpcServer:%s", id.Name())), sessions: sessionsLru, - connFac: NewDefaultConnectionFactory(), + connFac: common.NewDefaultConnectionFactory(), hc: NewHealthCheckServicer(), } diff --git a/mtls/verify_names.go b/mtls/verify_names.go index 57796ce..9684548 100644 --- a/mtls/verify_names.go +++ b/mtls/verify_names.go @@ -13,10 +13,10 @@ import ( type IdentityClass uint -type remoteIdentity struct { - class IdentityClass - domain string - princ string +type RemoteIdentity struct { + Class IdentityClass + Domain string + Principal string } const ( @@ -198,19 +198,19 @@ func (cv *mtlsPeerVerifier) AllowFrom(class IdentityClass, principals ...string) } func (cv *mtlsPeerVerifier) checkName(name string) error { - id, err := parseName(name) + id, err := ParseRemoteIdentity(name) if err != nil { return err } - cv.log.V(3).Debugf("parsed name %q to %+v (class=%s)", name, id, id.class.String()) + cv.log.V(3).Debugf("parsed name %q to %+v (class=%s)", name, id, id.Class.String()) if _, ok := cv.allowedPrincipals[All]; ok { return nil } if allowedDomains, ok := cv.allowedPrincipals[Domain]; ok { - if allowedDomains.Contains(id.domain) { - cv.log.V(3).Debugf("domain %q exactly matched allowlist", id.domain) + if allowedDomains.Contains(id.Domain) { + cv.log.V(3).Debugf("domain %q exactly matched allowlist", id.Domain) return nil } domainOk := false @@ -218,30 +218,30 @@ func (cv *mtlsPeerVerifier) checkName(name string) error { if val[0] != '.' { continue } - if strings.HasSuffix(id.domain, val) { - cv.log.V(3).Debugf("domain %q matched suffix rule %q", id.domain, val) + if strings.HasSuffix(id.Domain, val) { + cv.log.V(3).Debugf("domain %q matched suffix rule %q", id.Domain, val) domainOk = true break } } if !domainOk { - return fmt.Errorf("trust domain %q is not allowed to authenticate to this service", id.domain) + return fmt.Errorf("trust domain %q is not allowed to authenticate to this service", id.Domain) } } - if allowedPrincipals, ok := cv.allowedPrincipals[id.class]; ok { - if !allowedPrincipals.Contains(id.princ) { - return fmt.Errorf("principal %q is not allowed to authenticate to this service", id.princ) + if allowedPrincipals, ok := cv.allowedPrincipals[id.Class]; ok { + if !allowedPrincipals.Contains(id.Principal) { + return fmt.Errorf("principal %q is not allowed to authenticate to this service", id.Principal) } - cv.log.V(3).Debugf("principal %q matched allowlist", id.princ) + cv.log.V(3).Debugf("principal %q matched allowlist", id.Principal) return nil } return fmt.Errorf("principals of this type are not allowed") } -func parseName(name string) (*remoteIdentity, error) { +func ParseRemoteIdentity(name string) (*RemoteIdentity, error) { exps := []struct { expr *regexp.Regexp class IdentityClass @@ -267,10 +267,10 @@ func parseName(name string) (*remoteIdentity, error) { parts := e.expr.FindStringSubmatch(name) iDomain, iPrinc := e.expr.SubexpIndex("domain"), e.expr.SubexpIndex("principal") - return &remoteIdentity{ - class: e.class, - domain: parts[iDomain], - princ: parts[iPrinc], + return &RemoteIdentity{ + Class: e.class, + Domain: parts[iDomain], + Principal: parts[iPrinc], }, nil } diff --git a/utils/context/BUILD.bazel b/utils/context/BUILD.bazel new file mode 100644 index 0000000..d91ba7c --- /dev/null +++ b/utils/context/BUILD.bazel @@ -0,0 +1,11 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "context", + srcs = [ + "imports.go", + "interruptible.go", + ], + importpath = "go.fuhry.dev/runtime/utils/context", + visibility = ["//visibility:public"], +) diff --git a/utils/context/imports.go b/utils/context/imports.go new file mode 100644 index 0000000..992768a --- /dev/null +++ b/utils/context/imports.go @@ -0,0 +1,24 @@ +package context + +import ( + "context" +) + +type Context = context.Context +type CancelFunc = context.CancelFunc +type CancelCauseFunc = context.CancelCauseFunc + +var Canceled = context.Canceled +var DeadlineExceeded = context.DeadlineExceeded + +var Background = context.Background +var TODO = context.TODO + +var AfterFunc = context.AfterFunc +var Cause = context.Cause +var WithCancel = context.WithCancel +var WithCancelCause = context.WithCancelCause +var WithDeadline = context.WithDeadline +var WithDeadlineCause = context.WithDeadlineCause +var WithTimeout = context.WithTimeout +var WithValue = context.WithValue diff --git a/utils/context/interruptible.go b/utils/context/interruptible.go new file mode 100644 index 0000000..1b6383f --- /dev/null +++ b/utils/context/interruptible.go @@ -0,0 +1,23 @@ +package context + +import ( + "os/signal" + "sync" + "syscall" +) + +var interruptibleCtx Context +var interruptibleCancel CancelFunc +var interruptibleOnce sync.Once + +func Interruptible() (Context, CancelFunc) { + interruptibleOnce.Do(func() { + interruptibleCtx, interruptibleCancel = signal.NotifyContext( + Background(), + syscall.SIGINT, + syscall.SIGTERM, + ) + }) + + return interruptibleCtx, interruptibleCancel +} -- 2.50.1