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

     1  /*
     2   * Copyright 2023 VMware, Inc.  All rights reserved.  Licensed under the Apache v2 License.
     3   */
     4  
     5  package govcd
     6  
     7  import (
     8  	"fmt"
     9  	"net/http"
    10  	"net/url"
    11  	"regexp"
    12  	"strings"
    13  
    14  	"github.com/vmware/go-vcloud-director/v2/types/v56"
    15  )
    16  
    17  // NsxvDistributedFirewall defines a distributed firewall for a NSX-V VDC
    18  type NsxvDistributedFirewall struct {
    19  	VdcId         string                       // The ID of the VDC
    20  	Configuration *types.FirewallConfiguration // The latest firewall configuration
    21  	Etag          string
    22  	enabled       bool    // internal flag that signifies whether the firewall is enabled
    23  	client        *Client // internal usage client
    24  
    25  	Services      []types.Application      // The list of services for this VDC
    26  	ServiceGroups []types.ApplicationGroup // The list of service groups for this VDC
    27  }
    28  
    29  // NewNsxvDistributedFirewall creates a new NsxvDistributedFirewall
    30  func NewNsxvDistributedFirewall(client *Client, vdcId string) *NsxvDistributedFirewall {
    31  	return &NsxvDistributedFirewall{
    32  		client: client,
    33  		VdcId:  extractUuid(vdcId),
    34  	}
    35  }
    36  
    37  // GetConfiguration retrieves the configuration of a distributed firewall
    38  func (dfw *NsxvDistributedFirewall) GetConfiguration() (*types.FirewallConfiguration, error) {
    39  	// Explicitly retrieving only the Layer 3 rules, as we don't need to deal with layer 2
    40  	initialUrl, err := dfw.client.buildUrl("network", "firewall", "globalroot-0", "config", "layer3sections", dfw.VdcId)
    41  	if err != nil {
    42  		return nil, err
    43  	}
    44  
    45  	requestUrl, err := url.ParseRequestURI(initialUrl)
    46  	if err != nil {
    47  		return nil, err
    48  	}
    49  
    50  	req := dfw.client.NewRequest(nil, http.MethodGet, *requestUrl, nil)
    51  
    52  	resp, err := checkResp(dfw.client.Http.Do(req))
    53  	if err != nil {
    54  		return nil, err
    55  	}
    56  	var config types.FirewallConfiguration
    57  
    58  	var firewallSection types.FirewallSection
    59  	err = decodeBody(types.BodyTypeXML, resp, &firewallSection)
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  	dfw.Etag = resp.Header.Get("etag")
    64  	// The ETag header is needed for further operations. Rules insertion and update need to have a
    65  	// header "If-Match" with the contents of the ETag from a previous read.
    66  	// The same data can be found in the "GenerationNumber" within the section to update.
    67  	// The value of the ETag changes at every GET
    68  	if dfw.Etag == "" && firewallSection.GenerationNumber != "" {
    69  		dfw.Etag = firewallSection.GenerationNumber
    70  	}
    71  	config.Layer3Sections = &types.Layer3Sections{Section: &firewallSection}
    72  	dfw.Configuration = &config
    73  	dfw.Configuration.Layer3Sections = config.Layer3Sections
    74  	dfw.enabled = true
    75  	return &config, nil
    76  }
    77  
    78  // IsEnabled returns true when the distributed firewall is enabled
    79  func (dfw *NsxvDistributedFirewall) IsEnabled() (bool, error) {
    80  	if dfw.VdcId == "" {
    81  		return false, fmt.Errorf("no VDC set for this NsxvDistributedFirewall")
    82  	}
    83  
    84  	conf, err := dfw.GetConfiguration()
    85  	if err != nil {
    86  		return false, nil
    87  	}
    88  	if dfw.client.APIVersion == "36.0" {
    89  		return conf != nil, nil
    90  	}
    91  	return true, nil
    92  }
    93  
    94  // Enable makes the distributed firewall available
    95  // It fails with a non-NSX-V VDC
    96  func (dfw *NsxvDistributedFirewall) Enable() error {
    97  	dfw.enabled = false
    98  	if dfw.VdcId == "" {
    99  		return fmt.Errorf("no AdminVdc set for this NsxvDistributedFirewall")
   100  	}
   101  	initialUrl, err := dfw.client.buildUrl("network", "firewall", "vdc", extractUuid(dfw.VdcId))
   102  	if err != nil {
   103  		return err
   104  	}
   105  
   106  	requestUrl, err := url.ParseRequestURI(initialUrl)
   107  	if err != nil {
   108  		return err
   109  	}
   110  
   111  	req := dfw.client.NewRequest(nil, http.MethodPost, *requestUrl, nil)
   112  
   113  	resp, err := checkResp(dfw.client.Http.Do(req))
   114  	if err != nil {
   115  		return err
   116  	}
   117  	if resp != nil && resp.StatusCode != http.StatusCreated {
   118  		return fmt.Errorf("[enable DistributedFirewall] expected status code %d - received %d", http.StatusCreated, resp.StatusCode)
   119  	}
   120  	dfw.enabled = true
   121  	return nil
   122  }
   123  
   124  // Disable removes the availability of a distributed firewall
   125  // WARNING: it also removes all rules
   126  func (dfw *NsxvDistributedFirewall) Disable() error {
   127  	if dfw.VdcId == "" {
   128  		return fmt.Errorf("no AdminVdc set for this NsxvDistributedFirewall")
   129  	}
   130  	initialUrl, err := dfw.client.buildUrl("network", "firewall", "vdc", extractUuid(dfw.VdcId))
   131  	if err != nil {
   132  		return err
   133  	}
   134  
   135  	requestUrl, err := url.ParseRequestURI(initialUrl)
   136  	if err != nil {
   137  		return err
   138  	}
   139  
   140  	req := dfw.client.NewRequest(nil, http.MethodDelete, *requestUrl, nil)
   141  
   142  	resp, err := checkResp(dfw.client.Http.Do(req))
   143  	if err != nil {
   144  		// VCD 10.3.x sometimes returns an error even though the removal succeeds
   145  		if dfw.client.APIVersion == "36.0" {
   146  			conf, _ := dfw.GetConfiguration()
   147  			if conf == nil {
   148  				return nil
   149  			}
   150  		}
   151  		return fmt.Errorf("error deleting Distributed firewall: %s", err)
   152  
   153  	}
   154  	if resp != nil && resp.StatusCode != http.StatusNoContent {
   155  		return fmt.Errorf("[disable DistributedFirewall] expected status code %d - received %d", http.StatusNoContent, resp.StatusCode)
   156  	}
   157  	dfw.Configuration = nil
   158  	dfw.Services = nil
   159  	dfw.ServiceGroups = nil
   160  	dfw.enabled = false
   161  	return nil
   162  }
   163  
   164  // UpdateConfiguration will either create a new set of rules or update existing ones.
   165  // If the firewall already contains rules, they are overwritten by the ones passed as parameters
   166  func (dfw *NsxvDistributedFirewall) UpdateConfiguration(rules []types.NsxvDistributedFirewallRule) (*types.FirewallConfiguration, error) {
   167  
   168  	oldConf, err := dfw.GetConfiguration()
   169  	if err != nil {
   170  		return nil, err
   171  	}
   172  	if dfw.Etag == "" {
   173  		return nil, fmt.Errorf("error getting ETag from distributed firewall")
   174  	}
   175  	initialUrl, err := dfw.client.buildUrl("network", "firewall", "globalroot-0", "config", "layer3sections", dfw.VdcId)
   176  	if err != nil {
   177  		return nil, err
   178  	}
   179  
   180  	requestUrl, err := url.ParseRequestURI(initialUrl)
   181  	if err != nil {
   182  		return nil, err
   183  	}
   184  
   185  	var errorList []string
   186  	for i := 0; i < len(rules); i++ {
   187  		rules[i].SectionID = oldConf.Layer3Sections.Section.ID
   188  		if rules[i].Direction == "" {
   189  			errorList = append(errorList, fmt.Sprintf("missing Direction in rule n. %d ", i+1))
   190  		}
   191  		if rules[i].PacketType == "" {
   192  			errorList = append(errorList, fmt.Sprintf("missing Packet Type in rule n. %d ", i+1))
   193  		}
   194  		if rules[i].Action == "" {
   195  			errorList = append(errorList, fmt.Sprintf("missing Action in rule n. %d ", i+1))
   196  		}
   197  	}
   198  	if len(errorList) > 0 {
   199  		return nil, fmt.Errorf("missing required elements from rules: %s", strings.Join(errorList, "; "))
   200  	}
   201  
   202  	ruleSet := types.FirewallSection{
   203  		ID:               oldConf.Layer3Sections.Section.ID,
   204  		GenerationNumber: strings.Trim(dfw.Etag, `"`),
   205  		Name:             dfw.VdcId,
   206  		Rule:             rules,
   207  	}
   208  
   209  	var newRuleset types.FirewallSection
   210  
   211  	dfw.client.SetCustomHeader(map[string]string{
   212  		"If-Match": strings.Trim(oldConf.Layer3Sections.Section.GenerationNumber, `"`),
   213  	})
   214  	defer dfw.client.RemoveCustomHeader()
   215  
   216  	contentType := fmt.Sprintf("application/*+xml;version=%s", dfw.client.APIVersion)
   217  
   218  	resp, err := dfw.client.ExecuteRequest(requestUrl.String(), http.MethodPut, contentType,
   219  		"error updating NSX-V distributed firewall: %s", ruleSet, &newRuleset)
   220  
   221  	if err != nil {
   222  		return nil, err
   223  	}
   224  	if resp.StatusCode != http.StatusOK {
   225  		return nil, fmt.Errorf("[update DistributedFirewall] expected status code %d - received %d", http.StatusOK, resp.StatusCode)
   226  	}
   227  	return dfw.GetConfiguration()
   228  }
   229  
   230  // GetServices retrieves the list of services for the current VCD
   231  // If `refresh` = false and the services were already retrieved in a previous operation,
   232  // then it returns the internal values instead of fetching new ones
   233  func (dfw *NsxvDistributedFirewall) GetServices(refresh bool) ([]types.Application, error) {
   234  	if dfw.Services != nil && !refresh {
   235  		return dfw.Services, nil
   236  	}
   237  	if dfw.VdcId == "" {
   238  		return nil, fmt.Errorf("no AdminVdc set for this NsxvDistributedFirewall")
   239  	}
   240  	initialUrl, err := dfw.client.buildUrl("network", "services", "application", "scope", extractUuid(dfw.VdcId))
   241  	if err != nil {
   242  		return nil, err
   243  	}
   244  
   245  	requestUrl, err := url.ParseRequestURI(initialUrl)
   246  	if err != nil {
   247  		return nil, err
   248  	}
   249  
   250  	req := dfw.client.NewRequest(nil, http.MethodGet, *requestUrl, nil)
   251  
   252  	resp, err := checkResp(dfw.client.Http.Do(req))
   253  	if err != nil {
   254  		return nil, err
   255  	}
   256  	if resp != nil && resp.StatusCode != http.StatusOK {
   257  		return nil, fmt.Errorf("error fetching the services: %s", resp.Status)
   258  	}
   259  	var applicationList types.ApplicationList
   260  	err = decodeBody(types.BodyTypeXML, resp, &applicationList)
   261  	if err != nil {
   262  		return nil, err
   263  	}
   264  	dfw.Services = applicationList.Application
   265  	return applicationList.Application, nil
   266  }
   267  
   268  // GetServiceGroups retrieves the list of services for the current VDC
   269  // If `refresh` = false and the services were already retrieved in a previous operation,
   270  // then it returns the internal values instead of fetching new ones
   271  func (dfw *NsxvDistributedFirewall) GetServiceGroups(refresh bool) ([]types.ApplicationGroup, error) {
   272  	if dfw.ServiceGroups != nil && !refresh {
   273  		return dfw.ServiceGroups, nil
   274  	}
   275  	if dfw.VdcId == "" {
   276  		return nil, fmt.Errorf("no AdminVdc set for this NsxvDistributedFirewall")
   277  	}
   278  	initialUrl, err := dfw.client.buildUrl("network", "services", "applicationgroup", "scope", extractUuid(dfw.VdcId))
   279  	if err != nil {
   280  		return nil, err
   281  	}
   282  
   283  	requestUrl, err := url.ParseRequestURI(initialUrl)
   284  	if err != nil {
   285  		return nil, err
   286  	}
   287  
   288  	req := dfw.client.NewRequest(nil, http.MethodGet, *requestUrl, nil)
   289  
   290  	resp, err := checkResp(dfw.client.Http.Do(req))
   291  	if err != nil {
   292  		return nil, err
   293  	}
   294  	if resp != nil && resp.StatusCode != http.StatusOK {
   295  		return nil, fmt.Errorf("error fetching the service groups: %s", resp.Status)
   296  	}
   297  	var applicationGroupList types.ApplicationGroupList
   298  	err = decodeBody(types.BodyTypeXML, resp, &applicationGroupList)
   299  	if err != nil {
   300  		return nil, err
   301  	}
   302  	dfw.ServiceGroups = applicationGroupList.ApplicationGroup
   303  	return applicationGroupList.ApplicationGroup, nil
   304  }
   305  
   306  // Refresh retrieves fresh values for the distributed firewall rules, services, and service groups
   307  func (dfw *NsxvDistributedFirewall) Refresh() error {
   308  	if dfw.VdcId == "" {
   309  		return fmt.Errorf("no AdminVdc set for this NsxvDistributedFirewall")
   310  	}
   311  
   312  	_, err := dfw.GetServices(true)
   313  	if err != nil {
   314  		return err
   315  	}
   316  
   317  	_, err = dfw.GetServiceGroups(true)
   318  	if err != nil {
   319  		return err
   320  	}
   321  
   322  	_, err = dfw.GetConfiguration()
   323  	return err
   324  }
   325  
   326  // GetServiceById retrieves a single service, identified by its ID, for the current VDC
   327  // If the list of services was already retrieved, it uses it, otherwise fetches new ones.
   328  // Returns ErrorEntityNotFound when the requested services was not found
   329  func (dfw *NsxvDistributedFirewall) GetServiceById(serviceId string) (*types.Application, error) {
   330  	services, err := dfw.GetServices(false)
   331  	if err != nil {
   332  		return nil, err
   333  	}
   334  	for _, app := range services {
   335  		if app.ObjectID == serviceId {
   336  			return &app, nil
   337  		}
   338  	}
   339  	return nil, ErrorEntityNotFound
   340  }
   341  
   342  // GetServiceByName retrieves a single service, identified by its name, for the current VDC
   343  // If the list of services was already retrieved, it uses it, otherwise fetches new ones.
   344  // Returns ErrorEntityNotFound when the requested service was not found
   345  func (dfw *NsxvDistributedFirewall) GetServiceByName(serviceName string) (*types.Application, error) {
   346  	services, err := dfw.GetServices(false)
   347  	if err != nil {
   348  		return nil, err
   349  	}
   350  	var foundService types.Application
   351  	for _, app := range services {
   352  		if app.Name == serviceName {
   353  			if foundService.ObjectID != "" {
   354  				return nil, fmt.Errorf("more than one service found with name '%s'", serviceName)
   355  			}
   356  			foundService = app
   357  		}
   358  	}
   359  	if foundService.ObjectID == "" {
   360  		return nil, ErrorEntityNotFound
   361  	}
   362  	return &foundService, nil
   363  }
   364  
   365  // GetServicesByRegex returns a list of services with their names matching the given regular expression
   366  // It may return an empty list (without error)
   367  func (dfw *NsxvDistributedFirewall) GetServicesByRegex(expression string) ([]types.Application, error) {
   368  	services, err := dfw.GetServices(false)
   369  	if err != nil {
   370  		return nil, err
   371  	}
   372  	searchRegex, err := regexp.Compile(expression)
   373  	if err != nil {
   374  		return nil, fmt.Errorf("[GetServicesByRegex] error validating regular expression '%s': %s", expression, err)
   375  	}
   376  	var found []types.Application
   377  	for _, app := range services {
   378  		if searchRegex.MatchString(app.Name) {
   379  			found = append(found, app)
   380  		}
   381  	}
   382  	return found, nil
   383  }
   384  
   385  // GetServiceGroupById retrieves a single service group, identified by its ID, for the current VDC
   386  // If the list of service groups was already retrieved, it uses it, otherwise fetches new ones.
   387  // Returns ErrorEntityNotFound when the requested service group was not found
   388  func (dfw *NsxvDistributedFirewall) GetServiceGroupById(serviceGroupId string) (*types.ApplicationGroup, error) {
   389  	serviceGroups, err := dfw.GetServiceGroups(false)
   390  	if err != nil {
   391  		return nil, err
   392  	}
   393  	for _, appGroup := range serviceGroups {
   394  		if appGroup.ObjectID == serviceGroupId {
   395  			return &appGroup, nil
   396  		}
   397  	}
   398  	return nil, ErrorEntityNotFound
   399  }
   400  
   401  // GetServiceGroupByName retrieves a single service group, identified by its name, for the current VDC
   402  // If the list of service groups was already retrieved, it uses it, otherwise fetches new ones.
   403  // Returns ErrorEntityNotFound when the requested service group was not found
   404  func (dfw *NsxvDistributedFirewall) GetServiceGroupByName(serviceGroupName string) (*types.ApplicationGroup, error) {
   405  	serviceGroups, err := dfw.GetServiceGroups(false)
   406  	if err != nil {
   407  		return nil, err
   408  	}
   409  	var foundAppGroup types.ApplicationGroup
   410  	for _, appGroup := range serviceGroups {
   411  		if appGroup.Name == serviceGroupName {
   412  			if foundAppGroup.ObjectID != "" {
   413  				return nil, fmt.Errorf("more than one service group found with name %s", serviceGroupName)
   414  			}
   415  			foundAppGroup = appGroup
   416  		}
   417  	}
   418  	if foundAppGroup.ObjectID == "" {
   419  		return nil, ErrorEntityNotFound
   420  	}
   421  	return &foundAppGroup, nil
   422  }
   423  
   424  // GetServiceGroupsByRegex returns a list of services with their names matching the given regular expression
   425  // It may return an empty list (without error)
   426  func (dfw *NsxvDistributedFirewall) GetServiceGroupsByRegex(expression string) ([]types.ApplicationGroup, error) {
   427  	serviceGroups, err := dfw.GetServiceGroups(false)
   428  	if err != nil {
   429  		return nil, err
   430  	}
   431  	searchRegex, err := regexp.Compile(expression)
   432  	if err != nil {
   433  		return nil, fmt.Errorf("[GetServiceGroupsByRegex] error validating regular expression '%s': %s", expression, err)
   434  	}
   435  	var found []types.ApplicationGroup
   436  	for _, appGroup := range serviceGroups {
   437  		if searchRegex.MatchString(appGroup.Name) {
   438  			found = append(found, appGroup)
   439  		}
   440  	}
   441  	return found, nil
   442  }