1package influxdb 2 3import ( 4 "context" 5 "strings" 6 "time" 7 8 multierror "github.com/hashicorp/go-multierror" 9 "github.com/hashicorp/vault/api" 10 "github.com/hashicorp/vault/sdk/database/dbplugin" 11 "github.com/hashicorp/vault/sdk/database/helper/credsutil" 12 "github.com/hashicorp/vault/sdk/database/helper/dbutil" 13 "github.com/hashicorp/vault/sdk/helper/strutil" 14 influx "github.com/influxdata/influxdb/client/v2" 15) 16 17const ( 18 defaultUserCreationIFQL = `CREATE USER "{{username}}" WITH PASSWORD '{{password}}';` 19 defaultUserDeletionIFQL = `DROP USER "{{username}}";` 20 defaultRootCredentialRotationIFQL = `SET PASSWORD FOR "{{username}}" = '{{password}}';` 21 influxdbTypeName = "influxdb" 22) 23 24var _ dbplugin.Database = &Influxdb{} 25 26// Influxdb is an implementation of Database interface 27type Influxdb struct { 28 *influxdbConnectionProducer 29 credsutil.CredentialsProducer 30} 31 32// New returns a new Cassandra instance 33func New() (interface{}, error) { 34 db := new() 35 dbType := dbplugin.NewDatabaseErrorSanitizerMiddleware(db, db.secretValues) 36 37 return dbType, nil 38} 39 40func new() *Influxdb { 41 connProducer := &influxdbConnectionProducer{} 42 connProducer.Type = influxdbTypeName 43 44 credsProducer := &credsutil.SQLCredentialsProducer{ 45 DisplayNameLen: 15, 46 RoleNameLen: 15, 47 UsernameLen: 100, 48 Separator: "_", 49 } 50 51 return &Influxdb{ 52 influxdbConnectionProducer: connProducer, 53 CredentialsProducer: credsProducer, 54 } 55} 56 57// Run instantiates a Influxdb object, and runs the RPC server for the plugin 58func Run(apiTLSConfig *api.TLSConfig) error { 59 dbType, err := New() 60 if err != nil { 61 return err 62 } 63 64 dbplugin.Serve(dbType.(dbplugin.Database), api.VaultPluginTLSProvider(apiTLSConfig)) 65 66 return nil 67} 68 69// Type returns the TypeName for this backend 70func (i *Influxdb) Type() (string, error) { 71 return influxdbTypeName, nil 72} 73 74func (i *Influxdb) getConnection(ctx context.Context) (influx.Client, error) { 75 cli, err := i.Connection(ctx) 76 if err != nil { 77 return nil, err 78 } 79 80 return cli.(influx.Client), nil 81} 82 83// CreateUser generates the username/password on the underlying Influxdb secret backend as instructed by 84// the CreationStatement provided. 85func (i *Influxdb) CreateUser(ctx context.Context, statements dbplugin.Statements, usernameConfig dbplugin.UsernameConfig, expiration time.Time) (username string, password string, err error) { 86 // Grab the lock 87 i.Lock() 88 defer i.Unlock() 89 90 statements = dbutil.StatementCompatibilityHelper(statements) 91 92 // Get the connection 93 cli, err := i.getConnection(ctx) 94 if err != nil { 95 return "", "", err 96 } 97 98 creationIFQL := statements.Creation 99 if len(creationIFQL) == 0 { 100 creationIFQL = []string{defaultUserCreationIFQL} 101 } 102 103 rollbackIFQL := statements.Rollback 104 if len(rollbackIFQL) == 0 { 105 rollbackIFQL = []string{defaultUserDeletionIFQL} 106 } 107 108 username, err = i.GenerateUsername(usernameConfig) 109 username = strings.Replace(username, "-", "_", -1) 110 if err != nil { 111 return "", "", err 112 } 113 username = strings.ToLower(username) 114 password, err = i.GeneratePassword() 115 if err != nil { 116 return "", "", err 117 } 118 119 // Execute each query 120 for _, stmt := range creationIFQL { 121 for _, query := range strutil.ParseArbitraryStringSlice(stmt, ";") { 122 query = strings.TrimSpace(query) 123 if len(query) == 0 { 124 continue 125 } 126 127 q := influx.NewQuery(dbutil.QueryHelper(query, map[string]string{ 128 "username": username, 129 "password": password, 130 }), "", "") 131 response, err := cli.Query(q) 132 if err != nil && response.Error() != nil { 133 for _, stmt := range rollbackIFQL { 134 for _, query := range strutil.ParseArbitraryStringSlice(stmt, ";") { 135 query = strings.TrimSpace(query) 136 137 if len(query) == 0 { 138 continue 139 } 140 q := influx.NewQuery(dbutil.QueryHelper(query, map[string]string{ 141 "username": username, 142 }), "", "") 143 144 response, err := cli.Query(q) 145 if err != nil && response.Error() != nil { 146 return "", "", err 147 } 148 } 149 } 150 return "", "", err 151 } 152 } 153 } 154 return username, password, nil 155} 156 157// RenewUser is not supported on Influxdb, so this is a no-op. 158func (i *Influxdb) RenewUser(ctx context.Context, statements dbplugin.Statements, username string, expiration time.Time) error { 159 // NOOP 160 return nil 161} 162 163// RevokeUser attempts to drop the specified user. 164func (i *Influxdb) RevokeUser(ctx context.Context, statements dbplugin.Statements, username string) error { 165 // Grab the lock 166 i.Lock() 167 defer i.Unlock() 168 169 statements = dbutil.StatementCompatibilityHelper(statements) 170 171 cli, err := i.getConnection(ctx) 172 if err != nil { 173 return err 174 } 175 176 revocationIFQL := statements.Revocation 177 if len(revocationIFQL) == 0 { 178 revocationIFQL = []string{defaultUserDeletionIFQL} 179 } 180 181 var result *multierror.Error 182 for _, stmt := range revocationIFQL { 183 for _, query := range strutil.ParseArbitraryStringSlice(stmt, ";") { 184 query = strings.TrimSpace(query) 185 if len(query) == 0 { 186 continue 187 } 188 q := influx.NewQuery(dbutil.QueryHelper(query, map[string]string{ 189 "username": username, 190 }), "", "") 191 response, err := cli.Query(q) 192 result = multierror.Append(result, err) 193 result = multierror.Append(result, response.Error()) 194 } 195 } 196 return result.ErrorOrNil() 197} 198 199// RotateRootCredentials is useful when we try to change root credential 200func (i *Influxdb) RotateRootCredentials(ctx context.Context, statements []string) (map[string]interface{}, error) { 201 // Grab the lock 202 i.Lock() 203 defer i.Unlock() 204 205 cli, err := i.getConnection(ctx) 206 if err != nil { 207 return nil, err 208 } 209 210 rotateIFQL := statements 211 if len(rotateIFQL) == 0 { 212 rotateIFQL = []string{defaultRootCredentialRotationIFQL} 213 } 214 215 password, err := i.GeneratePassword() 216 if err != nil { 217 return nil, err 218 } 219 220 var result *multierror.Error 221 for _, stmt := range rotateIFQL { 222 for _, query := range strutil.ParseArbitraryStringSlice(stmt, ";") { 223 query = strings.TrimSpace(query) 224 if len(query) == 0 { 225 continue 226 } 227 q := influx.NewQuery(dbutil.QueryHelper(query, map[string]string{ 228 "username": i.Username, 229 "password": password, 230 }), "", "") 231 response, err := cli.Query(q) 232 result = multierror.Append(result, err) 233 result = multierror.Append(result, response.Error()) 234 } 235 } 236 237 err = result.ErrorOrNil() 238 if err != nil { 239 return nil, err 240 } 241 242 i.rawConfig["password"] = password 243 return i.rawConfig, nil 244} 245