Skip to content

Instantly share code, notes, and snippets.

@nicolai86
Created August 27, 2019 16:05
Show Gist options
  • Save nicolai86/ceb63fbedb6ce116a1bd95d024ce2149 to your computer and use it in GitHub Desktop.
Save nicolai86/ceb63fbedb6ce116a1bd95d024ce2149 to your computer and use it in GitHub Desktop.
package autogrant
import (
"context"
"fmt"
"log"
"strings"
chime "github.com/nicolai86/aws-chime-sdk"
)
type Poster interface {
Post(string) (*chime.Message, error)
}
type Command struct {
Channels []string
}
func (c *Command) Applies(msg, author string) bool {
return strings.Contains(msg, "requested access to this room")
}
func extractLogin(msg string) (string, string) {
start := 0
at := 0
login := ""
domain := ""
for i, c := range msg {
if c == '(' {
start = i
continue
}
if c == '@' {
login = msg[start+1 : i]
at = i
}
if c == ')' {
domain = msg[at+1 : i]
break
}
}
return login, domain
}
func (c *Command) Execute(client *chime.Client, p Poster, evt chime.WebsocketPushEvent, msg string) {
parts := strings.Split(evt.Data.ID, "|")
roomID := parts[0]
out, err := client.GetRoom(context.Background(), &chime.GetRoomInput{chime.String(roomID)})
if err != nil {
log.Printf("room fetch error: %v", err)
return
}
room := out.Room
match := false
for _, name := range c.Channels {
match = match || strings.Contains(strings.ToLower(*room.Name), strings.ToLower(name))
}
if !match {
log.Printf("Ignoring grant b/c it does not have autogrant enabled: %q", room.Name)
return
}
login, domain := extractLogin(msg)
if !strings.Contains(domain, "amazon.com") {
log.Printf("AutoComplete prevented non-amazon login: %v", err)
return
}
// TODO also search by name
cs, err := client.AutoCompleteContacts(context.Background(), &chime.AutoCompleteContactsInput{
Query: chime.String(login),
})
if err != nil {
log.Printf("AutoComplete failed: %v", err)
return
}
var cc *chime.Contact
expectedEmail := fmt.Sprintf("%[email protected]", login)
for _, c := range *cs.Contacts {
if *c.Email == expectedEmail {
cc = &c
break
}
}
if cc == nil {
expectedPrefix := fmt.Sprintf("%s@", login)
for _, c := range *cs.Contacts {
if strings.HasPrefix(*c.Email, expectedPrefix) {
cc = &c
break
}
}
}
if cc == nil {
log.Printf("AutoComplete returned %d contacts (expected: 1 match)", len(*cs.Contacts))
return
}
log.Printf("Adding %q to %q", cc.ID, roomID)
client.AddRoomMember(context.Background(), &chime.AddRoomMemberInput{
RoomID: chime.String(roomID),
ProfileID: cc.ID,
})
}
package main
import (
"context"
"flag"
"fmt"
"log"
"math/rand"
"net/http"
"os"
"os/signal"
"syscall"
"time"
vaultapi "github.com/hashicorp/vault/api"
chime "github.com/nicolai86/aws-chime-sdk"
"autogrant"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
processedChimeEventCount = prometheus.NewCounter(prometheus.CounterOpts{
Name: "chime_event_count_processed",
Help: "Number of chime events processed",
})
receivedChimeEventCount = prometheus.NewCounter(prometheus.CounterOpts{
Name: "chime_event_count_received",
Help: "Total number of chime events received",
})
)
func init() {
prometheus.MustRegister(processedChimeEventCount)
prometheus.MustRegister(receivedChimeEventCount)
}
func init() {
rand.Seed(time.Now().UTC().UnixNano())
}
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
func NewID() string {
b := make([]byte, 36) // Same length as uuid v4
for i := range b {
b[i] = charset[rand.Intn(len(charset))]
}
return string(b)
}
func main() {
var username, password string
token := flag.String("vault-token", "", "vault token")
vaultHost := flag.String("vault-host", "", "vault host")
config := flag.String("config", "", "chime config to resume sessions")
nsqHost := flag.String("nsq-host", "", "nsq host to connect to")
nsqClientCert := flag.String("nsq-client-cert", "", "nsq client cert")
nsqClientCertKey := flag.String("nsq-client-cert-key", "", "nsq client cert key")
caCertPath := flag.String("ca-cert", "", "ca cert path")
_ = nsqHost
_ = nsqClientCert
_ = nsqClientCertKey
_ = caCertPath
listen := flag.String("listen", ":8899", "interface/ port to listen on")
flag.Parse()
http.Handle("/metrics", promhttp.Handler())
go func() {
log.Fatal(http.ListenAndServe(*listen, nil))
}()
{
vaultConfig := vaultapi.DefaultConfig()
vaultConfig.Address = *vaultHost
client, err := vaultapi.NewClient(vaultConfig)
if err != nil {
panic(err)
}
client.SetToken(*token)
c := client.Logical()
{
secret, err := c.Read("secret/amazon")
if err != nil {
panic(err)
}
username = fmt.Sprintf("%[email protected]", secret.Data["login"].(string))
password = secret.Data["password"].(string)
}
}
if (username == "" || password == "") && config == nil {
flag.PrintDefaults()
os.Exit(1)
}
opts := []chime.ClientOption{
chime.WithIDGenerator(NewID),
chime.WithLogger(os.Stderr),
}
client, err := chime.New(opts...)
if err != nil {
log.Fatalf(err.Error())
}
if err := client.StartSession(nil, username, password); err != nil {
os.Remove(*config)
log.Fatalf("failed to start session: %v", err)
}
log.Printf("client running")
ctx := context.Background()
channels := []string{}
go func() {
if err := client.PeriodicRefresh(); err != nil {
os.Remove(*config)
log.Fatalf("failed to refresh token %s", err.Error())
os.Exit(1)
}
}()
rooms, err := client.ListRooms(context.Background(), nil)
if err != nil {
log.Fatal(err.Error())
}
cmds := []plugins.Command{
&autogrant.Command{[]string{"golang", "rust", "cdk builders"}},
}
for _, room := range rooms.Rooms {
channels = append(channels, *room.Channel)
}
subscriptions, err := client.Subscribe(ctx, &chime.SubscribeInput{
Subscriptions: channels,
})
if err != nil {
log.Fatalf("websocket connection failed %s", err.Error())
}
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGTERM)
signal.Notify(c, syscall.SIGINT)
signal.Notify(c, os.Interrupt)
go func() {
<-c
log.Printf("[chime] shutdown requested")
client.Close()
for _, cmd := range cmds {
v, ok := cmd.(plugins.Stoppable)
if !ok {
continue
}
<-v.Stop()
}
os.Exit(0)
}()
go func() {
<-subscriptions.Context.Done()
log.Printf("Shutdown complete")
os.Exit(1)
}()
for msg := range subscriptions.Events {
receivedChimeEventCount.Add(1.0)
switch msg.Data.Klass {
case "ConversationMessage":
{
processedChimeEventCount.Add(1.0)
evt := msg.Data.ParsedRecord.(*chime.WebsocketConversationMessageEvent)
for _, cmd := range cmds {
if cmd.Applies(evt.Content, evt.Sender) {
cmd.Execute(client, nil, *msg, evt.Content)
}
}
}
case "RoomMessage":
{
processedChimeEventCount.Add(1.0)
evt := msg.Data.ParsedRecord.(*chime.WebsocketRoomMessageEvent)
for _, cmd := range cmds {
if cmd.Applies(evt.Content, evt.Sender) {
cmd.Execute(client, nil, *msg, evt.Content)
}
}
}
default:
{
}
}
}
if err := client.Close(); err != nil {
log.Fatalf(err.Error())
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment