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 }