-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
feat: Aws certificate manager provider for exportable certificates #5592
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat: Aws certificate manager provider for exportable certificates #5592
Conversation
65fa867 to
0444696
Compare
WalkthroughAdds AWS Certificate Manager support: new ACM provider implementation, fake client and endpoint resolver, unit tests; updates API types, CRD schemas, docs to accept "CertificateManager"; and updates module dependencies to include AWS ACM SDK. Changes
Pre-merge checks✅ Passed checks (2 passed)
Comment |
Signed-off-by: aleksandarss <[email protected]>
…tests Signed-off-by: aleksandarss <[email protected]>
Signed-off-by: aleksandarss <[email protected]>
Signed-off-by: aleksandarss <[email protected]>
Signed-off-by: aleksandarss <[email protected]>
Signed-off-by: aleksandarss <[email protected]>
Signed-off-by: aleksandarss <[email protected]>
Signed-off-by: aleksandarss <[email protected]>
Signed-off-by: aleksandarss <[email protected]>
efe12e2 to
6bf24e9
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (8)
config/crds/bases/external-secrets.io_clustersecretstores.yaml (1)
645-652: Enum update for AWS service to includeCertificateManagerlooks correctAdding
CertificateManagerto theaws.serviceenum is consistent with the new ACM provider and simply broadens allowed values without breaking existing manifests. One optional follow‑up you might consider (in this or a later PR) is adjusting the nearbyawsdescription string (Line 469) so it no longer reads only as “Secret Manager provider,” since the service can now be SecretsManager, ParameterStore, or CertificateManager.providers/v1/aws/provider.go (1)
17-18: Update the package comment to include CertificateManager.The package documentation should reflect all supported services, including the newly added Certificate Manager.
🔎 Apply this diff to update the package comment:
-// Package aws implements AWS provider interfaces for External Secrets Operator, -// supporting SecretManager and ParameterStore services. +// Package aws implements AWS provider interfaces for External Secrets Operator, +// supporting SecretsManager, ParameterStore, and CertificateManager services.go.mod (1)
405-405: Consider updating to the latest ACM SDK version.v1.37.13 is not the latest version of the module. While no known security vulnerabilities exist for this version and the package maintains healthy release cadence, a more recent version may be available. Review the latest release notes to determine if an update is warranted.
config/crds/bases/external-secrets.io_secretstores.yaml (1)
645-652: ACM enum addition looks correct; consider aligning the AWS provider description.Adding
CertificateManagertospec.provider.aws.service.enumis consistent with the new ACM provider and will let v1SecretStoreobjects validate correctly. No schema issues here.Since
aws.servicenow supportsSecretsManager,ParameterStore, andCertificateManager, theawsdescription ("sync secrets using AWS Secret Manager provider") above is slightly misleading; consider updating it to reflect that this block configures AWS as a provider and the specific service is selected via theservicefield.providers/v1/aws/certificatemanager/certificatemanager_test.go (1)
166-189: RenamesuccessCasesand consider using subtests.The slice
successCasescontains both success and error scenarios, making the name misleading. Additionally, using numeric indices in error messages makes debugging difficult.🔎 Consider these improvements:
- Rename the slice:
- successCases := []*certificateManagerTestCase{ + testCases := []*certificateManagerTestCase{
- Add descriptive names and use subtests for better isolation:
testCases := []struct { name string tc *certificateManagerTestCase }{ {"valid certificate with all fields", makeValidCertificateManagerTestCaseCustom(setValidCertificate)}, {"certificate only", makeValidCertificateManagerTestCaseCustom(setCertificateOnly)}, {"certificate and chain", makeValidCertificateManagerTestCaseCustom(setCertificateAndChain)}, {"no data returned error", makeValidCertificateManagerTestCaseCustom(setNoDataReturned)}, {"export certificate failure", makeValidCertificateManagerTestCaseCustom(setExportCertFail)}, {"describe certificate failure", makeValidCertificateManagerTestCaseCustom(setDescribeCertFail)}, {"empty ARN error", makeValidCertificateManagerTestCaseCustom(setEmptyARN)}, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { // test logic here using tt.tc }) }This improves debuggability by showing which specific test case failed by name rather than index.
providers/v1/aws/certificatemanager/resolver.go (1)
37-49: Avoid shadowing theurlpackage name.Line 40 declares a variable named
urlthat shadows the importedurlpackage. While this code works, it reduces readability and could cause confusion.🔎 Apply this diff to use a more descriptive variable name:
func (c customEndpointResolver) ResolveEndpoint(ctx context.Context, params acm.EndpointParameters) (smithyendpoints.Endpoint, error) { endpoint := smithyendpoints.Endpoint{} if v := os.Getenv(ACMEndpointEnv); v != "" { - url, err := url.Parse(v) + parsedURL, err := url.Parse(v) if err != nil { return endpoint, fmt.Errorf("failed to parse acm endpoint %s: %w", v, err) } - endpoint.URI = *url + endpoint.URI = *parsedURL return endpoint, nil } defaultResolver := acm.NewDefaultEndpointResolverV2() return defaultResolver.ResolveEndpoint(ctx, params) }providers/v1/aws/certificatemanager/certificatemanager.go (2)
32-49: Unused field and interface methods.
- The
prefixfield (line 37) is stored but never used in any method implementation.ACMInterfacedefinesListCertificates,GetCertificate,AddTagsToCertificate, andRemoveTagsFromCertificate, but none of these are called by the provider.Consider removing unused code to reduce maintenance overhead.
🔎 Suggested diff
type CertificateManager struct { cfg *aws.Config client ACMInterface referentAuth bool - prefix string } // ACMInterface defines the subset of ACM API methods used by the provider. // see: https://docs.aws.amazon.com/sdk-for-go/api/service/acm/ type ACMInterface interface { DescribeCertificate(ctx context.Context, input *acm.DescribeCertificateInput, opts ...func(*acm.Options)) (*acm.DescribeCertificateOutput, error) ExportCertificate(ctx context.Context, input *acm.ExportCertificateInput, opts ...func(*acm.Options)) (*acm.ExportCertificateOutput, error) - ListCertificates(ctx context.Context, input *acm.ListCertificatesInput, opts ...func(*acm.Options)) (*acm.ListCertificatesOutput, error) - GetCertificate(ctx context.Context, input *acm.GetCertificateInput, opts ...func(*acm.Options)) (*acm.GetCertificateOutput, error) - AddTagsToCertificate(ctx context.Context, input *acm.AddTagsToCertificateInput, opts ...func(*acm.Options)) (*acm.AddTagsToCertificateOutput, error) - RemoveTagsFromCertificate(ctx context.Context, input *acm.RemoveTagsFromCertificateInput, opts ...func(*acm.Options)) (*acm.RemoveTagsFromCertificateOutput, error) }
84-99: Consider supportingremoteRef.Propertyfor component selection.The current implementation concatenates all components (certificate, chain, private key) into a single value. For Kubernetes TLS secrets, users typically need separate
tls.crtandtls.keyfields.Supporting
ref.Propertywould allow users to extract individual components:
certificate→ just the certificatechain→ just the certificate chainprivateKey→ just the private key- empty/default → all concatenated (current behavior)
This would improve flexibility for secret templating.
🔎 Example implementation
switch ref.Property { case "certificate": if exportOut.Certificate != nil { return []byte(*exportOut.Certificate), nil } return nil, fmt.Errorf("certificate not available") case "chain": if exportOut.CertificateChain != nil { return []byte(*exportOut.CertificateChain), nil } return nil, fmt.Errorf("certificate chain not available") case "privateKey": if exportOut.PrivateKey != nil { return []byte(*exportOut.PrivateKey), nil } return nil, fmt.Errorf("private key not available") default: // Current behavior - return all concatenated }
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
go.sumis excluded by!**/*.sumproviders/v1/aws/go.sumis excluded by!**/*.sum
📒 Files selected for processing (14)
apis/externalsecrets/v1/secretstore_aws_types.go(2 hunks)config/crds/bases/external-secrets.io_clustersecretstores.yaml(1 hunks)config/crds/bases/external-secrets.io_secretstores.yaml(1 hunks)deploy/crds/bundle.yaml(2 hunks)docs/api/spec.md(1 hunks)go.mod(1 hunks)providers/v1/aws/certificatemanager/certificatemanager.go(1 hunks)providers/v1/aws/certificatemanager/certificatemanager_test.go(1 hunks)providers/v1/aws/certificatemanager/fake/fake.go(1 hunks)providers/v1/aws/certificatemanager/resolver.go(1 hunks)providers/v1/aws/go.mod(4 hunks)providers/v1/aws/provider.go(4 hunks)tests/__snapshot__/clustersecretstore-v1.yaml(1 hunks)tests/__snapshot__/secretstore-v1.yaml(1 hunks)
👮 Files not reviewed due to content moderation or server errors (1)
- docs/api/spec.md
🧰 Additional context used
🧬 Code graph analysis (2)
providers/v1/aws/provider.go (2)
apis/externalsecrets/v1/secretstore_aws_types.go (1)
AWSServiceCertificateManager(67-67)providers/v1/aws/certificatemanager/certificatemanager.go (1)
New(52-61)
providers/v1/aws/certificatemanager/fake/fake.go (1)
providers/v1/aws/certificatemanager/certificatemanager.go (1)
New(52-61)
🔇 Additional comments (18)
providers/v1/aws/go.mod (3)
3-11: Verify Go version downgrade and AWS SDK compatibility.The Go version has been downgraded to 1.25.1 (line 3). If this is a minimum version requirement, clarify in the commit message. Additionally, ensure the AWS SDK versions across config, credentials, secretsmanager, ssm, and sts are compatible and have not introduced breaking changes.
45-45: LGTM on ACM SDK addition.The direct require for
github.com/aws/aws-sdk-go-v2/service/acm v1.37.13looks appropriate for the new ACM provider implementation.Also applies to: 45-45
29-49: Verify transitive dependencies of ACM SDK v1.37.13.The dependencies flagged—
oracle/oci-go-sdk/v65,sony/gobreaker,gofrs/flock, andgolang.org/x/mod—are confirmed present in lines 29-34 and 46-49. Confirm that ACM SDK v1.37.13 provides the required DescribeCertificate and ExportCertificate APIs, but determine whether these indirect dependencies are genuinely transitive from ACM or represent unrelated module changes.oracle/oci-go-sdkparticularly warrants clarification for an AWS-only feature.tests/__snapshot__/clustersecretstore-v1.yaml (1)
94-94: Documentation update aligns with ACM provider addition.The service enum comment correctly reflects that
CertificateManageris now an available option alongsideSecretsManagerandParameterStore. This snapshot update properly documents the new provider capability.Also applies to: 94-94
tests/__snapshot__/secretstore-v1.yaml (1)
94-94: LGTM!The comment correctly documents the new CertificateManager enum value alongside the existing options.
apis/externalsecrets/v1/secretstore_aws_types.go (1)
55-67: LGTM!The enum extension follows the established pattern and includes proper documentation. The kubebuilder validation annotation is correctly updated to include the new CertificateManager service type.
providers/v1/aws/provider.go (1)
167-168: LGTM!The CertificateManager integration follows the established pattern for both referent auth and normal auth scenarios. The calls to
certificatemanager.New()correctly pass the required configuration, prefix, and referentAuth flag.Also applies to: 224-225
providers/v1/aws/certificatemanager/certificatemanager_test.go (6)
37-46: LGTM - Test data setup is appropriate.The test constants provide reasonable fixtures for unit testing. The truncated certificate strings are sufficient for testing the concatenation and formatting logic without bloating the test file.
48-109: LGTM - Well-structured test case factories.The factory functions and functional options pattern make test cases easy to construct and customize. This approach enhances test readability and maintainability.
111-121: LGTM - Endpoint resolver test is correct.The test properly validates the custom endpoint resolution behavior and uses
t.Setenvfor automatic cleanup.
192-240: LGTM - Unsupported operations correctly tested.The tests appropriately verify that unsupported operations return errors with clear messages. The consistent pattern across all tests is good.
242-291: LGTM - Lifecycle and validation tests are appropriate.The tests provide adequate coverage for validation, close, and constructor functionality with clear assertions.
293-349: LGTM - Test helpers and mocks are well-implemented.The helper functions and mock implementations are minimal, focused, and appropriate for the test requirements.
providers/v1/aws/certificatemanager/resolver.go (1)
30-31: LGTM - Environment variable constant is well-defined.Exporting the constant allows tests to reference it, ensuring consistency.
providers/v1/aws/certificatemanager/certificatemanager.go (1)
102-142: LGTM for unsupported operations and lifecycle methods.The unsupported operations (
DeleteSecret,GetAllSecrets,GetSecretMap,SecretExists,PushSecret) appropriately return explicit errors, aligning with the read-only design of this provider.Minor note:
Validate(line 113) usescontext.Background()instead of accepting a context parameter. This is consistent with other providers in the codebase, so it's acceptable.providers/v1/aws/certificatemanager/fake/fake.go (3)
29-56: LGTM!The
Clientstruct and function types follow the standard fake client pattern, properly mirroring theACMInterfacefor test mocking.
57-137: LGTM!The method implementations correctly delegate to their respective function fields. The constructor helpers provide convenient ways to create mock behaviors.
Note: Calling any method without first setting its corresponding function field will panic due to nil function call. This is standard for test fakes and acceptable since tests should configure all used mock methods.
139-147: LGTM!The
WithValuehelper provides strict input validation forExportCertificatemocking, which is useful for verifying that the provider passes correct parameters. The use ofcmpopts.IgnoreUnexportedappropriately handles AWS SDK internal fields.If needed in the future, similar helpers could be added for other methods.
Signed-off-by: aleksandarss <[email protected]>
Signed-off-by: aleksandarss <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
providers/v1/aws/certificatemanager/certificatemanager_test.go (1)
173-197: Consider renaming test cases array and using subtests.The variable
successCases(line 173) contains both success and error test cases (lines 177-181 are error scenarios), which is misleading. Additionally, using subtests witht.Runwould improve test output clarity and make it easier to identify which specific scenario fails.💡 Suggested improvements
Rename the test cases array:
- successCases := []*certificateManagerTestCase{ + testCases := []*certificateManagerTestCase{Consider restructuring with subtests for better test organization:
testCases := map[string]struct { setup func(*certificateManagerTestCase) expectedSecret string expectError string }{ "valid certificate with all fields": { setup: setValidCertificate, expectedSecret: testCertWithAll, }, "certificate only": { setup: setCertificateOnly, expectedSecret: testCertificate + "\n", }, // ... other cases } for name, tc := range testCases { t.Run(name, func(t *testing.T) { // test logic }) }
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
providers/v1/aws/certificatemanager/certificatemanager.go(1 hunks)providers/v1/aws/certificatemanager/certificatemanager_test.go(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- providers/v1/aws/certificatemanager/certificatemanager.go
🧰 Additional context used
🧬 Code graph analysis (1)
providers/v1/aws/certificatemanager/certificatemanager_test.go (3)
providers/v1/aws/certificatemanager/fake/fake.go (3)
Client(30-37)DescribeCertificateFn(40-40)NewDescribeCertificateFn(63-67)providers/v1/aws/certificatemanager/resolver.go (1)
ACMEndpointEnv(31-31)providers/v1/aws/certificatemanager/certificatemanager.go (2)
New(52-61)CertificateManager(33-38)
…anager and ParameterStore Signed-off-by: aleksandarss <[email protected]>
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
providers/v1/aws/provider_test.go (1)
474-488: LGTM! Consider adding CertificateManager client creation test.The test case correctly validates the CertificateManager service with a valid region, following the established pattern. However,
TestProvider(lines 39-153) lacks a test case for creating a CertificateManager client, unlike SecretsManager and ParameterStore which have dedicated cases verifyingexpType. Consider adding:{ test: "should create certificate manager client", expErr: false, expType: &certificatemanager.CertificateManager{}, store: &esv1.SecretStore{ Spec: esv1.SecretStoreSpec{ Provider: &esv1.SecretStoreProvider{ AWS: &esv1.AWSProvider{ Service: esv1.AWSServiceCertificateManager, }, }, }, }, },
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
providers/v1/aws/provider.go(4 hunks)providers/v1/aws/provider_test.go(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
providers/v1/aws/provider_test.go (2)
providers/v1/aws/provider.go (1)
Provider(48-48)apis/externalsecrets/v1/secretstore_aws_types.go (1)
AWSServiceCertificateManager(67-67)
providers/v1/aws/provider.go (2)
apis/externalsecrets/v1/secretstore_aws_types.go (1)
AWSServiceCertificateManager(67-67)providers/v1/aws/certificatemanager/certificatemanager.go (1)
New(52-61)
🔇 Additional comments (4)
providers/v1/aws/provider.go (4)
29-29: LGTM!The new imports for the ACM SDK and certificatemanager package are correctly added and necessary for the new functionality.
Also applies to: 37-37
128-136: LGTM!The region validation for CertificateManager correctly mirrors the pattern used for SecretsManager and ParameterStore, using
acm.NewDefaultEndpointResolverV2()for early region validation. This addresses the previous review feedback.
175-176: LGTM!The CertificateManager client creation in the referent auth path correctly follows the established pattern, passing
referentAuth=trueand the appropriate prefix.
232-233: LGTM!The CertificateManager client creation in the standard path correctly uses the authenticated config and passes
referentAuth=false. The implementation is consistent with SecretsManager and ParameterStore.



Problem Statement
External Secrets Operator currently has no support for AWS Certificate Manager (ACM) as a provider. This makes it impossible to directly consume certificates that are managed in ACM from within Kubernetes.
AWS now supports exportable certificates in ACM, which means certificates (including private keys) can be retrieved via the AWS API. Despite this, users still cannot use ACM as a source of truth in ESO and are forced into workarounds such as exporting certificates manually or copying them into Secrets Manager first.
These workarounds introduce unnecessary complexity, additional moving parts, and operational overhead, and they go against the declarative secrets management model that ESO aims to provide.
The lack of native ACM support prevents users from managing TLS certificates in a clean and consistent way when AWS ACM is already their primary certificate authority.
Related Issue
Fixes #4936
Proposed Changes
This PR adds native support for AWS Certificate Manager (ACM) as a provider in External Secrets Operator.
Specifically:
Extend the AWS provider enum with a new value
CertificateManager, allowing ACM to be configured inSecretStoreandClusterSecretStore.Introduce a new provider implementation under
providers/v1/aws/certificatemanager:GetSecretmethod using AWS ACMDescribeCertificateandExportCertificateAPIs.remoteRef.key.Explicitly mark unsupported operations (push, delete, list, map, existence checks) as errors, since ACM is treated as a read-only source.
Add the required AWS SDK v2 dependency (
github.com/aws/aws-sdk-go-v2/service/acm) to support ACM API calls.This implementation allows ACM to be used as a source of truth for TLS certificates in ESO without requiring intermediate storage, while keeping the scope intentionally limited to read-only certificate retrieval.
Format
Please ensure that your PR follows the following format for the title:
Where
scopeis optionally one of:Checklist
git commit --signoffmake testmake reviewable