From adf3be3ea558227f6e4e03e8812c50349681bca4 Mon Sep 17 00:00:00 2001 From: datphamcode295 Date: Mon, 23 Dec 2024 16:22:38 +0700 Subject: [PATCH] feat: get approver from database --- .env.example | 3 ++ cmd/teleport-discord-bot/main.go | 3 ++ go.mod | 9 +++++ go.sum | 19 ++++++++++ internal/discord/client.go | 63 +++++++++++++++++++++++++------- internal/repository/init.go | 31 ++++++++++++++++ internal/repository/user.go | 35 ++++++++++++++++++ 7 files changed, 149 insertions(+), 14 deletions(-) create mode 100644 internal/repository/init.go create mode 100644 internal/repository/user.go diff --git a/.env.example b/.env.example index c91ea6c..692c4ba 100644 --- a/.env.example +++ b/.env.example @@ -9,3 +9,6 @@ WATCHER_LIST=tctl requests list # Path to Teleport authentication credentials AUTH_PEM=base64-encoded-auth-pem + +# Database URL for Teleport Audit Log +DB_URL=postgresql://postgres:password@localhost:5432/teleport_discord?sslmode=disable diff --git a/cmd/teleport-discord-bot/main.go b/cmd/teleport-discord-bot/main.go index 329d574..27b495c 100644 --- a/cmd/teleport-discord-bot/main.go +++ b/cmd/teleport-discord-bot/main.go @@ -10,6 +10,7 @@ import ( "github.com/dwarvesf/teleport-discord-bot/internal/config" "github.com/dwarvesf/teleport-discord-bot/internal/discord" "github.com/dwarvesf/teleport-discord-bot/internal/httpserver" + repo "github.com/dwarvesf/teleport-discord-bot/internal/repository" "github.com/dwarvesf/teleport-discord-bot/internal/teleport" ) @@ -21,6 +22,8 @@ func main() { os.Exit(1) } + repo.ConnectDatabase() + // Create HTTP server httpServer := httpserver.NewServer(cfg.Port) defer func() { diff --git a/go.mod b/go.mod index 55feaf2..44e94d4 100644 --- a/go.mod +++ b/go.mod @@ -29,6 +29,12 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.4.2 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgx/v5 v5.5.5 // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect github.com/jonboulle/clockwork v0.4.0 // indirect github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect github.com/russellhaering/gosaml2 v0.9.1 // indirect @@ -46,6 +52,7 @@ require ( golang.org/x/crypto v0.31.0 // indirect golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb // indirect golang.org/x/net v0.33.0 // indirect + golang.org/x/sync v0.10.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/term v0.27.0 // indirect golang.org/x/text v0.21.0 // indirect @@ -53,4 +60,6 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect + gorm.io/driver/postgres v1.5.11 // indirect + gorm.io/gorm v1.25.12 // indirect ) diff --git a/go.sum b/go.sum index 5e09937..b95c4d9 100644 --- a/go.sum +++ b/go.sum @@ -95,6 +95,18 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= github.com/gtuk/discordwebhook v1.2.0 h1:7+gWPKSGyXjopu/6X9+oGbn0knTkDVXUM909+IXGZ/U= github.com/gtuk/discordwebhook v1.2.0/go.mod h1:U3LdXNJ1e0bx3MMe2a4mB1VBantPHOPly2jNd8ZWXec= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= +github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= @@ -133,6 +145,7 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -208,6 +221,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -299,5 +314,9 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314= +gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= +gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= +gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/discord/client.go b/internal/discord/client.go index 060079b..4a12827 100644 --- a/internal/discord/client.go +++ b/internal/discord/client.go @@ -6,6 +6,8 @@ import ( "time" "github.com/dwarvesf/teleport-discord-bot/internal/config" + repo "github.com/dwarvesf/teleport-discord-bot/internal/repository" + "github.com/gravitational/teleport/api/types" "github.com/gtuk/discordwebhook" ) @@ -41,6 +43,23 @@ func ptrBool(b bool) *bool { // HandleNewAccessRequest creates a notification for a new access request func (d *Client) HandleNewAccessRequest(r types.AccessRequest) error { + approverIDs := "" + approvers, err := repo.GetApprovers() + if err != nil { + fmt.Printf("Failed to get approvers: %v\n", err) + } else { + for _, a := range approvers { + approverIDs += fmt.Sprintf("<@%s> ", a.DiscordID) + } + } + + requester := r.GetUser() + + dbRequester, err := repo.GetUserByTlpUsername(requester) + if err == nil { + requester = dbRequester.DiscordName + } + fields := []discordwebhook.Field{ { Name: ptrString("Request ID"), @@ -48,8 +67,8 @@ func (d *Client) HandleNewAccessRequest(r types.AccessRequest) error { Inline: ptrBool(false), }, { - Name: ptrString("User"), - Value: ptrString(r.GetUser()), + Name: ptrString("Requester"), + Value: ptrString(requester), Inline: ptrBool(true), }, { @@ -58,7 +77,7 @@ func (d *Client) HandleNewAccessRequest(r types.AccessRequest) error { Inline: ptrBool(true), }, { - Name: ptrString("Session TTL"), + Name: ptrString("Duration"), Value: ptrString(r.GetSessionTLL().Sub(r.GetCreationTime()).Round(time.Second).String()), Inline: ptrBool(true), }, @@ -66,21 +85,22 @@ func (d *Client) HandleNewAccessRequest(r types.AccessRequest) error { if r.GetRequestReason() != "" { fields = append(fields, discordwebhook.Field{ - Name: ptrString("Request Reason"), + Name: ptrString("Reason"), Value: ptrString(r.GetRequestReason()), Inline: ptrBool(false), }) } embed := discordwebhook.Embed{ - Title: ptrString("New Access Request"), - Description: ptrString(fmt.Sprintf("Approve request by running command %s: ```tctl requests approve %s```", d.cfg.WatcherList, r.GetName())), + Title: ptrString("New access request"), + Description: ptrString(fmt.Sprintf("Approve request by running command: ```tctl requests approve %s```", r.GetName())), Color: ptrString("3093206"), Fields: &fields, } message := discordwebhook.Message{ - Embeds: &[]discordwebhook.Embed{embed}, + Embeds: &[]discordwebhook.Embed{embed}, + Content: ptrString(approverIDs), } if err := d.sendWebhookNotification(message); err != nil { @@ -92,9 +112,17 @@ func (d *Client) HandleNewAccessRequest(r types.AccessRequest) error { // HandleApproveAccessRequest creates a notification for an approved access request func (d *Client) HandleApproveAccessRequest(r types.AccessRequest) error { + requester := r.GetUser() + + dbRequester, err := repo.GetUserByTlpUsername(requester) + if err == nil { + requester = dbRequester.DiscordName + } + embed := discordwebhook.Embed{ - Title: ptrString("Access Request Approved"), - Color: ptrString("2021216"), + Title: ptrString("Access request approved"), + Description: ptrString(fmt.Sprintf("Locking user by running command: ```tctl lock --user=%s --message=\"block reason\" --ttl=1h```", r.GetUser())), + Color: ptrString("2021216"), Fields: &[]discordwebhook.Field{ { Name: ptrString("Request ID"), @@ -102,8 +130,8 @@ func (d *Client) HandleApproveAccessRequest(r types.AccessRequest) error { Inline: ptrBool(false), }, { - Name: ptrString("User"), - Value: ptrString(r.GetUser()), + Name: ptrString("Requester"), + Value: ptrString(requester), Inline: ptrBool(true), }, { @@ -127,8 +155,15 @@ func (d *Client) HandleApproveAccessRequest(r types.AccessRequest) error { // HandleDenyAccessRequest creates a notification for a denied access request func (d *Client) HandleDenyAccessRequest(r types.AccessRequest) error { + requester := r.GetUser() + + dbRequester, err := repo.GetUserByTlpUsername(requester) + if err == nil { + requester = dbRequester.DiscordName + } + embed := discordwebhook.Embed{ - Title: ptrString("Access Request Denied"), + Title: ptrString("Access request denied"), Color: ptrString("15158332"), Fields: &[]discordwebhook.Field{ { @@ -137,8 +172,8 @@ func (d *Client) HandleDenyAccessRequest(r types.AccessRequest) error { Inline: ptrBool(false), }, { - Name: ptrString("User"), - Value: ptrString(r.GetUser()), + Name: ptrString("Requester"), + Value: ptrString(requester), Inline: ptrBool(true), }, { diff --git a/internal/repository/init.go b/internal/repository/init.go new file mode 100644 index 0000000..ef09e8b --- /dev/null +++ b/internal/repository/init.go @@ -0,0 +1,31 @@ +package repo + +import ( + "fmt" + "log" + "os" + + "gorm.io/driver/postgres" + "gorm.io/gorm" +) + +var DB *gorm.DB + +func ConnectDatabase() { + dbURL := os.Getenv("DB_URL") + if dbURL == "" { + log.Fatal("DB_URL environment variable is not set") + } + + database, err := gorm.Open(postgres.Open(dbURL), &gorm.Config{}) + if err != nil { + log.Fatal("Failed to connect to database: ", err) + } + + DB = database + fmt.Println("Successfully connected to database!") +} + +func GetDB() *gorm.DB { + return DB +} diff --git a/internal/repository/user.go b/internal/repository/user.go new file mode 100644 index 0000000..c0fe66d --- /dev/null +++ b/internal/repository/user.go @@ -0,0 +1,35 @@ +// user.go +package repo + +// User represents the user table schema +type User struct { + TlpUsername string `gorm:"column:tlp_username"` + DiscordID string `gorm:"column:discord_id;primaryKey"` + DiscordName string `gorm:"column:discord_name"` + IsApprover bool `gorm:"column:is_approver"` +} + +// TableName overrides the table name +func (User) TableName() string { + return "user" +} + +// GetApprovers returns all users with is_approver = true +func GetApprovers() ([]User, error) { + var approvers []User + result := DB.Where("is_approver = ?", true).Find(&approvers) + if result.Error != nil { + return nil, result.Error + } + return approvers, nil +} + +// GetUserByTlpUsername returns a user by their TLP username +func GetUserByTlpUsername(tlpUsername string) (*User, error) { + var user User + result := DB.Where("tlp_username = ?", tlpUsername).First(&user) + if result.Error != nil { + return nil, result.Error + } + return &user, nil +}