Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented ability to deploy to GCP Cloud run #7

Merged
merged 5 commits into from
Dec 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,25 @@ jobs:

- name: Run tests
run: npm test

- name: Set up Google Cloud SDK
uses: google-github-actions/setup-gcloud@v1
with:
project_id: ${{ secrets.GCP_PROJECT_ID }} # TODO: Set in GitHub Secrets.GCP_PROJECT_ID
service_account_key: ${{ secrets.GCP_SA_KEY }} # TODO: Set in GitHub Secrets.GCP_SA_KEY

- name: Build Docker image
run: |
docker build -t gcr.io/${{ secrets.GCP_PROJECT_ID }}/webserver:$GITHUB_SHA .

- name: Push Docker image to GCR
run: |
docker push gcr.io/${{ secrets.GCP_PROJECT_ID }}/webserver:$GITHUB_SHA

- name: Deploy to Cloud Run
run: |
gcloud run deploy webserver \
--image gcr.io/${{ secrets.GCP_PROJECT_ID }}/webserver:$GITHUB_SHA \
--platform managed \
--region us-central1 \
--allow-unauthenticated
5 changes: 2 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,9 @@ WORKDIR /app
COPY . .

WORKDIR /app/demo/webserver
ENV CHOKIDAR_USEPOLLING=true
EXPOSE 3000
# CMD ["npx", "ts-node-dev", "src/index.ts"]
CMD ["npm", "run", "dev"]
RUN npm run build
CMD ["npm", "run", "start"]

# docker build -t webserver .
# docker run -p 3000:3000 webserver
76 changes: 69 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,78 @@
# Domain Analysis Tool

A TypeScript library and demo web application for analyzing domains for potential security vulnerabilities, specifically focused on dangling domain takeover risks.

## Features

- Domain vulnerability analysis
- FQDN (Fully Qualified Domain Name) checking
- Heuristic analysis for potential security issues
- Web interface for scanning domains
- User management system with domain tracking
- Rate limiting for API endpoints

## Installation

```shell
$ npm install
```

## Run all tests

```shell
$ npm test # Run all tests
$ npx mocha test/parser.test.ts # Run specific test file
```

## Run Webserver Demo with Docker:

```shell
$ docker compose up --build # Start server
$ docker compose down # Stop server
```

The web interface will be available at http://localhost:3000

## Run Webserver Demo manually:

```shell
$ cd demo/webserver/
$ npm install
$ npm run dev
```

## Format code:

```shell
$ npm install prettier --global # Prerequisite

$ npx prettier --check "**/*.{js,ts}" # check format
$ npx prettier --write . # fix format for all files
$ npx prettier --write src/**/*.js # fix format for one file
$ npx prettier --check "**/*.{js,ts}" # Check code formatting
$ npx prettier --write . # Fix formatting for all files
$ npx prettier --write src/**/*.js # Fix formatting for specific file
```

## Run webserver demo:
## Project Structure

```shell
$ docker compose up --build # start server
$ docker compose down # stop server
```
src/ # Core library code
├── parsertld.ts # Domain parsing utilities
├── heuristic_analysis.ts # Domain vulnerability detection
├── fqdm_analysis.ts # FQDN analysis
└── fingerprints.json # Vulnerability patterns database

demo/ # Demo webserver application
└── webserver/ # Express.js web server
└── src/
├── index.ts # Main server code
├── database.ts # SQLite database handler
└── public/ # Static web assets

test/ # Test files
├── parser.test.ts # Parser unit tests
├── heuristic_analysis.test.ts # Analysis tests
└── fqdm_analysis.test.ts # FQDN tests
```

## License

This project is licensed under the MIT License - see the LICENSE file for details.
4 changes: 2 additions & 2 deletions demo/webserver/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"main": "index.js",
"scripts": {
"dev": "nodemon --exec ts-node src/index.ts",
"build": "tsc",
"start": "node dist/index.js"
"build": "tsc ; cp -r src/public dist/demo/webserver/src/",
"start": "node dist/demo/webserver/src/index.js"
},
"keywords": [],
"author": "",
Expand Down
4 changes: 1 addition & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ services:
volumes:
# - .:/app
- ./demo/webserver/database.db:/app/demo/webserver/database.db
environment:
- CHOKIDAR_USEPOLLING=true
command: npm run dev
command: npm run start

volumes:
db-data:
62 changes: 62 additions & 0 deletions src/domain_scanner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import dns from 'dns';
import { promisify } from 'util';

export interface DnsResolver {
resolve(domain: string): Promise<string[]>;
}

export class RealDnsResolver implements DnsResolver {
private dnsResolve = promisify(dns.resolve);

async resolve(domain: string): Promise<string[]> {
try {
await this.dnsResolve(domain);
return [domain];
} catch {
return [];
}
}
}

export async function scanSubdomains(domain: string, resolver: DnsResolver = new RealDnsResolver()): Promise<string[]> {
if (!domain || typeof domain !== 'string') {
throw new Error('Invalid domain input');
}

const commonSubdomains = [
'www',
'mail',
'drive',
'calendar',
'docs',
'cloud',
'api',
'dev',
'staging',
'test',
'admin',
'blog',
'shop',
'store',
'support',
];

const scanPromises = commonSubdomains.map(async (prefix) => {
const subdomain = `${prefix}.${domain}`;
const results = await resolver.resolve(subdomain);
return results.length > 0 ? subdomain : null;
});

const scannedResults = await Promise.all(scanPromises);
return scannedResults.filter((subdomain): subdomain is string => subdomain !== null);
}

// Usage example:
// async function main() {
// try {
// const subdomains = await scanSubdomains('google.com');
// console.log('Found subdomains:', subdomains);
// } catch (error) {
// console.error('Error:', error);
// }
// }
5 changes: 2 additions & 3 deletions src/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ export async function getDomainDetails(domain: string) {
let FQDN_Analysis = await fqdn.checkFQDM(domain).then((token) => { return token })
let status = getStatus(FQDN_Analysis)

let result = ha.matchDomain(domain) == -1? "Inconclusive":

let result = ha.matchDomain(domain) == -1? "Inconclusive":
{
isVulnerable: ha.matchDomain(domain) == 1? "Yes": "No",
isServer: ha.pingServer? "Yes":"No",
Expand All @@ -32,4 +32,3 @@ export async function getDomainDetails(domain: string) {
// else if (isDangling.dangling) return "Dangling"
else return "Not vulnerable to dangling"
}

49 changes: 49 additions & 0 deletions test/domain_scanner.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { expect } from 'chai';
import { DnsResolver, scanSubdomains } from '../src/domain_scanner';

class MockDnsResolver implements DnsResolver {
private responses: Map<string, string[]> = new Map();

setResponse(domain: string, response: string[]) {
this.responses.set(domain, response);
}

async resolve(domain: string): Promise<string[]> {
return this.responses.get(domain) || [];
}
}

describe('scanSubdomains', () => {
let mockResolver: MockDnsResolver;

beforeEach(() => {
mockResolver = new MockDnsResolver();
});

it('should return array of existing subdomains', async () => {
mockResolver.setResponse('www.example.com', ['1.2.3.4']);
mockResolver.setResponse('mail.example.com', ['1.2.3.5']);

const result = await scanSubdomains('example.com', mockResolver);
expect(result.sort()).to.deep.equal(['www.example.com', 'mail.example.com'].sort());
});

it('should throw error for invalid domain input', async () => {
try {
await scanSubdomains('', mockResolver);
expect.fail('Should have thrown error');
} catch (error) {
expect(error.message).to.equal('Invalid domain input');
}
});

it('should return empty array when no subdomains exist', async () => {
const result = await scanSubdomains('nonexistent.com', mockResolver);
expect(result).to.deep.equal([]);
});

it('should handle DNS resolution errors gracefully', async () => {
const result = await scanSubdomains('example.com', mockResolver);
expect(result).to.deep.equal([]);
});
});
Loading