IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    [原]Fernet Token in Keystone v3 (by quqi99)

    quqi99发表于 2016-08-30 21:20:45
    love 0

    **作者:张华 发表于:2016-08-11
    版权声明:可以任意转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本版权声明
    ( http://blog.csdn.net/quqi99 )**

    问题的由来

    • UUID: nova client通过user/pass从keystone获取UUID Token(UUID为一段32位字符串),nova client还得再拿UUID去keystone端验证token是否有效(有效期,用户组,服务目录等元数据信息存储在DB中),这样keystone的压力很大,另外由于需要持久化token所以token在不同region之间同步也是一个问题。
    • PKI: client通过user/pass从keystone获取PKI Token(包括了元数据信息, keystone会只用私钥对其进行签名和CMS编码,没有加密),nova client不需要再去keystone端验证, nova能直接解码得到token的有效性等元数据信息,故nova不需要再请求keystone来验证这个Token,故它能大大减轻keystone的压力。但缺点是它的长度容易超过WEB Server对HTTP HEADER长度的限制, 且同样需要持久化token。
    • PZKI: PKI的长度容易超过WEB Server对HTTP HEADER长度的限制,于是PZKI通过zlib来对token进行压缩。它只解决了token过长的问题,仍然没有解决持久化token带来的问题。
    • Fernet: fernet使用AES-CBC来对称加密并使用SHA256来签名token,token只能通过对称密钥读取和更改,不需要持久化token,对于多个keystone节点的region之间的部署,只需要将相同的key发布到所有节点上,那么无论是通过哪个节点生成的token,都可以被其他的keystone节点所验证。用户在部署时所要考虑的,就是如何在Fernet Key Rotate的时候保持所有节点上key repository的同步,而这远远要比同步上万条token纪录来的简单。且fernet token只加密必要的信息,长度一般不超过255字节,避免了PKI token过大的问题。

    Fernet Key Format

    Fernet Key是由下列字段组合的base64编码(base64.urlsafe_b64encode):

    • 8 bits, Fernet Format Version(0x80)
    • 64 bits, current timestamp, int(time.time())
    • 128 bits, Initailization Vector(IV), os.urandom(16)
    • Ciphertext(Version, User_ID, Methods, Project_ID, Expiration Time, Audit IDs)
    • 256 bits, SHA256 HMAC with signing key

    Fernet Key Rotation

    可以通过max_active_keys=3配置在/etc/keystone/fernet-keys目录生成至多3个key, 数字最大的叫Primary Key, 其次大的叫Secondary Key, 再其次大的叫Staged Key。keystone使用Primary Key加密,可使用所有的Key按2,1,0的顺序进行解密和验证。

    • 默认使用keystone-manage utility初始化/etc/keystone/fernet-keys目录时,1为Primar Key, 0为Stage Key,没有Secondary Key.
    • Fernet Key Rotation后,以前的0变成2成为Primary Key, 以前的1还是1作为Secondary Key, 新生成0作为Staged Key
    • 再次Fernet Key Rotation后,0 becomes 3, 1 gets deleted, 2 stays 2, a new key becomes 0
    • 两个region不需要同时进行Fernet Key Rotation, 因为keystone使用Primary Key加密,使用所有的Key按2,1,0的顺序进行解密和验证。不需要sync token,但是需要sync token repository, 这样就能使一个node生成的tokens可以立即被其他节点验证。

    快速测试

    1, 采用juju快速部署测试环境:

    $ juju-deployer -c ./keystone-v3.yaml -d xenial-mitaka
    $ cat keystone-v3.yaml
    keystone-v3:
      services:
        keystone:
          branch: https://github.com/openstack/charm-keystone
          num_units: 1
          options:
            preferred-api-version: 3
            admin-password: 'password'
            admin-token: 'ubuntutesting'
            debug: 'true'
            verbose: 'true'
        mysql:
          branch: https://github.com/openstack/charm-percona-cluster
          constraints: mem=1G
          num_units: 1
          options:
            dataset-size: '50%'
            root-password: 'password'
            sst-password: 'password'
      relations:
        - [ keystone, mysql ]
    xenial-mitaka:
      inherits: keystone-v3
      series: xenial

    2, 更新keystone.conf

    [token]
    #provider = keystone.token.providers.uuid.Provider
    provider = fernet

    3, 更新DB

    su -s /bin/sh -c "keystone-manage db_sync" keystone

    4, Initiate fernet key repository

    mkdir -p /etc/keystone/fernet-keys
    chown -R keystone:keystone /etc/keystone/fernet-keys
    keystone-manage fernet_setup --keystone-user keystone --keystone-group keystone

    5, restart apache2

    cat /etc/apache2/sites-enabled/wsgi-keystone.conf
    systemctl restart apache2

    6, Verify

    export OS_TOKEN=ubuntutesting
    export OS_URL=${OS_AUTH_PROTOCOL:-http}://`juju-deployer -f keystone`:5000/v3
    export OS_IDENTITY_API_VERSION=3
    
    openstack service create --name keystone --description "OpenStack Identity" identity
    openstack service list
    
    openstack endpoint create --region RegionOne identity public http://`juju-deployer -f keystone`:5000/v3
    openstack endpoint create --region RegionOne identity internal http://`juju-deployer -f keystone`:5000/v3
    openstack endpoint create --region RegionOne identity admin http://`juju-deployer -f keystone`:35357/v3
    openstack endpoint list
    
    openstack domain create --description "Default Domain" default
    openstack domain list
    
    openstack project create --domain default --description "Admin Project" admin
    openstack project create --domain default --description "Service Project" services
    openstack project list
    
    openstack role create admin
    openstack role create user
    openstack role list
    
    openstack user create --domain default --password-prompt admin
    openstack role add --project admin --user admin admin
    openstack user create --domain default --password-prompt demo
    openstack role add --project services --user demo user
    openstack user list
    
    #Verify Token
    cat > demo-openrc << OFF
    export OS_PROJECT_DOMAIN_NAME=default
    export OS_USER_DOMAIN_NAME=default
    export OS_PROJECT_NAME=services
    export OS_USERNAME=demo
    export OS_PASSWORD=password
    export OS_AUTH_URL=http://`juju-deployer -f keystone`:5000/v3
    export OS_IDENTITY_API_VERSION=3
    export OS_IMAGE_API_VERSION=2
    OFF
    source ./demo-openrc
    unset OS_TOKEN OS_URL
    openstack token issue
    
    #Get Token
    cat > token-request.json << OFF
    {
      "auth": {
        "identity": {
          "methods": [
            "password"
          ],
          "password": {
            "user": {
              "domain": {
                "name": "default"
              },
              "name": "demo",
              "password": "password"
            }
          }
        }
      }
    }
    OFF
    USER_ID=$(curl -d @token-request.json -H "Content-type: application/json" http://`juju-deployer -f keystone`:5000/v3/auth/tokens | python -c "import sys; import json; tok = json.loads(sys.stdin.read()); print tok['token']['user']['id'];")
    
    TOKEN=$(curl -i -d @token-request.json -H "Content-type: application/json" http://`juju-deployer -f keystone`:5000/v3/auth/tokens |grep X-Subject-Token |awk -F ':' '{print $2}') 
    
    #Use Token
    curl -H "X-Auth-Token: $TOKEN" http://`juju-deployer -f keystone`:5000/v2.0/tenants

    Fernet Key in Multiple Regions

    1, 需要创建第二个keystone作为RegionTwo

    openstack endpoint create \
    --publicurl http://controller:5000/v2.0 \
    --internalurl http://controller:5000/v2.0 \
    --adminurl http://controller:35357/v2.0 \
    --region RegionTwo \
    identity

    2, 这些Keystone Region Nodes所使用的DB应该sync
    3, 上面fernet_setup命令生成的fernet key初始时应该复制到所有Keystone region nodes.
    4, Key rotate

    keystone-manage fernet_rotate --keystone-user keystone --keystone-group keystone
    ls /etc/keystone/fernet-keys/

    5, token不需要sync,但fernet key repository应该在所有keystone region nodes间作sync。例:

    rsync -e 'ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no' \
              -avz \
              --delete \
              {{ keystone_fernet_tokens_key_repository }}/ \
              {{ keystone_system_user_name }}@{{ hostvars[host]['ansible_ssh_host'] }}:{{ keystone_fernet_tokens_key_repository }}/
    

    参考

    [1] https://developer.ibm.com/opentech/2015/11/11/deep-dive-keystone-fernet-tokens
    [2] http://blog.csdn.net/zjluobing/article/details/51492356
    [3] http://dolphm.com/inside-openstack-keystone-fernet-token-payloads/
    [4] https://github.com/openstack/openstack-ansible-os_keystone/blob/master/templates/keystone-fernet-rotate.sh.j2
    [5] http://gogosatellite.blogspot.jp/2016/05/openstack-keystone-fernet-key-in.html



沪ICP备19023445号-2号
友情链接