mirror of
https://github.com/github/codeql.git
synced 2026-04-28 10:15:14 +02:00
python: create shared serverless module and use it
Modelled on the javascript serverless module, but - The predicate that reports YAML files is now public so languages can implement their own file conventions. - It also reports framework and runtime. - The conveninece predicates with files still exist, but they only report the path. - Handler mapping conventions are now documented. - Use parameterised serverless module in Python, tests now pass.
This commit is contained in:
160
shared/yaml/codeql/serverless/ServerLess.qll
Normal file
160
shared/yaml/codeql/serverless/ServerLess.qll
Normal file
@@ -0,0 +1,160 @@
|
||||
/**
|
||||
* Provides classes and predicates for working with serverless handlers.
|
||||
* E.g. [AWS](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-handler.html) or [serverless](https://npmjs.com/package/serverless)
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides the input for the `ServerLess` module.
|
||||
* Most of these should be provided by the `yaml` library.
|
||||
*/
|
||||
signature module Input {
|
||||
// --------------------------------------------------
|
||||
// The below should be provided by the `yaml` library.
|
||||
// --------------------------------------------------
|
||||
class Container {
|
||||
string getAbsolutePath();
|
||||
|
||||
Container getParentContainer();
|
||||
}
|
||||
|
||||
class File extends Container;
|
||||
|
||||
class YamlNode {
|
||||
File getFile();
|
||||
|
||||
YamlCollection getParentNode();
|
||||
}
|
||||
|
||||
class YamlValue extends YamlNode;
|
||||
|
||||
class YamlCollection extends YamlValue;
|
||||
|
||||
class YamlScalar extends YamlValue {
|
||||
string getValue();
|
||||
}
|
||||
|
||||
class YamlMapping extends YamlCollection {
|
||||
YamlValue lookup(string key);
|
||||
|
||||
YamlValue getValue(int i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides classes and predicates for working with serverless handlers.
|
||||
* Supports AWS, Alibaba, and serverless.
|
||||
*
|
||||
* Common usage is to interpret the handlers as functions and add the
|
||||
* first argument of these as remote flow sources.
|
||||
*/
|
||||
module ServerLess<Input I> {
|
||||
import I
|
||||
|
||||
/**
|
||||
* Gets the looked up value as a convenience.
|
||||
*/
|
||||
pragma[inline]
|
||||
private string lookupValue(YamlMapping mapping, string property) {
|
||||
result = mapping.lookup(property).(YamlScalar).getValue()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a string where an ending "/." is simplified to "/" (if it exists).
|
||||
*/
|
||||
bindingset[base]
|
||||
private string removeTrailingDot(string base) {
|
||||
if base.regexpMatch(".*/\\.")
|
||||
then result = base.substring(0, base.length() - 1)
|
||||
else result = base
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a string where a leading "./" is simplified to "" (if it exists).
|
||||
*/
|
||||
bindingset[base]
|
||||
private string removeLeadingDotSlash(string base) {
|
||||
if base.regexpMatch("\\./.*") then result = base.substring(2, base.length()) else result = base
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a string suitable as part of a file path.
|
||||
*/
|
||||
bindingset[base]
|
||||
private string normalise(string base) { result = removeLeadingDotSlash(removeTrailingDot(base)) }
|
||||
|
||||
/**
|
||||
* Holds if the `.yml` file `ymlFile` contains a serverless configuration fro `framework` with
|
||||
* `handler`, `codeURI`, and `runtime` properties.
|
||||
* `codeURI` and `runtime` default to the empty string if no explicit value is set in the configuration.
|
||||
*
|
||||
* `handler` should be interpreted in a language specific way, see `mapping.md`.
|
||||
*/
|
||||
predicate hasServerlessHandler(
|
||||
File ymlFile, string framework, string handler, string codeUri, string runtime
|
||||
) {
|
||||
exists(YamlMapping resource | ymlFile = resource.getFile() |
|
||||
// There exists at least "AWS::Serverless::Function" and "Aliyun::Serverless::Function"
|
||||
resource.lookup("Type").(YamlScalar).getValue().regexpMatch(".*::Serverless::Function") and
|
||||
framework = lookupValue(resource, "Type") and
|
||||
exists(YamlMapping properties | properties = resource.lookup("Properties") |
|
||||
(
|
||||
handler = lookupValue(properties, "Handler") and
|
||||
(
|
||||
if exists(properties.lookup("CodeUri"))
|
||||
then codeUri = normalise(lookupValue(properties, "CodeUri"))
|
||||
else codeUri = ""
|
||||
) and
|
||||
(
|
||||
if exists(properties.lookup("Runtime"))
|
||||
then runtime = lookupValue(properties, "Runtime")
|
||||
else runtime = ""
|
||||
)
|
||||
)
|
||||
)
|
||||
or
|
||||
// The `serverless` library, which specifies a top-level `functions` property
|
||||
framework = "Serverless" and
|
||||
exists(YamlMapping functions |
|
||||
functions = resource.lookup("functions") and
|
||||
not exists(resource.getParentNode()) and
|
||||
handler = lookupValue(functions.getValue(_), "handler") and
|
||||
codeUri = "" and
|
||||
(
|
||||
if exists(functions.lookup("Runtime"))
|
||||
then runtime = lookupValue(functions, "Runtime")
|
||||
else runtime = ""
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `handler` = `filePart . astPart` and `filePart` does not contain a `.`.
|
||||
* This is a convenience predicate, as in many cases the first part of the handler property
|
||||
* should be interpreted as (the stem of) a file name.
|
||||
*/
|
||||
bindingset[handler]
|
||||
predicate splitHandler(string handler, string filePart, string astPart) {
|
||||
exists(string pattern | pattern = "(.*?)\\.(.*)" |
|
||||
filePart = handler.regexpCapture(pattern, 1) and
|
||||
astPart = handler.regexpCapture(pattern, 2)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a file with stem `fileStem` has a serverless handler denoted by `func`.
|
||||
*
|
||||
* This is a convenience predicate for the common case where the first part of the
|
||||
* handler property is the file name.
|
||||
*
|
||||
* `func` should be interpreted in a language specific way, see `mapping.md`.
|
||||
*/
|
||||
predicate hasServerlessHandler(string fileStem, string func, string framework, string runtime) {
|
||||
exists(File ymlFile, string handler, string codeUri, string filePart |
|
||||
hasServerlessHandler(ymlFile, framework, handler, codeUri, runtime)
|
||||
|
|
||||
splitHandler(handler, filePart, func) and
|
||||
fileStem = ymlFile.getParentContainer().getAbsolutePath() + "/" + codeUri + filePart
|
||||
)
|
||||
}
|
||||
}
|
||||
81
shared/yaml/codeql/serverless/mapping.md
Normal file
81
shared/yaml/codeql/serverless/mapping.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# Mapping the `handler` property to a function definition
|
||||
|
||||
## AWS
|
||||
|
||||
[Documentation](https://docs.aws.amazon.com/lambda/latest/dg/welcome.html)
|
||||
|
||||
### Node.js or Typescript
|
||||
See [documentaion](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-handler.html)
|
||||
|
||||
Setting `handler` to `index.handler` means that `handler` is exported from `index.js`.
|
||||
|
||||
For Typescript, code is first transpiled to JavaScript, see [documentation](https://docs.aws.amazon.com/lambda/latest/dg/lambda-typescript.html).
|
||||
|
||||
|
||||
### Python
|
||||
See [documentation](https://docs.aws.amazon.com/lambda/latest/dg/python-handler.html)
|
||||
|
||||
Setting `handler` to `lambda_function.lambda_handler` means that `def lambda_handler` is found in `lambda_function.py`.
|
||||
|
||||
### Ruby
|
||||
See [documentation](https://docs.aws.amazon.com/lambda/latest/dg/ruby-handler.html)
|
||||
|
||||
Setting `handler` to `function.handler` means that `def handler` is found in `function.rb`.
|
||||
Setting `handler` to `source.LambdaFunctions::Handler.process` means that `def self.process` is found inside `class Handler` inside `module LambdaFunctions` in `source.rb`.
|
||||
|
||||
### Java
|
||||
See [documentation](https://docs.aws.amazon.com/lambda/latest/dg/java-handler.html)
|
||||
|
||||
You can express the hander in the following formats:
|
||||
|
||||
- `package.Class::method` – Full format. For example: example.Handler::handleRequest.
|
||||
|
||||
- `package.Class` – Abbreviated format for functions that implement a handler interface. For example: example.Handler.
|
||||
|
||||
### Go
|
||||
See [documentation](https://docs.aws.amazon.com/lambda/latest/dg/golang-handler.html)
|
||||
|
||||
When you configure a function in Go, the value of the handler setting is the executable file name. For example, if you set the value of the handler to Handler, Lambda will call the main() function in the Handler executable file.
|
||||
|
||||
### C#
|
||||
See [documentation](https://docs.aws.amazon.com/lambda/latest/dg/csharp-handler.html)
|
||||
|
||||
`handler` is of this format: `Assembly::Namespace.ClassName::MethodName`.
|
||||
For example, `HelloWorldApp::Example.Hello::MyHandler` if `public Stream MyHandler` is found inside `public class Hello` inside `namespace Example` in the `HelloWorldApp` assembly.
|
||||
|
||||
|
||||
## Aliyun (Alibaba Cloud)
|
||||
[Properties](https://www.alibabacloud.com/help/en/resource-orchestration-service/latest/aliyun-serverless-function)
|
||||
[Languages](https://www.alibabacloud.com/help/en/function-compute/latest/programming-languages)
|
||||
|
||||
### Node.js
|
||||
See [documentation](https://www.alibabacloud.com/help/en/function-compute/latest/node-request-handler)
|
||||
|
||||
The handler must be in the `File name.Method name` format. For example, if your file name is `main.js` and your method name is `handler`, the handler is `main.handler`.
|
||||
|
||||
### Python
|
||||
See [documentation](https://www.alibabacloud.com/help/en/function-compute/latest/programming-languages-python)
|
||||
|
||||
In Python, your request handler must be in the `File name.Method name` format. For example, if your file name is `main.py` and your method name is `handler`, the handler is `main.handler`.
|
||||
|
||||
### Java
|
||||
See [documentation](https://www.alibabacloud.com/help/en/function-compute/latest/programming-languages-java)
|
||||
|
||||
The handler must be in the `[Package name].[Class name]::[Method name]` format. For example, if the name of your package is `example`, the class type is `HelloFC`, and method is `handleRequest`, the handler can be configured as `example.HelloFC::handleRequest`.
|
||||
|
||||
### C#
|
||||
See [documentation](https://www.alibabacloud.com/help/en/function-compute/latest/programming-languages-csharp)
|
||||
|
||||
The handler is in the format of `Assembly::Namespace.ClassName::MethodName`.
|
||||
|
||||
### Go
|
||||
See [documentation](https://www.alibabacloud.com/help/en/function-compute/latest/go-323505)
|
||||
|
||||
The handler for FC functions in the Go language is compiled into an executable binary file. You only need to set the Request Handler parameter of the FC function to the name of the executable file.
|
||||
|
||||
## Serverless
|
||||
[Documentation](https://www.serverless.com/framework/docs/providers/aws/guide/functions)
|
||||
|
||||
The handler property points to the file and module containing the code you want to run in your function.
|
||||
|
||||
There seems to be nothing language specific written down about the handler property.
|
||||
Reference in New Issue
Block a user