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)); }
/** * @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); }
/** * @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)); }
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)); }
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])); } } }
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())); }
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'); } }
/** * Set the program's description * * @param string $description * * @return \League\CLImate\CLImate */ public function description($description) { $this->arguments->description($description); return $this; }
/** * @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; } }
public function __construct() { parent::__construct(); $this->summary = new Summary(); }
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)); }