internal class InstallManager
{
#region Return Models
internal class InstallManagerResult
{
public bool Success { get; set; }
public string Error { get; set; }
public string Message { get; set; }
}
#endregion Return Models
internal InstallManagerResult SetupNewServer(Servers servers)
{
InstallManagerResult result = new InstallManagerResult();
using (ApplicationDbContext db = new ApplicationDbContext())
{
// Check the username, if its blank assume root
if (string.IsNullOrEmpty(servers.SSHUser))
{
servers.SSHUser = "root";
}
try
{
// Construct the ssh session.
using (SshClient ssh = new SshClient(servers.Address, servers.SSHUser, servers.SSHPassword))
{
// Connect to the server
ssh.Connect();
if (!ssh.IsConnected)
{
throw new Exception("The host connection failed. Check the server details and try again");
}
// Update system and install docker.io
ssh.RunCommand("sudo -S <<< " + servers.SSHPassword + " apt-get update -y");
ssh.RunCommand("sudo -S <<< " + servers.SSHPassword + " apt-get install -y docker.io");
// System is updated and docker is installed - It may or may not have started already but
// dont get hung on that right now, its present state is not relevant
// Cleanup APT
ssh.RunCommand("sudo -S <<< " + servers.SSHPassword + " apt-get clean -y");
// Configure DOCKER for SSL
// NOTE: Since all certificates are generated on the command line we have to pass passwords to and from make sure you
// clear the historys at the end.
// The SSL Certs are the be all and end all, you should guard them
ssh.RunCommand("openssl genrsa -aes256 -passout pass:" + servers.SSHPassword + " -out ca-key.pem 4096");
ssh.RunCommand(@"openssl req -new -x509 -days 365 -key ca-key.pem -sha256 -passin pass:" + servers.SSHPassword + @" -out ca.pem -subj ""/C=GB/ST=London/L=London/O=Global Security/OU=IT Department/CN=domain.local"" ");
ssh.RunCommand("openssl genrsa -out server-key.pem 4096");
// Construct a string we can modify, since the CN=HOST should be CN=host.domain.local Still with me?
string a = @"openssl req -subj ""/CN=" + servers.Host + "." + servers.DNSAddress + @""" -sha256 -new -key server-key.pem -out server.csr";
// Run the replace
ssh.RunCommand(a);
// As earlier - construct the string so we can inject / replace the correct value
string b = "echo subjectAltName = DNS:" + servers.Host + "." + servers.DNSAddress + ",IP:" + servers.Address + " >> extfile.cnf";
ssh.RunCommand(b);
ssh.RunCommand("echo extendedKeyUsage = serverAuth >> extfile.cnf");
ssh.RunCommand("openssl x509 -req -days 365 -sha256 -in server.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -passin pass:" + servers.SSHPassword + " -out server-cert.pem -extfile extfile.cnf");
ssh.RunCommand("openssl genrsa -passout pass:" + servers.SSHPassword + " -out key.pem 4096");
ssh.RunCommand("openssl req -subj '/CN=client' -new -key key.pem -out client.csr");
ssh.RunCommand("echo extendedKeyUsage = clientAuth > extfile-client.cnf");
ssh.RunCommand("openssl x509 -req -days 1000 -sha256 -in client.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -passin pass:" + servers.SSHPassword + " -out cert.pem -extfile extfile-client.cnf");
// Generate the PFX
ssh.RunCommand("openssl pkcs12 -export -inkey key.pem -in cert.pem -passout=pass:" + servers.CertificateKey + " -out " + servers.CertificateFile + ".pfx");
ssh.RunCommand("cp *pfx .docker/");
// Make certs available to docker
ssh.RunCommand("sudo -S <<< " + servers.SSHPassword + " mkdir /etc/docker/ssl");
ssh.RunCommand("sudo -S <<< " + servers.SSHPassword + " cp /home/mark/ca.pem /etc/docker/ssl/ca.pem");
ssh.RunCommand("sudo -S <<< " + servers.SSHPassword + " cp /home/mark/server-cert.pem /etc/docker/ssl/server-cert.pem");
ssh.RunCommand("sudo -S <<< " + servers.SSHPassword + " cp /home/mark/server-key.pem /etc/docker/ssl/server-key.pem");
ssh.RunCommand("sudo -S <<< " + servers.SSHPassword + " cp /home/mark/key.pem /etc/docker/ssl/key.pem");
ssh.RunCommand("sudo -S <<< " + servers.SSHPassword + " chown mark /etc/docker/ssl/*");
// Secure the files
ssh.RunCommand("chmod -v 0400 ca-key.pem key.pem server-key.pem");
ssh.RunCommand("chmod -v 0444 ca.pem server-cert.pem cert.pem");
// Configure for docker use
ssh.RunCommand("mkdir -pv ~/.docker");
ssh.RunCommand("cp client.csr server.csr extfile.cnf extfile-client.cnf ~/.docker");
ssh.RunCommand("cp " + servers.Host + ".pfx ~/.docker");
//// Put the files where dockers told to find them
ssh.RunCommand("cp -v {ca,cert,key}.pem ~/.docker");
//// Tell docker where to find trhem, again as above a injection / replacement will correct the value
string c = "export DOCKER_HOST=tcp://" + servers.Address + ":2376 DOCKER_TLS_VERIFY=1";
ssh.RunCommand(c);
var upload = new FileStream(@"D:\Certificates\base3.txt", System.IO.FileMode.Open);
using (SftpClient ftp = new SftpClient(servers.Address, servers.SSHUser, servers.SSHPassword))
{
// Connect
ftp.Connect();
ftp.UploadFile(upload, "/home/mark/docker22.json");
ftp.Dispose();
}
upload.Dispose();
ssh.RunCommand("sed -i 's/{HOST}/" + servers.Address + "/g' /home/mark/docker22.json");
ssh.RunCommand("sudo -S <<< " + servers.SSHPassword + " cp /home/mark/docker22.json /etc/docker/daemon.json");
ssh.RunCommand("sudo -S <<< " + servers.SSHPassword + " service docker stop");
ssh.RunCommand("sudo dockerd &");
System.Threading.Thread.Sleep(5000);
// Docker is now configured
// Were done with the SSH Connection
ssh.Disconnect();
// Dispose it, we dont need it at all
ssh.Dispose();
}
}
catch (SshException sEx)
{
result.Success = false;
result.Error = sEx.Message;
return result;
}
catch (Exception ex)
{
result.Success = false;
result.Error = ex.Message;
return result;
}
try
{
// Establish scp session for file transfer
using (SftpClient ftp = new SftpClient(servers.Address, servers.SSHUser, servers.SSHPassword))
{
// Connect
ftp.Connect();
if (ftp.IsConnected)
{
// If we connect, we need to check the local cert store is available, and if not create it
// couldnt be bothered with anything complex
if (!Directory.Exists(@"D:\Certificates"))
{
Directory.CreateDirectory(@"D:\Certificates");
}
// This is wrong, but the methods valid.
// Foreach file in the remote .docker directory, we need to get each certificate
foreach (Renci.SshNet.Sftp.SftpFile file in ftp.ListDirectory("/home/mark/.docker"))
{
// Make sure the certstore for this SERVER exists
if (!Directory.Exists(@"D:\Certificates\" + servers.Host))
{
Directory.CreateDirectory(@"D:\Certificates\" + servers.Host);
}
// Download the file
try
{
if (file.Name == ".." || file.Name == ".")
{
}
else
{
using (Stream fileStream = System.IO.File.Create(@"D:\Certificates\" + servers.Host + @"\" + file.Name))
{
ftp.DownloadFile(file.FullName, fileStream);
}
}
}
catch (Exception ex)
{
result.Success = false;
result.Error = file.Name;
return result;
}
// upload
}
}
// We dont need the ftp, at all so disconnect and dispose
ftp.Disconnect();
ftp.Dispose();
}
}
catch (Exception ex)
{
result.Success = false;
result.Error = ex.Message;
return result;
}
try
{
ServicePointManager.ServerCertificateValidationCallback += (o, c, ch, er) => true;
// Todo: Move this to class & Task
CertificateCredentials credentials = new CertificateCredentials(new X509Certificate2(@"D:\Certificates\docker\docker55.pfx", "password."));
credentials.ServerCertificateValidationCallback += (o, c, ch, er) => true;
//DockerClientConfiguration config = new DockerClientConfiguration(new Uri("tcp://" + servers.Host + "." + servers.DNSAddress + ":2376"), credentials, default);
DockerClientConfiguration config = new DockerClientConfiguration(new Uri("tcp://" + servers.Address + ":2376"), credentials, default);
// Because its not immediatly aparant what this line does, it tells the server not to validate the certificates, since the CSR are all
// self signed and thus normally invalid
DockerClient client = config.CreateClient();
// Attempt to talk to docker
VersionResponse chk = client.System.GetVersionAsync().GetAwaiter().GetResult();
if (chk == null)
{
throw new Exception("Connection failed");
}
// if no error, all good.
}
catch (Exception ex)
{
result.Success = false;
result.Error = ex.Message;
return result;
}
db.Servers.Add(servers);
db.SaveChanges();
result.Success = true;
result.Error = "";
result.Message = "Ok";
return result;
}
}
}