Skip to content

Commit

Permalink
Merge pull request #281 from stgraber/main
Browse files Browse the repository at this point in the history
Allow configuring OVN SSL settings through server configuration
  • Loading branch information
tych0 authored Dec 7, 2023
2 parents 1b78620 + 70e59be commit e0a6bfc
Show file tree
Hide file tree
Showing 7 changed files with 229 additions and 8 deletions.
4 changes: 4 additions & 0 deletions doc/api-extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -2292,3 +2292,7 @@ This introduces the configuration keys `cephfs.create_missing`, `cephfs.osd_pg_n

This API extension provides the ability to use flags `--profile`, `--no-profile`, `--device`, and `--config`
when moving an instance between projects and/or storage pools.

## `ovn_ssl_config`
This introduces new server configuration keys to provide the SSL CA and client key pair to access the OVN databases.
The new configuration keys are `network.ovn.ca_cert`, `network.ovn.client_cert` and `network.ovn.client_key`.
24 changes: 24 additions & 0 deletions doc/config_options.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1645,6 +1645,30 @@ When using custom automatic instance placement logic, this option stores the scr
See {ref}`clustering-instance-placement-scriptlet` for more information.
```

```{config:option} network.ovn.ca_cert server-miscellaneous
:defaultdesc: "Content of `/etc/ovn/ovn-central.crt` if present"
:scope: "global"
:shortdesc: "OVN SSL certificate authority"
:type: "string"

```

```{config:option} network.ovn.client_cert server-miscellaneous
:defaultdesc: "Content of `/etc/ovn/cert_host` if present"
:scope: "global"
:shortdesc: "OVN SSL client certificate"
:type: "string"

```

```{config:option} network.ovn.client_key server-miscellaneous
:defaultdesc: "Content of `/etc/ovn/key_host` if present"
:scope: "global"
:shortdesc: "OVN SSL client key"
:type: "string"

```

```{config:option} network.ovn.integration_bridge server-miscellaneous
:defaultdesc: "`br-int`"
:scope: "global"
Expand Down
47 changes: 47 additions & 0 deletions internal/linux/memfd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package linux

import (
"os"

"golang.org/x/sys/unix"

"github.com/lxc/incus/internal/revert"
)

// CreateMemfd creates a new memfd for the provided byte slice.
func CreateMemfd(content []byte) (*os.File, error) {
revert := revert.New()
defer revert.Fail()

// Create the memfd.
fd, err := unix.MemfdCreate("memfd", unix.MFD_CLOEXEC)
if err != nil {
return nil, err
}

revert.Add(func() { unix.Close(fd) })

// Set its size.
err = unix.Ftruncate(fd, int64(len(content)))
if err != nil {
return nil, err
}

// Prepare the storage.
data, err := unix.Mmap(fd, 0, len(content), unix.PROT_READ|unix.PROT_WRITE, unix.MAP_SHARED)
if err != nil {
return nil, err
}

// Write the content.
copy(data, content)

// Cleanup.
err = unix.Munmap(data)
if err != nil {
return nil, err
}

revert.Success()
return os.NewFile(uintptr(fd), "memfd"), nil
}
32 changes: 32 additions & 0 deletions internal/server/cluster/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,11 @@ func (c *Config) NetworkOVNNorthboundConnection() string {
return c.m.GetString("network.ovn.northbound_connection")
}

// NetworkOVNSSL returns all three SSL configuration keys needed for a connection.
func (c *Config) NetworkOVNSSL() (string, string, string) {
return c.m.GetString("network.ovn.ca_cert"), c.m.GetString("network.ovn.client_cert"), c.m.GetString("network.ovn.client_key")
}

// ShutdownTimeout returns the number of minutes to wait for running operation to complete
// before the server shuts down.
func (c *Config) ShutdownTimeout() time.Duration {
Expand Down Expand Up @@ -690,6 +695,33 @@ var ConfigSchema = config.Schema{
// defaultdesc: `unix:/var/run/ovn/ovnnb_db.sock`
// shortdesc: OVN northbound database connection string
"network.ovn.northbound_connection": {Default: "unix:/var/run/ovn/ovnnb_db.sock"},

// gendoc:generate(entity=server, group=miscellaneous, key=network.ovn.ca_cert)
//
// ---
// type: string
// scope: global
// defaultdesc: Content of `/etc/ovn/ovn-central.crt` if present
// shortdesc: OVN SSL certificate authority
"network.ovn.ca_cert": {Default: ""},

// gendoc:generate(entity=server, group=miscellaneous, key=network.ovn.client_cert)
//
// ---
// type: string
// scope: global
// defaultdesc: Content of `/etc/ovn/cert_host` if present
// shortdesc: OVN SSL client certificate
"network.ovn.client_cert": {Default: ""},

// gendoc:generate(entity=server, group=miscellaneous, key=network.ovn.client_key)
//
// ---
// type: string
// scope: global
// defaultdesc: Content of `/etc/ovn/key_host` if present
// shortdesc: OVN SSL client key
"network.ovn.client_key": {Default: ""},
}

func expiryValidator(value string) error {
Expand Down
27 changes: 27 additions & 0 deletions internal/server/metadata/configuration.json
Original file line number Diff line number Diff line change
Expand Up @@ -1801,6 +1801,33 @@
"type": "string"
}
},
{
"network.ovn.ca_cert": {
"defaultdesc": "Content of `/etc/ovn/ovn-central.crt` if present",
"longdesc": "",
"scope": "global",
"shortdesc": "OVN SSL certificate authority",
"type": "string"
}
},
{
"network.ovn.client_cert": {
"defaultdesc": "Content of `/etc/ovn/cert_host` if present",
"longdesc": "",
"scope": "global",
"shortdesc": "OVN SSL client certificate",
"type": "string"
}
},
{
"network.ovn.client_key": {
"defaultdesc": "Content of `/etc/ovn/key_host` if present",
"longdesc": "",
"scope": "global",
"shortdesc": "OVN SSL client key",
"type": "string"
}
},
{
"network.ovn.integration_bridge": {
"defaultdesc": "`br-int`",
Expand Down
102 changes: 94 additions & 8 deletions internal/server/network/openvswitch/ovn.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package openvswitch

import (
"context"
"fmt"
"net"
"os"
"strconv"
"strings"
"time"

"github.com/lxc/incus/internal/iprange"
"github.com/lxc/incus/internal/linux"
"github.com/lxc/incus/internal/server/state"
"github.com/lxc/incus/shared/subprocess"
"github.com/lxc/incus/shared/util"
Expand Down Expand Up @@ -179,16 +182,66 @@ type OVNRouterPeering struct {

// NewOVN initialises new OVN client wrapper with the connection set in network.ovn.northbound_connection config.
func NewOVN(s *state.State) (*OVN, error) {
// Get database connection strings.
nbConnection := s.GlobalConfig.NetworkOVNNorthboundConnection()

sbConnection, err := NewOVS().OVNSouthboundDBRemoteAddress()
if err != nil {
return nil, fmt.Errorf("Failed to get OVN southbound connection string: %w", err)
}

client := &OVN{}
client.SetNorthboundDBAddress(nbConnection)
client.SetSouthboundDBAddress(sbConnection)
// Create the OVN struct.
client := &OVN{
nbDBAddr: nbConnection,
sbDBAddr: sbConnection,
}

// If using SSL, then get the CA and client key pair.
if strings.Contains(nbConnection, "ssl:") {
sslCACert, sslClientCert, sslClientKey := s.GlobalConfig.NetworkOVNSSL()

if sslCACert == "" {
content, err := os.ReadFile("/etc/ovn/ovn-central.crt")
if err != nil {
if os.IsNotExist(err) {
return nil, fmt.Errorf("OVN configured to use SSL but no SSL CA certificate defined")
}

return nil, err
}

sslCACert = string(content)
}

if sslClientCert == "" {
content, err := os.ReadFile("/etc/ovn/cert_host")
if err != nil {
if os.IsNotExist(err) {
return nil, fmt.Errorf("OVN configured to use SSL but no SSL client certificate defined")
}

return nil, err
}

sslClientCert = string(content)
}

if sslClientKey == "" {
content, err := os.ReadFile("/etc/ovn/key_host")
if err != nil {
if os.IsNotExist(err) {
return nil, fmt.Errorf("OVN configured to use SSL but no SSL client key defined")
}

return nil, err
}

sslClientKey = string(content)
}

client.sslCACert = sslCACert
client.sslClientCert = sslClientCert
client.sslClientKey = sslClientKey
}

return client, nil
}
Expand All @@ -197,6 +250,10 @@ func NewOVN(s *state.State) (*OVN, error) {
type OVN struct {
nbDBAddr string
sbDBAddr string

sslCACert string
sslClientCert string
sslClientKey string
}

// SetNorthboundDBAddress sets the address that runs the OVN northbound databases.
Expand Down Expand Up @@ -253,16 +310,45 @@ func (o *OVN) xbctl(southbound bool, extraArgs ...string) (string, error) {
// Figure out args.
args := []string{"--timeout=10", "--db", dbAddr}

// Handle SSL args.
files := []*os.File{}
if strings.Contains(dbAddr, "ssl:") {
// Handle client certificate.
clientCertFile, err := linux.CreateMemfd([]byte(o.sslClientCert))
if err != nil {
return "", err
}

defer clientCertFile.Close()
files = append(files, clientCertFile)

// Handle client key.
clientKeyFile, err := linux.CreateMemfd([]byte(o.sslClientKey))
if err != nil {
return "", err
}

defer clientKeyFile.Close()
files = append(files, clientKeyFile)

// Handle CA certificate.
caCertFile, err := linux.CreateMemfd([]byte(o.sslCACert))
if err != nil {
return "", err
}

defer caCertFile.Close()
files = append(files, caCertFile)

args = append(args,
"-c", "/etc/ovn/cert_host",
"-p", "/etc/ovn/key_host",
"-C", "/etc/ovn/ovn-central.crt",
"-c", "/proc/self/fd/3",
"-p", "/proc/self/fd/4",
"-C", "/proc/self/fd/5",
)
}

args = append(args, extraArgs...)
return subprocess.RunCommand(cmd, args...)
return subprocess.RunCommandInheritFds(context.Background(), files, cmd, args...)
}

// LogicalRouterAdd adds a named logical router.
Expand Down
1 change: 1 addition & 0 deletions internal/version/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,7 @@ var APIExtensions = []string{
"disk_io_bus",
"storage_cephfs_create_missing",
"instance_move_config",
"ovn_ssl_config",
}

// APIExtensionsCount returns the number of available API extensions.
Expand Down

0 comments on commit e0a6bfc

Please sign in to comment.