Consul is a distributed, highly available, and data center aware solution to connect and configure applications across dynamic, distributed infrastructure.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

229 lines
6.9 KiB

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
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"
"github.com/hashicorp/consul/agent/grpc-external/testutils"
"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(),
//nolint:staticcheck
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(),
//nolint:staticcheck
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)
}