Exemple #1
0
 public function doExecute(Manager $args)
 {
     $email = $args->get("email");
     (yield \Amp\resolve($this->checkEmail($email)));
     $server = \Kelunik\AcmeClient\resolveServer($args->get("server"));
     $keyFile = \Kelunik\AcmeClient\serverToKeyname($server);
     $path = "accounts/{$keyFile}.pem";
     $bits = 4096;
     $keyStore = new KeyStore(\Kelunik\AcmeClient\normalizePath($args->get("storage")));
     $this->climate->br();
     try {
         $keyPair = (yield $keyStore->get($path));
         $this->climate->whisper("    Using existing private key ...");
     } catch (KeyStoreException $e) {
         $this->climate->whisper("    No private key found, generating new one ...");
         $keyPair = (new OpenSSLKeyGenerator())->generate($bits);
         $keyPair = (yield $keyStore->put($path, $keyPair));
         $this->climate->whisper("    Generated new private key with {$bits} bits.");
     }
     $acme = $this->acmeFactory->build($server, $keyPair);
     $this->climate->whisper("    Registering with " . substr($server, 8) . " ...");
     /** @var Registration $registration */
     $registration = (yield $acme->register($email));
     $this->climate->info("    Registration successful. Contacts: " . implode(", ", $registration->getContact()));
     $this->climate->br();
     (yield new CoroutineResult(0));
 }
Exemple #2
0
 /**
  * @param Manager $args
  * @return \Generator
  */
 private function doExecute(Manager $args)
 {
     $server = \Kelunik\AcmeClient\resolveServer($args->get("server"));
     $keyName = \Kelunik\AcmeClient\serverToKeyname($server);
     $storage = \Kelunik\AcmeClient\normalizePath($args->get("storage"));
     try {
         $keyStore = new KeyStore($storage);
         (yield $keyStore->get("accounts/{$keyName}.pem"));
         $setup = true;
     } catch (KeyStoreException $e) {
         $setup = false;
     }
     $this->climate->br();
     $this->climate->out("  [" . ($setup ? "<green> ✓ </green>" : "<red> ✗ </red>") . "] " . ($setup ? "Registered on " : "Not yet registered on ") . $server);
     $this->climate->br();
     if ((yield \Amp\File\exists($storage . "/certs/{$keyName}"))) {
         $certificateStore = new CertificateStore($storage . "/certs/{$keyName}");
         $domains = (yield \Amp\File\scandir($storage . "/certs/{$keyName}"));
         foreach ($domains as $domain) {
             $pem = (yield $certificateStore->get($domain));
             $cert = new Certificate($pem);
             $symbol = time() > $cert->getValidTo() ? "<red> ✗ </red>" : "<green> ✓ </green>";
             if (time() < $cert->getValidTo() && time() + $args->get("ttl") * 24 * 60 * 60 > $cert->getValidTo()) {
                 $symbol = "<yellow> ⭮ </yellow>";
             }
             $this->climate->out("  [" . $symbol . "] " . implode(", ", $cert->getNames()));
         }
         $this->climate->br();
     }
 }
 /**
  * @test
  */
 public function itShouldShowUsageWithNoArgs()
 {
     $this->argumentManager->parse(Argument::type('array'))->willThrow(\Exception::class);
     $args = ['phpa'];
     $this->climate->usage($args)->shouldBeCalled();
     $this->cli->handle($args);
 }
Exemple #4
0
 /**
  * @param Manager $args
  * @return \Generator
  */
 private function doExecute(Manager $args)
 {
     $server = \Kelunik\AcmeClient\resolveServer($args->get("server"));
     $server = \Kelunik\AcmeClient\serverToKeyname($server);
     $path = \Kelunik\AcmeClient\normalizePath($args->get("storage")) . "/certs/" . $server;
     $certificateStore = new CertificateStore($path);
     try {
         $pem = (yield $certificateStore->get($args->get("name")));
     } catch (CertificateStoreException $e) {
         $this->climate->br()->error("    Certificate not found.")->br();
         (yield new CoroutineResult(1));
         return;
     }
     $cert = new Certificate($pem);
     $this->climate->br();
     $this->climate->whisper("    Certificate is valid until " . date("d.m.Y", $cert->getValidTo()))->br();
     if ($args->defined("names")) {
         $names = array_map("trim", explode(",", $args->get("names")));
         $missingNames = array_diff($names, $cert->getNames());
         if ($missingNames) {
             $this->climate->comment("    The following names are not covered: " . implode(", ", $missingNames))->br();
             (yield new CoroutineResult(1));
             return;
         }
     }
     if ($cert->getValidTo() > time() + $args->get("ttl") * 24 * 60 * 60) {
         (yield new CoroutineResult(0));
         return;
     }
     $this->climate->comment("    Certificate is going to expire within the specified " . $args->get("ttl") . " days.")->br();
     (yield new CoroutineResult(1));
 }
Exemple #5
0
 private function doExecute(Manager $args)
 {
     $keyStore = new KeyStore(\Kelunik\AcmeClient\normalizePath($args->get("storage")));
     $server = \Kelunik\AcmeClient\resolveServer($args->get("server"));
     $keyFile = \Kelunik\AcmeClient\serverToKeyname($server);
     $keyPair = (yield $keyStore->get("accounts/{$keyFile}.pem"));
     $acme = $this->acmeFactory->build($server, $keyPair);
     $this->climate->br();
     $this->climate->whisper("    Revoking certificate ...");
     $path = \Kelunik\AcmeClient\normalizePath($args->get("storage")) . "/certs/" . $keyFile . "/" . $args->get("name") . "/cert.pem";
     try {
         $pem = (yield \Amp\File\get($path));
         $cert = new Certificate($pem);
     } catch (FilesystemException $e) {
         throw new \RuntimeException("There's no such certificate (" . $path . ")");
     }
     if ($cert->getValidTo() < time()) {
         $this->climate->comment("    Certificate did already expire, no need to revoke it.");
     }
     $names = $cert->getNames();
     $this->climate->whisper("    Certificate was valid for " . count($names) . " domains.");
     $this->climate->whisper("     - " . implode(PHP_EOL . "     - ", $names) . PHP_EOL);
     (yield $acme->revokeCertificate($pem));
     $this->climate->br();
     $this->climate->info("    Certificate has been revoked.");
     (yield (new CertificateStore(\Kelunik\AcmeClient\normalizePath($args->get("storage")) . "/certs/" . $keyFile))->delete($args->get("name")));
     (yield new CoroutineResult(0));
 }
Exemple #6
0
 public function execute(Manager $args)
 {
     $version = $this->getVersion();
     $buildTime = $this->readFileOr("info/build.time", time());
     $buildDate = date('M jS Y H:i:s T', (int) trim($buildTime));
     $package = json_decode($this->readFileOr("composer.json", new RuntimeException("No composer.json found.")));
     $this->climate->out("┌ <green>kelunik/acme-client</green> @ <yellow>{$version}</yellow> (built: {$buildDate})");
     $this->climate->out(($args->defined("deps") ? "│" : "└") . " " . $this->getDescription($package));
     if ($args->defined("deps")) {
         $lockFile = json_decode($this->readFileOr("composer.lock", new RuntimeException("No composer.lock found.")));
         $packages = $lockFile->packages;
         for ($i = 0; $i < count($packages); $i++) {
             $link = $i === count($packages) - 1 ? "└──" : "├──";
             $this->climate->out("{$link} <green>{$packages[$i]->name}</green> @ <yellow>{$packages[$i]->version}</yellow>");
             $link = $i === count($packages) - 1 ? "   " : "│  ";
             $this->climate->out("{$link} " . $this->getDescription($packages[$i]));
         }
     }
 }
Exemple #7
0
 private function checkRegistration(Manager $args)
 {
     $server = $args->get("server");
     $protocol = substr($server, 0, strpos("://", $server));
     if (!$protocol || $protocol === $server) {
         $server = "https://" . $server;
     } elseif ($protocol !== "https") {
         throw new \InvalidArgumentException("Invalid server protocol, only HTTPS supported");
     }
     $identity = str_replace(["/", "%"], "-", substr($server, 8));
     $path = __DIR__ . "/../../data/accounts";
     $pathPrivate = "{$path}/{$identity}.private.key";
     $pathPublic = "{$path}/{$identity}.public.key";
     if (file_exists($pathPrivate) && file_exists($pathPublic)) {
         $private = file_get_contents($pathPrivate);
         $public = file_get_contents($pathPublic);
         $this->logger->info("Found account keys.");
         return new KeyPair($private, $public);
     }
     throw new AcmeException("No registration found for server, please register first");
 }
 public function doExecute(Manager $args) : Generator
 {
     if (posix_geteuid() !== 0) {
         throw new AcmeException("Please run this script as root!");
     }
     $email = $args->get("email");
     (yield resolve($this->checkEmail($email)));
     $server = $args->get("server");
     $protocol = substr($server, 0, strpos("://", $server));
     if (!$protocol || $protocol === $server) {
         $server = "https://" . $server;
     } elseif ($protocol !== "https") {
         throw new \InvalidArgumentException("Invalid server protocol, only HTTPS supported");
     }
     $identity = str_replace(["/", "%"], "-", substr($server, 8));
     $path = __DIR__ . "/../../data/accounts";
     $pathPrivate = "{$path}/{$identity}.private.key";
     $pathPublic = "{$path}/{$identity}.public.key";
     if ((yield exists($pathPrivate)) && (yield exists($pathPublic))) {
         $this->logger->info("Loading existing keys ...");
         $private = file_get_contents($pathPrivate);
         $public = file_get_contents($pathPublic);
         $keyPair = new KeyPair($private, $public);
     } else {
         $this->logger->info("Generating key keys ...");
         $keyPair = (new OpenSSLKeyGenerator())->generate(4096);
         if (!file_exists($path) && !mkdir($path, 0700, true)) {
             throw new AcmeException("Couldn't create account directory");
         }
         file_put_contents($pathPrivate, $keyPair->getPrivate());
         file_put_contents($pathPublic, $keyPair->getPublic());
         chmod($pathPrivate, 600);
         chmod($pathPrivate, 600);
     }
     $acme = new AcmeService(new AcmeClient($server, $keyPair), $keyPair);
     $this->logger->info("Registering with ACME server " . substr($server, 8) . " ...");
     /** @var Registration $registration */
     $registration = (yield $acme->register($email));
     $this->logger->notice("Registration successful with contact " . json_encode($registration->getContact()));
 }
Exemple #9
0
 protected function bootstrapApp(array $argv)
 {
     $args = new Manager();
     $args->add('shoelace', ['prefix' => 's', 'longPrefix' => 'shoelace']);
     $args->parse($argv);
     if ($args->defined('shoelace')) {
         $bootstrap = $args->get('shoelace');
         $files = explode(',', $bootstrap);
         foreach ($files as $file) {
             require_once $file;
         }
     }
     if (!defined('COBBLER_OUTPUT')) {
         define('COBBLER_OUTPUT', __DIR__ . '/../../output');
     }
     if (!defined('COBBLER_CONFIG')) {
         define('COBBLER_CONFIG', __DIR__ . '/../../config');
     }
     if (!defined('TWBS_PATH')) {
         define('TWBS_PATH', __DIR__ . '/../../../../twbs/bootstrap');
     }
 }
Exemple #10
0
 /**
  * Set the program's description
  *
  * @param string $description
  *
  * @return \League\CLImate\CLImate
  */
 public function description($description)
 {
     $this->arguments->description($description);
     return $this;
 }
Exemple #11
0
 /**
  * @param Manager $args
  * @return \Generator
  */
 private function doExecute(Manager $args)
 {
     $configPath = $args->get("config");
     try {
         $config = Yaml::parse((yield \Amp\File\get($configPath)));
     } catch (FilesystemException $e) {
         $this->climate->error("Config file ({$configPath}) not found.");
         (yield new CoroutineResult(self::EXIT_CONFIG_ERROR));
         return;
     } catch (ParseException $e) {
         $this->climate->error("Config file ({$configPath}) had an invalid format and couldn't be parsed.");
         (yield new CoroutineResult(self::EXIT_CONFIG_ERROR));
         return;
     }
     if ($args->defined("server")) {
         $config["server"] = $args->get("server");
     } else {
         if (!isset($config["server"]) && $args->exists("server")) {
             $config["server"] = $args->get("server");
         }
     }
     if ($args->defined("storage")) {
         $config["storage"] = $args->get("storage");
     } else {
         if (!isset($config["storage"]) && $args->exists("storage")) {
             $config["storage"] = $args->get("storage");
         }
     }
     if (!isset($config["server"])) {
         $this->climate->error("Config file ({$configPath}) didn't have a 'server' set nor was it passed as command line argument.");
         (yield new CoroutineResult(self::EXIT_CONFIG_ERROR));
         return;
     }
     if (!isset($config["storage"])) {
         $this->climate->error("Config file ({$configPath}) didn't have a 'storage' set nor was it passed as command line argument.");
         (yield new CoroutineResult(self::EXIT_CONFIG_ERROR));
         return;
     }
     if (!isset($config["email"])) {
         $this->climate->error("Config file ({$configPath}) didn't have a 'email' set.");
         (yield new CoroutineResult(self::EXIT_CONFIG_ERROR));
         return;
     }
     if (!isset($config["certificates"]) || !is_array($config["certificates"])) {
         $this->climate->error("Config file ({$configPath}) didn't have a 'certificates' section that's an array.");
         (yield new CoroutineResult(self::EXIT_CONFIG_ERROR));
         return;
     }
     $command = implode(" ", array_map("escapeshellarg", [PHP_BINARY, $GLOBALS["argv"][0], "setup", "--server", $config["server"], "--storage", $config["storage"], "--email", $config["email"]]));
     $process = new Process($command);
     $result = (yield $process->exec(Process::BUFFER_ALL));
     if ($result->exit !== 0) {
         $this->climate->error("Registration failed ({$result->exit})");
         $this->climate->error($command);
         $this->climate->br()->out($result->stdout);
         $this->climate->br()->error($result->stderr);
         (yield new CoroutineResult(self::EXIT_SETUP_ERROR));
         return;
     }
     $certificateChunks = array_chunk($config["certificates"], 10, true);
     $errors = [];
     $values = [];
     foreach ($certificateChunks as $certificateChunk) {
         $promises = [];
         foreach ($certificateChunk as $certificate) {
             $promises[] = \Amp\resolve($this->checkAndIssue($certificate, $config["server"], $config["storage"]));
         }
         list($chunkErrors, $chunkValues) = (yield \Amp\any($promises));
         $errors += $chunkErrors;
         $values += $chunkValues;
     }
     $status = ["no_change" => count(array_filter($values, function ($value) {
         return $value === self::STATUS_NO_CHANGE;
     })), "renewed" => count(array_filter($values, function ($value) {
         return $value === self::STATUS_RENEWED;
     })), "failure" => count($errors)];
     if ($status["renewed"] > 0) {
         foreach ($values as $i => $value) {
             if ($value === self::STATUS_RENEWED) {
                 $certificate = $config["certificates"][$i];
                 $this->climate->info("Certificate for " . implode(", ", array_keys($this->toDomainPathMap($certificate["paths"]))) . " successfully renewed.");
             }
         }
     }
     if ($status["failure"] > 0) {
         foreach ($errors as $i => $error) {
             $certificate = $config["certificates"][$i];
             $this->climate->error("Issuance for the following domains failed: " . implode(", ", array_keys($this->toDomainPathMap($certificate["paths"]))));
             $this->climate->error("Reason: {$error}");
         }
         $exitCode = $status["renewed"] > 0 ? self::EXIT_ISSUANCE_PARTIAL : self::EXIT_ISSUANCE_ERROR;
         (yield new CoroutineResult($exitCode));
         return;
     }
     if ($status["renewed"] > 0) {
         (yield new CoroutineResult(self::EXIT_ISSUANCE_OK));
         return;
     }
 }
Exemple #12
0
 public function __construct()
 {
     parent::__construct();
     $this->summary = new Summary();
 }
Exemple #13
0
 private function doExecute(Manager $args)
 {
     if (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
         if (posix_geteuid() !== 0) {
             $processUser = posix_getpwnam(posix_geteuid());
             $currentUsername = $processUser["name"];
             $user = $args->get("user") ?: $currentUsername;
             if ($currentUsername !== $user) {
                 throw new AcmeException("Running this script with --user only works as root!");
             }
         } else {
             $user = $args->get("user") ?: "www-data";
         }
     }
     $domains = array_map("trim", explode(":", str_replace([",", ";"], ":", $args->get("domains"))));
     (yield \Amp\resolve($this->checkDnsRecords($domains)));
     $docRoots = explode(PATH_SEPARATOR, str_replace("\\", "/", $args->get("path")));
     $docRoots = array_map(function ($root) {
         return rtrim($root, "/");
     }, $docRoots);
     if (count($domains) < count($docRoots)) {
         throw new AcmeException("Specified more document roots than domains.");
     }
     if (count($domains) > count($docRoots)) {
         $docRoots = array_merge($docRoots, array_fill(count($docRoots), count($domains) - count($docRoots), end($docRoots)));
     }
     $keyStore = new KeyStore(\Kelunik\AcmeClient\normalizePath($args->get("storage")));
     $server = \Kelunik\AcmeClient\resolveServer($args->get("server"));
     $keyFile = \Kelunik\AcmeClient\serverToKeyname($server);
     try {
         $keyPair = (yield $keyStore->get("accounts/{$keyFile}.pem"));
     } catch (KeyStoreException $e) {
         throw new AcmeException("Account key not found, did you run 'bin/acme setup'?", 0, $e);
     }
     $this->climate->br();
     $acme = $this->acmeFactory->build($server, $keyPair);
     $errors = [];
     $domainChunks = array_chunk($domains, 10, true);
     foreach ($domainChunks as $domainChunk) {
         $promises = [];
         foreach ($domainChunk as $i => $domain) {
             $promises[] = \Amp\resolve($this->solveChallenge($acme, $keyPair, $domain, $docRoots[$i]));
         }
         list($chunkErrors) = (yield \Amp\any($promises));
         $errors += $chunkErrors;
     }
     if (!empty($errors)) {
         foreach ($errors as $error) {
             $this->climate->error($error->getMessage());
         }
         throw new AcmeException("Issuance failed, not all challenges could be solved.");
     }
     $path = "certs/" . $keyFile . "/" . reset($domains) . "/key.pem";
     $bits = $args->get("bits");
     try {
         $keyPair = (yield $keyStore->get($path));
     } catch (KeyStoreException $e) {
         $keyPair = (new OpenSSLKeyGenerator())->generate($bits);
         $keyPair = (yield $keyStore->put($path, $keyPair));
     }
     $this->climate->br();
     $this->climate->whisper("    Requesting certificate ...");
     $location = (yield $acme->requestCertificate($keyPair, $domains));
     $certificates = (yield $acme->pollForCertificate($location));
     $path = \Kelunik\AcmeClient\normalizePath($args->get("storage")) . "/certs/" . $keyFile;
     $certificateStore = new CertificateStore($path);
     (yield $certificateStore->put($certificates));
     $this->climate->info("    Successfully issued certificate.");
     $this->climate->info("    See {$path}/" . reset($domains));
     $this->climate->br();
     (yield new CoroutineResult(0));
 }