diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 9dc409dd7d2d2b2d3542bed502b7c41fc6b9d406..2f567fce310b6da77f7a30f69eba3efc59cd7e0e 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -2,4 +2,7 @@ blank_issues_enabled: false contact_links: - name: Question url: https://github.com/cdr/code-server/discussions/new?category_id=22503114 - about: Ask the community for help + about: Ask the community for help on our GitHub Discussions board + - name: Chat + about: Need immediate help or just want to talk? Hop in our Slack + url: https://cdr.co/join-community diff --git a/README.md b/README.md index ee0b8700bf3f1cc5c70fdcf66a35b43c6ac4e61f..0e7750039d099e5fdd4eae6c689f0fc964fdd39d 100644 --- a/README.md +++ b/README.md @@ -35,12 +35,17 @@ We also have an in-depth [setup and configuration](./doc/guide.md) guide. ### Alpha Program 🐣 -We're working on a cloud platform that makes deploying and managing code-server easier. Consider [joining our alpha program](https://codercom.typeform.com/to/U4IKyv0W) if you don't want to worry about +We're working on a cloud platform that makes deploying and managing code-server easier. Consider [updating to 3.6.2](https://github.com/cdr/code-server/releases/tag/v3.6.2) and running code-server with our experimental flag `--link` if you don't want to worry about - TLS - Authentication - Port Forwarding +```bash +$ code-server --link +Proxying code-server to Coder Cloud, you can access your IDE at https://valmar-jon.cdr.co +``` + ## FAQ See [./doc/FAQ.md](./doc/FAQ.md). diff --git a/ci/dev/fmt.sh b/ci/dev/fmt.sh index d3bd419159ed9ca8a08af32e3d1fe9180becf0c7..47911aadf409a89b4db0beb7a29ca5140e8d94b6 100755 --- a/ci/dev/fmt.sh +++ b/ci/dev/fmt.sh @@ -26,6 +26,7 @@ main() { doctoc --title '# Install' doc/install.md > /dev/null doctoc --title '# npm Install Requirements' doc/npm.md > /dev/null doctoc --title '# Contributing' doc/CONTRIBUTING.md > /dev/null + doctoc --title '# iPad' doc/ipad.md > /dev/null if [[ ${CI-} && $(git ls-files --other --modified --exclude-standard) ]]; then echo "Files need generation or are formatted incorrectly:" diff --git a/doc/FAQ.md b/doc/FAQ.md index 370dd6660ab629f48b23f078d04dac9f285468b8..1a6a217d676c5b6f1fa15cc956a651a2dcd991f8 100644 --- a/doc/FAQ.md +++ b/doc/FAQ.md @@ -3,6 +3,7 @@ # FAQ - [Questions?](#questions) +- [iPad Status?](#ipad-status) - [How can I reuse my VS Code configuration?](#how-can-i-reuse-my-vs-code-configuration) - [Differences compared to VS Code?](#differences-compared-to-vs-code) - [How can I request a missing extension?](#how-can-i-request-a-missing-extension) @@ -21,7 +22,6 @@ - [Heartbeat File](#heartbeat-file) - [Healthz endpoint](#healthz-endpoint) - [How does the config file work?](#how-does-the-config-file-work) -- [Blank screen on iPad?](#blank-screen-on-ipad) - [Isn't an install script piped into sh insecure?](#isnt-an-install-script-piped-into-sh-insecure) - [How do I make my keyboard shortcuts work?](#how-do-i-make-my-keyboard-shortcuts-work) - [Differences compared to Theia?](#differences-compared-to-theia) @@ -33,6 +33,10 @@ Please file all questions and support requests at https://github.com/cdr/code-server/discussions. +## iPad Status? + +Please see [./ipad.md](./ipad.md). + ## How can I reuse my VS Code configuration? The very popular [Settings Sync](https://marketplace.visualstudio.com/items?itemName=Shan.code-settings-sync) extension works. @@ -144,6 +148,9 @@ For HTTPS, you can use a self signed certificate by passing in just `--cert` or pass in an existing certificate by providing the path to `--cert` and the path to the key with `--cert-key`. +The self signed certificate will be generated into +`~/.local/share/code-server/self-signed.crt`. + If `code-server` has been passed a certificate it will also respond to HTTPS requests and will redirect all HTTP requests to HTTPS. @@ -279,15 +286,6 @@ The `--config` flag or `$CODE_SERVER_CONFIG` can be used to change the config fi The default location also respects `$XDG_CONFIG_HOME`. -## Blank screen on iPad? - -Unfortunately at the moment self signed certificates cause a blank screen on iPadOS - -There does seem to be a way to get it to work if you create your own CA and create a -certificate using the CA and then import the CA onto your iPad. - -See [#1566](https://github.com/cdr/code-server/issues/1566#issuecomment-623159434). - ## Isn't an install script piped into sh insecure? Please give diff --git a/doc/guide.md b/doc/guide.md index 8782f57f828cd2f4c3efe6d7277bd731f30f679d..ce17a36144a7cd7ee6ab4bfe9eb5a7592982810f 100644 --- a/doc/guide.md +++ b/doc/guide.md @@ -251,8 +251,7 @@ Visit `https://` to access `code-server`. Congratulations! ### Self Signed Certificate -**note:** Self signed certificates do not work with iPad and will cause a blank page. You'll -have to use [Let's Encrypt](#lets-encrypt) instead. See the [FAQ](./FAQ.md#blank-screen-on-ipad). +**note:** Self signed certificates do not work with iPad normally. See [./ipad.md](./ipad.md) for details. Recommended reading: https://security.stackexchange.com/a/8112. diff --git a/doc/ipad.md b/doc/ipad.md new file mode 100644 index 0000000000000000000000000000000000000000..1bda0bd44c7ab915d0a7260757e9c99d211f7cfd --- /dev/null +++ b/doc/ipad.md @@ -0,0 +1,56 @@ + + +# iPad + +- [iPad](#ipad) + - [Known Issues](#known-issues) + - [How to access code-server with a self signed certificate on iPad?](#how-to-access-code-server-with-a-self-signed-certificate-on-ipad) + + + +# iPad + +## Known Issues + +- Getting self signed certificates certificates to work is involved, see below. +- Keyboard may disappear sometimes [#1313](https://github.com/cdr/code-server/issues/1313), [#979](https://github.com/cdr/code-server/issues/979) +- Trackpad scrolling does not work [#1455](https://github.com/cdr/code-server/issues/1455) +- See [issues tagged with the iPad label](https://github.com/cdr/code-server/issues?q=is%3Aopen+is%3Aissue+label%3AiPad) for more. + +## How to access code-server with a self signed certificate on iPad? + +Accessing a self signed certificate on iPad isn't as easy as accepting through all +the security warnings. Safari will prevent WebSocket connections unless the certificate +is installed as a profile on the device. + +The below assumes you are using the self signed certificate that code-server +generates for you. If not, that's fine but you'll have to make sure your certificate +abides by the following guidelines from Apple: https://support.apple.com/en-us/HT210176 + +**note**: Another undocumented requirement we noticed is that the certificate has to have `basicConstraints=CA:true`. + +The following instructions assume you have code-server installed and running +with a self signed certificate. If not, please first go through [./guide.md](./guide.md)! + +**warning**: Your iPad must access code-server via a domain name. It could be local +DNS like `mymacbookpro.local` but it must be a domain name. Otherwise Safari will +refuse to allow WebSockets to connect. + +1. Your certificate **must** have a subject alt name that matches the hostname + at which you will access code-server from your iPad. You can pass this to code-server + so that it generates the certificate correctly with `--cert-host`. +2. Share your self signed certificate with the iPad. + - code-server will print the location of the certificate it has generated in the logs. + +``` +[2020-10-30T08:55:45.139Z] info - Using generated certificate and key for HTTPS: ~/.local/share/code-server/mymbp_local.crt +``` + +- You can mail it to yourself or if you have a Mac, it's easiest to just Airdrop to the iPad. + +3. When opening the `*.crt` file, you'll be prompted to go into settings to install. +4. Go to `Settings -> General -> Profile`, select the profile and then hit `Install`. + - It should say the profile is verified. +5. Go to `Settings -> About -> Certificate Trust Settings` and enable full trust for + the certificate. +6. Now you can access code-server! 🍻 diff --git a/src/node/cli.ts b/src/node/cli.ts index 1403d89207dabd74ffb2f7acf86e56eefd8dd864..4ff35b445126fdb0f2c9859d0d0b60c23164ea23 100644 --- a/src/node/cli.ts +++ b/src/node/cli.ts @@ -26,6 +26,7 @@ export interface Args extends VsArgs { readonly auth?: AuthType readonly password?: string readonly cert?: OptionalString + readonly "cert-host"?: string readonly "cert-key"?: string readonly "disable-telemetry"?: boolean readonly help?: boolean @@ -101,7 +102,11 @@ const options: Options> = { cert: { type: OptionalString, path: true, - description: "Path to certificate. Generated if no path is provided.", + description: "Path to certificate. A self signed certificate is generated if none is provided.", + }, + "cert-host": { + type: "string", + description: "Hostname to use when generating a self signed certificate.", }, "cert-key": { type: "string", path: true, description: "Path to certificate key when using non-generated cert." }, "disable-telemetry": { type: "boolean", description: "Disable telemetry." }, diff --git a/src/node/entry.ts b/src/node/entry.ts index 96db046e2fb11d6137dd7f2b321518634df95e77..5184c43448a8efa6fa4113ba109b20233f46c5d2 100644 --- a/src/node/entry.ts +++ b/src/node/entry.ts @@ -160,7 +160,7 @@ const main = async (args: Args, configArgs: Args): Promise => { proxyDomains: args["proxy-domain"], socket: args.socket, ...(args.cert && !args.cert.value - ? await generateCertificate() + ? await generateCertificate(args["cert-host"] || "localhost") : { cert: args.cert && args.cert.value, certKey: args["cert-key"], @@ -209,7 +209,7 @@ const main = async (args: Args, configArgs: Args): Promise => { logger.info( args.cert && args.cert.value ? ` - Using provided certificate and key for HTTPS` - : ` - Using generated certificate and key for HTTPS`, + : ` - Using generated certificate and key for HTTPS: ${humanPath(options.cert)}`, ) } else { logger.info(" - Not serving HTTPS") diff --git a/src/node/util.ts b/src/node/util.ts index 75122fe76d0d08f1f82bcf4e5678179d5d01111d..b50916b5c7637cacebb25e2d310197723f3a9abe 100644 --- a/src/node/util.ts +++ b/src/node/util.ts @@ -54,25 +54,45 @@ export function humanPath(p?: string): string { return p.replace(os.homedir(), "~") } -export const generateCertificate = async (): Promise<{ cert: string; certKey: string }> => { - const paths = { - cert: path.join(tmpdir, "self-signed.cert"), - certKey: path.join(tmpdir, "self-signed.key"), - } - const checks = await Promise.all([fs.pathExists(paths.cert), fs.pathExists(paths.certKey)]) +export const generateCertificate = async (hostname: string): Promise<{ cert: string; certKey: string }> => { + const certPath = path.join(paths.data, `${hostname.replace(/\./g, "_")}.crt`) + const certKeyPath = path.join(paths.data, `${hostname.replace(/\./g, "_")}.key`) + + const checks = await Promise.all([fs.pathExists(certPath), fs.pathExists(certKeyPath)]) if (!checks[0] || !checks[1]) { // Require on demand so openssl isn't required if you aren't going to // generate certificates. const pem = require("pem") as typeof import("pem") const certs = await new Promise((resolve, reject): void => { - pem.createCertificate({ selfSigned: true }, (error, result) => { - return error ? reject(error) : resolve(result) - }) + pem.createCertificate( + { + selfSigned: true, + commonName: hostname, + config: ` +[req] +req_extensions = v3_req + +[ v3_req ] +basicConstraints = CA:true +extendedKeyUsage = serverAuth +subjectAltName = @alt_names + +[alt_names] +DNS.1 = ${hostname} +`, + }, + (error, result) => { + return error ? reject(error) : resolve(result) + }, + ) }) - await fs.mkdirp(tmpdir) - await Promise.all([fs.writeFile(paths.cert, certs.certificate), fs.writeFile(paths.certKey, certs.serviceKey)]) + await fs.mkdirp(paths.data) + await Promise.all([fs.writeFile(certPath, certs.certificate), fs.writeFile(certKeyPath, certs.serviceKey)]) + } + return { + cert: certPath, + certKey: certKeyPath, } - return paths } export const generatePassword = async (length = 24): Promise => { diff --git a/test/socket.test.ts b/test/socket.test.ts index 7d4de985fb1d33a84870591230f6c89778d441ed..b1e974ad0959e6f1cc8ce0e36da7e8c0c08dd337 100644 --- a/test/socket.test.ts +++ b/test/socket.test.ts @@ -45,7 +45,7 @@ describe("SocketProxyProvider", () => { } before(async () => { - const cert = await generateCertificate() + const cert = await generateCertificate("localhost") const options = { cert: fs.readFileSync(cert.cert), key: fs.readFileSync(cert.certKey),