Skip to content

Commit

Permalink
Merge pull request #159 from fosrl/dev
Browse files Browse the repository at this point in the history
1.0.0-beta12
  • Loading branch information
miloschwartz authored Feb 5, 2025
2 parents 722b877 + c51f1cb commit 9e5d5e8
Show file tree
Hide file tree
Showing 38 changed files with 1,364 additions and 306 deletions.
9 changes: 2 additions & 7 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,8 @@ jobs:
- name: Update version in package.json
run: |
TAG=${{ env.TAG }}
if [ -f package.json ]; then
jq --arg version "$TAG" '.version = $version' package.json > package.tmp.json && mv package.tmp.json package.json
echo "Updated package.json with version $TAG"
else
echo "package.json not found"
fi
cat package.json
sed -i "s/export const APP_VERSION = \".*\";/export const APP_VERSION = \"$TAG\";/" server/lib/consts.ts
cat server/lib/
- name: Pull latest Gerbil version
id: get-gerbil-tag
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
[![Discord](https://img.shields.io/discord/1325658630518865980?logo=discord&style=flat-square)](https://discord.gg/HCJR8Xhme4)
[![Youtube](https://img.shields.io/badge/YouTube-red?logo=youtube&logoColor=white&style=flat-square)](https://www.youtube.com/@fossorial-app)

Pangolin is a self-hosted tunneled reverse proxy management server with identity and access management, designed to securely expose private resources through use with the Traefik reverse proxy and WireGuard tunnel clients like Newt. With Pangolin, you retain full control over your infrastructure while providing a user-friendly and feature-rich solution for managing proxies, authentication, and access, and simplifying complex network setups, all with a clean and simple UI.
Pangolin is a self-hosted tunneled reverse proxy management server with identity and access control, designed to securely expose private resources through use with the Traefik reverse proxy and WireGuard tunnel clients like Newt. With Pangolin, you retain full control over your infrastructure while providing a user-friendly and feature-rich solution for managing proxies, authentication, and access, and simplifying complex network setups, all with a clean and simple UI.

### Installation and Documentation

Expand Down Expand Up @@ -129,6 +129,10 @@ Pangolin was inspired by several existing projects and concepts:
- **Authentik and Authelia**:
These projects inspired Pangolin’s centralized authentication system for proxies, enabling robust user and role management.

## Project Development / Roadmap

Pangolin is under active development, and we are continuously adding new features and improvements. View the [project board](https://github.com/orgs/fosrl/projects/1) for more detailed info.

## Licensing

Pangolin is dual licensed under the AGPLv3 and the Fossorial Commercial license. For inquiries about commercial licensing, please contact us.
Expand Down
7 changes: 6 additions & 1 deletion docker-compose.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ services:
- 80:80 # Port for traefik because of the network_mode

traefik:
image: traefik:v3.1
image: traefik:v3.3.3
container_name: traefik
restart: unless-stopped
network_mode: service:gerbil # Ports appear on the gerbil service
Expand All @@ -49,3 +49,8 @@ services:
volumes:
- ./traefik:/etc/traefik:ro # Volume to store the Traefik configuration
- ./letsencrypt:/letsencrypt # Volume to store the Let's Encrypt certificates

networks:
default:
driver: bridge
name: pangolin
7 changes: 6 additions & 1 deletion install/fs/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ services:
{{end}}

traefik:
image: traefik:v3.1
image: traefik:v3.3.3
container_name: traefik
restart: unless-stopped
{{if .InstallGerbil}}
Expand All @@ -55,3 +55,8 @@ services:
volumes:
- ./config/traefik:/etc/traefik:ro # Volume to store the Traefik configuration
- ./config/letsencrypt:/letsencrypt # Volume to store the Let's Encrypt certificates

networks:
default:
driver: bridge
name: pangolin
267 changes: 267 additions & 0 deletions internationalization/de.md

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions internationalization/pl.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
## Authentication Site


| EN | PL | Notes |
| -------------------------------------------------------- | ------------------------------------------------------------ | ---------- |
| Powered by [Pangolin](https://github.com/fosrl/pangolin) | Zasilane przez [Pangolin](https://github.com/fosrl/pangolin) | |
| Authentication Required | Wymagane uwierzytelnienie | |
| Choose your preferred method to access {resource} | Wybierz preferowaną metodę dostępu do {resource} | |
| PIN | PIN | |
| User | Zaloguj | |
| 6-digit PIN Code | 6-cyfrowy kod PIN | pin login |
| Login in with PIN | Zaloguj się PIN’em | pin login |
| Email | Email | user login |
| Enter your email | Wprowadź swój email | user login |
| Password | Hasło | user login |
| Enter your password | Wprowadź swoje hasło | user login |
| Forgot your password? | Zapomniałeś hasła? | user login |
| Log in | Zaloguj | user login |


## Login site

| EN | PL | Notes |
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@fosrl/pangolin",
"version": "1.0.0-beta.10",
"version": "0.0.0",
"private": true,
"type": "module",
"description": "Tunneled Reverse Proxy Management Server with Identity and Access Control and Dashboard UI",
Expand Down
3 changes: 2 additions & 1 deletion server/db/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ export const resources = sqliteTable("resources", {
proxyPort: integer("proxyPort"),
emailWhitelistEnabled: integer("emailWhitelistEnabled", { mode: "boolean" })
.notNull()
.default(false)
.default(false),
isBaseDomain: integer("isBaseDomain", { mode: "boolean" })
});

export const targets = sqliteTable("targets", {
Expand Down
22 changes: 12 additions & 10 deletions server/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import { fromError } from "zod-validation-error";
import {
__DIRNAME,
APP_PATH,
APP_VERSION,
configFilePath1,
configFilePath2
} from "@server/lib/consts";
import { loadAppVersion } from "@server/lib/loadAppVersion";
import { passwordSchema } from "@server/auth/passwordSchema";
import stoi from "./stoi";

Expand Down Expand Up @@ -151,7 +151,8 @@ const configSchema = z.object({
require_email_verification: z.boolean().optional(),
disable_signup_without_invite: z.boolean().optional(),
disable_user_create_org: z.boolean().optional(),
allow_raw_resources: z.boolean().optional()
allow_raw_resources: z.boolean().optional(),
allow_base_domain_resources: z.boolean().optional()
})
.optional()
});
Expand Down Expand Up @@ -239,11 +240,7 @@ export class Config {
throw new Error(`Invalid configuration file: ${errors}`);
}

const appVersion = loadAppVersion();
if (!appVersion) {
throw new Error("Could not load the application version");
}
process.env.APP_VERSION = appVersion;
process.env.APP_VERSION = APP_VERSION;

process.env.NEXT_PORT = parsedConfig.data.server.next_port.toString();
process.env.SERVER_EXTERNAL_PORT =
Expand All @@ -255,9 +252,9 @@ export class Config {
? "true"
: "false";
process.env.FLAGS_ALLOW_RAW_RESOURCES = parsedConfig.data.flags
?.allow_raw_resources
? "true"
: "false";
?.allow_raw_resources
? "true"
: "false";
process.env.SESSION_COOKIE_NAME =
parsedConfig.data.server.session_cookie_name;
process.env.EMAIL_ENABLED = parsedConfig.data.email ? "true" : "false";
Expand All @@ -273,6 +270,11 @@ export class Config {
parsedConfig.data.server.resource_access_token_param;
process.env.RESOURCE_SESSION_REQUEST_PARAM =
parsedConfig.data.server.resource_session_request_param;
process.env.FLAGS_ALLOW_BASE_DOMAIN_RESOURCES = parsedConfig.data.flags
?.allow_base_domain_resources
? "true"
: "false";
process.env.DASHBOARD_URL = parsedConfig.data.app.dashboard_url;

this.rawConfig = parsedConfig.data;
}
Expand Down
4 changes: 3 additions & 1 deletion server/lib/consts.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import path from "path";
import { fileURLToPath } from "url";
import { existsSync } from "fs";

// This is a placeholder value replaced by the build process
export const APP_VERSION = "1.0.0-beta.12";

export const __FILENAME = fileURLToPath(import.meta.url);
export const __DIRNAME = path.dirname(__FILENAME);
Expand Down
16 changes: 0 additions & 16 deletions server/lib/loadAppVersion.ts

This file was deleted.

7 changes: 7 additions & 0 deletions server/routers/external.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,13 @@ authenticated.get(
resource.getResourceWhitelist
);

authenticated.post(
`/resource/:resourceId/transfer`,
verifyResourceAccess,
verifyUserHasAction(ActionsEnum.updateResource),
resource.transferResource
);

authenticated.post(
`/resource/:resourceId/access-token`,
verifyResourceAccess,
Expand Down
16 changes: 2 additions & 14 deletions server/routers/gerbil/getConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import config from "@server/lib/config";
import { getUniqueExitNodeEndpointName } from '@server/db/names';
import { findNextAvailableCidr } from "@server/lib/ip";
import { fromError } from 'zod-validation-error';
import { getAllowedIps } from '../target/helpers';
// Define Zod schema for request validation
const getConfigSchema = z.object({
publicKey: z.string(),
Expand Down Expand Up @@ -83,22 +84,9 @@ export async function getConfig(req: Request, res: Response, next: NextFunction)
});

const peers = await Promise.all(sitesRes.map(async (site) => {
// Fetch resources for this site
const resourcesRes = await db.query.resources.findMany({
where: eq(resources.siteId, site.siteId),
});

// Fetch targets for all resources of this site
const targetIps = await Promise.all(resourcesRes.map(async (resource) => {
const targetsRes = await db.query.targets.findMany({
where: eq(targets.resourceId, resource.resourceId),
});
return targetsRes.map(target => `${target.ip}/32`);
}));

return {
publicKey: site.pubKey,
allowedIps: targetIps.flat(),
allowedIps: await getAllowedIps(site.siteId)
};
}));

Expand Down
8 changes: 4 additions & 4 deletions server/routers/newt/targets.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Target } from "@server/db/schema";
import { sendToClient } from "../ws";

export async function addTargets(
export function addTargets(
newtId: string,
targets: Target[],
protocol: string
): Promise<void> {
) {
//create a list of udp and tcp targets
const payloadTargets = targets.map((target) => {
return `${target.internalPort ? target.internalPort + ":" : ""}${
Expand All @@ -22,11 +22,11 @@ export async function addTargets(
sendToClient(newtId, payload);
}

export async function removeTargets(
export function removeTargets(
newtId: string,
targets: Target[],
protocol: string
): Promise<void> {
) {
//create a list of udp and tcp targets
const payloadTargets = targets.map((target) => {
return `${target.internalPort ? target.internalPort + ":" : ""}${
Expand Down
65 changes: 51 additions & 14 deletions server/routers/resource/createResource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import stoi from "@server/lib/stoi";
import { fromError } from "zod-validation-error";
import logger from "@server/logger";
import { subdomainSchema } from "@server/schemas/subdomainSchema";
import config from "@server/lib/config";

const createResourceParamsSchema = z
.object({
Expand All @@ -33,7 +34,8 @@ const createResourceSchema = z
siteId: z.number(),
http: z.boolean(),
protocol: z.string(),
proxyPort: z.number().optional()
proxyPort: z.number().optional(),
isBaseDomain: z.boolean().optional()
})
.refine(
(data) => {
Expand All @@ -54,7 +56,7 @@ const createResourceSchema = z
)
.refine(
(data) => {
if (data.http) {
if (data.http && !data.isBaseDomain) {
return subdomainSchema.safeParse(data.subdomain).success;
}
return true;
Expand All @@ -63,6 +65,43 @@ const createResourceSchema = z
message: "Invalid subdomain",
path: ["subdomain"]
}
)
.refine(
(data) => {
if (!config.getRawConfig().flags?.allow_raw_resources) {
if (data.proxyPort !== undefined) {
return false;
}
}
return true;
},
{
message: "Proxy port cannot be set"
}
)
// .refine(
// (data) => {
// if (data.proxyPort === 443 || data.proxyPort === 80) {
// return false;
// }
// return true;
// },
// {
// message: "Port 80 and 443 are reserved for http and https resources"
// }
// )
.refine(
(data) => {
if (!config.getRawConfig().flags?.allow_base_domain_resources) {
if (data.isBaseDomain) {
return false;
}
}
return true;
},
{
message: "Base domain resources are not allowed"
}
);

export type CreateResourceResponse = Resource;
Expand All @@ -83,7 +122,7 @@ export async function createResource(
);
}

let { name, subdomain, protocol, proxyPort, http } = parsedBody.data;
let { name, subdomain, protocol, proxyPort, http, isBaseDomain } = parsedBody.data;

// Validate request params
const parsedParams = createResourceParamsSchema.safeParse(req.params);
Expand Down Expand Up @@ -120,7 +159,13 @@ export async function createResource(
);
}

const fullDomain = `${subdomain}.${org[0].domain}`;
let fullDomain = "";
if (isBaseDomain) {
fullDomain = org[0].domain;
} else {
fullDomain = `${subdomain}.${org[0].domain}`;
}

// if http is false check to see if there is already a resource with the same port and protocol
if (!http) {
const existingResource = await db
Expand All @@ -142,15 +187,6 @@ export async function createResource(
);
}
} else {
if (proxyPort === 443 || proxyPort === 80) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"Port 80 and 443 are reserved for https resources"
)
);
}

// make sure the full domain is unique
const existingResource = await db
.select()
Expand Down Expand Up @@ -179,7 +215,8 @@ export async function createResource(
http,
protocol,
proxyPort,
ssl: true
ssl: true,
isBaseDomain
})
.returning();

Expand Down
Loading

0 comments on commit 9e5d5e8

Please sign in to comment.