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/modules
Viewing File: /opt/saltstack/salt/lib/python3.10/site-packages/salt/modules/nftables.py
""" Support for nftables """ import json import logging import re import salt.utils.data import salt.utils.files import salt.utils.path from salt.exceptions import CommandExecutionError from salt.state import STATE_INTERNAL_KEYWORDS as _STATE_INTERNAL_KEYWORDS log = logging.getLogger(__name__) _NFTABLES_FAMILIES = { "ipv4": "ip", "ip4": "ip", "ip": "ip", "ipv6": "ip6", "ip6": "ip6", "inet": "inet", "arp": "arp", "bridge": "bridge", "netdev": "netdev", } def __virtual__(): """ Only load the module if nftables is installed """ if salt.utils.path.which("nft"): return "nftables" return ( False, "The nftables execution module failed to load: nftables is not installed.", ) def _nftables_cmd(): """ Return correct command """ return "nft" def _conf(family="ip"): """ Use the same file for rules for now. """ if __grains__["os_family"] == "RedHat": return "/etc/nftables" elif __grains__["os_family"] == "Arch": return "/etc/nftables" elif __grains__["os_family"] == "Debian": return "/etc/nftables" elif __grains__["os"] == "Gentoo": return "/etc/nftables" else: return False def version(): """ Return version from nftables --version CLI Example: .. code-block:: bash salt '*' nftables.version """ cmd = f"{_nftables_cmd()} --version" out = __salt__["cmd.run"](cmd).split() return out[1] def build_rule( table=None, chain=None, command=None, position="", full=None, family="ipv4", **kwargs, ): """ Build a well-formatted nftables rule based on kwargs. A `table` and `chain` are not required, unless `full` is True. If `full` is `True`, then `table`, `chain` and `command` are required. `command` may be specified as either insert, append, or delete. This will return the nftables command, exactly as it would be used from the command line. If a position is required (as with `insert` or `delete`), it may be specified as `position`. This will only be useful if `full` is True. If `connstate` is passed in, it will automatically be changed to `state`. CLI Examples: .. code-block:: bash salt '*' nftables.build_rule match=state \\ connstate=RELATED,ESTABLISHED jump=ACCEPT salt '*' nftables.build_rule filter input command=insert position=3 \\ full=True match=state state=related,established jump=accept IPv6: salt '*' nftables.build_rule match=state \\ connstate=related,established jump=accept \\ family=ipv6 salt '*' nftables.build_rule filter input command=insert position=3 \\ full=True match=state state=related,established jump=accept \\ family=ipv6 """ ret = {"comment": "", "rule": "", "result": False} if "target" in kwargs: kwargs["jump"] = kwargs["target"] del kwargs["target"] for ignore in list(_STATE_INTERNAL_KEYWORDS) + ["chain", "save", "table"]: if ignore in kwargs: del kwargs[ignore] rule = "" proto = "" nft_family = _NFTABLES_FAMILIES[family] if "if" in kwargs: rule += "meta iifname {} ".format(kwargs["if"]) del kwargs["if"] if "of" in kwargs: rule += "meta oifname {} ".format(kwargs["of"]) del kwargs["of"] if "proto" in kwargs: proto = kwargs["proto"] if "state" in kwargs: del kwargs["state"] if "connstate" in kwargs: rule += "ct state {{ {0} }} ".format(kwargs["connstate"]) del kwargs["connstate"] if "icmp-type" in kwargs: rule += "icmp type {{ {0} }} ".format(kwargs["icmp-type"]) del kwargs["icmp-type"] if "pkttype" in kwargs: rule += "meta pkttype {{ {0} }} ".format(kwargs["pkttype"]) del kwargs["pkttype"] if "counter" in kwargs: rule += "counter " del kwargs["counter"] if "saddr" in kwargs or "source" in kwargs: rule += "{} saddr {} ".format( nft_family, kwargs.get("saddr") or kwargs.get("source") ) if "saddr" in kwargs: del kwargs["saddr"] if "source" in kwargs: del kwargs["source"] if "daddr" in kwargs or "destination" in kwargs: rule += "{} daddr {} ".format( nft_family, kwargs.get("daddr") or kwargs.get("destination") ) if "daddr" in kwargs: del kwargs["daddr"] if "destination" in kwargs: del kwargs["destination"] if "dport" in kwargs: kwargs["dport"] = str(kwargs["dport"]) if ":" in kwargs["dport"]: kwargs["dport"] = kwargs["dport"].replace(":", "-") rule += "dport {{ {0} }} ".format(kwargs["dport"]) del kwargs["dport"] if "sport" in kwargs: kwargs["sport"] = str(kwargs["sport"]) if ":" in kwargs["sport"]: kwargs["sport"] = kwargs["sport"].replace(":", "-") rule += "sport {{ {0} }} ".format(kwargs["sport"]) del kwargs["sport"] if "dports" in kwargs: # nftables reverse sorts the ports from # high to low, create rule like this # so that the check will work _dports = kwargs["dports"].split(",") _dports = [int(x) for x in _dports] _dports.sort(reverse=True) kwargs["dports"] = ", ".join(str(x) for x in _dports) rule += "dport {{ {0} }} ".format(kwargs["dports"]) del kwargs["dports"] if "sports" in kwargs: # nftables reverse sorts the ports from # high to low, create rule like this # so that the check will work _sports = kwargs["sports"].split(",") _sports = [int(x) for x in _sports] _sports.sort(reverse=True) kwargs["sports"] = ", ".join(str(x) for x in _sports) rule += "sport {{ {0} }} ".format(kwargs["sports"]) del kwargs["sports"] # Jumps should appear last, except for any arguments that are passed to # jumps, which of course need to follow. after_jump = [] if "jump" in kwargs: after_jump.append("{} ".format(kwargs["jump"])) del kwargs["jump"] if "j" in kwargs: after_jump.append("{} ".format(kwargs["j"])) del kwargs["j"] if "redirect-to" in kwargs or "to-port" in kwargs: after_jump.append( "redirect to {} ".format(kwargs.get("redirect-to") or kwargs.get("to-port")) ) if "redirect-to" in kwargs: del kwargs["redirect-to"] if "to-port" in kwargs: del kwargs["to-port"] if "to-ports" in kwargs: after_jump.append("--to-ports {} ".format(kwargs["to-ports"])) del kwargs["to-ports"] if "to-source" in kwargs: after_jump.append("{} ".format(kwargs["to-source"])) del kwargs["to-source"] if "to-destination" in kwargs: after_jump.append("{} ".format(kwargs["to-destination"])) del kwargs["to-destination"] if "reject-with" in kwargs: after_jump.append("reject with {} ".format(kwargs["reject-with"])) del kwargs["reject-with"] for item in after_jump: rule += item # Strip trailing spaces off rule rule = rule.strip() # Insert the protocol prior to dport or sport rule = rule.replace("dport", f"{proto} dport") rule = rule.replace("sport", f"{proto} sport") ret["rule"] = rule if full in ["True", "true"]: if not table: ret["comment"] = "Table needs to be specified" return ret if not chain: ret["comment"] = "Chain needs to be specified" return ret if not command: ret["comment"] = "Command needs to be specified" return ret if command in ["Insert", "insert", "INSERT"]: if position: ret["rule"] = "{} insert rule {} {} {} position {} {}".format( _nftables_cmd(), nft_family, table, chain, position, rule ) else: ret["rule"] = "{} insert rule {} {} {} {}".format( _nftables_cmd(), nft_family, table, chain, rule ) else: ret["rule"] = "{} {} rule {} {} {} {}".format( _nftables_cmd(), command, nft_family, table, chain, rule ) if ret["rule"]: ret["comment"] = "Successfully built rule" ret["result"] = True return ret def get_saved_rules(conf_file=None): """ Return a data structure of the rules in the conf file CLI Example: .. code-block:: bash salt '*' nftables.get_saved_rules """ if _conf() and not conf_file: conf_file = _conf() with salt.utils.files.fopen(conf_file) as fp_: lines = salt.utils.data.decode(fp_.readlines()) rules = [] for line in lines: tmpline = line.strip() if not tmpline: continue if tmpline.startswith("#"): continue rules.append(line) return rules def list_tables(family="ipv4"): """ Return a data structure of the current, in-memory tables CLI Example: .. code-block:: bash salt '*' nftables.list_tables salt '*' nftables.list_tables family=ipv6 """ nft_family = _NFTABLES_FAMILIES[family] tables = [] cmd = "{} --json --numeric --numeric --numeric list tables {}".format( _nftables_cmd(), nft_family ) out = __salt__["cmd.run"](cmd, python_shell=False) if not out: return tables try: data = json.loads(out) except ValueError: return tables if not data or not data.get("nftables"): return tables for item in data.get("nftables", []): if "metainfo" not in item: tables.append(item["table"]) log.debug(tables) return tables def get_rules(family="ipv4"): """ Return a data structure of the current, in-memory rules CLI Example: .. code-block:: bash salt '*' nftables.get_rules salt '*' nftables.get_rules family=ipv6 """ tables = list_tables(family) nft_family = _NFTABLES_FAMILIES[family] rules = [] for table in tables: table_name = table["name"] cmd = "{} --numeric --numeric --numeric list table {} {}".format( _nftables_cmd(), nft_family, table_name ) out = __salt__["cmd.run"](cmd, python_shell=False) rules.append(out) return rules def get_rules_json(family="ipv4"): """ .. versionadded:: 3002 Return a list of dictionaries comprising the current, in-memory rules family Networking family, either ipv4 or ipv6 CLI Example: .. code-block:: bash salt '*' nftables.get_rules_json salt '*' nftables.get_rules_json family=ipv6 """ nft_family = _NFTABLES_FAMILIES[family] rules = [] cmd = "{} --numeric --numeric --numeric --json list ruleset {}".format( _nftables_cmd(), nft_family ) out = __salt__["cmd.run"](cmd, python_shell=False) if not out: return rules try: rules = (json.loads(out))["nftables"] except (KeyError, ValueError): return rules return rules def save(filename=None, family="ipv4"): """ .. versionchanged:: 3002 Save the current in-memory rules to disk. On systems where /etc/nftables is a directory, a file named salt-all-in-one.nft will be dropped inside by default. The main nftables configuration will need to include this file. CLI Example: .. code-block:: bash salt '*' nftables.save /etc/nftables """ if _conf() and not filename: filename = _conf() # Invert the dictionary twice to get unique values only. nft_families = {v: k for k, v in _NFTABLES_FAMILIES.items()} nft_families = {v: k for k, v in nft_families.items()} rules = "#! nft -f\n" for family in nft_families: out = get_rules(family) if out: rules += "\n" rules = rules + "\n".join(out) rules = rules + "\n" if __salt__["file.directory_exists"](filename): filename = f"{filename}/salt-all-in-one.nft" try: with salt.utils.files.fopen(filename, "wb") as _fh: # Write out any changes _fh.write(salt.utils.data.encode(rules)) except OSError as exc: raise CommandExecutionError(f"Problem writing to configuration file: {exc}") return rules def get_rule_handle(table="filter", chain=None, rule=None, family="ipv4"): """ Get the handle for a particular rule This function accepts a rule in a standard nftables command format, starting with the chain. Trying to force users to adapt to a new method of creating rules would be irritating at best, and we already have a parser that can handle it. CLI Example: .. code-block:: bash salt '*' nftables.get_rule_handle filter input \\ rule='tcp dport 22 log accept' IPv6: salt '*' nftables.get_rule_handle filter input \\ rule='tcp dport 22 log accept' \\ family=ipv6 """ ret = {"comment": "", "result": False} if not chain: ret["comment"] = "Chain needs to be specified" return ret if not rule: ret["comment"] = "Rule needs to be specified" return ret res = check_table(table, family=family) if not res["result"]: return res res = check_chain(table, chain, family=family) if not res["result"]: return res res = check(table, chain, rule, family=family) if not res["result"]: return res nft_family = _NFTABLES_FAMILIES[family] cmd = "{} --numeric --numeric --numeric --handle list chain {} {} {}".format( _nftables_cmd(), nft_family, table, chain ) out = __salt__["cmd.run"](cmd, python_shell=False) rules = re.split("\n+", out) pat = re.compile(rf"{rule} # handle (?P<handle>\d+)") for r in rules: match = pat.search(r) if match: return {"result": True, "handle": match.group("handle")} return {"result": False, "comment": f"Could not find rule {rule}"} def check(table="filter", chain=None, rule=None, family="ipv4"): """ Check for the existence of a rule in the table and chain This function accepts a rule in a standard nftables command format, starting with the chain. Trying to force users to adapt to a new method of creating rules would be irritating at best, and we already have a parser that can handle it. CLI Example: .. code-block:: bash salt '*' nftables.check filter input \\ rule='tcp dport 22 log accept' IPv6: salt '*' nftables.check filter input \\ rule='tcp dport 22 log accept' \\ family=ipv6 """ ret = {"comment": "", "result": False} if not chain: ret["comment"] = "Chain needs to be specified" return ret if not rule: ret["comment"] = "Rule needs to be specified" return ret res = check_table(table, family=family) if not res["result"]: return res res = check_chain(table, chain, family=family) if not res["result"]: return res nft_family = _NFTABLES_FAMILIES[family] cmd = "{} --handle --numeric --numeric --numeric list chain {} {} {}".format( _nftables_cmd(), nft_family, table, chain ) search_rule = f"{rule} #" out = __salt__["cmd.run"](cmd, python_shell=False).find(search_rule) if out == -1: ret["comment"] = ( "Rule {} in chain {} in table {} in family {} does not exist".format( rule, chain, table, family ) ) else: ret["comment"] = "Rule {} in chain {} in table {} in family {} exists".format( rule, chain, table, family ) ret["result"] = True return ret def check_chain(table="filter", chain=None, family="ipv4"): """ .. versionadded:: 2014.7.0 Check for the existence of a chain in the table CLI Example: .. code-block:: bash salt '*' nftables.check_chain filter input IPv6: salt '*' nftables.check_chain filter input family=ipv6 """ ret = {"comment": "", "result": False} if not chain: ret["comment"] = "Chain needs to be specified" return ret nft_family = _NFTABLES_FAMILIES[family] cmd = f"{_nftables_cmd()} list table {nft_family} {table}" out = __salt__["cmd.run"](cmd, python_shell=False).find(f"chain {chain} {{") if out == -1: ret["comment"] = "Chain {} in table {} in family {} does not exist".format( chain, table, family ) else: ret["comment"] = "Chain {} in table {} in family {} exists".format( chain, table, family ) ret["result"] = True return ret def check_table(table=None, family="ipv4"): """ Check for the existence of a table CLI Example: .. code-block:: bash salt '*' nftables.check_table nat """ ret = {"comment": "", "result": False} if not table: ret["comment"] = "Table needs to be specified" return ret nft_family = _NFTABLES_FAMILIES[family] cmd = f"{_nftables_cmd()} list tables {nft_family}" out = __salt__["cmd.run"](cmd, python_shell=False).find( f"table {nft_family} {table}" ) if out == -1: ret["comment"] = f"Table {table} in family {family} does not exist" else: ret["comment"] = f"Table {table} in family {family} exists" ret["result"] = True return ret def new_table(table, family="ipv4"): """ .. versionadded:: 2014.7.0 Create new custom table. CLI Example: .. code-block:: bash salt '*' nftables.new_table filter IPv6: salt '*' nftables.new_table filter family=ipv6 """ ret = {"comment": "", "result": False} if not table: ret["comment"] = "Table needs to be specified" return ret res = check_table(table, family=family) if res["result"]: return res nft_family = _NFTABLES_FAMILIES[family] cmd = f"{_nftables_cmd()} add table {nft_family} {table}" out = __salt__["cmd.run"](cmd, python_shell=False) if not out: ret["comment"] = f"Table {table} in family {family} created" ret["result"] = True else: ret["comment"] = "Table {} in family {} could not be created".format( table, family ) return ret def delete_table(table, family="ipv4"): """ .. versionadded:: 2014.7.0 Create new custom table. CLI Example: .. code-block:: bash salt '*' nftables.delete_table filter IPv6: salt '*' nftables.delete_table filter family=ipv6 """ ret = {"comment": "", "result": False} if not table: ret["comment"] = "Table needs to be specified" return ret res = check_table(table, family=family) if not res["result"]: return res nft_family = _NFTABLES_FAMILIES[family] cmd = f"{_nftables_cmd()} delete table {nft_family} {table}" out = __salt__["cmd.run"](cmd, python_shell=False) if not out: ret["comment"] = f"Table {table} in family {family} deleted" ret["result"] = True else: ret["comment"] = "Table {} in family {} could not be deleted".format( table, family ) return ret def new_chain( table="filter", chain=None, table_type=None, hook=None, priority=None, family="ipv4" ): """ .. versionadded:: 2014.7.0 Create new chain to the specified table. CLI Example: .. code-block:: bash salt '*' nftables.new_chain filter input salt '*' nftables.new_chain filter input \\ table_type=filter hook=input priority=0 salt '*' nftables.new_chain filter foo IPv6: salt '*' nftables.new_chain filter input family=ipv6 salt '*' nftables.new_chain filter input \\ table_type=filter hook=input priority=0 family=ipv6 salt '*' nftables.new_chain filter foo family=ipv6 """ ret = {"comment": "", "result": False} if not chain: ret["comment"] = "Chain needs to be specified" return ret res = check_table(table, family=family) if not res["result"]: return res res = check_chain(table, chain, family=family) if res["result"]: ret["comment"] = "Chain {} in table {} in family {} already exists".format( chain, table, family ) return ret nft_family = _NFTABLES_FAMILIES[family] cmd = f"{_nftables_cmd()} -- add chain {nft_family} {table} {chain}" if table_type or hook or priority: if table_type and hook and str(priority): cmd = r"{0} \{{ type {1} hook {2} priority {3}\; \}}".format( cmd, table_type, hook, priority ) else: # Specify one, require all ret["comment"] = "Table_type, hook, and priority required." return ret out = __salt__["cmd.run"](cmd, python_shell=False) if not out: ret["comment"] = "Chain {} in table {} in family {} created".format( chain, table, family ) ret["result"] = True else: ret["comment"] = ( "Chain {} in table {} in family {} could not be created".format( chain, table, family ) ) return ret def delete_chain(table="filter", chain=None, family="ipv4"): """ .. versionadded:: 2014.7.0 Delete the chain from the specified table. CLI Example: .. code-block:: bash salt '*' nftables.delete_chain filter input salt '*' nftables.delete_chain filter foo IPv6: salt '*' nftables.delete_chain filter input family=ipv6 salt '*' nftables.delete_chain filter foo family=ipv6 """ ret = {"comment": "", "result": False} if not chain: ret["comment"] = "Chain needs to be specified" return ret res = check_table(table, family=family) if not res["result"]: return res res = check_chain(table, chain, family=family) if not res["result"]: return res nft_family = _NFTABLES_FAMILIES[family] cmd = f"{_nftables_cmd()} delete chain {nft_family} {table} {chain}" out = __salt__["cmd.run"](cmd, python_shell=False) if not out: ret["comment"] = "Chain {} in table {} in family {} deleted".format( chain, table, family ) ret["result"] = True else: ret["comment"] = ( "Chain {} in table {} in family {} could not be deleted".format( chain, table, family ) ) return ret def append(table="filter", chain=None, rule=None, family="ipv4"): """ Append a rule to the specified table & chain. This function accepts a rule in a standard nftables command format, starting with the chain. Trying to force users to adapt to a new method of creating rules would be irritating at best, and we already have a parser that can handle it. CLI Example: .. code-block:: bash salt '*' nftables.append filter input \\ rule='tcp dport 22 log accept' IPv6: salt '*' nftables.append filter input \\ rule='tcp dport 22 log accept' \\ family=ipv6 """ ret = { "comment": "Failed to append rule {} to chain {} in table {}.".format( rule, chain, table ), "result": False, } if not chain: ret["comment"] = "Chain needs to be specified" return ret if not rule: ret["comment"] = "Rule needs to be specified" return ret res = check_table(table, family=family) if not res["result"]: return res res = check_chain(table, chain, family=family) if not res["result"]: return res res = check(table, chain, rule, family=family) if res["result"]: ret["comment"] = ( "Rule {} chain {} in table {} in family {} already exists".format( rule, chain, table, family ) ) return ret nft_family = _NFTABLES_FAMILIES[family] cmd = "{} add rule {} {} {} {}".format( _nftables_cmd(), nft_family, table, chain, rule ) out = __salt__["cmd.run"](cmd, python_shell=False) if not out: ret["result"] = True ret["comment"] = 'Added rule "{}" chain {} in table {} in family {}.'.format( rule, chain, table, family ) else: ret["comment"] = ( 'Failed to add rule "{}" chain {} in table {} in family {}.'.format( rule, chain, table, family ) ) return ret def insert(table="filter", chain=None, position=None, rule=None, family="ipv4"): """ Insert a rule into the specified table & chain, at the specified position. If position is not specified, rule will be inserted in first position. This function accepts a rule in a standard nftables command format, starting with the chain. Trying to force users to adapt to a new method of creating rules would be irritating at best, and we already have a parser that can handle it. CLI Examples: .. code-block:: bash salt '*' nftables.insert filter input \\ rule='tcp dport 22 log accept' salt '*' nftables.insert filter input position=3 \\ rule='tcp dport 22 log accept' IPv6: salt '*' nftables.insert filter input \\ rule='tcp dport 22 log accept' \\ family=ipv6 salt '*' nftables.insert filter input position=3 \\ rule='tcp dport 22 log accept' \\ family=ipv6 """ ret = { "comment": f"Failed to insert rule {rule} to table {table}.", "result": False, } if not chain: ret["comment"] = "Chain needs to be specified" return ret if not rule: ret["comment"] = "Rule needs to be specified" return ret res = check_table(table, family=family) if not res["result"]: return res res = check_chain(table, chain, family=family) if not res["result"]: return res res = check(table, chain, rule, family=family) if res["result"]: ret["comment"] = ( "Rule {} chain {} in table {} in family {} already exists".format( rule, chain, table, family ) ) return ret nft_family = _NFTABLES_FAMILIES[family] if position: cmd = "{} insert rule {} {} {} position {} {}".format( _nftables_cmd(), nft_family, table, chain, position, rule ) else: cmd = "{} insert rule {} {} {} {}".format( _nftables_cmd(), nft_family, table, chain, rule ) out = __salt__["cmd.run"](cmd, python_shell=False) if not out: ret["result"] = True ret["comment"] = 'Added rule "{}" chain {} in table {} in family {}.'.format( rule, chain, table, family ) else: ret["comment"] = ( 'Failed to add rule "{}" chain {} in table {} in family {}.'.format( rule, chain, table, family ) ) return ret def delete(table, chain=None, position=None, rule=None, family="ipv4"): """ Delete a rule from the specified table & chain, specifying either the rule in its entirety, or the rule's position in the chain. This function accepts a rule in a standard nftables command format, starting with the chain. Trying to force users to adapt to a new method of creating rules would be irritating at best, and we already have a parser that can handle it. CLI Examples: .. code-block:: bash salt '*' nftables.delete filter input position=3 salt '*' nftables.delete filter input \\ rule='tcp dport 22 log accept' IPv6: salt '*' nftables.delete filter input position=3 family=ipv6 salt '*' nftables.delete filter input \\ rule='tcp dport 22 log accept' \\ family=ipv6 """ ret = { "comment": f"Failed to delete rule {rule} in table {table}.", "result": False, } if position and rule: ret["comment"] = "Only specify a position or a rule, not both" return ret res = check_table(table, family=family) if not res["result"]: return res res = check_chain(table, chain, family=family) if not res["result"]: return res res = check(table, chain, rule, family=family) if not res["result"]: ret["comment"] = ( "Rule {} chain {} in table {} in family {} does not exist".format( rule, chain, table, family ) ) return ret # nftables rules can only be deleted using the handle # if we don't have it, find it. if not position: position = get_rule_handle(table, chain, rule, family) nft_family = _NFTABLES_FAMILIES[family] cmd = "{} delete rule {} {} {} handle {}".format( _nftables_cmd(), nft_family, table, chain, position ) out = __salt__["cmd.run"](cmd, python_shell=False) if not out: ret["result"] = True ret["comment"] = ( 'Deleted rule "{}" in chain {} in table {} in family {}.'.format( rule, chain, table, family ) ) else: ret["comment"] = ( 'Failed to delete rule "{}" in chain {} table {} in family {}'.format( rule, chain, table, family ) ) return ret def flush(table="filter", chain="", family="ipv4"): """ Flush the chain in the specified table, flush all chains in the specified table if chain is not specified. CLI Example: .. code-block:: bash salt '*' nftables.flush filter salt '*' nftables.flush filter input IPv6: salt '*' nftables.flush filter input family=ipv6 """ ret = { "comment": "Failed to flush rules from chain {} in table {}.".format( chain, table ), "result": False, } res = check_table(table, family=family) if not res["result"]: return res nft_family = _NFTABLES_FAMILIES[family] if chain: res = check_chain(table, chain, family=family) if not res["result"]: return res cmd = "{} flush chain {} {} {}".format( _nftables_cmd(), nft_family, table, chain ) comment = f"from chain {chain} in table {table} in family {family}." else: cmd = f"{_nftables_cmd()} flush table {nft_family} {table}" comment = f"from table {table} in family {family}." out = __salt__["cmd.run"](cmd, python_shell=False) if not out: ret["result"] = True ret["comment"] = f"Flushed rules {comment}" else: ret["comment"] = f"Failed to flush rules {comment}" return ret def get_policy(table="filter", chain=None, family="ipv4"): """ .. versionadded:: 3002 Return the current policy for the specified table/chain table Name of the table containing the chain to check chain Name of the chain to get the policy for family Networking family, either ipv4 or ipv6 CLI Example: .. code-block:: bash salt '*' nftables.get_policy filter input IPv6: salt '*' nftables.get_policy filter input family=ipv6 """ if not chain: return "Error: Chain needs to be specified" nft_family = _NFTABLES_FAMILIES[family] rules = get_rules_json(family=nft_family) try: for rule in rules["nftables"]: if ( rule.get("chain", {}).get("name") == chain and rule.get("chain", {}).get("type") == table ): return rule["chain"]["policy"] except (KeyError, TypeError, ValueError): return None def set_policy(table="filter", chain=None, policy=None, family="ipv4"): """ .. versionadded:: 3002 Set the current policy for the specified table/chain. This only works on chains with an existing base chain. table Name of the table containing the chain to modify chain Name of the chain to set the policy for policy accept or drop family Networking family, either ipv4 or ipv6 CLI Example: .. code-block:: bash salt '*' nftables.set_policy filter input accept IPv6: salt '*' nftables.set_policy filter input accept family=ipv6 """ if not chain: return "Error: Chain needs to be specified" if not policy: return "Error: Policy needs to be specified" nft_family = _NFTABLES_FAMILIES[family] chain_info = {} rules = get_rules_json(family=nft_family) if not rules: return False for rule in rules: try: if rule["chain"]["table"] == table and rule["chain"]["name"] == chain: chain_info = rule["chain"] break except KeyError: continue if not chain_info: return False cmd = f"{_nftables_cmd()} add chain {nft_family} {table} {chain}" # We can't infer the base chain parameters. Bail out if they're not present. if "type" not in chain_info or "hook" not in chain_info or "prio" not in chain_info: return False params = "type {} hook {} priority {};".format( chain_info["type"], chain_info["hook"], chain_info["prio"] ) cmd = f'{cmd} "{{ {params} policy {policy}; }}"' out = __salt__["cmd.run_all"](cmd, python_shell=False) return not out["retcode"]