포스트

PostgreSQL 15 설정 (Ubuntu)

만능 데이터베이스인 PostgreSQL 15 설정의 작업 과정을 기록합니다.

PostgreSQL 15 설정 (Ubuntu)

1. PostgreSQL 15

1) 특이사항

3) 참고자료

2. 우분투에 설치하기

1) 리포지토리 등록 및 APT 설치

설치 로그에 PG_HOME 과 PG_DATA 위치가 나온다. 기록해두자.

1
2
3
4
5
6
7
8
$ sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
$ wget -qO- https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo tee /etc/apt/trusted.gpg.d/pgdg.asc &>/dev/null

$ sudo apt update
$ sudo apt install postgresql postgresql-client -y

$ sudo systemctl status postgresql
$ psql --version

콘솔창을 열 때마다 서비스 상태를 출력하도록 하자. (.zshrc)

1
2
echo ""
systemctl list-units --state=running --no-pager | grep -E 'docker|postgres' | grep -E '.+\.service' | awk '{print $1"\t"$2,$3,$4}' | sort -nk1

차후 관리를 위해 (잊어먹을 수 있으니) 환경변수에 등록한다.

  • PG_HOME /usr/lib/postgresql/15
  • PG_DATA /var/lib/postgresql/15/main

계정 보호를 위해 postgres 암호를 변경한다. (데이터 랜섬웨어)

1
2
3
4
5
$ sudo -u postgres psql
postgres=#

postgres=# ALTER USER postgres PASSWORD 'demoPassword';
postgres=# \q

2) postgres.conf 및 pg_hba.conf 설정

PG_DATA 위치에 있을줄 알았는데 없다. (당황)

postgresql config 파일 위치 찾는 법 (stackoverflow)

  • 우분투에 postgres 계정이 생성되어 있다.
  • 소유권이 postgres 에게 있기 때문에 sudo -u postgres 를 사용하자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ sudo -u postgres psql -c 'SHOW config_file'
               config_file               
-----------------------------------------
 /etc/postgresql/15/main/postgresql.conf

$ sudo -u postgres vi /etc/postgresql/15/main/postgresql.conf
# 추가
# listen_addresses = '*'

$ sudo -u postgres vi /etc/postgresql/15/main/pg_hba.conf
# 변경
# IPv4 local connection:
# host all all 192.168.0.0/24 scram-sha-256

# 방화벽
$ sudo ufw allow 5432/tcp
$ sudo ufw status numbered

# 재시작
$ sudo systemctl restart postgresql
$ sudo systemctl status postgresql

# 패스워드 접속 테스트
$ psql -h 192.168.0.x -U postgres

3) 사용자 및 데이터베이스 생성

1
2
3
4
5
6
$ sudo -u postgres createuser <username> --createdb --no-superuser --no-createrole
$ sudo -u postgres createdb <dbname> --owner <username> --encoding='utf-8' --locale=en_US.utf-8 --template=template0

$ sudo -u postgres psql
psql=# ALTER USER `username` WITH ENCRYPTED PASSWORD 'p@ssw0rd';
psql=# GRANT ALL PRIVILEGES ON DATABASE `dbname` TO `username`;

template1 대신 template0 을 복사하는 일반적인 다른 이유는, template1 의 복사는 동일한 설정을 사용해야 하지만 template0 을 복사하는 경우에는 새 인코딩 및 로케일(locale) 설정을 지정할 수 있기 때문이다. template1 은 인코딩 또는 로케일(locale)에 관한 데이터를 포함하지만 template0 은 그렇지 않다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
-- 로그인 계정 생성 (상속 금지, 로그인)
CREATE USER `username`
  NOINHERIT LOGIN 
  WITH ENCRYPTED PASSWORD 'p@ssw0rd';

-- 데이터베이스 생성 (template0)
CREATE DATABASE `dbname`
  WITH OWNER `username`
  ENCODING 'utf-8' 
  LC_COLLATE = 'C.utf8'
  LC_CTYPE = 'C.utf8'
  TEMPLATE template0
  IS_TEMPLATE = False;

-- 데이터베이스 접속을 포함한 모든 권한 제거 (dbo 는 가능)
--   + PUBLIC(대문자) : 모든 사용자
--   + public 스키마에 대한 PUBLIC 사용자 권한도 없어진다.
REVOKE ALL ON DATABASE `dbname` FROM PUBLIC;

/*
-- public 스키마 접근 권한 제거 (dbo 는 가능)
--   + PUBLIC(대문자) : 모든 사용자
REVOKE ALL PRIVILEGES ON SCHEMA public FROM PUBLIC;
*/

4) Python 테스트 (psycopg3)

pyenv 및 poetry 업그레이드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 설치 가능한 3.x 버전들
$ pyenv install -l | grep -e '^\s*3\.'

# 3.10 최신 버전 설치
$ pyenv install 3.10 
$ pyenv global 3.10

# pip 및 pipx 업그레이드
$ pip install --upgrade pip
$ python -m pip install --user -U pipx

# poetry 업그레이드 (최신 1.3.2)
# pipx upgrade poetry
# poetry --version

프로젝트 생성 및 psycopg3 설치

1
2
3
4
5
6
7
8
9
10
$ poetry new hello-pg --name app
$ cd hello-pg
# 또는
$ poetry init --name app --python=3.10 -q

$ poetry env use 3.10
$ poetry install
$ poetry env info

$ poetry add psycopg

테스트 Python 코드

app/main.py 파일 생성 후 코드 작성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# Note: the module name is psycopg, not psycopg3
import psycopg

# Connect to an existing database
with psycopg.connect("host=HOST dbname=DB user=USER password=PASSWORD") as conn:

    # Open a cursor to perform database operations
    with conn.cursor() as cur:

        # Execute a command: this creates a new table
        cur.execute("""
            CREATE TABLE if not exists test (
                id serial PRIMARY KEY,
                num integer,
                data text)
            """)

        # Pass data to fill a query placeholders and let Psycopg perform
        # the correct conversion (no SQL injections!)
        cur.execute(
            "INSERT INTO test (num, data) VALUES (%s, %s)",
            (100, "abc'def"))

        # Query the database and obtain data as Python objects.
        cur.execute("SELECT * FROM test")
        cur.fetchone()
        # will return (1, 100, "abc'def")

        # You can use `cur.fetchmany()`, `cur.fetchall()` to return a list
        # of several records, or even iterate on the cursor
        for record in cur:
            print(record)

        # Make the changes to the database persistent
        conn.commit()

실행 및 확인

1
2
3
4
5
6
7
8
9
10
$ poetry shell
$ python app/main.py
(1, 100, "abc'def")

$ psql -h HOST -U USER -d DB -c "select * from test" -W
암호: 
 id | num |  data   
----+-----+---------
  1 | 100 | abc'def
(1개 행)

3. PostgREST Server 설치

1) PostgREST v10.1.2

  1. PostgREST 리눅스 바이너리 다운로드
  2. 압축해제 => postgrest 실행 파일
  3. /usr/local/bin 에 link 등록
1
2
3
$ wget https://github.com/PostgREST/postgrest/releases/download/v10.1.2/postgrest-v10.1.2-linux-static-x64.tar.xz
$ tar xJf postgrest-v10.1.2-linux-static-x64.tar.xz
$ sudo ln -s $HOME/Servers/postgrest /usr/local/bin/postgrest

2) PostgREST Tutorial ♯0

  1. tutorial 데이터베이스 생성
  2. api 스키마 생성
    • api.todos 테이블 생성 및 샘플 데이터 저장
  3. web_anon 역활 생성 (nologin)
    • api 스키마에 대한 사용과 api.todos 테이블에 대한 select 권한 부여
  4. authenticator 역활 생성 (로그인 패스워드)
    • web_anon 권한을 authenticator 에 부여
  5. tutorial.conf 파일 생성
  6. postgrest 실행
  7. REST API 에 대해 GET 조회
    • POST 는 권한 없음
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
$ sudo -u postgres createdb tutorial --encoding='utf-8' --locale=en_US.utf-8 --template=template0

$ sudo -u postgres psql -d tutorial
psql=# create schema api;
psql=# create table api.todos (
  id serial primary key,
  done boolean not null default false,
  task text not null,
  due timestamptz
);
psql=# insert into api.todos (task) values
  ('finish tutorial 0'), ('pat self on back');

    -- 권한 설정용 계정 생성
psql=# create role web_anon nologin;
psql=# grant usage on schema api to web_anon;
psql=# grant select on api.todos to web_anon;

    -- 로그인 전용 계정 생성
psql=# create role authenticator noinherit login with password 'p@ssw0rd';
    -- 데이터베이스 connect 권한 부여 (REVOKE ALL 데이터베이스 한 경우)
psql=# GRANT CONNECT ON DATABASE tutorial TO authenticator;

    -- 권한 상속: web_anon => authenticator
psql=# grant web_anon to authenticator;

psql=# \q

$ cat <<EOF > tutorial.conf
db-uri = "postgres://authenticator:mysecretpassword@localhost:5432/postgres"
db-schemas = "api"
db-anon-role = "web_anon"
EOF

$ ./postgrest tutorial.conf
08/Feb/2023:15:53:02 +0900: Attempting to connect to the database...
08/Feb/2023:15:53:02 +0900: Connection successful
08/Feb/2023:15:53:02 +0900: Listening on port 3000
08/Feb/2023:15:53:02 +0900: Config reloaded
08/Feb/2023:15:53:02 +0900: Listening for notifications on the pgrst channel
08/Feb/2023:15:53:02 +0900: Schema cache loaded

$ curl http://localhost:3000/todos
[{"id":1,"done":false,"task":"finish tutorial 0","due":null}, 
 {"id":2,"done":false,"task":"pat self on back","due":null}]% 

$ curl http://localhost:3000/todos -X POST \
     -H "Content-Type: application/json" \
     -d '{"task": "do bad thing"}'  
{"code":"42501","details":null,"hint":null,"message":"todos 테이블에 대한 접근 권한 없음"}%      

3) PostgREST Tutorial ♯1

  1. todo_user 역활 생성 (nologin)
    • api 스키마에 대한 사용 권한 부여
    • api.todos 테이블에 대한 모든 권한 부여
    • api.todos_id_seq 시퀀스에 대한 사용과 select 권한 부여
  2. todo_user 권한을 authenticator 에 부여
1
2
3
4
5
6
7
8
9
10
-- 권한 설정용 role 생성
create role todo_user nologin;
grant usage on schema api to todo_user;
grant all on api.todos to todo_user;
grant usage, select on sequence api.todos_id_seq to todo_user;

-- 로그인 계정에 권한 상속 (todo_user => authenticator)
grant todo_user to authenticator;

-- role 사용시 `set role todo_user;` 필요
  1. 길이 32바이트의 랜덤 문자열 생성 (JWT 암호로 사용)
  2. tutorial.conf 에 jwt-secret 값으로 랜덤 문자열 설정
  3. postgrest 재시작
  4. jwt.io 사이트에서 수동으로 token 생성
    • payload 에 암호화 대상 데이터를 작성
    • verify signature 의 secret 위치에 JWT 암호 설정
    • (왼쪽 텍스트박스) 생성된 token 을 복사
  5. PG_REST_TOKEN 값을 환경 변수로 설정
  6. POST 데이터 생성 테스트
    • GET 데이터 조회 OK!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ ./postgrest tutorial.conf

# Allow "tr" to process non-utf8 byte sequences
$ export LC_CTYPE=C

# read random bytes and keep only alphanumerics
$ < /dev/urandom tr -dc A-Za-z0-9 | head -c32

$ vi tutorial.conf
# 추가
jwt-secret = "<the password you made>"

$ export PG_REST_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoidG9kb191c2VyIn0.k-p_9S-fAbmGT1WXjPmgySvBtjpYJSGoD48tDzzdN-0

$ curl http://localhost:3000/todos -X POST \
     -H "Authorization: Bearer $PG_REST_TOKEN"   \
     -H "Content-Type: application/json" \
     -d '{"task": "learn how to auth"}'

$ curl http://localhost:3000/todos
[{"id":1,"done":false,"task":"finish tutorial 0","due":null}, 
 {"id":2,"done":false,"task":"pat self on back","due":null}, 
 {"id":3,"done":false,"task":"learn how to auth","due":null}]%      
  1. 폐기시간 exp 을 추가하여 JWT 토큰을 재생성 한다.
    • jwt.io Payload 에 exp 를 추가
  2. API GET 조회 실행
    • 5분 이후에 실행하면 HTTP 401 Unauthorized 오류 반환
    • JWT expired 메시지를 받게된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ sudo -u postgres psql -c "select extract(epoch from now() + '5 minutes'::interval) :: integer;" 
[sudo] bgmin 암호: 
  extract   
------------
 1675845577
(1개 행)

$ export PG_REST_TOKEN_EXP=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoidG9kb191c2VyIiwiZXhwIjoxNjc1ODQ1NTc3fQ.mY7hUreGZ3MstxdCrjY7EWh4yuTaeCJisM_bXcN1qSc

$ curl http://localhost:3000/todos \
     -H "Authorization: Bearer $PG_REST_TOKEN_EXP"
[{"id":1,"done":false,"task":"finish tutorial 0","due":null}, 
 {"id":2,"done":false,"task":"pat self on back","due":null}, 
 {"id":3,"done":false,"task":"learn how to auth","due":null}]%      
  1. auth 스키마를 만들고, web_anon 과 todo_user 에게 사용 권한 부여
  2. auth.check_token() 함수 생성
    • email 값이 disgruntled@mycompany.com 이면 예외 발생
1
2
3
4
5
6
7
8
9
10
11
12
13
14
create schema auth;
grant usage on schema auth to web_anon, todo_user;

create or replace function auth.check_token() returns void
  language plpgsql
  as $$
begin
  if current_setting('request.jwt.claims', true)::json->>'email' =
     'disgruntled@mycompany.com' then
    raise insufficient_privilege
      using hint = '아니, 우리는 당신에게 있습니다';
  end if;
end
$$;
  1. conf 에 db-pre-request 항목을 추가하고 postgrest 재시작
  2. email 항목이 추가된 JWT 토큰을 재생성
  3. email 항목을 담은 토큰으로 API PATCH 실행
    • 예외 메시지(hint) 출력
    • 다른 기능은 잘 작동함 : 폐기 토큰, PATCH 반영
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
$ vi tutorial.conf 
# 추가
db-pre-request = "auth.check_token"

$ ./postgrest tutorial.conf 

$ export PG_REST_TOKEN_EMAIL=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoidG9kb191c2VyIiwiZW1haWwiOiJkaXNncnVudGxlZEBteWNvbXBhbnkuY29tIn0.MaTNsYqXo77uww4R7ZL5AmQ0SVgDj6heWWkedgSEcFo

$ curl http://localhost:3000/todos -X PATCH \
     -H "Authorization: Bearer $PG_REST_TOKEN_EXP"    \
     -H "Content-Type: application/json"  \
     -d '{"done": true}'
{"code":"PGRST301","details":null,"hint":null,"message":"JWT expired"}%

$ curl http://localhost:3000/todos -X PATCH      \
     -H "Authorization: Bearer $PG_REST_TOKEN_EMAIL" \
     -H "Content-Type: application/json"       \
     -d '{"task": "AAAHHHH!", "done": false}'
{"code":"42501","details":null,"hint":"아니, 우리는 당신에게 있습니다","message":"insufficient_privilege"}% 

$ curl http://localhost:3000/todos -X PATCH \
     -H "Authorization: Bearer $PG_REST_TOKEN"    \
     -H "Content-Type: application/json"  \
     -d '{"done": true}'

$ curl http://localhost:3000/todos \
     -H "Authorization: Bearer $PG_REST_TOKEN"
[{"id":1,"done":true,"task":"finish tutorial 0","due":null}, 
 {"id":2,"done":true,"task":"pat self on back","due":null}, 
 {"id":3,"done":true,"task":"learn how to auth","due":null}]%  

9. Review

  • GraphQL 서버 설치까지 하려다 분량이 많아 다음 포스트로 넘긴다.
    • Hasura 서버를 사용할 계획이다.
  • PostgREST 서버 튜토리얼에서 JWT 토큰 사용법에 대해 배웠다.
    • image 데이터도 전송할 수 있더라.
    • auth 기능을 이용하면 데이터 보안도 가성비 좋게 구축할 수 있다.
  • PostgREST 서버 성능에 대해 알 수가 없어 서비스로 사용해도 될지 모르겠다.

set role <config_role>; 안해도 기본 적용시키는 환경변수 설정하기

  • alter role 을 이용하여 자동으로 적용할 환경변수들을 설정한다
  • 특정 데이터베이스에서 적용되도록 in database 구절을 사용하자
1
2
3
4
5
-- 기본 role 을 config_role 이 되도록 설정
ALTER ROLE `username` IN DATABASE `dbname` SET ROLE='<config_role>';

-- 설정 해제
ALTER ROLE `username` RESET ALL;

 
 

끝!   읽어주셔서 감사합니다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.