Remove internal notification caller tokens
This commit is contained in:
1 parent
1fe173397f
commit
caad7a3d5b
27 files changed
+112
-165
No files matched your search
@@ -13,13 +13,11 @@ import (
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Address string `yaml:"address"`
|
||||
TokenEnv string `yaml:"token_env"`
|
||||
CAFile string `yaml:"ca_file"`
|
||||
ServerName string `yaml:"server_name"`
|
||||
ActivationURL string `yaml:"activation_url"`
|
||||
@@ -33,15 +31,13 @@ type Sender interface {
|
||||
Send(context.Context, Message) error
|
||||
}
|
||||
type Client struct {
|
||||
conn *grpc.ClientConn
|
||||
rpc pb.NotificationsServiceClient
|
||||
token string
|
||||
conn *grpc.ClientConn
|
||||
rpc pb.NotificationsServiceClient
|
||||
}
|
||||
|
||||
func Open(c Config) (*Client, error) {
|
||||
token := os.Getenv(c.TokenEnv)
|
||||
if c.Address == "" || len(token) < 32 {
|
||||
return nil, merrors.InvalidArgument("notification address and caller token of at least 32 bytes are required")
|
||||
if c.Address == "" {
|
||||
return nil, merrors.InvalidArgument("notification address is required")
|
||||
}
|
||||
tlsConfig := &tls.Config{MinVersion: tls.VersionTLS12, ServerName: c.ServerName}
|
||||
if c.CAFile != "" {
|
||||
@@ -66,13 +62,12 @@ func Open(c Config) (*Client, error) {
|
||||
if err != nil {
|
||||
return nil, merrors.InternalWrap(err, "create notification client")
|
||||
}
|
||||
return &Client{conn: conn, rpc: pb.NewNotificationsServiceClient(conn), token: token}, nil
|
||||
return &Client{conn: conn, rpc: pb.NewNotificationsServiceClient(conn)}, nil
|
||||
}
|
||||
func (c *Client) Close() error { return c.conn.Close() }
|
||||
func (c *Client) Send(ctx context.Context, m Message) error {
|
||||
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
ctx = metadata.AppendToOutgoingContext(ctx, "authorization", "Bearer "+c.token)
|
||||
res, err := c.rpc.Send(ctx, request(m))
|
||||
if err != nil {
|
||||
return merrors.ServiceUnavailableWrap(err, "notification queue unavailable")
|
||||
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
type rpcStub struct {
|
||||
pb.NotificationsServiceClient
|
||||
request *pb.SendRequest
|
||||
auth string
|
||||
auth []string
|
||||
result *pb.SendResponse
|
||||
err error
|
||||
}
|
||||
@@ -31,16 +31,16 @@ type rpcStub struct {
|
||||
func (s *rpcStub) Send(ctx context.Context, r *pb.SendRequest, _ ...grpc.CallOption) (*pb.SendResponse, error) {
|
||||
s.request = r
|
||||
md, _ := metadata.FromOutgoingContext(ctx)
|
||||
s.auth = md.Get("authorization")[0]
|
||||
s.auth = md.Get("authorization")
|
||||
return s.result, s.err
|
||||
}
|
||||
func TestSendMapsOwnedContractAndRejectsFailedReceipt(t *testing.T) {
|
||||
rpc := &rpcStub{result: &pb.SendResponse{Notification: &pb.Notification{Id: "job", Deliveries: []*pb.Delivery{{State: pb.DeliveryState_DELIVERY_STATE_QUEUED}}}}}
|
||||
client := &Client{rpc: rpc, token: "service-secret"}
|
||||
client := &Client{rpc: rpc}
|
||||
expiry := time.Now().Add(time.Minute)
|
||||
m := Message{Key: "key", UserID: "customer", Email: "user@example.com", Locale: "ru", Template: "auth.login", Parameters: map[string]string{"code": "123456"}, ExpiresAt: expiry}
|
||||
require.NoError(t, client.Send(context.Background(), m))
|
||||
require.Equal(t, "Bearer service-secret", rpc.auth)
|
||||
require.Empty(t, rpc.auth)
|
||||
require.Equal(t, m.Email, rpc.request.Targets[0].GetEmail().Address)
|
||||
require.Equal(t, "1", rpc.request.TemplateVersion)
|
||||
require.Equal(t, m.Parameters, rpc.request.Parameters)
|
||||
@@ -50,57 +50,54 @@ func TestSendMapsOwnedContractAndRejectsFailedReceipt(t *testing.T) {
|
||||
rpc.result = nil
|
||||
require.ErrorIs(t, client.Send(context.Background(), m), merrors.ErrServiceUnavailable)
|
||||
}
|
||||
func TestOpenRequiresCredentialsAndValidCA(t *testing.T) {
|
||||
_, err := Open(Config{Address: "localhost:8443", TokenEnv: "ABSENT_NOTIFICATION_TEST_TOKEN"})
|
||||
func TestOpenRequiresAddressAndValidCA(t *testing.T) {
|
||||
_, err := Open(Config{})
|
||||
require.ErrorIs(t, err, merrors.ErrInvalidArg)
|
||||
t.Setenv("TEST_NOTIFICATION_TOKEN", "test-token-that-is-at-least-32-bytes")
|
||||
_, err = Open(Config{Address: "localhost:8443", TokenEnv: "TEST_NOTIFICATION_TOKEN", CAFile: "missing.pem"})
|
||||
_, err = Open(Config{Address: "localhost:8443", CAFile: "missing.pem"})
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
type tlsRPC struct {
|
||||
pb.UnimplementedNotificationsServiceServer
|
||||
auth chan string
|
||||
auth chan []string
|
||||
}
|
||||
|
||||
func (s *tlsRPC) Send(ctx context.Context, _ *pb.SendRequest) (*pb.SendResponse, error) {
|
||||
md, _ := metadata.FromIncomingContext(ctx)
|
||||
s.auth <- md.Get("authorization")[0]
|
||||
s.auth <- md.Get("authorization")
|
||||
return &pb.SendResponse{Notification: &pb.Notification{Id: "tls-job", Deliveries: []*pb.Delivery{{State: pb.DeliveryState_DELIVERY_STATE_QUEUED}}}}, nil
|
||||
}
|
||||
func TestOpenUsesVerifiedTLSAndCallerAuthentication(t *testing.T) {
|
||||
func TestOpenUsesVerifiedTLSWithoutCallerToken(t *testing.T) {
|
||||
fixture := httptest.NewTLSServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}))
|
||||
cert := fixture.TLS.Certificates[0]
|
||||
fixture.Close()
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
server := grpc.NewServer(grpc.Creds(credentials.NewTLS(&tls.Config{Certificates: []tls.Certificate{cert}, MinVersion: tls.VersionTLS12})))
|
||||
rpc := &tlsRPC{auth: make(chan string, 1)}
|
||||
rpc := &tlsRPC{auth: make(chan []string, 1)}
|
||||
pb.RegisterNotificationsServiceServer(server, rpc)
|
||||
go func() { _ = server.Serve(listener) }()
|
||||
t.Cleanup(server.Stop)
|
||||
ca := filepath.Join(t.TempDir(), "ca.pem")
|
||||
require.NoError(t, os.WriteFile(ca, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Certificate[0]}), 0600))
|
||||
t.Setenv("TEST_TLS_CALLER_TOKEN", "test-service-token-with-at-least-32-bytes")
|
||||
client, err := Open(Config{Address: listener.Addr().String(), TokenEnv: "TEST_TLS_CALLER_TOKEN", CAFile: ca})
|
||||
client, err := Open(Config{Address: listener.Addr().String(), CAFile: ca})
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { _ = client.Close() })
|
||||
require.NoError(t, client.Send(context.Background(), Message{ExpiresAt: time.Now().Add(time.Minute)}))
|
||||
require.Equal(t, "Bearer test-service-token-with-at-least-32-bytes", <-rpc.auth)
|
||||
require.Empty(t, <-rpc.auth)
|
||||
}
|
||||
|
||||
func TestOpenUsesPlaintextAndCallerTokenOnPrivateNetwork(t *testing.T) {
|
||||
func TestOpenUsesPlaintextWithoutCallerToken(t *testing.T) {
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
server := grpc.NewServer()
|
||||
rpc := &tlsRPC{auth: make(chan string, 1)}
|
||||
rpc := &tlsRPC{auth: make(chan []string, 1)}
|
||||
pb.RegisterNotificationsServiceServer(server, rpc)
|
||||
go func() { _ = server.Serve(listener) }()
|
||||
t.Cleanup(server.Stop)
|
||||
t.Setenv("TEST_PLAIN_CALLER_TOKEN", "test-private-service-token-at-least-32-bytes")
|
||||
client, err := Open(Config{Address: listener.Addr().String(), TokenEnv: "TEST_PLAIN_CALLER_TOKEN"})
|
||||
client, err := Open(Config{Address: listener.Addr().String()})
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { _ = client.Close() })
|
||||
require.NoError(t, client.Send(context.Background(), Message{ExpiresAt: time.Now().Add(time.Minute)}))
|
||||
require.Equal(t, "Bearer test-private-service-token-at-least-32-bytes", <-rpc.auth)
|
||||
require.Empty(t, <-rpc.auth)
|
||||
}
|
||||
Reference in new issue
Block a user