mirror of https://github.com/status-im/consul.git
NET-5590 - authorization: check for identity:write in CA certs, xds server, and getting envoy bootstrap params (#19049)
* NET-5590 - authorization: check for identity:write in CA certs, xds server, and getting envoy bootstrap params * gofmt file
This commit is contained in:
parent
41e6f6cd8b
commit
d67e5c6e35
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
@ -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 {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -34,6 +34,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
testIdentity = "test-identity"
|
||||
testToken = "acl-token-get-envoy-bootstrap-params"
|
||||
testServiceName = "web"
|
||||
proxyServiceID = "web-proxy"
|
||||
|
@ -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)
|
||||
|
@ -328,14 +345,14 @@ 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{},
|
||||
|
@ -343,7 +360,7 @@ func TestGetEnvoyBootstrapParams_Success_EnableV2(t *testing.T) {
|
|||
{
|
||||
name: "single proxy configuration",
|
||||
workloadData: &pbcatalog.Workload{
|
||||
Identity: "test-identity",
|
||||
Identity: testIdentity,
|
||||
},
|
||||
proxyCfgs: []*pbmesh.ProxyConfiguration{
|
||||
{
|
||||
|
@ -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{
|
||||
{
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue