Skip to content

Commit

Permalink
Merge pull request open-horizon#2513 from codejaeger/update_agbot_sec…
Browse files Browse the repository at this point in the history
…rets_api

Issue 2463 - Update agbot secrets api
  • Loading branch information
linggao authored May 24, 2021
2 parents 7bcdb6d + a03c0ab commit 121b05a
Show file tree
Hide file tree
Showing 6 changed files with 270 additions and 151 deletions.
19 changes: 18 additions & 1 deletion agreementbot/secrets/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,23 @@ type AgbotSecrets interface {
Close()
IsReady() bool

ListOrgSecret(user, token, org, name string) ([]string, error)
ListOrgSecret(user, token, org, name string) (map[string]string, error)
ListOrgSecrets(user, token, org string) ([]string, error)
CreateOrgSecret(user, token, org, vaultSecretName string, data CreateSecretRequest) error
DeleteOrgSecret(user, token, org, name string) error
}

type CreateSecretRequest struct {
SecretName string `json:"name"`
SecretValue string `json:"secret"`
}

type ErrorResponse struct {
Msg string // the error message which shall be logged and added to response body
Details string // optional log message
RespCode int // response type from the agbot API
}

func (e ErrorResponse) Error() string {
return e.Msg
}
6 changes: 5 additions & 1 deletion agreementbot/secrets/vault/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ type KeyData struct {
Keys []string `json:"keys"`
}

type ListSecretResponse struct {
Data map[string]string `json:"data"`
}

type ListSecretsResponse struct {
Data KeyData `json:"data"`
}
Expand Down Expand Up @@ -120,7 +124,7 @@ func (vs *AgbotVaultSecrets) newHTTPClient(cfg *config.HorizonConfig) (*http.Cli

}

// Common function to invoke the Exchange API with builtin retry logic.
// Common function to invoke the Vault API with builtin retry logic.
func (vs *AgbotVaultSecrets) invokeVaultWithRetry(token string, url string, method string, body interface{}) (*http.Response, error) {
var currRetry int
var resp *http.Response
Expand Down
124 changes: 108 additions & 16 deletions agreementbot/secrets/vault/vault.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,57 @@ func (vs *AgbotVaultSecrets) String() string {
return fmt.Sprintf("Token: %v", vs.token)
}

func (vs *AgbotVaultSecrets) ListOrgSecret(user, password, org, name string) ([]string, error) {
return []string{}, nil
// This utility will be available to any users within the org.
func (vs *AgbotVaultSecrets) ListOrgSecret(user, password, org, name string) (map[string]string, error) {

glog.V(3).Infof(vaultPluginLogString(fmt.Sprintf("list secret %v for org %v", name, org)))

// Login the user to ensure that the vault ACLs can take effect
userVaultToken, err := vs.loginUser(user, password, org)
if err != nil {
return nil, secrets.ErrorResponse{Msg: fmt.Sprintf("Unable to login user %s, error: %v", user, err), Details: "", RespCode: http.StatusUnauthorized}
}

url := fmt.Sprintf("%s/v1/openhorizon/%s/%s", vs.cfg.GetAgbotVaultURL(), org, name)

resp, err := vs.invokeVaultWithRetry(userVaultToken, url, http.MethodGet, nil)
if resp != nil && resp.Body != nil {
defer resp.Body.Close()
}
if err != nil {
return nil, secrets.ErrorResponse{Msg: fmt.Sprintf("Unable to list %s secret %s, error: %v", org, name, err), Details: "", RespCode: http.StatusServiceUnavailable}
}

respBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, secrets.ErrorResponse{Msg: fmt.Sprintf("Unable to read secret %s from %s, error: %v", name, org, err), Details: "", RespCode: http.StatusInternalServerError}
} else if httpCode := resp.StatusCode; httpCode == http.StatusNotFound {
return nil, secrets.ErrorResponse{Msg: fmt.Sprintf("Secret does not exist. Vault response %s", string(respBytes)), Details: "", RespCode: http.StatusNotFound}
} else if httpCode == http.StatusForbidden {
return nil, secrets.ErrorResponse{Msg: fmt.Sprintf("Secret not available with the specified credentials. Vault response %s", string(respBytes)), Details: "", RespCode: http.StatusForbidden}
} else if httpCode != http.StatusOK {
return nil, secrets.ErrorResponse{Msg: fmt.Sprintf("Unable to find secret %s for org %s, HTTP status code: %v", name, org, httpCode), Details: "", RespCode: httpCode}
}

respMsg := ListSecretResponse{}
if err := json.Unmarshal(respBytes, &respMsg); err != nil {
return nil, secrets.ErrorResponse{Msg: fmt.Sprintf("Unable to parse response body %v", err), Details: "", RespCode: http.StatusInternalServerError}
}

glog.V(3).Infof(vaultPluginLogString("Done reading secret value."))

return respMsg.Data, nil
}

// This utility will be available to only admin users within the org.
func (vs *AgbotVaultSecrets) ListOrgSecrets(user, password, org string) ([]string, error) {

glog.V(3).Infof(vaultPluginLogString(fmt.Sprintf("listing secrets in %v", org)))

// Login the user to ensure that the vault ACLs can take effect
userVaultToken, err := vs.loginUser(user, password, org)
if err != nil {
return nil, errors.New(fmt.Sprintf("unable to login user %s, error: %v", user, err))
return nil, secrets.ErrorResponse{Msg: fmt.Sprintf("Unable to login user %s, error: %v", user, err), Details: "", RespCode: http.StatusUnauthorized}
}

// Query the vault using the user's credentials
Expand All @@ -50,35 +89,88 @@ func (vs *AgbotVaultSecrets) ListOrgSecrets(user, password, org string) ([]strin
defer resp.Body.Close()
}
if err != nil {
return nil, errors.New(fmt.Sprintf("unable to list %s secrets, error: %v", org, err))
return nil, secrets.ErrorResponse{Msg: fmt.Sprintf("Unable to list %s secrets, error: %v", org, err), Details: "", RespCode: http.StatusServiceUnavailable}
}

httpCode := resp.StatusCode
if httpCode == http.StatusNotFound {
return []string{}, nil
respBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, secrets.ErrorResponse{Msg: fmt.Sprintf("Unable to read list %s secrets response, error: %v", org, err), Details: "", RespCode: http.StatusInternalServerError}
} else if httpCode := resp.StatusCode; httpCode == http.StatusNotFound {
return nil, secrets.ErrorResponse{Msg: fmt.Sprintf("Unable to list secrets for %s. Vault response %s ", org, string(respBytes)), Details: string(respBytes), RespCode: http.StatusNotFound}
} else if httpCode == http.StatusForbidden {
return nil, secrets.ErrorResponse{Msg: fmt.Sprintf("Unable to list secrets for %s. Vault response %s ", org, string(respBytes)), Details: string(respBytes), RespCode: http.StatusForbidden}
} else if httpCode != http.StatusOK {
return nil, secrets.ErrorResponse{Msg: fmt.Sprintf("Unable to list secrets for %s, HTTP status code: %v", org, httpCode), Details: "", RespCode: httpCode}
}

glog.V(5).Infof(vaultPluginLogString(fmt.Sprintf("list %s secrets response: %v", org, string(respBytes))))

respMsg := ListSecretsResponse{}
if err := json.Unmarshal(respBytes, &respMsg); err != nil {
return nil, secrets.ErrorResponse{Msg: fmt.Sprintf("Unable to parse response %v", string(respBytes)), Details: "", RespCode: http.StatusInternalServerError}
}

if httpCode != http.StatusOK {
return nil, errors.New(fmt.Sprintf("unable to list %s secrets, HTTP status code: %v", org, httpCode))
glog.V(3).Infof(vaultPluginLogString("Done listing secrets."))

return respMsg.Data.Keys, nil
}

// This utility will be used to create secrets.
func (vs *AgbotVaultSecrets) CreateOrgSecret(user, password, org, vaultSecretName string, data secrets.CreateSecretRequest) error {
glog.V(3).Infof(vaultPluginLogString(fmt.Sprintf("create secret %s for org %s", vaultSecretName, org)))

userVaultToken, err := vs.loginUser(user, password, org)
if err != nil {
return secrets.ErrorResponse{Msg: fmt.Sprintf("Unable to login user %s, error %v", user, err), Details: "", RespCode: http.StatusUnauthorized}
}

url := fmt.Sprintf("%s/v1/openhorizon/%s/%s", vs.cfg.GetAgbotVaultURL(), org, vaultSecretName)
resp, err := vs.invokeVaultWithRetry(userVaultToken, url, http.MethodPost, map[string]string{data.SecretName: data.SecretValue})
if resp != nil && resp.Body != nil {
defer resp.Body.Close()
}
if err != nil {
return secrets.ErrorResponse{Msg: fmt.Sprintf("Unable to create secret, error %v", err), Details: "", RespCode: http.StatusServiceUnavailable}
}

respBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, errors.New(fmt.Sprintf("unable to read list %s secrets response, error: %v", org, err))
return secrets.ErrorResponse{Msg: fmt.Sprintf("Unable to read response, error: %v", err), Details: "", RespCode: http.StatusInternalServerError}
} else if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
return secrets.ErrorResponse{Msg: fmt.Sprintf("Unable to create secret for %s, error %v", org, string(respBytes)), Details: "", RespCode: resp.StatusCode}
}
glog.V(3).Infof(vaultPluginLogString("Done creating secret in vault."))

glog.V(5).Infof(vaultPluginLogString(fmt.Sprintf("list %s secrets response: %v", org, string(respBytes))))
return nil
}

respMsg := ListSecretsResponse{}
err = json.Unmarshal(respBytes, &respMsg)
// This utility will be used to delete secrets.
func (vs *AgbotVaultSecrets) DeleteOrgSecret(user, password, org, name string) error {
glog.V(3).Infof(vaultPluginLogString(fmt.Sprintf("delete secret %s for org %s", name, org)))

userVaultToken, err := vs.loginUser(user, password, org)
if err != nil {
return nil, errors.New(fmt.Sprintf("unable to parse response %v", string(respBytes)))
return secrets.ErrorResponse{Msg: fmt.Sprintf("Unable to login user %s, error %v", user, err), Details: "", RespCode: http.StatusUnauthorized}
}

glog.V(3).Infof(vaultPluginLogString("done listing secrets."))
url := fmt.Sprintf("%s/v1/openhorizon/%s/%s", vs.cfg.GetAgbotVaultURL(), org, name)
resp, err := vs.invokeVaultWithRetry(userVaultToken, url, http.MethodDelete, nil)
if resp != nil && resp.Body != nil {
defer resp.Body.Close()
}
if err != nil {
return secrets.ErrorResponse{Msg: fmt.Sprintf("Unable to delete secret, error %v", err), Details: "", RespCode: http.StatusServiceUnavailable}
}

return respMsg.Data.Keys, nil
respBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return secrets.ErrorResponse{Msg: fmt.Sprintf("Unable to read response, error: %v", err), Details: "", RespCode: http.StatusInternalServerError}
} else if resp.StatusCode != http.StatusNoContent && resp.StatusCode != http.StatusOK {
return secrets.ErrorResponse{Msg: fmt.Sprintf("Unable to delete secret for %s, error %v", org, string(respBytes)), Details: "", RespCode: resp.StatusCode}
}
glog.V(3).Infof(vaultPluginLogString("Done deleting secret in vault."))

return nil
}

func (vs *AgbotVaultSecrets) loginUser(user, password, org string) (string, error) {
Expand Down
Loading

0 comments on commit 121b05a

Please sign in to comment.