Skip to content

Commit 4d423e7

Browse files
committed
Application Credential support
1 parent 6c2d4af commit 4d423e7

File tree

7 files changed

+225
-6
lines changed

7 files changed

+225
-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/barbican_controller.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ func (r *BarbicanReconciler) GetLogger(ctx context.Context) logr.Logger {
106106
//+kubebuilder:rbac:groups=keystone.openstack.org,resources=keystoneapis,verbs=get;list;watch;
107107
//+kubebuilder:rbac:groups=keystone.openstack.org,resources=keystoneservices,verbs=get;list;watch;create;update;patch;delete;
108108
//+kubebuilder:rbac:groups=keystone.openstack.org,resources=keystoneendpoints,verbs=get;list;watch;create;update;patch;delete;
109+
//+kubebuilder:rbac:groups=keystone.openstack.org,resources=keystoneapplicationcredentials,verbs=get;list;watch
109110
//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete;
110111
//+kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch;create;update;patch;delete;
111112
//+kubebuilder:rbac:groups=core,resources=configmaps,verbs=get;list;watch;create;update;patch;delete;
@@ -120,6 +121,7 @@ func (r *BarbicanReconciler) GetLogger(ctx context.Context) logr.Logger {
120121

121122
// service account, role, rolebinding
122123
//+kubebuilder:rbac:groups="",resources=serviceaccounts,verbs=get;list;watch;create;update;patch
124+
//+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch
123125
//+kubebuilder:rbac:groups="rbac.authorization.k8s.io",resources=roles,verbs=get;list;watch;create;update;patch
124126
//+kubebuilder:rbac:groups="rbac.authorization.k8s.io",resources=rolebindings,verbs=get;list;watch;create;update;patch
125127
//+kubebuilder:rbac:groups="security.openshift.io",resourceNames=anyuid,resources=securitycontextconstraints,verbs=use
@@ -717,6 +719,18 @@ func (r *BarbicanReconciler) generateServiceConfig(
717719
"EnableSecureRBAC": instance.Spec.BarbicanAPI.EnableSecureRBAC,
718720
}
719721

722+
templateParameters["UseApplicationCredentials"] = false
723+
// Try to get Application Credential for this service (via keystone api helper)
724+
if acData, err := keystonev1.GetApplicationCredentialFromSecret(ctx, r.Client, instance.Namespace, barbican.ServiceName); err != nil {
725+
Log.Error(err, "Failed to get ApplicationCredential for service", "service", barbican.ServiceName)
726+
return err
727+
} else if acData != nil {
728+
templateParameters["UseApplicationCredentials"] = true
729+
templateParameters["ACID"] = acData.ID
730+
templateParameters["ACSecret"] = acData.Secret
731+
Log.Info("Using ApplicationCredentials auth", "service", barbican.ServiceName)
732+
}
733+
720734
// To avoid a json parsing error in kolla files, we always need to set PKCS11ClientDataPath
721735
// This gets overridden in the PKCS11 section below if needed.
722736
templateParameters["PKCS11ClientDataPath"] = barbicanv1beta1.DefaultPKCS11ClientDataPath

controllers/barbicanapi_controller.go

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,11 @@ func (r *BarbicanAPIReconciler) reconcileNormal(ctx context.Context, instance *b
630630

631631
Log.Info(fmt.Sprintf("[API] Got secrets '%s'", instance.Name))
632632

633+
// Verify Application Credentials if available
634+
if res, err := r.verifyApplicationCredentials(ctx, helper, instance, &configVars); err != nil || res.RequeueAfter > 0 {
635+
return res, err
636+
}
637+
633638
//
634639
// TLS input validation
635640
//
@@ -1026,6 +1031,42 @@ func (r *BarbicanAPIReconciler) SetupWithManager(mgr ctrl.Manager) error {
10261031
return err
10271032
}
10281033

1034+
// Application Credential secret watching function
1035+
acSecretFn := func(_ context.Context, o client.Object) []reconcile.Request {
1036+
name := o.GetName()
1037+
ns := o.GetNamespace()
1038+
result := []reconcile.Request{}
1039+
1040+
// Only handle Secret objects
1041+
if _, isSecret := o.(*corev1.Secret); !isSecret {
1042+
return nil
1043+
}
1044+
1045+
// Check if this is a barbican AC secret by name pattern (ac-barbican-secret)
1046+
expectedSecretName := keystonev1.GetACSecretName("barbican")
1047+
if name == expectedSecretName {
1048+
// get all BarbicanAPI CRs in this namespace
1049+
barbicanAPIs := &barbicanv1beta1.BarbicanAPIList{}
1050+
listOpts := []client.ListOption{
1051+
client.InNamespace(ns),
1052+
}
1053+
if err := r.List(context.Background(), barbicanAPIs, listOpts...); err != nil {
1054+
return nil
1055+
}
1056+
1057+
// Enqueue reconcile for all barbican API instances
1058+
for _, cr := range barbicanAPIs.Items {
1059+
objKey := client.ObjectKey{
1060+
Namespace: ns,
1061+
Name: cr.Name,
1062+
}
1063+
result = append(result, reconcile.Request{NamespacedName: objKey})
1064+
}
1065+
}
1066+
1067+
return result
1068+
}
1069+
10291070
return ctrl.NewControllerManagedBy(mgr).
10301071
For(&barbicanv1beta1.BarbicanAPI{}).
10311072
Owns(&corev1.Service{}).
@@ -1037,9 +1078,12 @@ func (r *BarbicanAPIReconciler) SetupWithManager(mgr ctrl.Manager) error {
10371078
handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc),
10381079
builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}),
10391080
).
1081+
Watches(&corev1.Secret{},
1082+
handler.EnqueueRequestsFromMapFunc(acSecretFn)).
10401083
Watches(&topologyv1.Topology{},
10411084
handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc),
1042-
builder.WithPredicates(predicate.GenerationChangedPredicate{})).
1085+
builder.WithPredicates(predicate.GenerationChangedPredicate{}),
1086+
).
10431087
Complete(r)
10441088
}
10451089

@@ -1076,3 +1120,46 @@ func (r *BarbicanAPIReconciler) findObjectsForSrc(ctx context.Context, src clien
10761120

10771121
return requests
10781122
}
1123+
1124+
// verifyApplicationCredentials checks if ApplicationCredential secret exists and adds it to configVars
1125+
// The AC secret is created by the keystone-operator's AC controller when the AC is ready.
1126+
// If the secret exists and is valid, we use AC auth. Otherwise, we fall back to password auth.
1127+
func (r *BarbicanAPIReconciler) verifyApplicationCredentials(
1128+
ctx context.Context,
1129+
_ *helper.Helper,
1130+
instance *barbicanv1beta1.BarbicanAPI,
1131+
configVars *map[string]env.Setter,
1132+
) (ctrl.Result, error) {
1133+
log := r.GetLogger(ctx)
1134+
1135+
// Check if AC secret exists (created by keystone AC controller)
1136+
acSecretName := keystonev1.GetACSecretName(barbican.ServiceName)
1137+
secretKey := types.NamespacedName{Namespace: instance.Namespace, Name: acSecretName}
1138+
1139+
hash, res, err := secret.VerifySecret(
1140+
ctx,
1141+
secretKey,
1142+
[]string{"AC_ID", "AC_SECRET"},
1143+
r.Client,
1144+
10*time.Second,
1145+
)
1146+
1147+
// VerifySecret returns res.RequeueAfter > 0 when secret not found (not an error)
1148+
// For AC, this is optional, so we just skip it instead of requeueing
1149+
if res.RequeueAfter > 0 {
1150+
log.Info("ApplicationCredential secret not found, using password auth")
1151+
return ctrl.Result{}, nil
1152+
}
1153+
1154+
if err != nil {
1155+
// Actual error (not NotFound) - log and continue with password auth
1156+
log.Info("ApplicationCredential secret verification failed, continuing with password auth", "error", err.Error())
1157+
return ctrl.Result{}, nil
1158+
}
1159+
1160+
// AC secret exists and is valid - add to configVars for hash tracking
1161+
(*configVars)["secret-"+acSecretName] = env.SetValue(hash)
1162+
log.Info("Using ApplicationCredential authentication")
1163+
1164+
return ctrl.Result{}, nil
1165+
}

controllers/barbicankeystonelistener_controller.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@ import (
2929
"github.com/openstack-k8s-operators/barbican-operator/pkg/barbican"
3030
"github.com/openstack-k8s-operators/barbican-operator/pkg/barbicankeystonelistener"
3131
topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1"
32-
33-
// keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1"
3432
"github.com/openstack-k8s-operators/lib-common/modules/common"
3533
"github.com/openstack-k8s-operators/lib-common/modules/common/condition"
3634
"github.com/openstack-k8s-operators/lib-common/modules/common/deployment"
@@ -750,8 +748,10 @@ func (r *BarbicanKeystoneListenerReconciler) SetupWithManager(mgr ctrl.Manager)
750748
).
751749
Watches(&topologyv1.Topology{},
752750
handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc),
753-
builder.WithPredicates(predicate.GenerationChangedPredicate{})).
751+
builder.WithPredicates(predicate.GenerationChangedPredicate{}),
752+
).
754753
Complete(r)
754+
755755
}
756756

757757
func (r *BarbicanKeystoneListenerReconciler) findObjectsForSrc(ctx context.Context, src client.Object) []reconcile.Request {

controllers/barbicanworker_controller.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -771,7 +771,8 @@ func (r *BarbicanWorkerReconciler) SetupWithManager(mgr ctrl.Manager) error {
771771
).
772772
Watches(&topologyv1.Topology{},
773773
handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc),
774-
builder.WithPredicates(predicate.GenerationChangedPredicate{})).
774+
builder.WithPredicates(predicate.GenerationChangedPredicate{}),
775+
).
775776
Complete(r)
776777
}
777778

templates/barbican/config/00-default.conf

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,20 @@ connection={{ .DatabaseConnection }}
1414
[keystone_authtoken]
1515
auth_version = v3
1616
auth_url={{ .KeystoneAuthURL }}
17-
auth_type=password
17+
auth_type = {{ if .UseApplicationCredentials }}v3applicationcredential{{ else }}password{{ end }}
18+
19+
{{ if .UseApplicationCredentials -}}
20+
application_credential_id = {{ .ACID }}
21+
application_credential_secret = {{ .ACSecret }}
22+
{{- else -}}
1823
username={{ .ServiceUser }}
1924
user_domain_name=Default
2025
password = {{ .ServicePassword }}
2126
project_name=service
2227
project_domain_name=Default
2328
interface = internal
2429
{{- end }}
30+
{{- end }}
2531

2632
[oslo_messaging_notifications]
2733
driver=messagingv2

tests/functional/barbican_controller_test.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ import (
1515
controllers "github.com/openstack-k8s-operators/barbican-operator/controllers"
1616
"github.com/openstack-k8s-operators/barbican-operator/pkg/barbican"
1717
topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1"
18+
keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1"
1819
condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition"
1920
mariadb_test "github.com/openstack-k8s-operators/mariadb-operator/api/test/helpers"
2021
corev1 "k8s.io/api/core/v1"
22+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2123
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2224
"k8s.io/apimachinery/pkg/types"
2325
)
@@ -1589,6 +1591,114 @@ var _ = Describe("Barbican controller", func() {
15891591
})
15901592
})
15911593

1594+
When("An ApplicationCredential is created for Barbican", func() {
1595+
var (
1596+
acName string
1597+
acSecretName string
1598+
)
1599+
BeforeEach(func() {
1600+
DeferCleanup(k8sClient.Delete, ctx,
1601+
CreateBarbicanMessageBusSecret(
1602+
barbicanTest.Instance.Namespace,
1603+
barbicanTest.RabbitmqSecretName,
1604+
),
1605+
)
1606+
DeferCleanup(k8sClient.Delete, ctx,
1607+
CreateBarbicanSecret(
1608+
barbicanTest.Instance.Namespace, SecretName))
1609+
DeferCleanup(th.DeleteInstance,
1610+
CreateBarbican(barbicanTest.Instance, GetDefaultBarbicanSpec()))
1611+
DeferCleanup(
1612+
mariadb.DeleteDBService,
1613+
mariadb.CreateDBService(
1614+
barbicanTest.Instance.Namespace,
1615+
GetBarbican(barbicanTest.Instance).Spec.DatabaseInstance,
1616+
corev1.ServiceSpec{
1617+
Ports: []corev1.ServicePort{{Port: 3306}}}))
1618+
1619+
DeferCleanup(keystone.DeleteKeystoneAPI,
1620+
keystone.CreateKeystoneAPI(barbicanTest.Instance.Namespace),
1621+
)
1622+
1623+
acName = fmt.Sprintf("ac-%s", barbican.ServiceName)
1624+
acSecretName = acName + "-secret"
1625+
secret := &corev1.Secret{
1626+
ObjectMeta: metav1.ObjectMeta{
1627+
Namespace: barbicanTest.Instance.Namespace,
1628+
Name: acSecretName,
1629+
},
1630+
Data: map[string][]byte{
1631+
"AC_ID": []byte("foo"),
1632+
"AC_SECRET": []byte("supersecretacsecret"),
1633+
},
1634+
}
1635+
DeferCleanup(k8sClient.Delete, ctx, secret)
1636+
Expect(k8sClient.Create(ctx, secret)).To(Succeed())
1637+
1638+
ac := &keystonev1.KeystoneApplicationCredential{
1639+
ObjectMeta: metav1.ObjectMeta{
1640+
Namespace: barbicanTest.Instance.Namespace,
1641+
Name: acName,
1642+
},
1643+
Spec: keystonev1.KeystoneApplicationCredentialSpec{
1644+
UserName: barbican.ServiceName,
1645+
Secret: SecretName,
1646+
PasswordSelector: "BarbicanPassword",
1647+
Roles: []string{"admin", "member"},
1648+
AccessRules: []keystonev1.ACRule{{Service: "identity", Method: "POST", Path: "/auth/tokens"}},
1649+
ExpirationDays: 30,
1650+
GracePeriodDays: 5,
1651+
},
1652+
}
1653+
DeferCleanup(k8sClient.Delete, ctx, ac)
1654+
Expect(k8sClient.Create(ctx, ac)).To(Succeed())
1655+
1656+
fetched := &keystonev1.KeystoneApplicationCredential{}
1657+
key := types.NamespacedName{Namespace: ac.Namespace, Name: ac.Name}
1658+
Expect(k8sClient.Get(ctx, key, fetched)).To(Succeed())
1659+
1660+
fetched.Status.SecretName = acSecretName
1661+
now := metav1.Now()
1662+
readyCond := condition.Condition{
1663+
Type: condition.ReadyCondition,
1664+
Status: corev1.ConditionTrue,
1665+
Reason: condition.ReadyReason,
1666+
Message: condition.ReadyMessage,
1667+
LastTransitionTime: now,
1668+
}
1669+
fetched.Status.Conditions = condition.Conditions{readyCond}
1670+
Expect(k8sClient.Status().Update(ctx, fetched)).To(Succeed())
1671+
1672+
infra.SimulateTransportURLReady(barbicanTest.BarbicanTransportURL)
1673+
mariadb.SimulateMariaDBAccountCompleted(barbicanTest.BarbicanDatabaseAccount)
1674+
mariadb.SimulateMariaDBDatabaseCompleted(barbicanTest.BarbicanDatabaseName)
1675+
1676+
th.SimulateJobSuccess(barbicanTest.BarbicanDBSync)
1677+
1678+
keystone.SimulateKeystoneEndpointReady(barbicanTest.BarbicanKeystoneEndpoint)
1679+
})
1680+
1681+
It("should render ApplicationCredential auth in 00-default.conf", func() {
1682+
keystone.SimulateKeystoneEndpointReady(barbicanTest.BarbicanKeystoneEndpoint)
1683+
1684+
var cfgSecret corev1.Secret
1685+
Eventually(func(g Gomega) {
1686+
cfgSecret = th.GetSecret(barbicanTest.BarbicanAPIConfigSecret)
1687+
g.Expect(cfgSecret).NotTo(BeNil())
1688+
}, timeout, interval).Should(Succeed())
1689+
1690+
conf := string(cfgSecret.Data["00-default.conf"])
1691+
1692+
// check for rendered AC lines
1693+
Expect(conf).To(ContainSubstring(
1694+
"application_credential_id = foo"),
1695+
)
1696+
Expect(conf).To(ContainSubstring(
1697+
"application_credential_secret = supersecretacsecret"),
1698+
)
1699+
})
1700+
})
1701+
15921702
// Run MariaDBAccount suite tests. these are pre-packaged ginkgo tests
15931703
// that exercise standard account create / update patterns that should be
15941704
// common to all controllers that ensure MariaDBAccount CRs.

0 commit comments

Comments
 (0)