mirror of
https://github.com/ZeroTier/ZeroTierOne
synced 2025-08-21 22:03:52 -07:00
Add support for automatic Firewall Cloud Service Provider Rule Set management (UDP port) and flexible listening TCP port in ZeroTier TCP Proxy
This commit is contained in:
parent
969e8ff0ec
commit
e0ade99268
22 changed files with 4179 additions and 100 deletions
|
@ -2,8 +2,27 @@ CXX=$(shell which clang++ g++ c++ 2>/dev/null | head -n 1)
|
||||||
|
|
||||||
INCLUDES?=-I../ext/prometheus-cpp-lite-1.0/core/include -I../ext/prometheus-cpp-lite-1.0/simpleapi/include
|
INCLUDES?=-I../ext/prometheus-cpp-lite-1.0/core/include -I../ext/prometheus-cpp-lite-1.0/simpleapi/include
|
||||||
|
|
||||||
|
# Add libcurl dependency for provider targets
|
||||||
|
LIBS=-lcurl
|
||||||
|
|
||||||
|
# Default target - original version without cloud provider functionality
|
||||||
all:
|
all:
|
||||||
$(CXX) -O3 -fno-rtti $(INCLUDES) -std=c++11 -pthread -frtti -o tcp-proxy tcp-proxy.cpp ../node/Metrics.cpp
|
$(CXX) -O3 -fno-rtti $(INCLUDES) -std=c++11 -pthread -frtti -o tcp-proxy tcp-proxy.cpp ../node/Metrics.cpp
|
||||||
|
|
||||||
|
# Provider target - includes new functionality
|
||||||
|
provider: tcp-proxy-enhanced
|
||||||
|
|
||||||
|
tcp-proxy-enhanced:
|
||||||
|
$(CXX) -O3 -fno-rtti $(INCLUDES) -std=c++11 -pthread -frtti -DENABLE_CLOUD_PROVIDER -o tcp-proxy tcp-proxy.cpp cloud/provider/LinodeFirewallManager.cpp cloud/provider/FirewallManagerFactory.cpp ../node/Metrics.cpp $(LIBS)
|
||||||
|
|
||||||
|
# Test Provider target - includes new functionality
|
||||||
|
test_linode_firewall:
|
||||||
|
$(CXX) -O3 $(INCLUDES) -std=c++11 -pthread -DENABLE_CLOUD_PROVIDER -o test_linode_firewall cloud/provider/tests/test_linode_firewall.cpp cloud/provider/LinodeFirewallManager.cpp cloud/provider/FirewallManagerFactory.cpp $(LIBS)
|
||||||
|
|
||||||
|
# Test template target
|
||||||
|
test_cloud_firewall_template:
|
||||||
|
$(CXX) -O3 $(INCLUDES) -std=c++11 -pthread -DENABLE_CLOUD_PROVIDER -o test_cloud_firewall_template cloud/provider/tests/test_cloud_firewall_template.cpp cloud/provider/LinodeFirewallManager.cpp cloud/provider/FirewallManagerFactory.cpp $(LIBS)
|
||||||
|
|
||||||
|
# Clean all
|
||||||
clean:
|
clean:
|
||||||
rm -f *.o tcp-proxy *.dSYM
|
rm -f *.o tcp-proxy test_linode_firewall test_cloud_firewall_template *.dSYM
|
||||||
|
|
|
@ -3,6 +3,8 @@ TCP Proxy Server
|
||||||
|
|
||||||
This is the TCP proxy server we run for TCP tunneling from peers behind difficult NATs. Regular users won't have much use for this.
|
This is the TCP proxy server we run for TCP tunneling from peers behind difficult NATs. Regular users won't have much use for this.
|
||||||
|
|
||||||
|
The server now includes integration with Cloud Firewall to dynamically manage UDP ports as clients connect and disconnect. For more information, see the [Cloud Firewall Integration](cloud/README.md) documentation. Configuration examples for different cloud providers can be found in `conf/local.conf.cloud_examples`.
|
||||||
|
|
||||||
## How to run your own
|
## How to run your own
|
||||||
Currently you must build it and distribute it to your server manually.
|
Currently you must build it and distribute it to your server manually.
|
||||||
|
|
||||||
|
@ -10,8 +12,20 @@ To reduce latency, the tcp-relay should be as close as possible to the nodes it
|
||||||
|
|
||||||
|
|
||||||
### Build
|
### Build
|
||||||
`cd tcp-relay`
|
|
||||||
`make`
|
#### Standard Build
|
||||||
|
```bash
|
||||||
|
cd tcp-proxy
|
||||||
|
make
|
||||||
|
```
|
||||||
|
This builds the standard TCP proxy without cloud firewall integration.
|
||||||
|
|
||||||
|
#### Build with Cloud Firewall Support
|
||||||
|
```bash
|
||||||
|
cd tcp-proxy
|
||||||
|
make provider
|
||||||
|
```
|
||||||
|
This builds the TCP proxy with cloud firewall integration enabled. See the [Cloud Firewall Integration](cloud/README.md) documentation for more details.
|
||||||
|
|
||||||
### Point your node at it
|
### Point your node at it
|
||||||
The default tcp relay is at `204.80.128.1/443` -an anycast address.
|
The default tcp relay is at `204.80.128.1/443` -an anycast address.
|
||||||
|
|
121
tcp-proxy/cloud/README.md
Normal file
121
tcp-proxy/cloud/README.md
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
# Cloud Firewall Integration
|
||||||
|
|
||||||
|
## Build Instructions
|
||||||
|
|
||||||
|
To build the TCP proxy with cloud firewall integration support, use the following command from the `tcp-proxy` directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make provider
|
||||||
|
```
|
||||||
|
|
||||||
|
This will compile the TCP proxy with cloud provider functionality enabled by:
|
||||||
|
- Defining the `ENABLE_CLOUD_PROVIDER` preprocessor flag
|
||||||
|
- Including the necessary cloud provider implementation files
|
||||||
|
- Linking against required libraries (like libcurl for API requests)
|
||||||
|
|
||||||
|
The resulting binary will be named `tcp-proxy` but will include all cloud firewall management capabilities.
|
||||||
|
|
||||||
|
### Testing Cloud Provider Implementations
|
||||||
|
|
||||||
|
For testing specific cloud provider implementations, you can use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Test Linode firewall implementation
|
||||||
|
make test_linode_firewall
|
||||||
|
|
||||||
|
# Test cloud firewall template (useful when developing new providers)
|
||||||
|
make test_cloud_firewall_template
|
||||||
|
```
|
||||||
|
|
||||||
|
These test targets compile standalone executables that can be used to verify your cloud provider implementation works correctly without running the full TCP proxy.
|
||||||
|
|
||||||
|
### Cleaning Build Artifacts
|
||||||
|
|
||||||
|
To clean all build artifacts:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make clean
|
||||||
|
```
|
||||||
|
|
||||||
|
### Architecture Note
|
||||||
|
|
||||||
|
The build system uses a modular approach that allows adding new cloud providers without modifying the core TCP proxy code. When you build with `make provider`, the system compiles in the cloud-agnostic firewall management architecture described in [README_CLOUD_INTEGRATION.md](README_CLOUD_INTEGRATION.md).
|
||||||
|
|
||||||
|
## Linode Firewall Integration
|
||||||
|
|
||||||
|
The TCP proxy server can automatically manage cloud firewall rules to open and close UDP ports as clients connect and disconnect. This is useful when running the proxy on a cloud instance with a firewall. Currently, Linode Cloud Firewall is supported, with an extensible architecture to add support for other cloud providers.
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
To enable cloud firewall integration, you can use either the legacy Linode-specific configuration or the new generic cloud provider configuration in your `local.conf` file:
|
||||||
|
|
||||||
|
#### Option 1: Legacy Linode Configuration (Backward Compatible)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"settings": {
|
||||||
|
"tcpPort": 443,
|
||||||
|
"linodeApiToken": "your_linode_api_token_here",
|
||||||
|
"linodeFirewallId": "12345"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You need to provide:
|
||||||
|
1. `linodeApiToken`: A Linode API token with `firewall:read_write` permissions
|
||||||
|
2. `linodeFirewallId`: The ID of the Linode Firewall to manage
|
||||||
|
|
||||||
|
#### Option 2: Generic Cloud Provider Configuration
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"settings": {
|
||||||
|
"tcpPort": 443,
|
||||||
|
"cloudProvider": "linode",
|
||||||
|
"cloudApiToken": "your_cloud_api_token_here",
|
||||||
|
"cloudFirewallId": "12345"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You need to provide:
|
||||||
|
1. `cloudProvider`: The cloud provider name (currently supported: "linode")
|
||||||
|
2. `cloudApiToken`: A cloud provider API token with appropriate permissions
|
||||||
|
3. `cloudFirewallId`: The ID of the cloud firewall to manage
|
||||||
|
|
||||||
|
A sample configuration file with examples for different cloud providers is provided in `conf/local.conf.cloud_examples`.
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
|
||||||
|
When a client connects to the TCP proxy, the server:
|
||||||
|
1. Assigns a UDP port for the client
|
||||||
|
2. Adds that UDP port to the cloud firewall rules (with provider-specific implementation details)
|
||||||
|
3. When the client disconnects, removes the UDP port from the firewall
|
||||||
|
4. Handles provider-specific limitations (e.g., for Linode, automatically splits ports across multiple rules when necessary to comply with Linode's 15-piece limit per rule)
|
||||||
|
|
||||||
|
The server also periodically syncs with the cloud firewall every 15 minutes to ensure rules are up to date.
|
||||||
|
|
||||||
|
### Preserving Existing Rules
|
||||||
|
|
||||||
|
The cloud firewall integration is designed to work alongside your existing firewall rules:
|
||||||
|
|
||||||
|
- All existing non-ZeroTier rules (like TCP, ICMP, etc.) are preserved
|
||||||
|
- Provider-specific rules are created as needed (e.g., for Linode, if no UDP rule with the "ZeroTier-UDP-Ports" label exists, one will be created when the first client connects)
|
||||||
|
- If a TCP 443 rule doesn't exist, one will be added automatically
|
||||||
|
- Inbound and outbound policies from your existing configuration are preserved
|
||||||
|
- The system automatically handles provider-specific limitations (e.g., for Linode, maximum 15 port pieces per rule, where a single port counts as 1 piece and a port range counts as 2 pieces)
|
||||||
|
|
||||||
|
### Extending to Other Cloud Providers
|
||||||
|
|
||||||
|
The TCP proxy server uses an extensible architecture that makes it easy to add support for other cloud providers:
|
||||||
|
|
||||||
|
1. The `CloudFirewallManager` abstract base class defines the interface for all cloud firewall managers
|
||||||
|
2. Provider-specific implementations (like `LinodeFirewallManager`) inherit from this base class
|
||||||
|
3. The `FirewallManagerFactory` creates the appropriate firewall manager based on the configuration
|
||||||
|
|
||||||
|
To add support for a new cloud provider:
|
||||||
|
1. Create a new class that inherits from `CloudFirewallManager`
|
||||||
|
2. Implement the required methods for the specific cloud provider's API
|
||||||
|
3. Add the new provider to the `FirewallManagerFactory`
|
||||||
|
|
||||||
|
For more detailed information on implementing support for additional cloud providers, see the [Cloud Firewall Manager Integration Guide](README_CLOUD_INTEGRATION.md).
|
313
tcp-proxy/cloud/README_CLOUD_INTEGRATION.md
Normal file
313
tcp-proxy/cloud/README_CLOUD_INTEGRATION.md
Normal file
|
@ -0,0 +1,313 @@
|
||||||
|
# Cloud Firewall Manager Integration Guide
|
||||||
|
|
||||||
|
This document provides a comprehensive guide to the cloud-agnostic firewall management architecture implemented in the ZeroTier TCP Proxy. It explains how to integrate new cloud providers beyond the existing Linode implementation.
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
The cloud firewall management system uses an abstract base class and factory pattern to support multiple cloud providers while maintaining a consistent interface. This allows the TCP proxy to manage firewall rules across different cloud platforms without changing the core application logic.
|
||||||
|
|
||||||
|
### Key Components
|
||||||
|
|
||||||
|
1. **CloudFirewallManager** - Abstract base class defining the interface for all cloud firewall managers
|
||||||
|
2. **LinodeFirewallManager** - Concrete implementation for Linode Cloud
|
||||||
|
3. **FirewallManagerFactory** - Factory class for creating provider-specific implementations
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
tcp-proxy/
|
||||||
|
├── cloud/
|
||||||
|
│ ├── README_CLOUD_INTEGRATION.md # This documentation file
|
||||||
|
│ └── provider/
|
||||||
|
│ ├── CloudFirewallManager.hpp # Abstract base class
|
||||||
|
│ ├── FirewallManagerFactory.hpp # Factory class header
|
||||||
|
│ ├── FirewallManagerFactory.cpp # Factory class implementation
|
||||||
|
│ ├── LinodeFirewallManager.hpp # Linode-specific implementation header
|
||||||
|
│ ├── LinodeFirewallManager.cpp # Linode-specific implementation
|
||||||
|
│ ├── tests/ # Test files directory
|
||||||
|
│ │ ├── test_linode_firewall.cpp # Linode test implementation
|
||||||
|
│ │ └── test_cloud_firewall_template.cpp # Template for new provider tests
|
||||||
|
│ ├── YourCloudProvider.hpp # Your new cloud provider header
|
||||||
|
│ └── YourCloudProvider.cpp # Your new cloud provider implementation
|
||||||
|
└── tcp-proxy.cpp # Main application using the factory
|
||||||
|
```
|
||||||
|
|
||||||
|
## Adding a New Cloud Provider
|
||||||
|
|
||||||
|
To add support for a new cloud provider, follow these steps:
|
||||||
|
|
||||||
|
### 1. Create Provider-Specific Implementation Files
|
||||||
|
|
||||||
|
Create two new files for your cloud provider (e.g., `AwsFirewallManager.hpp` and `AwsFirewallManager.cpp`).
|
||||||
|
|
||||||
|
#### Header File Template (`YourCloudProvider.hpp`)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#ifndef YOUR_CLOUD_PROVIDER_FIREWALL_MANAGER_HPP
|
||||||
|
#define YOUR_CLOUD_PROVIDER_FIREWALL_MANAGER_HPP
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <set>
|
||||||
|
#include "CloudFirewallManager.hpp"
|
||||||
|
|
||||||
|
class YourCloudProviderFirewallManager : public CloudFirewallManager {
|
||||||
|
private:
|
||||||
|
// Provider-specific member variables
|
||||||
|
std::string apiToken;
|
||||||
|
std::string firewallId;
|
||||||
|
uint16_t internalTcpPort; // TCP port the proxy listens on internally
|
||||||
|
uint16_t externalTcpPort; // TCP port exposed in firewall rules (may be different from internal port)
|
||||||
|
std::set<uint16_t> activePorts;
|
||||||
|
|
||||||
|
// Private helper methods specific to your cloud provider
|
||||||
|
bool fetchCurrentRules();
|
||||||
|
// Add other helper methods as needed
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Constructor
|
||||||
|
YourCloudProviderFirewallManager(
|
||||||
|
const std::string& apiToken,
|
||||||
|
const std::string& firewallId,
|
||||||
|
uint16_t internalTcpPort,
|
||||||
|
uint16_t externalTcpPort = 0, // Default to 0, which means use internalTcpPort
|
||||||
|
const std::string& additionalParams = "");
|
||||||
|
|
||||||
|
// Implementation of abstract methods from CloudFirewallManager
|
||||||
|
bool initialize() override;
|
||||||
|
bool addPort(uint16_t port) override;
|
||||||
|
bool removePort(uint16_t port) override;
|
||||||
|
bool syncFirewallRules() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // YOUR_CLOUD_PROVIDER_FIREWALL_MANAGER_HPP
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Implementation File Template (`YourCloudProvider.cpp`)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include "YourCloudProviderFirewallManager.hpp"
|
||||||
|
#include <curl/curl.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
// External function for logging (defined in tcp-proxy.cpp)
|
||||||
|
extern void datetime_fprintf(FILE *stream, const char *fmt, ...);
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
YourCloudProviderFirewallManager::YourCloudProviderFirewallManager(
|
||||||
|
const std::string& apiToken,
|
||||||
|
const std::string& firewallId,
|
||||||
|
uint16_t internalTcpPort,
|
||||||
|
uint16_t externalTcpPort = 0, // Default to 0, which means use internalTcpPort
|
||||||
|
const std::string& additionalParams = "")
|
||||||
|
: apiToken(apiToken), firewallId(firewallId),
|
||||||
|
internalTcpPort(internalTcpPort),
|
||||||
|
externalTcpPort(externalTcpPort ? externalTcpPort : internalTcpPort) {
|
||||||
|
// Initialization code
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the firewall manager
|
||||||
|
bool YourCloudProviderFirewallManager::initialize() {
|
||||||
|
// Implementation specific to your cloud provider
|
||||||
|
// Verify credentials, check firewall existence, etc.
|
||||||
|
return true; // Return success/failure
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a port to the managed set
|
||||||
|
bool YourCloudProviderFirewallManager::addPort(uint16_t port) {
|
||||||
|
// Add port to the set of active ports
|
||||||
|
activePorts.insert(port);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove a port from the managed set
|
||||||
|
bool YourCloudProviderFirewallManager::removePort(uint16_t port) {
|
||||||
|
// Remove port from the set of active ports
|
||||||
|
activePorts.erase(port);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synchronize the current set of ports with the cloud provider
|
||||||
|
bool YourCloudProviderFirewallManager::syncFirewallRules() {
|
||||||
|
// Implementation specific to your cloud provider
|
||||||
|
// This is where you'll make API calls to update firewall rules
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement any private helper methods
|
||||||
|
bool YourCloudProviderFirewallManager::fetchCurrentRules() {
|
||||||
|
// Implementation specific to your cloud provider
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Update the FirewallManagerFactory
|
||||||
|
|
||||||
|
Modify `FirewallManagerFactory.hpp` and `FirewallManagerFactory.cpp` to include your new provider:
|
||||||
|
|
||||||
|
#### Update FirewallManagerFactory.hpp
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Add include for your new provider
|
||||||
|
#include "YourCloudProviderFirewallManager.hpp"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Update FirewallManagerFactory.cpp
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
std::unique_ptr<CloudFirewallManager> FirewallManagerFactory::createFirewallManager(
|
||||||
|
const std::string& provider,
|
||||||
|
const std::string& apiToken,
|
||||||
|
const std::string& firewallId,
|
||||||
|
uint16_t internalTcpPort,
|
||||||
|
uint16_t externalTcpPort = 0, // Default to 0, which means use internalTcpPort
|
||||||
|
const std::string& additionalParams = "") {
|
||||||
|
|
||||||
|
if (provider == "linode") {
|
||||||
|
return std::unique_ptr<CloudFirewallManager>(new LinodeFirewallManager(apiToken, firewallId, internalTcpPort, externalTcpPort));
|
||||||
|
} else if (provider == "yourprovider") {
|
||||||
|
// Add your provider here
|
||||||
|
return std::unique_ptr<CloudFirewallManager>(new YourCloudProviderFirewallManager(apiToken, firewallId, internalTcpPort, externalTcpPort, additionalParams));
|
||||||
|
} else {
|
||||||
|
// Unknown provider
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Update the Makefile
|
||||||
|
|
||||||
|
Add your new source files to the Makefile:
|
||||||
|
|
||||||
|
```makefile
|
||||||
|
tcp-proxy: tcp-proxy.cpp LinodeFirewallManager.cpp YourCloudProviderFirewallManager.cpp FirewallManagerFactory.cpp ../node/Metrics.cpp
|
||||||
|
$(CXX) $(CXXFLAGS) $(INCLUDES) -o tcp-proxy tcp-proxy.cpp LinodeFirewallManager.cpp YourCloudProviderFirewallManager.cpp FirewallManagerFactory.cpp ../node/Metrics.cpp -lcurl
|
||||||
|
|
||||||
|
test_linode_firewall: test_linode_firewall.cpp LinodeFirewallManager.cpp YourCloudProviderFirewallManager.cpp FirewallManagerFactory.cpp
|
||||||
|
$(CXX) $(TEST_CXXFLAGS) $(INCLUDES) -o test_linode_firewall test_linode_firewall.cpp LinodeFirewallManager.cpp YourCloudProviderFirewallManager.cpp FirewallManagerFactory.cpp -lcurl
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Update Configuration Documentation
|
||||||
|
|
||||||
|
Update `local.conf.cloud_examples` to include your provider's configuration options. The existing file already contains examples for different cloud providers, and you should add your provider's specific configuration following the same pattern:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"// Example: YourProvider configuration":"",
|
||||||
|
"// Replace the above cloud settings with these for YourProvider":"",
|
||||||
|
"// tcpPort": 8443,
|
||||||
|
"// externalTcpPort": 443,
|
||||||
|
"// cloudProvider": "yourprovider",
|
||||||
|
"// cloudApiToken": "your_provider_api_token",
|
||||||
|
"// cloudFirewallId": "your_firewall_id",
|
||||||
|
"// cloudAdditionalParams": {
|
||||||
|
"// "specificParam1": "value1",
|
||||||
|
"// "specificParam2": "value2"
|
||||||
|
"// }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that `local.conf.example` should remain focused on basic TCP proxy configuration, while cloud-specific examples belong in `local.conf.cloud_examples`.
|
||||||
|
|
||||||
|
### 5. Create Provider-Specific Tests (Optional)
|
||||||
|
|
||||||
|
You should create a test file for your provider based on the template in the tests directory:
|
||||||
|
|
||||||
|
1. Copy the template file as a starting point:
|
||||||
|
```bash
|
||||||
|
cp tcp-proxy/cloud/provider/tests/test_cloud_firewall_template.cpp tcp-proxy/cloud/provider/tests/test_yourprovider_firewall.cpp
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Modify the test file to include your provider-specific header and test cases
|
||||||
|
|
||||||
|
3. Add a build target to the Makefile:
|
||||||
|
```makefile
|
||||||
|
test_yourprovider_firewall:
|
||||||
|
$(CXX) -O3 $(INCLUDES) -std=c++11 -pthread -o test_yourprovider_firewall cloud/provider/tests/test_yourprovider_firewall.cpp cloud/provider/YourProviderFirewallManager.cpp cloud/provider/FirewallManagerFactory.cpp $(LIBS)
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Run your tests:
|
||||||
|
```bash
|
||||||
|
make test_yourprovider_firewall
|
||||||
|
./test_yourprovider_firewall
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Guidelines
|
||||||
|
|
||||||
|
### API Interaction
|
||||||
|
|
||||||
|
Most cloud providers offer REST APIs for firewall management. Use libcurl (already included in the project) for making HTTP requests. Follow these general steps:
|
||||||
|
|
||||||
|
1. **Authentication**: Implement proper authentication using the provided API token
|
||||||
|
2. **Error Handling**: Implement robust error handling for API responses
|
||||||
|
3. **Rate Limiting**: Be mindful of API rate limits and implement appropriate backoff strategies
|
||||||
|
4. **Idempotency**: Ensure operations are idempotent where possible
|
||||||
|
|
||||||
|
### Firewall Rule Management
|
||||||
|
|
||||||
|
The core functionality revolves around managing UDP ports in firewall rules:
|
||||||
|
|
||||||
|
1. **Rule Naming**: Use a consistent naming convention (e.g., "ZeroTier-UDP-Ports")
|
||||||
|
2. **Port Handling**: Efficiently manage port ranges to minimize API calls
|
||||||
|
3. **Rule Limits**: Be aware of provider-specific limits on rule complexity
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
Thoroughly test your implementation:
|
||||||
|
|
||||||
|
1. **Basic Functionality**: Test adding/removing ports and syncing rules
|
||||||
|
2. **Edge Cases**: Test behavior with many ports, rapid changes, etc.
|
||||||
|
3. **Error Conditions**: Test behavior when API calls fail
|
||||||
|
|
||||||
|
## Common Challenges
|
||||||
|
|
||||||
|
### Provider-Specific Rule Formats
|
||||||
|
|
||||||
|
Each cloud provider has its own format for firewall rules. You'll need to adapt the generic port management to your provider's specific rule format.
|
||||||
|
|
||||||
|
### Rule Consolidation
|
||||||
|
|
||||||
|
Some providers have limits on the number of rules or rule complexity. Implement strategies to consolidate rules when possible (e.g., combining adjacent ports into ranges).
|
||||||
|
|
||||||
|
### API Limitations
|
||||||
|
|
||||||
|
Be aware of API rate limits, authentication requirements, and other provider-specific limitations.
|
||||||
|
|
||||||
|
## Example: AWS Implementation Outline
|
||||||
|
|
||||||
|
Here's a brief outline of what an AWS implementation might look like:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// AwsFirewallManager.hpp
|
||||||
|
class AwsFirewallManager : public CloudFirewallManager {
|
||||||
|
private:
|
||||||
|
std::string accessKey;
|
||||||
|
std::string secretKey;
|
||||||
|
std::string securityGroupId;
|
||||||
|
std::string region;
|
||||||
|
std::set<uint16_t> activePorts;
|
||||||
|
|
||||||
|
// AWS-specific helper methods
|
||||||
|
bool authorizeSecurityGroupIngress(uint16_t port);
|
||||||
|
bool revokeSecurityGroupIngress(uint16_t port);
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Constructor with AWS-specific parameters
|
||||||
|
AwsFirewallManager(const std::string& accessKey, const std::string& secretKey,
|
||||||
|
const std::string& securityGroupId, const std::string& region);
|
||||||
|
|
||||||
|
// Implementation of abstract methods
|
||||||
|
bool initialize() override;
|
||||||
|
bool addPort(uint16_t port) override;
|
||||||
|
bool removePort(uint16_t port) override;
|
||||||
|
bool syncFirewallRules() override;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
You would then need to adapt the factory to handle the different parameter requirements for AWS.
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
By following this guide, you can extend the ZeroTier TCP Proxy's cloud firewall management to support additional providers. The abstract interface ensures that the core application logic remains unchanged while allowing for provider-specific implementations.
|
||||||
|
|
||||||
|
Remember to thoroughly test your implementation and contribute back to the project if possible!
|
93
tcp-proxy/cloud/cloud_architecture_diagram.svg
Normal file
93
tcp-proxy/cloud/cloud_architecture_diagram.svg
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="800" height="600" viewBox="0 0 800 600">
|
||||||
|
<!-- Background -->
|
||||||
|
<rect x="0" y="0" width="800" height="600" fill="#f8f9fa" />
|
||||||
|
|
||||||
|
<!-- Title -->
|
||||||
|
<text x="400" y="40" font-family="Arial" font-size="24" text-anchor="middle" font-weight="bold">ZeroTier Cloud Firewall Manager Architecture</text>
|
||||||
|
|
||||||
|
<!-- Abstract Base Class -->
|
||||||
|
<rect x="300" y="80" width="200" height="100" rx="5" ry="5" fill="#e6f2ff" stroke="#0066cc" stroke-width="2" />
|
||||||
|
<text x="400" y="110" font-family="Arial" font-size="16" text-anchor="middle" font-weight="bold">CloudFirewallManager</text>
|
||||||
|
<line x1="300" y1="120" x2="500" y2="120" stroke="#0066cc" stroke-width="1" />
|
||||||
|
<text x="310" y="140" font-family="Arial" font-size="12">+ initialize(): bool</text>
|
||||||
|
<text x="310" y="160" font-family="Arial" font-size="12">+ addPort(port: uint16_t): bool</text>
|
||||||
|
<text x="310" y="180" font-family="Arial" font-size="12">+ removePort(port: uint16_t): bool</text>
|
||||||
|
|
||||||
|
<!-- Factory Class -->
|
||||||
|
<rect x="550" y="80" width="200" height="100" rx="5" ry="5" fill="#fff2e6" stroke="#ff8c1a" stroke-width="2" />
|
||||||
|
<text x="650" y="110" font-family="Arial" font-size="16" text-anchor="middle" font-weight="bold">FirewallManagerFactory</text>
|
||||||
|
<line x1="550" y1="120" x2="750" y2="120" stroke="#ff8c1a" stroke-width="1" />
|
||||||
|
<text x="560" y="140" font-family="Arial" font-size="12">+ createFirewallManager(</text>
|
||||||
|
<text x="570" y="160" font-family="Arial" font-size="12">provider: string,</text>
|
||||||
|
<text x="570" y="180" font-family="Arial" font-size="12">...): CloudFirewallManager*</text>
|
||||||
|
|
||||||
|
<!-- Implementations -->
|
||||||
|
<rect x="50" y="250" width="180" height="80" rx="5" ry="5" fill="#e6ffe6" stroke="#009900" stroke-width="2" />
|
||||||
|
<text x="140" y="280" font-family="Arial" font-size="14" text-anchor="middle" font-weight="bold">LinodeFirewallManager</text>
|
||||||
|
<line x1="50" y1="290" x2="230" y2="290" stroke="#009900" stroke-width="1" />
|
||||||
|
<text x="60" y="310" font-family="Arial" font-size="12">Implementation for Linode</text>
|
||||||
|
|
||||||
|
<rect x="250" y="250" width="180" height="80" rx="5" ry="5" fill="#e6ffe6" stroke="#009900" stroke-width="2" />
|
||||||
|
<text x="340" y="280" font-family="Arial" font-size="14" text-anchor="middle" font-weight="bold">AwsFirewallManager</text>
|
||||||
|
<line x1="250" y1="290" x2="430" y2="290" stroke="#009900" stroke-width="1" />
|
||||||
|
<text x="260" y="310" font-family="Arial" font-size="12">Implementation for AWS</text>
|
||||||
|
|
||||||
|
<rect x="450" y="250" width="180" height="80" rx="5" ry="5" fill="#e6ffe6" stroke="#009900" stroke-width="2" />
|
||||||
|
<text x="540" y="280" font-family="Arial" font-size="14" text-anchor="middle" font-weight="bold">AzureFirewallManager</text>
|
||||||
|
<line x1="450" y1="290" x2="630" y2="290" stroke="#009900" stroke-width="1" />
|
||||||
|
<text x="460" y="310" font-family="Arial" font-size="12">Implementation for Azure</text>
|
||||||
|
|
||||||
|
<rect x="650" y="250" width="180" height="80" rx="5" ry="5" fill="#e6ffe6" stroke="#009900" stroke-width="2" />
|
||||||
|
<text x="740" y="280" font-family="Arial" font-size="14" text-anchor="middle" font-weight="bold">GcpFirewallManager</text>
|
||||||
|
<line x1="650" y1="290" x2="830" y2="290" stroke="#009900" stroke-width="1" />
|
||||||
|
<text x="660" y="310" font-family="Arial" font-size="12">Implementation for GCP</text>
|
||||||
|
|
||||||
|
<!-- Client Code -->
|
||||||
|
<rect x="300" y="400" width="200" height="80" rx="5" ry="5" fill="#ffe6e6" stroke="#cc0000" stroke-width="2" />
|
||||||
|
<text x="400" y="430" font-family="Arial" font-size="16" text-anchor="middle" font-weight="bold">tcp-proxy.cpp</text>
|
||||||
|
<line x1="300" y1="440" x2="500" y2="440" stroke="#cc0000" stroke-width="1" />
|
||||||
|
<text x="310" y="460" font-family="Arial" font-size="12">Client code using the factory</text>
|
||||||
|
|
||||||
|
<!-- Inheritance Arrows -->
|
||||||
|
<line x1="140" y1="250" x2="350" y2="180" stroke="#009900" stroke-width="1.5" stroke-dasharray="5,5" />
|
||||||
|
<polygon points="350,180 340,170 360,170" fill="#009900" />
|
||||||
|
|
||||||
|
<line x1="340" y1="250" x2="380" y2="180" stroke="#009900" stroke-width="1.5" stroke-dasharray="5,5" />
|
||||||
|
<polygon points="380,180 370,170 390,170" fill="#009900" />
|
||||||
|
|
||||||
|
<line x1="540" y1="250" x2="420" y2="180" stroke="#009900" stroke-width="1.5" stroke-dasharray="5,5" />
|
||||||
|
<polygon points="420,180 410,170 430,170" fill="#009900" />
|
||||||
|
|
||||||
|
<line x1="740" y1="250" x2="450" y2="180" stroke="#009900" stroke-width="1.5" stroke-dasharray="5,5" />
|
||||||
|
<polygon points="450,180 440,170 460,170" fill="#009900" />
|
||||||
|
|
||||||
|
<!-- Factory Usage Arrow -->
|
||||||
|
<line x1="550" y1="130" x2="500" y2="130" stroke="#ff8c1a" stroke-width="1.5" />
|
||||||
|
<polygon points="500,130 510,125 510,135" fill="#ff8c1a" />
|
||||||
|
|
||||||
|
<!-- Client Usage Arrows -->
|
||||||
|
<line x1="400" y1="400" x2="400" y2="180" stroke="#cc0000" stroke-width="1.5" />
|
||||||
|
<polygon points="400,180 395,190 405,190" fill="#cc0000" />
|
||||||
|
|
||||||
|
<line x1="400" y1="400" x2="650" y2="180" stroke="#cc0000" stroke-width="1.5" />
|
||||||
|
<polygon points="650,180 640,185 645,170" fill="#cc0000" />
|
||||||
|
|
||||||
|
<!-- Legend -->
|
||||||
|
<rect x="50" y="500" width="20" height="20" fill="#e6f2ff" stroke="#0066cc" stroke-width="2" />
|
||||||
|
<text x="80" y="515" font-family="Arial" font-size="12">Abstract Base Class</text>
|
||||||
|
|
||||||
|
<rect x="200" y="500" width="20" height="20" fill="#fff2e6" stroke="#ff8c1a" stroke-width="2" />
|
||||||
|
<text x="230" y="515" font-family="Arial" font-size="12">Factory Class</text>
|
||||||
|
|
||||||
|
<rect x="350" y="500" width="20" height="20" fill="#e6ffe6" stroke="#009900" stroke-width="2" />
|
||||||
|
<text x="380" y="515" font-family="Arial" font-size="12">Concrete Implementations</text>
|
||||||
|
|
||||||
|
<rect x="550" y="500" width="20" height="20" fill="#ffe6e6" stroke="#cc0000" stroke-width="2" />
|
||||||
|
<text x="580" y="515" font-family="Arial" font-size="12">Client Code</text>
|
||||||
|
|
||||||
|
<line x1="50" y="540" x2="70" y2="540" stroke="#009900" stroke-width="1.5" stroke-dasharray="5,5" />
|
||||||
|
<text x="80" y="545" font-family="Arial" font-size="12">Inheritance</text>
|
||||||
|
|
||||||
|
<line x1="200" y="540" x2="220" y2="540" stroke="#cc0000" stroke-width="1.5" />
|
||||||
|
<text x="230" y="545" font-family="Arial" font-size="12">Usage/Dependency</text>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 6 KiB |
65
tcp-proxy/cloud/provider/AwsFirewallManager.hpp
Normal file
65
tcp-proxy/cloud/provider/AwsFirewallManager.hpp
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
/**
|
||||||
|
* AwsFirewallManager.hpp
|
||||||
|
*
|
||||||
|
* Example implementation of CloudFirewallManager for AWS
|
||||||
|
* This is a sample to demonstrate how to implement a specific cloud provider
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef AWS_FIREWALL_MANAGER_HPP
|
||||||
|
#define AWS_FIREWALL_MANAGER_HPP
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <set>
|
||||||
|
#include "CloudFirewallManager.hpp"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AwsFirewallManager
|
||||||
|
*
|
||||||
|
* Implementation of CloudFirewallManager for AWS Security Groups
|
||||||
|
*/
|
||||||
|
class AwsFirewallManager : public CloudFirewallManager {
|
||||||
|
private:
|
||||||
|
// AWS credentials
|
||||||
|
std::string accessKey;
|
||||||
|
std::string secretKey;
|
||||||
|
|
||||||
|
// AWS Security Group ID
|
||||||
|
std::string securityGroupId;
|
||||||
|
|
||||||
|
// AWS Region
|
||||||
|
std::string region;
|
||||||
|
|
||||||
|
// Set of currently active ports
|
||||||
|
std::set<uint16_t> activePorts;
|
||||||
|
|
||||||
|
// Helper methods
|
||||||
|
bool fetchCurrentRules();
|
||||||
|
bool makeAwsApiRequest(const std::string& action,
|
||||||
|
const std::string& params,
|
||||||
|
std::string& response);
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param accessKey AWS access key
|
||||||
|
* @param secretKey AWS secret key
|
||||||
|
* @param securityGroupId AWS security group ID
|
||||||
|
* @param region AWS region
|
||||||
|
*/
|
||||||
|
AwsFirewallManager(const std::string& accessKey,
|
||||||
|
const std::string& secretKey,
|
||||||
|
const std::string& securityGroupId,
|
||||||
|
const std::string& region);
|
||||||
|
|
||||||
|
// Destructor
|
||||||
|
virtual ~AwsFirewallManager() = default;
|
||||||
|
|
||||||
|
// Implementation of abstract methods from CloudFirewallManager
|
||||||
|
bool initialize() override;
|
||||||
|
bool addPort(uint16_t port) override;
|
||||||
|
bool removePort(uint16_t port) override;
|
||||||
|
bool syncFirewallRules() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // AWS_FIREWALL_MANAGER_HPP
|
88
tcp-proxy/cloud/provider/CLOUD_PROVIDER_CHECKLIST.md
Normal file
88
tcp-proxy/cloud/provider/CLOUD_PROVIDER_CHECKLIST.md
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
# Cloud Provider Integration Checklist
|
||||||
|
|
||||||
|
This checklist guides you through the process of adding support for a new cloud provider to the ZeroTier TCP Proxy's cloud-agnostic firewall management system.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- [ ] Understand the cloud provider's firewall/security group API
|
||||||
|
- [ ] Obtain necessary API credentials for testing
|
||||||
|
- [ ] Create a test firewall/security group in the cloud provider's console
|
||||||
|
|
||||||
|
## Implementation Steps
|
||||||
|
|
||||||
|
### 1. Create Provider-Specific Header File
|
||||||
|
|
||||||
|
- [ ] Create `YourCloudProviderFirewallManager.hpp` file
|
||||||
|
- [ ] Define class that inherits from `CloudFirewallManager`
|
||||||
|
- [ ] Declare required member variables (API credentials, firewall ID, etc.)
|
||||||
|
- [ ] Declare constructor and destructor
|
||||||
|
- [ ] Declare override methods for the abstract base class:
|
||||||
|
- [ ] `bool initialize()`
|
||||||
|
- [ ] `bool addPort(uint16_t port)`
|
||||||
|
- [ ] `bool removePort(uint16_t port)`
|
||||||
|
- [ ] `bool syncFirewallRules()`
|
||||||
|
- [ ] Declare any provider-specific helper methods
|
||||||
|
|
||||||
|
### 2. Create Provider-Specific Implementation File
|
||||||
|
|
||||||
|
- [ ] Create `YourCloudProviderFirewallManager.cpp` file
|
||||||
|
- [ ] Implement constructor to initialize member variables
|
||||||
|
- [ ] Implement `initialize()` method to verify API credentials and firewall existence
|
||||||
|
- [ ] Implement `addPort()` method to add a port to the managed set
|
||||||
|
- [ ] Implement `removePort()` method to remove a port from the managed set
|
||||||
|
- [ ] Implement `syncFirewallRules()` method to update the cloud firewall rules
|
||||||
|
- [ ] Implement any provider-specific helper methods
|
||||||
|
- [ ] Add proper error handling and logging
|
||||||
|
|
||||||
|
### 3. Update FirewallManagerFactory
|
||||||
|
|
||||||
|
- [ ] Add include for your new provider's header file in `FirewallManagerFactory.hpp`
|
||||||
|
- [ ] Update `createFirewallManager()` method in `FirewallManagerFactory.cpp` to handle your provider
|
||||||
|
- [ ] Add parsing for any provider-specific parameters
|
||||||
|
|
||||||
|
### 4. Update Makefile
|
||||||
|
|
||||||
|
- [ ] Add your new source files to the compilation targets
|
||||||
|
|
||||||
|
### 5. Create Tests
|
||||||
|
|
||||||
|
- [ ] Create a test file based on `test_cloud_firewall_template.cpp`
|
||||||
|
- [ ] Customize the test file for your provider's specific requirements
|
||||||
|
- [ ] Add the test to the Makefile
|
||||||
|
|
||||||
|
### 6. Update Documentation
|
||||||
|
|
||||||
|
- [ ] Add configuration examples for your provider to `local.conf.example`
|
||||||
|
- [ ] Update `README_CLOUD_INTEGRATION.md` with provider-specific details
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
- [ ] Test basic functionality:
|
||||||
|
- [ ] Initialize the firewall manager
|
||||||
|
- [ ] Add a single port
|
||||||
|
- [ ] Remove a single port
|
||||||
|
- [ ] Add multiple ports
|
||||||
|
- [ ] Add port ranges
|
||||||
|
- [ ] Remove port ranges
|
||||||
|
- [ ] Sync firewall rules
|
||||||
|
|
||||||
|
- [ ] Test edge cases:
|
||||||
|
- [ ] Invalid credentials
|
||||||
|
- [ ] Non-existent firewall ID
|
||||||
|
- [ ] API rate limiting
|
||||||
|
- [ ] Network connectivity issues
|
||||||
|
- [ ] Maximum number of rules/ports
|
||||||
|
|
||||||
|
## Final Review
|
||||||
|
|
||||||
|
- [ ] Code follows project style guidelines
|
||||||
|
- [ ] All methods have proper error handling
|
||||||
|
- [ ] Logging is consistent with the rest of the codebase
|
||||||
|
- [ ] Documentation is complete and accurate
|
||||||
|
- [ ] Tests pass for all functionality
|
||||||
|
|
||||||
|
## Submission
|
||||||
|
|
||||||
|
- [ ] Create a pull request with your changes
|
||||||
|
- [ ] Include test results and documentation updates
|
||||||
|
- [ ] Address any review comments
|
75
tcp-proxy/cloud/provider/CloudFirewallManager.hpp
Normal file
75
tcp-proxy/cloud/provider/CloudFirewallManager.hpp
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
* ZeroTier One - Network Virtualization Everywhere
|
||||||
|
* Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CLOUD_FIREWALL_MANAGER_HPP
|
||||||
|
#define CLOUD_FIREWALL_MANAGER_HPP
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <set>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CloudFirewallManager
|
||||||
|
*
|
||||||
|
* Abstract base class for cloud firewall management.
|
||||||
|
* Provides a common interface for different cloud provider implementations.
|
||||||
|
*
|
||||||
|
* This class defines the interface that all cloud firewall manager implementations
|
||||||
|
* must follow, allowing the TCP proxy to work with different cloud providers
|
||||||
|
* without changing the core logic.
|
||||||
|
*/
|
||||||
|
class CloudFirewallManager {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Initialize the firewall manager
|
||||||
|
* Fetches current rules and prepares for updates
|
||||||
|
*
|
||||||
|
* @return true if initialization was successful, false otherwise
|
||||||
|
*/
|
||||||
|
virtual bool initialize() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a UDP port to the firewall rules
|
||||||
|
*
|
||||||
|
* @param port UDP port to add
|
||||||
|
* @return true if successful, false otherwise
|
||||||
|
*/
|
||||||
|
virtual bool addPort(uint16_t port) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a UDP port from the firewall rules
|
||||||
|
*
|
||||||
|
* @param port UDP port to remove
|
||||||
|
* @return true if successful, false otherwise
|
||||||
|
*/
|
||||||
|
virtual bool removePort(uint16_t port) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force synchronization of firewall rules with the cloud provider
|
||||||
|
*
|
||||||
|
* @return true if successful, false otherwise
|
||||||
|
*/
|
||||||
|
virtual bool syncFirewallRules() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Virtual destructor for proper cleanup in derived classes
|
||||||
|
*/
|
||||||
|
virtual ~CloudFirewallManager() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CLOUD_FIREWALL_MANAGER_HPP
|
229
tcp-proxy/cloud/provider/CloudFirewallManager_interface.md
Normal file
229
tcp-proxy/cloud/provider/CloudFirewallManager_interface.md
Normal file
|
@ -0,0 +1,229 @@
|
||||||
|
# CloudFirewallManager Interface
|
||||||
|
|
||||||
|
This document provides a detailed explanation of the `CloudFirewallManager` abstract base class interface. Understanding this interface is crucial for implementing support for new cloud providers in the ZeroTier TCP Proxy's cloud-agnostic firewall management system.
|
||||||
|
|
||||||
|
## Interface Overview
|
||||||
|
|
||||||
|
The `CloudFirewallManager` abstract base class defines a common interface that all cloud provider implementations must follow. This ensures that the TCP proxy can interact with different cloud providers in a consistent manner.
|
||||||
|
|
||||||
|
## Required Methods
|
||||||
|
|
||||||
|
Each cloud provider implementation must override the following methods:
|
||||||
|
|
||||||
|
### `bool initialize()`
|
||||||
|
|
||||||
|
**Purpose:** Initialize the firewall manager and verify that it can communicate with the cloud provider's API.
|
||||||
|
|
||||||
|
**Implementation Requirements:**
|
||||||
|
- Verify that the provided API credentials are valid
|
||||||
|
- Check that the specified firewall/security group exists
|
||||||
|
- Establish any necessary connections or sessions with the cloud provider's API
|
||||||
|
- Set up any required state for subsequent operations
|
||||||
|
|
||||||
|
**Return Value:**
|
||||||
|
- `true` if initialization was successful
|
||||||
|
- `false` if there was an error (invalid credentials, non-existent firewall, etc.)
|
||||||
|
|
||||||
|
**Example Implementation:**
|
||||||
|
```cpp
|
||||||
|
bool YourCloudProviderFirewallManager::initialize() {
|
||||||
|
// Log initialization attempt
|
||||||
|
datetime_fprintf(stdout, "Initializing Your Cloud Provider firewall manager...\n");
|
||||||
|
|
||||||
|
// Make an API call to verify credentials and firewall existence
|
||||||
|
std::string response;
|
||||||
|
if (!makeApiRequest("/firewalls/" + firewallId, "GET", "", response)) {
|
||||||
|
datetime_fprintf(stderr, "Failed to verify firewall existence\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse response to verify firewall exists
|
||||||
|
try {
|
||||||
|
json j = json::parse(response);
|
||||||
|
// Verify the response indicates the firewall exists
|
||||||
|
|
||||||
|
datetime_fprintf(stdout, "Successfully initialized Your Cloud Provider firewall manager\n");
|
||||||
|
return true;
|
||||||
|
} catch (json::parse_error& e) {
|
||||||
|
datetime_fprintf(stderr, "JSON parse error: %s\n", e.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `bool addPort(uint16_t port)`
|
||||||
|
|
||||||
|
**Purpose:** Add a UDP port to the set of ports that should be allowed through the firewall.
|
||||||
|
|
||||||
|
**Implementation Requirements:**
|
||||||
|
- Add the port to an internal data structure (e.g., a set of active ports)
|
||||||
|
- Do NOT make API calls to update the firewall rules immediately
|
||||||
|
- Changes should be applied when `syncFirewallRules()` is called
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `port`: The UDP port number to add (0-65535)
|
||||||
|
|
||||||
|
**Return Value:**
|
||||||
|
- `true` if the port was successfully added to the managed set
|
||||||
|
- `false` if there was an error
|
||||||
|
|
||||||
|
**Example Implementation:**
|
||||||
|
```cpp
|
||||||
|
bool YourCloudProviderFirewallManager::addPort(uint16_t port) {
|
||||||
|
datetime_fprintf(stdout, "Adding port %d to managed set\n", port);
|
||||||
|
activePorts.insert(port);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `bool removePort(uint16_t port)`
|
||||||
|
|
||||||
|
**Purpose:** Remove a UDP port from the set of ports that should be allowed through the firewall.
|
||||||
|
|
||||||
|
**Implementation Requirements:**
|
||||||
|
- Remove the port from the internal data structure
|
||||||
|
- Do NOT make API calls to update the firewall rules immediately
|
||||||
|
- Changes should be applied when `syncFirewallRules()` is called
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `port`: The UDP port number to remove (0-65535)
|
||||||
|
|
||||||
|
**Return Value:**
|
||||||
|
- `true` if the port was successfully removed from the managed set
|
||||||
|
- `false` if there was an error
|
||||||
|
|
||||||
|
**Example Implementation:**
|
||||||
|
```cpp
|
||||||
|
bool YourCloudProviderFirewallManager::removePort(uint16_t port) {
|
||||||
|
datetime_fprintf(stdout, "Removing port %d from managed set\n", port);
|
||||||
|
activePorts.erase(port);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `bool syncFirewallRules()`
|
||||||
|
|
||||||
|
**Purpose:** Synchronize the current set of managed ports with the cloud provider's firewall rules.
|
||||||
|
|
||||||
|
**Implementation Requirements:**
|
||||||
|
- Make API calls to update the firewall rules based on the current set of active ports
|
||||||
|
- Handle any provider-specific rule formatting or limitations
|
||||||
|
- Implement efficient rule updates (e.g., consolidate adjacent ports into ranges)
|
||||||
|
- Handle API rate limiting and errors
|
||||||
|
|
||||||
|
**Return Value:**
|
||||||
|
- `true` if the firewall rules were successfully synchronized
|
||||||
|
- `false` if there was an error
|
||||||
|
|
||||||
|
**Example Implementation:**
|
||||||
|
```cpp
|
||||||
|
bool YourCloudProviderFirewallManager::syncFirewallRules() {
|
||||||
|
datetime_fprintf(stdout, "Synchronizing firewall rules with Your Cloud Provider...\n");
|
||||||
|
|
||||||
|
// Fetch current rules to avoid unnecessary updates
|
||||||
|
if (!fetchCurrentRules()) {
|
||||||
|
datetime_fprintf(stderr, "Failed to fetch current firewall rules\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare the update payload based on the provider's API requirements
|
||||||
|
json payload;
|
||||||
|
|
||||||
|
// Convert individual ports to ranges where possible
|
||||||
|
std::vector<std::pair<uint16_t, uint16_t>> ranges = consolidatePortRanges(activePorts);
|
||||||
|
|
||||||
|
// Add each range to the payload
|
||||||
|
json rules = json::array();
|
||||||
|
for (const auto& range : ranges) {
|
||||||
|
json rule;
|
||||||
|
rule["protocol"] = "udp";
|
||||||
|
if (range.first == range.second) {
|
||||||
|
rule["port"] = range.first;
|
||||||
|
} else {
|
||||||
|
rule["portRange"] = {
|
||||||
|
{"from", range.first},
|
||||||
|
{"to", range.second}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
rule["description"] = "ZeroTier UDP Port";
|
||||||
|
rules.push_back(rule);
|
||||||
|
}
|
||||||
|
|
||||||
|
payload["rules"] = rules;
|
||||||
|
|
||||||
|
// Send update to cloud provider
|
||||||
|
std::string response;
|
||||||
|
if (!makeApiRequest("/firewalls/" + firewallId + "/rules", "PUT", payload.dump(), response)) {
|
||||||
|
datetime_fprintf(stderr, "Failed to update firewall rules\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
datetime_fprintf(stdout, "Successfully synchronized firewall rules\n");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Helper Methods
|
||||||
|
|
||||||
|
In addition to the required methods, you will likely need to implement several helper methods specific to your cloud provider:
|
||||||
|
|
||||||
|
### API Request Handling
|
||||||
|
|
||||||
|
Implement a method to handle API requests to the cloud provider:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
bool YourCloudProviderFirewallManager::makeApiRequest(
|
||||||
|
const std::string& endpoint,
|
||||||
|
const std::string& method,
|
||||||
|
const std::string& data,
|
||||||
|
std::string& response) {
|
||||||
|
|
||||||
|
// Implementation using libcurl or another HTTP client library
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Port Range Consolidation
|
||||||
|
|
||||||
|
Implement a method to consolidate adjacent ports into ranges to minimize the number of firewall rules:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
std::vector<std::pair<uint16_t, uint16_t>> YourCloudProviderFirewallManager::consolidatePortRanges(
|
||||||
|
const std::set<uint16_t>& ports) {
|
||||||
|
|
||||||
|
std::vector<std::pair<uint16_t, uint16_t>> ranges;
|
||||||
|
|
||||||
|
// Implementation to convert individual ports to ranges
|
||||||
|
|
||||||
|
return ranges;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Error Handling**: Implement robust error handling for API calls and other operations.
|
||||||
|
|
||||||
|
2. **Logging**: Use the `datetime_fprintf` function for consistent logging.
|
||||||
|
|
||||||
|
3. **Rate Limiting**: Be aware of the cloud provider's API rate limits and implement appropriate backoff strategies.
|
||||||
|
|
||||||
|
4. **Idempotency**: Ensure operations are idempotent where possible to avoid duplicate rules.
|
||||||
|
|
||||||
|
5. **Rule Limits**: Be aware of provider-specific limits on rule complexity or count.
|
||||||
|
|
||||||
|
6. **Authentication**: Handle authentication securely and refresh tokens if necessary.
|
||||||
|
|
||||||
|
7. **Cleanup**: Properly clean up resources in the destructor.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Thoroughly test your implementation with various scenarios:
|
||||||
|
|
||||||
|
1. Adding and removing individual ports
|
||||||
|
2. Adding and removing port ranges
|
||||||
|
3. Handling API errors and rate limiting
|
||||||
|
4. Recovering from network interruptions
|
||||||
|
5. Handling edge cases (e.g., maximum number of rules)
|
||||||
|
|
||||||
|
## Example Implementation
|
||||||
|
|
||||||
|
Refer to the `LinodeFirewallManager` implementation as a reference for how to implement the interface for your cloud provider.
|
23
tcp-proxy/cloud/provider/FirewallManagerFactory.cpp
Normal file
23
tcp-proxy/cloud/provider/FirewallManagerFactory.cpp
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* ZeroTier One - Network Virtualization Everywhere
|
||||||
|
* Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "FirewallManagerFactory.hpp"
|
||||||
|
|
||||||
|
// This file is intentionally minimal as most of the implementation
|
||||||
|
// is in the header file. It exists primarily to ensure the factory
|
||||||
|
// is properly included in the build process.
|
75
tcp-proxy/cloud/provider/FirewallManagerFactory.hpp
Normal file
75
tcp-proxy/cloud/provider/FirewallManagerFactory.hpp
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
* ZeroTier One - Network Virtualization Everywhere
|
||||||
|
* Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef FIREWALL_MANAGER_FACTORY_HPP
|
||||||
|
#define FIREWALL_MANAGER_FACTORY_HPP
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
#include "CloudFirewallManager.hpp"
|
||||||
|
#include "LinodeFirewallManager.hpp"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FirewallManagerFactory
|
||||||
|
*
|
||||||
|
* Factory class for creating cloud provider-specific firewall managers.
|
||||||
|
* This allows the TCP proxy to work with different cloud providers
|
||||||
|
* without changing the core logic.
|
||||||
|
*/
|
||||||
|
class FirewallManagerFactory {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Create a firewall manager for the specified cloud provider
|
||||||
|
*
|
||||||
|
* @param provider The cloud provider name (e.g., "linode")
|
||||||
|
* @param apiToken The API token for the cloud provider
|
||||||
|
* @param firewallId The ID of the firewall to manage
|
||||||
|
* @param internalTcpPort The internal TCP port the proxy is listening on
|
||||||
|
* @param externalTcpPort The external TCP port to expose in the firewall (defaults to same as internal)
|
||||||
|
* @param additionalParams JSON string with additional provider-specific parameters
|
||||||
|
* @return A unique pointer to a CloudFirewallManager instance, or nullptr if provider is not supported
|
||||||
|
*/
|
||||||
|
static std::unique_ptr<CloudFirewallManager> createFirewallManager(
|
||||||
|
const std::string& provider,
|
||||||
|
const std::string& apiToken,
|
||||||
|
const std::string& firewallId,
|
||||||
|
uint16_t internalTcpPort = 443,
|
||||||
|
uint16_t externalTcpPort = 0,
|
||||||
|
const std::string& additionalParams = "") {
|
||||||
|
|
||||||
|
// If external port is not specified, use the same as internal port
|
||||||
|
if (externalTcpPort == 0) {
|
||||||
|
externalTcpPort = internalTcpPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provider == "linode") {
|
||||||
|
// C++11 compatible way to create a unique_ptr
|
||||||
|
return std::unique_ptr<CloudFirewallManager>(new LinodeFirewallManager(apiToken, firewallId, internalTcpPort, externalTcpPort));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add support for other cloud providers here
|
||||||
|
// Example:
|
||||||
|
// if (provider == "aws") {
|
||||||
|
// return std::make_unique<AWSFirewallManager>(apiToken, firewallId, internalTcpPort, externalTcpPort, additionalParams);
|
||||||
|
// }
|
||||||
|
|
||||||
|
return nullptr; // Provider not supported
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // FIREWALL_MANAGER_FACTORY_HPP
|
753
tcp-proxy/cloud/provider/LinodeFirewallManager.cpp
Normal file
753
tcp-proxy/cloud/provider/LinodeFirewallManager.cpp
Normal file
|
@ -0,0 +1,753 @@
|
||||||
|
/*
|
||||||
|
* ZeroTier One - Network Virtualization Everywhere
|
||||||
|
* Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "LinodeFirewallManager.hpp"
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
// External function for logging with timestamp
|
||||||
|
extern void datetime_fprintf(FILE *stream, const char *fmt, ...) __attribute__((format(printf,2,3)));
|
||||||
|
#define datetime_printf(...) datetime_fprintf(stderr, __VA_ARGS__)
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
LinodeFirewallManager::LinodeFirewallManager(const std::string& token, const std::string& id, uint16_t internalPort, uint16_t externalPort)
|
||||||
|
: apiToken(token),
|
||||||
|
firewallId(id),
|
||||||
|
initialized(false),
|
||||||
|
internalTcpPort(internalPort),
|
||||||
|
externalTcpPort(externalPort) {
|
||||||
|
// If external port is not specified, use the same as internal port
|
||||||
|
if (externalTcpPort == 0) {
|
||||||
|
externalTcpPort = internalTcpPort;
|
||||||
|
}
|
||||||
|
// Initialize curl globally (should be called once per application)
|
||||||
|
curl_global_init(CURL_GLOBAL_DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destructor
|
||||||
|
LinodeFirewallManager::~LinodeFirewallManager() {
|
||||||
|
// Clean up curl
|
||||||
|
curl_global_cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Static callback function for curl to write response data
|
||||||
|
size_t LinodeFirewallManager::WriteCallback(void* contents, size_t size, size_t nmemb, std::string* s) {
|
||||||
|
size_t newLength = size * nmemb;
|
||||||
|
try {
|
||||||
|
s->append((char*)contents, newLength);
|
||||||
|
return newLength;
|
||||||
|
} catch(std::bad_alloc& e) {
|
||||||
|
// Handle memory problem
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform GET request to Linode API
|
||||||
|
bool LinodeFirewallManager::performGetRequest(const std::string& url, std::string& response) {
|
||||||
|
CURL* curl = curl_easy_init();
|
||||||
|
if (!curl) {
|
||||||
|
datetime_printf("Failed to initialize curl for GET request\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct curl_slist* headers = NULL;
|
||||||
|
headers = curl_slist_append(headers, ("Authorization: Bearer " + apiToken).c_str());
|
||||||
|
headers = curl_slist_append(headers, "Content-Type: application/json");
|
||||||
|
|
||||||
|
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||||
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L); // 10 seconds timeout
|
||||||
|
|
||||||
|
CURLcode res = curl_easy_perform(curl);
|
||||||
|
long http_code = 0;
|
||||||
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
|
||||||
|
|
||||||
|
curl_slist_free_all(headers);
|
||||||
|
curl_easy_cleanup(curl);
|
||||||
|
|
||||||
|
if (res != CURLE_OK) {
|
||||||
|
datetime_printf("GET request failed: %s\n", curl_easy_strerror(res));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (http_code < 200 || http_code >= 300) {
|
||||||
|
datetime_printf("GET request returned HTTP error: %ld\n", http_code);
|
||||||
|
datetime_printf("Response: %s\n", response.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform PUT request to Linode API
|
||||||
|
bool LinodeFirewallManager::performPutRequest(const std::string& url, const std::string& data, std::string& response) {
|
||||||
|
CURL* curl = curl_easy_init();
|
||||||
|
if (!curl) {
|
||||||
|
datetime_printf("Failed to initialize curl for PUT request\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct curl_slist* headers = NULL;
|
||||||
|
headers = curl_slist_append(headers, ("Authorization: Bearer " + apiToken).c_str());
|
||||||
|
headers = curl_slist_append(headers, "Content-Type: application/json");
|
||||||
|
|
||||||
|
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||||
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
|
||||||
|
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str());
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L); // 10 seconds timeout
|
||||||
|
|
||||||
|
CURLcode res = curl_easy_perform(curl);
|
||||||
|
long http_code = 0;
|
||||||
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
|
||||||
|
|
||||||
|
curl_slist_free_all(headers);
|
||||||
|
curl_easy_cleanup(curl);
|
||||||
|
|
||||||
|
if (res != CURLE_OK) {
|
||||||
|
datetime_printf("PUT request failed: %s\n", curl_easy_strerror(res));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (http_code < 200 || http_code >= 300) {
|
||||||
|
datetime_printf("PUT request returned HTTP error: %ld\n", http_code);
|
||||||
|
datetime_printf("Response: %s\n", response.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the firewall manager
|
||||||
|
bool LinodeFirewallManager::initialize() {
|
||||||
|
std::lock_guard<std::mutex> lock(apiMutex);
|
||||||
|
|
||||||
|
datetime_printf("Starting Linode Firewall Manager initialization\n");
|
||||||
|
|
||||||
|
if (apiToken.empty()) {
|
||||||
|
datetime_printf("Error: API token is empty\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firewallId.empty()) {
|
||||||
|
datetime_printf("Error: Firewall ID is empty\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
datetime_printf("Initializing Linode Firewall Manager for firewall ID: %s\n", firewallId.c_str());
|
||||||
|
datetime_printf("API token length: %zu characters\n", apiToken.length());
|
||||||
|
|
||||||
|
// Fetch current rules
|
||||||
|
datetime_printf("Fetching current firewall rules...\n");
|
||||||
|
if (!fetchCurrentRules()) {
|
||||||
|
datetime_printf("Failed to fetch current firewall rules\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
initialized = true;
|
||||||
|
datetime_printf("Linode Firewall Manager initialized successfully with %zu active UDP ports\n", activePorts.size());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch current firewall rules from Linode API
|
||||||
|
bool LinodeFirewallManager::fetchCurrentRules() {
|
||||||
|
std::string url = "https://api.linode.com/v4/networking/firewalls/" + firewallId + "/rules";
|
||||||
|
std::string response;
|
||||||
|
|
||||||
|
if (!performGetRequest(url, response)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
json rulesJson = json::parse(response);
|
||||||
|
|
||||||
|
// Extract fingerprint for future updates
|
||||||
|
if (rulesJson.contains("fingerprint")) {
|
||||||
|
fingerprint = rulesJson["fingerprint"];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse existing UDP ports
|
||||||
|
return parseExistingPorts(rulesJson);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
datetime_printf("Error parsing firewall rules JSON: %s\n", e.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse existing rules to extract UDP ports and store original rules
|
||||||
|
bool LinodeFirewallManager::parseExistingPorts(const json& rules) {
|
||||||
|
try {
|
||||||
|
// Clear current active ports
|
||||||
|
activePorts.clear();
|
||||||
|
|
||||||
|
// Store the original rules structure
|
||||||
|
originalRules = rules;
|
||||||
|
|
||||||
|
datetime_printf("Storing original firewall rules configuration\n");
|
||||||
|
|
||||||
|
// Check if inbound rules exist
|
||||||
|
if (!rules.contains("inbound") || !rules["inbound"].is_array()) {
|
||||||
|
datetime_printf("No inbound rules found, will create new rule set\n");
|
||||||
|
return true; // Not an error, just no rules
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look for all UDP rules and specifically identify our ZeroTier rule
|
||||||
|
bool foundZeroTierUdpRule = false;
|
||||||
|
int udpRuleCount = 0;
|
||||||
|
|
||||||
|
// First, log all UDP rules for better visibility during initialization
|
||||||
|
for (const auto& rule : rules["inbound"]) {
|
||||||
|
if (rule.contains("protocol") && rule["protocol"] == "UDP") {
|
||||||
|
udpRuleCount++;
|
||||||
|
std::string label = rule.contains("label") ?
|
||||||
|
rule["label"].get<std::string>() : "unlabeled";
|
||||||
|
std::string ports = rule.contains("ports") && rule["ports"].is_string() ?
|
||||||
|
rule["ports"].get<std::string>() : "unknown";
|
||||||
|
|
||||||
|
datetime_printf("Found UDP rule: %s (ports: %s)\n", label.c_str(), ports.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
datetime_printf("Total UDP rules found: %d\n", udpRuleCount);
|
||||||
|
|
||||||
|
// Now process all ZeroTier UDP rules if they exist
|
||||||
|
// We look for both the main rule "ZeroTier-UDP-Ports" and any additional rules "ZeroTier-UDP-Ports-X"
|
||||||
|
int zeroTierRuleCount = 0;
|
||||||
|
|
||||||
|
for (const auto& rule : rules["inbound"]) {
|
||||||
|
if (rule.contains("protocol") && rule["protocol"] == "UDP" &&
|
||||||
|
rule.contains("label") &&
|
||||||
|
rule.contains("ports") && rule["ports"].is_string()) {
|
||||||
|
|
||||||
|
std::string label = rule["label"];
|
||||||
|
|
||||||
|
// Check if this is one of our ZeroTier rules
|
||||||
|
// Either the main rule or a numbered rule (ZeroTier-UDP-Ports-2, ZeroTier-UDP-Ports-3, etc.)
|
||||||
|
if (label == "ZeroTier-UDP-Ports" ||
|
||||||
|
(label.find("ZeroTier-UDP-Ports-") == 0 && label.length() > 18)) {
|
||||||
|
|
||||||
|
zeroTierRuleCount++;
|
||||||
|
foundZeroTierUdpRule = true;
|
||||||
|
std::string portsStr = rule["ports"];
|
||||||
|
datetime_printf("Processing %s rule with ports: %s\n", label.c_str(), portsStr.c_str());
|
||||||
|
|
||||||
|
// Parse ports string (e.g., "9993, 9994, 9995, 9999" or "1024-65535")
|
||||||
|
std::istringstream ss(portsStr);
|
||||||
|
std::string portItem;
|
||||||
|
|
||||||
|
while (std::getline(ss, portItem, ',')) {
|
||||||
|
// Trim whitespace
|
||||||
|
portItem.erase(0, portItem.find_first_not_of(" \t\n\r\f\v"));
|
||||||
|
portItem.erase(portItem.find_last_not_of(" \t\n\r\f\v") + 1);
|
||||||
|
|
||||||
|
// Check if it's a range (e.g., "1024-65535")
|
||||||
|
size_t rangePos = portItem.find('-');
|
||||||
|
if (rangePos != std::string::npos) {
|
||||||
|
std::string startStr = portItem.substr(0, rangePos);
|
||||||
|
std::string endStr = portItem.substr(rangePos + 1);
|
||||||
|
|
||||||
|
try {
|
||||||
|
uint16_t startPort = static_cast<uint16_t>(std::stoi(startStr));
|
||||||
|
uint16_t endPort = static_cast<uint16_t>(std::stoi(endStr));
|
||||||
|
|
||||||
|
for (uint16_t p = startPort; p <= endPort; ++p) {
|
||||||
|
activePorts.insert(p);
|
||||||
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
datetime_printf("Error parsing port range: %s\n", e.what());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Single port
|
||||||
|
try {
|
||||||
|
uint16_t port = static_cast<uint16_t>(std::stoi(portItem));
|
||||||
|
activePorts.insert(port);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
datetime_printf("Error parsing port: %s\n", e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zeroTierRuleCount > 0) {
|
||||||
|
datetime_printf("Found %d ZeroTier UDP rules\n", zeroTierRuleCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!foundZeroTierUdpRule) {
|
||||||
|
datetime_printf("No ZeroTier UDP rule found, will create one when needed\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
datetime_printf("Parsed %zu active UDP ports from firewall rules\n", activePorts.size());
|
||||||
|
return true;
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
datetime_printf("Error parsing existing ports: %s\n", e.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a UDP port to the ZeroTier-UDP-Ports firewall rule
|
||||||
|
// Note: This only modifies the dedicated ZeroTier rule and preserves all other UDP rules
|
||||||
|
bool LinodeFirewallManager::addPort(uint16_t port) {
|
||||||
|
std::lock_guard<std::mutex> lock(apiMutex);
|
||||||
|
|
||||||
|
if (!initialized) {
|
||||||
|
datetime_printf("Firewall manager not initialized\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if port is already in the set
|
||||||
|
if (activePorts.find(port) != activePorts.end()) {
|
||||||
|
// Port already added, no need to update
|
||||||
|
datetime_printf("UDP port %d is already in the ZeroTier-UDP-Ports rules\n", port);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
datetime_printf("Adding UDP port %d to ZeroTier-UDP-Ports rules (other UDP rules remain unchanged)\n", port);
|
||||||
|
activePorts.insert(port);
|
||||||
|
|
||||||
|
return updateRules();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove a UDP port from the ZeroTier-UDP-Ports firewall rule
|
||||||
|
// Note: This only modifies the dedicated ZeroTier rule and preserves all other UDP rules
|
||||||
|
bool LinodeFirewallManager::removePort(uint16_t port) {
|
||||||
|
std::lock_guard<std::mutex> lock(apiMutex);
|
||||||
|
|
||||||
|
if (!initialized) {
|
||||||
|
datetime_printf("Firewall manager not initialized\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if port is in the set
|
||||||
|
if (activePorts.find(port) == activePorts.end()) {
|
||||||
|
// Port not in set, no need to update
|
||||||
|
datetime_printf("UDP port %d is not in the ZeroTier-UDP-Ports rules\n", port);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
datetime_printf("Removing UDP port %d from ZeroTier-UDP-Ports rules (other UDP rules remain unchanged)\n", port);
|
||||||
|
activePorts.erase(port);
|
||||||
|
|
||||||
|
return updateRules();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force synchronization of firewall rules with Linode
|
||||||
|
// Note: This preserves all existing non-ZeroTier UDP rules
|
||||||
|
bool LinodeFirewallManager::syncFirewallRules() {
|
||||||
|
std::lock_guard<std::mutex> lock(apiMutex);
|
||||||
|
|
||||||
|
if (!initialized) {
|
||||||
|
datetime_printf("Firewall manager not initialized\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch current rules first to ensure we have the latest fingerprint
|
||||||
|
// This also ensures we have the latest set of non-ZeroTier rules to preserve
|
||||||
|
if (!fetchCurrentRules()) {
|
||||||
|
datetime_printf("Failed to fetch current rules during sync\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
datetime_printf("Synchronizing firewall rules (preserving all non-ZeroTier UDP rules)\n");
|
||||||
|
return updateRules();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update firewall rules via Linode API
|
||||||
|
// This function preserves all existing non-ZeroTier UDP rules
|
||||||
|
// It only modifies the dedicated "ZeroTier-UDP-Ports" rules (including any numbered variants)
|
||||||
|
bool LinodeFirewallManager::updateRules() {
|
||||||
|
std::string url = "https://api.linode.com/v4/networking/firewalls/" + firewallId + "/rules";
|
||||||
|
std::string jsonData = buildRulesJson();
|
||||||
|
std::string response;
|
||||||
|
|
||||||
|
datetime_printf("Updating firewall rules with %zu ZeroTier UDP ports (preserving all other UDP rules)\n", activePorts.size());
|
||||||
|
|
||||||
|
if (!performPutRequest(url, jsonData, response)) {
|
||||||
|
datetime_printf("Failed to update firewall rules\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
json responseJson = json::parse(response);
|
||||||
|
|
||||||
|
// Update fingerprint for future updates
|
||||||
|
if (responseJson.contains("fingerprint")) {
|
||||||
|
std::string newFingerprint = responseJson["fingerprint"];
|
||||||
|
datetime_printf("Received new fingerprint: %s\n", newFingerprint.c_str());
|
||||||
|
fingerprint = newFingerprint;
|
||||||
|
} else {
|
||||||
|
datetime_printf("Warning: No fingerprint in response\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log inbound rules count for verification
|
||||||
|
if (responseJson.contains("inbound") && responseJson["inbound"].is_array()) {
|
||||||
|
datetime_printf("Updated firewall has %zu inbound rules\n", responseJson["inbound"].size());
|
||||||
|
}
|
||||||
|
|
||||||
|
datetime_printf("Firewall rules updated successfully\n");
|
||||||
|
return true;
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
datetime_printf("Error parsing update response: %s\n", e.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert ports to ranges for optimization
|
||||||
|
std::vector<std::pair<uint16_t, uint16_t>> LinodeFirewallManager::portsToRanges(const std::set<uint16_t>& ports) {
|
||||||
|
std::vector<std::pair<uint16_t, uint16_t>> ranges;
|
||||||
|
|
||||||
|
if (ports.empty()) {
|
||||||
|
return ranges;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto it = ports.begin();
|
||||||
|
uint16_t rangeStart = *it;
|
||||||
|
uint16_t rangeEnd = *it;
|
||||||
|
|
||||||
|
++it;
|
||||||
|
while (it != ports.end()) {
|
||||||
|
if (*it == rangeEnd + 1) {
|
||||||
|
// Extend current range
|
||||||
|
rangeEnd = *it;
|
||||||
|
} else {
|
||||||
|
// End current range and start a new one
|
||||||
|
ranges.push_back({rangeStart, rangeEnd});
|
||||||
|
rangeStart = *it;
|
||||||
|
rangeEnd = *it;
|
||||||
|
}
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the last range
|
||||||
|
ranges.push_back({rangeStart, rangeEnd});
|
||||||
|
|
||||||
|
return ranges;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count the number of pieces in a port set (single port = 1 piece, range = 2 pieces)
|
||||||
|
int LinodeFirewallManager::countPortPieces(const std::vector<std::pair<uint16_t, uint16_t>>& ranges) {
|
||||||
|
int pieces = 0;
|
||||||
|
|
||||||
|
for (const auto& range : ranges) {
|
||||||
|
if (range.first == range.second) {
|
||||||
|
// Single port counts as 1 piece
|
||||||
|
pieces += 1;
|
||||||
|
} else {
|
||||||
|
// Range counts as 2 pieces
|
||||||
|
pieces += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pieces;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert port set to string format for API
|
||||||
|
std::string LinodeFirewallManager::portsToString(const std::set<uint16_t>& ports) {
|
||||||
|
if (ports.empty()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert ports to ranges
|
||||||
|
std::vector<std::pair<uint16_t, uint16_t>> ranges = portsToRanges(ports);
|
||||||
|
|
||||||
|
// Count pieces to check against Linode's limit
|
||||||
|
int pieces = countPortPieces(ranges);
|
||||||
|
if (pieces > MAX_PORT_PIECES_PER_RULE) {
|
||||||
|
datetime_printf("Warning: Port specification exceeds Linode's %d-piece limit (%d pieces)\n",
|
||||||
|
MAX_PORT_PIECES_PER_RULE, pieces);
|
||||||
|
// Note: We'll still generate the string, but it may be rejected by the API
|
||||||
|
// The splitPortsIntoGroups method should be used to avoid this situation
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the string
|
||||||
|
std::ostringstream oss;
|
||||||
|
for (size_t i = 0; i < ranges.size(); ++i) {
|
||||||
|
if (i > 0) {
|
||||||
|
oss << ", ";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ranges[i].first == ranges[i].second) {
|
||||||
|
// Single port
|
||||||
|
oss << ranges[i].first;
|
||||||
|
} else {
|
||||||
|
// Port range
|
||||||
|
oss << ranges[i].first << "-" << ranges[i].second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return oss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split ports into multiple groups to stay within the piece limit
|
||||||
|
std::vector<std::set<uint16_t>> LinodeFirewallManager::splitPortsIntoGroups(const std::set<uint16_t>& ports) {
|
||||||
|
std::vector<std::set<uint16_t>> groups;
|
||||||
|
|
||||||
|
if (ports.empty()) {
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
// First, try to optimize by converting to ranges
|
||||||
|
std::vector<std::pair<uint16_t, uint16_t>> allRanges = portsToRanges(ports);
|
||||||
|
|
||||||
|
// Debug: Print all ranges and their piece counts
|
||||||
|
datetime_printf("DEBUG: Port ranges before splitting:\n");
|
||||||
|
int totalPiecesBeforeSplit = 0;
|
||||||
|
for (const auto& range : allRanges) {
|
||||||
|
int rangePieces = (range.first == range.second) ? 1 : 2;
|
||||||
|
totalPiecesBeforeSplit += rangePieces;
|
||||||
|
if (range.first == range.second) {
|
||||||
|
datetime_printf(" Single port: %d (1 piece)\n", range.first);
|
||||||
|
} else {
|
||||||
|
datetime_printf(" Port range: %d-%d (2 pieces)\n", range.first, range.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
datetime_printf("DEBUG: Total pieces before splitting: %d (MAX_PORT_PIECES_PER_RULE: %d)\n",
|
||||||
|
totalPiecesBeforeSplit, MAX_PORT_PIECES_PER_RULE);
|
||||||
|
|
||||||
|
// Create groups that stay within the piece limit
|
||||||
|
std::set<uint16_t> currentGroup;
|
||||||
|
int currentPieces = 0;
|
||||||
|
int groupNumber = 1;
|
||||||
|
|
||||||
|
for (const auto& range : allRanges) {
|
||||||
|
// Calculate how many pieces this range would add
|
||||||
|
int rangePieces = (range.first == range.second) ? 1 : 2;
|
||||||
|
|
||||||
|
// If adding this range would exceed the limit, start a new group
|
||||||
|
if (currentPieces + rangePieces > MAX_PORT_PIECES_PER_RULE) {
|
||||||
|
if (!currentGroup.empty()) {
|
||||||
|
datetime_printf("DEBUG: Group %d has %d pieces, starting new group\n",
|
||||||
|
groupNumber, currentPieces);
|
||||||
|
groups.push_back(currentGroup);
|
||||||
|
currentGroup.clear();
|
||||||
|
currentPieces = 0;
|
||||||
|
groupNumber++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the range to the current group
|
||||||
|
if (range.first == range.second) {
|
||||||
|
// Single port
|
||||||
|
currentGroup.insert(range.first);
|
||||||
|
datetime_printf("DEBUG: Adding single port %d to group %d (now %d pieces)\n",
|
||||||
|
range.first, groupNumber, currentPieces + rangePieces);
|
||||||
|
} else {
|
||||||
|
// Port range - add all ports in the range
|
||||||
|
datetime_printf("DEBUG: Adding port range %d-%d to group %d (now %d pieces)\n",
|
||||||
|
range.first, range.second, groupNumber, currentPieces + rangePieces);
|
||||||
|
for (uint16_t p = range.first; p <= range.second; ++p) {
|
||||||
|
currentGroup.insert(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update piece count
|
||||||
|
currentPieces += rangePieces;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the last group if not empty
|
||||||
|
if (!currentGroup.empty()) {
|
||||||
|
datetime_printf("DEBUG: Final group %d has %d pieces\n", groupNumber, currentPieces);
|
||||||
|
groups.push_back(currentGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
datetime_printf("Split %zu ports into %zu groups to stay within %d-piece limit\n",
|
||||||
|
ports.size(), groups.size(), MAX_PORT_PIECES_PER_RULE);
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build JSON for updating firewall rules
|
||||||
|
// This function carefully preserves all existing firewall rules except for the ZeroTier-UDP-Ports rule
|
||||||
|
// It ensures that any pre-existing UDP rules for other services remain untouched
|
||||||
|
std::string LinodeFirewallManager::buildRulesJson() {
|
||||||
|
json rulesJson;
|
||||||
|
|
||||||
|
// Start with the original rules structure if available
|
||||||
|
if (originalRules.contains("inbound_policy")) {
|
||||||
|
rulesJson["inbound_policy"] = originalRules["inbound_policy"];
|
||||||
|
} else {
|
||||||
|
rulesJson["inbound_policy"] = "DROP";
|
||||||
|
datetime_printf("No inbound policy found, defaulting to DROP\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (originalRules.contains("outbound_policy")) {
|
||||||
|
rulesJson["outbound_policy"] = originalRules["outbound_policy"];
|
||||||
|
} else {
|
||||||
|
rulesJson["outbound_policy"] = "ACCEPT";
|
||||||
|
datetime_printf("No outbound policy found, defaulting to ACCEPT\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create inbound rules array
|
||||||
|
json inboundRules = json::array();
|
||||||
|
|
||||||
|
// First, copy all non-ZeroTier UDP rules from the original configuration
|
||||||
|
// IMPORTANT: We preserve ALL existing rules EXCEPT the specific "ZeroTier-UDP-Ports" rules
|
||||||
|
// This ensures that any pre-existing UDP rules (for other services) remain untouched
|
||||||
|
bool hasTcp443Rule = false;
|
||||||
|
if (originalRules.contains("inbound") && originalRules["inbound"].is_array()) {
|
||||||
|
datetime_printf("Preserving existing firewall rules while updating ZeroTier rules\n");
|
||||||
|
for (const auto& rule : originalRules["inbound"]) {
|
||||||
|
// Skip all ZeroTier UDP rules as we'll add updated ones
|
||||||
|
if (rule.contains("protocol") && rule["protocol"] == "UDP" &&
|
||||||
|
rule.contains("label")) {
|
||||||
|
|
||||||
|
std::string label = rule["label"];
|
||||||
|
if (label == "ZeroTier-UDP-Ports" ||
|
||||||
|
(label.find("ZeroTier-UDP-Ports-") == 0 && label.length() > 18)) {
|
||||||
|
datetime_printf("Found existing %s rule, will update it\n", label.c_str());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log preservation of UDP rules explicitly for better visibility
|
||||||
|
if (rule.contains("protocol") && rule["protocol"] == "UDP") {
|
||||||
|
std::string label = rule.contains("label") ?
|
||||||
|
rule["label"].get<std::string>() : "unlabeled";
|
||||||
|
std::string ports = rule.contains("ports") && rule["ports"].is_string() ?
|
||||||
|
rule["ports"].get<std::string>() : "unknown";
|
||||||
|
datetime_printf("Preserving existing UDP rule: %s (ports: %s)\n",
|
||||||
|
label.c_str(), ports.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if there's already a ZeroTier TCP Proxy rule by label
|
||||||
|
if (rule.contains("label") && rule["label"] == "ZeroTier-TCP-Proxy-Port") {
|
||||||
|
datetime_printf("Found existing ZeroTier TCP Proxy rule\n");
|
||||||
|
hasTcp443Rule = true;
|
||||||
|
|
||||||
|
// Check if the port in the rule matches our configured port
|
||||||
|
if (rule.contains("protocol") && rule["protocol"] == "TCP" &&
|
||||||
|
rule.contains("ports") && rule["ports"].is_string()) {
|
||||||
|
std::string portsStr = rule["ports"];
|
||||||
|
std::string tcpPortStr = std::to_string(externalTcpPort);
|
||||||
|
|
||||||
|
if (portsStr != tcpPortStr) {
|
||||||
|
// Update the port in the existing rule
|
||||||
|
datetime_printf("Updating TCP port from %s to %s in existing rule\n",
|
||||||
|
portsStr.c_str(), tcpPortStr.c_str());
|
||||||
|
json updatedRule = rule;
|
||||||
|
updatedRule["ports"] = tcpPortStr;
|
||||||
|
updatedRule["description"] = "TCP port " + tcpPortStr + " for ZeroTier TCP Proxy (internal port: " + std::to_string(internalTcpPort) + ")";
|
||||||
|
inboundRules.push_back(updatedRule);
|
||||||
|
continue; // Skip adding the original rule
|
||||||
|
} else {
|
||||||
|
datetime_printf("TCP port %d already configured correctly\n", externalTcpPort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Also check for legacy TCP rules with the old naming format
|
||||||
|
else if (rule.contains("protocol") && rule["protocol"] == "TCP" &&
|
||||||
|
rule.contains("ports") && rule["ports"].is_string()) {
|
||||||
|
std::string portsStr = rule["ports"];
|
||||||
|
std::string tcpPortStr = std::to_string(externalTcpPort);
|
||||||
|
if (portsStr == tcpPortStr || portsStr.find(tcpPortStr) != std::string::npos) {
|
||||||
|
datetime_printf("Found existing TCP %d rule with legacy format, will preserve it\n", externalTcpPort);
|
||||||
|
hasTcp443Rule = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add this rule to our new configuration
|
||||||
|
inboundRules.push_back(rule);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
datetime_printf("No existing inbound rules found, creating new rule set\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add UDP rules with our ports if we have any
|
||||||
|
if (!activePorts.empty()) {
|
||||||
|
// Split ports into groups to stay within the 15-piece limit
|
||||||
|
std::vector<std::set<uint16_t>> portGroups = splitPortsIntoGroups(activePorts);
|
||||||
|
|
||||||
|
// Calculate total pieces for debugging
|
||||||
|
std::vector<std::pair<uint16_t, uint16_t>> allRanges = portsToRanges(activePorts);
|
||||||
|
int totalPieces = countPortPieces(allRanges);
|
||||||
|
datetime_printf("DEBUG: Total ports: %zu, Total pieces: %d, MAX_PORT_PIECES_PER_RULE: %d\n",
|
||||||
|
activePorts.size(), totalPieces, MAX_PORT_PIECES_PER_RULE);
|
||||||
|
|
||||||
|
datetime_printf("Adding %zu ZeroTier UDP rule(s) for %zu ports\n",
|
||||||
|
portGroups.size(), activePorts.size());
|
||||||
|
|
||||||
|
// Create a rule for each group
|
||||||
|
for (size_t i = 0; i < portGroups.size(); ++i) {
|
||||||
|
std::string portsString = portsToString(portGroups[i]);
|
||||||
|
std::string ruleName = "ZeroTier-UDP-Ports";
|
||||||
|
|
||||||
|
// Add suffix for additional rules
|
||||||
|
if (i > 0) {
|
||||||
|
ruleName += "-" + std::to_string(i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
datetime_printf("Adding %s rule with ports: %s\n", ruleName.c_str(), portsString.c_str());
|
||||||
|
|
||||||
|
json udpRule;
|
||||||
|
udpRule["protocol"] = "UDP";
|
||||||
|
udpRule["ports"] = portsString;
|
||||||
|
udpRule["addresses"] = json::object();
|
||||||
|
udpRule["addresses"]["ipv4"] = json::array({"0.0.0.0/0"});
|
||||||
|
udpRule["addresses"]["ipv6"] = json::array({"::/0"});
|
||||||
|
udpRule["action"] = "ACCEPT";
|
||||||
|
udpRule["label"] = ruleName;
|
||||||
|
udpRule["description"] = "Dynamically managed UDP ports for ZeroTier TCP Proxy";
|
||||||
|
|
||||||
|
inboundRules.push_back(udpRule);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
datetime_printf("No active UDP ports to add to firewall rules\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add TCP rule for our configured port if it doesn't exist already
|
||||||
|
if (!hasTcp443Rule) {
|
||||||
|
datetime_printf("Adding TCP %d rule with fixed label 'ZeroTier-TCP-Proxy-Port' for ZeroTier TCP Proxy\n", externalTcpPort);
|
||||||
|
|
||||||
|
json tcpRule;
|
||||||
|
tcpRule["protocol"] = "TCP";
|
||||||
|
tcpRule["ports"] = std::to_string(externalTcpPort);
|
||||||
|
tcpRule["addresses"] = json::object();
|
||||||
|
tcpRule["addresses"]["ipv4"] = json::array({"0.0.0.0/0"});
|
||||||
|
tcpRule["addresses"]["ipv6"] = json::array({"::/0"});
|
||||||
|
tcpRule["action"] = "ACCEPT";
|
||||||
|
tcpRule["label"] = "ZeroTier-TCP-Proxy-Port";
|
||||||
|
tcpRule["description"] = "TCP port " + std::to_string(externalTcpPort) + " for ZeroTier TCP Proxy" +
|
||||||
|
(internalTcpPort != externalTcpPort ? " (internal port: " + std::to_string(internalTcpPort) + ")" : "");
|
||||||
|
|
||||||
|
inboundRules.push_back(tcpRule);
|
||||||
|
} else {
|
||||||
|
datetime_printf("Using existing TCP %d rule\n", externalTcpPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
rulesJson["inbound"] = inboundRules;
|
||||||
|
datetime_printf("Total inbound rules: %zu\n", inboundRules.size());
|
||||||
|
|
||||||
|
// Copy outbound rules from original configuration
|
||||||
|
if (originalRules.contains("outbound") && originalRules["outbound"].is_array()) {
|
||||||
|
rulesJson["outbound"] = originalRules["outbound"];
|
||||||
|
datetime_printf("Preserving %zu existing outbound rules\n", originalRules["outbound"].size());
|
||||||
|
} else {
|
||||||
|
rulesJson["outbound"] = json::array();
|
||||||
|
datetime_printf("No existing outbound rules found, using empty array\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
datetime_printf("Firewall rules JSON prepared for update\n");
|
||||||
|
return rulesJson.dump();
|
||||||
|
}
|
141
tcp-proxy/cloud/provider/LinodeFirewallManager.hpp
Normal file
141
tcp-proxy/cloud/provider/LinodeFirewallManager.hpp
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
/*
|
||||||
|
* ZeroTier One - Network Virtualization Everywhere
|
||||||
|
* Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef LINODE_FIREWALL_MANAGER_HPP
|
||||||
|
#define LINODE_FIREWALL_MANAGER_HPP
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <set>
|
||||||
|
#include <mutex>
|
||||||
|
#include <curl/curl.h>
|
||||||
|
#include "../../../ext/nlohmann/json.hpp"
|
||||||
|
#include "CloudFirewallManager.hpp"
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LinodeFirewallManager
|
||||||
|
*
|
||||||
|
* This class manages Linode Cloud Firewall rules via the Linode API.
|
||||||
|
* It allows dynamic addition and removal of UDP ports to the firewall rules
|
||||||
|
* as clients connect and disconnect from the ZeroTier TCP Proxy.
|
||||||
|
*
|
||||||
|
* IMPORTANT: This class only modifies dedicated "ZeroTier-UDP-Ports" rules
|
||||||
|
* and carefully preserves all other existing firewall rules, including any
|
||||||
|
* pre-existing UDP rules for other services. It will never modify or remove
|
||||||
|
* UDP rules that don't have the specific "ZeroTier-UDP-Ports" label.
|
||||||
|
*
|
||||||
|
* To comply with Linode's 15-piece limit per rule, this class automatically
|
||||||
|
* splits ports across multiple rules ("ZeroTier-UDP-Ports", "ZeroTier-UDP-Ports-2", etc.)
|
||||||
|
* when necessary. A single port counts as 1 piece, and a port range counts as 2 pieces.
|
||||||
|
*/
|
||||||
|
class LinodeFirewallManager : public CloudFirewallManager {
|
||||||
|
private:
|
||||||
|
std::string apiToken; // Linode API token
|
||||||
|
std::string firewallId; // ID of the Linode Firewall to manage
|
||||||
|
std::set<uint16_t> activePorts; // Currently active UDP ports
|
||||||
|
std::string fingerprint; // Current firewall rules fingerprint for updates
|
||||||
|
bool initialized; // Whether initialization was successful
|
||||||
|
std::mutex apiMutex; // Mutex for thread safety during API operations
|
||||||
|
json originalRules; // Original firewall rules structure to preserve non-ZeroTier rules
|
||||||
|
uint16_t internalTcpPort; // Internal TCP port for ZeroTier TCP Proxy (default: 443)
|
||||||
|
uint16_t externalTcpPort; // External TCP port exposed in firewall (default: same as internal)
|
||||||
|
|
||||||
|
// Constants for Linode API limitations
|
||||||
|
static const int MAX_PORT_PIECES_PER_RULE = 15; // Maximum number of port pieces per rule
|
||||||
|
static const int MAX_RULES = 25; // Maximum number of rules per firewall
|
||||||
|
|
||||||
|
// Curl helpers
|
||||||
|
static size_t WriteCallback(void* contents, size_t size, size_t nmemb, std::string* s);
|
||||||
|
bool performGetRequest(const std::string& url, std::string& response);
|
||||||
|
bool performPutRequest(const std::string& url, const std::string& data, std::string& response);
|
||||||
|
|
||||||
|
// Rule management
|
||||||
|
bool fetchCurrentRules();
|
||||||
|
bool updateRules();
|
||||||
|
std::string buildRulesJson();
|
||||||
|
|
||||||
|
// Parse existing rules to extract UDP ports
|
||||||
|
bool parseExistingPorts(const json& rules);
|
||||||
|
|
||||||
|
// Convert port set to string format for API
|
||||||
|
std::string portsToString(const std::set<uint16_t>& ports);
|
||||||
|
|
||||||
|
// Count the number of pieces in a port set (single port = 1 piece, range = 2 pieces)
|
||||||
|
int countPortPieces(const std::vector<std::pair<uint16_t, uint16_t>>& ranges);
|
||||||
|
|
||||||
|
// Split ports into multiple groups to stay within the piece limit
|
||||||
|
std::vector<std::set<uint16_t>> splitPortsIntoGroups(const std::set<uint16_t>& ports);
|
||||||
|
|
||||||
|
// Convert ports to ranges for optimization
|
||||||
|
std::vector<std::pair<uint16_t, uint16_t>> portsToRanges(const std::set<uint16_t>& ports);
|
||||||
|
|
||||||
|
public:
|
||||||
|
LinodeFirewallManager(const std::string& token, const std::string& id, uint16_t internalPort = 443, uint16_t externalPort = 0);
|
||||||
|
~LinodeFirewallManager();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the firewall manager
|
||||||
|
* Fetches current rules and prepares for updates
|
||||||
|
*
|
||||||
|
* During initialization, all existing firewall rules are preserved.
|
||||||
|
* The manager will identify any existing "ZeroTier-UDP-Ports" rule
|
||||||
|
* and will only modify that specific rule in future operations.
|
||||||
|
* All other UDP rules will remain untouched.
|
||||||
|
*
|
||||||
|
* @return true if initialization was successful, false otherwise
|
||||||
|
*/
|
||||||
|
bool initialize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a UDP port to the ZeroTier-UDP-Ports firewall rules
|
||||||
|
*
|
||||||
|
* This only modifies the dedicated ZeroTier rules and preserves all other UDP rules.
|
||||||
|
* If necessary, ports will be split across multiple rules to comply with Linode's
|
||||||
|
* 15-piece limit per rule.
|
||||||
|
*
|
||||||
|
* @param port UDP port to add
|
||||||
|
* @return true if successful, false otherwise
|
||||||
|
*/
|
||||||
|
bool addPort(uint16_t port);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a UDP port from the ZeroTier-UDP-Ports firewall rules
|
||||||
|
*
|
||||||
|
* This only modifies the dedicated ZeroTier rules and preserves all other UDP rules.
|
||||||
|
* After removal, the rules may be reorganized to optimize port groupings.
|
||||||
|
*
|
||||||
|
* @param port UDP port to remove
|
||||||
|
* @return true if successful, false otherwise
|
||||||
|
*/
|
||||||
|
bool removePort(uint16_t port);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force synchronization of firewall rules with Linode
|
||||||
|
*
|
||||||
|
* This preserves all existing non-ZeroTier UDP rules and only updates
|
||||||
|
* the dedicated ZeroTier-UDP-Ports rules. If necessary, ports will be split
|
||||||
|
* across multiple rules ("ZeroTier-UDP-Ports", "ZeroTier-UDP-Ports-2", etc.)
|
||||||
|
* to comply with Linode's 15-piece limit per rule.
|
||||||
|
*
|
||||||
|
* @return true if successful, false otherwise
|
||||||
|
*/
|
||||||
|
bool syncFirewallRules();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // LINODE_FIREWALL_MANAGER_HPP
|
|
@ -0,0 +1,127 @@
|
||||||
|
/**
|
||||||
|
* FirewallManagerFactory_example.cpp
|
||||||
|
*
|
||||||
|
* Example implementation of FirewallManagerFactory with support for multiple cloud providers
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "FirewallManagerFactory_example.hpp"
|
||||||
|
#include "../../../ext/nlohmann/json.hpp"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
// For JSON parsing
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
// External function for logging (defined in tcp-proxy.cpp)
|
||||||
|
extern void datetime_fprintf(FILE *stream, const char *fmt, ...);
|
||||||
|
|
||||||
|
std::unique_ptr<CloudFirewallManager> FirewallManagerFactory::createFirewallManager(
|
||||||
|
const std::string& provider,
|
||||||
|
const std::string& apiToken,
|
||||||
|
const std::string& firewallId,
|
||||||
|
const std::string& additionalParams) {
|
||||||
|
|
||||||
|
datetime_fprintf(stdout, "Creating firewall manager for provider: %s\n", provider.c_str());
|
||||||
|
|
||||||
|
// Parse additional parameters if provided
|
||||||
|
json params;
|
||||||
|
try {
|
||||||
|
if (!additionalParams.empty()) {
|
||||||
|
params = json::parse(additionalParams);
|
||||||
|
}
|
||||||
|
} catch (json::parse_error& e) {
|
||||||
|
datetime_fprintf(stderr, "Failed to parse additional parameters: %s\n", e.what());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the appropriate firewall manager based on the provider
|
||||||
|
if (provider == "linode") {
|
||||||
|
return std::unique_ptr<CloudFirewallManager>(new LinodeFirewallManager(apiToken, firewallId));
|
||||||
|
}
|
||||||
|
else if (provider == "aws") {
|
||||||
|
// Extract AWS-specific parameters
|
||||||
|
std::string secretKey;
|
||||||
|
std::string region = "us-east-1"; // Default region
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (params.contains("secretKey")) {
|
||||||
|
secretKey = params["secretKey"].get<std::string>();
|
||||||
|
} else {
|
||||||
|
datetime_fprintf(stderr, "AWS requires secretKey in additionalParams\n");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.contains("region")) {
|
||||||
|
region = params["region"].get<std::string>();
|
||||||
|
}
|
||||||
|
} catch (json::exception& e) {
|
||||||
|
datetime_fprintf(stderr, "Error parsing AWS parameters: %s\n", e.what());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::unique_ptr<CloudFirewallManager>(
|
||||||
|
new AwsFirewallManager(apiToken, secretKey, firewallId, region));
|
||||||
|
}
|
||||||
|
// Example for Azure implementation
|
||||||
|
else if (provider == "azure") {
|
||||||
|
// Extract Azure-specific parameters
|
||||||
|
std::string tenantId;
|
||||||
|
std::string subscriptionId;
|
||||||
|
std::string resourceGroup;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (params.contains("tenantId") && params.contains("subscriptionId") &&
|
||||||
|
params.contains("resourceGroup")) {
|
||||||
|
|
||||||
|
tenantId = params["tenantId"].get<std::string>();
|
||||||
|
subscriptionId = params["subscriptionId"].get<std::string>();
|
||||||
|
resourceGroup = params["resourceGroup"].get<std::string>();
|
||||||
|
} else {
|
||||||
|
datetime_fprintf(stderr, "Azure requires tenantId, subscriptionId, and resourceGroup in additionalParams\n");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
} catch (json::exception& e) {
|
||||||
|
datetime_fprintf(stderr, "Error parsing Azure parameters: %s\n", e.what());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uncomment when Azure implementation is available
|
||||||
|
// return std::unique_ptr<CloudFirewallManager>(
|
||||||
|
// new AzureFirewallManager(apiToken, tenantId, subscriptionId, resourceGroup, firewallId));
|
||||||
|
|
||||||
|
datetime_fprintf(stderr, "Azure provider not yet implemented\n");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
// Example for GCP implementation
|
||||||
|
else if (provider == "gcp") {
|
||||||
|
// Extract GCP-specific parameters
|
||||||
|
std::string projectId;
|
||||||
|
std::string network = "default";
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (params.contains("projectId")) {
|
||||||
|
projectId = params["projectId"].get<std::string>();
|
||||||
|
} else {
|
||||||
|
datetime_fprintf(stderr, "GCP requires projectId in additionalParams\n");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.contains("network")) {
|
||||||
|
network = params["network"].get<std::string>();
|
||||||
|
}
|
||||||
|
} catch (json::exception& e) {
|
||||||
|
datetime_fprintf(stderr, "Error parsing GCP parameters: %s\n", e.what());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uncomment when GCP implementation is available
|
||||||
|
// return std::unique_ptr<CloudFirewallManager>(
|
||||||
|
// new GcpFirewallManager(apiToken, projectId, network, firewallId));
|
||||||
|
|
||||||
|
datetime_fprintf(stderr, "GCP provider not yet implemented\n");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
datetime_fprintf(stderr, "Unsupported cloud provider: %s\n", provider.c_str());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
/**
|
||||||
|
* FirewallManagerFactory_example.hpp
|
||||||
|
*
|
||||||
|
* Example of how to extend the FirewallManagerFactory to support multiple cloud providers
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef FIREWALL_MANAGER_FACTORY_EXAMPLE_HPP
|
||||||
|
#define FIREWALL_MANAGER_FACTORY_EXAMPLE_HPP
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include "../CloudFirewallManager.hpp"
|
||||||
|
#include "../LinodeFirewallManager.hpp"
|
||||||
|
#include "../AwsFirewallManager.hpp" // Example AWS implementation
|
||||||
|
// #include "../AzureFirewallManager.hpp" // Future Azure implementation
|
||||||
|
// #include "../GcpFirewallManager.hpp" // Future GCP implementation
|
||||||
|
|
||||||
|
class FirewallManagerFactory {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Create a firewall manager for the specified cloud provider
|
||||||
|
*
|
||||||
|
* @param provider Cloud provider name (e.g., "linode", "aws", "azure", "gcp")
|
||||||
|
* @param apiToken API token or access key for the cloud provider
|
||||||
|
* @param firewallId Firewall ID or security group ID
|
||||||
|
* @param additionalParams Additional provider-specific parameters as JSON string
|
||||||
|
* @return Unique pointer to a CloudFirewallManager implementation or nullptr if provider is not supported
|
||||||
|
*/
|
||||||
|
static std::unique_ptr<CloudFirewallManager> createFirewallManager(
|
||||||
|
const std::string& provider,
|
||||||
|
const std::string& apiToken,
|
||||||
|
const std::string& firewallId,
|
||||||
|
const std::string& additionalParams = "{}");
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // FIREWALL_MANAGER_FACTORY_EXAMPLE_HPP
|
231
tcp-proxy/cloud/provider/template/CloudProviderTemplate.cpp
Normal file
231
tcp-proxy/cloud/provider/template/CloudProviderTemplate.cpp
Normal file
|
@ -0,0 +1,231 @@
|
||||||
|
/**
|
||||||
|
* CloudProviderTemplate.cpp
|
||||||
|
*
|
||||||
|
* Implementation template for a new cloud provider's firewall manager
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "CloudProviderTemplate.hpp"
|
||||||
|
#include <curl/curl.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include "../../../ext/nlohmann/json.hpp"
|
||||||
|
|
||||||
|
// For JSON parsing
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
// External function for logging (defined in tcp-proxy.cpp)
|
||||||
|
extern void datetime_fprintf(FILE *stream, const char *fmt, ...);
|
||||||
|
|
||||||
|
// Callback function for curl HTTP requests
|
||||||
|
static size_t WriteCallback(void *contents, size_t size, size_t nmemb, std::string *s) {
|
||||||
|
size_t newLength = size * nmemb;
|
||||||
|
try {
|
||||||
|
s->append((char*)contents, newLength);
|
||||||
|
return newLength;
|
||||||
|
} catch(std::bad_alloc &e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
CloudProviderFirewallManager::CloudProviderFirewallManager(const std::string& apiToken, const std::string& firewallId)
|
||||||
|
: apiToken(apiToken), firewallId(firewallId) {
|
||||||
|
// Initialization code
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the firewall manager
|
||||||
|
bool CloudProviderFirewallManager::initialize() {
|
||||||
|
datetime_fprintf(stdout, "Initializing Cloud Provider firewall manager...\n");
|
||||||
|
|
||||||
|
// Verify API credentials and firewall existence
|
||||||
|
std::string response;
|
||||||
|
if (!makeApiRequest("/firewalls/" + firewallId, "GET", "", response)) {
|
||||||
|
datetime_fprintf(stderr, "Failed to verify firewall existence\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse response to verify firewall exists
|
||||||
|
try {
|
||||||
|
json j = json::parse(response);
|
||||||
|
// Verify firewall exists and is accessible
|
||||||
|
// Customize based on your cloud provider's API response format
|
||||||
|
|
||||||
|
datetime_fprintf(stdout, "Successfully initialized Cloud Provider firewall manager\n");
|
||||||
|
return true;
|
||||||
|
} catch (json::parse_error& e) {
|
||||||
|
datetime_fprintf(stderr, "JSON parse error: %s\n", e.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a port to the managed set
|
||||||
|
bool CloudProviderFirewallManager::addPort(uint16_t port) {
|
||||||
|
datetime_fprintf(stdout, "Adding port %d to managed set\n", port);
|
||||||
|
activePorts.insert(port);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove a port from the managed set
|
||||||
|
bool CloudProviderFirewallManager::removePort(uint16_t port) {
|
||||||
|
datetime_fprintf(stdout, "Removing port %d from managed set\n", port);
|
||||||
|
activePorts.erase(port);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synchronize the current set of ports with the cloud provider
|
||||||
|
bool CloudProviderFirewallManager::syncFirewallRules() {
|
||||||
|
datetime_fprintf(stdout, "Synchronizing firewall rules with Cloud Provider...\n");
|
||||||
|
|
||||||
|
// Fetch current rules
|
||||||
|
if (!fetchCurrentRules()) {
|
||||||
|
datetime_fprintf(stderr, "Failed to fetch current firewall rules\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare the update payload
|
||||||
|
// This will vary significantly based on your cloud provider's API
|
||||||
|
json payload;
|
||||||
|
|
||||||
|
// Example: Create a rule for each port or port range
|
||||||
|
json rules = json::array();
|
||||||
|
|
||||||
|
// Convert individual ports to ranges where possible
|
||||||
|
// This is a simplified example - implement proper range consolidation
|
||||||
|
if (!activePorts.empty()) {
|
||||||
|
uint16_t rangeStart = *activePorts.begin();
|
||||||
|
uint16_t rangeEnd = rangeStart;
|
||||||
|
|
||||||
|
for (auto it = std::next(activePorts.begin()); it != activePorts.end(); ++it) {
|
||||||
|
if (*it == rangeEnd + 1) {
|
||||||
|
// Extend the current range
|
||||||
|
rangeEnd = *it;
|
||||||
|
} else {
|
||||||
|
// Add the completed range and start a new one
|
||||||
|
json rule;
|
||||||
|
rule["protocol"] = "udp";
|
||||||
|
rule["ports"] = (rangeStart == rangeEnd) ?
|
||||||
|
std::to_string(rangeStart) :
|
||||||
|
std::to_string(rangeStart) + "-" + std::to_string(rangeEnd);
|
||||||
|
rule["description"] = "ZeroTier UDP Port";
|
||||||
|
rules.push_back(rule);
|
||||||
|
|
||||||
|
rangeStart = *it;
|
||||||
|
rangeEnd = *it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the last range
|
||||||
|
json rule;
|
||||||
|
rule["protocol"] = "udp";
|
||||||
|
rule["ports"] = (rangeStart == rangeEnd) ?
|
||||||
|
std::to_string(rangeStart) :
|
||||||
|
std::to_string(rangeStart) + "-" + std::to_string(rangeEnd);
|
||||||
|
rule["description"] = "ZeroTier UDP Port";
|
||||||
|
rules.push_back(rule);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add rules to payload
|
||||||
|
payload["rules"] = rules;
|
||||||
|
|
||||||
|
// Send update to cloud provider
|
||||||
|
std::string response;
|
||||||
|
if (!makeApiRequest("/firewalls/" + firewallId + "/rules", "PUT", payload.dump(), response)) {
|
||||||
|
datetime_fprintf(stderr, "Failed to update firewall rules\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
datetime_fprintf(stdout, "Successfully synchronized firewall rules\n");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch current firewall rules from the cloud provider
|
||||||
|
bool CloudProviderFirewallManager::fetchCurrentRules() {
|
||||||
|
datetime_fprintf(stdout, "Fetching current firewall rules...\n");
|
||||||
|
|
||||||
|
std::string response;
|
||||||
|
if (!makeApiRequest("/firewalls/" + firewallId + "/rules", "GET", "", response)) {
|
||||||
|
datetime_fprintf(stderr, "Failed to fetch firewall rules\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse response to extract current rules
|
||||||
|
// This will vary based on your cloud provider's API
|
||||||
|
try {
|
||||||
|
json j = json::parse(response);
|
||||||
|
// Process rules based on your cloud provider's API response format
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (json::parse_error& e) {
|
||||||
|
datetime_fprintf(stderr, "JSON parse error: %s\n", e.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper method for making API requests to the cloud provider
|
||||||
|
bool CloudProviderFirewallManager::makeApiRequest(
|
||||||
|
const std::string& endpoint,
|
||||||
|
const std::string& method,
|
||||||
|
const std::string& data,
|
||||||
|
std::string& response) {
|
||||||
|
|
||||||
|
CURL *curl;
|
||||||
|
CURLcode res;
|
||||||
|
bool success = false;
|
||||||
|
|
||||||
|
curl = curl_easy_init();
|
||||||
|
if(curl) {
|
||||||
|
// Set the API endpoint URL
|
||||||
|
std::string url = "https://api.yourcloudprovider.com/v1" + endpoint;
|
||||||
|
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||||
|
|
||||||
|
// Set request method
|
||||||
|
if (method == "POST" || method == "PUT") {
|
||||||
|
if (method == "POST") {
|
||||||
|
curl_easy_setopt(curl, CURLOPT_POST, 1L);
|
||||||
|
} else if (method == "PUT") {
|
||||||
|
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set request body for POST/PUT
|
||||||
|
if (!data.empty()) {
|
||||||
|
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str());
|
||||||
|
}
|
||||||
|
} else if (method == "DELETE") {
|
||||||
|
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set headers
|
||||||
|
struct curl_slist *headers = NULL;
|
||||||
|
headers = curl_slist_append(headers, "Content-Type: application/json");
|
||||||
|
std::string authHeader = "Authorization: Bearer " + apiToken;
|
||||||
|
headers = curl_slist_append(headers, authHeader.c_str());
|
||||||
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
||||||
|
|
||||||
|
// Set response callback
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
|
||||||
|
|
||||||
|
// Perform the request
|
||||||
|
res = curl_easy_perform(curl);
|
||||||
|
|
||||||
|
if(res != CURLE_OK) {
|
||||||
|
datetime_fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
|
||||||
|
} else {
|
||||||
|
long http_code = 0;
|
||||||
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
|
||||||
|
|
||||||
|
if (http_code >= 200 && http_code < 300) {
|
||||||
|
success = true;
|
||||||
|
} else {
|
||||||
|
datetime_fprintf(stderr, "API request failed with HTTP code %ld: %s\n",
|
||||||
|
http_code, response.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
curl_slist_free_all(headers);
|
||||||
|
curl_easy_cleanup(curl);
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
46
tcp-proxy/cloud/provider/template/CloudProviderTemplate.hpp
Normal file
46
tcp-proxy/cloud/provider/template/CloudProviderTemplate.hpp
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
/**
|
||||||
|
* CloudProviderTemplate.hpp
|
||||||
|
*
|
||||||
|
* Template for implementing a new cloud provider's firewall manager
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CLOUD_PROVIDER_FIREWALL_MANAGER_HPP
|
||||||
|
#define CLOUD_PROVIDER_FIREWALL_MANAGER_HPP
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <set>
|
||||||
|
#include "../CloudFirewallManager.hpp"
|
||||||
|
|
||||||
|
class CloudProviderFirewallManager : public CloudFirewallManager {
|
||||||
|
private:
|
||||||
|
// Authentication credentials
|
||||||
|
std::string apiToken;
|
||||||
|
|
||||||
|
// Firewall identifier
|
||||||
|
std::string firewallId;
|
||||||
|
|
||||||
|
// Set of currently active ports
|
||||||
|
std::set<uint16_t> activePorts;
|
||||||
|
|
||||||
|
// Provider-specific helper methods
|
||||||
|
bool fetchCurrentRules();
|
||||||
|
bool makeApiRequest(const std::string& endpoint,
|
||||||
|
const std::string& method,
|
||||||
|
const std::string& data,
|
||||||
|
std::string& response);
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Constructor
|
||||||
|
CloudProviderFirewallManager(const std::string& apiToken, const std::string& firewallId);
|
||||||
|
|
||||||
|
// Destructor
|
||||||
|
virtual ~CloudProviderFirewallManager() = default;
|
||||||
|
|
||||||
|
// Implementation of abstract methods from CloudFirewallManager
|
||||||
|
bool initialize() override;
|
||||||
|
bool addPort(uint16_t port) override;
|
||||||
|
bool removePort(uint16_t port) override;
|
||||||
|
bool syncFirewallRules() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CLOUD_PROVIDER_FIREWALL_MANAGER_HPP
|
303
tcp-proxy/cloud/provider/tests/test_cloud_firewall_template.cpp
Normal file
303
tcp-proxy/cloud/provider/tests/test_cloud_firewall_template.cpp
Normal file
|
@ -0,0 +1,303 @@
|
||||||
|
/**
|
||||||
|
* test_cloud_firewall_template.cpp
|
||||||
|
*
|
||||||
|
* Template for creating cloud provider-specific firewall manager tests
|
||||||
|
* This file serves as a starting point for testing new cloud provider implementations
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
#include <csignal>
|
||||||
|
#include <fstream>
|
||||||
|
#include <atomic>
|
||||||
|
#include <memory>
|
||||||
|
#include <curl/curl.h>
|
||||||
|
#include "../../../../ext/nlohmann/json.hpp"
|
||||||
|
#include "../FirewallManagerFactory.hpp"
|
||||||
|
// Include your cloud provider's header if needed for specific testing
|
||||||
|
// #include "YourCloudProviderFirewallManager.hpp"
|
||||||
|
|
||||||
|
// For JSON parsing
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
// Global variables for signal handling
|
||||||
|
static std::atomic<bool> running(true);
|
||||||
|
|
||||||
|
// Test ports
|
||||||
|
const uint16_t TEST_PORT_1 = 9993;
|
||||||
|
const uint16_t TEST_PORT_2 = 9994;
|
||||||
|
const uint16_t TEST_PORT_3 = 9995;
|
||||||
|
|
||||||
|
// Test port ranges
|
||||||
|
const uint16_t TEST_RANGE_START_1 = 10000;
|
||||||
|
const uint16_t TEST_RANGE_END_1 = 10010;
|
||||||
|
const uint16_t TEST_RANGE_START_2 = 10020;
|
||||||
|
const uint16_t TEST_RANGE_END_2 = 10030;
|
||||||
|
|
||||||
|
// Utility function for logging with timestamp
|
||||||
|
void datetime_fprintf(FILE *stream, const char *fmt, ...) {
|
||||||
|
time_t t = time(0);
|
||||||
|
struct tm *tm = localtime(&t);
|
||||||
|
char datetime[64];
|
||||||
|
strftime(datetime, sizeof(datetime), "%Y-%m-%d %H:%M:%S", tm);
|
||||||
|
fprintf(stream, "[%s] ", datetime);
|
||||||
|
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, fmt);
|
||||||
|
vfprintf(stream, fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
fflush(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback function for curl HTTP requests
|
||||||
|
size_t WriteCallback(void *contents, size_t size, size_t nmemb, std::string *s) {
|
||||||
|
size_t newLength = size * nmemb;
|
||||||
|
try {
|
||||||
|
s->append((char*)contents, newLength);
|
||||||
|
return newLength;
|
||||||
|
} catch(std::bad_alloc &e) {
|
||||||
|
// Handle memory problem
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to display current firewall rules (customize for your cloud provider)
|
||||||
|
void displayFirewallRules(const std::string& apiToken, const std::string& firewallId) {
|
||||||
|
// This is a placeholder - implement according to your cloud provider's API
|
||||||
|
datetime_fprintf(stdout, "Displaying current firewall rules...\n");
|
||||||
|
|
||||||
|
CURL *curl;
|
||||||
|
CURLcode res;
|
||||||
|
std::string readBuffer;
|
||||||
|
|
||||||
|
curl = curl_easy_init();
|
||||||
|
if(curl) {
|
||||||
|
// Replace with your cloud provider's API endpoint
|
||||||
|
std::string url = "https://api.yourcloudprovider.com/v1/firewalls/" + firewallId;
|
||||||
|
|
||||||
|
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||||
|
|
||||||
|
// Set headers including authorization
|
||||||
|
struct curl_slist *headers = NULL;
|
||||||
|
headers = curl_slist_append(headers, "Content-Type: application/json");
|
||||||
|
std::string authHeader = "Authorization: Bearer " + apiToken;
|
||||||
|
headers = curl_slist_append(headers, authHeader.c_str());
|
||||||
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
||||||
|
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
|
||||||
|
|
||||||
|
res = curl_easy_perform(curl);
|
||||||
|
|
||||||
|
if(res != CURLE_OK) {
|
||||||
|
datetime_fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
|
||||||
|
} else {
|
||||||
|
// Parse and display the rules
|
||||||
|
try {
|
||||||
|
json j = json::parse(readBuffer);
|
||||||
|
// Customize this part based on your cloud provider's API response format
|
||||||
|
datetime_fprintf(stdout, "Current firewall rules: %s\n", j.dump(2).c_str());
|
||||||
|
} catch (json::parse_error& e) {
|
||||||
|
datetime_fprintf(stderr, "JSON parse error: %s\n", e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_slist_free_all(headers);
|
||||||
|
curl_easy_cleanup(curl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signal handler for graceful termination
|
||||||
|
void signalHandler(int signum) {
|
||||||
|
datetime_fprintf(stdout, "Interrupt signal (%d) received. Cleaning up...\n", signum);
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
// Register signal handler
|
||||||
|
signal(SIGINT, signalHandler);
|
||||||
|
|
||||||
|
// Check command line arguments
|
||||||
|
if (argc < 4) {
|
||||||
|
std::cerr << "Usage: " << argv[0] << " <cloud_provider> <api_token> <firewall_id>" << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string cloudProvider = argv[1];
|
||||||
|
std::string apiToken = argv[2];
|
||||||
|
std::string firewallId = argv[3];
|
||||||
|
|
||||||
|
datetime_fprintf(stdout, "Starting firewall manager test for %s provider\n", cloudProvider.c_str());
|
||||||
|
datetime_fprintf(stdout, "Firewall ID: %s\n", firewallId.c_str());
|
||||||
|
|
||||||
|
// Initialize curl
|
||||||
|
curl_global_init(CURL_GLOBAL_ALL);
|
||||||
|
|
||||||
|
// Create firewall manager using factory
|
||||||
|
auto manager = FirewallManagerFactory::createFirewallManager(cloudProvider, apiToken, firewallId);
|
||||||
|
|
||||||
|
if (!manager) {
|
||||||
|
datetime_fprintf(stderr, "Failed to create firewall manager for provider: %s\n", cloudProvider.c_str());
|
||||||
|
curl_global_cleanup();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the manager
|
||||||
|
if (!manager->initialize()) {
|
||||||
|
datetime_fprintf(stderr, "Failed to initialize firewall manager\n");
|
||||||
|
curl_global_cleanup();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
datetime_fprintf(stdout, "Firewall manager initialized successfully\n");
|
||||||
|
|
||||||
|
// Display initial firewall rules
|
||||||
|
displayFirewallRules(apiToken, firewallId);
|
||||||
|
|
||||||
|
// Test 1: Add a single port
|
||||||
|
datetime_fprintf(stdout, "\n=== Test 1: Adding single port %d ===\n", TEST_PORT_1);
|
||||||
|
if (manager->addPort(TEST_PORT_1)) {
|
||||||
|
datetime_fprintf(stdout, "Successfully added port %d\n", TEST_PORT_1);
|
||||||
|
} else {
|
||||||
|
datetime_fprintf(stderr, "Failed to add port %d\n", TEST_PORT_1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync rules after adding port
|
||||||
|
datetime_fprintf(stdout, "Syncing firewall rules...\n");
|
||||||
|
if (manager->syncFirewallRules()) {
|
||||||
|
datetime_fprintf(stdout, "Successfully synced firewall rules\n");
|
||||||
|
} else {
|
||||||
|
datetime_fprintf(stderr, "Failed to sync firewall rules\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display rules after adding port
|
||||||
|
displayFirewallRules(apiToken, firewallId);
|
||||||
|
|
||||||
|
// Test 2: Remove the port
|
||||||
|
datetime_fprintf(stdout, "\n=== Test 2: Removing port %d ===\n", TEST_PORT_1);
|
||||||
|
if (manager->removePort(TEST_PORT_1)) {
|
||||||
|
datetime_fprintf(stdout, "Successfully removed port %d\n", TEST_PORT_1);
|
||||||
|
} else {
|
||||||
|
datetime_fprintf(stderr, "Failed to remove port %d\n", TEST_PORT_1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync rules after removing port
|
||||||
|
datetime_fprintf(stdout, "Syncing firewall rules...\n");
|
||||||
|
if (manager->syncFirewallRules()) {
|
||||||
|
datetime_fprintf(stdout, "Successfully synced firewall rules\n");
|
||||||
|
} else {
|
||||||
|
datetime_fprintf(stderr, "Failed to sync firewall rules\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display rules after removing port
|
||||||
|
displayFirewallRules(apiToken, firewallId);
|
||||||
|
|
||||||
|
// Test 3: Add multiple ports
|
||||||
|
datetime_fprintf(stdout, "\n=== Test 3: Adding multiple ports (%d, %d) ===\n", TEST_PORT_2, TEST_PORT_3);
|
||||||
|
if (manager->addPort(TEST_PORT_2) && manager->addPort(TEST_PORT_3)) {
|
||||||
|
datetime_fprintf(stdout, "Successfully added ports %d and %d\n", TEST_PORT_2, TEST_PORT_3);
|
||||||
|
} else {
|
||||||
|
datetime_fprintf(stderr, "Failed to add ports %d and %d\n", TEST_PORT_2, TEST_PORT_3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync rules after adding multiple ports
|
||||||
|
datetime_fprintf(stdout, "Syncing firewall rules...\n");
|
||||||
|
if (manager->syncFirewallRules()) {
|
||||||
|
datetime_fprintf(stdout, "Successfully synced firewall rules\n");
|
||||||
|
} else {
|
||||||
|
datetime_fprintf(stderr, "Failed to sync firewall rules\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display rules after adding multiple ports
|
||||||
|
displayFirewallRules(apiToken, firewallId);
|
||||||
|
|
||||||
|
// Test 4: Add port ranges
|
||||||
|
datetime_fprintf(stdout, "\n=== Test 4: Adding port ranges ===\n");
|
||||||
|
for (uint16_t port = TEST_RANGE_START_1; port <= TEST_RANGE_END_1; port++) {
|
||||||
|
if (manager->addPort(port)) {
|
||||||
|
datetime_fprintf(stdout, "Added port %d\n", port);
|
||||||
|
} else {
|
||||||
|
datetime_fprintf(stderr, "Failed to add port %d\n", port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync rules after adding port range
|
||||||
|
datetime_fprintf(stdout, "Syncing firewall rules...\n");
|
||||||
|
if (manager->syncFirewallRules()) {
|
||||||
|
datetime_fprintf(stdout, "Successfully synced firewall rules\n");
|
||||||
|
} else {
|
||||||
|
datetime_fprintf(stderr, "Failed to sync firewall rules\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display rules after adding port range
|
||||||
|
displayFirewallRules(apiToken, firewallId);
|
||||||
|
|
||||||
|
// Test 5: Add another port range to test rule consolidation
|
||||||
|
datetime_fprintf(stdout, "\n=== Test 5: Adding another port range to test rule consolidation ===\n");
|
||||||
|
for (uint16_t port = TEST_RANGE_START_2; port <= TEST_RANGE_END_2; port++) {
|
||||||
|
if (manager->addPort(port)) {
|
||||||
|
datetime_fprintf(stdout, "Added port %d\n", port);
|
||||||
|
} else {
|
||||||
|
datetime_fprintf(stderr, "Failed to add port %d\n", port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync rules after adding second port range
|
||||||
|
datetime_fprintf(stdout, "Syncing firewall rules...\n");
|
||||||
|
if (manager->syncFirewallRules()) {
|
||||||
|
datetime_fprintf(stdout, "Successfully synced firewall rules\n");
|
||||||
|
} else {
|
||||||
|
datetime_fprintf(stderr, "Failed to sync firewall rules\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display rules after adding second port range
|
||||||
|
displayFirewallRules(apiToken, firewallId);
|
||||||
|
|
||||||
|
// Test 6: Cleanup - remove all ports
|
||||||
|
datetime_fprintf(stdout, "\n=== Test 6: Cleanup - removing all ports ===\n");
|
||||||
|
|
||||||
|
// Remove individual ports
|
||||||
|
if (manager->removePort(TEST_PORT_2) && manager->removePort(TEST_PORT_3)) {
|
||||||
|
datetime_fprintf(stdout, "Successfully removed ports %d and %d\n", TEST_PORT_2, TEST_PORT_3);
|
||||||
|
} else {
|
||||||
|
datetime_fprintf(stderr, "Failed to remove ports %d and %d\n", TEST_PORT_2, TEST_PORT_3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove port ranges
|
||||||
|
for (uint16_t port = TEST_RANGE_START_1; port <= TEST_RANGE_END_1; port++) {
|
||||||
|
if (manager->removePort(port)) {
|
||||||
|
datetime_fprintf(stdout, "Removed port %d\n", port);
|
||||||
|
} else {
|
||||||
|
datetime_fprintf(stderr, "Failed to remove port %d\n", port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint16_t port = TEST_RANGE_START_2; port <= TEST_RANGE_END_2; port++) {
|
||||||
|
if (manager->removePort(port)) {
|
||||||
|
datetime_fprintf(stdout, "Removed port %d\n", port);
|
||||||
|
} else {
|
||||||
|
datetime_fprintf(stderr, "Failed to remove port %d\n", port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Final sync to apply all removals
|
||||||
|
datetime_fprintf(stdout, "Final sync to apply all removals...\n");
|
||||||
|
if (manager->syncFirewallRules()) {
|
||||||
|
datetime_fprintf(stdout, "Successfully synced firewall rules\n");
|
||||||
|
} else {
|
||||||
|
datetime_fprintf(stderr, "Failed to sync firewall rules\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display final firewall rules
|
||||||
|
displayFirewallRules(apiToken, firewallId);
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
curl_global_cleanup();
|
||||||
|
|
||||||
|
datetime_fprintf(stdout, "\n=== Test Summary ===\n");
|
||||||
|
datetime_fprintf(stdout, "All tests completed for %s provider\n", cloudProvider.c_str());
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
1025
tcp-proxy/cloud/provider/tests/test_linode_firewall.cpp
Normal file
1025
tcp-proxy/cloud/provider/tests/test_linode_firewall.cpp
Normal file
File diff suppressed because it is too large
Load diff
49
tcp-proxy/conf/local.conf.cloud_examples
Normal file
49
tcp-proxy/conf/local.conf.cloud_examples
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
{
|
||||||
|
"// Basic TCP proxy settings":"",
|
||||||
|
"tcpPort": 8443,
|
||||||
|
"// External TCP port for firewall rules (if different from internal port)":"",
|
||||||
|
"// externalTcpPort": 443,
|
||||||
|
|
||||||
|
"// Legacy Linode-specific settings (still supported for backward compatibility)":"",
|
||||||
|
"linodeApiToken": "your_linode_api_token",
|
||||||
|
"linodeFirewallId": "12345",
|
||||||
|
|
||||||
|
"// Cloud-agnostic settings":"",
|
||||||
|
"cloudProvider": "linode",
|
||||||
|
"cloudApiToken": "your_cloud_api_token",
|
||||||
|
"cloudFirewallId": "your_firewall_id",
|
||||||
|
|
||||||
|
"// Example: AWS configuration":"",
|
||||||
|
"// Replace the above cloud settings with these for AWS":"",
|
||||||
|
"// cloudProvider": "aws",
|
||||||
|
"// cloudApiToken": "your_aws_access_key",
|
||||||
|
"// cloudFirewallId": "sg-0123456789abcdef",
|
||||||
|
"// cloudAdditionalParams": {
|
||||||
|
"// "secretKey": "your_aws_secret_key",
|
||||||
|
"// "region": "us-east-1"
|
||||||
|
"// },
|
||||||
|
|
||||||
|
"// Example: Azure configuration":"",
|
||||||
|
"// Replace the above cloud settings with these for Azure":"",
|
||||||
|
"// cloudProvider": "azure",
|
||||||
|
"// cloudApiToken": "your_azure_client_secret",
|
||||||
|
"// cloudFirewallId": "your_network_security_group_name",
|
||||||
|
"// cloudAdditionalParams": {
|
||||||
|
"// "tenantId": "your_azure_tenant_id",
|
||||||
|
"// "subscriptionId": "your_azure_subscription_id",
|
||||||
|
"// "resourceGroup": "your_azure_resource_group"
|
||||||
|
"// },
|
||||||
|
|
||||||
|
"// Example: GCP configuration":"",
|
||||||
|
"// Replace the above cloud settings with these for GCP":"",
|
||||||
|
"// cloudProvider": "gcp",
|
||||||
|
"// cloudApiToken": "your_gcp_service_account_key",
|
||||||
|
"// cloudFirewallId": "your_firewall_name",
|
||||||
|
"// cloudAdditionalParams": {
|
||||||
|
"// "projectId": "your_gcp_project_id",
|
||||||
|
"// "network": "default"
|
||||||
|
"// },
|
||||||
|
|
||||||
|
"// Other settings":"",
|
||||||
|
"logStdout": true
|
||||||
|
}
|
8
tcp-proxy/conf/local.conf.example
Normal file
8
tcp-proxy/conf/local.conf.example
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"settings": {
|
||||||
|
// TCP port for the proxy server to listen on (internal port)
|
||||||
|
"tcpPort": 8443,
|
||||||
|
// External TCP port for firewall rules (if different from internal port)
|
||||||
|
// "externalTcpPort": 443
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,6 +25,12 @@
|
||||||
#define __FD_SETSIZE 1048576
|
#define __FD_SETSIZE 1048576
|
||||||
#undef FD_SETSIZE
|
#undef FD_SETSIZE
|
||||||
#define FD_SETSIZE 1048576
|
#define FD_SETSIZE 1048576
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <bits/stdc++.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include "../ext/nlohmann/json.hpp" // This path is correct as tcp-proxy.cpp is in the root directory
|
||||||
|
using json = nlohmann::json;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "../node/Metrics.hpp"
|
#include "../node/Metrics.hpp"
|
||||||
|
@ -42,12 +48,124 @@
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
// Only include cloud provider functionality when ENABLE_CLOUD_PROVIDER is defined
|
||||||
|
#ifdef ENABLE_CLOUD_PROVIDER
|
||||||
|
#include <curl/curl.h>
|
||||||
|
#include "cloud/provider/FirewallManagerFactory.hpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
#define ZT_TCP_PROXY_CONNECTION_TIMEOUT_SECONDS 300
|
#define ZT_TCP_PROXY_CONNECTION_TIMEOUT_SECONDS 300
|
||||||
#define ZT_TCP_PROXY_TCP_PORT 443
|
|
||||||
|
int ZT_TCP_PROXY_TCP_PORT;
|
||||||
|
int ZT_TCP_PROXY_EXTERNAL_TCP_PORT;
|
||||||
|
|
||||||
|
// Cloud provider variables only used when ENABLE_CLOUD_PROVIDER is defined
|
||||||
|
#ifdef ENABLE_CLOUD_PROVIDER
|
||||||
|
std::string ZT_CLOUD_PROVIDER = "linode"; // Default to linode for backward compatibility
|
||||||
|
std::string ZT_CLOUD_API_TOKEN;
|
||||||
|
std::string ZT_CLOUD_FIREWALL_ID;
|
||||||
|
bool ZT_CLOUD_FIREWALL_ENABLED = false;
|
||||||
|
std::unique_ptr<CloudFirewallManager> firewallManager;
|
||||||
|
#endif
|
||||||
|
|
||||||
using namespace ZeroTier;
|
using namespace ZeroTier;
|
||||||
|
|
||||||
|
// declaring datetime function
|
||||||
|
void datetime_fprintf(FILE *stream, const char *fmt, ...) __attribute__((format(printf,2,3)));
|
||||||
|
|
||||||
|
void datetime_fprintf(FILE *stream, const char *fmt, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
time_t timer;
|
||||||
|
struct tm * timeinfo;
|
||||||
|
char buffer [100];
|
||||||
|
time(&timer);
|
||||||
|
timeinfo = localtime (&timer);
|
||||||
|
strftime (buffer,100,"[%c] ",timeinfo);
|
||||||
|
va_start(ap, fmt);
|
||||||
|
fprintf(stream, "%s", buffer);
|
||||||
|
vfprintf(stream, fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
}
|
||||||
|
#define datetime_printf(...) datetime_fprintf(stderr, __VA_ARGS__)
|
||||||
|
|
||||||
|
// TCP_PORT namespace
|
||||||
|
namespace TCP_PORT
|
||||||
|
|
||||||
|
{
|
||||||
|
void func()
|
||||||
|
|
||||||
|
{
|
||||||
|
// Path to the directory and config file
|
||||||
|
std::string dir = "/var/lib/zerotier-one/";
|
||||||
|
std::string filePath = dir + "local.conf";
|
||||||
|
std::ifstream config(filePath);
|
||||||
|
|
||||||
|
if (!config)
|
||||||
|
{
|
||||||
|
datetime_printf("- No config file present.\n");
|
||||||
|
datetime_printf("- Switching to default port, Server listening on TCP port : 443\n");
|
||||||
|
ZT_TCP_PROXY_TCP_PORT = 443;
|
||||||
|
} else {
|
||||||
|
nlohmann::json j;
|
||||||
|
config >> j;
|
||||||
|
|
||||||
|
// Get TCP port
|
||||||
|
if (j["settings"].contains("tcpPort")) {
|
||||||
|
std::uint64_t tcpPort = j["settings"]["tcpPort"];
|
||||||
|
ZT_TCP_PROXY_TCP_PORT = tcpPort;
|
||||||
|
datetime_printf("- Config file present.\n");
|
||||||
|
datetime_printf("- Server listening on TCP port : %d\n",ZT_TCP_PROXY_TCP_PORT);
|
||||||
|
} else {
|
||||||
|
ZT_TCP_PROXY_TCP_PORT = 443;
|
||||||
|
datetime_printf("- No TCP port specified in config, using default port 443\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get external TCP port if specified, otherwise use internal port
|
||||||
|
if (j["settings"].contains("externalTcpPort")) {
|
||||||
|
std::uint64_t externalTcpPort = j["settings"]["externalTcpPort"];
|
||||||
|
ZT_TCP_PROXY_EXTERNAL_TCP_PORT = externalTcpPort;
|
||||||
|
datetime_printf("- Using external TCP port %d for firewall rules\n", ZT_TCP_PROXY_EXTERNAL_TCP_PORT);
|
||||||
|
} else {
|
||||||
|
ZT_TCP_PROXY_EXTERNAL_TCP_PORT = ZT_TCP_PROXY_TCP_PORT;
|
||||||
|
datetime_printf("- No external TCP port specified, using internal port %d for firewall rules\n", ZT_TCP_PROXY_TCP_PORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get Cloud Firewall API settings if present
|
||||||
|
#ifdef ENABLE_CLOUD_PROVIDER
|
||||||
|
// For backward compatibility, check for Linode settings first
|
||||||
|
if (j["settings"].contains("linodeApiToken") && j["settings"].contains("linodeFirewallId")) {
|
||||||
|
ZT_CLOUD_PROVIDER = "linode";
|
||||||
|
ZT_CLOUD_API_TOKEN = j["settings"]["linodeApiToken"];
|
||||||
|
ZT_CLOUD_FIREWALL_ID = j["settings"]["linodeFirewallId"];
|
||||||
|
ZT_CLOUD_FIREWALL_ENABLED = true;
|
||||||
|
datetime_printf("- Linode Firewall integration enabled for firewall ID: %s\n", ZT_CLOUD_FIREWALL_ID.c_str());
|
||||||
|
}
|
||||||
|
// Check for generic cloud provider settings
|
||||||
|
else if (j["settings"].contains("cloudProvider") &&
|
||||||
|
j["settings"].contains("cloudApiToken") &&
|
||||||
|
j["settings"].contains("cloudFirewallId")) {
|
||||||
|
ZT_CLOUD_PROVIDER = j["settings"]["cloudProvider"];
|
||||||
|
ZT_CLOUD_API_TOKEN = j["settings"]["cloudApiToken"];
|
||||||
|
ZT_CLOUD_FIREWALL_ID = j["settings"]["cloudFirewallId"];
|
||||||
|
ZT_CLOUD_FIREWALL_ENABLED = true;
|
||||||
|
datetime_printf("- %s Firewall integration enabled for firewall ID: %s\n",
|
||||||
|
ZT_CLOUD_PROVIDER.c_str(), ZT_CLOUD_FIREWALL_ID.c_str());
|
||||||
|
} else {
|
||||||
|
datetime_printf("- Cloud Firewall integration disabled (API token or Firewall ID not provided)\n");
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (j["settings"].contains("linodeApiToken") || j["settings"].contains("cloudApiToken")) {
|
||||||
|
datetime_printf("- Cloud Firewall integration not compiled in this build\n");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ZeroTier TCP Proxy Server
|
* ZeroTier TCP Proxy Server
|
||||||
*
|
*
|
||||||
|
@ -86,22 +204,26 @@ using namespace ZeroTier;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
struct TcpProxyService;
|
struct TcpProxyService;
|
||||||
struct TcpProxyService {
|
struct TcpProxyService
|
||||||
|
{
|
||||||
Phy<TcpProxyService *> *phy;
|
Phy<TcpProxyService *> *phy;
|
||||||
int udpPortCounter;
|
int udpPortCounter;
|
||||||
struct Client {
|
struct Client
|
||||||
|
{
|
||||||
char tcpReadBuf[131072];
|
char tcpReadBuf[131072];
|
||||||
char tcpWriteBuf[131072];
|
char tcpWriteBuf[131072];
|
||||||
unsigned long tcpWritePtr;
|
unsigned long tcpWritePtr;
|
||||||
unsigned long tcpReadPtr;
|
unsigned long tcpReadPtr;
|
||||||
PhySocket *tcp;
|
PhySocket *tcp;
|
||||||
PhySocket *udp;
|
PhySocket *udp;
|
||||||
|
uint16_t udpPort; // Store the UDP port number
|
||||||
time_t lastActivity;
|
time_t lastActivity;
|
||||||
bool newVersion;
|
bool newVersion;
|
||||||
|
bool isLocalhost; // Flag to indicate if client is localhost/127.0.0.1
|
||||||
};
|
};
|
||||||
std::map< PhySocket *,Client > clients;
|
std::map< PhySocket *,Client > clients;
|
||||||
|
|
||||||
PhySocket* getUnusedUdp(void* uptr)
|
PhySocket *getUnusedUdp(Client *client)
|
||||||
{
|
{
|
||||||
for(int i=0;i<65535;++i) {
|
for(int i=0;i<65535;++i) {
|
||||||
++udpPortCounter;
|
++udpPortCounter;
|
||||||
|
@ -111,10 +233,31 @@ struct TcpProxyService {
|
||||||
memset(&laddr,0,sizeof(struct sockaddr_in));
|
memset(&laddr,0,sizeof(struct sockaddr_in));
|
||||||
laddr.sin_family = AF_INET;
|
laddr.sin_family = AF_INET;
|
||||||
laddr.sin_port = htons((uint16_t)udpPortCounter);
|
laddr.sin_port = htons((uint16_t)udpPortCounter);
|
||||||
PhySocket* udp = phy->udpBind(reinterpret_cast<struct sockaddr*>(&laddr), uptr);
|
PhySocket *udp = phy->udpBind(reinterpret_cast<struct sockaddr *>(&laddr), (void*)client);
|
||||||
if (udp)
|
if (udp) {
|
||||||
|
// Store the UDP port in the client structure
|
||||||
|
client->udpPort = (uint16_t)udpPortCounter;
|
||||||
|
datetime_printf(">> Assigned UDP port %d to %.16llx\n", udpPortCounter, (unsigned long long)client);
|
||||||
|
|
||||||
|
// Add port to Cloud Firewall if enabled
|
||||||
|
#ifdef ENABLE_CLOUD_PROVIDER
|
||||||
|
if (ZT_CLOUD_FIREWALL_ENABLED && firewallManager) {
|
||||||
|
if (!client->isLocalhost) {
|
||||||
|
// Only add port to firewall if request is not from localhost
|
||||||
|
if (firewallManager->addPort((uint16_t)udpPortCounter)) {
|
||||||
|
datetime_printf(">> Added UDP port %d to %s Firewall\n", udpPortCounter, ZT_CLOUD_PROVIDER.empty() ? "Cloud" : ZT_CLOUD_PROVIDER.c_str());
|
||||||
|
} else {
|
||||||
|
datetime_printf("!! Failed to add UDP port %d to %s Firewall\n", udpPortCounter, ZT_CLOUD_PROVIDER.empty() ? "Cloud" : ZT_CLOUD_PROVIDER.c_str());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
datetime_printf(">> Skipped adding UDP port %d to %s Firewall (localhost request)\n", udpPortCounter, ZT_CLOUD_PROVIDER.empty() ? "Cloud" : ZT_CLOUD_PROVIDER.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
return udp;
|
return udp;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return (PhySocket *)0;
|
return (PhySocket *)0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,7 +296,7 @@ struct TcpProxyService {
|
||||||
c.tcpWriteBuf[c.tcpWritePtr++] = ((const char *)data)[i];
|
c.tcpWriteBuf[c.tcpWritePtr++] = ((const char *)data)[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("<< UDP %s:%d -> %.16llx\n", inet_ntoa(reinterpret_cast<const struct sockaddr_in*>(from)->sin_addr), (int)ntohs(reinterpret_cast<const struct sockaddr_in*>(from)->sin_port), (unsigned long long)&c);
|
datetime_printf("<< UDP %s:%d -> %.16llx\n",inet_ntoa(reinterpret_cast<const struct sockaddr_in *>(from)->sin_addr),(int)ntohs(reinterpret_cast<const struct sockaddr_in *>(from)->sin_port),(unsigned long long)&c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,31 +308,77 @@ struct TcpProxyService {
|
||||||
void phyOnTcpAccept(PhySocket *sockL,PhySocket *sockN,void **uptrL,void **uptrN,const struct sockaddr *from)
|
void phyOnTcpAccept(PhySocket *sockL,PhySocket *sockN,void **uptrL,void **uptrN,const struct sockaddr *from)
|
||||||
{
|
{
|
||||||
Client &c = clients[sockN];
|
Client &c = clients[sockN];
|
||||||
PhySocket* udp = getUnusedUdp((void*)&c);
|
|
||||||
if (! udp) {
|
|
||||||
phy->close(sockN);
|
|
||||||
clients.erase(sockN);
|
|
||||||
printf("** TCP rejected, no more UDP ports to assign\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
c.tcpWritePtr = 0;
|
c.tcpWritePtr = 0;
|
||||||
c.tcpReadPtr = 0;
|
c.tcpReadPtr = 0;
|
||||||
c.tcp = sockN;
|
c.tcp = sockN;
|
||||||
c.udp = udp;
|
c.udpPort = 0; // Initialize UDP port to 0
|
||||||
c.lastActivity = time((time_t *)0);
|
c.lastActivity = time((time_t *)0);
|
||||||
c.newVersion = false;
|
c.newVersion = false;
|
||||||
|
|
||||||
|
// Check if client is localhost/127.0.0.1
|
||||||
|
c.isLocalhost = false;
|
||||||
|
if (from && from->sa_family == AF_INET) {
|
||||||
|
const struct sockaddr_in* clientAddr = reinterpret_cast<const struct sockaddr_in*>(from);
|
||||||
|
c.isLocalhost = (clientAddr->sin_addr.s_addr == htonl(INADDR_LOOPBACK));
|
||||||
|
if (c.isLocalhost) {
|
||||||
|
datetime_printf("** TCP connection from localhost/127.0.0.1 detected\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
*uptrN = (void *)&c;
|
*uptrN = (void *)&c;
|
||||||
printf("<< TCP from %s -> %.16llx\n", inet_ntoa(reinterpret_cast<const struct sockaddr_in*>(from)->sin_addr), (unsigned long long)&c);
|
|
||||||
|
PhySocket *udp = getUnusedUdp(&c);
|
||||||
|
if (!udp) {
|
||||||
|
phy->close(sockN);
|
||||||
|
clients.erase(sockN);
|
||||||
|
datetime_printf("** TCP rejected, no more UDP ports to assign\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
c.udp = udp;
|
||||||
|
datetime_printf("<< TCP from %s -> %.16llx\n",inet_ntoa(reinterpret_cast<const struct sockaddr_in *>(from)->sin_addr),(unsigned long long)&c);
|
||||||
}
|
}
|
||||||
|
|
||||||
void phyOnTcpClose(PhySocket *sock,void **uptr)
|
void phyOnTcpClose(PhySocket *sock,void **uptr)
|
||||||
{
|
{
|
||||||
if (!*uptr)
|
if (!*uptr)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// Make a copy of the necessary values before erasing the client
|
||||||
|
uint16_t udpPort = 0;
|
||||||
|
bool isLocalhost = false;
|
||||||
|
PhySocket *udpSock = nullptr;
|
||||||
|
|
||||||
|
{
|
||||||
Client &c = *((Client *)*uptr);
|
Client &c = *((Client *)*uptr);
|
||||||
phy->close(c.udp);
|
udpPort = c.udpPort;
|
||||||
|
isLocalhost = c.isLocalhost;
|
||||||
|
udpSock = c.udp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the UDP socket
|
||||||
|
if (udpSock) {
|
||||||
|
phy->close(udpSock);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove client from map
|
||||||
clients.erase(sock);
|
clients.erase(sock);
|
||||||
printf("** TCP %.16llx closed\n", (unsigned long long)*uptr);
|
datetime_printf("** TCP %.16llx closed\n",(unsigned long long)*uptr);
|
||||||
|
|
||||||
|
// Remove port from Cloud Firewall if enabled
|
||||||
|
#ifdef ENABLE_CLOUD_PROVIDER
|
||||||
|
if (ZT_CLOUD_FIREWALL_ENABLED && firewallManager && udpPort > 0) {
|
||||||
|
if (!isLocalhost) {
|
||||||
|
// Only remove port from firewall if request is not from localhost
|
||||||
|
if (firewallManager->removePort(udpPort)) {
|
||||||
|
datetime_printf(">> Removed UDP port %d from %s Firewall\n", udpPort, ZT_CLOUD_PROVIDER.empty() ? "Cloud" : ZT_CLOUD_PROVIDER.c_str());
|
||||||
|
} else {
|
||||||
|
datetime_printf("!! Failed to remove UDP port %d from %s Firewall\n", udpPort, ZT_CLOUD_PROVIDER.empty() ? "Cloud" : ZT_CLOUD_PROVIDER.c_str());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
datetime_printf(">> Skipped removing UDP port %d from %s Firewall (localhost request)\n", udpPort, ZT_CLOUD_PROVIDER.empty() ? "Cloud" : ZT_CLOUD_PROVIDER.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void phyOnTcpData(PhySocket *sock,void **uptr,void *data,unsigned long len)
|
void phyOnTcpData(PhySocket *sock,void **uptr,void *data,unsigned long len)
|
||||||
|
@ -210,9 +399,8 @@ struct TcpProxyService {
|
||||||
if (mlen == 4) {
|
if (mlen == 4) {
|
||||||
// Right now just sending this means the client is 'new enough' for the IP header
|
// Right now just sending this means the client is 'new enough' for the IP header
|
||||||
c.newVersion = true;
|
c.newVersion = true;
|
||||||
printf("<< TCP %.16llx HELLO\n", (unsigned long long)*uptr);
|
datetime_printf("<< TCP %.16llx HELLO\n",(unsigned long long)*uptr);
|
||||||
}
|
} else if (mlen >= 7) {
|
||||||
else if (mlen >= 7) {
|
|
||||||
char *payload = c.tcpReadBuf + 5;
|
char *payload = c.tcpReadBuf + 5;
|
||||||
unsigned long payloadLen = mlen;
|
unsigned long payloadLen = mlen;
|
||||||
|
|
||||||
|
@ -229,8 +417,7 @@ struct TcpProxyService {
|
||||||
payload += 2;
|
payload += 2;
|
||||||
payloadLen -= 7;
|
payloadLen -= 7;
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
// For old clients we will just proxy everything to a local ZT instance. The
|
// For old clients we will just proxy everything to a local ZT instance. The
|
||||||
// fact that this will come from 127.0.0.1 will in turn prevent that instance
|
// fact that this will come from 127.0.0.1 will in turn prevent that instance
|
||||||
// from doing unite() with us. It'll just forward. There will not be many of
|
// from doing unite() with us. It'll just forward. There will not be many of
|
||||||
|
@ -243,7 +430,7 @@ struct TcpProxyService {
|
||||||
// Note: we do not relay to privileged ports... just an abuse prevention rule.
|
// Note: we do not relay to privileged ports... just an abuse prevention rule.
|
||||||
if ((ntohs(dest.sin_port) > 1024)&&(payloadLen >= 16)) {
|
if ((ntohs(dest.sin_port) > 1024)&&(payloadLen >= 16)) {
|
||||||
phy->udpSend(c.udp,(const struct sockaddr *)&dest,payload,payloadLen);
|
phy->udpSend(c.udp,(const struct sockaddr *)&dest,payload,payloadLen);
|
||||||
printf(">> TCP %.16llx to %s:%d\n", (unsigned long long)*uptr, inet_ntoa(dest.sin_addr), (int)ntohs(dest.sin_port));
|
datetime_printf(">> TCP %.16llx to %s:%d\n",(unsigned long long)*uptr,inet_ntoa(dest.sin_addr),(int)ntohs(dest.sin_port));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -263,9 +450,7 @@ struct TcpProxyService {
|
||||||
if (!c.tcpWritePtr)
|
if (!c.tcpWritePtr)
|
||||||
phy->setNotifyWritable(sock,false);
|
phy->setNotifyWritable(sock,false);
|
||||||
}
|
}
|
||||||
}
|
} else phy->setNotifyWritable(sock,false);
|
||||||
else
|
|
||||||
phy->setNotifyWritable(sock, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void doHousekeeping()
|
void doHousekeeping()
|
||||||
|
@ -285,10 +470,50 @@ struct TcpProxyService {
|
||||||
|
|
||||||
int main(int argc,char **argv)
|
int main(int argc,char **argv)
|
||||||
{
|
{
|
||||||
|
TCP_PORT :: func();
|
||||||
signal(SIGPIPE,SIG_IGN);
|
signal(SIGPIPE,SIG_IGN);
|
||||||
signal(SIGHUP,SIG_IGN);
|
signal(SIGHUP,SIG_IGN);
|
||||||
srand(time((time_t *)0));
|
srand(time((time_t *)0));
|
||||||
|
|
||||||
|
// Initialize Cloud Firewall Manager if enabled
|
||||||
|
#ifdef ENABLE_CLOUD_PROVIDER
|
||||||
|
if (ZT_CLOUD_FIREWALL_ENABLED) {
|
||||||
|
try {
|
||||||
|
const char* providerName = ZT_CLOUD_PROVIDER.empty() ? "Cloud" : ZT_CLOUD_PROVIDER.c_str();
|
||||||
|
datetime_printf("Initializing %s Firewall Manager...\n", providerName);
|
||||||
|
datetime_printf("- Using %s API Token (length: %zu) and Firewall ID: %s\n",
|
||||||
|
providerName, ZT_CLOUD_API_TOKEN.length(), ZT_CLOUD_FIREWALL_ID.c_str());
|
||||||
|
|
||||||
|
firewallManager = FirewallManagerFactory::createFirewallManager(
|
||||||
|
ZT_CLOUD_PROVIDER, ZT_CLOUD_API_TOKEN, ZT_CLOUD_FIREWALL_ID,
|
||||||
|
ZT_TCP_PROXY_TCP_PORT, ZT_TCP_PROXY_EXTERNAL_TCP_PORT);
|
||||||
|
|
||||||
|
if (!firewallManager || !firewallManager->initialize()) {
|
||||||
|
datetime_printf("!! Failed to initialize %s Firewall Manager\n", providerName);
|
||||||
|
firewallManager.reset();
|
||||||
|
ZT_CLOUD_FIREWALL_ENABLED = false;
|
||||||
|
} else {
|
||||||
|
datetime_printf("- %s Firewall Manager initialized successfully\n", providerName);
|
||||||
|
datetime_printf("- Will dynamically manage UDP ports for ZeroTier connections\n");
|
||||||
|
datetime_printf("- Will preserve existing firewall rules (TCP, ICMP, etc.)\n");
|
||||||
|
|
||||||
|
// Immediately sync firewall rules to ensure TCP rule is created
|
||||||
|
datetime_printf("- Performing initial firewall sync to ensure TCP rule is created...\n");
|
||||||
|
if (!firewallManager->syncFirewallRules()) {
|
||||||
|
datetime_printf("!! Failed to perform initial sync of %s Firewall rules\n", providerName);
|
||||||
|
} else {
|
||||||
|
datetime_printf("- Initial %s Firewall rules synchronization successful\n", providerName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
datetime_printf("!! Exception initializing %s Firewall Manager: %s\n",
|
||||||
|
ZT_CLOUD_PROVIDER.empty() ? "Cloud" : ZT_CLOUD_PROVIDER.c_str(), e.what());
|
||||||
|
firewallManager.reset();
|
||||||
|
ZT_CLOUD_FIREWALL_ENABLED = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
TcpProxyService svc;
|
TcpProxyService svc;
|
||||||
Phy<TcpProxyService *> phy(&svc,false,true);
|
Phy<TcpProxyService *> phy(&svc,false,true);
|
||||||
svc.phy = &phy;
|
svc.phy = &phy;
|
||||||
|
@ -300,12 +525,13 @@ int main(int argc, char** argv)
|
||||||
laddr.sin_family = AF_INET;
|
laddr.sin_family = AF_INET;
|
||||||
laddr.sin_port = htons(ZT_TCP_PROXY_TCP_PORT);
|
laddr.sin_port = htons(ZT_TCP_PROXY_TCP_PORT);
|
||||||
if (!phy.tcpListen((const struct sockaddr *)&laddr)) {
|
if (!phy.tcpListen((const struct sockaddr *)&laddr)) {
|
||||||
fprintf(stderr, "%s: fatal error: unable to bind TCP port %d\n", argv[0], ZT_TCP_PROXY_TCP_PORT);
|
datetime_printf("%s: fatal error: unable to bind TCP port %d\n",argv[0],ZT_TCP_PROXY_TCP_PORT);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
time_t lastDidHousekeeping = time((time_t *)0);
|
time_t lastDidHousekeeping = time((time_t *)0);
|
||||||
|
time_t lastFirewallSync = time((time_t *)0);
|
||||||
for(;;) {
|
for(;;) {
|
||||||
phy.poll(120000);
|
phy.poll(120000);
|
||||||
time_t now = time((time_t *)0);
|
time_t now = time((time_t *)0);
|
||||||
|
@ -313,7 +539,26 @@ int main(int argc, char** argv)
|
||||||
lastDidHousekeeping = now;
|
lastDidHousekeeping = now;
|
||||||
svc.doHousekeeping();
|
svc.doHousekeeping();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Periodically sync firewall rules (every 15 minutes)
|
||||||
|
#ifdef ENABLE_CLOUD_PROVIDER
|
||||||
|
if (ZT_CLOUD_FIREWALL_ENABLED && firewallManager && ((now - lastFirewallSync) > 900)) {
|
||||||
|
lastFirewallSync = now;
|
||||||
|
const char* providerName = ZT_CLOUD_PROVIDER.empty() ? "Cloud" : ZT_CLOUD_PROVIDER.c_str();
|
||||||
|
datetime_printf("Performing periodic %s Firewall sync (every 15 minutes)...\n", providerName);
|
||||||
|
if (!firewallManager->syncFirewallRules()) {
|
||||||
|
datetime_printf("!! Failed to sync %s Firewall rules\n", providerName);
|
||||||
|
} else {
|
||||||
|
datetime_printf("- %s Firewall rules synchronized successfully\n", providerName);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up (this will never be reached in normal operation)
|
||||||
|
#ifdef ENABLE_CLOUD_PROVIDER
|
||||||
|
firewallManager.reset();
|
||||||
|
#endif
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue