From 7d9c87b5253cc94b35d2548b72735145e81a8a17 Mon Sep 17 00:00:00 2001 From: Plamen Nedkov Date: Tue, 19 Dec 2023 22:52:00 -0600 Subject: [PATCH] Release first version of IP Get --- .dockerignore | 8 ++++ Dockerfile | 18 ++++++++ docker-compose.yaml | 25 ++++++++++ ipget/ipget.py | 95 ++++++++++++++++++++++++++++++++++++++ ipget/ipget.sh | 3 ++ ipget/static/style.css | 79 +++++++++++++++++++++++++++++++ ipget/static/style.css.bak | 79 +++++++++++++++++++++++++++++++ ipget/templates/index.html | 52 +++++++++++++++++++++ ipget/version.py | 1 + requirements.txt | 3 ++ resources/generate_cert.sh | 25 ++++++++++ resources/nginx.conf | 46 ++++++++++++++++++ 12 files changed, 434 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 docker-compose.yaml create mode 100644 ipget/ipget.py create mode 100755 ipget/ipget.sh create mode 100644 ipget/static/style.css create mode 100644 ipget/static/style.css.bak create mode 100644 ipget/templates/index.html create mode 100644 ipget/version.py create mode 100644 requirements.txt create mode 100755 resources/generate_cert.sh create mode 100644 resources/nginx.conf diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..7768fb4 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +.dockerignore +.git +.gitignore +__pycache__ +*.pyc +*.pyo +*.pyd +*plamen* diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..328b7cc --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM python:3.11-slim + +WORKDIR /ipget + +COPY ipget/ /ipget +COPY requirements.txt /ipget + +RUN pip install --no-cache-dir -r requirements.txt + +EXPOSE 5000 + +ENV WORKERS 2 +ENV IP_API false +ENV FLASK_ENV production + +RUN chmod +x ipget.sh + +ENTRYPOINT ["./ipget.sh"] diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..5c2de7c --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,25 @@ +version: '3.8' + +services: + ipget: + image: prestigen/ipget:0.0.1 + hostname: ipget + environment: + - WORKERS=4 + - IP_API=false + - FLASK_ENV=production + restart: always + + nginx: + image: nginx:latest + hostname: ipget-nginx + ports: + - "80:80" + - "443:443" + volumes: + - ./resources/nginx.conf:/etc/nginx/nginx.conf:ro + - $HOME/.nginx/cert.pem:/etc/nginx/cert.pem:ro + - $HOME/.nginx/key.pem:/etc/nginx/key.pem:ro + depends_on: + - ipget + restart: always diff --git a/ipget/ipget.py b/ipget/ipget.py new file mode 100644 index 0000000..561f611 --- /dev/null +++ b/ipget/ipget.py @@ -0,0 +1,95 @@ +from flask import Flask, request, render_template +from urllib.parse import urlparse +from version import __version__ as ipget_version +import os +import requests + + +ipget = Flask(__name__) +ip_api = os.getenv("IP_API", "False").lower() == "true" + + +def format_headers(headers): + return "
".join(f"{key}: {value}" for key, value in headers.items()) + + +# TODO: Store all ip-api.com json fields in a dictionary +def get_country(ip): + ip_api_location = None + try: + if ip_api: + response = requests.get(f"http://ip-api.com/json/{ip}") + json_response = response.json() + country = json_response.get("country") + region_name = json_response.get("regionName") + city = json_response.get("city") + ip_api_location = ( + f"{country}, {region_name}, {city}" if country is not None else None + ) + + return ip_api_location + except requests.RequestException: + return "N/A" + + +@ipget.route("/") +def home(): + client_ip = request.headers.get("X-Real-IP", request.remote_addr) + user_agent = request.headers.get("User-Agent", "").lower() + + if "mozilla" in user_agent or "chrome" in user_agent or "safari" in user_agent: + client_info = { + "ip": client_ip, + "remote_hostname": urlparse("//" + request.headers.get("Host")).hostname, + "x_forwarded_for": request.headers.get("X-Forwarded-For"), + "country": get_country(client_ip), + "user_agent": request.user_agent.string, + "headers": format_headers(request.headers), + } + return render_template( + "index.html", + client_info=client_info, + ip_api=ip_api, + ipget_version=ipget_version + ) + else: + return f"{client_ip}\n" + + +@ipget.route("/ip") +def return_ip(): + return f"{request.headers.get('X-Real-IP', request.remote_addr)}\n" + + +@ipget.route("/host") +def return_host(): + return f"{request.headers.get('Host')}\n" + + +@ipget.route("/xff") +def return_xff(): + return f"{request.headers.get('X-Forwarded-For')}\n" + + +@ipget.route("/country") +def return_country(): + return f"{get_country(request.headers.get('X-Real-IP', request.remote_addr))}\n" + + +@ipget.route("/ua") +def return_ua(): + return f"{request.user_agent.string}\n" + + +@ipget.route("/headers") +def return_headers(): + return dict(request.headers) + + +@ipget.errorhandler(404) +def page_not_found(e): + return "Error 404: Resource does not exist.\n", 404 + + +if __name__ == "__main__": + ipget.run() diff --git a/ipget/ipget.sh b/ipget/ipget.sh new file mode 100755 index 0000000..8568904 --- /dev/null +++ b/ipget/ipget.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +exec gunicorn -w ${WORKERS:-2} -b 0.0.0.0:5000 ipget:ipget diff --git a/ipget/static/style.css b/ipget/static/style.css new file mode 100644 index 0000000..fa81d45 --- /dev/null +++ b/ipget/static/style.css @@ -0,0 +1,79 @@ +body { + font-family: 'Roboto Mono', monospace; + background-color: #1e1e1e; /* Dark background */ + color: #ccffcc; /* Very light pastel green text */ + margin: 0; + padding: 20px; + display: flex; + justify-content: center; +} + +.container { + background-color: #252526; /* Slightly lighter dark background */ + padding: 20px; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + width: 80%; /* Adjust as needed */ +} + +h1 { + color: #98fb98; /* Pastel green color for headers */ +} + +table { + width: 100%; +/* table-layout: fixed; */ + margin-left: auto; + margin-right: auto; + border-collapse: collapse; +/* border: 1px solid #3c3c3c; /* Dark border for the table */ + border: 2px solid #4c4c4c; /* Thicker and darker border for the table */ +} + +th, td { + flex: 1; + text-align: left; + padding: 8px; + color: #ccffcc; /* Very light pastel green text for table data */ + border: 2px solid #4c4c4c; /* Thicker and darker border for cells */ + overflow: hidden; + text-overflow: ellipsis; +} + +tr:nth-child(even) { + background-color: #2d2d2d; /* Darker row background */ +} + +tr:nth-child(odd) { + background-color: #1e1e1e; /* Dark row background */ +} + +th { + background-color: #98fb98; /* Pastel green background for table headers */ + color: #1e1e1e; /* Dark text for better contrast */ + text-align: left; +} + +th:nth-child(1), td:nth-child(1), +th:nth-child(2), td:nth-child(2) { + overflow: hidden; + white-space: nowrap; +} + +th:nth-child(3), td:nth-child(3) { + word-wrap: break-word; + word-break: break-all; + overflow-wrap: break-word; +} + + +footer { + text-align: center; /* Center align text */ + padding: 5px 0; /* Some padding */ + margin-top: 20px; /* Space from content above */ + background-color: #1e1e1e; /* Background color */ + position: fixed; /* Fix to bottom */ + left: 0; + bottom: 0; + width: 100%; +} diff --git a/ipget/static/style.css.bak b/ipget/static/style.css.bak new file mode 100644 index 0000000..3d92443 --- /dev/null +++ b/ipget/static/style.css.bak @@ -0,0 +1,79 @@ +body { + font-family: 'Roboto Mono', monospace; + background-color: #1e1e1e; /* Dark background */ + color: #ccffcc; /* Very light pastel green text */ + margin: 0; + padding: 20px; + display: flex; + justify-content: center; +} + +.container { + background-color: #252526; /* Slightly lighter dark background */ + padding: 20px; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + width: 80%; /* Adjust as needed */ +} + +h1 { + color: #98fb98; /* Pastel green color for headers */ +} + +table { + width: 80%; + max-width: 80%; + table-layout: auto; + margin-left: auto; + margin-right: auto; + border-collapse: collapse; +/* border: 1px solid #3c3c3c; /* Dark border for the table */ + border: 2px solid #4c4c4c; /* Thicker and darker border for the table */ +} + +th, td { + text-align: left; + padding: 8px; + color: #ccffcc; /* Very light pastel green text for table data */ + overflow: hidden; /* Prevent overflow */ + border: 2px solid #4c4c4c; /* Thicker and darker border for cells */ + white-space: normal; +} + +tr:nth-child(even) { + background-color: #2d2d2d; /* Darker row background */ +} + +tr:nth-child(odd) { + background-color: #1e1e1e; /* Dark row background */ +} + +th { + background-color: #98fb98; /* Pastel green background for table headers */ + color: #1e1e1e; /* Dark text for better contrast */ + text-align: left; +} + +th:nth-child(1), td:nth-child(1), +th:nth-child(2), td:nth-child(2) { + width: 20%; /* Reduce the width; adjust this value as needed */ + max-width: 300px; /* Optional: you can set a maximum width */ + overflow: hidden; + white-space: nowrap; +} + +th:nth-child(3), td:nth-child(3) { + width: 60%; /* Allows the third column to resize */ + white-space: normal; +} + +footer { + text-align: center; /* Center align text */ + padding: 5px 0; /* Some padding */ + margin-top: 20px; /* Space from content above */ + background-color: #1e1e1e; /* Background color */ + position: fixed; /* Fix to bottom */ + left: 0; + bottom: 0; + width: 100%; +} diff --git a/ipget/templates/index.html b/ipget/templates/index.html new file mode 100644 index 0000000..4dd2ff5 --- /dev/null +++ b/ipget/templates/index.html @@ -0,0 +1,52 @@ + + + + + IP Get + + + + +
+ + + + + + + + + + + + + + + {% if ip_api %} + + + + + + {% endif %} + + + + + + + + + + + +
[root@{{ client_info.remote_hostname }}]: ~># ipget
curl {{ client_info.remote_hostname }}
curl {{ client_info.remote_hostname }}/ip
IP Address{{ client_info.ip }}
curl {{ client_info.remote_hostname }}/xffX-Forwarded-For{{ client_info.x_forwarded_for }}
Location{{ client_info.country }}
curl {{ client_info.remote_hostname }}/xffUser Agent{{ client_info.user_agent }}
curl {{ client_info.remote_hostname }}/headersHeaders{{ client_info.headers|safe }}
+
+

© IP Get v{{ ipget_version }}

+
+ +
+ + diff --git a/ipget/version.py b/ipget/version.py new file mode 100644 index 0000000..f102a9c --- /dev/null +++ b/ipget/version.py @@ -0,0 +1 @@ +__version__ = "0.0.1" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..40fb41b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +Flask +requests +gunicorn diff --git a/resources/generate_cert.sh b/resources/generate_cert.sh new file mode 100755 index 0000000..b510045 --- /dev/null +++ b/resources/generate_cert.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# Directory where the certificate and key will be stored +CERT_DIR="$HOME/.nginx" + +# Certificate and key configuration +ALG_NAME="rsa" +ALG_SIZE="4096" +DAYS="3650" +CN="ip.aumaton.com" + + +# Check if the directory exists, create if not +if [ ! -d "$CERT_DIR" ]; then + mkdir -p "$CERT_DIR" +fi + +# Set the filenames +CERT_FILE="$CERT_DIR/cert.pem" +KEY_FILE="$CERT_DIR/key.pem" + +# Generate the key and certificate +openssl req -x509 -newkey $ALG_NAME:$ALG_SIZE -keyout "$KEY_FILE" -out "$CERT_FILE" -days $DAYS -nodes -subj "/CN=$CN" + +echo "Certificate and key have been generated in $CERT_DIR" diff --git a/resources/nginx.conf b/resources/nginx.conf new file mode 100644 index 0000000..f550028 --- /dev/null +++ b/resources/nginx.conf @@ -0,0 +1,46 @@ +events {} + +http { + server { + listen 80; + + return 301 https://$host$request_uri; + } + + server { + listen 443 ssl; + + ssl_certificate /etc/nginx/cert.pem; + ssl_certificate_key /etc/nginx/key.pem; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305; + ssl_prefer_server_ciphers off; + + location / { + limit_except GET { deny all; } + + proxy_pass http://ipget:5000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + } + + # DOS + client_body_buffer_size 1K; + client_header_buffer_size 1k; + client_max_body_size 1k; + large_client_header_buffers 2 1k; + + # Security headers + add_header X-Frame-Options "DENY"; + add_header X-XSS-Protection "1; mode=block" always; + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "no-referrer-when-downgrade" always; + add_header Content-Security-Policy "default-src 'self' http: https: ws: wss: data: blob: 'unsafe-inline'; frame-ancestors 'self';" always; + add_header Permissions-Policy "interest-cohort=()" always; + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; +}