2023-03-28 18:39:22 +00:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
2023-08-11 13:12:13 +00:00
|
|
|
// SPDX-License-Identifier: BUSL-1.1
|
2023-03-28 18:39:22 +00:00
|
|
|
|
2022-05-04 16:38:45 +00:00
|
|
|
package acl
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/stretchr/testify/mock"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"google.golang.org/grpc"
|
|
|
|
"google.golang.org/grpc/codes"
|
|
|
|
"google.golang.org/grpc/status"
|
|
|
|
|
|
|
|
"github.com/hashicorp/go-hclog"
|
|
|
|
|
|
|
|
"github.com/hashicorp/consul/acl"
|
|
|
|
"github.com/hashicorp/consul/agent/consul/auth"
|
2022-07-13 15:33:48 +00:00
|
|
|
"github.com/hashicorp/consul/agent/grpc-external/testutils"
|
2022-05-04 16:38:45 +00:00
|
|
|
"github.com/hashicorp/consul/agent/structs"
|
|
|
|
"github.com/hashicorp/consul/proto-public/pbacl"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestServer_Logout_Success(t *testing.T) {
|
|
|
|
secretID := generateID(t)
|
|
|
|
|
|
|
|
tokenWriter := NewMockTokenWriter(t)
|
|
|
|
tokenWriter.On("Delete", secretID, true).Return(nil)
|
|
|
|
|
|
|
|
server := NewServer(Config{
|
|
|
|
ACLsEnabled: true,
|
|
|
|
InPrimaryDatacenter: true,
|
|
|
|
ForwardRPC: noopForwardRPC,
|
|
|
|
LocalTokensEnabled: noopLocalTokensEnabled,
|
|
|
|
Logger: hclog.NewNullLogger(),
|
|
|
|
NewTokenWriter: func() TokenWriter { return tokenWriter },
|
|
|
|
})
|
|
|
|
|
|
|
|
_, err := server.Logout(context.Background(), &pbacl.LogoutRequest{
|
|
|
|
Token: secretID,
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestServer_Logout_EmptyToken(t *testing.T) {
|
|
|
|
server := NewServer(Config{
|
|
|
|
ACLsEnabled: true,
|
|
|
|
Logger: hclog.NewNullLogger(),
|
|
|
|
})
|
|
|
|
|
|
|
|
_, err := server.Logout(context.Background(), &pbacl.LogoutRequest{
|
|
|
|
Token: "",
|
|
|
|
})
|
|
|
|
require.Error(t, err)
|
|
|
|
require.Equal(t, codes.InvalidArgument.String(), status.Code(err).String())
|
|
|
|
require.Contains(t, err.Error(), "token is required")
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestServer_Logout_ACLsDisabled(t *testing.T) {
|
|
|
|
server := NewServer(Config{
|
|
|
|
ACLsEnabled: false,
|
|
|
|
Logger: hclog.NewNullLogger(),
|
|
|
|
ValidateEnterpriseRequest: noopValidateEnterpriseRequest,
|
|
|
|
ForwardRPC: noopForwardRPC,
|
|
|
|
LocalTokensEnabled: noopLocalTokensEnabled,
|
|
|
|
})
|
|
|
|
|
|
|
|
_, err := server.Logout(context.Background(), &pbacl.LogoutRequest{
|
|
|
|
Token: generateID(t),
|
|
|
|
})
|
|
|
|
require.Error(t, err)
|
|
|
|
require.Equal(t, codes.FailedPrecondition.String(), status.Code(err).String())
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestServer_Logout_LocalTokensDisabled(t *testing.T) {
|
|
|
|
server := NewServer(Config{
|
|
|
|
ACLsEnabled: true,
|
|
|
|
Logger: hclog.NewNullLogger(),
|
|
|
|
ForwardRPC: noopForwardRPC,
|
|
|
|
LocalTokensEnabled: func() bool { return false },
|
|
|
|
})
|
|
|
|
|
|
|
|
_, err := server.Logout(context.Background(), &pbacl.LogoutRequest{
|
|
|
|
Token: generateID(t),
|
|
|
|
})
|
|
|
|
require.Error(t, err)
|
|
|
|
require.Equal(t, codes.FailedPrecondition.String(), status.Code(err).String())
|
|
|
|
require.Contains(t, err.Error(), "token replication is required")
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestServer_Logout_NoSuchToken(t *testing.T) {
|
|
|
|
tokenWriter := NewMockTokenWriter(t)
|
|
|
|
tokenWriter.On("Delete", mock.Anything, true).Return(acl.ErrNotFound)
|
|
|
|
|
|
|
|
server := NewServer(Config{
|
|
|
|
ACLsEnabled: true,
|
|
|
|
Logger: hclog.NewNullLogger(),
|
|
|
|
ForwardRPC: noopForwardRPC,
|
|
|
|
LocalTokensEnabled: noopLocalTokensEnabled,
|
|
|
|
NewTokenWriter: func() TokenWriter { return tokenWriter },
|
|
|
|
})
|
|
|
|
|
|
|
|
_, err := server.Logout(context.Background(), &pbacl.LogoutRequest{
|
|
|
|
Token: generateID(t),
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestServer_Logout_PermissionDenied(t *testing.T) {
|
|
|
|
tokenWriter := NewMockTokenWriter(t)
|
|
|
|
tokenWriter.On("Delete", mock.Anything, true).Return(acl.ErrPermissionDenied)
|
|
|
|
|
|
|
|
server := NewServer(Config{
|
|
|
|
ACLsEnabled: true,
|
|
|
|
InPrimaryDatacenter: true,
|
|
|
|
ForwardRPC: noopForwardRPC,
|
|
|
|
LocalTokensEnabled: noopLocalTokensEnabled,
|
|
|
|
Logger: hclog.NewNullLogger(),
|
|
|
|
NewTokenWriter: func() TokenWriter { return tokenWriter },
|
|
|
|
})
|
|
|
|
|
|
|
|
_, err := server.Logout(context.Background(), &pbacl.LogoutRequest{
|
|
|
|
Token: generateID(t),
|
|
|
|
})
|
|
|
|
require.Error(t, err)
|
|
|
|
require.Equal(t, codes.PermissionDenied.String(), status.Code(err).String())
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestServer_Logout_RPCForwarding(t *testing.T) {
|
|
|
|
tokenWriter := NewMockTokenWriter(t)
|
|
|
|
tokenWriter.On("Delete", mock.Anything, true).Return(nil)
|
|
|
|
|
|
|
|
dc1 := NewServer(Config{
|
|
|
|
ACLsEnabled: true,
|
|
|
|
Logger: hclog.NewNullLogger(),
|
|
|
|
NewTokenWriter: func() TokenWriter { return tokenWriter },
|
|
|
|
ForwardRPC: noopForwardRPC,
|
|
|
|
LocalTokensEnabled: func() bool { return true },
|
|
|
|
})
|
|
|
|
|
|
|
|
dc1Conn, err := grpc.Dial(
|
|
|
|
testutils.RunTestServer(t, dc1).String(),
|
2022-11-07 16:34:30 +00:00
|
|
|
//nolint:staticcheck
|
2022-05-04 16:38:45 +00:00
|
|
|
grpc.WithInsecure(),
|
|
|
|
)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
dc2 := NewServer(Config{
|
|
|
|
ACLsEnabled: true,
|
|
|
|
Logger: hclog.NewNullLogger(),
|
|
|
|
ForwardRPC: func(rpcInfo structs.RPCInfo, fn func(*grpc.ClientConn) error) (bool, error) {
|
|
|
|
return true, fn(dc1Conn)
|
|
|
|
},
|
|
|
|
})
|
|
|
|
_, err = dc2.Logout(context.Background(), &pbacl.LogoutRequest{
|
|
|
|
Token: generateID(t),
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestServer_Logout_GlobalWritesForwardedToPrimaryDC(t *testing.T) {
|
|
|
|
tokenWriter := NewMockTokenWriter(t)
|
|
|
|
tokenWriter.On("Delete", mock.Anything, true).Return(nil)
|
|
|
|
|
|
|
|
// This test checks that requests to delete global tokens are forwared to the
|
|
|
|
// primary datacenter by:
|
|
|
|
//
|
|
|
|
// 1. Setting up 2 servers (1 in the primary DC, 1 in the secondary).
|
|
|
|
// 2. Making a logout request to the secondary DC.
|
|
|
|
// 3. Mocking TokenWriter.Delete to return ErrCannotWriteGlobalToken in the
|
|
|
|
// secondary DC.
|
|
|
|
// 4. Checking that the primary DC server's TokenWriter receives a call to
|
|
|
|
// Delete.
|
|
|
|
// 5. Capturing the forwarded request's Datacenter in the primary DC server's
|
|
|
|
// ForwardRPC (to check that we overwrote the user-supplied Datacenter
|
|
|
|
// field to prevent infinite forwarding loops!)
|
|
|
|
var forwardedRequestDatacenter string
|
|
|
|
primary := NewServer(Config{
|
|
|
|
ACLsEnabled: true,
|
|
|
|
InPrimaryDatacenter: true,
|
|
|
|
LocalTokensEnabled: noopLocalTokensEnabled,
|
|
|
|
Logger: hclog.NewNullLogger(),
|
|
|
|
NewTokenWriter: func() TokenWriter { return tokenWriter },
|
|
|
|
ForwardRPC: func(info structs.RPCInfo, _ func(*grpc.ClientConn) error) (bool, error) {
|
|
|
|
forwardedRequestDatacenter = info.RequestDatacenter()
|
|
|
|
return false, nil
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
primaryConn, err := grpc.Dial(
|
|
|
|
testutils.RunTestServer(t, primary).String(),
|
2022-11-07 16:34:30 +00:00
|
|
|
//nolint:staticcheck
|
2022-05-04 16:38:45 +00:00
|
|
|
grpc.WithInsecure(),
|
|
|
|
)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
secondary := NewServer(Config{
|
|
|
|
ACLsEnabled: true,
|
|
|
|
InPrimaryDatacenter: false,
|
|
|
|
LocalTokensEnabled: noopLocalTokensEnabled,
|
|
|
|
Logger: hclog.NewNullLogger(),
|
|
|
|
PrimaryDatacenter: "primary",
|
|
|
|
ForwardRPC: func(info structs.RPCInfo, fn func(*grpc.ClientConn) error) (bool, error) {
|
|
|
|
dc := info.RequestDatacenter()
|
|
|
|
switch dc {
|
|
|
|
case "secondary":
|
|
|
|
return false, nil
|
|
|
|
case "primary":
|
|
|
|
return true, fn(primaryConn)
|
|
|
|
default:
|
|
|
|
return false, fmt.Errorf("unexpected target datacenter: %s", dc)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
NewTokenWriter: func() TokenWriter {
|
|
|
|
tokenWriter := NewMockTokenWriter(t)
|
|
|
|
tokenWriter.On("Delete", mock.Anything, true).Return(auth.ErrCannotWriteGlobalToken)
|
|
|
|
return tokenWriter
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
_, err = secondary.Logout(context.Background(), &pbacl.LogoutRequest{
|
|
|
|
Token: generateID(t),
|
|
|
|
Datacenter: "secondary",
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, "primary", forwardedRequestDatacenter)
|
|
|
|
}
|