Skip to content

Commit

Permalink
Merge pull request #209 from uselagoon/harbor-rotator
Browse files Browse the repository at this point in the history
  • Loading branch information
shreddedbacon authored May 8, 2023
2 parents 06f1a24 + 55cae15 commit ff6d946
Show file tree
Hide file tree
Showing 8 changed files with 347 additions and 151 deletions.
24 changes: 12 additions & 12 deletions controllers/v1beta1/build_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,8 @@ func (r *LagoonBuildReconciler) getOrCreateNamespace(ctx context.Context, namesp
hProject,
lagoonBuild.Spec.Project.Environment,
ns,
lagoonHarbor.RobotAccountExpiry)
lagoonHarbor.RobotAccountExpiry,
false)
if err != nil {
return fmt.Errorf("Error creating harbor robot account: %v", err)
}
Expand All @@ -233,21 +234,20 @@ func (r *LagoonBuildReconciler) getOrCreateNamespace(ctx context.Context, namesp
hProject,
lagoonBuild.Spec.Project.Environment,
ns,
time.Now().Add(lagoonHarbor.RobotAccountExpiry).Unix())
time.Now().Add(lagoonHarbor.RobotAccountExpiry).Unix(),
false)
if err != nil {
return fmt.Errorf("Error creating harbor robot account: %v", err)
}
}
if robotCreds != nil {
// if we have robotcredentials to create, do that here
if err := harbor.UpsertHarborSecret(ctx,
r.Client,
ns,
"lagoon-internal-registry-secret",
lagoonHarbor.Hostname,
robotCreds); err != nil {
return fmt.Errorf("Error upserting harbor robot account secret: %v", err)
}
// if we have robotcredentials to create, do that here
_, err = lagoonHarbor.UpsertHarborSecret(ctx,
r.Client,
ns,
"lagoon-internal-registry-secret",
robotCreds)
if err != nil {
return fmt.Errorf("Error upserting harbor robot account secret: %v", err)
}
}
return nil
Expand Down
83 changes: 83 additions & 0 deletions controllers/v1beta1/harborcredential_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package v1beta1

import (
"context"
"encoding/json"
"fmt"

"github.com/go-logr/logr"
"github.com/uselagoon/remote-controller/internal/harbor"
"github.com/uselagoon/remote-controller/internal/helpers"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"

corev1 "k8s.io/api/core/v1"
)

// HarborCredentialReconciler reconciles a namespace object
type HarborCredentialReconciler struct {
client.Client
Log logr.Logger
Scheme *runtime.Scheme
LFFHarborEnabled bool
Harbor harbor.Harbor
ControllerNamespace string
}

// all the things
// +kubebuilder:rbac:groups=*,resources=*,verbs=*

func (r *HarborCredentialReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
opLog := r.Log.WithValues("harbor", req.NamespacedName)

var harborSecret corev1.Secret
if err := r.Get(ctx, req.NamespacedName, &harborSecret); err != nil {
return ctrl.Result{}, helpers.IgnoreNotFound(err)
}

// check if the credentials need to be force rotated
value, ok := harborSecret.ObjectMeta.Labels["harbor.lagoon.sh/force-rotate"]
if ok && value == "true" {
opLog.Info(fmt.Sprintf("Rotating harbor credentials for namespace %s", harborSecret.ObjectMeta.Namespace))
lagoonHarbor, err := harbor.New(r.Harbor)
if err != nil {
return ctrl.Result{}, fmt.Errorf("Error creating harbor client, check your harbor configuration. Error was: %v", err)
}
var ns corev1.Namespace
if err := r.Get(ctx, types.NamespacedName{Name: harborSecret.ObjectMeta.Namespace}, &ns); err != nil {
return ctrl.Result{}, helpers.IgnoreNotFound(err)
}
rotated, err := lagoonHarbor.RotateRobotCredential(ctx, r.Client, ns, true)
if err != nil {
// @TODO: resource unknown
return ctrl.Result{}, fmt.Errorf("Error was: %v", err)
}
if rotated {
opLog.Info(fmt.Sprintf("Robot credentials rotated for %s", ns.ObjectMeta.Name))
}
mergePatch, _ := json.Marshal(map[string]interface{}{
"metadata": map[string]interface{}{
"labels": map[string]interface{}{
"harbor.lagoon.sh/force-rotate": nil,
},
},
})
if err := r.Patch(ctx, &harborSecret, client.RawPatch(types.MergePatchType, mergePatch)); err != nil {
return ctrl.Result{}, fmt.Errorf("There was an error patching the harbor secret. Error was: %v", err)
}
}

return ctrl.Result{}, nil
}

// SetupWithManager sets up the watch on the namespace resource with an event filter (see controller_predicates.go)
func (r *HarborCredentialReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&corev1.Secret{}).
WithEventFilter(SecretPredicates{
ControllerNamespace: r.ControllerNamespace,
}).
Complete(r)
}
53 changes: 53 additions & 0 deletions controllers/v1beta1/predicates.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,3 +225,56 @@ func (t TaskPredicates) Generic(e event.GenericEvent) bool {
}
return false
}

// SecretPredicates defines the funcs for predicates
type SecretPredicates struct {
predicate.Funcs
ControllerNamespace string
}

// Create is used when a creation event is received by the controller.
func (n SecretPredicates) Create(e event.CreateEvent) bool {
if controller, ok := e.Object.GetLabels()["lagoon.sh/controller"]; ok {
if controller == n.ControllerNamespace {
if val, ok := e.Object.GetLabels()["lagoon.sh/harbor-credential"]; ok {
if val == "true" {
return true
}
}
}
}
return false
}

// Delete is used when a deletion event is received by the controller.
func (n SecretPredicates) Delete(e event.DeleteEvent) bool {
return false
}

// Update is used when an update event is received by the controller.
func (n SecretPredicates) Update(e event.UpdateEvent) bool {
if controller, ok := e.ObjectOld.GetLabels()["lagoon.sh/controller"]; ok {
if controller == n.ControllerNamespace {
if val, ok := e.ObjectOld.GetLabels()["lagoon.sh/harbor-credential"]; ok {
if val == "true" {
return true
}
}
}
}
return false
}

// Generic is used when any other event is received by the controller.
func (n SecretPredicates) Generic(e event.GenericEvent) bool {
if controller, ok := e.Object.GetLabels()["lagoon.sh/controller"]; ok {
if controller == n.ControllerNamespace {
if val, ok := e.Object.GetLabels()["lagoon.sh/harbor-credential"]; ok {
if val == "true" {
return true
}
}
}
}
return false
}
58 changes: 32 additions & 26 deletions internal/harbor/harbor21x.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ func (h *Harbor) CreateOrRefreshRobot(ctx context.Context,
project *harborclientv3model.Project,
environmentName, namespace string,
expiry int64,
force bool,
) (*helpers.RegistryCredentials, error) {

// create a cluster specific robot account name
Expand Down Expand Up @@ -169,7 +170,7 @@ func (h *Harbor) CreateOrRefreshRobot(ctx context.Context,
for _, robot := range robots {
if h.matchRobotAccount(robot.Name, project.Name, environmentName) {
exists = true
if forceRecreate {
if forceRecreate || force {
// if the secret doesn't exist in kubernetes, then force re-creation of the robot
// account is required, as there isn't a way to get the credentials after
// robot accounts are created
Expand Down Expand Up @@ -237,32 +238,37 @@ func (h *Harbor) CreateOrRefreshRobot(ctx context.Context,
if !exists || deleted {
// if it doesn't exist, or was deleted
// create a new robot account
token, err := h.ClientV3.AddProjectRobot(
ctx,
project,
&harborclientv3legacy.RobotAccountCreate{
Name: robotName,
Description: fmt.Sprintf("Robot account created in %s", h.LagoonTargetName),
ExpiresAt: expiry,
Access: []*harborclientv3legacy.RobotAccountAccess{
{Action: "push", Resource: fmt.Sprintf("/project/%d/repository", project.ProjectID)},
{Action: "pull", Resource: fmt.Sprintf("/project/%d/repository", project.ProjectID)},
},
},
)
if err != nil {
h.Log.Info(fmt.Sprintf("Error adding project %s robot account %s", project.Name, robotName))
return nil, err
}
// then craft and return the harbor credential secret
harborRegistryCredentials := makeHarborSecret(
robotAccountCredential{
Token: token,
Name: h.addPrefix(robotName),
},
)
h.Log.Info(fmt.Sprintf("Created robot account %s", h.addPrefix(robotName)))
return &harborRegistryCredentials, nil
return h.CreateRobotAccount(ctx, robotName, project, expiry)
}
return nil, err
}

func (h *Harbor) CreateRobotAccount(ctx context.Context, robotName string, project *harborclientv3model.Project, expiryDays int64) (*helpers.RegistryCredentials, error) {
token, err := h.ClientV3.AddProjectRobot(
ctx,
project,
&harborclientv3legacy.RobotAccountCreate{
Name: robotName,
Description: fmt.Sprintf("Robot account created in %s", h.LagoonTargetName),
ExpiresAt: expiryDays,
Access: []*harborclientv3legacy.RobotAccountAccess{
{Action: "push", Resource: fmt.Sprintf("/project/%d/repository", project.ProjectID)},
{Action: "pull", Resource: fmt.Sprintf("/project/%d/repository", project.ProjectID)},
},
},
)
if err != nil {
h.Log.Info(fmt.Sprintf("Error adding project %s robot account %s", project.Name, robotName))
return nil, err
}
// then craft and return the harbor credential secret
harborRegistryCredentials := makeHarborSecret(
robotAccountCredential{
Token: token,
Name: h.addPrefix(robotName),
},
)
h.Log.Info(fmt.Sprintf("Created robot account %s", h.addPrefix(robotName)))
return &harborRegistryCredentials, nil
}
81 changes: 43 additions & 38 deletions internal/harbor/harbor22x.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ func (h *Harbor) CreateOrRefreshRobotV2(ctx context.Context,
project *harborclientv5model.Project,
environmentName, namespace string,
expiry time.Duration,
force bool,
) (*helpers.RegistryCredentials, error) {

// create a cluster specific robot account name
Expand Down Expand Up @@ -197,7 +198,7 @@ func (h *Harbor) CreateOrRefreshRobotV2(ctx context.Context,
if h.matchRobotAccountV2(robot.Name, project.Name, environmentName) && robot.Editable {
// if it is a new robot account, follow through here
exists = true
if forceRecreate {
if forceRecreate || force {
// if the secret doesn't exist in kubernetes, then force re-creation of the robot
// account is required, as there isn't a way to get the credentials after
// robot accounts are created
Expand Down Expand Up @@ -265,43 +266,7 @@ func (h *Harbor) CreateOrRefreshRobotV2(ctx context.Context,
if !exists || deleted {
// if it doesn't exist, or was deleted
// create a new robot account
robotf := harborclientv5model.RobotCreate{
Level: "project",
Name: robotName,
Duration: expiryDays,
Permissions: []*harborclientv5model.RobotPermission{
{
Kind: "project",
Namespace: project.Name,
Access: []*harborclientv5model.Access{
{
Action: "push",
Resource: "repository",
},
{
Action: "pull",
Resource: "repository",
},
},
},
},
Disable: false,
Description: fmt.Sprintf("Robot account created in %s", h.LagoonTargetName),
}
token, err := h.ClientV5.NewRobotAccount(ctx, &robotf)
if err != nil {
h.Log.Info(fmt.Sprintf("Error adding project %s robot account %s", project.Name, h.addPrefixV2(project.Name, environmentName)))
return nil, err
}
// then craft and return the harbor credential secret
harborRegistryCredentials := makeHarborSecret(
robotAccountCredential{
Token: token.Secret,
Name: token.Name,
},
)
h.Log.Info(fmt.Sprintf("Created robot account %s", token.Name))
return &harborRegistryCredentials, nil
return h.CreateRobotAccountV2(ctx, robotName, environmentName, project.Name, expiryDays)
}
return nil, err
}
Expand Down Expand Up @@ -348,3 +313,43 @@ func (h *Harbor) ListRepositories(ctx context.Context, projectName string, pageN
}
return listRepositories
}

func (h *Harbor) CreateRobotAccountV2(ctx context.Context, robotName, environmentName, projectName string, expiryDays int64) (*helpers.RegistryCredentials, error) {
robotf := harborclientv5model.RobotCreate{
Level: "project",
Name: robotName,
Duration: expiryDays,
Permissions: []*harborclientv5model.RobotPermission{
{
Kind: "project",
Namespace: projectName,
Access: []*harborclientv5model.Access{
{
Action: "push",
Resource: "repository",
},
{
Action: "pull",
Resource: "repository",
},
},
},
},
Disable: false,
Description: fmt.Sprintf("Robot account created in %s", h.LagoonTargetName),
}
token, err := h.ClientV5.NewRobotAccount(ctx, &robotf)
if err != nil {
h.Log.Info(fmt.Sprintf("Error adding project %s robot account %s", projectName, h.addPrefixV2(projectName, environmentName)))
return nil, err
}
// then craft and return the harbor credential secret
harborRegistryCredentials := makeHarborSecret(
robotAccountCredential{
Token: token.Secret,
Name: token.Name,
},
)
h.Log.Info(fmt.Sprintf("Created robot account %s", token.Name))
return &harborRegistryCredentials, nil
}
Loading

0 comments on commit ff6d946

Please sign in to comment.