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)
|
"we are %s", v.Datacenter, dc)
|
||||||
}
|
}
|
||||||
case *connect.SpiffeIDWorkloadIdentity:
|
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:
|
case *connect.SpiffeIDAgent:
|
||||||
v.GetEnterpriseMeta().FillAuthzContext(&authzContext)
|
v.GetEnterpriseMeta().FillAuthzContext(&authzContext)
|
||||||
if err := allow.NodeWriteAllowed(v.Agent, &authzContext); err != nil {
|
if err := allow.NodeWriteAllowed(v.Agent, &authzContext); err != nil {
|
||||||
|
|
|
@ -1317,6 +1317,12 @@ func TestCAManager_AuthorizeAndSignCertificate(t *testing.T) {
|
||||||
Host: "test-host",
|
Host: "test-host",
|
||||||
Partition: "test-partition",
|
Partition: "test-partition",
|
||||||
}.URI()
|
}.URI()
|
||||||
|
identityURL := connect.SpiffeIDWorkloadIdentity{
|
||||||
|
TrustDomain: "test-trust-domain",
|
||||||
|
Partition: "test-partition",
|
||||||
|
Namespace: "test-namespace",
|
||||||
|
WorkloadIdentity: "test-workload-identity",
|
||||||
|
}.URI()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
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 {
|
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)
|
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
|
// Get all proxy configurations for this workload. Currently we're only looking
|
||||||
// for proxy configurations in the same tenancy as the workload.
|
// for proxy configurations in the same tenancy as the workload.
|
||||||
|
|
|
@ -34,6 +34,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
testIdentity = "test-identity"
|
||||||
testToken = "acl-token-get-envoy-bootstrap-params"
|
testToken = "acl-token-get-envoy-bootstrap-params"
|
||||||
testServiceName = "web"
|
testServiceName = "web"
|
||||||
proxyServiceID = "web-proxy"
|
proxyServiceID = "web-proxy"
|
||||||
|
@ -308,7 +309,23 @@ func TestGetEnvoyBootstrapParams_Success_EnableV2(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
aclResolver.On("ResolveTokenAndDefaultMeta", testToken, mock.Anything, mock.Anything).
|
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)
|
resp, err := client.GetEnvoyBootstrapParams(ctx, req)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -328,14 +345,14 @@ func TestGetEnvoyBootstrapParams_Success_EnableV2(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "workload without node",
|
name: "workload without node",
|
||||||
workloadData: &pbcatalog.Workload{
|
workloadData: &pbcatalog.Workload{
|
||||||
Identity: "test-identity",
|
Identity: testIdentity,
|
||||||
},
|
},
|
||||||
expBootstrapCfg: &pbmesh.BootstrapConfig{},
|
expBootstrapCfg: &pbmesh.BootstrapConfig{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "workload with node",
|
name: "workload with node",
|
||||||
workloadData: &pbcatalog.Workload{
|
workloadData: &pbcatalog.Workload{
|
||||||
Identity: "test-identity",
|
Identity: testIdentity,
|
||||||
NodeName: "test-node",
|
NodeName: "test-node",
|
||||||
},
|
},
|
||||||
expBootstrapCfg: &pbmesh.BootstrapConfig{},
|
expBootstrapCfg: &pbmesh.BootstrapConfig{},
|
||||||
|
@ -343,7 +360,7 @@ func TestGetEnvoyBootstrapParams_Success_EnableV2(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "single proxy configuration",
|
name: "single proxy configuration",
|
||||||
workloadData: &pbcatalog.Workload{
|
workloadData: &pbcatalog.Workload{
|
||||||
Identity: "test-identity",
|
Identity: testIdentity,
|
||||||
},
|
},
|
||||||
proxyCfgs: []*pbmesh.ProxyConfiguration{
|
proxyCfgs: []*pbmesh.ProxyConfiguration{
|
||||||
{
|
{
|
||||||
|
@ -360,7 +377,7 @@ func TestGetEnvoyBootstrapParams_Success_EnableV2(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "multiple proxy configurations",
|
name: "multiple proxy configurations",
|
||||||
workloadData: &pbcatalog.Workload{
|
workloadData: &pbcatalog.Workload{
|
||||||
Identity: "test-identity",
|
Identity: testIdentity,
|
||||||
},
|
},
|
||||||
proxyCfgs: []*pbmesh.ProxyConfiguration{
|
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 {
|
func ACLOperatorRead(t *testing.T) resolver.Result {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ package proxytracker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/hashicorp/consul/acl"
|
"github.com/hashicorp/consul/acl"
|
||||||
|
"github.com/hashicorp/consul/internal/resource"
|
||||||
pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1"
|
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 {
|
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
|
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