暂无图片
暂无图片
1
暂无图片
暂无图片
暂无图片

【译】CockroachDB 与 SQLAlchemy 和 MIT Kerberos

原创 Ellison 2022-06-14
644

原文作者:阿尔乔姆·埃尔维茨
原文地址:https://dzone.com/articles/cockroachdb-with-sqlalchemy-and-mit-kerberos



今天,我将演示如何轻松地集成针对 Kerberized CockroachDB 集群运行的 SQLAlchemy 应用程序。



涵盖 CockroachDB 和 Kerberos 的文章

我发现 Kerberos 的主题非常有趣,我的同事通常会向我寻求有关这个复杂主题的帮助。我绝不是 Kerberos 方面的专家,但我对它足够熟悉以至于很危险。也就是说,我已经写了多篇关于这个主题的文章,你可以在下面找到:


第 1 部分:使用 MIT Kerberos 的 CockroachDB

第 2 部分:使用 Active Directory 的 CockroachDB

第 3 部分:使用 MIT Kerberos 和 Docker Compose 的 CockroachDB

第 5 部分:通过 GSSAPI 执行 CockroachDB 表导入

今天,我将演示如何将 CockroachDB 与 MIT Kerberos 和 SqlAlchemy 结合使用。我们有很多客户使用我们来满足他们的 Python 数据库需求,您可以在我们的文档网站上查看一些选项。 

对于今天的设置,我有一个多节点 CockroachDB 集群、一个名为 的 Django 容器web、一个负载均衡器容器和一个 Kerberos KDC 容器。您可以在我的repo中找到此示例的代码。

  1. 克隆回购。


git clone https://github.com/dbist/cockroach-docker
cd cockroach-docker/cockroach-gssapi-sqlalchemy


  1. 启动应用程序。


./up.sh



Creating network "cockroach-gssapi-sqlalchemy_default" with the default driver
Creating network "cockroach-gssapi-sqlalchemy_roachnet" with the default driver
Creating volume "cockroach-gssapi-sqlalchemy_certs-roach-0" with default driver
Creating volume "cockroach-gssapi-sqlalchemy_certs-roach-1" with default driver
Creating volume "cockroach-gssapi-sqlalchemy_certs-roach-2" with default driver
Creating volume "cockroach-gssapi-sqlalchemy_keytab" with default driver
Creating volume "cockroach-gssapi-sqlalchemy_certs-client" with default driver
Creating roach-cert ... done
Creating kdc        ... done
Creating roach-0    ... done
Creating roach-1    ... done
Creating roach-2    ... done
Creating lb         ... done
Creating web        ... done
CREATE ROLE

Time: 8.6299ms

CREATE DATABASE

Time: 15.1892ms

GRANT

Time: 6.4917ms

SET CLUSTER SETTING

Time: 12.3533ms

SET CLUSTER SETTING

Time: 11.2168ms

SET CLUSTER SETTING

Time: 15.9956ms

SET CLUSTER SETTING

Time: 12.6019ms


     3. 检查日志。


docker logs web



2020-08-17 14:29:32,949 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2020-08-17 14:29:32,949 INFO sqlalchemy.engine.base.Engine SAVEPOINT cockroach_restart
2020-08-17 14:29:32,950 INFO sqlalchemy.engine.base.Engine {}
2020-08-17 14:29:32,952 INFO sqlalchemy.engine.base.Engine SELECT accounts.id AS accounts_id, accounts.balance AS accounts_balance
FROM accounts
WHERE accounts.id = %(id_1)s
2020-08-17 14:29:32,952 INFO sqlalchemy.engine.base.Engine {'id_1': 95435663}
2020-08-17 14:29:32,955 INFO sqlalchemy.engine.base.Engine UPDATE accounts SET balance=%(balance)s WHERE accounts.id = %(accounts_id)s
2020-08-17 14:29:32,956 INFO sqlalchemy.engine.base.Engine {'balance': 484504, 'accounts_id': 95435663}
2020-08-17 14:29:32,958 INFO sqlalchemy.engine.base.Engine UPDATE accounts SET balance=(accounts.balance + %(balance_1)s) WHERE accounts.id = %(id_1)s
2020-08-17 14:29:32,959 INFO sqlalchemy.engine.base.Engine {'balance_1': 484503, 'id_1': 756738049}
2020-08-17 14:29:32,961 INFO sqlalchemy.engine.base.Engine RELEASE SAVEPOINT cockroach_restart
2020-08-17 14:29:32,961 INFO sqlalchemy.engine.base.Engine {}
2020-08-17 14:29:32,966 INFO sqlalchemy.engine.base.Engine COMMIT


     4. 查看申请状态。


docker-compose ps



   Name                 Command               State                                         Ports
----------------------------------------------------------------------------------------------------------------------------------------
kdc          /start.sh                        Up
lb           /docker-entrypoint.sh hapr ...   Up      0.0.0.0:26257->26257/tcp, 5432/tcp, 0.0.0.0:8080->8080/tcp, 0.0.0.0:8081->8081/tcp
roach-0      /cockroach/cockroach.sh st ...   Up      26257/tcp, 8080/tcp
roach-1      /cockroach/cockroach.sh st ...   Up      26257/tcp, 8080/tcp
roach-2      /cockroach/cockroach.sh st ...   Up      26257/tcp, 8080/tcp
roach-cert   /bin/sh -c tail -f /dev/null     Up
web          ./sqlalchemy/start.sh            Up      0.0.0.0:8000->8000/tcp


     5. 连接 CockroachDB 并检查帐户是否已填充。


docker exec -it roach-0 sh



cockroach sql --certs-dir=/certs --host=lb
#
# Welcome to the CockroachDB SQL shell.
# All statements must be terminated by a semicolon.
# To exit, type: \q.
#
# Server version: CockroachDB CCL v20.1.4 (x86_64-unknown-linux-gnu, built 2020/07/29 22:56:36, go1.13.9) (same version as client)
# Cluster ID: 333acd7f-ec6e-4e47-9b92-4130c8aad13b
# Organization: Cockroach Labs - Production Testing
#
# Enter \? for a brief introduction.
#
root@roach-0:26257/defaultdb> select * from bank.accounts;
     id     | balance
------------+----------
   28585249 |  269455
   76361884 |  638333
...
  997258425 |  181144
(100 rows)

Time: 2.1635ms

root@roach-0:26257/defaultdb> \q


让我们来看看这个应用程序的细节。

除了一些更改之外,这与我之前的教程类似。sqlalchemy我的项目树中有一个名为的新文件夹


.
├── README.md
├── docker-compose.yml
├── down.sh
├── haproxy
│   ├── Dockerfile
│   └── haproxy.cfg
├── kdc
│   ├── Dockerfile
│   ├── krb5.conf
│   └── start.sh
├── prune.sh
├── roach-cert
│   ├── Dockerfile
│   └── README.md
├── sqlalchemy
│   ├── Dockerfile
│   ├── requirements.txt
│   ├── sqlalchemy-basic-sample.py
│   └── start.sh
├── up.sh
└── writeup.md

4 directories, 17 files


我们的 SQLAlchemy 容器是一个标准的 python 3 映像,我在其中安装krb5-user包。下面Dockerfile是。

Dockerfile


FROM python:3
ENV PYTHONUNBUFFERED 1

RUN apt-get update && \
  DEBIAN_FRONTEND=noninteractive apt-get install --yes --no-install-recommends \
  krb5-user \
  && rm -rf /var/lib/apt/lists/*

RUN mkdir /code
WORKDIR /code
COPY requirements.txt /code/
RUN python -m pip install --upgrade pip && \
  pip install -r requirements.txt
COPY . /code/
ENTRYPOINT ["./sqlalchemy/start.sh"]


我有一个需求文件,接下来我们将查看它,我将其注入容器并使用pip. 然后,我将其余的应用程序代码复制到容器中,并使用 shell 脚本启动容器。

要求.txt


psycopg2==2.8.*
sqlalchemy==1.3.*
sqlalchemy-cockroachdb==1.3.*


start.sh


#!/bin/sh

set -e

echo psql | kinit sqlalchemy@EXAMPLE.COM

env

sleep 10

python ./sqlalchemy/sqlalchemy-basic-sample.py

tail -f /dev/null


在 shell 脚本中,我kinit作为sqlalchemy用户,恰好是我们 KDC 中的授权用户。然后我运行一个 python 脚本,它是代码的略微修改版本,您可以在 CockroachDB 教程站点中找到我指定 Kerberos 特定连接参数的地方。具体来说,我们只关心我们的用户,我们添加到 KDC 和可选的自定义 SPN,除非您更喜欢默认的postgres.

sqlalchemy-basic-sample.py


if secure_cluster:
    connect_args = {
        'sslmode': 'verify-full',
        'sslrootcert': '/certs/ca.crt',
        'user': 'sqlalchemy',
        'krbsrvname': 'customspn'
    }
else:
    connect_args = {'sslmode': 'disable'}


我在 KDC Dockerfile 中唯一更改的是主体的名称。


kadmin.local -q "addprinc -pw psql sqlalchemy@EXAMPLE.COM


我还在我的 compose 文件中保留了大部分服务定义,以匹配上周的 Django 示例。

docker-compose.yaml


  web:
    container_name: web
    hostname: web
    build: sqlalchemy/.
    extra_hosts:
      - "lb:172.28.1.7"
    depends_on:
      - roach-cert
      - lb
      - kdc
      - roach-0
      - roach-1
      - roach-2
    volumes:
      - .:/code
      - ./kdc/krb5.conf:/etc/krb5.conf
      - certs-client:/certs
      - ./sqlalchemy/start.sh:/start.sh
      - keytab:/keytab
    ports:
      - "8000:8000"
    networks:
      roachnet:
        ipv4_address: 172.28.1.8


command当我通过 Web Dockerfile 控制部署时,我删除了该参数。我也将相同krb5.conf的内容注入到 Web 容器中。

我们现在有一个完全 kerborized 的 SQLAlchemy 部署。最后,为了绝对确定 Kerberos 确实有效,让我们使用虚拟 SPN 修改连接属性。


if secure_cluster:
    connect_args = {
        'sslmode': 'verify-full',
        'sslrootcert': '/certs/ca.crt',
        'user': 'sqlalchemy',
        'krbsrvname': 'dummy'
    }


如果我们再次运行应用程序,web容器将无法启动,我们可以检查日志,我们将在其中收到与此类似的堆栈跟踪。


    connection = pool._invoke_creator(self)
  File "/usr/local/lib/python3.8/site-packages/sqlalchemy/engine/strategies.py", line 114, in connect
    return dialect.connect(*cargs, **cparams)
  File "/usr/local/lib/python3.8/site-packages/sqlalchemy/engine/default.py", line 493, in connect
    return self.dbapi.connect(*cargs, **cparams)
  File "/usr/local/lib/python3.8/site-packages/psycopg2/__init__.py", line 127, in connect
    conn = _connect(dsn, connection_factory=connection_factory, **kwasync)
sqlalchemy.exc.OperationalError: (psycopg2.OperationalError) GSSAPI continuation error: Unspecified GSS failure.  Minor code may provide more information
GSSAPI continuation error: Server dummy/lb@EXAMPLE.COM not found in Kerberos database


让我们完全删除它krbsrvname以确保我们的应用程序使用默认postgresSPN。


if secure_cluster:
    connect_args = {
        'sslmode': 'verify-full',
        'sslrootcert': '/certs/ca.crt',
        'user': 'sqlalchemy',
    }


我们可以重新启动 Web 容器docker-compose start web并检查日志。几秒钟后,我们应该看到以下内容:


FROM accounts
WHERE accounts.id = %(id_1)s
2020-08-17 17:34:01,232 INFO sqlalchemy.engine.base.Engine {'id_1': 642966854}
2020-08-17 17:34:01,235 INFO sqlalchemy.engine.base.Engine UPDATE accounts SET balance=%(balance)s WHERE accounts.id = %(accounts_id)s
2020-08-17 17:34:01,235 INFO sqlalchemy.engine.base.Engine {'balance': 439621, 'accounts_id': 642966854}
2020-08-17 17:34:01,238 INFO sqlalchemy.engine.base.Engine UPDATE accounts SET balance=(accounts.balance + %(balance_1)s) WHERE accounts.id = %(id_1)s
2020-08-17 17:34:01,238 INFO sqlalchemy.engine.base.Engine {'balance_1': 439620, 'id_1': 492716732}
2020-08-17 17:34:01,240 INFO sqlalchemy.engine.base.Engine RELEASE SAVEPOINT cockroach_restart
2020-08-17 17:34:01,240 INFO sqlalchemy.engine.base.Engine {}
2020-08-17 17:34:01,245 INFO sqlalchemy.engine.base.Engine COMMIT


如果我们登录到 Web 容器并运行klist,我们应该会看到当前的票证缓存。


# klist
Ticket cache: FILE:/tmp/krb5cc_0
Default principal: sqlalchemy@EXAMPLE.COM

Valid starting     Expires            Service principal
08/17/20 17:33:50  08/18/20 17:33:50  krbtgt/EXAMPLE.COM@EXAMPLE.COM
    renew until 08/17/20 17:33:50
08/17/20 17:34:00  08/18/20 17:33:50  postgres/lb@
    renew until 08/17/20 17:33:50
08/17/20 17:34:00  08/18/20 17:33:50  postgres/lb@EXAMPLE.COM
    renew until 08/17/20 17:33:50


因此,postgresSPN 确实在发挥作用。

让我们也尝试将用户名更改为 KDC 不知道的名称。


if secure_cluster:
    connect_args = {
        'sslmode': 'verify-full',
        'sslrootcert': '/certs/ca.crt',
        'user': 'unknown',
    }


使用 重新启动容器docker-compose restart web。几秒钟后,您应该会在日志中看到以下消息。


    connection = pool._invoke_creator(self)
  File "/usr/local/lib/python3.8/site-packages/sqlalchemy/engine/strategies.py", line 114, in connect
    return dialect.connect(*cargs, **cparams)
  File "/usr/local/lib/python3.8/site-packages/sqlalchemy/engine/default.py", line 493, in connect
    return self.dbapi.connect(*cargs, **cparams)
  File "/usr/local/lib/python3.8/site-packages/psycopg2/__init__.py", line 127, in connect
    conn = _connect(dsn, connection_factory=connection_factory, **kwasync)
sqlalchemy.exc.OperationalError: (psycopg2.OperationalError) ERROR:  password authentication failed for user unknown

(Background on this error at: http://sqlalche.me/e/13/e3q8)


希望你和我一样相信。如果您觉得本教程有用,请给我留言或在评论中给我反馈。

最后修改时间:2022-06-17 10:17:22
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论