566 lines
18 KiB
Go
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
|
|
}
|