Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion agent/consul/leader_connect_ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -1456,7 +1456,10 @@ func (c *CAManager) AuthorizeAndSignCertificate(csr *x509.CertificateRequest, au
"we are %s", v.Datacenter, dc)
}
case *connect.SpiffeIDWorkloadIdentity:
// TODO: Check for identity:write on the token when identity permissions are supported.
v.GetEnterpriseMeta().FillAuthzContext(&authzContext)
if err := allow.IdentityWriteAllowed(v.WorkloadIdentity, &authzContext); err != nil {
return nil, err
}
case *connect.SpiffeIDAgent:
v.GetEnterpriseMeta().FillAuthzContext(&authzContext)
if err := allow.NodeWriteAllowed(v.Agent, &authzContext); err != nil {
Expand Down
15 changes: 15 additions & 0 deletions agent/consul/leader_connect_ca_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1317,6 +1317,12 @@ func TestCAManager_AuthorizeAndSignCertificate(t *testing.T) {
Host: "test-host",
Partition: "test-partition",
}.URI()
identityURL := connect.SpiffeIDWorkloadIdentity{
TrustDomain: "test-trust-domain",
Partition: "test-partition",
Namespace: "test-namespace",
WorkloadIdentity: "test-workload-identity",
}.URI()

tests := []struct {
name string
Expand Down Expand Up @@ -1412,6 +1418,15 @@ func TestCAManager_AuthorizeAndSignCertificate(t *testing.T) {
}
},
},
{
name: "err_identity_write_not_allowed",
expectErr: "Permission denied",
getCSR: func() *x509.CertificateRequest {
return &x509.CertificateRequest{
URIs: []*url.URL{identityURL},
}
},
},
}

for _, tc := range tests {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,10 @@ func (s *Server) GetEnvoyBootstrapParams(ctx context.Context, req *pbdataplane.G
return nil, status.Errorf(codes.InvalidArgument, "workload %q doesn't have identity associated with it", req.ProxyId)
}

// todo (ishustava): ACL enforcement ensuring there's identity:write permissions.
// verify identity:write is allowed. if not, give permission denied error.
if err := authz.ToAllowAuthorizer().IdentityWriteAllowed(workload.Identity, &authzContext); err != nil {
return nil, err
}

// Get all proxy configurations for this workload. Currently we're only looking
// for proxy configurations in the same tenancy as the workload.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
)

const (
testIdentity = "test-identity"
testToken = "acl-token-get-envoy-bootstrap-params"
testServiceName = "web"
proxyServiceID = "web-proxy"
Expand Down Expand Up @@ -308,7 +309,23 @@ func TestGetEnvoyBootstrapParams_Success_EnableV2(t *testing.T) {
}

aclResolver.On("ResolveTokenAndDefaultMeta", testToken, mock.Anything, mock.Anything).
Return(testutils.ACLServiceRead(t, workloadResource.Id.Name), nil)
Return(testutils.ACLUseProvidedPolicy(t,
&acl.Policy{
PolicyRules: acl.PolicyRules{
Services: []*acl.ServiceRule{
{
Name: workloadResource.Id.Name,
Policy: acl.PolicyRead,
},
},
Identities: []*acl.IdentityRule{
{
Name: testIdentity,
Policy: acl.PolicyWrite,
},
},
},
}), nil)

resp, err := client.GetEnvoyBootstrapParams(ctx, req)
require.NoError(t, err)
Expand All @@ -328,22 +345,22 @@ func TestGetEnvoyBootstrapParams_Success_EnableV2(t *testing.T) {
{
name: "workload without node",
workloadData: &pbcatalog.Workload{
Identity: "test-identity",
Identity: testIdentity,
},
expBootstrapCfg: &pbmesh.BootstrapConfig{},
},
{
name: "workload with node",
workloadData: &pbcatalog.Workload{
Identity: "test-identity",
Identity: testIdentity,
NodeName: "test-node",
},
expBootstrapCfg: &pbmesh.BootstrapConfig{},
},
{
name: "single proxy configuration",
workloadData: &pbcatalog.Workload{
Identity: "test-identity",
Identity: testIdentity,
},
proxyCfgs: []*pbmesh.ProxyConfiguration{
{
Expand All @@ -360,7 +377,7 @@ func TestGetEnvoyBootstrapParams_Success_EnableV2(t *testing.T) {
{
name: "multiple proxy configurations",
workloadData: &pbcatalog.Workload{
Identity: "test-identity",
Identity: testIdentity,
},
proxyCfgs: []*pbmesh.ProxyConfiguration{
{
Expand Down
12 changes: 12 additions & 0 deletions agent/grpc-external/testutils/acl.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,18 @@ func ACLServiceRead(t *testing.T, serviceName string) resolver.Result {
}
}

func ACLUseProvidedPolicy(t *testing.T, aclPolicy *acl.Policy) resolver.Result {
t.Helper()

authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{aclPolicy}, nil)
require.NoError(t, err)

return resolver.Result{
Authorizer: authz,
ACLIdentity: randomACLIdentity(t),
}
}

func ACLOperatorRead(t *testing.T) resolver.Result {
t.Helper()

Expand Down
9 changes: 7 additions & 2 deletions internal/mesh/proxy-tracker/proxy_state_exports.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package proxytracker

import (
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/internal/resource"
pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1"
)

Expand Down Expand Up @@ -34,9 +35,13 @@ func (p *ProxyState) AllowEmptyClusters() bool {
}

func (p *ProxyState) Authorize(authz acl.Authorizer) error {
// TODO(proxystate): we'll need to implement this once identity policy is implemented
// authorize for mesh proxies.
// TODO(proxystate): implement differently for gateways
allow := authz.ToAllowAuthorizer()
if err := allow.IdentityWriteAllowed(p.Identity.Name, resource.AuthorizerContext(p.Identity.Tenancy)); err != nil {
return err
}

// Authed OK!
return nil
}

Expand Down
78 changes: 78 additions & 0 deletions internal/mesh/proxy-tracker/proxy_state_exports_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package proxytracker

import (
"github.com/hashicorp/consul/acl"
pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1"
"github.com/hashicorp/consul/proto-public/pbresource"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"strings"
"testing"
)

func TestProxyState_Authorize(t *testing.T) {
testIdentity := &pbresource.Reference{
Type: &pbresource.Type{
Group: "mesh",
GroupVersion: "v1alpha1",
Kind: "Identity",
},
Tenancy: &pbresource.Tenancy{
Partition: "default",
Namespace: "default",
PeerName: "local",
},
Name: "test-identity",
}

type testCase struct {
description string
proxyState *ProxyState
configureAuthorizer func(authorizer *acl.MockAuthorizer)
expectedErrorMessage string
}
testsCases := []testCase{
{
description: "ProxyState - if identity write is allowed for the workload then allow.",
proxyState: &ProxyState{
ProxyState: &pbmesh.ProxyState{
Identity: testIdentity,
},
},
expectedErrorMessage: "",
configureAuthorizer: func(authz *acl.MockAuthorizer) {
authz.On("IdentityWrite", testIdentity.Name, mock.Anything).Return(acl.Allow)
},
},
{
description: "ProxyState - if identity write is not allowed for the workload then deny.",
proxyState: &ProxyState{
ProxyState: &pbmesh.ProxyState{
Identity: testIdentity,
},
},
expectedErrorMessage: "Permission denied: token with AccessorID '' lacks permission 'identity:write' on \"test-identity\"",
configureAuthorizer: func(authz *acl.MockAuthorizer) {
authz.On("IdentityWrite", testIdentity.Name, mock.Anything).Return(acl.Deny)
},
},
}
for _, tc := range testsCases {
t.Run(tc.description, func(t *testing.T) {
authz := &acl.MockAuthorizer{}
authz.On("ToAllow").Return(acl.AllowAuthorizer{Authorizer: authz})
tc.configureAuthorizer(authz)
err := tc.proxyState.Authorize(authz)
errMsg := ""
if err != nil {
errMsg = err.Error()
}
// using contains because Enterprise tests append the parition and namespace
// information to the message.
require.True(t, strings.Contains(errMsg, tc.expectedErrorMessage))
})
}
}