Skip to content

Commit

Permalink
Remote secret decomission (#110)
Browse files Browse the repository at this point in the history
Remote secret decomission

Signed-off-by: Max Shaposhnyk <[email protected]>
  • Loading branch information
mshaposhnik authored May 31, 2024
1 parent 3296e49 commit 83b8a0a
Show file tree
Hide file tree
Showing 13 changed files with 264 additions and 430 deletions.
7 changes: 0 additions & 7 deletions api/v1alpha1/imagerepository_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,13 +156,6 @@ type CredentialsStatus struct {
// PullRobotAccountName is present only if ImageRepository has labels that connect it to Application and Component.
// Holds name of the quay robot account with real (pull only) permissions from the generated repository.
PullRobotAccountName string `json:"pull-robot-account,omitempty"`

// PushRemoteSecretName holds name of RemoteSecret object that manages push Secret and its linking to appstudio-pipeline Service Account.
PushRemoteSecretName string `json:"push-remote-secret,omitempty"`

// PullRemoteSecretName is present only if ImageRepository has labels that connect it to Application and Component.
// Holds the name of the RemoteSecret object that manages pull Secret.
PullRemoteSecretName string `json:"pull-remote-secret,omitempty"`
}

// NotificationStatus shows the status of the notification configuration.
Expand Down
10 changes: 0 additions & 10 deletions config/crd/bases/appstudio.redhat.com_imagerepositories.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,6 @@ spec:
were generated.
format: date-time
type: string
pull-remote-secret:
description: PullRemoteSecretName is present only if ImageRepository
has labels that connect it to Application and Component. Holds
the name of the RemoteSecret object that manages pull Secret.
type: string
pull-robot-account:
description: PullRobotAccountName is present only if ImageRepository
has labels that connect it to Application and Component. Holds
Expand All @@ -129,11 +124,6 @@ spec:
in the same namespace as ImageRepository, but created in other
environments.
type: string
push-remote-secret:
description: PushRemoteSecretName holds name of RemoteSecret object
that manages push Secret and its linking to appstudio-pipeline
Service Account.
type: string
push-robot-account:
description: PushRobotAccountName holds name of the quay robot
account with write (push and pull) permissions into the generated
Expand Down
10 changes: 1 addition & 9 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,4 @@ rules:
- get
- patch
- update
- apiGroups:
- appstudio.redhat.com
resources:
- remotesecrets
verbs:
- create
- get
- list
- watch

93 changes: 19 additions & 74 deletions controllers/component_image_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ import (
"github.com/konflux-ci/image-controller/pkg/metrics"
"github.com/konflux-ci/image-controller/pkg/quay"
appstudioredhatcomv1alpha1 "github.com/redhat-appstudio/application-api/api/v1alpha1"
remotesecretv1beta1 "github.com/redhat-appstudio/remote-secret/api/v1beta1"
)

const (
Expand All @@ -50,8 +49,6 @@ const (

ApplicationNameLabelName = "appstudio.redhat.com/application"
ComponentNameLabelName = "appstudio.redhat.com/component"

defaultServiceAccountName = "default"
)

// GenerateRepositoryOpts defines patameters for image repository to be generated.
Expand Down Expand Up @@ -263,10 +260,7 @@ func (r *ComponentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (

// Propagate the pull secret into all environments
pullSecretName := pushSecretName + "-pull"
if err := r.ensureComponentPullSecretRemoteSecret(ctx, component, pullSecretName); err != nil {
return ctrl.Result{}, err
}
if err := r.createRemoteSecretUploadSecret(ctx, pullRobotAccount, component.Namespace, pullSecretName, imageURL); err != nil {
if err := r.ensureComponentPullSecret(ctx, component, pullSecretName, pullRobotAccount, imageURL); err != nil {
return ctrl.Result{}, err
}
log.Info(fmt.Sprintf("Prepared remote secret %s for Component", pullSecretName), l.Action, l.ActionUpdate)
Expand Down Expand Up @@ -395,90 +389,41 @@ func (r *ComponentReconciler) ensureRobotAccountSecret(ctx context.Context, comp
return secretData, nil
}

// ensureComponentPullSecretRemoteSecret creates remote secret for component image repository pull token.
func (r *ComponentReconciler) ensureComponentPullSecretRemoteSecret(ctx context.Context, component *appstudioredhatcomv1alpha1.Component, remoteSecretName string) error {
// ensureComponentPullSecret creates secret for component image repository pull token.
func (r *ComponentReconciler) ensureComponentPullSecret(ctx context.Context, component *appstudioredhatcomv1alpha1.Component, secretName string, robotAccount *quay.RobotAccount, imageURL string) error {
log := ctrllog.FromContext(ctx)

remoteSecret := &remotesecretv1beta1.RemoteSecret{}
remoteSecretKey := types.NamespacedName{Namespace: component.Namespace, Name: remoteSecretName}
if err := r.Client.Get(ctx, remoteSecretKey, remoteSecret); err != nil {
pullSecret := &corev1.Secret{}
pullSecretKey := types.NamespacedName{Namespace: component.Namespace, Name: secretName}
if err := r.Client.Get(ctx, pullSecretKey, pullSecret); err != nil {
if !errors.IsNotFound(err) {
log.Error(err, fmt.Sprintf("failed to get remote secret: %v", remoteSecretKey), l.Action, l.ActionView)
log.Error(err, fmt.Sprintf("failed to get pull secret: %v", pullSecretKey), l.Action, l.ActionView)
return err
}

remoteSecret := &remotesecretv1beta1.RemoteSecret{
pullSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: remoteSecretName,
Name: secretName,
Namespace: component.Namespace,
// TODO: remove application/component labels when SEB controller will be discontinued
Labels: map[string]string{
ApplicationNameLabelName: component.Spec.Application,
ComponentNameLabelName: component.Name,
InternalRemoteSecretLabelName: "true",
},
OwnerReferences: []metav1.OwnerReference{
{
Name: component.Name,
APIVersion: component.APIVersion,
Kind: component.Kind,
UID: component.UID,
},
},
},
Spec: remotesecretv1beta1.RemoteSecretSpec{
Secret: remotesecretv1beta1.LinkableSecretSpec{
Name: remoteSecretName,
Type: corev1.SecretTypeDockerConfigJson,
LinkedTo: []remotesecretv1beta1.SecretLink{
{
ServiceAccount: remotesecretv1beta1.ServiceAccountLink{
Reference: corev1.LocalObjectReference{
Name: defaultServiceAccountName,
},
},
},
},
},
Targets: []remotesecretv1beta1.RemoteSecretTarget{
{
Namespace: component.Namespace,
},
InternalSecretLabelName: "true",
},
},
Type: corev1.SecretTypeDockerConfigJson,
StringData: generateDockerconfigSecretData(imageURL, robotAccount),
}
if err := r.Client.Create(ctx, remoteSecret); err != nil {
log.Error(err, fmt.Sprintf("failed to create remote secret: %v", remoteSecretKey), l.Action, l.ActionAdd, l.Audit, "true")

if err := controllerutil.SetOwnerReference(component, pullSecret, r.Scheme); err != nil {
log.Error(err, "failed to set owner for pull secret")
return err
}
}

return nil
}

// createRemoteSecretUploadSecret creates short lived secret to upload data into specified remote secret.
func (r *ComponentReconciler) createRemoteSecretUploadSecret(ctx context.Context, robotAccount *quay.RobotAccount, namespace, remoteSecretName, imageURL string) error {
log := ctrllog.FromContext(ctx)
if err := r.Client.Create(ctx, pullSecret); err != nil {
log.Error(err, fmt.Sprintf("failed to create pull secret: %v", pullSecretKey), l.Action, l.ActionAdd, l.Audit, "true")
return err
}

uploadSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "upload-secret-" + remoteSecretName,
Namespace: namespace,
Labels: map[string]string{
remotesecretv1beta1.UploadSecretLabel: "remotesecret",
},
Annotations: map[string]string{
remotesecretv1beta1.RemoteSecretNameAnnotation: remoteSecretName,
},
},
Type: corev1.SecretTypeDockerConfigJson,
StringData: generateDockerconfigSecretData(imageURL, robotAccount),
}
if err := r.Client.Create(ctx, uploadSecret); err != nil {
log.Error(err, fmt.Sprintf("failed to create upload secret: %v", uploadSecret.Name), l.Action, l.ActionAdd, l.Audit, "true")
return err
}

return nil
}

Expand Down
69 changes: 23 additions & 46 deletions controllers/component_image_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,7 @@ import (
. "github.com/onsi/gomega"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

remotesecretv1beta1 "github.com/redhat-appstudio/remote-secret/api/v1beta1"
corev1 "k8s.io/api/core/v1"
k8sErrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"

"github.com/konflux-ci/image-controller/pkg/quay"
Expand All @@ -46,7 +44,7 @@ var _ = Describe("Component image controller", func() {
pullToken string
expectedPushRobotAccountName string
expectedPullRobotAccountName string
expectedRemoteSecretName string
expectedPullSecretName string
expectedRepoName string
expectedImage string
)
Expand All @@ -62,7 +60,7 @@ var _ = Describe("Component image controller", func() {
pullToken = "pull-token1234"
expectedPushRobotAccountName = fmt.Sprintf("%s%s%s", defaultNamespace, defaultComponentApplication, defaultComponentName)
expectedPullRobotAccountName = expectedPushRobotAccountName + "-pull"
expectedRemoteSecretName = resourceKey.Name + "-pull"
expectedPullSecretName = resourceKey.Name + "-pull"
expectedRepoName = fmt.Sprintf("%s/%s/%s", defaultNamespace, defaultComponentApplication, defaultComponentName)
expectedImage = fmt.Sprintf("quay.io/%s/%s", quay.TestQuayOrg, expectedRepoName)
})
Expand Down Expand Up @@ -158,31 +156,17 @@ var _ = Describe("Component image controller", func() {
It("should propagate pull secret to environments", func() {
component := getComponent(resourceKey)

remoteSecretKey := types.NamespacedName{Name: expectedRemoteSecretName, Namespace: defaultNamespace}
remoteSecret := waitRemoteSecretExist(remoteSecretKey)
Expect(remoteSecret.Labels[ApplicationNameLabelName]).To(Equal(component.Spec.Application))
Expect(remoteSecret.Labels[ComponentNameLabelName]).To(Equal(component.Spec.ComponentName))
Expect(remoteSecret.Labels[InternalRemoteSecretLabelName]).To(Equal("true"))
Expect(remoteSecret.OwnerReferences).To(HaveLen(1))
Expect(remoteSecret.OwnerReferences[0].Name).To(Equal(component.Name))
Expect(remoteSecret.OwnerReferences[0].Kind).To(Equal("Component"))

Expect(remoteSecret.Spec.Secret.Name).To(Equal(remoteSecretKey.Name))
Expect(remoteSecret.Spec.Secret.Type).To(Equal(corev1.SecretTypeDockerConfigJson))
Expect(remoteSecret.Spec.Secret.LinkedTo).To(HaveLen(1))
Expect(remoteSecret.Spec.Secret.LinkedTo[0].ServiceAccount.Reference.Name).To(Equal(defaultServiceAccountName))
Expect(remoteSecret.Spec.Targets).To(HaveLen(1))
Expect(remoteSecret.Spec.Targets[0].Namespace).To(Equal(component.Namespace))

waitSecretExist(uploadSecretKey)
uploadSecret := &corev1.Secret{}
Expect(k8sClient.Get(ctx, uploadSecretKey, uploadSecret)).To(Succeed())

Expect(uploadSecret.Labels[remotesecretv1beta1.UploadSecretLabel]).To(Equal("remotesecret"))
Expect(uploadSecret.Annotations[remotesecretv1beta1.RemoteSecretNameAnnotation]).To(Equal(remoteSecretKey.Name))
Expect(uploadSecret.Type).To(Equal(corev1.SecretTypeDockerConfigJson))

uploadSecretDockerconfigJson := string(uploadSecret.Data[corev1.DockerConfigJsonKey])
pullSecretKey := types.NamespacedName{Name: expectedPullSecretName, Namespace: defaultNamespace}
pullSecret := waitSecretExist(pullSecretKey)
Expect(pullSecret.Labels[InternalSecretLabelName]).To(Equal("true"))
Expect(pullSecret.OwnerReferences).To(HaveLen(1))
Expect(pullSecret.OwnerReferences[0].Name).To(Equal(component.Name))
Expect(pullSecret.OwnerReferences[0].Kind).To(Equal("Component"))

Expect(pullSecret.Name).To(Equal(pullSecretKey.Name))
Expect(pullSecret.Type).To(Equal(corev1.SecretTypeDockerConfigJson))

uploadSecretDockerconfigJson := string(pullSecret.Data[corev1.DockerConfigJsonKey])
var authDataJson interface{}
Expect(json.Unmarshal([]byte(uploadSecretDockerconfigJson), &authDataJson)).To(Succeed())
Expect(uploadSecretDockerconfigJson).To(ContainSubstring(expectedImage))
Expand Down Expand Up @@ -603,10 +587,9 @@ var _ = Describe("Component image controller", func() {
})

It("should create pull robot account for existing image repositories with only push robot account and propagate it via remote secret", func() {
deleteSecret(types.NamespacedName{Name: expectedRemoteSecretName, Namespace: resourceKey.Namespace})
deleteSecret(types.NamespacedName{Name: expectedPullSecretName, Namespace: resourceKey.Namespace})

remoteSecretKey := types.NamespacedName{Name: expectedRemoteSecretName, Namespace: defaultNamespace}
Expect(k8sErrors.IsNotFound(k8sClient.Get(ctx, remoteSecretKey, &remotesecretv1beta1.RemoteSecret{})))
pullSecretKey := types.NamespacedName{Name: expectedPullSecretName, Namespace: defaultNamespace}

isCreateRepositoryInvoked := false
quay.CreateRepositoryFunc = func(repository quay.RepositoryRequest) (*quay.Repository, error) {
Expand Down Expand Up @@ -682,21 +665,15 @@ var _ = Describe("Component image controller", func() {
Expect(repoImageInfo.Visibility).To(Equal("public"))
Expect(repoImageInfo.Secret).To(Equal(resourceKey.Name))

remoteSecret := waitRemoteSecretExist(remoteSecretKey)
Expect(remoteSecret.Labels[ApplicationNameLabelName]).To(Equal(component.Spec.Application))
Expect(remoteSecret.Labels[ComponentNameLabelName]).To(Equal(component.Spec.ComponentName))
Expect(remoteSecret.Labels[InternalRemoteSecretLabelName]).To(Equal("true"))
Expect(remoteSecret.OwnerReferences).To(HaveLen(1))
Expect(remoteSecret.OwnerReferences[0].Name).To(Equal(component.Name))
Expect(remoteSecret.OwnerReferences[0].Kind).To(Equal("Component"))

Expect(remoteSecret.Spec.Secret.Name).To(Equal(remoteSecretKey.Name))
Expect(remoteSecret.Spec.Secret.Type).To(Equal(corev1.SecretTypeDockerConfigJson))
Expect(remoteSecret.Spec.Secret.LinkedTo).To(HaveLen(1))
Expect(remoteSecret.Spec.Secret.LinkedTo[0].ServiceAccount.Reference.Name).To(Equal(defaultServiceAccountName))
Expect(remoteSecret.Spec.Targets).To(HaveLen(1))
Expect(remoteSecret.Spec.Targets[0].Namespace).To(Equal(component.Namespace))
pullSecret := waitSecretExist(pullSecretKey)
Expect(pullSecret.Labels[InternalSecretLabelName]).To(Equal("true"))
Expect(pullSecret.OwnerReferences).To(HaveLen(1))
Expect(pullSecret.OwnerReferences[0].Name).To(Equal(component.Name))
Expect(pullSecret.OwnerReferences[0].Kind).To(Equal("Component"))

Expect(pullSecret.Name).To(Equal(pullSecretKey.Name))
Expect(pullSecret.Type).To(Equal(corev1.SecretTypeDockerConfigJson))
Expect(pullSecret.Data).To(HaveKey(".dockerconfigjson"))
})
})

Expand Down
Loading

0 comments on commit 83b8a0a

Please sign in to comment.