diff --git a/go.mod b/go.mod index f81e7c7d90..9d9e4e96fc 100644 --- a/go.mod +++ b/go.mod @@ -24,6 +24,7 @@ require ( github.com/aquasecurity/trivy v0.71.0 github.com/aquasecurity/trivy-db v0.0.0-20260528104838-11b0c9f9e5e4 github.com/aws/aws-sdk-go-v2 v1.42.1 +<<<<<<< HEAD github.com/aws/aws-sdk-go-v2/config v1.32.27 github.com/aws/aws-sdk-go-v2/credentials v1.19.26 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.30 @@ -47,6 +48,31 @@ require ( github.com/aws/aws-sdk-go-v2/service/securityhub v1.71.9 github.com/aws/aws-sdk-go-v2/service/sns v1.40.3 github.com/aws/aws-sdk-go-v2/service/sts v1.43.5 +======= + github.com/aws/aws-sdk-go-v2/config v1.32.25 + github.com/aws/aws-sdk-go-v2/credentials v1.19.24 + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.29 + github.com/aws/aws-sdk-go-v2/service/accessanalyzer v1.49.5 + github.com/aws/aws-sdk-go-v2/service/autoscaling v1.67.4 + github.com/aws/aws-sdk-go-v2/service/cloudformation v1.72.1 + github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.56.4 + github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.59.0 + github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.78.0 + github.com/aws/aws-sdk-go-v2/service/configservice v1.64.1 + github.com/aws/aws-sdk-go-v2/service/ec2 v1.308.0 + github.com/aws/aws-sdk-go-v2/service/ecr v1.58.4 + github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.34.6 + github.com/aws/aws-sdk-go-v2/service/iam v1.54.5 + github.com/aws/aws-sdk-go-v2/service/kms v1.53.4 + github.com/aws/aws-sdk-go-v2/service/lambda v1.93.0 + github.com/aws/aws-sdk-go-v2/service/organizations v1.51.10 + github.com/aws/aws-sdk-go-v2/service/rds v1.119.3 + github.com/aws/aws-sdk-go-v2/service/s3 v1.104.0 + github.com/aws/aws-sdk-go-v2/service/s3control v1.71.5 + github.com/aws/aws-sdk-go-v2/service/securityhub v1.71.7 + github.com/aws/aws-sdk-go-v2/service/sns v1.40.1 + github.com/aws/aws-sdk-go-v2/service/sts v1.43.3 +>>>>>>> 74745e63 ([Cloud Asset Inventory] Add extra resource fields #2 (EKS & Route 53) (#7183)) github.com/aws/smithy-go v1.27.3 github.com/dgraph-io/ristretto/v2 v2.4.1 github.com/djherbis/times v1.6.0 @@ -115,6 +141,11 @@ require ( modernc.org/sqlite v1.53.0 ) +require ( + github.com/aws/aws-sdk-go-v2/service/eks v1.88.1 + github.com/aws/aws-sdk-go-v2/service/route53 v1.62.5 +) + require ( cel.dev/expr v0.25.2 // indirect cloud.google.com/go/auth v0.20.0 // indirect @@ -358,6 +389,7 @@ require ( github.com/aquasecurity/trivy-java-db v0.0.0-20260513130532-ec095357db14 // indirect github.com/armon/go-radix v1.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect +<<<<<<< HEAD github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.14 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.30 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.30 // indirect @@ -370,6 +402,20 @@ require ( github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.31 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.31.5 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.8 // indirect +======= + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.13 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.30 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.30 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.30 // indirect + github.com/aws/aws-sdk-go-v2/service/ebs v1.34.7 // indirect + github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.55.4 + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.12 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.22 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.29 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.29 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.31.3 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.6 // indirect +>>>>>>> 74745e63 ([Cloud Asset Inventory] Add extra resource fields #2 (EKS & Route 53) (#7183)) github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect diff --git a/go.sum b/go.sum index 69f72e9fa5..efc79968b1 100644 --- a/go.sum +++ b/go.sum @@ -215,6 +215,7 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go-v2 v1.42.1 h1:9eOTgu1z/dVtYpNZ3/8/XbbaX0x/BqE3HUzAzs6K0ek= github.com/aws/aws-sdk-go-v2 v1.42.1/go.mod h1:5pKeft2eJj+gElQ38Jqg4ibCqh+/AK33/0X3hip7IjM= +<<<<<<< HEAD github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.14 h1:3IZY0XAJquT3aHzbkHfPzy4ACPcEjVG0x87KOwtpqGY= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.14/go.mod h1:zwM6veDkhGgQFqkBy+uT28AAYpLu+uFMlPl+rCg/73E= github.com/aws/aws-sdk-go-v2/config v1.32.27 h1:SJwJ9Q4kM7v5QVSYYyXj3znRr6lNyZEhSgAXmXXcVbI= @@ -223,10 +224,21 @@ github.com/aws/aws-sdk-go-v2/credentials v1.19.26 h1:Si8kk1kyJnuJWCEgiwpBtTdtgSd github.com/aws/aws-sdk-go-v2/credentials v1.19.26/go.mod h1:lBckz+W9SAdNtSDw3pYgQUJDJFcBBWry0GSzw+bK0TY= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.30 h1:/hi1JADLEW9YYryEz1w4GQu0EtP23pP553Cf9KgsDV4= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.30/go.mod h1:/3AOgy4K17Dm4ucMZVC/MJkzy5kmfKUcINRHZyo0koQ= +======= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.13 h1:p1BBrg/Hhp6uK7zpejeI8QFXHJeC/mynzi04Sl03k9g= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.13/go.mod h1:8cIfkE9MDhkRZGpQ22aV6/lkYeYSozpz16Smrs5x4Ls= +github.com/aws/aws-sdk-go-v2/config v1.32.25 h1:ACCejvStYoilgwrfegSt5ZntCbPrk52qfwyNcnl3omM= +github.com/aws/aws-sdk-go-v2/config v1.32.25/go.mod h1:LJyU8sDRbXUxFn8xMJIGP+v9QYYwveNLI8a/giAOiAs= +github.com/aws/aws-sdk-go-v2/credentials v1.19.24 h1:2hQqYCV9yqyePQ9o6dCrZc/zO8U3TwPr9mIKlZnPu/I= +github.com/aws/aws-sdk-go-v2/credentials v1.19.24/go.mod h1:IDwpACtwqHLISdzfwUUNq4P9DsB/h5BLg4FwJPNfqFY= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.29 h1:r6qZHbT+wxgWO/e9vYNUEtg7lv5+UN3pRqKhLXvnArg= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.29/go.mod h1:QRnaRcTVGKPGRy8w78HMQtKUGRYcnMZAANATkeVA6Mo= +>>>>>>> 74745e63 ([Cloud Asset Inventory] Add extra resource fields #2 (EKS & Route 53) (#7183)) github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.30 h1:xM/Is9cKMHa8Jj8zkvWhvrFkZsXJV9E+BB4g0HW0duQ= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.30/go.mod h1:WueJeNDZvK1fMYEWJIkcivBfEzUkTpBhzlrUKKY8EuA= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.30 h1:jn46zC9LdsVR/ZpMIJqMqb8hHv31BlLx3ulVqNspUOk= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.30/go.mod h1:1hTMsAgbdS/AtUi4bw8+gUuh1pceo+eXRLfpSuSQj3M= +<<<<<<< HEAD github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.31 h1:3GUprIsfmGcC5SACIyB0e7E0BM1O1b3Erl5CePYIAeQ= github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.31/go.mod h1:7PuV1yl5e2xnUbm+RqvVg5i2iBM8EyijZNoI9wsOoOc= github.com/aws/aws-sdk-go-v2/service/accessanalyzer v1.49.7 h1:0K5Pj48ZMiXkdozajvr+gMIyqPv1oWdFYyCRoeUcm98= @@ -275,6 +287,58 @@ github.com/aws/aws-sdk-go-v2/service/organizations v1.51.12 h1:Jdm3qi2mCGoZTZVyN github.com/aws/aws-sdk-go-v2/service/organizations v1.51.12/go.mod h1:2ibX1FoyhvTXbIR4TP/Vf6BB6Tc3YW9jWbvNflSOcUM= github.com/aws/aws-sdk-go-v2/service/rds v1.119.5 h1:1/qGMaWmjbeOTaufLLqjLnrjhemH1eM74iVWVWWg+Zc= github.com/aws/aws-sdk-go-v2/service/rds v1.119.5/go.mod h1:Ve7qHa8jBmStKNz/oaxs2yBuFnwyvN0k/8PpPZVxkEY= +======= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.30 h1:VTGy885W5DKBxWRUJbym9hytNaYzsyaPkCHGRRMAOhU= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.30/go.mod h1:AS0HycUvJRFvTt613AYDOgO2jzw+00cVSMny8XB3yMY= +github.com/aws/aws-sdk-go-v2/service/accessanalyzer v1.49.5 h1:YRmgSZDK5hzQGr3WD/tUVFCwVsnELDbTui7yhEgzy7g= +github.com/aws/aws-sdk-go-v2/service/accessanalyzer v1.49.5/go.mod h1:4v0AkcAcWguJx3gOcZTvunFMbVeJ1e0jiUZPkaDPbhI= +github.com/aws/aws-sdk-go-v2/service/autoscaling v1.67.4 h1:5xDkTDvStSuTO4qdQfgfmIgQNTjS3kp7CPXHQ3CnqMQ= +github.com/aws/aws-sdk-go-v2/service/autoscaling v1.67.4/go.mod h1:rvzjcotoLfbMIVBpxSl0b6rCQsuty5V2aNL0LalDBr8= +github.com/aws/aws-sdk-go-v2/service/cloudformation v1.72.1 h1:vI/iCtlKp8LscUuwiWfTNID18w+naIGXKyWiY5odsZU= +github.com/aws/aws-sdk-go-v2/service/cloudformation v1.72.1/go.mod h1:67kQqAVkI9zNMo9kj1ca5RQvsDczK6xKCXrXn7ObZA8= +github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.56.4 h1:vbwtZFqiHIXnOrBDp3B6efVnQKHMI8SiUYCIc74YHI0= +github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.56.4/go.mod h1:LouyoQcaBYLDjRpqIKNJbWquIWVgnPLDEi/9o4Uf5+s= +github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.59.0 h1:JOrwHweL6IzRjbDxdjup2YI2QjWa8/h0PGexR8MZpKw= +github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.59.0/go.mod h1:tsfAcBcMTF2G9UirQTP1In3DrkNO16SyUU527NPLPhs= +github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.78.0 h1:6r+3E3bDRGiPm2x5t0eKy5jkAtWtgpwdCHi2dMaZy1c= +github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.78.0/go.mod h1:N336OxQ6TvRbb6V1esVE8PtQFU86YvYaS+lVjsJTmP0= +github.com/aws/aws-sdk-go-v2/service/configservice v1.64.1 h1:CJ6YcGJiXPJIz6ICOcGBumi2TcUSldUESHeF243WvVo= +github.com/aws/aws-sdk-go-v2/service/configservice v1.64.1/go.mod h1:oqVF/7XFqk3GY0/zlvY3Dj2+42ynOx4x/Sp875yKcxE= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.59.0 h1:S1qETDbdXKZMYVveuxACCKuRqnAt2NlnmYnlq5SeuMY= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.59.0/go.mod h1:jLkDwIDBkCIpiENQhAOjAR2L9jwj56mZgVEvuro4gUE= +github.com/aws/aws-sdk-go-v2/service/ebs v1.34.7 h1:8cSeObfW/xr+s2Hra2kArFisWz05pMrgIV/ZskUeYLw= +github.com/aws/aws-sdk-go-v2/service/ebs v1.34.7/go.mod h1:u+YbhSelj9tbG7rWdjRbRnUi6izHK5hjZnxxA9dtCvQ= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.308.0 h1:xBP+yWpveXD/PxK7HRMcoG6yj1vdOjSahAg4qPomF+0= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.308.0/go.mod h1:8mrDF7OtbuL0QpwP4YCvLuoOE4/5lL7D33MXgp069/Y= +github.com/aws/aws-sdk-go-v2/service/ecr v1.58.4 h1:fo6cmbxkKq/OtKUG0sK70fDsYjtKuSkjIQZUJwt24YM= +github.com/aws/aws-sdk-go-v2/service/ecr v1.58.4/go.mod h1:7VJFM2lSPHz2I1rRb0a+lbphoOp7hXIgYjGhSTOLY7k= +github.com/aws/aws-sdk-go-v2/service/eks v1.88.1 h1:Z05Ct9QXAZXKbC1wNLQln0/6FDzTcWPqI1YTWqs/pRY= +github.com/aws/aws-sdk-go-v2/service/eks v1.88.1/go.mod h1:MSAmCaKIo6Ph/yg73tj8/HZnILwyk4Px2tXfL4PO/HQ= +github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.34.6 h1:bucMsYP3fQIJvyNCzb4EAFZFYAwidWsFTGUKpJUTi3M= +github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.34.6/go.mod h1:uhWp16djmWOwENzHggk29rZ331UcOpfcLciIBdFCkm8= +github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.55.4 h1:M/98mES2pXpnSYtBSdBZx/zo3CaT/oSxTXsYk1vYd8A= +github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.55.4/go.mod h1:sUBnPF4iTc3KaCTIbLTr8xXjsnw8J0kXwr0nPCaAK3I= +github.com/aws/aws-sdk-go-v2/service/iam v1.54.5 h1:a/gAOhIOi+vHYeRU224WIXlJrLXs4Z1Qbm92vfX64jc= +github.com/aws/aws-sdk-go-v2/service/iam v1.54.5/go.mod h1:tMNzI+fYFCk4cIdZ7FEybLzShwnmWkfxQw85ED1b4ng= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.12 h1:ZD2+BSw9vFsNlKYIasSNt3uDbjqqXIBcM13UJv/Lx2k= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.12/go.mod h1:Ms4zlcVBbXbiP7EVLhl+lgjvA/a7YphqQ3Ih3174EmI= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.22 h1:V51LGlOq/1VsDsHUdoklAQi7rMmx4qQubvFYAlP2254= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.22/go.mod h1:4Pzhyz8hJOm2bepgl+NjvRx8vlUFAIIvJnZ/MkcNPpU= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.12.6 h1:Bs2OwYq0HBgHYwfGmUwYIPtTNaGMGAHkRje4jmW2VoI= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.12.6/go.mod h1:OTctu4cW8t7/TRlTKPLT6akzyOkfceMWhtEHqtYDIQQ= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.29 h1:DRebniUGZ2MqiiIVmQJ04vIXr918hubdHMnarSLEWyU= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.29/go.mod h1:LfRkPCD8YHDM2E5eTkos2UpwYeZnBcVarTa8L59bJHA= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.29 h1:hiME6pBzC7OTl9LMtlyTWBuEl1f4QBcUmFDKC7MLXtc= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.29/go.mod h1:G7RP+uhagpKtKhd1BM9N6JQqjCcGEU47K5lBVZQyRQw= +github.com/aws/aws-sdk-go-v2/service/kms v1.53.4 h1:PEgVSsWtR8NNxsDxFL2Ywisi7R+1EFQARGsT4q3mWwI= +github.com/aws/aws-sdk-go-v2/service/kms v1.53.4/go.mod h1:3EeKyDGPGSCEphG2OolwNGNF45RvQIfm27AYYpfEWrw= +github.com/aws/aws-sdk-go-v2/service/lambda v1.93.0 h1:uEB7hBZO61H63g+rtUbJ5fjkxLw369wukdr4hCtaZ+M= +github.com/aws/aws-sdk-go-v2/service/lambda v1.93.0/go.mod h1:3bF6WydfupDwCv8Q3g/Flt89341w/+NObn+KdQmLA60= +github.com/aws/aws-sdk-go-v2/service/organizations v1.51.10 h1:k/1HG7/z3Ujtcq5+JDSSjp5GG8PzoPuY08Objd2oryI= +github.com/aws/aws-sdk-go-v2/service/organizations v1.51.10/go.mod h1:PRk/TRj/93nQOVYKHCuBNCOBBLaH4lKhaKW5EB/ZR50= +github.com/aws/aws-sdk-go-v2/service/rds v1.119.3 h1:SIGdk+wA+xGXgN+L7Jr3Ot83Mjh3jpjyJIwZd3DqAnU= +github.com/aws/aws-sdk-go-v2/service/rds v1.119.3/go.mod h1:zCRPUdp05FEZG3OO7LmJq9xkSDjMEhkiVrZV0oJs2a0= +>>>>>>> 74745e63 ([Cloud Asset Inventory] Add extra resource fields #2 (EKS & Route 53) (#7183)) github.com/aws/aws-sdk-go-v2/service/route53 v1.62.5 h1:Z+/OLsb85Kpq7TVLCspskqePaf68Tdv6GfmJP4kH6i0= github.com/aws/aws-sdk-go-v2/service/route53 v1.62.5/go.mod h1:TmxGowuBYwjmHFOsEDxaZdsQE62JJzOmtiWafTi/czg= github.com/aws/aws-sdk-go-v2/service/s3 v1.104.2 h1:bAY6O/TDv1HQnvylh9E247IyIKsUWUt2G965S7qX110= diff --git a/internal/inventory/ASSETS.md b/internal/inventory/ASSETS.md index 2f372226ed..1db6ea5f86 100644 --- a/internal/inventory/ASSETS.md +++ b/internal/inventory/ASSETS.md @@ -1,6 +1,6 @@ ## AWS Resources -**Progress: 24% (21/85)** +**Progress: 27% (23/85)** Access Management: 50% (1/2) Database: 100% (1/1) FaaS: 100% (3/3) @@ -8,10 +8,11 @@ Firewall: 100% (1/1) Gateway: 100% (4/4) Host: 100% (1/1) Identity: 11% (1/9) -Infrastructure: 0% (0/55) +Infrastructure: 1% (1/54) Load Balancer: 100% (2/2) Messaging Service: 100% (1/1) Networking: 100% (4/4) +Orchestrator: 100% (1/1) Service Account: 100% (1/1) Storage Bucket: 100% (1/1) @@ -44,6 +45,7 @@ Storage Bucket: 100% (1/1) | Infrastructure | API Gateway REST API | | No ❌ | | Infrastructure | API Gateway Stage | | No ❌ | | Infrastructure | Auto Scaling Group | | No ❌ | +| Infrastructure | Route53 DNS Record | AWS Route53 DNS Record | Yes ✅ | | Infrastructure | Cloud Service Provider Account | | No ❌ | | Infrastructure | Config Configuration Recorder | | No ❌ | | Infrastructure | Config Delivery Channel | | No ❌ | @@ -65,7 +67,6 @@ Storage Bucket: 100% (1/1) | Infrastructure | ECS Service | | No ❌ | | Infrastructure | ECS Task | | No ❌ | | Infrastructure | ECS Task Definition | | No ❌ | -| Infrastructure | EKS Cluster | | No ❌ | | Infrastructure | ELBv2 Listener | | No ❌ | | Infrastructure | Elastic IP | | No ❌ | | Infrastructure | Elasticsearch Domain | | No ❌ | @@ -85,7 +86,6 @@ Storage Bucket: 100% (1/1) | Infrastructure | RDS Cluster | | No ❌ | | Infrastructure | RDS Snapshot | | No ❌ | | Infrastructure | Redshift Cluster | | No ❌ | -| Infrastructure | Route53 DNS Record | | No ❌ | | Infrastructure | Route53 DNS Zone | | No ❌ | | Infrastructure | Secrets Manager Secret | | No ❌ | | Infrastructure | Security Hub | | No ❌ | @@ -102,6 +102,7 @@ Storage Bucket: 100% (1/1) | Networking | EC2 Subnet | AWS EC2 Subnet | Yes ✅ | | Networking | VPC | AWS VPC | Yes ✅ | | Networking | VPC Peering Connection | AWS VPC Peering Connection | Yes ✅ | +| Orchestrator | EKS Cluster | AWS EKS Cluster | Yes ✅ | | Service Account | IAM Role | AWS IAM Role | Yes ✅ | | Storage Bucket | S3 Bucket | AWS S3 Bucket | Yes ✅ | diff --git a/internal/inventory/asset.go b/internal/inventory/asset.go index 7871a3825e..2759921e3b 100644 --- a/internal/inventory/asset.go +++ b/internal/inventory/asset.go @@ -76,6 +76,7 @@ type AssetClassification struct { var ( // AWS AssetClassificationAwsEc2Instance = AssetClassification{CategoryHost, "AWS EC2 Instance"} + AssetClassificationAwsEksCluster = AssetClassification{CategoryOrchestrator, "AWS EKS Cluster"} AssetClassificationAwsElbV1 = AssetClassification{CategoryLoadBalancer, "AWS Elastic Load Balancer"} AssetClassificationAwsElbV2 = AssetClassification{CategoryLoadBalancer, "AWS Elastic Load Balancer v2"} AssetClassificationAwsIamPolicy = AssetClassification{CategoryAccessManagement, "AWS IAM Policy"} @@ -95,6 +96,7 @@ var ( AssetClassificationAwsVpcPeeringConnection = AssetClassification{CategoryNetworking, "AWS VPC Peering Connection"} AssetClassificationAwsVpc = AssetClassification{CategoryNetworking, "AWS VPC"} AssetClassificationAwsRds = AssetClassification{CategoryDatabase, "AWS RDS Instance"} + AssetClassificationAwsRoute53Record = AssetClassification{CategoryInfrastructure, "AWS Route53 DNS Record"} AssetClassificationAwsS3Bucket = AssetClassification{CategoryStorageBucket, "AWS S3 Bucket"} AssetClassificationAwsSnsTopic = AssetClassification{CategoryMessagingService, "AWS SNS Topic"} diff --git a/internal/inventory/awsfetcher/awsfetchers.go b/internal/inventory/awsfetcher/awsfetchers.go index 5f0a6d7f21..0d6f004f92 100644 --- a/internal/inventory/awsfetcher/awsfetchers.go +++ b/internal/inventory/awsfetcher/awsfetchers.go @@ -27,11 +27,13 @@ import ( "github.com/elastic/cloudbeat/internal/inventory" "github.com/elastic/cloudbeat/internal/resources/providers/awslib" "github.com/elastic/cloudbeat/internal/resources/providers/awslib/ec2" + "github.com/elastic/cloudbeat/internal/resources/providers/awslib/eks" "github.com/elastic/cloudbeat/internal/resources/providers/awslib/elb" elbv2 "github.com/elastic/cloudbeat/internal/resources/providers/awslib/elb_v2" "github.com/elastic/cloudbeat/internal/resources/providers/awslib/iam" "github.com/elastic/cloudbeat/internal/resources/providers/awslib/lambda" "github.com/elastic/cloudbeat/internal/resources/providers/awslib/rds" + "github.com/elastic/cloudbeat/internal/resources/providers/awslib/route53" "github.com/elastic/cloudbeat/internal/resources/providers/awslib/s3" "github.com/elastic/cloudbeat/internal/resources/providers/awslib/sns" "github.com/elastic/cloudbeat/internal/statushandler" @@ -39,16 +41,23 @@ import ( func New(ctx context.Context, logger *clog.Logger, identity *cloud.Identity, cfg aws.Config, statusHandler statushandler.StatusHandlerAPI) []inventory.AssetFetcher { ec2Provider := ec2.NewEC2Provider(ctx, logger, identity.Account, cfg, &awslib.MultiRegionClientFactory[ec2.Client]{}) + eksProvider := eks.NewProvider(ctx, logger, cfg, &awslib.MultiRegionClientFactory[eks.Client]{}) elbProvider := elb.NewElbProvider(ctx, logger, identity.Account, cfg, &awslib.MultiRegionClientFactory[elb.Client]{}) elbv2Provider := elbv2.NewElbV2Provider(ctx, logger, cfg, &awslib.MultiRegionClientFactory[elbv2.Client]{}) iamProvider := iam.NewIAMProvider(ctx, logger, cfg, &awslib.MultiRegionClientFactory[iam.AccessAnalyzerClient]{}) lambdaProvider := lambda.NewLambdaProvider(ctx, logger, cfg, &awslib.MultiRegionClientFactory[lambda.Client]{}) rdsProvider := rds.NewProvider(ctx, logger, cfg, &awslib.MultiRegionClientFactory[rds.Client]{}, ec2Provider) + route53Provider := route53.NewProvider(logger, cfg) s3Provider := s3.NewProvider(ctx, logger, cfg, &awslib.MultiRegionClientFactory[s3.Client]{}, identity.Account) snsProvider := sns.NewSNSProvider(ctx, logger, cfg, &awslib.MultiRegionClientFactory[sns.Client]{}) return []inventory.AssetFetcher{ +<<<<<<< HEAD newEc2InstancesFetcher(logger, identity, ec2Provider, statusHandler), +======= + newEc2InstancesFetcher(logger, identity, ec2Provider, iamProvider, statusHandler), + newEKSFetcher(logger, identity, eksProvider, statusHandler), +>>>>>>> 74745e63 ([Cloud Asset Inventory] Add extra resource fields #2 (EKS & Route 53) (#7183)) newElbFetcher(logger, identity, elbProvider, elbv2Provider, statusHandler), newIamPolicyFetcher(logger, identity, iamProvider, statusHandler), newIamRoleFetcher(logger, identity, iamProvider, statusHandler), @@ -56,6 +65,7 @@ func New(ctx context.Context, logger *clog.Logger, identity *cloud.Identity, cfg newLambdaFetcher(logger, identity, lambdaProvider, statusHandler), newNetworkingFetcher(logger, identity, ec2Provider, statusHandler), newRDSFetcher(logger, identity, rdsProvider, statusHandler), + newRoute53Fetcher(logger, identity, route53Provider, statusHandler), newS3BucketFetcher(logger, identity, s3Provider, statusHandler), newSNSFetcher(logger, identity, snsProvider, statusHandler), } diff --git a/internal/inventory/awsfetcher/fetcher_eks.go b/internal/inventory/awsfetcher/fetcher_eks.go new file mode 100644 index 0000000000..4b13fc070f --- /dev/null +++ b/internal/inventory/awsfetcher/fetcher_eks.go @@ -0,0 +1,114 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package awsfetcher + +import ( + "context" + + "github.com/elastic/cloudbeat/internal/dataprovider/providers/cloud" + "github.com/elastic/cloudbeat/internal/infra/clog" + "github.com/elastic/cloudbeat/internal/inventory" + "github.com/elastic/cloudbeat/internal/resources/providers/awslib" + "github.com/elastic/cloudbeat/internal/resources/providers/awslib/eks" + "github.com/elastic/cloudbeat/internal/statushandler" +) + +type eksFetcher struct { + logger *clog.Logger + provider eksProvider + accountId string + accountName string + statusHandler statushandler.StatusHandlerAPI +} + +type eksProvider interface { + DescribeClusters(ctx context.Context) ([]awslib.AwsResource, error) +} + +func newEKSFetcher(logger *clog.Logger, identity *cloud.Identity, provider eksProvider, statusHandler statushandler.StatusHandlerAPI) inventory.AssetFetcher { + return &eksFetcher{ + logger: logger, + provider: provider, + accountId: identity.Account, + accountName: identity.AccountAlias, + statusHandler: statusHandler, + } +} + +func (f *eksFetcher) Fetch(ctx context.Context, assetChannel chan<- inventory.AssetEvent) { + f.logger.Info("Fetching EKS Clusters") + defer f.logger.Info("Fetching EKS Clusters - Finished") + + resources, err := f.provider.DescribeClusters(ctx) + if err != nil { + f.logger.Errorf("Could not list EKS clusters: %v", err) + awslib.ReportMissingPermission(f.statusHandler, err) + } + + for _, item := range resources { + cluster, ok := item.(eks.Cluster) + if !ok { + continue + } + + assetChannel <- inventory.NewAssetEvent( + inventory.AssetClassificationAwsEksCluster, + item.GetResourceArn(), + item.GetResourceName(), + inventory.WithRawAsset(cluster), + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AwsCloudProvider, + Region: item.GetRegion(), + AccountID: f.accountId, + AccountName: f.accountName, + ServiceName: "AWS EKS", + }), + inventory.WithEntityAttributes(buildEKSAttributes(cluster)), + inventory.WithCreatedAt(cluster.CreatedAt), + ) + } +} + +// buildEKSAttributes maps a cluster's non-ECS fields into entity.attributes using +// UpperCamelCase keys. The endpoint-access booleans are always included as they are +// meaningful even when false; other empty values are omitted. +func buildEKSAttributes(cluster eks.Cluster) map[string]any { + attrs := map[string]any{ + "EndpointPublicAccess": cluster.EndpointPublicAccess, + "EndpointPrivateAccess": cluster.EndpointPrivateAccess, + } + if cluster.Status != "" { + attrs["Status"] = cluster.Status + } + if cluster.Version != "" { + attrs["Version"] = cluster.Version + } + if cluster.Endpoint != "" { + attrs["Endpoint"] = cluster.Endpoint + } + if cluster.RoleArn != "" { + attrs["RoleArn"] = cluster.RoleArn + } + if cluster.PlatformVersion != "" { + attrs["PlatformVersion"] = cluster.PlatformVersion + } + if v := cluster.GetOwnerTag(); v != "" { + attrs["OwnerTag"] = v + } + return attrs +} diff --git a/internal/inventory/awsfetcher/fetcher_eks_mock.go b/internal/inventory/awsfetcher/fetcher_eks_mock.go new file mode 100644 index 0000000000..e9c04cf1d2 --- /dev/null +++ b/internal/inventory/awsfetcher/fetcher_eks_mock.go @@ -0,0 +1,119 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +//go:build !release + +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify + +package awsfetcher + +import ( + "context" + + "github.com/elastic/cloudbeat/internal/resources/providers/awslib" + mock "github.com/stretchr/testify/mock" +) + +// newMockEksProvider creates a new instance of mockEksProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func newMockEksProvider(t interface { + mock.TestingT + Cleanup(func()) +}) *mockEksProvider { + mock := &mockEksProvider{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + +// mockEksProvider is an autogenerated mock type for the eksProvider type +type mockEksProvider struct { + mock.Mock +} + +type mockEksProvider_Expecter struct { + mock *mock.Mock +} + +func (_m *mockEksProvider) EXPECT() *mockEksProvider_Expecter { + return &mockEksProvider_Expecter{mock: &_m.Mock} +} + +// DescribeClusters provides a mock function for the type mockEksProvider +func (_mock *mockEksProvider) DescribeClusters(ctx context.Context) ([]awslib.AwsResource, error) { + ret := _mock.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for DescribeClusters") + } + + var r0 []awslib.AwsResource + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context) ([]awslib.AwsResource, error)); ok { + return returnFunc(ctx) + } + if returnFunc, ok := ret.Get(0).(func(context.Context) []awslib.AwsResource); ok { + r0 = returnFunc(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]awslib.AwsResource) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = returnFunc(ctx) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// mockEksProvider_DescribeClusters_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DescribeClusters' +type mockEksProvider_DescribeClusters_Call struct { + *mock.Call +} + +// DescribeClusters is a helper method to define mock.On call +// - ctx context.Context +func (_e *mockEksProvider_Expecter) DescribeClusters(ctx interface{}) *mockEksProvider_DescribeClusters_Call { + return &mockEksProvider_DescribeClusters_Call{Call: _e.mock.On("DescribeClusters", ctx)} +} + +func (_c *mockEksProvider_DescribeClusters_Call) Run(run func(ctx context.Context)) *mockEksProvider_DescribeClusters_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *mockEksProvider_DescribeClusters_Call) Return(awsResources []awslib.AwsResource, err error) *mockEksProvider_DescribeClusters_Call { + _c.Call.Return(awsResources, err) + return _c +} + +func (_c *mockEksProvider_DescribeClusters_Call) RunAndReturn(run func(ctx context.Context) ([]awslib.AwsResource, error)) *mockEksProvider_DescribeClusters_Call { + _c.Call.Return(run) + return _c +} diff --git a/internal/inventory/awsfetcher/fetcher_eks_test.go b/internal/inventory/awsfetcher/fetcher_eks_test.go new file mode 100644 index 0000000000..bae2a1fa6c --- /dev/null +++ b/internal/inventory/awsfetcher/fetcher_eks_test.go @@ -0,0 +1,112 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package awsfetcher + +import ( + "testing" + "time" + + "github.com/stretchr/testify/mock" + + "github.com/elastic/cloudbeat/internal/dataprovider/providers/cloud" + "github.com/elastic/cloudbeat/internal/inventory" + "github.com/elastic/cloudbeat/internal/inventory/testutil" + "github.com/elastic/cloudbeat/internal/resources/providers/awslib" + "github.com/elastic/cloudbeat/internal/resources/providers/awslib/eks" + "github.com/elastic/cloudbeat/internal/resources/utils/testhelper" + "github.com/elastic/cloudbeat/internal/statushandler" +) + +func TestEKSFetcher_Fetch(t *testing.T) { + createdAt := time.Date(2024, 5, 1, 12, 0, 0, 0, time.UTC) + + cluster1 := eks.Cluster{ + Name: "prod-cluster", + Arn: "arn:aws:eks:us-east-1:123:cluster/prod-cluster", + Status: "ACTIVE", + Version: "1.29", + Endpoint: "https://abc.eks.amazonaws.com", + RoleArn: "arn:aws:iam::123:role/eks-role", + PlatformVersion: "eks.5", + EndpointPublicAccess: true, + EndpointPrivateAccess: false, + Tags: map[string]string{"Owner": "team-infra"}, + CreatedAt: &createdAt, + } + + cluster2 := eks.Cluster{ + Name: "minimal-cluster", + Arn: "arn:aws:eks:us-east-1:123:cluster/minimal-cluster", + } + + in := []awslib.AwsResource{cluster1, cluster2} + + expected := []inventory.AssetEvent{ + inventory.NewAssetEvent( + inventory.AssetClassificationAwsEksCluster, + "arn:aws:eks:us-east-1:123:cluster/prod-cluster", + "prod-cluster", + inventory.WithRawAsset(cluster1), + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AwsCloudProvider, + AccountID: "123", + AccountName: "alias", + ServiceName: "AWS EKS", + }), + inventory.WithEntityAttributes(map[string]any{ + "EndpointPublicAccess": true, + "EndpointPrivateAccess": false, + "Status": "ACTIVE", + "Version": "1.29", + "Endpoint": "https://abc.eks.amazonaws.com", + "RoleArn": "arn:aws:iam::123:role/eks-role", + "PlatformVersion": "eks.5", + "OwnerTag": "team-infra", + }), + inventory.WithCreatedAt(&createdAt), + ), + inventory.NewAssetEvent( + inventory.AssetClassificationAwsEksCluster, + "arn:aws:eks:us-east-1:123:cluster/minimal-cluster", + "minimal-cluster", + inventory.WithRawAsset(cluster2), + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AwsCloudProvider, + AccountID: "123", + AccountName: "alias", + ServiceName: "AWS EKS", + }), + inventory.WithEntityAttributes(map[string]any{ + "EndpointPublicAccess": false, + "EndpointPrivateAccess": false, + }), + inventory.WithCreatedAt(nil), + ), + } + + logger := testhelper.NewLogger(t) + provider := newMockEksProvider(t) + provider.EXPECT().DescribeClusters(mock.Anything).Return(in, nil) + + msh := statushandler.NewMockStatusHandlerAPI(t) + + identity := &cloud.Identity{Account: "123", AccountAlias: "alias"} + fetcher := newEKSFetcher(logger, identity, provider, msh) + + testutil.CollectResourcesAndMatch(t, fetcher, expected) +} diff --git a/internal/inventory/awsfetcher/fetcher_route53.go b/internal/inventory/awsfetcher/fetcher_route53.go new file mode 100644 index 0000000000..8e8e6ac26e --- /dev/null +++ b/internal/inventory/awsfetcher/fetcher_route53.go @@ -0,0 +1,118 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package awsfetcher + +import ( + "context" + + "github.com/elastic/cloudbeat/internal/dataprovider/providers/cloud" + "github.com/elastic/cloudbeat/internal/infra/clog" + "github.com/elastic/cloudbeat/internal/inventory" + "github.com/elastic/cloudbeat/internal/resources/providers/awslib" + "github.com/elastic/cloudbeat/internal/resources/providers/awslib/route53" + "github.com/elastic/cloudbeat/internal/statushandler" +) + +type route53Fetcher struct { + logger *clog.Logger + provider route53Provider + accountId string + accountName string + statusHandler statushandler.StatusHandlerAPI +} + +type route53Provider interface { + ListRecords(ctx context.Context) ([]awslib.AwsResource, error) +} + +func newRoute53Fetcher(logger *clog.Logger, identity *cloud.Identity, provider route53Provider, statusHandler statushandler.StatusHandlerAPI) inventory.AssetFetcher { + return &route53Fetcher{ + logger: logger, + provider: provider, + accountId: identity.Account, + accountName: identity.AccountAlias, + statusHandler: statusHandler, + } +} + +func (f *route53Fetcher) Fetch(ctx context.Context, assetChannel chan<- inventory.AssetEvent) { + f.logger.Info("Fetching Route53 records") + defer f.logger.Info("Fetching Route53 records - Finished") + + resources, err := f.provider.ListRecords(ctx) + if err != nil { + f.logger.Errorf("Could not list Route53 records: %v", err) + awslib.ReportMissingPermission(f.statusHandler, err) + } + + for _, item := range resources { + record, ok := item.(route53.Record) + if !ok { + continue + } + + assetChannel <- inventory.NewAssetEvent( + inventory.AssetClassificationAwsRoute53Record, + item.GetResourceArn(), + item.GetResourceName(), + inventory.WithRawAsset(record), + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AwsCloudProvider, + Region: item.GetRegion(), + AccountID: f.accountId, + AccountName: f.accountName, + ServiceName: "AWS Route 53", + }), + inventory.WithEntityAttributes(buildRoute53Attributes(record)), + ) + } +} + +// buildRoute53Attributes maps a record's non-ECS fields into entity.attributes using +// UpperCamelCase keys. Empty values are omitted. +func buildRoute53Attributes(record route53.Record) map[string]any { + attrs := map[string]any{} + if record.Type != "" { + attrs["Type"] = record.Type + } + if len(record.Records) > 0 { + attrs["ResourceRecords"] = record.Records + } + if record.Weight != nil { + attrs["Weight"] = *record.Weight + } + if record.RoutingRegion != "" { + attrs["Region"] = record.RoutingRegion + } + if record.ZoneID != "" { + attrs["ZoneID"] = record.ZoneID + } + if record.ZoneName != "" { + attrs["ZoneName"] = record.ZoneName + } + if record.AliasTargetDNS != "" { + attrs["AliasTargetDNS"] = record.AliasTargetDNS + } + if record.AliasTargetZoneID != "" { + attrs["AliasTargetZoneId"] = record.AliasTargetZoneID + } + if record.HealthCheckID != "" { + attrs["HealthCheckId"] = record.HealthCheckID + } + return attrs +} diff --git a/internal/inventory/awsfetcher/fetcher_route53_mock.go b/internal/inventory/awsfetcher/fetcher_route53_mock.go new file mode 100644 index 0000000000..a5aedc665c --- /dev/null +++ b/internal/inventory/awsfetcher/fetcher_route53_mock.go @@ -0,0 +1,119 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +//go:build !release + +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify + +package awsfetcher + +import ( + "context" + + "github.com/elastic/cloudbeat/internal/resources/providers/awslib" + mock "github.com/stretchr/testify/mock" +) + +// newMockRoute53Provider creates a new instance of mockRoute53Provider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func newMockRoute53Provider(t interface { + mock.TestingT + Cleanup(func()) +}) *mockRoute53Provider { + mock := &mockRoute53Provider{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + +// mockRoute53Provider is an autogenerated mock type for the route53Provider type +type mockRoute53Provider struct { + mock.Mock +} + +type mockRoute53Provider_Expecter struct { + mock *mock.Mock +} + +func (_m *mockRoute53Provider) EXPECT() *mockRoute53Provider_Expecter { + return &mockRoute53Provider_Expecter{mock: &_m.Mock} +} + +// ListRecords provides a mock function for the type mockRoute53Provider +func (_mock *mockRoute53Provider) ListRecords(ctx context.Context) ([]awslib.AwsResource, error) { + ret := _mock.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for ListRecords") + } + + var r0 []awslib.AwsResource + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context) ([]awslib.AwsResource, error)); ok { + return returnFunc(ctx) + } + if returnFunc, ok := ret.Get(0).(func(context.Context) []awslib.AwsResource); ok { + r0 = returnFunc(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]awslib.AwsResource) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = returnFunc(ctx) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// mockRoute53Provider_ListRecords_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListRecords' +type mockRoute53Provider_ListRecords_Call struct { + *mock.Call +} + +// ListRecords is a helper method to define mock.On call +// - ctx context.Context +func (_e *mockRoute53Provider_Expecter) ListRecords(ctx interface{}) *mockRoute53Provider_ListRecords_Call { + return &mockRoute53Provider_ListRecords_Call{Call: _e.mock.On("ListRecords", ctx)} +} + +func (_c *mockRoute53Provider_ListRecords_Call) Run(run func(ctx context.Context)) *mockRoute53Provider_ListRecords_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *mockRoute53Provider_ListRecords_Call) Return(awsResources []awslib.AwsResource, err error) *mockRoute53Provider_ListRecords_Call { + _c.Call.Return(awsResources, err) + return _c +} + +func (_c *mockRoute53Provider_ListRecords_Call) RunAndReturn(run func(ctx context.Context) ([]awslib.AwsResource, error)) *mockRoute53Provider_ListRecords_Call { + _c.Call.Return(run) + return _c +} diff --git a/internal/inventory/awsfetcher/fetcher_route53_test.go b/internal/inventory/awsfetcher/fetcher_route53_test.go new file mode 100644 index 0000000000..ab531d5daa --- /dev/null +++ b/internal/inventory/awsfetcher/fetcher_route53_test.go @@ -0,0 +1,113 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package awsfetcher + +import ( + "testing" + + "github.com/stretchr/testify/mock" + + "github.com/elastic/cloudbeat/internal/dataprovider/providers/cloud" + "github.com/elastic/cloudbeat/internal/inventory" + "github.com/elastic/cloudbeat/internal/inventory/testutil" + "github.com/elastic/cloudbeat/internal/resources/providers/awslib" + "github.com/elastic/cloudbeat/internal/resources/providers/awslib/route53" + "github.com/elastic/cloudbeat/internal/resources/utils/pointers" + "github.com/elastic/cloudbeat/internal/resources/utils/testhelper" + "github.com/elastic/cloudbeat/internal/statushandler" +) + +func TestRoute53Fetcher_Fetch(t *testing.T) { + record1 := route53.Record{ + Name: "www.example.com.", + Type: "A", + Records: []string{"203.0.113.10"}, + Weight: pointers.Ref(int64(10)), + RoutingRegion: "us-east-1", + ZoneID: "Z123", + ZoneName: "example.com.", + AliasTargetDNS: "alias.example.com.", + AliasTargetZoneID: "Z456", + HealthCheckID: "hc-1", + } + + record2 := route53.Record{ + Name: "example.com.", + Type: "NS", + ZoneID: "Z123", + ZoneName: "example.com.", + } + + in := []awslib.AwsResource{record1, record2} + + expected := []inventory.AssetEvent{ + inventory.NewAssetEvent( + inventory.AssetClassificationAwsRoute53Record, + "arn:aws:route53:::hostedzone/Z123/recordset/www.example.com./A", + "www.example.com.", + inventory.WithRawAsset(record1), + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AwsCloudProvider, + Region: "global", + AccountID: "123", + AccountName: "alias", + ServiceName: "AWS Route 53", + }), + inventory.WithEntityAttributes(map[string]any{ + "Type": "A", + "ResourceRecords": []string{"203.0.113.10"}, + "Weight": int64(10), + "Region": "us-east-1", + "ZoneID": "Z123", + "ZoneName": "example.com.", + "AliasTargetDNS": "alias.example.com.", + "AliasTargetZoneId": "Z456", + "HealthCheckId": "hc-1", + }), + ), + inventory.NewAssetEvent( + inventory.AssetClassificationAwsRoute53Record, + "arn:aws:route53:::hostedzone/Z123/recordset/example.com./NS", + "example.com.", + inventory.WithRawAsset(record2), + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AwsCloudProvider, + Region: "global", + AccountID: "123", + AccountName: "alias", + ServiceName: "AWS Route 53", + }), + inventory.WithEntityAttributes(map[string]any{ + "Type": "NS", + "ZoneID": "Z123", + "ZoneName": "example.com.", + }), + ), + } + + logger := testhelper.NewLogger(t) + provider := newMockRoute53Provider(t) + provider.EXPECT().ListRecords(mock.Anything).Return(in, nil) + + msh := statushandler.NewMockStatusHandlerAPI(t) + + identity := &cloud.Identity{Account: "123", AccountAlias: "alias"} + fetcher := newRoute53Fetcher(logger, identity, provider, msh) + + testutil.CollectResourcesAndMatch(t, fetcher, expected) +} diff --git a/internal/inventory/cloud_assets.xlsx b/internal/inventory/cloud_assets.xlsx index b1cfabfa59..c50877b6b5 100644 Binary files a/internal/inventory/cloud_assets.xlsx and b/internal/inventory/cloud_assets.xlsx differ diff --git a/internal/resources/fetching/fetcher.go b/internal/resources/fetching/fetcher.go index 9baac71de0..c09a3990b1 100644 --- a/internal/resources/fetching/fetcher.go +++ b/internal/resources/fetching/fetcher.go @@ -38,6 +38,7 @@ const ( EBSType = "aws-ebs" EC2NetworkingType = "aws-ec2-network" EC2Type = "aws-ec2" + EKSType = "aws-eks" EcrType = "aws-ecr" ElbType = "aws-elb" IAMServerCertificateType = "aws-iam-server-certificate" @@ -56,6 +57,7 @@ const ( PolicyType = "aws-policy" PwdPolicyType = "aws-password-policy" RdsType = "aws-rds" + Route53Type = "aws-route53" S3Type = "aws-s3" SNSTopicType = "aws-sns" SecurityGroupType = "aws-security-group" diff --git a/internal/resources/providers/awslib/eks/cluster.go b/internal/resources/providers/awslib/eks/cluster.go new file mode 100644 index 0000000000..1a481877c4 --- /dev/null +++ b/internal/resources/providers/awslib/eks/cluster.go @@ -0,0 +1,86 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package eks + +import ( + "time" + + "github.com/aws/aws-sdk-go-v2/service/eks/types" + + "github.com/elastic/cloudbeat/internal/resources/fetching" + "github.com/elastic/cloudbeat/internal/resources/providers/awslib" + "github.com/elastic/cloudbeat/internal/resources/utils/pointers" +) + +// Cluster is a flattened, normalized view of an EKS cluster. One Cluster maps to one asset. +type Cluster struct { + Name string `json:"name"` + Arn string `json:"arn"` + Status string `json:"status,omitempty"` + Version string `json:"version,omitempty"` + Endpoint string `json:"endpoint,omitempty"` + RoleArn string `json:"role_arn,omitempty"` + PlatformVersion string `json:"platform_version,omitempty"` + EndpointPublicAccess bool `json:"endpoint_public_access"` + EndpointPrivateAccess bool `json:"endpoint_private_access"` + Tags map[string]string `json:"tags,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + + region string +} + +func newCluster(c types.Cluster, region string) Cluster { + cluster := Cluster{ + Name: pointers.Deref(c.Name), + Arn: pointers.Deref(c.Arn), + Status: string(c.Status), + Version: pointers.Deref(c.Version), + Endpoint: pointers.Deref(c.Endpoint), + RoleArn: pointers.Deref(c.RoleArn), + PlatformVersion: pointers.Deref(c.PlatformVersion), + Tags: c.Tags, + CreatedAt: c.CreatedAt, + region: region, + } + if c.ResourcesVpcConfig != nil { + cluster.EndpointPublicAccess = c.ResourcesVpcConfig.EndpointPublicAccess + cluster.EndpointPrivateAccess = c.ResourcesVpcConfig.EndpointPrivateAccess + } + return cluster +} + +func (c Cluster) GetResourceArn() string { + return c.Arn +} + +func (c Cluster) GetResourceName() string { + return c.Name +} + +func (c Cluster) GetResourceType() string { + return fetching.EKSType +} + +func (c Cluster) GetRegion() string { + return c.region +} + +// GetOwnerTag returns the value of the "Owner" tag (case-insensitive), if present. +func (c Cluster) GetOwnerTag() string { + return awslib.LookupTag(c.Tags, "owner") +} diff --git a/internal/resources/providers/awslib/eks/eks.go b/internal/resources/providers/awslib/eks/eks.go new file mode 100644 index 0000000000..431aa99ae6 --- /dev/null +++ b/internal/resources/providers/awslib/eks/eks.go @@ -0,0 +1,54 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package eks + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/eks" + + "github.com/elastic/cloudbeat/internal/infra/clog" + "github.com/elastic/cloudbeat/internal/resources/providers/awslib" +) + +// Client abstracts the subset of the EKS SDK client used by the provider. +type Client interface { + ListClusters(ctx context.Context, params *eks.ListClustersInput, optFns ...func(*eks.Options)) (*eks.ListClustersOutput, error) + DescribeCluster(ctx context.Context, params *eks.DescribeClusterInput, optFns ...func(*eks.Options)) (*eks.DescribeClusterOutput, error) +} + +type ClustersDescriber interface { + DescribeClusters(ctx context.Context) ([]awslib.AwsResource, error) +} + +type Provider struct { + log *clog.Logger + clients map[string]Client +} + +func NewProvider(ctx context.Context, log *clog.Logger, cfg aws.Config, factory awslib.CrossRegionFactory[Client]) *Provider { + f := func(cfg aws.Config) Client { + return eks.NewFromConfig(cfg) + } + m := factory.NewMultiRegionClients(ctx, awslib.AllRegionSelector(), cfg, f, log) + return &Provider{ + log: log, + clients: m.GetMultiRegionsClientMap(), + } +} diff --git a/internal/resources/providers/awslib/eks/eks_mock.go b/internal/resources/providers/awslib/eks/eks_mock.go new file mode 100644 index 0000000000..9768dd2d07 --- /dev/null +++ b/internal/resources/providers/awslib/eks/eks_mock.go @@ -0,0 +1,313 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +//go:build !release + +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify + +package eks + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/service/eks" + "github.com/elastic/cloudbeat/internal/resources/providers/awslib" + mock "github.com/stretchr/testify/mock" +) + +// NewMockClient creates a new instance of MockClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockClient(t interface { + mock.TestingT + Cleanup(func()) +}) *MockClient { + mock := &MockClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + +// MockClient is an autogenerated mock type for the Client type +type MockClient struct { + mock.Mock +} + +type MockClient_Expecter struct { + mock *mock.Mock +} + +func (_m *MockClient) EXPECT() *MockClient_Expecter { + return &MockClient_Expecter{mock: &_m.Mock} +} + +// DescribeCluster provides a mock function for the type MockClient +func (_mock *MockClient) DescribeCluster(ctx context.Context, params *eks.DescribeClusterInput, optFns ...func(*eks.Options)) (*eks.DescribeClusterOutput, error) { + var tmpRet mock.Arguments + if len(optFns) > 0 { + tmpRet = _mock.Called(ctx, params, optFns) + } else { + tmpRet = _mock.Called(ctx, params) + } + ret := tmpRet + + if len(ret) == 0 { + panic("no return value specified for DescribeCluster") + } + + var r0 *eks.DescribeClusterOutput + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, *eks.DescribeClusterInput, ...func(*eks.Options)) (*eks.DescribeClusterOutput, error)); ok { + return returnFunc(ctx, params, optFns...) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, *eks.DescribeClusterInput, ...func(*eks.Options)) *eks.DescribeClusterOutput); ok { + r0 = returnFunc(ctx, params, optFns...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*eks.DescribeClusterOutput) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, *eks.DescribeClusterInput, ...func(*eks.Options)) error); ok { + r1 = returnFunc(ctx, params, optFns...) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_DescribeCluster_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DescribeCluster' +type MockClient_DescribeCluster_Call struct { + *mock.Call +} + +// DescribeCluster is a helper method to define mock.On call +// - ctx context.Context +// - params *eks.DescribeClusterInput +// - optFns ...func(*eks.Options) +func (_e *MockClient_Expecter) DescribeCluster(ctx interface{}, params interface{}, optFns ...interface{}) *MockClient_DescribeCluster_Call { + return &MockClient_DescribeCluster_Call{Call: _e.mock.On("DescribeCluster", + append([]interface{}{ctx, params}, optFns...)...)} +} + +func (_c *MockClient_DescribeCluster_Call) Run(run func(ctx context.Context, params *eks.DescribeClusterInput, optFns ...func(*eks.Options))) *MockClient_DescribeCluster_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 *eks.DescribeClusterInput + if args[1] != nil { + arg1 = args[1].(*eks.DescribeClusterInput) + } + var arg2 []func(*eks.Options) + var variadicArgs []func(*eks.Options) + if len(args) > 2 { + variadicArgs = args[2].([]func(*eks.Options)) + } + arg2 = variadicArgs + run( + arg0, + arg1, + arg2..., + ) + }) + return _c +} + +func (_c *MockClient_DescribeCluster_Call) Return(describeClusterOutput *eks.DescribeClusterOutput, err error) *MockClient_DescribeCluster_Call { + _c.Call.Return(describeClusterOutput, err) + return _c +} + +func (_c *MockClient_DescribeCluster_Call) RunAndReturn(run func(ctx context.Context, params *eks.DescribeClusterInput, optFns ...func(*eks.Options)) (*eks.DescribeClusterOutput, error)) *MockClient_DescribeCluster_Call { + _c.Call.Return(run) + return _c +} + +// ListClusters provides a mock function for the type MockClient +func (_mock *MockClient) ListClusters(ctx context.Context, params *eks.ListClustersInput, optFns ...func(*eks.Options)) (*eks.ListClustersOutput, error) { + var tmpRet mock.Arguments + if len(optFns) > 0 { + tmpRet = _mock.Called(ctx, params, optFns) + } else { + tmpRet = _mock.Called(ctx, params) + } + ret := tmpRet + + if len(ret) == 0 { + panic("no return value specified for ListClusters") + } + + var r0 *eks.ListClustersOutput + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, *eks.ListClustersInput, ...func(*eks.Options)) (*eks.ListClustersOutput, error)); ok { + return returnFunc(ctx, params, optFns...) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, *eks.ListClustersInput, ...func(*eks.Options)) *eks.ListClustersOutput); ok { + r0 = returnFunc(ctx, params, optFns...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*eks.ListClustersOutput) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, *eks.ListClustersInput, ...func(*eks.Options)) error); ok { + r1 = returnFunc(ctx, params, optFns...) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_ListClusters_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListClusters' +type MockClient_ListClusters_Call struct { + *mock.Call +} + +// ListClusters is a helper method to define mock.On call +// - ctx context.Context +// - params *eks.ListClustersInput +// - optFns ...func(*eks.Options) +func (_e *MockClient_Expecter) ListClusters(ctx interface{}, params interface{}, optFns ...interface{}) *MockClient_ListClusters_Call { + return &MockClient_ListClusters_Call{Call: _e.mock.On("ListClusters", + append([]interface{}{ctx, params}, optFns...)...)} +} + +func (_c *MockClient_ListClusters_Call) Run(run func(ctx context.Context, params *eks.ListClustersInput, optFns ...func(*eks.Options))) *MockClient_ListClusters_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 *eks.ListClustersInput + if args[1] != nil { + arg1 = args[1].(*eks.ListClustersInput) + } + var arg2 []func(*eks.Options) + var variadicArgs []func(*eks.Options) + if len(args) > 2 { + variadicArgs = args[2].([]func(*eks.Options)) + } + arg2 = variadicArgs + run( + arg0, + arg1, + arg2..., + ) + }) + return _c +} + +func (_c *MockClient_ListClusters_Call) Return(listClustersOutput *eks.ListClustersOutput, err error) *MockClient_ListClusters_Call { + _c.Call.Return(listClustersOutput, err) + return _c +} + +func (_c *MockClient_ListClusters_Call) RunAndReturn(run func(ctx context.Context, params *eks.ListClustersInput, optFns ...func(*eks.Options)) (*eks.ListClustersOutput, error)) *MockClient_ListClusters_Call { + _c.Call.Return(run) + return _c +} + +// NewMockClustersDescriber creates a new instance of MockClustersDescriber. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockClustersDescriber(t interface { + mock.TestingT + Cleanup(func()) +}) *MockClustersDescriber { + mock := &MockClustersDescriber{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + +// MockClustersDescriber is an autogenerated mock type for the ClustersDescriber type +type MockClustersDescriber struct { + mock.Mock +} + +type MockClustersDescriber_Expecter struct { + mock *mock.Mock +} + +func (_m *MockClustersDescriber) EXPECT() *MockClustersDescriber_Expecter { + return &MockClustersDescriber_Expecter{mock: &_m.Mock} +} + +// DescribeClusters provides a mock function for the type MockClustersDescriber +func (_mock *MockClustersDescriber) DescribeClusters(ctx context.Context) ([]awslib.AwsResource, error) { + ret := _mock.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for DescribeClusters") + } + + var r0 []awslib.AwsResource + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context) ([]awslib.AwsResource, error)); ok { + return returnFunc(ctx) + } + if returnFunc, ok := ret.Get(0).(func(context.Context) []awslib.AwsResource); ok { + r0 = returnFunc(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]awslib.AwsResource) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = returnFunc(ctx) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClustersDescriber_DescribeClusters_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DescribeClusters' +type MockClustersDescriber_DescribeClusters_Call struct { + *mock.Call +} + +// DescribeClusters is a helper method to define mock.On call +// - ctx context.Context +func (_e *MockClustersDescriber_Expecter) DescribeClusters(ctx interface{}) *MockClustersDescriber_DescribeClusters_Call { + return &MockClustersDescriber_DescribeClusters_Call{Call: _e.mock.On("DescribeClusters", ctx)} +} + +func (_c *MockClustersDescriber_DescribeClusters_Call) Run(run func(ctx context.Context)) *MockClustersDescriber_DescribeClusters_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockClustersDescriber_DescribeClusters_Call) Return(awsResources []awslib.AwsResource, err error) *MockClustersDescriber_DescribeClusters_Call { + _c.Call.Return(awsResources, err) + return _c +} + +func (_c *MockClustersDescriber_DescribeClusters_Call) RunAndReturn(run func(ctx context.Context) ([]awslib.AwsResource, error)) *MockClustersDescriber_DescribeClusters_Call { + _c.Call.Return(run) + return _c +} diff --git a/internal/resources/providers/awslib/eks/provider.go b/internal/resources/providers/awslib/eks/provider.go new file mode 100644 index 0000000000..577a90485a --- /dev/null +++ b/internal/resources/providers/awslib/eks/provider.go @@ -0,0 +1,63 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package eks + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/service/eks" + "github.com/samber/lo" + + "github.com/elastic/cloudbeat/internal/resources/providers/awslib" + "github.com/elastic/cloudbeat/internal/resources/utils/pointers" +) + +// DescribeClusters returns every EKS cluster across all regions as Asset Discovery resources. +// EKS only exposes cluster names via ListClusters, so each is then described individually. +func (p *Provider) DescribeClusters(ctx context.Context) ([]awslib.AwsResource, error) { + clusters, err := awslib.MultiRegionFetch(ctx, p.clients, func(ctx context.Context, region string, c Client) ([]awslib.AwsResource, error) { + var names []string + input := &eks.ListClustersInput{} + for { + output, err := c.ListClusters(ctx, input) + if err != nil { + return nil, err + } + names = append(names, output.Clusters...) + if output.NextToken == nil { + break + } + input.NextToken = output.NextToken + } + + result := make([]awslib.AwsResource, 0, len(names)) + for _, name := range names { + described, err := c.DescribeCluster(ctx, &eks.DescribeClusterInput{Name: pointers.Ref(name)}) + if err != nil { + p.log.Errorf("Could not describe EKS cluster %s: %v", name, err) + continue + } + if described.Cluster == nil { + continue + } + result = append(result, newCluster(*described.Cluster, region)) + } + return result, nil + }) + return lo.Flatten(clusters), err +} diff --git a/internal/resources/providers/awslib/eks/provider_test.go b/internal/resources/providers/awslib/eks/provider_test.go new file mode 100644 index 0000000000..d3a829cc9b --- /dev/null +++ b/internal/resources/providers/awslib/eks/provider_test.go @@ -0,0 +1,81 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package eks + +import ( + "errors" + "testing" + + "github.com/aws/aws-sdk-go-v2/service/eks" + "github.com/aws/aws-sdk-go-v2/service/eks/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + "github.com/elastic/cloudbeat/internal/resources/providers/awslib" + "github.com/elastic/cloudbeat/internal/resources/utils/pointers" + "github.com/elastic/cloudbeat/internal/resources/utils/testhelper" +) + +func TestProvider_DescribeClusters(t *testing.T) { + t.Run("list clusters error", func(t *testing.T) { + m := &MockClient{} + m.On("ListClusters", mock.Anything, mock.Anything).Return(nil, errors.New("boom")) + p := &Provider{log: testhelper.NewLogger(t), clients: map[string]Client{awslib.DefaultRegion: m}} + + got, err := p.DescribeClusters(t.Context()) + require.Error(t, err) + assert.Empty(t, got) + }) + + t.Run("lists and describes clusters", func(t *testing.T) { + m := &MockClient{} + m.On("ListClusters", mock.Anything, mock.Anything).Return(&eks.ListClustersOutput{ + Clusters: []string{"c1"}, + }, nil) + m.On("DescribeCluster", mock.Anything, mock.Anything).Return(&eks.DescribeClusterOutput{ + Cluster: &types.Cluster{ + Name: pointers.Ref("c1"), + Arn: pointers.Ref("arn:aws:eks:us-east-1:123:cluster/c1"), + Status: types.ClusterStatusActive, + Version: pointers.Ref("1.29"), + PlatformVersion: pointers.Ref("eks.5"), + Tags: map[string]string{"Owner": "team-infra"}, + ResourcesVpcConfig: &types.VpcConfigResponse{ + EndpointPublicAccess: true, + EndpointPrivateAccess: false, + }, + }, + }, nil) + p := &Provider{log: testhelper.NewLogger(t), clients: map[string]Client{awslib.DefaultRegion: m}} + + got, err := p.DescribeClusters(t.Context()) + require.NoError(t, err) + require.Len(t, got, 1) + + c, ok := got[0].(Cluster) + require.True(t, ok) + assert.Equal(t, "c1", c.Name) + assert.Equal(t, "ACTIVE", c.Status) + assert.Equal(t, "1.29", c.Version) + assert.True(t, c.EndpointPublicAccess) + assert.False(t, c.EndpointPrivateAccess) + assert.Equal(t, "team-infra", c.GetOwnerTag()) + assert.Equal(t, awslib.DefaultRegion, c.GetRegion()) + }) +} diff --git a/internal/resources/providers/awslib/route53/provider.go b/internal/resources/providers/awslib/route53/provider.go new file mode 100644 index 0000000000..0f3bb44606 --- /dev/null +++ b/internal/resources/providers/awslib/route53/provider.go @@ -0,0 +1,84 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package route53 + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/service/route53" + + "github.com/elastic/cloudbeat/internal/resources/providers/awslib" + "github.com/elastic/cloudbeat/internal/resources/utils/pointers" +) + +// ListRecords returns every resource record set across all hosted zones in the account as +// Asset Discovery resources. Each record set becomes a single asset. +func (p *Provider) ListRecords(ctx context.Context) ([]awslib.AwsResource, error) { + p.log.Debug("Fetching Route53 records") + + var results []awslib.AwsResource + var lastErr error + + zonesInput := &route53.ListHostedZonesInput{} + for { + zones, err := p.client.ListHostedZones(ctx, zonesInput) + if err != nil { + return results, err + } + + for _, zone := range zones.HostedZones { + hostedZoneID := pointers.Deref(zone.Id) + records, err := p.listRecordsForZone(ctx, hostedZoneID, cleanZoneID(hostedZoneID), pointers.Deref(zone.Name)) + if err != nil { + p.log.Errorf("Could not list records for hosted zone %s: %v", hostedZoneID, err) + lastErr = err + continue + } + results = append(results, records...) + } + + if !zones.IsTruncated || zones.NextMarker == nil { + break + } + zonesInput.Marker = zones.NextMarker + } + + p.log.Debugf("Fetched %d Route53 records", len(results)) + return results, lastErr +} + +func (p *Provider) listRecordsForZone(ctx context.Context, hostedZoneID, zoneID, zoneName string) ([]awslib.AwsResource, error) { + var results []awslib.AwsResource + input := &route53.ListResourceRecordSetsInput{HostedZoneId: pointers.Ref(hostedZoneID)} + for { + output, err := p.client.ListResourceRecordSets(ctx, input) + if err != nil { + return nil, err + } + for _, rrs := range output.ResourceRecordSets { + results = append(results, newRecord(rrs, zoneID, zoneName)) + } + if !output.IsTruncated { + break + } + input.StartRecordName = output.NextRecordName + input.StartRecordType = output.NextRecordType + input.StartRecordIdentifier = output.NextRecordIdentifier + } + return results, nil +} diff --git a/internal/resources/providers/awslib/route53/provider_test.go b/internal/resources/providers/awslib/route53/provider_test.go new file mode 100644 index 0000000000..946fcd789e --- /dev/null +++ b/internal/resources/providers/awslib/route53/provider_test.go @@ -0,0 +1,96 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package route53 + +import ( + "errors" + "testing" + + "github.com/aws/aws-sdk-go-v2/service/route53" + "github.com/aws/aws-sdk-go-v2/service/route53/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + "github.com/elastic/cloudbeat/internal/resources/utils/pointers" + "github.com/elastic/cloudbeat/internal/resources/utils/testhelper" +) + +func TestProvider_ListRecords(t *testing.T) { + t.Run("list hosted zones error", func(t *testing.T) { + m := &MockClient{} + m.On("ListHostedZones", mock.Anything, mock.Anything).Return(nil, errors.New("boom")) + p := &Provider{log: testhelper.NewLogger(t), client: m} + + got, err := p.ListRecords(t.Context()) + require.Error(t, err) + assert.Empty(t, got) + }) + + t.Run("maps records and cleans zone id", func(t *testing.T) { + m := &MockClient{} + m.On("ListHostedZones", mock.Anything, mock.Anything).Return(&route53.ListHostedZonesOutput{ + HostedZones: []types.HostedZone{ + {Id: pointers.Ref("/hostedzone/Z123"), Name: pointers.Ref("example.com.")}, + }, + }, nil) + m.On("ListResourceRecordSets", mock.Anything, mock.Anything).Return(&route53.ListResourceRecordSetsOutput{ + ResourceRecordSets: []types.ResourceRecordSet{ + { + Name: pointers.Ref("www.example.com."), + Type: types.RRTypeA, + Weight: pointers.Ref(int64(10)), + Region: types.ResourceRecordSetRegionUsEast1, + HealthCheckId: pointers.Ref("hc-1"), + ResourceRecords: []types.ResourceRecord{ + {Value: pointers.Ref("203.0.113.10")}, + }, + }, + { + Name: pointers.Ref("alias.example.com."), + Type: types.RRTypeA, + AliasTarget: &types.AliasTarget{ + DNSName: pointers.Ref("target.example.com."), + HostedZoneId: pointers.Ref("Z456"), + }, + }, + }, + }, nil) + p := &Provider{log: testhelper.NewLogger(t), client: m} + + got, err := p.ListRecords(t.Context()) + require.NoError(t, err) + require.Len(t, got, 2) + + first, ok := got[0].(Record) + require.True(t, ok) + assert.Equal(t, "Z123", first.ZoneID) + assert.Equal(t, "example.com.", first.ZoneName) + assert.Equal(t, "A", first.Type) + assert.Equal(t, []string{"203.0.113.10"}, first.Records) + assert.Equal(t, int64(10), *first.Weight) + assert.Equal(t, "hc-1", first.HealthCheckID) + assert.Equal(t, "arn:aws:route53:::hostedzone/Z123/recordset/www.example.com./A", first.GetResourceArn()) + assert.Equal(t, "global", first.GetRegion()) + + second, ok := got[1].(Record) + require.True(t, ok) + assert.Equal(t, "target.example.com.", second.AliasTargetDNS) + assert.Equal(t, "Z456", second.AliasTargetZoneID) + }) +} diff --git a/internal/resources/providers/awslib/route53/record.go b/internal/resources/providers/awslib/route53/record.go new file mode 100644 index 0000000000..fe24ec2f1d --- /dev/null +++ b/internal/resources/providers/awslib/route53/record.go @@ -0,0 +1,98 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package route53 + +import ( + "fmt" + "strings" + + "github.com/aws/aws-sdk-go-v2/service/route53/types" + + "github.com/elastic/cloudbeat/internal/resources/fetching" + "github.com/elastic/cloudbeat/internal/resources/providers/awslib" + "github.com/elastic/cloudbeat/internal/resources/utils/pointers" +) + +// Record is a flattened, normalized view of a Route53 resource record set together with the +// hosted zone it belongs to. One Record maps to one Asset Discovery asset. +type Record struct { + Name string `json:"name"` + Type string `json:"type"` + Records []string `json:"records,omitempty"` + Weight *int64 `json:"weight,omitempty"` + RoutingRegion string `json:"routing_region,omitempty"` + ZoneID string `json:"zone_id"` + ZoneName string `json:"zone_name"` + AliasTargetDNS string `json:"alias_target_dns,omitempty"` + AliasTargetZoneID string `json:"alias_target_zone_id,omitempty"` + HealthCheckID string `json:"health_check_id,omitempty"` + + setIdentifier string +} + +func newRecord(rrs types.ResourceRecordSet, zoneID, zoneName string) Record { + r := Record{ + Name: pointers.Deref(rrs.Name), + Type: string(rrs.Type), + Weight: rrs.Weight, + RoutingRegion: string(rrs.Region), + ZoneID: zoneID, + ZoneName: zoneName, + HealthCheckID: pointers.Deref(rrs.HealthCheckId), + setIdentifier: pointers.Deref(rrs.SetIdentifier), + } + for _, rec := range rrs.ResourceRecords { + if v := pointers.Deref(rec.Value); v != "" { + r.Records = append(r.Records, v) + } + } + if rrs.AliasTarget != nil { + r.AliasTargetDNS = pointers.Deref(rrs.AliasTarget.DNSName) + r.AliasTargetZoneID = pointers.Deref(rrs.AliasTarget.HostedZoneId) + } + return r +} + +// GetResourceArn synthesizes a stable identifier for the record set. Route53 record sets have +// no ARN, so we derive one from the zone, record name, type and (for weighted/latency/geo +// records) the set identifier, which together uniquely identify a record set. +func (r Record) GetResourceArn() string { + arn := fmt.Sprintf("arn:aws:route53:::hostedzone/%s/recordset/%s/%s", r.ZoneID, r.Name, r.Type) + if r.setIdentifier != "" { + arn += "/" + r.setIdentifier + } + return arn +} + +func (r Record) GetResourceName() string { + return r.Name +} + +func (r Record) GetResourceType() string { + return fetching.Route53Type +} + +// GetRegion reports the AWS region. Route53 is a global service. +func (r Record) GetRegion() string { + return awslib.GlobalRegion +} + +// cleanZoneID strips the "/hostedzone/" prefix Route53 returns on hosted zone IDs. +func cleanZoneID(id string) string { + return strings.TrimPrefix(id, "/hostedzone/") +} diff --git a/internal/resources/providers/awslib/route53/route53.go b/internal/resources/providers/awslib/route53/route53.go new file mode 100644 index 0000000000..cb53589e29 --- /dev/null +++ b/internal/resources/providers/awslib/route53/route53.go @@ -0,0 +1,51 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package route53 + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/route53" + + "github.com/elastic/cloudbeat/internal/infra/clog" + "github.com/elastic/cloudbeat/internal/resources/providers/awslib" +) + +// Client abstracts the subset of the Route53 SDK client used by the provider. +// Route53 is a global service, so a single client (not per-region) is sufficient. +type Client interface { + ListHostedZones(ctx context.Context, params *route53.ListHostedZonesInput, optFns ...func(*route53.Options)) (*route53.ListHostedZonesOutput, error) + ListResourceRecordSets(ctx context.Context, params *route53.ListResourceRecordSetsInput, optFns ...func(*route53.Options)) (*route53.ListResourceRecordSetsOutput, error) +} + +type RecordsLister interface { + ListRecords(ctx context.Context) ([]awslib.AwsResource, error) +} + +type Provider struct { + log *clog.Logger + client Client +} + +func NewProvider(log *clog.Logger, cfg aws.Config) *Provider { + return &Provider{ + log: log, + client: route53.NewFromConfig(cfg), + } +} diff --git a/internal/resources/providers/awslib/route53/route53_mock.go b/internal/resources/providers/awslib/route53/route53_mock.go new file mode 100644 index 0000000000..8ff85af562 --- /dev/null +++ b/internal/resources/providers/awslib/route53/route53_mock.go @@ -0,0 +1,313 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +//go:build !release + +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify + +package route53 + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/service/route53" + "github.com/elastic/cloudbeat/internal/resources/providers/awslib" + mock "github.com/stretchr/testify/mock" +) + +// NewMockClient creates a new instance of MockClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockClient(t interface { + mock.TestingT + Cleanup(func()) +}) *MockClient { + mock := &MockClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + +// MockClient is an autogenerated mock type for the Client type +type MockClient struct { + mock.Mock +} + +type MockClient_Expecter struct { + mock *mock.Mock +} + +func (_m *MockClient) EXPECT() *MockClient_Expecter { + return &MockClient_Expecter{mock: &_m.Mock} +} + +// ListHostedZones provides a mock function for the type MockClient +func (_mock *MockClient) ListHostedZones(ctx context.Context, params *route53.ListHostedZonesInput, optFns ...func(*route53.Options)) (*route53.ListHostedZonesOutput, error) { + var tmpRet mock.Arguments + if len(optFns) > 0 { + tmpRet = _mock.Called(ctx, params, optFns) + } else { + tmpRet = _mock.Called(ctx, params) + } + ret := tmpRet + + if len(ret) == 0 { + panic("no return value specified for ListHostedZones") + } + + var r0 *route53.ListHostedZonesOutput + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, *route53.ListHostedZonesInput, ...func(*route53.Options)) (*route53.ListHostedZonesOutput, error)); ok { + return returnFunc(ctx, params, optFns...) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, *route53.ListHostedZonesInput, ...func(*route53.Options)) *route53.ListHostedZonesOutput); ok { + r0 = returnFunc(ctx, params, optFns...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*route53.ListHostedZonesOutput) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, *route53.ListHostedZonesInput, ...func(*route53.Options)) error); ok { + r1 = returnFunc(ctx, params, optFns...) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_ListHostedZones_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListHostedZones' +type MockClient_ListHostedZones_Call struct { + *mock.Call +} + +// ListHostedZones is a helper method to define mock.On call +// - ctx context.Context +// - params *route53.ListHostedZonesInput +// - optFns ...func(*route53.Options) +func (_e *MockClient_Expecter) ListHostedZones(ctx interface{}, params interface{}, optFns ...interface{}) *MockClient_ListHostedZones_Call { + return &MockClient_ListHostedZones_Call{Call: _e.mock.On("ListHostedZones", + append([]interface{}{ctx, params}, optFns...)...)} +} + +func (_c *MockClient_ListHostedZones_Call) Run(run func(ctx context.Context, params *route53.ListHostedZonesInput, optFns ...func(*route53.Options))) *MockClient_ListHostedZones_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 *route53.ListHostedZonesInput + if args[1] != nil { + arg1 = args[1].(*route53.ListHostedZonesInput) + } + var arg2 []func(*route53.Options) + var variadicArgs []func(*route53.Options) + if len(args) > 2 { + variadicArgs = args[2].([]func(*route53.Options)) + } + arg2 = variadicArgs + run( + arg0, + arg1, + arg2..., + ) + }) + return _c +} + +func (_c *MockClient_ListHostedZones_Call) Return(listHostedZonesOutput *route53.ListHostedZonesOutput, err error) *MockClient_ListHostedZones_Call { + _c.Call.Return(listHostedZonesOutput, err) + return _c +} + +func (_c *MockClient_ListHostedZones_Call) RunAndReturn(run func(ctx context.Context, params *route53.ListHostedZonesInput, optFns ...func(*route53.Options)) (*route53.ListHostedZonesOutput, error)) *MockClient_ListHostedZones_Call { + _c.Call.Return(run) + return _c +} + +// ListResourceRecordSets provides a mock function for the type MockClient +func (_mock *MockClient) ListResourceRecordSets(ctx context.Context, params *route53.ListResourceRecordSetsInput, optFns ...func(*route53.Options)) (*route53.ListResourceRecordSetsOutput, error) { + var tmpRet mock.Arguments + if len(optFns) > 0 { + tmpRet = _mock.Called(ctx, params, optFns) + } else { + tmpRet = _mock.Called(ctx, params) + } + ret := tmpRet + + if len(ret) == 0 { + panic("no return value specified for ListResourceRecordSets") + } + + var r0 *route53.ListResourceRecordSetsOutput + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, *route53.ListResourceRecordSetsInput, ...func(*route53.Options)) (*route53.ListResourceRecordSetsOutput, error)); ok { + return returnFunc(ctx, params, optFns...) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, *route53.ListResourceRecordSetsInput, ...func(*route53.Options)) *route53.ListResourceRecordSetsOutput); ok { + r0 = returnFunc(ctx, params, optFns...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*route53.ListResourceRecordSetsOutput) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, *route53.ListResourceRecordSetsInput, ...func(*route53.Options)) error); ok { + r1 = returnFunc(ctx, params, optFns...) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_ListResourceRecordSets_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListResourceRecordSets' +type MockClient_ListResourceRecordSets_Call struct { + *mock.Call +} + +// ListResourceRecordSets is a helper method to define mock.On call +// - ctx context.Context +// - params *route53.ListResourceRecordSetsInput +// - optFns ...func(*route53.Options) +func (_e *MockClient_Expecter) ListResourceRecordSets(ctx interface{}, params interface{}, optFns ...interface{}) *MockClient_ListResourceRecordSets_Call { + return &MockClient_ListResourceRecordSets_Call{Call: _e.mock.On("ListResourceRecordSets", + append([]interface{}{ctx, params}, optFns...)...)} +} + +func (_c *MockClient_ListResourceRecordSets_Call) Run(run func(ctx context.Context, params *route53.ListResourceRecordSetsInput, optFns ...func(*route53.Options))) *MockClient_ListResourceRecordSets_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 *route53.ListResourceRecordSetsInput + if args[1] != nil { + arg1 = args[1].(*route53.ListResourceRecordSetsInput) + } + var arg2 []func(*route53.Options) + var variadicArgs []func(*route53.Options) + if len(args) > 2 { + variadicArgs = args[2].([]func(*route53.Options)) + } + arg2 = variadicArgs + run( + arg0, + arg1, + arg2..., + ) + }) + return _c +} + +func (_c *MockClient_ListResourceRecordSets_Call) Return(listResourceRecordSetsOutput *route53.ListResourceRecordSetsOutput, err error) *MockClient_ListResourceRecordSets_Call { + _c.Call.Return(listResourceRecordSetsOutput, err) + return _c +} + +func (_c *MockClient_ListResourceRecordSets_Call) RunAndReturn(run func(ctx context.Context, params *route53.ListResourceRecordSetsInput, optFns ...func(*route53.Options)) (*route53.ListResourceRecordSetsOutput, error)) *MockClient_ListResourceRecordSets_Call { + _c.Call.Return(run) + return _c +} + +// NewMockRecordsLister creates a new instance of MockRecordsLister. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockRecordsLister(t interface { + mock.TestingT + Cleanup(func()) +}) *MockRecordsLister { + mock := &MockRecordsLister{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + +// MockRecordsLister is an autogenerated mock type for the RecordsLister type +type MockRecordsLister struct { + mock.Mock +} + +type MockRecordsLister_Expecter struct { + mock *mock.Mock +} + +func (_m *MockRecordsLister) EXPECT() *MockRecordsLister_Expecter { + return &MockRecordsLister_Expecter{mock: &_m.Mock} +} + +// ListRecords provides a mock function for the type MockRecordsLister +func (_mock *MockRecordsLister) ListRecords(ctx context.Context) ([]awslib.AwsResource, error) { + ret := _mock.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for ListRecords") + } + + var r0 []awslib.AwsResource + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context) ([]awslib.AwsResource, error)); ok { + return returnFunc(ctx) + } + if returnFunc, ok := ret.Get(0).(func(context.Context) []awslib.AwsResource); ok { + r0 = returnFunc(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]awslib.AwsResource) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = returnFunc(ctx) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockRecordsLister_ListRecords_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListRecords' +type MockRecordsLister_ListRecords_Call struct { + *mock.Call +} + +// ListRecords is a helper method to define mock.On call +// - ctx context.Context +func (_e *MockRecordsLister_Expecter) ListRecords(ctx interface{}) *MockRecordsLister_ListRecords_Call { + return &MockRecordsLister_ListRecords_Call{Call: _e.mock.On("ListRecords", ctx)} +} + +func (_c *MockRecordsLister_ListRecords_Call) Run(run func(ctx context.Context)) *MockRecordsLister_ListRecords_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockRecordsLister_ListRecords_Call) Return(awsResources []awslib.AwsResource, err error) *MockRecordsLister_ListRecords_Call { + _c.Call.Return(awsResources, err) + return _c +} + +func (_c *MockRecordsLister_ListRecords_Call) RunAndReturn(run func(ctx context.Context) ([]awslib.AwsResource, error)) *MockRecordsLister_ListRecords_Call { + _c.Call.Return(run) + return _c +}