美文网首页
keystone token和zoning

keystone token和zoning

作者: 爱吃土豆的程序猿 | 来源:发表于2017-02-21 14:41 被阅读0次

    keystone四种token

    keystone的四种token为:UUID token、PKI token、PKIZ token和Fernet token。

    什么是token

    通俗的讲,token 是用户的一种凭证,需拿正确的用户名/密码向 Keystone 申请才能得到。如果用户每次都采用用户名/密码访问 OpenStack API,容易泄露用户信息,带来安全隐患。所以 OpenStack 要求用户访问其 API 前,必须先获取 token,然后用 token 作为用户凭据访问 OpenStack API。

    四种 Token 的由来

    D 版本时,仅有 UUID 类型的 Token,UUID token 简单易用,却容易给 Keystone 带来性能问题。认证过程如下:

    每当 OpenStack API 收到用户请求,都需要向 Keystone 验证该 token 是否有效。随着集群规模的扩大,Keystone 需处理大量验证 token 的请求,在高并发下容易出现性能问题。

    于是 PKI(Public Key Infrastructrue) token 在 G 版本运用而生,和 UUID 相比,PKI token 携带更多用户信息的同时还附上了数字签名,以支持本地认证,从而避免了步骤 4。PKI的认证过程为:

    因为 PKI token 携带了更多的信息,这些信息就包括 service catalog,随着 OpenStack 的 Region 数增多,service catalog 携带的 endpoint 数量越多,PKI token 也相应增大,很容易超出 HTTP Server 允许的最大 HTTP Header(默认为 8 KB),导致 HTTP 请求失败。

    于是,PKIZ token随之出现,PKIZ toke就是 PKI token 的压缩版,认证过程为:

    但PKIZ token的压缩效果有限,无法良好的处理 token size 过大问题。

    前三种 token 都会持久性存于数据库,与日俱增积累的大量 token 引起数据库性能下降,所以用户需经常清理数据库的 token。为了避免该问题,社区提出了 Fernet token,它携带了少量的用户信息,大小约为 255 Byte,采用了对称加密,无需存于数据库中。认证过程为:

    1、UUID

    UUID token 是长度固定为 32 Byte 的随机字符串,由 uuid.uuid4().hex 生成。

    def _get_token_id(self, token_data):
        return uuid.uuid4().hex
    

    但是因 UUID token 不携带其它信息,OpenStack API 收到该 token 后,既不能判断该 token 是否有效,更无法得知该 token 携带的用户信息,所以需经图一步骤 4 向 Keystone 校验 token,并获用户相关的信息。其样例如下:

    144d8a99a42447379ac37f78bf0ef608
    

    UUID token 简单美观,不携带其它信息,因此 Keystone 必须实现 token 的存储和认证,随着集群的规模增大,Keystone 将成为性能瓶颈。

    2、PKI

    在阐述 PKI(Public Key Infrastruction) token 前,让我们简单的回顾公开密钥加密(public-key cryptography)和数字签名。公开密钥加密,也称为非对称加密(asymmetric cryptography,加密密钥和解密密钥不相同),在这种密码学方法中,需要一对密钥,分别为公钥(Public Key)和私钥(Private Key),公钥是公开的,私钥是非公开的,需用户妥善保管。如果把加密和解密的流程当做函数 C(x) 和 D(x),P 和 S 分别代表公钥和私钥,对明文 A 和密文 B 而言,数学的角度上有以下公式:

    B = C(A, S)
    A = D(B, P)

    其中加密函数 C(x), 解密函数 D(x) 以及公钥 P 均是公开的。采用公钥加密的密文只能用私钥解密,采用私钥加密的密文只能用公钥解密。非对称加密广泛运用在安全领域,诸如常见的 HTTPS,SSH 登录等。

    数字签名又称为公钥数字签名,首先采用 Hash 函数对消息生成摘要,摘要经私钥加密后称为数字签名。接收方用公钥解密该数字签名,并与接收消息生成的摘要做对比,如果二者一致,便可以确认该消息的完整性和真实性。

    PKI 的本质就是基于数字签名,Keystone 用私钥对 token 进行数字签名,各个 API server 用公钥在本地验证该 token。相关代码简化如下:

    def _get_token_id(self, token_data):
        try:
            token_json = jsonutils.dumps(token_data, cls=utils.PKIEncoder)
            token_id = str(cms.cms_sign_token(token_json,
                                              CONF.signing.certfile,
                                              CONF.signing.keyfile))
            return token_id
    

    token_data 经 cms.cms_sign_token 签名生成的 token_id 如下,共 1932 Byte:

    MIIKoZIhvcNAQcCoIIFljCCBZICAQExDTALBglghkgBZQMEAgEwggPzBgkqhkiG9w0B
    ......
    rhr0acV3bMKzmqvViHf-fPVnLDMJajOWSuhimqfLZHRdr+ck0WVQosB6+M6iAvrEF7v
    

    3、PKIZ

    PKIZ 在 PKI 的基础上做了压缩处理,但是压缩的效果极其有限,一般情况下,压缩后的大小为 PKI token 的 90 % 左右,所以 PKIZ 不能友好的解决 token size 太大问题。

    def _get_token_id(self, token_data):
        try:
            token_json = jsonutils.dumps(token_data, cls=utils.PKIEncoder)
            token_id = str(cms.pkiz_sign(token_json,
                                         CONF.signing.certfile,
                                         CONF.signing.keyfile))
            return token_id
    

    其中 cms.pkiz_sign() 中的以下代码调用 zlib 对签名后的消息进行压缩级别为 6 的压缩。

    compressed = zlib.compress(token_id, compression_level=6)
    

    PKIZ token 样例如下,共 1645 Byte,比 PKI token 减小 14.86 %:

    PKIZ_eJytVcuOozgU3fMVs49aTXhUN0vAQEHFJiRg8IVHgn5OnA149JVaunNS3NYjoSU
    ......
    W4fRaxrbNtinojheVICXYrEk0oPX6TSnP71IYj2e3nm4MLy7S84PtIPDz4_03IsOb2Q=
    

    4、Fernet

    用户可能会碰上这么一个问题,当集群运行较长一段时间后,访问其 API 会变得奇慢无比,究其原因在于 Keystone 数据库存储了大量的 token 导致性能太差,解决的办法是经常清理 token。为了避免上述问题,社区提出了Fernet token,它采用 cryptography 对称加密库(symmetric cryptography,加密密钥和解密密钥相同) 加密 token,具体由 AES-CBC 加密和散列函数 SHA256 签名。Fernet 是专为 API token 设计的一种轻量级安全消息格式,不需要存储于数据库,减少了磁盘的 IO,带来了一定的性能提升。为了提高安全性,需要采用 Key Rotation 更换密钥。

    def create_token(self, user_id, expires_at, audit_ids, methods=None,
                     domain_id=None, project_id=None, trust_id=None,
                     federated_info=None):
        """Given a set of payload attributes, generate a Fernet token."""
    
        if trust_id:
            version = TrustScopedPayload.version
            payload = TrustScopedPayload.assemble(
                user_id,
                methods,
                project_id,
                expires_at,
                audit_ids,
                trust_id)
    
        ...
    
        versioned_payload = (version,) + payload
        serialized_payload = msgpack.packb(versioned_payload)
        token = self.pack(serialized_payload)
    
        return token
    

    以上代码表明,token 包含了 user_id,project_id,domain_id,methods,expires_at 等信息,重要的是,它没有 service_catalog,所以 region 的数量并不影响它的大小。self.pack() 最终调用如下代码对上述信息加密:

    def crypto(self):
        keys = utils.load_keys()
    
        if not keys:
            raise exception.KeysNotFound()
    
        fernet_instances = [fernet.Fernet(key) for key in utils.load_keys()]
        return fernet.MultiFernet(fernet_instances)
    

    该 token 的大小一般在 200 多 Byte 左右,本例样式如下,大小为 186 Byte:

    gAAAAABWfX8riU57aj0tkWdoIL6UdbViV-632pv0rw4zk9igCZXgC-sKwhVuVb-wyMVC9e5TFc
    7uPfKwNlT6cnzLalb3Hj0K3bc1X9ZXhde9C2ghsSfVuudMhfR8rThNBnh55RzOB8YTyBnl9MoQ
    XBO5UIFvC7wLTh_2klihb6hKuUqB6Sj3i_8
    

    如何选择 Token

    Token类型 UUID PKI PKIZ Fernet
    大小 32 Byte KB 级别 KB 级别 约 255 Byte
    支持本地认证 不支持 支持 支持 不支持
    Keystone 负载
    存储于数据库
    携带信息 user, catalog 等 user, catalog 等 user 等
    涉及加密方式 非对称加密 非对称加密 对称加密(AES)
    是否压缩
    版本支持 D G J K

    Token 类型的选择涉及多个因素,包括 Keystone server 的负载、region 数量、安全因素、维护成本以及 token 本身的成熟度。region 的数量影响 PKI/PKIZ token 的大小,从安全的角度上看,UUID 无需维护密钥,PKI 需要妥善保管 Keystone server 上的私钥,Fernet 需要周期性的更换密钥,因此从安全、维护成本和成熟度上看,UUID > PKI/PKIZ > Fernet 如果:

    • Keystone server 负载低,region 少于 3 个,采用 UUID token。
    • Keystone server 负载高,region 少于 3 个,采用 PKI/PKIZ token。
    • Keystone server 负载低,region 大与或等于 3 个,采用 UUID token。
    • Keystone server 负载高,region 大于或等于 3 个,K 版本及以上可考虑采用 Fernet token。

    keystone 四个重要概念

    openstack中region、cell、availability zone、host aggregate 几个概念都是对节点进行划分,具体有什么区别呢?

    1、Region

    更像是一个地理上的概念,每个region有自己独立的endpoint,regions之间完全隔离,但是多个regions之间共享同一个keystone和dashboard。

    所以除了提供隔离的功能,region的设计更多侧重地理位置的概念,用户可以选择离自己更近的region来部署自己的服务。

    2、Cells

    在大规模部署的时候,API、Conductor等服务需要集中部署,并且比较容易扩展,因为它们是无状态的,或接近于无状态的,主要的压力在数据库上面。

    Compute服务会部署在每个hypervisor节点,因为消息队列服务能够支持的规模在200个hypervisor左右,所以,Compute服务如何扩展是一个需要仔细考虑的问题。
    此外,在上千台规模时,Scheduler可能会出现性能瓶颈。因为,目前Scheduler的实现方式不是很优雅,不能处理大规模、高并发的调度请求。
    为了解决数据库和消息队列扩展的问题,nova项目引入了cell服务,通过cell服务可以将hypervisor节点分成若干个组,每组hypervisor节点使用不同的数据库和消息队列服务,从而解决数据库和消息队列的扩展问题。目前,nova cell有两个实现版本v1 和 v2,v1实现的架构如下:

    由于cell被实现为树形结构,自然而然引入了分级调度的概念。通过在每级cell引入nova-cell服务,实现了以下功能:
    (1)Messages的路由,即父cell通过nova-cell将Messages路由到子cell的AMQP模块
    (2)分级调度功能,即调度某个instances的时候先要进行cell的选择,目前只支持随机调度,后续会增加基于filter和weighing策略的调度。
    (3)资源统计,子cell定时的将自己的资源信息上报给父cell,用来给分级调度策略提供决策数据和基于cell的资源监控
    (4)cell之间的通信(通过rpc完成)
    最后,所有的子cell公用底层cell的nova-api,子cell包含除了nova-api之外的其他nova服务,当然所有的cell都共用keystone服务。
    (注:nova-*是指除了nova-api之外的其他nova服务,子cell + 父cell才构成了完整的nova服务)

    3、Availability Zone

    AZ可以简单理解为一组节点的集合,这组节点具有独立的电力供应设备,比如一个个独立供电的机房,一个个独立供电的机架都可以被划分成AZ。所以,AZ主要是通过冗余来解决可用性问题。

    AZ是用户可见的一个概念,用户在创建instance的时候可以选择创建到哪些AZ中,以保证instance的可用性。

    4、Host Aggregate

    AZ是一个面向用户的概念和能力,而host aggregate是管理员用来根据硬件资源的某一属性来对硬件进行划分的功能,只对管理员可见,主要用来给nova-scheduler通过某一属性来进行instance的调度。其主要功能就是实现根据某一属性来划分物理机,比如按照地理位置,使用固态硬盘的机器,内存超过32G的机器,根据这些指标来构成一个host group。

    /etc/nova/nova.conf:
    scheduler_default_filters=AggregateInstanceExtraSpecsFilter,AvailabilityZoneFilter,RamFilter,ComputeFilter
    
    $ nova aggregate-create fast-io nova
    +----+---------+-------------------+-------+----------+
    | Id | Name    | Availability Zone | Hosts | Metadata |
    +----+---------+-------------------+-------+----------+
    | 1  | fast-io | nova              |       |          |
    +----+---------+-------------------+-------+----------+
    
    $ nova aggregate-set-metadata 1 ssd=true
    +----+---------+-------------------+-------+-------------------+
    | Id | Name    | Availability Zone | Hosts | Metadata          |
    +----+---------+-------------------+-------+-------------------+
    | 1  | fast-io | nova              | []    | {u'ssd': u'true'} |
    +----+---------+-------------------+-------+-------------------+
    
    $ nova aggregate-add-host 1 node1
    +----+---------+-------------------+-----------+-------------------+
    | Id | Name    | Availability Zone | Hosts      | Metadata          |
    +----+---------+-------------------+------------+-------------------+
    | 1  | fast-io | nova              | [u'node1'] | {u'ssd': u'true'} |
    +----+---------+-------------------+------------+-------------------+
    
    $ nova aggregate-add-host 1 node2
    +----+---------+-------------------+---------------------+-------------------+
    | Id | Name    | Availability Zone | Hosts                | Metadata          |
    +----+---------+-------------------+----------------------+-------------------+
    | 1  | fast-io | nova              | [u'node1', u'node2'] | {u'ssd': u'true'} |
    +----+---------+-------------------+----------------------+-------------------+
    
    $ nova flavor-create ssd.large 6 8192 80 4
    +----+-----------+-----------+------+-----------+------+-------+-------------+-----------+-------------+
    | ID | Name      | Memory_MB | Disk | Ephemeral | Swap | VCPUs | RXTX_Factor | Is_Public | extra_specs |
    +----+-----------+-----------+------+-----------+------+-------+-------------+-----------+-------------+
    | 6  | ssd.large | 8192      | 80   | 0         |      | 4     | 1           | True      | {}          |
    +----+-----------+-----------+------+-----------+------+-------+-------------+-----------+-------------+
    
    # nova flavor-key set_key --name=ssd.large  --key=ssd --value=true
    $ nova flavor-show ssd.large
    +----------------------------+-------------------+
    | Property                   | Value             |
    +----------------------------+-------------------+
    | OS-FLV-DISABLED:disabled   | False             |
    | OS-FLV-EXT-DATA:ephemeral  | 0                 |
    | disk                       | 80                |
    | extra_specs                | {u'ssd': u'true'} |
    | id                         | 6                 |
    | name                       | ssd.large         |
    | os-flavor-access:is_public | True              |
    | ram                        | 8192              |
    | rxtx_factor                | 1.0               |
    | swap                       |                   |
    | vcpus                      | 4                 |
    +----------------------------+-------------------+
    
    Now, when a user requests an instance with the ssd.large flavor, the scheduler only considers hosts with the ssd=true key-value pair. In this example, these are node1 and node2.
    
    

    参考

    Openstack中国社区
    linuxsky的博客
    crowns的简书

    相关文章

      网友评论

          本文标题:keystone token和zoning

          本文链接:https://www.haomeiwen.com/subject/xpkuwttx.html