t}esbNo%$GX0C2+xsCRgZPnX8lzqN??8w)>
z3<~0x^221=)+Y^%4?Hq?cStAea_!M)9$AA4BQwI14<$Xr7h|NYVq3&FJ5K8g*dERy
z?O)TG{4Po5Qr=$U;JrG5FY3;YWb(cqD{Fpi`qXD&0+ei!ZIBPp`Sn&`Ud@
z_58*odyZMH-yYo1%QVAA$raZQWKz#@R}Obq=bx|CK7LQjvww8kG9BflkQ}+F+xp6q
zCMP9)O*&OZW=n<;V1^v}OJd_gCUzFd+
z_6t~s-=VHg2xbOq2Ie2%kn`Z`#_thRtkLI+Hl?-<#FMT5!a3bV5;aY_n%BjseQ&iZ
zTEUx1j0rz+<2HQyR72voYOm?{o3CF|Dc9a)X;L<=Q|_#>v;XJyWQow|EoFiqpzeVgs7?pe=XtEX8M<2cG0jJHF6-Y8>jwP>_mKyrwKPk>a=V&Fqr&
z7XvDuL-o!jYts7y)^sYg*I#J4)4f5t3y?u6h;|2fk^TXze?7wC`P0
zfr;)ri_*Bg7f&!!ifY@IkcRu4G0Mds&s5ZyJnCR*C5Nj*stGSPm+pO@d|*IP$crR7
zc0t=$K1SdA+Q-{>I8g!?)4kedLxTQw?lxX~Gpu^;4cwj}OdqYf)i5Nkwd%fAuOl5O_sjFI_yt}n-8
z<*wmxo;HFAN8g`@*CV|*BT_Cl_2vuT+PJ!_xW~8G?z4TUP2@PwBewmg(K=zJG6kt-
z$FW82I^YpKG`ptT)?;HCpdO8(_Z|j
z{Mo#Z*{dy{B^XKcx$r!;P{0#&HuE&4YV)-{9}`vDg>Zw0;jzX=!
z?iofh#1fyMRE^51?99&Ge~og=)reOA`l}D`ap3&$x
z7eE%G*G<0v@ceoIO|f&(`%;YGO)J^=KZNgypH|#*bYl5~Q3yUq6$QM^1s#L5oRbgbG^|W8Dlh)0w8N6vaHgxJ1q5;u}NS$Tc
zY0^_!A$pm!)YKJ+9=?_fu%4}5bFoAFT#j+m4Vh;V%_(;}mMNJ#3xBVVZ<8^W+?~Hu
z$n18?S(}FkEr(0Kt8&gPGtIRUGI#!2pQ`oz;G(pGOGssv>%vQUCi$GBB%e!N`xW0m
z9dW$QX&zj3SWPoh)oJu@;hE|EFSQK#iW=qvn%+_?w3EzMSnpo-;l|>1=hP#ge;rA1
zcpIO8MkyiJ`&w&R&&iI%#-h_P);r$%tXOmGfSm6>@BJhNqj7cq|f;QmB=$5`^pY>8N}D
zK})VKbnv7y{#BB`xAKHa%>lcg+-Ky<^Ef5Pp5vl7rda#kH#CO-NN;}}n{m-Kpkil2
z_1Nyd*s|>zW53pHdEyh`=)UhKxY1ZOMVZuZzO_D-|LKL;%TImh`}kY?bQ4OxRAbgQ
zsOjv#)UGbAtr}LTyF7VdxV(~F7IlnnY4<3n(n{qB0y@viEZ)0Z&W`wncl?nNA>xO>
z#M&%jZ@=cMWuH6QJ2V|bl4qlnsTCQtvtjlTKC}JBrNJR(cdd^>CE^2=XZS(8(1;6x
zrDJb5JQ|FtbnG~Opy|PCcd@&}7PXKZYF*5|)5W!=eYzKezukYgo1f2rmS!G(Z@J&{
zdyI5OQNqogMYDco(8*?~Sm)7s9sm5k%7_mic&)eNdgPY*=a2qUesTKKj|XR4>vt2f
zS3CM_ukkna*{0DZc#C`mqg!HGp?7qfb-Dk|Y^Cl9)uoc=n7lV@Wy;Hv?!2+}?LSxc
zbNbO?|Cdv-Zp&Yly}J6Gua7o3YgKP{JOYuU{>yADj)Asqd&^
zOQQUwu>rT<^yE~2uqkp;RymroMBGgH$)5f@X@)7M+N=HXgh-%Uz8|1!Av
zt2Tf9utX~H$2klPF!UssF(q8A=(l_n_T|T{
ztPb99OEUH@>R%s~hCK32zKz|o^t^%3E!7oo<+4^P`h?Q)R%(9JTbj<^qy`FP^(#(v
zVO~GsPAlchYgeSzD|PdpopR8WvF_P}I#|T|Y0KV#JL!15yY+|LsSGApN*x1C*w(d5qbi27Yicz1J~
z{t&6l*4p*<%TN2wPwVEGb?E8dS^qt!>%|6)jtx<(t6gT}(wS|Yseba^=~63`uf2~(
z+bg@>S#cxk&9A+?UVgl`3gzuP&=~C&De-E>K?oE2DJ$l4hZN(aKVhTC>&$RMLT%`&
zM@J`$b{LP~o^?rmeyEw7e|d|fccO5x#HWi%d#;~4TpuPYg)&zLySN_=fpfE}+5>SJ
zjhPQlC`((N720J&DevNyTpd;wB)_6gKYSv!W6{U0c_z!rrTqUBRz0c0(FL-;2o~|Y
zKM086>^+vq8x@r0SmA>4XkNM{@~vUMktn5BRLzdZW-UIZ2X-vT61k&f?>qdl{s
zL9J1JG78arkwlseLUJG7(u>acanOw;ZKR>G7y}J%6JR}2VvQlDc6&uv-jj{>i&J#f
z9vO6Bz@Qi-m~5oGNn1TyQVG$KqqaCR0g5`ont8<|&%<5)#m
zJ)GC+Ebq&FqM%QdB$FC|#{2R#`aR^Tw9C}XF
zOom+vBbj?8ZU_4&E0u=HF}KLGaxz)9Alf@S5rU96PPNiHm2_hJP(Epl1hH!wO=aN8
zZ61OAm`Sk7l}U07^#M?Aj1%j=(+G&6%TRHjV0<&0m7jRvFAf#Qx&=>1zvE9-c;K*O
zEXMd%fI-j%URb5~Gh-Kr?4EzS+w;2YaY#^aUm0k4!`qRAH9blsgq-G>cx<-1MSAjw
z!X^233H>z(WjD$EWGhhrT^tWiLj(@=!k@l_!K6nTEtv90+(1q2c}qgEwvz`cYCtmr
zUC?$_puuXC37CR|4Q9lu;?5Xp$x*VZ+Pm>=*=^Yq$~`a-mK6y!z?>ZibiFlbxH&BB
zdOq$=BzMyaaKu=HXV20(49oP39Z1C)GiK|7@KOFg*H%40LQHA5R6<8ib2-qSwQ8&5asH5&t!pEa>Y-y$sLbh>u>v6&ed1
zp_RK^@{ma$Mn%Hty#p=SVdy>?p5hhd8wjMcNOwwBeP)Q;O452mc~CuJoDfh+zy{1hZquNj>3q$%R}B1_|xsK?SP@lEgB(Mj2%@RRKRs*G!0QdQSW7a
zBawBpj|3#-{;WI1Wl@C$3<1cBNn#z?)sF-rQ>RgEMUfdWK*iO_)m@db8iu=H&GQzD
zL&2_w+9g*dvvZFqO5aF^gD7E=49HN*8X5+n^qw1vptMObAV3KVl7gQUa#zhhDVCVy
zJ!x#d%sgr6N!;E^Kz_#R+{sn7eD(y!yWHq}gXA=tDI~O%=|tSCc%D|UU$!K62Y46I
zi{&a@0fHN4BLVEEr#~Kum)?NL!I%WCFTx;_E|;kf@(bPuiSD`}yW$4*1o=W9NQ7Wx
zfh=SL4oHEb1q_gaexe`_LW&~uMJ(wi1kV#`9M^_DfAr#y0{ybrjD8|x4b*)F-(?%z
zB`+Whwt-Wbc@3g
z(OS(lhU^}d_^#ghbt~T$;}($B(JDX;cAQfBgfG(gklI4QsNM7A(mt7g066*-&$F6V
z^F1!Km@tmNzzGW}idT$qGv1w42aznIJl_)tcOc#R!
zS@-(IcXk4Op~EQBt*Kg&Ow1<6lrotUR>=)>RL;(^SP|-07^soq;g`NB-wRRLtx!vZ
zS0o6^_))^C@cgy!L7-ak8UpubtjhVDTt-lG2SOw#5Hg<+;rG?dW?ixyNuxpuJcj
z%*M|{f+1qNOj|HtGg8?CGcMA$!IE0!@`g1Rl$O+liIFMh8&I}Qzo-3m;s~L(Aq^#w
z{EDaapBYX`-%H@c$Pavp&)1bRqyf;4jF;Sqpiv?aY**N-0=H)SoPAlxAv2?{Dppql
zSNzIl+hEeP`=V#Mgu%9JHC%pFBkMf$tRUhsU2EUfxyW+gk1Y4Vz?z^0X>s-PQuDCB~P_^(mDH~Y{70p#jFIxL)S?5!h
z0QB@uJ!Te=aP;(4?CO@yp(qf#kqhQ41*C_d>9_DfG0JlFl|-j+t)X__v;WfXGz-cf
zmZWA`={(FgDen{DT;56e!B=u@lXfi#no&gHu2#W_86~!)K%MapHyPdmoW)`qfQK)m
ze;4LAwx^zWHXetgtz-DGK5@KTi)|70OL1&a_Nqxo==D|YC4Q#z`%YB8_xtUtgL2J<
zj!7xlxr#Giji~r*+pT{6-F)|Va2gZ1M~
zq}-79!Ux7siRop9kx3)q;sVU!+SHIRxCNk_jdX6U5aek}nC|c4(vt#p2TFl6zw7`A
zeh~~%TcCb)k$VK4`iaAmDf^bD4@tV#ngHxVAg<&R(=j489W!B*J1Q{&35MVY#gIg=
zhxKJFqS;Gl*IBPctAat$7)
z3PtBXUl7dNz(pdx6<+FI`{f~&xFtLzl(&j1Ps7X4vI!la5XJ^820=OUv$^TlJ|P?g
zUid&DAH#T-uNA6>2^BGS_P1Td6yz{fP{}00pvjB(Y7b_9T$C{Ai2@wXe>?x>^Nk~_4U{I}`r6`lhCJLLcBJpC?emY~=uC}Z
zeTS`;X_`k^Y51ELwY0HVbHV_IpBH|i_(?j7cbR}-rM&l$AJjz8jek0!u)XNdmhG4+
z`UNNT1A*Gvpqkko$$
za{62NT9nkK-6VWFAf}?#s&4y#PHl2@IUscixpOq7&6_W#`iLKKFYp0n$}hGi7BA!E
zQW3`vV#7{koY(7yT~zClLKm9}Yw6!X>zd@6{$Kn5QS8$`j*0~KAvxLl-}j9}9COV(
z=W>2O=Ed8i_mg4JKnV1WqYz-ABm=r;q1Qa#b-l>oln
z4ZKJ-cH%wvg}^fJxA8r?KmMoMVJDe)w24QG%qAu9WaY2mtEbs1_J#UR6~a=T0TI
zbKc|BI%MUCpR9uhsIrw;Gf67V0Wv5Eg270T1Yx2E>H`9iBno5<1`@*%QN##DC=d}5
zkx)c}BuSDE%S;gFW|~XmBmOSVuhU-Vu*y^#($tTc
zHD^KNZ7`Bag<@{!P~99M9UOX9sE~h6mip{OCKNkvx4UV*g6CnsL(fp
zd+>a=Jsg2Yu*X%6Rlq3k&)Tn8kfrmWk9*=cCb9DR|u-bNhZ3}B6Cvkj^)$s?~~8QsZk%`;AHG^pwT0Z-IWa!
z#m{V;5q~tob+C!Pf7?RtS%LXC-ed7_+rygeY=d|IpvvE^YJ^GMGSJep)Lf^eJCrwx4yO
zAa#c@fH*s7&-FQ`QRs@fO@q1XOiuM1$QKrih^Ik_s;NTs-m(~JYGrw)LZ|&_?@~^W
zKtru1sD+0AT;=trLP17Dsuj$p)Lh4v3KR6q+ChvT(2npp*C;B>l^@*!nH#M^rk3Kd
zOf$7xG9LiwRmZyZOfMNqmDe0v1kA33CpWNMzuOE0gETifqN+|SunBk6T*!{
zg$J2@Q3un#8hr=sJerxw8}gI_G+WB!aW)pTjm>HwCLIrw*Vko0FDA#)^^Q6V4MoT6
zKnYOqb_@7urogCggTLdv{zO~0&@T;Kj(61{VyN$M@yq}2xpDZRSVhM2o@yv0o7%KZ
zjf30_hJaVzGF9pmae2VwxaZ#Y=X4E7)KogV?d)UCxod_yU)2oCpn=I@epG;Io)XLa
z>VV%OgzFGi@EeIHNyA->Z~+-A;)0j@I@jRKX0c%3zpbcht9UEOGW~z8?($^DXtM?#
zKe~}cK`Jc|+P2gmLpI-r<4YfF8K#L3Z^jUh+`b+>wt`<7R(_C_;4-X7o7h^p9|}ZL
zZ;rOMi70C$Qk(J_0)$*fRCq8`C|P?tTjAo$ta=!ay3Vj*6V=Pqq$*s?Udc1OONNr{
za7x)u4hmGVM^k&hUfwc?Ye{EU}{q1xCVJYNm%RgwIqm+ma#wn
zM#V%EWE&nLqZP^84FnE}pqZ9QL=x%a@MgT&M@uMn(%2}`rbyHGnzHe+lj3Rrn(DuN
zP1zesP%LBtCD0lL7osico(xxU%gU@rC-@c}fH(#SS#b68_|*x*pTq%*x)xbE7fhlF
zLFS@njnm=?yJ(qWSl(vi2r)P3>!4wmc8vX2WVJg-fU!Ck+geN_=@Sh*ntrz~k$aB{
z*I&wiTVIIh!!b57l(qc_XvV5LbiLsOpB&v#Bii8_G;D&fJQdN#7Ty7Yq*d7U2F6RsLLCE=d
z7K-y;^uL5k``BT}713LM2paAAI)KzZb!}!q&ReCI=C#qYJIny*4p4C1kA0PuP$2zsplcUqPIpH}8ve4O(51cw)Wr+_8hjQ)QE1s`
zyP1d-IVoig26}Kd0WCZ=cX0gplj1~?5v~T2-h(~gdDE;?7Nhcku1darpBa$91%-#i
zH5~{r$nUCwRdxk3-hi5O<=BLG6pEEE9!CNs!Pc25Xq_@)}A{r~?}!32VEzIKMz2dIT~
z{0$8c1F$1+_xOK3+&ORc19>7oRhorC633w@qgG>zm8DzVy=iA#1772Lr}Ff_5}cAj
zU#I)lns>g&&1!hKVLGeczK7ni
z^>y6U1-pTjV4yukT(`hYs`^v7v;E27x8v9G9)WuL{zO{icWc8dOvmaEfrGfwDw-tI
zW&)Wgje`)P#3)0EU<4u~B9R0VNFX8s1(GBkz#cx)k^Vt#!PEm-#QPB!wb{G!`Wl$T^M~`F9wSyX}x1!xMsb@=Pf5
z;8<82L4HnR%}*1$Dua7?mw+BEw7JNWg1ifqb$A*z)Y%|K0S
z-4eS(#1pBxp801AZi!OFA?Uz2;ZTPAhk<4@gVLjpDtX@Us*)2_hxRGsvwcn%xQTcf
z%xg^XVy%QQ>Z&vD$52zMyE>L+a-F|Hom*
zY`MUud5JMHNvu@@WBM4c1A$@VpPm#}4j1&nQcz_D0f(vQB&Kzd%S(-mh48G`Ay(n3
z-VeqBgL*+&AExS!r=WV~dujO4Rtl#A^3xnL8y&?&pI$to2`yDe{<0BFY)JGxZKk?${jGw#|xScof5cp+c++uH@&PqMq-c`S3D$
zcw*9*CE2>mK|`UdKNF^asi?vwdcJNSh$}GSvf1TWs?=#XH`E~D?a~d7fe6$!#+^}g
z#`E4Q3*$j1nP(oL1emeIWtAVU=#zW5#Qxb5MH&*(3e~19htl!E@xNa~6ttF@%g5K`
z%nFyul@&AjUa&48A4j%M34cTwF<4O2CK7I{PR@U+7(61YG1gRlIwzXvX_~!@pPxNn
zfgFVZRqSKRUEi4VH;wgtr1-|g+$U#;L8PFuh
z!=44{ph*+*s!yk8RxLghBGnlI-0}fuWo<`8Xklb`QcG!UZAxivWodG3ARr(hARr(h
zARr(hAYxrYP-8J}WO8e7FgY)FFK-}OWoUI|VR|e`Y%g+Uc5QELF*I#;Wo#ftWoJMx
zZDn6)I3P-IXl@`#VRvqBUvO`4Y-wv~VQnofWNBnyUtKLWYH4j@UubMyGBI;?MQCDW
zZewU)UvzGDZggLFZ*pXFE_rQiW@~F-Zgpd9Wp8w1cy486UuWVlOmeb7U_rNo_+j
zcOY&q3IJj;Wiv5mF)lGKXLJNJ-4bK07%N7?0(pDlCcCds_R|1s$0Wfdsp>rBN8L#
zy4aM-a#g8V;K|SRU9w6_E#Aw@)S0fd_|HsMpZ=DsDwVV0Z^d7S(eNx5$3>Z>;^>>c
zc=7z`J3Oky)y>Jz;@5va5|;R7sh6e9GF3h`;z})4sq(oJZ|Wpgy=XWw;^t!fQ#72e
z#KrOZQ8c{BM2QdaLoCL5rOKjI#Fn1@rsmZ^mx;>hcQY@OqDu0mNM*jPCYFF
z%jtMZgh0L!uTwDy;)N&osx*nt{qNHoIgiu4Fzfq`fA(Bl7=a&;OQGkLO#SmydJ-ok
zxY1=;J))=T0sQ7Nbzi=sfAKiU?PI_8bwjz36}|Is#(QbMYu3;Gv+L$rs_s>w{o^Cg
z6@RU>Oy;qFmNaXL^SrH*k|`v!QZ{@%H0yVG9MHQHxXfuLR4!+!q6dGc|KLfS7=Cn8
z&gsd(DdzR)`1}$s`Hu3id0t3LjHlY
zZJqIUk)+B$!xnW0mlDI_kUi*ZG(EiBEADgl*k%NM+K~KN{3!SEVZC*vg
z6L?A`mtwF^;%X)Sgjj$YryJ1=c__I@(t5-xJOh1Uv=fu0Sjl^mLLgprR*>U4y&17C
z6GT5zu>xV0dQkJaV%22UDxQZAvP>w3jHQ@sQEdt(4&qHNvt&-c!4QYNs9EUg&cP}4
zTuD5LWhF`3q*5XPU-@~P*J!QFawFtS*Oj<;+NiK4OTfluF&e!k)rwdn8CtZ=
zFvu>T6MP=4Tw7%(C4yE%UjwmB^-QLHf$zvCliXAiKFHpg>bXq2{7yNB5P8twgEPJr
z*tFk(&LXX;wjn*F6~iXitwm(i?F(u%xX
z^Mc0R%dR{lq~=YvqKs$*!{&-stcU_Zm+mH!CTb`ptsya@$0URilFCH$Qn76zEB45R
zspl&(lLq==PnL$6g5)-!jlv>4wA+|2m#5z1P)Op8PP=9b{uG;DbfQRt!2UiiS)iO!
z2NuW;I8=Te{9Y}?85WvVK~L*ONN04ZM1k~FC}L&R!cAl$}W7`WY
z#B112JxI>}oQo*5+?Ox~*fawqTG~*~IS$)&kpED2;S~{o!a5IfyxMQyD6jgcpmjGYJUF^pNk0VB={
z6rDCDLA|9m#zjxFT~#Vdz1J)3i2vyY)d31is79i0eH2c*uE=9VoFBIuSn`n
znw-@+Viwx0wm$Agi~}!QCO-Q10+||`g*3=+Mo~73RiW^okS9%D5Ck;VvH%;1)in9E
z4Y@`HVzv;LiCRUNaYjt(nxKY{@(}zMh#|LXR4l(fYSuSw@*3<32llnf^GuGFi<>+l
zDqUeul4V|JP~MODMsVt7|F(}COEDm`!A&kRuL(<
z5n>bp5H^LHCktfKAYd=z6v<(bA`dgdvE`8s2*Q@M9-&+TsI@LsBA%zRDqZNe>Ip;bQD+GZjY^N=fD
zMUQko!1KW(C1rTeNf5iX4NP3xka28HZt0-u|7e{R%!ic`x0!fD-V>wcXuYadvf^Tm
z%n(z;qZo17NCNzkgJinSE9-Mf84m#dkz^YZ!^@8X#DRmb69JohnWPN-?h|Pfn4I5{
zm0PV+OY1;Klw)bnH=rT<5dp?@V+H!DB+B9_a{V$A^@qfuJoxoPT`rl2^EY8BK?Z5zQV+JOv0Sl72+MXJH~o1}VM&$dY|p^tht#O_Q8m`1GhnrLmT
z-WaCDgdG8CW2Q*%1G9}%>nvz|CfveK5iGm5W=}0DTbFDHL?oTO1M1g=E^GxmFt87R
zyg8)6b1b0MMbH5OCzSfx6VRDc??u`xsO>wV+O^eF^^RV={N_LY^zCqIPG8HFCf-s=Zw`HAM;DQuw+x1~*6F&fIZQ&mI
zOt-DGFYFk#b`*Pd+!-ewkYVBk#^ylzq)IJ@;xL-pw5&X6C`cMKkBA|o3dyw|gcssK
zoF}69M6G_QG|iRN{hw#pS=R={g{hCm%2
zpzb;sKsarjzwk;XPFR)%d>)}*!ePAPH1+HfS*%!53RiB~x8Hsn5j=DY0q~IlRvNKO
z2}&S0Xa~qV=tL4J9X^~hgo6VnCZeWCEoEC0&Lo^)?j^dhjsdpkcJ}E{-KVFrHl8yI
zJ(k-i^suZ$_xDZyoZES1@O!@LIs%}$1iZ0=9hgH0100EfIM_)&iST$RKG0`quHdR^
zOhXfAWS@S~^TG8GUb({N+i{)2d1!l`+ey$4X~Qm++X$jfTE02*+dJlO{nv0=Yzrp@
zBYBc`L(>b@D62Kw^UuO+PxAAttu8kmO$Oz)#o7gNZQfqDXSR)fY>?u|^pP0-3klqj
za%<9iL)D+$OhGB^eYR__v@PMpx0}YgAMqf+vh6{fvmCE6c`5`7LXa2Ftp8GVqJjiWhl->Z3*SzlxpaU>MWdV3!4H>h
z$YlV?^H{IV;N{T|;^6f1&o5sbwindV&GqZSckM@cbUZyBj|bnKjIYJP$@t><>gU6z
zJX=*yqxVf7B!I~1V{|VrkFVcGD!)%koilW4hHwN2WQm;AXt^)yJSE&om5Fc+=RXdzc6%}!$h
z)IgnRx~8Qm~L3!yuP%o3#xa
ztyL64w07;n4rwDGK@nEe6;R|JU>P?Q3d)};3e(5{|G1`!hFdheKZXG*#Sm*Kef6AD
zu*{tZf9OSPm&EzWlm9vTWADk|dj9~5oz-NhX6CWh}F*q&X}c^h!s+^j5Y
zYkYM3oay_c=$flMH2_dsWoNvG>UK8{?QHYF=Z2%h4&9q*s;WJe?U5q$dy5Jb%(2+n
zYx_*>`xlK2?o5dR%40HkOj!_CB-ORhVw90>m*HCuy*y+_OTgx!zkD(KJdZxK*@3Bg
z2ABw_r#GY{9phByJmJIW>SQt<^{7ecR8f#R+9gGhwa&gsX@S~i`L>x1#02t)>MnjL;Kk#e
zPu%d%9kO80vkkQHVLH^x)5cC?vf^K3Q3O;QVVhbeMjf?(2AGI
zha{^rI6}UxcnlW4r9lA~D*A+v$eL&w9Q8$}B(p&{mk4$PXugeAIS#E#?TLi$#Jw%E
z;MMmlRtxN@VxEqNT-E2P9hVeHrBNROuOuc9_U2r#Tj|u!F@$)tO--Md4h)DHG{8c=w
zvf|lqb$*BEb3{z@J1xLNTagQZ^WjY10nv-6h_JetOQXJe`kC+FA7B5n{g6ml!oIXS
z)wvcLP(+nP88JpxliiaFGd0tr+#V!0$7tjZ=oPqAXok^!@(_eN#T*at7D;L72+z>{
z1NO{cRnPmntyBajeFxr;xPYTgM|IyI7Ik50j9G3h!r?BLPdWG-yw8fSx|KHH4rpoM
zKWYnc+4+8{Yi!5!O^*SYf7e)3!_TJzU=lpEJfU%G0!M3tc{F|JYd40bVj+p*fn4A5
z4Cisbuwk`C)(1AD3lxgReqA#a(J>uS(u|VyY16$JSK*|
zE!@q(H$a=dh_PdA7qHfbCU~+1wHAV+)3hfkx;3Ja3qC2a`O3BfHB~h%QW4)a+2aid
zpkSBK@e|+Yp}z2#W?7!_f}O4@6s5}GT`^}{Hq!Ccn-4AyDmSz%ky8t8qb)Qs4s5$2
z*}q`9{jG!;7<3bbO}afQ6+Oy~Nq(>I{CMNP2M?Q1Pn|5o$hsS^{Z-|>{0SCpoB2Ly
zy$ut=@m59T&dZMvw}D_sBU?_r1CWH6l70Q@uBDEQUrOPCrm=V)9@Oa5xB@wLZ=8EH
zLqB2xbzOIdd1OR7n1O0tk>}}VWIZUL-Ij12pIqTdsD+e22OZn3?+y&u
zdn4@{$=`?L%0v3l{P@O;yf7#gESxJoPw|%^0s@H>GY71}2Q|b=Z91mnU=us<0PI7@
zj~0fa`krT9nbj
z+N|BYu;Z(QY|cs@OAMm*~U#Gx++>5iL
zf_lVL6}sf;@+KwJurp-{W(R)CoiM%Sytz({+HV1;hiyUq0dx
zVR~|Ty?5R>+?761=+u+_j6nh0OwptL%Ey~Hw&IUn&po5Z3`>$6QJcx;-bw3p
zt;tLFGdrVoBsxRdS6`fybPn%AeTkL3mrDm3?XiMC>^ltS+BHS0`y3GsPg~e$p-6QI
zteP*m!JE=?EM2(cU-y5*tzh$=ZK$93Bcazd7hE-Bqo6}fe@*q)chCtM2v_NC4(WwgYy6v76<3UnQ(
z2Iec3!z8lWK8-z|UWfUryCAVku*32Hk-BY8Y$2trlJk@>r(315KWro+jh-ibmS*oG
z*$ZUuhL2?r`6xGmn>4YL1`1ST07+6F!d7a|V>LJRb;E)dfm;WzZYi%^hMwv7lk*^-
z@`kQ$vCNWB?y?$Rk(==qN{2Rfa)7^s|NjI1z65antgGp3+H)J%TB*nlVh$fe!0q$>
zZf5=BpB#j#wtqr);o~q(&?bZ6o
zH@sY%`9yN(q%j;>5+NSbu|4;SY47vWbPk}z`E6&?81`X&=~h~Mso`??is!pdw(omS
z-cH_+o`FYh8eesPMRw=~L$`|ZvAgUf-8oNZgh!i%J+S)w-e4^T@06uG+X6V~982;zR{&R2tS=)bp>^E~&!tb9!hI}RKQE1qj
zH#qtE6!Kqi37h+@8F*|OZC%@U*96=#+9H+9PGjJYD1!qaGUqB*=F8`zJG`@_^VKKz
z4?m-}H^9t_#)Hk}uIoo#FfoxsgB)~ZG?5|sMbC(+`wD#Yy(Ar&s7cYiKeRoweSN4yI;F55*wf*~&Qpfe&
zU9_}#i_iB+syMk%=*)%hyP=nNm*VOCr6C4ALpNB+>_B|kl$9+7MDlm3;N_MI(s;ib
zzS&*3r3#k~u7jQCby@P?c8PBqGvS3a{!S7IK{pg9bor*v?FEAFImOY@{J~_QTAHN@8-@dt;{CNHL_Vw}U_2jD8&hxbT?dbUOc6@<#)APwc
zTe;faG>#zr{eH#uLnOPk31I9v>6Ze=4s9V8;Hau1r4SDL6zn5+U=pc6zVpn?zF{ul
zsEK7Kc<***c6N4m=6QBBq){c?l4YIWinCYZChuy+y^(#QPh-pmj@NFOQ{OH0^!+BT
zRq4-@g~kf-V!A^Hntv>YBpxmJ9+QQEwH}BWpB0K4(6qbe%s(gJz!r)BxoTICcKV2>
z{~$g*k^ZD|QZDV~pwm%NuX8Nz{`^FsaR_V?T5`hFYt$5#5@{!3Q=?f}M+W{YTixEN
z`JvG7qu+nvWm&P~Zzu1h`!>P=UCekeD}9_ooY
z0w*ZdhNj=U*6*G6NT;8g{5r|8xgqCem=waJ0e(7NTqNQh!M$wS$&&~1&vE}Kk`OWw
z^oJc7j+uP}3EBQO@jxuLb9kxBP^NRBb-hyErmP=hcm}3AYX#Ji%`~99K<*ERo?gt_
zax80qC4yZzw~eBJF_9U6sF))nW0Q1Yo$jpH+9e>Ii-oj^lD&o0h_6SetG3oFDwz6@
zd`x!!rftRHH(jngDDUBVC-q*dvQx|Lv%GN2?pBReY9-Xrwq*4Ql{Zp#mNQlP(>KoK
zr1Cz2F8Yc}ZKu@k{;2H{LK@EtAsfrxc2^8{6eVL!+ebG!_jAJC>XbO=<|19{pL5#!h5;uhVm-8vw`zg9=IQN*=?ioOs}o)@>*;aamC80+5RIQ
zcK_}Khq2*ujMO%5r%DctoTU!vvB#Fli3K1kRoGIJeO=qdQ=Y3PXVU)psIIrNFqt=p3>pJaQd?{dkTk?@M3{w|1~%b`(0m
z4!c*w&eeONgB9N!ZMUNp>W;cymHuLfWK`uupDhnDrF&^>P&-Zwqql=j^Mc&H{kGeu
zEiHWWBYZPFCTO$wJ+DP%{f3>E$C?w=4A!zY4@7o~oYoul^Yi%Nq<%c1+1w{*BOwoV^j-N({z&}@ljqf|sgyCcJ{#~dpZf?7DPWYk$?Kv~vazp5Tr
zYxEUYt8e%#{sk$G_~2&L4rL){kl}Z1IIScK#nek8Z}Sj?({vam>-<
ziWPK%>zWcS)k=aI6m+9bAaNy}-dL^4_VX3l>%+nL%S(K7(iNLEd!^uDi!ey^B%ft`)F7|~qvlyfOQ$9>&;#iLC6W6{HfNvw7e09uSvF^$;F^f3
zc{UL|T3Q6mp|PQ7dtiH%eyltUGLb6mq11`eLZMj7!_bl)7i%Dr?<~UIKn71}Qk-U`
zfwi}`aW%>n2P6A1%2oHt(p(HglVl8dgj@~!MhpTl?G3yFq}zh18^C=nhnvur-e
zzA`UgW2NKl+g7+=Lhb;7LfLGVe!@9!olIz|EKM-n@}U3vs7=D;$sP8OSbfGc>9Sk1
z?e*YMLHSo!vu^(?ZrxlBMYjnZDiTf`M?~EuS92Ul`0AQ8AO(v<(Xiqg1zf~I
z9x&(XR1al(dH%ORPl}!c^Ars^#c{r&OF)Z@)r1D<4t~Pf8)2St86u&WPgY7^RtypY
zrjuf6^Pquj7-}{}wh&svciWGOsLqa!s@qjk}^#<&ahbRDRi0tecqAm@|
zQ^`;n$u!!?)HsppQ^?dehrn{QkjQ2st?L+2;uy$+g#<-okSt>$rWF#@>e?$tr732F
zCuXA(Gp;f{!I<8i2F6<$7*7BpnIs2??Vvp*i$rpNirRq+gh=H%sgMBT11})h8Q7d1
zI9J|4PJ~jUxxfRez@kjx^hoOU8mSjXq@Fn;^-h4)Q*A)z@=>G6qZ(cSx*zgveP;h>@Q=a_zbSEiUMZym~3MGujvuLWRm9UW0;kj>tk
zSp4l$jA>~U@M#gvnf4;6
z8!|9IrFE|^6ZY1ur1#wF6|aSY{)n9bkwPHx(60V{P*tVLURmK9az@zR8t{@gy4PR^
z)=I8ybbfezM+F=nN;UlPBqQ3Tg!9L6uBaO+ozmV0BsnFe{7$JINq(1?()v8FeLWXbchAD^jBjc_Bck;M;?N|71QW)&K8tCjQ{
zvgE?I=x~^5*~H*3M;J{doJ{atZ5Y8%z%Q1q&$Xvaig74BiujX3Kf`Bkv3x?*09M5o
zWDDi71H|@ZzRtmP98
z;5N}cxXbq2H~PGm2D4V7AVGqHV7c0L-`>R@*n`7yG6mrNq}ibl~G_?mJ%L@qTc`j
literal 0
HcmV?d00001
From 24c84edd7b8737d8de7dd0bb5a907a8d6b0272dc Mon Sep 17 00:00:00 2001
From: Jahziel Villasana-Espinoza
Date: Tue, 1 Oct 2024 12:32:41 -0400
Subject: [PATCH 17/26] fix: attempt to prevent race in profile adding and
deleting (#22338)
> Follow up on: #21891
# Checklist for submitter
If some of the following don't apply, delete the relevant line.
- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements)
- [x] Added/updated tests
- [x] Manual QA for all new/changed functionality
---
server/datastore/mysql/apple_mdm.go | 29 +++++++++--
server/service/apple_mdm.go | 3 ++
.../service/integration_mdm_profiles_test.go | 51 +++++++++++++++++--
3 files changed, 74 insertions(+), 9 deletions(-)
diff --git a/server/datastore/mysql/apple_mdm.go b/server/datastore/mysql/apple_mdm.go
index 610a25db52fa..ad18242eb754 100644
--- a/server/datastore/mysql/apple_mdm.go
+++ b/server/datastore/mysql/apple_mdm.go
@@ -291,7 +291,9 @@ WHERE
}
func (ds *Datastore) DeleteMDMAppleConfigProfileByDeprecatedID(ctx context.Context, profileID uint) error {
- return ds.deleteMDMAppleConfigProfileByIDOrUUID(ctx, profileID, "")
+ return ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
+ return deleteMDMAppleConfigProfileByIDOrUUID(ctx, tx, profileID, "")
+ })
}
func (ds *Datastore) DeleteMDMAppleConfigProfile(ctx context.Context, profileUUID string) error {
@@ -299,10 +301,20 @@ func (ds *Datastore) DeleteMDMAppleConfigProfile(ctx context.Context, profileUUI
if strings.HasPrefix(profileUUID, fleet.MDMAppleDeclarationUUIDPrefix) {
return ds.deleteMDMAppleDeclaration(ctx, profileUUID)
}
- return ds.deleteMDMAppleConfigProfileByIDOrUUID(ctx, 0, profileUUID)
+ return ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
+ if err := deleteMDMAppleConfigProfileByIDOrUUID(ctx, tx, 0, profileUUID); err != nil {
+ return err
+ }
+
+ if err := deleteUnsentAppleHostMDMProfile(ctx, tx, profileUUID); err != nil {
+ return err
+ }
+
+ return nil
+ })
}
-func (ds *Datastore) deleteMDMAppleConfigProfileByIDOrUUID(ctx context.Context, id uint, uuid string) error {
+func deleteMDMAppleConfigProfileByIDOrUUID(ctx context.Context, tx sqlx.ExtContext, id uint, uuid string) error {
var arg any
stmt := `DELETE FROM mdm_apple_configuration_profiles WHERE `
if uuid != "" {
@@ -312,7 +324,7 @@ func (ds *Datastore) deleteMDMAppleConfigProfileByIDOrUUID(ctx context.Context,
arg = id
stmt += `profile_id = ?`
}
- res, err := ds.writer(ctx).ExecContext(ctx, stmt, arg)
+ res, err := tx.ExecContext(ctx, stmt, arg)
if err != nil {
return ctxerr.Wrap(ctx, err)
}
@@ -328,6 +340,15 @@ func (ds *Datastore) deleteMDMAppleConfigProfileByIDOrUUID(ctx context.Context,
return nil
}
+func deleteUnsentAppleHostMDMProfile(ctx context.Context, tx sqlx.ExtContext, uuid string) error {
+ const stmt = `DELETE FROM host_mdm_apple_profiles WHERE profile_uuid = ? AND status IS NULL AND operation_type = ? AND command_uuid = ''`
+ if _, err := tx.ExecContext(ctx, stmt, uuid, fleet.MDMOperationTypeInstall); err != nil {
+ return ctxerr.Wrap(ctx, err, "deleting host profile that has not been sent to host")
+ }
+
+ return nil
+}
+
func (ds *Datastore) DeleteMDMAppleDeclarationByName(ctx context.Context, teamID *uint, name string) error {
const stmt = `DELETE FROM mdm_apple_declarations WHERE team_id = ? AND name = ?`
diff --git a/server/service/apple_mdm.go b/server/service/apple_mdm.go
index 4387367ef017..c96777ea580e 100644
--- a/server/service/apple_mdm.go
+++ b/server/service/apple_mdm.go
@@ -770,9 +770,12 @@ func (svc *Service) DeleteMDMAppleConfigProfile(ctx context.Context, profileUUID
}
}
+ // This call will also delete host_mdm_apple_profiles references IFF the profile has not been sent to
+ // the host yet.
if err := svc.ds.DeleteMDMAppleConfigProfile(ctx, profileUUID); err != nil {
return ctxerr.Wrap(ctx, err)
}
+
// cannot use the profile ID as it is now deleted
if _, err := svc.ds.BulkSetPendingMDMHostProfiles(ctx, nil, []uint{teamID}, nil, nil); err != nil {
return ctxerr.Wrap(ctx, err, "bulk set pending host profiles")
diff --git a/server/service/integration_mdm_profiles_test.go b/server/service/integration_mdm_profiles_test.go
index 49a1f6eab0cf..1566fae3b114 100644
--- a/server/service/integration_mdm_profiles_test.go
+++ b/server/service/integration_mdm_profiles_test.go
@@ -4359,6 +4359,9 @@ func (s *integrationMDMTestSuite) TestMDMAppleConfigProfileCRUD() {
testTeam, err := s.ds.NewTeam(ctx, &fleet.Team{Name: "TestTeam"})
require.NoError(t, err)
+ teamDelete, err := s.ds.NewTeam(ctx, &fleet.Team{Name: "TeamDelete"})
+ require.NoError(t, err)
+
testProfiles := make(map[string]fleet.MDMAppleConfigProfile)
generateTestProfile := func(name string, identifier string) {
i := identifier
@@ -4402,6 +4405,12 @@ func (s *integrationMDMTestSuite) TestMDMAppleConfigProfileCRUD() {
require.Equal(t, expected.Identifier, actual.Identifier)
}
+ host, _ := createHostThenEnrollMDM(s.ds, s.server.URL, t)
+ s.Do("POST", "/api/latest/fleet/hosts/transfer", addHostsToTeamRequest{
+ TeamID: &teamDelete.ID,
+ HostIDs: []uint{host.ID},
+ }, http.StatusOK)
+
// create new profile (no team)
generateTestProfile("TestNoTeam", "")
body, headers := generateNewReq("TestNoTeam", nil)
@@ -4421,9 +4430,42 @@ func (s *integrationMDMTestSuite) TestMDMAppleConfigProfileCRUD() {
require.NotEmpty(t, newCP.ProfileID)
setTestProfileID("TestWithTeamID", newCP.ProfileID)
+ // Create a profile that we're going to remove immediately
+ generateTestProfile("TestImmediateDelete", "")
+ body, headers = generateNewReq("TestImmediateDelete", &teamDelete.ID)
+ newResp = s.DoRawWithHeaders("POST", "/api/latest/fleet/mdm/apple/profiles", body.Bytes(), http.StatusOK, headers)
+ newCP = fleet.MDMAppleConfigProfile{}
+ err = json.NewDecoder(newResp.Body).Decode(&newCP)
+ require.NoError(t, err)
+ require.NotEmpty(t, newCP.ProfileID)
+ setTestProfileID("TestImmediateDelete", newCP.ProfileID)
+
+ // check that host_mdm_apple_profiles entry was created
+ var hostResp getHostResponse
+ s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d", host.ID), nil, http.StatusOK, &hostResp)
+ require.NotNil(t, hostResp.Host.MDM.Profiles)
+ require.Len(t, *hostResp.Host.MDM.Profiles, 1)
+ require.Equal(t, (*hostResp.Host.MDM.Profiles)[0].Name, "TestImmediateDelete")
+
+ // now delete the profile before it's sent, we should see the host_mdm_apple_profiles entry go
+ // away
+ deletedCP := testProfiles["TestImmediateDelete"]
+ deletePath := fmt.Sprintf("/api/latest/fleet/mdm/apple/profiles/%d", deletedCP.ProfileID)
+ var deleteResp deleteMDMAppleConfigProfileResponse
+ s.DoJSON("DELETE", deletePath, nil, http.StatusOK, &deleteResp)
+ // confirm deleted
+ var listResp listMDMAppleConfigProfilesResponse
+ s.DoJSON("GET", "/api/latest/fleet/mdm/apple/profiles", listMDMAppleConfigProfilesRequest{TeamID: teamDelete.ID}, http.StatusOK, &listResp)
+ require.Len(t, listResp.ConfigProfiles, 0)
+ getPath := fmt.Sprintf("/api/latest/fleet/mdm/apple/profiles/%d", deletedCP.ProfileID)
+ _ = s.DoRawWithHeaders("GET", getPath, nil, http.StatusNotFound, map[string]string{"Authorization": fmt.Sprintf("Bearer %s", s.token)})
+ // confirm no host profiles
+ hostResp = getHostResponse{}
+ s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d", host.ID), nil, http.StatusOK, &hostResp)
+ require.Nil(t, hostResp.Host.MDM.Profiles)
+
// list profiles (no team)
expectedCP := testProfiles["TestNoTeam"]
- var listResp listMDMAppleConfigProfilesResponse
s.DoJSON("GET", "/api/latest/fleet/mdm/apple/profiles", nil, http.StatusOK, &listResp)
require.Len(t, listResp.ConfigProfiles, 1)
respCP := listResp.ConfigProfiles[0]
@@ -4445,7 +4487,7 @@ func (s *integrationMDMTestSuite) TestMDMAppleConfigProfileCRUD() {
// get profile (no team)
expectedCP = testProfiles["TestNoTeam"]
- getPath := fmt.Sprintf("/api/latest/fleet/mdm/apple/profiles/%d", expectedCP.ProfileID)
+ getPath = fmt.Sprintf("/api/latest/fleet/mdm/apple/profiles/%d", expectedCP.ProfileID)
getResp := s.DoRawWithHeaders("GET", getPath, nil, http.StatusOK, map[string]string{"Authorization": fmt.Sprintf("Bearer %s", s.token)})
checkGetResponse(getResp, expectedCP)
@@ -4456,9 +4498,8 @@ func (s *integrationMDMTestSuite) TestMDMAppleConfigProfileCRUD() {
checkGetResponse(getResp, expectedCP)
// delete profile (no team)
- deletedCP := testProfiles["TestNoTeam"]
- deletePath := fmt.Sprintf("/api/latest/fleet/mdm/apple/profiles/%d", deletedCP.ProfileID)
- var deleteResp deleteMDMAppleConfigProfileResponse
+ deletedCP = testProfiles["TestNoTeam"]
+ deletePath = fmt.Sprintf("/api/latest/fleet/mdm/apple/profiles/%d", deletedCP.ProfileID)
s.DoJSON("DELETE", deletePath, nil, http.StatusOK, &deleteResp)
// confirm deleted
listResp = listMDMAppleConfigProfilesResponse{}
From 8977594a809026ba387918a05ae2c3ab4de1e15c Mon Sep 17 00:00:00 2001
From: Noah Talerman <47070608+noahtalerman@users.noreply.github.com>
Date: Tue, 1 Oct 2024 12:53:09 -0400
Subject: [PATCH 18/26] Product design handbook: missing reference docs are
bugs (#22541)
This + announcement addresses of the following Fleet objective
([OKRs](https://docs.google.com/spreadsheets/d/1Hso0LxqwrRVINCyW_n436bNHmoqhoLhC8bcbvLPOs9A/edit?gid=1846478041#gid=1846478041)):
Handbook and train team that anytime there is a missing or incorrect
config setting or REST API in the docs, it is a released bug to be filed
and fixed ASAP.
---
handbook/product-design/README.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/handbook/product-design/README.md b/handbook/product-design/README.md
index a81f4376f9e7..6b9a74ce64fe 100644
--- a/handbook/product-design/README.md
+++ b/handbook/product-design/README.md
@@ -137,6 +137,8 @@ Next, the API design DRI reviews all user stories and bugs with the release mile
To signal that the reference docs branch is ready for release, the API design DRI opens a PR to `main`, adds the DRI for what goes in a release as the reviewer, and adds the release milestone.
+> Anytime there is a missing or incorrect configuration option or REST API endpoint in the docs, it is treated as a released bug to be filed and fixed ASAP.
+
### Interview a Product Designer candidate
Ensure the interview process follows these steps in order. This process must follow [creating a new position](https://fleetdm.com/handbook/company/leadership#creating-a-new-position) through [receiving job applications](https://fleetdm.com/handbook/company/leadership#receiving-job-applications).
From 514ca727ec75c5ba81be17155229d3362bd73e1a Mon Sep 17 00:00:00 2001
From: Mike McNeil
Date: Tue, 1 Oct 2024 15:38:00 -0500
Subject: [PATCH 19/26] Update why-fleet.md (#22499)
---
docs/Get started/why-fleet.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/docs/Get started/why-fleet.md b/docs/Get started/why-fleet.md
index e0db75d68759..6ee517a43da3 100644
--- a/docs/Get started/why-fleet.md
+++ b/docs/Get started/why-fleet.md
@@ -4,9 +4,9 @@ Fleet is an open-source device management platform for Linux, macOS, Windows, Ch
## What's it for?
-Managing computers today is getting harder. You have to juggle a mix of operating systems and devices, with a whole bunch of middleman vendors in between.
+Managing computers today is getting harder. You have to juggle a mix of operating systems and devices, with a bunch of middleman vendors in between.
-Fleet makes things easier by giving you a single system to manage and secure all your computing devices. You can do MDM, patch stuff, and verify anything—all from one dashboard. It's like having a universal remote control for all your organization's computers.
+Fleet makes things easier by giving you a single system to secure and maintain all your computing devices over the air. You can do MDM, patch stuff, and verify anything—all from one system. It's like having a universal remote control for all your organization's computers.
Fleet is open source, and free features will always be free.
@@ -15,7 +15,7 @@ Fleet is open source, and free features will always be free.
Fleet is used in production by IT and security teams with thousands of laptops and servers. Many deployments support tens of thousands of hosts, and a few large organizations manage deployments as large as 400,000+ hosts.
- **Get what you need:** Fleet lets you work directly with [data](https://fleetdm.com/integrations) and events from the native operating system. It lets you go all the way down to the bare metal. It’s also modular. (You can turn off features you are not using.)
-- **Out of the box:** Ready-to-use integrations exist for the [most common tools](https://fleetdm.com/integrations). You can also build custom workflows with the REST API, webhook events, and the fleetctl command line tool. Or go all in and manage computers [with GitOps](https://fleetdm.com/handbook/company#history).
+- **Out of the box:** Ready-to-use integrations exist for the [most common tools](https://fleetdm.com/integrations). You can also build custom workflows with the REST API, webhook events, and the fleetctl command line tool. Or go all in and govern computers [with GitOps](https://github.com/fleetdm/fleet-gitops).
- **Good neighbors:** We think tools should be as easy as possible for everyone to understand. We helped [create osquery](https://fleetdm.com/handbook/company#history), and we are committed to improving it.
- **Free as in free:** The free version of Fleet will [always be free](https://fleetdm.com/pricing). Fleet is independently backed and actively maintained with the help of many amazing contributors.
From a9a9e92f3f1fc3616d9d81e046e38c2a5d2d5df0 Mon Sep 17 00:00:00 2001
From: Lucas Manuel Rodriguez
Date: Tue, 1 Oct 2024 17:38:22 -0300
Subject: [PATCH 20/26] Use node version defined in package.json (#22504)
We did the same thing for Go. (This allows us to not require admin
permissions to update the used Node version in CI.)
---
.github/workflows/build-binaries.yaml | 7 +++----
.github/workflows/fleet-and-orbit.yml | 5 ++---
.github/workflows/goreleaser-fleet.yaml | 5 ++---
.github/workflows/goreleaser-snapshot-fleet.yaml | 4 ++--
.github/workflows/test-js.yml | 14 +++++++-------
5 files changed, 16 insertions(+), 19 deletions(-)
diff --git a/.github/workflows/build-binaries.yaml b/.github/workflows/build-binaries.yaml
index 278f958b2842..8499171c1583 100644
--- a/.github/workflows/build-binaries.yaml
+++ b/.github/workflows/build-binaries.yaml
@@ -37,11 +37,10 @@ jobs:
with:
go-version-file: 'go.mod'
- # Set the Node.js version
- - name: Set up Node.js ${{ vars.NODE_VERSION }}
+ - name: Set up Node.js
uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with:
- node-version: ${{ vars.NODE_VERSION }}
+ node-version-file: package.json
- name: JS Dependency Cache
id: js-cache
@@ -51,7 +50,7 @@ jobs:
**/node_modules
# Use a separate cache for this from other JS jobs since we run the
# webpack steps and will have more to cache.
- key: ${{ runner.os }}-node_modules-${{ hashFiles('**/yarn.lock') }}-node_version-${{ vars.NODE_VERSION }}
+ key: ${{ runner.os }}-node_modules-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-node_modules-
diff --git a/.github/workflows/fleet-and-orbit.yml b/.github/workflows/fleet-and-orbit.yml
index f4dfb2780eb7..22be676c7868 100644
--- a/.github/workflows/fleet-and-orbit.yml
+++ b/.github/workflows/fleet-and-orbit.yml
@@ -79,11 +79,10 @@ jobs:
with:
go-version-file: 'go.mod'
- # Set the Node.js version
- - name: Set up Node.js ${{ vars.NODE_VERSION }}
+ - name: Set up Node.js
uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with:
- node-version: ${{ vars.NODE_VERSION }}
+ node-version-file: package.json
- name: Start tunnel
env:
diff --git a/.github/workflows/goreleaser-fleet.yaml b/.github/workflows/goreleaser-fleet.yaml
index 6ba9aff8f08f..7a23296ef2fe 100644
--- a/.github/workflows/goreleaser-fleet.yaml
+++ b/.github/workflows/goreleaser-fleet.yaml
@@ -46,11 +46,10 @@ jobs:
with:
go-version-file: 'go.mod'
- # Set the Node.js version
- - name: Set up Node.js ${{ vars.NODE_VERSION }}
+ - name: Set up Node.js
uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with:
- node-version: ${{ vars.NODE_VERSION }}
+ node-version-file: package.json
- name: Install JS Dependencies
run: make deps-js
diff --git a/.github/workflows/goreleaser-snapshot-fleet.yaml b/.github/workflows/goreleaser-snapshot-fleet.yaml
index 927cf31be1da..f3c6a9340c96 100644
--- a/.github/workflows/goreleaser-snapshot-fleet.yaml
+++ b/.github/workflows/goreleaser-snapshot-fleet.yaml
@@ -60,10 +60,10 @@ jobs:
go-version-file: 'go.mod'
# Set the Node.js version
- - name: Set up Node.js ${{ vars.NODE_VERSION }}
+ - name: Set up Node.js
uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with:
- node-version: ${{ vars.NODE_VERSION }}
+ node-version-file: package.json
- name: Install Dependencies
run: make deps
diff --git a/.github/workflows/test-js.yml b/.github/workflows/test-js.yml
index 15b4fd05cee5..2b547e1ad5c7 100644
--- a/.github/workflows/test-js.yml
+++ b/.github/workflows/test-js.yml
@@ -42,14 +42,14 @@ jobs:
with:
egress-policy: audit
- - name: Set up Node.js ${{ vars.NODE_VERSION }}
- uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
- with:
- node-version: ${{ vars.NODE_VERSION }}
-
- name: Checkout Code
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+ - name: Set up Node.js
+ uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
+ with:
+ node-version-file: package.json
+
- name: JS Dependency Cache
id: js-cache
uses: actions/cache@69d9d449aced6a2ede0bc19182fadc3a0a42d2b0 # v2
@@ -87,10 +87,10 @@ jobs:
with:
egress-policy: audit
- - name: Set up Node.js ${{ vars.NODE_VERSION }}
+ - name: Set up Node.js
uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with:
- node-version: ${{ vars.NODE_VERSION }}
+ node-version-file: package.json
- name: Checkout Code
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
From 00d31e84505907824e6a4bfd1183bf6d026e7806 Mon Sep 17 00:00:00 2001
From: Rachael Shaw
Date: Tue, 1 Oct 2024 15:39:00 -0500
Subject: [PATCH 21/26] Update linux-device-health.policies.yml (#22516)
See https://github.com/fleetdm/fleet/pull/22498
---
it-and-security/lib/linux-device-health.policies.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/it-and-security/lib/linux-device-health.policies.yml b/it-and-security/lib/linux-device-health.policies.yml
index 0d9e2f8aa2fb..b7093c02acdc 100644
--- a/it-and-security/lib/linux-device-health.policies.yml
+++ b/it-and-security/lib/linux-device-health.policies.yml
@@ -1,6 +1,6 @@
- name: Linux - Enable disk encryption
- query: SELECT 1 FROM disk_encryption WHERE encrypted=1 AND name LIKE '/dev/dm-1';
+ query: SELECT 1 FROM mounts m, disk_encryption d WHERE m.device_alias = d.name AND d.encrypted = 1 AND m.path = '/';
critical: false
description: This policy checks if disk encryption is enabled.
resolution: As an IT admin, deploy an image that includes disk encryption.
- platform: linux
\ No newline at end of file
+ platform: linux
From beec753a3f055f63f3eacfb6ce1c84e6fcb9a6d2 Mon Sep 17 00:00:00 2001
From: Noah Talerman <47070608+noahtalerman@users.noreply.github.com>
Date: Tue, 1 Oct 2024 17:07:30 -0400
Subject: [PATCH 22/26] API docs: OTA enrollment profile (#22457)
- Bring OTA enrollment profile endpoint into REST API docs
---
.../config-less-fleetd-agent-deployment.md | 43 ++++++++++-
docs/Contributing/API-for-contributors.md | 1 -
docs/REST API/rest-api.md | 75 +++++++++++++++++++
3 files changed, 116 insertions(+), 3 deletions(-)
diff --git a/articles/config-less-fleetd-agent-deployment.md b/articles/config-less-fleetd-agent-deployment.md
index eb37159fdcf5..2ca3b5cd7c93 100644
--- a/articles/config-less-fleetd-agent-deployment.md
+++ b/articles/config-less-fleetd-agent-deployment.md
@@ -4,7 +4,7 @@
Deploying Fleet's agent across a diverse range of devices often involves the crucial step of enrolling each device. Traditionally, this involves [packaging](https://fleetdm.com/docs/using-fleet/fleetd#packaging) `fleetd` with configuration including the enroll secret and server URL. While effective, an alternative offers more flexibility in your deployment process. This guide introduces a different approach for deploying Fleet's agent without embedding configuration settings directly into `fleetd`. Ideal for IT administrators who prefer to generate a single package and maintain greater control over the distribution of enrollment secrets and server URLs, this method simplifies the enrollment process across macOS and Windows hosts.
-Emphasizing adaptability and convenience, this approach allows for a more efficient way to manage device enrollments. Let’s dive into how to deploy Fleet's agent using this alternative method, ensuring a more open and flexible deployment process.
+This approach emphasizes adaptability and convenience and allows for a more efficient way to manage device enrollments. Let’s explore how to deploy Fleet's agent using this alternative method, ensuring a more open and flexible deployment process.
## For macOS:
@@ -44,6 +44,18 @@ fleetctl package --type=pkg --use-system-configuration --fleet-desktop
PayloadVersion
1
+
+ EndUserEmail
+ END_USER_EMAIL_HERE
+ PayloadIdentifier
+ com.fleetdm.fleet.mdm.apple.mdm
+ PayloadType
+ com.apple.mdm
+ PayloadUUID
+ 29713130-1602-4D27-90C9-B822A295E44E
+ PayloadVersion
+ 1
+
PayloadDisplayName
Fleetd configuration
@@ -56,11 +68,38 @@ fleetctl package --type=pkg --use-system-configuration --fleet-desktop
PayloadVersion
1
PayloadDescription
- Default configuration for the fleetd agent.
+ Configuration for the fleetd agent.
```
+You can optionally specify the `END_USER_EMAIL` that will be added to the host's [human-device mapping](https://fleetdm.com/docs/rest-api/rest-api#get-human-device-mapping):
+
+```xml
+
+
+
+
+ PayloadContent
+
+ ...
+
+ EndUserEmail
+ END_USER_EMAIL
+ PayloadIdentifier
+ com.fleetdm.fleet.mdm.apple.mdm
+ PayloadType
+ com.apple.mdm
+ PayloadUUID
+ 29713130-1602-4D27-90C9-B822A295E44E
+ PayloadVersion
+ 1
+
+
+ ...
+
+
+```
## For Windows:
diff --git a/docs/Contributing/API-for-contributors.md b/docs/Contributing/API-for-contributors.md
index 074c3338b4c2..64caabc4cbf6 100644
--- a/docs/Contributing/API-for-contributors.md
+++ b/docs/Contributing/API-for-contributors.md
@@ -549,7 +549,6 @@ The MDM endpoints exist to support the related command-line interface sub-comman
- [Get FileVault statistics](#get-filevault-statistics)
- [Upload VPP content token](#upload-vpp-content-token)
- [Disable VPP](#disable-vpp)
-- [Get an over the air (OTA) enrollment profile](#get-an-over-the-air-ota-enrollment-profile)
### Generate Apple Business Manager public key (ADE)
diff --git a/docs/REST API/rest-api.md b/docs/REST API/rest-api.md
index 0a760c32d5fc..a42ec1865781 100644
--- a/docs/REST API/rest-api.md
+++ b/docs/REST API/rest-api.md
@@ -5573,6 +5573,7 @@ solely on the response status code returned by this endpoint.
```
###### Example response body
+
```xml
@@ -5720,6 +5721,7 @@ Get aggregate status counts of profiles for to macOS and Windows hosts that are
- [Set custom MDM setup enrollment profile](#set-custom-mdm-setup-enrollment-profile)
- [Get custom MDM setup enrollment profile](#get-custom-mdm-setup-enrollment-profile)
- [Delete custom MDM setup enrollment profile](#delete-custom-mdm-setup-enrollment-profile)
+- [Get Over-the-Air (OTA) enrollment profile](#get-over-the-air-ota-enrollment-profile)
- [Get manual enrollment profile](#get-manual-enrollment-profile)
- [Upload a bootstrap package](#upload-a-bootstrap-package)
- [Get metadata about a bootstrap package](#get-metadata-about-a-bootstrap-package)
@@ -5827,10 +5829,83 @@ Deletes the custom MDM setup enrollment profile assigned to a team or no team.
`Status: 204`
+### Get Over-the-Air (OTA) enrollment profile
+
+`GET /api/v1/fleet/enrollment_profiles/ota`
+
+The returned value is a signed `.mobileconfig` OTA enrollment profile. Install this profile on macOS, iOS, or iPadOS hosts to enroll them to a specific team in Fleet and turn on MDM features.
+
+To enroll macOS hosts, turn on MDM features, and add [human-device mapping](#get-human-device-mapping), install the [manual enrollment profile](#get-manual-enrollment-profile) instead.
+
+Learn more about OTA profiles [here](https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/iPhoneOTAConfiguration/OTASecurity/OTASecurity.html).
+
+#### Parameters
+
+| Name | Type | In | Description |
+|-------------------|---------|-------|----------------------------------------------------------------------------------|
+| enroll_secret | string | query | **Required**. The enroll secret of the team this host will be assigned to. |
+
+#### Example
+
+`GET /api/v1/fleet/enrollment_profiles/ota?enroll_secret=foobar`
+
+##### Default response
+
+`Status: 200`
+
+> **Note:** To confirm success, it is important for clients to match content length with the response header (this is done automatically by most clients, including the browser) rather than relying solely on the response status code returned by this endpoint.
+
+##### Example response headers
+
+```http
+ Content-Length: 542
+ Content-Type: application/x-apple-aspen-config; charset=urf-8
+ Content-Disposition: attachment;filename="fleet-mdm-enrollment-profile.mobileconfig"
+ X-Content-Type-Options: nosniff
+```
+
+###### Example response body
+
+```xml
+
+
+
+
+ PayloadContent
+
+ URL
+ https://foo.example.com/api/fleet/ota_enrollment?enroll_secret=foobar
+ DeviceAttributes
+
+ UDID
+ VERSION
+ PRODUCT
+ SERIAL
+
+
+ PayloadOrganization
+ Acme Inc.
+ PayloadDisplayName
+ Acme Inc. enrollment
+ PayloadVersion
+ 1
+ PayloadUUID
+ fdb376e5-b5bb-4d8c-829e-e90865f990c9
+ PayloadIdentifier
+ com.fleetdm.fleet.mdm.apple.ota
+ PayloadType
+ Profile Service
+
+
+```
+
+
### Get manual enrollment profile
Retrieves an unsigned manual enrollment profile for macOS hosts. Install this profile on macOS hosts to turn on MDM features manually.
+To add [human-device mapping](#get-human-device-mapping), add the end user's email to the enrollment profle. Learn how [here](https://fleetdm.com/guides/config-less-fleetd-agent-deployment#basic-article).
+
`GET /api/v1/fleet/enrollment_profiles/manual`
##### Example
From c545495f607cd4819288b52307cf5ba9b66a3573 Mon Sep 17 00:00:00 2001
From: Marko Lisica <83164494+marko-lisica@users.noreply.github.com>
Date: Tue, 1 Oct 2024 23:09:33 +0200
Subject: [PATCH 23/26] API design: Self-service: Install Apple App Store apps
on macOS (#22102)
API design for:
- #19620
---
docs/Configuration/yaml-files.md | 4 +-
docs/Contributing/API-for-contributors.md | 191 +++++++++++++++++++++-
docs/REST API/rest-api.md | 178 ++++----------------
3 files changed, 228 insertions(+), 145 deletions(-)
diff --git a/docs/Configuration/yaml-files.md b/docs/Configuration/yaml-files.md
index 7599fd259f9a..fae1c212c192 100644
--- a/docs/Configuration/yaml-files.md
+++ b/docs/Configuration/yaml-files.md
@@ -354,7 +354,9 @@ software:
- `app_store_id` is the ID of the Apple App Store app. You can find this at the end of the app's App Store URL. For example, "Bear - Markdown Notes" URL is "https://apps.apple.com/us/app/bear-markdown-notes/id1016366447" and the `app_store_id` is `1016366447`.
-> Make sure to include only the ID itself, and not the `id` prefix shown in the URL. The ID must be wrapped in quotes as shown in the example so that it is processed as a string.
+> Make sure to include only the ID itself, and not the `id` prefix shown in the URL. The ID must be wrapped in quotes as shown in the example so that it is processed as a string.
+
+`self_service` only applies to macOS, and is ignored for other platforms. For example, if the app is supported on macOS, iOS, and iPadOS, and `self_service` is set to `true`, it will be self-service on macOS workstations but not iPhones or iPads.
##### Separate file
diff --git a/docs/Contributing/API-for-contributors.md b/docs/Contributing/API-for-contributors.md
index 64caabc4cbf6..27f819f72467 100644
--- a/docs/Contributing/API-for-contributors.md
+++ b/docs/Contributing/API-for-contributors.md
@@ -541,6 +541,10 @@ The MDM endpoints exist to support the related command-line interface sub-comman
- [Renew VPP token](#renew-vpp-token)
- [Delete VPP token](#delete-vpp-token)
- [Batch-apply MDM custom settings](#batch-apply-mdm-custom-settings)
+- [Batch-apply packages](#batch-apply-packages)
+- [Batch-apply App Store apps](#batch-apply-app-store-apps)
+- [Get token to download package](#get-token-to-download-package)
+- [Download package using a token](#download-package-using-a-token)
- [Initiate SSO during DEP enrollment](#initiate-sso-during-dep-enrollment)
- [Complete SSO during DEP enrollment](#complete-sso-during-dep-enrollment)
- [Over the air enrollment](#over-the-air-enrollment)
@@ -1743,7 +1747,7 @@ If the `name` is not already associated with an existing team, this API route cr
| scripts | list | body | A list of script files to add to this team so they can be executed at a later time. |
| software | object | body | The team's software that will be available for install. |
| software.packages | list | body | An array of objects. Each object consists of:`url`- URL to the software package (PKG, MSI, EXE or DEB),`install_script` - command that Fleet runs to install software, `pre_install_query` - condition query that determines if the install will proceed, `post_install_script` - script that runs after software install, and `self_service` boolean. |
-| software.app_store_apps | list | body | An array objects. Each object consists of `app_store_id` - ID of the App Store app formatted as a string (in quotes) rather than a number. |
+| software.app_store_apps | list | body | An array of objects. Each object consists of `app_store_id` - ID of the App Store app and `self_service` boolean. |
| mdm.macos_settings.enable_disk_encryption | bool | body | Whether disk encryption should be enabled for hosts that belong to this team. |
| force | bool | query | Force apply the spec even if there are (ignorable) validation errors. Those are unknown keys and agent options-related validations. |
| dry_run | bool | query | Validate the provided JSON for unknown keys and invalid value types and return any validation errors, but do not apply the changes. |
@@ -1836,6 +1840,7 @@ If the `name` is not already associated with an existing team, this API route cr
"app_store_apps": [
{
"app_store_id": "12464567",
+ "self_service": true
}
]
}
@@ -3376,3 +3381,187 @@ Run a live script and get results back (5 minute timeout). Live scripts only run
"exit_code": 0
}
```
+## Software
+
+### Batch-apply software
+
+_Available in Fleet Premium._
+
+`POST /api/v1/fleet/software/batch`
+
+This endpoint is asynchronous, meaning it will start a background process to download and apply the software and return a `request_uuid` in the JSON response that can be used to query the status of the batch-apply (using the `GET /api/v1/fleet/software/batch/:request_uuid` endpoint defined below).
+
+#### Parameters
+
+| Name | Type | In | Description |
+| --------- | ------ | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| team_name | string | query | The name of the team to add the software package to. Ommitting these parameters will add software to 'No Team'. |
+| dry_run | bool | query | If `true`, will validate the provided software packages and return any validation errors, but will not apply the changes. |
+| software | object | body | The team's software that will be available for install. |
+| software.packages | list | body | An array of objects. Each object consists of:`url`- URL to the software package (PKG, MSI, EXE or DEB),`install_script` - command that Fleet runs to install software, `pre_install_query` - condition query that determines if the install will proceed, `post_install_script` - script that runs after software install, and `uninstall_script` - command that Fleet runs to uninstall software. |
+
+#### Example
+
+`POST /api/v1/fleet/software/batch`
+
+##### Default response
+
+`Status: 200`
+```json
+{
+ "request_uuid": "ec23c7b6-c336-4109-b89d-6afd859659b4",
+}
+```
+
+### Get status of software batch-apply request
+
+_Available in Fleet Premium._
+
+`GET /api/v1/fleet/software/batch/:request_uuid`
+
+This endpoint allows querying the status of a batch-apply software request (`POST /api/v1/fleet/software/batch`).
+Returns `"status"` field that can be one of `"processing"`, `"complete"` or `"failed"`.
+If `"status"` is `"completed"` then the `"packages"` field contains the applied packages.
+If `"status"` is `"processing"` then the operation is ongoing and the request should be retried.
+If `"status"` is `"failed"` then the `"message"` field contains the error message.
+
+#### Parameters
+
+| Name | Type | In | Description |
+| ------------ | ------ | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| request_uuid | string | query | The request_uuid returned by the `POST /api/v1/fleet/software/batch` endpoint. |
+| team_name | string | query | The name of the team to add the software package to. Ommitting these parameters will add software to 'No Team'. |
+| dry_run | bool | query | If `true`, will validate the provided software packages and return any validation errors, but will not apply the changes. |
+
+##### Default responses
+
+`Status: 200`
+```json
+{
+ "status": "processing",
+ "message": "",
+ "packages": null
+}
+```
+
+`Status: 200`
+```json
+{
+ "status": "completed",
+ "message": "",
+ "packages": [
+ {
+ "team_id": 1,
+ "title_id": 2751,
+ "url": "https://ftp.mozilla.org/pub/firefox/releases/129.0.2/win64/en-US/Firefox%20Setup%20129.0.2.msi"
+ }
+ ]
+}
+```
+
+`Status: 200`
+```json
+{
+ "status": "failed",
+ "message": "validation failed: software.url Couldn't edit software. URL (\"https://foobar.does.not.exist.com\") returned \"Not Found\". Please make sure that URLs are reachable from your Fleet server.",
+ "packages": null
+}
+```
+
+### Batch-apply App Store apps
+
+_Available in Fleet Premium._
+
+`POST /api/latest/fleet/software/app_store_apps/batch`
+
+#### Parameters
+
+| Name | Type | In | Description |
+| --------- | ------ | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| team_name | string | query | The name of the team to add the software package to. Ommitting this parameter will add software to "No team". |
+| dry_run | bool | query | If `true`, will validate the provided VPP apps and return any validation errors, but will not apply the changes. |
+| app_store_apps | list | body | An array of objects. Each object contains `app_store_id` and `self_service`. |
+| app_store_apps.app_store_id | string | body | ID of the App Store app. |
+| app_store_apps.self_service | boolean | body | Whether the VPP app is "Self-service" or not. |
+
+#### Example
+
+`POST /api/latest/fleet/software/app_store_apps/batch`
+```json
+{
+ "team_name": "Foobar",
+ "app_store_apps": {
+ {
+ "app_store_id": "597799333",
+ "self_service": false,
+ },
+ {
+ "app_store_id": "497799835",
+ "self_service": true,
+ }
+ }
+}
+```
+
+##### Default response
+
+`Status: 204`
+
+### Get token to download package
+
+_Available in Fleet Premium._
+
+`POST /api/v1/fleet/software/titles/:software_title_id/package/token?alt=media`
+
+The returned token is a one-time use token that expires after 10 minutes.
+
+#### Parameters
+
+| Name | Type | In | Description |
+|-------------------|---------|-------|------------------------------------------------------------------|
+| software_title_id | integer | path | **Required**. The ID of the software title for software package. |
+| team_id | integer | query | **Required**. The team ID containing the software package. |
+| alt | integer | query | **Required**. Must be specified and set to "media". |
+
+#### Example
+
+`POST /api/v1/fleet/software/titles/123/package/token?alt=media&team_id=2`
+
+##### Default response
+
+`Status: 200`
+
+```json
+{
+ "token": "e905e33e-07fe-4f82-889c-4848ed7eecb7"
+}
+```
+
+### Download package using a token
+
+_Available in Fleet Premium._
+
+`GET /api/v1/fleet/software/titles/:software_title_id/package/token/:token?alt=media`
+
+#### Parameters
+
+| Name | Type | In | Description |
+|-------------------|---------|------|--------------------------------------------------------------------------|
+| software_title_id | integer | path | **Required**. The ID of the software title to download software package. |
+| token | string | path | **Required**. The token to download the software package. |
+
+#### Example
+
+`GET /api/v1/fleet/software/titles/123/package/token/e905e33e-07fe-4f82-889c-4848ed7eecb7`
+
+##### Default response
+
+`Status: 200`
+
+```http
+Status: 200
+Content-Type: application/octet-stream
+Content-Disposition: attachment
+Content-Length:
+Body:
+```
diff --git a/docs/REST API/rest-api.md b/docs/REST API/rest-api.md
index a42ec1865781..cb23ae838055 100644
--- a/docs/REST API/rest-api.md
+++ b/docs/REST API/rest-api.md
@@ -4295,8 +4295,10 @@ Resends a configuration profile for the specified host.
"name": "Logic Pro",
"software_package": null
"app_store_app": {
- "app_store_id": "1091189122"
+ "app_store_id": "1091189122",
+ "icon_url": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/f4/25/1f/f4251f60-e27a-6f05-daa7-9f3a63aac929/AppIcon-0-0-85-220-0-0-4-0-0-2x-0-0-0-0-0.png/512x512bb.png"
"version": "2.04",
+ "self_service": false,
"last_install": {
"command_uuid": "0aa14ae5-58fe-491a-ac9a-e4ee2b3aac40",
"installed_at": "2024-05-15T15:23:57Z"
@@ -6517,7 +6519,8 @@ None.
]
```
-Get Volume Purchasing Program (VPP)
+### Get Volume Purchasing Program (VPP)
+
> **Experimental feature**. This feature is undergoing rapid improvement, which may result in breaking changes to the API or configuration surface. It is not recommended for use in automated workflows.
@@ -8727,10 +8730,6 @@ Deletes the session specified by ID. When the user associated with the session n
- [Get package install result](#get-package-install-result)
- [Download package](#download-package)
- [Delete package or App Store app](#delete-package-or-app-store-app)
-- [Batch-apply software](#batch-apply-software)
-- [Batch-apply app store apps](#batch-apply-app-store-apps)
-- [Get token to download package](#get-token-to-download-package)
-- [Download package using a token](#download-package-using-a-token)
### List software
@@ -9101,9 +9100,10 @@ Returns information about the specified software. By default, `versions` are sor
"software_package": null,
"app_store_app": {
"name": "Logic Pro",
- "app_store_id": "1091189122",
+ "app_store_id": 1091189122,
"latest_version": "2.04",
"icon_url": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/f1/65/1e/a4844ccd-486d-455f-bb31-67336fe46b14/AppIcon-1x_U007emarketing-0-7-0-85-220-0.png/512x512bb.jpg",
+ "self_service": true,
"status": {
"installed": 3,
"pending": 1,
@@ -9182,6 +9182,7 @@ Returns information about the specified software version.
}
```
+
### Get operating system version
Retrieves information about the specified operating system (OS) version.
@@ -9454,6 +9455,7 @@ Add App Store (VPP) app purchased in Apple Business Manager.
| app_store_id | string | body | **Required.** The ID of App Store app. |
| team_id | integer | body | **Required**. The team ID. Adds VPP software to the specified team. |
| platform | string | body | The platform of the app (`darwin`, `ios`, or `ipados`). Default is `darwin`. |
+| self_service | boolean | body | Self-service software is optional and can be installed by the end user. |
#### Example
@@ -9466,6 +9468,7 @@ Add App Store (VPP) app purchased in Apple Business Manager.
"app_store_id": "497799835",
"team_id": 2,
"platform": "ipados"
+ "self_service": true
}
```
@@ -9473,38 +9476,6 @@ Add App Store (VPP) app purchased in Apple Business Manager.
`Status: 200`
-### Download package
-
-> **Experimental feature**. This feature is undergoing rapid improvement, which may result in breaking changes to the API or configuration surface. It is not recommended for use in automated workflows.
-
-_Available in Fleet Premium._
-
-`GET /api/v1/fleet/software/titles/:software_title_id/package?alt=media`
-
-#### Parameters
-
-| Name | Type | In | Description |
-| ---- | ------- | ---- | -------------------------------------------- |
-| software_title_id | integer | path | **Required**. The ID of the software title to download software package.|
-| team_id | integer | query | **Required**. The team ID. Downloads a software package added to the specified team. |
-| alt | integer | query | **Required**. If specified and set to "media", downloads the specified software package. |
-
-#### Example
-
-`GET /api/v1/fleet/software/titles/123/package?alt=media?team_id=2`
-
-##### Default response
-
-`Status: 200`
-
-```http
-Status: 200
-Content-Type: application/octet-stream
-Content-Disposition: attachment
-Content-Length:
-Body:
-```
-
### Install package or App Store app
> **Experimental feature**. This feature is undergoing rapid improvement, which may result in breaking changes to the API or configuration surface. It is not recommended for use in automated workflows.
@@ -9562,7 +9533,7 @@ _Available in Fleet Premium._
`GET /api/v1/fleet/software/install/:install_uuid/results`
-Get the results of a software package install.
+Get the results of a software package install.
To get the results of an App Store app install, use the [List MDM commands](#list-mdm-commands) and [Get MDM command results](#get-mdm-command-results) API enpoints. Fleet uses an MDM command to install App Store apps.
@@ -9593,141 +9564,62 @@ To get the results of an App Store app install, use the [List MDM commands](#lis
}
```
-### Delete package or App Store app
+### Download package
> **Experimental feature**. This feature is undergoing rapid improvement, which may result in breaking changes to the API or configuration surface. It is not recommended for use in automated workflows.
_Available in Fleet Premium._
-Deletes software that's available for install (package or App Store app).
-
-`DELETE /api/v1/fleet/software/titles/:software_title_id/available_for_install`
+`GET /api/v1/fleet/software/titles/:software_title_id/package?alt=media`
#### Parameters
| Name | Type | In | Description |
| ---- | ------- | ---- | -------------------------------------------- |
-| software_title_id | integer | path | **Required**. The ID of the software title to delete software available for install. |
-| team_id | integer | query | **Required**. The team ID. Deletes a software package added to the specified team. |
-
-#### Example
-
-`DELETE /api/v1/fleet/software/titles/24/available_for_install?team_id=2`
-
-##### Default response
-
-`Status: 204`
-
-### Batch-apply software
-
-_Available in Fleet Premium._
-
-`POST /api/v1/fleet/software/batch`
-
-#### Parameters
-
-| Name | Type | In | Description |
-| --------- | ------ | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| team_id | number | query | The ID of the team to add the software package to. Only one team identifier (`team_id` or `team_name`) can be included in the request; omit this parameter if using `team_name`. Omitting these parameters will add software to "No Team". |
-| team_name | string | query | The name of the team to add the software package to. Only one team identifier (`team_id` or `team_name`) can be included in the request; omit this parameter if using `team_id`. Omitting these parameters will add software to "No Team". |
-| dry_run | bool | query | If `true`, will validate the provided software packages and return any validation errors, but will not apply the changes. |
-| software | object | body | The team's software that will be available for install. |
-| software.packages | list | body | An array of objects. Each object consists of:`url`- URL to the software package (PKG, MSI, EXE or DEB),`install_script` - command that Fleet runs to install software, `pre_install_query` - condition query that determines if the install will proceed, `post_install_script` - script that runs after software install, and `uninstall_script` - command that Fleet runs to uninstall software. |
-| software.app_store_apps | list | body | An array objects. Each object consists of `app_store_id` - ID of the App Store app. |
-
-If both `team_id` and `team_name` parameters are included, this endpoint will respond with an error. If no `team_name` or `team_id` is provided, the scripts will be applied for **all hosts**.
-
-#### Example
-
-`POST /api/v1/fleet/software/batch`
-
-##### Default response
-
-`Status: 204`
-
-### Batch-apply app store apps
-
-_Available in Fleet Premium._
-
-`POST /api/v1/fleet/software/app_store_apps/batch`
-
-#### Parameters
-
-| Name | Type | In | Description |
-|-----------------|---------|-------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| team_name | integer | query | **Required**. The name of the team to add the app to. |
-| dry_run | bool | query | If `true`, will validate the provided apps and return any validation errors, but will not apply the changes. |
-| apps_store_apps | list | body | The list of objects containing `app_store_id`: a string representation of the app's App ID, `self_service`: a bool indicating if the app's installation can be initiated by end users. |
-
-> Note that this endpoint replaces all apps associated with a team.
-
-#### Example
-
-`POST /api/v1/fleet/software/app_store_apps/batch`
-
-#### Default response
-
-`Status: 204`
-
-### Get token to download package
-
-_Available in Fleet Premium._
-
-`POST /api/v1/fleet/software/titles/:software_title_id/package/token?alt=media`
-
-The returned token is a one-time use token that expires after 10 minutes.
-
-#### Parameters
-
-| Name | Type | In | Description |
-|-------------------|---------|-------|------------------------------------------------------------------|
-| software_title_id | integer | path | **Required**. The ID of the software title for software package. |
-| team_id | integer | query | **Required**. The team ID containing the software package. |
-| alt | integer | query | **Required**. Must be specified and set to "media". |
+| software_title_id | integer | path | **Required**. The ID of the software title to download software package.|
+| team_id | integer | query | **Required**. The team ID. Downloads a software package added to the specified team. |
+| alt | integer | query | **Required**. If specified and set to "media", downloads the specified software package. |
#### Example
-`POST /api/v1/fleet/software/titles/123/package/token?alt=media&team_id=2`
+`GET /api/v1/fleet/software/titles/123/package?alt=media?team_id=2`
##### Default response
`Status: 200`
-```json
-{
- "token": "e905e33e-07fe-4f82-889c-4848ed7eecb7"
-}
+```http
+Status: 200
+Content-Type: application/octet-stream
+Content-Disposition: attachment
+Content-Length:
+Body:
```
-### Download package using a token
+### Delete package or App Store app
+
+> **Experimental feature**. This feature is undergoing rapid improvement, which may result in breaking changes to the API or configuration surface. It is not recommended for use in automated workflows.
_Available in Fleet Premium._
-`GET /api/v1/fleet/software/titles/:software_title_id/package/token/:token?alt=media`
+Deletes software that's available for install (package or App Store app).
+
+`DELETE /api/v1/fleet/software/titles/:software_title_id/available_for_install`
#### Parameters
-| Name | Type | In | Description |
-|-------------------|---------|------|--------------------------------------------------------------------------|
-| software_title_id | integer | path | **Required**. The ID of the software title to download software package. |
-| token | string | path | **Required**. The token to download the software package. |
+| Name | Type | In | Description |
+| ---- | ------- | ---- | -------------------------------------------- |
+| software_title_id | integer | path | **Required**. The ID of the software title to delete software available for install. |
+| team_id | integer | query | **Required**. The team ID. Deletes a software package added to the specified team. |
#### Example
-`GET /api/v1/fleet/software/titles/123/package/token/e905e33e-07fe-4f82-889c-4848ed7eecb7`
+`DELETE /api/v1/fleet/software/titles/24/available_for_install?team_id=2`
##### Default response
-`Status: 200`
-
-```http
-Status: 200
-Content-Type: application/octet-stream
-Content-Disposition: attachment
-Content-Length:
-Body:
-```
-
+`Status: 204`
## Vulnerabilities
From f8fff1685dd51e51e3f6133555ce121d15f52be1 Mon Sep 17 00:00:00 2001
From: Lucas Manuel Rodriguez
Date: Tue, 1 Oct 2024 18:25:17 -0300
Subject: [PATCH 24/26] Fix lint-js (#22557)
I missed this change in https://github.com/fleetdm/fleet/pull/22504
---
.github/workflows/test-js.yml | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/test-js.yml b/.github/workflows/test-js.yml
index 2b547e1ad5c7..ab60d81939a2 100644
--- a/.github/workflows/test-js.yml
+++ b/.github/workflows/test-js.yml
@@ -87,14 +87,14 @@ jobs:
with:
egress-policy: audit
+ - name: Checkout Code
+ uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+
- name: Set up Node.js
uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with:
node-version-file: package.json
- - name: Checkout Code
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
-
- name: JS Dependency Cache
id: js-cache
uses: actions/cache@69d9d449aced6a2ede0bc19182fadc3a0a42d2b0 # v2
From fd928e78b9742a2af715ecd490ad4f69fd58e1be Mon Sep 17 00:00:00 2001
From: Eric
Date: Tue, 1 Oct 2024 16:34:01 -0500
Subject: [PATCH 25/26] Website: add JNUC banner to device-management page
(#22560)
Closes: https://github.com/fleetdm/fleet/issues/22559
Changes:
-Added a banner to the top of the device management page for JNUC
atendees to book a meeting with the CEO.
---
.../assets/images/icon-hand-wave-20x20@2x.png | Bin 0 -> 3655 bytes
.../styles/pages/device-management.less | 21 ++++++++++++++++++
website/views/pages/device-management.ejs | 1 +
3 files changed, 22 insertions(+)
create mode 100644 website/assets/images/icon-hand-wave-20x20@2x.png
diff --git a/website/assets/images/icon-hand-wave-20x20@2x.png b/website/assets/images/icon-hand-wave-20x20@2x.png
new file mode 100644
index 0000000000000000000000000000000000000000..ab60da6214029ac30c0a3aed79d6d87e70278414
GIT binary patch
literal 3655
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
Z@Edh7l=U&8RX_j$002ovPDHLkV1l!>@mc@?
literal 0
HcmV?d00001
diff --git a/website/assets/styles/pages/device-management.less b/website/assets/styles/pages/device-management.less
index 10557dc6196e..551df9ac5800 100644
--- a/website/assets/styles/pages/device-management.less
+++ b/website/assets/styles/pages/device-management.less
@@ -39,6 +39,27 @@
strong {
color: @core-fleet-black;
}
+ [purpose='jnuc-banner'] {
+ height: 44px;
+ background-color: #0587FF;
+ p {
+ color: #FFF;
+ font-size: 14px;
+ font-weight: 700;
+ margin-bottom: 0px;
+ }
+ a {
+ color: #FFF;
+ text-decoration-line: underline;
+ text-underline-offset: 2px;
+
+ }
+ img {
+ display: inline;
+ height: 20px;
+ margin-right: 10px;
+ }
+ }
[purpose='page-container'] {
padding-left: 64px;
padding-right: 64px;
diff --git a/website/views/pages/device-management.ejs b/website/views/pages/device-management.ejs
index 0c077596e59a..409a23381318 100644
--- a/website/views/pages/device-management.ejs
+++ b/website/views/pages/device-management.ejs
@@ -1,4 +1,5 @@
+
Attending JNUC? Chat with our CEO.
From 570d1847f2a01de8df32dc65f6aa66441d75c2a4 Mon Sep 17 00:00:00 2001
From: Sam Pfluger <108141731+Sampfluger88@users.noreply.github.com>
Date: Tue, 1 Oct 2024 17:08:48 -0500
Subject: [PATCH 26/26] Update change a fleetie's job title (#22561)
---
handbook/company/leadership.md | 6 +++++-
handbook/digital-experience/README.md | 3 ++-
2 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/handbook/company/leadership.md b/handbook/company/leadership.md
index 1cad2e84de28..90c9d0b2a70c 100644
--- a/handbook/company/leadership.md
+++ b/handbook/company/leadership.md
@@ -415,7 +415,11 @@ Although it's sad to see someone go, Fleet understands that not everything is me
## Changing someone's position
-From time to time, someone's job title changes. To do this, reach out to [Digital Experience](https://fleetdm.com/handbook/digital-experience).
+
+From time to time, someone's job title changes. Use the following steps to change someone's position:
+1. Create Slack channel: Create a private "#YYYY-change-title-for-xxxxxx" Slack channel (where "xxxxxx" is the Fleetie's name and YYYY is the current year) for discussion and invite the CEO and Head of Digital Experience.
+2. At-mention the Head of Digital Experience in the new channel with any context regarding the title change. Share any related documents with the Head of Digital Experience and the CEO.
+3. After getting approval from the [Head of People](https://fleetdm.com/handbook/digital-experience#team), the Digital Experience team will take the necessary steps to [change the fleetie's job title](https://fleetdm.com/handbook/digital-experience#change-a-fleeties-job-title).
diff --git a/handbook/digital-experience/README.md b/handbook/digital-experience/README.md
index b634a973ec1f..ab17f0cf5c89 100644
--- a/handbook/digital-experience/README.md
+++ b/handbook/digital-experience/README.md
@@ -91,7 +91,8 @@ When a Fleetie, consultant or advisor requests an update to their personnel deta
### Change a Fleetie's job title
When Digital Experience receives notification of a Fleetie's job title changing, follow these steps to ensure accurate recording of the change across our systems.
-1. Update ["🧑🚀 Fleeties"](https://docs.google.com/spreadsheets/d/1OSLn-ZCbGSjPusHPiR5dwQhheH1K8-xqyZdsOe9y7qc/edit#gid=0):
+1. The Head of Digital Experience will bring the proposed title change to the next Roundup meeting with the CEO for approval. If the proposed change is rejected, the Head of Digital Experience will inform the requesting manager as to why.
+1. If approved, update ["🧑🚀 Fleeties"](https://docs.google.com/spreadsheets/d/1OSLn-ZCbGSjPusHPiR5dwQhheH1K8-xqyZdsOe9y7qc/edit#gid=0):
- Search the spreadsheet for the Fleetie in need of a job title change.
- Input the new job title in the Fleetie's row in the "Job title" cell.
- Navigate to the "Org chart" tab of the spreadsheet, and verify that the Fleetie's title appears correctly in the org chart.