waf.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package aws
  15. import (
  16. "strconv"
  17. "strings"
  18. "github.com/aws/aws-sdk-go/service/wafv2"
  19. "yunion.io/x/jsonutils"
  20. "yunion.io/x/pkg/errors"
  21. api "yunion.io/x/cloudmux/pkg/apis/compute"
  22. "yunion.io/x/cloudmux/pkg/cloudprovider"
  23. "yunion.io/x/cloudmux/pkg/multicloud"
  24. )
  25. const (
  26. SCOPE_REGIONAL = "REGIONAL"
  27. SCOPE_CLOUDFRONT = "CLOUDFRONT"
  28. )
  29. var (
  30. WAF_SCOPES = []string{
  31. SCOPE_REGIONAL,
  32. SCOPE_CLOUDFRONT,
  33. }
  34. )
  35. type SWafRule struct {
  36. Action struct {
  37. Block struct {
  38. } `json:"Block"`
  39. } `json:"Action"`
  40. Name string `json:"Name"`
  41. }
  42. type SVisibilityConfig struct {
  43. CloudWatchMetricsEnabled bool
  44. MetricName string
  45. SampledRequestsEnabled bool
  46. }
  47. type SWebAcl struct {
  48. multicloud.SResourceBase
  49. AwsTags
  50. region *SRegion
  51. *wafv2.WebACL
  52. scope string
  53. LockToken string
  54. }
  55. func (self *SWebAcl) GetIsAccessProduct() bool {
  56. return false
  57. }
  58. func (self *SWebAcl) GetAccessHeaders() []string {
  59. return []string{}
  60. }
  61. func (self *SWebAcl) GetHttpPorts() []int {
  62. return []int{}
  63. }
  64. func (self *SWebAcl) GetHttpsPorts() []int {
  65. return []int{}
  66. }
  67. func (self *SWebAcl) GetCname() string {
  68. return ""
  69. }
  70. func (self *SWebAcl) GetCertId() string {
  71. return ""
  72. }
  73. func (self *SWebAcl) GetCertName() string {
  74. return ""
  75. }
  76. func (self *SWebAcl) GetUpstreamScheme() string {
  77. return ""
  78. }
  79. func (self *SWebAcl) GetUpstreamPort() int {
  80. return 0
  81. }
  82. func (self *SWebAcl) GetSourceIps() []string {
  83. return []string{}
  84. }
  85. func (self *SWebAcl) GetCcList() []string {
  86. return []string{}
  87. }
  88. func (self *SRegion) ListWebACLs(scope string) ([]SWebAcl, error) {
  89. if scope == SCOPE_CLOUDFRONT && self.RegionId != "us-east-1" {
  90. return []SWebAcl{}, nil
  91. }
  92. client, err := self.getWafClient()
  93. if err != nil {
  94. return nil, errors.Wrapf(err, "getWafClient")
  95. }
  96. ret := []SWebAcl{}
  97. input := wafv2.ListWebACLsInput{}
  98. input.SetScope(scope)
  99. for {
  100. resp, err := client.ListWebACLs(&input)
  101. if err != nil {
  102. return nil, errors.Wrapf(err, "ListWebACLs")
  103. }
  104. part := []SWebAcl{}
  105. jsonutils.Update(&part, resp.WebACLs)
  106. ret = append(ret, part...)
  107. if resp.NextMarker == nil || len(*resp.NextMarker) == 0 {
  108. break
  109. }
  110. input.SetNextMarker(*resp.NextMarker)
  111. }
  112. return ret, nil
  113. }
  114. func (self *SRegion) GetWebAcl(id, name, scope string) (*SWebAcl, error) {
  115. client, err := self.getWafClient()
  116. if err != nil {
  117. return nil, errors.Wrapf(err, "getWafClient")
  118. }
  119. input := wafv2.GetWebACLInput{}
  120. input.SetId(id)
  121. input.SetName(name)
  122. input.SetScope(scope)
  123. resp, err := client.GetWebACL(&input)
  124. if err != nil {
  125. if _, ok := err.(*wafv2.WAFNonexistentItemException); ok {
  126. return nil, errors.Wrapf(cloudprovider.ErrNotFound, "%s", err.Error())
  127. }
  128. return nil, errors.Wrapf(err, "GetWebAcl")
  129. }
  130. ret := &SWebAcl{region: self, scope: scope, WebACL: resp.WebACL, LockToken: *resp.LockToken}
  131. return ret, nil
  132. }
  133. func (self *SRegion) DeleteWebAcl(id, name, scope, lockToken string) error {
  134. client, err := self.getWafClient()
  135. if err != nil {
  136. return errors.Wrapf(err, "getWafClient")
  137. }
  138. input := wafv2.DeleteWebACLInput{}
  139. input.SetId(id)
  140. input.SetName(name)
  141. input.SetScope(scope)
  142. input.SetLockToken(lockToken)
  143. _, err = client.DeleteWebACL(&input)
  144. return errors.Wrapf(err, "DeleteWebACL")
  145. }
  146. func (self *SRegion) ListResourcesForWebACL(resType, arn string) ([]string, error) {
  147. client, err := self.getWafClient()
  148. if err != nil {
  149. return nil, errors.Wrapf(err, "getWafClient")
  150. }
  151. input := wafv2.ListResourcesForWebACLInput{}
  152. input.SetResourceType(resType)
  153. input.SetWebACLArn(arn)
  154. resp, err := client.ListResourcesForWebACL(&input)
  155. if err != nil {
  156. return nil, errors.Wrapf(err, "ListResourcesForWebACL")
  157. }
  158. ret := []string{}
  159. for _, id := range resp.ResourceArns {
  160. ret = append(ret, *id)
  161. }
  162. return ret, nil
  163. }
  164. func (self *SRegion) GetICloudWafInstanceById(id string) (cloudprovider.ICloudWafInstance, error) {
  165. idInfo := strings.Split(id, "/")
  166. if len(idInfo) != 4 {
  167. return nil, errors.Wrapf(cloudprovider.ErrNotFound, "invalid arn %s", id)
  168. }
  169. scope := SCOPE_CLOUDFRONT
  170. if strings.HasSuffix(idInfo[0], "regional") {
  171. scope = SCOPE_REGIONAL
  172. }
  173. ins, err := self.GetWebAcl(idInfo[3], idInfo[2], scope)
  174. if err != nil {
  175. return nil, errors.Wrapf(err, "GetWebAcl(%s, %s, %s)", idInfo[3], idInfo[2], scope)
  176. }
  177. return ins, nil
  178. }
  179. func (self *SRegion) GetICloudWafInstances() ([]cloudprovider.ICloudWafInstance, error) {
  180. ret := []cloudprovider.ICloudWafInstance{}
  181. for _, scope := range WAF_SCOPES {
  182. ins, err := self.ListWebACLs(scope)
  183. if err != nil {
  184. return nil, errors.Wrapf(err, "ListWebACLs")
  185. }
  186. for i := range ins {
  187. ins[i].region = self
  188. ins[i].scope = scope
  189. ret = append(ret, &ins[i])
  190. }
  191. }
  192. return ret, nil
  193. }
  194. func (self *SWebAcl) GetEnabled() bool {
  195. return true
  196. }
  197. func (self *SWebAcl) GetGlobalId() string {
  198. return *self.ARN
  199. }
  200. func (self *SWebAcl) GetName() string {
  201. return *self.Name
  202. }
  203. func (self *SWebAcl) GetId() string {
  204. return *self.ARN
  205. }
  206. func (self *SWebAcl) GetWafType() cloudprovider.TWafType {
  207. if self.scope == SCOPE_CLOUDFRONT {
  208. return cloudprovider.WafTypeCloudFront
  209. }
  210. return cloudprovider.WafTypeRegional
  211. }
  212. func (self *SWebAcl) GetStatus() string {
  213. return api.WAF_STATUS_AVAILABLE
  214. }
  215. func (self *SWebAcl) GetDefaultAction() *cloudprovider.DefaultAction {
  216. ret := &cloudprovider.DefaultAction{}
  217. if self.WebACL.DefaultAction == nil {
  218. self.Refresh()
  219. }
  220. if self.WebACL.DefaultAction != nil {
  221. action := self.WebACL.DefaultAction
  222. if action.Allow != nil {
  223. ret.Action = cloudprovider.WafActionAllow
  224. } else if action.Block != nil {
  225. ret.Action = cloudprovider.WafActionBlock
  226. }
  227. }
  228. return ret
  229. }
  230. func (self *SWebAcl) Refresh() error {
  231. acl, err := self.region.GetWebAcl(*self.Id, *self.Name, self.scope)
  232. if err != nil {
  233. return errors.Wrapf(err, "GetWebAcl")
  234. }
  235. self.WebACL = acl.WebACL
  236. return jsonutils.Update(self, acl)
  237. }
  238. func (self *SWebAcl) Delete() error {
  239. return self.region.DeleteWebAcl(*self.Id, *self.Name, self.scope, self.LockToken)
  240. }
  241. func (self *SRegion) CreateICloudWafInstance(opts *cloudprovider.WafCreateOptions) (cloudprovider.ICloudWafInstance, error) {
  242. waf, err := self.CreateWebAcl(opts.Name, opts.Desc, opts.Type, opts.DefaultAction)
  243. if err != nil {
  244. return nil, errors.Wrapf(err, "CreateWebAcl")
  245. }
  246. return waf, nil
  247. }
  248. func (self *SRegion) CreateWebAcl(name, desc string, wafType cloudprovider.TWafType, action *cloudprovider.DefaultAction) (*SWebAcl, error) {
  249. input := wafv2.CreateWebACLInput{}
  250. input.SetName(name)
  251. if len(desc) > 0 {
  252. input.SetDescription(desc)
  253. }
  254. switch wafType {
  255. case cloudprovider.WafTypeRegional, cloudprovider.WafTypeCloudFront:
  256. input.SetScope(strings.ToUpper(string(wafType)))
  257. default:
  258. return nil, errors.Errorf("invalid waf type %s", wafType)
  259. }
  260. if action != nil {
  261. defaultAction := wafv2.DefaultAction{}
  262. switch action.Action {
  263. case cloudprovider.WafActionAllow:
  264. defaultAction.Allow = &wafv2.AllowAction{}
  265. case cloudprovider.WafActionBlock:
  266. defaultAction.Block = &wafv2.BlockAction{}
  267. }
  268. input.SetDefaultAction(&defaultAction)
  269. }
  270. visib := &wafv2.VisibilityConfig{}
  271. visib.SetSampledRequestsEnabled(true)
  272. visib.SetCloudWatchMetricsEnabled(true)
  273. visib.SetMetricName(name)
  274. input.SetVisibilityConfig(visib)
  275. client, err := self.getWafClient()
  276. if err != nil {
  277. return nil, errors.Wrapf(err, "getWafClient")
  278. }
  279. output, err := client.CreateWebACL(&input)
  280. if err != nil {
  281. return nil, errors.Wrapf(err, "CreateWebAcl")
  282. }
  283. return self.GetWebAcl(*output.Summary.Id, name, *input.Scope)
  284. }
  285. func reverseConvertField(opts cloudprovider.SWafStatement) *wafv2.FieldToMatch {
  286. ret := &wafv2.FieldToMatch{}
  287. switch opts.MatchField {
  288. case cloudprovider.WafMatchFieldBody:
  289. body := &wafv2.Body{}
  290. ret.SetBody(body)
  291. case cloudprovider.WafMatchFieldJsonBody:
  292. case cloudprovider.WafMatchFieldMethod:
  293. method := &wafv2.Method{}
  294. ret.SetMethod(method)
  295. case cloudprovider.WafMatchFieldQuery:
  296. switch opts.MatchFieldKey {
  297. case "SingleArgument":
  298. query := &wafv2.SingleQueryArgument{}
  299. ret.SetSingleQueryArgument(query)
  300. case "AllArguments":
  301. query := &wafv2.AllQueryArguments{}
  302. ret.SetAllQueryArguments(query)
  303. default:
  304. query := &wafv2.QueryString{}
  305. ret.SetQueryString(query)
  306. }
  307. case cloudprovider.WafMatchFiledHeader:
  308. head := &wafv2.SingleHeader{}
  309. head.SetName(opts.MatchFieldKey)
  310. ret.SetSingleHeader(head)
  311. case cloudprovider.WafMatchFiledUriPath:
  312. uri := &wafv2.UriPath{}
  313. ret.SetUriPath(uri)
  314. }
  315. return ret
  316. }
  317. func reverseConvertStatement(statement cloudprovider.SWafStatement) *wafv2.Statement {
  318. ret := &wafv2.Statement{}
  319. trans := []*wafv2.TextTransformation{}
  320. if statement.Transformations != nil {
  321. for i, tran := range *statement.Transformations {
  322. t := &wafv2.TextTransformation{}
  323. switch tran {
  324. case cloudprovider.WafTextTransformationNone:
  325. t.SetType(wafv2.TextTransformationTypeNone)
  326. case cloudprovider.WafTextTransformationLowercase:
  327. t.SetType(wafv2.TextTransformationTypeLowercase)
  328. case cloudprovider.WafTextTransformationCmdLine:
  329. t.SetType(wafv2.TextTransformationTypeCmdLine)
  330. case cloudprovider.WafTextTransformationUrlDecode:
  331. t.SetType(wafv2.TextTransformationTypeUrlDecode)
  332. case cloudprovider.WafTextTransformationHtmlEntityDecode:
  333. t.SetType(wafv2.TextTransformationTypeHtmlEntityDecode)
  334. case cloudprovider.WafTextTransformationCompressWithSpace:
  335. t.SetType(wafv2.TextTransformationTypeCompressWhiteSpace)
  336. }
  337. t.SetPriority(int64(i))
  338. trans = append(trans, t)
  339. }
  340. }
  341. rules := []*wafv2.ExcludedRule{}
  342. if statement.ExcludeRules != nil {
  343. for _, r := range *statement.ExcludeRules {
  344. name := r.Name
  345. rules = append(rules, &wafv2.ExcludedRule{
  346. Name: &name,
  347. })
  348. }
  349. }
  350. field := reverseConvertField(statement)
  351. switch statement.Type {
  352. case cloudprovider.WafStatementTypeRate:
  353. rate := &wafv2.RateBasedStatement{}
  354. limit := int(0)
  355. if statement.MatchFieldValues != nil && len(*statement.MatchFieldValues) == 1 {
  356. limit, _ = strconv.Atoi((*statement.MatchFieldValues)[0])
  357. }
  358. rate.SetLimit(int64(limit))
  359. fd := &wafv2.ForwardedIPConfig{}
  360. if len(statement.ForwardedIPHeader) > 0 {
  361. fd.SetHeaderName(statement.ForwardedIPHeader)
  362. rate.SetForwardedIPConfig(fd)
  363. }
  364. ret.SetRateBasedStatement(rate)
  365. case cloudprovider.WafStatementTypeIPSet:
  366. ipset := &wafv2.IPSetReferenceStatement{}
  367. ipset.SetARN(statement.IPSetId)
  368. fd := &wafv2.IPSetForwardedIPConfig{}
  369. if len(statement.ForwardedIPHeader) > 0 {
  370. fd.SetHeaderName(statement.ForwardedIPHeader)
  371. ipset.SetIPSetForwardedIPConfig(fd)
  372. }
  373. ret.SetIPSetReferenceStatement(ipset)
  374. case cloudprovider.WafStatementTypeXssMatch:
  375. xss := &wafv2.XssMatchStatement{}
  376. if len(trans) > 0 {
  377. xss.SetTextTransformations(trans)
  378. }
  379. field := &wafv2.FieldToMatch{}
  380. xss.SetFieldToMatch(field)
  381. xss.SetTextTransformations(trans)
  382. ret.SetXssMatchStatement(xss)
  383. case cloudprovider.WafStatementTypeSize:
  384. size := &wafv2.SizeConstraintStatement{}
  385. size.SetFieldToMatch(field)
  386. value := int(0)
  387. if statement.MatchFieldValues != nil && len(*statement.MatchFieldValues) == 1 {
  388. value, _ = strconv.Atoi((*statement.MatchFieldValues)[0])
  389. }
  390. size.SetSize(int64(value))
  391. ret.SetSizeConstraintStatement(size)
  392. case cloudprovider.WafStatementTypeGeoMatch:
  393. geo := &wafv2.GeoMatchStatement{}
  394. values := []*string{}
  395. if statement.MatchFieldValues != nil {
  396. for i := range *statement.MatchFieldValues {
  397. v := (*statement.MatchFieldValues)[i]
  398. values = append(values, &v)
  399. }
  400. geo.SetCountryCodes(values)
  401. }
  402. fd := &wafv2.ForwardedIPConfig{}
  403. if len(statement.ForwardedIPHeader) > 0 {
  404. fd.SetHeaderName(statement.ForwardedIPHeader)
  405. geo.SetForwardedIPConfig(fd)
  406. }
  407. ret.SetGeoMatchStatement(geo)
  408. case cloudprovider.WafStatementTypeRegexSet:
  409. regex := &wafv2.RegexPatternSetReferenceStatement{}
  410. regex.SetARN(statement.RegexSetId)
  411. if len(trans) > 0 {
  412. regex.SetTextTransformations(trans)
  413. }
  414. regex.SetFieldToMatch(field)
  415. ret.SetRegexPatternSetReferenceStatement(regex)
  416. case cloudprovider.WafStatementTypeByteMatch:
  417. bm := &wafv2.ByteMatchStatement{}
  418. if len(trans) > 0 {
  419. bm.SetTextTransformations(trans)
  420. }
  421. bm.SetSearchString([]byte(statement.SearchString))
  422. if len(statement.Operator) > 0 {
  423. bm.SetPositionalConstraint(string(statement.Operator))
  424. }
  425. bm.SetFieldToMatch(field)
  426. ret.SetByteMatchStatement(bm)
  427. case cloudprovider.WafStatementTypeRuleGroup:
  428. rg := &wafv2.RuleGroupReferenceStatement{}
  429. rg.SetARN(statement.RuleGroupId)
  430. if len(rules) > 0 {
  431. rg.SetExcludedRules(rules)
  432. }
  433. ret.SetRuleGroupReferenceStatement(rg)
  434. case cloudprovider.WafStatementTypeSqliMatch:
  435. sqli := &wafv2.SqliMatchStatement{}
  436. if len(trans) > 0 {
  437. sqli.SetTextTransformations(trans)
  438. }
  439. sqli.SetFieldToMatch(field)
  440. ret.SetSqliMatchStatement(sqli)
  441. case cloudprovider.WafStatementTypeLabelMatch:
  442. case cloudprovider.WafStatementTypeManagedRuleGroup:
  443. rg := &wafv2.ManagedRuleGroupStatement{}
  444. rg.SetName(statement.ManagedRuleGroupName)
  445. rg.SetVendorName("aws")
  446. if len(rules) > 0 {
  447. rg.SetExcludedRules(rules)
  448. }
  449. ret.SetManagedRuleGroupStatement(rg)
  450. }
  451. return ret
  452. }
  453. func (self *SWebAcl) AddRule(opts *cloudprovider.SWafRule) (cloudprovider.ICloudWafRule, error) {
  454. input := &wafv2.UpdateWebACLInput{}
  455. input.SetLockToken(self.LockToken)
  456. input.SetId(*self.Id)
  457. input.SetName(*self.Name)
  458. input.SetScope(self.scope)
  459. input.SetDescription(*self.Description)
  460. input.SetDefaultAction(self.DefaultAction)
  461. input.SetVisibilityConfig(self.WebACL.VisibilityConfig)
  462. rules := self.Rules
  463. rule := &wafv2.Rule{}
  464. rule.SetName(opts.Name)
  465. rule.SetPriority(int64(opts.Priority))
  466. action := &wafv2.RuleAction{}
  467. if opts.Action != nil {
  468. switch opts.Action.Action {
  469. case cloudprovider.WafActionAllow:
  470. allow := &wafv2.AllowAction{}
  471. action.SetAllow(allow)
  472. case cloudprovider.WafActionBlock:
  473. block := &wafv2.BlockAction{}
  474. action.SetBlock(block)
  475. case cloudprovider.WafActionCount:
  476. count := &wafv2.CountAction{}
  477. action.SetCount(count)
  478. }
  479. }
  480. rule.SetAction(action)
  481. visib := &wafv2.VisibilityConfig{}
  482. visib.SetSampledRequestsEnabled(false)
  483. visib.SetCloudWatchMetricsEnabled(true)
  484. visib.SetMetricName(opts.Name)
  485. rule.SetVisibilityConfig(visib)
  486. statement := &wafv2.Statement{}
  487. switch opts.StatementCondition {
  488. case cloudprovider.WafStatementConditionOr:
  489. ss := &wafv2.OrStatement{}
  490. for _, s := range opts.Statements {
  491. ss.Statements = append(ss.Statements, reverseConvertStatement(s))
  492. }
  493. statement.SetOrStatement(ss)
  494. case cloudprovider.WafStatementConditionAnd:
  495. ss := &wafv2.AndStatement{}
  496. for _, s := range opts.Statements {
  497. ss.Statements = append(ss.Statements, reverseConvertStatement(s))
  498. }
  499. statement.SetAndStatement(ss)
  500. case cloudprovider.WafStatementConditionNot:
  501. ss := &wafv2.NotStatement{}
  502. for _, s := range opts.Statements {
  503. ss.SetStatement(reverseConvertStatement(s))
  504. break
  505. }
  506. statement.SetNotStatement(ss)
  507. case cloudprovider.WafStatementConditionNone:
  508. for _, s := range opts.Statements {
  509. statement = reverseConvertStatement(s)
  510. break
  511. }
  512. }
  513. rule.SetStatement(statement)
  514. rules = append(rules, rule)
  515. input.SetRules(rules)
  516. client, err := self.region.getWafClient()
  517. if err != nil {
  518. return nil, errors.Wrapf(err, "getWafClient")
  519. }
  520. _, err = client.UpdateWebACL(input)
  521. if err != nil {
  522. return nil, errors.Wrapf(err, "UpdateWebACL")
  523. }
  524. ret := &sWafRule{waf: self, Rule: rule}
  525. return ret, nil
  526. }
  527. func (self *SWebAcl) GetCloudResources() ([]cloudprovider.SCloudResource, error) {
  528. ret := []cloudprovider.SCloudResource{}
  529. if self.scope != SCOPE_REGIONAL {
  530. return ret, nil
  531. }
  532. for _, resType := range []string{"APPLICATION_LOAD_BALANCER", "API_GATEWAY", "APPSYNC"} {
  533. resIds, err := self.region.ListResourcesForWebACL(resType, *self.ARN)
  534. if err != nil {
  535. return nil, errors.Wrapf(err, "ListResourcesForWebACL(%s, %s)", resType, *self.ARN)
  536. }
  537. for _, resId := range resIds {
  538. ret = append(ret, cloudprovider.SCloudResource{
  539. Id: resId,
  540. Name: resId,
  541. Type: resType,
  542. })
  543. }
  544. }
  545. return ret, nil
  546. }
  547. func (self *SWebAcl) GetDescription() string {
  548. return self.AwsTags.GetDescription()
  549. }