diff --git a/tool/tctl/common/collection.go b/tool/tctl/common/collection.go index 50374a7a766..aa1df7a33fb 100644 --- a/tool/tctl/common/collection.go +++ b/tool/tctl/common/collection.go @@ -636,27 +636,6 @@ func (c *userGroupCollection) WriteText(w io.Writer, verbose bool) error { return trace.Wrap(err) } -type auditQueryCollection struct { - auditQueries []*secreports.AuditQuery -} - -func (c *auditQueryCollection) Resources() []types.Resource { - r := make([]types.Resource, len(c.auditQueries)) - for i, resource := range c.auditQueries { - r[i] = resource - } - return r -} - -func (c *auditQueryCollection) WriteText(w io.Writer, verbose bool) error { - t := asciitable.MakeTable([]string{"Name", "Title", "Query", "Description"}) - for _, v := range c.auditQueries { - t.AddRow([]string{v.GetName(), v.Spec.Title, v.Spec.Query, v.Spec.Description}) - } - _, err := t.AsBuffer().WriteTo(w) - return trace.Wrap(err) -} - type securityReportCollection struct { items []*secreports.Report } diff --git a/tool/tctl/common/resource_command.go b/tool/tctl/common/resource_command.go index 5171f454214..28b23b399f3 100644 --- a/tool/tctl/common/resource_command.go +++ b/tool/tctl/common/resource_command.go @@ -128,7 +128,6 @@ func (rc *ResourceCommand) Initialize(app *kingpin.Application, _ *tctlcfg.Globa types.KindDevice: rc.createDevice, types.KindOktaImportRule: rc.createOktaImportRule, types.KindIntegration: rc.createIntegration, - types.KindAuditQuery: rc.createAuditQuery, types.KindSecurityReport: rc.createSecurityReport, types.KindCrownJewel: rc.createCrownJewel, types.KindVnetConfig: rc.createVnetConfig, @@ -963,11 +962,6 @@ func (rc *ResourceCommand) Delete(ctx context.Context, client *authclient.Client return trace.Wrap(err) } fmt.Printf("User group %q has been deleted\n", rc.ref.Name) - case types.KindAuditQuery: - if err := client.SecReportsClient().DeleteSecurityAuditQuery(ctx, rc.ref.Name); err != nil { - return trace.Wrap(err) - } - fmt.Printf("Audit query %q has been deleted\n", rc.ref.Name) case types.KindSecurityReport: if err := client.SecReportsClient().DeleteSecurityReport(ctx, rc.ref.Name); err != nil { return trace.Wrap(err) @@ -1404,21 +1398,6 @@ func (rc *ResourceCommand) getCollection(ctx context.Context, client *authclient } return &integrationCollection{integrations: resources}, nil - case types.KindAuditQuery: - if rc.ref.Name != "" { - auditQuery, err := client.SecReportsClient().GetSecurityAuditQuery(ctx, rc.ref.Name) - if err != nil { - return nil, trace.Wrap(err) - } - return &auditQueryCollection{auditQueries: []*secreports.AuditQuery{auditQuery}}, nil - } - - resources, err := client.SecReportsClient().GetSecurityAuditQueries(ctx) - if err != nil { - return nil, trace.Wrap(err) - } - - return &auditQueryCollection{auditQueries: resources}, nil case types.KindSecurityReport: if rc.ref.Name != "" { @@ -1557,22 +1536,6 @@ func findDeviceByIDOrTag(ctx context.Context, remote devicepb.DeviceTrustService return nil, trace.BadParameter("found multiple devices for asset tag %q, please retry using the device ID instead", idOrTag) } -func (rc *ResourceCommand) createAuditQuery(ctx context.Context, client *authclient.Client, raw services.UnknownResource) error { - in, err := services.UnmarshalAuditQuery(raw.Raw, services.DisallowUnknown()) - if err != nil { - return trace.Wrap(err) - } - - if err := in.CheckAndSetDefaults(); err != nil { - return trace.Wrap(err) - } - - if err = client.SecReportsClient().UpsertSecurityAuditQuery(ctx, in); err != nil { - return trace.Wrap(err) - } - return nil -} - func (rc *ResourceCommand) createSecurityReport(ctx context.Context, client *authclient.Client, raw services.UnknownResource) error { in, err := services.UnmarshalSecurityReport(raw.Raw, services.DisallowUnknown()) if err != nil { diff --git a/tool/tctl/common/resources/audit_query.go b/tool/tctl/common/resources/audit_query.go new file mode 100644 index 00000000000..77302a68edd --- /dev/null +++ b/tool/tctl/common/resources/audit_query.go @@ -0,0 +1,107 @@ +/* + * Teleport + * Copyright (C) 2026 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 . + */ + +package resources + +import ( + "context" + "fmt" + "io" + + "github.com/gravitational/trace" + + "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/api/types/secreports" + "github.com/gravitational/teleport/lib/asciitable" + "github.com/gravitational/teleport/lib/auth/authclient" + "github.com/gravitational/teleport/lib/services" +) + +type auditQueryCollection struct { + auditQueries []*secreports.AuditQuery +} + +func (c *auditQueryCollection) Resources() []types.Resource { + r := make([]types.Resource, len(c.auditQueries)) + for i, resource := range c.auditQueries { + r[i] = resource + } + return r +} + +func (c *auditQueryCollection) WriteText(w io.Writer, verbose bool) error { + t := asciitable.MakeTable([]string{"Name", "Title", "Query", "Description"}) + for _, v := range c.auditQueries { + t.AddRow([]string{v.GetName(), v.Spec.Title, v.Spec.Query, v.Spec.Description}) + } + _, err := t.AsBuffer().WriteTo(w) + return trace.Wrap(err) +} + +func auditQueryHandler() Handler { + return Handler{ + getHandler: getAuditQuery, + createHandler: createAuditQuery, + deleteHandler: deleteAuditQuery, + singleton: false, + mfaRequired: true, + description: "A saved audit query, can be invoked directly or using Access Monitoring Rules. Requires Access Monitoring.", + } +} + +func getAuditQuery(ctx context.Context, client *authclient.Client, ref services.Ref, opts GetOpts) (Collection, error) { + if ref.Name == "" { + auditQueries, err := client.SecReportsClient().GetSecurityAuditQueries(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + return &auditQueryCollection{auditQueries: auditQueries}, nil + } + + auditQuery, err := client.SecReportsClient().GetSecurityAuditQuery(ctx, ref.Name) + if err != nil { + return nil, trace.Wrap(err) + } + return &auditQueryCollection{auditQueries: []*secreports.AuditQuery{auditQuery}}, nil +} + +func createAuditQuery(ctx context.Context, client *authclient.Client, raw services.UnknownResource, opts CreateOpts) error { + auditQuery, err := services.UnmarshalAuditQuery(raw.Raw, services.DisallowUnknown()) + if err != nil { + return trace.Wrap(err) + } + + if err := auditQuery.CheckAndSetDefaults(); err != nil { + return trace.Wrap(err) + } + + if err := client.SecReportsClient().UpsertSecurityAuditQuery(ctx, auditQuery); err != nil { + return trace.Wrap(err) + } + fmt.Printf("audit query %q upserted\n", auditQuery.GetName()) + return nil +} + +func deleteAuditQuery(ctx context.Context, client *authclient.Client, ref services.Ref) error { + name := ref.Name + if err := client.SecReportsClient().DeleteSecurityAuditQuery(ctx, name); err != nil { + return trace.Wrap(err) + } + fmt.Printf("audit query %q deleted\n", name) + return nil +} diff --git a/tool/tctl/common/resources/resource.go b/tool/tctl/common/resources/resource.go index 3c40206840c..cfd39df8194 100644 --- a/tool/tctl/common/resources/resource.go +++ b/tool/tctl/common/resources/resource.go @@ -40,6 +40,7 @@ func Handlers() map[string]Handler { types.KindAccessRequest: accessRequestHandler(), types.KindApp: appHandler(), types.KindAppServer: appServerHandler(), + types.KindAuditQuery: auditQueryHandler(), types.KindAuthServer: authHandler(), types.KindAutoUpdateAgentReport: autoUpdateAgentReportHandler(), types.KindAutoUpdateAgentRollout: autoUpdateAgentRolloutHandler(),