騰訊篇
騰訊BG各自為戰(zhàn)惠啄,每個(gè)BG甚至某個(gè)部門都有自成一派的研發(fā)體系。我當(dāng)時(shí)所在部門也不例外社牲,它自己有一套從研發(fā)到上線的內(nèi)部系統(tǒng)。當(dāng)時(shí)我們寫的代碼提交之后都會(huì)觸發(fā)云端的自動(dòng)編譯型宝,編譯出來bin供給后續(xù)部署上線。這套CI/CD的流程大家想必在各個(gè)公司都有體驗(yàn)。
筆者當(dāng)時(shí)利用這個(gè)自動(dòng)編譯平臺(tái)的設(shè)計(jì)漏洞做過的一件趣事。由于多為C++模塊,各個(gè)團(tuán)隊(duì)模塊編譯方式千差萬別柄错,沒有統(tǒng)一的構(gòu)建工具舷夺。所以每個(gè)模塊(指一個(gè)代碼庫(kù))需要在這個(gè)自動(dòng)編譯的平臺(tái)網(wǎng)站上設(shè)置編譯方式,比如make命令售貌,或者指定執(zhí)行一個(gè)shell腳本给猾。因?yàn)槟憧赡芤谀_本中做一些準(zhǔn)備工作才能讓自動(dòng)編譯的機(jī)器正常編譯,當(dāng)然前提是把腳本也放到代碼庫(kù)中颂跨。
當(dāng)編譯任務(wù)觸發(fā)時(shí)敢伸,編譯機(jī)上的腳本會(huì)用這個(gè)賬戶來下載代碼,然后執(zhí)行設(shè)置的編譯命令來執(zhí)行編譯動(dòng)作恒削。最后將結(jié)果打包成zip池颈,然后上傳獲取一個(gè)FTP地址。
通常來說钓丰,代碼倉(cāng)庫(kù)都是有權(quán)限控制的躯砰,即使是一個(gè)部門,也不會(huì)讓所有源碼默認(rèn)對(duì)所有人開放携丁,這不難理解琢歇。有趣的地方就出在這個(gè)自動(dòng)編譯機(jī)的原理上,自動(dòng)編譯的機(jī)器可以編譯整個(gè)大部門所有團(tuán)隊(duì)的代碼梦鉴,當(dāng)時(shí)他們是創(chuàng)建了一個(gè)具有極高代碼權(quán)限的虛擬賬戶李茫,用來下載代碼。
在我后來的其他工作經(jīng)歷中CI/CD的流程中肥橙,直線構(gòu)建任務(wù)時(shí)魄宏,都是以觸發(fā)任務(wù)的那個(gè)用戶的身份來執(zhí)行編譯。換句話說也就是沒辦法下載沒有權(quán)限的代碼存筏。
本來我和這個(gè)平臺(tái)也相安無事娜庇,直到后來。
回說一些工作背景方篮,我們組負(fù)責(zé)的業(yè)務(wù)是兩年前開辟的新業(yè)務(wù)名秀,整個(gè)后端都是從頭架設(shè)的。初創(chuàng)之時(shí)(在我來之前)為了快速上線藕溅,其中一些基礎(chǔ)模塊是直接搬運(yùn)的成熟業(yè)務(wù)中的模塊匕得,后來加以修改。后來我們這塊的業(yè)務(wù)逐漸成熟,和之前業(yè)務(wù)的相關(guān)團(tuán)隊(duì)分道揚(yáng)鑣汁掠,代碼和人員都慢慢獨(dú)立出來略吨。其中有個(gè)模塊的代碼是直接拿過來的,單獨(dú)存了一份代碼(也就是一份舊版代碼)考阱,比較基礎(chǔ)的模塊翠忠,沒有過多業(yè)務(wù)上的邏輯,我們只修改配置乞榨,并不迭代代碼秽之。但這個(gè)模塊在之前的團(tuán)隊(duì)中后來又迭代許久,功能十分豐富吃既。彼時(shí)他們的代碼我自然沒有權(quán)限訪問考榨,沒有辦法克隆。我曾和他們?cè)儐栐撃K最新代碼鹦倚,但是他們敝帚自珍河质,把我拒絕。
這時(shí)候我想起了這個(gè)自動(dòng)編譯系統(tǒng)震叙。它是允許每個(gè)指定自己指定編譯腳本的掀鹅,且是用高權(quán)限的賬戶來下載代碼的。這可就有意思了媒楼,這是妥妥的“OS”注入漏洞啊淫半,當(dāng)然這是對(duì)內(nèi)系統(tǒng),外部是無法訪問的匣砖。請(qǐng)注意這不代表整個(gè)騰訊科吭,只是我們部門用的自動(dòng)編譯平臺(tái)當(dāng)年有此BUG。我呢猴鲫,自然也不會(huì)用它來搞破壞对人,我只是一心為了工作。
我找了一個(gè)我自己的模塊拂共,在里面放進(jìn)去了一個(gè)腳本牺弄。然后在自動(dòng)編譯網(wǎng)站上設(shè)置編譯命令為執(zhí)行這個(gè)腳本。這個(gè)腳本除了正常的編譯流程以外宜狐,還有一部分是調(diào)用svn命令(當(dāng)時(shí)還未遷移git)去克隆那個(gè)我沒有權(quán)限的模塊及其依賴的代碼势告,然后將源碼復(fù)制某個(gè)目錄中。后面被自動(dòng)編譯機(jī)打包抚恒,生成FTP路徑咱台。
而我通過FTP下載zip包,最終剝離出了那個(gè)模塊的源碼……
后來我雖然看過他們代碼俭驮,不過最終沒有給我們業(yè)務(wù)使用回溺,想來想去春贸,畢竟也名不正言不順,權(quán)當(dāng)學(xué)習(xí)了遗遵。一年以后萍恕,我們團(tuán)隊(duì)和他們達(dá)成了合作,他們給出了源碼车要,并提供了技術(shù)支持允粤,指導(dǎo)我們?nèi)绾闻渲檬褂米钚掳娴哪K。其實(shí)我早已可以隨時(shí)查看他們的代碼了翼岁,在后來的工作中我再也沒有遇到過這種漏洞类垫。這權(quán)當(dāng)是一則趣事。
百度篇
在百度工作期間登澜,我所在的部門是對(duì)外號(hào)稱“鳳巢”的商業(yè)化廣告部門阔挠。眾所周知飘庄,在商業(yè)化部門脑蠕,做數(shù)據(jù)分析是必不可少的工作,即使我是C++后臺(tái)開發(fā)也不例外跪削,因?yàn)槲覀內(nèi)粘I暇€的feature都關(guān)乎著CTR谴仙、CPM等廣告指標(biāo)。
對(duì)于日常的數(shù)據(jù)分析碾盐,內(nèi)部有一個(gè)在線的數(shù)據(jù)分析網(wǎng)站晃跺。這個(gè)網(wǎng)站的后端存儲(chǔ)是基于Spark的。我們可以在這個(gè)網(wǎng)站上輸入SQL毫玖,來查詢數(shù)據(jù)掀虎。還可以導(dǎo)出成文件,或者轉(zhuǎn)儲(chǔ)到HDFS付枫。當(dāng)然這里SQL是Hive/Spark的SQL烹玉,盡管很像關(guān)系型數(shù)據(jù)庫(kù)的SQL但是支持的語法會(huì)更多樣些。
在大數(shù)據(jù)領(lǐng)域阐滩、BI領(lǐng)域常用的DB都是列式存儲(chǔ)而不是MySQL那種行式存儲(chǔ)的二打。為避免一次查詢的數(shù)據(jù)量過大,所以這個(gè)網(wǎng)站也有同時(shí)查詢的任務(wù)個(gè)數(shù)的限制(多了會(huì)排一會(huì)隊(duì))和單次最多只能查詢?nèi)鞌?shù)據(jù)的限制掂榔。
題外話:如果是行式存儲(chǔ)要避免查詢查詢過多继效,一般是限制返回的行數(shù),也就是讓你自己寫limit装获,offset瑞信。
當(dāng)然有其他途徑可以跑MR任務(wù)在Hadoop集群一次性導(dǎo)出更長(zhǎng)時(shí)間范圍的數(shù)據(jù),但是集群太慢穴豫,排隊(duì)太多喧伞,跑個(gè)小任務(wù)完全沒必要。所以一般可以在這個(gè)在線分析網(wǎng)站上查詢,但是蛋疼的是潘鲫,只能三天三天的查翁逞,如果要查詢一個(gè)月的數(shù)據(jù),只能分成10次查詢溉仑,每次修改WHERE條件后面的起止時(shí)間挖函,然后導(dǎo)出了10次查詢的結(jié)果。
某日在組內(nèi)工作群里浊竟,一個(gè)同事被安排查一兩個(gè)月的數(shù)據(jù)怨喘,他說很不好辦≌穸ǎ靠人工三天三天地查詢必怜,經(jīng)常這樣太費(fèi)時(shí)間。我多嘴說了一句后频,不用人工梳庆,其實(shí)可以寫程序做到自動(dòng)化查詢。然后被問:“怎么實(shí)現(xiàn)卑惜?你幫忙寫一個(gè)膏执?”
得……禍從口出。大話已出露久,搞吧更米。
我為什么說有辦法自動(dòng)化呢?按理說我們可以和這個(gè)網(wǎng)站交互毫痕,輸入SQL征峦,點(diǎn)按鈕查詢,最后網(wǎng)頁能自動(dòng)把結(jié)果渲染出表格消请,應(yīng)該都是有API的栏笆,內(nèi)部網(wǎng)站反爬蟲功能都不強(qiáng)。話不多說梯啤,給Chrome按F12吧竖伯,找找找,果然找到幾個(gè)HTTP請(qǐng)求因宇。
打開Postman做測(cè)試七婴,發(fā)現(xiàn)是需要鑒權(quán)的,不然直接給發(fā)請(qǐng)求會(huì)報(bào)錯(cuò)察滑。嘗試是Chrome中把Cookie復(fù)制出來打厘,塞到Postman的Header里,OK贺辰,成功拿到結(jié)果JSON户盯。
在理論上驗(yàn)證確實(shí)可行之后嵌施,我開始改寫成python腳本。首先將復(fù)制的Cookie寫入到文件中莽鸭,供腳本讀取吗伤。第一版腳本功能如下:
- 讀取文件中的SQL語句,其中WHERE的起止日期是變量(可以用python的字符串format功能硫眨,方便后續(xù)替換)足淆。
- 腳本中指定好查詢的時(shí)間范圍。
- 腳本執(zhí)行時(shí)會(huì)自動(dòng)以每三天為一個(gè)單位生成一條有效的SQL礁阁,然后攜帶Cookie 給SQL查詢的HTTP API發(fā)請(qǐng)求巧号。
- 給獲取查詢結(jié)果的API輪詢發(fā)起請(qǐng)求,等待查詢結(jié)果ready姥闭。
- 結(jié)果ready丹鸿,給HDFS轉(zhuǎn)儲(chǔ)的HTTP API發(fā)送請(qǐng)求轉(zhuǎn)儲(chǔ)到HDFS。
不多時(shí)棚品,一兩個(gè)月的數(shù)據(jù)就自動(dòng)出現(xiàn)在HDFS上了靠欢。可以用hadoop fs -getmerge
自行下載下來南片。
這個(gè)腳本完成后就給了那位同事和他們的業(yè)務(wù)使用掺涛。
許久之后我曾用這個(gè)腳本幫經(jīng)理(百度的經(jīng)理就是其他公司的組長(zhǎng))回溯過整整半年的數(shù)據(jù)庭敦。
故事到這里就結(jié)束了么疼进?沒有,后來我還在一直改良我的這個(gè)腳本秧廉,但這和工作就無關(guān)了伞广,完全是我在業(yè)余時(shí)間的玩味之作。
我把這個(gè)網(wǎng)站上能調(diào)用的HTTP API都在腳本里包裝成了函數(shù)疼电,可以很方便的當(dāng)成RPC函數(shù)調(diào)用嚼锄。
很快我遇到了Cookie的有效期問題,每次Cookie過期后蔽豺,我就要重新從瀏覽器復(fù)制Cookie区丑,也太不自動(dòng)化了。Cookie的生成可以通過一個(gè)內(nèi)網(wǎng)統(tǒng)一的登錄頁面來獲取修陡,登錄方式有輸入用戶名密碼沧侥、郵箱驗(yàn)證、短信驗(yàn)證三種魄鸦。而且有一些反爬蟲的限制宴杀,沒辦法通過抓包找HTTP API,腳本模擬發(fā)起請(qǐng)求的方式來獲取Cookie拾因。
這個(gè)問題曾困擾了我一段時(shí)間旺罢,后來終于想到了解決辦法旷余。那便是自動(dòng)化測(cè)試工具Seleninum
以及“無頭瀏覽器”PhantomJs
。無頭是我的說法……原是Headless扁达,通常譯作:無界面瀏覽器
正卧。就是可以在沒有GUI的操作系統(tǒng)上運(yùn)行的虛擬瀏覽器。雖然沒有實(shí)際的界面跪解,但是可以通過Seleninum腳本執(zhí)行瀏覽器界面上的操作穗酥,來控制完成輸入、點(diǎn)擊按鈕等事件惠遏。
于是我在我的Linux開發(fā)機(jī)上砾跃,啟動(dòng)了一個(gè)腳本,并讓其常駐后臺(tái)节吮。比如這般nohup python auto_login.py &
抽高。這個(gè)腳本就是每隔一段時(shí)間打開PhantomJs瀏覽器打開登錄的網(wǎng)址,自動(dòng)完成用戶名和密碼的輸入透绩,點(diǎn)擊登錄按鈕翘骂,而后獲取Cookie,接著將Cookie寫入到固定的文件中帚豪。這樣我之前的腳本就能自動(dòng)化使用了碳竟,而無需每次自己復(fù)制Cookie啦!
我是一個(gè)有強(qiáng)迫癥的人狸臣,自動(dòng)化登錄獲取Cookie的事情雖然做完了莹桅,但是有一點(diǎn)我不滿意,那就是我的密碼需要寫到腳本里或者文件里烛亦。盡管可以對(duì)密碼做一下加密诈泼,在腳本中做一下解密,但是對(duì)于能看到腳本源碼的人來說這無疑形同虛設(shè)煤禽。即使你把密碼設(shè)置成腳本的啟動(dòng)參數(shù)铐达,別人通過ps aux
命令也能看個(gè)一目了然。因?yàn)閜s看到的進(jìn)程信息是帶著啟動(dòng)參數(shù)的檬果。
我想了想可以在腳本中讀取標(biāo)準(zhǔn)輸入瓮孙,在標(biāo)準(zhǔn)輸入中鍵入的密碼,后續(xù)他人無法看到选脊『伎伲可是如果讓腳本啟動(dòng)的時(shí)候讀取標(biāo)準(zhǔn)輸入,那么怎么變成后臺(tái)常駐進(jìn)程呢知牌。我一般都是用nohup python auto_login.py &
將其丟到后臺(tái)常駐的祈争。nohup & 是沒辦法讀取標(biāo)準(zhǔn)輸入的啊。
這時(shí)候我想到了一道Linux C面試題:Linux如何生成守護(hù)進(jìn)程角寸?對(duì)菩混,就是不用nohup忿墅,不用&,讓程序自己變成后臺(tái)(background)常駐的守護(hù)進(jìn)程(daemon)沮峡。python也有和Linux C類似的API疚脐,我就用同樣的思路實(shí)現(xiàn)了。
于是這個(gè)自動(dòng)登錄腳本的用法是這樣:不加nohup &邢疙,直接執(zhí)行python auto_login.py
棍弄,出現(xiàn)一個(gè)用戶名和密碼的輸入提示,輸入之疟游。然后腳本會(huì)自動(dòng)變成一個(gè)daemon進(jìn)程在后臺(tái)常駐呼畸,根據(jù)一定的頻率定時(shí)刷新Cookie。于是我可以在不泄露密碼的情況下颁虐,完成這個(gè)常駐后臺(tái)刷Cookie的進(jìn)程啦蛮原!
這個(gè)自動(dòng)登錄的腳本,算是一個(gè)意外收獲另绩。百度內(nèi)部系統(tǒng)用到都是同一套內(nèi)部鑒權(quán)方案儒陨,所以后來我又陸續(xù)給其他的內(nèi)部網(wǎng)站抓包分析,寫了一些例行的自動(dòng)化腳本笋籽,比如我們每次上線之前都要在一個(gè)平臺(tái)填表達(dá)登記蹦漠,上線完成又要在這個(gè)平臺(tái)上進(jìn)行確認(rèn),不然沒辦法發(fā)起第二天的上線车海。在有了自動(dòng)登錄腳本之后笛园,這些都很容易實(shí)現(xiàn)了。它們的鑒權(quán)都是得益于這個(gè)自動(dòng)登錄的腳本獲取的Cookie容劳。
各位看官看到這喘沿,可能發(fā)現(xiàn)無論是自動(dòng)登錄的腳本闸度,還是自動(dòng)做數(shù)據(jù)查詢的腳本都基本成熟了竭贩。但這也還不是故事的結(jié)束,一段時(shí)間以后莺禁,我又做了一件有趣的事留量。
前面提到我將數(shù)據(jù)查詢的腳本根據(jù)原網(wǎng)站上的各類功能包裝成了不同的函數(shù)。我又做了一些完善哟冬,使之成為了一個(gè)很方便的第三方庫(kù)楼熄。我將里面一些函數(shù)的返回類型替換成了pandas的DataFrame。于是乎浩峡,我可以方便地在Jupyter/JupyterLab
里使用啦可岂!
然后我可以在JupyterLab里import我自己的庫(kù),然后調(diào)用函數(shù)去發(fā)起SQL查詢翰灾,然后直接將查詢結(jié)果在JupyterLab渲染成表格(因?yàn)樗荄ataFrame類型)缕粹。也可以在Jupyter里面進(jìn)行HDFS轉(zhuǎn)儲(chǔ)稚茅。另外呢,Jupyter可以執(zhí)行shell命令平斩,我可以wget一下載一些其他的文件亚享,比如某些數(shù)據(jù)的詞典映射文件,從而可以將數(shù)據(jù)中的枚舉值換成字符串名稱绘面。接著我可以繼續(xù)數(shù)據(jù)分析欺税,并且用matplotlib
直接渲染成圖表。
這些其實(shí)都不是我的本職工作揭璃,并不是被安排的任務(wù)晚凿,純屬我的個(gè)人興趣和個(gè)人探索。雖然后來我把最終版向幾個(gè)同事安利過瘦馍,但是他們也并不感冒晃虫,我的第一版他們后來微調(diào)了下已然夠用。
不過我倒也并不失望扣墩,我在不停打磨自己腳本的過程中哲银,克服一個(gè)又一個(gè)的困難,實(shí)現(xiàn)一個(gè)又一個(gè)的功能呻惕,看著它日臻完美荆责,已然心滿意足。而最終有沒有人使用它亚脆,則只是結(jié)果做院,這個(gè)過程已然足夠有趣。感覺自己就像乘興而來濒持,興盡而去的古人王子猷键耕。
這是兩則趣事,謝謝觀賞柑营。