feat(xo-server-sdn-controller): encryption for private networks (#4441)
This commit is contained in:
parent
dcf55e4385
commit
5c54611d1b
@ -9,6 +9,7 @@
|
||||
|
||||
- [VM/disks] Don't hide disks that are attached to the same VM twice [#4400](https://github.com/vatesfr/xen-orchestra/issues/4400) (PR [#4414](https://github.com/vatesfr/xen-orchestra/pull/4414))
|
||||
- [VM/console] Add a button to connect to the VM via the local SSH client (PR [#4415](https://github.com/vatesfr/xen-orchestra/pull/4415))
|
||||
- [SDN Controller] Add possibility to encrypt private networks (PR [#4441](https://github.com/vatesfr/xen-orchestra/pull/4441))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
@ -28,5 +29,6 @@
|
||||
>
|
||||
> Rule of thumb: add packages on top.
|
||||
|
||||
- xo-server-sdn-controller v0.3.0
|
||||
- xo-server v5.50.0
|
||||
- xo-web v5.50.0
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 59 KiB |
@ -18,7 +18,8 @@ In the network creation view:
|
||||
- Select a `pool`
|
||||
- Select `Private network`
|
||||
- Select an interface on which to create the network's tunnels
|
||||
- Select the encapsulation: a choice is offered between `GRE` and `VxLAN`, if `VxLAN` is chosen, then port 4789 must be open for UDP traffic on all the network's hosts (see [the requirements](#requirements))
|
||||
- Select the encapsulation: a choice is offered between `GRE` and `VxLAN`, if `VxLAN` is chosen, then port 4789 must be open for UDP traffic on all the network's hosts (see [the requirements](#vxlan))
|
||||
- Choose if the network should be encrypted or not (see [the requirements](#encryption) to use encryption)
|
||||
- Select other `pool`s to add them to the network if desired
|
||||
- For each added `pool`: select an interface on which to create the tunnels
|
||||
- Create the network
|
||||
@ -27,6 +28,7 @@ In the network creation view:
|
||||
***NB:***
|
||||
- All hosts in a private network must be able to reach the other hosts' management interface.
|
||||
> The term ‘management interface’ is used to indicate the IP-enabled NIC that carries the management traffic.
|
||||
- Only 1 encrypted GRE network and 1 encrypted VxLAN network per pool can exist at a time due to Open vSwitch limitation.
|
||||
|
||||
### Configuration
|
||||
|
||||
@ -40,9 +42,19 @@ If none is provided, the plugin will create its own self-signed certificates.
|
||||
|
||||
## Requirements
|
||||
|
||||
> All requirements are met by running up to date XCP-ng hosts.
|
||||
>
|
||||
> On older XCP-ng hosts, or hosts running Citrix Hypervisor, changes might have to be done manually.
|
||||
### VxLAN
|
||||
|
||||
To be able to use `VxLAN`, the following line needs to be added, if not already present, in `/etc/sysconfig/iptables` of all the hosts where `VxLAN` is wanted:
|
||||
- `-A xapi-INPUT -p udp -m conntrack --ctstate NEW -m udp --dport 4789 -j ACCEPT`
|
||||
- On XCP-ng prior to 7.6:
|
||||
- To be able to use `VxLAN`, the following line needs to be added, if not already present, in `/etc/sysconfig/iptables` of all the hosts where `VxLAN` is wanted: `-A xapi-INPUT -p udp -m conntrack --ctstate NEW -m udp --dport 4789 -j ACCEPT`
|
||||
|
||||
### Encryption
|
||||
|
||||
> Encryption is not available prior to 8.0.
|
||||
|
||||
- On XCP-ng 8.0:
|
||||
- To be able to encrypt the networks, `openvswitch-ipsec` package must be installed on all the hosts:
|
||||
- `yum install openvswitch-ipsec --enablerepo=xcp-ng-testing`
|
||||
- `systemctl enable ipsec`
|
||||
- `systemctl enable openvswitch-ipsec`
|
||||
- `systemctl start ipsec`
|
||||
- `systemctl start openvswitch-ipsec`
|
@ -4,7 +4,7 @@ import NodeOpenssl from 'node-openssl-cert'
|
||||
import uuidv4 from 'uuid/v4'
|
||||
import { access, constants, readFile, writeFile } from 'fs'
|
||||
import { EventEmitter } from 'events'
|
||||
import { filter, find, forOwn, map, omitBy } from 'lodash'
|
||||
import { filter, find, forOwn, map, omitBy, sample } from 'lodash'
|
||||
import { fromCallback, fromEvent } from 'promise-toolbox'
|
||||
import { join } from 'path'
|
||||
|
||||
@ -103,6 +103,14 @@ function updateNetworkOtherConfig(network) {
|
||||
)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
function createPassword() {
|
||||
const chars =
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789?!'
|
||||
return Array.from({ length: 16 }, _ => sample(chars)).join('')
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
class SDNController extends EventEmitter {
|
||||
@ -110,6 +118,7 @@ class SDNController extends EventEmitter {
|
||||
Attributes on created networks:
|
||||
- `other_config`:
|
||||
- `xo:sdn-controller:encapsulation` : encapsulation protocol used for tunneling (either `gre` or `vxlan`)
|
||||
- `xo:sdn-controller:encrypted` : `true` if the network is encrypted
|
||||
- `xo:sdn-controller:pif-device` : PIF device on which the tunnels are created, must be physical and have an IP configuration
|
||||
- `xo:sdn-controller:private-pool-wide`: `true` if the network is created (and so must be managed) by a SDN Controller
|
||||
- `xo:sdn-controller:vni` : VxLAN Network Identifier,
|
||||
@ -207,6 +216,7 @@ class SDNController extends EventEmitter {
|
||||
networkDescription: { type: 'string' },
|
||||
encapsulation: { type: 'string' },
|
||||
pifId: { type: 'string' },
|
||||
encrypted: { type: 'boolean', optional: true },
|
||||
}
|
||||
createPrivateNetwork.resolve = {
|
||||
xoPool: ['poolId', 'pool', ''],
|
||||
@ -235,6 +245,7 @@ class SDNController extends EventEmitter {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
encrypted: { type: 'boolean', optional: true },
|
||||
}
|
||||
|
||||
this._unsetApiMethods = this._xo.addApiMethods({
|
||||
@ -394,6 +405,7 @@ class SDNController extends EventEmitter {
|
||||
encapsulation,
|
||||
xoPif,
|
||||
vni,
|
||||
encrypted = false,
|
||||
}) {
|
||||
const pool = this._xo.getXapiObject(xoPool)
|
||||
await this._setPoolControllerIfNeeded(pool)
|
||||
@ -410,6 +422,7 @@ class SDNController extends EventEmitter {
|
||||
// See: https://citrix.github.io/xenserver-sdk/#network
|
||||
automatic: 'false',
|
||||
'xo:sdn-controller:encapsulation': encapsulation,
|
||||
'xo:sdn-controller:encrypted': encrypted ? 'true' : undefined,
|
||||
'xo:sdn-controller:pif-device': pif.device,
|
||||
'xo:sdn-controller:private-pool-wide': 'true',
|
||||
'xo:sdn-controller:vni': String(vni),
|
||||
@ -453,6 +466,7 @@ class SDNController extends EventEmitter {
|
||||
networkDescription,
|
||||
encapsulation,
|
||||
xoPifIds,
|
||||
encrypted = false,
|
||||
}) {
|
||||
const uuid = uuidv4()
|
||||
const crossPoolNetwork = {
|
||||
@ -481,6 +495,7 @@ class SDNController extends EventEmitter {
|
||||
encapsulation,
|
||||
xoPif,
|
||||
vni,
|
||||
encrypted,
|
||||
})
|
||||
|
||||
const network = pool.$xapi.getObjectByRef(poolNetwork.network)
|
||||
@ -1134,6 +1149,11 @@ class SDNController extends EventEmitter {
|
||||
const encapsulation =
|
||||
otherConfig['xo:sdn-controller:encapsulation'] ?? 'gre'
|
||||
const vni = otherConfig['xo:sdn-controller:vni'] ?? '0'
|
||||
const password =
|
||||
otherConfig['xo:sdn-controller:encrypted'] === 'true'
|
||||
? createPassword()
|
||||
: undefined
|
||||
|
||||
try {
|
||||
await Promise.all([
|
||||
client.addInterfaceAndPort(
|
||||
@ -1142,6 +1162,7 @@ class SDNController extends EventEmitter {
|
||||
centerClient.host.address,
|
||||
encapsulation,
|
||||
vni,
|
||||
password,
|
||||
centerNetwork.uuid
|
||||
),
|
||||
centerClient.addInterfaceAndPort(
|
||||
@ -1150,6 +1171,7 @@ class SDNController extends EventEmitter {
|
||||
client.host.address,
|
||||
encapsulation,
|
||||
vni,
|
||||
password,
|
||||
network.uuid
|
||||
),
|
||||
])
|
||||
@ -1227,6 +1249,12 @@ class SDNController extends EventEmitter {
|
||||
const encapsulation =
|
||||
otherConfig['xo:sdn-controller:encapsulation'] ?? 'gre'
|
||||
const vni = otherConfig['xo:sdn-controller:vni'] ?? '0'
|
||||
|
||||
const password =
|
||||
otherConfig['xo:sdn-controller:encrypted'] === 'true'
|
||||
? createPassword()
|
||||
: undefined
|
||||
|
||||
let bridgeName
|
||||
try {
|
||||
;[bridgeName] = await Promise.all([
|
||||
@ -1235,14 +1263,16 @@ class SDNController extends EventEmitter {
|
||||
network.name_label,
|
||||
starCenterClient.host.address,
|
||||
encapsulation,
|
||||
vni
|
||||
vni,
|
||||
password
|
||||
),
|
||||
starCenterClient.addInterfaceAndPort(
|
||||
network.uuid,
|
||||
network.name_label,
|
||||
hostClient.host.address,
|
||||
encapsulation,
|
||||
vni
|
||||
vni,
|
||||
password
|
||||
),
|
||||
])
|
||||
} catch (error) {
|
||||
|
@ -18,6 +18,7 @@ export class OvsdbClient {
|
||||
See:
|
||||
- OVSDB Protocol: https://tools.ietf.org/html/rfc7047
|
||||
- OVS Tunneling : http://docs.openvswitch.org/en/latest/howto/tunneling/
|
||||
- OVS IPSEC : http://docs.openvswitch.org/en/latest/howto/ipsec/
|
||||
|
||||
Attributes on created OVS ports (corresponds to a XAPI `PIF` or `VIF`):
|
||||
- `other_config`:
|
||||
@ -65,6 +66,7 @@ export class OvsdbClient {
|
||||
remoteAddress,
|
||||
encapsulation,
|
||||
key,
|
||||
password,
|
||||
remoteNetwork
|
||||
) {
|
||||
if (
|
||||
@ -111,6 +113,10 @@ export class OvsdbClient {
|
||||
|
||||
// Add interface and port to the bridge
|
||||
const options = ['map', [['remote_ip', remoteAddress], ['key', key]]]
|
||||
if (password !== undefined) {
|
||||
options[1].push(['psk', password])
|
||||
}
|
||||
|
||||
const otherConfig =
|
||||
remoteNetwork !== undefined
|
||||
? ['map', [['xo:sdn-controller:cross-pool', remoteNetwork]]]
|
||||
|
@ -1755,6 +1755,9 @@ const messages = {
|
||||
newNetworkInfo: 'Info',
|
||||
newNetworkType: 'Type',
|
||||
newNetworkEncapsulation: 'Encapsulation',
|
||||
newNetworkEncrypted: 'Encrypted',
|
||||
encryptionWarning:
|
||||
'A pool can have 1 encrypted GRE network and 1 encrypted VxLAN network max',
|
||||
newNetworkSdnControllerTip:
|
||||
'Private networks work on up-to-date XCP-ng hosts, for other scenarios please see the requirements',
|
||||
deleteNetwork: 'Delete network',
|
||||
|
@ -36,6 +36,7 @@ const EMPTY = {
|
||||
bondMode: undefined,
|
||||
description: '',
|
||||
encapsulation: 'gre',
|
||||
encrypted: false,
|
||||
isPrivate: false,
|
||||
mtu: '',
|
||||
name: '',
|
||||
@ -127,6 +128,9 @@ const NewNetwork = decorate([
|
||||
bonded: isPrivate ? bonded : false,
|
||||
}
|
||||
},
|
||||
toggleEncrypted() {
|
||||
return { encrypted: !this.state.encrypted }
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
disableAddPool: ({ networks }, { nPools }) =>
|
||||
@ -181,6 +185,7 @@ const NewNetwork = decorate([
|
||||
isPrivate,
|
||||
description,
|
||||
encapsulation,
|
||||
encrypted,
|
||||
mtu,
|
||||
name,
|
||||
networks,
|
||||
@ -212,6 +217,7 @@ const NewNetwork = decorate([
|
||||
networkDescription: description,
|
||||
encapsulation: encapsulation,
|
||||
xoPifIds: pifIds,
|
||||
encrypted,
|
||||
})
|
||||
})()
|
||||
: createPrivateNetwork({
|
||||
@ -220,6 +226,7 @@ const NewNetwork = decorate([
|
||||
networkDescription: description,
|
||||
encapsulation: encapsulation,
|
||||
pifId: pif.id,
|
||||
encrypted,
|
||||
})
|
||||
: createNetwork({
|
||||
description,
|
||||
@ -268,6 +275,7 @@ const NewNetwork = decorate([
|
||||
isPrivate,
|
||||
description,
|
||||
encapsulation,
|
||||
encrypted,
|
||||
modeOptions,
|
||||
mtu,
|
||||
name,
|
||||
@ -347,6 +355,16 @@ const NewNetwork = decorate([
|
||||
]}
|
||||
value={encapsulation}
|
||||
/>
|
||||
<Toggle
|
||||
onChange={effects.toggleEncrypted}
|
||||
value={encrypted}
|
||||
/>{' '}
|
||||
<label>{_('newNetworkEncrypted')}</label>
|
||||
<div>
|
||||
<em>
|
||||
<Icon icon='info' /> {_('encryptionWarning')}
|
||||
</em>
|
||||
</div>
|
||||
<div className='mt-1'>
|
||||
{state.networks.map(({ pool, pif }, key) => (
|
||||
<div key={key}>
|
||||
|
Loading…
Reference in New Issue
Block a user