Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c0bf7c293 | ||
|
|
2e03568225 | ||
|
|
7dabef4851 | ||
|
|
29c3841eaa | ||
|
|
45204da444 | ||
|
|
c18ddbf255 | ||
|
|
76410df0e8 | ||
|
|
6a0f3a38d0 | ||
|
|
265e99ccd0 | ||
|
|
aa71a1968f | ||
|
|
5382f1c7bb | ||
|
|
a5dbf74123 | ||
|
|
784fd7d233 | ||
|
|
13b7c0e73d | ||
|
|
d747ec8c41 | ||
|
|
cabe5d1543 | ||
|
|
38da624930 | ||
|
|
e0ff8a014f | ||
|
|
8721f21874 | ||
|
|
9eb858fb5f | ||
|
|
9f084f48f6 | ||
|
|
c998325448 | ||
|
|
a243ff6459 | ||
|
|
47b3294666 | ||
|
|
3afed2473e | ||
|
|
fdeacdcf30 | ||
|
|
d7ddd7752c | ||
|
|
2b2c7d5e39 | ||
|
|
e26478c2e7 | ||
|
|
c7b9d30fd3 | ||
|
|
5803655840 | ||
|
|
61ce26ba29 | ||
|
|
32e651ec96 | ||
|
|
dbed0e94cb | ||
|
|
8b39a268b9 | ||
|
|
d40ba7c5ce | ||
|
|
985257f2d7 | ||
|
|
a4f3383f21 | ||
|
|
b2d828ec84 | ||
|
|
b2a74de715 | ||
|
|
430761ad2e | ||
|
|
38a1f6937d | ||
|
|
8b46914999 | ||
|
|
1b81e60187 | ||
|
|
526c597cd0 | ||
|
|
7a97783ead | ||
|
|
b4425d8e65 | ||
|
|
1afda35bd3 | ||
|
|
0953f5ebdd | ||
|
|
1b03615e50 | ||
|
|
8f208c53df | ||
|
|
c0d2dabe66 | ||
|
|
6da4aaf176 | ||
|
|
5b6a6fce9a | ||
|
|
79fb2def57 | ||
|
|
7f5c24144b | ||
|
|
3cf876fafa | ||
|
|
771aa83282 | ||
|
|
03404ac95d | ||
|
|
decf314238 | ||
|
|
ab1849ff07 | ||
|
|
33b18c7229 | ||
|
|
4a2efb29ec | ||
|
|
00a432193a | ||
|
|
d83eb599a0 | ||
|
|
c1c9b1975d | ||
|
|
ba0d9ad9f2 | ||
|
|
4feabc4ab6 | ||
|
|
1a14301c50 | ||
|
|
5dc52a7c06 | ||
|
|
4d45b27696 | ||
|
|
5995c4a00f | ||
|
|
906388adaa | ||
|
|
5dbc679e16 | ||
|
|
d48864a6be | ||
|
|
462482d319 | ||
|
|
5ef9d78461 | ||
|
|
1b20010a69 | ||
|
|
d750d00c6f | ||
|
|
66cb564e2c | ||
|
|
f782decbd6 | ||
|
|
717582c4ba | ||
|
|
cd62c0755f | ||
|
|
c6f5ab7d79 | ||
|
|
09607ddcbe |
@@ -234,6 +234,60 @@ jobs:
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_etcd_fail_https:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 10
|
||||
run: ETCD_SCHEME=https /root/vitastor/tests/test_etcd_fail.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_etcd_fail_https_antietcd:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 10
|
||||
run: ETCD_SCHEME=https ANTIETCD=1 /root/vitastor/tests/test_etcd_fail.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_snapshot_https:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: ETCD_SCHEME=https /root/vitastor/tests/test_snapshot.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_interrupted_rebalance:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
@@ -720,6 +774,42 @@ jobs:
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_snapshot_chain_encrypted:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: ENCRYPTED=1 /root/vitastor/tests/test_snapshot_chain.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_snapshot_chain_enc_gcm:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: TEST_NAME=enc_gcm ENCRYPTED=1 VITASTOR_CFG=',"proto_checksums":"gcm"' /root/vitastor/tests/test_snapshot_chain.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_old_snapshot_chain:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
@@ -1278,6 +1368,24 @@ jobs:
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_heal_ec_rdma:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 10
|
||||
run: TEST_NAME=ec_rdma RDMA=1 SCHEME=ec /root/vitastor/tests/test_heal.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_checksum:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
@@ -1296,6 +1404,24 @@ jobs:
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_checksum_xxhash:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: TEST_NAME=xxhash OSD_ARGS="--data_csum_type xxh3_32" /root/vitastor/tests/test_checksum.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_old_checksum:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
@@ -2160,3 +2286,39 @@ jobs:
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_write_encrypted:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: /root/vitastor/tests/test_write_encrypted.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_write_encrypted_ec:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: SCHEME=ec /root/vitastor/tests/test_write_encrypted.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
|
||||
@@ -38,6 +38,14 @@ for my $line (<>)
|
||||
{
|
||||
$test_name .= '_antietcd';
|
||||
}
|
||||
elsif ($1 eq 'ETCD_SCHEME' && $2 eq 'https')
|
||||
{
|
||||
$test_name .= '_https';
|
||||
}
|
||||
elsif ($1 eq 'ENCRYPTED')
|
||||
{
|
||||
$test_name .= '_encrypted';
|
||||
}
|
||||
elsif ($1 eq 'OLD')
|
||||
{
|
||||
$test_name =~ s/^test_/test_old_/s;
|
||||
|
||||
@@ -3,3 +3,4 @@
|
||||
package-lock.json
|
||||
fio
|
||||
qemu
|
||||
node_modules
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 2.8...3.30)
|
||||
|
||||
project(vitastor)
|
||||
|
||||
set(VITASTOR_VERSION "3.0.14")
|
||||
set(VITASTOR_VERSION "3.0.15")
|
||||
|
||||
include(CTest)
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ Vitastor поддерживает QEMU-драйвер, протоколы UBLK,
|
||||
- [Дисковые параметры OSD](docs/config/layout-osd.ru.md)
|
||||
- [Прочие параметры OSD](docs/config/osd.ru.md)
|
||||
- [Параметры мониторов](docs/config/monitor.ru.md)
|
||||
- [Безопасность](docs/config/security.ru.md)
|
||||
- [Настройки пулов](docs/config/pool.ru.md)
|
||||
- [Метаданные образов в etcd](docs/config/inode.ru.md)
|
||||
- Использование
|
||||
|
||||
@@ -62,6 +62,7 @@ Read more details in the documentation. You can start from here: [Quick Start](d
|
||||
- [OSD Disk Layout](docs/config/layout-osd.en.md)
|
||||
- [OSD Runtime Parameters](docs/config/osd.en.md)
|
||||
- [Monitor](docs/config/monitor.en.md)
|
||||
- [Security](docs/config/security.en.md)
|
||||
- [Pool configuration](docs/config/pool.en.md)
|
||||
- [Image metadata in etcd](docs/config/inode.en.md)
|
||||
- Usage
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
VITASTOR_VERSION ?= v3.0.14
|
||||
VITASTOR_VERSION ?= v3.0.15
|
||||
|
||||
all: build push
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ spec:
|
||||
capabilities:
|
||||
add: ["SYS_ADMIN"]
|
||||
allowPrivilegeEscalation: true
|
||||
image: vitalif/vitastor-csi:v3.0.14
|
||||
image: vitalif/vitastor-csi:v3.0.15
|
||||
args:
|
||||
- "--node=$(NODE_ID)"
|
||||
- "--endpoint=$(CSI_ENDPOINT)"
|
||||
|
||||
@@ -121,7 +121,7 @@ spec:
|
||||
privileged: true
|
||||
capabilities:
|
||||
add: ["SYS_ADMIN"]
|
||||
image: vitalif/vitastor-csi:v3.0.14
|
||||
image: vitalif/vitastor-csi:v3.0.15
|
||||
args:
|
||||
- "--node=$(NODE_ID)"
|
||||
- "--endpoint=$(CSI_ENDPOINT)"
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ package vitastor
|
||||
|
||||
const (
|
||||
vitastorCSIDriverName = "csi.vitastor.io"
|
||||
vitastorCSIDriverVersion = "3.0.14"
|
||||
vitastorCSIDriverVersion = "3.0.15"
|
||||
)
|
||||
|
||||
// Config struct fills the parameters of request or user input
|
||||
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
vitastor (3.0.14-1) unstable; urgency=medium
|
||||
vitastor (3.0.15-1) unstable; urgency=medium
|
||||
|
||||
* Bugfixes
|
||||
|
||||
|
||||
Vendored
+1
-1
@@ -3,7 +3,7 @@ Section: admin
|
||||
Priority: optional
|
||||
Maintainer: Vitaliy Filippov <vitalif@yourcmc.ru>
|
||||
Build-Depends: debhelper, g++ (>= 8), libstdc++6 (>= 8),
|
||||
linux-libc-dev, libgoogle-perftools-dev, libjerasure-dev, libgf-complete-dev,
|
||||
linux-libc-dev, libgoogle-perftools-dev, libjerasure-dev, libgf-complete-dev, libc-ares-dev,
|
||||
libibverbs-dev, librdmacm-dev, libisal-dev, cmake, pkg-config, libnl-3-dev, libnl-genl-3-dev,
|
||||
node-bindings <!nocheck>, node-gyp, node-nan
|
||||
Standards-Version: 4.5.0
|
||||
|
||||
Vendored
+1
@@ -11,6 +11,7 @@ override_dh_install:
|
||||
cp -v node-binding/package.json node-binding/index.js node-binding/addon.cc node-binding/addon.h node-binding/client.cc node-binding/client.h debian/tmp/usr/lib/x86_64-linux-gnu/nodejs/vitastor
|
||||
cp -v node-binding/build/Release/addon.node debian/tmp/usr/lib/x86_64-linux-gnu/nodejs/vitastor/build/Release
|
||||
dh_install
|
||||
cd debian/vitastor-mon/usr/lib/vitastor/mon && npm install --production
|
||||
|
||||
override_dh_installdeb:
|
||||
cat debian/fio_version >> debian/vitastor-fio.substvars
|
||||
|
||||
Vendored
-6
@@ -37,12 +37,6 @@ rm -rf a b
|
||||
|
||||
echo "dep:fio=$FIO" > debian/fio_version
|
||||
|
||||
cd /root/vitastor/packages/vitastor-$REL/vitastor-$VER
|
||||
mkdir mon/node_modules
|
||||
cd mon/node_modules
|
||||
curl -s https://git.yourcmc.ru/vitalif/antietcd/archive/master.tar.gz | tar -zx
|
||||
curl -s https://git.yourcmc.ru/vitalif/tinyraft/archive/master.tar.gz | tar -zx
|
||||
|
||||
cd /root/vitastor/packages/vitastor-$REL
|
||||
if [[ ( "$REL" = "trixie" || "$REL" = "resolute" ) && -e ../vitastor-bookworm/vitastor_$VER.orig.tar.xz ]]; then
|
||||
# Fucking shit, archives differ between bookworm (xz 5.4.1) and trixie (xz 5.8.1)
|
||||
|
||||
Vendored
+6
-8
@@ -12,20 +12,18 @@ ARG REL=
|
||||
WORKDIR /root
|
||||
|
||||
RUN set -e -x; \
|
||||
if [ "$REL" = "buster" ]; then \
|
||||
perl -i -pe 's/deb.debian.org/archive.debian.org/' /etc/apt/sources.list; \
|
||||
apt-get update; \
|
||||
apt-get -y install wget; \
|
||||
wget https://vitastor.io/debian/pubkey.gpg -O /etc/apt/trusted.gpg.d/vitastor.gpg; \
|
||||
echo "deb https://vitastor.io/debian $REL main" >> /etc/apt/sources.list; \
|
||||
fi; \
|
||||
perl -i -pe 's/deb.debian.org/archive.debian.org/' /etc/apt/sources.list; \
|
||||
apt-get update; \
|
||||
apt-get -y install wget; \
|
||||
wget https://vitastor.io/debian/pubkey.gpg -O /etc/apt/trusted.gpg.d/vitastor.gpg; \
|
||||
echo "deb https://vitastor.io/debian $REL main" >> /etc/apt/sources.list; \
|
||||
grep '^deb ' /etc/apt/sources.list | perl -pe 's/^deb/deb-src/' >> /etc/apt/sources.list; \
|
||||
perl -i -pe 's/Types: deb$/Types: deb deb-src/' /etc/apt/sources.list.d/*.sources || true; \
|
||||
echo 'APT::Install-Recommends false;' >> /etc/apt/apt.conf; \
|
||||
echo 'APT::Install-Suggests false;' >> /etc/apt/apt.conf
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get -y install fio libgoogle-perftools-dev devscripts libjerasure-dev cmake \
|
||||
apt-get -y install fio libgoogle-perftools-dev devscripts libjerasure-dev cmake libc-ares-dev libisal-crypto-dev \
|
||||
libibverbs-dev librdmacm-dev libisal-dev libnl-3-dev libnl-genl-3-dev curl nodejs npm node-nan node-bindings && \
|
||||
apt-get -y build-dep fio && \
|
||||
apt-get --download-only source fio
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
VITASTOR_VERSION ?= v3.0.14
|
||||
VITASTOR_VERSION ?= v3.0.15
|
||||
|
||||
all: build push
|
||||
|
||||
|
||||
@@ -12,12 +12,7 @@ EnvironmentFile=/etc/vitastor/etcd.conf
|
||||
SyslogIdentifier=etcd
|
||||
ExecStart=bash -c 'docker run --rm -i -v /var/lib/vitastor/etcd:/data \
|
||||
--log-driver none --network host $CONTAINER_OPTIONS --name vitastor-etcd \
|
||||
$ETCD_IMAGE /usr/local/bin/etcd --name "$ETCD_NAME" --data-dir /data \
|
||||
--snapshot-count 10000 --advertise-client-urls http://$ETCD_IP:2379 --listen-client-urls http://$ETCD_IP:2379 \
|
||||
--initial-advertise-peer-urls http://$ETCD_IP:2380 --listen-peer-urls http://$ETCD_IP:2380 \
|
||||
--initial-cluster-token vitastor-etcd-1 --initial-cluster "$ETCD_INITIAL_CLUSTER" \
|
||||
--initial-cluster-state new --max-txn-ops=100000 --max-request-bytes=104857600 \
|
||||
--auto-compaction-retention=10 --auto-compaction-mode=revision'
|
||||
$ETCD_IMAGE /usr/local/bin/etcd --data-dir /data
|
||||
ExecStop=docker stop vitastor-etcd
|
||||
Restart=always
|
||||
StartLimitInterval=0
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#
|
||||
|
||||
# Desired Vitastor version
|
||||
VITASTOR_VERSION=v3.0.14
|
||||
VITASTOR_VERSION=v3.0.15
|
||||
|
||||
# Additional arguments for all containers
|
||||
# For example, you may want to specify a custom logging driver here
|
||||
|
||||
@@ -1,4 +1 @@
|
||||
ETCD_IMAGE=quay.io/coreos/etcd:v3.5.18
|
||||
ETCD_NAME=""
|
||||
ETCD_IP=""
|
||||
ETCD_INITIAL_CLUSTER=""
|
||||
|
||||
@@ -38,3 +38,4 @@ In the future, additional configuration methods may be added:
|
||||
- [OSD Disk Layout](config/layout-osd.en.md)
|
||||
- [OSD Runtime Parameters](config/osd.en.md)
|
||||
- [Monitor](config/monitor.en.md)
|
||||
- [Security Parameters](config/security.en.md)
|
||||
|
||||
@@ -41,3 +41,4 @@
|
||||
- [Дисковые параметры OSD](config/layout-osd.ru.md)
|
||||
- [Прочие параметры OSD](config/osd.ru.md)
|
||||
- [Параметры мониторов](config/monitor.ru.md)
|
||||
- [Параметры безопасности](config/security.ru.md)
|
||||
|
||||
@@ -198,8 +198,14 @@ put a modified value into etcd key /vitastor/config/global.
|
||||
- Type: string
|
||||
- Default: none
|
||||
|
||||
Data checksum type to use. May be "crc32c" or "none". Set to "crc32c" to
|
||||
enable data checksums.
|
||||
Data and metadata checksum type to use. May be "crc32c", "xxh3_32" or "none".
|
||||
Select crc32c or xxh3_32 and set csum_block_size to enable data checksums.
|
||||
|
||||
Both crc32c and xxh3_32 are almost equally fast, xxh3_32 is safer. xxh3_32 is
|
||||
the xxhash3 algorithm truncated from 64 to 32 bits (which is still a good hash).
|
||||
|
||||
Note that enabled data checksums either increase memory usage or reduce
|
||||
performance. Check details in [csum_block_size](#csum_block_size) description.
|
||||
|
||||
## csum_block_size
|
||||
|
||||
|
||||
@@ -209,8 +209,12 @@ journal_block_size и meta_block_size. Однако на данный момен
|
||||
- Тип: строка
|
||||
- Значение по умолчанию: none
|
||||
|
||||
Тип используемых OSD контрольных сумм данных. Может быть "crc32c" или "none".
|
||||
Установите в "crc32c", чтобы включить расчёт и проверку контрольных сумм данных.
|
||||
Тип используемых OSD контрольных сумм данных и метаданных. Может быть "crc32c",
|
||||
"xxh3_32" или "none". Выберите crc32c или xxh3_32 и установите csum_block_size,
|
||||
чтобы включить контрольные суммы данных.
|
||||
|
||||
И crc32c, и xxh3_32 примерно одинаково быстры, xxh3_32 надёжней. xxh3_32 - это
|
||||
алгоритм xxhash3, обрезанный с 64 до 32 бит (это всё равно хороший хеш).
|
||||
|
||||
Следует понимать, что контрольные суммы в зависимости от размера блока их
|
||||
расчёта либо увеличивают потребление памяти, либо снижают производительность.
|
||||
|
||||
@@ -50,6 +50,9 @@ or antietcd_data_dir options). All other antietcd parameters
|
||||
cluster, cluster_key, persist_filter, stale_read can also be set in
|
||||
Vitastor configuration with `antietcd_` prefix.
|
||||
|
||||
See also: [antietcd_cert](security.en.md#antietcd_cert),
|
||||
[antietcd_key](security.en.md#antietcd_key) and [etcd_proxy](security.en.md#etcd_proxyurls).
|
||||
|
||||
You can dump/load data to or from antietcd using Antietcd `anticli` tool:
|
||||
|
||||
```
|
||||
|
||||
@@ -50,6 +50,9 @@ antietcd_data_file или antietcd_data_dir). Все остальные пара
|
||||
node_id, cluster, cluster_key, persist_filter, stale_read также можно задавать
|
||||
в конфигурации Vitastor с префиксом `antietcd_`.
|
||||
|
||||
Смотрите также настройки [antietcd_cert](security.ru.md#antietcd_cert),
|
||||
[antietcd_key](security.ru.md#antietcd_key) и [etcd_proxy](security.ru.md#etcd_proxyurls).
|
||||
|
||||
Вы можете выгружать/загружать данные в или из antietcd с помощью его инструмента
|
||||
`anticli`:
|
||||
|
||||
|
||||
@@ -0,0 +1,306 @@
|
||||
[Documentation](../../README.md#documentation) → [Configuration](../config.en.md) → Security Parameters
|
||||
|
||||
-----
|
||||
|
||||
[Читать на русском](security.ru.md)
|
||||
|
||||
# Security Parameters
|
||||
|
||||
These parameters affect your Vitastor installation security and apply to OSDs, monitors and clients.
|
||||
|
||||
Most of them can be set in /etc/vitastor/vitastor.conf and in etcd, but don't support online modification.
|
||||
|
||||
All certificate and private key parameters mentioned may contain a path to a PEM file or just
|
||||
a PEM string with certificate or a private key. In the latter case, the string must begin with
|
||||
"-----BEGIN CERTIFICATE-----" or "-----BEGIN PRIVATE KEY-----".
|
||||
|
||||
- [use_perms](#use_perms)
|
||||
- [cert](#cert)
|
||||
- [pkey](#pkey)
|
||||
- [etcd_ca](#etcd_ca)
|
||||
- [client_ca](#client_ca)
|
||||
- [osd_ca](#osd_ca)
|
||||
- [mon_ca](#mon_ca)
|
||||
- [antietcd_cert](#antietcd_cert)
|
||||
- [antietcd_key](#antietcd_key)
|
||||
- [etcd_proxy.urls](#etcd_proxyurls)
|
||||
- [etcd_proxy.cert](#etcd_proxycert)
|
||||
- [etcd_proxy.key](#etcd_proxykey)
|
||||
- [etcd_proxy.ca](#etcd_proxyca)
|
||||
- [osd_cert](#osd_cert)
|
||||
- [osd_pkey](#osd_pkey)
|
||||
- [api_cert](#api_cert)
|
||||
- [api_pkey](#api_pkey)
|
||||
- [etcd_client_cert](#etcd_client_cert)
|
||||
- [etcd_client_key](#etcd_client_key)
|
||||
- [osd_etcd_client_cert](#osd_etcd_client_cert)
|
||||
- [osd_etcd_client_key](#osd_etcd_client_key)
|
||||
- [mon_etcd_client_cert](#mon_etcd_client_cert)
|
||||
- [mon_etcd_client_key](#mon_etcd_client_key)
|
||||
- [proto_checksums](#proto_checksums)
|
||||
- [force_proto_checksums](#force_proto_checksums)
|
||||
- [max_cipher_pool_size](#max_cipher_pool_size)
|
||||
- [vault_url](#vault_url)
|
||||
- [vault_secret_api_path](#vault_secret_api_path)
|
||||
- [vault_client_cert](#vault_client_cert)
|
||||
- [vault_client_key](#vault_client_key)
|
||||
- [vault_ca](#vault_ca)
|
||||
- [vault_timeout_ms](#vault_timeout_ms)
|
||||
- [vault_error_timeout_sec](#vault_error_timeout_sec)
|
||||
- [vault_refresh_leeway_sec](#vault_refresh_leeway_sec)
|
||||
|
||||
## use_perms
|
||||
|
||||
- Type: boolean
|
||||
- Default: false
|
||||
|
||||
Enable client permissions in a Vitastor cluster, including Antietcd built into the Monitor.
|
||||
Requires configured encryption. Also note that separate Antietcd requires separate configuration
|
||||
to use permissions (see [security documentation](../intro/security.en.md) for details).
|
||||
|
||||
## cert
|
||||
|
||||
- Type: string
|
||||
|
||||
Client certificate of the current Vitastor user. Required for Vitastor protocol encryption.
|
||||
Must be signed with [client_ca](#client_ca). Also used as the client certificate for etcd/Antietcd
|
||||
connections by default.
|
||||
|
||||
## pkey
|
||||
|
||||
- Type: string
|
||||
|
||||
Private key of the current Vitastor user.
|
||||
|
||||
## etcd_ca
|
||||
|
||||
- Type: string
|
||||
|
||||
Trusted TLS CA to verify etcd server certificate. Or just the etcd server's
|
||||
certificate itself - it's fine to use it for etcd_ca.
|
||||
|
||||
## client_ca
|
||||
|
||||
- Type: string
|
||||
|
||||
Trusted TLS CA to verify Vitastor client certificates.
|
||||
Mandatory for Vitastor protocol encryption.
|
||||
|
||||
## osd_ca
|
||||
|
||||
- Type: string
|
||||
|
||||
Trusted TLS CA to verify Vitastor OSD certificates. Also mandatory for Vitastor protocol
|
||||
encryption. Must be different from client_ca. May be equal to osd_cert - different OSDs
|
||||
don't require separate certificates at the moment because their permissions don't differ.
|
||||
|
||||
## mon_ca
|
||||
|
||||
- Type: string
|
||||
|
||||
Trusted TLS CA to verify Vitastor Monitor certificates. Used only for separate Antietcd,
|
||||
not required when a monitor built-in Antietcd is used. May be equal to mon_client_etcd_cert.
|
||||
|
||||
## antietcd_cert
|
||||
|
||||
- Type: string
|
||||
|
||||
Server TLS certificate for Antietcd built into the Monitor.
|
||||
|
||||
## antietcd_key
|
||||
|
||||
- Type: string
|
||||
|
||||
Private key for antietcd_cert.
|
||||
|
||||
## etcd_proxy.urls
|
||||
|
||||
- Type: string or array of strings
|
||||
|
||||
etcd URLs for Antietcd etcd proxy mode.
|
||||
See [Mon as Etcd proxy](../intro/security.en.md#mon-as-etcd-proxy) for details.
|
||||
|
||||
## etcd_proxy.cert
|
||||
|
||||
- Type: string
|
||||
|
||||
Client certificate for Antietcd connections to etcd in proxy mode.
|
||||
|
||||
## etcd_proxy.key
|
||||
|
||||
- Type: string
|
||||
|
||||
Private key for etcd_proxy.cert.
|
||||
|
||||
## etcd_proxy.ca
|
||||
|
||||
- Type: string
|
||||
|
||||
Trusted TLS CA to verify etcd server certificate when connecting to it from Antietcd.
|
||||
|
||||
## osd_cert
|
||||
|
||||
- Type: string
|
||||
|
||||
Vitastor OSD server certificate. Required for Vitastor protocol encryption. May be equal
|
||||
to [osd_ca](#osd_ca) - all OSDs share the same permission set for now. Also used as the client
|
||||
certificate for connections from OSD to etcd/Antietcd by default.
|
||||
|
||||
## osd_pkey
|
||||
|
||||
- Type: string
|
||||
|
||||
Private key for osd_cert.
|
||||
|
||||
## api_cert
|
||||
|
||||
- Type: string
|
||||
|
||||
Server TLS certificate for [vitastor-cli serve](../usage/cli.en.md#serve) API server.
|
||||
|
||||
## api_pkey
|
||||
|
||||
- Type: string
|
||||
|
||||
Private key for api_cert.
|
||||
|
||||
## etcd_client_cert
|
||||
|
||||
- Type: string
|
||||
|
||||
Client TLS certificate to use for connections from Vitastor clients to etcd/Antietcd if you don't want
|
||||
to use the common client certificate [cert](#cert).
|
||||
|
||||
## etcd_client_key
|
||||
|
||||
- Type: string
|
||||
|
||||
Private key for etcd_client_cert.
|
||||
|
||||
## osd_etcd_client_cert
|
||||
|
||||
- Type: string
|
||||
|
||||
Client TLS certificate to use for connections from Vitastor OSDs to etcd/Antietcd if you don't want
|
||||
to use the common OSD certificate [osd_cert](#osd_cert).
|
||||
|
||||
## osd_etcd_client_key
|
||||
|
||||
- Type: string
|
||||
|
||||
Private key for osd_etcd_client_cert.
|
||||
|
||||
## mon_etcd_client_cert
|
||||
|
||||
- Type: string
|
||||
|
||||
Client TLS certificate to use for connections from Vitastor Monitors to etcd/Antietcd - required
|
||||
if you don't use the built-in Antietcd. In case you use it Monitor has direct access to Antietcd data
|
||||
and doesn't require any connection.
|
||||
|
||||
## mon_etcd_client_key
|
||||
|
||||
- Type: string
|
||||
|
||||
Private key for mon_etcd_client_cert.
|
||||
|
||||
## proto_checksums
|
||||
|
||||
- Type: string
|
||||
- Default: payload
|
||||
|
||||
One of "full", "payload", "gcm", "none":
|
||||
- "full" means calculate and verify transport level checksums from the full message data
|
||||
including the header - recommended for unencrypted setups.
|
||||
- "payload" enables checksums only for the actual read/write data, but skips them for message
|
||||
headers - recommended for encrypted setups because headers are already protected by AES-GCM.
|
||||
- "gcm" disables checksums and enables AES-GCM encryption of the whole messages including headers
|
||||
and data - AES-GCM already includes MAC which is actually a stronger checksum. This option is
|
||||
slower and is only recommended for untrusted networks.
|
||||
- "none" disables transport level checksums at all.
|
||||
|
||||
## force_proto_checksums
|
||||
|
||||
- Type: string
|
||||
|
||||
To allow older clients to connect to a Vitastor cluster with enabled checksums, Vitastor OSDs
|
||||
allow clients to downgrade their proto_checksums by default. force_proto_checksums sets the
|
||||
minimum security level allowed for connecting clients. When encryption is disabled, default
|
||||
force_proto_checksums is none and clients without checksums are allowed. With enabled
|
||||
encryption, force_proto_checksums becomes "payload" by default to block unauthenticated data
|
||||
on the transport level.
|
||||
|
||||
## max_cipher_pool_size
|
||||
|
||||
- Type: integer
|
||||
- Default: 256
|
||||
|
||||
Maximum number of OpenSSL cipher contexts cached in OSD memory, counted separately
|
||||
for each cipher and for encryption/decryption. Probably doesn't require modification.
|
||||
|
||||
## vault_url
|
||||
|
||||
- Type: string
|
||||
|
||||
Vault base URL.
|
||||
|
||||
Vitastor clients support AES-256-XTS image data encryption with different per-image keys.
|
||||
Encryption is performed by the client, OSDs don't have access to decrypted data.
|
||||
|
||||
Encryption keys may be stored in etcd or, for the increased security level, in an external
|
||||
[HashiCorp Vault](https://developer.hashicorp.com/vault/) or [OpenBao](https://openbao.org/)
|
||||
instance.
|
||||
|
||||
Vitastor clients use [v1 k/v secrets engine](https://openbao.org/api-docs/secret/kv/kv-v1/)
|
||||
and [TLS authentication engine](https://openbao.org/api-docs/auth/cert/) in Vault.
|
||||
|
||||
In that case, only key IDs are stored in etcd.
|
||||
|
||||
## vault_secret_api_path
|
||||
|
||||
- Type: string
|
||||
- Default: /v1/secret/
|
||||
|
||||
Vault v1 secret API mount path to use.
|
||||
|
||||
## vault_client_cert
|
||||
|
||||
- Type: string
|
||||
|
||||
Client TLS certificate to use for Vault connections if you don't want to use the common Vitastor
|
||||
client certificate [cert](#cert) which is also used for Vault connections by default.
|
||||
|
||||
## vault_client_key
|
||||
|
||||
- Type: string
|
||||
|
||||
Private key for the vault_client_cert certificate.
|
||||
|
||||
## vault_ca
|
||||
|
||||
- Type: string
|
||||
|
||||
Trusted TLS CA to verify Vault server certificate. May be path to a file,
|
||||
directory or just a PEM string with certificate.
|
||||
|
||||
## vault_timeout_ms
|
||||
|
||||
- Type: integer
|
||||
- Default: 5000
|
||||
|
||||
Timeout for Vault requests in milliseconds.
|
||||
|
||||
## vault_error_timeout_sec
|
||||
|
||||
- Type: integer
|
||||
- Default: 60
|
||||
|
||||
Time (in seconds) to wait before retrying after receiving an error from Vault.
|
||||
|
||||
## vault_refresh_leeway_sec
|
||||
|
||||
- Type: integer
|
||||
- Default: 60
|
||||
|
||||
Extra time (in seconds) before real Vault token lease_timeout to refresh it, just
|
||||
in case of system clock drift.
|
||||
@@ -0,0 +1,312 @@
|
||||
[Документация](../../README-ru.md#документация) → [Конфигурация](../config.ru.md) → Параметры безопасности
|
||||
|
||||
-----
|
||||
|
||||
[Read in English](security.en.md)
|
||||
|
||||
# Параметры безопасности
|
||||
|
||||
Данные параметры затрагивают безопасность инсталляций Vitastor и используются
|
||||
OSD, мониторами и клиентами.
|
||||
|
||||
Большая их часть может задаваться в /etc/vitastor/vitastor.conf и в etcd, но не
|
||||
поддерживает онлайн-изменение.
|
||||
|
||||
Все параметры сертификатов и закрытых ключей могут быть путём к файлу или просто
|
||||
строкой с сертификатом в формате PEM. В последнем случае строка должна начинаться с
|
||||
"-----BEGIN CERTIFICATE-----" или "-----BEGIN PRIVATE KEY-----".
|
||||
|
||||
- [use_perms](#use_perms)
|
||||
- [cert](#cert)
|
||||
- [pkey](#pkey)
|
||||
- [etcd_ca](#etcd_ca)
|
||||
- [client_ca](#client_ca)
|
||||
- [osd_ca](#osd_ca)
|
||||
- [mon_ca](#mon_ca)
|
||||
- [antietcd_cert](#antietcd_cert)
|
||||
- [antietcd_key](#antietcd_key)
|
||||
- [etcd_proxy.urls](#etcd_proxyurls)
|
||||
- [etcd_proxy.cert](#etcd_proxycert)
|
||||
- [etcd_proxy.key](#etcd_proxykey)
|
||||
- [etcd_proxy.ca](#etcd_proxyca)
|
||||
- [osd_cert](#osd_cert)
|
||||
- [osd_pkey](#osd_pkey)
|
||||
- [api_cert](#api_cert)
|
||||
- [api_pkey](#api_pkey)
|
||||
- [etcd_client_cert](#etcd_client_cert)
|
||||
- [etcd_client_key](#etcd_client_key)
|
||||
- [osd_etcd_client_cert](#osd_etcd_client_cert)
|
||||
- [osd_etcd_client_key](#osd_etcd_client_key)
|
||||
- [mon_etcd_client_cert](#mon_etcd_client_cert)
|
||||
- [mon_etcd_client_key](#mon_etcd_client_key)
|
||||
- [proto_checksums](#proto_checksums)
|
||||
- [force_proto_checksums](#force_proto_checksums)
|
||||
- [max_cipher_pool_size](#max_cipher_pool_size)
|
||||
- [vault_url](#vault_url)
|
||||
- [vault_secret_api_path](#vault_secret_api_path)
|
||||
- [vault_client_cert](#vault_client_cert)
|
||||
- [vault_client_key](#vault_client_key)
|
||||
- [vault_ca](#vault_ca)
|
||||
- [vault_timeout_ms](#vault_timeout_ms)
|
||||
- [vault_error_timeout_sec](#vault_error_timeout_sec)
|
||||
- [vault_refresh_leeway_sec](#vault_refresh_leeway_sec)
|
||||
|
||||
## use_perms
|
||||
|
||||
- Тип: булево (да/нет)
|
||||
- Значение по умолчанию: false
|
||||
|
||||
Включает клиентские привилегии в кластере Vitastor, в том числе во встроенном в мониторе Antietcd.
|
||||
Требует настроенного шифрования протокола. Также обратите внимание, что отдельно установленный Antietcd
|
||||
требует отдельной настройки привилегий (подробности смотрите в [документации безопасности](../intro/security.ru.md)).
|
||||
|
||||
## cert
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Клиентский сертификат текущего пользователя Vitastor. Требуется для шифрования протокола Vitastor.
|
||||
Должен быть подписан [client_ca](#client_ca). Также по умолчанию используется как клиентский
|
||||
сертификат для подключения к etcd/Antietcd и Vault.
|
||||
|
||||
## pkey
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Закрытый ключ текущего пользователя Vitastor.
|
||||
|
||||
## etcd_ca
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Доверенный корневой TLS-сертификат для проверки сертификата сервера etcd.
|
||||
Либо же просто сам сертификат сервера etcd - его можно использовать как etcd_ca.
|
||||
|
||||
## client_ca
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Доверенный TLS-сертификат для проверки сертификатов клиентов Vitastor.
|
||||
Требуется для шифрования протокола Vitastor.
|
||||
|
||||
## osd_ca
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Доверенный TLS-сертификат для проверки сертификатов OSD Vitastor. Также обязателен
|
||||
для шифрования протокола Vitastor. Должен отличаться от client_ca. Может быть равен
|
||||
osd_cert - разные OSD не требуют разных сертификатов, потому что на данный момент
|
||||
привилегии разных OSD никак не отличаются.
|
||||
|
||||
## mon_ca
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Доверенный TLS-сертификат для проверки сертификатов мониторов Vitastor. Используется
|
||||
только отдельно установленным Antietcd, не требуется при использовании встроенного в монитор
|
||||
Antietcd. Может быть равен mon_client_etcd_cert.
|
||||
|
||||
## antietcd_cert
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Серверный TLS-сертификат для Antietcd, встроенного в монитор.
|
||||
|
||||
## antietcd_key
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Закрытый ключ для сертификата antietcd_cert.
|
||||
|
||||
## etcd_proxy.urls
|
||||
|
||||
- Тип: строка или массив строк
|
||||
|
||||
Адреса etcd для режима Antietcd etcd-прокси.
|
||||
Смотрите подробности в разделе [Mon в роли Etcd proxy](../intro/security.ru.md#mon-в-роли-etcd-proxy).
|
||||
|
||||
## etcd_proxy.cert
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Клиентский сертификат для подключений от Antietcd к etcd в режиме прокси.
|
||||
|
||||
## etcd_proxy.key
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Закрытый ключ для сертификата etcd_proxy.cert.
|
||||
|
||||
## etcd_proxy.ca
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Доверенный TLS-сертификат для проверки сертификата сервера etcd при подключениях от Antietcd.
|
||||
|
||||
## osd_cert
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Сертификат сервера Vitastor OSD. Требуется для шифрования протокола Vitastor. Может быть равен
|
||||
[osd_ca](#osd_ca) - все OSD на данный момент имеют одинаковые привилегии. Также по умолчанию
|
||||
используется как клиентский сертификат для подключения от OSD к etcd/Antietcd.
|
||||
|
||||
## osd_pkey
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Закрытый ключ для сертификата osd_cert.
|
||||
|
||||
## api_cert
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Серверный TLS-сертификат для API-сервера [vitastor-cli serve](../usage/cli.ru.md#serve).
|
||||
|
||||
## api_pkey
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Закрытый ключ для сертификата api_cert.
|
||||
|
||||
## etcd_client_cert
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Клиентский TLS сертификат для подключений от клиентов Vitastor к etcd/Antietcd, если вы не хотите
|
||||
использовать общий клиентский сертификат [cert](#cert).
|
||||
|
||||
## etcd_client_key
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Закрытый ключ для сертификата etcd_client_cert.
|
||||
|
||||
## osd_etcd_client_cert
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Клиентский TLS сертификат для подключений от Vitastor OSD к etcd/Antietcd, если вы не хотите
|
||||
использовать общий сертификат OSD [osd_cert](#osd_cert).
|
||||
|
||||
## osd_etcd_client_key
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Закрытый ключ для сертификата osd_etcd_client_cert.
|
||||
|
||||
## mon_etcd_client_cert
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Клиентский TLS сертификат для подключений от мониторов Vitastor к etcd/Antietcd - требуется, если
|
||||
вы не используете встроенный в монитор Antietcd. Если вы используете его, то монитор и так имеет
|
||||
прямой доступ к данным Antietcd и не требует никаких соединений.
|
||||
|
||||
## mon_etcd_client_key
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Закрытый ключ для сертификата mon_etcd_client_cert.
|
||||
|
||||
## proto_checksums
|
||||
|
||||
- Тип: строка
|
||||
- Значение по умолчанию: payload
|
||||
|
||||
Одно из значений "full", "payload", "gcm" и "none":
|
||||
- "full" означает расчёт и проверку контрольных сумм на транспортном уровне от полных сообщений,
|
||||
включая их заголовки и данные - рекомендуется для кластеров без шифрования.
|
||||
- "payload" включает контрольные суммы только для данных сообщений, но пропускает заголовки -
|
||||
такая настройка рекомендуется для кластеров с включённым шифрованием, потому что в них заголовки
|
||||
и так защищены шифрованием AES-GCM.
|
||||
- "gcm" отключает контрольные суммы и включает шифрование полных сообщений включая заголовки и
|
||||
данные - AES-GCM уже включает в себя MAC, который по сути является криптостойкой контрольной
|
||||
суммой. Такая настройка медленнее и рекомендуется только для недоверенных сетей.
|
||||
- "none" полностью отключает контрольные суммы на транспортном уровне.
|
||||
|
||||
## force_proto_checksums
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Чтобы старые клиенты Vitastor могли подключаться к кластеру с включёнными контрольными
|
||||
суммами, Vitastor OSD по умолчанию разрешают клиентам отключать контрольные суммы
|
||||
данных (proto_checksums). Настройка force_proto_checksums задаёт минимальный уровень
|
||||
безопасности, разрешённый для подключающихся клиентов. Когда шифрование отключено,
|
||||
force_proto_checksums по умолчанию равно none и подключения клиентов без контрольных
|
||||
сумм разрешаются. При включённом шифровании значение по умолчанию force_proto_checksums
|
||||
становится "payload", чтобы блокировать подключения с неаутентифицированными данными.
|
||||
|
||||
## max_cipher_pool_size
|
||||
|
||||
- Тип: целое число
|
||||
- Значение по умолчанию: 256
|
||||
|
||||
Максимальное количество кэшируемых в памяти OSD контекстов шифра OpenSSL, учитываемое
|
||||
отдельно для каждого шифра и для шифрования и расшифровки. Вряд ли требует изменения.
|
||||
|
||||
## vault_url
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Базовый адрес Vault.
|
||||
|
||||
Клиенты Vitastor поддерживают AES-256-XTS шифрование данных образов с отдельными ключами на
|
||||
каждый образ. Данные шифруются клиентами, OSD не имеют доступа к незашифрованным данным.
|
||||
|
||||
Ключи шифрования могут храниться в etcd или, для повышенного уровня безопасности, во внешнем
|
||||
[HashiCorp Vault](https://developer.hashicorp.com/vault/) или [OpenBao](https://openbao.org/).
|
||||
|
||||
Клиенты Vitastor используют [движок секретов v1](https://openbao.org/api-docs/secret/kv/kv-v1/)
|
||||
и [TLS-аутентификацию](https://openbao.org/api-docs/auth/cert/) в Vault.
|
||||
|
||||
В этом случае, только ID ключей хранятся в etcd.
|
||||
|
||||
## vault_secret_api_path
|
||||
|
||||
- Тип: строка
|
||||
- Значение по умолчанию: /v1/secret/
|
||||
|
||||
Путь к API секретов v1 для использования клиентами.
|
||||
|
||||
## vault_client_cert
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Клиентский TLS сертификат для подключений к Vault на тот случай, если вы не хотите использовать
|
||||
общий сертификат клиента Vitastor [cert](#cert), используемый для подключений к Vault по умолчанию.
|
||||
|
||||
## vault_client_key
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Закрытый ключ для сертификата vault_client_cert.
|
||||
|
||||
## vault_ca
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Доверенный корневой TLS-сертификат для проверки сертификата сервера Vault.
|
||||
Может быть путём к файлу, директории или просто строкой с сертификатом в
|
||||
формате PEM.
|
||||
|
||||
## vault_timeout_ms
|
||||
|
||||
- Тип: целое число
|
||||
- Значение по умолчанию: 5000
|
||||
|
||||
Максимально время выполнения Vault-запросов в миллисекундах.
|
||||
|
||||
## vault_error_timeout_sec
|
||||
|
||||
- Тип: целое число
|
||||
- Значение по умолчанию: 60
|
||||
|
||||
Время (в секундах) для ожидания перед повторной попыткой при получении ошибки от Vault.
|
||||
|
||||
## vault_refresh_leeway_sec
|
||||
|
||||
- Тип: целое число
|
||||
- Значение по умолчанию: 60
|
||||
|
||||
Зазор времени (в секундах), чтобы обновлять токены Vault чуть раньше их реального
|
||||
lease_timeout, на случай "ухода" системных часов.
|
||||
@@ -44,6 +44,8 @@
|
||||
|
||||
{{../../config/monitor.en.md|indent=2}}
|
||||
|
||||
{{../../config/security.en.md|indent=2}}
|
||||
|
||||
{{../../config/pool.en.md|indent=2}}
|
||||
|
||||
{{../../config/inode.en.md|indent=2}}
|
||||
|
||||
@@ -44,6 +44,8 @@
|
||||
|
||||
{{../../config/monitor.ru.md|indent=2}}
|
||||
|
||||
{{../../config/security.ru.md|indent=2}}
|
||||
|
||||
{{../../config/pool.ru.md|indent=2}}
|
||||
|
||||
{{../../config/inode.ru.md|indent=2}}
|
||||
|
||||
@@ -233,11 +233,21 @@
|
||||
type: string
|
||||
default: none
|
||||
info: |
|
||||
Data checksum type to use. May be "crc32c" or "none". Set to "crc32c" to
|
||||
enable data checksums.
|
||||
Data and metadata checksum type to use. May be "crc32c", "xxh3_32" or "none".
|
||||
Select crc32c or xxh3_32 and set csum_block_size to enable data checksums.
|
||||
|
||||
Both crc32c and xxh3_32 are almost equally fast, xxh3_32 is safer. xxh3_32 is
|
||||
the xxhash3 algorithm truncated from 64 to 32 bits (which is still a good hash).
|
||||
|
||||
Note that enabled data checksums either increase memory usage or reduce
|
||||
performance. Check details in [csum_block_size](#csum_block_size) description.
|
||||
info_ru: |
|
||||
Тип используемых OSD контрольных сумм данных. Может быть "crc32c" или "none".
|
||||
Установите в "crc32c", чтобы включить расчёт и проверку контрольных сумм данных.
|
||||
Тип используемых OSD контрольных сумм данных и метаданных. Может быть "crc32c",
|
||||
"xxh3_32" или "none". Выберите crc32c или xxh3_32 и установите csum_block_size,
|
||||
чтобы включить контрольные суммы данных.
|
||||
|
||||
И crc32c, и xxh3_32 примерно одинаково быстры, xxh3_32 надёжней. xxh3_32 - это
|
||||
алгоритм xxhash3, обрезанный с 64 до 32 бит (это всё равно хороший хеш).
|
||||
|
||||
Следует понимать, что контрольные суммы в зависимости от размера блока их
|
||||
расчёта либо увеличивают потребление памяти, либо снижают производительность.
|
||||
|
||||
@@ -64,7 +64,7 @@ for (const file of params_files)
|
||||
let out = '\n';
|
||||
for (const c of cfg)
|
||||
{
|
||||
out += `\n- [${c.name}](#${c.name})`;
|
||||
out += `\n- [${c.name}](#${c.name.replace(/\./g, '')})`;
|
||||
}
|
||||
for (const c of cfg)
|
||||
{
|
||||
|
||||
@@ -21,6 +21,9 @@
|
||||
cluster, cluster_key, persist_filter, stale_read can also be set in
|
||||
Vitastor configuration with `antietcd_` prefix.
|
||||
|
||||
See also: [antietcd_cert](security.en.md#antietcd_cert),
|
||||
[antietcd_key](security.en.md#antietcd_key) and [etcd_proxy](security.en.md#etcd_proxyurls).
|
||||
|
||||
You can dump/load data to or from antietcd using Antietcd `anticli` tool:
|
||||
|
||||
```
|
||||
@@ -47,6 +50,9 @@
|
||||
node_id, cluster, cluster_key, persist_filter, stale_read также можно задавать
|
||||
в конфигурации Vitastor с префиксом `antietcd_`.
|
||||
|
||||
Смотрите также настройки [antietcd_cert](security.ru.md#antietcd_cert),
|
||||
[antietcd_key](security.ru.md#antietcd_key) и [etcd_proxy](security.ru.md#etcd_proxyurls).
|
||||
|
||||
Вы можете выгружать/загружать данные в или из antietcd с помощью его инструмента
|
||||
`anticli`:
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"yaml": "^2.8.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
# Security Parameters
|
||||
|
||||
These parameters affect your Vitastor installation security and apply to OSDs, monitors and clients.
|
||||
|
||||
Most of them can be set in /etc/vitastor/vitastor.conf and in etcd, but don't support online modification.
|
||||
|
||||
All certificate and private key parameters mentioned may contain a path to a PEM file or just
|
||||
a PEM string with certificate or a private key. In the latter case, the string must begin with
|
||||
"-----BEGIN CERTIFICATE-----" or "-----BEGIN PRIVATE KEY-----".
|
||||
@@ -0,0 +1,11 @@
|
||||
# Параметры безопасности
|
||||
|
||||
Данные параметры затрагивают безопасность инсталляций Vitastor и используются
|
||||
OSD, мониторами и клиентами.
|
||||
|
||||
Большая их часть может задаваться в /etc/vitastor/vitastor.conf и в etcd, но не
|
||||
поддерживает онлайн-изменение.
|
||||
|
||||
Все параметры сертификатов и закрытых ключей могут быть путём к файлу или просто
|
||||
строкой с сертификатом в формате PEM. В последнем случае строка должна начинаться с
|
||||
"-----BEGIN CERTIFICATE-----" или "-----BEGIN PRIVATE KEY-----".
|
||||
@@ -0,0 +1,276 @@
|
||||
- name: use_perms
|
||||
type: bool
|
||||
default: false
|
||||
info: |
|
||||
Enable client permissions in a Vitastor cluster, including Antietcd built into the Monitor.
|
||||
Requires configured encryption. Also note that separate Antietcd requires separate configuration
|
||||
to use permissions (see [security documentation](../intro/security.en.md) for details).
|
||||
info_ru: |
|
||||
Включает клиентские привилегии в кластере Vitastor, в том числе во встроенном в мониторе Antietcd.
|
||||
Требует настроенного шифрования протокола. Также обратите внимание, что отдельно установленный Antietcd
|
||||
требует отдельной настройки привилегий (подробности смотрите в [документации безопасности](../intro/security.ru.md)).
|
||||
- name: cert
|
||||
type: string
|
||||
info: |
|
||||
Client certificate of the current Vitastor user. Required for Vitastor protocol encryption.
|
||||
Must be signed with [client_ca](#client_ca). Also used as the client certificate for etcd/Antietcd
|
||||
connections by default.
|
||||
info_ru: |
|
||||
Клиентский сертификат текущего пользователя Vitastor. Требуется для шифрования протокола Vitastor.
|
||||
Должен быть подписан [client_ca](#client_ca). Также по умолчанию используется как клиентский
|
||||
сертификат для подключения к etcd/Antietcd и Vault.
|
||||
- name: pkey
|
||||
type: string
|
||||
info: Private key of the current Vitastor user.
|
||||
info_ru: Закрытый ключ текущего пользователя Vitastor.
|
||||
- name: etcd_ca
|
||||
type: string
|
||||
info: |
|
||||
Trusted TLS CA to verify etcd server certificate. Or just the etcd server's
|
||||
certificate itself - it's fine to use it for etcd_ca.
|
||||
info_ru: |
|
||||
Доверенный корневой TLS-сертификат для проверки сертификата сервера etcd.
|
||||
Либо же просто сам сертификат сервера etcd - его можно использовать как etcd_ca.
|
||||
- name: client_ca
|
||||
type: string
|
||||
info: |
|
||||
Trusted TLS CA to verify Vitastor client certificates.
|
||||
Mandatory for Vitastor protocol encryption.
|
||||
info_ru: |
|
||||
Доверенный TLS-сертификат для проверки сертификатов клиентов Vitastor.
|
||||
Требуется для шифрования протокола Vitastor.
|
||||
- name: osd_ca
|
||||
type: string
|
||||
info: |
|
||||
Trusted TLS CA to verify Vitastor OSD certificates. Also mandatory for Vitastor protocol
|
||||
encryption. Must be different from client_ca. May be equal to osd_cert - different OSDs
|
||||
don't require separate certificates at the moment because their permissions don't differ.
|
||||
info_ru: |
|
||||
Доверенный TLS-сертификат для проверки сертификатов OSD Vitastor. Также обязателен
|
||||
для шифрования протокола Vitastor. Должен отличаться от client_ca. Может быть равен
|
||||
osd_cert - разные OSD не требуют разных сертификатов, потому что на данный момент
|
||||
привилегии разных OSD никак не отличаются.
|
||||
- name: mon_ca
|
||||
type: string
|
||||
info: |
|
||||
Trusted TLS CA to verify Vitastor Monitor certificates. Used only for separate Antietcd,
|
||||
not required when a monitor built-in Antietcd is used. May be equal to mon_client_etcd_cert.
|
||||
info_ru: |
|
||||
Доверенный TLS-сертификат для проверки сертификатов мониторов Vitastor. Используется
|
||||
только отдельно установленным Antietcd, не требуется при использовании встроенного в монитор
|
||||
Antietcd. Может быть равен mon_client_etcd_cert.
|
||||
- name: antietcd_cert
|
||||
type: string
|
||||
info: Server TLS certificate for Antietcd built into the Monitor.
|
||||
info_ru: Серверный TLS-сертификат для Antietcd, встроенного в монитор.
|
||||
- name: antietcd_key
|
||||
type: string
|
||||
info: Private key for antietcd_cert.
|
||||
info_ru: Закрытый ключ для сертификата antietcd_cert.
|
||||
- name: etcd_proxy.urls
|
||||
type: string or array of strings
|
||||
type_ru: строка или массив строк
|
||||
info: |
|
||||
etcd URLs for Antietcd etcd proxy mode.
|
||||
See [Mon as Etcd proxy](../intro/security.en.md#mon-as-etcd-proxy) for details.
|
||||
info_ru: |
|
||||
Адреса etcd для режима Antietcd etcd-прокси.
|
||||
Смотрите подробности в разделе [Mon в роли Etcd proxy](../intro/security.ru.md#mon-в-роли-etcd-proxy).
|
||||
- name: etcd_proxy.cert
|
||||
type: string
|
||||
info: Client certificate for Antietcd connections to etcd in proxy mode.
|
||||
info_ru: Клиентский сертификат для подключений от Antietcd к etcd в режиме прокси.
|
||||
- name: etcd_proxy.key
|
||||
type: string
|
||||
info: Private key for etcd_proxy.cert.
|
||||
info_ru: Закрытый ключ для сертификата etcd_proxy.cert.
|
||||
- name: etcd_proxy.ca
|
||||
type: string
|
||||
info: Trusted TLS CA to verify etcd server certificate when connecting to it from Antietcd.
|
||||
info_ru: Доверенный TLS-сертификат для проверки сертификата сервера etcd при подключениях от Antietcd.
|
||||
- name: osd_cert
|
||||
type: string
|
||||
info: |
|
||||
Vitastor OSD server certificate. Required for Vitastor protocol encryption. May be equal
|
||||
to [osd_ca](#osd_ca) - all OSDs share the same permission set for now. Also used as the client
|
||||
certificate for connections from OSD to etcd/Antietcd by default.
|
||||
info_ru: |
|
||||
Сертификат сервера Vitastor OSD. Требуется для шифрования протокола Vitastor. Может быть равен
|
||||
[osd_ca](#osd_ca) - все OSD на данный момент имеют одинаковые привилегии. Также по умолчанию
|
||||
используется как клиентский сертификат для подключения от OSD к etcd/Antietcd.
|
||||
- name: osd_pkey
|
||||
type: string
|
||||
info: Private key for osd_cert.
|
||||
info_ru: Закрытый ключ для сертификата osd_cert.
|
||||
- name: api_cert
|
||||
type: string
|
||||
info: Server TLS certificate for [vitastor-cli serve](../usage/cli.en.md#serve) API server.
|
||||
info_ru: Серверный TLS-сертификат для API-сервера [vitastor-cli serve](../usage/cli.ru.md#serve).
|
||||
- name: api_pkey
|
||||
type: string
|
||||
info: Private key for api_cert.
|
||||
info_ru: Закрытый ключ для сертификата api_cert.
|
||||
- name: etcd_client_cert
|
||||
type: string
|
||||
info: |
|
||||
Client TLS certificate to use for connections from Vitastor clients to etcd/Antietcd if you don't want
|
||||
to use the common client certificate [cert](#cert).
|
||||
info_ru: |
|
||||
Клиентский TLS сертификат для подключений от клиентов Vitastor к etcd/Antietcd, если вы не хотите
|
||||
использовать общий клиентский сертификат [cert](#cert).
|
||||
- name: etcd_client_key
|
||||
type: string
|
||||
info: Private key for etcd_client_cert.
|
||||
info_ru: Закрытый ключ для сертификата etcd_client_cert.
|
||||
- name: osd_etcd_client_cert
|
||||
type: string
|
||||
info: |
|
||||
Client TLS certificate to use for connections from Vitastor OSDs to etcd/Antietcd if you don't want
|
||||
to use the common OSD certificate [osd_cert](#osd_cert).
|
||||
info_ru: |
|
||||
Клиентский TLS сертификат для подключений от Vitastor OSD к etcd/Antietcd, если вы не хотите
|
||||
использовать общий сертификат OSD [osd_cert](#osd_cert).
|
||||
- name: osd_etcd_client_key
|
||||
type: string
|
||||
info: Private key for osd_etcd_client_cert.
|
||||
info_ru: Закрытый ключ для сертификата osd_etcd_client_cert.
|
||||
- name: mon_etcd_client_cert
|
||||
type: string
|
||||
info: |
|
||||
Client TLS certificate to use for connections from Vitastor Monitors to etcd/Antietcd - required
|
||||
if you don't use the built-in Antietcd. In case you use it Monitor has direct access to Antietcd data
|
||||
and doesn't require any connection.
|
||||
info_ru: |
|
||||
Клиентский TLS сертификат для подключений от мониторов Vitastor к etcd/Antietcd - требуется, если
|
||||
вы не используете встроенный в монитор Antietcd. Если вы используете его, то монитор и так имеет
|
||||
прямой доступ к данным Antietcd и не требует никаких соединений.
|
||||
- name: mon_etcd_client_key
|
||||
type: string
|
||||
info: Private key for mon_etcd_client_cert.
|
||||
info_ru: Закрытый ключ для сертификата mon_etcd_client_cert.
|
||||
- name: proto_checksums
|
||||
type: string
|
||||
default: payload
|
||||
info: |
|
||||
One of "full", "payload", "gcm", "none":
|
||||
- "full" means calculate and verify transport level checksums from the full message data
|
||||
including the header - recommended for unencrypted setups.
|
||||
- "payload" enables checksums only for the actual read/write data, but skips them for message
|
||||
headers - recommended for encrypted setups because headers are already protected by AES-GCM.
|
||||
- "gcm" disables checksums and enables AES-GCM encryption of the whole messages including headers
|
||||
and data - AES-GCM already includes MAC which is actually a stronger checksum. This option is
|
||||
slower and is only recommended for untrusted networks.
|
||||
- "none" disables transport level checksums at all.
|
||||
info_ru: |
|
||||
Одно из значений "full", "payload", "gcm" и "none":
|
||||
- "full" означает расчёт и проверку контрольных сумм на транспортном уровне от полных сообщений,
|
||||
включая их заголовки и данные - рекомендуется для кластеров без шифрования.
|
||||
- "payload" включает контрольные суммы только для данных сообщений, но пропускает заголовки -
|
||||
такая настройка рекомендуется для кластеров с включённым шифрованием, потому что в них заголовки
|
||||
и так защищены шифрованием AES-GCM.
|
||||
- "gcm" отключает контрольные суммы и включает шифрование полных сообщений включая заголовки и
|
||||
данные - AES-GCM уже включает в себя MAC, который по сути является криптостойкой контрольной
|
||||
суммой. Такая настройка медленнее и рекомендуется только для недоверенных сетей.
|
||||
- "none" полностью отключает контрольные суммы на транспортном уровне.
|
||||
- name: force_proto_checksums
|
||||
type: string
|
||||
info: |
|
||||
To allow older clients to connect to a Vitastor cluster with enabled checksums, Vitastor OSDs
|
||||
allow clients to downgrade their proto_checksums by default. force_proto_checksums sets the
|
||||
minimum security level allowed for connecting clients. When encryption is disabled, default
|
||||
force_proto_checksums is none and clients without checksums are allowed. With enabled
|
||||
encryption, force_proto_checksums becomes "payload" by default to block unauthenticated data
|
||||
on the transport level.
|
||||
info_ru: |
|
||||
Чтобы старые клиенты Vitastor могли подключаться к кластеру с включёнными контрольными
|
||||
суммами, Vitastor OSD по умолчанию разрешают клиентам отключать контрольные суммы
|
||||
данных (proto_checksums). Настройка force_proto_checksums задаёт минимальный уровень
|
||||
безопасности, разрешённый для подключающихся клиентов. Когда шифрование отключено,
|
||||
force_proto_checksums по умолчанию равно none и подключения клиентов без контрольных
|
||||
сумм разрешаются. При включённом шифровании значение по умолчанию force_proto_checksums
|
||||
становится "payload", чтобы блокировать подключения с неаутентифицированными данными.
|
||||
- name: max_cipher_pool_size
|
||||
type: int
|
||||
default: 256
|
||||
info: |
|
||||
Maximum number of OpenSSL cipher contexts cached in OSD memory, counted separately
|
||||
for each cipher and for encryption/decryption. Probably doesn't require modification.
|
||||
info_ru: |
|
||||
Максимальное количество кэшируемых в памяти OSD контекстов шифра OpenSSL, учитываемое
|
||||
отдельно для каждого шифра и для шифрования и расшифровки. Вряд ли требует изменения.
|
||||
- name: vault_url
|
||||
type: string
|
||||
info: |
|
||||
Vault base URL.
|
||||
|
||||
Vitastor clients support AES-256-XTS image data encryption with different per-image keys.
|
||||
Encryption is performed by the client, OSDs don't have access to decrypted data.
|
||||
|
||||
Encryption keys may be stored in etcd or, for the increased security level, in an external
|
||||
[HashiCorp Vault](https://developer.hashicorp.com/vault/) or [OpenBao](https://openbao.org/)
|
||||
instance.
|
||||
|
||||
Vitastor clients use [v1 k/v secrets engine](https://openbao.org/api-docs/secret/kv/kv-v1/)
|
||||
and [TLS authentication engine](https://openbao.org/api-docs/auth/cert/) in Vault.
|
||||
|
||||
In that case, only key IDs are stored in etcd.
|
||||
info_ru: |
|
||||
Базовый адрес Vault.
|
||||
|
||||
Клиенты Vitastor поддерживают AES-256-XTS шифрование данных образов с отдельными ключами на
|
||||
каждый образ. Данные шифруются клиентами, OSD не имеют доступа к незашифрованным данным.
|
||||
|
||||
Ключи шифрования могут храниться в etcd или, для повышенного уровня безопасности, во внешнем
|
||||
[HashiCorp Vault](https://developer.hashicorp.com/vault/) или [OpenBao](https://openbao.org/).
|
||||
|
||||
Клиенты Vitastor используют [движок секретов v1](https://openbao.org/api-docs/secret/kv/kv-v1/)
|
||||
и [TLS-аутентификацию](https://openbao.org/api-docs/auth/cert/) в Vault.
|
||||
|
||||
В этом случае, только ID ключей хранятся в etcd.
|
||||
- name: vault_secret_api_path
|
||||
type: string
|
||||
default: /v1/secret/
|
||||
info: Vault v1 secret API mount path to use.
|
||||
info_ru: Путь к API секретов v1 для использования клиентами.
|
||||
- name: vault_client_cert
|
||||
type: string
|
||||
info: |
|
||||
Client TLS certificate to use for Vault connections if you don't want to use the common Vitastor
|
||||
client certificate [cert](#cert) which is also used for Vault connections by default.
|
||||
info_ru: |
|
||||
Клиентский TLS сертификат для подключений к Vault на тот случай, если вы не хотите использовать
|
||||
общий сертификат клиента Vitastor [cert](#cert), используемый для подключений к Vault по умолчанию.
|
||||
- name: vault_client_key
|
||||
type: string
|
||||
info: Private key for the vault_client_cert certificate.
|
||||
info_ru: Закрытый ключ для сертификата vault_client_cert.
|
||||
- name: vault_ca
|
||||
type: string
|
||||
info: |
|
||||
Trusted TLS CA to verify Vault server certificate. May be path to a file,
|
||||
directory or just a PEM string with certificate.
|
||||
info_ru: |
|
||||
Доверенный корневой TLS-сертификат для проверки сертификата сервера Vault.
|
||||
Может быть путём к файлу, директории или просто строкой с сертификатом в
|
||||
формате PEM.
|
||||
- name: vault_timeout_ms
|
||||
type: int
|
||||
default: 5000
|
||||
info: Timeout for Vault requests in milliseconds.
|
||||
info_ru: Максимально время выполнения Vault-запросов в миллисекундах.
|
||||
- name: vault_error_timeout_sec
|
||||
type: int
|
||||
default: 60
|
||||
info: |
|
||||
Time (in seconds) to wait before retrying after receiving an error from Vault.
|
||||
info_ru: |
|
||||
Время (в секундах) для ожидания перед повторной попыткой при получении ошибки от Vault.
|
||||
- name: vault_refresh_leeway_sec
|
||||
type: int
|
||||
default: 60
|
||||
info: |
|
||||
Extra time (in seconds) before real Vault token lease_timeout to refresh it, just
|
||||
in case of system clock drift.
|
||||
info_ru: |
|
||||
Зазор времени (в секундах), чтобы обновлять токены Vault чуть раньше их реального
|
||||
lease_timeout, на случай "ухода" системных часов.
|
||||
@@ -26,9 +26,9 @@ at Vitastor Kubernetes operator: https://github.com/Antilles7227/vitastor-operat
|
||||
The instruction is very simple.
|
||||
|
||||
1. Download a Docker image of the desired version: \
|
||||
`docker pull vitalif/vitastor:v3.0.14`
|
||||
`docker pull vitalif/vitastor:v3.0.15`
|
||||
2. Install scripts to the host system: \
|
||||
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.14 install.sh`
|
||||
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.15 install.sh`
|
||||
3. Reload udev rules: \
|
||||
`udevadm control --reload-rules`
|
||||
4. Enable the vitastor-host service: \
|
||||
|
||||
@@ -25,9 +25,9 @@ Vitastor можно установить в Docker/Podman. При этом etcd,
|
||||
Инструкция по установке максимально простая.
|
||||
|
||||
1. Скачайте Docker-образ желаемой версии: \
|
||||
`docker pull vitalif/vitastor:v3.0.14`
|
||||
`docker pull vitalif/vitastor:v3.0.15`
|
||||
2. Установите скрипты в хост-систему командой: \
|
||||
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.14 install.sh`
|
||||
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.15 install.sh`
|
||||
3. Перезагрузите правила udev: \
|
||||
`udevadm control --reload-rules`
|
||||
4. Включите сервис vitastor-host: \
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
- gcc and g++ 8 or newer, clang 10 or newer, or other compiler with C++11 plus
|
||||
designated initializers support from C++20
|
||||
- CMake
|
||||
- jerasure headers and libraries
|
||||
- jerasure, c-ares headers and libraries
|
||||
- ISA-L, libibverbs, librdmacm, libnl3 headers and libraries (optional)
|
||||
|
||||
## Basic instructions
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
- gcc и g++ >= 8, либо clang >= 10, либо другой компилятор с поддержкой C++11 плюс
|
||||
назначенных инициализаторов (designated initializers) из C++20
|
||||
- CMake
|
||||
- Заголовки и библиотеки jerasure
|
||||
- Заголовки и библиотеки jerasure, c-ares
|
||||
- Опционально - заголовки и библиотеки ISA-L, libibverbs, librdmacm, libnl3
|
||||
|
||||
## Базовая инструкция
|
||||
|
||||
@@ -41,6 +41,8 @@
|
||||
- [Built-in Prometheus metric exporter](../config/monitor.en.md#enable_prometheus)
|
||||
- [NFS RDMA support](../usage/nfs.en.md#rdma) (probably also usable for GPUDirect)
|
||||
- [S3](../installation/s3.en.md)
|
||||
- [TLS support for etcd connections](../config/security.en.md)
|
||||
- [AES-256-XTS image encryption](../usage/cli.en.md#create) and [Vault support](../config/security.en.md#vault_url) for key storage
|
||||
|
||||
## Plugins and tools
|
||||
|
||||
|
||||
@@ -43,6 +43,8 @@
|
||||
- [Встроенный Prometheus-экспортер метрик](../config/monitor.ru.md#enable_prometheus)
|
||||
- [Поддержка NFS RDMA](../usage/nfs.ru.md#rdma) (вероятно, также подходящая для GPUDirect)
|
||||
- [S3](../installation/s3.ru.md)
|
||||
- [Поддержка TLS-соединений с etcd](../config/security.ru.md)
|
||||
- [AES-256-XTS шифрование данных](../usage/cli.ru.md#create) и [поддержка Vault](../config/security.ru.md#vault_url) для хранения ключей
|
||||
|
||||
## Драйверы и инструменты
|
||||
|
||||
|
||||
@@ -41,12 +41,16 @@
|
||||
## Configure monitors
|
||||
|
||||
On the monitor hosts:
|
||||
- Put identical etcd_address into `/etc/vitastor/vitastor.conf`. Example:
|
||||
- Create minimal configuration in `/etc/vitastor/vitastor.conf`:
|
||||
```
|
||||
{
|
||||
"etcd_address": ["10.200.1.10:2379","10.200.1.11:2379","10.200.1.12:2379"]
|
||||
"etcd_address": ["http://10.200.1.10:2379","http://10.200.1.11:2379","http://10.200.1.12:2379"],
|
||||
"osd_network": "10.200.1.0/24",
|
||||
"use_perms": false
|
||||
}
|
||||
```
|
||||
- Note that you can enable encryption by using `https://` and `use_perms` option.
|
||||
[Details](security.en.md#quick-setup) about encryption setup with make-etcd.
|
||||
- Create systemd units for etcd by running: `/usr/lib/vitastor/mon/make-etcd`
|
||||
Or, if you installed Vitastor in Docker, run `systemctl start vitastor-host; docker exec vitastor make-etcd`.
|
||||
- Start etcd and monitors: `systemctl enable --now vitastor-etcd vitastor-mon`
|
||||
|
||||
@@ -41,25 +41,23 @@
|
||||
## Настройте мониторы
|
||||
|
||||
На хостах, выделенных под мониторы:
|
||||
- Пропишите одинаковые etcd_address в `/etc/vitastor/vitastor.conf`. Например:
|
||||
- Создайте минимальную конфигурацию в `/etc/vitastor/vitastor.conf`:
|
||||
```
|
||||
{
|
||||
"etcd_address": ["10.200.1.10:2379","10.200.1.11:2379","10.200.1.12:2379"]
|
||||
"etcd_address": ["http://10.200.1.10:2379","http://10.200.1.11:2379","http://10.200.1.12:2379"],
|
||||
"osd_network": "10.200.1.0/24",
|
||||
"use_perms": false
|
||||
}
|
||||
```
|
||||
- Обратите внимание, что с помощью схемы `https://` и опции `use_perms` можно включить шифрование.
|
||||
[Подробно](security.ru.md#быстрая-настройка) о настройке шифрования через make-etcd.
|
||||
- Инициализируйте сервисы etcd, запустив `/usr/lib/vitastor/mon/make-etcd`.\
|
||||
Либо, если вы установили Vitastor в Docker, запустите `systemctl start vitastor-host; docker exec vitastor make-etcd`.
|
||||
- Запустите etcd и мониторы: `systemctl enable --now vitastor-etcd vitastor-mon`
|
||||
|
||||
## Настройте OSD
|
||||
|
||||
- Пропишите etcd_address и [osd_network](../config/network.ru.md#osd_network) в `/etc/vitastor/vitastor.conf`. Например:
|
||||
```
|
||||
{
|
||||
"etcd_address": ["10.200.1.10:2379","10.200.1.11:2379","10.200.1.12:2379"],
|
||||
"osd_network": "10.200.1.0/24"
|
||||
}
|
||||
```
|
||||
- Создайте/скопируйте с узлов с мониторами файл конфигурации `/etc/vitastor/vitastor.conf`.
|
||||
- Инициализуйте OSD:
|
||||
- Только SSD или только HDD: `vitastor-disk prepare /dev/sdXXX [/dev/sdYYY ...]`.
|
||||
Если вы используете десктопные SSD без конденсаторов, добавьте опцию `--disable_data_fsync off`,
|
||||
|
||||
@@ -0,0 +1,657 @@
|
||||
[Documentation](../../README.md#documentation) → Introduction → Security in Vitastor
|
||||
|
||||
-----
|
||||
|
||||
[Читать на русском](security.ru.md)
|
||||
|
||||
# Security in Vitastor
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Quick setup](#quick-setup)
|
||||
- Principles of operation
|
||||
- [etcd transport encryption (TLS)](#etcd-transport-encryption-tls)
|
||||
- [OSD transport encryption (AES-GCM)](#osd-transport-encryption-aes-gcm)
|
||||
- [End-to-end image data encryption (AES-XTS)](#end-to-end-image-data-encryption-aes-xts)
|
||||
- [Certificate-based authentication](#certificate-based-authentication)
|
||||
- [Users and access rights](#users-and-access-rights)
|
||||
- [etcd privileges](#etcd-privileges)
|
||||
- Manual setup
|
||||
- [Configuring OSD transport encryption](#configuring-osd-transport-encryption)
|
||||
- etcd/Antietcd setup options
|
||||
- [Mon with embedded Antietcd](#mon-with-embedded-antietcd)
|
||||
- [Mon as an Etcd proxy](#mon-as-an-etcd-proxy)
|
||||
- [Mon with a separate Antietcd Proxy](#mon-with-a-separate-antietcd-proxy)
|
||||
- [Standalone Antietcd without etcd](#standalone-antietcd-without-etcd)
|
||||
- [Vault/OpenBao setup](#vaultopenbao-setup)
|
||||
- [Vault setup example](#vault-setup-example)
|
||||
- Lists of allowed operations
|
||||
- [etcd data access rights](#etcd-data-access-rights)
|
||||
- [OSD data access rights](#osd-data-access-rights)
|
||||
- [API access rights](#api-access-rights)
|
||||
- [Encryption performance](#encryption-performance)
|
||||
|
||||
## Overview
|
||||
|
||||
Starting from version 3.1.0, Vitastor provides full data protection:
|
||||
control plane protection (etcd), data plane protection (OSDs), and end-to-end data encryption.
|
||||
|
||||
- Control plane protection:
|
||||
- etcd transport encryption (TLS)
|
||||
- Authentication via client TLS (X.509) certificates
|
||||
- Access control of clients to etcd data
|
||||
- Data plane protection:
|
||||
- Full AES-GCM encryption of OSD transport (similar to TLS, but faster)
|
||||
- Alternatively, AES-GCM encryption of just operation headers with data checksums using a secret "salt"
|
||||
- Authentication via client TLS (X.509) certificates
|
||||
- Access control of clients on the OSD side
|
||||
- End-to-end encryption:
|
||||
- Data is encrypted using AES-XTS on the client side, the Vitastor cluster has no access to plaintext data
|
||||
- AES-XTS keys can be stored in etcd or in an external Vault/OpenBao
|
||||
|
||||
All features are optional and disabled in the simplest configuration. By default, only
|
||||
transport-level data checksums ([proto_checksums](../config/security.en.md#proto_checksums)=payload)
|
||||
are enabled for clients that support them (>= 3.1.0). For older clients, connections
|
||||
without data checksums are allowed by default ([force_proto_checksums](../config/security.en.md#force_proto_checksums) is empty).
|
||||
|
||||
For a quick setup, jump to the [Quick setup](#quick-setup) section.
|
||||
|
||||
Descriptions of all security-related parameters can be found [here](../config/security.en.md).
|
||||
|
||||
## Quick setup
|
||||
|
||||
For a quick setup, use the `/usr/lib/vitastor/mon/make-etcd` script:
|
||||
|
||||
1. Log in to the node where the first monitor and etcd will be located.
|
||||
2. Create `/etc/vitastor/vitastor.conf` with minimal parameters: etcd_address,
|
||||
osd_network and, if you want to enable privileges, use_perms (note `https://`
|
||||
in etcd addresses):
|
||||
```
|
||||
{
|
||||
"etcd_address": ["https://10.0.0.10:2379","https://10.0.0.11:2379","https://10.0.0.12:2379"],
|
||||
"osd_network": "10.0.0.0/24",
|
||||
"use_perms": true
|
||||
}
|
||||
```
|
||||
3. Run `/usr/lib/vitastor/mon/make-etcd` without parameters or with the `--antietcd-only`
|
||||
parameter if you want to initialize the cluster with Antietcd only, without etcd.
|
||||
4. The script will generate all necessary certificates and offer to copy them to the other
|
||||
monitor nodes (agree!).
|
||||
5. Log in to all other monitor nodes and repeat the `/usr/lib/vitastor/mon/make-etcd` call there.
|
||||
6. If you also have nodes with OSDs only (without monitors), run the following command to
|
||||
copy only the required configuration to these nodes:
|
||||
```
|
||||
/usr/lib/vitastor/mon/make-etcd --copy-to-osd osdnode1,osdnode2,...
|
||||
```
|
||||
|
||||
After that, you can proceed with OSD initialization.
|
||||
|
||||
If you want to understand the setup in more detail, read the [Principles of operation](#principles-of-operation)
|
||||
and [Manual setup](#manual-setup) sections below.
|
||||
|
||||
## Principles of operation
|
||||
|
||||
### etcd transport encryption (TLS)
|
||||
|
||||
Possible setups:
|
||||
- Without encryption (http)
|
||||
- With encryption (https)
|
||||
- With encryption and client certificate authentication. Either the same certificate
|
||||
used for authentication on the OSD side (`cert`+`pkey` / `osd_cert`+`osd_pkey`)
|
||||
is used, or a separately specified certificate (`etcd_client_cert`+`etcd_client_key`).
|
||||
|
||||
### OSD transport encryption (AES-GCM)
|
||||
|
||||
Possible setups:
|
||||
- Unencrypted transport without checksums: `proto_checksums=none`.
|
||||
- Unencrypted transport with data checksums: `proto_checksums=payload` (may be omitted,
|
||||
this is the default value). It's allowed to disable checksums on the client side, or
|
||||
use an older client that does not support checksums. If you want to block connections
|
||||
from clients without checksums, use the option `force_proto_checksums=payload`.
|
||||
- Header-only encryption with data checksums: activated when the options
|
||||
`cert`, `pkey`, `osd_ca` are set on the client side and `osd_cert`, `osd_pkey`, `osd_ca`, `client_ca`
|
||||
on the OSD side, with `proto_checksums=payload`. In this mode, disabling checksums on the client
|
||||
side is forbidden by default, i.e. `force_proto_checksums=payload` is used.
|
||||
- Full transport encryption of all traffic: same as the previous option, but with `proto_checksums=gcm`.
|
||||
In this case, clients are by default allowed to downgrade to checksums only, but this
|
||||
can also be forbidden via `force_proto_checksums=gcm`. This is the slowest setup and
|
||||
it's only recommended for insecure (public) networks. In particular, full traffic
|
||||
encryption together with end-to-end AES-XTS image encryption encrypts data twice.
|
||||
|
||||
Encryption uses the AES-256-GCM algorithm and a custom simplified key exchange protocol,
|
||||
fully analogous to TLS 1.3 ECDHE.
|
||||
|
||||
### End-to-end image data encryption (AES-XTS)
|
||||
|
||||
The Vitastor client supports encrypting each image's data with its own key. In this case,
|
||||
data is encrypted by the client before sending it to OSDs and OSDs can't see it in plain.
|
||||
The encryption key can be changed when cloning/creating image snapshots. For example,
|
||||
you can make a base VM image (say, Debian Linux) unencrypted, but have encrypted client VM
|
||||
images inheriting from it.
|
||||
|
||||
Image encryption keys can be stored in etcd or in an external Vault. In the latter case,
|
||||
etcd only stores key IDs and Vitastor cluster can't decrypt the data at all. To use
|
||||
Vault, create an image with the `--enc_key vault:ID` option, specify vault_url and vault_ca
|
||||
options in the configuration, create accounts for all clients in Vault, and grant them access
|
||||
to the required v1 secrets.
|
||||
|
||||
Once again, if AES-XTS is used together with full traffic encryption (`proto_checksums=gcm`),
|
||||
image data is encrypted twice — first with AES-XTS, and then with AES-GCM. Use it only if
|
||||
you are completely paranoid :-).
|
||||
|
||||
### Certificate-based authentication
|
||||
|
||||
When encryption is enabled, Vitastor clients, OSDs, and monitors authenticate via certificates
|
||||
for both etcd (Antietcd) and OSD connections.
|
||||
|
||||
Separate certificates must be used for OSDs and monitors — either self-signed, or signed
|
||||
by separate CAs (`osd_ca` and `mon_ca`). All OSDs can use the same certificate, and all
|
||||
monitors can also use the same certificate, since the privileges of different OSDs or
|
||||
different monitors do not differ (theoretically, one could differentiate OSD certificates
|
||||
by pool, but there has been no need for this so far).
|
||||
|
||||
Also, a monitor certificate may not be needed at all if Antietcd is embedded into the monitor
|
||||
itself. In this case, the monitor already has access to all etcd data directly in memory.
|
||||
|
||||
### Users and access rights
|
||||
|
||||
When transport encryption is disabled, Vitastor operates without access control, i.e.,
|
||||
any cluster client has full access to both the management layer and the data layer. This
|
||||
option is suitable for dedicated trusted storage networks.
|
||||
|
||||
When OSD transport encryption is enabled (at least for headers), you can enable access
|
||||
rights by turning on the `use_perms=true` option. When this option is enabled, each user
|
||||
can perform only the operations that they are permitted, and even OSDs and monitors are
|
||||
also forbidden from performing "unnecessary" operations.
|
||||
|
||||
Each user (or administrator) must have their own certificate signed by a common root
|
||||
certificate for clients (`client_ca`), with a Common Name equal to the user name.
|
||||
Privilege settings are stored in etcd. OSDs and monitors don't need user accounts;
|
||||
they authenticate via separate certificates.
|
||||
|
||||
User privileges are stored in etcd data under the keys `/vitastor/config/user/<name>`.
|
||||
The following is defined per user in this key:
|
||||
- Type:
|
||||
- Client (`type=client` or omitted) — can only read and modify explicitly permitted images.
|
||||
- Administrator (`type=admin`) — can read and modify all images, and also administer the
|
||||
cluster: view overall statistics and status, create and delete OSDs, etc.
|
||||
- List of group names the user is a member of.
|
||||
|
||||
Images have the following properties:
|
||||
- Owner (owner) — the user name that is allowed to both read and modify the image
|
||||
- Owner group (owner_group) — the owner group name
|
||||
- Reader group (reader_group) — the name of the group of users allowed to read the image
|
||||
|
||||
And there is also a property on the pool:
|
||||
- Creator group (creator_group) — the name of the group of users allowed to create images in the pool
|
||||
|
||||
For the list of allowed operations on image data on the OSD side, see the
|
||||
[OSD data access rights](#osd-data-access-rights) section.
|
||||
|
||||
### etcd privileges
|
||||
|
||||
etcd privileges are implemented through Antietcd in all modes of operation.
|
||||
|
||||
Built-in etcd privileges are not supported due to numerous inconveniences:
|
||||
- Certificate-based authentication does not work at all in etcd's REST interface,
|
||||
- Privileges are stored separately from k/v data and cannot participate in transactions,
|
||||
- Only the administrator (root) can change privileges,
|
||||
- There is no support for filtering range read responses by privileges.
|
||||
|
||||
If etcd is used, Antietcd acts as a filtering proxy and can be embedded in the Vitastor
|
||||
monitor or run separately. In this case, etcd must allow incoming connections only from
|
||||
Antietcd, and all other components must connect to Antietcd.
|
||||
|
||||
If Antietcd runs as a part of the Vitastor monitor, it is sufficient to enable the option
|
||||
`use_perms=true` and set the required certificates. If Antietcd is run separately, privileges
|
||||
have to be enabled separately using Antietcd options. For more details on the setup, see
|
||||
the [etcd/Antietcd setup options](#etcdantietcd-setup-options) section.
|
||||
|
||||
For the list of allowed operations with etcd data, see the
|
||||
[etcd data access rights](#etcd-data-access-rights) section.
|
||||
|
||||
## Manual setup
|
||||
|
||||
### Configuring OSD transport encryption
|
||||
|
||||
You need 2 certificates: one for OSDs and one for signing all client certificates.
|
||||
For OSDs, you can use a self-signed certificate (osd_ca.crt) or a separate certificate (osd.crt)
|
||||
signed by a trusted osd_ca.crt certificate. For clients, you must use separate certificates
|
||||
signed by a common trusted (client_ca.crt).
|
||||
|
||||
Add to the Vitastor configuration on OSD servers:
|
||||
- use_perms: true
|
||||
- osd_ca: osd_ca.crt
|
||||
- client_ca: client_ca.crt
|
||||
- osd_cert: osd_ca.crt
|
||||
- osd_pkey: osd_ca.key
|
||||
|
||||
On the client side:
|
||||
- use_perms: true
|
||||
- cert: client.crt
|
||||
- pkey: client.key
|
||||
|
||||
### etcd/Antietcd setup options
|
||||
|
||||
The following configuration options are available:
|
||||
|
||||
#### Mon with embedded Antietcd
|
||||
|
||||
The simplest option. You need 1 certificate for Antietcd (antietcd.crt), plus root
|
||||
certificates for OSDs and clients.
|
||||
|
||||
Vitastor settings (`/etc/vitastor/vitastor.conf`):
|
||||
- etcd_address: [ "http://mon1:2379", ... ] (addresses of your monitors with port 2379)
|
||||
- use_perms: true
|
||||
- use_antietcd: true
|
||||
- antietcd_cert: antietcd.crt
|
||||
- antietcd_key: antietcd.key
|
||||
- etcd_ca: antietcd.crt
|
||||
- osd_ca: osd_ca.crt
|
||||
- client_ca: client_ca.crt
|
||||
|
||||
#### Mon as an Etcd proxy
|
||||
|
||||
If you want to enable privileges, but stay on etcd, you can use etcd proxy mode.
|
||||
|
||||
You will need 2 separate certificates: one for etcd (etcd.crt) and one for antietcd (antietcd.crt).
|
||||
The etcd client port must be different from the standard 2379 — for example, you can pick 2381.
|
||||
OSD and client certificates are also needed.
|
||||
|
||||
Vitastor settings:
|
||||
- etcd_address: [ "http://mon1:2379", ... ] (addresses of your monitors with port 2379)
|
||||
- use_perms: true
|
||||
- use_antietcd: true
|
||||
- etcd_proxy:
|
||||
```
|
||||
{
|
||||
"urls": [ "http://mon1:2381", ... ], // addresses of your etcd with port 2381
|
||||
"cert": "antietcd.crt",
|
||||
"key": "antietcd.key",
|
||||
"ca": "etcd.crt"
|
||||
}
|
||||
```
|
||||
- antietcd_cert: antietcd.crt
|
||||
- antietcd_key: antietcd.key
|
||||
- etcd_ca: antietcd.crt
|
||||
- osd_ca: osd_ca.crt
|
||||
- client_ca: client_ca.crt
|
||||
|
||||
etcd command-line options:
|
||||
```
|
||||
--advertise-client-urls=https://<ADDRESS>:2381 --listen-client-urls=https://<ADDRESS>:2381 \
|
||||
--client-cert-auth --cert-file=etcd.crt --key-file=etcd.key --trusted-ca-file=antietcd.crt \
|
||||
--peer-client-cert-auth --peer-cert-file=etcd.crt --peer-key-file=etcd.key --peer-trusted-ca-file=etcd.crt
|
||||
```
|
||||
|
||||
#### Mon with a separate Antietcd Proxy
|
||||
|
||||
If in addition to the previous option you want to offload Antietcd from the Vitastor monitor's
|
||||
tasks, you can run it separately.
|
||||
|
||||
Similar to the previous option, 2 certificates are needed: one for etcd and one for antietcd,
|
||||
plus separate certificates for clients, OSDs, and monitors will be needed.
|
||||
|
||||
Vitastor settings:
|
||||
- etcd_address: [ "http://mon1:2379", ... ] (addresses of your monitors with port 2379)
|
||||
- use_perms: true
|
||||
- use_antietcd: false
|
||||
- etcd_ca: antietcd.crt
|
||||
- osd_ca: osd_ca.crt
|
||||
- client_ca: client_ca.crt
|
||||
- mon_etcd_client_cert: mon_ca.crt
|
||||
- mon_etcd_client_key: mon_ca.key
|
||||
|
||||
Antietcd command-line options:
|
||||
```
|
||||
--port 2379 \
|
||||
--client_cert_auth 1 --auth_filter vitastor_auth_filter.js --etcd_proxy url1,url2,... \
|
||||
--cert antietcd.crt --key antietcd.key --ca client_ca.crt --osd_ca osd_ca.crt --mon_ca mon_ca.crt \
|
||||
--etcd_cert antietcd.crt --etcd_key antietcd.key --etcd_ca etcd.crt
|
||||
```
|
||||
|
||||
etcd command-line options (same as in the previous option):
|
||||
```
|
||||
--advertise-client-urls=https://<ADDRESS>:2381 --listen-client-urls=https://<ADDRESS>:2381 \
|
||||
--client-cert-auth --cert-file=etcd.crt --key-file=etcd.key --trusted-ca-file=antietcd.crt \
|
||||
--peer-client-cert-auth --peer-cert-file=etcd.crt --peer-key-file=etcd.key --peer-trusted-ca-file=etcd.crt
|
||||
```
|
||||
|
||||
#### Standalone Antietcd without etcd
|
||||
|
||||
Same as the previous option, but etcd and its certificate are not needed:
|
||||
|
||||
Vitastor settings (same as in the previous option):
|
||||
- etcd_address: [ "http://mon1:2379", ... ] (addresses of your monitors with port 2379)
|
||||
- use_perms: true
|
||||
- use_antietcd: false
|
||||
- etcd_ca: antietcd.crt
|
||||
- osd_ca: osd_ca.crt
|
||||
- client_ca: client_ca.crt
|
||||
- mon_etcd_client_cert: mon_ca.crt
|
||||
- mon_etcd_client_key: mon_ca.key
|
||||
|
||||
Antietcd command-line options:
|
||||
```
|
||||
--port 2379 \
|
||||
--client_cert_auth 1 --auth_filter vitastor_auth_filter.js \
|
||||
--persist_filter vitastor_persist_filter.js \
|
||||
--cert antietcd.crt --key antietcd.key --ca client_ca.crt --osd_ca osd_ca.crt --mon_ca mon_ca.crt
|
||||
```
|
||||
|
||||
### Vault/OpenBao setup
|
||||
|
||||
To use Vault, each client that needs to get image keys from Vault needs a Vault account.
|
||||
Vitastor only supports client certificate-based authentication, so all client certificates
|
||||
(`cert`+`pkey`) must be registered in Vault, and they must be granted access to the
|
||||
corresponding secrets (v1 secrets API is supported).
|
||||
|
||||
The required format of a Vault secret is a single `key` field as a hexadecimal string.
|
||||
The AES-256-XTS algorithm is used, so the key length is 64 bytes, i.e., the string must
|
||||
consist of 128 hexadecimal digits.
|
||||
|
||||
To connect to Vault, set the following settings in Vitastor.conf:
|
||||
- `vault_url` — Vault address (e.g., `https://vault:8200`)
|
||||
- `vault_ca` — Vault's own certificate
|
||||
|
||||
After that, if you create an image (`vitastor-cli create`) with the option `--enc_key vault:<ID>`,
|
||||
Vitastor clients will first contact Vault to obtain a token at `/v1/auth/cert/login`,
|
||||
and then request the actual secret from Vault at `/v1/secret/<ID>`.
|
||||
|
||||
#### Vault setup example
|
||||
|
||||
Step-by-step instructions for setting up a test Vault using OpenBao as an example:
|
||||
|
||||
1. If TLS is not yet configured, generate a self-signed TLS certificate for Vault:
|
||||
```
|
||||
openssl req -days 3650 -x509 -addext basicConstraints=critical,CA:TRUE,pathlen:1 --addext subjectAltName=DNS:vault \
|
||||
-new -newkey rsa:4096 -nodes -keyout /etc/openbao/vault.key -out /etc/openbao/vault.crt
|
||||
```
|
||||
Configure it in `/etc/openbao/openbao.hcl`:
|
||||
```
|
||||
listener "tcp" {
|
||||
address = "0.0.0.0:8200"
|
||||
tls_cert_file = "/etc/openbao/vault.crt"
|
||||
tls_key_file = "/etc/openbao/vault.key"
|
||||
}
|
||||
```
|
||||
And restart OpenBao (`systemctl restart openbao`).
|
||||
2. Copy Vault's TLS certificate for Vitastor:
|
||||
```
|
||||
cp /etc/openbao/vault.crt /etc/vitastor/vault.crt
|
||||
```
|
||||
Transfer it to all client nodes and specify it in `/etc/vitastor/vitastor.conf`:
|
||||
```
|
||||
{
|
||||
...
|
||||
"vault_url": "http://vault:8200",
|
||||
"vault_ca": "/etc/vitastor/vault.crt"
|
||||
}
|
||||
```
|
||||
3. Check Vault status:
|
||||
```
|
||||
bao status -ca-cert /etc/openbao/vault.crt -address=https://vault:8200
|
||||
```
|
||||
4. Initialize Vault in test mode from 1 node (with 1 key share):
|
||||
```
|
||||
bao operator init -n 1 -t 1 -ca-cert /etc/openbao/vault.crt -address=https://vault:8200
|
||||
```
|
||||
5. Unseal Vault:
|
||||
```
|
||||
bao operator unseal -ca-cert /etc/openbao/vault.crt -address=https://vault:8200
|
||||
```
|
||||
6. Enable certificate-based authentication:
|
||||
```
|
||||
bao auth enable -ca-cert /etc/openbao/vault.crt -address=https://vault:8200 cert
|
||||
```
|
||||
7. Enable v1 secrets:
|
||||
```
|
||||
bao secrets enable -ca-cert /etc/openbao/vault.crt -address=https://vault:8200 -path=secret kv-v1
|
||||
```
|
||||
8. Create a test secret:
|
||||
```
|
||||
bao kv put -ca-cert /etc/openbao/vault.crt -address=https://vault:8200 secret/vitastor/testimg3 key=$(openssl rand -hex 64)
|
||||
```
|
||||
9. Generate a signed certificate for a Vitastor user (on a machine where you have `client_ca.crt` and `client_ca.key`):
|
||||
```
|
||||
openssl req -subj '/CN=testimg3' -nodes -new -keyout testimg3.key -out testimg3.csr
|
||||
openssl x509 -req -days 3650 -CA client_ca.crt -CAkey client_ca.key -CAcreateserial -in testimg3.csr -out testimg3.crt
|
||||
rm testimg3.csr
|
||||
```
|
||||
10. Create a user in Vault and grant it access to the secret:
|
||||
```
|
||||
cat >testimg3.policy <<EOF
|
||||
path "/secret/vitastor/testimg3" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
EOF
|
||||
|
||||
bao policy write -ca-cert /etc/openbao/vault.crt -address=https://vault:8200 testimg3 testimg3.policy
|
||||
|
||||
bao write -ca-cert /etc/openbao/vault.crt -address=https://vault:8200 auth/cert/certs/testimg3 \
|
||||
certificate=@testimg3.crt display_name=testimg3 token_ttl=24h token_policies=testimg3
|
||||
```
|
||||
11. Test access to the secret:
|
||||
```
|
||||
curl --cacert /etc/vitastor/vault.crt --cert testimg3.crt --key testimg3.key \
|
||||
--json '{}' https://vault:8200/v1/auth/cert/login
|
||||
```
|
||||
A token will be printed, substitute it into the following request:
|
||||
```
|
||||
curl --cacert /etc/vitastor/vault.crt --cert testimg3.crt --key testimg3.key \
|
||||
-H 'X-Vault-Token: <RECEIVED TOKEN>' https://vault:8200/v1/secret/vitastor/testimg3
|
||||
```
|
||||
12. Create an image in Vitastor with the given secret (as an administrator or someone who
|
||||
has the right to create images in your pool):
|
||||
```
|
||||
vitastor-cli create -s 100G --enc_key vault:vitastor/testimg3 --owner testimg3 testimg3
|
||||
```
|
||||
13. Test access to the image as user testimg3:
|
||||
```
|
||||
vitastor-cli --cert testimg3.crt --pkey testimg3.key dd if=/dev/urandom oimg=testimg3 bs=1M count=100
|
||||
```
|
||||
|
||||
## Lists of allowed operations
|
||||
|
||||
### etcd data access rights
|
||||
|
||||
Below, all key names are given without the common prefix `/vitastor`.
|
||||
|
||||
Allowed operations with keys in Antietcd for clients (`type=client`):
|
||||
- Read-only:
|
||||
- Always allowed:
|
||||
- `/config/global`
|
||||
- `/config/node_placement`
|
||||
- `/config/pools`
|
||||
- `/pg/config`
|
||||
- `/osd/state/*`
|
||||
- `/pg/state/*`
|
||||
- `/index/maxid/*`
|
||||
- For images [readable by the user](#users-and-access-rights):
|
||||
- `/config/inode/*`
|
||||
- `/index/image/*`
|
||||
- `/inode/stats/*`
|
||||
- Read and write:
|
||||
- For pools in which the user can create images:
|
||||
- `/index/maxid/*`
|
||||
- For images owned by the user:
|
||||
- `/config/inode/*`
|
||||
- `/index/image/*`
|
||||
|
||||
Allowed operations with keys in Antietcd for administrators (`type=admin`):
|
||||
- Read:
|
||||
- `/stats`
|
||||
- `/mon/*`
|
||||
- `/pg/*`
|
||||
- `/pgstats/*`
|
||||
- `/inode/stats/*`
|
||||
- `/pool/stats/*`
|
||||
- Read and write:
|
||||
- `/config/*`
|
||||
- `/osd/*`
|
||||
- `/index/*`
|
||||
- `/pg/history/*`
|
||||
|
||||
Allowed operations with keys in etcd for OSDs:
|
||||
- Read:
|
||||
- `/pg/config`
|
||||
- `/config/*`
|
||||
- Read and write:
|
||||
- `/osd/*`
|
||||
- `/pg/state/*`
|
||||
- `/pg/history/*`
|
||||
- `/pgstats/*`
|
||||
|
||||
Allowed operations with keys in etcd for monitors:
|
||||
- Read:
|
||||
- `/config/*`
|
||||
- `/osd/*`
|
||||
- `/pgstats/*`
|
||||
- Read and write:
|
||||
- `/pg/config`
|
||||
- `/stats`
|
||||
- `/history/last_clean_pgs`
|
||||
- `/mon/*`
|
||||
- `/pg/history/*`
|
||||
- `/inode/stats/*`
|
||||
- `/pool/stats/*`
|
||||
|
||||
### OSD data access rights
|
||||
|
||||
When the `use_perms` option and encryption are enabled, OSDs authenticate clients via
|
||||
certificates and allow each client only what is allowed by the access control model.
|
||||
|
||||
Client operations:
|
||||
- READ — allowed for images the user has read access to.
|
||||
- WRITE, DELETE, SCRUB — allowed for images the user has write access to.
|
||||
- SYNC — the operation is not tied to an image and is always allowed.
|
||||
- DESCRIBE — the operation is allowed only for administrators (used by the commands
|
||||
`vitastor-cli describe` and `fix`).
|
||||
- PING — the operation is always allowed.
|
||||
- SHOW_CONFIG — the operation is always allowed, however, if the client presents
|
||||
itself as an OSD in it, then it is verified that it uses a certificate signed by `osd_ca`.
|
||||
- SEC_LIST (listing) — allowed for other OSDs and administrators with any parameters,
|
||||
and for regular clients only allowed for requests limited to an image the user has
|
||||
read access to.
|
||||
|
||||
Cluster operations — allowed only for other OSDs:
|
||||
- SEC_READ
|
||||
- SEC_WRITE
|
||||
- SEC_WRITE_STABLE
|
||||
- SEC_SYNC
|
||||
- SEC_STABILIZE
|
||||
- SEC_ROLLBACK
|
||||
- SEC_DELETE
|
||||
- SEC_READ_BMP
|
||||
- SEC_LOCK
|
||||
|
||||
### API access rights
|
||||
|
||||
[vitastor-cli serve](../usage/cli.en.md#serve) also supports client authentication
|
||||
via certificates. Only certificates signed by `client_ca` are accepted. A separate
|
||||
certificate `server_cert` with the key `server_pkey` is used as the server certificate.
|
||||
|
||||
For `vitastor-cli serve` to work correctly, it itself must use a certificate
|
||||
(`cert`+`pkey`) of a user with administrator rights (`type=admin`) to access Vitastor.
|
||||
|
||||
Regular clients, when accessing the API, are only allowed API operations on images
|
||||
available to them either for reading (for reads) or for writing (for modification).
|
||||
All other API calls are allowed only for administrators.
|
||||
|
||||
List of allowed API operations:
|
||||
|
||||
Clients (users with `type=client`) are allowed the following operations:
|
||||
- image/list — for images the user can read.
|
||||
- image/create — for pools in which the user is allowed to create images, or for
|
||||
creating snapshots of images owned by the user.
|
||||
- image/delete, image/flatten, image/modify — for images owned by the user.
|
||||
|
||||
All other operations are allowed only for administrators (`type=admin`).
|
||||
|
||||
## Encryption performance
|
||||
|
||||
You may wonder — how fast is all this wonderful encryption?
|
||||
|
||||
The answer is — it depends heavily on the CPU. On modern processors (with AVX512 with VAES
|
||||
support) it is very fast — AES encryption speed can reach 10-20 GB/s and above. This
|
||||
primarily concerns the CPU of client machines, because end-to-end encryption is performed
|
||||
entirely on the client, and client uses its signle thread for transport encryption too,
|
||||
while there are many OSDs on the server side, and it is easier to add resources there.
|
||||
|
||||
On older processors, the speed is noticeably worse — for example, on a Xeon E5 v4 it is
|
||||
only 3 GB/s.
|
||||
|
||||
You can evaluate the performance of your processors using the `vitastor-cli cpubench` command.
|
||||
|
||||
Example output (💪 AMD EPYC 9575F):
|
||||
|
||||
```
|
||||
$ vitastor-cli cpubench
|
||||
Vitastor transport encryption benchmark (AES-256-GCM, AES-256-XTS and xxhash3)
|
||||
|
||||
Warmup...
|
||||
|
||||
No transport encryption, data checksums enabled, e2e unencrypted image
|
||||
xxhash3 1 M block... 209000 iterations in 2001 ms = 104447.78 MB/s
|
||||
xxhash3 4 K block... 37000000 iterations in 2022 ms = 71479.35 MB/s
|
||||
|
||||
Header encryption with payload checksums, e2e unencrypted image
|
||||
AES-256-GCM encrypt header + xxhash3 1 M block... 210000 iterations in 2015 ms = 104218.36 MB/s
|
||||
AES-256-GCM encrypt header + xxhash3 4 K block... 26000000 iterations in 2073 ms = 48993.01 MB/s
|
||||
|
||||
Full transport encryption, e2e unencrypted image
|
||||
AES-256-GCM encrypt header and 1 M block... 54000 iterations in 2000 ms = 27000.00 MB/s
|
||||
AES-256-GCM encrypt header and 4 K block... 11700000 iterations in 2014 ms = 22692.71 MB/s
|
||||
|
||||
No transport encryption, no checksums, e2e encrypted image
|
||||
AES-256-XTS encrypt 1 M block... 50000 iterations in 2039 ms = 24521.82 MB/s
|
||||
AES-256-XTS encrypt 4 K block... 12600000 iterations in 2009 ms = 24499.13 MB/s
|
||||
|
||||
No transport encryption, e2e encrypted image, data checksums enabled
|
||||
AES-256-XTS encrypt + xxhash3 1 M block... 40000 iterations in 2013 ms = 19870.84 MB/s
|
||||
AES-256-XTS encrypt + xxhash3 4 K block... 10200000 iterations in 2011 ms = 19812.90 MB/s
|
||||
|
||||
Header encryption with payload checksums, e2e encrypted image
|
||||
AES-256-GCM encrypt header + AES-256-XTS encrypt + xxhash3 1 M block... 40000 iterations in 2014 ms = 19860.97 MB/s
|
||||
AES-256-GCM encrypt header + AES-256-XTS encrypt + xxhash3 4 K block... 8700000 iterations in 2011 ms = 16899.24 MB/s
|
||||
|
||||
Full transport encryption, e2e encrypted image
|
||||
AES-256-XTS + AES-256-GCM encrypt 1 M block... 26000 iterations in 2062 ms = 12609.12 MB/s
|
||||
AES-256-XTS + AES-256-GCM encrypt 4 K block... 6300000 iterations in 2006 ms = 12267.88 MB/s
|
||||
```
|
||||
|
||||
And here is Xeon E5-2680v4:
|
||||
|
||||
```
|
||||
$ vitastor-cli cpubench
|
||||
Vitastor transport encryption benchmark (AES-256-GCM, AES-256-XTS and xxhash3)
|
||||
|
||||
Warmup...
|
||||
|
||||
No transport encryption, data checksums enabled, e2e unencrypted image
|
||||
xxhash3 1 M block... 62000 iterations in 2021 ms = 30677.88 MB/s
|
||||
xxhash3 4 K block... 12400000 iterations in 2006 ms = 24146.31 MB/s
|
||||
|
||||
Header encryption with payload checksums, e2e unencrypted image
|
||||
AES-256-GCM encrypt header + xxhash3 1 M block... 62000 iterations in 2027 ms = 30587.07 MB/s
|
||||
AES-256-GCM encrypt header + xxhash3 4 K block... 6800000 iterations in 2011 ms = 13208.60 MB/s
|
||||
|
||||
Full transport encryption, e2e unencrypted image
|
||||
AES-256-GCM encrypt header and 1 M block... 7000 iterations in 2317 ms = 3021.15 MB/s
|
||||
AES-256-GCM encrypt header and 4 K block... 1500000 iterations in 2102 ms = 2787.52 MB/s
|
||||
|
||||
No transport encryption, no checksums, e2e encrypted image
|
||||
AES-256-XTS encrypt 1 M block... 7000 iterations in 2317 ms = 3021.15 MB/s
|
||||
AES-256-XTS encrypt 4 K block... 1600000 iterations in 2088 ms = 2993.30 MB/s
|
||||
|
||||
No transport encryption, e2e encrypted image, data checksums enabled
|
||||
AES-256-XTS encrypt + xxhash3 1 M block... 6000 iterations in 2188 ms = 2742.23 MB/s
|
||||
AES-256-XTS encrypt + xxhash3 4 K block... 1400000 iterations in 2053 ms = 2663.78 MB/s
|
||||
|
||||
Header encryption with payload checksums, e2e encrypted image
|
||||
AES-256-GCM encrypt header + AES-256-XTS encrypt + xxhash3 1 M block... 6000 iterations in 2190 ms = 2739.73 MB/s
|
||||
AES-256-GCM encrypt header + AES-256-XTS encrypt + xxhash3 4 K block... 1300000 iterations in 2101 ms = 2417.00 MB/s
|
||||
|
||||
Full transport encryption, e2e encrypted image
|
||||
AES-256-XTS + AES-256-GCM encrypt 1 M block... 4000 iterations in 2666 ms = 1500.38 MB/s
|
||||
AES-256-XTS + AES-256-GCM encrypt 4 K block... 800000 iterations in 2113 ms = 1478.94 MB/s
|
||||
```
|
||||
@@ -0,0 +1,662 @@
|
||||
[Документация](../../README-ru.md#документация) → Введение → Безопасность в Vitastor
|
||||
|
||||
-----
|
||||
|
||||
[Read in English](security.en.md)
|
||||
|
||||
# Безопасность в Vitastor
|
||||
|
||||
- [Обзор](#обзор)
|
||||
- [Быстрая настройка](#быстрая-настройка)
|
||||
- Принципы работы
|
||||
- [Шифрование соединений с etcd (TLS)](#шифрование-соединений-с-etcd-tls)
|
||||
- [Шифрование соединений с OSD (AES-GCM)](#шифрование-соединений-с-osd-aes-gcm)
|
||||
- [Сквозное шифрование данных образов (AES-XTS)](#сквозное-шифрование-данных-образов-aes-xts)
|
||||
- [Аутентификация по сертификатам](#аутентификация-по-сертификатам)
|
||||
- [Пользователи и права доступа](#пользователи-и-права-доступа)
|
||||
- [Привилегии etcd](#привилегии-etcd)
|
||||
- Ручная настройка
|
||||
- [Настройка шифрования соединений OSD](#настройка-шифрования-соединений-osd)
|
||||
- Варианты настройки etcd/Antietcd
|
||||
- [Mon со встроенным Antietcd](#mon-со-встроенным-antietcd)
|
||||
- [Mon в роли Etcd proxy](#mon-в-роли-etcd-proxy)
|
||||
- [Mon с отдельным Antietcd Proxy](#mon-с-отдельным-antietcd-proxy)
|
||||
- [Отдельный Antietcd без etcd](#отдельный-antietcd-без-etcd)
|
||||
- [Настройка Vault/OpenBao](#настройка-vaultopenbao)
|
||||
- [Пример настройки Vault](#пример-настройки-vault)
|
||||
- Списки разрешённых операций
|
||||
- [Права доступа к данным etcd](#права-доступа-к-данным-etcd)
|
||||
- [Права доступа к данным OSD](#права-доступа-к-данным-osd)
|
||||
- [Права доступа к API](#права-доступа-к-api)
|
||||
- [Производительность шифрования](#производительность-шифрования)
|
||||
|
||||
## Обзор
|
||||
|
||||
Начиная с версии 3.1.0, Vitastor предоставляет полную защиту данных: защиту слоя
|
||||
управления (etcd), защиту слоя данных (OSD) и сквозное шифрование данных.
|
||||
|
||||
- Защита слоя управления:
|
||||
- Шифрование соединений с etcd (TLS)
|
||||
- Аутентификация по клиентским TLS (X.509) сертификатам
|
||||
- Разграничение прав доступа клиентов к данным etcd
|
||||
- Защита слоя данных:
|
||||
- Либо полное AES-GCM шифрование соединений с OSD (аналогично TLS, но быстрее)
|
||||
- Либо шифрование AES-GCM только заголовков команд с контрольными суммами данных с секретной "солью"
|
||||
- Аутентификация по клиентским TLS (X.509) сертификатам
|
||||
- Разграничение прав доступа клиентов на стороне OSD
|
||||
- Сквозное шифрование:
|
||||
- Данные шифруются AES-XTS на стороне клиента, кластер Vitastor не имеет доступа к открытым данным
|
||||
- Ключи AES-XTS могут храниться в etcd или во внешнем Vault/OpenBao
|
||||
|
||||
Все функции опциональны и в простейшем варианте настройки выключены. По умолчанию включены
|
||||
только контрольные суммы данных на транспортном уровне ([proto_checksums](../config/security.ru.md#proto_checksums)=payload) для
|
||||
поддерживающих их клиентов (>= 3.1.0). Для более старых клиентов по умолчанию разрешены
|
||||
соединения без контрольных сумм данных ([force_proto_checksums](../config/security.ru.md#force_proto_checksums) пусто).
|
||||
|
||||
Для быстрой настройки перейдите к разделу [Быстрая настройка](#быстрая-настройка).
|
||||
|
||||
Описания всех параметров, связанных с безопасностью, читайте [здесь](../config/security.ru.md).
|
||||
|
||||
## Быстрая настройка
|
||||
|
||||
Для быстрой настройки используйте скрипт `/usr/lib/vitastor/mon/make-etcd`:
|
||||
|
||||
1. Зайдите на узел, на котором будет располагаться первый монитор и etcd.
|
||||
2. Создайте там минимальный `/etc/vitastor/vitastor.conf` с параметрами etcd_address,
|
||||
osd_network и, если хотите включить привилегии - use_perms (обратите внимание на `https://`
|
||||
в адресах etcd):
|
||||
```
|
||||
{
|
||||
"etcd_address": ["https://10.0.0.10:2379","https://10.0.0.11:2379","https://10.0.0.12:2379"],
|
||||
"osd_network": "10.0.0.0/24",
|
||||
"use_perms": true
|
||||
}
|
||||
```
|
||||
3. Запустите `/usr/lib/vitastor/mon/make-etcd` без параметров или с параметром `--antietcd-only`,
|
||||
если хотите инициализировать кластер только с Antietcd без etcd.
|
||||
4. Скрипт сгенерирует все необходимые сертификаты и предложит скопировать их на остальные узлы
|
||||
мониторов (соглашайтесь!).
|
||||
5. Зайдите на все остальные узлы мониторов и повторите там вызов `/usr/lib/vitastor/mon/make-etcd`.
|
||||
6. Если у вас будут узлы только с OSD без мониторов, выполните следующую команду, чтобы скопировать
|
||||
только нужную конфигурацию на эти узлы:
|
||||
```
|
||||
/usr/lib/vitastor/mon/make-etcd --copy-to-osd osdnode1,osdnode2,...
|
||||
```
|
||||
|
||||
После этого можете переходить к инициализации OSD.
|
||||
|
||||
Если хотите разобраться в настройке подробнее, читайте далее разделы [Принципы работы](#принципы-работы)
|
||||
и [Ручная настройка](#ручная-настройка).
|
||||
|
||||
## Принципы работы
|
||||
|
||||
### Шифрование соединений с etcd (TLS)
|
||||
|
||||
Варианты настройки:
|
||||
- Без шифрования (http)
|
||||
- С шифрованием (https)
|
||||
- С шифрованием и аутентификацией по клиентским сертификатам. Используется либо тот
|
||||
же сертификат, что используется для аутентификации на стороне OSD (`cert`+`pkey` / `osd_cert`+`osd_pkey`),
|
||||
либо отдельно указанный сертификат (`etcd_client_cert`+`etcd_client_key`)
|
||||
|
||||
### Шифрование соединений с OSD (AES-GCM)
|
||||
|
||||
Варианты настройки:
|
||||
- Без шифрования и без контрольных сумм: `proto_checksums=none`.
|
||||
- Без шифрования, с контрольными суммами данных: `proto_checksums=payload` (можно не указывать,
|
||||
т.к. это значение по умолчанию). При этом контрольные суммы можно отключить на стороне
|
||||
клиента либо использовать более старые версии клиента, не поддерживающие контрольные суммы.
|
||||
Если нужно запретить подключение клиентов без контрольных сумм, можно использовать опцию
|
||||
`force_proto_checksums=payload`.
|
||||
- С шифрованием заголовков и контрольными суммами данных: активируется при установленных опциях
|
||||
`cert`, `pkey`, `osd_ca` на стороне клиента и `osd_cert`, `osd_pkey`, `osd_ca`, `client_ca`
|
||||
на стороне OSD, при `proto_checksums=payload`. При этом по умолчанию запрещается
|
||||
отключение контрольных сумм на уровне клиента, то есть используется `force_proto_checksums=payload`.
|
||||
- С полным шифрованием всего трафика: аналогично прошлому варианту, но с `proto_checksums=gcm`.
|
||||
Клиенту при этом по умолчанию разрешается понизить уровень защиты до контрольных сумм, но
|
||||
это тоже можно запретить через `force_proto_checksums=gcm`. Данный вариант самый медленный и
|
||||
рекомендуется только для небезопасных (публичных) сетей. В том числе потому, что при использовании
|
||||
и полного шифрования трафика, и сквозного шифрования образов AES-XTS, данные шифруются дважды.
|
||||
|
||||
Для шифрования используется алгоритм AES-256-GCM и собственный упрощённый протокол согласования
|
||||
ключей, полностью аналогичный TLS 1.3 ECDHE.
|
||||
|
||||
### Сквозное шифрование данных образов (AES-XTS)
|
||||
|
||||
Клиент Vitastor поддерживает шифрование данных каждого образа своим ключом. В этом случае на OSD
|
||||
уходят уже зашифрованные данные и сами OSD не видят исходные данные клиента. При этом ключ можно
|
||||
менять при клонировании/создании снимков образов. Например, можно сделать базовый образ ВМ
|
||||
(условный Debian Linux) нешифрованным, но наследовать от него шифрованные образы клиентских ВМ.
|
||||
|
||||
Ключи шифрования образов могут храниться либо в etcd, либо во внешнем Vault. Во втором случае
|
||||
в etcd хранятся только ID ключей, а Vitastor вообще не имеет доступа к данным образов. Для
|
||||
использования Vault нужно создать образ с опцией `--enc_key vault:ID`, в конфигурации указать
|
||||
опции vault_url, и vault_ca, создать всем клиентам учётные записи в Vault и дать им доступ
|
||||
к требуемым секретам v1.
|
||||
|
||||
Ещё раз повторимся, что если AES-XTS используется с полным шифрованием трафика (`proto_checksums=gcm`),
|
||||
то данные образов шифруются дважды - сначала AES-XTS, а потом AES-GCM. Можете использовать,
|
||||
только если вы совсем параноик :-).
|
||||
|
||||
### Аутентификация по сертификатам
|
||||
|
||||
При включённом шифровании клиенты, OSD и мониторы Vitastor аутентифицируются по сертификатам
|
||||
как при соединениях с etcd (Antietcd), так и с OSD.
|
||||
|
||||
Для OSD и мониторов должны использоваться отдельные сертификаты - либо самоподписанные, либо
|
||||
подписанные отдельными CA (`osd_ca` и `mon_ca`). При этом все OSD могут использовать один и
|
||||
тот же сертификат и все мониторы тоже могут использовать один и тот же сертификат, так как
|
||||
привилегии разных OSD или разных мониторов ничем не отличаются (теоретически можно было бы
|
||||
сделать разграничение сертификатов OSD по пулам, но пока что такой необходимости не было).
|
||||
|
||||
Также сертификат монитора может быть вообще не нужен, если Antietcd встраивается в сам монитор.
|
||||
В этом случае монитор и так имеет доступ ко всем данным etcd прямо в памяти.
|
||||
|
||||
### Пользователи и права доступа
|
||||
|
||||
При отключённом шифровании трафика Vitastor работает без разграничения прав доступа, то есть,
|
||||
любой клиент кластера имеет полный доступ как к слою управлению, так и к слою данных. Такой
|
||||
вариант подходит для выделенных доверенных сетей хранения.
|
||||
|
||||
При включённом шифровании трафика OSD (хотя бы заголовков) есть возможность задействовать
|
||||
права доступа, включив опцию `use_perms=true`. При включённой опции каждый пользователь может
|
||||
выполнять только те операции, которые ему разрешены, и даже OSD и мониторам также запрещены
|
||||
"лишние" операции.
|
||||
|
||||
Каждый пользователь (или администратор) должен иметь свой сертификат, подписанный общим
|
||||
корневым сертификатом для клиентов (`client_ca`), с Common Name, равным имени пользователя.
|
||||
Настройки привилегий же хранятся в etcd. Для OSD и мониторов учётные записи не нужны,
|
||||
они аутентифицируются по отдельным сертификатам.
|
||||
|
||||
Привилегии пользователей хранятся в данных etcd в ключах `/vitastor/config/user/<имя>`.
|
||||
В этом ключе для каждого пользователя задаётся:
|
||||
- Тип:
|
||||
- Клиент (`type=client` или не указано) - может читать и модифицировать только явным образом
|
||||
разрешённые образы.
|
||||
- Администратор (`type=admin`) - может читать и модифицировать все образы, а также администрировать
|
||||
кластер: смотреть общую статистику и состояние, создавать и удалять OSD и так далее.
|
||||
- Список имён групп, членом которых пользователь является.
|
||||
|
||||
У образов есть следующие свойства:
|
||||
- Владелец (owner) - имя пользователя, которому разрешено и читать, и менять образ
|
||||
- Группа владельцев (owner_group) - имя группы владельцев
|
||||
- Группа читателей (reader_group) - имя группы пользователей, которым разрешено читать образ
|
||||
|
||||
И также есть свойство у пула:
|
||||
- Группа создателей (creator_group) - имя группы пользователей, которым разрешено создавать образы в пуле
|
||||
|
||||
Перечень разрешённых операций с данными образов на стороне OSD смотрите в разделе
|
||||
[Права доступа к данным OSD](#права-доступа-к-данным-osd).
|
||||
|
||||
### Привилегии etcd
|
||||
|
||||
Привилегии etcd реализуются через Antietcd во всех режимах работы.
|
||||
|
||||
Встроенные привилегии etcd не поддерживаются по причине их многочисленных неудобств:
|
||||
- Аутентификация по сертификатам вообще не работает в REST интерфейсе etcd,
|
||||
- Привилегии хранятся отдельно от k/v данных и не могут участвовать в транзакциях,
|
||||
- Менять привилегии может только администратор (root),
|
||||
- Нет поддержки фильтрации диапазонных ответов чтения по привилегиям.
|
||||
|
||||
Если используется etcd, то Antietcd выступает в роли фильтрующего прокси, при этом он
|
||||
может быть встроен в монитор Vitastor или запущен отдельно. В этом случае etcd должен
|
||||
разрешать входящие подключения только от Antietcd, а все остальные компоненты должны
|
||||
соединяться с Antietcd.
|
||||
|
||||
Если Antietcd запускается в составе монитора Vitastor, то достаточно включить опцию
|
||||
`use_perms=true` и задать нужные сертификаты. Если Antietcd запускается отдельно, то
|
||||
привилегии нужно включать отдельно опциями Antietcd. Подробнее о настройке смотрите
|
||||
раздел [Варианты настройки etcd/Antietcd](#варианты-настройки-etcdantietcd).
|
||||
|
||||
Перечень разрешённых операций с данными etcd смотрите в разделе
|
||||
[Права доступа к данным etcd](#права-доступа-к-данным-etcd).
|
||||
|
||||
## Ручная настройка
|
||||
|
||||
### Настройка шифрования соединений OSD
|
||||
|
||||
Вам нужно 2 сертификата: один для OSD и один для подписи сертификатов всех клиентов.
|
||||
Для OSD можно использовать самоподписанный сертификат (osd_ca.crt) или отдельный сертификат (osd.crt),
|
||||
подписанный доверенным сертификатом osd_ca.crt. Для клиентов нужно использовать отдельные
|
||||
сертификаты, подписанные общим доверенным (client_ca.crt).
|
||||
|
||||
В конфигурацию Vitastor на серверах OSD нужно добавить:
|
||||
- use_perms: true
|
||||
- osd_ca: osd_ca.crt
|
||||
- client_ca: client_ca.crt
|
||||
- osd_cert: osd_ca.crt
|
||||
- osd_pkey: osd_ca.key
|
||||
|
||||
На стороне клиентов:
|
||||
- use_perms: true
|
||||
- cert: client.crt
|
||||
- pkey: client.key
|
||||
|
||||
### Варианты настройки etcd/Antietcd
|
||||
|
||||
Доступны следующие варианты настройки:
|
||||
|
||||
#### Mon со встроенным Antietcd
|
||||
|
||||
Самый простой вариант. Вам нужен 1 сертификат для Antietcd (antietcd.crt), плюс
|
||||
корневые сертификаты для OSD и клиентов.
|
||||
|
||||
Настройки Vitastor (`/etc/vitastor/vitastor.conf`):
|
||||
- etcd_address: [ "http://mon1:2379", ... ] (адреса ваших мониторов с портом 2379)
|
||||
- use_perms: true
|
||||
- use_antietcd: true
|
||||
- antietcd_cert: antietcd.crt
|
||||
- antietcd_key: antietcd.key
|
||||
- etcd_ca: antietcd.crt
|
||||
- osd_ca: osd_ca.crt
|
||||
- client_ca: client_ca.crt
|
||||
|
||||
#### Mon в роли Etcd proxy
|
||||
|
||||
Если вы хотите включить привилегии, но остаться на etcd, можно задействовать режим etcd proxy.
|
||||
|
||||
Вам понадобится 2 отдельных сертификата: один для etcd (etcd.crt) и один для antietcd (antietcd.crt).
|
||||
Клиентский порт etcd должен отличаться от стандартного 2379, например, можно выбрать 2381.
|
||||
Также нужны сертификаты OSD и клиентов.
|
||||
|
||||
Настройки Vitastor:
|
||||
- etcd_address: [ "http://mon1:2379", ... ] (адреса ваших мониторов с портом 2379)
|
||||
- use_perms: true
|
||||
- use_antietcd: true
|
||||
- etcd_proxy:
|
||||
```
|
||||
{
|
||||
"urls": [ "http://mon1:2381", ... ], // адреса ваших etcd с портом 2381
|
||||
"cert": "antietcd.crt",
|
||||
"key": "antietcd.key",
|
||||
"ca": "etcd.crt"
|
||||
}
|
||||
```
|
||||
- antietcd_cert: antietcd.crt
|
||||
- antietcd_key: antietcd.key
|
||||
- etcd_ca: antietcd.crt
|
||||
- osd_ca: osd_ca.crt
|
||||
- client_ca: client_ca.crt
|
||||
|
||||
Опции командной строки etcd:
|
||||
```
|
||||
--advertise-client-urls=https://<АДРЕС>:2381 --listen-client-urls=https://<АДРЕС>:2381 \
|
||||
--client-cert-auth --cert-file=etcd.crt --key-file=etcd.key --trusted-ca-file=antietcd.crt \
|
||||
--peer-client-cert-auth --peer-cert-file=etcd.crt --peer-key-file=etcd.key --peer-trusted-ca-file=etcd.crt
|
||||
```
|
||||
|
||||
#### Mon с отдельным Antietcd Proxy
|
||||
|
||||
Если в дополнение к предыдущему варианту вы хотите разгрузить Antietcd от задач монитора Vitastor,
|
||||
можно запустить его отдельно.
|
||||
|
||||
Аналогично предыдущему варианту нужно 2 сертификата: один для etcd и один для antietcd, плюс понадобятся
|
||||
отдельные сертификаты для клиентов, OSD и монитора.
|
||||
|
||||
Настройки Vitastor:
|
||||
- etcd_address: [ "http://mon1:2379", ... ] (адреса ваших мониторов с портом 2379)
|
||||
- use_perms: true
|
||||
- use_antietcd: false
|
||||
- etcd_ca: antietcd.crt
|
||||
- osd_ca: osd_ca.crt
|
||||
- client_ca: client_ca.crt
|
||||
- mon_etcd_client_cert: mon_ca.crt
|
||||
- mon_etcd_client_key: mon_ca.key
|
||||
|
||||
Опции командной строки Antietcd:
|
||||
```
|
||||
--port 2379 \
|
||||
--client_cert_auth 1 --auth_filter vitastor_auth_filter.js --etcd_proxy url1,url2,... \
|
||||
--cert antietcd.crt --key antietcd.key --ca client_ca.crt --osd_ca osd_ca.crt --mon_ca mon_ca.crt \
|
||||
--etcd_cert antietcd.crt --etcd_key antietcd.key --etcd_ca etcd.crt
|
||||
```
|
||||
|
||||
Опции командной строки etcd (не отличаются от предыдущего варианта):
|
||||
```
|
||||
--advertise-client-urls=https://<АДРЕС>:2381 --listen-client-urls=https://<АДРЕС>:2381 \
|
||||
--client-cert-auth --cert-file=etcd.crt --key-file=etcd.key --trusted-ca-file=antietcd.crt \
|
||||
--peer-client-cert-auth --peer-cert-file=etcd.crt --peer-key-file=etcd.key --peer-trusted-ca-file=etcd.crt
|
||||
```
|
||||
|
||||
#### Отдельный Antietcd без etcd
|
||||
|
||||
Аналогично предыдущему варианту, но etcd и его сертификат не нужны:
|
||||
|
||||
Настройки Vitastor (не отличаются от предыдущего варианта):
|
||||
- etcd_address: [ "http://mon1:2379", ... ] (адреса ваших мониторов с портом 2379)
|
||||
- use_perms: true
|
||||
- use_antietcd: false
|
||||
- etcd_ca: antietcd.crt
|
||||
- osd_ca: osd_ca.crt
|
||||
- client_ca: client_ca.crt
|
||||
- mon_etcd_client_cert: mon_ca.crt
|
||||
- mon_etcd_client_key: mon_ca.key
|
||||
|
||||
Опции командной строки Antietcd:
|
||||
```
|
||||
--port 2379 \
|
||||
--client_cert_auth 1 --auth_filter vitastor_auth_filter.js \
|
||||
--persist_filter vitastor_persist_filter.js \
|
||||
--cert antietcd.crt --key antietcd.key --ca client_ca.crt --osd_ca osd_ca.crt --mon_ca mon_ca.crt
|
||||
```
|
||||
|
||||
### Настройка Vault/OpenBao
|
||||
|
||||
Для использования Vault каждому клиенту, который будет получать из Vault ключи
|
||||
образов, нужна учётная запись в Vault. Vitastor поддерживает только аутентификацию
|
||||
по клиентским сертификатам, так что все сертификаты клиентов (`cert`+`pkey`) должны
|
||||
быть зарегистрированы в Vault и им должен быть дан доступ к соответствующим секретам
|
||||
(поддерживается API секретов v1).
|
||||
|
||||
Требуемый формат секрета Vault - одно поле `key` в формате шестнадцатеричной строки.
|
||||
Используется алгоритм AES-256-XTS, так что длина ключа - 64 байта, то есть строка
|
||||
должна состоять из 128 шестнадцатеричных цифр.
|
||||
|
||||
Для подключения Vault включите следующие настройки в Vitastor.conf:
|
||||
- `vault_url` - адрес Vault (например, `https://vault:8200`)
|
||||
- `vault_ca` - сертификат самого Vault
|
||||
|
||||
После этого, если создать образ (`vitastor-cli create`) с опцией `--enc_key vault:<ID>`,
|
||||
то для получения ключа клиенты Vitastor сначала обратятся к Vault для получения токена
|
||||
по адресу `/v1/auth/cert/login`, а потом запросят из Vault сам секрет по адресу `/v1/secret/<ID>`.
|
||||
|
||||
#### Пример настройки Vault
|
||||
|
||||
Пошаговая инструкция для настройки тестового Vault на примере OpenBao:
|
||||
|
||||
1. Если ещё не настроен TLS, генерируем самоподписанный TLS сертификат для Vault:
|
||||
```
|
||||
openssl req -days 3650 -x509 -addext basicConstraints=critical,CA:TRUE,pathlen:1 --addext subjectAltName=DNS:vault \
|
||||
-new -newkey rsa:4096 -nodes -keyout /etc/openbao/vault.key -out /etc/openbao/vault.crt
|
||||
```
|
||||
Настраиваем его в `/etc/openbao/openbao.hcl`:
|
||||
```
|
||||
listener "tcp" {
|
||||
address = "0.0.0.0:8200"
|
||||
tls_cert_file = "/etc/openbao/vault.crt"
|
||||
tls_key_file = "/etc/openbao/vault.key"
|
||||
}
|
||||
```
|
||||
И перезапускаем OpenBao (`systemctl restart openbao`).
|
||||
2. Копируем TLS сертификат Vault для Vitastor:
|
||||
```
|
||||
cp /etc/openbao/vault.crt /etc/vitastor/vault.crt
|
||||
```
|
||||
Переносим его на все клиентские ноды и прописываем в `/etc/vitastor/vitastor.conf`:
|
||||
```
|
||||
{
|
||||
...
|
||||
"vault_url": "http://vault:8200",
|
||||
"vault_ca": "/etc/vitastor/vault.crt"
|
||||
}
|
||||
```
|
||||
3. Проверяем статус Vault:
|
||||
```
|
||||
bao status -ca-cert /etc/openbao/vault.crt -address=https://vault:8200
|
||||
```
|
||||
4. Инициализируем Vault в тестовом режиме из 1 ноды (с 1 частью ключа):
|
||||
```
|
||||
bao operator init -n 1 -t 1 -ca-cert /etc/openbao/vault.crt -address=https://vault:8200
|
||||
```
|
||||
5. Разблокируем Vault:
|
||||
```
|
||||
bao operator unseal -ca-cert /etc/openbao/vault.crt -address=https://vault:8200
|
||||
```
|
||||
6. Включаем аутентификацию по сертификатам:
|
||||
```
|
||||
bao auth enable -ca-cert /etc/openbao/vault.crt -address=https://vault:8200 cert
|
||||
```
|
||||
7. Включаем секреты v1:
|
||||
```
|
||||
bao secrets enable -ca-cert /etc/openbao/vault.crt -address=https://vault:8200 -path=secret kv-v1
|
||||
```
|
||||
8. Создаём тестовый секрет:
|
||||
```
|
||||
bao kv put -ca-cert /etc/openbao/vault.crt -address=https://vault:8200 secret/vitastor/testimg3 key=$(openssl rand -hex 64)
|
||||
```
|
||||
9. Генерируем подписанный сертификат для пользователя Vitastor (там, где у вас есть `client_ca.crt` и `client_ca.key`):
|
||||
```
|
||||
openssl req -subj '/CN=testimg3' -nodes -new -keyout testimg3.key -out testimg3.csr
|
||||
openssl x509 -req -days 3650 -CA client_ca.crt -CAkey client_ca.key -CAcreateserial -in testimg3.csr -out testimg3.crt
|
||||
rm testimg3.csr
|
||||
```
|
||||
10. Создаём пользователя в Vault и даём ему доступ к секрету:
|
||||
```
|
||||
cat >testimg3.policy <<EOF
|
||||
path "/secret/vitastor/testimg3" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
EOF
|
||||
|
||||
bao policy write -ca-cert /etc/openbao/vault.crt -address=https://vault:8200 testimg3 testimg3.policy
|
||||
|
||||
bao write -ca-cert /etc/openbao/vault.crt -address=https://vault:8200 auth/cert/certs/testimg3 \
|
||||
certificate=@testimg3.crt display_name=testimg3 token_ttl=24h token_policies=testimg3
|
||||
```
|
||||
11. Тестируем доступ к секрету:
|
||||
```
|
||||
curl --cacert /etc/vitastor/vault.crt --cert testimg3.crt --key testimg3.key \
|
||||
--json '{}' https://vault:8200/v1/auth/cert/login
|
||||
```
|
||||
Будет выведен токен, подставляем его в следующий запрос:
|
||||
```
|
||||
curl --cacert /etc/vitastor/vault.crt --cert testimg3.crt --key testimg3.key \
|
||||
-H 'X-Vault-Token: <ПОЛУЧЕННЫЙ ТОКЕН>' https://vault:8200/v1/secret/vitastor/testimg3
|
||||
```
|
||||
12. Создаём образ в Vitastor с заданным секретом (от имени администратора или того, кто имеет
|
||||
право создавать образы в вашем пуле):
|
||||
```
|
||||
vitastor-cli create -s 100G --enc_key vault:vitastor/testimg3 --owner testimg3 testimg3
|
||||
```
|
||||
13. Тестируем доступ к образу от имени пользователя testimg3:
|
||||
```
|
||||
vitastor-cli --cert testimg3.crt --pkey testimg3.key dd if=/dev/urandom oimg=testimg3 bs=1M count=100
|
||||
```
|
||||
|
||||
## Списки разрешённых операций
|
||||
|
||||
### Права доступа к данным etcd
|
||||
|
||||
Ниже все названия ключей приведены без общего префикса `/vitastor`.
|
||||
|
||||
Разрешённые операции с ключами в Antietcd для клиентов (`type=client`):
|
||||
- Только чтение:
|
||||
- Разрешено всегда:
|
||||
- `/config/global`
|
||||
- `/config/node_placement`
|
||||
- `/config/pools`
|
||||
- `/pg/config`
|
||||
- `/osd/state/*`
|
||||
- `/pg/state/*`
|
||||
- `/index/maxid/*`
|
||||
- Для образов, которые [может читать пользователь](#пользователи-и-права-доступа):
|
||||
- `/config/inode/*`
|
||||
- `/index/image/*`
|
||||
- `/inode/stats/*`
|
||||
- Чтение и запись:
|
||||
- Для пулов, в которых может создавать образы пользователь:
|
||||
- `/index/maxid/*`
|
||||
- Для образов, которыми владеет пользователь:
|
||||
- `/config/inode/*`
|
||||
- `/index/image/*`
|
||||
|
||||
Разрешённые операции с ключами в Antietcd для администраторов (`type=admin`):
|
||||
- Чтение:
|
||||
- `/stats`
|
||||
- `/mon/*`
|
||||
- `/pg/*`
|
||||
- `/pgstats/*`
|
||||
- `/inode/stats/*`
|
||||
- `/pool/stats/*`
|
||||
- Чтение и запись:
|
||||
- `/config/*`
|
||||
- `/osd/*`
|
||||
- `/index/*`
|
||||
- `/pg/history/*`
|
||||
|
||||
Разрешённые операции с ключами в etcd для OSD:
|
||||
- Чтение:
|
||||
- `/pg/config`
|
||||
- `/config/*`
|
||||
- Чтение и запись:
|
||||
- `/osd/*`
|
||||
- `/pg/state/*`
|
||||
- `/pg/history/*`
|
||||
- `/pgstats/*`
|
||||
|
||||
Разрешённые операции с ключами в etcd для мониторов:
|
||||
- Чтение:
|
||||
- `/config/*`
|
||||
- `/osd/*`
|
||||
- `/pgstats/*`
|
||||
- Чтение и запись:
|
||||
- `/pg/config`
|
||||
- `/stats`
|
||||
- `/history/last_clean_pgs`
|
||||
- `/mon/*`
|
||||
- `/pg/history/*`
|
||||
- `/inode/stats/*`
|
||||
- `/pool/stats/*`
|
||||
|
||||
### Права доступа к данным OSD
|
||||
|
||||
При включённой опции `use_perms` и шифровании OSD аутентифицирует клиентов по сертификатам
|
||||
и разрешает каждому клиенту только то, что ему разрешено согласно модели прав доступа.
|
||||
|
||||
Клиентские операции:
|
||||
- READ - разрешено для образов, доступных пользователю на чтение.
|
||||
- WRITE, DELETE, SCRUB - разрешены для образов, доступных пользователю на запись.
|
||||
- SYNC - операция не связана с образом и разрешена всегда.
|
||||
- DESCRIBE - операция разрешена только для администраторов (используются командами
|
||||
`vitastor-cli describe` и `fix`).
|
||||
- PING - операция разрешена всегда.
|
||||
- SHOW_CONFIG - операция разрешена всегда, однако если в ней клиент представляется
|
||||
как OSD, то проверяется, что он использует сертификат, подписанный `osd_ca`.
|
||||
- SEC_LIST (листинг) - разрешена другим OSD и администраторам с любыми параметрами,
|
||||
а обычным клиентам разрешена только для запросов, ограниченных образом, доступным
|
||||
пользователю на чтение.
|
||||
|
||||
Кластерные операции - разрешаются только другим OSD:
|
||||
- SEC_READ
|
||||
- SEC_WRITE
|
||||
- SEC_WRITE_STABLE
|
||||
- SEC_SYNC
|
||||
- SEC_STABILIZE
|
||||
- SEC_ROLLBACK
|
||||
- SEC_DELETE
|
||||
- SEC_READ_BMP
|
||||
- SEC_LOCK
|
||||
|
||||
### Права доступа к API
|
||||
|
||||
[vitastor-cli serve](../usage/cli.ru.md#serve) также поддерживает клиентскую
|
||||
аутентификацию по сертификатам. Принимаются только сертификаты, подписанные
|
||||
`client_ca`. В качестве серверного сертификата используется отдельный сертификат
|
||||
`server_cert` с ключом `server_pkey`.
|
||||
|
||||
При этом для корректной работы `vitastor-cli serve` он сам должен использовать
|
||||
для доступа в Vitastor сертификат (`cert`+`pkey`) пользователя с правами
|
||||
администратора (`type=admin`).
|
||||
|
||||
Обычным клиентам при доступе к API разрешаются только API-операции с образами,
|
||||
доступными им либо на чтение (для чтения), либо на запись (для модификации).
|
||||
Все остальные API-вызовы разрешаются только для администраторов.
|
||||
|
||||
Список разрешённых операций API:
|
||||
|
||||
Клиентам (пользователям с `type=client`) разрешаются операции:
|
||||
- image/list - для образов, которые пользователь может читать.
|
||||
- image/create - для пулов, в которых пользователю разрешено создавать образы, либо
|
||||
для создания снимков образов, которыми пользователь владеет.
|
||||
- image/delete, image/flatten, image/modify - для образов, которыми пользователь владеет.
|
||||
|
||||
Все остальные операции разрешаются только администраторам (`type=admin`).
|
||||
|
||||
## Производительность шифрования
|
||||
|
||||
У вас может возникнуть вопрос - а как быстро всё это прекрасное шифрование работает?
|
||||
|
||||
Ответ - сильно зависит от процессора. На современных процессорах (при наличии AVX512 с VAES)
|
||||
очень быстро - скорость шифрования AES может составлять 10-20 Гбайт/с и выше. В первую очередь
|
||||
подразумевается CPU клиентских машин, потому что сквозное шифрование выполняется целиком на
|
||||
клиенте, а транспортное хоть также и затрагивает OSD, но у клиента поток один, а OSD на стороне
|
||||
сервера много и добавить там ресурсов легче.
|
||||
|
||||
На более старых процессорах скорость заметно хуже, например, на Xeon E5 v4 она составляет
|
||||
буквально 3 Гбайт/с.
|
||||
|
||||
Вы можете оценить производительность своих процессоров с помощью команды `vitastor-cli cpubench`.
|
||||
|
||||
Пример вывода (💪 AMD EPYC 9575F):
|
||||
|
||||
```
|
||||
$ vitastor-cli cpubench
|
||||
Vitastor transport encryption benchmark (AES-256-GCM, AES-256-XTS and xxhash3)
|
||||
|
||||
Warmup...
|
||||
|
||||
No transport encryption, data checksums enabled, e2e unencrypted image
|
||||
xxhash3 1 M block... 209000 iterations in 2001 ms = 104447.78 MB/s
|
||||
xxhash3 4 K block... 37000000 iterations in 2022 ms = 71479.35 MB/s
|
||||
|
||||
Header encryption with payload checksums, e2e unencrypted image
|
||||
AES-256-GCM encrypt header + xxhash3 1 M block... 210000 iterations in 2015 ms = 104218.36 MB/s
|
||||
AES-256-GCM encrypt header + xxhash3 4 K block... 26000000 iterations in 2073 ms = 48993.01 MB/s
|
||||
|
||||
Full transport encryption, e2e unencrypted image
|
||||
AES-256-GCM encrypt header and 1 M block... 54000 iterations in 2000 ms = 27000.00 MB/s
|
||||
AES-256-GCM encrypt header and 4 K block... 11700000 iterations in 2014 ms = 22692.71 MB/s
|
||||
|
||||
No transport encryption, no checksums, e2e encrypted image
|
||||
AES-256-XTS encrypt 1 M block... 50000 iterations in 2039 ms = 24521.82 MB/s
|
||||
AES-256-XTS encrypt 4 K block... 12600000 iterations in 2009 ms = 24499.13 MB/s
|
||||
|
||||
No transport encryption, e2e encrypted image, data checksums enabled
|
||||
AES-256-XTS encrypt + xxhash3 1 M block... 40000 iterations in 2013 ms = 19870.84 MB/s
|
||||
AES-256-XTS encrypt + xxhash3 4 K block... 10200000 iterations in 2011 ms = 19812.90 MB/s
|
||||
|
||||
Header encryption with payload checksums, e2e encrypted image
|
||||
AES-256-GCM encrypt header + AES-256-XTS encrypt + xxhash3 1 M block... 40000 iterations in 2014 ms = 19860.97 MB/s
|
||||
AES-256-GCM encrypt header + AES-256-XTS encrypt + xxhash3 4 K block... 8700000 iterations in 2011 ms = 16899.24 MB/s
|
||||
|
||||
Full transport encryption, e2e encrypted image
|
||||
AES-256-XTS + AES-256-GCM encrypt 1 M block... 26000 iterations in 2062 ms = 12609.12 MB/s
|
||||
AES-256-XTS + AES-256-GCM encrypt 4 K block... 6300000 iterations in 2006 ms = 12267.88 MB/s
|
||||
```
|
||||
|
||||
А вот Xeon E5-2680v4:
|
||||
|
||||
```
|
||||
$ vitastor-cli cpubench
|
||||
Vitastor transport encryption benchmark (AES-256-GCM, AES-256-XTS and xxhash3)
|
||||
|
||||
Warmup...
|
||||
|
||||
No transport encryption, data checksums enabled, e2e unencrypted image
|
||||
xxhash3 1 M block... 62000 iterations in 2021 ms = 30677.88 MB/s
|
||||
xxhash3 4 K block... 12400000 iterations in 2006 ms = 24146.31 MB/s
|
||||
|
||||
Header encryption with payload checksums, e2e unencrypted image
|
||||
AES-256-GCM encrypt header + xxhash3 1 M block... 62000 iterations in 2027 ms = 30587.07 MB/s
|
||||
AES-256-GCM encrypt header + xxhash3 4 K block... 6800000 iterations in 2011 ms = 13208.60 MB/s
|
||||
|
||||
Full transport encryption, e2e unencrypted image
|
||||
AES-256-GCM encrypt header and 1 M block... 7000 iterations in 2317 ms = 3021.15 MB/s
|
||||
AES-256-GCM encrypt header and 4 K block... 1500000 iterations in 2102 ms = 2787.52 MB/s
|
||||
|
||||
No transport encryption, no checksums, e2e encrypted image
|
||||
AES-256-XTS encrypt 1 M block... 7000 iterations in 2317 ms = 3021.15 MB/s
|
||||
AES-256-XTS encrypt 4 K block... 1600000 iterations in 2088 ms = 2993.30 MB/s
|
||||
|
||||
No transport encryption, e2e encrypted image, data checksums enabled
|
||||
AES-256-XTS encrypt + xxhash3 1 M block... 6000 iterations in 2188 ms = 2742.23 MB/s
|
||||
AES-256-XTS encrypt + xxhash3 4 K block... 1400000 iterations in 2053 ms = 2663.78 MB/s
|
||||
|
||||
Header encryption with payload checksums, e2e encrypted image
|
||||
AES-256-GCM encrypt header + AES-256-XTS encrypt + xxhash3 1 M block... 6000 iterations in 2190 ms = 2739.73 MB/s
|
||||
AES-256-GCM encrypt header + AES-256-XTS encrypt + xxhash3 4 K block... 1300000 iterations in 2101 ms = 2417.00 MB/s
|
||||
|
||||
Full transport encryption, e2e encrypted image
|
||||
AES-256-XTS + AES-256-GCM encrypt 1 M block... 4000 iterations in 2666 ms = 1500.38 MB/s
|
||||
AES-256-XTS + AES-256-GCM encrypt 4 K block... 800000 iterations in 2113 ms = 1478.94 MB/s
|
||||
```
|
||||
+21
-7
@@ -125,18 +125,31 @@ bench-kaveri kaveri 10 G 10 G 0 B/s 0 0 0 us 0 B/s 0
|
||||
|
||||
## create
|
||||
|
||||
`vitastor-cli create -s|--size <size> [-p|--pool <id|name>] [--parent <parent_name>[@<snapshot>]] <name>`
|
||||
`vitastor-cli create -s|--size SIZE [OPTIONS] <name>`
|
||||
|
||||
Create an image. You may use K/M/G/T suffixes for `<size>`. If `--parent` is specified,
|
||||
a copy-on-write image clone is created. Parent must be a snapshot (readonly image).
|
||||
Pool must be specified if there is more than one pool.
|
||||
Create an image. Options:
|
||||
|
||||
* `-s|--size SIZE` - New image size in bytes or with a K/M/G/T unit suffix.
|
||||
* `-p|--pool POOL` - Specify pool for the new image (may be omitted if there is only 1 pool).
|
||||
* `--parent PARENT` - Create a copy-on-write image clone based on PARENT (or PARENT@SNAPSHOT).
|
||||
If parent is not a snapshot, it must be a read-only image.
|
||||
* `--enc-key random` - Generate a new random AES-256-XTS encryption key for the new image.
|
||||
* `--enc-key HEX` - Set a specified AES-256-XTS key (64 bytes in hex) for the new image.
|
||||
* `--enc-key vault:ID` - Use an encryption key from an external Vault secret with specified ID.
|
||||
|
||||
```
|
||||
vitastor-cli create --snapshot <snapshot> [-p|--pool <id|name>] <image>
|
||||
vitastor-cli snap-create [-p|--pool <id|name>] <image>@<snapshot>
|
||||
vitastor-cli create --snapshot <snapshot> [OPTIONS] <image>
|
||||
vitastor-cli snap-create [OPTIONS] <image>@<snapshot>
|
||||
```
|
||||
|
||||
Create a snapshot of image `<name>` (either form can be used). May be used live if only a single writer is active.
|
||||
Create a snapshot of image `<image>`. May be used live if only a single writer is active.
|
||||
|
||||
Options:
|
||||
|
||||
* `-p|--pool POOL` - Move image to pool POOL, leaving the snapshot in the old pool.
|
||||
* `--enc-key random` - Change image encryption key to a new random AES-256-XTS key.
|
||||
* `--enc-key KEY` - Change image encryption key to a specified key, Vault key or to an empty key.
|
||||
By default, the image retains its old encryption key when taking a snapshot.
|
||||
|
||||
See also about [how to export snapshots](qemu.en.md#exporting-snapshots).
|
||||
|
||||
@@ -151,6 +164,7 @@ You should resize file system in the image, if present, before shrinking it.
|
||||
* `--deleted 1|0` - Set/clear 'deleted image' flag (set automatically during unfinished deletes).
|
||||
* `-f|--force` - Proceed with shrinking or setting readwrite flag even if the image has children.
|
||||
* `--down-ok` - Proceed with shrinking even if some data will be left on unavailable OSDs.
|
||||
* `--enc-key HEX` - Change image encryption key (allowed only with `--force`).
|
||||
|
||||
## dd
|
||||
|
||||
|
||||
+22
-8
@@ -127,19 +127,32 @@ bench-kaveri kaveri 10 G 10 G 0 B/s 0 0 0 us 0 B/s 0
|
||||
|
||||
## create
|
||||
|
||||
`vitastor-cli create -s|--size <size> [-p|--pool <id|name>] [--parent <parent_name>[@<snapshot>]] <name>`
|
||||
`vitastor-cli create -s|--size SIZE [ОПЦИИ] <name>`
|
||||
|
||||
Создать образ. Для размера `<size>` можно использовать суффиксы K/M/G/T (килобайт-мегабайт-гигабайт-терабайт).
|
||||
Если указана опция `--parent`, создаётся клон образа. Родитель `<parent_name>[@<snapshot>]` должен быть
|
||||
снимком (или просто немодифицируемым образом). Пул обязательно указывать, если в кластере больше одного пула.
|
||||
Создать образ. Опции:
|
||||
|
||||
* `-s|--size SIZE` - Размер нового образа в байтах или с суффиксом K/M/G/T (кило/мега/гига/терабайт).
|
||||
* `-p|--pool POOL` - Создать образ в заданном пуле (можно не указывать, если пул всего один).
|
||||
* `--parent PARENT` - Создать легковесный клон на основе образа `PARENT` или снимка `PARENT@SNAP`.
|
||||
Если `PARENT` - не снимок, он должен быть помечен как образ только для чтения.
|
||||
* `--enc-key random` - Сгенерировать случайный ключ шифрования AES-256-XTS для нового образа.
|
||||
* `--enc-key HEX` - Установить заданный ключ AES-256-XTS (64 байта в hex) для нового образа.
|
||||
* `--enc-key vault:ID` - Использовать ключ из внешнего секрета с заданным ID из Vault.
|
||||
|
||||
```
|
||||
vitastor-cli create --snapshot <snapshot> [-p|--pool <id|name>] <image>
|
||||
vitastor-cli snap-create [-p|--pool <id|name>] <image>@<snapshot>
|
||||
vitastor-cli create --snapshot <snapshot> [ОПЦИИ] <image>
|
||||
vitastor-cli snap-create [ОПЦИИ] <image>@<snapshot>
|
||||
```
|
||||
|
||||
Создать снимок образа `<name>` (можно использовать любую форму команды). Снимок можно создавать без остановки
|
||||
клиентов, если пишущий клиент максимум 1.
|
||||
Создать снимок образа `<image>` (можно использовать любую форму команды).
|
||||
Снимок можно создавать без остановки клиентов, если пишущих клиентов не больше одного.
|
||||
|
||||
Опции:
|
||||
|
||||
* `-p|--pool POOL` - Переместить образ в пул POOL, оставив снимок в старом пуле.
|
||||
* `--enc-key random` - Изменить ключ шифрования образа на новый случайный ключ AES-256-XTS.
|
||||
* `--enc-key KEY` - Изменить ключ шифрования образа на заданный ключ, ключ из Vault или пустой ключ.
|
||||
По умолчанию шифрованные образы сохраняют старый ключ при снятии снимка.
|
||||
|
||||
Смотрите также информацию о том, [как экспортировать снимки](qemu.ru.md#экспорт-снимков).
|
||||
|
||||
@@ -156,6 +169,7 @@ vitastor-cli snap-create [-p|--pool <id|name>] <image>@<snapshot>
|
||||
* `--deleted 1|0` - Установить/снять флаг "образ удалён" (устанавливается при незавершённом удалении).
|
||||
* `-f|--force` - Разрешить уменьшение или перевод в чтение-запись образа, у которого есть клоны.
|
||||
* `--down-ok` - Разрешить уменьшение, даже если часть данных останется неудалённой на недоступных OSD.
|
||||
* `--enc-key HEX` - Изменить ключ шифрования образа (разрешено только с `--force`).
|
||||
|
||||
## dd
|
||||
|
||||
|
||||
+49
-8
@@ -3,6 +3,7 @@
|
||||
|
||||
const AntiEtcd = require('antietcd');
|
||||
|
||||
const vitastor_auth_filter = require('./vitastor_auth_filter.js');
|
||||
const vitastor_persist_filter = require('./vitastor_persist_filter.js');
|
||||
const { b64, local_ips } = require('./utils.js');
|
||||
|
||||
@@ -18,7 +19,7 @@ class AntiEtcdAdapter
|
||||
cluster = cluster ? (''+(cluster||'')).split(/,+/) : [];
|
||||
cluster = Object.keys(cluster.reduce((a, url) =>
|
||||
{
|
||||
a[url.toLowerCase().replace(/^(https?:\/\/)/, '').replace(/\/.*$/, '')] = true;
|
||||
a[url.toLowerCase().replace(/^(https?:\/\/)?(.*?)(\/.*)?$/, (m, m1, m2) => (m1||'http://')+m2)] = true;
|
||||
return a;
|
||||
}, {}));
|
||||
const cfg_port = config.antietcd_port;
|
||||
@@ -26,7 +27,18 @@ class AntiEtcdAdapter
|
||||
is_local['0.0.0.0'] = true;
|
||||
is_local['::'] = true;
|
||||
is_local[''] = true;
|
||||
const selected = cluster.map(s => s.split(':', 2)).filter(ip => is_local[ip[0]] && (!cfg_port || ip[1] == cfg_port));
|
||||
// split :, 3 -> <schema>:<//ip>:<port>
|
||||
const selected = [];
|
||||
for (let i = 0; i < cluster.length; i++)
|
||||
{
|
||||
const m = /^(https?:\/\/)?(?:\[(.*)\]|([^\[\:]+))(?::(\d+))?$/.exec(cluster[i]);
|
||||
if (!m)
|
||||
continue;
|
||||
const ip = m[3] || m[2];
|
||||
const port = m[4] || 2379;
|
||||
if (is_local[ip] && (!cfg_port || port == cfg_port))
|
||||
selected.push({ idx: i, ip, port });
|
||||
}
|
||||
if (selected.length > 1)
|
||||
{
|
||||
console.error('More than 1 etcd_address matches local IPs, please specify port');
|
||||
@@ -35,16 +47,45 @@ class AntiEtcdAdapter
|
||||
else if (selected.length == 1)
|
||||
{
|
||||
const antietcd_config = {
|
||||
ip: selected[0][0],
|
||||
port: selected[0][1],
|
||||
data: config.antietcd_data_file || ((config.antietcd_data_dir || '/var/lib/vitastor') + '/mon_'+selected[0][1]+'.json.gz'),
|
||||
ip: selected[0].ip,
|
||||
port: selected[0].port,
|
||||
cert: config.antietcd_cert,
|
||||
key: config.antietcd_key,
|
||||
ca: config.client_ca,
|
||||
data: config.antietcd_data_file || ((config.antietcd_data_dir || '/var/lib/vitastor') + '/mon_'+selected[0].port+'.json.gz'),
|
||||
persist_filter: vitastor_persist_filter({ vitastor_prefix: config.etcd_prefix || '/vitastor' }),
|
||||
node_id: selected[0][0]+':'+selected[0][1], // node_id = ip:port
|
||||
cluster: (cluster.length == 1 ? null : cluster.reduce((a, c) => { a[c] = "http://"+c; return a; }, {})),
|
||||
node_id: cluster[selected[0].idx].replace(/^(https?:\/\/)/, ''), // same as in <cluster> below
|
||||
cluster: (cluster.length == 1 ? null : cluster.reduce((a, c) => { a[c.replace(/^(https?:\/\/)/, '')] = c; return a; }, {})),
|
||||
cluster_key: (config.etcd_prefix || '/vitastor'),
|
||||
stale_read: 1,
|
||||
log_level: 1,
|
||||
logs: { cluster: true },
|
||||
};
|
||||
if (config.etcd_proxy)
|
||||
{
|
||||
// Monitor may use the builtin etcd_proxy mode
|
||||
if (!config.etcd_proxy.urls)
|
||||
{
|
||||
console.error('etcd_proxy.urls are empty');
|
||||
process.exit(1);
|
||||
}
|
||||
antietcd_config.etcd_proxy = config.etcd_proxy.urls;
|
||||
antietcd_config.etcd_cert = config.etcd_proxy.cert;
|
||||
antietcd_config.etcd_key = config.etcd_proxy.key;
|
||||
antietcd_config.etcd_ca = config.etcd_proxy.ca;
|
||||
delete antietcd_config.data;
|
||||
delete antietcd_config.persist_filter;
|
||||
delete antietcd_config.cluster;
|
||||
delete antietcd_config.cluster_key;
|
||||
}
|
||||
if (config.use_perms)
|
||||
{
|
||||
antietcd_config.client_cert_auth = true;
|
||||
antietcd_config.auth_filter = vitastor_auth_filter;
|
||||
antietcd_config.ca = config.client_ca;
|
||||
antietcd_config.osd_ca = config.osd_ca;
|
||||
antietcd_config.mon_ca = config.mon_ca;
|
||||
}
|
||||
for (const key in config)
|
||||
{
|
||||
if (key.substr(0, 9) === 'antietcd_')
|
||||
@@ -169,7 +210,7 @@ class AntiEtcdAdapter
|
||||
await new Promise(ok => setTimeout(ok, timeout-(Date.now()-prev)));
|
||||
}
|
||||
prev = Date.now();
|
||||
const res = await this.antietcd.api(path.replace(/^\/+/, '').replace(/\/+$/, '').replace(/\/+/g, '_'), body);
|
||||
const res = await this.antietcd.api(path.replace(/^\/+/, '').replace(/\/+$/, '').replace(/\/+/g, '_'), body, { user_type: 'mon' });
|
||||
if (res.error)
|
||||
{
|
||||
console.error('Failed to query antietcd '+path+' (retry '+retry+'/'+retries+'): '+res.error);
|
||||
|
||||
+27
-6
@@ -1,7 +1,9 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2019+
|
||||
// License: VNPL-1.1 (see README.md for details)
|
||||
|
||||
const fs = require('fs');
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
const WebSocket = require('ws');
|
||||
const { b64, local_ips } = require('./utils.js');
|
||||
|
||||
@@ -15,11 +17,30 @@ class EtcdAdapter
|
||||
this.ws = null;
|
||||
this.ws_alive = false;
|
||||
this.ws_keepalive_timer = null;
|
||||
this.opts = {};
|
||||
}
|
||||
|
||||
parse_config(config)
|
||||
{
|
||||
this.parse_etcd_addresses(config.etcd_address||config.etcd_url);
|
||||
if (config.mon_etcd_client_cert || config.etcd_client_cert)
|
||||
{
|
||||
this.opts.cert = config.mon_etcd_client_cert || config.etcd_client_cert;
|
||||
if (this.opts.cert.substr(0, 5) != '-----')
|
||||
this.opts.cert = fs.readFileSync(this.opts.cert, { encoding: 'utf-8' });
|
||||
}
|
||||
if (config.mon_etcd_client_key || config.etcd_client_key)
|
||||
{
|
||||
this.opts.key = config.mon_etcd_client_key || config.etcd_client_key;
|
||||
if (this.opts.key.substr(0, 5) != '-----')
|
||||
this.opts.key = fs.readFileSync(this.opts.key, { encoding: 'utf-8' });
|
||||
}
|
||||
if (config.etcd_ca)
|
||||
{
|
||||
this.opts.ca = config.etcd_ca;
|
||||
if (this.opts.ca.substr(0, 5) != '-----')
|
||||
this.opts.ca = fs.readFileSync(this.opts.ca, { encoding: 'utf-8' });
|
||||
}
|
||||
}
|
||||
|
||||
parse_etcd_addresses(addrs)
|
||||
@@ -39,7 +60,7 @@ class EtcdAdapter
|
||||
for (let url of addrs)
|
||||
{
|
||||
let scheme = 'http';
|
||||
url = url.trim().replace(/^(https?):\/\//, (m, m1) => { scheme = m1; return ''; });
|
||||
url = url.trim().replace(/^(https?):\/\//i, (m, m1) => { scheme = m1.toLowerCase(); return ''; });
|
||||
const slash = url.indexOf('/');
|
||||
const colon = url.indexOf(':');
|
||||
const is_local = is_local_ip[colon >= 0 ? url.substr(0, colon) : (slash >= 0 ? url.substr(0, slash) : url)];
|
||||
@@ -130,7 +151,7 @@ class EtcdAdapter
|
||||
}
|
||||
ok(false);
|
||||
}, this.mon.config.etcd_mon_timeout);
|
||||
this.ws = new WebSocket(base+'/watch');
|
||||
this.ws = new WebSocket(base+'/watch', this.opts);
|
||||
this.ws_used_url = cur_addr;
|
||||
const fail = () =>
|
||||
{
|
||||
@@ -272,7 +293,7 @@ class EtcdAdapter
|
||||
{
|
||||
throw new Error(MON_STOPPED);
|
||||
}
|
||||
const res = await POST(base+path, body, timeout);
|
||||
const res = await POST(base+path, body, timeout, this.opts);
|
||||
if (this.mon.stopped)
|
||||
{
|
||||
throw new Error(MON_STOPPED);
|
||||
@@ -298,7 +319,7 @@ class EtcdAdapter
|
||||
}
|
||||
}
|
||||
|
||||
function POST(url, body, timeout)
|
||||
function POST(url, body, timeout, opts)
|
||||
{
|
||||
return new Promise(ok =>
|
||||
{
|
||||
@@ -310,10 +331,10 @@ function POST(url, body, timeout)
|
||||
req = null;
|
||||
ok({ error: 'timeout' });
|
||||
}, timeout) : null;
|
||||
let req = http.request(url, { method: 'POST', headers: {
|
||||
let req = (url.substr(0, 5) == 'https' ? https : http).request(url, { method: 'POST', headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': body_text.length,
|
||||
} }, (res) =>
|
||||
}, ...(opts||{}) }, (res) =>
|
||||
{
|
||||
if (!req)
|
||||
{
|
||||
|
||||
+22
-1
@@ -16,6 +16,7 @@ const etcd_allow = new RegExp('^'+[
|
||||
'config/pools',
|
||||
'config/osd/[1-9]\\d*',
|
||||
'config/pgs', // old name
|
||||
'config/user/.*',
|
||||
'pg/config',
|
||||
'config/inode/[1-9]\\d*/[1-9]\\d*',
|
||||
'osd/state/[1-9]\\d*',
|
||||
@@ -45,7 +46,14 @@ const etcd_tree = {
|
||||
config_path: "/etc/vitastor/vitastor.conf",
|
||||
etcd_prefix: "/vitastor",
|
||||
// etcd connection - configurable online
|
||||
etcd_address: "10.0.115.10:2379/v3",
|
||||
etcd_address: "http://10.0.115.10:2379/v3",
|
||||
etcd_client_cert: "",
|
||||
etcd_client_key: "",
|
||||
osd_etcd_client_cert: "",
|
||||
osd_etcd_client_key: "",
|
||||
mon_etcd_client_cert: "",
|
||||
mon_etcd_client_key: "",
|
||||
etcd_ca: "",
|
||||
// mon
|
||||
etcd_mon_ttl: 5, // min: 1
|
||||
etcd_mon_timeout: 1000, // ms. min: 0
|
||||
@@ -201,6 +209,8 @@ const etcd_tree = {
|
||||
primary_affinity_tags?: 'nvme' | [ 'nvme', ... ],
|
||||
// scrub interval
|
||||
scrub_interval?: '30d',
|
||||
// users allowed to create images in this pool
|
||||
creator_group?: '',
|
||||
},
|
||||
...
|
||||
}, */
|
||||
@@ -217,10 +227,21 @@ const etcd_tree = {
|
||||
parent_id?: <inode_t>,
|
||||
readonly?: boolean,
|
||||
deleted?: boolean,
|
||||
enc_key?: string,
|
||||
owner?: string,
|
||||
owner_group?: string,
|
||||
reader_group?: string,
|
||||
}
|
||||
}
|
||||
}, */
|
||||
inode: {},
|
||||
/* user: {
|
||||
<username>: {
|
||||
type: 'osd'|'mon'|'admin'|'client',
|
||||
groups: string[],
|
||||
},
|
||||
}, */
|
||||
user: {},
|
||||
},
|
||||
osd: {
|
||||
state: {
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vitastor-mon",
|
||||
"version": "3.0.14",
|
||||
"version": "3.0.15",
|
||||
"description": "Vitastor SDS monitor service",
|
||||
"main": "mon-main.js",
|
||||
"scripts": {
|
||||
@@ -9,7 +9,7 @@
|
||||
"author": "Vitaliy Filippov",
|
||||
"license": "UNLICENSED",
|
||||
"dependencies": {
|
||||
"antietcd": "^1.2.4",
|
||||
"antietcd": "^1.3.1",
|
||||
"sprintf-js": "^1.1.2",
|
||||
"ws": "^7.2.5"
|
||||
},
|
||||
|
||||
+379
-46
@@ -1,38 +1,324 @@
|
||||
#!/usr/bin/node
|
||||
// Simple systemd unit generator for etcd
|
||||
// Simple Vitastor etcd / antietcd / TLS configurator
|
||||
// Copyright (c) Vitaliy Filippov, 2019+
|
||||
// License: MIT
|
||||
|
||||
// USAGE:
|
||||
// 1) Put the same etcd_address into /etc/vitastor/vitastor.conf on all monitor nodes
|
||||
// 2) Run ./make-etcd.js. It will create the etcd service on one of specified IPs
|
||||
|
||||
const child_process = require('child_process');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const readline = require('readline');
|
||||
|
||||
run().catch(e => { console.error(e); process.exit(1); });
|
||||
|
||||
const help_text = `Initialize a Vitastor cluster (etcd, vitastor.conf and TLS certificates)
|
||||
(c) Vitaliy Filippov, 2026+ (MIT)
|
||||
|
||||
USAGE:
|
||||
1) Create a minimal vitastor.conf with etcd_address, osd_network and (optionally) use_perms.
|
||||
Non-encrypted: {"etcd_address":["http://10.0.0.10:2379","http://10.0.0.11:2379","http://10.0.0.12:2379"],"osd_network":"10.0.0.0/24"}
|
||||
Encrypted: {"etcd_address":["https://10.0.0.10:2379","https://10.0.0.11:2379","https://10.0.0.12:2379"],"use_perms":true,"osd_network":"10.0.0.0/24"}
|
||||
(Note https:// etcd URLs!)
|
||||
2) Run: ${process.argv[1]} [./vitastor.conf] [--antietcd-only]
|
||||
You can run it on etcd/monitor nodes or on an external node.
|
||||
It configures etcd, generates TLS certificates (on the first or external node), copies
|
||||
them to other etcd/monitor nodes, and updates vitastor.conf with TLS options.
|
||||
3) If you have OSD-only nodes, run:
|
||||
${process.argv[1]} --copy-to-osd-node NODE_NAME ./vitastor.conf
|
||||
It copies vitastor.conf and required TLS certificates to that node.
|
||||
|
||||
OPTIONS:
|
||||
--antietcd-only
|
||||
disable etcd (proxy or direct mode), use only antietcd
|
||||
--gen-certs
|
||||
force certificate generation even if it's not the first node
|
||||
--no-certs
|
||||
disable certificate generation
|
||||
--copy yes|no|ask
|
||||
copy vitastor.conf and TLS certificates for monitor&etcd to monitor nodes using scp
|
||||
(default is ask)
|
||||
--copy-to-osd-node NODE[,NODE2,...]
|
||||
copy vitastor.conf and TLS certificates for OSDs to NODES using scp
|
||||
`;
|
||||
|
||||
async function run()
|
||||
{
|
||||
const config_path = process.argv[2] || '/etc/vitastor/vitastor.conf';
|
||||
if (config_path == '-h' || config_path == '--help')
|
||||
let config_path = '/etc/vitastor/vitastor.conf';
|
||||
let config_dir = '/etc/vitastor/';
|
||||
let gen_certs = 'auto';
|
||||
let antietcd_only = false;
|
||||
let copy = 'ask';
|
||||
let copy_to_osd = null;
|
||||
for (let i = 2; i < process.argv.length; i++)
|
||||
{
|
||||
console.log(
|
||||
'Initialize systemd etcd service for Vitastor\n'+
|
||||
'(c) Vitaliy Filippov, 2019+ (MIT)\n'+
|
||||
'\n'+
|
||||
'USAGE:\n'+
|
||||
'1) Put the same etcd_address into /etc/vitastor/vitastor.conf on all monitor nodes\n'+
|
||||
'2) Run '+process.argv[1]+' [config_path]\n'
|
||||
);
|
||||
process.exit(0);
|
||||
const arg = process.argv[i];
|
||||
if (arg == '-h' || arg == '--help')
|
||||
{
|
||||
console.log(help_text);
|
||||
process.exit(0);
|
||||
}
|
||||
else if (arg == '--gen-certs')
|
||||
{
|
||||
gen_certs = true;
|
||||
}
|
||||
else if (arg == '--no-certs')
|
||||
{
|
||||
gen_certs = false;
|
||||
}
|
||||
else if (arg == '--antietcd-only')
|
||||
{
|
||||
antietcd_only = true;
|
||||
}
|
||||
else if (arg == '--copy-to-osd-node' && i < process.argv.length-1)
|
||||
{
|
||||
i++;
|
||||
copy_to_osd = process.argv[i].split(/,/);
|
||||
}
|
||||
else if (arg == '--copy' && i < process.argv.length-1)
|
||||
{
|
||||
i++;
|
||||
copy = process.argv[i];
|
||||
if (copy !== 'ask' && copy !== 'yes' && copy !== 'no')
|
||||
{
|
||||
console.error('--copy should be "ask", "yes" or "no"');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
else if (arg[0] == '-')
|
||||
{
|
||||
console.error('Unknown option: '+arg[0]);
|
||||
process.exit(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
config_path = arg;
|
||||
config_dir = path.dirname(arg);
|
||||
}
|
||||
}
|
||||
if (!fs.existsSync(config_path))
|
||||
{
|
||||
console.log(config_path+' is missing');
|
||||
process.exit(1);
|
||||
}
|
||||
const config = JSON.parse(fs.readFileSync(config_path, { encoding: 'utf-8' }));
|
||||
if (!config.etcd_address)
|
||||
{
|
||||
console.log("etcd_address is missing in "+config_path);
|
||||
process.exit(1);
|
||||
}
|
||||
const etcds = (config.etcd_address instanceof Array ? config.etcd_address : (''+config.etcd_address).split(/,/))
|
||||
.map(s => /^(https?):\/\/(\[[^\]]+\]|[^\[\]\:\/]+)(?::(\d+))?/.exec(s.toLowerCase()))
|
||||
.filter(s => s)
|
||||
.map(s => ({
|
||||
scheme: s[1],
|
||||
addr: s[2].indexOf(':') && s[2][0] != '[' ? '['+s[2]+']' : s[2],
|
||||
ip: s[2][0] == '[' ? s[2].substr(1, s[2].length-2) : s[2],
|
||||
port: s[3],
|
||||
}));
|
||||
const tls = etcds.filter(e => e.scheme === 'https').length > 0;
|
||||
const use_perms = tls && config.use_perms;
|
||||
const num = select_local_etcd(etcds);
|
||||
if (copy_to_osd)
|
||||
{
|
||||
copy_to_osd_nodes(copy_to_osd, config_dir, use_perms, antietcd_only);
|
||||
process.exit(0);
|
||||
}
|
||||
if (tls)
|
||||
{
|
||||
const etcd_ca = config_dir+'/'+path.basename(config.etcd_ca);
|
||||
if (gen_certs === true)
|
||||
{
|
||||
console.log('Certificate generation is requested explicitly, generating');
|
||||
}
|
||||
else if (gen_certs === false)
|
||||
{
|
||||
console.log('Certificate generation is disabled explicitly, skipping');
|
||||
}
|
||||
else if (num < 0)
|
||||
{
|
||||
gen_certs = true;
|
||||
console.log('No matching IPs in etcd_address from '+config_path+', only generating certificates');
|
||||
}
|
||||
else if (config.etcd_ca && fs.existsSync(etcd_ca))
|
||||
{
|
||||
gen_certs = false;
|
||||
console.log(etcd_ca+' already exists, assuming certificates are already generated');
|
||||
}
|
||||
else if (num === 0)
|
||||
{
|
||||
gen_certs = true;
|
||||
console.log('This is monitor node 1, generating certificates');
|
||||
}
|
||||
else
|
||||
{
|
||||
console.log('This is monitor node '+(num+1)+', '+etcd_ca+' does not exist, please copy certificates to this node');
|
||||
process.exit(1);
|
||||
}
|
||||
await write_auth_config(config, config_path, etcds, use_perms, antietcd_only);
|
||||
if (gen_certs)
|
||||
{
|
||||
if (copy === 'ask')
|
||||
copy = await ask_copy('Copy certificates and vitastor.conf to other nodes after generation?');
|
||||
copy = (copy === 'y' || copy === 'yes');
|
||||
await make_certs(config_dir, copy, etcds, use_perms, antietcd_only);
|
||||
}
|
||||
}
|
||||
if (num < 0)
|
||||
{
|
||||
console.log('No matching IPs in etcd_address from '+config_path);
|
||||
process.exit(tls && gen_certs ? 0 : 1);
|
||||
}
|
||||
await configure_etcd(etcds, num, tls, use_perms);
|
||||
await enable_mon();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
async function ask_copy(question)
|
||||
{
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
prompt: '> ',
|
||||
});
|
||||
let copy;
|
||||
while (copy != 'y' && copy != 'n' && copy != 'yes' && copy != 'no')
|
||||
{
|
||||
if (copy)
|
||||
console.log('Please type "yes" or "no"');
|
||||
copy = await new Promise(ok => rl.question(question, ok));
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
async function copy_to_osd_nodes(to, dir, use_perms, antietcd_only)
|
||||
{
|
||||
const osd_to_copy = [ 'vitastor.conf' ];
|
||||
if (!antietcd_only && !use_perms)
|
||||
osd_to_copy.push('etcd_ca.crt');
|
||||
else
|
||||
osd_to_copy.push('antietcd_ca.crt');
|
||||
if (use_perms)
|
||||
osd_to_copy.push('osd.crt', 'osd.key', 'client_ca.crt');
|
||||
console.warn('Copying configuration to OSD nodes '+to.join(', '));
|
||||
for (const node of to)
|
||||
await system("scp "+dir+osd_to_copy.join(" "+dir)+" root@"+node+":/etc/vitastor/");
|
||||
}
|
||||
|
||||
async function make_certs(dir, copy, etcds, use_perms, antietcd_only)
|
||||
{
|
||||
console.log(`-----
|
||||
Generating certificates in ${dir}
|
||||
-----
|
||||
`);
|
||||
const to_copy = [ 'vitastor.conf' ];
|
||||
const osd_to_copy = [ 'vitastor.conf' ];
|
||||
if (!antietcd_only)
|
||||
{
|
||||
await make_ca("/O=Vitastor etcd CA", dir+"etcd_ca");
|
||||
await make_signed("/CN=Vitastor etcd", dir+"etcd", dir+"etcd_ca", etcds.map(e => "IP:"+e.ip).join(','));
|
||||
to_copy.push('etcd_ca.crt', 'etcd.crt', 'etcd.key');
|
||||
if (!use_perms)
|
||||
osd_to_copy.push('etcd_ca.crt');
|
||||
}
|
||||
if (use_perms || antietcd_only)
|
||||
{
|
||||
await make_ca("/O=Vitastor Antietcd CA", dir+"antietcd_ca");
|
||||
await make_signed("/CN=Vitastor Antietcd", dir+"antietcd", dir+"antietcd_ca", etcds.map(e => "IP:"+e.ip).join(','));
|
||||
to_copy.push('antietcd_ca.crt', 'antietcd.crt', 'antietcd.key');
|
||||
osd_to_copy.push('antietcd_ca.crt');
|
||||
}
|
||||
if (use_perms)
|
||||
{
|
||||
await make_ca("/CN=Vitastor OSD", dir+"osd");
|
||||
await make_ca("/O=Vitastor Client CA", dir+"client_ca");
|
||||
await make_signed("/CN=admin", dir+"admin", dir+"client_ca");
|
||||
to_copy.push('osd.crt', 'osd.key', 'client_ca.crt');
|
||||
osd_to_copy.push('osd.crt', 'osd.key', 'client_ca.crt');
|
||||
}
|
||||
console.log(`-----
|
||||
Certificates generated, commands to copy them:
|
||||
- Monitor+OSD node:
|
||||
cd ${dir} && scp ${to_copy.join(' ')} root@NODE:/etc/vitastor/
|
||||
- Monitor node:
|
||||
cd ${dir} && scp ${to_copy.filter(f => f != 'osd.key').join(' ')} root@NODE:/etc/vitastor/
|
||||
- OSD node:
|
||||
cd ${dir} && scp ${osd_to_copy.join(' ')} root@NODE:/etc/vitastor/
|
||||
-----
|
||||
`);
|
||||
if (copy)
|
||||
{
|
||||
for (const node of etcds)
|
||||
{
|
||||
await system("scp "+dir+to_copy.join(" "+dir)+" root@"+node.ip+":/etc/vitastor/");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
console.warn('Certificates generated in '+dir+', please copy them to other nodes');
|
||||
}
|
||||
}
|
||||
|
||||
async function write_auth_config(config, config_path, etcds, use_perms, antietcd_only)
|
||||
{
|
||||
const auth = {};
|
||||
if (use_perms)
|
||||
{
|
||||
auth["use_antietcd"] = true;
|
||||
if (!antietcd_only)
|
||||
{
|
||||
auth["etcd_proxy"] = {
|
||||
urls: etcds.map(e => e.ip+':2381'),
|
||||
cert: "/etc/vitastor/antietcd.crt",
|
||||
key: "/etc/vitastor/antietcd.key",
|
||||
ca: "/etc/vitastor/etcd_ca.crt",
|
||||
};
|
||||
}
|
||||
auth["antietcd_cert"] = "/etc/vitastor/antietcd.crt";
|
||||
auth["antietcd_key"] = "/etc/vitastor/antietcd.key";
|
||||
auth["etcd_ca"] = "/etc/vitastor/antietcd_ca.crt";
|
||||
auth["osd_cert"] = "/etc/vitastor/osd.crt";
|
||||
auth["osd_pkey"] = "/etc/vitastor/osd.key";
|
||||
auth["osd_ca"] = "/etc/vitastor/osd.crt";
|
||||
auth["client_ca"] = "/etc/vitastor/client_ca.crt";
|
||||
auth["cert"] = "/etc/vitastor/admin.crt";
|
||||
auth["pkey"] = "/etc/vitastor/admin.key";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (antietcd_only)
|
||||
{
|
||||
auth["use_antietcd"] = true;
|
||||
auth["antietcd_cert"] = "/etc/vitastor/antietcd.crt";
|
||||
auth["antietcd_key"] = "/etc/vitastor/antietcd.key";
|
||||
auth["etcd_ca"] = "/etc/vitastor/antietcd_ca.crt";
|
||||
}
|
||||
else
|
||||
{
|
||||
auth["etcd_ca"] = "/etc/vitastor/etcd.crt";
|
||||
}
|
||||
}
|
||||
for (const k in auth)
|
||||
{
|
||||
if ((k in config) && JSON.stringify(auth[k]) != JSON.stringify(config[k]))
|
||||
{
|
||||
// Auth options already overridden with non-default
|
||||
console.log(k+" is already overridden in "+config_path+", skipping config update");
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (const k in auth)
|
||||
{
|
||||
config[k] = auth[k];
|
||||
}
|
||||
console.log(`-----
|
||||
Updating ${config_path}
|
||||
-----
|
||||
`);
|
||||
fs.writeFileSync(config_path, JSON.stringify(config, 0, 4));
|
||||
}
|
||||
|
||||
async function configure_etcd(etcds, num, tls, use_perms)
|
||||
{
|
||||
const in_docker = fs.existsSync("/etc/vitastor/etcd.conf") &&
|
||||
fs.existsSync("/etc/vitastor/docker.conf");
|
||||
if (!in_docker && fs.existsSync("/etc/systemd/system/vitastor-etcd.service"))
|
||||
@@ -45,37 +331,55 @@ async function run()
|
||||
console.log("/etc/systemd/system/etcd.service already exists");
|
||||
process.exit(1);
|
||||
}
|
||||
const config = JSON.parse(fs.readFileSync(config_path, { encoding: 'utf-8' }));
|
||||
if (!config.etcd_address)
|
||||
const etcd_url = etcds[num].scheme + '://' + etcds[num].addr;
|
||||
const options = {
|
||||
name: 'etcd'+etcds[num].ip.replace(/[^0-9a-z_]/ig, '_'),
|
||||
advertise_client_urls: etcd_url+':'+(use_perms ? 2381 : 2379),
|
||||
listen_client_urls: etcd_url+':'+(use_perms ? 2381 : 2379),
|
||||
initial_advertise_peer_urls: etcd_url+':2380',
|
||||
listen_peer_urls: etcd_url+':2380',
|
||||
initial_cluster_token: 'vitastor-etcd-1',
|
||||
initial_cluster_state: 'new',
|
||||
initial_cluster: etcds.map(e => `etcd${e.ip.replace(/[^0-9a-z_]/ig, '_')}=${e.scheme}://${e.addr}:2380`).join(','),
|
||||
snapshot_count: 10000,
|
||||
max_txn_ops: 100000,
|
||||
max_request_bytes: 104857600,
|
||||
auto_compaction_retention: 10,
|
||||
auto_compaction_mode: 'revision',
|
||||
};
|
||||
if (tls)
|
||||
{
|
||||
console.log("etcd_address is missing in "+config_path);
|
||||
process.exit(1);
|
||||
options['cert_file'] = '/etc/vitastor/etcd.crt';
|
||||
options['key_file'] = '/etc/vitastor/etcd.key';
|
||||
if (use_perms)
|
||||
{
|
||||
options['client_cert_auth'] = '1';
|
||||
options['trusted_ca_file'] = '/etc/vitastor/antietcd.crt';
|
||||
}
|
||||
options['peer_cert_file'] = '/etc/vitastor/etcd.crt';
|
||||
options['peer_key_file'] = '/etc/vitastor/etcd.key';
|
||||
if (use_perms)
|
||||
{
|
||||
options['peer_client_cert_auth'] = '1';
|
||||
options['peer_trusted_ca_file'] = '/etc/vitastor/etcd.crt';
|
||||
}
|
||||
}
|
||||
const etcds = (config.etcd_address instanceof Array ? config.etcd_address : (''+config.etcd_address).split(/,/))
|
||||
.map(s => (''+s).replace(/^https?:\/\/|(:\d+)?(\/.*)?$/g, '').replace(/^\[(.*)\]$/, '$1').toLowerCase());
|
||||
const num = select_local_etcd(etcds);
|
||||
if (num < 0)
|
||||
let etcd_conf = fs.existsSync("/etc/vitastor/etcd.conf")
|
||||
? fs.readFileSync("/etc/vitastor/etcd.conf", { encoding: 'utf-8' })
|
||||
: "";
|
||||
for (const k in options)
|
||||
{
|
||||
console.log('No matching IPs in etcd_address from '+config_path);
|
||||
process.exit(0);
|
||||
etcd_conf = replace_env(etcd_conf, 'ETCD_'+k.toUpperCase().replace(/-/, '_'), options[k]);
|
||||
}
|
||||
const etcd_url = 'http://' + (etcds[num].indexOf(':') >= 0 ? '['+etcds[num]+']' : etcds[num]);
|
||||
const etcd_name = 'etcd'+etcds[num].replace(/[^0-9a-z_]/ig, '_');
|
||||
const etcd_cluster = etcds.map(e => `etcd${e.replace(/[^0-9a-z_]/ig, '_')}=http://${e.indexOf(':') >= 0 ? '['+e+']' : e}:2380`).join(',');
|
||||
fs.writeFileSync("/etc/vitastor/etcd.conf", etcd_conf);
|
||||
if (in_docker)
|
||||
{
|
||||
let etcd_conf = fs.readFileSync("/etc/vitastor/etcd.conf", { encoding: 'utf-8' });
|
||||
etcd_conf = replace_env(etcd_conf, 'ETCD_NAME', etcd_name);
|
||||
etcd_conf = replace_env(etcd_conf, 'ETCD_IP', etcds[num]);
|
||||
etcd_conf = replace_env(etcd_conf, 'ETCD_INITIAL_CLUSTER', etcd_cluster);
|
||||
fs.writeFileSync("/etc/vitastor/etcd.conf", etcd_conf);
|
||||
console.log('etcd for Vitastor configured. Run `systemctl enable --now vitastor-etcd` to start etcd');
|
||||
process.exit(0);
|
||||
}
|
||||
await system(`mkdir -p /var/lib/etcd/vitastor`);
|
||||
fs.writeFileSync(
|
||||
"/etc/systemd/system/vitastor-etcd.service",
|
||||
`[Unit]
|
||||
"/etc/systemd/system/vitastor-etcd.service", `[Unit]
|
||||
Description=etcd for vitastor
|
||||
After=network-online.target local-fs.target time-sync.target
|
||||
Wants=network-online.target local-fs.target time-sync.target
|
||||
@@ -83,12 +387,8 @@ Wants=network-online.target local-fs.target time-sync.target
|
||||
[Service]
|
||||
Restart=always
|
||||
Environment=GOGC=50
|
||||
ExecStart=etcd --name ${etcd_name} --data-dir /var/lib/etcd/vitastor \\
|
||||
--snapshot-count 10000 --advertise-client-urls ${etcd_url}:2379 --listen-client-urls ${etcd_url}:2379 \\
|
||||
--initial-advertise-peer-urls ${etcd_url}:2380 --listen-peer-urls ${etcd_url}:2380 \\
|
||||
--initial-cluster-token vitastor-etcd-1 --initial-cluster ${etcd_cluster} \\
|
||||
--initial-cluster-state new --max-txn-ops=100000 --max-request-bytes=104857600 \\
|
||||
--auto-compaction-retention=10 --auto-compaction-mode=revision
|
||||
EnvironmentFile=/etc/vitastor/etcd.conf
|
||||
ExecStart=etcd --data-dir /var/lib/etcd/vitastor
|
||||
WorkingDirectory=/var/lib/etcd/vitastor
|
||||
ExecStartPre=+chown -R etcd /var/lib/etcd/vitastor
|
||||
User=etcd
|
||||
@@ -106,7 +406,11 @@ WantedBy=multi-user.target
|
||||
// Disable distribution etcd unit and enable our one
|
||||
await system(`systemctl disable --now etcd`);
|
||||
await system(`systemctl enable --now vitastor-etcd`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
async function enable_mon()
|
||||
{
|
||||
await system(`systemctl enable --now vitastor-mon`);
|
||||
}
|
||||
|
||||
function replace_env(text, key, value)
|
||||
@@ -119,16 +423,29 @@ function replace_env(text, key, value)
|
||||
function select_local_etcd(etcds)
|
||||
{
|
||||
const ifaces = os.networkInterfaces();
|
||||
const local = {};
|
||||
for (const ifname in ifaces)
|
||||
{
|
||||
for (const iface of ifaces[ifname])
|
||||
for (let i = 0; i < etcds.length; i++)
|
||||
if (etcds[i] == iface.address.toLowerCase())
|
||||
return i;
|
||||
{
|
||||
const addr = iface.address;
|
||||
if (iface.family == 'IPv6')
|
||||
local[addr.toLowerCase()] = local['['+addr.toLowerCase()+']'] = true;
|
||||
else
|
||||
local[addr] = true;
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < etcds.length; i++)
|
||||
{
|
||||
if (local[etcds[i].addr])
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
async function system(cmd)
|
||||
{
|
||||
console.log('Running '+cmd);
|
||||
const cp = child_process.spawn(cmd, { shell: true, stdio: [ 0, 1, 2 ] });
|
||||
let finish_cb;
|
||||
cp.on('exit', () => finish_cb && finish_cb());
|
||||
@@ -136,3 +453,19 @@ async function system(cmd)
|
||||
await new Promise(ok => finish_cb = ok);
|
||||
return cp.exitCode;
|
||||
}
|
||||
|
||||
async function make_ca(subj, filename)
|
||||
{
|
||||
if (await system("openssl req -days 3650 -x509 -subj '"+subj+"' -addext basicConstraints=critical,CA:TRUE,pathlen:1"+
|
||||
" -new -newkey rsa:4096 -nodes -keyout "+filename+".key -out "+filename+".crt"))
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
async function make_signed(subj, f, ca, san)
|
||||
{
|
||||
if (await system(`openssl req -subj '${subj}' ${san ? "-addext 'subjectAltName="+san+"'" : ""} -nodes -new -keyout ${f}.key -out ${f}.csr`))
|
||||
process.exit(1);
|
||||
if (await system(`openssl x509 -req -days 3650 -CA ${ca}.crt -CAkey ${ca}.key -CAcreateserial -in ${f}.csr -out ${f}.crt`))
|
||||
process.exit(1);
|
||||
fs.unlinkSync(f+".csr");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,539 @@
|
||||
// AntiEtcd authentication filter for Vitastor
|
||||
// (c) Vitaliy Filippov, 2026
|
||||
// License: Mozilla Public License 2.0 or Vitastor Network Public License 1.1
|
||||
|
||||
// Permissions are based on:
|
||||
// 1. Users.
|
||||
// Stored in /vitastor/config/user/<username>.
|
||||
// Has 2 properties:
|
||||
// - type, one of: admin, client.
|
||||
// admin has full access to all images and also to cluster config.
|
||||
// client has r/w access to owned images and r/o access to images with reader_group.
|
||||
// - groups, a list of group names the user is included in.
|
||||
// 2. Images.
|
||||
// Stored in /vitastor/config/inode/<pool>/<inode>. Has the following properties:
|
||||
// - owner (user name)
|
||||
// - owner_group (group name)
|
||||
// - reader_group
|
||||
// 3. Certificates.
|
||||
// - osd, mon use their own trusted certificates.
|
||||
|
||||
const { X509Certificate } = require('node:crypto');
|
||||
|
||||
const static_perms = {
|
||||
invalid: {
|
||||
keys: {},
|
||||
prefixes: {},
|
||||
},
|
||||
osd: {
|
||||
keys: { '/pg/config': false },
|
||||
prefixes: { '/config/': false, '/osd/': true, '/pg/state/': true, '/pg/history/': true, '/pgstats/': true },
|
||||
},
|
||||
mon: {
|
||||
keys: { '/pg/config': true, '/stats': true, '/history/last_clean_pgs': true },
|
||||
prefixes: {
|
||||
'/config/': false, '/osd/': false, '/mon/': true, '/pg/history/': true,
|
||||
'/pgstats/': false, '/inode/stats/': true, '/pool/stats/': true,
|
||||
},
|
||||
},
|
||||
admin: {
|
||||
keys: { '/stats': false },
|
||||
prefixes: {
|
||||
'/config/': true, '/osd/': true, '/index/': true, '/pg/history/': true,
|
||||
'/mon/': false, '/pg/': false, '/pgstats/': false, '/inode/stats/': false, '/pool/stats/': false,
|
||||
},
|
||||
},
|
||||
client: {
|
||||
keys: { '/config/global': false, '/config/node_placement': false, '/config/pools': false, '/pg/config': false },
|
||||
prefixes: { '/osd/state/': false, '/pg/state/': false, '/index/maxid/': false },
|
||||
},
|
||||
};
|
||||
|
||||
const api_perms = {
|
||||
osd: { lease_grant: true, lease_revoke: true, lease_keepalive: true, maintenance_status: true },
|
||||
mon: { lease_grant: true, lease_revoke: true, lease_keepalive: true, maintenance_status: true },
|
||||
admin: { maintenance_status: true },
|
||||
client: { maintenance_status: true },
|
||||
};
|
||||
|
||||
class VitastorAuthFilter
|
||||
{
|
||||
constructor(antietcd)
|
||||
{
|
||||
this.cfg = antietcd.cfg;
|
||||
this.antietcd = antietcd;
|
||||
this.prefix = this.cfg.vitastor_prefix || '/vitastor';
|
||||
this.prefix_parts = this.prefix.split('/');
|
||||
}
|
||||
|
||||
async init()
|
||||
{
|
||||
if (!this.cfg.cert || !this.cfg.key || !this.cfg.ca || !this.cfg.osd_ca || !this.cfg.client_cert_auth)
|
||||
{
|
||||
throw new Error('Authenticated Vitastor setups require enabled client_cert_auth, cert, key'+
|
||||
' and separate ca (client CA), osd_ca and optionally mon_ca');
|
||||
}
|
||||
this.osd_ca = await this.antietcd.readPEM(this.cfg.osd_ca);
|
||||
this.osd_ca_obj = new X509Certificate(this.osd_ca);
|
||||
this.antietcd.tls.ca.push(this.osd_ca);
|
||||
if (this.cfg.mon_ca)
|
||||
{
|
||||
this.mon_ca = await this.antietcd.readPEM(this.cfg.mon_ca);
|
||||
this.mon_ca_obj = new X509Certificate(this.mon_ca_obj);
|
||||
this.antietcd.tls.ca.push(this.mon_ca);
|
||||
}
|
||||
}
|
||||
|
||||
init_context(context, clientCert)
|
||||
{
|
||||
let cert = clientCert;
|
||||
while (cert)
|
||||
{
|
||||
if (cert.fingerprint256 == this.osd_ca_obj.fingerprint256)
|
||||
{
|
||||
context.user_type = 'osd';
|
||||
break;
|
||||
}
|
||||
if (this.mon_ca_obj && cert.fingerprint256 == this.mon_ca_obj.fingerprint256)
|
||||
{
|
||||
context.user_type = 'mon';
|
||||
break;
|
||||
}
|
||||
cert = cert.issuerCertificate;
|
||||
}
|
||||
}
|
||||
|
||||
_get(path, decode)
|
||||
{
|
||||
let cur = this.antietcd.etctree.state;
|
||||
path = path instanceof Array ? path : path.split('/');
|
||||
for (const p of path)
|
||||
{
|
||||
if (!cur.children)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
cur = cur.children[p];
|
||||
if (!cur)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (decode)
|
||||
{
|
||||
return this._decode(path, cur.value);
|
||||
}
|
||||
return cur;
|
||||
}
|
||||
|
||||
_decode(path, cur)
|
||||
{
|
||||
if (!cur)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (cur)
|
||||
{
|
||||
try
|
||||
{
|
||||
cur = JSON.parse(cur);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
console.warn('Invalid JSON in '+(path instanceof Array ? path.join('/') : path)+': '+e);
|
||||
}
|
||||
}
|
||||
return cur;
|
||||
}
|
||||
|
||||
// userInfo: { name: string, type: string, perms: static_perms[type], groups: { [string]: true } }
|
||||
_check_compare(check, userInfo, checked)
|
||||
{
|
||||
let key = String(check.key);
|
||||
if (key.substr(0, this.prefix.length) !== this.prefix)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
key = key.substr(this.prefix.length);
|
||||
if (key in userInfo.perms.keys)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
for (const pfx in userInfo.perms.prefixes)
|
||||
{
|
||||
if (key.substr(0, pfx.length) == pfx)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (userInfo.type == 'client')
|
||||
{
|
||||
// Image permissions
|
||||
if (key.substr(0, 14) == '/config/inode/')
|
||||
{
|
||||
// Allowed to check that a key does not exist
|
||||
if (check.target == 'VERSION' && check.version == 0)
|
||||
{
|
||||
checked['M'+key] = true;
|
||||
return true;
|
||||
}
|
||||
else if (check.target == 'MOD')
|
||||
{
|
||||
const data = this._get(check.key);
|
||||
if (!data || data.mod_revision != check.mod_revision)
|
||||
{
|
||||
// Break check to trigger CAS failure
|
||||
check.mod_revision = '18446744073709551615'; // UINT64_MAX
|
||||
return true;
|
||||
}
|
||||
const inode = this._decode(check.key, data.value);
|
||||
if (inode && (inode.owner_group && userInfo.groups[inode.owner_group] ||
|
||||
inode.owner === userInfo.name))
|
||||
{
|
||||
checked['M'+key] = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (key.substr(0, 13) == '/index/image/')
|
||||
{
|
||||
// Allowed to check that a key does not exist
|
||||
if (check.target == 'VERSION' && check.version == 0)
|
||||
{
|
||||
checked['M'+key] = true;
|
||||
return true;
|
||||
}
|
||||
else if (check.target == 'MOD')
|
||||
{
|
||||
let data = this._get(check.key);
|
||||
if (!data || data.mod_revision != check.mod_revision)
|
||||
{
|
||||
// Break check to trigger CAS failure
|
||||
check.mod_revision = '18446744073709551615'; // UINT64_MAX
|
||||
return true;
|
||||
}
|
||||
data = this._decode(check.key, data.value);
|
||||
if (data)
|
||||
{
|
||||
const inode = this._get([ ...this.prefix_parts, 'config', 'inode', data.pool_id, data.id ], true);
|
||||
if (inode && (inode.owner_group && userInfo.groups[inode.owner_group] ||
|
||||
inode.owner === userInfo.name))
|
||||
{
|
||||
checked['M'+key] = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (key.substr(0, 13) == '/index/maxid/')
|
||||
{
|
||||
const pool_id = key.substr(13);
|
||||
const pool_cfg = this._get([ ...this.prefix_parts, 'config', 'pools' ], true);
|
||||
if (!pool_cfg || !pool_cfg[pool_id] || !pool_cfg[pool_id].creator_group || !userInfo.groups[pool_cfg[pool_id].creator_group])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (check.target == 'VERSION' && check.version == 0)
|
||||
{
|
||||
checked['I'+parseInt(key.substr(13))+'_0'] = true;
|
||||
return true;
|
||||
}
|
||||
else if (check.target == 'MOD')
|
||||
{
|
||||
const data = this._get(check.key);
|
||||
if (!data || data.mod_revision != check.mod_revision)
|
||||
{
|
||||
// Break check to trigger CAS failure
|
||||
check.mod_revision = '18446744073709551615'; // UINT64_MAX
|
||||
return true;
|
||||
}
|
||||
checked['I'+parseInt(key.substr(13))+'_'+data.value] = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
_check_read(kv, userInfo)
|
||||
{
|
||||
let key = String(kv.key);
|
||||
if (key.substr(0, this.prefix.length) !== this.prefix)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
key = key.substr(this.prefix.length);
|
||||
if (key in userInfo.perms.keys)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
for (const pfx in userInfo.perms.prefixes)
|
||||
{
|
||||
if (key.substr(0, pfx.length) == pfx)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (userInfo.type == 'client')
|
||||
{
|
||||
// Image permissions
|
||||
if (key.substr(0, 14) == '/config/inode/')
|
||||
{
|
||||
const inode = this._decode(kv.key, kv.value);
|
||||
if (inode && (inode.reader_group && userInfo.groups[inode.reader_group] ||
|
||||
inode.owner_group && userInfo.groups[inode.owner_group] ||
|
||||
inode.owner === userInfo.name))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (key.substr(0, 13) == '/index/image/')
|
||||
{
|
||||
const data = this._decode(kv.key, kv.value);
|
||||
const inode = this._get([ ...this.prefix_parts, 'config', 'inode', data.pool_id, data.id ], true);
|
||||
if (inode && (inode.reader_group && userInfo.groups[inode.reader_group] ||
|
||||
inode.owner_group && userInfo.groups[inode.owner_group] ||
|
||||
inode.owner === userInfo.name))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (key.substr(0, 13) == '/inode/stats/')
|
||||
{
|
||||
const [ pool_id, id ] = key.substr(13).split('/');
|
||||
const inode = this._get([ ...this.prefix_parts, 'config', 'inode', pool_id, id ], true);
|
||||
if (inode && (inode.reader_group && userInfo.groups[inode.reader_group] ||
|
||||
inode.owner_group && userInfo.groups[inode.owner_group] ||
|
||||
inode.owner === userInfo.name))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
_check_write(put, userInfo, checked)
|
||||
{
|
||||
let key = String(put.key);
|
||||
if (key.substr(0, this.prefix.length) !== this.prefix)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
key = key.substr(this.prefix.length);
|
||||
if (userInfo.perms.keys[key])
|
||||
{
|
||||
return true;
|
||||
}
|
||||
for (const pfx in userInfo.perms.prefixes)
|
||||
{
|
||||
if (userInfo.perms.prefixes[pfx] && key.substr(0, pfx.length) == pfx)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (checked && userInfo.type == 'client')
|
||||
{
|
||||
if (key.substr(0, 13) == '/index/maxid/' &&
|
||||
checked['I'+parseInt(key.substr(13))+'_'+(put.value-1)])
|
||||
{
|
||||
// Allowed to increment maxid
|
||||
return true;
|
||||
}
|
||||
if (checked['M'+key])
|
||||
{
|
||||
// Allowed to modify known images with CAS checks
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
_check_req(req, userInfo, checked)
|
||||
{
|
||||
let r;
|
||||
if ((r = (req.request_range || req.requestRange)))
|
||||
{
|
||||
// All range queries are allowed, but responses are filtered - it's simpler
|
||||
}
|
||||
else if ((r = (req.request_put || req.requestPut)))
|
||||
{
|
||||
if (!this._check_write(r, userInfo, checked))
|
||||
return false;
|
||||
}
|
||||
else if ((r = (req.request_delete_range || req.requestDeleteRange)))
|
||||
{
|
||||
if (!r.range_end || r.range_end === r.key)
|
||||
{
|
||||
if (!this._check_write({ key: r.key }, userInfo))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// All keys in range must satisfy prefix
|
||||
r.range_end = String(r.range_end);
|
||||
if (r.key.length != r.range_end.length ||
|
||||
r.key[r.key.length-1] != '/' ||
|
||||
r.range_end[r.range_end.length-1] != '0')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
let key = r.key.substr(this.prefix.length);
|
||||
let found = false;
|
||||
for (const pfx in userInfo.perms.prefixes)
|
||||
{
|
||||
if (userInfo.perms.prefixes[pfx] && key.substr(0, pfx.length) == pfx)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
_get_user(context)
|
||||
{
|
||||
if (context.user_type === 'osd' || context.user_type === 'mon')
|
||||
{
|
||||
return {
|
||||
name: context.user_type,
|
||||
type: context.user_type,
|
||||
perms: static_perms[context.user_type],
|
||||
};
|
||||
}
|
||||
if (!context.username)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
let userInfo = this._get([ ...this.prefix_parts, 'config', 'user', context.username ], true);
|
||||
if (!userInfo)
|
||||
{
|
||||
userInfo = { type: 'client' };
|
||||
}
|
||||
else if (userInfo.type !== 'client' && userInfo.type !== 'admin')
|
||||
{
|
||||
userInfo.type = 'client';
|
||||
}
|
||||
userInfo.perms = static_perms[userInfo.type] || static_perms['invalid'];
|
||||
userInfo.name = context.username;
|
||||
if (userInfo.groups instanceof Array)
|
||||
{
|
||||
userInfo.groups = userInfo.groups.reduce((a, c) => { a[c] = true; return a; }, {});
|
||||
}
|
||||
else
|
||||
{
|
||||
userInfo.groups = {};
|
||||
}
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
filter_api(context, api/*, data*/)
|
||||
{
|
||||
let type = 'client';
|
||||
if (context.user_type === 'osd' || context.user_type === 'mon')
|
||||
{
|
||||
type = context.user_type;
|
||||
}
|
||||
else if (context.username)
|
||||
{
|
||||
const userInfo = this._get([ ...this.prefix_parts, 'config', 'user', context.username ], true);
|
||||
if (userInfo && userInfo.type === 'admin')
|
||||
{
|
||||
type = 'admin';
|
||||
}
|
||||
}
|
||||
return api_perms[type] && api_perms[type][api];
|
||||
}
|
||||
|
||||
filter_txn(context, txn)
|
||||
{
|
||||
const userInfo = this._get_user(context);
|
||||
if (!userInfo)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
const checked = {};
|
||||
if (txn.compare)
|
||||
{
|
||||
for (const check of txn.compare)
|
||||
{
|
||||
if (!this._check_compare(check, userInfo, checked))
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// Special transactions:
|
||||
// 1. create image: create config/inode and index/image, increment index/maxid/<pool> (with CAS)
|
||||
// 2. create snapshot: same as create image but also rename previous to @snap
|
||||
if (txn.success)
|
||||
{
|
||||
for (const req of txn.success)
|
||||
{
|
||||
if (!this._check_req(req, userInfo, checked))
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (txn.failure)
|
||||
{
|
||||
for (const req of txn.failure)
|
||||
{
|
||||
if (!this._check_req(req, userInfo, null))
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return txn;
|
||||
}
|
||||
|
||||
filter_txn_response(context, txn, res)
|
||||
{
|
||||
if (!res.responses)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const userInfo = this._get_user(context);
|
||||
if (!userInfo)
|
||||
{
|
||||
for (const resp of res.responses)
|
||||
{
|
||||
if (resp.response_range && resp.response_range.kvs)
|
||||
{
|
||||
resp.response_range.kvs = [];
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (const resp of res.responses)
|
||||
{
|
||||
if (resp.response_range && resp.response_range.kvs)
|
||||
{
|
||||
resp.response_range.kvs = resp.response_range.kvs.filter(kv => this._check_read(kv, userInfo));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filter_watch_message(context, msg)
|
||||
{
|
||||
if (!msg.result || !msg.result.events)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const userInfo = this._get_user(context);
|
||||
if (!userInfo)
|
||||
{
|
||||
msg.result.events = [];
|
||||
return;
|
||||
}
|
||||
msg.result.events = msg.result.events.filter(ev => this._check_read(ev.kv, userInfo));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = VitastorAuthFilter;
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vitastor",
|
||||
"version": "3.0.14",
|
||||
"version": "3.0.15",
|
||||
"description": "Low-level native bindings to Vitastor client library",
|
||||
"main": "index.js",
|
||||
"keywords": [
|
||||
|
||||
+45
-10
@@ -366,15 +366,38 @@ sub map_volume
|
||||
my $prefix = defined $scfg->{vitastor_prefix} ? $scfg->{vitastor_prefix} : 'pve/';
|
||||
|
||||
my ($vtype, $img_name, $vmid) = $class->parse_volname($volname);
|
||||
my $name = $img_name;
|
||||
my $name = $prefix.$img_name;
|
||||
$name .= '@'.$snapname if $snapname;
|
||||
|
||||
my $mapped = run_cli($scfg, [ 'ls' ], binary => '/usr/bin/vitastor-nbd');
|
||||
my ($kerneldev) = grep { $mapped->{$_}->{image} eq $prefix.$name } keys %$mapped;
|
||||
return $kerneldev if $kerneldev && -b $kerneldev; # already mapped
|
||||
my ($kerneldev) = grep {
|
||||
$mapped->{$_} && $mapped->{$_}->{image} && $mapped->{$_}->{image} eq $name
|
||||
} keys %$mapped;
|
||||
|
||||
$kerneldev = run_cli($scfg, [ 'map', '--image', $prefix.$name ], binary => '/usr/bin/vitastor-nbd', json => 0);
|
||||
return $kerneldev;
|
||||
if ($kerneldev && -b $kerneldev)
|
||||
{
|
||||
my $size = `/usr/sbin/blockdev --getsize64 $kerneldev`;
|
||||
return $kerneldev if $size && $size > 0;
|
||||
}
|
||||
|
||||
my $map_out = run_cli($scfg, [ 'map', '--image', $name ], binary => '/usr/bin/vitastor-nbd', json => 0);
|
||||
$map_out =~ s/^\s+|\s+$//gso;
|
||||
|
||||
# Wait until the device is started
|
||||
for (my $i = 0; $i < 100; $i++)
|
||||
{
|
||||
$mapped = run_cli($scfg, [ 'ls' ], binary => '/usr/bin/vitastor-nbd');
|
||||
($kerneldev) = grep { $mapped->{$_} && $mapped->{$_}->{image} && $mapped->{$_}->{image} eq $name } keys %$mapped;
|
||||
if ($kerneldev && -b $kerneldev)
|
||||
{
|
||||
my $size = `/usr/sbin/blockdev --getsize64 $kerneldev`;
|
||||
return $kerneldev if $size && $size > 0;
|
||||
}
|
||||
select(undef, undef, undef, 0.1);
|
||||
}
|
||||
|
||||
die "Failed to map Vitastor image $name via NBD".
|
||||
($map_out ? ", vitastor-nbd map returned '$map_out'" : "")."\n";
|
||||
}
|
||||
|
||||
sub unmap_volume
|
||||
@@ -383,13 +406,19 @@ sub unmap_volume
|
||||
my $prefix = defined $scfg->{vitastor_prefix} ? $scfg->{vitastor_prefix} : 'pve/';
|
||||
|
||||
my ($vtype, $name, $vmid) = $class->parse_volname($volname);
|
||||
$name = $prefix.$name;
|
||||
$name .= '@'.$snapname if $snapname;
|
||||
|
||||
my $mapped = run_cli($scfg, [ 'ls' ], binary => '/usr/bin/vitastor-nbd');
|
||||
my ($kerneldev) = grep { $mapped->{$_}->{image} eq $prefix.$name } keys %$mapped;
|
||||
if ($kerneldev && -b $kerneldev)
|
||||
|
||||
my @kerneldevs = grep {
|
||||
$mapped->{$_} && $mapped->{$_}->{image} && $mapped->{$_}->{image} eq $name
|
||||
} keys %$mapped;
|
||||
|
||||
for my $kerneldev (@kerneldevs)
|
||||
{
|
||||
run_cli($scfg, [ 'unmap', $kerneldev ], binary => '/usr/bin/vitastor-nbd', json => 0);
|
||||
next if !$kerneldev || !-b $kerneldev;
|
||||
eval { run_cli($scfg, [ 'unmap', $kerneldev ], binary => '/usr/bin/vitastor-nbd', json => 0); };
|
||||
warn "Failed to unmap Vitastor image $name from $kerneldev: $@" if $@;
|
||||
}
|
||||
|
||||
return 1;
|
||||
@@ -405,7 +434,13 @@ sub activate_volume
|
||||
sub deactivate_volume
|
||||
{
|
||||
my ($class, $storeid, $scfg, $volname, $snapname, $cache) = @_;
|
||||
$class->unmap_volume($storeid, $scfg, $volname, $snapname) if $scfg->{vitastor_nbd};
|
||||
|
||||
# Even with vitastor_nbd=0, Proxmox may call map_volume() for special
|
||||
# volumes like tpmstate0 because swtpm needs a local file/block path.
|
||||
# Therefore, always try to unmap an existing NBD mapping here.
|
||||
# unmap_volume() is a no-op if the volume is not currently mapped.
|
||||
$class->unmap_volume($storeid, $scfg, $volname, $snapname);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ from cinder.volume import configuration
|
||||
from cinder.volume import driver
|
||||
from cinder.volume import volume_utils
|
||||
|
||||
VITASTOR_VERSION = '3.0.14'
|
||||
VITASTOR_VERSION = '3.0.15'
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ WORKDIR /root
|
||||
RUN sed -i 's/enabled=0/enabled=1/' /etc/yum.repos.d/*.repo
|
||||
RUN dnf -y install epel-release dnf-plugins-core
|
||||
RUN dnf -y install https://vitastor.io/rpms/centos/10/vitastor-release-1.0-1.el10.noarch.rpm
|
||||
RUN dnf -y install gcc-c++ gperftools-devel fio nodejs rpm-build jerasure-devel isa-l-devel gf-complete-devel rdma-core-devel cmake libnl3-devel
|
||||
RUN dnf -y install gcc-c++ gperftools-devel fio nodejs rpm-build jerasure-devel isa-l-devel gf-complete-devel rdma-core-devel cmake libnl3-devel c-ares-devel
|
||||
RUN dnf download --source fio
|
||||
RUN rpm --nomd5 -i fio*.src.rpm
|
||||
RUN cd ~/rpmbuild/SPECS && dnf builddep -y --spec fio.spec
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Name: vitastor
|
||||
Version: 3.0.14
|
||||
Version: 3.0.15
|
||||
Release: 1%{?dist}
|
||||
Summary: Vitastor, a fast software-defined clustered block storage
|
||||
|
||||
License: Vitastor Network Public License 1.1
|
||||
URL: https://vitastor.io/
|
||||
Source0: vitastor-3.0.14.el10.tar.gz
|
||||
Source0: vitastor-3.0.15.el10.tar.gz
|
||||
|
||||
BuildRequires: gperftools-devel
|
||||
BuildRequires: gcc-c++
|
||||
@@ -16,6 +16,7 @@ BuildRequires: gf-complete-devel
|
||||
BuildRequires: rdma-core-devel
|
||||
BuildRequires: cmake
|
||||
BuildRequires: libnl3-devel
|
||||
BuildRequires: c-ares-devel
|
||||
Requires: vitastor-osd = %{version}-%{release}
|
||||
Requires: vitastor-mon = %{version}-%{release}
|
||||
Requires: vitastor-client = %{version}-%{release}
|
||||
|
||||
@@ -15,7 +15,7 @@ RUN yum -y --enablerepo=extras install centos-release-scl epel-release yum-utils
|
||||
RUN perl -i -pe 's!mirrorlist=!#mirrorlist=!s; s!#\s*baseurl=http://mirror.centos.org!baseurl=http://vault.centos.org!' /etc/yum.repos.d/CentOS-SCLo-scl*.repo
|
||||
RUN yum -y install https://vitastor.io/rpms/centos/7/vitastor-release-1.0-1.el7.noarch.rpm
|
||||
RUN yum -y install devtoolset-9-gcc-c++ devtoolset-9-libatomic-devel gcc make cmake gperftools-devel \
|
||||
fio rh-nodejs12 jerasure-devel libisa-l-devel gf-complete-devel rdma-core-devel libnl3-devel
|
||||
fio rh-nodejs12 jerasure-devel libisa-l-devel gf-complete-devel rdma-core-devel libnl3-devel c-ares-devel
|
||||
RUN yumdownloader --disablerepo=centos-sclo-rh --source fio
|
||||
RUN rpm --nomd5 -i fio*.src.rpm
|
||||
RUN rm -f /etc/yum.repos.d/CentOS-Media.repo
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Name: vitastor
|
||||
Version: 3.0.14
|
||||
Version: 3.0.15
|
||||
Release: 1%{?dist}
|
||||
Summary: Vitastor, a fast software-defined clustered block storage
|
||||
|
||||
License: Vitastor Network Public License 1.1
|
||||
URL: https://vitastor.io/
|
||||
Source0: vitastor-3.0.14.el7.tar.gz
|
||||
Source0: vitastor-3.0.15.el7.tar.gz
|
||||
|
||||
BuildRequires: gperftools-devel
|
||||
BuildRequires: devtoolset-9-gcc-c++
|
||||
@@ -17,6 +17,7 @@ BuildRequires: gf-complete-devel
|
||||
BuildRequires: rdma-core-devel
|
||||
BuildRequires: cmake3
|
||||
BuildRequires: libnl3-devel
|
||||
BuildRequires: c-ares-devel
|
||||
Requires: vitastor-osd = %{version}-%{release}
|
||||
Requires: vitastor-mon = %{version}-%{release}
|
||||
Requires: vitastor-client = %{version}-%{release}
|
||||
|
||||
@@ -13,7 +13,7 @@ RUN dnf -y install centos-release-advanced-virtualization epel-release dnf-plugi
|
||||
RUN sed -i 's/^mirrorlist=/#mirrorlist=/; s!#baseurl=.*!baseurl=http://vault.centos.org/centos/8.4.2105/virt/$basearch/$avdir/!; s!^baseurl=.*Source/.*!baseurl=http://vault.centos.org/centos/8.4.2105/virt/Source/advanced-virtualization/!' /etc/yum.repos.d/CentOS-Advanced-Virtualization.repo
|
||||
RUN yum -y install https://vitastor.io/rpms/centos/8/vitastor-release-1.0-1.el8.noarch.rpm
|
||||
RUN dnf -y install gcc-toolset-9 gcc-toolset-9-gcc-c++ gperftools-devel \
|
||||
fio nodejs rpm-build jerasure-devel libisa-l-devel gf-complete-devel libibverbs-devel libarchive cmake libnl3-devel
|
||||
fio nodejs rpm-build jerasure-devel libisa-l-devel gf-complete-devel libibverbs-devel libarchive cmake libnl3-devel c-ares-devel
|
||||
RUN dnf download --source fio
|
||||
RUN rpm --nomd5 -i fio*.src.rpm
|
||||
RUN cd ~/rpmbuild/SPECS && dnf builddep -y --enablerepo=powertools --spec fio.spec
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Name: vitastor
|
||||
Version: 3.0.14
|
||||
Version: 3.0.15
|
||||
Release: 1%{?dist}
|
||||
Summary: Vitastor, a fast software-defined clustered block storage
|
||||
|
||||
License: Vitastor Network Public License 1.1
|
||||
URL: https://vitastor.io/
|
||||
Source0: vitastor-3.0.14.el8.tar.gz
|
||||
Source0: vitastor-3.0.15.el8.tar.gz
|
||||
|
||||
BuildRequires: gperftools-devel
|
||||
BuildRequires: gcc-toolset-9-gcc-c++
|
||||
@@ -16,6 +16,7 @@ BuildRequires: gf-complete-devel
|
||||
BuildRequires: rdma-core-devel
|
||||
BuildRequires: cmake
|
||||
BuildRequires: libnl3-devel
|
||||
BuildRequires: c-ares-devel
|
||||
Requires: vitastor-osd = %{version}-%{release}
|
||||
Requires: vitastor-mon = %{version}-%{release}
|
||||
Requires: vitastor-client = %{version}-%{release}
|
||||
|
||||
@@ -10,7 +10,7 @@ WORKDIR /root
|
||||
RUN sed -i 's/enabled=0/enabled=1/' /etc/yum.repos.d/*.repo
|
||||
RUN dnf -y install epel-release dnf-plugins-core
|
||||
RUN dnf -y install https://vitastor.io/rpms/centos/9/vitastor-release-1.0-1.el9.noarch.rpm
|
||||
RUN dnf -y install gcc-c++ gperftools-devel fio nodejs rpm-build jerasure-devel libisa-l-devel gf-complete-devel rdma-core-devel libarchive cmake libnl3-devel
|
||||
RUN dnf -y install gcc-c++ gperftools-devel fio nodejs rpm-build jerasure-devel libisa-l-devel gf-complete-devel rdma-core-devel libarchive cmake libnl3-devel c-ares-devel
|
||||
RUN dnf download --source fio
|
||||
RUN rpm --nomd5 -i fio*.src.rpm
|
||||
RUN cd ~/rpmbuild/SPECS && dnf builddep -y --spec fio.spec
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Name: vitastor
|
||||
Version: 3.0.14
|
||||
Version: 3.0.15
|
||||
Release: 1%{?dist}
|
||||
Summary: Vitastor, a fast software-defined clustered block storage
|
||||
|
||||
License: Vitastor Network Public License 1.1
|
||||
URL: https://vitastor.io/
|
||||
Source0: vitastor-3.0.14.el9.tar.gz
|
||||
Source0: vitastor-3.0.15.el9.tar.gz
|
||||
|
||||
BuildRequires: gperftools-devel
|
||||
BuildRequires: gcc-c++
|
||||
@@ -16,6 +16,7 @@ BuildRequires: gf-complete-devel
|
||||
BuildRequires: rdma-core-devel
|
||||
BuildRequires: cmake
|
||||
BuildRequires: libnl3-devel
|
||||
BuildRequires: c-ares-devel
|
||||
Requires: vitastor-osd = %{version}-%{release}
|
||||
Requires: vitastor-mon = %{version}-%{release}
|
||||
Requires: vitastor-client = %{version}-%{release}
|
||||
|
||||
+11
-1
@@ -20,7 +20,7 @@ if("${CMAKE_INSTALL_PREFIX}" MATCHES "^/usr/local/?$")
|
||||
endif()
|
||||
set(ENABLE_COVERAGE false CACHE BOOL "Enable code coverage")
|
||||
|
||||
add_definitions(-DVITASTOR_VERSION="3.0.14")
|
||||
add_definitions(-DVITASTOR_VERSION="3.0.15")
|
||||
add_definitions(-D_GNU_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -Wall -Wno-sign-compare -Wno-comment -Wno-parentheses -Wno-pointer-arith -fdiagnostics-color=always -fno-omit-frame-pointer -fvisibility=hidden -I ${CMAKE_SOURCE_DIR}/src)
|
||||
add_link_options(-fno-omit-frame-pointer)
|
||||
if (${WITH_ASAN})
|
||||
@@ -69,11 +69,21 @@ pkg_check_modules(ISAL libisal)
|
||||
if (ISAL_LIBRARIES)
|
||||
add_definitions(-DWITH_ISAL)
|
||||
endif (ISAL_LIBRARIES)
|
||||
pkg_check_modules(ISAL_CRYPTO libisal_crypto)
|
||||
if (ISAL_CRYPTO_LIBRARIES)
|
||||
add_definitions(-DWITH_ISAL_CRYPTO)
|
||||
endif (ISAL_CRYPTO_LIBRARIES)
|
||||
pkg_check_modules(RDMACM librdmacm)
|
||||
if (RDMACM_LIBRARIES)
|
||||
add_definitions(-DWITH_RDMACM)
|
||||
endif (RDMACM_LIBRARIES)
|
||||
|
||||
find_package(OpenSSL REQUIRED)
|
||||
add_definitions(-DWITH_OPENSSL)
|
||||
|
||||
pkg_check_modules(CARES REQUIRED libcares)
|
||||
include_directories(${CARES_INCLUDE_DIRS})
|
||||
|
||||
if (${WITH_SYSTEM_LIBURING})
|
||||
pkg_check_modules(LIBURING REQUIRED liburing>=2.10)
|
||||
include_directories(${LIBURING_INCLUDE_DIRS})
|
||||
|
||||
@@ -83,13 +83,17 @@ void blockstore_disk_t::parse_config(std::map<std::string, std::string> & config
|
||||
{
|
||||
data_csum_type = BLOCKSTORE_CSUM_CRC32C;
|
||||
}
|
||||
else if (config["data_csum_type"] == "xxh3_32")
|
||||
{
|
||||
data_csum_type = BLOCKSTORE_CSUM_XXH3_32;
|
||||
}
|
||||
else if (config["data_csum_type"] == "" || config["data_csum_type"] == "none")
|
||||
{
|
||||
data_csum_type = BLOCKSTORE_CSUM_NONE;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("data_csum_type="+config["data_csum_type"]+" is unsupported, only \"crc32c\" and \"none\" are supported");
|
||||
throw std::runtime_error("data_csum_type="+config["data_csum_type"]+" is unsupported, only \"crc32c\", \"xxh3_32\" and \"none\" are supported");
|
||||
}
|
||||
csum_block_size = parse_size(config["csum_block_size"]);
|
||||
discard_on_start = config.find("discard_on_start") != config.end() &&
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#define BLOCKSTORE_CSUM_NONE 0
|
||||
// Lower byte of checksum type is its length
|
||||
#define BLOCKSTORE_CSUM_CRC32C 0x104
|
||||
#define BLOCKSTORE_CSUM_XXH3_32 0x204
|
||||
|
||||
#define MOCK_DATA_FD 1000
|
||||
#define MOCK_META_FD 1001
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "blockstore_heap.h"
|
||||
#include "../util/allocator.h"
|
||||
#include "../util/crc32c.h"
|
||||
#include "../util/xxh_x86dispatch.h"
|
||||
#include "../util/malloc_or_die.h"
|
||||
|
||||
#define BS_HEAP_FREE_MVCC 1
|
||||
@@ -65,19 +66,19 @@ uint32_t blockstore_heap_t::get_simple_entry_size()
|
||||
uint32_t blockstore_heap_t::get_big_entry_size()
|
||||
{
|
||||
return sizeof(heap_big_write_t) + dsk->clean_entry_bitmap_size*2 +
|
||||
(!dsk->data_csum_type ? 0 : dsk->data_block_size/dsk->csum_block_size * (dsk->data_csum_type & 0xFF));
|
||||
(!dsk->csum_block_size ? 0 : dsk->data_block_size/dsk->csum_block_size * (dsk->data_csum_type & 0xFF));
|
||||
}
|
||||
|
||||
uint32_t blockstore_heap_t::get_big_intent_entry_size()
|
||||
{
|
||||
return sizeof(heap_big_intent_t) + dsk->clean_entry_bitmap_size*2 +
|
||||
(!dsk->data_csum_type ? 4 : dsk->data_block_size/dsk->csum_block_size * (dsk->data_csum_type & 0xFF));
|
||||
(!dsk->csum_block_size ? 4 : dsk->data_block_size/dsk->csum_block_size * (dsk->data_csum_type & 0xFF));
|
||||
}
|
||||
|
||||
uint32_t blockstore_heap_t::get_small_entry_size(uint32_t offset, uint32_t len)
|
||||
{
|
||||
return sizeof(heap_small_write_t) + dsk->clean_entry_bitmap_size +
|
||||
(!dsk->data_csum_type ? 4 : (dsk->data_csum_type & 0xFF) *
|
||||
(!dsk->csum_block_size ? 4 : (dsk->data_csum_type & 0xFF) *
|
||||
((offset+len+dsk->csum_block_size-1)/dsk->csum_block_size - offset/dsk->csum_block_size));
|
||||
}
|
||||
|
||||
@@ -92,7 +93,7 @@ uint32_t blockstore_heap_t::get_csum_size(heap_entry_t *wr)
|
||||
|
||||
uint32_t blockstore_heap_t::get_csum_size(uint32_t entry_type, uint32_t offset, uint32_t len)
|
||||
{
|
||||
if (!dsk->data_csum_type)
|
||||
if (!dsk->csum_block_size)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -217,15 +218,24 @@ void heap_entry_t::set_big_location(blockstore_heap_t *heap, uint64_t location)
|
||||
big().block_num = location / heap->dsk->data_block_size;
|
||||
}
|
||||
|
||||
uint32_t heap_entry_t::calc_crc32c()
|
||||
uint32_t heap_entry_t::calc_checksum(blockstore_disk_t *dsk)
|
||||
{
|
||||
auto old_crc32c = crc32c;
|
||||
crc32c = 0;
|
||||
uint32_t res = ::crc32c(0, (uint8_t*)this, size);
|
||||
crc32c = old_crc32c;
|
||||
auto old_checksum = checksum;
|
||||
checksum = 0;
|
||||
uint32_t res = 0;
|
||||
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32)
|
||||
res = (uint32_t)XXH3_64bits(this, size);
|
||||
else
|
||||
res = ::crc32c(0, (uint8_t*)this, size);
|
||||
checksum = old_checksum;
|
||||
return res;
|
||||
}
|
||||
|
||||
uint32_t heap_entry_t::calc_checksum(blockstore_heap_t *heap)
|
||||
{
|
||||
return calc_checksum(heap->dsk);
|
||||
}
|
||||
|
||||
uint64_t blockstore_heap_t::get_pg_id(inode_t inode, uint64_t stripe)
|
||||
{
|
||||
uint64_t pg_num = 0;
|
||||
@@ -380,12 +390,12 @@ corrupted_object:
|
||||
goto corrupted_object;
|
||||
}
|
||||
// Verify crc
|
||||
uint32_t expected_crc32c = wr->calc_crc32c();
|
||||
if (wr->crc32c != expected_crc32c)
|
||||
uint32_t expected_checksum = wr->calc_checksum(this);
|
||||
if (wr->checksum != expected_checksum)
|
||||
{
|
||||
fprintf(stderr, "Error: entry %jx:%jx v%ju l%ju in metadata block %u at %u is corrupt (crc32c mismatch: expected %08x, got %08x). ",
|
||||
fprintf(stderr, "Error: entry %jx:%jx v%ju l%ju in metadata block %u at %u is corrupt (checksum mismatch: expected %08x, got %08x). ",
|
||||
wr->inode, wr->stripe, wr->version, wr->lsn,
|
||||
block_num, block_offset, expected_crc32c, wr->crc32c);
|
||||
block_num, block_offset, expected_checksum, wr->checksum);
|
||||
goto corrupted_object;
|
||||
}
|
||||
// Verify offset & len
|
||||
@@ -1083,7 +1093,11 @@ bool blockstore_heap_t::calc_checksums(heap_entry_t *wr, uint8_t *data, bool set
|
||||
len = wr->big_intent().len;
|
||||
else
|
||||
assert(0);
|
||||
uint32_t real_csum = crc32c(0, data, len);
|
||||
uint32_t real_csum = 0;
|
||||
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32)
|
||||
real_csum = (uint32_t)XXH3_64bits(data, len);
|
||||
else
|
||||
real_csum = crc32c(0, data, len);
|
||||
if (set)
|
||||
{
|
||||
*wr_csum = real_csum;
|
||||
@@ -1133,11 +1147,26 @@ static uint32_t crc32c_iter(uint32_t prev_crc, const std::function<uint8_t*(uint
|
||||
return prev_crc;
|
||||
}
|
||||
|
||||
static void xxh3_iter(XXH3_state_t* xxh3_state, const std::function<uint8_t*(uint32_t start, uint32_t & len)> & next, uint32_t pos, uint32_t size)
|
||||
{
|
||||
uint32_t cur_len = 0;
|
||||
while (size > 0)
|
||||
{
|
||||
uint8_t *data = next(pos, cur_len);
|
||||
assert(data);
|
||||
cur_len = (cur_len < size ? cur_len : size);
|
||||
XXH3_64bits_update(xxh3_state, data, cur_len);
|
||||
pos += cur_len;
|
||||
size -= cur_len;
|
||||
}
|
||||
}
|
||||
|
||||
bool blockstore_heap_t::calc_block_checksums(uint32_t *block_csums, uint8_t *bitmap,
|
||||
uint32_t start, uint32_t end, std::function<uint8_t*(uint32_t start, uint32_t & len)> next,
|
||||
bool set, std::function<void(uint32_t, uint32_t, uint32_t)> bad_block_cb)
|
||||
{
|
||||
bool res = true;
|
||||
XXH3_state_t* xxh3_state = NULL;
|
||||
uint32_t pos = start;
|
||||
uint32_t block_end = (start/dsk->csum_block_size + 1)*dsk->csum_block_size;
|
||||
uint32_t block_crc = 0;
|
||||
@@ -1153,43 +1182,90 @@ bool blockstore_heap_t::calc_block_checksums(uint32_t *block_csums, uint8_t *bit
|
||||
while (pos < end && pos < block_end && !(bitmap[pos/dsk->bitmap_granularity/8] & (1 << ((pos/dsk->bitmap_granularity) % 8))))
|
||||
pos += dsk->bitmap_granularity;
|
||||
// zero padding at the beginning or at the end of the block is not counted
|
||||
if (pos > prev && prev > 0 && pos < block_end)
|
||||
block_crc = crc32c_pad(block_crc, NULL, 0, pos-prev, 0);
|
||||
if (pos > prev && prev > blk_start && pos < block_end)
|
||||
{
|
||||
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32)
|
||||
{
|
||||
if (!xxh3_state)
|
||||
{
|
||||
xxh3_state = XXH3_createState();
|
||||
XXH3_64bits_reset(xxh3_state);
|
||||
}
|
||||
uint32_t zeropad = pos-prev;
|
||||
while (zeropad > 0)
|
||||
{
|
||||
uint32_t zerolen = zeropad > 4096 ? 4096 : zeropad;
|
||||
XXH3_64bits_update(xxh3_state, zero_page, zerolen);
|
||||
zeropad -= zerolen;
|
||||
}
|
||||
}
|
||||
else
|
||||
block_crc = crc32c_pad(block_crc, NULL, 0, pos-prev, 0);
|
||||
}
|
||||
prev = pos;
|
||||
while (pos < end && pos < block_end && (bitmap[pos/dsk->bitmap_granularity/8] & (1 << ((pos/dsk->bitmap_granularity) % 8))))
|
||||
pos += dsk->bitmap_granularity;
|
||||
if (pos > prev)
|
||||
{
|
||||
isset = true;
|
||||
block_crc = crc32c_iter(block_crc, next, prev, pos-prev);
|
||||
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32)
|
||||
{
|
||||
if (!xxh3_state)
|
||||
{
|
||||
xxh3_state = XXH3_createState();
|
||||
XXH3_64bits_reset(xxh3_state);
|
||||
}
|
||||
xxh3_iter(xxh3_state, next, prev, pos-prev);
|
||||
}
|
||||
else
|
||||
block_crc = crc32c_iter(block_crc, next, prev, pos-prev);
|
||||
}
|
||||
prev = pos;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
block_crc = crc32c_iter(block_crc, next, pos, (end > block_end ? block_end : end)-pos);
|
||||
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32)
|
||||
{
|
||||
if (!xxh3_state)
|
||||
{
|
||||
xxh3_state = XXH3_createState();
|
||||
XXH3_64bits_reset(xxh3_state);
|
||||
}
|
||||
xxh3_iter(xxh3_state, next, pos, (end > block_end ? block_end : end)-pos);
|
||||
}
|
||||
else
|
||||
block_crc = crc32c_iter(block_crc, next, pos, (end > block_end ? block_end : end)-pos);
|
||||
pos = (end > block_end ? block_end : end);
|
||||
isset = true;
|
||||
}
|
||||
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32 && xxh3_state)
|
||||
{
|
||||
block_crc = (uint32_t)XXH3_64bits_digest(xxh3_state);
|
||||
XXH3_64bits_reset(xxh3_state);
|
||||
}
|
||||
if (set)
|
||||
{
|
||||
*block_csums = block_crc;
|
||||
}
|
||||
else if (isset && block_crc != *block_csums)
|
||||
{
|
||||
res = false;
|
||||
if (bad_block_cb)
|
||||
{
|
||||
bad_block_cb(blk_start, *block_csums, block_crc);
|
||||
res = false;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
block_end += dsk->csum_block_size;
|
||||
block_crc = 0;
|
||||
block_csums++;
|
||||
}
|
||||
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32 && xxh3_state)
|
||||
{
|
||||
block_crc = (uint32_t)XXH3_64bits_digest(xxh3_state);
|
||||
XXH3_freeState(xxh3_state);
|
||||
xxh3_state = NULL;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -1556,7 +1632,7 @@ int blockstore_heap_t::add_entry(uint32_t wr_size, uint32_t *modified_block,
|
||||
insert_list_items(&li, 1, false);
|
||||
li->block_num = block_num;
|
||||
new_wr->size = wr_size;
|
||||
new_wr->crc32c = new_wr->calc_crc32c();
|
||||
new_wr->checksum = new_wr->calc_checksum(this);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1628,7 +1704,7 @@ int blockstore_heap_t::add_big_write(object_id oid, heap_entry_t *old_head, bool
|
||||
memset(wr->get_ext_bitmap(this), 0, dsk->clean_entry_bitmap_size);
|
||||
memset(wr->get_int_bitmap(this), 0, dsk->clean_entry_bitmap_size);
|
||||
bitmap_set(wr->get_int_bitmap(this), offset, len, dsk->bitmap_granularity);
|
||||
if (dsk->data_csum_type)
|
||||
if (dsk->csum_block_size)
|
||||
{
|
||||
memset(wr->get_checksums(this), 0, get_csum_size(wr));
|
||||
calc_checksums(wr, (uint8_t*)data, true, offset, len);
|
||||
@@ -1657,7 +1733,7 @@ int blockstore_heap_t::add_redirect_intent(object_id oid, heap_entry_t **obj_ptr
|
||||
memset(wr->get_ext_bitmap(this), 0, dsk->clean_entry_bitmap_size);
|
||||
memset(wr->get_int_bitmap(this), 0, dsk->clean_entry_bitmap_size);
|
||||
bitmap_set(wr->get_int_bitmap(this), offset, len, dsk->bitmap_granularity);
|
||||
if (dsk->data_csum_type)
|
||||
if (dsk->csum_block_size)
|
||||
memset(wr->get_checksums(this), 0, get_csum_size(wr));
|
||||
calc_checksums(wr, (uint8_t*)data, true);
|
||||
*obj_ptr = wr;
|
||||
@@ -1695,7 +1771,7 @@ int blockstore_heap_t::add_big_intent(object_id oid, heap_entry_t **obj_ptr, uin
|
||||
memcpy(wr->get_ext_bitmap(this), obj->get_ext_bitmap(this), dsk->clean_entry_bitmap_size);
|
||||
memcpy(wr->get_int_bitmap(this), obj->get_int_bitmap(this), dsk->clean_entry_bitmap_size);
|
||||
bitmap_set(wr->get_int_bitmap(this), offset, len, dsk->bitmap_granularity);
|
||||
if (dsk->data_csum_type)
|
||||
if (dsk->csum_block_size)
|
||||
{
|
||||
if (checksums)
|
||||
memcpy(wr->get_checksums(this), checksums, get_csum_size(wr));
|
||||
@@ -1742,7 +1818,7 @@ int blockstore_heap_t::add_compact(heap_entry_t *obj, uint64_t compact_version,
|
||||
new_wr->set_big_location(this, compact_location);
|
||||
memcpy(new_wr->get_int_bitmap(this), new_int_bitmap, dsk->clean_entry_bitmap_size);
|
||||
memcpy(new_wr->get_ext_bitmap(this), new_ext_bitmap, dsk->clean_entry_bitmap_size);
|
||||
if (dsk->data_csum_type && new_csums)
|
||||
if (dsk->csum_block_size && new_csums)
|
||||
memcpy(new_wr->get_checksums(this), new_csums, dsk->data_block_size/dsk->csum_block_size*(dsk->data_csum_type & 0xFF));
|
||||
});
|
||||
}
|
||||
@@ -1761,7 +1837,7 @@ int blockstore_heap_t::punch_holes(heap_entry_t *wr, uint8_t *new_bitmap, uint8_
|
||||
*modified_block = block_num;
|
||||
memcpy(wr->get_int_bitmap(this), new_bitmap, dsk->clean_entry_bitmap_size);
|
||||
memcpy(wr->get_checksums(this), new_csums, dsk->data_block_size/dsk->csum_block_size*(dsk->data_csum_type & 0xFF));
|
||||
wr->crc32c = wr->calc_crc32c();
|
||||
wr->checksum = wr->calc_checksum(dsk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -2300,7 +2376,7 @@ void blockstore_heap_t::use_data(inode_t inode, uint64_t location)
|
||||
{
|
||||
auto sh_it = pool_shard_settings.find(INODE_POOL(inode));
|
||||
if (sh_it != pool_shard_settings.end() && sh_it->second.no_inode_stats)
|
||||
inode = (INODE_POOL(inode) << POOL_ID_BITS);
|
||||
inode = INODE_WITH_POOL(INODE_POOL(inode), 0);
|
||||
assert(!data_alloc->get(location / dsk->data_block_size));
|
||||
data_alloc->set(location / dsk->data_block_size, true);
|
||||
inode_space_stats[inode] += dsk->data_block_size;
|
||||
@@ -2311,7 +2387,7 @@ void blockstore_heap_t::free_data(inode_t inode, uint64_t location)
|
||||
{
|
||||
auto sh_it = pool_shard_settings.find(INODE_POOL(inode));
|
||||
if (sh_it != pool_shard_settings.end() && sh_it->second.no_inode_stats)
|
||||
inode = (INODE_POOL(inode) << POOL_ID_BITS);
|
||||
inode = INODE_WITH_POOL(INODE_POOL(inode), 0);
|
||||
assert(data_alloc->get(location / dsk->data_block_size));
|
||||
data_alloc->set(location / dsk->data_block_size, false);
|
||||
auto sp_it = inode_space_stats.find(inode);
|
||||
|
||||
@@ -43,7 +43,7 @@ struct __attribute__((__packed__)) heap_entry_t
|
||||
{
|
||||
uint16_t size;
|
||||
uint16_t entry_type;
|
||||
uint32_t crc32c;
|
||||
uint32_t checksum;
|
||||
uint64_t lsn;
|
||||
uint64_t inode;
|
||||
uint64_t stripe;
|
||||
@@ -69,7 +69,8 @@ struct __attribute__((__packed__)) heap_entry_t
|
||||
uint32_t *get_checksum(blockstore_heap_t *heap);
|
||||
uint64_t big_location(blockstore_heap_t *heap);
|
||||
void set_big_location(blockstore_heap_t *heap, uint64_t location);
|
||||
uint32_t calc_crc32c();
|
||||
uint32_t calc_checksum(blockstore_heap_t *heap);
|
||||
uint32_t calc_checksum(blockstore_disk_t *dsk);
|
||||
};
|
||||
|
||||
struct __attribute__((__packed__)) heap_small_write_t
|
||||
@@ -80,7 +81,7 @@ struct __attribute__((__packed__)) heap_small_write_t
|
||||
uint32_t offset;
|
||||
uint32_t len;
|
||||
|
||||
// Also includes 1 bitmap and 1 crc32c after the bitmap if checksums are disabled
|
||||
// Also includes 1 bitmap and 1 checksum after the bitmap if block checksums are disabled
|
||||
};
|
||||
|
||||
struct __attribute__((__packed__)) heap_big_write_t
|
||||
@@ -98,7 +99,7 @@ struct __attribute__((__packed__)) heap_big_intent_t
|
||||
uint32_t offset;
|
||||
uint32_t len;
|
||||
|
||||
// Also includes 2 bitmaps and 1 crc32c if checksums are disabled
|
||||
// Also includes 2 bitmaps and 1 checksums if block checksums are disabled
|
||||
};
|
||||
|
||||
struct __attribute__((__packed__)) heap_list_item_t
|
||||
|
||||
@@ -10,13 +10,18 @@ add_library(vitastor_common STATIC
|
||||
../../json11/json11.cpp
|
||||
osd_ops.cpp
|
||||
pg_states.cpp
|
||||
msgr_encrypt.cpp
|
||||
msgr_handshake.cpp
|
||||
../util/allocator.cpp
|
||||
../util/addr_util.cpp
|
||||
../util/timerfd_manager.cpp
|
||||
../util/str_util.cpp
|
||||
../util/json_util.cpp
|
||||
../util/xxh_x86dispatch.c
|
||||
../util/openssl_util.cpp
|
||||
)
|
||||
target_compile_options(vitastor_common PUBLIC -fPIC)
|
||||
target_link_libraries(vitastor_common ${OPENSSL_LIBRARIES} ${ISAL_CRYPTO_LIBRARIES})
|
||||
|
||||
# libvitastor_net.a
|
||||
set(MSGR_RDMA "")
|
||||
@@ -34,29 +39,41 @@ add_library(vitastor_net STATIC
|
||||
msgr_iothread.cpp
|
||||
msgr_send.cpp
|
||||
msgr_receive.cpp
|
||||
msgr_encrypt.cpp
|
||||
../util/ringloop.cpp
|
||||
http_client.cpp
|
||||
${MSGR_RDMA}
|
||||
${MSGR_RDMACM}
|
||||
)
|
||||
target_link_libraries(vitastor_net pthread vitastor_common)
|
||||
target_link_libraries(vitastor_net pthread vitastor_common ${CARES_LIBRARIES})
|
||||
target_compile_options(vitastor_net PUBLIC -fPIC)
|
||||
|
||||
# libvitastor_client.so
|
||||
add_library(vitastor_client SHARED
|
||||
# libvitastor_client_int.a
|
||||
add_library(vitastor_client_int STATIC
|
||||
cluster_client.cpp
|
||||
cluster_client_real.cpp
|
||||
cluster_client_list.cpp
|
||||
cluster_client_wb.cpp
|
||||
vitastor_c.cpp
|
||||
cluster_client_icache.cpp
|
||||
)
|
||||
set_target_properties(vitastor_client PROPERTIES PUBLIC_HEADER "client/vitastor_c.h")
|
||||
target_link_libraries(vitastor_client
|
||||
target_link_libraries(vitastor_client_int
|
||||
vitastor_net
|
||||
vitastor_cli
|
||||
${LIBURING_LIBRARIES}
|
||||
${IBVERBS_LIBRARIES}
|
||||
${RDMACM_LIBRARIES}
|
||||
${OPENSSL_LIBRARIES}
|
||||
${ISAL_CRYPTO_LIBRARIES}
|
||||
)
|
||||
target_compile_options(vitastor_client_int PUBLIC -fPIC)
|
||||
|
||||
# libvitastor_client.so
|
||||
add_library(vitastor_client SHARED
|
||||
vitastor_c.cpp
|
||||
)
|
||||
set_target_properties(vitastor_client PROPERTIES PUBLIC_HEADER "client/vitastor_c.h")
|
||||
target_link_libraries(vitastor_client
|
||||
vitastor_client_int
|
||||
)
|
||||
set_target_properties(vitastor_client PROPERTIES VERSION ${VITASTOR_VERSION} SOVERSION 0)
|
||||
configure_file(vitastor.pc.in vitastor.pc @ONLY)
|
||||
@@ -122,10 +139,12 @@ add_executable(test_cluster_client
|
||||
cluster_client.cpp
|
||||
cluster_client_list.cpp
|
||||
cluster_client_wb.cpp
|
||||
cluster_client_icache.cpp
|
||||
../test/mock/messenger.cpp
|
||||
../test/mock/vault.cpp
|
||||
etcd_state_client_mock.cpp
|
||||
)
|
||||
target_link_libraries(test_cluster_client vitastor_common ${LIBURING_LIBRARIES})
|
||||
target_link_libraries(test_cluster_client vitastor_common ${LIBURING_LIBRARIES} ${OPENSSL_LIBRARIES} ${ISAL_CRYPTO_LIBRARIES})
|
||||
target_include_directories(test_cluster_client BEFORE PUBLIC ${CMAKE_SOURCE_DIR}/src/test/mock)
|
||||
add_dependencies(build_tests test_cluster_client)
|
||||
add_test(NAME test_cluster_client COMMAND test_cluster_client)
|
||||
|
||||
+116
-70
@@ -51,7 +51,7 @@ cluster_client_t::cluster_client_t(ring_loop_t *ringloop, timerfd_manager_t *tfd
|
||||
msgr.stop_client(op->client_id);
|
||||
delete op;
|
||||
};
|
||||
msgr.parse_config(config);
|
||||
msgr.parse_config(config, true);
|
||||
|
||||
st_cli = std::move(st_cli_ptr);
|
||||
st_cli->on_load_config_hook = [this](json11::Json::object & cfg) { on_load_config_hook(cfg); };
|
||||
@@ -62,6 +62,7 @@ cluster_client_t::cluster_client_t(ring_loop_t *ringloop, timerfd_manager_t *tfd
|
||||
st_cli->on_change_node_placement_hook = [this]() { on_change_node_placement_hook(); };
|
||||
st_cli->on_load_pgs_hook = [this](bool success) { on_load_pgs_hook(success); };
|
||||
st_cli->on_reload_hook = [this]() { this->st_cli->load_global_config(); };
|
||||
st_cli->on_inode_change_hook = [this](uint64_t inode, bool removed) { on_change_inode_hook(inode, removed); };
|
||||
|
||||
st_cli->parse_config(config);
|
||||
st_cli->infinite_start = false;
|
||||
@@ -70,13 +71,11 @@ cluster_client_t::cluster_client_t(ring_loop_t *ringloop, timerfd_manager_t *tfd
|
||||
st_cli->infinite_start = config["client_infinite_start"].bool_value();
|
||||
}
|
||||
st_cli->load_global_config();
|
||||
|
||||
scrap_buffer_size = SCRAP_BUFFER_SIZE;
|
||||
scrap_buffer = malloc_or_die(scrap_buffer_size);
|
||||
}
|
||||
|
||||
cluster_client_t::~cluster_client_t()
|
||||
{
|
||||
vault_destroy();
|
||||
if (retry_timeout_id >= 0)
|
||||
{
|
||||
tfd->clear_timer(retry_timeout_id);
|
||||
@@ -94,7 +93,6 @@ cluster_client_t::~cluster_client_t()
|
||||
{
|
||||
ringloop->unregister_consumer(&consumer);
|
||||
}
|
||||
free(scrap_buffer);
|
||||
delete wb;
|
||||
wb = NULL;
|
||||
}
|
||||
@@ -481,7 +479,9 @@ void cluster_client_t::on_load_config_hook(json11::Json::object & etcd_global_co
|
||||
self_tree_metrics.clear();
|
||||
client_hostname = new_hostname;
|
||||
}
|
||||
msgr.parse_config(config);
|
||||
// vault
|
||||
vault_parse_config();
|
||||
msgr.parse_config(config, false);
|
||||
st_cli->parse_config(config);
|
||||
st_cli->load_pgs();
|
||||
}
|
||||
@@ -607,6 +607,9 @@ void cluster_client_t::on_change_pool_config_hook()
|
||||
pg_counts[pool_item.first] = pool_item.second.real_pg_count;
|
||||
}
|
||||
}
|
||||
inode_cache.clear();
|
||||
inode_cache_children.clear();
|
||||
vault_keys.clear();
|
||||
continue_ops();
|
||||
}
|
||||
|
||||
@@ -673,6 +676,10 @@ bool cluster_client_t::flush()
|
||||
{
|
||||
if (!ringloop)
|
||||
{
|
||||
if (vault_loading)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (wb->writeback_queue.size())
|
||||
{
|
||||
wb->start_writebacks(this, 0);
|
||||
@@ -695,7 +702,7 @@ bool cluster_client_t::flush()
|
||||
sync_done = true;
|
||||
};
|
||||
execute(sync);
|
||||
while (!sync_done)
|
||||
while (!sync_done || vault_loading)
|
||||
{
|
||||
ringloop->loop();
|
||||
if (!sync_done)
|
||||
@@ -958,10 +965,40 @@ bool cluster_client_t::check_rw(cluster_op_t *op)
|
||||
{
|
||||
op->flags |= OP_IMMEDIATE_COMMIT;
|
||||
}
|
||||
bool searched = false;
|
||||
std::shared_ptr<inode_cache_t> icache;
|
||||
if (op->opcode == OSD_OP_READ || op->opcode == OSD_OP_WRITE)
|
||||
{
|
||||
if (!searched)
|
||||
{
|
||||
icache = inode_cache_get(op->inode);
|
||||
searched = true;
|
||||
}
|
||||
if (icache && icache->has_parent_loop && op->opcode == OSD_OP_READ)
|
||||
{
|
||||
op->retval = -EINVAL;
|
||||
auto cb = std::move(op->callback);
|
||||
cb(op);
|
||||
return false;
|
||||
}
|
||||
if (icache && icache->op_enc)
|
||||
{
|
||||
// Use shared_ptr aliasing to attach op_enc to the inode cache entry
|
||||
op->enc = std::shared_ptr<osd_op_enc_t>(icache, icache->op_enc);
|
||||
}
|
||||
else
|
||||
op->enc.reset();
|
||||
}
|
||||
else
|
||||
op->enc.reset();
|
||||
if ((op->opcode == OSD_OP_WRITE || op->opcode == OSD_OP_DELETE) && !(op->flags & OSD_OP_IGNORE_READONLY))
|
||||
{
|
||||
auto ino_it = st_cli->inode_config.find(op->inode);
|
||||
if (ino_it != st_cli->inode_config.end() && ino_it->second.readonly)
|
||||
if (!searched)
|
||||
{
|
||||
icache = inode_cache_get(op->inode);
|
||||
searched = true;
|
||||
}
|
||||
if (icache && icache->readonly)
|
||||
{
|
||||
op->retval = -EROFS;
|
||||
auto cb = std::move(op->callback);
|
||||
@@ -972,33 +1009,39 @@ bool cluster_client_t::check_rw(cluster_op_t *op)
|
||||
op->deoptimise_snapshot = false;
|
||||
if (enable_writeback && (op->opcode == OSD_OP_READ || op->opcode == OSD_OP_READ_BITMAP || op->opcode == OSD_OP_READ_CHAIN_BITMAP))
|
||||
{
|
||||
auto ino_it = st_cli->inode_config.find(op->inode);
|
||||
if (ino_it != st_cli->inode_config.end())
|
||||
if (!searched)
|
||||
{
|
||||
int chain_size = 0;
|
||||
while (ino_it != st_cli->inode_config.end() && ino_it->second.parent_id)
|
||||
icache = inode_cache_get(op->inode);
|
||||
searched = true;
|
||||
}
|
||||
if (icache)
|
||||
{
|
||||
for (auto & parent: icache->chain)
|
||||
{
|
||||
// Check for loops - FIXME check it in etcd_state_client
|
||||
if (ino_it->second.parent_id == op->inode ||
|
||||
chain_size > st_cli->inode_config.size())
|
||||
{
|
||||
op->retval = -EINVAL;
|
||||
auto cb = std::move(op->callback);
|
||||
cb(op);
|
||||
return false;
|
||||
}
|
||||
if (INODE_POOL(ino_it->second.parent_id) == INODE_POOL(ino_it->first) &&
|
||||
wb->has_inode(ino_it->second.parent_id))
|
||||
if (INODE_POOL(parent) == INODE_POOL(op->inode) && wb->has_inode(parent))
|
||||
{
|
||||
// Deoptimise reads - we have dirty data for one of the parent layer(s).
|
||||
op->deoptimise_snapshot = true;
|
||||
break;
|
||||
}
|
||||
chain_size++;
|
||||
ino_it = st_cli->inode_config.find(ino_it->second.parent_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (icache && icache->err_code)
|
||||
{
|
||||
if (icache->err_code == EPERM)
|
||||
{
|
||||
op->retval = -EPERM;
|
||||
auto cb = std::move(op->callback);
|
||||
cb(op);
|
||||
return false;
|
||||
}
|
||||
else if (icache->err_code == EAGAIN)
|
||||
{
|
||||
key_wait_ops.push_back(op);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1128,31 +1171,33 @@ resume_2:
|
||||
}
|
||||
if (op->opcode == OSD_OP_READ || op->opcode == OSD_OP_READ_CHAIN_BITMAP)
|
||||
{
|
||||
// Check parent inode
|
||||
auto ino_it = st_cli->inode_config.find(op->cur_inode);
|
||||
// Skip parents from the same pool
|
||||
int skipped = 0;
|
||||
while (!op->deoptimise_snapshot &&
|
||||
ino_it != st_cli->inode_config.end() && ino_it->second.parent_id &&
|
||||
INODE_POOL(ino_it->second.parent_id) == INODE_POOL(op->cur_inode))
|
||||
uint64_t next_inode = 0;
|
||||
auto icache = inode_cache_get(op->cur_inode);
|
||||
if (icache)
|
||||
{
|
||||
// Check for loops - FIXME check it in etcd_state_client
|
||||
if (ino_it->second.parent_id == op->inode ||
|
||||
skipped > st_cli->inode_config.size())
|
||||
if (icache->has_parent_loop)
|
||||
{
|
||||
op->retval = -EINVAL;
|
||||
erase_op(op);
|
||||
return 1;
|
||||
}
|
||||
skipped++;
|
||||
ino_it = st_cli->inode_config.find(ino_it->second.parent_id);
|
||||
if (op->deoptimise_snapshot)
|
||||
{
|
||||
if (icache->chain.size() > 1)
|
||||
next_inode = icache->chain[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (icache->other_pool_parent_id)
|
||||
next_inode = icache->other_pool_parent_id;
|
||||
}
|
||||
}
|
||||
if (ino_it != st_cli->inode_config.end() &&
|
||||
ino_it->second.parent_id &&
|
||||
ino_it->second.parent_id != op->inode)
|
||||
if (next_inode)
|
||||
{
|
||||
// Continue reading from the parent inode
|
||||
op->cur_inode = ino_it->second.parent_id;
|
||||
icache = inode_cache_get(next_inode);
|
||||
op->cur_inode = next_inode;
|
||||
op->enc = (icache && icache->op_enc ? std::shared_ptr<osd_op_enc_t>(icache, icache->op_enc) : nullptr);
|
||||
op->parts.clear();
|
||||
op->done_count = 0;
|
||||
goto resume_0;
|
||||
@@ -1203,7 +1248,7 @@ resume_2:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void add_iov(int size, bool skip, cluster_op_t *op, int &iov_idx, size_t &iov_pos, osd_op_buf_list_t &iov, void *scrap, int scrap_len)
|
||||
static void add_iov(int size, int skip, cluster_op_t *op, int &iov_idx, size_t &iov_pos, osd_op_buf_list_t &iov)
|
||||
{
|
||||
int left = size;
|
||||
while (left > 0 && iov_idx < op->iov.count)
|
||||
@@ -1211,7 +1256,7 @@ static void add_iov(int size, bool skip, cluster_op_t *op, int &iov_idx, size_t
|
||||
int cur_left = op->iov.buf[iov_idx].iov_len - iov_pos;
|
||||
if (cur_left < left)
|
||||
{
|
||||
if (!skip)
|
||||
if (skip == 0)
|
||||
{
|
||||
iov.push_back((uint8_t*)op->iov.buf[iov_idx].iov_base + iov_pos, cur_left);
|
||||
}
|
||||
@@ -1221,7 +1266,7 @@ static void add_iov(int size, bool skip, cluster_op_t *op, int &iov_idx, size_t
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!skip)
|
||||
if (skip == 0)
|
||||
{
|
||||
iov.push_back((uint8_t*)op->iov.buf[iov_idx].iov_base + iov_pos, left);
|
||||
}
|
||||
@@ -1230,16 +1275,10 @@ static void add_iov(int size, bool skip, cluster_op_t *op, int &iov_idx, size_t
|
||||
}
|
||||
}
|
||||
assert(left == 0);
|
||||
if (skip && scrap_len > 0)
|
||||
if (skip == 1)
|
||||
{
|
||||
// All skipped ranges are read into the same useless buffer
|
||||
left = size;
|
||||
while (left > 0)
|
||||
{
|
||||
int cur_left = scrap_len < left ? scrap_len : left;
|
||||
iov.push_back(scrap, cur_left);
|
||||
left -= cur_left;
|
||||
}
|
||||
// data read into a NULL buffer will be discarded by messenger
|
||||
iov.push_back(NULL, size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1259,7 +1298,11 @@ void cluster_client_t::slice_rw(cluster_op_t *op)
|
||||
// Allocate memory for the bitmap
|
||||
unsigned object_bitmap_size = ((op->len / pool_cfg.bitmap_granularity + 7) / 8);
|
||||
object_bitmap_size = (object_bitmap_size < 8 ? 8 : object_bitmap_size);
|
||||
unsigned bitmap_mem = object_bitmap_size + (pool_cfg.data_block_size / pool_cfg.bitmap_granularity / 8 * pg_data_size) * op->parts.size();
|
||||
unsigned bitmap_mem = object_bitmap_size +
|
||||
op->parts.size() * pg_data_size *
|
||||
(pool_cfg.data_block_size / pool_cfg.bitmap_granularity / 8
|
||||
// read chain_info - max 4 bytes per block
|
||||
+ (op->enc ? osd_op_rw_t::chain_info_bytes(op->enc->chain_size)*op->len/pool_cfg.bitmap_granularity : 0));
|
||||
if (!op->bitmap_buf || op->bitmap_buf_size < bitmap_mem)
|
||||
{
|
||||
op->bitmap_buf = realloc_or_die(op->bitmap_buf, bitmap_mem);
|
||||
@@ -1301,10 +1344,10 @@ void cluster_client_t::slice_rw(cluster_op_t *op)
|
||||
{
|
||||
begin = cur;
|
||||
// Just advance iov_idx & iov_pos
|
||||
add_iov(cur-prev, true, op, iov_idx, iov_pos, op->parts[i].iov, NULL, 0);
|
||||
add_iov(cur-prev, 2, op, iov_idx, iov_pos, op->parts[i].iov);
|
||||
}
|
||||
else
|
||||
add_iov(cur-prev, skip_prev, op, iov_idx, iov_pos, op->parts[i].iov, scrap_buffer, scrap_buffer_size);
|
||||
add_iov(cur-prev, skip_prev ? 1 : 0, op, iov_idx, iov_pos, op->parts[i].iov);
|
||||
}
|
||||
skip_prev = skip;
|
||||
prev = cur;
|
||||
@@ -1315,11 +1358,11 @@ void cluster_client_t::slice_rw(cluster_op_t *op)
|
||||
if (skip_prev)
|
||||
{
|
||||
// Just advance iov_idx & iov_pos
|
||||
add_iov(end-prev, true, op, iov_idx, iov_pos, op->parts[i].iov, NULL, 0);
|
||||
add_iov(end-prev, 2, op, iov_idx, iov_pos, op->parts[i].iov);
|
||||
end = prev;
|
||||
}
|
||||
else
|
||||
add_iov(cur-prev, skip_prev, op, iov_idx, iov_pos, op->parts[i].iov, scrap_buffer, scrap_buffer_size);
|
||||
add_iov(cur-prev, skip_prev ? 1 : 0, op, iov_idx, iov_pos, op->parts[i].iov);
|
||||
if (end == begin)
|
||||
{
|
||||
op->done_count++;
|
||||
@@ -1328,7 +1371,7 @@ void cluster_client_t::slice_rw(cluster_op_t *op)
|
||||
}
|
||||
else if (op->opcode != OSD_OP_READ_BITMAP && op->opcode != OSD_OP_READ_CHAIN_BITMAP && op->opcode != OSD_OP_DELETE)
|
||||
{
|
||||
add_iov(end-begin, false, op, iov_idx, iov_pos, op->parts[i].iov, NULL, 0);
|
||||
add_iov(end-begin, 0, op, iov_idx, iov_pos, op->parts[i].iov);
|
||||
}
|
||||
op->parts[i].parent = op;
|
||||
op->parts[i].offset = begin;
|
||||
@@ -1414,9 +1457,12 @@ int cluster_client_t::try_send(cluster_op_t *op, int i, std::function<void(osd_o
|
||||
osd_client_t *cl = peer_it->second;
|
||||
part->flags |= PART_SENT|PART_VALID;
|
||||
op->inflight_count++;
|
||||
uint64_t pg_bitmap_size = (pool_cfg.data_block_size / pool_cfg.bitmap_granularity / 8) * (
|
||||
pool_cfg.scheme == POOL_SCHEME_REPLICATED ? 1 : pool_cfg.pg_size-pool_cfg.parity_chunks
|
||||
);
|
||||
uint32_t pg_data_size = (pool_cfg.scheme == POOL_SCHEME_REPLICATED ? 1 : pool_cfg.pg_size-pool_cfg.parity_chunks);
|
||||
uint64_t pg_bitmap_size = pg_data_size * (pool_cfg.data_block_size / pool_cfg.bitmap_granularity / 8
|
||||
// read chain_info - max 4 bytes per block
|
||||
+ (op->opcode == OSD_OP_READ && op->enc
|
||||
? osd_op_rw_t::chain_info_bytes(op->enc->chain_size)*pool_cfg.data_block_size/pool_cfg.bitmap_granularity
|
||||
: 0));
|
||||
uint64_t meta_rev = 0;
|
||||
if (op->opcode != OSD_OP_READ_BITMAP && op->opcode != OSD_OP_DELETE && !op->deoptimise_snapshot)
|
||||
{
|
||||
@@ -1435,6 +1481,7 @@ int cluster_client_t::try_send(cluster_op_t *op, int i, std::function<void(osd_o
|
||||
.inode = op->cur_inode,
|
||||
.offset = part->offset,
|
||||
.len = part->len,
|
||||
.flags = op->opcode == OSD_OP_READ && op->enc && !op->deoptimise_snapshot ? OSD_OP_RETURN_CHAIN : 0,
|
||||
.meta_revision = meta_rev,
|
||||
.version = op->opcode == OSD_OP_WRITE || op->opcode == OSD_OP_DELETE ? op->version : 0,
|
||||
} },
|
||||
@@ -1442,6 +1489,7 @@ int cluster_client_t::try_send(cluster_op_t *op, int i, std::function<void(osd_o
|
||||
? (uint8_t*)op->part_bitmaps + pg_bitmap_size*i : NULL),
|
||||
.bitmap_len = (unsigned)(op->opcode == OSD_OP_READ || op->opcode == OSD_OP_READ_BITMAP || op->opcode == OSD_OP_READ_CHAIN_BITMAP
|
||||
? pg_bitmap_size : 0),
|
||||
.enc = op->enc,
|
||||
.callback = cb ? cb : [this, part](osd_op_t *op_part)
|
||||
{
|
||||
handle_op_part(part);
|
||||
@@ -1559,6 +1607,9 @@ static inline void mem_or(void *res, const void *r2, unsigned int len)
|
||||
}
|
||||
}
|
||||
|
||||
// Error priority: others > EPERM > EIO > ENOSPC > ETIMEDOUT > EPIPE
|
||||
#define ERR_PRIO(e) (((e) == -EPERM ? 5 : ((e) == -EIO ? 4 : ((e) == -ENOSPC ? 3 : ((e) == -ETIMEDOUT ? 2 : ((e) == -EPIPE ? 1 : (!(e) ? 0 : 10)))))))
|
||||
|
||||
void cluster_client_t::handle_op_part(cluster_op_part_t *part)
|
||||
{
|
||||
cluster_op_t *op = part->parent;
|
||||
@@ -1567,15 +1618,10 @@ void cluster_client_t::handle_op_part(cluster_op_part_t *part)
|
||||
{
|
||||
// Operation failed, retry
|
||||
part->flags |= PART_ERROR;
|
||||
if (!op->retval || op->retval == -EPIPE ||
|
||||
part->op.reply.hdr.retval == -ENOSPC && op->retval == -ETIMEDOUT ||
|
||||
part->op.reply.hdr.retval == -EIO)
|
||||
{
|
||||
// Error priority: EIO > ENOSPC > ETIMEDOUT > EPIPE
|
||||
if (ERR_PRIO(part->op.reply.hdr.retval) > ERR_PRIO(op->retval))
|
||||
op->retval = part->op.reply.hdr.retval;
|
||||
}
|
||||
uint64_t stop_client_id = 0;
|
||||
if (op->retval != -EINTR && op->retval != -EIO && op->retval != -ENOSPC)
|
||||
if (op->retval != -EINTR && op->retval != -EIO && op->retval != -ENOSPC && op->retval != -EPERM)
|
||||
{
|
||||
stop_client_id = part->op.client_id;
|
||||
if (op->retval != -EPIPE || log_level > 0)
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "messenger.h"
|
||||
#include "etcd_state_client_http.h"
|
||||
#include "../util/robin_hood.h"
|
||||
|
||||
#define DEFAULT_CLIENT_MAX_DIRTY_BYTES 32*1024*1024
|
||||
#define DEFAULT_CLIENT_MAX_DIRTY_OPS 1024
|
||||
@@ -71,6 +72,7 @@ protected:
|
||||
cluster_op_t *prev = NULL, *next = NULL;
|
||||
int prev_wait = 0;
|
||||
uint64_t flush_id = 0;
|
||||
std::shared_ptr<osd_op_enc_t> enc;
|
||||
friend class cluster_client_t;
|
||||
friend class writeback_cache_t;
|
||||
};
|
||||
@@ -80,14 +82,33 @@ struct inode_list_osd_t;
|
||||
struct inode_list_pg_t;
|
||||
class writeback_cache_t;
|
||||
|
||||
struct inode_cache_t
|
||||
{
|
||||
std::vector<inode_t> chain; // only parents from the same pool
|
||||
uint8_t *key_data = NULL;
|
||||
osd_op_enc_t *op_enc = NULL;
|
||||
bool readonly = false;
|
||||
bool has_parent_loop = false;
|
||||
inode_t other_pool_parent_id = 0;
|
||||
int err_code = 0;
|
||||
|
||||
~inode_cache_t();
|
||||
};
|
||||
|
||||
struct vault_load_key_t
|
||||
{
|
||||
int key_state = 0;
|
||||
std::string key;
|
||||
};
|
||||
|
||||
// FIXME: Split into public and private interfaces
|
||||
class __attribute__((visibility("default"))) cluster_client_t
|
||||
{
|
||||
timerfd_manager_t *tfd = NULL;
|
||||
ring_loop_t *ringloop = NULL;
|
||||
|
||||
std::map<pool_id_t, uint64_t> pg_counts;
|
||||
std::map<pool_pg_num_t, osd_num_t> pg_primary;
|
||||
// config:
|
||||
|
||||
// client_max_dirty_* is actually "max unsynced", for the case when immediate_commit is off
|
||||
uint64_t client_max_dirty_bytes = 0;
|
||||
uint64_t client_max_dirty_ops = 0;
|
||||
@@ -99,12 +120,23 @@ class __attribute__((visibility("default"))) cluster_client_t
|
||||
uint64_t client_max_writeback_iodepth = 0;
|
||||
std::string conf_hostname;
|
||||
|
||||
std::string vault_url;
|
||||
std::string vault_client_cert;
|
||||
std::string vault_client_key;
|
||||
std::string vault_ca;
|
||||
std::string vault_secret_api_path;
|
||||
uint64_t vault_timeout_ms = 0;
|
||||
uint64_t vault_error_timeout_sec = 0;
|
||||
uint64_t vault_refresh_leeway_sec = 0;
|
||||
|
||||
int log_level = 0;
|
||||
int client_retry_interval = 50; // ms
|
||||
int client_eio_retry_interval = 1000; // ms
|
||||
bool client_retry_enospc = true;
|
||||
int client_wait_up_timeout = 16; // sec (for listings)
|
||||
|
||||
// state:
|
||||
|
||||
std::string client_hostname;
|
||||
std::map<std::string, int> self_tree_metrics;
|
||||
std::map<osd_num_t, int> osd_tree_metrics;
|
||||
@@ -112,15 +144,28 @@ class __attribute__((visibility("default"))) cluster_client_t
|
||||
int retry_timeout_id = -1;
|
||||
int retry_timeout_duration = 0;
|
||||
std::vector<cluster_op_t*> offline_ops;
|
||||
std::vector<cluster_op_t*> key_wait_ops;
|
||||
cluster_op_t *op_queue_head = NULL, *op_queue_tail = NULL;
|
||||
writeback_cache_t *wb = NULL;
|
||||
std::set<osd_num_t> dirty_osds;
|
||||
uint64_t dirty_bytes = 0, dirty_ops = 0;
|
||||
|
||||
void *scrap_buffer = NULL;
|
||||
unsigned scrap_buffer_size = 0;
|
||||
// inodes require some extra state for read/write, it's stored here.
|
||||
// moreover, robin_hood access is slightly faster than std::map :)
|
||||
robin_hood::unordered_flat_map<inode_t, std::shared_ptr<inode_cache_t>> inode_cache;
|
||||
std::set<std::pair<inode_t, inode_t>> inode_cache_children;
|
||||
|
||||
http_context_t *vault_http_ctx = NULL;
|
||||
http_co_t *vault_http_cli = NULL;
|
||||
bool vault_loading = false;
|
||||
std::string vault_token;
|
||||
bool vault_auth_error = false;
|
||||
timespec vault_token_expire = {};
|
||||
std::vector<std::string> vault_key_load_queue;
|
||||
std::map<std::string, vault_load_key_t> vault_keys;
|
||||
|
||||
bool pgs_loaded = false;
|
||||
std::map<pool_id_t, uint64_t> pg_counts;
|
||||
ring_consumer_t consumer;
|
||||
std::vector<std::function<void(void)>> on_ready_hooks;
|
||||
int list_retry_timeout_id = -1;
|
||||
@@ -156,6 +201,13 @@ public:
|
||||
protected:
|
||||
void continue_ops(int time_passed = 0);
|
||||
|
||||
std::shared_ptr<inode_cache_t> inode_cache_get(inode_t ino);
|
||||
void vault_parse_config();
|
||||
bool vault_check_token();
|
||||
void vault_load_keys();
|
||||
void vault_destroy();
|
||||
void vault_parse_secret(const std::string & key_id, const std::string & err, json11::Json data);
|
||||
|
||||
bool affects_osd(uint64_t inode, uint64_t offset, uint64_t len, osd_num_t osd);
|
||||
bool affects_pg(uint64_t inode, uint64_t offset, uint64_t len, pool_id_t pool_id, pg_num_t pg_num);
|
||||
|
||||
@@ -165,6 +217,7 @@ protected:
|
||||
void on_change_pg_state_hook(pool_id_t pool_id, pg_num_t pg_num, osd_num_t prev_primary);
|
||||
void on_change_osd_state_hook(uint64_t peer_osd);
|
||||
void on_change_node_placement_hook();
|
||||
void on_change_inode_hook(uint64_t inode, bool removed);
|
||||
|
||||
void execute_internal(cluster_op_t *op);
|
||||
void execute_cas(cluster_op_t *op);
|
||||
@@ -181,6 +234,7 @@ protected:
|
||||
void erase_op(cluster_op_t *op);
|
||||
void calc_wait(cluster_op_t *op);
|
||||
void inc_wait(uint64_t opcode, uint64_t flags, cluster_op_t *next, int inc);
|
||||
|
||||
void continue_lists();
|
||||
bool continue_listing(inode_list_t *lst);
|
||||
bool restart_listing(inode_list_t* lst);
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2019+
|
||||
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
|
||||
|
||||
#include <stdexcept>
|
||||
#include <assert.h>
|
||||
#include "cluster_client_impl.h"
|
||||
#include "str_util.h"
|
||||
|
||||
inode_cache_t::~inode_cache_t()
|
||||
{
|
||||
if (key_data)
|
||||
{
|
||||
free(key_data);
|
||||
key_data = NULL;
|
||||
op_enc = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void cluster_client_t::vault_parse_config()
|
||||
{
|
||||
vault_url = config["vault_url"].string_value();
|
||||
vault_client_cert = config["vault_client_cert"].string_value();
|
||||
if (vault_client_cert.empty())
|
||||
vault_client_cert = config["cert"].string_value();
|
||||
vault_client_key = config["vault_client_key"].string_value();
|
||||
if (vault_client_key.empty())
|
||||
vault_client_key = config["pkey"].string_value();
|
||||
vault_ca = config["vault_ca"].string_value();
|
||||
vault_secret_api_path = "/v1/secret/";
|
||||
if (config["vault_secret_api_path"].is_string())
|
||||
vault_secret_api_path = config["vault_secret_api_path"].string_value();
|
||||
vault_timeout_ms = config["vault_timeout_ms"].uint64_value();
|
||||
if (!vault_timeout_ms)
|
||||
vault_timeout_ms = 5000;
|
||||
vault_error_timeout_sec = config["vault_error_timeout_sec"].uint64_value();
|
||||
if (!vault_error_timeout_sec)
|
||||
vault_error_timeout_sec = 60;
|
||||
vault_refresh_leeway_sec = config["vault_refresh_leeway_sec"].uint64_value();
|
||||
if (!vault_refresh_leeway_sec)
|
||||
vault_refresh_leeway_sec = 60;
|
||||
}
|
||||
|
||||
// FIXME: Rework client API by adding open/close and cache inode information in the "FD" (maybe)
|
||||
void cluster_client_t::on_change_inode_hook(uint64_t inode, bool removed)
|
||||
{
|
||||
std::vector<inode_t> children = { inode };
|
||||
for (size_t i = 0; i < children.size(); i++)
|
||||
{
|
||||
auto it = inode_cache_children.lower_bound(std::make_pair(children[i], (inode_t)0));
|
||||
while (it != inode_cache_children.end() && it->first == children[i])
|
||||
{
|
||||
children.push_back(it->second);
|
||||
it++;
|
||||
}
|
||||
}
|
||||
for (auto & inode: children)
|
||||
{
|
||||
auto it = inode_cache.find(inode);
|
||||
if (it != inode_cache.end())
|
||||
{
|
||||
auto icache = it->second;
|
||||
for (auto & parent: icache->chain)
|
||||
{
|
||||
inode_cache_children.erase(std::make_pair(parent, inode));
|
||||
}
|
||||
inode_cache.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<inode_cache_t> cluster_client_t::inode_cache_get(inode_t ino)
|
||||
{
|
||||
auto icache_it = inode_cache.find(ino);
|
||||
if (icache_it != inode_cache.end())
|
||||
{
|
||||
return icache_it->second;
|
||||
}
|
||||
// Fill inode cache
|
||||
auto ino_it = st_cli->inode_config.find(ino);
|
||||
if (ino_it == st_cli->inode_config.end())
|
||||
{
|
||||
inode_cache[ino] = NULL;
|
||||
return NULL;
|
||||
}
|
||||
auto pool_it = st_cli->pool_config.find(INODE_POOL(ino));
|
||||
if (pool_it == st_cli->pool_config.end())
|
||||
{
|
||||
inode_cache[ino] = NULL;
|
||||
return NULL;
|
||||
}
|
||||
auto & inode_cfg = ino_it->second;
|
||||
auto & pool_cfg = pool_it->second;
|
||||
std::shared_ptr<inode_cache_t> icache = std::make_shared<inode_cache_t>();
|
||||
icache->readonly = inode_cfg.readonly;
|
||||
icache->chain.push_back(ino);
|
||||
std::vector<inode_config_t*> chain_cfg;
|
||||
// FIXME: Allow unencrypted read & write when all chain is encrypted with the same key
|
||||
int enc_key_count = !inode_cfg.enc_key.empty() ? 1 : 0;
|
||||
if (inode_cfg.parent_id)
|
||||
{
|
||||
// Check for loops and cache the chain
|
||||
robin_hood::unordered_flat_set<inode_t> seen;
|
||||
seen.insert(ino);
|
||||
uint64_t parent_id = inode_cfg.parent_id;
|
||||
while (parent_id)
|
||||
{
|
||||
if (seen.find(parent_id) != seen.end())
|
||||
{
|
||||
icache->has_parent_loop = true;
|
||||
break;
|
||||
}
|
||||
seen.insert(parent_id);
|
||||
ino_it = st_cli->inode_config.find(parent_id);
|
||||
if (INODE_POOL(parent_id) == INODE_POOL(ino))
|
||||
{
|
||||
icache->chain.push_back(parent_id);
|
||||
if (ino_it == st_cli->inode_config.end())
|
||||
chain_cfg.push_back(NULL);
|
||||
else
|
||||
{
|
||||
chain_cfg.push_back(&ino_it->second);
|
||||
if (!ino_it->second.enc_key.empty())
|
||||
enc_key_count++;
|
||||
}
|
||||
}
|
||||
else if (!icache->other_pool_parent_id)
|
||||
icache->other_pool_parent_id = parent_id;
|
||||
if (ino_it == st_cli->inode_config.end())
|
||||
break;
|
||||
parent_id = ino_it->second.parent_id;
|
||||
}
|
||||
}
|
||||
// Check external keys and wait for loading, if required
|
||||
if (enc_key_count)
|
||||
{
|
||||
for (size_t i = 0; i <= chain_cfg.size(); i++)
|
||||
{
|
||||
inode_config_t *cfg = !i ? &inode_cfg : chain_cfg[i-1];
|
||||
if (cfg && cfg->enc_key.substr(0, strlen(VAULT_KEY_PREFIX)) == VAULT_KEY_PREFIX)
|
||||
{
|
||||
auto & ik = vault_keys[inode_cfg.enc_key];
|
||||
if (ik.key_state == VAULT_KEY_ERROR || vault_url.empty())
|
||||
{
|
||||
icache->err_code = EPERM;
|
||||
enc_key_count = 0;
|
||||
}
|
||||
else if (ik.key_state == VAULT_KEY_NOT_LOADED)
|
||||
{
|
||||
ik.key_state = VAULT_KEY_LOADING;
|
||||
vault_key_load_queue.push_back(inode_cfg.enc_key);
|
||||
vault_load_keys();
|
||||
icache->err_code = EAGAIN;
|
||||
enc_key_count = 0;
|
||||
}
|
||||
else if (ik.key_state == VAULT_KEY_LOADING)
|
||||
{
|
||||
icache->err_code = EAGAIN;
|
||||
enc_key_count = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(ik.key_state == VAULT_KEY_LOADED);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Generate encryption key chain, if applicable
|
||||
if (enc_key_count)
|
||||
{
|
||||
uint8_t *key_data = (uint8_t*)malloc_or_die(
|
||||
AES_256_XTS_KEY_SIZE * enc_key_count +
|
||||
sizeof(uint8_t*) * icache->chain.size() +
|
||||
sizeof(osd_op_enc_t)
|
||||
);
|
||||
uint8_t **keys = (uint8_t**)(key_data + AES_256_XTS_KEY_SIZE * enc_key_count);
|
||||
osd_op_enc_t *enc = (osd_op_enc_t*)((uint8_t*)keys + sizeof(uint8_t*)*icache->chain.size());
|
||||
size_t key_pos = 0;
|
||||
for (size_t i = 0; i <= chain_cfg.size(); i++)
|
||||
{
|
||||
inode_config_t *cfg = !i ? &inode_cfg : chain_cfg[i-1];
|
||||
if (cfg && !cfg->enc_key.empty())
|
||||
{
|
||||
const auto & key = cfg->enc_key.substr(0, strlen(VAULT_KEY_PREFIX)) == VAULT_KEY_PREFIX
|
||||
? vault_keys.at(cfg->enc_key).key
|
||||
: cfg->enc_key;
|
||||
assert(key_pos < AES_256_XTS_KEY_SIZE * enc_key_count);
|
||||
assert(key.size() == 2*AES_256_XTS_KEY_SIZE);
|
||||
keys[i] = key_data + key_pos;
|
||||
fromhexstr(key, AES_256_XTS_KEY_SIZE, key_data + key_pos);
|
||||
key_pos += AES_256_XTS_KEY_SIZE;
|
||||
}
|
||||
else
|
||||
keys[i] = NULL;
|
||||
}
|
||||
enc->key_chain = keys;
|
||||
enc->chain_size = icache->chain.size();
|
||||
enc->read_chain_bitmap_pos = pool_cfg.data_block_size/pool_cfg.bitmap_granularity/8;
|
||||
enc->bitmap_granularity = pool_cfg.bitmap_granularity;
|
||||
icache->key_data = key_data;
|
||||
icache->op_enc = enc;
|
||||
}
|
||||
inode_cache[ino] = icache;
|
||||
for (auto & parent: icache->chain)
|
||||
{
|
||||
if (parent != ino)
|
||||
inode_cache_children.insert(std::make_pair(parent, ino));
|
||||
}
|
||||
return icache;
|
||||
}
|
||||
|
||||
void cluster_client_t::vault_parse_secret(const std::string & key_id, const std::string & err, json11::Json data)
|
||||
{
|
||||
vault_loading = false;
|
||||
auto & k = vault_keys[key_id];
|
||||
if (err != "")
|
||||
{
|
||||
k.key_state = VAULT_KEY_ERROR;
|
||||
fprintf(stderr, "Vault %s%s%s request failed: %s\n", vault_url.c_str(),
|
||||
vault_secret_api_path.c_str(), key_id.c_str()+strlen(VAULT_KEY_PREFIX), err.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
auto hexkey = data["data"]["key"].string_value();
|
||||
if (hexkey.empty() || !ishexstr(hexkey) || hexkey.size() != 2*AES_256_XTS_KEY_SIZE)
|
||||
{
|
||||
k.key_state = VAULT_KEY_ERROR;
|
||||
fprintf(stderr, "Vault /v1/secret/%s request failed: 'key' is empty or has invalid format\n", key_id.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
k.key_state = VAULT_KEY_LOADED;
|
||||
k.key = hexkey;
|
||||
}
|
||||
}
|
||||
if (vault_key_load_queue.empty())
|
||||
{
|
||||
auto ops = std::move(key_wait_ops);
|
||||
for (cluster_op_t *op: ops)
|
||||
inode_cache.erase(op->inode);
|
||||
for (cluster_op_t *op: ops)
|
||||
execute_internal(op);
|
||||
}
|
||||
else
|
||||
vault_load_keys();
|
||||
}
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
#include "cluster_client.h"
|
||||
|
||||
#define SCRAP_BUFFER_SIZE 4*1024*1024
|
||||
#define PART_SENT 1
|
||||
#define PART_DONE 2
|
||||
#define PART_ERROR 4
|
||||
@@ -18,6 +17,11 @@
|
||||
#define OP_FLUSH_BUFFER 0x02
|
||||
#define OP_IMMEDIATE_COMMIT 0x04
|
||||
|
||||
#define VAULT_KEY_NOT_LOADED 0
|
||||
#define VAULT_KEY_LOADING 1
|
||||
#define VAULT_KEY_LOADED 2
|
||||
#define VAULT_KEY_ERROR 3
|
||||
|
||||
struct cluster_buffer_t
|
||||
{
|
||||
uint8_t *buf;
|
||||
|
||||
@@ -2,10 +2,124 @@
|
||||
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
|
||||
|
||||
#include "cluster_client.h"
|
||||
#include "cluster_client_impl.h"
|
||||
#include "etcd_state_client_http.h"
|
||||
#include "http_client.h"
|
||||
|
||||
cluster_client_t* cluster_client_t::create(ring_loop_t *ringloop, timerfd_manager_t *tfd, json11::Json config)
|
||||
{
|
||||
auto st_cli = new etcd_state_client_http_t(tfd);
|
||||
return new cluster_client_t(ringloop, tfd, config, std::unique_ptr<etcd_state_client_t>(st_cli));
|
||||
}
|
||||
|
||||
bool cluster_client_t::vault_check_token()
|
||||
{
|
||||
timespec now;
|
||||
clock_gettime(CLOCK_REALTIME, &now);
|
||||
if (!vault_token_expire.tv_sec || vault_token_expire.tv_sec < now.tv_sec)
|
||||
{
|
||||
vault_loading = true;
|
||||
http_json_post(
|
||||
vault_http_cli, vault_url+"/v1/auth/cert/login", json11::Json::object{}, "",
|
||||
(http_options_t){ .timeout = (int)vault_timeout_ms, .keepalive = true },
|
||||
[this](http_message_t *response)
|
||||
{
|
||||
clock_gettime(CLOCK_REALTIME, &vault_token_expire);
|
||||
vault_loading = false;
|
||||
std::string err;
|
||||
json11::Json data;
|
||||
response->parse_json_response(err, data);
|
||||
if (err != "")
|
||||
{
|
||||
vault_token_expire.tv_sec += vault_error_timeout_sec;
|
||||
fprintf(stderr, "Vault request failed: %s\n", err.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
uint64_t ttl = data["auth"]["lease_duration"].uint64_value();
|
||||
vault_token = data["auth"]["client_token"].string_value();
|
||||
if (vault_token.empty() || !ttl)
|
||||
{
|
||||
vault_token_expire.tv_sec += vault_error_timeout_sec;
|
||||
fprintf(stderr, "No token or lease_duration in Vault response: %s\n", data.dump().c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ttl < vault_refresh_leeway_sec)
|
||||
vault_token_expire.tv_sec += ttl/2;
|
||||
else
|
||||
vault_token_expire.tv_sec += ttl - vault_refresh_leeway_sec;
|
||||
}
|
||||
}
|
||||
vault_load_keys();
|
||||
}
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (vault_token.empty())
|
||||
{
|
||||
// Auth error happened, mark all loads as failed
|
||||
for (auto & key_id: vault_key_load_queue)
|
||||
{
|
||||
auto & k = vault_keys[key_id];
|
||||
k.key_state = VAULT_KEY_ERROR;
|
||||
}
|
||||
vault_key_load_queue.clear();
|
||||
auto ops = std::move(key_wait_ops);
|
||||
for (cluster_op_t *op: ops)
|
||||
inode_cache.erase(op->inode);
|
||||
for (cluster_op_t *op: ops)
|
||||
execute_internal(op);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void cluster_client_t::vault_destroy()
|
||||
{
|
||||
if (vault_http_ctx)
|
||||
{
|
||||
http_destroy(vault_http_cli);
|
||||
http_context_destroy(vault_http_ctx);
|
||||
vault_http_cli = NULL;
|
||||
vault_http_ctx = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void cluster_client_t::vault_load_keys()
|
||||
{
|
||||
if (vault_loading || !vault_key_load_queue.size())
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!vault_http_ctx)
|
||||
{
|
||||
std::string error;
|
||||
vault_http_ctx = http_context_init(tfd, vault_client_cert, vault_client_key, vault_ca, true, error);
|
||||
if (!vault_http_ctx)
|
||||
{
|
||||
fprintf(stderr, "Failed to initialize HTTP context for Vault: %s\n", error.c_str());
|
||||
exit(1);
|
||||
}
|
||||
vault_http_cli = http_init(vault_http_ctx);
|
||||
}
|
||||
if (!vault_check_token())
|
||||
{
|
||||
return;
|
||||
}
|
||||
std::string key_id = vault_key_load_queue[0];
|
||||
vault_key_load_queue.erase(vault_key_load_queue.begin());
|
||||
vault_loading = true;
|
||||
http_get(
|
||||
vault_http_cli, vault_url+vault_secret_api_path+key_id.substr(strlen(VAULT_KEY_PREFIX)), "X-Vault-Token: "+vault_token+"\r\n",
|
||||
(http_options_t){ .timeout = (int)vault_timeout_ms, .keepalive = true },
|
||||
[this, key_id](http_message_t *response)
|
||||
{
|
||||
vault_loading = false;
|
||||
std::string err;
|
||||
json11::Json data;
|
||||
response->parse_json_response(err, data);
|
||||
vault_parse_secret(key_id, err, data);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2019+
|
||||
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "malloc_or_die.h"
|
||||
#include "osd_ops.h"
|
||||
#include "msgr_op.h"
|
||||
#include "pg_states.h"
|
||||
#include "etcd_state_client.h"
|
||||
#include "addr_util.h"
|
||||
#include "str_util.h"
|
||||
#include "json_util.h"
|
||||
|
||||
etcd_state_client_t::~etcd_state_client_t()
|
||||
{
|
||||
@@ -51,48 +52,93 @@ std::vector<std::string> etcd_state_client_t::get_addresses()
|
||||
return addrs;
|
||||
}
|
||||
|
||||
void etcd_state_client_t::add_etcd_url(std::string addr)
|
||||
std::shared_ptr<user_info_t> etcd_state_client_t::get_user(const std::string & username)
|
||||
{
|
||||
if (addr.length() > 0)
|
||||
auto user_it = user_info.find(username);
|
||||
if (user_it != user_info.end())
|
||||
{
|
||||
return user_it->second;
|
||||
}
|
||||
auto inf = std::make_shared<user_info_t>();
|
||||
inf->name = username;
|
||||
return inf;
|
||||
}
|
||||
|
||||
bool etcd_state_client_t::check_image_perm(const std::shared_ptr<user_info_t> & user_info, inode_t inode_num, bool write)
|
||||
{
|
||||
if (user_info->type == user_type_t::ADMIN)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
auto cache_it = user_info->perm_cache.find(inode_num);
|
||||
if (cache_it != user_info->perm_cache.end() &&
|
||||
cache_it->second.mod_revision == user_perm_cache_revision)
|
||||
{
|
||||
return write ? (cache_it->second.perm == user_perm_t::OWNER) : (cache_it->second.perm != user_perm_t::DENY);
|
||||
}
|
||||
auto inode_it = inode_config.find(inode_num);
|
||||
if (inode_it == inode_config.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// FIXME Implement cache reset after reworking etcd interaction to not keep everything in memory
|
||||
auto & perm_item = user_info->perm_cache[inode_num];
|
||||
perm_item.mod_revision = user_perm_cache_revision;
|
||||
perm_item.perm = (user_info->name == inode_it->second.owner || inode_it->second.owner_group != "" &&
|
||||
user_info->groups.find(inode_it->second.owner_group) != user_info->groups.end()
|
||||
? user_perm_t::OWNER : (inode_it->second.reader_group != "" &&
|
||||
user_info->groups.find(inode_it->second.reader_group) != user_info->groups.end()
|
||||
? user_perm_t::READER : user_perm_t::DENY));
|
||||
return write ? (perm_item.perm == user_perm_t::OWNER) : (perm_item.perm != user_perm_t::DENY);
|
||||
}
|
||||
|
||||
void etcd_state_client_t::add_etcd_url(std::string etcd_address)
|
||||
{
|
||||
if (etcd_address.size() > 0)
|
||||
{
|
||||
if (strtolower(addr.substr(0, 7)) == "http://")
|
||||
addr = addr.substr(7);
|
||||
else if (strtolower(addr.substr(0, 8)) == "https://")
|
||||
{
|
||||
fprintf(stderr, "HTTPS is unsupported for etcd. Either use plain HTTP or setup a local proxy for etcd interaction\n");
|
||||
exit(1);
|
||||
}
|
||||
if (!local_ips.size())
|
||||
local_ips = getifaddr_list(std::vector<addr_mask_t>(), true);
|
||||
std::string check_addr;
|
||||
int pos = addr.find('/');
|
||||
int pos2 = addr.find(':');
|
||||
if (pos2 >= 0)
|
||||
check_addr = addr.substr(0, pos2);
|
||||
else if (pos >= 0)
|
||||
check_addr = addr.substr(0, pos);
|
||||
else
|
||||
check_addr = addr;
|
||||
if (pos == std::string::npos)
|
||||
addr += "/v3";
|
||||
bool local = false;
|
||||
int i;
|
||||
for (i = 0; i < local_ips.size(); i++)
|
||||
{
|
||||
if (local_ips[i] == check_addr)
|
||||
{
|
||||
local = true;
|
||||
break;
|
||||
}
|
||||
// Fill local_ips
|
||||
for (auto & ip: getifaddr_list(std::vector<addr_mask_t>(), true))
|
||||
local_ips.insert(ip);
|
||||
}
|
||||
auto & to = local ? this->etcd_local : this->etcd_addresses;
|
||||
std::string etcd_api_path;
|
||||
bool ssl = false;
|
||||
if (etcd_address.substr(0, 8) == "https://")
|
||||
{
|
||||
ssl = true;
|
||||
etcd_address = etcd_address.substr(8);
|
||||
}
|
||||
else if (etcd_address.substr(0, 7) == "http://")
|
||||
etcd_address = etcd_address.substr(7);
|
||||
auto pos = etcd_address.find('/');
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
etcd_api_path = etcd_address.substr(pos);
|
||||
etcd_address = etcd_address.substr(0, pos);
|
||||
}
|
||||
else
|
||||
etcd_api_path = "/v3";
|
||||
pos = etcd_address.find(':');
|
||||
auto check_addr = (pos != std::string::npos ? etcd_address.substr(0, pos) : etcd_address);
|
||||
bool is_local = local_ips.find(check_addr) != local_ips.end();
|
||||
auto & to = (is_local ? etcd_local : etcd_addresses);
|
||||
check_addr = (ssl ? "https://" : "http://") + etcd_address + etcd_api_path;
|
||||
size_t i;
|
||||
for (i = 0; i < to.size(); i++)
|
||||
{
|
||||
if (to[i] == addr)
|
||||
if (to[i] == check_addr)
|
||||
break;
|
||||
}
|
||||
if (i >= to.size())
|
||||
to.push_back(addr);
|
||||
{
|
||||
to.push_back(check_addr);
|
||||
// Check if it's a domain name
|
||||
sockaddr_storage ss;
|
||||
bool is_name = !is_local && !string_to_addr(etcd_address, true, 0, &ss);
|
||||
auto & to_addr = (is_local ? etcd_local_addr_urls : (is_name ? etcd_name_urls : etcd_nonlocal_addr_urls));
|
||||
to_addr.push_back((http_url_t){ .ssl = ssl, .addr = etcd_address, .hostname = etcd_address, .path = etcd_api_path });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +146,9 @@ void etcd_state_client_t::parse_config(const json11::Json & config)
|
||||
{
|
||||
this->etcd_local.clear();
|
||||
this->etcd_addresses.clear();
|
||||
this->etcd_local_addr_urls.clear();
|
||||
this->etcd_nonlocal_addr_urls.clear();
|
||||
this->etcd_name_urls.clear();
|
||||
if (config["etcd_address"].is_string())
|
||||
{
|
||||
std::string ea = config["etcd_address"].string_value();
|
||||
@@ -120,6 +169,22 @@ void etcd_state_client_t::parse_config(const json11::Json & config)
|
||||
add_etcd_url(ea.string_value());
|
||||
}
|
||||
}
|
||||
if (this->etcd_client_cert != "")
|
||||
{
|
||||
this->etcd_client_cert = config["etcd_client_cert"].string_value();
|
||||
this->etcd_client_key = config["etcd_client_key"].string_value();
|
||||
}
|
||||
else if (this->osd_num)
|
||||
{
|
||||
this->etcd_client_cert = config["osd_cert"].string_value();
|
||||
this->etcd_client_key = config["osd_pkey"].string_value();
|
||||
}
|
||||
else
|
||||
{
|
||||
this->etcd_client_cert = config["cert"].string_value();
|
||||
this->etcd_client_key = config["pkey"].string_value();
|
||||
}
|
||||
this->etcd_ca = config["etcd_ca"].string_value();
|
||||
this->etcd_prefix = config["etcd_prefix"].string_value();
|
||||
if (this->etcd_prefix == "")
|
||||
{
|
||||
@@ -223,12 +288,8 @@ void etcd_state_client_t::load_pgs(std::function<void(const std::string &)> cb)
|
||||
json11::Json::array txn = {
|
||||
json11::Json::object {
|
||||
{ "request_range", json11::Json::object {
|
||||
{ "key", base64_encode(etcd_prefix+"/config/pools") },
|
||||
} }
|
||||
},
|
||||
json11::Json::object {
|
||||
{ "request_range", json11::Json::object {
|
||||
{ "key", base64_encode(etcd_prefix+"/config/pgs") },
|
||||
{ "key", base64_encode(etcd_prefix+"/config/") },
|
||||
{ "range_end", base64_encode(etcd_prefix+"/config0") },
|
||||
} }
|
||||
},
|
||||
json11::Json::object {
|
||||
@@ -236,12 +297,6 @@ void etcd_state_client_t::load_pgs(std::function<void(const std::string &)> cb)
|
||||
{ "key", base64_encode(etcd_prefix+"/pg/config") },
|
||||
} }
|
||||
},
|
||||
json11::Json::object {
|
||||
{ "request_range", json11::Json::object {
|
||||
{ "key", base64_encode(etcd_prefix+"/config/inode/") },
|
||||
{ "range_end", base64_encode(etcd_prefix+"/config/inode0") },
|
||||
} }
|
||||
},
|
||||
json11::Json::object {
|
||||
{ "request_range", json11::Json::object {
|
||||
{ "key", base64_encode(etcd_prefix+"/pg/history/") },
|
||||
@@ -494,6 +549,8 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv)
|
||||
pc.used_for_app = "fs:"+pc.used_for_app;
|
||||
else
|
||||
pc.used_for_app = pool_item.second["used_for_app"].as_string();
|
||||
// Create group permission
|
||||
pc.creator_group = pool_item.second["creator_group"].string_value();
|
||||
// Local Read Configuration
|
||||
std::string local_reads = pool_item.second["local_reads"].string_value();
|
||||
if (local_reads == "nearest")
|
||||
@@ -817,37 +874,15 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv)
|
||||
{
|
||||
on_inode_change_hook(inode_num, true);
|
||||
}
|
||||
if (this->inode_config.find(inode_num) != this->inode_config.end())
|
||||
{
|
||||
user_perm_cache_revision = kv.mod_revision;
|
||||
}
|
||||
this->inode_config.erase(inode_num);
|
||||
}
|
||||
else
|
||||
{
|
||||
inode_t parent_inode_num = value["parent_id"].uint64_value();
|
||||
if (parent_inode_num && !(parent_inode_num >> (64-POOL_ID_BITS)))
|
||||
{
|
||||
uint64_t parent_pool_id = value["parent_pool"].uint64_value();
|
||||
if (!parent_pool_id)
|
||||
parent_inode_num |= pool_id << (64-POOL_ID_BITS);
|
||||
else if (parent_pool_id >= POOL_ID_MAX)
|
||||
{
|
||||
fprintf(
|
||||
stderr, "Inode %ju/%ju parent_pool value is invalid, ignoring parent setting\n",
|
||||
inode_num >> (64-POOL_ID_BITS), inode_num & (((uint64_t)1 << (64-POOL_ID_BITS)) - 1)
|
||||
);
|
||||
parent_inode_num = 0;
|
||||
}
|
||||
else
|
||||
parent_inode_num |= parent_pool_id << (64-POOL_ID_BITS);
|
||||
}
|
||||
insert_inode_config((inode_config_t){
|
||||
.num = inode_num,
|
||||
.name = value["name"].string_value(),
|
||||
.size = value["size"].uint64_value(),
|
||||
.parent_id = parent_inode_num,
|
||||
.readonly = value["readonly"].bool_value(),
|
||||
.deleted = value["deleted"].bool_value(),
|
||||
.meta = value["meta"],
|
||||
.mod_revision = kv.mod_revision,
|
||||
});
|
||||
insert_inode_config(deserialize_inode_cfg(inode_num, kv.value, kv.mod_revision));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -858,6 +893,38 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv)
|
||||
if (on_change_node_placement_hook)
|
||||
on_change_node_placement_hook();
|
||||
}
|
||||
else if (key.substr(0, etcd_prefix.length()+13) == etcd_prefix+"/config/user/")
|
||||
{
|
||||
// <etcd_prefix>/config/user/<username>
|
||||
auto name = key.substr(etcd_prefix.length()+13);
|
||||
auto & inf = user_info[name];
|
||||
if (!value.is_object())
|
||||
{
|
||||
if (inf)
|
||||
{
|
||||
inf->type = user_type_t::CLIENT;
|
||||
inf->groups.clear();
|
||||
inf->perm_cache.clear();
|
||||
}
|
||||
user_info.erase(name);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!inf)
|
||||
{
|
||||
inf = std::make_shared<user_info_t>();
|
||||
inf->name = name;
|
||||
}
|
||||
inf->type = value["type"] == "admin" ? user_type_t::ADMIN : user_type_t::CLIENT;
|
||||
inf->groups.clear();
|
||||
for (auto & group: value["groups"].array_items())
|
||||
{
|
||||
if (group.string_value() != "")
|
||||
inf->groups.insert(group.string_value());
|
||||
}
|
||||
inf->perm_cache.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t etcd_state_client_t::parse_immediate_commit(const std::string & immediate_commit_str, uint32_t default_value)
|
||||
@@ -880,7 +947,12 @@ uint32_t etcd_state_client_t::parse_scheme(const std::string & scheme)
|
||||
|
||||
void etcd_state_client_t::insert_inode_config(const inode_config_t & cfg)
|
||||
{
|
||||
this->inode_config[cfg.num] = cfg;
|
||||
auto & cfg_ref = this->inode_config[cfg.num];
|
||||
if (cfg_ref.mod_revision != cfg.mod_revision)
|
||||
{
|
||||
user_perm_cache_revision = cfg.mod_revision;
|
||||
}
|
||||
cfg_ref = cfg;
|
||||
if (cfg.name != "")
|
||||
{
|
||||
this->inode_by_name[cfg.name] = cfg.num;
|
||||
@@ -936,6 +1008,10 @@ json11::Json::object etcd_state_client_t::serialize_inode_cfg(inode_config_t *cf
|
||||
new_cfg["parent_pool"] = (uint64_t)INODE_POOL(cfg->parent_id);
|
||||
new_cfg["parent_id"] = (uint64_t)INODE_NO_POOL(cfg->parent_id);
|
||||
}
|
||||
if (!cfg->enc_key.empty())
|
||||
{
|
||||
new_cfg["enc_key"] = cfg->enc_key;
|
||||
}
|
||||
if (cfg->readonly)
|
||||
{
|
||||
new_cfg["readonly"] = true;
|
||||
@@ -944,6 +1020,18 @@ json11::Json::object etcd_state_client_t::serialize_inode_cfg(inode_config_t *cf
|
||||
{
|
||||
new_cfg["deleted"] = true;
|
||||
}
|
||||
if (!cfg->owner.empty())
|
||||
{
|
||||
new_cfg["owner"] = cfg->owner;
|
||||
}
|
||||
if (!cfg->owner_group.empty())
|
||||
{
|
||||
new_cfg["owner_group"] = cfg->owner_group;
|
||||
}
|
||||
if (!cfg->reader_group.empty())
|
||||
{
|
||||
new_cfg["reader_group"] = cfg->reader_group;
|
||||
}
|
||||
if (cfg->meta.is_object())
|
||||
{
|
||||
new_cfg["meta"] = cfg->meta;
|
||||
@@ -951,6 +1039,53 @@ json11::Json::object etcd_state_client_t::serialize_inode_cfg(inode_config_t *cf
|
||||
return new_cfg;
|
||||
}
|
||||
|
||||
inode_config_t etcd_state_client_t::deserialize_inode_cfg(uint64_t inode_num, json11::Json value, uint64_t mod_revision)
|
||||
{
|
||||
inode_t parent_inode_num = value["parent_id"].uint64_value();
|
||||
if (parent_inode_num && !INODE_POOL(parent_inode_num))
|
||||
{
|
||||
uint64_t parent_pool_id = value["parent_pool"].uint64_value();
|
||||
if (!parent_pool_id)
|
||||
parent_inode_num = INODE_WITH_POOL(INODE_POOL(inode_num), parent_inode_num);
|
||||
else if (parent_pool_id >= POOL_ID_MAX)
|
||||
{
|
||||
fprintf(
|
||||
stderr, "Inode %u/%ju parent_pool value is invalid, ignoring parent setting\n",
|
||||
INODE_POOL(inode_num), INODE_NO_POOL(inode_num)
|
||||
);
|
||||
parent_inode_num = 0;
|
||||
}
|
||||
else
|
||||
parent_inode_num |= parent_pool_id << (64-POOL_ID_BITS);
|
||||
}
|
||||
std::string enc_key;
|
||||
if (!value["enc_key"].is_null())
|
||||
{
|
||||
enc_key = value["enc_key"].string_value();
|
||||
if (enc_key.substr(0, strlen(VAULT_KEY_PREFIX)) != VAULT_KEY_PREFIX &&
|
||||
(enc_key.size() != 2*AES_256_XTS_KEY_SIZE || !ishexstr(enc_key)))
|
||||
{
|
||||
enc_key = "";
|
||||
fprintf(stderr, "Inode %u/%ju has invalid enc_key, should be %u bit hex string or Vault key reference\n",
|
||||
INODE_POOL(inode_num), INODE_NO_POOL(inode_num), AES_256_XTS_KEY_SIZE);
|
||||
}
|
||||
}
|
||||
return (inode_config_t){
|
||||
.num = inode_num,
|
||||
.name = value["name"].string_value(),
|
||||
.size = value["size"].uint64_value(),
|
||||
.parent_id = parent_inode_num,
|
||||
.readonly = value["readonly"].bool_value(),
|
||||
.deleted = value["deleted"].bool_value(),
|
||||
.enc_key = std::move(enc_key),
|
||||
.owner = value["owner"].string_value(),
|
||||
.owner_group = value["owner_group"].string_value(),
|
||||
.reader_group = value["reader_group"].string_value(),
|
||||
.meta = value["meta"],
|
||||
.mod_revision = mod_revision,
|
||||
};
|
||||
}
|
||||
|
||||
int etcd_state_client_t::address_count()
|
||||
{
|
||||
return etcd_addresses.size() + etcd_local.size();
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
#include <memory>
|
||||
|
||||
#include "json11/json11.hpp"
|
||||
#include "object_id.h"
|
||||
#include "timerfd_manager.h"
|
||||
#include "../util/robin_hood.h"
|
||||
|
||||
#define ETCD_CONFIG_WATCH_ID 1
|
||||
#define ETCD_OSD_STATE_WATCH_ID 2
|
||||
@@ -19,6 +21,8 @@
|
||||
#define MAX_DATA_BLOCK_SIZE 128*1024*1024
|
||||
#define DEFAULT_BITMAP_GRANULARITY 4096
|
||||
|
||||
#define VAULT_KEY_PREFIX "vault:"
|
||||
|
||||
#ifndef IMMEDIATE_NONE
|
||||
#define IMMEDIATE_NONE 0
|
||||
#define IMMEDIATE_SMALL 1
|
||||
@@ -66,6 +70,7 @@ struct pool_config_t
|
||||
std::map<pg_num_t, pg_config_t> pg_config;
|
||||
uint64_t scrub_interval = 0;
|
||||
std::string used_for_app;
|
||||
std::string creator_group;
|
||||
int backfillfull = 0;
|
||||
int local_reads = 0;
|
||||
|
||||
@@ -83,6 +88,9 @@ struct inode_config_t
|
||||
inode_t parent_id = 0;
|
||||
bool readonly = false;
|
||||
bool deleted = false;
|
||||
std::string enc_key;
|
||||
// Permissions
|
||||
std::string owner, owner_group, reader_group;
|
||||
// Arbitrary metadata
|
||||
json11::Json meta;
|
||||
// Change revision of the metadata in etcd
|
||||
@@ -95,14 +103,49 @@ struct inode_watch_t
|
||||
inode_config_t cfg = {};
|
||||
};
|
||||
|
||||
struct http_url_t
|
||||
{
|
||||
bool ssl;
|
||||
std::string addr;
|
||||
std::string hostname;
|
||||
std::string path;
|
||||
};
|
||||
|
||||
enum class user_type_t
|
||||
{
|
||||
CLIENT = 0,
|
||||
ADMIN = 1,
|
||||
};
|
||||
|
||||
struct user_perm_t
|
||||
{
|
||||
enum class perm_type_t: uint8_t;
|
||||
constexpr static perm_type_t DENY = (perm_type_t)0;
|
||||
constexpr static perm_type_t READER = (perm_type_t)1;
|
||||
constexpr static perm_type_t OWNER = (perm_type_t)2;
|
||||
uint64_t mod_revision = 0;
|
||||
perm_type_t perm = DENY;
|
||||
};
|
||||
|
||||
struct user_info_t
|
||||
{
|
||||
std::string name;
|
||||
user_type_t type;
|
||||
robin_hood::unordered_flat_set<std::string> groups;
|
||||
robin_hood::unordered_flat_map<inode_t, user_perm_t> perm_cache;
|
||||
};
|
||||
|
||||
struct http_co_t;
|
||||
|
||||
struct __attribute__((visibility("default"))) etcd_state_client_t
|
||||
{
|
||||
protected:
|
||||
std::vector<std::string> local_ips;
|
||||
std::vector<std::string> etcd_addresses;
|
||||
std::set<std::string> local_ips;
|
||||
std::vector<std::string> etcd_local;
|
||||
std::vector<std::string> etcd_addresses;
|
||||
std::vector<http_url_t> etcd_local_addr_urls;
|
||||
std::vector<http_url_t> etcd_nonlocal_addr_urls;
|
||||
std::vector<http_url_t> etcd_name_urls;
|
||||
std::vector<inode_watch_t*> watches;
|
||||
std::set<osd_num_t> seen_peers;
|
||||
bool new_pg_config = false;
|
||||
@@ -121,7 +164,12 @@ public:
|
||||
uint64_t global_block_size = DEFAULT_BLOCK_SIZE;
|
||||
uint32_t global_bitmap_granularity = DEFAULT_BITMAP_GRANULARITY;
|
||||
uint32_t global_immediate_commit = IMMEDIATE_NONE;
|
||||
|
||||
uint64_t osd_num = 0;
|
||||
std::string etcd_prefix;
|
||||
std::string etcd_client_cert;
|
||||
std::string etcd_client_key;
|
||||
std::string etcd_ca;
|
||||
int log_level = 0;
|
||||
|
||||
uint64_t etcd_watch_revision_config = 0;
|
||||
@@ -132,6 +180,8 @@ public:
|
||||
std::map<osd_num_t, json11::Json> peer_states;
|
||||
std::map<inode_t, inode_config_t> inode_config;
|
||||
std::map<std::string, inode_t> inode_by_name;
|
||||
robin_hood::unordered_flat_map<std::string, std::shared_ptr<user_info_t>> user_info;
|
||||
uint64_t user_perm_cache_revision = 0;
|
||||
json11::Json node_placement;
|
||||
|
||||
std::function<void(std::map<std::string, etcd_kv_t> &)> on_change_hook;
|
||||
@@ -150,13 +200,17 @@ public:
|
||||
std::function<void(http_co_t *)> on_start_watcher_hook;
|
||||
|
||||
json11::Json::object serialize_inode_cfg(inode_config_t *cfg);
|
||||
inode_config_t deserialize_inode_cfg(uint64_t inode_num, json11::Json value, uint64_t mod_revision);
|
||||
etcd_kv_t parse_etcd_kv(const json11::Json & kv_json);
|
||||
std::vector<std::string> get_addresses();
|
||||
virtual void etcd_call_oneshot(std::string etcd_address, std::string api, json11::Json payload, int timeout, std::function<void(std::string, json11::Json)> callback) = 0;
|
||||
virtual void etcd_call(std::string api, json11::Json payload, int timeout, int retries, int interval, std::function<void(std::string, json11::Json)> callback) = 0;
|
||||
std::shared_ptr<user_info_t> get_user(const std::string & username);
|
||||
bool check_image_perm(const std::shared_ptr<user_info_t> & user_info, inode_t inode_num, bool write);
|
||||
virtual void etcd_call_oneshot(const std::string & etcd_address, const std::string & api, json11::Json payload, int timeout, std::function<void(std::string, json11::Json)> callback) = 0;
|
||||
virtual void etcd_call(const std::string & api, json11::Json payload, int timeout, int retries, int interval, std::function<void(std::string, json11::Json)> callback) = 0;
|
||||
void etcd_txn(json11::Json txn, int timeout, int retries, int interval, std::function<void(std::string, json11::Json)> callback);
|
||||
void etcd_txn_slow(json11::Json txn, std::function<void(std::string, json11::Json)> callback);
|
||||
virtual void etcd_add_watch(json11::Json watch) = 0;
|
||||
virtual std::string get_username() = 0;
|
||||
void load_global_config(std::function<void(const std::string &)> cb);
|
||||
virtual void load_global_config() = 0;
|
||||
void load_pgs(std::function<void(const std::string &)> cb);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2019+
|
||||
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
|
||||
|
||||
#include <assert.h>
|
||||
#include "etcd_state_client_http.h"
|
||||
#include "addr_util.h"
|
||||
#include "http_client.h"
|
||||
@@ -16,12 +17,12 @@ etcd_state_client_http_t::~etcd_state_client_http_t()
|
||||
stop_ws_keepalive();
|
||||
if (etcd_watch_ws)
|
||||
{
|
||||
http_close(etcd_watch_ws);
|
||||
http_destroy(etcd_watch_ws);
|
||||
etcd_watch_ws = NULL;
|
||||
}
|
||||
if (keepalive_client)
|
||||
{
|
||||
http_close(keepalive_client);
|
||||
http_destroy(keepalive_client);
|
||||
keepalive_client = NULL;
|
||||
}
|
||||
if (load_pgs_timer_id >= 0)
|
||||
@@ -29,6 +30,11 @@ etcd_state_client_http_t::~etcd_state_client_http_t()
|
||||
tfd->clear_timer(load_pgs_timer_id);
|
||||
load_pgs_timer_id = -1;
|
||||
}
|
||||
if (http_ctx)
|
||||
{
|
||||
http_context_destroy(http_ctx);
|
||||
http_ctx = NULL;
|
||||
}
|
||||
etcd_watches_initialised = -1;
|
||||
}
|
||||
|
||||
@@ -40,55 +46,56 @@ void etcd_state_client_http_t::etcd_add_watch(json11::Json watch)
|
||||
}
|
||||
}
|
||||
|
||||
void etcd_state_client_http_t::etcd_call_oneshot(std::string etcd_address, std::string api, json11::Json payload,
|
||||
std::string etcd_state_client_http_t::get_username()
|
||||
{
|
||||
return http_context_get_ssl_cn(get_http_ctx());
|
||||
}
|
||||
|
||||
http_context_t *etcd_state_client_http_t::get_http_ctx()
|
||||
{
|
||||
if (!http_ctx)
|
||||
{
|
||||
std::string error;
|
||||
http_ctx = http_context_init(tfd, etcd_client_cert, etcd_client_key, etcd_ca, true, error);
|
||||
if (!http_ctx)
|
||||
{
|
||||
fprintf(stderr, "Failed to initialize HTTP context: %s\n", error.c_str());
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
return http_ctx;
|
||||
}
|
||||
|
||||
void etcd_state_client_http_t::etcd_call_oneshot(const std::string & etcd_url, const std::string & api, json11::Json payload,
|
||||
int timeout, std::function<void(std::string, json11::Json)> callback)
|
||||
{
|
||||
std::string etcd_api_path;
|
||||
int pos = etcd_address.find('/');
|
||||
if (pos >= 0)
|
||||
{
|
||||
etcd_api_path = etcd_address.substr(pos);
|
||||
etcd_address = etcd_address.substr(0, pos);
|
||||
}
|
||||
std::string req = payload.dump();
|
||||
req = "POST "+etcd_api_path+api+" HTTP/1.1\r\n"
|
||||
"Host: "+etcd_address+"\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Content-Length: "+std::to_string(req.size())+"\r\n"
|
||||
"Connection: close\r\n"
|
||||
"\r\n"+req;
|
||||
auto http_cli = http_init(tfd);
|
||||
auto cb = [http_cli, callback](const http_response_t *response)
|
||||
auto http_cli = http_init(get_http_ctx());
|
||||
http_json_post(http_cli, etcd_url+api, payload, "", { .timeout = timeout }, [http_cli, callback](http_message_t *response)
|
||||
{
|
||||
std::string err;
|
||||
json11::Json data;
|
||||
response->parse_json_response(err, data);
|
||||
callback(err, data);
|
||||
http_close(http_cli);
|
||||
};
|
||||
http_request(http_cli, etcd_address, req, { .timeout = timeout }, cb);
|
||||
http_destroy(http_cli);
|
||||
});
|
||||
}
|
||||
|
||||
void etcd_state_client_http_t::etcd_call(std::string api, json11::Json payload, int timeout,
|
||||
void etcd_state_client_http_t::etcd_call(const std::string & api, json11::Json payload, int timeout,
|
||||
int retries, int interval, std::function<void(std::string, json11::Json)> callback)
|
||||
{
|
||||
if (!etcd_addresses.size() && !etcd_local.size())
|
||||
pick_next_etcd([=]()
|
||||
{
|
||||
fprintf(stderr, "etcd_address is missing in Vitastor configuration\n");
|
||||
exit(1);
|
||||
}
|
||||
pick_next_etcd();
|
||||
std::string etcd_address = selected_etcd_address;
|
||||
std::string etcd_api_path;
|
||||
int pos = etcd_address.find('/');
|
||||
if (pos >= 0)
|
||||
{
|
||||
etcd_api_path = etcd_address.substr(pos);
|
||||
etcd_address = etcd_address.substr(0, pos);
|
||||
}
|
||||
etcd_call_selected(api, payload, timeout, retries, interval, callback);
|
||||
});
|
||||
}
|
||||
|
||||
void etcd_state_client_http_t::etcd_call_selected(const std::string & api, json11::Json payload, int timeout,
|
||||
int retries, int interval, std::function<void(std::string, json11::Json)> callback)
|
||||
{
|
||||
const auto & url = selected_etcd_url;
|
||||
std::string req = payload.dump();
|
||||
req = "POST "+etcd_api_path+api+" HTTP/1.1\r\n"
|
||||
"Host: "+etcd_address+"\r\n"
|
||||
req = "POST "+url.path+api+" HTTP/1.1\r\n"
|
||||
"Host: "+url.hostname+"\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Content-Length: "+std::to_string(req.size())+"\r\n"
|
||||
"Connection: keep-alive\r\n"
|
||||
@@ -96,15 +103,15 @@ void etcd_state_client_http_t::etcd_call(std::string api, json11::Json payload,
|
||||
"\r\n"+req;
|
||||
retries--;
|
||||
auto cb = [this, api, payload, timeout, retries, interval, callback,
|
||||
cur_addr = selected_etcd_address](const http_response_t *response)
|
||||
cur_addr = url.addr](http_message_t *response)
|
||||
{
|
||||
std::string err;
|
||||
json11::Json data;
|
||||
response->parse_json_response(err, data);
|
||||
if (err != "")
|
||||
{
|
||||
if (cur_addr == selected_etcd_address)
|
||||
selected_etcd_address = "";
|
||||
if (cur_addr == selected_etcd_url.addr)
|
||||
selected_etcd_url = (http_url_t){};
|
||||
if (retries > 0)
|
||||
{
|
||||
if (this->log_level > 0)
|
||||
@@ -132,10 +139,8 @@ void etcd_state_client_http_t::etcd_call(std::string api, json11::Json payload,
|
||||
callback(err, data);
|
||||
};
|
||||
if (!keepalive_client)
|
||||
{
|
||||
keepalive_client = http_init(tfd);
|
||||
}
|
||||
http_request(keepalive_client, etcd_address, req, { .timeout = timeout, .keepalive = true }, cb);
|
||||
keepalive_client = http_init(get_http_ctx());
|
||||
http_request(keepalive_client, url.addr, req, { .timeout = timeout, .keepalive = true, .ssl = url.ssl }, cb);
|
||||
}
|
||||
|
||||
void etcd_state_client_http_t::parse_config(const json11::Json & config)
|
||||
@@ -149,66 +154,130 @@ void etcd_state_client_http_t::parse_config(const json11::Json & config)
|
||||
}
|
||||
}
|
||||
|
||||
void etcd_state_client_http_t::pick_next_etcd()
|
||||
{
|
||||
if (selected_etcd_address != "")
|
||||
return;
|
||||
if (addresses_to_try.size() == 0)
|
||||
{
|
||||
// Prefer local etcd, if any
|
||||
for (int i = 0; i < etcd_local.size(); i++)
|
||||
addresses_to_try.push_back(etcd_local[i]);
|
||||
std::vector<int> ns;
|
||||
for (int i = 0; i < etcd_addresses.size(); i++)
|
||||
ns.push_back(i);
|
||||
if (!rand_initialized)
|
||||
{
|
||||
timespec tv;
|
||||
clock_gettime(CLOCK_REALTIME, &tv);
|
||||
srand48(tv.tv_sec*1000000000 + tv.tv_nsec);
|
||||
rand_initialized = true;
|
||||
}
|
||||
while (ns.size())
|
||||
{
|
||||
int i = lrand48() % ns.size();
|
||||
addresses_to_try.push_back(etcd_addresses[ns[i]]);
|
||||
ns.erase(ns.begin()+i, ns.begin()+i+1);
|
||||
}
|
||||
}
|
||||
selected_etcd_address = addresses_to_try[0];
|
||||
addresses_to_try.erase(addresses_to_try.begin(), addresses_to_try.begin()+1);
|
||||
}
|
||||
|
||||
void etcd_state_client_http_t::start_etcd_watcher()
|
||||
void etcd_state_client_http_t::pick_next_etcd(std::function<void()> cb)
|
||||
{
|
||||
if (!etcd_addresses.size() && !etcd_local.size())
|
||||
{
|
||||
fprintf(stderr, "etcd_address is missing in Vitastor configuration\n");
|
||||
exit(1);
|
||||
}
|
||||
pick_next_etcd();
|
||||
std::string etcd_address = selected_etcd_address;
|
||||
std::string etcd_api_path;
|
||||
int pos = etcd_address.find('/');
|
||||
if (pos >= 0)
|
||||
if (selected_etcd_url.addr != "")
|
||||
{
|
||||
etcd_api_path = etcd_address.substr(pos);
|
||||
etcd_address = etcd_address.substr(0, pos);
|
||||
cb();
|
||||
return;
|
||||
}
|
||||
if (etcd_urls_to_try.size() != 0)
|
||||
{
|
||||
selected_etcd_url = std::move(etcd_urls_to_try[0]);
|
||||
etcd_urls_to_try.erase(etcd_urls_to_try.begin());
|
||||
cb();
|
||||
return;
|
||||
}
|
||||
on_resolve_queue.push_back(std::move(cb));
|
||||
if (on_resolve_queue.size() > 1)
|
||||
{
|
||||
// Already resolving
|
||||
return;
|
||||
}
|
||||
assert(!resolve_count);
|
||||
local_to_try = 0;
|
||||
for (auto & url: etcd_local_addr_urls)
|
||||
{
|
||||
// Prefer local IPs, if any
|
||||
etcd_urls_to_try.push_back(url);
|
||||
local_to_try++;
|
||||
}
|
||||
for (auto & url: etcd_nonlocal_addr_urls)
|
||||
{
|
||||
etcd_urls_to_try.push_back(url);
|
||||
}
|
||||
resolve_count++;
|
||||
for (auto & url: etcd_name_urls)
|
||||
{
|
||||
resolve_count++;
|
||||
http_resolve(get_http_ctx(), url.ssl, url.addr, [this, url](const std::string & error, const std::vector<std::string>& addresses)
|
||||
{
|
||||
if (error != "")
|
||||
fprintf(stderr, "Error resolving %s: %s\n", url.addr.c_str(), error.c_str());
|
||||
for (auto & addr: addresses)
|
||||
{
|
||||
auto url_copy = url;
|
||||
url_copy.addr = addr;
|
||||
if (local_ips.find(addr) != local_ips.end())
|
||||
{
|
||||
etcd_urls_to_try.insert(etcd_urls_to_try.begin(), std::move(url_copy));
|
||||
local_to_try++;
|
||||
}
|
||||
else
|
||||
etcd_urls_to_try.push_back(std::move(url_copy));
|
||||
}
|
||||
resolve_count--;
|
||||
if (!resolve_count)
|
||||
pick_next_etcd_on_resolve();
|
||||
});
|
||||
}
|
||||
resolve_count--;
|
||||
if (!resolve_count)
|
||||
{
|
||||
pick_next_etcd_on_resolve();
|
||||
}
|
||||
}
|
||||
|
||||
void etcd_state_client_http_t::pick_next_etcd_on_resolve()
|
||||
{
|
||||
if (!etcd_urls_to_try.size())
|
||||
{
|
||||
fprintf(stderr, "None of etcd_address could be resolved\n");
|
||||
exit(1);
|
||||
}
|
||||
if (!rand_initialized)
|
||||
{
|
||||
timespec tv;
|
||||
clock_gettime(CLOCK_REALTIME, &tv);
|
||||
srand48(tv.tv_sec*1000000000 + tv.tv_nsec);
|
||||
rand_initialized = true;
|
||||
}
|
||||
// Shuffle addresses
|
||||
for (size_t i = etcd_urls_to_try.size()-1; i > local_to_try; i--)
|
||||
{
|
||||
size_t j = local_to_try + lrand48() % (i - local_to_try);
|
||||
if (j != i)
|
||||
std::swap(etcd_urls_to_try[i], etcd_urls_to_try[j]);
|
||||
}
|
||||
selected_etcd_url = std::move(etcd_urls_to_try[0]);
|
||||
etcd_urls_to_try.erase(etcd_urls_to_try.begin());
|
||||
auto cbs = std::move(on_resolve_queue);
|
||||
for (auto cb: cbs)
|
||||
{
|
||||
cb();
|
||||
}
|
||||
}
|
||||
|
||||
void etcd_state_client_http_t::start_etcd_watcher()
|
||||
{
|
||||
pick_next_etcd([this]()
|
||||
{
|
||||
start_etcd_watcher_selected();
|
||||
});
|
||||
}
|
||||
|
||||
void etcd_state_client_http_t::start_etcd_watcher_selected()
|
||||
{
|
||||
const auto & url = selected_etcd_url;
|
||||
etcd_watches_initialised = 0;
|
||||
ws_alive = 1;
|
||||
if (etcd_watch_ws)
|
||||
{
|
||||
http_close(etcd_watch_ws);
|
||||
etcd_watch_ws = NULL;
|
||||
}
|
||||
if (this->log_level > 1)
|
||||
{
|
||||
fprintf(stderr, "Trying to connect to etcd websocket at %s, watch from revision %ju/%ju/%ju\n", etcd_address.c_str(),
|
||||
fprintf(stderr, "Trying to connect to etcd websocket at %s%s%s (hostname %s), watch from revision %ju/%ju/%ju\n",
|
||||
url.ssl ? "https://" : "http://", url.addr.c_str(), url.path.c_str(), url.hostname.c_str(),
|
||||
etcd_watch_revision_config, etcd_watch_revision_osd, etcd_watch_revision_pg);
|
||||
}
|
||||
etcd_watch_ws = open_websocket(tfd, etcd_address, etcd_api_path+"/watch", etcd_slow_timeout,
|
||||
[this, cur_addr = selected_etcd_address](const http_response_t *msg)
|
||||
if (!etcd_watch_ws)
|
||||
etcd_watch_ws = http_init(get_http_ctx());
|
||||
else
|
||||
http_close(etcd_watch_ws);
|
||||
open_websocket(etcd_watch_ws, url.addr, url.hostname, url.path+"/watch", { .timeout = etcd_slow_timeout, .ssl = url.ssl },
|
||||
[this, cur_addr = url.addr](http_message_t *msg)
|
||||
{
|
||||
if (msg->body.length())
|
||||
{
|
||||
@@ -251,7 +320,6 @@ void etcd_state_client_http_t::start_etcd_watcher()
|
||||
fprintf(stderr, "Revisions before %ju were compacted by etcd, reloading state\n",
|
||||
data["result"]["compact_revision"].uint64_value());
|
||||
http_close(etcd_watch_ws);
|
||||
etcd_watch_ws = NULL;
|
||||
etcd_watch_revision_config = etcd_watch_revision_osd = etcd_watch_revision_pg = 0;
|
||||
on_reload_hook();
|
||||
}
|
||||
@@ -296,7 +364,7 @@ void etcd_state_client_http_t::start_etcd_watcher()
|
||||
etcd_watch_revision_pg = watch_rev;
|
||||
else if (watch_id == ETCD_OSD_STATE_WATCH_ID)
|
||||
etcd_watch_revision_osd = watch_rev;
|
||||
addresses_to_try.clear();
|
||||
etcd_urls_to_try.clear();
|
||||
}
|
||||
// First gather all changes into a hash to remove multiple overwrites
|
||||
std::map<std::string, etcd_kv_t> changes;
|
||||
@@ -326,13 +394,8 @@ void etcd_state_client_http_t::start_etcd_watcher()
|
||||
if (msg->eof)
|
||||
{
|
||||
fprintf(stderr, "Disconnected from etcd %s\n", cur_addr.c_str());
|
||||
if (cur_addr == selected_etcd_address)
|
||||
selected_etcd_address = "";
|
||||
if (etcd_watch_ws)
|
||||
{
|
||||
http_close(etcd_watch_ws);
|
||||
etcd_watch_ws = NULL;
|
||||
}
|
||||
if (cur_addr == selected_etcd_url.addr)
|
||||
selected_etcd_url = (http_url_t){};
|
||||
if (etcd_watches_initialised == 0)
|
||||
{
|
||||
// Connection not established, retry in <etcd_quick_timeout>
|
||||
@@ -407,12 +470,7 @@ void etcd_state_client_http_t::start_ws_keepalive()
|
||||
{
|
||||
if (this->log_level > 0)
|
||||
{
|
||||
fprintf(stderr, "Websocket ping failed, disconnecting from etcd %s\n", selected_etcd_address.c_str());
|
||||
}
|
||||
if (etcd_watch_ws)
|
||||
{
|
||||
http_close(etcd_watch_ws);
|
||||
etcd_watch_ws = NULL;
|
||||
fprintf(stderr, "Websocket ping failed, disconnecting from etcd %s\n", selected_etcd_url.addr.c_str());
|
||||
}
|
||||
start_etcd_watcher();
|
||||
}
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
|
||||
#include "etcd_state_client.h"
|
||||
|
||||
struct http_context_t;
|
||||
|
||||
struct __attribute__((visibility("default"))) etcd_state_client_http_t: public etcd_state_client_t
|
||||
{
|
||||
protected:
|
||||
timerfd_manager_t *tfd = NULL;
|
||||
std::string selected_etcd_address;
|
||||
std::vector<std::string> addresses_to_try;
|
||||
int ws_keepalive_timer = -1;
|
||||
int ws_alive = 0;
|
||||
bool rand_initialized = false;
|
||||
@@ -18,17 +18,31 @@ protected:
|
||||
timespec etcd_last_reload = {};
|
||||
int load_pgs_timer_id = -1;
|
||||
http_co_t *keepalive_client = NULL;
|
||||
http_co_t *etcd_watch_ws = NULL;
|
||||
http_context_t *http_ctx = NULL;
|
||||
size_t local_to_try = 0;
|
||||
std::vector<http_url_t> etcd_urls_to_try;
|
||||
http_url_t selected_etcd_url;
|
||||
size_t resolve_count = 0;
|
||||
std::vector<std::function<void()>> on_resolve_queue;
|
||||
|
||||
void pick_next_etcd();
|
||||
void pick_next_etcd(std::function<void()> cb);
|
||||
void pick_next_etcd_on_resolve();
|
||||
void start_etcd_watcher();
|
||||
void start_etcd_watcher_selected();
|
||||
void stop_ws_keepalive();
|
||||
void start_ws_keepalive();
|
||||
http_context_t *get_http_ctx();
|
||||
public:
|
||||
http_co_t *etcd_watch_ws = NULL;
|
||||
etcd_state_client_http_t(timerfd_manager_t *tfd);
|
||||
void etcd_call_oneshot(std::string etcd_address, std::string api, json11::Json payload, int timeout, std::function<void(std::string, json11::Json)> callback) override;
|
||||
void etcd_call(std::string api, json11::Json payload, int timeout, int retries, int interval, std::function<void(std::string, json11::Json)> callback) override;
|
||||
void etcd_call_oneshot(const std::string & etcd_url, const std::string & api, json11::Json payload,
|
||||
int timeout, std::function<void(std::string, json11::Json)> callback) override;
|
||||
void etcd_call(const std::string & api, json11::Json payload, int timeout,
|
||||
int retries, int interval, std::function<void(std::string, json11::Json)> callback) override;
|
||||
void etcd_call_selected(const std::string & api, json11::Json payload, int timeout,
|
||||
int retries, int interval, std::function<void(std::string, json11::Json)> callback);
|
||||
void etcd_add_watch(json11::Json watch) override;
|
||||
std::string get_username() override;
|
||||
void load_global_config() override;
|
||||
void load_pgs() override;
|
||||
void parse_config(const json11::Json & config) override;
|
||||
|
||||
@@ -16,7 +16,12 @@ void etcd_state_client_mock_t::etcd_add_watch(json11::Json watch)
|
||||
{
|
||||
}
|
||||
|
||||
void etcd_state_client_mock_t::etcd_call_oneshot(std::string etcd_address, std::string api, json11::Json payload,
|
||||
std::string etcd_state_client_mock_t::get_username()
|
||||
{
|
||||
return username;
|
||||
}
|
||||
|
||||
void etcd_state_client_mock_t::etcd_call_oneshot(const std::string & etcd_address, const std::string & api, json11::Json payload,
|
||||
int timeout, std::function<void(std::string, json11::Json)> callback)
|
||||
{
|
||||
}
|
||||
@@ -43,7 +48,7 @@ void etcd_state_client_mock_t::set(const std::string& key, json11::Json data, ui
|
||||
this->data[key] = (etcd_mock_key_data_t){ .value = data.dump(), .mod_revision = mod_revision, .lease_id = lease_id };
|
||||
}
|
||||
|
||||
void etcd_state_client_mock_t::etcd_call(std::string api, json11::Json payload, int timeout,
|
||||
void etcd_state_client_mock_t::etcd_call(const std::string & api, json11::Json payload, int timeout,
|
||||
int retries, int interval, std::function<void(std::string, json11::Json)> callback)
|
||||
{
|
||||
if (paused)
|
||||
|
||||
@@ -30,13 +30,15 @@ struct etcd_state_client_mock_t: public etcd_state_client_t
|
||||
public:
|
||||
std::map<uint64_t, uint64_t> leases;
|
||||
std::map<std::string, etcd_mock_key_data_t> data;
|
||||
std::string username;
|
||||
etcd_state_client_mock_t();
|
||||
void set(const std::string& key, json11::Json data, uint64_t mod_revision = 0, uint64_t lease_id = 0);
|
||||
void pause();
|
||||
void resume();
|
||||
void etcd_call_oneshot(std::string etcd_address, std::string api, json11::Json payload, int timeout, std::function<void(std::string, json11::Json)> callback) override;
|
||||
void etcd_call(std::string api, json11::Json payload, int timeout, int retries, int interval, std::function<void(std::string, json11::Json)> callback) override;
|
||||
void etcd_call_oneshot(const std::string & etcd_address, const std::string & api, json11::Json payload, int timeout, std::function<void(std::string, json11::Json)> callback) override;
|
||||
void etcd_call(const std::string & api, json11::Json payload, int timeout, int retries, int interval, std::function<void(std::string, json11::Json)> callback) override;
|
||||
void etcd_add_watch(json11::Json watch) override;
|
||||
std::string get_username() override;
|
||||
void load_global_config() override;
|
||||
void load_pgs() override;
|
||||
};
|
||||
|
||||
+629
-87
File diff suppressed because it is too large
Load Diff
@@ -17,14 +17,19 @@
|
||||
|
||||
class timerfd_manager_t;
|
||||
|
||||
#pragma GCC visibility push(default)
|
||||
|
||||
struct http_options_t
|
||||
{
|
||||
int timeout;
|
||||
bool want_streaming;
|
||||
bool keepalive;
|
||||
bool ssl;
|
||||
};
|
||||
|
||||
struct http_response_t
|
||||
struct http_context_t;
|
||||
|
||||
struct http_message_t
|
||||
{
|
||||
std::string error;
|
||||
|
||||
@@ -41,10 +46,26 @@ struct http_response_t
|
||||
// Opened websocket or keepalive HTTP connection
|
||||
struct http_co_t;
|
||||
|
||||
http_co_t* http_init(timerfd_manager_t *tfd);
|
||||
http_co_t* open_websocket(timerfd_manager_t *tfd, const std::string & host, const std::string & path,
|
||||
int timeout, std::function<void(const http_response_t *msg)> on_message);
|
||||
http_context_t* http_context_init(timerfd_manager_t *tfd, const std::string & ssl_cert, const std::string & ssl_key,
|
||||
const std::string & ssl_ca, bool verify_peer, std::string & error);
|
||||
std::string http_context_get_ssl_cn(http_context_t *ctx);
|
||||
void http_resolve(http_context_t *ctx, bool ssl, std::string host,
|
||||
std::function<void(const std::string & error, const std::vector<std::string> & addrs)> cb);
|
||||
void http_context_destroy(http_context_t *ctx);
|
||||
http_co_t* http_init(http_context_t *ctx = NULL);
|
||||
void open_websocket(http_co_t *handler, const std::string & addr, const std::string & hostname, const std::string & path,
|
||||
const http_options_t & options, std::function<void(http_message_t *msg)> on_message);
|
||||
void http_request(http_co_t *handler, const std::string & host, const std::string & request,
|
||||
const http_options_t & options, std::function<void(const http_response_t *response)> response_callback);
|
||||
const http_options_t & options, std::function<void(http_message_t *response)> response_callback);
|
||||
void http_get(http_co_t *handler, const std::string & url, const std::string & headers,
|
||||
const http_options_t & options, std::function<void(http_message_t *response)> response_callback);
|
||||
void http_json_post(http_co_t *handler, const std::string & url, json11::Json body, const std::string & headers,
|
||||
const http_options_t & options, std::function<void(http_message_t *response)> response_callback);
|
||||
void http_post_message(http_co_t *handler, uint8_t type, const std::string & msg);
|
||||
void http_serve(http_co_t *handler, int peer_fd, const http_options_t & options,
|
||||
std::function<void(http_message_t *msg)> request_callback);
|
||||
void http_reply(http_co_t *handler, const std::string & reply);
|
||||
void http_close(http_co_t *co);
|
||||
void http_destroy(http_co_t *co);
|
||||
|
||||
#pragma GCC visibility pop
|
||||
|
||||
+111
-24
@@ -10,6 +10,7 @@
|
||||
#include <stdexcept>
|
||||
|
||||
#include "addr_util.h"
|
||||
#include "str_util.h"
|
||||
#include "messenger.h"
|
||||
#ifdef WITH_RDMA
|
||||
#include "msgr_rdma.h"
|
||||
@@ -17,6 +18,7 @@
|
||||
|
||||
void osd_messenger_t::init()
|
||||
{
|
||||
init_tls();
|
||||
#ifdef WITH_RDMACM
|
||||
if (use_rdmacm)
|
||||
{
|
||||
@@ -184,10 +186,67 @@ osd_messenger_t::~osd_messenger_t()
|
||||
rdmacm_evch = NULL;
|
||||
}
|
||||
#endif
|
||||
for (auto encrypt_ctx: encrypt_xts_pool)
|
||||
{
|
||||
destroy_aes_xts_encrypt(encrypt_ctx);
|
||||
}
|
||||
for (auto decrypt_ctx: decrypt_xts_pool)
|
||||
{
|
||||
destroy_aes_xts_decrypt(decrypt_ctx);
|
||||
}
|
||||
destroy_tls();
|
||||
}
|
||||
|
||||
void osd_messenger_t::parse_config(const json11::Json & config)
|
||||
static int parse_proto_checksums(const json11::Json & val, int default_value)
|
||||
{
|
||||
if (val.is_string())
|
||||
{
|
||||
const auto & str = val.string_value();
|
||||
if (str == "full")
|
||||
return MSGR_CSUM_FULL;
|
||||
else if (str == "payload")
|
||||
return MSGR_CSUM_PAYLOAD;
|
||||
else if (str == "gcm")
|
||||
return MSGR_CSUM_GCM;
|
||||
else if (str == "none")
|
||||
return 0;
|
||||
else if (str == "")
|
||||
return default_value;
|
||||
}
|
||||
else if (val.is_null())
|
||||
return default_value;
|
||||
fprintf(stderr, "proto_checksums should be \"full\", \"payload\", \"gcm\", \"none\""
|
||||
", \"\" or null (default), but it is: %s\n", val.dump().c_str());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void osd_messenger_t::parse_config(const json11::Json & config, bool init)
|
||||
{
|
||||
this->max_cipher_pool_size = config["max_cipher_pool_size"].uint64_value();
|
||||
if (!this->max_cipher_pool_size)
|
||||
this->max_cipher_pool_size = 256;
|
||||
this->receive_buffer_size = (uint32_t)config["tcp_header_buffer_size"].uint64_value();
|
||||
if (!this->receive_buffer_size || this->receive_buffer_size > 1024*1024*1024)
|
||||
this->receive_buffer_size = 65536;
|
||||
this->min_zerocopy_send_size = config["min_zerocopy_send_size"].is_null()
|
||||
? DEFAULT_MIN_ZEROCOPY_SEND_SIZE
|
||||
: (int)config["min_zerocopy_send_size"].int64_value();
|
||||
this->peer_connect_interval = config["peer_connect_interval"].uint64_value();
|
||||
if (!this->peer_connect_interval)
|
||||
this->peer_connect_interval = 5;
|
||||
this->peer_connect_timeout = config["peer_connect_timeout"].uint64_value();
|
||||
if (!this->peer_connect_timeout)
|
||||
this->peer_connect_timeout = 5;
|
||||
this->osd_idle_timeout = config["osd_idle_timeout"].uint64_value();
|
||||
if (!this->osd_idle_timeout)
|
||||
this->osd_idle_timeout = 5;
|
||||
this->osd_ping_timeout = config["osd_ping_timeout"].uint64_value();
|
||||
if (!this->osd_ping_timeout)
|
||||
this->osd_ping_timeout = 5;
|
||||
this->log_level = config["log_level"].uint64_value();
|
||||
// All other parameters are only set on init
|
||||
if (!init)
|
||||
return;
|
||||
#ifdef WITH_RDMA
|
||||
if (!config["use_rdma"].is_null())
|
||||
{
|
||||
@@ -218,31 +277,27 @@ void osd_messenger_t::parse_config(const json11::Json & config)
|
||||
if (!this->rdma_max_msg || this->rdma_max_msg > 128*1024*1024)
|
||||
this->rdma_max_msg = 129*1024;
|
||||
#endif
|
||||
if (!osd_num)
|
||||
{
|
||||
tls_cert = config["cert"].string_value();
|
||||
tls_key = config["pkey"].string_value();
|
||||
osd_tls_ca = config["osd_ca"].string_value();
|
||||
}
|
||||
else
|
||||
{
|
||||
tls_cert = config["osd_cert"].string_value();
|
||||
tls_key = config["osd_pkey"].string_value();
|
||||
osd_tls_ca = config["osd_ca"].string_value();
|
||||
client_tls_ca = config["client_ca"].string_value();
|
||||
}
|
||||
this->use_proto_checksums = parse_proto_checksums(config["proto_checksums"], MSGR_CSUM_PAYLOAD);
|
||||
this->force_proto_checksums = parse_proto_checksums(config["force_proto_checksums"], tls_cert != "" ? MSGR_CSUM_PAYLOAD : 0);
|
||||
if (!osd_num)
|
||||
this->iothread_count = (uint32_t)config["client_iothread_count"].uint64_value();
|
||||
else
|
||||
this->iothread_count = (uint32_t)config["osd_iothread_count"].uint64_value();
|
||||
this->receive_buffer_size = (uint32_t)config["tcp_header_buffer_size"].uint64_value();
|
||||
if (!this->receive_buffer_size || this->receive_buffer_size > 1024*1024*1024)
|
||||
this->receive_buffer_size = 65536;
|
||||
this->use_sync_send_recv = config["use_sync_send_recv"].bool_value() ||
|
||||
config["use_sync_send_recv"].uint64_value();
|
||||
this->min_zerocopy_send_size = config["min_zerocopy_send_size"].is_null()
|
||||
? DEFAULT_MIN_ZEROCOPY_SEND_SIZE
|
||||
: (int)config["min_zerocopy_send_size"].int64_value();
|
||||
this->peer_connect_interval = config["peer_connect_interval"].uint64_value();
|
||||
if (!this->peer_connect_interval)
|
||||
this->peer_connect_interval = 5;
|
||||
this->peer_connect_timeout = config["peer_connect_timeout"].uint64_value();
|
||||
if (!this->peer_connect_timeout)
|
||||
this->peer_connect_timeout = 5;
|
||||
this->osd_idle_timeout = config["osd_idle_timeout"].uint64_value();
|
||||
if (!this->osd_idle_timeout)
|
||||
this->osd_idle_timeout = 5;
|
||||
this->osd_ping_timeout = config["osd_ping_timeout"].uint64_value();
|
||||
if (!this->osd_ping_timeout)
|
||||
this->osd_ping_timeout = 5;
|
||||
this->log_level = config["log_level"].uint64_value();
|
||||
config["use_sync_send_recv"].uint64_value() || !ringloop;
|
||||
// OSD public & cluster networks
|
||||
this->osd_networks.clear();
|
||||
if (config["osd_network"].is_string())
|
||||
@@ -399,7 +454,7 @@ void osd_messenger_t::try_connect_peer_tcp(osd_num_t peer_osd, const char *peer_
|
||||
cl->peer_state = PEER_CONNECTING;
|
||||
cl->connect_timeout_id = -1;
|
||||
cl->osd_num = peer_osd;
|
||||
cl->in_buf = malloc_or_die(receive_buffer_size);
|
||||
cl->in_buf = (uint8_t*)malloc_or_die(receive_buffer_size);
|
||||
clients[client_id] = cl;
|
||||
clients_by_fd[peer_fd] = cl;
|
||||
tfd->set_fd_handler(peer_fd, true, [this](int peer_fd, int epoll_events)
|
||||
@@ -448,6 +503,7 @@ void osd_messenger_t::handle_connect_epoll(int peer_fd)
|
||||
handle_peer_epoll(peer_fd, epoll_events);
|
||||
});
|
||||
// Check OSD number
|
||||
init_tls_client(cl);
|
||||
check_peer_config(cl);
|
||||
}
|
||||
|
||||
@@ -537,7 +593,12 @@ void osd_messenger_t::check_peer_config(osd_client_t *cl)
|
||||
// Inform that we're OSD <osd_num>
|
||||
payload["osd_num"] = osd_num;
|
||||
}
|
||||
payload["features"] = json11::Json::object{ { "check_sequencing", true } };
|
||||
auto features = json11::Json::object{ { "check_sequencing", true } };
|
||||
if (use_proto_checksums)
|
||||
{
|
||||
features["proto_checksums"] = use_proto_checksums;
|
||||
}
|
||||
payload["features"] = features;
|
||||
#ifdef WITH_RDMA
|
||||
if (!use_rdmacm && rdma_contexts.size())
|
||||
{
|
||||
@@ -577,6 +638,11 @@ void osd_messenger_t::check_peer_config(osd_client_t *cl)
|
||||
err = true;
|
||||
fprintf(stderr, "Failed to get config from OSD %ju (retval=%jd), disconnecting peer\n", cl->osd_num, op->reply.hdr.retval);
|
||||
}
|
||||
else if (cl->gcm_enabled && !cl->hs_result.peer_is_osd)
|
||||
{
|
||||
err = true;
|
||||
fprintf(stderr, "Client %ju is not authenticated as an OSD, disconnecting peer\n", cl->client_id);
|
||||
}
|
||||
else
|
||||
{
|
||||
config = json11::Json::parse(std::string((char*)op->buf), json_err);
|
||||
@@ -604,6 +670,21 @@ void osd_messenger_t::check_peer_config(osd_client_t *cl)
|
||||
err = !check_config_hook(cl, config);
|
||||
}
|
||||
}
|
||||
if (!err && use_proto_checksums)
|
||||
{
|
||||
auto peer_csums = config["features"]["proto_checksums"].uint64_value();
|
||||
if (peer_csums == MSGR_CSUM_GCM && use_proto_checksums == MSGR_CSUM_GCM && cl->gcm_enabled)
|
||||
cl->proto_csum_status = MSGR_CSUM_GCM;
|
||||
else if (peer_csums == MSGR_CSUM_FULL && use_proto_checksums == MSGR_CSUM_FULL)
|
||||
cl->proto_csum_status = MSGR_CSUM_FULL;
|
||||
else if (peer_csums && use_proto_checksums)
|
||||
cl->proto_csum_status = MSGR_CSUM_PAYLOAD;
|
||||
if (cl->proto_csum_status < force_proto_checksums)
|
||||
{
|
||||
fprintf(stderr, "Error: OSD %ju use_proto_checksums security level is lower than force_proto_checksums\n", cl->osd_num);
|
||||
err = true;
|
||||
}
|
||||
}
|
||||
if (err)
|
||||
{
|
||||
osd_num_t peer_osd = cl->osd_num;
|
||||
@@ -675,7 +756,8 @@ void osd_messenger_t::accept_connections(int listen_fd)
|
||||
cl->peer_port = ntohs(((sockaddr_in*)&addr)->sin_port);
|
||||
cl->peer_fd = peer_fd;
|
||||
cl->peer_state = PEER_CONNECTED;
|
||||
cl->in_buf = malloc_or_die(receive_buffer_size);
|
||||
cl->in_buf = (uint8_t*)malloc_or_die(receive_buffer_size);
|
||||
init_tls_client(cl);
|
||||
// Add FD to epoll
|
||||
tfd->set_fd_handler(peer_fd, false, [this](int peer_fd, int epoll_events)
|
||||
{
|
||||
@@ -718,6 +800,11 @@ bool osd_messenger_t::is_use_rdmacm()
|
||||
}
|
||||
#endif
|
||||
|
||||
bool osd_messenger_t::is_encryption_enabled()
|
||||
{
|
||||
return tls_cert != "" || tls_key != "" || osd_tls_ca != "";
|
||||
}
|
||||
|
||||
json11::Json::object osd_messenger_t::read_config(const json11::Json & config)
|
||||
{
|
||||
json11::Json::object file_config;
|
||||
|
||||
+122
-15
@@ -12,12 +12,20 @@
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
|
||||
#include <openssl/types.h>
|
||||
|
||||
#ifdef WITH_ISAL_CRYPTO
|
||||
#include <isa-l_crypto/aes_gcm.h>
|
||||
#endif
|
||||
|
||||
#include "../util/xxh_x86dispatch.h"
|
||||
#include "../util/robin_hood.h"
|
||||
#include "malloc_or_die.h"
|
||||
#include "json11/json11.hpp"
|
||||
#include "msgr_op.h"
|
||||
#include "timerfd_manager.h"
|
||||
#include "addr_util.h"
|
||||
#include "msgr_handshake.h"
|
||||
#include <ringloop.h>
|
||||
|
||||
#define CL_READ_HDR 1
|
||||
@@ -31,12 +39,17 @@
|
||||
#define PEER_RDMA 4
|
||||
#define PEER_STOPPED 5
|
||||
|
||||
#define MSGR_CSUM_PAYLOAD 1
|
||||
#define MSGR_CSUM_FULL 2
|
||||
#define MSGR_CSUM_GCM 4
|
||||
#define MSGR_CSUM_NEG 8
|
||||
|
||||
#define VITASTOR_CONFIG_PATH "/etc/vitastor/vitastor.conf"
|
||||
|
||||
#define DEFAULT_MIN_ZEROCOPY_SEND_SIZE 32*1024
|
||||
|
||||
#define MSGR_SENDP_HDR 1
|
||||
#define MSGR_SENDP_FREE 2
|
||||
#define AES_256_GCM_KEY_SIZE 32
|
||||
#define AES_256_GCM_IV_SIZE 12
|
||||
|
||||
#define MAX_SIMPLE_PAYLOAD_SIZE 1048576
|
||||
|
||||
@@ -51,6 +64,13 @@ struct msgr_rdma_connection_t;
|
||||
struct msgr_rdma_context_t;
|
||||
#endif
|
||||
|
||||
struct op_aes_xts_encrypt_t;
|
||||
struct op_aes_xts_decrypt_t;
|
||||
void destroy_aes_xts_encrypt(op_aes_xts_encrypt_t *encrypt_ctx);
|
||||
void destroy_aes_xts_decrypt(op_aes_xts_decrypt_t *decrypt_ctx);
|
||||
|
||||
struct user_info_t;
|
||||
|
||||
struct osd_client_t
|
||||
{
|
||||
uint64_t client_id = 0;
|
||||
@@ -67,23 +87,49 @@ struct osd_client_t
|
||||
osd_num_t in_osd_num = 0;
|
||||
bool is_incoming = false;
|
||||
|
||||
void *in_buf = NULL;
|
||||
uint8_t *in_buf = NULL;
|
||||
|
||||
#ifdef WITH_RDMA
|
||||
msgr_rdma_connection_t *rdma_conn = NULL;
|
||||
#endif
|
||||
|
||||
bool gcm_enabled = false;
|
||||
msgr_handshake_i *hs = NULL;
|
||||
msgr_handshake_result_t hs_result;
|
||||
std::shared_ptr<user_info_t> user_info;
|
||||
std::vector<uint8_t> my_key, peer_key;
|
||||
uint64_t my_iv_ctr = 0, peer_iv_ctr = 0;
|
||||
#ifdef WITH_ISAL_CRYPTO
|
||||
isal_gcm_key_data my_key_isal, peer_key_isal;
|
||||
isal_gcm_context_data *enc_ctx = NULL;
|
||||
isal_gcm_context_data *dec_ctx = NULL;
|
||||
#else
|
||||
EVP_CIPHER_CTX *enc_ctx = NULL;
|
||||
EVP_CIPHER_CTX *dec_ctx = NULL;
|
||||
#endif
|
||||
uint8_t enc_tag[16];
|
||||
size_t enc_tag_size = 0;
|
||||
uint8_t dec_tag[16];
|
||||
size_t dec_tag_size = 0;
|
||||
|
||||
// Read state
|
||||
bool io_error = false;
|
||||
int read_ready = 0;
|
||||
osd_op_t *read_op = NULL;
|
||||
size_t read_op_size = 0;
|
||||
size_t read_op_pos = 0;
|
||||
iovec read_iov = { 0 };
|
||||
msghdr read_msg = { 0 };
|
||||
int read_remaining = 0;
|
||||
int read_state = 0;
|
||||
osd_op_buf_list_t recv_list;
|
||||
std::vector<iovec> recv_list;
|
||||
std::vector<int> recv_flags;
|
||||
uint64_t read_op_id = 1;
|
||||
bool check_sequencing = false;
|
||||
bool enable_pg_locks = false;
|
||||
op_aes_xts_decrypt_t *xts_dec_ctx = NULL;
|
||||
size_t read_op_inline_decrypt_pos = 0;
|
||||
size_t read_op_inline_decrypt_in = 0;
|
||||
int proto_csum_status = 0;
|
||||
XXH3_state_t* read_csum_state = NULL;
|
||||
|
||||
// Incoming operations
|
||||
std::vector<osd_op_t*> received_ops;
|
||||
@@ -96,11 +142,17 @@ struct osd_client_t
|
||||
std::set<pool_pg_num_t> dirty_pgs;
|
||||
|
||||
// Write state
|
||||
std::deque<osd_op_t *> write_ops;
|
||||
osd_op_t *write_op = NULL;
|
||||
size_t write_op_pos = 0;
|
||||
msghdr write_msg = { 0 };
|
||||
int write_state = 0;
|
||||
std::vector<iovec> send_list, next_send_list;
|
||||
std::vector<msgr_sendp_t> outbox, next_outbox;
|
||||
std::vector<iovec> send_list;
|
||||
size_t send_list_size = 0;
|
||||
std::deque<osd_op_t*> send_free_ops;
|
||||
std::vector<osd_op_t*> zc_free_list;
|
||||
op_aes_xts_encrypt_t *xts_enc_ctx = NULL;
|
||||
XXH3_state_t* write_csum_state = NULL;
|
||||
|
||||
~osd_client_t();
|
||||
void cancel_ops();
|
||||
@@ -142,9 +194,19 @@ struct osd_messenger_t;
|
||||
struct rdmacm_connecting_t;
|
||||
#endif
|
||||
|
||||
class msgr_op_reader_t;
|
||||
class msgr_op_writer_t;
|
||||
|
||||
struct __attribute__((visibility("default"))) osd_messenger_t
|
||||
{
|
||||
protected:
|
||||
friend class copy_op_reader_t;
|
||||
friend class gcm_op_reader_t;
|
||||
friend class get_op_reader_t;
|
||||
friend class copy_op_writer_t;
|
||||
friend class gcm_op_writer_t;
|
||||
friend class get_op_writer_t;
|
||||
|
||||
int keepalive_timer_id = -1;
|
||||
|
||||
uint32_t receive_buffer_size = 0;
|
||||
@@ -156,6 +218,12 @@ protected:
|
||||
bool use_sync_send_recv = false;
|
||||
int min_zerocopy_send_size = DEFAULT_MIN_ZEROCOPY_SEND_SIZE;
|
||||
int iothread_count = 0;
|
||||
int max_cipher_pool_size = 256;
|
||||
|
||||
std::string tls_cert;
|
||||
std::string tls_key;
|
||||
std::string osd_tls_ca;
|
||||
std::string client_tls_ca;
|
||||
|
||||
#ifdef WITH_RDMA
|
||||
bool use_rdma = true;
|
||||
@@ -173,12 +241,32 @@ protected:
|
||||
robin_hood::unordered_flat_map<rdma_cm_id*, rdmacm_connecting_t*> rdmacm_connecting;
|
||||
#endif
|
||||
|
||||
bool gcm_enabled = false;
|
||||
msgr_handshake_ctx_i *hs_ctx = NULL;
|
||||
|
||||
void init_tls();
|
||||
void destroy_tls();
|
||||
void init_tls_client(osd_client_t *cl);
|
||||
bool do_tls_handshake(osd_client_t *cl, bool from_recv = false);
|
||||
bool derive_aes_keys(osd_client_t *cl, bool update_my, bool update_peer);
|
||||
|
||||
std::vector<msgr_iothread_t*> iothreads;
|
||||
std::vector<uint64_t> read_ready_clients;
|
||||
std::vector<uint64_t> write_ready_clients;
|
||||
// We don't use ringloop->set_immediate here because we may have no ringloop in client :)
|
||||
std::deque<osd_op_t*> set_immediate_ops;
|
||||
|
||||
std::vector<op_aes_xts_encrypt_t*> encrypt_xts_pool;
|
||||
std::vector<op_aes_xts_decrypt_t*> decrypt_xts_pool;
|
||||
|
||||
#ifdef WITH_ISAL_CRYPTO
|
||||
std::vector<isal_gcm_context_data*> encrypt_gcm_pool;
|
||||
std::vector<isal_gcm_context_data*> decrypt_gcm_pool;
|
||||
#else
|
||||
std::vector<EVP_CIPHER_CTX*> encrypt_gcm_pool;
|
||||
std::vector<EVP_CIPHER_CTX*> decrypt_gcm_pool;
|
||||
#endif
|
||||
|
||||
public:
|
||||
timerfd_manager_t *tfd = NULL;
|
||||
ring_loop_i *ringloop = NULL;
|
||||
@@ -199,12 +287,14 @@ public:
|
||||
std::vector<addr_mask_t> osd_cluster_network_masks;
|
||||
std::vector<std::string> all_osd_networks;
|
||||
std::vector<addr_mask_t> all_osd_network_masks;
|
||||
int use_proto_checksums = 0;
|
||||
int force_proto_checksums = 0;
|
||||
// op statistics
|
||||
osd_op_stats_t stats, recovery_stats;
|
||||
|
||||
void init();
|
||||
void init_iothreads();
|
||||
void parse_config(const json11::Json & config);
|
||||
void parse_config(const json11::Json & config, bool init);
|
||||
void connect_peer(uint64_t osd_num, json11::Json peer_state);
|
||||
void stop_client(uint64_t client_id, bool force_delete = false);
|
||||
void destroy_client(osd_client_t *cl);
|
||||
@@ -213,6 +303,7 @@ public:
|
||||
std::function<void(osd_num_t)> repeer_pgs;
|
||||
std::function<void(osd_num_t)> break_pg_locks;
|
||||
std::function<bool(osd_client_t*, json11::Json)> check_config_hook;
|
||||
std::function<void(osd_client_t*)> handshake_hook;
|
||||
void read_requests();
|
||||
void send_replies();
|
||||
void accept_connections(int listen_fd);
|
||||
@@ -234,6 +325,7 @@ public:
|
||||
rdma_cm_id *rdmacm_listen(const std::string & bind_address, int rdmacm_port, int *bound_port, int log_level);
|
||||
void rdmacm_destroy_listener(rdma_cm_id *listener);
|
||||
#endif
|
||||
bool is_encryption_enabled();
|
||||
|
||||
void inc_op_stats(osd_op_stats_t & stats, uint64_t opcode, timespec & tv_begin, timespec & tv_end, uint64_t len);
|
||||
void measure_exec(osd_op_t *cur_op);
|
||||
@@ -250,15 +342,30 @@ protected:
|
||||
|
||||
bool try_send(osd_client_t *cl);
|
||||
void handle_send(int result, bool prev, bool more, osd_client_t *cl);
|
||||
bool op_write_to(osd_client_t *cl, msgr_op_writer_t & wr);
|
||||
void next_write_op(osd_client_t *cl);
|
||||
bool op_write_buf(osd_client_t *cl, uint8_t *src, size_t src_len, uint8_t *dst, size_t dst_len, bool skip_csum, size_t & from, size_t & done);
|
||||
bool op_copy_data_to(osd_client_t *cl, uint8_t *dst, size_t dst_len, size_t & from, size_t & done);
|
||||
size_t copy_ops_to(osd_client_t *cl, uint8_t *dst, size_t dst_len);
|
||||
template<typename T> size_t copy_ops_to_with(osd_client_t *cl, uint8_t *dst, size_t dst_len);
|
||||
|
||||
bool handle_read(int result, osd_client_t *cl);
|
||||
bool handle_read_buffer(osd_client_t *cl, void *curbuf, int remain);
|
||||
bool handle_finished_read(osd_client_t *cl);
|
||||
bool handle_op_hdr(osd_client_t *cl);
|
||||
bool handle_reply_hdr(osd_client_t *cl);
|
||||
void handle_reply_ready(osd_op_t *op);
|
||||
void handle_read(int result, osd_client_t *cl);
|
||||
bool handle_read_buffer(osd_client_t *cl, uint8_t *curbuf, size_t bufsize);
|
||||
template<typename T> bool handle_buffer_with(osd_client_t *cl, uint8_t *curbuf, size_t bufsize);
|
||||
bool handle_hdr(osd_client_t *cl);
|
||||
bool allocate_op_buffers(osd_client_t *cl);
|
||||
bool allocate_reply_buffers(osd_client_t *cl, osd_op_t *op);
|
||||
bool op_read_from(osd_client_t *cl, msgr_op_reader_t & rdr);
|
||||
bool handle_finished_op(osd_client_t *cl);
|
||||
void handle_immediate_ops();
|
||||
|
||||
void op_encrypted_copy_buf(osd_client_t *cl, uint8_t *enc_buf, size_t enc_len, uint8_t *plain, size_t plain_len, size_t & done_plain, size_t & done_enc);
|
||||
void op_encrypt_free(osd_client_t* cl);
|
||||
void op_decrypted_copy_buf(osd_client_t *cl, uint8_t *enc_buf, size_t enc_len, uint8_t *plain, size_t plain_len, size_t & done_plain, size_t & done_enc);
|
||||
void op_decrypt_start(osd_client_t* cl);
|
||||
void op_decrypt_inline(osd_client_t* cl);
|
||||
void op_decrypt_free(osd_client_t* cl);
|
||||
|
||||
#ifdef WITH_RDMA
|
||||
void try_send_rdma(osd_client_t *cl);
|
||||
bool init_recv_rdma(osd_client_t *cl);
|
||||
|
||||
@@ -0,0 +1,663 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2026+
|
||||
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#ifdef WITH_ISAL_CRYPTO
|
||||
#include <isa-l_crypto/isal_crypto_api.h>
|
||||
#endif
|
||||
|
||||
#include "str_util.h"
|
||||
#include "etcd_state_client.h"
|
||||
#include "messenger.h"
|
||||
#include "msgr_encrypt.h"
|
||||
#include "http_client.h"
|
||||
#include "openssl_util.h"
|
||||
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/err.h>
|
||||
|
||||
op_aes_xts_encrypt_t::op_aes_xts_encrypt_t()
|
||||
{
|
||||
#ifndef WITH_ISAL_CRYPTO
|
||||
if (!(ctx = EVP_CIPHER_CTX_new()))
|
||||
{
|
||||
ERR_print_errors_fp(stderr);
|
||||
abort();
|
||||
}
|
||||
EVP_CIPHER_CTX_set_padding(ctx, 0);
|
||||
if (EVP_EncryptInit_ex(ctx, EVP_aes_256_xts(), NULL, NULL, NULL) != 1)
|
||||
{
|
||||
ERR_print_errors_fp(stderr);
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
op_aes_xts_encrypt_t::~op_aes_xts_encrypt_t()
|
||||
{
|
||||
assert(!encrypted);
|
||||
#ifndef WITH_ISAL_CRYPTO
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
#endif
|
||||
if (tmp)
|
||||
free(tmp);
|
||||
}
|
||||
|
||||
void op_aes_xts_encrypt_t::start(osd_client_t *cl, uint8_t *key, uint64_t start_offset, size_t block_size)
|
||||
{
|
||||
assert(!encrypted);
|
||||
this->cl = cl;
|
||||
this->start_offset = start_offset;
|
||||
this->key = key;
|
||||
this->block_size = block_size;
|
||||
this->offset = 0;
|
||||
this->encrypted = false;
|
||||
this->tmp_pos = 0;
|
||||
if (tmp && tmp_size != block_size)
|
||||
{
|
||||
free(tmp);
|
||||
tmp = NULL;
|
||||
tmp_size = 0;
|
||||
}
|
||||
#ifndef WITH_ISAL_CRYPTO
|
||||
if (EVP_EncryptInit_ex(ctx, NULL, NULL, key, NULL) != 1)
|
||||
{
|
||||
ERR_print_errors_fp(stderr);
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void op_aes_xts_encrypt_t::encrypt_block(uint8_t *in, uint8_t *out)
|
||||
{
|
||||
uint8_t iv[16] = { 0 };
|
||||
*((uint64_t*)iv) = start_offset + offset - offset%block_size;
|
||||
#ifdef WITH_ISAL_CRYPTO
|
||||
int r = isal_aes_xts_enc_256(key+32, key, iv, block_size, in, out);
|
||||
assert(r == 0 || r == ISAL_CRYPTO_ERR_XTS_SAME_KEYS);
|
||||
#else
|
||||
if (EVP_EncryptInit_ex(ctx, NULL, NULL, NULL, iv) != 1)
|
||||
{
|
||||
ERR_print_errors_fp(stderr);
|
||||
abort();
|
||||
}
|
||||
int actual_out = 0;
|
||||
if (EVP_EncryptUpdate(ctx, out, &actual_out, in, block_size) != 1)
|
||||
{
|
||||
ERR_print_errors_fp(stderr);
|
||||
abort();
|
||||
}
|
||||
assert(actual_out == block_size);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void copy_or_gcm(osd_client_t *cl, uint8_t *out, uint8_t *in, size_t n)
|
||||
{
|
||||
if (cl->proto_csum_status != MSGR_CSUM_GCM)
|
||||
memcpy(out, in, n);
|
||||
else
|
||||
{
|
||||
#ifdef WITH_ISAL_CRYPTO
|
||||
int r = isal_aes_gcm_enc_256_update(&cl->my_key_isal, cl->enc_ctx, out, in, n);
|
||||
assert(!r);
|
||||
#else
|
||||
int actual_out;
|
||||
if (EVP_EncryptUpdate(cl->enc_ctx, out, &actual_out, in, n) != 1)
|
||||
{
|
||||
fprintf(stderr, "EncryptUpdate error: ");
|
||||
ERR_print_errors_fp(stderr);
|
||||
abort();
|
||||
}
|
||||
assert(actual_out == n);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void op_aes_xts_encrypt_t::update(uint8_t *in, size_t max_in, uint8_t *out, size_t max_out, size_t & done_in, size_t & done_out)
|
||||
{
|
||||
// Fucking AES-XTS implementations (all of them) don't have streaming support,
|
||||
// crafting IV to resume encryption is slow, so we have to accumulate a full block
|
||||
// and encrypt it at once :-(
|
||||
// And then we have to support consuming it in parts because it's simpler for the
|
||||
// higher layers.
|
||||
if (encrypted)
|
||||
{
|
||||
// Copy accumulated and encrypted output
|
||||
assert(tmp);
|
||||
if (max_out > block_size - tmp_pos)
|
||||
max_out = block_size - tmp_pos;
|
||||
copy_or_gcm(cl, out, tmp + tmp_pos, max_out);
|
||||
done_out += max_out;
|
||||
tmp_pos += max_out;
|
||||
if (tmp_pos >= block_size)
|
||||
{
|
||||
encrypted = false;
|
||||
done_in += 1;
|
||||
}
|
||||
}
|
||||
else if (max_in < block_size - offset%block_size)
|
||||
{
|
||||
// Just accumulate input
|
||||
if (!tmp)
|
||||
{
|
||||
tmp = (uint8_t*)malloc_or_die(block_size);
|
||||
tmp_size = block_size;
|
||||
}
|
||||
memcpy(tmp + offset%block_size, in, max_in);
|
||||
done_in += max_in;
|
||||
offset += max_in;
|
||||
}
|
||||
else if (max_out < block_size)
|
||||
{
|
||||
// Accumulate and encrypt input in <tmp>, then copy part of it to <out>
|
||||
if (!tmp)
|
||||
{
|
||||
tmp = (uint8_t*)malloc_or_die(block_size);
|
||||
tmp_size = block_size;
|
||||
}
|
||||
max_in = block_size - offset%block_size;
|
||||
memcpy(tmp + offset%block_size, in, max_in);
|
||||
encrypt_block(tmp, tmp);
|
||||
encrypted = true;
|
||||
copy_or_gcm(cl, out, tmp, max_out);
|
||||
tmp_pos = max_out;
|
||||
done_in += max_in-1;
|
||||
offset += max_in;
|
||||
done_out += max_out;
|
||||
}
|
||||
else if (!(offset%block_size))
|
||||
{
|
||||
// Full block - simplest case
|
||||
encrypt_block(in, out);
|
||||
if (cl->proto_csum_status == MSGR_CSUM_GCM)
|
||||
copy_or_gcm(cl, out, out, block_size);
|
||||
done_in += block_size;
|
||||
offset += block_size;
|
||||
done_out += block_size;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Accumulate input and encrypt directly to <output>
|
||||
assert(tmp);
|
||||
max_in = block_size - offset%block_size;
|
||||
memcpy(tmp + offset%block_size, in, max_in);
|
||||
encrypt_block(tmp, out);
|
||||
if (cl->proto_csum_status == MSGR_CSUM_GCM)
|
||||
copy_or_gcm(cl, out, out, block_size);
|
||||
done_in += max_in;
|
||||
offset += max_in;
|
||||
done_out += block_size;
|
||||
}
|
||||
}
|
||||
|
||||
void destroy_aes_xts_encrypt(op_aes_xts_encrypt_t *encrypt_ctx)
|
||||
{
|
||||
delete encrypt_ctx;
|
||||
}
|
||||
|
||||
op_aes_xts_decrypt_t::op_aes_xts_decrypt_t()
|
||||
{
|
||||
#ifndef WITH_ISAL_CRYPTO
|
||||
if (!(ctx = EVP_CIPHER_CTX_new()))
|
||||
{
|
||||
ERR_print_errors_fp(stderr);
|
||||
abort();
|
||||
}
|
||||
EVP_CIPHER_CTX_set_padding(ctx, 0);
|
||||
if (EVP_DecryptInit_ex(ctx, EVP_aes_256_xts(), NULL, NULL, NULL) != 1)
|
||||
{
|
||||
ERR_print_errors_fp(stderr);
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
op_aes_xts_decrypt_t::~op_aes_xts_decrypt_t()
|
||||
{
|
||||
assert(!decrypted);
|
||||
#ifndef WITH_ISAL_CRYPTO
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
#endif
|
||||
if (tmp)
|
||||
free(tmp);
|
||||
}
|
||||
|
||||
void op_aes_xts_decrypt_t::start(osd_client_t *cl, uint8_t **key_chain, size_t chain_size, void *key_indexes, uint64_t start_offset, size_t block_size)
|
||||
{
|
||||
assert(!decrypted);
|
||||
this->cl = cl;
|
||||
this->start_offset = start_offset;
|
||||
this->key_chain = key_chain;
|
||||
this->chain_size = chain_size;
|
||||
this->key_indexes = key_indexes;
|
||||
this->key_index_bytes = osd_op_rw_t::chain_info_bytes(chain_size);
|
||||
assert(chain_size <= 1 || key_indexes != NULL);
|
||||
this->block_size = block_size;
|
||||
this->offset = 0;
|
||||
this->tmp_pos = 0;
|
||||
if (tmp && tmp_size != block_size)
|
||||
{
|
||||
free(tmp);
|
||||
tmp = NULL;
|
||||
tmp_size = 0;
|
||||
}
|
||||
#ifndef WITH_ISAL_CRYPTO
|
||||
if (chain_size == 1 && key_chain[0] && EVP_DecryptInit_ex(ctx, NULL, NULL, key_chain[0], NULL) != 1)
|
||||
{
|
||||
ERR_print_errors_fp(stderr);
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void op_aes_xts_decrypt_t::decrypt_block(uint8_t *in, uint8_t *out)
|
||||
{
|
||||
uint8_t *key = NULL;
|
||||
if (chain_size > 1)
|
||||
{
|
||||
uint32_t key_index = key_index_bytes == 1
|
||||
? ((uint8_t*)key_indexes)[offset/block_size]
|
||||
: (key_index_bytes == 2
|
||||
? ((uint16_t*)key_indexes)[offset/block_size]
|
||||
: (key_index_bytes == 4
|
||||
? ((uint32_t*)key_indexes)[offset/block_size]
|
||||
: UINT32_MAX));
|
||||
assert(key_index < chain_size);
|
||||
key = key_chain[key_index];
|
||||
}
|
||||
else
|
||||
{
|
||||
key = key_chain[0];
|
||||
}
|
||||
if (!key)
|
||||
{
|
||||
if (in != out)
|
||||
memcpy(out, in, block_size);
|
||||
return;
|
||||
}
|
||||
uint8_t iv[16] = { 0 };
|
||||
*((uint64_t*)iv) = start_offset + offset - offset%block_size;
|
||||
#ifdef WITH_ISAL_CRYPTO
|
||||
int r = isal_aes_xts_dec_256(key+32, key, iv, block_size, in, out);
|
||||
assert(r == 0 || r == ISAL_CRYPTO_ERR_XTS_SAME_KEYS);
|
||||
#else
|
||||
if (EVP_DecryptInit_ex(ctx, NULL, NULL, chain_size == 1 ? NULL : key, iv) != 1)
|
||||
{
|
||||
ERR_print_errors_fp(stderr);
|
||||
abort();
|
||||
}
|
||||
int actual_out = 0;
|
||||
if (EVP_DecryptUpdate(ctx, out, &actual_out, in, block_size) != 1)
|
||||
{
|
||||
ERR_print_errors_fp(stderr);
|
||||
abort();
|
||||
}
|
||||
assert(actual_out == block_size);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void gcm_dec(osd_client_t *cl, uint8_t *out, uint8_t *in, size_t n)
|
||||
{
|
||||
#ifdef WITH_ISAL_CRYPTO
|
||||
int r = isal_aes_gcm_dec_256_update(&cl->peer_key_isal, cl->dec_ctx, out, in, n);
|
||||
assert(!r);
|
||||
#else
|
||||
int actual_out;
|
||||
if (EVP_DecryptUpdate(cl->dec_ctx, out, &actual_out, in, n) != 1)
|
||||
{
|
||||
fprintf(stderr, "DecryptUpdate error: ");
|
||||
ERR_print_errors_fp(stderr);
|
||||
abort();
|
||||
}
|
||||
assert(actual_out == n);
|
||||
#endif
|
||||
}
|
||||
|
||||
// out may be NULL, in this case all input is still decrypted to calculate checksums,
|
||||
// but part of it is skipped and not copied to out
|
||||
void op_aes_xts_decrypt_t::update(uint8_t *in, size_t max_in, uint8_t *out, size_t max_out, size_t & done_in, size_t & done_out)
|
||||
{
|
||||
// Fucking AES-XTS implementations (all of them) don't have streaming support,
|
||||
// crafting IV to resume decryption is slow, so we have to accumulate a full block
|
||||
// and decrypt it at once :-(
|
||||
// And then we have to support consuming it in parts because clients sometimes need
|
||||
// fragmented output.
|
||||
if (decrypted)
|
||||
{
|
||||
// Copy accumulated and decrypted output
|
||||
assert(tmp);
|
||||
if (max_out > block_size - tmp_pos)
|
||||
max_out = block_size - tmp_pos;
|
||||
if (out)
|
||||
memcpy(out, tmp + tmp_pos, max_out);
|
||||
done_out += max_out;
|
||||
tmp_pos += max_out;
|
||||
if (tmp_pos >= block_size)
|
||||
{
|
||||
decrypted = false;
|
||||
done_in += 1;
|
||||
}
|
||||
}
|
||||
else if (max_in < block_size - offset%block_size)
|
||||
{
|
||||
// Just accumulate input
|
||||
if (!tmp)
|
||||
{
|
||||
tmp = (uint8_t*)malloc_or_die(block_size);
|
||||
tmp_size = block_size;
|
||||
}
|
||||
memcpy(tmp + offset%block_size, in, max_in);
|
||||
done_in += max_in;
|
||||
offset += max_in;
|
||||
}
|
||||
else if (max_out < block_size || !out)
|
||||
{
|
||||
// Accumulate and decrypt input in <tmp>, then copy part of it to <out>
|
||||
if (!tmp)
|
||||
{
|
||||
tmp = (uint8_t*)malloc_or_die(block_size);
|
||||
tmp_size = block_size;
|
||||
}
|
||||
max_in = block_size - offset%block_size;
|
||||
memcpy(tmp + offset%block_size, in, max_in);
|
||||
if (cl->proto_csum_status == MSGR_CSUM_GCM)
|
||||
gcm_dec(cl, tmp, tmp, block_size);
|
||||
decrypt_block(tmp, tmp);
|
||||
decrypted = true;
|
||||
if (out)
|
||||
memcpy(out, tmp, max_out);
|
||||
tmp_pos = max_out;
|
||||
done_in += max_in-1;
|
||||
offset += max_in;
|
||||
done_out += max_out;
|
||||
}
|
||||
else if (!(offset%block_size))
|
||||
{
|
||||
// Full block - simplest case
|
||||
if (cl->proto_csum_status == MSGR_CSUM_GCM)
|
||||
{
|
||||
if (!tmp)
|
||||
{
|
||||
tmp = (uint8_t*)malloc_or_die(block_size);
|
||||
tmp_size = block_size;
|
||||
}
|
||||
gcm_dec(cl, tmp, in, block_size);
|
||||
if (out)
|
||||
decrypt_block(tmp, out);
|
||||
}
|
||||
else
|
||||
decrypt_block(in, out);
|
||||
done_in += block_size;
|
||||
offset += block_size;
|
||||
done_out += block_size;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Accumulate input and decrypt directly to <output>
|
||||
assert(tmp);
|
||||
max_in = block_size - offset%block_size;
|
||||
memcpy(tmp + offset%block_size, in, max_in);
|
||||
assert(out);
|
||||
if (cl->proto_csum_status == MSGR_CSUM_GCM)
|
||||
gcm_dec(cl, tmp, tmp, block_size);
|
||||
decrypt_block(tmp, out);
|
||||
done_in += max_in;
|
||||
offset += max_in;
|
||||
done_out += block_size;
|
||||
}
|
||||
}
|
||||
|
||||
void destroy_aes_xts_decrypt(op_aes_xts_decrypt_t *decrypt_ctx)
|
||||
{
|
||||
delete decrypt_ctx;
|
||||
}
|
||||
|
||||
void osd_messenger_t::op_encrypted_copy_buf(osd_client_t *cl, uint8_t *enc_buf, size_t enc_len, uint8_t *plain, size_t plain_len, size_t & done_plain, size_t & done_enc)
|
||||
{
|
||||
if (!cl->xts_enc_ctx)
|
||||
{
|
||||
if (encrypt_xts_pool.size())
|
||||
{
|
||||
cl->xts_enc_ctx = encrypt_xts_pool.back();
|
||||
encrypt_xts_pool.pop_back();
|
||||
}
|
||||
else
|
||||
cl->xts_enc_ctx = new op_aes_xts_encrypt_t();
|
||||
assert(cl->write_op->enc->key_chain[0]);
|
||||
cl->xts_enc_ctx->start(cl, cl->write_op->enc->key_chain[0], cl->write_op->req.rw.offset, cl->write_op->enc->bitmap_granularity);
|
||||
}
|
||||
size_t old_out = done_enc;
|
||||
while (done_plain < plain_len && done_enc < enc_len)
|
||||
{
|
||||
size_t done_in = 0;
|
||||
size_t done_out = 0;
|
||||
cl->xts_enc_ctx->update(plain+done_plain, plain_len-done_plain, enc_buf+done_enc, enc_len-done_enc, done_in, done_out);
|
||||
done_enc += done_out;
|
||||
cl->write_op_pos += done_in;
|
||||
done_plain += done_in;
|
||||
}
|
||||
if (cl->write_csum_state && done_enc > old_out)
|
||||
XXH3_64bits_update(cl->write_csum_state, enc_buf+old_out, done_enc-old_out);
|
||||
}
|
||||
|
||||
void osd_messenger_t::op_decrypted_copy_buf(osd_client_t *cl, uint8_t *enc_buf, size_t enc_len, uint8_t *plain, size_t plain_len, size_t & done_plain, size_t & done_enc)
|
||||
{
|
||||
op_decrypt_start(cl);
|
||||
size_t old_in = done_enc;
|
||||
while (done_plain < plain_len && done_enc < enc_len)
|
||||
{
|
||||
size_t done_in = 0;
|
||||
size_t done_out = 0;
|
||||
// plain == NULL means skip output
|
||||
cl->xts_dec_ctx->update(enc_buf+done_enc, enc_len-done_enc, plain ? plain+done_plain : NULL, plain_len-done_plain, done_in, done_out);
|
||||
done_enc += done_in;
|
||||
cl->read_op_pos += done_out;
|
||||
cl->read_op_inline_decrypt_in += done_in;
|
||||
done_plain += done_out;
|
||||
}
|
||||
if (cl->read_csum_state && done_enc > old_in)
|
||||
XXH3_64bits_update(cl->read_csum_state, enc_buf+old_in, done_enc-old_in);
|
||||
}
|
||||
|
||||
void osd_messenger_t::op_decrypt_start(osd_client_t* cl)
|
||||
{
|
||||
if (!cl->xts_dec_ctx)
|
||||
{
|
||||
if (decrypt_xts_pool.size())
|
||||
{
|
||||
cl->xts_dec_ctx = decrypt_xts_pool.back();
|
||||
decrypt_xts_pool.pop_back();
|
||||
}
|
||||
else
|
||||
cl->xts_dec_ctx = new op_aes_xts_decrypt_t();
|
||||
auto & enc = cl->read_op->enc;
|
||||
assert(cl->read_op->req.hdr.opcode == OSD_OP_READ);
|
||||
cl->xts_dec_ctx->start(cl, enc->key_chain, enc->chain_size,
|
||||
(cl->read_op->req.rw.flags & OSD_OP_RETURN_CHAIN) ? (uint8_t*)cl->read_op->bitmap + enc->read_chain_bitmap_pos : 0,
|
||||
cl->read_op->req.rw.offset, enc->bitmap_granularity);
|
||||
}
|
||||
}
|
||||
|
||||
void osd_messenger_t::op_decrypt_inline(osd_client_t* cl)
|
||||
{
|
||||
op_decrypt_start(cl);
|
||||
osd_op_t *op = cl->read_op;
|
||||
size_t from_in = cl->read_op_inline_decrypt_in;
|
||||
int i = 0;
|
||||
while (i < op->iov.count && from_in >= op->iov.buf[i].iov_len)
|
||||
{
|
||||
from_in -= op->iov.buf[i].iov_len;
|
||||
i++;
|
||||
}
|
||||
size_t from_out = cl->read_op_inline_decrypt_pos - OSD_PACKET_SIZE - op->reply.rw.bitmap_len;
|
||||
int j = 0;
|
||||
while (j < op->iov.count && from_out >= op->iov.buf[j].iov_len)
|
||||
{
|
||||
from_out -= op->iov.buf[j].iov_len;
|
||||
j++;
|
||||
}
|
||||
while (i < op->iov.count && j < op->iov.count)
|
||||
{
|
||||
uint8_t *in = (uint8_t*)op->iov.buf[i].iov_base + from_in;
|
||||
size_t in_len = op->iov.buf[i].iov_len - from_in;
|
||||
uint8_t *out = (uint8_t*)op->iov.buf[j].iov_base + from_out;
|
||||
size_t out_len = op->iov.buf[j].iov_len - from_out;
|
||||
size_t done_in = 0;
|
||||
size_t done_out = 0;
|
||||
cl->xts_dec_ctx->update(in, in_len, out, out_len, done_in, done_out);
|
||||
if (done_in >= in_len)
|
||||
{
|
||||
i++;
|
||||
from_in = 0;
|
||||
}
|
||||
else
|
||||
from_in += done_in;
|
||||
if (done_out >= out_len)
|
||||
{
|
||||
j++;
|
||||
from_out = 0;
|
||||
}
|
||||
else
|
||||
from_out += done_out;
|
||||
}
|
||||
assert(j >= op->iov.count);
|
||||
}
|
||||
|
||||
void osd_messenger_t::op_decrypt_free(osd_client_t* cl)
|
||||
{
|
||||
if (cl->xts_dec_ctx)
|
||||
{
|
||||
if (decrypt_xts_pool.size() > max_cipher_pool_size)
|
||||
delete cl->xts_dec_ctx;
|
||||
else
|
||||
decrypt_xts_pool.push_back(cl->xts_dec_ctx);
|
||||
cl->xts_dec_ctx = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void osd_messenger_t::op_encrypt_free(osd_client_t* cl)
|
||||
{
|
||||
if (cl->xts_enc_ctx)
|
||||
{
|
||||
if (encrypt_xts_pool.size() > max_cipher_pool_size)
|
||||
delete cl->xts_enc_ctx;
|
||||
else
|
||||
encrypt_xts_pool.push_back(cl->xts_enc_ctx);
|
||||
cl->xts_enc_ctx = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool osd_messenger_t::derive_aes_keys(osd_client_t *cl, bool update_my, bool update_peer)
|
||||
{
|
||||
if (!cl->hs_result.shared_secret.size())
|
||||
{
|
||||
cl->hs_result = cl->hs->get_result();
|
||||
if (handshake_hook)
|
||||
{
|
||||
handshake_hook(cl);
|
||||
}
|
||||
}
|
||||
std::vector<uint8_t> old_my = cl->my_key, old_peer = cl->peer_key;
|
||||
// Both keys include AES key and iv + xxhash3 secret
|
||||
const auto len = AES_256_GCM_KEY_SIZE + AES_256_GCM_IV_SIZE + XXH_SECRET_DEFAULT_SIZE;
|
||||
cl->my_key.resize(len);
|
||||
cl->peer_key.resize(len);
|
||||
bool ok = true;
|
||||
if (update_my || !old_my.size())
|
||||
{
|
||||
ok = ok && hs_ctx->derive_kdf(cl->hs_result.shared_secret.data(), cl->hs_result.shared_secret.size(),
|
||||
old_my.size() ? old_my.data() : NULL, old_my.size(),
|
||||
cl->is_incoming ? "server key" : "client key", cl->my_key.data(), len);
|
||||
#ifdef WITH_ISAL_CRYPTO
|
||||
if (ok)
|
||||
isal_aes_gcm_pre_256(cl->my_key.data(), &cl->my_key_isal);
|
||||
#endif
|
||||
cl->my_iv_ctr = 0;
|
||||
}
|
||||
if (update_peer || !old_peer.size())
|
||||
{
|
||||
ok = ok && hs_ctx->derive_kdf(cl->hs_result.shared_secret.data(), cl->hs_result.shared_secret.size(),
|
||||
old_peer.size() ? old_peer.data() : NULL, old_peer.size(),
|
||||
!cl->is_incoming ? "server key" : "client key", cl->peer_key.data(), len);
|
||||
#ifdef WITH_ISAL_CRYPTO
|
||||
if (ok)
|
||||
isal_aes_gcm_pre_256(cl->peer_key.data(), &cl->peer_key_isal);
|
||||
#endif
|
||||
cl->peer_iv_ctr = 0;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
void osd_messenger_t::init_tls()
|
||||
{
|
||||
if (!tls_cert.empty() || !tls_key.empty() || !osd_tls_ca.empty() || !client_tls_ca.empty())
|
||||
{
|
||||
if (tls_cert.empty() || tls_key.empty() || osd_tls_ca.empty() || osd_num && client_tls_ca.empty())
|
||||
{
|
||||
if (osd_num)
|
||||
fprintf(stderr, "Vitastor transport encryption requires osd_cert, osd_pkey, osd_ca, client_ca options for OSDs\n");
|
||||
else
|
||||
fprintf(stderr, "Vitastor transport encryption requires cert, pkey and osd_ca options\n");
|
||||
exit(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifndef __MOCK__
|
||||
gcm_enabled = true;
|
||||
hs_ctx = msgr_handshake_ctx_i::create_ctx();
|
||||
if (!hs_ctx->init(tls_cert, tls_key, osd_tls_ca, client_tls_ca))
|
||||
{
|
||||
fprintf(stderr, "Error: %s\n", hs_ctx->get_error().c_str());
|
||||
exit(1);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void osd_messenger_t::init_tls_client(osd_client_t *cl)
|
||||
{
|
||||
if (gcm_enabled)
|
||||
{
|
||||
cl->gcm_enabled = true;
|
||||
cl->hs = hs_ctx->create();
|
||||
cl->hs->init(cl->is_incoming);
|
||||
if (cl->hs->out_size())
|
||||
{
|
||||
if (cl->write_state == 0)
|
||||
{
|
||||
cl->write_state = CL_WRITE_READY;
|
||||
write_ready_clients.push_back(cl->client_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void osd_messenger_t::destroy_tls()
|
||||
{
|
||||
#ifdef WITH_ISAL_CRYPTO
|
||||
for (isal_gcm_context_data *ctx: encrypt_gcm_pool)
|
||||
{
|
||||
free(ctx);
|
||||
}
|
||||
for (isal_gcm_context_data *ctx: decrypt_gcm_pool)
|
||||
{
|
||||
free(ctx);
|
||||
}
|
||||
#else
|
||||
for (EVP_CIPHER_CTX *ctx: encrypt_gcm_pool)
|
||||
{
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
}
|
||||
for (EVP_CIPHER_CTX *ctx: decrypt_gcm_pool)
|
||||
{
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
}
|
||||
#endif
|
||||
if (hs_ctx)
|
||||
{
|
||||
delete hs_ctx;
|
||||
hs_ctx = NULL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2026+
|
||||
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef WITH_ISAL_CRYPTO
|
||||
#include <isa-l_crypto/aes_xts.h>
|
||||
#endif
|
||||
|
||||
#include "../util/xxh_x86dispatch.h"
|
||||
#include <openssl/conf.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/err.h>
|
||||
|
||||
struct osd_client_t;
|
||||
|
||||
class op_aes_xts_encrypt_t
|
||||
{
|
||||
#ifndef WITH_ISAL_CRYPTO
|
||||
EVP_CIPHER_CTX *ctx = NULL;
|
||||
#endif
|
||||
osd_client_t *cl = NULL;
|
||||
uint64_t start_offset = 0;
|
||||
uint8_t *key = NULL;
|
||||
size_t offset = 0;
|
||||
size_t block_size = 0;
|
||||
uint8_t *tmp = NULL;
|
||||
size_t tmp_size = 0;
|
||||
size_t tmp_pos = 0;
|
||||
bool encrypted = false;
|
||||
|
||||
void encrypt_block(uint8_t *in, uint8_t *out);
|
||||
|
||||
public:
|
||||
op_aes_xts_encrypt_t();
|
||||
~op_aes_xts_encrypt_t();
|
||||
|
||||
void start(osd_client_t *cl, uint8_t *key, uint64_t start_offset, size_t block_size);
|
||||
void update(uint8_t *in, size_t max_in, uint8_t *out, size_t max_out, size_t & done_in, size_t & done_out);
|
||||
};
|
||||
|
||||
void destroy_aes_xts_encrypt(op_aes_xts_encrypt_t *encrypt_ctx);
|
||||
|
||||
class op_aes_xts_decrypt_t
|
||||
{
|
||||
#ifndef WITH_ISAL_CRYPTO
|
||||
EVP_CIPHER_CTX *ctx = NULL;
|
||||
#endif
|
||||
osd_client_t *cl = NULL;
|
||||
uint64_t start_offset = 0;
|
||||
uint8_t **key_chain = NULL;
|
||||
size_t chain_size = 0;
|
||||
void *key_indexes = NULL;
|
||||
int key_index_bytes = 0;
|
||||
size_t offset = 0;
|
||||
size_t block_size = 0;
|
||||
uint8_t *tmp = NULL;
|
||||
size_t tmp_size = 0;
|
||||
size_t tmp_pos = 0;
|
||||
bool decrypted = false;
|
||||
|
||||
void decrypt_block(uint8_t *in, uint8_t *out);
|
||||
|
||||
public:
|
||||
op_aes_xts_decrypt_t();
|
||||
~op_aes_xts_decrypt_t();
|
||||
|
||||
void start(osd_client_t *cl, uint8_t **key_chain, size_t chain_size, void *key_indexes, uint64_t start_offset, size_t block_size);
|
||||
void update(uint8_t *in, size_t max_in, uint8_t *out, size_t max_out, size_t & done_in, size_t & done_out);
|
||||
};
|
||||
|
||||
void destroy_aes_xts_decrypt(op_aes_xts_decrypt_t *decrypt_ctx);
|
||||
@@ -0,0 +1,810 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2026+
|
||||
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
|
||||
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include <openssl/conf.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/obj_mac.h>
|
||||
#include <openssl/ec.h>
|
||||
#include <openssl/kdf.h>
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/err.h>
|
||||
|
||||
#include "msgr_handshake.h"
|
||||
#include "malloc_or_die.h"
|
||||
#include "openssl_util.h"
|
||||
#include "str_util.h"
|
||||
|
||||
#define AES_256_GCM_KEY_SIZE 32
|
||||
#define AES_256_GCM_IV_SIZE 12
|
||||
|
||||
// TLS 1.3-like handshake
|
||||
|
||||
// 1. Client->server: EC public key
|
||||
// 2. Server->client: EC public key, encrypted certificate and digital signature of the handshake
|
||||
// 3. Client->server: encrypted certificate and digital signature of the handshake
|
||||
|
||||
// "vitaECDH" interleaved
|
||||
#define MSGR_HS_MAGIC 0x4861447443694576l
|
||||
#define MSGR_HS_MAX_LEN 131072
|
||||
|
||||
#define MSGR_HS_SERVER_INIT 0
|
||||
#define MSGR_HS_CLIENT_INIT 1
|
||||
#define MSGR_HS_SERVER_REPLY 2
|
||||
#define MSGR_HS_CLIENT_REPLY 3
|
||||
#define MSGR_HS_DONE 100
|
||||
#define MSGR_HS_ERROR 101
|
||||
|
||||
struct __attribute__((__packed__)) msgr_handshake_hdr_t
|
||||
{
|
||||
uint32_t msg_len;
|
||||
uint64_t magic;
|
||||
uint32_t type;
|
||||
};
|
||||
|
||||
class msgr_handshake_ctx_t: public msgr_handshake_ctx_i
|
||||
{
|
||||
friend class msgr_handshake_t;
|
||||
|
||||
EVP_PKEY_CTX *pctx = NULL;
|
||||
EVP_PKEY *params = NULL;
|
||||
X509_STORE *ca = NULL;
|
||||
X509 *osd_ca = NULL;
|
||||
X509 *client_ca = NULL;
|
||||
std::string my_cert_pem;
|
||||
X509 *my_cert = NULL;
|
||||
EVP_PKEY *my_pubkey = NULL;
|
||||
const EVP_MD *md = NULL;
|
||||
EVP_PKEY *my_privkey = NULL;
|
||||
EVP_KDF_CTX* kdf_ctx = NULL;
|
||||
std::string error;
|
||||
|
||||
bool on_error(const std::string & prefix);
|
||||
|
||||
public:
|
||||
~msgr_handshake_ctx_t();
|
||||
msgr_handshake_i* create() override;
|
||||
bool init(const std::string & pem_cert, const std::string & pem_key,
|
||||
const std::string & pem_osd_ca, const std::string & pem_client_ca) override;
|
||||
std::string get_error() override;
|
||||
bool derive_kdf(const uint8_t* insecret, size_t insecret_len,
|
||||
const uint8_t* salt, size_t salt_len, const char *label, uint8_t *outsecret, size_t outsize) override;
|
||||
};
|
||||
|
||||
class msgr_handshake_t: public msgr_handshake_i
|
||||
{
|
||||
msgr_handshake_ctx_t *ctx = NULL;
|
||||
bool is_server = false;
|
||||
|
||||
std::vector<uint8_t> full_handshake;
|
||||
std::vector<uint8_t> in_buf;
|
||||
uint8_t *out_buf = NULL;
|
||||
size_t out_buf_size = 0;
|
||||
std::string error;
|
||||
|
||||
int state = 0;
|
||||
|
||||
EVP_PKEY *ec_key = NULL;
|
||||
X509 *peer_cert = NULL;
|
||||
bool peer_is_osd = false;
|
||||
std::vector<uint8_t> shared_secret;
|
||||
std::vector<uint8_t> hs_key, peer_hs_key;
|
||||
|
||||
msgr_handshake_hdr_t *cur_hdr = NULL;
|
||||
uint8_t *cur_buf = NULL;
|
||||
size_t cur_left = 0;
|
||||
|
||||
bool on_error(const std::string & prefix);
|
||||
bool derive_shared_secret(EVP_PKEY *peer_ec_key);
|
||||
bool derive_hs_keys(const uint8_t *encoded_peer_key, size_t encoded_key_len);
|
||||
bool sign(std::vector<uint8_t> & out);
|
||||
bool verify(const uint8_t *signature, size_t signature_len);
|
||||
bool encrypt(const uint8_t* src, size_t len, uint8_t* dest);
|
||||
bool decrypt(const uint8_t* src, size_t & len, uint8_t* dest);
|
||||
bool make_client_init();
|
||||
bool make_server_reply();
|
||||
bool make_client_reply();
|
||||
bool verify_peer(const uint8_t *peer_cert_pem, size_t peer_cert_len);
|
||||
ssize_t start_msg(uint8_t* src, size_t len, uint32_t expected_type);
|
||||
bool read_with_len(const uint8_t* & dst, uint32_t & dst_len);
|
||||
bool handle_client_init();
|
||||
bool handle_server_reply();
|
||||
bool handle_client_reply();
|
||||
bool handle_peer_cert(const uint8_t *key, uint32_t key_len);
|
||||
void complete();
|
||||
|
||||
public:
|
||||
// Workflow: create -> init -> handle -> get_result/get_error -> destruct
|
||||
msgr_handshake_t(msgr_handshake_ctx_t *ctx): ctx(ctx) {}
|
||||
~msgr_handshake_t();
|
||||
bool init(bool server_mode) override;
|
||||
ssize_t handle(uint8_t* in_buf, size_t in_size) override;
|
||||
bool done() override;
|
||||
uint8_t *get_out() override;
|
||||
size_t out_size() override;
|
||||
void eat_out(size_t n) override;
|
||||
void reset_out() override;
|
||||
msgr_handshake_result_t get_result() override;
|
||||
std::string get_error() override;
|
||||
};
|
||||
|
||||
msgr_handshake_ctx_i* msgr_handshake_ctx_i::create_ctx()
|
||||
{
|
||||
return new msgr_handshake_ctx_t();
|
||||
}
|
||||
|
||||
msgr_handshake_i* msgr_handshake_ctx_t::create()
|
||||
{
|
||||
return new msgr_handshake_t(this);
|
||||
}
|
||||
|
||||
bool msgr_handshake_ctx_t::init(const std::string & pem_cert, const std::string & pem_key,
|
||||
const std::string & pem_osd_ca, const std::string & pem_client_ca)
|
||||
{
|
||||
if (pem_cert.substr(0, 5) == "-----")
|
||||
my_cert_pem = pem_cert;
|
||||
else
|
||||
{
|
||||
my_cert_pem = read_file(pem_cert);
|
||||
if (my_cert_pem.empty())
|
||||
{
|
||||
error = "Failed to load certificate file";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
{
|
||||
BIO *bio = BIO_new_mem_buf(my_cert_pem.data(), my_cert_pem.size());
|
||||
if (!bio)
|
||||
return on_error("BIO_new_mem_buf: ");
|
||||
my_cert = PEM_read_bio_X509(bio, NULL, 0, NULL);
|
||||
BIO_free(bio);
|
||||
if (!my_cert)
|
||||
return on_error("Failed to load certificate: ");
|
||||
}
|
||||
if (!(my_pubkey = X509_get0_pubkey(my_cert)))
|
||||
return on_error("X509_get0_pubkey: ");
|
||||
if (!(md = EVP_get_digestbynid(NID_sha384)))
|
||||
return on_error("EVP_get_digestbynid SHA384: ");
|
||||
if (!(my_privkey = openssl_load_key(pem_key)))
|
||||
return on_error("Failed to load private key: ");
|
||||
if (!(ca = X509_STORE_new()))
|
||||
return on_error("X509_STORE_CTX_new: ");
|
||||
if (!(osd_ca = openssl_load_cert(pem_osd_ca)))
|
||||
return on_error("Failed to load OSD CA certificate: ");
|
||||
if (X509_STORE_add_cert(ca, osd_ca) <= 0)
|
||||
return on_error("X509_STORE_add_cert OSD CA: ");
|
||||
if (!pem_client_ca.empty() && !(client_ca = openssl_load_cert(pem_client_ca)))
|
||||
return on_error("Failed to load client CA certificate: ");
|
||||
if (client_ca && X509_STORE_add_cert(ca, client_ca) <= 0)
|
||||
return on_error("X509_STORE_add_cert client CA: ");
|
||||
if (!(pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL)))
|
||||
return on_error("EVP_PKEY_CTX_new_id: ");
|
||||
if (EVP_PKEY_paramgen_init(pctx) <= 0)
|
||||
return on_error("EVP_PKEY_paramgen_init: ");
|
||||
if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, /*NID_X9_62_prime256v1*/NID_secp384r1) <= 0)
|
||||
return on_error("EVP_PKEY_CTX_set_ec_paramgen_curve_nid: ");
|
||||
if (EVP_PKEY_paramgen(pctx, ¶ms) <= 0)
|
||||
return on_error("EVP_PKEY_paramgen: ");
|
||||
EVP_KDF *kdf = EVP_KDF_fetch(NULL, "hkdf", NULL);
|
||||
if (!kdf)
|
||||
return on_error("EVP_KDF_fetch: ");
|
||||
kdf_ctx = EVP_KDF_CTX_new(kdf);
|
||||
EVP_KDF_free(kdf);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string msgr_handshake_ctx_t::get_error()
|
||||
{
|
||||
return error;
|
||||
}
|
||||
|
||||
msgr_handshake_ctx_t::~msgr_handshake_ctx_t()
|
||||
{
|
||||
if (pctx)
|
||||
EVP_PKEY_CTX_free(pctx);
|
||||
if (params)
|
||||
EVP_PKEY_free(params);
|
||||
if (ca)
|
||||
X509_STORE_free(ca);
|
||||
if (osd_ca)
|
||||
X509_free(osd_ca);
|
||||
if (client_ca)
|
||||
X509_free(client_ca);
|
||||
my_pubkey = NULL;
|
||||
if (my_cert)
|
||||
X509_free(my_cert);
|
||||
if (my_privkey)
|
||||
EVP_PKEY_free(my_privkey);
|
||||
if (kdf_ctx)
|
||||
EVP_KDF_CTX_free(kdf_ctx);
|
||||
}
|
||||
|
||||
bool msgr_handshake_ctx_t::on_error(const std::string & prefix)
|
||||
{
|
||||
error = prefix+ERR_error_string(ERR_get_error(), NULL);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool msgr_handshake_ctx_t::derive_kdf(const uint8_t* insecret, size_t insecret_len,
|
||||
const uint8_t* salt, size_t salt_len, const char *label, uint8_t *outsecret, size_t outsize)
|
||||
{
|
||||
OSSL_PARAM params[5];
|
||||
int n = 0;
|
||||
params[n++] = OSSL_PARAM_construct_utf8_string("digest", (char*)"sha384", (size_t)7);
|
||||
params[n++] = OSSL_PARAM_construct_octet_string("key", (void*)insecret, insecret_len);
|
||||
params[n++] = OSSL_PARAM_construct_octet_string("info", (void*)label, strlen(label)+1);
|
||||
params[n++] = OSSL_PARAM_construct_octet_string("salt", (salt ? (void*)salt : (void*)""), salt_len);
|
||||
params[n++] = OSSL_PARAM_construct_end();
|
||||
assert(n <= sizeof(params)/sizeof(OSSL_PARAM));
|
||||
if (EVP_KDF_CTX_set_params(kdf_ctx, params) <= 0)
|
||||
return false;
|
||||
if (EVP_KDF_derive(kdf_ctx, outsecret, outsize, NULL) <= 0)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
msgr_handshake_t::~msgr_handshake_t()
|
||||
{
|
||||
if (out_buf)
|
||||
free(out_buf);
|
||||
if (peer_cert)
|
||||
X509_free(peer_cert);
|
||||
if (ec_key)
|
||||
EVP_PKEY_free(ec_key);
|
||||
}
|
||||
|
||||
bool msgr_handshake_t::on_error(const std::string & prefix)
|
||||
{
|
||||
error = prefix+ERR_error_string(ERR_get_error(), NULL);
|
||||
state = MSGR_HS_ERROR;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool msgr_handshake_t::init(bool server_mode)
|
||||
{
|
||||
this->ctx = ctx;
|
||||
std::unique_ptr<EVP_PKEY_CTX, decltype(&EVP_PKEY_CTX_free)> kctx(EVP_PKEY_CTX_new(ctx->params, NULL), EVP_PKEY_CTX_free);
|
||||
if (!kctx)
|
||||
return on_error("EVP_PKEY_CTX_new with EC params: ");
|
||||
if (EVP_PKEY_keygen_init(kctx.get()) <= 0)
|
||||
return on_error("EVP_PKEY_keygen_init: ");
|
||||
if (EVP_PKEY_keygen(kctx.get(), &ec_key) <= 0)
|
||||
return on_error("EVP_PKEY_keygen: ");
|
||||
if (!server_mode)
|
||||
{
|
||||
// Send initial message - only the EC public key
|
||||
if (!make_client_init())
|
||||
return false;
|
||||
}
|
||||
this->is_server = server_mode;
|
||||
this->state = server_mode ? MSGR_HS_SERVER_INIT : MSGR_HS_CLIENT_INIT;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void copy_to(std::vector<uint8_t> & buf, const void* src, uint32_t len)
|
||||
{
|
||||
size_t old_size = buf.size();
|
||||
buf.resize(buf.size() + len);
|
||||
memcpy(buf.data() + old_size, src, len);
|
||||
}
|
||||
|
||||
static void copy_to_raw(uint8_t* & buf, const void* src, size_t len)
|
||||
{
|
||||
memcpy(buf, src, len);
|
||||
buf += len;
|
||||
}
|
||||
|
||||
static void copy_to_with_len(std::vector<uint8_t> & buf, const void* src, uint32_t len)
|
||||
{
|
||||
copy_to(buf, &len, sizeof(len));
|
||||
copy_to(buf, src, len);
|
||||
}
|
||||
|
||||
bool msgr_handshake_t::derive_shared_secret(EVP_PKEY *peer_ec_key)
|
||||
{
|
||||
EVP_PKEY_CTX *dh_ctx = NULL;
|
||||
if (!(dh_ctx = EVP_PKEY_CTX_new(ec_key, NULL)))
|
||||
return on_error("EVP_PKEY_CTX_new for ECDH: ");
|
||||
if (!EVP_PKEY_derive_init(dh_ctx))
|
||||
{
|
||||
EVP_PKEY_CTX_free(dh_ctx);
|
||||
return on_error("EVP_PKEY_derive_init: ");
|
||||
}
|
||||
if (!EVP_PKEY_derive_set_peer(dh_ctx, peer_ec_key))
|
||||
{
|
||||
EVP_PKEY_CTX_free(dh_ctx);
|
||||
return on_error("EVP_PKEY_derive_set_peer: ");
|
||||
}
|
||||
size_t len = 0;
|
||||
if (!EVP_PKEY_derive(dh_ctx, NULL, &len))
|
||||
{
|
||||
EVP_PKEY_CTX_free(dh_ctx);
|
||||
return on_error("EVP_PKEY_derive get length: ");
|
||||
}
|
||||
shared_secret.resize(len);
|
||||
assert(len == 48);
|
||||
if (!EVP_PKEY_derive(dh_ctx, shared_secret.data(), &len))
|
||||
{
|
||||
EVP_PKEY_CTX_free(dh_ctx);
|
||||
return on_error("EVP_PKEY_derive: ");
|
||||
}
|
||||
assert(len == shared_secret.size());
|
||||
shared_secret.resize(len);
|
||||
EVP_PKEY_CTX_free(dh_ctx);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool msgr_handshake_t::derive_hs_keys(const uint8_t *encoded_peer_key, size_t encoded_key_len)
|
||||
{
|
||||
std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)> peer_ec_key(EVP_PKEY_new(), EVP_PKEY_free);
|
||||
if (!peer_ec_key)
|
||||
return on_error("EVP_PKEY_new: ");
|
||||
if (EVP_PKEY_copy_parameters(peer_ec_key.get(), ec_key) <= 0)
|
||||
return on_error("EVP_PKEY_copy_parameters: ");
|
||||
if (EVP_PKEY_set1_encoded_public_key(peer_ec_key.get(), encoded_peer_key, encoded_key_len) <= 0)
|
||||
return on_error("Invalid handshake peer key: ");
|
||||
if (!derive_shared_secret(peer_ec_key.get()))
|
||||
return false;
|
||||
hs_key.resize(AES_256_GCM_KEY_SIZE + AES_256_GCM_IV_SIZE);
|
||||
peer_hs_key.resize(AES_256_GCM_KEY_SIZE + AES_256_GCM_IV_SIZE);
|
||||
if (!ctx->derive_kdf(shared_secret.data(), shared_secret.size(),
|
||||
NULL, 0, (state == MSGR_HS_SERVER_INIT ? "server hs key" : "client hs key"),
|
||||
hs_key.data(), hs_key.size()))
|
||||
return on_error("derive_kdf: ");
|
||||
if (!ctx->derive_kdf(shared_secret.data(), shared_secret.size(),
|
||||
NULL, 0, (state != MSGR_HS_SERVER_INIT ? "server hs key" : "client hs key"),
|
||||
peer_hs_key.data(), peer_hs_key.size()))
|
||||
return on_error("derive_kdf: ");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool msgr_handshake_t::sign(std::vector<uint8_t> & out)
|
||||
{
|
||||
std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)> md_ctx(EVP_MD_CTX_new(), EVP_MD_CTX_free);
|
||||
if (!md_ctx)
|
||||
return on_error("EVP_MD_CTX_create: ");
|
||||
if (EVP_DigestSignInit(md_ctx.get(), NULL, ctx->md, NULL, ctx->my_privkey) <= 0)
|
||||
return on_error("EVP_DigestSignInit: ");
|
||||
if (EVP_DigestSignUpdate(md_ctx.get(), full_handshake.data(), full_handshake.size()) <= 0)
|
||||
return on_error("EVP_DigestSignUpdate: ");
|
||||
size_t siglen = 0;
|
||||
if (EVP_DigestSignFinal(md_ctx.get(), NULL, &siglen) <= 0)
|
||||
return on_error("EVP_DigestSignFinal get length: ");
|
||||
size_t oldsize = out.size();
|
||||
out.resize(oldsize + siglen);
|
||||
if (EVP_DigestSignFinal(md_ctx.get(), out.data() + oldsize, &siglen) <= 0)
|
||||
return on_error("EVP_DigestSignFinal: ");
|
||||
out.resize(oldsize + siglen);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool msgr_handshake_t::verify(const uint8_t *signature, size_t signature_len)
|
||||
{
|
||||
std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)> md_ctx(EVP_MD_CTX_new(), EVP_MD_CTX_free);
|
||||
if (!md_ctx)
|
||||
return on_error("EVP_MD_CTX_create: ");
|
||||
if (EVP_DigestVerifyInit(md_ctx.get(), NULL, ctx->md, NULL, X509_get0_pubkey(peer_cert)) <= 0)
|
||||
return on_error("EVP_DigestVerifyInit: ");
|
||||
if (EVP_DigestVerifyUpdate(md_ctx.get(), full_handshake.data(), full_handshake.size()) <= 0)
|
||||
return on_error("EVP_DigestVerifyUpdate: ");
|
||||
if (EVP_DigestVerifyFinal(md_ctx.get(), signature, signature_len) <= 0)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool msgr_handshake_t::encrypt(const uint8_t* src, size_t len, uint8_t* dest)
|
||||
{
|
||||
std::unique_ptr<EVP_CIPHER_CTX, decltype(&EVP_CIPHER_CTX_free)> enc_ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free);
|
||||
if (!ctx)
|
||||
return on_error("EVP_CIPHER_CTX_new: ");
|
||||
if (EVP_EncryptInit_ex(enc_ctx.get(), EVP_aes_256_gcm(), NULL, hs_key.data(), hs_key.data() + AES_256_GCM_KEY_SIZE) <= 0)
|
||||
return on_error("EVP_EncryptInit AES-256-GCM: ");
|
||||
int actual_out;
|
||||
if (EVP_EncryptUpdate(enc_ctx.get(), dest, &actual_out, src, len) <= 0)
|
||||
return on_error("EVP_EncryptUpdate: ");
|
||||
assert(actual_out == len);
|
||||
if (EVP_EncryptFinal_ex(enc_ctx.get(), NULL, &actual_out) <= 0)
|
||||
return on_error("EVP_EncryptFinal: ");
|
||||
if (EVP_CIPHER_CTX_ctrl(enc_ctx.get(), EVP_CTRL_GCM_GET_TAG, 16, dest+len) <= 0)
|
||||
return on_error("EVP_CTRL_GCM_GET_TAG: ");
|
||||
(*(uint64_t*)(hs_key.data() + AES_256_GCM_KEY_SIZE))++; // change IV
|
||||
return true;
|
||||
}
|
||||
|
||||
bool msgr_handshake_t::decrypt(const uint8_t *src, size_t & len, uint8_t* dest)
|
||||
{
|
||||
if (len <= 16) // only tag?!
|
||||
{
|
||||
len = 0;
|
||||
error = "Handshake decryption failed";
|
||||
state = MSGR_HS_ERROR;
|
||||
return false;
|
||||
}
|
||||
std::unique_ptr<EVP_CIPHER_CTX, decltype(&EVP_CIPHER_CTX_free)> dec_ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free);
|
||||
if (!ctx)
|
||||
return on_error("EVP_CIPHER_CTX_new: ");
|
||||
if (EVP_DecryptInit_ex(dec_ctx.get(), EVP_aes_256_gcm(), NULL, peer_hs_key.data(), peer_hs_key.data() + AES_256_GCM_KEY_SIZE) <= 0)
|
||||
return on_error("EVP_DecryptInit AES-256-GCM: ");
|
||||
int actual_out;
|
||||
len -= 16;
|
||||
if (EVP_DecryptUpdate(dec_ctx.get(), dest, &actual_out, src, len) <= 0)
|
||||
return on_error("EVP_DecryptUpdate: ");
|
||||
assert(actual_out == len);
|
||||
if (EVP_CIPHER_CTX_ctrl(dec_ctx.get(), EVP_CTRL_GCM_SET_TAG, 16, (void*)(src+len)) <= 0)
|
||||
return on_error("EVP_CTRL_GCM_SET_TAG: ");
|
||||
if (EVP_DecryptFinal_ex(dec_ctx.get(), NULL, &actual_out) <= 0)
|
||||
{
|
||||
error = "Handshake decryption failed";
|
||||
state = MSGR_HS_ERROR;
|
||||
return false;
|
||||
}
|
||||
(*(uint64_t*)(peer_hs_key.data() + AES_256_GCM_KEY_SIZE))++; // change IV
|
||||
return true;
|
||||
}
|
||||
|
||||
bool msgr_handshake_t::make_client_init()
|
||||
{
|
||||
uint8_t *key = NULL;
|
||||
size_t key_len = EVP_PKEY_get1_encoded_public_key(ec_key, &key);
|
||||
if (!key_len)
|
||||
return on_error("EVP_PKEY_get1_encoded_public_key: ");
|
||||
const size_t old_out_size = out_buf_size;
|
||||
out_buf_size += key_len + sizeof(msgr_handshake_hdr_t);
|
||||
out_buf = (uint8_t*)realloc_or_die(out_buf, out_buf_size);
|
||||
uint8_t *buf = out_buf + old_out_size;
|
||||
msgr_handshake_hdr_t *hdr = (msgr_handshake_hdr_t *)buf;
|
||||
hdr->msg_len = key_len + sizeof(msgr_handshake_hdr_t);
|
||||
hdr->magic = MSGR_HS_MAGIC;
|
||||
hdr->type = MSGR_HS_CLIENT_INIT;
|
||||
memcpy(buf + sizeof(msgr_handshake_hdr_t), key, key_len);
|
||||
copy_to(full_handshake, &hdr->type, sizeof(hdr->type));
|
||||
copy_to_with_len(full_handshake, key, key_len);
|
||||
OPENSSL_free(key);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool msgr_handshake_t::make_server_reply()
|
||||
{
|
||||
uint8_t *key = NULL;
|
||||
size_t key_len = EVP_PKEY_get1_encoded_public_key(ec_key, &key);
|
||||
if (!key_len)
|
||||
return on_error("EVP_PKEY_get1_encoded_public_key: ");
|
||||
// Append type, key and raw certificate to signed data and sign it
|
||||
msgr_handshake_hdr_t hdr = { .magic = MSGR_HS_MAGIC, .type = MSGR_HS_SERVER_REPLY };
|
||||
copy_to(full_handshake, &hdr.type, sizeof(hdr.type));
|
||||
copy_to_with_len(full_handshake, key, key_len);
|
||||
copy_to_with_len(full_handshake, ctx->my_cert_pem.data(), ctx->my_cert_pem.size());
|
||||
std::vector<uint8_t> signature;
|
||||
if (!sign(signature))
|
||||
{
|
||||
OPENSSL_free(key);
|
||||
return false;
|
||||
}
|
||||
// Encrypt certificate and signature
|
||||
std::vector<uint8_t> encrypt_data;
|
||||
copy_to_with_len(encrypt_data, ctx->my_cert_pem.data(), ctx->my_cert_pem.size());
|
||||
copy_to_with_len(encrypt_data, signature.data(), signature.size());
|
||||
encrypt_data.resize(encrypt_data.size()+16);
|
||||
if (!encrypt(encrypt_data.data(), encrypt_data.size()-16, encrypt_data.data()))
|
||||
{
|
||||
OPENSSL_free(key);
|
||||
return false;
|
||||
}
|
||||
// Construct message
|
||||
hdr.msg_len = sizeof(msgr_handshake_hdr_t) + 4 + key_len + encrypt_data.size();
|
||||
out_buf = (uint8_t*)realloc_or_die(out_buf, (out_buf_size += hdr.msg_len));
|
||||
uint8_t *cur = out_buf + out_buf_size - hdr.msg_len;
|
||||
copy_to_raw(cur, &hdr, sizeof(hdr));
|
||||
copy_to_raw(cur, &key_len, 4);
|
||||
copy_to_raw(cur, key, key_len);
|
||||
copy_to_raw(cur, encrypt_data.data(), encrypt_data.size());
|
||||
OPENSSL_free(key);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool msgr_handshake_t::make_client_reply()
|
||||
{
|
||||
// Append type and raw certificate to signed data and sign it
|
||||
msgr_handshake_hdr_t hdr = { .magic = MSGR_HS_MAGIC, .type = MSGR_HS_CLIENT_REPLY };
|
||||
copy_to(full_handshake, &hdr.type, sizeof(hdr.type));
|
||||
copy_to_with_len(full_handshake, ctx->my_cert_pem.data(), ctx->my_cert_pem.size());
|
||||
std::vector<uint8_t> signature;
|
||||
if (!sign(signature))
|
||||
return false;
|
||||
// Encrypt certificate and signature
|
||||
std::vector<uint8_t> encrypt_data;
|
||||
copy_to_with_len(encrypt_data, ctx->my_cert_pem.data(), ctx->my_cert_pem.size());
|
||||
copy_to_with_len(encrypt_data, signature.data(), signature.size());
|
||||
encrypt_data.resize(encrypt_data.size()+16);
|
||||
if (!encrypt(encrypt_data.data(), encrypt_data.size()-16, encrypt_data.data()))
|
||||
return false;
|
||||
// Construct message
|
||||
hdr.msg_len = sizeof(msgr_handshake_hdr_t) + encrypt_data.size();
|
||||
out_buf = (uint8_t*)realloc_or_die(out_buf, (out_buf_size += hdr.msg_len));
|
||||
uint8_t *cur = out_buf + out_buf_size - hdr.msg_len;
|
||||
copy_to_raw(cur, &hdr, sizeof(hdr));
|
||||
copy_to_raw(cur, encrypt_data.data(), encrypt_data.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool msgr_handshake_t::verify_peer(const uint8_t *peer_cert_pem, size_t peer_cert_len)
|
||||
{
|
||||
BIO *bio = BIO_new_mem_buf(peer_cert_pem, peer_cert_len);
|
||||
if (!bio)
|
||||
return on_error("BIO_new_mem_buf: ");
|
||||
peer_cert = PEM_read_bio_X509(bio, NULL, 0, NULL);
|
||||
BIO_free(bio);
|
||||
if (!peer_cert)
|
||||
{
|
||||
error = "Invalid peer certificate";
|
||||
state = MSGR_HS_ERROR;
|
||||
return false;
|
||||
}
|
||||
std::unique_ptr<X509_STORE_CTX, decltype(&X509_STORE_CTX_free)> ca_ctx(X509_STORE_CTX_new(), X509_STORE_CTX_free);
|
||||
if (!ca_ctx)
|
||||
return on_error("X509_STORE_CTX_new: ");
|
||||
if (X509_STORE_CTX_init(ca_ctx.get(), ctx->ca, peer_cert, NULL) <= 0)
|
||||
return on_error("X509_STORE_CTX_init: ");
|
||||
// Maybe use X509_VERIFY_PARAM_set_auth_level(X509_STORE_CTX_get0_param(ca_ctx.get()), 2) ?
|
||||
X509_STORE_CTX_set_default(ca_ctx.get(), is_server ? "ssl_client" : "ssl_server");
|
||||
if (X509_verify_cert(ca_ctx.get()) <= 0)
|
||||
{
|
||||
error = "Peer certificate verification failed: ";
|
||||
error += X509_verify_cert_error_string(X509_STORE_CTX_get_error(ca_ctx.get()));
|
||||
state = MSGR_HS_ERROR;
|
||||
return false;
|
||||
}
|
||||
peer_is_osd = (X509_verify(peer_cert, X509_get0_pubkey(ctx->osd_ca)) > 0);
|
||||
if (!is_server && !peer_is_osd)
|
||||
{
|
||||
error = "Peer is not an OSD";
|
||||
state = MSGR_HS_ERROR;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ssize_t msgr_handshake_t::start_msg(uint8_t* src, size_t len, uint32_t expected_type)
|
||||
{
|
||||
size_t orig_len = len;
|
||||
size_t to_buffer = (len < sizeof(msgr_handshake_hdr_t)-in_buf.size()
|
||||
? len : sizeof(msgr_handshake_hdr_t)-in_buf.size());
|
||||
in_buf.insert(in_buf.end(), src, src+to_buffer);
|
||||
len -= to_buffer;
|
||||
src += to_buffer;
|
||||
if (in_buf.size() < sizeof(msgr_handshake_hdr_t))
|
||||
return 0;
|
||||
cur_hdr = (msgr_handshake_hdr_t *)in_buf.data();
|
||||
if (cur_hdr->magic != MSGR_HS_MAGIC ||
|
||||
cur_hdr->type != expected_type ||
|
||||
cur_hdr->msg_len <= sizeof(msgr_handshake_hdr_t) ||
|
||||
cur_hdr->msg_len >= MSGR_HS_MAX_LEN)
|
||||
{
|
||||
error = "Invalid handshake packet magic, type or size";
|
||||
state = MSGR_HS_ERROR;
|
||||
return -1;
|
||||
}
|
||||
to_buffer = (len < cur_hdr->msg_len-in_buf.size()
|
||||
? len : cur_hdr->msg_len-in_buf.size());
|
||||
in_buf.insert(in_buf.end(), src, src+to_buffer);
|
||||
cur_hdr = (msgr_handshake_hdr_t *)in_buf.data();
|
||||
len -= to_buffer;
|
||||
src += to_buffer;
|
||||
if (in_buf.size() < cur_hdr->msg_len)
|
||||
return 0;
|
||||
cur_left = cur_hdr->msg_len - sizeof(msgr_handshake_hdr_t);
|
||||
cur_buf = in_buf.data() + sizeof(msgr_handshake_hdr_t);
|
||||
return orig_len - len;
|
||||
}
|
||||
|
||||
bool msgr_handshake_t::read_with_len(const uint8_t* & dst, uint32_t & dst_len)
|
||||
{
|
||||
if (cur_left < 4)
|
||||
{
|
||||
error = "Handshake packet too short";
|
||||
state = MSGR_HS_ERROR;
|
||||
return false;
|
||||
}
|
||||
dst_len = *(uint32_t*)cur_buf;
|
||||
cur_buf += 4;
|
||||
cur_left -= 4;
|
||||
if (cur_left < dst_len)
|
||||
{
|
||||
error = "Handshake packet too short";
|
||||
state = MSGR_HS_ERROR;
|
||||
return false;
|
||||
}
|
||||
dst = cur_buf;
|
||||
cur_buf += dst_len;
|
||||
cur_left -= dst_len;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool msgr_handshake_t::handle_client_init()
|
||||
{
|
||||
// Derive shared secret and handshake keys
|
||||
if (!derive_hs_keys(cur_buf, cur_left))
|
||||
return false;
|
||||
// Add type and key to full_handshake
|
||||
copy_to(full_handshake, &cur_hdr->type, sizeof(cur_hdr->type));
|
||||
copy_to_with_len(full_handshake, cur_buf, cur_left);
|
||||
in_buf.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Decrypt and check peer certificate
|
||||
bool msgr_handshake_t::handle_peer_cert(const uint8_t *key, uint32_t key_len)
|
||||
{
|
||||
if (!decrypt(cur_buf, cur_left, cur_buf))
|
||||
return false;
|
||||
const uint8_t *peer_cert_pem = NULL;
|
||||
uint32_t peer_cert_len = 0;
|
||||
if (!read_with_len(peer_cert_pem, peer_cert_len))
|
||||
return false;
|
||||
// Parse and verify certificate
|
||||
if (!verify_peer(peer_cert_pem, peer_cert_len))
|
||||
return false;
|
||||
// Verify signature
|
||||
copy_to(full_handshake, &cur_hdr->type, sizeof(cur_hdr->type));
|
||||
if (key)
|
||||
copy_to_with_len(full_handshake, key, key_len);
|
||||
copy_to_with_len(full_handshake, peer_cert_pem, peer_cert_len);
|
||||
const uint8_t *signature = NULL;
|
||||
uint32_t signature_len = 0;
|
||||
if (!read_with_len(signature, signature_len))
|
||||
return false;
|
||||
if (!verify(signature, signature_len))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool msgr_handshake_t::handle_server_reply()
|
||||
{
|
||||
// Derive shared secret and handshake keys
|
||||
const uint8_t *key = NULL;
|
||||
uint32_t key_len = 0;
|
||||
if (!read_with_len(key, key_len))
|
||||
return false;
|
||||
if (!derive_hs_keys(key, key_len))
|
||||
return false;
|
||||
// Decrypt and check peer certificate
|
||||
if (!handle_peer_cert(key, key_len))
|
||||
return false;
|
||||
in_buf.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool msgr_handshake_t::handle_client_reply()
|
||||
{
|
||||
// Decrypt and check peer certificate
|
||||
if (!handle_peer_cert(NULL, 0))
|
||||
return false;
|
||||
in_buf.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
void msgr_handshake_t::complete()
|
||||
{
|
||||
state = MSGR_HS_DONE;
|
||||
hs_key.clear();
|
||||
peer_hs_key.clear();
|
||||
full_handshake.clear();
|
||||
}
|
||||
|
||||
ssize_t msgr_handshake_t::handle(uint8_t* in_buf, size_t in_size)
|
||||
{
|
||||
if (state == MSGR_HS_SERVER_INIT)
|
||||
{
|
||||
ssize_t r = start_msg(in_buf, in_size, MSGR_HS_CLIENT_INIT);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return in_size;
|
||||
if (!handle_client_init())
|
||||
return -1;
|
||||
// Send encrypted & signed response
|
||||
if (!make_server_reply())
|
||||
return -1;
|
||||
state = MSGR_HS_SERVER_REPLY;
|
||||
return r;
|
||||
}
|
||||
else if (state == MSGR_HS_CLIENT_INIT)
|
||||
{
|
||||
ssize_t r = start_msg(in_buf, in_size, MSGR_HS_SERVER_REPLY);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return in_size;
|
||||
if (!handle_server_reply())
|
||||
return -1;
|
||||
// Verification passed, send certificate to the server
|
||||
if (!make_client_reply())
|
||||
return -1;
|
||||
// Finished!
|
||||
complete();
|
||||
return r;
|
||||
}
|
||||
else if (state == MSGR_HS_SERVER_REPLY)
|
||||
{
|
||||
ssize_t r = start_msg(in_buf, in_size, MSGR_HS_CLIENT_REPLY);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return in_size;
|
||||
if (!handle_client_reply())
|
||||
return -1;
|
||||
// Verification passed
|
||||
// Finished!
|
||||
complete();
|
||||
return r;
|
||||
}
|
||||
else if (state == MSGR_HS_DONE)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else if (state != MSGR_HS_ERROR)
|
||||
{
|
||||
error = "Unexpected handshake state: "+std::to_string(state);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool msgr_handshake_t::done()
|
||||
{
|
||||
return (state == MSGR_HS_DONE);
|
||||
}
|
||||
|
||||
uint8_t *msgr_handshake_t::get_out()
|
||||
{
|
||||
return out_buf;
|
||||
}
|
||||
|
||||
size_t msgr_handshake_t::out_size()
|
||||
{
|
||||
return out_buf_size;
|
||||
}
|
||||
|
||||
void msgr_handshake_t::eat_out(size_t n)
|
||||
{
|
||||
if (n >= out_buf_size)
|
||||
{
|
||||
free(out_buf);
|
||||
out_buf = NULL;
|
||||
out_buf_size = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
memmove(out_buf, out_buf + n, out_buf_size - n);
|
||||
out_buf_size -= n;
|
||||
}
|
||||
}
|
||||
|
||||
void msgr_handshake_t::reset_out()
|
||||
{
|
||||
out_buf = NULL;
|
||||
out_buf_size = 0;
|
||||
}
|
||||
|
||||
msgr_handshake_result_t msgr_handshake_t::get_result()
|
||||
{
|
||||
if (state != MSGR_HS_DONE)
|
||||
return msgr_handshake_result_t{};
|
||||
X509_up_ref(peer_cert);
|
||||
return msgr_handshake_result_t{
|
||||
.peer_cert = peer_cert,
|
||||
.peer_is_osd = peer_is_osd,
|
||||
.shared_secret = shared_secret,
|
||||
};
|
||||
}
|
||||
|
||||
std::string msgr_handshake_t::get_error()
|
||||
{
|
||||
return error;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2026+
|
||||
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <openssl/types.h>
|
||||
|
||||
#define AES_256_GCM_KEY_SIZE 32
|
||||
#define AES_256_GCM_IV_SIZE 12
|
||||
#define AES_256_GCM_MAX_IV_CTR ((uint64_t)1 << 32)
|
||||
|
||||
// TLS 1.3-like handshake
|
||||
|
||||
// 1. Client->server: EC public key
|
||||
// 2. Server->client: EC public key, encrypted certificate and digital signature of the handshake
|
||||
// 3. Client->server: encrypted certificate and digital signature of the handshake
|
||||
|
||||
struct msgr_handshake_result_t
|
||||
{
|
||||
X509 *peer_cert = NULL;
|
||||
bool peer_is_osd = false;
|
||||
std::vector<uint8_t> shared_secret;
|
||||
};
|
||||
|
||||
class msgr_handshake_i
|
||||
{
|
||||
public:
|
||||
// Workflow: create -> init -> handle_msg -> get_result/get_error -> destruct
|
||||
virtual ~msgr_handshake_i() = default;
|
||||
virtual bool init(bool server_mode) = 0;
|
||||
virtual ssize_t handle(uint8_t* in_buf, size_t in_size) = 0;
|
||||
virtual bool done() = 0;
|
||||
virtual uint8_t *get_out() = 0;
|
||||
virtual size_t out_size() = 0;
|
||||
virtual void eat_out(size_t n) = 0;
|
||||
virtual void reset_out() = 0;
|
||||
virtual msgr_handshake_result_t get_result() = 0;
|
||||
virtual std::string get_error() = 0;
|
||||
};
|
||||
|
||||
class msgr_handshake_ctx_i
|
||||
{
|
||||
public:
|
||||
static msgr_handshake_ctx_i* create_ctx();
|
||||
virtual ~msgr_handshake_ctx_i() = default;
|
||||
virtual msgr_handshake_i* create() = 0;
|
||||
virtual bool init(const std::string & pem_cert, const std::string & pem_key,
|
||||
const std::string & pem_osd_ca, const std::string & pem_client_ca) = 0;
|
||||
virtual std::string get_error() = 0;
|
||||
virtual bool derive_kdf(const uint8_t* insecret, size_t insecret_len,
|
||||
const uint8_t* salt, size_t salt_len, const char *label, uint8_t *outsecret, size_t outsize) = 0;
|
||||
};
|
||||
+21
-1
@@ -3,6 +3,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <sys/uio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
@@ -16,6 +18,8 @@
|
||||
|
||||
#define OSD_OP_INLINE_BUF_COUNT 16
|
||||
|
||||
#define AES_256_XTS_KEY_SIZE 64
|
||||
|
||||
// Kind of a vector with small-list-optimisation
|
||||
struct osd_op_buf_list_t
|
||||
{
|
||||
@@ -152,6 +156,19 @@ struct blockstore_op_t;
|
||||
|
||||
struct osd_primary_op_data_t;
|
||||
|
||||
struct osd_op_enc_t
|
||||
{
|
||||
// Keys may contain more information in the future, like encryption algorithm and key ID
|
||||
// In this case, key_chain will become inode_key_t* with inode_key_t also being a structure
|
||||
// Currently all keys are required to be 512 bit (64 byte) long, for AES-256-XTS
|
||||
// Raw pointers are convenient for messenger code; external users may use shared_ptr aliasing
|
||||
// to implement complex freeing of osd_op_enc_t along with their external inode cache info
|
||||
uint8_t** key_chain = NULL;
|
||||
size_t chain_size = 0;
|
||||
uint32_t read_chain_bitmap_pos = 0;
|
||||
uint32_t bitmap_granularity = 0;
|
||||
};
|
||||
|
||||
struct __attribute__((visibility("default"))) osd_op_t
|
||||
{
|
||||
timespec tv_begin = { 0 }, tv_end = { 0 };
|
||||
@@ -166,8 +183,10 @@ struct __attribute__((visibility("default"))) osd_op_t
|
||||
void *bitmap = NULL;
|
||||
unsigned bitmap_len = 0;
|
||||
size_t bmp_data = 0;
|
||||
void *bitmap_buf = NULL;
|
||||
uint8_t *bitmap_buf = NULL;
|
||||
void *rmw_buf = NULL;
|
||||
std::shared_ptr<osd_op_enc_t> enc;
|
||||
uint64_t csum = 0; // network layer checksum
|
||||
osd_primary_op_data_t* op_data = NULL;
|
||||
std::function<void(osd_op_t*)> callback;
|
||||
|
||||
@@ -177,4 +196,5 @@ struct __attribute__((visibility("default"))) osd_op_t
|
||||
void cancel();
|
||||
|
||||
bool is_recovery_related();
|
||||
uint64_t calc_data_checksum();
|
||||
};
|
||||
|
||||
+19
-62
@@ -571,28 +571,6 @@ static void try_send_rdma_wr(osd_client_t *cl, ibv_sge *sge, int op_sge)
|
||||
cl->rdma_conn->cur_send++;
|
||||
}
|
||||
|
||||
static int try_send_rdma_copy(osd_client_t *cl, uint8_t *dst, int dst_len)
|
||||
{
|
||||
auto rc = cl->rdma_conn;
|
||||
int total_dst_len = dst_len;
|
||||
while (dst_len > 0 && rc->send_pos < cl->send_list.size())
|
||||
{
|
||||
iovec & iov = cl->send_list[rc->send_pos];
|
||||
uint32_t len = (uint32_t)(iov.iov_len-rc->send_buf_pos < dst_len
|
||||
? iov.iov_len-rc->send_buf_pos : dst_len);
|
||||
memcpy(dst, (uint8_t*)iov.iov_base+rc->send_buf_pos, len);
|
||||
dst += len;
|
||||
dst_len -= len;
|
||||
rc->send_buf_pos += len;
|
||||
if (rc->send_buf_pos >= iov.iov_len)
|
||||
{
|
||||
rc->send_pos++;
|
||||
rc->send_buf_pos = 0;
|
||||
}
|
||||
}
|
||||
return total_dst_len-dst_len;
|
||||
}
|
||||
|
||||
void osd_messenger_t::try_send_rdma(osd_client_t *cl)
|
||||
{
|
||||
auto rc = cl->rdma_conn;
|
||||
@@ -620,7 +598,12 @@ void osd_messenger_t::try_send_rdma(osd_client_t *cl)
|
||||
: rc->send_done_pos-rc->send_out_pos);
|
||||
if (dst_len > rc->max_msg)
|
||||
dst_len = rc->max_msg;
|
||||
copied = try_send_rdma_copy(cl, dst, dst_len);
|
||||
copied = copy_ops_to(cl, dst, dst_len);
|
||||
if (cl->io_error)
|
||||
{
|
||||
stop_client(cl->client_id);
|
||||
return;
|
||||
}
|
||||
if (copied > 0)
|
||||
{
|
||||
rc->send_out_pos += copied;
|
||||
@@ -636,6 +619,7 @@ void osd_messenger_t::try_send_rdma(osd_client_t *cl)
|
||||
};
|
||||
try_send_rdma_wr(cl, &sge, 1);
|
||||
rc->send_sizes.push_back(copied);
|
||||
cl->send_free_ops.push_back(NULL); // end marker
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -732,9 +716,6 @@ void osd_messenger_t::handle_rdma_events(msgr_rdma_context_t *rdma_context)
|
||||
}
|
||||
if (!is_send)
|
||||
{
|
||||
// Reset OSD ping state - client is obviously alive
|
||||
cl->ping_time_remaining = 0;
|
||||
cl->idle_time_remaining = osd_idle_timeout;
|
||||
rc->cur_recv--;
|
||||
if (!handle_read_buffer(cl, rc->recv_buffers[rc->next_recv_buf], wc[i].byte_len))
|
||||
{
|
||||
@@ -746,56 +727,32 @@ void osd_messenger_t::handle_rdma_events(msgr_rdma_context_t *rdma_context)
|
||||
else
|
||||
{
|
||||
rc->cur_send--;
|
||||
uint64_t sent_size = rc->send_sizes.at(0);
|
||||
rc->send_sizes.erase(rc->send_sizes.begin(), rc->send_sizes.begin()+1);
|
||||
// byte_len is not filled for send operations
|
||||
uint64_t sent_size = rc->send_sizes.front();
|
||||
rc->send_sizes.pop_front();
|
||||
rc->send_done_pos += sent_size;
|
||||
rc->send_out_full = false;
|
||||
if (rc->send_done_pos == rc->send_out_size)
|
||||
rc->send_done_pos = 0;
|
||||
assert(rc->send_done_pos < rc->send_out_size);
|
||||
int send_pos = 0, send_buf_pos = 0;
|
||||
while (sent_size > 0)
|
||||
while (osd_op_t *op = cl->send_free_ops.front())
|
||||
{
|
||||
if (sent_size >= cl->send_list.at(send_pos).iov_len)
|
||||
{
|
||||
sent_size -= cl->send_list[send_pos].iov_len;
|
||||
send_pos++;
|
||||
}
|
||||
if (!((size_t)op & 7))
|
||||
delete op;
|
||||
else
|
||||
{
|
||||
send_buf_pos = sent_size;
|
||||
sent_size = 0;
|
||||
}
|
||||
free((void*)((size_t)op & ~(size_t)7));
|
||||
cl->send_free_ops.pop_front();
|
||||
}
|
||||
assert(rc->send_pos >= send_pos);
|
||||
if (rc->send_pos == send_pos)
|
||||
cl->send_free_ops.pop_front();
|
||||
if ((cl->proto_csum_status & MSGR_CSUM_NEG) && !cl->write_op && !cl->write_ops.size())
|
||||
{
|
||||
rc->send_buf_pos -= send_buf_pos;
|
||||
}
|
||||
rc->send_pos -= send_pos;
|
||||
for (int i = 0; i < send_pos; i++)
|
||||
{
|
||||
if (cl->outbox[i].flags & MSGR_SENDP_FREE)
|
||||
{
|
||||
// Reply fully sent
|
||||
delete cl->outbox[i].op;
|
||||
}
|
||||
}
|
||||
if (send_pos > 0)
|
||||
{
|
||||
cl->send_list.erase(cl->send_list.begin(), cl->send_list.begin()+send_pos);
|
||||
cl->outbox.erase(cl->outbox.begin(), cl->outbox.begin()+send_pos);
|
||||
}
|
||||
if (send_buf_pos > 0)
|
||||
{
|
||||
cl->send_list[0].iov_base = (uint8_t*)cl->send_list[0].iov_base + send_buf_pos;
|
||||
cl->send_list[0].iov_len -= send_buf_pos;
|
||||
// Checksums negotiated, enable
|
||||
cl->proto_csum_status = cl->proto_csum_status & (~MSGR_CSUM_NEG);
|
||||
}
|
||||
try_send_rdma(cl);
|
||||
}
|
||||
}
|
||||
} while (event_count > 0);
|
||||
handle_immediate_ops();
|
||||
}
|
||||
|
||||
void osd_messenger_t::destroy_rdma_conn(msgr_rdma_connection_t *rdma_conn)
|
||||
|
||||
@@ -8,8 +8,11 @@
|
||||
#include <infiniband/verbs.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include "addr_util.h"
|
||||
|
||||
struct osd_op_t;
|
||||
|
||||
struct msgr_rdma_address_t
|
||||
{
|
||||
ibv_gid gid;
|
||||
@@ -72,11 +75,11 @@ struct msgr_rdma_connection_t
|
||||
int cur_send = 0, cur_recv = 0;
|
||||
int send_pos = 0, send_buf_pos = 0;
|
||||
int next_recv_buf = 0;
|
||||
std::vector<void*> recv_buffers;
|
||||
std::vector<uint8_t*> recv_buffers;
|
||||
msgr_rdma_buf_t recv_buf;
|
||||
std::vector<uint64_t> send_sizes;
|
||||
std::deque<uint64_t> send_sizes;
|
||||
msgr_rdma_buf_t send_out;
|
||||
int send_out_pos = 0, send_done_pos = 0, send_out_size = 0;
|
||||
size_t send_out_pos = 0, send_done_pos = 0, send_out_size = 0;
|
||||
bool send_out_full = false;
|
||||
|
||||
~msgr_rdma_connection_t();
|
||||
|
||||
@@ -19,6 +19,7 @@ struct rdmacm_connecting_t
|
||||
int tcp_port = 0;
|
||||
int timeout_ms = 0;
|
||||
int timeout_id = -1;
|
||||
bool is_incoming = false;
|
||||
msgr_rdma_context_t *rdma_context = NULL;
|
||||
};
|
||||
|
||||
@@ -292,6 +293,7 @@ void osd_messenger_t::rdmacm_accept(rdma_cm_event *ev)
|
||||
conn->client_id = next_client_id++;
|
||||
conn->parsed_addr = *(sockaddr_storage*)rdma_get_peer_addr(ev->id);
|
||||
conn->rdma_context = rdma_context;
|
||||
conn->is_incoming = true;
|
||||
rdmacm_set_conn_timeout(conn);
|
||||
rdmacm_connecting[ev->id] = conn;
|
||||
fprintf(stderr, "[OSD %ju] new client %ju: connection from %s via RDMA-CM\n", this->osd_num, conn->client_id,
|
||||
@@ -492,11 +494,13 @@ void osd_messenger_t::rdmacm_established(rdma_cm_event *ev)
|
||||
cl->peer_addr = conn->parsed_addr;
|
||||
cl->peer_port = conn->rdmacm_port;
|
||||
cl->client_id = conn->client_id;
|
||||
cl->is_incoming = conn->is_incoming;
|
||||
cl->peer_state = PEER_RDMA;
|
||||
cl->connect_timeout_id = -1;
|
||||
cl->osd_num = peer_osd;
|
||||
cl->in_buf = malloc_or_die(receive_buffer_size);
|
||||
cl->in_buf = (uint8_t*)malloc_or_die(receive_buffer_size);
|
||||
cl->rdma_conn = rc;
|
||||
init_tls_client(cl);
|
||||
clients[conn->client_id] = cl;
|
||||
if (conn->timeout_id >= 0)
|
||||
{
|
||||
|
||||
+756
-219
File diff suppressed because it is too large
Load Diff
+665
-131
@@ -8,6 +8,424 @@
|
||||
#include "messenger.h"
|
||||
#include "msgr_iothread.h"
|
||||
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/err.h>
|
||||
|
||||
#define WR_GCM 1
|
||||
#define WR_XTS 2
|
||||
#define WR_NO_CSUM 4
|
||||
|
||||
#define GCM_TMP_BUF_SIZE 4096
|
||||
|
||||
class msgr_op_writer_t
|
||||
{
|
||||
public:
|
||||
virtual bool write(uint8_t *src, size_t src_len, int flags = 0) = 0;
|
||||
virtual bool finish() = 0;
|
||||
};
|
||||
|
||||
class copy_op_writer_t: public msgr_op_writer_t
|
||||
{
|
||||
protected:
|
||||
osd_messenger_t* msgr;
|
||||
osd_client_t* cl;
|
||||
size_t from;
|
||||
|
||||
uint8_t *curbuf;
|
||||
size_t bufsize;
|
||||
size_t done;
|
||||
|
||||
public:
|
||||
copy_op_writer_t(osd_messenger_t* msgr, osd_client_t* cl, uint8_t *curbuf, size_t bufsize):
|
||||
msgr(msgr), cl(cl), from(cl->write_op_pos), curbuf(curbuf), bufsize(bufsize), done(0)
|
||||
{}
|
||||
|
||||
void reset()
|
||||
{
|
||||
from = cl->write_op_pos;
|
||||
}
|
||||
|
||||
bool write(uint8_t *src, size_t src_len, int flags = 0) override
|
||||
{
|
||||
if (from >= src_len)
|
||||
{
|
||||
from -= src_len;
|
||||
return true;
|
||||
}
|
||||
if (flags & WR_XTS)
|
||||
{
|
||||
msgr->op_encrypted_copy_buf(cl, curbuf, bufsize, src, src_len, from, done);
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t n = src_len-from;
|
||||
if (n > bufsize-done)
|
||||
n = bufsize-done;
|
||||
if (cl->write_csum_state && !(flags & WR_NO_CSUM))
|
||||
XXH3_64bits_update(cl->write_csum_state, src+from, n);
|
||||
memcpy(curbuf+done, src+from, n);
|
||||
done += n;
|
||||
cl->write_op_pos += n;
|
||||
from += n;
|
||||
}
|
||||
if (from < src_len)
|
||||
return false;
|
||||
from = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool finish() override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t get_done()
|
||||
{
|
||||
return done;
|
||||
}
|
||||
};
|
||||
|
||||
class gcm_op_writer_t: public msgr_op_writer_t
|
||||
{
|
||||
osd_messenger_t* msgr;
|
||||
osd_client_t* cl;
|
||||
size_t from;
|
||||
|
||||
uint8_t *curbuf;
|
||||
size_t bufsize;
|
||||
size_t done;
|
||||
|
||||
public:
|
||||
gcm_op_writer_t(osd_messenger_t* msgr, osd_client_t* cl, uint8_t *curbuf, size_t bufsize):
|
||||
msgr(msgr), cl(cl), from(cl->write_op_pos), curbuf(curbuf), bufsize(bufsize), done(0)
|
||||
{
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
from = cl->write_op_pos;
|
||||
init_ctx(msgr, cl);
|
||||
}
|
||||
|
||||
static void init_ctx(osd_messenger_t* msgr, osd_client_t *cl)
|
||||
{
|
||||
if (!cl->enc_ctx)
|
||||
{
|
||||
if (msgr->encrypt_gcm_pool.size())
|
||||
{
|
||||
cl->enc_ctx = msgr->encrypt_gcm_pool.back();
|
||||
msgr->encrypt_gcm_pool.pop_back();
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef WITH_ISAL_CRYPTO
|
||||
cl->enc_ctx = (isal_gcm_context_data*)malloc_or_die(sizeof(isal_gcm_context_data));
|
||||
#else
|
||||
cl->enc_ctx = EVP_CIPHER_CTX_new();
|
||||
assert(cl->enc_ctx);
|
||||
int r = EVP_EncryptInit_ex(cl->enc_ctx, EVP_aes_256_gcm(), NULL, NULL, NULL);
|
||||
if (r != 1)
|
||||
{
|
||||
fprintf(stderr, "EncryptInit error: ");
|
||||
ERR_print_errors_fp(stderr);
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
if (cl->my_iv_ctr >= AES_256_GCM_MAX_IV_CTR)
|
||||
{
|
||||
// Rotate key every 2^32 messages
|
||||
bool ok = msgr->derive_aes_keys(cl, true, false);
|
||||
assert(ok);
|
||||
}
|
||||
#ifdef WITH_ISAL_CRYPTO
|
||||
int r = isal_aes_gcm_init_256(&cl->my_key_isal, cl->enc_ctx, cl->my_key.data() + AES_256_GCM_KEY_SIZE, NULL, 0);
|
||||
if (r != 0)
|
||||
{
|
||||
fprintf(stderr, "isal_aes_gcm_init_256 error %d\n", r);
|
||||
abort();
|
||||
}
|
||||
#else
|
||||
int r = EVP_EncryptInit_ex(cl->enc_ctx, NULL, NULL, (uint8_t*)cl->my_key.data(), cl->my_key.data() + AES_256_GCM_KEY_SIZE);
|
||||
if (r != 1)
|
||||
{
|
||||
fprintf(stderr, "EncryptInit error: ");
|
||||
ERR_print_errors_fp(stderr);
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
// Increase IV
|
||||
cl->my_iv_ctr++;
|
||||
(*(uint64_t*)(cl->my_key.data() + AES_256_GCM_KEY_SIZE))++;
|
||||
}
|
||||
|
||||
static void free_ctx(osd_messenger_t* msgr, osd_client_t *cl)
|
||||
{
|
||||
if (msgr->encrypt_gcm_pool.size() < msgr->max_cipher_pool_size)
|
||||
msgr->encrypt_gcm_pool.push_back(cl->enc_ctx);
|
||||
else
|
||||
{
|
||||
#ifdef WITH_ISAL_CRYPTO
|
||||
free(cl->enc_ctx);
|
||||
#else
|
||||
EVP_CIPHER_CTX_free(cl->enc_ctx);
|
||||
#endif
|
||||
}
|
||||
cl->enc_ctx = NULL;
|
||||
}
|
||||
|
||||
bool write(uint8_t *src, size_t src_len, int flags) override
|
||||
{
|
||||
if (from >= src_len)
|
||||
{
|
||||
from -= src_len;
|
||||
return true;
|
||||
}
|
||||
if (flags & WR_XTS)
|
||||
{
|
||||
msgr->op_encrypted_copy_buf(cl, curbuf, bufsize, src, src_len, from, done);
|
||||
}
|
||||
else if (flags & WR_GCM)
|
||||
{
|
||||
size_t n = src_len-from;
|
||||
if (n > bufsize-done)
|
||||
n = bufsize-done;
|
||||
if (!n)
|
||||
return false;
|
||||
#ifdef WITH_ISAL_CRYPTO
|
||||
int r = isal_aes_gcm_enc_256_update(&cl->my_key_isal, cl->enc_ctx, curbuf+done, src+from, n);
|
||||
assert(!r);
|
||||
#else
|
||||
int actual_out;
|
||||
if (EVP_EncryptUpdate(cl->enc_ctx, curbuf+done, &actual_out, src+from, n) != 1)
|
||||
{
|
||||
fprintf(stderr, "EncryptUpdate error: ");
|
||||
ERR_print_errors_fp(stderr);
|
||||
abort();
|
||||
}
|
||||
assert(actual_out == n);
|
||||
#endif
|
||||
if (cl->write_csum_state && !(flags & WR_NO_CSUM))
|
||||
XXH3_64bits_update(cl->write_csum_state, src+from, n);
|
||||
done += n;
|
||||
cl->write_op_pos += n;
|
||||
from += n;
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t n = src_len-from;
|
||||
if (n > bufsize-done)
|
||||
n = bufsize-done;
|
||||
if (!n)
|
||||
return false;
|
||||
if (cl->write_csum_state && !(flags & WR_NO_CSUM))
|
||||
XXH3_64bits_update(cl->write_csum_state, src+from, n);
|
||||
memcpy(curbuf+done, src+from, n);
|
||||
done += n;
|
||||
cl->write_op_pos += n;
|
||||
from += n;
|
||||
}
|
||||
if (from < src_len)
|
||||
return false;
|
||||
from = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void write_tag_to(osd_messenger_t *msgr, osd_client_t *cl, uint8_t *dst)
|
||||
{
|
||||
#ifdef WITH_ISAL_CRYPTO
|
||||
int r = isal_aes_gcm_enc_256_finalize(&cl->my_key_isal, cl->enc_ctx, dst, 16);
|
||||
assert(!r);
|
||||
#else
|
||||
int actual_out = 0;
|
||||
int r = EVP_EncryptFinal_ex(cl->enc_ctx, NULL, &actual_out);
|
||||
if (r != 1)
|
||||
{
|
||||
fprintf(stderr, "EncryptFinal error: ");
|
||||
ERR_print_errors_fp(stderr);
|
||||
abort();
|
||||
}
|
||||
assert(actual_out == 0);
|
||||
r = EVP_CIPHER_CTX_ctrl(cl->enc_ctx, EVP_CTRL_GCM_GET_TAG, 16, dst);
|
||||
assert(r == 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool finish() override
|
||||
{
|
||||
// Tag is 16 bytes
|
||||
if (done >= bufsize)
|
||||
return false;
|
||||
if (bufsize-done < 16 || cl->enc_tag_size)
|
||||
{
|
||||
// No space for the full tag, but msgr_rdma expects us to always fill the whole buffer
|
||||
if (!cl->enc_tag_size)
|
||||
{
|
||||
write_tag_to(msgr, cl, cl->enc_tag);
|
||||
cl->enc_tag_size = 16;
|
||||
}
|
||||
size_t n = bufsize-done;
|
||||
if (n > cl->enc_tag_size)
|
||||
n = cl->enc_tag_size;
|
||||
memcpy(curbuf+done, cl->enc_tag+16-cl->enc_tag_size, n);
|
||||
done += n;
|
||||
cl->enc_tag_size -= n;
|
||||
if (cl->enc_tag_size > 0)
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The whole tag fits at once
|
||||
write_tag_to(msgr, cl, curbuf+done);
|
||||
done += 16;
|
||||
}
|
||||
free_ctx(msgr, cl);
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t get_done()
|
||||
{
|
||||
return done;
|
||||
}
|
||||
};
|
||||
|
||||
class get_op_writer_t: public msgr_op_writer_t
|
||||
{
|
||||
osd_messenger_t* msgr;
|
||||
osd_client_t* cl;
|
||||
size_t from;
|
||||
size_t done;
|
||||
size_t op_enc;
|
||||
size_t enc_size;
|
||||
size_t done_enc;
|
||||
uint8_t *enc_buf;
|
||||
|
||||
public:
|
||||
get_op_writer_t(osd_messenger_t* msgr, osd_client_t* cl, uint8_t*, size_t):
|
||||
msgr(msgr), cl(cl), from(cl->write_op_pos), done(0), enc_size(0), done_enc(0), enc_buf(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
op_enc = 0;
|
||||
from = cl->write_op_pos;
|
||||
if (cl->gcm_enabled)
|
||||
{
|
||||
gcm_op_writer_t::init_ctx(msgr, cl);
|
||||
}
|
||||
}
|
||||
|
||||
void extend_tmp(size_t n)
|
||||
{
|
||||
if (!enc_buf || done_enc + n > enc_size)
|
||||
{
|
||||
enc_size = n < GCM_TMP_BUF_SIZE ? GCM_TMP_BUF_SIZE : n;
|
||||
enc_buf = (uint8_t*)malloc_or_die(enc_size);
|
||||
done_enc = 0;
|
||||
assert(!((size_t)enc_buf & 7));
|
||||
cl->send_free_ops.push_back((osd_op_t*)((size_t)enc_buf | 1));
|
||||
}
|
||||
}
|
||||
|
||||
void send_tmp(size_t n)
|
||||
{
|
||||
if (cl->send_list.size() && cl->send_list.back().iov_base == (enc_buf + done_enc))
|
||||
cl->send_list.back().iov_len += n;
|
||||
else
|
||||
cl->send_list.push_back((iovec){ .iov_base = enc_buf + done_enc, .iov_len = n });
|
||||
done += n;
|
||||
done_enc += n;
|
||||
}
|
||||
|
||||
bool write(uint8_t *src, size_t src_len, int flags) override
|
||||
{
|
||||
if (from >= src_len)
|
||||
{
|
||||
// Skip
|
||||
from -= src_len;
|
||||
return true;
|
||||
}
|
||||
if (cl->send_list.size() >= IOV_MAX-1)
|
||||
{
|
||||
// Make sure tag always fits
|
||||
return false;
|
||||
}
|
||||
if (flags & WR_XTS)
|
||||
{
|
||||
// Allocate a temporary buffer and encrypt data to it
|
||||
if (!op_enc)
|
||||
{
|
||||
assert(cl->write_op->req.hdr.opcode == OSD_OP_WRITE);
|
||||
op_enc = cl->write_op->req.rw.len - from + (from % 16);
|
||||
assert(op_enc > 0);
|
||||
extend_tmp(op_enc);
|
||||
}
|
||||
size_t new_done = done_enc;
|
||||
msgr->op_encrypted_copy_buf(cl, enc_buf, enc_size, src, src_len, from, new_done);
|
||||
send_tmp(new_done-done_enc);
|
||||
assert(from == src_len);
|
||||
}
|
||||
else if ((flags & WR_GCM) && cl->gcm_enabled)
|
||||
{
|
||||
// Allocate a temporary buffer and encrypt data to it
|
||||
size_t n = src_len-from;
|
||||
extend_tmp(n);
|
||||
#ifdef WITH_ISAL_CRYPTO
|
||||
int r = isal_aes_gcm_enc_256_update(&cl->my_key_isal, cl->enc_ctx, enc_buf+done_enc, src+from, n);
|
||||
assert(!r);
|
||||
#else
|
||||
int actual_out;
|
||||
if (EVP_EncryptUpdate(cl->enc_ctx, enc_buf+done_enc, &actual_out, src+from, n) != 1)
|
||||
{
|
||||
fprintf(stderr, "EncryptUpdate error: ");
|
||||
ERR_print_errors_fp(stderr);
|
||||
abort();
|
||||
}
|
||||
assert(actual_out == n);
|
||||
#endif
|
||||
if (cl->write_csum_state && !(flags & WR_NO_CSUM))
|
||||
XXH3_64bits_update(cl->write_csum_state, src+from, n);
|
||||
send_tmp(n);
|
||||
cl->write_op_pos += n;
|
||||
from += n;
|
||||
if (from < src_len)
|
||||
return false;
|
||||
from = 0;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cl->write_csum_state && !(flags & WR_NO_CSUM))
|
||||
XXH3_64bits_update(cl->write_csum_state, src+from, src_len-from);
|
||||
cl->send_list.push_back((iovec){ src+from, src_len-from });
|
||||
done += src_len-from;
|
||||
cl->write_op_pos += src_len-from;
|
||||
}
|
||||
from = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool finish() override
|
||||
{
|
||||
if (cl->enc_ctx)
|
||||
{
|
||||
// Tag is 16 bytes
|
||||
extend_tmp(16);
|
||||
gcm_op_writer_t::write_tag_to(msgr, cl, enc_buf + done_enc);
|
||||
send_tmp(16);
|
||||
gcm_op_writer_t::free_ctx(msgr, cl);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t get_done()
|
||||
{
|
||||
return done;
|
||||
}
|
||||
};
|
||||
|
||||
void osd_messenger_t::outbox_push(osd_op_t *cur_op)
|
||||
{
|
||||
assert(cur_op->client_id);
|
||||
@@ -22,6 +440,7 @@ void osd_messenger_t::outbox_push(osd_op_t *cur_op)
|
||||
{
|
||||
clock_gettime(CLOCK_REALTIME, &cur_op->tv_begin);
|
||||
cur_op->req.hdr.id = ++cl->send_op_id;
|
||||
cl->sent_ops[cur_op->req.hdr.id] = cur_op;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -38,82 +457,9 @@ void osd_messenger_t::outbox_push(osd_op_t *cur_op)
|
||||
}
|
||||
// Can't be not found because client IDs are unique
|
||||
assert(found);
|
||||
}
|
||||
auto & to_send_list = cl->write_msg.msg_iovlen ? cl->next_send_list : cl->send_list;
|
||||
auto & to_outbox = cl->write_msg.msg_iovlen ? cl->next_outbox : cl->outbox;
|
||||
if (cur_op->op_type == OSD_OP_IN)
|
||||
{
|
||||
measure_exec(cur_op);
|
||||
to_send_list.push_back((iovec){ .iov_base = cur_op->reply.buf, .iov_len = OSD_PACKET_SIZE });
|
||||
}
|
||||
else
|
||||
{
|
||||
to_send_list.push_back((iovec){ .iov_base = cur_op->req.buf, .iov_len = OSD_PACKET_SIZE });
|
||||
cl->sent_ops[cur_op->req.hdr.id] = cur_op;
|
||||
}
|
||||
to_outbox.push_back((msgr_sendp_t){ .op = cur_op, .flags = MSGR_SENDP_HDR });
|
||||
// Bitmap
|
||||
if (cur_op->op_type == OSD_OP_IN &&
|
||||
cur_op->req.hdr.opcode == OSD_OP_SEC_READ &&
|
||||
cur_op->reply.sec_rw.attr_len > 0)
|
||||
{
|
||||
to_send_list.push_back((iovec){
|
||||
.iov_base = cur_op->bitmap,
|
||||
.iov_len = cur_op->reply.sec_rw.attr_len,
|
||||
});
|
||||
to_outbox.push_back((msgr_sendp_t){ .op = cur_op, .flags = 0 });
|
||||
}
|
||||
else if (cur_op->op_type == OSD_OP_OUT &&
|
||||
(cur_op->req.hdr.opcode == OSD_OP_SEC_WRITE || cur_op->req.hdr.opcode == OSD_OP_SEC_WRITE_STABLE) &&
|
||||
cur_op->req.sec_rw.attr_len > 0)
|
||||
{
|
||||
to_send_list.push_back((iovec){
|
||||
.iov_base = cur_op->bitmap,
|
||||
.iov_len = cur_op->req.sec_rw.attr_len,
|
||||
});
|
||||
to_outbox.push_back((msgr_sendp_t){ .op = cur_op, .flags = 0 });
|
||||
}
|
||||
// Operation data
|
||||
if ((cur_op->op_type == OSD_OP_IN
|
||||
? (cur_op->req.hdr.opcode == OSD_OP_READ ||
|
||||
cur_op->req.hdr.opcode == OSD_OP_SEC_READ ||
|
||||
cur_op->req.hdr.opcode == OSD_OP_SEC_LIST ||
|
||||
cur_op->req.hdr.opcode == OSD_OP_SHOW_CONFIG ||
|
||||
cur_op->req.hdr.opcode == OSD_OP_DESCRIBE)
|
||||
: (cur_op->req.hdr.opcode == OSD_OP_WRITE ||
|
||||
cur_op->req.hdr.opcode == OSD_OP_SEC_WRITE ||
|
||||
cur_op->req.hdr.opcode == OSD_OP_SEC_WRITE_STABLE ||
|
||||
cur_op->req.hdr.opcode == OSD_OP_SEC_STABILIZE ||
|
||||
cur_op->req.hdr.opcode == OSD_OP_SEC_ROLLBACK ||
|
||||
cur_op->req.hdr.opcode == OSD_OP_SHOW_CONFIG)) && cur_op->iov.count > 0)
|
||||
{
|
||||
for (int i = 0; i < cur_op->iov.count; i++)
|
||||
{
|
||||
if (cur_op->iov.buf[i].iov_len > 0)
|
||||
{
|
||||
assert(cur_op->iov.buf[i].iov_base);
|
||||
to_send_list.push_back(cur_op->iov.buf[i]);
|
||||
to_outbox.push_back((msgr_sendp_t){ .op = cur_op, .flags = 0 });
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cur_op->req.hdr.opcode == OSD_OP_SEC_READ_BMP)
|
||||
{
|
||||
if (cur_op->op_type == OSD_OP_IN && cur_op->reply.hdr.retval > 0)
|
||||
{
|
||||
to_send_list.push_back((iovec){ .iov_base = cur_op->buf, .iov_len = (size_t)cur_op->reply.hdr.retval });
|
||||
to_outbox.push_back((msgr_sendp_t){ .op = cur_op, .flags = 0 });
|
||||
}
|
||||
else if (cur_op->op_type == OSD_OP_OUT && cur_op->req.sec_read_bmp.len > 0)
|
||||
{
|
||||
to_send_list.push_back((iovec){ .iov_base = cur_op->buf, .iov_len = (size_t)cur_op->req.sec_read_bmp.len });
|
||||
to_outbox.push_back((msgr_sendp_t){ .op = cur_op, .flags = 0 });
|
||||
}
|
||||
}
|
||||
if (cur_op->op_type == OSD_OP_IN)
|
||||
{
|
||||
to_outbox[to_outbox.size()-1].flags |= MSGR_SENDP_FREE;
|
||||
}
|
||||
cl->write_ops.push_back(cur_op);
|
||||
#ifdef WITH_RDMA
|
||||
if (cl->peer_state == PEER_RDMA)
|
||||
{
|
||||
@@ -124,17 +470,17 @@ void osd_messenger_t::outbox_push(osd_op_t *cur_op)
|
||||
if (!ringloop)
|
||||
{
|
||||
// FIXME: It's worse because it doesn't allow batching
|
||||
while (cl->outbox.size())
|
||||
while (cl->write_op || cl->write_ops.size())
|
||||
{
|
||||
try_send(cl);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((cl->write_msg.msg_iovlen > 0 || !try_send(cl)) && (cl->write_state == 0))
|
||||
if (!try_send(cl) && cl->write_state == 0)
|
||||
{
|
||||
cl->write_state = CL_WRITE_READY;
|
||||
write_ready_clients.push_back(cur_op->client_id);
|
||||
write_ready_clients.push_back(cl->client_id);
|
||||
}
|
||||
ringloop->wakeup();
|
||||
}
|
||||
@@ -142,12 +488,49 @@ void osd_messenger_t::outbox_push(osd_op_t *cur_op)
|
||||
|
||||
bool osd_messenger_t::try_send(osd_client_t *cl)
|
||||
{
|
||||
if (!cl->send_list.size() || cl->write_msg.msg_iovlen > 0 || cl->peer_state == PEER_STOPPED || cl->peer_fd < 0)
|
||||
if (cl->peer_state == PEER_STOPPED || cl->peer_fd < 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (cl->write_msg.msg_iovlen > 0 || !ringloop->space_left() && !use_sync_send_recv)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
assert(cl->peer_state != PEER_RDMA);
|
||||
if (ringloop && !use_sync_send_recv)
|
||||
if (cl->hs)
|
||||
{
|
||||
// Send handshake message
|
||||
if (cl->hs->out_size())
|
||||
{
|
||||
uint8_t *out = cl->hs->get_out();
|
||||
cl->send_list.push_back((iovec){ .iov_base = out, .iov_len = cl->hs->out_size() });
|
||||
assert(!((size_t)out & 7));
|
||||
cl->send_free_ops.push_back((osd_op_t*)((size_t)out | 1));
|
||||
cl->hs->reset_out();
|
||||
}
|
||||
if (!cl->hs->out_size() && cl->hs->done())
|
||||
{
|
||||
delete cl->hs;
|
||||
cl->hs = NULL;
|
||||
goto copy_ops;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
copy_ops:
|
||||
copy_ops_to_with<get_op_writer_t>(cl, NULL, 0);
|
||||
}
|
||||
if (cl->io_error)
|
||||
{
|
||||
stop_client(cl->client_id);
|
||||
return true;
|
||||
}
|
||||
if (!cl->send_list.size())
|
||||
{
|
||||
cl->write_state = 0;
|
||||
return true;
|
||||
}
|
||||
if (!use_sync_send_recv)
|
||||
{
|
||||
auto iothread = iothreads.size() ? iothreads[cl->peer_fd % iothreads.size()] : NULL;
|
||||
io_uring_sqe sqe_local;
|
||||
@@ -158,21 +541,22 @@ bool osd_messenger_t::try_send(osd_client_t *cl)
|
||||
sqe_local = { .user_data = (uint64_t)&data_local };
|
||||
data_local = {};
|
||||
}
|
||||
if (!sqe)
|
||||
return false;
|
||||
assert(sqe);
|
||||
cl->send_list_size = 0;
|
||||
for (auto & iov: cl->send_list)
|
||||
{
|
||||
cl->send_list_size += iov.iov_len;
|
||||
}
|
||||
cl->write_msg.msg_iov = cl->send_list.data();
|
||||
cl->write_msg.msg_iovlen = cl->send_list.size() < IOV_MAX ? cl->send_list.size() : IOV_MAX;
|
||||
cl->refs++;
|
||||
ring_data_t* data = ((ring_data_t*)sqe->user_data);
|
||||
data->callback = [this, cl](ring_data_t *data) { handle_send(data->res, data->prev, data->more, cl); };
|
||||
bool use_zc = has_sendmsg_zc && min_zerocopy_send_size >= 0;
|
||||
if (use_zc && min_zerocopy_send_size > 0)
|
||||
if (use_zc && min_zerocopy_send_size > 0 &&
|
||||
cl->send_list_size/cl->write_msg.msg_iovlen < min_zerocopy_send_size)
|
||||
{
|
||||
size_t avg_size = 0;
|
||||
for (size_t i = 0; i < cl->write_msg.msg_iovlen; i++)
|
||||
avg_size += cl->write_msg.msg_iov[i].iov_len;
|
||||
if (avg_size/cl->write_msg.msg_iovlen < min_zerocopy_send_size)
|
||||
use_zc = false;
|
||||
use_zc = false;
|
||||
}
|
||||
if (use_zc)
|
||||
{
|
||||
@@ -203,6 +587,75 @@ bool osd_messenger_t::try_send(osd_client_t *cl)
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t osd_messenger_t::copy_ops_to(osd_client_t *cl, uint8_t *dst, size_t dst_len)
|
||||
{
|
||||
if (cl->gcm_enabled)
|
||||
{
|
||||
if (cl->hs)
|
||||
{
|
||||
// Send handshake message
|
||||
size_t n = 0;
|
||||
if (cl->hs->out_size())
|
||||
{
|
||||
n = cl->hs->out_size() < dst_len ? cl->hs->out_size() : dst_len;
|
||||
memcpy(dst, cl->hs->get_out(), n);
|
||||
cl->hs->eat_out(n);
|
||||
}
|
||||
if (!cl->hs->out_size() && cl->hs->done())
|
||||
{
|
||||
delete cl->hs;
|
||||
cl->hs = NULL;
|
||||
n += copy_ops_to_with<gcm_op_writer_t>(cl, dst+n, dst_len-n);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
return copy_ops_to_with<gcm_op_writer_t>(cl, dst, dst_len);
|
||||
}
|
||||
return copy_ops_to_with<copy_op_writer_t>(cl, dst, dst_len);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
size_t osd_messenger_t::copy_ops_to_with(osd_client_t *cl, uint8_t *dst, size_t dst_len)
|
||||
{
|
||||
T wr(this, cl, dst, dst_len);
|
||||
while (cl->write_op || cl->write_ops.size())
|
||||
{
|
||||
if (!cl->write_op)
|
||||
{
|
||||
wr.reset();
|
||||
next_write_op(cl);
|
||||
}
|
||||
osd_op_t *op = cl->write_op;
|
||||
if (!op_write_to(cl, wr))
|
||||
{
|
||||
if (cl->io_error)
|
||||
return 0;
|
||||
break;
|
||||
}
|
||||
if (!cl->write_op && op->op_type == OSD_OP_IN)
|
||||
{
|
||||
// this is a reply, free the op after sending it
|
||||
cl->send_free_ops.push_back(op);
|
||||
}
|
||||
}
|
||||
return wr.get_done();
|
||||
}
|
||||
|
||||
void osd_messenger_t::next_write_op(osd_client_t *cl)
|
||||
{
|
||||
cl->write_op = cl->write_ops.front();
|
||||
cl->write_ops.pop_front();
|
||||
if (cl->proto_csum_status == MSGR_CSUM_FULL || cl->proto_csum_status == MSGR_CSUM_PAYLOAD)
|
||||
{
|
||||
if (!cl->write_csum_state)
|
||||
cl->write_csum_state = XXH3_createState();
|
||||
if (cl->my_key.size() == AES_256_GCM_KEY_SIZE + AES_256_GCM_IV_SIZE + XXH_SECRET_DEFAULT_SIZE)
|
||||
XXH3_64bits_reset_withSecret(cl->write_csum_state, cl->my_key.data() + AES_256_GCM_KEY_SIZE + AES_256_GCM_IV_SIZE, XXH_SECRET_DEFAULT_SIZE);
|
||||
else
|
||||
XXH3_64bits_reset(cl->write_csum_state);
|
||||
}
|
||||
}
|
||||
|
||||
void osd_messenger_t::send_replies()
|
||||
{
|
||||
for (int i = 0; i < write_ready_clients.size(); i++)
|
||||
@@ -223,6 +676,7 @@ void osd_messenger_t::handle_send(int result, bool prev, bool more, osd_client_t
|
||||
if (!prev)
|
||||
{
|
||||
cl->write_msg.msg_iovlen = 0;
|
||||
cl->send_list.clear();
|
||||
}
|
||||
if (!more)
|
||||
{
|
||||
@@ -250,62 +704,45 @@ void osd_messenger_t::handle_send(int result, bool prev, bool more, osd_client_t
|
||||
// Second notification - only free a batch of postponed ops
|
||||
int i = 0;
|
||||
for (; i < cl->zc_free_list.size() && cl->zc_free_list[i]; i++)
|
||||
delete cl->zc_free_list[i];
|
||||
{
|
||||
if (!((size_t)cl->zc_free_list[i] & 7))
|
||||
delete cl->zc_free_list[i];
|
||||
else
|
||||
free((void*)((size_t)cl->zc_free_list[i] & ~(size_t)7));
|
||||
}
|
||||
if (i > 0)
|
||||
cl->zc_free_list.erase(cl->zc_free_list.begin(), cl->zc_free_list.begin()+i+1);
|
||||
return;
|
||||
}
|
||||
int done = 0;
|
||||
while (result > 0 && done < cl->send_list.size())
|
||||
if (cl->send_list_size > result)
|
||||
{
|
||||
iovec & iov = cl->send_list[done];
|
||||
if (iov.iov_len <= result)
|
||||
{
|
||||
if (cl->outbox[done].flags & MSGR_SENDP_FREE)
|
||||
{
|
||||
// Reply fully sent
|
||||
if (more)
|
||||
cl->zc_free_list.push_back(cl->outbox[done].op);
|
||||
else
|
||||
delete cl->outbox[done].op;
|
||||
}
|
||||
result -= iov.iov_len;
|
||||
done++;
|
||||
}
|
||||
fprintf(stderr, "Client %ju socket write error: expected to send "
|
||||
"%zu bytes with MSG_WAITALL but sent %u. Disconnecting client\n", cl->client_id, cl->send_list_size, result);
|
||||
stop_client(cl->client_id);
|
||||
return;
|
||||
}
|
||||
for (auto op: cl->send_free_ops)
|
||||
{
|
||||
if (more)
|
||||
cl->zc_free_list.push_back(op);
|
||||
else if (!((size_t)op & 7))
|
||||
delete op;
|
||||
else
|
||||
{
|
||||
iov.iov_len -= result;
|
||||
iov.iov_base = (uint8_t*)iov.iov_base + result;
|
||||
break;
|
||||
}
|
||||
free((void*)((size_t)op & ~(size_t)7));
|
||||
}
|
||||
if (more)
|
||||
{
|
||||
int expected = cl->send_list.size() < IOV_MAX ? cl->send_list.size() : IOV_MAX;
|
||||
if (done != expected)
|
||||
{
|
||||
fprintf(stderr, "Client %ju socket write error: expected to send "
|
||||
"%d iovecs with MSG_WAITALL but sent %d. Disconnecting client\n", cl->client_id, expected, done);
|
||||
stop_client(cl->client_id);
|
||||
return;
|
||||
}
|
||||
cl->zc_free_list.push_back(NULL); // end marker
|
||||
}
|
||||
if (done > 0)
|
||||
cl->send_free_ops.clear();
|
||||
cl->write_state = 0;
|
||||
if (cl->write_op || cl->write_ops.size())
|
||||
cl->write_state = CL_WRITE_READY;
|
||||
if ((cl->proto_csum_status & MSGR_CSUM_NEG) && !cl->write_op && !cl->write_ops.size())
|
||||
{
|
||||
cl->send_list.erase(cl->send_list.begin(), cl->send_list.begin()+done);
|
||||
cl->outbox.erase(cl->outbox.begin(), cl->outbox.begin()+done);
|
||||
// Checksums negotiated, enable
|
||||
cl->proto_csum_status = cl->proto_csum_status & (~MSGR_CSUM_NEG);
|
||||
}
|
||||
if (cl->next_send_list.size())
|
||||
{
|
||||
cl->send_list.insert(cl->send_list.end(), cl->next_send_list.begin(), cl->next_send_list.end());
|
||||
cl->outbox.insert(cl->outbox.end(), cl->next_outbox.begin(), cl->next_outbox.end());
|
||||
cl->next_send_list.clear();
|
||||
cl->next_outbox.clear();
|
||||
}
|
||||
cl->write_state = cl->outbox.size() > 0 ? CL_WRITE_READY : 0;
|
||||
#ifdef WITH_RDMA
|
||||
if (cl->rdma_conn && !cl->outbox.size() && cl->peer_state == PEER_RDMA_CONNECTING)
|
||||
if (cl->rdma_conn && !cl->write_op && !cl->write_ops.size() && cl->peer_state == PEER_RDMA_CONNECTING)
|
||||
{
|
||||
// FIXME: Ignore pings during RDMA state transition
|
||||
if (log_level > 0)
|
||||
@@ -323,3 +760,100 @@ void osd_messenger_t::handle_send(int result, bool prev, bool more, osd_client_t
|
||||
write_ready_clients.push_back(cl->client_id);
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool op_has_data_for_ssl(osd_op_t *op)
|
||||
{
|
||||
return (op->op_type == OSD_OP_IN
|
||||
? (op->req.hdr.opcode == OSD_OP_SEC_LIST ||
|
||||
op->req.hdr.opcode == OSD_OP_SHOW_CONFIG ||
|
||||
op->req.hdr.opcode == OSD_OP_DESCRIBE)
|
||||
: (op->req.hdr.opcode == OSD_OP_SEC_STABILIZE ||
|
||||
op->req.hdr.opcode == OSD_OP_SEC_ROLLBACK ||
|
||||
op->req.hdr.opcode == OSD_OP_SHOW_CONFIG)) && op->iov.count > 0;
|
||||
}
|
||||
|
||||
static inline bool op_has_data_for_nonssl(osd_op_t *op)
|
||||
{
|
||||
return (op->op_type == OSD_OP_IN
|
||||
? (op->req.hdr.opcode == OSD_OP_READ ||
|
||||
op->req.hdr.opcode == OSD_OP_SEC_READ)
|
||||
: (op->req.hdr.opcode == OSD_OP_WRITE ||
|
||||
op->req.hdr.opcode == OSD_OP_SEC_WRITE ||
|
||||
op->req.hdr.opcode == OSD_OP_SEC_WRITE_STABLE)) && op->iov.count > 0;
|
||||
}
|
||||
|
||||
bool osd_messenger_t::op_write_to(osd_client_t *cl, msgr_op_writer_t & wr)
|
||||
{
|
||||
osd_op_t *op = cl->write_op;
|
||||
// Header
|
||||
if (!wr.write((op->op_type == OSD_OP_IN ? op->reply.buf : op->req.buf), OSD_PACKET_SIZE,
|
||||
WR_GCM | (cl->proto_csum_status == MSGR_CSUM_PAYLOAD ? WR_NO_CSUM : 0)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// Bitmap
|
||||
if (op->op_type == OSD_OP_IN)
|
||||
{
|
||||
if (op->req.hdr.opcode == OSD_OP_SEC_READ && op->reply.sec_rw.attr_len > 0)
|
||||
{
|
||||
if (!wr.write((uint8_t*)op->bitmap, op->reply.sec_rw.attr_len, WR_GCM))
|
||||
return false;
|
||||
}
|
||||
else if (op->req.hdr.opcode == OSD_OP_SEC_READ_BMP && op->reply.hdr.retval > 0)
|
||||
{
|
||||
if (!wr.write((uint8_t*)op->buf, (size_t)op->reply.hdr.retval, WR_GCM))
|
||||
return false;
|
||||
}
|
||||
else if (op->req.hdr.opcode == OSD_OP_READ && op->reply.rw.bitmap_len > 0)
|
||||
{
|
||||
if (!wr.write((uint8_t*)op->bitmap, op->reply.rw.bitmap_len, WR_GCM))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (op->op_type == OSD_OP_OUT)
|
||||
{
|
||||
if ((op->req.hdr.opcode == OSD_OP_SEC_WRITE || op->req.hdr.opcode == OSD_OP_SEC_WRITE_STABLE) &&
|
||||
op->req.sec_rw.attr_len > 0)
|
||||
{
|
||||
if (!wr.write((uint8_t*)op->bitmap, op->req.sec_rw.attr_len, WR_GCM))
|
||||
return false;
|
||||
}
|
||||
else if (op->req.hdr.opcode == OSD_OP_SEC_READ_BMP && op->req.sec_read_bmp.len > 0)
|
||||
{
|
||||
if (!wr.write((uint8_t*)op->buf, (size_t)op->req.sec_read_bmp.len, WR_GCM))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Operation data
|
||||
if (op_has_data_for_ssl(op))
|
||||
{
|
||||
for (int i = 0; i < cl->write_op->iov.count; i++)
|
||||
{
|
||||
auto & iov = cl->write_op->iov.buf[i];
|
||||
if (!wr.write((uint8_t*)iov.iov_base, iov.iov_len, WR_GCM))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (op_has_data_for_nonssl(op))
|
||||
{
|
||||
for (int i = 0; i < cl->write_op->iov.count; i++)
|
||||
{
|
||||
auto & iov = cl->write_op->iov.buf[i];
|
||||
if (!wr.write((uint8_t*)iov.iov_base, iov.iov_len, (op->enc ? WR_XTS : 0) | (cl->proto_csum_status == MSGR_CSUM_GCM ? WR_GCM : 0)))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (cl->proto_csum_status == MSGR_CSUM_FULL ||
|
||||
cl->proto_csum_status == MSGR_CSUM_PAYLOAD && cl->write_op_pos > OSD_PACKET_SIZE)
|
||||
{
|
||||
cl->write_op->csum = XXH3_64bits_digest(cl->write_csum_state);
|
||||
if (!wr.write((uint8_t*)&cl->write_op->csum, 8, WR_GCM|WR_NO_CSUM))
|
||||
return false;
|
||||
}
|
||||
if (!wr.finish())
|
||||
return false;
|
||||
op_encrypt_free(cl);
|
||||
cl->write_op = NULL;
|
||||
cl->write_op_pos = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
#include <assert.h>
|
||||
|
||||
#include "messenger.h"
|
||||
#include "../util/xxh_x86dispatch.h"
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/pem.h>
|
||||
|
||||
void osd_client_t::cancel_ops()
|
||||
{
|
||||
@@ -76,6 +79,22 @@ void osd_messenger_t::stop_client(uint64_t client_id, bool force_delete)
|
||||
fprintf(stderr, "[OSD %ju] Stopping client %ju (regular client)\n", osd_num, client_id);
|
||||
}
|
||||
}
|
||||
if (cl->xts_enc_ctx)
|
||||
{
|
||||
if (encrypt_xts_pool.size() > max_cipher_pool_size)
|
||||
destroy_aes_xts_encrypt(cl->xts_enc_ctx);
|
||||
else
|
||||
encrypt_xts_pool.push_back(cl->xts_enc_ctx);
|
||||
cl->xts_enc_ctx = NULL;
|
||||
}
|
||||
if (cl->xts_dec_ctx)
|
||||
{
|
||||
if (decrypt_xts_pool.size() > max_cipher_pool_size)
|
||||
destroy_aes_xts_decrypt(cl->xts_dec_ctx);
|
||||
else
|
||||
decrypt_xts_pool.push_back(cl->xts_dec_ctx);
|
||||
cl->xts_dec_ctx = NULL;
|
||||
}
|
||||
// First set state to STOPPED so another stop_client() call doesn't try to free it again
|
||||
cl->refs++;
|
||||
int prev_state = cl->peer_state;
|
||||
@@ -179,11 +198,62 @@ osd_client_t::~osd_client_t()
|
||||
}
|
||||
// Cancel outbound ops
|
||||
cancel_ops();
|
||||
for (osd_op_t *op: send_free_ops)
|
||||
{
|
||||
if (op)
|
||||
{
|
||||
if (!((size_t)op & 7))
|
||||
delete op;
|
||||
else
|
||||
free((void*)((size_t)op & ~(size_t)7));
|
||||
}
|
||||
}
|
||||
for (osd_op_t *op: zc_free_list)
|
||||
{
|
||||
if (op)
|
||||
{
|
||||
delete op;
|
||||
if (!((size_t)op & 7))
|
||||
delete op;
|
||||
else
|
||||
free((void*)((size_t)op & ~(size_t)7));
|
||||
}
|
||||
}
|
||||
if (read_csum_state)
|
||||
{
|
||||
XXH3_freeState(read_csum_state);
|
||||
read_csum_state = NULL;
|
||||
}
|
||||
if (write_csum_state)
|
||||
{
|
||||
XXH3_freeState(write_csum_state);
|
||||
write_csum_state = NULL;
|
||||
}
|
||||
if (enc_ctx)
|
||||
{
|
||||
#ifdef WITH_ISAL_CRYPTO
|
||||
free(enc_ctx);
|
||||
#else
|
||||
EVP_CIPHER_CTX_free(enc_ctx);
|
||||
#endif
|
||||
enc_ctx = NULL;
|
||||
}
|
||||
if (dec_ctx)
|
||||
{
|
||||
#ifdef WITH_ISAL_CRYPTO
|
||||
free(dec_ctx);
|
||||
#else
|
||||
EVP_CIPHER_CTX_free(dec_ctx);
|
||||
#endif
|
||||
dec_ctx = NULL;
|
||||
}
|
||||
if (hs)
|
||||
{
|
||||
delete hs;
|
||||
hs = NULL;
|
||||
}
|
||||
if (hs_result.peer_cert)
|
||||
{
|
||||
X509_free(hs_result.peer_cert);
|
||||
hs_result.peer_cert = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
+17
-2
@@ -37,6 +37,7 @@
|
||||
|
||||
#define OSD_OP_RECOVERY_RELATED (uint32_t)1
|
||||
#define OSD_OP_IGNORE_PG_LOCK (uint32_t)2
|
||||
#define OSD_OP_RETURN_CHAIN (uint32_t)4
|
||||
|
||||
// Memory alignment for direct I/O (usually 512 bytes)
|
||||
#ifndef DIRECT_IO_ALIGNMENT
|
||||
@@ -228,13 +229,27 @@ struct __attribute__((__packed__)) osd_op_rw_t
|
||||
uint64_t offset;
|
||||
// length. 0 means to read all bitmaps of the specified range, but no data.
|
||||
uint32_t len;
|
||||
// flags (for future)
|
||||
// flags
|
||||
// OSD_OP_RETURN_CHAIN for chained reads: return parent number in chain for each block
|
||||
// read_chain size comes after bitmap, takes 0 bytes / 1 byte / 2 byte / 4 byte per each block,
|
||||
// depending on the number of parent inodes (0 parents = 0 bytes, up to 255 parents = 1 byte, etc)
|
||||
uint32_t flags;
|
||||
// inode metadata revision
|
||||
// inode metadata revision for chained reads
|
||||
uint64_t meta_revision;
|
||||
// object version for atomic "CAS" (compare-and-set) writes
|
||||
// writes and deletes fail with -EINTR if object version differs from (version-1)
|
||||
uint64_t version;
|
||||
|
||||
static inline size_t chain_info_bytes(size_t chain_size)
|
||||
{
|
||||
if (chain_size <= 1)
|
||||
return 0;
|
||||
if (chain_size <= 256)
|
||||
return 1;
|
||||
if (chain_size <= 65536)
|
||||
return 2;
|
||||
return 4;
|
||||
}
|
||||
};
|
||||
|
||||
struct __attribute__((__packed__)) osd_reply_rw_t
|
||||
|
||||
@@ -880,6 +880,25 @@ static int vitastor_refresh_limits(BlockDriverState *bs)
|
||||
// return 0;
|
||||
//}
|
||||
|
||||
// Move the running coroutine to the BlockDriverState's home AioContext.
|
||||
//
|
||||
// The block-coroutine-wrapper generator sets poll_state.ctx to
|
||||
// qemu_get_current_aio_context() in the sync wrappers (bdrv_flush(),
|
||||
// bdrv_pread() etc.). When bdrv_flush_all() runs under BQL from outside the
|
||||
// bs's iothread (e.g. on the migration thread inside do_vm_stop()), that is
|
||||
// the main AioContext, not the iothread that actually owns the bs. The
|
||||
// coroutine then runs on the wrong context while completions are delivered on
|
||||
// the iothread, and racing aio_co_schedule() vs. qemu_aio_coroutine_enter()
|
||||
// on the same coroutine triggers "Co-routine was already scheduled in
|
||||
// aio_co_schedule" and aborts the process (observed during live migration).
|
||||
//
|
||||
// aio_co_reschedule_self() is a no-op when we are already on the target ctx.
|
||||
#if QEMU_VERSION_MAJOR > 5 || QEMU_VERSION_MAJOR == 5 && QEMU_VERSION_MINOR >= 2
|
||||
#define vitastor_co_pin_to_bs_ctx(bs) aio_co_reschedule_self(bdrv_get_aio_context(bs))
|
||||
#else
|
||||
#define vitastor_co_pin_to_bs_ctx(bs) ((void)0)
|
||||
#endif
|
||||
|
||||
static void vitastor_co_init_task(BlockDriverState *bs, VitastorRPC *task)
|
||||
{
|
||||
*task = (VitastorRPC) {
|
||||
@@ -938,6 +957,7 @@ static int coroutine_fn vitastor_co_preadv(BlockDriverState *bs,
|
||||
{
|
||||
VitastorClient *client = bs->opaque;
|
||||
VitastorRPC task;
|
||||
vitastor_co_pin_to_bs_ctx(bs);
|
||||
vitastor_co_init_task(bs, &task);
|
||||
task.iov = iov;
|
||||
|
||||
@@ -966,6 +986,7 @@ static int coroutine_fn vitastor_co_pwritev(BlockDriverState *bs,
|
||||
{
|
||||
VitastorClient *client = bs->opaque;
|
||||
VitastorRPC task;
|
||||
vitastor_co_pin_to_bs_ctx(bs);
|
||||
vitastor_co_init_task(bs, &task);
|
||||
task.iov = iov;
|
||||
|
||||
@@ -1039,6 +1060,7 @@ static int coroutine_fn vitastor_co_block_status(BlockDriverState *bs,
|
||||
#endif
|
||||
VitastorRPC task;
|
||||
VitastorClient *client = bs->opaque;
|
||||
vitastor_co_pin_to_bs_ctx(bs);
|
||||
uint64_t inode = client->watch ? vitastor_c_inode_get_num(client->watch) : client->inode;
|
||||
uint8_t bit = 0;
|
||||
if (client->last_bitmap && client->last_bitmap_inode == inode &&
|
||||
@@ -1151,6 +1173,7 @@ static int coroutine_fn vitastor_co_flush(BlockDriverState *bs)
|
||||
{
|
||||
VitastorClient *client = bs->opaque;
|
||||
VitastorRPC task;
|
||||
vitastor_co_pin_to_bs_ctx(bs);
|
||||
vitastor_co_init_task(bs, &task);
|
||||
|
||||
qemu_mutex_lock(&client->mutex);
|
||||
@@ -1236,6 +1259,8 @@ static BlockDriver bdrv_vitastor = {
|
||||
#endif
|
||||
.bdrv_close = vitastor_close,
|
||||
|
||||
// Re-register fd handlers when the bs is moved to a different AioContext
|
||||
// (live migration, drain, iothread reassignment).
|
||||
.bdrv_detach_aio_context = vitastor_detach_aio_context,
|
||||
.bdrv_attach_aio_context = vitastor_attach_aio_context,
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
|
||||
|
||||
Name: Vitastor
|
||||
Description: Vitastor client library
|
||||
Version: 3.0.14
|
||||
Version: 3.0.15
|
||||
Libs: -L${libdir} -lvitastor_client
|
||||
Cflags: -I${includedir}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user