Skip to content

Commit c2aa939

Browse files
committed
Use RBAC while connecting ovn-controllers to SB database
This patch configures RBAC to access OVN SB databases so that ovn-controllers now have limited access to this DB and will only be able to modify its own data. On the other hand Northd requires "full access" to the SB DB, and to achieve that there is another DB listener created on port 16642 for to be used by northd. More info about OVN RBAC can be found in its documentation at [1]. [1] https://docs.ovn.org/en/latest/tutorials/ovn-rbac.html Related: #1922 Assisted-by: claude-opus-4.6 Signed-off-by: Slawek Kaplonski <skaplons@redhat.com>
1 parent 2950c0c commit c2aa939

7 files changed

Lines changed: 111 additions & 7 deletions

File tree

api/bases/ovn.openstack.org_ovndbclusters.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,12 @@ spec:
428428
description: InternalDBAddress - DB IP address used by other Pods
429429
in the cluster
430430
type: string
431+
internalDbAddressRbacFullAccess:
432+
description: |-
433+
InternalDBAddressRbacFullAccess - DB IP address used by other Pods which
434+
requires full access to the SB db, like e.g. Northd. This is used only
435+
when OVN RBAC for ovn-controllers is used (TLS enabled)
436+
type: string
431437
lastAppliedTopology:
432438
description: LastAppliedTopology - the last applied Topology
433439
properties:

api/v1beta1/ovndbcluster_types.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,11 @@ type OVNDBClusterStatus struct {
168168
// InternalDBAddress - DB IP address used by other Pods in the cluster
169169
InternalDBAddress string `json:"internalDbAddress,omitempty"`
170170

171+
// InternalDBAddressRbacFullAccess - DB IP address used by other Pods which
172+
// requires full access to the SB db, like e.g. Northd. This is used only
173+
// when OVN RBAC for ovn-controllers is used (TLS enabled)
174+
InternalDBAddressRbacFullAccess string `json:"internalDbAddressRbacFullAccess,omitempty"`
175+
171176
// NetworkAttachments status of the deployment pods
172177
NetworkAttachments map[string][]string `json:"networkAttachments,omitempty"`
173178

@@ -236,6 +241,23 @@ func (instance OVNDBCluster) GetInternalEndpoint() (string, error) {
236241
return instance.Status.InternalDBAddress, nil
237242
}
238243

244+
// GetInternalEndpointRbacFullAccess - return the DNS name that openshift coreDNS can resolve
245+
func (instance OVNDBCluster) GetInternalEndpointRbacFullAccess() (string, error) {
246+
if !instance.Spec.TLS.Enabled() {
247+
// if TLS is disabled, this is the same as internalDbAddress
248+
return instance.GetInternalEndpoint()
249+
}
250+
if instance.Spec.DBType != SBDBType {
251+
// if DBType is not SB, this is the same as internalDbAddress
252+
return instance.GetInternalEndpoint()
253+
}
254+
255+
if instance.Status.InternalDBAddressRbacFullAccess == "" {
256+
return "", fmt.Errorf("internal DBEndpoint not ready yet for %s", instance.Spec.DBType)
257+
}
258+
return instance.Status.InternalDBAddressRbacFullAccess, nil
259+
}
260+
239261
// GetExternalEndpoint - return the DNS that openstack dnsmasq can resolve
240262
func (instance OVNDBCluster) GetExternalEndpoint() (string, error) {
241263
if (instance.Spec.NetworkAttachment != "" || instance.Spec.Override.Service != nil) && instance.Status.DBAddress == "" {

config/crd/bases/ovn.openstack.org_ovndbclusters.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,12 @@ spec:
428428
description: InternalDBAddress - DB IP address used by other Pods
429429
in the cluster
430430
type: string
431+
internalDbAddressRbacFullAccess:
432+
description: |-
433+
InternalDBAddressRbacFullAccess - DB IP address used by other Pods which
434+
requires full access to the SB db, like e.g. Northd. This is used only
435+
when OVN RBAC for ovn-controllers is used (TLS enabled)
436+
type: string
431437
lastAppliedTopology:
432438
description: LastAppliedTopology - the last applied Topology
433439
properties:

internal/controller/ovndbcluster_controller.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -725,6 +725,7 @@ func (r *OVNDBClusterReconciler) reconcileNormal(ctx context.Context, instance *
725725
instance.Status.Conditions.MarkTrue(condition.DeploymentReadyCondition, condition.DeploymentReadyMessage)
726726
instance.Status.Conditions.MarkTrue(condition.ExposeServiceReadyCondition, condition.ExposeServiceReadyMessage)
727727
internalDbAddress := []string{}
728+
internalDbAddressRbacFullAccess := []string{}
728729
var svcPort int32
729730
scheme := "tcp"
730731
if instance.Spec.TLS.Enabled() {
@@ -740,6 +741,12 @@ func (r *OVNDBClusterReconciler) reconcileNormal(ctx context.Context, instance *
740741
// TODO: Watch operator.openshift.io resource once cluster domain is customizable
741742
clusterDomain := clusterdns.GetDNSClusterDomain()
742743
internalDbAddress = append(internalDbAddress, fmt.Sprintf("%s:%s.%s.svc.%s:%d", scheme, svc.Name, svc.Namespace, clusterDomain, svcPort))
744+
745+
// if TLS is enabled and DBType is SB, RBAC for ovn-controller is used, so additionally
746+
// set the internalDbAddressRbacFullAccess has to be set
747+
if instance.Spec.TLS.Enabled() && instance.Spec.DBType == ovnv1.SBDBType {
748+
internalDbAddressRbacFullAccess = append(internalDbAddressRbacFullAccess, fmt.Sprintf("%s:%s.%s.svc.%s:%d", scheme, svc.Name, svc.Namespace, clusterDomain, ovndbcluster.DbPortSBRBACFullAccess))
749+
}
743750
}
744751

745752
// Note setting this to the singular headless service address (e.g ssl:ovsdbserver-sb...) "works" but will not
@@ -749,6 +756,9 @@ func (r *OVNDBClusterReconciler) reconcileNormal(ctx context.Context, instance *
749756

750757
// Set DB Address
751758
instance.Status.InternalDBAddress = strings.Join(internalDbAddress, ",")
759+
if len(internalDbAddressRbacFullAccess) > 0 {
760+
instance.Status.InternalDBAddressRbacFullAccess = strings.Join(internalDbAddressRbacFullAccess, ",")
761+
}
752762
if instance.Spec.DBType == ovnv1.SBDBType && (instance.Spec.NetworkAttachment != "" || instance.Spec.Override.Service != nil) {
753763
// This config map will populate the sb db address to edpm, can't use the nb
754764
// If there's no networkAttachments the configMap is not needed

internal/controller/ovnnorthd_controller.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -642,7 +642,17 @@ func getInternalEndpoint(
642642
if err != nil {
643643
return "", err
644644
}
645-
internalEndpoint, err := cluster.GetInternalEndpoint()
645+
internalEndpoint := ""
646+
if instance.Spec.TLS.Enabled() && dbType == ovnv1.SBDBType {
647+
// When TLS is enabled, OVN is configured to use RBAC and in that case
648+
// the "regular" internal endpoint is provides limited access to the SB
649+
// for ovn-controllers. Northd howerver needs endpoint with full access
650+
// to the SB db
651+
internalEndpoint, err = cluster.GetInternalEndpointRbacFullAccess()
652+
} else {
653+
internalEndpoint, err = cluster.GetInternalEndpoint()
654+
}
655+
646656
if err != nil {
647657
return "", err
648658
}

test/functional/base_test.go

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,19 +80,29 @@ func GetDefaultOVNDBClusterSpec() ovnv1.OVNDBClusterSpec {
8080
}
8181
}
8282

83-
func GetTLSOVNDBClusterSpec() ovnv1.OVNDBClusterSpec {
83+
func getTLSOVNDBClusterSpecWithTLSSecrets(caBundleSecretName, certSecretName string) ovnv1.OVNDBClusterSpec {
8484
spec := GetDefaultOVNDBClusterSpec()
8585
spec.TLS = tls.SimpleService{
8686
Ca: tls.Ca{
87-
CaBundleSecretName: CABundleSecretName,
87+
CaBundleSecretName: caBundleSecretName,
8888
},
8989
GenericService: tls.GenericService{
90-
SecretName: ptr.To(OvnDbCertSecretName),
90+
SecretName: ptr.To(certSecretName),
9191
},
9292
}
9393
return spec
9494
}
9595

96+
func GetTLSOVNDBClusterSpec() ovnv1.OVNDBClusterSpec {
97+
return getTLSOVNDBClusterSpecWithTLSSecrets(CABundleSecretName, OvnDbCertSecretName)
98+
}
99+
100+
// ovnDBClusterTestTLSSecrets names the K8s secrets used by OVNDBCluster TLS (nil means no TLS).
101+
type ovnDBClusterTestTLSSecrets struct {
102+
CaBundle string
103+
Cert string
104+
}
105+
96106
func CreateOVNDBCluster(namespace string, spec ovnv1.OVNDBClusterSpec) client.Object {
97107
name := ovn.CreateOVNDBCluster(nil, namespace, spec)
98108
return ovn.GetOVNDBCluster(name)
@@ -113,9 +123,32 @@ func ScaleDBCluster(name types.NamespacedName, replicas int32) {
113123

114124
// CreateOVNDBClusters Creates NB and SB OVNDBClusters
115125
func CreateOVNDBClusters(namespace string, nad map[string][]string, replicas int32) []types.NamespacedName {
126+
return createOVNDBClusters(namespace, nad, replicas, nil)
127+
}
128+
129+
// CreateTLSOVNDBClusters Creates NB and SB OVNDBClusters with TLS
130+
func CreateTLSOVNDBClusters(namespace string, nad map[string][]string, replicas int32) []types.NamespacedName {
131+
return createOVNDBClusters(namespace, nad, replicas, &ovnDBClusterTestTLSSecrets{
132+
CaBundle: CABundleSecretName,
133+
Cert: OvnDbCertSecretName,
134+
})
135+
}
136+
137+
// CreateTLSOVNDBClustersUsingSecrets Creates NB and SB OVNDBClusters with TLS using the given secret names.
138+
func CreateTLSOVNDBClustersUsingSecrets(namespace string, nad map[string][]string, replicas int32, caBundleSecret, certSecret string) []types.NamespacedName {
139+
return createOVNDBClusters(namespace, nad, replicas, &ovnDBClusterTestTLSSecrets{
140+
CaBundle: caBundleSecret,
141+
Cert: certSecret,
142+
})
143+
}
144+
145+
func createOVNDBClusters(namespace string, nad map[string][]string, replicas int32, tlsSecrets *ovnDBClusterTestTLSSecrets) []types.NamespacedName {
116146
dbs := []types.NamespacedName{}
117147
for _, db := range []string{ovnv1.NBDBType, ovnv1.SBDBType} {
118148
spec := GetDefaultOVNDBClusterSpec()
149+
if tlsSecrets != nil {
150+
spec = getTLSOVNDBClusterSpecWithTLSSecrets(tlsSecrets.CaBundle, tlsSecrets.Cert)
151+
}
119152
stringNad := ""
120153
// OVNDBCluster doesn't allow multiple NADs, hence map len
121154
// must be <= 1
@@ -157,7 +190,11 @@ func CreateOVNDBClusters(namespace string, nad map[string][]string, replicas int
157190
endpoint := ""
158191
// Check External endpoint when NAD is set
159192
if len(nad) == 0 {
160-
endpoint, _ = ovndbcluster.GetInternalEndpoint()
193+
if tlsSecrets != nil && db == ovnv1.SBDBType {
194+
endpoint, _ = ovndbcluster.GetInternalEndpointRbacFullAccess()
195+
} else {
196+
endpoint, _ = ovndbcluster.GetInternalEndpoint()
197+
}
161198
} else {
162199
endpoint, _ = ovndbcluster.GetExternalEndpoint()
163200
}

test/functional/ovnnorthd_controller_test.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,18 @@ var _ = Describe("OVNNorthd controller", func() {
263263
var ovnNorthdName types.NamespacedName
264264

265265
BeforeEach(func() {
266-
dbs := CreateOVNDBClusters(namespace, map[string][]string{}, 1)
266+
// OVNDBCluster TLS needs these secrets before its StatefulSet exists; use names
267+
// distinct from northd's CABundleSecretName / OvnDbCertSecretName so missing-secret specs stay valid.
268+
DeferCleanup(k8sClient.Delete, ctx, th.CreateCABundleSecret(types.NamespacedName{
269+
Name: OvnDBClusterFuncTLSCaBundleSecretName,
270+
Namespace: namespace,
271+
}))
272+
DeferCleanup(k8sClient.Delete, ctx, th.CreateCertSecret(types.NamespacedName{
273+
Name: OvnDBClusterFuncTLSCertSecretName,
274+
Namespace: namespace,
275+
}))
276+
dbs := CreateTLSOVNDBClustersUsingSecrets(namespace, map[string][]string{}, 1,
277+
OvnDBClusterFuncTLSCaBundleSecretName, OvnDBClusterFuncTLSCertSecretName)
267278
DeferCleanup(DeleteOVNDBClusters, dbs)
268279
spec := GetTLSOVNNorthdSpec()
269280
ovnNorthdName = ovn.CreateOVNNorthd(nil, namespace, spec)
@@ -309,7 +320,7 @@ var _ = Describe("OVNNorthd controller", func() {
309320
)
310321
})
311322

312-
It("creates a StatefulSet with TLS certs attached", func() {
323+
It("creates a StatefulSet with TLS certs attached and DB endpoints set", func() {
313324
DeferCleanup(k8sClient.Delete, ctx, th.CreateCABundleSecret(types.NamespacedName{
314325
Name: CABundleSecretName,
315326
Namespace: namespace,
@@ -345,6 +356,8 @@ var _ = Describe("OVNNorthd controller", func() {
345356
ContainElement(ContainSubstring("--private-key=")),
346357
ContainElement(ContainSubstring("--certificate=")),
347358
ContainElement(ContainSubstring("--ca-cert=")),
359+
ContainElement(ContainSubstring("--ovnnb-db=ssl:ovsdbserver-nb-0."+namespace+".svc.cluster.local:6641")),
360+
ContainElement(ContainSubstring("--ovnsb-db=ssl:ovsdbserver-sb-0."+namespace+".svc.cluster.local:16642")),
348361
))
349362

350363
// Verify metrics container exists and has correct configuration

0 commit comments

Comments
 (0)