Программное создание приватного прокси

31.10.2017 at 08:09

Я уже рассказывал о том, как можно создать приватный прокси. У таких прокси только один существенный недостаток — необходимость ручных действий. Так что я решил этот процесс автоматизировть.
К счастью Digital Ocean предоставляет удобное API для управления дроплетами.

Создание SSH ключей

Для подключения к виртуалке и настройки прокси сервера потребуются SSH ключи. Можно и без них подключиться, но тогда потребуется получать письмо с рутовым паролем.
На убунту ключи создаются командой

ssh-keygen -t rsa

Созданный публичный ключ нужно загрузить на сайт Digital Ocean в раздел Settings -> Security

Создание дроплета

Для c# создана удобная обертка для работы с API DO — nuget пакет DigitalOcean.API.
Для работы с апи нужно на сайте Digital Ocean создать токен.
Так же при создании дроплета нужно загрузить в него публичный ключ для SSH.

using DigitalOcean.API;
using DigitalOcean.API.Models.Requests;
...
String token = "<token>";
var digitalOceanClient = new DigitalOceanClient(token);
var keyFingerprint = 
    digitalOceanClient.Keys.GetAll().Result.First().Fingerprint;
var dropletModel = new Droplet()
{
    Name = "proxy",
    RegionSlug = "nyc1",
    ImageIdOrSlug = "ubuntu-16-04-x64",
    SizeSlug = "512mb",
    SshIdsOrFingerprints = new List<object>() { keyFingerprint }
};
var ip = GetDropletIp(digitalOceanClient, 
    digitalOceanClient.Droplets.Create(dropletModel).Result.Id);
Thread.Sleep(30000);

Таймаут в конце нужен потому, что сервер не сразу принимает SSH подключения.
Метод GetDropletIp нужен для того, чтобы дождаться создания дроплета, т.к. IP адрес доступен не сразу.

private static string GetDropletIp(DigitalOceanClient client, 
    int dropletId)
{
    while (true)
    {
        var droplet = client.Droplets.Get(dropletId).Result;
        if (droplet.Status == "active")
            return droplet.Networks.v4.First().IpAddress;
        Thread.Sleep(5000);
    }
}

Подключение по SSH и установка squid

Для работы с SSH я использую пакет SSH.NET.

using Renci.SshNet;
...
PrivateKeyFile privateKeyFile = 
    new PrivateKeyFile(@"path/to/private/key");
AuthenticationMethod authenticationMethod = 
    new PrivateKeyAuthenticationMethod("root", privateKeyFile);
var connectionInfo = new ConnectionInfo(ip, 
    "root", authenticationMethod);
using (var sshClient = new SshClient(connectionInfo))
{

    sshClient.HostKeyReceived += (sender, e) =>
    {
        e.CanTrust = true;
    };

    sshClient.Connect();
    ExecuteCommand(sshClient, "sudo apt-get update");
    using (var stream = sshClient.CreateShellStream(
        "customCommand", 80, 24, 800, 600, 1024))
    {
        SendCommandToStream(stream, "sudo apt-get install squid");
        SendCommandToStream(stream, "y");
    }
    ExecuteCommand(sshClient, 
        "sed -i 's/http_access deny all/http_access allow all/' /etc/squid/squid.conf");
    ExecuteCommand(sshClient, "sudo systemctl restart squid.service");
    sshClient.Disconnect();
}

Вспомогательные методы:

private static void SendCommandToStream(ShellStream stream, string cmd)
{
    var reader = new StreamReader(stream);
    var writer = new StreamWriter(stream);
    writer.AutoFlush = true;
    writer.WriteLine(cmd);
    while (stream.Length == 0)
        Thread.Sleep(500);
    while ((reader.ReadLine()) != null)
        Thread.Sleep(2000);
}

static void ExecuteCommand(SshClient sshClient, string commandText)
{
    using (var command = sshClient.CreateCommand(commandText))
    {
        command.Execute();
    }
}        

Как видно используется два способа выполнить команду. Более сложный вариант со стримом нужен потому, что команда sudo apt-get install squid спрашивает подтверждение.

Использование с .net core

Единственная проблема при запуске этого кода на ubuntu заключалась в ссылках на устаревшие пакеты. У меня заработала такая комбинация версий пакетов:

< PackageReference Include="DigitalOcean.API" Version="1.0.7" />
< PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
< PackageReference Include="RestSharp" Version="105.0.0" />
< PackageReference Include="ssh.net" Version="2016.1.0" />