PNG  IHDRX cHRMz&u0`:pQ<bKGD pHYsodtIME MeqIDATxw]Wug^Qd˶ 6`!N:!@xI~)%7%@Bh&`lnjVF29gΨ4E$|>cɚ{gk= %,a KX%,a KX%,a KX%,a KX%,a KX%,a KX%, b` ǟzeאfp]<!SJmɤY޲ڿ,%c ~ع9VH.!Ͳz&QynֺTkRR.BLHi٪:l;@(!MԴ=žI,:o&N'Kù\vRmJ雵֫AWic H@" !: Cé||]k-Ha oݜ:y F())u]aG7*JV@J415p=sZH!=!DRʯvɱh~V\}v/GKY$n]"X"}t@ xS76^[bw4dsce)2dU0 CkMa-U5tvLƀ~mlMwfGE/-]7XAƟ`׮g ewxwC4\[~7@O-Q( a*XGƒ{ ՟}$_y3tĐƤatgvێi|K=uVyrŲlLӪuܿzwk$m87k( `múcE)"@rK( z4$D; 2kW=Xb$V[Ru819קR~qloѱDyįݎ*mxw]y5e4K@ЃI0A D@"BDk_)N\8͜9dz"fK0zɿvM /.:2O{ Nb=M=7>??Zuo32 DLD@D| &+֎C #B8ַ`bOb $D#ͮҪtx]%`ES`Ru[=¾!@Od37LJ0!OIR4m]GZRJu$‡c=%~s@6SKy?CeIh:[vR@Lh | (BhAMy=݃  G"'wzn޺~8ԽSh ~T*A:xR[ܹ?X[uKL_=fDȊ؂p0}7=D$Ekq!/t.*2ʼnDbŞ}DijYaȲ(""6HA;:LzxQ‘(SQQ}*PL*fc\s `/d'QXW, e`#kPGZuŞuO{{wm[&NBTiiI0bukcA9<4@SӊH*؎4U/'2U5.(9JuDfrޱtycU%j(:RUbArLֺN)udA':uGQN"-"Is.*+k@ `Ojs@yU/ H:l;@yyTn}_yw!VkRJ4P)~y#)r,D =ě"Q]ci'%HI4ZL0"MJy 8A{ aN<8D"1#IJi >XjX֔#@>-{vN!8tRݻ^)N_╗FJEk]CT՟ YP:_|H1@ CBk]yKYp|og?*dGvzنzӴzjֺNkC~AbZƷ`.H)=!QͷVTT(| u78y֮}|[8-Vjp%2JPk[}ԉaH8Wpqhwr:vWª<}l77_~{s۴V+RCģ%WRZ\AqHifɤL36: #F:p]Bq/z{0CU6ݳEv_^k7'>sq*+kH%a`0ԣisqにtү04gVgW΂iJiS'3w.w}l6MC2uԯ|>JF5`fV5m`Y**Db1FKNttu]4ccsQNnex/87+}xaUW9y>ͯ骵G{䩓Գ3+vU}~jJ.NFRD7<aJDB1#ҳgSb,+CS?/ VG J?|?,2#M9}B)MiE+G`-wo߫V`fio(}S^4e~V4bHOYb"b#E)dda:'?}׮4繏`{7Z"uny-?ǹ;0MKx{:_pÚmFמ:F " .LFQLG)Q8qN q¯¯3wOvxDb\. BKD9_NN &L:4D{mm o^tֽ:q!ƥ}K+<"m78N< ywsard5+вz~mnG)=}lYݧNj'QJS{S :UYS-952?&O-:W}(!6Mk4+>A>j+i|<<|;ر^߉=HE|V#F)Emm#}/"y GII웻Jі94+v뾧xu~5C95~ūH>c@덉pʃ1/4-A2G%7>m;–Y,cyyaln" ?ƻ!ʪ<{~h~i y.zZB̃/,雋SiC/JFMmBH&&FAbϓO^tubbb_hZ{_QZ-sύodFgO(6]TJA˯#`۶ɟ( %$&+V'~hiYy>922 Wp74Zkq+Ovn錄c>8~GqܲcWꂎz@"1A.}T)uiW4="jJ2W7mU/N0gcqܗOO}?9/wìXžΏ0 >֩(V^Rh32!Hj5`;O28؇2#ݕf3 ?sJd8NJ@7O0 b־?lldщ̡&|9C.8RTWwxWy46ah嘦mh٤&l zCy!PY?: CJyв]dm4ǜҐR޻RլhX{FƯanшQI@x' ao(kUUuxW_Ñ줮[w8 FRJ(8˼)_mQ _!RJhm=!cVmm ?sFOnll6Qk}alY}; "baӌ~M0w,Ggw2W:G/k2%R,_=u`WU R.9T"v,<\Ik޽/2110Ӿxc0gyC&Ny޽JҢrV6N ``یeA16"J³+Rj*;BϜkZPJaÍ<Jyw:NP8/D$ 011z֊Ⱳ3ι֘k1V_"h!JPIΣ'ɜ* aEAd:ݺ>y<}Lp&PlRfTb1]o .2EW\ͮ]38؋rTJsǏP@芎sF\> P^+dYJLbJ C-xϐn> ι$nj,;Ǖa FU *择|h ~izť3ᤓ`K'-f tL7JK+vf2)V'-sFuB4i+m+@My=O҈0"|Yxoj,3]:cо3 $#uŘ%Y"y죯LebqtҢVzq¼X)~>4L׶m~[1_k?kxֺQ`\ |ٛY4Ѯr!)N9{56(iNq}O()Em]=F&u?$HypWUeB\k]JɩSع9 Zqg4ZĊo oMcjZBU]B\TUd34ݝ~:7ڶSUsB0Z3srx 7`:5xcx !qZA!;%͚7&P H<WL!džOb5kF)xor^aujƍ7 Ǡ8/p^(L>ὴ-B,{ۇWzֺ^k]3\EE@7>lYBȝR.oHnXO/}sB|.i@ɥDB4tcm,@ӣgdtJ!lH$_vN166L__'Z)y&kH;:,Y7=J 9cG) V\hjiE;gya~%ks_nC~Er er)muuMg2;֫R)Md) ,¶ 2-wr#F7<-BBn~_(o=KO㭇[Xv eN_SMgSҐ BS헃D%g_N:/pe -wkG*9yYSZS.9cREL !k}<4_Xs#FmҶ:7R$i,fi!~' # !6/S6y@kZkZcX)%5V4P]VGYq%H1!;e1MV<!ϐHO021Dp= HMs~~a)ަu7G^];git!Frl]H/L$=AeUvZE4P\.,xi {-~p?2b#amXAHq)MWǾI_r`S Hz&|{ +ʖ_= (YS(_g0a03M`I&'9vl?MM+m~}*xT۲(fY*V4x@29s{DaY"toGNTO+xCAO~4Ϳ;p`Ѫ:>Ҵ7K 3}+0 387x\)a"/E>qpWB=1 ¨"MP(\xp߫́A3+J] n[ʼnӼaTbZUWb={~2ooKױӰp(CS\S筐R*JغV&&"FA}J>G֐p1ٸbk7 ŘH$JoN <8s^yk_[;gy-;߉DV{c B yce% aJhDȶ 2IdйIB/^n0tNtџdcKj4϶v~- CBcgqx9= PJ) dMsjpYB] GD4RDWX +h{y`,3ꊕ$`zj*N^TP4L:Iz9~6s) Ga:?y*J~?OrMwP\](21sZUD ?ܟQ5Q%ggW6QdO+\@ ̪X'GxN @'4=ˋ+*VwN ne_|(/BDfj5(Dq<*tNt1х!MV.C0 32b#?n0pzj#!38}޴o1KovCJ`8ŗ_"]] rDUy޲@ Ȗ-;xџ'^Y`zEd?0„ DAL18IS]VGq\4o !swV7ˣι%4FѮ~}6)OgS[~Q vcYbL!wG3 7띸*E Pql8=jT\꘿I(z<[6OrR8ºC~ډ]=rNl[g|v TMTղb-o}OrP^Q]<98S¤!k)G(Vkwyqyr޽Nv`N/e p/~NAOk \I:G6]4+K;j$R:Mi #*[AȚT,ʰ,;N{HZTGMoּy) ]%dHء9Պ䠬|<45,\=[bƟ8QXeB3- &dҩ^{>/86bXmZ]]yޚN[(WAHL$YAgDKp=5GHjU&99v簪C0vygln*P)9^͞}lMuiH!̍#DoRBn9l@ xA/_v=ȺT{7Yt2N"4!YN`ae >Q<XMydEB`VU}u]嫇.%e^ánE87Mu\t`cP=AD/G)sI"@MP;)]%fH9'FNsj1pVhY&9=0pfuJ&gޤx+k:!r˭wkl03׼Ku C &ѓYt{.O.zҏ z}/tf_wEp2gvX)GN#I ݭ߽v/ .& и(ZF{e"=V!{zW`, ]+LGz"(UJp|j( #V4, 8B 0 9OkRrlɱl94)'VH9=9W|>PS['G(*I1==C<5"Pg+x'K5EMd؞Af8lG ?D FtoB[je?{k3zQ vZ;%Ɠ,]E>KZ+T/ EJxOZ1i #T<@ I}q9/t'zi(EMqw`mYkU6;[t4DPeckeM;H}_g pMww}k6#H㶏+b8雡Sxp)&C $@'b,fPߑt$RbJ'vznuS ~8='72_`{q纶|Q)Xk}cPz9p7O:'|G~8wx(a 0QCko|0ASD>Ip=4Q, d|F8RcU"/KM opKle M3#i0c%<7׿p&pZq[TR"BpqauIp$ 8~Ĩ!8Սx\ւdT>>Z40ks7 z2IQ}ItԀ<-%S⍤};zIb$I 5K}Q͙D8UguWE$Jh )cu4N tZl+[]M4k8֦Zeq֮M7uIqG 1==tLtR,ƜSrHYt&QP윯Lg' I,3@P'}'R˪e/%-Auv·ñ\> vDJzlӾNv5:|K/Jb6KI9)Zh*ZAi`?S {aiVDԲuy5W7pWeQJk֤#5&V<̺@/GH?^τZL|IJNvI:'P=Ϛt"¨=cud S Q.Ki0 !cJy;LJR;G{BJy޺[^8fK6)=yʊ+(k|&xQ2`L?Ȓ2@Mf 0C`6-%pKpm')c$׻K5[J*U[/#hH!6acB JA _|uMvDyk y)6OPYjœ50VT K}cǻP[ $:]4MEA.y)|B)cf-A?(e|lɉ#P9V)[9t.EiQPDѠ3ϴ;E:+Օ t ȥ~|_N2,ZJLt4! %ա]u {+=p.GhNcŞQI?Nd'yeh n7zi1DB)1S | S#ًZs2|Ɛy$F SxeX{7Vl.Src3E℃Q>b6G ўYCmtկ~=K0f(=LrAS GN'ɹ9<\!a`)֕y[uՍ[09` 9 +57ts6}b4{oqd+J5fa/,97J#6yν99mRWxJyѡyu_TJc`~W>l^q#Ts#2"nD1%fS)FU w{ܯ R{ ˎ󅃏џDsZSQS;LV;7 Od1&1n$ N /.q3~eNɪ]E#oM~}v֯FڦwyZ=<<>Xo稯lfMFV6p02|*=tV!c~]fa5Y^Q_WN|Vs 0ҘދU97OI'N2'8N֭fgg-}V%y]U4 峧p*91#9U kCac_AFңĪy뚇Y_AiuYyTTYЗ-(!JFLt›17uTozc. S;7A&&<ԋ5y;Ro+:' *eYJkWR[@F %SHWP 72k4 qLd'J "zB6{AC0ƁA6U.'F3:Ȅ(9ΜL;D]m8ڥ9}dU "v!;*13Rg^fJyShyy5auA?ɩGHRjo^]׽S)Fm\toy 4WQS@mE#%5ʈfFYDX ~D5Ϡ9tE9So_aU4?Ѽm%&c{n>.KW1Tlb}:j uGi(JgcYj0qn+>) %\!4{LaJso d||u//P_y7iRJ߬nHOy) l+@$($VFIQ9%EeKʈU. ia&FY̒mZ=)+qqoQn >L!qCiDB;Y<%} OgBxB!ØuG)WG9y(Ą{_yesuZmZZey'Wg#C~1Cev@0D $a@˲(.._GimA:uyw֬%;@!JkQVM_Ow:P.s\)ot- ˹"`B,e CRtaEUP<0'}r3[>?G8xU~Nqu;Wm8\RIkբ^5@k+5(By'L&'gBJ3ݶ!/㮻w҅ yqPWUg<e"Qy*167΃sJ\oz]T*UQ<\FԎ`HaNmڜ6DysCask8wP8y9``GJ9lF\G g's Nn͵MLN֪u$| /|7=]O)6s !ĴAKh]q_ap $HH'\1jB^s\|- W1:=6lJBqjY^LsPk""`]w)󭃈,(HC ?䔨Y$Sʣ{4Z+0NvQkhol6C.婧/u]FwiVjZka&%6\F*Ny#8O,22+|Db~d ~Çwc N:FuuCe&oZ(l;@ee-+Wn`44AMK➝2BRՈt7g*1gph9N) *"TF*R(#'88pm=}X]u[i7bEc|\~EMn}P瘊J)K.0i1M6=7'_\kaZ(Th{K*GJyytw"IO-PWJk)..axӝ47"89Cc7ĐBiZx 7m!fy|ϿF9CbȩV 9V-՛^pV̌ɄS#Bv4-@]Vxt-Z, &ֺ*diؠ2^VXbs֔Ìl.jQ]Y[47gj=幽ex)A0ip׳ W2[ᎇhuE^~q흙L} #-b۸oFJ_QP3r6jr+"nfzRJTUqoaۍ /$d8Mx'ݓ= OՃ| )$2mcM*cЙj}f };n YG w0Ia!1Q.oYfr]DyISaP}"dIӗթO67jqR ҊƐƈaɤGG|h;t]䗖oSv|iZqX)oalv;۩meEJ\!8=$4QU4Xo&VEĊ YS^E#d,yX_> ۘ-e\ "Wa6uLĜZi`aD9.% w~mB(02G[6y.773a7 /=o7D)$Z 66 $bY^\CuP. (x'"J60׿Y:Oi;F{w佩b+\Yi`TDWa~|VH)8q/=9!g߆2Y)?ND)%?Ǐ`k/sn:;O299yB=a[Ng 3˲N}vLNy;*?x?~L&=xyӴ~}q{qE*IQ^^ͧvü{Huu=R|>JyUlZV, B~/YF!Y\u_ݼF{_C)LD]m {H 0ihhadd nUkf3oٺCvE\)QJi+֥@tDJkB$1!Đr0XQ|q?d2) Ӣ_}qv-< FŊ߫%roppVBwü~JidY4:}L6M7f٬F "?71<2#?Jyy4뷢<_a7_=Q E=S1И/9{+93֮E{ǂw{))?maÆm(uLE#lïZ  ~d];+]h j?!|$F}*"4(v'8s<ŏUkm7^7no1w2ؗ}TrͿEk>p'8OB7d7R(A 9.*Mi^ͳ; eeUwS+C)uO@ =Sy]` }l8^ZzRXj[^iUɺ$tj))<sbDJfg=Pk_{xaKo1:-uyG0M ԃ\0Lvuy'ȱc2Ji AdyVgVh!{]/&}}ċJ#%d !+87<;qN޼Nفl|1N:8ya  8}k¾+-$4FiZYÔXk*I&'@iI99)HSh4+2G:tGhS^繿 Kتm0 вDk}֚+QT4;sC}rՅE,8CX-e~>G&'9xpW,%Fh,Ry56Y–hW-(v_,? ; qrBk4-V7HQ;ˇ^Gv1JVV%,ik;D_W!))+BoS4QsTM;gt+ndS-~:11Sgv!0qRVh!"Ȋ(̦Yl.]PQWgٳE'`%W1{ndΗBk|Ž7ʒR~,lnoa&:ü$ 3<a[CBݮwt"o\ePJ=Hz"_c^Z.#ˆ*x z̝grY]tdkP*:97YľXyBkD4N.C_[;F9`8& !AMO c `@BA& Ost\-\NX+Xp < !bj3C&QL+*&kAQ=04}cC!9~820G'PC9xa!w&bo_1 Sw"ܱ V )Yl3+ס2KoXOx]"`^WOy :3GO0g;%Yv㐫(R/r (s } u B &FeYZh0y> =2<Ϟc/ -u= c&׭,.0"g"7 6T!vl#sc>{u/Oh Bᾈ)۴74]x7 gMӒ"d]U)}" v4co[ ɡs 5Gg=XR14?5A}D "b{0$L .\4y{_fe:kVS\\O]c^W52LSBDM! C3Dhr̦RtArx4&agaN3Cf<Ԉp4~ B'"1@.b_/xQ} _߃҉/gٓ2Qkqp0շpZ2fԫYz< 4L.Cyυι1t@鎫Fe sYfsF}^ V}N<_`p)alٶ "(XEAVZ<)2},:Ir*#m_YӼ R%a||EƼIJ,,+f"96r/}0jE/)s)cjW#w'Sʯ5<66lj$a~3Kʛy 2:cZ:Yh))+a߭K::N,Q F'qB]={.]h85C9cr=}*rk?vwV렵ٸW Rs%}rNAkDv|uFLBkWY YkX מ|)1!$#3%y?pF<@<Rr0}: }\J [5FRxY<9"SQdE(Q*Qʻ)q1E0B_O24[U'],lOb ]~WjHޏTQ5Syu wq)xnw8~)c 쫬gٲߠ H% k5dƝk> kEj,0% b"vi2Wس_CuK)K{n|>t{P1򨾜j>'kEkƗBg*H%'_aY6Bn!TL&ɌOb{c`'d^{t\i^[uɐ[}q0lM˕G:‚4kb祔c^:?bpg… +37stH:0}en6x˟%/<]BL&* 5&fK9Mq)/iyqtA%kUe[ڛKN]Ě^,"`/ s[EQQm?|XJ߅92m]G.E΃ח U*Cn.j_)Tѧj̿30ڇ!A0=͜ar I3$C^-9#|pk!)?7.x9 @OO;WƝZBFU keZ75F6Tc6"ZȚs2y/1 ʵ:u4xa`C>6Rb/Yм)^=+~uRd`/|_8xbB0?Ft||Z\##|K 0>>zxv8۴吅q 8ĥ)"6>~\8:qM}#͚'ĉ#p\׶ l#bA?)|g g9|8jP(cr,BwV (WliVxxᡁ@0Okn;ɥh$_ckCgriv}>=wGzβ KkBɛ[˪ !J)h&k2%07δt}!d<9;I&0wV/ v 0<H}L&8ob%Hi|޶o&h1L|u֦y~󛱢8fٲUsւ)0oiFx2}X[zVYr_;N(w]_4B@OanC?gĦx>мgx>ΛToZoOMp>40>V Oy V9iq!4 LN,ˢu{jsz]|"R޻&'ƚ{53ўFu(<٪9:΋]B;)B>1::8;~)Yt|0(pw2N%&X,URBK)3\zz&}ax4;ǟ(tLNg{N|Ǽ\G#C9g$^\}p?556]/RP.90 k,U8/u776s ʪ_01چ|\N 0VV*3H鴃J7iI!wG_^ypl}r*jɤSR 5QN@ iZ#1ٰy;_\3\BQQ x:WJv츟ٯ$"@6 S#qe딇(/P( Dy~TOϻ<4:-+F`0||;Xl-"uw$Цi󼕝mKʩorz"mϺ$F:~E'ҐvD\y?Rr8_He@ e~O,T.(ފR*cY^m|cVR[8 JҡSm!ΆԨb)RHG{?MpqrmN>߶Y)\p,d#xۆWY*,l6]v0h15M˙MS8+EdI='LBJIH7_9{Caз*Lq,dt >+~ّeʏ?xԕ4bBAŚjﵫ!'\Ը$WNvKO}ӽmSşذqsOy?\[,d@'73'j%kOe`1.g2"e =YIzS2|zŐƄa\U,dP;jhhhaxǶ?КZ՚.q SE+XrbOu%\GتX(H,N^~]JyEZQKceTQ]VGYqnah;y$cQahT&QPZ*iZ8UQQM.qo/T\7X"u?Mttl2Xq(IoW{R^ ux*SYJ! 4S.Jy~ BROS[V|žKNɛP(L6V^|cR7i7nZW1Fd@ Ara{詑|(T*dN]Ko?s=@ |_EvF]׍kR)eBJc" MUUbY6`~V޴dJKß&~'d3i WWWWWW
Current Directory: /opt/saltstack/salt/lib/python3.10/site-packages/salt/states
Viewing File: /opt/saltstack/salt/lib/python3.10/site-packages/salt/states/boto_secgroup.py
""" Manage Security Groups ====================== .. versionadded:: 2014.7.0 Create and destroy Security Groups. Be aware that this interacts with Amazon's services, and so may incur charges. This module uses ``boto``, which can be installed via package, or pip. This module accepts explicit EC2 credentials but can also utilize IAM roles assigned to the instance through Instance Profiles. Dynamic credentials are then automatically obtained from AWS API and no further configuration is necessary. More information available `here <http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html>`_. If IAM roles are not used you need to specify them either in a pillar file or in the minion's config file: .. code-block:: yaml secgroup.keyid: GKTADJGHEIQSXMKKRBJ08H secgroup.key: askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs It's also possible to specify ``key``, ``keyid`` and ``region`` via a profile, either passed in as a dict, or as a string to pull from pillars or minion config: .. code-block:: yaml myprofile: keyid: GKTADJGHEIQSXMKKRBJ08H key: askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs region: us-east-1 .. code-block:: yaml Ensure mysecgroup exists: boto_secgroup.present: - name: mysecgroup - description: My security group - vpc_name: myvpc - rules: - ip_protocol: tcp from_port: 80 to_port: 80 cidr_ip: - 10.0.0.0/8 - 192.168.0.0/16 - ip_protocol: tcp from_port: 8080 to_port: 8090 cidr_ip: - 10.0.0.0/8 - 192.168.0.0/16 - ip_protocol: icmp from_port: -1 to_port: -1 source_group_name: mysecgroup - ip_protocol: tcp from_port: 8080 to_port: 8080 source_group_name: MyOtherSecGroup source_group_name_vpc: MyPeeredVPC - rules_egress: - ip_protocol: all from_port: -1 to_port: -1 cidr_ip: - 10.0.0.0/8 - 192.168.0.0/16 - tags: SomeTag: 'My Tag Value' SomeOtherTag: 'Other Tag Value' - region: us-east-1 - keyid: GKTADJGHEIQSXMKKRBJ08H - key: askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs # Using a profile from pillars Ensure mysecgroup exists: boto_secgroup.present: - name: mysecgroup - description: My security group - profile: myprofile # Passing in a profile Ensure mysecgroup exists: boto_secgroup.present: - name: mysecgroup - description: My security group - profile: keyid: GKTADJGHEIQSXMKKRBJ08H key: askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs region: us-east-1 .. note:: When using the ``profile`` parameter and ``region`` is set outside of the profile group, region is ignored and a default region will be used. If ``region`` is missing from the ``profile`` data set, ``us-east-1`` will be used as the default region. """ import logging import pprint import salt.utils.dictupdate as dictupdate from salt.exceptions import SaltInvocationError log = logging.getLogger(__name__) def __virtual__(): """ Only load if boto is available. """ if "boto_secgroup.exists" in __salt__: return "boto_secgroup" return (False, "boto_secgroup module could not be loaded") def present( name, description, vpc_id=None, vpc_name=None, rules=None, rules_egress=None, delete_ingress_rules=True, delete_egress_rules=True, region=None, key=None, keyid=None, profile=None, tags=None, ): """ Ensure the security group exists with the specified rules. name Name of the security group. description A description of this security group. vpc_id The ID of the VPC to create the security group in, if any. Exclusive with vpc_name. vpc_name The name of the VPC to create the security group in, if any. Exclusive with vpc_id. .. versionadded:: 2016.3.0 .. versionadded:: 2015.8.2 rules A list of ingress rule dicts. If not specified, ``rules=None``, the ingress rules will be unmanaged. If set to an empty list, ``[]``, then all ingress rules will be removed. rules_egress A list of egress rule dicts. If not specified, ``rules_egress=None``, the egress rules will be unmanaged. If set to an empty list, ``[]``, then all egress rules will be removed. delete_ingress_rules Some tools (EMR comes to mind) insist on adding rules on-the-fly, which salt will happily remove on the next run. Set this param to False to avoid deleting rules which were added outside of salt. delete_egress_rules Some tools (EMR comes to mind) insist on adding rules on-the-fly, which salt will happily remove on the next run. Set this param to False to avoid deleting rules which were added outside of salt. region Region to connect to. key Secret key to be used. keyid Access key to be used. profile A dict with region, key and keyid, or a pillar key (string) that contains a dict with region, key, and keyid. tags List of key:value pairs of tags to set on the security group .. versionadded:: 2016.3.0 """ ret = {"name": name, "result": True, "comment": "", "changes": {}} _ret = _security_group_present( name, description, vpc_id=vpc_id, vpc_name=vpc_name, region=region, key=key, keyid=keyid, profile=profile, ) ret["changes"] = _ret["changes"] ret["comment"] = " ".join([ret["comment"], _ret["comment"]]) if not _ret["result"]: ret["result"] = _ret["result"] if ret["result"] is False: return ret elif ret["result"] is None: return ret if rules is not None: _ret = _rules_present( name, rules, delete_ingress_rules, vpc_id=vpc_id, vpc_name=vpc_name, region=region, key=key, keyid=keyid, profile=profile, ) ret["changes"] = dictupdate.update(ret["changes"], _ret["changes"]) ret["comment"] = " ".join([ret["comment"], _ret["comment"]]) if not _ret["result"]: ret["result"] = _ret["result"] if rules_egress is not None: _ret = _rules_egress_present( name, rules_egress, delete_egress_rules, vpc_id=vpc_id, vpc_name=vpc_name, region=region, key=key, keyid=keyid, profile=profile, ) ret["changes"] = dictupdate.update(ret["changes"], _ret["changes"]) ret["comment"] = " ".join([ret["comment"], _ret["comment"]]) if not _ret["result"]: ret["result"] = _ret["result"] _ret = _tags_present( name=name, tags=tags, vpc_id=vpc_id, vpc_name=vpc_name, region=region, key=key, keyid=keyid, profile=profile, ) ret["changes"] = dictupdate.update(ret["changes"], _ret["changes"]) ret["comment"] = " ".join([ret["comment"], _ret["comment"]]) if not _ret["result"]: ret["result"] = _ret["result"] return ret def _security_group_present( name, description, vpc_id=None, vpc_name=None, region=None, key=None, keyid=None, profile=None, ): """ given a group name or a group name and vpc id (or vpc name): 1. determine if the group exists 2. if the group does not exist, creates the group 3. return the group's configuration and any changes made """ ret = {"result": True, "comment": "", "changes": {}} exists = __salt__["boto_secgroup.exists"]( name, region, key, keyid, profile, vpc_id, vpc_name ) if not exists: if __opts__["test"]: ret["comment"] = f"Security group {name} is set to be created." ret["result"] = None return ret created = __salt__["boto_secgroup.create"]( name=name, description=description, vpc_id=vpc_id, vpc_name=vpc_name, region=region, key=key, keyid=keyid, profile=profile, ) if created: ret["changes"]["old"] = {"secgroup": None} sg = __salt__["boto_secgroup.get_config"]( name=name, group_id=None, region=region, key=key, keyid=keyid, profile=profile, vpc_id=vpc_id, vpc_name=vpc_name, ) ret["changes"]["new"] = {"secgroup": sg} ret["comment"] = f"Security group {name} created." else: ret["result"] = False ret["comment"] = f"Failed to create {name} security group." else: ret["comment"] = f"Security group {name} present." return ret def _split_rules(rules): """ Split rules with lists into individual rules. We accept some attributes as lists or strings. The data we get back from the execution module lists rules as individual rules. We need to split the provided rules into individual rules to compare them. """ split = [] for rule in rules: cidr_ip = rule.get("cidr_ip") group_name = rule.get("source_group_name") group_id = rule.get("source_group_group_id") if cidr_ip and not isinstance(cidr_ip, str): for ip in cidr_ip: _rule = rule.copy() _rule["cidr_ip"] = ip split.append(_rule) elif group_name and not isinstance(group_name, str): for name in group_name: _rule = rule.copy() _rule["source_group_name"] = name split.append(_rule) elif group_id and not isinstance(group_id, str): for _id in group_id: _rule = rule.copy() _rule["source_group_group_id"] = _id split.append(_rule) else: split.append(rule) return split def _check_rule(rule, _rule): """ Check to see if two rules are the same. Needed to compare rules fetched from boto, since they may not completely match rules defined in sls files but may be functionally equivalent. """ # We need to alter what Boto returns if no ports are specified # so that we can compare rules fairly. # # Boto returns None for from_port and to_port where we're required # to pass in "-1" instead. if _rule.get("from_port") is None: _rule["from_port"] = -1 if _rule.get("to_port") is None: _rule["to_port"] = -1 if ( rule["ip_protocol"] == _rule["ip_protocol"] and str(rule["from_port"]) == str(_rule["from_port"]) and str(rule["to_port"]) == str(_rule["to_port"]) ): _cidr_ip = _rule.get("cidr_ip") if _cidr_ip and _cidr_ip == rule.get("cidr_ip"): return True _owner_id = _rule.get("source_group_owner_id") if _owner_id and _owner_id == rule.get("source_group_owner_id"): return True _group_id = _rule.get("source_group_group_id") if _group_id and _group_id == rule.get("source_group_group_id"): return True _group_name = _rule.get("source_group_name") if _group_name and _group_id == rule.get("source_group_name"): return True return False def _get_rule_changes(rules, _rules): """ given a list of desired rules (rules) and existing rules (_rules) return a list of rules to delete (to_delete) and to create (to_create) """ to_delete = [] to_create = [] # for each rule in state file # 1. validate rule # 2. determine if rule exists in existing security group rules for rule in rules: try: ip_protocol = str(rule.get("ip_protocol")) except KeyError: raise SaltInvocationError( "ip_protocol, to_port, and from_port are" " required arguments for security group" " rules." ) supported_protocols = [ "tcp", "6", 6, "udp", "17", 17, "icmp", "1", 1, "all", "-1", -1, ] if ip_protocol not in supported_protocols and ( not f"{ip_protocol}".isdigit() or int(ip_protocol) > 255 ): raise SaltInvocationError( "Invalid ip_protocol {} specified in security group rule.".format( ip_protocol ) ) # For the 'all' case, we need to change the protocol name to '-1'. if ip_protocol == "all": rule["ip_protocol"] = "-1" cidr_ip = rule.get("cidr_ip", None) group_name = rule.get("source_group_name", None) group_id = rule.get("source_group_group_id", None) if cidr_ip and (group_id or group_name): raise SaltInvocationError( "cidr_ip and source groups can not both" " be specified in security group rules." ) if group_id and group_name: raise SaltInvocationError( "Either source_group_group_id or" " source_group_name can be specified in" " security group rules, but not both." ) if not (cidr_ip or group_id or group_name): raise SaltInvocationError( "cidr_ip, source_group_group_id, or" " source_group_name must be provided for" " security group rules." ) rule_found = False # for each rule in existing security group ruleset determine if # new rule exists for _rule in _rules: if _check_rule(rule, _rule): rule_found = True break if not rule_found: to_create.append(rule) # for each rule in existing security group configuration # 1. determine if rules needed to be deleted for _rule in _rules: rule_found = False for rule in rules: if _check_rule(rule, _rule): rule_found = True break if not rule_found: # Can only supply name or id, not both. Since we're deleting # entries, it doesn't matter which we pick. _rule.pop("source_group_name", None) to_delete.append(_rule) log.debug("Rules to be deleted: %s", to_delete) log.debug("Rules to be created: %s", to_create) return (to_delete, to_create) def _rules_present( name, rules, delete_ingress_rules=True, vpc_id=None, vpc_name=None, region=None, key=None, keyid=None, profile=None, ): """ given a group name or group name and vpc_id (or vpc name): 1. get lists of desired rule changes (using _get_rule_changes) 2. authorize/create rules missing rules 3. if delete_ingress_rules is True, delete/revoke non-requested rules 4. return 'old' and 'new' group rules """ ret = {"result": True, "comment": "", "changes": {}} sg = __salt__["boto_secgroup.get_config"]( name=name, group_id=None, region=region, key=key, keyid=keyid, profile=profile, vpc_id=vpc_id, vpc_name=vpc_name, ) if not sg: ret["comment"] = f"{name} security group configuration could not be retrieved." ret["result"] = False return ret rules = _split_rules(rules) if vpc_id or vpc_name: for rule in rules: _source_group_name = rule.get("source_group_name", None) if _source_group_name: _group_vpc_name = vpc_name _group_vpc_id = vpc_id _source_group_name_vpc = rule.get("source_group_name_vpc", None) if _source_group_name_vpc: _group_vpc_name = _source_group_name_vpc _group_vpc_id = None _group_id = __salt__["boto_secgroup.get_group_id"]( name=_source_group_name, vpc_id=_group_vpc_id, vpc_name=_group_vpc_name, region=region, key=key, keyid=keyid, profile=profile, ) if not _group_id: raise SaltInvocationError( "source_group_name {} does not map to a valid " "source group id.".format(_source_group_name) ) rule["source_group_name"] = None if _source_group_name_vpc: rule.pop("source_group_name_vpc") rule["source_group_group_id"] = _group_id # rules = rules that exist in salt state # sg['rules'] = that exist in present group to_delete, to_create = _get_rule_changes(rules, sg["rules"]) to_delete = to_delete if delete_ingress_rules else [] if to_create or to_delete: if __opts__["test"]: msg = """Security group {} set to have rules modified. To be created: {} To be deleted: {}""".format( name, pprint.pformat(to_create), pprint.pformat(to_delete) ) ret["comment"] = msg ret["result"] = None return ret if to_delete: deleted = True for rule in to_delete: _deleted = __salt__["boto_secgroup.revoke"]( name, vpc_id=vpc_id, vpc_name=vpc_name, region=region, key=key, keyid=keyid, profile=profile, **rule, ) if not _deleted: deleted = False if deleted: ret["comment"] = f"Removed rules on {name} security group." else: ret["comment"] = "Failed to remove rules on {} security group.".format( name ) ret["result"] = False if to_create: created = True for rule in to_create: _created = __salt__["boto_secgroup.authorize"]( name, vpc_id=vpc_id, vpc_name=vpc_name, region=region, key=key, keyid=keyid, profile=profile, **rule, ) if not _created: created = False if created: ret["comment"] = " ".join( [ ret["comment"], f"Created rules on {name} security group.", ] ) else: ret["comment"] = " ".join( [ ret["comment"], f"Failed to create rules on {name} security group.", ] ) ret["result"] = False ret["changes"]["old"] = {"rules": sg["rules"]} sg = __salt__["boto_secgroup.get_config"]( name=name, group_id=None, region=region, key=key, keyid=keyid, profile=profile, vpc_id=vpc_id, vpc_name=vpc_name, ) ret["changes"]["new"] = {"rules": sg["rules"]} return ret def _rules_egress_present( name, rules_egress, delete_egress_rules=True, vpc_id=None, vpc_name=None, region=None, key=None, keyid=None, profile=None, ): """ given a group name or group name and vpc_id (or vpc name): 1. get lists of desired rule changes (using _get_rule_changes) 2. authorize/create missing rules 3. if delete_egress_rules is True, delete/revoke non-requested rules 4. return 'old' and 'new' group rules """ ret = {"result": True, "comment": "", "changes": {}} sg = __salt__["boto_secgroup.get_config"]( name=name, group_id=None, region=region, key=key, keyid=keyid, profile=profile, vpc_id=vpc_id, vpc_name=vpc_name, ) if not sg: ret["comment"] = f"{name} security group configuration could not be retrieved." ret["result"] = False return ret rules_egress = _split_rules(rules_egress) if vpc_id or vpc_name: for rule in rules_egress: _source_group_name = rule.get("source_group_name", None) if _source_group_name: _group_vpc_name = vpc_name _group_vpc_id = vpc_id _source_group_name_vpc = rule.get("source_group_name_vpc", None) if _source_group_name_vpc: _group_vpc_name = _source_group_name_vpc _group_vpc_id = None _group_id = __salt__["boto_secgroup.get_group_id"]( name=_source_group_name, vpc_id=_group_vpc_id, vpc_name=_group_vpc_name, region=region, key=key, keyid=keyid, profile=profile, ) if not _group_id: raise SaltInvocationError( "source_group_name {} does not map to a valid " "source group id.".format(_source_group_name) ) rule["source_group_name"] = None if _source_group_name_vpc: rule.pop("source_group_name_vpc") rule["source_group_group_id"] = _group_id # rules_egress = rules that exist in salt state # sg['rules_egress'] = that exist in present group to_delete, to_create = _get_rule_changes(rules_egress, sg["rules_egress"]) to_delete = to_delete if delete_egress_rules else [] if to_create or to_delete: if __opts__["test"]: msg = """Security group {} set to have rules modified. To be created: {} To be deleted: {}""".format( name, pprint.pformat(to_create), pprint.pformat(to_delete) ) ret["comment"] = msg ret["result"] = None return ret if to_delete: deleted = True for rule in to_delete: _deleted = __salt__["boto_secgroup.revoke"]( name, vpc_id=vpc_id, vpc_name=vpc_name, region=region, key=key, keyid=keyid, profile=profile, egress=True, **rule, ) if not _deleted: deleted = False if deleted: ret["comment"] = " ".join( [ ret["comment"], f"Removed egress rule on {name} security group.", ] ) else: ret["comment"] = " ".join( [ ret["comment"], "Failed to remove egress rule on {} security group.".format( name ), ] ) ret["result"] = False if to_create: created = True for rule in to_create: _created = __salt__["boto_secgroup.authorize"]( name, vpc_id=vpc_id, vpc_name=vpc_name, region=region, key=key, keyid=keyid, profile=profile, egress=True, **rule, ) if not _created: created = False if created: ret["comment"] = " ".join( [ ret["comment"], f"Created egress rules on {name} security group.", ] ) else: ret["comment"] = " ".join( [ ret["comment"], "Failed to create egress rules on {} security group.".format( name ), ] ) ret["result"] = False ret["changes"]["old"] = {"rules_egress": sg["rules_egress"]} sg = __salt__["boto_secgroup.get_config"]( name=name, group_id=None, region=region, key=key, keyid=keyid, profile=profile, vpc_id=vpc_id, vpc_name=vpc_name, ) ret["changes"]["new"] = {"rules_egress": sg["rules_egress"]} return ret def absent( name, vpc_id=None, vpc_name=None, region=None, key=None, keyid=None, profile=None ): """ Ensure a security group with the specified name does not exist. name Name of the security group. vpc_id The ID of the VPC to remove the security group from, if any. Exclusive with vpc_name. vpc_name The name of the VPC to remove the security group from, if any. Exclusive with vpc_name. .. versionadded:: 2016.3.0 region Region to connect to. key Secret key to be used. keyid Access key to be used. profile A dict with region, key and keyid, or a pillar key (string) that contains a dict with region, key and keyid. .. versionadded:: 2016.3.0 """ ret = {"name": name, "result": True, "comment": "", "changes": {}} sg = __salt__["boto_secgroup.get_config"]( name=name, group_id=None, region=region, key=key, keyid=keyid, profile=profile, vpc_id=vpc_id, vpc_name=vpc_name, ) if sg: if __opts__["test"]: ret["comment"] = f"Security group {name} is set to be removed." ret["result"] = None return ret deleted = __salt__["boto_secgroup.delete"]( name=name, group_id=None, region=region, key=key, keyid=keyid, profile=profile, vpc_id=vpc_id, vpc_name=vpc_name, ) if deleted: ret["changes"]["old"] = {"secgroup": sg} ret["changes"]["new"] = {"secgroup": None} ret["comment"] = f"Security group {name} deleted." else: ret["result"] = False ret["comment"] = f"Failed to delete {name} security group." else: ret["comment"] = f"{name} security group does not exist." return ret def _tags_present( name, tags, vpc_id=None, vpc_name=None, region=None, key=None, keyid=None, profile=None, ): """ helper function to validate tags are correct """ ret = {"result": True, "comment": "", "changes": {}} if tags: sg = __salt__["boto_secgroup.get_config"]( name=name, group_id=None, region=region, key=key, keyid=keyid, profile=profile, vpc_id=vpc_id, vpc_name=vpc_name, ) if not sg: ret["comment"] = ( f"{name} security group configuration could not be retrieved." ) ret["result"] = False return ret tags_to_add = tags tags_to_update = {} tags_to_remove = [] if sg.get("tags"): for existing_tag in sg["tags"]: if existing_tag not in tags: if existing_tag not in tags_to_remove: tags_to_remove.append(existing_tag) else: if tags[existing_tag] != sg["tags"][existing_tag]: tags_to_update[existing_tag] = tags[existing_tag] tags_to_add.pop(existing_tag) if tags_to_remove: if __opts__["test"]: msg = "The following tag{} set to be removed: {}.".format( ("s are" if len(tags_to_remove) > 1 else " is"), ", ".join(tags_to_remove), ) ret["comment"] = " ".join([ret["comment"], msg]) ret["result"] = None else: temp_ret = __salt__["boto_secgroup.delete_tags"]( tags_to_remove, name=name, group_id=None, vpc_name=vpc_name, vpc_id=vpc_id, region=region, key=key, keyid=keyid, profile=profile, ) if not temp_ret: ret["result"] = False ret["comment"] = " ".join( [ ret["comment"], "Error attempting to delete tags {}.".format( tags_to_remove ), ] ) return ret if "old" not in ret["changes"]: ret["changes"] = dictupdate.update( ret["changes"], {"old": {"tags": {}}} ) for rem_tag in tags_to_remove: ret["changes"]["old"]["tags"][rem_tag] = sg["tags"][rem_tag] if tags_to_add or tags_to_update: if __opts__["test"]: if tags_to_add: msg = "The following tag{} set to be added: {}.".format( ("s are" if len(tags_to_add.keys()) > 1 else " is"), ", ".join(tags_to_add.keys()), ) ret["comment"] = " ".join([ret["comment"], msg]) ret["result"] = None if tags_to_update: msg = "The following tag {} set to be updated: {}.".format( ( "values are" if len(tags_to_update.keys()) > 1 else "value is" ), ", ".join(tags_to_update.keys()), ) ret["comment"] = " ".join([ret["comment"], msg]) ret["result"] = None else: all_tag_changes = dictupdate.update(tags_to_add, tags_to_update) temp_ret = __salt__["boto_secgroup.set_tags"]( all_tag_changes, name=name, group_id=None, vpc_name=vpc_name, vpc_id=vpc_id, region=region, key=key, keyid=keyid, profile=profile, ) if not temp_ret: ret["result"] = False msg = "Error attempting to set tags." ret["comment"] = " ".join([ret["comment"], msg]) return ret if "old" not in ret["changes"]: ret["changes"] = dictupdate.update( ret["changes"], {"old": {"tags": {}}} ) if "new" not in ret["changes"]: ret["changes"] = dictupdate.update( ret["changes"], {"new": {"tags": {}}} ) for tag in all_tag_changes: ret["changes"]["new"]["tags"][tag] = tags[tag] if "tags" in sg: if sg["tags"]: if tag in sg["tags"]: ret["changes"]["old"]["tags"][tag] = sg["tags"][tag] if not tags_to_update and not tags_to_remove and not tags_to_add: ret["comment"] = " ".join([ret["comment"], "Tags are already set."]) return ret