-
Notifications
You must be signed in to change notification settings - Fork 518
AGENT-1449: Add single-phase IRI registry credential rotation #6414
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?
Changes from all commits
62bf770
bdab432
ac97036
0711521
565dd42
cf50618
110ca75
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| package internalreleaseimage | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "strings" | ||
|
|
||
| ctrlcommon "github.com/openshift/machine-config-operator/pkg/controller/common" | ||
| "golang.org/x/crypto/bcrypt" | ||
| corev1 "k8s.io/api/core/v1" | ||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
| clientset "k8s.io/client-go/kubernetes" | ||
| "k8s.io/klog/v2" | ||
| ) | ||
|
|
||
| // generateHtpasswdEntry generates an htpasswd-formatted line for the given username | ||
| // and password using bcrypt hashing. | ||
| func generateHtpasswdEntry(username, password string) (string, error) { | ||
| hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) | ||
| if err != nil { | ||
| return "", fmt.Errorf("failed to generate bcrypt hash: %w", err) | ||
| } | ||
| return fmt.Sprintf("%s:%s", username, string(hash)), nil | ||
| } | ||
|
|
||
| // HtpasswdMatchesPassword reports whether the given htpasswd line matches | ||
| // the provided username and password. | ||
| func HtpasswdMatchesPassword(htpasswd, username, password string) bool { | ||
| prefix := username + ":" | ||
| if !strings.HasPrefix(htpasswd, prefix) { | ||
| return false | ||
| } | ||
| hash := []byte(strings.TrimPrefix(htpasswd, prefix)) | ||
| return bcrypt.CompareHashAndPassword(hash, []byte(password)) == nil | ||
| } | ||
|
|
||
| // reconcileHtpasswd ensures the htpasswd field in the IRI auth secret is in | ||
| // sync with the password field. If the password has changed (or htpasswd is | ||
| // missing), it generates a new bcrypt hash and updates the secret. This is the | ||
| // trigger for single-phase credential rotation: the updated htpasswd causes the | ||
| // MachineConfig to be re-rendered, which MCDs roll out to nodes. Brief registry | ||
| // downtime during the rollout is accepted. | ||
| func reconcileHtpasswd(kubeClient clientset.Interface, authSecret *corev1.Secret) (*corev1.Secret, error) { | ||
| password := string(authSecret.Data["password"]) | ||
| if password == "" { | ||
| return nil, fmt.Errorf("IRI auth secret %s/%s missing or empty \"password\" field", authSecret.Namespace, authSecret.Name) | ||
| } | ||
| htpasswd := string(authSecret.Data["htpasswd"]) | ||
|
|
||
| if HtpasswdMatchesPassword(htpasswd, ctrlcommon.IRIRegistryUsername, password) { | ||
| return authSecret, nil | ||
| } | ||
|
|
||
| klog.V(4).Infof("IRI auth secret htpasswd is out of sync with password, regenerating") | ||
|
|
||
| newHtpasswd, err := generateHtpasswdEntry(ctrlcommon.IRIRegistryUsername, password) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to generate htpasswd: %w", err) | ||
| } | ||
|
|
||
| updated := authSecret.DeepCopy() | ||
| updated.Data["htpasswd"] = []byte(newHtpasswd) | ||
|
|
||
| result, err := kubeClient.CoreV1().Secrets(authSecret.Namespace).Update( | ||
| context.TODO(), updated, metav1.UpdateOptions{}) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to update IRI auth secret: %w", err) | ||
| } | ||
|
Comment on lines
+61
to
+68
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win Add conflict retry and a bounded context for the Secret update.
Also pass a context with a deadline instead of ♻️ Proposed change-func reconcileHtpasswd(kubeClient clientset.Interface, authSecret *corev1.Secret) (*corev1.Secret, error) {
+func reconcileHtpasswd(ctx context.Context, kubeClient clientset.Interface, authSecret *corev1.Secret) (*corev1.Secret, error) {
@@
- updated := authSecret.DeepCopy()
- updated.Data["htpasswd"] = []byte(newHtpasswd)
-
- result, err := kubeClient.CoreV1().Secrets(authSecret.Namespace).Update(
- context.TODO(), updated, metav1.UpdateOptions{})
- if err != nil {
- return nil, fmt.Errorf("failed to update IRI auth secret: %w", err)
- }
+ var result *corev1.Secret
+ if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
+ cur, err := kubeClient.CoreV1().Secrets(authSecret.Namespace).Get(ctx, authSecret.Name, metav1.GetOptions{})
+ if err != nil {
+ return err
+ }
+ if cur.Data == nil {
+ cur.Data = map[string][]byte{}
+ }
+ cur.Data["htpasswd"] = []byte(newHtpasswd)
+ result, err = kubeClient.CoreV1().Secrets(cur.Namespace).Update(ctx, cur, metav1.UpdateOptions{})
+ return err
+ }); err != nil {
+ return nil, fmt.Errorf("failed to update IRI auth secret: %w", err)
+ }As per path instructions: "context.Context for cancellation and timeouts". 🤖 Prompt for AI AgentsSource: Path instructions |
||
|
|
||
| klog.Infof("Regenerated IRI auth secret htpasswd for credential rotation (secret %s/%s)", authSecret.Namespace, authSecret.Name) | ||
| return result, 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.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Fix the gofmt violation and fail fast in the helper.
golangci-lint reports the file is not properly formatted at line 304. Run
gofmt -won the file.Use
require.NoErrorin the helper. Withassert.NoError, generation failure returns an empty string and the table cases continue with invalid data.♻️ Proposed change
func mustGenerateHtpasswd(t *testing.T, password string) string { t.Helper() entry, err := generateHtpasswdEntry(ctrlcommon.IRIRegistryUsername, password) - assert.NoError(t, err) + require.NoError(t, err) return entry }📝 Committable suggestion
🧰 Tools
🪛 golangci-lint (2.12.2)
[error] 304-304: File is not properly formatted
(gofmt)
🤖 Prompt for AI Agents
Source: Linters/SAST tools