From c71e8a851b3c62b581077c4980aae163fe448e5f Mon Sep 17 00:00:00 2001 From: Trent Clarke Date: Fri, 11 Aug 2023 16:23:23 +1000 Subject: [PATCH 1/2] Changes to Discord plugin for running in hosted mode. Includes: * Moves Default API URL setting into Sidcord config CheckAndSetDefaults() * Only creates a Teleport client is one is not supplied during plugin construction. * Optional hosted-plugin status updates --- api/types/plugin.go | 2 +- integrations/access/discord/bot.go | 54 +++++++++++++++++++++------ integrations/access/discord/config.go | 36 +++++++++++++----- 3 files changed, 70 insertions(+), 22 deletions(-) diff --git a/api/types/plugin.go b/api/types/plugin.go index 747c2da8d3953..a3db835b67869 100644 --- a/api/types/plugin.go +++ b/api/types/plugin.go @@ -46,7 +46,7 @@ const ( PluginTypePagerDuty = "pagerduty" // PluginTypeMattermost is the PagerDuty access plugin PluginTypeMattermost = "mattermost" - // PluginTypeDiscord indicates the Discord plugin + // PluginTypeDiscord indicates the Discord access plugin PluginTypeDiscord = "discord" ) diff --git a/integrations/access/discord/bot.go b/integrations/access/discord/bot.go index 0c8931b204b86..1e4680b6fe6ef 100644 --- a/integrations/access/discord/bot.go +++ b/integrations/access/discord/bot.go @@ -29,6 +29,7 @@ import ( "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/integrations/access/common" "github.com/gravitational/teleport/integrations/lib" + "github.com/gravitational/teleport/integrations/lib/logger" pd "github.com/gravitational/teleport/integrations/lib/plugindata" ) @@ -36,6 +37,7 @@ const discordMaxConns = 100 const discordHTTPTimeout = 10 * time.Second const discordRedColor = 13771309 // Green OxD2222D const discordGreenColor = 2328611 // Red 0x2328611 +const discordStatusUpdateTimeout = 10 * time.Second // DiscordBot is a discord client that works with AccessRequest. // It's responsible for formatting and posting a message on Discord when an @@ -47,22 +49,50 @@ type DiscordBot struct { webProxyURL *url.URL } -// onAfterResponseDiscord resty error function for Discord -func onAfterResponseDiscord(_ *resty.Client, resp *resty.Response) error { - if resp.IsSuccess() { - return nil - } +// onAfterResponseDiscord creates and configures a post-response +// handler for Discord Requests. Handles routing status updates +// through to the status sink (if supplied). +func onAfterResponseDiscord(statusSink common.StatusSink) resty.ResponseMiddleware { + return func(_ *resty.Client, resp *resty.Response) error { + if statusSink != nil { + emitStatusUpdate(resp, statusSink) + } - var result DiscordResponse - if err := json.Unmarshal(resp.Body(), &result); err != nil { - return trace.Wrap(err) - } + if resp.IsSuccess() { + return nil + } + + var result DiscordResponse + if err := json.Unmarshal(resp.Body(), &result); err != nil { + return trace.Wrap(err) + } + + if result.Message != "" { + return trace.Errorf("%s (code: %v, status: %d)", result.Message, result.Code, resp.StatusCode()) + } - if result.Message != "" { - return trace.Errorf("%s (code: %v, status: %d)", result.Message, result.Code, resp.StatusCode()) + return trace.Errorf("Discord API returned error: %s (status: %d)", string(resp.Body()), resp.StatusCode()) } +} - return trace.Errorf("Discord API returned error: %s (status: %d)", string(resp.Body()), resp.StatusCode()) +func emitStatusUpdate(resp *resty.Response, statusSink common.StatusSink) { + status := common.StatusFromStatusCode(resp.StatusCode()) + + // There is sensible context in scope for us to use when emitting the + // status update. We can't use the context from the Resty response, + // as that could already be canceled, which would block us from emitting + // a status update showing that the plugin is currently broken. + // + // Using the background context with a reasonable timeout seems the + // least-bad option. + ctx, cancel := context.WithTimeout(context.Background(), discordStatusUpdateTimeout) + defer cancel() + + if err := statusSink.Emit(ctx, status); err != nil { + logger.Get(resp.Request.Context()). + WithError(err). + Errorf("Error while emitting Discord plugin status: %v", err) + } } func (b DiscordBot) CheckHealth(ctx context.Context) error { diff --git a/integrations/access/discord/config.go b/integrations/access/discord/config.go index dfc9af741d792..3f0379c6cbba0 100644 --- a/integrations/access/discord/config.go +++ b/integrations/access/discord/config.go @@ -17,6 +17,7 @@ limitations under the License. package discord import ( + "context" "net/http" "net/url" @@ -25,6 +26,7 @@ import ( "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/integrations/access/common" + "github.com/gravitational/teleport/integrations/access/common/teleport" "github.com/gravitational/teleport/integrations/lib" ) @@ -33,6 +35,15 @@ const discordAPIUrl = "https://discord.com/api/" type Config struct { common.BaseConfig Discord common.GenericAPIConfig + + // Teleport is a handle to the client to use when communicating with + // the Teleport auth server. The PagerDuty app will create a GRPC- + // based client on startup if this is not set. + Client teleport.Client + + // StatusSink receives any status updates from the plugin for + // further processing. Status updates will be ignored if not set. + StatusSink common.StatusSink } // CheckAndSetDefaults checks the config struct for any logical errors, and sets default values @@ -45,6 +56,9 @@ func (c *Config) CheckAndSetDefaults() error { if c.Discord.Token == "" { return trace.BadParameter("missing required value discord.token") } + if c.Discord.APIURL == "" { + c.Discord.APIURL = discordAPIUrl + } if c.Log.Output == "" { c.Log.Output = "stderr" } @@ -61,6 +75,16 @@ func (c *Config) CheckAndSetDefaults() error { return nil } +// GetTeleportClient implements PluginConfiguration. If a pre-created client +// was supplied on construction, this method will return that. If not, an RPC +// client will be created using the values in the config. +func (c *Config) GetTeleportClient(ctx context.Context) (teleport.Client, error) { + if c.Client != nil { + return c.Client, nil + } + return c.BaseConfig.GetTeleportClient(ctx) +} + // NewBot initializes the new Discord message generator (DiscordBot) func (c *Config) NewBot(clusterName, webProxyAddr string) (common.MessagingBot, error) { var ( @@ -83,17 +107,11 @@ func (c *Config) NewBot(clusterName, webProxyAddr string) (common.MessagingBot, MaxIdleConnsPerHost: discordMaxConns, }, }). + SetBaseURL(c.Discord.APIURL). SetHeader("Content-Type", "application/json"). SetHeader("Accept", "application/json"). - SetHeader("Authorization", token) - - // APIURL parameter is set only in tests - if endpoint := c.Discord.APIURL; endpoint != "" { - client.SetBaseURL(endpoint) - } else { - client.SetBaseURL(discordAPIUrl) - client.OnAfterResponse(onAfterResponseDiscord) - } + SetHeader("Authorization", token). + OnAfterResponse(onAfterResponseDiscord(c.StatusSink)) return DiscordBot{ client: client, From b21a9d6735d9b8dea37ef0eb4dd02e2e52e6f66e Mon Sep 17 00:00:00 2001 From: Trent Clarke Date: Mon, 14 Aug 2023 23:15:42 +1000 Subject: [PATCH 2/2] fmt --- integrations/access/discord/config.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integrations/access/discord/config.go b/integrations/access/discord/config.go index 3f0379c6cbba0..648b8a3ab0c87 100644 --- a/integrations/access/discord/config.go +++ b/integrations/access/discord/config.go @@ -75,8 +75,8 @@ func (c *Config) CheckAndSetDefaults() error { return nil } -// GetTeleportClient implements PluginConfiguration. If a pre-created client -// was supplied on construction, this method will return that. If not, an RPC +// GetTeleportClient implements PluginConfiguration. If a pre-created client +// was supplied on construction, this method will return that. If not, an RPC // client will be created using the values in the config. func (c *Config) GetTeleportClient(ctx context.Context) (teleport.Client, error) { if c.Client != nil {