Merge branch '18656-dynamic-gpu-req' refs #18656
authorPeter Amstutz <peter.amstutz@curii.com>
Fri, 4 Mar 2022 21:44:28 +0000 (16:44 -0500)
committerPeter Amstutz <peter.amstutz@curii.com>
Fri, 4 Mar 2022 21:44:28 +0000 (16:44 -0500)
Arvados-DCO-1.1-Signed-off-by: Peter Amstutz <peter.amstutz@curii.com>

44 files changed:
apps/workbench/app/views/users/link_account.html.erb
build/run-library.sh
doc/_includes/_multi_host_install_custom_certificates.liquid [moved from doc/_includes/_install_custom_certificates.liquid with 57% similarity]
doc/admin/link-accounts.html.textile.liquid
doc/api/index.html.textile.liquid
doc/install/salt-multi-host.html.textile.liquid
doc/install/salt-single-host.html.textile.liquid
doc/install/salt.html.textile.liquid
doc/sdk/python/cookbook.html.textile.liquid
doc/user/topics/link-accounts.html.textile.liquid
lib/controller/localdb/login_oidc.go
lib/controller/localdb/login_oidc_test.go
sdk/go/arvadostest/oidc_provider.go
sdk/python/arvados/api.py
sdk/python/arvados/util.py
sdk/python/arvados/vocabulary.py [new file with mode: 0644]
sdk/python/tests/test_vocabulary.py [new file with mode: 0644]
services/api/app/controllers/arvados/v1/schema_controller.rb
services/api/app/models/user.rb
services/api/test/unit/user_test.rb
tools/salt-install/Vagrantfile
tools/salt-install/config_examples/single_host/multiple_hostnames/states/dns.sls [new file with mode: 0644]
tools/salt-install/config_examples/single_host/single_hostname/pillars/arvados.sls
tools/salt-install/config_examples/single_host/single_hostname/pillars/aws_credentials.sls [new file with mode: 0644]
tools/salt-install/config_examples/single_host/single_hostname/pillars/docker.sls
tools/salt-install/config_examples/single_host/single_hostname/pillars/letsencrypt.sls [new file with mode: 0644]
tools/salt-install/config_examples/single_host/single_hostname/pillars/nginx_api_configuration.sls
tools/salt-install/config_examples/single_host/single_hostname/pillars/nginx_controller_configuration.sls
tools/salt-install/config_examples/single_host/single_hostname/pillars/nginx_keepproxy_configuration.sls
tools/salt-install/config_examples/single_host/single_hostname/pillars/nginx_keepweb_configuration.sls
tools/salt-install/config_examples/single_host/single_hostname/pillars/nginx_passenger.sls
tools/salt-install/config_examples/single_host/single_hostname/pillars/nginx_webshell_configuration.sls
tools/salt-install/config_examples/single_host/single_hostname/pillars/nginx_websocket_configuration.sls
tools/salt-install/config_examples/single_host/single_hostname/pillars/nginx_workbench2_configuration.sls
tools/salt-install/config_examples/single_host/single_hostname/pillars/nginx_workbench_configuration.sls
tools/salt-install/config_examples/single_host/single_hostname/states/custom_certs.sls [new file with mode: 0644]
tools/salt-install/config_examples/single_host/single_hostname/states/dns.sls [new file with mode: 0644]
tools/salt-install/config_examples/single_host/single_hostname/states/host_entries.sls
tools/salt-install/config_examples/single_host/single_hostname/states/snakeoil_certs.sls
tools/salt-install/local.params.example.multiple_hosts
tools/salt-install/local.params.example.single_host_multiple_hostnames
tools/salt-install/local.params.example.single_host_single_hostname
tools/salt-install/provision.sh
tools/salt-install/tests/run-test.sh

index 86a0446e76e07603b079ac50465a63a3885c81cb..e45073e288fee16bf4d843df4d3d0dba2061b21d 100644 (file)
@@ -75,6 +75,8 @@ SPDX-License-Identifier: AGPL-3.0 %>
   });
 <% end %>
 
+<% if Rails.configuration.Login.LoginCluster.empty? %>
+
 <div id="need-login" style="display: none">
 
   <p>You are currently logged in as <b><%= Thread.current[:user].email %></b> (<%= Thread.current[:user].username%>, <%= Thread.current[:user].uuid %>) created at <b><%= Thread.current[:user].created_at%></b></p>
@@ -91,7 +93,7 @@ SPDX-License-Identifier: AGPL-3.0 %>
     <i class="fa fa-fw fa-sign-in"></i> Use this login to access another account
   </button>
 
-</p>
+  </p>
 </div>
 
 <div id="ready-to-link" style="display: none">
@@ -105,8 +107,13 @@ SPDX-License-Identifier: AGPL-3.0 %>
     <input type="hidden" id="new-user-token-input" name="direction" value="<%=params[:direction]%>" />
     <%= button_tag class: "btn btn-primary", id: "link-account-submit" do %>
       <i class="fa fa-fw fa-link"></i> Link accounts
+    <% end %>
   <% end %>
-<% end %>
 
 </div>
+
+<% else %>
+<div>
+Self-serve account linking is not supported on this cluster. Please contact your Arvados administrator.
 </div>
+<% end %>
index 2a869553d1ef0acfcf996c9a6415396ddef87283..0257e652418ea345da94f4408eb43204a2cf6785 100755 (executable)
@@ -578,12 +578,12 @@ handle_workbench () {
   # Build the workbench server package
   test_rails_package_presence arvados-workbench "$WORKSPACE/apps/workbench"
   if [[ "$?" == "0" ]] ; then
+    calculate_go_package_version arvados_server_version cmd/arvados-server
+    arvados_server_iteration=$(default_iteration "arvados-server" "$arvados_server_version" "go")
+
     (
         set -e
 
-        calculate_go_package_version arvados_server_version cmd/arvados-server
-        arvados_server_iteration=$(default_iteration "arvados-server" "$arvados_server_version" "go")
-
         # The workbench package has a build-time dependency on the arvados-server
         # package for config manipulation, so install it first.
         cd $WORKSPACE/cmd/arvados-server
similarity index 57%
rename from doc/_includes/_install_custom_certificates.liquid
rename to doc/_includes/_multi_host_install_custom_certificates.liquid
index 4a4aff5cfbcc7ad63cef0710b12e39d51d66f4c0..b831aadcf9aff4f2acb2cf065f27d19049dbaef3 100644 (file)
@@ -4,7 +4,7 @@ Copyright (C) The Arvados Authors. All rights reserved.
 SPDX-License-Identifier: CC-BY-SA-3.0
 {% endcomment %}
 
-If you plan to use custom certificates, please set the variable <i>USE_LETSENCRYPT=no</i> and copy your certificates to the directory specified with the variable @CUSTOM_CERTS_DIR@ (usually "./certs") in the remote directory where you copied the @provision.sh@ script. From this dir, the provision script will install the certificates required for the role you're installing.
+Copy your certificates to the directory specified with the variable @CUSTOM_CERTS_DIR@ in the remote directory where you copied the @provision.sh@ script. The provision script will find the certificates there.
 
 The script expects cert/key files with these basenames (matching the role except for <i>keepweb</i>, which is split in both <i>download / collections</i>):
 
@@ -17,10 +17,12 @@ The script expects cert/key files with these basenames (matching the role except
 * "collections"      # Part of keepweb
 * "keepproxy"
 
-Ie., for 'keepproxy', the script will look for
+E.g. for 'keepproxy', the script will look for
 
 <notextile>
 <pre><code>${CUSTOM_CERTS_DIR}/keepproxy.crt
 ${CUSTOM_CERTS_DIR}/keepproxy.key
 </code></pre>
 </notextile>
+
+Make sure that all the FQDNs that you will use for the public-facing applications (API/controller, Workbench, Keepproxy/Keepweb) are reachable.
index d0ac6a036a2ddb173b02b1fe7bced96d94b6ca75..6e880fdf6623b2687347b8808eeebba6c46d92fc 100644 (file)
@@ -11,6 +11,8 @@ SPDX-License-Identifier: CC-BY-SA-3.0
 
 If a user needs to log in to Arvados with a upstream account or provider, they may end up with two Arvados user accounts.  If the user still has the ability to log in with the old account, they can use the "self-serve account linking":{{site.baseurl}}/user/topics/link-accounts.html feature of workbench.  However, if the user does not have the ability to log in with both upstream accounts, the admin can also link the accounts using the command line.
 
+bq. NOTE: self-serve account linking is currently not supported on LoginCluster federations and needs to be performed manually by the site admin.
+
 h3. Step 1: Determine user uuids
 
 User uuids can be determined by browsing workbench or using @arv user list@ at the command line.
index 8586a166d35eaa94d8e330a31748ccd9d87a2caf..3d69d02ea92379fcb11c17d78cae442112e8df84 100644 (file)
@@ -20,6 +20,10 @@ h2. Exported configuration
 
 The Controller exposes a subset of the cluster's configuration and makes it available to clients in JSON format. This public config includes valuable information like several service's URLs, timeout settings, etc. and it is available at @/arvados/v1/config@, for example @https://{{ site.arvados_api_host }}/arvados/v1/config@. The new Workbench is one example of a client using this information, as it's a client-side application and doesn't have access to the cluster's config file.
 
+h2. Exported vocabulary definition
+
+When configured, the Controller also exports the "metadata vocabulary definition":{{site.baseurl}}/admin/metadata-vocabulary.html in JSON format. This functionality is useful for clients like Workbench2 and the Python SDK to provide "identifier to human-readable labels" translations facilities for reading and writing objects on the system. This is available at @/arvados/v1/vocabulary@, for example @https://{{ site.arvados_api_host }}/arvados/v1/vocabulary@.
+
 h2. Workbench examples
 
 Many Arvados Workbench pages, under the *Advanced* tab, provide examples of API and SDK use for accessing the current resource .
index 10f2e32ef134569591940ffa8f44bdbdae593d70..6ef274a03d7c6070409297030b82523d10dec3dc 100644 (file)
@@ -98,7 +98,9 @@ Edit the variables in the <i>local.params</i> file. Pay attention to the <b>*_IN
 
 The <i>multi_host</i> example includes Let's Encrypt salt code to automatically request and install the certificates for the public-facing hosts (API/controller, Workbench, Keepproxy/Keepweb) using AWS' Route53.
 
-{% include 'install_custom_certificates' %}
+{% include 'multi_host_install_custom_certificates' %}
+
+If you want to use valid certificates provided by Let's Encrypt, set the variable <i>SSL_MODE=lets-encrypt</i> and make sure that all the FQDNs that you will use for the public-facing applications (API/controller, Workbench, Keepproxy/Keepweb) are reachable.
 
 h3(#further_customization). Further customization of the installation (modifying the salt pillars and states)
 
index 0f06412f9103c12fb3c46d8fa9f2c8a826e7eb89..106fab9bd4369c145fd69da01de9ce4b3f2edf3f 100644 (file)
@@ -9,109 +9,151 @@ Copyright (C) The Arvados Authors. All rights reserved.
 SPDX-License-Identifier: CC-BY-SA-3.0
 {% endcomment %}
 
-# "Single host install using the provision.sh script":#single_host
-# "Choose the desired configuration":#choose_configuration
-## "Single host / single hostname":#single_host_single_hostnames
-## "Single host / multiple hostnames (Alternative configuration)":#single_host_multiple_hostnames
-## "Further customization of the installation (modifying the salt pillars and states)":#further_customization
+# "Limitations of the single host install":#limitations
+# "Prerequisites":#prerequisites
+# "Download the installer":#single_host
+# "Choose the SSL configuration":#certificates
+## "Using a self-signed certificate":#self-signed
+## "Using a Let's Encrypt certificate":#lets-encrypt
+## "Bring your own certificate":#bring-your-own
+# "Further customization of the installation (modifying the salt pillars and states)":#further_customization
 # "Run the provision.sh script":#run_provision_script
-# "Final configuration steps":#final_steps
-## "Install the CA root certificate (required in both alternatives)":#ca_root_certificate
-## "DNS configuration (single host / multiple hostnames)":#single_host_multiple_hostnames_dns_configuration
+# "Install the CA root certificate":#ca_root_certificate
 # "Initial user and login":#initial_user
 # "Test the installed cluster running a simple workflow":#test_install
 # "After the installation":#post_install
 
-h2(#single_host). Single host install using the provision.sh script
+h2(#limitations). Limitations of the single host install
 
-<b>NOTE: The single host installation is not recommended for production use.</b>
+<b>NOTE: The single host installation is a good choice for evaluating Arvados, but it is not recommended for production use.</b>
 
-{% include 'branchname' %}
+Using the default configuration, this installation method has a number of limitations:
 
-This is a package-based installation method. Start with the @provision.sh@ script which is available by cloning the @{{ branchname }}@ branch from "https://git.arvados.org/arvados.git":https://git.arvados.org/arvados.git .  The @provision.sh@ script and its supporting files can be found in the "arvados/tools/salt-install":https://git.arvados.org/arvados.git/tree/refs/heads/{{ branchname }}:/tools/salt-install directory in the Arvados git repository.
+* all services run on the same machine, and they will compete for resources. This includes any compute jobs.
+* it uses the local machine disk for Keep storage (under the @/tmp@ directory). There may not be a lot of space available.
+* it installs the @crunch-dispatch-local@ dispatcher, which can run just eight concurrent CWL jobs. These jobs will be executed on the same machine that runs all the Arvados services and may well starve them of resources.
 
-This procedure will install all the main Arvados components to get you up and running in a single host. The whole installation procedure takes somewhere between 15 to 60 minutes, depending on the host resources and its network bandwidth. As a reference, on a virtual machine with 1 core and 1 GB RAM, it takes ~25 minutes to do the initial install.
+It is possible to start with the single host installation method and modify the Arvados configuration file later to address these limitations. E.g. switch to a "different storage volume setup":{{site.baseurl}}/install/configure-s3-object-storage.html for Keep, and switch to "the cloud dispatcher":{{site.baseurl}}/install/crunch2-cloud/install-dispatch-cloud.html to provision compute resources dynamically.
 
-The @provision.sh@ script will help you deploy Arvados by preparing your environment to be able to run the installer, then running it. The actual installer is located at "arvados-formula":https://git.arvados.org/arvados-formula.git/tree/refs/heads/{{ branchname }} and will be cloned during the running of the @provision.sh@ script.  The installer is built using "Saltstack":https://saltproject.io/ and @provision.sh@ performs the install using master-less mode.
+h2(#prerequisites). Prerequisites and planning
 
-After setting up a few variables in a config file (next step), you'll be ready to run it and get Arvados deployed.
+Prerequisites:
 
-h2(#choose_configuration). Choose the desired configuration
+* git
+* a dedicated (virtual) machine for your Arvados server with at least 2 cores and 8 GiB of RAM, running a "supported Arvados distribution":{{site.baseurl}}/install/install-manual-prerequisites.html#supportedlinux
+* a DNS hostname that resolves to the IP address of your Arvados server
+* ports 443, 8800-8805 need to be reachable from your client (configurable in @local.params@, see below)
+* port 80 needs to be reachable from everywhere on the internet (only when using "Let's Encrypt":#lets-encrypt)
+* an SSL certificate matching the hostname in use (only when using "bring your own certificate":#bring-your-own)
 
-For documentation's sake, we will use the cluster name <i>arva2</i> and the domain <i>arv.local</i>. If you don't change them as required in the next steps, installation won't proceed.
+h2(#single_host). Download the installer
 
-Arvados' single host installation can be done in two fashions:
+{% include 'branchname' %}
 
-* Using a single hostname, assigning <i>a different port (other than 443) for each user-facing service</i>: This choice is easier to setup, but the user will need to know the port/s for the different services she wants to connect to.
-* Using multiple hostnames on the same IP: this setup involves a few extra steps but each service will have a meaningful hostname so it will make easier to access them later.
+This procedure will install all the main Arvados components to get you up and running in a single host.
 
-Once you decide which of these choices you prefer, copy one the two example configuration files and directory, and edit them to suit your needs.
+This is a package-based installation method, however the installation script is currently distributed in source form via @git@:
+
+<notextile>
+<pre><code>git clone https://git.arvados.org/arvados.git
+git checkout {{ branchname }}
+cd arvados/tools/salt-install
+</code></pre>
+</notextile>
+
+The @provision.sh@ script will help you deploy Arvados by preparing your environment to be able to run the installer, then running it. The actual installer is located in the "arvados-formula git repository":https://git.arvados.org/arvados-formula.git/tree/refs/heads/{{ branchname }} and will be cloned during the running of the @provision.sh@ script.  The installer is built using "Saltstack":https://saltproject.io/ and @provision.sh@ performs the install using master-less mode.
+
+First, copy the configuration files:
 
-h3(#single_host_single_hostnames). Single host / single hostname
 <notextile>
 <pre><code>cp local.params.example.single_host_single_hostname local.params
 cp -r config_examples/single_host/single_hostname local_config_dir
 </code></pre>
 </notextile>
 
-Edit the variables in the <i>local.params</i> file. Pay attention to the <b>*_PORT, *_TOKEN</b> and <b>*KEY</b> variables.
+Edit the variables in the <i>local.params</i> file. Pay attention to the <b>*_PORT, *_TOKEN</b> and <b>*KEY</b> variables. The *SSL_MODE* variable is discussed in the next section.
+
+h2(#certificates). Choose the SSL configuration (SSL_MODE)
+
+Arvados requires an SSL certificate to work correctly. This installer supports these options:
 
-The <i>single_host</i> examples use self-signed SSL certificates, which are deployed using the same mechanism used to deploy custom certificates.
+* @self-signed@: let the installer create a self-signed certificate
+* @lets-encrypt@: automatically obtain and install an SSL certificate for your hostname
+* @bring-your-own@: supply your own certificate in the `certs` directory
 
-{% include 'install_custom_certificates' %}
+h3(#self-signed). Using a self-signed certificate
 
-If you want to use valid certificates provided by Let's Encrypt, please set the variable <i>USE_LETSENCRYPT=yes</i> and make sure that all the FQDNs that you will use for the public-facing applications (API/controller, Workbench, Keepproxy/Keepweb) are reachable.
+In the default configuration, this installer uses self-signed certificate(s):
 
-h3(#single_host_multiple_hostnames). Single host / multiple hostnames (Alternative configuration)
 <notextile>
-<pre><code>cp local.params.example.single_host_multiple_hostnames local.params
-cp -r config_examples/single_host/multiple_hostnames local_config_dir
+<pre><code>SSL_MODE="self-signed"
 </code></pre>
 </notextile>
 
-Edit the variables in the <i>local.params</i> file.
+When connecting to the Arvados web interface for the first time, you will need to accept the self-signed certificate as trusted to bypass the browser warnings.
 
-h3(#further_customization). Further customization of the installation (modifying the salt pillars and states)
+h3(#lets-encrypt). Using a Let's Encrypt certificate
 
-If you want or need further customization, you can edit the Saltstack pillars and states files. Pay particular attention to the <i>pillars/arvados.sls</i> one. Any extra <i>state</i> file you add under <i>local_config_dir/states</i> will be added to the salt run and applied to the host.
+To automatically get a valid certificate via Let's Encrypt, change the configuration like this:
 
-h2(#run_provision_script). Run the provision.sh script
+<notextile>
+<pre><code>SSL_MODE="lets-encrypt"
+</code></pre>
+</notextile>
 
-When you finished customizing the configuration, you are ready to copy the files to the host (if needed) and run the @provision.sh@ script:
+The hostname for your Arvados cluster must be defined in @HOSTNAME_EXT@ and resolve to the public IP address of your Arvados instance, so that Let's Encrypt can validate the domainname ownership and issue the certificate.
+
+When using AWS, EC2 instances can have a default hostname that ends with <i>amazonaws.com</i>. Let's Encrypt has a blacklist of domain names for which it will not issue certificates, and that blacklist includes the <i>amazonaws.com</i> domain, which means the default hostname can not be used to get a certificate from Let's Encrypt.
+
+h3(#bring-your-own). Bring your own certificate
+
+To supply your own certificate, change the configuration like this:
 
 <notextile>
-<pre><code>scp -r provision.sh local* tests user@host:
-# if you use custom certificates (not Let's Encrypt), make sure to copy those too:
-# scp -r certs user@host:
-ssh user@host sudo ./provision.sh
+<pre><code>SSL_MODE="bring-your-own"
+CUSTOM_CERTS_DIR="${SCRIPT_DIR}/certs"
 </code></pre>
 </notextile>
 
-or, if you saved the @local.params@ in another directory or with some other name
+Copy your certificate files to the directory specified with the variable @CUSTOM_CERTS_DIR@. The provision script will find it there. The certificate and its key need to be copied to a file named after @HOSTNAME_EXT@. For example, if @HOSTNAME_EXT@ is defined as @my-arvados.example.net@, the script will look for
 
 <notextile>
-<pre><code>scp -r provision.sh local* tests user@host:
-ssh user@host sudo ./provision.sh -c /path/to/your/local.params.file
+<pre><code>${CUSTOM_CERTS_DIR}/my-arvados.example.net.crt
+${CUSTOM_CERTS_DIR}/my-arvados.example.net.key
 </code></pre>
 </notextile>
 
-and wait for it to finish.
+All certificate files will be used by nginx. You may need to include intermediate certificates in your certificate file. See "the nginx documentation":http://nginx.org/en/docs/http/configuring_https_servers.html#chains for more details.
+
+h2(#further_customization). Further customization of the installation (modifying the salt pillars and states)
+
+If you want or need further customization, you can edit the Saltstack pillars and states files. Pay particular attention to the <i>pillars/arvados.sls</i> one. Any extra <i>state</i> file you add under <i>local_config_dir/states</i> will be added to the salt run and applied to the host.
+
+h2(#run_provision_script). Run the provision.sh script
 
-If everything goes OK, you'll get some final lines stating something like:
+When you finished customizing the configuration, you are ready to copy the files to the host (if needed) and run the @provision.sh@ script:
 
 <notextile>
-<pre><code>arvados: Succeeded: 109 (changed=9)
-arvados: Failed:      0
+<pre><code>scp -r provision.sh local* tests user@host:
+# if you have set SSL_MODE to "bring-your-own", make sure to also copy the certificate files:
+# scp -r certs user@host:
+ssh user@host sudo ./provision.sh
 </code></pre>
 </notextile>
 
-h2(#final_steps). Final configuration steps
+and wait for it to finish. The script will need 5 to 10 minutes to install and configure everything.
+
+If everything goes OK, you'll get final output that looks similar to this:
 
-Once the deployment went OK, you'll need to perform a few extra steps in your local browser/host to access the cluster.
+<notextile>
+<pre><code>arvados: Succeeded: 151 (changed=36)
+arvados: Failed:      0
+</code></pre>
+</notextile>
 
-h3(#ca_root_certificate). Install the CA root certificate (required in both alternatives)
+h2(#ca_root_certificate). Install the CA root certificate (SSL_MODE=self-signed only)
 
-Arvados uses SSL to encrypt communications. Its UI uses AJAX which will silently fail if the certificate is not valid or signed by an unknown Certification Authority.
+Arvados uses SSL to encrypt communications. The web interface uses AJAX which will silently fail if the certificate is not valid or signed by an unknown Certification Authority.
 
 For this reason, the @arvados-formula@ has a helper state to create a root certificate to authorize Arvados services. The @provision.sh@ script will leave a copy of the generated CA's certificate (@arvados-snakeoil-ca.pem@) in the script's directory so you can add it to your workstation.
 
@@ -142,39 +184,13 @@ To access your Arvados instance using command line clients (such as arv-get and
 </code></pre>
 </notextile>
 
-h3(#single_host_multiple_hostnames_dns_configuration). DNS configuration (single host / multiple hostnames)
-
-When using multiple hostnames, after the setup is done, you need to set up your DNS to be able to access the cluster.
-
-If you don't have access to the domain's DNS to add the required entries, the simplest way to do it is to edit your @/etc/hosts@ file (as root):
-
-<notextile>
-<pre><code>export CLUSTER="arva2"
-export DOMAIN="arv.local"
-export HOST_IP="127.0.0.2"    # This is valid either if installing in your computer directly
-                              # or in a Vagrant VM. If you're installing it on a remote host
-                              # just change the IP to match that of the host.
-echo "${HOST_IP} api keep keep0 collections download ws workbench workbench2 ${CLUSTER}.${DOMAIN} api.${CLUSTER}.${DOMAIN} keep.${CLUSTER}.${DOMAIN} keep0.${CLUSTER}.${DOMAIN} collections.${CLUSTER}.${DOMAIN} download.${CLUSTER}.${DOMAIN} ws.${CLUSTER}.${DOMAIN} workbench.${CLUSTER}.${DOMAIN} workbench2.${CLUSTER}.${DOMAIN}" >> /etc/hosts
-</code></pre>
-</notextile>
-
 h2(#initial_user). Initial user and login
 
-At this point you should be able to log into the Arvados cluster. The initial URL will be:
-
-* https://workbench.arva2.arv.local
-
-or, in general, the url format will be:
+At this point you should be able to log on to your new Arvados cluster. The workbench URL will be
 
-* https://workbench.@<cluster>.<domain>@
+* https://@HOSTNAME_EXT@
 
-By default, the provision script creates an initial user for testing purposes. This user is configured as administrator of the newly created cluster.
-
-Assuming you didn't change these values in the @local.params@ file, the initial credentials are:
-
-* User: 'admin'
-* Password: 'password'
-* Email: 'admin@arva2.arv.local'
+By default, the provision script creates an initial user for testing purposes. This user is configured as administrator of the newly created cluster. The username, password and e-mail address for the initial user are configured in the @local.params@ file. Log in with the e-mail address and password.
 
 h2(#test_install). Test the installed cluster running a simple workflow
 
@@ -197,15 +213,6 @@ Arvados project uuid is 'arva2-j7d0g-0prd8cjlk6kfl7y'
  "owner_uuid":"arva2-tpzed-000000000000000",
  ...
 }
-Uploading arvados/jobs' docker image to the project
-2.1.1: Pulling from arvados/jobs
-8559a31e96f4: Pulling fs layer
-...
-Status: Downloaded newer image for arvados/jobs:2.1.1
-docker.io/arvados/jobs:2.1.1
-2020-11-23 21:43:39 arvados.arv_put[32678] INFO: Creating new cache file at /home/vagrant/.cache/arvados/arv-put/c59256eda1829281424c80f588c7cc4d
-2020-11-23 21:43:46 arvados.arv_put[32678] INFO: Collection saved as 'Docker image arvados jobs:2.1.1 sha256:0dd50'
-arva2-4zz18-1u5pvbld7cvxuy2
 Creating initial user ('admin')
 Setting up user ('admin')
 {
index 8db0ac15e4e0357d3a255de983f0087c5cc4a32b..29a6eacf3ba284c52be0771305e08c67ff35dc03 100644 (file)
@@ -31,8 +31,6 @@ You don't need to be running a Saltstack infrastructure to install Arvados: we w
 
 This is a package-based installation method.
 
-
-
 h2(#provisioning_arvados). Provisioning Arvados with Saltstack
 
 The "tools/salt-install":https://git.arvados.org/arvados.git/tree/{{ branchname }}:/tools/salt-install directory in the Arvados git repository contains a script that you can run in the node/s where you want to install Arvados' components (the @provision.sh@ script) and a few configuration examples for different setups, that you can use to customize your installation.
index eda6563d7a1de99602eecada47eab021807a1782..f3186ebbb6d76d66221860f669960cb9737688eb 100644 (file)
@@ -298,3 +298,37 @@ api = arvados.api()
 for c in arvados.util.keyset_list_all(api.collections().list, filters=[["name", "like", "%sample123%"]]):
     print("got collection " + c["uuid"])
 {% endcodeblock %}
+
+h2. Querying the vocabulary definition
+
+The Python SDK provides facilities to interact with the "active metadata vocabulary":{{ site.baseurl }}/admin/metadata-vocabulary.html in the system. The developer can do key and value lookups in a case-insensitive manner:
+
+{% codeblock as python %}
+from arvados import api, vocabulary
+voc = vocabulary.load_vocabulary(api('v1'))
+
+[k.identifier for k in set(voc.key_aliases.values())]
+# Example output: ['IDTAGCOLORS', 'IDTAGFRUITS', 'IDTAGCOMMENT', 'IDTAGIMPORTANCES', 'IDTAGCATEGORIES', 'IDTAGSIZES', 'IDTAGANIMALS']
+voc['IDTAGSIZES'].preferred_label
+# Example output: 'Size'
+[v.preferred_label for v in set(voc['size'].value_aliases.values())]
+# Example output: ['S', 'M', 'L', 'XL', 'XS']
+voc['size']['s'].aliases
+# Example output: ['S', 'small']
+voc['size']['Small'].identifier
+# Example output: 'IDVALSIZES2'
+{% endcodeblock %}
+
+h2. Translating between vocabulary identifiers and labels
+
+Client software might need to present properties to the user in a human-readable form or take input from the user without requiring them to remember identifiers. For these cases, there're a couple of conversion methods that take a dictionary as input like this:
+
+{% codeblock as python %}
+from arvados import api, vocabulary
+voc = vocabulary.load_vocabulary(api('v1'))
+
+voc.convert_to_labels({'IDTAGIMPORTANCES': 'IDVALIMPORTANCES1'})
+# Example output: {'Importance': 'Critical'}
+voc.convert_to_identifiers({'creature': 'elephant'})
+# Example output: {'IDTAGANIMALS': 'IDVALANIMALS3'}
+{% endcodeblock %}
\ No newline at end of file
index 3854bf64964a28de515389708ec64f13772271b8..8cfb935f281235ddc5f945911d97ca46ad36de2f 100644 (file)
@@ -11,6 +11,8 @@ SPDX-License-Identifier: CC-BY-SA-3.0
 
 This page describes how to link additional login accounts to the same Arvados account.  This can be used to migrate login accounts, for example, from one Google account to another.  It can also be used to migrate login providers, for example from LDAP to Google.  In order to do this, you must be able to log into both the "old" and "new" accounts.
 
+bq. NOTE: If you need to link your accounts on an Arvados cluster federation where user management is centralized, this feature may not be available. If that's the case, the federation admin can do the linking manually.
+
 h2. Link accounts
 
 Follow this process to link the "new" login to the "old" login.
index e076f7e1289c2b7ad48c6b7fb7e8782fd85ff1ce..6d6f80f39c70ac5427578ddd6ed5eb3e78b6a136 100644 (file)
@@ -31,6 +31,7 @@ import (
        "github.com/coreos/go-oidc"
        lru "github.com/hashicorp/golang-lru"
        "github.com/jmoiron/sqlx"
+       "github.com/lib/pq"
        "github.com/sirupsen/logrus"
        "golang.org/x/oauth2"
        "google.golang.org/api/option"
@@ -43,6 +44,7 @@ var (
        tokenCacheNegativeTTL = time.Minute * 5
        tokenCacheTTL         = time.Minute * 10
        tokenCacheRaceWindow  = time.Minute
+       pqCodeUniqueViolation = pq.ErrorCode("23505")
 )
 
 type oidcLoginController struct {
@@ -479,7 +481,6 @@ func (ta *oidcTokenAuthorizer) registerToken(ctx context.Context, tok string) er
        // it's expiring.
        exp := time.Now().UTC().Add(tokenCacheTTL + tokenCacheRaceWindow)
 
-       var aca arvados.APIClientAuthorization
        if updating {
                _, err = tx.ExecContext(ctx, `update api_client_authorizations set expires_at=$1 where api_token=$2`, exp, hmac)
                if err != nil {
@@ -487,23 +488,44 @@ func (ta *oidcTokenAuthorizer) registerToken(ctx context.Context, tok string) er
                }
                ctxlog.FromContext(ctx).WithField("HMAC", hmac).Debug("(*oidcTokenAuthorizer)registerToken: updated api_client_authorizations row")
        } else {
-               aca, err = ta.ctrl.Parent.CreateAPIClientAuthorization(ctx, ta.ctrl.Cluster.SystemRootToken, *authinfo)
+               aca, err := ta.ctrl.Parent.CreateAPIClientAuthorization(ctx, ta.ctrl.Cluster.SystemRootToken, *authinfo)
                if err != nil {
                        return err
                }
-               _, err = tx.ExecContext(ctx, `update api_client_authorizations set api_token=$1, expires_at=$2 where uuid=$3`, hmac, exp, aca.UUID)
+               _, err = tx.ExecContext(ctx, `savepoint upd`)
                if err != nil {
+                       return err
+               }
+               _, err = tx.ExecContext(ctx, `update api_client_authorizations set api_token=$1, expires_at=$2 where uuid=$3`, hmac, exp, aca.UUID)
+               if e, ok := err.(*pq.Error); ok && e.Code == pqCodeUniqueViolation {
+                       // unique_violation, given that the above
+                       // query did not find a row with matching
+                       // api_token, means another thread/process
+                       // also received this same token and won the
+                       // race to insert it -- in which case this
+                       // thread doesn't need to update the database.
+                       // Discard the redundant row.
+                       _, err = tx.ExecContext(ctx, `rollback to savepoint upd`)
+                       if err != nil {
+                               return err
+                       }
+                       _, err = tx.ExecContext(ctx, `delete from api_client_authorizations where uuid=$1`, aca.UUID)
+                       if err != nil {
+                               return err
+                       }
+                       ctxlog.FromContext(ctx).WithField("HMAC", hmac).Debug("(*oidcTokenAuthorizer)registerToken: api_client_authorizations row inserted by another thread")
+               } else if err != nil {
+                       ctxlog.FromContext(ctx).Errorf("%#v", err)
                        return fmt.Errorf("error adding OIDC access token to database: %w", err)
+               } else {
+                       ctxlog.FromContext(ctx).WithFields(logrus.Fields{"UUID": aca.UUID, "HMAC": hmac}).Debug("(*oidcTokenAuthorizer)registerToken: inserted api_client_authorizations row")
                }
-               aca.APIToken = hmac
-               ctxlog.FromContext(ctx).WithFields(logrus.Fields{"UUID": aca.UUID, "HMAC": hmac}).Debug("(*oidcTokenAuthorizer)registerToken: inserted api_client_authorizations row")
        }
        err = tx.Commit()
        if err != nil {
                return err
        }
-       aca.ExpiresAt = exp
-       ta.cache.Add(tok, aca)
+       ta.cache.Add(tok, arvados.APIClientAuthorization{ExpiresAt: exp})
        return nil
 }
 
index 4778e45f5fe48f3a8edeb7e9afa295524d7af5e4..b9f0f56e058482eb74eb527b038136e56979feff 100644 (file)
@@ -17,6 +17,7 @@ import (
        "net/url"
        "sort"
        "strings"
+       "sync"
        "testing"
        "time"
 
@@ -236,18 +237,49 @@ func (s *OIDCLoginSuite) TestOIDCAuthorizer(c *check.C) {
 
        ctx := auth.NewContext(context.Background(), &auth.Credentials{Tokens: []string{accessToken}})
        var exp1 time.Time
-       oidcAuthorizer.WrapCalls(func(ctx context.Context, opts interface{}) (interface{}, error) {
-               creds, ok := auth.FromContext(ctx)
-               c.Assert(ok, check.Equals, true)
-               c.Assert(creds.Tokens, check.HasLen, 1)
-               c.Check(creds.Tokens[0], check.Equals, accessToken)
 
-               err := db.QueryRowContext(ctx, `select expires_at at time zone 'UTC' from api_client_authorizations where api_token=$1`, apiToken).Scan(&exp1)
-               c.Check(err, check.IsNil)
-               c.Check(exp1.Sub(time.Now()) > -time.Second, check.Equals, true)
-               c.Check(exp1.Sub(time.Now()) < time.Second, check.Equals, true)
-               return nil, nil
-       })(ctx, nil)
+       concurrent := 4
+       s.fakeProvider.HoldUserInfo = make(chan *http.Request)
+       s.fakeProvider.ReleaseUserInfo = make(chan struct{})
+       go func() {
+               for i := 0; ; i++ {
+                       if i == concurrent {
+                               close(s.fakeProvider.ReleaseUserInfo)
+                       }
+                       <-s.fakeProvider.HoldUserInfo
+               }
+       }()
+       var wg sync.WaitGroup
+       for i := 0; i < concurrent; i++ {
+               i := i
+               wg.Add(1)
+               go func() {
+                       defer wg.Done()
+                       _, err := oidcAuthorizer.WrapCalls(func(ctx context.Context, opts interface{}) (interface{}, error) {
+                               c.Logf("concurrent req %d/%d", i, concurrent)
+                               var exp time.Time
+
+                               creds, ok := auth.FromContext(ctx)
+                               c.Assert(ok, check.Equals, true)
+                               c.Assert(creds.Tokens, check.HasLen, 1)
+                               c.Check(creds.Tokens[0], check.Equals, accessToken)
+
+                               err := db.QueryRowContext(ctx, `select expires_at at time zone 'UTC' from api_client_authorizations where api_token=$1`, apiToken).Scan(&exp)
+                               c.Check(err, check.IsNil)
+                               c.Check(exp.Sub(time.Now()) > -time.Second, check.Equals, true)
+                               c.Check(exp.Sub(time.Now()) < time.Second, check.Equals, true)
+                               if i == 0 {
+                                       exp1 = exp
+                               }
+                               return nil, nil
+                       })(ctx, nil)
+                       c.Check(err, check.IsNil)
+               }()
+       }
+       wg.Wait()
+       if c.Failed() {
+               c.Fatal("giving up")
+       }
 
        // If the token is used again after the in-memory cache
        // expires, oidcAuthorizer must re-check the token and update
@@ -257,8 +289,8 @@ func (s *OIDCLoginSuite) TestOIDCAuthorizer(c *check.C) {
                var exp time.Time
                err := db.QueryRowContext(ctx, `select expires_at at time zone 'UTC' from api_client_authorizations where api_token=$1`, apiToken).Scan(&exp)
                c.Check(err, check.IsNil)
-               c.Check(exp.Sub(exp1) > 0, check.Equals, true)
-               c.Check(exp.Sub(exp1) < time.Second, check.Equals, true)
+               c.Check(exp.Sub(exp1) > 0, check.Equals, true, check.Commentf("expect %v > 0", exp.Sub(exp1)))
+               c.Check(exp.Sub(exp1) < time.Second, check.Equals, true, check.Commentf("expect %v < 1s", exp.Sub(exp1)))
                return nil, nil
        })(ctx, nil)
 
index fa5e55c42e10af410d86d0e16fc23a637dbaeff2..087adc4b2441648111c0857b93c84eeb48d58cca 100644 (file)
@@ -35,6 +35,12 @@ type OIDCProvider struct {
 
        PeopleAPIResponse map[string]interface{}
 
+       // send incoming /userinfo requests to HoldUserInfo (if not
+       // nil), then receive from ReleaseUserInfo (if not nil),
+       // before responding (these are used to set up races)
+       HoldUserInfo    chan *http.Request
+       ReleaseUserInfo chan struct{}
+
        key       *rsa.PrivateKey
        Issuer    *httptest.Server
        PeopleAPI *httptest.Server
@@ -126,6 +132,12 @@ func (p *OIDCProvider) serveOIDC(w http.ResponseWriter, req *http.Request) {
        case "/auth":
                w.WriteHeader(http.StatusInternalServerError)
        case "/userinfo":
+               if p.HoldUserInfo != nil {
+                       p.HoldUserInfo <- req
+               }
+               if p.ReleaseUserInfo != nil {
+                       <-p.ReleaseUserInfo
+               }
                authhdr := req.Header.Get("Authorization")
                if _, err := jwt.ParseSigned(strings.TrimPrefix(authhdr, "Bearer ")); err != nil {
                        p.c.Logf("OIDCProvider: bad auth %q", authhdr)
index 88596211d4e06b028e6974df2d31a9eee1cf7e19..e0d1c50f03a10fe6f3c0e0e5f45df4cb0f57aec9 100644 (file)
@@ -253,6 +253,7 @@ def api(version=None, cache=True, host=None, token=None, insecure=False,
     svc.insecure = insecure
     svc.request_id = request_id
     svc.config = lambda: util.get_config_once(svc)
+    svc.vocabulary = lambda: util.get_vocabulary_once(svc)
     kwargs['http'].max_request_size = svc._rootDesc.get('maxRequestSize', 0)
     kwargs['http'].cache = None
     kwargs['http']._request_id = lambda: svc.request_id or util.new_request_id()
index 2380e48b734005505f125a05f453e6b88c76265c..be8a03fc314d2cf599c16d5f44b1ab61cc9e885d 100644 (file)
@@ -491,3 +491,11 @@ def get_config_once(svc):
     if not hasattr(svc, '_cached_config'):
         svc._cached_config = svc.configs().get().execute()
     return svc._cached_config
+
+def get_vocabulary_once(svc):
+    if not svc._rootDesc.get('resources').get('vocabularies', False):
+        # Old API server version, no vocabulary export endpoint
+        return {}
+    if not hasattr(svc, '_cached_vocabulary'):
+        svc._cached_vocabulary = svc.vocabularies().get().execute()
+    return svc._cached_vocabulary
diff --git a/sdk/python/arvados/vocabulary.py b/sdk/python/arvados/vocabulary.py
new file mode 100644 (file)
index 0000000..3bb87c4
--- /dev/null
@@ -0,0 +1,127 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
+import logging
+
+from . import api
+
+_logger = logging.getLogger('arvados.vocabulary')
+
+def load_vocabulary(api_client=None):
+    """Load the Arvados vocabulary from the API.
+    """
+    if api_client is None:
+        api_client = api('v1')
+    return Vocabulary(api_client.vocabulary())
+
+class VocabularyError(Exception):
+    """Base class for all vocabulary errors.
+    """
+    pass
+
+class VocabularyKeyError(VocabularyError):
+    pass
+
+class VocabularyValueError(VocabularyError):
+    pass
+
+class Vocabulary(object):
+    def __init__(self, voc_definition={}):
+        self.strict_keys = voc_definition.get('strict_tags', False)
+        self.key_aliases = {}
+
+        for key_id, val in (voc_definition.get('tags') or {}).items():
+            strict = val.get('strict', False)
+            key_labels = [l['label'] for l in val.get('labels', [])]
+            values = {}
+            for v_id, v_val in (val.get('values') or {}).items():
+                labels = [l['label'] for l in v_val.get('labels', [])]
+                values[v_id] = VocabularyValue(v_id, labels)
+            vk = VocabularyKey(key_id, key_labels, values, strict)
+            self.key_aliases[key_id.lower()] = vk
+            for alias in vk.aliases:
+                self.key_aliases[alias.lower()] = vk
+
+    def __getitem__(self, key):
+        return self.key_aliases[key.lower()]
+
+    def convert_to_identifiers(self, obj={}):
+        """Translate key/value pairs to machine readable identifiers.
+        """
+        return self._convert_to_what(obj, 'identifier')
+
+    def convert_to_labels(self, obj={}):
+        """Translate key/value pairs to human readable labels.
+        """
+        return self._convert_to_what(obj, 'preferred_label')
+
+    def _convert_to_what(self, obj={}, what=None):
+        if not isinstance(obj, dict):
+            raise ValueError("obj must be a dict")
+        if what not in ['preferred_label', 'identifier']:
+            raise ValueError("what attr must be 'preferred_label' or 'identifier'")
+        r = {}
+        for k, v in obj.items():
+            # Key validation & lookup
+            key_found = False
+            if not isinstance(k, str):
+                raise VocabularyKeyError("key '{}' must be a string".format(k))
+            k_what, v_what = k, v
+            try:
+                k_what = getattr(self[k], what)
+                key_found = True
+            except KeyError:
+                if self.strict_keys:
+                    raise VocabularyKeyError("key '{}' not found in vocabulary".format(k))
+
+            # Value validation & lookup
+            if isinstance(v, list):
+                v_what = []
+                for x in v:
+                    if not isinstance(x, str):
+                        raise VocabularyValueError("value '{}' for key '{}' must be a string".format(x, k))
+                    try:
+                        v_what.append(getattr(self[k][x], what))
+                    except KeyError:
+                        if self[k].strict:
+                            raise VocabularyValueError("value '{}' not found for key '{}'".format(x, k))
+                        v_what.append(x)
+            else:
+                if not isinstance(v, str):
+                    raise VocabularyValueError("{} value '{}' for key '{}' must be a string".format(type(v).__name__, v, k))
+                try:
+                    v_what = getattr(self[k][v], what)
+                except KeyError:
+                    if key_found and self[k].strict:
+                        raise VocabularyValueError("value '{}' not found for key '{}'".format(v, k))
+
+            r[k_what] = v_what
+        return r
+
+class VocabularyData(object):
+    def __init__(self, identifier, aliases=[]):
+        self.identifier = identifier
+        self.aliases = aliases
+
+    def __getattribute__(self, name):
+        if name == 'preferred_label':
+            return self.aliases[0]
+        return super(VocabularyData, self).__getattribute__(name)
+
+class VocabularyValue(VocabularyData):
+    def __init__(self, identifier, aliases=[]):
+        super(VocabularyValue, self).__init__(identifier, aliases)
+
+class VocabularyKey(VocabularyData):
+    def __init__(self, identifier, aliases=[], values={}, strict=False):
+        super(VocabularyKey, self).__init__(identifier, aliases)
+        self.strict = strict
+        self.value_aliases = {}
+        for v_id, v_val in values.items():
+            self.value_aliases[v_id.lower()] = v_val
+            for v_alias in v_val.aliases:
+                self.value_aliases[v_alias.lower()] = v_val
+
+    def __getitem__(self, key):
+        return self.value_aliases[key.lower()]
\ No newline at end of file
diff --git a/sdk/python/tests/test_vocabulary.py b/sdk/python/tests/test_vocabulary.py
new file mode 100644 (file)
index 0000000..aa2e739
--- /dev/null
@@ -0,0 +1,319 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
+import arvados
+import unittest
+import mock
+
+from arvados import api, vocabulary
+
+class VocabularyTest(unittest.TestCase):
+    EXAMPLE_VOC = {
+        'tags': {
+            'IDTAGANIMALS': {
+                'strict': False,
+                'labels': [
+                    {'label': 'Animal'},
+                    {'label': 'Creature'},
+                ],
+                'values': {
+                    'IDVALANIMAL1': {
+                        'labels': [
+                            {'label': 'Human'},
+                            {'label': 'Homo sapiens'},
+                        ],
+                    },
+                    'IDVALANIMAL2': {
+                        'labels': [
+                            {'label': 'Elephant'},
+                            {'label': 'Loxodonta'},
+                        ],
+                    },
+                },
+            },
+            'IDTAGIMPORTANCES': {
+                'strict': True,
+                'labels': [
+                    {'label': 'Importance'},
+                    {'label': 'Priority'},
+                ],
+                'values': {
+                    'IDVALIMPORTANCE1': {
+                        'labels': [
+                            {'label': 'High'},
+                            {'label': 'High priority'},
+                        ],
+                    },
+                    'IDVALIMPORTANCE2': {
+                        'labels': [
+                            {'label': 'Medium'},
+                            {'label': 'Medium priority'},
+                        ],
+                    },
+                    'IDVALIMPORTANCE3': {
+                        'labels': [
+                            {'label': 'Low'},
+                            {'label': 'Low priority'},
+                        ],
+                    },
+                },
+            },
+            'IDTAGCOMMENTS': {
+                'strict': False,
+                'labels': [
+                    {'label': 'Comment'},
+                    {'label': 'Notes'},
+                ],
+                'values': None,
+            },
+        },
+    }
+
+    def setUp(self):
+        self.api = arvados.api('v1')
+        self.voc = vocabulary.Vocabulary(self.EXAMPLE_VOC)
+        self.api.vocabulary = mock.MagicMock(return_value=self.EXAMPLE_VOC)
+
+    def test_vocabulary_keys(self):
+        self.assertEqual(self.voc.strict_keys, False)
+        self.assertEqual(
+            self.voc.key_aliases.keys(),
+            set(['idtaganimals', 'creature', 'animal',
+                'idtagimportances', 'importance', 'priority',
+                'idtagcomments', 'comment', 'notes'])
+        )
+
+        vk = self.voc.key_aliases['creature']
+        self.assertEqual(vk.strict, False)
+        self.assertEqual(vk.identifier, 'IDTAGANIMALS')
+        self.assertEqual(vk.aliases, ['Animal', 'Creature'])
+        self.assertEqual(vk.preferred_label, 'Animal')
+        self.assertEqual(
+            vk.value_aliases.keys(),
+            set(['idvalanimal1', 'human', 'homo sapiens',
+                'idvalanimal2', 'elephant', 'loxodonta'])
+        )
+
+    def test_vocabulary_values(self):
+        vk = self.voc.key_aliases['creature']
+        vv = vk.value_aliases['human']
+        self.assertEqual(vv.identifier, 'IDVALANIMAL1')
+        self.assertEqual(vv.aliases, ['Human', 'Homo sapiens'])
+        self.assertEqual(vv.preferred_label, 'Human')
+
+    def test_vocabulary_indexing(self):
+        self.assertEqual(self.voc['creature']['human'].identifier, 'IDVALANIMAL1')
+        self.assertEqual(self.voc['Creature']['Human'].identifier, 'IDVALANIMAL1')
+        self.assertEqual(self.voc['CREATURE']['HUMAN'].identifier, 'IDVALANIMAL1')
+        with self.assertRaises(KeyError):
+            inexistant = self.voc['foo']
+
+    def test_empty_vocabulary(self):
+        voc = vocabulary.Vocabulary({})
+        self.assertEqual(voc.strict_keys, False)
+        self.assertEqual(voc.key_aliases, {})
+
+    def test_load_vocabulary_with_api(self):
+        voc = vocabulary.load_vocabulary(self.api)
+        self.assertEqual(voc['creature']['human'].identifier, 'IDVALANIMAL1')
+        self.assertEqual(voc['Creature']['Human'].identifier, 'IDVALANIMAL1')
+        self.assertEqual(voc['CREATURE']['HUMAN'].identifier, 'IDVALANIMAL1')
+
+    def test_convert_to_identifiers(self):
+        cases = [
+            {'IDTAGIMPORTANCES': 'IDVALIMPORTANCE1'},
+            {'IDTAGIMPORTANCES': 'High'},
+            {'importance': 'IDVALIMPORTANCE1'},
+            {'priority': 'high priority'},
+        ]
+        for case in cases:
+            self.assertEqual(
+                self.voc.convert_to_identifiers(case),
+                {'IDTAGIMPORTANCES': 'IDVALIMPORTANCE1'},
+                "failing test case: {}".format(case)
+            )
+
+    def test_convert_to_identifiers_multiple_pairs(self):
+        cases = [
+            {'IDTAGIMPORTANCES': 'IDVALIMPORTANCE1', 'IDTAGANIMALS': 'IDVALANIMAL1', 'IDTAGCOMMENTS': 'Very important person'},
+            {'IDTAGIMPORTANCES': 'High', 'IDTAGANIMALS': 'IDVALANIMAL1', 'comment': 'Very important person'},
+            {'importance': 'IDVALIMPORTANCE1', 'animal': 'IDVALANIMAL1', 'notes': 'Very important person'},
+            {'priority': 'high priority', 'animal': 'IDVALANIMAL1', 'NOTES': 'Very important person'},
+        ]
+        for case in cases:
+            self.assertEqual(
+                self.voc.convert_to_identifiers(case),
+                {'IDTAGIMPORTANCES': 'IDVALIMPORTANCE1', 'IDTAGANIMALS': 'IDVALANIMAL1', 'IDTAGCOMMENTS': 'Very important person'},
+                "failing test case: {}".format(case)
+            )
+
+    def test_convert_to_identifiers_value_lists(self):
+        cases = [
+            {'IDTAGIMPORTANCES': ['IDVALIMPORTANCE1', 'IDVALIMPORTANCE2']},
+            {'IDTAGIMPORTANCES': ['High', 'Medium']},
+            {'importance': ['IDVALIMPORTANCE1', 'IDVALIMPORTANCE2']},
+            {'priority': ['high', 'medium']},
+        ]
+        for case in cases:
+            self.assertEqual(
+                self.voc.convert_to_identifiers(case),
+                {'IDTAGIMPORTANCES': ['IDVALIMPORTANCE1', 'IDVALIMPORTANCE2']},
+                "failing test case: {}".format(case)
+            )
+
+    def test_convert_to_identifiers_unknown_key(self):
+        # Non-strict vocabulary
+        self.assertEqual(self.voc.strict_keys, False)
+        self.assertEqual(self.voc.convert_to_identifiers({'foo': 'bar'}), {'foo': 'bar'})
+        # Strict vocabulary
+        strict_voc = arvados.vocabulary.Vocabulary(self.EXAMPLE_VOC)
+        strict_voc.strict_keys = True
+        with self.assertRaises(vocabulary.VocabularyKeyError):
+            strict_voc.convert_to_identifiers({'foo': 'bar'})
+
+    def test_convert_to_identifiers_invalid_key(self):
+        with self.assertRaises(vocabulary.VocabularyKeyError):
+            self.voc.convert_to_identifiers({42: 'bar'})
+        with self.assertRaises(vocabulary.VocabularyKeyError):
+            self.voc.convert_to_identifiers({None: 'bar'})
+        with self.assertRaises(vocabulary.VocabularyKeyError):
+            self.voc.convert_to_identifiers({('f', 'o', 'o'): 'bar'})
+
+    def test_convert_to_identifiers_unknown_value(self):
+        # Non-strict key
+        self.assertEqual(self.voc['animal'].strict, False)
+        self.assertEqual(self.voc.convert_to_identifiers({'Animal': 'foo'}), {'IDTAGANIMALS': 'foo'})
+        # Strict key
+        self.assertEqual(self.voc['priority'].strict, True)
+        with self.assertRaises(vocabulary.VocabularyValueError):
+            self.voc.convert_to_identifiers({'Priority': 'foo'})
+
+    def test_convert_to_identifiers_invalid_value(self):
+        with self.assertRaises(vocabulary.VocabularyValueError):
+            self.voc.convert_to_identifiers({'Animal': 42})
+        with self.assertRaises(vocabulary.VocabularyValueError):
+            self.voc.convert_to_identifiers({'Animal': None})
+        with self.assertRaises(vocabulary.VocabularyValueError):
+            self.voc.convert_to_identifiers({'Animal': {'hello': 'world'}})
+        with self.assertRaises(vocabulary.VocabularyValueError):
+            self.voc.convert_to_identifiers({'Animal': [42]})
+        with self.assertRaises(vocabulary.VocabularyValueError):
+            self.voc.convert_to_identifiers({'Animal': [None]})
+        with self.assertRaises(vocabulary.VocabularyValueError):
+            self.voc.convert_to_identifiers({'Animal': [{'hello': 'world'}]})
+
+    def test_convert_to_identifiers_unknown_value_list(self):
+        # Non-strict key
+        self.assertEqual(self.voc['animal'].strict, False)
+        self.assertEqual(
+            self.voc.convert_to_identifiers({'Animal': ['foo', 'loxodonta']}),
+            {'IDTAGANIMALS': ['foo', 'IDVALANIMAL2']}
+        )
+        # Strict key
+        self.assertEqual(self.voc['priority'].strict, True)
+        with self.assertRaises(vocabulary.VocabularyValueError):
+            self.voc.convert_to_identifiers({'Priority': ['foo', 'bar']})
+
+    def test_convert_to_labels(self):
+        cases = [
+            {'IDTAGIMPORTANCES': 'IDVALIMPORTANCE1'},
+            {'IDTAGIMPORTANCES': 'High'},
+            {'importance': 'IDVALIMPORTANCE1'},
+            {'priority': 'high priority'},
+        ]
+        for case in cases:
+            self.assertEqual(
+                self.voc.convert_to_labels(case),
+                {'Importance': 'High'},
+                "failing test case: {}".format(case)
+            )
+
+    def test_convert_to_labels_multiple_pairs(self):
+        cases = [
+            {'IDTAGIMPORTANCES': 'IDVALIMPORTANCE1', 'IDTAGANIMALS': 'IDVALANIMAL1', 'IDTAGCOMMENTS': 'Very important person'},
+            {'IDTAGIMPORTANCES': 'High', 'IDTAGANIMALS': 'IDVALANIMAL1', 'comment': 'Very important person'},
+            {'importance': 'IDVALIMPORTANCE1', 'animal': 'IDVALANIMAL1', 'notes': 'Very important person'},
+            {'priority': 'high priority', 'animal': 'IDVALANIMAL1', 'NOTES': 'Very important person'},
+        ]
+        for case in cases:
+            self.assertEqual(
+                self.voc.convert_to_labels(case),
+                {'Importance': 'High', 'Animal': 'Human', 'Comment': 'Very important person'},
+                "failing test case: {}".format(case)
+            )
+
+    def test_convert_to_labels_value_lists(self):
+        cases = [
+            {'IDTAGIMPORTANCES': ['IDVALIMPORTANCE1', 'IDVALIMPORTANCE2']},
+            {'IDTAGIMPORTANCES': ['High', 'Medium']},
+            {'importance': ['IDVALIMPORTANCE1', 'IDVALIMPORTANCE2']},
+            {'priority': ['high', 'medium']},
+        ]
+        for case in cases:
+            self.assertEqual(
+                self.voc.convert_to_labels(case),
+                {'Importance': ['High', 'Medium']},
+                "failing test case: {}".format(case)
+            )
+
+    def test_convert_to_labels_unknown_key(self):
+        # Non-strict vocabulary
+        self.assertEqual(self.voc.strict_keys, False)
+        self.assertEqual(self.voc.convert_to_labels({'foo': 'bar'}), {'foo': 'bar'})
+        # Strict vocabulary
+        strict_voc = arvados.vocabulary.Vocabulary(self.EXAMPLE_VOC)
+        strict_voc.strict_keys = True
+        with self.assertRaises(vocabulary.VocabularyKeyError):
+            strict_voc.convert_to_labels({'foo': 'bar'})
+
+    def test_convert_to_labels_invalid_key(self):
+        with self.assertRaises(vocabulary.VocabularyKeyError):
+            self.voc.convert_to_labels({42: 'bar'})
+        with self.assertRaises(vocabulary.VocabularyKeyError):
+            self.voc.convert_to_labels({None: 'bar'})
+        with self.assertRaises(vocabulary.VocabularyKeyError):
+            self.voc.convert_to_labels({('f', 'o', 'o'): 'bar'})
+
+    def test_convert_to_labels_unknown_value(self):
+        # Non-strict key
+        self.assertEqual(self.voc['animal'].strict, False)
+        self.assertEqual(self.voc.convert_to_labels({'IDTAGANIMALS': 'foo'}), {'Animal': 'foo'})
+        # Strict key
+        self.assertEqual(self.voc['priority'].strict, True)
+        with self.assertRaises(vocabulary.VocabularyValueError):
+            self.voc.convert_to_labels({'IDTAGIMPORTANCES': 'foo'})
+
+    def test_convert_to_labels_invalid_value(self):
+        with self.assertRaises(vocabulary.VocabularyValueError):
+            self.voc.convert_to_labels({'IDTAGIMPORTANCES': {'high': True}})
+        with self.assertRaises(vocabulary.VocabularyValueError):
+            self.voc.convert_to_labels({'IDTAGIMPORTANCES': None})
+        with self.assertRaises(vocabulary.VocabularyValueError):
+            self.voc.convert_to_labels({'IDTAGIMPORTANCES': 42})
+        with self.assertRaises(vocabulary.VocabularyValueError):
+            self.voc.convert_to_labels({'IDTAGIMPORTANCES': False})
+        with self.assertRaises(vocabulary.VocabularyValueError):
+            self.voc.convert_to_labels({'IDTAGIMPORTANCES': [42]})
+        with self.assertRaises(vocabulary.VocabularyValueError):
+            self.voc.convert_to_labels({'IDTAGIMPORTANCES': [None]})
+        with self.assertRaises(vocabulary.VocabularyValueError):
+            self.voc.convert_to_labels({'IDTAGIMPORTANCES': [{'high': True}]})
+
+    def test_convert_to_labels_unknown_value_list(self):
+        # Non-strict key
+        self.assertEqual(self.voc['animal'].strict, False)
+        self.assertEqual(
+            self.voc.convert_to_labels({'IDTAGANIMALS': ['foo', 'IDVALANIMAL1']}),
+            {'Animal': ['foo', 'Human']}
+        )
+        # Strict key
+        self.assertEqual(self.voc['priority'].strict, True)
+        with self.assertRaises(vocabulary.VocabularyValueError):
+            self.voc.convert_to_labels({'IDTAGIMPORTANCES': ['foo', 'bar']})
+
+    def test_convert_roundtrip(self):
+        initial = {'IDTAGIMPORTANCES': 'IDVALIMPORTANCE1', 'IDTAGANIMALS': 'IDVALANIMAL1', 'IDTAGCOMMENTS': 'Very important person'}
+        converted = self.voc.convert_to_labels(initial)
+        self.assertNotEqual(converted, initial)
+        self.assertEqual(self.voc.convert_to_identifiers(converted), initial)
index 59ac639baf929dd2c06c9352159f33288be1d792..100b9168179e04b4baa5d6b51a438f00e2e42104 100644 (file)
@@ -37,7 +37,7 @@ class Arvados::V1::SchemaController < ApplicationController
         # format is YYYYMMDD, must be fixed width (needs to be lexically
         # sortable), updated manually, may be used by clients to
         # determine availability of API server features.
-        revision: "20210628",
+        revision: "20220222",
         source_version: AppVersion.hash,
         sourceVersion: AppVersion.hash, # source_version should be deprecated in the future
         packageVersion: AppVersion.package_version,
@@ -427,6 +427,27 @@ class Arvados::V1::SchemaController < ApplicationController
         }
       }
 
+      discovery[:resources]['vocabularies'] = {
+        methods: {
+          get: {
+            id: "arvados.vocabularies.get",
+            path: "vocabulary",
+            httpMethod: "GET",
+            description: "Get vocabulary definition",
+            parameters: {
+            },
+            parameterOrder: [
+            ],
+            response: {
+            },
+            scopes: [
+              "https://api.arvados.org/auth/arvados",
+              "https://api.arvados.org/auth/arvados.readonly"
+            ]
+          },
+        }
+      }
+
       discovery[:resources]['sys'] = {
         methods: {
           get: {
index febb8ea51611eb5a21549b8133770fe059a2ca5c..811cd89758d9ef8642be5382c9bac54b4f649134 100644 (file)
@@ -21,6 +21,7 @@ class User < ArvadosModel
             uniqueness: true,
             allow_nil: true)
   validate :must_unsetup_to_deactivate
+  validate :identity_url_nil_if_empty
   before_update :prevent_privilege_escalation
   before_update :prevent_inactive_admin
   before_update :verify_repositories_empty, :if => Proc.new {
@@ -810,4 +811,10 @@ SELECT target_uuid, perm_level
       repo.save!
     end
   end
+
+  def identity_url_nil_if_empty
+    if identity_url == ""
+      self.identity_url = nil
+    end
+  end
 end
index 7368d893745658fd20de42de6d2410f421a1098b..9a0e1dbf9cc2427be71613b1ed939bc125f07351 100644 (file)
@@ -797,4 +797,12 @@ class UserTest < ActiveSupport::TestCase
     assert user.save
   end
 
+  test "empty identity_url saves as null" do
+    set_user_from_auth :admin
+    user = users(:active)
+    assert user.update_attributes(identity_url: '')
+    user.reload
+    assert_nil user.identity_url
+  end
+
 end
index f5759c482d2a03aedce42b26dcb9e68520a7a389..27f1591c8f498c2112aa7178eba4003c5f999baa 100644 (file)
@@ -37,9 +37,9 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
                                     s#domain_fixme_or_this_wont_work#local#g;
                                     s#CONTROLLER_EXT_SSL_PORT=443#CONTROLLER_EXT_SSL_PORT=8443#g;
                                     s#RELEASE=\"production\"#RELEASE=\"development\"#g;
-                                    s/# VERSION=.*$/VERSION=\"latest\"/g;
-                                    s/#\ BRANCH=\"main\"/\ BRANCH=\"main\"/g' \
+                                    s/# VERSION=.*$/VERSION=\"latest\"/g;' \
                                     /vagrant/local.params.example.single_host_multiple_hostnames > /tmp/local.params.single_host_multiple_hostnames"
+                                    # s/#\ BRANCH=\"main\"/\ BRANCH=\"main\"/g;' \
 
      arv.vm.provision "shell",
                       path: "provision.sh",
@@ -54,7 +54,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
 
    # A single_host single_hostname example
    config.vm.define "arvados-sh-sn" do |arv|
-     arv.vm.box = "bento/debian-10"
+     arv.vm.box = "bento/debian-11"
      arv.vm.hostname = "zeppo"
      # CPU/RAM
      config.vm.provider :virtualbox do |v|
@@ -80,15 +80,15 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
      arv.vm.provision "shell",
                       inline: "cp -vr /vagrant/config_examples/single_host/single_hostname /home/vagrant/local_config_dir;
                                cp -vr /vagrant/tests /home/vagrant/tests;
-                               sed 's#HOSTNAME_EXT=\"\"#HOSTNAME_EXT=\"zeppo.local\"#g;
+                               sed 's#HOSTNAME_EXT=\"hostname_ext_fixme_or_this_wont_work\"#HOSTNAME_EXT=\"zeppo.local\"#g;
                                     s#cluster_fixme_or_this_wont_work#zeppo#g;
-                                    s/#\ BRANCH=\"main\"/\ BRANCH=\"main\"/g;
                                     s#domain_fixme_or_this_wont_work#local#g;' \
                                     /vagrant/local.params.example.single_host_single_hostname > /tmp/local.params.single_host_single_hostname"
+                                    # s/#\ BRANCH=\"main\"/\ BRANCH=\"main\"/g;
      arv.vm.provision "shell",
                       path: "provision.sh",
                       args: [
-                        "--debug",
+                        "--debug",
                         "--config /tmp/local.params.single_host_single_hostname",
                         "--test",
                         "--vagrant"
diff --git a/tools/salt-install/config_examples/single_host/multiple_hostnames/states/dns.sls b/tools/salt-install/config_examples/single_host/multiple_hostnames/states/dns.sls
new file mode 100644 (file)
index 0000000..f298e8f
--- /dev/null
@@ -0,0 +1,8 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
+dns:
+  pkg.installed:
+    - pkgs:
+      - dnsmasq
index 78a5a938f337d437b5a8a1606ef571945f81dccf..334edb946b28880336bf85683fc30a45eef815a1 100644 (file)
@@ -68,6 +68,7 @@ arvados:
       password: "__DATABASE_PASSWORD__"
       user: __CLUSTER___arvados
       encoding: en_US.utf8
+      client_encoding: UTF8
 
     tls:
       # certificate: ''
@@ -80,7 +81,6 @@ arvados:
       system_root: __SYSTEM_ROOT_TOKEN__
       management: __MANAGEMENT_TOKEN__
       anonymous_user: __ANONYMOUS_USER_TOKEN__
-      rails_secret: YDLxHf4GqqmLXYAMgndrAmFEdqgC0sBqX7TEjMN2rw9D6EVwgx
 
     ### KEYS
     secrets:
@@ -102,7 +102,7 @@ arvados:
       # <cluster>-nyw5e-<volume>
       __CLUSTER__-nyw5e-000000000000000:
         AccessViaHosts:
-          'http://__HOSTNAME_INT__:25107':
+          'http://__IP_INT__:25107':
             ReadOnly: false
         Replication: 2
         Driver: Directory
@@ -119,21 +119,21 @@ arvados:
       Controller:
         ExternalURL: 'https://__HOSTNAME_EXT__:__CONTROLLER_EXT_SSL_PORT__'
         InternalURLs:
-          'http://__HOSTNAME_INT__:8003': {}
+          'http://__IP_INT__:8003': {}
       Keepproxy:
         ExternalURL: 'https://__HOSTNAME_EXT__:__KEEP_EXT_SSL_PORT__'
         InternalURLs:
-          'http://__HOSTNAME_INT__:25100': {}
+          'http://__IP_INT__:25100': {}
       Keepstore:
         InternalURLs:
-          'http://__HOSTNAME_INT__:25107': {}
+          'http://__IP_INT__:25107': {}
       RailsAPI:
         InternalURLs:
-          'http://__HOSTNAME_INT__:8004': {}
+          'http://__IP_INT__:8004': {}
       WebDAV:
         ExternalURL: 'https://__HOSTNAME_EXT__:__KEEPWEB_EXT_SSL_PORT__'
         InternalURLs:
-          'http://__HOSTNAME_INT__:9003': {}
+          'http://__IP_INT__:9003': {}
       WebDAVDownload:
         ExternalURL: 'https://__HOSTNAME_EXT__:__KEEPWEB_EXT_SSL_PORT__'
       WebShell:
@@ -141,7 +141,7 @@ arvados:
       Websocket:
         ExternalURL: 'wss://__HOSTNAME_EXT__:__WEBSOCKET_EXT_SSL_PORT__/websocket'
         InternalURLs:
-          'http://__HOSTNAME_INT__:8005': {}
+          'http://__IP_INT__:8005': {}
       Workbench1:
         ExternalURL: 'https://__HOSTNAME_EXT__:__WORKBENCH1_EXT_SSL_PORT__'
       Workbench2:
diff --git a/tools/salt-install/config_examples/single_host/single_hostname/pillars/aws_credentials.sls b/tools/salt-install/config_examples/single_host/single_hostname/pillars/aws_credentials.sls
new file mode 100644 (file)
index 0000000..35cdbf7
--- /dev/null
@@ -0,0 +1,9 @@
+---
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
+aws_credentials:
+  region: __LE_AWS_REGION__
+  access_key_id: __LE_AWS_ACCESS_KEY_ID__
+  secret_access_key: __LE_AWS_SECRET_ACCESS_KEY__
index 54d22561594ac4ebc2019edb9b54fe156f7440ec..30d90153e8d49899013bf41aa20cb6670b47aa88 100644 (file)
@@ -7,3 +7,4 @@ docker:
   pkg:
     docker:
       use_upstream: package
+      daemon_config: {"dns": ["__IP_INT__"]}
diff --git a/tools/salt-install/config_examples/single_host/single_hostname/pillars/letsencrypt.sls b/tools/salt-install/config_examples/single_host/single_hostname/pillars/letsencrypt.sls
new file mode 100644 (file)
index 0000000..895c650
--- /dev/null
@@ -0,0 +1,24 @@
+---
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
+### LETSENCRYPT
+letsencrypt:
+  use_package: true
+  pkgs:
+    - certbot: latest
+    - python3-certbot-nginx
+  config:
+    server: https://acme-v02.api.letsencrypt.org/directory
+    email: __INITIAL_USER_EMAIL__
+    authenticator: nginx
+    agree-tos: true
+    keep-until-expiring: true
+    expand: true
+    max-log-backups: 0
+    deploy-hook: systemctl reload nginx
+
+  domainsets:
+    __HOSTNAME_EXT__:
+      - __HOSTNAME_EXT__
index 18f09af50329e4af62674e5f79393e8348e8a7c3..ac3ba99f16cd5316bbe88f757c56502ab2499091 100644 (file)
@@ -13,12 +13,12 @@ nginx:
   ### SITES
   servers:
     managed:
-      arvados_api:
+      arvados_api.conf:
         enabled: true
         overwrite: true
         config:
           - server:
-            - listen: '__HOSTNAME_INT__:8004'
+            - listen: '__IP_INT__:8004'
             - server_name: api
             - root: /var/www/arvados-api/current/public
             - index:  index.html index.htm
index b7b75ab9c289e6e1c4f35d3edbb98b40d56bd382..cfd1525924873e3899823b977130f937c2e845dd 100644 (file)
@@ -14,28 +14,30 @@ nginx:
           default: 1
           '127.0.0.0/8': 0
         upstream controller_upstream:
-          - server: '__HOSTNAME_INT__:8003  fail_timeout=10s'
+          - server: '__IP_INT__:8003  fail_timeout=10s'
 
   ### SITES
   servers:
     managed:
       ### DEFAULT
-      arvados_controller_default:
+      arvados_controller_default.conf:
         enabled: true
         overwrite: true
         config:
           - server:
             - server_name: _
             - listen:
-              - 80 default_server
+              - 80
             - location /.well-known:
               - root: /var/www
             - location /:
               - return: '301 https://$host$request_uri'
 
-      arvados_controller_ssl:
+      arvados_controller_ssl.conf:
         enabled: true
         overwrite: true
+        requires:
+          __CERT_REQUIRES__
         config:
           - server:
             - server_name: __HOSTNAME_EXT__
@@ -52,7 +54,9 @@ nginx:
               - proxy_set_header: 'X-Real-IP $remote_addr'
               - proxy_set_header: 'X-Forwarded-For $proxy_add_x_forwarded_for'
               - proxy_set_header: 'X-External-Client $external_client'
-            - include: 'snippets/arvados-snakeoil.conf'
+            - include: snippets/ssl_hardening_default.conf
+            - ssl_certificate: __CERT_PEM__
+            - ssl_certificate_key: __CERT_KEY__
             - access_log: /var/log/nginx/__CLUSTER__.__DOMAIN__.access.log combined
             - error_log: /var/log/nginx/__CLUSTER__.__DOMAIN__.error.log
             - client_max_body_size: 128m
index 81d72aac74109531a073aea84b13be8d8d56c002..11f6e85695bf664ff120af37b705d37a449fcd64 100644 (file)
@@ -11,13 +11,27 @@ nginx:
       ### STREAMS
       http:
         upstream keepproxy_upstream:
-          - server: '__HOSTNAME_INT__:25100 fail_timeout=10s'
+          - server: '__IP_INT__:25100 fail_timeout=10s'
 
   servers:
     managed:
-      arvados_keepproxy_ssl:
+      ### DEFAULT
+      arvados_keepproxy_default.conf:
         enabled: true
         overwrite: true
+        config:
+          - server:
+            - server_name: keep.__CLUSTER__.__DOMAIN__
+            - listen:
+              - 80
+            - location /:
+              - return: '301 https://$host$request_uri'
+
+      arvados_keepproxy_ssl.conf:
+        enabled: true
+        overwrite: true
+        requires:
+          __CERT_REQUIRES__
         config:
           - server:
             - server_name: __HOSTNAME_EXT__
@@ -38,6 +52,8 @@ nginx:
             - client_max_body_size: 64M
             - proxy_http_version: '1.1'
             - proxy_request_buffering: 'off'
-            - include: 'snippets/arvados-snakeoil.conf'
+            - include: snippets/ssl_hardening_default.conf
+            - ssl_certificate: __CERT_PEM__
+            - ssl_certificate_key: __CERT_KEY__
             - access_log: /var/log/nginx/keepproxy.__CLUSTER__.__DOMAIN__.access.log combined
             - error_log: /var/log/nginx/keepproxy.__CLUSTER__.__DOMAIN__.error.log
index fcb56c994964bed01a55c6818ea82bae75a962ac..1082b5357d0a0cd9cee38da0b67d7f48cf72b7e2 100644 (file)
@@ -11,14 +11,16 @@ nginx:
       ### STREAMS
       http:
         upstream collections_downloads_upstream:
-          - server: '__HOSTNAME_INT__:9003 fail_timeout=10s'
+          - server: '__IP_INT__:9003 fail_timeout=10s'
 
   servers:
     managed:
       ### COLLECTIONS / DOWNLOAD
-      arvados_collections_download_ssl:
+      arvados_collections_download_ssl.conf:
         enabled: true
         overwrite: true
+        requires:
+          __CERT_REQUIRES__
         config:
           - server:
             - server_name: __HOSTNAME_EXT__
@@ -38,6 +40,8 @@ nginx:
             - client_max_body_size: 0
             - proxy_http_version: '1.1'
             - proxy_request_buffering: 'off'
-            - include: 'snippets/arvados-snakeoil.conf'
+            - include: snippets/ssl_hardening_default.conf
+            - ssl_certificate: __CERT_PEM__
+            - ssl_certificate_key: __CERT_KEY__
             - access_log: /var/log/nginx/keepweb.__CLUSTER__.__DOMAIN__.access.log combined
             - error_log: /var/log/nginx/keepweb.__CLUSTER__.__DOMAIN__.error.log
index a4d3c34f260e3cb5905830c40e19388f31561415..854c543aca652d411afabb770a9983701bdd75d9 100644 (file)
@@ -69,6 +69,16 @@ nginx:
   ### SITES
   servers:
     managed:
-      # Remove default webserver
+      # Update default config to redirect to https
       default:
-        enabled: false
+        enabled: true
+        overwrite: true
+        config:
+          - server:
+            - server_name: _
+            - listen:
+              - 80 default_server
+            - location /.well-known:
+              - root: /var/www
+            - location /:
+              - return: '301 https://$host$request_uri'
index 1b21aaaeb6b1744545535ea4eab4ce02ece6ac44..67013f93c2ad96efd88e28b6a3c68c57b0abb754 100644 (file)
@@ -12,14 +12,16 @@ nginx:
       ### STREAMS
       http:
         upstream webshell_upstream:
-          - server: '__HOSTNAME_INT__:4200 fail_timeout=10s'
+          - server: '__IP_INT__:4200 fail_timeout=10s'
 
   ### SITES
   servers:
     managed:
-      arvados_webshell_ssl:
+      arvados_webshell_ssl.conf:
         enabled: true
         overwrite: true
+        requires:
+          __CERT_REQUIRES__
         config:
           - server:
             - server_name: __HOSTNAME_EXT__
@@ -55,7 +57,9 @@ nginx:
                 - add_header: "'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'"
                 - add_header: "'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'"
 
-            - include: 'snippets/arvados-snakeoil.conf'
+            - include: snippets/ssl_hardening_default.conf
+            - ssl_certificate: __CERT_PEM__
+            - ssl_certificate_key: __CERT_KEY__
             - access_log: /var/log/nginx/webshell.__CLUSTER__.__DOMAIN__.access.log combined
             - error_log: /var/log/nginx/webshell.__CLUSTER__.__DOMAIN__.error.log
 
index 7c4ff7835c580a78d6ffd91b391f194518f75f66..e77207217463072bc60baab280a19eb7d9090529 100644 (file)
@@ -11,13 +11,15 @@ nginx:
       ### STREAMS
       http:
         upstream websocket_upstream:
-          - server: '__HOSTNAME_INT__:8005 fail_timeout=10s'
+          - server: '__IP_INT__:8005 fail_timeout=10s'
 
   servers:
     managed:
-      arvados_websocket_ssl:
+      arvados_websocket_ssl.conf:
         enabled: true
         overwrite: true
+        requires:
+          __CERT_REQUIRES__
         config:
           - server:
             - server_name: __HOSTNAME_EXT__
@@ -39,6 +41,8 @@ nginx:
             - client_max_body_size: 64M
             - proxy_http_version: '1.1'
             - proxy_request_buffering: 'off'
-            - include: 'snippets/arvados-snakeoil.conf'
+            - include: snippets/ssl_hardening_default.conf
+            - ssl_certificate: __CERT_PEM__
+            - ssl_certificate_key: __CERT_KEY__
             - access_log: /var/log/nginx/ws.__CLUSTER__.__DOMAIN__.access.log combined
             - error_log: /var/log/nginx/ws.__CLUSTER__.__DOMAIN__.error.log
index 462443c1fa04fb90c5a894dcb70286a71eb1343b..8e4b9b4aa1d99b4e53dac95580e85f1dc79f2f2a 100644 (file)
@@ -13,9 +13,11 @@ nginx:
   ### SITES
   servers:
     managed:
-      arvados_workbench2_ssl:
+      arvados_workbench2_ssl.conf:
         enabled: true
         overwrite: true
+        requires:
+          __CERT_REQUIRES__
         config:
           - server:
             - server_name: __HOSTNAME_EXT__
@@ -29,6 +31,8 @@ nginx:
                 - return: 503
             - location /config.json:
               - return: {{ "200 '" ~ '{"API_HOST":"__HOSTNAME_EXT__:__CONTROLLER_EXT_SSL_PORT__"}' ~ "'" }}
-            - include: 'snippets/arvados-snakeoil.conf'
+            - include: snippets/ssl_hardening_default.conf
+            - ssl_certificate: __CERT_PEM__
+            - ssl_certificate_key: __CERT_KEY__
             - access_log: /var/log/nginx/workbench2.__CLUSTER__.__DOMAIN__.access.log combined
             - error_log: /var/log/nginx/workbench2.__CLUSTER__.__DOMAIN__.error.log
index 9ed6e3b87aa61abcb1be03efb1a2a8e9ad2c2870..3477c02750c98860ab555bb619b96cf1d9a450ff 100644 (file)
@@ -17,14 +17,16 @@ nginx:
       ### STREAMS
       http:
         upstream workbench_upstream:
-          - server: '__HOSTNAME_INT__:9000 fail_timeout=10s'
+          - server: '__IP_INT__:9000 fail_timeout=10s'
 
   ### SITES
   servers:
     managed:
-      arvados_workbench_ssl:
+      arvados_workbench_ssl.conf:
         enabled: true
         overwrite: true
+        requires:
+          __CERT_REQUIRES__
         config:
           - server:
             - server_name: __HOSTNAME_EXT__
@@ -40,7 +42,9 @@ nginx:
               - proxy_set_header: 'Host $http_host'
               - proxy_set_header: 'X-Real-IP $remote_addr'
               - proxy_set_header: 'X-Forwarded-For $proxy_add_x_forwarded_for'
-            - include: 'snippets/arvados-snakeoil.conf'
+            - include: snippets/ssl_hardening_default.conf
+            - ssl_certificate: __CERT_PEM__
+            - ssl_certificate_key: __CERT_KEY__
             - access_log: /var/log/nginx/workbench.__CLUSTER__.__DOMAIN__.access.log combined
             - error_log: /var/log/nginx/workbench.__CLUSTER__.__DOMAIN__.error.log
 
@@ -49,7 +53,7 @@ nginx:
         overwrite: true
         config:
           - server:
-            - listen: '__HOSTNAME_INT__:9000'
+            - listen: '__IP_INT__:9000'
             - server_name: workbench
             - root: /var/www/arvados-workbench/current/public
             - index:  index.html index.htm
diff --git a/tools/salt-install/config_examples/single_host/single_hostname/states/custom_certs.sls b/tools/salt-install/config_examples/single_host/single_hostname/states/custom_certs.sls
new file mode 100644 (file)
index 0000000..3b2be59
--- /dev/null
@@ -0,0 +1,33 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
+{%- set orig_cert_dir = salt['pillar.get']('extra_custom_certs_dir', '/srv/salt/certs')  %}
+{%- set dest_cert_dir = '/etc/nginx/ssl' %}
+{%- set certs = salt['pillar.get']('extra_custom_certs', [])  %}
+
+{% if certs %}
+extra_custom_certs_file_directory_certs_dir:
+  file.directory:
+    - name: /etc/nginx/ssl
+    - require:
+      - pkg: nginx_install
+
+  {%- for cert in certs %}
+    {%- set cert_file = 'arvados-' ~ cert ~ '.pem' %}
+    {#- set csr_file = 'arvados-' ~ cert ~ '.csr' #}
+    {%- set key_file = 'arvados-' ~ cert ~ '.key' %}
+    {% for c in [cert_file, key_file] %}
+extra_custom_certs_file_copy_{{ c }}:
+  file.copy:
+    - name: {{ dest_cert_dir }}/{{ c }}
+    - source: {{ orig_cert_dir }}/{{ c }}
+    - force: true
+    - user: root
+    - group: root
+    - unless: cmp {{ dest_cert_dir }}/{{ c }} {{ orig_cert_dir }}/{{ c }}
+    - require:
+      - file: extra_custom_certs_file_directory_certs_dir
+    {%- endfor %}
+  {%- endfor %}
+{%- endif %}
diff --git a/tools/salt-install/config_examples/single_host/single_hostname/states/dns.sls b/tools/salt-install/config_examples/single_host/single_hostname/states/dns.sls
new file mode 100644 (file)
index 0000000..f298e8f
--- /dev/null
@@ -0,0 +1,8 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
+dns:
+  pkg.installed:
+    - pkgs:
+      - dnsmasq
index 53a9148cc0a7a832cd87d618d7576270554e30d4..a688f4f8c11535fdcaaac7b33eaaccf5cddd16c9 100644 (file)
@@ -7,12 +7,21 @@
 {%- from "arvados/map.jinja" import arvados with context %}
 {%- set tpldir = curr_tpldir %}
 
+# We need the external hostname to resolve to the internal IP for docker. We
+# tell docker to resolve via the local dnsmasq, which reads from /etc/hosts by
+# default.
+arvados_local_access_to_hostname_ext:
+  host.present:
+    - ip: __IP_INT__
+    - names:
+      - __HOSTNAME_EXT__
+
 arvados_test_salt_states_examples_single_host_etc_hosts_host_present:
   host.present:
     - ip: 127.0.1.1
     - names:
       - {{ arvados.cluster.name }}.{{ arvados.cluster.domain }}
-      # FIXME! This just works for our testings.
+      # FIXME! This just works for our testing.
       # Won't work if the cluster name != host name
       {%- for entry in [
           'api',
index b6929fb887ba6827a0979872ccee415a01d22c94..4cbdee32fc0527b3a03c0229d86f86d2d755cb90 100644 (file)
@@ -7,6 +7,8 @@
 {%- from "arvados/map.jinja" import arvados with context %}
 {%- set tpldir = curr_tpldir %}
 
+{%- set orig_cert_dir = salt['pillar.get']('extra_custom_certs_dir', '/srv/salt/certs')  %}
+
 include:
   - nginx.passenger
   - nginx.config
@@ -16,31 +18,49 @@ include:
 # we'll keep it simple here.
 {%- set arvados_ca_cert_file = '/etc/ssl/private/arvados-snakeoil-ca.pem' %}
 {%- set arvados_ca_key_file = '/etc/ssl/private/arvados-snakeoil-ca.key' %}
-{%- set arvados_cert_file = '/etc/ssl/private/arvados-snakeoil-cert.pem' %}
-{%- set arvados_csr_file = '/etc/ssl/private/arvados-snakeoil-cert.csr' %}
-{%- set arvados_key_file = '/etc/ssl/private/arvados-snakeoil-cert.key' %}
 
 {%- if grains.get('os_family') == 'Debian' %}
   {%- set arvados_ca_cert_dest = '/usr/local/share/ca-certificates/arvados-snakeoil-ca.crt' %}
   {%- set update_ca_cert = '/usr/sbin/update-ca-certificates' %}
   {%- set openssl_conf = '/etc/ssl/openssl.cnf' %}
+
+extra_snakeoil_certs_ssl_cert_pkg_installed:
+  pkg.installed:
+    - name: ssl-cert
+    - require_in:
+      - sls: postgres
+
 {%- else %}
   {%- set arvados_ca_cert_dest = '/etc/pki/ca-trust/source/anchors/arvados-snakeoil-ca.pem' %}
   {%- set update_ca_cert = '/usr/bin/update-ca-trust' %}
   {%- set openssl_conf = '/etc/pki/tls/openssl.cnf' %}
+
 {%- endif %}
 
-arvados_test_salt_states_examples_single_host_snakeoil_certs_dependencies_pkg_installed:
+extra_snakeoil_certs_dependencies_pkg_installed:
   pkg.installed:
     - pkgs:
       - openssl
       - ca-certificates
 
-arvados_test_salt_states_examples_single_host_snakeoil_certs_arvados_snake_oil_ca_cmd_run:
+# Remove the RANDFILE parameter in openssl.cnf as it makes openssl fail in Ubuntu 18.04
+# Saving and restoring the rng state is not necessary anymore in the openssl 1.1.1
+# random generator, cf
+#   https://github.com/openssl/openssl/issues/7754
+#
+extra_snakeoil_certs_file_comment_etc_openssl_conf:
+  file.comment:
+    - name: /etc/ssl/openssl.cnf
+    - regex: ^RANDFILE.*
+    - onlyif: grep -q ^RANDFILE /etc/ssl/openssl.cnf
+    - require_in:
+      - cmd: extra_snakeoil_certs_arvados_snakeoil_ca_cmd_run
+
+extra_snakeoil_certs_arvados_snakeoil_ca_cmd_run:
   # Taken from https://github.com/arvados/arvados/blob/master/tools/arvbox/lib/arvbox/docker/service/certificate/run
   cmd.run:
     - name: |
-        # These dirs are not to CentOS-ish, but this is a helper script
+        # These dirs are not too CentOS-ish, but this is a helper script
         # and they should be enough
         mkdir -p /etc/ssl/certs/ /etc/ssl/private/ && \
         openssl req \
@@ -61,64 +81,55 @@ arvados_test_salt_states_examples_single_host_snakeoil_certs_arvados_snake_oil_c
       - test -f {{ arvados_ca_cert_file }}
       - openssl verify -CAfile {{ arvados_ca_cert_file }} {{ arvados_ca_cert_file }}
     - require:
-      - pkg: arvados_test_salt_states_examples_single_host_snakeoil_certs_dependencies_pkg_installed
+      - pkg: extra_snakeoil_certs_dependencies_pkg_installed
 
-arvados_test_salt_states_examples_single_host_snakeoil_certs_arvados_snake_oil_cert_cmd_run:
+{%- set arvados_cert_file = orig_cert_dir ~ '/arvados-__HOSTNAME_EXT__.pem' %}
+{%- set arvados_csr_file = orig_cert_dir ~ '/arvadoos-__HOSTNAME_EXT__.csr' %}
+{%- set arvados_key_file = orig_cert_dir ~ '/arvados-__HOSTNAME_EXT__.key' %}
+
+extra_snakeoil_certs_arvados_snakeoil_cert___HOSTNAME_EXT___cmd_run:
   cmd.run:
     - name: |
-        cat > /tmp/openssl.cnf <<-CNF
+        cat > /tmp/__HOSTNAME_EXT__.openssl.cnf <<-CNF
         [req]
         default_bits = 2048
         prompt = no
         default_md = sha256
-        req_extensions = rext
         distinguished_name = dn
+        req_extensions = rext
+        [rext]
+        subjectAltName = @alt_names
         [dn]
         C   = CC
         ST  = Some State
         L   = Some Location
-        O   = Arvados Formula
-        OU  = arvados-formula
+        O   = Arvados Provision Example Single Host / Single Hostname
+        OU  = arvados-provision-example-single_host_single_hostname
         CN  = {{ arvados.cluster.name }}.{{ arvados.cluster.domain }}
         emailAddress = admin@{{ arvados.cluster.name }}.{{ arvados.cluster.domain }}
-        [rext]
-        subjectAltName = @alt_names
         [alt_names]
         {%- for entry in grains.get('ipv4') %}
         IP.{{ loop.index }} = {{ entry }}
         {%- endfor %}
-        {%- for entry in [
-            'keep',
-            'collections',
-            'download',
-            'keepweb',
-            'ws',
-            'workbench',
-            'workbench2',
-          ]
-        %}
-        DNS.{{ loop.index }} = {{ entry }}
-        {%- endfor %}
-        DNS.8 = {{ arvados.cluster.name }}.{{ arvados.cluster.domain }}
-        DNS.9 = '__HOSTNAME_EXT__'
-        DNS.10 = '__HOSTNAME_INT__'
+        DNS.1 = {{ arvados.cluster.name }}.{{ arvados.cluster.domain }}
+        DNS.2 = '__HOSTNAME_EXT__'
         CNF
 
         # The req
         openssl req \
-          -config /tmp/openssl.cnf \
+          -config /tmp/__HOSTNAME_EXT__.openssl.cnf \
           -new \
           -nodes \
           -sha256 \
           -out {{ arvados_csr_file }} \
-          -keyout {{ arvados_key_file }} > /tmp/snake_oil_certs.output 2>&1 && \
+          -keyout {{ arvados_key_file }} > /tmp/snake_oil_certs.__HOSTNAME_EXT__.output 2>&1 && \
         # The cert
         openssl x509 \
           -req \
           -days 365 \
           -in {{ arvados_csr_file }} \
           -out {{ arvados_cert_file }} \
-          -extfile /tmp/openssl.cnf \
+          -extfile /tmp/__HOSTNAME_EXT__.openssl.cnf \
           -extensions rext \
           -CA {{ arvados_ca_cert_file }} \
           -CAkey {{ arvados_ca_key_file }} \
@@ -129,27 +140,19 @@ arvados_test_salt_states_examples_single_host_snakeoil_certs_arvados_snake_oil_c
       - test -f {{ arvados_key_file }}
       - openssl verify -CAfile {{ arvados_ca_cert_file }} {{ arvados_cert_file }}
     - require:
-      - pkg: arvados_test_salt_states_examples_single_host_snakeoil_certs_dependencies_pkg_installed
-      - cmd: arvados_test_salt_states_examples_single_host_snakeoil_certs_arvados_snake_oil_ca_cmd_run
-    # We need this before we can add the nginx's snippet
+      - pkg: extra_snakeoil_certs_dependencies_pkg_installed
+      - cmd: extra_snakeoil_certs_arvados_snakeoil_ca_cmd_run
     - require_in:
-      - file: nginx_snippet_arvados-snakeoil.conf
+      - file: extra_custom_certs_file_copy_arvados-__HOSTNAME_EXT__.pem
+      - file: extra_custom_certs_file_copy_arvados-__HOSTNAME_EXT__.key
 
-{%- if grains.get('os_family') == 'Debian' %}
-arvados_test_salt_states_examples_single_host_snakeoil_certs_ssl_cert_pkg_installed:
-  pkg.installed:
-    - name: ssl-cert
-    - require_in:
-      - sls: postgres
-
-arvados_test_salt_states_examples_single_host_snakeoil_certs_certs_permissions_cmd_run:
+  {%- if grains.get('os_family') == 'Debian' %}
+extra_snakeoil_certs_certs_permissions___HOSTNAME_EXT___cmd_run:
   file.managed:
     - name: {{ arvados_key_file }}
     - owner: root
     - group: ssl-cert
     - require:
-      - cmd: arvados_test_salt_states_examples_single_host_snakeoil_certs_arvados_snake_oil_cert_cmd_run
-      - pkg: arvados_test_salt_states_examples_single_host_snakeoil_certs_ssl_cert_pkg_installed
-    - require_in:
-      - file: nginx_snippet_arvados-snakeoil.conf
-{%- endif %}
+      - cmd: extra_snakeoil_certs_arvados_snakeoil_cert___HOSTNAME_EXT___cmd_run
+      - pkg: extra_snakeoil_certs_ssl_cert_pkg_installed
+  {%- endif %}
index cb0afecc468c17d78ba27b1ef0af60b533414b9d..221e7b35ebc7952b4b680e7db2691ddabf7843dc 100644 (file)
@@ -58,14 +58,14 @@ WORKBENCH_SECRET_KEY=workbenchsecretkeymushaveatleast32characters
 DATABASE_PASSWORD=please_set_this_to_some_secure_value
 
 # SSL CERTIFICATES
-# Arvados REQUIRES valid SSL to work correctly. Otherwise, some components will fail
-# to communicate and can silently drop traffic. You can try to use the Letsencrypt
-# salt formula (https://github.com/saltstack-formulas/letsencrypt-formula) to try to
-# automatically obtain and install SSL certificates for your instances or set this
-# variable to "no", provide and upload your own certificates to the instances and
-# modify the 'nginx_*' salt pillars accordingly (see CUSTOM_CERTS_DIR below)
-USE_LETSENCRYPT="yes"
-USE_LETSENCRYPT_IAM_USER="yes"
+# Arvados requires SSL certificates to work correctly. This installer supports these options:
+# * self-signed: let the installer create self-signed certificate(s)
+# * bring-your-own: supply your own certificate(s) in the `certs` directory
+# * lets-encrypt: automatically obtain and install SSL certificates for your hostname(s)
+#
+# See https://doc.arvados.org/intall/salt-multi-host.html for more information.
+SSL_MODE="lets-encrypt"
+USE_LETSENCRYPT_ROUTE53="yes"
 # For collections, we need to obtain a wildcard certificate for
 # '*.collections.<cluster>.<domain>'. This is only possible through a DNS-01 challenge.
 # For that reason, you'll need to provide AWS credentials with permissions to manage
@@ -76,7 +76,7 @@ LE_AWS_ACCESS_KEY_ID="AKIABCDEFGHIJKLMNOPQ"
 LE_AWS_SECRET_ACCESS_KEY="thisistherandomstringthatisyoursecretkey"
 
 # If you going to provide your own certificates for Arvados, the provision script can
-# help you deploy them. In order to do that, you need to set `USE_LETSENCRYPT=no` above,
+# help you deploy them. In order to do that, you need to set `SSL_MODE=bring-your-own` above,
 # and copy the required certificates under the directory specified in the next line.
 # The certs will be copied from this directory by the provision script.
 # Please set it to the FULL PATH to the certs dir if you're going to use a different dir
@@ -120,7 +120,7 @@ RELEASE="production"
 # Formulas versions
 # ARVADOS_TAG="2.2.0"
 # POSTGRES_TAG="v0.43.0"
-# NGINX_TAG="temp-fix-missing-statements-in-pillar"
+# NGINX_TAG="v2.8.0"
 # DOCKER_TAG="v2.0.7"
 # LOCALE_TAG="v0.3.4"
 # LETSENCRYPT_TAG="v2.1.0"
index ef47467e5769458ede52888446a24b34219b4780..77015248c1e7cffae398816de43724385bcde242 100644 (file)
@@ -11,13 +11,9 @@ CLUSTER="cluster_fixme_or_this_wont_work"
 # The domainname you want tou give to your cluster's hosts
 DOMAIN="domain_fixme_or_this_wont_work"
 
-# Host SSL port where you want to point your browser to access Arvados
-# Defaults to 443 for regular runs, and to 8443 when called in Vagrant.
-# You can point it to another port if desired
-# In Vagrant, make sure it matches what you set in the Vagrantfile (8443)
+# External ports used by the Arvados services
 CONTROLLER_EXT_SSL_PORT=443
 KEEP_EXT_SSL_PORT=25101
-# Both for collections and downloads
 KEEPWEB_EXT_SSL_PORT=9002
 WEBSHELL_EXT_SSL_PORT=4202
 WEBSOCKET_EXT_SSL_PORT=8002
@@ -25,7 +21,6 @@ WORKBENCH1_EXT_SSL_PORT=443
 WORKBENCH2_EXT_SSL_PORT=3001
 
 INITIAL_USER="admin"
-
 # If not specified, the initial user email will be composed as
 # INITIAL_USER@CLUSTER.DOMAIN
 INITIAL_USER_EMAIL="admin@cluster_fixme_or_this_wont_work.domain_fixme_or_this_wont_work"
@@ -40,42 +35,22 @@ WORKBENCH_SECRET_KEY=workbenchsecretkeymushaveatleast32characters
 DATABASE_PASSWORD=please_set_this_to_some_secure_value
 
 # SSL CERTIFICATES
-# Arvados REQUIRES valid SSL to work correctly. Otherwise, some components will
-# fail to communicate and can silently drop traffic. Set USE_LETSENCRYPT="yes"
-# to use the Let's Encrypt salt formula
-# (https://github.com/saltstack-formulas/letsencrypt-formula) to automatically
-# obtain and install SSL certificates for your hostname(s).
+# Arvados requires SSL certificates to work correctly. This installer supports these options:
+# * self-signed: let the installer create self-signed certificate(s)
+# * bring-your-own: supply your own certificate(s) in the `certs` directory
+# * lets-encrypt: automatically obtain and install SSL certificates for your hostname(s)
 #
-# Alternatively, set this variable to "no" and provide and upload your own
-# certificates to the instances and modify the 'nginx_*' salt pillars
-# accordingly
-USE_LETSENCRYPT="no"
+# See https://doc.arvados.org/intall/salt-single-host.html#certificates for more information.
+SSL_MODE="self-signed"
 
-# If you going to provide your own certificates for Arvados, the provision script can
-# help you deploy them. In order to do that, you need to set `USE_LETSENCRYPT=no` above,
-# and copy the required certificates under the directory specified in the next line.
-# The certs will be copied from this directory by the provision script.
-# Please set it to the FULL PATH to the certs dir if you're going to use a different dir
-# Default is "${SCRIPT_DIR}/certs", where the variable "SCRIPT_DIR" has the path to the
-# directory where the  "provision.sh" script was copied in the destination host.
+# CUSTOM_CERTS_DIR is only used when SSL_MODE is set to "bring-your-own".
+# See https://doc.arvados.org/intall/salt-single-host.html#bring-your-own for more information.
 # CUSTOM_CERTS_DIR="${SCRIPT_DIR}/certs"
-# The script expects cert/key files with these basenames (matching the role except for
-# keepweb, which is split in both download/collections):
-#  "controller"
-#  "websocket"
-#  "workbench"
-#  "workbench2"
-#  "webshell"
-#  "download"         # Part of keepweb
-#  "collections"      # Part of keepweb
-#  "keepproxy"
-# Ie., 'keepproxy', the script will lookup for
-# ${CUSTOM_CERTS_DIR}/keepproxy.crt
-# ${CUSTOM_CERTS_DIR}/keepproxy.key
 
 # The directory to check for the config files (pillars, states) you want to use.
 # There are a few examples under 'config_examples'.
 # CONFIG_DIR="local_config_dir"
+
 # Extra states to apply. If you use your own subdir, change this value accordingly
 # EXTRA_STATES_DIR="${CONFIG_DIR}/states"
 
@@ -96,7 +71,7 @@ RELEASE="production"
 # Formulas versions
 # ARVADOS_TAG="2.2.0"
 # POSTGRES_TAG="v0.43.0"
-# NGINX_TAG="temp-fix-missing-statements-in-pillar"
+# NGINX_TAG="v2.8.0"
 # DOCKER_TAG="v2.0.7"
 # LOCALE_TAG="v0.3.4"
 # LETSENCRYPT_TAG="v2.1.0"
index d09cdb2ef0c48215e751c9a7cbc0effab674a61f..4c129c9c85a1a09ae444c8b89b7a6e186fb3052b 100644 (file)
@@ -5,42 +5,36 @@
 
 # These are the basic parameters to configure the installation
 
-# The FIVE ALPHANUMERIC CHARACTERS name you want to give your cluster
+# The Arvados cluster ID, needs to be five alphanumeric characters.
 CLUSTER="cluster_fixme_or_this_wont_work"
 
-# The domainname you want tou give to your cluster's hosts
+# The domainname for your cluster's hosts
 DOMAIN="domain_fixme_or_this_wont_work"
 
-# Set this value when installing a cluster in a single host with a single hostname
-# to access all the instances. Not used in the other examples.
-# When using virtualization (ie AWS), this should be
-# the EXTERNAL/PUBLIC hostname for the instance.
-# If empty, ${CLUSTER}.${DOMAIN} will be used
-HOSTNAME_EXT=""
-# The internal hostname for the host. In the example files, only used in the
-# single_host/single_hostname example
-HOSTNAME_INT="127.0.1.1"
-# Host SSL port where you want to point your browser to access Arvados
-# Defaults to 443 for regular runs, and to 8443 when called in Vagrant.
-# You can point it to another port if desired
-# In Vagrant, make sure it matches what you set in the Vagrantfile (8443)
-CONTROLLER_EXT_SSL_PORT=9443
-KEEP_EXT_SSL_PORT=35101
-# Both for collections and downloads
-KEEPWEB_EXT_SSL_PORT=11002
-WEBSHELL_EXT_SSL_PORT=14202
-WEBSOCKET_EXT_SSL_PORT=18002
-WORKBENCH1_EXT_SSL_PORT=9444
-WORKBENCH2_EXT_SSL_PORT=9445
+# Set this value when installing a cluster in a single host with a single
+# hostname to access all the instances. HOSTNAME_EXT should be set to the
+# external hostname for the instance.
+HOSTNAME_EXT="hostname_ext_fixme_or_this_wont_work"
 
-INITIAL_USER="admin"
+# The internal IP address for the host.
+IP_INT="ip_int_fixme_or_this_wont_work"
+
+# External ports used by the Arvados services
+CONTROLLER_EXT_SSL_PORT=8800
+KEEP_EXT_SSL_PORT=8801
+KEEPWEB_EXT_SSL_PORT=8802
+WEBSHELL_EXT_SSL_PORT=8803
+WEBSOCKET_EXT_SSL_PORT=8804
+WORKBENCH1_EXT_SSL_PORT=8805
+WORKBENCH2_EXT_SSL_PORT=443
 
+INITIAL_USER="admin"
 # If not specified, the initial user email will be composed as
 # INITIAL_USER@CLUSTER.DOMAIN
 INITIAL_USER_EMAIL="admin@cluster_fixme_or_this_wont_work.domain_fixme_or_this_wont_work"
 INITIAL_USER_PASSWORD="password"
 
-# YOU SHOULD CHANGE THESE TO SOME RANDOM STRINGS
+# Populate these values with random strings
 BLOB_SIGNING_KEY=blobsigningkeymushaveatleast32characters
 MANAGEMENT_TOKEN=managementtokenmushaveatleast32characters
 SYSTEM_ROOT_TOKEN=systemroottokenmushaveatleast32characters
@@ -49,20 +43,22 @@ WORKBENCH_SECRET_KEY=workbenchsecretkeymushaveatleast32characters
 DATABASE_PASSWORD=please_set_this_to_some_secure_value
 
 # SSL CERTIFICATES
-# Arvados REQUIRES valid SSL to work correctly. Otherwise, some components will
-# fail to communicate and can silently drop traffic. Set USE_LETSENCRYPT="yes"
-# to use the Let's Encrypt salt formula
-# (https://github.com/saltstack-formulas/letsencrypt-formula) to automatically
-# obtain and install SSL certificates for your hostname(s).
+# Arvados requires SSL certificates to work correctly. This installer supports these options:
+# * self-signed: let the installer create self-signed certificate(s)
+# * bring-your-own: supply your own certificate(s) in the `certs` directory
+# * lets-encrypt: automatically obtain and install SSL certificates for your hostname(s)
 #
-# Alternatively, set this variable to "no" and provide and upload your own
-# certificates to the instances and modify the 'nginx_*' salt pillars
-# accordingly
-USE_LETSENCRYPT="no"
+# See https://doc.arvados.org/intall/salt-single-host.html#certificates for more information.
+SSL_MODE="self-signed"
+
+# CUSTOM_CERTS_DIR is only used when SSL_MODE is set to "bring-your-own".
+# See https://doc.arvados.org/intall/salt-single-host.html#bring-your-own for more information.
+# CUSTOM_CERTS_DIR="${SCRIPT_DIR}/certs"
 
 # The directory to check for the config files (pillars, states) you want to use.
 # There are a few examples under 'config_examples'.
 # CONFIG_DIR="local_config_dir"
+
 # Extra states to apply. If you use your own subdir, change this value accordingly
 # EXTRA_STATES_DIR="${CONFIG_DIR}/states"
 
@@ -83,7 +79,7 @@ RELEASE="production"
 # Formulas versions
 # ARVADOS_TAG="2.2.0"
 # POSTGRES_TAG="v0.43.0"
-# NGINX_TAG="temp-fix-missing-statements-in-pillar"
+# NGINX_TAG="v2.8.0"
 # DOCKER_TAG="v2.0.7"
 # LOCALE_TAG="v0.3.4"
 # LETSENCRYPT_TAG="v2.1.0"
index 594dad2ebb95a3a20ac131f9946bc96eb4d37069..644b87cbbf9c254eaf4c9782b7def1c47a565c4a 100755 (executable)
@@ -168,8 +168,7 @@ CLUSTER=""
 DOMAIN=""
 
 # Hostnames/IPs used for single-host deploys
-HOSTNAME_EXT=""
-HOSTNAME_INT="127.0.1.1"
+IP_INT="127.0.1.1"
 
 # Initial user setup
 INITIAL_USER=""
@@ -185,7 +184,8 @@ WEBSOCKET_EXT_SSL_PORT=8002
 WORKBENCH1_EXT_SSL_PORT=443
 WORKBENCH2_EXT_SSL_PORT=3001
 
-USE_LETSENCRYPT="no"
+SSL_MODE="self-signed"
+USE_LETSENCRYPT_ROUTE53="no"
 CUSTOM_CERTS_DIR="${SCRIPT_DIR}/certs"
 
 ## These are ARVADOS-related parameters
@@ -205,7 +205,7 @@ VERSION="latest"
 
 # Other formula versions we depend on
 POSTGRES_TAG="v0.43.0"
-NGINX_TAG="temp-fix-missing-statements-in-pillar"
+NGINX_TAG="v2.8.0"
 DOCKER_TAG="v2.0.7"
 LOCALE_TAG="v0.3.4"
 LETSENCRYPT_TAG="v2.1.0"
@@ -254,7 +254,21 @@ if ! grep -qE '^[[:alnum:]]{5}$' <<<${CLUSTER} ; then
 fi
 
 # Only used in single_host/single_name deploys
-if [ "x${HOSTNAME_EXT}" = "x" ] ; then
+if [ ! -z "${HOSTNAME_EXT}" ] ; then
+  # We need to add some extra control vars to manage a single certificate vs. multiple
+  USE_SINGLE_HOSTNAME="yes"
+  # Make sure that the value configured as IP_INT is a real IP on the system.
+  # If we don't error out early here when there is a mismatch, the formula will
+  # fail with hard to interpret nginx errors later on.
+  ip addr list |grep -q " ${IP_INT}/"
+  if [[ $? -ne 0 ]]; then
+    echo "Unable to find the IP_INT address '${IP_INT}' on the system, please correct the value in local.params. Exiting..."
+    exit 1
+  fi
+else
+  USE_SINGLE_HOSTNAME="no"
+  # We set this variable, anyway, so sed lines do not fail and we don't need to add more
+  # conditionals
   HOSTNAME_EXT="${CLUSTER}.${DOMAIN}"
 fi
 
@@ -313,18 +327,23 @@ rm -rf ${F_DIR}/* || exit 1
 git clone --quiet https://github.com/saltstack-formulas/docker-formula.git ${F_DIR}/docker
 ( cd docker && git checkout --quiet tags/"${DOCKER_TAG}" -b "${DOCKER_TAG}" )
 
+echo "...locale"
 git clone --quiet https://github.com/saltstack-formulas/locale-formula.git ${F_DIR}/locale
 ( cd locale && git checkout --quiet tags/"${LOCALE_TAG}" -b "${LOCALE_TAG}" )
 
-git clone --quiet https://github.com/netmanagers/nginx-formula.git ${F_DIR}/nginx
+echo "...nginx"
+git clone --quiet https://github.com/saltstack-formulas/nginx-formula.git ${F_DIR}/nginx
 ( cd nginx && git checkout --quiet tags/"${NGINX_TAG}" -b "${NGINX_TAG}" )
 
+echo "...postgres"
 git clone --quiet https://github.com/saltstack-formulas/postgres-formula.git ${F_DIR}/postgres
 ( cd postgres && git checkout --quiet tags/"${POSTGRES_TAG}" -b "${POSTGRES_TAG}" )
 
+echo "...letsencrypt"
 git clone --quiet https://github.com/saltstack-formulas/letsencrypt-formula.git ${F_DIR}/letsencrypt
 ( cd letsencrypt && git checkout --quiet tags/"${LETSENCRYPT_TAG}" -b "${LETSENCRYPT_TAG}" )
 
+echo "...arvados"
 git clone --quiet https://git.arvados.org/arvados-formula.git ${F_DIR}/arvados
 
 # If we want to try a specific branch of the formula
@@ -361,7 +380,7 @@ for f in $(ls "${SOURCE_PILLARS_DIR}"/*); do
        s#__CLUSTER__#${CLUSTER}#g;
        s#__DOMAIN__#${DOMAIN}#g;
        s#__HOSTNAME_EXT__#${HOSTNAME_EXT}#g;
-       s#__HOSTNAME_INT__#${HOSTNAME_INT}#g;
+       s#__IP_INT__#${IP_INT}#g;
        s#__INITIAL_USER_EMAIL__#${INITIAL_USER_EMAIL}#g;
        s#__INITIAL_USER_PASSWORD__#${INITIAL_USER_PASSWORD}#g;
        s#__INITIAL_USER__#${INITIAL_USER}#g;
@@ -402,16 +421,21 @@ fi
 mkdir -p ${T_DIR}
 # Replace cluster and domain name in the test files
 for f in $(ls "${SOURCE_TESTS_DIR}"/*); do
-  sed "s#__CLUSTER__#${CLUSTER}#g;
+  FILTERS="s#__CLUSTER__#${CLUSTER}#g;
        s#__CONTROLLER_EXT_SSL_PORT__#${CONTROLLER_EXT_SSL_PORT}#g;
        s#__DOMAIN__#${DOMAIN}#g;
-       s#__HOSTNAME_INT__#${HOSTNAME_INT}#g;
+       s#__IP_INT__#${IP_INT}#g;
        s#__INITIAL_USER_EMAIL__#${INITIAL_USER_EMAIL}#g;
        s#__INITIAL_USER_PASSWORD__#${INITIAL_USER_PASSWORD}#g
        s#__INITIAL_USER__#${INITIAL_USER}#g;
        s#__DATABASE_PASSWORD__#${DATABASE_PASSWORD}#g;
-       s#__SYSTEM_ROOT_TOKEN__#${SYSTEM_ROOT_TOKEN}#g" \
-  "${f}" > ${T_DIR}/$(basename "${f}")
+       s#__SYSTEM_ROOT_TOKEN__#${SYSTEM_ROOT_TOKEN}#g"
+  if [ "$USE_SINGLE_HOSTNAME" = "yes" ]; then
+    FILTERS="s#__CLUSTER__.__DOMAIN__#${HOSTNAME_EXT}#g;
+       $FILTERS"
+  fi
+  sed "$FILTERS" \
+    "${f}" > ${T_DIR}/$(basename "${f}")
 done
 chmod 755 ${T_DIR}/run-test.sh
 
@@ -426,7 +450,7 @@ if [ -d "${SOURCE_STATES_DIR}" ]; then
          s#__CONTROLLER_EXT_SSL_PORT__#${CONTROLLER_EXT_SSL_PORT}#g;
          s#__DOMAIN__#${DOMAIN}#g;
          s#__HOSTNAME_EXT__#${HOSTNAME_EXT}#g;
-         s#__HOSTNAME_INT__#${HOSTNAME_INT}#g;
+         s#__IP_INT__#${IP_INT}#g;
          s#__INITIAL_USER_EMAIL__#${INITIAL_USER_EMAIL}#g;
          s#__INITIAL_USER_PASSWORD__#${INITIAL_USER_PASSWORD}#g;
          s#__INITIAL_USER__#${INITIAL_USER}#g;
@@ -478,18 +502,19 @@ EOFPSLS
 
 # States, extra states
 if [ -d "${F_DIR}"/extra/extra ]; then
-  if [ "$DEV_MODE" = "yes" ]; then
+  SKIP_SNAKE_OIL="snakeoil_certs"
+
+  if [[ "$DEV_MODE" = "yes" || "${SSL_MODE}" == "self-signed" ]] ; then
     # In dev mode, we create some snake oil certs that we'll
-    # use as CUSTOM_CERTS, so we don't skip the states file
-    SKIP_SNAKE_OIL="dont_snakeoil_certs"
-  else
-    SKIP_SNAKE_OIL="snakeoil_certs"
+    # use as CUSTOM_CERTS, so we don't skip the states file.
+    # Same when using self-signed certificates.
+    SKIP_SNAKE_OIL="dont_add_snakeoil_certs"
   fi
   for f in $(ls "${F_DIR}"/extra/extra/*.sls | grep -v ${SKIP_SNAKE_OIL}); do
   echo "    - extra.$(basename ${f} | sed 's/.sls$//g')" >> ${S_DIR}/top.sls
   done
-  # Use custom certs
-  if [ "x${USE_LETSENCRYPT}" != "xyes" ]; then
+  # Use byo or self-signed certificates
+  if [ "${SSL_MODE}" != "lets-encrypt" ]; then
     mkdir -p "${F_DIR}"/extra/extra/files
   fi
 fi
@@ -499,14 +524,13 @@ fi
 if [ -z "${ROLES}" ]; then
   # States
   echo "    - nginx.passenger" >> ${S_DIR}/top.sls
-  # Currently, only available on config_examples/multi_host/aws
-  if [ "x${USE_LETSENCRYPT}" = "xyes" ]; then
-    if [ "x${USE_LETSENCRYPT_IAM_USER}" != "xyes" ]; then
+  if [ "${SSL_MODE}" = "lets-encrypt" ]; then
+    if [ "${USE_LETSENCRYPT_ROUTE53}" = "yes" ]; then
       grep -q "aws_credentials" ${S_DIR}/top.sls || echo "    - extra.aws_credentials" >> ${S_DIR}/top.sls
     fi
     grep -q "letsencrypt"     ${S_DIR}/top.sls || echo "    - letsencrypt" >> ${S_DIR}/top.sls
   else
-    # Use custom certs
+    # Use custom certs, as both bring-your-own and self-signed are copied using this state
     # Copy certs to formula extra/files
     # In dev mode, the files will be created and put in the destination directory by the
     # snakeoil_certs.sls state file
@@ -533,18 +557,25 @@ if [ -z "${ROLES}" ]; then
   echo "    - nginx_workbench_configuration" >> ${P_DIR}/top.sls
   echo "    - postgresql" >> ${P_DIR}/top.sls
 
-  # Currently, only available on config_examples/multi_host/aws
-  if [ "x${USE_LETSENCRYPT}" = "xyes" ]; then
-    if [ "x${USE_LETSENCRYPT_IAM_USER}" != "xyes" ]; then
+  if [ "${SSL_MODE}" = "lets-encrypt" ]; then
+    if [ "${USE_LETSENCRYPT_ROUTE53}" = "yes" ]; then
       grep -q "aws_credentials" ${P_DIR}/top.sls || echo "    - aws_credentials" >> ${P_DIR}/top.sls
     fi
-    grep -q "letsencrypt"     ${P_DIR}/top.sls || echo "    - letsencrypt" >> ${P_DIR}/top.sls
+    grep -q "letsencrypt" ${P_DIR}/top.sls || echo "    - letsencrypt" >> ${P_DIR}/top.sls
 
     # As the pillar differ whether we use LE or custom certs, we need to do a final edition on them
-    for c in controller websocket workbench workbench2 webshell download collections keepproxy; do
-      sed -i "s/__CERT_REQUIRES__/cmd: create-initial-cert-${c}.${CLUSTER}.${DOMAIN}*/g;
-              s#__CERT_PEM__#/etc/letsencrypt/live/${c}.${CLUSTER}.${DOMAIN}/fullchain.pem#g;
-              s#__CERT_KEY__#/etc/letsencrypt/live/${c}.${CLUSTER}.${DOMAIN}/privkey.pem#g" \
+    for c in controller websocket workbench workbench2 webshell keepweb keepproxy; do
+      if [ "${USE_SINGLE_HOSTNAME}" = "yes" ]; then
+        # Are we in a single-host-single-hostname env?
+        CERT_NAME=${HOSTNAME_EXT}
+      else
+        # We are in a single-host-multiple-hostnames env
+        CERT_NAME=${c}.${CLUSTER}.${DOMAIN}
+      fi
+
+      sed -i "s/__CERT_REQUIRES__/cmd: create-initial-cert-${CERT_NAME}*/g;
+              s#__CERT_PEM__#/etc/letsencrypt/live/${CERT_NAME}/fullchain.pem#g;
+              s#__CERT_KEY__#/etc/letsencrypt/live/${CERT_NAME}/privkey.pem#g" \
       ${P_DIR}/nginx_${c}_configuration.sls
     done
   else
@@ -554,24 +585,37 @@ if [ -z "${ROLES}" ]; then
     echo "extra_custom_certs_dir: /srv/salt/certs" > ${P_DIR}/extra_custom_certs.sls
     echo "extra_custom_certs:" >> ${P_DIR}/extra_custom_certs.sls
 
-    for c in controller websocket workbench workbench2 webshell download collections keepproxy; do
-      grep -q ${c} ${P_DIR}/extra_custom_certs.sls || echo "  - ${c}" >> ${P_DIR}/extra_custom_certs.sls
-
-      # As the pillar differ whether we use LE or custom certs, we need to do a final edition on them
-      sed -i "s/__CERT_REQUIRES__/file: extra_custom_certs_file_copy_arvados-${c}.pem/g;
-              s#__CERT_PEM__#/etc/nginx/ssl/arvados-${c}.pem#g;
-              s#__CERT_KEY__#/etc/nginx/ssl/arvados-${c}.key#g" \
+    for c in controller websocket workbench workbench2 webshell keepweb keepproxy; do
+      # Are we in a single-host-single-hostname env?
+      if [ "${USE_SINGLE_HOSTNAME}" = "yes" ]; then
+        # Are we in a single-host-single-hostname env?
+        CERT_NAME=${HOSTNAME_EXT}
+      else
+        # We are in a multiple-hostnames env
+        CERT_NAME=${c}
+      fi
+
+      if [[ "$SSL_MODE" == "bring-your-own" ]]; then
+        copy_custom_cert ${CUSTOM_CERTS_DIR} ${CERT_NAME}
+      fi
+
+      grep -q ${CERT_NAME} ${P_DIR}/extra_custom_certs.sls || echo "  - ${CERT_NAME}" >> ${P_DIR}/extra_custom_certs.sls
+
+      # As the pillar differs whether we use LE or custom certs, we need to do a final edition on them
+      sed -i "s/__CERT_REQUIRES__/file: extra_custom_certs_file_copy_arvados-${CERT_NAME}.pem/g;
+              s#__CERT_PEM__#/etc/nginx/ssl/arvados-${CERT_NAME}.pem#g;
+              s#__CERT_KEY__#/etc/nginx/ssl/arvados-${CERT_NAME}.key#g" \
       ${P_DIR}/nginx_${c}_configuration.sls
     done
   fi
 else
   # If we add individual roles, make sure we add the repo first
   echo "    - arvados.repo" >> ${S_DIR}/top.sls
-  # We add the custom_certs state
-  grep -q "custom_certs"    ${S_DIR}/top.sls || echo "    - extra.custom_certs" >> ${S_DIR}/top.sls
+  # We add the extra_custom_certs state
+  grep -q "extra.custom_certs"    ${S_DIR}/top.sls || echo "    - extra.custom_certs" >> ${S_DIR}/top.sls
 
   # And we add the basic part for the certs pillar
-  if [ "x${USE_LETSENCRYPT}" != "xyes" ]; then
+  if [ "${SSL_MODE}" != "lets-encrypt" ]; then
     # And add the certs in the custom_certs pillar
     echo "extra_custom_certs_dir: /srv/salt/certs" > ${P_DIR}/extra_custom_certs.sls
     echo "extra_custom_certs:" >> ${P_DIR}/extra_custom_certs.sls
@@ -594,14 +638,16 @@ else
         ### If we don't install and run LE before arvados-api-server, it fails and breaks everything
         ### after it. So we add this here as we are, after all, sharing the host for api and controller
         # Currently, only available on config_examples/multi_host/aws
-        if [ "x${USE_LETSENCRYPT}" = "xyes" ]; then
-          if [ "x${USE_LETSENCRYPT_IAM_USER}" != "xyes" ]; then
+        if [ "${SSL_MODE}" = "lets-encrypt" ]; then
+          if [ "${USE_LETSENCRYPT_ROUTE53}" = "yes" ]; then
             grep -q "aws_credentials" ${S_DIR}/top.sls || echo "    - aws_credentials" >> ${S_DIR}/top.sls
           fi
           grep -q "letsencrypt" ${S_DIR}/top.sls || echo "    - letsencrypt" >> ${S_DIR}/top.sls
         else
           # Use custom certs
-          copy_custom_cert ${CUSTOM_CERTS_DIR} controller
+          if [ "${SSL_MODE}" = "bring-your-own" ]; then
+            copy_custom_cert ${CUSTOM_CERTS_DIR} controller
+          fi
           grep -q controller ${P_DIR}/extra_custom_certs.sls || echo "  - controller" >> ${P_DIR}/extra_custom_certs.sls
         fi
         grep -q "arvados.${R}" ${S_DIR}/top.sls    || echo "    - arvados.${R}" >> ${S_DIR}/top.sls
@@ -615,18 +661,22 @@ else
         # States
         grep -q "nginx.passenger" ${S_DIR}/top.sls || echo "    - nginx.passenger" >> ${S_DIR}/top.sls
         # Currently, only available on config_examples/multi_host/aws
-        if [ "x${USE_LETSENCRYPT}" = "xyes" ]; then
-          if [ "x${USE_LETSENCRYPT_IAM_USER}" != "xyes" ]; then
+        if [ "${SSL_MODE}" = "lets-encrypt" ]; then
+          if [ "x${USE_LETSENCRYPT_ROUTE53}" = "xyes" ]; then
             grep -q "aws_credentials" ${S_DIR}/top.sls || echo "    - aws_credentials" >> ${S_DIR}/top.sls
           fi
           grep -q "letsencrypt"     ${S_DIR}/top.sls || echo "    - letsencrypt" >> ${S_DIR}/top.sls
         else
           # Use custom certs, special case for keepweb
           if [ ${R} = "keepweb" ]; then
-            copy_custom_cert ${CUSTOM_CERTS_DIR} download
-            copy_custom_cert ${CUSTOM_CERTS_DIR} collections
+            if [ "${SSL_MODE}" = "bring-your-own" ]; then
+              copy_custom_cert ${CUSTOM_CERTS_DIR} download
+              copy_custom_cert ${CUSTOM_CERTS_DIR} collections
+            fi
           else
-            copy_custom_cert ${CUSTOM_CERTS_DIR} ${R}
+            if [ "${SSL_MODE}" = "bring-your-own" ]; then
+              copy_custom_cert ${CUSTOM_CERTS_DIR} ${R}
+            fi
           fi
         fi
         # webshell role is just a nginx vhost, so it has no state
@@ -643,8 +693,8 @@ else
         fi
 
         # Currently, only available on config_examples/multi_host/aws
-        if [ "x${USE_LETSENCRYPT}" = "xyes" ]; then
-          if [ "x${USE_LETSENCRYPT_IAM_USER}" != "xyes" ]; then
+        if [ "${SSL_MODE}" = "lets-encrypt" ]; then
+          if [ "${USE_LETSENCRYPT_ROUTE53}" = "yes" ]; then
             grep -q "aws_credentials" ${P_DIR}/top.sls || echo "    - aws_credentials" >> ${P_DIR}/top.sls
           fi
           grep -q "letsencrypt"     ${P_DIR}/top.sls || echo "    - letsencrypt" >> ${P_DIR}/top.sls
@@ -733,6 +783,12 @@ echo '\pset pager off' >> /root/.psqlrc
 # Now run the install
 salt-call --local state.apply -l ${LOG_LEVEL}
 
+# Finally, make sure that /etc/hosts is not overwritten on reboot
+if [ -d /etc/cloud/cloud.cfg.d ]; then
+  # TODO: will this work on CentOS?
+  sed -i 's/^manage_etc_hosts: true/#manage_etc_hosts: true/g' /etc/cloud/cloud.cfg.d/*
+fi
+
 # FIXME! #16992 Temporary fix for psql call in arvados-api-server
 if [ "x${DELETE_PSQL}" = "xyes" ]; then
   echo "Removing .psql file"
index a47294b3bd15c9874803c5c4aef9d2e765d83afb..cf43273a14d584b390079400b096f12ec1e2d683 100755 (executable)
@@ -37,10 +37,6 @@ fi
 
 echo "Arvados project uuid is '${project_uuid}'"
 
-echo "Uploading arvados/jobs' docker image to the project"
-VERSION="2.1.1"
-arv-keepdocker --pull arvados/jobs "${VERSION}" --project-uuid "${project_uuid}"
-
 # Create the initial user
 echo "Creating initial user '__INITIAL_USER__'"
 user_uuid=$(arv --format=uuid user list --filters '[["email", "=", "__INITIAL_USER_EMAIL__"], ["username", "=", "__INITIAL_USER__"]]')