14 KiB
Creating private and secure P2P Docker Managment System
Introduction
Peardock is an innovative, peer-to-peer Docker management application inspired by Portainer.
Portainer offers various options for connecting to Docker environments, including Agent, Socket, and REST API Endpoints, catering to a wide range of use cases. However, deploying Portainer in large networks where exposing management software is a security concern can be challenging. Exposing management interfaces increases the attack surface and may not comply with organizational security policies.
Peardock addresses these challenges by leveraging peer-to-peer (P2P) networking to securely manage Docker containers without exposing any management software to the network. By using P2P connections, Peardock ensures that communication between the client and the agent is direct, encrypted, and confined to authenticated peers, enhancing security and efficiency.
This post provides an in-depth analysis of Peardock's design, backend architecture, custom message protocol, and the code that powers the entire application.
Source: https://git.ssh.surf/snxraven/Peardock
Concept and Motivation
Portainer revolutionized the container industry by providing an easy-to-use interface for managing Docker containers. It offers amazing choices for connecting to Docker environments through Agent, Socket, and REST API Endpoints, making it versatile for various deployment scenarios.
However, in large networks where security is paramount, deploying Portainer may not be the most secure option. Exposing management software or APIs can introduce vulnerabilities, and in environments where you do not want to expose any management interfaces, this becomes a critical issue. The risk of unauthorized access, interception, or exploitation increases with exposed endpoints.
Peardock aims to bring similar functionalities to a peer-to-peer network, eliminating the need to expose any management endpoints. By providing a peer-to-peer client that communicates with its agent solely via a P2P network connection, Peardock enhances security by:
- Not Exposing Management Software: No open ports or endpoints are required, reducing the attack surface.
- Encrypted Communication: All data transmitted over the P2P network is encrypted using secure protocols.
- Direct Peer-to-Peer Connections: Communication occurs directly between authenticated peers without intermediaries.
- Decentralization: Eliminates the need for centralized servers, mitigating single points of failure and potential targets for attacks.
By using peer-to-peer connections, users can manage Docker containers on remote hosts securely and efficiently. Peardock is designed for environments where security and privacy are critical, such as large enterprise networks, sensitive infrastructures, or situations where network exposure must be minimized.
Architecture Overview
Frontend
The frontend is a web application built with HTML, CSS, and JavaScript.
It uses:
- Bootstrap for responsive UI components.
- Xterm.js for terminal emulation within the browser.
- Hyperswarm for peer-to-peer communication.
- Browser APIs for storage and event handling.
Backend
The backend is a Node.js application that runs on each container host node:
- Hyperswarm is used to establish peer-to-peer connections.
- Dockerode interacts with the Docker Engine API.
- Custom message protocol facilitates command and response exchanges between peers.
Peer-to-Peer Communication with Hyperswarm
Hyperswarm is a networking stack that simplifies peer-to-peer connections using distributed hash tables (DHT).
In Peardock:
- Each peer joins a swarm identified by a unique topic (a cryptographic key).
- Peers discover each other by announcing and looking up this topic in the DHT.
- Once connected, peers communicate directly over encrypted channels.
This architecture ensures:
- Decentralization: No central server is required.
- Scalability: Peers can join or leave without affecting the network.
- Security: Communications are encrypted and confined to authenticated peers.
Docker Interaction Using Dockerode
Dockerode is a Node.js library for Docker's Remote API.
It allows:
- Managing containers (list, start, stop, remove).
- Executing commands inside containers.
- Streaming logs and stats.
In Peardock's backend (server.js
):
- Dockerode interfaces with the local Docker daemon.
- Commands received from peers are executed, and responses are sent back securely.
Custom Message Protocol
Peardock uses a JSON-based custom message protocol over Hyperswarm connections.
Command Structure
Every command sent from the client to the server has the following structure:
{
"command": "commandName",
"args": {
"key1": "value1",
"key2": "value2"
}
}
- command: Specifies the action (e.g.,
listContainers
,startContainer
). - args: An object containing necessary parameters.
Response Structure
Responses from the server follow this structure:
{
"type": "responseType",
"data": {
"key1": "value1",
"key2": "value2"
},
"success": true,
"message": "Optional message",
"error": "Optional error message"
}
- type: Indicates the type of response (e.g.,
containers
,terminalOutput
). - data: Contains the payload.
- success: A boolean indicating the success of the operation.
- message: Informational message.
- error: Error message if the operation failed.
Code Breakdown
Frontend Components
Index.html
The main HTML file sets up the application's structure:
- Title Bar: A draggable region for desktop applications.
- Sidebar: Manages connections to different peers.
- Content Area: Displays the dashboard or welcome page.
- Modals: For actions like duplicating containers, viewing logs, and deploying templates.
- Terminal Modal: An embedded terminal using Xterm.js.
- Alert Container: Displays notifications to the user.
Key Sections:
<!-- Connection List -->
<ul id="connection-list" class="list-group mb-3"></ul>
<!-- Container List -->
<tbody id="container-list"></tbody>
<!-- Terminal Modal -->
<div id="terminal-modal">
<!-- Terminal Content -->
<div id="terminal-container"></div>
</div>
App.js
The primary JavaScript file responsible for:
- Managing connections.
- Handling user interactions.
- Communicating with the backend.
- Rendering container lists and stats.
Highlights:
- Connection Management: Allows users to add and remove peer connections.
- Command Sending: Sends JSON commands over Hyperswarm to the backend.
- Container Rendering: Updates the UI with container information.
- Event Handling: Responds to user actions like starting/stopping containers.
Sample Function:
function sendCommand(command, args = {}) {
if (window.activePeer) {
const message = JSON.stringify({ command, args });
window.activePeer.write(message);
} else {
console.error('[ERROR] No active peer to send command.');
}
}
Terminal.js
Handles terminal sessions within the application:
- Uses Xterm.js to create terminal emulators.
- Manages multiple terminal sessions.
- Sends user input to the backend and displays output.
Key Features:
- Session Management: Tracks terminal sessions per container.
- Data Handling: Encodes and decodes data for transmission.
- UI Integration: Displays terminals in modals.
TemplateDeploy.js
Manages the deployment of containers using templates:
- Fetches templates from a remote source.
- Allows users to search and select templates.
- Sends deployment commands to the backend.
Implementation Details:
- Template Fetching: Uses
fetch
API to retrieve templates. - Dynamic Form Generation: Builds forms based on template parameters.
- Deployment Handling: Validates inputs and sends commands.
DockerTerminal.js
Provides a Docker CLI terminal:
- Allows executing Docker commands on the remote peer.
- Handles input/output with the backend.
- Ensures security by sanitizing commands.
Functionality:
- Command Prepending: Ensures all commands start with
docker
. - Input Buffering: Manages user input before sending.
- Response Handling: Decodes and displays output from the backend.
Backend Components
Server.js
The Node.js backend that interacts with Docker and handles peer connections.
Core Responsibilities:
- Hyperswarm Server: Listens for incoming connections.
- Docker Management: Uses Dockerode to control Docker.
- Command Handling: Processes commands received from peers.
- Terminal Sessions: Manages interactive sessions inside containers.
- Event Broadcasting: Sends updates to all connected peers.
Key Sections:
-
Peer Connection Handling:
swarm.on('connection', (peer) => { connectedPeers.add(peer); peer.on('data', async (data) => { const parsedData = JSON.parse(data.toString()); // Handle commands }); peer.on('close', () => { connectedPeers.delete(peer); // Clean up sessions }); });
-
Command Processing:
switch (parsedData.command) { case 'listContainers': // List and send container data break; case 'startContainer': // Start specified container break; // Additional cases... }
-
Terminal Handling:
async function handleTerminal(containerId, peer) { const container = docker.getContainer(containerId); const exec = await container.exec({ /* ... */ }); const stream = await exec.start({ hijack: true, stdin: true }); // Handle input/output }
Features and Implementation Details
Connection Management
- Adding Connections: Users can add new peers by entering a topic key.
- Persistent Storage: Connections are saved in cookies for session persistence.
- Sidebar Interaction: The UI allows switching between different peer connections.
Code Snippet:
function addConnection(topicHex) {
const topic = b4a.from(topicHex, 'hex');
const topicId = topicHex.substring(0, 12);
connections[topicId] = { topic, peer: null, swarm: null, topicHex };
// UI updates and event listeners
}
Container Management
- Listing Containers: Retrieves a list of Docker containers from the peer.
- Starting/Stopping Containers: Sends commands to control container states.
- Removing Containers: Allows deletion of containers on the peer.
Implementation:
- Uses commands like
listContainers
,startContainer
, etc. - Handles responses to update the UI accordingly.
- Provides user feedback through alerts and status indicators.
Terminal Sessions
- Interactive Terminals: Users can open terminals to interact with containers.
- Multiple Sessions: Supports multiple terminal sessions simultaneously.
- Data Streaming: Streams input/output between the client and the container.
Backend Handling:
async function handleTerminal(containerId, peer) {
const container = docker.getContainer(containerId);
const exec = await container.exec({ /* ... */ });
const stream = await exec.start({ hijack: true, stdin: true });
// Stream data between peer and container
}
Template Deployment
- Template Source: Fetches application templates from a GitHub repository.
- Dynamic Forms: Generates forms based on template parameters.
- Deployment Process: Validates inputs and sends deployment commands.
Frontend Workflow:
- Fetch templates using
fetchTemplates()
. - Display templates in a searchable list.
- On selection, populate the deployment form.
- Handle form submission and send
deployContainer
command.
Backend Deployment:
case 'deployContainer':
// Validate and sanitize inputs
// Pull Docker image
// Create and start the container
// Send success or error response
break;
Final Thoughts
Peardock brings a novel approach to Docker container management by harnessing peer-to-peer networks. By eliminating the need to expose any management software or endpoints, it offers a secure, decentralized, and efficient way to manage containers across different hosts.
The combination of Hyperswarm for networking and Dockerode for container management provides a robust backend. The custom message protocol ensures structured and efficient communication between peers, confined to authenticated connections.
The frontend offers a user-friendly interface with advanced functionalities like terminal access and template deployment. The modular codebase allows for easy maintenance and extension.
Future Enhancements:
- Authentication and Access Control: Implementing robust authentication mechanisms and access controls for secure peer connections.
- Advanced Monitoring: Integrating detailed monitoring and alerting features for container health and performance.
- Plugin System: Allowing third-party plugins to extend functionalities and adapt to specific needs.
- Mobile Support: Optimizing the UI for mobile devices to manage containers on the go.
Peardock is a testament to the possibilities of decentralized applications and opens new avenues for managing containerized applications in distributed and security-sensitive environments. It combines the best of Portainer's user experience with the security and privacy benefits of peer-to-peer networking.