Skip to content

Commit 445a848

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 Closes: #OSPRH-1921 Closes: #OSPRH-1922
1 parent 032238f commit 445a848

11 files changed

Lines changed: 145 additions & 10 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
@@ -719,6 +719,7 @@ func (r *OVNDBClusterReconciler) reconcileNormal(ctx context.Context, instance *
719719
instance.Status.Conditions.MarkTrue(condition.DeploymentReadyCondition, condition.DeploymentReadyMessage)
720720
instance.Status.Conditions.MarkTrue(condition.ExposeServiceReadyCondition, condition.ExposeServiceReadyMessage)
721721
internalDbAddress := []string{}
722+
internalDbAddressRbacFullAccess := []string{}
722723
var svcPort int32
723724
scheme := "tcp"
724725
if instance.Spec.TLS.Enabled() {
@@ -734,6 +735,12 @@ func (r *OVNDBClusterReconciler) reconcileNormal(ctx context.Context, instance *
734735
// TODO: Watch operator.openshift.io resource once cluster domain is customizable
735736
clusterDomain := clusterdns.GetDNSClusterDomain()
736737
internalDbAddress = append(internalDbAddress, fmt.Sprintf("%s:%s.%s.svc.%s:%d", scheme, svc.Name, svc.Namespace, clusterDomain, svcPort))
738+
739+
// if TLS is enabled and DBType is SB, RBAC for ovn-controller is used, so additionally
740+
// set the internalDbAddressRbacFullAccess has to be set
741+
if instance.Spec.TLS.Enabled() && instance.Spec.DBType == ovnv1.SBDBType {
742+
internalDbAddressRbacFullAccess = append(internalDbAddressRbacFullAccess, fmt.Sprintf("%s:%s.%s.svc.%s:%d", scheme, svc.Name, svc.Namespace, clusterDomain, ovndbcluster.DbPortSBRBACFullAccess))
743+
}
737744
}
738745

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

744751
// Set DB Address
745752
instance.Status.InternalDBAddress = strings.Join(internalDbAddress, ",")
753+
if len(internalDbAddressRbacFullAccess) > 0 {
754+
instance.Status.InternalDBAddressRbacFullAccess = strings.Join(internalDbAddressRbacFullAccess, ",")
755+
}
746756
if instance.Spec.DBType == ovnv1.SBDBType && (instance.Spec.NetworkAttachment != "" || instance.Spec.Override.Service != nil) {
747757
// This config map will populate the sb db address to edpm, can't use the nb
748758
// 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
}

internal/ovndbcluster/const.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ const (
99

1010
// DbPortSB is the port number for the OVN Southbound database
1111
DbPortSB int32 = 6642
12+
// DbPortSBRBACFullAccess is the port number for the OVN Southbound database
13+
// which provides connection with full access to the SB db.
14+
// In case when DbPortSB provides RBAC listener for ovn-controller,
15+
// this port is used to provide listener with full write access used by Northd.
16+
DbPortSBRBACFullAccess int32 = 16642
1217
// RaftPortSB is the port number for the OVN Southbound database Raft protocol
1318
RaftPortSB int32 = 6644
1419
)

internal/ovndbcluster/service.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ func Service(
3737
Protocol: corev1.ProtocolTCP,
3838
},
3939
}
40+
if instance.Spec.TLS.Enabled() && instance.Spec.DBType == ovnv1.SBDBType {
41+
ports = append(ports, corev1.ServicePort{
42+
Name: dbPortName + "-rbac-full-access",
43+
Port: DbPortSBRBACFullAccess,
44+
Protocol: corev1.ProtocolTCP,
45+
})
46+
}
4047

4148
// Add metrics port if metrics are enabled and exporter image is specified
4249
if instance.Spec.ExporterImage != "" && (instance.Spec.MetricsEnabled == nil || *instance.Spec.MetricsEnabled) {

templates/ovndbcluster/bin/setup.sh

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,24 @@ if [[ "$(hostname)" == "{{ .SERVICE_NAME }}-0" ]]; then
120120

121121
{{- if .TLS }}
122122
${CTLCMD} set-ssl {{.OVNDB_KEY_PATH}} {{.OVNDB_CERT_PATH}} {{.OVNDB_CACERT_PATH}}
123+
if [[ "${DB_TYPE}" == "sb" ]]; then
124+
# Use RBAC for the connections of the ovn-controller to SB
125+
${CTLCMD} set-connection role=ovn-controller ${DB_SCHEME}:${DB_PORT}:${DB_ADDR}
126+
# In this case, Northd needs to have full access to the DB so there need
127+
# to be another connection defined for it
128+
# TODO(slaweq): port has to be also set in Northd
129+
${CTLCMD} -- --id=@conn_uuid create Connection target="${DB_SCHEME}\:16642" -- add SB_Global . connections @conn_uuid
130+
else
131+
# No RBAC for connecting to the Northbound DB so only one connection
132+
# defined is fine
133+
${CTLCMD} set-connection ${DB_SCHEME}:${DB_PORT}:${DB_ADDR}
134+
fi
135+
123136
{{- else }}
124137
${CTLCMD} del-ssl
125-
{{- end }}
138+
# If TLS is disabled, RBAC can't be used, so one connection defined is enough
126139
${CTLCMD} set-connection ${DB_SCHEME}:${DB_PORT}:${DB_ADDR}
140+
{{- end }}
127141

128142
# OVN does not support setting inactivity-probe through --remote cli arg so
129143
# we have to set it after database is up.
@@ -138,8 +152,10 @@ if [[ "$(hostname)" == "{{ .SERVICE_NAME }}-0" ]]; then
138152
# TODO: Consider migrating inactivity probe setting to config files when
139153
# we update to ovs 3.3. See --config-file in ovsdb-server(1) for more
140154
# details.
141-
while [ "$(${CTLCMD} get connection . inactivity_probe)" != "{{ .OVN_INACTIVITY_PROBE }}" ]; do
142-
${CTLCMD} --inactivity-probe={{ .OVN_INACTIVITY_PROBE }} set-connection ${DB_SCHEME}:${DB_PORT}:${DB_ADDR}
155+
for connection_id in $(${CTLCMD} -f csv --no-headings --columns=_uuid list connection); do
156+
while [ "$(${CTLCMD} get connection $connection_id inactivity_probe)" != "{{ .OVN_INACTIVITY_PROBE }}" ]; do
157+
${CTLCMD} set connection $connection_id inactivity_probe={{ .OVN_INACTIVITY_PROBE }}
158+
done
143159
done
144160
${CTLCMD} list connection
145161

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)