Skip to content

Commit

Permalink
Added support for certificate providers. NONE (default) and SELF_SIGN…
Browse files Browse the repository at this point in the history
…ED have been implemented.
  • Loading branch information
jgillam committed Aug 16, 2023
1 parent 0286c56 commit d4c8f93
Show file tree
Hide file tree
Showing 7 changed files with 283 additions and 25 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -240,5 +240,6 @@ cython_debug/
#.idea/

tmp/
certs/

.idea/**/*
1 change: 1 addition & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pyyaml = "*"
docker = "*"
jinja2 = "*"
python-dotenv = "*"
cryptography = "*"

[dev-packages]

Expand Down
107 changes: 106 additions & 1 deletion Pipfile.lock

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

17 changes: 9 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,16 @@ This project is designed for creating student labs using Docker and Nginx. It pr

4. Setting up SSL with Nginx (Optional):

**Acquire an SSL Certificate (Self-Signed):**
Shogun can be configured to automatically generate certificates by using a certificate provider. The following providers are supported:

If you need one, you can generate a self-signed certificate using the following command
(useful for testing and dev purposes):
- NONE: No SSL certificates will be generated. This is the default.
- SELF_SIGNED: Self-signed certificates will be generated automatically.

To configure the certificate provider, set the `CERT_PROVIDER` environment variable to the desired provider. This can be done in the `.env` file or by setting the environment variable directly.

For example, to use self-signed certificates, set the following environment variable:
```
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/nginx/ssl/nginx.key -out /etc/nginx/ssl/nginx.crt
CERT_PROVIDER=SELF_SIGNED
```

**Modify the Nginx Configuration:**
Expand All @@ -60,15 +63,13 @@ Environment Variables:
Adjust your `.env` file to include relevant SSL related variables:

```
USE_SSL=true # or false, depending on whether you wish to use SSL
SSL_CERT_PATH=/etc/nginx/ssl/nginx.crt
SSL_KEY_PATH=/etc/nginx/ssl/nginx.key
ROOT_DOMAIN=example.com
```
Use the actual paths to your SSL certificate and key files.
Use the actual root domain for your lab environment.




## CLI Usage:

You can use the provided shogun.bat (for Windows) or shogun shell script (for Unix systems) to interact with the CLI. The available commands are:
Expand Down
4 changes: 3 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ python-dotenv==1.0.0
PyYAML==6.0
requests==2.31.0
urllib3==2.0.3
websocket-client==1.6.1
websocket-client==1.6.1
Jinja2~=3.1.2
cryptography~=41.0.3
122 changes: 122 additions & 0 deletions src/certificate_providers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import datetime
from abc import ABC, abstractmethod
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import rsa
from lab_config import config
import os

# Get the directory where compose_providers.py is located
compose_providers_dir = os.path.dirname(os.path.abspath(__file__))

# Get the parent directory of the compose directory
parent_dir = os.path.dirname(compose_providers_dir)

# Create the certs directory inside the parent directory if it doesn't exist
certs_dir = os.path.join(parent_dir, 'certs')
os.makedirs(certs_dir, exist_ok=True)


class CertificateProvider(ABC):
"""
Abstract class defining the interface for certificate providers.
"""

@abstractmethod
def generate_certificates(self, domain: str):
"""
Generates or retrieves the certificates for the given domain.
"""
pass

@abstractmethod
def get_certificate_paths(self, lab_subdomain: str) -> tuple:
"""
Returns the paths to the certificate and key files.
"""
pass


class NoneProvider(CertificateProvider):
"""
A no-op certificate provider implementation that signifies no SSL/TLS configuration.
"""

def generate_certificates(self, domain: str):
"""
No-op implementation.
"""
pass

def get_certificate_paths(self, lab_subdomain: str) -> tuple:
"""
Returns None for both certificate and key paths.
"""
return None, None


class SelfSignedProvider(CertificateProvider):
"""
Self-Signed certificate provider implementation that generates wildcard certificates for subdomains.
"""

def __init__(self):
"""
Initializes the certificate provider.
"""
self.cert_dir = certs_dir

def generate_certificates(self, lab_subdomain: str):
"""
Generates a self-signed wildcard certificate for the given lab subdomain if it doesn't exist.
"""
cert_path = os.path.join(self.cert_dir, f"{lab_subdomain}.crt")
key_path = os.path.join(self.cert_dir, f"{lab_subdomain}.key")
# Check if the certificate and key files already exist
if os.path.exists(cert_path) and os.path.exists(key_path):
return

# Generate private key
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)

# Generate self-signed certificate
subject = issuer = x509.Name([
x509.NameAttribute(x509.NameOID.COMMON_NAME, f"*.{lab_subdomain}.{config['domain']}"),
x509.NameAttribute(x509.NameOID.ORGANIZATION_NAME, "OWASP"),
x509.NameAttribute(x509.NameOID.ORGANIZATIONAL_UNIT_NAME, "SamuraiWTF")
])
cert = (x509.CertificateBuilder()
.subject_name(subject)
.issuer_name(issuer)
.public_key(private_key.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(datetime.datetime.utcnow())
.not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=365))
.sign(private_key, hashes.SHA256(), default_backend()))

# Write the private key and certificate to files
with open(key_path, "wb") as key_file:
key_file.write(private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption()
))

with open(cert_path, "wb") as cert_file:
cert_file.write(cert.public_bytes(serialization.Encoding.PEM))

def get_certificate_paths(self, lab_subdomain: str) -> tuple:
"""
Returns the paths to the wildcard certificate and key files for the given lab subdomain.
"""
# If the certificate and key files don't exist, generate them
self.generate_certificates(lab_subdomain)

cert_path = os.path.join(self.cert_dir, f"{lab_subdomain}.crt")
key_path = os.path.join(self.cert_dir, f"{lab_subdomain}.key")
return cert_path, key_path
Loading

0 comments on commit d4c8f93

Please sign in to comment.