Jul 12, 2019 - Odoo加载机制流程源码解析

“Odoo加载机制流程源码解析

本文参考主要内容参考![]https://www.cnblogs.com/dancesir/p/7008852.html 分析的是odoo12的源码,加了一些自己的理解,特别是关于model注册到registry部分内容,查遍整个互联网也没看到详细解释,为自己研究和原创。模块加载外层就是封装一个Registry(Mapping)对象:实际是一个字典,它包含对应的db,model等映射关系,一个DB对应一个Registry。后续的操作都会围绕这个Registry进行,将相关的数据赋值给相应的属性项。modules/registry.py定义了Registry类,Registry类的__new__函数调用new()函数产生一个实例,new()调用odoo.modules.load_modules()函数装载模块(位于modules/loading.py:321)。其中的核心程序为load_modules函数。函数经过如下步骤完成了模块(module)和模型(model)的加载。

  1. 初始化数据库(初次运行) 调用odoo.modules.db.initialize(cr)(modules/db.py:17)完成加载base模块下的base.sql文件并执行。 此时数据库表为(具体见base.sql):
    CREATE TABLE ir_actions (
    ...
    CREATE TABLE ir_model (
    ...
    CREATE TABLE ir_model_fields (
    ...
    CREATE TABLE res_lang (
    ...
    CREATE TABLE res_users (
    ...
    create table wkf (
    ...
    CREATE TABLE ir_module_category (
    ...
    CREATE TABLE ir_module_module (
    ...
    CREATE TABLE ir_module_module_dependency (
    ...
    CREATE TABLE ir_model_data (
    ...
    CREATE TABLE ir_model_constraint (
    ...
    CREATE TABLE ir_model_relation (
    ...  
    CREATE TABLE res_currency (
    ...
    CREATE TABLE res_company (
    ...
    CREATE TABLE res_partner (
    ...
    

    这20张表是odoo系统级的,它是模块加载及系统运行的基础。后续模块生成的表及相关数据都可以在这20张中找到蛛丝马迹。

  2. 数据库表初始化后,就可以加载模块数据(addons)到数据库了,这个也是odoo作为平台灵活的原因,所有的数据都在数据库。 找到addons-path下所有的模块,然后一个一个的加载到数据库中。 先完成如下操作odoo.modules.db.initialize()(modules/db.py:336):
    • 将模块信息写入ir_module_module表:
    • 将module信息写入ir_model_data表: 一个module要写两次ir_model_data表,
    • 写module的dependency表:根据依赖关系进行判断,递归更新那些需要auto_install的模块状态为“to install”。

到目前为止,模块的加载都是在数据库级别,只是将“模块文件”信息存入数据库表,但是还没有真正加载到程序中。 Odoo运行时查找object是通过Registry.get()获取的,而不是通过python自己的机制来找到相应的object,所以odoo在最初import模块时(cli/command.py:53)会把模块下包含的model全部注册到models.py的MetaModel.module_to_models类变量(注意此时仅仅登记了model的类,并未完成实例化)。 下面的步骤就是加载模块到内存(实例化)。

  1. 加载base模块 graph.add_module()(:352)定义在modules/graph.py中两个类Graph,Node。Graph是一个存放Node的类,Node代表一个模块,Graph表示了模块之间的关系。其depth代表深度0,代表没有依赖模块,children代表依赖他的模块,所以Node的graph指向Graph类。 Graph的add_modules方法使得一组module和其关系信息全部导入Graph,当一个模块的所有依赖模块已经在Graph里,则简单的加入该模块(add_node),如果不满足,则加入later并且将模块放到列表最后,如此循环,直到packages列表空或者later列表里的模块已经超过等待导入的模块。注意info保存了模块所有信息的字典,通过load_information_from_description_file()导入文件__manifest__.py的内容。
    。 之后调用module/loading.py(:360)的load_module_graph()方法加载模块,最终执行加载的方法。这个方法是odoo加载model的核心,通过 __import__方法加载模块,这个是python的机制,当import到某个继承了BaseModel类的class时,它的实例化将有别于python自身的实例化操作,后者说它根本不会通过python自身的__new__方法创建实例,所有的实例创建都是通过实例化Registry对象在执行new函数是调用load_modules(registry.py:86)实现模型的实例化。load_modules(loading.py:321) _build_model 方法及元类创建。通过这种方式实例化model就可以解决我们在xml中配置model时指定的继承,字段,约束等各种属性。

  2. 标记需要加载或者更新的模块(db)
  3. 加载被标记的模块(加载过程与加载base模块一致)
  4. 完成及清理安装
  5. 清理菜单
  6. 删除卸载的模块
  7. 核实model的view
  8. 运行post-install测试

Jun 11, 2019 - odoo设置nginx的反向代理及ssl

odoo设置nginx反向代理及ssl

本文参考:Odoo.11.Development.Cookbook.2nd.Edition一书,也可以参考https://alanhou.org/odoo12-deployment/ 及官方文档 https://www.odoo.com/documentation/12.0/setup/deploy.html
假设已经安装好odoo和nginx,并且申请了CA证书(别忘了设置证书自动更新)。配置以odoo.example.com为例。关于证书的安装,可以参考我前面的文章。

  1. 作为root,建立文件 /etc/nginx/sites-available/odoo-80:
    server { 
     listen [::]:80 ipv6only=off; 
     server_name odoo.example.com; 
     access_log /var/log/nginx/odoo80.access.log combined; 
     error_log /var/log/nginx/odoo80.error.log; 
     location / { 
         rewrite ^/(.*) https://odoo.example.com:443/$1 permanent; 
     } 
    }
    
  2. 建立配置文件 /etc/nginx/sites-available/odoo-443:
    server { 
     listen [::]:443 ipv6only=off; 
     server_name odoo.example.com; 
     ssl on; 
     ssl_certificate /etc/letsencrypt/live/odoo.example.com/fullchain.pem; 
     ssl_certificate_key /etc/letsencrypt/live/odoo.example.com/privkey.pem; 
     access_log /var/log/nginx/odoo443.access.log combined; 
     error_log /var/log/nginx/odoo443.error.log; 
     client_max_body_size 128M; 
     gzip on; 
     proxy_read_timeout 600s; 
     index index.html index.htm index.php; 
     add_header Strict-Transport-Security "max-age=31536000";
    	
     proxy_set_header Host $http_host; 
     proxy_set_header X-Real-IP $remote_addr; 
     proxy_set_header X-Forward-For $proxy_add_x_forwarded_for; 
     proxy_set_header X-Forwarded-Proto https; 
     proxy_set_header X-Forwarded-Host $http_host; 
     location / { 
         proxy_pass http://localhost:8069; 
         proxy_read_timeout 6h; 
         proxy_connect_timeout 5s; 
         proxy_redirect http://$http_host/ https://$host:$server_port/; 
         add_header X-Static no; 
         proxy_buffer_size 64k; 
         proxy_buffering off; 
         proxy_buffers 4 64k; 
         proxy_busy_buffers_size 64k; 
         proxy_intercept_errors on; 
     } 
     location /longpolling/ { 
         proxy_pass http://localhost:8072; 
     } 
     location ~ /[a-zA-Z0-9_-]*/static/ { 
         proxy_pass http://localhost:8069; 
         proxy_cache_valid 200 60m; 
         proxy_buffering on; 
         expires 864000; 
     } 
    } 
    
  3. 建立配置文件链接 /etc/nginx/sites-enabled/:
    # ln -s /etc/nginx/sites-available/odoo-80 /etc/nginx/sites-enabled/odoo-80 
    # ln -s /etc/nginx/sites-available/odoo-443 /etc/nginx/sites-enabled/odoo-443 
    
  4. 删除默认文件/etc/nginx/sites-enabled/default:
    # rm /etc/nginx/sites-enabled/default 
    
  5. 编辑odoo的启动配置文件 修改proxy_mode = True

  6. 重启odoo和nginx,浏览http://odoo.example.com

  7. 以上为Cookbook上的内容,按照此方法配置好以后还存在问题,在discuss模块无法正常进行及时聊天。后发现odoo使用长轮询机制实现聊天,应该是会打开一个8072端口。nginx已经配置了8072端口的跳转,但发现8072端口根本没有启动(无法进行反向代理,但不用反向代理情况下可以正常使用discuss)。后百度找到解决办法,把odoo的配置文件里的workers = 0调整为workers = 1,再次启动odoo服务后用lsof -i命令,可以看到8072端口已经启动。

May 24, 2019 - 申请Let’s Encrypt免费证书实践

1.什么是SSL和CA证书

  互联网的http协议是通过明文传输的,很容易造成信息的泄漏。为了在网上实现信息安全,Netscape开发了ssl协议(https)用以保障在Internet上数据传输之安全。
CA证书顾名思义就是由CA(Certification Authority)机构发布的数字证书。要对CA证书完全理解及其作用,首先要理解SSL。SSL(security sockets layer,安全套接层)是为网络通信提供安全及数据完整性的一种安全协议。SSL3.0版本以后又被称为TLS。
SSL如何保证网络通信的安全和数据的完整性呢?就是采用了两种手段:身份认证和数据加密。首先身份认证就需要用到CA证书了,解决了”我是谁?”的问题。
证书分三种:

  • 域名型https证书(DVSSL):信任等级一般,只需验证网站的真实性便可颁发证书保护网站;
  • 企业型https证书(OVSSL):信任等级高,须要验证企业的身份,审核严格,安全性更强;
  • 增强型https证书(EVSSL):信任等级最高,一般用于银行证券等金融机构,审核严格,安全性最好,同时可以激活绿色网址栏

2.SSL协议的建立和传输

  需要理解SSL的加密机制,在使用SSL的网络通讯过程中,消息在请求和响应中都是加密传送的。首先要知道加密算法分为两种:对称加密和非对称加密。对称加密就是发送双发使用相同的密钥对消息进行加解密,常见的对称加密为DES、3DES,AES等。非对称加密是发送双方各自拥有一对公钥私钥,其中公钥是公开的,私钥是保密的。当发送方向接收方发送消息时,发送方利用接收方的公钥对消息进行加密,接收方收到消息后,利用自己的私钥解密就能得到消息的明文。其中非对称加密方法有RSA、Elgamal、ECC等。由于非对称加密的速度比较慢,所以它一般用于密钥交换,双方通过公钥算法协商出一份密钥,然后通过对称加密来通信。
  密钥协商过程作为一种互联网安全加密技术,原理较为复杂,枯燥而无味。这里我们用个形象的比喻,我们假设A与B通信,A是SSL客户端,B是SSL服务器端,加密后的消息放在方括号[]里,以突出明文消息的区别。双方的处理动作的说明用圆括号()括起。

  • A:我想和你安全的通话,我这里的对称加密算法有DES,RC5,密钥交换算法有RSA和DH,摘要算法有MD5和SHA。
  • B:我们用DES-RSA-SHA这对组合好了。 这是我的证书,里面有我的名字和公钥,你拿去验证一下我的身份(把证书发给A)。 目前没有别的可说的了。
  • A:(查看证书上B的名字是否无误,并通过手头早已有的CA的证书验证了B的证书的真实性,如果其中一项有误,发出警告并断开连接,这一步保证了B的公钥的真实性) (产生一份秘密消息,这份秘密消息处理后将用作加密密钥,加密初始化向量(IV)和hmac的密钥。将这份秘密消息-协议中称为per_master_secret-用B的公钥加密,封装成称作ClientKeyExchange的消息。由于用了B的公钥,保证了第三方无法窃听) 我生成了一份秘密消息,并用你的公钥加密了,给你(把ClientKeyExchange发给B) 注意,下面我就要用加密的办法给你发消息了! (将秘密消息进行处理,生成加密密钥,加密初始化向量和hmac的密钥) [我说完了]
  • B:(用自己的私钥将ClientKeyExchange中的秘密消息解密出来,然后将秘密消息进行处理,生成加密密钥,加密初始化向量和hmac的密钥,这时双方已经安全的协商出一套加密办法了) 注意,我也要开始用加密的办法给你发消息了! [我说完了]
  • A: [我的秘密是…]
  • B: [其它人不会听到的…]

3.如何部署HTTPS网站

  部署HTTPS网站的时候需要证书,证书由CA机构签发,大部分传统CA机构签发证书是需要收费的,这不利于推动HTTPS协议的使用。 Let’s Encrypt 也是一个CA机构,但这个CA机构是免费的!!!也就是说签发证书不需要任何费用。当然他的证书属于DVSSL。
  Let’s Encrypt设计了一个ACME协议。那为什么要创建ACME协议呢,传统的CA机构是人工受理证书申请、证书更新、证书撤销,完全是手动处理的。而ACME协议规范化了证书申请、更新、撤销等流程,只要一个客户端实现了该协议的功能,通过客户端就可以向 Let’s Encrypt 申请证书,也就是说 Let’s Encrypt CA完全是自动化操作的。任何人都可以基于ACME协议实现一个客户端,官方推荐的客户端是Certbot

4.Certbot客户端安装和命令

  除非您有非常特别的要求,建议您使用您操作系统的包管理工具提供的Certbot包(参见certbot.eff.org)。如果这个包不可用,我们建议使用certbot-AUTO,它自动在您的系统上安装Certbot。Certbot客户端支持两种用于获取和安装证书的两种类型的插件:身份验证和安装程序。
  份验证与certonly命令一起使用的插件,用于获取证书。身份验证验证您是否控制了请求证书的域,获得了指定域的证书,并将证书放置在计算机上的/etc/letscrypt目录中。身份验证器不安装证书(它不编辑服务器的任何配置文件以提供所获得的证书)。如果指定要进行身份验证的多个域,则所有域都将在单个证书中列出。要获得多个单独的证书,您需要多次运行Certbot。
安装程序是与install命令一起使用的插件,用于安装证书。这些插件可以修改Web服务器的配置,使certbot获得的证书通过HTTPS为您的网站提供服务。官方有支持不同web服务器的列表
比如debain以nginx为例的安装命令(其他环境参考官网):

$ sudo apt-get install certbot python-certbot-nginx

命令说明:
不写任何子命令时,默认使用 run 命令。certbot-auto –nginx -d xx.com 等价于 certbot-auto run –nginx -d xx.com。 列表只涉及获取、安装、更新证书部分,完整的命令文档请参考官方用户手册。

  • (default) run 为当前服务器获取并安装一个证书
  • certonly 只获取或更新证书,不安装
  • renew 更新所有快要过期的证书
  • -d DOMAINS 指定要获取证书的域名,一个证书支持多个域名,用逗号分隔
  • –apache 使用Apache服务器插件来认证和安装证书
  • –standalone certbot 会自己运行一个 web server 来进行验证
  • –nginx 使用 Nginx 服务器插件来认证和安装证书,可以读取 Nginx 配置文件并自动更改。
  • –webroot 会利用既有的 web server,在其 web root目录下创建隐藏文件, Let’s Encrypt 服务端会通过域名来访问这些隐藏文件,以确认你的确拥有对应域名的控制权
  • –manual 交互式获取证书,或者使用钩子脚本
  • -n 非交互式运行
  • –test-cert 从 staging server 获取一个测试证书
  • –dry-run 测试 “renew” 和 “certonly” 命令,不保存证书到磁盘上

5.获取安装单域名证书

  客户在申请Let’s Encrypt证书的时候,需要校验域名的所有权,证明操作者有权利为该域名申请证书,目前支持三种验证方式:

  • dns-01:给域名添加一个 DNS TXT 记录。有时候我们想为内网的某台主机设置HTTPS,内网的主机无法被letsencrypt的服务器访问到,可以使用DNS记录来验证域名
  • http-01:在域名对应的 Web 服务器下放置一个 HTTP well-known URL 资源文件。
  • tls-sni-01:在域名对应的 Web 服务器下放置一个 HTTPS well-known URL 资源文件。 比如下命令获取证书(不安装):
    $./certbot certonly --standalone --email 58472399@qq.com -d quqianzhao.top -d www.quqianzhao.top
    

    此处需要注意:

  • (1)执行此命令必须使用 root用户获得文件夹的权限
  • (2)域名能访问并且有绑定的公网IP
  • (3)必须在此域名绑定的服务器上运行
  • (4)会使用80断端口,如果nginx监听80端口,把nginx先关掉(nginx -s stop) 命令采用内置web server来验证给出的域名,并在/etc/letsencrypt/live/quqianzhao.top下生成此域名证书(不安装),目录下有4个文件就是生成的密钥证书文件。
  • cert.pem - Apache服务器端证书
  • chain.pem - Apache根证书和中继证书
  • fullchain.pem - Nginx所需要ssl_certificate文件
  • privkey.pem - 安全证书KEY文件 如下命令获取证书并安装(通过nginx验证,并修改nginx配置):
    $./certbot --nginx --email 58472399@qq.com -d quqianzhao.top -d www.quqianzhao.top
    

    此命令采用ngnix服务器验证并在/etc/nginx/site-enable/目录下生成default的配置文件(会自动导入nginx配置),注意此时要保持nginx运行。有时候服务器响应慢执行失败可以多试几次。 在执行之前可以用certbot delete -d quqianzhao.top删除已经安装的证书
    如果配置文件有问题,可以重新安装nginx:

    $ sudo apt-get --purge remove nginx
    $ sudo apt-get --purge remove nginx-common
    $ sudo apt-get install nginx
    

6.获取和安装通配符证书

  对于个人用户来说,主机并不是太多,使用单域名证书(证书仅仅包含一个主机)也没什么问题,但是对于大公司来子域名非常多,而且过一段时间可能就要使用一个新的主机。注册域也非常多。Let’s Encrypt 通配符证书通配符证书就是证书中可以包含一个通配符,比如.example.com、.example.cn,这样大型企业也可以使用通配符证书了,一张证书可以防护更多的主机了。这个功能可以说非常重要,从功能上看 Let’s Encrypt和传统CA机构没有什么区别了。
而申请通配符证书,只能使用 dns-01 的方式进行验证
申请证书命令:

./certbot certonly  -d *.newyingyong.cn --manual --preferred-challenges dns --server https://acme-v02.api.letsencrypt.org/directory

介绍下相关参数:

  • certonly,表示安装模式,Certbot 有安装模式和验证模式两种类型的插件。
  • –manual  表示手动安装插件,Certbot 有很多插件,不同的插件都可以申请证书,用户可以根据需要自行选择
  • -d 为那些主机申请证书,如果是通配符,输入 *.newyingyong.cn(可以替换为你自己的域名)
  • –preferred-challenges dns,使用 DNS 方式校验域名所有权
  • –server,Let’s Encrypt ACME v2 版本使用的服务器,不同于 v1 版本,需要显示指定 安装过程中,有两个交互式的提示:是否同意 Let’s Encrypt协议要求询问是否对域名和机器(IP)进行绑定确认同意才能继续。继续查看命令行的输出,非常关键:
    -------------------------------------------------------------------------------Please deploy a DNS TXT record under the 
    name_acme-challenge.newyingyong.cnwiththe following value:2_8KBE_jXH8nYZ2unEViIbW52LhIqxkg6i9mcwsRvhQBefore continuing, verify the
     recordisdeployed.-------------------------------------------------------------------------------Press Enter to ContinueWaitingforverification...Cleaning up challenges
    

    要求配置 DNS TXT 记录,从而校验域名所有权,也就是判断证书申请者是否有域名的所有权。上面输出要求给 _acme-challenge.newyingyong.cn  配置一条 TXT 记录,在没有确认 TXT 记录生效之前不要回车执行。然后进入你的DNS的提供方网站,增加txt记录,值为:2_8KBE_jXH8nYZ2unEViIbW52LhIqxkg6i9mcwsRvhQ
    然后输入下列命令确认 TXT 记录是否生效:

    $ sudo apt-get install dnsutils $debian 安装dig工具
    $ dig -t txt  _acme-challenge.newyingyong.cn @8.8.8.8
    

    确认生效后,回车执行。恭喜您,证书申请成功,证书和密钥保存在下列目录:/etc/letsencrypt/live/newyingyong.cn
    注意:之前设置了一个CNAME域名,执行后会发生错误:Detail: CAA record for *.xxx.xxx prevents issuance的错误提示,删除CNAME记录就可以了。
    最后校验证书信息,输入如下命令:

    openssl x509 -in /etc/letsencrypt/liev/newyingyong.cn/cert1.pem -noout -text
    

    关键输出如下:X509v3SubjectAlternativeName:DNS:*.newyingyong.cn完美,证书包含了扩展的值就是 *.newyingyong.cn
    openssl的使用可以参考openssl用法详解
    修改nginx配置文件(/etc/nginx/nginx.conf)。
    最后将域名配置成https访问

    server {
          listen 443 ssl http2;
          listen [::]:443 ssl http2 ;
          server_name maomao.run;
    
          ssl on;
          ssl_certificate /etc/letsencrypt/live/newyingyong.cn/fullchain.pem;
          ssl_certificate_key /etc/letsencrypt/live/newyingyong.cn/privkey.pem;
          ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
          ssl_session_cache shared:SSL:1m;
          ssl_session_timeout 10m;
          ssl_ciphers HIGH:!aNULL:!MD5;
          ssl_prefer_server_ciphers on;
    
          location / {
              proxy_pass http://127.0.0.1:8001;
          }
    }
    

    将80端口重定向到443端口。用户无论如何输入域名,都将使用https访问。

    server {
          listen 80;
          listen [::]:80;
          server_name maomao.run;
          rewrite ^(.*)$ https://${server_name}$1 permanent;
    }
    

    设置后重启nginx,可以在SSL Labs输入域名进行验证测试。

7.证书的自动更新

  证书有效期只有三个月,所以更新域名非常关键。更新操作其实和初次申请差不多,只是命令不同,参数都一样。所以智能的 Certbot 在初次申请完成以后就把所有参数都保存下来,方便更新的时候使用。更新的参数保存在 /etc/letsencrypt/renewal/newyingyong.cn。 既然参数都还在,那更新证书只需要一条简单的命令就可以了:

$ sudo certbot renew

Linux 的定时任务有两种方法:cron 和 systemd/timers,任何一种都能实现定时的更新任务,可爱的 Certbot 两种都设置了,我们分别看看。
*cron

$ vim /etc/cron.d/certbot

打开文件可以看到 Certbot 设定的任务记录,如果使用Timers方式,则将这条规则注释到。 *Timers Certbot 在 /lib/systemd/system/ 下生成了两个文件:certbot.service 和 certbot.timer,一个是服务,一个是定时器。 certbot.service:

[Unit]
Description=Certbot
Documentation=file:///usr/share/doc/python-certbot-doc/html/index.html
Documentation=https://letsencrypt.readthedocs.io/en/latest/
[Service]
Type=oneshot
ExecStart=/usr/bin/certbot -q renew
PrivateTmp=true

可以看到也是运行 certbot -q renew 命令。
certbot.timer:

[Unit]
Description=Run certbot twice daily

[Timer]
OnCalendar=*-*-* 00,12:00:00
RandomizedDelaySec=43200
Persistent=true

[Install]
WantedBy=timers.target

每天 0 点和 12 点激活 certbot.service。其实不需要这么频繁的更新证书,而且在更新证书的时候我们加了钩子来关闭和开启 Nginx 服务,所以最好是在凌晨没人访问网站的时候更新证书,我们稍微修改一下:

[Unit]
Description=Run certbot every 05:00

[Timer]
OnCalendar=*-*-* 05:00:00
Persistent=true

[Install]
WantedBy=timers.target

保存修改以后需要重启定时器:

$ systemctl daemon-reload
$ systemctl restart certbot.timer

至此就算大功告成了!

参考1:certbot User Guide
参考2:https://www.jianshu.com/p/da6803c5ab57
参考3:理解服务器证书 CA&SSL
参考4:在CentOS7上使用Certbot申请Wildcard证书
参考5:Nginx 实现 HTTPS(基于 Let’s Encrypt 的免费证书)