From b803bbe5bf596cca691c4c404e98b331acf0d198 Mon Sep 17 00:00:00 2001 From: Lucas Manuel Rodriguez Date: Fri, 14 Jul 2023 13:06:34 -0300 Subject: [PATCH] `osquery-perf` changes needed for load testing with simulated Windows hosts (#12754) Changes in osquery-perf to allow for testing of Windows hosts in loadtest environments. --- cmd/osquery-perf/agent.go | 169 ++++++------------ ...ble.software => macos_vulnerable.software} | 0 .../ubuntu_2204-software.json.bz2 | Bin 0 -> 14158 bytes cmd/osquery-perf/windows_11-software.json.bz2 | Bin 0 -> 2166 bytes .../loadtesting/terraform/loadtesting.tf | 1 + .../loadtesting/terraform/terraform.tfvars | 3 - 6 files changed, 59 insertions(+), 114 deletions(-) rename cmd/osquery-perf/{vulnerable.software => macos_vulnerable.software} (100%) create mode 100644 cmd/osquery-perf/ubuntu_2204-software.json.bz2 create mode 100644 cmd/osquery-perf/windows_11-software.json.bz2 delete mode 100644 infrastructure/loadtesting/terraform/terraform.tfvars diff --git a/cmd/osquery-perf/agent.go b/cmd/osquery-perf/agent.go index 529c9c7412..32504c8680 100644 --- a/cmd/osquery-perf/agent.go +++ b/cmd/osquery-perf/agent.go @@ -9,14 +9,10 @@ import ( "errors" "flag" "fmt" - "io" "log" "math/rand" "net/http" "os" - "path" - "path/filepath" - "runtime" "strconv" "strings" "sync" @@ -32,33 +28,77 @@ import ( "github.com/valyala/fasthttp" ) -//go:embed *.tmpl -var templatesFS embed.FS +var ( + //go:embed *.tmpl + templatesFS embed.FS -//go:embed *.software -var softwareFS embed.FS + //go:embed *.software + macOSVulnerableSoftwareFS embed.FS -var vulnerableSoftware []fleet.Software + //go:embed ubuntu_2204-software.json.bz2 + ubuntuSoftwareFS embed.FS + //go:embed windows_11-software.json.bz2 + windowsSoftwareFS embed.FS -func init() { - vulnerableSoftwareData, err := softwareFS.ReadFile("vulnerable.software") + macosVulnerableSoftware []fleet.Software + windowsSoftware []map[string]string + ubuntuSoftware []map[string]string +) + +func loadMacOSVulnerableSoftware() { + macOSVulnerableSoftwareData, err := macOSVulnerableSoftwareFS.ReadFile("macos_vulnerable.software") if err != nil { - log.Fatal("reading vulnerable software file: ", err) + log.Fatal("reading vulnerable macOS software file: ", err) } - lines := bytes.Split(vulnerableSoftwareData, []byte("\n")) + lines := bytes.Split(macOSVulnerableSoftwareData, []byte("\n")) for _, line := range lines { parts := bytes.Split(line, []byte("##")) if len(parts) < 2 { fmt.Println("skipping", string(line)) continue } - vulnerableSoftware = append(vulnerableSoftware, fleet.Software{ + macosVulnerableSoftware = append(macosVulnerableSoftware, fleet.Software{ Name: strings.TrimSpace(string(parts[0])), Version: strings.TrimSpace(string(parts[1])), Source: "apps", }) } - log.Printf("Loaded %d vulnerable software\n", len(vulnerableSoftware)) + log.Printf("Loaded %d vulnerable macOS software\n", len(macosVulnerableSoftware)) +} + +func loadSoftwareItems(fs embed.FS, path string) []map[string]string { + bz2, err := fs.Open(path) + if err != nil { + panic(err) + } + + type softwareJSON struct { + Name string `json:"name"` + Version string `json:"version"` + Release string `json:"release,omitempty"` + Arch string `json:"arch,omitempty"` + } + var softwareList []softwareJSON + // ignoring "G110: Potential DoS vulnerability via decompression bomb", as this is test code. + if err := json.NewDecoder(bzip2.NewReader(bz2)).Decode(&softwareList); err != nil { //nolint:gosec + panic(err) + } + + softwareRows := make([]map[string]string, 0, len(softwareList)) + for _, s := range softwareList { + softwareRows = append(softwareRows, map[string]string{ + "name": s.Name, + "version": s.Version, + "source": "programs", + }) + } + return softwareRows +} + +func init() { + loadMacOSVulnerableSoftware() + windowsSoftware = loadSoftwareItems(windowsSoftwareFS, "windows_11-software.json.bz2") + ubuntuSoftware = loadSoftwareItems(ubuntuSoftwareFS, "ubuntu_2204-software.json.bz2") } type Stats struct { @@ -748,99 +788,6 @@ func (a *agent) hostUsers() []map[string]string { return users } -func extract(src, dst string) { - srcF, err := os.Open(src) - if err != nil { - panic(err) - } - defer srcF.Close() - - dstF, err := os.Create(dst) - if err != nil { - panic(err) - } - defer dstF.Close() - - r := bzip2.NewReader(srcF) - // ignoring "G110: Potential DoS vulnerability via decompression bomb", as this is test code. - _, err = io.Copy(dstF, r) //nolint:gosec - if err != nil { - panic(err) - } -} - -func loadSoftware(platform string, ver string) []map[string]string { - _, exFilename, _, ok := runtime.Caller(0) - if !ok { - panic("No caller information") - } - exDir := path.Dir(exFilename) - - srcPath := filepath.Join( - exDir, - "..", - "..", - "server", - "vulnerabilities", - "testdata", - platform, - "software", - fmt.Sprintf("%s_%s-software.json.bz2", platform, ver), - ) - - tmpDir, err := os.MkdirTemp("", "osquery-perf") - if err != nil { - panic(err) - } - defer os.RemoveAll(tmpDir) - dstPath := filepath.Join(tmpDir, fmt.Sprintf("%s-software.json", ver)) - - extract(srcPath, dstPath) - - type softwareJSON struct { - Name string `json:"name"` - Version string `json:"version"` - Release string `json:"release,omitempty"` - Arch string `json:"arch,omitempty"` - } - - var software []softwareJSON - contents, err := os.ReadFile(dstPath) - if err != nil { - log.Printf("reading vuln software for %s %s: %s\n", platform, ver, err) - return nil - } - - err = json.Unmarshal(contents, &software) - if err != nil { - log.Printf("unmarshalling vuln software for %s %s:%s", platform, ver, err) - return nil - } - - var r []map[string]string - for i, fi := range software { - installedPath := "" - if i%2 == 0 { - installedPath = fmt.Sprintf("/some/path/%s", fi.Name) - } - r = append(r, map[string]string{ - "name": fi.Name, - "version": fi.Version, - "source": "osquery-perf", - "installed_path": installedPath, - }) - } - return r -} - -func (a *agent) softwareWindows11() []map[string]string { - return loadSoftware("windows", "11") -} - -func (a *agent) softwareUbuntu2204() []map[string]string { - return loadSoftware("ubuntu", "2204") -} - func (a *agent) softwareMacOS() []map[string]string { var lastOpenedCount int commonSoftware := make([]map[string]string, a.softwareCount.common) @@ -887,7 +834,7 @@ func (a *agent) softwareMacOS() []map[string]string { } randomVulnerableSoftware := make([]map[string]string, a.softwareCount.vulnerable) for i := 0; i < len(randomVulnerableSoftware); i++ { - sw := vulnerableSoftware[rand.Intn(len(vulnerableSoftware))] + sw := macosVulnerableSoftware[rand.Intn(len(macosVulnerableSoftware))] var lastOpenedAt string if l := a.genLastOpenedAt(&lastOpenedCount); l != nil { lastOpenedAt = l.Format(time.UnixDate) @@ -1245,7 +1192,7 @@ func (a *agent) processQuery(name, query string) (handled bool, results []map[st case name == hostDetailQueryPrefix+"software_windows": ss := fleet.OsqueryStatus(rand.Intn(2)) if ss == fleet.StatusOK { - results = a.softwareWindows11() + results = windowsSoftware } return true, results, &ss, nil case name == hostDetailQueryPrefix+"software_linux": @@ -1253,7 +1200,7 @@ func (a *agent) processQuery(name, query string) (handled bool, results []map[st if ss == fleet.StatusOK { switch a.os { case "ubuntu_22.04": - results = a.softwareUbuntu2204() + results = ubuntuSoftware } } return true, results, &ss, nil diff --git a/cmd/osquery-perf/vulnerable.software b/cmd/osquery-perf/macos_vulnerable.software similarity index 100% rename from cmd/osquery-perf/vulnerable.software rename to cmd/osquery-perf/macos_vulnerable.software diff --git a/cmd/osquery-perf/ubuntu_2204-software.json.bz2 b/cmd/osquery-perf/ubuntu_2204-software.json.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..ea9cc3399de9b3cc7e92739fee778085070a2bbc GIT binary patch literal 14158 zcmV-UH?hbz41U(r>dVHJ-g??FS4B(eYc<%V{FiA@10jN z>(+tSMP|n*ryYviH;rq1-Qn%M4eS6soP#ae+dydAwtxqo`F*o{)v=#vdb_#q|#5+r{vWJfY8teKmfpyl|4kz0QEF&Kn9Hgp`$<~(j-Dc zWXYkTG)*)?kYEvn2$Cj*(FRRQex8P-Mw*(M4FEJ60z^Q75Fkl}nG`Y}pfWNVP$Ggq zpo$3L0Th0)Ku~ZZf7DHZ6&)D-kWjr8)`8n5 zaH#z>rgE8u@SC57#t8UI<8~ul60s`B%4-=qLpe-MrPa2jNV4^==PclYX^{6 zMu~V+1_q|urExA_CRv)Z907o_6?x-uV+|Jbtu+^VwZtf;g;6BA}v&I-#zJ zE$GRJuL@i(0U{bAGPJZZ)dt!oQqd~uOm(ZY?`fNuV8p_dsgbx=iq;A>D{GcwA%}U3 zF)c~ag)u2^`OwEpVMHq!?qpCpcREWk=?fW>$CSe@kT&4%h2o)PLru%OGC3@=mc@mL zR;p{(l#!ayNXFUBn1%}!F|y}dCr&)`VS2kTU?@Ddb{LC{5{=cR+`~QV}?Gr5=LdIl2-7Yj5Bj(RrzAuZ7qnI&9$Ejjbjq6N` zuBIL{5K0gx4P`uLY8#ZQkBVNd$E2QILFDEpQoeI5dM3GJ-9bD;h-tKlNH(F0xWh`- z=x$tSnvu)lgRWTWno`q^B3Ab~&IQVj+Bs;gddngstWBmg5D%RFf1gDSdY7PxJwLPX zXY`o!zw!pV4wxRVO~c|x`M>?DrJ7+s;h7I#>6(l)_fWvE1mS1xq=%vv5vBYzVmJP)NN2(I3T=I- zIBAz+lh>SQcEJ^=lDKbae zS4T{p1;mET>}|3YwS9hN7>Y5w99@Y$v+grFWDqd|F8NkWQju*E@7^8<6pneuv2}Yr z0r-)GxI+%k5Z!mXa-UPP7~&NMk}gqfPO*rQm2(e>f(bto`cSpsYwY^?nH))6E}9RE z)6ZELP5U5K3zoh^*{!}@K2Yf#Iqzh_mj;AFG;Iz);v@)XYVhnnFfOy> zp9QHQYD!2_DVAjNTj{wNQOM^t$b7G%#%Fug4s#8O)?Ob{ccVSVrOhr%AnVl(F>5EcM% zpYtJ>YLwKX>5%O(eKdN`@)*#>d^PpMf!E9VmzX|>g2;-x)sTx*XDZKWX)>SLCjdhR zExggT-0&g0*XY1xv4QhCyn9^Y_Bs7#62b4x>+C2nr7S^t`t137wUn}mx@jEr+d1+j z;J>gTEgx;S%@rvebPu3h`q@|PSC|L&zU+q zxJ3kDf`k!47@{0Ho}E5?{HTotK4$w-0{K8O8{+`NKPQ=L5i}_mc#(~ASzNJC-oz5s z#W`3_LtKM;XIkZoNu19bV#TN8B7#4P3YK30Ep+792>-^uF_v)}Z#<7)n-Ui4Zu{!sxwchNjI%SbK{aM^Pc?uBT$*)kK{voa&NM1WnWFYYv?B z)(Rv__BrJcE;QO=9p^Lf^_~oi367avR3{TO_N$#tVx9FpEagiPS<^Xoa7lEc2O5l& zpp)U0FMUT%Yt*ts0`c#D24S?Vyqr%|a5f+g@x zX$HO-!WkwBeP(d*U?~F&=<2>0+&djA*NEoRLY%7yY{ha=h8$PJa|5ijxw?mI_p)!reTL#&{@kgt)hi9@b+1soXCl(?5TI|__P;o$g{DP z8^!>>6fRg}1|U?0;Fwfx)&bH9E(aMDUo~fZ)QDP^O`y1$;C3?$i|ZFvoQ*EjRx~DP znYI}aXKsmcvrvU>dc!)3iW$sd+Osg@SUFG*)re!*hYo`~0v#QTGjPqSD_5xlW3D>E zGELw~SP&h^IrbzSyz;`hdl%c0$Bi1fVuK--{1r6#bnWm$zRK_twGjh;UXkZoT)^u- zv5LsOwOCOuwT|yrl`a%(nk8!Ot|e0(M9b7JqS6at#SRUo*5kab| zj$cG)u3%o{bwS4C7{bLyGGldha_X~k@?E%xLI{czdqjrPN6+?rd|S7om0z}Bbs-{;`e#%o9=I59#XU8A^G zl#@Ki&Q+DtSYDZfh<8ireh%E1+j(wb3Z&};eVuy|0Vh!G%B+E2K%mS>wPA2ZR00}) zj|=X-_sxc-vuA#ut;oSDsVss$*h{P@d12tr{cS~@_D#w7{D z$vE?TgtPAB?V&M!rSmUAeEm0F_oSH!g=Jy&IM#cePZUo}^LOu^-G`6al9{3dzPJcxDkOsvcuomzFct%9rP*Gq$6nCAlDoA zd}n2(Ec=zXJ2l!&l%n_NYfdRDJ*0VZ_rrlC*HhOm^*lB#4pV(=lirLx@F8t(gFPu= zuF+Sj>Q2&GlfsWX$(}>Q^Nhwr@3{OHr=2A{k;F+#a59VXdE_zB;fyVQ7z-&EU9 z?%wOvgt^at$r<)y@#_{1E4rVzM`$-gmR%+ zklAyVkG!>+%;fqhVK*iPQx~0E=0W-w4k#_WP#&VaJPebqX~{Hqbn) z7`YoQ=^Sq%9WuDI9`{9j6CP5}i^o^@8kpM4Q*y%z%vLqtTTJrOIs$Oc;OmNK2wNyR zELvn|l>J$6dAox*OthhgADZh|gT;v)9lm+dvp$_=X5Uz)Q@gY0Cf#i_6?1u`?uBptRVcHchQ;YG{ zuvnN%YEl{nO{-@Pq$0H9iYiL{Z5mmrtV_-OVY_?9=;J3kX)57#?jRU}EPI@98(j%D zPyp=OJ@eS=S)IK5R1<2ykaCWa8A-l1p{=Z4kCN*VBy|_ z1CU6DV&IW*Tr9m<;YNgUp_&Rb;w0O6oe0%Py30m9H56moFrWubk%;4Q9%#sK6p27= z<;0>*-ZBje=P<&sM%c2eG9v0?;nolhoVL&bcZYk2lsh7EAZ;Za0rIix(qA)qG8iHgQa z#gx<`DuAtDJ~U5E-uP)19EypBoO%!$R}*%&@=4Km3wrL`x*XUR^u%Izk2?*6gR#u* z(VN?r9#CxXVe2HRsEkCYJUwP=6r!UU7Xg$UN*($ya+fcotW7-UW~RpUdVxcODnlbFfyxE)}AUU^he|Vqh_OUN zV2@-&gK>;=11JqRTZUXH*-TKcV{>^F6$lgv#v92d4qMQNZpw%$P`7cEFh&_x=(uAQ z=p6}bBzF)-+>0_^OrTGQqjD_0!YV67rFEOqjz;54VRNfpPYV;vnb&Zfq0XLxl1#P` z!vV}QD9OradvXsbtQaFDkk257~vD$$OVJmtKhK(vwh)W$*(&XSSL3Wm)he{00a_LPX3@3w- z-MU$3!y;wMg$$!iL@5|@c-o#q>&9Mvy<9!Q!%I=ICI?(2He z5YUKmcXFI9hSoVl28S{do)9d@MulktS)=;Ur0Jvw@qqYkPGG#0xK2RH#v04Z|r3DjEn$Me#6> z24u*LNFh^Zc<;s?#swOxE*%r8W#BUf1Z=o9D%i&%A|@Ea5K%i56~zsUnC^2a!zVad zL(&zcwl>&Nqj^ou18q5HG2u1Gy9ym^CM|J;dD`| zk3(}RoB<9?xdsFWzK&mG8s0uWWGk!wr5ma{@0Q~a%>Fh-84eOb0=Op5F`Tx}3{^ca z$;aI6c$1V43r&>)2YfBiXh3Nwy>=}&Fs>j&Y~l!K>Lz1CF<(9`V>`|y#NzYh-f?ZB z8!Y(?dic+9xPnKesQriXPOpA>a1Qb1$POYw=?C%QnW^h6ht&J17|7#i!qCe_BuZYu z9k4HwQiisnkr@?(6_HnINhcXRxFiLH@H%*w7BW;&HJNCk1(|Gugcg4UJRle(P*b%p z36ft~EhY>L8U1oh6O&Bg!GN0sFc=>8b1h$S*$TpMT*PD?2FNL6{fx!vY=V=eZ$nlJ zLY3=f$XujHC)wnX&JvUAlL&%4SkF*-kkUzorWY7kcr#gqb;&1`QcE;^hU&olnWOQA z3phO>cdprx?m{Wo!|9=>S@=l`P_+dbL}aL@u3PSB$drB^6cFa;5WF1Z@XTk63KQqt zn46>|^6x$!Y%nywG=>bQ7{~})mY*{TG>?Dj*-@Y`D?=y?K+(oEa`AVM(*K{;%6$Ia zVe6z1YjvA)7#{4!4H}Bp!SMi)S{CM5|W6W@1FOaP9_77Frx+v5@IbZ zQMD0^M%1jOD+ER#&XyP@5fK(r*;km-;~NUh!k}VVV34w?!qiC0<*H>^TLvj$vb7~y zM2%uqjYScd!h%J`qKG!O3bG@UC1zAel^EMBMJ=SUMvX>e3`&Mz$k>&m0+A@pN~*;e zgORmTr34&iOldD!RU8*wFcQmZQc~JlO4rkyNYc94T9$=nj(m%1(_fDa;)5eZM;Idp zK#ZIn(I@A>puT;6KTaU__42*h@TbK#%ksQTbL5i|roW!8`aoA}M*pRxH;sxWVQ`jA z6mm3TO@j(Xn7*T6n=_y`2XB><=p==_Vy1iXh#oDU4nuYUc0oo{MFOBK#xpoB(s_$neME-RX#0EGiE_Vsai13v{Ih`G z0L{aeC8;ZKyx;8=`tzQ9=yI9u`M+4Gvlw|{BtNg>3RWQ-u_Yi<9zoK>@jhAFpeLku z9Zf=6qso?ZuTiDtGNa?-6HF+?W-}zqH@n+P1zSTmaM5)ziA7*E{S#CA<6!q7$t6ev`pD``g5uo^~+D2o!bV5M4Gs)|`cr3wtJvaEohw4p>QBPkFY zSk|zcl*(43D+(5s4=G%l%FNBrJS)VjhciI|Lq@D(qlgs4Uy#1`f|L5Tf3M%ynSPhw z@AJL$edR?4uMbaPt%sb~(GK}iHHpt#VBRh3o9SM2$s5`~p8NFuWxiV3`-ND*VBhJ} z@0Xpouoee~A$l?|W8mr#EZNyiC09m_=!ipWhIRuRMtebH`8Z)> zc)<(}mP$d959E;iraZWNgvMg^hBCJI-Bnlc_A5Iqq(0JZP0~deWgA4p>G!VDej%K} zw2B;(6FOxeCKnM6+W2TOV6>yjJUAblFhe`)5ZJ@@GJtYH;Ym~=sFHX|EHLTMm*b|_ zGkHV?#E?ZG)Wd_3y8?wx5E~T5!cay?MhgvtP_WcO>;oBl^ZR)8@z&zqh*_}dFc|2d zAm@ZzJbUjAXuA<0ddnoM<}?e6X);Y@c2$&EX^Sn0>D@RjPjL5j1M{X zGMawQdJ4-I_}VLhRY5|m_|mN1>SiWvurLIzt6N%F(J4emHA_W^iARN$Or?w{kKEXz zz`~`ics0DBnRcbJBror$Vt39eRQLY9?%ih$7N3@YReGhr9@h*iSHVsbc(xKaJ124m zIC10i_KE{-bawk`gGX;?y+{xYVUr4fHYiZ`$|(*B6&5i48Ts{LF=@91yGnOW#7ltW4145gTGIK<b zv`8#1w(AT8L9`O0!6|}!U&zhkIvMA&AMj(=pS66nzAjZptk=qnov`z(+!O^jJIM$9 z1E~y2MtPmlDqD~0M&VND#u6l(jR;F{g7F~}A6ynt`M}E+Lcyq|Lw@Ro8XO`Xh7e&2 zXfr?yrbuBKcm>1UUly4xZJCmR{3WwHEP^Mv&iw}T44qEAU^cnJL%Suk0Kt$$F_C&U zEixD=lxWPDS^ECDCPavFEIa}hLl!*-Ra|8l1&JuY5OJ{95_(UA1K`9lvKiqF;PytO zf$tp8zmZ*+M+pS!*C8fNN-D}3NMcXjOHogBGKy*Yc=&CvwLY5p{eAi1b&uiNa*Nt0 z1ejqev!btRO#L?Q^Jrj))y}Ab2gCI&JTeA%C7)MW*n6q@Fw|6oY)krw3#`#k)S-TG zU_)Y{L^7y=8fj;KCacaMF++KVvs9?NC|MDtK=;c2o$S^J7#yWGB8?26sUPab)Gonp z=&=YW5a9VGx%!!-sLQhMN+dw``+S}z-&TLrxJXU`zcv#4t%uj=^vr$ccE7N5USnhv@j>s)Gby zlcV4?FzyLP#!gZYF-A%mrDT&39oG&8=kHM&4S6cZ$~X!T3Eap!WbK*8gmOh-so_6q z95g?jzVdUYpzTUF&};L`jxi6ou*5d3+IgQ%(aRypl;)Ukp&MlUB4Qje8eKlPU|Pbj z=R!EJ#yU+Z=}d(}!NylJQtpWqm=cktnGE@2(uF4sX_7X&r@sH4zSTb;sQh9n@M!-( znHF~5B$jhBN^Sck1gS(2AX3#13!Cq$BWG3deEoMCw>Em4Y8D) z$Qq=Y70@CD(rK?b!utIS)ffa&NkRv_6$EFqL_|?i3Fd_mUxf)#i56hcNGuV8#1Mk0 z5MZCD_VWLH&-2~>I2ZbUc1wC!uj&4>|G%DHC(NF*e*4;Xv%kydhtJ>O(7y`oR9<8> zynUI+ZP4QlcmfBGci;5Qe{5?>>dmUmy8rIG*;+n-Yl_{J{Pg8T_Edp=ApTK{9YqIAuEBd+p}?CO^sgw{JH6pb1A>GE3J8lpRAy9- zvNUO>Cd@`exI0=k<`z9J?oTJ}q@K-PmJob2P-&7Rw-_QC)u3B>fDVm9`sgt~T1lO_VIww*PjQH>}*?@3^hmg&Qm zCVzy63Joo5S}I%J6Cl{mjgul&qBTybx3owJjHDcGJIF>>mTu+Ey>tUQjEqN6UbMNx z6g_#MYMdH9+RF5q@PYfFNf3{~k_3!ognQ0bJy z%GE{+=IrWLhKEKGYUPp2^|=%$j}9;7XXDQEvzW>n68I7 znUoMq@XhzUa+MvshZalEzn3+!61y!A#ILLN%~@XTsqo$`rl|g3RY@NG00P?lW=X9< zAUox@+Fw2J7a9^h*p*myYp(a~*4)7Ty&Z;z-J|x?Q!I~r2tV-Bp!}Y?k;(LkQx#}- zg~odG=CfI-@7`=f6CZ3socX?8HiFqZ!TFg#T6<)B;?V`%#ASP=W>JxZ5Q_Cy(`DKY zD0?aNv}@Te(d{}}8efdK+p@pA#bgxw#0dwf3j`OU9(>OewC8Sp=-53Ek4L*~9cz)7 z;vJL)8yj_&-Tapf{S0TzF|Ad((+Qh}xU~#zs<}!B#b(~#?9KN5v%keGXp%A$w7Q;z zOYzld!WFJ&eYdur0kRDIy?iL|xl&V~QaaU>Nr?;L_SG3do`m)zOq6Hg{!8RkwA1V5 z)^LBR*{VXpx z3mZv!3Lfs`?`iIeMc99X+xi=@eD0&#-YX4WKeT5$8&hZwpn9}h(OAAI1UG$=+D@6t zoG$?EYZ(JR7Tr{e>vETDt>02xJQ;twP6dv=sX|!tU6!z4dUlRRq9AGCjc75YdL+4P zeRC{P14ye5Csb}5{RvOCYikB$GsuKKFHc#UZe#ZjFj*d{o_Zb)lEI=rE2)cph=yf^ zH36YPwhs{98agyyOUR-jhh&Tg0uH3#iY%$$*xI4bp-lv+mFIASZ6rttM@s1j?$dh} z%0Y-+=7bp0)w-^iyX;O!cLZl=v(JAV>pLw!my4KpI8i0It9v)=svsqCQ8NTO^Sy6= zedo^T>=zvoqStIC7@|mA*HF@Jih4Ce%$>6s;>3{?5+NW$@a001cn}Xm?Ry=#U7qJl;`%$pmK%61OvZp`!7JI@eN{*%8& z(w`7YOKL34vuHagkh&9?;t$7jxhK8w)x7IGWuiw}x(Jzzn9on)`A!Rc50Ea+=vz2} zu(9z44g*84W>~<;AVEkp685O|s6FvM)1&Umt)=5n2U(Qot;DHJuaQL%Ccz=J0&Ygu zw3+nOiheVti9}D!Gx(@s7(Ft-D*a6P{lUU_qAHORjS{k? zDc6Kk zDi{mdw1XlT>4xSyn0|fxXo|JnE-Kfr!?hC8Vl4_(N7GsC&iU~5>kiON!YAx=(I&3x zg@pmf$L5r&vP-7ZAY*N|QSgH+D%JYzk%_(!qVSKX>Ocj2g8Au@Rk*H%SL)YCBbC_K zE9qWiCLcyn+hF){*AZWH#%GRUAUf1h`#2EH*R$57W@eomjGQpNV~k1zdy}{7#1Bh0 zb>NssFWLQ{4vf-QZlIw9hBn#=>OuF9DM)IavcGH6kU-d*5xqAkhuD6R=eGo$9AHZ) zy9~0FO{qrOG@(U0Z|gNfcdp0d0U4>40wYms(vU@nu;3<&VRYOOFp+Wq;gZaTjhU-7 z-UxE*PZBG?m*uHH_1acLL}e2ZUX9wmyX0vS%dD7`5xxIKZw;n=pSM}X+B-ehjGE~k zJyaBRXDeIZ37v}{A?Ft?HD0$WJVT^*!WM;lQpf~IS;cTg#l#z0Wb3bE6(5Dn7k3T) z&y>66ZjR2cUY|6--y2em_U+ZS$lUj#M3MnXhiS7VxAur9v^qpxK=p@m^Hx>$;;7_`l;tqD8vY#p1`dzgO_vx9!oFL7t_|Kqzq()cGW%E>hGz z{TUW#FawWxA?QLEJ|j9Hy~D=N(w`Y*_U$6976K^A^rb~mtBXX)C zpfd+fIvQ3#Z5<`|2gW=vh;pJyBYRqeP(~s{8kpIM5;ZLZGKa(} zJ}&culq`zl7RJg5uy$x&K5h_MWXO8!27iTzV5VL1@KEgRJsGkvy-HZsMb#O`Q8PLc z!KiAhqt;FJBp9fg?S38knT51gtFoYy5Qxbj17Lx$ZHPP$5I;;+V0yV49f=7^ZA$Q?+fGK^3nOEQY@$yB8T=f(=`qD zEoEq<{Y_%QOUh%surZKQR-hUG-B1 zf;`Avt)axR86%M~xnn&1ho5RP*{B5`J^BVqd;B@0T8O>iWjh>IRS&f=ANEAWP2rGr@hC@~vn!C$u zKo)G$YX&t0p^xp688}%{*uE&3z3M>4N*!-;g-y#Gmd%1SSVcbW`i6VH{^6OqSOj1y zp+ksano)oZ7YtdoWp4qak7rhxQ)75QffzqVVB1(e&RA+yo#-hMAtDD;xp!}}%Mnu# z!=Y(g=i$eW!z;gkSROg5b=y3xK1`vKVPs1dluiKYoGK2=3Wj-Y%e5&-;WnCV;xw|N z6d8NyAdRS}Y@R{BWZ|E)Lhw#i*KuzL%}lSXCp3F|>#k>&*6X*bo7;z5 z^{*Xnr)#A5jGi|3G$^dmKw~+d6p!ha~zC1A-qD?YSAsIwIx;Jv zTM_7pZJgJLP{pxul|Ta;DOKf^kFrs9!-pCk0=TebL-Fh%B`25!jlM2?H$KG zqb8-xT(m;pZn!vPe8LZAygIVZ;PIwKR>rWH$9aarLwOFRZ*@sAPbGQtS+1j%5Vg$E z4N6q?7j3pNBofjaZL|2$@;&Mv7W5hS%Xu!v_lO~#IjpV2@302ibR zixdx&0;EsIoM8{>4E(%rN-%Zvf>;D5e(dZ!+CAWZGoV{ErH~ed&d?_H=7S26H<8pN z@m?gN@L>+cutqEM1^B6d!m!+FeLp~QM|TR7X$i*nsG|%rQVga_DiT1LS5$c=KcW0T zYrumJcHv9v25__QtD><~GyRh5sZ4;2{$&|tx< zdd1AP&@GbYxoNFMWcDmO;7?);i1Z$ICh`1XEW=cjs~nQL}=4^&(Dt}z!rm9B6M@k7ffqN7H3=A2U| zH+*sUILE5R1Y4FbJ(AcXT$2 zgJvxl9K9kEY;=U(2R^r`uYAZIJ)U`9s>^OB?uL(CXrVE>lFx?34<|1~>I_*p>_cN8 zEay5KclYuDjLtU2<6)A`3uOXxGsGCDOvi_St^u-UFUd(BjYx!n3h)eLZ(`Q%beJOA zu+t1QwKSNYYx7&YGc+z0^QlW~7YyP!f`u$kxVF7H23e!WQ8^zd^vC(ibv?mH(zzDn zd^%};OIXVB=Eg0TjTRp+8)uv9l_+JhiYnG?qDs zFc8n>WFdH>j{LsVVolC@_nDTeNso`Z?L$I2Cs`%dvu^I*FAU_&;7wWdff7+#c{3NG zJwxcI5QEw$Xf({ip1iHsJcdhDG~1&=5veCgoZF%y?+GX-Gs3LDOsTu-4eN6$ z8(wQ#RN~*`4N&bJ4VvQd;NWqG&*?LV))S@md({w;~lqn1OC))o}No5~9s# zgIOEFtWI9Z62UQyU>igQC10 zGv+&B<=fXZcyZBX^uc08$s!UmAAdive7$M+Vfa%HJj=IA2=gv8`DPe&JV}a%!j`gbK+J8N(*R~R9x)7o zgKnh*%r!BvL%0RC9Wj(vP>8-KK(c760xF^QH;Z_dWM%G|v?!Nq(IP?2GZDMJM++=T z5Q84A%_bLghzSOvz}iBg7gUV-@bgb=Q6Ph13CL5+RKSu-Q%@&DxI7YSVLF%7o%CcX zXC+ygOwr0oOOjj51I`pWP8*kXIw}Xvx|mj7q>mxa$eL9$~Dg9BX#=@ zeG4eQiutGxM{C?KgdW*YQeh21%v#fuFyZ-J)HlIpWeqZk0FQEU|A7Ht-1^?wc-*zKv+^Yo;jS~E?M<)f{L zOI)s3Hj~6V1LS&{kx#@kj%U~WSpl_FLrvOk4ObODvkHC3_ z{@{rDb;r2r1KuOY;F=WuyY{B}FA;%}U!w`oxq0Rhdz-9#tgMKWjhZ~9EP}`H)%|{} zl2{LzR3qsPr3lADzK3`uU`i&oBo*`rooL8TR zcRy~rlQ}c9J0+GKJGY9)EF=>5>t6Sn0GN>xpk7@k;q8;Im<6f86-Wy$J+!m-=nUV( zvthcZBtE<|b`K9L_XrUPVF8pfRp5gWN~msV&-gD6>$d@+pL#xmfr|VbRK2oCViBl5 zR%TFvzR=!vEb4=vd!KIo zuueJ~t!wIckDC8=K3$F7$Sz|C)2z~v8ie>bv~cqMyuJ>n7w6yX;rR7>&O;vI2?6fg ze_6fg>#TCp$IqJ5wZ8e|K}ox<9r#lF zohtKL&39Kev(nV9BA2qIVzeNmc9?N6^(~C?-?u%~4TUlrl^VAG^Ip!ilMmH{&+;h| z4RL9*JLY*%@aV^@=i8*;*9)fo@*)lo*0yvT_eHc_S)*3_hP*a&Yu=&;#85OMfj}$q zMFJ5N2&(hrA)l7oF2O$*t*(lUB$7!y0rmshiU+(D4Jf=#1Ip(Iq2=r6w-|2BZW#_5 zmkwdS8oD)FnnLi4f@IQ=f@Z{|idLnM6k8Z^<9kKt5e9KILMaysh*DDW7OkfR>{&P} zp#rO`EJ{kM)M1D(Y-9rX%#@fjNOFRxRYGYZ&gzkz!-BSIYkSH`m!*a~K$4>pa*D;R zirIl9r0(!~p8khV2(0l(-jhIB4|ut!jU{9P<$;M7CYPnkgMe%YRONt8?IENAxyxmqOz+$xQYbZ>DS~0KUO5pR2*hoi zrA&dAMo?aZK6=2JIYc-_!Fp!%V5ysYf<(eFASe_ zg7VlQ1?6kjo9XpriUulc#Ljy5frfyA_m~g!QrZc;J+FR8*&XKtUZeEUhP{@y?b1&Z zzk$;A*-NG-vj-~_mohlZh0}p11SvGs(^pplI3&hUe$L*JI=&t6-E<%-^hD_kZDJm_ zeZ@{Fz+kpdYR@DM9icl5LV=h;leYZBU|p+%JGsqnrmLUdy3jXj4l-W@q@f@*rZWbA z;=k!AcnmyCBkjSzmec+LhrZ^fM%NAGozKnc_v^(x94iR+_YnH4@z=j^Br`)Zoy;l* zo}}#Kl*cW-4B9)(F7M09`qnB(`--E)CTR|Oe^M?l|!@4 z4gQ?>T_dl6N;sG4L2IyRNCtz4ghG{Sqlyv#cmii#YK}0n;YJ=xb@rB$2^Y4OqnKh* z10md_Q=4axC2-mu7d!e8#?6Y>hL}5%s8L}R(GS)HQ`k-!MM|h5Cs5&8q)E%JhKN;! zBnOU}wFEyTTk51G;ifa)zh3R0C3_)OFW;|}H3p?^Apj86UYF}d1G=Pe*q8y~Fu~@Q zR|Ftp7T`lPLpiiH1F9E0TDBTI7L>Dv2vK7)`obCY+w+g?Tjs3%x%?NA5a&1E~$1)wckv!GD zWFP;m;ZDy1hd+nbtw4k