Skip to content

Generation of mod_security rules from Swagger spec

Notifications You must be signed in to change notification settings

EP-Tribe/openapi-sec

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

23 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

swagger-mod_security

Let's imagine a simple API which ask for a number of tomatoes or potatoes and return the double of this number.
If we post {"value": "21", "unit": "tomatoes"} to http://server/makeItDouble it'll return {"value": "42", "unit": "tomatoes"} with a status code 200.
Here is the simplified definition of such an API produced by swagger :

"paths": {  
  "/makeItDouble": {  
    "post": {  
      "tags": [  
        "MakeItDouble"  
      ],  
      "summary": "/makeItDouble",  
      "description": "doPostMakeItDouble",  
      "operationId": "doPostMakeItDoubleUsingPOST",  
      "consumes": [  
        "application/json"  
	  ],  
      "produces": [  
        "*/*"  
      ],  
      "parameters": [  
        {  
          "name": "value",  
          "in": "query",  
          "description": "valueToDouble",  
          "required": true,  
          "type": "integer",  
          "format": "int32"  
        },  
        {  
          "name": "unit",  
          "in": "query",  
          "description": "Unit",  
          "required": false,  
          "type": "string",  
  		  "enum": [  
            "potatoes",  
            "tomatoes"  
          ]  
        }  
      ],  
      "responses": {  
        "200": {  
          "description": "OK",  
          "schema": {  
            "$ref": "#/definitions/Double"  
          }  
        },  
        "400": {  
          "description": "invalid query"  
        },  
        "401": {  
          "description": "valid query but insert is impossible"  
    	  },  
        "403": {  
          "description": "insert forbidden for this user"  
        },  
        "404": {  
          "description": "Unknown entity"  
        },  
        "500": {  
          "description": "internal server error"  
        }  
      }  
	}  
  }  
},  
"definitions": {  
  "Double": {  
    "properties": {  
	  "value": {  
	    "type": "number"  
	  },  
	  "unit": {  
	    "type": "string",  
		"enum": [  
            "potatoes",  
            "tomatoes"  
        ]  
	  }  
	}  
  }  
}  

Here comes the output that'll be generated by SecRuleGen if we supply the URL pointing to this specification file :

  • First we open a LocationMatch directive, specifying on which path the rules will be applied :
    <LocationMatch "^/makeItDouble$">

  • A method rule, which will allow only POST and OPTIONS (note : OPTIONS is always accepted by default, because swagger never describe this method) methods on the /makeItDouble endpoint :
    SecRule REQUEST_METHOD "!^(?:POST|OPTIONS)$" "phase:2,t:none,deny,id:'200',msg:'Unauthorized method',logdata:%{REQUEST_METHOD},setenv:METHODERROR"

  • A rule to exclude given IP adresses from the rate limit rules SecRule REMOTE_ADDR "@ipMatch 127.0.0.0/24" "phase:1,skip:4,nolog,id:'201'"

  • Maximum rate rules, preventing client to make more than X ( 50 by default ) requests per minute on a given endpoint :

SecAction "initcol:ip=%{REMOTE_ADDR}_%{TX.uahash},pass,nolog,id:'202'"
SecAction "phase:5,deprecatevar:ip./makeItDouble=50/60,pass,nolog,id:'203'"
SecRule IP:/makeItDouble "@gt 50" "phase:2,pause:300,deny,setenv:RATELIMITED,skip:1,id:'204',msg:'too many request per minute',logdata:%{MATCHED_VAR}"
SecAction "phase:2,pass,setvar:ip./makeItDouble=+1,nolog,id:'205'"
  • An ARGS_NAMES rule, allowing only known arguments (value and unit in our case) to be supplied :
    SecRule ARGS_NAMES "!^(?:value|unit)$" "phase:2,t:none,deny,id:'206',msg:'Unrecognized argument',logdata:%{ARGS_NAMES},setenv:ARGNAMEERROR=%{MATCHED_VAR}"

  • An ARGS rule, allowing only valid value to be supplied in a know variable, in our case unit can contain "tomatoes" or "potatoes" :
    SecRule ARGS:unit "!^(?:tomatoes|potatoes)$" "phase:2,t:none,deny,id:'207',msg:'Bad content',logdata:%{MATCHED_VAR},setenv:CONTENTERROR=%{MATCHED_VAR}"

  • A rule to check if the content-ype of the supplied request is the one described on the API : SecRule REQUEST_HEADERS:Content-Type "!@rx (?i)^(application/json)" "phase:1,t:none,deny,id:'363',status:400,msg:'Invalid content-type',logdata:%{MATCHED_VAR},setenv:CTYPEERROR"

  • Then we close the LocationMatch section :
    </LocationMatch>

  • Every endpoints is described in his own LocationMatch section, once every endpoint are listed we include some rules to return the error to the user in a custom http header :

Header always set Retry-After "60" env=RATELIMITED
Header always set X-error-id "PROXY-000" env=RATELIMITED
Header always set X-error-message "too many request in the last minute" env=RATELIMITED
Header always set X-error-id "PROXY-001" env=ARGNAMEERROR
Header always set X-error-message "wrong argument supplied : %{ARGNAMEERROR}e" env=ARGNAMEERROR
Header always set X-error-id "PROXY-002" env=CTYPERROR
Header always set X-error-message "wrong content type" env=CTYPERROR
Header always set X-error-id "PROXY-003" env=METHODERROR
Header always set X-error-message "wrong method used" env=METHODERROR
Header always set X-error-id "PROXY-004" env=CONTENTERROR
Header always set X-error-message "wrong content in one argument : %{CONTENTERROR}e" env=CONTENTERROR

About

Generation of mod_security rules from Swagger spec

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published