Skip to content

Commit

Permalink
Merge pull request #973 from openkfw/967-security-improvements
Browse files Browse the repository at this point in the history
Pen Test Security fixes
  • Loading branch information
Stezido authored Sep 23, 2021
2 parents 3ded1b9 + d81d1b2 commit bdb6dbd
Show file tree
Hide file tree
Showing 74 changed files with 1,926 additions and 499 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/notification/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ runs:
-F "variables[GITHUB_HEAD_BRANCH]=$GITHUB_HEAD_BRANCH"
-F "variables[GITHUB_BASE_BRANCH]=$GITHUB_BASE_BRANCH"
-F "variables[GITHUB_RUN_ID]=$GITHUB_RUN_ID"
-F "variables[GITHUB_EVENT_NAME]=$GITHUB_EVENT_NAME"
-F "variables[GITHUB_EVENT_NAME]=$GITHUB_EVENT_NAME-$GITHUB_BASE_BRANCH"
-F "variables[GITHUB_PULL_REQUEST]=$GITHUB_PR_NUMBER"
-F "variables[GITHUB_REPOSITORY]=$GITHUB_REPOSITORY"
$WEBHOOK_URL > /dev/null
Expand Down
1 change: 1 addition & 0 deletions api/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ ROOT_SECRET=root-secret
API_PORT=8080
SIGNING_METHOD=node
#ENCRYPTION_PASSWORD=MyPassword
NODE_ENV=development
2 changes: 2 additions & 0 deletions api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
| STORAGE_SERVICE_EXTERNAL_URL | no | localhost | IP and port of own connected storage service accessible externally |
| ENCRYPTION_PASSWORD | no | - | If set, all data that is send to the MultiChain node and external storage will be symmetrically encrypted by the ENCRYPTION_PASSWORD |
| SIGNING_METHOD | no | node | Possible signing methods are: `node` and `user`. Transactions on the chain will be signed using either the address of the node or the address of the specific user publishing that transaction. |
| NODE_ENV | no | production | If set to `development` api will allow any string as password. If set to `production` passwords must satisfy safePasswordSchema, see lib/joiValidation-.ts & -.spec.ts files |
| ACCESS_CONTROL_ALLOW_ORIGIN | no | "\*" | Since the service uses CORS, the domain by which it can be called needs to be set. Setting this value to `"*"` means that it can be called from any domain. Read more about this topic [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). |

## Setup

Expand Down
88 changes: 88 additions & 0 deletions api/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion api/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "trubudget-api",
"version": "1.25.0",
"version": "1.25.0",
"private": true,
"repository": {
"type": "git",
Expand Down Expand Up @@ -72,6 +72,8 @@
"axios": "^0.21.1",
"bcryptjs": "^2.4.3",
"fastify": "^3.11.0",
"fastify-cors": "^6.0.2",
"fastify-helmet": "^5.3.2",
"fastify-jwt": "^2.3.0",
"fastify-metrics": "^6.0.3",
"fastify-swagger": "^3.3.0",
Expand Down
6 changes: 6 additions & 0 deletions api/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ interface Config {
};
encryptionPassword: string | undefined;
signingMethod: string;
nodeEnv: string | undefined;
accessControlAllowOrigin: string;
}

const requiredEnvVars = ["ORGANIZATION", "ORGANIZATION_VAULT_SECRET"];
Expand Down Expand Up @@ -97,6 +99,8 @@ export const config: Config = {
encryptionPassword:
process.env.ENCRYPTION_PASSWORD === "" ? undefined : process.env.ENCRYPTION_PASSWORD,
signingMethod: process.env.SIGNING_METHOD || "node",
nodeEnv: process.env.NODE_ENV || "production",
accessControlAllowOrigin: process.env.ACCESS_CONTROL_ALLOW_ORIGIN || "*",
};

function exitIfMissing(requiredEnvVars) {
Expand Down Expand Up @@ -153,6 +157,8 @@ const getValidConfig = (): Config => {
return config;
};

export const isProductionEnvironment = () => config.nodeEnv === "production";

declare global {
namespace NodeJS {
interface ProcessEnv extends ProcessEnvVars {}
Expand Down
3 changes: 2 additions & 1 deletion api/src/global_permission_grant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import * as Result from "./result";
import { Identity } from "./service/domain/organization/identity";
import { ServiceUser } from "./service/domain/organization/service_user";
import Joi = require("joi");
import { safeIdSchema } from "./lib/joiValidation";

interface RequestBodyV1 {
apiVersion: "1.0";
Expand All @@ -21,7 +22,7 @@ interface RequestBodyV1 {
const requestBodyV1Schema = Joi.object({
apiVersion: Joi.valid("1.0").required(),
data: Joi.object({
identity: Joi.string().required(),
identity: safeIdSchema.required(),
intent: Joi.valid(globalIntents).required(),
}).required(),
});
Expand Down
3 changes: 2 additions & 1 deletion api/src/global_permission_revoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Ctx } from "./lib/ctx";
import * as Result from "./result";
import { Identity } from "./service/domain/organization/identity";
import { ServiceUser } from "./service/domain/organization/service_user";
import { safeIdSchema } from "./lib/joiValidation";
import Joi = require("joi");

interface RequestBodyV1 {
Expand All @@ -21,7 +22,7 @@ interface RequestBodyV1 {
const requestBodyV1Schema = Joi.object({
apiVersion: Joi.valid("1.0").required(),
data: Joi.object({
identity: Joi.string().required(),
identity: safeIdSchema.required(),
intent: Joi.valid(globalIntents).required(),
}).required(),
});
Expand Down
3 changes: 2 additions & 1 deletion api/src/global_permissions_grant_all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
identitiesAuthorizedFor,
} from "./service/domain/workflow/global_permissions";
import Joi = require("joi");
import { safeIdSchema } from "./lib/joiValidation";

interface RequestBodyV1 {
apiVersion: "1.0";
Expand All @@ -25,7 +26,7 @@ interface RequestBodyV1 {
const requestBodyV1Schema = Joi.object({
apiVersion: Joi.valid("1.0").required(),
data: Joi.object({
identity: Joi.string().required(),
identity: safeIdSchema.required(),
}).required(),
});

Expand Down
7 changes: 4 additions & 3 deletions api/src/group_create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import * as Result from "./result";
import * as GroupCreate from "./service/domain/organization/group_create";
import { ServiceUser } from "./service/domain/organization/service_user";
import Joi = require("joi");
import { safeIdSchema, safeStringSchema } from "./lib/joiValidation";

interface Group {
id: string;
Expand All @@ -27,9 +28,9 @@ const requestBodyV1Schema = Joi.object({
apiVersion: Joi.valid("1.0").required(),
data: Joi.object({
group: Joi.object({
id: Joi.string().required(),
displayName: Joi.string().required(),
users: Joi.array().required().items(Joi.string()),
id: safeIdSchema.required(),
displayName: safeStringSchema.required(),
users: Joi.array().required().items(safeStringSchema),
}).required(),
}).required(),
});
Expand Down
5 changes: 3 additions & 2 deletions api/src/group_member_add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Ctx } from "./lib/ctx";
import * as Result from "./result";
import { ServiceUser } from "./service/domain/organization/service_user";
import Joi = require("joi");
import { safeIdSchema } from "./lib/joiValidation";

interface RequestBodyV1 {
apiVersion: "1.0";
Expand All @@ -20,8 +21,8 @@ interface RequestBodyV1 {
const requestBodyV1Schema = Joi.object({
apiVersion: Joi.valid("1.0").required(),
data: Joi.object({
groupId: Joi.string().required(),
userId: Joi.string().required(),
groupId: safeIdSchema.required(),
userId: safeIdSchema.required(),
}).required(),
});

Expand Down
5 changes: 3 additions & 2 deletions api/src/group_member_remove.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Ctx } from "./lib/ctx";
import * as Result from "./result";
import { ServiceUser } from "./service/domain/organization/service_user";
import Joi = require("joi");
import { safeIdSchema } from "./lib/joiValidation";

interface RequestBodyV1 {
apiVersion: "1.0";
Expand All @@ -20,8 +21,8 @@ interface RequestBodyV1 {
const requestBodyV1Schema = Joi.object({
apiVersion: Joi.valid("1.0").required(),
data: Joi.object({
groupId: Joi.string().required(),
userId: Joi.string().required(),
groupId: safeIdSchema.required(),
userId: safeIdSchema.required(),
}).required(),
});

Expand Down
13 changes: 11 additions & 2 deletions api/src/httpd/server.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import * as Ajv from "ajv";
import { fastify, FastifyInstance } from "fastify";
import * as metricsPlugin from "fastify-metrics";
import fastifyCors from "fastify-cors";
import helmet from "fastify-helmet";
import { IncomingMessage, Server, ServerResponse } from "http";
import rawBody = require("raw-body");

import logger from "../lib/logger";

Expand Down Expand Up @@ -109,15 +110,23 @@ export const createBasicApp = (
urlPrefix: string,
apiPort: number,
swaggerBasePath: string,
accessControlAllowOrigin: string,
) => {
const server: FastifyInstance<Server, IncomingMessage, ServerResponse> = fastify({
logger: false,
bodyLimit: 104857600,
});

server.setValidatorCompiler(({ schema, method, url, httpPart }) => ajv.compile(schema));

server.register(metricsPlugin, { endpoint: "/metrics" });
server.register(fastifyCors, { origin: accessControlAllowOrigin });
server.register(helmet, {
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
},
},
});

registerSwagger(server, urlPrefix, apiPort, swaggerBasePath);

Expand Down
9 changes: 8 additions & 1 deletion api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ const {
documentFeatureEnabled,
encryptionPassword,
signingMethod,
accessControlAllowOrigin,
} = getValidConfig();

/*
Expand Down Expand Up @@ -211,7 +212,13 @@ if (documentFeatureEnabled) {
}
const storageServiceClient = new StorageServiceClient(storageServiceSettings);

const server = createBasicApp(jwtSecret, URL_PREFIX, port, swaggerBasepath);
const server = createBasicApp(
jwtSecret,
URL_PREFIX,
port,
swaggerBasepath,
accessControlAllowOrigin,
);

/*
* Run the app:
Expand Down
Loading

0 comments on commit bdb6dbd

Please sign in to comment.