fakekms/crypto_key_version_rpcs.go (150 lines of code) (raw):

// Copyright 2021 Google LLC // // 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 fakekms import ( "context" "sort" "strconv" "time" "cloud.google.com/go/kms/apiv1/kmspb" "google.golang.org/protobuf/types/known/timestamppb" ) // CreateCryptoKeyVersionVersion fakes a Cloud KMS API function. func (f *fakeKMS) CreateCryptoKeyVersion(ctx context.Context, req *kmspb.CreateCryptoKeyVersionRequest) (*kmspb.CryptoKeyVersion, error) { if err := allowlist("parent").check(req); err != nil { return nil, err } ckName, err := parseCryptoKeyName(req.Parent) if err != nil { return nil, err } ck, err := f.cryptoKey(ckName) if err != nil { return nil, err } return f.createVersion(ck), nil } // createVersion adds a new version to the provided cryptoKey and returns its proto. // Callers must already be holding f.mux (normally via the locking interceptor). func (f *fakeKMS) createVersion(ck *cryptoKey) *kmspb.CryptoKeyVersion { ckName, _ := parseCryptoKeyName(ck.pb.Name) name := cryptoKeyVersionName{ cryptoKeyName: ckName, CryptoKeyVersionID: strconv.Itoa(len(ck.versions) + 1), } pb := &kmspb.CryptoKeyVersion{ Name: name.String(), CreateTime: timestamppb.Now(), Algorithm: ck.pb.VersionTemplate.Algorithm, ProtectionLevel: ck.pb.VersionTemplate.ProtectionLevel, } ckv := &cryptoKeyVersion{pb: pb} def, _ := algorithmDef(pb.Algorithm) if def.Asymmetric() { // async generation pb.State = kmspb.CryptoKeyVersion_PENDING_GENERATION go func() { k := def.KeyFactory.Generate() // no need to wait on the lock for this // Our lock interceptor ensures that f.mux is held whenever an RPC is in // flight. This goroutine won't be able to acquire f.mux until after the // CreateCryptoKeyVersion RPC completes. Since all RPCs acquire f.mux, we // can further guarantee that no other RPCs are in flight when this // routine proceeds. f.mux.Lock() defer f.mux.Unlock() ckv.keyMaterial = k pb.State = kmspb.CryptoKeyVersion_ENABLED pb.GenerateTime = timestamppb.Now() }() } else { // sync generation ckv.keyMaterial = def.KeyFactory.Generate() pb.GenerateTime = pb.CreateTime pb.State = kmspb.CryptoKeyVersion_ENABLED } ck.versions[name] = ckv return pb } // GetCryptoKeyVersion fakes a Cloud KMS API function. func (f *fakeKMS) GetCryptoKeyVersion(ctx context.Context, req *kmspb.GetCryptoKeyVersionRequest) (*kmspb.CryptoKeyVersion, error) { if err := allowlist("name").check(req); err != nil { return nil, err } name, err := parseCryptoKeyVersionName(req.Name) if err != nil { return nil, err } ckv, err := f.cryptoKeyVersion(name) if err != nil { return nil, err } return ckv.pb, nil } // ListCryptoKeyVersions fakes a Cloud KMS API function. func (f *fakeKMS) ListCryptoKeyVersions(ctx context.Context, req *kmspb.ListCryptoKeyVersionsRequest) (*kmspb.ListCryptoKeyVersionsResponse, error) { if err := allowlist("parent").check(req); err != nil { return nil, err } parent, err := parseCryptoKeyName(req.Parent) if err != nil { return nil, err } ck, err := f.cryptoKey(parent) if err != nil { return nil, err } r := make([]*kmspb.CryptoKeyVersion, 0, len(ck.versions)) for _, ckv := range ck.versions { r = append(r, ckv.pb) } if len(r) > maxPageSize { return nil, errMaxPageSize(len(r)) } sort.Slice(r, func(i, j int) bool { return r[i].Name < r[j].Name }) return &kmspb.ListCryptoKeyVersionsResponse{ CryptoKeyVersions: r, TotalSize: int32(len(r)), }, nil } // UpdateCryptoKeyVersion fakes a Cloud KMS API function. func (f *fakeKMS) UpdateCryptoKeyVersion(ctx context.Context, req *kmspb.UpdateCryptoKeyVersionRequest) (*kmspb.CryptoKeyVersion, error) { if err := allowlist("crypto_key_version", "update_mask").check(req); err != nil { return nil, err } if len(req.UpdateMask.GetPaths()) == 0 { return nil, errInvalidArgument("no fields selected for update") } name, err := parseCryptoKeyVersionName(req.CryptoKeyVersion.GetName()) if err != nil { return nil, err } ckv, err := f.cryptoKeyVersion(name) if err != nil { return nil, err } for _, p := range req.UpdateMask.Paths { switch p { case "state": switch req.CryptoKeyVersion.State { case kmspb.CryptoKeyVersion_ENABLED, kmspb.CryptoKeyVersion_DISABLED: ckv.pb.State = req.CryptoKeyVersion.State default: return nil, errInvalidArgument("unsupported state in update call: %s", nameForValue(kmspb.CryptoKeyVersion_CryptoKeyVersionState_name, int32(req.CryptoKeyVersion.State))) } default: return nil, errInvalidArgument("unsupported update path: %s", p) } } return ckv.pb, nil } // DestroyCryptoKeyVersion fakes a Cloud KMS API function. func (f *fakeKMS) DestroyCryptoKeyVersion(ctx context.Context, req *kmspb.DestroyCryptoKeyVersionRequest) (*kmspb.CryptoKeyVersion, error) { if err := allowlist("name").check(req); err != nil { return nil, err } name, err := parseCryptoKeyVersionName(req.Name) if err != nil { return nil, err } ckv, err := f.cryptoKeyVersion(name) if err != nil { return nil, err } switch ckv.pb.State { case kmspb.CryptoKeyVersion_ENABLED, kmspb.CryptoKeyVersion_DISABLED: break default: return nil, errFailedPrecondition("state must be one of ENABLED or DISABLED in order to destroy") } // destroy_time is represented internally in KMS as int64 micros destroyTime := time.Now().Add(30 * 24 * time.Hour).Truncate(time.Microsecond) ckv.pb.DestroyTime = timestamppb.New(destroyTime) ckv.pb.State = kmspb.CryptoKeyVersion_DESTROY_SCHEDULED return ckv.pb, nil }