Skip to content
Snippets Groups Projects
Commit 4df3eb2d authored by Sergios Aftsidis's avatar Sergios Aftsidis
Browse files

Fix REST API issues

* Fix errors not allowing `PUT` method from the API
* Validate `status`, to allow for status changes when using
PUT (allow changing a `Route`  to `INACTIVE`, re-submitting
`Route`s with `ERROR` state & more)
* Move decision for whether to `commit_*` a `Route` from
`post_save` to `update` since we need to know both the current
`status` of the `Route` and the desired (new) to pick which
`commit` we want
* We need to expose `id`s in all REST API models since those are
needed when creating relationships between those models
* Register `MatchDscp` model (`Route` uses it)
* Add REST API documentation
* When creating / editing / deleting a `Route` from the API an
asynchronous task is issued which uploads the required configuration
on the flowspec device. Since this is asynchronous, the object
must have a status of `PENDING` until this operation is completed.
parent 8fb4af55
No related branches found
No related tags found
No related merge requests found
[![Documentation Status](https://readthedocs.org/projects/flowspy/badge/?version=latest)](https://readthedocs.org/projects/flowspy/?badge=latest) [![Documentation Status](https://readthedocs.org/projects/flowspy/badge/?version=latest)](https://readthedocs.org/projects/flowspy/?badge=latest)
#Firewall on Demand# # Firewall on Demand
##Description## ## Description
Firewall on Demand applies via NETCONF, flow rules to a network Firewall on Demand applies via NETCONF, flow rules to a network
device. These rules are then propagated via e-bgp to peering routers. device. These rules are then propagated via e-bgp to peering routers.
...@@ -29,84 +29,40 @@ flowspec capable routers. Of course FoD could apply rules directly ...@@ -29,84 +29,40 @@ flowspec capable routers. Of course FoD could apply rules directly
(via NETCONF always) to a router and then ibgp would do the rest. In (via NETCONF always) to a router and then ibgp would do the rest. In
GRNET's case the flowspec capable device is an EX4200. GRNET's case the flowspec capable device is an EX4200.
**Attention**: Make sure your FoD server has ssh access to your flowspec device. **Attention**: Make sure your FoD server has SSH access to your flowspec device.
##Installation Considerations## ## Documentation
You can find the installation instructions for Debian Wheezy (64) You can find detailed documentation including installation / configuration
with Django 1.4.x at [Flowspy documentation](http://flowspy.readthedocs.org). examples at [Flowspy documentation](http://flowspy.readthedocs.org).
If upgrading from a previous version bear in mind the changes introduced in Django 1.4.
## Installation Considerations
##Rest Api## If you are upgrading from a previous version bear in mind the changes
FoD provides a rest api. It uses token as authentication method. introduced in Django 1.4.
### Generating Tokens ## Rest Api
A user can generate a token for his account on "my profile" page from FoD's FoD provides a rest api. It uses token as authentication method. For usage
UI. Then by using this token in the header of the request he can list, retrieve, instructions & examples check the documentation.
modify and create rules.
### Example Usage ## Limitations
Here are some examples:
#### GET items A user can belong to more than one `Peer` without any limitations.
- List all the rules your user has created (admin users can see all the rules) FoD UI polls the server to dynamically update the dashboard and the
"Live Status" about the `Route`s they are aware of. In addition, the polling
implementation fetches information for every `Peer` the user is associated
with. Thus, if a user belongs to many `Peer`s too many AJAX calls will be sent
to the backend which may result in a non responsive state. It is recommended to
keep the peers associated with any user under 5.
curl -X GET https://fod.example.com/api/routes/ -H 'Authorization: Token <Your users token>'
- Retrieve a specific rule: ## Contact
curl -X GET https://fod.example.com/api/routes/<rule_id>/ -H 'Authorization: Token <Your users token>'
- In order to create or modify a rule you have to use POST/PUT methods.
#### POST/PUT rules
In order to update or create rules you can follow this example:
##### Foreign Keys
In order to create/modify a rule you have to connect the rule with some foreign keys:
###### Ports, Fragmentypes, protocols, thenactions
When creating a rule, one can specify:
- source port
- destination port
- port (if source = destination)
That can be done by getting the url of the desired port instance from `/api/ports/<port_id>/`
Same with Fragmentypes in `/api/fragmenttypes/<fragmenttype_id>/`, protocols in `/api/matchprotocol/<protocol_id>/` and then actions in `/api/thenactions/<action_id>/`.
Since we have the urls we want to connect with the rule we want to create, we can make a POST request like the following:
curl -X POST -H 'Authorization: Token <Your users token>' -F "name=Example" -F "comments=Description" -F "source=0.0.0.0/0" -F "sourceport=https://fod.example.com/api/ports/7/" -F "destination=203.0.113.12" https://fod.example.com/api/routes/
And here is a PUT request example:
curl -X PUT -F "name=Example" -F "comments=Description" -F "source=0.0.0.0/0" -F "sourceport=https://fod.example.com/api/ports/7/" -F "destination=83.212.9.93" https://fod.example.com/api/routes/12/ -H 'Authorization: Token <Your users token>'
##Limitations##
A user can belong to more than one peer, without any limitation. This fact may
produce some limitations though, to FoD application. FoD uses polling for updating
dashboard and let users know about other users' actions, who belong to the same
peer. In order to fetch updates from all user's peers, FoD makes ajax calls for
any one of them. It is recommended not to add more than 5 peers to any user,
because it may cause malfunction to FoD application.
##Contact##
You can find more about FoD or raise your issues at GRNET FoD
repository: [GRNET repo](https://code.grnet.gr/fod) or [Github repo](https://github.com/grnet/flowspy).
You can contact us directly at dev{at}noc[dot]grnet(.)gr You can contact us directly at dev{at}noc[dot]grnet(.)gr
## Copyright and license ## Copyright and license
Copyright © 2010-2014 Greek Research and Technology Network (GRNET S.A.) Copyright © 2010-2017 Greek Research and Technology Network (GRNET S.A.)
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
......
# Description
Since v1.3 FoD officially has a REST API. This allows operations on:
* `ThenAction`
* `MatchPort`
* `MatchProtocol`
* `MatchDscp`
* `FragmentType`
* `Route`
The API needs authentication. Out of the box the supported authentication
type is Token Authentication.
## Generating Tokens
A user can generate an API token using the FoD UI. Select "My Profile" from the
top right menu and on the "Api Token" section click "Generate One".
## Accessing the API
The API is available at `/api/`. One can see the available API endpoints for
each model by making a GET request there. An authentication token must be added
in the request:
* Using `cURL`, add the `-H "Authorization: Token <your-token>"`
parameter
* Using Postman, under the "Headers" add a header with name
"Authorization" and value "Token <your-token>".
# Usage Examples
Some basic usage examples will be provided including available
actions. Examples will be provided in `cURL` form.
An example will be provided for `ThenAction`. This example applies to most other
models (`MatchPort`, `FragmentType`, `MatchProtocol`, `MatchDscp`) except
`Route` which is more complex and will be treated separately.
## ThenAction
### GET
#### All items
URL: `/api/thenactions/`
Example:
```
curl -X GET https://fod.example.com/api/thenactions/ -H "Authorization: Token <your-token>"
RESPONSE:
[
{
"id" 1,
"action":"discard",
"action_value":""
},
{
"id" 3,
"action":"rate-limit",
"action_value":"10000k"
},
...
]
```
#### A specific item
One can also GET a specific `ThenAction`, by using the `id` in the GET url
URL: `/api/thenactions/<thenaction-id>/`
Example:
```
curl -X GET https://fod.example.com/api/thenactions/13/ -H "Authorization: Token <your-token>"
RESPONSE:
{
"id" 13,
"action":"discard",
"action_value":""
},
```
### POST
Here both `action`, `action_value` fields are required.
URL: `/api/thenactions/`
Example:
```
curl -X POST https://fod.example.com/api/thenactions/ -F "action=rate-limit" -F "action_value=10k" -H "Authorization: Token <your-token>"
RESPONSE:
{
"id": 24,
"action": "rate-limit",
"action_value": "10k"
}
```
### PUT
Here whichever of the `action`, `action_value` fields can be supplied
URL: `/api/thenactions/<thenaction-id>/`
Example:
```
curl -X PUT https://fod.example.com/api/thenactions/24/ -F "action=rate-limit" -F "action_value=10k" -H "Authorization: Token <your-token>"
RESPONSE:
{
"id": 24,
"action": "rate-limit",
"action_value": "10k"
}
```
### DELETE
URL: `/api/thenactions/<thenaction-id>/`
Example:
```
curl -X DELETE https://fod.example.com/api/thenactions/24/ -H "Authorization: Token <your-token>"
RESPONSE:
NO CONTENT
```
## Route
### GET
#### All items
URL: `/api/routes/`
Example:
```
curl -X GET https://fod.example.com/api/routes/ -H "Authorization: Token <your-token>"
RESPONSE:
[
{
"name": "nonadmin_safts_4T0ABD",
"id": 1,
"comments": "testing rule myman",
"applier": "admin",
"source": "62.217.45.76/32",
"sourceport": [],
"destination": "62.217.45.88/32",
"destinationport": [],
"port": [],
"dscp": [],
"fragmenttype": [],
"icmpcode": "",
"packetlength": null,
"protocol": [],
"tcpflag": "",
"then": [],
"filed": "2017-03-28T14:51:33Z",
"last_updated": "2017-03-28T14:51:33Z",
"status": "INACTIVE",
"expires": "2017-04-04",
"response": "Successfully committed",
"requesters_address": "83.212.9.94"
},
...
]
```
#### A specific item
One can also GET a specific `Route`, by using the `id` in the GET url
URL: `/api/routes/<route-id>/`
Example:
```
curl -X GET https://fod.example.com/api/routes/1/ -H "Authorization: Token <your-token>"
RESPONSE:
{
"name": "nonadmin_safts_4T0ABD",
"id": 1,
"comments": "testing rule myman",
"applier": "admin",
"source": "62.217.45.76/32",
"sourceport": [],
"destination": "62.217.45.88/32",
"destinationport": [],
"port": [],
"dscp": [],
"fragmenttype": [],
"icmpcode": "",
"packetlength": null,
"protocol": [],
"tcpflag": "",
"then": [],
"filed": "2017-03-28T14:51:33Z",
"last_updated": "2017-03-28T14:51:33Z",
"status": "INACTIVE",
"expires": "2017-04-04",
"response": "Successfully committed",
"requesters_address": "83.212.9.94"
}
```
### POST
Required fields:
* `name`: a name for the route
* `source`: a source subnet in CIDR formation
* `destination`: a destination subnet in CIDR formation
* `comments`: a small comment on what this route is about
The response will contain all the additional fields
URL: `/api/routes/`
Example:
```
curl -X POST https://fod.example.com/api/routes/ -F "source=62.217.45.75/32" -F "destination=62.217.45.91/32" -F "name=testroute" -F "comments=Route for testing" -H "Authorization: Token <your-token>"
RESPONSE:
{
"name": "testroute_ODUI3E",
"id": 3,
"comments": "Route for testing",
"applier": "admin",
"source": "62.217.45.76/32",
"sourceport": [],
"destination": "62.217.45.90/32",
"destinationport": [],
"port": [],
"dscp": [],
"fragmenttype": [],
"icmpcode": null,
"packetlength": null,
"protocol": [],
"tcpflag": null,
"then": [],
"filed": "2017-03-29T13:56:45.860Z",
"last_updated": "2017-03-29T13:56:45.860Z",
"status": "PENDING",
"expires": "2017-04-05",
"response": null,
"requesters_address": null
}
```
Notice that the `Route` has a `PENDING` status. This happens because the `Route`
is applied asynchronously to the Flowspec device (the API does not wait for the
operation). After a while the `Route` application will be finished and the
`status` field will contain the updated status (`ACTIVE`, `ERROR` etc).
You can check this `Route`s status by issuing a `GET` request with the `id`
the API returned.
This `Route`, however, is totally useless, since it applies no action for the
matched traffic. Let's add one with a `then` action which will discard it.
To do that, we must first add a `ThenAction` (or pick one of the already
existing) since we need it's `id`. Let's assume a `ThenAction` with an `id` of
`4` exists. To create a new `Route` with this `ThenAction`:
```
curl -X POST https://fod.example.com/api/routes/ -F "source=62.217.45.75/32" -F "destination=62.217.45.91/32" -F "name=testroute" -F "comments=Route for testing" -F "then=https://fod.example.com/api/thenactions/4" -H "Authorization: Token <your-token>"
{
"name":"testroute_9Q5Y90",
"id":5,
"comments":"Route for testing",
"applier":"admin",
"source":"62.217.45.75/32",
"sourceport":[],
"destination":"62.217.45.94/32",
"destinationport":[],
"port":[],
"dscp":[],
"fragmenttype":[],
"icmpcode":null,
"packetlength":null,
"protocol":[],
"tcpflag":null,
"then":[
"https://fod.example.com/api/thenactions/4/"
],
"filed":"2017-03-29T14:21:03.261Z",
"last_updated":"2017-03-29T14:21:03.261Z",
"status":"PENDING",
"expires":"2017-04-05",
"response":null,
"requesters_address":null
}
```
With the same process one can associate a `Route` with the `MatchPort`,
`FragmentType`, `MatchProtocol` & `MatchDscp` models.
NOTE:
When adding multiple `ForeignKey` related fields (such as multiple
`MatchPort` or `ThenAction` items) it is best to use a `json` file on the
request instead of specifying each field as a form argument.
Example:
```
curl -X POST https://fod.example.com/api/routes/ -d@data.json -H "Authorization: Token <your-token>"
data.json:
{
"name": "testroute",
"comments": "Route for testing",
"then": [
"https://fod.example.com/api/thenactions/4",
"https://fod.example.com/api/thenactions/5",
],
"source": "62.217.45.75/32",
"destination": "62.217.45.91/32"
}
RESPONSE:
{
"name":"testroute_9Q5Y90",
"id":5,
"comments":"Route for testing",
"applier":"admin",
"source":"62.217.45.75/32",
"sourceport":[],
"destination":"62.217.45.94/32",
"destinationport":[],
"port":[],
"dscp":[],
"fragmenttype":[],
"icmpcode":null,
"packetlength":null,
"protocol":[],
"tcpflag":null,
"then":[
"https://fod.example.com/api/thenactions/4/"
],
"filed":"2017-03-29T14:21:03.261Z",
"last_updated":"2017-03-29T14:21:03.261Z",
"status":"PENDING",
"expires":"2017-04-05",
"response":null,
"requesters_address":null
}
```
### PUT, PATCH
`Route` objects can be modified using the `PUT` / `PATCH` HTTP methods.
When using `PUT` all fields should be specified (see `POST` section).
However, when using `PATCH` one can specify single fields too. This is useful
for changing the `status` of an `INACTIVE` `Route` to `ACTIVE`.
The process is the same as described above with `POST`. Don't forget to use
the correct method.
### DELETE
See `ThenAction`s.
### General notes on `Route` models:
* When `POST`ing a new `Route`, FoD will automatically commit it to the flowspec
device. Thus, `POST`ing a new `Route` with a status of `INACTIVE` has no effect,
since the `Route` will be activated and the status will be restored to `ACTIVE`.
* When `DELETE`ing a `Route`, the actual `Route` object will remain. FoD will
only delete the rule from the flowspec device and change the `Route`'s status to
'INACTIVE'
* When changing (`PUT`/`PATCH`) a `Route`, FoD will sync the changes to the
flowspec device. Changing the status of the `Route` will activate / delete the
rule respectively.
...@@ -26,7 +26,7 @@ case the flowspec capable device is an EX4200. ...@@ -26,7 +26,7 @@ case the flowspec capable device is an EX4200.
> ** Attention ** > ** Attention **
> >
> Make sure your FoD server has ssh access to your flowspec device. > Make sure your FoD server has SSH access to your flowspec device.
# Contact # Contact
...@@ -37,14 +37,12 @@ You can contact us directly at dev{at}noc[dot]grnet(.)gr ...@@ -37,14 +37,12 @@ You can contact us directly at dev{at}noc[dot]grnet(.)gr
# Repositories # Repositories
- [GRNET FoD repository](https://code.grnet.gr/projects/flowspy)
- [Github FoD repository](https://github.com/grnet/flowspy) - [Github FoD repository](https://github.com/grnet/flowspy)
## Copyright and license ## Copyright and license
Copyright © 2010-2014 Greek Research and Technology Network (GRNET S.A.) Copyright © 2010-2017 Greek Research and Technology Network (GRNET S.A.)
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
...@@ -58,5 +56,3 @@ GNU General Public License for more details. ...@@ -58,5 +56,3 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
...@@ -178,9 +178,9 @@ class Route(models.Model): ...@@ -178,9 +178,9 @@ class Route(models.Model):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if not self.pk: if not self.pk:
hash = id_gen() suff = id_gen()
self.name = "%s_%s" % (self.name, hash) self.name = "%s_%s" % (self.name, suff)
super(Route, self).save(*args, **kwargs) # Call the "real" save() method. super(Route, self).save(*args, **kwargs)
def clean(self, *args, **kwargs): def clean(self, *args, **kwargs):
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
......
"""
Serializers for flowspec models
"""
from rest_framework import serializers from rest_framework import serializers
from flowspec.models import ( from flowspec.models import (
Route, Route, MatchPort, ThenAction, FragmentType, MatchProtocol, MatchDscp)
MatchPort,
ThenAction,
FragmentType,
MatchProtocol
)
from flowspec.validators import ( from flowspec.validators import (
clean_source, clean_source, clean_destination, clean_expires, clean_status)
clean_destination,
clean_expires,
check_if_rule_exists
)
class RouteSerializer(serializers.HyperlinkedModelSerializer): class RouteSerializer(serializers.HyperlinkedModelSerializer):
"""
A serializer for `Route` objects
"""
applier = serializers.CharField(source='applier_username', read_only=True) applier = serializers.CharField(source='applier_username', read_only=True)
def validate(self, data): def validate_source(self, attrs, source):
user = self.context.get('request').user user = self.context.get('request').user
# validate source source_ip = attrs.get('source')
source = data.get('source') res = clean_source(user, source_ip)
res = clean_source( if res != source_ip:
user,
source
)
if res != source:
raise serializers.ValidationError(res) raise serializers.ValidationError(res)
return attrs
# validate destination def validate_destination(self, attrs, source):
destination = data.get('destination') user = self.context.get('request').user
res = clean_destination( destination = attrs.get('destination')
user, res = clean_destination(user, destination)
destination
)
if res != destination: if res != destination:
raise serializers.ValidationError(res) raise serializers.ValidationError(res)
return attrs
# validate expires def validate_expires(self, attrs, source):
expires = data.get('expires') expires = attrs.get('expires')
res = clean_expires( res = clean_expires(expires)
expires
)
if res != expires: if res != expires:
raise serializers.ValidationError(res) raise serializers.ValidationError(res)
return attrs
# check if rule already exists with different name def validate_status(self, attrs, source):
fields = { status = attrs.get('status')
'source': data.get('source'), res = clean_status(status)
'destination': data.get('destination'), if res != status:
} raise serializers.ValidationError(res)
exists = check_if_rule_exists(fields) return attrs
if exists:
raise serializers.ValidationError(exists)
return data
class Meta: class Meta:
model = Route model = Route
fields = ( fields = (
'name', 'name', 'id', 'comments', 'applier', 'source', 'sourceport',
'id', 'destination', 'destinationport', 'port', 'dscp', 'fragmenttype',
'comments', 'icmpcode', 'packetlength', 'protocol', 'tcpflag', 'then', 'filed',
'applier', 'last_updated', 'status', 'expires', 'response', 'comments',
'source', 'requesters_address')
'sourceport', read_only_fields = (
'destination', 'requesters_address', 'response', 'last_updated', 'id', 'filed')
'destinationport',
'port',
'dscp',
'fragmenttype',
'icmpcode',
'packetlength',
'protocol',
'tcpflag',
'then',
'filed',
'last_updated',
'status',
'expires',
'response',
'comments',
'requesters_address',
)
read_only_fields = ('status', 'expires', 'requesters_address', 'response')
class PortSerializer(serializers.HyperlinkedModelSerializer): class PortSerializer(serializers.HyperlinkedModelSerializer):
class Meta: class Meta:
model = MatchPort model = MatchPort
fields = ('port', ) fields = ('id', 'port', )
read_only_fields = ('id', )
class ThenActionSerializer(serializers.HyperlinkedModelSerializer): class ThenActionSerializer(serializers.HyperlinkedModelSerializer):
class Meta: class Meta:
model = ThenAction model = ThenAction
fields = ('action', 'action_value') fields = ('id', 'action', 'action_value')
read_only_fields = ('id', )
class FragmentTypeSerializer(serializers.HyperlinkedModelSerializer): class FragmentTypeSerializer(serializers.HyperlinkedModelSerializer):
class Meta: class Meta:
model = FragmentType model = FragmentType
fields = ('fragmenttype', ) fields = ('id', 'fragmenttype', )
read_only_fields = ('id', )
class MatchProtocolSerializer(serializers.HyperlinkedModelSerializer): class MatchProtocolSerializer(serializers.HyperlinkedModelSerializer):
class Meta: class Meta:
model = MatchProtocol model = MatchProtocol
fields = ('protocol', ) fields = ('id', 'protocol', )
read_only_fields = ('id', )
class MatchDscpSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = MatchDscp
fields = ('id', 'dscp', )
read_only_fields = ('id', )
...@@ -28,6 +28,27 @@ def clean_ip(address): ...@@ -28,6 +28,27 @@ def clean_ip(address):
return _('Malformed address format. Cannot be ...255/32') return _('Malformed address format. Cannot be ...255/32')
def clean_status(status):
"""
Verifies the `status` of a `Route` is valid.
Only allows `ACTIVE` / `INACTIVE` states since the rest should be
assigned from the application
:param status: the status of a `Route`
:type status: str
:returns: Either the status or a validation error message
:rtype: str
"""
allowed_states = ['ACTIVE', 'INACTIVE']
if status not in allowed_states:
return _('Invalid status value. You are allowed to use "{}".'.format(
', '.join(allowed_states)))
return status
def clean_source(user, source): def clean_source(user, source):
success, address = get_network(source) success, address = get_network(source)
if not success: if not success:
...@@ -88,10 +109,12 @@ def clean_destination(user, destination): ...@@ -88,10 +109,12 @@ def clean_destination(user, destination):
def clean_expires(date): def clean_expires(date):
if date: if date:
range_days = (date - datetime.date.today()).days range_days = (date - datetime.date.today()).days
if range_days > 0 and range_days < 11: if range_days > 0 and range_days < settings.MAX_RULE_EXPIRE_DAYS:
return date return date
else: else:
return _('Invalid date range') return _(
'Invalid date range. A rule cannot remain active '
'for more than {} days'.format(settings.MAX_RULE_EXPIRE_DAYS))
def value_list_to_list(valuelist): def value_list_to_list(valuelist):
...@@ -143,11 +166,40 @@ def clean_route_form(data): ...@@ -143,11 +166,40 @@ def clean_route_form(data):
return _('This action "%s" is not permitted') % (then[0].action) return _('This action "%s" is not permitted') % (then[0].action)
def check_if_rule_exists(fields): def check_if_rule_exists(fields, queryset):
"""
Checks if a `Route` object with the same source / destination
addresses exists in a queryset. If not, it checks any `Route`
object (belonging to any user) exists with the same addresses
and reports respectively
:param fields: the source / destination IP addresses
:type fields: dict
:param queryset: the queryset with the user's `Route` objects
:type queryset: `django.db.models.query.QuerySet`
:returns: if the rule exists or not, a message
:rtype: tuple(bool, str)
"""
routes = queryset.filter(
source=fields.get('source'),
destination=IPNetwork(fields.get('destination')).compressed,
)
if routes:
ids = [str(item[0]) for item in routes.values_list('pk')]
return (
True, _('Rule(s) regarding those addresses already exist '
'with id(s) {}. Please edit those instead'.format(', '.join(ids))))
routes = Route.objects.filter( routes = Route.objects.filter(
source=fields.get('source'), source=fields.get('source'),
destination=IPNetwork(fields.get('destination')).compressed, destination=IPNetwork(fields.get('destination')).compressed,
) )
for route in routes: for route in routes:
return _('Rule exists with id %s and status %s. Please edit it.' % (route.id, route.status)) return (
return False True, _('Rule(s) regarding those addresses already exist '
'but you cannot edit them. Please refer to the '
'application\'s administrators for further clarification'))
return (False, None)
...@@ -4,12 +4,8 @@ from rest_framework.exceptions import PermissionDenied ...@@ -4,12 +4,8 @@ from rest_framework.exceptions import PermissionDenied
from rest_framework import viewsets from rest_framework import viewsets
from flowspec.models import ( from flowspec.models import (
Route, Route, MatchPort, ThenAction, FragmentType, MatchProtocol,
MatchPort, MatchDscp)
ThenAction,
FragmentType,
MatchProtocol
)
from flowspec.serializers import ( from flowspec.serializers import (
RouteSerializer, RouteSerializer,
...@@ -17,8 +13,9 @@ from flowspec.serializers import ( ...@@ -17,8 +13,9 @@ from flowspec.serializers import (
ThenActionSerializer, ThenActionSerializer,
FragmentTypeSerializer, FragmentTypeSerializer,
MatchProtocolSerializer, MatchProtocolSerializer,
) MatchDscpSerializer)
from flowspec.validators import check_if_rule_exists
from rest_framework.response import Response from rest_framework.response import Response
...@@ -37,22 +34,115 @@ class RouteViewSet(viewsets.ModelViewSet): ...@@ -37,22 +34,115 @@ class RouteViewSet(viewsets.ModelViewSet):
if self.request.user.is_superuser: if self.request.user.is_superuser:
return Route.objects.all() return Route.objects.all()
elif self.request.user.is_authenticated() and not self.request.user.is_anonymous(): elif (self.request.user.is_authenticated() and not
self.request.user.is_anonymous()):
return Route.objects.filter(applier=self.request.user) return Route.objects.filter(applier=self.request.user)
def list(self, request): def list(self, request):
serializer = RouteSerializer(self.get_queryset(), many=True, context={'request': request}) serializer = RouteSerializer(
self.get_queryset(), many=True, context={'request': request})
return Response(serializer.data) return Response(serializer.data)
def create(self, request): def create(self, request):
serializer = RouteSerializer(context={'request': request}) serializer = RouteSerializer(
return super(RouteViewSet, self).create(request) context={'request': request}, data=request.DATA, partial=True)
if serializer.is_valid():
(exists, message) = check_if_rule_exists(
{'source': serializer.object.source,
'destination': serializer.object.destination},
self.get_queryset())
if exists:
return Response({"non_field_errors": [message]}, status=400)
else:
return super(RouteViewSet, self).create(request)
else:
return Response(serializer.errors, status=400)
def retrieve(self, request, pk=None): def retrieve(self, request, pk=None):
route = get_object_or_404(self.get_queryset(), pk=pk) route = get_object_or_404(self.get_queryset(), pk=pk)
serializer = RouteSerializer(route) serializer = RouteSerializer(route, context={'request': request})
return Response(serializer.data) return Response(serializer.data)
def update(self, request, pk=None, partial=False):
"""
Overriden to customize `status` update behaviour.
Changes in `status` need to be handled here, since we have to know the
previous `status` of the object to choose the correct action.
"""
def set_object_pending(obj):
"""
Sets an object's status to "PENDING". This reflects that
the object has not already been commited to the flowspec device,
and the asynchronous job that will handle the sync will
update the status accordingly
:param obj: the object whose status will be changed
:type obj: `flowspec.models.Route`
"""
obj.status = "PENDING"
obj.response = "N/A"
obj.save()
def work_on_active_object(obj, new_status):
"""
Decides which `commit` action to choose depending on the
requested status
Cases:
* `ACTIVE` ~> `INACTIVE`: The `Route` must be deleted from the
flowspec device (`commit_delete`)
* `ACTIVE` ~> `ACTIVE`: The `Route` is present, so it must be
edited (`commit_edit`)
:param new_status: the newly requested status
:type new_status: str
:param obj: the `Route` object
:type obj: `flowspec.models.Route`
"""
set_object_pending(obj)
if new_status == 'INACTIVE':
obj.commit_delete()
else:
obj.commit_edit()
def work_on_inactive_object(obj, new_status):
"""
Decides which `commit` action to choose depending on the
requested status
Cases:
* `INACTIVE` ~> `ACTIVE`: The `Route` is not present on the device
:param new_status: the newly requested status
:type new_status: str
:param obj: the `Route` object
:type obj: `flowspec.models.Route`
"""
if new_status == 'ACTIVE':
set_object_pending(obj)
obj.commit_add()
obj = get_object_or_404(self.queryset, pk=pk)
old_status = obj.status
serializer = RouteSerializer(
obj, context={'request': request},
data=request.DATA, partial=partial)
if serializer.is_valid():
new_status = serializer.object.status
super(RouteViewSet, self).update(request, pk, partial=partial)
if old_status == 'ACTIVE':
work_on_active_object(obj, new_status)
elif old_status in ['INACTIVE', 'ERROR']:
work_on_inactive_object(obj, new_status)
return Response(
RouteSerializer(obj,context={'request': request}).data,
status=200)
else:
return Response(serializer.errors, status=400)
def pre_save(self, obj): def pre_save(self, obj):
# DEBUG # DEBUG
if settings.DEBUG: if settings.DEBUG:
...@@ -69,9 +159,6 @@ class RouteViewSet(viewsets.ModelViewSet): ...@@ -69,9 +159,6 @@ class RouteViewSet(viewsets.ModelViewSet):
def post_save(self, obj, created): def post_save(self, obj, created):
if created: if created:
obj.commit_add() obj.commit_add()
else:
if obj.status not in ['EXPIRED', 'INACTIVE', 'ADMININACTIVE']:
obj.commit_edit()
def pre_delete(self, obj): def pre_delete(self, obj):
obj.commit_delete() obj.commit_delete()
...@@ -95,3 +182,8 @@ class FragmentTypeViewSet(viewsets.ModelViewSet): ...@@ -95,3 +182,8 @@ class FragmentTypeViewSet(viewsets.ModelViewSet):
class MatchProtocolViewSet(viewsets.ModelViewSet): class MatchProtocolViewSet(viewsets.ModelViewSet):
queryset = MatchProtocol.objects.all() queryset = MatchProtocol.objects.all()
serializer_class = MatchProtocolSerializer serializer_class = MatchProtocolSerializer
class MatchDscpViewSet(viewsets.ModelViewSet):
queryset = MatchDscp.objects.all()
serializer_class = MatchDscpSerializer
...@@ -288,6 +288,7 @@ ACCOUNT_ACTIVATION_DAYS = 7 ...@@ -288,6 +288,7 @@ ACCOUNT_ACTIVATION_DAYS = 7
# Define subnets that should not have any rules applied whatsoever # Define subnets that should not have any rules applied whatsoever
PROTECTED_SUBNETS = ['10.10.0.0/16'] PROTECTED_SUBNETS = ['10.10.0.0/16']
MAX_RULE_EXPIRE_DAYS = 10
# Add two whois servers in order to be able to get all the subnets for an AS. # Add two whois servers in order to be able to get all the subnets for an AS.
PRIMARY_WHOIS = 'whois.example.com' PRIMARY_WHOIS = 'whois.example.com'
...@@ -356,4 +357,4 @@ if SENTRY.get('activate'): ...@@ -356,4 +357,4 @@ if SENTRY.get('activate'):
LOGGING['handlers']['sentry'] = { LOGGING['handlers']['sentry'] = {
'class': 'raven.contrib.django.handlers.SentryHandler' 'class': 'raven.contrib.django.handlers.SentryHandler'
} }
LOGGING['loggers']['django.request']['handlers'] = ['sentry'] LOGGING['loggers']['django.request']['handlers'] = ['sentry']
\ No newline at end of file
...@@ -9,6 +9,7 @@ from flowspec.viewsets import ( ...@@ -9,6 +9,7 @@ from flowspec.viewsets import (
ThenActionViewSet, ThenActionViewSet,
FragmentTypeViewSet, FragmentTypeViewSet,
MatchProtocolViewSet, MatchProtocolViewSet,
MatchDscpViewSet,
) )
admin.autodiscover() admin.autodiscover()
...@@ -20,6 +21,7 @@ router.register(r'ports', PortViewSet) ...@@ -20,6 +21,7 @@ router.register(r'ports', PortViewSet)
router.register(r'thenactions', ThenActionViewSet) router.register(r'thenactions', ThenActionViewSet)
router.register(r'fragmentypes', FragmentTypeViewSet) router.register(r'fragmentypes', FragmentTypeViewSet)
router.register(r'matchprotocol', MatchProtocolViewSet) router.register(r'matchprotocol', MatchProtocolViewSet)
router.register(r'matchdscp', MatchDscpViewSet)
urlpatterns = patterns( urlpatterns = patterns(
......
...@@ -10,3 +10,4 @@ pages: ...@@ -10,3 +10,4 @@ pages:
- 'Debian': 'installation/debian_wheezy.md' - 'Debian': 'installation/debian_wheezy.md'
- 'Red Hat': 'installation/redhat.md' - 'Red Hat': 'installation/redhat.md'
- 'Configuration': 'configuration.md' - 'Configuration': 'configuration.md'
- 'API': 'api.md'
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment