-
Notifications
You must be signed in to change notification settings - Fork 47
Add AutoProvider certificate manager
#227
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?
Conversation
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: ArkaSaha30 The full list of commands accepted by this bot can be found here.
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
f819caf to
82651bc
Compare
AutoProvider interface functionsAutoProvider interface functions
|
@abdurrehman107 @ballista01 @ivanvc @hakman PTAL. Reviewing this PR is also an opportunity to get familiar with the certificate design & implementation. Reference: https://docs.google.com/document/d/1k8os77Hqxe36nUaZ31YqBGBuXcgdozVPppJYOfwUDFA/edit?tab=t.0 |
|
cc @frederiko |
276d55e to
9886706
Compare
AutoProvider interface functionsAutoProvider certificate manager
pkg/certificate/auto/provider.go
Outdated
| return nil, fmt.Errorf("failed to get certificate: %w", err) | ||
| } | ||
|
|
||
| return &interfaces.Config{}, nil |
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.
We should get the info from the certificate included in the secret.
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.
Hi @ahrtr , I explored getting the original config from the secret itself but it will be a complicated one since we are manipulating the config and passing it to transport.SelfCert() which in turn creats the certificate.
Another workaround is to retrieve the certificateConfig from the etcdCluster object via the certificateSecret.ownerRef.Name, but it will cause a circular dependency
What can be the suggested solution here?
cc @ivanvc
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.
but it will be a complicated one
Where is the complication? I don't see a PoC so far, nor a reasonable explanation.
The easiest solution is to just save the config passed to EnsureCertificateSecret, and return it in GetCertificateConfig. The problem is that it isn't the final real configuration that we used to create the certificate.
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.
Sure, I have updated the GetCertificateConfig to return the saved user-defined config
ivanvc
left a comment
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.
Thanks for the pull request, Arka. I left some observations, comments, and suggestions.
9d90c9e to
e2c11fd
Compare
|
/test pull-etcd-operator-test-e2e |
|
@ArkaSaha30, where do we stand in this pull request? Is this ready for another review? |
e2c11fd to
ba83138
Compare
This commit will add the initial scaffolding for AutoProvider. Signed-off-by: ArkaSaha30 <[email protected]>
ba83138 to
6027cab
Compare
Signed-off-by: ArkaSaha30 <[email protected]>
Signed-off-by: ArkaSaha30 <[email protected]>
Signed-off-by: ArkaSaha30 <[email protected]>
6027cab to
ccf14e3
Compare
neolit123
left a comment
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.
thanks for the updates
generally LGTM, just a few minor comments from me
| autoConfig := ec.Spec.TLS.ProviderCfg.AutoCfg | ||
| duration, err := time.ParseDuration(autoConfig.ValidityDuration) | ||
| if err != nil { | ||
| log.Printf("Failed to parse ValidityDuration: %s", err) |
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.
should it return an error here?
| log.Printf("Failed to parse ValidityDuration: %s", err) | ||
| } | ||
|
|
||
| var getAltNames certInterface.AltNames |
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.
maybe just call it altNames
|
|
||
| func createCertificate(ec *ecv1alpha1.EtcdCluster, ctx context.Context, c client.Client, certName string) error { | ||
| cert, certErr := certificate.NewProvider(certificate.ProviderType(ec.Spec.TLS.Provider), c) | ||
| // tls field is present but spec is empty |
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.
| // tls field is present but spec is empty | |
| // The TLS field is present but spec is empty |
| func createClientCertificate(ctx context.Context, ec *ecv1alpha1.EtcdCluster, c client.Client, scheme *runtime.Scheme) error { | ||
| certName := getClientCertName(ec.Name) | ||
| createClientCertErr := createCertificate(ec, ctx, c, certName) | ||
| patchCertErr := patchCertificateSecret(ctx, ec, c, scheme, certName) |
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.
just use a single err and check it on every stage.
| patchCertErr := patchCertificateSecret(ctx, ec, c, scheme, serverCertName) | ||
| if patchCertErr != nil { | ||
| return fmt.Errorf("patching certificate secret: %s with ownerReference failed: %w", serverCertName, patchCertErr) |
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.
same comments as above
|
|
||
| func (ac *Provider) EnsureCertificateSecret(ctx context.Context, secretName, namespace string, | ||
| cfg *interfaces.Config) error { | ||
| // save the user defined AutoConfig so that it can be returned from GetCertificateConfig |
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.
| // save the user defined AutoConfig so that it can be returned from GetCertificateConfig | |
| // Save the user defined AutoConfig so that it can be returned from GetCertificateConfig |
| if err := controllerutil.SetControllerReference(ec, getCertSecret, scheme); err != nil { | ||
| return err | ||
| } | ||
| if updateErr := c.Update(ctx, getCertSecret); updateErr != nil { |
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.
err
| } | ||
| } | ||
|
|
||
| log.Printf("hosts: %v", hosts) |
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.
maybe add a more detailed log msg, like "calling SelfCert with hosts: ...`
|
|
||
| caPEM, err := os.ReadFile(caPath) | ||
| if err != nil || len(caPEM) == 0 { | ||
| // use certPEM when CA file is not found or empty |
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.
| // use certPEM when CA file is not found or empty | |
| // Use certPEM when CA file is not found or empty |
|
|
||
| // Validate cert and key pair | ||
| if _, err := tls.X509KeyPair(certPEM, keyPEM); err != nil { | ||
| return fmt.Errorf("generated keypair invalid: %w", err) |
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.
| return fmt.Errorf("generated keypair invalid: %w", err) | |
| return fmt.Errorf("generated keypair is invalid: %w", err) |
| // Ensure the operator has TLS credentials when the cluster requests TLS. | ||
| if ec.Spec.TLS != nil { | ||
| if err := createClientCertificate(ctx, ec, r.Client); err != nil { | ||
| if err := createClientCertificate(ctx, ec, r.Client, r.Scheme); err != nil { |
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.
r.Client also has the Scheme embedded. Can't that be used instead of passing Scheme through the call chain?
|
|
||
| func patchCertificateSecret(ctx context.Context, ec *ecv1alpha1.EtcdCluster, c client.Client, scheme *runtime.Scheme, certSecretName string) error { | ||
| getCertSecret := &corev1.Secret{} | ||
| getErr := c.Get(ctx, client.ObjectKey{Name: certSecretName, Namespace: ec.Namespace}, getCertSecret) |
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.
also, the Get call and the error check can be folded to the same block.
|
|
||
| var userAutoConfig *interfaces.Config | ||
|
|
||
| func (ac *Provider) EnsureCertificateSecret(ctx context.Context, secretName, namespace string, |
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.
Whenever the "name, namespace" pattern is used, an object key can be used. This ensures consistency. See github.com/kubernetes-sigs/controller-runtime/blob/main/pkg/client/example_test.go for example.
| func (ac *Provider) EnsureCertificateSecret(ctx context.Context, secretName, namespace string, | |
| func (ac *Provider) EnsureCertificateSecret(ctx context.Context, secretKey ctrlruntimeclient.ObjectKey |
| return interfaces.ErrTLSCert | ||
| } | ||
|
|
||
| decodeCertificatePem, _ := pem.Decode(certificateData) |
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.
Do we not need an err check?
| err := ac.Get(ctx, client.ObjectKey{Name: secretName, Namespace: namespace}, &secret) | ||
| if k8serrors.IsNotFound(err) { | ||
| return nil | ||
| } | ||
| if err != nil { | ||
| return err | ||
| } |
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.
| err := ac.Get(ctx, client.ObjectKey{Name: secretName, Namespace: namespace}, &secret) | |
| if k8serrors.IsNotFound(err) { | |
| return nil | |
| } | |
| if err != nil { | |
| return err | |
| } | |
| if err := ac.Get(ctx, client.ObjectKey{Name: secretName, Namespace: namespace}, &secret); err != nil { | |
| if k8serrors.IsNotFound(err) { | |
| return nil | |
| } | |
| return err | |
| } |
|
|
||
| // parsePrivateKey parses the private key from the PEM-encoded data. | ||
| func parsePrivateKey(privateKeyData []byte) (crypto.PrivateKey, error) { | ||
| block, _ := pem.Decode(privateKeyData) |
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.
same question as above
| if err != nil { | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to parse private key: %w", err) | ||
| } | ||
| } |
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.
| if err != nil { | |
| if err != nil { | |
| return nil, fmt.Errorf("failed to parse private key: %w", err) | |
| } | |
| } | |
| if err != nil { | |
| return nil, fmt.Errorf("failed to parse private key: %w", err) | |
| } |
|
@ArkaSaha30 pls feedback after you resolve all the review comments, thx |
This PR will add
AutoProvidercertificate manager as a part of generating default self-signed certificates: