diff --git a/artifacts/linting_rules/.github/workflows/megalinter.yml b/artifacts/linting_rules/.github/workflows/megalinter.yml new file mode 100644 index 00000000..6bda7009 --- /dev/null +++ b/artifacts/linting_rules/.github/workflows/megalinter.yml @@ -0,0 +1,78 @@ +--- +# MegaLinter GitHub Action configuration file +# More info at https://megalinter.io +# CAMARA Project - Github Action for Pull Reqests +# 31.01.2024 - initial version + +name: MegaLinter + +on: # yamllint disable-line rule:truthy + # Pull Requests to main + pull_request: + branches: [master, main] + +env: # Comment env block if you do not want to apply fixes + # Apply linter fixes configuration + APPLY_FIXES: all # When active, APPLY_FIXES must also be defined as environment variable (in github/workflows/mega-linter.yml or other CI tool) + APPLY_FIXES_EVENT: pull_request # Decide which event triggers application of fixes in a commit or a PR (pull_request, push, all) + APPLY_FIXES_MODE: commit # If APPLY_FIXES is used, defines if the fixes are directly committed (commit) or posted in a PR (pull_request) + +concurrency: + group: ${{ github.ref }}-${{ github.workflow }} + cancel-in-progress: true + +jobs: + build: + name: MegaLinter + runs-on: ubuntu-latest + permissions: + # Give the default GITHUB_TOKEN write permission to commit and push, comment issues & post new PR + # Remove the ones you do not need + contents: write + issues: write + pull-requests: write + steps: + # Git Checkout + - name: Checkout Code + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + fetch-depth: 0 # If you use VALIDATE_ALL_CODEBASE = true, you can remove this line to improve performances + - name: Install Spectral + run: npm install -g @stoplight/spectral + - name: Install Spectral functions + run: npm install -g @stoplight/spectral-functions + # - name: Run spectral:oas Spectral Linting + # run: spectral lint code/API_definitions/*.yaml --verbose --ruleset .spectral.yml + # Replace openapi.yaml file with your API specification file + + # MegaLinter + - name: MegaLinter + id: ml + # You can override MegaLinter flavor used to have faster performances + # More info at https://megalinter.io/flavors/ + uses: oxsecurity/megalinter/flavors/java@v7.3.0 + env: + # All available variables are described in documentation + # https://megalinter.io/configuration/ + PRINT_ALPACA: false + # VALIDATE_ALL_CODEBASE: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} # Validates all source when push on main, else just the git diff with main. Override with true if you always want to lint all sources + VALIDATE_ALL_CODEBASE: true + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # ADD YOUR CUSTOM ENV VARIABLES HERE OR DEFINE THEM IN A FILE .mega-linter.yml AT THE ROOT OF YOUR REPOSITORY + DISABLE: COPYPASTE,MARKDOWN + DISABLE_LINTERS: SPELL_CSPELL,SPELL_LYCHEE,YAML_PRETTIER,REPOSITORY_GRYPE, REPOSITORY_SEMGREP,REPOSITORY_DEVSKIM,REPOSITORY_KICS,REPOSITORY_TRIVY,REPOSITORY_TRIVY_SBOM,REPOSITORY_TRUFFLEHOG,REPOSITORY_CHECKOV,REPOSITORY_GITLEAKS,YAML_V8R,JAVA_PMD,JAVA_CHECKSTYLE + YAML_YAMLLINT_CONFIG_FILE: ".yamllint.yaml" + OPENAPI_SPECTRAL_CONFIG_FILE: ".spectral.yml" + YAML_YAMLLINT_FILTER_REGEX_INCLUDE: "(code/)" + OPENAPI_SPECTRAL_FILTER_REGEX_INCLUDE: "(code/)" + + # Upload MegaLinter artifacts + - name: Archive production artifacts + if: ${{ success() }} || ${{ failure() }} + uses: actions/upload-artifact@v4 + with: + name: MegaLinter reports + path: | + megalinter-reports + mega-linter.log diff --git a/artifacts/linting_rules/.github/workflows/spectral_oas_lint.yml b/artifacts/linting_rules/.github/workflows/spectral_oas_lint.yml new file mode 100644 index 00000000..a828fd58 --- /dev/null +++ b/artifacts/linting_rules/.github/workflows/spectral_oas_lint.yml @@ -0,0 +1,36 @@ +--- +# CAMARA Project - workflow configuration to manually run CAMARA OAS rules +# see https://docs.github.com/en/actions/using-workflows/manually-running-a-workflow +# 31.01.2024 - initial version + +name: Spectral manual run + +on: workflow_dispatch + +concurrency: + group: ${{ github.ref }}-${{ github.workflow }} + cancel-in-progress: true + +jobs: + build: + name: Spectral linting + runs-on: ubuntu-latest + permissions: + # Give the default GITHUB_TOKEN write permission to commit and push, comment issues & post new PR + # Remove the ones you do not need + contents: write + issues: write + pull-requests: write + steps: + # Git Checkout + - name: Checkout Code + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + fetch-depth: 0 # If you use VALIDATE_ALL_CODEBASE = true, you can remove this line to improve performances + - name: Install Spectral + run: npm install -g @stoplight/spectral + - name: Install Spectral functions + run: npm install -g @stoplight/spectral-functions + - name: Run Spectral linting + run: spectral lint code/API_definitions/*.yaml --verbose --ruleset .spectral.yml diff --git a/artifacts/linting_rules/.spectral.yml b/artifacts/linting_rules/.spectral.yml new file mode 100644 index 00000000..0b16508e --- /dev/null +++ b/artifacts/linting_rules/.spectral.yml @@ -0,0 +1,258 @@ +# CAMARA Project - linting ruleset - documentation avaialable here: +# https://github.com/camaraproject/Commonalities/blob/main/documentation/Linting-rules.md +# 31.01.2024 - initial version + +extends: "spectral:oas" +functions: + - camara-reserved-words + - camara-language-avoid-telco + - camara-security-no-secrets-in-path-or-query-parameters +functionsDir: "./lint_function" +rules: + # Built-in OpenAPI Specification ruleset. Each rule then can be enabled individually. + # The severity keyword is optional in rule definition and can be error, warn, info, hint, or off. The default value is warn. + contact-properties: false + duplicated-entry-in-enum: true + info-contact: true + info-description: true + info-license: true + license-url: true + no-$ref-siblings: error + no-eval-in-markdown: true + no-script-tags-in-markdown: true + openapi-tags: false + openapi-tags-alphabetical: false + openapi-tags-uniqueness: error + operation-description: true + operation-operationId: true + operation-operationId-unique: error + operation-operationId-valid-in-url: true + operation-parameters: true + operation-singular-tag: true + operation-success-response: true + operation-tags: true + operation-tag-defined: true + path-declarations-must-exist: true + path-keys-no-trailing-slash: true + path-not-include-query: true + path-params: error + tag-description: false + typed-enum: true + oas3-api-servers: true + oas3-examples-value-or-externalValue: true + oas3-operation-security-defined: false + oas3-parameter-description: false + oas3-schema: true + oas3-server-not-example.com: false + oas3-server-trailing-slash: true + oas3-unused-component: true + oas3-valid-media-example: true + oas3-valid-schema-example: true + # oas3-server-variables: true + + # Custom Rules Utilizing Spectral's Built-in Functions and JavaScript Implementations + + camara-language-avoid-telco: + message: "{{error}}" + severity: hint + description: | + This rule checks for telco-specific terminology in your API definitions and suggests more inclusive terms. + given: "$..*.*" + then: + function: camara-language-avoid-telco + recommended: false # Set to true/false to enable/disable this rule + + camara-oas-version: + message: "OpenAPI Version Error: The OpenAPI specification must adhere to version 3.0.3." + severity: error + description: | + This rule validates the OpenAPI version in your specification and requires compliance with version 3.0.3. + given: "$" + then: + field: openapi + function: pattern + functionOptions: + match: 3.0.3 + recommended: true # Set to true/false to enable/disable this rule + + camara-path-param-id: + message: "Path Parameter Naming Warning: Use 'resource_id' instead of just 'id' in path parameters." + severity: warn + description: | + This rule ensures consistent and descriptive naming for path parameters in your OpenAPI specification. + Please use 'resource_id' instead of just 'id' for your path parameters. + given: "$..parameters[?(@.in == 'path')]" + then: + field: name + function: pattern + functionOptions: + notMatch: \b(id|Id|ID|iD)\b + recommended: true # Set to true/false to enable/disable this rule + + camara-security-no-secrets-in-path-or-query-parameters: + message: "Sensitive data found in path: {{error}} Consider avoiding the use of Sesentive data " + severity: warn + description: | + This rule checks for sensitive data ('MSISDN' and 'IMSI') in API paths and suggests avoiding their use. + given: + - "$.paths" + then: + function: camara-security-no-secrets-in-path-or-query-parameters + recommended: true # Set to true/false to enable/disable this rule + + camara-http-methods: + description: "Ensure that all path URLs have valid HTTP methods (GET, PUT, POST, DELETE, PATCH, OPTIONS)." + message: "Invalid HTTP method for '{{path}}'. Must be one of get, put, post, delete, patch, options." + severity: error + given: $.paths[*][*]~ + then: + function: pattern + functionOptions: + match: "^(get|put|post|delete|patch|options)$" + recommended: true # Set to true/false to enable/disable this rule + + camara-get-no-request-body: + message: There must be no request body for Get and DELETE + severity: error + given: + - "$.paths.*.get" + - "$.paths.*.delete" + then: + field: requestBody + function: falsy + recommended: true # Set to true/false to enable/disable this rule + + camara-reserved-words: + message: "Reserved words found {{error}} Consider avoiding the use of reserved word " + severity: warn + description: | + This rule checks Reserved words must not be used in the following parts of an API specification [Paths, Request Body properties, Component, Operation Id, Security Schema] + given: + - "$.paths" # Paths + - "$..parameters[*]" # Path or Query Parameter Names: + - "$..components.schemas.*.properties.*" # Request and Response body parameter + - "$.paths.*." # Path and Operation Names: + - "$.components.securitySchemes" # Security Schemes: + - "$.components.*.*" # Component Names: + - "$.paths.*.*.operationId" # OperationIds: + then: + function: camara-reserved-words + recommended: true # Set to true/false to enable/disable this rule + + camara-routes-description: + message: "Functionality method description Warning: Each method should have description." + severity: warn + description: | + This rule checks if each operation (POST, GET, DELETE, PUT, PATCH, OPTIONS) in your API specification has a description. + Ensure that you have added a 'summary' field for each operation in your OpenAPI specification. + given: + - "$.paths.*.post" + - "$.paths.*.get" + - "$.paths.*.delete" + - "$.paths.*.put" + - "$.paths.*.patch" + - "$.paths.*.options" + then: + field: description + function: truthy + recommended: true # Set to true/false to enable/disable this rule + + camara-parameters-descriptions: + message: "Parameter description is missing or empty: {{error}}" + severity: warn + description: | + This Spectral rule ensures that each path parameter in the API specification has a descriptive and meaningful description. + given: + - "$.paths..parameters.*" + then: + field: description + function: truthy + recommended: true # Set to true/false to enable/disable this rule + + camara-response-descriptions: + message: "Parameter description is missing or empty: {{error}}" + severity: warn + description: | + This Spectral rule ensures that each responese object in the API specification has a descriptive and meaningful description. + given: + - "$.paths..responses.*" + then: + field: description + function: truthy + recommended: true # Set to true/false to enable/disable this rule + + camara-properties-descriptions: + message: "Property description is missing or empty: {{error}}" + severity: warn + description: | + This Spectral rule ensures that each propoerty within objects in the API specification has a descriptive and meaningful description. + given: + - "$.components.*.*" + - "$.components.*.*.properties.*" + then: + field: description + function: truthy + recommended: true # Set to true/false to enable/disable this rule + + camara-operation-summary: + message: "Operation Summary Warning: Each operation should include a short summary for better understanding." + severity: warn + description: | + This rule checks if each operation (POST, GET, DELETE, PUT, PATCH, OPTIONS) in your API specification has a meaningful summary. + Ensure that you have added a 'summary' field for each operation in your OpenAPI specification. + given: + - "$.paths.*.post" + - "$.paths.*.get" + - "$.paths.*.delete" + - "$.paths.*.put" + - "$.paths.*.patch" + - "$.paths.*.options" + then: + field: summary + function: truthy + recommended: true # Set to true/false to enable/disable this rule + + camara-discriminator-use: + description: | + Ensure that API definition YAML files with oneOf or anyOf sections include a discriminator object for serialization, deserialization, and validation. + severity: hint + given: "$..[?(@.oneOf || @.anyOf)]" + then: + field: discriminator + function: truthy + description: "Discriminator object is required when using oneOf or anyOf." + recommended: true # Set to true/false to enable/disable this rule + + camara-operationid-casing-convention: + message: Operation Id must be in Camel case "{{error}}" + severity: hint + description: | + This rule checks Operation ids should follow a specific case convention: camel case. + given: "$.paths.*.*.operationId" + then: + function: casing + functionOptions: + type: camel + recommended: true # Set to true/false to enable/disable this rule + + camara-schema-casing-convention: + description: This rule checks schema should follow a specific case convention pascal case. + message: "{{property}} should be pascal case (UppperCamelCase)" + severity: warn + given: $.components.schemas[*]~ + then: + function: casing + functionOptions: + type: pascal + recommended: true # Set to true/false to enable/disable this rule + + camara-parameter-casing-convention: + description: Paths should be kebab-case. + severity: error + message: "{{property}} is not kebab-case: {{error}}" + given: $.paths[*]~ + then: + function: pattern + functionOptions: + match: "^\/([a-z0-9]+(-[a-z0-9]+)*)?(\/[a-z0-9]+(-[a-z0-9]+)*|\/{.+})*$" # doesn't allow /asasd{asdas}sadas pattern or not closed braces + recommended: true # Set to true/false to enable/disable this rule diff --git a/artifacts/linting_rules/.yamllint.yaml b/artifacts/linting_rules/.yamllint.yaml new file mode 100644 index 00000000..081ef093 --- /dev/null +++ b/artifacts/linting_rules/.yamllint.yaml @@ -0,0 +1,35 @@ +--- +# CAMARA Project - YAML linting configuration for yamllint https://yamllint.readthedocs.io/en/latest/rules.html +# 31.01.2024 - initial version + +yaml-files: + - '*.yaml' + - '*.yml' + - '.yamllint' + +rules: + braces: enable + brackets: enable + colons: enable + commas: enable + comments: + min-spaces-from-content: 1 + level: error + comments-indentation: + level: error + document-end: disable + document-start: disable + empty-lines: enable + empty-values: disable + hyphens: enable + indentation: enable + key-duplicates: enable + key-ordering: disable + line-length: disable + new-line-at-end-of-file: enable + new-lines: disable + octal-values: disable + quoted-strings: disable + trailing-spaces: enable + truthy: + level: error diff --git a/artifacts/linting_rules/lint_function/camara-language-avoid-telco.js b/artifacts/linting_rules/lint_function/camara-language-avoid-telco.js new file mode 100644 index 00000000..061b5431 --- /dev/null +++ b/artifacts/linting_rules/lint_function/camara-language-avoid-telco.js @@ -0,0 +1,40 @@ +// CAMARA Project - support function for Spectral linter +// 31.01.2024 - initial version + +const replacements = [ + { original: 'UE', recommended: 'device' }, + { original: 'MSISDN', recommended: 'phone number' }, + { original: 'mobile network', recommended: 'network' } +]; + +export default async function (input) { + const errors = []; + const suggestions = []; + + // Iterate over properties of the input object + for (const path in input) { + const value = input[path]; + + // Check if the value is a string + if (typeof value === 'string') { + for (const replacement of replacements) { + const original = replacement.original; + const recommended = replacement.recommended; + + // Use a regular expression to match 'original' as a standalone word + const regex = new RegExp(`\\b${original}\\b`, 'g'); + + // Check if 'original' exists in the value + if (regex.test(value)) { + errors.push(replacement); + suggestions.push(` Telco-specific terminology found in input: Consider replacing '${original}' with '${recommended}'.`); + } + } + } + } + + // Check if any word from 'replacements' is in the suggestions + if (errors.length > 0) { + console.log(`Hint camara-language-avoid-telco ` + suggestions.join(', ')); + } +}; diff --git a/artifacts/linting_rules/lint_function/camara-reserved-words.js b/artifacts/linting_rules/lint_function/camara-reserved-words.js new file mode 100644 index 00000000..c28e63ab --- /dev/null +++ b/artifacts/linting_rules/lint_function/camara-reserved-words.js @@ -0,0 +1,98 @@ +// CAMARA Project - support function for Spectral linter +// 31.01.2024 - initial version + +const reservedWords = [ + 'abstract', + 'apiclient', + 'apiexception', + 'apiresponse', + 'assert', + 'boolean', + 'break', + 'byte', + 'case', + 'catch', + 'char', + 'class', + 'configuration', + 'const', + 'continue', + 'do', + 'double', + 'else', + 'extends', + 'file', + 'final', + 'finally', + 'float', + 'for', + 'goto', + 'if', + 'implements', + 'import', + 'instanceof', + 'int', + 'interface', + 'list', + 'localdate', + 'localreturntype', + 'localtime', + 'localvaraccept', + 'localvaraccepts', + 'localvarauthnames', + 'localvarcollectionqueryparams', + 'localvarcontenttype', + 'localvarcontenttypes', + 'localvarcookieparams', + 'localvarformparams', + 'localvarheaderparams', + 'localvarpath', + 'localvarpostbody', + 'localvarqueryparams', + 'long', + 'native', + 'new', + 'null', + 'object', + 'offsetdatetime', + 'package', + 'private', + 'protected', + 'public', + 'return', + 'short', + 'static', + 'strictfp', + 'stringutil', + 'super', + 'switch', + 'synchronized', + 'this', + 'throw', + 'throws', + 'transient', + 'try', + 'void', + 'volatile', + 'while' +]; +// Reserved word 'enum' and 'default' are removed from above reserved word array as they are common in openAPI keyword +export default async function lintReservedWords(input) { + // Iterate over properties of the input object + for (const path in input) { + if (typeof path === 'string') { + + for (const word of reservedWords) { + const regex = new RegExp(`\\b${word}\\b`, 'g'); // Use a regular expression to match 'word' as a standalone word + + if (regex.test(path)) { + const warningRuleName = 'camara-reserved-words'; + const description = `Reserved words found in input: Consider avoiding the use of reserved word '${word}'`; + // const location = `${path}`; + + console.log(`warning ${warningRuleName} ${description} ${path}`); + } + } + } + } +} diff --git a/artifacts/linting_rules/lint_function/camara-security-no-secrets-in-path-or-query-parameters.js b/artifacts/linting_rules/lint_function/camara-security-no-secrets-in-path-or-query-parameters.js new file mode 100644 index 00000000..ebbff2a4 --- /dev/null +++ b/artifacts/linting_rules/lint_function/camara-security-no-secrets-in-path-or-query-parameters.js @@ -0,0 +1,26 @@ +// CAMARA Project - support function for Spectral linter +// 31.01.2024 - initial version + +const sensitiveData = ['MSISDN','IMSI','phoneNumber']; + +export default async function (input) { + + // Iterate over properties of the input object + for (const path in input) { + + if (typeof path === 'string') { + for (const word of sensitiveData ) { + const regex = new RegExp(`\\b${word}\\b`, 'g'); // Use a regular expression to match 'word' as a standalone word + + if (regex.test(path)) { + + const warningRuleName = 'camara-security-no-secrets-in-path-or-query-parameters'; + const description = `sensitiveData Data found in path: Consider avoiding the use of sensitiveData data '${word}'`; + const location = `paths.${path}`; + console.log(`warning ${warningRuleName} ${description} ${location}`); + + } + } + } + } +} diff --git a/documentation/API-linting-Implementation-Guideline.md b/documentation/API-linting-Implementation-Guideline.md new file mode 100644 index 00000000..f26256b0 --- /dev/null +++ b/documentation/API-linting-Implementation-Guideline.md @@ -0,0 +1,81 @@ +# CAMARA OpenAPI Linting Rules Implementaion Guideline [ How to integrate the rules into CAMARA repository ] + +## Introduction + +This guide provides instructions how to implement linting rules for the CAMARA APIs using two methods: **[GitHub Actions](API-linting-Implementation-Guideline.md#github-actions-integration)** and **[local deployment](API-linting-Implementation-Guideline.md#github-actions-integration)**, both methods use [Spectral tool](https://docs.stoplight.io/docs/spectral/674b27b261c3c-overview). +All needed files are stored in [artifacts subfolder](https://github.com/camaraproject/Commonalities/tree/API-linting-Implementation-Guideline/artifacts/linting_rules). + +The target method is linting rules integration with CAMARA API subproject repositories using GitHub Actions. + + +## Spectral Configuration + +The Spectral configuration consists of .spectral.yml file, which contains all the rules defined for CAMARA OpenAPI specification as described in [Linting-rules.md](Linting-rules.md) + +This file consolidates all rules: + +1. Spectral Core OpenAPI specification linting ruleset: + + `Ruleset extension: extends: "spectral:oas"` + +2. Spectral rules with built-in functions +3. Spectral rules with custom JavaScript functions + + +## GitHub Actions Integration + +1. Add **[.spectral.yml](https://github.com/camaraproject/Commonalities/blob/main/artifacts/linting_rules/.spectral.yml)** (rules) file to -> root location of repository + +2. Create **lint-function** folder + + Make a folder named `lint_function` at root location and add custom [JavaScript function files](https://github.com/camaraproject/Commonalities/tree/API-linting-Implementation-Guideline/artifacts/linting_rules/lint_function) that are imported in .spectral.yml (some rules require custom JavaScript functions to execute). + +3. Add **[spectral_oas_lint.yml](https://github.com/camaraproject/Commonalities/blob/main/artifacts/linting_rules/.github/workflows/spectral_oas_lint.yml)** to GitHub action workflows in `.github/workflows` folder + which includes the configuration of Spectral workflow for GitHub actions. + +4. Add [megalinter.yml](https://github.com/camaraproject/Commonalities/blob/main/artifacts/linting_rules/.github/workflows/megalinter.yml) to GitHub action workflows in `.github/workflows` folder + which includes the configuration of Megalinter and Spectral for GitHub actions. + +### Manually running linting workflow + +**spectral_oas_lint.yml** includes configuration of the OAS linting workflow to be run manually as described in [GitHub Actions documentation](https://docs.github.com/en/actions/using-workflows/manually-running-a-workflow). + +The rules will be applied to all files with *.yaml extension in '/code/API_definitions/' folder of the repository. +Write access to the repository is required to perform these steps. + +The output from Spectral can be seen by expanding the step **Run Spectral Linting** of given worflow run Actions section of GitHub repository. + + +### Megalinter integration + +[Megalinter](https://megalinter.io/latest/) is an Open-Source tool for CI/CD workflows that analyzes the consistency of code, configurations and scripts in repository sources. Megalinter supports Spectral linting. +The Megalinter job will be automatically activated once you submit a pull request on the [main/master] branch of the CAMARA repository, as configured in megalinter.yml. + +The Megalinter configuration consists of the megalinter.yml file containing the necessary settings to run Megalinter and Spectral jobs on GitHub actions. + +Additionally, Megalinter also supports linting of YAML files. To enable this, users need to add the following ruleset files to the root location. + +- YAML Linting: .yamllint.yaml + + + + +## API Linting configuration steps for local deployment + +1. Install Spectral locally: + + npm install -g @stoplight/spectral + +2. Install Spectral functions locally: + + npm install --save @stoplight/spectral-functions + +3. Save files locally: + + Save ".spectral.yml" file (contains Linting rules) and lint_function folder (contains JavaScript customized functions) at the root location. + +4. Apply spectral rules on API specification loacally: + + spectral lint openapi.yaml --verbose --ruleset .spectral.yml + + *Replace **'openapi.yaml'** with the path to your OpenAPI specification file.*