示例#1
0
        /// <summary>
        /// Retrieves the VPN's certificate authority files from the hive Vault.
        /// </summary>
        /// <returns>The <see cref="VpnCaFiles"/>.</returns>
        private VpnCaFiles GetVpnCaFiles()
        {
            var manager  = hive.GetReachableManager();
            var response = manager.SudoCommand($"export VAULT_TOKEN={hiveLogin.VaultCredentials.RootToken} && vault read -format=json /neon-secret/vpn/ca.zip.encrypted", RunOptions.Redact);

            if (response.ExitCode != 0)
            {
                Console.Error.Write(response.ErrorText);
                Program.Exit(response.ExitCode);
            }

            var rObject  = JObject.Parse(response.OutputText);
            var dObject  = (JObject)rObject.GetValue("data");
            var zipBytes = Convert.FromBase64String((string)dObject.GetValue("value"));

            return(VpnCaFiles.LoadZip(zipBytes, hiveLogin.VpnCredentials.CaZipKey));
        }
示例#2
0
        /// <summary>
        /// Configures OpenVPN on a manager node.
        /// </summary>
        /// <param name="manager">The manager.</param>
        private void ConfigManagerVpn(SshProxy <NodeDefinition> manager)
        {
            // Upload the setup and configuration files.
            //
            // NOTE:
            //
            // These steps are redundant and will be repeated during the
            // common node configuration, but we need some of the scripts
            // here, before that happens.

            manager.CreateHiveHostFolders();
            manager.UploadConfigFiles(hive.Definition);
            manager.UploadResources(hive.Definition);

            // Install OpenVPN.

            manager.Status = "vpn install";
            manager.SudoCommand("safe-apt-get update");
            manager.SudoCommand("safe-apt-get install -yq openvpn");

            // Configure OpenVPN.

            var nodesSubnet      = NetworkCidr.Parse(hive.Definition.Network.NodesSubnet);
            var vpnSubnet        = NetworkCidr.Parse(manager.Metadata.VpnPoolSubnet);
            var duplicateCN      = hive.Definition.Vpn.AllowSharedCredentials ? "duplicate-cn" : ";duplicate-cn";
            var vpnServerAddress = NetHelper.UintToAddress(NetHelper.AddressToUint(vpnSubnet.Address) + 1);

            var serverConf =
                $@"#------------------------------------------------------------------------------
# OpenVPN config file customized for the [{manager.Name}] neonHIVE manager node.

# OpenVPN listening port.
port {NetworkPorts.OpenVPN}

# Enable TCP and/or UDP transports.
proto tcp
;proto udp

# Set packet tunneling mode.
dev tun

# SSL/TLS root certificate (ca), certificate
# (cert), and private key (key).  Each client
# and the server must have their own cert and
# key file.  The server and all clients will
# use the same ca file.
#
# See the [easy-rsa] directory for a series
# of scripts for generating RSA certificates
# and private keys.  Remember to use
# a unique Common Name for the server
# and each of the client certificates.
#
# Any X509 key management system can be used.
# OpenVPN can also use a PKCS #12 formatted key file
# (see [pkcs12] directive in man page).
ca ca.crt
cert server.crt
key server.key  # This file should be kept secret

# Diffie hellman parameters (2048-bit) generated via:
# 
#   openssl dhparam -out dhparam.pem 2048
# 
dh dhparam.pem

# The currently recommended topology.
topology subnet

# Configure server mode and supply a VPN subnet
# for OpenVPN to draw client addresses from.
# The server will take {vpnServerAddress} for itself,
# the rest will be made available to clients.
# Each client will be able to reach the server
# on {vpnServerAddress}. Comment this line out if you are
# ethernet bridging. See the man page for more info.
server {vpnSubnet.Address} {vpnSubnet.Mask}

# Maintain a record of client  virtual IP address
# associations in this file.  If OpenVPN goes down or
# is restarted, reconnecting clients can be assigned
# the same virtual IP address from the pool that was
# previously assigned.
;ifconfig-pool-persist ipp.txt

# Push routes to the client to allow it
# to reach other private subnets behind
# the server.  Remember that these
# private subnets will also need
# to know to route the OpenVPN client
# address pool ({vpnSubnet.Address})
# back to this specific OpenVPN server.
push ""route {nodesSubnet.Address} {nodesSubnet.Mask}""

# Uncomment this directive if multiple clients
# might connect with the same certificate/key
# files or common names.  This is recommended
# only for testing purposes.  For production use,
# each client should have its own certificate/key
# pair.
{duplicateCN}

# The keepalive directive causes ping-like
# messages to be sent back and forth over
# the link so that each side knows when
# the other side has gone down.
# Ping every 10 seconds, assume that remote
# peer is down if no ping received during
# a 120 second time period.
keepalive 10 120

# For extra security beyond that provided
# by SSL/TLS, create an [HMAC firewall]
# to help block DoS attacks and UDP port flooding.
#
# Generate with:
#   openvpn --genkey --secret ta.key
#
# The server and each client must have
# a copy of this key.
# The second parameter should be '0'
# on the server and '1' on the clients.
tls-auth ta.key 0 # This file is secret

# Select a cryptographic cipher.
# This config item must be copied to
# the client config file as well.
cipher AES-256-CBC 

# Enable compression on the VPN link.
# Don't enable this unless it is also
# enabled in the client config file.
#
# We're not enabling this due to the
# VORACLE security vulnerablity:
#
#   https://community.openvpn.net/openvpn/wiki/VORACLE
#

# The maximum number of concurrently connected
# clients we want to allow.
max-clients {VpnOptions.ServerAddressCount - 2}

# This macro sets the TCP_NODELAY socket flag on 
# the server as well as pushes it to connecting
# clients. The TCP_NODELAY flag disables the Nagle
# algorithm on TCP sockets causing packets to be
# transmitted immediately with low latency, rather
# than waiting a short period of time in order to 
# aggregate several packets into a larger containing
# packet. In VPN applications over TCP, TCP_NODELAY
# is generally a good latency optimization.
tcp-nodelay

# It's a good idea to reduce the OpenVPN
# daemon's privileges after initialization.
#
# You can uncomment this out on
# non-Windows systems.
;user nobody
;group nobody

# The persist options will try to avoid
# accessing certain resources on restart
# that may no longer be accessible because
# of the privilege downgrade.
persist-key
persist-tun

# Output a short status file showing
# current connections, truncated
# and rewritten every minute.
status openvpn-status.log

# By default, log messages will go to the syslog (ork
# on Windows, if running as a service, they will go to
# the [\Program Files\OpenVPN\log] directory).
# Use log or log-append to override this default.
# [log] will truncate the log file on OpenVPN startup,
# while [log-append] will append to it.  Use one
# or the other (but not both).
log         /var/log/openvpn.log
;log-append  openvpn.log

# Set the appropriate level of log
# file verbosity.
#
# 0 is silent, except for fatal errors
# 4 is reasonable for general usage
# 5 and 6 can help to debug connection problems
# 9 is extremely verbose
verb 4

# Silence repeating messages.  At most 20
# sequential messages of the same message
# category will be output to the log.
;mute 20
";

            manager.Status = "vpn config";
            manager.SudoCommand("mkdir -p /etc/openvpn");
            manager.UploadText("/etc/openvpn/server.conf", serverConf);

            manager.UploadText("/etc/openvpn/ca.crt", vpnCaFiles.GetCert("ca"));
            manager.UploadText("/etc/openvpn/server.crt", vpnCaFiles.GetCert("server"));
            manager.UploadText("/etc/openvpn/server.key", vpnCaFiles.GetKey("server"));
            manager.SudoCommand("chmod 600", "/etc/openvpn/server.key");    // This is a secret!

            manager.UploadText("/etc/openvpn/ta.key", vpnCaFiles.GetTaKey());
            manager.SudoCommand("chmod 600", "/etc/openvpn/ta.key");        // This is a secret too!

            manager.UploadText("/etc/openvpn/dhparam.pem", vpnCaFiles.GetDHParam());

            // Initialize the [root] user's credentials.

            vpnCredentials =
                new VpnCredentials()
            {
                CaCert   = vpnCaFiles.GetCert("ca"),
                UserCert = vpnCaFiles.GetCert(HiveConst.RootUser),
                UserKey  = vpnCaFiles.GetKey(HiveConst.RootUser),
                TaKey    = vpnCaFiles.GetTaKey(),
                CaZipKey = VpnCaFiles.GenerateKey(),
                CaZip    = vpnCaFiles.ToZipBytes()
            };

            // Upload the initial (empty) Certificate Revocation List (CRL) file and then
            // upload a OpenVPN systemd unit drop-in so that it will recognize revoked certificates.

            manager.UploadText("/etc/openvpn/crl.pem", vpnCaFiles.GetFile("crl.pem"));
            manager.SudoCommand("chmod 664", "/etc/openvpn/crl.pem");    // OpenVPN needs to be able to read this after having its privileges downgraded.

            var openVpnUnit =
                @"[Unit]
Description=OpenVPN connection to %i
PartOf=openvpn.service
ReloadPropagatedFrom=openvpn.service
Before=systemd-user-sessions.service
Documentation=man:openvpn(8)
Documentation=https://community.openvpn.net/openvpn/wiki/Openvpn23ManPage
Documentation=https://community.openvpn.net/openvpn/wiki/HOWTO

[Service]
PrivateTmp=true
KillMode=mixed
Type=forking
ExecStart=/usr/sbin/openvpn --daemon ovpn-%i --status /run/openvpn/%i.status 10 --cd /etc/openvpn --script-security 2 --config /etc/openvpn/%i.conf --writepid /run/openvpn/%i.pid --crl-verify /etc/openvpn/crl.pem
PIDFile=/run/openvpn/%i.pid
ExecReload=/bin/kill -HUP $MAINPID
WorkingDirectory=/etc/openvpn
ProtectSystem=yes
CapabilityBoundingSet=CAP_IPC_LOCK CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW CAP_SETGID CAP_SETUID CAP_SYS_CHROOT CAP_DAC_READ_SEARCH CAP_AUDIT_WRITE
LimitNPROC=10
DeviceAllow=/dev/null rw
DeviceAllow=/dev/net/tun rw

[Install]
WantedBy=multi-user.target
";

            manager.UploadText("/etc/systemd/system/[email protected]", openVpnUnit);
            manager.SudoCommand("chmod 644 /etc/systemd/system/[email protected]");

            // Do a daemon-reload so systemd will be aware of the new drop-in.

            manager.SudoCommand("systemctl disable openvpn");
            manager.SudoCommand("systemctl daemon-reload");

            // Enable and restart OpenVPN.

            manager.SudoCommand("systemctl enable openvpn");
            manager.SudoCommand("systemctl restart openvpn");

            //-----------------------------------------------------------------
            // SPECIAL NOTE:
            //
            // I figured out that I need this lovely bit of code after banging my head on the desk for
            // 12 freaking days.  The problem was getting OpenVPN to work in Windows Azure (this will
            // also probably impact other cloud environments).
            //
            // Azure implements VNETs as layer 3 overlays.  This means that the host network interfaces
            // are not actually on an ethernet segment and the VPN default gateway is actually handling
            // all of the ARP packets, routing between the VNET subnets, load balancers, and the Internet.
            // This is problematic for OpenVPN traffic because the VPN client IP address space is not
            // part of the VNET which means the VNET gateway is not able to route packets from hive
            // hosts back to the manager's OpenVPN client addresses by default.
            //
            // The solution is to configure the managers with secondary NIC cards in a different subnet
            // and provision special Azure user-defined routes that direct VPN return packets to the
            // correct manager.
            //
            // I figured this part out the second day.  The problem was though that it simply didn't work.
            // From an external VPN client, I would try to ping a worker node through OpenVPN running on
            // a manager.  I'd see the ping traffic:
            //
            //      1. manager/tun0: request
            //      2. manager/eth1: request
            //      3. worker/eth0: request
            //      4. worker/eth0: reply
            //      5. manager/eth0: reply
            //      6: NOTHING! EXPECTED: manager/tun0: reply
            //
            // So the problem was that I could see the ICMP ping request hit the various interfaces
            // on the manager and be received by the worker.  I'd then see the worker send the reply,
            // and be routed via the user-defined Azure rult back to the manager.  The problem was
            // that the packet was simply dropped there.  It never made it back to tun0 so OpenVPN
            // could forward it back to the client.
            //
            // After days and days of trying to learn about Linux routing, iptables and policy rules,
            // I finally ran across this posting for the second time:
            //
            //      https://unix.stackexchange.com/questions/21093/output-traffic-on-different-interfaces-based-on-destination-port
            //
            // This was the key.  I ran across this a few days ago and didn't read it closely enough.
            // It made more sense after learning more about this stuff.
            //
            // Linux has a built-in IP address spoofing filter enabled by default.  This filter has the
            // kernel discard any packets whose source address doesn't match the IP address/route implied
            // by the remote interface that transmitted the packet.  This is exactly what's happening
            // when Azure forwards the VPN return packets via the user-defined route.  I'd see return
            // packets hit eth0 on the manager, be processed by the low-level RAW and MANGLE iptables
            // and then they'd disappear.
            //
            // The solution is simply to disable the spoofing filter.  I'm going to go ahead and do this
            // for all interfaces which should be fine for hives hosted in cloud environments, because the
            // VNET/Load Balancer/Security Groups will be used to lock things down.  Local hives will
            // need to be manually placed behind a suitable router/firewall as well.
            //
            // For robustness, I'm going to deploy this as a service daemon that polls the filter state
            // for each interface every 5 seconds, and disables any enabled filters.  This will ensure
            // that the filters will always be disabled, even as interfaces are bought up and down.

            var disableSpoofUnit =
                $@"[Unit]
Description=Disable Network Anti-Spoofing Filters
Documentation=
After=
Requires=
Before=

[Service]
Type=simple
ExecStart={HiveHostFolders.Bin}/disable-spoof-filters.sh

[Install]
WantedBy=multi-user.target
";

            var disableSpoofScript =
                @"#!/bin/bash
#------------------------------------------------------------------------------
# This script is a deployed as a service to ensure that the Linux anti-spoofing
# filters are disabled for the network interfaces on manager nodes hosting
# OpenVPN.  This is required to allow VPN return traffic from other nodes to
# routed back to tun0 and ultimately, connected VPN clients.
#
# Note that it appears that we need to disable the filter for all interfaces
# for this to actually work.

while :
do
    flush=false

    for f in /proc/sys/net/ipv4/conf/*/rp_filter
    do
        filter_enabled=$(cat $f)

        if [ ""$filter_enabled"" == ""1"" ] ; then
            echo 0 > $f
            flush=true
        fi
    done

    if [ ""$flush"" == ""true"" ] ; then
      echo 1 > /proc/sys/net/ipv4/route/flush
    fi

    sleep 5
done";

            manager.UploadText("/lib/systemd/system/disable-spoof-filters.service", disableSpoofUnit);
            manager.SudoCommand("chmod 644 /lib/systemd/system/disable-spoof-filters.service");

            manager.UploadText($"{HiveHostFolders.Bin}/disable-spoof-filters.sh", disableSpoofScript);
            manager.SudoCommand($"chmod 770 {HiveHostFolders.Bin}/disable-spoof-filters.sh");

            manager.SudoCommand("systemctl enable disable-spoof-filters");
            manager.SudoCommand("systemctl restart disable-spoof-filters");
        }
示例#3
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);
                }
            }
        }
示例#4
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();
            }
        }
示例#5
0
        /// <summary>
        /// Lists the known certificates.
        /// </summary>
        /// <param name="vpnCaFiles">The VPN user certificates.</param>
        /// <returns>The user certificate information.</returns>
        private List <CertInfo> ListCerts(VpnCaFiles vpnCaFiles)
        {
            // The issued certificates are listed in the [index.txt] file within the encrypted
            // certificate authority ZIP file stored in the Vault at [/neon-secret/vpn/ca.zip.encrypted].
            // This file is formatted something like:
            //
            //      V	30160714162952Z	    D4C549184FCBD2FC	unknown	/C=US/O=dev-vpn-ca/CN=ca
            //      V   30160714163000Z     D4C549184FCBD2FD    unknown /C=US/O=dev-vpn-ca/CN=server
            //      V   30160714163001Z     D4C549184FCBD2FE    unknown /C=US/O=dev-vpn-ca/CN=root
            //
            // where:
            //
            //      Column 0: V or R, indicating valid or revoked
            //      Column 1: expiration date
            //      Column 2: appears to always be empty
            //      Column 3: certificate serial number (or thumbprint)
            //      Column 4: ??
            //      Column 5: country, organization, and common name
            //
            // Note that the columns are separed by TABs.

            var indexText = vpnCaFiles.GetFile("index.txt");

            // We're filter out the [ca] and [server] certificates and then output details for
            // the remaining user certificates.

            var certificates = new List <CertInfo>();

            using (var reader = new StringReader(indexText))
            {
                foreach (var line in reader.Lines())
                {
                    if (string.IsNullOrWhiteSpace(line))
                    {
                        continue;
                    }

                    var columns = line.Split('\t');

                    if (columns.Length != 6)
                    {
                        continue;
                    }

                    // Column[1] in [index.txt] is the certificate expiration date.
                    // OpenSSL appears to generate two different formats, one with
                    // a 2-digit year like:
                    //
                    //      yyMMddHHmmssZ
                    //
                    // and one with a 4-digit year like:
                    //
                    //      yyyyMMddHHmmssZ
                    //
                    // This seems a bit strange but we'll go with the flow and choose
                    // the format based on the string length.

                    DateTime validUntil;

                    if (columns[1].Length == "yyMMddHHmmssZ".Length)
                    {
                        validUntil = DateTime.ParseExact(columns[1], "yyMMddHHmmssZ", CultureInfo.InvariantCulture);
                    }
                    else
                    {
                        validUntil = DateTime.ParseExact(columns[1], "yyyyMMddHHmmssZ", CultureInfo.InvariantCulture);
                    }

                    var info =
                        new CertInfo()
                    {
                        IsValid    = columns[0] == "V",
                        ValidUntil = validUntil,
                        Thumbprint = columns[3].ToLowerInvariant()
                    };

                    var pos = columns[5].IndexOf("/CN=");

                    if (pos == -1)
                    {
                        continue;
                    }

                    info.Name = columns[5].Substring(pos + "/CN=".Length);

                    certificates.Add(info);
                }
            }

            return(certificates);
        }
示例#6
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();
            }
        }