consul/internal/resource/mappers/bimapper/bimapper.go

324 lines
9.2 KiB
Go
Raw Normal View History

// Copyright (c) HashiCorp, Inc.
[COMPLIANCE] License changes (#18443) * Adding explicit MPL license for sub-package This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Adding explicit MPL license for sub-package This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Updating the license from MPL to Business Source License Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at <Blog URL>, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl. * add missing license headers * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 --------- Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
2023-08-11 13:12:13 +00:00
// SPDX-License-Identifier: BUSL-1.1
package bimapper
import (
"context"
"fmt"
"sync"
"github.com/hashicorp/consul/internal/controller"
"github.com/hashicorp/consul/internal/resource"
"github.com/hashicorp/consul/proto-public/pbresource"
)
var wildcardType = &pbresource.Type{
Group: "@@any@@",
GroupVersion: "@@any@@",
Kind: "@@any@@",
}
// Mapper tracks bidirectional lookup for an item that contains references to
// other items. For example: an HTTPRoute has many references to Services.
//
// The primary object is called the "item" and an item has many "links".
// Tracking is done on items.
type Mapper struct {
itemType, linkType *pbresource.Type
wildcardLink bool
lock sync.Mutex
itemToLink map[resource.ReferenceKey]map[resource.ReferenceKey]struct{}
linkToItem map[resource.ReferenceKey]map[resource.ReferenceKey]struct{}
}
// New creates a bimapper between the two required provided types.
func New(itemType, linkType *pbresource.Type) *Mapper {
if itemType == nil {
panic("itemType is required")
}
if linkType == nil {
panic("linkType is required")
}
return &Mapper{
itemType: itemType,
linkType: linkType,
itemToLink: make(map[resource.ReferenceKey]map[resource.ReferenceKey]struct{}),
linkToItem: make(map[resource.ReferenceKey]map[resource.ReferenceKey]struct{}),
}
}
// NewWithWildcardLinkType creates a bimapper between the provided item type
// and can have a mixed set of link types.
func NewWithWildcardLinkType(itemType *pbresource.Type) *Mapper {
m := New(itemType, wildcardType)
m.wildcardLink = true
return m
}
// Reset clears the internal mappings.
func (m *Mapper) Reset() {
m.lock.Lock()
defer m.lock.Unlock()
m.itemToLink = make(map[resource.ReferenceKey]map[resource.ReferenceKey]struct{})
m.linkToItem = make(map[resource.ReferenceKey]map[resource.ReferenceKey]struct{})
}
// IsEmpty returns true if the internal structures are empty.
func (m *Mapper) IsEmpty() bool {
m.lock.Lock()
defer m.lock.Unlock()
return len(m.itemToLink) == 0 && len(m.linkToItem) == 0
}
// UntrackItem removes tracking for the provided item. The item type MUST match
// the type configured for the item.
func (m *Mapper) UntrackItem(item resource.ReferenceOrID) {
if !resource.EqualType(item.GetType(), m.itemType) {
panic(fmt.Sprintf("expected item type %q got %q",
resource.TypeToString(m.itemType),
resource.TypeToString(item.GetType()),
))
}
m.untrackItem(resource.NewReferenceKey(item))
}
// UntrackLink removes tracking for the provided link. The link type MUST match
// the type configured for the link.
func (m *Mapper) UntrackLink(link resource.ReferenceOrID) {
if !m.wildcardLink && !resource.EqualType(link.GetType(), m.linkType) {
panic(fmt.Sprintf("expected link type %q got %q",
resource.TypeToString(m.linkType),
resource.TypeToString(link.GetType()),
))
}
m.untrackLink(resource.NewReferenceKey(link))
}
func (m *Mapper) untrackLink(link resource.ReferenceKey) {
m.lock.Lock()
defer m.lock.Unlock()
m.removeLinkLocked(link)
}
func (m *Mapper) untrackItem(item resource.ReferenceKey) {
m.lock.Lock()
defer m.lock.Unlock()
m.removeItemLocked(item)
}
// TrackItem adds tracking for the provided item. The item and link types MUST
// match the types configured for the items and links.
func (m *Mapper) TrackItem(item resource.ReferenceOrID, links []resource.ReferenceOrID) {
if !resource.EqualType(item.GetType(), m.itemType) {
panic(fmt.Sprintf("expected item type %q got %q",
resource.TypeToString(m.itemType),
resource.TypeToString(item.GetType()),
))
}
linksAsKeys := make([]resource.ReferenceKey, 0, len(links))
for _, link := range links {
if !m.wildcardLink && !resource.EqualType(link.GetType(), m.linkType) {
panic(fmt.Sprintf("expected link type %q got %q",
resource.TypeToString(m.linkType),
resource.TypeToString(link.GetType()),
))
}
linksAsKeys = append(linksAsKeys, resource.NewReferenceKey(link))
}
m.trackItem(resource.NewReferenceKey(item), linksAsKeys)
}
func (m *Mapper) trackItem(item resource.ReferenceKey, links []resource.ReferenceKey) {
m.lock.Lock()
defer m.lock.Unlock()
m.removeItemLocked(item)
m.addItemLocked(item, links)
}
// you must hold the lock before calling this function
func (m *Mapper) removeItemLocked(item resource.ReferenceKey) {
for link := range m.itemToLink[item] {
delete(m.linkToItem[link], item)
if len(m.linkToItem[link]) == 0 {
delete(m.linkToItem, link)
}
}
delete(m.itemToLink, item)
}
func (m *Mapper) removeLinkLocked(link resource.ReferenceKey) {
for item := range m.linkToItem[link] {
delete(m.itemToLink[item], link)
if len(m.itemToLink[item]) == 0 {
delete(m.itemToLink, item)
}
}
delete(m.linkToItem, link)
}
// you must hold the lock before calling this function
func (m *Mapper) addItemLocked(item resource.ReferenceKey, links []resource.ReferenceKey) {
if m.itemToLink[item] == nil {
m.itemToLink[item] = make(map[resource.ReferenceKey]struct{})
}
for _, link := range links {
m.itemToLink[item][link] = struct{}{}
if m.linkToItem[link] == nil {
m.linkToItem[link] = make(map[resource.ReferenceKey]struct{})
}
m.linkToItem[link][item] = struct{}{}
}
}
// LinksForItem returns references to links related to the requested item.
// Deprecated: use LinksRefs
func (m *Mapper) LinksForItem(item *pbresource.ID) []*pbresource.Reference {
return m.LinkRefsForItem(item)
}
// LinkRefsForItem returns references to links related to the requested item.
func (m *Mapper) LinkRefsForItem(item *pbresource.ID) []*pbresource.Reference {
if !resource.EqualType(item.Type, m.itemType) {
panic(fmt.Sprintf("expected item type %q got %q",
resource.TypeToString(m.itemType),
resource.TypeToString(item.Type),
))
}
m.lock.Lock()
defer m.lock.Unlock()
links, ok := m.itemToLink[resource.NewReferenceKey(item)]
if !ok {
return nil
}
out := make([]*pbresource.Reference, 0, len(links))
for link := range links {
out = append(out, link.ToReference())
}
return out
}
// LinkIDsForItem returns IDs to links related to the requested item.
func (m *Mapper) LinkIDsForItem(item *pbresource.ID) []*pbresource.ID {
if !resource.EqualType(item.Type, m.itemType) {
panic(fmt.Sprintf("expected item type %q got %q",
resource.TypeToString(m.itemType),
resource.TypeToString(item.Type),
))
}
m.lock.Lock()
defer m.lock.Unlock()
links, ok := m.itemToLink[resource.NewReferenceKey(item)]
if !ok {
return nil
}
out := make([]*pbresource.ID, 0, len(links))
for l := range links {
out = append(out, l.ToID())
}
return out
}
// ItemsForLink returns item ids for items related to the provided link.
// Deprecated: use ItemIDsForLink
func (m *Mapper) ItemsForLink(link *pbresource.ID) []*pbresource.ID {
return m.ItemIDsForLink(link)
}
// ItemIDsForLink returns item ids for items related to the provided link.
xds controller: setup watches for and compute leaf cert references in ProxyStateTemplate, and wire up leaf cert manager dependency (#18756) * Refactors the leafcert package to not have a dependency on agent/consul and agent/cache to avoid import cycles. This way the xds controller can just import the leafcert package to use the leafcert manager. The leaf cert logic in the controller: * Sets up watches for leaf certs that are referenced in the ProxyStateTemplate (which generates the leaf certs too). * Gets the leaf cert from the leaf cert cache * Stores the leaf cert in the ProxyState that's pushed to xds * For the cert watches, this PR also uses a bimapper + a thin wrapper to map leaf cert events to related ProxyStateTemplates Since bimapper uses a resource.Reference or resource.ID to map between two resource types, I've created an internal type for a leaf certificate to use for the resource.Reference, since it's not a v2 resource. The wrapper allows mapping events to resources (as opposed to mapping resources to resources) The controller tests: Unit: Ensure that we resolve leaf cert references Lifecycle: Ensure that when the CA is updated, the leaf cert is as well Also adds a new spiffe id type, and adds workload identity and workload identity URI to leaf certs. This is so certs are generated with the new workload identity based SPIFFE id. * Pulls out some leaf cert test helpers into a helpers file so it can be used in the xds controller tests. * Wires up leaf cert manager dependency * Support getting token from proxytracker * Add workload identity spiffe id type to the authorize and sign functions --------- Co-authored-by: John Murret <john.murret@hashicorp.com>
2023-09-12 19:56:43 +00:00
func (m *Mapper) ItemIDsForLink(link resource.ReferenceOrID) []*pbresource.ID {
if !m.wildcardLink && !resource.EqualType(link.GetType(), m.linkType) {
panic(fmt.Sprintf("expected link type %q got %q",
resource.TypeToString(m.linkType),
xds controller: setup watches for and compute leaf cert references in ProxyStateTemplate, and wire up leaf cert manager dependency (#18756) * Refactors the leafcert package to not have a dependency on agent/consul and agent/cache to avoid import cycles. This way the xds controller can just import the leafcert package to use the leafcert manager. The leaf cert logic in the controller: * Sets up watches for leaf certs that are referenced in the ProxyStateTemplate (which generates the leaf certs too). * Gets the leaf cert from the leaf cert cache * Stores the leaf cert in the ProxyState that's pushed to xds * For the cert watches, this PR also uses a bimapper + a thin wrapper to map leaf cert events to related ProxyStateTemplates Since bimapper uses a resource.Reference or resource.ID to map between two resource types, I've created an internal type for a leaf certificate to use for the resource.Reference, since it's not a v2 resource. The wrapper allows mapping events to resources (as opposed to mapping resources to resources) The controller tests: Unit: Ensure that we resolve leaf cert references Lifecycle: Ensure that when the CA is updated, the leaf cert is as well Also adds a new spiffe id type, and adds workload identity and workload identity URI to leaf certs. This is so certs are generated with the new workload identity based SPIFFE id. * Pulls out some leaf cert test helpers into a helpers file so it can be used in the xds controller tests. * Wires up leaf cert manager dependency * Support getting token from proxytracker * Add workload identity spiffe id type to the authorize and sign functions --------- Co-authored-by: John Murret <john.murret@hashicorp.com>
2023-09-12 19:56:43 +00:00
resource.TypeToString(link.GetType()),
))
}
return m.itemIDsByLink(resource.NewReferenceKey(link))
}
// ItemRefsForLink returns item references for items related to the provided link.
xds controller: setup watches for and compute leaf cert references in ProxyStateTemplate, and wire up leaf cert manager dependency (#18756) * Refactors the leafcert package to not have a dependency on agent/consul and agent/cache to avoid import cycles. This way the xds controller can just import the leafcert package to use the leafcert manager. The leaf cert logic in the controller: * Sets up watches for leaf certs that are referenced in the ProxyStateTemplate (which generates the leaf certs too). * Gets the leaf cert from the leaf cert cache * Stores the leaf cert in the ProxyState that's pushed to xds * For the cert watches, this PR also uses a bimapper + a thin wrapper to map leaf cert events to related ProxyStateTemplates Since bimapper uses a resource.Reference or resource.ID to map between two resource types, I've created an internal type for a leaf certificate to use for the resource.Reference, since it's not a v2 resource. The wrapper allows mapping events to resources (as opposed to mapping resources to resources) The controller tests: Unit: Ensure that we resolve leaf cert references Lifecycle: Ensure that when the CA is updated, the leaf cert is as well Also adds a new spiffe id type, and adds workload identity and workload identity URI to leaf certs. This is so certs are generated with the new workload identity based SPIFFE id. * Pulls out some leaf cert test helpers into a helpers file so it can be used in the xds controller tests. * Wires up leaf cert manager dependency * Support getting token from proxytracker * Add workload identity spiffe id type to the authorize and sign functions --------- Co-authored-by: John Murret <john.murret@hashicorp.com>
2023-09-12 19:56:43 +00:00
func (m *Mapper) ItemRefsForLink(link resource.ReferenceOrID) []*pbresource.Reference {
if !m.wildcardLink && !resource.EqualType(link.GetType(), m.linkType) {
panic(fmt.Sprintf("expected link type %q got %q",
resource.TypeToString(m.linkType),
xds controller: setup watches for and compute leaf cert references in ProxyStateTemplate, and wire up leaf cert manager dependency (#18756) * Refactors the leafcert package to not have a dependency on agent/consul and agent/cache to avoid import cycles. This way the xds controller can just import the leafcert package to use the leafcert manager. The leaf cert logic in the controller: * Sets up watches for leaf certs that are referenced in the ProxyStateTemplate (which generates the leaf certs too). * Gets the leaf cert from the leaf cert cache * Stores the leaf cert in the ProxyState that's pushed to xds * For the cert watches, this PR also uses a bimapper + a thin wrapper to map leaf cert events to related ProxyStateTemplates Since bimapper uses a resource.Reference or resource.ID to map between two resource types, I've created an internal type for a leaf certificate to use for the resource.Reference, since it's not a v2 resource. The wrapper allows mapping events to resources (as opposed to mapping resources to resources) The controller tests: Unit: Ensure that we resolve leaf cert references Lifecycle: Ensure that when the CA is updated, the leaf cert is as well Also adds a new spiffe id type, and adds workload identity and workload identity URI to leaf certs. This is so certs are generated with the new workload identity based SPIFFE id. * Pulls out some leaf cert test helpers into a helpers file so it can be used in the xds controller tests. * Wires up leaf cert manager dependency * Support getting token from proxytracker * Add workload identity spiffe id type to the authorize and sign functions --------- Co-authored-by: John Murret <john.murret@hashicorp.com>
2023-09-12 19:56:43 +00:00
resource.TypeToString(link.GetType()),
))
}
return m.itemRefsByLink(resource.NewReferenceKey(link))
}
// MapLink is suitable as a DependencyMapper to map the provided link event to its item.
func (m *Mapper) MapLink(_ context.Context, _ controller.Runtime, res *pbresource.Resource) ([]controller.Request, error) {
link := res.Id
if !m.wildcardLink && !resource.EqualType(link.Type, m.linkType) {
return nil, fmt.Errorf("expected type %q got %q",
resource.TypeToString(m.linkType),
resource.TypeToString(link.Type),
)
}
itemIDs := m.itemIDsByLink(resource.NewReferenceKey(link))
out := make([]controller.Request, 0, len(itemIDs))
for _, item := range itemIDs {
if !resource.EqualType(item.Type, m.itemType) {
return nil, fmt.Errorf("expected type %q got %q",
resource.TypeToString(m.itemType),
resource.TypeToString(item.Type),
)
}
out = append(out, controller.Request{ID: item})
}
return out, nil
}
func (m *Mapper) itemIDsByLink(link resource.ReferenceKey) []*pbresource.ID {
// a lock must be held both to read item from the map and to read the
// the returned items.
2023-08-18 13:55:12 +00:00
m.lock.Lock()
defer m.lock.Unlock()
items, ok := m.linkToItem[link]
if !ok {
return nil
}
out := make([]*pbresource.ID, 0, len(items))
for item := range items {
out = append(out, item.ToID())
}
return out
}
func (m *Mapper) itemRefsByLink(link resource.ReferenceKey) []*pbresource.Reference {
// a lock must be held both to read item from the map and to read the
// the returned items.
2023-08-18 13:55:12 +00:00
m.lock.Lock()
defer m.lock.Unlock()
items, ok := m.linkToItem[link]
if !ok {
return nil
}
out := make([]*pbresource.Reference, 0, len(items))
for item := range items {
out = append(out, item.ToReference())
}
return out
}