Files
teleport/lib/services/spiffe_federations.go
rosstimothy 4f17314a11 Initial migration to the Opaque API (#67279)
* Convert existing non-gogo codegen to the Hybrid API

Contributes to https://github.com/gravitational/teleport/issues/66776.

All existing protos explicitly set to API_OPEN have been change to
API_HBYRID. The new codegen was performed via make grpc. There are no
other functional changes to the code to start consuming the Hybrid API
those will come later. The intent is to get all Hybrid codegen in and
backported to ease the transition.

* Initial migration to the Opaque API

Contributes to https://github.com/gravitational/teleport/issues/66776.

All of the changes here are mechanical conversions generated from
`open2opaque rewrite -levels=green ./...`. There will be a follow up
to this in teleport.e which does the same. Once all changes have been
merged the process will be repeated with -levels=yellow followed by
-levels=red.


See https://protobuf.dev/reference/go/opaque-migration/ for more
details.
2026-06-05 14:52:58 +00:00

145 lines
6.0 KiB
Go

// Teleport
// Copyright (C) 2024 Gravitational, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package services
import (
"context"
"net/url"
"strings"
"github.com/gravitational/trace"
"github.com/spiffe/go-spiffe/v2/bundle/spiffebundle"
"github.com/spiffe/go-spiffe/v2/spiffeid"
machineidv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/machineid/v1"
"github.com/gravitational/teleport/api/types"
)
// SPIFFEFederations is an interface over the SPIFFEFederations service. This
// interface may also be implemented by a client to allow remote and local
// consumers to access the resource in a similar way.
type SPIFFEFederations interface {
// GetSPIFFEFederation gets a SPIFFE Federation by name.
GetSPIFFEFederation(
ctx context.Context, name string,
) (*machineidv1.SPIFFEFederation, error)
// ListSPIFFEFederations lists all SPIFFE Federations using Google style
// pagination.
ListSPIFFEFederations(
ctx context.Context, pageSize int, lastToken string,
) ([]*machineidv1.SPIFFEFederation, string, error)
// CreateSPIFFEFederation creates a new SPIFFE Federation.
CreateSPIFFEFederation(
ctx context.Context, spiffeFederation *machineidv1.SPIFFEFederation,
) (*machineidv1.SPIFFEFederation, error)
// DeleteSPIFFEFederation deletes a SPIFFE Federation by name.
DeleteSPIFFEFederation(ctx context.Context, name string) error
// UpdateSPIFFEFederation updates a SPIFFE Federation. It will not act if the resource is not found
// or where the revision does not match.
UpdateSPIFFEFederation(
ctx context.Context, spiffeFederation *machineidv1.SPIFFEFederation,
) (*machineidv1.SPIFFEFederation, error)
}
// MarshalSPIFFEFederation marshals the SPIFFEFederation object into a JSON byte
// array.
func MarshalSPIFFEFederation(object *machineidv1.SPIFFEFederation, opts ...MarshalOption) ([]byte, error) {
return MarshalProtoResource(object, opts...)
}
// UnmarshalSPIFFEFederation unmarshals the SPIFFEFederation object from a
// JSON byte array.
func UnmarshalSPIFFEFederation(
data []byte, opts ...MarshalOption,
) (*machineidv1.SPIFFEFederation, error) {
return UnmarshalProtoResource[*machineidv1.SPIFFEFederation](data, opts...)
}
// ValidateSPIFFEFederation validates the SPIFFEFederation object.
func ValidateSPIFFEFederation(s *machineidv1.SPIFFEFederation) error {
switch {
case s == nil:
return trace.BadParameter("object cannot be nil")
case s.GetVersion() != types.V1:
return trace.BadParameter("version: only %q is supported", types.V1)
case s.GetKind() != types.KindSPIFFEFederation:
return trace.BadParameter("kind: must be %q", types.KindSPIFFEFederation)
case !s.HasMetadata():
return trace.BadParameter("metadata: is required")
case s.GetMetadata().GetName() == "":
return trace.BadParameter("metadata.name: is required")
case !s.HasSpec():
return trace.BadParameter("spec: is required")
case !s.GetSpec().HasBundleSource():
return trace.BadParameter("spec.bundle_source: is required")
case s.GetSpec().GetBundleSource().HasHttpsWeb() && s.GetSpec().GetBundleSource().HasStatic():
return trace.BadParameter("spec.bundle_source: at most one of https_web or static can be set")
case !s.GetSpec().GetBundleSource().HasHttpsWeb() && !s.GetSpec().GetBundleSource().HasStatic():
return trace.BadParameter("spec.bundle_source: at least one of https_web or static must be set")
}
// Validate name is valid SPIFFE Trust Domain name without the "spiffe://"
name := s.GetMetadata().GetName()
if strings.HasPrefix(name, "spiffe://") {
return trace.BadParameter(
"metadata.name: must not include the spiffe:// prefix",
)
}
td, err := spiffeid.TrustDomainFromString(name)
if err != nil {
return trace.Wrap(err, "validating metadata.name")
}
// Validate Static
if s.GetSpec().GetBundleSource().HasStatic() {
if s.GetSpec().GetBundleSource().GetStatic().GetBundle() == "" {
return trace.BadParameter("spec.bundle_source.static.bundle: is required")
}
// Validate contents
// TODO(noah): Is this a bit intense to run on every validation?
// This could easily be moved into reconciliation...
_, err := spiffebundle.Parse(td, []byte(s.GetSpec().GetBundleSource().GetStatic().GetBundle()))
if err != nil {
return trace.Wrap(err, "validating spec.bundle_source.static.bundle")
}
}
// Validate HTTPSWeb
if s.GetSpec().GetBundleSource().HasHttpsWeb() {
if s.GetSpec().GetBundleSource().GetHttpsWeb().GetBundleEndpointUrl() == "" {
return trace.BadParameter("spec.bundle_source.https_web.bundle_endpoint_url: is required")
}
_, err := url.Parse(s.GetSpec().GetBundleSource().GetHttpsWeb().GetBundleEndpointUrl())
if err != nil {
return trace.Wrap(err, "validating spec.bundle_source.https_web.bundle_endpoint_url")
}
}
// Ensure that all key status fields are set if any are set. This is a safeguard against weird inconsistent states
// where some fields are set and others are not.
currentBundleSet := s.GetStatus().GetCurrentBundle() != ""
currentBundledSyncedAtSet := s.GetStatus().GetCurrentBundleSyncedAt() != nil
currentBundleSyncedFromSet := s.GetStatus().GetCurrentBundleSyncedFrom() != nil
anyStatusFieldSet := currentBundleSet || currentBundledSyncedAtSet || currentBundleSyncedFromSet
allStatusFieldsSet := currentBundleSet && currentBundledSyncedAtSet && currentBundleSyncedFromSet
if anyStatusFieldSet && !allStatusFieldsSet {
return trace.BadParameter("status: all of ['current_bundle', 'current_bundle_synced_at', 'current_bundle_synced_from'] must be set if any are set")
}
return nil
}