ARO-RP/pkg/cluster/acrtoken.go

179 строки
5.5 KiB
Go

package cluster
// Copyright (c) Microsoft Corporation.
// Licensed under the Apache License 2.0.
import (
"context"
"time"
corev1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
v1 "k8s.io/client-go/applyconfigurations/core/v1"
"k8s.io/client-go/util/retry"
"github.com/Azure/ARO-RP/pkg/api"
"github.com/Azure/ARO-RP/pkg/operator"
"github.com/Azure/ARO-RP/pkg/util/acrtoken"
"github.com/Azure/ARO-RP/pkg/util/pullsecret"
)
var pullSecretName = types.NamespacedName{Name: "pull-secret", Namespace: "openshift-config"}
func (m *manager) ensureACRToken(ctx context.Context) error {
if m.env.IsLocalDevelopmentMode() {
return nil
}
token, err := acrtoken.NewManager(m.env, m.localFpAuthorizer)
if err != nil {
return err
}
rp := token.GetRegistryProfile(m.doc.OpenShiftCluster)
if rp == nil {
// 1. choose a name and establish the intent to create a token with
// that name
rp = token.NewRegistryProfile(m.doc.OpenShiftCluster)
m.doc, err = m.db.PatchWithLease(ctx, m.doc.Key, func(doc *api.OpenShiftClusterDocument) error {
token.PutRegistryProfile(doc.OpenShiftCluster, rp)
return nil
})
if err != nil {
return err
}
}
if rp.Password == "" {
// 2. ensure a token with the chosen name exists, generate a
// password for it and store it in the database
password, err := token.EnsureTokenAndPassword(ctx, rp)
if err != nil {
return err
}
rp.Password = api.SecureString(password)
m.doc, err = m.db.PatchWithLease(ctx, m.doc.Key, func(doc *api.OpenShiftClusterDocument) error {
token.PutRegistryProfile(doc.OpenShiftCluster, rp)
return nil
})
if err != nil {
return err
}
}
return nil
}
func (m *manager) rotateACRTokenPassword(ctx context.Context) error {
// we do not want to rotate tokens in local development
if m.env.IsLocalDevelopmentMode() || m.env.IsCI() {
return nil
}
token, err := acrtoken.NewManager(m.env, m.localFpAuthorizer)
if err != nil {
return err
}
registryProfile := token.GetRegistryProfile(m.doc.OpenShiftCluster)
err = token.RotateTokenPassword(ctx, registryProfile)
if err != nil {
return err
}
m.doc, err = m.db.PatchWithLease(ctx, m.doc.Key, func(doc *api.OpenShiftClusterDocument) error {
token.PutRegistryProfile(doc.OpenShiftCluster, registryProfile)
return nil
})
if err != nil {
return err
}
// update cluster pull secret in openshift-azure-operator namespace
// secret is stored as a .dockerconfigjson string in the .dockerconfigjson key
encodedDockerConfigJson, _, err := pullsecret.SetRegistryProfiles("", registryProfile)
if err != nil {
return err
}
// wait for response from operator that reconciliation is completed successfully
pullSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: operator.SecretName,
Namespace: operator.Namespace,
},
Data: make(map[string][]byte),
}
pullSecret.Data[corev1.DockerConfigJsonKey] = []byte(encodedDockerConfigJson)
_, err = m.kubernetescli.CoreV1().Secrets(operator.Namespace).Update(ctx, pullSecret, metav1.UpdateOptions{})
if err != nil {
return err
}
err = retryOperation(func() error {
return m.rotateOpenShiftConfigSecret(ctx, pullSecret.Data[corev1.DockerConfigJsonKey])
})
if err != nil {
return err
}
return nil
}
func (m *manager) rotateOpenShiftConfigSecret(ctx context.Context, encodedDockerConfigJson []byte) error {
openshiftConfigSecret, err := m.kubernetescli.CoreV1().Secrets(pullSecretName.Namespace).Get(ctx, pullSecretName.Name, metav1.GetOptions{})
if err != nil && !kerrors.IsNotFound(err) {
return err
}
// by default, we create a patch with only the rotated acr token
applyConfiguration := v1.Secret(pullSecretName.Name, pullSecretName.Namespace).
WithData(map[string][]byte{corev1.DockerConfigJsonKey: encodedDockerConfigJson}).
WithType(corev1.SecretTypeDockerConfigJson)
recreationOfSecretRequired := openshiftConfigSecret == nil ||
(openshiftConfigSecret.Type != corev1.SecretTypeDockerConfigJson || openshiftConfigSecret.Data == nil) ||
(openshiftConfigSecret.Immutable != nil && *openshiftConfigSecret.Immutable)
if recreationOfSecretRequired {
err := retryOperation(func() error {
return m.kubernetescli.CoreV1().Secrets(pullSecretName.Namespace).Delete(ctx, pullSecretName.Name, metav1.DeleteOptions{})
})
if err != nil && !kerrors.IsNotFound(err) {
return err
}
}
// attempt to merge the data
if openshiftConfigSecret != nil && openshiftConfigSecret.Data != nil {
previousConfigData, previousConfigDataExists := openshiftConfigSecret.Data[corev1.DockerConfigJsonKey]
if previousConfigDataExists {
mergedPullSecretData, _, err := pullsecret.Merge(string(previousConfigData), string(encodedDockerConfigJson))
if err == nil {
applyConfiguration.Data[corev1.DockerConfigJsonKey] = []byte(mergedPullSecretData)
} else {
m.log.Error("Could not merge openshift config pull secret, overriding with new acr token", err)
}
}
}
return retryOperation(func() error {
_, err = m.kubernetescli.CoreV1().Secrets(pullSecretName.Namespace).Apply(ctx, applyConfiguration, metav1.ApplyOptions{FieldManager: "aro-rp", Force: true})
return err
})
}
func retryOperation(retryable func() error) error {
return retry.OnError(wait.Backoff{
Steps: 10,
Duration: 2 * time.Second,
}, func(err error) bool {
return kerrors.IsBadRequest(err) || kerrors.IsInternalError(err) || kerrors.IsServerTimeout(err) || kerrors.IsConflict(err)
}, retryable)
}