Skip to content

Commit

Permalink
Create secret, service account and role for the RKE2 machine plans
Browse files Browse the repository at this point in the history
Signed-off-by: Alexandr Demicev <[email protected]>
  • Loading branch information
alexander-demicev committed Feb 19, 2024
1 parent 9cd0410 commit 3d24fbb
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 3 deletions.
2 changes: 1 addition & 1 deletion bootstrap/config/default/manager_image_patch.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ spec:
template:
spec:
containers:
- image: ghcr.io/rancher-sandbox/cluster-api-provider-rke2-bootstrap:dev
- image: ghcr.io/rancher-sandbox/cluster-api-provider-rke2-bootstrap-arm64:dev
name: manager
14 changes: 14 additions & 0 deletions bootstrap/config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ rules:
- configmaps
- events
- secrets
- serviceaccounts
verbs:
- create
- delete
Expand Down Expand Up @@ -55,3 +56,16 @@ rules:
- get
- list
- watch
- apiGroups:
- rbac.authorization.k8s.io
resources:
- rolebindings
- roles
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
137 changes: 136 additions & 1 deletion bootstrap/internal/controllers/rke2config_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ package controllers
import (
"context"
"fmt"
"strings"
"time"

"github.com/go-logr/logr"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
Expand Down Expand Up @@ -67,6 +69,14 @@ type RKE2ConfigReconciler struct {
}

const (
ClusterNameLabel = "cluster.x-k8s.io/cluster-name"

Check failure on line 72 in bootstrap/internal/controllers/rke2config_controller.go

View workflow job for this annotation

GitHub Actions / lint

exported: exported const ClusterNameLabel should have comment (or a comment on this block) or be unexported (revive)

Check warning on line 72 in bootstrap/internal/controllers/rke2config_controller.go

View workflow job for this annotation

GitHub Actions / lint

exported: exported const ClusterNameLabel should have comment (or a comment on this block) or be unexported (revive)
MachineNameLabel = "cluster.x-k8s.io/machine-name"
PlanSecretNameLabel = "cluster-api.cattle.io/plan-secret-name"

Check failure on line 74 in bootstrap/internal/controllers/rke2config_controller.go

View workflow job for this annotation

GitHub Actions / lint

G101: Potential hardcoded credentials (gosec)

Check failure on line 74 in bootstrap/internal/controllers/rke2config_controller.go

View workflow job for this annotation

GitHub Actions / lint

G101: Potential hardcoded credentials (gosec)

ServiceAccountSecretLabel = "cluster-api.cattle.io/service-account.name"

Check failure on line 76 in bootstrap/internal/controllers/rke2config_controller.go

View workflow job for this annotation

GitHub Actions / lint

G101: Potential hardcoded credentials (gosec)

Check failure on line 76 in bootstrap/internal/controllers/rke2config_controller.go

View workflow job for this annotation

GitHub Actions / lint

G101: Potential hardcoded credentials (gosec)

SecretTypeMachinePlan = "cluster-api.cattle.io/machine-plan"

Check failure on line 78 in bootstrap/internal/controllers/rke2config_controller.go

View workflow job for this annotation

GitHub Actions / lint

G101: Potential hardcoded credentials (gosec)

Check failure on line 78 in bootstrap/internal/controllers/rke2config_controller.go

View workflow job for this annotation

GitHub Actions / lint

G101: Potential hardcoded credentials (gosec)

// DefaultManifestDirectory is the default directory to store kubernetes manifests that RKE2 will deploy automatically.
DefaultManifestDirectory string = "/var/lib/rancher/rke2/server/manifests"

Expand All @@ -78,7 +88,8 @@ const (
//+kubebuilder:rbac:groups=bootstrap.cluster.x-k8s.io,resources=rke2configs;rke2configs/status;rke2configs/finalizers,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=controlplane.cluster.x-k8s.io,resources=rke2controlplanes;rke2controlplanes/status,verbs=get;list;watch
//+kubebuilder:rbac:groups=cluster.x-k8s.io,resources=clusters;clusters/status;machinesets;machines;machines/status;machinepools;machinepools/status,verbs=get;list;watch
//+kubebuilder:rbac:groups="",resources=secrets;events;configmaps,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups="",resources=secrets;events;configmaps;serviceaccounts,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups="rbac.authorization.k8s.io",resources=roles;rolebindings,verbs=get;list;watch;create;update;patch;delete

// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
Expand Down Expand Up @@ -166,6 +177,23 @@ func (r *RKE2ConfigReconciler) Reconcile(ctx context.Context, req ctrl.Request)
return ctrl.Result{}, nil
}

// POC: Create a ServiceAccount with Secret, Role, and RoleBinding for the system-agent to use later.
planSecretName := strings.Join([]string{scope.Machine.Name, "machine", "plan"}, "-")

if err := r.createSecretPlanResources(ctx, planSecretName, scope.Cluster, scope.Machine); err != nil {
return ctrl.Result{}, err
}

secretPopulated, err := r.ensureServiceAccountSecretPopulated(ctx, planSecretName)
if err != nil {
return ctrl.Result{}, err
}

if !secretPopulated {
logger.Info(fmt.Sprintf("Secret %s not yet populated", planSecretName))
return ctrl.Result{RequeueAfter: DefaultRequeueAfter}, nil

Check failure on line 194 in bootstrap/internal/controllers/rke2config_controller.go

View workflow job for this annotation

GitHub Actions / lint

return with no blank line before (nlreturn)

Check failure on line 194 in bootstrap/internal/controllers/rke2config_controller.go

View workflow job for this annotation

GitHub Actions / lint

return with no blank line before (nlreturn)
}

// Note: can't use IsFalse here because we need to handle the absence of the condition as well as false.
if !conditions.IsTrue(scope.Cluster, clusterv1.ControlPlaneInitializedCondition) {
return r.handleClusterNotInitialized(ctx, scope)
Expand Down Expand Up @@ -886,6 +914,113 @@ func (r *RKE2ConfigReconciler) createOrUpdateSecretFromObject(
return
}

func (r *RKE2ConfigReconciler) createSecretPlanResources(ctx context.Context, planSecretName string, cluster *clusterv1.Cluster, machine *clusterv1.Machine) error {

Check failure on line 917 in bootstrap/internal/controllers/rke2config_controller.go

View workflow job for this annotation

GitHub Actions / lint

line is 164 characters (lll)
logger := log.FromContext(ctx)

logger.Info("Creating secret plan resources")

sa := &corev1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: planSecretName,
Namespace: machine.Namespace,
Labels: map[string]string{
MachineNameLabel: machine.Name,
PlanSecretNameLabel: planSecretName,
},
},
}

secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: planSecretName,
Namespace: machine.Namespace,
Labels: map[string]string{
MachineNameLabel: machine.Name,
ClusterNameLabel: cluster.Name,
},
},
Type: SecretTypeMachinePlan,
}

role := &rbacv1.Role{
ObjectMeta: metav1.ObjectMeta{
Name: planSecretName,
Namespace: machine.Namespace,
},
Rules: []rbacv1.PolicyRule{
{
Verbs: []string{"watch", "get", "update", "list"},
APIGroups: []string{""},
Resources: []string{"secrets"},
ResourceNames: []string{planSecretName},
},
},
}

roleBinding := &rbacv1.RoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: planSecretName,
Namespace: machine.Namespace,
},
Subjects: []rbacv1.Subject{
{
Kind: "ServiceAccount",
Name: sa.Name,
Namespace: sa.Namespace,
},
},
RoleRef: rbacv1.RoleRef{
APIGroup: rbacv1.GroupName,
Kind: "Role",
Name: planSecretName,
},
}

saSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%s-token", planSecretName),
Namespace: machine.Namespace,
Annotations: map[string]string{
"kubernetes.io/service-account.name": planSecretName,
},
Labels: map[string]string{
ServiceAccountSecretLabel: planSecretName,
},
},
Type: corev1.SecretTypeServiceAccountToken,
}

for _, obj := range []client.Object{sa, secret, role, roleBinding, saSecret} {
if err := r.Create(ctx, obj); err != nil {
if !apierrors.IsAlreadyExists(err) {
return fmt.Errorf("failed to create %s: %w", obj.GetObjectKind().GroupVersionKind().String(), err)
}
}
}

return nil
}

func (r *RKE2ConfigReconciler) ensureServiceAccountSecretPopulated(ctx context.Context, planSecretName string) (bool, error) {
logger := log.FromContext(ctx)

logger.Info("Ensuring service account secret is populated")

secretList := &corev1.SecretList{}

if err := r.List(ctx, secretList, client.MatchingLabels{ServiceAccountSecretLabel: planSecretName}); err != nil {
return false, fmt.Errorf("failed to list secrets: %w", err)
}

if len(secretList.Items) == 0 || len(secretList.Items) > 1 {
return false, fmt.Errorf("secret for %s doesn't exist, or more than one secret exists", planSecretName)
}

saSecret := secretList.Items[0]

return len(saSecret.Data[corev1.ServiceAccountTokenKey]) > 0, nil
}

func generateFilesFromManifestConfig(
ctx context.Context,
cl client.Client,
Expand Down
2 changes: 1 addition & 1 deletion controlplane/config/default/manager_image_patch.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ spec:
template:
spec:
containers:
- image: ghcr.io/rancher-sandbox/cluster-api-provider-rke2-controlplane:dev
- image: ghcr.io/rancher-sandbox/cluster-api-provider-rke2-controlplane-arm64:dev
name: manager

0 comments on commit 3d24fbb

Please sign in to comment.