|
|
@@ -0,0 +1,227 @@
|
|
|
+/*
|
|
|
+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 dsm
|
|
|
+
|
|
|
+import (
|
|
|
+ "context"
|
|
|
+ "crypto/tls"
|
|
|
+ "encoding/json"
|
|
|
+ "errors"
|
|
|
+ "fmt"
|
|
|
+ "io/ioutil"
|
|
|
+ "net/http"
|
|
|
+ "net/url"
|
|
|
+
|
|
|
+ esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
|
|
+ senhaseguraAuth "github.com/external-secrets/external-secrets/pkg/provider/senhasegura/auth"
|
|
|
+)
|
|
|
+
|
|
|
+type clientDSMInterface interface {
|
|
|
+ FetchSecrets() (respObj IsoDappResponse, err error)
|
|
|
+}
|
|
|
+
|
|
|
+// https://github.com/external-secrets/external-secrets/issues/644
|
|
|
+var _ esv1beta1.SecretsClient = &DSM{}
|
|
|
+
|
|
|
+/*
|
|
|
+ DSM service for SenhaseguraProvider
|
|
|
+*/
|
|
|
+type DSM struct {
|
|
|
+ isoSession *senhaseguraAuth.SenhaseguraIsoSession
|
|
|
+ dsmClient clientDSMInterface
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ IsoDappResponse is a response object from senhasegura /iso/dapp/response (DevOps Secrets Management API endpoint)
|
|
|
+ Contains information about API request and Secrets linked with authorization
|
|
|
+*/
|
|
|
+type IsoDappResponse struct {
|
|
|
+ Response struct {
|
|
|
+ Status int `json:"status"`
|
|
|
+ Message string `json:"message"`
|
|
|
+ Error bool `json:"error"`
|
|
|
+ ErrorCode int `json:"error_code"`
|
|
|
+ } `json:"response"`
|
|
|
+ Application struct {
|
|
|
+ Name string `json:"name"`
|
|
|
+ Description string `json:"description"`
|
|
|
+ Tags []string `json:"tags"`
|
|
|
+ System string `json:"system"`
|
|
|
+ Environment string `json:"Environment"`
|
|
|
+ Secrets []struct {
|
|
|
+ SecretID string `json:"secret_id"`
|
|
|
+ SecretName string `json:"secret_name"`
|
|
|
+ Identity string `json:"identity"`
|
|
|
+ Version string `json:"version"`
|
|
|
+ ExpirationDate string `json:"expiration_date"`
|
|
|
+ Engine string `json:"engine"`
|
|
|
+ Data []map[string]string `json:"data"`
|
|
|
+ } `json:"secrets"`
|
|
|
+ } `json:"application"`
|
|
|
+}
|
|
|
+
|
|
|
+var (
|
|
|
+ errCannotCreateRequest = errors.New("cannot create request to senhasegura resource /iso/dapp/application")
|
|
|
+ errCannotDoRequest = errors.New("cannot do request in senhasegura, SSL certificate is valid ?")
|
|
|
+ errInvalidResponseBody = errors.New("invalid HTTP response body received from senhasegura")
|
|
|
+ errInvalidHTTPCode = errors.New("received invalid HTTP code from senhasegura")
|
|
|
+ errApplicationError = errors.New("received application error from senhasegura")
|
|
|
+)
|
|
|
+
|
|
|
+/*
|
|
|
+ New creates an senhasegura DSM client based on ISO session
|
|
|
+*/
|
|
|
+func New(isoSession *senhaseguraAuth.SenhaseguraIsoSession) (*DSM, error) {
|
|
|
+ return &DSM{
|
|
|
+ isoSession: isoSession,
|
|
|
+ dsmClient: &DSM{},
|
|
|
+ }, nil
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ GetSecret implements ESO interface and get a single secret from senhasegura provider with DSM service
|
|
|
+*/
|
|
|
+func (dsm *DSM) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (resp []byte, err error) {
|
|
|
+ appSecrets, err := dsm.FetchSecrets()
|
|
|
+ if err != nil {
|
|
|
+ return []byte(""), err
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, v := range appSecrets.Application.Secrets {
|
|
|
+ if ref.Key == v.Identity {
|
|
|
+ // Return whole data content in json-encoded when ref.Property is empty
|
|
|
+ if ref.Property == "" {
|
|
|
+ jsonStr, err := json.Marshal(v.Data)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ return jsonStr, nil
|
|
|
+ }
|
|
|
+
|
|
|
+ // Return raw data content when ref.Property is provided
|
|
|
+ for _, v2 := range v.Data {
|
|
|
+ for k, v3 := range v2 {
|
|
|
+ if k == ref.Property {
|
|
|
+ resp = []byte(v3)
|
|
|
+ return resp, nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return []byte(""), esv1beta1.NoSecretErr
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ GetSecretMap implements ESO interface and returns miltiple k/v pairs from senhasegura provider with DSM service
|
|
|
+*/
|
|
|
+func (dsm *DSM) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (secretData map[string][]byte, err error) {
|
|
|
+ secretData = make(map[string][]byte)
|
|
|
+ appSecrets, err := dsm.FetchSecrets()
|
|
|
+ if err != nil {
|
|
|
+ return secretData, err
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, v := range appSecrets.Application.Secrets {
|
|
|
+ if v.Identity == ref.Key {
|
|
|
+ for _, v2 := range v.Data {
|
|
|
+ for k, v3 := range v2 {
|
|
|
+ secretData[k] = []byte(v3)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return secretData, nil
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ GetAllSecrets implements ESO interface and returns multiple secrets from senhasegura provider with DSM service
|
|
|
+
|
|
|
+ TODO: GetAllSecrets functionality is to get secrets from either regexp-matching against the names or via metadata label matching.
|
|
|
+ https://github.com/external-secrets/external-secrets/pull/830#discussion_r858657107
|
|
|
+*/
|
|
|
+func (dsm *DSM) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (secretData map[string][]byte, err error) {
|
|
|
+ return nil, fmt.Errorf("GetAllSecrets not implemented yet")
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ fetchSecrets calls senhasegura DSM /iso/dapp/application API endpoint
|
|
|
+ Return an IsoDappResponse with all related information from senhasegura provider with DSM service and error
|
|
|
+*/
|
|
|
+func (dsm *DSM) FetchSecrets() (respObj IsoDappResponse, err error) {
|
|
|
+ u, _ := url.ParseRequestURI(dsm.isoSession.URL)
|
|
|
+ u.Path = "/iso/dapp/application"
|
|
|
+
|
|
|
+ tr := &http.Transport{
|
|
|
+ // nolint
|
|
|
+ TLSClientConfig: &tls.Config{InsecureSkipVerify: dsm.isoSession.IgnoreSslCertificate},
|
|
|
+ }
|
|
|
+
|
|
|
+ client := &http.Client{Transport: tr}
|
|
|
+
|
|
|
+ r, err := http.NewRequest("GET", u.String(), nil)
|
|
|
+ if err != nil {
|
|
|
+ return respObj, errCannotCreateRequest
|
|
|
+ }
|
|
|
+
|
|
|
+ r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
|
+ r.Header.Set("Authorization", "Bearer "+dsm.isoSession.Token)
|
|
|
+
|
|
|
+ resp, err := client.Do(r)
|
|
|
+ if err != nil {
|
|
|
+ return respObj, errCannotDoRequest
|
|
|
+ }
|
|
|
+ defer resp.Body.Close()
|
|
|
+
|
|
|
+ if resp.StatusCode != 200 {
|
|
|
+ return respObj, errInvalidHTTPCode
|
|
|
+ }
|
|
|
+
|
|
|
+ respData, err := ioutil.ReadAll(resp.Body)
|
|
|
+ if err != nil {
|
|
|
+ return respObj, errInvalidResponseBody
|
|
|
+ }
|
|
|
+
|
|
|
+ err = json.Unmarshal(respData, &respObj)
|
|
|
+ if err != nil {
|
|
|
+ return respObj, errInvalidResponseBody
|
|
|
+ }
|
|
|
+
|
|
|
+ if respObj.Response.Error {
|
|
|
+ return respObj, errApplicationError
|
|
|
+ }
|
|
|
+
|
|
|
+ return respObj, nil
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ Close implements ESO interface and do nothing in senhasegura
|
|
|
+*/
|
|
|
+func (dsm *DSM) Close(ctx context.Context) error {
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+// Validate if has valid connection with senhasegura, credentials, authorization using fetchSecrets method
|
|
|
+// fetchSecrets method implement required check about request
|
|
|
+// https://github.com/external-secrets/external-secrets/pull/830#discussion_r833275463
|
|
|
+func (dsm *DSM) Validate() (esv1beta1.ValidationResult, error) {
|
|
|
+ _, err := dsm.FetchSecrets()
|
|
|
+ if err != nil {
|
|
|
+ return esv1beta1.ValidationResultError, err
|
|
|
+ }
|
|
|
+
|
|
|
+ return esv1beta1.ValidationResultReady, nil
|
|
|
+}
|