1package hana 2 3import ( 4 "context" 5 "database/sql" 6 "errors" 7 "fmt" 8 "strings" 9 "time" 10 11 _ "github.com/SAP/go-hdb/driver" 12 "github.com/hashicorp/vault/api" 13 "github.com/hashicorp/vault/sdk/database/dbplugin" 14 "github.com/hashicorp/vault/sdk/database/helper/connutil" 15 "github.com/hashicorp/vault/sdk/database/helper/credsutil" 16 "github.com/hashicorp/vault/sdk/database/helper/dbutil" 17 "github.com/hashicorp/vault/sdk/helper/dbtxn" 18 "github.com/hashicorp/vault/sdk/helper/strutil" 19) 20 21const ( 22 hanaTypeName = "hdb" 23) 24 25// HANA is an implementation of Database interface 26type HANA struct { 27 *connutil.SQLConnectionProducer 28 credsutil.CredentialsProducer 29} 30 31var _ dbplugin.Database = &HANA{} 32 33// New implements builtinplugins.BuiltinFactory 34func New() (interface{}, error) { 35 db := new() 36 // Wrap the plugin with middleware to sanitize errors 37 dbType := dbplugin.NewDatabaseErrorSanitizerMiddleware(db, db.SecretValues) 38 39 return dbType, nil 40} 41 42func new() *HANA { 43 connProducer := &connutil.SQLConnectionProducer{} 44 connProducer.Type = hanaTypeName 45 46 credsProducer := &credsutil.SQLCredentialsProducer{ 47 DisplayNameLen: 32, 48 RoleNameLen: 20, 49 UsernameLen: 128, 50 Separator: "_", 51 } 52 53 return &HANA{ 54 SQLConnectionProducer: connProducer, 55 CredentialsProducer: credsProducer, 56 } 57} 58 59// Run instantiates a HANA object, and runs the RPC server for the plugin 60func Run(apiTLSConfig *api.TLSConfig) error { 61 dbType, err := New() 62 if err != nil { 63 return err 64 } 65 66 dbplugin.Serve(dbType.(dbplugin.Database), api.VaultPluginTLSProvider(apiTLSConfig)) 67 68 return nil 69} 70 71// Type returns the TypeName for this backend 72func (h *HANA) Type() (string, error) { 73 return hanaTypeName, nil 74} 75 76func (h *HANA) getConnection(ctx context.Context) (*sql.DB, error) { 77 db, err := h.Connection(ctx) 78 if err != nil { 79 return nil, err 80 } 81 82 return db.(*sql.DB), nil 83} 84 85// CreateUser generates the username/password on the underlying HANA secret backend 86// as instructed by the CreationStatement provided. 87func (h *HANA) CreateUser(ctx context.Context, statements dbplugin.Statements, usernameConfig dbplugin.UsernameConfig, expiration time.Time) (username string, password string, err error) { 88 // Grab the lock 89 h.Lock() 90 defer h.Unlock() 91 92 statements = dbutil.StatementCompatibilityHelper(statements) 93 94 // Get the connection 95 db, err := h.getConnection(ctx) 96 if err != nil { 97 return "", "", err 98 } 99 100 if len(statements.Creation) == 0 { 101 return "", "", dbutil.ErrEmptyCreationStatement 102 } 103 104 // Generate username 105 username, err = h.GenerateUsername(usernameConfig) 106 if err != nil { 107 return "", "", err 108 } 109 110 // HANA does not allow hyphens in usernames, and highly prefers capital letters 111 username = strings.Replace(username, "-", "_", -1) 112 username = strings.ToUpper(username) 113 114 // Generate password 115 password, err = h.GeneratePassword() 116 if err != nil { 117 return "", "", err 118 } 119 // Most HANA configurations have password constraints 120 // Prefix with A1a to satisfy these constraints. User will be forced to change upon login 121 password = strings.Replace(password, "-", "_", -1) 122 password = "A1a" + password 123 124 // If expiration is in the role SQL, HANA will deactivate the user when time is up, 125 // regardless of whether vault is alive to revoke lease 126 expirationStr, err := h.GenerateExpiration(expiration) 127 if err != nil { 128 return "", "", err 129 } 130 131 // Start a transaction 132 tx, err := db.BeginTx(ctx, nil) 133 if err != nil { 134 return "", "", err 135 } 136 defer tx.Rollback() 137 138 // Execute each query 139 for _, stmt := range statements.Creation { 140 for _, query := range strutil.ParseArbitraryStringSlice(stmt, ";") { 141 query = strings.TrimSpace(query) 142 if len(query) == 0 { 143 continue 144 } 145 146 m := map[string]string{ 147 "name": username, 148 "password": password, 149 "expiration": expirationStr, 150 } 151 if err := dbtxn.ExecuteTxQuery(ctx, tx, m, query); err != nil { 152 return "", "", err 153 } 154 } 155 } 156 157 // Commit the transaction 158 if err := tx.Commit(); err != nil { 159 return "", "", err 160 } 161 162 return username, password, nil 163} 164 165// Renewing hana user just means altering user's valid until property 166func (h *HANA) RenewUser(ctx context.Context, statements dbplugin.Statements, username string, expiration time.Time) error { 167 statements = dbutil.StatementCompatibilityHelper(statements) 168 169 // Get connection 170 db, err := h.getConnection(ctx) 171 if err != nil { 172 return err 173 } 174 175 // Start a transaction 176 tx, err := db.BeginTx(ctx, nil) 177 if err != nil { 178 return err 179 } 180 defer tx.Rollback() 181 182 // If expiration is in the role SQL, HANA will deactivate the user when time is up, 183 // regardless of whether vault is alive to revoke lease 184 expirationStr, err := h.GenerateExpiration(expiration) 185 if err != nil { 186 return err 187 } 188 189 // Renew user's valid until property field 190 stmt, err := tx.PrepareContext(ctx, "ALTER USER "+username+" VALID UNTIL "+"'"+expirationStr+"'") 191 if err != nil { 192 return err 193 } 194 defer stmt.Close() 195 if _, err := stmt.ExecContext(ctx); err != nil { 196 return err 197 } 198 199 // Commit the transaction 200 if err := tx.Commit(); err != nil { 201 return err 202 } 203 204 return nil 205} 206 207// Revoking hana user will deactivate user and try to perform a soft drop 208func (h *HANA) RevokeUser(ctx context.Context, statements dbplugin.Statements, username string) error { 209 statements = dbutil.StatementCompatibilityHelper(statements) 210 211 // default revoke will be a soft drop on user 212 if len(statements.Revocation) == 0 { 213 return h.revokeUserDefault(ctx, username) 214 } 215 216 // Get connection 217 db, err := h.getConnection(ctx) 218 if err != nil { 219 return err 220 } 221 222 // Start a transaction 223 tx, err := db.BeginTx(ctx, nil) 224 if err != nil { 225 return err 226 } 227 defer tx.Rollback() 228 229 // Execute each query 230 for _, stmt := range statements.Revocation { 231 for _, query := range strutil.ParseArbitraryStringSlice(stmt, ";") { 232 query = strings.TrimSpace(query) 233 if len(query) == 0 { 234 continue 235 } 236 237 m := map[string]string{ 238 "name": username, 239 } 240 if err := dbtxn.ExecuteTxQuery(ctx, tx, m, query); err != nil { 241 return err 242 } 243 } 244 } 245 246 return tx.Commit() 247} 248 249func (h *HANA) revokeUserDefault(ctx context.Context, username string) error { 250 // Get connection 251 db, err := h.getConnection(ctx) 252 if err != nil { 253 return err 254 } 255 256 // Start a transaction 257 tx, err := db.BeginTx(ctx, nil) 258 if err != nil { 259 return err 260 } 261 defer tx.Rollback() 262 263 // Disable server login for user 264 disableStmt, err := tx.PrepareContext(ctx, fmt.Sprintf("ALTER USER %s DEACTIVATE USER NOW", username)) 265 if err != nil { 266 return err 267 } 268 defer disableStmt.Close() 269 if _, err := disableStmt.ExecContext(ctx); err != nil { 270 return err 271 } 272 273 // Invalidates current sessions and performs soft drop (drop if no dependencies) 274 // if hard drop is desired, custom revoke statements should be written for role 275 dropStmt, err := tx.PrepareContext(ctx, fmt.Sprintf("DROP USER %s RESTRICT", username)) 276 if err != nil { 277 return err 278 } 279 defer dropStmt.Close() 280 if _, err := dropStmt.ExecContext(ctx); err != nil { 281 return err 282 } 283 284 // Commit transaction 285 if err := tx.Commit(); err != nil { 286 return err 287 } 288 289 return nil 290} 291 292// RotateRootCredentials is not currently supported on HANA 293func (h *HANA) RotateRootCredentials(ctx context.Context, statements []string) (map[string]interface{}, error) { 294 return nil, errors.New("root credentaion rotation is not currently implemented in this database secrets engine") 295} 296