github.com/vmware/go-vcloud-director/v2@v2.24.0/govcd/nsxt_distributed_firewall.go (about)

     1  package govcd
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"strings"
     8  
     9  	"github.com/vmware/go-vcloud-director/v2/types/v56"
    10  	"github.com/vmware/go-vcloud-director/v2/util"
    11  )
    12  
    13  const (
    14  	labelDistributedFirewall     = "NSX-T Distributed Firewall"
    15  	labelDistributedFirewallRule = "NSX-T Distributed Firewall Rule"
    16  )
    17  
    18  // DistributedFirewall contains a types.DistributedFirewallRules which handles Distributed Firewall
    19  // rules in a VDC Group
    20  type DistributedFirewall struct {
    21  	DistributedFirewallRuleContainer *types.DistributedFirewallRules
    22  	client                           *Client
    23  	VdcGroup                         *VdcGroup
    24  }
    25  
    26  // wrap is a hidden helper that facilitates the usage of a generic CRUD function
    27  //
    28  //lint:ignore U1000 this method is used in generic functions, but annoys staticcheck
    29  func (d DistributedFirewall) wrap(inner *types.DistributedFirewallRules) *DistributedFirewall {
    30  	d.DistributedFirewallRuleContainer = inner
    31  	return &d
    32  }
    33  
    34  // DistributedFirewallRule is a representation of a single rule
    35  type DistributedFirewallRule struct {
    36  	Rule     *types.DistributedFirewallRule
    37  	client   *Client
    38  	VdcGroup *VdcGroup
    39  }
    40  
    41  // wrap is a hidden helper that facilitates the usage of a generic CRUD function
    42  //
    43  //lint:ignore U1000 this method is used in generic functions, but annoys staticcheck
    44  func (d DistributedFirewallRule) wrap(inner *types.DistributedFirewallRule) *DistributedFirewallRule {
    45  	d.Rule = inner
    46  	return &d
    47  }
    48  
    49  // GetDistributedFirewall retrieves Distributed Firewall in a VDC Group which contains all rules
    50  //
    51  // Note. This function works only with `default` policy as this was the only supported when this
    52  // functions was created
    53  func (vdcGroup *VdcGroup) GetDistributedFirewall() (*DistributedFirewall, error) {
    54  	c := crudConfig{
    55  		entityLabel:    labelDistributedFirewall,
    56  		endpoint:       types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointVdcGroupsDfwRules,
    57  		endpointParams: []string{vdcGroup.VdcGroup.Id, types.DistributedFirewallPolicyDefault},
    58  	}
    59  
    60  	outerType := DistributedFirewall{client: vdcGroup.client, VdcGroup: vdcGroup}
    61  	return getOuterEntity[DistributedFirewall, types.DistributedFirewallRules](vdcGroup.client, outerType, c)
    62  }
    63  
    64  // UpdateDistributedFirewall updates Distributed Firewall in a VDC Group
    65  //
    66  // Note. This function works only with `default` policy as this was the only supported when this
    67  // functions was created
    68  func (vdcGroup *VdcGroup) UpdateDistributedFirewall(dfwRules *types.DistributedFirewallRules) (*DistributedFirewall, error) {
    69  	c := crudConfig{
    70  		entityLabel:    labelDistributedFirewall,
    71  		endpoint:       types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointVdcGroupsDfwRules,
    72  		endpointParams: []string{vdcGroup.VdcGroup.Id, types.DistributedFirewallPolicyDefault},
    73  	}
    74  
    75  	outerType := DistributedFirewall{client: vdcGroup.client, VdcGroup: vdcGroup}
    76  	return updateOuterEntity[DistributedFirewall, types.DistributedFirewallRules](vdcGroup.client, outerType, c, dfwRules)
    77  }
    78  
    79  // DeleteAllDistributedFirewallRules removes all Distributed Firewall rules
    80  //
    81  // Note. This function works only with `default` policy as this was the only supported when this
    82  // functions was created
    83  func (vdcGroup *VdcGroup) DeleteAllDistributedFirewallRules() error {
    84  	_, err := vdcGroup.UpdateDistributedFirewall(&types.DistributedFirewallRules{})
    85  	return err
    86  }
    87  
    88  // DeleteAllRules removes all Distributed Firewall rules
    89  //
    90  // Note. This function works only with `default` policy as this was the only supported when this
    91  // functions was created
    92  func (firewall *DistributedFirewall) DeleteAllRules() error {
    93  	if firewall.VdcGroup != nil && firewall.VdcGroup.VdcGroup != nil && firewall.VdcGroup.VdcGroup.Id == "" {
    94  		return errors.New("empty VDC Group ID for parent VDC Group")
    95  	}
    96  
    97  	return firewall.VdcGroup.DeleteAllDistributedFirewallRules()
    98  }
    99  
   100  // GetDistributedFirewallRuleById retrieves single Distributed Firewall Rule by ID
   101  func (vdcGroup *VdcGroup) GetDistributedFirewallRuleById(id string) (*DistributedFirewallRule, error) {
   102  	c := crudConfig{
   103  		entityLabel:    labelDistributedFirewallRule,
   104  		endpoint:       types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointVdcGroupsDfwRules,
   105  		endpointParams: []string{vdcGroup.VdcGroup.Id, types.DistributedFirewallPolicyDefault, "/", id},
   106  	}
   107  
   108  	outerType := DistributedFirewallRule{client: vdcGroup.client, VdcGroup: vdcGroup}
   109  	return getOuterEntity[DistributedFirewallRule, types.DistributedFirewallRule](vdcGroup.client, outerType, c)
   110  }
   111  
   112  // GetDistributedFirewallRuleByName retrieves single firewall rule by name
   113  func (vdcGroup *VdcGroup) GetDistributedFirewallRuleByName(name string) (*DistributedFirewallRule, error) {
   114  	if name == "" {
   115  		return nil, fmt.Errorf("name must be specified")
   116  	}
   117  
   118  	dfw, err := vdcGroup.GetDistributedFirewall()
   119  	if err != nil {
   120  		return nil, fmt.Errorf("error returning distributed firewall rules: %s", err)
   121  	}
   122  
   123  	singleRuleByName, err := localFilterOneOrError(labelDistributedFirewallRule, dfw.DistributedFirewallRuleContainer.Values, "Name", name)
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  
   128  	return vdcGroup.GetDistributedFirewallRuleById(singleRuleByName.ID)
   129  }
   130  
   131  // CreateDistributedFirewallRule is a non-thread safe wrapper around
   132  // "vdcGroups/%s/dfwPolicies/%s/rules" endpoint which handles all distributed firewall (DFW) rules
   133  // at once. While there is no real endpoint to create single firewall rule, it is a requirements for
   134  // some cases (e.g. using in Terraform)
   135  // The code works by doing the following steps:
   136  //
   137  // 1. Getting all Distributed Firewall Rules and storing them in private intermediate
   138  // type`distributedFirewallRulesRaw` which holds a []json.RawMessage (text) instead of exact types.
   139  // This will prevent altering existing rules in any way (for example if a new field appears in
   140  // schema in future VCD versions)
   141  //
   142  // 2. Converting the given `rule` into json.RawMessage so that it is provided in the same format as
   143  // other already retrieved rules
   144  //
   145  // 3. Creating a new structure of []json.RawMessage which puts the new rule into one of places:
   146  // 3.1. to the end of []json.RawMessage - bottom of the list
   147  // 3.2. if `optionalAboveRuleId` argument is specified - identifying the position and placing new
   148  // rule above it
   149  // 4. Perform a PUT (update) call to the "vdcGroups/%s/dfwPolicies/%s/rules" endpoint using the
   150  // newly constructed payload
   151  //
   152  // Note. Running this function concurrently will corrupt firewall rules as it uses an endpoint that
   153  // manages all rules ("vdcGroups/%s/dfwPolicies/%s/rules")
   154  func (vdcGroup *VdcGroup) CreateDistributedFirewallRule(optionalAboveRuleId string, rule *types.DistributedFirewallRule) (*DistributedFirewall, *DistributedFirewallRule, error) {
   155  	// 1. Getting all Distributed Firewall Rules and storing them in private intermediate
   156  	// type`distributedFirewallRulesRaw` which holds a []json.RawMessage (text) instead of exact types.
   157  	// This will prevent altering existing rules in any way (for example if a new field appears in
   158  	// schema in future VCD versions)
   159  
   160  	c := crudConfig{
   161  		entityLabel:    labelDistributedFirewallRule,
   162  		endpoint:       types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointVdcGroupsDfwRules,
   163  		endpointParams: []string{vdcGroup.VdcGroup.Id, types.DistributedFirewallPolicyDefault},
   164  	}
   165  	rawJsonExistingFirewallRules, err := getInnerEntity[distributedFirewallRulesRaw](vdcGroup.client, c)
   166  	if err != nil {
   167  		return nil, nil, err
   168  	}
   169  
   170  	// 2. Converting the give `rule` (*types.DistributedFirewallRule) into json.RawMessage so that
   171  	// it is provided in the same format as other already retrieved rules
   172  	newRuleRawJson, err := firewallRuleToRawJson(rule)
   173  	if err != nil {
   174  		return nil, nil, err
   175  	}
   176  
   177  	// dfwRuleUpdatePayload will contain complete request for Distributed Firewall Rule Update
   178  	// operation. Its content will be decided based on whether 'optionalAboveRuleId' parameter was
   179  	// specified or not.
   180  	var dfwRuleUpdatePayload []json.RawMessage
   181  	// newRuleSlicePosition will contain slice index to where new firewall rule will be put
   182  	var newRuleSlicePosition int
   183  
   184  	// 3. Creating a new structure of []json.RawMessage which puts the new rule into one of places:
   185  	switch {
   186  	// 3.1. to the end of []json.RawMessage - bottom of the list (optionalAboveRuleId is empty)
   187  	case optionalAboveRuleId == "":
   188  		rawJsonExistingFirewallRules.Values = append(rawJsonExistingFirewallRules.Values, newRuleRawJson)
   189  		dfwRuleUpdatePayload = rawJsonExistingFirewallRules.Values
   190  		newRuleSlicePosition = len(dfwRuleUpdatePayload) - 1 // -1 to match for slice index
   191  
   192  		// 3.2. if `optionalAboveRuleId` argument is specified - identifying the position and placing new
   193  		// rule above it
   194  	case optionalAboveRuleId != "":
   195  		// 3.2.1 Convert '[]json.Rawmessage' to 'types.DistributedFirewallRules'
   196  		dfwRules, err := convertRawJsonToFirewallRules(rawJsonExistingFirewallRules)
   197  		if err != nil {
   198  			return nil, nil, err
   199  		}
   200  		// 3.2.2 Find index for specified 'optionalAboveRuleId' rule
   201  		newFwRuleSliceIndex, err := getFirewallRuleIndexById(dfwRules, optionalAboveRuleId)
   202  		if err != nil {
   203  			return nil, nil, err
   204  		}
   205  
   206  		// 3.2.3 Compose new update (PUT) payload with all firewall rules and inject
   207  		// 'newRuleRawJson' into position 'newFwRuleSliceIndex' and shift other rules to the bottom
   208  		dfwRuleUpdatePayload, err = composeUpdatePayloadWithNewRulePosition(newFwRuleSliceIndex, rawJsonExistingFirewallRules, newRuleRawJson)
   209  		if err != nil {
   210  			return nil, nil, fmt.Errorf("error creating update payload with optionalAboveRuleId '%s' :%s", optionalAboveRuleId, err)
   211  		}
   212  	}
   213  	// 4. Perform a PUT (update) call to the "vdcGroups/%s/dfwPolicies/%s/rules" endpoint using the
   214  	// newly constructed payload
   215  	updateRequestPayload := &distributedFirewallRulesRaw{
   216  		Values: dfwRuleUpdatePayload,
   217  	}
   218  
   219  	c2 := crudConfig{
   220  		entityLabel:    labelDistributedFirewallRule,
   221  		endpoint:       types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointVdcGroupsDfwRules,
   222  		endpointParams: []string{vdcGroup.VdcGroup.Id, types.DistributedFirewallPolicyDefault},
   223  	}
   224  
   225  	updatedFirewallRules, err := updateInnerEntity(vdcGroup.client, c2, updateRequestPayload)
   226  	if err != nil {
   227  		return nil, nil, err
   228  	}
   229  
   230  	dfwResults, err := convertRawJsonToFirewallRules(updatedFirewallRules)
   231  	if err != nil {
   232  		return nil, nil, err
   233  	}
   234  
   235  	returnObjectSingleRule := &DistributedFirewallRule{
   236  		client:   vdcGroup.client,
   237  		VdcGroup: vdcGroup,
   238  		Rule:     dfwResults.Values[newRuleSlicePosition],
   239  	}
   240  
   241  	returnAllFirewallRules := &DistributedFirewall{
   242  		DistributedFirewallRuleContainer: dfwResults,
   243  		client:                           vdcGroup.client,
   244  		VdcGroup:                         vdcGroup,
   245  	}
   246  
   247  	return returnAllFirewallRules, returnObjectSingleRule, nil
   248  }
   249  
   250  // Update a single Distributed Firewall Rule
   251  func (dfwRule *DistributedFirewallRule) Update(rule *types.DistributedFirewallRule) (*DistributedFirewallRule, error) {
   252  	c := crudConfig{
   253  		endpoint:       types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointVdcGroupsDfwRules,
   254  		endpointParams: []string{dfwRule.VdcGroup.VdcGroup.Id, types.DistributedFirewallPolicyDefault, "/", dfwRule.Rule.ID},
   255  		entityLabel:    labelDistributedFirewallRule,
   256  	}
   257  	outerType := DistributedFirewallRule{client: dfwRule.client, VdcGroup: dfwRule.VdcGroup}
   258  	return updateOuterEntity(dfwRule.client, outerType, c, rule)
   259  }
   260  
   261  // Delete a single Distributed Firewall Rule
   262  func (dfwRule *DistributedFirewallRule) Delete() error {
   263  	c := crudConfig{
   264  		entityLabel:    labelDistributedFirewallRule,
   265  		endpoint:       types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointVdcGroupsDfwRules,
   266  		endpointParams: []string{dfwRule.VdcGroup.VdcGroup.Id, types.DistributedFirewallPolicyDefault, "/", dfwRule.Rule.ID},
   267  	}
   268  	return deleteEntityById(dfwRule.client, c)
   269  }
   270  
   271  // getFirewallRuleIndexById searches for 'firewallRuleId' going through a list of available firewall
   272  // rules and returns its index or error if the firewall rule is not found
   273  func getFirewallRuleIndexById(dfwRules *types.DistributedFirewallRules, firewallRuleId string) (int, error) {
   274  	util.Logger.Printf("[DEBUG] CreateDistributedFirewallRule 'optionalAboveRuleId=%s'. Searching within '%d' items",
   275  		firewallRuleId, len(dfwRules.Values))
   276  	var fwRuleSliceIndex *int
   277  	for index := range dfwRules.Values {
   278  		if dfwRules.Values[index].ID == firewallRuleId {
   279  			// using function `addrOf` to get copy of `index` value as taking a direct address
   280  			// of `&index` will shift before it is used in later code due to how Go range works
   281  			fwRuleSliceIndex = addrOf(index)
   282  			util.Logger.Printf("[DEBUG] CreateDistributedFirewallRule found existing Firewall Rule with ID '%s' at position '%d'",
   283  				firewallRuleId, index)
   284  			continue
   285  		}
   286  	}
   287  
   288  	if fwRuleSliceIndex == nil {
   289  		return 0, fmt.Errorf("specified above rule ID '%s' does not exist in current Distributed Firewall Rule list", firewallRuleId)
   290  	}
   291  
   292  	return *fwRuleSliceIndex, nil
   293  }
   294  
   295  // firewallRuleToRawJson Marshal a single `types.DistributedFirewallRule` into `json.RawMessage`
   296  // representation
   297  func firewallRuleToRawJson(rule *types.DistributedFirewallRule) (json.RawMessage, error) {
   298  	ruleByteSlice, err := json.Marshal(rule)
   299  	if err != nil {
   300  		return nil, fmt.Errorf("error marshalling 'rule': %s", err)
   301  	}
   302  	ruleJsonMessage := json.RawMessage(string(ruleByteSlice))
   303  	return ruleJsonMessage, nil
   304  }
   305  
   306  // convertRawJsonToFirewallRules converts []json.RawMessage to
   307  // types.DistributedFirewallRules.Values so that entries can be filtered by ID or other fields.
   308  // Note. Slice order remains the same
   309  func convertRawJsonToFirewallRules(rawBodyStructure *distributedFirewallRulesRaw) (*types.DistributedFirewallRules, error) {
   310  	var rawJsonBodies []string
   311  	for _, singleObject := range rawBodyStructure.Values {
   312  		rawJsonBodies = append(rawJsonBodies, string(singleObject))
   313  	}
   314  	// rawJsonBodies contains a slice of all response objects and they must be formatted as a JSON slice (wrapped
   315  	// into `[]`, separated with semicolons) so that unmarshalling to specified `outType` works in one go
   316  	allResponses := `[` + strings.Join(rawJsonBodies, ",") + `]`
   317  
   318  	// Convert the retrieved []json.RawMessage to *types.DistributedFirewallRules.Values so that IDs can be searched for
   319  	// Note. The main goal here is to have 2 slices - one with []json.RawMessage and other
   320  	// []*DistributedFirewallRule. One can look for IDs and capture firewall rule index
   321  	dfwRules := &types.DistributedFirewallRules{}
   322  	// Unmarshal all accumulated responses into `dfwRules`
   323  	if err := json.Unmarshal([]byte(allResponses), &dfwRules.Values); err != nil {
   324  		return nil, fmt.Errorf("error decoding values into type types.DistributedFirewallRules: %s", err)
   325  	}
   326  
   327  	return dfwRules, nil
   328  }
   329  
   330  // composeUpdatePayloadWithNewRulePosition takes a slice of existing firewall rules and injects new
   331  // firewall rule at a given position `newRuleSlicePosition`
   332  func composeUpdatePayloadWithNewRulePosition(newRuleSlicePosition int, rawBodyStructure *distributedFirewallRulesRaw, newRuleJsonMessage json.RawMessage) ([]json.RawMessage, error) {
   333  	// Create a new slice with additional capacity of 1 to add new firewall rule into existing list
   334  	newFwRuleSlice := make([]json.RawMessage, len(rawBodyStructure.Values)+1)
   335  	util.Logger.Printf("[DEBUG] CreateDistributedFirewallRule new container slice of size '%d' with previous element count '%d'", len(newFwRuleSlice), len(rawBodyStructure.Values))
   336  	// if newRulePosition is not 0 (at the top), then previous rules need to be copied to the beginning of new slice
   337  	if newRuleSlicePosition != 0 {
   338  		util.Logger.Printf("[DEBUG] CreateDistributedFirewallRule copying first '%d' slice [:%d]", newRuleSlicePosition, newRuleSlicePosition)
   339  		copy(newFwRuleSlice[:newRuleSlicePosition], rawBodyStructure.Values[:newRuleSlicePosition])
   340  	}
   341  
   342  	// Insert the new element at specified index
   343  	util.Logger.Printf("[DEBUG] CreateDistributedFirewallRule inserting new element into position %d", newRuleSlicePosition)
   344  	newFwRuleSlice[newRuleSlicePosition] = newRuleJsonMessage
   345  
   346  	// Copy the remaining elements after new rule
   347  	copy(newFwRuleSlice[newRuleSlicePosition+1:], rawBodyStructure.Values[newRuleSlicePosition:])
   348  	util.Logger.Printf("[DEBUG] CreateDistributedFirewallRule copying remaining items '%d'", newRuleSlicePosition)
   349  
   350  	return newFwRuleSlice, nil
   351  }
   352  
   353  // distributedFirewallRulesRaw is a copy of `types.DistributedFirewallRules` so that values can be
   354  // unmarshalled into json.RawMessage (as strings) instead of exact types `DistributedFirewallRule`
   355  // It has Public field Values so that marshalling can work, but is not exported itself as it is only
   356  // an intermediate type used in `VdcGroup.CreateDistributedFirewallRule`
   357  type distributedFirewallRulesRaw struct {
   358  	Values []json.RawMessage `json:"values"`
   359  }