如何做与openstack对接的用户验证

可以在openstack horizon中加一个文件夹Security,里面按照horizon的要求放置好子目录和文件。


nsfocus@controller:/usr/src/horizon$ ls
build  HACKING.rst  horizon.egg-info  Makefile   MANIFEST.in            openstack_dashboard  requirements.txt  security   setup.py  test-requirements.txt  tox.ini
doc    horizon      LICENSE           manage.py  openstack-common.conf  README.rst           run_tests.sh      setup.cfg  static    tools
nsfocus@controller:/usr/src/horizon$ cd security/
nsfocus@controller:/usr/src/horizon/security$ ls
ads     appmanager  byod          dashboard.pyc  flowviewer  __init__.py   knowledge       misc       models.pyc    static     waf
adsapp  assetcheck  dashboard.py  devicemanager  ids         __init__.pyc  liveprotection  models.py  moduleviewer  templates  webprotection
nsfocus@controller:/usr/src/horizon/security$ vi liveprotection/
__init__.py   __init__.pyc  panel.py      panel.pyc     templates/    urls.py       urls.pyc      views.py      views.pyc
查看liveprotection/view.py
from django.core.urlresolvers import reverse_lazy # noqa 
from django.utils.translation import ugettext_lazy as _ # noqa 
from horizon import tables 
from horizon import exceptions 
from openstack_dashboard import api 
from django.http import HttpResponse 
from django.template import RequestContext, loader 
from horizon import forms def index(request): template_name = 'security/liveprotection/index.html' 
template = loader.get_template(template_name) context = RequestContext(request, { 'target': 'http://liveprotection.research.intra.nsfocus.com:8000/?username=%s&token=%s'%(request.user,request.user.token.id), }) 
return HttpResponse(template.render(context))

在liveprotection页面中,处理函数为:

def index(request):
    print request.GET
    error = None
    username = auth.verify(request)
    if username is None:
        error = "You are not logined in"
    return render_to_response("routers.html")

from datetime import datetime
import time
import client

import iso8601
def parse_isotime(timestr):
    """Parse time from ISO 8601 format."""
    try:
        return iso8601.parse_date(timestr)
    except iso8601.ParseError as e:
        raise ValueError(six.text_type(e))
    except TypeError as e:
        raise ValueError(six.text_type(e))

def valid_token(expires):
    #return datetime.now() > time.replace(tzinfo=None)
    now = time.time()
    return now < expires

def verify(request):

    token = None
    expire = None
    username = None
    verified_username = None

    if request.session.has_key("token"):
        token = request.session["token"]
    if request.session.has_key("expires"):
        expires = request.session["expires"]
    if request.session.has_key("username"):
        username = request.session["username"]

    if not token is None :
        if valid_token(expires):
            return username
    print 'session :%s' %request.session
    print 'Get :%s'%request.GET
    token =request.GET.get('token')
    username =request.GET.get('username')
    print 'username :%s'%username
    if token is None or username is None:
        return None
    headers = {
        "X-Auth-Token": token,
        }
    code, data = client.http_request(url="http://192.168.19.1:35357/v2.0"+"/tokens/"+token+"?belongsTo", headers=headers)
    print '__________________________++++++++++++++++++++++++_____________________'
    print data
    if code != "200":
        valid = 0
        print "token invalid: return code:%s" % code
    else:
        expires_dt = parse_isotime(data["access"]["token"]["expires"])
        expires_dt = expires_dt.replace(tzinfo=None)
        expires = long(time.mktime(expires_dt.timetuple()))

        verified_username = data["access"]["user"]["username"]
        if valid_token(expires):
            valid = 1
        else:
            valid = 0

    print 'valid :%d'%valid
    print 'token :%s'%token
    if valid:
        request.session["username"] = verified_username
        request.session["token"] = token
        request.session["expires"] = expires
    return verified_username

Django国际化

本文在django 1.7验证过

1 在项目settings.py中添加两行(+表示添加行):


+ LOCALE_PATHS = (BASE_DIR+ "/locale",)

MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.locale.LocaleMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',

2 在views.py中标记要翻译的字符串,格式为{% trans ‘字符串’%} 如

{% load i18n %}


  • {% trans 'User' %}
  • 3 建立locale目录结构

    $ mkdir locale/en -p
    $ mkdir locale/zh_CN -p
    $ python manage.py makemessages -a
    processing locale zh_CN
    processing locale en

    此时会新建./locale/{lang}/LC_MESSAGES/django.po文件,lang表示语言,中文为zh_CN。
    其中django.po文件包含了django目录中所有需要翻译的字符串。如

    #, fuzzy
    msgid ""
    msgstr ""
    "Project-Id-Version: PACKAGE VERSION\n"
    "Report-Msgid-Bugs-To: \n"
    "POT-Creation-Date: 2016-04-07 10:33+0800\n"
    "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
    "Last-Translator: FULL NAME \n"
    "Language-Team: LANGUAGE \n"
    "Language: \n"
    "MIME-Version: 1.0\n"
    "Content-Type: text/plain; charset=UTF-8\n"
    "Content-Transfer-Encoding: 8bit\n"

    #: templates/base.html:138
    msgid "User"
    msgstr ""

    4 翻译相关语言下的django.po。对于每个msgid的字符串,修改对应的翻译语句,如上例为

    #: templates/base.html:138
    msgid "User"
    msgstr ""

    5 编译:
    $ python manage.py compilemessages
    processing file django.po in /usr/src/appstoreserver/appstore/locale/zh_CN/LC_MESSAGES
    processing file django.po in /usr/src/appstoreserver/appstore/locale/en/LC_MESSAGES

    6 done

    创新沙盒:“软件定义安全”不再是实验室产物

    自从著名咨询机构Gartner在《The Impact of Software-Defined Data Centers on Information Security》 一文中提出软件定义安全(Software Defined Security,SDS)的概念后,软件定义与安全的结合已成为业界的前沿发展热点。背后的原因很直观:软件定义安全强调了安全控制平面与数据平面分离,从而在控制平面上可灵活调整应用策略,快速变更安全业务。

    在安全可被软件定义后,新的安全业务可在企业网络中可新上线,特别是在数据中心中,可实现计算、存储、网络和安全的弹性控制,实现软件定义的数据中心SDDC。正是因为这些优秀的特性,解决了企业客户长期面临的安全管理和运营“痛点”,软件定义安全自从开始就引起了学术界和工业界极大的关注。

    各大厂商都开始做相关的研究和研发工作,RSA大会一直是厂商们展现自己最新工作的舞台。如Check Point在RSA 2014大会上宣布推出软件定义防护(Software Defined Protection,SDP)革新性安全架构,可在当今日新月异的IT和威胁环境中为企业提供虚拟化的边界防护。赛门铁克也在RSA 2015提出使用软件定义网络技术对APT攻击进行取证的话题也提供了一种安全事件事后快速分析的新思路 。

    而RSA大会开到了第25个年头时,我们惊喜地发现更多的公司在展示在软件定义安全的领域的工作,特别是在体现创新的Innovation Sandbox(创新沙盒)竞赛中,10家经过专业评审的公司,居然有3家与这个话题有关,分别在不同的方面做出了开创性的工作。
    如Versa Networks公司,强调在软件定义广域网(SD-WAN)和分支(Branch)网络的环境中,通过虚拟化网络功能(VNF)技术,将各种各样异构的网络功能编程通用的组件,可快速在相应的网络中部署,大大减少了企业部署相应业务的开销,提高了整个过程的敏捷程度。

    Skyport Systems公司同样也是为企业提供高效的安全计算基础设施,但按照传统建立边界思维,攻击者在进入系统内部后就容易进一步攻击内部其他重要资源。该公司的逻辑是,所有的资源都是零信任,这样即便内部某资源被攻破,那么从该点作为跳板进一步攻击也是困难的。那么这里就涉及到软件定义的访问控制,例如如何做到“零信任”条件下各处的访问控制策略快速调整。该公司在B轮融资中获得3000万美元。

    再如Phantom Cyber公司认为在大量出现攻击的场景下,花费大量的人力去发现解决问题已不太现实。与前两个公司不同,Phantom Cyber从应用层入手,构建自动化、可编排的安全应用体系。它支持多种主流的数据分析平台,可利用较为高层的脚本实现安全运维自动化。

    当然除了这些初创公司,还有很多公司也在基于自身产品做相关的工作。如在29日的Session环节,VMWare的安全产品部门SVP Tom Corn就演示了在NSX的环境中,如何可按需定义微分段(MicroSegmentation),并对任意APP间快速添加加密处理。厂商展示区域,Catbird公司的软件定义安全架构 通过微分区(Micro-Segmentation)在虚拟环境中划分不同的区域,并通过编排将安全策略下发给多种类型的安全设备,并作用在区域级别或虚拟机级别。这些工作都体现了各家在成熟产品线通过软件定义做了很多延展性的工作。

    绿盟科技自2013年开始研究SDN和软件定义安全,研发了包括软件定义的抗DDoS、流量异常检测和Web安全等原型系统,并在2015年发布了软件定义安全的白皮书,探讨在该领域的进展。

    创新沙盒中10个产品中出现了三个能体现SDS的产品,笔者认为其背后的原因有几个:其一,作为软件定义安全的支撑技术,如VNF/NFV、SDN方案,在国外已经有一些成熟的应用,如NSX已经代替Vsphere成为VMWare成长最快的产品,Cisco的ACI方案也与很多安全厂商有合作;其二,企业的高效安全运营需求,直接催生了安全编排这些应用层面的创新;其三,也是最重要的,出于企业对降低成本的天然需求,软件定义的理念转换为实际产品的动力十足。

    RSA大会的创新沙盒一直是硅谷安全行业的风向标,今年的沙盒竞赛体现了软件定义安全确实不只是一些实验室的原型系统,一些初创企业已经开始将其作为重点,根据企业在安全运营方面出现的存在各种问题,有针对性的提出了自己的解决方案。我们有理由做出判断,软件定义安全恐怕离真正的产品化和商用已经不远了。

    UPDATE:创新沙盒的获胜者已公布,即Phantom。除去其他领域的竞争者,Phantom能在上述其他两家公司脱颖而出,笔者认为主要是因为它从客户的业务作为切入点,强调了业务层面的编排,且一开始便是开放架构设计,可支持第三方的VNF组成服务链。给客户安全运维提供高效手段,降低运维成本的同时,打消了客户被厂商锁定(Vendor Lock-in)的顾虑,从而拥有更好的商业模式(事实上,该公司在种子轮融资中获得了270万美元)。

    ssh翻墙简单说明

    1 购买vps。设置用户名user和密码pass
    2 新建ssh profile。以xshell为例,新建一个profile,输入vps的ip、port、user和pass
    3 建立隧道。在profile属性->连接->SSH->隧道->添加,类型为Dynamic,侦听端口1080。同时启用转发x11(不知是否需要),保存profile。

    vps-tunnel

    4 连接vps,默认情况下连接成功。存在两 会等时候,可能被封掉…
    5 安装浏览器扩展,如firefox的foxyproxy
    6 以foxyproxy为例,新建一个proxy,URL匹配模式为要翻墙的url,如*google*,代理服务器细节中选择手动配置代理服务器,主机为127.0.0.1,端口1080,socks代理。确认
    foxyproxy

    7 启用该proxy,即可正常翻墙

    Ubuntu下openvswitch添加tcp监听

    本来这不应该是什么值得写的内容,不过有些需要hack一下,所以还是记一下为好。

    Ubuntu下面的openvswitch默认是开启punix(被动监听unix)进行管理ovsdb的,那么就不能查看和控制远程主机的ovsdb,openvswitch其实还可以开启ptcp或pssl模式,就可以打开远程的访问(当然存在风险,需假设控制网络是可信的)。

    不过配置文件(/etc/default/openvswitch-switch,/etc/init.d/openvswitch-switch)都没有该配置项,后来找了找,相关脚本在/usr/share/openvswitch/。(dpkg -L openvswitch-switch)

    其中启动ovsdb-server的脚本在/usr/share/openvswitch/scripts/ovs-ctl里面。

    187         set "$@" -vconsole:emer -vsyslog:err -vfile:info
    188         set "$@" --remote=punix:"$DB_SOCK" --remote=ptcp:6640
    

    这里面添加一个tcp端口即可。

    p.s. 如果去掉punix应该不行,因为ovs-vsctl默认使用了punix的方式,所以这里使用了同时启用punix和ptcp模式。

    另,这个py-ovsdb-client项目还不错,封装了多种ovsdb的api,直接在python中调用即可。

    在宿主机中用ovs连接内网vm

    经常需要做实验验证VM的性能,那么实验环境为:H是一个物理主机(物理网卡网段为192.168.19.0/24),H中启动一台VM,VM的一个管理口eth-M配有私网ip(网段100.100.100.0/24),并连接到一个OVS桥br-test上。现在需要在H中直接连接VM。

    一个直观的想法是直接ping VM,但VM与宿主机的主IP不是一个网段,所以不能直接通信,需要一个网关GW。那就直接在br-test上新建一个类型为internal的接口做网关。

    ovs-vsctl add-port br-test gw -- set Interface gw type=internal
    ifconfig gw 100.100.100.1
    iptables -t nat -I POSTROUTING -j SNAT  -s 192.168.19.0/24 --to-source 100.100.100.1
    iptables -t nat -I POSTROUTING -j SNAT  -s 100.100.100.0/24 --to-source 192.168.19.13
    

    并使用iptables做NAT,但发现还是ping不通。我印象中iptables的访问规则与openvswitch不兼容,不知道nat是不是也受到影响。

    既然如此,gw的路由应该不能放在openvswitch中,只能在namespace或原linux系统中。换一个思路,不将网关直接放在br-test上,而是使用veth对的形式:

     ip link add gw type veth peer name gw-o
    ovs-vsctl add-port br-test gw
    ifconfig gw 100.100.100.1
    iptables -t nat -I POSTROUTING -j SNAT  -s 192.168.19.0/24 --to-source 100.100.100.100
    iptables -t nat -I POSTROUTING -j SNAT  -s 100.100.100.0/24 --to-source 192.168.19.13
    

    那么数据从gw传到了gw-o,此时可以正常路由。

    使用cloud-init实现虚拟机信息管理

    为什么要用cloud-init

    不同种类的设备VM启动总是一件非常麻烦的事情,例如安全设备有WAF、IPS等,每种设备的网络接口、启动脚本互不一样,即便同一种设备,其主机名、网络地址等也不一样。那么如何对这些VM启动过程进行管理,并完成所有数据的配置呢?

    在这之前,我的实习生是怎么做的:将一台VM的管理口网络地址设置为192.168.2.100,然后每次启动实例之后定时访问http://192.168.2.100/somepath,当成功访问这个页面之后,使用REST接口配置该机器的IP地址为所需的新地址(如200.0.0.2);这个时候网络会短暂不同,然后在访问http://200.0.0.2/somepath,当成功访问之后,接下来配置各种值。 整个过程比较麻烦,所有的配置都需要实现REST接口,无法做到自定义启动脚本的更新;最不可接受的是,这个过程是串行的,当要启动100个VM时,只能一个VM一个VM顺序启动,否则两个VM都有同一个地址(192.168.2.100),那么网络访问就可能出现问题了。 不过受到各种Stack管理虚拟机用到cloud-init的启发,我认为我们也可以使用这套工具实现上述过程的。

    什么是cloud-init

    cloud-init(简称ci)在AWS、Openstack和Cloudstack上都有使用,所以应该算是事实上的云主机元数据管理标准。那么问题来了,google相关的文档,发现中文这方面几乎没有,Stacker你们再搞虾米呢?当然话说回来英文的资料除了官网外几乎也没有什么,我花了近一周的时间才弄明白了。

    首先要明确的是cloud-init在工作之前,VM是从DHCP服务器获取到了IP,所有DHCP发现不是cloud-init的事情。当你在Openstack中用ubuntu cloud VM启动卡在cloud-init界面时,多半是因为DHCP还没获取IP,而不是cloud-init本身的问题。那么cloud-init主要走什么呢?它向一台数据服务器获取元数据(meta data)和用户数据(user data),前者是指VM的必要信息,如主机名、网络地址等;后者是系统或用户需要的数据和文件,如用户组信息、启动脚本等。当cloud-init获取这些信息后,开始使用一些模块对数据进行处理,如新建用户、启动脚本等。

    cloud-init工作原理

    首先,数据服务器开启HTTP服务,cloud-init会向数据服务器发送请求,确认数据源模块,依次获取版本、数据类型和具体数据内容信息。

    确认数据源模块

    cloud-init会查找/etc/cloud/cloud.cfg.d/90_dpkg.cfg中的datasource_list变量,依次使用其中的数据源模块,选择一个可用的数据源模块。如我的配置文件中:datasource_list: [ Nsfocus, NoCloud, AltCloud, CloudStack, ConfigDrive, Ec2, MAAS, OVF, None ],那么ci首先调用$PYTHON_HOME/dist-packages/cloudinit/sources/DataSourceNsfocus.py中类DataSourceNsfocus的get_data函数,当且仅当访问链接DEF_MD_URL为正常时,这个数据源被认为是OK的。

    在我的实践中,CloudStack的DEF_MD_URL为DHCP的服务器ip,而Openstack和AWS则为一个常值169.254.169.254,然后在宿主机的中做一个iptables重定向,这样就到了我们的服务器监听端口8807:

    $ sudo ip netns exec ns-router iptables -L -nvx -t nat
    Chain PREROUTING (policy ACCEPT 169850 packets, 21565088 bytes)
        pkts      bytes target     prot opt in     out     source               destination         
          47     2820 REDIRECT   tcp  --  *      *       0.0.0.0/0            169.254.169.254      tcp dpt:80 redir ports 8807
    $ sudo ip netns exec ns-router iptables -L -nvx
    Chain INPUT (policy ACCEPT 97027 packets, 8636621 bytes)
        pkts      bytes target     prot opt in     out     source               destination         
           0        0 ACCEPT     tcp  --  *      *       0.0.0.0/0            127.0.0.1            tcp dpt:8807
    

    一些系统假设

    需要说明的是,虽然每个数据源访问的入口都是get_data,但每个数据服务的格式和位置是不一样的,元数据可能在/nsfocus/latest/metadata/,也可能在/latest/metadata.json,也就是说数据源模块根据自己系统的规定,访问相应的数据,并根据ci的规定,指定如何将这些数据与ci接下来的处理模块对应上。

    那么我们的数据访问地址是这样的:

    --namespace
               |
               |------version
                            |
                            |---------meta_data.json
                            |---------meta_data
                            |                  |---------public-hostname
                            |                  |---------network_config
                            |
                            |---------user_data
    

    其中,namespace为nsfocus,meta_data.json是一个json文件,里面包含所有元数据。
    其次,我们的数据服务器IP为111.0.0.2

    获得元数据

    因为获取是HTTP的形式,所以以curl为例说明下面过程:

    $ curl http://111.0.0.2/nsfocus
    1.0
    latest
    $ curl http://111.0.0.2/nsfocus/latest
    meta_data
    user_data
    meta_data.json
    $ curl http://111.0.0.2/nsfocus/latest/meta_data
    public-hostname
    local-ipv4
    network_config
    ...
    $ curl http://111.0.0.2/nsfocus/latest/meta_data/local-ipv4
    111.0.0.11
    $ curl http://111.0.0.2/nsfocus/latest/meta_data.json
    {"files": {}, "public_keys": {"controller": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCxtEfzf8I0jA7IHDRHJtDq3nTcTXAWgFYEsAV0i7WU6v8gvFr/R+DTvkVdFGgbM/qhVNWUehmPicENac6xldbL5ov6J7c8Y+UytPwJCt13IzDHXaL1BxVYUV6dpe6SYGYohNQ2KZYkG/95NzjxI1Max5DDvU8mbpEz/KyphowseburknQTkOTEigJ7CKM4G1eGVhBHKRHXbNsoPZwJnqvIHIpDcwGaj+OgVGF+o3ytH4twrwNwUFiWrUaxo9j2uRTSejYRh1eC9KOYXTnXInzV1xCVHYs/x+eIzav+2oM8hgR3xr1efgSU2sMzXrp+mJAPzHaAyAat+s7AMDu9tKrd marvel@marvel-ThinkPad-X230"}, "hostname": "waf-ba-0001", "id": "waf-ba-0001", "network_config": {"content_path": "latest/meta_data/network_config"}}
    

    这个meta_data.json是我们参考Openstack的标准,自己实现的。当获得meta_data.json后,DataSourceNsfocus解析里面的字段,填入自己的数据结构中,如放入DataSourceNsfocus的result字典中。

                if found and translator:
                    try:
                        data = translator(data)
                    except Exception as e:
                        raise BrokenMetadata("Failed to process "
                                             "path %s: %s" % (path, e)) 
                if found:
                    results[name] = data
    

    这样,如hostname就存为self.result[‘meta’][‘hostname’]。

    供其他处理模块使用的获取元数据函数

    在上一阶段,元数据的提供、获取和存储都是很自由的,那么这些数据怎么被使用,例如hostname怎么设置呢?那就需要根据ci的标准实现一些接口,如设置hostname就需要我们实现DataSourceNsfocus的get_hostname方法:

        def get_hostname(self, fqdn=False):
            return self.metadata.get("hostname")
    

    这样,其他模块如set_hostname和update_hostname就会使用这个方法正确设置主机名了。如果你想设置其他数据,可参考cloud-init数据源参考的介绍。了解还有哪些处理模块,可读一下/etc/cloud/cloud.cfg文件。

    至此,一些VM所需的常用配置已经搞定,那么如果我们想做一些流程方面的自动下发和运行该怎么做呢?则需要设置一下user_data。

    获取用户数据

    用户数据包括几类:

    • 配置文件(Cloud Config Data),类型为Content-Type: text/cloud-config,系统配置文件,如管理用户等,与/etc/cloud下的cloud.cfg最后合并配置项,更多的配置细节参考 配置样例
    • 启动任务(Upstart Job),类型为Content-Type: text/upstart-job,建立Upstart的服务
    • 用户数据脚本(User-Data Script),类型为Content-Type: text/x-shellscript,用户自定义的脚本,在启动时执行
    • 包含文件(Include File),类型为Content-Type: text/x-include-url,该文件内容是一个链接,这个链接的内容是一个文件,
    • (Cloud Boothook),类型为Content-Type: text/cloud-boothook,
    • 压缩内容( Gzip Compressed Content),
    • 处理句柄(Part Handler),类型为Content-Type: text/part-handler,内容为python脚本,根据用户数据文件的类型做相应的处理
    • 多部分存档(Mime Multi Part archive),当客户端需要下载多个上述用户数据文件时,可用Mime编码为Mime Multi Part archive一次下载

    实例

    我在data目录下面建立三个文件:

    cloud.config

    groups:
      - nsfocus: [nsfocus]
    users:
      - default
      - name: nsfocus
        lock-passwd: false 
        sudo: ALL=(ALL) NOPASSWD:ALL
    system_info:
      default_user:
        name: nsfocus
        groups: [nsfocus,sudo]
    bootcmd:
      - echo "#HOSTS\n127.0.0.1    localhost\n::1    localhost ip6-localhost\nff02::1    ip6-allnodes\nff03::1    ip6-allrouters\n#ip#    #host#" > /etc/hosts
    runcmd:
      - [echo, "RUNCMD: welcome to nsfocus-------------------------------------------"]
    final_message: "Welcome to NSFOCUS SECURITY #type#====================================="
    

    这是一个cloud-config文件,内容表示新建一个nsfocus的用户,归于nsfocus和sudo组,在启动时运行bootcmd的命令更新hosts,启动最后输出final_message。

    nsfocus-init.script

    $ cat nsfocus-init.script 
    #!/bin/bash
    echo "this is a startup script from nsfocus" 
    echo "this is a startup script from nsfocus" >> /tmp/nsfocus-init-script
    

    这是一个测试脚本,在系统启动时会被调用

    nsfocus-init.upstart

    $ cat nsfocus-init.upstart 
    description "a nsfocus upstart job"
    
    start on cloud-config
    console output
    task
    script
    echo "====BEGIN======="
    echo "HELLO From nsfocus Upstart Job, $UPSTART_JOB"
    echo "HELLO From nsfocus Upstart Job, $UPSTART_JOB" >> /tmp/hello
    echo "=====END========"
    end script
    

    这是一个测试访问,在系统启动时会被启动

    HTTP服务器收到/nsfocus/latest/user_data时,作如下处理:

    from email.mime.multipart import MIMEMultipart
    from email.mime.text import MIMEText
    def encode_mime(fps):
        combined_message = MIMEMultipart()
        for fn, patterns in fps:
            print fn
            (filename, format_type) = fn.split(":", 1)
            print filename
            print "---"
            with open(filename) as fh: 
                contents = fh.read()
            for (p, v) in patterns:
                contents = contents.replace(p, v)
        
            sub_message = MIMEText(contents, format_type, sys.getdefaultencoding())
            sub_message.add_header('Content-Disposition', 'attachment; filename="%s"' % (filename[filename.rindex("/")+1:]))
            combined_message.attach(sub_message)
        return str(combined_message)
    
    #main process
    #....blablabla
          if subtype == "user_data":
                if len(arr) == 0:
                    res = encode_mime([
                        ("./data/nsfocus-init.upstart:upstart-job",[]),
                        ("./data/nsfocus-init.script:x-shellscript",[]),
                        ("./data/cloud.config:cloud-config",[('#ip#', device.management_ip), ('#host#',device.id), ('#type#', device.type)])])
                    return self.gen_resp(200, res)
    

    虚拟机启动之后,服务器收到请求,返回下面的内容:

    From nobody Fri Dec 26 15:34:36 2014
    Content-Type: multipart/mixed; boundary="===============5883341837158849895=="
    MIME-Version: 1.0
    
    --===============5883341837158849895==
    MIME-Version: 1.0
    Content-Type: text/upstart-job; charset="us-ascii"
    Content-Transfer-Encoding: 7bit
    Content-Disposition: attachment; filename="nsfocus-init.upstart"
    
    description "a nsfocus upstart job"
    
    start on cloud-config
    console output
    task
    script
    echo "====BEGIN======="
    echo "HELLO From nsfocus Upstart Job, $UPSTART_JOB"
    echo "HELLO From nsfocus Upstart Job, $UPSTART_JOB" >> /tmp/hello
    echo "=====END========"
    end script
    
    --===============5883341837158849895==
    MIME-Version: 1.0
    Content-Type: text/x-shellscript; charset="us-ascii"
    Content-Transfer-Encoding: 7bit
    Content-Disposition: attachment; filename="nsfocus-init.script"
    
    #!/bin/bash
    echo "this is a startup script from nsfocus" 
    echo "this is a startup script from nsfocus" >> /tmp/nsfocus-init-script
    
    --===============5883341837158849895==
    MIME-Version: 1.0
    Content-Type: text/cloud-config; charset="us-ascii"
    Content-Transfer-Encoding: 7bit
    Content-Disposition: attachment; filename="cloud.config"
    
    groups:
      - nsfocus: [nsfocus]
      - dev
    users:
      - default
      - name: nsfocus
        lock-passwd: false 
        sudo: ALL=(ALL) NOPASSWD:ALL
    system_info:
      default_user:
        name: nsfocus
        groups: [nsfocus,sudo]
    bootcmd:
      - echo "#HOSTS\n127.0.0.1    localhost\n::1    localhost ip6-localhost\nff02::1    ip6-allnodes\nff03::1    ip6-allrouters\n111.0.0.12    waf-ba-0001" > /etc/hosts
    runcmd:
      - [echo, "RUNCMD: welcome to nsfocus-------------------------------------------"]
    
    final_message: "Welcome to NSFOCUS SECURITY waf====================================="
    
    --===============5883341837158849895==--
    

    VM启动界面打印如下信息,且主机名变成了我们预定的值,说明确实获取meta-data和user-data成功,脚本运行也成功了。不过要说明一点,upstart在Ubuntu上没问题,但Debian没通过,可能当前阶段Debian的启动机制还有一些区别,所以还是使用bootcmd或启动脚本的方式启动。

    Ubuntu登陆页面

    参考文献

    cloud-init数据源参考 http://cloudinit.readthedocs.org/en/latest/topics/datasources.html
    dnsmasq参考 http://www.thekelleys.org.uk/dnsmasq/docs/dnsmasq-man.html
    更多样例 https://github.com/number5/cloud-init/blob/master/doc/examples/

    Openstack节点网卡连Cisco交换机出现环路的处理

    前一阵子部署了两台Openstack的计算节点,奇怪的是每隔一段时间连管理网的网卡eth0灯不亮,交换机上对应的19端口灯变橙色,登陆上去一看,eth0显示:

    2: eth0:  mtu 1500 qdisc mq master ovs−system state DOWN mode DEFAULT group default qlen 1000
    

    运行ip link set eth0 up无效。

    登陆交换机,查看端口状态:

    Switch>enable 
    Switch#show interfaces gigabitethernet 0/19 status 
    Port    Name       Status       Vlan       Duplex  Speed Type
    G0/19   err−disabled 100          full   1000 1000BaseSX
    

    可以看到19端口出现问题,继续看日志

    Switch#show logging
    ...
    Mar 30 01:43:39.827: %ETHCNTR−3−LOOP_BACK_DETECTED: Loop−back detected on GigabitEthernet0/19.
    Mar 30 01:43:39.827: %PM−4−ERR_DISABLE: loopback error detected on Gi0/19, putting Gi0/19 in err−disable state
    Mar 30 01:43:40.833: %LINEPROTO−5−UPDOWN: Line protocol on Interface GigabitEthernet0/19, changed state to down
    Mar 30 01:43:41.840: %LINK−3−UPDOWN: Interface GigabitEthernet0/19, changed state to down
    

    发现原来是出现了环路,参考cisco的官方文档,一个可能的原因是交换机定时发送keepalive,这个数据包进入节点后又出来,到了同一个端口,则交换机认为可能出现环路,从而禁用。

    解决办法是禁用keepalive:

    Switch(config)#interface gigabitethernet 0/19 
    Switch(config−if)#no keepalive
    

    当然也可以升级到高版本的IOS,默认会禁用keeplive。

    然后恢复出错的端口:

    Switch(config)#errdisable recovery cause loopback 
    Switch(config)#exit
    Switch#show errdisable recovery
    ...
    Interfaces that will be enabled at the next timeout:  
    Interface       Errdisable reason       Time left(sec)
    −−−−−−−−−       −−−−−−−−−−−−−−−−−       −−−−−−−−−−−−−−
    Gi0/9                   loopback          205
    Gi0/10                  loopback          205
    Gi0/19                  loopback          205
    

    过了300秒后,端口应该恢复正常,此时能够连接上主机了。

    我觉得也可以在节点侧,在连接eth0的ovs桥br0加stp,以观后效

    root@node4:~# ovs−vsctl get bridge br0 stp_enable
    false
    root@node4:~# ovs−vsctl set bridge br0 stp_enable=true
    

    参考文章

    Cisco交换机命令行配置(http://www.cisco.com/c/en/us/td/docs/switches/datacenter/nexus5000/sw/configuration/guide/cli_rel_4_0_1a/CLIConfigurationGuide/sm_syslog.html)

    Cisco交换机端口出错处理(http://www.cisco.com/c/en/us/support/docs/lan-switching/spanning-tree-protocol/69980-errdisable-recovery.html)

    openstack icehouse update errors

    最近将Openstack从havana升级到icehouse,总体问题不大,但是还是有点小问题,花了一点时间。

    1 密钥过期,keystone的密钥提示过期。

    如nova提示:
    2014-04-29 10:52:14.074 27081 WARNING keystoneclient.middleware.auth_token [-] Verify error: Command ‘openssl’ returned non-zero exit status 4

    解决办法是1) 备份/etc/keystone/ssl目录并删除,运行keystone-manage pki_setup –keystone-user keystone –keystone-group keystone,上面的ssl目录会生成,这里的用户名和用户组为linux系统中的keystone对应的用户和组 3) 修改ssl目录属主和权限700.

    neutron也可能由此提示,解决办法是删除/var/lib/neutron/keystone-signing目录,并重启neutron-server。

    此外,可能还需要删除/tmp下的keystone-signing-xxxxx目录。

    总体而言,上面的错误表面现象为keystone 401 Unauthorized的错误,但是解决问题需要定位到相应模块,用debug=true和verbose=true从调试信息中找出问题。

    如nova list返回401的原因有可能是neutron的证书问题,所以大家在调错是一定要仔细。

    2 数据库升级,这个也不算是问题,只是每个模块升级的时候几乎都要升级数据库,如 keystone-manage db_sync等。

    Openstack中构造伪造源地址攻击

    前一篇是将防御,这一篇简单说一下openstack下如何发动伪造源ip的DoS攻击
    1 修改iptables和ebtables的设定,例如攻击vm为100.0.0.17,那么查询相应的iptables规则:

    neutron port-list |grep 17
    | 091f121f-8cd7-4a02-b1d8-53866ab25d3d |      | fa:16:3e:f7:9d:70 | {"subnet_id": "57b30eb5-e9ee-489f-85ea-77bcaa6249e5", "ip_address": "100.0.0.17"}     |
    

    找到iptables中含有091f121f-8的链:neutron-openvswi-s091f121f-8,neutron-openvswi-i091f121f-8,neutron-openvswi-o091f121f-8,和nova-instance-xxx(与该vm对应的)插入一条优先级最高的ACCEPT链

    找到ebtables中nat表中091f121f-8的链:I-tap091f121f-8c-arp-ip,I-tap091f121f-8c-ipv4-ip,插入一条优先级最高的ACCEPT链

    2 安装ruby,下载pentibox

    3 启动pentibox,选择Network tools->Net DoS Tester -> 任意一种即可发动攻击

    如果第1步中加了规则还是不通,请检查源主机和目的主机的安全组是否允许

    下面的我写的脚本,希望对大家有所帮助

    #!/bin/bash

    ip=$1
    echo $ip
    id=`neutron port-list |grep "$ip"|awk '{print $2}'`
    echo $id
    if [ -z $id ]
    then
    echo "id null"
    exit 1
    fi
    itid=`expr substr $id 1 10`
    echo $itid
    etid=`expr substr $id 1 11`
    echo $etid

    novarule=`iptables -L -nvx|grep $ip|grep nova |awk '{print $3}'`
    echo $novarule
    #clear previous rules
    iptables -D neutron-openvswi-s$itid -j ACCEPT
    iptables -D neutron-openvswi-i$itid -j ACCEPT
    iptables -D neutron-openvswi-o$itid -j ACCEPT
    iptables -D $novarule -j ACCEPT

    ebtables -t nat -I I-tap$etid-arp-ip -j ACCEPT
    ebtables -t nat -I I-tap$etid-ipv4-ip -j ACCEPT

    #create rules
    iptables -I neutron-openvswi-s$itid -j ACCEPT
    iptables -I neutron-openvswi-i$itid -j ACCEPT
    iptables -I neutron-openvswi-o$itid -j ACCEPT
    iptables -I $novarule -j ACCEPT

    ebtables -t nat -I I-tap$etid-arp-ip -j ACCEPT
    ebtables -t nat -I I-tap$etid-ipv4-ip -j ACCEPT