Merge pull request #120896 from aramase/aramase/f/kmsv2_pkcs11

[KMSv2] pkcs11 reference implementation using SoftHSM
This commit is contained in:
Kubernetes Prow Robot 2023-09-29 12:11:56 -07:00 committed by GitHub
commit 46728a8fa2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 239 additions and 701 deletions

View File

@ -26,7 +26,7 @@ source "${KUBE_ROOT}/hack/lib/init.sh"
MODULES=(
hack/tools
staging/src/k8s.io/code-generator/examples
staging/src/k8s.io/kms/internal/plugins/mock
staging/src/k8s.io/kms/internal/plugins/_mock
)
# Explicitly opt into go modules, even though we're inside a GOPATH directory

View File

@ -301,12 +301,9 @@
- baseImportPath: "./vendor/k8s.io/kms/"
allowedImports:
- k8s.io/api
- k8s.io/apimachinery
- k8s.io/client-go
- k8s.io/klog
- k8s.io/kms
- k8s.io/utils
- baseImportPath: "./vendor/k8s.io/endpointslice/"
allowedImports:

View File

@ -296,10 +296,6 @@ rules:
dependencies:
- repository: apimachinery
branch: master
- repository: api
branch: master
- repository: client-go
branch: master
source:
branch: master
dir: staging/src/k8s.io/kms

View File

@ -8,7 +8,6 @@ require (
github.com/gogo/protobuf v1.3.2
google.golang.org/grpc v1.54.0
k8s.io/apimachinery v0.0.0
k8s.io/client-go v0.0.0
k8s.io/klog/v2 v2.100.1
)
@ -18,15 +17,12 @@ require (
golang.org/x/net v0.13.0 // indirect
golang.org/x/sys v0.10.0 // indirect
golang.org/x/text v0.11.0 // indirect
golang.org/x/time v0.3.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect
google.golang.org/protobuf v1.31.0 // indirect
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
)
replace (
k8s.io/api => ../api
k8s.io/apimachinery => ../apimachinery
k8s.io/client-go => ../client-go
k8s.io/kms => ../kms
)

View File

@ -6,7 +6,6 @@ github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34=
github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w=
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
@ -20,11 +19,9 @@ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
@ -32,9 +29,6 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
@ -44,11 +38,8 @@ github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/onsi/ginkgo/v2 v2.9.4/go.mod h1:gCQYp2Q+kSoIj7ykSVb9nskRSsR6PUj4AiLywzIhbKM=
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
@ -69,7 +60,7 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY=
golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -83,7 +74,6 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=

View File

@ -1,96 +0,0 @@
/*
Copyright 2023 The Kubernetes 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 internal
import (
"context"
"crypto/aes"
"errors"
aestransformer "k8s.io/kms/pkg/encrypt/aes"
"k8s.io/kms/pkg/service"
"k8s.io/kms/pkg/value"
)
var _ service.Service = &mockAESRemoteService{}
const (
mockAnnotationKey = "version.encryption.remote.io"
)
type mockAESRemoteService struct {
keyID string
transformer value.Transformer
dataCtx value.DefaultContext
}
func (s *mockAESRemoteService) Encrypt(ctx context.Context, uid string, plaintext []byte) (*service.EncryptResponse, error) {
out, err := s.transformer.TransformToStorage(ctx, plaintext, s.dataCtx)
if err != nil {
return nil, err
}
return &service.EncryptResponse{
KeyID: s.keyID,
Ciphertext: out,
Annotations: map[string][]byte{
mockAnnotationKey: []byte("1"),
},
}, nil
}
func (s *mockAESRemoteService) Decrypt(ctx context.Context, uid string, req *service.DecryptRequest) ([]byte, error) {
if len(req.Annotations) != 1 {
return nil, errors.New("invalid annotations")
}
if v, ok := req.Annotations[mockAnnotationKey]; !ok || string(v) != "1" {
return nil, errors.New("invalid version in annotations")
}
if req.KeyID != s.keyID {
return nil, errors.New("invalid keyID")
}
from, _, err := s.transformer.TransformFromStorage(ctx, req.Ciphertext, s.dataCtx)
if err != nil {
return nil, err
}
return from, nil
}
func (s *mockAESRemoteService) Status(ctx context.Context) (*service.StatusResponse, error) {
resp := &service.StatusResponse{
Version: "v2beta1",
Healthz: "ok",
KeyID: s.keyID,
}
return resp, nil
}
// NewMockAESService creates an instance of mockAESRemoteService.
func NewMockAESService(aesKey string, keyID string) (service.Service, error) {
block, err := aes.NewCipher([]byte(aesKey))
if err != nil {
return nil, err
}
if len(keyID) == 0 {
return nil, errors.New("invalid keyID")
}
return &mockAESRemoteService{
transformer: aestransformer.NewGCMTransformer(block),
keyID: keyID,
dataCtx: value.DefaultContext([]byte{}),
}, nil
}

View File

@ -1,117 +0,0 @@
/*
Copyright 2023 The Kubernetes 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 internal
import (
"bytes"
"context"
"testing"
"k8s.io/kms/pkg/service"
)
const (
version = "v2beta1"
testAESKey = "abcdefghijklmnop"
testKeyID = "test-key-id"
testPlaintext = "lorem ipsum dolor sit amet"
)
func testContext(t *testing.T) context.Context {
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
return ctx
}
func TestMockAESRemoteService(t *testing.T) {
t.Parallel()
ctx := testContext(t)
plaintext := []byte(testPlaintext)
kmsService, err := NewMockAESService(testAESKey, testKeyID)
if err != nil {
t.Fatal(err)
}
t.Run("should be able to encrypt and decrypt", func(t *testing.T) {
t.Parallel()
encRes, err := kmsService.Encrypt(ctx, "", plaintext)
if err != nil {
t.Fatal(err)
}
if bytes.Equal(plaintext, encRes.Ciphertext) {
t.Fatal("plaintext and ciphertext shouldn't be equal!")
}
decRes, err := kmsService.Decrypt(ctx, "", &service.DecryptRequest{
Ciphertext: encRes.Ciphertext,
KeyID: encRes.KeyID,
Annotations: encRes.Annotations,
})
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(decRes, plaintext) {
t.Errorf("want: %q, have: %q", plaintext, decRes)
}
})
t.Run("should return error when decrypt with an invalid keyID", func(t *testing.T) {
t.Parallel()
encRes, err := kmsService.Encrypt(ctx, "", plaintext)
if err != nil {
t.Fatal(err)
}
if bytes.Equal(plaintext, encRes.Ciphertext) {
t.Fatal("plaintext and ciphertext shouldn't be equal!")
}
_, err = kmsService.Decrypt(ctx, "", &service.DecryptRequest{
Ciphertext: encRes.Ciphertext,
KeyID: encRes.KeyID + "1",
Annotations: encRes.Annotations,
})
if err.Error() != "invalid keyID" {
t.Errorf("should have returned an invalid keyID error. Got %v, requested keyID: %q, remote service keyID: %q", err, encRes.KeyID+"1", testKeyID)
}
})
t.Run("should return status data", func(t *testing.T) {
t.Parallel()
status, err := kmsService.Status(ctx)
if err != nil {
t.Fatal(err)
}
if status.Healthz != "ok" {
t.Errorf("want: %q, have: %q", "ok", status.Healthz)
}
if len(status.KeyID) == 0 {
t.Errorf("want: len(keyID) > 0, have: %d", len(status.KeyID))
}
if status.Version != version {
t.Errorf("want %q, have: %q", version, status.Version)
}
})
}

View File

@ -1,54 +0,0 @@
/*
Copyright 2023 The Kubernetes 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 internal
import (
"context"
"time"
"k8s.io/kms/pkg/service"
)
type mockLatencyRemoteService struct {
delegate service.Service
latency time.Duration
}
var _ service.Service = &mockLatencyRemoteService{}
func (s *mockLatencyRemoteService) Decrypt(ctx context.Context, uid string, req *service.DecryptRequest) ([]byte, error) {
time.Sleep(s.latency)
return s.delegate.Decrypt(ctx, uid, req)
}
func (s *mockLatencyRemoteService) Encrypt(ctx context.Context, uid string, data []byte) (*service.EncryptResponse, error) {
time.Sleep(s.latency)
return s.delegate.Encrypt(ctx, uid, data)
}
func (s *mockLatencyRemoteService) Status(ctx context.Context) (*service.StatusResponse, error) {
// Passthrough here, not adding any delays for status as delays are usually negligible compare to encrypt and decrypt requests.
return s.delegate.Status(ctx)
}
// NewMockLatencyService creates an instance of mockLatencyRemoteService.
func NewMockLatencyService(delegate service.Service, latency time.Duration) service.Service {
return &mockLatencyRemoteService{
delegate: delegate,
latency: latency,
}
}

View File

@ -1,99 +0,0 @@
/*
Copyright 2023 The Kubernetes 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 internal
import (
"bytes"
"testing"
"time"
"k8s.io/kms/pkg/service"
)
const (
testLatencyInMillisecond = 100 * time.Millisecond
)
func TestMockLatencyRemoteService(t *testing.T) {
t.Parallel()
ctx := testContext(t)
plaintext := []byte(testPlaintext)
aesService, err := NewMockAESService(testAESKey, testKeyID)
if err != nil {
t.Fatal(err)
}
kmsService := NewMockLatencyService(aesService, testLatencyInMillisecond)
t.Run("should be able to encrypt and decrypt with some known latency", func(t *testing.T) {
t.Parallel()
start := time.Now()
encRes, err := kmsService.Encrypt(ctx, "", plaintext)
if err != nil {
t.Fatal(err)
}
duration := time.Since(start)
if bytes.Equal(plaintext, encRes.Ciphertext) {
t.Fatal("plaintext and ciphertext shouldn't be equal!")
}
// Max is set to 3s to limit the risk of a CPU limited CI node taking a long time to do encryption.
if duration < testLatencyInMillisecond || duration > 3*time.Second {
t.Errorf("duration for encrypt should be around: %q, have: %q", testLatencyInMillisecond, duration)
}
start = time.Now()
decRes, err := kmsService.Decrypt(ctx, "", &service.DecryptRequest{
Ciphertext: encRes.Ciphertext,
KeyID: encRes.KeyID,
Annotations: encRes.Annotations,
})
if err != nil {
t.Fatal(err)
}
duration = time.Since(start)
if !bytes.Equal(decRes, plaintext) {
t.Errorf("want: %q, have: %q", plaintext, decRes)
}
if duration < testLatencyInMillisecond || duration > 3*time.Second {
t.Errorf("duration decrypt should be around: %q, have: %q", testLatencyInMillisecond, duration)
}
})
t.Run("should return status data", func(t *testing.T) {
t.Parallel()
start := time.Now()
status, err := kmsService.Status(ctx)
if err != nil {
t.Fatal(err)
}
duration := time.Since(start)
if status.Healthz != "ok" {
t.Errorf("want: %q, have: %q", "ok", status.Healthz)
}
if len(status.KeyID) == 0 {
t.Errorf("want: len(keyID) > 0, have: %d", len(status.KeyID))
}
if status.Version != version {
t.Errorf("want %q, have: %q", version, status.Version)
}
if duration > 3*time.Second {
t.Errorf("duration status should be less than: 3s, have: %q", duration)
}
})
}

View File

@ -1,60 +0,0 @@
/*
Copyright 2023 The Kubernetes 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 internal
import (
"context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"k8s.io/client-go/util/flowcontrol"
"k8s.io/kms/pkg/service"
)
type mockRateLimitRemoteService struct {
delegate service.Service
limiter flowcontrol.RateLimiter
}
var _ service.Service = &mockRateLimitRemoteService{}
func (s *mockRateLimitRemoteService) Decrypt(ctx context.Context, uid string, req *service.DecryptRequest) ([]byte, error) {
if !s.limiter.TryAccept() {
return nil, status.New(codes.ResourceExhausted, "remote decrypt rate limit exceeded").Err()
}
return s.delegate.Decrypt(ctx, uid, req)
}
func (s *mockRateLimitRemoteService) Encrypt(ctx context.Context, uid string, data []byte) (*service.EncryptResponse, error) {
if !s.limiter.TryAccept() {
return nil, status.New(codes.ResourceExhausted, "remote encrypt rate limit exceeded").Err()
}
return s.delegate.Encrypt(ctx, uid, data)
}
func (s *mockRateLimitRemoteService) Status(ctx context.Context) (*service.StatusResponse, error) {
// Passthrough here, not adding any rate limiting for status as rate limits are usually for encrypt and decrypt requests.
return s.delegate.Status(ctx)
}
// NewMockRateLimitService creates an instance of mockRateLimitRemoteService.
func NewMockRateLimitService(delegate service.Service, qps float32, burst int) service.Service {
return &mockRateLimitRemoteService{
delegate: delegate,
limiter: flowcontrol.NewTokenBucketRateLimiter(qps, burst),
}
}

View File

@ -1,74 +0,0 @@
/*
Copyright 2023 The Kubernetes 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 internal
import (
"bytes"
"testing"
)
const (
testQPS = 1
// testBurst should be no more than 9 since 9*100millisecond (test latency) = 900ms, which guarantees there is enough bursts per second.
testBurst = 5
)
func TestMockRateLimitRemoteService(t *testing.T) {
t.Parallel()
ctx := testContext(t)
plaintext := []byte(testPlaintext)
aesService, err := NewMockAESService(testAESKey, testKeyID)
if err != nil {
t.Fatal(err)
}
mockLatencyService := NewMockLatencyService(aesService, testLatencyInMillisecond)
kmsService := NewMockRateLimitService(mockLatencyService, testQPS, testBurst)
t.Run("should hit rate limit", func(t *testing.T) {
rateLimitExceeded := false
for i := 0; i < 100; i++ {
encRes, err := kmsService.Encrypt(ctx, "", plaintext)
if i >= testBurst {
if err != nil {
if err.Error() != "rpc error: code = ResourceExhausted desc = remote encrypt rate limit exceeded" {
t.Fatalf("should have failed with rate limit exceeded %d, have err: %v", testBurst, err)
}
rateLimitExceeded = true
} else {
if bytes.Equal(plaintext, encRes.Ciphertext) {
t.Fatal("plaintext and ciphertext shouldn't be equal!")
}
}
} else {
if err != nil {
t.Fatalf("err: %v, i: %d", err, i)
}
if bytes.Equal(plaintext, encRes.Ciphertext) {
t.Fatal("plaintext and ciphertext shouldn't be equal!")
}
}
// status should not hit any rate limit
_, err = kmsService.Status(ctx)
if err != nil {
t.Fatal(err)
}
}
if !rateLimitExceeded {
t.Errorf("should have reached the rate limit of %d", testBurst)
}
})
}

View File

@ -18,19 +18,18 @@ WORKDIR /workspace
# Copy the source
COPY apimachinery/ apimachinery/
COPY client-go/ client-go/
COPY kms/ kms/
WORKDIR /workspace/kms/internal/plugins/mock
WORKDIR /workspace/kms/internal/plugins/_mock
ARG TARGETARCH
ARG TARGETPLATFORM
RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} GO111MODULE=on go build -a -o mock-kms-plugin plugin.go
RUN chmod +x mock-kms-plugin
RUN CGO_ENABLED=1 GOOS=linux GOARCH=${TARGETARCH} GO111MODULE=on go build -a -o mock-kms-plugin plugin.go
# Use distroless as minimal base image to package the manager binary
# Refer to https://github.com/GoogleContainerTools/distroless for more details
FROM --platform=${TARGETPLATFORM:-linux/amd64} gcr.io/distroless/static:nonroot
WORKDIR /
COPY --from=builder /workspace/kms/internal/plugins/mock/mock-kms-plugin .
FROM alpine:latest
ENTRYPOINT [ "/mock-kms-plugin" ]
RUN apk add --update --no-cache ca-certificates gcompat
RUN apk add --no-cache softhsm
COPY --from=builder /workspace/kms/internal/plugins/_mock/mock-kms-plugin /usr/local/bin/mock-kms-plugin
ENTRYPOINT [ "mock-kms-plugin" ]

View File

@ -0,0 +1,5 @@
# Mock KMS Plugin
This is a mock KMS plugin for testing purposes. It implements the KMS plugin using PKCS#11 interface backed by [SoftHSM](https://www.opendnssec.org/softhsm/). It is intended to be used for testing only and not for production use.
The directory is named `_mock` so that it is ignored by the `go mod` tooling in the root directory.

View File

@ -3,6 +3,7 @@ module k8s.io/kms/plugins/mock
go 1.19
require (
github.com/ThalesIgnite/crypto11 v1.2.5
k8s.io/klog/v2 v2.100.1
k8s.io/kms v0.0.0-00010101000000-000000000000
)
@ -11,15 +12,15 @@ require (
github.com/go-logr/logr v1.2.4 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f // indirect
github.com/pkg/errors v0.8.1 // indirect
github.com/thales-e-security/pool v0.0.2 // indirect
golang.org/x/net v0.13.0 // indirect
golang.org/x/sys v0.10.0 // indirect
golang.org/x/text v0.11.0 // indirect
golang.org/x/time v0.3.0 // indirect
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
google.golang.org/grpc v1.54.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
k8s.io/client-go v0.0.0 // indirect
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
)
replace (

View File

@ -1,3 +1,7 @@
github.com/ThalesIgnite/crypto11 v1.2.5 h1:1IiIIEqYmBvUYFeMnHqRft4bwf/O36jryEUpY+9ef8E=
github.com/ThalesIgnite/crypto11 v1.2.5/go.mod h1:ILDKtnCKiQ7zRoNxcp36Y1ZR8LBPmR2E23+wTQe/MlE=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@ -10,6 +14,17 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f h1:eVB9ELsoq5ouItQBr5Tj334bhPJG/MX+m7rTchmzVUQ=
github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg=
github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@ -35,8 +50,6 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
@ -56,4 +69,3 @@ google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw
k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg=
k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=
k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=

View File

@ -0,0 +1,69 @@
apiVersion: v1
kind: Pod
metadata:
name: mock-kmsv2-provider
namespace: kube-system
labels:
tier: control-plane
component: mock-kmsv2-provider
spec:
# hostNetwork: true is required because the plugin is run as a static pod
# on the control plane node and needs to run before the CNI plugins are initialized.
hostNetwork: true
initContainers:
- args:
- |
#!/bin/sh
set -e
set -x
# if token exists, skip initialization
if [ $(ls -1 /var/lib/softhsm/tokens | wc -l) -ge 1 ]; then
echo "Skipping initialization of softhsm"
exit 0
fi
mkdir -p /var/lib/softhsm/tokens
apk add --update --no-cache ca-certificates jq
apk add --no-cache ccid opensc softhsm
TOKEN_LABEL=$(jq -r '.tokenLabel' /etc/softhsm-config.json)
PIN=$(jq -r '.pin' /etc/softhsm-config.json)
MODULE_PATH=$(jq -r '.path' /etc/softhsm-config.json)
softhsm2-util --init-token --free --label $TOKEN_LABEL --pin $PIN --so-pin $PIN
pkcs11-tool --module $MODULE_PATH --keygen --key-type aes:32 --pin $PIN --token-label $TOKEN_LABEL --label kms-test
command:
- /bin/sh
- -c
image: alpine:latest
imagePullPolicy: IfNotPresent
name: init-mock-kmsv2-provider
volumeMounts:
- mountPath: /var/lib/softhsm/tokens
name: softhsm-tokens
- mountPath: /etc/softhsm-config.json
name: softhsm-config
containers:
- name: mock-kmsv2-provider
image: localhost:5000/mock-kms-provider:e2e
imagePullPolicy: IfNotPresent
volumeMounts:
- name: sock
mountPath: /tmp
- name: softhsm-config
mountPath: /etc/softhsm-config.json
- name: softhsm-tokens
mountPath: /var/lib/softhsm/tokens
volumes:
- name: sock
hostPath:
path: /tmp
- name: softhsm-config
hostPath:
path: /etc/softhsm-config.json
type: File
- name: softhsm-tokens
hostPath:
path: /var/lib/softhsm/tokens
type: DirectoryOrCreate

View File

@ -0,0 +1,118 @@
/*
Copyright 2023 The Kubernetes 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 pkcs11
import (
"context"
"crypto/cipher"
"crypto/rand"
"fmt"
crypot11 "github.com/ThalesIgnite/crypto11"
"k8s.io/kms/pkg/service"
)
const (
mockAnnotationKey = "version.encryption.remote.io"
)
var _ service.Service = &pkcs11RemoteService{}
type pkcs11RemoteService struct {
keyID string
aead cipher.AEAD
}
// NewPKCS11RemoteService creates a new PKCS11 remote service with SoftHSMv2 configuration file and keyID
func NewPKCS11RemoteService(configFilePath, keyID string) (service.Service, error) {
ctx, err := crypot11.ConfigureFromFile(configFilePath)
if err != nil {
return nil, err
}
if len(keyID) == 0 {
return nil, fmt.Errorf("invalid keyID")
}
remoteService := &pkcs11RemoteService{
keyID: keyID,
}
key, err := ctx.FindKey(nil, []byte(keyID))
if err != nil {
return nil, err
}
if key == nil {
return nil, fmt.Errorf("key not found")
}
if remoteService.aead, err = key.NewGCM(); err != nil {
return nil, err
}
return remoteService, nil
}
func (s *pkcs11RemoteService) Encrypt(ctx context.Context, uid string, plaintext []byte) (*service.EncryptResponse, error) {
nonceSize := s.aead.NonceSize()
result := make([]byte, nonceSize+s.aead.Overhead()+len(plaintext))
n, err := rand.Read(result[:nonceSize])
if err != nil {
return nil, err
}
if n != nonceSize {
return nil, fmt.Errorf("unable to read sufficient random bytes")
}
cipherText := s.aead.Seal(result[nonceSize:nonceSize], result[:nonceSize], plaintext, []byte(s.keyID))
return &service.EncryptResponse{
Ciphertext: result[:nonceSize+len(cipherText)],
KeyID: s.keyID,
Annotations: map[string][]byte{
mockAnnotationKey: []byte("1"),
},
}, nil
}
func (s *pkcs11RemoteService) Decrypt(ctx context.Context, uid string, req *service.DecryptRequest) ([]byte, error) {
if len(req.Annotations) != 1 {
return nil, fmt.Errorf("invalid annotations")
}
if v, ok := req.Annotations[mockAnnotationKey]; !ok || string(v) != "1" {
return nil, fmt.Errorf("invalid version in annotations")
}
if req.KeyID != s.keyID {
return nil, fmt.Errorf("invalid keyID")
}
nonceSize := s.aead.NonceSize()
data := req.Ciphertext
if len(data) < nonceSize {
return nil, fmt.Errorf("the stored data was shorter than the required size")
}
return s.aead.Open(nil, data[:nonceSize], data[nonceSize:], []byte(s.keyID))
}
func (s *pkcs11RemoteService) Status(ctx context.Context) (*service.StatusResponse, error) {
return &service.StatusResponse{
Version: "v2beta1",
Healthz: "ok",
KeyID: s.keyID,
}, nil
}

View File

@ -25,14 +25,15 @@ import (
"time"
"k8s.io/klog/v2"
"k8s.io/kms/internal"
"k8s.io/kms/pkg/service"
"k8s.io/kms/pkg/util"
"k8s.io/kms/plugins/mock/pkcs11"
)
var (
listenAddr = flag.String("listen-addr", "unix:///tmp/kms.socket", "gRPC listen address")
timeout = flag.Duration("timeout", 5*time.Second, "gRPC timeout")
listenAddr = flag.String("listen-addr", "unix:///tmp/kms.socket", "gRPC listen address")
timeout = flag.Duration("timeout", 5*time.Second, "gRPC timeout")
configFilePath = flag.String("config-file-path", "/etc/softhsm-config.json", "SoftHSM config file path")
)
func main() {
@ -44,7 +45,7 @@ func main() {
os.Exit(1)
}
remoteKMSService, err := internal.NewMockAESService("somerandomstring", "aes-key-id")
remoteKMSService, err := pkcs11.NewPKCS11RemoteService(*configFilePath, "kms-test")
if err != nil {
klog.ErrorS(err, "failed to create remote service")
os.Exit(1)

View File

@ -1,21 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: mock-kmsv2-provider
namespace: kube-system
labels:
tier: control-plane
component: mock-kmsv2-provider
spec:
hostNetwork: true
containers:
- name: mock-kmsv2-provider
image: localhost:5000/mock-kms-provider:e2e
imagePullPolicy: IfNotPresent
volumeMounts:
- name: sock
mountPath: /tmp
volumes:
- name: sock
hostPath:
path: /tmp

View File

@ -1,85 +0,0 @@
/*
Copyright 2017 The Kubernetes 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.
*/
// Vendored from kubernetes/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/aes/aes.go
// * commit: 90b42f91fd904b71fd52ca9ae55a5de73e6b779a
// * link: https://github.com/kubernetes/kubernetes/blob/90b42f91fd904b71fd52ca9ae55a5de73e6b779a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/aes/aes.go
// Package aes transforms values for storage at rest using AES-GCM.
package aes
import (
"context"
"crypto/cipher"
"crypto/rand"
"fmt"
"k8s.io/kms/pkg/value"
)
// gcm implements AEAD encryption of the provided values given a cipher.Block algorithm.
// The authenticated data provided as part of the value.Context method must match when the same
// value is set to and loaded from storage. In order to ensure that values cannot be copied by
// an attacker from a location under their control, use characteristics of the storage location
// (such as the etcd key) as part of the authenticated data.
//
// Because this mode requires a generated IV and IV reuse is a known weakness of AES-GCM, keys
// must be rotated before a birthday attack becomes feasible. NIST SP 800-38D
// (http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf) recommends using the same
// key with random 96-bit nonces (the default nonce length) no more than 2^32 times, and
// therefore transformers using this implementation *must* ensure they allow for frequent key
// rotation. Future work should include investigation of AES-GCM-SIV as an alternative to
// random nonces.
type gcm struct {
block cipher.Block
}
// NewGCMTransformer takes the given block cipher and performs encryption and decryption on the given
// data.
func NewGCMTransformer(block cipher.Block) value.Transformer {
return &gcm{block: block}
}
func (t *gcm) TransformFromStorage(ctx context.Context, data []byte, dataCtx value.Context) ([]byte, bool, error) {
aead, err := cipher.NewGCM(t.block)
if err != nil {
return nil, false, err
}
nonceSize := aead.NonceSize()
if len(data) < nonceSize {
return nil, false, fmt.Errorf("the stored data was shorter than the required size")
}
result, err := aead.Open(nil, data[:nonceSize], data[nonceSize:], dataCtx.AuthenticatedData())
return result, false, err
}
func (t *gcm) TransformToStorage(ctx context.Context, data []byte, dataCtx value.Context) ([]byte, error) {
aead, err := cipher.NewGCM(t.block)
if err != nil {
return nil, err
}
nonceSize := aead.NonceSize()
result := make([]byte, nonceSize+aead.Overhead()+len(data))
n, err := rand.Read(result[:nonceSize])
if err != nil {
return nil, err
}
if n != nonceSize {
return nil, fmt.Errorf("unable to read sufficient random bytes")
}
cipherText := aead.Seal(result[nonceSize:nonceSize], result[:nonceSize], data, dataCtx.AuthenticatedData())
return result[:nonceSize+len(cipherText)], nil
}

View File

@ -1,49 +0,0 @@
/*
Copyright 2017 The Kubernetes 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 value
import "context"
// Vendored from kubernetes/staging/src/k8s.io/apiserver/pkg/storage/value/transformer.go
// * commit: 59e1a32fc8ed35e328a3971d3a1d640ffc28ff55
// * link: https://github.com/kubernetes/kubernetes/blob/59e1a32fc8ed35e328a3971d3a1d640ffc28ff55/staging/src/k8s.io/apiserver/pkg/storage/value/transformer.go
// Transformer allows a value to be transformed before being read from or written to the underlying store. The methods
// must be able to undo the transformation caused by the other.
type Transformer interface {
// TransformFromStorage may transform the provided data from its underlying storage representation or return an error.
// Stale is true if the object on disk is stale and a write to etcd should be issued, even if the contents of the object
// have not changed.
TransformFromStorage(ctx context.Context, data []byte, dataCtx Context) (out []byte, stale bool, err error)
// TransformToStorage may transform the provided data into the appropriate form in storage or return an error.
TransformToStorage(ctx context.Context, data []byte, dataCtx Context) (out []byte, err error)
}
// Context is additional information that a storage transformation may need to verify the data at rest.
type Context interface {
// AuthenticatedData should return an array of bytes that describes the current value. If the value changes,
// the transformer may report the value as unreadable or tampered. This may be nil if no such description exists
// or is needed. For additional verification, set this to data that strongly identifies the value, such as
// the key and creation version of the stored data.
AuthenticatedData() []byte
}
// DefaultContext is a simple implementation of Context for a slice of bytes.
type DefaultContext []byte
// AuthenticatedData returns itself.
func (c DefaultContext) AuthenticatedData() []byte { return c }

View File

@ -1,6 +1,6 @@
# Encryption at rest testing manifests
This directory contains manifests for testing encryption at rest with a [mock KMS provider](../../../../../staging/src/k8s.io/kms/internal/plugins/mock). The mock KMS provider is a fake KMS provider that does not communicate with any external KMS. It is used for testing purposes only.
This directory contains manifests for testing encryption at rest with a [mock KMS provider](../../../../../staging/src/k8s.io/kms/internal/plugins/_mock). The mock KMS provider is a fake KMS provider that does not communicate with any external KMS. It is used for testing purposes only.
## run-e2e.sh

View File

@ -13,7 +13,11 @@ nodes:
readOnly: true
propagation: None
- containerPath: /etc/kubernetes/manifests/kubernetes-kms.yaml
hostPath: staging/src/k8s.io/kms/internal/plugins/mock/kms.yaml
hostPath: staging/src/k8s.io/kms/internal/plugins/_mock/kms.yaml
readOnly: true
propagation: None
- containerPath: /etc/softhsm-config.json
hostPath: test/e2e/testing-manifests/auth/encrypt/softhsm-config.json
readOnly: true
propagation: None
kubeadmConfigPatches:

View File

@ -36,7 +36,7 @@ build_and_push_mock_plugin() {
--platform linux/amd64 \
--output=type=docker \
-t localhost:5000/mock-kms-provider:e2e \
-f staging/src/k8s.io/kms/internal/plugins/mock/Dockerfile staging/src/k8s.io/ \
-f staging/src/k8s.io/kms/internal/plugins/_mock/Dockerfile staging/src/k8s.io/ \
--progress=plain;
docker push localhost:5000/mock-kms-provider:e2e

View File

@ -0,0 +1,5 @@
{
"path": "/usr/lib/softhsm/libsofthsm2.so",
"tokenLabel": "kms-test",
"pin": "kms-test"
}