示例#1
0
        /// <summary>
        /// Creates a new hive login.
        /// </summary>
        /// <param name="commandLine">The command line.</param>
        private void UserCreate(CommandLine commandLine)
        {
            DirectNotAllowed();

            var username = commandLine.Arguments.FirstOrDefault();

            if (string.IsNullOrEmpty(username))
            {
                Console.Error.WriteLine("***ERROR: USER argument is required.");
                Program.Exit(1);
            }

            var isUserValid = true;

            foreach (var ch in username)
            {
                if (!(char.IsLetterOrDigit(ch) || ch == '.' || ch == '_' || ch == '-'))
                {
                    isUserValid = false;
                    break;
                }
            }

            if (!isUserValid)
            {
                Console.WriteLine($"***ERROR: USER [{username}] is not valid.  Only letters, digits, periods, underscores, or dashes are allowed.");
                Program.Exit(1);
            }

            switch (username.ToLowerInvariant())
            {
            case "ca":
            case "dhparam":
            case "server":

                Console.WriteLine($"***ERROR: USER [{username}] is reserved by neonHIVE.  Please choose another name.");
                Program.Exit(1);
                break;
            }

            var daysOption = commandLine.GetOption("--days", "365");
            int days       = 365;

            if (string.IsNullOrEmpty(daysOption) || !int.TryParse(daysOption, out days) || days <= 0)
            {
                Console.WriteLine($"***ERROR: [--days={daysOption}] is not valid.  This must be a positive integer.");
                Program.Exit(1);
            }

            var rootPrivileges = commandLine.HasOption("--root");

            RootLogin();

            Directory.CreateDirectory(caFolder);

            try
            {
                // Retrieve the VPN certificate authority ZIP archive from Vault and extract
                // its contents to a temporary folder.

                var caZipBytes = hive.Vault.Client.ReadBytesAsync("neon-secret/vpn/ca.zip.encrypted").Result;
                var vpnCaFiles = VpnCaFiles.LoadZip(caZipBytes, hiveLogin.VpnCredentials.CaZipKey);

                vpnCaFiles.Extract(caFolder);

                // Initialize the file paths.
                //
                // IMPORTANT:
                //
                // Do not change these file names because the [VpnCaFiles] class
                // depends on this naming convention.

                var indexPath     = Path.Combine(caFolder, "index.txt");
                var caSignCnfPath = Path.Combine(caFolder, "ca-sign.cnf");
                var caCnfPath     = Path.Combine(caFolder, "ca.cnf");
                var caKeyPath     = Path.Combine(caFolder, "ca.key");
                var caReqPath     = Path.Combine(caFolder, "ca.req");
                var caCrtPath     = Path.Combine(caFolder, "ca.crt");
                var dhParamPath   = Path.Combine(caFolder, "dhparam.pem");
                var serverCnfPath = Path.Combine(caFolder, "server.cnf");
                var serverKeyPath = Path.Combine(caFolder, "server.key");
                var serverReqPath = Path.Combine(caFolder, "server.req");
                var serverCrtPath = Path.Combine(caFolder, "server.crt");
                var userCnfPath   = Path.Combine(caFolder, $"{username}.cnf");
                var userReqPath   = Path.Combine(caFolder, $"{username}.req");
                var userKeyPath   = Path.Combine(caFolder, $"{username}.key");
                var userCrtPath   = Path.Combine(caFolder, $"{username}.crt");
                var taKeyPath     = Path.Combine(caFolder, "ta.key");
                var crlnumberPath = Path.Combine(caFolder, "crlnumber");
                var crlPath       = Path.Combine(caFolder, "crl.pem");

                // Build the new user client login.

                File.WriteAllText(userCnfPath, GetClientConfig(hive.Definition, username, rootPrivileges));

                Program.Execute("openssl", "req", "-new",
                                "-config", userCnfPath,
                                "-keyout", userKeyPath,
                                "-out", userReqPath);

                Program.Execute("openssl", "ca", "-batch",
                                "-config", caSignCnfPath,
                                "-days", days,
                                "-out", userCrtPath,
                                "-in", userReqPath);

                // Generate the new hive login file and also write its name
                // to [new-login.txt] so the outer shim will know what it is.

                var newLogin = hiveLogin.Clone();

                newLogin.Username = username;
                newLogin.VpnCredentials.UserCert = VpnCaFiles.NormalizePem(File.ReadAllText(userCrtPath));
                newLogin.VpnCredentials.UserKey  = File.ReadAllText(userKeyPath);

                if (!rootPrivileges)
                {
                    newLogin.ClearRootSecrets();
                }

                File.WriteAllText($"{username}@{newLogin.HiveName}.login.json", NeonHelper.JsonSerialize(newLogin, Formatting.Indented));
                File.WriteAllText("new-login.txt", $"{username}@{newLogin.HiveName}.login.json");

                // ZIP the CA files and store them to the hive Vault.

                vpnCaFiles = VpnCaFiles.LoadFolder(caFolder);

                vpnCaFiles.Clean();
                hive.Vault.Client.WriteBytesAsync("neon-secret/vpn/ca.zip.encrypted", vpnCaFiles.ToZipBytes()).Wait();
            }
            finally
            {
                Directory.Delete(caFolder, recursive: true);
                HiveHelper.CloseHive();
            }
        }
示例#2
0
        /// <summary>
        /// Initializes the VPN certificate authority as well as the server and root user's certificates and keys.
        /// </summary>
        private void CreateVpnCredentials()
        {
            // This is a bit tricky: We're going to invoke the [neon vpn ca ...] command to
            // initialize the hive's certificate authority files.  This command must be
            // run in the [neon-cli] container so we need to detect whether we're already
            // running in the tool container and do the right thing.
            //
            // Note that the we can't pass the original hive definition file to the command
            // because the command will shim into a [neon-cli] container and any environment
            // variable references within the definition won't be able to be resolved because
            // the environment variables aren't mapped into the container.
            //
            // The solution is to persist a temporary copy of the loaded hive definition
            // that has already resolved environment variables to the neonFORGE temp folder
            // and pass that.  The user's neonFORGE folder is encrypted in place so doing this
            // will be as safe as storing hive logins there.

            string tempCaFolder;
            string tempDefPath;

            if (HiveHelper.InToolContainer)
            {
                tempCaFolder = "/shim/ca";
            }
            else
            {
                tempCaFolder = Path.Combine(Program.HiveTempFolder, Guid.NewGuid().ToString("D"));
            }

            tempDefPath = Path.Combine(HiveHelper.GetTempFolder(), $"{Guid.NewGuid().ToString("D").ToLowerInvariant()}.def.json");

            File.WriteAllText(tempDefPath, NeonHelper.JsonSerialize(hive.Definition, Formatting.Indented));

            try
            {
                Directory.CreateDirectory(tempCaFolder);

                // Execute the [neon vpn cert init ...] command to generate the certificate
                // authority files and the root client certificate and key and then load
                // the files into a [VpnCaFiles] object.

                var neonExecutable = NeonHelper.IsWindows ? "neon.cmd" : "neon";

                if (HiveHelper.InToolContainer)
                {
                    Program.Execute(neonExecutable, "vpn", "ca", tempDefPath, tempCaFolder);
                }
                else
                {
                    Program.Execute(neonExecutable, "vpn", "ca", tempDefPath, tempCaFolder);
                }

                vpnCaFiles = VpnCaFiles.LoadFolder(tempCaFolder);
            }
            finally
            {
                if (Directory.Exists(tempCaFolder))
                {
                    Directory.Delete(tempCaFolder, recursive: true);
                }

                if (File.Exists(tempDefPath))
                {
                    File.Delete(tempDefPath);
                }
            }
        }
示例#3
0
        /// <summary>
        /// Revokes a user certificate.
        /// </summary>
        /// <param name="commandLine">The command line.</param>
        private void UserRevoke(CommandLine commandLine)
        {
            DirectNotAllowed();

            var restartVpn = commandLine.HasOption("--restart-vpn");
            var thumbprint = commandLine.Arguments.FirstOrDefault();

            if (string.IsNullOrEmpty(thumbprint))
            {
                Console.Error.WriteLine("*** ERROR: THUMPRINT expected.");
                Program.Exit(1);
            }

            thumbprint = thumbprint.ToLowerInvariant();

            RootLogin();

            try
            {
                var vpnCaFiles = GetVpnCaFiles();
                var certInfo   = ListCerts(vpnCaFiles).Where(c => c.Thumbprint.ToLowerInvariant() == thumbprint).FirstOrDefault();

                if (certInfo == null)
                {
                    Console.Error.WriteLine($"*** ERROR: Certificate with thumbprint [{thumbprint}] is not known.");
                    Program.Exit(1);
                }

                if (!certInfo.IsValid)
                {
                    Console.Error.WriteLine($"*** ERROR: Certificate with thumbprint [{thumbprint}] is already revoked.");
                    Program.Exit(1);
                }

                // Initialize the file paths.
                //
                // IMPORTANT:
                //
                // Do not change these file names because the [VpnCaFiles] class
                // depends on this naming convention.

                Directory.CreateDirectory(caFolder);

                vpnCaFiles.Extract(caFolder);

                var indexPath     = Path.Combine(caFolder, "index.txt");
                var caSignCnfPath = Path.Combine(caFolder, "ca-sign.cnf");
                var caCnfPath     = Path.Combine(caFolder, "ca.cnf");
                var caKeyPath     = Path.Combine(caFolder, "ca.key");
                var caReqPath     = Path.Combine(caFolder, "ca.req");
                var caCrtPath     = Path.Combine(caFolder, "ca.crt");
                var dhParamPath   = Path.Combine(caFolder, "dhparam.pem");
                var serverCnfPath = Path.Combine(caFolder, "server.cnf");
                var serverKeyPath = Path.Combine(caFolder, "server.key");
                var serverReqPath = Path.Combine(caFolder, "server.req");
                var serverCrtPath = Path.Combine(caFolder, "server.crt");
                var taKeyPath     = Path.Combine(caFolder, "ta.key");
                var crlnumberPath = Path.Combine(caFolder, "crlnumber");
                var crlPath       = Path.Combine(caFolder, "crl.pem");

                // Mark the certificate as revoked.

                Program.Execute("openssl", "ca",
                                "-config", caSignCnfPath,
                                "-crl_reason", "unspecified",
                                "-revoke", $"{Path.Combine(caFolder, thumbprint.ToUpperInvariant())}.pem",
                                "-cert", caCrtPath,
                                "-keyfile", caKeyPath);

                // Generate the new CRL file.

                Program.Execute("openssl", "ca",
                                "-config", caSignCnfPath,
                                "-gencrl",
                                "-out", crlPath);

                // Save the CA files back to the hive Vault.

                vpnCaFiles = VpnCaFiles.LoadFolder(caFolder);

                hive.Vault.Client.WriteBytesAsync("neon-secret/vpn/ca.zip.encrypted", vpnCaFiles.ToZipBytes()).Wait();

                // Write the updated CRL to each manager.

                var crlText = vpnCaFiles.GetFile("crl.pem");

                Console.WriteLine();

                foreach (var manager in hive.Managers)
                {
                    Console.WriteLine($"*** {manager.Name}: Revoking");
                    manager.UploadText("/etc/openvpn/crl.pem", crlText);
                    manager.SudoCommand("chmod 664 /etc/openvpn/crl.pem");
                }

                // Restart OpenVPN on each manager if requested.

                if (restartVpn)
                {
                    Console.WriteLine();

                    foreach (var manager in hive.Managers)
                    {
                        Console.WriteLine($"*** {manager.Name}: Restarting OpenVPN");
                        manager.SudoCommand("systemctl restart openvpn");
                        Thread.Sleep(TimeSpan.FromSeconds(5));
                    }
                }
            }
            finally
            {
                HiveHelper.CloseHive();
            }
        }