From 93e43d1a0f164e8157bba27a59eb0dbe23eb9cfb Mon Sep 17 00:00:00 2001 From: Humble Chirammal Date: Mon, 20 Dec 2021 17:49:02 +0530 Subject: [PATCH] rebase: IBM key protect integration module dependency update This commit adds the Key protect client SDK for the Key Protect KMS integration to the Ceph CSI driver. Signed-off-by: Humble Chirammal --- go.mod | 1 + go.sum | 4 + .../IBM/keyprotect-go-client/.gitignore | 44 ++ .../IBM/keyprotect-go-client/.travis.yml | 25 + .../IBM/keyprotect-go-client/CONTRIBUTING.md | 49 ++ .../IBM/keyprotect-go-client/LICENSE | 201 +++++++ .../IBM/keyprotect-go-client/Makefile | 8 + .../IBM/keyprotect-go-client/README.md | 173 ++++++ .../IBM/keyprotect-go-client/iam/iam.go | 252 ++++++++ .../IBM/keyprotect-go-client/import_token.go | 219 +++++++ .../IBM/keyprotect-go-client/instances.go | 543 ++++++++++++++++++ .../IBM/keyprotect-go-client/key_alias.go | 68 +++ .../IBM/keyprotect-go-client/key_rings.go | 75 +++ .../IBM/keyprotect-go-client/keys.go | 463 +++++++++++++++ .../github.com/IBM/keyprotect-go-client/kp.go | 510 ++++++++++++++++ .../IBM/keyprotect-go-client/policy.go | 317 ++++++++++ .../IBM/keyprotect-go-client/registrations.go | 69 +++ vendor/modules.txt | 4 + 18 files changed, 3025 insertions(+) create mode 100644 vendor/github.com/IBM/keyprotect-go-client/.gitignore create mode 100644 vendor/github.com/IBM/keyprotect-go-client/.travis.yml create mode 100644 vendor/github.com/IBM/keyprotect-go-client/CONTRIBUTING.md create mode 100644 vendor/github.com/IBM/keyprotect-go-client/LICENSE create mode 100644 vendor/github.com/IBM/keyprotect-go-client/Makefile create mode 100644 vendor/github.com/IBM/keyprotect-go-client/README.md create mode 100644 vendor/github.com/IBM/keyprotect-go-client/iam/iam.go create mode 100644 vendor/github.com/IBM/keyprotect-go-client/import_token.go create mode 100644 vendor/github.com/IBM/keyprotect-go-client/instances.go create mode 100644 vendor/github.com/IBM/keyprotect-go-client/key_alias.go create mode 100644 vendor/github.com/IBM/keyprotect-go-client/key_rings.go create mode 100644 vendor/github.com/IBM/keyprotect-go-client/keys.go create mode 100644 vendor/github.com/IBM/keyprotect-go-client/kp.go create mode 100644 vendor/github.com/IBM/keyprotect-go-client/policy.go create mode 100644 vendor/github.com/IBM/keyprotect-go-client/registrations.go diff --git a/go.mod b/go.mod index a34869984..cb0c684f3 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/ceph/ceph-csi go 1.17 require ( + github.com/IBM/keyprotect-go-client v0.7.0 github.com/aws/aws-sdk-go v1.42.7 github.com/ceph/ceph-csi/api v0.0.0-00010101000000-000000000000 github.com/ceph/go-ceph v0.12.0 diff --git a/go.sum b/go.sum index b9e205a3a..4cb846876 100644 --- a/go.sum +++ b/go.sum @@ -80,6 +80,8 @@ github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3 github.com/DataDog/zstd v1.4.4/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/GoogleCloudPlatform/k8s-cloud-provider v1.16.1-0.20210702024009-ea6160c1d0e3/go.mod h1:8XasY4ymP2V/tn2OOV9ZadmiTE1FIB/h3W+yNlPttKw= github.com/IBM/keyprotect-go-client v0.5.1/go.mod h1:5TwDM/4FRJq1ZOlwQL1xFahLWQ3TveR88VmL1u3njyI= +github.com/IBM/keyprotect-go-client v0.7.0 h1:JstSHD14Lp6ihwQseyPuGcs1AjOBjAmcisP0dTBA6A0= +github.com/IBM/keyprotect-go-client v0.7.0/go.mod h1:SVr2ylV/fhSQPDiUjWirN9fsyWFCNNbt8GIT8hPJVjE= github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab/go.mod h1:3VYc5hodBMJ5+l/7J4xAyMeuM2PNuepvHlGs8yilUCA= github.com/Jeffail/gabs v1.1.1 h1:V0uzR08Hj22EX8+8QMhyI9sX2hwRu+/RJhJUmnwda/E= github.com/Jeffail/gabs v1.1.1/go.mod h1:6xMvQMK4k33lb7GUUpaAPh6nKMmemQeg5d4gn7/bOXc= @@ -498,6 +500,7 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= github.com/hashicorp/consul-template v0.25.0/go.mod h1:/vUsrJvDuuQHcxEw0zik+YXTS7ZKWZjQeaQhshBmfH0= @@ -1626,6 +1629,7 @@ gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qS gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.0/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/h2non/gock.v1 v1.0.15 h1:SzLqcIlb/fDfg7UvukMpNcWsu7sI5tWwL+KCATZqks0= gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= diff --git a/vendor/github.com/IBM/keyprotect-go-client/.gitignore b/vendor/github.com/IBM/keyprotect-go-client/.gitignore new file mode 100644 index 000000000..548739959 --- /dev/null +++ b/vendor/github.com/IBM/keyprotect-go-client/.gitignore @@ -0,0 +1,44 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test +vendor + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof + +.DS_Store + +cover.* + +# Eclipse +.project + +# Visual Studio Code +.vscode +/*.covtmp +**/coverage +**/coverage.htmlvendor +**/coverage.txt +**/coverage.html +**/cover.html +**/coverage.out +**/coverage.tmp +*.coverprofile diff --git a/vendor/github.com/IBM/keyprotect-go-client/.travis.yml b/vendor/github.com/IBM/keyprotect-go-client/.travis.yml new file mode 100644 index 000000000..f269b7b4b --- /dev/null +++ b/vendor/github.com/IBM/keyprotect-go-client/.travis.yml @@ -0,0 +1,25 @@ +language: go +dist: xenial + +go: + - 1.14.x + - 1.15.x + +env: + - GO111MODULE=on + +before_script: + - GO111MODULE=off go get -u github.com/haya14busa/goverage + +install: + - go build ./... + +script: + - $GOPATH/bin/goverage -v -race -coverprofile=cover.out $(go list ./... | grep -v '/vendor|/scripts') + - go tool cover -func=cover.out + - go tool cover -html=cover.out -o=cover.html + +# FIXME: these scripts don't exist in this repo +# after_success: +# - ./scripts/calculateCoverage.sh +# - ./scripts/publishCoverage.sh diff --git a/vendor/github.com/IBM/keyprotect-go-client/CONTRIBUTING.md b/vendor/github.com/IBM/keyprotect-go-client/CONTRIBUTING.md new file mode 100644 index 000000000..3f9ac45dc --- /dev/null +++ b/vendor/github.com/IBM/keyprotect-go-client/CONTRIBUTING.md @@ -0,0 +1,49 @@ +# Contributing to keyprotect-go-client + +`keyprotect-go-client` is open for code perusal and contributions. We welcome contributions in the form of feedback, bugs, or patches. + +## Bugs and Feature Requests + +If you find something that does not work as expected or would like to see a new feature added, +please open a [Github Issue](https://github.com/IBM/keyprotect-go-client/issues) + +## Pull Requests + +For your pull request to be merged, it must meet the criteria of a "correct patch", and also +be fully reviewed and approved by two Maintainer level contributors. + +A correct patch is defined as the following: + + - If the patch fixes a bug, it must be the simplest way to fix the issue + - Your patch must come with unit tests + - Unit tests (CI job) must pass + - New feature function should have integration tests as well + + +# Development + +## Compiling the package + +```sh +go build ./... +``` + +The client relies on go modules to pull in required dependencies at build time. + +https://github.com/golang/go/wiki/Modules#how-to-use-modules + +## Running the test cases + +Using `go test` + +```sh +go test -v -race ./... +``` + +The test cases are also runnable through `make` + +```sh +make test +# or +make test-integration +``` diff --git a/vendor/github.com/IBM/keyprotect-go-client/LICENSE b/vendor/github.com/IBM/keyprotect-go-client/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/vendor/github.com/IBM/keyprotect-go-client/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/vendor/github.com/IBM/keyprotect-go-client/Makefile b/vendor/github.com/IBM/keyprotect-go-client/Makefile new file mode 100644 index 000000000..6e166501c --- /dev/null +++ b/vendor/github.com/IBM/keyprotect-go-client/Makefile @@ -0,0 +1,8 @@ + +.PHONY: test test-integration + +test: + go test -v -race ./... + +test-integration: + go test -v -tags=integration ./... diff --git a/vendor/github.com/IBM/keyprotect-go-client/README.md b/vendor/github.com/IBM/keyprotect-go-client/README.md new file mode 100644 index 000000000..2fe0bd79b --- /dev/null +++ b/vendor/github.com/IBM/keyprotect-go-client/README.md @@ -0,0 +1,173 @@ +# keyprotect-go-client + +[![Build Status](https://travis-ci.com/IBM/keyprotect-go-client.svg?branch=master)](https://travis-ci.com/IBM/keyprotect-go-client) +[![GoDoc](https://godoc.org/github.com/keyprotect-go-client?status.svg)](https://godoc.org/github.com/IBM/keyprotect-go-client) + +keyprotect-go-client is a Go client library for interacting with the IBM KeyProtect service. + +* [Questions / Support](#questions--support) +* [Usage](#usage) + * [Migrating](#migrating) + * [Authentication](#authentication) + * [Finding Instance UUIDs](#finding-a-keyprotect-service-instances-uuid) + * [Examples](#examples) +* [Contributing](/CONTRIBUTING.md) + +## Questions / Support + +There are many channels for asking questions about KeyProtect and this client. + +- Ask a question on Stackoverflow and tag it with `key-protect` and `ibm-cloud` +- Open a [Github Issue](https://github.com/IBM/keyprotect-go-client/issues) +- If you work at IBM and have access to the internal Slack, you can join the `#key-protect` channel and ask there. + +## Usage + +This client expects that you have an existing IBM Cloud Key Protect Service Instance. To get started, visit the [IBM KeyProtect Catalog Page](https://cloud.ibm.com/catalog/services/key-protect). + +Build a client with `ClientConfig` and `New`, then use the client to do some operations. +```go +import "github.com/IBM/keyprotect-go-client" + +// Use your IAM API Key and your KeyProtect Service Instance GUID/UUID to create a ClientConfig +cc := kp.ClientConfig{ + BaseURL: kp.DefaultBaseURL, + APIKey: "......", + InstanceID: "1234abcd-906d-438a-8a68-deadbeef1a2b3", +} + +// Build a new client from the config +client := kp.New(cc, kp.DefaultTransport()) + +// List keys in your KeyProtect instance +keys, err := client.GetKeys(context.Background(), 0, 0) +``` + +### Migrating + +For users of the original `key-protect-client` that is now deprecated, this library is a drop in replacement. Updating the package reference to `github.com/IBM/keyprotect-go-client` should be the only change needed. If you are worried about new incompatible changes, version `v0.3.1` of `key-protect-client` is equivalent to version `v0.3.3` of `keyprotect-go-client`, so pinning `v0.3.3` of the new library should be sufficient to pull from the new repo with no new functional changes. + +## Authentication + +The KeyProtect client requires a valid [IAM API Key](https://cloud.ibm.com/docs/iam?topic=iam-userapikey#create_user_key) that is passed via the `APIKey` field in the `ClientConfig`. The client will call IAM to get an access token for that API key, caches the access token, and reuses that token on subsequent calls. If the access token is expired, the client will call IAM to get a new access token. + +Alternatively, you may also inject your own tokens during runtime. When using your own tokens, it's the responsibilty of the caller to ensure the access token is valid and is not expired. You can specify the access token in either the `ClientConfig` structure or on the context (see below.) + +To specify authorization token on the context: + +```go +// Create a ClientConfig and Client like before, but without an APIKey +cc := kp.ClientConfig{ + BaseURL: kp.DefaultBaseURL, + InstanceID: "1234abcd-906d-438a-8a68-deadbeef1a2b3", +} +client := kp.New(cc, kp.DefaultTransport()) + +// Use NewContextWithAuth to add your token into the context +ctx := context.Background() +ctx = kp.NewContextWithAuth(ctx, "Bearer ABCDEF123456....") + +// List keys with our injected token via the context +keys, err := api.GetKeys(ctx, 0, 0) +``` + +For information on IAM API Keys and tokens please refer to the [IAM docs](https://cloud.ibm.com/docs/iam?topic=iam-manapikey) + +## Finding a KeyProtect Service Instance's UUID + +The client requires a valid UUID that identifies your KeyProtect Service Instance to be able to interact with your key data in the instance. An instance is somewhat like a folder or directory of keys; you can have many of them per account, but the keys they contain are separate and cannot be shared between instances. + +The [IBM Cloud CLI](https://cloud.ibm.com/docs/cli?topic=cloud-cli-getting-started) can be used to find the UUID for your KeyProtect instance. + +```sh +$ ic resource service-instances +OK +Name Location State Type +Key Protect-private us-south active service_instance +Key Protect-abc123 us-east active service_instance +``` + +Find the name of your KeyProtect instance as you created it, and the use the client to get its details. The Instance ID is the GUID field, or if you do not see GUID, it will be the last part of the CRN. For example: + +```sh +$ ic resource service-instance "Key Protect-private" +OK + +Name: Key Protect-private +ID: crn:v1:bluemix:public:kms:us-south:a/.......:1234abcd-906d-438a-8a68-deadbeef1a2b3:: +GUID: 1234abcd-906d-438a-8a68-deadbeef1a2b3 +``` + +## Examples + +### Generating a root key (CRK) + +```go +// Create a root key named MyRootKey with no expiration +key, err := client.CreateRootKey(ctx, "MyRootKey", nil) +if err != nil { + fmt.Println(err) +} +fmt.Println(key.ID, key.Name) + +crkID := key.ID +``` + +### Wrapping and Unwrapping a DEK using a specific Root Key. + +```go +myDEK := []byte{"thisisadataencryptionkey"} +// Do some encryption with myDEK +// Wrap the DEK so we can safely store it +wrappedDEK, err := client.Wrap(ctx, crkID, myDEK, nil) + + +// Unwrap the DEK +dek, err := client.Unwrap(ctx, crkID, wrappedDEK, nil) +// Do some encryption/decryption using the DEK +// Discard the DEK +dek = nil +``` + +Note you can also pass additional authentication data (AAD) to wrap and unwrap calls +to provide another level of protection for your DEK. The AAD is a string array with +each element up to 255 chars. For example: + +```go +myAAD := []string{"First aad string", "second aad string", "third aad string"} +myDEK := []byte{"thisisadataencryptionkey"} +// Do some encryption with myDEK +// Wrap the DEK so we can safely store it +wrappedDEK, err := client.Wrap(ctx, crkID, myDEK, &myAAD) + + +// Unwrap the DEK +dek, err := client.Unwrap(ctx, crkID, wrappedDEK, &myAAD) +// Do some encryption/decryption using the DEK +// Discard the DEK +dek = nil +``` + +Have key protect create a DEK for you: + +```go +dek, wrappedDek, err := client.WrapCreateDEK(ctx, crkID, nil) +// Do some encrypt/decrypt with the dek +// Discard the DEK +dek = nil + +// Save the wrapped DEK for later. Use Unwrap to use it. +``` + +Can also specify AAD: + +```go +myAAD := []string{"First aad string", "second aad string", "third aad string"} +dek, wrappedDek, err := client.WrapCreateDEK(ctx, crkID, &myAAD) +// Do some encrypt/decrypt with the dek +// Discard the DEK +dek = nil + +// Save the wrapped DEK for later. Call Unwrap to use it, make +// sure to specify the same AAD. +``` diff --git a/vendor/github.com/IBM/keyprotect-go-client/iam/iam.go b/vendor/github.com/IBM/keyprotect-go-client/iam/iam.go new file mode 100644 index 000000000..25407b4db --- /dev/null +++ b/vendor/github.com/IBM/keyprotect-go-client/iam/iam.go @@ -0,0 +1,252 @@ +// Copyright 2019 IBM Corp. +// +// 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 iam + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" + "sync" + "time" + + rhttp "github.com/hashicorp/go-retryablehttp" +) + +// IAMTokenURL is the global endpoint URL for the IAM token service +const IAMTokenURL = "https://iam.cloud.ibm.com/oidc/token" + +var ( + // RetryWaitMax is the maximum time to wait between HTTP retries + RetryWaitMax = 30 * time.Second + + // RetryMax is the max number of attempts to retry for failed HTTP requests + RetryMax = 4 +) + +type TokenSource interface { + Token() (*Token, error) +} + +// CredentialFromAPIKey returns an IAMTokenSource that requests access tokens +// from the default token endpoint using an IAM API Key as the authentication mechanism +func CredentialFromAPIKey(apiKey string) *IAMTokenSource { + return &IAMTokenSource{ + TokenURL: IAMTokenURL, + APIKey: apiKey, + } +} + +// Token represents an IAM credential used to authorize requests to another service. +type Token struct { + AccessToken string + RefreshToken string + TokenType string + Expiry time.Time +} + +func (t *Token) Valid() bool { + if t == nil || t.AccessToken == "" { + return false + } + + if t.Expiry.Before(time.Now()) { + return false + } + + return true +} + +// jsonToken is for deserializing the token from the response body +type jsonToken struct { + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + TokenType string `json:"token_type"` + ExpiresIn int32 `json:"expires_in"` +} + +// getExpireTime uses local time and the ExpiresIn offset to calculate an +// expiration time based off our local clock, which is more accurate for +// us to determine when it expires relative to our client. +// we also pad the time a bit, because long running requests can fail +// mid-request if we send a soon-to-expire token along +func (jt jsonToken) getExpireTime() time.Time { + // set the expiration time for 1 min less than the + // actual time to prevent timeout errors + return time.Now().Add(time.Duration(jt.ExpiresIn-60) * time.Second) +} + +// IAMTokenSource is used to retrieve access tokens from the IAM token service. +// Most will probably want to use CredentialFromAPIKey to build an IAMTokenSource type, +// but it can also be created directly if one wishes to override the default IAM +// endpoint by setting TokenURL +type IAMTokenSource struct { + TokenURL string + APIKey string + + mu sync.Mutex + t *Token +} + +// Token requests an access token from IAM using the IAMTokenSource config. +func (ts *IAMTokenSource) Token() (*Token, error) { + ts.mu.Lock() + defer ts.mu.Unlock() + + if ts.t.Valid() { + return ts.t, nil + } + + if ts.APIKey == "" { + return nil, errors.New("iam: APIKey is empty") + } + + v := url.Values{} + v.Set("grant_type", "urn:ibm:params:oauth:grant-type:apikey") + v.Set("apikey", ts.APIKey) + reqBody := []byte(v.Encode()) + + u, err := url.Parse(ts.TokenURL) + if err != nil { + return nil, err + } + + // NewRequest will calculate Content-Length if we pass it a bytes.Buffer + // instead of a io.Reader type + bodyBuf := bytes.NewBuffer(reqBody) + request, err := rhttp.NewRequest("POST", u.String(), bodyBuf) + if err != nil { + return nil, err + } + + request.Header.Set("Content-Type", "application/x-www-form-urlencoded") + request.Header.Set("Accept", "application/json") + + // use hashicorp retryable client with max wait time and attempts from module vars + client := rhttp.NewClient() + client.Logger = nil + client.RetryWaitMax = RetryWaitMax + client.RetryMax = RetryMax + client.ErrorHandler = rhttp.PassthroughErrorHandler + + // need to use the go http DefaultTransport for tests to override with stubs (gock HTTP stubbing) + client.HTTPClient = &http.Client{ + Timeout: time.Duration(60) * time.Second, + } + + // this is the DefaultRetryPolicy but with retry on 429s as well + client.CheckRetry = func(ctx context.Context, resp *http.Response, err error) (bool, error) { + // do not retry on context.Canceled or context.DeadlineExceeded + if ctx.Err() != nil { + return false, ctx.Err() + } + + if err != nil { + return true, err + } + + // retry on connection error (code == 0), all 500s except 501, and 429s + if resp.StatusCode == 0 || (resp.StatusCode >= 500 && resp.StatusCode != 501) || resp.StatusCode == 429 { + return true, nil + } + + return false, nil + } + + resp, err := client.Do(request) + if err != nil { + return nil, err + } + + buf := new(bytes.Buffer) + if _, err := buf.ReadFrom(resp.Body); err != nil { + return nil, err + } + resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + var iamErr Error + if err = json.Unmarshal(buf.Bytes(), &iamErr); err != nil { + return nil, err + } + iamErr.HTTPResponse = resp + return nil, iamErr + } + + var jToken jsonToken + if err = json.Unmarshal(buf.Bytes(), &jToken); err != nil { + return nil, err + } + + token := &Token{ + AccessToken: jToken.AccessToken, + RefreshToken: jToken.RefreshToken, + TokenType: jToken.TokenType, + Expiry: jToken.getExpireTime(), + } + + ts.t = token + + return token, nil +} + +// Error is a type to hold error information that the IAM services sends back +// when a request cannot be completed. ErrorCode, ErrorMessage, and Context.RequestID +// are probably the most useful fields. IAM will most likely ask you for the RequestID +// if you ask for support. +// +// Also of note is that the http.Response object is included in HTTPResponse for +// error handling at the higher application levels. +type Error struct { + ErrorCode string `json:"errorCode"` + ErrorMessage string `json:"errorMessage"` + Context *iamRequestContext `json:"context"` + HTTPResponse *http.Response +} + +type iamRequestContext struct { + ClientIP string `json:"clientIp"` + ClusterName string `json:"clusterName"` + Host string `json:"host"` + InstanceID string `json:"instanceId"` + RequestID string `json:"requestId"` + RequestType string `json:"requestType"` + ElapsedTime string `json:"elapsedTime"` + StartTime string `json:"startTime"` + EndTime string `json:"endTime"` + ThreadID string `json:"threadId"` + URL string `json:"url"` + UserAgent string `json:"userAgent"` + Locale string `json:"locale"` +} + +func (ie Error) Error() string { + + reqId := "" + if ie.Context != nil { + reqId = ie.Context.RequestID + } + + statusCode := 0 + if ie.HTTPResponse != nil { + statusCode = ie.HTTPResponse.StatusCode + } + + return fmt.Sprintf("iam.Error: HTTP %d requestId='%s' message='%s %s'", + statusCode, reqId, ie.ErrorCode, ie.ErrorMessage) +} diff --git a/vendor/github.com/IBM/keyprotect-go-client/import_token.go b/vendor/github.com/IBM/keyprotect-go-client/import_token.go new file mode 100644 index 000000000..fcdd08599 --- /dev/null +++ b/vendor/github.com/IBM/keyprotect-go-client/import_token.go @@ -0,0 +1,219 @@ +// Copyright 2019 IBM Corp. +// +// 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 kp + +import ( + "bytes" + "context" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/rsa" + "crypto/sha1" + "crypto/sha256" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "fmt" + "hash" + "io" + "time" +) + +const importTokenEncAlgo = "RSAES_OAEP_SHA_256" // currently the only one supported + +// ImportTokenCreateRequest represents request parameters for creating a +// ImportToken. +type ImportTokenCreateRequest struct { + MaxAllowedRetrievals int `json:"maxAllowedRetrievals,omitempty"` + ExpiresInSeconds int `json:"expiration,omitempty"` +} + +// ImportTokenKeyResponse represents the response body for various ImportToken +// API calls. +type ImportTokenKeyResponse struct { + ID string `json:"id"` + CreationDate *time.Time `json:"creationDate"` + ExpirationDate *time.Time `json:"expirationDate"` + Payload string `json:"payload"` + Nonce string `json:"nonce"` +} + +// ImportTokenMetadata represents the metadata of a ImportToken. +type ImportTokenMetadata struct { + ID string `json:"id"` + CreationDate *time.Time `json:"creationDate"` + ExpirationDate *time.Time `json:"expirationDate"` + MaxAllowedRetrievals int `json:"maxAllowedRetrievals"` + RemainingRetrievals int `json:"remainingRetrievals"` +} + +// CreateImportToken creates a key ImportToken. +func (c *Client) CreateImportToken(ctx context.Context, expiration, maxAllowedRetrievals int) (*ImportTokenMetadata, error) { + reqBody := ImportTokenCreateRequest{ + MaxAllowedRetrievals: maxAllowedRetrievals, + ExpiresInSeconds: expiration, + } + + req, err := c.newRequest("POST", "import_token", &reqBody) + if err != nil { + return nil, err + } + + res := ImportTokenMetadata{} + if _, err := c.do(ctx, req, &res); err != nil { + return nil, err + } + + return &res, nil +} + +// GetImportTokenTransportKey retrieves the ImportToken transport key. +func (c *Client) GetImportTokenTransportKey(ctx context.Context) (*ImportTokenKeyResponse, error) { + res := ImportTokenKeyResponse{} + + req, err := c.newRequest("GET", "import_token", nil) + if err != nil { + return nil, err + } + + if _, err := c.do(ctx, req, &res); err != nil { + return nil, err + } + + return &res, nil +} + +// EncryptNonce will wrap the KP generated nonce with the users key-material +func EncryptNonce(key, value, iv string) (string, string, error) { + return encryptNonce(key, value, iv) +} + +// EncryptKey will encrypt the user key-material with the public key from key protect +func EncryptKey(key, pubkey string) (string, error) { + return encryptKey(key, pubkey) +} + +func encryptNonce(key, value, iv string) (string, string, error) { + var cipherText []byte + pubKey, err := base64.StdEncoding.DecodeString(key) + if err != nil { + return "", "", fmt.Errorf("Failed to decode public key: %s", err) + } + nonce, err := base64.StdEncoding.DecodeString(value) + if err != nil { + return "", "", fmt.Errorf("Failed to decode nonce: %s", err) + } + block, err := aes.NewCipher(pubKey) + if err != nil { + return "", "", err + } + aesgcm, err := cipher.NewGCM(block) + if err != nil { + return "", "", err + } + if iv == "" { + newIv := make([]byte, 12) + if _, err := io.ReadFull(rand.Reader, newIv); err != nil { + panic(err.Error()) + } + cipherText = aesgcm.Seal(nil, newIv, nonce, nil) + return base64.StdEncoding.EncodeToString(cipherText), base64.StdEncoding.EncodeToString(newIv), nil + } + cipherText = aesgcm.Seal(nil, []byte(iv), nonce, nil) + return base64.StdEncoding.EncodeToString(cipherText), iv, nil +} + +// EncryptNonceWithCBCPAD encrypts the nonce using the user's key-material +// with CBC encrypter. It will also pad the nonce using pkcs7. This is needed +// for Hyper Protect Crypto Services, since it supports only CBC Encryption. +func EncryptNonceWithCBCPAD(key, value, iv string) (string, string, error) { + keyMat, err := base64.StdEncoding.DecodeString(key) + if err != nil { + return "", "", fmt.Errorf("Failed to decode Key: %s", err) + } + + nonce, err := base64.StdEncoding.DecodeString(value) + if err != nil { + return "", "", fmt.Errorf("Failed to decode Nonce: %s", err) + } + + block, err := aes.NewCipher(keyMat) + if err != nil { + return "", "", err + } + + // PKCS7 Padding + paddingLength := aes.BlockSize - (len(nonce) % aes.BlockSize) + paddingBytes := []byte{byte(paddingLength)} + paddingText := bytes.Repeat(paddingBytes, paddingLength) + nonce = append(nonce, paddingText...) + + var newIv []byte + if iv != "" { + newIv = []byte(iv) + } else { + newIv = make([]byte, aes.BlockSize) + // Generate an IV to achieve semantic security + if _, err := io.ReadFull(rand.Reader, newIv); err != nil { + return "", "", fmt.Errorf("Failed to generate IV: %s", err) + } + } + + cipherText := make([]byte, len(nonce)) + + mode := cipher.NewCBCEncrypter(block, newIv) + mode.CryptBlocks(cipherText, nonce) + + return base64.StdEncoding.EncodeToString(cipherText), base64.StdEncoding.EncodeToString(newIv), nil +} + +// encryptKey uses sha256 to encrypt the key +func encryptKey(key, pubKey string) (string, error) { + return encryptKeyWithSHA(key, pubKey, sha256.New()) +} + +// EncryptKeyWithSHA1 uses sha1 to encrypt the key +func EncryptKeyWithSHA1(key, pubKey string) (string, error) { + return encryptKeyWithSHA(key, pubKey, sha1.New()) +} + +func encryptKeyWithSHA(key, pubKey string, sha hash.Hash) (string, error) { + decodedPubKey, err := base64.StdEncoding.DecodeString(pubKey) + if err != nil { + return "", fmt.Errorf("Failed to decode public key: %s", err) + } + keyMat, err := base64.StdEncoding.DecodeString(key) + if err != nil { + return "", fmt.Errorf("Failed to decode key material: %s", err) + } + pubKeyBlock, _ := pem.Decode(decodedPubKey) + if pubKeyBlock == nil { + return "", fmt.Errorf("Failed to decode public key into pem format: %s", err) + } + parsedPubKey, err := x509.ParsePKIXPublicKey(pubKeyBlock.Bytes) + if err != nil { + return "", fmt.Errorf("Failed to parse public key: %s", err) + } + publicKey, isRSAPublicKey := parsedPubKey.(*rsa.PublicKey) + if !isRSAPublicKey { + return "", fmt.Errorf("invalid public key") + } + encryptedKey, err := rsa.EncryptOAEP(sha, rand.Reader, publicKey, keyMat, []byte("")) + if err != nil { + return "", fmt.Errorf("Failed to encrypt key: %s", err) + } + return base64.StdEncoding.EncodeToString(encryptedKey), nil +} diff --git a/vendor/github.com/IBM/keyprotect-go-client/instances.go b/vendor/github.com/IBM/keyprotect-go-client/instances.go new file mode 100644 index 000000000..257950648 --- /dev/null +++ b/vendor/github.com/IBM/keyprotect-go-client/instances.go @@ -0,0 +1,543 @@ +// Copyright 2019 IBM Corp. +// +// 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 kp + +import ( + "context" + "fmt" + "net/url" + "time" +) + +const ( + // DualAuthDelete defines the policy type as dual auth delete + DualAuthDelete = "dualAuthDelete" + + // AllowedNetwork defines the policy type as allowed network + AllowedNetwork = "allowedNetwork" + + // AllowedIP defines the policy type as allowed ip that are whitelisted + AllowedIP = "allowedIP" + + // Metrics defines the policy type as metrics + Metrics = "metrics" + // KeyAccess defines the policy type as key create import access + KeyCreateImportAccess = "keyCreateImportAccess" + + // KeyAccess policy attributes + CreateRootKey = "CreateRootKey" + CreateStandardKey = "CreateStandardKey" + ImportRootKey = "ImportRootKey" + ImportStandardKey = "ImportStandardKey" + EnforceToken = "EnforceToken" +) + + +// InstancePolicy represents a instance-level policy of a key as returned by the KP API. +// this policy enables dual authorization for deleting a key +type InstancePolicy struct { + CreatedBy string `json:"createdBy,omitempty"` + CreatedAt *time.Time `json:"creationDate,omitempty"` + UpdatedAt *time.Time `json:"lastUpdated,omitempty"` + UpdatedBy string `json:"updatedBy,omitempty"` + PolicyType string `json:"policy_type,omitempty"` + PolicyData PolicyData `json:"policy_data,omitempty" mapstructure:"policyData"` +} + +// PolicyData contains the details of the policy type +type PolicyData struct { + Enabled *bool `json:"enabled,omitempty"` + Attributes *Attributes `json:"attributes,omitempty"` +} + +// Attributes contains the detals of allowed network policy type +type Attributes struct { + AllowedNetwork *string `json:"allowed_network,omitempty"` + AllowedIP IPAddresses `json:"allowed_ip,omitempty"` + CreateRootKey *bool `json:"create_root_key,omitempty"` + CreateStandardKey *bool `json:"create_standard_key,omitempty"` + ImportRootKey *bool `json:"import_root_key,omitempty"` + ImportStandardKey *bool `json:"import_standard_key,omitempty"` + EnforceToken *bool `json:"enforce_token,omitempty"` +} + +// IPAddresses ... +type IPAddresses []string + +// InstancePolicies represents a collection of Policies associated with Key Protect instances. +type InstancePolicies struct { + Metadata PoliciesMetadata `json:"metadata"` + Policies []InstancePolicy `json:"resources"` +} + +// GetDualAuthInstancePolicy retrieves the dual auth delete policy details associated with the instance +// For more information can refer the Key Protect docs in the link below: +// https://cloud.ibm.com/docs/key-protect?topic=key-protect-manage-dual-auth +func (c *Client) GetDualAuthInstancePolicy(ctx context.Context) (*InstancePolicy, error) { + policyResponse := InstancePolicies{} + + err := c.getInstancePolicy(ctx, DualAuthDelete, &policyResponse) + if err != nil { + return nil, err + } + + if len(policyResponse.Policies) == 0 { + return nil, nil + } + return &policyResponse.Policies[0], nil +} + +// GetAllowedNetworkInstancePolicy retrieves the allowed network policy details associated with the instance. +// For more information can refer the Key Protect docs in the link below: +// https://cloud.ibm.com/docs/key-protect?topic=key-protect-managing-network-access-policies +func (c *Client) GetAllowedNetworkInstancePolicy(ctx context.Context) (*InstancePolicy, error) { + policyResponse := InstancePolicies{} + + err := c.getInstancePolicy(ctx, AllowedNetwork, &policyResponse) + if err != nil { + return nil, err + } + + if len(policyResponse.Policies) == 0 { + return nil, nil + } + + return &policyResponse.Policies[0], nil +} + +// GetAllowedIPInstancePolicy retrieves the allowed IP instance policy details associated with the instance. +// For more information can refer the Key Protect docs in the link below: +// https://cloud.ibm.com/docs/key-protect?topic=key-protect-manage-allowed-ip +func (c *Client) GetAllowedIPInstancePolicy(ctx context.Context) (*InstancePolicy, error) { + policyResponse := InstancePolicies{} + + err := c.getInstancePolicy(ctx, AllowedIP, &policyResponse) + if err != nil { + return nil, err + } + + if len(policyResponse.Policies) == 0 { + return nil, nil + } + + return &policyResponse.Policies[0], nil +} + +// GetKeyCreateImportAccessInstancePolicy retrieves the key create import access policy details associated with the instance. +// For more information can refer the Key Protect docs in the link below: +// https://cloud.ibm.com/docs/key-protect?topic=key-protect-manage-keyCreateImportAccess +func (c *Client) GetKeyCreateImportAccessInstancePolicy(ctx context.Context) (*InstancePolicy, error) { + policyResponse := InstancePolicies{} + + err := c.getInstancePolicy(ctx, KeyCreateImportAccess, &policyResponse) + if err != nil { + return nil, err + } + + if len(policyResponse.Policies) == 0 { + return nil, nil + } + + return &policyResponse.Policies[0], nil +} + +func (c *Client) getInstancePolicy(ctx context.Context, policyType string, policyResponse *InstancePolicies) error { + req, err := c.newRequest("GET", "instance/policies", nil) + if err != nil { + return err + } + + v := url.Values{} + v.Set("policy", policyType) + req.URL.RawQuery = v.Encode() + + _, err = c.do(ctx, req, &policyResponse) + + return err +} + +// GetMetricsInstancePolicy retrieves the metrics policy details associated with the instance +// For more information can refer the Key Protect docs in the link below: +// https://cloud.ibm.com/docs/key-protect?topic=key-protect-manage-sysdig-metrics +func (c *Client) GetMetricsInstancePolicy(ctx context.Context) (*InstancePolicy, error) { + policyResponse := InstancePolicies{} + + err := c.getInstancePolicy(ctx, Metrics, &policyResponse) + if err != nil { + return nil, err + } + + if len(policyResponse.Policies) == 0 { + return nil, nil + } + return &policyResponse.Policies[0], nil +} + +// GetInstancePolicies retrieves all policies of an Instance. +func (c *Client) GetInstancePolicies(ctx context.Context) ([]InstancePolicy, error) { + policyresponse := InstancePolicies{} + + req, err := c.newRequest("GET", "instance/policies", nil) + if err != nil { + return nil, err + } + + _, err = c.do(ctx, req, &policyresponse) + if err != nil { + return nil, err + } + + return policyresponse.Policies, nil +} + +func (c *Client) setInstancePolicy(ctx context.Context, policyType string, policyRequest InstancePolicies) error { + req, err := c.newRequest("PUT", "instance/policies", &policyRequest) + if err != nil { + return err + } + + v := url.Values{} + v.Set("policy", policyType) + req.URL.RawQuery = v.Encode() + + policiesResponse := InstancePolicies{} + _, err = c.do(ctx, req, &policiesResponse) + + return err +} + +// SetDualAuthInstancePolicy updates the dual auth delete policy details associated with an instance +// For more information can refer the Key Protect docs in the link below: +// https://cloud.ibm.com/docs/key-protect?topic=key-protect-manage-dual-auth +func (c *Client) SetDualAuthInstancePolicy(ctx context.Context, enable bool) error { + policy := InstancePolicy{ + PolicyType: DualAuthDelete, + PolicyData: PolicyData{ + Enabled: &enable, + }, + } + + policyRequest := InstancePolicies{ + Metadata: PoliciesMetadata{ + CollectionType: policyType, + NumberOfPolicies: 1, + }, + Policies: []InstancePolicy{policy}, + } + + err := c.setInstancePolicy(ctx, DualAuthDelete, policyRequest) + + return err +} + +// SetAllowedIPInstancePolices updates the allowed IP instance policy details associated with an instance. +// For more information can refet to the Key Protect docs in the link below: +// https://cloud.ibm.com/docs/key-protect?topic=key-protect-manage-allowed-ip +func (c *Client) SetAllowedIPInstancePolicy(ctx context.Context, enable bool, allowedIPs []string) error { + + policy := InstancePolicy{ + PolicyType: AllowedIP, + PolicyData: PolicyData{ + Enabled: &enable, + }, + } + + // The IP address validation is performed by the key protect service. + if enable && len(allowedIPs) != 0 { + policy.PolicyData.Attributes = &Attributes{} + policy.PolicyData.Attributes.AllowedIP = allowedIPs + } else if enable && len(allowedIPs) == 0 { + return fmt.Errorf("Please provide at least 1 IP subnet specified with CIDR notation") + } else if !enable && len(allowedIPs) != 0 { + return fmt.Errorf("IP address list should only be provided if the policy is being enabled") + } + + policyRequest := InstancePolicies{ + Metadata: PoliciesMetadata{ + CollectionType: policyType, + NumberOfPolicies: 1, + }, + Policies: []InstancePolicy{policy}, + } + err := c.setInstancePolicy(ctx, AllowedIP, policyRequest) + + return err +} + +// SetAllowedNetWorkInstancePolicy updates the allowed network policy details associated with an instance +// For more information can refer to the Key Protect docs in the link below: +// https://cloud.ibm.com/docs/key-protect?topic=key-protect-managing-network-access-policies +func (c *Client) SetAllowedNetworkInstancePolicy(ctx context.Context, enable bool, networkType string) error { + policy := InstancePolicy{ + PolicyType: AllowedNetwork, + PolicyData: PolicyData{ + Enabled: &enable, + Attributes: &Attributes{}, + }, + } + if networkType != "" { + policy.PolicyData.Attributes.AllowedNetwork = &networkType + } + + policyRequest := InstancePolicies{ + Metadata: PoliciesMetadata{ + CollectionType: policyType, + NumberOfPolicies: 1, + }, + Policies: []InstancePolicy{policy}, + } + + err := c.setInstancePolicy(ctx, AllowedNetwork, policyRequest) + + return err +} + +// SetMetricsInstancePolicy updates the metrics policy details associated with an instance +// For more information can refer the Key Protect docs in the link below: +// https://cloud.ibm.com/docs/key-protect?topic=key-protect-manage-sysdig-metrics +func (c *Client) SetMetricsInstancePolicy(ctx context.Context, enable bool) error { + policy := InstancePolicy{ + PolicyType: Metrics, + PolicyData: PolicyData{ + Enabled: &enable, + }, + } + + policyRequest := InstancePolicies{ + Metadata: PoliciesMetadata{ + CollectionType: policyType, + NumberOfPolicies: 1, + }, + Policies: []InstancePolicy{policy}, + } + + err := c.setInstancePolicy(ctx, Metrics, policyRequest) + if err != nil { + return err + } + + return err +} + +// SetKeyCreateImportAccessInstancePolicy updates the key create import access policy details associated with an instance. +// For more information, please refer to the Key Protect docs in the link below: +// https://cloud.ibm.com/docs/key-protect?topic=key-protect-manage-keyCreateImportAccess +func (c *Client) SetKeyCreateImportAccessInstancePolicy(ctx context.Context, enable bool, attributes map[string]bool) error { + policy := InstancePolicy{ + PolicyType: KeyCreateImportAccess, + PolicyData: PolicyData{ + Enabled: &enable, + }, + } + + if enable { + policy.PolicyData.Attributes = &Attributes{} + a := policy.PolicyData.Attributes + if val, ok := attributes[CreateRootKey]; ok { + a.CreateRootKey = &val + } + if val, ok := attributes[CreateStandardKey]; ok { + a.CreateStandardKey = &val + } + if val, ok := attributes[ImportRootKey]; ok { + a.ImportRootKey = &val + } + if val, ok := attributes[ImportStandardKey]; ok { + a.ImportStandardKey = &val + } + if val, ok := attributes[EnforceToken]; ok { + a.EnforceToken = &val + } + } + + policyRequest := InstancePolicies{ + Metadata: PoliciesMetadata{ + CollectionType: policyType, + NumberOfPolicies: 1, + }, + Policies: []InstancePolicy{policy}, + } + + err := c.setInstancePolicy(ctx, KeyCreateImportAccess, policyRequest) + + return err +} + +// BasicPolicyData defines the attribute input for the policy that supports only enabled parameter +type BasicPolicyData struct { + Enabled bool +} + +// AllowedNetworkPolicyData defines the attribute input for the Allowed Network instance policy +type AllowedNetworkPolicyData struct { + Enabled bool + Network string +} + +// AllowedIPPolicyData defines the attribute input for the Allowed IP instance policy +type AllowedIPPolicyData struct { + Enabled bool + IPAddresses IPAddresses +} + +// KeyAccessInstancePolicyData defines the attribute input for the Key Create Import Access instance policy +type KeyCreateImportAccessInstancePolicy struct { + Enabled bool + CreateRootKey bool + CreateStandardKey bool + ImportRootKey bool + ImportStandardKey bool + EnforceToken bool +} + +// MultiplePolicies defines the input for the SetInstancPolicies method that can hold multiple policy details +type MultiplePolicies struct { + DualAuthDelete *BasicPolicyData + AllowedNetwork *AllowedNetworkPolicyData + AllowedIP *AllowedIPPolicyData + Metrics *BasicPolicyData + KeyCreateImportAccess *KeyCreateImportAccessInstancePolicy +} + +// SetInstancePolicies updates single or multiple policy details of an instance. +func (c *Client) SetInstancePolicies(ctx context.Context, policies MultiplePolicies) error { + var resPolicies []InstancePolicy + + if policies.DualAuthDelete != nil { + policy := InstancePolicy{ + PolicyType: DualAuthDelete, + PolicyData: PolicyData{ + Enabled: &(policies.DualAuthDelete.Enabled), + }, + } + resPolicies = append(resPolicies, policy) + } + + if policies.AllowedNetwork != nil { + policy := InstancePolicy{ + PolicyType: AllowedNetwork, + PolicyData: PolicyData{ + Enabled: &(policies.AllowedNetwork.Enabled), + Attributes: &Attributes{ + AllowedNetwork: &(policies.AllowedNetwork.Network), + }, + }, + } + resPolicies = append(resPolicies, policy) + } + + if policies.AllowedIP != nil { + policy := InstancePolicy{ + PolicyType: AllowedIP, + PolicyData: PolicyData{ + Enabled: &(policies.AllowedIP.Enabled), + Attributes: &Attributes{ + AllowedIP: policies.AllowedIP.IPAddresses, + }, + }, + } + resPolicies = append(resPolicies, policy) + } + + if policies.Metrics != nil { + policy := InstancePolicy{ + PolicyType: Metrics, + PolicyData: PolicyData{ + Enabled: &(policies.Metrics.Enabled), + }, + } + resPolicies = append(resPolicies, policy) + } + + if policies.KeyCreateImportAccess != nil { + policy := InstancePolicy{ + PolicyType: KeyCreateImportAccess, + PolicyData: PolicyData{ + Enabled: &(policies.KeyCreateImportAccess.Enabled), + Attributes: &Attributes{}, + }, + } + + if policies.KeyCreateImportAccess.CreateRootKey { + policy.PolicyData.Attributes.CreateRootKey = &policies.KeyCreateImportAccess.CreateRootKey + } + if policies.KeyCreateImportAccess.CreateStandardKey { + policy.PolicyData.Attributes.CreateStandardKey = &policies.KeyCreateImportAccess.CreateStandardKey + } + if policies.KeyCreateImportAccess.ImportRootKey { + policy.PolicyData.Attributes.ImportRootKey = &policies.KeyCreateImportAccess.ImportRootKey + } + if policies.KeyCreateImportAccess.ImportStandardKey { + policy.PolicyData.Attributes.ImportStandardKey = &policies.KeyCreateImportAccess.ImportStandardKey + } + if policies.KeyCreateImportAccess.EnforceToken { + policy.PolicyData.Attributes.EnforceToken = &policies.KeyCreateImportAccess.EnforceToken + } + + resPolicies = append(resPolicies, policy) + } + + policyRequest := InstancePolicies{ + Metadata: PoliciesMetadata{ + CollectionType: policyType, + NumberOfPolicies: len(resPolicies), + }, + Policies: resPolicies, + } + + policyresponse := Policies{} + + req, err := c.newRequest("PUT", "instance/policies", &policyRequest) + if err != nil { + return err + } + + _, err = c.do(ctx, req, &policyresponse) + + return err +} + +type portsMetadata struct { + CollectionType string `json:"collectionType"` + NumberOfPorts int `json:"collectionTotal"` +} + +type portResponse struct { + Metadata portsMetadata `json:"metadata"` + Ports []privatePort `json:"resources"` +} +type privatePort struct { + PrivatePort int `json:"private_endpoint_port,omitempty"` +} + +// GetAllowedIPPrivateNetworkPort retrieves the private endpoint port assigned to allowed ip policy. +func (c *Client) GetAllowedIPPrivateNetworkPort(ctx context.Context) (int, error) { + var portResponse portResponse + + req, err := c.newRequest("GET", "instance/allowed_ip_port", nil) + if err != nil { + return 0, err + } + + _, err = c.do(ctx, req, &portResponse) + if err != nil { + return 0, err + } + + if len(portResponse.Ports) == 0 { + return 0, fmt.Errorf("No port number available. Please check the instance has an enabled allowedIP policy") + } + return portResponse.Ports[0].PrivatePort, nil +} diff --git a/vendor/github.com/IBM/keyprotect-go-client/key_alias.go b/vendor/github.com/IBM/keyprotect-go-client/key_alias.go new file mode 100644 index 000000000..fdb63f9e9 --- /dev/null +++ b/vendor/github.com/IBM/keyprotect-go-client/key_alias.go @@ -0,0 +1,68 @@ +package kp + +import ( + "context" + "fmt" + "time" +) + +var ( + requestPath = "keys/%s/aliases/%s" +) + +// KeyAlias represents an Alias details of a key as returned by KP API +type KeyAlias struct { + KeyID string `json:"keyId,omitempty"` + Alias string `json:"alias,omitempty"` + CreatedBy string `json:"createdBy,omitempty"` + CreationDate *time.Time `json:"creationDate,omitempty"` +} + +// AliasesMetadata represents the metadata of a collection of aliases +type AliasesMetadata struct { + CollectionType string `json:"collectionType"` + NumberOfAliases int `json:"collectionTotal"` +} + +type KeyAliases struct { + Metadata AliasesMetadata `json:"metadata"` + KeyAliases []KeyAlias `json:"resources"` +} + +// CreateKeyAlias creates an alias name for a key. +// An alias name acts as an identifier just like key ID +// For more information please refer to the link below: +// https://cloud.ibm.com/docs/key-protect?topic=key-protect-create-key-alias#create-key-alias-api +func (c *Client) CreateKeyAlias(ctx context.Context, aliasName, keyID string) (*KeyAlias, error) { + + req, err := c.newRequest("POST", fmt.Sprintf(requestPath, keyID, aliasName), nil) + if err != nil { + return nil, err + } + + aliasesResponse := KeyAliases{} + _, err = c.do(ctx, req, &aliasesResponse) + if err != nil { + return nil, err + } + + if len(aliasesResponse.KeyAliases) == 0 { + return nil, nil + } + + return &aliasesResponse.KeyAliases[0], nil +} + +// DeleteKeyAlias deletes an alias name associated with a key +// For more information please refer to the link below: +// https://cloud.ibm.com/docs/key-protect?topic=key-protect-create-key-alias#delete-key-alias +func (c *Client) DeleteKeyAlias(ctx context.Context, aliasName, keyID string) error { + + req, err := c.newRequest("DELETE", fmt.Sprintf(requestPath, keyID, aliasName), nil) + if err != nil { + return err + } + _, err = c.do(ctx, req, nil) + + return err +} diff --git a/vendor/github.com/IBM/keyprotect-go-client/key_rings.go b/vendor/github.com/IBM/keyprotect-go-client/key_rings.go new file mode 100644 index 000000000..eddb0710f --- /dev/null +++ b/vendor/github.com/IBM/keyprotect-go-client/key_rings.go @@ -0,0 +1,75 @@ +package kp + +import ( + "context" + "fmt" + "time" +) + +const ( + path = "key_rings" +) + +type KeyRing struct { + ID string `json:"id,omitempty"` + CreationDate *time.Time `json:"creationDate,omitempty"` + CreatedBy string `json:"createdBy,omitempty"` +} + +type KeyRings struct { + Metadata KeysMetadata `json:"metadata"` + KeyRings []KeyRing `json:"resources"` +} + +// CreateRing method creates a key ring in the instance with the provided name +// For information please refer to the link below: +// https://cloud.ibm.com/docs/key-protect?topic=key-protect-managing-key-rings#create-key-ring-api +func (c *Client) CreateKeyRing(ctx context.Context, id string) error { + + req, err := c.newRequest("POST", fmt.Sprintf(path+"/%s", id), nil) + if err != nil { + return err + } + + _, err = c.do(ctx, req, nil) + if err != nil { + return err + } + + return nil +} + +// GetRings method retrieves all the key rings associated with the instance +// For information please refer to the link below: +// https://cloud.ibm.com/docs/key-protect?topic=key-protect-managing-key-rings#list-key-ring-api +func (c *Client) GetKeyRings(ctx context.Context) (*KeyRings, error) { + rings := KeyRings{} + req, err := c.newRequest("GET", path, nil) + if err != nil { + return nil, err + } + + _, err = c.do(ctx, req, &rings) + if err != nil { + return nil, err + } + + return &rings, nil +} + +// DeleteRing method deletes the key ring with the provided name in the instance +// For information please refer to the link below: +// https://cloud.ibm.com/docs/key-protect?topic=key-protect-managing-key-rings#delete-key-ring-api +func (c *Client) DeleteKeyRing(ctx context.Context, id string) error { + req, err := c.newRequest("DELETE", fmt.Sprintf(path+"/%s", id), nil) + if err != nil { + return err + } + + _, err = c.do(ctx, req, nil) + if err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/IBM/keyprotect-go-client/keys.go b/vendor/github.com/IBM/keyprotect-go-client/keys.go new file mode 100644 index 000000000..d87821951 --- /dev/null +++ b/vendor/github.com/IBM/keyprotect-go-client/keys.go @@ -0,0 +1,463 @@ +// Copyright 2019 IBM Corp. +// +// 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 kp + +import ( + "context" + "encoding/base64" + "fmt" + "log" + "net/url" + "strconv" + "time" +) + +const ( + ReturnMinimal PreferReturn = 0 + ReturnRepresentation PreferReturn = 1 + + keyType = "application/vnd.ibm.kms.key+json" +) + +var ( + preferHeaders = []string{"return=minimal", "return=representation"} +) + +// PreferReturn designates the value for the "Prefer" header. +type PreferReturn int + +// Key represents a key as returned by the KP API. +type Key struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Type string `json:"type,omitempty"` + Tags []string `json:"Tags,omitempty"` + Aliases []string `json:"aliases,omitempty"` + AlgorithmType string `json:"algorithmType,omitempty"` + CreatedBy string `json:"createdBy,omitempty"` + CreationDate *time.Time `json:"creationDate,omitempty"` + LastUpdateDate *time.Time `json:"lastUpdateDate,omitempty"` + LastRotateDate *time.Time `json:"lastRotateDate,omitempty"` + KeyVersion *KeyVersion `json:"keyVersion,omitempty" mapstructure:keyVersion` + KeyRingID string `json:"keyRingID,omitempty"` + Extractable bool `json:"extractable"` + Expiration *time.Time `json:"expirationDate,omitempty"` + Imported bool `json:"imported,omitempty"` + Payload string `json:"payload,omitempty"` + State int `json:"state,omitempty"` + EncryptionAlgorithm string `json:"encryptionAlgorithm,omitempty"` + CRN string `json:"crn,omitempty"` + EncryptedNonce string `json:"encryptedNonce,omitempty"` + IV string `json:"iv,omitempty"` + Deleted *bool `json:"deleted,omitempty"` + DeletedBy *string `json:"deletedBy,omitempty"` + DeletionDate *time.Time `json:"deletionDate,omitempty"` + DualAuthDelete *DualAuth `json:"dualAuthDelete,omitempty"` +} + +// KeysMetadata represents the metadata of a collection of keys. +type KeysMetadata struct { + CollectionType string `json:"collectionType"` + NumberOfKeys int `json:"collectionTotal"` +} + +// Keys represents a collection of Keys. +type Keys struct { + Metadata KeysMetadata `json:"metadata"` + Keys []Key `json:"resources"` +} + +// KeysActionRequest represents request parameters for a key action +// API call. +type KeysActionRequest struct { + PlainText string `json:"plaintext,omitempty"` + AAD []string `json:"aad,omitempty"` + CipherText string `json:"ciphertext,omitempty"` + Payload string `json:"payload,omitempty"` +} + +type KeyVersion struct { + ID string `json:"id,omitempty"` + CreationDate *time.Time `json:"creationDate,omitempty"` +} + +// CreateKey creates a new KP key. +func (c *Client) CreateKey(ctx context.Context, name string, expiration *time.Time, extractable bool) (*Key, error) { + return c.CreateImportedKey(ctx, name, expiration, "", "", "", extractable) +} + +// CreateImportedKey creates a new KP key from the given key material. +func (c *Client) CreateImportedKey(ctx context.Context, name string, expiration *time.Time, payload, encryptedNonce, iv string, extractable bool) (*Key, error) { + key := Key{ + Name: name, + Type: keyType, + Extractable: extractable, + Payload: payload, + } + + if payload != "" && encryptedNonce != "" && iv != "" { + key.EncryptedNonce = encryptedNonce + key.IV = iv + key.EncryptionAlgorithm = importTokenEncAlgo + } + + if expiration != nil { + key.Expiration = expiration + } + + return c.createKey(ctx, key) +} + +// CreateRootKey creates a new, non-extractable key resource without +// key material. +func (c *Client) CreateRootKey(ctx context.Context, name string, expiration *time.Time) (*Key, error) { + return c.CreateKey(ctx, name, expiration, false) +} + +// CreateStandardKey creates a new, extractable key resource without +// key material. +func (c *Client) CreateStandardKey(ctx context.Context, name string, expiration *time.Time) (*Key, error) { + return c.CreateKey(ctx, name, expiration, true) +} + +// CreateImportedRootKey creates a new, non-extractable key resource +// with the given key material. +func (c *Client) CreateImportedRootKey(ctx context.Context, name string, expiration *time.Time, payload, encryptedNonce, iv string) (*Key, error) { + return c.CreateImportedKey(ctx, name, expiration, payload, encryptedNonce, iv, false) +} + +// CreateStandardKey creates a new, extractable key resource with the +// given key material. +func (c *Client) CreateImportedStandardKey(ctx context.Context, name string, expiration *time.Time, payload string) (*Key, error) { + return c.CreateImportedKey(ctx, name, expiration, payload, "", "", true) +} + +// CreateKeyWithAliaes creats a new key with alias names. A key can have a maximum of 5 alias names. +// For more information please refer to the links below: +// https://cloud.ibm.com/docs/key-protect?topic=key-protect-create-root-keys#create-root-key-api +// https://cloud.ibm.com/docs/key-protect?topic=key-protect-create-standard-keys#create-standard-key-api +func (c *Client) CreateKeyWithAliases(ctx context.Context, name string, expiration *time.Time, extractable bool, aliases []string) (*Key, error) { + return c.CreateImportedKeyWithAliases(ctx, name, expiration, "", "", "", extractable, aliases) +} + +// CreateImportedKeyWithAliases creates a new key with alias name and provided key material. A key can have a maximum of 5 alias names +// When importing root keys with import-token encryptedNonce and iv need to passed along with payload. +// Standard Keys cannot be imported with an import token hence only payload is required. +// For more information please refer to the links below: +// https://cloud.ibm.com/docs/key-protect?topic=key-protect-import-root-keys#import-root-key-api +// https://cloud.ibm.com/docs/key-protect?topic=key-protect-import-standard-keys#import-standard-key-gui +func (c *Client) CreateImportedKeyWithAliases(ctx context.Context, name string, expiration *time.Time, payload, encryptedNonce, iv string, extractable bool, aliases []string) (*Key, error) { + key := Key{ + Name: name, + Type: keyType, + Extractable: extractable, + Payload: payload, + Aliases: aliases, + } + + if !extractable && payload != "" && encryptedNonce != "" && iv != "" { + key.EncryptedNonce = encryptedNonce + key.IV = iv + key.EncryptionAlgorithm = importTokenEncAlgo + } + + if expiration != nil { + key.Expiration = expiration + } + + return c.createKey(ctx, key) +} + +func (c *Client) createKey(ctx context.Context, key Key) (*Key, error) { + keysRequest := Keys{ + Metadata: KeysMetadata{ + CollectionType: keyType, + NumberOfKeys: 1, + }, + Keys: []Key{key}, + } + + req, err := c.newRequest("POST", "keys", &keysRequest) + if err != nil { + return nil, err + } + + keysResponse := Keys{} + if _, err := c.do(ctx, req, &keysResponse); err != nil { + return nil, err + } + + return &keysResponse.Keys[0], nil +} + +// GetKeys retrieves a collection of keys that can be paged through. +func (c *Client) GetKeys(ctx context.Context, limit int, offset int) (*Keys, error) { + if limit == 0 { + limit = 2000 + } + + req, err := c.newRequest("GET", "keys", nil) + if err != nil { + return nil, err + } + + v := url.Values{} + v.Set("limit", strconv.Itoa(limit)) + v.Set("offset", strconv.Itoa(offset)) + req.URL.RawQuery = v.Encode() + + keys := Keys{} + _, err = c.do(ctx, req, &keys) + if err != nil { + return nil, err + } + + return &keys, nil +} + +// GetKey retrieves a key by ID or alias name. +// For more information on Key Alias please refer to the link below +// https://cloud.ibm.com/docs/key-protect?topic=key-protect-retrieve-key +func (c *Client) GetKey(ctx context.Context, idOrAlias string) (*Key, error) { + return c.getKey(ctx, idOrAlias, "keys/%s") +} + +// GetKeyMetadata retrieves the metadata of a Key by ID or alias name. +// Note that the "/api/v2/keys/{id}/metadata" API does not return the payload, +// therefore the payload attribute in the Key pointer will always be empty. +// If you need the payload, you need to use the GetKey() function with the +// correct service access role. +// https://cloud.ibm.com/docs/key-protect?topic=key-protect-manage-access#service-access-roles +func (c *Client) GetKeyMetadata(ctx context.Context, idOrAlias string) (*Key, error) { + return c.getKey(ctx, idOrAlias, "keys/%s/metadata") +} + +func (c *Client) getKey(ctx context.Context, id string, path string) (*Key, error) { + keys := Keys{} + + req, err := c.newRequest("GET", fmt.Sprintf(path, id), nil) + if err != nil { + return nil, err + } + + _, err = c.do(ctx, req, &keys) + if err != nil { + return nil, err + } + + return &keys.Keys[0], nil +} + +type CallOpt interface{} + +type ForceOpt struct { + Force bool +} + +// DeleteKey deletes a key resource by specifying the ID of the key. +func (c *Client) DeleteKey(ctx context.Context, id string, prefer PreferReturn, callOpts ...CallOpt) (*Key, error) { + + req, err := c.newRequest("DELETE", fmt.Sprintf("keys/%s", id), nil) + if err != nil { + return nil, err + } + + for _, opt := range callOpts { + switch v := opt.(type) { + case ForceOpt: + params := url.Values{} + params.Set("force", strconv.FormatBool(v.Force)) + req.URL.RawQuery = params.Encode() + default: + log.Printf("WARNING: Ignoring invalid CallOpt passed to DeleteKey: %v\n", v) + } + } + + req.Header.Set("Prefer", preferHeaders[prefer]) + + keys := Keys{} + _, err = c.do(ctx, req, &keys) + if err != nil { + return nil, err + } + + if len(keys.Keys) > 0 { + return &keys.Keys[0], nil + } + + return nil, nil +} + +// RestoreKey method reverts a delete key status to active key +// This method performs restore of any key from deleted state to active state. +// For more information please refer to the link below: +// https://cloud.ibm.com/dowcs/key-protect?topic=key-protect-restore-keys +func (c *Client) RestoreKey(ctx context.Context, id string) (*Key, error) { + req, err := c.newRequest("POST", fmt.Sprintf("keys/%s/restore", id), nil) + if err != nil { + return nil, err + } + + keysResponse := Keys{} + + _, err = c.do(ctx, req, &keysResponse) + if err != nil { + return nil, err + } + + return &keysResponse.Keys[0], nil +} + +// Wrap calls the wrap action with the given plain text. +func (c *Client) Wrap(ctx context.Context, id string, plainText []byte, additionalAuthData *[]string) ([]byte, error) { + _, ct, err := c.wrap(ctx, id, plainText, additionalAuthData) + return ct, err +} + +// WrapCreateDEK calls the wrap action without plain text. +func (c *Client) WrapCreateDEK(ctx context.Context, id string, additionalAuthData *[]string) ([]byte, []byte, error) { + return c.wrap(ctx, id, nil, additionalAuthData) +} + +func (c *Client) wrap(ctx context.Context, id string, plainText []byte, additionalAuthData *[]string) ([]byte, []byte, error) { + keysActionReq := &KeysActionRequest{} + + if plainText != nil { + _, err := base64.StdEncoding.DecodeString(string(plainText)) + if err != nil { + return nil, nil, err + } + keysActionReq.PlainText = string(plainText) + } + + if additionalAuthData != nil { + keysActionReq.AAD = *additionalAuthData + } + + keysAction, err := c.doKeysAction(ctx, id, "wrap", keysActionReq) + if err != nil { + return nil, nil, err + } + + pt := []byte(keysAction.PlainText) + ct := []byte(keysAction.CipherText) + + return pt, ct, nil +} + +// Unwrap is deprecated since it returns only plaintext and doesn't know how to handle rotation. +func (c *Client) Unwrap(ctx context.Context, id string, cipherText []byte, additionalAuthData *[]string) ([]byte, error) { + plainText, _, err := c.UnwrapV2(ctx, id, cipherText, additionalAuthData) + if err != nil { + return nil, err + } + return plainText, nil +} + +// Unwrap with rotation support. +func (c *Client) UnwrapV2(ctx context.Context, id string, cipherText []byte, additionalAuthData *[]string) ([]byte, []byte, error) { + + keysAction := &KeysActionRequest{ + CipherText: string(cipherText), + } + + if additionalAuthData != nil { + keysAction.AAD = *additionalAuthData + } + + respAction, err := c.doKeysAction(ctx, id, "unwrap", keysAction) + if err != nil { + return nil, nil, err + } + + plainText := []byte(respAction.PlainText) + rewrapped := []byte(respAction.CipherText) + + return plainText, rewrapped, nil +} + +// Rotate rotates a CRK. +func (c *Client) Rotate(ctx context.Context, id, payload string) error { + + actionReq := &KeysActionRequest{ + Payload: payload, + } + + _, err := c.doKeysAction(ctx, id, "rotate", actionReq) + if err != nil { + return err + } + + return nil +} + +// Disable a key. The key will not be deleted but it will not be active +// and key operations cannot be performed on a disabled key. +// For more information can refer to the Key Protect docs in the link below: +// https://cloud.ibm.com/docs/key-protect?topic=key-protect-disable-keys +func (c *Client) DisableKey(ctx context.Context, id string) error { + _, err := c.doKeysAction(ctx, id, "disable", nil) + return err +} + +// Enable a key. Only disabled keys can be enabled. After enable +// the key becomes active and key operations can be performed on it. +// Note: This does not recover Deleted keys. +// For more information can refer to the Key Protect docs in the link below: +// https://cloud.ibm.com/docs/key-protect?topic=key-protect-disable-keys#enable-api +func (c *Client) EnableKey(ctx context.Context, id string) error { + _, err := c.doKeysAction(ctx, id, "enable", nil) + return err +} + +// InitiateDualAuthDelete sets a key for deletion. The key must be configured with a DualAuthDelete policy. +// After the key is set to deletion it can be deleted by another user who has Manager access. +// For more information refer to the Key Protect docs in the link below: +// https://cloud.ibm.com/docs/key-protect?topic=key-protect-delete-dual-auth-keys#set-key-deletion-api +func (c *Client) InitiateDualAuthDelete(ctx context.Context, id string) error { + _, err := c.doKeysAction(ctx, id, "setKeyForDeletion", nil) + return err +} + +// CancelDualAuthDelete unsets the key for deletion. If a key is set for deletion, it can +// be prevented from getting deleted by unsetting the key for deletion. +// For more information refer to the Key Protect docs in the link below: +//https://cloud.ibm.com/docs/key-protect?topic=key-protect-delete-dual-auth-keys#unset-key-deletion-api +func (c *Client) CancelDualAuthDelete(ctx context.Context, id string) error { + _, err := c.doKeysAction(ctx, id, "unsetKeyForDeletion", nil) + return err +} + +// doKeysAction calls the KP Client to perform an action on a key. +func (c *Client) doKeysAction(ctx context.Context, id string, action string, keysActionReq *KeysActionRequest) (*KeysActionRequest, error) { + keyActionRsp := KeysActionRequest{} + + v := url.Values{} + v.Set("action", action) + + req, err := c.newRequest("POST", fmt.Sprintf("keys/%s", id), keysActionReq) + if err != nil { + return nil, err + } + + req.URL.RawQuery = v.Encode() + + _, err = c.do(ctx, req, &keyActionRsp) + if err != nil { + return nil, err + } + return &keyActionRsp, nil +} diff --git a/vendor/github.com/IBM/keyprotect-go-client/kp.go b/vendor/github.com/IBM/keyprotect-go-client/kp.go new file mode 100644 index 000000000..bcadb064b --- /dev/null +++ b/vendor/github.com/IBM/keyprotect-go-client/kp.go @@ -0,0 +1,510 @@ +// Copyright 2019 IBM Corp. +// +// 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. + +// keyprotect-go-client is a Go client library for interacting with the IBM KeyProtect service. +package kp + +import ( + "bytes" + "context" + "crypto/tls" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "strings" + "time" + + "github.com/google/uuid" + rhttp "github.com/hashicorp/go-retryablehttp" + + "github.com/IBM/keyprotect-go-client/iam" +) + +const ( + // DefaultBaseURL ... + DefaultBaseURL = "https://us-south.kms.cloud.ibm.com" + // DefaultTokenURL .. + DefaultTokenURL = iam.IAMTokenURL + + // VerboseNone ... + VerboseNone = 0 + // VerboseBodyOnly ... + VerboseBodyOnly = 1 + // VerboseAll ... + VerboseAll = 2 + // VerboseFailOnly ... + VerboseFailOnly = 3 + // VerboseAllNoRedact ... + VerboseAllNoRedact = 4 + + authContextKey ContextKey = 0 + defaultTimeout = 30 // in seconds. +) + +var ( + // RetryWaitMax is the maximum time to wait between HTTP retries + RetryWaitMax = 30 * time.Second + + // RetryMax is the max number of attempts to retry for failed HTTP requests + RetryMax = 4 + + cidCtxKey = ctxKey("X-Correlation-Id") +) + +type ctxKey string + +// ClientConfig ... +type ClientConfig struct { + BaseURL string + Authorization string // The IBM Cloud (Bluemix) access token + APIKey string // Service ID API key, can be used instead of an access token + TokenURL string // The URL used to get an access token from the API key + InstanceID string // The IBM Cloud (Bluemix) instance ID that identifies your Key Protect service instance. + KeyRing string // The ID of the target Key Ring the key is associated with. It is optional but recommended for better performance. + Verbose int // See verbose values above + Timeout float64 // KP request timeout in seconds. +} + +// DefaultTransport ... +func DefaultTransport() http.RoundTripper { + transport := &http.Transport{ + DisableKeepAlives: true, + MaxIdleConnsPerHost: -1, + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: false, + }, + } + return transport +} + +// API is deprecated. Use Client instead. +type API = Client + +// Client holds configuration and auth information to interact with KeyProtect. +// It is expected that one of these is created per KeyProtect service instance/credential pair. +type Client struct { + URL *url.URL + HttpClient http.Client + Dump Dump + Config ClientConfig + Logger Logger + + tokenSource iam.TokenSource +} + +// New creates and returns a Client without logging. +func New(config ClientConfig, transport http.RoundTripper) (*Client, error) { + return NewWithLogger(config, transport, nil) +} + +// NewWithLogger creates and returns a Client with logging. The +// error value will be non-nil if the config is invalid. +func NewWithLogger(config ClientConfig, transport http.RoundTripper, logger Logger) (*Client, error) { + + if transport == nil { + transport = DefaultTransport() + } + + if logger == nil { + logger = NewLogger(func(args ...interface{}) { + fmt.Println(args...) + }) + } + + if config.Verbose > len(dumpers)-1 || config.Verbose < 0 { + return nil, errors.New("verbose value is out of range") + } + + if config.Timeout == 0 { + config.Timeout = defaultTimeout + } + keysURL := fmt.Sprintf("%s/api/v2/", config.BaseURL) + + u, err := url.Parse(keysURL) + if err != nil { + return nil, err + } + + ts := iam.CredentialFromAPIKey(config.APIKey) + + if config.TokenURL != "" { + ts.TokenURL = config.TokenURL + } + + c := &Client{ + URL: u, + HttpClient: http.Client{ + Timeout: time.Duration(config.Timeout * float64(time.Second)), + Transport: transport, + }, + Dump: dumpers[config.Verbose], + Config: config, + Logger: logger, + tokenSource: ts, + } + return c, nil +} + +func (c *Client) newRequest(method, path string, body interface{}) (*http.Request, error) { + + u, err := c.URL.Parse(path) + if err != nil { + return nil, err + } + + var reqBody []byte + var buf io.Reader + + if body != nil { + reqBody, err = json.Marshal(body) + if err != nil { + return nil, err + } + buf = bytes.NewBuffer(reqBody) + } + + request, err := http.NewRequest(method, u.String(), buf) + if err != nil { + return nil, err + } + + request.Header.Set("accept", "application/json") + + return request, nil +} + +type reason struct { + Code string + Message string + Status int + MoreInfo string +} + +func (r reason) String() string { + if r.MoreInfo != "" { + return fmt.Sprintf("%s: %s - FOR_MORE_INFO_REFER: %s", r.Code, r.Message, r.MoreInfo) + } + return fmt.Sprintf("%s: %s", r.Code, r.Message) +} + +type Error struct { + URL string // URL of request that resulted in this error + StatusCode int // HTTP error code from KeyProtect service + Message string // error message from KeyProtect service + BodyContent []byte // raw body content if more inspection is needed + CorrelationID string // string value of a UUID that uniquely identifies the request to KeyProtect + Reasons []reason // collection of reason types containing detailed error messages +} + +// Error returns correlation id and error message string +func (e Error) Error() string { + var extraVars string + if e.Reasons != nil && len(e.Reasons) > 0 { + extraVars = fmt.Sprintf(", reasons='%s'", e.Reasons) + } + + return fmt.Sprintf("kp.Error: correlation_id='%v', msg='%s'%s", e.CorrelationID, e.Message, extraVars) +} + +// URLError wraps an error from client.do() calls with a correlation ID from KeyProtect +type URLError struct { + Err error + CorrelationID string +} + +func (e URLError) Error() string { + return fmt.Sprintf( + "error during request to KeyProtect correlation_id='%s': %s", e.CorrelationID, e.Err.Error()) +} + +func (c *Client) do(ctx context.Context, req *http.Request, res interface{}) (*http.Response, error) { + + acccesToken, err := c.getAccessToken(ctx) + if err != nil { + return nil, err + } + + // retrieve the correlation id from the context. If not present, then a UUID will be + // generated for the correlation ID and feed it into the request + // KeyProtect will use this when it is set on a request header rather than generating its + // own inside the service + // if not present, we generate our own here because a connection error might actually + // mean the request doesn't make it server side, so having a correlation ID locally helps + // us know that when comparing with server side logs. + corrID := c.getCorrelationID(ctx) + + req.Header.Set("bluemix-instance", c.Config.InstanceID) + req.Header.Set("authorization", acccesToken) + req.Header.Set("correlation-id", corrID) + + if c.Config.KeyRing != "" { + req.Header.Set("x-kms-key-ring", c.Config.KeyRing) + } + + // set request up to be retryable on 500-level http codes and client errors + retryableClient := getRetryableClient(&c.HttpClient) + retryableRequest, err := rhttp.FromRequest(req) + if err != nil { + return nil, err + } + + response, err := retryableClient.Do(retryableRequest.WithContext(ctx)) + if err != nil { + return nil, &URLError{err, corrID} + } + defer response.Body.Close() + + resBody, err := ioutil.ReadAll(response.Body) + redact := []string{c.Config.APIKey, req.Header.Get("authorization")} + c.Dump(req, response, []byte{}, resBody, c.Logger, redact) + if err != nil { + return nil, err + } + + type KPErrorMsg struct { + Message string `json:"errorMsg,omitempty"` + Reasons []reason + } + + type KPError struct { + Resources []KPErrorMsg `json:"resources,omitempty"` + } + + switch response.StatusCode { + case http.StatusCreated: + if len(resBody) != 0 { + if err := json.Unmarshal(resBody, res); err != nil { + return nil, err + } + } + case http.StatusOK: + if err := json.Unmarshal(resBody, res); err != nil { + return nil, err + } + case http.StatusNoContent: + default: + errMessage := string(resBody) + var reasons []reason + + if strings.Contains(string(resBody), "errorMsg") { + kperr := KPError{} + json.Unmarshal(resBody, &kperr) + if len(kperr.Resources) > 0 && len(kperr.Resources[0].Message) > 0 { + errMessage = kperr.Resources[0].Message + reasons = kperr.Resources[0].Reasons + } + } + + return nil, &Error{ + URL: response.Request.URL.String(), + StatusCode: response.StatusCode, + Message: errMessage, + BodyContent: resBody, + CorrelationID: corrID, + Reasons: reasons, + } + } + + return response, nil +} + +// getRetryableClient returns a fully configured retryable HTTP client +func getRetryableClient(client *http.Client) *rhttp.Client { + // build base client with the library defaults and override as neeeded + rc := rhttp.NewClient() + rc.Logger = nil + rc.HTTPClient = client + rc.RetryWaitMax = RetryWaitMax + rc.RetryMax = RetryMax + rc.CheckRetry = kpCheckRetry + rc.ErrorHandler = rhttp.PassthroughErrorHandler + return rc +} + +// kpCheckRetry will retry on connection errors, server errors, and 429s (rate limit) +func kpCheckRetry(ctx context.Context, resp *http.Response, err error) (bool, error) { + // do not retry on context.Canceled or context.DeadlineExceeded + if ctx.Err() != nil { + return false, ctx.Err() + } + + if err != nil { + return true, err + } + // Retry on connection errors, 500+ errors (except 501 - not implemented), and 429 - too many requests + if resp.StatusCode == 0 || resp.StatusCode == 429 || (resp.StatusCode >= 500 && resp.StatusCode != 501) { + return true, nil + } + + return false, nil +} + +// ContextKey provides a type to auth context keys. +type ContextKey int + +// NewContextWithAuth ... +func NewContextWithAuth(parent context.Context, auth string) context.Context { + return context.WithValue(parent, authContextKey, auth) +} + +// getAccessToken returns the auth context from the given Context, or +// calls to the IAMTokenSource to retrieve an auth token. +func (c *Client) getAccessToken(ctx context.Context) (string, error) { + if ctx.Value(authContextKey) != nil { + return ctx.Value(authContextKey).(string), nil + } + + if len(c.Config.Authorization) > 0 { + return c.Config.Authorization, nil + } + + token, err := c.tokenSource.Token() + if err != nil { + return "", err + } + + return fmt.Sprintf("%s %s", token.TokenType, token.AccessToken), nil +} + +// getCorrelationId returns the correlation ID value from the given Context, or +// returns a new UUID if not present +func (c *Client) getCorrelationID(ctx context.Context) string { + corrID := GetCorrelationID(ctx) + if corrID == nil { + return uuid.New().String() + } + + return corrID.String() +} + +// NewContextWithCorrelationID retuns a context containing the UUID +func NewContextWithCorrelationID(ctx context.Context, uuid *uuid.UUID) context.Context { + return context.WithValue(ctx, cidCtxKey, uuid) +} + +// GetCorrelationID returns the correlation ID from the context +func GetCorrelationID(ctx context.Context) *uuid.UUID { + if id := ctx.Value(cidCtxKey); id != nil { + return id.(*uuid.UUID) + } + return nil +} + +func (c ctxKey) String() string { + return string(c) +} + +// Logger writes when called. +type Logger interface { + Info(...interface{}) +} + +type logger struct { + writer func(...interface{}) +} + +func (l *logger) Info(args ...interface{}) { + l.writer(args...) +} + +func NewLogger(writer func(...interface{})) Logger { + return &logger{writer: writer} +} + +var dumpers = []Dump{dumpNone, dumpBodyOnly, dumpAll, dumpFailOnly, dumpAllNoRedact} + +// Dump writes various parts of an HTTP request and an HTTP response. +type Dump func(*http.Request, *http.Response, []byte, []byte, Logger, []string) + +// Redact replaces various pieces of output. +type Redact func(string, []string) string + +// dumpFailOnly calls dumpAll when the HTTP response isn't 200 (ok), +// 201 (created), or 204 (no content). +func dumpFailOnly(req *http.Request, rsp *http.Response, reqBody, resBody []byte, log Logger, redactStrings []string) { + switch rsp.StatusCode { + case http.StatusOK, http.StatusCreated, http.StatusNoContent: + return + } + dumpAll(req, rsp, reqBody, resBody, log, redactStrings) +} + +// dumpAll dumps the HTTP request and the HTTP response body. +func dumpAll(req *http.Request, rsp *http.Response, reqBody, resBody []byte, log Logger, redactStrings []string) { + dumpRequest(req, rsp, log, redactStrings, redact) + dumpBody(reqBody, resBody, log, redactStrings, redact) +} + +// dumpAllNoRedact dumps the HTTP request and HTTP response body without redaction. +func dumpAllNoRedact(req *http.Request, rsp *http.Response, reqBody, resBody []byte, log Logger, redactStrings []string) { + dumpRequest(req, rsp, log, redactStrings, noredact) + dumpBody(reqBody, resBody, log, redactStrings, noredact) +} + +// dumpBodyOnly dumps the HTTP response body. +func dumpBodyOnly(req *http.Request, rsp *http.Response, reqBody, resBody []byte, log Logger, redactStrings []string) { + dumpBody(reqBody, resBody, log, redactStrings, redact) +} + +// dumpNone does nothing. +func dumpNone(req *http.Request, rsp *http.Response, reqBody, resBody []byte, log Logger, redactStrings []string) { +} + +// dumpRequest dumps the HTTP request. +func dumpRequest(req *http.Request, rsp *http.Response, log Logger, redactStrings []string, redact Redact) { + // log.Info(redact(fmt.Sprint(req), redactStrings)) + // log.Info(redact(fmt.Sprint(rsp), redactStrings)) +} + +// dumpBody dumps the HTTP response body with redactions. +func dumpBody(reqBody, resBody []byte, log Logger, redactStrings []string, redact Redact) { + // log.Info(string(redact(string(reqBody), redactStrings))) + // Redact the access token and refresh token if it shows up in the reponnse body. This will happen + // when using an API Key + var auth iam.Token + if strings.Contains(string(resBody), "access_token") { + err := json.Unmarshal(resBody, &auth) + if err != nil { + log.Info(err) + } + redactStrings = append(redactStrings, auth.AccessToken) + redactStrings = append(redactStrings, auth.RefreshToken) + } + // log.Info(string(redact(string(resBody), redactStrings))) +} + +// redact replaces substrings within the given string. +func redact(s string, redactStrings []string) string { + if len(redactStrings) < 1 { + return s + } + var a []string + for _, s1 := range redactStrings { + if s1 != "" { + a = append(a, s1) + a = append(a, "***Value redacted***") + } + } + r := strings.NewReplacer(a...) + return r.Replace(s) +} + +// noredact does not perform redaction, and returns the given string. +func noredact(s string, redactStrings []string) string { + return s +} diff --git a/vendor/github.com/IBM/keyprotect-go-client/policy.go b/vendor/github.com/IBM/keyprotect-go-client/policy.go new file mode 100644 index 000000000..53bb5ea69 --- /dev/null +++ b/vendor/github.com/IBM/keyprotect-go-client/policy.go @@ -0,0 +1,317 @@ +// Copyright 2020 IBM Corp. +// +// 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 kp + +import ( + "context" + "fmt" + "net/url" + "time" +) + +const ( + policyType = "application/vnd.ibm.kms.policy+json" + + RotationPolicy = "rotation" +) + +// Policy represents a policy as returned by the KP API. +type Policy struct { + Type string `json:"type,omitempty"` + CreatedBy string `json:"createdBy,omitempty"` + CreatedAt *time.Time `json:"creationDate,omitempty"` + CRN string `json:"crn,omitempty"` + UpdatedAt *time.Time `json:"lastUpdateDate,omitempty"` + UpdatedBy string `json:"updatedBy,omitempty"` + Rotation *Rotation `json:"rotation,omitempty"` + DualAuth *DualAuth `json:"dualAuthDelete,omitempty"` +} + +type Rotation struct { + Interval int `json:"interval_month,omitempty"` +} + +type DualAuth struct { + Enabled *bool `json:"enabled,omitempty"` +} + +// PoliciesMetadata represents the metadata of a collection of keys. +type PoliciesMetadata struct { + CollectionType string `json:"collectionType"` + NumberOfPolicies int `json:"collectionTotal"` +} + +// Policies represents a collection of Policies. +type Policies struct { + Metadata PoliciesMetadata `json:"metadata"` + Policies []Policy `json:"resources"` +} + +// GetPolicy retrieves a policy by Key ID. This function is +// deprecated, as it only returns one policy and does not let you +// select which policy set it will return. It is kept for backward +// compatibility on keys with only one rotation policy. Please update +// to use the new GetPolicies or GetPolicy functions. +func (c *Client) GetPolicy(ctx context.Context, id string) (*Policy, error) { + policyresponse := Policies{} + + req, err := c.newRequest("GET", fmt.Sprintf("keys/%s/policies", id), nil) + if err != nil { + return nil, err + } + + _, err = c.do(ctx, req, &policyresponse) + if err != nil { + return nil, err + } + + return &policyresponse.Policies[0], nil +} + +// SetPolicy updates a policy resource by specifying the ID of the key and +// the rotation interval needed. This function is deprecated as it will only +// let you set key rotation policies. To set dual auth and other newer policies +// on a key, please use the new SetPolicies of SetPolicy functions. +func (c *Client) SetPolicy(ctx context.Context, id string, prefer PreferReturn, rotationInterval int) (*Policy, error) { + + policy := Policy{ + Type: policyType, + Rotation: &Rotation{ + Interval: rotationInterval, + }, + } + + policyRequest := Policies{ + Metadata: PoliciesMetadata{ + CollectionType: policyType, + NumberOfPolicies: 1, + }, + Policies: []Policy{policy}, + } + + policyresponse := Policies{} + + req, err := c.newRequest("PUT", fmt.Sprintf("keys/%s/policies", id), &policyRequest) + if err != nil { + return nil, err + } + + req.Header.Set("Prefer", preferHeaders[prefer]) + + _, err = c.do(ctx, req, &policyresponse) + if err != nil { + return nil, err + } + + return &policyresponse.Policies[0], nil +} + +// GetPolicies retrieves all policies details associated with a Key ID. +func (c *Client) GetPolicies(ctx context.Context, id string) ([]Policy, error) { + policyresponse := Policies{} + + req, err := c.newRequest("GET", fmt.Sprintf("keys/%s/policies", id), nil) + if err != nil { + return nil, err + } + + _, err = c.do(ctx, req, &policyresponse) + if err != nil { + return nil, err + } + + return policyresponse.Policies, nil +} + +func (c *Client) getPolicy(ctx context.Context, id, policyType string, policyresponse *Policies) error { + req, err := c.newRequest("GET", fmt.Sprintf("keys/%s/policies", id), nil) + if err != nil { + return err + } + + v := url.Values{} + v.Set("policy", policyType) + req.URL.RawQuery = v.Encode() + + _, err = c.do(ctx, req, &policyresponse) + if err != nil { + return err + } + return err +} + +// GetRotationPolivy method retrieves rotation policy details of a key +// For more information can refet the Key Protect docs in the link below: +// https://cloud.ibm.com/docs/key-protect?topic=key-protect-set-rotation-policy#view-rotation-policy-api +func (c *Client) GetRotationPolicy(ctx context.Context, id string) (*Policy, error) { + policyresponse := Policies{} + + err := c.getPolicy(ctx, id, RotationPolicy, &policyresponse) + if err != nil { + return nil, err + } + + if len(policyresponse.Policies) == 0 { + return nil, nil + } + + return &policyresponse.Policies[0], nil +} + +// GetDualAuthDeletePolicy method retrieves dual auth delete policy details of a key +// For more information can refer the Key Protect docs in the link below: +// https://cloud.ibm.com/docs/key-protect?topic=key-protect-set-dual-auth-key-policy#view-dual-auth-key-policy-api +func (c *Client) GetDualAuthDeletePolicy(ctx context.Context, id string) (*Policy, error) { + policyresponse := Policies{} + + err := c.getPolicy(ctx, id, DualAuthDelete, &policyresponse) + if err != nil { + return nil, err + } + + if len(policyresponse.Policies) == 0 { + return nil, nil + } + + return &policyresponse.Policies[0], nil +} + +func (c *Client) setPolicy(ctx context.Context, id, policyType string, policyRequest Policies) (*Policies, error) { + policyresponse := Policies{} + + req, err := c.newRequest("PUT", fmt.Sprintf("keys/%s/policies", id), &policyRequest) + if err != nil { + return nil, err + } + + v := url.Values{} + v.Set("policy", policyType) + req.URL.RawQuery = v.Encode() + + _, err = c.do(ctx, req, &policyresponse) + if err != nil { + return nil, err + } + return &policyresponse, nil +} + +// SetRotationPolicy updates the rotation policy associated with a key by specifying key ID and rotation interval. +// For more information can refer the Key Protect docs in the link below: +// https://cloud.ibm.com/docs/key-protect?topic=key-protect-set-rotation-policy#update-rotation-policy-api +func (c *Client) SetRotationPolicy(ctx context.Context, id string, rotationInterval int) (*Policy, error) { + policy := Policy{ + Type: policyType, + Rotation: &Rotation{ + Interval: rotationInterval, + }, + } + + policyRequest := Policies{ + Metadata: PoliciesMetadata{ + CollectionType: policyType, + NumberOfPolicies: 1, + }, + Policies: []Policy{policy}, + } + + policyresponse, err := c.setPolicy(ctx, id, RotationPolicy, policyRequest) + if err != nil { + return nil, err + } + + if len(policyresponse.Policies) == 0 { + return nil, nil + } + + return &policyresponse.Policies[0], nil +} + +// SetDualAuthDeletePolicy updates the dual auth delete policy by passing the key ID and enable detail +// For more information can refer the Key Protect docs in the link below: +// https://cloud.ibm.com/docs/key-protect?topic=key-protect-set-dual-auth-key-policy#create-dual-auth-key-policy-api +func (c *Client) SetDualAuthDeletePolicy(ctx context.Context, id string, enabled bool) (*Policy, error) { + policy := Policy{ + Type: policyType, + DualAuth: &DualAuth{ + Enabled: &enabled, + }, + } + + policyRequest := Policies{ + Metadata: PoliciesMetadata{ + CollectionType: policyType, + NumberOfPolicies: 1, + }, + Policies: []Policy{policy}, + } + + policyresponse, err := c.setPolicy(ctx, id, DualAuthDelete, policyRequest) + if err != nil { + return nil, err + } + + if len(policyresponse.Policies) == 0 { + return nil, nil + } + + return &policyresponse.Policies[0], nil +} + +// SetPolicies updates all policies of the key or a single policy by passing key ID. +// To set rotation policy for the key pass the setRotationPolicy parameter as true and set the rotationInterval detail. +// To set dual auth delete policy for the key pass the setDualAuthDeletePolicy parameter as true and set the dualAuthEnable detail. +// Both the policies can be set or either of the policies can be set. +func (c *Client) SetPolicies(ctx context.Context, id string, setRotationPolicy bool, rotationInterval int, setDualAuthDeletePolicy, dualAuthEnable bool) ([]Policy, error) { + policies := []Policy{} + if setRotationPolicy { + rotationPolicy := Policy{ + Type: policyType, + Rotation: &Rotation{ + Interval: rotationInterval, + }, + } + policies = append(policies, rotationPolicy) + } + if setDualAuthDeletePolicy { + dulaAuthPolicy := Policy{ + Type: policyType, + DualAuth: &DualAuth{ + Enabled: &dualAuthEnable, + }, + } + policies = append(policies, dulaAuthPolicy) + } + + policyRequest := Policies{ + Metadata: PoliciesMetadata{ + CollectionType: policyType, + NumberOfPolicies: len(policies), + }, + Policies: policies, + } + + policyresponse := Policies{} + + req, err := c.newRequest("PUT", fmt.Sprintf("keys/%s/policies", id), &policyRequest) + if err != nil { + return nil, err + } + _, err = c.do(ctx, req, &policyresponse) + if err != nil { + return nil, err + } + + return policyresponse.Policies, nil +} diff --git a/vendor/github.com/IBM/keyprotect-go-client/registrations.go b/vendor/github.com/IBM/keyprotect-go-client/registrations.go new file mode 100644 index 000000000..04c0363ef --- /dev/null +++ b/vendor/github.com/IBM/keyprotect-go-client/registrations.go @@ -0,0 +1,69 @@ +// Copyright 2020 IBM Corp. +// +// 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 kp + +import ( + "context" + "fmt" + "net/url" + "time" +) + +// Registration represents the registration as returned by KP API +type Registration struct { + KeyID string `json:"keyId,omitempty"` + ResourceCrn string `json:"resourceCrn,omitempty"` + CreatedBy string `json:"createdBy,omitempty"` + CreationDate *time.Time `json:"creationDate,omitempty"` + UpdatedBy string `json:"updatedBy,omitempty"` + LastUpdateDate *time.Time `json:"lastUpdated,omitempty"` + Description string `json:"description,omitempty"` + PreventKeyDeletion bool `json:"preventKeyDeletion,omitempty"` + KeyVersion KeyVersion `json:"keyVersion,omitempty"` +} + +type registrations struct { + Metadata KeysMetadata `json:"metadata"` + Registrations []Registration `json:"resources"` +} + +// ListRegistrations retrieves a collection of registrations +func (c *Client) ListRegistrations(ctx context.Context, keyId, crn string) (*registrations, error) { + registrationAPI := "" + if keyId != "" { + registrationAPI = fmt.Sprintf("keys/%s/registrations", keyId) + } else { + registrationAPI = "keys/registrations" + } + + req, err := c.newRequest("GET", registrationAPI, nil) + if err != nil { + return nil, err + } + + if crn != "" { + v := url.Values{} + v.Set("urlEncodedResourceCRNQuery", crn) + req.URL.RawQuery = v.Encode() + } + + regs := registrations{} + _, err = c.do(ctx, req, ®s) + if err != nil { + return nil, err + } + + return ®s, nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 73f82105f..c4ae88fff 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,3 +1,7 @@ +# github.com/IBM/keyprotect-go-client v0.7.0 +## explicit; go 1.12 +github.com/IBM/keyprotect-go-client +github.com/IBM/keyprotect-go-client/iam # github.com/armon/go-metrics v0.3.9 ## explicit; go 1.12 github.com/armon/go-metrics