mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-14 02:43:36 +00:00
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 <hchiramm@redhat.com>
This commit is contained in:
committed by
mergify[bot]
parent
967076e4ba
commit
93e43d1a0f
510
vendor/github.com/IBM/keyprotect-go-client/kp.go
generated
vendored
Normal file
510
vendor/github.com/IBM/keyprotect-go-client/kp.go
generated
vendored
Normal file
@ -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
|
||||
}
|
Reference in New Issue
Block a user