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 }