open-vault/vendor/github.com/cloudfoundry-community/go-cfclient/secgroups.go
2019-06-06 12:26:04 -07:00

566 lines
18 KiB
Go

package cfclient
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"reflect"
"strings"
"github.com/Masterminds/semver"
"github.com/pkg/errors"
)
type SecGroupResponse struct {
Count int `json:"total_results"`
Pages int `json:"total_pages"`
NextUrl string `json:"next_url"`
Resources []SecGroupResource `json:"resources"`
}
type SecGroupCreateResponse struct {
Code int `json:"code"`
ErrorCode string `json:"error_code"`
Description string `json:"description"`
}
type SecGroupResource struct {
Meta Meta `json:"metadata"`
Entity SecGroup `json:"entity"`
}
type SecGroup struct {
Guid string `json:"guid"`
Name string `json:"name"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
Rules []SecGroupRule `json:"rules"`
Running bool `json:"running_default"`
Staging bool `json:"staging_default"`
SpacesURL string `json:"spaces_url"`
StagingSpacesURL string `json:"staging_spaces_url"`
SpacesData []SpaceResource `json:"spaces"`
StagingSpacesData []SpaceResource `json:"staging_spaces"`
c *Client
}
type SecGroupRule struct {
Protocol string `json:"protocol"`
Ports string `json:"ports,omitempty"` //e.g. "4000-5000,9142"
Destination string `json:"destination"` //CIDR Format
Description string `json:"description,omitempty"` //Optional description
Code int `json:"code"` // ICMP code
Type int `json:"type"` //ICMP type. Only valid if Protocol=="icmp"
Log bool `json:"log,omitempty"` //If true, log this rule
}
var MinStagingSpacesVersion *semver.Version = getMinStagingSpacesVersion()
func (c *Client) ListSecGroups() (secGroups []SecGroup, err error) {
requestURL := "/v2/security_groups?inline-relations-depth=1"
for requestURL != "" {
var secGroupResp SecGroupResponse
r := c.NewRequest("GET", requestURL)
resp, err := c.DoRequest(r)
if err != nil {
return nil, errors.Wrap(err, "Error requesting sec groups")
}
resBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, errors.Wrap(err, "Error reading sec group response body")
}
err = json.Unmarshal(resBody, &secGroupResp)
if err != nil {
return nil, errors.Wrap(err, "Error unmarshaling sec group")
}
for _, secGroup := range secGroupResp.Resources {
secGroup.Entity.Guid = secGroup.Meta.Guid
secGroup.Entity.CreatedAt = secGroup.Meta.CreatedAt
secGroup.Entity.UpdatedAt = secGroup.Meta.UpdatedAt
secGroup.Entity.c = c
for i, space := range secGroup.Entity.SpacesData {
space.Entity.Guid = space.Meta.Guid
secGroup.Entity.SpacesData[i] = space
}
if len(secGroup.Entity.SpacesData) == 0 {
spaces, err := secGroup.Entity.ListSpaceResources()
if err != nil {
return nil, err
}
for _, space := range spaces {
secGroup.Entity.SpacesData = append(secGroup.Entity.SpacesData, space)
}
}
if len(secGroup.Entity.StagingSpacesData) == 0 {
spaces, err := secGroup.Entity.ListStagingSpaceResources()
if err != nil {
return nil, err
}
for _, space := range spaces {
secGroup.Entity.StagingSpacesData = append(secGroup.Entity.SpacesData, space)
}
}
secGroups = append(secGroups, secGroup.Entity)
}
requestURL = secGroupResp.NextUrl
resp.Body.Close()
}
return secGroups, nil
}
func (c *Client) ListRunningSecGroups() ([]SecGroup, error) {
secGroups := make([]SecGroup, 0)
requestURL := "/v2/config/running_security_groups"
for requestURL != "" {
var secGroupResp SecGroupResponse
r := c.NewRequest("GET", requestURL)
resp, err := c.DoRequest(r)
if err != nil {
return nil, errors.Wrap(err, "Error requesting sec groups")
}
resBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, errors.Wrap(err, "Error reading sec group response body")
}
err = json.Unmarshal(resBody, &secGroupResp)
if err != nil {
return nil, errors.Wrap(err, "Error unmarshaling sec group")
}
for _, secGroup := range secGroupResp.Resources {
secGroup.Entity.Guid = secGroup.Meta.Guid
secGroup.Entity.CreatedAt = secGroup.Meta.CreatedAt
secGroup.Entity.UpdatedAt = secGroup.Meta.UpdatedAt
secGroup.Entity.c = c
secGroups = append(secGroups, secGroup.Entity)
}
requestURL = secGroupResp.NextUrl
resp.Body.Close()
}
return secGroups, nil
}
func (c *Client) ListStagingSecGroups() ([]SecGroup, error) {
secGroups := make([]SecGroup, 0)
requestURL := "/v2/config/staging_security_groups"
for requestURL != "" {
var secGroupResp SecGroupResponse
r := c.NewRequest("GET", requestURL)
resp, err := c.DoRequest(r)
if err != nil {
return nil, errors.Wrap(err, "Error requesting sec groups")
}
resBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, errors.Wrap(err, "Error reading sec group response body")
}
err = json.Unmarshal(resBody, &secGroupResp)
if err != nil {
return nil, errors.Wrap(err, "Error unmarshaling sec group")
}
for _, secGroup := range secGroupResp.Resources {
secGroup.Entity.Guid = secGroup.Meta.Guid
secGroup.Entity.CreatedAt = secGroup.Meta.CreatedAt
secGroup.Entity.UpdatedAt = secGroup.Meta.UpdatedAt
secGroup.Entity.c = c
secGroups = append(secGroups, secGroup.Entity)
}
requestURL = secGroupResp.NextUrl
resp.Body.Close()
}
return secGroups, nil
}
func (c *Client) GetSecGroupByName(name string) (secGroup SecGroup, err error) {
requestURL := "/v2/security_groups?q=name:" + name
var secGroupResp SecGroupResponse
r := c.NewRequest("GET", requestURL)
resp, err := c.DoRequest(r)
if err != nil {
return secGroup, errors.Wrap(err, "Error requesting sec groups")
}
resBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return secGroup, errors.Wrap(err, "Error reading sec group response body")
}
err = json.Unmarshal(resBody, &secGroupResp)
if err != nil {
return secGroup, errors.Wrap(err, "Error unmarshaling sec group")
}
if len(secGroupResp.Resources) == 0 {
return secGroup, fmt.Errorf("No security group with name %v found", name)
}
secGroup = secGroupResp.Resources[0].Entity
secGroup.Guid = secGroupResp.Resources[0].Meta.Guid
secGroup.CreatedAt = secGroupResp.Resources[0].Meta.CreatedAt
secGroup.UpdatedAt = secGroupResp.Resources[0].Meta.UpdatedAt
secGroup.c = c
resp.Body.Close()
return secGroup, nil
}
func (secGroup *SecGroup) ListSpaceResources() ([]SpaceResource, error) {
var spaceResources []SpaceResource
requestURL := secGroup.SpacesURL
for requestURL != "" {
spaceResp, err := secGroup.c.getSpaceResponse(requestURL)
if err != nil {
return []SpaceResource{}, err
}
for i, spaceRes := range spaceResp.Resources {
spaceRes.Entity.Guid = spaceRes.Meta.Guid
spaceRes.Entity.CreatedAt = spaceRes.Meta.CreatedAt
spaceRes.Entity.UpdatedAt = spaceRes.Meta.UpdatedAt
spaceResp.Resources[i] = spaceRes
}
spaceResources = append(spaceResources, spaceResp.Resources...)
requestURL = spaceResp.NextUrl
}
return spaceResources, nil
}
func (secGroup *SecGroup) ListStagingSpaceResources() ([]SpaceResource, error) {
var spaceResources []SpaceResource
requestURL := secGroup.StagingSpacesURL
for requestURL != "" {
spaceResp, err := secGroup.c.getSpaceResponse(requestURL)
if err != nil {
// if this is a 404, let's make sure that it's not because we're on a legacy system
if cause := errors.Cause(err); cause != nil {
if httpErr, ok := cause.(CloudFoundryHTTPError); ok {
if httpErr.StatusCode == 404 {
info, infoErr := secGroup.c.GetInfo()
if infoErr != nil {
return nil, infoErr
}
apiVersion, versionErr := semver.NewVersion(info.APIVersion)
if versionErr != nil {
return nil, versionErr
}
if MinStagingSpacesVersion.GreaterThan(apiVersion) {
// this is probably not really an error, we're just trying to use a non-existent api
return nil, nil
}
}
}
}
return []SpaceResource{}, err
}
for i, spaceRes := range spaceResp.Resources {
spaceRes.Entity.Guid = spaceRes.Meta.Guid
spaceRes.Entity.CreatedAt = spaceRes.Meta.CreatedAt
spaceRes.Entity.UpdatedAt = spaceRes.Meta.UpdatedAt
spaceResp.Resources[i] = spaceRes
}
spaceResources = append(spaceResources, spaceResp.Resources...)
requestURL = spaceResp.NextUrl
}
return spaceResources, nil
}
/*
CreateSecGroup contacts the CF endpoint for creating a new security group.
name: the name to give to the created security group
rules: A slice of rule objects that describe the rules that this security group enforces.
This can technically be nil or an empty slice - we won't judge you
spaceGuids: The security group will be associated with the spaces specified by the contents of this slice.
If nil, the security group will not be associated with any spaces initially.
*/
func (c *Client) CreateSecGroup(name string, rules []SecGroupRule, spaceGuids []string) (*SecGroup, error) {
return c.secGroupCreateHelper("/v2/security_groups", "POST", name, rules, spaceGuids)
}
/*
UpdateSecGroup contacts the CF endpoint to update an existing security group.
guid: identifies the security group that you would like to update.
name: the new name to give to the security group
rules: A slice of rule objects that describe the rules that this security group enforces.
If this is left nil, the rules will not be changed.
spaceGuids: The security group will be associated with the spaces specified by the contents of this slice.
If nil, the space associations will not be changed.
*/
func (c *Client) UpdateSecGroup(guid, name string, rules []SecGroupRule, spaceGuids []string) (*SecGroup, error) {
return c.secGroupCreateHelper("/v2/security_groups/"+guid, "PUT", name, rules, spaceGuids)
}
/*
DeleteSecGroup contacts the CF endpoint to delete an existing security group.
guid: Indentifies the security group to be deleted.
*/
func (c *Client) DeleteSecGroup(guid string) error {
//Perform the DELETE and check for errors
resp, err := c.DoRequest(c.NewRequest("DELETE", fmt.Sprintf("/v2/security_groups/%s", guid)))
if err != nil {
return err
}
if resp.StatusCode != 204 { //204 No Content
return fmt.Errorf("CF API returned with status code %d", resp.StatusCode)
}
return nil
}
/*
GetSecGroup contacts the CF endpoint for fetching the info for a particular security group.
guid: Identifies the security group to fetch information from
*/
func (c *Client) GetSecGroup(guid string) (*SecGroup, error) {
//Perform the GET and check for errors
resp, err := c.DoRequest(c.NewRequest("GET", "/v2/security_groups/"+guid))
if err != nil {
return nil, err
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("CF API returned with status code %d", resp.StatusCode)
}
//get the json out of the response body
return respBodyToSecGroup(resp.Body, c)
}
/*
BindSecGroup contacts the CF endpoint to associate a space with a security group
secGUID: identifies the security group to add a space to
spaceGUID: identifies the space to associate
*/
func (c *Client) BindSecGroup(secGUID, spaceGUID string) error {
//Perform the PUT and check for errors
resp, err := c.DoRequest(c.NewRequest("PUT", fmt.Sprintf("/v2/security_groups/%s/spaces/%s", secGUID, spaceGUID)))
if err != nil {
return err
}
if resp.StatusCode != 201 { //201 Created
return fmt.Errorf("CF API returned with status code %d", resp.StatusCode)
}
return nil
}
/*
BindSpaceStagingSecGroup contacts the CF endpoint to associate a space with a security group for staging functions only
secGUID: identifies the security group to add a space to
spaceGUID: identifies the space to associate
*/
func (c *Client) BindStagingSecGroupToSpace(secGUID, spaceGUID string) error {
//Perform the PUT and check for errors
resp, err := c.DoRequest(c.NewRequest("PUT", fmt.Sprintf("/v2/security_groups/%s/staging_spaces/%s", secGUID, spaceGUID)))
if err != nil {
return err
}
if resp.StatusCode != 201 { //201 Created
return fmt.Errorf("CF API returned with status code %d", resp.StatusCode)
}
return nil
}
/*
BindRunningSecGroup contacts the CF endpoint to associate a security group
secGUID: identifies the security group to add a space to
*/
func (c *Client) BindRunningSecGroup(secGUID string) error {
//Perform the PUT and check for errors
resp, err := c.DoRequest(c.NewRequest("PUT", fmt.Sprintf("/v2/config/running_security_groups/%s", secGUID)))
if err != nil {
return err
}
if resp.StatusCode != 200 { //200
return fmt.Errorf("CF API returned with status code %d", resp.StatusCode)
}
return nil
}
/*
UnbindRunningSecGroup contacts the CF endpoint to dis-associate a security group
secGUID: identifies the security group to add a space to
*/
func (c *Client) UnbindRunningSecGroup(secGUID string) error {
//Perform the DELETE and check for errors
resp, err := c.DoRequest(c.NewRequest("DELETE", fmt.Sprintf("/v2/config/running_security_groups/%s", secGUID)))
if err != nil {
return err
}
if resp.StatusCode != http.StatusNoContent { //204
return fmt.Errorf("CF API returned with status code %d", resp.StatusCode)
}
return nil
}
/*
BindStagingSecGroup contacts the CF endpoint to associate a space with a security group
secGUID: identifies the security group to add a space to
*/
func (c *Client) BindStagingSecGroup(secGUID string) error {
//Perform the PUT and check for errors
resp, err := c.DoRequest(c.NewRequest("PUT", fmt.Sprintf("/v2/config/staging_security_groups/%s", secGUID)))
if err != nil {
return err
}
if resp.StatusCode != 200 { //200
return fmt.Errorf("CF API returned with status code %d", resp.StatusCode)
}
return nil
}
/*
UnbindStagingSecGroup contacts the CF endpoint to dis-associate a space with a security group
secGUID: identifies the security group to add a space to
*/
func (c *Client) UnbindStagingSecGroup(secGUID string) error {
//Perform the DELETE and check for errors
resp, err := c.DoRequest(c.NewRequest("DELETE", fmt.Sprintf("/v2/config/staging_security_groups/%s", secGUID)))
if err != nil {
return err
}
if resp.StatusCode != http.StatusNoContent { //204
return fmt.Errorf("CF API returned with status code %d", resp.StatusCode)
}
return nil
}
/*
UnbindSecGroup contacts the CF endpoint to dissociate a space from a security group
secGUID: identifies the security group to remove a space from
spaceGUID: identifies the space to dissociate from the security group
*/
func (c *Client) UnbindSecGroup(secGUID, spaceGUID string) error {
//Perform the DELETE and check for errors
resp, err := c.DoRequest(c.NewRequest("DELETE", fmt.Sprintf("/v2/security_groups/%s/spaces/%s", secGUID, spaceGUID)))
if err != nil {
return err
}
if resp.StatusCode != 204 { //204 No Content
return fmt.Errorf("CF API returned with status code %d", resp.StatusCode)
}
return nil
}
//Reads most security group response bodies into a SecGroup object
func respBodyToSecGroup(body io.ReadCloser, c *Client) (*SecGroup, error) {
//get the json from the response body
bodyRaw, err := ioutil.ReadAll(body)
if err != nil {
return nil, errors.Wrap(err, "Could not read response body")
}
jStruct := SecGroupResource{}
//make it a SecGroup
err = json.Unmarshal(bodyRaw, &jStruct)
if err != nil {
return nil, errors.Wrap(err, "Could not unmarshal response body as json")
}
//pull a few extra fields from other places
ret := jStruct.Entity
ret.Guid = jStruct.Meta.Guid
ret.CreatedAt = jStruct.Meta.CreatedAt
ret.UpdatedAt = jStruct.Meta.UpdatedAt
ret.c = c
return &ret, nil
}
func convertStructToMap(st interface{}) map[string]interface{} {
reqRules := make(map[string]interface{})
v := reflect.ValueOf(st)
t := reflect.TypeOf(st)
for i := 0; i < v.NumField(); i++ {
key := strings.ToLower(t.Field(i).Name)
typ := v.FieldByName(t.Field(i).Name).Kind().String()
structTag := t.Field(i).Tag.Get("json")
jsonName := strings.TrimSpace(strings.Split(structTag, ",")[0])
value := v.FieldByName(t.Field(i).Name)
// if jsonName is not empty use it for the key
if jsonName != "" {
key = jsonName
}
if typ == "string" {
if !(value.String() == "" && strings.Contains(structTag, "omitempty")) {
reqRules[key] = value.String()
}
} else if typ == "int" {
reqRules[key] = value.Int()
} else {
reqRules[key] = value.Interface()
}
}
return reqRules
}
//Create and Update secGroup pretty much do the same thing, so this function abstracts those out.
func (c *Client) secGroupCreateHelper(url, method, name string, rules []SecGroupRule, spaceGuids []string) (*SecGroup, error) {
reqRules := make([]map[string]interface{}, len(rules))
for i, rule := range rules {
reqRules[i] = convertStructToMap(rule)
protocol := strings.ToLower(reqRules[i]["protocol"].(string))
// if not icmp protocol need to remove the Code/Type fields
if protocol != "icmp" {
delete(reqRules[i], "code")
delete(reqRules[i], "type")
}
}
req := c.NewRequest(method, url)
//set up request body
inputs := map[string]interface{}{
"name": name,
"rules": reqRules,
}
if spaceGuids != nil {
inputs["space_guids"] = spaceGuids
}
req.obj = inputs
//fire off the request and check for problems
resp, err := c.DoRequest(req)
if err != nil {
return nil, err
}
if resp.StatusCode != 201 { // Both create and update should give 201 CREATED
var response SecGroupCreateResponse
bodyRaw, _ := ioutil.ReadAll(resp.Body)
err = json.Unmarshal(bodyRaw, &response)
if err != nil {
return nil, errors.Wrap(err, "Error unmarshaling response")
}
return nil, fmt.Errorf(`Request failed CF API returned with status code %d
-------------------------------
Error Code %s
Code %d
Description %s`,
resp.StatusCode, response.ErrorCode, response.Code, response.Description)
}
//get the json from the response body
return respBodyToSecGroup(resp.Body, c)
}
func getMinStagingSpacesVersion() *semver.Version {
v, _ := semver.NewVersion("2.68.0")
return v
}