Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

register traffic permission and workload identity types #18704

Merged
merged 10 commits into from
Sep 14, 2023
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
2 changes: 2 additions & 0 deletions agent/consul/type_registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package consul

import (
"github.com/hashicorp/consul/internal/auth"
"github.com/hashicorp/consul/internal/catalog"
"github.com/hashicorp/consul/internal/mesh"
"github.com/hashicorp/consul/internal/resource"
Expand All @@ -23,6 +24,7 @@ func NewTypeRegistry() resource.Registry {
demo.RegisterTypes(registry)
mesh.RegisterTypes(registry)
catalog.RegisterTypes(registry)
auth.RegisterTypes(registry)

return registry
}
41 changes: 41 additions & 0 deletions internal/auth/exports.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package auth

import (
"github.com/hashicorp/consul/internal/auth/internal/types"
"github.com/hashicorp/consul/internal/resource"
)

var (
// API Group Information

APIGroup = types.GroupName
VersionV1Alpha1 = types.VersionV1Alpha1
CurrentVersion = types.CurrentVersion

// Resource Kind Names.

WorkloadIdentity = types.WorkloadIdentityKind
TrafficPermissions = types.TrafficPermissionsKind
ComputedTrafficPermissions = types.ComputedTrafficPermissionsKind

// Resource Types for the v1alpha1 version.

WorkloadIdentityV1Alpha1Type = types.WorkloadIdentityV1Alpha1Type
TrafficPermissionsV1Alpha1Type = types.TrafficPermissionsV1Alpha1Type
ComputedTrafficPermissionsV1Alpha1Type = types.ComputedTrafficPermissionsV1Alpha1Type

// Resource Types for the latest version.

WorkloadIdentityType = types.WorkloadIdentityType
TrafficPermissionsType = types.TrafficPermissionsType
ComputedTrafficPermissionsType = types.ComputedTrafficPermissionsType
)
skpratt marked this conversation as resolved.
Show resolved Hide resolved

// RegisterTypes adds all resource types within the "catalog" API group
// to the given type registry
func RegisterTypes(r resource.Registry) {
types.Register(r)
}
33 changes: 33 additions & 0 deletions internal/auth/internal/types/computed_traffic_permissions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package types

import (
"github.com/hashicorp/consul/internal/resource"
pbauth "github.com/hashicorp/consul/proto-public/pbauth/v1alpha1"
"github.com/hashicorp/consul/proto-public/pbresource"
)

const (
ComputedTrafficPermissionsKind = "ComputedTrafficPermission"
)

var (
ComputedTrafficPermissionsV1Alpha1Type = &pbresource.Type{
Group: GroupName,
GroupVersion: VersionV1Alpha1,
Kind: ComputedTrafficPermissionsKind,
}

ComputedTrafficPermissionsType = ComputedTrafficPermissionsV1Alpha1Type
)

func RegisterComputedTrafficPermission(r resource.Registry) {
r.Register(resource.Registration{
Type: ComputedTrafficPermissionsV1Alpha1Type,
Proto: &pbauth.ComputedTrafficPermissions{},
Scope: resource.ScopeNamespace,
Validate: nil,
})
}
12 changes: 12 additions & 0 deletions internal/auth/internal/types/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package types

import "errors"

var (
errInvalidAction = errors.New("action must be either allow or deny")
errSourcesTenancy = errors.New("permissions sources may not specify partitions, peers, and sameness_groups together")
errInvalidPrefixValues = errors.New("prefix values, regex values, and explicit names must not combined")
)
144 changes: 144 additions & 0 deletions internal/auth/internal/types/traffic_permissions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package types

import (
"github.com/hashicorp/go-multierror"

"github.com/hashicorp/consul/internal/resource"
pbauth "github.com/hashicorp/consul/proto-public/pbauth/v1alpha1"
"github.com/hashicorp/consul/proto-public/pbresource"
)

const (
TrafficPermissionsKind = "TrafficPermissions"
)

var (
TrafficPermissionsV1Alpha1Type = &pbresource.Type{
Group: GroupName,
GroupVersion: VersionV1Alpha1,
Kind: TrafficPermissionsKind,
}

TrafficPermissionsType = TrafficPermissionsV1Alpha1Type
)

func RegisterTrafficPermissions(r resource.Registry) {
r.Register(resource.Registration{
Type: TrafficPermissionsV1Alpha1Type,
Proto: &pbauth.TrafficPermissions{},
Scope: resource.ScopeNamespace,
Validate: ValidateTrafficPermissions,
})
}

func ValidateTrafficPermissions(res *pbresource.Resource) error {
var tp pbauth.TrafficPermissions

if err := res.Data.UnmarshalTo(&tp); err != nil {
return resource.NewErrDataParse(&tp, err)
}

var err error

if tp.Action == pbauth.Action_ACTION_UNSPECIFIED {
ishustava marked this conversation as resolved.
Show resolved Hide resolved
err = multierror.Append(err, resource.ErrInvalidField{
Name: "data.action",
Wrapped: errInvalidAction,
})
}
if tp.Destination == nil || (len(tp.Destination.IdentityName) == 0) {
err = multierror.Append(err, resource.ErrInvalidField{
Name: "data.destination",
Wrapped: resource.ErrEmpty,
})
}
// Validate permissions
for i, permission := range tp.Permissions {
wrapPermissionErr := func(err error) error {
return resource.ErrInvalidListElement{
Name: "permissions",
Index: i,
Wrapped: err,
}
}
for s, src := range permission.Sources {
wrapSrcErr := func(err error) error {
return wrapPermissionErr(resource.ErrInvalidListElement{
Name: "sources",
Index: s,
Wrapped: err,
})
}
if (len(src.Partition) > 0 && len(src.Peer) > 0) ||
(len(src.Partition) > 0 && len(src.SamenessGroup) > 0) ||
(len(src.Peer) > 0 && len(src.SamenessGroup) > 0) {
err = multierror.Append(err, wrapSrcErr(resource.ErrInvalidListElement{
Name: "source",
Wrapped: errSourcesTenancy,
}))
}
if len(src.Exclude) > 0 {
for e, d := range src.Exclude {
wrapExclSrcErr := func(err error) error {
return wrapPermissionErr(resource.ErrInvalidListElement{
Name: "exclude_sources",
Index: e,
Wrapped: err,
})
}
if (len(d.Partition) > 0 && len(d.Peer) > 0) ||
(len(d.Partition) > 0 && len(d.SamenessGroup) > 0) ||
(len(d.Peer) > 0 && len(d.SamenessGroup) > 0) {
err = multierror.Append(err, wrapExclSrcErr(resource.ErrInvalidListElement{
Name: "exclude_source",
Wrapped: errSourcesTenancy,
}))
}
}
}
}
if len(permission.DestinationRules) > 0 {
for d, dest := range permission.DestinationRules {
wrapDestRuleErr := func(err error) error {
return wrapPermissionErr(resource.ErrInvalidListElement{
Name: "destination_rules",
Index: d,
Wrapped: err,
})
}
if (len(dest.PathExact) > 0 && len(dest.PathPrefix) > 0) ||
(len(dest.PathRegex) > 0 && len(dest.PathExact) > 0) ||
(len(dest.PathRegex) > 0 && len(dest.PathPrefix) > 0) {
err = multierror.Append(err, wrapDestRuleErr(resource.ErrInvalidListElement{
Name: "destination_rule",
Wrapped: errInvalidPrefixValues,
}))
}
if len(dest.Exclude) > 0 {
for e, excl := range dest.Exclude {
wrapExclPermRuleErr := func(err error) error {
return wrapPermissionErr(resource.ErrInvalidListElement{
Name: "exclude_permission_rules",
Index: e,
Wrapped: err,
})
}
if (len(excl.PathExact) > 0 && len(excl.PathPrefix) > 0) ||
(len(excl.PathRegex) > 0 && len(excl.PathExact) > 0) ||
(len(excl.PathRegex) > 0 && len(excl.PathPrefix) > 0) {
err = multierror.Append(err, wrapExclPermRuleErr(resource.ErrInvalidListElement{
Name: "exclude_permission_rule",
Wrapped: errInvalidPrefixValues,
}))
}
}
}
}
}
}

return err
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function needs tests

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added a few, more on the way

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant more unit-test rather than integration tests, for example, similar to what we have here for validation functions:

func TestValidateService_Ok(t *testing.T) {
data := &pbcatalog.Service{
Workloads: &pbcatalog.WorkloadSelector{
Names: []string{"foo", "bar"},
Prefixes: []string{"abc-"},
},
Ports: []*pbcatalog.ServicePort{
{
TargetPort: "http-internal",
VirtualPort: 42,
Protocol: pbcatalog.Protocol_PROTOCOL_HTTP,
},
{
TargetPort: "other",
// leaving VirtualPort unset to verify that seeing
// a zero virtual port multiple times is fine.
Protocol: pbcatalog.Protocol_PROTOCOL_HTTP2,
},
{
TargetPort: "other2",
// leaving VirtualPort unset to verify that seeing
// a zero virtual port multiple times is fine.
Protocol: pbcatalog.Protocol_PROTOCOL_GRPC,
},
},
VirtualIps: []string{"198.18.0.1"},
}
res := createServiceResource(t, data)
err := ValidateService(res)
require.NoError(t, err)
}
func TestValidateService_ParseError(t *testing.T) {
// Any type other than the Service type would work
// to cause the error we are expecting
data := &pbcatalog.IP{Address: "198.18.0.1"}
res := createServiceResource(t, data)
err := ValidateService(res)
require.Error(t, err)
require.ErrorAs(t, err, &resource.ErrDataParse{})
}
func TestValidateService_EmptySelector(t *testing.T) {
data := &pbcatalog.Service{
Ports: []*pbcatalog.ServicePort{
{
TargetPort: "http-internal",
Protocol: pbcatalog.Protocol_PROTOCOL_HTTP,
},
},
}
res := createServiceResource(t, data)
err := ValidateService(res)
require.NoError(t, err)
}
func TestValidateService_InvalidSelector(t *testing.T) {
data := &pbcatalog.Service{
Workloads: &pbcatalog.WorkloadSelector{
Names: []string{""},
},
Ports: []*pbcatalog.ServicePort{
{
TargetPort: "http-internal",
Protocol: pbcatalog.Protocol_PROTOCOL_HTTP,
},
},
}
res := createServiceResource(t, data)
err := ValidateService(res)
expected := resource.ErrInvalidListElement{
Name: "names",
Index: 0,
Wrapped: resource.ErrEmpty,
}
var actual resource.ErrInvalidListElement
require.ErrorAs(t, err, &actual)
require.Equal(t, expected, actual)
}
func TestValidateService_InvalidTargetPort(t *testing.T) {
data := &pbcatalog.Service{
Workloads: &pbcatalog.WorkloadSelector{
Prefixes: []string{""},
},
Ports: []*pbcatalog.ServicePort{
{
TargetPort: "",
},
},
}
res := createServiceResource(t, data)
err := ValidateService(res)
require.Error(t, err)
var actual resource.ErrInvalidField
require.ErrorAs(t, err, &actual)
require.Equal(t, "target_port", actual.Name)
require.Equal(t, resource.ErrEmpty, actual.Wrapped)
}
func TestValidateService_VirtualPortReused(t *testing.T) {
data := &pbcatalog.Service{
Workloads: &pbcatalog.WorkloadSelector{
Prefixes: []string{""},
},
Ports: []*pbcatalog.ServicePort{
{
VirtualPort: 42,
TargetPort: "foo",
},
{
VirtualPort: 42,
TargetPort: "bar",
},
},
}
res := createServiceResource(t, data)
err := ValidateService(res)
require.Error(t, err)
var actual errVirtualPortReused
require.ErrorAs(t, err, &actual)
require.EqualValues(t, 0, actual.Index)
require.EqualValues(t, 42, actual.Value)
}
func TestValidateService_InvalidPortProtocol(t *testing.T) {
data := &pbcatalog.Service{
Workloads: &pbcatalog.WorkloadSelector{
Prefixes: []string{""},
},
Ports: []*pbcatalog.ServicePort{
{
TargetPort: "foo",
Protocol: 99,
},
},
}
res := createServiceResource(t, data)
err := ValidateService(res)
expected := resource.ErrInvalidListElement{
Name: "ports",
Index: 0,
Wrapped: resource.ErrInvalidField{
Name: "protocol",
Wrapped: resource.NewConstError("not a supported enum value: 99"),
},
}
var actual resource.ErrInvalidListElement
require.ErrorAs(t, err, &actual)
require.Equal(t, expected, actual)
}
func TestValidateService_VirtualPortInvalid(t *testing.T) {
data := &pbcatalog.Service{
Workloads: &pbcatalog.WorkloadSelector{
Prefixes: []string{""},
},
Ports: []*pbcatalog.ServicePort{
{
VirtualPort: 100000,
TargetPort: "foo",
},
},
}
res := createServiceResource(t, data)
err := ValidateService(res)
require.Error(t, err)
require.ErrorIs(t, err, errInvalidVirtualPort)
}
func TestValidateService_InvalidVIP(t *testing.T) {
data := &pbcatalog.Service{
Workloads: &pbcatalog.WorkloadSelector{
Prefixes: []string{""},
},
Ports: []*pbcatalog.ServicePort{
{
TargetPort: "foo",
},
},
VirtualIps: []string{"foo"},
}
res := createServiceResource(t, data)
err := ValidateService(res)
require.Error(t, err)
require.ErrorIs(t, err, errNotIPAddress)
}

Loading