Skip to content

Commit 139670c

Browse files
committed
Application Credential support
1 parent 6e48eac commit 139670c

File tree

6 files changed

+142
-6
lines changed

6 files changed

+142
-6
lines changed

config/rbac/role.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ rules:
102102
- keystone.openstack.org
103103
resources:
104104
- keystoneapis
105+
- keystoneapplicationcredentials
105106
verbs:
106107
- get
107108
- list

controllers/swift_common.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@ import (
2222
"fmt"
2323
"time"
2424

25+
topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1"
2526
"github.com/openstack-k8s-operators/lib-common/modules/common/condition"
27+
"github.com/openstack-k8s-operators/lib-common/modules/common/env"
2628
"github.com/openstack-k8s-operators/lib-common/modules/common/helper"
2729
"github.com/openstack-k8s-operators/lib-common/modules/common/secret"
28-
"k8s.io/apimachinery/pkg/types"
2930

30-
topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1"
31-
"github.com/openstack-k8s-operators/lib-common/modules/common/env"
3231
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
32+
"k8s.io/apimachinery/pkg/types"
3333
ctrl "sigs.k8s.io/controller-runtime"
3434
"sigs.k8s.io/controller-runtime/pkg/client"
3535
"sigs.k8s.io/controller-runtime/pkg/log"

controllers/swift_controller.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ func (r *SwiftReconciler) GetLogger(ctx context.Context) logr.Logger {
5959
//+kubebuilder:rbac:groups=swift.openstack.org,resources=swifts,verbs=get;list;watch;create;update;patch;delete
6060
//+kubebuilder:rbac:groups=swift.openstack.org,resources=swifts/status,verbs=get;update;patch
6161
//+kubebuilder:rbac:groups=swift.openstack.org,resources=swifts/finalizers,verbs=update;patch
62+
//+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch
63+
//+kubebuilder:rbac:groups=keystone.openstack.org,resources=keystoneapplicationcredentials,verbs=get;list;watch
6264

6365
// service account, role, rolebinding
6466
// +kubebuilder:rbac:groups="",resources=serviceaccounts,verbs=get;list;watch;create;update;patch

controllers/swiftproxy_controller.go

Lines changed: 114 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,19 @@ func (r *SwiftProxyReconciler) Reconcile(ctx context.Context, req ctrl.Request)
512512
return ctrlResult, err
513513
}
514514

515+
// Check for Application Credentials
516+
ctrlResult, err = r.verifyApplicationCredentials(
517+
ctx,
518+
r.GetLogger(ctx),
519+
helper.GetClient(),
520+
instance.Namespace,
521+
"swift",
522+
&envVars,
523+
)
524+
if (err != nil || ctrlResult != ctrl.Result{}) {
525+
return ctrlResult, err
526+
}
527+
515528
// Get the service password and pass it to the template
516529
sps, _, err := secret.GetSecret(ctx, helper, instance.Spec.Secret, instance.Namespace)
517530
if err != nil {
@@ -578,6 +591,20 @@ func (r *SwiftProxyReconciler) Reconcile(ctx context.Context, req ctrl.Request)
578591
return ctrl.Result{}, err
579592
}
580593

594+
// Get Application Credential data if available
595+
useAC := false
596+
acID := ""
597+
acSecret := ""
598+
// Try to get Application Credential for this service (via keystone api helper)
599+
if acData, err := keystonev1.GetApplicationCredentialFromSecret(ctx, r.Client, instance.Namespace, swift.ServiceName); err != nil {
600+
Log.Error(err, "Failed to get ApplicationCredential for service", "service", swift.ServiceName)
601+
} else if acData != nil {
602+
useAC = true
603+
acID = acData.ID
604+
acSecret = acData.Secret
605+
Log.Info("Using ApplicationCredentials auth", "service", swift.ServiceName)
606+
}
607+
581608
// Create a Secret populated with content from templates/
582609
tpl := swiftproxy.SecretTemplates(
583610
instance,
@@ -591,6 +618,9 @@ func (r *SwiftProxyReconciler) Reconcile(ctx context.Context, req ctrl.Request)
591618
os.GetRegion(),
592619
transportURLString,
593620
instance.Spec.APITimeout,
621+
useAC,
622+
acID,
623+
acSecret,
594624
)
595625
err = secret.EnsureSecrets(ctx, helper, instance, tpl, &envVars)
596626
if err != nil {
@@ -846,7 +876,43 @@ func (r *SwiftProxyReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Ma
846876
return nil
847877
}
848878

849-
return ctrl.NewControllerManagedBy(mgr).
879+
// Application Credential secret watching function
880+
acSecretFn := func(_ context.Context, o client.Object) []reconcile.Request {
881+
name := o.GetName()
882+
ns := o.GetNamespace()
883+
result := []reconcile.Request{}
884+
885+
// Only handle Secret objects
886+
if _, isSecret := o.(*corev1.Secret); !isSecret {
887+
return nil
888+
}
889+
890+
// Check if this is a swift AC secret by name pattern (ac-swift-secret)
891+
expectedSecretName := keystonev1.GetACSecretName("swift")
892+
if name == expectedSecretName {
893+
// get all SwiftProxy CRs in this namespace
894+
swiftProxies := &swiftv1beta1.SwiftProxyList{}
895+
listOpts := []client.ListOption{
896+
client.InNamespace(ns),
897+
}
898+
if err := r.List(context.Background(), swiftProxies, listOpts...); err != nil {
899+
return nil
900+
}
901+
902+
// Enqueue reconcile for all swift proxy instances
903+
for _, cr := range swiftProxies.Items {
904+
objKey := client.ObjectKey{
905+
Namespace: ns,
906+
Name: cr.Name,
907+
}
908+
result = append(result, reconcile.Request{NamespacedName: objKey})
909+
}
910+
}
911+
912+
return result
913+
}
914+
915+
b := ctrl.NewControllerManagedBy(mgr).
850916
For(&swiftv1beta1.SwiftProxy{}).
851917
Owns(&corev1.Secret{}).
852918
Owns(&keystonev1.KeystoneService{}).
@@ -859,15 +925,17 @@ func (r *SwiftProxyReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Ma
859925
handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc),
860926
builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}),
861927
).
928+
Watches(&corev1.Secret{},
929+
handler.EnqueueRequestsFromMapFunc(acSecretFn)).
862930
Watches(&memcachedv1.Memcached{},
863931
handler.EnqueueRequestsFromMapFunc(memcachedFn)).
864932
Watches(&topologyv1.Topology{},
865933
handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc),
866934
builder.WithPredicates(predicate.GenerationChangedPredicate{})).
867935
Watches(&keystonev1.KeystoneAPI{},
868936
handler.EnqueueRequestsFromMapFunc(r.findObjectForSrc),
869-
builder.WithPredicates(keystonev1.KeystoneAPIStatusChangedPredicate)).
870-
Complete(r)
937+
builder.WithPredicates(keystonev1.KeystoneAPIStatusChangedPredicate))
938+
return b.Complete(r)
871939
}
872940

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

10321100
return transportURL, op, err
10331101
}
1102+
1103+
// verifyApplicationCredentials checks if ApplicationCredential secret exists and adds it to configVars
1104+
// The AC secret is created by the keystone-operator's AC controller when the AC is ready.
1105+
// If the secret exists and is valid, we use AC auth. Otherwise, we fall back to password auth.
1106+
func (r *SwiftProxyReconciler) verifyApplicationCredentials(
1107+
ctx context.Context,
1108+
log logr.Logger,
1109+
client client.Client,
1110+
namespace string,
1111+
serviceName string,
1112+
configVars *map[string]env.Setter,
1113+
) (ctrl.Result, error) {
1114+
// Check if AC secret exists (created by keystone AC controller)
1115+
acSecretName := keystonev1.GetACSecretName(serviceName)
1116+
secretKey := types.NamespacedName{Namespace: namespace, Name: acSecretName}
1117+
1118+
hash, res, err := secret.VerifySecret(
1119+
ctx,
1120+
secretKey,
1121+
[]string{"AC_ID", "AC_SECRET"},
1122+
client,
1123+
10*time.Second,
1124+
)
1125+
1126+
// VerifySecret returns res.RequeueAfter > 0 when secret not found (not an error)
1127+
// For AC, this is optional, so we just skip it instead of requeueing
1128+
if res.RequeueAfter > 0 {
1129+
log.V(1).Info("ApplicationCredential secret not found, using password auth")
1130+
return ctrl.Result{}, nil
1131+
}
1132+
1133+
if err != nil {
1134+
// Actual error (not NotFound) - log and continue with password auth
1135+
log.Info("ApplicationCredential secret verification failed, continuing with password auth", "error", err.Error())
1136+
return ctrl.Result{}, nil
1137+
}
1138+
1139+
// AC secret exists and is valid - add to configVars for hash tracking
1140+
(*configVars)["secret-"+acSecretName] = env.SetValue(hash)
1141+
log.Info("Using ApplicationCredential authentication")
1142+
1143+
return ctrl.Result{}, nil
1144+
}

pkg/swiftproxy/templates.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ func SecretTemplates(
4040
keystoneRegion string,
4141
transportURL string,
4242
apiTimeout int,
43+
useApplicationCredentials bool,
44+
applicationCredentialID string,
45+
applicationCredentialSecret string,
4346
) []util.Template {
4447
templateParameters := make(map[string]any)
4548
templateParameters["ServiceUser"] = instance.Spec.ServiceUser
@@ -54,6 +57,13 @@ func SecretTemplates(
5457
templateParameters["TransportURL"] = transportURL
5558
templateParameters["APITimeout"] = apiTimeout
5659

60+
// Application Credential parameters
61+
templateParameters["UseApplicationCredentials"] = useApplicationCredentials
62+
if useApplicationCredentials {
63+
templateParameters["ApplicationCredentialID"] = applicationCredentialID
64+
templateParameters["ApplicationCredentialSecret"] = applicationCredentialSecret
65+
}
66+
5767
// MTLS params
5868
if mc.Status.MTLSCert != "" {
5969
templateParameters["MemcachedAuthCert"] = fmt.Sprint(memcachedv1.CertMountPath())

templates/swiftproxy/config/00-proxy-server.conf

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,18 @@ project_reader_roles = SwiftProjectReader
8080
paste.filter_factory = keystonemiddleware.auth_token:filter_factory
8181
www_authenticate_uri = {{ .KeystonePublicURL }}
8282
auth_url = {{ .KeystonePublicURL }}
83+
{{ if .UseApplicationCredentials -}}
84+
auth_type = v3applicationcredential
85+
application_credential_id = {{ .ApplicationCredentialID }}
86+
application_credential_secret = {{ .ApplicationCredentialSecret }}
87+
{{- else -}}
8388
auth_plugin=password
8489
project_domain_id = default
8590
user_domain_id = default
8691
project_name = service
8792
username = {{ .ServiceUser }}
8893
password = {{ .ServicePassword }}
94+
{{- end }}
8995
delay_auth_decision = True
9096

9197
[filter:s3api]
@@ -108,8 +114,14 @@ use = egg:swift#encryption
108114
[filter:ceilometer]
109115
paste.filter_factory = ceilometermiddleware.swift:filter_factory
110116
auth_url = {{ .KeystonePublicURL }}
117+
{{ if .UseApplicationCredentials -}}
118+
auth_type = v3applicationcredential
119+
application_credential_id = {{ .ApplicationCredentialID }}
120+
application_credential_secret = {{ .ApplicationCredentialSecret }}
121+
{{- else -}}
111122
password = {{ .ServicePassword }}
112123
username = {{ .ServiceUser }}
124+
{{- end }}
113125
region_name = {{ .KeystoneRegion }}
114126
url = {{ .TransportURL }}
115127
project_name = service

0 commit comments

Comments
 (0)