Skip to content
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

[NET-9153] Handle Terminating Gateway ACL Setup #3975

Merged
merged 16 commits into from
May 13, 2024
Merged
8 changes: 8 additions & 0 deletions control-plane/api/v1alpha1/registration_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
)

func init() {
Expand Down Expand Up @@ -289,6 +290,13 @@ func (r *Registration) ToCatalogDeregistration() *capi.CatalogDeregistration {
}
}

func (r *Registration) NamespacedName() types.NamespacedName {
return types.NamespacedName{
Namespace: r.Namespace,
Name: r.Name,
}
}

// SetSyncedCondition sets the synced condition on the Registration.
func (r *Registration) SetSyncedCondition(status corev1.ConditionStatus, reason string, message string) {
r.Status.Conditions = Conditions{
Expand Down
202 changes: 171 additions & 31 deletions control-plane/controllers/configentries/registrations_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@

import (
"context"
"errors"
"fmt"
"slices"
"strings"
"time"

"github.com/go-logr/logr"
Expand All @@ -24,14 +28,21 @@

const RegistrationFinalizer = "registration.finalizers.consul.hashicorp.com"

// Status Reasons

Check failure on line 31 in control-plane/controllers/configentries/registrations_controller.go

View workflow job for this annotation

GitHub Actions / golangci-lint

Comment should end in a period (godot)
const (
ConsulErrorRegistration = "ConsulErrorRegistration"
ConsulErrorDeregistration = "ConsulErrorDeregistration"
ConsulErrorACL = "ConsulErrorACL"
)

// RegistrationsController is the controller for Registrations resources.
type RegistrationsController struct {
client.Client
FinalizerPatcher
Log logr.Logger
Scheme *runtime.Scheme
ConsulClientConfig *consul.Config
ConsulServerConnMgr consul.ServerConnectionManager
Log logr.Logger
}

// +kubebuilder:rbac:groups=consul.hashicorp.com,resources=servicerouters,verbs=get;list;watch;create;update;patch;delete
Expand All @@ -58,42 +69,43 @@

// deletion request
if !registration.ObjectMeta.DeletionTimestamp.IsZero() {
log.Info("Deregistering service")
err = r.deregisterService(ctx, log, client, registration)
err := r.handleDeletion(ctx, log, client, registration)
if err != nil {
r.updateStatusError(ctx, registration, "ConsulErrorDeregistration", err)
return ctrl.Result{}, err
}
err := r.updateStatus(ctx, req.NamespacedName)
if err != nil {
log.Error(err, "failed to update status")
}

return ctrl.Result{}, nil
}

log.Info("Registering service")
err = r.registerService(ctx, log, client, registration)
// registration request
err = r.handleRegistration(ctx, log, client, registration)
if err != nil {
r.updateStatusError(ctx, registration, "ConsulErrorRegistration", err)
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}

err = r.updateStatus(ctx, req.NamespacedName)
func (r *RegistrationsController) handleRegistration(ctx context.Context, log logr.Logger, client *capi.Client, registration *v1alpha1.Registration) error {
log.Info("Registering service")
err := r.registerService(log, client, registration)
if err != nil {
log.Error(err, "failed to update status")
r.updateStatusError(ctx, log, registration, ConsulErrorRegistration, err)
return err
}
return ctrl.Result{}, err
}

func (r *RegistrationsController) registerService(ctx context.Context, log logr.Logger, client *capi.Client, registration *v1alpha1.Registration) error {
patch := r.AddFinalizersPatch(registration, RegistrationFinalizer)

err := r.Patch(ctx, registration, patch)
if r.ConsulClientConfig.APIClientConfig.Token != "" || r.ConsulClientConfig.APIClientConfig.TokenFile != "" {
err = r.updateTermGWACLRole(log, client, registration)
if err != nil {
r.updateStatusError(ctx, log, registration, ConsulErrorACL, err)
return err
}
}
err = r.updateStatus(ctx, log, registration.NamespacedName())
if err != nil {
return err
}
return nil
}

func (r *RegistrationsController) registerService(log logr.Logger, client *capi.Client, registration *v1alpha1.Registration) error {
regReq, err := registration.ToCatalogRegistration()
if err != nil {
return err
Expand All @@ -109,7 +121,85 @@
return nil
}

func (r *RegistrationsController) deregisterService(ctx context.Context, log logr.Logger, client *capi.Client, registration *v1alpha1.Registration) error {
func (r *RegistrationsController) updateTermGWACLRole(log logr.Logger, client *capi.Client, registration *v1alpha1.Registration) error {
roles, _, err := client.ACL().RoleList(nil)
if err != nil {
return err
}

var role *capi.ACLRole
for _, r := range roles {
if strings.HasSuffix(r.Name, "terminating-gateway-acl-role") {
jm96441n marked this conversation as resolved.
Show resolved Hide resolved
role = r
break
}
}

if role == nil {
log.Info("terminating gateway role not found")
return errors.New("terminating gateway role not found")
}

policy := &capi.ACLPolicy{
Name: servicePolicyName(registration.Spec.Service.Name),
Description: "Write policy for terminating gateways for external service",
Rules: `service "zoidberg" { policy = "write" }`,
Datacenters: []string{registration.Spec.Datacenter},
Namespace: registration.Spec.Service.Namespace,
Partition: registration.Spec.Service.Partition,
}

existingPolicy, _, err := client.ACL().PolicyReadByName(policy.Name, nil)
if err != nil {
log.Error(err, "error reading policy")
return err
}

// existingPolicy will never be nil beause of how PolicyReadByName works so we need to check if the ID is empty
if existingPolicy.ID == "" {
policy, _, err = client.ACL().PolicyCreate(policy, nil)
if err != nil {
return fmt.Errorf("error creating policy: %w", err)
}
} else {
policy = existingPolicy
}

role.Policies = append(role.Policies, &capi.ACLRolePolicyLink{Name: policy.Name, ID: policy.ID})

_, _, err = client.ACL().RoleUpdate(role, nil)
if err != nil {
log.Error(err, "error updating role")
return err
}

return nil
}

func (r *RegistrationsController) handleDeletion(ctx context.Context, log logr.Logger, client *capi.Client, registration *v1alpha1.Registration) error {
log.Info("Deregistering service")
err := r.deregisterService(log, client, registration)
if err != nil {
r.updateStatusError(ctx, log, registration, ConsulErrorDeregistration, err)
return err
}
if r.ConsulClientConfig.APIClientConfig.Token != "" || r.ConsulClientConfig.APIClientConfig.TokenFile != "" {
err = r.removeTermGWACLRole(log, client, registration)
if err != nil {
r.updateStatusError(ctx, log, registration, ConsulErrorACL, err)
return err
}
}
patch := r.RemoveFinalizersPatch(registration, RegistrationFinalizer)
err = r.Patch(ctx, registration, patch)
if err != nil {
return err
}

return nil
}

func (r *RegistrationsController) deregisterService(log logr.Logger, client *capi.Client, registration *v1alpha1.Registration) error {
deRegReq := registration.ToCatalogDeregistration()

_, err := client.Catalog().Deregister(deRegReq, nil)
Expand All @@ -118,30 +208,76 @@
return err
}

patch := r.RemoveFinalizersPatch(registration, RegistrationFinalizer)
log.Info("Successfully deregistered service", "svcID", deRegReq.ServiceID)
return nil
}

if err := r.Patch(ctx, registration, patch); err != nil {
func (r *RegistrationsController) removeTermGWACLRole(log logr.Logger, client *capi.Client, registration *v1alpha1.Registration) error {
roles, _, err := client.ACL().RoleList(nil)
if err != nil {
return err
}
log.Info("Successfully deregistered service", "svcID", deRegReq.ServiceID)

var role *capi.ACLRole
for _, r := range roles {
if strings.HasSuffix(r.Name, "terminating-gateway-acl-role") {
fmt.Printf("Role: %v\n", r)
role = r
break
}
}

if role == nil {
log.Info("terminating gateway role not found")
return nil
}

var policyID string

expectedPolicyName := servicePolicyName(registration.Spec.Service.Name)
role.Policies = slices.DeleteFunc(role.Policies, func(i *capi.ACLRolePolicyLink) bool {
if i.Name == expectedPolicyName {
policyID = i.ID
return true
}
return false
})

if policyID == "" {
log.Info("policy not found on terminating gateway role", "policyName", expectedPolicyName)
return nil
}

_, _, err = client.ACL().RoleUpdate(role, nil)
if err != nil {
log.Error(err, "error updating role")
return err
}

_, err = client.ACL().PolicyDelete(policyID, nil)
if err != nil {
log.Error(err, "error deleting service policy")
return err
}

return nil
}

func (r *RegistrationsController) Logger(name types.NamespacedName) logr.Logger {
return r.Log.WithValues("request", name)
func servicePolicyName(name string) string {
return fmt.Sprintf("%s-write-policy", name)
}

func (r *RegistrationsController) updateStatusError(ctx context.Context, registration *v1alpha1.Registration, reason string, reconcileErr error) {
func (r *RegistrationsController) updateStatusError(ctx context.Context, log logr.Logger, registration *v1alpha1.Registration, reason string, reconcileErr error) {
registration.SetSyncedCondition(corev1.ConditionFalse, reason, reconcileErr.Error())
registration.Status.LastSyncedTime = &metav1.Time{Time: time.Now()}

err := r.Status().Update(ctx, registration)
if err != nil {
r.Log.Error(err, "failed to update Registration status", "name", registration.Name, "namespace", registration.Namespace)
log.Error(err, "failed to update Registration status", "name", registration.Name, "namespace", registration.Namespace)
}
}

func (r *RegistrationsController) updateStatus(ctx context.Context, req types.NamespacedName) error {
func (r *RegistrationsController) updateStatus(ctx context.Context, log logr.Logger, req types.NamespacedName) error {
registration := &v1alpha1.Registration{}

err := r.Get(ctx, req, registration)
Expand All @@ -154,12 +290,16 @@

err = r.Status().Update(ctx, registration)
if err != nil {
r.Log.Error(err, "failed to update Registration status", "name", registration.Name, "namespace", registration.Namespace)
log.Error(err, "failed to update Registration status", "name", registration.Name, "namespace", registration.Namespace)
return err
}
return nil
}

func (r *RegistrationsController) Logger(name types.NamespacedName) logr.Logger {
return r.Log.WithValues("request", name)
}

func (r *RegistrationsController) SetupWithManager(mgr ctrl.Manager) error {
return setupWithManager(mgr, &v1alpha1.Registration{}, r)
}
Loading
Loading