diff --git a/go.mod b/go.mod index 369db7155c908..97efddaf9814e 100644 --- a/go.mod +++ b/go.mod @@ -43,7 +43,7 @@ require ( github.com/aquasecurity/libbpfgo v0.5.1-libbpf-1.2 github.com/armon/go-radix v1.0.0 github.com/aws/aws-sdk-go v1.55.5 - github.com/aws/aws-sdk-go-v2 v1.32.7 + github.com/aws/aws-sdk-go-v2 v1.32.8 github.com/aws/aws-sdk-go-v2/config v1.28.7 github.com/aws/aws-sdk-go-v2/credentials v1.17.48 github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.23 @@ -60,6 +60,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/ec2instanceconnect v1.27.8 github.com/aws/aws-sdk-go-v2/service/ecs v1.53.2 github.com/aws/aws-sdk-go-v2/service/eks v1.56.0 + github.com/aws/aws-sdk-go-v2/service/elasticache v1.44.4 github.com/aws/aws-sdk-go-v2/service/glue v1.105.0 github.com/aws/aws-sdk-go-v2/service/iam v1.38.3 github.com/aws/aws-sdk-go-v2/service/identitystore v1.27.8 @@ -275,8 +276,8 @@ require ( github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/atotto/clipboard v0.1.4 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.27 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.27 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.26 // indirect github.com/aws/aws-sdk-go-v2/service/ecr v1.37.0 // indirect diff --git a/go.sum b/go.sum index e922ba031ecc4..25dc42a14073e 100644 --- a/go.sum +++ b/go.sum @@ -851,8 +851,8 @@ github.com/aws/aws-sdk-go v1.49.12/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3Tj github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/aws/aws-sdk-go-v2 v1.18.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= -github.com/aws/aws-sdk-go-v2 v1.32.7 h1:ky5o35oENWi0JYWUZkB7WYvVPP+bcRF5/Iq7JWSb5Rw= -github.com/aws/aws-sdk-go-v2 v1.32.7/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U= +github.com/aws/aws-sdk-go-v2 v1.32.8 h1:cZV+NUS/eGxKXMtmyhtYPJ7Z4YLoI/V8bkTdRZfYhGo= +github.com/aws/aws-sdk-go-v2 v1.32.8/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 h1:lL7IfaFzngfx0ZwUGOZdsFFnQ5uLvR0hWqqhyE7Q9M8= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7/go.mod h1:QraP0UcVlQJsmHfioCrveWOC1nbiWUl3ej08h4mXWoc= github.com/aws/aws-sdk-go-v2/config v1.18.25/go.mod h1:dZnYpD5wTW/dQF0rRNLVypB396zWCcPiBIvdvSWHEg4= @@ -873,11 +873,11 @@ github.com/aws/aws-sdk-go-v2/feature/rds/auth v1.5.2/go.mod h1:fnqb94UO6YCjBIic4 github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.45 h1:ZxB8WFVYwolhDZxuZXoesHkl+L9cXLWy0K/G0QkNATc= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.45/go.mod h1:1krrbyoFFDqaNldmltPTP+mK3sAXLHPoaFtISOw2Hkk= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33/go.mod h1:7i0PF1ME/2eUPFcjkVIwq+DOygHEoK92t5cDqNgYbIw= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26 h1:I/5wmGMffY4happ8NOCuIUEWGUvvFp5NSeQcXl9RHcI= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26/go.mod h1:FR8f4turZtNy6baO0KJ5FJUmXH/cSkI9fOngs0yl6mA= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.27 h1:jSJjSBzw8VDIbWv+mmvBSP8ezsztMYJGH+eKqi9AmNs= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.27/go.mod h1:/DAhLbFRgwhmvJdOfSm+WwikZrCuUJiA4WgJG0fTNSw= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27/go.mod h1:UrHnn3QV/d0pBZ6QBAEQcqFLf8FAzLmoUfPVIueOvoM= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26 h1:zXFLuEuMMUOvEARXFUVJdfqZ4bvvSgdGRq/ATcrQxzM= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26/go.mod h1:3o2Wpy0bogG1kyOPrgkXA8pgIfEEv0+m19O9D5+W8y8= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.27 h1:l+X4K77Dui85pIj5foXDhPlnqcNRG2QUyvca300lXh8= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.27/go.mod h1:KvZXSFEXm6x84yE8qffKvT3x8J5clWnVFXphpohhzJ8= github.com/aws/aws-sdk-go-v2/internal/ini v1.3.34/go.mod h1:Etz2dj6UHYuw+Xw830KfzCfWGMzqvUTCjUj5b76GVDc= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= @@ -905,6 +905,8 @@ github.com/aws/aws-sdk-go-v2/service/ecs v1.53.2 h1:o/FdG76sTAoC8h20j6bSBE6MPJYO github.com/aws/aws-sdk-go-v2/service/ecs v1.53.2/go.mod h1:YpTRClSDOPvN2e3kiIrYOx1sI+YKTZVmlMiNO2AwYhE= github.com/aws/aws-sdk-go-v2/service/eks v1.56.0 h1:x31cGGE/t/QkrHVh5m2uWvYwDiaDXpj88nh6OdnI5r0= github.com/aws/aws-sdk-go-v2/service/eks v1.56.0/go.mod h1:kNUWaiotRWCnfQlprrxSMg8ALqbZyA9xLCwKXuLumSk= +github.com/aws/aws-sdk-go-v2/service/elasticache v1.44.4 h1:unSvRFU/oMJ9pzwvhtCCnbKGqAp92oetajVv6cW7H2A= +github.com/aws/aws-sdk-go-v2/service/elasticache v1.44.4/go.mod h1:z/DqeOQ4R/9fGJsKTCHKtbGSR7Vupon53C6D8FNUU1w= github.com/aws/aws-sdk-go-v2/service/glue v1.105.0 h1:raq38Qb6iJJtzADr7Z4IYHOFp5E1NVpHDGoTOsGLHNM= github.com/aws/aws-sdk-go-v2/service/glue v1.105.0/go.mod h1:FyYpmVnMux6fzG2kcLnVwT/swhs8DNtleGIkc8gh63c= github.com/aws/aws-sdk-go-v2/service/iam v1.38.3 h1:2sFIoFzU1IEL9epJWubJm9Dhrn45aTNEJuwsesaCGnk= diff --git a/integrations/event-handler/go.mod b/integrations/event-handler/go.mod index ebbfad68368e9..19fa5317b2e96 100644 --- a/integrations/event-handler/go.mod +++ b/integrations/event-handler/go.mod @@ -63,14 +63,14 @@ require ( github.com/armon/go-radix v1.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/aws/aws-sdk-go v1.55.5 // indirect - github.com/aws/aws-sdk-go-v2 v1.32.7 // indirect + github.com/aws/aws-sdk-go-v2 v1.32.8 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 // indirect github.com/aws/aws-sdk-go-v2/config v1.28.7 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.17.48 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22 // indirect github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.45 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.27 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.27 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.26 // indirect github.com/aws/aws-sdk-go-v2/service/athena v1.49.2 // indirect @@ -78,6 +78,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/ec2instanceconnect v1.27.8 // indirect github.com/aws/aws-sdk-go-v2/service/ecs v1.53.2 // indirect github.com/aws/aws-sdk-go-v2/service/eks v1.56.0 // indirect + github.com/aws/aws-sdk-go-v2/service/elasticache v1.44.4 // indirect github.com/aws/aws-sdk-go-v2/service/glue v1.105.0 // indirect github.com/aws/aws-sdk-go-v2/service/iam v1.38.3 // indirect github.com/aws/aws-sdk-go-v2/service/identitystore v1.27.8 // indirect diff --git a/integrations/event-handler/go.sum b/integrations/event-handler/go.sum index 07c3f091d97fa..1e1e49c77493c 100644 --- a/integrations/event-handler/go.sum +++ b/integrations/event-handler/go.sum @@ -729,8 +729,8 @@ 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 v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= -github.com/aws/aws-sdk-go-v2 v1.32.7 h1:ky5o35oENWi0JYWUZkB7WYvVPP+bcRF5/Iq7JWSb5Rw= -github.com/aws/aws-sdk-go-v2 v1.32.7/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U= +github.com/aws/aws-sdk-go-v2 v1.32.8 h1:cZV+NUS/eGxKXMtmyhtYPJ7Z4YLoI/V8bkTdRZfYhGo= +github.com/aws/aws-sdk-go-v2 v1.32.8/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 h1:lL7IfaFzngfx0ZwUGOZdsFFnQ5uLvR0hWqqhyE7Q9M8= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7/go.mod h1:QraP0UcVlQJsmHfioCrveWOC1nbiWUl3ej08h4mXWoc= github.com/aws/aws-sdk-go-v2/config v1.28.7 h1:GduUnoTXlhkgnxTD93g1nv4tVPILbdNQOzav+Wpg7AE= @@ -741,10 +741,10 @@ github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22 h1:kqOrpojG71DxJm/KDPO+Z/ github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22/go.mod h1:NtSFajXVVL8TA2QNngagVZmUtXciyrHOt7xgz4faS/M= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.45 h1:ZxB8WFVYwolhDZxuZXoesHkl+L9cXLWy0K/G0QkNATc= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.45/go.mod h1:1krrbyoFFDqaNldmltPTP+mK3sAXLHPoaFtISOw2Hkk= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26 h1:I/5wmGMffY4happ8NOCuIUEWGUvvFp5NSeQcXl9RHcI= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26/go.mod h1:FR8f4turZtNy6baO0KJ5FJUmXH/cSkI9fOngs0yl6mA= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26 h1:zXFLuEuMMUOvEARXFUVJdfqZ4bvvSgdGRq/ATcrQxzM= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26/go.mod h1:3o2Wpy0bogG1kyOPrgkXA8pgIfEEv0+m19O9D5+W8y8= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.27 h1:jSJjSBzw8VDIbWv+mmvBSP8ezsztMYJGH+eKqi9AmNs= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.27/go.mod h1:/DAhLbFRgwhmvJdOfSm+WwikZrCuUJiA4WgJG0fTNSw= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.27 h1:l+X4K77Dui85pIj5foXDhPlnqcNRG2QUyvca300lXh8= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.27/go.mod h1:KvZXSFEXm6x84yE8qffKvT3x8J5clWnVFXphpohhzJ8= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.26 h1:GeNJsIFHB+WW5ap2Tec4K6dzcVTsRbsT1Lra46Hv9ME= @@ -759,6 +759,8 @@ github.com/aws/aws-sdk-go-v2/service/ecs v1.53.2 h1:o/FdG76sTAoC8h20j6bSBE6MPJYO github.com/aws/aws-sdk-go-v2/service/ecs v1.53.2/go.mod h1:YpTRClSDOPvN2e3kiIrYOx1sI+YKTZVmlMiNO2AwYhE= github.com/aws/aws-sdk-go-v2/service/eks v1.56.0 h1:x31cGGE/t/QkrHVh5m2uWvYwDiaDXpj88nh6OdnI5r0= github.com/aws/aws-sdk-go-v2/service/eks v1.56.0/go.mod h1:kNUWaiotRWCnfQlprrxSMg8ALqbZyA9xLCwKXuLumSk= +github.com/aws/aws-sdk-go-v2/service/elasticache v1.44.4 h1:unSvRFU/oMJ9pzwvhtCCnbKGqAp92oetajVv6cW7H2A= +github.com/aws/aws-sdk-go-v2/service/elasticache v1.44.4/go.mod h1:z/DqeOQ4R/9fGJsKTCHKtbGSR7Vupon53C6D8FNUU1w= github.com/aws/aws-sdk-go-v2/service/glue v1.105.0 h1:raq38Qb6iJJtzADr7Z4IYHOFp5E1NVpHDGoTOsGLHNM= github.com/aws/aws-sdk-go-v2/service/glue v1.105.0/go.mod h1:FyYpmVnMux6fzG2kcLnVwT/swhs8DNtleGIkc8gh63c= github.com/aws/aws-sdk-go-v2/service/iam v1.38.3 h1:2sFIoFzU1IEL9epJWubJm9Dhrn45aTNEJuwsesaCGnk= diff --git a/integrations/terraform/go.mod b/integrations/terraform/go.mod index d4fa8384d2190..4bd3de020d8ab 100644 --- a/integrations/terraform/go.mod +++ b/integrations/terraform/go.mod @@ -75,14 +75,14 @@ require ( github.com/armon/go-radix v1.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/aws/aws-sdk-go v1.55.5 // indirect - github.com/aws/aws-sdk-go-v2 v1.32.7 // indirect + github.com/aws/aws-sdk-go-v2 v1.32.8 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 // indirect github.com/aws/aws-sdk-go-v2/config v1.28.7 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.17.48 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22 // indirect github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.45 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.27 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.27 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.26 // indirect github.com/aws/aws-sdk-go-v2/service/athena v1.49.2 // indirect @@ -90,6 +90,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/ec2instanceconnect v1.27.8 // indirect github.com/aws/aws-sdk-go-v2/service/ecs v1.53.2 // indirect github.com/aws/aws-sdk-go-v2/service/eks v1.56.0 // indirect + github.com/aws/aws-sdk-go-v2/service/elasticache v1.44.4 // indirect github.com/aws/aws-sdk-go-v2/service/glue v1.105.0 // indirect github.com/aws/aws-sdk-go-v2/service/iam v1.38.3 // indirect github.com/aws/aws-sdk-go-v2/service/identitystore v1.27.8 // indirect diff --git a/integrations/terraform/go.sum b/integrations/terraform/go.sum index 92d0858f74eab..22f7869aa9238 100644 --- a/integrations/terraform/go.sum +++ b/integrations/terraform/go.sum @@ -786,8 +786,8 @@ github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3A github.com/aws/aws-sdk-go v1.25.3/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= -github.com/aws/aws-sdk-go-v2 v1.32.7 h1:ky5o35oENWi0JYWUZkB7WYvVPP+bcRF5/Iq7JWSb5Rw= -github.com/aws/aws-sdk-go-v2 v1.32.7/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U= +github.com/aws/aws-sdk-go-v2 v1.32.8 h1:cZV+NUS/eGxKXMtmyhtYPJ7Z4YLoI/V8bkTdRZfYhGo= +github.com/aws/aws-sdk-go-v2 v1.32.8/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 h1:lL7IfaFzngfx0ZwUGOZdsFFnQ5uLvR0hWqqhyE7Q9M8= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7/go.mod h1:QraP0UcVlQJsmHfioCrveWOC1nbiWUl3ej08h4mXWoc= github.com/aws/aws-sdk-go-v2/config v1.28.7 h1:GduUnoTXlhkgnxTD93g1nv4tVPILbdNQOzav+Wpg7AE= @@ -804,10 +804,10 @@ github.com/aws/aws-sdk-go-v2/feature/rds/auth v1.5.2 h1:fo+GuZNME9oGDc7VY+EBT+oC github.com/aws/aws-sdk-go-v2/feature/rds/auth v1.5.2/go.mod h1:fnqb94UO6YCjBIic4WaqDYkNVAEFWOWiReVHitBBWW0= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.45 h1:ZxB8WFVYwolhDZxuZXoesHkl+L9cXLWy0K/G0QkNATc= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.45/go.mod h1:1krrbyoFFDqaNldmltPTP+mK3sAXLHPoaFtISOw2Hkk= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26 h1:I/5wmGMffY4happ8NOCuIUEWGUvvFp5NSeQcXl9RHcI= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26/go.mod h1:FR8f4turZtNy6baO0KJ5FJUmXH/cSkI9fOngs0yl6mA= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26 h1:zXFLuEuMMUOvEARXFUVJdfqZ4bvvSgdGRq/ATcrQxzM= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26/go.mod h1:3o2Wpy0bogG1kyOPrgkXA8pgIfEEv0+m19O9D5+W8y8= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.27 h1:jSJjSBzw8VDIbWv+mmvBSP8ezsztMYJGH+eKqi9AmNs= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.27/go.mod h1:/DAhLbFRgwhmvJdOfSm+WwikZrCuUJiA4WgJG0fTNSw= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.27 h1:l+X4K77Dui85pIj5foXDhPlnqcNRG2QUyvca300lXh8= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.27/go.mod h1:KvZXSFEXm6x84yE8qffKvT3x8J5clWnVFXphpohhzJ8= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.26 h1:GeNJsIFHB+WW5ap2Tec4K6dzcVTsRbsT1Lra46Hv9ME= @@ -830,6 +830,8 @@ github.com/aws/aws-sdk-go-v2/service/ecs v1.53.2 h1:o/FdG76sTAoC8h20j6bSBE6MPJYO github.com/aws/aws-sdk-go-v2/service/ecs v1.53.2/go.mod h1:YpTRClSDOPvN2e3kiIrYOx1sI+YKTZVmlMiNO2AwYhE= github.com/aws/aws-sdk-go-v2/service/eks v1.56.0 h1:x31cGGE/t/QkrHVh5m2uWvYwDiaDXpj88nh6OdnI5r0= github.com/aws/aws-sdk-go-v2/service/eks v1.56.0/go.mod h1:kNUWaiotRWCnfQlprrxSMg8ALqbZyA9xLCwKXuLumSk= +github.com/aws/aws-sdk-go-v2/service/elasticache v1.44.4 h1:unSvRFU/oMJ9pzwvhtCCnbKGqAp92oetajVv6cW7H2A= +github.com/aws/aws-sdk-go-v2/service/elasticache v1.44.4/go.mod h1:z/DqeOQ4R/9fGJsKTCHKtbGSR7Vupon53C6D8FNUU1w= github.com/aws/aws-sdk-go-v2/service/glue v1.105.0 h1:raq38Qb6iJJtzADr7Z4IYHOFp5E1NVpHDGoTOsGLHNM= github.com/aws/aws-sdk-go-v2/service/glue v1.105.0/go.mod h1:FyYpmVnMux6fzG2kcLnVwT/swhs8DNtleGIkc8gh63c= github.com/aws/aws-sdk-go-v2/service/iam v1.38.3 h1:2sFIoFzU1IEL9epJWubJm9Dhrn45aTNEJuwsesaCGnk= diff --git a/lib/cloud/aws/aws.go b/lib/cloud/aws/aws.go index 7361ff75f219c..8e925f35d8593 100644 --- a/lib/cloud/aws/aws.go +++ b/lib/cloud/aws/aws.go @@ -22,10 +22,10 @@ import ( "slices" "strings" + "github.com/aws/aws-sdk-go-v2/aws" + ectypes "github.com/aws/aws-sdk-go-v2/service/elasticache/types" rdstypes "github.com/aws/aws-sdk-go-v2/service/rds/types" redshifttypes "github.com/aws/aws-sdk-go-v2/service/redshift/types" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/elasticache" "github.com/aws/aws-sdk-go/service/memorydb" "github.com/aws/aws-sdk-go/service/opensearchservice" "github.com/coreos/go-semver/semver" @@ -41,7 +41,7 @@ import ( // everything. For types that have other known status values, separate // functions (e.g. IsDBClusterAvailable) can be implemented. func IsResourceAvailable(r any, status *string) bool { - switch strings.ToLower(aws.StringValue(status)) { + switch strings.ToLower(aws.ToString(status)) { case "available", "modifying", "snapshotting", "active": return true @@ -50,7 +50,7 @@ func IsResourceAvailable(r any, status *string) bool { default: slog.WarnContext(context.Background(), "Assuming that AWS resource with an unknown status is available", - "status", aws.StringValue(status), + "status", aws.ToString(status), "resource", logutils.TypeAttr(r), ) return true @@ -59,7 +59,7 @@ func IsResourceAvailable(r any, status *string) bool { // IsElastiCacheClusterAvailable checks if the ElastiCache cluster is // available. -func IsElastiCacheClusterAvailable(cluster *elasticache.ReplicationGroup) bool { +func IsElastiCacheClusterAvailable(cluster *ectypes.ReplicationGroup) bool { return IsResourceAvailable(cluster, cluster.Status) } @@ -70,7 +70,7 @@ func IsMemoryDBClusterAvailable(cluster *memorydb.Cluster) bool { // IsOpenSearchDomainAvailable checks if the OpenSearch domain is available. func IsOpenSearchDomainAvailable(domain *opensearchservice.DomainStatus) bool { - return aws.BoolValue(domain.Created) && !aws.BoolValue(domain.Deleted) + return aws.ToBool(domain.Created) && !aws.ToBool(domain.Deleted) } // IsRDSProxyAvailable checks if the RDS Proxy is available. @@ -120,14 +120,14 @@ func IsRDSProxyCustomEndpointAvailable(customEndpoint *rdstypes.DBProxyEndpoint) // Currently, only MariaDB is being checked. func IsRDSInstanceSupported(instance *rdstypes.DBInstance) bool { // TODO(jakule): Check other engines. - if aws.StringValue(instance.Engine) != services.RDSEngineMariaDB { + if aws.ToString(instance.Engine) != services.RDSEngineMariaDB { return true } // MariaDB follows semver schema: https://mariadb.org/about/ - ver, err := semver.NewVersion(aws.StringValue(instance.EngineVersion)) + ver, err := semver.NewVersion(aws.ToString(instance.EngineVersion)) if err != nil { - slog.ErrorContext(context.Background(), "Failed to parse RDS MariaDB version", "version", aws.StringValue(instance.EngineVersion)) + slog.ErrorContext(context.Background(), "Failed to parse RDS MariaDB version", "version", aws.ToString(instance.EngineVersion)) return false } @@ -139,7 +139,7 @@ func IsRDSInstanceSupported(instance *rdstypes.DBInstance) bool { // IsRDSClusterSupported checks whether the Aurora cluster is supported. func IsRDSClusterSupported(cluster *rdstypes.DBCluster) bool { - switch aws.StringValue(cluster.EngineMode) { + switch aws.ToString(cluster.EngineMode) { // Aurora Serverless v1 does NOT support IAM authentication. // https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-serverless.html#aurora-serverless.limitations // @@ -175,7 +175,7 @@ func AuroraMySQLVersion(cluster *rdstypes.DBCluster) string { // // general format is: .mysql_aurora. // 5.6.10a and 5.7.12 are "legacy" versions and they are returned as it is - version := aws.StringValue(cluster.EngineVersion) + version := aws.ToString(cluster.EngineVersion) parts := strings.Split(version, ".mysql_aurora.") if len(parts) == 2 { return parts[1] @@ -188,9 +188,9 @@ func AuroraMySQLVersion(cluster *rdstypes.DBCluster) string { // // https://docs.aws.amazon.com/documentdb/latest/developerguide/iam-identity-auth.html func IsDocumentDBClusterSupported(cluster *rdstypes.DBCluster) bool { - ver, err := semver.NewVersion(aws.StringValue(cluster.EngineVersion)) + ver, err := semver.NewVersion(aws.ToString(cluster.EngineVersion)) if err != nil { - slog.ErrorContext(context.Background(), "Failed to parse DocumentDB engine version", "version", aws.StringValue(cluster.EngineVersion)) + slog.ErrorContext(context.Background(), "Failed to parse DocumentDB engine version", "version", aws.ToString(cluster.EngineVersion)) return false } @@ -199,20 +199,20 @@ func IsDocumentDBClusterSupported(cluster *rdstypes.DBCluster) bool { // IsElastiCacheClusterSupported checks whether the ElastiCache cluster is // supported. -func IsElastiCacheClusterSupported(cluster *elasticache.ReplicationGroup) bool { - return aws.BoolValue(cluster.TransitEncryptionEnabled) +func IsElastiCacheClusterSupported(cluster *ectypes.ReplicationGroup) bool { + return aws.ToBool(cluster.TransitEncryptionEnabled) } // IsMemoryDBClusterSupported checks whether the MemoryDB cluster is supported. func IsMemoryDBClusterSupported(cluster *memorydb.Cluster) bool { - return aws.BoolValue(cluster.TLSEnabled) + return aws.ToBool(cluster.TLSEnabled) } // IsRDSInstanceAvailable checks if the RDS instance is available. func IsRDSInstanceAvailable(instanceStatus, instanceIdentifier *string) bool { // For a full list of status values, see: // https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/accessing-monitoring.html - switch aws.StringValue(instanceStatus) { + switch aws.ToString(instanceStatus) { // Statuses marked as "Billed" in the above guide. case "available", "backing-up", "configuring-enhanced-monitoring", "configuring-iam-database-auth", "configuring-log-exports", @@ -240,8 +240,8 @@ func IsRDSInstanceAvailable(instanceStatus, instanceIdentifier *string) bool { default: slog.WarnContext(context.Background(), "Assuming RDS instance with unknown status is available", - "status", aws.StringValue(instanceStatus), - "instance", aws.StringValue(instanceIdentifier), + "status", aws.ToString(instanceStatus), + "instance", aws.ToString(instanceIdentifier), ) return true } @@ -251,7 +251,7 @@ func IsRDSInstanceAvailable(instanceStatus, instanceIdentifier *string) bool { func IsDBClusterAvailable(clusterStatus, clusterIndetifier *string) bool { // For a full list of status values, see: // https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/accessing-monitoring.html - switch aws.StringValue(clusterStatus) { + switch aws.ToString(clusterStatus) { // Statuses marked as "Billed" in the above guide. case "active", "available", "backing-up", "backtracking", "failing-over", "maintenance", "migrating", "modifying", "promoting", "renaming", @@ -269,8 +269,8 @@ func IsDBClusterAvailable(clusterStatus, clusterIndetifier *string) bool { default: slog.WarnContext(context.Background(), "Assuming Aurora cluster with unknown status is available", - "status", aws.StringValue(clusterStatus), - "cluster", aws.StringValue(clusterIndetifier), + "status", aws.ToString(clusterStatus), + "cluster", aws.ToString(clusterIndetifier), ) return true } @@ -289,7 +289,7 @@ func IsRedshiftClusterAvailable(cluster *redshifttypes.Cluster) bool { // For "incompatible-xxx" statuses, the cluster is assumed to be available // if the status is resulted by modifying the cluster, and the cluster is // assumed to be unavailable if the cluster cannot be created or restored. - switch aws.StringValue(cluster.ClusterStatus) { + switch aws.ToString(cluster.ClusterStatus) { //nolint:misspell // cancelling is marked as non-existing word case "available", "available, prep-for-resize", "available, resize-cleanup", "cancelling-resize", "final-snapshot", "modifying", "rebooting", @@ -303,8 +303,8 @@ func IsRedshiftClusterAvailable(cluster *redshifttypes.Cluster) bool { default: slog.WarnContext(context.Background(), "Assuming Redshift cluster with unknown status is available", - "status", aws.StringValue(cluster.ClusterStatus), - "cluster", aws.StringValue(cluster.ClusterIdentifier), + "status", aws.ToString(cluster.ClusterStatus), + "cluster", aws.ToString(cluster.ClusterIdentifier), ) return true } diff --git a/lib/cloud/aws/tags_helpers.go b/lib/cloud/aws/tags_helpers.go index a3e1b87ebbf4e..f49ce3bb83e5d 100644 --- a/lib/cloud/aws/tags_helpers.go +++ b/lib/cloud/aws/tags_helpers.go @@ -21,19 +21,16 @@ package aws import ( "context" "log/slog" - "slices" - ec2TypesV2 "github.com/aws/aws-sdk-go-v2/service/ec2/types" + "github.com/aws/aws-sdk-go-v2/aws" + ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" + ectypes "github.com/aws/aws-sdk-go-v2/service/elasticache/types" rdstypes "github.com/aws/aws-sdk-go-v2/service/rds/types" redshifttypes "github.com/aws/aws-sdk-go-v2/service/redshift/types" rsstypes "github.com/aws/aws-sdk-go-v2/service/redshiftserverless/types" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/ec2" - "github.com/aws/aws-sdk-go/service/elasticache" "github.com/aws/aws-sdk-go/service/memorydb" "github.com/aws/aws-sdk-go/service/opensearchservice" "github.com/aws/aws-sdk-go/service/secretsmanager" - "golang.org/x/exp/maps" "github.com/gravitational/teleport/api/types" ) @@ -43,10 +40,9 @@ type ResourceTag interface { // TODO Go generic does not allow access common fields yet. List all types // here and use a type switch for now. rdstypes.Tag | - ec2TypesV2.Tag | + ec2types.Tag | redshifttypes.Tag | - *ec2.Tag | - *elasticache.Tag | + ectypes.Tag | *memorydb.Tag | rsstypes.Tag | *opensearchservice.Tag | @@ -74,48 +70,23 @@ func TagsToLabels[Tag ResourceTag](tags []Tag) map[string]string { func resourceTagToKeyValue[Tag ResourceTag](tag Tag) (string, string) { switch v := any(tag).(type) { - case *ec2.Tag: - return aws.StringValue(v.Key), aws.StringValue(v.Value) - case *elasticache.Tag: - return aws.StringValue(v.Key), aws.StringValue(v.Value) + case ectypes.Tag: + return aws.ToString(v.Key), aws.ToString(v.Value) case *memorydb.Tag: - return aws.StringValue(v.Key), aws.StringValue(v.Value) + return aws.ToString(v.Key), aws.ToString(v.Value) case rsstypes.Tag: - return aws.StringValue(v.Key), aws.StringValue(v.Value) + return aws.ToString(v.Key), aws.ToString(v.Value) case rdstypes.Tag: - return aws.StringValue(v.Key), aws.StringValue(v.Value) - case ec2TypesV2.Tag: - return aws.StringValue(v.Key), aws.StringValue(v.Value) + return aws.ToString(v.Key), aws.ToString(v.Value) + case ec2types.Tag: + return aws.ToString(v.Key), aws.ToString(v.Value) case redshifttypes.Tag: - return aws.StringValue(v.Key), aws.StringValue(v.Value) + return aws.ToString(v.Key), aws.ToString(v.Value) case *opensearchservice.Tag: - return aws.StringValue(v.Key), aws.StringValue(v.Value) + return aws.ToString(v.Key), aws.ToString(v.Value) case *secretsmanager.Tag: - return aws.StringValue(v.Key), aws.StringValue(v.Value) + return aws.ToString(v.Key), aws.ToString(v.Value) default: return "", "" } } - -// SettableTag is a generic interface that represents an AWS resource tag with -// SetKey and SetValue functions. -type SettableTag[T any] interface { - SetKey(key string) *T - SetValue(Value string) *T - *T -} - -// LabelsToTags converts a label map to a list of AWS resource tags. -func LabelsToTags[T any, PT SettableTag[T]](labels map[string]string) (tags []*T) { - keys := maps.Keys(labels) - slices.Sort(keys) - - for _, key := range keys { - tag := PT(new(T)) - tag.SetKey(key) - tag.SetValue(labels[key]) - - tags = append(tags, (*T)(tag)) - } - return -} diff --git a/lib/cloud/aws/tags_helpers_test.go b/lib/cloud/aws/tags_helpers_test.go index 0bc75677fefbd..cdaa0fa8bfe33 100644 --- a/lib/cloud/aws/tags_helpers_test.go +++ b/lib/cloud/aws/tags_helpers_test.go @@ -21,11 +21,9 @@ package aws import ( "testing" - rdsTypesV2 "github.com/aws/aws-sdk-go-v2/service/rds/types" + "github.com/aws/aws-sdk-go-v2/aws" + ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" rdstypes "github.com/aws/aws-sdk-go-v2/service/rds/types" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/ec2" - "github.com/aws/aws-sdk-go/service/elasticache" "github.com/stretchr/testify/require" ) @@ -59,7 +57,7 @@ func TestTagsToLabels(t *testing.T) { }) t.Run("ec2", func(t *testing.T) { - inputTags := []*ec2.Tag{ + inputTags := []ec2types.Tag{ { Key: aws.String("Env"), Value: aws.String("dev"), @@ -83,56 +81,4 @@ func TestTagsToLabels(t *testing.T) { actuallabels := TagsToLabels(inputTags) require.Equal(t, expectLabels, actuallabels) }) - - t.Run("rdsV2", func(t *testing.T) { - inputTags := []rdsTypesV2.Tag{ - { - Key: aws.String("Env"), - Value: aws.String("dev"), - }, - { - Key: aws.String("aws:cloudformation:stack-id"), - Value: aws.String("some-id"), - }, - { - Key: aws.String("Name"), - Value: aws.String("test"), - }, - } - - expectLabels := map[string]string{ - "Name": "test", - "Env": "dev", - "aws:cloudformation:stack-id": "some-id", - } - - actuallabels := TagsToLabels(inputTags) - require.Equal(t, expectLabels, actuallabels) - }) - -} - -func TestLabelsToTags(t *testing.T) { - t.Parallel() - - t.Run("elasticcache", func(t *testing.T) { - inputLabels := map[string]string{ - "labelB": "valueB", - "labelA": "valueA", - } - - expectTags := []*elasticache.Tag{ - { - Key: aws.String("labelA"), - Value: aws.String("valueA"), - }, - { - Key: aws.String("labelB"), - Value: aws.String("valueB"), - }, - } - - actualTags := LabelsToTags[elasticache.Tag](inputLabels) - require.Equal(t, expectTags, actualTags) - }) } diff --git a/lib/cloud/awstesthelpers/tags.go b/lib/cloud/awstesthelpers/tags.go index 85f7700e58e36..3d80436dca91e 100644 --- a/lib/cloud/awstesthelpers/tags.go +++ b/lib/cloud/awstesthelpers/tags.go @@ -22,6 +22,7 @@ import ( "maps" "slices" + ectypes "github.com/aws/aws-sdk-go-v2/service/elasticache/types" rdstypes "github.com/aws/aws-sdk-go-v2/service/rds/types" redshifttypes "github.com/aws/aws-sdk-go-v2/service/redshift/types" rsstypes "github.com/aws/aws-sdk-go-v2/service/redshiftserverless/types" @@ -59,3 +60,10 @@ func LabelsToRedshiftServerlessTags(labels map[string]string) []rsstypes.Tag { return rsstypes.Tag{Key: &key, Value: &value} }) } + +// LabelsToElastiCacheTags converts labels into a [ectypes.Tag] list. +func LabelsToElastiCacheTags(labels map[string]string) []ectypes.Tag { + return LabelsToTags(labels, func(key, value string) ectypes.Tag { + return ectypes.Tag{Key: &key, Value: &value} + }) +} diff --git a/lib/cloud/clients.go b/lib/cloud/clients.go index 79ba6557b8f47..a8ceb5a34a605 100644 --- a/lib/cloud/clients.go +++ b/lib/cloud/clients.go @@ -39,8 +39,6 @@ import ( "github.com/aws/aws-sdk-go/aws/endpoints" "github.com/aws/aws-sdk-go/aws/request" awssession "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/elasticache" - "github.com/aws/aws-sdk-go/service/elasticache/elasticacheiface" "github.com/aws/aws-sdk-go/service/iam" "github.com/aws/aws-sdk-go/service/iam/iamiface" "github.com/aws/aws-sdk-go/service/kms" @@ -105,8 +103,6 @@ type GCPClients interface { type AWSClients interface { // GetAWSSession returns AWS session for the specified region and any role(s). GetAWSSession(ctx context.Context, region string, opts ...AWSOptionsFn) (*awssession.Session, error) - // GetAWSElastiCacheClient returns AWS ElastiCache client for the specified region. - GetAWSElastiCacheClient(ctx context.Context, region string, opts ...AWSOptionsFn) (elasticacheiface.ElastiCacheAPI, error) // GetAWSMemoryDBClient returns AWS MemoryDB client for the specified region. GetAWSMemoryDBClient(ctx context.Context, region string, opts ...AWSOptionsFn) (memorydbiface.MemoryDBAPI, error) // GetAWSOpenSearchClient returns AWS OpenSearch client for the specified region. @@ -496,15 +492,6 @@ func (c *cloudClients) GetAWSSession(ctx context.Context, region string, opts .. return c.getAWSSessionForRole(ctx, region, options) } -// GetAWSElastiCacheClient returns AWS ElastiCache client for the specified region. -func (c *cloudClients) GetAWSElastiCacheClient(ctx context.Context, region string, opts ...AWSOptionsFn) (elasticacheiface.ElastiCacheAPI, error) { - session, err := c.GetAWSSession(ctx, region, opts...) - if err != nil { - return nil, trace.Wrap(err) - } - return elasticache.New(session), nil -} - // GetAWSOpenSearchClient returns AWS OpenSearch client for the specified region. func (c *cloudClients) GetAWSOpenSearchClient(ctx context.Context, region string, opts ...AWSOptionsFn) (opensearchserviceiface.OpenSearchServiceAPI, error) { session, err := c.GetAWSSession(ctx, region, opts...) @@ -993,7 +980,6 @@ var _ Clients = (*TestCloudClients)(nil) // TestCloudClients are used in tests. type TestCloudClients struct { - ElastiCache elasticacheiface.ElastiCacheAPI OpenSearch opensearchserviceiface.OpenSearchServiceAPI MemoryDB memorydbiface.MemoryDBAPI SecretsManager secretsmanageriface.SecretsManagerAPI @@ -1062,15 +1048,6 @@ func (c *TestCloudClients) getAWSSessionForRegion(region string) (*awssession.Se }) } -// GetAWSElastiCacheClient returns AWS ElastiCache client for the specified region. -func (c *TestCloudClients) GetAWSElastiCacheClient(ctx context.Context, region string, opts ...AWSOptionsFn) (elasticacheiface.ElastiCacheAPI, error) { - _, err := c.GetAWSSession(ctx, region, opts...) - if err != nil { - return nil, trace.Wrap(err) - } - return c.ElastiCache, nil -} - // GetAWSOpenSearchClient returns AWS OpenSearch client for the specified region. func (c *TestCloudClients) GetAWSOpenSearchClient(ctx context.Context, region string, opts ...AWSOptionsFn) (opensearchserviceiface.OpenSearchServiceAPI, error) { _, err := c.GetAWSSession(ctx, region, opts...) diff --git a/lib/cloud/mocks/aws_elasticache.go b/lib/cloud/mocks/aws_elasticache.go index 14d9955e743ef..e750bd56a0336 100644 --- a/lib/cloud/mocks/aws_elasticache.go +++ b/lib/cloud/mocks/aws_elasticache.go @@ -19,39 +19,38 @@ package mocks import ( + "context" "fmt" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/request" - "github.com/aws/aws-sdk-go/service/elasticache" - "github.com/aws/aws-sdk-go/service/elasticache/elasticacheiface" + "github.com/aws/aws-sdk-go-v2/aws" + elasticache "github.com/aws/aws-sdk-go-v2/service/elasticache" + ectypes "github.com/aws/aws-sdk-go-v2/service/elasticache/types" "github.com/gravitational/trace" ) // ElastiCache mocks AWS ElastiCache API. -type ElastiCacheMock struct { - elasticacheiface.ElastiCacheAPI +type ElastiCacheClient struct { // Unauth set to true will make API calls return unauthorized errors. Unauth bool - ReplicationGroups []*elasticache.ReplicationGroup - Users []*elasticache.User - TagsByARN map[string][]*elasticache.Tag + ReplicationGroups []ectypes.ReplicationGroup + Users []ectypes.User + TagsByARN map[string][]ectypes.Tag } -func (m *ElastiCacheMock) AddMockUser(user *elasticache.User, tagsMap map[string]string) { +func (m *ElastiCacheClient) AddMockUser(user ectypes.User, tagsMap map[string]string) { m.Users = append(m.Users, user) - m.addTags(aws.StringValue(user.ARN), tagsMap) + m.addTags(aws.ToString(user.ARN), tagsMap) } -func (m *ElastiCacheMock) addTags(arn string, tagsMap map[string]string) { +func (m *ElastiCacheClient) addTags(arn string, tagsMap map[string]string) { if m.TagsByARN == nil { - m.TagsByARN = make(map[string][]*elasticache.Tag) + m.TagsByARN = make(map[string][]ectypes.Tag) } - var tags []*elasticache.Tag + var tags []ectypes.Tag for key, value := range tagsMap { - tags = append(tags, &elasticache.Tag{ + tags = append(tags, ectypes.Tag{ Key: aws.String(key), Value: aws.String(value), }) @@ -59,7 +58,7 @@ func (m *ElastiCacheMock) addTags(arn string, tagsMap map[string]string) { m.TagsByARN[arn] = tags } -func (m *ElastiCacheMock) DescribeUsersWithContext(_ aws.Context, input *elasticache.DescribeUsersInput, opts ...request.Option) (*elasticache.DescribeUsersOutput, error) { +func (m *ElastiCacheClient) DescribeUsers(_ context.Context, input *elasticache.DescribeUsersInput, opts ...func(*elasticache.Options)) (*elasticache.DescribeUsersOutput, error) { if m.Unauth { return nil, trace.AccessDenied("unauthorized") } @@ -67,62 +66,47 @@ func (m *ElastiCacheMock) DescribeUsersWithContext(_ aws.Context, input *elastic return &elasticache.DescribeUsersOutput{Users: m.Users}, nil } for _, user := range m.Users { - if aws.StringValue(user.UserId) == aws.StringValue(input.UserId) { - return &elasticache.DescribeUsersOutput{Users: []*elasticache.User{user}}, nil + if aws.ToString(user.UserId) == aws.ToString(input.UserId) { + return &elasticache.DescribeUsersOutput{Users: []ectypes.User{user}}, nil } } - return nil, trace.NotFound("ElastiCache UserId %v not found", aws.StringValue(input.UserId)) + return nil, trace.NotFound("ElastiCache UserId %q not found", aws.ToString(input.UserId)) } -func (m *ElastiCacheMock) DescribeReplicationGroupsWithContext(_ aws.Context, input *elasticache.DescribeReplicationGroupsInput, opts ...request.Option) (*elasticache.DescribeReplicationGroupsOutput, error) { +func (m *ElastiCacheClient) DescribeReplicationGroups(_ context.Context, input *elasticache.DescribeReplicationGroupsInput, opts ...func(*elasticache.Options)) (*elasticache.DescribeReplicationGroupsOutput, error) { if m.Unauth { return nil, trace.AccessDenied("unauthorized") } + if input.ReplicationGroupId == nil { + return &elasticache.DescribeReplicationGroupsOutput{ + ReplicationGroups: m.ReplicationGroups, + }, nil + } for _, replicationGroup := range m.ReplicationGroups { - if aws.StringValue(replicationGroup.ReplicationGroupId) == aws.StringValue(input.ReplicationGroupId) { + if aws.ToString(replicationGroup.ReplicationGroupId) == aws.ToString(input.ReplicationGroupId) { return &elasticache.DescribeReplicationGroupsOutput{ - ReplicationGroups: []*elasticache.ReplicationGroup{replicationGroup}, + ReplicationGroups: []ectypes.ReplicationGroup{replicationGroup}, }, nil } } - return nil, trace.NotFound("ElastiCache %v not found", aws.StringValue(input.ReplicationGroupId)) -} - -func (m *ElastiCacheMock) DescribeReplicationGroupsPagesWithContext(_ aws.Context, _ *elasticache.DescribeReplicationGroupsInput, fn func(*elasticache.DescribeReplicationGroupsOutput, bool) bool, _ ...request.Option) error { - if m.Unauth { - return trace.AccessDenied("unauthorized") - } - fn(&elasticache.DescribeReplicationGroupsOutput{ - ReplicationGroups: m.ReplicationGroups, - }, true) - return nil + return nil, trace.NotFound("ElastiCache ReplicationGroupId %q not found", aws.ToString(input.ReplicationGroupId)) } -func (m *ElastiCacheMock) DescribeUsersPagesWithContext(_ aws.Context, _ *elasticache.DescribeUsersInput, fn func(*elasticache.DescribeUsersOutput, bool) bool, _ ...request.Option) error { +func (m *ElastiCacheClient) DescribeCacheClusters(context.Context, *elasticache.DescribeCacheClustersInput, ...func(*elasticache.Options)) (*elasticache.DescribeCacheClustersOutput, error) { if m.Unauth { - return trace.AccessDenied("unauthorized") - } - fn(&elasticache.DescribeUsersOutput{ - Users: m.Users, - }, true) - return nil -} - -func (m *ElastiCacheMock) DescribeCacheClustersPagesWithContext(aws.Context, *elasticache.DescribeCacheClustersInput, func(*elasticache.DescribeCacheClustersOutput, bool) bool, ...request.Option) error { - if m.Unauth { - return trace.AccessDenied("unauthorized") + return nil, trace.AccessDenied("unauthorized") } - return trace.NotImplemented("elasticache:DescribeCacheClustersPagesWithContext is not implemented") + return nil, trace.NotImplemented("elasticache:DescribeCacheClusters is not implemented") } -func (m *ElastiCacheMock) DescribeCacheSubnetGroupsPagesWithContext(aws.Context, *elasticache.DescribeCacheSubnetGroupsInput, func(*elasticache.DescribeCacheSubnetGroupsOutput, bool) bool, ...request.Option) error { +func (m *ElastiCacheClient) DescribeCacheSubnetGroups(context.Context, *elasticache.DescribeCacheSubnetGroupsInput, ...func(*elasticache.Options)) (*elasticache.DescribeCacheSubnetGroupsOutput, error) { if m.Unauth { - return trace.AccessDenied("unauthorized") + return nil, trace.AccessDenied("unauthorized") } - return trace.NotImplemented("elasticache:DescribeCacheSubnetGroupsPagesWithContext is not implemented") + return nil, trace.NotImplemented("elasticache:DescribeCacheSubnetGroups is not implemented") } -func (m *ElastiCacheMock) ListTagsForResourceWithContext(_ aws.Context, input *elasticache.ListTagsForResourceInput, _ ...request.Option) (*elasticache.TagListMessage, error) { +func (m *ElastiCacheClient) ListTagsForResource(_ context.Context, input *elasticache.ListTagsForResourceInput, _ ...func(*elasticache.Options)) (*elasticache.ListTagsForResourceOutput, error) { if m.Unauth { return nil, trace.AccessDenied("unauthorized") } @@ -130,41 +114,41 @@ func (m *ElastiCacheMock) ListTagsForResourceWithContext(_ aws.Context, input *e return nil, trace.NotFound("no tags") } - tags, ok := m.TagsByARN[aws.StringValue(input.ResourceName)] + tags, ok := m.TagsByARN[aws.ToString(input.ResourceName)] if !ok { return nil, trace.NotFound("no tags") } - return &elasticache.TagListMessage{ + return &elasticache.ListTagsForResourceOutput{ TagList: tags, }, nil } -func (m *ElastiCacheMock) ModifyUserWithContext(_ aws.Context, input *elasticache.ModifyUserInput, opts ...request.Option) (*elasticache.ModifyUserOutput, error) { +func (m *ElastiCacheClient) ModifyUser(_ context.Context, input *elasticache.ModifyUserInput, opts ...func(*elasticache.Options)) (*elasticache.ModifyUserOutput, error) { if m.Unauth { return nil, trace.AccessDenied("unauthorized") } for _, user := range m.Users { - if aws.StringValue(user.UserId) == aws.StringValue(input.UserId) { + if aws.ToString(user.UserId) == aws.ToString(input.UserId) { return &elasticache.ModifyUserOutput{}, nil } } - return nil, trace.NotFound("user %s not found", aws.StringValue(input.UserId)) + return nil, trace.NotFound("ElastiCache UserId %q not found", aws.ToString(input.UserId)) } -// ElastiCacheCluster returns a sample elasticache.ReplicationGroup. -func ElastiCacheCluster(name, region string, opts ...func(*elasticache.ReplicationGroup)) *elasticache.ReplicationGroup { - cluster := &elasticache.ReplicationGroup{ +// ElastiCacheCluster returns a sample ectypes.ReplicationGroup. +func ElastiCacheCluster(name, region string, opts ...func(*ectypes.ReplicationGroup)) *ectypes.ReplicationGroup { + cluster := &ectypes.ReplicationGroup{ ARN: aws.String(fmt.Sprintf("arn:aws:elasticache:%s:123456789012:replicationgroup:%s", region, name)), ReplicationGroupId: aws.String(name), Status: aws.String("available"), TransitEncryptionEnabled: aws.Bool(true), // Default has one primary endpoint in the only node group. - NodeGroups: []*elasticache.NodeGroup{{ - PrimaryEndpoint: &elasticache.Endpoint{ + NodeGroups: []ectypes.NodeGroup{{ + PrimaryEndpoint: &ectypes.Endpoint{ Address: aws.String(fmt.Sprintf("master.%v-cluster.xxxxxx.use1.cache.amazonaws.com", name)), - Port: aws.Int64(6379), + Port: aws.Int32(6379), }, }}, } @@ -177,21 +161,21 @@ func ElastiCacheCluster(name, region string, opts ...func(*elasticache.Replicati // WithElastiCacheReaderEndpoint is an option function for // MakeElastiCacheCluster to set a reader endpoint. -func WithElastiCacheReaderEndpoint(cluster *elasticache.ReplicationGroup) { - cluster.NodeGroups = append(cluster.NodeGroups, &elasticache.NodeGroup{ - ReaderEndpoint: &elasticache.Endpoint{ - Address: aws.String(fmt.Sprintf("replica.%v-cluster.xxxxxx.use1.cache.amazonaws.com", aws.StringValue(cluster.ReplicationGroupId))), - Port: aws.Int64(6379), +func WithElastiCacheReaderEndpoint(cluster *ectypes.ReplicationGroup) { + cluster.NodeGroups = append(cluster.NodeGroups, ectypes.NodeGroup{ + ReaderEndpoint: &ectypes.Endpoint{ + Address: aws.String(fmt.Sprintf("replica.%v-cluster.xxxxxx.use1.cache.amazonaws.com", aws.ToString(cluster.ReplicationGroupId))), + Port: aws.Int32(6379), }, }) } // WithElastiCacheConfigurationEndpoint in an option function for // MakeElastiCacheCluster to set a configuration endpoint. -func WithElastiCacheConfigurationEndpoint(cluster *elasticache.ReplicationGroup) { +func WithElastiCacheConfigurationEndpoint(cluster *ectypes.ReplicationGroup) { cluster.ClusterEnabled = aws.Bool(true) - cluster.ConfigurationEndpoint = &elasticache.Endpoint{ - Address: aws.String(fmt.Sprintf("clustercfg.%v-shards.xxxxxx.use1.cache.amazonaws.com", aws.StringValue(cluster.ReplicationGroupId))), - Port: aws.Int64(6379), + cluster.ConfigurationEndpoint = &ectypes.Endpoint{ + Address: aws.String(fmt.Sprintf("clustercfg.%v-shards.xxxxxx.use1.cache.amazonaws.com", aws.ToString(cluster.ReplicationGroupId))), + Port: aws.Int32(6379), } } diff --git a/lib/srv/db/access_test.go b/lib/srv/db/access_test.go index ee02ee02d903b..b936670da0817 100644 --- a/lib/srv/db/access_test.go +++ b/lib/srv/db/access_test.go @@ -2439,8 +2439,6 @@ type agentParams struct { NoStart bool // GCPSQL defines the GCP Cloud SQL mock to use for GCP API calls. GCPSQL *mocks.GCPSQLAdminClientMock - // ElastiCache defines the AWS ElastiCache mock to use for ElastiCache API calls. - ElastiCache *mocks.ElastiCacheMock // MemoryDB defines the AWS MemoryDB mock to use for MemoryDB API calls. MemoryDB *mocks.MemoryDBMock // OnHeartbeat defines a heartbeat function that generates heartbeat events. @@ -2479,9 +2477,6 @@ func (p *agentParams) setDefaults(c *testContext) { }, } } - if p.ElastiCache == nil { - p.ElastiCache = &mocks.ElastiCacheMock{} - } if p.MemoryDB == nil { p.MemoryDB = &mocks.MemoryDBMock{} } @@ -2494,7 +2489,6 @@ func (p *agentParams) setDefaults(c *testContext) { if p.CloudClients == nil { p.CloudClients = &clients.TestCloudClients{ STS: &mocks.STSClientV1{}, - ElastiCache: p.ElastiCache, MemoryDB: p.MemoryDB, SecretsManager: secrets.NewMockSecretsManagerClient(secrets.MockSecretsManagerClientConfig{}), IAM: &mocks.IAMMock{}, @@ -2540,7 +2534,7 @@ func (c *testContext) setupDatabaseServer(ctx context.Context, t testing.TB, p a AccessPoint: c.authClient, Clients: &clients.TestCloudClients{}, Clock: c.clock, - AWSConfigProvider: &mocks.AWSConfigProvider{}, + AWSConfigProvider: p.AWSConfigProvider, }) require.NoError(t, err) diff --git a/lib/srv/db/auth_test.go b/lib/srv/db/auth_test.go index f89cd83d4bf29..a956a90713c46 100644 --- a/lib/srv/db/auth_test.go +++ b/lib/srv/db/auth_test.go @@ -25,8 +25,9 @@ import ( "testing" "time" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/elasticache" + "github.com/aws/aws-sdk-go-v2/aws" + elasticache "github.com/aws/aws-sdk-go-v2/service/elasticache" + ectypes "github.com/aws/aws-sdk-go-v2/service/elasticache/types" "github.com/aws/aws-sdk-go/service/memorydb" "github.com/gravitational/trace" "github.com/stretchr/testify/require" @@ -39,6 +40,7 @@ import ( "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/fixtures" "github.com/gravitational/teleport/lib/srv/db/common" + "github.com/gravitational/teleport/lib/srv/db/redis" ) // TestAuthTokens verifies that proper IAM auth tokens are used when connecting @@ -74,12 +76,16 @@ func TestAuthTokens(t *testing.T) { for _, withDB := range withDBs { databases = append(databases, withDB(t, ctx, testCtx)) } - ecMock := &mocks.ElastiCacheMock{} - elastiCacheIAMUser := &elasticache.User{ + ecMock := &mocks.ElastiCacheClient{} + elastiCacheIAMUser := ectypes.User{ UserId: aws.String("default"), - Authentication: &elasticache.Authentication{Type: aws.String("iam")}, + Authentication: &ectypes.Authentication{Type: ectypes.AuthenticationTypeIam}, } ecMock.AddMockUser(elastiCacheIAMUser, nil) + registerTestRedisEngine(t, fakeRedisAWSClients{ + ecClient: ecMock, + }) + memorydbMock := &mocks.MemoryDBMock{} memorydbIAMUser := &memorydb.User{ Name: aws.String("default"), @@ -87,9 +93,8 @@ func TestAuthTokens(t *testing.T) { } memorydbMock.AddMockUser(memorydbIAMUser, nil) testCtx.server = testCtx.setupDatabaseServer(ctx, t, agentParams{ - Databases: databases, - ElastiCache: ecMock, - MemoryDB: memorydbMock, + Databases: databases, + MemoryDB: memorydbMock, }) go testCtx.startHandlingConnections() @@ -468,3 +473,35 @@ func TestMongoDBAtlas(t *testing.T) { }) } } + +func registerTestRedisEngine(t *testing.T, awsClients redis.AWSClientProvider) { + t.Helper() + // To prevent tests running in parallel with this engine, call t.Setenv with + // a dummy value. + t.Setenv("WithTestRedisEngine", "1") + oldEngineFn := common.GetEngineFn(defaults.ProtocolRedis) + t.Cleanup(func() { + common.RegisterEngine(oldEngineFn, defaults.ProtocolRedis) + }) + // Override Redis engine that is used normally with the test one that mocks + // AWS clients. + common.RegisterEngine(newTestRedisEngine(awsClients), defaults.ProtocolRedis) +} + +func newTestRedisEngine(awsClients redis.AWSClientProvider) common.EngineFn { + return func(ec common.EngineConfig) common.Engine { + ec.AWSConfigProvider = &mocks.AWSConfigProvider{} + return &redis.Engine{ + EngineConfig: ec, + AWSClients: awsClients, + } + } +} + +type fakeRedisAWSClients struct { + ecClient redis.ElastiCacheClient +} + +func (f fakeRedisAWSClients) GetElastiCacheClient(cfg aws.Config, optFns ...func(*elasticache.Options)) redis.ElastiCacheClient { + return f.ecClient +} diff --git a/lib/srv/db/cloud/iam_test.go b/lib/srv/db/cloud/iam_test.go index 36397d6a64727..caa6ed7d72500 100644 --- a/lib/srv/db/cloud/iam_test.go +++ b/lib/srv/db/cloud/iam_test.go @@ -24,8 +24,8 @@ import ( "testing" "time" + "github.com/aws/aws-sdk-go-v2/aws" rdstypes "github.com/aws/aws-sdk-go-v2/service/rds/types" - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/iam" "github.com/google/uuid" @@ -109,7 +109,7 @@ func TestAWSIAM(t *testing.T) { }) require.NoError(t, err) - elasticache, err := types.NewDatabaseV3(types.Metadata{ + elasticacheDB, err := types.NewDatabaseV3(types.Metadata{ Name: "aws-elasticache", }, types.DatabaseSpecV3{ Protocol: "redis", @@ -185,7 +185,7 @@ func TestAWSIAM(t *testing.T) { wantPolicyContains: rdsDatabase.GetAWS().RDS.ResourceID, getIAMAuthEnabled: func() bool { rdsInstance := &clt.DBInstances[0] - out := aws.BoolValue(rdsInstance.IAMDatabaseAuthenticationEnabled) + out := aws.ToBool(rdsInstance.IAMDatabaseAuthenticationEnabled) // reset it rdsInstance.IAMDatabaseAuthenticationEnabled = aws.Bool(false) return out @@ -196,7 +196,7 @@ func TestAWSIAM(t *testing.T) { wantPolicyContains: auroraDatabase.GetAWS().RDS.ResourceID, getIAMAuthEnabled: func() bool { auroraCluster := &clt.DBClusters[0] - out := aws.BoolValue(auroraCluster.IAMDatabaseAuthenticationEnabled) + out := aws.ToBool(auroraCluster.IAMDatabaseAuthenticationEnabled) // reset it auroraCluster.IAMDatabaseAuthenticationEnabled = aws.Bool(false) return out @@ -217,8 +217,8 @@ func TestAWSIAM(t *testing.T) { }, }, "ElastiCache": { - database: elasticache, - wantPolicyContains: elasticache.GetAWS().ElastiCache.ReplicationGroupID, + database: elasticacheDB, + wantPolicyContains: elasticacheDB.GetAWS().ElastiCache.ReplicationGroupID, getIAMAuthEnabled: func() bool { return true // it always is for ElastiCache. }, @@ -258,7 +258,7 @@ func TestAWSIAM(t *testing.T) { output, err := iamClient.GetRolePolicyWithContext(ctx, getRolePolicyInput) require.NoError(t, err) require.True(t, tt.getIAMAuthEnabled()) - require.Contains(t, aws.StringValue(output.PolicyDocument), tt.wantPolicyContains) + require.Contains(t, aws.ToString(output.PolicyDocument), tt.wantPolicyContains) err = configurator.UpdateIAMStatus(ctx, database) require.NoError(t, err) @@ -295,24 +295,6 @@ func TestAWSIAMNoPermissions(t *testing.T) { stsClient := &mocks.STSClientV1{ ARN: "arn:aws:iam::123456789012:role/test-role", } - // Make configurator. - configurator, err := NewIAM(ctx, IAMConfig{ - AccessPoint: &mockAccessPoint{}, - Clients: &clients.TestCloudClients{}, // placeholder, - HostID: "host-id", - AWSConfigProvider: &mocks.AWSConfigProvider{ - STSClient: &mocks.STSClient{ - STSClientV1: mocks.STSClientV1{ - ARN: "arn:aws:iam::123456789012:role/test-role", - }, - }, - }, - awsClients: fakeAWSClients{ - rdsClient: &mocks.RDSClient{Unauth: true}, - }, - }) - require.NoError(t, err) - tests := []struct { name string meta types.AWS @@ -362,9 +344,6 @@ func TestAWSIAMNoPermissions(t *testing.T) { name: "ElastiCache", meta: types.AWS{Region: "localhost", AccountID: "123456789012", ElastiCache: types.ElastiCache{ReplicationGroupID: "some-group"}}, clients: &clients.TestCloudClients{ - // As of writing this API won't be called by the configurator anyway, - // but might as well provide it in case that changes. - ElastiCache: &mocks.ElastiCacheMock{Unauth: true}, IAM: &mocks.IAMErrorMock{ Error: trace.AccessDenied("unauthorized"), }, @@ -385,8 +364,23 @@ func TestAWSIAMNoPermissions(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - // Update cloud clients. - configurator.cfg.Clients = test.clients + // Make configurator. + configurator, err := NewIAM(ctx, IAMConfig{ + AccessPoint: &mockAccessPoint{}, + Clients: test.clients, + HostID: "host-id", + AWSConfigProvider: &mocks.AWSConfigProvider{ + STSClient: &mocks.STSClient{ + STSClientV1: mocks.STSClientV1{ + ARN: "arn:aws:iam::123456789012:role/test-role", + }, + }, + }, + awsClients: fakeAWSClients{ + rdsClient: &mocks.RDSClient{Unauth: true}, + }, + }) + require.NoError(t, err) database, err := types.NewDatabaseV3(types.Metadata{ Name: "test", diff --git a/lib/srv/db/cloud/meta.go b/lib/srv/db/cloud/meta.go index b57059da18ab7..9fe5bd2812589 100644 --- a/lib/srv/db/cloud/meta.go +++ b/lib/srv/db/cloud/meta.go @@ -24,14 +24,14 @@ import ( "strings" "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/elasticache" + ectypes "github.com/aws/aws-sdk-go-v2/service/elasticache/types" "github.com/aws/aws-sdk-go-v2/service/rds" rdstypes "github.com/aws/aws-sdk-go-v2/service/rds/types" "github.com/aws/aws-sdk-go-v2/service/redshift" redshifttypes "github.com/aws/aws-sdk-go-v2/service/redshift/types" rss "github.com/aws/aws-sdk-go-v2/service/redshiftserverless" rsstypes "github.com/aws/aws-sdk-go-v2/service/redshiftserverless/types" - "github.com/aws/aws-sdk-go/service/elasticache" - "github.com/aws/aws-sdk-go/service/elasticache/elasticacheiface" "github.com/aws/aws-sdk-go/service/memorydb" "github.com/aws/aws-sdk-go/service/memorydb/memorydbiface" "github.com/gravitational/trace" @@ -45,6 +45,11 @@ import ( logutils "github.com/gravitational/teleport/lib/utils/log" ) +// elasticacheClient defines a subset of the AWS ElastiCache client API. +type elasticacheClient interface { + elasticache.DescribeReplicationGroupsAPIClient +} + // rdsClient defines a subset of the AWS RDS client API. type rdsClient interface { rds.DescribeDBClustersAPIClient @@ -68,6 +73,7 @@ type rssClient interface { // awsClientProvider is an AWS SDK client provider. type awsClientProvider interface { + getElastiCacheClient(cfg aws.Config, optFns ...func(*elasticache.Options)) elasticacheClient getRDSClient(cfg aws.Config, optFns ...func(*rds.Options)) rdsClient getRedshiftClient(cfg aws.Config, optFns ...func(*redshift.Options)) redshiftClient getRedshiftServerlessClient(cfg aws.Config, optFns ...func(*rss.Options)) rssClient @@ -75,6 +81,10 @@ type awsClientProvider interface { type defaultAWSClients struct{} +func (defaultAWSClients) getElastiCacheClient(cfg aws.Config, optFns ...func(*elasticache.Options)) elasticacheClient { + return elasticache.NewFromConfig(cfg, optFns...) +} + func (defaultAWSClients) getRDSClient(cfg aws.Config, optFns ...func(*rds.Options)) rdsClient { return rds.NewFromConfig(cfg, optFns...) } @@ -271,14 +281,15 @@ func (m *Metadata) fetchRedshiftServerlessMetadata(ctx context.Context, database // fetchElastiCacheMetadata fetches metadata for the provided ElastiCache database. func (m *Metadata) fetchElastiCacheMetadata(ctx context.Context, database types.Database) (*types.AWS, error) { meta := database.GetAWS() - elastiCacheClient, err := m.cfg.Clients.GetAWSElastiCacheClient(ctx, meta.Region, - cloud.WithAssumeRoleFromAWSMeta(meta), - cloud.WithAmbientCredentials(), + awsCfg, err := m.cfg.AWSConfigProvider.GetConfig(ctx, meta.Region, + awsconfig.WithAssumeRole(meta.AssumeRoleARN, meta.ExternalID), + awsconfig.WithAmbientCredentials(), ) if err != nil { return nil, trace.Wrap(err) } - cluster, err := describeElastiCacheCluster(ctx, elastiCacheClient, meta.ElastiCache.ReplicationGroupID) + clt := m.cfg.awsClients.getElastiCacheClient(awsCfg) + cluster, err := describeElastiCacheCluster(ctx, clt, meta.ElastiCache.ReplicationGroupID) if err != nil { return nil, trace.Wrap(err) } @@ -370,8 +381,8 @@ func describeRedshiftCluster(ctx context.Context, clt redshiftClient, clusterID // describeElastiCacheCluster returns AWS ElastiCache Redis cluster for the // specified ID. -func describeElastiCacheCluster(ctx context.Context, elastiCacheClient elasticacheiface.ElastiCacheAPI, replicationGroupID string) (*elasticache.ReplicationGroup, error) { - out, err := elastiCacheClient.DescribeReplicationGroupsWithContext(ctx, &elasticache.DescribeReplicationGroupsInput{ +func describeElastiCacheCluster(ctx context.Context, elastiCacheClient elasticacheClient, replicationGroupID string) (*ectypes.ReplicationGroup, error) { + out, err := elastiCacheClient.DescribeReplicationGroups(ctx, &elasticache.DescribeReplicationGroupsInput{ ReplicationGroupId: aws.String(replicationGroupID), }) if err != nil { @@ -380,7 +391,7 @@ func describeElastiCacheCluster(ctx context.Context, elastiCacheClient elasticac if len(out.ReplicationGroups) != 1 { return nil, trace.BadParameter("expected 1 ElastiCache cluster for %v, got %+v", replicationGroupID, out.ReplicationGroups) } - return out.ReplicationGroups[0], nil + return &out.ReplicationGroups[0], nil } // describeMemoryDBCluster returns AWS MemoryDB cluster for the specified ID. diff --git a/lib/srv/db/cloud/meta_test.go b/lib/srv/db/cloud/meta_test.go index 9cbefec8457b9..3c212fc800ae0 100644 --- a/lib/srv/db/cloud/meta_test.go +++ b/lib/srv/db/cloud/meta_test.go @@ -23,13 +23,14 @@ import ( "testing" "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/elasticache" + ectypes "github.com/aws/aws-sdk-go-v2/service/elasticache/types" "github.com/aws/aws-sdk-go-v2/service/rds" rdstypes "github.com/aws/aws-sdk-go-v2/service/rds/types" "github.com/aws/aws-sdk-go-v2/service/redshift" redshifttypes "github.com/aws/aws-sdk-go-v2/service/redshift/types" rss "github.com/aws/aws-sdk-go-v2/service/redshiftserverless" rsstypes "github.com/aws/aws-sdk-go-v2/service/redshiftserverless/types" - "github.com/aws/aws-sdk-go/service/elasticache" "github.com/aws/aws-sdk-go/service/memorydb" "github.com/stretchr/testify/require" @@ -96,14 +97,14 @@ func TestAWSMetadata(t *testing.T) { } // Configure ElastiCache API mock. - elasticache := &mocks.ElastiCacheMock{ - ReplicationGroups: []*elasticache.ReplicationGroup{ + ecClient := &mocks.ElastiCacheClient{ + ReplicationGroups: []ectypes.ReplicationGroup{ { ARN: aws.String("arn:aws:elasticache:us-west-1:123456789012:replicationgroup:my-redis"), ReplicationGroupId: aws.String("my-redis"), ClusterEnabled: aws.Bool(true), TransitEncryptionEnabled: aws.Bool(true), - UserGroupIds: []*string{aws.String("my-user-group")}, + UserGroupIds: []string{"my-user-group"}, }, }, } @@ -133,14 +134,14 @@ func TestAWSMetadata(t *testing.T) { // Create metadata fetcher. metadata, err := NewMetadata(MetadataConfig{ Clients: &cloud.TestCloudClients{ - ElastiCache: elasticache, - MemoryDB: memorydb, - STS: &fakeSTS.STSClientV1, + MemoryDB: memorydb, + STS: &fakeSTS.STSClientV1, }, AWSConfigProvider: &mocks.AWSConfigProvider{ STSClient: fakeSTS, }, awsClients: fakeAWSClients{ + ecClient: ecClient, rdsClient: rdsClt, redshiftClient: redshiftClt, rssClient: redshiftServerless, @@ -502,11 +503,16 @@ func TestAWSMetadataNoPermissions(t *testing.T) { } type fakeAWSClients struct { + ecClient elasticacheClient rdsClient rdsClient redshiftClient redshiftClient rssClient rssClient } +func (f fakeAWSClients) getElastiCacheClient(cfg aws.Config, optFns ...func(*elasticache.Options)) elasticacheClient { + return f.ecClient +} + func (f fakeAWSClients) getRDSClient(aws.Config, ...func(*rds.Options)) rdsClient { return f.rdsClient } diff --git a/lib/srv/db/cloud/resource_checker_url_aws.go b/lib/srv/db/cloud/resource_checker_url_aws.go index f5de449ec2c65..7f34f481e95ee 100644 --- a/lib/srv/db/cloud/resource_checker_url_aws.go +++ b/lib/srv/db/cloud/resource_checker_url_aws.go @@ -21,8 +21,8 @@ package cloud import ( "context" + "github.com/aws/aws-sdk-go-v2/aws" rdstypes "github.com/aws/aws-sdk-go-v2/service/rds/types" - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/opensearchservice" "github.com/gravitational/trace" @@ -114,7 +114,7 @@ func (c *urlChecker) checkRDSCluster(ctx context.Context, database types.Databas databases, err := common.NewDatabasesFromRDSCluster(rdsCluster, []rdstypes.DBInstance{}) if err != nil { c.logger.WarnContext(ctx, "Could not convert RDS cluster to database resources", - "cluster", aws.StringValue(rdsCluster.DBClusterIdentifier), + "cluster", aws.ToString(rdsCluster.DBClusterIdentifier), "error", err, ) @@ -150,7 +150,7 @@ func (c *urlChecker) checkRDSProxyPrimaryEndpoint(ctx context.Context, database } // Port has to be fetched from a separate API. Instead of fetching that, // just validate the host domain. - return requireDatabaseHost(database, aws.StringValue(rdsProxy.Endpoint)) + return requireDatabaseHost(database, aws.ToString(rdsProxy.Endpoint)) } func (c *urlChecker) checkRDSProxyCustomEndpoint(ctx context.Context, database types.Database, clt rdsClient, proxyEndpointName string) error { @@ -173,7 +173,7 @@ func (c *urlChecker) checkRedshift(ctx context.Context, database types.Database) return trace.Wrap(err) } if cluster.Endpoint == nil { - return trace.BadParameter("missing endpoint in Redshift cluster %v", aws.StringValue(cluster.ClusterIdentifier)) + return trace.BadParameter("missing endpoint in Redshift cluster %v", aws.ToString(cluster.ClusterIdentifier)) } return trace.Wrap(requireDatabaseAddressPort(database, cluster.Endpoint.Address, cluster.Endpoint.Port)) } @@ -216,14 +216,15 @@ func (c *urlChecker) checkRedshiftServerlessWorkgroup(ctx context.Context, datab func (c *urlChecker) checkElastiCache(ctx context.Context, database types.Database) error { meta := database.GetAWS() - elastiCacheClient, err := c.clients.GetAWSElastiCacheClient(ctx, meta.Region, - cloud.WithAssumeRoleFromAWSMeta(meta), - cloud.WithAmbientCredentials(), + awsCfg, err := c.awsConfigProvider.GetConfig(ctx, meta.Region, + awsconfig.WithAssumeRole(meta.AssumeRoleARN, meta.ExternalID), + awsconfig.WithAmbientCredentials(), ) if err != nil { return trace.Wrap(err) } - cluster, err := describeElastiCacheCluster(ctx, elastiCacheClient, meta.ElastiCache.ReplicationGroupID) + clt := c.awsClients.getElastiCacheClient(awsCfg) + cluster, err := describeElastiCacheCluster(ctx, clt, meta.ElastiCache.ReplicationGroupID) if err != nil { return trace.Wrap(err) } @@ -307,7 +308,7 @@ func (c *urlChecker) checkDocumentDB(ctx context.Context, database types.Databas databases, err := common.NewDatabasesFromDocumentDBCluster(cluster) if err != nil { c.logger.WarnContext(ctx, "Could not convert DocumentDB cluster to database resources", - "cluster", aws.StringValue(cluster.DBClusterIdentifier), + "cluster", aws.ToString(cluster.DBClusterIdentifier), "error", err, ) diff --git a/lib/srv/db/cloud/resource_checker_url_aws_test.go b/lib/srv/db/cloud/resource_checker_url_aws_test.go index 493fdcb76400d..acde0723c93ed 100644 --- a/lib/srv/db/cloud/resource_checker_url_aws_test.go +++ b/lib/srv/db/cloud/resource_checker_url_aws_test.go @@ -22,10 +22,10 @@ import ( "context" "testing" + ectypes "github.com/aws/aws-sdk-go-v2/service/elasticache/types" rdstypes "github.com/aws/aws-sdk-go-v2/service/rds/types" redshifttypes "github.com/aws/aws-sdk-go-v2/service/redshift/types" rsstypes "github.com/aws/aws-sdk-go-v2/service/redshiftserverless/types" - "github.com/aws/aws-sdk-go/service/elasticache" "github.com/aws/aws-sdk-go/service/memorydb" "github.com/aws/aws-sdk-go/service/opensearchservice" "github.com/stretchr/testify/require" @@ -121,9 +121,6 @@ func TestURLChecker_AWS(t *testing.T) { // Mock cloud clients. mockClients := &cloud.TestCloudClients{ - ElastiCache: &mocks.ElastiCacheMock{ - ReplicationGroups: []*elasticache.ReplicationGroup{elastiCacheClusterConfigurationMode, elastiCacheCluster}, - }, MemoryDB: &mocks.MemoryDBMock{ Clusters: []*memorydb.Cluster{memoryDBCluster}, }, @@ -133,10 +130,9 @@ func TestURLChecker_AWS(t *testing.T) { STS: &mocks.STSClientV1{}, } mockClientsUnauth := &cloud.TestCloudClients{ - ElastiCache: &mocks.ElastiCacheMock{Unauth: true}, - MemoryDB: &mocks.MemoryDBMock{Unauth: true}, - OpenSearch: &mocks.OpenSearchMock{Unauth: true}, - STS: &mocks.STSClientV1{}, + MemoryDB: &mocks.MemoryDBMock{Unauth: true}, + OpenSearch: &mocks.OpenSearchMock{Unauth: true}, + STS: &mocks.STSClientV1{}, } // Test both check methods. @@ -153,6 +149,9 @@ func TestURLChecker_AWS(t *testing.T) { clients: mockClients, awsConfigProvider: &mocks.AWSConfigProvider{}, awsClients: fakeAWSClients{ + ecClient: &mocks.ElastiCacheClient{ + ReplicationGroups: []ectypes.ReplicationGroup{*elastiCacheClusterConfigurationMode, *elastiCacheCluster}, + }, rdsClient: &mocks.RDSClient{ DBInstances: []rdstypes.DBInstance{*rdsInstance}, DBClusters: []rdstypes.DBCluster{*rdsCluster, *docdbCluster}, @@ -173,6 +172,7 @@ func TestURLChecker_AWS(t *testing.T) { clients: mockClientsUnauth, awsConfigProvider: &mocks.AWSConfigProvider{}, awsClients: fakeAWSClients{ + ecClient: &mocks.ElastiCacheClient{Unauth: true}, rdsClient: &mocks.RDSClient{Unauth: true}, redshiftClient: &mocks.RedshiftClient{Unauth: true}, rssClient: &mocks.RedshiftServerlessClient{Unauth: true}, diff --git a/lib/srv/db/cloud/users/elasticache.go b/lib/srv/db/cloud/users/elasticache.go index eab4498d169b3..888d47bf26ad4 100644 --- a/lib/srv/db/cloud/users/elasticache.go +++ b/lib/srv/db/cloud/users/elasticache.go @@ -22,14 +22,14 @@ import ( "context" "slices" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/elasticache" - "github.com/aws/aws-sdk-go/service/elasticache/elasticacheiface" + "github.com/aws/aws-sdk-go-v2/aws" + elasticache "github.com/aws/aws-sdk-go-v2/service/elasticache" + ectypes "github.com/aws/aws-sdk-go-v2/service/elasticache/types" "github.com/gravitational/trace" "github.com/gravitational/teleport/api/types" - "github.com/gravitational/teleport/lib/cloud" libaws "github.com/gravitational/teleport/lib/cloud/aws" + "github.com/gravitational/teleport/lib/cloud/awsconfig" libsecrets "github.com/gravitational/teleport/lib/srv/db/secrets" libutils "github.com/gravitational/teleport/lib/utils" ) @@ -74,13 +74,14 @@ func (f *elastiCacheFetcher) FetchDatabaseUsers(ctx context.Context, database ty return nil, nil } - client, err := f.cfg.Clients.GetAWSElastiCacheClient(ctx, meta.Region, - cloud.WithAssumeRoleFromAWSMeta(meta), - cloud.WithAmbientCredentials(), + awsCfg, err := f.cfg.AWSConfigProvider.GetConfig(ctx, meta.Region, + awsconfig.WithAssumeRole(meta.AssumeRoleARN, meta.ExternalID), + awsconfig.WithAmbientCredentials(), ) if err != nil { return nil, trace.Wrap(err) } + client := f.cfg.awsClients.getElastiCacheClient(awsCfg) secrets, err := newSecretStore(ctx, database, f.cfg.Clients, f.cfg.ClusterName) if err != nil { @@ -95,7 +96,7 @@ func (f *elastiCacheFetcher) FetchDatabaseUsers(ctx context.Context, database ty } for _, managedUser := range managedUsers { - user, err := f.createUser(managedUser, client, secrets) + user, err := f.createUser(&managedUser, client, secrets) if err != nil { return nil, trace.Wrap(err) } @@ -107,33 +108,33 @@ func (f *elastiCacheFetcher) FetchDatabaseUsers(ctx context.Context, database ty } // getManagedUsersForGroup returns all managed users for specified user group ID. -func (f *elastiCacheFetcher) getManagedUsersForGroup(ctx context.Context, region, userGroupID string, client elasticacheiface.ElastiCacheAPI) ([]*elasticache.User, error) { +func (f *elastiCacheFetcher) getManagedUsersForGroup(ctx context.Context, region, userGroupID string, client elasticacheClient) ([]ectypes.User, error) { allUsers, err := f.getUsersForRegion(ctx, region, client) if err != nil { return nil, trace.Wrap(err) } - managedUsers := []*elasticache.User{} + managedUsers := []ectypes.User{} for _, user := range allUsers { // Match user group ID. - if !slices.Contains(aws.StringValueSlice(user.UserGroupIds), userGroupID) { + if !slices.Contains(user.UserGroupIds, userGroupID) { continue } // Match special Teleport "managed" tag. // If failed to get tags for some users, log the errors instead of failing the function. - userTags, err := f.getUserTags(ctx, user, client) + userTags, err := f.getUserTags(ctx, &user, client) if err != nil { if trace.IsAccessDenied(err) { - f.cfg.Log.DebugContext(ctx, "No Permission to get tags.", "user", aws.StringValue(user.ARN), "error", err) + f.cfg.Log.DebugContext(ctx, "No Permission to get tags.", "user", aws.ToString(user.ARN), "error", err) } else { - f.cfg.Log.WarnContext(ctx, "Failed to get tags.", "user", aws.StringValue(user.ARN), "error", err) + f.cfg.Log.WarnContext(ctx, "Failed to get tags.", "user", aws.ToString(user.ARN), "error", err) } continue } for _, tag := range userTags { - if aws.StringValue(tag.Key) == libaws.TagKeyTeleportManaged && - libaws.IsTagValueTrue(aws.StringValue(tag.Value)) { + if aws.ToString(tag.Key) == libaws.TagKeyTeleportManaged && + libaws.IsTagValueTrue(aws.ToString(tag.Value)) { managedUsers = append(managedUsers, user) break } @@ -143,15 +144,21 @@ func (f *elastiCacheFetcher) getManagedUsersForGroup(ctx context.Context, region } // getUsersForRegion discovers all ElastiCache users for provided region. -func (f *elastiCacheFetcher) getUsersForRegion(ctx context.Context, region string, client elasticacheiface.ElastiCacheAPI) ([]*elasticache.User, error) { - getFunc := func(ctx context.Context) ([]*elasticache.User, error) { - var users []*elasticache.User - err := client.DescribeUsersPagesWithContext(ctx, &elasticache.DescribeUsersInput{}, func(output *elasticache.DescribeUsersOutput, _ bool) bool { - users = append(users, output.Users...) - return true - }) - if err != nil { - return nil, trace.Wrap(libaws.ConvertRequestFailureError(err)) +func (f *elastiCacheFetcher) getUsersForRegion(ctx context.Context, region string, client elasticacheClient) ([]ectypes.User, error) { + getFunc := func(ctx context.Context) ([]ectypes.User, error) { + pager := elasticache.NewDescribeUsersPaginator(client, + &elasticache.DescribeUsersInput{}, + func(opts *elasticache.DescribeUsersPaginatorOptions) { + opts.StopOnDuplicateToken = true + }, + ) + var users []ectypes.User + for pager.HasMorePages() { + page, err := pager.NextPage(ctx) + if err != nil { + return nil, trace.Wrap(libaws.ConvertRequestFailureErrorV2(err)) + } + users = append(users, page.Users...) } return users, nil } @@ -164,18 +171,18 @@ func (f *elastiCacheFetcher) getUsersForRegion(ctx context.Context, region strin } // getUserTags discovers resource tags for provided user. -func (f *elastiCacheFetcher) getUserTags(ctx context.Context, user *elasticache.User, client elasticacheiface.ElastiCacheAPI) ([]*elasticache.Tag, error) { - getFunc := func(ctx context.Context) ([]*elasticache.Tag, error) { - output, err := client.ListTagsForResourceWithContext(ctx, &elasticache.ListTagsForResourceInput{ +func (f *elastiCacheFetcher) getUserTags(ctx context.Context, user *ectypes.User, client elasticacheClient) ([]ectypes.Tag, error) { + getFunc := func(ctx context.Context) ([]ectypes.Tag, error) { + output, err := client.ListTagsForResource(ctx, &elasticache.ListTagsForResourceInput{ ResourceName: user.ARN, }) if err != nil { - return nil, trace.Wrap(libaws.ConvertRequestFailureError(err)) + return nil, trace.Wrap(libaws.ConvertRequestFailureErrorV2(err)) } return output.TagList, nil } - userTags, err := libutils.FnCacheGet(ctx, f.cache, aws.StringValue(user.ARN), getFunc) + userTags, err := libutils.FnCacheGet(ctx, f.cache, aws.ToString(user.ARN), getFunc) if err != nil { return nil, trace.Wrap(err) } @@ -184,8 +191,8 @@ func (f *elastiCacheFetcher) getUserTags(ctx context.Context, user *elasticache. } // createUser creates an ElastiCache User. -func (f *elastiCacheFetcher) createUser(ecUser *elasticache.User, client elasticacheiface.ElastiCacheAPI, secrets libsecrets.Secrets) (User, error) { - secretKey, err := secretKeyFromAWSARN(aws.StringValue(ecUser.ARN)) +func (f *elastiCacheFetcher) createUser(ecUser *ectypes.User, client elasticacheClient, secrets libsecrets.Secrets) (User, error) { + secretKey, err := secretKeyFromAWSARN(aws.ToString(ecUser.ARN)) if err != nil { return nil, trace.Wrap(err) } @@ -195,7 +202,7 @@ func (f *elastiCacheFetcher) createUser(ecUser *elasticache.User, client elastic secretKey: secretKey, secrets: secrets, secretTTL: f.cfg.Interval, - databaseUsername: aws.StringValue(ecUser.UserName), + databaseUsername: aws.ToString(ecUser.UserName), clock: f.cfg.Clock, // Maximum ElastiCache User password size is 128. @@ -221,8 +228,8 @@ func (f *elastiCacheFetcher) createUser(ecUser *elasticache.User, client elastic // elastiCacheUserResource implements cloudResource interface for an // ElastiCache user. type elastiCacheUserResource struct { - user *elasticache.User - client elasticacheiface.ElastiCacheAPI + user *ectypes.User + client elasticacheClient } // ModifyUserPassword updates passwords of an ElastiCache user. @@ -237,11 +244,11 @@ func (r *elastiCacheUserResource) ModifyUserPassword(ctx context.Context, oldPas input := &elasticache.ModifyUserInput{ UserId: r.user.UserId, - Passwords: aws.StringSlice(passwords), + Passwords: passwords, NoPasswordRequired: aws.Bool(len(passwords) == 0), } - if _, err := r.client.ModifyUserWithContext(ctx, input); err != nil { - return trace.Wrap(libaws.ConvertRequestFailureError(err)) + if _, err := r.client.ModifyUser(ctx, input); err != nil { + return trace.Wrap(libaws.ConvertRequestFailureErrorV2(err)) } return nil } diff --git a/lib/srv/db/cloud/users/users.go b/lib/srv/db/cloud/users/users.go index f0bffc41022b2..a18d654c1e4b6 100644 --- a/lib/srv/db/cloud/users/users.go +++ b/lib/srv/db/cloud/users/users.go @@ -23,6 +23,8 @@ import ( "log/slog" "time" + "github.com/aws/aws-sdk-go-v2/aws" + elasticache "github.com/aws/aws-sdk-go-v2/service/elasticache" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" @@ -30,11 +32,14 @@ import ( "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/utils/retryutils" "github.com/gravitational/teleport/lib/cloud" + "github.com/gravitational/teleport/lib/cloud/awsconfig" "github.com/gravitational/teleport/lib/utils/interval" ) // Config is the config for users service. type Config struct { + // AWSConfigProvider provides [aws.Config] for AWS SDK service clients. + AWSConfigProvider awsconfig.Provider // Clients is an interface for retrieving cloud clients. Clients cloud.Clients // Clock is used to control time. @@ -48,10 +53,37 @@ type Config struct { UpdateMeta func(context.Context, types.Database) error // ClusterName is the name of the Teleport cluster (for tagging purpose). ClusterName string + + // awsClients is an SDK client provider. + awsClients awsClientProvider +} + +// awsClientProvider is an AWS SDK client provider. +type awsClientProvider interface { + getElastiCacheClient(cfg aws.Config, optFns ...func(*elasticache.Options)) elasticacheClient +} + +type elasticacheClient interface { + elasticache.DescribeUsersAPIClient + + ListTagsForResource(ctx context.Context, in *elasticache.ListTagsForResourceInput, optFns ...func(*elasticache.Options)) (*elasticache.ListTagsForResourceOutput, error) + ModifyUser(ctx context.Context, in *elasticache.ModifyUserInput, optFns ...func(*elasticache.Options)) (*elasticache.ModifyUserOutput, error) +} + +type defaultAWSClients struct{} + +func (defaultAWSClients) getElastiCacheClient(cfg aws.Config, optFns ...func(*elasticache.Options)) elasticacheClient { + return elasticache.NewFromConfig(cfg, optFns...) } // CheckAndSetDefaults validates the config and set defaults. func (c *Config) CheckAndSetDefaults() error { + if c.AWSConfigProvider == nil { + return trace.BadParameter("missing AWSConfigProvider") + } + if c.ClusterName == "" { + return trace.BadParameter("missing cluster name") + } if c.UpdateMeta == nil { return trace.BadParameter("missing UpdateMeta") } @@ -80,8 +112,8 @@ func (c *Config) CheckAndSetDefaults() error { if c.Log == nil { c.Log = slog.With(teleport.ComponentKey, "clouduser") } - if c.ClusterName == "" { - return trace.BadParameter("missing cluster name") + if c.awsClients == nil { + c.awsClients = defaultAWSClients{} } return nil } diff --git a/lib/srv/db/cloud/users/users_test.go b/lib/srv/db/cloud/users/users_test.go index d477b47003060..a23bb099e8b1a 100644 --- a/lib/srv/db/cloud/users/users_test.go +++ b/lib/srv/db/cloud/users/users_test.go @@ -24,8 +24,9 @@ import ( "testing" "time" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/elasticache" + "github.com/aws/aws-sdk-go-v2/aws" + elasticache "github.com/aws/aws-sdk-go-v2/service/elasticache" + ectypes "github.com/aws/aws-sdk-go-v2/service/elasticache/types" "github.com/aws/aws-sdk-go/service/memorydb" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" @@ -54,7 +55,7 @@ func TestUsers(t *testing.T) { smMock := libsecrets.NewMockSecretsManagerClient(libsecrets.MockSecretsManagerClientConfig{ Clock: clock, }) - ecMock := &mocks.ElastiCacheMock{} + ecMock := &mocks.ElastiCacheClient{} ecMock.AddMockUser(elastiCacheUser("alice", "group1"), managedTags) ecMock.AddMockUser(elastiCacheUser("bob", "group1", "group2"), managedTags) ecMock.AddMockUser(elastiCacheUser("charlie", "group2", "group3"), managedTags) @@ -74,8 +75,8 @@ func TestUsers(t *testing.T) { db6 := mustCreateMemoryDBDatabase(t, "db6", "acl1") users, err := NewUsers(Config{ + AWSConfigProvider: &mocks.AWSConfigProvider{}, Clients: &clients.TestCloudClients{ - ElastiCache: ecMock, MemoryDB: mdbMock, SecretsManager: smMock, }, @@ -90,6 +91,9 @@ func TestUsers(t *testing.T) { return nil }, ClusterName: "example.teleport.sh", + awsClients: fakeAWSClients{ + ecClient: ecMock, + }, }) require.NoError(t, err) @@ -187,12 +191,12 @@ func mustCreateRDSDatabase(t *testing.T, name string) types.Database { return db } -func elastiCacheUser(name string, groupIDs ...string) *elasticache.User { - return &elasticache.User{ +func elastiCacheUser(name string, groupIDs ...string) ectypes.User { + return ectypes.User{ UserId: aws.String(name), ARN: aws.String("arn:aws:elasticache:us-east-1:123456789012:user:" + name), UserName: aws.String(name), - UserGroupIds: aws.StringSlice(groupIDs), + UserGroupIds: groupIDs, } } @@ -203,3 +207,11 @@ func memoryDBUser(name string, aclNames ...string) *memorydb.User { ACLNames: aws.StringSlice(aclNames), } } + +type fakeAWSClients struct { + ecClient elasticacheClient +} + +func (f fakeAWSClients) getElastiCacheClient(cfg aws.Config, optFns ...func(*elasticache.Options)) elasticacheClient { + return f.ecClient +} diff --git a/lib/srv/db/common/auth.go b/lib/srv/db/common/auth.go index 35723b4e0fbe8..c72c8152cc0e4 100644 --- a/lib/srv/db/common/auth.go +++ b/lib/srv/db/common/auth.go @@ -40,7 +40,6 @@ import ( rss "github.com/aws/aws-sdk-go-v2/service/redshiftserverless" "github.com/aws/aws-sdk-go/aws/credentials" v4 "github.com/aws/aws-sdk-go/aws/signer/v4" - "github.com/aws/aws-sdk-go/service/elasticache" "github.com/aws/aws-sdk-go/service/memorydb" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" @@ -64,6 +63,7 @@ import ( "github.com/gravitational/teleport/lib/tlsca" "github.com/gravitational/teleport/lib/utils" awsutils "github.com/gravitational/teleport/lib/utils/aws" + "github.com/gravitational/teleport/lib/utils/aws/migration" ) // azureVirtualMachineCacheTTL is the default TTL for Azure virtual machine @@ -635,9 +635,9 @@ func (a *dbAuth) GetAzureAccessToken(ctx context.Context) (string, error) { // GetElastiCacheRedisToken generates an ElastiCache Redis auth token. func (a *dbAuth) GetElastiCacheRedisToken(ctx context.Context, database types.Database, databaseUser string) (string, error) { meta := database.GetAWS() - awsSession, err := a.cfg.Clients.GetAWSSession(ctx, meta.Region, - cloud.WithAssumeRoleFromAWSMeta(meta), - cloud.WithAmbientCredentials(), + awsCfg, err := a.cfg.AWSConfigProvider.GetConfig(ctx, meta.Region, + awsconfig.WithAssumeRole(meta.AssumeRoleARN, meta.ExternalID), + awsconfig.WithAmbientCredentials(), ) if err != nil { return "", trace.Wrap(err) @@ -651,9 +651,9 @@ func (a *dbAuth) GetElastiCacheRedisToken(ctx context.Context, database types.Da // https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/auth-iam.html#auth-iam-limits userID: databaseUser, targetID: meta.ElastiCache.ReplicationGroupID, - serviceName: elasticache.ServiceName, + serviceName: "elasticache", region: meta.Region, - credentials: awsSession.Config.Credentials, + credentials: migration.NewCredentialsAdapter(awsCfg.Credentials), clock: a.cfg.Clock, } token, err := tokenReq.toSignedRequestURI() diff --git a/lib/srv/db/common/engines.go b/lib/srv/db/common/engines.go index 36de44cd1c113..5d5c0196ba410 100644 --- a/lib/srv/db/common/engines.go +++ b/lib/srv/db/common/engines.go @@ -30,6 +30,7 @@ import ( "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/auth/authclient" "github.com/gravitational/teleport/lib/cloud" + "github.com/gravitational/teleport/lib/cloud/awsconfig" "github.com/gravitational/teleport/lib/srv/db/common/enterprise" ) @@ -43,6 +44,13 @@ var ( // EngineFn defines a database engine constructor function. type EngineFn func(EngineConfig) Engine +// GetEngineFn returns the constructor function for the given protocol. +func GetEngineFn(protocol string) EngineFn { + enginesMu.RLock() + defer enginesMu.RUnlock() + return engines[protocol] +} + // RegisterEngine registers a new engine constructor. func RegisterEngine(fn EngineFn, names ...string) { enginesMu.Lock() @@ -103,6 +111,8 @@ type EngineConfig struct { Audit Audit // AuthClient is the cluster auth server client. AuthClient *authclient.Client + // AWSConfigProvider provides [aws.Config] for AWS SDK service clients. + AWSConfigProvider awsconfig.Provider // CloudClients provides access to cloud API clients. CloudClients cloud.Clients // Context is the database server close context. @@ -135,6 +145,9 @@ func (c *EngineConfig) CheckAndSetDefaults() error { if c.AuthClient == nil { return trace.BadParameter("engine config AuthClient is missing") } + if c.AWSConfigProvider == nil { + return trace.BadParameter("missing AWSConfigProvider") + } if c.CloudClients == nil { return trace.BadParameter("engine config CloudClients are missing") } diff --git a/lib/srv/db/common/engines_test.go b/lib/srv/db/common/engines_test.go index 01d1d36e3c2c4..8ef522db8ded5 100644 --- a/lib/srv/db/common/engines_test.go +++ b/lib/srv/db/common/engines_test.go @@ -30,6 +30,7 @@ import ( "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/auth/authclient" "github.com/gravitational/teleport/lib/cloud" + "github.com/gravitational/teleport/lib/cloud/mocks" ) // TestRegisterEngine verifies database engine registration. @@ -43,13 +44,14 @@ func TestRegisterEngine(t *testing.T) { cloudClients, err := cloud.NewClients() require.NoError(t, err) ec := EngineConfig{ - Context: context.Background(), - Clock: clockwork.NewFakeClock(), - Log: slog.Default(), - Auth: &testAuth{}, - Audit: &testAudit{}, - AuthClient: &authclient.Client{}, - CloudClients: cloudClients, + Context: context.Background(), + Clock: clockwork.NewFakeClock(), + Log: slog.Default(), + Auth: &testAuth{}, + Audit: &testAudit{}, + AuthClient: &authclient.Client{}, + AWSConfigProvider: &mocks.AWSConfigProvider{}, + CloudClients: cloudClients, } require.NoError(t, ec.CheckAndSetDefaults()) diff --git a/lib/srv/db/redis/engine.go b/lib/srv/db/redis/engine.go index 2d0a41b919a88..0a83459611934 100644 --- a/lib/srv/db/redis/engine.go +++ b/lib/srv/db/redis/engine.go @@ -29,8 +29,9 @@ import ( "strings" "time" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/elasticache" + "github.com/aws/aws-sdk-go-v2/aws" + elasticache "github.com/aws/aws-sdk-go-v2/service/elasticache" + ectypes "github.com/aws/aws-sdk-go-v2/service/elasticache/types" "github.com/aws/aws-sdk-go/service/memorydb" "github.com/gravitational/trace" "github.com/redis/go-redis/v9" @@ -40,6 +41,7 @@ import ( apiawsutils "github.com/gravitational/teleport/api/utils/aws" "github.com/gravitational/teleport/lib/cloud" libaws "github.com/gravitational/teleport/lib/cloud/aws" + "github.com/gravitational/teleport/lib/cloud/awsconfig" "github.com/gravitational/teleport/lib/cloud/azure" "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/srv/db/common" @@ -66,6 +68,9 @@ type redisClientFactoryFn func(username, password string) (redis.UniversalClient type Engine struct { // EngineConfig is the common database engine configuration. common.EngineConfig + // AWSClients is an SDK client provider. + // This field is only exported so it can be overridden in integration tests. + AWSClients AWSClientProvider // clientConn is a client connection. clientConn net.Conn // clientReader is a go-redis wrapper for Redis client connection. @@ -82,11 +87,31 @@ type Engine struct { clientMessageRead bool } +// AWSClientProvider provides AWS service API clients. +type AWSClientProvider interface { + // GetElastiCacheClient provides an [ElasticacheClient]. + GetElastiCacheClient(cfg aws.Config, optFns ...func(*elasticache.Options)) ElastiCacheClient +} + +// ElastiCacheClient is a subset of the AWS ElastiCache API. +type ElastiCacheClient interface { + elasticache.DescribeUsersAPIClient +} + +type defaultAWSClients struct{} + +func (defaultAWSClients) GetElastiCacheClient(cfg aws.Config, optFns ...func(*elasticache.Options)) ElastiCacheClient { + return elasticache.NewFromConfig(cfg, optFns...) +} + // InitializeConnection initializes the database connection. func (e *Engine) InitializeConnection(clientConn net.Conn, sessionCtx *common.Session) error { e.clientConn = clientConn e.clientReader = redis.NewReader(clientConn) e.sessionCtx = sessionCtx + if e.AWSClients == nil { + e.AWSClients = defaultAWSClients{} + } // Use Redis default user named "default" if a user is not provided. if e.sessionCtx.DatabaseUser == "" { @@ -370,7 +395,7 @@ func (e *Engine) isAWSIAMAuthSupported(ctx context.Context, sessionCtx *common.S return false } dbUser := sessionCtx.DatabaseUser - ok, err := checkUserIAMAuthIsEnabled(ctx, sessionCtx, e.CloudClients, dbUser) + ok, err := e.checkUserIAMAuthIsEnabled(ctx, sessionCtx, dbUser) if err != nil { e.Log.DebugContext(e.Context, "Assuming IAM auth is not enabled for user.", "user", dbUser, "error", err) return false @@ -394,38 +419,39 @@ func checkDBSupportsIAMAuth(database types.Database) (bool, error) { // checkUserIAMAuthIsEnabled returns whether a given ElastiCache or MemoryDB // user has IAM auth enabled. -func checkUserIAMAuthIsEnabled(ctx context.Context, sessionCtx *common.Session, clients cloud.Clients, username string) (bool, error) { +func (e *Engine) checkUserIAMAuthIsEnabled(ctx context.Context, sessionCtx *common.Session, username string) (bool, error) { switch sessionCtx.Database.GetType() { case types.DatabaseTypeElastiCache: - return checkElastiCacheUserIAMAuthIsEnabled(ctx, clients, sessionCtx.Database.GetAWS(), username) + return e.checkElastiCacheUserIAMAuthIsEnabled(ctx, sessionCtx.Database.GetAWS(), username) case types.DatabaseTypeMemoryDB: - return checkMemoryDBUserIAMAuthIsEnabled(ctx, clients, sessionCtx.Database.GetAWS(), username) + return checkMemoryDBUserIAMAuthIsEnabled(ctx, e.CloudClients, sessionCtx.Database.GetAWS(), username) default: return false, nil } } -func checkElastiCacheUserIAMAuthIsEnabled(ctx context.Context, clients cloud.Clients, awsMeta types.AWS, username string) (bool, error) { - client, err := clients.GetAWSElastiCacheClient(ctx, awsMeta.Region, - cloud.WithAssumeRoleFromAWSMeta(awsMeta), - cloud.WithAmbientCredentials(), +func (e *Engine) checkElastiCacheUserIAMAuthIsEnabled(ctx context.Context, awsMeta types.AWS, username string) (bool, error) { + awsCfg, err := e.AWSConfigProvider.GetConfig(ctx, awsMeta.Region, + awsconfig.WithAssumeRole(awsMeta.AssumeRoleARN, awsMeta.ExternalID), + awsconfig.WithAmbientCredentials(), ) if err != nil { return false, trace.Wrap(err) } + client := e.AWSClients.GetElastiCacheClient(awsCfg) // For IAM-enabled ElastiCache users, the username and user id properties // must be identical. // https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/auth-iam.html#auth-iam-limits input := elasticache.DescribeUsersInput{UserId: aws.String(username)} - out, err := client.DescribeUsersWithContext(ctx, &input) + out, err := client.DescribeUsers(ctx, &input) if err != nil { - return false, trace.Wrap(libaws.ConvertRequestFailureError(err)) + return false, trace.Wrap(libaws.ConvertRequestFailureErrorV2(err)) } if len(out.Users) < 1 || out.Users[0].Authentication == nil { return false, nil } - authType := aws.StringValue(out.Users[0].Authentication.Type) - return elasticache.AuthenticationTypeIam == authType, nil + authType := out.Users[0].Authentication.Type + return ectypes.AuthenticationTypeIam == authType, nil } func checkMemoryDBUserIAMAuthIsEnabled(ctx context.Context, clients cloud.Clients, awsMeta types.AWS, username string) (bool, error) { @@ -444,7 +470,7 @@ func checkMemoryDBUserIAMAuthIsEnabled(ctx context.Context, clients cloud.Client if len(out.Users) < 1 || out.Users[0].Authentication == nil { return false, nil } - authType := aws.StringValue(out.Users[0].Authentication.Type) + authType := aws.ToString(out.Users[0].Authentication.Type) return memorydb.AuthenticationTypeIam == authType, nil } diff --git a/lib/srv/db/server.go b/lib/srv/db/server.go index 6c8da739fc5db..6b21e8420fa13 100644 --- a/lib/srv/db/server.go +++ b/lib/srv/db/server.go @@ -285,9 +285,10 @@ func (c *Config) CheckAndSetDefaults(ctx context.Context) (err error) { return trace.Wrap(err) } c.CloudUsers, err = users.NewUsers(users.Config{ - Clients: c.CloudClients, - UpdateMeta: c.CloudMeta.Update, - ClusterName: clusterName.GetClusterName(), + AWSConfigProvider: c.AWSConfigProvider, + Clients: c.CloudClients, + UpdateMeta: c.CloudMeta.Update, + ClusterName: clusterName.GetClusterName(), }) if err != nil { return trace.Wrap(err) @@ -1192,15 +1193,16 @@ func (s *Server) dispatch(sessionCtx *common.Session, rec events.SessionPreparer // An error is returned when a protocol is not supported. func (s *Server) createEngine(sessionCtx *common.Session, audit common.Audit) (common.Engine, error) { return common.GetEngine(sessionCtx.Database, common.EngineConfig{ - Auth: common.NewAuthForSession(s.cfg.Auth, sessionCtx), - Audit: audit, - AuthClient: s.cfg.AuthClient, - CloudClients: s.cfg.CloudClients, - Context: s.connContext, - Clock: s.cfg.Clock, - Log: sessionCtx.Log, - Users: s.cfg.CloudUsers, - DataDir: s.cfg.DataDir, + Auth: common.NewAuthForSession(s.cfg.Auth, sessionCtx), + Audit: audit, + AuthClient: s.cfg.AuthClient, + AWSConfigProvider: s.cfg.AWSConfigProvider, + CloudClients: s.cfg.CloudClients, + Context: s.connContext, + Clock: s.cfg.Clock, + Log: sessionCtx.Log, + Users: s.cfg.CloudUsers, + DataDir: s.cfg.DataDir, GetUserProvisioner: func(aub common.AutoUsers) *common.UserProvisioner { return &common.UserProvisioner{ AuthClient: s.cfg.AuthClient, diff --git a/lib/srv/db/watcher_test.go b/lib/srv/db/watcher_test.go index d5fef782fae75..f0a6f10e0ed90 100644 --- a/lib/srv/db/watcher_test.go +++ b/lib/srv/db/watcher_test.go @@ -29,6 +29,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/sql/armsql" "github.com/aws/aws-sdk-go-v2/aws" + elasticache "github.com/aws/aws-sdk-go-v2/service/elasticache" "github.com/aws/aws-sdk-go-v2/service/rds" "github.com/aws/aws-sdk-go-v2/service/redshift" rss "github.com/aws/aws-sdk-go-v2/service/redshiftserverless" @@ -467,11 +468,16 @@ func makeAzureSQLServer(t *testing.T, name, group string) (*armsql.Server, types } type fakeAWSClients struct { + ecClient db.ElastiCacheClient rdsClient db.RDSClient redshiftClient db.RedshiftClient rssClient db.RSSClient } +func (f fakeAWSClients) GetElastiCacheClient(cfg aws.Config, optFns ...func(*elasticache.Options)) db.ElastiCacheClient { + return f.ecClient +} + func (f fakeAWSClients) GetRDSClient(cfg aws.Config, optFns ...func(*rds.Options)) db.RDSClient { return f.rdsClient } diff --git a/lib/srv/discovery/common/database.go b/lib/srv/discovery/common/database.go index aa8b354cd2531..2dd8971687829 100644 --- a/lib/srv/discovery/common/database.go +++ b/lib/srv/discovery/common/database.go @@ -29,11 +29,11 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/redisenterprise/armredisenterprise" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/sql/armsql" "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/aws/arn" + ectypes "github.com/aws/aws-sdk-go-v2/service/elasticache/types" rdstypes "github.com/aws/aws-sdk-go-v2/service/rds/types" redshifttypes "github.com/aws/aws-sdk-go-v2/service/redshift/types" rsstypes "github.com/aws/aws-sdk-go-v2/service/redshiftserverless/types" - "github.com/aws/aws-sdk-go/aws/arn" - "github.com/aws/aws-sdk-go/service/elasticache" "github.com/aws/aws-sdk-go/service/memorydb" "github.com/aws/aws-sdk-go/service/opensearchservice" "github.com/gravitational/trace" @@ -802,7 +802,7 @@ func NewDatabaseFromRedshiftCluster(cluster *redshifttypes.Cluster) (types.Datab // NewDatabaseFromElastiCacheConfigurationEndpoint creates a database resource // from ElastiCache configuration endpoint. -func NewDatabaseFromElastiCacheConfigurationEndpoint(cluster *elasticache.ReplicationGroup, extraLabels map[string]string) (types.Database, error) { +func NewDatabaseFromElastiCacheConfigurationEndpoint(cluster *ectypes.ReplicationGroup, extraLabels map[string]string) (types.Database, error) { if cluster.ConfigurationEndpoint == nil { return nil, trace.BadParameter("missing configuration endpoint") } @@ -812,7 +812,7 @@ func NewDatabaseFromElastiCacheConfigurationEndpoint(cluster *elasticache.Replic // NewDatabasesFromElastiCacheNodeGroups creates database resources from // ElastiCache node groups. -func NewDatabasesFromElastiCacheNodeGroups(cluster *elasticache.ReplicationGroup, extraLabels map[string]string) (types.Databases, error) { +func NewDatabasesFromElastiCacheNodeGroups(cluster *ectypes.ReplicationGroup, extraLabels map[string]string) (types.Databases, error) { var databases types.Databases for _, nodeGroup := range cluster.NodeGroups { if nodeGroup.PrimaryEndpoint != nil { @@ -836,7 +836,7 @@ func NewDatabasesFromElastiCacheNodeGroups(cluster *elasticache.ReplicationGroup // NewDatabasesFromElastiCacheReplicationGroup creates all database resources // from an ElastiCache ReplicationGroup. -func NewDatabasesFromElastiCacheReplicationGroup(cluster *elasticache.ReplicationGroup, extraLabels map[string]string) (types.Databases, error) { +func NewDatabasesFromElastiCacheReplicationGroup(cluster *ectypes.ReplicationGroup, extraLabels map[string]string) (types.Databases, error) { // Create database using configuration endpoint for Redis with cluster // mode enabled. if aws.ToBool(cluster.ClusterEnabled) { @@ -856,7 +856,7 @@ func NewDatabasesFromElastiCacheReplicationGroup(cluster *elasticache.Replicatio } // newElastiCacheDatabase returns a new ElastiCache database. -func newElastiCacheDatabase(cluster *elasticache.ReplicationGroup, endpoint *elasticache.Endpoint, endpointType string, extraLabels map[string]string) (types.Database, error) { +func newElastiCacheDatabase(cluster *ectypes.ReplicationGroup, endpoint *ectypes.Endpoint, endpointType string, extraLabels map[string]string) (types.Database, error) { metadata, err := MetadataFromElastiCacheCluster(cluster, endpointType) if err != nil { return nil, trace.Wrap(err) @@ -872,7 +872,7 @@ func newElastiCacheDatabase(cluster *elasticache.ReplicationGroup, endpoint *ela Labels: labelsFromMetaAndEndpointType(metadata, endpointType, extraLabels), }, aws.ToString(cluster.ReplicationGroupId), suffix...), types.DatabaseSpecV3{ Protocol: defaults.ProtocolRedis, - URI: fmt.Sprintf("%v:%v", aws.ToString(endpoint.Address), aws.ToInt64(endpoint.Port)), + URI: fmt.Sprintf("%v:%v", aws.ToString(endpoint.Address), aws.ToInt32(endpoint.Port)), AWS: *metadata, }) } @@ -1153,7 +1153,7 @@ func MetadataFromRedshiftCluster(cluster *redshifttypes.Cluster) (*types.AWS, er // MetadataFromElastiCacheCluster creates AWS metadata for the provided // ElastiCache cluster. -func MetadataFromElastiCacheCluster(cluster *elasticache.ReplicationGroup, endpointType string) (*types.AWS, error) { +func MetadataFromElastiCacheCluster(cluster *ectypes.ReplicationGroup, endpointType string) (*types.AWS, error) { parsedARN, err := arn.Parse(aws.ToString(cluster.ARN)) if err != nil { return nil, trace.Wrap(err) @@ -1165,7 +1165,7 @@ func MetadataFromElastiCacheCluster(cluster *elasticache.ReplicationGroup, endpo // messages don't fail. var userGroupIDs []string if len(cluster.UserGroupIds) != 0 { - userGroupIDs = aws.ToStringSlice(cluster.UserGroupIds) + userGroupIDs = cluster.UserGroupIds } return &types.AWS{ @@ -1257,7 +1257,7 @@ func MetadataFromRedshiftServerlessVPCEndpoint(endpoint *rsstypes.EndpointAccess // ExtraElastiCacheLabels returns a list of extra labels for provided // ElastiCache cluster. -func ExtraElastiCacheLabels(cluster *elasticache.ReplicationGroup, tags []*elasticache.Tag, allNodes []*elasticache.CacheCluster, allSubnetGroups []*elasticache.CacheSubnetGroup) map[string]string { +func ExtraElastiCacheLabels(cluster *ectypes.ReplicationGroup, tags []ectypes.Tag, allNodes []ectypes.CacheCluster, allSubnetGroups []ectypes.CacheSubnetGroup) map[string]string { replicationGroupID := aws.ToString(cluster.ReplicationGroupId) subnetGroupName := "" labels := make(map[string]string) diff --git a/lib/srv/discovery/common/database_test.go b/lib/srv/discovery/common/database_test.go index 819db125399b2..83b5e53c14b1a 100644 --- a/lib/srv/discovery/common/database_test.go +++ b/lib/srv/discovery/common/database_test.go @@ -28,9 +28,9 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/redisenterprise/armredisenterprise" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/sql/armsql" "github.com/aws/aws-sdk-go-v2/aws" + ectypes "github.com/aws/aws-sdk-go-v2/service/elasticache/types" rdstypes "github.com/aws/aws-sdk-go-v2/service/rds/types" redshifttypes "github.com/aws/aws-sdk-go-v2/service/redshift/types" - "github.com/aws/aws-sdk-go/service/elasticache" "github.com/aws/aws-sdk-go/service/memorydb" "github.com/google/go-cmp/cmp" "github.com/google/uuid" @@ -1293,21 +1293,21 @@ func TestDatabaseFromRedshiftCluster(t *testing.T) { } func TestDatabaseFromElastiCacheConfigurationEndpoint(t *testing.T) { - cluster := &elasticache.ReplicationGroup{ + cluster := &ectypes.ReplicationGroup{ ARN: aws.String("arn:aws:elasticache:us-east-1:123456789012:replicationgroup:my-cluster"), ReplicationGroupId: aws.String("my-cluster"), Status: aws.String("available"), TransitEncryptionEnabled: aws.Bool(true), ClusterEnabled: aws.Bool(true), - ConfigurationEndpoint: &elasticache.Endpoint{ + ConfigurationEndpoint: &ectypes.Endpoint{ Address: aws.String("configuration.localhost"), - Port: aws.Int64(6379), + Port: aws.Int32(6379), }, - UserGroupIds: []*string{aws.String("my-user-group")}, - NodeGroups: []*elasticache.NodeGroup{ + UserGroupIds: []string{"my-user-group"}, + NodeGroups: []ectypes.NodeGroup{ { NodeGroupId: aws.String("0001"), - NodeGroupMembers: []*elasticache.NodeGroupMember{ + NodeGroupMembers: []ectypes.NodeGroupMember{ { CacheClusterId: aws.String("my-cluster-0001-001"), }, @@ -1318,7 +1318,7 @@ func TestDatabaseFromElastiCacheConfigurationEndpoint(t *testing.T) { }, { NodeGroupId: aws.String("0002"), - NodeGroupMembers: []*elasticache.NodeGroupMember{ + NodeGroupMembers: []ectypes.NodeGroupMember{ { CacheClusterId: aws.String("my-cluster-0002-001"), }, @@ -1365,21 +1365,21 @@ func TestDatabaseFromElastiCacheConfigurationEndpoint(t *testing.T) { func TestDatabaseFromElastiCacheConfigurationEndpointNameOverride(t *testing.T) { for _, overrideLabel := range types.AWSDatabaseNameOverrideLabels { t.Run("via "+overrideLabel, func(t *testing.T) { - cluster := &elasticache.ReplicationGroup{ + cluster := &ectypes.ReplicationGroup{ ARN: aws.String("arn:aws:elasticache:us-east-1:123456789012:replicationgroup:my-cluster"), ReplicationGroupId: aws.String("my-cluster"), Status: aws.String("available"), TransitEncryptionEnabled: aws.Bool(true), ClusterEnabled: aws.Bool(true), - ConfigurationEndpoint: &elasticache.Endpoint{ + ConfigurationEndpoint: &ectypes.Endpoint{ Address: aws.String("configuration.localhost"), - Port: aws.Int64(6379), + Port: aws.Int32(6379), }, - UserGroupIds: []*string{aws.String("my-user-group")}, - NodeGroups: []*elasticache.NodeGroup{ + UserGroupIds: []string{"my-user-group"}, + NodeGroups: []ectypes.NodeGroup{ { NodeGroupId: aws.String("0001"), - NodeGroupMembers: []*elasticache.NodeGroupMember{ + NodeGroupMembers: []ectypes.NodeGroupMember{ { CacheClusterId: aws.String("my-cluster-0001-001"), }, @@ -1390,7 +1390,7 @@ func TestDatabaseFromElastiCacheConfigurationEndpointNameOverride(t *testing.T) }, { NodeGroupId: aws.String("0002"), - NodeGroupMembers: []*elasticache.NodeGroupMember{ + NodeGroupMembers: []ectypes.NodeGroupMember{ { CacheClusterId: aws.String("my-cluster-0002-001"), }, @@ -1441,23 +1441,23 @@ func TestDatabaseFromElastiCacheConfigurationEndpointNameOverride(t *testing.T) } func TestDatabaseFromElastiCacheNodeGroups(t *testing.T) { - cluster := &elasticache.ReplicationGroup{ + cluster := &ectypes.ReplicationGroup{ ARN: aws.String("arn:aws:elasticache:us-east-1:123456789012:replicationgroup:my-cluster"), ReplicationGroupId: aws.String("my-cluster"), Status: aws.String("available"), TransitEncryptionEnabled: aws.Bool(true), ClusterEnabled: aws.Bool(false), - UserGroupIds: []*string{aws.String("my-user-group")}, - NodeGroups: []*elasticache.NodeGroup{ + UserGroupIds: []string{"my-user-group"}, + NodeGroups: []ectypes.NodeGroup{ { NodeGroupId: aws.String("0001"), - PrimaryEndpoint: &elasticache.Endpoint{ + PrimaryEndpoint: &ectypes.Endpoint{ Address: aws.String("primary.localhost"), - Port: aws.Int64(6379), + Port: aws.Int32(6379), }, - ReaderEndpoint: &elasticache.Endpoint{ + ReaderEndpoint: &ectypes.Endpoint{ Address: aws.String("reader.localhost"), - Port: aws.Int64(6379), + Port: aws.Int32(6379), }, }, }, @@ -1524,23 +1524,23 @@ func TestDatabaseFromElastiCacheNodeGroups(t *testing.T) { func TestDatabaseFromElastiCacheNodeGroupsNameOverride(t *testing.T) { for _, overrideLabel := range types.AWSDatabaseNameOverrideLabels { t.Run("via "+overrideLabel, func(t *testing.T) { - cluster := &elasticache.ReplicationGroup{ + cluster := &ectypes.ReplicationGroup{ ARN: aws.String("arn:aws:elasticache:us-east-1:123456789012:replicationgroup:my-cluster"), ReplicationGroupId: aws.String("my-cluster"), Status: aws.String("available"), TransitEncryptionEnabled: aws.Bool(true), ClusterEnabled: aws.Bool(false), - UserGroupIds: []*string{aws.String("my-user-group")}, - NodeGroups: []*elasticache.NodeGroup{ + UserGroupIds: []string{"my-user-group"}, + NodeGroups: []ectypes.NodeGroup{ { NodeGroupId: aws.String("0001"), - PrimaryEndpoint: &elasticache.Endpoint{ + PrimaryEndpoint: &ectypes.Endpoint{ Address: aws.String("primary.localhost"), - Port: aws.Int64(6379), + Port: aws.Int32(6379), }, - ReaderEndpoint: &elasticache.Endpoint{ + ReaderEndpoint: &ectypes.Endpoint{ Address: aws.String("reader.localhost"), - Port: aws.Int64(6379), + Port: aws.Int32(6379), }, }, }, @@ -1784,15 +1784,15 @@ func TestDatabaseFromMemoryDBClusterNameOverride(t *testing.T) { } func TestExtraElastiCacheLabels(t *testing.T) { - cluster := &elasticache.ReplicationGroup{ + cluster := &ectypes.ReplicationGroup{ ReplicationGroupId: aws.String("my-redis"), } - tags := []*elasticache.Tag{ + tags := []ectypes.Tag{ {Key: aws.String("key1"), Value: aws.String("value1")}, {Key: aws.String("key2"), Value: aws.String("value2")}, } - nodes := []*elasticache.CacheCluster{ + nodes := []ectypes.CacheCluster{ { ReplicationGroupId: aws.String("some-other-redis"), EngineVersion: aws.String("8.8.8"), @@ -1805,7 +1805,7 @@ func TestExtraElastiCacheLabels(t *testing.T) { }, } - subnetGroups := []*elasticache.CacheSubnetGroup{ + subnetGroups := []ectypes.CacheSubnetGroup{ { CacheSubnetGroupName: aws.String("some-other-subnet-group"), VpcId: aws.String("some-other-vpc"), @@ -1818,9 +1818,9 @@ func TestExtraElastiCacheLabels(t *testing.T) { tests := []struct { name string - inputTags []*elasticache.Tag - inputNodes []*elasticache.CacheCluster - inputSubnetGroups []*elasticache.CacheSubnetGroup + inputTags []ectypes.Tag + inputNodes []ectypes.CacheCluster + inputSubnetGroups []ectypes.CacheSubnetGroup expectLabels map[string]string }{ { diff --git a/lib/srv/discovery/discovery_test.go b/lib/srv/discovery/discovery_test.go index 64a156bc5fd62..bb48372a1e141 100644 --- a/lib/srv/discovery/discovery_test.go +++ b/lib/srv/discovery/discovery_test.go @@ -41,6 +41,7 @@ import ( ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" "github.com/aws/aws-sdk-go-v2/service/eks" ekstypes "github.com/aws/aws-sdk-go-v2/service/eks/types" + "github.com/aws/aws-sdk-go-v2/service/elasticache" "github.com/aws/aws-sdk-go-v2/service/rds" rdstypes "github.com/aws/aws-sdk-go-v2/service/rds/types" "github.com/aws/aws-sdk-go-v2/service/redshift" @@ -3869,11 +3870,16 @@ func newPopulatedGCPProjectsMock() *mockProjectsAPI { } type fakeAWSClients struct { + ecClient db.ElastiCacheClient rdsClient db.RDSClient redshiftClient db.RedshiftClient rssClient db.RSSClient } +func (f fakeAWSClients) GetElastiCacheClient(cfg aws.Config, optFns ...func(*elasticache.Options)) db.ElastiCacheClient { + return f.ecClient +} + func (f fakeAWSClients) GetRDSClient(cfg aws.Config, optFns ...func(*rds.Options)) db.RDSClient { return f.rdsClient } diff --git a/lib/srv/discovery/fetchers/db/aws_elasticache.go b/lib/srv/discovery/fetchers/db/aws_elasticache.go index c5c5ebd5fcccf..b200e732e9b79 100644 --- a/lib/srv/discovery/fetchers/db/aws_elasticache.go +++ b/lib/srv/discovery/fetchers/db/aws_elasticache.go @@ -21,17 +21,26 @@ package db import ( "context" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/elasticache" - "github.com/aws/aws-sdk-go/service/elasticache/elasticacheiface" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/elasticache" + ectypes "github.com/aws/aws-sdk-go-v2/service/elasticache/types" "github.com/gravitational/trace" "github.com/gravitational/teleport/api/types" - "github.com/gravitational/teleport/lib/cloud" libcloudaws "github.com/gravitational/teleport/lib/cloud/aws" + "github.com/gravitational/teleport/lib/cloud/awsconfig" "github.com/gravitational/teleport/lib/srv/discovery/common" ) +// ElastiCacheClient is a subset of the AWS ElastiCache API. +type ElastiCacheClient interface { + elasticache.DescribeCacheClustersAPIClient + elasticache.DescribeCacheSubnetGroupsAPIClient + elasticache.DescribeReplicationGroupsAPIClient + + ListTagsForResource(ctx context.Context, in *elasticache.ListTagsForResourceInput, optFns ...func(*elasticache.Options)) (*elasticache.ListTagsForResourceOutput, error) +} + // newElastiCacheFetcher returns a new AWS fetcher for ElastiCache databases. func newElastiCacheFetcher(cfg awsFetcherConfig) (common.Fetcher, error) { return newAWSFetcher(cfg, &elastiCachePlugin{}) @@ -48,31 +57,32 @@ func (f *elastiCachePlugin) ComponentShortName() string { // // TODO(greedy52) support ElastiCache global datastore. func (f *elastiCachePlugin) GetDatabases(ctx context.Context, cfg *awsFetcherConfig) (types.Databases, error) { - ecClient, err := cfg.AWSClients.GetAWSElastiCacheClient(ctx, cfg.Region, - cloud.WithAssumeRole(cfg.AssumeRole.RoleARN, cfg.AssumeRole.ExternalID), - cloud.WithCredentialsMaybeIntegration(cfg.Integration), + awsCfg, err := cfg.AWSConfigProvider.GetConfig(ctx, cfg.Region, + awsconfig.WithAssumeRole(cfg.AssumeRole.RoleARN, cfg.AssumeRole.ExternalID), + awsconfig.WithCredentialsMaybeIntegration(cfg.Integration), ) if err != nil { return nil, trace.Wrap(err) } - clusters, err := getElastiCacheClusters(ctx, ecClient) + clt := cfg.awsClients.GetElastiCacheClient(awsCfg) + clusters, err := getElastiCacheClusters(ctx, clt) if err != nil { return nil, trace.Wrap(err) } - var eligibleClusters []*elasticache.ReplicationGroup + var eligibleClusters []ectypes.ReplicationGroup for _, cluster := range clusters { - if !libcloudaws.IsElastiCacheClusterSupported(cluster) { + if !libcloudaws.IsElastiCacheClusterSupported(&cluster) { cfg.Logger.DebugContext(ctx, "Skipping unsupported ElastiCache cluster", - "cluster", aws.StringValue(cluster.ReplicationGroupId), + "cluster", aws.ToString(cluster.ReplicationGroupId), ) continue } - if !libcloudaws.IsElastiCacheClusterAvailable(cluster) { + if !libcloudaws.IsElastiCacheClusterAvailable(&cluster) { cfg.Logger.DebugContext(ctx, "Skipping unavailable ElastiCache cluster", - "cluster", aws.StringValue(cluster.ReplicationGroupId), - "status", aws.StringValue(cluster.Status), + "cluster", aws.ToString(cluster.ReplicationGroupId), + "status", aws.ToString(cluster.Status), ) continue } @@ -86,7 +96,7 @@ func (f *elastiCachePlugin) GetDatabases(ctx context.Context, cfg *awsFetcherCon // Fetch more information to provide extra labels. Do not fail because some // of these labels are missing. - allNodes, err := getElastiCacheNodes(ctx, ecClient) + allNodes, err := getElastiCacheNodes(ctx, clt) if err != nil { if trace.IsAccessDenied(err) { cfg.Logger.DebugContext(ctx, "No permissions to describe nodes", "error", err) @@ -94,7 +104,7 @@ func (f *elastiCachePlugin) GetDatabases(ctx context.Context, cfg *awsFetcherCon cfg.Logger.InfoContext(ctx, "Failed to describe nodes", "error", err) } } - allSubnetGroups, err := getElastiCacheSubnetGroups(ctx, ecClient) + allSubnetGroups, err := getElastiCacheSubnetGroups(ctx, clt) if err != nil { if trace.IsAccessDenied(err) { cfg.Logger.DebugContext(ctx, "No permissions to describe subnet groups", "error", err) @@ -105,26 +115,26 @@ func (f *elastiCachePlugin) GetDatabases(ctx context.Context, cfg *awsFetcherCon var databases types.Databases for _, cluster := range eligibleClusters { - // Resource tags are not found in elasticache.ReplicationGroup but can - // be on obtained by elasticache.ListTagsForResource (one call per + // Resource tags are not found in ectypes.ReplicationGroup but can + // be on obtained by ectypes.ListTagsForResource (one call per // resource). - tags, err := getElastiCacheResourceTags(ctx, ecClient, cluster.ARN) + tags, err := getElastiCacheResourceTags(ctx, clt, cluster.ARN) if err != nil { if trace.IsAccessDenied(err) { cfg.Logger.DebugContext(ctx, "No permissions to list resource tags", "error", err) } else { cfg.Logger.InfoContext(ctx, "Failed to list resource tags for ElastiCache cluster", - "cluster", aws.StringValue(cluster.ReplicationGroupId), + "cluster", aws.ToString(cluster.ReplicationGroupId), "error", err, ) } } - extraLabels := common.ExtraElastiCacheLabels(cluster, tags, allNodes, allSubnetGroups) + extraLabels := common.ExtraElastiCacheLabels(&cluster, tags, allNodes, allSubnetGroups) - if dbs, err := common.NewDatabasesFromElastiCacheReplicationGroup(cluster, extraLabels); err != nil { + if dbs, err := common.NewDatabasesFromElastiCacheReplicationGroup(&cluster, extraLabels); err != nil { cfg.Logger.InfoContext(ctx, "Could not convert ElastiCache cluster to database resources", - "cluster", aws.StringValue(cluster.ReplicationGroupId), + "cluster", aws.ToString(cluster.ReplicationGroupId), "error", err, ) } else { @@ -135,76 +145,81 @@ func (f *elastiCachePlugin) GetDatabases(ctx context.Context, cfg *awsFetcherCon } // getElastiCacheClusters fetches all ElastiCache replication groups. -func getElastiCacheClusters(ctx context.Context, client elasticacheiface.ElastiCacheAPI) ([]*elasticache.ReplicationGroup, error) { - var clusters []*elasticache.ReplicationGroup - var pageNum int - - err := client.DescribeReplicationGroupsPagesWithContext( - ctx, +func getElastiCacheClusters(ctx context.Context, client ElastiCacheClient) ([]ectypes.ReplicationGroup, error) { + var out []ectypes.ReplicationGroup + pager := elasticache.NewDescribeReplicationGroupsPaginator(client, &elasticache.DescribeReplicationGroupsInput{}, - func(page *elasticache.DescribeReplicationGroupsOutput, lastPage bool) bool { - pageNum++ - clusters = append(clusters, page.ReplicationGroups...) - return pageNum <= maxAWSPages + func(opts *elasticache.DescribeReplicationGroupsPaginatorOptions) { + opts.StopOnDuplicateToken = true }, ) - return clusters, trace.Wrap(libcloudaws.ConvertRequestFailureError(err)) + for i := 0; i < maxAWSPages && pager.HasMorePages(); i++ { + page, err := pager.NextPage(ctx) + if err != nil { + return nil, trace.Wrap(libcloudaws.ConvertRequestFailureErrorV2(err)) + } + out = append(out, page.ReplicationGroups...) + } + return out, nil } // getElastiCacheNodes fetches all ElastiCache nodes that associated with a // replication group. -func getElastiCacheNodes(ctx context.Context, client elasticacheiface.ElastiCacheAPI) ([]*elasticache.CacheCluster, error) { - var nodes []*elasticache.CacheCluster - var pageNum int - - err := client.DescribeCacheClustersPagesWithContext( - ctx, +func getElastiCacheNodes(ctx context.Context, client ElastiCacheClient) ([]ectypes.CacheCluster, error) { + var out []ectypes.CacheCluster + pager := elasticache.NewDescribeCacheClustersPaginator(client, &elasticache.DescribeCacheClustersInput{}, - func(page *elasticache.DescribeCacheClustersOutput, lastPage bool) bool { - pageNum++ - - // There are three types of elasticache.CacheCluster: - // 1) a Memcache cluster. - // 2) a Redis node belongs to a single node deployment (legacy, no TLS support). - // 3) a Redis node belongs to a Redis replication group. - // Only the ones belong to replication groups are wanted. - for _, cacheCluster := range page.CacheClusters { - if cacheCluster.ReplicationGroupId != nil { - nodes = append(nodes, cacheCluster) - } - } - return pageNum <= maxAWSPages + func(opts *elasticache.DescribeCacheClustersPaginatorOptions) { + opts.StopOnDuplicateToken = true }, ) - return nodes, trace.Wrap(libcloudaws.ConvertRequestFailureError(err)) + for i := 0; i < maxAWSPages && pager.HasMorePages(); i++ { + page, err := pager.NextPage(ctx) + if err != nil { + return nil, trace.Wrap(libcloudaws.ConvertRequestFailureErrorV2(err)) + } + // There are three types of ectypes.CacheCluster: + // 1) a Memcache cluster. + // 2) a Redis node belongs to a single node deployment (legacy, no TLS support). + // 3) a Redis node belongs to a Redis replication group. + // Only the ones belong to replication groups are wanted. + for _, cacheCluster := range page.CacheClusters { + if cacheCluster.ReplicationGroupId != nil { + out = append(out, cacheCluster) + } + } + } + return out, nil } // getElastiCacheSubnetGroups fetches all ElastiCache subnet groups. -func getElastiCacheSubnetGroups(ctx context.Context, client elasticacheiface.ElastiCacheAPI) ([]*elasticache.CacheSubnetGroup, error) { - var subnetGroups []*elasticache.CacheSubnetGroup - var pageNum int - - err := client.DescribeCacheSubnetGroupsPagesWithContext( - ctx, +func getElastiCacheSubnetGroups(ctx context.Context, client ElastiCacheClient) ([]ectypes.CacheSubnetGroup, error) { + var out []ectypes.CacheSubnetGroup + pager := elasticache.NewDescribeCacheSubnetGroupsPaginator(client, &elasticache.DescribeCacheSubnetGroupsInput{}, - func(page *elasticache.DescribeCacheSubnetGroupsOutput, lastPage bool) bool { - pageNum++ - subnetGroups = append(subnetGroups, page.CacheSubnetGroups...) - return pageNum <= maxAWSPages + func(opts *elasticache.DescribeCacheSubnetGroupsPaginatorOptions) { + opts.StopOnDuplicateToken = true }, ) - return subnetGroups, trace.Wrap(libcloudaws.ConvertRequestFailureError(err)) + for i := 0; i < maxAWSPages && pager.HasMorePages(); i++ { + page, err := pager.NextPage(ctx) + if err != nil { + return nil, trace.Wrap(libcloudaws.ConvertRequestFailureErrorV2(err)) + } + out = append(out, page.CacheSubnetGroups...) + } + return out, nil } // getElastiCacheResourceTags fetches resource tags for provided ElastiCache // replication group. -func getElastiCacheResourceTags(ctx context.Context, client elasticacheiface.ElastiCacheAPI, resourceName *string) ([]*elasticache.Tag, error) { +func getElastiCacheResourceTags(ctx context.Context, client ElastiCacheClient, resourceName *string) ([]ectypes.Tag, error) { input := &elasticache.ListTagsForResourceInput{ ResourceName: resourceName, } - output, err := client.ListTagsForResourceWithContext(ctx, input) + output, err := client.ListTagsForResource(ctx, input) if err != nil { - return nil, trace.Wrap(libcloudaws.ConvertRequestFailureError(err)) + return nil, trace.Wrap(libcloudaws.ConvertRequestFailureErrorV2(err)) } return output.TagList, nil diff --git a/lib/srv/discovery/fetchers/db/aws_elasticache_test.go b/lib/srv/discovery/fetchers/db/aws_elasticache_test.go index 25382df73f437..3fe82d0598e64 100644 --- a/lib/srv/discovery/fetchers/db/aws_elasticache_test.go +++ b/lib/srv/discovery/fetchers/db/aws_elasticache_test.go @@ -21,12 +21,11 @@ package db import ( "testing" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/elasticache" + "github.com/aws/aws-sdk-go-v2/aws" + ectypes "github.com/aws/aws-sdk-go-v2/service/elasticache/types" "github.com/stretchr/testify/require" "github.com/gravitational/teleport/api/types" - "github.com/gravitational/teleport/lib/cloud" "github.com/gravitational/teleport/lib/cloud/mocks" "github.com/gravitational/teleport/lib/srv/discovery/common" ) @@ -36,37 +35,40 @@ func TestElastiCacheFetcher(t *testing.T) { elasticacheProd, elasticacheDatabasesProd, elasticacheProdTags := makeElastiCacheCluster(t, "ec1", "us-east-1", "prod", mocks.WithElastiCacheReaderEndpoint) elasticacheQA, elasticacheDatabasesQA, elasticacheQATags := makeElastiCacheCluster(t, "ec2", "us-east-1", "qa", mocks.WithElastiCacheConfigurationEndpoint) - elasticacheUnavailable, _, elasticacheUnavailableTags := makeElastiCacheCluster(t, "ec4", "us-east-1", "prod", func(cluster *elasticache.ReplicationGroup) { + elasticacheUnavailable, _, elasticacheUnavailableTags := makeElastiCacheCluster(t, "ec4", "us-east-1", "prod", func(cluster *ectypes.ReplicationGroup) { cluster.Status = aws.String("deleting") }) - elasticacheUnsupported, _, elasticacheUnsupportedTags := makeElastiCacheCluster(t, "ec5", "us-east-1", "prod", func(cluster *elasticache.ReplicationGroup) { + elasticacheUnsupported, _, elasticacheUnsupportedTags := makeElastiCacheCluster(t, "ec5", "us-east-1", "prod", func(cluster *ectypes.ReplicationGroup) { cluster.TransitEncryptionEnabled = aws.Bool(false) }) - elasticacheTagsByARN := map[string][]*elasticache.Tag{ - aws.StringValue(elasticacheProd.ARN): elasticacheProdTags, - aws.StringValue(elasticacheQA.ARN): elasticacheQATags, - aws.StringValue(elasticacheUnavailable.ARN): elasticacheUnavailableTags, - aws.StringValue(elasticacheUnsupported.ARN): elasticacheUnsupportedTags, + elasticacheTagsByARN := map[string][]ectypes.Tag{ + aws.ToString(elasticacheProd.ARN): elasticacheProdTags, + aws.ToString(elasticacheQA.ARN): elasticacheQATags, + aws.ToString(elasticacheUnavailable.ARN): elasticacheUnavailableTags, + aws.ToString(elasticacheUnsupported.ARN): elasticacheUnsupportedTags, } tests := []awsFetcherTest{ { name: "fetch all", - inputClients: &cloud.TestCloudClients{ - ElastiCache: &mocks.ElastiCacheMock{ - ReplicationGroups: []*elasticache.ReplicationGroup{elasticacheProd, elasticacheQA}, - TagsByARN: elasticacheTagsByARN, - }, + fetcherCfg: AWSFetcherFactoryConfig{ + AWSClients: fakeAWSClients{ + ecClient: &mocks.ElastiCacheClient{ + ReplicationGroups: []ectypes.ReplicationGroup{*elasticacheProd, *elasticacheQA}, + TagsByARN: elasticacheTagsByARN, + }}, }, inputMatchers: makeAWSMatchersForType(types.AWSMatcherElastiCache, "us-east-1", wildcardLabels), wantDatabases: append(elasticacheDatabasesProd, elasticacheDatabasesQA...), }, { name: "fetch prod", - inputClients: &cloud.TestCloudClients{ - ElastiCache: &mocks.ElastiCacheMock{ - ReplicationGroups: []*elasticache.ReplicationGroup{elasticacheProd, elasticacheQA}, - TagsByARN: elasticacheTagsByARN, + fetcherCfg: AWSFetcherFactoryConfig{ + AWSClients: fakeAWSClients{ + ecClient: &mocks.ElastiCacheClient{ + ReplicationGroups: []ectypes.ReplicationGroup{*elasticacheProd, *elasticacheQA}, + TagsByARN: elasticacheTagsByARN, + }, }, }, inputMatchers: makeAWSMatchersForType(types.AWSMatcherElastiCache, "us-east-1", envProdLabels), @@ -74,10 +76,12 @@ func TestElastiCacheFetcher(t *testing.T) { }, { name: "skip unavailable", - inputClients: &cloud.TestCloudClients{ - ElastiCache: &mocks.ElastiCacheMock{ - ReplicationGroups: []*elasticache.ReplicationGroup{elasticacheProd, elasticacheUnavailable}, - TagsByARN: elasticacheTagsByARN, + fetcherCfg: AWSFetcherFactoryConfig{ + AWSClients: fakeAWSClients{ + ecClient: &mocks.ElastiCacheClient{ + ReplicationGroups: []ectypes.ReplicationGroup{*elasticacheProd, *elasticacheUnavailable}, + TagsByARN: elasticacheTagsByARN, + }, }, }, inputMatchers: makeAWSMatchersForType(types.AWSMatcherElastiCache, "us-east-1", wildcardLabels), @@ -85,10 +89,12 @@ func TestElastiCacheFetcher(t *testing.T) { }, { name: "skip unsupported", - inputClients: &cloud.TestCloudClients{ - ElastiCache: &mocks.ElastiCacheMock{ - ReplicationGroups: []*elasticache.ReplicationGroup{elasticacheProd, elasticacheUnsupported}, - TagsByARN: elasticacheTagsByARN, + fetcherCfg: AWSFetcherFactoryConfig{ + AWSClients: fakeAWSClients{ + ecClient: &mocks.ElastiCacheClient{ + ReplicationGroups: []ectypes.ReplicationGroup{*elasticacheProd, *elasticacheUnsupported}, + TagsByARN: elasticacheTagsByARN, + }, }, }, inputMatchers: makeAWSMatchersForType(types.AWSMatcherElastiCache, "us-east-1", wildcardLabels), @@ -98,16 +104,16 @@ func TestElastiCacheFetcher(t *testing.T) { testAWSFetchers(t, tests...) } -func makeElastiCacheCluster(t *testing.T, name, region, env string, opts ...func(*elasticache.ReplicationGroup)) (*elasticache.ReplicationGroup, types.Databases, []*elasticache.Tag) { +func makeElastiCacheCluster(t *testing.T, name, region, env string, opts ...func(*ectypes.ReplicationGroup)) (*ectypes.ReplicationGroup, types.Databases, []ectypes.Tag) { cluster := mocks.ElastiCacheCluster(name, region, opts...) - tags := []*elasticache.Tag{{ + tags := []ectypes.Tag{{ Key: aws.String("env"), Value: aws.String(env), }} extraLabels := common.ExtraElastiCacheLabels(cluster, tags, nil, nil) - if aws.BoolValue(cluster.ClusterEnabled) { + if aws.ToBool(cluster.ClusterEnabled) { database, err := common.NewDatabaseFromElastiCacheConfigurationEndpoint(cluster, extraLabels) require.NoError(t, err) common.ApplyAWSDatabaseNameSuffix(database, types.AWSMatcherElastiCache) diff --git a/lib/srv/discovery/fetchers/db/aws_rds_test.go b/lib/srv/discovery/fetchers/db/aws_rds_test.go index b86529f22f7a6..fb297ffe12923 100644 --- a/lib/srv/discovery/fetchers/db/aws_rds_test.go +++ b/lib/srv/discovery/fetchers/db/aws_rds_test.go @@ -22,6 +22,7 @@ import ( "testing" "github.com/aws/aws-sdk-go-v2/aws" + elasticache "github.com/aws/aws-sdk-go-v2/service/elasticache" "github.com/aws/aws-sdk-go-v2/service/rds" rdstypes "github.com/aws/aws-sdk-go-v2/service/rds/types" "github.com/aws/aws-sdk-go-v2/service/redshift" @@ -308,11 +309,16 @@ func newRegionalFakeRDSClientProvider(cs map[string]RDSClient) fakeRegionalRDSCl } type fakeAWSClients struct { + ecClient ElastiCacheClient rdsClient RDSClient redshiftClient RedshiftClient rssClient RSSClient } +func (f fakeAWSClients) GetElastiCacheClient(cfg aws.Config, optFns ...func(*elasticache.Options)) ElastiCacheClient { + return f.ecClient +} + func (f fakeAWSClients) GetRDSClient(cfg aws.Config, optFns ...func(*rds.Options)) RDSClient { return f.rdsClient } diff --git a/lib/srv/discovery/fetchers/db/db.go b/lib/srv/discovery/fetchers/db/db.go index 72ee5093786d4..13deae724ecc6 100644 --- a/lib/srv/discovery/fetchers/db/db.go +++ b/lib/srv/discovery/fetchers/db/db.go @@ -23,6 +23,7 @@ import ( "log/slog" "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/elasticache" "github.com/aws/aws-sdk-go-v2/service/rds" "github.com/aws/aws-sdk-go-v2/service/redshift" rss "github.com/aws/aws-sdk-go-v2/service/redshiftserverless" @@ -71,6 +72,8 @@ func IsAzureMatcherType(matcherType string) bool { // AWSClientProvider provides AWS service API clients. type AWSClientProvider interface { + // GetElastiCacheClient provides an [ElasticacheClient]. + GetElastiCacheClient(cfg aws.Config, optFns ...func(*elasticache.Options)) ElastiCacheClient // GetRDSClient provides an [RDSClient]. GetRDSClient(cfg aws.Config, optFns ...func(*rds.Options)) RDSClient // GetRedshiftClient provides an [RedshiftClient]. @@ -81,6 +84,10 @@ type AWSClientProvider interface { type defaultAWSClients struct{} +func (defaultAWSClients) GetElastiCacheClient(cfg aws.Config, optFns ...func(*elasticache.Options)) ElastiCacheClient { + return elasticache.NewFromConfig(cfg, optFns...) +} + func (defaultAWSClients) GetRDSClient(cfg aws.Config, optFns ...func(*rds.Options)) RDSClient { return rds.NewFromConfig(cfg, optFns...) }