diff --git a/docs/deploy-rbd.md b/docs/deploy-rbd.md index cc5f3c6b9..91e8918b9 100644 --- a/docs/deploy-rbd.md +++ b/docs/deploy-rbd.md @@ -415,6 +415,38 @@ the AWS KMS is expected to contain: This Secret is expected to be created by the tenant/user in each namespace where Ceph-CSI is used to create encrypted rbd volumes. +#### Configuring KMIP KMS + +The Key Management Interoperability Protocol (KMIP) is an extensible +communication protocol that defines message formats for the manipulation +of cryptographic keys on a key management server. +Ceph-CSI can be configured to connect to various KMS servers using +[KMIP](https://en.wikipedia.org/wiki/Key_Management_Interoperability_Protocol) +for encrypting RBD volumes. + +There are a few settings that need to be included in the [KMS configuration +file](../examples/kms/vault/kms-config.yaml): + +1. `KMS_PROVIDER`: should be set to `kmip`. +1. `KMIP_ENDPOINT` KMIP endpoint address. +1. `KMIP_SECRET_NAME`(optional): name of the Kubernetes Secret which contains + the credentials for communicating with KMIP server, defaults to + `ceph-csi-kmip-credentials`. +1. `TLS_SERVER_NAME`(optional): The endpoint server name. Useful when the + KMIP endpoint does not have a DNS entry. +1. `READ_TIMEOUT`(optional): Network read timeout, in seconds. The default + value is 10. +1. `WRITE_TIMEOUT`(optional): Network write timeout, in seconds. The default + value is 10. + +The [Secret with credentials](../examples/kms/vault/kmip-credentials.yaml) for +the KMIP KMS is expected to contain: + +1. `CA_CERT`: CA certificate that will be used to connect to KMIP server. +1. `CLIENT_CERT`: Client certificate that will be used to connect to KMIP server. +1. `CLIENT_KEY`: Client key that will be used to connect to KMIP server. +1. `UNIQUE_IDENTIFIER`: Unique ID of the key to use for encrypting/decrypting. + ### Encryption prerequisites In order for encryption to work you need to make sure that `dm-crypt` kernel diff --git a/examples/kms/vault/csi-kms-connection-details.yaml b/examples/kms/vault/csi-kms-connection-details.yaml index 23e896a5b..66c1ad7aa 100644 --- a/examples/kms/vault/csi-kms-connection-details.yaml +++ b/examples/kms/vault/csi-kms-connection-details.yaml @@ -74,5 +74,14 @@ data: "encryptionKMSType": "aws-sts-metadata", "secretName": "ceph-csi-aws-credentials" } + kmip-test: |- + { + "KMS_PROVIDER": "kmip", + "KMIP_ENDPOINT": "kmip:5696", + "KMIP_SECRET_NAME": "ceph-csi-kmip-credentials", + "TLS_SERVER_NAME": "kmip.ciphertrustmanager.local", + "READ_TIMEOUT": 10, + "WRITE_TIMEOUT": 10 + } metadata: name: csi-kms-connection-details diff --git a/examples/kms/vault/kmip-credentials.yaml b/examples/kms/vault/kmip-credentials.yaml new file mode 100644 index 000000000..16eae301a --- /dev/null +++ b/examples/kms/vault/kmip-credentials.yaml @@ -0,0 +1,13 @@ +--- +# This is an example Kubernetes Secret that can be created in the Kubernetes +# Namespace where Ceph-CSI is deployed. The contents of this Secret will be +# used to connect to the KMS using KMIP. +apiVersion: v1 +kind: Secret +metadata: + name: ceph-csi-kmip-credentials +stringData: + CA_CERT: "" + CLIENT_CERT: "" + CLIENT_KEY: "" + UNIQUE_IDENTIFIER: "" diff --git a/examples/kms/vault/kms-config.yaml b/examples/kms/vault/kms-config.yaml index 01b3f3e37..59fb3547b 100644 --- a/examples/kms/vault/kms-config.yaml +++ b/examples/kms/vault/kms-config.yaml @@ -100,6 +100,14 @@ data: "aws-sts-metadata-test": { "encryptionKMSType": "aws-sts-metadata", "secretName": "ceph-csi-aws-credentials" + }, + "kmip-test": { + "KMS_PROVIDER": "kmip", + "KMIP_ENDPOINT": "kmip:5696", + "KMIP_SECRET_NAME": "ceph-csi-kmip-credentials", + "TLS_SERVER_NAME": "kmip.ciphertrustmanager.local", + "READ_TIMEOUT": 10, + "WRITE_TIMEOUT": 10 } } metadata: diff --git a/go.mod b/go.mod index 07ce3bd12..5ffedbe4a 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,9 @@ require ( github.com/container-storage-interface/spec v1.6.0 github.com/csi-addons/replication-lib-utils v0.2.0 github.com/csi-addons/spec v0.1.2-0.20211220115741-32fa508dadbe + github.com/gemalto/kmip-go v0.0.8-0.20220721195433-3fe83e2d3f26 github.com/golang/protobuf v1.5.2 + github.com/google/uuid v1.3.0 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 github.com/hashicorp/vault/api v1.7.2 @@ -25,7 +27,8 @@ require ( github.com/prometheus/client_golang v1.12.2 github.com/stretchr/testify v1.8.0 golang.org/x/crypto v0.0.0-20220214200702-86341886e292 - golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 + golang.org/x/net v0.0.0-20220225172249-27dd8689420f + golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886 google.golang.org/grpc v1.48.0 google.golang.org/protobuf v1.28.0 k8s.io/api v0.24.3 @@ -42,11 +45,11 @@ require ( sigs.k8s.io/controller-runtime v0.11.0-beta.0.0.20211208212546-f236f0345ad2 ) -require golang.org/x/net v0.0.0-20220225172249-27dd8689420f - require ( github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/ansel1/merry v1.6.2 // indirect + github.com/ansel1/merry/v2 v2.0.1 // indirect github.com/armon/go-metrics v0.3.9 // indirect github.com/armon/go-radix v1.0.0 // indirect github.com/aws/aws-sdk-go-v2 v1.16.11 // indirect @@ -65,6 +68,7 @@ require ( github.com/fatih/color v1.9.0 // indirect github.com/felixge/httpsnoop v1.0.1 // indirect github.com/fsnotify/fsnotify v1.5.1 // indirect + github.com/gemalto/flume v0.13.0 // indirect github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 // indirect github.com/go-logr/logr v1.2.3 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect @@ -77,7 +81,6 @@ require ( github.com/google/gnostic v0.5.7-v3refs // indirect github.com/google/go-cmp v0.5.8 // indirect github.com/google/gofuzz v1.1.0 // indirect - github.com/google/uuid v1.3.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect @@ -104,9 +107,10 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.6 // indirect - github.com/mattn/go-colorable v0.1.6 // indirect - github.com/mattn/go-isatty v0.0.12 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect + github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mitchellh/copystructure v1.0.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-testing-interface v1.0.0 // indirect @@ -142,13 +146,15 @@ require ( go.opentelemetry.io/otel/trace v0.20.0 // indirect go.opentelemetry.io/proto/otlp v0.7.0 // indirect go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.8.0 // indirect + go.uber.org/zap v1.21.0 // indirect golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368 // indirect + google.golang.org/genproto v0.0.0-20220208230804-65c12eb4c068 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/square/go-jose.v2 v2.5.1 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect diff --git a/go.sum b/go.sum index 4144678ad..a79e3edc2 100644 --- a/go.sum +++ b/go.sum @@ -115,6 +115,15 @@ github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk5 github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190412020505-60e2075261b6/go.mod h1:T9M45xf79ahXVelWoOBmH0y4aC1t5kXO5BxwyakgIGA= github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190620160927-9418d7b0cd0f/go.mod h1:myCDvQSzCW+wB1WAlocEru4wMGJxy+vlxHdhegi1CDQ= github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= +github.com/ansel1/merry v1.5.0/go.mod h1:wUy/yW0JX0ix9GYvUbciq+bi3jW/vlKPlbpI7qdZpOw= +github.com/ansel1/merry v1.5.1/go.mod h1:wUy/yW0JX0ix9GYvUbciq+bi3jW/vlKPlbpI7qdZpOw= +github.com/ansel1/merry v1.6.1/go.mod h1:ioJjPJ/IsjxH+cC0lpf5TmbKnbcGa9qTk0fDbeRfnGQ= +github.com/ansel1/merry v1.6.2 h1:0xr40haRrfVzmOH/JVOu7KOKGEI1c/7q5EmgTEbn+Ng= +github.com/ansel1/merry v1.6.2/go.mod h1:pAcMW+2uxIgpzEON021vMtFsrymREY6faJWiiz1QGVQ= +github.com/ansel1/merry/v2 v2.0.0-beta.10/go.mod h1:OUvUYh4KLVhf3+sR9Hk8QxCukijznkpheEd837b7vLg= +github.com/ansel1/merry/v2 v2.0.1 h1:WeiKZdslHPAPFYxTtgX7clC2Vh75NCoWs5OjCZbIA0A= +github.com/ansel1/merry/v2 v2.0.1/go.mod h1:dD5OhpiPrVkvgseRYd+xgYlx7s6ytU3v9BTTJlDA7FM= +github.com/ansel1/vespucci/v4 v4.1.1/go.mod h1:zzdrO4IgBfgcGMbGTk/qNGL8JPslmW3nPpcBHKReFYY= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210826220005-b48c857c3a0e/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= @@ -157,6 +166,7 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.16.13/go.mod h1:Ru3QVMLygVs/07UQ3YDu github.com/aws/smithy-go v1.12.1 h1:yQRC55aXN/y1W10HgwHle01DRuV9Dpf31iGkotjt3Ag= github.com/aws/smithy-go v1.12.1/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= +github.com/baum/kmip-go v0.0.0-20220714190649-7b37ecf92eb2/go.mod h1:5WlKRqL5dfI68V56W+4ZmlPSL+TSfqQrKJYI8CSJz+E= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -351,6 +361,10 @@ github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXt github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= github.com/gammazero/deque v0.0.0-20190130191400-2afb3858e9c7/go.mod h1:GeIq9qoE43YdGnDXURnmKTnGg15pQz4mYkXSTChbneI= github.com/gammazero/workerpool v0.0.0-20190406235159-88d534f22b56/go.mod h1:w9RqFVO2BM3xwWEcAB8Fwp0OviTBBEiRmSBDfbXnd3w= +github.com/gemalto/flume v0.13.0 h1:EEeQvAxyFys3BH8IxEU7ZpM6Kr1sYn20HuZq6dgyMR8= +github.com/gemalto/flume v0.13.0/go.mod h1:3iOEZiK/HD8SnFTqHCQoOHQKaHlBY0b6z55P8SLaOzk= +github.com/gemalto/kmip-go v0.0.8-0.20220721195433-3fe83e2d3f26 h1:AGbIx+qTKLkYrrxL6QuwjAR5MvbuX06uMHJFb8mG+ro= +github.com/gemalto/kmip-go v0.0.8-0.20220721195433-3fe83e2d3f26/go.mod h1:7bAnjuzri8yGoJMwngnAd0HdXMRDQU+l1Zaiz12Tr68= github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -358,6 +372,8 @@ github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ER github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-errors/errors v1.1.1 h1:ljK/pL5ltg3qoN+OtN6yCv9HWSfMwxSx90GJCZQxYNg= +github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -740,6 +756,8 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= +github.com/k0kubun/pp v2.3.0+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg= github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= github.com/kelseyhightower/envconfig v1.3.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= github.com/keybase/go-crypto v0.0.0-20190403132359-d65b6b94177f h1:Gsc9mVHLRqBjMgdQCghN9NObCcRncDqxJvBvEaIIQEo= @@ -792,21 +810,27 @@ github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ github.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11/go.mod h1:Ah2dBMoxZEqk118as2T4u4fjfXarE0pPnMJaArZQZsI= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-shellwords v1.0.5/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mholt/archiver v3.1.1+incompatible/go.mod h1:Dh2dOXnSdiLxRiPoVfIr/fI1TwETms9B8CTWfeh7ROU= github.com/michaelklishin/rabbit-hole v0.0.0-20191008194146-93d9988f0cd5/go.mod h1:+pmbihVqjC3GPdfWv1V2TnRSuVvwrWLKfEP/MZVB/Wc= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= @@ -1179,17 +1203,22 @@ go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= +go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= -go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= +go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= +go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= golang.org/x/arch v0.0.0-20180920145803-b19384d3c130/go.mod h1:cYlCBUl1MsqxdiKgmc4uh7TxZfWSFLOGSRR090WDxt8= golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -1440,6 +1469,7 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211029165221-6e7872819dc8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1447,8 +1477,9 @@ golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 h1:OH54vjqzRWmbJ62fjuhxy7AxFFgoHN0/DPc/UrL8cAs= golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886 h1:eJv7u3ksNXoLbGSKuv2s/SIO4tJVxc/A+MTpzxDgz/Q= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1544,6 +1575,7 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff/go.mod h1:YD9qOF0M9xpSpdWTBbzEl5e/RnCefISl8E5Noe10jFM= golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1638,6 +1670,7 @@ google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210224155714-063164c882e6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -1645,8 +1678,9 @@ google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaE google.golang.org/genproto v0.0.0-20210429181445-86c259c2b4ab/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368 h1:Et6SkiuvnBn+SgrSYXs/BrUpGB4mbdwt4R3vaPIlicA= google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220208230804-65c12eb4c068 h1:pwzFiZfBTH/GjBWz1BcDwMBaHBo8mZvpLa7eBKJpFAk= +google.golang.org/genproto v0.0.0-20220208230804-65c12eb4c068/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= @@ -1678,6 +1712,7 @@ google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQ google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= +google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.48.0 h1:rQOsyJ/8+ufEDJd/Gdsz7HG220Mh9HAhFHRGnIjda0w= google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= diff --git a/internal/kms/kmip.go b/internal/kms/kmip.go new file mode 100644 index 000000000..f4862691a --- /dev/null +++ b/internal/kms/kmip.go @@ -0,0 +1,527 @@ +/* +Copyright 2022 The Ceph-CSI Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kms + +import ( + "bufio" + "bytes" + "context" + "crypto/tls" + "crypto/x509" + "encoding/json" + "errors" + "fmt" + "io" + "time" + + "github.com/ceph/ceph-csi/internal/util/k8s" + + kmip "github.com/gemalto/kmip-go" + "github.com/gemalto/kmip-go/kmip14" + "github.com/gemalto/kmip-go/ttlv" + "github.com/google/uuid" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + kmsTypeKMIP = "kmip" + + // kmipDefaulfReadTimeout is the default read network timeout. + kmipDefaulfReadTimeout = 10 + + // kmipDefaultWriteTimeout is the default write network timeout. + kmipDefaultWriteTimeout = 10 + + // KMIP version. + protocolMajor = 1 + protocolMinor = 4 + + // nonceSize is required to generate nonce for encrypting DEK. + nonceSize = 16 + + // kmipDefaultSecretsName is the default name of the Kubernetes Secret + // that contains the credentials to access the KMIP server. The name of + // the Secret can be configured by setting the `KMIP_SECRET_NAME` + // option. + // + // #nosec:G101, value not credential, just references token. + kmipDefaultSecretsName = "ceph-csi-kmip-credentials" + + kmipEndpoint = "KMIP_ENDPOINT" + kmipTLSServerName = "TLS_SERVER_NAME" + kmipReadTimeOut = "READ_TIMEOUT" + kmipWriteTimeOut = "WRITE_TIMEOUT" + + // The following options are part of the Kubernetes Secrets. + // + // #nosec:G101, value not credential, just configuration keys. + kmipSecretNameKey = "KMIP_SECRET_NAME" + kmipCACert = "CA_CERT" + kmipCLientCert = "CLIENT_CERT" + kmipClientKey = "CLIENT_KEY" + kmipUniqueIdentifier = "UNIQUE_IDENTIFIER" +) + +var _ = RegisterProvider(Provider{ + UniqueID: kmsTypeKMIP, + Initializer: initKMIPKMS, +}) + +type kmipKMS struct { + // basic options to get the secret + secretName string + namespace string + + // standard KMIP configuration options + endpoint string + tlsConfig *tls.Config + uniqueIdentifier string + readTimeout uint8 + writeTimeout uint8 +} + +func initKMIPKMS(args ProviderInitArgs) (EncryptionKMS, error) { + kms := &kmipKMS{ + namespace: args.Namespace, + } + + // get secret name if set, else use default. + err := setConfigString(&kms.secretName, args.Config, kmipSecretNameKey) + if errors.Is(err, errConfigOptionInvalid) { + return nil, err + } else if errors.Is(err, errConfigOptionMissing) { + kms.secretName = kmipDefaultSecretsName + } + + err = setConfigString(&kms.endpoint, args.Config, kmipEndpoint) + if err != nil { + return nil, err + } + + // optional + serverName := "" + err = setConfigString(&serverName, args.Config, kmipTLSServerName) + if errors.Is(err, errConfigOptionInvalid) { + return nil, err + } + + // optional + timeout := kmipDefaulfReadTimeout + err = setConfigInt(&timeout, args.Config, kmipReadTimeOut) + if errors.Is(err, errConfigOptionInvalid) { + return nil, err + } + kms.readTimeout = uint8(timeout) + + // optional + timeout = kmipDefaultWriteTimeout + err = setConfigInt(&timeout, args.Config, kmipWriteTimeOut) + if errors.Is(err, errConfigOptionInvalid) { + return nil, err + } + kms.writeTimeout = uint8(timeout) + + // read the Kubernetes Secret with CA cert, client cert, client key + // & key unique identifier. + secrets, err := kms.getSecrets() + if err != nil { + return nil, fmt.Errorf("failed to get secrets: %w", err) + } + + caCert, found := secrets[kmipCACert] + if !found { + return nil, fmt.Errorf("%w: %s", errConfigOptionMissing, kmipCACert) + } + + clientCert, found := secrets[kmipCLientCert] + if !found { + return nil, fmt.Errorf("%w: %s", errConfigOptionMissing, kmipCLientCert) + } + + clientKey, found := secrets[kmipClientKey] + if !found { + return nil, fmt.Errorf("%w: %s", errConfigOptionMissing, kmipCLientCert) + } + + kms.uniqueIdentifier, found = secrets[kmipUniqueIdentifier] + if !found { + return nil, fmt.Errorf("%w: %s", errConfigOptionMissing, kmipUniqueIdentifier) + } + + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM([]byte(caCert)) + cert, err := tls.X509KeyPair([]byte(clientCert), []byte(clientKey)) + if err != nil { + return nil, fmt.Errorf("invalid X509 key pair: %w", err) + } + + kms.tlsConfig = &tls.Config{ + MinVersion: tls.VersionTLS12, + ServerName: serverName, + RootCAs: caCertPool, + Certificates: []tls.Certificate{cert}, + } + + return kms, nil +} + +// EncryptDEK uses the KMIP encrypt operation to encrypt the DEK. +func (kms *kmipKMS) EncryptDEK(_, plainDEK string) (string, error) { + conn, err := kms.connect() + if err != nil { + return "", err + } + defer conn.Close() + + emd := encryptedMetedataDEK{} + emd.Nonce, err = generateNonce(nonceSize) + if err != nil { + return "", fmt.Errorf("failed to generated nonce: %w", err) + } + + respMsg, decoder, uniqueBatchItemID, err := kms.send(conn, + kmip14.OperationEncrypt, + EncryptRequestPayload{ + UniqueIdentifier: kms.uniqueIdentifier, + Data: []byte(plainDEK), + CryptographicParameters: kmip.CryptographicParameters{ + PaddingMethod: kmip14.PaddingMethodPKCS5, + CryptographicAlgorithm: kmip14.CryptographicAlgorithmAES, + BlockCipherMode: kmip14.BlockCipherModeCBC, + }, + IVCounterNonce: emd.Nonce, + }) + if err != nil { + return "", err + } + + batchItem, err := kms.verifyResponse(respMsg, kmip14.OperationEncrypt, uniqueBatchItemID) + if err != nil { + return "", err + } + + ttlvPayload, ok := batchItem.ResponsePayload.(ttlv.TTLV) + if !ok { + return "", errors.New("failed to parse responsePayload") + } + + var encryptRespPayload EncryptResponsePayload + err = decoder.DecodeValue(&encryptRespPayload, ttlvPayload) + if err != nil { + return "", err + } + + emd.DEK = encryptRespPayload.Data + emdData, err := json.Marshal(&emd) + if err != nil { + return "", fmt.Errorf("failed to convert "+ + "encryptedMetedataDEK to JSON: %w", err) + } + + return string(emdData), nil +} + +// DecryptDEK uses the KMIP decrypt operation to decrypt the DEK. +func (kms *kmipKMS) DecryptDEK(_, encryptedDEK string) (string, error) { + conn, err := kms.connect() + if err != nil { + return "", err + } + defer conn.Close() + + emd := encryptedMetedataDEK{} + err = json.Unmarshal([]byte(encryptedDEK), &emd) + if err != nil { + return "", fmt.Errorf("failed to convert data to "+ + "encryptedMetedataDEK: %w", err) + } + + respMsg, decoder, uniqueBatchItemID, err := kms.send(conn, + kmip14.OperationDecrypt, + DecryptRequestPayload{ + UniqueIdentifier: kms.uniqueIdentifier, + Data: emd.DEK, + IVCounterNonce: emd.DEK, + CryptographicParameters: kmip.CryptographicParameters{ + PaddingMethod: kmip14.PaddingMethodPKCS5, + CryptographicAlgorithm: kmip14.CryptographicAlgorithmAES, + BlockCipherMode: kmip14.BlockCipherModeCBC, + }, + }) + if err != nil { + return "", err + } + + batchItem, err := kms.verifyResponse(respMsg, kmip14.OperationDecrypt, uniqueBatchItemID) + if err != nil { + return "", err + } + + ttlvPayload, ok := batchItem.ResponsePayload.(ttlv.TTLV) + if !ok { + return "", errors.New("failed to parse responsePayload") + } + + var decryptRespPayload DecryptRequestPayload + err = decoder.DecodeValue(&decryptRespPayload, ttlvPayload) + if err != nil { + return "", err + } + + return string(decryptRespPayload.Data), nil +} + +func (kms *kmipKMS) Destroy() { + // Nothing to do. +} + +func (kms *kmipKMS) RequiresDEKStore() DEKStoreType { + return DEKStoreMetadata +} + +// getSecrets returns required options from the Kubernetes Secret. +func (kms *kmipKMS) getSecrets() (map[string]string, error) { + c, err := k8s.NewK8sClient() + if err != nil { + return nil, fmt.Errorf("failed to connect to Kubernetes to "+ + "get Secret %s/%s: %w", kms.namespace, kms.secretName, err) + } + + secret, err := c.CoreV1().Secrets(kms.namespace).Get(context.TODO(), + kms.secretName, metav1.GetOptions{}) + if err != nil { + return nil, fmt.Errorf("failed to get Secret %s/%s: %w", + kms.namespace, kms.secretName, err) + } + + config := make(map[string]string) + for k, v := range secret.Data { + switch k { + case kmipClientKey, kmipCLientCert, kmipCACert, kmipUniqueIdentifier: + config[k] = string(v) + default: + return nil, fmt.Errorf("unsupported option for KMS "+ + "provider %q: %s", kmsTypeKMIP, k) + } + } + + return config, nil +} + +// connect to the kmip endpoint, perform TLS and KMIP handshakes. +func (kms *kmipKMS) connect() (*tls.Conn, error) { + conn, err := tls.Dial("tcp", kms.endpoint, kms.tlsConfig) + if err != nil { + return nil, fmt.Errorf("failed to dial kmip connection endpoint: %w", err) + } + if kms.readTimeout != 0 { + err = conn.SetReadDeadline(time.Now().Add(time.Second * time.Duration(kms.readTimeout))) + if err != nil { + return nil, fmt.Errorf("failed to set read deadline: %w", err) + } + } + if kms.writeTimeout != 0 { + err = conn.SetReadDeadline(time.Now().Add(time.Second * time.Duration(kms.writeTimeout))) + if err != nil { + return nil, fmt.Errorf("failed to set write deadline: %w", err) + } + } + defer func() { + if err != nil { + conn.Close() + } + }() + + err = conn.Handshake() + if err != nil { + return nil, fmt.Errorf("failed to perform connection handshake: %w", err) + } + + err = kms.discover(conn) + if err != nil { + return nil, err + } + + return conn, nil +} + +// discover performs KMIP discover operation. +// https://docs.oasis-open.org/kmip/spec/v1.4/kmip-spec-v1.4.html +// chapter 4.26. +func (kms *kmipKMS) discover(conn io.ReadWriter) error { + respMsg, decoder, uniqueBatchItemID, err := kms.send(conn, + kmip14.OperationDiscoverVersions, + kmip.DiscoverVersionsRequestPayload{ + ProtocolVersion: []kmip.ProtocolVersion{ + { + ProtocolVersionMajor: protocolMajor, + ProtocolVersionMinor: protocolMinor, + }, + }, + }) + if err != nil { + return err + } + + batchItem, err := kms.verifyResponse( + respMsg, + kmip14.OperationDiscoverVersions, + uniqueBatchItemID) + if err != nil { + return err + } + + ttlvPayload, ok := batchItem.ResponsePayload.(ttlv.TTLV) + if !ok { + return errors.New("failed to parse responsePayload") + } + + var respDiscoverVersionsPayload kmip.DiscoverVersionsResponsePayload + err = decoder.DecodeValue(&respDiscoverVersionsPayload, ttlvPayload) + if err != nil { + return err + } + + if len(respDiscoverVersionsPayload.ProtocolVersion) != 1 { + return fmt.Errorf("invalid len of discovered protocol versions %v expected 1", + len(respDiscoverVersionsPayload.ProtocolVersion)) + } + pv := respDiscoverVersionsPayload.ProtocolVersion[0] + if pv.ProtocolVersionMajor != protocolMajor || pv.ProtocolVersionMinor != protocolMinor { + return fmt.Errorf("invalid discovered protocol version %v.%v expected %v.%v", + pv.ProtocolVersionMajor, pv.ProtocolVersionMinor, protocolMajor, protocolMinor) + } + + return nil +} + +// send sends KMIP operation over tls connection, returns +// kmip response message, +// ttlv Decoder to decode message into desired format, +// batchItem ID, +// and error. +func (kms *kmipKMS) send( + conn io.ReadWriter, + operation kmip14.Operation, + payload interface{}, +) (*kmip.ResponseMessage, *ttlv.Decoder, []byte, error) { + biID := uuid.New() + + msg := kmip.RequestMessage{ + RequestHeader: kmip.RequestHeader{ + ProtocolVersion: kmip.ProtocolVersion{ + ProtocolVersionMajor: protocolMajor, + ProtocolVersionMinor: protocolMinor, + }, + BatchCount: 1, + }, + BatchItem: []kmip.RequestBatchItem{ + { + UniqueBatchItemID: biID[:], + Operation: operation, + RequestPayload: payload, + }, + }, + } + + req, err := ttlv.Marshal(msg) + if err != nil { + return nil, nil, nil, + fmt.Errorf("failed to ttlv marshal message: %w", err) + } + + _, err = conn.Write(req) + if err != nil { + return nil, nil, nil, + fmt.Errorf("failed to write request onto connection: %w", err) + } + + decoder := ttlv.NewDecoder(bufio.NewReader(conn)) + resp, err := decoder.NextTTLV() + if err != nil { + return nil, nil, nil, + fmt.Errorf("failed to read ttlv KMIP value: %w", err) + } + + var respMsg kmip.ResponseMessage + err = decoder.DecodeValue(&respMsg, resp) + if err != nil { + return nil, nil, nil, + fmt.Errorf("failed to decode response value: %w", err) + } + + return &respMsg, decoder, biID[:], nil +} + +// verifyResponse verifies the response success and return the batch item. +func (kms *kmipKMS) verifyResponse( + respMsg *kmip.ResponseMessage, + operation kmip14.Operation, + uniqueBatchItemID []byte, +) (*kmip.ResponseBatchItem, error) { + if respMsg.ResponseHeader.BatchCount != 1 { + return nil, fmt.Errorf("batch count %v should be 1", respMsg.ResponseHeader.BatchCount) + } + if len(respMsg.BatchItem) != 1 { + return nil, fmt.Errorf("batch Intems list len %v should be 1", + len(respMsg.BatchItem)) + } + batchItem := respMsg.BatchItem[0] + if operation != batchItem.Operation { + return nil, fmt.Errorf("unexpected operation, real %v expected %v", + batchItem.Operation, operation) + } + if !bytes.Equal(uniqueBatchItemID, batchItem.UniqueBatchItemID) { + return nil, fmt.Errorf("unexpected uniqueBatchItemID, real %v expected %v", + batchItem.UniqueBatchItemID, uniqueBatchItemID) + } + if kmip14.ResultStatusSuccess != batchItem.ResultStatus { + return nil, fmt.Errorf("unexpected result status %v expected success %v", + batchItem.ResultStatus, kmip14.ResultStatusSuccess) + } + + return &batchItem, nil +} + +// TODO: use the following structs from https://github.com/gemalto/kmip-go +// when https://github.com/ThalesGroup/kmip-go/issues/21 is resolved. +// refer: https://docs.oasis-open.org/kmip/spec/v1.4/kmip-spec-v1.4.html. +type EncryptRequestPayload struct { + UniqueIdentifier string + CryptographicParameters kmip.CryptographicParameters + Data []byte + IVCounterNonce []byte +} + +type EncryptResponsePayload struct { + UniqueIdentifier string + Data []byte + IVCounterNonce []byte +} + +type DecryptRequestPayload struct { + UniqueIdentifier string + CryptographicParameters kmip.CryptographicParameters + Data []byte + IVCounterNonce []byte +} + +type DecryptResponsePayload struct { + UniqueIdentifier string + Data []byte + IVCounterNonce []byte +} diff --git a/internal/kms/kmip_test.go b/internal/kms/kmip_test.go new file mode 100644 index 000000000..8a558ed4b --- /dev/null +++ b/internal/kms/kmip_test.go @@ -0,0 +1,29 @@ +/* +Copyright 2022 The Ceph-CSI Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kms + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestKMIPKMSRegistered(t *testing.T) { + t.Parallel() + _, ok := kmsManager.providers[kmsTypeKMIP] + assert.True(t, ok) +} diff --git a/internal/kms/kms_util.go b/internal/kms/kms_util.go new file mode 100644 index 000000000..6c8939f50 --- /dev/null +++ b/internal/kms/kms_util.go @@ -0,0 +1,43 @@ +/* +Copyright 2022 The Ceph-CSI Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kms + +import "fmt" + +// setConfigInt fetches a value from a configuration map and converts it to +// a integer. +// +// If the value is not available, *option is not adjusted and +// errConfigOptionMissing is returned. +// In case the value is available, but can not be converted to a string, +// errConfigOptionInvalid is returned. +func setConfigInt(option *int, config map[string]interface{}, key string) error { + value, ok := config[key] + if !ok { + return fmt.Errorf("%w: %s", errConfigOptionMissing, key) + } + + s, ok := value.(float64) + if !ok { + return fmt.Errorf("%w: expected float64 for %q, but got %T", + errConfigOptionInvalid, key, value) + } + + *option = int(s) + + return nil +} diff --git a/internal/kms/kms_util_test.go b/internal/kms/kms_util_test.go new file mode 100644 index 000000000..5682dded4 --- /dev/null +++ b/internal/kms/kms_util_test.go @@ -0,0 +1,88 @@ +/* +Copyright 2022 The Ceph-CSI Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kms + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSetConfigInt(t *testing.T) { + t.Parallel() + type args struct { + option *int + config map[string]interface{} + key string + } + option := 1 + tests := []struct { + name string + args args + err error + value int + }{ + { + name: "valid value", + args: args{ + option: &option, + config: map[string]interface{}{ + "a": 1.0, + }, + key: "a", + }, + err: nil, + value: 1, + }, + { + name: "invalid value", + args: args{ + option: &option, + config: map[string]interface{}{ + "a": "abc", + }, + key: "a", + }, + err: errConfigOptionInvalid, + value: 0, + }, + { + name: "missing value", + args: args{ + option: &option, + config: map[string]interface{}{}, + key: "a", + }, + err: errConfigOptionMissing, + value: 0, + }, + } + for _, tt := range tests { + currentTT := tt + t.Run(currentTT.name, func(t *testing.T) { + t.Parallel() + err := setConfigInt(currentTT.args.option, currentTT.args.config, currentTT.args.key) + if !errors.Is(err, currentTT.err) { + t.Errorf("setConfigInt() error = %v, wantErr %v", err, currentTT.err) + } + if err != nil { + assert.NotEqual(t, currentTT.value, currentTT.args.option) + } + }) + } +}