1package hcloud 2 3import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "errors" 8 "fmt" 9 "net" 10 "net/url" 11 "strconv" 12 "time" 13 14 "github.com/hetznercloud/hcloud-go/hcloud/schema" 15) 16 17// Firewall represents a Firewall in the Hetzner Cloud. 18type Firewall struct { 19 ID int 20 Name string 21 Labels map[string]string 22 Created time.Time 23 Rules []FirewallRule 24 AppliedTo []FirewallResource 25} 26 27// FirewallRule represents a Firewall's rules. 28type FirewallRule struct { 29 Direction FirewallRuleDirection 30 SourceIPs []net.IPNet 31 DestinationIPs []net.IPNet 32 Protocol FirewallRuleProtocol 33 Port *string 34} 35 36// FirewallRuleDirection specifies the direction of a Firewall rule. 37type FirewallRuleDirection string 38 39const ( 40 // FirewallRuleDirectionIn specifies a rule for inbound traffic. 41 FirewallRuleDirectionIn FirewallRuleDirection = "in" 42 43 // FirewallRuleDirectionOut specifies a rule for outbound traffic. 44 FirewallRuleDirectionOut FirewallRuleDirection = "out" 45) 46 47// FirewallRuleProtocol specifies the protocol of a Firewall rule. 48type FirewallRuleProtocol string 49 50const ( 51 // FirewallRuleProtocolTCP specifies a TCP rule. 52 FirewallRuleProtocolTCP FirewallRuleProtocol = "tcp" 53 // FirewallRuleProtocolUDP specifies a UDP rule. 54 FirewallRuleProtocolUDP FirewallRuleProtocol = "udp" 55 // FirewallRuleProtocolICMP specifies an ICMP rule. 56 FirewallRuleProtocolICMP FirewallRuleProtocol = "icmp" 57) 58 59// FirewallResourceType specifies the resource to apply a Firewall on. 60type FirewallResourceType string 61 62const ( 63 // FirewallResourceTypeServer specifies a Server. 64 FirewallResourceTypeServer FirewallResourceType = "server" 65) 66 67// FirewallResource represents a resource to apply the new Firewall on. 68type FirewallResource struct { 69 Type FirewallResourceType 70 Server *FirewallResourceServer 71} 72 73// FirewallResourceServer represents a Server to apply a Firewall on. 74type FirewallResourceServer struct { 75 ID int 76} 77 78// FirewallClient is a client for the Firewalls API. 79type FirewallClient struct { 80 client *Client 81} 82 83// GetByID retrieves a Firewall by its ID. If the Firewall does not exist, nil is returned. 84func (c *FirewallClient) GetByID(ctx context.Context, id int) (*Firewall, *Response, error) { 85 req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/firewalls/%d", id), nil) 86 if err != nil { 87 return nil, nil, err 88 } 89 90 var body schema.FirewallGetResponse 91 resp, err := c.client.Do(req, &body) 92 if err != nil { 93 if IsError(err, ErrorCodeNotFound) { 94 return nil, resp, nil 95 } 96 return nil, nil, err 97 } 98 return FirewallFromSchema(body.Firewall), resp, nil 99} 100 101// GetByName retrieves a Firewall by its name. If the Firewall does not exist, nil is returned. 102func (c *FirewallClient) GetByName(ctx context.Context, name string) (*Firewall, *Response, error) { 103 if name == "" { 104 return nil, nil, nil 105 } 106 firewalls, response, err := c.List(ctx, FirewallListOpts{Name: name}) 107 if len(firewalls) == 0 { 108 return nil, response, err 109 } 110 return firewalls[0], response, err 111} 112 113// Get retrieves a Firewall by its ID if the input can be parsed as an integer, otherwise it 114// retrieves a Firewall by its name. If the Firewall does not exist, nil is returned. 115func (c *FirewallClient) Get(ctx context.Context, idOrName string) (*Firewall, *Response, error) { 116 if id, err := strconv.Atoi(idOrName); err == nil { 117 return c.GetByID(ctx, int(id)) 118 } 119 return c.GetByName(ctx, idOrName) 120} 121 122// FirewallListOpts specifies options for listing Firewalls. 123type FirewallListOpts struct { 124 ListOpts 125 Name string 126} 127 128func (l FirewallListOpts) values() url.Values { 129 vals := l.ListOpts.values() 130 if l.Name != "" { 131 vals.Add("name", l.Name) 132 } 133 return vals 134} 135 136// List returns a list of Firewalls for a specific page. 137// 138// Please note that filters specified in opts are not taken into account 139// when their value corresponds to their zero value or when they are empty. 140func (c *FirewallClient) List(ctx context.Context, opts FirewallListOpts) ([]*Firewall, *Response, error) { 141 path := "/firewalls?" + opts.values().Encode() 142 req, err := c.client.NewRequest(ctx, "GET", path, nil) 143 if err != nil { 144 return nil, nil, err 145 } 146 147 var body schema.FirewallListResponse 148 resp, err := c.client.Do(req, &body) 149 if err != nil { 150 return nil, nil, err 151 } 152 firewalls := make([]*Firewall, 0, len(body.Firewalls)) 153 for _, s := range body.Firewalls { 154 firewalls = append(firewalls, FirewallFromSchema(s)) 155 } 156 return firewalls, resp, nil 157} 158 159// All returns all Firewalls. 160func (c *FirewallClient) All(ctx context.Context) ([]*Firewall, error) { 161 allFirewalls := []*Firewall{} 162 163 opts := FirewallListOpts{} 164 opts.PerPage = 50 165 166 err := c.client.all(func(page int) (*Response, error) { 167 opts.Page = page 168 firewalls, resp, err := c.List(ctx, opts) 169 if err != nil { 170 return resp, err 171 } 172 allFirewalls = append(allFirewalls, firewalls...) 173 return resp, nil 174 }) 175 if err != nil { 176 return nil, err 177 } 178 179 return allFirewalls, nil 180} 181 182// AllWithOpts returns all Firewalls for the given options. 183func (c *FirewallClient) AllWithOpts(ctx context.Context, opts FirewallListOpts) ([]*Firewall, error) { 184 var allFirewalls []*Firewall 185 186 err := c.client.all(func(page int) (*Response, error) { 187 opts.Page = page 188 firewalls, resp, err := c.List(ctx, opts) 189 if err != nil { 190 return resp, err 191 } 192 allFirewalls = append(allFirewalls, firewalls...) 193 return resp, nil 194 }) 195 if err != nil { 196 return nil, err 197 } 198 199 return allFirewalls, nil 200} 201 202// FirewallCreateOpts specifies options for creating a new Firewall. 203type FirewallCreateOpts struct { 204 Name string 205 Labels map[string]string 206 Rules []FirewallRule 207 ApplyTo []FirewallResource 208} 209 210// Validate checks if options are valid. 211func (o FirewallCreateOpts) Validate() error { 212 if o.Name == "" { 213 return errors.New("missing name") 214 } 215 return nil 216} 217 218// FirewallCreateResult is the result of a create Firewall call. 219type FirewallCreateResult struct { 220 Firewall *Firewall 221 Actions []*Action 222} 223 224// Create creates a new Firewall. 225func (c *FirewallClient) Create(ctx context.Context, opts FirewallCreateOpts) (FirewallCreateResult, *Response, error) { 226 if err := opts.Validate(); err != nil { 227 return FirewallCreateResult{}, nil, err 228 } 229 reqBody := firewallCreateOptsToSchema(opts) 230 reqBodyData, err := json.Marshal(reqBody) 231 if err != nil { 232 return FirewallCreateResult{}, nil, err 233 } 234 req, err := c.client.NewRequest(ctx, "POST", "/firewalls", bytes.NewReader(reqBodyData)) 235 if err != nil { 236 return FirewallCreateResult{}, nil, err 237 } 238 239 respBody := schema.FirewallCreateResponse{} 240 resp, err := c.client.Do(req, &respBody) 241 if err != nil { 242 return FirewallCreateResult{}, resp, err 243 } 244 result := FirewallCreateResult{ 245 Firewall: FirewallFromSchema(respBody.Firewall), 246 Actions: ActionsFromSchema(respBody.Actions), 247 } 248 return result, resp, nil 249} 250 251// FirewallUpdateOpts specifies options for updating a Firewall. 252type FirewallUpdateOpts struct { 253 Name string 254 Labels map[string]string 255} 256 257// Update updates a Firewall. 258func (c *FirewallClient) Update(ctx context.Context, firewall *Firewall, opts FirewallUpdateOpts) (*Firewall, *Response, error) { 259 reqBody := schema.FirewallUpdateRequest{} 260 if opts.Name != "" { 261 reqBody.Name = &opts.Name 262 } 263 if opts.Labels != nil { 264 reqBody.Labels = &opts.Labels 265 } 266 reqBodyData, err := json.Marshal(reqBody) 267 if err != nil { 268 return nil, nil, err 269 } 270 271 path := fmt.Sprintf("/firewalls/%d", firewall.ID) 272 req, err := c.client.NewRequest(ctx, "PUT", path, bytes.NewReader(reqBodyData)) 273 if err != nil { 274 return nil, nil, err 275 } 276 277 respBody := schema.FirewallUpdateResponse{} 278 resp, err := c.client.Do(req, &respBody) 279 if err != nil { 280 return nil, resp, err 281 } 282 return FirewallFromSchema(respBody.Firewall), resp, nil 283} 284 285// Delete deletes a Firewall. 286func (c *FirewallClient) Delete(ctx context.Context, firewall *Firewall) (*Response, error) { 287 req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("/firewalls/%d", firewall.ID), nil) 288 if err != nil { 289 return nil, err 290 } 291 return c.client.Do(req, nil) 292} 293 294// FirewallSetRulesOpts specifies options for setting rules of a Firewall. 295type FirewallSetRulesOpts struct { 296 Rules []FirewallRule 297} 298 299// SetRules sets the rules of a Firewall. 300func (c *FirewallClient) SetRules(ctx context.Context, firewall *Firewall, opts FirewallSetRulesOpts) ([]*Action, *Response, error) { 301 reqBody := firewallSetRulesOptsToSchema(opts) 302 reqBodyData, err := json.Marshal(reqBody) 303 if err != nil { 304 return nil, nil, err 305 } 306 307 path := fmt.Sprintf("/firewalls/%d/actions/set_rules", firewall.ID) 308 req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) 309 if err != nil { 310 return nil, nil, err 311 } 312 313 var respBody schema.FirewallActionSetRulesResponse 314 resp, err := c.client.Do(req, &respBody) 315 if err != nil { 316 return nil, resp, err 317 } 318 return ActionsFromSchema(respBody.Actions), resp, nil 319} 320 321func (c *FirewallClient) ApplyResources(ctx context.Context, firewall *Firewall, resources []FirewallResource) ([]*Action, *Response, error) { 322 applyTo := make([]schema.FirewallResource, len(resources)) 323 for i, r := range resources { 324 applyTo[i] = firewallResourceToSchema(r) 325 } 326 327 reqBody := schema.FirewallActionApplyToResourcesRequest{ApplyTo: applyTo} 328 reqBodyData, err := json.Marshal(reqBody) 329 if err != nil { 330 return nil, nil, err 331 } 332 333 path := fmt.Sprintf("/firewalls/%d/actions/apply_to_resources", firewall.ID) 334 req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) 335 if err != nil { 336 return nil, nil, err 337 } 338 339 var respBody schema.FirewallActionApplyToResourcesResponse 340 resp, err := c.client.Do(req, &respBody) 341 if err != nil { 342 return nil, resp, err 343 } 344 return ActionsFromSchema(respBody.Actions), resp, nil 345} 346 347func (c *FirewallClient) RemoveResources(ctx context.Context, firewall *Firewall, resources []FirewallResource) ([]*Action, *Response, error) { 348 removeFrom := make([]schema.FirewallResource, len(resources)) 349 for i, r := range resources { 350 removeFrom[i] = firewallResourceToSchema(r) 351 } 352 353 reqBody := schema.FirewallActionRemoveFromResourcesRequest{RemoveFrom: removeFrom} 354 reqBodyData, err := json.Marshal(reqBody) 355 if err != nil { 356 return nil, nil, err 357 } 358 359 path := fmt.Sprintf("/firewalls/%d/actions/remove_from_resources", firewall.ID) 360 req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) 361 if err != nil { 362 return nil, nil, err 363 } 364 365 var respBody schema.FirewallActionRemoveFromResourcesResponse 366 resp, err := c.client.Do(req, &respBody) 367 if err != nil { 368 return nil, resp, err 369 } 370 return ActionsFromSchema(respBody.Actions), resp, nil 371} 372