tctl resource: convert saml_idp_service_provider handler (#61386)

This commit is contained in:
Sakshyam Shah
2025-11-14 20:16:03 +00:00
committed by GitHub
parent 5fa63c3a7e
commit 4bd425c037
4 changed files with 166 additions and 83 deletions
-22
View File
@@ -692,28 +692,6 @@ func (l *loginRuleCollection) Resources() []types.Resource {
return resources
}
//nolint:revive // Because we want this to be IdP.
type samlIdPServiceProviderCollection struct {
serviceProviders []types.SAMLIdPServiceProvider
}
func (c *samlIdPServiceProviderCollection) Resources() []types.Resource {
r := make([]types.Resource, len(c.serviceProviders))
for i, resource := range c.serviceProviders {
r[i] = resource
}
return r
}
func (c *samlIdPServiceProviderCollection) WriteText(w io.Writer, verbose bool) error {
t := asciitable.MakeTable([]string{"Name"})
for _, serviceProvider := range c.serviceProviders {
t.AddRow([]string{serviceProvider.GetName()})
}
_, err := t.AsBuffer().WriteTo(w)
return trace.Wrap(err)
}
type deviceCollection struct {
devices []*devicepb.Device
}
-61
View File
@@ -33,7 +33,6 @@ import (
"time"
"github.com/alecthomas/kingpin/v2"
"github.com/crewjam/saml/samlsp"
"github.com/gravitational/trace"
kyaml "k8s.io/apimachinery/pkg/util/yaml"
@@ -131,7 +130,6 @@ func (rc *ResourceCommand) Initialize(app *kingpin.Application, _ *tctlcfg.Globa
types.KindNetworkRestrictions: rc.createNetworkRestrictions,
types.KindKubernetesCluster: rc.createKubeCluster,
types.KindLoginRule: rc.createLoginRule,
types.KindSAMLIdPServiceProvider: rc.createSAMLIdPServiceProvider,
types.KindDevice: rc.createDevice,
types.KindOktaImportRule: rc.createOktaImportRule,
types.KindIntegration: rc.createIntegration,
@@ -776,46 +774,6 @@ func (rc *ResourceCommand) createLoginRule(ctx context.Context, client *authclie
return nil
}
func (rc *ResourceCommand) createSAMLIdPServiceProvider(ctx context.Context, client *authclient.Client, raw services.UnknownResource) error {
// Create services.SAMLIdPServiceProvider from raw YAML to extract the service provider name.
sp, err := services.UnmarshalSAMLIdPServiceProvider(raw.Raw, services.DisallowUnknown())
if err != nil {
return trace.Wrap(err)
}
if sp.GetEntityDescriptor() != "" {
// verify that entity descriptor parses
ed, err := samlsp.ParseMetadata([]byte(sp.GetEntityDescriptor()))
if err != nil {
return trace.BadParameter("invalid entity descriptor for SAML IdP Service Provider %q: %v", sp.GetEntityID(), err)
}
// issue warning about unsupported ACS bindings.
if err := services.FilterSAMLEntityDescriptor(ed, false /* quiet */); err != nil {
slog.WarnContext(ctx, "Entity descriptor for SAML IdP service provider contains unsupported ACS bindings",
"entity_id", sp.GetEntityID(),
"error", err,
)
}
}
serviceProviderName := sp.GetName()
exists := false
if err = client.CreateSAMLIdPServiceProvider(ctx, sp); err != nil {
if trace.IsAlreadyExists(err) {
exists = true
err = client.UpdateSAMLIdPServiceProvider(ctx, sp)
}
if err != nil {
return trace.Wrap(err)
}
}
fmt.Printf("SAML IdP service provider %q has been %s\n", serviceProviderName, UpsertVerb(exists, rc.IsForced()))
return nil
}
func (rc *ResourceCommand) createDevice(ctx context.Context, client *authclient.Client, raw services.UnknownResource) error {
res, err := services.UnmarshalDevice(raw.Raw)
if err != nil {
@@ -1204,11 +1162,6 @@ func (rc *ResourceCommand) Delete(ctx context.Context, client *authclient.Client
return trail.FromGRPC(err)
}
fmt.Printf("login rule %q has been deleted\n", rc.ref.Name)
case types.KindSAMLIdPServiceProvider:
if err := client.DeleteSAMLIdPServiceProvider(ctx, rc.ref.Name); err != nil {
return trace.Wrap(err)
}
fmt.Printf("SAML IdP service provider %q has been deleted\n", rc.ref.Name)
case types.KindDevice:
remote := client.DevicesClient()
device, err := findDeviceByIDOrTag(ctx, remote, rc.ref.Name)
@@ -1667,20 +1620,6 @@ func (rc *ResourceCommand) getCollection(ctx context.Context, client *authclient
Name: rc.ref.Name,
})
return &loginRuleCollection{[]*loginrulepb.LoginRule{rule}}, trail.FromGRPC(err)
case types.KindSAMLIdPServiceProvider:
if rc.ref.Name != "" {
serviceProvider, err := client.GetSAMLIdPServiceProvider(ctx, rc.ref.Name)
if err != nil {
return nil, trace.Wrap(err)
}
return &samlIdPServiceProviderCollection{serviceProviders: []types.SAMLIdPServiceProvider{serviceProvider}}, nil
}
resources, err := stream.Collect(clientutils.Resources(ctx, client.ListSAMLIdPServiceProviders))
if err != nil {
return nil, trace.Wrap(err)
}
return &samlIdPServiceProviderCollection{serviceProviders: resources}, nil
case types.KindDevice:
remote := client.DevicesClient()
if rc.ref.Name != "" {
+1
View File
@@ -63,6 +63,7 @@ func Handlers() map[string]Handler {
types.KindProxy: proxyHandler(),
types.KindRole: roleHandler(),
types.KindSAMLConnector: samlConnectorHandler(),
types.KindSAMLIdPServiceProvider: samlIdPServiceProviderHandler(),
types.KindSessionRecordingConfig: sessionRecordingConfigHandler(),
types.KindSigstorePolicy: sigstorePolicyHandler(),
types.KindSPIFFEFederation: spiffeFederationHandler(),
@@ -0,0 +1,165 @@
/*
* Teleport
* Copyright (C) 2025 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 resources
import (
"context"
"fmt"
"io"
"log/slog"
"github.com/crewjam/saml/samlsp"
"github.com/gravitational/trace"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/api/utils/clientutils"
"github.com/gravitational/teleport/lib/asciitable"
"github.com/gravitational/teleport/lib/auth/authclient"
"github.com/gravitational/teleport/lib/itertools/stream"
"github.com/gravitational/teleport/lib/services"
)
type samlIdPServiceProviderCollection struct {
serviceProviders []types.SAMLIdPServiceProvider
}
// Resources returns collection of SAML IdP service provider resource.
func (c *samlIdPServiceProviderCollection) Resources() []types.Resource {
r := make([]types.Resource, len(c.serviceProviders))
for i, resource := range c.serviceProviders {
r[i] = resource
}
return r
}
// WriteText writes collection of SAML IdP service provider resource to [w].
func (c *samlIdPServiceProviderCollection) WriteText(w io.Writer, verbose bool) error {
t := asciitable.MakeTable([]string{"Name"})
for _, serviceProvider := range c.serviceProviders {
t.AddRow([]string{serviceProvider.GetName()})
}
_, err := t.AsBuffer().WriteTo(w)
return trace.Wrap(err)
}
func samlIdPServiceProviderHandler() Handler {
return Handler{
getHandler: getSAMLIdPServiceProvider,
createHandler: createSAMLIdPServiceProvider,
updateHandler: updateSAMLIdPServiceProvider,
deleteHandler: deleteSAMLIdPServiceProvider,
singleton: false,
// MFA not enforced in Auth.
mfaRequired: false,
description: "Configure service provider for the Teleport SAML IdP",
}
}
func getSAMLIdPServiceProvider(ctx context.Context, client *authclient.Client, ref services.Ref, opts GetOpts) (Collection, error) {
if ref.Name != "" {
serviceProvider, err := client.GetSAMLIdPServiceProvider(ctx, ref.Name)
if err != nil {
return nil, trace.Wrap(err)
}
return &samlIdPServiceProviderCollection{serviceProviders: []types.SAMLIdPServiceProvider{serviceProvider}}, nil
}
resources, err := stream.Collect(clientutils.Resources(ctx, client.ListSAMLIdPServiceProviders))
if err != nil {
return nil, trace.Wrap(err)
}
return &samlIdPServiceProviderCollection{serviceProviders: resources}, nil
}
func createSAMLIdPServiceProvider(ctx context.Context, client *authclient.Client, raw services.UnknownResource, opts CreateOpts) error {
// Create services.SAMLIdPServiceProvider from raw YAML to extract the service provider name.
sp, err := services.UnmarshalSAMLIdPServiceProvider(raw.Raw, services.DisallowUnknown())
if err != nil {
return trace.Wrap(err)
}
if sp.GetEntityDescriptor() != "" {
// verify that entity descriptor parses
ed, err := samlsp.ParseMetadata([]byte(sp.GetEntityDescriptor()))
if err != nil {
return trace.BadParameter("invalid entity descriptor for SAML IdP Service Provider %q: %v", sp.GetEntityID(), err)
}
// issue warning about unsupported ACS bindings.
if err := services.FilterSAMLEntityDescriptor(ed, false /* quiet */); err != nil {
slog.WarnContext(ctx, "Entity descriptor for SAML IdP service provider contains unsupported ACS bindings",
"entity_id", sp.GetEntityID(),
"error", err,
)
}
}
exists := false
if err = client.CreateSAMLIdPServiceProvider(ctx, sp); err != nil {
if trace.IsAlreadyExists(err) {
exists = true
err = client.UpdateSAMLIdPServiceProvider(ctx, sp)
}
if err != nil {
return trace.Wrap(err)
}
}
fmt.Printf("SAML IdP service provider %q has been %s\n", sp.GetName(), upsertVerb(exists, opts.Force))
return nil
}
func updateSAMLIdPServiceProvider(ctx context.Context, client *authclient.Client, raw services.UnknownResource, opts CreateOpts) error {
// Create services.SAMLIdPServiceProvider from raw YAML to extract the service provider name.
sp, err := services.UnmarshalSAMLIdPServiceProvider(raw.Raw, services.DisallowUnknown())
if err != nil {
return trace.Wrap(err)
}
if sp.GetEntityDescriptor() != "" {
// verify that entity descriptor parses
ed, err := samlsp.ParseMetadata([]byte(sp.GetEntityDescriptor()))
if err != nil {
return trace.BadParameter("invalid entity descriptor for SAML IdP Service Provider %q: %v", sp.GetEntityID(), err)
}
// issue warning about unsupported ACS bindings.
if err := services.FilterSAMLEntityDescriptor(ed, false /* quiet */); err != nil {
slog.WarnContext(ctx, "Entity descriptor for SAML IdP service provider contains unsupported ACS bindings",
"entity_id", sp.GetEntityID(),
"error", err,
)
}
}
if err = client.UpdateSAMLIdPServiceProvider(ctx, sp); err != nil {
return trace.Wrap(err)
}
fmt.Printf("SAML IdP service provider %q has been updated\n", sp.GetName())
return nil
}
func deleteSAMLIdPServiceProvider(ctx context.Context, client *authclient.Client, ref services.Ref) error {
if err := client.DeleteSAMLIdPServiceProvider(ctx, ref.Name); err != nil {
return trace.Wrap(err)
}
fmt.Printf("SAML IdP service provider %q has been deleted\n", ref.Name)
return nil
}