From 1d86db97ba8af73ac7938fe78e4496e55056f18b Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Thu, 9 Jul 2020 14:40:49 +0200 Subject: [PATCH 1/6] Update documentation --- LICENSE | 21 ++++++++ Makefile | 4 ++ README.md | 107 +++++++++++++++++++++++++++++++++++++++- assets/architecture.png | Bin 0 -> 9661 bytes assets/diagram.drawio | 1 + 5 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 LICENSE create mode 100644 assets/architecture.png create mode 100644 assets/diagram.drawio diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a909e62 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Xenit AB + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile index 8904013..5369be0 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,10 @@ TAG = latest IMG ?= quay.io/xenitab/azdo-proxy:$(TAG) +assets: + draw.io -b 10 -x -f png -p 0 -o assets/architecture.png assets/diagram.drawio +.PHONY: assets + fmt: go fmt ./... diff --git a/README.md b/README.md index 271ab64..fb83af2 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,107 @@ # Azure DevOps Proxy -Proxy application to allow one PAT to be shared with limited scope to other clients. +Proxy to allow controlled sharing of a Personal Access Token in Azure DevOps. + +

+ +

+ +Azure Devops Proxy is meant to be run in the same environment as the applications that need +access to Azure Devops. All applications will then send their request with a separate token +to the proxy instead of the Azure DevOps endpoint. If the token allows it the proxy will add +the PAT to the request and forward it to Azure DevOps. + +## How To +Start off by [creating a new PAT](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=preview-page) as it has to be given to the proxy. + +> The example will show how to run azdo-proxy in Kubernetes, but there is nothing limiting azdo-proxy to run in any other environment. + +The proxy reads its configuration from a json file. The file will contain the PAT used to authenticate requests with, the AzureDevops organization, and a list of repositories that can be accessed through the proxy along with a unique token for each repository. +```json +{ + "pat": "", + "organization": "org", + "repositories": [ + { + "project": "project", + "name": "repo-1", + "token": "" + }, + { + "project": "project", + "name": "repo-2", + "token": "" + } + ] +} +``` + +Create a Kubernetes secret containing the configuration json file. +```bash +kubectl create secret generic azdo-proxy-config --from-file=config.json +``` + +Add the Helm repository and install the chart, be sure to set the secret name. +``` +helm repo add https://xenitab.github.io/azdo-proxy/ +helm install azdo-proxy --set configSecretName=azdo-proxy-config +``` + +There should now be a azdo-proxy Pod and Service in the cluster, ready to proxy traffic. + +### Git +Cloning a repo through the proxy is not too different from doing so directly from Azure DevOps. +The only limitation is that it is not possible to clone through ssh, as azdo-proxy only proxies http traffic. +To clone the repository `repo-1` [get the clone url from the respository page](https://docs.microsoft.com/en-us/azure/devops/repos/git/clone?view=azure-devops&tabs=visual-studio#get-the-clone-url-to-your-repo). +Then replace the host part of the url with `azdo-proxy` and att the token as a basci auth parameter. +The result should be similar to below. +``` +git clone http://@azdo-proxy/org/proj/_git/repo-1 +``` + +### Api +Authenticated Api calls can also be done through the proxy. Currently only repository specific requests will be premitted. This may change in future releases. As an example execute the following command to list all pull requests in the repository `repo-1`. +``` +curl http://@azdo-proxy/org/proj/_apis/git/repositories/repo-1/pullrequests?api-version=5.1 +``` + +> :warning: **If you intend on using a language specific API**: Please read this! +Some APIs built by microsoft like [azure-devops-go-api](https://github.com/microsoft/azure-devops-go-api) will make a request to the [Resource Areas API](https://docs.microsoft.com/en-us/azure/devops/extend/develop/work-with-urls?view=azure-devops&tabs=http#how-to-get-an-organizations-url) which returns a list of location urls for a specific organization. They will then use those urls when making additional requests, skipping the proxy. To avoid this you need to explicitly create your client instead of allowing it to be created automatically. + +In the case of golang you should create a client in the following way. +```golang +package main + +import ( + "github.com/microsoft/azure-devops-go-api/azuredevops" + "github.com/microsoft/azure-devops-go-api/azuredevops/git" +) + +func main() { + connection := azuredevops.NewAnonymousConnection("http://azdo-proxy") + client := connection.GetClientByUrl("http://azdo-proxy") + gitClient := &git.ClientImpl{ + Client: *client, + } +} +``` + +Instead of the cleaner solution which would ignore the proxy. +``` +package main + +import ( + "context" + + "github.com/microsoft/azure-devops-go-api/azuredevops" + "github.com/microsoft/azure-devops-go-api/azuredevops/git" +) + +func main() { + connection := azuredevops.NewAnonymousConnection("http://azdo-proxy") + ctx := context.Background() + gitClient, _ := git.NewClient(ctx, connection) +} +``` + +## License +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. diff --git a/assets/architecture.png b/assets/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..8107383caee698c0137f114433bfd3b7925f8b8d GIT binary patch literal 9661 zcmcI~S6GwVwsvTOND&Pd6mdxlu%swOkS>a#bO=aY2+|@=n$!dlSae02)F8bibcmq} z0wTSH(1Re-n-F?AGwa`1|Haw+JkL27JS4Myqr79xG2Sm>+M24&j2w&*2!vVfv5GDP zLe&TU5DaI)h^6*OHTXy6rmLz9!S-BVgg|aW)Krx8U#G54di!a3owRT6b}Q@Z>&Bxo zY3F4YF8MxR{uE`5yt`TnePBQsxG?TMm|0b`ZTO_1ufVC&63W@Ht7DL*Ve)pYD(mgb z_{X6^rj*eL^KdW2Wg_EhC(10|Jy++w#K`9XKQ6Zz)nqx$?}W`Wzi6$<+C) zsNP{L7N&19IFCl9jJm^AJ#vqW6Q$C1ZLpI|uOM+K2;Iccldu8@v0YR`K%%{e03=)q zMeVfm_)%c`-X3XbV~35GtG5Dw9g+xyVl7?NjyCR+%hy3@iek>;&Qe1+Zk9M}=%v{o zCJoa~pt?nwT9$4>EaE>Rh=p;aDzN;5=P9`S+*cvAI%LnUxDVn`-?9QVhEJEY*aNLd11%&ioMM2-Sj_{0(|7E z@duM^x-(XiT=GR-6Uw}&33=RC=iPa-6fIZ^&3;uu8s0$YQm26i@MGj(d@HVIUYP-2 zZ}WYBMjib4id4R{|Jo@`ucfU>!xVmP+L5|g7_xCGg*C&VdF@>0vrBpvhI&Fg+TBI; z6iHi$Fz8=|AodF@c}c;cRr?#y@*3r*ml?FtOp!|CvH$&|U3ox-+SOT2@6-6Mq274oddO_#HO0`7^58=BcrC z(R$4ssDW$GkMWUf*ojA^n`l) z`H_<2w2X(duG(aZ{=oZ4>iW2(!k@F=-@n%sf!z+Fca-Vs*r^!f-j8JlhOfIt0ORSz zG;#^OvUvP1Bbmt1u!2wvFEIJC=~3aJ#CX=`Il~4mElj%&FIAAuhACQzDjB>!erIcC z%pKP5df&45W6@fkryV#r;e-2J-;0AQr6K>qrSs5HRM5c>m2`wfh0nWa>r_3P^m_dm zDRak}pW%@8R#UcVp0oZ4{L!p%N8?<)jO;#)#l?S*g*r7G9C+bixDp@voWOW?DEEs; zu`tbs9l)@Pn1|3ptEK@)IP32P87Y|eYk!FnHfs+7l&Tdo)Ftp=uqo>7`+L}6+vlZ3 zQ75F1np?Yx?!t`{gO%jjAGi>oGChMtcX8u^!%NKZyk&!NUqQ$tIeTFKfK zX()AL5sc}S7O>zYdoq2KG45x0!3x|_Mg-?l}wMzeDj7{ z?qDHH5Qh70%nw-xdFooSygwd}H7&3o%zb9Yh`Jyed=8h1M&F#9pD!}~{OWseZw<&t zt$)!n;yw!K<=$(k9(|cC>s=m9fWXcI!*-iq_c#9vgSuz|LrT3`T-dtxC==!T9ISu; zd#+RH=1oO*A-KMSLt%4sb6i>)2FQ?h->YZoH^lS}8*G~I!o`d1*RL1Tox8N~v*SZ- z_6L3~hc9pF3bixeNWL8T%QYAx+{yrT(UVNk@bjf!i(rF6AbjW15J-|DiW>44H5DTS zb(Z#jdk`nkB3dj=)x`dph@b@jaaoWM30TU6?iiJX<`g{`Ed3lKA z19EJ3#|Qr9ZYfe7)<16e-5(F2TwKV}okvj|1x}NAhoSuUw{G1UDz$vjopygW86k-1 zOcbfX)1sfAM{h7jTo!?E{pzl=?ugZu3mB`kkA~b9A_W{`xR3ZWQ}ZobLd=Nr-<9Lv7HhI>Flp4R*9u3>hke6{Aj#}CE{ z=v8{I8s`Az_J7YgOx1m6xqc4~1nF?9p&asoPeb?V(?}Wj1@zA9_%iU`SeaGmqsS|n zYm>FhpXbx{G&Q50YF8y+SX;w*cvP}fW8lilZ_NG425P!MRr0Wjvw(zzf&bx-fs#^S zc}0bU-`+;&&gO($?;^p_f}n7^uQmEol2D+hx;5XMn30iju)AIoM9*4i)1CaiqeFn; z&3NwIpqbZXPQCAL;lqeat!wv1MYY|2b@k7*Mi`Ws%T4+$W7M^@h@T!tgv%vHT)Hi| zDchZ)V6nNm37km*S%QcKc0S!#^Kf_Furc7c(tS~P-t;`G=n5*-cQGe1GT`{&YKY|( zj7g2;l<&G+izpMfloT=F^mF;F>w3c>R?KBukuoSNJMrz?H*h73Qq#~xc`Of8c2(GP z-w9#ibhr{?>Ao`3Khoe|Yk4JPbGf9EqY6Qk1dC-nmaLzCyweIcj#SvjuwA`s4;FW& zNcCP3d;S(I7IU3Zu{zjZd<2IlxOU#Ixngf`4^D1FQPD&K=UyGJah8%lSm36}aQk*Q z12c2|OD>b@mtuI&RfmyPPcUxv?cE=3Zh^de>FjFKPy*>kw362*tM(} z9m;ufSxq-h#$$W6hV)&OiGoj8SJ&wF{lh~cIXSb~p+W;8HC^%&fpa{(!nXGYfa(*e zN%s%g=k&T`-H-NMDemJ6*vq7*rjCn`&jHA|?Cqqh8|%3~g<;{8c^nIXWKiW`kS6P` z3uK6Mw>+-aNRi}i2w0!0XNN+wS5{t*u6o`!#c6}547PJ2O;;Lr*CvZXn6E9hU2~4( z@mL&q2rMYzdTJ=66(hHk1WcAcRqtD*8gs*80#~-PHfb``0MUAYYR=;2<6HU~c9BEg z56823w7)ghh-{4Jyq~+Vv2iQrcpZiRW>e8YB9UCjMVSDW)drF!5c*}$TRYP)o49hMarr7Rg2Py4L0i>J{BVGK7MH)mQYW3eAT#HXefo7H+s4->y9iios`UPrto`E5nbdItmqfb`!u(=c9X zpQR~#NYGd->o%C`T$PSw|diClR_MUQGzGj+?&##ZR;@02Z39a3~ zbxZkhXLa!7U0WVC1Q8pbEtK9l(?dxl=H7Gc1gx3KgHRU22QB%6lExFf{ygUflolon z_wm8YpB}l9*GY}vcJk7F)IogTVR~TI7TFJ;n3wq=R1gOHCiZD*ASVJuTy|Vs+=X2Q z=P6kVNICC&>SxINUmr~44GRy~1t|(RVSdDOHcAR4J@4?SD1zMXL{@XNGDxU8+^W$- z5h4xw!e)3FxZGG}H&ADou4r3xIgVBK&&DWV7Z&WQoJr;#9UT;`Jh!r$)E^M7%8&?NEh;0n|MYjGW3#d%yxS=;Y~?Uzqhv7 z+1Yads2sYJB{(EsCOHfj3#4>=65P(drcQCq+2@DIuLiNo_Z^UzBaTxArkAe-Qf@|W zN_RuKxC>4L=7k`;%KwM=<^TK+ru1UP!ql)RTN~$cJng;F2^}$bA|%cj1$r~$^S!K9 zaQ|xFvE9E^^aO%EtB3nPzVZK!2B>q{E9&t11U7gg6`BcDfuTZw{#l8qZQ^G`D+(O# z9kY<@MAXwhaTFpB6&6x3DLiUwDU$guljQ5C9Po461G&tx+52whdfRCpLzuy|u{hjt znMmyhTMCBYQHMgN&X8ELAqtg1za%}dbLO`BV z+S<@U-r-W1{H}WJFvdKSJ}YGrY&~#`iI%}3OL!JL+POSs*SprAIgGDJuI|}HoM`98 zYG%kRzQ!0@aAXOH$QriUvTT@xqMlJC+TS>a^?Gj9IC8j8=i>IjNFJ}6qZNLklaro2 z7aio|@RRnTc!Sw)W%Tfu486()q^(^m-qARIPee6IZ2(W;LTt5p9JsfzV6?_=w}^_b zlMN&M%@mNxwC;}n^7t_0>N`5lPyKb;k#f1HCPmKseu|uH{*TuK%KsUfZ)fT^*%Tu0 z56dkLZ?Q#j$}7fh4%s!W2KTxW?XBt zUn>}1U#5n_f_Dua;Wg$l>alC~oEVGf8-lM+8dhFp?(BVV0%h?_$rSvqx2al+RNUU!uIH+-8soVVY+FrDUkqve zUAN7L$0`1*G{hC zdPSKuUVFZ~!FBKH`~VU0!5`dh_p2w3r1fpf?{L-(YUar_U|+c%8Dx#kel_>~{#x%-twBr;A%Taoxv%e>lDGYc!Ds2G_iOm2o#xZ!iY6v13Uwt+ zW4C{G84lI=&KmD4)twA^l{Oa~wf}*{!5uyi7xtmFpc3=JhG(B%wS_5MG=eH>qGZdq7COXPMIc!Cf4v!J`DjJB2C+9T;RRI{pW*>4b<@oys z=NEs9{lN==0>|u8)7V){7cg{vgDEd=LLAMsS!Y?6`Z`Vc}xE zzP5`d!TGa>9?9rWkcsA16>@Nwa|L1djQr`DiQ%j>@wk9084^ju(Q3`WY3lt zv^VN<1*mO}%xJYadb_5wf5cQ*;F%Kf6_)In$)l|1BGODH5!Yy@*KM|)$)O_dI);dA zOPw*IwAK)qKq{4;^ znBsDdj{Kr-$QgrP_O4BrHmGVqMOQ4``65><0~;@72+BHpc;d_*P!Je6Ibm}(QYdv) zYinx(8r`&1j=C`V|1M1&Oi1WSufl2FukMsQ6bdpoH>VaWXoxND>UtVclWiLB{kAzN1Gi?mj_Pg>*{3LFI`%~)J*Bx+2w-{{&q1F?m87?jSgtJx|75n zu*z+}169g1>#zNw-ajYghEe;#C;cdGe}7-T>c+@tH`{h%yr6+DC}`NPT`L0RUZLme z*d_QG(`YHSFOK3bN3@x*Ne+E`&rN7VA^{hH04qVm=FtBsm@xPGYfe^ggmAPnzh+-x!e?|0nm@J zb8rZ%$skYaboKNMvK}!<2)W6nbn{sp?Z^fk;Q)G|=Ups=7l#V;C{;CBbQ{YA3ul8( zJbL-E7}Vjv7_`)YdK(=Z+b<*>hFsp}x&PS8Ja<`GKjwzqpT&&-cx_`0Q9>brd=@f8 zi-qZ&@_l#L3N0ENY`T&x0kuY>hbx@`XNiMO)SpZ+1PF!uc`)v(8`n5tD6Ru=g(p&< zN0%m8UirPdn>guIgNN2UK$-Udq5$;cz%O7PFiVIk`@u&hHBMD{rq>Jr*`Q=r155^3 zje-Pwv_)RcN|*D!!x@Sgz?$9nSRFH%ZI1zyd-tfJ36CE??hB-$7x&q=D|!-0&mrZM z3LujsffzP2WAwX%K)XoG6AfjG1I;IqNT(|SMV)$i$=v!J^CMzHte0EmAD}fC*T3D*X(ojKo zWTZauAt9%UetV(+0cfEJT_5jagz4l_0;cygK*QS>!&L(M7bCN9)#r@FIylDIerILW zVYEC4Y>Hq*yD%nywNRiQl1=VuVNYrf*6aI|3jS$P)-(|E@MD&m>;#p@{EwHkuVGOV`GnV~O2_%>feNc5FI81%kJ!>t{6by)5PmNI!$QHKw7I_S;W_Q6NYP6e*B zZ5$E~ac5X>yaZz3qJ?3{0qH`CE)XRqW{ldOl!DGIPJDJV#=92)iJp|6312P;+!Yz9 z12_W!BM&1Qk7(csyGgtU1#n&`whR z@cQ+g4b#2Md>#P&3JWJX0PF+Vfe9vRHaO9F9-?2}&xH@`Wl~ zHzFcJ4`ANz0&58cU2+>uG|8ftZzzvEkA}qq3@^@SAcvF0tUw6B+yL^3F$yR7z4C`$ zOS;oaMl`JkY4ZNYQP-q~3-t1UTk}fKEcs++X3F^Ol`1PMgIzaJY%pxNht?ira?h;s zc{J`570nsD37o6LNNFY@?lmu8y;|^|TXn$wMvgM-lZtp)s0i?fdW3{FG=gt)*ot|` zSUjzr0OYx(F@sS>8As%9;W%<#T(2g7rJ>BQp??CKF4BGcLXvXU|PJhS)s?4Cl&pS;4IYpTPfZ$2yg&5AP zua}54>?!y6_XGS(DWjAD6R|W^Ukjq!5k&vE1F4~Yt?@JfpePVRb`=k!SC0W=K@n*A zNWRUrC<*P|hlAdaBNhEQmeWzv;%T8OsDnR&z`y$4Z(N{S`T$IBs*v0hB%#e!;Y^a3 zJKAU&tMw`&isV-TD8)PwM<7PAZy;nQeywzU zkY?RyzMci)A0ZaJ{NP5wo@M=3&;7Uj+9Ay?EfhMjz_7#|XmOca(f@0u{oqi(4mSv# z?j>DF$zX-87KJ|mhm_-?xbu2Ac@*DfdYFJ|FMv8p8Fc^&IPc4MLXu_GnD;-Yhcl11 zIg48EbDyd?k2VLIqL%yJr@X-DaPZl~PG5S{*B|3I z65$YBD9HO1eigW~0uPN78&<5R)tISgMWCmGbGN2KA><3-sq(b>UYUz^yOxZ^bA3EF zT#({vl(A3{EYl!z*3FWd$$x=$PEUXMhydy6sfU6#X@+YzPnYY#G$)wW7JmYfO>Asz zlysWN2UJqglP?}O73KOUTm;5M3MA_`AZ-$qlM~~+UT=7jU$YVqO;SPmBms&70ob(g zhzKAdglOOV_N=<5=I~e*0wgFt5Ex1W0|QVtw#>miEszRKQ4-oDwhI?N0SdSf^aJq3 zH;5*Np`eZSGOG1Tk7am*wd{GSr9S{tUQ=Y{qaLCGI&B01on(sx@zTj(4~2&n{V!_F z%h6LU{j$5TUdy0OQW^B_{Q|{aqp0i)bOP@{q$Kl9wQ429MIB) z9Q2i^Kk$?Y#8C*Ju&^-kG%r2c{A-zNvZ&KXgWkIjQTVf98g^D=e)1v$?F4WIV?m(z zoWVkoNX1Er#tc>588CT{%0Y~>@OE-f?PcmAWpMGox4Z%(O4|Xj+Hg?vP5JMd6+PMX zE)F#Q34#I+ZU7JnwjKg5dc}b=>VW;v0~JI*c%1;!X9ZS3G2>%>$H%HB<}S{;h776M zgRKBlWa&VaIg*NaK;6RS=!MY=3{9%=iy-itvJ*3QXAXkmyG)VA|1d=VH}6jV4U_T@ zkCPC%J(wrH@gD0V+7c%b1%c2ZD8B{3e+1z`f4pAYSm0PO?yfZ}8pZM3cJ8OOiP;6e zx06mpgh+dV!5|0deu)$sWM%YvQRl0x)Y~#IY%}_;J=V28}ShWFvF&8{Pw3&Wp z$;C@9ic?H+zBeP0rz-#Qr(kDIJ(78v2XdUxxP00}Z2-&VNg;adesA?Iy?5&O5ygM3 zM%I5GIR=Gtx5+`6%)}wOGx}_L%P7$z$^_x?y*QY$1pa_sP+#9YkKTQIgDR1eYa*qh zr19@I4f6L$Z^rt9a7qWc!?`}#&rNUU=?C9BfE>SSu~i?dR9bF0P|M(7)%mW~ap2dj zt`?io*x2*yr`_mxmD?sC^QCed@@E@wd;p=$vZkf;bRO%2B-=!BoQ9`!$z_im8|G{4 zR4om8&HeEu=-%N$ZT-F(es*m*dfWn8rQ=ogaz557=+JG4Og!pIaG3bnyfR+Wc)cG_ z5P}YVF6xZ#bui~bbK+(5V~GBk-%3h?h7EPelUH*9*Yaut=}cUU%`V+oa`5v#cMNzi zHQiQqsX`5RTQyxEh>Xu3YG6>a_5?EBZ|Z&AohGI>-!oU#n?;lAF1{g-#h>_0lGHiZ zu_eE}R2EM{eWW-4o`@jQ+dUey*GLvMt>?1C$eWE*y6W=r)#yWKrn4S?W@XQLU%Yr> zI$|#UwM-5KrION@1394w-1G{c^1rz}d>DctvfelIy1DTfi#uUz=@YuBx)i!zjhGtH z0tE`0Gz{Z4JU%k{9FS7IDuXR9W&)jh_POXTIa(t#R7H3C!5f~%773G|;BE}-Wf6Xb zwDC!Hs>f9GEbbOR4Gw{SE|udp3$X;;{-S9>!z;I5L7vKR^YopBp0v(Hp5UWXtH4;? z%z^Lf+`XJMO+sDHSwl=m8R30g zQuy(zeYo9_{>jOS`si^`HLrg`h4iU+3b~MdT`e|1+UK#a*D3m!S5+L(+*~?g9wGmp z2XolYDDk)5r+0lR`qDi5fVU`cFp>x``catbf9!uqw(+l**13D)b(P31JiD^IZb5q4 z|8rhd;IM05#%nDVj{Mzli-WXwKvVTMF-GxFL0Z?fcbnM0F4E6sJb~F1>FwKlPvg-p zcX{eHn>i-rMz}rpu5CGVw^=_p*wApS-~%25H}h}Yy4D@A>J8p`?|mFi5kJyd_YNhcF&Ejb{^DXc zKTfzcuII^%*h4GWw^Y|(GO&q~?yRv}%fC|831cH>c+DQT-NrV40twJuA=p=f_xQwx zxzP}k>w5e}-4sb|pQi!80J>Wjz`U*Qd6EiW*=f^A5!y`g7sj4x2w7%@&m7bSfcM=0 zn-s&p`K8Do)5aO1fCRT4qKacAaut6ihd%ii3jl#=wLpdc=WoL|kiV6WdA1TQAAsLu OLDU{dh=f`Bo8?N literal 0 HcmV?d00001 diff --git a/assets/diagram.drawio b/assets/diagram.drawio new file mode 100644 index 0000000..58219dd --- /dev/null +++ b/assets/diagram.drawio @@ -0,0 +1 @@ +1VfLkpswEPwajk7xdNjj2jjvVCrxIckppYUxKBEMK4QN/voIEK/C9m4qu/b6BOqZQVJPq1Vo1jIu3nKSRp8xAKaZelBolqeZpmEbtnxUSNkgrmk1QMhpoJJ6YE33oEBdoTkNIBslCkQmaDoGfUwS8MUII5zjbpy2QTaeNSUhTIC1T9gU/U4DETWoZel6H3gHNIzU1PbcVSUxabNVahaRAHcDyFpp1pIjiuYtLpbAKvZaYpq6N0ei3co4JOJRBenHG0689zPTzHdft97+A97PVDe2hOVqx7e5iOQXqU8EBDL0De5zyITagyhbZiCQRKkhchFhiAlhqx5dcMyTAKrZdTnqcz4hphI0JPgbhChV10kuUEKRiJmKNnNWEx3dsoIyzLkPJ/bZaofwEMSJPLNrjJQ0YAyCl7KOAyOCbsfrIEpbYZfXsy9fVAP+oRnGpBlkH+As5ViUE/7H7O4iKmCdkpqEnTyLYyY3lLElMuR1rRUQcDe+xDPB8Q8MInPfhbtNx/0WuIDiNPtTttoCvT0l6vDb7VHY9UfJaLFocIrm+jMxbE4YnvBKsrQxkg0tKnqHNKZIE1GvyVlojicRwmiYSMCXnIDkcEHj2lEWG0yEErZh9rhH41CunNG7av2ZT0A+b/c5h18ebL+k2atsGz4V/ZYzor8zrQH9zgH2nedi//XD7P+3Z0hyePlD1deDn8OIVwxDXqlGT+g09iOd5kjzzuM09tRp0rT1n6syGdcee4zjXtpj3JNXZYLJS7sbnWtQrHNQsQ8ZyEtU7ORavLxkb84i2c6ajQtZ8/wahD4/KHTrGoVuzM8ndDnsf6Xq2OCP1Fr9BQ== \ No newline at end of file From b44c248a4b259e4fad6725ce3c2cc35e8489c3f6 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Thu, 9 Jul 2020 15:01:07 +0200 Subject: [PATCH 2/6] Fix small issues --- README.md | 44 ++++++++++++++++++++++++---------------- assets/architecture.png | Bin 9661 -> 9288 bytes assets/diagram.drawio | 2 +- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index fb83af2..a075394 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,25 @@ # Azure DevOps Proxy Proxy to allow controlled sharing of a Personal Access Token in Azure DevOps. +Azure DevOps allows the use of Personal Access Tokens (PAT) to authenticate access to both its +API and Git repositories. Sadly it does not provide an API to create new PAT, making the process +of automation cumbersome if multiple tokens are needed with limited scopes. +

-Azure Devops Proxy is meant to be run in the same environment as the applications that need -access to Azure Devops. All applications will then send their request with a separate token -to the proxy instead of the Azure DevOps endpoint. If the token allows it the proxy will add -the PAT to the request and forward it to Azure DevOps. +Azure Devops Proxy (azdo-proxy) is an attempt to solve this issue by enabling a single PAT +to be shared by many applications, while at the same time limiting access for each application. +Requests are sent to azdo-proxy together with a token, which gives access to a specific repository. +The request is checked and if allowed forwarded to Azure DevOps with the PAT appended to the request. ## How To Start off by [creating a new PAT](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=preview-page) as it has to be given to the proxy. > The example will show how to run azdo-proxy in Kubernetes, but there is nothing limiting azdo-proxy to run in any other environment. -The proxy reads its configuration from a json file. The file will contain the PAT used to authenticate requests with, the AzureDevops organization, and a list of repositories that can be accessed through the proxy along with a unique token for each repository. +The proxy reads its configuration from a JSON file. The file will contain the PAT used to authenticate requests with, the Azure DevOps organization, and a list of repositories that can be accessed through the proxy along with a unique token for each repository. ```json { "pat": "", @@ -35,39 +39,45 @@ The proxy reads its configuration from a json file. The file will contain the PA } ``` -Create a Kubernetes secret containing the configuration json file. -```bash +Create a Kubernetes secret containing the configuration JSON file. +```shell kubectl create secret generic azdo-proxy-config --from-file=config.json ``` Add the Helm repository and install the chart, be sure to set the secret name. -``` +```shell helm repo add https://xenitab.github.io/azdo-proxy/ helm install azdo-proxy --set configSecretName=azdo-proxy-config ``` There should now be a azdo-proxy Pod and Service in the cluster, ready to proxy traffic. -### Git -Cloning a repo through the proxy is not too different from doing so directly from Azure DevOps. +### GIT +Cloning a repository through the proxy is not too different from doing so directly from Azure DevOps. The only limitation is that it is not possible to clone through ssh, as azdo-proxy only proxies http traffic. To clone the repository `repo-1` [get the clone url from the respository page](https://docs.microsoft.com/en-us/azure/devops/repos/git/clone?view=azure-devops&tabs=visual-studio#get-the-clone-url-to-your-repo). Then replace the host part of the url with `azdo-proxy` and att the token as a basci auth parameter. The result should be similar to below. -``` +```shell git clone http://@azdo-proxy/org/proj/_git/repo-1 ``` -### Api -Authenticated Api calls can also be done through the proxy. Currently only repository specific requests will be premitted. This may change in future releases. As an example execute the following command to list all pull requests in the repository `repo-1`. -``` +### API +Authenticated API calls can also be done through the proxy. Currently only repository specific +requests will be permitted. This may change in future releases. As an example execute the +following command to list all pull requests in the repository `repo-1`. +```shell curl http://@azdo-proxy/org/proj/_apis/git/repositories/repo-1/pullrequests?api-version=5.1 ``` > :warning: **If you intend on using a language specific API**: Please read this! -Some APIs built by microsoft like [azure-devops-go-api](https://github.com/microsoft/azure-devops-go-api) will make a request to the [Resource Areas API](https://docs.microsoft.com/en-us/azure/devops/extend/develop/work-with-urls?view=azure-devops&tabs=http#how-to-get-an-organizations-url) which returns a list of location urls for a specific organization. They will then use those urls when making additional requests, skipping the proxy. To avoid this you need to explicitly create your client instead of allowing it to be created automatically. -In the case of golang you should create a client in the following way. +Some APIs built by Microsoft like [azure-devops-go-api](https://github.com/microsoft/azure-devops-go-api) will make a request to the [Resource Areas API](https://docs.microsoft.com/en-us/azure/devops/extend/develop/work-with-urls?view=azure-devops&tabs=http#how-to-get-an-organizations-url) +which returns a list of location URLs for a specific organization. They will then use those URLs +when making additional requests, skipping the proxy. To avoid this you need to explicitly create +your client instead of allowing it to be created automatically. + +In the case of Go you should create a client in the following way. ```golang package main @@ -86,7 +96,7 @@ func main() { ``` Instead of the cleaner solution which would ignore the proxy. -``` +```golang package main import ( diff --git a/assets/architecture.png b/assets/architecture.png index 8107383caee698c0137f114433bfd3b7925f8b8d..86ebefcf93e0f961e27be77d1ba2a0f991a646fa 100644 GIT binary patch literal 9288 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 literal 9661 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 OLDU{dh=f`Bo8?N diff --git a/assets/diagram.drawio b/assets/diagram.drawio index 58219dd..15ca63a 100644 --- a/assets/diagram.drawio +++ b/assets/diagram.drawio @@ -1 +1 @@ -1VfLkpswEPwajk7xdNjj2jjvVCrxIckppYUxKBEMK4QN/voIEK/C9m4qu/b6BOqZQVJPq1Vo1jIu3nKSRp8xAKaZelBolqeZpmEbtnxUSNkgrmk1QMhpoJJ6YE33oEBdoTkNIBslCkQmaDoGfUwS8MUII5zjbpy2QTaeNSUhTIC1T9gU/U4DETWoZel6H3gHNIzU1PbcVSUxabNVahaRAHcDyFpp1pIjiuYtLpbAKvZaYpq6N0ei3co4JOJRBenHG0689zPTzHdft97+A97PVDe2hOVqx7e5iOQXqU8EBDL0De5zyITagyhbZiCQRKkhchFhiAlhqx5dcMyTAKrZdTnqcz4hphI0JPgbhChV10kuUEKRiJmKNnNWEx3dsoIyzLkPJ/bZaofwEMSJPLNrjJQ0YAyCl7KOAyOCbsfrIEpbYZfXsy9fVAP+oRnGpBlkH+As5ViUE/7H7O4iKmCdkpqEnTyLYyY3lLElMuR1rRUQcDe+xDPB8Q8MInPfhbtNx/0WuIDiNPtTttoCvT0l6vDb7VHY9UfJaLFocIrm+jMxbE4YnvBKsrQxkg0tKnqHNKZIE1GvyVlojicRwmiYSMCXnIDkcEHj2lEWG0yEErZh9rhH41CunNG7av2ZT0A+b/c5h18ebL+k2atsGz4V/ZYzor8zrQH9zgH2nedi//XD7P+3Z0hyePlD1deDn8OIVwxDXqlGT+g09iOd5kjzzuM09tRp0rT1n6syGdcee4zjXtpj3JNXZYLJS7sbnWtQrHNQsQ8ZyEtU7ORavLxkb84i2c6ajQtZ8/wahD4/KHTrGoVuzM8ndDnsf6Xq2OCP1Fr9BQ== \ No newline at end of file +1ZfbjpswEIafJpepOASavdyEdHtUW6VS26vKCwO4NQw1JkCevg6YAPIm3a66OVyB/xlj++OfQUzsZVLdcZLFHzAANrGMoJrY3sSyLMO15GWn1K1iWjOjVSJOA6X1wppuQYldWkEDyEeJApEJmo1FH9MUfDHSCOdYjtNCZONVMxKBJqx9wnT1Kw1E3Kq2bRh94DXQKFZLz9y5mpKQLlul5jEJsBxI9mpiLzmiaO+Saglsh68D0857dSC63xmHVDxqQvbuhhPvzdSyivLzxtu+xd9Tu33KhrBCnfjT7Re1X1F3FCCQUNQQuYgxwpSwVa8uOBZpALuVDDnqc94jZlI0pfgThKjVGyaFQCnFImEqqp9GHTDHgvtw5AidLQiPQBzJU1bcnWWwgGJ1B5iA4LVM4MCIoJuxAYjyUbTP60nLGwX7H8CbGniyDXCacaxqjf+YbhlTAeuMNFRKWXhjkiFlbIkMeTPXDgjMQ1/queD4CwYR15/DfXiM/Qa4gOoorS5qdBWhKn3W2b7sy8bstHhQMa7xTIQtjbDGleRZ2zRCWu3wDjFmSFPR7MlZTBxPKoTRKJWCLzGBZLigSdM9FiGmQhnbtHrdo0kkd87o/W7/uU9AXm+3BYcfHmw+ZvmLfBP9L/y2M8K/b1AD/M4D9J3nov9Soy+k+9LO9wfN/YTWIbHx+pua3wy+DyNeNQx5tRo9veHMHtlwzItqODO94WTZX1/HJfaa+Wzcapz5uVvN/IDZ9Y4z/JKmmJ750+lcpZOdB52ss758J2tfzfNb+eaAle2TWHnfys3TtHL3KgvAfbAA9Dd0BQVguqcrADns/7aa2OCv1V79AQ== \ No newline at end of file From 3b9d7921ca03d9dec55619b6995b0478d97b7019 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Thu, 9 Jul 2020 15:03:14 +0200 Subject: [PATCH 3/6] Fix indentation issues in docs --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index a075394..049c461 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ curl http://@azdo-proxy/org/proj/_apis/git/repositories/repo-1/pullrequ > :warning: **If you intend on using a language specific API**: Please read this! -Some APIs built by Microsoft like [azure-devops-go-api](https://github.com/microsoft/azure-devops-go-api) will make a request to the [Resource Areas API](https://docs.microsoft.com/en-us/azure/devops/extend/develop/work-with-urls?view=azure-devops&tabs=http#how-to-get-an-organizations-url) +Some APIs built by Microsoft, like [azure-devops-go-api](https://github.com/microsoft/azure-devops-go-api), will make a request to the [Resource Areas API](https://docs.microsoft.com/en-us/azure/devops/extend/develop/work-with-urls?view=azure-devops&tabs=http#how-to-get-an-organizations-url) which returns a list of location URLs for a specific organization. They will then use those URLs when making additional requests, skipping the proxy. To avoid this you need to explicitly create your client instead of allowing it to be created automatically. @@ -82,16 +82,16 @@ In the case of Go you should create a client in the following way. package main import ( - "github.com/microsoft/azure-devops-go-api/azuredevops" - "github.com/microsoft/azure-devops-go-api/azuredevops/git" + "github.com/microsoft/azure-devops-go-api/azuredevops" + "github.com/microsoft/azure-devops-go-api/azuredevops/git" ) func main() { - connection := azuredevops.NewAnonymousConnection("http://azdo-proxy") - client := connection.GetClientByUrl("http://azdo-proxy") - gitClient := &git.ClientImpl{ - Client: *client, - } + connection := azuredevops.NewAnonymousConnection("http://azdo-proxy") + client := connection.GetClientByUrl("http://azdo-proxy") + gitClient := &git.ClientImpl{ + Client: *client, + } } ``` @@ -100,15 +100,15 @@ Instead of the cleaner solution which would ignore the proxy. package main import ( - "context" + "context" "github.com/microsoft/azure-devops-go-api/azuredevops" - "github.com/microsoft/azure-devops-go-api/azuredevops/git" + "github.com/microsoft/azure-devops-go-api/azuredevops/git" ) func main() { - connection := azuredevops.NewAnonymousConnection("http://azdo-proxy") - ctx := context.Background() + connection := azuredevops.NewAnonymousConnection("http://azdo-proxy") + ctx := context.Background() gitClient, _ := git.NewClient(ctx, connection) } ``` From 2bf4a3d8858d62ebfadd6f2fdbf3c34e13493cc4 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Thu, 9 Jul 2020 15:04:22 +0200 Subject: [PATCH 4/6] Update oneliner --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 049c461..f586154 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Azure DevOps Proxy -Proxy to allow controlled sharing of a Personal Access Token in Azure DevOps. +Proxy to allow controlled sharing of a Azure DevOps Personal Access Token. Azure DevOps allows the use of Personal Access Tokens (PAT) to authenticate access to both its API and Git repositories. Sadly it does not provide an API to create new PAT, making the process From 240398620e09c1444ab6c4ee0dac8efa1cc7bdc6 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Thu, 9 Jul 2020 15:06:15 +0200 Subject: [PATCH 5/6] Add badges --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index f586154..40fad73 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ # Azure DevOps Proxy +[![Go Report Card](https://goreportcard.com/badge/github.com/XenitAB/azdo-proxy)](https://goreportcard.com/report/github.com/XenitAB/azdo-proxy) +[![Docker Repository on Quay](https://quay.io/repository/xenitab/azdo-proxy/status "Docker Repository on Quay")](https://quay.io/repository/xenitab/azdo-proxy) + Proxy to allow controlled sharing of a Azure DevOps Personal Access Token. Azure DevOps allows the use of Personal Access Tokens (PAT) to authenticate access to both its From 790eba670f5e15abee37fa14526e12d2eaada48c Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Thu, 9 Jul 2020 15:35:55 +0200 Subject: [PATCH 6/6] Add default value for domain --- pkg/config/config.go | 6 +++++- pkg/config/config_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/pkg/config/config.go b/pkg/config/config.go index fe70208..61b50ac 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -16,7 +16,7 @@ type Repository struct { } type Configuration struct { - Domain string `json:"domain" validate:"required"` + Domain string `json:"domain,omitempty"` Pat string `json:"pat" validate:"required"` Organization string `json:"organization" validate:"required"` Repositories []Repository `json:"repositories" validate:"required"` @@ -45,6 +45,10 @@ func LoadConfiguration(src io.Reader) (*Configuration, error) { return nil, err } + if len(c.Domain) == 0 { + c.Domain = "dev.azure.com" + } + err = validate.New().Struct(c) if err != nil { return nil, err diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 12190d2..51d5ca4 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -41,6 +41,20 @@ const missingParamJson = ` } ` +const minimalJson = ` +{ + "pat": "foobar", + "organization": "xenitab", + "repositories": [ + { + "name": "gitops-deployment", + "project": "Lab", + "token": "foobar" + } + ] +} +` + func TestValidJson(t *testing.T) { reader := strings.NewReader(validJson) _, err := LoadConfiguration(reader) @@ -64,3 +78,15 @@ func TestMissingParam(t *testing.T) { t.Error("error should not be nil") } } + +func TestMinimalJson(t *testing.T) { + reader := strings.NewReader(minimalJson) + c, err := LoadConfiguration(reader) + if err != nil { + t.Errorf("could not parse json: %v", err) + } + + if c.Domain != "dev.azure.com" { + t.Errorf("default domain incorrect") + } +}