1package rabbitmq 2 3import ( 4 "context" 5 "fmt" 6 "strings" 7 "sync" 8 9 cleanhttp "github.com/hashicorp/go-cleanhttp" 10 "github.com/hashicorp/vault/sdk/framework" 11 "github.com/hashicorp/vault/sdk/logical" 12 rabbithole "github.com/michaelklishin/rabbit-hole" 13) 14 15// Factory creates and configures the backend 16func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) { 17 b := Backend() 18 if err := b.Setup(ctx, conf); err != nil { 19 return nil, err 20 } 21 return b, nil 22} 23 24// Creates a new backend with all the paths and secrets belonging to it 25func Backend() *backend { 26 var b backend 27 b.Backend = &framework.Backend{ 28 Help: strings.TrimSpace(backendHelp), 29 30 PathsSpecial: &logical.Paths{ 31 SealWrapStorage: []string{ 32 "config/connection", 33 }, 34 }, 35 36 Paths: []*framework.Path{ 37 pathConfigConnection(&b), 38 pathConfigLease(&b), 39 pathListRoles(&b), 40 pathCreds(&b), 41 pathRoles(&b), 42 }, 43 44 Secrets: []*framework.Secret{ 45 secretCreds(&b), 46 }, 47 48 Clean: b.resetClient, 49 Invalidate: b.invalidate, 50 BackendType: logical.TypeLogical, 51 } 52 53 return &b 54} 55 56type backend struct { 57 *framework.Backend 58 59 client *rabbithole.Client 60 lock sync.RWMutex 61} 62 63// DB returns the database connection. 64func (b *backend) Client(ctx context.Context, s logical.Storage) (*rabbithole.Client, error) { 65 b.lock.RLock() 66 67 // If we already have a client, return it 68 if b.client != nil { 69 b.lock.RUnlock() 70 return b.client, nil 71 } 72 73 b.lock.RUnlock() 74 75 // Otherwise, attempt to make connection 76 entry, err := s.Get(ctx, "config/connection") 77 if err != nil { 78 return nil, err 79 } 80 if entry == nil { 81 return nil, fmt.Errorf("configure the client connection with config/connection first") 82 } 83 84 var connConfig connectionConfig 85 if err := entry.DecodeJSON(&connConfig); err != nil { 86 return nil, err 87 } 88 89 b.lock.Lock() 90 defer b.lock.Unlock() 91 92 // If the client was created during the lock switch, return it 93 if b.client != nil { 94 return b.client, nil 95 } 96 97 b.client, err = rabbithole.NewClient(connConfig.URI, connConfig.Username, connConfig.Password) 98 if err != nil { 99 return nil, err 100 } 101 // Use a default pooled transport so there would be no leaked file descriptors 102 b.client.SetTransport(cleanhttp.DefaultPooledTransport()) 103 104 return b.client, nil 105} 106 107// resetClient forces a connection next time Client() is called. 108func (b *backend) resetClient(_ context.Context) { 109 b.lock.Lock() 110 defer b.lock.Unlock() 111 112 b.client = nil 113} 114 115func (b *backend) invalidate(ctx context.Context, key string) { 116 switch key { 117 case "config/connection": 118 b.resetClient(ctx) 119 } 120} 121 122// Lease returns the lease information 123func (b *backend) Lease(ctx context.Context, s logical.Storage) (*configLease, error) { 124 entry, err := s.Get(ctx, "config/lease") 125 if err != nil { 126 return nil, err 127 } 128 if entry == nil { 129 return nil, nil 130 } 131 132 var result configLease 133 if err := entry.DecodeJSON(&result); err != nil { 134 return nil, err 135 } 136 137 return &result, nil 138} 139 140const backendHelp = ` 141The RabbitMQ backend dynamically generates RabbitMQ users. 142 143After mounting this backend, configure it using the endpoints within 144the "config/" path. 145` 146