DataARTIGO

PostgreSQL via SSL com Golang

No seguinte artigo, Cesar Gimenes apresenta um passo a passo ensinando a configurar PostgreSQL para conexão usando SSL via Go.

PostgreSQL via SSL com Golang
Imagem: Cesar Gimenes

Configurando PostgreSQL com SSL

Criando o certificado

Primeiro vamos criar um certificado de testes. Claro que quando for usar em produção você deve usar um certificado gerado por uma entidade certificadora reconhecida.

Com o comando abaixo vamos criar dois arquivos server.crt e server.key que vamos usar para configurar o PostgreSQL.

csharp
openssl req -new -x509 -days 365 -nodes -text -out server.crt \
-keyout server.key -subj "/CN=example.com"

Configurando PostgreSQL

Edite o arquivo postgresql.conf para ativar o uso da chave SSL.

csharp
ssl = on
ssl_cert_file = 'server.crt'
ssl_key_file = 'server.key'

Reinicie o serviço e teste a conexão com o seguinte comando:

bash
psql "sslmode=require"

Na string de conexão use slmode=verify-full no lugar de sslmode=disable. Como seu certificado não é assinado por uma entidade certificadora, você vai precisar marcar ele como confiável no seu sistema operacional.

Veja a documentação do PostgreSQL para ver mais exemplos de configuração.

Exemplo usando Go

No exemplo abaixo usei a função Connect do pacote sqlx que vai abrir o banco e em seguida tentar fazer ping no servidor.

Dessa forma ele vai imediatamente retornar um erro, caso não consiga estabelecer uma conexão completa com o banco.

csharp
dbsource := "postgres://postgres:password@example.com/testdb?sslmode=verify-full"
conn, err := sqlx.Connect("postgres", dbsource)
if err != nil {
	fmt.Printf("error open db: %v\n", err)
	return
}

Bônus

As principais operações com PostgreSQL usando Go

Tabela simples para executar os exemplos:

sql
CREATE TABLE public.clients
(
    id integer NOT NULL DEFAULT nextval('clientes_id_seq'::regclass),
    name character varying(200),
    address text,
    CONSTRAINT clientes_pkey PRIMARY KEY (id)
);
javascript
package main

import (
	"fmt"

	"github.com/jmoiron/sqlx"
	_ "github.com/lib/pq"
)

func open(dbsource string) (db *sqlx.DB, err error) {
	db, err = sqlx.Open("postgres", dbsource)
	if err != nil {
		err = fmt.Errorf("error open db: %v", err)
		return
	}
	err = db.Ping()
	if err != nil {
		err = fmt.Errorf("error ping db: %v", err)
	}
	return
}

func main() {
	/*************************
	 ** Abre banco de dados **
	 *************************/

	//dbsource := "postgres://postgres:password@example.com/testdb?sslmode=verify-full"
	dbsource := "postgres://postgres@localhost/testdb?sslmode=disable"
	db, err := open(dbsource)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(db.DriverName())

	/************
	 ** Insert **
	 ************/

	/*
	   Na maioria dos casos você pode usar tanto
	   Exec como Query, a diferença esta no retorno,
	   Query é mais adequado para quando você quer
	   ler linhas de retorno.
	*/

	// Insert simples
	// -=-=-=-=-=-=-=

	sql := `INSERT INTO "clients" ("name","address") VALUES ($1,$2)`

	_, err = db.Exec(sql,
		"Tyrell Corporation",
		"TC Earth Headquarters")
	if err != nil {
		fmt.Println(err)
		return
	}

	// named insert
	// -=-=-=-=-=-=
	type client struct {
		Name    string `json:"name" db:"name"`
		Address string `json:"address" db:"address"`
	}

	namedSQL := `INSERT INTO "clients" ("name","address") VALUES (:name,:address)`

	_, err = db.NamedExec(namedSQL,
		client{
			Name:    "Cyberdyne Systems",
			Address: "2144 Kramer St",
		})
	if err != nil {
		fmt.Println(err)
		return
	}

	// named insert e retorna ID
	// -=-=-=-=-=-=-=-=-=-=-=-=-
	id := 0
	rows, err := db.NamedQuery(`INSERT INTO "clients" ("name","address") VALUES (:name,:address) RETURNING id`,
		client{
			Name:    "Umbrella Corporation",
			Address: "545 S Birdneck RD STE 202B Virginia Beach, VA 23451",
		})
	if err != nil {
		fmt.Println(err)
		return
	}
	for rows.Next() {
		err = rows.Scan(&id)
		if err != nil {
			fmt.Println(err)
			return
		}
		fmt.Println("id", id)
	}
	err = rows.Close()
	if err != nil {
		fmt.Println(err)
		return
	}

	// insert com transação
	// -=-=-=-=-=-=-=-=-=-=
	tx, err := db.Begin()
	if err != nil {
		fmt.Println(err)
		return
	}

	_, err = tx.Exec(sql,
		"OCP Omni Consumer Products",
		"Delta City (formerly Detroit)")
	if err != nil {
		fmt.Println(err)
		return
	}

	_, err = tx.Exec(sql,
		"Weyland-Yutani Corporation",
		"Weyland-Yutani Corporation HQ, Tokyo")
	if err != nil {
		fmt.Println(err)
		return
	}

	_, err = tx.Exec(sql,
		"GeneCo",
		"401 N. Boonville Avenue Springfield")
	if err != nil {
		fmt.Println(err)
		return
	}

	err = tx.Commit()
	if err != nil {
		fmt.Println(err)
		return
	}

	// insert usando prepare
	// -=-=-=-=-=-=-=-=-=-=-
	stmt, err := db.Prepare(sql)
	if err != nil {
		fmt.Println(err)
		return
	}

	_, err = stmt.Exec("Black Mesa", "Black Mesa, New Mexico, USA")
	if err != nil {
		fmt.Println(err)
		return
	}

	err = stmt.Close()
	if err != nil {
		fmt.Println(err)
		return
	}

	// MustExec (se der erro quebra tudo)
	// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

	db.MustExec(sql,
		"League of Industrial Nations",
		"CON-AM 27, Io, Jupter")

	db.MustExec(sql,
		"Aperture Laboratories",
		"Upper Michigan, USA")

	/************
	 ** Select **
	 ************/

	sql = `select "name", "address" from "clients" order by name`

	// Select simples
	// -=-=-=-=-=-=-=
	r, err := db.Query(sql)
	if err != nil {
		fmt.Println(err)
		return
	}

	// list := []client{}
	for r.Next() {
		c := client{}                     // nova instancia para conter o cliente
		err = r.Scan(&c.Name, &c.Address) // popula nova instancia
		if err != nil {                   // verifica se erro
			fmt.Println(err)
			return
		}
		fmt.Println("Nome....:", c.Name)
		fmt.Println("Endereço:", c.Address)
		fmt.Println("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-")
		// list = append(list, c)
	}
	err = r.Close()
	if err != nil {
		fmt.Println(err)
		return
	}

	// Select lendo item a item com StructScan
	// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
	rows, err = db.Queryx(sql)
	if err != nil {
		fmt.Println(err)
		return
	}

	// list := []client{}
	for rows.Next() {
		c := client{}             // nova instancia para conter o cliente
		err = rows.StructScan(&c) // popula nova instancia
		if err != nil {           // verifica se erro
			fmt.Println(err)
			return
		}
		fmt.Println("Nome....:", c.Name)
		fmt.Println("Endereço:", c.Address)
		fmt.Println("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-")
		// list = append(list, c)
	}
	err = rows.Close()
	if err != nil {
		fmt.Println(err)
		return
	}

	// Select lendo todos os itens de uma vez usando db.Select
	// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

	list := []client{}
	err = db.Select(&list, sql)
	if err != nil {
		fmt.Println(err)
		return
	}

	for k, v := range list {
		fmt.Println("Registro:", k+1) // não é o id :D
		fmt.Println("Nome....:", v.Name)
		fmt.Println("Endereço:", v.Address)
		fmt.Println("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-")
	}

	// Select lendo apenas um item
	// -=-=-=-=-=-=-=-=-=-=-=-=-=-

	// limit 1
	sql = `select "name", "address" from "clients" limit 1`
	c := client{}
	err = db.Get(&c, sql)
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Println("Nome....:", c.Name)
	fmt.Println("Endereço:", c.Address)
	fmt.Println("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-")

	// count
	sql = `select count(*) from "clients"`
	count := 0
	err = db.Get(&count, sql)
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Println("count:", count)
	fmt.Println("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-")

	/***********
	 ** Close **
	 ***********/

	err = db.Close()
	if err != nil {
		fmt.Println(err)
		return
	}
}

 

Cesar GimenesAutor

Trabalha com tecnologia desde a década de 90. Já atuou na área de educação e participou de projetos de mobilidade de grande volume para laboratórios farmacêuticos. Criou games tanto para PC, como para iOS. Hoje está direcionando seus esforços em plataformas de Sistemas Embarcados, IoT, microservices e cloud computing. É um entusiasta de tecnologias como Golang e Docker.

Ver perfil