Golang에서 데이터베이스 자격 증명 순환 처리

18673 단어 beginnersdatabasego


소개



많은 분들이 데이터베이스 자격 증명 순환에 대해 들어 보셨을 것입니다. 데이터베이스 보안을 위한 일반적인 솔루션입니다. Hashicorp Vault, AWS Secret Manager 등과 같은 많은 공급자가 이 솔루션을 구현했습니다. 데이터베이스 자격 증명을 자동으로 교체하면 아무도 데이터베이스에 연결하는 방법을 알 수 없습니다. 실제로 안전하지만 또 다른 문제가 발생합니다. 데이터베이스 자격 증명을 교체하면 애플리케이션이 일정 시간이 지나면 데이터베이스에 연결하지 못할 수 있습니다. 특히 데이터베이스 최대 연결 수명을 선언할 때. 이 기사에서는 golang에서 프로그래밍 방식으로 데이터베이스 자격 증명을 회전하는 방법을 배웁니다.

커스텀 드라이버



이 로테이터를 일반 솔루션으로 구현하려면 기본 데이터베이스 드라이버(예: postgres, sqlite3, mysql 등)를 보유하는 customDriver struct 및 데이터베이스 자격 증명을 가져오는 데 사용할 Fetcher interface를 생성해야 합니다.

type Fetcher interface {
    Fetch() (string, error)
}

type FetcherFunc func() (string, error)
func (f FetcherFunc) Fetch() (string, error) {
    return f()
}

// customDriver implements the `sql.Driver` interface.
type customDriver struct {
    // base is the base database driver
    base      driver.Driver

    // fetcherFn is the function that will be used to fetch the database credential
    fetcherFn FetcherFunc
}

func (d *customDriver) Open(_ string) (driver.Conn, error) {
    // fetch the database credential
    dsn, err := d.fetcherFn()
    if err != nil {
        return nil, err
    }

    // open the database connection using the fetched credential and base driver
    return d.base.Open(dsn)
}


드라이버 등록 및 연결 열기



이제 드라이버를 사용하려면 드라이버를 등록하고 연결을 열어야 합니다.

func OpenWithRotator(name string, base driver.Driver, fetcher Fetcher) (*sql.DB, error) {
    sql.Register(name, &customDriver{
        base:      base,
        fetcherFn: fetcher.Fetch,
    })

    // you don't need to fill the dsn, it will be fetched from the fetcher.
    return sql.Open(name, "")
}


Fetcher 인터페이스 구현



간단한 함수를 사용하여 Fetcher interface를 구현해 봅시다. 데이터베이스 수명이 다할 때마다 다른 데이터베이스에 연결하려고 한다고 가정해 보겠습니다.

var counter int

func simpleFetcher() (string, error) {
    log.Println("fetcher called")

    // add your custom logic
    // e.g. fetching from vault / config / etc.

    counter++
    return fmt.Sprintf("file:foobar-%d.sqlite", counter), nil
}


로테이터로 연결 열기



열고 연결의 최대 수명을 2초로 설정합니다. simpleFetcher는 2초마다 호출됩니다.

If your database credential is rotated faster than your connection lifetime, Golang still can use the old connection.



// you can adjust the `&sqlite3.SQLiteDriver{}` accordingly (e.g. &pq.Driver{}, etc.)
db, err := OpenWithRotator("foobar", &sqlite3.SQLiteDriver{}, FetcherFunc(simpleFetcher))
if err != nil {
    log.Fatal(err)
}
defer db.Close()
db.SetConnMaxLifetime(2 * time.Second)


연결 테스트



연결이 작동하는지 간단히 테스트하려면 매초 Ping 메서드를 사용할 수 있습니다.

for range time.Tick(time.Second) {
    if err := db.Ping(); err != nil {
        log.Fatal(err)
    }
}


이제 프로그램을 실행하면 다음과 같은 출력이 표시됩니다.

$ go run .
2022/08/29 16:56:16 fetcher called
2022/08/29 16:56:19 fetcher called
2022/08/29 16:56:22 fetcher called
2022/08/29 16:56:25 fetcher called
2022/08/29 16:56:28 fetcher called
2022/08/29 16:56:31 fetcher called


다음은 sqlite3 데이터베이스 파일입니다.

$ ls | grep foobar-
foobar-1.sqlite
foobar-2.sqlite
foobar-3.sqlite
foobar-4.sqlite
foobar-5.sqlite
foobar-6.sqlite


전체 코드는 다음과 같습니다.

package main

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

    "github.com/mattn/go-sqlite3"
)

type Fetcher interface {
    Fetch() (string, error)
}

type FetcherFunc func() (string, error)
func (f FetcherFunc) Fetch() (string, error) {
    return f()
}

// customDriver implements the `sql.Driver` interface.
type customDriver struct {
    // base is the base database driver
    base      driver.Driver

    // fetcherFn is the function that will be used to fetch the database credential
    fetcherFn FetcherFunc
}

func (d *customDriver) Open(_ string) (driver.Conn, error) {
    // fetch the database credential
    dsn, err := d.fetcherFn()
    if err != nil {
        return nil, err
    }

    // open the database connection using the fetched credential and base driver
    return d.base.Open(dsn)
}

func OpenWithRotator(name string, base driver.Driver, fetcher Fetcher) (*sql.DB, error) {
    sql.Register(name, &customDriver{
        base:      base,
        fetcherFn: fetcher.Fetch,
    })

    // you don't need to fill the dsn, it will be fetched from the fetcher.
    return sql.Open(name, "")
}

var counter int
func simpleFetcher() (string, error) {
    log.Println("fetcher called")

    // add your custom logic
    // e.g. fetching from vault / config / etc.

    counter++
    return fmt.Sprintf("file:foobar-%d.sqlite", counter), nil
}

func main() {
    // you can adjust the `&sqlite3.SQLiteDriver{}` accordingly (e.g. &pq.Driver{}, etc.)
    db, err := OpenWithRotator("foobar", &sqlite3.SQLiteDriver{}, FetcherFunc(simpleFetcher))
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()
    db.SetConnMaxLifetime(2 * time.Second)

    for range time.Tick(time.Second) {
        if err := db.Ping(); err != nil {
            log.Fatal(err)
        }
    }
}


결론



이것은 데이터베이스 로테이터의 간단한 구현입니다. 사용 사례에 따라 페처를 구현하는 것이 좋습니다.

읽어 주셔서 감사합니다!

좋은 웹페이지 즐겨찾기