Database Connections

The concept

sql.Open does not return a database connection but *DB: a database connection pool. When a database operation is about to run (e.g. query) an available connection is taken from the pool, which should be returned to the pool as soon as the operation completes.

Remind that a database connection will be opened only when first required to perform a database operation such as a query. sql.Open doesn't even test database connectivity: wrong database credentials will trigger an error at the first database operation execution time.

Looking for a rule of thumb, the context variant of database/sql interface (e.g. QueryContext()) should always be used and provided with the appropriate Context.

From the official Go documentation "Package context defines the Context type, which carries deadlines, cancelation signals, and other request-scoped values across API boundaries and between processes.". At a database level when the context is canceled, a transaction will be rolled back if not committed, a Rows (from QueryContext) will be closed and any resources will be returned.

package main

import (
    "context"
    "database/sql"
    "fmt"
    "log"
    "time"

    _ "github.com/go-sql-driver/mysql"
)

type program struct {
    base context.Context
    cancel func()
    db *sql.DB
}

func main() {
    db, err := sql.Open("mysql", "user:@/cxdb")
    if err != nil {
        log.Fatal(err)
    }
    p := &program{db: db}
    p.base, p.cancel = context.WithCancel(context.Background())

    // Wait for program termination request, cancel base context on request.
    go func() {
        osSignal := // ...
        select {
        case <-p.base.Done():
        case <-osSignal:
            p.cancel()
        }
        // Optionally wait for N milliseconds before calling os.Exit.
    }()

    err =  p.doOperation()
    if err != nil {
        log.Fatal(err)
    }
}

func (p *program) doOperation() error {
    ctx, cancel := context.WithTimeout(p.base, 10 * time.Second)
    defer cancel()

    var version string
    err := p.db.QueryRowContext(ctx, "SELECT VERSION();").Scan(&version)
    if err != nil {
        return fmt.Errorf("unable to read version %v", err)
    }
    fmt.Println("Connected to:", version)
}

Connection string protection

To keep your connection strings secure, it's always a good practice to put the authentication details on a separated configuration file outside public access.

Instead of placing your configuration file at /home/public_html/, consider /home/private/configDB.xml (should be placed in a protected area)

<connectionDB>
  <serverDB>localhost</serverDB>
  <userDB>f00</userDB>
  <passDB>f00?bar#ItsP0ssible</passDB>
</connectionDB>

Then you can call the configDB.xml file on your Go file:

configFile, _ := os.Open("../private/configDB.xml")

After reading the file, make the database connection:

db, _ := sql.Open(serverDB, userDB, passDB)

Of course, if the attacker has root access, he could see the file. Which brings us to the most cautious thing you can do - encrypt the file.

Database Credentials

You should use different credentials for every trust distinction and level:

  • User
  • Read-only user
  • Guest
  • Admin

That way if a connection is being made for a read-only user, they could never mess up with your database information because the user actually can only read the data.

results matching ""

    No results matching ""