Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ rules:
- keystone.openstack.org
resources:
- keystoneapis
- keystoneapplicationcredentials
verbs:
- get
- list
Expand Down
6 changes: 3 additions & 3 deletions controllers/swift_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ import (
"fmt"
"time"

topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1"
"github.com/openstack-k8s-operators/lib-common/modules/common/condition"
"github.com/openstack-k8s-operators/lib-common/modules/common/env"
"github.com/openstack-k8s-operators/lib-common/modules/common/helper"
"github.com/openstack-k8s-operators/lib-common/modules/common/secret"
"k8s.io/apimachinery/pkg/types"

topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1"
"github.com/openstack-k8s-operators/lib-common/modules/common/env"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
Expand Down
2 changes: 2 additions & 0 deletions controllers/swift_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ func (r *SwiftReconciler) GetLogger(ctx context.Context) logr.Logger {
//+kubebuilder:rbac:groups=swift.openstack.org,resources=swifts,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=swift.openstack.org,resources=swifts/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=swift.openstack.org,resources=swifts/finalizers,verbs=update;patch
//+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch
//+kubebuilder:rbac:groups=keystone.openstack.org,resources=keystoneapplicationcredentials,verbs=get;list;watch

// service account, role, rolebinding
// +kubebuilder:rbac:groups="",resources=serviceaccounts,verbs=get;list;watch;create;update;patch
Expand Down
117 changes: 114 additions & 3 deletions controllers/swiftproxy_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,19 @@ func (r *SwiftProxyReconciler) Reconcile(ctx context.Context, req ctrl.Request)
return ctrlResult, err
}

// Check for Application Credentials
ctrlResult, err = r.verifyApplicationCredentials(
ctx,
r.GetLogger(ctx),
helper.GetClient(),
instance.Namespace,
"swift",
&envVars,
)
if (err != nil || ctrlResult != ctrl.Result{}) {
return ctrlResult, err
}

// Get the service password and pass it to the template
sps, _, err := secret.GetSecret(ctx, helper, instance.Spec.Secret, instance.Namespace)
if err != nil {
Expand Down Expand Up @@ -578,6 +591,20 @@ func (r *SwiftProxyReconciler) Reconcile(ctx context.Context, req ctrl.Request)
return ctrl.Result{}, err
}

// Get Application Credential data if available
useAC := false
acID := ""
acSecret := ""
// Try to get Application Credential for this service (via keystone api helper)
if acData, err := keystonev1.GetApplicationCredentialFromSecret(ctx, r.Client, instance.Namespace, swift.ServiceName); err != nil {
Log.Error(err, "Failed to get ApplicationCredential for service", "service", swift.ServiceName)
} else if acData != nil {
useAC = true
acID = acData.ID
acSecret = acData.Secret
Log.Info("Using ApplicationCredentials auth", "service", swift.ServiceName)
}

// Create a Secret populated with content from templates/
tpl := swiftproxy.SecretTemplates(
instance,
Expand All @@ -591,6 +618,9 @@ func (r *SwiftProxyReconciler) Reconcile(ctx context.Context, req ctrl.Request)
os.GetRegion(),
transportURLString,
instance.Spec.APITimeout,
useAC,
acID,
acSecret,
)
err = secret.EnsureSecrets(ctx, helper, instance, tpl, &envVars)
if err != nil {
Expand Down Expand Up @@ -846,7 +876,43 @@ func (r *SwiftProxyReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Ma
return nil
}

return ctrl.NewControllerManagedBy(mgr).
// Application Credential secret watching function
acSecretFn := func(_ context.Context, o client.Object) []reconcile.Request {
name := o.GetName()
ns := o.GetNamespace()
result := []reconcile.Request{}

// Only handle Secret objects
if _, isSecret := o.(*corev1.Secret); !isSecret {
return nil
}

// Check if this is a swift AC secret by name pattern (ac-swift-secret)
expectedSecretName := keystonev1.GetACSecretName("swift")
if name == expectedSecretName {
// get all SwiftProxy CRs in this namespace
swiftProxies := &swiftv1beta1.SwiftProxyList{}
listOpts := []client.ListOption{
client.InNamespace(ns),
}
if err := r.List(context.Background(), swiftProxies, listOpts...); err != nil {
return nil
}

// Enqueue reconcile for all swift proxy instances
for _, cr := range swiftProxies.Items {
objKey := client.ObjectKey{
Namespace: ns,
Name: cr.Name,
}
result = append(result, reconcile.Request{NamespacedName: objKey})
}
}

return result
}

b := ctrl.NewControllerManagedBy(mgr).
For(&swiftv1beta1.SwiftProxy{}).
Owns(&corev1.Secret{}).
Owns(&keystonev1.KeystoneService{}).
Expand All @@ -859,15 +925,17 @@ func (r *SwiftProxyReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Ma
handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc),
builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}),
).
Watches(&corev1.Secret{},
handler.EnqueueRequestsFromMapFunc(acSecretFn)).
Watches(&memcachedv1.Memcached{},
handler.EnqueueRequestsFromMapFunc(memcachedFn)).
Watches(&topologyv1.Topology{},
handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc),
builder.WithPredicates(predicate.GenerationChangedPredicate{})).
Watches(&keystonev1.KeystoneAPI{},
handler.EnqueueRequestsFromMapFunc(r.findObjectForSrc),
builder.WithPredicates(keystonev1.KeystoneAPIStatusChangedPredicate)).
Complete(r)
builder.WithPredicates(keystonev1.KeystoneAPIStatusChangedPredicate))
return b.Complete(r)
}

func (r *SwiftProxyReconciler) findObjectsForSrc(ctx context.Context, src client.Object) []reconcile.Request {
Expand Down Expand Up @@ -1031,3 +1099,46 @@ func (r *SwiftProxyReconciler) transportURLCreateOrUpdate(

return transportURL, op, err
}

// verifyApplicationCredentials checks if ApplicationCredential secret exists and adds it to configVars
// The AC secret is created by the keystone-operator's AC controller when the AC is ready.
// If the secret exists and is valid, we use AC auth. Otherwise, we fall back to password auth.
func (r *SwiftProxyReconciler) verifyApplicationCredentials(
ctx context.Context,
log logr.Logger,
client client.Client,
namespace string,
serviceName string,
configVars *map[string]env.Setter,
) (ctrl.Result, error) {
// Check if AC secret exists (created by keystone AC controller)
acSecretName := keystonev1.GetACSecretName(serviceName)
secretKey := types.NamespacedName{Namespace: namespace, Name: acSecretName}

hash, res, err := secret.VerifySecret(
ctx,
secretKey,
[]string{"AC_ID", "AC_SECRET"},
client,
10*time.Second,
)

// VerifySecret returns res.RequeueAfter > 0 when secret not found (not an error)
// For AC, this is optional, so we just skip it instead of requeueing
if res.RequeueAfter > 0 {
log.V(1).Info("ApplicationCredential secret not found, using password auth")
return ctrl.Result{}, nil
}

if err != nil {
// Actual error (not NotFound) - log and continue with password auth
log.Info("ApplicationCredential secret verification failed, continuing with password auth", "error", err.Error())
return ctrl.Result{}, nil
}

// AC secret exists and is valid - add to configVars for hash tracking
(*configVars)["secret-"+acSecretName] = env.SetValue(hash)
log.Info("Using ApplicationCredential authentication")

return ctrl.Result{}, nil
}
10 changes: 10 additions & 0 deletions pkg/swiftproxy/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ func SecretTemplates(
keystoneRegion string,
transportURL string,
apiTimeout int,
useApplicationCredentials bool,
applicationCredentialID string,
applicationCredentialSecret string,
) []util.Template {
templateParameters := make(map[string]any)
templateParameters["ServiceUser"] = instance.Spec.ServiceUser
Expand All @@ -54,6 +57,13 @@ func SecretTemplates(
templateParameters["TransportURL"] = transportURL
templateParameters["APITimeout"] = apiTimeout

// Application Credential parameters
templateParameters["UseApplicationCredentials"] = useApplicationCredentials
if useApplicationCredentials {
templateParameters["ApplicationCredentialID"] = applicationCredentialID
templateParameters["ApplicationCredentialSecret"] = applicationCredentialSecret
}

// MTLS params
if mc.Status.MTLSCert != "" {
templateParameters["MemcachedAuthCert"] = fmt.Sprint(memcachedv1.CertMountPath())
Expand Down
12 changes: 12 additions & 0 deletions templates/swiftproxy/config/00-proxy-server.conf
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,18 @@ project_reader_roles = SwiftProjectReader
paste.filter_factory = keystonemiddleware.auth_token:filter_factory
www_authenticate_uri = {{ .KeystonePublicURL }}
auth_url = {{ .KeystonePublicURL }}
{{ if .UseApplicationCredentials -}}
auth_type = v3applicationcredential
application_credential_id = {{ .ApplicationCredentialID }}
application_credential_secret = {{ .ApplicationCredentialSecret }}
{{- else -}}
auth_plugin=password
project_domain_id = default
user_domain_id = default
project_name = service
username = {{ .ServiceUser }}
password = {{ .ServicePassword }}
{{- end }}
delay_auth_decision = True

[filter:s3api]
Expand All @@ -108,8 +114,14 @@ use = egg:swift#encryption
[filter:ceilometer]
paste.filter_factory = ceilometermiddleware.swift:filter_factory
auth_url = {{ .KeystonePublicURL }}
{{ if .UseApplicationCredentials -}}
auth_type = v3applicationcredential
application_credential_id = {{ .ApplicationCredentialID }}
application_credential_secret = {{ .ApplicationCredentialSecret }}
{{- else -}}
password = {{ .ServicePassword }}
username = {{ .ServiceUser }}
{{- end }}
region_name = {{ .KeystoneRegion }}
url = {{ .TransportURL }}
project_name = service
Expand Down