Harbor遷移升級(jí)(從v1到v2)

勢(shì)在必行的計(jì)劃狼荞,前期越周全越好宅粥。

一啊楚,升級(jí)原因

1吠冤,安全漏洞

Harbor 官方倉(cāng)庫(kù)公布了 5 個(gè)漏洞,其中包括 2 個(gè)官方定級(jí)為嚴(yán)重的漏洞(CVE-2019-19025恭理、CVE-2019-19023)拯辙,2個(gè)高危級(jí)別漏洞(CVE-2019-19029、CVE-2019-19026)颜价,1個(gè)中等級(jí)別漏洞(CVE-2019-3990)涯保。

  • CVE-2019-19025:缺少 CSRF 保護(hù)漏洞,Harbor Web界面未實(shí)現(xiàn)針對(duì)跨站點(diǎn)請(qǐng)求偽造(CSRF)的保護(hù)機(jī)制周伦。通過把經(jīng)過身份驗(yàn)證的用戶吸引到事先準(zhǔn)備好的第三方網(wǎng)站夕春,可導(dǎo)致第三方代表經(jīng)過身份驗(yàn)證的用戶或管理員在平臺(tái)上執(zhí)行任意操作。
  • CVE-2019-19023:特權(quán)提升漏洞横辆,該漏洞使普通用戶可以通過API調(diào)用來修改特定用戶的電子郵件地址撇他,從而獲得管理員帳戶特權(quán)。漏洞源于Harbor API沒有對(duì)修改電子郵件地址的API請(qǐng)求進(jìn)行適當(dāng)?shù)臋?quán)限限制狈蚤。
  • CVE-2019-19029:通過用戶組進(jìn)行SQL注入困肩,具有項(xiàng)目管理功能的用戶可以利用SQL注入來從底層數(shù)據(jù)庫(kù)讀取機(jī)密信息或進(jìn)行權(quán)限提升。
  • CVE-2019-19026:通過項(xiàng)目quotas進(jìn)行SQL注入脆侮,Harbor API的quotas部分存在一個(gè)SQL注入漏洞锌畸。經(jīng)過身份驗(yàn)證的管理員可以通過GET參數(shù)發(fā)送特制的SQL有效負(fù)載,從而從數(shù)據(jù)庫(kù)中提取敏感信息靖避。
  • CVE-2019-3990:用戶枚舉漏洞潭枣,該漏洞存在于 “/users” api 中比默,這個(gè)功能應(yīng)該僅限于管理員使用,可是該限制可被繞過盆犁,非管理員用戶(例如通過自我注冊(cè)創(chuàng)建的用戶)可以通過向 /api/users/search發(fā)送 GET請(qǐng)求來列出所有用戶名和用戶ID命咐、確認(rèn)與用戶名關(guān)聯(lián)的電子郵件地址等。

2谐岁,功能升級(jí)

Harbor 的 OCI Artifact 功能醋奠,可以用來存儲(chǔ)、分發(fā)和管理機(jī)器學(xué)習(xí)的模型文件伊佃。

二窜司,升級(jí)流程

harbor-migrate.png

三,升級(jí)步驟

先按v1版的harbor配置航揉,建一個(gè)v2版的harbor(掛載大存儲(chǔ)塞祈,ldap用戶認(rèn)證等)。

1,導(dǎo)出v1版本的項(xiàng)目和鏡像列表

運(yùn)行harbor_v1_images_export.py腳本帅涂,從V1版本harbor中议薪,提取所有的項(xiàng)目列表和鏡像文件列表。
為支持多次運(yùn)行此腳本漠秋,會(huì)導(dǎo)入之前運(yùn)行結(jié)果笙蒙,以縮短再次運(yùn)行的時(shí)間抵屿。
其中庆锦,項(xiàng)目列表為pro.csv文件,鏡像列表為repo_v1.csv文件

"""
用于將老版本的harbor中的Project和repo鏡像提取出來轧葛,保存到文件中搂抒。
admin
2020-11-24
"""
import requests

# 常量定義
harbor_domain_v1 = 'harbor.demo.cn'
username = 'admin'
password = 'xxxx'
pro_file = 'pro.csv'
repo_file = 'repo_v1.csv'


# V1版本,請(qǐng)求API使用session尿扯。
class RequestClient:
    def __init__(self, login_url, username, password):
        self.login_url = login_url
        self.username = username
        self.password = password
        self.session = requests.Session()
        self.login()

    def login(self):
        self.session.post(self.login_url,
                          params={'principal': self.username,
                                  'password': self.password})
        print('login', self.session)


# 將Harbor常用操作包裝成一個(gè)class
class HarborReposV1:
    def __init__(self, harbor_domain, username, password, schema='http'):
        self.schema = schema
        self.harbor_domain = harbor_domain
        self.harbor_url = self.schema + '://' + harbor_domain
        self.harbor_login_url = self.harbor_url + '/login'
        self.harbor_api_url = self.harbor_url + '/api'
        self.harbor_pro_url = self.harbor_api_url + '/projects'
        self.harbor_repos_url = self.harbor_api_url + '/repositories'

        self.username = username
        self.password = password
        # self.client和self.pros_obj在初始化時(shí)就生成好求晶,使用起來更流暢
        self.client = RequestClient(self.harbor_login_url,
                                    self.username,
                                    self.password)
        self.pros_obj = self.__fetch_pros_obj()

    def __fetch_pros_obj(self):
        return self.client.session.get(self.harbor_pro_url).json()

    # 獲取所有的project id
    def fetch_pros_id(self):
        pros_id = list()
        for i in self.pros_obj:
            pros_id.append(i['project_id'])
        return pros_id

    # 根據(jù)project id獲取project名稱及public屬性
    def fetch_pro_name(self, pro_id):
        for i in self.pros_obj:
            if i['project_id'] == pro_id:
                pro_name = i['name']
                pro_public = i['metadata']['public']
        return pro_name, pro_public

    # 根據(jù)project id獲取此project下所有鏡像名稱
    def fetch_repos_name(self, pro_id):
        repos_name = list()
        repos_res = self.client.session.get(self.harbor_repos_url,
                                            params={'project_id': pro_id})
        for repo in repos_res.json():
            repos_name.append(repo['name'])
        return repos_name

    # 根據(jù)鏡像名稱,獲取此鏡像的所有tag
    def fetch_repos(self, repo_name):
        repos = list()
        harbor_tag_url = self.harbor_repos_url + '/' + repo_name + '/tags'
        repos_res = self.client.session.get(harbor_tag_url)
        for tag in repos_res.json():
            full_repo_name = '{}/{}:{}'.format(self.harbor_domain, repo_name, tag['name'])
            repos.append(full_repo_name)
        return repos


# 將文件內(nèi)的所有行讀取成列表
def read_file_list(file_name):
    with open(file_name, 'r') as f_r:
        return [line.strip() for line in f_r]


# 將指定內(nèi)容追加到指定文件
def insert_file(file_name, content):
    # 對(duì)于python3, 第三個(gè)參數(shù)衷笋,指定編碼方式必須要加encoding=“utf-8”
    with open(file_name, 'a', encoding='utf-8') as f_w:
        print(content)
        f_w.write('{}\r\n'.format(content))
        print("保存{}成功".format(content))


if __name__ == '__main__':
    # 初始化harbor連接數(shù)據(jù)
    res_v1 = HarborReposV1(harbor_domain_v1, username, password)
    # 如果文件中已存在項(xiàng)目和鏡像芳杏,則先提取到列表里,避免重復(fù)插入辟宗,這樣腳本就可以多次運(yùn)行遷移
    try:
        pro_list = read_file_list(pro_file)
        repo_list = read_file_list(repo_file)
    except FileNotFoundError:  # 文件不能找到的異常處理
        pro_list = []
        repo_list = []
        print("首次運(yùn)行爵赵,還沒有文件,繼續(xù)處理泊脐。空幻。.")
    # 獲取所有project id
    for pro_id in res_v1.fetch_pros_id():
        pro_name, is_public = res_v1.fetch_pro_name(pro_id)
        line_str = '{},{}'.format(pro_name, is_public)
        # 只將新的項(xiàng)目加入文件
        if line_str not in pro_list:
            insert_file(pro_file, line_str)
        else:
            print('project{}已存在于文件{}中'.format(line_str, pro_file))
        # 獲取到所有鏡像名稱
        repos_name = res_v1.fetch_repos_name(pro_id=pro_id)
        for repo_name in repos_name:
            # 獲取到鏡像的所有tag
            repos = res_v1.fetch_repos(repo_name=repo_name)
            for full_repo_name in repos:
                # 只將新的鏡像tag加入文件
                if full_repo_name not in repo_list:
                    insert_file(repo_file, full_repo_name)
                else:
                    print('鏡像tag{}已存在于文件{}中'.format(full_repo_name, repo_file))

2,在v2版中導(dǎo)入項(xiàng)目列表

運(yùn)行harbor_v2_projects_create.py腳本,將pro.csv文件中的項(xiàng)目列表導(dǎo)入新版harbor中容客,其中秕铛,保留了每個(gè)項(xiàng)目的public屬性(true or false)约郁。

"""
將從將倉(cāng)庫(kù)里獲取到的所有project,在新倉(cāng)庫(kù)中重建好但两,帶public屬性的
admin
2020-11-24
"""
import requests
from requests.auth import HTTPBasicAuth
import json

pro_file = 'pro.csv'
harbor_domain_v2 = 'harbor-test.demo.cn:8086'
v2_username = 'admin'
v2_password = 'xxxxx'


# Harbor V2版本的class
class HarborReposV2:
    def __init__(self, harbor_domain, username, password, schema='http'):
        self.schema = schema
        self.harbor_domain = harbor_domain
        self.harbor_url = self.schema + '://' + harbor_domain
        # 新版harbor 2版本的api地址
        self.harbor_api_url = self.harbor_url + '/api/v2.0'
        self.harbor_pro_url = self.harbor_api_url + '/projects'

        self.username = username
        self.password = password
        # 使用HTTPBasicAuth認(rèn)證鬓梅,這也是避免那些CSRF Token Invalid的最佳辦法
        # 是從harbor API里看認(rèn)證方式獲得的啟發(fā)。
        self.auth = HTTPBasicAuth(self.username, self.password)

    # 好像只要三個(gè)要素谨湘,就可以新建一個(gè)project了己肮。細(xì)節(jié)待完善。
    def create_pros(self, pro_name, is_public):
        pro_obj = dict()
        pro_obj['project_name'] = pro_name
        pro_obj["metadata"] = dict()
        pro_obj["metadata"]["public"] = is_public
        # pro_obj["metadata"]["enable_content_trust"] = i["enable_content_trust"]
        # pro_obj["metadata"]["prevent_vul"] = i["prevent_vulnerable_images_from_running"]
        # pro_obj["metadata"]["severity"] = i["prevent_vulnerable_images_from_running_severity"]
        # pro_obj["metadata"]["auto_scan"] = i["automatically_scan_images_on_push"]
        headers = {"content-type": "application/json"}
        res = requests.post(self.harbor_pro_url,
                            auth=self.auth,
                            headers=headers,
                            data=json.dumps(pro_obj))
        if res.status_code == 409:
            print("\033[32m 項(xiàng)目 %s 已經(jīng)存在!\033[0m" % pro_name)
            return True
        elif res.status_code == 201:
            # print(res.status_code)
            print("\033[33m 創(chuàng)建項(xiàng)目%s成功!\033[0m" % pro_name)
            return True
        else:
            print("\033[35m 創(chuàng)建項(xiàng)目%s失敗!\033[0m" % pro_name)
            return False


# 從舊倉(cāng)庫(kù)導(dǎo)出來的projects列表悲关,讀取出來
def read_file_list(file_name):
    with open(file_name, 'r') as f_r:
        return [line.strip() for line in f_r]


if __name__ == '__main__':
    # 初始化
    res_v2 = HarborReposV2(harbor_domain_v2, v2_username, v2_password)

    pro_list = read_file_list(pro_file)
    for item in pro_list:
        pro_name, is_public = item.split(',')
        res_v2.create_pros(pro_name, is_public)

3谎僻,在v2版中導(dǎo)入鏡像列表

運(yùn)行harbor_v2_images_import.py腳本,將repo_v1.csv文件中的鏡像列表導(dǎo)入新版harbor中寓辱,同時(shí)艘绍,生成repo_v2.csv作為校驗(yàn)文件,以支持多次運(yùn)行此腳本秫筏。
為了讓中間的遷移機(jī)器不至于容量爆掉诱鞠,在每遷移完一個(gè)鏡像的所有tag之后,會(huì)刪除此鏡像的所有文件(每個(gè)鏡像的所有tag这敬,會(huì)共用基礎(chǔ)層航夺,如果導(dǎo)入一個(gè)鏡像就刪除一個(gè)鏡像,效率會(huì)很慢崔涂,且重復(fù)傳輸嚴(yán)重阳掐,想你一個(gè)有300個(gè)tag的鏡像)。

"""
用于保存到文件中鏡像遷移到新的harbor鏡像倉(cāng)庫(kù)當(dāng)中冷蚂。
admin
2020-11-24
"""
import subprocess

# 定義常量
repo_file = 'repo_v1.csv'
new_repo_file = 'repo_v2.csv'

harbor_domain_v1 = 'harbor.demo.cn'
v1_username = 'admin'
v1_password = 'xxxx'

harbor_domain_v2 = 'harbor-test.demo.cn:8086'
v2_username = 'admin'
v2_password = 'xxxx'

repo_list = list()
repo_dict = dict()
new_repo_list = list()


def read_file_list(file_name):
    with open(file_name, 'r') as f_r:
        return [line.strip() for line in f_r]


def insert_file(file_name, content):
    # 對(duì)于python3, 第三個(gè)參數(shù)缭保,指定編碼方式必須要加encoding=“utf-8”
    with open(file_name, 'a', encoding='utf-8') as f_w:
        print(content)
        f_w.write('{}\r\n'.format(content))
        print("保存{}成功".format(content))


# 從舊版的harbor中pull鏡像,tag更名之后蝙茶,push到新倉(cāng)庫(kù)艺骂,記得先登陸
def migrate_repos(v1_repo_name, v2_repo_name):
    cmd_list = []
    old_repo_login = "docker login {} -u {} -p {}".format(harbor_domain_v1, v1_username, v1_password)
    pull_old_repo = "docker pull " + v1_repo_name
    tag_repo = "docker tag " + v1_repo_name + " " + v2_repo_name
    new_repo_login = "docker login {} -u {} -p {}".format(harbor_domain_v2, v2_username, v2_password)
    push_new_repo = "docker push " + v2_repo_name
    cmd_list.append(old_repo_login)
    cmd_list.append(pull_old_repo)
    cmd_list.append(tag_repo)
    cmd_list.append(new_repo_login)
    cmd_list.append(push_new_repo)

    ret_sum = 0
    for cmd in cmd_list:
        print("\033[34m Current command: %s\033[0m" % cmd)
        ret = subprocess.call(cmd, shell=True)
        ret_sum += ret
    if ret_sum == 0:
        print("\033[32m migrate %s success!\033[0m" % v2_repo_name)
        insert_file(new_repo_file, v2_repo_name)
        return True

    else:
        print("\033[33m migrate %s faild!\033[0m" % v2_repo_name)
        return False


# 當(dāng)一個(gè)鏡像的所有tag遷移完成之后,清除此鏡像所有tag,挪出空間隆夯,不然會(huì)爆掉
def delete_local_repos(repo, tags):
    for tag in tags:
        repo_tag = '{}:{}'.format(repo, tag)
        cmd = 'docker rmi {}'.format(repo_tag)
        if subprocess.call(cmd, shell=True) == 0:
            print("\033[32m 刪除 {} 成功!\033[0m".format(repo_tag))
        else:
            print("\033[32m 刪除 {} 失敗钳恕,繼續(xù)執(zhí)行!\033[0m".format(repo_tag))


if __name__ == '__main__':
    try:
        new_repo_list = read_file_list(new_repo_file)
    except FileNotFoundError:
        print('首次導(dǎo)入。還沒有文件蹄衷。')
    # 這里的騷操作忧额,是為了能讓同一個(gè)鏡像的不同tag,作同一批次的pull和push宦芦,
    # 操作完之后宙址,才作docker image rmi的操作,肯定會(huì)顯著縮短時(shí)間调卑,
    # 因?yàn)橥粋€(gè)鏡像的不同tag抡砂,很多層是相同的
    # 字典的鍵為鏡像名,值為tag列表
    for item in read_file_list(repo_file):
        repo, tag = item.split(':')
        if repo not in repo_dict:
            repo_dict[repo] = [tag]
        else:
            repo_dict[repo].append(tag)
    # 遍歷這個(gè)字典大咱,作遷移
    for item in repo_dict.items():
        repo, tags = item
        # 新的倉(cāng)庫(kù)的鏡像地址,需要整合新倉(cāng)庫(kù)的地址及舊倉(cāng)庫(kù)的項(xiàng)目repo名稱
        repo_replace = repo.split('/')
        repo_replace[0] = harbor_domain_v2
        v2_repo = '/'.join(repo_replace)
        # demo小劑量測(cè)試
        if 'nginx-ingress-controller' in repo:
            for tag in tags:
                v1_repo_name = '{}:{}'.format(repo, tag)
                v2_repo_name = '{}:{}'.format(v2_repo, tag)
                # print(v1_repo_name)
                # print(v2_repo_name)
                # 已導(dǎo)入過的注益,忽略,減少時(shí)間
                if v2_repo_name in new_repo_list:
                    print('{}已導(dǎo)入新harbor倉(cāng)庫(kù)'.format(v2_repo_name))
                    continue
                # 真正的導(dǎo)出導(dǎo)入操作
                if not migrate_repos(v1_repo_name, v2_repo_name):
                    print('導(dǎo)入失敗')
                    break
                else:
                    print('導(dǎo)入{}成功'.format(v2_repo_name))
            # 這里使用for...else...配合continue和break丑搔,可以直接跳出兩個(gè)for循環(huán)外面
            else:
                print('{}導(dǎo)入完成厦瓢,清除此鏡像的所有tag'.format(repo))
                # 一個(gè)repo導(dǎo)入完成,清除新舊倉(cāng)庫(kù)的所有tag啤月。
                delete_local_repos(repo, tags)
                delete_local_repos(v2_repo, tags)
                continue
            break

4煮仇,DNS切換

DNS切換,將指到v1版harbor的域名谎仲,指向v1版的harbor浙垫。

5,更新V2版配置

新版harbor更改配置郑诺,提供與域名一致的服務(wù)夹姥。
Harbor.yml


截屏2020-11-25下午1.29.07.png

重啟harbor,使配置生效

docker-comppose down
./prepare
docker-compose up -d

(同時(shí)辙诞,v1版harbor更改為另外的域名或ip辙售,不急馬下線,待v2版穩(wěn)定后下線飞涂,有個(gè)別鏡像旦部,還可以手工導(dǎo)入)

6,測(cè)試驗(yàn)證

在k8s環(huán)境封拧,或是docker環(huán)境下志鹃,測(cè)試是否已平滑升級(jí)完成夭问。

四泽西,此種升級(jí)方案的優(yōu)勢(shì)和注意要點(diǎn)

在標(biāo)準(zhǔn)推薦的harbor升級(jí)方案中,從1.5到2.1缰趋,會(huì)涉及數(shù)據(jù)庫(kù)的轉(zhuǎn)換(從mysql轉(zhuǎn)postgresql)捧杉。而我公司安裝的harbor,是用的docker-compose方案秘血,全docker部署味抖,無形中增加了升級(jí)難度。
在我們?cè)O(shè)計(jì)的這個(gè)升級(jí)方案中灰粮,如果在DNS切換后仔涩,測(cè)試失敗,是可以作回滾的粘舟,只要DNS切回即可熔脂。
另外佩研,它也支持?jǐn)帱c(diǎn)持續(xù)升級(jí)。也就是在空間和時(shí)間許可的情況下霞揉,分多個(gè)批次旬薯,來將V1版的鏡像遷移到V2版中。而在DNS切換的這個(gè)維護(hù)時(shí)間窗口內(nèi)适秩,只需要遷移極少的鏡像绊序,花極少的時(shí)間來作最后的升級(jí)。而無須在短短一天之內(nèi)秽荞,遷移上T的數(shù)據(jù)骤公。

參考URL:
http://blog.nsfocus.net/cve-2019-19025-cve-2019-19023-cve-2019-19029-cve-2019-19026-cve-2019-3990/
https://www.cnblogs.com/breezey/p/10615242.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市扬跋,隨后出現(xiàn)的幾起案子淋样,更是在濱河造成了極大的恐慌,老刑警劉巖胁住,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件趁猴,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡彪见,警方通過查閱死者的電腦和手機(jī)儡司,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來余指,“玉大人捕犬,你說我怎么就攤上這事〗途担” “怎么了碉碉?”我有些...
    開封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)淮韭。 經(jīng)常有香客問我垢粮,道長(zhǎng),這世上最難降的妖魔是什么靠粪? 我笑而不...
    開封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任蜡吧,我火速辦了婚禮,結(jié)果婚禮上占键,老公的妹妹穿的比我還像新娘昔善。我一直安慰自己,他們只是感情好畔乙,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開白布君仆。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪返咱。 梳的紋絲不亂的頭發(fā)上氮帐,一...
    開封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音洛姑,去河邊找鬼上沐。 笑死,一個(gè)胖子當(dāng)著我的面吹牛楞艾,可吹牛的內(nèi)容都是我干的参咙。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼硫眯,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼蕴侧!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起两入,我...
    開封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤净宵,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后裹纳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體择葡,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年剃氧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了敏储。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡朋鞍,死狀恐怖已添,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情滥酥,我是刑警寧澤更舞,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站坎吻,受9級(jí)特大地震影響缆蝉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜禾怠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一返奉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧吗氏,春花似錦、人聲如沸雷逆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至往产,卻和暖如春被碗,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背仿村。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工锐朴, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蔼囊。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓焚志,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親畏鼓。 傳聞我的和親對(duì)象是個(gè)殘疾皇子酱酬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容