Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ssl command to download mkcert and generate ssl certificates #465

Merged
merged 45 commits into from
May 6, 2022
Merged
Changes from 1 commit
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
480f504
Add ssl command to download mkcert and generate ssl certificates
shadyvb Apr 22, 2022
3b1dd4f
Add link to install mkcert after errors installing it
shadyvb Apr 25, 2022
824746e
Remove redundant return statement
shadyvb Apr 25, 2022
901b3b9
Improve logging around errors with ssl
shadyvb Apr 26, 2022
afad00a
Remove redundant output
shadyvb Apr 26, 2022
f2786f9
Removed duplicate handling of get_mkcert_binary()
shadyvb Apr 26, 2022
9441243
Fix docblock
shadyvb Apr 26, 2022
26494bd
:nail_care: Fix CS
shadyvb Apr 26, 2022
b0bb233
Programmatically create SSL certificate and use with Traefik
shadyvb Apr 23, 2022
1be8adf
Allow custom domain name/tld from #341
shadyvb Apr 26, 2022
60baaf5
Fix function args
shadyvb Apr 23, 2022
aa942fc
Fix an error with the run command
shadyvb Apr 27, 2022
5c025de
Skip verifying https for S3 requests
shadyvb Apr 27, 2022
97e4ce9
Try to fix s3 bucket/path mapping
shadyvb Apr 27, 2022
cfec957
Better detect WSL environment
shadyvb Apr 28, 2022
9827897
Restart the proxy container after generating a certificate
shadyvb May 3, 2022
7f36778
Add support for extra custom domains
shadyvb May 3, 2022
af3b6c0
Stop starting if domain has changed to avoid orphan containers
shadyvb May 3, 2022
859114c
Only attempt to restart the proxy container if it is running
shadyvb May 3, 2022
e28a24a
Check if secure is set to false to avoid generating SSL certificate
shadyvb May 3, 2022
e259410
Revert "Check if secure is set to false to avoid generating SSL certi…
shadyvb May 3, 2022
e20718a
:nail_care: CS polish
shadyvb May 3, 2022
578c219
Install mkcert for tests
shadyvb May 3, 2022
4091cad
Add note on mkcert installation url and location
shadyvb May 3, 2022
b50194c
More verbose output on detected os arch
shadyvb May 3, 2022
6b9708f
Fix arch detection for linux
shadyvb May 3, 2022
ef984c4
Fix missing name/tld config
shadyvb May 3, 2022
a3d42f8
Fix missing domains config key
shadyvb May 3, 2022
ce9fb0c
Merge remote-tracking branch 'origin/master' into product-dev-987/ssl…
shadyvb May 3, 2022
2a73d9f
Test connectivity to site in CI
shadyvb May 3, 2022
81ab150
Add aux service URLs to generated certificate
shadyvb May 4, 2022
90ce957
Fix S3 and Tachyon issues with bucket path
shadyvb May 4, 2022
4e6fd76
Add a warning for missing hosts entries
shadyvb May 4, 2022
7c0a020
Fix indentation
shadyvb May 4, 2022
8a79a58
Remove relative path use in traefik config
shadyvb May 4, 2022
2634b85
Fix generation of hosts entries note
shadyvb May 4, 2022
0d6430e
Fix generation of cert around default domain
shadyvb May 4, 2022
6c187a1
Less testing output
shadyvb May 4, 2022
bd8eabc
Do not generate SSL for altis.dev
shadyvb May 4, 2022
37518b7
More efficient domain selection
shadyvb May 4, 2022
b164550
Correct typo
shadyvb May 4, 2022
72c6318
Add traefik.domain label for future multi-instance SSL generation
shadyvb May 4, 2022
b008194
Return instead of exit
shadyvb May 5, 2022
4e26c8f
Document custom domains and SSL command features
shadyvb May 5, 2022
2f11c98
Move SSL generation out of experimental section
shadyvb May 6, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 181 additions & 1 deletion inc/composer/class-command.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ protected function configure() {
->setName( 'server' )
->setDescription( 'Altis Local Server' )
->setDefinition( [
new InputArgument( 'subcommand', null, 'start, stop, restart, cli, exec, shell, ssh, status, db, set, logs.' ),
new InputArgument( 'subcommand', null, 'start, stop, restart, cli, exec, shell, ssh, status, db, ssl, set, logs.' ),
new InputArgument( 'options', InputArgument::IS_ARRAY ),
] )
->setAliases( [ 'local-server' ] )
Expand Down Expand Up @@ -75,6 +75,11 @@ protected function configure() {
db sequel Generates an SPF file for Sequel Pro
db info Prints out Database connection details
db exec -- "<query>" Run and output the result of a SQL query.
SSL commands:
ssl Show status on generated SSL certificates
ssl install Installs and trusts Root Certificate Authority
ssl generate [domains] Generate SSL certificates for configured domains
ssl exec -- "command" Executes an arbitrary mkcert command
View the logs
logs <service> <service> can be php, nginx, db, s3, elasticsearch, xray
Import files from content/uploads directly to s3:
Expand Down Expand Up @@ -167,6 +172,8 @@ protected function execute( InputInterface $input, OutputInterface $output ) : i
return $this->exec( $input, $output );
} elseif ( $subcommand === 'db' ) {
return $this->db( $input, $output );
} elseif ( $subcommand === 'ssl' ) {
return $this->ssl( $input, $output );
} elseif ( $subcommand === 'status' ) {
return $this->status( $input, $output );
} elseif ( $subcommand === 'logs' ) {
Expand Down Expand Up @@ -654,6 +661,179 @@ protected function db( InputInterface $input, OutputInterface $output ) {
return $return_val;
}

/**
* Generate SSL certificates for development environment.
*
* @param InputInterface $input Command input object.
* @param OutputInterface $output Command output object.
* @return int
*/
protected function ssl( InputInterface $input, OutputInterface $output ) {
$subcommand = $input->getArgument( 'options' )[0] ?? null;

switch ( $subcommand ) {
case 'install':
// Detect platform architecture to attempt automatic installation.
$os = php_uname( 's' ); # 'Darwin', 'Linux', 'Windows'
$arch = php_uname( 'm' ); # 'arm64' for arm, 'x86_64' or 'amd64' for x64
$mkcert_version = 'v1.4.3';

switch( $os ) {
# macOS
case 'Darwin':
$binary_arch = $arch === 'x86_64' ? 'darwin-amd64' : 'darwin-arm64';
break;
# Linux
case 'Linux':
$binary_arch = $arch === 'amd64' ? 'linux-amd64' : 'linux-arm64';
break;
# Windows
case 'Windows':
shadyvb marked this conversation as resolved.
Show resolved Hide resolved
$binary_arch = 'windows-amd64.exe';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will need to check what the situation with this and WSL is. If you can run .exe from WSL, or global commands from there we might be ok. @missjwo might be able to assist in testing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, testing cross-platform was supposed to be the next step. I can do that in a VM.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WSL won’t replicate accurately within a VM so far as I know, since it’s a VM itself.

For WSL though, the arch will be Linux because it’s literally a Linux VM you’re running inside of.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If composer is running within WSL, the certificate would be automatically installed to Windows store itself, which is a problem. We'll need to add docs on manually accepting the root CA.

Also, does this mean we do not support Windows itself but rather WSL ? ( I have not used WSL nor Docker on Windows )

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah there was a PR to support windows natively but I think there were some limitations to that. We do have a way to check it’s a WSL environment at least.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to test stuff - just let me know when the PR is ready to be tested and what i should be looking out for.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shadyvb Correct, only WSL is supported, per https://docs.altis-dxp.com/local-server/windows/

You can execute exe files, yeah; WSL will transparently map execution of those, and execute them. Note though, php_uname( 's' ) will return Linux because it is Linux. We need to use the Command::is_wsl() function to detect WSL, not uname.

Also happy to test with my WSL env, and screenshare as needed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to test on my windows machine. Let me know when the PR is ready to be tested.

break;
default:
$binary_arch = null;
break;
}

// If couldn't detect a support architecture, ask the user to install mkcert manually.
if ( ! $binary_arch ) {
$output->writeln( '<error>This command is only supported on macOS, Linux, and Windows x64, install `mkcert` manually for other systems.</error>' );
shadyvb marked this conversation as resolved.
Show resolved Hide resolved
return 1;
}

$binary = "mkcert-$mkcert_version-$binary_arch";
$mkcert = "vendor/mkcert";

// Check if mkcert is installed globally already, bail if so.
$version = trim( shell_exec( 'mkcert -version' ) );
if ( $version ) {
$output->writeln( "<error>mkcert $version is installed globally already</error>" );
roborourke marked this conversation as resolved.
Show resolved Hide resolved
return 1;
}

// Check if mkcert is installed locally already, bail if so.
$version = trim( shell_exec( "$mkcert -version" ) );
if ( $version ) {
$output->writeln( "<error>mkcert $version is installed locally already</error>" );
return 1;
}

exec( "curl -o $mkcert -L https://github.com/FiloSottile/mkcert/releases/download/$mkcert_version/$binary", $dummy, $result );
roborourke marked this conversation as resolved.
Show resolved Hide resolved
if ( $result ) {
$output->writeln( "<error>Could not download mkcert binary, try using sudo or manually installing mkcert.</error>" );
shadyvb marked this conversation as resolved.
Show resolved Hide resolved
return 1;
}

$output->writeln( "<info>mkcert $mkcert_version was downloaded.</info>" );

chmod( $mkcert, 0755);

exec( "$mkcert -version", $dummy, $result );
if ( $result ) {
$output->writeln( "<error>Could not launch mkcert binary, try manually installing mkcert.</error>" );
return 1;
}
$output->writeln( "<info>mkcert $mkcert_version was installed.</info>" );

// Setup and accept the root certificate.
exec( "$mkcert -install", $dummy, $result );
if ( $result ) {
$output->writeln( "<error>Could not setup mkcert properly, try manually installing mkcert.</error>" );
return 1;
}

$output->writeln( "<info>mkcert root CA was installed and accepted successfully.</info>" );
return 0;
break;
roborourke marked this conversation as resolved.
Show resolved Hide resolved
case 'generate':
$mkcert = $this->get_mkcert_binary();
if ( ! $mkcert ) {
$output->writeln( "<error>mkcert is not installed, run 'composer server ssl install' or install mkcert manually first.</error>" );
return 1;
}

// TODO figure out how to programmatically detect the domains to use
$domains = $input->getArgument( 'options' )[1] ?? '*.altis.dev';
roborourke marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the default behaviour here should be the following:

  • Always generate certs for <project>.<tld> and *.<project>.<tld> (no harm in them being there by default, even if someone calls the function with other domains, its better to avoid surprises here IMO than assume someone knows it might be a deleterious action if we don't explicitly call that out)
  • Add support for a list of hosts in the config and merge those in by default too, we can document that custom ones must be pointed to localhost

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Traefik will need to know about the extra domains too 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the extra domains support to nginx ⬆️

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also added the configured domain(s) to the domains within generate() command as discussed, also altis.dev *.altis.dev.


exec( "$mkcert -cert-file vendor/ssl-cert.pem -key-file vendor/ssl-key.pem $domains", $dummy, $result );

if ( $result ) {
$output->writeln( "<error>Could not generate certificates! Try generating them manually using mkcert.</error>" );
roborourke marked this conversation as resolved.
Show resolved Hide resolved
return 1;
}

$output->writeln( "<info>Generated SSL certificate successfully to vendor/ssl-cert.pem and key to vendor/ssl-key.pem.</info>" );
roborourke marked this conversation as resolved.
Show resolved Hide resolved
break;

case 'exec':
$mkcert = $this->get_mkcert_binary();
if ( ! $mkcert ) {
$output->writeln( "<error>mkcert is not installed, run 'composer server ssl install' or install mkcert manually first.</error>" );
return 1;
}

$command = $input->getArgument( 'options' )[1] ?? null;
exec( "$mkcert $command", $exec_output, $result );

if ( $result ) {
$output->writeln( "<error>$exec_output</error>" );
return 1;
} else {
$output->writeln( $exec_output );
}

break;

case '':
$mkcert = $this->get_mkcert_binary();
roborourke marked this conversation as resolved.
Show resolved Hide resolved
if ( ! $mkcert ) {
$output->writeln( "<error>mkcert is not installed, run 'composer server ssl install' or install mkcert manually first.</error>" );
return 1;
} else {
$output->writeln( '<info>mkcert is installed correctly.</info>' );
}

$cert_exists = file_exists( 'vendor/ssl-cert.pem' ) && file_exists( 'vendor/ssl-key.pem' );
if ( ! $cert_exists ) {
$output->writeln( "<error>Certificate file does not exist. Use 'composer server ssl generate' to generate one. </error>" );
return 1;
} else {
$output->writeln( "<info>Certificate file exists.</info>" );
}

break;

default:
$output->writeln( "<error>The subcommand $subcommand is not recognized</error>" );
return 1;
}
return 0;
}

/**
* Retrieves path to the working copy of mkcert.
*
* @return string|false Path to the mkcert binary or false if not found.
roborourke marked this conversation as resolved.
Show resolved Hide resolved
*/
protected function get_mkcert_binary() : ?string {
$mkcert = "vendor/mkcert";

// Check if mkcert is installed globally already, bail if so.
$version = trim( shell_exec( 'mkcert -version' ) );
if ( $version ) {
return 'mkcert';
}

// Check if mkcert is installed locally already, bail if so.
$version = trim( shell_exec( "$mkcert -version" ) );
if ( $version ) {
return $mkcert;
}

return null;
}

/**
* Generates the docker-compose.yml file.
*
Expand Down